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.
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/PKG-INFO +1 -1
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/__init__.py +5 -2
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/_version.py +2 -2
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/release.py +182 -120
- qubx-1.0.6.dev3/src/qubx/cli/resolver.py +225 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/configs.py +5 -1
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/.gitignore +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/LICENSE +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/README.md +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/pyproject.toml +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/scripts/build.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/_nb_magic.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/account.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/broker.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/data.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/iteratedstream.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/management.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/ome.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/optimization.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/runner.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/simulator.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/transfers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/backtester/utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/commands.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/deploy.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/misc.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/theme.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/cli/tui.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/config.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/registry.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/account.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/basics.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/context.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/detectors/delisting.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/detectors/stale.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/errors.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/exceptions.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/helpers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/initializer.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/interfaces.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/loggers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/lookups.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/metrics.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/market.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/series.pxd +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/series.pyi +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/series.pyx +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/utils.pyi +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/core/utils.pyx +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/cache.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/containers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/guards.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/registry.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storage.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/ccxt.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/csv.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/handy.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/multi.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/questdb.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/stub.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/storages/utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/data/transformers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/base.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/composite.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/csv.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/indicator.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/emitters/questdb.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/composite.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/exporters/slack.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/gathering/simplest.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/health/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/health/base.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/health/dummy.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/health/server.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/csv.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/factory.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/loggers/mongo.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/notifications/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/notifications/composite.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/notifications/slack.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/notifications/throttler.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/pandaz/stats.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/pandaz/ta.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/pandaz/utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/plugins/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/plugins/loader.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/_build.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restarts/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/balance.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/factory.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/position.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/signal.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/state.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/restorers/utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/state/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/state/dummy.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/state/redis.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/ta/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/base.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/project/template.yml +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/advanced.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/composite.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/trackers/sizers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/collections.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/misc.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/nonce.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/ntp.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/orderbook.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/questdb.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/rate_limiter.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/results.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/runner.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/app.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/handlers.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/init_code.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/slack.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/throttler.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/time.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/src/qubx/utils/websocket_manager.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
- {qubx-1.0.6.dev1 → qubx-1.0.6.dev3}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
|
@@ -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
|
-
|
|
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 0, 6, '
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
766
|
+
# Find the source package directory
|
|
727
767
|
_src_dir_name = project_name.replace("-", "_")
|
|
728
|
-
_src_root =
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
_src_root
|
|
766
|
-
|
|
767
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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(
|
|
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
|
-
#
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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 +
|
|
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
|
-
|
|
1129
|
-
|
|
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}=={
|
|
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(
|
|
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
|
-
|
|
1169
|
-
|
|
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}=={
|
|
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}=={
|
|
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}=={
|
|
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(
|
|
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
|
|
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
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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
|
|
1317
|
+
f"adding '{spec}' from uv.lock"
|
|
1256
1318
|
)
|
|
1257
|
-
|
|
1319
|
+
else:
|
|
1258
1320
|
logger.warning(
|
|
1259
1321
|
f" Plugin '{module_name}' ({pkg_name}) not found in "
|
|
1260
|
-
"optional-deps or
|
|
1322
|
+
"optional-deps or uv.lock - skipping"
|
|
1261
1323
|
)
|
|
1262
1324
|
|
|
1263
1325
|
return plugin_deps
|