Qubx 0.6.84__tar.gz → 0.6.85__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 (216) hide show
  1. {qubx-0.6.84 → qubx-0.6.85}/PKG-INFO +1 -1
  2. {qubx-0.6.84 → qubx-0.6.85}/pyproject.toml +1 -1
  3. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/account.py +1 -0
  4. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +17 -9
  5. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/orderbook.py +8 -6
  6. qubx-0.6.85/src/qubx/connectors/ccxt/handlers/trade.py +207 -0
  7. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/helpers.py +9 -3
  8. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/mixins/subscription.py +7 -1
  9. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/series.pxd +3 -2
  10. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/series.pyi +3 -2
  11. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/series.pyx +30 -5
  12. qubx-0.6.84/src/qubx/connectors/ccxt/handlers/trade.py +0 -111
  13. {qubx-0.6.84 → qubx-0.6.85}/LICENSE +0 -0
  14. {qubx-0.6.84 → qubx-0.6.85}/README.md +0 -0
  15. {qubx-0.6.84 → qubx-0.6.85}/build.py +0 -0
  16. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/__init__.py +0 -0
  17. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/_nb_magic.py +0 -0
  18. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/__init__.py +0 -0
  19. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/account.py +0 -0
  20. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/broker.py +0 -0
  21. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/data.py +0 -0
  22. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/management.py +0 -0
  23. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/ome.py +0 -0
  24. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/optimization.py +0 -0
  25. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/runner.py +0 -0
  26. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/sentinels.py +0 -0
  27. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/simulated_data.py +0 -0
  28. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/simulated_exchange.py +0 -0
  29. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/simulator.py +0 -0
  30. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/backtester/utils.py +0 -0
  31. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/cli/__init__.py +0 -0
  32. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/cli/commands.py +0 -0
  33. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/cli/deploy.py +0 -0
  34. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/cli/misc.py +0 -0
  35. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/cli/release.py +0 -0
  36. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/cli/tui.py +0 -0
  37. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/__init__.py +0 -0
  38. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  39. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  40. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/broker.py +0 -0
  41. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  42. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/data.py +0 -0
  43. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  44. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  45. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  46. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  47. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  48. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  49. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  50. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  51. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  52. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  53. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  54. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/factory.py +0 -0
  55. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  56. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  57. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  58. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  59. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  60. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  61. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  62. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  63. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/reader.py +0 -0
  64. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  65. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  66. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  67. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/utils.py +0 -0
  68. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  69. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/tardis/data.py +0 -0
  70. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/connectors/tardis/utils.py +0 -0
  71. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/__init__.py +0 -0
  72. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/account.py +0 -0
  73. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/basics.py +0 -0
  74. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/context.py +0 -0
  75. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/deque.py +0 -0
  76. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/errors.py +0 -0
  77. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/exceptions.py +0 -0
  78. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/initializer.py +0 -0
  79. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/interfaces.py +0 -0
  80. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/loggers.py +0 -0
  81. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/lookups.py +0 -0
  82. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/metrics.py +0 -0
  83. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/mixins/__init__.py +0 -0
  84. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/mixins/market.py +0 -0
  85. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/mixins/processing.py +0 -0
  86. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/mixins/trading.py +0 -0
  87. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/mixins/universe.py +0 -0
  88. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/mixins/utils.py +0 -0
  89. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/stale_data_detector.py +0 -0
  90. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/utils.pyi +0 -0
  91. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/core/utils.pyx +0 -0
  92. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/data/__init__.py +0 -0
  93. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/data/composite.py +0 -0
  94. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/data/helpers.py +0 -0
  95. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/data/hft.py +0 -0
  96. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/data/readers.py +0 -0
  97. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/data/registry.py +0 -0
  98. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/data/tardis.py +0 -0
  99. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/emitters/__init__.py +0 -0
  100. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/emitters/base.py +0 -0
  101. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/emitters/composite.py +0 -0
  102. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/emitters/csv.py +0 -0
  103. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/emitters/indicator.py +0 -0
  104. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/emitters/inmemory.py +0 -0
  105. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/emitters/prometheus.py +0 -0
  106. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/emitters/questdb.py +0 -0
  107. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/exporters/__init__.py +0 -0
  108. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/exporters/composite.py +0 -0
  109. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/exporters/formatters/__init__.py +0 -0
  110. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/exporters/formatters/base.py +0 -0
  111. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/exporters/formatters/incremental.py +0 -0
  112. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/exporters/formatters/slack.py +0 -0
  113. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/exporters/redis_streams.py +0 -0
  114. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/exporters/slack.py +0 -0
  115. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/features/__init__.py +0 -0
  116. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/features/core.py +0 -0
  117. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/features/orderbook.py +0 -0
  118. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/features/price.py +0 -0
  119. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/features/trades.py +0 -0
  120. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/features/utils.py +0 -0
  121. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/gathering/simplest.py +0 -0
  122. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/health/__init__.py +0 -0
  123. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/health/base.py +0 -0
  124. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/loggers/__init__.py +0 -0
  125. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/loggers/csv.py +0 -0
  126. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/loggers/factory.py +0 -0
  127. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/loggers/inmemory.py +0 -0
  128. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/loggers/mongo.py +0 -0
  129. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/math/__init__.py +0 -0
  130. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/math/stats.py +0 -0
  131. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/notifications/__init__.py +0 -0
  132. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/notifications/composite.py +0 -0
  133. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/notifications/slack.py +0 -0
  134. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/notifications/throttler.py +0 -0
  135. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/pandaz/__init__.py +0 -0
  136. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/pandaz/ta.py +0 -0
  137. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/pandaz/utils.py +0 -0
  138. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/_build.py +0 -0
  139. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/crypto-fees.ini +0 -0
  140. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  141. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  142. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  143. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  144. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  145. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  146. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  147. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  148. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  149. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  150. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  151. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restarts/__init__.py +0 -0
  152. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restarts/state_resolvers.py +0 -0
  153. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restarts/time_finders.py +0 -0
  154. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restorers/__init__.py +0 -0
  155. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restorers/balance.py +0 -0
  156. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restorers/factory.py +0 -0
  157. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restorers/interfaces.py +0 -0
  158. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restorers/position.py +0 -0
  159. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restorers/signal.py +0 -0
  160. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restorers/state.py +0 -0
  161. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/restorers/utils.py +0 -0
  162. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/ta/__init__.py +0 -0
  163. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/ta/indicators.pxd +0 -0
  164. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/ta/indicators.pyi +0 -0
  165. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/ta/indicators.pyx +0 -0
  166. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/__init__.py +0 -0
  167. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/base.py +0 -0
  168. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  169. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/project/config.yml.j2 +0 -0
  170. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  171. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  172. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  173. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  174. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  175. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/project/template.yml +0 -0
  176. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  177. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  178. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/simple/config.yml.j2 +0 -0
  179. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  180. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  181. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  182. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/templates/simple/template.yml +0 -0
  183. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/trackers/__init__.py +0 -0
  184. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/trackers/advanced.py +0 -0
  185. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/trackers/composite.py +0 -0
  186. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/trackers/rebalancers.py +0 -0
  187. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/trackers/riskctrl.py +0 -0
  188. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/trackers/sizers.py +0 -0
  189. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/__init__.py +0 -0
  190. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/_pyxreloader.py +0 -0
  191. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/charting/lookinglass.py +0 -0
  192. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  193. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/charting/orderbook.py +0 -0
  194. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/collections.py +0 -0
  195. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/marketdata/binance.py +0 -0
  196. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/marketdata/ccxt.py +0 -0
  197. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/marketdata/dukas.py +0 -0
  198. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/misc.py +0 -0
  199. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/ntp.py +0 -0
  200. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/numbers_utils.py +0 -0
  201. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/orderbook.py +0 -0
  202. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/plotting/__init__.py +0 -0
  203. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/plotting/dashboard.py +0 -0
  204. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/plotting/data.py +0 -0
  205. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/plotting/interfaces.py +0 -0
  206. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  207. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  208. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/questdb.py +0 -0
  209. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/runner/__init__.py +0 -0
  210. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  211. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/runner/accounts.py +0 -0
  212. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/runner/configs.py +0 -0
  213. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/runner/factory.py +0 -0
  214. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/runner/runner.py +0 -0
  215. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/time.py +0 -0
  216. {qubx-0.6.84 → qubx-0.6.85}/src/qubx/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Qubx
3
- Version: 0.6.84
3
+ Version: 0.6.85
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  License-File: LICENSE
6
6
  Author: Dmitry Marienko
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "Qubx"
7
- version = "0.6.84"
7
+ version = "0.6.85"
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"
@@ -130,6 +130,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
130
130
 
131
131
  if not self.exchange_manager.exchange.isSandboxModeEnabled:
132
132
  # - start polling tasks
133
+ self._loop.submit(self.exchange_manager.exchange.load_markets()).result()
133
134
  self._polling_tasks["balance"] = self._loop.submit(
134
135
  self._poller("balance", self._update_balance, self.balance_interval)
135
136
  )
@@ -9,7 +9,7 @@ from qubx.core.exceptions import BadRequest
9
9
  class HyperliquidCcxtBroker(CcxtBroker):
10
10
  """
11
11
  HyperLiquid-specific broker that handles market order slippage requirements.
12
-
12
+
13
13
  HyperLiquid requires a price even for market orders to calculate max slippage.
14
14
  This broker automatically calculates slippage-protected prices for market orders.
15
15
  """
@@ -17,7 +17,7 @@ class HyperliquidCcxtBroker(CcxtBroker):
17
17
  def __init__(
18
18
  self,
19
19
  *args,
20
- market_order_slippage: float = 0.05, # 5% default slippage
20
+ market_order_slippage: float = 0.01, # 5% default slippage
21
21
  **kwargs,
22
22
  ):
23
23
  super().__init__(*args, **kwargs)
@@ -38,21 +38,29 @@ class HyperliquidCcxtBroker(CcxtBroker):
38
38
  if order_type.lower() == "market" and price is None:
39
39
  quote = self.data_provider.get_quote(instrument)
40
40
  if quote is None:
41
- logger.warning(f"[<y>{instrument.symbol}</y>] :: Quote is not available for market order slippage calculation.")
42
- raise BadRequest(f"Quote is not available for market order slippage calculation for {instrument.symbol}")
41
+ logger.warning(
42
+ f"[<y>{instrument.symbol}</y>] :: Quote is not available for market order slippage calculation."
43
+ )
44
+ raise BadRequest(
45
+ f"Quote is not available for market order slippage calculation for {instrument.symbol}"
46
+ )
43
47
 
44
48
  # Get slippage from options or use default
45
49
  slippage = options.get("slippage", self.market_order_slippage)
46
-
50
+
47
51
  # Calculate slippage-protected price
48
52
  if order_side.upper() == "BUY":
49
53
  # For buy orders, add slippage to ask price to ensure execution
50
54
  price = quote.ask * (1 + slippage)
51
- logger.debug(f"[<y>{instrument.symbol}</y>] :: Market BUY order: using slippage-protected price {price:.6f} (ask: {quote.ask:.6f}, slippage: {slippage:.1%})")
55
+ logger.debug(
56
+ f"[<y>{instrument.symbol}</y>] :: Market BUY order: using slippage-protected price {price:.6f} (ask: {quote.ask:.6f}, slippage: {slippage:.1%})"
57
+ )
52
58
  else: # SELL
53
59
  # For sell orders, subtract slippage from bid price to ensure execution
54
60
  price = quote.bid * (1 - slippage)
55
- logger.debug(f"[<y>{instrument.symbol}</y>] :: Market SELL order: using slippage-protected price {price:.6f} (bid: {quote.bid:.6f}, slippage: {slippage:.1%})")
61
+ logger.debug(
62
+ f"[<y>{instrument.symbol}</y>] :: Market SELL order: using slippage-protected price {price:.6f} (bid: {quote.bid:.6f}, slippage: {slippage:.1%})"
63
+ )
56
64
 
57
65
  # Call parent implementation with calculated price
58
66
  payload = super()._prepare_order_payload(
@@ -64,6 +72,6 @@ class HyperliquidCcxtBroker(CcxtBroker):
64
72
  if "slippage" in options:
65
73
  # HyperLiquid accepts slippage as a percentage (e.g., 0.05 for 5%)
66
74
  params["px"] = price # Explicit price for slippage calculation
67
-
75
+
68
76
  payload["params"] = params
69
- return payload
77
+ return payload
@@ -65,7 +65,7 @@ class OrderBookDataHandler(BaseDataTypeHandler):
65
65
 
66
66
  # Notify all listeners
67
67
  self._data_provider.notify_data_arrival(sub_type, dt_64(ob.time, "ns"))
68
-
68
+
69
69
  channel.send((instrument, sub_type, ob, False))
70
70
  return True
71
71
 
@@ -150,7 +150,7 @@ class OrderBookDataHandler(BaseDataTypeHandler):
150
150
  ) -> SubscriptionConfiguration:
151
151
  """
152
152
  Prepare subscription configuration for individual instruments.
153
-
153
+
154
154
  Creates separate subscriber functions for each instrument to enable independent
155
155
  WebSocket streams without waiting for all instruments. This follows the same
156
156
  pattern as the OHLC handler for proper individual stream management.
@@ -169,10 +169,10 @@ class OrderBookDataHandler(BaseDataTypeHandler):
169
169
  try:
170
170
  # Watch orderbook for single instrument
171
171
  ccxt_ob = await self._exchange_manager.exchange.watch_order_book(symbol)
172
-
172
+
173
173
  # Use private processing method to avoid duplication
174
174
  self._process_orderbook(ccxt_ob, inst, sub_type, channel, depth, tick_size_pct)
175
-
175
+
176
176
  except Exception as e:
177
177
  logger.error(
178
178
  f"<yellow>{exchange_id}</yellow> Error in individual orderbook subscription for {inst.symbol}: {e}"
@@ -186,13 +186,15 @@ class OrderBookDataHandler(BaseDataTypeHandler):
186
186
  # Create individual unsubscriber if exchange supports it
187
187
  un_watch_method = getattr(self._exchange_manager.exchange, "un_watch_order_book", None)
188
188
  if un_watch_method is not None and callable(un_watch_method):
189
-
189
+
190
190
  def create_individual_unsubscriber(symbol=ccxt_symbol, exchange_id=self._exchange_id):
191
191
  async def individual_unsubscriber():
192
192
  try:
193
193
  await self._exchange_manager.exchange.un_watch_order_book(symbol)
194
194
  except Exception as e:
195
- logger.error(f"<yellow>{exchange_id}</yellow> Error unsubscribing orderbook for {symbol}: {e}")
195
+ logger.error(
196
+ f"<yellow>{exchange_id}</yellow> Error unsubscribing orderbook for {symbol}: {e}"
197
+ )
196
198
 
197
199
  return individual_unsubscriber
198
200
 
@@ -0,0 +1,207 @@
1
+ """
2
+ Trade data type handler for CCXT data provider.
3
+
4
+ Handles subscription and warmup for trade data.
5
+ """
6
+
7
+ from typing import Set
8
+
9
+ from qubx import logger
10
+ from qubx.core.basics import CtrlChannel, DataType, Instrument, dt_64
11
+ from qubx.core.series import Quote
12
+
13
+ from ..subscription_config import SubscriptionConfiguration
14
+ from ..utils import (
15
+ ccxt_convert_trade,
16
+ ccxt_find_instrument,
17
+ create_market_type_batched_subscriber,
18
+ instrument_to_ccxt_symbol,
19
+ )
20
+ from .base import BaseDataTypeHandler
21
+
22
+
23
+ class TradeDataHandler(BaseDataTypeHandler):
24
+ """Handler for trade data subscription and processing."""
25
+
26
+ @property
27
+ def data_type(self) -> str:
28
+ return "trade"
29
+
30
+ def _process_trade(self, trades: list, instrument: Instrument, sub_type: str, channel: CtrlChannel):
31
+ """
32
+ Process trades with synthetic quote generation.
33
+
34
+ This method handles the common logic for processing trade data that's shared between
35
+ bulk and individual subscription approaches.
36
+
37
+ Args:
38
+ trades: List of CCXT trade dictionaries
39
+ instrument: Instrument these trades belong to
40
+ sub_type: Subscription type string
41
+ channel: Control channel to send data through
42
+ """
43
+ for trade in trades:
44
+ converted_trade = ccxt_convert_trade(trade)
45
+
46
+ # Notify all listeners
47
+ self._data_provider.notify_data_arrival(sub_type, dt_64(converted_trade.time, "ns"))
48
+
49
+ channel.send((instrument, sub_type, converted_trade, False))
50
+
51
+ # Generate synthetic quote if no quote/orderbook subscription exists
52
+ if len(trades) > 0 and not (
53
+ self._data_provider.has_subscription(instrument, DataType.ORDERBOOK)
54
+ or self._data_provider.has_subscription(instrument, DataType.QUOTE)
55
+ ):
56
+ last_trade = trades[-1]
57
+ converted_trade = ccxt_convert_trade(last_trade)
58
+ _price = converted_trade.price
59
+ _time = converted_trade.time
60
+ _s2 = instrument.tick_size / 2.0
61
+ _bid, _ask = _price - _s2, _price + _s2
62
+ self._data_provider._last_quotes[instrument] = Quote(_time, _bid, _ask, 0.0, 0.0)
63
+
64
+ def prepare_subscription(
65
+ self, name: str, sub_type: str, channel: CtrlChannel, instruments: Set[Instrument], **params
66
+ ) -> SubscriptionConfiguration:
67
+ """
68
+ Prepare trade subscription configuration.
69
+
70
+ Args:
71
+ name: Stream name for this subscription
72
+ sub_type: Parsed subscription type ("trade")
73
+ channel: Control channel for managing subscription lifecycle
74
+ instruments: Set of instruments to subscribe to
75
+
76
+ Returns:
77
+ SubscriptionConfiguration with subscriber and unsubscriber functions
78
+ """
79
+ # Use exchange-specific approach based on capabilities
80
+ if self._exchange_manager.exchange.has.get("watchTradesForSymbols", False):
81
+ return self._prepare_subscription_for_instruments(name, sub_type, channel, instruments)
82
+ else:
83
+ # Fall back to individual instrument subscriptions
84
+ return self._prepare_subscription_for_individual_instruments(name, sub_type, channel, instruments)
85
+
86
+ def _prepare_subscription_for_instruments(
87
+ self,
88
+ name: str,
89
+ sub_type: str,
90
+ channel: CtrlChannel,
91
+ instruments: Set[Instrument],
92
+ ) -> SubscriptionConfiguration:
93
+ """Prepare subscription configuration for multiple instruments using bulk API."""
94
+ _instr_to_ccxt_symbol = {i: instrument_to_ccxt_symbol(i) for i in instruments}
95
+ _symbol_to_instrument = {_instr_to_ccxt_symbol[i]: i for i in instruments}
96
+
97
+ async def watch_trades(instruments_batch: list[Instrument]):
98
+ symbols = [_instr_to_ccxt_symbol[i] for i in instruments_batch]
99
+ trades = await self._exchange_manager.exchange.watch_trades_for_symbols(symbols)
100
+
101
+ exch_symbol = trades[0]["symbol"]
102
+ instrument = ccxt_find_instrument(exch_symbol, self._exchange_manager.exchange, _symbol_to_instrument)
103
+
104
+ # Use private processing method to avoid duplication
105
+ self._process_trade(trades, instrument, sub_type, channel)
106
+
107
+ async def un_watch_trades(instruments_batch: list[Instrument]):
108
+ symbols = [_instr_to_ccxt_symbol[i] for i in instruments_batch]
109
+ await self._exchange_manager.exchange.un_watch_trades_for_symbols(symbols)
110
+
111
+ return SubscriptionConfiguration(
112
+ subscription_type=sub_type,
113
+ subscriber_func=create_market_type_batched_subscriber(watch_trades, instruments),
114
+ unsubscriber_func=create_market_type_batched_subscriber(un_watch_trades, instruments),
115
+ stream_name=name,
116
+ requires_market_type_batching=True,
117
+ )
118
+
119
+ def _prepare_subscription_for_individual_instruments(
120
+ self,
121
+ name: str,
122
+ sub_type: str,
123
+ channel: CtrlChannel,
124
+ instruments: Set[Instrument],
125
+ ) -> SubscriptionConfiguration:
126
+ """
127
+ Prepare subscription configuration for individual instruments.
128
+
129
+ Creates separate subscriber functions for each instrument to enable independent
130
+ WebSocket streams without waiting for all instruments. This follows the same
131
+ pattern as the orderbook handler for proper individual stream management.
132
+ """
133
+ _instr_to_ccxt_symbol = {i: instrument_to_ccxt_symbol(i) for i in instruments}
134
+
135
+ individual_subscribers = {}
136
+ individual_unsubscribers = {}
137
+
138
+ for instrument in instruments:
139
+ ccxt_symbol = _instr_to_ccxt_symbol[instrument]
140
+
141
+ # Create individual subscriber for this instrument using closure
142
+ def create_individual_subscriber(inst=instrument, symbol=ccxt_symbol, exchange_id=self._exchange_id):
143
+ async def individual_subscriber():
144
+ try:
145
+ # Watch trades for single instrument
146
+ trades = await self._exchange_manager.exchange.watch_trades(symbol)
147
+
148
+ # Use private processing method to avoid duplication
149
+ self._process_trade(trades, inst, sub_type, channel)
150
+
151
+ except Exception as e:
152
+ logger.error(
153
+ f"<yellow>{exchange_id}</yellow> Error in individual trade subscription for {inst.symbol}: {e}"
154
+ )
155
+ raise # Let connection manager handle retries
156
+
157
+ return individual_subscriber
158
+
159
+ individual_subscribers[instrument] = create_individual_subscriber()
160
+
161
+ # Create individual unsubscriber if exchange supports it
162
+ un_watch_method = getattr(self._exchange_manager.exchange, "un_watch_trades", None)
163
+ if un_watch_method is not None and callable(un_watch_method):
164
+
165
+ def create_individual_unsubscriber(symbol=ccxt_symbol, exchange_id=self._exchange_id):
166
+ async def individual_unsubscriber():
167
+ try:
168
+ await self._exchange_manager.exchange.un_watch_trades(symbol)
169
+ except Exception as e:
170
+ logger.error(f"<yellow>{exchange_id}</yellow> Error unsubscribing trades for {symbol}: {e}")
171
+
172
+ return individual_unsubscriber
173
+
174
+ individual_unsubscribers[instrument] = create_individual_unsubscriber()
175
+
176
+ return SubscriptionConfiguration(
177
+ subscription_type=sub_type,
178
+ instrument_subscribers=individual_subscribers,
179
+ instrument_unsubscribers=individual_unsubscribers if individual_unsubscribers else None,
180
+ stream_name=name,
181
+ requires_market_type_batching=False,
182
+ )
183
+
184
+ async def warmup(self, instruments: Set[Instrument], channel: CtrlChannel, warmup_period: str, **params) -> None:
185
+ """
186
+ Fetch historical trade data for warmup during backtesting.
187
+
188
+ Args:
189
+ instruments: Set of instruments to warm up
190
+ channel: Control channel for sending warmup data
191
+ warmup_period: Period to warm up (e.g., "30d", "1000h")
192
+ """
193
+ for instrument in instruments:
194
+ trades = await self._exchange_manager.exchange.fetch_trades(
195
+ instrument.symbol, since=self._data_provider._time_msec_nbars_back(warmup_period)
196
+ )
197
+
198
+ logger.debug(f"<yellow>{self._exchange_id}</yellow> Loaded {len(trades)} trades for {instrument}")
199
+
200
+ channel.send(
201
+ (
202
+ instrument,
203
+ DataType.TRADE,
204
+ [ccxt_convert_trade(trade) for trade in trades],
205
+ True, # historical data
206
+ )
207
+ )
@@ -233,10 +233,16 @@ class CachedMarketDataHolder:
233
233
  if series:
234
234
  total_vol = trade.size
235
235
  bought_vol = total_vol if trade.side == 1 else 0.0
236
+ volume_quote = trade.price * trade.size
237
+ bought_volume_quote = volume_quote if trade.side == 1 else 0.0
236
238
  for ser in series.values():
237
- if len(ser) > 0 and ser[0].time > trade.time:
238
- continue
239
- ser.update(trade.time, trade.price, total_vol, bought_vol)
239
+ if len(ser) > 0:
240
+ current_bar_start = floor_t64(np.datetime64(ser[0].time, 'ns'), np.timedelta64(ser.timeframe, 'ns'))
241
+ trade_bar_start = floor_t64(np.datetime64(trade.time, 'ns'), np.timedelta64(ser.timeframe, 'ns'))
242
+ if trade_bar_start < current_bar_start:
243
+ # Trade belongs to a previous bar - skip it
244
+ continue
245
+ ser.update(trade.time, trade.price, total_vol, bought_vol, volume_quote, bought_volume_quote, 1)
240
246
 
241
247
  def finalize_ohlc_for_instruments(self, time: dt_64, instruments: list[Instrument]):
242
248
  """
@@ -235,10 +235,16 @@ class SubscriptionManager(ISubscriptionManager):
235
235
  self._pending_warmups[(sub, instr)] = _warmup_period
236
236
 
237
237
  # TODO: think about appropriate handling of timeouts
238
- _data_provider.warmup(self._pending_warmups.copy())
238
+ _warmup_configs = self._get_pending_warmups_for_exchange(_data_provider.exchange())
239
+ _data_provider.warmup(_warmup_configs)
239
240
 
240
241
  self._pending_warmups.clear()
241
242
 
243
+ def _get_pending_warmups_for_exchange(self, exchange: str) -> dict[tuple[str, Instrument], str]:
244
+ return {
245
+ (sub, instr): period for (sub, instr), period in self._pending_warmups.items() if instr.exchange == exchange
246
+ }
247
+
242
248
  def _get_data_provider(self, exchange: str) -> IDataProvider:
243
249
  if exchange in self._exchange_to_data_provider:
244
250
  return self._exchange_to_data_provider[exchange]
@@ -63,7 +63,7 @@ cdef class Bar:
63
63
  cdef public double bought_volume_quote # volume bought (in quote asset) if presented
64
64
  cdef public int trade_count # number of trades in this bar
65
65
 
66
- cpdef Bar update(Bar self, double price, double volume, double volume_quote=*, double bought_volume=*, double bought_volume_quote=*, int trade_count=*)
66
+ cpdef Bar update(Bar self, double price, double volume, double bought_volume=*, double volume_quote=*, double bought_volume_quote=*, int trade_count=*)
67
67
 
68
68
  cpdef dict to_dict(Bar self, unsigned short skip_time=*)
69
69
 
@@ -78,8 +78,9 @@ cdef class OHLCV(TimeSeries):
78
78
  cdef public TimeSeries volume_quote
79
79
  cdef public TimeSeries bvolume_quote
80
80
  cdef public TimeSeries trade_count
81
+ cdef public dict columns
81
82
 
82
- cpdef short update(OHLCV self, long long time, double price, double volume=*, double bvolume=*)
83
+ cpdef short update(OHLCV self, long long time, double price, double volume=*, double bvolume=*, double volume_quote=*, double bought_volume_quote=*, int trade_count=*)
83
84
 
84
85
  cpdef short update_by_bar(
85
86
  OHLCV self, long long time, double open, double high, double low, double close,
@@ -31,8 +31,8 @@ class Bar:
31
31
  self,
32
32
  price: float,
33
33
  volume: float,
34
- volume_quote: float = 0,
35
34
  bought_volume: float = 0,
35
+ volume_quote: float = 0,
36
36
  bought_volume_quote: float = 0,
37
37
  trade_count: int = 0,
38
38
  ) -> "Bar": ...
@@ -127,7 +127,8 @@ class OHLCV(TimeSeries):
127
127
 
128
128
  def __init__(self, name, timeframe, max_series_length: int | float = np.inf) -> None: ...
129
129
  def __len__(self) -> int: ...
130
- def update(self, time: int, price: float, volume: float = 0.0, bvolume: float = 0.0) -> bool: ...
130
+ def update(self, time: int, price: float, volume: float = 0.0, bvolume: float = 0.0,
131
+ volume_quote: float = 0.0, bought_volume_quote: float = 0.0, trade_count: int = 0) -> bool: ...
131
132
  def update_by_bar(
132
133
  self,
133
134
  time: int,
@@ -773,7 +773,7 @@ cdef class Bar:
773
773
  self.bought_volume += bought_volume
774
774
  self.volume_quote += volume_quote
775
775
  self.bought_volume_quote += bought_volume_quote
776
- self.trade_count = trade_count # Use latest trade count (cumulative)
776
+ self.trade_count += trade_count # Increment trade count
777
777
  return self
778
778
 
779
779
  cpdef dict to_dict(self, unsigned short skip_time=0):
@@ -1020,6 +1020,22 @@ cdef class OHLCV(TimeSeries):
1020
1020
  self.volume_quote = TimeSeries('volume_quote', timeframe, max_series_length)
1021
1021
  self.bvolume_quote = TimeSeries('bvolume_quote', timeframe, max_series_length)
1022
1022
  self.trade_count = TimeSeries('trade_count', timeframe, max_series_length)
1023
+ self.columns = {
1024
+ "open": self.open,
1025
+ "high": self.high,
1026
+ "low": self.low,
1027
+ "close": self.close,
1028
+ "volume": self.volume,
1029
+ "bvolume": self.bvolume,
1030
+ "volume_quote": self.volume_quote,
1031
+ "bvolume_quote": self.bvolume_quote,
1032
+ "trade_count": self.trade_count,
1033
+ }
1034
+
1035
+ def __getitem__(self, idx):
1036
+ if isinstance(idx, str):
1037
+ return self.columns[idx]
1038
+ return super().__getitem__(idx)
1023
1039
 
1024
1040
  cpdef object append_data(self,
1025
1041
  np.ndarray times,
@@ -1114,18 +1130,25 @@ cdef class OHLCV(TimeSeries):
1114
1130
  self.trade_count._update_last_item(time, value.trade_count)
1115
1131
  self._is_new_item = False
1116
1132
 
1117
- cpdef short update(self, long long time, double price, double volume=0.0, double bvolume=0.0):
1133
+ cpdef short update(self, long long time, double price, double volume=0.0, double bvolume=0.0,
1134
+ double volume_quote=0.0, double bought_volume_quote=0.0, int trade_count=0):
1118
1135
  cdef Bar b
1119
1136
  bar_start_time = floor_t64(time, self.timeframe)
1120
1137
 
1121
1138
  if not self.times:
1122
- self._add_new_item(bar_start_time, Bar(bar_start_time, price, price, price, price, volume=volume, bought_volume=bvolume))
1139
+ self._add_new_item(bar_start_time, Bar(bar_start_time, price, price, price, price,
1140
+ volume=volume, bought_volume=bvolume,
1141
+ volume_quote=volume_quote, bought_volume_quote=bought_volume_quote,
1142
+ trade_count=trade_count))
1123
1143
 
1124
1144
  # Here we disable first notification because first item may be incomplete
1125
1145
  self._is_new_item = False
1126
1146
 
1127
1147
  elif (_dt := time - self.times[0]) >= self.timeframe:
1128
- b = Bar(bar_start_time, price, price, price, price, volume=volume, bought_volume=bvolume)
1148
+ b = Bar(bar_start_time, price, price, price, price,
1149
+ volume=volume, bought_volume=bvolume,
1150
+ volume_quote=volume_quote, bought_volume_quote=bought_volume_quote,
1151
+ trade_count=trade_count)
1129
1152
 
1130
1153
  # - add new item
1131
1154
  self._add_new_item(bar_start_time, b)
@@ -1138,7 +1161,9 @@ cdef class OHLCV(TimeSeries):
1138
1161
  if _dt < 0:
1139
1162
  raise ValueError(f"Attempt to update past data at {time_to_str(time)} !")
1140
1163
 
1141
- self._update_last_item(bar_start_time, self[0].update(price, volume, bvolume))
1164
+ self._update_last_item(bar_start_time, self[0].update(price, volume, bvolume,
1165
+ volume_quote, bought_volume_quote,
1166
+ trade_count))
1142
1167
 
1143
1168
  # - update indicators by new data
1144
1169
  self._update_indicators(bar_start_time, self[0], False)
@@ -1,111 +0,0 @@
1
- """
2
- Trade data type handler for CCXT data provider.
3
-
4
- Handles subscription and warmup for trade data.
5
- """
6
-
7
- from typing import Set
8
-
9
- from qubx import logger
10
- from qubx.core.basics import CtrlChannel, DataType, Instrument, dt_64
11
- from qubx.core.series import Quote
12
-
13
- from ..subscription_config import SubscriptionConfiguration
14
- from ..utils import (
15
- ccxt_convert_trade,
16
- ccxt_find_instrument,
17
- create_market_type_batched_subscriber,
18
- instrument_to_ccxt_symbol,
19
- )
20
- from .base import BaseDataTypeHandler
21
-
22
-
23
- class TradeDataHandler(BaseDataTypeHandler):
24
- """Handler for trade data subscription and processing."""
25
-
26
- @property
27
- def data_type(self) -> str:
28
- return "trade"
29
-
30
- def prepare_subscription(
31
- self, name: str, sub_type: str, channel: CtrlChannel, instruments: Set[Instrument], **params
32
- ) -> SubscriptionConfiguration:
33
- """
34
- Prepare trade subscription configuration.
35
-
36
- Args:
37
- name: Stream name for this subscription
38
- sub_type: Parsed subscription type ("trade")
39
- channel: Control channel for managing subscription lifecycle
40
- instruments: Set of instruments to subscribe to
41
-
42
- Returns:
43
- SubscriptionConfiguration with subscriber and unsubscriber functions
44
- """
45
- _instr_to_ccxt_symbol = {i: instrument_to_ccxt_symbol(i) for i in instruments}
46
- _symbol_to_instrument = {_instr_to_ccxt_symbol[i]: i for i in instruments}
47
-
48
- async def watch_trades(instruments_batch: list[Instrument]):
49
- symbols = [_instr_to_ccxt_symbol[i] for i in instruments_batch]
50
- trades = await self._exchange_manager.exchange.watch_trades_for_symbols(symbols)
51
-
52
- exch_symbol = trades[0]["symbol"]
53
- instrument = ccxt_find_instrument(exch_symbol, self._exchange_manager.exchange, _symbol_to_instrument)
54
-
55
- for trade in trades:
56
- converted_trade = ccxt_convert_trade(trade)
57
-
58
- # Notify all listeners
59
- self._data_provider.notify_data_arrival(sub_type, dt_64(converted_trade.time, "ns"))
60
-
61
- channel.send((instrument, sub_type, converted_trade, False))
62
-
63
- if len(trades) > 0 and not (
64
- self._data_provider.has_subscription(instrument, DataType.ORDERBOOK)
65
- or self._data_provider.has_subscription(instrument, DataType.QUOTE)
66
- ):
67
- last_trade = trades[-1]
68
- converted_trade = ccxt_convert_trade(last_trade)
69
- _price = converted_trade.price
70
- _time = converted_trade.time
71
- _s2 = instrument.tick_size / 2.0
72
- _bid, _ask = _price - _s2, _price + _s2
73
- self._data_provider._last_quotes[instrument] = Quote(_time, _bid, _ask, 0.0, 0.0)
74
-
75
- async def un_watch_trades(instruments_batch: list[Instrument]):
76
- symbols = [_instr_to_ccxt_symbol[i] for i in instruments_batch]
77
- await self._exchange_manager.exchange.un_watch_trades_for_symbols(symbols)
78
-
79
- # Return subscription configuration instead of calling _listen_to_stream directly
80
- return SubscriptionConfiguration(
81
- subscription_type=sub_type,
82
- subscriber_func=create_market_type_batched_subscriber(watch_trades, instruments),
83
- unsubscriber_func=create_market_type_batched_subscriber(un_watch_trades, instruments),
84
- stream_name=name,
85
- requires_market_type_batching=True,
86
- )
87
-
88
- async def warmup(self, instruments: Set[Instrument], channel: CtrlChannel, warmup_period: str, **params) -> None:
89
- """
90
- Fetch historical trade data for warmup during backtesting.
91
-
92
- Args:
93
- instruments: Set of instruments to warm up
94
- channel: Control channel for sending warmup data
95
- warmup_period: Period to warm up (e.g., "30d", "1000h")
96
- """
97
- for instrument in instruments:
98
- trades = await self._exchange_manager.exchange.fetch_trades(
99
- instrument.symbol, since=self._data_provider._time_msec_nbars_back(warmup_period)
100
- )
101
-
102
- logger.debug(f"<yellow>{self._exchange_id}</yellow> Loaded {len(trades)} trades for {instrument}")
103
-
104
- channel.send(
105
- (
106
- instrument,
107
- DataType.TRADE,
108
- [ccxt_convert_trade(trade) for trade in trades],
109
- True, # historical data
110
- )
111
- )
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