def test_dna_to_hp(): strategy_hp = [ {'name': 'hp1', 'type': float, 'min': 0.01, 'max': 1.0, 'default': 0.09}, {'name': 'hp2', 'type': int, 'min': 1, 'max': 10, 'default': 2}, ] dna = ".6" assert jh.dna_to_hp(strategy_hp, dna) == {'hp1': 0.08518987341772151, 'hp2': 3}
def run(dna=False): """ :param dna: """ # # # # # # # # # # # # # # # # # # # # # # # # # trading routes # # # # # # # # # # # # # # # # # # # # # # # # arr = [] if not dna: print( jh.color('{}{}{}'.format('#' * 25, ' Trading Routes ', '#' * 25), 'blue')) arr.append(('exchange', 'symbol', 'timeframe', 'strategy name', 'DNA')) else: print(jh.color('Translated DNAs into hyper-parameters:', 'blue')) translated_DNAs_count = 0 for i, r in enumerate(router.routes): if dna and r.dna: translated_DNAs_count += 1 StrategyClass = jh.get_strategy_class(r.strategy_name) hyper_parameters = jh.dna_to_hp(StrategyClass.hyper_parameters(), r.dna) table.key_value(hyper_parameters.items(), r.strategy_name, uppercase_title=False) print('\n') else: arr.append( [r.exchange, r.symbol, r.timeframe, r.strategy_name, r.dna]) if not dna: table.multi_value(arr) print('\n') else: if not translated_DNAs_count: print('No DNA string found.') # # # # # # # # # # # # # # # # # # # # # # # # # extra_candles # # # # # # # # # # # # # # # # # # # # # # # # if not dna: print( jh.color('{}{}{}'.format('#' * 25, ' Extra Candles ', '#' * 25), 'blue')) arr = [('exchange', 'symbol', 'timeframe')] for i, r in enumerate(router.extra_candles): arr.append([r[0], r[1], r[2]]) table.multi_value(arr) print('\n')
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 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 fitness(self, dna) -> tuple: hp = jh.dna_to_hp(self.strategy_hp, dna) # init candle store store.candles.init_storage(5000) # inject required TRAINING candles to the candle store for num, c in enumerate(config['app']['considering_candles']): required_candles.inject_required_candles_to_store( self.training_initial_candles[num], c[0], c[1]) # run backtest simulation simulator(self.training_candles, hp) log = '' # TODO: some of these have to be dynamic based on how many days it's trading for like for example "total" # I'm guessing we should accept "optimal" total from command line if store.completed_trades.count > 5: training_data = stats.trades(store.completed_trades.trades, store.app.daily_balance) total_effect_rate = log10(training_data['total']) / log10( self.optimal_total) if total_effect_rate > 1: total_effect_rate = 1 win_rate = training_data['win_rate'] ratio_config = jh.get_config('env.optimization.ratio', 'sharpe') if ratio_config == 'sharpe': ratio = training_data['sharpe_ratio'] elif ratio_config == 'calmar': ratio = training_data['calmar_ratio'] elif ratio_config == 'sortiono': ratio = training_data['sortino_ratio'] elif ratio_config == 'omega': ratio = training_data['omega_ratio'] else: raise ValueError( 'The entered ratio configuration `{}` for the optimization is unknown. Choose between sharpe, calmar, sortino and omega.' .format(ratio_config)) if ratio < 0: score = 0.0001 # reset store store.reset() return score, log ratio_normalized = jh.normalize(ratio, -.5, 4) # log for debugging/monitoring log = 'win-rate: {}%, total: {}, PNL: {}%'.format( int(win_rate * 100), training_data['total'], round(training_data['net_profit_percentage'], 2), ) score = total_effect_rate * ratio_normalized # perform backtest with testing data. this is using data # model hasn't trained for. if it works well, there is # high change it will do good with future data too. store.reset() store.candles.init_storage(5000) # inject required TESTING candles to the candle store for num, c in enumerate(config['app']['considering_candles']): required_candles.inject_required_candles_to_store( self.testing_initial_candles[num], c[0], c[1]) # run backtest simulation simulator(self.testing_candles, hp) testing_data = stats.trades(store.completed_trades.trades, store.app.daily_balance) # log for debugging/monitoring log += ' || ' if store.completed_trades.count > 0: log += 'win-rate: {}%, total: {}, PNL: {}%'.format( int(testing_data['win_rate'] * 100), testing_data['total'], round(testing_data['net_profit_percentage'], 2), ) if testing_data['net_profit_percentage'] > 0 and training_data[ 'net_profit_percentage'] > 0: log = jh.style(log, 'bold') else: log += 'win-rate: -, total: -, PNL%: -' else: score = 0.0001 # reset store store.reset() return score, log
def take_snapshot(self, index: int) -> None: """ stores a snapshot of the fittest population members into a file. """ study_name = f"{self.options['strategy_name']}-{self.options['exchange']}-{ self.options['symbol']}-{self.options['timeframe']}-{self.options['start_date']}-{self.options['finish_date']}" dnas_json = {'snapshot': []} for i in range(30): dnas_json['snapshot'].append( {'iteration': index, 'dna': self.population[i]['dna'], 'fitness': self.population[i]['fitness'], 'training_log': self.population[i]['training_log'], 'testing_log': self.population[i]['testing_log'], 'parameters': jh.dna_to_hp(self.options['strategy_hp'], self.population[i]['dna'])}) path = f'./storage/genetics/{study_name}.txt' os.makedirs('./storage/genetics', exist_ok=True) txt = '' with open(path, 'a', encoding="utf-8") as f: txt += '\n\n' txt += f'# iteration {index}' txt += '\n' for i in range(30): log = f"win-rate: {self.population[i]['training_log']['win-rate']} %, total: {self.population[i]['training_log']['total']}, PNL: {self.population[i]['training_log']['PNL']} % || win-rate: {self.population[i]['testing_log']['win-rate']} %, total: {self.population[i]['testing_log']['total']}, PNL: {self.population[i]['testing_log']['PNL']} %" txt += '\n' txt += f"{i + 1} == {self.population[i]['dna']} == {self.population[i]['fitness']} == {log}" f.write(txt) if self.options['csv']: path = f'storage/genetics/csv/{study_name}.csv' os.makedirs('./storage/genetics/csv', exist_ok=True) exists = os.path.exists(path) df = json_normalize(dnas_json['snapshot']) with open(path, 'a', newline='', encoding="utf-8") as outfile: if not exists: # header of CSV file df.to_csv(outfile, header=True, index=False, encoding='utf-8') df.to_csv(outfile, header=False, index=False, encoding='utf-8') if self.options['json']: path = f'storage/genetics/json/{study_name}.json' os.makedirs('./storage/genetics/json', exist_ok=True) exists = os.path.exists(path) mode = 'r+' if exists else 'w' with open(path, mode, encoding="utf-8") as file: if not exists: snapshots = {"snapshots": []} snapshots["snapshots"].append(dnas_json['snapshot']) json.dump(snapshots, file, ensure_ascii=False) file.write('\n') else: # file exists - append file.seek(0) data = json.load(file) data["snapshots"].append(dnas_json['snapshot']) file.seek(0) json.dump(data, file, ensure_ascii=False) file.write('\n')
def get_fitness(optimization_config: dict, routes: list, extra_routes: list, strategy_hp, dna: str, training_candles, testing_candles, optimal_total) -> tuple: """ Notice that this function is likely to be executed inside workers, hence its inputs must have everything required for it to run. So it cannot access store, config, etc """ hp = jh.dna_to_hp(strategy_hp, dna) # run backtest simulation try: training_data_metrics = isolated_backtest( _formatted_inputs_for_isolated_backtest(optimization_config, routes), routes, extra_routes, training_candles, hyperparameters=hp)['metrics'] except Exception as e: # get the main title of the exception log_text = e log_text = f"Exception in strategy execution:\n {log_text}" logger.log_optimize_mode(log_text) raise e training_log = {'win-rate': None, 'total': None, 'PNL': None} testing_log = {'win-rate': None, 'total': None, 'PNL': None} # TODO: some of these have to be dynamic based on how many days it's trading for like for example "total" if training_data_metrics['total'] > 5: total_effect_rate = log10( training_data_metrics['total']) / log10(optimal_total) total_effect_rate = min(total_effect_rate, 1) ratio_config = jh.get_config('env.optimization.ratio', 'sharpe') if ratio_config == 'sharpe': ratio = training_data_metrics['sharpe_ratio'] ratio_normalized = jh.normalize(ratio, -.5, 5) elif ratio_config == 'calmar': ratio = training_data_metrics['calmar_ratio'] ratio_normalized = jh.normalize(ratio, -.5, 30) elif ratio_config == 'sortino': ratio = training_data_metrics['sortino_ratio'] ratio_normalized = jh.normalize(ratio, -.5, 15) elif ratio_config == 'omega': ratio = training_data_metrics['omega_ratio'] ratio_normalized = jh.normalize(ratio, -.5, 5) elif ratio_config == 'serenity': ratio = training_data_metrics['serenity_index'] ratio_normalized = jh.normalize(ratio, -.5, 15) elif ratio_config == 'smart sharpe': ratio = training_data_metrics['smart_sharpe'] ratio_normalized = jh.normalize(ratio, -.5, 5) elif ratio_config == 'smart sortino': ratio = training_data_metrics['smart_sortino'] ratio_normalized = jh.normalize(ratio, -.5, 15) else: raise ValueError( f'The entered ratio configuration `{ratio_config}` for the optimization is unknown. Choose between sharpe, calmar, sortino, serenity, smart shapre, smart sortino and omega.' ) if ratio < 0: score = 0.0001 logger.log_optimize_mode( f"NEGATIVE RATIO: DNA is not usable => {ratio_config}: {ratio}, total: {training_data_metrics['total']}" ) return score, training_log, testing_log # log for debugging/monitoring training_log = { 'win-rate': int(training_data_metrics['win_rate'] * 100), 'total': training_data_metrics['total'], 'PNL': round(training_data_metrics['net_profit_percentage'], 2) } score = total_effect_rate * ratio_normalized # if score is numpy nan, replace it with 0.0001 if np.isnan(score): logger.log_optimize_mode(f'Score is nan. DNA is invalid') score = 0.0001 # elif jh.is_debugging(): else: logger.log_optimize_mode( f"DNA is usable => {ratio_config}: {round(ratio, 2)}, total: {training_data_metrics['total']}, PNL%: {round(training_data_metrics['net_profit_percentage'], 2)}%, win-rate: {round(training_data_metrics['win_rate']*100, 2)}%" ) # run backtest simulation testing_data_metrics = isolated_backtest( _formatted_inputs_for_isolated_backtest(optimization_config, routes), routes, extra_routes, testing_candles, hyperparameters=hp)['metrics'] # log for debugging/monitoring if testing_data_metrics['total'] > 0: testing_log = { 'win-rate': int(testing_data_metrics['win_rate'] * 100), 'total': testing_data_metrics['total'], 'PNL': round(testing_data_metrics['net_profit_percentage'], 2) } else: logger.log_optimize_mode( f'Less than 5 trades in the training data. DNA is invalid') score = 0.0001 return score, training_log, testing_log
def fitness(self, dna) -> tuple: hp = jh.dna_to_hp(self.strategy_hp, dna) # init candle store store.candles.init_storage(5000) # inject required TRAINING candles to the candle store required_candles.inject_required_candles_to_store( self.required_initial_training_candles, self.exchange, self.symbol) # run backtest simulation simulator(self.training_candles, hp) log = '' # TODO: some of these have to be dynamic based on how many days it's trading for like for example "total" # I'm guessing we should accept "optimal" total from command line if store.completed_trades.count > 5: training_data = stats.trades(store.completed_trades.trades) optimal_expected_total = 100 total = jh.normalize(training_data['total'], 0, 200) total_effect_rate = log10( training_data['total']) / log10(optimal_expected_total) win_rate = training_data['win_rate'] # log for debugging/monitoring log = 'win_rate:[{}-{}], total:[{}-{}], PNL%:[{}], TER:[{}]'.format( round(win_rate, 2), round(training_data['win_rate'], 2), round(total, 2), training_data['total'], round(training_data['pnl_percentage'], 2), round(total_effect_rate, 3)) # the fitness score score = win_rate * total_effect_rate # perform backtest with testing data. this is using data # model hasn't trained for. if it works well, there is # high change it will do good with future data too. store.reset() store.candles.init_storage(5000) # inject required TESTING candles to the candle store required_candles.inject_required_candles_to_store( self.required_initial_testing_candles, self.exchange, self.symbol) # run backtest simulation simulator(self.testing_candles, hp) testing_data = stats.trades(store.completed_trades.trades) # log for debugging/monitoring log += ' | ' log += 'win_rate:[{}], total:[{}], PNL%:[{}]'.format( round(testing_data['win_rate'], 2), testing_data['total'], round(testing_data['pnl_percentage'], 2), ) if testing_data['pnl_percentage'] > 0 and training_data[ 'pnl_percentage'] > 0: log = jh.style(log, 'bold') else: score = 0.0001 # reset store store.reset() return score, log
def fitness(self, dna: str) -> tuple: hp = jh.dna_to_hp(self.strategy_hp, dna) # init candle store store.candles.init_storage(5000) # inject required TRAINING candles to the candle store for num, c in enumerate(config['app']['considering_candles']): required_candles.inject_required_candles_to_store( self.training_initial_candles[num], c[0], c[1] ) # run backtest simulation simulator(self.training_candles, hp) training_data = {'win_rate': None, 'total': None, 'net_profit_percentage': None} testing_data = {'win_rate': None, 'total': None, 'net_profit_percentage': None} # TODO: some of these have to be dynamic based on how many days it's trading for like for example "total" # I'm guessing we should accept "optimal" total from command line if store.completed_trades.count > 5: training_data = stats.trades(store.completed_trades.trades, store.app.daily_balance) total_effect_rate = log10(training_data['total']) / log10(self.optimal_total) total_effect_rate = min(total_effect_rate, 1) ratio_config = jh.get_config('env.optimization.ratio', 'sharpe') if ratio_config == 'sharpe': ratio = training_data['sharpe_ratio'] ratio_normalized = jh.normalize(ratio, -.5, 5) elif ratio_config == 'calmar': ratio = training_data['calmar_ratio'] ratio_normalized = jh.normalize(ratio, -.5, 30) elif ratio_config == 'sortino': ratio = training_data['sortino_ratio'] ratio_normalized = jh.normalize(ratio, -.5, 15) elif ratio_config == 'omega': ratio = training_data['omega_ratio'] ratio_normalized = jh.normalize(ratio, -.5, 5) elif ratio_config == 'serenity': ratio = training_data['serenity_index'] ratio_normalized = jh.normalize(ratio, -.5, 15) elif ratio_config == 'smart sharpe': ratio = training_data['smart_sharpe'] ratio_normalized = jh.normalize(ratio, -.5, 5) elif ratio_config == 'smart sortino': ratio = training_data['smart_sortino'] ratio_normalized = jh.normalize(ratio, -.5, 15) else: raise ValueError( f'The entered ratio configuration `{ratio_config}` for the optimization is unknown. Choose between sharpe, calmar, sortino, serenity, smart shapre, smart sortino and omega.') if ratio < 0: score = 0.0001 # reset store store.reset() return score, training_data, testing_data score = total_effect_rate * ratio_normalized # perform backtest with testing data. this is using data # model hasn't trained for. if it works well, there is # high change it will do good with future data too. store.reset() store.candles.init_storage(5000) # inject required TESTING candles to the candle store for num, c in enumerate(config['app']['considering_candles']): required_candles.inject_required_candles_to_store( self.testing_initial_candles[num], c[0], c[1] ) # run backtest simulation simulator(self.testing_candles, hp) # log for debugging/monitoring if store.completed_trades.count > 0: testing_data = stats.trades(store.completed_trades.trades, store.app.daily_balance) else: score = 0.0001 # reset store store.reset() return score, training_data, testing_data
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()