Qubx 1.0.6.dev1__tar.gz → 1.0.6.dev3__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 (269) hide show
  1. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/PKG-INFO +1 -1
  2. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/__init__.py +5 -2
  3. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/_version.py +2 -2
  4. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/release.py +182 -120
  5. qubx-1.0.6.dev3/src/qubx/cli/resolver.py +225 -0
  6. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/configs.py +5 -1
  7. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/.gitignore +0 -0
  8. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/LICENSE +0 -0
  9. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/README.md +0 -0
  10. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/pyproject.toml +0 -0
  11. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/scripts/build.py +0 -0
  12. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/_nb_magic.py +0 -0
  13. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/__init__.py +0 -0
  14. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/account.py +0 -0
  15. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/broker.py +0 -0
  16. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/data.py +0 -0
  17. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/iteratedstream.py +0 -0
  18. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/management.py +0 -0
  19. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/ome.py +0 -0
  20. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/optimization.py +0 -0
  21. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/runner.py +0 -0
  22. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/sentinels.py +0 -0
  23. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/simulated_data.py +0 -0
  24. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/simulated_exchange.py +0 -0
  25. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/simulator.py +0 -0
  26. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/transfers.py +0 -0
  27. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/utils.py +0 -0
  28. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/__init__.py +0 -0
  29. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/commands.py +0 -0
  30. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/deploy.py +0 -0
  31. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/misc.py +0 -0
  32. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/theme.py +0 -0
  33. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/tui.py +0 -0
  34. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/config.py +0 -0
  35. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/__init__.py +0 -0
  36. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/__init__.py +0 -0
  37. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/account.py +0 -0
  38. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  39. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  40. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/broker.py +0 -0
  41. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  42. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/data.py +0 -0
  43. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  44. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  45. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  46. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  47. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  48. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  49. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  50. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  51. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
  52. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
  53. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  54. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  55. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  56. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  57. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  58. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
  59. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
  60. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
  61. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
  62. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/factory.py +0 -0
  63. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  64. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  65. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  66. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  67. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  68. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  69. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  70. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  71. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  72. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  73. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  74. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  75. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  76. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/utils.py +0 -0
  77. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  78. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/registry.py +0 -0
  79. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/tardis/data.py +0 -0
  80. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/tardis/utils.py +0 -0
  81. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/__init__.py +0 -0
  82. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/account.py +0 -0
  83. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/basics.py +0 -0
  84. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/context.py +0 -0
  85. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/detectors/__init__.py +0 -0
  86. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/detectors/delisting.py +0 -0
  87. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/detectors/stale.py +0 -0
  88. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/errors.py +0 -0
  89. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/exceptions.py +0 -0
  90. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/helpers.py +0 -0
  91. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/initializer.py +0 -0
  92. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/interfaces.py +0 -0
  93. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/loggers.py +0 -0
  94. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/lookups.py +0 -0
  95. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/metrics.py +0 -0
  96. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/__init__.py +0 -0
  97. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/market.py +0 -0
  98. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/processing.py +0 -0
  99. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/subscription.py +0 -0
  100. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/trading.py +0 -0
  101. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/universe.py +0 -0
  102. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/utils.py +0 -0
  103. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/series.pxd +0 -0
  104. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/series.pyi +0 -0
  105. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/series.pyx +0 -0
  106. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/utils.pyi +0 -0
  107. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/utils.pyx +0 -0
  108. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/__init__.py +0 -0
  109. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/cache.py +0 -0
  110. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/containers.py +0 -0
  111. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/guards.py +0 -0
  112. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/registry.py +0 -0
  113. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storage.py +0 -0
  114. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/ccxt.py +0 -0
  115. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/csv.py +0 -0
  116. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/handy.py +0 -0
  117. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/multi.py +0 -0
  118. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/questdb.py +0 -0
  119. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/stub.py +0 -0
  120. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/utils.py +0 -0
  121. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/transformers.py +0 -0
  122. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/__init__.py +0 -0
  123. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/base.py +0 -0
  124. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/composite.py +0 -0
  125. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/csv.py +0 -0
  126. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/indicator.py +0 -0
  127. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/inmemory.py +0 -0
  128. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/prometheus.py +0 -0
  129. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/questdb.py +0 -0
  130. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/__init__.py +0 -0
  131. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/composite.py +0 -0
  132. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/__init__.py +0 -0
  133. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/base.py +0 -0
  134. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/incremental.py +0 -0
  135. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/slack.py +0 -0
  136. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/target_position.py +0 -0
  137. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/redis_streams.py +0 -0
  138. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/slack.py +0 -0
  139. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/gathering/simplest.py +0 -0
  140. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/health/__init__.py +0 -0
  141. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/health/base.py +0 -0
  142. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/health/dummy.py +0 -0
  143. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/health/server.py +0 -0
  144. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/__init__.py +0 -0
  145. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/csv.py +0 -0
  146. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/factory.py +0 -0
  147. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/inmemory.py +0 -0
  148. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/mongo.py +0 -0
  149. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/notifications/__init__.py +0 -0
  150. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/notifications/composite.py +0 -0
  151. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/notifications/slack.py +0 -0
  152. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/notifications/throttler.py +0 -0
  153. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/pandaz/__init__.py +0 -0
  154. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/pandaz/stats.py +0 -0
  155. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/pandaz/ta.py +0 -0
  156. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/pandaz/utils.py +0 -0
  157. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/plugins/__init__.py +0 -0
  158. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/plugins/loader.py +0 -0
  159. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/_build.py +0 -0
  160. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/crypto-fees.ini +0 -0
  161. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  162. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  163. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  164. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  165. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  166. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  167. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  168. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  169. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  170. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  171. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  172. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restarts/__init__.py +0 -0
  173. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restarts/state_resolvers.py +0 -0
  174. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restarts/time_finders.py +0 -0
  175. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/__init__.py +0 -0
  176. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/balance.py +0 -0
  177. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/factory.py +0 -0
  178. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/interfaces.py +0 -0
  179. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/position.py +0 -0
  180. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/signal.py +0 -0
  181. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/state.py +0 -0
  182. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/utils.py +0 -0
  183. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/state/__init__.py +0 -0
  184. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/state/dummy.py +0 -0
  185. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/state/redis.py +0 -0
  186. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/ta/__init__.py +0 -0
  187. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/ta/indicators.pxd +0 -0
  188. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/ta/indicators.pyi +0 -0
  189. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/ta/indicators.pyx +0 -0
  190. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/__init__.py +0 -0
  191. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/base.py +0 -0
  192. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  193. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/config.yml.j2 +0 -0
  194. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  195. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  196. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  197. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  198. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  199. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/template.yml +0 -0
  200. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  201. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  202. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/config.yml.j2 +0 -0
  203. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  204. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  205. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  206. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/template.yml +0 -0
  207. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/__init__.py +0 -0
  208. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/advanced.py +0 -0
  209. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/composite.py +0 -0
  210. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/rebalancers.py +0 -0
  211. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/riskctrl.py +0 -0
  212. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/sizers.py +0 -0
  213. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/__init__.py +0 -0
  214. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/_pyxreloader.py +0 -0
  215. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/charting/lookinglass.py +0 -0
  216. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  217. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/charting/orderbook.py +0 -0
  218. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/collections.py +0 -0
  219. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/hft/__init__.py +0 -0
  220. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/hft/numba_utils.py +0 -0
  221. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/hft/orderbook.pyi +0 -0
  222. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/hft/orderbook.pyx +0 -0
  223. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/marketdata/binance.py +0 -0
  224. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/marketdata/ccxt.py +0 -0
  225. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/marketdata/dukas.py +0 -0
  226. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/misc.py +0 -0
  227. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/nonce.py +0 -0
  228. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/ntp.py +0 -0
  229. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/numbers_utils.py +0 -0
  230. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/orderbook.py +0 -0
  231. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/__init__.py +0 -0
  232. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/dashboard.py +0 -0
  233. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/data.py +0 -0
  234. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/interfaces.py +0 -0
  235. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  236. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  237. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/questdb.py +0 -0
  238. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/rate_limiter.py +0 -0
  239. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/results.py +0 -0
  240. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/ringbuffer.pxd +0 -0
  241. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/ringbuffer.pyi +0 -0
  242. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/ringbuffer.pyx +0 -0
  243. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/__init__.py +0 -0
  244. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  245. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/accounts.py +0 -0
  246. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/factory.py +0 -0
  247. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/kernel_service.py +0 -0
  248. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/runner.py +0 -0
  249. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/__init__.py +0 -0
  250. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/app.py +0 -0
  251. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/handlers.py +0 -0
  252. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/init_code.py +0 -0
  253. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/kernel.py +0 -0
  254. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  255. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  256. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  257. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  258. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  259. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  260. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  261. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  262. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/slack.py +0 -0
  263. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/throttler.py +0 -0
  264. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/time.py +0 -0
  265. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/websocket_manager.py +0 -0
  266. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  267. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
  268. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  269. {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Qubx
3
- Version: 1.0.6.dev1
3
+ Version: 1.0.6.dev3
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
@@ -178,7 +178,10 @@ if runtime_env() in ["notebook", "shell"]:
178
178
  from IPython.core.getipython import get_ipython
179
179
  from IPython.core.magic import Magics, line_cell_magic, line_magic, magics_class
180
180
 
181
- from qubx.utils.charting.mpl_helpers import set_mpl_theme
181
+ try:
182
+ from qubx.utils.charting.mpl_helpers import set_mpl_theme
183
+ except ImportError:
184
+ set_mpl_theme = None # matplotlib not available (e.g. deployed strategy)
182
185
 
183
186
  @magics_class
184
187
  class QubxMagics(Magics):
@@ -219,7 +222,7 @@ if runtime_env() in ["notebook", "shell"]:
219
222
  exec(s, self.shell.user_ns)
220
223
 
221
224
  # setup more funcy mpl theme instead of ugly default
222
- if line:
225
+ if line and set_mpl_theme is not None:
223
226
  if "dark" in line.lower():
224
227
  set_mpl_theme("dark")
225
228
  # - temporary workaround for vscode - dark theme not applying to ipywidgets in notebook
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.0.6.dev1'
32
- __version_tuple__ = version_tuple = (1, 0, 6, 'dev1')
31
+ __version__ = version = '1.0.6.dev3'
32
+ __version_tuple__ = version_tuple = (1, 0, 6, 'dev3')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -411,8 +411,8 @@ def load_strategy_from_config(config_path: Path, directory: str) -> StrategyInfo
411
411
  StrategyInfo object
412
412
  """
413
413
  try:
414
- # Load the config using the utility function
415
- strategy_config = load_strategy_config_from_yaml(os.path.expanduser(config_path))
414
+ # Load the config without resolving env vars (not needed for release)
415
+ strategy_config = load_strategy_config_from_yaml(os.path.expanduser(config_path), resolve_env=False)
416
416
 
417
417
  # Extract strategy name from config
418
418
  strategy_class_names = strategy_config.strategy
@@ -537,6 +537,40 @@ def release_strategy(
537
537
  logger.error(f"<r>Error releasing strategy: {str(e).replace('<', chr(92) + '<')}</r>")
538
538
 
539
539
 
540
+ def _find_source_root(pyproject_root: str, project_name: str) -> str | None:
541
+ """Find the source package directory for a project.
542
+
543
+ Searches for src-layout first (e.g. src/xincubator), then flat layout (e.g. xincubator/).
544
+
545
+ Args:
546
+ pyproject_root: Root directory containing pyproject.toml
547
+ project_name: Project name (e.g. "xincubator")
548
+
549
+ Returns:
550
+ Absolute path to the source package directory, or None if not found.
551
+ """
552
+ src_dir_name = project_name.replace("-", "_")
553
+
554
+ # Try src layout first
555
+ candidate = os.path.join(pyproject_root, "src", src_dir_name)
556
+ if os.path.isdir(candidate):
557
+ return candidate
558
+
559
+ # Also walk to find nested src/ dirs
560
+ for root, dirs, _files in os.walk(pyproject_root):
561
+ if src_dir_name in dirs:
562
+ c = os.path.join(root, src_dir_name)
563
+ if os.path.sep + "src" + os.path.sep in c:
564
+ return c
565
+
566
+ # Try flat layout
567
+ candidate = os.path.join(pyproject_root, src_dir_name)
568
+ if os.path.isdir(candidate):
569
+ return candidate
570
+
571
+ return None
572
+
573
+
540
574
  def _collect_all_imports(strategy_files: list[str], project_root: str) -> set[str]:
541
575
  """
542
576
  Collect all top-level import module names from strategy source files.
@@ -597,8 +631,9 @@ def _parse_uv_lock(uv_lock_path: str) -> dict[str, str]:
597
631
  def _scan_strategy_deps(
598
632
  strategy_files: list[str],
599
633
  pyproject_root: str,
600
- uv_lock_path: str,
634
+ lock_versions: dict[str, str],
601
635
  pyproject_data: dict,
636
+ external_imports: set[str] | None = None,
602
637
  ) -> list[str]:
603
638
  """
604
639
  Scan strategy source files for external imports, map to packages, pin versions from lock.
@@ -612,21 +647,23 @@ def _scan_strategy_deps(
612
647
  Args:
613
648
  strategy_files: List of absolute paths to strategy .py files
614
649
  pyproject_root: Root directory containing pyproject.toml
615
- uv_lock_path: Path to uv.lock for version pinning
650
+ lock_versions: Pre-parsed {normalized_name: version} from uv.lock
616
651
  pyproject_data: Parsed pyproject.toml dict
652
+ external_imports: Pre-computed set of external top-level import names.
653
+ If provided, skips internal import collection.
617
654
 
618
655
  Returns:
619
656
  List of pinned dependency specs like ["cachetools==6.2.5", "Qubx==1.0.1.dev1"]
620
657
  """
621
658
  import importlib.metadata
622
659
 
623
- # Step 1: collect all imports from strategy files
624
- strategy_imports = _collect_all_imports(strategy_files, pyproject_root)
660
+ # Step 1: collect all imports from strategy files (or use pre-computed)
661
+ if external_imports is not None:
662
+ strategy_imports = external_imports
663
+ else:
664
+ strategy_imports = _collect_all_imports(strategy_files, pyproject_root)
625
665
  logger.info(f"Strategy imports (top-level modules): {sorted(strategy_imports)}")
626
666
 
627
- # Step 2: parse uv.lock for version pinning
628
- lock_versions = _parse_uv_lock(uv_lock_path)
629
-
630
667
  # Step 3: gather all declared deps (regular + optional)
631
668
  all_deps: list[str] = list(pyproject_data.get("project", {}).get("dependencies", []))
632
669
  for group_deps in pyproject_data.get("project", {}).get("optional-dependencies", {}).values():
@@ -686,12 +723,13 @@ def _build_strategy_wheel(
686
723
  stg_info: StrategyInfo,
687
724
  scanned_deps: list[str],
688
725
  release_dir: str,
726
+ internal_files: set[str] | None = None,
689
727
  ) -> str | None:
690
728
  """
691
729
  Build a compiled wheel for the strategy in a temp directory.
692
730
 
693
731
  1. Create temp dir
694
- 2. Copy strategy source files (same files as today's _copy_strategy_file/_copy_dependencies)
732
+ 2. Copy strategy source files (resolved set or full package as fallback)
695
733
  3. Generate pyproject.toml with only scanned deps + source project's build system
696
734
  4. Copy build.py for Cython compilation
697
735
  5. Run `uv build --wheel .` from temp dir
@@ -702,6 +740,8 @@ def _build_strategy_wheel(
702
740
  stg_info: Strategy information with class locations
703
741
  scanned_deps: List of pinned dependency specs for the strategy
704
742
  release_dir: Release output directory
743
+ internal_files: Pre-resolved set of internal file paths to include.
744
+ If provided, only these files are copied instead of the entire package.
705
745
 
706
746
  Returns:
707
747
  Wheel filename (e.g. "xincubator-0.3.0-cp312-linux_x86_64.whl") or None on failure
@@ -723,25 +763,9 @@ def _build_strategy_wheel(
723
763
  project_name = src_pyproject.get("project", {}).get("name", os.path.basename(pyproject_root))
724
764
  project_version = src_pyproject.get("project", {}).get("version", "0.1.0")
725
765
 
726
- # Find the source package directory (e.g. src/xincubator or xincubator)
766
+ # Find the source package directory
727
767
  _src_dir_name = project_name.replace("-", "_")
728
- _src_root = None
729
- for root, dirs, files in os.walk(pyproject_root):
730
- if _src_dir_name in dirs:
731
- candidate = os.path.join(root, _src_dir_name)
732
- if os.path.sep + "src" + os.path.sep in candidate:
733
- _src_root = candidate
734
- break
735
- if _src_root is None:
736
- # Try non-src layout
737
- candidate = os.path.join(pyproject_root, _src_dir_name)
738
- if os.path.exists(candidate):
739
- _src_root = candidate
740
- else:
741
- # Check under src/
742
- candidate = os.path.join(pyproject_root, "src", _src_dir_name)
743
- if os.path.exists(candidate):
744
- _src_root = candidate
768
+ _src_root = _find_source_root(pyproject_root, project_name)
745
769
 
746
770
  if _src_root is None:
747
771
  logger.error(f"Could not find source package directory for {_src_dir_name}")
@@ -759,13 +783,26 @@ def _build_strategy_wheel(
759
783
  else:
760
784
  dest_pkg_dir = os.path.join(tmp_dir, _src_dir_name)
761
785
 
762
- # Copy the entire source package (so all internal imports resolve)
763
- logger.info(f"Copying source package from {_src_root} to {dest_pkg_dir}")
764
- shutil.copytree(
765
- _src_root,
766
- dest_pkg_dir,
767
- ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".git", "*.egg-info"),
768
- )
786
+ if internal_files is not None:
787
+ # Selective copy: only resolved internal files
788
+ logger.info(f"Copying {len(internal_files)} resolved source files to {dest_pkg_dir}")
789
+ _src_root_norm = os.path.normpath(_src_root)
790
+ for fpath in internal_files:
791
+ fpath_norm = os.path.normpath(fpath)
792
+ if not fpath_norm.startswith(_src_root_norm):
793
+ continue
794
+ rel = os.path.relpath(fpath_norm, _src_root_norm)
795
+ dest = os.path.join(dest_pkg_dir, rel)
796
+ os.makedirs(os.path.dirname(dest), exist_ok=True)
797
+ shutil.copy2(fpath_norm, dest)
798
+ else:
799
+ # Fallback: copy the entire source package
800
+ logger.info(f"Copying source package from {_src_root} to {dest_pkg_dir}")
801
+ shutil.copytree(
802
+ _src_root,
803
+ dest_pkg_dir,
804
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".git", "*.egg-info"),
805
+ )
769
806
 
770
807
  # Generate pyproject.toml for the wheel build
771
808
  wheel_pyproject = {
@@ -866,7 +903,8 @@ def _generate_release_pyproject(
866
903
  pkg_name = parts[0].replace("_", "-")
867
904
  pkg_version = parts[1]
868
905
  deps.append(f"{pkg_name}=={pkg_version}")
869
- elif external_deps:
906
+
907
+ if external_deps:
870
908
  deps.extend(external_deps)
871
909
 
872
910
  if plugin_deps:
@@ -943,64 +981,105 @@ def create_released_pack(
943
981
  with open(pyproject_src) as f:
944
982
  pyproject_data = toml.load(f)
945
983
 
984
+ # Parse uv.lock once as the single source of truth for versions.
985
+ # If missing, generate it first.
946
986
  uv_lock_path = os.path.join(pyproject_root, "uv.lock")
987
+ if not os.path.exists(uv_lock_path):
988
+ logger.info("uv.lock not found in source project, generating...")
989
+ _generate_lock_file(pyproject_root)
990
+ lock_versions = _parse_uv_lock(uv_lock_path)
947
991
 
948
- # --- Step 1: Scan strategy deps ---
992
+ # --- Step 1: Resolve internal files and scan strategy deps ---
949
993
  strategy_files = [sc.path for sc in stg_info.classes] if has_strategy_code else []
950
- scanned_deps = _scan_strategy_deps(strategy_files, pyproject_root, uv_lock_path, pyproject_data)
994
+ internal_files: set[str] | None = None
995
+ external_imports: set[str] | None = None
996
+
997
+ if has_strategy_code:
998
+ project_name = pyproject_data.get("project", {}).get("name", os.path.basename(pyproject_root))
999
+ src_root = _find_source_root(pyproject_root, project_name)
1000
+ if src_root:
1001
+ from .resolver import ModuleResolver
1002
+
1003
+ package_name = project_name.replace("-", "_")
1004
+ resolver = ModuleResolver(
1005
+ package_root=src_root,
1006
+ project_root=pyproject_root,
1007
+ package_name=package_name,
1008
+ )
1009
+ internal_files, external_imports = resolver.resolve(strategy_files)
1010
+ logger.info(f"Resolved {len(internal_files)} internal files, {len(external_imports)} external imports")
1011
+
1012
+ scanned_deps = _scan_strategy_deps(
1013
+ strategy_files, pyproject_root, lock_versions, pyproject_data,
1014
+ external_imports=external_imports,
1015
+ )
951
1016
 
952
1017
  # --- Step 2: Build strategy wheel (if custom code) ---
953
1018
  strategy_wheel_name: str | None = None
954
1019
  if has_strategy_code:
955
- strategy_wheel_name = _build_strategy_wheel(pyproject_root, stg_info, scanned_deps, release_dir)
1020
+ strategy_wheel_name = _build_strategy_wheel(
1021
+ pyproject_root, stg_info, scanned_deps, release_dir,
1022
+ internal_files=internal_files,
1023
+ )
956
1024
  if not strategy_wheel_name:
957
1025
  raise RuntimeError("Failed to build strategy wheel")
958
1026
 
959
- # --- Step 3: Bundle private/local dependency wheels ---
960
- # Build required_packages set from scanned deps
961
- required_packages: set[str] = set()
962
- for dep in scanned_deps:
963
- name = dep.split(">=")[0].split("==")[0].split("<")[0].strip().lower()
964
- required_packages.add(name)
965
-
966
- # Detect plugin deps
1027
+ # --- Step 3: Detect plugin deps ---
967
1028
  plugin_deps: list[str] = []
968
1029
  if stg_info.config:
969
- plugin_deps = _get_plugin_deps(stg_info.config, pyproject_data)
1030
+ plugin_deps = _get_plugin_deps(stg_info.config, pyproject_data, lock_versions)
970
1031
  if plugin_deps:
971
1032
  logger.info(f"Plugin dependencies from config: {plugin_deps}")
972
- for pdep in plugin_deps:
973
- name = pdep.split(">=")[0].split("==")[0].split("<")[0].strip().lower()
974
- required_packages.add(name)
1033
+
1034
+ # --- Step 4: Detect external strategy package deps ---
1035
+ # Always check for external strategy packages (e.g. quantkit.universe.basics.TopNUniverse)
1036
+ # These need to be included as dependencies whether or not there's also local strategy code.
1037
+ current_package = get_project_package_name(pyproject_root)
1038
+ ext_pkgs = extract_external_dependencies(stg_info.config, current_package)
1039
+ external_deps: list[str] | None = None
1040
+ if ext_pkgs:
1041
+ external_deps = []
1042
+ for pkg in ext_pkgs:
1043
+ pkg_norm = pkg.lower().replace("-", "_")
1044
+ ver = lock_versions.get(pkg_norm)
1045
+ if ver:
1046
+ external_deps.append(f"{pkg}=={ver}")
1047
+ else:
1048
+ logger.warning(f" {pkg} not found in uv.lock, using unversioned spec")
1049
+ external_deps.append(pkg)
1050
+ logger.info(f"External strategy packages: {external_deps}")
1051
+
1052
+ # --- Step 5: Bundle private/local dependency wheels ---
1053
+ # Build required_packages from scanned deps + external deps + plugin deps
1054
+ required_packages: set[str] = set()
1055
+ for dep in scanned_deps:
1056
+ name = dep.split(">=")[0].split("==")[0].split("<")[0].strip().lower()
1057
+ required_packages.add(name)
1058
+ if external_deps:
1059
+ for dep in external_deps:
1060
+ name = dep.split(">=")[0].split("==")[0].split("<")[0].strip().lower()
1061
+ required_packages.add(name)
1062
+ for pdep in plugin_deps:
1063
+ name = pdep.split(">=")[0].split("==")[0].split("<")[0].strip().lower()
1064
+ required_packages.add(name)
975
1065
 
976
1066
  bundled_packages: list[str] = []
977
1067
  if required_packages:
978
1068
  logger.info("Resolving private/local source dependencies...")
979
- bundled_packages = _bundle_source_overrides(pyproject_data, pyproject_root, release_dir, required_packages)
1069
+ bundled_packages = _bundle_source_overrides(
1070
+ pyproject_data, pyproject_root, release_dir, required_packages, lock_versions,
1071
+ )
980
1072
  if bundled_packages:
981
1073
  logger.info(f"Bundled {len(bundled_packages)} package(s): {', '.join(bundled_packages)}")
982
1074
 
983
- # --- Step 4: Generate release pyproject.toml ---
984
- # For external-deps-only configs, build the external deps list
985
- external_deps: list[str] | None = None
986
- if not has_strategy_code:
987
- current_package = get_project_package_name(pyproject_root)
988
- ext_pkgs = extract_external_dependencies(stg_info.config, current_package)
989
- if ext_pkgs:
990
- from importlib.metadata import version as get_version
991
-
992
- external_deps = []
993
- lock_versions = _parse_uv_lock(uv_lock_path)
994
- for pkg in ext_pkgs:
995
- pkg_norm = pkg.lower().replace("-", "_")
996
- ver = lock_versions.get(pkg_norm)
997
- if ver:
998
- external_deps.append(f"{pkg}=={ver}")
999
- else:
1000
- try:
1001
- external_deps.append(f"{pkg}>={get_version(pkg)}")
1002
- except Exception:
1003
- external_deps.append(pkg)
1075
+ # Log all wheels included in the release
1076
+ wheels_dir = os.path.join(release_dir, "wheels")
1077
+ if os.path.isdir(wheels_dir):
1078
+ wheel_files = [f for f in os.listdir(wheels_dir) if f.endswith(".whl")]
1079
+ if wheel_files:
1080
+ logger.info(f"Wheels included in release ({len(wheel_files)}):")
1081
+ for whl in sorted(wheel_files):
1082
+ logger.info(f" {whl}")
1004
1083
 
1005
1084
  _generate_release_pyproject(
1006
1085
  release_dir=release_dir,
@@ -1098,18 +1177,19 @@ def _bundle_source_overrides(
1098
1177
  pyproject_root: str,
1099
1178
  release_dir: str,
1100
1179
  required_packages: set[str],
1180
+ lock_versions: dict[str, str],
1101
1181
  ) -> list[str]:
1102
1182
  """
1103
1183
  For each [tool.uv.sources] entry that is required by this release:
1104
- - path source + installed version on public PyPI → skip (will resolve from PyPI)
1184
+ - path source + version on public PyPI → skip (will resolve from PyPI)
1105
1185
  - path source + NOT on public PyPI → build wheel from local path and bundle in wheels/
1106
1186
  - index source (private registry) → download wheel from that index and bundle in wheels/
1107
1187
 
1188
+ Uses uv.lock as the single source of truth for package versions.
1189
+
1108
1190
  Returns list of bundled package names (lowercase).
1109
1191
  """
1110
1192
  import subprocess
1111
- from importlib.metadata import PackageNotFoundError
1112
- from importlib.metadata import version as get_version
1113
1193
 
1114
1194
  sources = pyproject_data.get("tool", {}).get("uv", {}).get("sources", {})
1115
1195
  if not sources:
@@ -1122,32 +1202,29 @@ def _bundle_source_overrides(
1122
1202
  if pkg_name.lower() not in required_packages:
1123
1203
  continue
1124
1204
 
1205
+ pkg_norm = pkg_name.lower().replace("-", "_")
1206
+ pkg_ver = lock_versions.get(pkg_norm)
1207
+ if not pkg_ver:
1208
+ logger.warning(f" {pkg_name} not found in uv.lock, skipping bundle")
1209
+ continue
1210
+
1125
1211
  if "path" in source:
1126
1212
  local_path = os.path.normpath(os.path.join(pyproject_root, source["path"]))
1127
1213
 
1128
- try:
1129
- installed_ver = get_version(pkg_name)
1130
- except PackageNotFoundError:
1131
- logger.warning(f" {pkg_name} not installed, skipping bundle")
1132
- continue
1133
-
1134
- if _version_exists_on_pypi(pkg_name, installed_ver):
1135
- logger.info(f" {pkg_name}=={installed_ver} found on public PyPI, will resolve from registry")
1214
+ if _version_exists_on_pypi(pkg_name, pkg_ver):
1215
+ logger.info(f" {pkg_name}=={pkg_ver} found on public PyPI, will resolve from registry")
1136
1216
  continue
1137
1217
 
1138
- logger.info(f" Bundling {pkg_name}=={installed_ver} from local path {local_path} ...")
1218
+ logger.info(f" Bundling {pkg_name}=={pkg_ver} from local path {local_path} ...")
1139
1219
  os.makedirs(wheels_dir, exist_ok=True)
1140
1220
  try:
1141
- # Run from the package's own directory so uv reads its [tool.uv.sources]
1142
- # (needed when the package has local-path build deps like qubx)
1143
1221
  subprocess.run(
1144
1222
  ["uv", "build", "--wheel", ".", "--out-dir", wheels_dir],
1145
1223
  cwd=local_path,
1146
1224
  check=True, capture_output=True, text=True,
1147
1225
  )
1148
- # Warn if the wheel is platform-specific (compiled extensions)
1149
1226
  for whl in os.listdir(wheels_dir):
1150
- if whl.lower().startswith(pkg_name.lower().replace("-", "_")) and "none-any" not in whl:
1227
+ if whl.lower().startswith(pkg_norm) and "none-any" not in whl:
1151
1228
  logger.warning(
1152
1229
  f" {whl} is platform-specific. "
1153
1230
  "Ensure the container architecture matches the build machine."
@@ -1165,35 +1242,20 @@ def _bundle_source_overrides(
1165
1242
  logger.warning(f" Index '{index_name}' not found in [[tool.uv.index]] (needed for {pkg_name})")
1166
1243
  continue
1167
1244
 
1168
- try:
1169
- installed_ver = get_version(pkg_name)
1170
- except PackageNotFoundError:
1171
- # Package not installed (e.g. optional dep not synced) — fall back to
1172
- # the version declared in pyproject optional-deps or regular deps
1173
- installed_ver = _find_version_in_pyproject(pkg_name, pyproject_data)
1174
- if not installed_ver:
1175
- logger.warning(
1176
- f" {pkg_name} not installed and no version found in pyproject deps, skipping bundle"
1177
- )
1178
- continue
1179
- logger.info(f" {pkg_name} not installed; using declared version {installed_ver} from pyproject")
1180
-
1181
- if _version_exists_on_pypi(pkg_name, installed_ver):
1182
- logger.info(f" {pkg_name}=={installed_ver} found on public PyPI, will resolve from registry")
1245
+ if _version_exists_on_pypi(pkg_name, pkg_ver):
1246
+ logger.info(f" {pkg_name}=={pkg_ver} found on public PyPI, will resolve from registry")
1183
1247
  continue
1184
1248
 
1185
- logger.info(f" Downloading {pkg_name}=={installed_ver} from private index '{index_name}' ...")
1249
+ logger.info(f" Downloading {pkg_name}=={pkg_ver} from private index '{index_name}' ...")
1186
1250
  os.makedirs(wheels_dir, exist_ok=True)
1187
1251
  try:
1188
- import shutil
1189
-
1190
1252
  pip_exe = shutil.which("pip") or shutil.which("pip3")
1191
1253
  if not pip_exe:
1192
1254
  raise RuntimeError("pip not found — cannot download wheel from private index")
1193
1255
  subprocess.run(
1194
1256
  [
1195
1257
  pip_exe, "download",
1196
- f"{pkg_name}=={installed_ver}",
1258
+ f"{pkg_name}=={pkg_ver}",
1197
1259
  "--index-url", index_url,
1198
1260
  "--dest", wheels_dir,
1199
1261
  "--no-deps",
@@ -1202,7 +1264,7 @@ def _bundle_source_overrides(
1202
1264
  check=True, capture_output=True, text=True,
1203
1265
  )
1204
1266
  bundled.append(pkg_name.lower())
1205
- logger.info(f" Downloaded {pkg_name}=={installed_ver}")
1267
+ logger.info(f" Downloaded {pkg_name}=={pkg_ver}")
1206
1268
  except subprocess.CalledProcessError as e:
1207
1269
  err_msg = (e.stderr or str(e)).replace("<", r"\<")
1208
1270
  logger.warning(f" Failed to download wheel for {pkg_name}: {err_msg}")
@@ -1210,19 +1272,18 @@ def _bundle_source_overrides(
1210
1272
  return bundled
1211
1273
 
1212
1274
 
1213
- def _get_plugin_deps(stg_config: "StrategyConfig", pyproject_data: dict) -> list[str]:
1275
+ def _get_plugin_deps(
1276
+ stg_config: "StrategyConfig", pyproject_data: dict, lock_versions: dict[str, str],
1277
+ ) -> list[str]:
1214
1278
  """
1215
1279
  Extract package dependency specs for plugin modules listed in the strategy config.
1216
1280
 
1217
1281
  Maps plugin module names (e.g. qubx_lighter) to package specs
1218
1282
  (e.g. qubx-lighter==0.1.0) by looking in [project.optional-dependencies].
1219
- Falls back to the installed version if not found there.
1283
+ Falls back to uv.lock version if not found there.
1220
1284
 
1221
1285
  Returns list of dep specs like ["qubx-lighter==0.1.0"].
1222
1286
  """
1223
- from importlib.metadata import PackageNotFoundError
1224
- from importlib.metadata import version as get_version
1225
-
1226
1287
  if not stg_config.plugins or not stg_config.plugins.modules:
1227
1288
  return []
1228
1289
 
@@ -1246,18 +1307,19 @@ def _get_plugin_deps(stg_config: "StrategyConfig", pyproject_data: dict) -> list
1246
1307
  break
1247
1308
 
1248
1309
  if not found:
1249
- try:
1250
- ver = get_version(pkg_name)
1251
- spec = f"{pkg_name}>={ver}"
1310
+ pkg_norm = pkg_name.lower().replace("-", "_")
1311
+ ver = lock_versions.get(pkg_norm)
1312
+ if ver:
1313
+ spec = f"{pkg_name}=={ver}"
1252
1314
  plugin_deps.append(spec)
1253
1315
  logger.warning(
1254
1316
  f" Plugin '{module_name}' not in optional-deps; "
1255
- f"adding '{spec}' from installed packages"
1317
+ f"adding '{spec}' from uv.lock"
1256
1318
  )
1257
- except PackageNotFoundError:
1319
+ else:
1258
1320
  logger.warning(
1259
1321
  f" Plugin '{module_name}' ({pkg_name}) not found in "
1260
- "optional-deps or installed packages - skipping"
1322
+ "optional-deps or uv.lock - skipping"
1261
1323
  )
1262
1324
 
1263
1325
  return plugin_deps