Qubx 0.6.71__tar.gz → 0.6.73__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.71 → qubx-0.6.73}/PKG-INFO +1 -1
  2. {qubx-0.6.71 → qubx-0.6.73}/pyproject.toml +1 -1
  3. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/data.py +16 -6
  4. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/runner.py +7 -4
  5. qubx-0.6.73/src/qubx/connectors/ccxt/adapters/polling_adapter.py +247 -0
  6. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/connection_manager.py +122 -133
  7. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/data.py +108 -43
  8. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/__init__.py +26 -0
  9. qubx-0.6.73/src/qubx/connectors/ccxt/exchanges/base.py +63 -0
  10. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +186 -166
  11. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +3 -1
  12. qubx-0.6.73/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +6 -0
  13. qubx-0.6.73/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +69 -0
  14. qubx-0.6.73/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +519 -0
  15. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +3 -1
  16. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/factory.py +4 -0
  17. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/base.py +2 -1
  18. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/funding_rate.py +88 -88
  19. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/liquidation.py +1 -0
  20. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/ohlc.py +63 -45
  21. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/open_interest.py +12 -13
  22. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/orderbook.py +65 -39
  23. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/quote.py +3 -1
  24. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/trade.py +15 -1
  25. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/reader.py +179 -72
  26. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/subscription_config.py +39 -34
  27. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/subscription_manager.py +103 -118
  28. qubx-0.6.73/src/qubx/connectors/ccxt/subscription_orchestrator.py +365 -0
  29. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/utils.py +50 -26
  30. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/account.py +5 -5
  31. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/basics.py +24 -0
  32. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/initializer.py +7 -0
  33. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/interfaces.py +21 -5
  34. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/subscription.py +6 -1
  35. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/readers.py +11 -0
  36. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/advanced.py +41 -4
  37. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/charting/mpl_helpers.py +134 -0
  38. qubx-0.6.73/src/qubx/utils/charting/orderbook.py +314 -0
  39. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/runner.py +9 -0
  40. qubx-0.6.71/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -439
  41. qubx-0.6.71/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -1
  42. qubx-0.6.71/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -161
  43. qubx-0.6.71/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -328
  44. {qubx-0.6.71 → qubx-0.6.73}/LICENSE +0 -0
  45. {qubx-0.6.71 → qubx-0.6.73}/README.md +0 -0
  46. {qubx-0.6.71 → qubx-0.6.73}/build.py +0 -0
  47. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/__init__.py +0 -0
  48. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/_nb_magic.py +0 -0
  49. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/__init__.py +0 -0
  50. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/account.py +0 -0
  51. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/broker.py +0 -0
  52. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/management.py +0 -0
  53. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/ome.py +0 -0
  54. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/optimization.py +0 -0
  55. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/sentinels.py +0 -0
  56. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/simulated_data.py +0 -0
  57. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/simulated_exchange.py +0 -0
  58. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/simulator.py +0 -0
  59. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/backtester/utils.py +0 -0
  60. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/__init__.py +0 -0
  61. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/commands.py +0 -0
  62. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/deploy.py +0 -0
  63. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/misc.py +0 -0
  64. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/release.py +0 -0
  65. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/cli/tui.py +0 -0
  66. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/__init__.py +0 -0
  67. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/account.py +0 -0
  68. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  69. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/broker.py +0 -0
  70. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  71. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  72. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  73. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  74. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  75. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  76. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/tardis/data.py +0 -0
  77. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/connectors/tardis/utils.py +0 -0
  78. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/__init__.py +0 -0
  79. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/context.py +0 -0
  80. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/deque.py +0 -0
  81. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/errors.py +0 -0
  82. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/exceptions.py +0 -0
  83. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/helpers.py +0 -0
  84. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/loggers.py +0 -0
  85. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/lookups.py +0 -0
  86. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/metrics.py +0 -0
  87. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/__init__.py +0 -0
  88. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/market.py +0 -0
  89. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/processing.py +0 -0
  90. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/trading.py +0 -0
  91. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/mixins/universe.py +0 -0
  92. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/series.pxd +0 -0
  93. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/series.pyi +0 -0
  94. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/series.pyx +0 -0
  95. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/stale_data_detector.py +0 -0
  96. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/utils.pyi +0 -0
  97. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/core/utils.pyx +0 -0
  98. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/__init__.py +0 -0
  99. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/composite.py +0 -0
  100. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/helpers.py +0 -0
  101. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/hft.py +0 -0
  102. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/registry.py +0 -0
  103. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/data/tardis.py +0 -0
  104. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/__init__.py +0 -0
  105. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/base.py +0 -0
  106. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/composite.py +0 -0
  107. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/csv.py +0 -0
  108. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/indicator.py +0 -0
  109. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/inmemory.py +0 -0
  110. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/prometheus.py +0 -0
  111. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/emitters/questdb.py +0 -0
  112. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/__init__.py +0 -0
  113. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/composite.py +0 -0
  114. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/formatters/__init__.py +0 -0
  115. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/formatters/base.py +0 -0
  116. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/formatters/incremental.py +0 -0
  117. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/formatters/slack.py +0 -0
  118. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/redis_streams.py +0 -0
  119. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/exporters/slack.py +0 -0
  120. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/__init__.py +0 -0
  121. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/core.py +0 -0
  122. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/orderbook.py +0 -0
  123. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/price.py +0 -0
  124. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/trades.py +0 -0
  125. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/features/utils.py +0 -0
  126. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/gathering/simplest.py +0 -0
  127. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/health/__init__.py +0 -0
  128. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/health/base.py +0 -0
  129. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/__init__.py +0 -0
  130. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/csv.py +0 -0
  131. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/factory.py +0 -0
  132. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/inmemory.py +0 -0
  133. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/loggers/mongo.py +0 -0
  134. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/math/__init__.py +0 -0
  135. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/math/stats.py +0 -0
  136. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/notifications/__init__.py +0 -0
  137. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/notifications/composite.py +0 -0
  138. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/notifications/slack.py +0 -0
  139. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/notifications/throttler.py +0 -0
  140. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/pandaz/__init__.py +0 -0
  141. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/pandaz/ta.py +0 -0
  142. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/pandaz/utils.py +0 -0
  143. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/_build.py +0 -0
  144. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/crypto-fees.ini +0 -0
  145. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  146. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  147. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  148. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  149. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  150. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  151. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  152. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  153. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  154. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  155. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  156. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restarts/__init__.py +0 -0
  157. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restarts/state_resolvers.py +0 -0
  158. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restarts/time_finders.py +0 -0
  159. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/__init__.py +0 -0
  160. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/balance.py +0 -0
  161. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/factory.py +0 -0
  162. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/interfaces.py +0 -0
  163. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/position.py +0 -0
  164. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/signal.py +0 -0
  165. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/state.py +0 -0
  166. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/restorers/utils.py +0 -0
  167. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/ta/__init__.py +0 -0
  168. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/ta/indicators.pxd +0 -0
  169. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/ta/indicators.pyi +0 -0
  170. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/ta/indicators.pyx +0 -0
  171. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/__init__.py +0 -0
  172. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/base.py +0 -0
  173. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  174. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/config.yml.j2 +0 -0
  175. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  176. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  177. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  178. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  179. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  180. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/project/template.yml +0 -0
  181. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  182. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  183. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/config.yml.j2 +0 -0
  184. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  185. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  186. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  187. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/templates/simple/template.yml +0 -0
  188. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/__init__.py +0 -0
  189. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/composite.py +0 -0
  190. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/rebalancers.py +0 -0
  191. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/riskctrl.py +0 -0
  192. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/trackers/sizers.py +0 -0
  193. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/__init__.py +0 -0
  194. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/_pyxreloader.py +0 -0
  195. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/charting/lookinglass.py +0 -0
  196. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/collections.py +0 -0
  197. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/marketdata/binance.py +0 -0
  198. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/marketdata/ccxt.py +0 -0
  199. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/marketdata/dukas.py +0 -0
  200. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/misc.py +0 -0
  201. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/ntp.py +0 -0
  202. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/numbers_utils.py +0 -0
  203. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/orderbook.py +0 -0
  204. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/__init__.py +0 -0
  205. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/dashboard.py +0 -0
  206. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/data.py +0 -0
  207. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/interfaces.py +0 -0
  208. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  209. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  210. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/questdb.py +0 -0
  211. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/__init__.py +0 -0
  212. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  213. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/accounts.py +0 -0
  214. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/configs.py +0 -0
  215. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/runner/factory.py +0 -0
  216. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/time.py +0 -0
  217. {qubx-0.6.71 → qubx-0.6.73}/src/qubx/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Qubx
3
- Version: 0.6.71
3
+ Version: 0.6.73
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Author: Dmitry Marienko
6
6
  Author-email: dmitry.marienko@xlydian.com
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "Qubx"
7
- version = "0.6.71"
7
+ version = "0.6.73"
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"
@@ -27,7 +27,7 @@ def _get_first_existing(data: dict, keys: list, default: T = None) -> T:
27
27
  data_get = data.get # Cache method lookup
28
28
  sentinel = object()
29
29
  for key in keys:
30
- if (value := data_get(key, sentinel)) is not sentinel:
30
+ if (value := data_get(key, sentinel)) is not sentinel and value is not None:
31
31
  return value
32
32
  return default
33
33
 
@@ -169,14 +169,24 @@ class SimulatedDataProvider(IDataProvider):
169
169
  if _b_ts_0 <= cut_time_ns and cut_time_ns < _b_ts_1:
170
170
  break
171
171
 
172
+ # Handle None values in OHLC data
173
+ open_price = r.data["open"]
174
+ high_price = r.data["high"]
175
+ low_price = r.data["low"]
176
+ close_price = r.data["close"]
177
+
178
+ # Skip this record if any OHLC value is None
179
+ if open_price is None or high_price is None or low_price is None or close_price is None:
180
+ continue
181
+
172
182
  bars.append(
173
183
  Bar(
174
184
  _b_ts_0,
175
- r.data["open"],
176
- r.data["high"],
177
- r.data["low"],
178
- r.data["close"],
179
- volume=r.data.get("volume", 0),
185
+ open_price,
186
+ high_price,
187
+ low_price,
188
+ close_price,
189
+ volume=r.data.get("volume", 0) or 0, # Handle None volume
180
190
  bought_volume=_get_first_existing(r.data, ["taker_buy_volume", "bought_volume"], 0),
181
191
  volume_quote=_get_first_existing(r.data, ["quote_volume", "volume_quote"], 0),
182
192
  bought_volume_quote=_get_first_existing(
@@ -4,10 +4,9 @@ import numpy as np
4
4
  import pandas as pd
5
5
  from tqdm.auto import tqdm
6
6
 
7
- from qubx import logger
7
+ from qubx import QubxLogConfig, logger
8
8
  from qubx.backtester.sentinels import NoDataContinue
9
9
  from qubx.backtester.simulated_data import IterableSimulationData
10
- from qubx.backtester.utils import SimulationDataConfig, TimeGuardedWrapper
11
10
  from qubx.core.account import CompositeAccountProcessor
12
11
  from qubx.core.basics import SW, DataType, Instrument, TransactionCostsCalculator
13
12
  from qubx.core.context import StrategyContext
@@ -41,6 +40,7 @@ from .utils import (
41
40
  SimulatedTimeProvider,
42
41
  SimulationDataConfig,
43
42
  SimulationSetup,
43
+ TimeGuardedWrapper,
44
44
  )
45
45
 
46
46
 
@@ -328,6 +328,7 @@ class SimulationRunner:
328
328
 
329
329
  if not _run(instrument, data_type, event, is_hist):
330
330
  return False
331
+
331
332
  return True
332
333
 
333
334
  def _handle_no_data_scenario(self, stop_time):
@@ -356,8 +357,9 @@ class SimulationRunner:
356
357
  return False # No scheduled events, stop simulation
357
358
 
358
359
  def print_latency_report(self) -> None:
359
- _l_r = SW.latency_report()
360
- if _l_r is not None:
360
+ if (_l_r := SW.latency_report()) is not None:
361
+ _llvl = QubxLogConfig.get_log_level()
362
+ QubxLogConfig.set_log_level("INFO")
361
363
  logger.info(
362
364
  "<BLUE> Time spent in simulation report </BLUE>\n<r>"
363
365
  + _frame_to_str(
@@ -365,6 +367,7 @@ class SimulationRunner:
365
367
  )
366
368
  + "</r>"
367
369
  )
370
+ QubxLogConfig.set_log_level(_llvl)
368
371
 
369
372
  def _create_backtest_context(self) -> IStrategyContext:
370
373
  logger.debug(
@@ -0,0 +1,247 @@
1
+ """
2
+ Simplified polling adapter to convert CCXT fetch_* methods into watch_* behavior.
3
+
4
+ This adapter provides a much simpler approach:
5
+ - No background tasks or queues
6
+ - get_next_data() waits until it's time to poll, then polls synchronously
7
+ - Time-aligned polling (e.g., 11:30, 11:35, 11:40 for 5-minute intervals)
8
+ - Immediate polling when symbols change
9
+ """
10
+
11
+ import asyncio
12
+ import math
13
+ import time
14
+ from dataclasses import dataclass
15
+ from typing import Any, Callable, Dict, List, Optional, Set
16
+
17
+ from qubx import logger
18
+
19
+ # Constants
20
+ DEFAULT_POLL_INTERVAL = 300 # 5 minutes
21
+ MIN_POLL_INTERVAL = 1 # 1 second minimum
22
+ MAX_POLL_INTERVAL = 3600 # 1 hour maximum
23
+
24
+
25
+ @dataclass
26
+ class PollingConfig:
27
+ """Configuration for polling adapter."""
28
+
29
+ poll_interval_seconds: float = DEFAULT_POLL_INTERVAL
30
+
31
+ def __post_init__(self):
32
+ """Validate configuration after initialization."""
33
+ if not MIN_POLL_INTERVAL <= self.poll_interval_seconds <= MAX_POLL_INTERVAL:
34
+ raise ValueError(
35
+ f"poll_interval_seconds must be between {MIN_POLL_INTERVAL} and {MAX_POLL_INTERVAL}, "
36
+ f"got {self.poll_interval_seconds}"
37
+ )
38
+
39
+
40
+ class PollingToWebSocketAdapter:
41
+ """
42
+ Simplified polling adapter that polls synchronously when data is requested.
43
+
44
+ Key features:
45
+ - No background tasks or queues
46
+ - Time-aligned polling (respects clock boundaries)
47
+ - Immediate polling when symbols change
48
+ - Thread-safe symbol management
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ fetch_method: Callable,
54
+ symbols: Optional[List[str]] = None,
55
+ params: Optional[Dict[str, Any]] = None,
56
+ config: Optional[PollingConfig] = None,
57
+ ):
58
+ """
59
+ Initialize the simplified polling adapter.
60
+
61
+ Args:
62
+ fetch_method: The CCXT fetch_* method to call
63
+ symbols: Initial list of symbols to watch
64
+ params: Additional parameters for fetch_method
65
+ config: PollingConfig instance (uses default if None)
66
+ """
67
+ self.config = config if config is not None else PollingConfig()
68
+ self.fetch_method = fetch_method
69
+ self.params = params or {}
70
+ self.adapter_id = f"polling_adapter_{id(self)}"
71
+
72
+ # Thread-safe symbol management
73
+ self._symbols_lock = asyncio.Lock()
74
+ self._symbols: Set[str] = set(symbols or [])
75
+
76
+ # Polling state
77
+ self._last_poll_time: Optional[float] = None
78
+ self._symbols_changed = False # Flag to trigger immediate poll
79
+
80
+ # Statistics
81
+ self._poll_count = 0
82
+ self._error_count = 0
83
+
84
+ async def get_next_data(self) -> Dict[str, Any]:
85
+ """
86
+ Get the next available data by waiting until it's time to poll, then polling.
87
+
88
+ This method:
89
+ 1. Checks if symbols changed (immediate poll)
90
+ 2. Calculates when next poll should happen based on time alignment
91
+ 3. Waits until that time
92
+ 4. Polls and returns fresh data
93
+
94
+ Returns:
95
+ Dictionary containing fetched data for symbols
96
+ """
97
+ async with self._symbols_lock:
98
+ current_symbols = list(self._symbols)
99
+ symbols_changed = self._symbols_changed
100
+
101
+ # If symbols changed, poll immediately
102
+ if symbols_changed:
103
+ logger.debug(f"Symbols changed, polling immediately for adapter {self.adapter_id}")
104
+ async with self._symbols_lock:
105
+ self._symbols_changed = False
106
+ return await self._poll_now(current_symbols)
107
+
108
+ # Calculate wait time for next aligned poll
109
+ wait_time = self._calculate_wait_time()
110
+
111
+ if wait_time > 0:
112
+ logger.debug(f"Waiting {wait_time:.1f}s for next poll cycle for adapter {self.adapter_id}")
113
+ await asyncio.sleep(wait_time)
114
+
115
+ # Time to poll
116
+ logger.debug(f"Polling now for adapter {self.adapter_id}")
117
+ return await self._poll_now(current_symbols)
118
+
119
+ def _calculate_wait_time(self) -> float:
120
+ """
121
+ Calculate how long to wait until the next aligned poll time.
122
+
123
+ For intervals >= 1 minute: aligns to clock boundaries (11:30, 11:35, 11:40)
124
+ For intervals < 1 minute: uses simple interval-based timing
125
+
126
+ Returns:
127
+ Number of seconds to wait (0 if should poll now)
128
+ """
129
+ current_time = time.time()
130
+ interval_seconds = self.config.poll_interval_seconds
131
+
132
+ # First poll is always immediate
133
+ if self._last_poll_time is None:
134
+ return 0
135
+
136
+ if interval_seconds >= 60:
137
+ # Time-aligned polling for intervals >= 1 minute using UTC
138
+ # Calculate next boundary based on seconds since epoch
139
+ next_boundary = math.ceil(current_time / interval_seconds) * interval_seconds
140
+ wait_time = next_boundary - current_time
141
+ return max(0, wait_time)
142
+ else:
143
+ # Simple interval-based polling for sub-minute intervals
144
+ next_poll_time = self._last_poll_time + interval_seconds
145
+ wait_time = next_poll_time - current_time
146
+ return max(0, wait_time)
147
+
148
+ async def _poll_now(self, symbols: List[str]) -> Dict[str, Any]:
149
+ """
150
+ Perform a poll operation immediately.
151
+
152
+ Args:
153
+ symbols: List of symbols to poll for
154
+
155
+ Returns:
156
+ Dictionary containing fetched data for symbols
157
+ """
158
+ self._poll_count += 1
159
+ self._last_poll_time = time.time()
160
+
161
+ logger.debug(f"Polling {len(symbols) if symbols else 'all'} symbols for adapter {self.adapter_id}")
162
+
163
+ try:
164
+ # Filter out adapter-specific parameters
165
+ adapter_params = {"pollInterval", "interval", "updateInterval", "poll_interval_minutes"}
166
+ fetch_params = {k: v for k, v in self.params.items() if k not in adapter_params}
167
+
168
+ # Call the fetch method
169
+ result = await self.fetch_method(symbols, **fetch_params)
170
+
171
+ logger.debug(f"Poll completed successfully for adapter {self.adapter_id}")
172
+ return result
173
+
174
+ except Exception as e:
175
+ self._error_count += 1
176
+ logger.error(f"Poll failed for adapter {self.adapter_id}: {e}")
177
+ raise
178
+
179
+ async def update_symbols(self, new_symbols: List[str]) -> None:
180
+ """
181
+ Update the symbol list.
182
+
183
+ If symbols changed, the next call to get_next_data() will poll immediately.
184
+
185
+ Args:
186
+ new_symbols: New complete list of symbols to watch
187
+ """
188
+ async with self._symbols_lock:
189
+ old_symbols = self._symbols.copy()
190
+ self._symbols = set(new_symbols or [])
191
+ symbols_changed = old_symbols != self._symbols
192
+
193
+ if symbols_changed:
194
+ self._symbols_changed = True
195
+ logger.debug(
196
+ f"Symbols updated for adapter {self.adapter_id}: {len(old_symbols)} -> {len(self._symbols)}"
197
+ )
198
+
199
+ async def add_symbols(self, new_symbols: List[str]) -> None:
200
+ """Add symbols to the existing watch list."""
201
+ if not new_symbols:
202
+ return
203
+
204
+ async with self._symbols_lock:
205
+ before_count = len(self._symbols)
206
+ self._symbols.update(new_symbols)
207
+ after_count = len(self._symbols)
208
+
209
+ if after_count > before_count:
210
+ self._symbols_changed = True
211
+ logger.debug(f"Added {after_count - before_count} symbols to adapter {self.adapter_id}")
212
+
213
+ async def remove_symbols(self, symbols_to_remove: List[str]) -> None:
214
+ """Remove symbols from the watch list."""
215
+ if not symbols_to_remove:
216
+ return
217
+
218
+ async with self._symbols_lock:
219
+ before_count = len(self._symbols)
220
+ self._symbols.difference_update(symbols_to_remove)
221
+ after_count = len(self._symbols)
222
+
223
+ if after_count < before_count:
224
+ self._symbols_changed = True
225
+ logger.debug(f"Removed {before_count - after_count} symbols from adapter {self.adapter_id}")
226
+
227
+ def is_watching(self, symbol: Optional[str] = None) -> bool:
228
+ """Check if adapter has symbols configured to watch."""
229
+ if symbol is None:
230
+ return len(self._symbols) > 0
231
+ else:
232
+ return symbol in self._symbols
233
+
234
+ def get_statistics(self) -> Dict[str, Any]:
235
+ """Get adapter statistics for monitoring."""
236
+ return {
237
+ "adapter_id": self.adapter_id,
238
+ "symbol_count": len(self._symbols),
239
+ "poll_count": self._poll_count,
240
+ "error_count": self._error_count,
241
+ "last_poll_time": self._last_poll_time,
242
+ "poll_interval_seconds": self.config.poll_interval_seconds,
243
+ }
244
+
245
+ async def stop(self) -> None:
246
+ """Stop the adapter (cleanup method for compatibility)."""
247
+ logger.debug(f"Adapter {self.adapter_id} stopped (polled {self._poll_count} times, {self._error_count} errors)")