Qubx 0.7.40.dev8__tar.gz → 0.7.40.dev9__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 (277) hide show
  1. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/PKG-INFO +1 -2
  2. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/pyproject.toml +0 -1
  3. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/_version.py +2 -2
  4. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/account.py +0 -1
  5. qubx-0.7.40.dev9/src/qubx/connectors/__init__.py +26 -0
  6. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/account.py +33 -13
  7. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/broker.py +27 -5
  8. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/connection_manager.py +1 -0
  9. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/data.py +23 -19
  10. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchange_manager.py +1 -0
  11. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +1 -0
  12. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/factory.py +22 -19
  13. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/subscription_orchestrator.py +1 -0
  14. qubx-0.7.40.dev9/src/qubx/connectors/registry.py +224 -0
  15. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/tardis/data.py +20 -29
  16. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/metrics.py +13 -3
  17. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/utils.pyx +3 -3
  18. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/registry.py +38 -0
  19. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/loggers/inmemory.py +4 -2
  20. qubx-0.7.40.dev9/src/qubx/plugins/__init__.py +7 -0
  21. qubx-0.7.40.dev9/src/qubx/plugins/loader.py +155 -0
  22. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/trackers/sizers.py +1 -1
  23. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/configs.py +11 -0
  24. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/factory.py +51 -10
  25. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/runner.py +93 -209
  26. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/__init__.py +0 -82
  27. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/account.py +0 -607
  28. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/broker.py +0 -737
  29. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/client.py +0 -467
  30. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/constants.py +0 -117
  31. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/data.py +0 -725
  32. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/extensions.py +0 -253
  33. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/factory.py +0 -240
  34. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/handlers/__init__.py +0 -13
  35. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/handlers/base.py +0 -116
  36. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/handlers/orderbook.py +0 -387
  37. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/handlers/stats.py +0 -352
  38. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/handlers/trades.py +0 -146
  39. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/instruments.py +0 -60
  40. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/nonce.py +0 -29
  41. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/parsers.py +0 -654
  42. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/rate_limits.py +0 -96
  43. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/reader.py +0 -453
  44. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/utils.py +0 -303
  45. qubx-0.7.40.dev8/src/qubx/connectors/xlighter/websocket.py +0 -503
  46. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/.gitignore +0 -0
  47. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/LICENSE +0 -0
  48. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/README.md +0 -0
  49. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/hatch_build.py +0 -0
  50. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/__init__.py +0 -0
  51. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/_nb_magic.py +0 -0
  52. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/__init__.py +0 -0
  53. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/broker.py +0 -0
  54. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/data.py +0 -0
  55. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/management.py +0 -0
  56. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/ome.py +0 -0
  57. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/optimization.py +0 -0
  58. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/runner.py +0 -0
  59. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/sentinels.py +0 -0
  60. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/simulated_data.py +0 -0
  61. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/simulated_exchange.py +0 -0
  62. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/simulator.py +0 -0
  63. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/transfers.py +0 -0
  64. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/backtester/utils.py +0 -0
  65. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/cli/__init__.py +0 -0
  66. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/cli/commands.py +0 -0
  67. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/cli/deploy.py +0 -0
  68. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/cli/misc.py +0 -0
  69. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/cli/release.py +0 -0
  70. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/cli/tui.py +0 -0
  71. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/__init__.py +0 -0
  72. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  73. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  74. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  75. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  76. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  77. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  78. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  79. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  80. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  81. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  82. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  83. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  84. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  85. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  86. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  87. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  88. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  89. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  90. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  91. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  92. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  93. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  94. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  95. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/reader.py +1 -1
  96. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  97. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  98. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/utils.py +2 -2
  99. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  100. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/connectors/tardis/utils.py +0 -0
  101. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/__init__.py +0 -0
  102. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/account.py +0 -0
  103. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/basics.py +0 -0
  104. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/context.py +0 -0
  105. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/detectors/__init__.py +0 -0
  106. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/detectors/delisting.py +0 -0
  107. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/detectors/stale.py +0 -0
  108. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/errors.py +0 -0
  109. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/exceptions.py +0 -0
  110. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/helpers.py +0 -0
  111. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/initializer.py +0 -0
  112. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/interfaces.py +0 -0
  113. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/loggers.py +0 -0
  114. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/lookups.py +0 -0
  115. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/mixins/__init__.py +0 -0
  116. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/mixins/market.py +0 -0
  117. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/mixins/processing.py +0 -0
  118. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/mixins/subscription.py +0 -0
  119. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/mixins/trading.py +0 -0
  120. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/mixins/universe.py +0 -0
  121. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/mixins/utils.py +0 -0
  122. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/series.pxd +0 -0
  123. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/series.pyi +0 -0
  124. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/series.pyx +0 -0
  125. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/core/utils.pyi +0 -0
  126. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/__init__.py +0 -0
  127. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/composite.py +0 -0
  128. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/containers.py +0 -0
  129. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/helpers.py +0 -0
  130. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/hft.py +0 -0
  131. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/readers.py +0 -0
  132. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/storage.py +0 -0
  133. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/storages/csv.py +0 -0
  134. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/storages/questdb.py +0 -0
  135. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/storages/utils.py +0 -0
  136. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/tardis.py +0 -0
  137. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/data/transformers.py +0 -0
  138. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/emitters/__init__.py +0 -0
  139. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/emitters/base.py +0 -0
  140. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/emitters/composite.py +0 -0
  141. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/emitters/csv.py +0 -0
  142. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/emitters/indicator.py +0 -0
  143. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/emitters/inmemory.py +0 -0
  144. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/emitters/prometheus.py +0 -0
  145. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/emitters/questdb.py +0 -0
  146. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/__init__.py +0 -0
  147. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/composite.py +0 -0
  148. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/formatters/__init__.py +0 -0
  149. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/formatters/base.py +0 -0
  150. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/formatters/incremental.py +0 -0
  151. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/formatters/slack.py +0 -0
  152. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/formatters/target_position.py +0 -0
  153. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/redis_streams.py +0 -0
  154. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/exporters/slack.py +0 -0
  155. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/gathering/simplest.py +0 -0
  156. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/health/__init__.py +0 -0
  157. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/health/base.py +0 -0
  158. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/health/dummy.py +0 -0
  159. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/loggers/__init__.py +0 -0
  160. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/loggers/csv.py +0 -0
  161. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/loggers/factory.py +0 -0
  162. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/loggers/mongo.py +0 -0
  163. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/notifications/__init__.py +0 -0
  164. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/notifications/composite.py +0 -0
  165. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/notifications/slack.py +0 -0
  166. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/notifications/throttler.py +0 -0
  167. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/pandaz/__init__.py +0 -0
  168. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/pandaz/stats.py +0 -0
  169. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/pandaz/ta.py +0 -0
  170. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/pandaz/utils.py +0 -0
  171. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/_build.py +0 -0
  172. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/crypto-fees.ini +0 -0
  173. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  174. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  175. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  176. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  177. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  178. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  179. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  180. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  181. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  182. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  183. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  184. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restarts/__init__.py +0 -0
  185. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restarts/state_resolvers.py +0 -0
  186. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restarts/time_finders.py +0 -0
  187. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restorers/__init__.py +0 -0
  188. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restorers/balance.py +0 -0
  189. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restorers/factory.py +0 -0
  190. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restorers/interfaces.py +0 -0
  191. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restorers/position.py +0 -0
  192. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restorers/signal.py +0 -0
  193. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restorers/state.py +0 -0
  194. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/restorers/utils.py +0 -0
  195. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/state/__init__.py +0 -0
  196. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/state/dummy.py +0 -0
  197. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/state/redis.py +0 -0
  198. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/ta/__init__.py +0 -0
  199. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/ta/indicators.pxd +0 -0
  200. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/ta/indicators.pyi +0 -0
  201. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/ta/indicators.pyx +0 -0
  202. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/__init__.py +0 -0
  203. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/base.py +0 -0
  204. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  205. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/project/config.yml.j2 +0 -0
  206. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  207. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  208. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  209. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  210. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  211. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/project/template.yml +0 -0
  212. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  213. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  214. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/simple/config.yml.j2 +0 -0
  215. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  216. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  217. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  218. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/templates/simple/template.yml +0 -0
  219. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/trackers/__init__.py +0 -0
  220. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/trackers/advanced.py +0 -0
  221. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/trackers/composite.py +0 -0
  222. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/trackers/rebalancers.py +0 -0
  223. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/trackers/riskctrl.py +0 -0
  224. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/__init__.py +0 -0
  225. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/_pyxreloader.py +0 -0
  226. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/charting/lookinglass.py +0 -0
  227. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  228. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/charting/orderbook.py +0 -0
  229. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/collections.py +0 -0
  230. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/hft/__init__.py +0 -0
  231. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/hft/numba_utils.py +0 -0
  232. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/hft/orderbook.pyi +0 -0
  233. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/hft/orderbook.pyx +0 -0
  234. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/marketdata/binance.py +0 -0
  235. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/marketdata/ccxt.py +0 -0
  236. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/marketdata/dukas.py +0 -0
  237. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/misc.py +0 -0
  238. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/nonce.py +0 -0
  239. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/ntp.py +0 -0
  240. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/numbers_utils.py +0 -0
  241. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/orderbook.py +0 -0
  242. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/plotting/__init__.py +0 -0
  243. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/plotting/dashboard.py +0 -0
  244. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/plotting/data.py +0 -0
  245. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/plotting/interfaces.py +0 -0
  246. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  247. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  248. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/questdb.py +0 -0
  249. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/rate_limiter.py +0 -0
  250. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/ringbuffer.pxd +0 -0
  251. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/ringbuffer.pyi +0 -0
  252. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/ringbuffer.pyx +0 -0
  253. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/__init__.py +0 -0
  254. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  255. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/accounts.py +0 -0
  256. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/kernel_service.py +0 -0
  257. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/__init__.py +0 -0
  258. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/app.py +0 -0
  259. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/handlers.py +0 -0
  260. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/init_code.py +0 -0
  261. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/kernel.py +0 -0
  262. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  263. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  264. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  265. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  266. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  267. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  268. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  269. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  270. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/slack.py +0 -0
  271. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/throttler.py +0 -0
  272. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/time.py +0 -0
  273. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/src/qubx/utils/websocket_manager.py +0 -0
  274. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  275. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
  276. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  277. {qubx-0.7.40.dev8 → qubx-0.7.40.dev9}/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.dev8
3
+ Version: 0.7.40.dev9
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
@@ -39,7 +39,6 @@ Requires-Dist: python-binance<2,>=1.0.19
39
39
  Requires-Dist: python-dotenv<2,>=1.0.0
40
40
  Requires-Dist: pyyaml<7,>=6.0.2
41
41
  Requires-Dist: qubx-bitfinex-api<4,>=3.0.7
42
- Requires-Dist: qubx-lighter-api<1,>=0.1.5
43
42
  Requires-Dist: questdb<3,>=2.0.3
44
43
  Requires-Dist: redis<6,>=5.2.1
45
44
  Requires-Dist: rich<14,>=13.9.4
@@ -54,7 +54,6 @@ dependencies = [
54
54
  "textual-serve>=1.0.0,<2",
55
55
  "rich>=13.9.4,<14",
56
56
  "jinja2>=3.1.0,<4",
57
- "qubx-lighter-api>=0.1.5,<1",
58
57
  "uvloop>=0.22.1,<1; sys_platform != 'win32'",
59
58
  "textual[syntax]>=6.0.0,<7",
60
59
  ]
@@ -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.dev8'
32
- __version_tuple__ = version_tuple = (0, 7, 40, 'dev8')
31
+ __version__ = version = '0.7.40.dev9'
32
+ __version_tuple__ = version_tuple = (0, 7, 40, 'dev9')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -42,7 +42,6 @@ class SimulatedAccountProcessor(BasicAccountProcessor):
42
42
  self._channel = channel
43
43
 
44
44
  if restored_state is not None:
45
- # Convert list of AssetBalance to dict for internal storage
46
45
  for balance in restored_state.balances:
47
46
  self._balances[balance.currency] = balance
48
47
  for instrument, position in restored_state.positions.items():
@@ -0,0 +1,26 @@
1
+ """
2
+ Qubx connectors package.
3
+
4
+ This module provides the connector registry and ensures all built-in connectors are registered.
5
+
6
+ Note: Paper trading connectors (SimulatedAccountProcessor, SimulatedBroker) are NOT registered
7
+ with the registry - they are created directly by the backtester and paper trading runner.
8
+ """
9
+
10
+ # Re-export the registry for convenience
11
+ from qubx.connectors.registry import ( # noqa: F401
12
+ ConnectorRegistry,
13
+ account_processor,
14
+ broker,
15
+ data_provider,
16
+ )
17
+
18
+ # Import connector modules to trigger decorator registration
19
+ from qubx.connectors.ccxt.account import CcxtAccountProcessor # noqa: F401
20
+ from qubx.connectors.ccxt.broker import CcxtBroker # noqa: F401
21
+ from qubx.connectors.ccxt.data import CcxtDataProvider # noqa: F401
22
+ from qubx.connectors.tardis.data import TardisDataProvider # noqa: F401
23
+
24
+ # Note: Paper trading connectors (SimulatedAccountProcessor, SimulatedBroker) are
25
+ # NOT registered with the registry - they are created directly by the backtester
26
+ # and paper trading runner.
@@ -2,14 +2,15 @@ import asyncio
2
2
  import concurrent.futures
3
3
  from asyncio.exceptions import CancelledError
4
4
  from collections import defaultdict
5
- from typing import Awaitable, Callable
5
+ from typing import TYPE_CHECKING, Awaitable, Callable
6
6
 
7
+ import ccxt.pro as cxp
7
8
  import numpy as np
8
9
  import pandas as pd
9
-
10
- import ccxt.pro as cxp
11
10
  from ccxt import ExchangeClosedByUser, ExchangeError, ExchangeNotAvailable, NetworkError
11
+
12
12
  from qubx import logger
13
+ from qubx.connectors.registry import account_processor
13
14
  from qubx.core.account import BasicAccountProcessor
14
15
  from qubx.core.basics import (
15
16
  CtrlChannel,
@@ -42,7 +43,11 @@ from .utils import (
42
43
  instrument_to_ccxt_symbol,
43
44
  )
44
45
 
46
+ if TYPE_CHECKING:
47
+ from qubx.utils.runner.accounts import AccountConfigurationManager
48
+
45
49
 
50
+ @account_processor("ccxt")
46
51
  class CcxtAccountProcessor(BasicAccountProcessor):
47
52
  """
48
53
  Subscribes to account information from the exchange.
@@ -70,14 +75,16 @@ class CcxtAccountProcessor(BasicAccountProcessor):
70
75
 
71
76
  def __init__(
72
77
  self,
73
- account_id: str,
74
- exchange_manager: ExchangeManager,
78
+ exchange_name: str,
75
79
  channel: CtrlChannel,
76
80
  time_provider: ITimeProvider,
77
- base_currency: str,
78
- health_monitor: IHealthMonitor,
79
- exchange: str,
81
+ account_manager: "AccountConfigurationManager",
80
82
  tcc: TransactionCostsCalculator,
83
+ health_monitor: IHealthMonitor,
84
+ data_provider=None,
85
+ restored_state: RestoredState | None = None,
86
+ read_only: bool = False,
87
+ loop: asyncio.AbstractEventLoop | None = None,
81
88
  balance_interval: str = "30Sec",
82
89
  position_interval: str = "30Sec",
83
90
  subscription_interval: str = "10Sec",
@@ -86,14 +93,27 @@ class CcxtAccountProcessor(BasicAccountProcessor):
86
93
  max_position_restore_days: int = 5,
87
94
  max_retries: int = 10,
88
95
  connection_timeout: int = 30,
89
- read_only: bool = False,
90
- restored_state: RestoredState | None = None,
96
+ **kwargs,
91
97
  ):
98
+ from qubx.connectors.ccxt.factory import get_ccxt_exchange_manager
99
+
100
+ creds = account_manager.get_exchange_credentials(exchange_name)
101
+
102
+ exchange_manager = get_ccxt_exchange_manager(
103
+ exchange=exchange_name,
104
+ use_testnet=creds.testnet,
105
+ api_key=creds.api_key,
106
+ secret=creds.secret,
107
+ health_monitor=health_monitor,
108
+ time_provider=time_provider,
109
+ loop=loop,
110
+ )
111
+
92
112
  super().__init__(
93
- account_id=account_id,
113
+ account_id=exchange_name,
94
114
  time_provider=time_provider,
95
- base_currency=base_currency,
96
- exchange=exchange,
115
+ base_currency=creds.base_currency,
116
+ exchange=exchange_name,
97
117
  tcc=tcc,
98
118
  health_monitor=health_monitor,
99
119
  initial_capital=0,
@@ -1,12 +1,13 @@
1
1
  import asyncio
2
2
  import traceback
3
- from typing import Any
4
-
5
- import pandas as pd
3
+ from typing import TYPE_CHECKING, Any
6
4
 
7
5
  import ccxt
6
+ import pandas as pd
8
7
  from ccxt.base.errors import ExchangeError
8
+
9
9
  from qubx import logger
10
+ from qubx.connectors.registry import broker
10
11
  from qubx.core.basics import (
11
12
  CtrlChannel,
12
13
  Instrument,
@@ -27,25 +28,46 @@ from qubx.utils.misc import AsyncThreadLoop
27
28
  from .exchange_manager import ExchangeManager
28
29
  from .utils import ccxt_convert_order_info, instrument_to_ccxt_symbol
29
30
 
31
+ if TYPE_CHECKING:
32
+ from qubx.utils.runner.accounts import AccountConfigurationManager
33
+
30
34
 
35
+ @broker("ccxt")
31
36
  class CcxtBroker(IBroker):
32
37
  _exchange_manager: ExchangeManager
33
38
 
34
39
  def __init__(
35
40
  self,
36
- exchange_manager: ExchangeManager,
41
+ exchange_name: str,
37
42
  channel: CtrlChannel,
38
43
  time_provider: ITimeProvider,
39
44
  account: IAccountProcessor,
40
45
  data_provider: IDataProvider,
46
+ account_manager: "AccountConfigurationManager",
47
+ health_monitor,
48
+ loop: asyncio.AbstractEventLoop | None = None,
41
49
  cancel_timeout: int = 30,
42
50
  cancel_retry_interval: int = 2,
43
51
  max_cancel_retries: int = 10,
44
52
  enable_create_order_ws: bool = False,
45
53
  enable_cancel_order_ws: bool = False,
46
54
  enable_edit_order_ws: bool = False,
55
+ enable_mm: bool = False,
56
+ **kwargs,
47
57
  ):
48
- self._exchange_manager = exchange_manager
58
+ from qubx.connectors.ccxt.factory import get_ccxt_exchange_manager
59
+
60
+ creds = account_manager.get_exchange_credentials(exchange_name)
61
+ self._exchange_manager = get_ccxt_exchange_manager(
62
+ exchange=exchange_name,
63
+ use_testnet=creds.testnet,
64
+ api_key=creds.api_key,
65
+ secret=creds.secret,
66
+ time_provider=time_provider,
67
+ enable_mm=enable_mm,
68
+ health_monitor=health_monitor,
69
+ loop=loop,
70
+ )
49
71
  self.ccxt_exchange_id = str(self._exchange_manager.exchange.name)
50
72
  self.channel = channel
51
73
  self.time_provider = time_provider
@@ -15,6 +15,7 @@ from typing import Awaitable, Callable
15
15
  from ccxt import ExchangeClosedByUser, ExchangeError, ExchangeNotAvailable, NetworkError
16
16
  from ccxt.async_support.base.ws.client import Client as _WsClient
17
17
  from ccxt.pro import Exchange
18
+
18
19
  from qubx import logger
19
20
  from qubx.core.basics import CtrlChannel
20
21
  from qubx.utils.misc import AsyncThreadLoop
@@ -1,12 +1,14 @@
1
+ import asyncio
1
2
  import re
2
3
  from collections import defaultdict
3
- from typing import Dict, List, Optional, Set, Tuple
4
+ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
4
5
 
5
6
  import pandas as pd
6
7
 
7
8
  # CCXT exceptions are now handled in ConnectionManager
8
9
  from qubx import logger
9
10
  from qubx.connectors.ccxt.utils import ccxt_convert_timeframe_to_exchange_format
11
+ from qubx.connectors.registry import data_provider
10
12
  from qubx.core.basics import CtrlChannel, DataType, Instrument, ITimeProvider
11
13
  from qubx.core.helpers import BasicScheduler
12
14
  from qubx.core.interfaces import IDataProvider, IHealthMonitor
@@ -22,38 +24,49 @@ from .subscription_manager import SubscriptionManager
22
24
  from .subscription_orchestrator import SubscriptionOrchestrator
23
25
  from .warmup_service import WarmupService
24
26
 
27
+ if TYPE_CHECKING:
28
+ from qubx.utils.runner.accounts import AccountConfigurationManager
25
29
 
30
+
31
+ @data_provider("ccxt")
26
32
  class CcxtDataProvider(IDataProvider):
27
33
  time_provider: ITimeProvider
28
34
  _exchange_manager: ExchangeManager
29
35
  _scheduler: BasicScheduler | None = None
30
-
31
- # Core state - still needed
32
36
  _last_quotes: dict[Instrument, Optional[Quote]]
33
37
  _warmup_timeout: int
34
38
 
35
39
  def __init__(
36
40
  self,
37
- exchange_manager: ExchangeManager,
41
+ exchange_name: str,
38
42
  time_provider: ITimeProvider,
39
43
  channel: CtrlChannel,
44
+ health_monitor: IHealthMonitor,
45
+ account_manager: "AccountConfigurationManager",
46
+ loop: asyncio.AbstractEventLoop | None = None,
40
47
  max_ws_retries: int = 10,
41
48
  warmup_timeout: int = 120,
42
- health_monitor: IHealthMonitor | None = None,
49
+ **kwargs,
43
50
  ):
44
- # Store the exchange manager (always ExchangeManager now)
45
- self._exchange_manager = exchange_manager
51
+ from qubx.connectors.ccxt.factory import get_ccxt_exchange_manager
52
+
53
+ settings = account_manager.get_exchange_settings(exchange_name)
54
+ self._exchange_manager = get_ccxt_exchange_manager(
55
+ exchange=exchange_name,
56
+ use_testnet=settings.testnet,
57
+ health_monitor=health_monitor,
58
+ time_provider=time_provider,
59
+ loop=loop,
60
+ **kwargs,
61
+ )
46
62
 
47
63
  self.time_provider = time_provider
48
64
  self.channel = channel
49
65
  self.max_ws_retries = max_ws_retries
50
66
  self._warmup_timeout = warmup_timeout
51
67
  self._health_monitor = health_monitor
52
-
53
- # Core components - access exchange directly via exchange_manager.exchange
54
68
  self._exchange_id = str(self._exchange_manager.exchange.name)
55
69
 
56
- # Initialize composed components
57
70
  self._subscription_manager = SubscriptionManager()
58
71
  self._connection_manager = ConnectionManager(
59
72
  exchange_id=self._exchange_id,
@@ -67,15 +80,11 @@ class CcxtDataProvider(IDataProvider):
67
80
  connection_manager=self._connection_manager,
68
81
  exchange_manager=self._exchange_manager,
69
82
  )
70
-
71
- # Data type handler factory for clean separation of data processing logic
72
83
  self._data_type_handler_factory = DataTypeHandlerFactory(
73
84
  data_provider=self,
74
85
  exchange_manager=self._exchange_manager,
75
86
  exchange_id=self._exchange_id,
76
87
  )
77
-
78
- # Warmup service for handling historical data warmup
79
88
  self._warmup_service = WarmupService(
80
89
  handler_factory=self._data_type_handler_factory,
81
90
  channel=channel,
@@ -83,14 +92,9 @@ class CcxtDataProvider(IDataProvider):
83
92
  exchange_manager=self._exchange_manager,
84
93
  warmup_timeout=warmup_timeout,
85
94
  )
86
-
87
- # Quote caching for synthetic quote generation
88
95
  self._last_quotes = defaultdict(lambda: None)
89
96
 
90
- # Start ExchangeManager monitoring
91
97
  self._exchange_manager.start_monitoring()
92
-
93
- # Register recreation callback for automatic resubscription
94
98
  self._exchange_manager.register_recreation_callback(self._handle_exchange_recreation)
95
99
 
96
100
  logger.info(f"<yellow>{self._exchange_id}</yellow> Initialized")
@@ -10,6 +10,7 @@ import time
10
10
  from typing import Any, Callable, Optional
11
11
 
12
12
  import ccxt.pro as cxp
13
+
13
14
  from qubx import logger
14
15
  from qubx.core.interfaces import IHealthMonitor, ITimeProvider
15
16
 
@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional
4
4
  import ccxt.pro as cxp
5
5
  from ccxt.async_support.base.ws.client import Client
6
6
  from ccxt.base.errors import ExchangeError, InvalidOrder
7
+
7
8
  from qubx import logger
8
9
 
9
10
  from ...adapters.polling_adapter import PollingConfig, PollingToWebSocketAdapter
@@ -3,6 +3,7 @@ from threading import Thread
3
3
  from typing import Any
4
4
 
5
5
  import ccxt.pro as cxp
6
+
6
7
  from qubx.connectors.ccxt.broker import CcxtBroker
7
8
  from qubx.core.basics import CtrlChannel
8
9
  from qubx.core.interfaces import IAccountProcessor, IBroker, IDataProvider, IHealthMonitor, ITimeProvider
@@ -79,6 +80,9 @@ def get_ccxt_exchange(
79
80
  return ccxt_exchange
80
81
 
81
82
 
83
+ _exchange_manager_cache: dict[tuple, ExchangeManager] = {}
84
+
85
+
82
86
  def get_ccxt_exchange_manager(
83
87
  exchange: str,
84
88
  health_monitor: IHealthMonitor,
@@ -94,37 +98,28 @@ def get_ccxt_exchange_manager(
94
98
  Get a CCXT exchange with automatic stability management.
95
99
 
96
100
  Returns ExchangeManager wrapper that handles exchange recreation
97
- during data stall scenarios via self-monitoring.
101
+ during data stall scenarios via self-monitoring. Instances are cached
102
+ by (exchange, api_key, secret, use_testnet) to enable sharing across
103
+ data provider, account processor, and broker.
104
+ """
105
+ cache_key = (exchange.lower(), api_key, secret, use_testnet, check_interval_seconds)
98
106
 
99
- Parameters:
100
- exchange (str): The exchange name.
101
- api_key (str, optional): The API key. Default is None.
102
- secret (str, optional): The API secret. Default is None.
103
- loop (asyncio.AbstractEventLoop, optional): Event loop. Default is None.
104
- use_testnet (bool): Use testnet/sandbox mode. Default is False.
105
- check_interval_seconds (float): How often to check for stalls. Default is 30.0.
106
- **kwargs: Additional parameters for exchange configuration.
107
+ if cache_key in _exchange_manager_cache:
108
+ return _exchange_manager_cache[cache_key]
107
109
 
108
- Returns:
109
- ExchangeManager wrapping the CCXT Exchange
110
- """
111
- # Prepare factory parameters for ExchangeManager recreation
112
110
  factory_params = {
113
111
  "exchange": exchange,
114
112
  "api_key": api_key,
115
113
  "secret": secret,
116
- "loop": loop,
114
+ "loop": None,
117
115
  "use_testnet": use_testnet,
118
- **{k: v for k, v in kwargs.items() if k != "check_interval_seconds"},
119
116
  }
120
117
 
121
- # Create raw CCXT exchange using public factory method
122
118
  ccxt_exchange = get_ccxt_exchange(
123
- exchange=exchange, api_key=api_key, secret=secret, loop=loop, use_testnet=use_testnet, **kwargs
119
+ exchange=exchange, api_key=api_key, secret=secret, loop=None, use_testnet=use_testnet
124
120
  )
125
121
 
126
- # Wrap in ExchangeManager for stability management
127
- return ExchangeManager(
122
+ manager = ExchangeManager(
128
123
  exchange_name=exchange,
129
124
  factory_params=factory_params,
130
125
  health_monitor=health_monitor,
@@ -133,6 +128,14 @@ def get_ccxt_exchange_manager(
133
128
  check_interval_seconds=check_interval_seconds,
134
129
  )
135
130
 
131
+ _exchange_manager_cache[cache_key] = manager
132
+ return manager
133
+
134
+
135
+ def clear_exchange_manager_cache() -> None:
136
+ """Clear the exchange manager cache. Useful for testing."""
137
+ _exchange_manager_cache.clear()
138
+
136
139
 
137
140
  def get_ccxt_broker(
138
141
  exchange_name: str,
@@ -10,6 +10,7 @@ import uuid
10
10
  from typing import Awaitable, Callable
11
11
 
12
12
  from ccxt.pro import Exchange
13
+
13
14
  from qubx import logger
14
15
  from qubx.core.basics import CtrlChannel, DataType, Instrument
15
16
  from qubx.utils.misc import AsyncThreadLoop
@@ -0,0 +1,224 @@
1
+ """
2
+ Registry for data providers, account processors, and brokers.
3
+
4
+ This module provides a registry pattern for connectors, allowing plugins
5
+ to register custom connectors using decorators (like readers and storages).
6
+ """
7
+
8
+ from typing import Any, Callable, Type, TypeVar
9
+
10
+ from qubx import logger
11
+ from qubx.core.interfaces import IAccountProcessor, IBroker, IDataProvider
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ class ConnectorRegistry:
17
+ """
18
+ Registry for connector classes (data providers, account processors, brokers).
19
+
20
+ This registry allows plugins to register their own connector implementations
21
+ using decorators, making them available for use in strategy configurations.
22
+
23
+ Classes are registered and instantiated with standardized constructor arguments.
24
+ """
25
+
26
+ _data_providers: dict[str, Type[IDataProvider]] = {}
27
+ _account_processors: dict[str, Type[IAccountProcessor]] = {}
28
+ _brokers: dict[str, Type[IBroker]] = {}
29
+
30
+ @classmethod
31
+ def register_data_provider(cls, name: str) -> Callable[[Type[T]], Type[T]]:
32
+ """
33
+ Decorator to register a data provider class.
34
+
35
+ Args:
36
+ name: The name to register the data provider under (e.g., "ccxt", "tardis")
37
+
38
+ Returns:
39
+ A decorator function that registers the class
40
+ """
41
+
42
+ def decorator(provider_cls: Type[T]) -> Type[T]:
43
+ cls._data_providers[name.lower()] = provider_cls # type: ignore
44
+ logger.debug(f"Registered data provider: {name}")
45
+ return provider_cls
46
+
47
+ return decorator
48
+
49
+ @classmethod
50
+ def register_account_processor(cls, name: str) -> Callable[[Type[T]], Type[T]]:
51
+ """
52
+ Decorator to register an account processor class.
53
+
54
+ Args:
55
+ name: The name to register the account processor under
56
+
57
+ Returns:
58
+ A decorator function that registers the class
59
+ """
60
+
61
+ def decorator(processor_cls: Type[T]) -> Type[T]:
62
+ cls._account_processors[name.lower()] = processor_cls # type: ignore
63
+ logger.debug(f"Registered account processor: {name}")
64
+ return processor_cls
65
+
66
+ return decorator
67
+
68
+ @classmethod
69
+ def register_broker(cls, name: str) -> Callable[[Type[T]], Type[T]]:
70
+ """
71
+ Decorator to register a broker class.
72
+
73
+ Args:
74
+ name: The name to register the broker under
75
+
76
+ Returns:
77
+ A decorator function that registers the class
78
+ """
79
+
80
+ def decorator(broker_cls: Type[T]) -> Type[T]:
81
+ cls._brokers[name.lower()] = broker_cls # type: ignore
82
+ logger.debug(f"Registered broker: {name}")
83
+ return broker_cls
84
+
85
+ return decorator
86
+
87
+ @classmethod
88
+ def get_data_provider(cls, name: str, **kwargs: Any) -> IDataProvider:
89
+ """
90
+ Get a data provider instance by name.
91
+
92
+ Args:
93
+ name: The name of the data provider
94
+ **kwargs: Arguments to pass to the constructor
95
+
96
+ Returns:
97
+ An instance of the data provider
98
+
99
+ Raises:
100
+ ValueError: If the data provider is not found
101
+ """
102
+ provider_cls = cls._data_providers.get(name.lower())
103
+ if provider_cls is None:
104
+ raise ValueError(
105
+ f"Data provider '{name}' is not registered. "
106
+ f"Available: {list(cls._data_providers.keys())}"
107
+ )
108
+ return provider_cls(**kwargs)
109
+
110
+ @classmethod
111
+ def get_account_processor(cls, name: str, **kwargs: Any) -> IAccountProcessor:
112
+ """
113
+ Get an account processor instance by name.
114
+
115
+ Args:
116
+ name: The name of the account processor
117
+ **kwargs: Arguments to pass to the constructor
118
+
119
+ Returns:
120
+ An instance of the account processor
121
+
122
+ Raises:
123
+ ValueError: If the account processor is not found
124
+ """
125
+ processor_cls = cls._account_processors.get(name.lower())
126
+ if processor_cls is None:
127
+ raise ValueError(
128
+ f"Account processor '{name}' is not registered. "
129
+ f"Available: {list(cls._account_processors.keys())}"
130
+ )
131
+ return processor_cls(**kwargs)
132
+
133
+ @classmethod
134
+ def get_broker(cls, name: str, **kwargs: Any) -> IBroker:
135
+ """
136
+ Get a broker instance by name.
137
+
138
+ Args:
139
+ name: The name of the broker
140
+ **kwargs: Arguments to pass to the constructor
141
+
142
+ Returns:
143
+ An instance of the broker
144
+
145
+ Raises:
146
+ ValueError: If the broker is not found
147
+ """
148
+ broker_cls = cls._brokers.get(name.lower())
149
+ if broker_cls is None:
150
+ raise ValueError(
151
+ f"Broker '{name}' is not registered. "
152
+ f"Available: {list(cls._brokers.keys())}"
153
+ )
154
+ return broker_cls(**kwargs)
155
+
156
+ @classmethod
157
+ def is_data_provider_registered(cls, name: str) -> bool:
158
+ """Check if a data provider is registered."""
159
+ return name.lower() in cls._data_providers
160
+
161
+ @classmethod
162
+ def is_account_processor_registered(cls, name: str) -> bool:
163
+ """Check if an account processor is registered."""
164
+ return name.lower() in cls._account_processors
165
+
166
+ @classmethod
167
+ def is_broker_registered(cls, name: str) -> bool:
168
+ """Check if a broker is registered."""
169
+ return name.lower() in cls._brokers
170
+
171
+ @classmethod
172
+ def get_all_data_providers(cls) -> dict[str, Type[IDataProvider]]:
173
+ """Get all registered data provider classes."""
174
+ return cls._data_providers.copy()
175
+
176
+ @classmethod
177
+ def get_all_account_processors(cls) -> dict[str, Type[IAccountProcessor]]:
178
+ """Get all registered account processor classes."""
179
+ return cls._account_processors.copy()
180
+
181
+ @classmethod
182
+ def get_all_brokers(cls) -> dict[str, Type[IBroker]]:
183
+ """Get all registered broker classes."""
184
+ return cls._brokers.copy()
185
+
186
+
187
+ # Convenience decorators
188
+ def data_provider(name: str) -> Callable[[Type[T]], Type[T]]:
189
+ """
190
+ Decorator for registering a data provider class.
191
+
192
+ Usage:
193
+ @data_provider("my_exchange")
194
+ class MyExchangeDataProvider(IDataProvider):
195
+ def __init__(self, exchange_name, time_provider, channel, ...):
196
+ ...
197
+ """
198
+ return ConnectorRegistry.register_data_provider(name)
199
+
200
+
201
+ def account_processor(name: str) -> Callable[[Type[T]], Type[T]]:
202
+ """
203
+ Decorator for registering an account processor class.
204
+
205
+ Usage:
206
+ @account_processor("my_exchange")
207
+ class MyExchangeAccountProcessor(IAccountProcessor):
208
+ def __init__(self, exchange_name, channel, time_provider, ...):
209
+ ...
210
+ """
211
+ return ConnectorRegistry.register_account_processor(name)
212
+
213
+
214
+ def broker(name: str) -> Callable[[Type[T]], Type[T]]:
215
+ """
216
+ Decorator for registering a broker class.
217
+
218
+ Usage:
219
+ @broker("my_exchange")
220
+ class MyExchangeBroker(IBroker):
221
+ def __init__(self, exchange_name, channel, time_provider, account, ...):
222
+ ...
223
+ """
224
+ return ConnectorRegistry.register_broker(name)