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
qubx/core/context.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from threading import Thread
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
from qubx import logger
|
|
6
|
+
from qubx.core.basics import (
|
|
7
|
+
SW,
|
|
8
|
+
AssetBalance,
|
|
9
|
+
CtrlChannel,
|
|
10
|
+
DataType,
|
|
11
|
+
Instrument,
|
|
12
|
+
ITimeProvider,
|
|
13
|
+
MarketType,
|
|
14
|
+
Order,
|
|
15
|
+
OrderRequest,
|
|
16
|
+
Position,
|
|
17
|
+
dt_64,
|
|
18
|
+
)
|
|
19
|
+
from qubx.core.helpers import (
|
|
20
|
+
BasicScheduler,
|
|
21
|
+
CachedMarketDataHolder,
|
|
22
|
+
set_parameters_to_object,
|
|
23
|
+
)
|
|
24
|
+
from qubx.core.interfaces import (
|
|
25
|
+
IAccountProcessor,
|
|
26
|
+
IBroker,
|
|
27
|
+
IDataProvider,
|
|
28
|
+
IMarketManager,
|
|
29
|
+
IPositionGathering,
|
|
30
|
+
IProcessingManager,
|
|
31
|
+
IStrategy,
|
|
32
|
+
IStrategyContext,
|
|
33
|
+
ISubscriptionManager,
|
|
34
|
+
ITradingManager,
|
|
35
|
+
IUniverseManager,
|
|
36
|
+
PositionsTracker,
|
|
37
|
+
RemovalPolicy,
|
|
38
|
+
)
|
|
39
|
+
from qubx.core.loggers import StrategyLogging
|
|
40
|
+
from qubx.data.readers import DataReader
|
|
41
|
+
from qubx.gathering.simplest import SimplePositionGatherer
|
|
42
|
+
from qubx.trackers.sizers import FixedSizer
|
|
43
|
+
|
|
44
|
+
from .mixins import (
|
|
45
|
+
MarketManager,
|
|
46
|
+
ProcessingManager,
|
|
47
|
+
SubscriptionManager,
|
|
48
|
+
TradingManager,
|
|
49
|
+
UniverseManager,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class StrategyContext(IStrategyContext):
|
|
54
|
+
DEFAULT_POSITION_TRACKER: Callable[[], PositionsTracker] = lambda: PositionsTracker(
|
|
55
|
+
FixedSizer(1.0, amount_in_quote=False)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
_market_data_provider: IMarketManager
|
|
59
|
+
_universe_manager: IUniverseManager
|
|
60
|
+
_subscription_manager: ISubscriptionManager
|
|
61
|
+
_trading_manager: ITradingManager
|
|
62
|
+
_processing_manager: IProcessingManager
|
|
63
|
+
|
|
64
|
+
_broker: IBroker # service for exchange API: orders managemewnt
|
|
65
|
+
_logging: StrategyLogging # recording all activities for the strat: execs, positions, portfolio
|
|
66
|
+
_data_provider: IDataProvider # market data provider
|
|
67
|
+
_cache: CachedMarketDataHolder
|
|
68
|
+
_scheduler: BasicScheduler
|
|
69
|
+
_initial_instruments: list[Instrument]
|
|
70
|
+
|
|
71
|
+
_thread_data_loop: Thread | None = None # market data loop
|
|
72
|
+
_is_initialized: bool = False
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
strategy: IStrategy,
|
|
77
|
+
broker: IBroker,
|
|
78
|
+
data_provider: IDataProvider,
|
|
79
|
+
account: IAccountProcessor,
|
|
80
|
+
scheduler: BasicScheduler,
|
|
81
|
+
time_provider: ITimeProvider,
|
|
82
|
+
instruments: list[Instrument],
|
|
83
|
+
logging: StrategyLogging,
|
|
84
|
+
config: dict[str, Any] | None = None,
|
|
85
|
+
position_gathering: IPositionGathering | None = None, # TODO: make position gathering part of the strategy
|
|
86
|
+
aux_data_provider: DataReader | None = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
self.account = account
|
|
89
|
+
self.strategy = self.__instantiate_strategy(strategy, config)
|
|
90
|
+
|
|
91
|
+
self._time_provider = time_provider
|
|
92
|
+
self._broker = broker
|
|
93
|
+
self._data_provider = data_provider
|
|
94
|
+
self._logging = logging
|
|
95
|
+
self._scheduler = scheduler
|
|
96
|
+
self._initial_instruments = instruments
|
|
97
|
+
|
|
98
|
+
self._cache = CachedMarketDataHolder()
|
|
99
|
+
|
|
100
|
+
__position_tracker = self.strategy.tracker(self)
|
|
101
|
+
if __position_tracker is None:
|
|
102
|
+
__position_tracker = StrategyContext.DEFAULT_POSITION_TRACKER()
|
|
103
|
+
|
|
104
|
+
__position_gathering = position_gathering if position_gathering is not None else SimplePositionGatherer()
|
|
105
|
+
|
|
106
|
+
self._subscription_manager = SubscriptionManager(
|
|
107
|
+
data_provider=self._data_provider,
|
|
108
|
+
default_base_subscription=DataType.ORDERBOOK if not self._data_provider.is_simulation else DataType.NONE,
|
|
109
|
+
)
|
|
110
|
+
self.account.set_subscription_manager(self._subscription_manager)
|
|
111
|
+
|
|
112
|
+
self._market_data_provider = MarketManager(
|
|
113
|
+
time_provider=self._time_provider,
|
|
114
|
+
cache=self._cache,
|
|
115
|
+
data_provider=self._data_provider,
|
|
116
|
+
universe_manager=self,
|
|
117
|
+
aux_data_provider=aux_data_provider,
|
|
118
|
+
)
|
|
119
|
+
self._universe_manager = UniverseManager(
|
|
120
|
+
context=self,
|
|
121
|
+
strategy=self.strategy,
|
|
122
|
+
broker=self._data_provider,
|
|
123
|
+
trading_service=self._broker,
|
|
124
|
+
cache=self._cache,
|
|
125
|
+
logging=self._logging,
|
|
126
|
+
subscription_manager=self,
|
|
127
|
+
trading_manager=self,
|
|
128
|
+
time_provider=self,
|
|
129
|
+
account=self.account,
|
|
130
|
+
position_gathering=__position_gathering,
|
|
131
|
+
)
|
|
132
|
+
self._trading_manager = TradingManager(
|
|
133
|
+
time_provider=self,
|
|
134
|
+
broker=self._broker,
|
|
135
|
+
account=self.account,
|
|
136
|
+
strategy_name=self.strategy.__class__.__name__,
|
|
137
|
+
)
|
|
138
|
+
self._processing_manager = ProcessingManager(
|
|
139
|
+
context=self,
|
|
140
|
+
strategy=self.strategy,
|
|
141
|
+
logging=self._logging,
|
|
142
|
+
market_data=self,
|
|
143
|
+
subscription_manager=self,
|
|
144
|
+
time_provider=self,
|
|
145
|
+
account=self.account,
|
|
146
|
+
position_tracker=__position_tracker,
|
|
147
|
+
position_gathering=__position_gathering,
|
|
148
|
+
universe_manager=self._universe_manager,
|
|
149
|
+
cache=self._cache,
|
|
150
|
+
scheduler=self._scheduler,
|
|
151
|
+
is_simulation=self._data_provider.is_simulation,
|
|
152
|
+
)
|
|
153
|
+
self.__post_init__()
|
|
154
|
+
|
|
155
|
+
def __post_init__(self) -> None:
|
|
156
|
+
self.strategy.on_init(self)
|
|
157
|
+
# - update cache default timeframe
|
|
158
|
+
sub_type = self.get_base_subscription()
|
|
159
|
+
_, params = DataType.from_str(sub_type)
|
|
160
|
+
__default_timeframe = params.get("timeframe", "1sec")
|
|
161
|
+
self._cache.update_default_timeframe(__default_timeframe)
|
|
162
|
+
|
|
163
|
+
def start(self, blocking: bool = False):
|
|
164
|
+
if self._is_initialized:
|
|
165
|
+
raise ValueError("Strategy is already started !")
|
|
166
|
+
|
|
167
|
+
# - run cron scheduler
|
|
168
|
+
self._scheduler.run()
|
|
169
|
+
|
|
170
|
+
# - create incoming market data processing
|
|
171
|
+
databus = self._data_provider.channel
|
|
172
|
+
databus.register(self)
|
|
173
|
+
|
|
174
|
+
# - start account processing
|
|
175
|
+
self.account.start()
|
|
176
|
+
|
|
177
|
+
# - update universe with initial instruments after the strategy is initialized
|
|
178
|
+
self.set_universe(self._initial_instruments, skip_callback=True)
|
|
179
|
+
|
|
180
|
+
# - initialize strategy (should we do that after any first market data received ?)
|
|
181
|
+
if not self._is_initialized:
|
|
182
|
+
try:
|
|
183
|
+
self.strategy.on_start(self)
|
|
184
|
+
self._is_initialized = True
|
|
185
|
+
except Exception as strat_error:
|
|
186
|
+
logger.error(
|
|
187
|
+
f"[StrategyContext] :: Strategy {self.strategy.__class__.__name__} raised an exception in on_start: {strat_error}"
|
|
188
|
+
)
|
|
189
|
+
logger.error(traceback.format_exc())
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# - for live we run loop
|
|
193
|
+
if not self._data_provider.is_simulation:
|
|
194
|
+
self._thread_data_loop = Thread(target=self.__process_incoming_data_loop, args=(databus,), daemon=True)
|
|
195
|
+
self._thread_data_loop.start()
|
|
196
|
+
logger.info("[StrategyContext] :: strategy is started in thread")
|
|
197
|
+
if blocking:
|
|
198
|
+
self._thread_data_loop.join()
|
|
199
|
+
|
|
200
|
+
def stop(self):
|
|
201
|
+
# - invoke strategy's stop code
|
|
202
|
+
try:
|
|
203
|
+
self.strategy.on_stop(self)
|
|
204
|
+
except Exception as strat_error:
|
|
205
|
+
logger.error(
|
|
206
|
+
f"[<y>StrategyContext</y>] :: Strategy {self.strategy.__class__.__name__} raised an exception in on_stop: {strat_error}"
|
|
207
|
+
)
|
|
208
|
+
logger.opt(colors=False).error(traceback.format_exc())
|
|
209
|
+
|
|
210
|
+
if self._thread_data_loop:
|
|
211
|
+
self._data_provider.close()
|
|
212
|
+
self._data_provider.channel.stop()
|
|
213
|
+
self._thread_data_loop.join()
|
|
214
|
+
self._thread_data_loop = None
|
|
215
|
+
|
|
216
|
+
# - stop account processing
|
|
217
|
+
self.account.stop()
|
|
218
|
+
|
|
219
|
+
# - close logging
|
|
220
|
+
self._logging.close()
|
|
221
|
+
|
|
222
|
+
def is_running(self):
|
|
223
|
+
return self._thread_data_loop is not None and self._thread_data_loop.is_alive()
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def is_simulation(self) -> bool:
|
|
227
|
+
return self._data_provider.is_simulation
|
|
228
|
+
|
|
229
|
+
# IAccountViewer delegation
|
|
230
|
+
|
|
231
|
+
# capital information
|
|
232
|
+
def get_capital(self) -> float:
|
|
233
|
+
return self.account.get_capital()
|
|
234
|
+
|
|
235
|
+
def get_total_capital(self) -> float:
|
|
236
|
+
return self.account.get_total_capital()
|
|
237
|
+
|
|
238
|
+
# balance and position information
|
|
239
|
+
def get_balances(self) -> dict[str, AssetBalance]:
|
|
240
|
+
return dict(self.account.get_balances())
|
|
241
|
+
|
|
242
|
+
def get_positions(self) -> dict[Instrument, Position]:
|
|
243
|
+
return dict(self.account.get_positions())
|
|
244
|
+
|
|
245
|
+
def get_position(self, instrument: Instrument) -> Position:
|
|
246
|
+
return self.account.get_position(instrument)
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def positions(self):
|
|
250
|
+
return self.account.positions
|
|
251
|
+
|
|
252
|
+
def get_orders(self, instrument: Instrument | None = None) -> dict[str, Order]:
|
|
253
|
+
return self.account.get_orders(instrument)
|
|
254
|
+
|
|
255
|
+
def position_report(self) -> dict:
|
|
256
|
+
return self.account.position_report()
|
|
257
|
+
|
|
258
|
+
# leverage information
|
|
259
|
+
def get_leverage(self, instrument: Instrument) -> float:
|
|
260
|
+
return self.account.get_leverage(instrument)
|
|
261
|
+
|
|
262
|
+
def get_leverages(self) -> dict[Instrument, float]:
|
|
263
|
+
return self.account.get_leverages()
|
|
264
|
+
|
|
265
|
+
def get_net_leverage(self) -> float:
|
|
266
|
+
return self.account.get_net_leverage()
|
|
267
|
+
|
|
268
|
+
def get_gross_leverage(self) -> float:
|
|
269
|
+
return self.account.get_gross_leverage()
|
|
270
|
+
|
|
271
|
+
# margin information
|
|
272
|
+
def get_total_required_margin(self) -> float:
|
|
273
|
+
return self.account.get_total_required_margin()
|
|
274
|
+
|
|
275
|
+
def get_available_margin(self) -> float:
|
|
276
|
+
return self.account.get_available_margin()
|
|
277
|
+
|
|
278
|
+
def get_margin_ratio(self) -> float:
|
|
279
|
+
return self.account.get_margin_ratio()
|
|
280
|
+
|
|
281
|
+
# IMarketDataProvider delegation
|
|
282
|
+
def time(self) -> dt_64:
|
|
283
|
+
return self._market_data_provider.time()
|
|
284
|
+
|
|
285
|
+
def ohlc(self, instrument: Instrument, timeframe: str | None = None, length: int | None = None):
|
|
286
|
+
return self._market_data_provider.ohlc(instrument, timeframe, length)
|
|
287
|
+
|
|
288
|
+
def quote(self, instrument: Instrument):
|
|
289
|
+
return self._market_data_provider.quote(instrument)
|
|
290
|
+
|
|
291
|
+
def get_data(self, instrument: Instrument, sub_type: str) -> list[Any]:
|
|
292
|
+
return self._market_data_provider.get_data(instrument, sub_type)
|
|
293
|
+
|
|
294
|
+
def get_aux_data(self, data_id: str, **parameters):
|
|
295
|
+
return self._market_data_provider.get_aux_data(data_id, **parameters)
|
|
296
|
+
|
|
297
|
+
def get_instruments(self):
|
|
298
|
+
return self._market_data_provider.get_instruments()
|
|
299
|
+
|
|
300
|
+
def query_instrument(self, symbol: str, exchange: str | None = None) -> Instrument | None:
|
|
301
|
+
return self._market_data_provider.query_instrument(
|
|
302
|
+
symbol, exchange if exchange is not None else self.exchanges[0]
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# ITradingManager delegation
|
|
306
|
+
def trade(self, instrument: Instrument, amount: float, price: float | None = None, time_in_force="gtc", **options):
|
|
307
|
+
return self._trading_manager.trade(instrument, amount, price, time_in_force, **options)
|
|
308
|
+
|
|
309
|
+
def submit_orders(self, order_requests: list[OrderRequest]) -> list[Order]:
|
|
310
|
+
return self._trading_manager.submit_orders(order_requests)
|
|
311
|
+
|
|
312
|
+
def set_target_position(
|
|
313
|
+
self, instrument: Instrument, target: float, price: float | None = None, **options
|
|
314
|
+
) -> Order:
|
|
315
|
+
return self._trading_manager.set_target_position(instrument, target, price, **options)
|
|
316
|
+
|
|
317
|
+
def close_position(self, instrument: Instrument) -> None:
|
|
318
|
+
return self._trading_manager.close_position(instrument)
|
|
319
|
+
|
|
320
|
+
def close_positions(self, market_type: MarketType | None = None) -> None:
|
|
321
|
+
return self._trading_manager.close_positions(market_type)
|
|
322
|
+
|
|
323
|
+
def cancel_order(self, order_id: str) -> None:
|
|
324
|
+
return self._trading_manager.cancel_order(order_id)
|
|
325
|
+
|
|
326
|
+
def cancel_orders(self, instrument: Instrument):
|
|
327
|
+
return self._trading_manager.cancel_orders(instrument)
|
|
328
|
+
|
|
329
|
+
# IUniverseManager delegation
|
|
330
|
+
def set_universe(
|
|
331
|
+
self, instruments: list[Instrument], skip_callback: bool = False, if_has_position_then: RemovalPolicy = "close"
|
|
332
|
+
):
|
|
333
|
+
return self._universe_manager.set_universe(instruments, skip_callback, if_has_position_then)
|
|
334
|
+
|
|
335
|
+
def add_instruments(self, instruments: list[Instrument]):
|
|
336
|
+
return self._universe_manager.add_instruments(instruments)
|
|
337
|
+
|
|
338
|
+
def remove_instruments(self, instruments: list[Instrument]):
|
|
339
|
+
return self._universe_manager.remove_instruments(instruments)
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def instruments(self):
|
|
343
|
+
return self._universe_manager.instruments
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def exchanges(self) -> list[str]:
|
|
347
|
+
return self._trading_manager.exchanges()
|
|
348
|
+
|
|
349
|
+
# ISubscriptionManager delegation
|
|
350
|
+
def subscribe(self, subscription_type: str, instruments: list[Instrument] | Instrument | None = None):
|
|
351
|
+
return self._subscription_manager.subscribe(subscription_type, instruments)
|
|
352
|
+
|
|
353
|
+
def unsubscribe(self, subscription_type: str, instruments: list[Instrument] | Instrument | None = None):
|
|
354
|
+
return self._subscription_manager.unsubscribe(subscription_type, instruments)
|
|
355
|
+
|
|
356
|
+
def has_subscription(self, instrument: Instrument, subscription_type: str):
|
|
357
|
+
return self._subscription_manager.has_subscription(instrument, subscription_type)
|
|
358
|
+
|
|
359
|
+
def get_subscriptions(self, instrument: Instrument | None = None) -> list[str]:
|
|
360
|
+
return self._subscription_manager.get_subscriptions(instrument)
|
|
361
|
+
|
|
362
|
+
def get_base_subscription(self) -> str:
|
|
363
|
+
return self._subscription_manager.get_base_subscription()
|
|
364
|
+
|
|
365
|
+
def set_base_subscription(self, subscription_type: str):
|
|
366
|
+
return self._subscription_manager.set_base_subscription(subscription_type)
|
|
367
|
+
|
|
368
|
+
def get_subscribed_instruments(self, subscription_type: str | None = None) -> list[Instrument]:
|
|
369
|
+
return self._subscription_manager.get_subscribed_instruments(subscription_type)
|
|
370
|
+
|
|
371
|
+
def get_warmup(self, subscription_type: str) -> str | None:
|
|
372
|
+
return self._subscription_manager.get_warmup(subscription_type)
|
|
373
|
+
|
|
374
|
+
def set_warmup(self, configs: dict[Any, str]):
|
|
375
|
+
return self._subscription_manager.set_warmup(configs)
|
|
376
|
+
|
|
377
|
+
def commit(self):
|
|
378
|
+
return self._subscription_manager.commit()
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def auto_subscribe(self) -> bool:
|
|
382
|
+
return self._subscription_manager.auto_subscribe
|
|
383
|
+
|
|
384
|
+
@auto_subscribe.setter
|
|
385
|
+
def auto_subscribe(self, value: bool):
|
|
386
|
+
self._subscription_manager.auto_subscribe = value
|
|
387
|
+
|
|
388
|
+
# IProcessingManager delegation
|
|
389
|
+
def process_data(self, instrument: Instrument, d_type: str, data: Any, is_historical: bool):
|
|
390
|
+
return self._processing_manager.process_data(instrument, d_type, data, is_historical)
|
|
391
|
+
|
|
392
|
+
def set_fit_schedule(self, schedule: str):
|
|
393
|
+
return self._processing_manager.set_fit_schedule(schedule)
|
|
394
|
+
|
|
395
|
+
def set_event_schedule(self, schedule: str):
|
|
396
|
+
return self._processing_manager.set_event_schedule(schedule)
|
|
397
|
+
|
|
398
|
+
def get_event_schedule(self, event_id: str) -> str | None:
|
|
399
|
+
return self._processing_manager.get_event_schedule(event_id)
|
|
400
|
+
|
|
401
|
+
def is_fitted(self) -> bool:
|
|
402
|
+
return self._processing_manager.is_fitted()
|
|
403
|
+
|
|
404
|
+
# private methods
|
|
405
|
+
def __process_incoming_data_loop(self, channel: CtrlChannel):
|
|
406
|
+
logger.info("[StrategyContext] :: Start processing market data")
|
|
407
|
+
while channel.control.is_set():
|
|
408
|
+
with SW("StrategyContext._process_incoming_data"):
|
|
409
|
+
# - waiting for incoming market data
|
|
410
|
+
instrument, d_type, data, hist = channel.receive()
|
|
411
|
+
if self.process_data(instrument, d_type, data, hist):
|
|
412
|
+
channel.stop()
|
|
413
|
+
break
|
|
414
|
+
logger.info("[StrategyContext] :: Market data processing stopped")
|
|
415
|
+
|
|
416
|
+
def __instantiate_strategy(self, strategy: IStrategy, config: dict[str, Any] | None) -> IStrategy:
|
|
417
|
+
__strategy = strategy() if isinstance(strategy, type) else strategy
|
|
418
|
+
__strategy.ctx = self
|
|
419
|
+
set_parameters_to_object(__strategy, **config if config else {})
|
|
420
|
+
return __strategy
|
qubx/core/exceptions.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class BaseError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ExchangeError(BaseError):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BadRequest(ExchangeError):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InvalidOrder(ExchangeError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OrderNotFound(InvalidOrder):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NotSupported(ExchangeError):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class QueueTimeout(BaseError):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StrategyExceededMaxNumberOfRuntimeFailuresError(Exception):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SimulationError(Exception):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SimulationConfigError(Exception):
|
|
38
|
+
pass
|