Qubx 0.6.20__tar.gz → 0.6.21__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.21}/PKG-INFO +1 -1
- {qubx-0.6.20 → qubx-0.6.21}/pyproject.toml +1 -1
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/broker.py +25 -14
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +4 -4
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/trading.py +57 -9
- {qubx-0.6.20 → qubx-0.6.21}/LICENSE +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/README.md +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/build.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/_nb_magic.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/account.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/broker.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/data.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/management.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/ome.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/optimization.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/runner.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/simulator.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/commands.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/deploy.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/misc.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/release.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/reader.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/account.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/basics.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/context.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/deque.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/errors.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/exceptions.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/helpers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/initializer.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/interfaces.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/loggers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/lookups.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/metrics.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/market.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/series.pxd +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/series.pyi +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/series.pyx +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/utils.pyi +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/utils.pyx +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/helpers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/hft.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/readers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/registry.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/tardis.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/base.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/csv.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/questdb.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/slack.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/core.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/orderbook.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/price.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/trades.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/gathering/simplest.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/health/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/health/base.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/math/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/math/stats.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/notifications/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/notifications/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/notifications/slack.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/pandaz/ta.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/pandaz/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/_build.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-binance.json +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restarts/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/balance.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/factory.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/position.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/signal.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/state.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/ta/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/advanced.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/composite.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/sizers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/collections.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/misc.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/ntp.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/orderbook.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/runner.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/time.py +0 -0
- {qubx-0.6.20 → qubx-0.6.21}/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.21"
|
|
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"
|
|
@@ -12,6 +12,7 @@ from qubx.core.basics import (
|
|
|
12
12
|
CtrlChannel,
|
|
13
13
|
Instrument,
|
|
14
14
|
Order,
|
|
15
|
+
OrderSide,
|
|
15
16
|
)
|
|
16
17
|
from qubx.core.errors import OrderCancellationError, OrderCreationError, create_error_event
|
|
17
18
|
from qubx.core.exceptions import BadRequest, InvalidOrderParameters
|
|
@@ -63,7 +64,7 @@ class CcxtBroker(IBroker):
|
|
|
63
64
|
def send_order_async(
|
|
64
65
|
self,
|
|
65
66
|
instrument: Instrument,
|
|
66
|
-
order_side:
|
|
67
|
+
order_side: OrderSide,
|
|
67
68
|
order_type: str,
|
|
68
69
|
amount: float,
|
|
69
70
|
price: float | None = None,
|
|
@@ -127,7 +128,7 @@ class CcxtBroker(IBroker):
|
|
|
127
128
|
def send_order(
|
|
128
129
|
self,
|
|
129
130
|
instrument: Instrument,
|
|
130
|
-
order_side:
|
|
131
|
+
order_side: OrderSide,
|
|
131
132
|
order_type: str,
|
|
132
133
|
amount: float,
|
|
133
134
|
price: float | None = None,
|
|
@@ -195,7 +196,7 @@ class CcxtBroker(IBroker):
|
|
|
195
196
|
async def _create_order(
|
|
196
197
|
self,
|
|
197
198
|
instrument: Instrument,
|
|
198
|
-
order_side:
|
|
199
|
+
order_side: OrderSide,
|
|
199
200
|
order_type: str,
|
|
200
201
|
amount: float,
|
|
201
202
|
price: float | None = None,
|
|
@@ -246,13 +247,12 @@ class CcxtBroker(IBroker):
|
|
|
246
247
|
logger.error(
|
|
247
248
|
f"(::_create_order) {order_side} {amount} {order_type} for {instrument.symbol} exception : {err}"
|
|
248
249
|
)
|
|
249
|
-
logger.error(traceback.format_exc())
|
|
250
250
|
return None, err
|
|
251
251
|
|
|
252
252
|
def _prepare_order_payload(
|
|
253
253
|
self,
|
|
254
254
|
instrument: Instrument,
|
|
255
|
-
order_side:
|
|
255
|
+
order_side: OrderSide,
|
|
256
256
|
order_type: str,
|
|
257
257
|
amount: float,
|
|
258
258
|
price: float | None = None,
|
|
@@ -263,11 +263,6 @@ class CcxtBroker(IBroker):
|
|
|
263
263
|
params = {}
|
|
264
264
|
_is_trigger_order = order_type.startswith("stop_")
|
|
265
265
|
|
|
266
|
-
if order_type == "limit" or _is_trigger_order:
|
|
267
|
-
params["timeInForce"] = time_in_force.upper()
|
|
268
|
-
if price is None:
|
|
269
|
-
raise InvalidOrderParameters(f"Price must be specified for '{order_type}' order")
|
|
270
|
-
|
|
271
266
|
quote = self.data_provider.get_quote(instrument)
|
|
272
267
|
if quote is None:
|
|
273
268
|
logger.warning(f"[<y>{instrument.symbol}</y>] :: Quote is not available for order creation.")
|
|
@@ -293,10 +288,27 @@ class CcxtBroker(IBroker):
|
|
|
293
288
|
params["type"] = "swap"
|
|
294
289
|
|
|
295
290
|
ccxt_symbol = instrument_to_ccxt_symbol(instrument)
|
|
291
|
+
|
|
292
|
+
if order_type == "limit" or _is_trigger_order:
|
|
293
|
+
time_in_force = time_in_force.upper()
|
|
294
|
+
params["timeInForce"] = time_in_force
|
|
295
|
+
if price is None:
|
|
296
|
+
raise InvalidOrderParameters(f"Price must be specified for '{order_type}' order")
|
|
297
|
+
if order_side == "BUY" and time_in_force == "GTX" and price >= quote.ask:
|
|
298
|
+
logger.info(
|
|
299
|
+
f"[{instrument.symbol}] :: GTX BUY order price {price} is greater than ask price {quote.ask}. Setting 1 tick below ask."
|
|
300
|
+
)
|
|
301
|
+
price = quote.ask - instrument.tick_size
|
|
302
|
+
elif order_side == "SELL" and time_in_force == "GTX" and price <= quote.bid:
|
|
303
|
+
logger.info(
|
|
304
|
+
f"[{instrument.symbol}] :: GTX SELL order price {price} is less than bid price {quote.bid}. Setting 1 tick above bid."
|
|
305
|
+
)
|
|
306
|
+
price = quote.bid + instrument.tick_size
|
|
307
|
+
|
|
296
308
|
return {
|
|
297
309
|
"symbol": ccxt_symbol,
|
|
298
|
-
"type": order_type,
|
|
299
|
-
"side": order_side,
|
|
310
|
+
"type": order_type.lower(),
|
|
311
|
+
"side": order_side.lower(),
|
|
300
312
|
"amount": amount,
|
|
301
313
|
"price": price,
|
|
302
314
|
"params": params,
|
|
@@ -336,11 +348,10 @@ class CcxtBroker(IBroker):
|
|
|
336
348
|
logger.debug(f"[{order_id}] Could not cancel order: {err}")
|
|
337
349
|
return False
|
|
338
350
|
except (ccxt.NetworkError, ccxt.ExchangeError, ccxt.ExchangeNotAvailable) as e:
|
|
339
|
-
logger.
|
|
351
|
+
logger.warning(f"[{order_id}] Network or exchange error while cancelling: {e}")
|
|
340
352
|
# Continue with retry logic
|
|
341
353
|
except Exception as err:
|
|
342
354
|
logger.error(f"Unexpected error canceling order {order_id}: {err}")
|
|
343
|
-
logger.error(traceback.format_exc())
|
|
344
355
|
return False
|
|
345
356
|
|
|
346
357
|
# Common retry logic for all retryable errors
|
|
@@ -2,7 +2,7 @@ from typing import Any
|
|
|
2
2
|
|
|
3
3
|
from qubx import logger
|
|
4
4
|
from qubx.connectors.ccxt.broker import CcxtBroker
|
|
5
|
-
from qubx.core.basics import Instrument
|
|
5
|
+
from qubx.core.basics import Instrument, OrderSide
|
|
6
6
|
from qubx.core.exceptions import BadRequest
|
|
7
7
|
|
|
8
8
|
|
|
@@ -21,7 +21,7 @@ class BinanceCcxtBroker(CcxtBroker):
|
|
|
21
21
|
def _prepare_order_payload(
|
|
22
22
|
self,
|
|
23
23
|
instrument: Instrument,
|
|
24
|
-
order_side:
|
|
24
|
+
order_side: OrderSide,
|
|
25
25
|
order_type: str,
|
|
26
26
|
amount: float,
|
|
27
27
|
price: float | None = None,
|
|
@@ -42,8 +42,8 @@ class BinanceCcxtBroker(CcxtBroker):
|
|
|
42
42
|
raise BadRequest(f"Quote is not available for price match for {instrument.symbol}")
|
|
43
43
|
|
|
44
44
|
if time_in_force == "gtx" and price is not None and self.enable_price_match:
|
|
45
|
-
if (order_side == "
|
|
46
|
-
order_side == "
|
|
45
|
+
if (order_side == "BUY" and quote.bid - price < self.price_match_ticks * instrument.tick_size) or (
|
|
46
|
+
order_side == "SELL" and price - quote.ask < self.price_match_ticks * instrument.tick_size
|
|
47
47
|
):
|
|
48
48
|
params["priceMatch"] = "QUEUE"
|
|
49
49
|
logger.debug(f"[<y>{instrument.symbol}</y>] :: Price match is set to QUEUE. Price will be ignored.")
|
|
@@ -1,17 +1,68 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
3
|
from qubx import logger
|
|
4
|
-
from qubx.core.basics import Instrument, MarketType, Order, OrderRequest
|
|
4
|
+
from qubx.core.basics import Instrument, MarketType, Order, OrderRequest, OrderSide
|
|
5
5
|
from qubx.core.interfaces import IAccountProcessor, IBroker, ITimeProvider, ITradingManager
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class ClientIdStore:
|
|
9
|
+
"""Manages generation of unique client order IDs."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize a client ID store."""
|
|
13
|
+
self._order_id: int | None = None
|
|
14
|
+
|
|
15
|
+
def generate_id(self, time_provider: ITimeProvider, symbol: str) -> str:
|
|
16
|
+
"""Generate a unique client order ID.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
time_provider: Time provider to get current timestamp
|
|
20
|
+
symbol: Trading symbol for the order
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
A unique client order ID
|
|
24
|
+
"""
|
|
25
|
+
# Initialize order ID from timestamp if not yet set
|
|
26
|
+
if self._order_id is None:
|
|
27
|
+
self._order_id = self._initialize_id_from_timestamp(time_provider)
|
|
28
|
+
|
|
29
|
+
# Increment order ID to ensure uniqueness across calls
|
|
30
|
+
self._order_id += 1
|
|
31
|
+
|
|
32
|
+
# Create and return the unique ID
|
|
33
|
+
return self._create_id(symbol, self._order_id)
|
|
34
|
+
|
|
35
|
+
def _initialize_id_from_timestamp(self, time_provider: ITimeProvider) -> int:
|
|
36
|
+
"""Initialize the order ID from the current timestamp.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
time_provider: Time provider to get current timestamp
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Initial order ID value
|
|
43
|
+
"""
|
|
44
|
+
return time_provider.time().astype("int64") // 100_000_000
|
|
45
|
+
|
|
46
|
+
def _create_id(self, symbol: str, order_id: int) -> str:
|
|
47
|
+
"""Create the ID from symbol and order ID.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
symbol: Trading symbol
|
|
51
|
+
order_id: Current order ID counter
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Client ID string
|
|
55
|
+
"""
|
|
56
|
+
return "_".join(["qubx", symbol, str(order_id)])
|
|
57
|
+
|
|
58
|
+
|
|
8
59
|
class TradingManager(ITradingManager):
|
|
9
60
|
_time_provider: ITimeProvider
|
|
10
61
|
_broker: IBroker
|
|
11
62
|
_account: IAccountProcessor
|
|
12
63
|
_strategy_name: str
|
|
13
64
|
|
|
14
|
-
|
|
65
|
+
_client_id_store: ClientIdStore
|
|
15
66
|
|
|
16
67
|
def __init__(
|
|
17
68
|
self, time_provider: ITimeProvider, broker: IBroker, account: IAccountProcessor, strategy_name: str
|
|
@@ -20,6 +71,7 @@ class TradingManager(ITradingManager):
|
|
|
20
71
|
self._broker = broker
|
|
21
72
|
self._account = account
|
|
22
73
|
self._strategy_name = strategy_name
|
|
74
|
+
self._client_id_store = ClientIdStore()
|
|
23
75
|
|
|
24
76
|
def trade(
|
|
25
77
|
self,
|
|
@@ -97,8 +149,8 @@ class TradingManager(ITradingManager):
|
|
|
97
149
|
return price
|
|
98
150
|
return instrument.round_price_down(price) if amount > 0 else instrument.round_price_up(price)
|
|
99
151
|
|
|
100
|
-
def _get_side(self, amount: float) ->
|
|
101
|
-
return "
|
|
152
|
+
def _get_side(self, amount: float) -> OrderSide:
|
|
153
|
+
return "BUY" if amount > 0 else "SELL"
|
|
102
154
|
|
|
103
155
|
def _get_order_type(self, instrument: Instrument, price: float | None, options: dict[str, Any]) -> str:
|
|
104
156
|
if price is None:
|
|
@@ -137,11 +189,7 @@ class TradingManager(ITradingManager):
|
|
|
137
189
|
self.cancel_order(o.id)
|
|
138
190
|
|
|
139
191
|
def _generate_order_client_id(self, symbol: str) -> str:
|
|
140
|
-
|
|
141
|
-
self._order_id = self._time_provider.time().astype("int64") // 100_000_000
|
|
142
|
-
assert self._order_id is not None
|
|
143
|
-
self._order_id += 1
|
|
144
|
-
return "_".join(["qubx", symbol, str(self._order_id)])
|
|
192
|
+
return self._client_id_store.generate_id(self._time_provider, symbol)
|
|
145
193
|
|
|
146
194
|
def exchanges(self) -> list[str]:
|
|
147
195
|
return [self._broker.exchange()]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|