Qubx 0.7.40.dev12__tar.gz → 1.0.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/PKG-INFO +1 -3
  2. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/pyproject.toml +1 -2
  3. qubx-1.0.0.dev2/src/qubx/_nb_magic.py +99 -0
  4. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/_version.py +2 -2
  5. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/data.py +25 -90
  6. qubx-1.0.0.dev2/src/qubx/backtester/iteratedstream.py +194 -0
  7. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/runner.py +148 -171
  8. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/sentinels.py +11 -15
  9. qubx-1.0.0.dev2/src/qubx/backtester/simulated_data.py +1202 -0
  10. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/simulator.py +20 -30
  11. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/utils.py +135 -434
  12. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/data.py +0 -3
  13. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/tardis/data.py +7 -8
  14. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/basics.py +42 -9
  15. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/context.py +32 -36
  16. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/detectors/stale.py +5 -5
  17. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/helpers.py +3 -266
  18. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/interfaces.py +57 -15
  19. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/lookups.py +12 -5
  20. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/metrics.py +14 -7
  21. qubx-1.0.0.dev2/src/qubx/core/mixins/market.py +455 -0
  22. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/processing.py +7 -15
  23. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/universe.py +7 -7
  24. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/series.pxd +23 -0
  25. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/series.pyi +71 -0
  26. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/series.pyx +324 -0
  27. qubx-1.0.0.dev2/src/qubx/data/__init__.py +24 -0
  28. qubx-1.0.0.dev2/src/qubx/data/cache.py +574 -0
  29. qubx-1.0.0.dev2/src/qubx/data/containers.py +304 -0
  30. qubx-1.0.0.dev2/src/qubx/data/guards.py +129 -0
  31. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/data/registry.py +38 -155
  32. qubx-1.0.0.dev2/src/qubx/data/storage.py +116 -0
  33. qubx-1.0.0.dev2/src/qubx/data/storages/ccxt.py +769 -0
  34. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/data/storages/csv.py +42 -11
  35. qubx-1.0.0.dev2/src/qubx/data/storages/handy.py +311 -0
  36. qubx-1.0.0.dev2/src/qubx/data/storages/multi.py +454 -0
  37. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/data/storages/questdb.py +370 -23
  38. qubx-1.0.0.dev2/src/qubx/data/storages/stub.py +23 -0
  39. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/data/storages/utils.py +50 -23
  40. qubx-1.0.0.dev2/src/qubx/data/transformers.py +594 -0
  41. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/ta/indicators.pyi +7 -2
  42. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/marketdata/ccxt.py +7 -6
  43. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/marketdata/dukas.py +8 -15
  44. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/misc.py +22 -1
  45. qubx-1.0.0.dev2/src/qubx/utils/runner/__init__.py +0 -0
  46. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/configs.py +20 -16
  47. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/factory.py +72 -79
  48. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/runner.py +70 -39
  49. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/time.py +108 -7
  50. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +1 -0
  51. qubx-0.7.40.dev12/src/qubx/_nb_magic.py +0 -101
  52. qubx-0.7.40.dev12/src/qubx/backtester/simulated_data.py +0 -473
  53. qubx-0.7.40.dev12/src/qubx/connectors/ccxt/__init__.py +0 -3
  54. qubx-0.7.40.dev12/src/qubx/connectors/ccxt/reader.py +0 -874
  55. qubx-0.7.40.dev12/src/qubx/core/mixins/market.py +0 -167
  56. qubx-0.7.40.dev12/src/qubx/data/__init__.py +0 -38
  57. qubx-0.7.40.dev12/src/qubx/data/composite.py +0 -824
  58. qubx-0.7.40.dev12/src/qubx/data/containers.py +0 -234
  59. qubx-0.7.40.dev12/src/qubx/data/helpers.py +0 -2080
  60. qubx-0.7.40.dev12/src/qubx/data/hft.py +0 -855
  61. qubx-0.7.40.dev12/src/qubx/data/readers.py +0 -2245
  62. qubx-0.7.40.dev12/src/qubx/data/storage.py +0 -74
  63. qubx-0.7.40.dev12/src/qubx/data/tardis.py +0 -892
  64. qubx-0.7.40.dev12/src/qubx/data/transformers.py +0 -527
  65. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/.gitignore +0 -0
  66. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/LICENSE +0 -0
  67. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/README.md +0 -0
  68. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/hatch_build.py +0 -0
  69. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/__init__.py +0 -0
  70. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/__init__.py +0 -0
  71. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/account.py +0 -0
  72. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/broker.py +0 -0
  73. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/management.py +0 -0
  74. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/ome.py +0 -0
  75. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/optimization.py +0 -0
  76. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/simulated_exchange.py +0 -0
  77. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/backtester/transfers.py +0 -0
  78. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/__init__.py +0 -0
  79. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/commands.py +0 -0
  80. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/deploy.py +0 -0
  81. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/misc.py +0 -0
  82. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/release.py +0 -0
  83. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/cli/tui.py +0 -0
  84. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/__init__.py +0 -0
  85. {qubx-0.7.40.dev12/src/qubx/core → qubx-1.0.0.dev2/src/qubx/connectors/ccxt}/__init__.py +0 -0
  86. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/account.py +0 -0
  87. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  88. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  89. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/broker.py +0 -0
  90. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  91. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  92. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  93. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  94. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  95. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  96. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  97. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  98. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  99. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
  100. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
  101. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  102. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  103. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  104. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  105. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  106. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/factory.py +0 -0
  107. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  108. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  109. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  110. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  111. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  112. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  113. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  114. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  115. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  116. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  117. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  118. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  119. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  120. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/utils.py +0 -0
  121. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  122. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/registry.py +0 -0
  123. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/connectors/tardis/utils.py +0 -0
  124. {qubx-0.7.40.dev12/src/qubx/restarts → qubx-1.0.0.dev2/src/qubx/core}/__init__.py +0 -0
  125. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/account.py +0 -0
  126. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/detectors/__init__.py +0 -0
  127. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/detectors/delisting.py +0 -0
  128. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/errors.py +0 -0
  129. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/exceptions.py +0 -0
  130. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/initializer.py +0 -0
  131. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/loggers.py +0 -0
  132. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/__init__.py +0 -0
  133. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/subscription.py +0 -0
  134. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/trading.py +0 -0
  135. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/mixins/utils.py +0 -0
  136. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/utils.pyi +0 -0
  137. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/core/utils.pyx +0 -0
  138. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/__init__.py +0 -0
  139. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/base.py +0 -0
  140. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/composite.py +0 -0
  141. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/csv.py +0 -0
  142. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/indicator.py +0 -0
  143. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/inmemory.py +0 -0
  144. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/prometheus.py +0 -0
  145. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/emitters/questdb.py +0 -0
  146. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/__init__.py +0 -0
  147. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/composite.py +0 -0
  148. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/__init__.py +0 -0
  149. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/base.py +0 -0
  150. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/incremental.py +0 -0
  151. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/slack.py +0 -0
  152. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/formatters/target_position.py +0 -0
  153. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/redis_streams.py +0 -0
  154. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/exporters/slack.py +0 -0
  155. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/gathering/simplest.py +0 -0
  156. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/health/__init__.py +0 -0
  157. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/health/base.py +0 -0
  158. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/health/dummy.py +0 -0
  159. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/__init__.py +0 -0
  160. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/csv.py +0 -0
  161. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/factory.py +0 -0
  162. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/inmemory.py +0 -0
  163. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/loggers/mongo.py +0 -0
  164. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/notifications/__init__.py +0 -0
  165. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/notifications/composite.py +0 -0
  166. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/notifications/slack.py +0 -0
  167. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/notifications/throttler.py +0 -0
  168. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/pandaz/__init__.py +0 -0
  169. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/pandaz/stats.py +0 -0
  170. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/pandaz/ta.py +0 -0
  171. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/pandaz/utils.py +0 -0
  172. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/plugins/__init__.py +0 -0
  173. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/plugins/loader.py +0 -0
  174. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/_build.py +0 -0
  175. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/crypto-fees.ini +0 -0
  176. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  177. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  178. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  179. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  180. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  181. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  182. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  183. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  184. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  185. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  186. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  187. {qubx-0.7.40.dev12/src/qubx/ta → qubx-1.0.0.dev2/src/qubx/restarts}/__init__.py +0 -0
  188. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restarts/state_resolvers.py +0 -0
  189. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restarts/time_finders.py +0 -0
  190. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/__init__.py +0 -0
  191. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/balance.py +0 -0
  192. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/factory.py +0 -0
  193. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/interfaces.py +0 -0
  194. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/position.py +0 -0
  195. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/signal.py +0 -0
  196. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/state.py +0 -0
  197. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/restorers/utils.py +0 -0
  198. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/state/__init__.py +0 -0
  199. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/state/dummy.py +0 -0
  200. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/state/redis.py +0 -0
  201. {qubx-0.7.40.dev12/src/qubx/utils/plotting → qubx-1.0.0.dev2/src/qubx/ta}/__init__.py +0 -0
  202. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/ta/indicators.pxd +0 -0
  203. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/ta/indicators.pyx +0 -0
  204. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/__init__.py +0 -0
  205. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/base.py +0 -0
  206. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  207. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/config.yml.j2 +0 -0
  208. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  209. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  210. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  211. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  212. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  213. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/project/template.yml +0 -0
  214. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  215. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  216. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/config.yml.j2 +0 -0
  217. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  218. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  219. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  220. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/templates/simple/template.yml +0 -0
  221. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/__init__.py +0 -0
  222. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/advanced.py +0 -0
  223. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/composite.py +0 -0
  224. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/rebalancers.py +0 -0
  225. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/riskctrl.py +0 -0
  226. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/trackers/sizers.py +0 -0
  227. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/__init__.py +0 -0
  228. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/_pyxreloader.py +0 -0
  229. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/charting/lookinglass.py +0 -0
  230. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  231. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/charting/orderbook.py +0 -0
  232. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/collections.py +0 -0
  233. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/hft/__init__.py +0 -0
  234. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/hft/numba_utils.py +0 -0
  235. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/hft/orderbook.pyi +0 -0
  236. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/hft/orderbook.pyx +0 -0
  237. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/marketdata/binance.py +0 -0
  238. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/nonce.py +0 -0
  239. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/ntp.py +0 -0
  240. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/numbers_utils.py +0 -0
  241. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/orderbook.py +0 -0
  242. {qubx-0.7.40.dev12/src/qubx/utils/plotting/renderers → qubx-1.0.0.dev2/src/qubx/utils/plotting}/__init__.py +0 -0
  243. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/plotting/dashboard.py +0 -0
  244. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/plotting/data.py +0 -0
  245. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/plotting/interfaces.py +0 -0
  246. {qubx-0.7.40.dev12/src/qubx/utils/runner → qubx-1.0.0.dev2/src/qubx/utils/plotting/renderers}/__init__.py +0 -0
  247. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  248. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/questdb.py +0 -0
  249. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/rate_limiter.py +0 -0
  250. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/ringbuffer.pxd +0 -0
  251. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/ringbuffer.pyi +0 -0
  252. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/ringbuffer.pyx +0 -0
  253. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  254. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/accounts.py +0 -0
  255. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/kernel_service.py +0 -0
  256. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/__init__.py +0 -0
  257. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/app.py +0 -0
  258. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/handlers.py +0 -0
  259. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/init_code.py +0 -0
  260. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/kernel.py +0 -0
  261. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  262. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  263. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  264. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  265. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  266. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  267. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  268. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  269. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/slack.py +0 -0
  270. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/throttler.py +0 -0
  271. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/src/qubx/utils/websocket_manager.py +0 -0
  272. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  273. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  274. {qubx-0.7.40.dev12 → qubx-1.0.0.dev2}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Qubx
3
- Version: 0.7.40.dev12
3
+ Version: 1.0.0.dev2
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Project-URL: homepage, https://xlydian.com
6
6
  Project-URL: repository, https://github.com/xLydianSoftware/Qubx
@@ -55,8 +55,6 @@ Requires-Dist: toml<1,>=0.10.2
55
55
  Requires-Dist: tqdm
56
56
  Requires-Dist: uvloop<1,>=0.22.1; sys_platform != 'win32'
57
57
  Requires-Dist: websockets==15.0.1
58
- Provides-Extra: hft
59
- Requires-Dist: hftbacktest<3,>=2.2.0; extra == 'hft'
60
58
  Provides-Extra: k8
61
59
  Requires-Dist: prometheus-client<1,>=0.21.1; extra == 'k8'
62
60
  Description-Content-Type: text/markdown
@@ -66,7 +66,6 @@ docs = "https://xlydiansoftware.github.io/Qubx"
66
66
  [project.optional-dependencies]
67
67
  # Runtime optional features only (shipped with package)
68
68
  k8 = ["prometheus-client>=0.21.1,<1"]
69
- hft = ["hftbacktest>=2.2.0,<3"]
70
69
 
71
70
  [project.scripts]
72
71
  qubx = "qubx.cli.commands:main"
@@ -131,7 +130,7 @@ extend-select = ["I"]
131
130
  ignore = ["E731", "E722", "E741"]
132
131
 
133
132
  [tool.ruff.lint.extend-per-file-ignores]
134
- "*.ipynb" = ["F405", "F401", "E701", "E402", "F403", "E401", "E702", "I001"]
133
+ "*.ipynb" = [ "F405", "F401", "E701", "E402", "F403", "E401", "E702", "F821", "E731", ]
135
134
 
136
135
  [tool.pytest.ini_options]
137
136
  asyncio_mode = "auto"
@@ -0,0 +1,99 @@
1
+ """ "
2
+ Here stuff we want to have in every Jupyter notebook after calling %qubx magic
3
+ """
4
+
5
+ import qubx
6
+ from qubx import runtime_env
7
+ from qubx.utils.misc import add_project_to_system_path, logo
8
+
9
+
10
+ def np_fmt_short():
11
+ # default np output is 75 columns so extend it a bit and suppress scientific fmt for small floats
12
+ np.set_printoptions(linewidth=240, suppress=True)
13
+
14
+
15
+ def np_fmt_reset():
16
+ # reset default np printing options
17
+ np.set_printoptions(
18
+ edgeitems=3,
19
+ infstr="inf",
20
+ linewidth=75,
21
+ nanstr="nan",
22
+ precision=8,
23
+ suppress=False,
24
+ threshold=1000,
25
+ formatter=None,
26
+ )
27
+
28
+
29
+ if runtime_env() in ["notebook", "shell"]:
30
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
31
+ # -- all imports below will appear in notebook after calling %%qubx magic ---
32
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
33
+
34
+ # - - - - Common stuff - - - -
35
+ import numpy as np # type: ignore # noqa: F401
36
+ import pandas as pd # type: ignore # noqa: F401
37
+
38
+ # - - - - Charting stuff - - - -
39
+ from matplotlib import pyplot as plt # type: ignore # noqa: F401
40
+ from tqdm.auto import tqdm # type: ignore # noqa: F401
41
+
42
+ # - - - - TA stuff and indicators - - - -
43
+ import qubx.pandaz.ta as pta # type: ignore # noqa: F401
44
+ import qubx.ta.indicators as ta # type: ignore # noqa: F401
45
+ from qubx.backtester.optimization import variate # type: ignore # noqa: F401
46
+
47
+ # - - - - Simulator stuff - - - -
48
+ from qubx.backtester.simulator import simulate # type: ignore # noqa: F401
49
+
50
+ # - - - - Portfolio analysis - - - -
51
+ from qubx.core.metrics import ( # type: ignore # noqa: F401
52
+ calculate_leverage_per_symbol, # type: ignore
53
+ calculate_pnl_per_symbol, # type: ignore
54
+ chart_signals, # type: ignore
55
+ combine_sessions, # type: ignore
56
+ drop_symbols, # type: ignore
57
+ extend_trading_results, # type: ignore
58
+ find_session, # type: ignore
59
+ find_sessions, # type: ignore
60
+ get_symbol_pnls, # type: ignore
61
+ pick_symbols, # type: ignore
62
+ pnl, # type: ignore
63
+ portfolio_metrics, # type: ignore
64
+ tearsheet, # type: ignore
65
+ )
66
+
67
+ # - - - - Data storages - - - -
68
+ from qubx.data import ( # noqa: F401
69
+ CachedStorage, # type: ignore
70
+ CsvStorage, # type: ignore
71
+ QuestDBStorage, # type: ignore
72
+ )
73
+ from qubx.data.registry import StorageRegistry # noqa: F401 # type: ignore
74
+
75
+ # - - - - Utils - - - -
76
+ from qubx.pandaz.utils import ( # noqa: F401
77
+ continuous_periods, # type: ignore
78
+ drop_duplicated_indexes, # type: ignore
79
+ generate_equal_date_ranges, # type: ignore
80
+ ohlc_resample, # type: ignore
81
+ retain_columns_and_join, # type: ignore
82
+ rolling_forward_test_split, # type: ignore
83
+ scols, # type: ignore
84
+ srows, # type: ignore
85
+ )
86
+ from qubx.utils.charting.lookinglass import LookingGlass # type: ignore # noqa: F401
87
+ from qubx.utils.charting.mpl_helpers import fig, ohlc_plot, plot_trends, sbp, subplot # type: ignore # noqa: F401
88
+ from qubx.utils.misc import this_project_root # type: ignore # noqa: F401
89
+
90
+ # - setup short numpy output format
91
+ np_fmt_short()
92
+
93
+ # - add project home to system path
94
+ add_project_to_system_path()
95
+
96
+ # show logo first time
97
+ if not hasattr(qubx.QubxMagics, "__already_initialized__"):
98
+ setattr(qubx.QubxMagics, "__already_initialized__", True)
99
+ logo()
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.7.40.dev12'
32
- __version_tuple__ = version_tuple = (0, 7, 40, 'dev12')
31
+ __version__ = version = '1.0.0.dev2'
32
+ __version_tuple__ = version_tuple = (1, 0, 0, 'dev2')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -4,18 +4,14 @@ from typing import TypeVar
4
4
  import pandas as pd
5
5
 
6
6
  from qubx import logger
7
- from qubx.backtester.simulated_data import IterableSimulationData
7
+ from qubx.backtester.simulated_data import SimulatedDataIterator
8
8
  from qubx.core.basics import (
9
9
  CtrlChannel,
10
10
  DataType,
11
11
  Instrument,
12
- TimestampedDict,
13
12
  )
14
- from qubx.core.helpers import BasicScheduler
15
13
  from qubx.core.interfaces import IDataProvider
16
- from qubx.core.series import Bar, Quote, time_as_nsec
17
- from qubx.data.readers import AsDict, DataReader
18
- from qubx.utils.time import infer_series_frequency
14
+ from qubx.core.series import Bar, Quote
19
15
 
20
16
  from .account import SimulatedAccountProcessor
21
17
  from .utils import SimulatedTimeProvider
@@ -23,49 +19,50 @@ from .utils import SimulatedTimeProvider
23
19
  T = TypeVar("T")
24
20
 
25
21
 
26
- def _get_first_existing(data: dict, keys: list, default: T = None) -> T:
27
- data_get = data.get # Cache method lookup
28
- sentinel = object()
29
- for key in keys:
30
- if (value := data_get(key, sentinel)) is not sentinel and value is not None:
31
- return value
32
- return default
22
+ class SimulatedDataProvider(IDataProvider):
23
+ """
24
+ Per-exchange data provider for backtesting simulation.
33
25
 
26
+ Thin wrapper around SimulatedDataIterator (shared across all exchanges in a simulation).
27
+ Handles exchange-specific concerns: subscribe/unsubscribe lifecycle, last-quote tracking,
28
+ and account notifications (OME). One instance per exchange in the simulation.
29
+
30
+ Data flow for market data subscriptions:
31
+ subscribe() → SimulatedDataIterator.add_instruments_for_subscription()
32
+ → peek historical data → emulate last quote → notify account OME
33
+
34
+ Data flow for historical OHLC lookback (ctx.ohlc()):
35
+ get_ohlc(instrument, timeframe, nbarsback)
36
+ → uses time_provider.time() as current simulated time
37
+ → delegates to SimulatedDataIterator.get_ohlc() which reads from IStorage,
38
+ transforms via TypedRecords, and applies open_close_time_indent cut
39
+ to exclude bars that haven't "closed" yet at the simulated time
40
+ """
34
41
 
35
- class SimulatedDataProvider(IDataProvider):
36
42
  time_provider: SimulatedTimeProvider
37
43
  channel: CtrlChannel
38
44
 
39
- _scheduler: BasicScheduler
40
45
  _account: SimulatedAccountProcessor
41
46
  _last_quotes: dict[Instrument, Quote | None]
42
- _readers: dict[str, DataReader]
43
- _data_source: IterableSimulationData
44
- _open_close_time_indent_ns: int
47
+ _data_source: SimulatedDataIterator
45
48
 
46
49
  def __init__(
47
50
  self,
48
51
  exchange_id: str,
49
52
  channel: CtrlChannel,
50
- scheduler: BasicScheduler,
51
53
  time_provider: SimulatedTimeProvider,
52
54
  account: SimulatedAccountProcessor,
53
- readers: dict[str, DataReader],
54
- data_source: IterableSimulationData,
55
- open_close_time_indent_secs=1,
55
+ data_source: SimulatedDataIterator,
56
56
  ):
57
57
  self.channel = channel
58
58
  self.time_provider = time_provider
59
59
  self._exchange_id = exchange_id
60
- self._scheduler = scheduler
61
60
  self._account = account
62
- self._readers = readers
63
61
 
64
62
  # - simulation data source
65
63
  self._data_source = data_source
66
- self._open_close_time_indent_ns = open_close_time_indent_secs * 1_000_000_000 # convert seconds to nanoseconds
67
64
 
68
- # - create exchange's instance
65
+ # - create last quote holder
69
66
  self._last_quotes = defaultdict(lambda: None)
70
67
 
71
68
  logger.info(f"{self.__class__.__name__}.{exchange_id} is initialized")
@@ -152,19 +149,9 @@ class SimulatedDataProvider(IDataProvider):
152
149
  self._data_source.set_warmup_period(si[0], warm_period)
153
150
 
154
151
  def get_ohlc(self, instrument: Instrument, timeframe: str, nbarsback: int) -> list[Bar]:
155
- _reader = self._readers.get(DataType.OHLC)
156
- if _reader is None:
157
- logger.error(f"Reader for {DataType.OHLC} data not configured")
158
- return []
159
-
160
152
  start = pd.Timestamp(self.time_provider.time())
161
- end = start - nbarsback * (_timeframe := pd.Timedelta(timeframe))
162
- _spec = f"{instrument.exchange}:{instrument.symbol}"
163
- return self._convert_records_to_bars(
164
- _reader.read(data_id=_spec, start=start, stop=end, timeframe=timeframe, transform=AsDict()), # type: ignore
165
- time_as_nsec(self.time_provider.time()),
166
- _timeframe.asm8.item(),
167
- )
153
+ end = start - nbarsback * pd.Timedelta(timeframe)
154
+ return self._data_source.get_ohlc(instrument, timeframe, start, end)
168
155
 
169
156
  def get_quote(self, instrument: Instrument) -> Quote | None:
170
157
  return self._last_quotes[instrument]
@@ -172,57 +159,5 @@ class SimulatedDataProvider(IDataProvider):
172
159
  def close(self):
173
160
  pass
174
161
 
175
- def _convert_records_to_bars(
176
- self, records: list[TimestampedDict], cut_time_ns: int, timeframe_ns: int
177
- ) -> list[Bar]:
178
- """
179
- Convert records to bars and we need to cut last bar up to the cut_time_ns
180
- """
181
- bars = []
182
-
183
- # - if no records, return empty list to avoid exception from infer_series_frequency
184
- if not records or records is None:
185
- return bars
186
-
187
- if len(records) > 1:
188
- _data_tf = infer_series_frequency([r.time for r in records[:50]])
189
- timeframe_ns = _data_tf.item()
190
-
191
- for r in records:
192
- _b_ts_0 = r.time
193
- _b_ts_1 = _b_ts_0 + timeframe_ns - self._open_close_time_indent_ns
194
-
195
- if _b_ts_0 <= cut_time_ns and cut_time_ns < _b_ts_1:
196
- break
197
-
198
- # Handle None values in OHLC data
199
- open_price = r.data["open"]
200
- high_price = r.data["high"]
201
- low_price = r.data["low"]
202
- close_price = r.data["close"]
203
-
204
- # Skip this record if any OHLC value is None
205
- if open_price is None or high_price is None or low_price is None or close_price is None:
206
- continue
207
-
208
- bars.append(
209
- Bar(
210
- _b_ts_0,
211
- open_price,
212
- high_price,
213
- low_price,
214
- close_price,
215
- volume=r.data.get("volume", 0) or 0, # Handle None volume
216
- bought_volume=_get_first_existing(r.data, ["taker_buy_volume", "bought_volume"], 0),
217
- volume_quote=_get_first_existing(r.data, ["quote_volume", "volume_quote"], 0),
218
- bought_volume_quote=_get_first_existing(
219
- r.data, ["taker_buy_quote_volume", "bought_volume_quote"], 0
220
- ),
221
- trade_count=_get_first_existing(r.data, ["count", "trade_count"], 0),
222
- )
223
- )
224
-
225
- return bars
226
-
227
162
  def exchange(self) -> str:
228
163
  return self._exchange_id.upper()
@@ -0,0 +1,194 @@
1
+ import math
2
+ from collections import defaultdict, deque
3
+ from collections.abc import Callable, Iterator, Mapping
4
+ from typing import Any, TypeAlias
5
+
6
+ from qubx.core.basics import Timestamped
7
+
8
+ SlicerOutData: TypeAlias = tuple[str, int, Timestamped] | tuple
9
+
10
+
11
+ class IteratedDataStreamsSlicer(Iterator[SlicerOutData]):
12
+ """
13
+ This class manages seamless iteration over multiple time-series data streams,
14
+ ensuring that events are processed in the correct chronological order regardless of their source.
15
+ It supports adding / removing new data streams to the slicer on the fly (during the itration).
16
+ """
17
+
18
+ _iterators: dict[str, Iterator[list[Timestamped]]]
19
+ _buffers: dict[str, list[Timestamped]]
20
+ _keys: deque[str]
21
+ _iterating: bool
22
+
23
+ def __init__(self, time_func: Callable[[Timestamped], Any] = lambda x: x.time):
24
+ self._buffers = defaultdict(list)
25
+ self._iterators = {}
26
+ self._keys = deque()
27
+ self._iterating = False
28
+ self._time_func = time_func
29
+
30
+ def put(self, data: Mapping[str, Iterator[list[Timestamped]]]):
31
+ _rebuild = False
32
+ for k, vi in data.items():
33
+ if k not in self._keys:
34
+ self._iterators[k] = vi
35
+ self._buffers[k] = self._load_next_chunk_to_buffer(k) # do initial chunk fetching
36
+ self._keys.append(k)
37
+ _rebuild = True
38
+
39
+ # - rebuild strategy
40
+ if _rebuild and self._iterating:
41
+ self._build_initial_iteration_seq()
42
+
43
+ def __add__(self, data: Mapping[str, Iterator]) -> "IteratedDataStreamsSlicer":
44
+ self.put(data)
45
+ return self
46
+
47
+ def remove(self, keys: list[str] | str):
48
+ """
49
+ Remove data iterator and associated keys from the queue.
50
+ If the key is not found, it does nothing.
51
+ """
52
+ _keys = keys if isinstance(keys, list) else [keys]
53
+ _rebuild = False
54
+ for i in _keys:
55
+ # Check and remove from each data structure independently
56
+ removed_any = False
57
+
58
+ if i in self._buffers:
59
+ self._buffers.pop(i)
60
+ removed_any = True
61
+
62
+ if i in self._iterators:
63
+ self._iterators.pop(i)
64
+ removed_any = True
65
+
66
+ if i in self._keys:
67
+ self._keys.remove(i)
68
+ removed_any = True
69
+
70
+ if removed_any:
71
+ _rebuild = True
72
+
73
+ # - rebuild strategy
74
+ if _rebuild and self._iterating:
75
+ self._build_initial_iteration_seq()
76
+
77
+ def __iter__(self) -> Iterator:
78
+ self._build_initial_iteration_seq()
79
+ self._iterating = True
80
+ return self
81
+
82
+ def _build_initial_iteration_seq(self):
83
+ _init_seq = {k: self._time_func(self._buffers[k][-1]) for k in self._keys if self._buffers[k]}
84
+ _init_seq = dict(sorted(_init_seq.items(), key=lambda item: item[1]))
85
+ self._keys = deque(_init_seq.keys())
86
+
87
+ def _load_next_chunk_to_buffer(self, index: str) -> list[Timestamped]:
88
+ try:
89
+ return list(reversed(next(self._iterators[index])))
90
+ except (StopIteration, IndexError, RuntimeError):
91
+ return []
92
+
93
+ def _remove_iterator(self, key: str):
94
+ self._buffers.pop(key)
95
+ self._iterators.pop(key)
96
+ self._keys.remove(key)
97
+
98
+ def _pop_top(self, k: str) -> Timestamped:
99
+ """
100
+ Removes and returns the most recent timestamped data element from the buffer associated with the given key.
101
+ If the buffer is empty after popping, it attempts to load the next chunk of data into the buffer.
102
+ If no more data is available, the iterator associated with the key is removed.
103
+
104
+ Parameters:
105
+ k (str): The key identifying the data stream buffer to pop from.
106
+
107
+ Returns:
108
+ Timestamped: The most recent timestamped data element from the buffer.
109
+ """
110
+ if not self._buffers[k]:
111
+ raise StopIteration
112
+
113
+ v = (data := self._buffers[k]).pop()
114
+ if not data:
115
+ try:
116
+ data.extend(self._load_next_chunk_to_buffer(k)) # - get next chunk of data
117
+ except StopIteration:
118
+ self._remove_iterator(k) # - remove iterable data
119
+ return v
120
+
121
+ def fetch_before_time(self, key: str, time_ns: int) -> list[Timestamped]:
122
+ """
123
+ Fetches and returns all timestamped data elements from the buffer associated with the given key
124
+ that have a timestamp earlier than the specified time.
125
+
126
+ Parameters:
127
+ - key (str): The key identifying the data stream buffer to fetch from.
128
+ - time_ns (int): The timestamp in nanoseconds. All returned elements will have a timestamp less than this value.
129
+
130
+ Returns:
131
+ - list[Timestamped]: A list of timestamped data elements that occur before the specified time.
132
+ """
133
+ values = []
134
+ data = self._buffers[key]
135
+ if not data:
136
+ try:
137
+ data.extend(self._load_next_chunk_to_buffer(key)) # - get next chunk of data
138
+ except StopIteration:
139
+ self._remove_iterator(key)
140
+ # Return empty list if no data is available
141
+ return values
142
+
143
+ # Check if data is still empty after attempting to load
144
+ if not data:
145
+ return values
146
+
147
+ # pull most past elements
148
+ v = data[-1]
149
+ while self._time_func(v) < time_ns:
150
+ values.append(data.pop())
151
+ if not data:
152
+ try:
153
+ data.extend(self._load_next_chunk_to_buffer(key)) # - get next chunk of data
154
+ except StopIteration:
155
+ self._remove_iterator(key)
156
+ break
157
+ # Check if data is still empty after loading attempt
158
+ if not data:
159
+ break
160
+ v = data[-1]
161
+
162
+ return values
163
+
164
+ def __next__(self) -> SlicerOutData:
165
+ """
166
+ Advances the iterator to the next available timestamped data element across all data streams.
167
+
168
+ Returns:
169
+ - SlicerOutData: A tuple containing the key of the data stream, the timestamp of the data element, and the data element itself.
170
+ - NoDataContinue: If there are no data streams but scheduler has pending events.
171
+
172
+ Raises:
173
+ - StopIteration: If there are no more data elements to iterate over and no scheduled events.
174
+ """
175
+ if not self._keys:
176
+ # DON'T set _iterating = False here! We're still iterating, just temporarily out of data
177
+ # Return sentinel indicating no data streams but iteration could continue
178
+ from qubx.backtester.sentinels import NoDataContinue
179
+
180
+ return ("", 0, NoDataContinue())
181
+
182
+ _min_t = math.inf
183
+ _min_k = self._keys[0]
184
+ for i in self._keys:
185
+ if not self._buffers[i]:
186
+ continue
187
+
188
+ _x = self._buffers[i][-1]
189
+ if self._time_func(_x) < _min_t:
190
+ _min_t = self._time_func(_x)
191
+ _min_k = i
192
+
193
+ _v = self._pop_top(_min_k)
194
+ return (_min_k, self._time_func(_v), _v)