Files
jesse-trading/jesse/research/backtest.py
2022-02-09 17:28:12 +01:00

151 lines
4.9 KiB
Python

from typing import List, Dict
from jesse.services import charts
def backtest(
config: dict,
routes: List[Dict[str, str]],
extra_routes: List[Dict[str, str]],
candles: dict,
hyperparameters: dict = None
) -> dict:
"""
An isolated backtest() function which is perfect for using in research, and AI training
such as our own optimization mode. Because of it being a pure function, it can be used
in Python's multiprocessing without worrying about pickling issues.
Example `config`:
{
'starting_balance': 5_000,
'fee': 0.001,
'futures_leverage': 3,
'futures_leverage_mode': 'cross',
'exchange': 'Binance',
'settlement_currency': 'USDT',
'warm_up_candles': 100
}
Example `route`:
[{'exchange': 'Binance', 'strategy': 'A1', 'symbol': 'BTC-USDT', 'timeframe': '1m'}]
Example `extra_route`:
[{'exchange': 'Binance', 'symbol': 'BTC-USD', 'timeframe': '3m'}]
Example `candles`:
{
'Binance-BTC-USDT': {
'exchange': 'Binance',
'symbol': 'BTC-USDT',
'candles': np.array([]),
},
}
"""
return _isolated_backtest(config, routes, extra_routes, candles, run_silently=True, hyperparameters=hyperparameters)
def _isolated_backtest(
config: dict,
routes: List[Dict[str, str]],
extra_routes: List[Dict[str, str]],
candles: dict,
run_silently: bool = True,
hyperparameters: dict = None
) -> dict:
from jesse.services.validators import validate_routes
from jesse.modes.backtest_mode import simulator
from jesse.config import config as jesse_config, reset_config
from jesse.routes import router
from jesse.store import store
from jesse.config import set_config
from jesse.services import metrics
from jesse.services import required_candles
import jesse.helpers as jh
jesse_config['app']['trading_mode'] = 'backtest'
# inject (formatted) configuration values
set_config(_format_config(config))
# set routes
router.initiate(routes, extra_routes)
# register_custom_exception_handler()
validate_routes(router)
# TODO: further validate routes and allow only one exchange
# TODO: validate the name of the exchange in the config and the route? or maybe to make sure it's a supported exchange
# initiate candle store
store.candles.init_storage(5000)
# divide candles into warm_up_candles and trading_candles and then inject warm_up_candles
max_timeframe = jh.max_timeframe(jesse_config['app']['considering_timeframes'])
warm_up_num = config['warm_up_candles'] * jh.timeframe_to_one_minutes(max_timeframe)
trading_candles = candles
if warm_up_num != 0:
for c in jesse_config['app']['considering_candles']:
key = jh.key(c[0], c[1])
# update trading_candles
trading_candles[key]['candles'] = candles[key]['candles'][warm_up_num:]
# inject warm-up candles
required_candles.inject_required_candles_to_store(
candles[key]['candles'][:warm_up_num],
c[0],
c[1]
)
# run backtest simulation
simulator(trading_candles, run_silently, hyperparameters)
result = {
'metrics': {'total': 0, 'win_rate': 0, 'net_profit_percentage': 0},
'charts': None,
'logs': None,
}
if store.completed_trades.count > 0:
# add metrics
result['metrics'] = metrics.trades(store.completed_trades.trades, store.app.daily_balance)
# add charts
result['charts'] = charts.portfolio_vs_asset_returns()
# add logs
result['logs'] = store.logs.info
# reset store and config so rerunning would be flawlessly possible
reset_config()
store.reset()
return result
def _format_config(config):
"""
Jesse's required format for user_config is different from what this function accepts (so it
would be easier to write for the researcher). Hence we need to reformat the config_dict:
"""
return {
'exchanges': {
config['exchange']: {
'balance': config['starting_balance'],
'fee': config['fee'],
'futures_leverage': config['futures_leverage'],
'futures_leverage_mode': config['futures_leverage_mode'],
'name': config['exchange'],
'settlement_currency': config['settlement_currency']
}
},
'logging': {
'balance_update': True,
'order_cancellation': True,
'order_execution': True,
'order_submission': True,
'position_closed': True,
'position_increased': True,
'position_opened': True,
'position_reduced': True,
'shorter_period_candles': False,
'trading_candles': True
},
'warm_up_candles': config['warm_up_candles']
}