Qubx 0.6.76__tar.gz → 0.6.78__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 (215) hide show
  1. {qubx-0.6.76 → qubx-0.6.78}/PKG-INFO +1 -1
  2. {qubx-0.6.76 → qubx-0.6.78}/pyproject.toml +1 -1
  3. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/cli/release.py +23 -6
  4. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/account.py +18 -17
  5. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/subscription_manager.py +51 -30
  6. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/account.py +17 -0
  7. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/context.py +15 -5
  8. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/interfaces.py +50 -10
  9. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/loggers.py +2 -0
  10. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/mixins/processing.py +55 -5
  11. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/mixins/trading.py +20 -14
  12. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/mixins/universe.py +13 -2
  13. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/emitters/indicator.py +8 -3
  14. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/emitters/questdb.py +1 -1
  15. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restarts/state_resolvers.py +4 -4
  16. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restorers/signal.py +2 -2
  17. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/runner/_jupyter_runner.pyt +9 -9
  18. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/runner/runner.py +2 -1
  19. {qubx-0.6.76 → qubx-0.6.78}/LICENSE +0 -0
  20. {qubx-0.6.76 → qubx-0.6.78}/README.md +0 -0
  21. {qubx-0.6.76 → qubx-0.6.78}/build.py +0 -0
  22. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/__init__.py +0 -0
  23. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/_nb_magic.py +0 -0
  24. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/__init__.py +0 -0
  25. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/account.py +0 -0
  26. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/broker.py +0 -0
  27. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/data.py +0 -0
  28. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/management.py +0 -0
  29. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/ome.py +0 -0
  30. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/optimization.py +0 -0
  31. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/runner.py +0 -0
  32. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/sentinels.py +0 -0
  33. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/simulated_data.py +0 -0
  34. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/simulated_exchange.py +0 -0
  35. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/simulator.py +0 -0
  36. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/backtester/utils.py +0 -0
  37. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/cli/__init__.py +0 -0
  38. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/cli/commands.py +0 -0
  39. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/cli/deploy.py +0 -0
  40. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/cli/misc.py +0 -0
  41. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/cli/tui.py +0 -0
  42. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/__init__.py +0 -0
  43. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  44. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  45. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/broker.py +0 -0
  46. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  47. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/data.py +0 -0
  48. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  49. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  50. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  51. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  52. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  53. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  54. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  55. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  56. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  57. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  58. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  59. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  60. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/factory.py +0 -0
  61. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  62. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  63. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  64. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  65. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  66. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  67. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  68. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  69. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  70. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  71. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/reader.py +0 -0
  72. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  73. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  74. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/utils.py +0 -0
  75. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  76. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/tardis/data.py +0 -0
  77. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/connectors/tardis/utils.py +0 -0
  78. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/__init__.py +0 -0
  79. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/basics.py +0 -0
  80. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/deque.py +0 -0
  81. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/errors.py +0 -0
  82. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/exceptions.py +0 -0
  83. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/helpers.py +0 -0
  84. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/initializer.py +0 -0
  85. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/lookups.py +0 -0
  86. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/metrics.py +0 -0
  87. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/mixins/__init__.py +0 -0
  88. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/mixins/market.py +0 -0
  89. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/mixins/subscription.py +0 -0
  90. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/mixins/utils.py +0 -0
  91. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/series.pxd +0 -0
  92. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/series.pyi +0 -0
  93. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/series.pyx +0 -0
  94. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/stale_data_detector.py +0 -0
  95. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/utils.pyi +0 -0
  96. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/core/utils.pyx +0 -0
  97. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/data/__init__.py +0 -0
  98. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/data/composite.py +0 -0
  99. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/data/helpers.py +0 -0
  100. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/data/hft.py +0 -0
  101. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/data/readers.py +0 -0
  102. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/data/registry.py +0 -0
  103. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/data/tardis.py +0 -0
  104. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/emitters/__init__.py +0 -0
  105. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/emitters/base.py +0 -0
  106. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/emitters/composite.py +0 -0
  107. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/emitters/csv.py +0 -0
  108. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/emitters/inmemory.py +0 -0
  109. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/emitters/prometheus.py +0 -0
  110. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/exporters/__init__.py +0 -0
  111. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/exporters/composite.py +0 -0
  112. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/exporters/formatters/__init__.py +0 -0
  113. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/exporters/formatters/base.py +0 -0
  114. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/exporters/formatters/incremental.py +0 -0
  115. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/exporters/formatters/slack.py +0 -0
  116. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/exporters/redis_streams.py +0 -0
  117. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/exporters/slack.py +0 -0
  118. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/features/__init__.py +0 -0
  119. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/features/core.py +0 -0
  120. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/features/orderbook.py +0 -0
  121. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/features/price.py +0 -0
  122. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/features/trades.py +0 -0
  123. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/features/utils.py +0 -0
  124. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/gathering/simplest.py +0 -0
  125. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/health/__init__.py +0 -0
  126. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/health/base.py +0 -0
  127. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/loggers/__init__.py +0 -0
  128. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/loggers/csv.py +0 -0
  129. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/loggers/factory.py +0 -0
  130. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/loggers/inmemory.py +0 -0
  131. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/loggers/mongo.py +0 -0
  132. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/math/__init__.py +0 -0
  133. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/math/stats.py +0 -0
  134. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/notifications/__init__.py +0 -0
  135. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/notifications/composite.py +0 -0
  136. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/notifications/slack.py +0 -0
  137. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/notifications/throttler.py +0 -0
  138. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/pandaz/__init__.py +0 -0
  139. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/pandaz/ta.py +0 -0
  140. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/pandaz/utils.py +0 -0
  141. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/_build.py +0 -0
  142. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/crypto-fees.ini +0 -0
  143. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  144. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  145. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  146. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  147. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  148. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  149. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  150. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  151. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  152. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  153. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  154. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restarts/__init__.py +0 -0
  155. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restarts/time_finders.py +0 -0
  156. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restorers/__init__.py +0 -0
  157. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restorers/balance.py +0 -0
  158. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restorers/factory.py +0 -0
  159. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restorers/interfaces.py +0 -0
  160. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restorers/position.py +0 -0
  161. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restorers/state.py +0 -0
  162. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/restorers/utils.py +0 -0
  163. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/ta/__init__.py +0 -0
  164. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/ta/indicators.pxd +0 -0
  165. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/ta/indicators.pyi +0 -0
  166. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/ta/indicators.pyx +0 -0
  167. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/__init__.py +0 -0
  168. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/base.py +0 -0
  169. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  170. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/project/config.yml.j2 +0 -0
  171. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  172. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  173. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  174. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  175. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  176. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/project/template.yml +0 -0
  177. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  178. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  179. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/simple/config.yml.j2 +0 -0
  180. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  181. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  182. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  183. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/templates/simple/template.yml +0 -0
  184. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/trackers/__init__.py +0 -0
  185. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/trackers/advanced.py +0 -0
  186. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/trackers/composite.py +0 -0
  187. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/trackers/rebalancers.py +0 -0
  188. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/trackers/riskctrl.py +0 -0
  189. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/trackers/sizers.py +0 -0
  190. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/__init__.py +0 -0
  191. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/_pyxreloader.py +0 -0
  192. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/charting/lookinglass.py +0 -0
  193. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  194. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/charting/orderbook.py +0 -0
  195. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/collections.py +0 -0
  196. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/marketdata/binance.py +0 -0
  197. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/marketdata/ccxt.py +0 -0
  198. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/marketdata/dukas.py +0 -0
  199. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/misc.py +0 -0
  200. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/ntp.py +0 -0
  201. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/numbers_utils.py +0 -0
  202. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/orderbook.py +0 -0
  203. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/plotting/__init__.py +0 -0
  204. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/plotting/dashboard.py +0 -0
  205. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/plotting/data.py +0 -0
  206. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/plotting/interfaces.py +0 -0
  207. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  208. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  209. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/questdb.py +0 -0
  210. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/runner/__init__.py +0 -0
  211. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/runner/accounts.py +0 -0
  212. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/runner/configs.py +0 -0
  213. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/runner/factory.py +0 -0
  214. {qubx-0.6.76 → qubx-0.6.78}/src/qubx/utils/time.py +0 -0
  215. {qubx-0.6.76 → qubx-0.6.78}/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.76
3
+ Version: 0.6.78
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.76"
7
+ version = "0.6.78"
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"
@@ -84,9 +84,12 @@ def resolve_relative_import(relative_module: str, file_path: str, project_root:
84
84
  # Get the directory containing the file (remove filename)
85
85
  file_dir = os.path.dirname(rel_file_path)
86
86
 
87
- # Convert file directory path to module path
87
+ # Convert file directory path to module path
88
88
  if file_dir:
89
89
  current_module_parts = file_dir.replace(os.sep, ".").split(".")
90
+ # Remove 'src' prefix if present (common Python project structure)
91
+ if current_module_parts[0] == "src" and len(current_module_parts) > 1:
92
+ current_module_parts = current_module_parts[1:]
90
93
  else:
91
94
  current_module_parts = []
92
95
 
@@ -684,8 +687,8 @@ def _copy_dependencies(strategy_path: str, pyproject_root: str, release_dir: str
684
687
  if _src_root is None:
685
688
  raise DependencyResolutionError(f"Could not find the source root for {_src_dir} in {pyproject_root}")
686
689
 
687
- # Now call _get_imports with the correct source root directory
688
- _imports = _get_imports(strategy_path, _src_root, [_src_dir])
690
+ # Now call _get_imports with the correct source root directory and pyproject_root for relative imports
691
+ _imports = _get_imports(strategy_path, _src_root, [_src_dir], pyproject_root)
689
692
 
690
693
  # Validate all dependencies before copying
691
694
  valid_imports, missing_dependencies = _validate_dependencies(_imports, _src_root, _src_dir)
@@ -920,7 +923,7 @@ def _create_zip_archive(output_dir: str, release_dir: str, tag: str) -> None:
920
923
  shutil.rmtree(release_dir)
921
924
 
922
925
 
923
- def _get_imports(file_name: str, current_directory: str, what_to_look: list[str]) -> list[Import]:
926
+ def _get_imports(file_name: str, current_directory: str, what_to_look: list[str], pyproject_root: str | None = None, visited: set[str] | None = None) -> list[Import]:
924
927
  """
925
928
  Recursively get all imports from a file and its dependencies.
926
929
 
@@ -928,6 +931,8 @@ def _get_imports(file_name: str, current_directory: str, what_to_look: list[str]
928
931
  file_name: Path to the Python file to analyze
929
932
  current_directory: Root directory for resolving imports
930
933
  what_to_look: List of module prefixes to filter for
934
+ pyproject_root: Root directory of the project for resolving relative imports
935
+ visited: Set of already visited files to prevent infinite recursion
931
936
 
932
937
  Returns:
933
938
  List of Import objects for all discovered dependencies
@@ -935,8 +940,20 @@ def _get_imports(file_name: str, current_directory: str, what_to_look: list[str]
935
940
  Raises:
936
941
  DependencyResolutionError: If a required dependency cannot be found or processed
937
942
  """
943
+ # Initialize visited set if not provided
944
+ if visited is None:
945
+ visited = set()
946
+
947
+ # Skip if already visited to prevent infinite recursion
948
+ if file_name in visited:
949
+ return []
950
+ visited.add(file_name)
951
+
952
+ # Use pyproject_root if provided, otherwise use current_directory as fallback
953
+ project_root_for_resolution = pyproject_root or current_directory
954
+
938
955
  try:
939
- imports = list(get_imports(file_name, what_to_look, project_root=current_directory))
956
+ imports = list(get_imports(file_name, what_to_look, project_root=project_root_for_resolution))
940
957
  except (SyntaxError, FileNotFoundError) as e:
941
958
  raise DependencyResolutionError(f"Failed to parse imports from {file_name}: {e}")
942
959
 
@@ -959,7 +976,7 @@ def _get_imports(file_name: str, current_directory: str, what_to_look: list[str]
959
976
  if dependency_file:
960
977
  # Recursively process the dependency
961
978
  try:
962
- imports.extend(_get_imports(dependency_file, current_directory, what_to_look))
979
+ imports.extend(_get_imports(dependency_file, current_directory, what_to_look, pyproject_root, visited))
963
980
  except DependencyResolutionError as e:
964
981
  # Log nested dependency errors but continue processing
965
982
  logger.warning(f"Failed to resolve nested dependency: {e}")
@@ -28,6 +28,7 @@ from qubx.utils.marketdata.ccxt import ccxt_symbol_to_instrument
28
28
  from qubx.utils.misc import AsyncThreadLoop
29
29
 
30
30
  from .exceptions import CcxtSymbolNotRecognized
31
+ from .exchange_manager import ExchangeManager
31
32
  from .utils import (
32
33
  ccxt_convert_balance,
33
34
  ccxt_convert_deal_info,
@@ -46,7 +47,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
46
47
  Subscribes to account information from the exchange.
47
48
  """
48
49
 
49
- exchange: cxp.Exchange
50
+ exchange_manager: ExchangeManager
50
51
  channel: CtrlChannel
51
52
  base_currency: str
52
53
  balance_interval: str
@@ -69,7 +70,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
69
70
  def __init__(
70
71
  self,
71
72
  account_id: str,
72
- exchange: cxp.Exchange,
73
+ exchange_manager: ExchangeManager,
73
74
  channel: CtrlChannel,
74
75
  time_provider: ITimeProvider,
75
76
  base_currency: str,
@@ -90,7 +91,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
90
91
  tcc=tcc,
91
92
  initial_capital=0,
92
93
  )
93
- self.exchange = exchange
94
+ self.exchange_manager = exchange_manager
94
95
  self.channel = channel
95
96
  self.max_retries = max_retries
96
97
  self.balance_interval = balance_interval
@@ -99,7 +100,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
99
100
  self.open_order_interval = open_order_interval
100
101
  self.open_order_backoff = open_order_backoff
101
102
  self.max_position_restore_days = max_position_restore_days
102
- self._loop = AsyncThreadLoop(exchange.asyncio_loop)
103
+ self._loop = AsyncThreadLoop(exchange_manager.exchange.asyncio_loop)
103
104
  self._is_running = False
104
105
  self._polling_tasks = {}
105
106
  self._polling_to_init = defaultdict(bool)
@@ -125,7 +126,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
125
126
 
126
127
  self._is_running = True
127
128
 
128
- if not self.exchange.isSandboxModeEnabled:
129
+ if not self.exchange_manager.exchange.isSandboxModeEnabled:
129
130
  # - start polling tasks
130
131
  self._polling_tasks["balance"] = self._loop.submit(
131
132
  self._poller("balance", self._update_balance, self.balance_interval)
@@ -178,8 +179,8 @@ class CcxtAccountProcessor(BasicAccountProcessor):
178
179
 
179
180
  def _get_instrument_for_currency(self, currency: str) -> Instrument:
180
181
  symbol = f"{currency}/{self.base_currency}"
181
- market = self.exchange.market(symbol)
182
- exchange_name = self.exchange.name
182
+ market = self.exchange_manager.exchange.market(symbol)
183
+ exchange_name = self.exchange_manager.exchange.name
183
184
  assert exchange_name is not None
184
185
  return ccxt_symbol_to_instrument(exchange_name, market)
185
186
 
@@ -267,7 +268,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
267
268
 
268
269
  async def _update_balance(self) -> None:
269
270
  """Fetch and update balances from exchange"""
270
- balances_raw = await self.exchange.fetch_balance()
271
+ balances_raw = await self.exchange_manager.exchange.fetch_balance()
271
272
  balances = ccxt_convert_balance(balances_raw)
272
273
  current_balances = self.get_balances()
273
274
 
@@ -292,8 +293,8 @@ class CcxtAccountProcessor(BasicAccountProcessor):
292
293
 
293
294
  async def _update_positions(self) -> None:
294
295
  # fetch and update positions from exchange
295
- ccxt_positions = await self.exchange.fetch_positions()
296
- positions = ccxt_convert_positions(ccxt_positions, self.exchange.name, self.exchange.markets) # type: ignore
296
+ ccxt_positions = await self.exchange_manager.exchange.fetch_positions()
297
+ positions = ccxt_convert_positions(ccxt_positions, self.exchange_manager.exchange.name, self.exchange_manager.exchange.markets) # type: ignore
297
298
  # update required instruments that we need to subscribe to
298
299
  self._required_instruments.update([p.instrument for p in positions])
299
300
  # update positions
@@ -358,7 +359,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
358
359
  if _fetch_instruments:
359
360
  logger.debug(f"Fetching missing tickers for {_fetch_instruments}")
360
361
  _fetch_symbols = [instrument_to_ccxt_symbol(instr) for instr in _fetch_instruments]
361
- tickers: dict[str, dict] = await self.exchange.fetch_tickers(_fetch_symbols)
362
+ tickers: dict[str, dict] = await self.exchange_manager.exchange.fetch_tickers(_fetch_symbols)
362
363
  for symbol, ticker in tickers.items():
363
364
  instr = _symbol_to_instrument.get(symbol)
364
365
  if instr is not None:
@@ -457,7 +458,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
457
458
 
458
459
  async def _cancel_order(order: Order) -> None:
459
460
  try:
460
- await self.exchange.cancel_order(order.id, symbol=instrument_to_ccxt_symbol(order.instrument))
461
+ await self.exchange_manager.exchange.cancel_order(order.id, symbol=instrument_to_ccxt_symbol(order.instrument))
461
462
  logger.debug(
462
463
  f" :: [SYNC] Canceled {order.id} {order.instrument.symbol} {order.side} {order.quantity} @ {order.price} ({order.status})"
463
464
  )
@@ -475,7 +476,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
475
476
  ) -> dict[str, Order]:
476
477
  _start_ms = self._get_start_time_in_ms(days_before) if limit is None else None
477
478
  _ccxt_symbol = instrument_to_ccxt_symbol(instrument)
478
- _fetcher = self.exchange.fetch_open_orders if is_open else self.exchange.fetch_orders
479
+ _fetcher = self.exchange_manager.exchange.fetch_open_orders if is_open else self.exchange_manager.exchange.fetch_orders
479
480
  _raw_orders = await _fetcher(_ccxt_symbol, since=_start_ms, limit=limit)
480
481
  _orders = [ccxt_convert_order_info(instrument, o) for o in _raw_orders]
481
482
  _id_to_order = {o.id: o for o in _orders}
@@ -484,7 +485,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
484
485
  async def _fetch_deals(self, instrument: Instrument, days_before: int = 30) -> list[Deal]:
485
486
  _start_ms = self._get_start_time_in_ms(days_before)
486
487
  _ccxt_symbol = instrument_to_ccxt_symbol(instrument)
487
- deals_data = await self.exchange.fetch_my_trades(_ccxt_symbol, since=_start_ms)
488
+ deals_data = await self.exchange_manager.exchange.fetch_my_trades(_ccxt_symbol, since=_start_ms)
488
489
  deals: list[Deal] = [ccxt_convert_deal_info(o) for o in deals_data]
489
490
  return sorted(deals, key=lambda x: x.time) if deals else []
490
491
 
@@ -530,9 +531,9 @@ class CcxtAccountProcessor(BasicAccountProcessor):
530
531
  _symbol_to_instrument = {}
531
532
 
532
533
  async def _watch_executions():
533
- exec = await self.exchange.watch_orders()
534
+ exec = await self.exchange_manager.exchange.watch_orders()
534
535
  for report in exec:
535
- instrument = ccxt_find_instrument(report["symbol"], self.exchange, _symbol_to_instrument)
536
+ instrument = ccxt_find_instrument(report["symbol"], self.exchange_manager.exchange, _symbol_to_instrument)
536
537
  order = ccxt_convert_order_info(instrument, report)
537
538
  deals = ccxt_extract_deals_from_exec(report)
538
539
  channel.send((instrument, "order", order, False))
@@ -541,7 +542,7 @@ class CcxtAccountProcessor(BasicAccountProcessor):
541
542
 
542
543
  await self._listen_to_stream(
543
544
  subscriber=_watch_executions,
544
- exchange=self.exchange,
545
+ exchange=self.exchange_manager.exchange,
545
546
  channel=channel,
546
547
  name=name,
547
548
  )
@@ -8,7 +8,7 @@ separating subscription concerns from connection management and data handling.
8
8
  from collections import defaultdict
9
9
  from typing import Dict, List, Set
10
10
 
11
- from qubx.core.basics import Instrument
11
+ from qubx.core.basics import DataType, Instrument
12
12
 
13
13
 
14
14
  class SubscriptionManager:
@@ -37,7 +37,7 @@ class SubscriptionManager:
37
37
 
38
38
  # Symbol to instrument mapping for quick lookups
39
39
  self._symbol_to_instrument: dict[str, Instrument] = {}
40
-
40
+
41
41
  # Individual stream mappings: {subscription_type: {instrument: stream_name}}
42
42
  self._individual_streams: dict[str, dict[Instrument, str]] = defaultdict(dict)
43
43
 
@@ -125,7 +125,7 @@ class SubscriptionManager:
125
125
 
126
126
  # Clean up name mapping
127
127
  self._sub_to_name.pop(subscription_type, None)
128
-
128
+
129
129
  # Clean up individual stream mappings
130
130
  self._individual_streams.pop(subscription_type, None)
131
131
 
@@ -163,15 +163,21 @@ class SubscriptionManager:
163
163
  """
164
164
  if instrument is not None:
165
165
  # Return subscriptions (both active and pending) that contain this instrument
166
- active = [sub for sub, instrs in self._subscriptions.items()
167
- if instrument in instrs and self._sub_connection_ready.get(sub, False)]
166
+ active = [
167
+ sub
168
+ for sub, instrs in self._subscriptions.items()
169
+ if instrument in instrs and self._sub_connection_ready.get(sub, False)
170
+ ]
168
171
  pending = [sub for sub, instrs in self._pending_subscriptions.items() if instrument in instrs]
169
172
  return list(set(active + pending))
170
173
 
171
174
  # Return all subscription types that have any instruments (both active and pending)
172
175
  # Only include active subscriptions if connection is ready
173
- active = [sub for sub, instruments in self._subscriptions.items()
174
- if instruments and self._sub_connection_ready.get(sub, False)]
176
+ active = [
177
+ sub
178
+ for sub, instruments in self._subscriptions.items()
179
+ if instruments and self._sub_connection_ready.get(sub, False)
180
+ ]
175
181
  pending = [sub for sub, instruments in self._pending_subscriptions.items() if instruments]
176
182
  return list(set(active + pending))
177
183
 
@@ -211,17 +217,24 @@ class SubscriptionManager:
211
217
 
212
218
  Args:
213
219
  instrument: Instrument to check
214
- subscription_type: Full subscription type (e.g., "ohlc(1m)")
220
+ subscription_type: Base or full subscription type (e.g., "orderbook" or "orderbook(0.0, 20)")
215
221
 
216
222
  Returns:
217
223
  True if subscription is active (not just pending)
218
224
  """
219
- # Only return True if subscription is actually active (not just pending)
220
- return (
221
- subscription_type in self._subscriptions
222
- and instrument in self._subscriptions[subscription_type]
223
- and self._sub_connection_ready.get(subscription_type, False)
224
- )
225
+ # Get the base type for comparison
226
+ base_type = DataType.from_str(subscription_type)[0]
227
+
228
+ # Check if any subscription with matching base type contains the instrument and is ready
229
+ for stored_sub_type, instruments in self._subscriptions.items():
230
+ if (
231
+ DataType.from_str(stored_sub_type)[0] == base_type
232
+ and instrument in instruments
233
+ and self._sub_connection_ready.get(stored_sub_type, False)
234
+ ):
235
+ return True
236
+
237
+ return False
225
238
 
226
239
  def has_pending_subscription(self, instrument: Instrument, subscription_type: str) -> bool:
227
240
  """
@@ -229,16 +242,24 @@ class SubscriptionManager:
229
242
 
230
243
  Args:
231
244
  instrument: Instrument to check
232
- subscription_type: Full subscription type (e.g., "ohlc(1m)")
245
+ subscription_type: Base or full subscription type (e.g., "orderbook" or "orderbook(0.0, 20)")
233
246
 
234
247
  Returns:
235
248
  True if subscription is pending (connection being established)
236
249
  """
237
- return (
238
- subscription_type in self._pending_subscriptions
239
- and instrument in self._pending_subscriptions[subscription_type]
240
- and not self._sub_connection_ready.get(subscription_type, False)
241
- )
250
+ # Get the base type for comparison
251
+ base_type = DataType.from_str(subscription_type)[0]
252
+
253
+ # Check if any pending subscription with matching base type contains the instrument and is not ready
254
+ for stored_sub_type, instruments in self._pending_subscriptions.items():
255
+ if (
256
+ DataType.from_str(stored_sub_type)[0] == base_type
257
+ and instrument in instruments
258
+ and not self._sub_connection_ready.get(stored_sub_type, False)
259
+ ):
260
+ return True
261
+
262
+ return False
242
263
 
243
264
  def get_all_subscribed_instruments(self) -> Set[Instrument]:
244
265
  """
@@ -262,14 +283,14 @@ class SubscriptionManager:
262
283
  True if connection is established and ready
263
284
  """
264
285
  return self._sub_connection_ready.get(subscription_type, False)
265
-
286
+
266
287
  def has_subscription_type(self, subscription_type: str) -> bool:
267
288
  """
268
289
  Check if a subscription type exists (has any instruments).
269
-
290
+
270
291
  Args:
271
292
  subscription_type: Full subscription type (e.g., "ohlc(1m)")
272
-
293
+
273
294
  Returns:
274
295
  True if subscription type has any instruments
275
296
  """
@@ -283,33 +304,33 @@ class SubscriptionManager:
283
304
  Dictionary mapping symbols to instruments
284
305
  """
285
306
  return self._symbol_to_instrument.copy()
286
-
307
+
287
308
  def set_individual_streams(self, subscription_type: str, streams: dict[Instrument, str]) -> None:
288
309
  """
289
310
  Store individual stream mappings for a subscription type.
290
-
311
+
291
312
  Args:
292
313
  subscription_type: Full subscription type (e.g., "ohlc(1m)")
293
314
  streams: Dictionary mapping instrument to stream name
294
315
  """
295
316
  self._individual_streams[subscription_type] = streams
296
-
317
+
297
318
  def get_individual_streams(self, subscription_type: str) -> dict[Instrument, str]:
298
319
  """
299
320
  Get individual stream mappings for a subscription type.
300
-
321
+
301
322
  Args:
302
323
  subscription_type: Full subscription type (e.g., "ohlc(1m)")
303
-
324
+
304
325
  Returns:
305
326
  Dictionary mapping instrument to stream name
306
327
  """
307
328
  return self._individual_streams.get(subscription_type, {})
308
-
329
+
309
330
  def clear_individual_streams(self, subscription_type: str) -> None:
310
331
  """
311
332
  Clear individual stream mappings for a subscription type.
312
-
333
+
313
334
  Args:
314
335
  subscription_type: Full subscription type (e.g., "ohlc(1m)")
315
336
  """
@@ -18,6 +18,7 @@ from qubx.core.basics import (
18
18
  )
19
19
  from qubx.core.helpers import extract_price
20
20
  from qubx.core.interfaces import IAccountProcessor, ISubscriptionManager
21
+ from qubx.core.mixins.utils import EXCHANGE_MAPPINGS
21
22
 
22
23
 
23
24
  class BasicAccountProcessor(IAccountProcessor):
@@ -75,6 +76,9 @@ class BasicAccountProcessor(IAccountProcessor):
75
76
  def get_positions(self, exchange: str | None = None) -> dict[Instrument, Position]:
76
77
  return self._positions
77
78
 
79
+ def get_fees_calculator(self, exchange: str | None = None) -> TransactionCostsCalculator:
80
+ return self._tcc
81
+
78
82
  def get_position(self, instrument: Instrument) -> Position:
79
83
  _pos = self._positions.get(instrument)
80
84
  if _pos is None:
@@ -353,11 +357,20 @@ class CompositeAccountProcessor(IAccountProcessor):
353
357
  """
354
358
  if exchange:
355
359
  if exchange not in self._account_processors:
360
+ # Check if there's a mapping for this exchange
361
+ if exchange in EXCHANGE_MAPPINGS and EXCHANGE_MAPPINGS[exchange] in self._account_processors:
362
+ return EXCHANGE_MAPPINGS[exchange]
356
363
  raise ValueError(f"Unknown exchange: {exchange}")
357
364
  return exchange
358
365
 
359
366
  if instrument:
360
367
  if instrument.exchange not in self._account_processors:
368
+ # Check if there's a mapping for this exchange
369
+ if (
370
+ instrument.exchange in EXCHANGE_MAPPINGS
371
+ and EXCHANGE_MAPPINGS[instrument.exchange] in self._account_processors
372
+ ):
373
+ return EXCHANGE_MAPPINGS[instrument.exchange]
361
374
  raise ValueError(f"Unknown exchange: {instrument.exchange}")
362
375
  return instrument.exchange
363
376
 
@@ -443,6 +456,10 @@ class CompositeAccountProcessor(IAccountProcessor):
443
456
  exch = self._get_exchange(exchange)
444
457
  return self._account_processors[exch].position_report()
445
458
 
459
+ def get_fees_calculator(self, exchange: str | None = None) -> TransactionCostsCalculator:
460
+ exch = self._get_exchange(exchange)
461
+ return self._account_processors[exch].get_fees_calculator()
462
+
446
463
  ########################################################
447
464
  # Leverage information
448
465
  ########################################################
@@ -15,6 +15,7 @@ from qubx.core.basics import (
15
15
  Order,
16
16
  OrderRequest,
17
17
  Position,
18
+ RestoredState,
18
19
  Signal,
19
20
  TargetPosition,
20
21
  Timestamped,
@@ -112,6 +113,7 @@ class StrategyContext(IStrategyContext):
112
113
  strategy_name: str | None = None,
113
114
  strategy_state: StrategyState | None = None,
114
115
  health_monitor: IHealthMonitor | None = None,
116
+ restored_state: RestoredState | None = None,
115
117
  ) -> None:
116
118
  self.account = account
117
119
  self.strategy = self.__instantiate_strategy(strategy, config)
@@ -138,6 +140,7 @@ class StrategyContext(IStrategyContext):
138
140
  self._lifecycle_notifier = lifecycle_notifier
139
141
  self._strategy_state = strategy_state if strategy_state is not None else StrategyState()
140
142
  self._strategy_name = strategy_name if strategy_name is not None else strategy.__class__.__name__
143
+ self._restored_state = restored_state
141
144
 
142
145
  self._health_monitor = health_monitor or DummyHealthMonitor()
143
146
  self.health = self._health_monitor
@@ -150,6 +153,8 @@ class StrategyContext(IStrategyContext):
150
153
  if __position_gathering is None:
151
154
  __position_gathering = position_gathering if position_gathering is not None else SimplePositionGatherer()
152
155
 
156
+ __warmup_position_gathering = SimplePositionGatherer()
157
+
153
158
  self._subscription_manager = SubscriptionManager(
154
159
  data_providers=self._data_providers,
155
160
  default_base_subscription=DataType.ORDERBOOK
@@ -175,9 +180,10 @@ class StrategyContext(IStrategyContext):
175
180
  time_provider=self,
176
181
  account=self.account,
177
182
  position_gathering=__position_gathering,
183
+ warmup_position_gathering=__warmup_position_gathering,
178
184
  )
179
185
  self._trading_manager = TradingManager(
180
- time_provider=self,
186
+ context=self,
181
187
  brokers=self._brokers,
182
188
  account=self.account,
183
189
  strategy_name=self._strategy_name,
@@ -192,6 +198,7 @@ class StrategyContext(IStrategyContext):
192
198
  account=self.account,
193
199
  position_tracker=__position_tracker,
194
200
  position_gathering=__position_gathering,
201
+ warmup_position_gathering=__warmup_position_gathering,
195
202
  universe_manager=self._universe_manager,
196
203
  cache=self._cache,
197
204
  scheduler=self._scheduler,
@@ -455,11 +462,11 @@ class StrategyContext(IStrategyContext):
455
462
  ) -> Order:
456
463
  return self._trading_manager.set_target_position(instrument, target, price, **options)
457
464
 
458
- def close_position(self, instrument: Instrument) -> None:
459
- return self._trading_manager.close_position(instrument)
465
+ def close_position(self, instrument: Instrument, without_signals: bool = False) -> None:
466
+ return self._trading_manager.close_position(instrument, without_signals)
460
467
 
461
- def close_positions(self, market_type: MarketType | None = None) -> None:
462
- return self._trading_manager.close_positions(market_type)
468
+ def close_positions(self, market_type: MarketType | None = None, without_signals: bool = False) -> None:
469
+ return self._trading_manager.close_positions(market_type, without_signals)
463
470
 
464
471
  def cancel_order(self, order_id: str, exchange: str | None = None) -> None:
465
472
  return self._trading_manager.cancel_order(order_id, exchange)
@@ -585,6 +592,9 @@ class StrategyContext(IStrategyContext):
585
592
  def get_warmup_orders(self) -> dict[Instrument, list[Order]]:
586
593
  return self._warmup_orders if self._warmup_orders is not None else {}
587
594
 
595
+ def get_restored_state(self) -> RestoredState | None:
596
+ return self._restored_state
597
+
588
598
  # private methods
589
599
  def __process_incoming_data_loop(self, channel: CtrlChannel):
590
600
  logger.info("[StrategyContext] :: Start processing market data")
@@ -36,6 +36,7 @@ from qubx.core.basics import (
36
36
  Signal,
37
37
  TargetPosition,
38
38
  Timestamped,
39
+ TransactionCostsCalculator,
39
40
  TriggerEvent,
40
41
  dt_64,
41
42
  td_64,
@@ -153,6 +154,17 @@ class IAccountViewer:
153
154
  """
154
155
  ...
155
156
 
157
+ def get_fees_calculator(self, exchange: str | None = None) -> TransactionCostsCalculator:
158
+ """Get the fees calculator.
159
+
160
+ Args:
161
+ exchange: The exchange to get the fees calculator for
162
+
163
+ Returns:
164
+ TransactionCostsCalculator: The transaction costs calculator
165
+ """
166
+ ...
167
+
156
168
  @property
157
169
  def positions(self) -> dict[Instrument, Position]:
158
170
  """[Deprecated: Use get_positions()] Get all current positions.
@@ -673,15 +685,16 @@ class ITradingManager:
673
685
  """
674
686
  ...
675
687
 
676
- def close_position(self, instrument: Instrument) -> None:
688
+ def close_position(self, instrument: Instrument, without_signals: bool = False) -> None:
677
689
  """Close position for an instrument.
678
690
 
679
691
  Args:
680
692
  instrument: The instrument to close position for
693
+ without_signals: If True, trade submitted instead of emitting signal
681
694
  """
682
695
  ...
683
696
 
684
- def close_positions(self, market_type: MarketType | None = None, exchange: str | None = None) -> None:
697
+ def close_positions(self, market_type: MarketType | None = None, without_signals: bool = False) -> None:
685
698
  """Close all positions."""
686
699
  ...
687
700
 
@@ -1200,6 +1213,10 @@ class IStrategyContext(
1200
1213
  """Get the list of exchanges."""
1201
1214
  return []
1202
1215
 
1216
+ def get_restored_state(self) -> "RestoredState | None":
1217
+ """Get the restored state."""
1218
+ return None
1219
+
1203
1220
 
1204
1221
  class IPositionGathering:
1205
1222
  """
@@ -1226,6 +1243,28 @@ class IPositionGathering:
1226
1243
 
1227
1244
  def on_execution_report(self, ctx: IStrategyContext, instrument: Instrument, deal: Deal): ...
1228
1245
 
1246
+ def update(self, ctx: IStrategyContext, instrument: Instrument, update: Timestamped) -> None:
1247
+ """
1248
+ Position gatherer is being updated by new market data.
1249
+
1250
+ Args:
1251
+ ctx: Strategy context object
1252
+ instrument: The instrument for which market data was updated
1253
+ update: The market data update (Quote, Trade, Bar, etc.)
1254
+ """
1255
+ pass
1256
+
1257
+ def restore_from_target_positions(self, ctx: IStrategyContext, target_positions: list[TargetPosition]) -> None:
1258
+ """
1259
+ Restore gatherer state from target positions.
1260
+
1261
+ Args:
1262
+ ctx: Strategy context object
1263
+ target_positions: List of target positions to restore gatherer state from
1264
+ """
1265
+ # Default implementation - subclasses can override if needed
1266
+ pass
1267
+
1229
1268
 
1230
1269
  class IPositionSizer:
1231
1270
  """Interface for calculating target positions from signals."""
@@ -1307,15 +1346,16 @@ class PositionsTracker:
1307
1346
  """
1308
1347
  ...
1309
1348
 
1310
- def restore_position_from_target(self, ctx: IStrategyContext, target: TargetPosition):
1349
+ def restore_position_from_signals(self, ctx: IStrategyContext, signals: list[Signal]) -> None:
1311
1350
  """
1312
- Restore active position and tracking from the target.
1351
+ Restore tracker state from signals.
1313
1352
 
1314
1353
  Args:
1315
- - ctx: Strategy context object.
1316
- - target: Target position to restore from.
1354
+ ctx: Strategy context object
1355
+ signals: List of signals to restore tracker state from
1317
1356
  """
1318
- ...
1357
+ # Default implementation - subclasses can override
1358
+ pass
1319
1359
 
1320
1360
 
1321
1361
  @dataclass
@@ -1350,12 +1390,12 @@ class HealthMetrics:
1350
1390
  @runtime_checkable
1351
1391
  class IDataArrivalListener(Protocol):
1352
1392
  """Interface for components that want to be notified of data arrivals."""
1353
-
1393
+
1354
1394
  def on_data_arrival(self, event_type: str, event_time: dt_64) -> None:
1355
1395
  """Called when new data arrives.
1356
-
1396
+
1357
1397
  Args:
1358
- event_type: Type of data event (e.g., "ohlcv:BTC/USDT:1m")
1398
+ event_type: Type of data event (e.g., "ohlcv:BTC/USDT:1m")
1359
1399
  event_time: Timestamp of the data event
1360
1400
  """
1361
1401
  ...
@@ -244,6 +244,7 @@ class SignalsAndTargetsLogger(_BaseIntervalDumper):
244
244
  "entry_price": t.entry_price,
245
245
  "take_price": t.take_price,
246
246
  "stop_price": t.stop_price,
247
+ "options": t.options,
247
248
  }
248
249
  for t in self._targets
249
250
  ]
@@ -262,6 +263,7 @@ class SignalsAndTargetsLogger(_BaseIntervalDumper):
262
263
  "group": s.group,
263
264
  "comment": s.comment,
264
265
  "service": s.is_service,
266
+ "options": s.options,
265
267
  }
266
268
  for s in self._signals
267
269
  ]