Qubx 0.5.7__cp312-cp312-manylinux_2_39_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Qubx might be problematic. Click here for more details.
- qubx/__init__.py +207 -0
- qubx/_nb_magic.py +100 -0
- qubx/backtester/__init__.py +5 -0
- qubx/backtester/account.py +145 -0
- qubx/backtester/broker.py +87 -0
- qubx/backtester/data.py +296 -0
- qubx/backtester/management.py +378 -0
- qubx/backtester/ome.py +296 -0
- qubx/backtester/optimization.py +201 -0
- qubx/backtester/simulated_data.py +558 -0
- qubx/backtester/simulator.py +362 -0
- qubx/backtester/utils.py +780 -0
- qubx/cli/__init__.py +0 -0
- qubx/cli/commands.py +67 -0
- qubx/connectors/ccxt/__init__.py +0 -0
- qubx/connectors/ccxt/account.py +495 -0
- qubx/connectors/ccxt/broker.py +132 -0
- qubx/connectors/ccxt/customizations.py +193 -0
- qubx/connectors/ccxt/data.py +612 -0
- qubx/connectors/ccxt/exceptions.py +17 -0
- qubx/connectors/ccxt/factory.py +93 -0
- qubx/connectors/ccxt/utils.py +307 -0
- qubx/core/__init__.py +0 -0
- qubx/core/account.py +251 -0
- qubx/core/basics.py +850 -0
- qubx/core/context.py +420 -0
- qubx/core/exceptions.py +38 -0
- qubx/core/helpers.py +480 -0
- qubx/core/interfaces.py +1150 -0
- qubx/core/loggers.py +514 -0
- qubx/core/lookups.py +475 -0
- qubx/core/metrics.py +1512 -0
- qubx/core/mixins/__init__.py +13 -0
- qubx/core/mixins/market.py +94 -0
- qubx/core/mixins/processing.py +428 -0
- qubx/core/mixins/subscription.py +203 -0
- qubx/core/mixins/trading.py +88 -0
- qubx/core/mixins/universe.py +270 -0
- qubx/core/series.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/series.pxd +125 -0
- qubx/core/series.pyi +118 -0
- qubx/core/series.pyx +988 -0
- qubx/core/utils.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/core/utils.pyi +6 -0
- qubx/core/utils.pyx +62 -0
- qubx/data/__init__.py +25 -0
- qubx/data/helpers.py +416 -0
- qubx/data/readers.py +1562 -0
- qubx/data/tardis.py +100 -0
- qubx/gathering/simplest.py +88 -0
- qubx/math/__init__.py +3 -0
- qubx/math/stats.py +129 -0
- qubx/pandaz/__init__.py +23 -0
- qubx/pandaz/ta.py +2757 -0
- qubx/pandaz/utils.py +638 -0
- qubx/resources/instruments/symbols-binance.cm.json +1 -0
- qubx/resources/instruments/symbols-binance.json +1 -0
- qubx/resources/instruments/symbols-binance.um.json +1 -0
- qubx/resources/instruments/symbols-bitfinex.f.json +1 -0
- qubx/resources/instruments/symbols-bitfinex.json +1 -0
- qubx/resources/instruments/symbols-kraken.f.json +1 -0
- qubx/resources/instruments/symbols-kraken.json +1 -0
- qubx/ta/__init__.py +0 -0
- qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so +0 -0
- qubx/ta/indicators.pxd +149 -0
- qubx/ta/indicators.pyi +41 -0
- qubx/ta/indicators.pyx +787 -0
- qubx/trackers/__init__.py +3 -0
- qubx/trackers/abvanced.py +236 -0
- qubx/trackers/composite.py +146 -0
- qubx/trackers/rebalancers.py +129 -0
- qubx/trackers/riskctrl.py +641 -0
- qubx/trackers/sizers.py +235 -0
- qubx/utils/__init__.py +5 -0
- qubx/utils/_pyxreloader.py +281 -0
- qubx/utils/charting/lookinglass.py +1057 -0
- qubx/utils/charting/mpl_helpers.py +1183 -0
- qubx/utils/marketdata/binance.py +284 -0
- qubx/utils/marketdata/ccxt.py +90 -0
- qubx/utils/marketdata/dukas.py +130 -0
- qubx/utils/misc.py +541 -0
- qubx/utils/ntp.py +63 -0
- qubx/utils/numbers_utils.py +7 -0
- qubx/utils/orderbook.py +491 -0
- qubx/utils/plotting/__init__.py +0 -0
- qubx/utils/plotting/dashboard.py +150 -0
- qubx/utils/plotting/data.py +137 -0
- qubx/utils/plotting/interfaces.py +25 -0
- qubx/utils/plotting/renderers/__init__.py +0 -0
- qubx/utils/plotting/renderers/plotly.py +0 -0
- qubx/utils/runner/__init__.py +1 -0
- qubx/utils/runner/_jupyter_runner.pyt +60 -0
- qubx/utils/runner/accounts.py +88 -0
- qubx/utils/runner/configs.py +65 -0
- qubx/utils/runner/runner.py +470 -0
- qubx/utils/time.py +312 -0
- qubx-0.5.7.dist-info/METADATA +105 -0
- qubx-0.5.7.dist-info/RECORD +100 -0
- qubx-0.5.7.dist-info/WHEEL +4 -0
- qubx-0.5.7.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from joblib import delayed
|
|
6
|
+
|
|
7
|
+
from qubx import QubxLogConfig, logger, lookup
|
|
8
|
+
from qubx.core.basics import SW, DataType
|
|
9
|
+
from qubx.core.context import StrategyContext
|
|
10
|
+
from qubx.core.exceptions import SimulationConfigError, SimulationError
|
|
11
|
+
from qubx.core.helpers import extract_parameters_from_object, full_qualified_class_name
|
|
12
|
+
from qubx.core.interfaces import IStrategy
|
|
13
|
+
from qubx.core.loggers import InMemoryLogsWriter, StrategyLogging
|
|
14
|
+
from qubx.core.metrics import TradingSessionResult
|
|
15
|
+
from qubx.data.readers import DataReader
|
|
16
|
+
from qubx.pandaz.utils import _frame_to_str
|
|
17
|
+
from qubx.utils.misc import ProgressParallel, Stopwatch, get_current_user
|
|
18
|
+
from qubx.utils.time import handle_start_stop
|
|
19
|
+
|
|
20
|
+
from .account import SimulatedAccountProcessor
|
|
21
|
+
from .broker import SimulatedBroker
|
|
22
|
+
from .data import SimulatedDataProvider
|
|
23
|
+
from .utils import (
|
|
24
|
+
DataDecls_t,
|
|
25
|
+
ExchangeName_t,
|
|
26
|
+
SetupTypes,
|
|
27
|
+
SignalsProxy,
|
|
28
|
+
SimulatedCtrlChannel,
|
|
29
|
+
SimulatedLogFormatter,
|
|
30
|
+
SimulatedScheduler,
|
|
31
|
+
SimulatedTimeProvider,
|
|
32
|
+
SimulationDataConfig,
|
|
33
|
+
SimulationSetup,
|
|
34
|
+
StrategiesDecls_t,
|
|
35
|
+
SymbolOrInstrument_t,
|
|
36
|
+
find_instruments_and_exchanges,
|
|
37
|
+
recognize_simulation_configuration,
|
|
38
|
+
recognize_simulation_data_config,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def simulate(
|
|
43
|
+
strategies: StrategiesDecls_t,
|
|
44
|
+
data: DataDecls_t,
|
|
45
|
+
capital: float,
|
|
46
|
+
instruments: list[SymbolOrInstrument_t] | dict[ExchangeName_t, list[SymbolOrInstrument_t]],
|
|
47
|
+
commissions: str,
|
|
48
|
+
start: str | pd.Timestamp,
|
|
49
|
+
stop: str | pd.Timestamp | None = None,
|
|
50
|
+
exchange: ExchangeName_t | None = None,
|
|
51
|
+
base_currency: str = "USDT",
|
|
52
|
+
n_jobs: int = 1,
|
|
53
|
+
silent: bool = False,
|
|
54
|
+
aux_data: DataReader | None = None,
|
|
55
|
+
accurate_stop_orders_execution: bool = False,
|
|
56
|
+
signal_timeframe: str = "1Min",
|
|
57
|
+
open_close_time_indent_secs=1,
|
|
58
|
+
debug: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = "WARNING",
|
|
59
|
+
show_latency_report: bool = False,
|
|
60
|
+
parallel_backend: Literal["loky", "multiprocessing"] = "multiprocessing",
|
|
61
|
+
) -> list[TradingSessionResult]:
|
|
62
|
+
"""
|
|
63
|
+
Backtest utility for trading strategies or signals using historical data.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
- strategies (StrategiesDecls_t): Trading strategy or signals configuration.
|
|
67
|
+
- data (DataDecls_t): Historical data for simulation, either as a dictionary of DataFrames or a DataReader object.
|
|
68
|
+
- capital (float): Initial capital for the simulation.
|
|
69
|
+
- instruments (list[SymbolOrInstrument_t] | dict[ExchangeName_t, list[SymbolOrInstrument_t]]): List of trading instruments or a dictionary mapping exchanges to instrument lists.
|
|
70
|
+
- commissions (str): Commission structure for trades.
|
|
71
|
+
- start (str | pd.Timestamp): Start time of the simulation.
|
|
72
|
+
- stop (str | pd.Timestamp | None): End time of the simulation. If None, simulates until the last accessible data.
|
|
73
|
+
- exchange (ExchangeName_t | None): Exchange name if not specified in the instruments list.
|
|
74
|
+
- base_currency (str): Base currency for the simulation, default is "USDT".
|
|
75
|
+
- n_jobs (int): Number of parallel jobs for simulation, default is 1.
|
|
76
|
+
- silent (bool): If True, suppresses output during simulation.
|
|
77
|
+
- aux_data (DataReader | None): Auxiliary data provider (default is None).
|
|
78
|
+
- accurate_stop_orders_execution (bool): If True, enables more accurate stop order execution simulation.
|
|
79
|
+
- signal_timeframe (str): Timeframe for signals, default is "1Min".
|
|
80
|
+
- open_close_time_indent_secs (int): Time indent in seconds for open/close times, default is 1.
|
|
81
|
+
- debug (Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None): Logging level for debugging.
|
|
82
|
+
- show_latency_report: If True, shows simulator's latency report.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
- list[TradingSessionResult]: A list of TradingSessionResult objects containing the results of each simulation setup.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
# - setup logging
|
|
89
|
+
QubxLogConfig.set_log_level(debug.upper() if debug else "WARNING")
|
|
90
|
+
|
|
91
|
+
# - we need to reset stopwatch
|
|
92
|
+
Stopwatch().reset()
|
|
93
|
+
|
|
94
|
+
# - process instruments:
|
|
95
|
+
_instruments, _exchanges = find_instruments_and_exchanges(instruments, exchange)
|
|
96
|
+
|
|
97
|
+
# - check we have exchange
|
|
98
|
+
if not _exchanges:
|
|
99
|
+
logger.error(
|
|
100
|
+
_msg
|
|
101
|
+
:= "No exchange information provided - you can specify it by exchange parameter or use <yellow>EXCHANGE:SYMBOL</yellow> format for symbols"
|
|
102
|
+
)
|
|
103
|
+
raise SimulationError(_msg)
|
|
104
|
+
|
|
105
|
+
# - check if instruments are from the same exchange (mmulti-exchanges is not supported yet)
|
|
106
|
+
if len(_exchanges) > 1:
|
|
107
|
+
logger.error(
|
|
108
|
+
_msg := f"Multiple exchanges found: {', '.join(_exchanges)} - this mode is not supported yet in Qubx !"
|
|
109
|
+
)
|
|
110
|
+
raise SimulationError(_msg)
|
|
111
|
+
|
|
112
|
+
exchange = _exchanges[0]
|
|
113
|
+
|
|
114
|
+
# - recognize provided data
|
|
115
|
+
data_setup = recognize_simulation_data_config(data, _instruments, exchange, open_close_time_indent_secs, aux_data)
|
|
116
|
+
|
|
117
|
+
# - recognize setup: it can be either a strategy or set of signals
|
|
118
|
+
simulation_setups = recognize_simulation_configuration(
|
|
119
|
+
"",
|
|
120
|
+
strategies,
|
|
121
|
+
_instruments,
|
|
122
|
+
exchange,
|
|
123
|
+
capital,
|
|
124
|
+
base_currency,
|
|
125
|
+
commissions,
|
|
126
|
+
signal_timeframe,
|
|
127
|
+
accurate_stop_orders_execution,
|
|
128
|
+
)
|
|
129
|
+
if not simulation_setups:
|
|
130
|
+
logger.error(
|
|
131
|
+
_msg
|
|
132
|
+
:= "Can't recognize setup - it should be a strategy, a set of signals or list of signals/strategies + tracker !"
|
|
133
|
+
)
|
|
134
|
+
raise SimulationError(_msg)
|
|
135
|
+
|
|
136
|
+
# - preprocess start and stop and convert to datetime if necessary
|
|
137
|
+
if stop is None:
|
|
138
|
+
# - check stop time : here we try to backtest till now (may be we need to get max available time from data reader ?)
|
|
139
|
+
stop = pd.Timestamp.now(tz="UTC").astimezone(None)
|
|
140
|
+
|
|
141
|
+
_start, _stop = handle_start_stop(start, stop, convert=pd.Timestamp)
|
|
142
|
+
assert isinstance(_start, pd.Timestamp) and isinstance(_stop, pd.Timestamp), "Invalid start and stop times"
|
|
143
|
+
|
|
144
|
+
# - run simulations
|
|
145
|
+
return _run_setups(
|
|
146
|
+
simulation_setups,
|
|
147
|
+
data_setup,
|
|
148
|
+
_start,
|
|
149
|
+
_stop,
|
|
150
|
+
n_jobs=n_jobs,
|
|
151
|
+
silent=silent,
|
|
152
|
+
show_latency_report=show_latency_report,
|
|
153
|
+
parallel_backend=parallel_backend,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _run_setups(
|
|
158
|
+
strategies_setups: list[SimulationSetup],
|
|
159
|
+
data_setup: SimulationDataConfig,
|
|
160
|
+
start: pd.Timestamp,
|
|
161
|
+
stop: pd.Timestamp,
|
|
162
|
+
n_jobs: int = -1,
|
|
163
|
+
silent: bool = False,
|
|
164
|
+
show_latency_report: bool = False,
|
|
165
|
+
parallel_backend: Literal["loky", "multiprocessing"] = "multiprocessing",
|
|
166
|
+
) -> list[TradingSessionResult]:
|
|
167
|
+
# loggers don't work well with joblib and multiprocessing in general because they contain
|
|
168
|
+
# open file handlers that cannot be pickled. I found a solution which requires the usage of enqueue=True
|
|
169
|
+
# in the logger configuration and specifying backtest "multiprocessing" instead of the default "loky"
|
|
170
|
+
# for joblib. But it works now.
|
|
171
|
+
# See: https://stackoverflow.com/questions/59433146/multiprocessing-logging-how-to-use-loguru-with-joblib-parallel
|
|
172
|
+
_main_loop_silent = len(strategies_setups) == 1
|
|
173
|
+
n_jobs = 1 if _main_loop_silent else n_jobs
|
|
174
|
+
|
|
175
|
+
reports = ProgressParallel(
|
|
176
|
+
n_jobs=n_jobs, total=len(strategies_setups), silent=_main_loop_silent, backend=parallel_backend
|
|
177
|
+
)(
|
|
178
|
+
delayed(_run_setup)(id, f"Simulated-{id}", setup, data_setup, start, stop, silent, show_latency_report)
|
|
179
|
+
for id, setup in enumerate(strategies_setups)
|
|
180
|
+
)
|
|
181
|
+
return reports # type: ignore
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _run_setup(
|
|
185
|
+
setup_id: int,
|
|
186
|
+
account_id: str,
|
|
187
|
+
setup: SimulationSetup,
|
|
188
|
+
data_setup: SimulationDataConfig,
|
|
189
|
+
start: pd.Timestamp,
|
|
190
|
+
stop: pd.Timestamp,
|
|
191
|
+
silent: bool,
|
|
192
|
+
show_latency_report: bool,
|
|
193
|
+
) -> TradingSessionResult:
|
|
194
|
+
_stop = pd.Timestamp(stop)
|
|
195
|
+
|
|
196
|
+
# - fees for this exchange
|
|
197
|
+
tcc = lookup.fees.find(setup.exchange.lower(), setup.commissions)
|
|
198
|
+
if tcc is None:
|
|
199
|
+
raise SimulationConfigError(
|
|
200
|
+
f"Can't find transaction costs calculator for '{setup.exchange}' for specification '{setup.commissions}' !"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
channel = SimulatedCtrlChannel("databus", sentinel=(None, None, None, None))
|
|
204
|
+
simulated_clock = SimulatedTimeProvider(np.datetime64(start, "ns"))
|
|
205
|
+
|
|
206
|
+
# - we want to see simulate time in log messages
|
|
207
|
+
QubxLogConfig.setup_logger(QubxLogConfig.get_log_level(), SimulatedLogFormatter(simulated_clock).formatter)
|
|
208
|
+
|
|
209
|
+
logger.debug(
|
|
210
|
+
f"[<y>simulator</y>] :: Preparing simulated trading on <g>{setup.exchange.upper()}</g> for {setup.capital} {setup.base_currency}..."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
account = SimulatedAccountProcessor(
|
|
214
|
+
account_id=account_id,
|
|
215
|
+
channel=channel,
|
|
216
|
+
base_currency=setup.base_currency,
|
|
217
|
+
initial_capital=setup.capital,
|
|
218
|
+
time_provider=simulated_clock,
|
|
219
|
+
tcc=tcc,
|
|
220
|
+
accurate_stop_orders_execution=setup.accurate_stop_orders_execution,
|
|
221
|
+
)
|
|
222
|
+
scheduler = SimulatedScheduler(channel, lambda: simulated_clock.time().item())
|
|
223
|
+
broker = SimulatedBroker(channel, account, setup.exchange)
|
|
224
|
+
data_provider = SimulatedDataProvider(
|
|
225
|
+
exchange_id=setup.exchange,
|
|
226
|
+
channel=channel,
|
|
227
|
+
scheduler=scheduler,
|
|
228
|
+
time_provider=simulated_clock,
|
|
229
|
+
account=account,
|
|
230
|
+
readers=data_setup.data_providers,
|
|
231
|
+
open_close_time_indent_secs=data_setup.adjusted_open_close_time_indent_secs,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# - it will store simulation results into memory
|
|
235
|
+
logs_writer = InMemoryLogsWriter(account_id, setup.name, "0")
|
|
236
|
+
strat: IStrategy | None = None
|
|
237
|
+
|
|
238
|
+
match setup.setup_type:
|
|
239
|
+
case SetupTypes.STRATEGY:
|
|
240
|
+
strat = setup.generator # type: ignore
|
|
241
|
+
|
|
242
|
+
case SetupTypes.STRATEGY_AND_TRACKER:
|
|
243
|
+
strat = setup.generator # type: ignore
|
|
244
|
+
strat.tracker = lambda ctx: setup.tracker # type: ignore
|
|
245
|
+
|
|
246
|
+
case SetupTypes.SIGNAL:
|
|
247
|
+
strat = SignalsProxy(timeframe=setup.signal_timeframe)
|
|
248
|
+
data_provider.set_generated_signals(setup.generator) # type: ignore
|
|
249
|
+
|
|
250
|
+
# - we don't need any unexpected triggerings
|
|
251
|
+
_stop = min(setup.generator.index[-1], _stop) # type: ignore
|
|
252
|
+
|
|
253
|
+
case SetupTypes.SIGNAL_AND_TRACKER:
|
|
254
|
+
strat = SignalsProxy(timeframe=setup.signal_timeframe)
|
|
255
|
+
strat.tracker = lambda ctx: setup.tracker
|
|
256
|
+
data_provider.set_generated_signals(setup.generator) # type: ignore
|
|
257
|
+
|
|
258
|
+
# - we don't need any unexpected triggerings
|
|
259
|
+
_stop = min(setup.generator.index[-1], _stop) # type: ignore
|
|
260
|
+
|
|
261
|
+
case _:
|
|
262
|
+
raise SimulationError(f"Unsupported setup type: {setup.setup_type} !")
|
|
263
|
+
|
|
264
|
+
if not isinstance(strat, IStrategy):
|
|
265
|
+
raise SimulationConfigError(f"Strategy should be an instance of IStrategy, but got {strat} !")
|
|
266
|
+
|
|
267
|
+
# - get aux data provider
|
|
268
|
+
_aux_data = data_setup.get_timeguarded_aux_reader(simulated_clock)
|
|
269
|
+
|
|
270
|
+
ctx = StrategyContext(
|
|
271
|
+
strategy=strat,
|
|
272
|
+
broker=broker,
|
|
273
|
+
data_provider=data_provider,
|
|
274
|
+
account=account,
|
|
275
|
+
scheduler=scheduler,
|
|
276
|
+
time_provider=simulated_clock,
|
|
277
|
+
instruments=setup.instruments,
|
|
278
|
+
logging=StrategyLogging(logs_writer),
|
|
279
|
+
aux_data_provider=_aux_data,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# - setup base subscription from spec
|
|
283
|
+
if ctx.get_base_subscription() == DataType.NONE:
|
|
284
|
+
logger.debug(
|
|
285
|
+
f"[<y>simulator</y>] :: Setting up default base subscription: {data_setup.default_base_subscription}"
|
|
286
|
+
)
|
|
287
|
+
ctx.set_base_subscription(data_setup.default_base_subscription)
|
|
288
|
+
|
|
289
|
+
# - set default on_event schedule if detected and strategy didn't set it's own schedule
|
|
290
|
+
if not ctx.get_event_schedule("time") and data_setup.default_trigger_schedule:
|
|
291
|
+
logger.debug(f"[<y>simulator</y>] :: Setting default schedule: {data_setup.default_trigger_schedule}")
|
|
292
|
+
ctx.set_event_schedule(data_setup.default_trigger_schedule)
|
|
293
|
+
|
|
294
|
+
# - get strategy parameters BEFORE simulation start
|
|
295
|
+
# potentially strategy may change it's parameters during simulation
|
|
296
|
+
_s_class, _s_params = "", None
|
|
297
|
+
if setup.setup_type in [SetupTypes.STRATEGY, SetupTypes.STRATEGY_AND_TRACKER]:
|
|
298
|
+
_s_params = extract_parameters_from_object(setup.generator)
|
|
299
|
+
_s_class = full_qualified_class_name(setup.generator)
|
|
300
|
+
|
|
301
|
+
# - start context at this point
|
|
302
|
+
ctx.start()
|
|
303
|
+
|
|
304
|
+
# - apply default warmup periods if strategy didn't set them
|
|
305
|
+
for s in ctx.get_subscriptions():
|
|
306
|
+
if not ctx.get_warmup(s) and (_d_wt := data_setup.default_warmups.get(s)):
|
|
307
|
+
logger.debug(
|
|
308
|
+
f"[<y>simulator</y>] :: Strategy didn't set warmup period for <c>{s}</c> so default <c>{_d_wt}</c> will be used"
|
|
309
|
+
)
|
|
310
|
+
ctx.set_warmup({s: _d_wt})
|
|
311
|
+
|
|
312
|
+
def _is_known_type(t: str):
|
|
313
|
+
try:
|
|
314
|
+
DataType(t)
|
|
315
|
+
return True
|
|
316
|
+
except: # noqa: E722
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
# - if any custom data providers are in the data spec
|
|
320
|
+
for t, r in data_setup.data_providers.items():
|
|
321
|
+
if not _is_known_type(t) or t in [DataType.TRADE, DataType.OHLC_TRADES, DataType.OHLC_QUOTES, DataType.QUOTE]:
|
|
322
|
+
logger.debug(f"[<y>simulator</y>] :: Subscribing to: {t}")
|
|
323
|
+
ctx.subscribe(t, ctx.instruments)
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
data_provider.run(start, _stop, silent=silent) # type: ignore
|
|
327
|
+
except KeyboardInterrupt:
|
|
328
|
+
logger.error("Simulated trading interrupted by user !")
|
|
329
|
+
|
|
330
|
+
# - stop context at this point
|
|
331
|
+
ctx.stop()
|
|
332
|
+
|
|
333
|
+
# - service latency report
|
|
334
|
+
if show_latency_report:
|
|
335
|
+
_l_r = SW.latency_report()
|
|
336
|
+
if _l_r is not None:
|
|
337
|
+
logger.info(
|
|
338
|
+
"<BLUE> Time spent in simulation report </BLUE>\n<r>"
|
|
339
|
+
+ _frame_to_str(
|
|
340
|
+
_l_r.sort_values("latency", ascending=False).reset_index(drop=True), "simulation", -1, -1, False
|
|
341
|
+
)
|
|
342
|
+
+ "</r>"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
return TradingSessionResult(
|
|
346
|
+
setup_id,
|
|
347
|
+
setup.name,
|
|
348
|
+
start,
|
|
349
|
+
stop,
|
|
350
|
+
setup.exchange,
|
|
351
|
+
setup.instruments,
|
|
352
|
+
setup.capital,
|
|
353
|
+
setup.base_currency,
|
|
354
|
+
setup.commissions,
|
|
355
|
+
logs_writer.get_portfolio(as_plain_dataframe=True),
|
|
356
|
+
logs_writer.get_executions(),
|
|
357
|
+
logs_writer.get_signals(),
|
|
358
|
+
strategy_class=_s_class,
|
|
359
|
+
parameters=_s_params,
|
|
360
|
+
is_simulation=True,
|
|
361
|
+
author=get_current_user(),
|
|
362
|
+
)
|