Qubx 3.6.0__tar.gz → 3.6.2.dev1__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 (328) hide show
  1. {qubx-3.6.0 → qubx-3.6.2.dev1}/PKG-INFO +2 -2
  2. {qubx-3.6.0 → qubx-3.6.2.dev1}/pyproject.toml +4 -1
  3. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/_version.py +2 -2
  4. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/commands.py +6 -1
  5. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/deploy.py +11 -1
  6. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/release.py +88 -2
  7. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/account_manager/diffs.py +10 -1
  8. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/account_manager/state.py +9 -1
  9. qubx-3.6.2.dev1/src/qubx/core/boot.py +103 -0
  10. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/fit_executor.py +3 -0
  11. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/initializer.py +7 -0
  12. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/interfaces.py +16 -0
  13. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/mixins/processing.py +142 -75
  14. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restarts/state_resolvers.py +55 -36
  15. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/runner.py +0 -4
  16. {qubx-3.6.0 → qubx-3.6.2.dev1}/.gitignore +0 -0
  17. {qubx-3.6.0 → qubx-3.6.2.dev1}/LICENSE +0 -0
  18. {qubx-3.6.0 → qubx-3.6.2.dev1}/README.md +0 -0
  19. {qubx-3.6.0 → qubx-3.6.2.dev1}/scripts/build.py +0 -0
  20. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/__init__.py +0 -0
  21. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/_nb_magic.py +0 -0
  22. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/__init__.py +0 -0
  23. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/connector.py +0 -0
  24. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/data.py +0 -0
  25. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/iteratedstream.py +0 -0
  26. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/management.py +0 -0
  27. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/ome.py +0 -0
  28. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/optimization.py +0 -0
  29. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/runner.py +0 -0
  30. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/sentinels.py +0 -0
  31. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/simulated_data.py +0 -0
  32. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/simulated_exchange.py +0 -0
  33. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/simulator.py +0 -0
  34. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/transfers.py +0 -0
  35. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/backtester/utils.py +0 -0
  36. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/__init__.py +0 -0
  37. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/misc.py +0 -0
  38. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/resolver.py +0 -0
  39. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/s3.py +0 -0
  40. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/theme.py +0 -0
  41. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/tui.py +0 -0
  42. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/cli/user_config.py +0 -0
  43. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/config.py +0 -0
  44. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/__init__.py +0 -0
  45. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/__init__.py +0 -0
  46. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  47. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  48. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  49. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/connector.py +0 -0
  50. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/data.py +0 -0
  51. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  52. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  53. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  54. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/_two_stream.py +0 -0
  55. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  56. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/binance/connector.py +0 -0
  57. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  58. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  59. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/bitfinex/connector.py +0 -0
  60. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
  61. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
  62. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  63. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  64. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  65. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
  66. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/okx/connector.py +0 -0
  67. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
  68. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/factory.py +0 -0
  69. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  70. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  71. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  72. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  73. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  74. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  75. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  76. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  77. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  78. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  79. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/plugin.py +0 -0
  80. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/rate_limits.py +0 -0
  81. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  82. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  83. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  84. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/utils.py +0 -0
  85. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  86. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/plugin.py +0 -0
  87. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/registry.py +0 -0
  88. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/tardis/data.py +0 -0
  89. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/tardis/plugin.py +0 -0
  90. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/connectors/tardis/utils.py +0 -0
  91. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/control/__init__.py +0 -0
  92. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/control/builtin.py +0 -0
  93. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/control/decorator.py +0 -0
  94. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/control/executor.py +0 -0
  95. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/control/interfaces.py +0 -0
  96. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/control/server.py +0 -0
  97. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/control/types.py +0 -0
  98. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/__init__.py +0 -0
  99. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/account_manager/__init__.py +0 -0
  100. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/account_manager/config.py +0 -0
  101. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/account_manager/manager.py +0 -0
  102. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/account_manager/reconciler.py +0 -0
  103. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/account_manager/reducer.py +0 -0
  104. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/account_manager/state_machine.py +0 -0
  105. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/basics.py +0 -0
  106. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/connector.py +0 -0
  107. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/context.py +0 -0
  108. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/detectors/__init__.py +0 -0
  109. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/detectors/delisting.py +0 -0
  110. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/detectors/stale.py +0 -0
  111. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/errors.py +0 -0
  112. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/events.py +0 -0
  113. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/exceptions.py +0 -0
  114. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/fit_context.py +0 -0
  115. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/helpers.py +0 -0
  116. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/instrument_service.py +0 -0
  117. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/loggers.py +0 -0
  118. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/lookups.py +0 -0
  119. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/metrics.py +0 -0
  120. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/mixins/__init__.py +0 -0
  121. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/mixins/instrument_service.py +0 -0
  122. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/mixins/market.py +0 -0
  123. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/mixins/subscription.py +0 -0
  124. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/mixins/trading.py +0 -0
  125. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/mixins/universe.py +0 -0
  126. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/mixins/utils.py +0 -0
  127. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/series.pxd +0 -0
  128. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/series.pyi +0 -0
  129. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/series.pyx +0 -0
  130. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/status.py +0 -0
  131. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/utils.pyi +0 -0
  132. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/core/utils.pyx +0 -0
  133. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/__init__.py +0 -0
  134. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/cache.py +0 -0
  135. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/containers.py +0 -0
  136. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/guards.py +0 -0
  137. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/registry.py +0 -0
  138. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/storage.py +0 -0
  139. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/storages/ccxt.py +0 -0
  140. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/storages/csv.py +0 -0
  141. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/storages/handy.py +0 -0
  142. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/storages/multi.py +0 -0
  143. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/storages/questdb.py +0 -0
  144. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/storages/stub.py +0 -0
  145. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/storages/utils.py +0 -0
  146. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/data/transformers.py +0 -0
  147. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/emitters/__init__.py +0 -0
  148. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/emitters/base.py +0 -0
  149. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/emitters/composite.py +0 -0
  150. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/emitters/csv.py +0 -0
  151. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/emitters/indicator.py +0 -0
  152. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/emitters/inmemory.py +0 -0
  153. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/emitters/prometheus.py +0 -0
  154. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/emitters/questdb.py +0 -0
  155. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/__init__.py +0 -0
  156. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/composite.py +0 -0
  157. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/formatters/__init__.py +0 -0
  158. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/formatters/base.py +0 -0
  159. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/formatters/incremental.py +0 -0
  160. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/formatters/slack.py +0 -0
  161. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/formatters/target_position.py +0 -0
  162. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/redis_streams.py +0 -0
  163. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/exporters/slack.py +0 -0
  164. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/gathering/simplest.py +0 -0
  165. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/health/__init__.py +0 -0
  166. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/health/base.py +0 -0
  167. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/health/dummy.py +0 -0
  168. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/loggers/__init__.py +0 -0
  169. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/loggers/csv.py +0 -0
  170. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/loggers/factory.py +0 -0
  171. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/loggers/inmemory.py +0 -0
  172. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/loggers/mongo.py +0 -0
  173. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/loggers/postgres.py +0 -0
  174. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/notifications/__init__.py +0 -0
  175. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/notifications/composite.py +0 -0
  176. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/notifications/slack.py +0 -0
  177. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/notifications/throttler.py +0 -0
  178. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/pandaz/__init__.py +0 -0
  179. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/pandaz/stats.py +0 -0
  180. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/pandaz/ta.py +0 -0
  181. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/pandaz/utils.py +0 -0
  182. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/plugins/__init__.py +0 -0
  183. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/plugins/loader.py +0 -0
  184. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/__init__.py +0 -0
  185. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/backend.py +0 -0
  186. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/config.py +0 -0
  187. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/engine.py +0 -0
  188. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/ip_resolver.py +0 -0
  189. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/manager.py +0 -0
  190. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/pools.py +0 -0
  191. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/redis_backend.py +0 -0
  192. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/rate_limiting/resilient.py +0 -0
  193. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/_build.py +0 -0
  194. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/crypto-fees.ini +0 -0
  195. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  196. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  197. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  198. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  199. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  200. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  201. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  202. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  203. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  204. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  205. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  206. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restarts/__init__.py +0 -0
  207. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restarts/time_finders.py +0 -0
  208. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restorers/__init__.py +0 -0
  209. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restorers/balance.py +0 -0
  210. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restorers/factory.py +0 -0
  211. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restorers/interfaces.py +0 -0
  212. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restorers/position.py +0 -0
  213. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restorers/signal.py +0 -0
  214. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restorers/state.py +0 -0
  215. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/restorers/utils.py +0 -0
  216. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/state/__init__.py +0 -0
  217. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/state/dummy.py +0 -0
  218. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/state/redis.py +0 -0
  219. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/state/safe.py +0 -0
  220. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/ta/__init__.py +0 -0
  221. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/ta/indicators.pxd +0 -0
  222. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/ta/indicators.pyi +0 -0
  223. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/ta/indicators.pyx +0 -0
  224. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/__init__.py +0 -0
  225. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/base.py +0 -0
  226. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  227. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/project/config.yml.j2 +0 -0
  228. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  229. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  230. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  231. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  232. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  233. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/project/template.yml +0 -0
  234. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
  235. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
  236. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
  237. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
  238. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
  239. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.gitignore.j2 +0 -0
  240. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.python-version +0 -0
  241. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.vscode/launch.json +0 -0
  242. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.vscode/settings.json +0 -0
  243. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
  244. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
  245. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/README.md.j2 +0 -0
  246. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/configs/.gitkeep +0 -0
  247. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/justfile.j2 +0 -0
  248. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
  249. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/research/.gitkeep +0 -0
  250. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  251. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
  252. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  253. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/template.yml +0 -0
  254. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
  255. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  256. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  257. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/simple/config.yml.j2 +0 -0
  258. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  259. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  260. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  261. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/templates/simple/template.yml +0 -0
  262. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/trackers/__init__.py +0 -0
  263. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/trackers/advanced.py +0 -0
  264. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/trackers/composite.py +0 -0
  265. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/trackers/rebalancers.py +0 -0
  266. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/trackers/riskctrl.py +0 -0
  267. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/trackers/sizers.py +0 -0
  268. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/__init__.py +0 -0
  269. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/_pyxreloader.py +0 -0
  270. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/charting/lookinglass.py +0 -0
  271. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  272. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/charting/orderbook.py +0 -0
  273. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/clock.py +0 -0
  274. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/collections.py +0 -0
  275. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/hft/__init__.py +0 -0
  276. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/hft/numba_utils.py +0 -0
  277. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/hft/orderbook.pyi +0 -0
  278. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/hft/orderbook.pyx +0 -0
  279. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/marketdata/binance.py +0 -0
  280. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/marketdata/ccxt.py +0 -0
  281. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/marketdata/dukas.py +0 -0
  282. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/misc.py +0 -0
  283. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/nonce.py +0 -0
  284. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/numbers_utils.py +0 -0
  285. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/orderbook.py +0 -0
  286. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/plotting/__init__.py +0 -0
  287. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/plotting/dashboard.py +0 -0
  288. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/plotting/data.py +0 -0
  289. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/plotting/interfaces.py +0 -0
  290. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  291. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  292. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/questdb.py +0 -0
  293. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/rate_limiter.py +0 -0
  294. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/results.py +0 -0
  295. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/ringbuffer.pxd +0 -0
  296. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/ringbuffer.pyi +0 -0
  297. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/ringbuffer.pyx +0 -0
  298. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/__init__.py +0 -0
  299. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  300. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/accounts.py +0 -0
  301. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/configs.py +0 -0
  302. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/factory.py +0 -0
  303. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/kernel_service.py +0 -0
  304. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/__init__.py +0 -0
  305. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/app.py +0 -0
  306. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/handlers.py +0 -0
  307. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/init_code.py +0 -0
  308. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/kernel.py +0 -0
  309. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  310. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  311. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
  312. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  313. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  314. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/log_filter.py +0 -0
  315. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  316. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  317. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  318. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  319. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/s3.py +0 -0
  320. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/slack.py +0 -0
  321. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/threading.py +0 -0
  322. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/throttler.py +0 -0
  323. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/time.py +0 -0
  324. {qubx-3.6.0 → qubx-3.6.2.dev1}/src/qubx/utils/websocket_manager.py +0 -0
  325. {qubx-3.6.0 → qubx-3.6.2.dev1}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  326. {qubx-3.6.0 → qubx-3.6.2.dev1}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
  327. {qubx-3.6.0 → qubx-3.6.2.dev1}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  328. {qubx-3.6.0 → qubx-3.6.2.dev1}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: Qubx
3
- Version: 3.6.0
3
+ Version: 3.6.2.dev1
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
@@ -43,7 +43,7 @@ Requires-Dist: rich<14,>=13.9.4
43
43
  Requires-Dist: scipy<2,>=1.12.0
44
44
  Requires-Dist: sortedcontainers<3,>=2.4.0
45
45
  Requires-Dist: stackprinter<1,>=0.2.10
46
- Requires-Dist: statsmodels<1,>=0.14.6
46
+ Requires-Dist: statsmodels<0.15,>=0.14.6
47
47
  Requires-Dist: tabulate<1,>=0.9.0
48
48
  Requires-Dist: textual-autocomplete<5,>=4.0.0
49
49
  Requires-Dist: textual-serve<2,>=1.0.0
@@ -56,7 +56,10 @@ dependencies = [
56
56
  "jinja2>=3.1.0,<4",
57
57
  "rich>=13.9.4,<14",
58
58
  "click>=8.1.7,<9",
59
- "statsmodels>=0.14.6,<1",
59
+ # 0.15.0 (2026-08-27) ships no cp312 manylinux wheels — fresh resolutions (Docker image
60
+ # pip install, cibuildwheel test-install, downstream consumers) fall back to a meson sdist
61
+ # build that needs a compiler. Drop the <0.15 cap when 0.15.x publishes cp312 linux wheels.
62
+ "statsmodels>=0.14.6,<0.15",
60
63
 
61
64
  # - - - Database backends - - -
62
65
  "pymongo>=4.6.1,<5",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '3.6.0'
22
- __version_tuple__ = version_tuple = (3, 6, 0)
21
+ __version__ = version = '3.6.2.dev1'
22
+ __version_tuple__ = version_tuple = (3, 6, 2, 'dev1')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -332,6 +332,9 @@ def run(
332
332
  help="Write simulation logs to a specific file path.",
333
333
  show_default=True,
334
334
  )
335
+ @click.option(
336
+ "--dev", is_flag=True, default=False, help="Enable dev mode (adds ~/projects to path).", show_default=True
337
+ )
335
338
  def simulate(
336
339
  config_file: Path,
337
340
  start: str | None,
@@ -340,6 +343,7 @@ def simulate(
340
343
  report: str | None,
341
344
  name: str | None,
342
345
  log_file: str | None,
346
+ dev: bool,
343
347
  ):
344
348
  """
345
349
  Simulates the strategy with the given configuration file.
@@ -347,7 +351,8 @@ def simulate(
347
351
  from qubx.utils.misc import add_project_to_system_path, logo
348
352
  from qubx.utils.runner.runner import simulate_strategy
349
353
 
350
- add_project_to_system_path()
354
+ if dev:
355
+ add_project_to_system_path() # Adds ~/projects in dev mode
351
356
  add_project_to_system_path(str(config_file.parent))
352
357
  logo()
353
358
  logger.info(f"Process PID: <g>{os.getpid()}</g>")
@@ -120,7 +120,12 @@ def ensure_lock_exists(output_dir: str) -> bool:
120
120
  """
121
121
  uv_lock_path = os.path.join(output_dir, "uv.lock")
122
122
  if not os.path.exists(uv_lock_path):
123
- logger.warning("uv.lock not found in the zip file. Attempting to generate it.")
123
+ logger.warning(
124
+ "uv.lock not found in the zip file. Attempting to generate it. "
125
+ "This is a legacy release built without a shipped lock — resolving here, in the pod, "
126
+ "is non-hermetic: transitive deps can float to whatever is newest on PyPI at deploy "
127
+ "time (see issue #398). Re-release with a current qubx to ship a lock instead."
128
+ )
124
129
  try:
125
130
  subprocess.run(["uv", "lock"], cwd=output_dir, check=True, capture_output=True, text=True)
126
131
  return True
@@ -155,6 +160,11 @@ def setup_uv_environment(output_dir: str) -> bool:
155
160
  logger.info("Installing dependencies")
156
161
 
157
162
  install_cmd = ["uv", "sync"]
163
+ if os.path.exists(os.path.join(output_dir, "uv.lock")):
164
+ # Shipped lock is authoritative: never re-resolve in the pod
165
+ # (the runtime image cannot build sdists; see issue #398).
166
+ install_cmd.append("--frozen")
167
+
158
168
  if in_venv:
159
169
  # Force uv to create a new environment even if we're in an active one
160
170
  env = os.environ.copy()
@@ -4,6 +4,7 @@ import os
4
4
  import platform
5
5
  import re
6
6
  import shutil
7
+ import tempfile
7
8
  import urllib.request
8
9
  from base64 import b64encode
9
10
  from collections import namedtuple
@@ -835,6 +836,57 @@ def _parse_uv_lock_git_commits(uv_lock_path: str) -> dict[str, str]:
835
836
  return commits
836
837
 
837
838
 
839
+ def _lock_constraint_dependencies(uv_lock_path: str) -> list[str]:
840
+ """Build `name==version` constraints from the source repo's uv.lock.
841
+
842
+ The wrapper's fresh resolution must reproduce the source repo's tested
843
+ resolution — otherwise a same-day PyPI publish of any transitive dep changes
844
+ what bots run (see issue #398). Names that appear with multiple versions in
845
+ the lock (platform-forked resolutions) are skipped: a single == constraint
846
+ could make the wrapper unresolvable.
847
+
848
+ Only registry-sourced entries (`source = { registry = ... }`) are eligible.
849
+ Git, editable, path, directory, and URL sources are pinned by their own
850
+ ref / bundled wheel already — emitting an `==` constraint for them is wrong
851
+ and unsatisfiable, since uv can only satisfy a bare `==` constraint from an
852
+ index, and that exact version was never published to one. If ANY lock
853
+ entry for a given name is non-registry, the name is excluded entirely.
854
+ """
855
+ import toml
856
+
857
+ if not os.path.exists(uv_lock_path):
858
+ logger.warning(f"uv.lock not found at {uv_lock_path}")
859
+ return []
860
+
861
+ with open(uv_lock_path) as f:
862
+ lock_data = toml.load(f)
863
+
864
+ versions_by_name: dict[str, set[str]] = {}
865
+ non_registry_names: set[str] = set()
866
+ for pkg in lock_data.get("package", []):
867
+ name = pkg.get("name", "")
868
+ version = pkg.get("version", "")
869
+ if not name or not version:
870
+ continue
871
+ versions_by_name.setdefault(name, set()).add(version)
872
+ if "registry" not in pkg.get("source", {}):
873
+ non_registry_names.add(name)
874
+
875
+ skipped_multi = sorted(name for name, versions in versions_by_name.items() if len(versions) > 1)
876
+ if skipped_multi:
877
+ logger.debug(f"Skipping constraint-dependencies for multi-version packages: {skipped_multi}")
878
+
879
+ skipped_non_registry = sorted(non_registry_names)
880
+ if skipped_non_registry:
881
+ logger.debug(f"Skipping constraint-dependencies for non-registry-sourced packages: {skipped_non_registry}")
882
+
883
+ return sorted(
884
+ f"{name}=={next(iter(versions))}"
885
+ for name, versions in versions_by_name.items()
886
+ if len(versions) == 1 and name not in non_registry_names
887
+ )
888
+
889
+
838
890
  def _find_uv_git_checkout(commit_sha: str) -> str | None:
839
891
  """
840
892
  Find the uv git cache checkout directory for a given commit SHA.
@@ -921,6 +973,7 @@ def _generate_release_pyproject(
921
973
  plugin_deps: list[str] | None = None,
922
974
  external_deps: list[str] | None = None,
923
975
  pyproject_data: dict | None = None,
976
+ constraint_dependencies: list[str] | None = None,
924
977
  ) -> None:
925
978
  """
926
979
  Generate a minimal pyproject.toml for the release package.
@@ -940,6 +993,9 @@ def _generate_release_pyproject(
940
993
  plugin_deps: Plugin dependency specs to include
941
994
  external_deps: External package dependency specs (for no-code configs)
942
995
  pyproject_data: Original pyproject data for preserving index config
996
+ constraint_dependencies: `name==version` pins from the source repo's uv.lock,
997
+ written to `tool.uv.constraint-dependencies` so the wrapper's fresh
998
+ resolution reproduces the tested one (see issue #398)
943
999
  """
944
1000
  import toml
945
1001
 
@@ -986,11 +1042,22 @@ def _generate_release_pyproject(
986
1042
  if index_sources:
987
1043
  uv_config["sources"] = index_sources
988
1044
 
1045
+ if constraint_dependencies:
1046
+ uv_config["constraint-dependencies"] = constraint_dependencies
1047
+
1048
+ # The wrapper only ever runs inside the deploy image (Dockerfile: FROM
1049
+ # python:3.12-slim, linux/amd64) — never resolved for anything else. Pin
1050
+ # both requires-python and the resolution environment to exactly that
1051
+ # target so `uv lock` doesn't try (and fail) to find candidates for other
1052
+ # Python versions/platforms; the bundled first-party wheels are
1053
+ # cp312-linux_x86_64-only anyway. Must move together with the Dockerfile.
1054
+ uv_config["environments"] = ["sys_platform == 'linux' and platform_machine == 'x86_64'"]
1055
+
989
1056
  release_pyproject: dict = {
990
1057
  "project": {
991
1058
  "name": "strategy-release",
992
1059
  "version": "0.1.0",
993
- "requires-python": ">=3.12",
1060
+ "requires-python": "==3.12.*",
994
1061
  "dependencies": deps,
995
1062
  },
996
1063
  "tool": {
@@ -1128,6 +1195,9 @@ def create_released_pack(
1128
1195
  for whl in sorted(wheel_files):
1129
1196
  logger.info(f" {whl}")
1130
1197
 
1198
+ constraint_dependencies = _lock_constraint_dependencies(uv_lock_path)
1199
+ logger.info(f"Constraining wrapper resolution to {len(constraint_dependencies)} pin(s) from source uv.lock")
1200
+
1131
1201
  _generate_release_pyproject(
1132
1202
  release_dir=release_dir,
1133
1203
  strategy_wheel_name=strategy_wheel_name,
@@ -1135,6 +1205,7 @@ def create_released_pack(
1135
1205
  plugin_deps=plugin_deps,
1136
1206
  external_deps=external_deps,
1137
1207
  pyproject_data=pyproject_data,
1208
+ constraint_dependencies=constraint_dependencies,
1138
1209
  )
1139
1210
 
1140
1211
  # --- Save config + metadata ---
@@ -1588,10 +1659,24 @@ def _bundle_source_overrides(
1588
1659
 
1589
1660
  logger.info(f" Bundling {pkg_name}=={pkg_ver} from {build_cwd} ...")
1590
1661
  os.makedirs(wheels_dir, exist_ok=True)
1662
+
1663
+ # `uv build` refuses to build a project that lives inside uv's own
1664
+ # cache dir ("The project directory `.` is inside the cache
1665
+ # directory"), which is exactly where a cache-hit git checkout
1666
+ # lives. Always build from a fresh temp copy — this also keeps
1667
+ # build artifacts out of the shared uv cache checkout. `.git` is
1668
+ # copied along with everything else: build backends that derive
1669
+ # the version from VCS metadata (e.g. hatch-vcs, as quantkit
1670
+ # uses) need it to produce a real version instead of "0.0.0".
1671
+ build_tmp_root = tempfile.mkdtemp(prefix="qubx-release-build-")
1591
1672
  try:
1673
+ build_copy = os.path.join(build_tmp_root, "src")
1674
+ shutil.copytree(checkout_dir, build_copy, dirs_exist_ok=True)
1675
+ copy_build_cwd = os.path.join(build_copy, subdir) if subdir else build_copy
1676
+
1592
1677
  result = subprocess.run(
1593
1678
  ["uv", "build", "--wheel", ".", "--out-dir", wheels_dir],
1594
- cwd=build_cwd,
1679
+ cwd=copy_build_cwd,
1595
1680
  check=True,
1596
1681
  capture_output=True,
1597
1682
  text=True,
@@ -1608,6 +1693,7 @@ def _bundle_source_overrides(
1608
1693
  except subprocess.CalledProcessError as e:
1609
1694
  logger.opt(colors=False).warning(f" Failed to build wheel for {pkg_name}: {e.stderr or e}")
1610
1695
  finally:
1696
+ shutil.rmtree(build_tmp_root, ignore_errors=True)
1611
1697
  if cloned_locally and os.path.isdir(checkout_dir):
1612
1698
  shutil.rmtree(checkout_dir, ignore_errors=True)
1613
1699
 
@@ -28,6 +28,11 @@ RTOL = 1e-9
28
28
  # under 1 cent is not a reconcilable difference. Absolute, because "1 cent" is absolute.
29
29
  BALANCE_ABS_TOL = 0.01
30
30
 
31
+ # Maintenance margin re-marks with every snapshot's mark price, so RTOL-tight comparison
32
+ # emits a diff per position per poll. Only a material move (leverage/size change) is worth
33
+ # re-syncing; sub-0.5% (and sub-cent) drift is mark noise.
34
+ MARGIN_RTOL = 5e-3
35
+
31
36
 
32
37
  # - Diff atoms ------------------------------------------------------------------ #
33
38
  @dataclass_transform(frozen_default=True, kw_only_default=True)
@@ -69,6 +74,10 @@ def _close_bal(a: float, b: float) -> bool:
69
74
  return abs(a - b) <= BALANCE_ABS_TOL
70
75
 
71
76
 
77
+ def _close_margin(a: float, b: float) -> bool:
78
+ return abs(a - b) <= max(BALANCE_ABS_TOL, MARGIN_RTOL * max(abs(a), abs(b)))
79
+
80
+
72
81
  def _material_pos(position: Position) -> bool:
73
82
  # a position smaller than half a lot is effectively flat — never a presence diff
74
83
  return abs(position.quantity) > _qeps(position.instrument)
@@ -377,7 +386,7 @@ class Differ:
377
386
  if abs(lp.position_avg_price - sp.position_avg_price) > _peps(sp.instrument):
378
387
  diffs.append(PositionAvgPriceMismatch(local=lp, origin=sp))
379
388
 
380
- if not _close_rel(lp.maint_margin, sp.maint_margin):
389
+ if not _close_margin(lp.maint_margin, sp.maint_margin):
381
390
  diffs.append(PositionMarginMismatch(local=lp, origin=sp))
382
391
 
383
392
  for inst, lp in local_pos.items():
@@ -26,6 +26,10 @@ from qubx.core.basics import STABLE_CURRENCIES, Balance, Deal, Instrument, Order
26
26
  # sessions. A re-delivered funding event only needs RECENT buckets to dedup against.
27
27
  _FUNDING_BUCKET_CAP: int = 4096
28
28
 
29
+ # Snapshot balance-apply log level: a total move above this is real money (fill/funding/
30
+ # transfer) and logs INFO; below it (incl. pure free/locked reshuffles) logs DEBUG.
31
+ _BALANCE_LOG_ABS_TOL: float = 0.01
32
+
29
33
 
30
34
  @dataclass
31
35
  class VenueAccountFigures:
@@ -527,7 +531,11 @@ class AccountState:
527
531
  )
528
532
  if changed:
529
533
  old = "—" if existing is None else f"total={existing.total} free={existing.free} locked={existing.locked}"
530
- logger.info(
534
+ # - free/locked reshuffle with total unchanged is margin mark-to-market noise
535
+ # (every poll on a leveraged book); only a real total move is INFO-worthy
536
+ material = existing is None or abs(balance.total - existing.total) > _BALANCE_LOG_ABS_TOL
537
+ log = logger.info if material else logger.debug
538
+ log(
531
539
  f"[{self.exchange}] reconcile: balance <y>{balance.currency}</y> from snapshot -> "
532
540
  f"total=<g>{balance.total}</g> free={balance.free} locked={balance.locked} (was {old})"
533
541
  )
@@ -0,0 +1,103 @@
1
+ from enum import IntEnum
2
+
3
+ from qubx import logger
4
+ from qubx.core.basics import dt_64, td_64
5
+
6
+
7
+ class BootPhase(IntEnum):
8
+ BLOCKED = -1
9
+ WAIT_READY = 0
10
+ ON_START = 1
11
+ RESOLVE = 2
12
+ RESTORE = 3
13
+ WARMUP_FINISHED = 4
14
+ BOOT_FIT = 5
15
+ TRADING = 6
16
+
17
+
18
+ class BootStateMachine:
19
+ """Live-boot phase bookkeeping: current phase, boot-fit retry policy, health gauges.
20
+
21
+ Pure state — the ProcessingManager drives transitions and executes side effects
22
+ (see _advance_boot). BLOCKED is sticky for trading; a later successful fit releases
23
+ it (record_fit_success), a late account snapshot releases WAIT_READY (caller-side).
24
+ """
25
+
26
+ def __init__(self, health_monitor, *, fit_max_attempts: int = 3, fit_retry_delay: td_64 = td_64(60, "s")) -> None:
27
+ self.phase = BootPhase.WAIT_READY
28
+ self.blocked_reason: str | None = None
29
+ self.fit_attempts = 0
30
+ self._health = health_monitor
31
+ self._fit_max_attempts = fit_max_attempts
32
+ self._fit_retry_delay = fit_retry_delay
33
+ self._next_fit_attempt: dt_64 | None = None
34
+ self._sync_alerted = False
35
+
36
+ @property
37
+ def is_trading(self) -> bool:
38
+ return self.phase == BootPhase.TRADING
39
+
40
+ @property
41
+ def is_blocked(self) -> bool:
42
+ return self.phase == BootPhase.BLOCKED
43
+
44
+ def advance(self, phase: BootPhase) -> None:
45
+ if phase == self.phase:
46
+ return
47
+ logger.info(f"<yellow>boot</yellow> :: {self.phase.name} -> <green>{phase.name}</green>")
48
+ self.phase = phase
49
+ self._gauge("boot.state", float(phase))
50
+
51
+ def account_sync_alert(self) -> None:
52
+ if self._sync_alerted:
53
+ return
54
+ self._sync_alerted = True
55
+ self._gauge("boot.account_sync_blocked", 1.0)
56
+ logger.warning("<yellow>Boot blocked: initial account snapshot not applied — holding until synced</yellow>")
57
+
58
+ def account_synced(self) -> None:
59
+ if not self._sync_alerted:
60
+ return
61
+ self._sync_alerted = False
62
+ self._gauge("boot.account_sync_blocked", 0.0)
63
+
64
+ def fit_attempt_allowed(self, now: dt_64) -> bool:
65
+ if self.phase != BootPhase.BOOT_FIT:
66
+ return False
67
+ return self._next_fit_attempt is None or now >= self._next_fit_attempt
68
+
69
+ def record_fit_attempt(self) -> None:
70
+ self.fit_attempts += 1
71
+ self._gauge("boot.fit_attempts", float(self.fit_attempts))
72
+
73
+ def record_fit_failure(self, now: dt_64) -> None:
74
+ if self.is_blocked:
75
+ return
76
+ if self.fit_attempts >= self._fit_max_attempts:
77
+ self.blocked_reason = "boot fit failed"
78
+ self.advance(BootPhase.BLOCKED)
79
+ self._gauge("boot.fit_failed", 1.0)
80
+ logger.error(
81
+ f"<red>Boot fit failed after {self.fit_attempts} attempts — trading blocked, "
82
+ "book unreconciled; a later successful fit will unblock</red>"
83
+ )
84
+ else:
85
+ self._next_fit_attempt = now + self._fit_retry_delay
86
+ logger.warning(
87
+ f"<yellow>Boot fit failed (attempt {self.fit_attempts}/{self._fit_max_attempts}) — retrying</yellow>"
88
+ )
89
+
90
+ def record_fit_success(self) -> None:
91
+ if self.is_blocked:
92
+ self._gauge("boot.fit_failed", 0.0)
93
+ self.blocked_reason = None
94
+ self.advance(BootPhase.TRADING)
95
+
96
+ def record_warmup_finished_failure(self) -> None:
97
+ self._gauge("boot.warmup_finished_failed", 1.0)
98
+
99
+ def _gauge(self, name: str, value: float) -> None:
100
+ try:
101
+ self._health.record_gauge(name, value)
102
+ except Exception:
103
+ logger.exception(f"failed to record {name} gauge")
@@ -47,11 +47,14 @@ class FitCommitData:
47
47
  order (they run on the ProcessorThread, where the fit-thread interception no
48
48
  longer triggers, so each takes today's normal path). ``signals`` are the
49
49
  fit-emitted signals, drained into the normal pipeline in emission order.
50
+ ``error`` carries whatever made the fit fail (strategy raise or framework raise):
51
+ the ProcessorThread latches ``is_on_fit_called`` only when it is None.
50
52
  """
51
53
 
52
54
  ops: tuple[Callable[[], None], ...] = ()
53
55
  signals: tuple[Signal, ...] = ()
54
56
  duration_s: float = 0.0
57
+ error: BaseException | None = None
55
58
 
56
59
 
57
60
  class FitCycleState:
@@ -37,6 +37,7 @@ class BasicStrategyInitializer(IStrategyInitializer):
37
37
  default_instrument_leverage: float | None = None
38
38
  start_time_finder: StartTimeFinderProtocol | None = None
39
39
  mismatch_resolver: StateResolverProtocol | None = None
40
+ _fit_on_start: bool = False
40
41
  auto_subscribe: bool | None = None
41
42
  simulation: bool | None = None
42
43
  subscription_warmup: dict[Any, str] | None = None
@@ -131,6 +132,12 @@ class BasicStrategyInitializer(IStrategyInitializer):
131
132
  def set_state_resolver(self, resolver: StateResolverProtocol) -> None:
132
133
  self.mismatch_resolver = resolver
133
134
 
135
+ def set_fit_on_start(self, enabled: bool = True) -> None:
136
+ self._fit_on_start = enabled
137
+
138
+ def get_fit_on_start(self) -> bool:
139
+ return self._fit_on_start
140
+
134
141
  def set_config(self, key: str, value: Any) -> None:
135
142
  self.config[key] = value
136
143
 
@@ -2311,6 +2311,22 @@ class IStrategyInitializer:
2311
2311
  """
2312
2312
  ...
2313
2313
 
2314
+ def set_fit_on_start(self, enabled: bool = True) -> None:
2315
+ """
2316
+ Force the first on_fit to run in the live context even when a warmup-sim fit ran.
2317
+
2318
+ The warmup simulation fits against sim state; strategies that reconcile positions
2319
+ through their tracker on fit (e.g. buffered trackers) need the first fit to run
2320
+ live, against live positions. Opting in guarantees exactly one live fit after
2321
+ on_warmup_finished and replaces calling ctx.trigger_fit() there (doing both
2322
+ double-fits). No-op when no warmup sim runs — the first fit is live anyway.
2323
+ """
2324
+ ...
2325
+
2326
+ def get_fit_on_start(self) -> bool:
2327
+ """Whether the first on_fit is forced to run in the live context (default False)."""
2328
+ ...
2329
+
2314
2330
  def set_config(self, key: str, value: Any) -> None:
2315
2331
  """
2316
2332
  Set an additional configuration value.