|
14 | 14 | # You should have received a copy of the GNU General Public |
15 | 15 | # License along with OctoBot-Script. If not, see <https://www.gnu.org/licenses/>. |
16 | 16 |
|
| 17 | +import datetime |
| 18 | + |
17 | 19 | import octobot_backtesting.api as backtesting_api |
18 | 20 | import octobot_commons.symbols as commons_symbols |
19 | 21 | import octobot_commons.enums as commons_enums |
20 | 22 | import octobot_trading.enums as trading_enums |
21 | 23 | import octobot_script.internal.octobot_mocks as octobot_mocks |
22 | 24 |
|
23 | 25 |
|
| 26 | +def _validate_tentacles_source(tentacles_config, profile_id): |
| 27 | + if tentacles_config is not None and profile_id is not None: |
| 28 | + raise ValueError("Only one of tentacles_config or profile_id can be provided.") |
| 29 | + |
| 30 | + |
24 | 31 | def _ensure_ms_timestamp(timestamp): |
25 | 32 | if timestamp is None: |
26 | 33 | return timestamp |
27 | 34 | if timestamp < 16737955050: # Friday 28 May 2500 07:57:30 |
28 | 35 | return timestamp * 1000 |
29 | 36 |
|
30 | 37 |
|
31 | | -async def historical_data(symbol, timeframe, exchange="binance", exchange_type=trading_enums.ExchangeTypes.SPOT.value, |
32 | | - start_timestamp=None, end_timestamp=None): |
| 38 | +def _yesterday_midnight_ms() -> int: |
| 39 | + """Return today at 00:00:00 UTC (= end of yesterday) in milliseconds. |
| 40 | + Used as a stable default end_timestamp so data files collected on the |
| 41 | + same calendar day share an identical end boundary and can be cached.""" |
| 42 | + today_midnight = datetime.datetime.now(datetime.timezone.utc).replace( |
| 43 | + hour=0, minute=0, second=0, microsecond=0 |
| 44 | + ) |
| 45 | + return int(today_midnight.timestamp() * 1000) |
| 46 | + |
| 47 | + |
| 48 | +def _resolve_end_timestamp_ms(end_timestamp) -> int: |
| 49 | + if end_timestamp is None: |
| 50 | + return _yesterday_midnight_ms() |
| 51 | + return _ensure_ms_timestamp(end_timestamp) |
| 52 | + |
| 53 | + |
| 54 | +async def historical_data( |
| 55 | + symbol, |
| 56 | + timeframe, |
| 57 | + exchange="binance", |
| 58 | + exchange_type=trading_enums.ExchangeTypes.SPOT.value, |
| 59 | + start_timestamp=None, |
| 60 | + end_timestamp=None, |
| 61 | + tentacles_config=None, |
| 62 | + profile_id=None, |
| 63 | +): |
| 64 | + _validate_tentacles_source(tentacles_config, profile_id) |
33 | 65 | symbols = [symbol] |
34 | 66 | time_frames = [commons_enums.TimeFrames(timeframe)] |
35 | | - data_collector_instance = backtesting_api.exchange_historical_data_collector_factory( |
36 | | - exchange, |
37 | | - trading_enums.ExchangeTypes(exchange_type), |
38 | | - octobot_mocks.get_tentacles_config(), |
39 | | - [commons_symbols.parse_symbol(symbol) for symbol in symbols], |
| 67 | + start_timestamp_ms = _ensure_ms_timestamp(start_timestamp) |
| 68 | + end_timestamp_ms = _resolve_end_timestamp_ms(end_timestamp) |
| 69 | + existing_file = await backtesting_api.find_matching_data_file( |
| 70 | + exchange_name=exchange, |
| 71 | + symbols=symbols, |
40 | 72 | time_frames=time_frames, |
41 | | - start_timestamp=_ensure_ms_timestamp(start_timestamp), |
42 | | - end_timestamp=_ensure_ms_timestamp(end_timestamp) |
| 73 | + start_timestamp=start_timestamp_ms, |
| 74 | + end_timestamp=end_timestamp_ms, |
| 75 | + ) |
| 76 | + if existing_file: |
| 77 | + return existing_file |
| 78 | + data_collector_instance = ( |
| 79 | + backtesting_api.exchange_historical_data_collector_factory( |
| 80 | + exchange, |
| 81 | + trading_enums.ExchangeTypes(exchange_type), |
| 82 | + octobot_mocks.get_tentacles_config( |
| 83 | + tentacles_config, profile_id, activate_strategy_tentacles=False |
| 84 | + ), |
| 85 | + [commons_symbols.parse_symbol(symbol) for symbol in symbols], |
| 86 | + time_frames=time_frames, |
| 87 | + start_timestamp=start_timestamp_ms, |
| 88 | + end_timestamp=end_timestamp_ms, |
| 89 | + ) |
| 90 | + ) |
| 91 | + return await backtesting_api.initialize_and_run_data_collector( |
| 92 | + data_collector_instance |
| 93 | + ) |
| 94 | + |
| 95 | + |
| 96 | +async def social_historical_data( |
| 97 | + services: list[str], |
| 98 | + sources: list[str] | None = None, |
| 99 | + symbols: list[str] | None = None, |
| 100 | + start_timestamp=None, |
| 101 | + end_timestamp=None, |
| 102 | + tentacles_config=None, |
| 103 | + profile_id=None, |
| 104 | +): |
| 105 | + _validate_tentacles_source(tentacles_config, profile_id) |
| 106 | + start_timestamp_ms = _ensure_ms_timestamp(start_timestamp) |
| 107 | + end_timestamp_ms = _resolve_end_timestamp_ms(end_timestamp) |
| 108 | + existing_file = await backtesting_api.find_matching_data_file( |
| 109 | + services=services, |
| 110 | + symbols=symbols or [], |
| 111 | + start_timestamp=start_timestamp_ms, |
| 112 | + end_timestamp=end_timestamp_ms, |
| 113 | + ) |
| 114 | + if existing_file: |
| 115 | + return existing_file |
| 116 | + data_collector_instance = backtesting_api.social_historical_data_collector_factory( |
| 117 | + services=services, |
| 118 | + tentacles_setup_config=octobot_mocks.get_tentacles_config( |
| 119 | + tentacles_config, profile_id, activate_strategy_tentacles=False |
| 120 | + ), |
| 121 | + sources=sources, |
| 122 | + symbols=[commons_symbols.parse_symbol(symbol) for symbol in symbols] |
| 123 | + if symbols |
| 124 | + else None, |
| 125 | + start_timestamp=start_timestamp_ms, |
| 126 | + end_timestamp=end_timestamp_ms, |
| 127 | + config=octobot_mocks.get_config(), |
| 128 | + ) |
| 129 | + return await backtesting_api.initialize_and_run_data_collector( |
| 130 | + data_collector_instance |
| 131 | + ) |
| 132 | + |
| 133 | + |
| 134 | +async def get_data( |
| 135 | + symbol, |
| 136 | + time_frame, |
| 137 | + exchange="binance", |
| 138 | + exchange_type=trading_enums.ExchangeTypes.SPOT.value, |
| 139 | + start_timestamp=None, |
| 140 | + end_timestamp=None, |
| 141 | + data_file=None, |
| 142 | + social_data_files: list[str] | None = None, |
| 143 | + social_services: list[str] | None = None, |
| 144 | + social_sources: list[str] | None = None, |
| 145 | + social_symbols: list[str] | None = None, |
| 146 | + tentacles_config=None, |
| 147 | + profile_id=None, |
| 148 | +): |
| 149 | + _validate_tentacles_source(tentacles_config, profile_id) |
| 150 | + data_files = ( |
| 151 | + [data_file] |
| 152 | + if data_file |
| 153 | + else [ |
| 154 | + await historical_data( |
| 155 | + symbol, |
| 156 | + timeframe=time_frame, |
| 157 | + exchange=exchange, |
| 158 | + exchange_type=exchange_type, |
| 159 | + start_timestamp=start_timestamp, |
| 160 | + end_timestamp=end_timestamp, |
| 161 | + tentacles_config=tentacles_config, |
| 162 | + profile_id=profile_id, |
| 163 | + ) |
| 164 | + ] |
43 | 165 | ) |
44 | | - return await backtesting_api.initialize_and_run_data_collector(data_collector_instance) |
45 | 166 |
|
| 167 | + if social_data_files is not None: |
| 168 | + data_files.extend(social_data_files) |
| 169 | + elif ( |
| 170 | + profile_id is not None |
| 171 | + or social_sources is not None |
| 172 | + or tentacles_config is not None |
| 173 | + ): |
| 174 | + social_services = ( |
| 175 | + social_services |
| 176 | + if social_services is not None |
| 177 | + else octobot_mocks.get_activated_social_services( |
| 178 | + tentacles_config, profile_id, requested_sources=social_sources |
| 179 | + ) |
| 180 | + ) |
| 181 | + if social_services: |
| 182 | + for service in social_services: |
| 183 | + data_files.append( |
| 184 | + await social_historical_data( |
| 185 | + [service], |
| 186 | + sources=social_sources, |
| 187 | + symbols=social_symbols, |
| 188 | + start_timestamp=start_timestamp, |
| 189 | + end_timestamp=end_timestamp, |
| 190 | + tentacles_config=tentacles_config, |
| 191 | + profile_id=profile_id, |
| 192 | + ) |
| 193 | + ) |
46 | 194 |
|
47 | | -async def get_data(symbol, time_frame, exchange="binance", exchange_type=trading_enums.ExchangeTypes.SPOT.value, |
48 | | - start_timestamp=None, end_timestamp=None, data_file=None): |
49 | | - data = data_file or \ |
50 | | - await historical_data(symbol, timeframe=time_frame, exchange=exchange, exchange_type=exchange_type, |
51 | | - start_timestamp=start_timestamp, end_timestamp=end_timestamp) |
52 | 195 | return await backtesting_api.create_and_init_backtest_data( |
53 | | - [data], |
| 196 | + data_files, |
54 | 197 | octobot_mocks.get_config(), |
55 | | - octobot_mocks.get_tentacles_config(), |
56 | | - use_accurate_price_time_frame=True |
| 198 | + octobot_mocks.get_tentacles_config( |
| 199 | + tentacles_config, profile_id, activate_strategy_tentacles=False |
| 200 | + ), |
| 201 | + use_accurate_price_time_frame=True, |
57 | 202 | ) |
0 commit comments