Qubx 1.1.3.dev17__tar.gz → 1.1.3.dev18__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 (309) hide show
  1. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/PKG-INFO +1 -1
  2. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/_version.py +2 -2
  3. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/commands.py +41 -0
  4. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/release.py +213 -114
  5. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/configs.py +37 -0
  6. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/.gitignore +0 -0
  7. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/LICENSE +0 -0
  8. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/README.md +0 -0
  9. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/pyproject.toml +0 -0
  10. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/scripts/build.py +0 -0
  11. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/__init__.py +0 -0
  12. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/_nb_magic.py +0 -0
  13. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/__init__.py +0 -0
  14. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/account.py +0 -0
  15. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/broker.py +0 -0
  16. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/data.py +0 -0
  17. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/iteratedstream.py +0 -0
  18. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/management.py +0 -0
  19. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/ome.py +0 -0
  20. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/optimization.py +0 -0
  21. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/runner.py +0 -0
  22. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/sentinels.py +0 -0
  23. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/simulated_data.py +0 -0
  24. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/simulated_exchange.py +0 -0
  25. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/simulator.py +0 -0
  26. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/transfers.py +0 -0
  27. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/backtester/utils.py +0 -0
  28. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/__init__.py +0 -0
  29. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/deploy.py +0 -0
  30. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/misc.py +0 -0
  31. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/resolver.py +0 -0
  32. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/s3.py +0 -0
  33. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/theme.py +0 -0
  34. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/tui.py +0 -0
  35. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/cli/user_config.py +0 -0
  36. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/config.py +0 -0
  37. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/__init__.py +0 -0
  38. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/__init__.py +0 -0
  39. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/account.py +0 -0
  40. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  41. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  42. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/broker.py +0 -0
  43. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  44. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/data.py +0 -0
  45. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  46. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  47. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  48. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  49. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  50. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  51. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  52. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  53. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
  54. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
  55. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  56. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  57. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  58. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  59. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  60. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
  61. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
  62. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
  63. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
  64. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/factory.py +0 -0
  65. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  66. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  67. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  68. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  69. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  70. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  71. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  72. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  73. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  74. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  75. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/rate_limits.py +0 -0
  76. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  77. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  78. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  79. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/utils.py +0 -0
  80. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  81. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/registry.py +0 -0
  82. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/tardis/data.py +0 -0
  83. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/connectors/tardis/utils.py +0 -0
  84. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/control/__init__.py +0 -0
  85. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/control/builtin.py +0 -0
  86. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/control/decorator.py +0 -0
  87. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/control/executor.py +0 -0
  88. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/control/interfaces.py +0 -0
  89. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/control/server.py +0 -0
  90. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/control/types.py +0 -0
  91. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/__init__.py +0 -0
  92. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/account.py +0 -0
  93. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/basics.py +0 -0
  94. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/context.py +0 -0
  95. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/detectors/__init__.py +0 -0
  96. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/detectors/delisting.py +0 -0
  97. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/detectors/stale.py +0 -0
  98. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/errors.py +0 -0
  99. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/exceptions.py +0 -0
  100. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/helpers.py +0 -0
  101. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/initializer.py +0 -0
  102. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/interfaces.py +0 -0
  103. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/loggers.py +0 -0
  104. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/lookups.py +0 -0
  105. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/metrics.py +0 -0
  106. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/mixins/__init__.py +0 -0
  107. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/mixins/market.py +0 -0
  108. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/mixins/processing.py +0 -0
  109. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/mixins/subscription.py +0 -0
  110. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/mixins/trading.py +0 -0
  111. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/mixins/universe.py +0 -0
  112. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/mixins/utils.py +0 -0
  113. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/series.pxd +0 -0
  114. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/series.pyi +0 -0
  115. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/series.pyx +0 -0
  116. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/utils.pyi +0 -0
  117. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/core/utils.pyx +0 -0
  118. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/__init__.py +0 -0
  119. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/cache.py +0 -0
  120. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/containers.py +0 -0
  121. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/guards.py +0 -0
  122. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/registry.py +0 -0
  123. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/storage.py +0 -0
  124. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/storages/ccxt.py +0 -0
  125. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/storages/csv.py +0 -0
  126. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/storages/handy.py +0 -0
  127. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/storages/multi.py +0 -0
  128. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/storages/questdb.py +0 -0
  129. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/storages/stub.py +0 -0
  130. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/storages/utils.py +0 -0
  131. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/data/transformers.py +0 -0
  132. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/emitters/__init__.py +0 -0
  133. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/emitters/base.py +0 -0
  134. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/emitters/composite.py +0 -0
  135. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/emitters/csv.py +0 -0
  136. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/emitters/indicator.py +0 -0
  137. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/emitters/inmemory.py +0 -0
  138. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/emitters/prometheus.py +0 -0
  139. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/emitters/questdb.py +0 -0
  140. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/__init__.py +0 -0
  141. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/composite.py +0 -0
  142. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/formatters/__init__.py +0 -0
  143. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/formatters/base.py +0 -0
  144. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/formatters/incremental.py +0 -0
  145. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/formatters/slack.py +0 -0
  146. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/formatters/target_position.py +0 -0
  147. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/redis_streams.py +0 -0
  148. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/exporters/slack.py +0 -0
  149. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/gathering/simplest.py +0 -0
  150. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/health/__init__.py +0 -0
  151. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/health/base.py +0 -0
  152. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/health/dummy.py +0 -0
  153. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/loggers/__init__.py +0 -0
  154. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/loggers/csv.py +0 -0
  155. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/loggers/factory.py +0 -0
  156. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/loggers/inmemory.py +0 -0
  157. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/loggers/mongo.py +0 -0
  158. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/loggers/postgres.py +0 -0
  159. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/notifications/__init__.py +0 -0
  160. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/notifications/composite.py +0 -0
  161. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/notifications/slack.py +0 -0
  162. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/notifications/throttler.py +0 -0
  163. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/pandaz/__init__.py +0 -0
  164. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/pandaz/stats.py +0 -0
  165. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/pandaz/ta.py +0 -0
  166. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/pandaz/utils.py +0 -0
  167. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/plugins/__init__.py +0 -0
  168. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/plugins/loader.py +0 -0
  169. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/rate_limiting/__init__.py +0 -0
  170. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/rate_limiting/backend.py +0 -0
  171. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/rate_limiting/config.py +0 -0
  172. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/rate_limiting/engine.py +0 -0
  173. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/rate_limiting/ip_resolver.py +0 -0
  174. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/rate_limiting/manager.py +0 -0
  175. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/rate_limiting/redis_backend.py +0 -0
  176. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/_build.py +0 -0
  177. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/crypto-fees.ini +0 -0
  178. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  179. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  180. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  181. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  182. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  183. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  184. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  185. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  186. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  187. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  188. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  189. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restarts/__init__.py +0 -0
  190. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restarts/state_resolvers.py +0 -0
  191. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restarts/time_finders.py +0 -0
  192. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restorers/__init__.py +0 -0
  193. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restorers/balance.py +0 -0
  194. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restorers/factory.py +0 -0
  195. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restorers/interfaces.py +0 -0
  196. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restorers/position.py +0 -0
  197. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restorers/signal.py +0 -0
  198. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restorers/state.py +0 -0
  199. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/restorers/utils.py +0 -0
  200. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/state/__init__.py +0 -0
  201. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/state/dummy.py +0 -0
  202. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/state/redis.py +0 -0
  203. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/ta/__init__.py +0 -0
  204. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/ta/indicators.pxd +0 -0
  205. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/ta/indicators.pyi +0 -0
  206. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/ta/indicators.pyx +0 -0
  207. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/__init__.py +0 -0
  208. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/base.py +0 -0
  209. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  210. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/project/config.yml.j2 +0 -0
  211. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  212. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  213. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  214. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  215. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  216. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/project/template.yml +0 -0
  217. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
  218. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
  219. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
  220. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
  221. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
  222. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.gitignore.j2 +0 -0
  223. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.python-version +0 -0
  224. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.vscode/launch.json +0 -0
  225. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.vscode/settings.json +0 -0
  226. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
  227. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
  228. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/README.md.j2 +0 -0
  229. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/configs/.gitkeep +0 -0
  230. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/justfile.j2 +0 -0
  231. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
  232. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/research/.gitkeep +0 -0
  233. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  234. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
  235. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  236. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/template.yml +0 -0
  237. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
  238. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  239. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  240. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/simple/config.yml.j2 +0 -0
  241. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  242. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  243. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  244. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/templates/simple/template.yml +0 -0
  245. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/trackers/__init__.py +0 -0
  246. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/trackers/advanced.py +0 -0
  247. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/trackers/composite.py +0 -0
  248. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/trackers/rebalancers.py +0 -0
  249. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/trackers/riskctrl.py +0 -0
  250. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/trackers/sizers.py +0 -0
  251. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/__init__.py +0 -0
  252. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/_pyxreloader.py +0 -0
  253. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/charting/lookinglass.py +0 -0
  254. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  255. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/charting/orderbook.py +0 -0
  256. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/collections.py +0 -0
  257. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/hft/__init__.py +0 -0
  258. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/hft/numba_utils.py +0 -0
  259. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/hft/orderbook.pyi +0 -0
  260. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/hft/orderbook.pyx +0 -0
  261. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/marketdata/binance.py +0 -0
  262. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/marketdata/ccxt.py +0 -0
  263. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/marketdata/dukas.py +0 -0
  264. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/misc.py +0 -0
  265. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/nonce.py +0 -0
  266. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/ntp.py +0 -0
  267. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/numbers_utils.py +0 -0
  268. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/orderbook.py +0 -0
  269. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/plotting/__init__.py +0 -0
  270. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/plotting/dashboard.py +0 -0
  271. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/plotting/data.py +0 -0
  272. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/plotting/interfaces.py +0 -0
  273. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  274. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  275. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/questdb.py +0 -0
  276. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/rate_limiter.py +0 -0
  277. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/results.py +0 -0
  278. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/ringbuffer.pxd +0 -0
  279. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/ringbuffer.pyi +0 -0
  280. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/ringbuffer.pyx +0 -0
  281. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/__init__.py +0 -0
  282. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  283. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/accounts.py +0 -0
  284. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/factory.py +0 -0
  285. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/kernel_service.py +0 -0
  286. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/runner.py +0 -0
  287. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/__init__.py +0 -0
  288. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/app.py +0 -0
  289. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/handlers.py +0 -0
  290. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/init_code.py +0 -0
  291. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/kernel.py +0 -0
  292. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  293. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  294. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
  295. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  296. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  297. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  298. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  299. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  300. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  301. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/s3.py +0 -0
  302. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/slack.py +0 -0
  303. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/throttler.py +0 -0
  304. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/time.py +0 -0
  305. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/src/qubx/utils/websocket_manager.py +0 -0
  306. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  307. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
  308. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  309. {qubx-1.1.3.dev17 → qubx-1.1.3.dev18}/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.1.3.dev17
3
+ Version: 1.1.3.dev18
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
@@ -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 = '1.1.3.dev17'
22
- __version_tuple__ = version_tuple = (1, 1, 3, 'dev17')
21
+ __version__ = version = '1.1.3.dev18'
22
+ __version_tuple__ = version_tuple = (1, 1, 3, 'dev18')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -412,6 +412,47 @@ def release(
412
412
  )
413
413
 
414
414
 
415
+ @main.command("release-from-source")
416
+ @click.option(
417
+ "--config",
418
+ "-c",
419
+ type=click.Path(exists=True, resolve_path=True),
420
+ help="Path to a config YAML file with a release.source section",
421
+ required=True,
422
+ )
423
+ @click.option(
424
+ "--output-dir",
425
+ "-o",
426
+ type=click.Path(exists=False),
427
+ help="Output directory to put zip file.",
428
+ default=".releases",
429
+ show_default=True,
430
+ )
431
+ @click.option(
432
+ "--tag",
433
+ "-t",
434
+ type=click.STRING,
435
+ help="Additional tag for this release",
436
+ required=False,
437
+ )
438
+ def release_from_source_cmd(config: str, output_dir: str, tag: str | None) -> None:
439
+ """
440
+ Build a release by cloning the source repo specified in the config's release.source section.
441
+
442
+ Used for CI-driven releases where the config lives in a separate repo from the strategy code.
443
+ The config must have a release.source section specifying the GitHub repo and ref.
444
+ """
445
+ from .release import release_from_source
446
+
447
+ zip_path = release_from_source(
448
+ config_file=config,
449
+ output_dir=output_dir,
450
+ tag=tag,
451
+ )
452
+ if zip_path:
453
+ click.echo(zip_path)
454
+
455
+
415
456
  @main.command()
416
457
  @click.argument(
417
458
  "zip-file",
@@ -447,6 +447,10 @@ def load_strategy_from_config(config_path: Path, directory: str) -> StrategyInfo
447
447
  if len(strat_name) > 16 and len(_found_classes) > 1:
448
448
  strat_name = _name_leader + generate_name(strat_name, 8)
449
449
 
450
+ # - fall back to the config name field when no local classes found
451
+ if not strat_name and strategy_config.name:
452
+ strat_name = strategy_config.name.replace(".", "_").replace(" ", "_")
453
+
450
454
  return StrategyInfo(name=strat_name, classes=_found_classes, config=strategy_config)
451
455
 
452
456
  except Exception as e:
@@ -558,6 +562,132 @@ def release_strategy(
558
562
  logger.opt(colors=False).error(f"Error releasing strategy: {e}")
559
563
 
560
564
 
565
+ def release_from_source(
566
+ config_file: str,
567
+ output_dir: str,
568
+ tag: str | None = None,
569
+ ) -> str | None:
570
+ """
571
+ Build a release by cloning the source repo specified in the config's release.source section.
572
+
573
+ This is the entry point for CI-driven releases (e.g., from xrelease GitHub Action).
574
+ It reads the release.source.repo and release.source.ref from the config YAML,
575
+ clones that repo at the specified ref into a temp directory, copies the config
576
+ into the clone, and runs the standard release flow.
577
+
578
+ Args:
579
+ config_file: Path to the strategy config YAML (must have a release.source section)
580
+ output_dir: Directory to write the release ZIP
581
+ tag: Optional tag suffix for the release name
582
+
583
+ Returns:
584
+ Path to the created ZIP file, or None on failure
585
+ """
586
+ import subprocess
587
+ import tempfile
588
+
589
+ from qubx import QubxLogConfig
590
+
591
+ QubxLogConfig.set_log_level("INFO")
592
+
593
+ try:
594
+ config_path = os.path.abspath(os.path.expanduser(config_file))
595
+ if not is_config_file(config_path):
596
+ raise ValueError(f"Not a valid config file: {config_file}")
597
+
598
+ # Parse the config to extract release.source
599
+ stg_config = load_strategy_config_from_yaml(config_path, resolve_env=False)
600
+ if not stg_config.release or not stg_config.release.source:
601
+ raise ValueError("Config must have a release.source section with repo and ref")
602
+
603
+ source = stg_config.release.source
604
+ repo_url = f"https://github.com/{source.repo}.git"
605
+ ref = source.ref
606
+
607
+ logger.info(f"Cloning {source.repo} at ref '{ref}' ...")
608
+ clone_dir = tempfile.mkdtemp(prefix="qubx-release-")
609
+
610
+ try:
611
+ subprocess.run(
612
+ ["git", "clone", "--depth", "1", "--branch", ref, repo_url, clone_dir],
613
+ check=True,
614
+ capture_output=True,
615
+ text=True,
616
+ )
617
+ except subprocess.CalledProcessError:
618
+ # --branch doesn't work with commit SHAs, fall back to full clone + checkout
619
+ logger.info(f"Shallow clone failed for ref '{ref}', trying full clone...")
620
+ shutil.rmtree(clone_dir, ignore_errors=True)
621
+ os.makedirs(clone_dir)
622
+ subprocess.run(
623
+ ["git", "clone", repo_url, clone_dir],
624
+ check=True,
625
+ capture_output=True,
626
+ text=True,
627
+ )
628
+ subprocess.run(
629
+ ["git", "checkout", ref],
630
+ cwd=clone_dir,
631
+ check=True,
632
+ capture_output=True,
633
+ text=True,
634
+ )
635
+
636
+ # Resolve dependencies in the cloned project
637
+ logger.info("Resolving dependencies in cloned project...")
638
+ subprocess.run(
639
+ ["uv", "lock"],
640
+ cwd=clone_dir,
641
+ check=True,
642
+ capture_output=True,
643
+ text=True,
644
+ )
645
+
646
+ # Copy the config into the clone root so release can find it
647
+ config_basename = os.path.basename(config_path)
648
+ cloned_config = os.path.join(clone_dir, config_basename)
649
+ shutil.copy2(config_path, cloned_config)
650
+
651
+ # Run the standard release flow from the cloned project
652
+ logger.info(f"Running release from {clone_dir} ...")
653
+ output_dir = os.path.abspath(output_dir)
654
+ stg_info = load_strategy_from_config(Path(cloned_config), clone_dir)
655
+ pyproject_root = clone_dir
656
+
657
+ git_info = ReleaseInfo(
658
+ tag=generate_tag(stg_info.name, tag),
659
+ commit=ref,
660
+ user=getpass.getuser(),
661
+ time=datetime.now(),
662
+ commited_files=[],
663
+ )
664
+
665
+ create_released_pack(
666
+ stg_info=stg_info,
667
+ git_info=git_info,
668
+ pyproject_root=pyproject_root,
669
+ output_dir=output_dir,
670
+ config_file=cloned_config,
671
+ )
672
+
673
+ # Find the created ZIP
674
+ zip_path = os.path.join(output_dir, f"{git_info.tag}.zip")
675
+ if os.path.exists(zip_path):
676
+ logger.info(f"Release created: {zip_path}")
677
+ return zip_path
678
+
679
+ logger.error("Release ZIP not found after build")
680
+ return None
681
+
682
+ except Exception as e:
683
+ logger.opt(colors=False).error(f"Error in release_from_source: {e}")
684
+ return None
685
+ finally:
686
+ # Clean up temp clone
687
+ if "clone_dir" in locals():
688
+ shutil.rmtree(clone_dir, ignore_errors=True)
689
+
690
+
561
691
  def _find_source_root(pyproject_root: str, project_name: str) -> str | None:
562
692
  """Find the source package directory for a project.
563
693
 
@@ -592,34 +722,6 @@ def _find_source_root(pyproject_root: str, project_name: str) -> str | None:
592
722
  return None
593
723
 
594
724
 
595
- def _collect_all_imports(strategy_files: list[str], project_root: str) -> set[str]:
596
- """
597
- Collect all top-level import module names from strategy source files.
598
-
599
- Scans each file's AST for import/from-import statements and returns
600
- the set of top-level (first component) module names, excluding stdlib
601
- and the project's own package.
602
-
603
- Args:
604
- strategy_files: List of absolute paths to strategy .py files
605
- project_root: Project root for resolving relative imports
606
-
607
- Returns:
608
- Set of top-level import names (e.g. {"numpy", "cachetools", "qubx"})
609
- """
610
- top_level_modules: set[str] = set()
611
-
612
- for file_path in strategy_files:
613
- try:
614
- # Pass empty what_to_look to get ALL imports (no filter)
615
- for imp in get_imports(file_path, what_to_look=[], project_root=project_root):
616
- if imp.module:
617
- top_level_modules.add(imp.module[0])
618
- except Exception as e:
619
- logger.warning(f"Failed to scan imports from {file_path}: {e}")
620
-
621
- return top_level_modules
622
-
623
725
 
624
726
  def _parse_uv_lock(uv_lock_path: str) -> dict[str, str]:
625
727
  """
@@ -649,96 +751,48 @@ def _parse_uv_lock(uv_lock_path: str) -> dict[str, str]:
649
751
  return versions
650
752
 
651
753
 
652
- def _scan_strategy_deps(
653
- strategy_files: list[str],
654
- pyproject_root: str,
655
- lock_versions: dict[str, str],
656
- pyproject_data: dict,
657
- external_imports: set[str] | None = None,
658
- ) -> list[str]:
754
+ def _parse_uv_lock_git_commits(uv_lock_path: str) -> dict[str, str]:
659
755
  """
660
- Scan strategy source files for external imports, map to packages, pin versions from lock.
756
+ Parse uv.lock and return {normalized_name: commit_sha} for git-sourced packages.
661
757
 
662
- For each dependency declared in pyproject.toml [project.dependencies]:
663
- 1. Extract the package name from the dep spec
664
- 2. Look up its top-level import names via importlib.metadata
665
- 3. Check if any of those import names appear in the strategy's imports
666
- 4. If yes, include that dep with the exact version from uv.lock
758
+ Lock entries look like:
759
+ source = { git = "https://github.com/org/repo?tag=v1.0#abcdef1234..." }
760
+ """
761
+ import toml
667
762
 
668
- Args:
669
- strategy_files: List of absolute paths to strategy .py files
670
- pyproject_root: Root directory containing pyproject.toml
671
- lock_versions: Pre-parsed {normalized_name: version} from uv.lock
672
- pyproject_data: Parsed pyproject.toml dict
673
- external_imports: Pre-computed set of external top-level import names.
674
- If provided, skips internal import collection.
763
+ if not os.path.exists(uv_lock_path):
764
+ return {}
675
765
 
676
- Returns:
677
- List of pinned dependency specs like ["cachetools==6.2.5", "Qubx==1.0.1.dev1"]
678
- """
679
- import importlib.metadata
766
+ with open(uv_lock_path) as f:
767
+ lock_data = toml.load(f)
680
768
 
681
- # Step 1: collect all imports from strategy files (or use pre-computed)
682
- if external_imports is not None:
683
- strategy_imports = external_imports
684
- else:
685
- strategy_imports = _collect_all_imports(strategy_files, pyproject_root)
686
- logger.info(f"Strategy imports (top-level modules): {sorted(strategy_imports)}")
687
-
688
- # Step 3: gather all declared deps (regular + optional)
689
- all_deps: list[str] = list(pyproject_data.get("project", {}).get("dependencies", []))
690
- for group_deps in pyproject_data.get("project", {}).get("optional-dependencies", {}).values():
691
- all_deps.extend(group_deps)
692
-
693
- # Step 4: for each declared dep, check if the strategy uses it
694
- scanned_deps: list[str] = []
695
- for dep_spec in all_deps:
696
- # Parse dep spec like "qubx[connectors,db,k8,tui]==1.0.3" or "cachetools>=6.2.1,<7"
697
- # Extract: package name, extras (if any), version specifiers
698
- match = re.match(r"^([A-Za-z0-9_.-]+)(\[[^\]]*\])?", dep_spec)
699
- pkg_name = (
700
- match.group(1).strip()
701
- if match
702
- else dep_spec.split("[")[0].split(">")[0].split("=")[0].split("<")[0].strip()
703
- )
704
- extras = match.group(2) or "" if match else ""
705
- pkg_name_normalized = pkg_name.lower().replace("-", "_")
769
+ commits: dict[str, str] = {}
770
+ for pkg in lock_data.get("package", []):
771
+ source = pkg.get("source", {})
772
+ git_url = source.get("git", "")
773
+ if git_url and "#" in git_url:
774
+ commit_sha = git_url.split("#")[-1]
775
+ name = pkg.get("name", "").lower().replace("-", "_")
776
+ if name and commit_sha:
777
+ commits[name] = commit_sha
778
+ return commits
706
779
 
707
- # Determine import names for this package
708
- import_names: set[str] = set()
709
- try:
710
- dist = importlib.metadata.distribution(pkg_name)
711
- top_level_text = dist.read_text("top_level.txt")
712
- if top_level_text:
713
- for line in top_level_text.strip().splitlines():
714
- import_names.add(line.strip())
715
- else:
716
- # Fallback: check RECORD for package directories
717
- if dist.files:
718
- for f in dist.files:
719
- parts = str(f).split("/")
720
- if len(parts) > 1 and parts[0] and not parts[0].endswith(".dist-info"):
721
- import_names.add(parts[0].replace("-", "_"))
722
- if not import_names:
723
- import_names.add(pkg_name_normalized)
724
- except importlib.metadata.PackageNotFoundError:
725
- # Package not installed — use fallback name mapping
726
- import_names.add(pkg_name_normalized)
727
-
728
- # Check if any import name is used by strategy
729
- if import_names & strategy_imports:
730
- # Pin version from lock file, preserving extras
731
- version = lock_versions.get(pkg_name_normalized)
732
- if version:
733
- pinned = f"{pkg_name}{extras}=={version}"
734
- else:
735
- # Fallback to declared spec
736
- pinned = dep_spec
737
- scanned_deps.append(pinned)
738
- logger.debug(f" Matched dep: {pinned} (imports: {import_names & strategy_imports})")
739
780
 
740
- logger.info(f"Scanned strategy deps ({len(scanned_deps)}): {scanned_deps}")
741
- return scanned_deps
781
+ def _find_uv_git_checkout(commit_sha: str) -> str | None:
782
+ """
783
+ Find the uv git cache checkout directory for a given commit SHA.
784
+
785
+ uv stores git checkouts at: ~/.cache/uv/git-v0/checkouts/{url_hash}/{commit[:7]}/
786
+ """
787
+ import glob
788
+
789
+ short = commit_sha[:7]
790
+ uv_cache = os.path.expanduser("~/.cache/uv/git-v0/checkouts")
791
+ matches = glob.glob(os.path.join(uv_cache, "*", short))
792
+ for match in matches:
793
+ if os.path.isdir(match):
794
+ return match
795
+ return None
742
796
 
743
797
 
744
798
  def _build_strategy_wheel(
@@ -762,7 +816,7 @@ def _build_strategy_wheel(
762
816
  """
763
817
  import subprocess
764
818
 
765
- wheels_dir = os.path.join(release_dir, "wheels")
819
+ wheels_dir = os.path.abspath(os.path.join(release_dir, "wheels"))
766
820
  os.makedirs(wheels_dir, exist_ok=True)
767
821
 
768
822
  logger.info("Building strategy wheel...")
@@ -981,6 +1035,8 @@ def create_released_pack(
981
1035
  name = pdep.split(">=")[0].split("==")[0].split("<")[0].split("[")[0].strip().lower()
982
1036
  required_packages.add(name)
983
1037
 
1038
+ git_commits = _parse_uv_lock_git_commits(uv_lock_path)
1039
+
984
1040
  bundled_packages: list[str] = []
985
1041
  if required_packages:
986
1042
  logger.info("Resolving private/local source dependencies...")
@@ -990,6 +1046,7 @@ def create_released_pack(
990
1046
  release_dir,
991
1047
  required_packages,
992
1048
  lock_versions,
1049
+ git_commits,
993
1050
  )
994
1051
  if bundled_packages:
995
1052
  logger.info(f"Bundled {len(bundled_packages)} package(s): {', '.join(bundled_packages)}")
@@ -1192,12 +1249,14 @@ def _bundle_source_overrides(
1192
1249
  release_dir: str,
1193
1250
  required_packages: set[str],
1194
1251
  lock_versions: dict[str, str],
1252
+ git_commits: dict[str, str] | None = None,
1195
1253
  ) -> list[str]:
1196
1254
  """
1197
1255
  For each [tool.uv.sources] entry that is required by this release:
1198
1256
  - path source + version on public PyPI → skip (will resolve from PyPI)
1199
1257
  - path source + NOT on public PyPI → build wheel from local path and bundle in wheels/
1200
1258
  - index source (private registry) → download wheel from that index and bundle in wheels/
1259
+ - git source → build wheel from uv's cached checkout and bundle in wheels/
1201
1260
 
1202
1261
  Uses uv.lock as the single source of truth for package versions.
1203
1262
 
@@ -1209,7 +1268,7 @@ def _bundle_source_overrides(
1209
1268
  if not sources:
1210
1269
  return []
1211
1270
 
1212
- wheels_dir = os.path.join(release_dir, "wheels")
1271
+ wheels_dir = os.path.abspath(os.path.join(release_dir, "wheels"))
1213
1272
  bundled: list[str] = []
1214
1273
 
1215
1274
  for pkg_name, source in sources.items():
@@ -1280,6 +1339,46 @@ def _bundle_source_overrides(
1280
1339
  except Exception as e:
1281
1340
  logger.opt(colors=False).warning(f" Failed to download wheel for {pkg_name}: {e}")
1282
1341
 
1342
+ elif "git" in source:
1343
+ if not pkg_ver:
1344
+ logger.warning(f" {pkg_name} not found in uv.lock, skipping bundle")
1345
+ continue
1346
+
1347
+ commit_sha = (git_commits or {}).get(pkg_norm)
1348
+ if not commit_sha:
1349
+ logger.warning(f" {pkg_name}: no git commit found in uv.lock, skipping bundle")
1350
+ continue
1351
+
1352
+ checkout_dir = _find_uv_git_checkout(commit_sha)
1353
+ if not checkout_dir:
1354
+ logger.warning(
1355
+ f" {pkg_name}: git checkout not found in uv cache (commit {commit_sha[:7]}), "
1356
+ "run `uv sync` first to populate the cache"
1357
+ )
1358
+ continue
1359
+
1360
+ logger.info(f" Bundling {pkg_name}=={pkg_ver} from git checkout {checkout_dir} ...")
1361
+ os.makedirs(wheels_dir, exist_ok=True)
1362
+ try:
1363
+ result = subprocess.run(
1364
+ ["uv", "build", "--wheel", ".", "--out-dir", wheels_dir],
1365
+ cwd=checkout_dir,
1366
+ check=True,
1367
+ capture_output=True,
1368
+ text=True,
1369
+ )
1370
+ logger.debug(f"uv build stdout: {result.stdout}")
1371
+ for whl in os.listdir(wheels_dir):
1372
+ if whl.lower().startswith(pkg_norm) and "none-any" not in whl:
1373
+ logger.warning(
1374
+ f" {whl} is platform-specific. "
1375
+ "Ensure the container architecture matches the build machine."
1376
+ )
1377
+ bundled.append(pkg_name.lower())
1378
+ logger.info(f" Bundled {pkg_name}=={pkg_ver} from git")
1379
+ except subprocess.CalledProcessError as e:
1380
+ logger.opt(colors=False).warning(f" Failed to build wheel for {pkg_name}: {e.stderr or e}")
1381
+
1283
1382
  return bundled
1284
1383
 
1285
1384
 
@@ -1401,7 +1500,7 @@ def generate_tag(strategy_name_id: str, tag_sfx: str | None) -> str:
1401
1500
  _tn = datetime.now()
1402
1501
  tag_s = f".{tag_sfx}" if tag_sfx else ""
1403
1502
  _strategy_name_id = strategy_name_id.replace(",", "_")
1404
- tag = f"R_{_strategy_name_id}_{_tn.strftime('%Y%m%d%H%M%S')}{tag_s}"
1503
+ tag = f"{_tn.strftime('%Y%m%d%H%M%S')}_{_strategy_name_id}{tag_s}"
1405
1504
  return tag
1406
1505
 
1407
1506
 
@@ -228,6 +228,42 @@ class PluginsConfig(StrictBaseModel):
228
228
  """Module names to import (for pip-installed packages)."""
229
229
 
230
230
 
231
+ class ReleaseSourceConfig(StrictBaseModel):
232
+ """Source repository for building a release."""
233
+
234
+ repo: str
235
+ """GitHub org/repo (e.g., 'xLydianSoftware/xincubator')."""
236
+
237
+ ref: str
238
+ """Git ref to build from — tag, branch, or commit SHA."""
239
+
240
+
241
+ class ReleasePlatformConfig(StrictBaseModel):
242
+ """Platform deployment configuration."""
243
+
244
+ name: str
245
+ """Release name on platform.xlydian.com."""
246
+
247
+ exchanges: list[str] = Field(default_factory=list)
248
+ """Exchange identifiers (e.g., ['binance'])."""
249
+
250
+ image_tag: str | None = None
251
+ """Qubx Docker image tag (e.g., '1.1.3.dev16'). Defaults to latest."""
252
+
253
+ tags: list[str] = Field(default_factory=list)
254
+ """Descriptive tags for the release."""
255
+
256
+
257
+ class ReleaseConfig(StrictBaseModel):
258
+ """Configuration for automated release packaging and platform deployment."""
259
+
260
+ source: ReleaseSourceConfig
261
+ """Source repository and ref to build from."""
262
+
263
+ platform: ReleasePlatformConfig | None = None
264
+ """Platform deployment settings. If omitted, release is built but not deployed."""
265
+
266
+
231
267
  class StrategyConfig(StrictBaseModel):
232
268
  name: str | None = None
233
269
  description: str | list[str] | None = None
@@ -238,6 +274,7 @@ class StrategyConfig(StrictBaseModel):
238
274
  plugins: PluginsConfig | None = None
239
275
  live: LiveConfig | None = None
240
276
  simulation: SimulationConfig | None = None
277
+ release: ReleaseConfig | None = None
241
278
 
242
279
 
243
280
  def normalize_aux_config(aux_config: list[StorageConfig] | StorageConfig | None) -> list[StorageConfig]:
File without changes
File without changes
File without changes
File without changes
File without changes