|
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 |
|
| 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 | + |
31 | 54 | async def historical_data(symbol, timeframe, exchange="binance", exchange_type=trading_enums.ExchangeTypes.SPOT.value, |
32 | | - start_timestamp=None, end_timestamp=None): |
| 55 | + start_timestamp=None, end_timestamp=None, tentacles_config=None, profile_id=None): |
| 56 | + _validate_tentacles_source(tentacles_config, profile_id) |
33 | 57 | symbols = [symbol] |
34 | 58 | time_frames = [commons_enums.TimeFrames(timeframe)] |
| 59 | + start_timestamp_ms = _ensure_ms_timestamp(start_timestamp) |
| 60 | + end_timestamp_ms = _resolve_end_timestamp_ms(end_timestamp) |
| 61 | + existing_file = await backtesting_api.find_matching_data_file( |
| 62 | + exchange_name=exchange, |
| 63 | + symbols=symbols, |
| 64 | + time_frames=time_frames, |
| 65 | + start_timestamp=start_timestamp_ms, |
| 66 | + end_timestamp=end_timestamp_ms, |
| 67 | + ) |
| 68 | + if existing_file: |
| 69 | + return existing_file |
35 | 70 | data_collector_instance = backtesting_api.exchange_historical_data_collector_factory( |
36 | 71 | exchange, |
37 | 72 | trading_enums.ExchangeTypes(exchange_type), |
38 | | - octobot_mocks.get_tentacles_config(), |
| 73 | + octobot_mocks.get_tentacles_config(tentacles_config, profile_id, activate_strategy_tentacles=False), |
39 | 74 | [commons_symbols.parse_symbol(symbol) for symbol in symbols], |
40 | 75 | time_frames=time_frames, |
41 | | - start_timestamp=_ensure_ms_timestamp(start_timestamp), |
42 | | - end_timestamp=_ensure_ms_timestamp(end_timestamp) |
| 76 | + start_timestamp=start_timestamp_ms, |
| 77 | + end_timestamp=end_timestamp_ms, |
| 78 | + ) |
| 79 | + return await backtesting_api.initialize_and_run_data_collector(data_collector_instance) |
| 80 | + |
| 81 | + |
| 82 | +async def social_historical_data(services: list[str], sources: list[str] | None = None, |
| 83 | + symbols: list[str] | None = None, start_timestamp=None, end_timestamp=None, |
| 84 | + tentacles_config=None, profile_id=None): |
| 85 | + _validate_tentacles_source(tentacles_config, profile_id) |
| 86 | + start_timestamp_ms = _ensure_ms_timestamp(start_timestamp) |
| 87 | + end_timestamp_ms = _resolve_end_timestamp_ms(end_timestamp) |
| 88 | + existing_file = await backtesting_api.find_matching_data_file( |
| 89 | + services=services, |
| 90 | + symbols=symbols or [], |
| 91 | + start_timestamp=start_timestamp_ms, |
| 92 | + end_timestamp=end_timestamp_ms, |
| 93 | + ) |
| 94 | + if existing_file: |
| 95 | + return existing_file |
| 96 | + data_collector_instance = backtesting_api.social_historical_data_collector_factory( |
| 97 | + services=services, |
| 98 | + tentacles_setup_config=octobot_mocks.get_tentacles_config( |
| 99 | + tentacles_config, profile_id, activate_strategy_tentacles=False |
| 100 | + ), |
| 101 | + sources=sources, |
| 102 | + symbols=[commons_symbols.parse_symbol(symbol) for symbol in symbols] if symbols else None, |
| 103 | + start_timestamp=start_timestamp_ms, |
| 104 | + end_timestamp=end_timestamp_ms, |
| 105 | + config=octobot_mocks.get_config(), |
43 | 106 | ) |
44 | 107 | return await backtesting_api.initialize_and_run_data_collector(data_collector_instance) |
45 | 108 |
|
46 | 109 |
|
47 | 110 | 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) |
| 111 | + start_timestamp=None, end_timestamp=None, data_file=None, |
| 112 | + social_data_files: list[str] | None = None, social_services: list[str] | None = None, |
| 113 | + social_sources: list[str] | None = None, social_symbols: list[str] | None = None, |
| 114 | + tentacles_config=None, profile_id=None): |
| 115 | + _validate_tentacles_source(tentacles_config, profile_id) |
| 116 | + data_files = [data_file] if data_file else [ |
| 117 | + await historical_data( |
| 118 | + symbol, |
| 119 | + timeframe=time_frame, |
| 120 | + exchange=exchange, |
| 121 | + exchange_type=exchange_type, |
| 122 | + start_timestamp=start_timestamp, |
| 123 | + end_timestamp=end_timestamp, |
| 124 | + tentacles_config=tentacles_config, |
| 125 | + profile_id=profile_id, |
| 126 | + ) |
| 127 | + ] |
| 128 | + |
| 129 | + if social_data_files is not None: |
| 130 | + data_files.extend(social_data_files) |
| 131 | + elif profile_id is not None: |
| 132 | + social_services = social_services if social_services is not None \ |
| 133 | + else octobot_mocks.get_activated_social_services( |
| 134 | + tentacles_config, profile_id, requested_sources=social_sources |
| 135 | + ) |
| 136 | + if social_services: |
| 137 | + for service in social_services: |
| 138 | + data_files.append( |
| 139 | + await social_historical_data( |
| 140 | + [service], |
| 141 | + sources=social_sources, |
| 142 | + symbols=social_symbols, |
| 143 | + start_timestamp=start_timestamp, |
| 144 | + end_timestamp=end_timestamp, |
| 145 | + tentacles_config=tentacles_config, |
| 146 | + profile_id=profile_id, |
| 147 | + ) |
| 148 | + ) |
| 149 | + |
52 | 150 | return await backtesting_api.create_and_init_backtest_data( |
53 | | - [data], |
| 151 | + data_files, |
54 | 152 | octobot_mocks.get_config(), |
55 | | - octobot_mocks.get_tentacles_config(), |
| 153 | + octobot_mocks.get_tentacles_config(tentacles_config, profile_id, activate_strategy_tentacles=False), |
56 | 154 | use_accurate_price_time_frame=True |
57 | 155 | ) |
0 commit comments