Qubx 0.7.8__tar.gz → 0.7.35__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.
Files changed (283) hide show
  1. {qubx-0.7.8 → qubx-0.7.35}/PKG-INFO +35 -7
  2. {qubx-0.7.8 → qubx-0.7.35}/README.md +29 -1
  3. {qubx-0.7.8 → qubx-0.7.35}/pyproject.toml +7 -5
  4. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/account.py +8 -1
  5. qubx-0.7.35/src/qubx/backtester/broker.py +168 -0
  6. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/data.py +33 -7
  7. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/management.py +1 -1
  8. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/ome.py +5 -2
  9. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/runner.py +23 -11
  10. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/simulated_exchange.py +23 -0
  11. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/simulator.py +3 -0
  12. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/transfers.py +13 -8
  13. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/utils.py +9 -2
  14. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/commands.py +71 -33
  15. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/release.py +9 -10
  16. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/tui.py +0 -3
  17. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/account.py +22 -10
  18. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/broker.py +225 -69
  19. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/data.py +19 -38
  20. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchange_manager.py +33 -79
  21. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +121 -108
  22. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +1 -1
  23. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/factory.py +6 -2
  24. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -4
  25. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/liquidation.py +7 -6
  26. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/ohlc.py +25 -24
  27. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -6
  28. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/orderbook.py +1 -3
  29. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/quote.py +1 -5
  30. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/trade.py +0 -4
  31. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/reader.py +367 -166
  32. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/utils.py +45 -41
  33. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/tardis/data.py +9 -3
  34. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/__init__.py +3 -4
  35. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/account.py +236 -105
  36. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/broker.py +151 -109
  37. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/client.py +43 -52
  38. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/constants.py +10 -22
  39. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/data.py +268 -333
  40. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/extensions.py +7 -2
  41. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/factory.py +13 -89
  42. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/__init__.py +0 -2
  43. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/orderbook.py +111 -47
  44. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/stats.py +6 -5
  45. qubx-0.7.35/src/qubx/connectors/xlighter/instruments.py +60 -0
  46. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/nonce.py +9 -1
  47. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/parsers.py +10 -7
  48. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/rate_limits.py +2 -2
  49. qubx-0.7.35/src/qubx/connectors/xlighter/reader.py +453 -0
  50. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/utils.py +22 -0
  51. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/websocket.py +18 -5
  52. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/account.py +280 -41
  53. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/basics.py +116 -12
  54. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/context.py +80 -48
  55. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/detectors/delisting.py +16 -5
  56. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/errors.py +3 -1
  57. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/helpers.py +67 -4
  58. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/initializer.py +2 -1
  59. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/interfaces.py +418 -158
  60. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/loggers.py +14 -10
  61. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/metrics.py +48 -19
  62. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/market.py +14 -11
  63. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/processing.py +139 -52
  64. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/subscription.py +125 -38
  65. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/trading.py +167 -65
  66. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/universe.py +18 -22
  67. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/series.pyx +27 -6
  68. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/utils.pyi +2 -1
  69. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/utils.pyx +21 -2
  70. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/composite.py +45 -15
  71. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/helpers.py +9 -5
  72. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/readers.py +31 -10
  73. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/storages/questdb.py +18 -2
  74. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/transformers.py +43 -42
  75. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/base.py +8 -1
  76. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/indicator.py +2 -10
  77. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/questdb.py +42 -13
  78. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/base.py +6 -5
  79. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/incremental.py +24 -20
  80. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/target_position.py +7 -2
  81. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/gathering/simplest.py +21 -7
  82. qubx-0.7.35/src/qubx/health/__init__.py +4 -0
  83. qubx-0.7.35/src/qubx/health/base.py +543 -0
  84. qubx-0.7.35/src/qubx/health/dummy.py +99 -0
  85. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/pandaz/__init__.py +18 -16
  86. {qubx-0.7.8/src/qubx/math → qubx-0.7.35/src/qubx/pandaz}/stats.py +41 -16
  87. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/pandaz/ta.py +2 -2
  88. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restarts/state_resolvers.py +1 -1
  89. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/balance.py +36 -38
  90. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/interfaces.py +2 -2
  91. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/position.py +25 -24
  92. qubx-0.7.35/src/qubx/state/__init__.py +14 -0
  93. qubx-0.7.35/src/qubx/state/dummy.py +35 -0
  94. qubx-0.7.35/src/qubx/state/redis.py +170 -0
  95. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/ta/indicators.pxd +58 -1
  96. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/ta/indicators.pyi +11 -0
  97. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/ta/indicators.pyx +436 -25
  98. qubx-0.7.35/src/qubx/templates/__init__.py +5 -0
  99. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/base.py +41 -42
  100. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/riskctrl.py +2 -2
  101. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/misc.py +24 -4
  102. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/rate_limiter.py +3 -6
  103. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/_jupyter_runner.pyt +3 -3
  104. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/configs.py +83 -4
  105. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/factory.py +60 -25
  106. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/runner.py +163 -27
  107. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/__init__.py +3 -1
  108. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/app.py +18 -2
  109. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/init_code.py +6 -5
  110. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/repl_output.py +9 -0
  111. qubx-0.7.35/src/qubx/utils/throttler.py +136 -0
  112. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/time.py +10 -2
  113. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/websocket_manager.py +5 -2
  114. qubx-0.7.8/src/qubx/backtester/broker.py +0 -137
  115. qubx-0.7.8/src/qubx/connectors/xlighter/handlers/quote.py +0 -158
  116. qubx-0.7.8/src/qubx/connectors/xlighter/instruments.py +0 -253
  117. qubx-0.7.8/src/qubx/connectors/xlighter/reader.py +0 -490
  118. qubx-0.7.8/src/qubx/core/deque.py +0 -182
  119. qubx-0.7.8/src/qubx/features/__init__.py +0 -14
  120. qubx-0.7.8/src/qubx/features/core.py +0 -254
  121. qubx-0.7.8/src/qubx/features/orderbook.py +0 -42
  122. qubx-0.7.8/src/qubx/features/price.py +0 -20
  123. qubx-0.7.8/src/qubx/features/trades.py +0 -105
  124. qubx-0.7.8/src/qubx/features/utils.py +0 -10
  125. qubx-0.7.8/src/qubx/health/__init__.py +0 -3
  126. qubx-0.7.8/src/qubx/health/base.py +0 -672
  127. qubx-0.7.8/src/qubx/math/__init__.py +0 -3
  128. qubx-0.7.8/src/qubx/templates/__init__.py +0 -5
  129. {qubx-0.7.8 → qubx-0.7.35}/LICENSE +0 -0
  130. {qubx-0.7.8 → qubx-0.7.35}/build.py +0 -0
  131. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/__init__.py +0 -0
  132. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/_nb_magic.py +0 -0
  133. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/__init__.py +0 -0
  134. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/optimization.py +0 -0
  135. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/sentinels.py +0 -0
  136. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/backtester/simulated_data.py +0 -0
  137. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/__init__.py +0 -0
  138. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/deploy.py +0 -0
  139. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/cli/misc.py +0 -0
  140. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/__init__.py +0 -0
  141. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  142. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  143. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  144. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  145. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  146. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  147. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  148. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  149. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  150. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  151. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  152. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  153. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  154. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  155. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  156. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  157. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  158. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  159. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  160. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  161. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/tardis/utils.py +0 -0
  162. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/base.py +0 -0
  163. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/connectors/xlighter/handlers/trades.py +0 -0
  164. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/__init__.py +0 -0
  165. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/detectors/__init__.py +0 -0
  166. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/detectors/stale.py +0 -0
  167. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/exceptions.py +0 -0
  168. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/lookups.py +0 -0
  169. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/__init__.py +0 -0
  170. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/mixins/utils.py +0 -0
  171. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/series.pxd +0 -0
  172. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/core/series.pyi +0 -0
  173. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/__init__.py +0 -0
  174. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/containers.py +0 -0
  175. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/hft.py +0 -0
  176. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/registry.py +0 -0
  177. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/storage.py +0 -0
  178. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/storages/csv.py +0 -0
  179. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/storages/utils.py +0 -0
  180. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/data/tardis.py +0 -0
  181. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/__init__.py +0 -0
  182. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/composite.py +0 -0
  183. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/csv.py +0 -0
  184. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/inmemory.py +0 -0
  185. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/emitters/prometheus.py +0 -0
  186. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/__init__.py +0 -0
  187. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/composite.py +0 -0
  188. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/__init__.py +0 -0
  189. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/formatters/slack.py +0 -0
  190. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/redis_streams.py +0 -0
  191. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/exporters/slack.py +0 -0
  192. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/__init__.py +0 -0
  193. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/csv.py +0 -0
  194. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/factory.py +0 -0
  195. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/inmemory.py +0 -0
  196. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/loggers/mongo.py +0 -0
  197. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/notifications/__init__.py +0 -0
  198. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/notifications/composite.py +0 -0
  199. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/notifications/slack.py +0 -0
  200. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/notifications/throttler.py +0 -0
  201. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/pandaz/utils.py +0 -0
  202. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/_build.py +0 -0
  203. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/crypto-fees.ini +0 -0
  204. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  205. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  206. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  207. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  208. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  209. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  210. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  211. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  212. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  213. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  214. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  215. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restarts/__init__.py +0 -0
  216. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restarts/time_finders.py +0 -0
  217. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/__init__.py +0 -0
  218. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/factory.py +0 -0
  219. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/signal.py +0 -0
  220. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/state.py +0 -0
  221. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/restorers/utils.py +0 -0
  222. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/ta/__init__.py +0 -0
  223. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  224. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/config.yml.j2 +0 -0
  225. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  226. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  227. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  228. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  229. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  230. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/project/template.yml +0 -0
  231. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  232. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  233. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/config.yml.j2 +0 -0
  234. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  235. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  236. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  237. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/templates/simple/template.yml +0 -0
  238. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/__init__.py +0 -0
  239. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/advanced.py +0 -0
  240. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/composite.py +0 -0
  241. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/rebalancers.py +0 -0
  242. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/trackers/sizers.py +0 -0
  243. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/__init__.py +0 -0
  244. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/_pyxreloader.py +0 -0
  245. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/charting/lookinglass.py +0 -0
  246. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  247. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/charting/orderbook.py +0 -0
  248. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/collections.py +0 -0
  249. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/hft/__init__.py +0 -0
  250. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/hft/numba_utils.py +0 -0
  251. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/hft/orderbook.pyi +0 -0
  252. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/hft/orderbook.pyx +0 -0
  253. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/marketdata/binance.py +0 -0
  254. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/marketdata/ccxt.py +0 -0
  255. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/marketdata/dukas.py +0 -0
  256. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/nonce.py +0 -0
  257. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/ntp.py +0 -0
  258. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/numbers_utils.py +0 -0
  259. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/orderbook.py +0 -0
  260. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/__init__.py +0 -0
  261. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/dashboard.py +0 -0
  262. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/data.py +0 -0
  263. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/interfaces.py +0 -0
  264. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  265. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  266. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/questdb.py +0 -0
  267. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/ringbuffer.pxd +0 -0
  268. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/ringbuffer.pyi +0 -0
  269. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/ringbuffer.pyx +0 -0
  270. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/__init__.py +0 -0
  271. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/accounts.py +0 -0
  272. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/kernel_service.py +0 -0
  273. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/handlers.py +0 -0
  274. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/kernel.py +0 -0
  275. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  276. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  277. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  278. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  279. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  280. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  281. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  282. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/slack.py +0 -0
  283. {qubx-0.7.8 → qubx-0.7.35}/src/qubx/utils/version.py +0 -0
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Qubx
3
- Version: 0.7.8
3
+ Version: 0.7.35
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  License-File: LICENSE
6
6
  Author: Dmitry Marienko
7
7
  Author-email: dmitry.marienko@xlydian.com
8
- Requires-Python: >=3.10,<4.0
8
+ Requires-Python: >=3.11,<4.0
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.10
11
10
  Classifier: Programming Language :: Python :: 3.11
12
11
  Classifier: Programming Language :: Python :: 3.12
13
12
  Classifier: Programming Language :: Python :: 3.13
@@ -43,21 +42,22 @@ Requires-Dist: python-binance (>=1.0.19,<2.0.0)
43
42
  Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
44
43
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
45
44
  Requires-Dist: qubx-bitfinex-api (>=3.0.7,<4.0.0)
46
- Requires-Dist: qubx-lighter-api (>=0.1.4,<0.2.0)
45
+ Requires-Dist: qubx-lighter-api (>=0.1.5,<0.2.0)
47
46
  Requires-Dist: questdb (>=2.0.3,<3.0.0)
48
47
  Requires-Dist: redis (>=5.2.1,<6.0.0)
49
48
  Requires-Dist: rich (>=13.9.4,<14.0.0)
50
- Requires-Dist: scikit-learn (>=1.4.2,<2.0.0)
49
+ Requires-Dist: scikit-learn (>=1.8.0)
51
50
  Requires-Dist: scipy (>=1.12.0,<2.0.0)
52
51
  Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
53
52
  Requires-Dist: stackprinter (>=0.2.10,<0.3.0)
54
- Requires-Dist: statsmodels (>=0.14.2,<0.15.0)
53
+ Requires-Dist: statsmodels (>=0.14.6,<0.15.0)
55
54
  Requires-Dist: tabulate (>=0.9.0,<0.10.0)
56
55
  Requires-Dist: textual-autocomplete (>=4.0.0,<5.0.0)
57
56
  Requires-Dist: textual-serve (>=1.0.0,<2.0.0)
58
57
  Requires-Dist: textual[syntax] (>=6.0.0,<7.0.0)
59
58
  Requires-Dist: toml (>=0.10.2,<0.11.0)
60
59
  Requires-Dist: tqdm
60
+ Requires-Dist: uvloop (>=0.22.1,<0.23.0)
61
61
  Requires-Dist: websockets (==15.0.1)
62
62
  Project-URL: Repository, https://github.com/xLydianSoftware/Qubx
63
63
  Description-Content-Type: text/markdown
@@ -69,20 +69,33 @@ Description-Content-Type: text/markdown
69
69
  ```
70
70
  ⠀⠀⡰⡖⠒⠒⢒⢦⠀⠀
71
71
  ⠀⢠⠃⠈⢆⣀⣎⣀⣱⡀ QUBX | Quantitative Backtesting Environment
72
- ⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c) 2024, by Dmytro Mariienko
72
+ ⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c) 2026, by xLydian
73
73
  ⠀⠀⠱⣜⣀⣀⣈⣦⠃⠀⠀⠀
74
74
  ```
75
75
 
76
76
  Qubx is a next-generation quantitative trading framework designed for efficient backtesting and live trading. Built with Python, it offers a robust environment for developing, testing, and deploying trading strategies.
77
77
 
78
+ **Qubx is under active development.** We are continuously improving the framework and will update our documentation in the coming days/weeks. This will include comprehensive end-to-end examples for running simulations and live trading.
79
+
80
+ ### Supported Data Types
81
+
82
+ Qubx supports a wide range of market data:
83
+ - OHLC (candlestick data)
84
+ - L2 Orderbook
85
+ - Liquidations
86
+ - Funding rates
87
+ - And more...
88
+
78
89
  ## Quick Start
79
90
 
80
91
  ### 1. Install Dependencies
92
+
81
93
  ```bash
82
94
  poetry install
83
95
  ```
84
96
 
85
97
  ### 2. Create a Strategy
98
+
86
99
  ```bash
87
100
  # Create a simple strategy template (default)
88
101
  poetry run qubx init
@@ -92,6 +105,7 @@ poetry run qubx init --name my_strategy --symbols BTCUSDT,ETHUSDT
92
105
  ```
93
106
 
94
107
  ### 3. Run Your Strategy
108
+
95
109
  ```bash
96
110
  cd my_strategy
97
111
 
@@ -103,6 +117,7 @@ poetry run qubx run config.yml --paper
103
117
  ```
104
118
 
105
119
  ### Available Templates
120
+
106
121
  ```bash
107
122
  # List available strategy templates
108
123
  poetry run qubx init --list-templates
@@ -112,6 +127,7 @@ poetry run qubx init --template project --name my_project
112
127
  ```
113
128
 
114
129
  ### Strategy Development Workflow
130
+
115
131
  1. **Initialize**: `poetry run qubx init` - Create strategy from template
116
132
  2. **Develop**: Edit `strategy.py` to implement your trading logic
117
133
  3. **Test**: `poetry run qubx run config.yml --paper` - Run in paper mode
@@ -192,6 +208,18 @@ just test
192
208
  - Build package: `just build`
193
209
  - Run verbose tests: `just test-verbose`
194
210
 
211
+ ## In Production
212
+
213
+ Qubx powers the [AllegedAlpha](https://app.lighter.xyz/public-pools/281474976625478) public pool on Lighter. Public pools allow users to deposit funds from their blockchain wallet into a smart contract. The pool operator manages the trading strategy, and a performance fee is taken from profits (X: [@allegedalpha](https://x.com/allegedalpha)).
214
+
215
+ ## About xLydian
216
+
217
+ Qubx is developed by [xLydian](https://xlydian.com/).
218
+
219
+ - Website: [xlydian.com](https://xlydian.com/)
220
+ - X: [@xLydian_xyz](https://x.com/xLydian_xyz)
221
+ - Contact: [info@xlydian.com](mailto:info@xlydian.com)
222
+
195
223
  ## Contributing
196
224
 
197
225
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -5,20 +5,33 @@
5
5
  ```
6
6
  ⠀⠀⡰⡖⠒⠒⢒⢦⠀⠀
7
7
  ⠀⢠⠃⠈⢆⣀⣎⣀⣱⡀ QUBX | Quantitative Backtesting Environment
8
- ⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c) 2024, by Dmytro Mariienko
8
+ ⠀⢳⠒⠒⡞⠚⡄⠀⡰⠁ (c) 2026, by xLydian
9
9
  ⠀⠀⠱⣜⣀⣀⣈⣦⠃⠀⠀⠀
10
10
  ```
11
11
 
12
12
  Qubx is a next-generation quantitative trading framework designed for efficient backtesting and live trading. Built with Python, it offers a robust environment for developing, testing, and deploying trading strategies.
13
13
 
14
+ **Qubx is under active development.** We are continuously improving the framework and will update our documentation in the coming days/weeks. This will include comprehensive end-to-end examples for running simulations and live trading.
15
+
16
+ ### Supported Data Types
17
+
18
+ Qubx supports a wide range of market data:
19
+ - OHLC (candlestick data)
20
+ - L2 Orderbook
21
+ - Liquidations
22
+ - Funding rates
23
+ - And more...
24
+
14
25
  ## Quick Start
15
26
 
16
27
  ### 1. Install Dependencies
28
+
17
29
  ```bash
18
30
  poetry install
19
31
  ```
20
32
 
21
33
  ### 2. Create a Strategy
34
+
22
35
  ```bash
23
36
  # Create a simple strategy template (default)
24
37
  poetry run qubx init
@@ -28,6 +41,7 @@ poetry run qubx init --name my_strategy --symbols BTCUSDT,ETHUSDT
28
41
  ```
29
42
 
30
43
  ### 3. Run Your Strategy
44
+
31
45
  ```bash
32
46
  cd my_strategy
33
47
 
@@ -39,6 +53,7 @@ poetry run qubx run config.yml --paper
39
53
  ```
40
54
 
41
55
  ### Available Templates
56
+
42
57
  ```bash
43
58
  # List available strategy templates
44
59
  poetry run qubx init --list-templates
@@ -48,6 +63,7 @@ poetry run qubx init --template project --name my_project
48
63
  ```
49
64
 
50
65
  ### Strategy Development Workflow
66
+
51
67
  1. **Initialize**: `poetry run qubx init` - Create strategy from template
52
68
  2. **Develop**: Edit `strategy.py` to implement your trading logic
53
69
  3. **Test**: `poetry run qubx run config.yml --paper` - Run in paper mode
@@ -128,6 +144,18 @@ just test
128
144
  - Build package: `just build`
129
145
  - Run verbose tests: `just test-verbose`
130
146
 
147
+ ## In Production
148
+
149
+ Qubx powers the [AllegedAlpha](https://app.lighter.xyz/public-pools/281474976625478) public pool on Lighter. Public pools allow users to deposit funds from their blockchain wallet into a smart contract. The pool operator manages the trading strategy, and a performance fee is taken from profits (X: [@allegedalpha](https://x.com/allegedalpha)).
150
+
151
+ ## About xLydian
152
+
153
+ Qubx is developed by [xLydian](https://xlydian.com/).
154
+
155
+ - Website: [xlydian.com](https://xlydian.com/)
156
+ - X: [@xLydian_xyz](https://x.com/xLydian_xyz)
157
+ - Contact: [info@xlydian.com](mailto:info@xlydian.com)
158
+
131
159
  ## Contributing
132
160
 
133
161
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "Qubx"
7
- version = "0.7.8"
7
+ version = "0.7.35"
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"
@@ -32,13 +32,13 @@ generate-setup-file = false
32
32
  qubx = "qubx.cli.commands:main"
33
33
 
34
34
  [tool.poetry.dependencies]
35
- python = ">=3.10,<4.0"
35
+ python = ">=3.11,<4.0"
36
36
  numpy = "^1.26.3"
37
37
  pandas = "^2.2.2"
38
38
  pyarrow = "^15.0.0"
39
39
  scipy = "^1.12.0"
40
- scikit-learn = "^1.4.2"
41
- statsmodels = "^0.14.2"
40
+ scikit-learn = ">=1.8.0"
41
+ statsmodels = "^0.14.6"
42
42
  numba = "^0.59.1"
43
43
  sortedcontainers = "^2.4.0"
44
44
  ntplib = "^0.4.0"
@@ -78,7 +78,8 @@ textual-autocomplete = "^4.0.0"
78
78
  textual-serve = "^1.0.0"
79
79
  rich = "^13.9.4"
80
80
  jinja2 = "^3.1.0"
81
- qubx-lighter-api = "^0.1.4"
81
+ qubx-lighter-api = "^0.1.5"
82
+ uvloop = "^0.22.1"
82
83
 
83
84
  [tool.ruff.lint]
84
85
  extend-select = [ "I",]
@@ -119,6 +120,7 @@ jinja2 = "3.1.5"
119
120
  mike = "2.1.3"
120
121
  mkdocs-jupyter = "0.25.1"
121
122
  debugpy = "^1.8.12"
123
+ py-spy = "^0.4.1"
122
124
 
123
125
  [tool.poetry.group.test.dependencies]
124
126
  pytest-asyncio = "^0.24.0"
@@ -8,6 +8,7 @@ from qubx.core.basics import (
8
8
  Timestamped,
9
9
  dt_64,
10
10
  )
11
+ from qubx.core.interfaces import IHealthMonitor
11
12
  from qubx.core.series import OrderBook, Quote, Trade, TradeArray
12
13
  from qubx.restorers import RestoredState
13
14
 
@@ -21,7 +22,9 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
21
22
  account_id: str,
22
23
  exchange: ISimulatedExchange,
23
24
  channel: CtrlChannel,
25
+ health_monitor: IHealthMonitor,
24
26
  base_currency: str,
27
+ exchange_name: str,
25
28
  initial_capital: float,
26
29
  restored_state: RestoredState | None = None,
27
30
  ) -> None:
@@ -29,6 +32,8 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
29
32
  account_id=account_id,
30
33
  time_provider=exchange.get_time_provider(),
31
34
  base_currency=base_currency,
35
+ health_monitor=health_monitor,
36
+ exchange=exchange_name,
32
37
  tcc=exchange.get_transaction_costs_calculator(),
33
38
  initial_capital=initial_capital,
34
39
  )
@@ -37,7 +42,9 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
37
42
  self._channel = channel
38
43
 
39
44
  if restored_state is not None:
40
- self._balances.update(restored_state.balances)
45
+ # Convert list of AssetBalance to dict for internal storage
46
+ for balance in restored_state.balances:
47
+ self._balances[balance.currency] = balance
41
48
  for instrument, position in restored_state.positions.items():
42
49
  _pos = self.get_position(instrument)
43
50
  _pos.reset_by_position(position)
@@ -0,0 +1,168 @@
1
+ from qubx import logger
2
+ from qubx.backtester.ome import SimulatedExecutionReport
3
+ from qubx.backtester.simulated_exchange import ISimulatedExchange
4
+ from qubx.core.basics import (
5
+ CtrlChannel,
6
+ Instrument,
7
+ Order,
8
+ OrderRequest,
9
+ )
10
+ from qubx.core.exceptions import BadRequest, OrderNotFound
11
+ from qubx.core.interfaces import IBroker
12
+
13
+ from .account import SimulatedAccountProcessor
14
+
15
+
16
+ class SimulatedBroker(IBroker):
17
+ channel: CtrlChannel
18
+
19
+ _account: SimulatedAccountProcessor
20
+ _exchange: ISimulatedExchange
21
+
22
+ def __init__(
23
+ self,
24
+ channel: CtrlChannel,
25
+ account: SimulatedAccountProcessor,
26
+ simulated_exchange: ISimulatedExchange,
27
+ ) -> None:
28
+ self.channel = channel
29
+ self._account = account
30
+ self._exchange = simulated_exchange
31
+
32
+ @property
33
+ def is_simulated_trading(self) -> bool:
34
+ return True
35
+
36
+ def send_order(self, request: OrderRequest) -> Order:
37
+ """Submit order synchronously in simulation."""
38
+ instrument = request.instrument
39
+ order_side = request.side
40
+ order_type = request.order_type
41
+ amount = request.quantity
42
+ price = request.price
43
+ client_id = request.client_id
44
+ time_in_force = request.time_in_force
45
+ options = request.options
46
+
47
+ self._send_execution_report(
48
+ report := self._exchange.place_order(
49
+ instrument, order_side, order_type, amount, price, client_id, time_in_force, **options
50
+ )
51
+ )
52
+ return report.order
53
+
54
+ def send_order_async(self, request: OrderRequest) -> str | None:
55
+ """Submit order asynchronously (same as sync in simulation)."""
56
+ self.send_order(request)
57
+ return request.client_id
58
+
59
+ def _validate_order_ids(self, order_id: str | None, client_order_id: str | None) -> None:
60
+ if (order_id is None and client_order_id is None) or (order_id is not None and client_order_id is not None):
61
+ raise ValueError("Exactly one of order_id or client_order_id must be provided")
62
+
63
+ def _resolve_order_id(self, order_id: str | None, client_order_id: str | None) -> str | None:
64
+ if order_id is not None:
65
+ return order_id
66
+ if client_order_id is not None:
67
+ order = self._account.find_order_by_client_id(client_order_id)
68
+ return order.id if order is not None else None
69
+ return None
70
+
71
+ def cancel_order(self, order_id: str | None = None, client_order_id: str | None = None) -> bool:
72
+ """Cancel an order synchronously and return success status."""
73
+ self._validate_order_ids(order_id, client_order_id)
74
+ resolved_id = self._resolve_order_id(order_id, client_order_id)
75
+ if resolved_id is None:
76
+ return False
77
+ try:
78
+ self._send_execution_report(order_update := self._exchange.cancel_order(resolved_id))
79
+ return order_update is not None
80
+ except OrderNotFound:
81
+ # Order was already cancelled or doesn't exist
82
+ logger.debug(f"Order {resolved_id} not found")
83
+ return False
84
+
85
+ def cancel_order_async(self, order_id: str | None = None, client_order_id: str | None = None) -> None:
86
+ """Cancel an order asynchronously (fire-and-forget)."""
87
+ self._validate_order_ids(order_id, client_order_id)
88
+ resolved_id = self._resolve_order_id(order_id, client_order_id)
89
+ if resolved_id is None:
90
+ return
91
+ self.cancel_order(order_id=resolved_id)
92
+
93
+ def cancel_orders(self, instrument: Instrument) -> None:
94
+ raise NotImplementedError("Not implemented yet")
95
+
96
+ def update_order(
97
+ self, price: float, amount: float, order_id: str | None = None, client_order_id: str | None = None
98
+ ) -> Order:
99
+ """Update an existing limit order using cancel+recreate strategy.
100
+
101
+ Args:
102
+ order_id: The ID of the order to update
103
+ price: New price for the order
104
+ amount: New amount for the order
105
+
106
+ Returns:
107
+ Order: The updated (newly created) order object
108
+
109
+ Raises:
110
+ OrderNotFound: If the order is not found
111
+ BadRequest: If the order is not a limit order
112
+ """
113
+ self._validate_order_ids(order_id, client_order_id)
114
+ resolved_id = self._resolve_order_id(order_id, client_order_id)
115
+ if resolved_id is None:
116
+ raise OrderNotFound(f"Order {order_id or client_order_id} not found")
117
+
118
+ # Get the existing order from account
119
+ active_orders = self._account.get_orders()
120
+ existing_order = active_orders.get(resolved_id)
121
+ if not existing_order:
122
+ raise OrderNotFound(f"Order {resolved_id} not found")
123
+
124
+ # Validate that it's a limit order
125
+ if existing_order.type != "LIMIT":
126
+ raise BadRequest(
127
+ f"Order {resolved_id} is not a limit order (type: {existing_order.type}). "
128
+ "Only limit orders can be updated."
129
+ )
130
+
131
+ self.cancel_order(order_id=resolved_id)
132
+
133
+ request = OrderRequest(
134
+ instrument=existing_order.instrument,
135
+ quantity=abs(amount),
136
+ price=price,
137
+ order_type="LIMIT",
138
+ side=existing_order.side,
139
+ time_in_force=existing_order.time_in_force or "gtc",
140
+ client_id=existing_order.client_id,
141
+ options={},
142
+ )
143
+
144
+ updated_order = self.send_order(request)
145
+
146
+ return updated_order
147
+
148
+ def update_order_async(
149
+ self, price: float, amount: float, order_id: str | None = None, client_order_id: str | None = None
150
+ ) -> str | None:
151
+ """Update order asynchronously (same as sync in simulation)."""
152
+ self._validate_order_ids(order_id, client_order_id)
153
+ resolved_id = self._resolve_order_id(order_id, client_order_id)
154
+ if resolved_id is None:
155
+ return None
156
+ updated_order = self.update_order(order_id=resolved_id, price=price, amount=amount)
157
+ return updated_order.client_id
158
+
159
+ def _send_execution_report(self, report: SimulatedExecutionReport | None):
160
+ if report is None:
161
+ return
162
+
163
+ self.channel.send((report.instrument, "order", report.order, False))
164
+ if report.exec is not None:
165
+ self.channel.send((report.instrument, "deals", [report.exec], False))
166
+
167
+ def exchange(self) -> str:
168
+ return self._exchange.exchange_id
@@ -1,5 +1,5 @@
1
1
  from collections import defaultdict
2
- from typing import Any, TypeVar
2
+ from typing import TypeVar
3
3
 
4
4
  import pandas as pd
5
5
 
@@ -74,16 +74,35 @@ class SimulatedDataProvider(IDataProvider):
74
74
  def is_simulation(self) -> bool:
75
75
  return True
76
76
 
77
+ def is_connected(self) -> bool:
78
+ """
79
+ Check if the data provider is currently connected to the exchange.
80
+
81
+ For simulated data provider, always returns True since data is loaded from files.
82
+
83
+ Returns:
84
+ bool: Always True for simulated data
85
+ """
86
+ return True
87
+
77
88
  def subscribe(self, subscription_type: str, instruments: set[Instrument], reset: bool) -> None:
78
89
  _new_instr = [i for i in instruments if not self.has_subscription(i, subscription_type)]
90
+
79
91
  self._data_source.add_instruments_for_subscription(subscription_type, list(instruments))
80
92
 
81
93
  # - provide historical data and last quote for subscribed instruments
82
94
  for i in _new_instr:
83
- # Check if the instrument was actually subscribed (not filtered out)
95
+ # - check if the instrument was actually subscribed (not filtered out)
84
96
  if not self.has_subscription(i, subscription_type):
85
97
  continue
86
98
 
99
+ # - notify simulating exchange that instrument is subscribed
100
+ self._account._exchange.on_subscribe(i)
101
+
102
+ # - we need to clear last quote as it can be staled
103
+ self._last_quotes.pop(i, None)
104
+
105
+ # - try to peek most recent market data
87
106
  h_data = self._data_source.peek_historical_data(i, subscription_type)
88
107
  if h_data:
89
108
  # _s_type = DataType.from_str(subscription_type)[0]
@@ -103,9 +122,16 @@ class SimulatedDataProvider(IDataProvider):
103
122
  def unsubscribe(self, subscription_type: str, instruments: set[Instrument] | Instrument | None = None) -> None:
104
123
  # logger.debug(f" | unsubscribe: {subscription_type} -> {instruments}")
105
124
  if instruments is not None:
106
- self._data_source.remove_instruments_from_subscription(
107
- subscription_type, [instruments] if isinstance(instruments, Instrument) else list(instruments)
108
- )
125
+ _instruments = [instruments] if isinstance(instruments, Instrument) else list(instruments)
126
+ self._data_source.remove_instruments_from_subscription(subscription_type, _instruments)
127
+
128
+ # - Clear last quotes for unsubscribed instruments
129
+ for instr in _instruments:
130
+ # - clear last quote
131
+ self._last_quotes.pop(instr, None)
132
+
133
+ # - Notify simulating exchange that instrument is unsubscribed
134
+ self._account._exchange.on_unsubscribe(instr)
109
135
 
110
136
  def has_subscription(self, instrument: Instrument, subscription_type: str) -> bool:
111
137
  return self._data_source.has_subscription(instrument, subscription_type)
@@ -174,11 +200,11 @@ class SimulatedDataProvider(IDataProvider):
174
200
  high_price = r.data["high"]
175
201
  low_price = r.data["low"]
176
202
  close_price = r.data["close"]
177
-
203
+
178
204
  # Skip this record if any OHLC value is None
179
205
  if open_price is None or high_price is None or low_price is None or close_price is None:
180
206
  continue
181
-
207
+
182
208
  bars.append(
183
209
  Bar(
184
210
  _b_ts_0,
@@ -382,7 +382,7 @@ class BacktestsResultsManager:
382
382
  _mtrx[_nm] = v.get("performance", {})
383
383
 
384
384
  _m_repr = pd.DataFrame.from_dict(_mtrx, orient="index")[
385
- ["gain", "cagr", "sharpe", "qr", "max_dd_pct", "mdd_usd", "fees", "execs"]
385
+ ["gain", "cagr", "sharpe", "qr", "mdd_pct", "mdd_usd", "fees", "execs"]
386
386
  ].astype(float)
387
387
  _m_repr = _m_repr.round(3)
388
388
  _m_repr = _m_repr.sort_values(by=sort_by, ascending=ascending) if sort_by else _m_repr
@@ -79,8 +79,11 @@ class OrdersManagementEngine:
79
79
  self.active_orders = dict()
80
80
  self.stop_orders = dict()
81
81
  self.bbo = None
82
- self.__order_id = 100000
83
- self.__trade_id = 100000
82
+ # - 2025-12-09: OME always started with fixed order_id / trade_id
83
+ # executions stopped to be accounted in portfolio when OME re-created
84
+ _start_id = int(time_provider.time().view("i8")) + 1000
85
+ self.__order_id = _start_id
86
+ self.__trade_id = _start_id
84
87
  self._fill_stops_at_price = fill_stop_order_at_price
85
88
  self._tick_size = instrument.tick_size
86
89
  self._last_update_time = np.datetime64(0, "ns")
@@ -16,6 +16,7 @@ from qubx.core.initializer import BasicStrategyInitializer
16
16
  from qubx.core.interfaces import (
17
17
  CtrlChannel,
18
18
  IDataProvider,
19
+ IHealthMonitor,
19
20
  IMetricEmitter,
20
21
  IStrategy,
21
22
  IStrategyContext,
@@ -26,6 +27,7 @@ from qubx.core.interfaces import (
26
27
  from qubx.core.loggers import StrategyLogging
27
28
  from qubx.core.lookups import lookup
28
29
  from qubx.data.helpers import CachedPrefetchReader
30
+ from qubx.health import DummyHealthMonitor
29
31
  from qubx.loggers.inmemory import InMemoryLogsWriter
30
32
  from qubx.pandaz.utils import _frame_to_str
31
33
  from qubx.utils.time import now_ns
@@ -140,18 +142,20 @@ class SimulationRunner:
140
142
 
141
143
  self._prefetch_aux_data()
142
144
 
143
- # Start the context
145
+ # - Apply warmup periods before the start
146
+ # - merge default warmups with strategy warmups (strategy warmups take precedence)
147
+ _merged_warmups = {
148
+ **(self.data_config.default_warmups or {}),
149
+ **self.ctx.initializer.get_subscription_warmup(),
150
+ }
151
+ if _merged_warmups:
152
+ logger.debug(f"[<y>SimulationRunner</y>] :: Setting warmups: {_merged_warmups}")
153
+ self.ctx.set_warmup(_merged_warmups)
154
+
155
+ # - Start the context
144
156
  self.ctx.start()
145
157
 
146
- # Apply default warmup periods if strategy didn't set them
147
- for s in self.ctx.get_subscriptions():
148
- if not self.ctx.get_warmup(s) and (_d_wt := self.data_config.default_warmups.get(s)):
149
- logger.debug(
150
- f"[<y>SimulationRunner</y>] :: Strategy didn't set warmup period for <c>{s}</c> so default <c>{_d_wt}</c> will be used"
151
- )
152
- self.ctx.set_warmup({s: _d_wt})
153
-
154
- # Subscribe to any custom data types if needed
158
+ # - Subscribe to any custom data types if needed
155
159
  def _is_known_type(t: str):
156
160
  try:
157
161
  DataType(t)
@@ -387,9 +391,14 @@ class SimulationRunner:
387
391
 
388
392
  channel = SimulatedCtrlChannel("databus", sentinel=(None, None, None, None))
389
393
  simulated_clock = SimulatedTimeProvider(np.datetime64(self.start, "ns"))
394
+ health_monitor = DummyHealthMonitor()
390
395
 
391
396
  account = self._construct_account_processor(
392
- self.setup.exchanges, self.setup.commissions, simulated_clock, channel
397
+ self.setup.exchanges,
398
+ self.setup.commissions,
399
+ simulated_clock,
400
+ channel,
401
+ health_monitor,
393
402
  )
394
403
 
395
404
  scheduler = SimulatedScheduler(channel, lambda: simulated_clock.time().item())
@@ -528,6 +537,7 @@ class SimulationRunner:
528
537
  commissions: str | dict[str, str | None] | None,
529
538
  time_provider: ITimeProvider,
530
539
  channel: CtrlChannel,
540
+ health_monitor: IHealthMonitor,
531
541
  ) -> CompositeAccountProcessor:
532
542
  _exchange_to_tcc = self._construct_tcc(exchanges, commissions)
533
543
  for tcc in _exchange_to_tcc.values():
@@ -555,7 +565,9 @@ class SimulationRunner:
555
565
  account_id=self.account_id,
556
566
  exchange=_exchange_to_simulated_exchange[exchange],
557
567
  channel=channel,
568
+ health_monitor=health_monitor,
558
569
  base_currency=self.setup.base_currency,
570
+ exchange_name=exchange,
559
571
  initial_capital=_initial_capital,
560
572
  )
561
573
 
@@ -47,6 +47,10 @@ class ISimulatedExchange:
47
47
 
48
48
  def get_open_orders(self, instrument: Instrument | None = None) -> dict[str, Order]: ...
49
49
 
50
+ def on_unsubscribe(self, instrument: Instrument) -> None: ...
51
+
52
+ def on_subscribe(self, instrument: Instrument) -> None: ...
53
+
50
54
  def process_market_data(
51
55
  self, instrument: Instrument, data: Quote | OrderBook | Trade | TradeArray
52
56
  ) -> Generator[SimulatedExecutionReport]: ...
@@ -93,6 +97,7 @@ class BasicSimulatedExchange(ISimulatedExchange):
93
97
  """
94
98
 
95
99
  _ome: dict[Instrument, OrdersManagementEngine]
100
+ _half_tick_size: dict[Instrument, float]
96
101
  _order_to_instrument: dict[str, Instrument]
97
102
  _fill_stop_order_at_price: bool
98
103
  _time_provider: ITimeProvider
@@ -112,6 +117,7 @@ class BasicSimulatedExchange(ISimulatedExchange):
112
117
  self._fill_stop_order_at_price = accurate_stop_orders_execution
113
118
  self._time_provider = time_provider
114
119
  self._tcc = tcc
120
+
115
121
  if self._fill_stop_order_at_price:
116
122
  logger.info(
117
123
  f"[<y>{self.__class__.__name__}</y>] :: emulation of stop orders executions at exact price is ON"
@@ -193,6 +199,23 @@ class BasicSimulatedExchange(ISimulatedExchange):
193
199
 
194
200
  return {o.id: o for ome in self._ome.values() for o in ome.get_open_orders()}
195
201
 
202
+ def on_unsubscribe(self, instrument: Instrument) -> None:
203
+ """
204
+ Called when an instrument is unsubscribed.
205
+ """
206
+ # - clears the OME to remove stale BBO data.
207
+ self._ome.pop(instrument, None)
208
+ self._half_tick_size.pop(instrument, None)
209
+
210
+ def on_subscribe(self, instrument: Instrument) -> None:
211
+ """
212
+ Called when new instrument is subscribed.
213
+ """
214
+ # - just for sanity: remove OME for this instrument if it wasn't removed in on_unsubscribe call
215
+ if instrument in self._ome:
216
+ self._ome.pop(instrument, None)
217
+ self._half_tick_size.pop(instrument, None)
218
+
196
219
  def _get_ome(self, instrument: Instrument) -> OrdersManagementEngine:
197
220
  if (ome := self._ome.get(instrument)) is None:
198
221
  self._half_tick_size[instrument] = instrument.tick_size / 2 # type: ignore