1313#
1414# You should have received a copy of the GNU General Public
1515# License along with OctoBot. If not, see <https://www.gnu.org/licenses/>.
16+ import json
1617import uuid
1718import gc
1819import sys
1920import asyncio
21+ import time
2022
21- import octobot_commons .logging as logging
23+ import octobot_commons .logging as commons_logging
24+ import octobot_commons .configuration as commons_configuration
25+ import octobot_commons .databases as commons_databases
26+ import octobot_commons .constants as commons_constants
2227
2328import octobot_backtesting .api as backtesting_api
2429import octobot_backtesting .importers as importers
3237import octobot_trading .api as trading_api
3338import octobot_trading .enums as trading_enums
3439
35- import octobot_commons .databases as databases
36- import octobot_commons .constants as commons_constants
37-
3840import octobot .logger as logger
41+ import octobot .storage as storage
42+ import octobot .databases_util as databases_util
3943
4044
4145class OctoBotBacktesting :
@@ -47,8 +51,9 @@ def __init__(self, backtesting_config,
4751 run_on_common_part_only ,
4852 start_timestamp = None ,
4953 end_timestamp = None ,
50- enable_logs = True ):
51- self .logger = logging .get_logger (self .__class__ .__name__ )
54+ enable_logs = True ,
55+ enable_storage = True ):
56+ self .logger = commons_logging .get_logger (self .__class__ .__name__ )
5257 self .backtesting_config = backtesting_config
5358 self .tentacles_setup_config = tentacles_setup_config
5459 self .bot_id = str (uuid .uuid4 ())
@@ -60,14 +65,26 @@ def __init__(self, backtesting_config,
6065 self .backtesting_files = backtesting_files
6166 self .backtesting = None
6267 self .run_on_common_part_only = run_on_common_part_only
68+ self .start_time = None
6369 self .start_timestamp = start_timestamp
6470 self .end_timestamp = end_timestamp
6571 self .enable_logs = enable_logs
6672 self .exchange_type_by_exchange = {}
6773 self .futures_contract_type = trading_enums .FutureContractType .LINEAR_PERPETUAL
74+ self .enable_storage = enable_storage
6875
6976 async def initialize_and_run (self ):
7077 self .logger .info (f"Starting on { self .backtesting_files } with { self .symbols_to_create_exchange_classes } " )
78+ self .start_time = time .time ()
79+ await commons_databases .init_bot_storage (
80+ self .bot_id ,
81+ databases_util .get_run_databases_identifier (
82+ self .backtesting_config ,
83+ self .tentacles_setup_config ,
84+ enable_storage = self .enable_storage ,
85+ ),
86+ False
87+ )
7188 await self ._init_evaluators ()
7289 await self ._init_service_feeds ()
7390 await self ._init_exchanges ()
@@ -91,16 +108,27 @@ async def stop(self, memory_check=False, should_raise=False):
91108 if self .backtesting is None :
92109 self .logger .warning ("No backtesting to stop, there was probably an issue when starting the backtesting" )
93110 else :
111+ exchange_managers = trading_api .get_exchange_managers_from_exchange_ids (self .exchange_manager_ids )
112+ if exchange_managers and self .enable_storage :
113+ try :
114+ for exchange_manager in exchange_managers :
115+ await trading_api .store_history_in_run_storage (exchange_manager )
116+ except Exception as e :
117+ self .logger .exception (e , True , f"Error when saving exchange historical data: { e } " )
118+ try :
119+ await self ._store_metadata (exchange_managers )
120+ self .logger .info (f"Stored backtesting run metadata" )
121+ except Exception as e :
122+ self .logger .exception (e , True , f"Error when saving run metadata: { e } " )
94123 await backtesting_api .stop_backtesting (self .backtesting )
95124 try :
96- for exchange_manager in trading_api .get_exchange_managers_from_exchange_ids (self .exchange_manager_ids ):
97- exchange_managers .append (exchange_manager )
125+ for exchange_manager in exchange_managers :
98126 await trading_api .stop_exchange (exchange_manager )
99127 except KeyError :
100128 # exchange managers are not added in global exchange list when an exception occurred
101129 pass
102130 # close run databases
103- await trading_api .close_bot_storage (self .bot_id )
131+ await commons_databases .close_bot_storage (self .bot_id )
104132 # stop evaluators
105133 for evaluators in self .evaluators :
106134 # evaluators by type
@@ -109,7 +137,7 @@ async def stop(self, memory_check=False, should_raise=False):
109137 if evaluator is not None :
110138 await evaluator_api .stop_evaluator (evaluator )
111139 # close all caches (if caches have to be used later on, they can always be re-opened)
112- await databases .CacheManager ().close_cache (commons_constants .UNPROVIDED_CACHE_IDENTIFIER )
140+ await commons_databases .CacheManager ().close_cache (commons_constants .UNPROVIDED_CACHE_IDENTIFIER )
113141 try :
114142 await evaluator_api .stop_all_evaluator_channels (self .matrix_id )
115143 except KeyError :
@@ -170,6 +198,10 @@ def check_remaining_objects(self):
170198 }
171199 for obj in gc .get_objects ():
172200 if isinstance (obj , to_watch_objects ):
201+ if isinstance (obj , exchanges .ExchangeManager ) and not obj .is_initialized :
202+ # Ignore exchange managers that have not been initialized
203+ # and are irrelevant. Pytest fixtures can also retain references failing tests
204+ continue
173205 objects_references [type (obj )][1 ].append (obj )
174206 objects_references [type (obj )] = (objects_references [type (obj )][0 ] + 1 ,
175207 objects_references [type (obj )][1 ])
@@ -186,6 +218,24 @@ def check_remaining_objects(self):
186218 f"[Dev oriented error: no effect on backtesting result, please report if you see it]: { errors } "
187219 )
188220
221+ async def _store_metadata (self , exchange_managers ):
222+ run_db = commons_databases .RunDatabasesProvider .instance ().get_run_db (self .bot_id )
223+ await run_db .flush ()
224+ user_inputs = await commons_configuration .get_user_inputs (run_db )
225+ await storage .store_run_metadata (
226+ self .bot_id ,
227+ exchange_managers ,
228+ self .start_time ,
229+ user_inputs = user_inputs ,
230+ )
231+ metadata = await storage .store_backtesting_run_metadata (
232+ exchange_managers ,
233+ self .start_time ,
234+ user_inputs ,
235+ commons_databases .RunDatabasesProvider .instance ().get_run_databases_identifier (self .bot_id ),
236+ )
237+ self .logger .info (f"Backtesting metadata:\n { json .dumps (metadata , indent = 4 )} " )
238+
189239 async def _init_evaluators (self ):
190240 self .matrix_id = await evaluator_api .initialize_evaluators (self .backtesting_config , self .tentacles_setup_config )
191241 await evaluator_api .create_evaluator_channels (self .matrix_id , is_backtesting = True )
@@ -223,7 +273,6 @@ async def _init_exchanges(self):
223273 data_files = self .backtesting_files )
224274 # modify_backtesting_channels before creating exchanges as they require the current backtesting time to
225275 # initialize
226- trading_api .init_bot_storage (self .bot_id , self .backtesting_config , self .tentacles_setup_config )
227276 await backtesting_api .adapt_backtesting_channels (self .backtesting ,
228277 self .backtesting_config ,
229278 importers .ExchangeDataImporter ,
@@ -241,7 +290,8 @@ async def _init_exchanges(self):
241290 .is_simulated () \
242291 .is_rest_only () \
243292 .is_backtesting (self .backtesting ) \
244- .is_future (is_future , self .futures_contract_type )
293+ .is_future (is_future , self .futures_contract_type ) \
294+ .enable_storage (self .enable_storage )
245295 try :
246296 await exchange_builder .build ()
247297 finally :
@@ -260,5 +310,8 @@ async def start_exchange_loggers(self):
260310def _get_remaining_object_error (obj , expected , actual ):
261311 error = f"too many remaining { obj .__name__ } instances: expected: { expected } actual { actual [0 ]} "
262312 for i in range (len (actual [1 ])):
263- error += f"{ sys .getrefcount (actual [1 ][i ])} references on { actual [1 ][i ]} "
313+ debug_info = ""
314+ if isinstance (actual [1 ][i ], exchanges .ExchangeManager ):
315+ debug_info = f" ({ actual [1 ][i ].debug_info } )"
316+ error += f"{ sys .getrefcount (actual [1 ][i ])} references on { actual [1 ][i ]} { debug_info } "
264317 return error
0 commit comments