def _simulate_price_change_effect(real_candle: np.ndarray, exchange: str, symbol: str) -> None: orders = store.orders.get_orders(exchange, symbol) current_temp_candle = real_candle.copy() executed_order = False while True: if len(orders) == 0: executed_order = False else: for index, order in enumerate(orders): if index == len(orders) - 1 and not order.is_active: executed_order = False if not order.is_active: continue if candle_includes_price(current_temp_candle, order.price): storable_temp_candle, current_temp_candle = split_candle( current_temp_candle, order.price) store.candles.add_candle(storable_temp_candle, exchange, symbol, '1m', with_execution=False, with_generation=False) p = selectors.get_position(exchange, symbol) p.current_price = storable_temp_candle[2] executed_order = True order.execute() # break from the for loop, we'll try again inside the while # loop with the new current_temp_candle break else: executed_order = False if not executed_order: # add/update the real_candle to the store so we can move on store.candles.add_candle(real_candle, exchange, symbol, '1m', with_execution=False, with_generation=False) p = selectors.get_position(exchange, symbol) if p: p.current_price = real_candle[2] break _check_for_liquidations(real_candle, exchange, symbol)
def execute(self, silent=False) -> None: if self.is_canceled or self.is_executed: return self.executed_at = jh.now_to_timestamp() self.status = order_statuses.EXECUTED if jh.is_live(): self.save() if not silent: txt = f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}' if self.price: txt += f', ${round(self.price, 2)}' # log if jh.is_debuggable('order_execution'): logger.info(txt) # notify if jh.is_live(): self.broadcast() if config['env']['notifications']['events']['executed_orders']: notify(txt) p = selectors.get_position(self.exchange, self.symbol) if p: p._on_executed_order(self) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_execution(self)
def available_margin(self, symbol: str = '') -> float: # a temp which gets added to per each asset (remember that all future assets use the same currency for settlement) temp_credits = self.assets[self.settlement_currency] # we need to consider buy and sell orders of ALL pairs # also, consider the value of all open positions for asset in self.assets: if asset == self.settlement_currency: continue position = selectors.get_position(self.name, f"{asset}-{self.settlement_currency}") if position is None: continue if position.is_open: # add unrealized PNL temp_credits += position.pnl # only which of these has actual values, so we can count all of them! sum_buy_orders = (self.buy_orders[asset][:][:, 0] * self.buy_orders[asset][:][:, 1]).sum() sum_sell_orders = (self.sell_orders[asset][:][:, 0] * self.sell_orders[asset][:][:, 1]).sum() if position.is_open: temp_credits -= position.total_cost # Subtract the amount we paid for open orders. Notice that this does NOT include # reduce_only orders so either sum_buy_orders or sum_sell_orders is zero. We also # care about the cost we actually paid for it which takes into account the leverage temp_credits -= max( abs(sum_buy_orders) / self.futures_leverage, abs(sum_sell_orders) / self.futures_leverage ) # count in the leverage return temp_credits * self.futures_leverage
def set_up_without_fee(is_margin_trading=False): reset_config() config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'margin' config['env']['exchanges'][exchanges.SANDBOX]['fee'] = 0 config['env']['exchanges'][exchanges.SANDBOX]['assets'] = [ { 'asset': 'USDT', 'balance': 1000 }, { 'asset': 'BTC', 'balance': 0 }, ] if is_margin_trading: # used only in margin trading config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'margin' config['env']['exchanges'][ exchanges.SANDBOX]['settlement_currency'] = 'USDT' config['app']['trading_mode'] = 'backtest' config['app']['considering_exchanges'] = ['Sandbox'] router.set_routes([(exchanges.SANDBOX, 'BTC-USDT', '5m', 'Test19')]) store.reset(True) global position global exchange position = selectors.get_position(exchanges.SANDBOX, 'BTC-USDT') position.current_price = 50 exchange = selectors.get_exchange(exchanges.SANDBOX)
def cancel(self): """ :return: """ if self.is_canceled or self.is_executed: return self.canceled_at = jh.now() self.status = order_statuses.CANCELED if jh.is_debuggable('order_cancellation'): logger.info( 'CANCELED order: {}, {}, {}, {}, ${}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2) ) ) # notify if jh.is_live() and config['env']['notifications']['events']['cancelled_orders']: notify( 'CANCELED order: {}, {}, {}, {}, {}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2) ) ) p = selectors.get_position(self.exchange, self.symbol) if p: p._on_canceled_order(self)
def test_backtesting_three_routes(): reset_config() router.set_routes([ (exchanges.SANDBOX, 'BTCUSD', timeframes.MINUTE_5, 'Test19'), (exchanges.SANDBOX, 'ETHUSD', timeframes.MINUTE_5, 'Test19'), (exchanges.SANDBOX, 'XRPUSD', timeframes.MINUTE_15, 'Test19'), ]) store.reset(True) candles = {} routes = router.routes for r in routes: key = jh.key(r.exchange, r.symbol) candles[key] = { 'exchange': r.exchange, 'symbol': r.symbol, 'candles': fake_range_candle(5 * 3 * 20) } # assert that strategy hasn't been initiated before running backtest_mode() assert r.strategy is None # run backtest (dates are fake just to pass) backtest_mode.run('2019-04-01', '2019-04-02', candles) # there must be three positions present with the updated current_price assert len(store.positions.storage) == 3 for r in routes: # r3's '15m' timeframe makes r1 and r2 to support # '15' timeframe as well. r1 and r2 also make r3 # to support '5m' timeframe also. r_one_min = store.candles.get_candles(r.exchange, r.symbol, '1m') r_five_min = store.candles.get_candles(r.exchange, r.symbol, '5m') r_fifteen_min = store.candles.get_candles(r.exchange, r.symbol, '15m') # assert the count of present candles assert len(r_one_min) == (5 * 3) * 20 assert len(r_five_min) == 20 * 3 assert len(r_fifteen_min) == 20 r_first_1 = r_one_min[0] r_last_1 = r_one_min[-1] r_first_5 = r_five_min[0] r_last_5 = r_five_min[-1] r_first_15 = r_fifteen_min[0] r_last_15 = r_fifteen_min[-1] # assert timestamps assert r_first_1[0] == r_first_5[0] assert r_last_1[0] == (r_last_5[0] + 60000 * 4) assert r_last_5[0] == (r_last_15[0] + 60000 * 10) # assert positions p = selectors.get_position(r.exchange, r.symbol) assert p.is_close is True last_candle = store.candles.get_candles(r.exchange, r.symbol, '1m')[-1] assert p.current_price == last_candle[2] # assert that the strategy has been initiated assert r.strategy is not None
def _check_for_liquidations(candle: np.ndarray, exchange: str, symbol: str) -> None: p: Position = selectors.get_position(exchange, symbol) if not p: return # for now, we only support the isolated mode: if p.mode != 'isolated': return if candle_includes_price(candle, p.liquidation_price): closing_order_side = jh.closing_side(p.type) # create the market order that is used as the liquidation order order = Order({ 'id': jh.generate_unique_id(), 'symbol': symbol, 'exchange': exchange, 'side': closing_order_side, 'type': order_types.MARKET, 'flag': order_flags.REDUCE_ONLY, 'qty': jh.prepare_qty(p.qty, closing_order_side), 'price': p.bankruptcy_price, 'role': order_roles.CLOSE_POSITION }) store.orders.add_order(order) store.app.total_liquidations += 1 logger.info(f'{p.symbol} liquidated at {p.liquidation_price}') order.execute()
def execute(self): if self.is_canceled or self.is_executed: return self.executed_at = jh.now_to_timestamp() self.status = order_statuses.EXECUTED # log if jh.is_debuggable('order_execution'): logger.info('EXECUTED order: {}, {}, {}, {}, ${}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2))) # notify if jh.is_live( ) and config['env']['notifications']['events']['executed_orders']: notify('EXECUTED order: {}, {}, {}, {}, {}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2))) p = selectors.get_position(self.exchange, self.symbol) if p: p._on_executed_order(self) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_execution(self)
def available_margin(self, symbol: str = '') -> float: temp_credit = self.assets[self.settlement_currency] * self.futures_leverage # we need to consider buy and sell orders of ALL pairs # also, consider the value of all open positions for asset in self.assets: if asset == self.settlement_currency: continue position = selectors.get_position(self.name, asset + "-" + self.settlement_currency) if position is None: continue if position.is_open: # add unrealized PNL temp_credit += position.pnl # subtract worst scenario orders' used margin sum_buy_orders = (self.buy_orders[asset][:][:, 0] * self.buy_orders[asset][:][:, 1]).sum() sum_sell_orders = (self.sell_orders[asset][:][:, 0] * self.sell_orders[asset][:][:, 1]).sum() if position.is_open: if position.type == 'long': sum_buy_orders += position.value else: sum_sell_orders -= abs(position.value) temp_credit -= max(abs(sum_buy_orders), abs(sum_sell_orders)) return temp_credit
def test_strategy_properties(): set_up([ (exchanges.SANDBOX, 'ETHUSDT', timeframes.MINUTE_5, 'Test19'), (exchanges.SANDBOX, 'BTCUSDT', timeframes.MINUTE_5, 'Test19'), ]) candles = {} routes = router.routes for r in routes: key = jh.key(r.exchange, r.symbol) candles[key] = { 'exchange': r.exchange, 'symbol': r.symbol, 'candles': fake_range_candle((5 * 3) * 20) } # run backtest (dates are fake just to pass) backtest_mode.run('2019-04-01', '2019-04-02', candles) for r in routes: s: Strategy = r.strategy assert s.name == r.strategy_name assert s.symbol == r.symbol assert s.exchange == r.exchange assert s.timeframe == r.timeframe assert s.trade is None assert s._is_executing is False assert s._is_initiated is True np.testing.assert_equal(s.current_candle, store.candles.get_current_candle(r.exchange, r.symbol, r.timeframe)) np.testing.assert_equal(s.candles, store.candles.get_candles(r.exchange, r.symbol, r.timeframe)) assert s.position == selectors.get_position(r.exchange, r.symbol) assert s.orders == store.orders.get_orders(r.exchange, r.symbol)
def set_up_with_fee(is_futures_trading=False): reset_config() config['env']['exchanges'][exchanges.SANDBOX]['fee'] = 0.002 config['env']['exchanges'][exchanges.SANDBOX]['assets'] = [ { 'asset': 'USDT', 'balance': 1000 }, { 'asset': 'BTC', 'balance': 0 }, ] if is_futures_trading: # used only in futures trading config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'futures' else: config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'spot' config['env']['exchanges'][ exchanges.SANDBOX]['settlement_currency'] = 'USDT' config['app']['trading_mode'] = 'backtest' config['app']['considering_exchanges'] = ['Sandbox'] router.set_routes([(exchanges.SANDBOX, 'BTC-USDT', '5m', 'Test19')]) store.reset(True) global position global exchange global broker position = selectors.get_position(exchanges.SANDBOX, 'BTC-USDT') position.current_price = 50 exchange = selectors.get_exchange(exchanges.SANDBOX) broker = Broker(position, exchanges.SANDBOX, 'BTC-USDT', timeframes.MINUTE_5)
def _init_objects(self): """ This method gets called after right creating the Strategy object. It is just a workaround as a part of not being able to set them inside self.__init__() for the purpose of removing __init__() methods from strategies. """ self.position = selectors.get_position(self.exchange, self.symbol) self.broker = Broker(self.position, self.exchange, self.symbol, self.timeframe)
def test_backtesting_one_route(): reset_config() router.set_routes([ (exchanges.SANDBOX, 'BTC-USDT', timeframes.MINUTE_5, 'Test19') ]) config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'margin' store.reset(True) candles = {} key = jh.key(exchanges.SANDBOX, 'BTC-USDT') candles[key] = { 'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'candles': fake_range_candle(5 * 20) } routes = router.routes # assert that strategy hasn't been initiated before running backtest_mode() assert routes[0].strategy is None # run backtest (dates are fake just to pass) backtest_mode.run('2019-04-01', '2019-04-02', candles) one_min = store.candles.get_candles(exchanges.SANDBOX, 'BTC-USDT', '1m') five_min = store.candles.get_candles(exchanges.SANDBOX, 'BTC-USDT', '5m') # assert the count of present candles assert len(five_min) == 20 assert len(one_min) == 20 * 5 first_1 = one_min[0] last_1 = one_min[-1] first_5 = five_min[0] last_5 = five_min[-1] # assert time in store assert store.app.time == last_1[0] + 60000 # assert timestamps assert first_1[0] == first_5[0] assert last_1[0] == (last_5[0] + 60000 * 4) # there must be only one positions present assert len(store.positions.storage) == 1 p = selectors.get_position(exchanges.SANDBOX, 'BTC-USDT') assert p.is_close assert p.current_price == last_1[2] assert p.current_price == last_5[2] # assert routes assert len(routes) == 1 assert routes[0].exchange == exchanges.SANDBOX assert routes[0].symbol == 'BTC-USDT' assert routes[0].timeframe == '5m' assert routes[0].strategy_name == 'Test19' # assert that the strategy has been initiated assert routes[0].strategy is not None
def update_position(exchange: str, symbol: str, candle: np.ndarray): # get position object p = selectors.get_position(exchange, symbol) # for extra_route candles, p == None, hence no further action is required if p is None: return # update position.current_price p.current_price = jh.round_price_for_live_mode(candle[2], candle[2])
def _init_objects(self) -> None: """ This method gets called after right creating the Strategy object. It is just a workaround as a part of not being able to set them inside self.__init__() for the purpose of removing __init__() methods from strategies. """ self.position = selectors.get_position(self.exchange, self.symbol) self.broker = Broker(self.position, self.exchange, self.symbol, self.timeframe) if self.hp is None and len(self.hyperparameters()) > 0: self.hp = {} for dna in self.hyperparameters(): self.hp[dna['name']] = dna['default']
def update_position(exchange: str, symbol: str, candle: np.ndarray) -> None: # get position object p = selectors.get_position(exchange, symbol) # for extra_route candles, p == None, hence no further action is required if p is None: return price_precision = 0 if jh.is_live(): price_precision = selectors.get_exchange(exchange).vars['precisions'][symbol]['price_precision'] # update position.current_price p.current_price = jh.round_price_for_live_mode(candle[2], candle[2], price_precision)
def __init__(self, attributes=None): # id generated by Jesse for database usage self.id = '' # id generated by market, used in live-trade mode self.exchange_id = '' # some exchanges might require even further info self.vars = {} self.symbol = '' self.exchange = '' self.side = '' self.type = '' self.flag = '' self.qty = 0 self.price = 0 self.status = order_statuses.ACTIVE self.created_at = None self.executed_at = None self.canceled_at = None self.role = None if attributes is None: attributes = {} for a in attributes: setattr(self, a, attributes[a]) if self.created_at is None: self.created_at = jh.now_to_timestamp() p = selectors.get_position(self.exchange, self.symbol) if p: if jh.is_live() and config['env']['notifications']['events']['submitted_orders']: self.notify_submission() if jh.is_debuggable('order_submission'): logger.info( '{} order: {}, {}, {}, {}, ${}'.format( 'QUEUED' if self.is_queued else 'SUBMITTED', self.symbol, self.type, self.side, self.qty, round(self.price, 2) ) ) p._on_opened_order(self) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_submission(self)
def set_up_with_fee(): reset_config() config['env']['exchanges'][exchanges.SANDBOX]['fee'] = 0.002 config['env']['exchanges'][exchanges.SANDBOX]['starting_balance'] = 1000 config['app']['trading_mode'] = 'backtest' config['app']['considering_exchanges'] = ['Sandbox'] router.set_routes([(exchanges.SANDBOX, 'BTCUSD', '5m', 'Test19')]) store.reset(True) global position global exchange global broker position = selectors.get_position(exchanges.SANDBOX, 'BTCUSD') position.current_price = 50 exchange = selectors.get_exchange(exchanges.SANDBOX) broker = Broker(position, exchanges.SANDBOX, 'BTCUSD', timeframes.MINUTE_5)
def __init__(self, attributes=None): # id generated by Jesse for database usage self.id = '' # id generated by market self.exchange_id = '' self.symbol = '' self.exchange = '' self.side = '' self.type = '' self.flag = '' self.qty = 0 self.price = 0 self.status = order_statuses.ACTIVE self.created_at = None self.executed_at = None self.canceled_at = None self.role = None if attributes is None: attributes = {} for a in attributes: setattr(self, a, attributes[a]) if self.created_at is None: self.created_at = jh.now() p = selectors.get_position(self.exchange, self.symbol) if p: p._on_opened_order(self) if jh.is_live() and config['env']['notifications']['events']['submitted_orders']: self.notify_submission() if jh.is_debuggable('order_submission'): logger.info( '{} order: {}, {}, {}, {}, ${}'.format( 'QUEUED' if self.is_queued else 'SUBMITTED', self.symbol, self.type, self.side, self.qty, round(self.price, 2) ) )
def test_strategy_properties(): two_routes_backtest('Test19', 'Test19') for r in router.routes: s: Strategy = r.strategy assert s.name == r.strategy_name assert s.symbol == r.symbol assert s.exchange == r.exchange assert s.timeframe == r.timeframe assert s.trade is None assert s._is_executing is False assert s._is_initiated is True np.testing.assert_equal( s.current_candle, store.candles.get_current_candle(r.exchange, r.symbol, r.timeframe)) np.testing.assert_equal( s.candles, store.candles.get_candles(r.exchange, r.symbol, r.timeframe)) assert s.position == selectors.get_position(r.exchange, r.symbol) assert s.orders == store.orders.get_orders(r.exchange, r.symbol)
def tradable_balance(self, symbol=''): if self.type == 'spot': if symbol == '': raise ValueError quote_asset = jh.quote_asset(symbol) return self.available_assets[quote_asset] else: temp_credit = self.assets[self.settlement_currency] # we need to consider buy and sell orders of ALL pairs # also, consider the value of all open positions for asset in self.assets: if asset == self.settlement_currency: continue position = selectors.get_position( self.name, asset + "-" + self.settlement_currency) if position is None: continue if position.is_open: # add unrealized PNL temp_credit += position.pnl # subtract worst scenario orders' used margin sum_buy_orders = (self.buy_orders[asset][:][:, 0] * self.buy_orders[asset][:][:, 1]).sum() sum_sell_orders = (self.sell_orders[asset][:][:, 0] * self.sell_orders[asset][:][:, 1]).sum() if position.is_open: if position.type == 'long': sum_buy_orders += position.value else: sum_sell_orders -= abs(position.value) temp_credit -= max(abs(sum_buy_orders), abs(sum_sell_orders)) return temp_credit
def simulator( candles: dict, run_silently: bool, hyperparameters: dict = None ) -> None: begin_time_track = time.time() key = f"{config['app']['considering_candles'][0][0]}-{config['app']['considering_candles'][0][1]}" first_candles_set = candles[key]['candles'] length = len(first_candles_set) # to preset the array size for performance try: store.app.starting_time = first_candles_set[0][0] except IndexError: raise IndexError('Check your "warm_up_candles" config value') store.app.time = first_candles_set[0][0] # initiate strategies for r in router.routes: # if the r.strategy is str read it from file if isinstance(r.strategy_name, str): StrategyClass = jh.get_strategy_class(r.strategy_name) # else it is a class object so just use it else: StrategyClass = r.strategy_name try: r.strategy = StrategyClass() except TypeError: raise exceptions.InvalidStrategy( "Looks like the structure of your strategy directory is incorrect. Make sure to include the strategy INSIDE the __init__.py file." "\nIf you need working examples, check out: https://github.com/jesse-ai/example-strategies" ) except: raise r.strategy.name = r.strategy_name r.strategy.exchange = r.exchange r.strategy.symbol = r.symbol r.strategy.timeframe = r.timeframe # read the dna from strategy's dna() and use it for injecting inject hyperparameters # first convert DNS string into hyperparameters if len(r.strategy.dna()) > 0 and hyperparameters is None: hyperparameters = jh.dna_to_hp(r.strategy.hyperparameters(), r.strategy.dna()) # inject hyperparameters sent within the optimize mode if hyperparameters is not None: r.strategy.hp = hyperparameters # init few objects that couldn't be initiated in Strategy __init__ # it also injects hyperparameters into self.hp in case the route does not uses any DNAs r.strategy._init_objects() selectors.get_position(r.exchange, r.symbol).strategy = r.strategy # add initial balance save_daily_portfolio_balance() progressbar = Progressbar(length, step=60) for i in range(length): # update time store.app.time = first_candles_set[i][0] + 60_000 # add candles for j in candles: short_candle = candles[j]['candles'][i] if i != 0: previous_short_candle = candles[j]['candles'][i - 1] short_candle = _get_fixed_jumped_candle(previous_short_candle, short_candle) exchange = candles[j]['exchange'] symbol = candles[j]['symbol'] store.candles.add_candle(short_candle, exchange, symbol, '1m', with_execution=False, with_generation=False) # print short candle if jh.is_debuggable('shorter_period_candles'): print_candle(short_candle, True, symbol) _simulate_price_change_effect(short_candle, exchange, symbol) # generate and add candles for bigger timeframes for timeframe in config['app']['considering_timeframes']: # for 1m, no work is needed if timeframe == '1m': continue count = jh.timeframe_to_one_minutes(timeframe) # until = count - ((i + 1) % count) if (i + 1) % count == 0: generated_candle = generate_candle_from_one_minutes( timeframe, candles[j]['candles'][(i - (count - 1)):(i + 1)]) store.candles.add_candle(generated_candle, exchange, symbol, timeframe, with_execution=False, with_generation=False) # update progressbar if not run_silently and i % 60 == 0: progressbar.update() sync_publish('progressbar', { 'current': progressbar.current, 'estimated_remaining_seconds': progressbar.estimated_remaining_seconds }) # now that all new generated candles are ready, execute for r in router.routes: count = jh.timeframe_to_one_minutes(r.timeframe) # 1m timeframe if r.timeframe == timeframes.MINUTE_1: r.strategy._execute() elif (i + 1) % count == 0: # print candle if jh.is_debuggable('trading_candles'): print_candle(store.candles.get_current_candle(r.exchange, r.symbol, r.timeframe), False, r.symbol) r.strategy._execute() # now check to see if there's any MARKET orders waiting to be executed store.orders.execute_pending_market_orders() if i != 0 and i % 1440 == 0: save_daily_portfolio_balance() if not run_silently: # print executed time for the backtest session finish_time_track = time.time() sync_publish('alert', { 'message': f'Successfully executed backtest simulation in: {round(finish_time_track - begin_time_track, 2)} seconds', 'type': 'success' }) for r in router.routes: r.strategy._terminate() store.orders.execute_pending_market_orders() # now that backtest is finished, add finishing balance save_daily_portfolio_balance()
def simulator(candles, hyper_parameters=None): begin_time_track = time.time() key = '{}-{}'.format(config['app']['trading_exchanges'][0], config['app']['trading_symbols'][0]) first_candles_set = candles[key]['candles'] length = len(first_candles_set) # to preset the array size for performance store.app.starting_time = first_candles_set[0][0] # initiate strategies for r in router.routes: StrategyClass = jh.get_strategy_class(r.strategy_name) # convert DNS string into hyper_parameters if r.dna and hyper_parameters is None: hyper_parameters = jh.dna_to_hp(StrategyClass.hyper_parameters(), r.dna) r.strategy = StrategyClass() r.strategy.name = r.strategy_name r.strategy.exchange = r.exchange r.strategy.symbol = r.symbol r.strategy.timeframe = r.timeframe # init few objects that couldn't be initiated in Strategy __init__ r.strategy._init_objects() # inject hyper parameters (used for optimize_mode) if hyper_parameters is not None: r.strategy.hp = hyper_parameters selectors.get_position(r.exchange, r.symbol).strategy = r.strategy # add initial balance _save_daily_portfolio_balance() with click.progressbar(length=length, label='Executing simulation...') as progressbar: for i in range(length): # update time store.app.time = first_candles_set[i][0] + 60_000 # add candles for j in candles: short_candle = candles[j]['candles'][i] exchange = candles[j]['exchange'] symbol = candles[j]['symbol'] store.candles.add_candle(short_candle, exchange, symbol, '1m', with_execution=False, with_generation=False) # print short candle if jh.is_debuggable('shorter_period_candles'): print_candle(short_candle, True, symbol) _simulate_price_change_effect(short_candle, exchange, symbol) # generate and add candles for bigger timeframes for timeframe in config['app']['considering_timeframes']: # for 1m, no work is needed if timeframe == '1m': continue count = jh.timeframe_to_one_minutes(timeframe) until = count - ((i + 1) % count) if (i + 1) % count == 0: generated_candle = generate_candle_from_one_minutes( timeframe, candles[j]['candles'][(i - (count - 1)):(i + 1)]) store.candles.add_candle(generated_candle, exchange, symbol, timeframe, with_execution=False, with_generation=False) # update progressbar if not jh.is_debugging() and not jh.should_execute_silently( ) and i % 60 == 0: progressbar.update(60) # now that all new generated candles are ready, execute for r in router.routes: count = jh.timeframe_to_one_minutes(r.timeframe) # 1m timeframe if r.timeframe == timeframes.MINUTE_1: r.strategy._execute() elif (i + 1) % count == 0: # print candle if jh.is_debuggable('trading_candles'): print_candle( store.candles.get_current_candle( r.exchange, r.symbol, r.timeframe), False, r.symbol) r.strategy._execute() # now check to see if there's any MARKET orders waiting to be executed store.orders.execute_pending_market_orders() if i != 0 and i % 1440 == 0: _save_daily_portfolio_balance() if not jh.should_execute_silently(): if jh.is_debuggable('trading_candles') or jh.is_debuggable( 'shorter_period_candles'): print('\n') # print executed time for the backtest session finish_time_track = time.time() print( 'Executed backtest simulation in: ', '{} seconds'.format(round(finish_time_track - begin_time_track, 2))) for r in router.routes: r.strategy._terminate() # now that backtest is finished, add finishing balance _save_daily_portfolio_balance()
def simulator(candles: Dict[str, Dict[str, Union[str, np.ndarray]]], hyperparameters=None) -> None: begin_time_track = time.time() key = '{}-{}'.format(config['app']['considering_candles'][0][0], config['app']['considering_candles'][0][1]) first_candles_set = candles[key]['candles'] length = len(first_candles_set) # to preset the array size for performance store.app.starting_time = first_candles_set[0][0] store.app.time = first_candles_set[0][0] # initiate strategies for r in router.routes: StrategyClass = jh.get_strategy_class(r.strategy_name) try: r.strategy = StrategyClass() except TypeError: raise exceptions.InvalidStrategy( "Looks like the structure of your strategy directory is incorrect. Make sure to include the strategy INSIDE the __init__.py file." "\nIf you need working examples, check out: https://github.com/jesse-ai/example-strategies" ) except: raise r.strategy.name = r.strategy_name r.strategy.exchange = r.exchange r.strategy.symbol = r.symbol r.strategy.timeframe = r.timeframe # inject hyper parameters (used for optimize_mode) # convert DNS string into hyperparameters if r.dna and hyperparameters is None: hyperparameters = jh.dna_to_hp(r.strategy.hyperparameters(), r.dna) # inject hyperparameters sent within the optimize mode if hyperparameters is not None: r.strategy.hp = hyperparameters # init few objects that couldn't be initiated in Strategy __init__ # it also injects hyperparameters into self.hp in case the route does not uses any DNAs r.strategy._init_objects() selectors.get_position(r.exchange, r.symbol).strategy = r.strategy # add initial balance save_daily_portfolio_balance() with click.progressbar(length=length, label='Executing simulation...') as progressbar: for i in range(length): # update time store.app.time = first_candles_set[i][0] + 60_000 # add candles for j in candles: short_candle = candles[j]['candles'][i] if i != 0: previous_short_candle = candles[j]['candles'][i - 1] short_candle = _get_fixed_jumped_candle( previous_short_candle, short_candle) exchange = candles[j]['exchange'] symbol = candles[j]['symbol'] store.candles.add_candle(short_candle, exchange, symbol, '1m', with_execution=False, with_generation=False) # print short candle if jh.is_debuggable('shorter_period_candles'): print_candle(short_candle, True, symbol) _simulate_price_change_effect(short_candle, exchange, symbol) # generate and add candles for bigger timeframes for timeframe in config['app']['considering_timeframes']: # for 1m, no work is needed if timeframe == '1m': continue count = jh.timeframe_to_one_minutes(timeframe) until = count - ((i + 1) % count) if (i + 1) % count == 0: generated_candle = generate_candle_from_one_minutes( timeframe, candles[j]['candles'][(i - (count - 1)):(i + 1)]) store.candles.add_candle(generated_candle, exchange, symbol, timeframe, with_execution=False, with_generation=False) # update progressbar if not jh.is_debugging() and not jh.should_execute_silently( ) and i % 60 == 0: progressbar.update(60) # now that all new generated candles are ready, execute for r in router.routes: count = jh.timeframe_to_one_minutes(r.timeframe) # 1m timeframe if r.timeframe == timeframes.MINUTE_1: r.strategy._execute() elif (i + 1) % count == 0: # print candle if jh.is_debuggable('trading_candles'): print_candle( store.candles.get_current_candle( r.exchange, r.symbol, r.timeframe), False, r.symbol) r.strategy._execute() # now check to see if there's any MARKET orders waiting to be executed store.orders.execute_pending_market_orders() if i != 0 and i % 1440 == 0: save_daily_portfolio_balance() if not jh.should_execute_silently(): if jh.is_debuggable('trading_candles') or jh.is_debuggable( 'shorter_period_candles'): print('\n') # print executed time for the backtest session finish_time_track = time.time() print( 'Executed backtest simulation in: ', '{} seconds'.format(round(finish_time_track - begin_time_track, 2))) for r in router.routes: r.strategy._terminate() store.orders.execute_pending_market_orders() # now that backtest is finished, add finishing balance save_daily_portfolio_balance()
def test_backtesting_three_routes(): reset_config() routes = [{ 'exchange': exchanges.SANDBOX, 'symbol': 'BTC-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test19' }, { 'exchange': exchanges.SANDBOX, 'symbol': 'ETH-USDT', 'timeframe': timeframes.MINUTE_5, 'strategy': 'Test19' }, { 'exchange': exchanges.SANDBOX, 'symbol': 'XRP-USDT', 'timeframe': timeframes.MINUTE_15, 'strategy': 'Test19' }] config['env']['exchanges'][exchanges.SANDBOX]['type'] = 'futures' candles = {} for r in routes: key = jh.key(r['exchange'], r['symbol']) candles[key] = { 'exchange': r['exchange'], 'symbol': r['symbol'], 'candles': range_candles(5 * 3 * 20) } # run backtest (dates are fake just to pass) backtest_mode.run(False, {}, routes, [], '2019-04-01', '2019-04-02', candles) # there must be three positions present with the updated current_price assert len(store.positions.storage) == 3 for r in router.routes: # r3's '15m' timeframe makes r1 and r2 to support # '15' timeframe as well. r1 and r2 also make r3 # to support '5m' timeframe also. r_one_min = store.candles.get_candles(r.exchange, r.symbol, '1m') r_five_min = store.candles.get_candles(r.exchange, r.symbol, '5m') r_fifteen_min = store.candles.get_candles(r.exchange, r.symbol, '15m') # assert the count of present candles assert len(r_one_min) == (5 * 3) * 20 assert len(r_five_min) == 20 * 3 assert len(r_fifteen_min) == 20 r_first_1 = r_one_min[0] r_last_1 = r_one_min[-1] r_first_5 = r_five_min[0] r_last_5 = r_five_min[-1] r_last_15 = r_fifteen_min[-1] # assert timestamps assert r_first_1[0] == r_first_5[0] assert r_last_1[0] == (r_last_5[0] + 60000 * 4) assert r_last_5[0] == (r_last_15[0] + 60000 * 10) # assert positions p = selectors.get_position(r.exchange, r.symbol) assert p.is_close is True last_candle = store.candles.get_candles(r.exchange, r.symbol, '1m')[-1] assert p.current_price == last_candle[2] # assert that the strategy has been initiated assert r.strategy is not None