Qubx 0.6.20__tar.gz → 0.6.23__tar.gz
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-0.6.20 → qubx-0.6.23}/PKG-INFO +1 -1
- {qubx-0.6.20 → qubx-0.6.23}/pyproject.toml +1 -1
- qubx-0.6.23/src/qubx/backtester/account.py +83 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/broker.py +19 -38
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/data.py +6 -7
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/ome.py +13 -9
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/runner.py +15 -4
- qubx-0.6.23/src/qubx/backtester/simulated_exchange.py +233 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/broker.py +25 -14
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +4 -4
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/context.py +1 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/interfaces.py +29 -29
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/mixins/trading.py +57 -9
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/ta/indicators.pxd +1 -1
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/ta/indicators.pyx +5 -8
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/runner/runner.py +7 -3
- qubx-0.6.20/src/qubx/backtester/account.py +0 -161
- {qubx-0.6.20 → qubx-0.6.23}/LICENSE +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/README.md +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/build.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/management.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/cli/commands.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/cli/release.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/reader.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/account.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/basics.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/deque.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/helpers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/initializer.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/metrics.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/series.pxd +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/series.pyi +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/series.pyx +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/data/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/data/readers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/data/registry.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/emitters/base.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/emitters/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/emitters/csv.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/emitters/questdb.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/features/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/features/core.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/features/orderbook.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/features/price.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/features/trades.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/features/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/health/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/health/base.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/resources/instruments/symbols-binance.json +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restorers/position.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/time.py +0 -0
- {qubx-0.6.20 → qubx-0.6.23}/src/qubx/utils/version.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "Qubx"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.23"
|
|
8
8
|
description = "Qubx - Quantitative Trading Framework"
|
|
9
9
|
authors = [ "Dmitry Marienko <dmitry.marienko@xlydian.com>", "Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",]
|
|
10
10
|
readme = "README.md"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from qubx.backtester.simulated_exchange import ISimulatedExchange
|
|
2
|
+
from qubx.core.account import BasicAccountProcessor
|
|
3
|
+
from qubx.core.basics import (
|
|
4
|
+
CtrlChannel,
|
|
5
|
+
Instrument,
|
|
6
|
+
Order,
|
|
7
|
+
Position,
|
|
8
|
+
Timestamped,
|
|
9
|
+
dt_64,
|
|
10
|
+
)
|
|
11
|
+
from qubx.core.series import OrderBook, Quote, Trade, TradeArray
|
|
12
|
+
from qubx.restorers import RestoredState
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SimulatedAccountProcessor(BasicAccountProcessor):
|
|
16
|
+
_channel: CtrlChannel
|
|
17
|
+
_exchange: ISimulatedExchange
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
account_id: str,
|
|
22
|
+
exchange: ISimulatedExchange,
|
|
23
|
+
channel: CtrlChannel,
|
|
24
|
+
base_currency: str,
|
|
25
|
+
initial_capital: float,
|
|
26
|
+
restored_state: RestoredState | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
super().__init__(
|
|
29
|
+
account_id=account_id,
|
|
30
|
+
time_provider=exchange.get_time_provider(),
|
|
31
|
+
base_currency=base_currency,
|
|
32
|
+
tcc=exchange.get_transaction_costs_calculator(),
|
|
33
|
+
initial_capital=initial_capital,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
self._exchange = exchange
|
|
37
|
+
self._channel = channel
|
|
38
|
+
|
|
39
|
+
if restored_state is not None:
|
|
40
|
+
self._balances.update(restored_state.balances)
|
|
41
|
+
for instrument, position in restored_state.positions.items():
|
|
42
|
+
_pos = self.get_position(instrument)
|
|
43
|
+
_pos.reset_by_position(position)
|
|
44
|
+
|
|
45
|
+
def get_orders(self, instrument: Instrument | None = None) -> dict[str, Order]:
|
|
46
|
+
return self._exchange.get_open_orders(instrument)
|
|
47
|
+
|
|
48
|
+
def get_position(self, instrument: Instrument) -> Position:
|
|
49
|
+
if instrument in self.positions:
|
|
50
|
+
return self.positions[instrument]
|
|
51
|
+
|
|
52
|
+
# - initialize empty position
|
|
53
|
+
position = Position(instrument) # type: ignore
|
|
54
|
+
self.attach_positions(position)
|
|
55
|
+
return self.positions[instrument]
|
|
56
|
+
|
|
57
|
+
def update_position_price(self, time: dt_64, instrument: Instrument, update: float | Timestamped) -> None:
|
|
58
|
+
self.get_position(instrument)
|
|
59
|
+
|
|
60
|
+
super().update_position_price(time, instrument, update)
|
|
61
|
+
|
|
62
|
+
quote = (
|
|
63
|
+
update if isinstance(update, Quote) else self._exchange.emulate_quote_from_data(instrument, time, update)
|
|
64
|
+
)
|
|
65
|
+
if quote is None:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# - process new data
|
|
69
|
+
self._process_new_data(instrument, quote)
|
|
70
|
+
|
|
71
|
+
def process_market_data(self, time: dt_64, instrument: Instrument, update: Timestamped) -> None:
|
|
72
|
+
if isinstance(update, (TradeArray, Quote, Trade, OrderBook)):
|
|
73
|
+
# - process new data
|
|
74
|
+
self._process_new_data(instrument, update)
|
|
75
|
+
|
|
76
|
+
super().process_market_data(time, instrument, update)
|
|
77
|
+
|
|
78
|
+
def _process_new_data(self, instrument: Instrument, data: Quote | OrderBook | Trade | TradeArray) -> None:
|
|
79
|
+
for r in self._exchange.process_market_data(instrument, data):
|
|
80
|
+
if r.exec is not None:
|
|
81
|
+
# - process methods will be called from stg context
|
|
82
|
+
self._channel.send((instrument, "order", r.order, False))
|
|
83
|
+
self._channel.send((instrument, "deals", [r.exec], False))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from qubx.backtester.ome import
|
|
1
|
+
from qubx.backtester.ome import SimulatedExecutionReport
|
|
2
|
+
from qubx.backtester.simulated_exchange import ISimulatedExchange
|
|
2
3
|
from qubx.core.basics import (
|
|
3
4
|
CtrlChannel,
|
|
4
5
|
Instrument,
|
|
@@ -13,16 +14,17 @@ class SimulatedBroker(IBroker):
|
|
|
13
14
|
channel: CtrlChannel
|
|
14
15
|
|
|
15
16
|
_account: SimulatedAccountProcessor
|
|
17
|
+
_exchange: ISimulatedExchange
|
|
16
18
|
|
|
17
19
|
def __init__(
|
|
18
20
|
self,
|
|
19
21
|
channel: CtrlChannel,
|
|
20
22
|
account: SimulatedAccountProcessor,
|
|
21
|
-
|
|
23
|
+
simulated_exchange: ISimulatedExchange,
|
|
22
24
|
) -> None:
|
|
23
25
|
self.channel = channel
|
|
24
26
|
self._account = account
|
|
25
|
-
self.
|
|
27
|
+
self._exchange = simulated_exchange
|
|
26
28
|
|
|
27
29
|
@property
|
|
28
30
|
def is_simulated_trading(self) -> bool:
|
|
@@ -39,22 +41,12 @@ class SimulatedBroker(IBroker):
|
|
|
39
41
|
time_in_force: str = "gtc",
|
|
40
42
|
**options,
|
|
41
43
|
) -> Order:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
report = ome.place_order(
|
|
48
|
-
order_side.upper(), # type: ignore
|
|
49
|
-
order_type.upper(), # type: ignore
|
|
50
|
-
amount,
|
|
51
|
-
price,
|
|
52
|
-
client_id,
|
|
53
|
-
time_in_force,
|
|
54
|
-
**options,
|
|
44
|
+
# - place order at exchange and send exec report to data channel
|
|
45
|
+
self._send_execution_report(
|
|
46
|
+
report := self._exchange.place_order(
|
|
47
|
+
instrument, order_side, order_type, amount, price, client_id, time_in_force, **options
|
|
48
|
+
)
|
|
55
49
|
)
|
|
56
|
-
|
|
57
|
-
self._send_exec_report(instrument, report)
|
|
58
50
|
return report.order
|
|
59
51
|
|
|
60
52
|
def send_order_async(
|
|
@@ -71,22 +63,8 @@ class SimulatedBroker(IBroker):
|
|
|
71
63
|
self.send_order(instrument, order_side, order_type, amount, price, client_id, time_in_force, **optional)
|
|
72
64
|
|
|
73
65
|
def cancel_order(self, order_id: str) -> Order | None:
|
|
74
|
-
|
|
75
|
-
if
|
|
76
|
-
raise ValueError(f"ExchangeService:cancel_order :: can't find order with id = '{order_id}'!")
|
|
77
|
-
|
|
78
|
-
ome = self._account.ome.get(instrument)
|
|
79
|
-
if ome is None:
|
|
80
|
-
raise ValueError(f"ExchangeService:send_order :: No OME configured for '{instrument}'!")
|
|
81
|
-
|
|
82
|
-
# - cancel order in OME and remove from the map to free memory
|
|
83
|
-
order_update = ome.cancel_order(order_id)
|
|
84
|
-
if order_update is None:
|
|
85
|
-
return None
|
|
86
|
-
|
|
87
|
-
self._send_exec_report(instrument, order_update)
|
|
88
|
-
|
|
89
|
-
return order_update.order
|
|
66
|
+
self._send_execution_report(order_update := self._exchange.cancel_order(order_id))
|
|
67
|
+
return order_update.order if order_update is not None else None
|
|
90
68
|
|
|
91
69
|
def cancel_orders(self, instrument: Instrument) -> None:
|
|
92
70
|
raise NotImplementedError("Not implemented yet")
|
|
@@ -94,10 +72,13 @@ class SimulatedBroker(IBroker):
|
|
|
94
72
|
def update_order(self, order_id: str, price: float | None = None, amount: float | None = None) -> Order:
|
|
95
73
|
raise NotImplementedError("Not implemented yet")
|
|
96
74
|
|
|
97
|
-
def
|
|
98
|
-
|
|
75
|
+
def _send_execution_report(self, report: SimulatedExecutionReport | None):
|
|
76
|
+
if report is None:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
self.channel.send((report.instrument, "order", report.order, False))
|
|
99
80
|
if report.exec is not None:
|
|
100
|
-
self.channel.send((instrument, "deals", [report.exec], False))
|
|
81
|
+
self.channel.send((report.instrument, "deals", [report.exec], False))
|
|
101
82
|
|
|
102
83
|
def exchange(self) -> str:
|
|
103
|
-
return self.
|
|
84
|
+
return self._exchange.exchange_id
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas as pd
|
|
@@ -30,9 +30,8 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
30
30
|
|
|
31
31
|
_scheduler: BasicScheduler
|
|
32
32
|
_account: SimulatedAccountProcessor
|
|
33
|
-
_last_quotes:
|
|
33
|
+
_last_quotes: dict[Instrument, Quote | None]
|
|
34
34
|
_readers: dict[str, DataReader]
|
|
35
|
-
_scheduler: BasicScheduler
|
|
36
35
|
_pregenerated_signals: dict[Instrument, pd.Series | pd.DataFrame]
|
|
37
36
|
_to_process: dict[Instrument, list]
|
|
38
37
|
_data_source: IterableSimulationData
|
|
@@ -143,7 +142,7 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
143
142
|
if h_data:
|
|
144
143
|
# _s_type = DataType.from_str(subscription_type)[0]
|
|
145
144
|
last_update = h_data[-1]
|
|
146
|
-
if last_quote := self._account.emulate_quote_from_data(i, last_update.time, last_update): # type: ignore
|
|
145
|
+
if last_quote := self._account._exchange.emulate_quote_from_data(i, last_update.time, last_update): # type: ignore
|
|
147
146
|
# - send historical data to the channel
|
|
148
147
|
self.channel.send((i, subscription_type, h_data, True))
|
|
149
148
|
|
|
@@ -151,7 +150,7 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
151
150
|
self._last_quotes[i] = last_quote
|
|
152
151
|
|
|
153
152
|
# - also need to pass this quote to OME !
|
|
154
|
-
self._account.
|
|
153
|
+
self._account.process_market_data(last_quote.time, i, last_quote) # type: ignore
|
|
155
154
|
|
|
156
155
|
logger.debug(f" | subscribed {subscription_type} {i} -> {last_quote}")
|
|
157
156
|
|
|
@@ -266,7 +265,7 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
266
265
|
cc.send((instrument, "event", {"order": sigs[0][1]}, False))
|
|
267
266
|
sigs.pop(0)
|
|
268
267
|
|
|
269
|
-
if q := self._account.emulate_quote_from_data(instrument, t, data):
|
|
268
|
+
if q := self._account._exchange.emulate_quote_from_data(instrument, t, data):
|
|
270
269
|
self._last_quotes[instrument] = q
|
|
271
270
|
|
|
272
271
|
self.time_provider.set_time(t)
|
|
@@ -284,7 +283,7 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
284
283
|
self.time_provider.set_time(_next_exp_time)
|
|
285
284
|
self._scheduler.check_and_run_tasks()
|
|
286
285
|
|
|
287
|
-
if q := self._account.emulate_quote_from_data(instrument, t, data):
|
|
286
|
+
if q := self._account._exchange.emulate_quote_from_data(instrument, t, data):
|
|
288
287
|
self._last_quotes[instrument] = q
|
|
289
288
|
|
|
290
289
|
self.time_provider.set_time(t)
|
|
@@ -27,7 +27,8 @@ from qubx.core.series import OrderBook, Quote, Trade, TradeArray
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@dataclass
|
|
30
|
-
class
|
|
30
|
+
class SimulatedExecutionReport:
|
|
31
|
+
instrument: Instrument
|
|
31
32
|
timestamp: dt_64
|
|
32
33
|
order: Order
|
|
33
34
|
exec: Deal | None
|
|
@@ -91,7 +92,7 @@ class OrdersManagementEngine:
|
|
|
91
92
|
def get_open_orders(self) -> list[Order]:
|
|
92
93
|
return list(self.active_orders.values()) + list(self.stop_orders.values())
|
|
93
94
|
|
|
94
|
-
def process_market_data(self, mdata: Quote | OrderBook | Trade | TradeArray) -> list[
|
|
95
|
+
def process_market_data(self, mdata: Quote | OrderBook | Trade | TradeArray) -> list[SimulatedExecutionReport]:
|
|
95
96
|
"""
|
|
96
97
|
Processes the new market data (quote, trade or trades array) and simulates the execution of pending orders.
|
|
97
98
|
"""
|
|
@@ -173,7 +174,7 @@ class OrdersManagementEngine:
|
|
|
173
174
|
client_id: str | None = None,
|
|
174
175
|
time_in_force: str = "gtc",
|
|
175
176
|
**options,
|
|
176
|
-
) ->
|
|
177
|
+
) -> SimulatedExecutionReport:
|
|
177
178
|
if self.bbo is None:
|
|
178
179
|
raise ExchangeError(f"Simulator is not ready for order management - no quote for {self.instrument.symbol}")
|
|
179
180
|
|
|
@@ -200,7 +201,7 @@ class OrdersManagementEngine:
|
|
|
200
201
|
def _dbg(self, message, **kwargs) -> None:
|
|
201
202
|
logger.debug(f" [<y>OME</y>(<g>{self.instrument}</g>)] :: {message}", **kwargs)
|
|
202
203
|
|
|
203
|
-
def _process_order(self, timestamp: dt_64, order: Order) ->
|
|
204
|
+
def _process_order(self, timestamp: dt_64, order: Order) -> SimulatedExecutionReport:
|
|
204
205
|
if order.status in ["CLOSED", "CANCELED"]:
|
|
205
206
|
raise InvalidOrder(f"Order {order.id} is already closed or canceled.")
|
|
206
207
|
|
|
@@ -273,12 +274,15 @@ class OrdersManagementEngine:
|
|
|
273
274
|
self.active_orders[order.id] = order
|
|
274
275
|
|
|
275
276
|
self._dbg(f"registered {order.id} {order.type} {order.side} {order.quantity} {order.price}")
|
|
276
|
-
return
|
|
277
|
+
return SimulatedExecutionReport(self.instrument, timestamp, order, None)
|
|
277
278
|
|
|
278
|
-
def _execute_order(
|
|
279
|
+
def _execute_order(
|
|
280
|
+
self, timestamp: dt_64, exec_price: float, order: Order, taker: bool
|
|
281
|
+
) -> SimulatedExecutionReport:
|
|
279
282
|
order.status = "CLOSED"
|
|
280
283
|
self._dbg(f"<red>{order.id}</red> {order.type} {order.side} {order.quantity} executed at {exec_price}")
|
|
281
|
-
return
|
|
284
|
+
return SimulatedExecutionReport(
|
|
285
|
+
self.instrument,
|
|
282
286
|
timestamp,
|
|
283
287
|
order,
|
|
284
288
|
Deal(
|
|
@@ -322,7 +326,7 @@ class OrdersManagementEngine:
|
|
|
322
326
|
f"Stop price would trigger immediately: STOP_MARKET {order_side} {amount} of {self.instrument.symbol} at {price} | market: {c_ask} / {c_bid}"
|
|
323
327
|
)
|
|
324
328
|
|
|
325
|
-
def cancel_order(self, order_id: str) ->
|
|
329
|
+
def cancel_order(self, order_id: str) -> SimulatedExecutionReport | None:
|
|
326
330
|
# - check limit orders
|
|
327
331
|
if order_id in self.active_orders:
|
|
328
332
|
order = self.active_orders.pop(order_id)
|
|
@@ -346,7 +350,7 @@ class OrdersManagementEngine:
|
|
|
346
350
|
|
|
347
351
|
order.status = "CANCELED"
|
|
348
352
|
self._dbg(f"{order.id} {order.type} {order.side} {order.quantity} canceled")
|
|
349
|
-
return
|
|
353
|
+
return SimulatedExecutionReport(self.instrument, self.time_service.time(), order, None)
|
|
350
354
|
|
|
351
355
|
def __str__(self) -> str:
|
|
352
356
|
_a, _b = True, True
|
|
@@ -17,6 +17,7 @@ from qubx.pandaz.utils import _frame_to_str
|
|
|
17
17
|
from .account import SimulatedAccountProcessor
|
|
18
18
|
from .broker import SimulatedBroker
|
|
19
19
|
from .data import SimulatedDataProvider
|
|
20
|
+
from .simulated_exchange import get_simulated_exchange
|
|
20
21
|
from .utils import (
|
|
21
22
|
SetupTypes,
|
|
22
23
|
SignalsProxy,
|
|
@@ -176,17 +177,25 @@ class SimulationRunner:
|
|
|
176
177
|
f"[<y>simulator</y>] :: Preparing simulated trading on <g>{self.setup.exchange.upper()}</g> for {self.setup.capital} {self.setup.base_currency}..."
|
|
177
178
|
)
|
|
178
179
|
|
|
180
|
+
# - create simulated exchange:
|
|
181
|
+
# - we can use different emulations of real exchanges features in future here: for Binance, Bybit, InteractiveBrokers, etc.
|
|
182
|
+
# - for now we use simple basic simulated exchange implementation
|
|
183
|
+
simulated_exchange = get_simulated_exchange(
|
|
184
|
+
self.setup.exchange, simulated_clock, tcc, self.setup.accurate_stop_orders_execution
|
|
185
|
+
)
|
|
186
|
+
|
|
179
187
|
account = SimulatedAccountProcessor(
|
|
180
188
|
account_id=self.account_id,
|
|
189
|
+
exchange=simulated_exchange,
|
|
181
190
|
channel=channel,
|
|
182
191
|
base_currency=self.setup.base_currency,
|
|
183
192
|
initial_capital=self.setup.capital,
|
|
184
|
-
time_provider=simulated_clock,
|
|
185
|
-
tcc=tcc,
|
|
186
|
-
accurate_stop_orders_execution=self.setup.accurate_stop_orders_execution,
|
|
187
193
|
)
|
|
188
194
|
scheduler = SimulatedScheduler(channel, lambda: simulated_clock.time().item())
|
|
189
|
-
|
|
195
|
+
|
|
196
|
+
# - broker is order's interface to the exchange
|
|
197
|
+
broker = SimulatedBroker(channel, account, simulated_exchange)
|
|
198
|
+
|
|
190
199
|
data_provider = SimulatedDataProvider(
|
|
191
200
|
exchange_id=self.setup.exchange,
|
|
192
201
|
channel=channel,
|
|
@@ -196,8 +205,10 @@ class SimulationRunner:
|
|
|
196
205
|
readers=self.data_config.data_providers,
|
|
197
206
|
open_close_time_indent_secs=self.data_config.adjusted_open_close_time_indent_secs,
|
|
198
207
|
)
|
|
208
|
+
|
|
199
209
|
# - get aux data provider
|
|
200
210
|
_aux_data = self.data_config.get_timeguarded_aux_reader(simulated_clock)
|
|
211
|
+
|
|
201
212
|
# - it will store simulation results into memory
|
|
202
213
|
logs_writer = InMemoryLogsWriter(self.account_id, self.setup.name, "0")
|
|
203
214
|
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
|
|
3
|
+
from qubx import logger
|
|
4
|
+
from qubx.backtester.ome import OrdersManagementEngine, SimulatedExecutionReport
|
|
5
|
+
from qubx.core.basics import (
|
|
6
|
+
ZERO_COSTS,
|
|
7
|
+
Instrument,
|
|
8
|
+
ITimeProvider,
|
|
9
|
+
Order,
|
|
10
|
+
Timestamped,
|
|
11
|
+
TransactionCostsCalculator,
|
|
12
|
+
dt_64,
|
|
13
|
+
)
|
|
14
|
+
from qubx.core.series import Bar, OrderBook, Quote, Trade, TradeArray
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ISimulatedExchange:
|
|
18
|
+
"""
|
|
19
|
+
Generic interface for simulated exchange.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
exchange_id: str
|
|
23
|
+
_half_tick_size: dict[Instrument, float]
|
|
24
|
+
|
|
25
|
+
def __init__(self, exchange_id: str):
|
|
26
|
+
self.exchange_id = exchange_id.upper()
|
|
27
|
+
self._half_tick_size = {}
|
|
28
|
+
|
|
29
|
+
def get_time_provider(self) -> ITimeProvider: ...
|
|
30
|
+
|
|
31
|
+
def get_transaction_costs_calculator(self) -> TransactionCostsCalculator: ...
|
|
32
|
+
|
|
33
|
+
def place_order(
|
|
34
|
+
self,
|
|
35
|
+
instrument: Instrument,
|
|
36
|
+
order_side: str,
|
|
37
|
+
order_type: str,
|
|
38
|
+
amount: float,
|
|
39
|
+
price: float | None = None,
|
|
40
|
+
client_id: str | None = None,
|
|
41
|
+
time_in_force: str = "gtc",
|
|
42
|
+
**options,
|
|
43
|
+
) -> SimulatedExecutionReport: ...
|
|
44
|
+
|
|
45
|
+
def cancel_order(self, order_id: str) -> SimulatedExecutionReport | None: ...
|
|
46
|
+
|
|
47
|
+
def get_open_orders(self, instrument: Instrument | None = None) -> dict[str, Order]: ...
|
|
48
|
+
|
|
49
|
+
def process_market_data(
|
|
50
|
+
self, instrument: Instrument, data: Quote | OrderBook | Trade | TradeArray
|
|
51
|
+
) -> Generator[SimulatedExecutionReport]: ...
|
|
52
|
+
|
|
53
|
+
def emulate_quote_from_data(
|
|
54
|
+
self, instrument: Instrument, timestamp: dt_64, data: float | Timestamped
|
|
55
|
+
) -> Quote | None:
|
|
56
|
+
"""
|
|
57
|
+
Emulate quote from data.
|
|
58
|
+
|
|
59
|
+
TODO: we need to get rid of this method in the future
|
|
60
|
+
"""
|
|
61
|
+
if instrument not in self._half_tick_size:
|
|
62
|
+
self._half_tick_size[instrument] = instrument.tick_size / 2 # type: ignore
|
|
63
|
+
|
|
64
|
+
if isinstance(data, Quote):
|
|
65
|
+
return data
|
|
66
|
+
|
|
67
|
+
elif isinstance(data, Trade):
|
|
68
|
+
_ts2 = self._half_tick_size[instrument]
|
|
69
|
+
if data.side == 1: # type: ignore
|
|
70
|
+
return Quote(timestamp, data.price - _ts2 * 2, data.price, 0, 0) # type: ignore
|
|
71
|
+
else:
|
|
72
|
+
return Quote(timestamp, data.price, data.price + _ts2 * 2, 0, 0) # type: ignore
|
|
73
|
+
|
|
74
|
+
elif isinstance(data, Bar):
|
|
75
|
+
_ts2 = self._half_tick_size[instrument]
|
|
76
|
+
return Quote(timestamp, data.close - _ts2, data.close + _ts2, 0, 0) # type: ignore
|
|
77
|
+
|
|
78
|
+
elif isinstance(data, OrderBook):
|
|
79
|
+
return data.to_quote()
|
|
80
|
+
|
|
81
|
+
elif isinstance(data, float):
|
|
82
|
+
_ts2 = self._half_tick_size[instrument]
|
|
83
|
+
return Quote(timestamp, data - _ts2, data + _ts2, 0, 0)
|
|
84
|
+
|
|
85
|
+
else:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class BasicSimulatedExchange(ISimulatedExchange):
|
|
90
|
+
"""
|
|
91
|
+
Basic implementation of generic crypto exchange.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
_ome: dict[Instrument, OrdersManagementEngine]
|
|
95
|
+
_order_to_instrument: dict[str, Instrument]
|
|
96
|
+
_fill_stop_order_at_price: bool
|
|
97
|
+
_time_provider: ITimeProvider
|
|
98
|
+
_tcc: TransactionCostsCalculator
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
exchange_id: str,
|
|
103
|
+
time_provider: ITimeProvider,
|
|
104
|
+
tcc: TransactionCostsCalculator = ZERO_COSTS,
|
|
105
|
+
accurate_stop_orders_execution: bool = False,
|
|
106
|
+
):
|
|
107
|
+
super().__init__(exchange_id)
|
|
108
|
+
self._ome = {}
|
|
109
|
+
self._order_to_instrument = {}
|
|
110
|
+
self._half_tick_size = {}
|
|
111
|
+
self._fill_stop_order_at_price = accurate_stop_orders_execution
|
|
112
|
+
self._time_provider = time_provider
|
|
113
|
+
self._tcc = tcc
|
|
114
|
+
if self._fill_stop_order_at_price:
|
|
115
|
+
logger.info(
|
|
116
|
+
f"[<y>{self.__class__.__name__}</y>] :: emulation of stop orders executions at exact price is ON"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def get_time_provider(self) -> ITimeProvider:
|
|
120
|
+
return self._time_provider
|
|
121
|
+
|
|
122
|
+
def get_transaction_costs_calculator(self) -> TransactionCostsCalculator:
|
|
123
|
+
return self._tcc
|
|
124
|
+
|
|
125
|
+
def place_order(
|
|
126
|
+
self,
|
|
127
|
+
instrument: Instrument,
|
|
128
|
+
order_side: str,
|
|
129
|
+
order_type: str,
|
|
130
|
+
amount: float,
|
|
131
|
+
price: float | None = None,
|
|
132
|
+
client_id: str | None = None,
|
|
133
|
+
time_in_force: str = "gtc",
|
|
134
|
+
**options,
|
|
135
|
+
) -> SimulatedExecutionReport:
|
|
136
|
+
# - try to place order in OME
|
|
137
|
+
return self._get_ome(instrument).place_order(
|
|
138
|
+
order_side.upper(), # type: ignore
|
|
139
|
+
order_type.upper(), # type: ignore
|
|
140
|
+
amount,
|
|
141
|
+
price,
|
|
142
|
+
client_id,
|
|
143
|
+
time_in_force,
|
|
144
|
+
**options,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def cancel_order(self, order_id: str) -> SimulatedExecutionReport | None:
|
|
148
|
+
# - first check in active orders
|
|
149
|
+
instrument = self._order_to_instrument.get(order_id)
|
|
150
|
+
|
|
151
|
+
if instrument is None:
|
|
152
|
+
# - if not found in active orders, check in each OME
|
|
153
|
+
for o in self._ome.values():
|
|
154
|
+
for order in o.get_open_orders():
|
|
155
|
+
if order.id == order_id:
|
|
156
|
+
return self._process_ome_response(o.cancel_order(order_id))
|
|
157
|
+
|
|
158
|
+
logger.error(
|
|
159
|
+
f"[<y>{self.__class__.__name__}</y>] :: cancel_order :: can't find order with id = 'ValueError{order_id}'!"
|
|
160
|
+
)
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
ome = self._ome.get(instrument)
|
|
164
|
+
if ome is None:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"{self.__class__.__name__}</y>] :: cancel_order :: No OME created for '{instrument}' - fatal error!"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# - cancel order in OME and remove from the map to free memory
|
|
170
|
+
return self._process_ome_response(ome.cancel_order(order_id))
|
|
171
|
+
|
|
172
|
+
def _process_ome_response(self, report: SimulatedExecutionReport | None) -> SimulatedExecutionReport | None:
|
|
173
|
+
if report is not None:
|
|
174
|
+
_order = report.order
|
|
175
|
+
_new = _order.status == "NEW"
|
|
176
|
+
_open = _order.status == "OPEN"
|
|
177
|
+
_cancel = _order.status == "CANCELED"
|
|
178
|
+
_closed = _order.status == "CLOSED"
|
|
179
|
+
|
|
180
|
+
if _new or _open:
|
|
181
|
+
self._order_to_instrument[_order.id] = _order.instrument
|
|
182
|
+
|
|
183
|
+
if (_cancel or _closed) and _order.id in self._order_to_instrument:
|
|
184
|
+
self._order_to_instrument.pop(_order.id)
|
|
185
|
+
|
|
186
|
+
return report
|
|
187
|
+
|
|
188
|
+
def get_open_orders(self, instrument: Instrument | None = None) -> dict[str, Order]:
|
|
189
|
+
if instrument is not None:
|
|
190
|
+
ome = self._get_ome(instrument)
|
|
191
|
+
return {o.id: o for o in ome.get_open_orders()}
|
|
192
|
+
|
|
193
|
+
return {o.id: o for ome in self._ome.values() for o in ome.get_open_orders()}
|
|
194
|
+
|
|
195
|
+
def _get_ome(self, instrument: Instrument) -> OrdersManagementEngine:
|
|
196
|
+
if (ome := self._ome.get(instrument)) is None:
|
|
197
|
+
self._half_tick_size[instrument] = instrument.tick_size / 2 # type: ignore
|
|
198
|
+
# - create order management engine for instrument
|
|
199
|
+
self._ome[instrument] = (
|
|
200
|
+
ome := OrdersManagementEngine(
|
|
201
|
+
instrument=instrument,
|
|
202
|
+
time_provider=self._time_provider,
|
|
203
|
+
tcc=self._tcc, # type: ignore
|
|
204
|
+
fill_stop_order_at_price=self._fill_stop_order_at_price,
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
return ome
|
|
208
|
+
|
|
209
|
+
def process_market_data(
|
|
210
|
+
self, instrument: Instrument, data: Quote | OrderBook | Trade | TradeArray
|
|
211
|
+
) -> Generator[SimulatedExecutionReport]:
|
|
212
|
+
ome = self._get_ome(instrument)
|
|
213
|
+
|
|
214
|
+
for r in ome.process_market_data(data):
|
|
215
|
+
if r.exec is not None:
|
|
216
|
+
if r.order.id in self._order_to_instrument:
|
|
217
|
+
self._order_to_instrument.pop(r.order.id)
|
|
218
|
+
yield r
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get_simulated_exchange(
|
|
222
|
+
exchange_name: str,
|
|
223
|
+
time_provider: ITimeProvider,
|
|
224
|
+
tcc: TransactionCostsCalculator,
|
|
225
|
+
accurate_stop_orders_execution=False,
|
|
226
|
+
) -> ISimulatedExchange:
|
|
227
|
+
"""
|
|
228
|
+
Factory function to create different types of simulated exchanges based on it's name etc
|
|
229
|
+
Now it supports only basic exchange that fits for most cases of crypto trading.
|
|
230
|
+
"""
|
|
231
|
+
return BasicSimulatedExchange(
|
|
232
|
+
exchange_name, time_provider, tcc, accurate_stop_orders_execution=accurate_stop_orders_execution
|
|
233
|
+
)
|