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