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.

Files changed (151) hide show
  1. {qubx-0.6.20 → qubx-0.6.21}/PKG-INFO +1 -1
  2. {qubx-0.6.20 → qubx-0.6.21}/pyproject.toml +1 -1
  3. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/broker.py +25 -14
  4. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +4 -4
  5. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/trading.py +57 -9
  6. {qubx-0.6.20 → qubx-0.6.21}/LICENSE +0 -0
  7. {qubx-0.6.20 → qubx-0.6.21}/README.md +0 -0
  8. {qubx-0.6.20 → qubx-0.6.21}/build.py +0 -0
  9. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/__init__.py +0 -0
  10. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/_nb_magic.py +0 -0
  11. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/__init__.py +0 -0
  12. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/account.py +0 -0
  13. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/broker.py +0 -0
  14. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/data.py +0 -0
  15. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/management.py +0 -0
  16. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/ome.py +0 -0
  17. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/optimization.py +0 -0
  18. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/runner.py +0 -0
  19. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/simulated_data.py +0 -0
  20. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/simulator.py +0 -0
  21. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/backtester/utils.py +0 -0
  22. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/__init__.py +0 -0
  23. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/commands.py +0 -0
  24. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/deploy.py +0 -0
  25. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/misc.py +0 -0
  26. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/cli/release.py +0 -0
  27. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/__init__.py +0 -0
  28. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/account.py +0 -0
  29. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/data.py +0 -0
  30. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  31. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  32. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  33. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/factory.py +0 -0
  34. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/reader.py +0 -0
  35. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/connectors/ccxt/utils.py +0 -0
  36. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/__init__.py +0 -0
  37. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/account.py +0 -0
  38. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/basics.py +0 -0
  39. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/context.py +0 -0
  40. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/deque.py +0 -0
  41. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/errors.py +0 -0
  42. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/exceptions.py +0 -0
  43. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/helpers.py +0 -0
  44. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/initializer.py +0 -0
  45. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/interfaces.py +0 -0
  46. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/loggers.py +0 -0
  47. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/lookups.py +0 -0
  48. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/metrics.py +0 -0
  49. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/__init__.py +0 -0
  50. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/market.py +0 -0
  51. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/processing.py +0 -0
  52. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/subscription.py +0 -0
  53. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/mixins/universe.py +0 -0
  54. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/series.pxd +0 -0
  55. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/series.pyi +0 -0
  56. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/series.pyx +0 -0
  57. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/utils.pyi +0 -0
  58. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/core/utils.pyx +0 -0
  59. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/__init__.py +0 -0
  60. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/composite.py +0 -0
  61. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/helpers.py +0 -0
  62. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/hft.py +0 -0
  63. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/readers.py +0 -0
  64. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/registry.py +0 -0
  65. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/data/tardis.py +0 -0
  66. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/__init__.py +0 -0
  67. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/base.py +0 -0
  68. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/composite.py +0 -0
  69. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/csv.py +0 -0
  70. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/prometheus.py +0 -0
  71. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/emitters/questdb.py +0 -0
  72. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/__init__.py +0 -0
  73. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/composite.py +0 -0
  74. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/formatters/__init__.py +0 -0
  75. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/formatters/base.py +0 -0
  76. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/formatters/incremental.py +0 -0
  77. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/formatters/slack.py +0 -0
  78. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/redis_streams.py +0 -0
  79. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/exporters/slack.py +0 -0
  80. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/__init__.py +0 -0
  81. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/core.py +0 -0
  82. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/orderbook.py +0 -0
  83. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/price.py +0 -0
  84. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/trades.py +0 -0
  85. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/features/utils.py +0 -0
  86. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/gathering/simplest.py +0 -0
  87. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/health/__init__.py +0 -0
  88. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/health/base.py +0 -0
  89. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/math/__init__.py +0 -0
  90. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/math/stats.py +0 -0
  91. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/notifications/__init__.py +0 -0
  92. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/notifications/composite.py +0 -0
  93. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/notifications/slack.py +0 -0
  94. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/pandaz/__init__.py +0 -0
  95. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/pandaz/ta.py +0 -0
  96. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/pandaz/utils.py +0 -0
  97. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/_build.py +0 -0
  98. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
  99. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-binance.json +0 -0
  100. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
  101. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
  102. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
  103. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
  104. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
  105. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restarts/__init__.py +0 -0
  106. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restarts/state_resolvers.py +0 -0
  107. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restarts/time_finders.py +0 -0
  108. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/__init__.py +0 -0
  109. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/balance.py +0 -0
  110. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/factory.py +0 -0
  111. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/interfaces.py +0 -0
  112. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/position.py +0 -0
  113. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/signal.py +0 -0
  114. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/state.py +0 -0
  115. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/restorers/utils.py +0 -0
  116. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/ta/__init__.py +0 -0
  117. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/ta/indicators.pxd +0 -0
  118. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/ta/indicators.pyi +0 -0
  119. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/ta/indicators.pyx +0 -0
  120. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/__init__.py +0 -0
  121. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/advanced.py +0 -0
  122. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/composite.py +0 -0
  123. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/rebalancers.py +0 -0
  124. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/riskctrl.py +0 -0
  125. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/trackers/sizers.py +0 -0
  126. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/__init__.py +0 -0
  127. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/_pyxreloader.py +0 -0
  128. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/charting/lookinglass.py +0 -0
  129. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  130. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/collections.py +0 -0
  131. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/marketdata/binance.py +0 -0
  132. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/marketdata/ccxt.py +0 -0
  133. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/marketdata/dukas.py +0 -0
  134. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/misc.py +0 -0
  135. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/ntp.py +0 -0
  136. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/numbers_utils.py +0 -0
  137. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/orderbook.py +0 -0
  138. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/__init__.py +0 -0
  139. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/dashboard.py +0 -0
  140. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/data.py +0 -0
  141. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/interfaces.py +0 -0
  142. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  143. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  144. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/__init__.py +0 -0
  145. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  146. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/accounts.py +0 -0
  147. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/configs.py +0 -0
  148. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/factory.py +0 -0
  149. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/runner/runner.py +0 -0
  150. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/time.py +0 -0
  151. {qubx-0.6.20 → qubx-0.6.21}/src/qubx/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Qubx
3
- Version: 0.6.20
3
+ Version: 0.6.21
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Author: Dmitry Marienko
6
6
  Author-email: dmitry.marienko@xlydian.com
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "Qubx"
7
- version = "0.6.20"
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: str,
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: str,
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: str,
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: str,
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.debug(f"[{order_id}] Network or exchange error while cancelling: {e}")
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: str,
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 == "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
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
- _order_id: int | None = None
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) -> str:
101
- return "buy" if amount > 0 else "sell"
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
- if self._order_id is None:
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