Qubx 0.6.29__tar.gz → 0.6.31__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.29 → qubx-0.6.31}/PKG-INFO +1 -1
- {qubx-0.6.29 → qubx-0.6.31}/pyproject.toml +1 -1
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/ome.py +14 -7
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/data.py +4 -3
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/exchanges/__init__.py +9 -1
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +5 -0
- qubx-0.6.31/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +169 -0
- qubx-0.6.31/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +45 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/factory.py +11 -2
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/utils.py +5 -1
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/context.py +2 -2
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/helpers.py +2 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/mixins/trading.py +3 -1
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/data/tardis.py +9 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/runner/runner.py +35 -30
- qubx-0.6.29/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -43
- {qubx-0.6.29 → qubx-0.6.31}/LICENSE +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/README.md +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/build.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/account.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/data.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/management.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/runner.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/cli/commands.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/cli/release.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/ccxt/reader.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/account.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/basics.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/deque.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/initializer.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/interfaces.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/metrics.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/series.pxd +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/series.pyi +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/series.pyx +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/data/composite.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/data/readers.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/data/registry.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/emitters/base.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/emitters/composite.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/emitters/csv.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/emitters/questdb.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/features/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/features/core.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/features/orderbook.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/features/price.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/features/trades.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/features/utils.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/health/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/health/base.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/resources/instruments/symbols-binance.json +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restorers/position.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/questdb.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/src/qubx/utils/time.py +0 -0
- {qubx-0.6.29 → qubx-0.6.31}/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.31"
|
|
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"
|
|
@@ -105,11 +105,13 @@ class OrdersManagementEngine:
|
|
|
105
105
|
if mdata.time < self._last_data_update_time_ns:
|
|
106
106
|
return _exec_report
|
|
107
107
|
self._last_data_update_time_ns = mdata.time
|
|
108
|
+
_mkt_state = "UNKNOWN"
|
|
108
109
|
|
|
109
110
|
# - new quote
|
|
110
111
|
if isinstance(mdata, Quote):
|
|
111
112
|
_b, _a = mdata.bid, mdata.ask
|
|
112
113
|
_bs, _as = _b, _a
|
|
114
|
+
_mkt_state = "Q: " + str(mdata)
|
|
113
115
|
|
|
114
116
|
# - update BBO by new quote
|
|
115
117
|
self.__prev_bbo = self.bbo
|
|
@@ -122,16 +124,19 @@ class OrdersManagementEngine:
|
|
|
122
124
|
_b = max_buy_price - self._tick_size
|
|
123
125
|
_a = min_sell_price + self._tick_size
|
|
124
126
|
_bs, _as = _a, _b
|
|
127
|
+
_mkt_state = "TA: " + str(mdata)
|
|
125
128
|
|
|
126
129
|
# - single trade
|
|
127
130
|
elif isinstance(mdata, Trade):
|
|
128
131
|
_b, _a = mdata.price - self._tick_size, mdata.price + self._tick_size
|
|
129
132
|
_bs, _as = _b, _a
|
|
133
|
+
_mkt_state = "T: " + str(mdata)
|
|
130
134
|
|
|
131
135
|
# - order book
|
|
132
136
|
elif isinstance(mdata, OrderBook):
|
|
133
137
|
_b, _a = mdata.top_bid, mdata.top_ask
|
|
134
138
|
_bs, _as = _b, _a
|
|
139
|
+
_mkt_state = "OB: " + str(mdata)
|
|
135
140
|
|
|
136
141
|
else:
|
|
137
142
|
raise SimulationError(f"Invalid market data type: {type(mdata)} for update OME({self.instrument.symbol})")
|
|
@@ -142,7 +147,7 @@ class OrdersManagementEngine:
|
|
|
142
147
|
for level in _asks_to_execute:
|
|
143
148
|
for order_id in self.asks[level]:
|
|
144
149
|
order = self.active_orders.pop(order_id)
|
|
145
|
-
_exec_report.append(self._execute_order(timestamp, order.price, order, False))
|
|
150
|
+
_exec_report.append(self._execute_order(timestamp, order.price, order, False, _mkt_state))
|
|
146
151
|
self.asks.pop(level)
|
|
147
152
|
|
|
148
153
|
# - when new quote ask is lower than the highest bid order execute all affected orders
|
|
@@ -151,7 +156,7 @@ class OrdersManagementEngine:
|
|
|
151
156
|
for level in _bids_to_execute:
|
|
152
157
|
for order_id in self.bids[level]:
|
|
153
158
|
order = self.active_orders.pop(order_id)
|
|
154
|
-
_exec_report.append(self._execute_order(timestamp, order.price, order, False))
|
|
159
|
+
_exec_report.append(self._execute_order(timestamp, order.price, order, False, _mkt_state))
|
|
155
160
|
self.bids.pop(level)
|
|
156
161
|
|
|
157
162
|
# - processing stop orders
|
|
@@ -162,12 +167,12 @@ class OrdersManagementEngine:
|
|
|
162
167
|
if so.side == "BUY" and _as >= so.price:
|
|
163
168
|
_exec_price = _as if not _emulate_price_exec else so.price
|
|
164
169
|
self.stop_orders.pop(soid)
|
|
165
|
-
_exec_report.append(self._execute_order(timestamp, _exec_price, so, True))
|
|
170
|
+
_exec_report.append(self._execute_order(timestamp, _exec_price, so, True, _mkt_state))
|
|
166
171
|
|
|
167
172
|
elif so.side == "SELL" and _bs <= so.price:
|
|
168
173
|
_exec_price = _bs if not _emulate_price_exec else so.price
|
|
169
174
|
self.stop_orders.pop(soid)
|
|
170
|
-
_exec_report.append(self._execute_order(timestamp, _exec_price, so, True))
|
|
175
|
+
_exec_report.append(self._execute_order(timestamp, _exec_price, so, True, _mkt_state))
|
|
171
176
|
|
|
172
177
|
self._last_update_time = timestamp
|
|
173
178
|
return _exec_report
|
|
@@ -268,7 +273,7 @@ class OrdersManagementEngine:
|
|
|
268
273
|
|
|
269
274
|
# - if order must be "executed" immediately
|
|
270
275
|
if _exec_price is not None:
|
|
271
|
-
return self._execute_order(timestamp, _exec_price, order, True)
|
|
276
|
+
return self._execute_order(timestamp, _exec_price, order, True, "BBO: " + str(self.bbo))
|
|
272
277
|
|
|
273
278
|
# - processing limit orders
|
|
274
279
|
if _need_update_book:
|
|
@@ -284,10 +289,12 @@ class OrdersManagementEngine:
|
|
|
284
289
|
return SimulatedExecutionReport(self.instrument, timestamp, order, None)
|
|
285
290
|
|
|
286
291
|
def _execute_order(
|
|
287
|
-
self, timestamp: dt_64, exec_price: float, order: Order, taker: bool
|
|
292
|
+
self, timestamp: dt_64, exec_price: float, order: Order, taker: bool, market_state: str
|
|
288
293
|
) -> SimulatedExecutionReport:
|
|
289
294
|
order.status = "CLOSED"
|
|
290
|
-
self._dbg(
|
|
295
|
+
self._dbg(
|
|
296
|
+
f"<red>{order.id}</red> {order.type} {order.side} {order.quantity} executed at {exec_price} ::: {market_state}"
|
|
297
|
+
)
|
|
291
298
|
return SimulatedExecutionReport(
|
|
292
299
|
self.instrument,
|
|
293
300
|
timestamp,
|
|
@@ -616,9 +616,10 @@ class CcxtDataProvider(IDataProvider):
|
|
|
616
616
|
for exch_symbol, ccxt_ticker in ccxt_tickers.items(): # type: ignore
|
|
617
617
|
instrument = ccxt_find_instrument(exch_symbol, self._exchange, _symbol_to_instrument)
|
|
618
618
|
quote = ccxt_convert_ticker(ccxt_ticker)
|
|
619
|
-
self.
|
|
620
|
-
|
|
621
|
-
|
|
619
|
+
if self._last_quotes[instrument] is None or quote.time > self._last_quotes[instrument].time:
|
|
620
|
+
self._health_monitor.record_data_arrival(sub_type, dt_64(quote.time, "ns"))
|
|
621
|
+
self._last_quotes[instrument] = quote
|
|
622
|
+
channel.send((instrument, sub_type, quote, False))
|
|
622
623
|
|
|
623
624
|
async def un_watch_quote(instruments: list[Instrument]):
|
|
624
625
|
symbols = [_instr_to_ccxt_symbol[i] for i in instruments]
|
|
@@ -8,6 +8,8 @@ import ccxt.pro as cxp
|
|
|
8
8
|
|
|
9
9
|
from .binance.broker import BinanceCcxtBroker
|
|
10
10
|
from .binance.exchange import BINANCE_UM_MM, BinancePortfolioMargin, BinanceQV, BinanceQVUSDM
|
|
11
|
+
from .bitfinex.bitfinex import BitfinexF
|
|
12
|
+
from .bitfinex.bitfinex_account import BitfinexAccountProcessor
|
|
11
13
|
from .kraken.kraken import CustomKrakenFutures
|
|
12
14
|
|
|
13
15
|
EXCHANGE_ALIASES = {
|
|
@@ -17,7 +19,7 @@ EXCHANGE_ALIASES = {
|
|
|
17
19
|
"binance.pm": "binancepm",
|
|
18
20
|
"kraken.f": "custom_krakenfutures",
|
|
19
21
|
"binance.um.mm": "binance_um_mm",
|
|
20
|
-
"bitfinex.f": "
|
|
22
|
+
"bitfinex.f": "bitfinex_f",
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
CUSTOM_BROKERS = {
|
|
@@ -27,11 +29,16 @@ CUSTOM_BROKERS = {
|
|
|
27
29
|
"binance.pm": partial(BinanceCcxtBroker, enable_create_order_ws=False, enable_cancel_order_ws=False),
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
CUSTOM_ACCOUNTS = {
|
|
33
|
+
"bitfinex.f": BitfinexAccountProcessor,
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
cxp.binanceqv = BinanceQV # type: ignore
|
|
31
37
|
cxp.binanceqv_usdm = BinanceQVUSDM # type: ignore
|
|
32
38
|
cxp.binancepm = BinancePortfolioMargin # type: ignore
|
|
33
39
|
cxp.binance_um_mm = BINANCE_UM_MM # type: ignore
|
|
34
40
|
cxp.custom_krakenfutures = CustomKrakenFutures # type: ignore
|
|
41
|
+
cxp.bitfinex_f = BitfinexF # type: ignore
|
|
35
42
|
|
|
36
43
|
cxp.exchanges.append("binanceqv")
|
|
37
44
|
cxp.exchanges.append("binanceqv_usdm")
|
|
@@ -39,3 +46,4 @@ cxp.exchanges.append("binancepm")
|
|
|
39
46
|
cxp.exchanges.append("binancepm_usdm")
|
|
40
47
|
cxp.exchanges.append("binance_um_mm")
|
|
41
48
|
cxp.exchanges.append("custom_krakenfutures")
|
|
49
|
+
cxp.exchanges.append("bitfinex_f")
|
|
@@ -202,7 +202,12 @@ class BinanceQV(cxp.binance):
|
|
|
202
202
|
executionType = self.safe_string(message, "X")
|
|
203
203
|
if executionType == "INSURANCE_FUND":
|
|
204
204
|
return
|
|
205
|
+
|
|
206
|
+
# - fix 2025-04-16: filter out trades with zero price
|
|
205
207
|
trade = self.parse_ws_trade(message, market)
|
|
208
|
+
if trade["price"] == 0.0:
|
|
209
|
+
return
|
|
210
|
+
|
|
206
211
|
tradesArray = self.safe_value(self.trades, symbol)
|
|
207
212
|
if tradesArray is None:
|
|
208
213
|
limit = self.safe_integer(self.options, "tradesLimit", 1000)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
import ccxt.pro as cxp
|
|
4
|
+
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheByTimestamp
|
|
5
|
+
from ccxt.async_support.base.ws.client import Client
|
|
6
|
+
from ccxt.base.errors import ArgumentsRequired, BadRequest, NotSupported
|
|
7
|
+
from ccxt.base.precise import Precise
|
|
8
|
+
from ccxt.base.types import (
|
|
9
|
+
Any,
|
|
10
|
+
Balances,
|
|
11
|
+
Int,
|
|
12
|
+
Market,
|
|
13
|
+
Num,
|
|
14
|
+
Order,
|
|
15
|
+
OrderSide,
|
|
16
|
+
OrderType,
|
|
17
|
+
Str,
|
|
18
|
+
Strings,
|
|
19
|
+
Tickers,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BitfinexF(cxp.bitfinex):
|
|
24
|
+
"""
|
|
25
|
+
Extended binance exchange to provide quote asset volumes support
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def describe(self):
|
|
29
|
+
"""
|
|
30
|
+
Overriding watchTrades to use aggTrade instead of trade.
|
|
31
|
+
"""
|
|
32
|
+
return self.deep_extend(
|
|
33
|
+
super().describe(),
|
|
34
|
+
{
|
|
35
|
+
"has": {
|
|
36
|
+
"watchBidsAsks": True,
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def watch_bids_asks(self, symbol: str, params: dict = {}):
|
|
42
|
+
return self.watch_order_book(symbol, None, params)
|
|
43
|
+
|
|
44
|
+
def un_watch_bids_asks(self, symbol: str, params: dict = {}):
|
|
45
|
+
if hasattr(self, "un_watch_order_book"):
|
|
46
|
+
return self.un_watch_order_book(symbol, params)
|
|
47
|
+
|
|
48
|
+
async def fetch_balance(self, params={}) -> Balances:
|
|
49
|
+
params["type"] = "margin"
|
|
50
|
+
return await super().fetch_balance(params)
|
|
51
|
+
|
|
52
|
+
async def create_order(
|
|
53
|
+
self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}
|
|
54
|
+
):
|
|
55
|
+
params.pop("type", None)
|
|
56
|
+
if "timeInForce" in params and params["timeInForce"] == "GTX":
|
|
57
|
+
# GTX is not supported by bitfinex, so we need to convert it to PO
|
|
58
|
+
params["timeInForce"] = "PO"
|
|
59
|
+
params["postOnly"] = True
|
|
60
|
+
response = await super().create_order(symbol, type, side, amount, price, params)
|
|
61
|
+
return response
|
|
62
|
+
|
|
63
|
+
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
64
|
+
response = await super().watch_orders(symbol, since, limit, params)
|
|
65
|
+
print(response)
|
|
66
|
+
return response
|
|
67
|
+
|
|
68
|
+
def parse_ws_order_status(self, status):
|
|
69
|
+
print(status)
|
|
70
|
+
statuses: dict = {
|
|
71
|
+
"ACTIVE": "open",
|
|
72
|
+
"CANCELED": "canceled",
|
|
73
|
+
"EXECUTED": "closed",
|
|
74
|
+
"PARTIALLY": "open",
|
|
75
|
+
"POSTONLY": "canceled", # this status means that a postonly order got canceled on submission
|
|
76
|
+
}
|
|
77
|
+
return self.safe_string(statuses, status, status)
|
|
78
|
+
|
|
79
|
+
def parse_order_status(self, status: Str):
|
|
80
|
+
if status is None:
|
|
81
|
+
return status
|
|
82
|
+
parts = status.split(" ")
|
|
83
|
+
state = self.safe_string(parts, 0)
|
|
84
|
+
statuses: dict = {
|
|
85
|
+
"ACTIVE": "open",
|
|
86
|
+
"PARTIALLY": "open",
|
|
87
|
+
"EXECUTED": "closed",
|
|
88
|
+
"CANCELED": "canceled",
|
|
89
|
+
"INSUFFICIENT": "canceled",
|
|
90
|
+
"POSTONLY CANCELED": "canceled",
|
|
91
|
+
"POSTONLY": "canceled",
|
|
92
|
+
"RSN_DUST": "rejected",
|
|
93
|
+
"RSN_PAUSE": "rejected",
|
|
94
|
+
"IOC CANCELED": "canceled",
|
|
95
|
+
"FILLORKILL CANCELED": "canceled",
|
|
96
|
+
}
|
|
97
|
+
return self.safe_string(statuses, state, status)
|
|
98
|
+
|
|
99
|
+
def parse_position(self, position: dict, market: Market = None):
|
|
100
|
+
#
|
|
101
|
+
# [
|
|
102
|
+
# "tBTCUSD", # SYMBOL
|
|
103
|
+
# "ACTIVE", # STATUS
|
|
104
|
+
# 0.0195, # AMOUNT
|
|
105
|
+
# 8565.0267019, # BASE_PRICE
|
|
106
|
+
# 0, # MARGIN_FUNDING
|
|
107
|
+
# 0, # MARGIN_FUNDING_TYPE
|
|
108
|
+
# -0.33455568705000516, # PL
|
|
109
|
+
# -0.0003117550117425625, # PL_PERC
|
|
110
|
+
# 7045.876419249083, # PRICE_LIQ
|
|
111
|
+
# 3.0673001895895604, # LEVERAGE
|
|
112
|
+
# null, # _PLACEHOLDER
|
|
113
|
+
# 142355652, # POSITION_ID
|
|
114
|
+
# 1574002216000, # MTS_CREATE
|
|
115
|
+
# 1574002216000, # MTS_UPDATE
|
|
116
|
+
# null, # _PLACEHOLDER
|
|
117
|
+
# 0, # TYPE
|
|
118
|
+
# null, # _PLACEHOLDER
|
|
119
|
+
# 0, # COLLATERAL
|
|
120
|
+
# 0, # COLLATERAL_MIN
|
|
121
|
+
# # META
|
|
122
|
+
# {
|
|
123
|
+
# "reason": "TRADE",
|
|
124
|
+
# "order_id": 34271018124,
|
|
125
|
+
# "liq_stage": null,
|
|
126
|
+
# "trade_price": "8565.0267019",
|
|
127
|
+
# "trade_amount": "0.0195",
|
|
128
|
+
# "order_id_oppo": 34277498022
|
|
129
|
+
# }
|
|
130
|
+
# ]
|
|
131
|
+
#
|
|
132
|
+
positionList = self.safe_list(position, "result")
|
|
133
|
+
marketId = self.safe_string(positionList, 0)
|
|
134
|
+
amount = self.safe_string(positionList, 2)
|
|
135
|
+
timestamp = self.safe_integer(positionList, 12)
|
|
136
|
+
meta = self.safe_string(positionList, 19)
|
|
137
|
+
tradePrice = self.safe_string(meta, "trade_price")
|
|
138
|
+
tradeAmount = self.safe_string(meta, "trade_amount")
|
|
139
|
+
return self.safe_position(
|
|
140
|
+
{
|
|
141
|
+
"info": positionList,
|
|
142
|
+
"id": self.safe_string(positionList, 11),
|
|
143
|
+
"symbol": self.safe_symbol(marketId, market),
|
|
144
|
+
"notional": None,
|
|
145
|
+
"marginMode": "isolated", # derivatives use isolated, margin uses cross, https://support.bitfinex.com/hc/en-us/articles/360035475374-Derivatives-Trading-on-Bitfinex
|
|
146
|
+
"liquidationPrice": self.safe_number(positionList, 8),
|
|
147
|
+
"entryPrice": self.safe_number(positionList, 3),
|
|
148
|
+
"unrealizedPnl": self.safe_number(positionList, 6),
|
|
149
|
+
"percentage": self.safe_number(positionList, 7),
|
|
150
|
+
"contracts": self.parse_number(amount),
|
|
151
|
+
"contractSize": None,
|
|
152
|
+
"markPrice": None,
|
|
153
|
+
"lastPrice": None,
|
|
154
|
+
"side": "long" if Precise.string_gt(amount, "0") else "short",
|
|
155
|
+
"hedged": None,
|
|
156
|
+
"timestamp": timestamp,
|
|
157
|
+
"datetime": self.iso8601(timestamp),
|
|
158
|
+
"lastUpdateTimestamp": self.safe_integer(positionList, 13),
|
|
159
|
+
"maintenanceMargin": self.safe_number(positionList, 18),
|
|
160
|
+
"maintenanceMarginPercentage": None,
|
|
161
|
+
"collateral": self.safe_number(positionList, 17),
|
|
162
|
+
"initialMargin": self.parse_number(Precise.string_mul(tradeAmount, tradePrice)),
|
|
163
|
+
"initialMarginPercentage": None,
|
|
164
|
+
"leverage": self.safe_number(positionList, 9),
|
|
165
|
+
"marginRatio": None,
|
|
166
|
+
"stopLossPrice": None,
|
|
167
|
+
"takeProfitPrice": None,
|
|
168
|
+
}
|
|
169
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from qubx.connectors.ccxt.account import CcxtAccountProcessor
|
|
4
|
+
from qubx.connectors.ccxt.utils import (
|
|
5
|
+
ccxt_convert_order_info,
|
|
6
|
+
ccxt_extract_deals_from_exec,
|
|
7
|
+
ccxt_find_instrument,
|
|
8
|
+
)
|
|
9
|
+
from qubx.core.basics import CtrlChannel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BitfinexAccountProcessor(CcxtAccountProcessor):
|
|
13
|
+
async def _subscribe_executions(self, name: str, channel: CtrlChannel):
|
|
14
|
+
_symbol_to_instrument = {}
|
|
15
|
+
|
|
16
|
+
async def _watch_orders():
|
|
17
|
+
orders = await self.exchange.watch_orders()
|
|
18
|
+
for order in orders:
|
|
19
|
+
instrument = ccxt_find_instrument(order["symbol"], self.exchange, _symbol_to_instrument)
|
|
20
|
+
order = ccxt_convert_order_info(instrument, order)
|
|
21
|
+
channel.send((instrument, "order", order, False))
|
|
22
|
+
|
|
23
|
+
async def _watch_my_trades():
|
|
24
|
+
trades = await self.exchange.watch_my_trades()
|
|
25
|
+
for trade in trades: # type: ignore
|
|
26
|
+
instrument = ccxt_find_instrument(trade["symbol"], self.exchange, _symbol_to_instrument)
|
|
27
|
+
print(trade)
|
|
28
|
+
deals = ccxt_extract_deals_from_exec({"trades": [trade]})
|
|
29
|
+
print(deals)
|
|
30
|
+
channel.send((instrument, "deals", deals, False))
|
|
31
|
+
|
|
32
|
+
await asyncio.gather(
|
|
33
|
+
self._listen_to_stream(
|
|
34
|
+
subscriber=_watch_orders,
|
|
35
|
+
exchange=self.exchange,
|
|
36
|
+
channel=channel,
|
|
37
|
+
name=name,
|
|
38
|
+
),
|
|
39
|
+
self._listen_to_stream(
|
|
40
|
+
subscriber=_watch_my_trades,
|
|
41
|
+
exchange=self.exchange,
|
|
42
|
+
channel=channel,
|
|
43
|
+
name=name,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
@@ -7,7 +7,8 @@ from qubx.connectors.ccxt.broker import CcxtBroker
|
|
|
7
7
|
from qubx.core.basics import CtrlChannel
|
|
8
8
|
from qubx.core.interfaces import IAccountProcessor, IBroker, IDataProvider, ITimeProvider
|
|
9
9
|
|
|
10
|
-
from .
|
|
10
|
+
from .account import CcxtAccountProcessor
|
|
11
|
+
from .exchanges import CUSTOM_ACCOUNTS, CUSTOM_BROKERS, EXCHANGE_ALIASES
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def get_ccxt_exchange(
|
|
@@ -69,10 +70,18 @@ def get_ccxt_broker(
|
|
|
69
70
|
data_provider: IDataProvider,
|
|
70
71
|
**kwargs,
|
|
71
72
|
) -> IBroker:
|
|
72
|
-
broker_cls = CUSTOM_BROKERS.get(exchange_name, CcxtBroker)
|
|
73
|
+
broker_cls = CUSTOM_BROKERS.get(exchange_name.lower(), CcxtBroker)
|
|
73
74
|
return broker_cls(exchange, channel, time_provider, account, data_provider, **kwargs)
|
|
74
75
|
|
|
75
76
|
|
|
77
|
+
def get_ccxt_account(
|
|
78
|
+
exchange_name: str,
|
|
79
|
+
**kwargs,
|
|
80
|
+
) -> IAccountProcessor:
|
|
81
|
+
account_cls = CUSTOM_ACCOUNTS.get(exchange_name.lower(), CcxtAccountProcessor)
|
|
82
|
+
return account_cls(**kwargs)
|
|
83
|
+
|
|
84
|
+
|
|
76
85
|
def _get_api_credentials(
|
|
77
86
|
api_key: str | None, secret: str | None, kwargs: dict[str, Any]
|
|
78
87
|
) -> tuple[str | None, str | None]:
|
|
@@ -38,6 +38,9 @@ def ccxt_convert_order_info(instrument: Instrument, raw: dict[str, Any]) -> Orde
|
|
|
38
38
|
Convert CCXT excution record to Order object
|
|
39
39
|
"""
|
|
40
40
|
ri = raw["info"]
|
|
41
|
+
if isinstance(ri, list):
|
|
42
|
+
# we don't handle case when order info is a list
|
|
43
|
+
ri = {}
|
|
41
44
|
amnt = float(ri.get("origQty", raw.get("amount")))
|
|
42
45
|
price = raw["price"]
|
|
43
46
|
status = raw["status"]
|
|
@@ -157,7 +160,8 @@ def ccxt_convert_positions(
|
|
|
157
160
|
quantity=info["contracts"] * (-1 if info["side"] == "short" else 1),
|
|
158
161
|
pos_average_price=info["entryPrice"],
|
|
159
162
|
)
|
|
160
|
-
|
|
163
|
+
if info.get("markPrice", None) is not None:
|
|
164
|
+
pos.update_market_price(pd.Timestamp(info["timestamp"], unit="ms").asm8, info["markPrice"], 1)
|
|
161
165
|
positions.append(pos)
|
|
162
166
|
return positions
|
|
163
167
|
|
|
@@ -430,8 +430,8 @@ class StrategyContext(IStrategyContext):
|
|
|
430
430
|
def close_positions(self, market_type: MarketType | None = None) -> None:
|
|
431
431
|
return self._trading_manager.close_positions(market_type)
|
|
432
432
|
|
|
433
|
-
def cancel_order(self, order_id: str) -> None:
|
|
434
|
-
return self._trading_manager.cancel_order(order_id)
|
|
433
|
+
def cancel_order(self, order_id: str, exchange: str | None = None) -> None:
|
|
434
|
+
return self._trading_manager.cancel_order(order_id, exchange)
|
|
435
435
|
|
|
436
436
|
def cancel_orders(self, instrument: Instrument):
|
|
437
437
|
return self._trading_manager.cancel_orders(instrument)
|
|
@@ -202,6 +202,8 @@ class CachedMarketDataHolder:
|
|
|
202
202
|
series = self._ohlcvs.get(instrument)
|
|
203
203
|
if series:
|
|
204
204
|
for ser in series.values():
|
|
205
|
+
if len(ser) > 0 and ser[0].time > quote.time:
|
|
206
|
+
continue
|
|
205
207
|
ser.update(quote.time, quote.mid_price(), 0)
|
|
206
208
|
|
|
207
209
|
@SW.watch("CachedMarketDataHolder")
|
|
@@ -180,7 +180,9 @@ class TradingManager(ITradingManager):
|
|
|
180
180
|
def _adjust_size(self, instrument: Instrument, amount: float) -> float:
|
|
181
181
|
size_adj = instrument.round_size_down(abs(amount))
|
|
182
182
|
if size_adj < instrument.min_size:
|
|
183
|
-
raise ValueError(
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"[{instrument.symbol}] Attempt to trade size {abs(amount)} less than minimal allowed {instrument.min_size} !"
|
|
185
|
+
)
|
|
184
186
|
return size_adj
|
|
185
187
|
|
|
186
188
|
def _adjust_price(self, instrument: Instrument, price: float | None, amount: float) -> float | None:
|
|
@@ -529,6 +529,7 @@ class TardisMachineReader(DataReader):
|
|
|
529
529
|
raise ValueError(f"Invalid data_id format: {data_id}. Expected format: 'exchange:symbol'")
|
|
530
530
|
|
|
531
531
|
exchange = TARDIS_EXCHANGE_MAPPERS.get(exchange.lower(), exchange)
|
|
532
|
+
symbol = self._map_symbol(symbol, exchange)
|
|
532
533
|
|
|
533
534
|
start_date, end_date = handle_start_stop(start, stop)
|
|
534
535
|
if not start_date:
|
|
@@ -607,6 +608,7 @@ class TardisMachineReader(DataReader):
|
|
|
607
608
|
try:
|
|
608
609
|
chunk = chunk_queue.get()
|
|
609
610
|
if chunk is None: # End of chunks
|
|
611
|
+
logger.info(f"End of chunks for {data_id}")
|
|
610
612
|
break
|
|
611
613
|
|
|
612
614
|
# Process and yield the chunk
|
|
@@ -877,3 +879,10 @@ class TardisMachineReader(DataReader):
|
|
|
877
879
|
def _stream_data(self, url: str, line_queue: Queue, stop_event: threading.Event):
|
|
878
880
|
"""Submit the streaming coroutine to the asyncio loop"""
|
|
879
881
|
return self._async_loop.submit(self._fetch_stream(url, line_queue, stop_event))
|
|
882
|
+
|
|
883
|
+
def _map_symbol(self, symbol: str, exchange: str) -> str:
|
|
884
|
+
"""Map symbol to Tardis Machine API format"""
|
|
885
|
+
if exchange.lower() == "bitfinex-derivatives":
|
|
886
|
+
return f"{symbol[:3]}F0:USTF0"
|
|
887
|
+
else:
|
|
888
|
+
return symbol
|
|
@@ -21,9 +21,8 @@ from qubx.backtester.utils import (
|
|
|
21
21
|
SimulationSetup,
|
|
22
22
|
recognize_simulation_data_config,
|
|
23
23
|
)
|
|
24
|
-
from qubx.connectors.ccxt.account import CcxtAccountProcessor
|
|
25
24
|
from qubx.connectors.ccxt.data import CcxtDataProvider
|
|
26
|
-
from qubx.connectors.ccxt.factory import get_ccxt_broker, get_ccxt_exchange
|
|
25
|
+
from qubx.connectors.ccxt.factory import get_ccxt_account, get_ccxt_broker, get_ccxt_exchange
|
|
27
26
|
from qubx.connectors.tardis.data import TardisDataProvider
|
|
28
27
|
from qubx.core.account import CompositeAccountProcessor
|
|
29
28
|
from qubx.core.basics import (
|
|
@@ -464,40 +463,43 @@ def _create_account_processor(
|
|
|
464
463
|
restored_state: RestoredState | None = None,
|
|
465
464
|
read_only: bool = False,
|
|
466
465
|
) -> IAccountProcessor:
|
|
467
|
-
if paper:
|
|
468
|
-
settings = account_manager.get_exchange_settings(exchange_name)
|
|
469
|
-
|
|
470
|
-
# - TODO: here we can create different types of simulated exchanges based on it's name etc
|
|
471
|
-
simulated_exchange = get_simulated_exchange(exchange_name, time_provider, tcc)
|
|
472
|
-
|
|
473
|
-
return SimulatedAccountProcessor(
|
|
474
|
-
account_id=exchange_name,
|
|
475
|
-
exchange=simulated_exchange,
|
|
476
|
-
channel=channel,
|
|
477
|
-
base_currency=settings.base_currency,
|
|
478
|
-
initial_capital=settings.initial_capital,
|
|
479
|
-
restored_state=restored_state,
|
|
480
|
-
)
|
|
481
|
-
|
|
482
|
-
creds = account_manager.get_exchange_credentials(exchange_name)
|
|
483
|
-
connector = exchange_config.connector
|
|
484
466
|
if exchange_config.account is not None:
|
|
485
467
|
connector = exchange_config.account.connector
|
|
468
|
+
elif paper:
|
|
469
|
+
connector = "paper"
|
|
470
|
+
else:
|
|
471
|
+
connector = exchange_config.connector
|
|
486
472
|
|
|
487
473
|
match connector.lower():
|
|
488
474
|
case "ccxt":
|
|
475
|
+
creds = account_manager.get_exchange_credentials(exchange_name)
|
|
489
476
|
exchange = get_ccxt_exchange(
|
|
490
477
|
exchange_name, use_testnet=creds.testnet, api_key=creds.api_key, secret=creds.secret
|
|
491
478
|
)
|
|
492
|
-
return
|
|
479
|
+
return get_ccxt_account(
|
|
493
480
|
exchange_name,
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
481
|
+
account_id=exchange_name,
|
|
482
|
+
exchange=exchange,
|
|
483
|
+
channel=channel,
|
|
484
|
+
time_provider=time_provider,
|
|
497
485
|
base_currency=creds.base_currency,
|
|
498
486
|
tcc=tcc,
|
|
499
487
|
read_only=read_only,
|
|
500
488
|
)
|
|
489
|
+
case "paper":
|
|
490
|
+
settings = account_manager.get_exchange_settings(exchange_name)
|
|
491
|
+
|
|
492
|
+
# - TODO: here we can create different types of simulated exchanges based on it's name etc
|
|
493
|
+
simulated_exchange = get_simulated_exchange(exchange_name, time_provider, tcc)
|
|
494
|
+
|
|
495
|
+
return SimulatedAccountProcessor(
|
|
496
|
+
account_id=exchange_name,
|
|
497
|
+
exchange=simulated_exchange,
|
|
498
|
+
channel=channel,
|
|
499
|
+
base_currency=settings.base_currency,
|
|
500
|
+
initial_capital=settings.initial_capital,
|
|
501
|
+
restored_state=restored_state,
|
|
502
|
+
)
|
|
501
503
|
case _:
|
|
502
504
|
raise ValueError(f"Connector {exchange_config.connector} is not supported yet !")
|
|
503
505
|
|
|
@@ -512,19 +514,19 @@ def _create_broker(
|
|
|
512
514
|
account_manager: AccountConfigurationManager,
|
|
513
515
|
paper: bool,
|
|
514
516
|
) -> IBroker:
|
|
515
|
-
if paper:
|
|
516
|
-
assert isinstance(account, SimulatedAccountProcessor)
|
|
517
|
-
return SimulatedBroker(channel=channel, account=account, simulated_exchange=account._exchange)
|
|
518
|
-
|
|
519
|
-
creds = account_manager.get_exchange_credentials(exchange_name)
|
|
520
|
-
connector = exchange_config.connector
|
|
521
|
-
params = exchange_config.params
|
|
522
517
|
if exchange_config.broker is not None:
|
|
523
518
|
connector = exchange_config.broker.connector
|
|
524
519
|
params = exchange_config.broker.params
|
|
520
|
+
elif paper:
|
|
521
|
+
connector = "paper"
|
|
522
|
+
params = {}
|
|
523
|
+
else:
|
|
524
|
+
connector = exchange_config.connector
|
|
525
|
+
params = exchange_config.params
|
|
525
526
|
|
|
526
527
|
match connector.lower():
|
|
527
528
|
case "ccxt":
|
|
529
|
+
creds = account_manager.get_exchange_credentials(exchange_name)
|
|
528
530
|
_enable_mm = params.pop("enable_mm", False)
|
|
529
531
|
exchange = get_ccxt_exchange(
|
|
530
532
|
exchange_name,
|
|
@@ -536,6 +538,9 @@ def _create_broker(
|
|
|
536
538
|
return get_ccxt_broker(
|
|
537
539
|
exchange_name, exchange, channel, time_provider, account, data_provider, **exchange_config.params
|
|
538
540
|
)
|
|
541
|
+
case "paper":
|
|
542
|
+
assert isinstance(account, SimulatedAccountProcessor)
|
|
543
|
+
return SimulatedBroker(channel=channel, account=account, simulated_exchange=account._exchange)
|
|
539
544
|
case _:
|
|
540
545
|
raise ValueError(f"Connector {exchange_config.connector} is not supported yet !")
|
|
541
546
|
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
2
|
-
|
|
3
|
-
import ccxt.pro as cxp
|
|
4
|
-
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheByTimestamp
|
|
5
|
-
from ccxt.async_support.base.ws.client import Client
|
|
6
|
-
from ccxt.base.errors import ArgumentsRequired, BadRequest, NotSupported
|
|
7
|
-
from ccxt.base.precise import Precise
|
|
8
|
-
from ccxt.base.types import (
|
|
9
|
-
Any,
|
|
10
|
-
Balances,
|
|
11
|
-
Num,
|
|
12
|
-
Order,
|
|
13
|
-
OrderSide,
|
|
14
|
-
OrderType,
|
|
15
|
-
Strings,
|
|
16
|
-
Tickers,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class MyBitfinex(cxp.bitfinex):
|
|
21
|
-
"""
|
|
22
|
-
Extended binance exchange to provide quote asset volumes support
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def describe(self):
|
|
26
|
-
"""
|
|
27
|
-
Overriding watchTrades to use aggTrade instead of trade.
|
|
28
|
-
"""
|
|
29
|
-
return self.deep_extend(
|
|
30
|
-
super().describe(),
|
|
31
|
-
{
|
|
32
|
-
"has": {
|
|
33
|
-
"watchBidsAsks": True,
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
def watch_bids_asks(self, symbol: str, params: dict = {}):
|
|
39
|
-
return self.watch_order_book(symbol, None, params)
|
|
40
|
-
|
|
41
|
-
def un_watch_bids_asks(self, symbol: str, params: dict = {}):
|
|
42
|
-
if hasattr(self, "un_watch_order_book"):
|
|
43
|
-
return self.un_watch_order_book(symbol, params)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|