Qubx 0.6.84__tar.gz → 0.6.87__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 (217) hide show
  1. {qubx-0.6.84 → qubx-0.6.87}/PKG-INFO +1 -1
  2. {qubx-0.6.84 → qubx-0.6.87}/pyproject.toml +1 -1
  3. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/management.py +3 -2
  4. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/runner.py +1 -1
  5. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/commands.py +46 -1
  6. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/account.py +1 -0
  7. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +17 -9
  8. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +1 -1
  9. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/funding_rate.py +3 -3
  10. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/orderbook.py +8 -6
  11. qubx-0.6.87/src/qubx/connectors/ccxt/handlers/trade.py +207 -0
  12. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/reader.py +3 -2
  13. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/helpers.py +9 -3
  14. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/interfaces.py +7 -6
  15. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/metrics.py +74 -14
  16. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/subscription.py +7 -1
  17. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/series.pxd +3 -2
  18. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/series.pyi +3 -2
  19. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/series.pyx +30 -5
  20. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/base.py +23 -14
  21. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/composite.py +13 -0
  22. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/csv.py +2 -1
  23. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/indicator.py +4 -2
  24. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/inmemory.py +5 -4
  25. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/prometheus.py +2 -2
  26. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/questdb.py +16 -10
  27. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/formatters/__init__.py +8 -1
  28. qubx-0.6.87/src/qubx/exporters/formatters/target_position.py +78 -0
  29. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/health/base.py +7 -10
  30. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/configs.py +120 -17
  31. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/runner.py +6 -6
  32. qubx-0.6.84/src/qubx/connectors/ccxt/handlers/trade.py +0 -111
  33. {qubx-0.6.84 → qubx-0.6.87}/LICENSE +0 -0
  34. {qubx-0.6.84 → qubx-0.6.87}/README.md +0 -0
  35. {qubx-0.6.84 → qubx-0.6.87}/build.py +0 -0
  36. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/__init__.py +0 -0
  37. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/_nb_magic.py +0 -0
  38. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/__init__.py +0 -0
  39. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/account.py +0 -0
  40. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/broker.py +0 -0
  41. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/data.py +0 -0
  42. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/ome.py +0 -0
  43. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/optimization.py +0 -0
  44. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/sentinels.py +0 -0
  45. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/simulated_data.py +0 -0
  46. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/simulated_exchange.py +0 -0
  47. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/simulator.py +0 -0
  48. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/backtester/utils.py +0 -0
  49. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/__init__.py +0 -0
  50. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/deploy.py +0 -0
  51. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/misc.py +0 -0
  52. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/release.py +0 -0
  53. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/cli/tui.py +0 -0
  54. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/__init__.py +0 -0
  55. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  56. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  57. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/broker.py +0 -0
  58. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  59. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/data.py +0 -0
  60. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  61. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  62. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  63. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  64. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  65. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  66. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  67. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  68. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  69. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  70. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/factory.py +0 -0
  71. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  72. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  73. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  74. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  75. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  76. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  77. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  78. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  79. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  80. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  81. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/utils.py +0 -0
  82. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  83. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/tardis/data.py +0 -0
  84. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/connectors/tardis/utils.py +0 -0
  85. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/__init__.py +0 -0
  86. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/account.py +0 -0
  87. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/basics.py +0 -0
  88. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/context.py +0 -0
  89. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/deque.py +0 -0
  90. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/errors.py +0 -0
  91. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/exceptions.py +0 -0
  92. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/initializer.py +0 -0
  93. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/loggers.py +0 -0
  94. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/lookups.py +0 -0
  95. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/__init__.py +0 -0
  96. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/market.py +0 -0
  97. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/processing.py +0 -0
  98. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/trading.py +0 -0
  99. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/universe.py +0 -0
  100. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/mixins/utils.py +0 -0
  101. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/stale_data_detector.py +0 -0
  102. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/utils.pyi +0 -0
  103. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/core/utils.pyx +0 -0
  104. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/__init__.py +0 -0
  105. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/composite.py +0 -0
  106. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/helpers.py +0 -0
  107. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/hft.py +0 -0
  108. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/readers.py +0 -0
  109. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/registry.py +0 -0
  110. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/data/tardis.py +0 -0
  111. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/emitters/__init__.py +0 -0
  112. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/__init__.py +0 -0
  113. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/composite.py +0 -0
  114. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/formatters/base.py +0 -0
  115. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/formatters/incremental.py +0 -0
  116. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/formatters/slack.py +0 -0
  117. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/redis_streams.py +0 -0
  118. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/exporters/slack.py +0 -0
  119. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/__init__.py +0 -0
  120. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/core.py +0 -0
  121. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/orderbook.py +0 -0
  122. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/price.py +0 -0
  123. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/trades.py +0 -0
  124. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/features/utils.py +0 -0
  125. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/gathering/simplest.py +0 -0
  126. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/health/__init__.py +0 -0
  127. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/__init__.py +0 -0
  128. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/csv.py +0 -0
  129. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/factory.py +0 -0
  130. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/inmemory.py +0 -0
  131. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/loggers/mongo.py +0 -0
  132. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/math/__init__.py +0 -0
  133. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/math/stats.py +0 -0
  134. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/notifications/__init__.py +0 -0
  135. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/notifications/composite.py +0 -0
  136. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/notifications/slack.py +0 -0
  137. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/notifications/throttler.py +0 -0
  138. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/pandaz/__init__.py +0 -0
  139. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/pandaz/ta.py +0 -0
  140. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/pandaz/utils.py +0 -0
  141. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/_build.py +0 -0
  142. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/crypto-fees.ini +0 -0
  143. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  144. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  145. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  146. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  147. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  148. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  149. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  150. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  151. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  152. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  153. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  154. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restarts/__init__.py +0 -0
  155. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restarts/state_resolvers.py +0 -0
  156. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restarts/time_finders.py +0 -0
  157. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/__init__.py +0 -0
  158. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/balance.py +0 -0
  159. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/factory.py +0 -0
  160. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/interfaces.py +0 -0
  161. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/position.py +0 -0
  162. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/signal.py +0 -0
  163. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/state.py +0 -0
  164. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/restorers/utils.py +0 -0
  165. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/ta/__init__.py +0 -0
  166. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/ta/indicators.pxd +0 -0
  167. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/ta/indicators.pyi +0 -0
  168. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/ta/indicators.pyx +0 -0
  169. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/__init__.py +0 -0
  170. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/base.py +0 -0
  171. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  172. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/config.yml.j2 +0 -0
  173. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  174. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  175. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  176. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  177. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  178. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/project/template.yml +0 -0
  179. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  180. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  181. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/config.yml.j2 +0 -0
  182. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  183. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  184. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  185. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/templates/simple/template.yml +0 -0
  186. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/__init__.py +0 -0
  187. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/advanced.py +0 -0
  188. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/composite.py +0 -0
  189. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/rebalancers.py +0 -0
  190. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/riskctrl.py +0 -0
  191. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/trackers/sizers.py +0 -0
  192. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/__init__.py +0 -0
  193. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/_pyxreloader.py +0 -0
  194. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/charting/lookinglass.py +0 -0
  195. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  196. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/charting/orderbook.py +0 -0
  197. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/collections.py +0 -0
  198. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/marketdata/binance.py +0 -0
  199. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/marketdata/ccxt.py +0 -0
  200. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/marketdata/dukas.py +0 -0
  201. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/misc.py +0 -0
  202. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/ntp.py +0 -0
  203. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/numbers_utils.py +0 -0
  204. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/orderbook.py +0 -0
  205. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/__init__.py +0 -0
  206. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/dashboard.py +0 -0
  207. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/data.py +0 -0
  208. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/interfaces.py +0 -0
  209. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  210. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  211. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/questdb.py +0 -0
  212. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/__init__.py +0 -0
  213. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  214. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/accounts.py +0 -0
  215. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/runner/factory.py +0 -0
  216. {qubx-0.6.84 → qubx-0.6.87}/src/qubx/utils/time.py +0 -0
  217. {qubx-0.6.84 → qubx-0.6.87}/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.87
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.87"
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"
@@ -327,10 +327,11 @@ class BacktestsResultsManager:
327
327
  if not as_table:
328
328
  print(_s)
329
329
 
330
+ dd_column = "max_dd_pct" if "max_dd_pct" in metrics else "mdd_pct"
330
331
  if with_metrics:
331
332
  _m_repr = (
332
333
  pd.DataFrame.from_dict(metrics, orient="index")
333
- .T[["gain", "cagr", "sharpe", "qr", "max_dd_pct", "mdd_usd", "fees", "execs"]]
334
+ .T[["gain", "cagr", "sharpe", "qr", dd_column, "mdd_usd", "fees", "execs"]]
334
335
  .astype(float)
335
336
  )
336
337
  _m_repr = _m_repr.round(3).to_string(index=False)
@@ -345,7 +346,7 @@ class BacktestsResultsManager:
345
346
  metrics = {
346
347
  m: round(v, 3)
347
348
  for m, v in metrics.items()
348
- if m in ["gain", "cagr", "sharpe", "qr", "max_dd_pct", "mdd_usd", "fees", "execs"]
349
+ if m in ["gain", "cagr", "sharpe", "qr", dd_column, "mdd_usd", "fees", "execs"]
349
350
  }
350
351
  _t_rep.append(
351
352
  {"Index": info.get("idx", ""), "Strategy": name}
@@ -478,7 +478,7 @@ class SimulationRunner:
478
478
  )
479
479
 
480
480
  if self.emitter is not None:
481
- self.emitter.set_time_provider(simulated_clock)
481
+ self.emitter.set_context(ctx)
482
482
 
483
483
  # - setup base subscription from spec
484
484
  if ctx.get_base_subscription() == DataType.NONE:
@@ -137,6 +137,51 @@ def ls(directory: str):
137
137
  ls_strats(directory)
138
138
 
139
139
 
140
+ @main.command()
141
+ @click.argument("config-file", type=Path, required=True)
142
+ @click.option(
143
+ "--no-check-imports",
144
+ is_flag=True,
145
+ default=False,
146
+ help="Skip checking if strategy class can be imported",
147
+ show_default=True,
148
+ )
149
+ def validate(config_file: Path, no_check_imports: bool):
150
+ """
151
+ Validates a strategy configuration file without running it.
152
+
153
+ Checks for:
154
+ - Valid YAML syntax
155
+ - Required configuration fields
156
+ - Strategy class exists and can be imported (unless --no-check-imports)
157
+ - Exchange configurations are valid
158
+ - Simulation parameters are valid (if present)
159
+
160
+ Returns exit code 0 if valid, 1 if invalid.
161
+ """
162
+ from qubx.utils.runner.configs import validate_strategy_config
163
+
164
+ result = validate_strategy_config(config_file, check_imports=not no_check_imports)
165
+
166
+ if result.valid:
167
+ click.echo(click.style("✓ Configuration is valid", fg="green", bold=True))
168
+ if result.warnings:
169
+ click.echo(click.style("\nWarnings:", fg="yellow", bold=True))
170
+ for warning in result.warnings:
171
+ click.echo(click.style(f" - {warning}", fg="yellow"))
172
+ raise SystemExit(0)
173
+ else:
174
+ click.echo(click.style("✗ Configuration is invalid", fg="red", bold=True))
175
+ click.echo(click.style("\nErrors:", fg="red", bold=True))
176
+ for error in result.errors:
177
+ click.echo(click.style(f" - {error}", fg="red"))
178
+ if result.warnings:
179
+ click.echo(click.style("\nWarnings:", fg="yellow", bold=True))
180
+ for warning in result.warnings:
181
+ click.echo(click.style(f" - {warning}", fg="yellow"))
182
+ raise SystemExit(1)
183
+
184
+
140
185
  @main.command()
141
186
  @click.argument(
142
187
  "directory",
@@ -358,7 +403,7 @@ def init(
358
403
  The generated strategy can be run immediately with:
359
404
  poetry run qubx run --config config.yml --paper
360
405
  """
361
- from qubx.templates import TemplateManager, TemplateError
406
+ from qubx.templates import TemplateError, TemplateManager
362
407
 
363
408
  try:
364
409
  manager = TemplateManager()
@@ -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
@@ -8,7 +8,7 @@ from ...adapters.polling_adapter import PollingConfig, PollingToWebSocketAdapter
8
8
  from ..base import CcxtFuturePatchMixin
9
9
 
10
10
  # Constants
11
- FUNDING_RATE_DEFAULT_POLL_MINUTES = 5
11
+ FUNDING_RATE_DEFAULT_POLL_MINUTES = 1
12
12
  FUNDING_RATE_HOUR_MS = 60 * 60 * 1000 # 1 hour in milliseconds
13
13
 
14
14
 
@@ -71,7 +71,7 @@ class FundingRateDataHandler(BaseDataTypeHandler):
71
71
  channel.send((instrument, DataType.FUNDING_RATE, funding_rate, False))
72
72
 
73
73
  # Emit payment if funding interval changed
74
- if self._should_emit_payment(instrument, funding_rate):
74
+ if self._should_emit_payment(instrument, funding_rate, current_time):
75
75
  payment = self._create_funding_payment(instrument)
76
76
  channel.send((instrument, DataType.FUNDING_PAYMENT, payment, False))
77
77
 
@@ -101,7 +101,7 @@ class FundingRateDataHandler(BaseDataTypeHandler):
101
101
  stream_name=name,
102
102
  )
103
103
 
104
- def _should_emit_payment(self, instrument: Instrument, rate: FundingRate) -> bool:
104
+ def _should_emit_payment(self, instrument: Instrument, rate: FundingRate, current_time: dt_64) -> bool:
105
105
  """
106
106
  Determine if a funding payment should be emitted.
107
107
 
@@ -132,7 +132,7 @@ class FundingRateDataHandler(BaseDataTypeHandler):
132
132
  return False
133
133
 
134
134
  # Emit if next_funding_time has advanced (new funding period started)
135
- if rate.next_funding_time > last_info["payment_time"]:
135
+ if rate.next_funding_time > last_info["payment_time"] and current_time > last_info["payment_time"]:
136
136
  # Store payment info for _create_funding_payment
137
137
  self._pending_funding_rates[f"{key}_payment"] = {
138
138
  "rate": last_info["rate"].rate,
@@ -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
+ )
@@ -20,7 +20,7 @@ from .utils import ccxt_find_instrument, instrument_to_ccxt_symbol
20
20
 
21
21
  @reader("ccxt")
22
22
  class CcxtDataReader(DataReader):
23
- SUPPORTED_DATA_TYPES = {"ohlc", "funding_payment"}
23
+ SUPPORTED_DATA_TYPES = {"ohlc"}
24
24
 
25
25
  _exchanges: dict[str, Exchange]
26
26
  _loop: AsyncThreadLoop
@@ -74,7 +74,8 @@ class CcxtDataReader(DataReader):
74
74
  if instrument is None:
75
75
  return []
76
76
 
77
- _timeframe = pd.Timedelta(timeframe or "1m")
77
+ timeframe = timeframe or "1m"
78
+ _timeframe = pd.Timedelta(timeframe)
78
79
  _start, _stop = self._get_start_stop(start, stop, _timeframe)
79
80
 
80
81
  if _start > _stop:
@@ -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
  """
@@ -2050,7 +2050,7 @@ class IMetricEmitter:
2050
2050
  self,
2051
2051
  name: str,
2052
2052
  value: float,
2053
- tags: dict[str, str] | None = None,
2053
+ tags: dict[str, Any] | None = None,
2054
2054
  timestamp: dt_64 | None = None,
2055
2055
  instrument: Instrument | None = None,
2056
2056
  ) -> None:
@@ -2092,15 +2092,16 @@ class IMetricEmitter:
2092
2092
  """
2093
2093
  pass
2094
2094
 
2095
- def set_time_provider(self, time_provider: ITimeProvider) -> None:
2095
+ def set_context(self, context: "IStrategyContext") -> None:
2096
2096
  """
2097
- Set the time provider for the metric emitter.
2097
+ Set the strategy context for the metric emitter.
2098
2098
 
2099
- This method is used to set the time provider that will be used to get timestamps
2100
- when no explicit timestamp is provided in the emit method.
2099
+ This method is used to set the context that provides access to time and simulation state.
2100
+ The context is used to automatically add is_live tag and get timestamps when no explicit
2101
+ timestamp is provided in the emit method.
2101
2102
 
2102
2103
  Args:
2103
- time_provider: The time provider to use
2104
+ context: The strategy context to use
2104
2105
  """
2105
2106
  pass
2106
2107