Qubx 1.0.0.dev6__tar.gz → 1.0.1.dev2__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.0.dev6 → qubx-1.0.1.dev2}/PKG-INFO +1 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/pyproject.toml +1 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/__init__.py +76 -3
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/_version.py +2 -2
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/utils.py +4 -2
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/cli/commands.py +20 -2
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/cli/release.py +301 -27
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/config.py +8 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/account.py +11 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/broker.py +29 -5
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/connection_manager.py +10 -4
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/data.py +5 -3
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/__init__.py +29 -12
- qubx-1.0.1.dev2/src/qubx/connectors/ccxt/exchanges/okx/account.py +70 -0
- qubx-1.0.1.dev2/src/qubx/connectors/ccxt/exchanges/okx/broker.py +18 -0
- qubx-1.0.1.dev2/src/qubx/connectors/ccxt/exchanges/okx/okx.py +56 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/factory.py +9 -3
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/ohlc.py +1 -3
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/orderbook.py +30 -6
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/utils.py +2 -2
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/basics.py +19 -13
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/context.py +3 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/mixins/market.py +3 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/mixins/processing.py +14 -14
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/mixins/trading.py +2 -2
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/storages/ccxt.py +5 -4
- qubx-1.0.1.dev2/src/qubx/health/__init__.py +5 -0
- qubx-1.0.1.dev2/src/qubx/health/server.py +67 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/crypto-fees.ini +14 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/ta/indicators.pxd +11 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/ta/indicators.pyi +4 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/ta/indicators.pyx +69 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/trackers/sizers.py +12 -10
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/marketdata/ccxt.py +5 -1
- qubx-1.0.1.dev2/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/accounts.py +4 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/configs.py +26 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/factory.py +7 -1
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/runner.py +59 -9
- qubx-1.0.0.dev6/src/qubx/health/__init__.py +0 -4
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/.gitignore +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/LICENSE +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/README.md +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/hatch_build.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/_nb_magic.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/account.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/broker.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/data.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/iteratedstream.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/management.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/ome.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/optimization.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/runner.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/simulator.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/backtester/transfers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/cli/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/cli/deploy.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/cli/misc.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/cli/theme.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/cli/tui.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-1.0.0.dev6/src/qubx/core → qubx-1.0.1.dev2/src/qubx/connectors/ccxt/exchanges/okx}/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/registry.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-1.0.0.dev6/src/qubx/restarts → qubx-1.0.1.dev2/src/qubx/core}/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/account.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/detectors/delisting.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/detectors/stale.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/errors.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/exceptions.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/helpers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/initializer.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/interfaces.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/loggers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/lookups.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/metrics.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/series.pxd +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/series.pyi +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/series.pyx +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/utils.pyi +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/core/utils.pyx +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/cache.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/containers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/guards.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/registry.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/storage.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/storages/csv.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/storages/handy.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/storages/multi.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/storages/questdb.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/storages/stub.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/storages/utils.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/data/transformers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/emitters/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/emitters/base.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/emitters/composite.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/emitters/csv.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/emitters/indicator.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/emitters/prometheus.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/emitters/questdb.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/composite.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/exporters/slack.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/gathering/simplest.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/health/base.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/health/dummy.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/loggers/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/loggers/csv.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/loggers/factory.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/loggers/mongo.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/notifications/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/notifications/composite.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/notifications/slack.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/notifications/throttler.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/pandaz/stats.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/pandaz/ta.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/pandaz/utils.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/plugins/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/plugins/loader.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/_build.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-1.0.0.dev6/src/qubx/ta → qubx-1.0.1.dev2/src/qubx/restarts}/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restorers/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restorers/balance.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restorers/factory.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restorers/position.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restorers/signal.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restorers/state.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/restorers/utils.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/state/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/state/dummy.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/state/redis.py +0 -0
- {qubx-1.0.0.dev6/src/qubx/utils/plotting → qubx-1.0.1.dev2/src/qubx/ta}/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/base.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/project/template.yml +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/trackers/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/trackers/advanced.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/trackers/composite.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/collections.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/misc.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/nonce.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/ntp.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/orderbook.py +0 -0
- {qubx-1.0.0.dev6/src/qubx/utils/plotting/renderers → qubx-1.0.1.dev2/src/qubx/utils/plotting}/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-1.0.0.dev6/src/qubx/utils/runner → qubx-1.0.1.dev2/src/qubx/utils/plotting/renderers}/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/questdb.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/rate_limiter.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/results.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/app.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/handlers.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/init_code.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/slack.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/throttler.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/time.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/src/qubx/utils/websocket_manager.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
- {qubx-1.0.0.dev6 → qubx-1.0.1.dev2}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
|
@@ -133,7 +133,7 @@ extend-select = ["I"]
|
|
|
133
133
|
ignore = ["E731", "E722", "E741"]
|
|
134
134
|
|
|
135
135
|
[tool.ruff.lint.extend-per-file-ignores]
|
|
136
|
-
"*.ipynb" = [ "F405", "F401", "E701", "E402", "F403", "E401", "E702", "F821", "E731", ]
|
|
136
|
+
"*.ipynb" = [ "F405", "F401", "E701", "E402", "F403", "E401", "E702", "F821", "E731", "I001", ]
|
|
137
137
|
|
|
138
138
|
[tool.pytest.ini_options]
|
|
139
139
|
asyncio_mode = "auto"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json as _json
|
|
1
2
|
import os
|
|
2
3
|
import sys
|
|
3
4
|
from typing import Callable
|
|
@@ -32,15 +33,30 @@ def runtime_env():
|
|
|
32
33
|
return "python" # Probably standard Python interpreter
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
def format_platform_identity(record) -> str:
|
|
37
|
+
"""Return a colored identity prefix from record extras (bot_id / instance_id)."""
|
|
38
|
+
bot_id = record["extra"].get("bot_id")
|
|
39
|
+
instance_id = record["extra"].get("instance_id")
|
|
40
|
+
if bot_id or instance_id:
|
|
41
|
+
parts = []
|
|
42
|
+
if bot_id:
|
|
43
|
+
parts.append(f"bot={bot_id}")
|
|
44
|
+
if instance_id:
|
|
45
|
+
parts.append(f"inst={instance_id}")
|
|
46
|
+
return "<magenta>[%s]</magenta> " % " ".join(parts)
|
|
47
|
+
return ""
|
|
48
|
+
|
|
49
|
+
|
|
35
50
|
def formatter(record):
|
|
36
51
|
end = record["extra"].get("end", "\n")
|
|
37
52
|
fmt = "<lvl>{message}</lvl>%s" % end
|
|
38
53
|
if record["level"].name in {"WARNING", "SNAKY"}:
|
|
39
54
|
fmt = "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - %s" % fmt
|
|
40
55
|
|
|
56
|
+
identity = format_platform_identity(record)
|
|
41
57
|
prefix = (
|
|
42
|
-
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> [ <level>%s</level> ] <cyan>({module})</cyan> "
|
|
43
|
-
% record["level"].icon
|
|
58
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> [ <level>%s</level> ] %s<cyan>({module})</cyan> "
|
|
59
|
+
% (record["level"].icon, identity)
|
|
44
60
|
)
|
|
45
61
|
|
|
46
62
|
if record["exception"] is not None:
|
|
@@ -70,6 +86,38 @@ class QubxLogConfig:
|
|
|
70
86
|
os.environ["QUBX_LOG_LEVEL"] = level
|
|
71
87
|
QubxLogConfig.setup_logger(level)
|
|
72
88
|
|
|
89
|
+
_COLOR_TAG_RE = None
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _strip_color_tags(text: str) -> str:
|
|
93
|
+
"""Remove loguru color markup tags like <yellow>...</yellow> from text."""
|
|
94
|
+
import re
|
|
95
|
+
|
|
96
|
+
if QubxLogConfig._COLOR_TAG_RE is None:
|
|
97
|
+
QubxLogConfig._COLOR_TAG_RE = re.compile(r"</?[a-z_]+>")
|
|
98
|
+
return QubxLogConfig._COLOR_TAG_RE.sub("", text)
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def _json_sink(message):
|
|
102
|
+
"""Emit one JSON line per log record for Loki/Promtail ingestion."""
|
|
103
|
+
record = message.record
|
|
104
|
+
entry = {
|
|
105
|
+
"timestamp": record["time"].strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
|
|
106
|
+
"level": record["level"].name,
|
|
107
|
+
"module": record["module"],
|
|
108
|
+
"function": record["function"],
|
|
109
|
+
"line": record["line"],
|
|
110
|
+
"message": QubxLogConfig._strip_color_tags(record["message"]),
|
|
111
|
+
}
|
|
112
|
+
# Merge platform identity and any other extras (skip internal loguru keys)
|
|
113
|
+
for key, val in record["extra"].items():
|
|
114
|
+
if key not in ("user", "end", "stack"):
|
|
115
|
+
entry[key] = val
|
|
116
|
+
if record["exception"] is not None:
|
|
117
|
+
entry["exception"] = stackprinter.format(record["exception"], style="plaintext")
|
|
118
|
+
sys.stdout.write(_json.dumps(entry, default=str) + "\n")
|
|
119
|
+
sys.stdout.flush()
|
|
120
|
+
|
|
73
121
|
@staticmethod
|
|
74
122
|
def setup_logger(level: str | None = None, custom_formatter: Callable | None = None, colorize: bool = True):
|
|
75
123
|
global logger
|
|
@@ -84,7 +132,21 @@ class QubxLogConfig:
|
|
|
84
132
|
logger.remove(None)
|
|
85
133
|
|
|
86
134
|
level = level or QubxLogConfig.get_log_level()
|
|
87
|
-
|
|
135
|
+
|
|
136
|
+
# Check if JSON format is requested (for Loki/container deployments)
|
|
137
|
+
log_format = os.getenv("QUBX_LOG_FORMAT", "text").lower()
|
|
138
|
+
if log_format == "json":
|
|
139
|
+
logger.add(
|
|
140
|
+
QubxLogConfig._json_sink,
|
|
141
|
+
level=level,
|
|
142
|
+
enqueue=True,
|
|
143
|
+
backtrace=True,
|
|
144
|
+
diagnose=True,
|
|
145
|
+
)
|
|
146
|
+
# No colorize opt needed for JSON
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# Default: human-readable text format
|
|
88
150
|
logger.add(
|
|
89
151
|
sys.stdout,
|
|
90
152
|
format=custom_formatter or formatter,
|
|
@@ -96,6 +158,17 @@ class QubxLogConfig:
|
|
|
96
158
|
)
|
|
97
159
|
logger = logger.opt(colors=colorize)
|
|
98
160
|
|
|
161
|
+
@staticmethod
|
|
162
|
+
def bind_platform_identity(bot_id: str | None = None, instance_id: str | None = None):
|
|
163
|
+
"""Bind platform identity fields (bot_id, instance_id) to all log messages globally."""
|
|
164
|
+
def patcher(record):
|
|
165
|
+
if bot_id:
|
|
166
|
+
record["extra"]["bot_id"] = bot_id
|
|
167
|
+
if instance_id:
|
|
168
|
+
record["extra"]["instance_id"] = instance_id
|
|
169
|
+
|
|
170
|
+
logger.configure(patcher=patcher)
|
|
171
|
+
|
|
99
172
|
|
|
100
173
|
QubxLogConfig.setup_logger()
|
|
101
174
|
|
|
@@ -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.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
31
|
+
__version__ = version = '1.0.1.dev2'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 0, 1, 'dev2')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -133,8 +133,10 @@ class SimulatedLogFormatter:
|
|
|
133
133
|
else:
|
|
134
134
|
now = self.time_provider.time().astype("datetime64[us]").item().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
from qubx import format_platform_identity
|
|
137
|
+
|
|
138
|
+
identity = format_platform_identity(record)
|
|
139
|
+
prefix = f"<lc>{now}</lc> [<level>{record['level'].icon}</level>] {identity}<cyan>({{module}})</cyan> "
|
|
138
140
|
|
|
139
141
|
if record["exception"] is not None:
|
|
140
142
|
record["extra"]["stack"] = stackprinter.format(record["exception"], style="darkbg3")
|
|
@@ -109,6 +109,7 @@ def main(debug: bool, debug_port: int, log_level: str):
|
|
|
109
109
|
@click.option("--no-emission", is_flag=True, default=False, help="Disable metric emission.", show_default=True)
|
|
110
110
|
@click.option("--no-notifiers", is_flag=True, default=False, help="Disable lifecycle notifiers.", show_default=True)
|
|
111
111
|
@click.option("--no-exporters", is_flag=True, default=False, help="Disable trade exporters.", show_default=True)
|
|
112
|
+
@click.option("--override", type=Path, default=None, help="Sparse YAML file to deep-merge on top of config.", show_default=False)
|
|
112
113
|
def run(
|
|
113
114
|
config_file: Path,
|
|
114
115
|
account_file: Path | None,
|
|
@@ -127,6 +128,7 @@ def run(
|
|
|
127
128
|
no_emission: bool,
|
|
128
129
|
no_notifiers: bool,
|
|
129
130
|
no_exporters: bool,
|
|
131
|
+
override: Path | None,
|
|
130
132
|
):
|
|
131
133
|
"""
|
|
132
134
|
Starts the strategy with the given configuration file. If paper mode is enabled, account is not required.
|
|
@@ -210,6 +212,7 @@ def run(
|
|
|
210
212
|
no_emission=no_emission,
|
|
211
213
|
no_notifiers=no_notifiers,
|
|
212
214
|
no_exporters=no_exporters,
|
|
215
|
+
config_overrides=override,
|
|
213
216
|
)
|
|
214
217
|
|
|
215
218
|
|
|
@@ -236,8 +239,23 @@ def run(
|
|
|
236
239
|
help="Write simulation logs to a file in the output directory.",
|
|
237
240
|
show_default=True,
|
|
238
241
|
)
|
|
242
|
+
@click.option(
|
|
243
|
+
"--name",
|
|
244
|
+
"-n",
|
|
245
|
+
default=None,
|
|
246
|
+
type=str,
|
|
247
|
+
help="Override the run name used for output folder (e.g. 'smoketest', '01_reference'). "
|
|
248
|
+
"Overrides the 'name:' field from the config file.",
|
|
249
|
+
show_default=True,
|
|
250
|
+
)
|
|
239
251
|
def simulate(
|
|
240
|
-
config_file: Path,
|
|
252
|
+
config_file: Path,
|
|
253
|
+
start: str | None,
|
|
254
|
+
end: str | None,
|
|
255
|
+
output: str | None,
|
|
256
|
+
report: str | None,
|
|
257
|
+
log_to_file: bool,
|
|
258
|
+
name: str | None,
|
|
241
259
|
):
|
|
242
260
|
"""
|
|
243
261
|
Simulates the strategy with the given configuration file.
|
|
@@ -249,7 +267,7 @@ def simulate(
|
|
|
249
267
|
add_project_to_system_path(str(config_file.parent))
|
|
250
268
|
logo()
|
|
251
269
|
logger.info(f"Process PID: <g>{os.getpid()}</g>")
|
|
252
|
-
simulate_strategy(config_file, output, start, end, report, log_to_file)
|
|
270
|
+
simulate_strategy(config_file, output, start, end, report, log_to_file, name=name)
|
|
253
271
|
|
|
254
272
|
|
|
255
273
|
@main.command()
|
|
@@ -534,9 +534,9 @@ def release_strategy(
|
|
|
534
534
|
config_file=config_file,
|
|
535
535
|
)
|
|
536
536
|
except ValueError as e:
|
|
537
|
-
logger.error(f"<r>{str(e)}</r>")
|
|
537
|
+
logger.error(f"<r>{str(e).replace('<', chr(92) + '<')}</r>")
|
|
538
538
|
except Exception as e:
|
|
539
|
-
logger.error(f"<r>Error releasing strategy: {e}</r>")
|
|
539
|
+
logger.error(f"<r>Error releasing strategy: {str(e).replace('<', chr(92) + '<')}</r>")
|
|
540
540
|
|
|
541
541
|
|
|
542
542
|
def create_released_pack(
|
|
@@ -814,7 +814,217 @@ def _create_metadata(stg_name: str, git_info: ReleaseInfo, release_dir: str) ->
|
|
|
814
814
|
fs.write(f"Commit: {git_info.commit}\n")
|
|
815
815
|
|
|
816
816
|
|
|
817
|
-
def
|
|
817
|
+
def _version_exists_on_pypi(pkg_name: str, version: str) -> bool:
|
|
818
|
+
"""Check if a specific package version is available on public PyPI."""
|
|
819
|
+
import urllib.request
|
|
820
|
+
|
|
821
|
+
try:
|
|
822
|
+
url = f"https://pypi.org/pypi/{pkg_name}/{version}/json"
|
|
823
|
+
with urllib.request.urlopen(url, timeout=5) as response:
|
|
824
|
+
return response.status == 200
|
|
825
|
+
except Exception:
|
|
826
|
+
return False
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _get_index_url(index_name: str, pyproject_data: dict) -> str | None:
|
|
830
|
+
"""Get URL for a named uv index from [[tool.uv.index]] entries."""
|
|
831
|
+
indexes = pyproject_data.get("tool", {}).get("uv", {}).get("index", [])
|
|
832
|
+
for idx in indexes:
|
|
833
|
+
if idx.get("name") == index_name:
|
|
834
|
+
return idx.get("url")
|
|
835
|
+
return None
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
def _find_version_in_pyproject(pkg_name: str, pyproject_data: dict) -> str | None:
|
|
839
|
+
"""
|
|
840
|
+
Find a package's pinned version from pyproject.toml deps (regular + optional).
|
|
841
|
+
Returns the version string (e.g. "0.1.0") or None if not found.
|
|
842
|
+
"""
|
|
843
|
+
all_deps: list[str] = list(pyproject_data.get("project", {}).get("dependencies", []))
|
|
844
|
+
for group_deps in pyproject_data.get("project", {}).get("optional-dependencies", {}).values():
|
|
845
|
+
all_deps.extend(group_deps)
|
|
846
|
+
|
|
847
|
+
for dep in all_deps:
|
|
848
|
+
dep_pkg = dep.split(">=")[0].split("==")[0].split("<")[0].strip()
|
|
849
|
+
if dep_pkg.lower() == pkg_name.lower():
|
|
850
|
+
if "==" in dep:
|
|
851
|
+
return dep.split("==")[1].strip()
|
|
852
|
+
if ">=" in dep:
|
|
853
|
+
return dep.split(">=")[1].split(",")[0].strip()
|
|
854
|
+
return None
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def _bundle_source_overrides(
|
|
858
|
+
pyproject_data: dict,
|
|
859
|
+
pyproject_root: str,
|
|
860
|
+
release_dir: str,
|
|
861
|
+
required_packages: set[str],
|
|
862
|
+
) -> list[str]:
|
|
863
|
+
"""
|
|
864
|
+
For each [tool.uv.sources] entry that is required by this release:
|
|
865
|
+
- path source + installed version on public PyPI → skip (will resolve from PyPI)
|
|
866
|
+
- path source + NOT on public PyPI → build wheel from local path and bundle in wheels/
|
|
867
|
+
- index source (private registry) → download wheel from that index and bundle in wheels/
|
|
868
|
+
|
|
869
|
+
Returns list of bundled package names (lowercase).
|
|
870
|
+
"""
|
|
871
|
+
import subprocess
|
|
872
|
+
from importlib.metadata import PackageNotFoundError
|
|
873
|
+
from importlib.metadata import version as get_version
|
|
874
|
+
|
|
875
|
+
sources = pyproject_data.get("tool", {}).get("uv", {}).get("sources", {})
|
|
876
|
+
if not sources:
|
|
877
|
+
return []
|
|
878
|
+
|
|
879
|
+
wheels_dir = os.path.join(release_dir, "wheels")
|
|
880
|
+
bundled: list[str] = []
|
|
881
|
+
|
|
882
|
+
for pkg_name, source in sources.items():
|
|
883
|
+
if pkg_name.lower() not in required_packages:
|
|
884
|
+
continue
|
|
885
|
+
|
|
886
|
+
if "path" in source:
|
|
887
|
+
local_path = os.path.normpath(os.path.join(pyproject_root, source["path"]))
|
|
888
|
+
|
|
889
|
+
try:
|
|
890
|
+
installed_ver = get_version(pkg_name)
|
|
891
|
+
except PackageNotFoundError:
|
|
892
|
+
logger.warning(f" {pkg_name} not installed, skipping bundle")
|
|
893
|
+
continue
|
|
894
|
+
|
|
895
|
+
if _version_exists_on_pypi(pkg_name, installed_ver):
|
|
896
|
+
logger.info(f" {pkg_name}=={installed_ver} found on public PyPI, will resolve from registry")
|
|
897
|
+
continue
|
|
898
|
+
|
|
899
|
+
logger.info(f" Bundling {pkg_name}=={installed_ver} from local path {local_path} ...")
|
|
900
|
+
os.makedirs(wheels_dir, exist_ok=True)
|
|
901
|
+
try:
|
|
902
|
+
# Run from the package's own directory so uv reads its [tool.uv.sources]
|
|
903
|
+
# (needed when the package has local-path build deps like qubx)
|
|
904
|
+
subprocess.run(
|
|
905
|
+
["uv", "build", "--wheel", ".", "--out-dir", wheels_dir],
|
|
906
|
+
cwd=local_path,
|
|
907
|
+
check=True, capture_output=True, text=True,
|
|
908
|
+
)
|
|
909
|
+
# Warn if the wheel is platform-specific (compiled extensions)
|
|
910
|
+
for whl in os.listdir(wheels_dir):
|
|
911
|
+
if whl.lower().startswith(pkg_name.lower().replace("-", "_")) and "none-any" not in whl:
|
|
912
|
+
logger.warning(
|
|
913
|
+
f" {whl} is platform-specific. "
|
|
914
|
+
"Ensure the container architecture matches the build machine."
|
|
915
|
+
)
|
|
916
|
+
bundled.append(pkg_name.lower())
|
|
917
|
+
logger.info(f" Bundled {pkg_name}")
|
|
918
|
+
except subprocess.CalledProcessError as e:
|
|
919
|
+
err_msg = (e.stderr or str(e)).replace("<", r"\<")
|
|
920
|
+
logger.warning(f" Failed to build wheel for {pkg_name}: {err_msg}")
|
|
921
|
+
|
|
922
|
+
elif "index" in source:
|
|
923
|
+
index_name = source["index"]
|
|
924
|
+
index_url = _get_index_url(index_name, pyproject_data)
|
|
925
|
+
if not index_url:
|
|
926
|
+
logger.warning(f" Index '{index_name}' not found in [[tool.uv.index]] (needed for {pkg_name})")
|
|
927
|
+
continue
|
|
928
|
+
|
|
929
|
+
try:
|
|
930
|
+
installed_ver = get_version(pkg_name)
|
|
931
|
+
except PackageNotFoundError:
|
|
932
|
+
# Package not installed (e.g. optional dep not synced) — fall back to
|
|
933
|
+
# the version declared in pyproject optional-deps or regular deps
|
|
934
|
+
installed_ver = _find_version_in_pyproject(pkg_name, pyproject_data)
|
|
935
|
+
if not installed_ver:
|
|
936
|
+
logger.warning(
|
|
937
|
+
f" {pkg_name} not installed and no version found in pyproject deps, skipping bundle"
|
|
938
|
+
)
|
|
939
|
+
continue
|
|
940
|
+
logger.info(f" {pkg_name} not installed; using declared version {installed_ver} from pyproject")
|
|
941
|
+
|
|
942
|
+
if _version_exists_on_pypi(pkg_name, installed_ver):
|
|
943
|
+
logger.info(f" {pkg_name}=={installed_ver} found on public PyPI, will resolve from registry")
|
|
944
|
+
continue
|
|
945
|
+
|
|
946
|
+
logger.info(f" Downloading {pkg_name}=={installed_ver} from private index '{index_name}' ...")
|
|
947
|
+
os.makedirs(wheels_dir, exist_ok=True)
|
|
948
|
+
try:
|
|
949
|
+
import shutil
|
|
950
|
+
|
|
951
|
+
pip_exe = shutil.which("pip") or shutil.which("pip3")
|
|
952
|
+
if not pip_exe:
|
|
953
|
+
raise RuntimeError("pip not found — cannot download wheel from private index")
|
|
954
|
+
subprocess.run(
|
|
955
|
+
[
|
|
956
|
+
pip_exe, "download",
|
|
957
|
+
f"{pkg_name}=={installed_ver}",
|
|
958
|
+
"--index-url", index_url,
|
|
959
|
+
"--dest", wheels_dir,
|
|
960
|
+
"--no-deps",
|
|
961
|
+
"--quiet",
|
|
962
|
+
],
|
|
963
|
+
check=True, capture_output=True, text=True,
|
|
964
|
+
)
|
|
965
|
+
bundled.append(pkg_name.lower())
|
|
966
|
+
logger.info(f" Downloaded {pkg_name}=={installed_ver}")
|
|
967
|
+
except subprocess.CalledProcessError as e:
|
|
968
|
+
err_msg = (e.stderr or str(e)).replace("<", r"\<")
|
|
969
|
+
logger.warning(f" Failed to download wheel for {pkg_name}: {err_msg}")
|
|
970
|
+
|
|
971
|
+
return bundled
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def _get_plugin_deps(stg_config: "StrategyConfig", pyproject_data: dict) -> list[str]:
|
|
975
|
+
"""
|
|
976
|
+
Extract package dependency specs for plugin modules listed in the strategy config.
|
|
977
|
+
|
|
978
|
+
Maps plugin module names (e.g. qubx_lighter) to package specs
|
|
979
|
+
(e.g. qubx-lighter==0.1.0) by looking in [project.optional-dependencies].
|
|
980
|
+
Falls back to the installed version if not found there.
|
|
981
|
+
|
|
982
|
+
Returns list of dep specs like ["qubx-lighter==0.1.0"].
|
|
983
|
+
"""
|
|
984
|
+
from importlib.metadata import PackageNotFoundError
|
|
985
|
+
from importlib.metadata import version as get_version
|
|
986
|
+
|
|
987
|
+
if not stg_config.plugins or not stg_config.plugins.modules:
|
|
988
|
+
return []
|
|
989
|
+
|
|
990
|
+
optional_deps: dict[str, list[str]] = pyproject_data.get("project", {}).get("optional-dependencies", {})
|
|
991
|
+
|
|
992
|
+
plugin_deps: list[str] = []
|
|
993
|
+
for module_name in stg_config.plugins.modules:
|
|
994
|
+
pkg_name = module_name.replace("_", "-")
|
|
995
|
+
|
|
996
|
+
# Search in optional-dependency groups first
|
|
997
|
+
found = False
|
|
998
|
+
for group_name, group_deps in optional_deps.items():
|
|
999
|
+
for dep in group_deps:
|
|
1000
|
+
dep_pkg = dep.split(">=")[0].split("==")[0].split("<")[0].strip()
|
|
1001
|
+
if dep_pkg.lower() == pkg_name.lower():
|
|
1002
|
+
plugin_deps.append(dep)
|
|
1003
|
+
logger.info(f" Plugin '{module_name}' -> adding '{dep}' (from optional-deps [{group_name}])")
|
|
1004
|
+
found = True
|
|
1005
|
+
break
|
|
1006
|
+
if found:
|
|
1007
|
+
break
|
|
1008
|
+
|
|
1009
|
+
if not found:
|
|
1010
|
+
try:
|
|
1011
|
+
ver = get_version(pkg_name)
|
|
1012
|
+
spec = f"{pkg_name}>={ver}"
|
|
1013
|
+
plugin_deps.append(spec)
|
|
1014
|
+
logger.warning(
|
|
1015
|
+
f" Plugin '{module_name}' not in optional-deps; "
|
|
1016
|
+
f"adding '{spec}' from installed packages"
|
|
1017
|
+
)
|
|
1018
|
+
except PackageNotFoundError:
|
|
1019
|
+
logger.warning(
|
|
1020
|
+
f" Plugin '{module_name}' ({pkg_name}) not found in "
|
|
1021
|
+
"optional-deps or installed packages - skipping"
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
return plugin_deps
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
def _clean_pyproject_for_release(pyproject_data: dict, bundled_packages: list[str] | None = None) -> dict:
|
|
818
1028
|
"""
|
|
819
1029
|
Remove dev-only and local-path-dependent sections from pyproject.toml for release.
|
|
820
1030
|
|
|
@@ -852,10 +1062,24 @@ def _clean_pyproject_for_release(pyproject_data: dict) -> dict:
|
|
|
852
1062
|
del pyproject_data["tool"][key]
|
|
853
1063
|
logger.debug(f"Removed [tool.{key}] from release pyproject.toml")
|
|
854
1064
|
|
|
1065
|
+
# If any wheels were bundled, add find-links so uv can resolve them
|
|
1066
|
+
if bundled_packages:
|
|
1067
|
+
if "tool" not in pyproject_data:
|
|
1068
|
+
pyproject_data["tool"] = {}
|
|
1069
|
+
if "uv" not in pyproject_data["tool"]:
|
|
1070
|
+
pyproject_data["tool"]["uv"] = {}
|
|
1071
|
+
pyproject_data["tool"]["uv"]["find-links"] = ["./wheels"]
|
|
1072
|
+
logger.debug("Added find-links = ['./wheels'] to [tool.uv]")
|
|
1073
|
+
|
|
855
1074
|
return pyproject_data
|
|
856
1075
|
|
|
857
1076
|
|
|
858
|
-
def _modify_pyproject_toml(
|
|
1077
|
+
def _modify_pyproject_toml(
|
|
1078
|
+
pyproject_path: str,
|
|
1079
|
+
package_name: str,
|
|
1080
|
+
bundled_packages: list[str] | None = None,
|
|
1081
|
+
plugin_deps: list[str] | None = None,
|
|
1082
|
+
) -> None:
|
|
859
1083
|
"""
|
|
860
1084
|
Modify the pyproject.toml file to include the project package as a dependency.
|
|
861
1085
|
|
|
@@ -873,7 +1097,7 @@ def _modify_pyproject_toml(pyproject_path: str, package_name: str) -> None:
|
|
|
873
1097
|
pyproject_data = toml.load(f)
|
|
874
1098
|
|
|
875
1099
|
# Clean up dev-only and local-path-dependent sections
|
|
876
|
-
pyproject_data = _clean_pyproject_for_release(pyproject_data)
|
|
1100
|
+
pyproject_data = _clean_pyproject_for_release(pyproject_data, bundled_packages)
|
|
877
1101
|
|
|
878
1102
|
# Handle PEP 621 format
|
|
879
1103
|
if "project" in pyproject_data:
|
|
@@ -886,15 +1110,29 @@ def _modify_pyproject_toml(pyproject_path: str, package_name: str) -> None:
|
|
|
886
1110
|
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
887
1111
|
pyproject_data["project"]["requires-python"] = f">={python_version}"
|
|
888
1112
|
|
|
889
|
-
#
|
|
1113
|
+
# Pin qubx/quantkit versions only if bare (no version constraint specified)
|
|
890
1114
|
deps = pyproject_data["project"]["dependencies"]
|
|
891
1115
|
for i, dep in enumerate(deps):
|
|
892
1116
|
dep_name = dep.split(">=")[0].split("==")[0].split("<")[0].strip()
|
|
893
1117
|
if dep_name.lower().startswith("qubx") or dep_name.lower().startswith("quantkit"):
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1118
|
+
if dep.strip() == dep_name:
|
|
1119
|
+
# Bare package name — add installed version
|
|
1120
|
+
try:
|
|
1121
|
+
deps[i] = f"{dep_name}>={version(dep_name)}"
|
|
1122
|
+
except Exception:
|
|
1123
|
+
pass
|
|
1124
|
+
|
|
1125
|
+
# Add plugin dependencies (from config's plugins.modules)
|
|
1126
|
+
if plugin_deps:
|
|
1127
|
+
for pdep in plugin_deps:
|
|
1128
|
+
pdep_pkg = pdep.split(">=")[0].split("==")[0].split("<")[0].strip().lower()
|
|
1129
|
+
already_present = any(
|
|
1130
|
+
d.split(">=")[0].split("==")[0].split("<")[0].strip().lower() == pdep_pkg
|
|
1131
|
+
for d in deps
|
|
1132
|
+
)
|
|
1133
|
+
if not already_present:
|
|
1134
|
+
deps.append(pdep)
|
|
1135
|
+
logger.debug(f"Added plugin dep: {pdep}")
|
|
898
1136
|
|
|
899
1137
|
# Check if build-system section exists
|
|
900
1138
|
if "build-system" not in pyproject_data:
|
|
@@ -1007,6 +1245,21 @@ def _configure_pyproject_for_external_deps(pyproject_path: str, packages: list[s
|
|
|
1007
1245
|
logger.warning(f"Could not determine version for {package}: {e}")
|
|
1008
1246
|
deps.append(package)
|
|
1009
1247
|
|
|
1248
|
+
# No custom code to package — disable package building
|
|
1249
|
+
if "tool" not in pyproject_data:
|
|
1250
|
+
pyproject_data["tool"] = {}
|
|
1251
|
+
if "uv" not in pyproject_data["tool"]:
|
|
1252
|
+
pyproject_data["tool"]["uv"] = {}
|
|
1253
|
+
pyproject_data["tool"]["uv"]["package"] = False
|
|
1254
|
+
|
|
1255
|
+
# Also disable poetry package mode if present
|
|
1256
|
+
if "poetry" in pyproject_data["tool"]:
|
|
1257
|
+
poetry_config = pyproject_data["tool"]["poetry"]
|
|
1258
|
+
poetry_config["package-mode"] = False
|
|
1259
|
+
if "packages" in poetry_config:
|
|
1260
|
+
del poetry_config["packages"]
|
|
1261
|
+
logger.debug("Set package = false (external deps only, no custom code)")
|
|
1262
|
+
|
|
1010
1263
|
# Write updated pyproject.toml
|
|
1011
1264
|
with open(pyproject_path, "w") as f:
|
|
1012
1265
|
toml.dump(pyproject_data, f)
|
|
@@ -1095,13 +1348,10 @@ def _handle_project_files(
|
|
|
1095
1348
|
) -> None:
|
|
1096
1349
|
"""
|
|
1097
1350
|
Handle project files like pyproject.toml and generate lock file.
|
|
1098
|
-
|
|
1099
|
-
Args:
|
|
1100
|
-
pyproject_root: Root directory containing pyproject.toml
|
|
1101
|
-
release_dir: Destination directory for release
|
|
1102
|
-
stg_info: Strategy info with config (optional, for dependency extraction)
|
|
1103
1351
|
"""
|
|
1104
|
-
|
|
1352
|
+
import toml
|
|
1353
|
+
|
|
1354
|
+
# Copy pyproject.toml
|
|
1105
1355
|
pyproject_src = os.path.join(pyproject_root, "pyproject.toml")
|
|
1106
1356
|
if not os.path.exists(pyproject_src):
|
|
1107
1357
|
raise FileNotFoundError(f"pyproject.toml not found in {pyproject_root}")
|
|
@@ -1110,25 +1360,51 @@ def _handle_project_files(
|
|
|
1110
1360
|
logger.debug(f"Copying pyproject.toml from {pyproject_src} to {pyproject_dest}")
|
|
1111
1361
|
shutil.copy2(pyproject_src, pyproject_dest)
|
|
1112
1362
|
|
|
1113
|
-
#
|
|
1363
|
+
# Read original data once for analysis (before any modifications)
|
|
1364
|
+
with open(pyproject_dest, "r") as f:
|
|
1365
|
+
pyproject_data = toml.load(f)
|
|
1366
|
+
|
|
1367
|
+
# --- Plugin deps from config ---
|
|
1368
|
+
plugin_deps: list[str] = []
|
|
1369
|
+
if stg_info and stg_info.config:
|
|
1370
|
+
plugin_deps = _get_plugin_deps(stg_info.config, pyproject_data)
|
|
1371
|
+
if plugin_deps:
|
|
1372
|
+
logger.info(f"Plugin dependencies from config: {plugin_deps}")
|
|
1373
|
+
|
|
1374
|
+
# --- Build required_packages set for targeted bundling ---
|
|
1375
|
+
# Start from declared project deps
|
|
1376
|
+
required_packages: set[str] = set()
|
|
1377
|
+
for dep in pyproject_data.get("project", {}).get("dependencies", []):
|
|
1378
|
+
name = dep.split(">=")[0].split("==")[0].split("<")[0].strip().lower()
|
|
1379
|
+
required_packages.add(name)
|
|
1380
|
+
# Add plugin packages
|
|
1381
|
+
for pdep in plugin_deps:
|
|
1382
|
+
name = pdep.split(">=")[0].split("==")[0].split("<")[0].strip().lower()
|
|
1383
|
+
required_packages.add(name)
|
|
1384
|
+
|
|
1385
|
+
# --- Bundle private/local wheels ---
|
|
1386
|
+
bundled_packages: list[str] = []
|
|
1387
|
+
if required_packages:
|
|
1388
|
+
logger.info("Resolving private/local source dependencies...")
|
|
1389
|
+
bundled_packages = _bundle_source_overrides(pyproject_data, pyproject_root, release_dir, required_packages)
|
|
1390
|
+
if bundled_packages:
|
|
1391
|
+
logger.info(f"Bundled {len(bundled_packages)} package(s): {', '.join(bundled_packages)}")
|
|
1392
|
+
|
|
1393
|
+
# --- Handle external-deps-only configs (no custom strategy classes) ---
|
|
1114
1394
|
if stg_info and not stg_info.classes:
|
|
1115
1395
|
current_package = get_project_package_name(pyproject_root)
|
|
1116
1396
|
external_deps = extract_external_dependencies(stg_info.config, current_package)
|
|
1117
|
-
|
|
1118
1397
|
if external_deps:
|
|
1119
1398
|
logger.info(f"Configuring pyproject.toml for external dependencies: {external_deps}")
|
|
1120
1399
|
_configure_pyproject_for_external_deps(pyproject_dest, external_deps)
|
|
1121
1400
|
|
|
1122
|
-
# Copy build.py
|
|
1401
|
+
# --- Copy build.py ---
|
|
1123
1402
|
build_src = os.path.join(pyproject_root, "build.py")
|
|
1124
1403
|
if not os.path.exists(build_src):
|
|
1125
1404
|
logger.info(f"build.py not found in {pyproject_root} using default one")
|
|
1126
1405
|
build_src = load_qubx_resources_as_text("_build.py")
|
|
1127
|
-
|
|
1128
|
-
# - setup project's name in default build.py
|
|
1129
1406
|
prj_name = os.path.basename(pyproject_root)
|
|
1130
1407
|
build_src = build_src.replace("<<PROJECT_NAME>>", prj_name)
|
|
1131
|
-
|
|
1132
1408
|
with open(os.path.join(release_dir, "build.py"), "wt") as fs:
|
|
1133
1409
|
fs.write(build_src)
|
|
1134
1410
|
else:
|
|
@@ -1136,13 +1412,11 @@ def _handle_project_files(
|
|
|
1136
1412
|
logger.debug(f"Copying build.py from {build_src} to {build_dest}")
|
|
1137
1413
|
shutil.copy2(build_src, build_dest)
|
|
1138
1414
|
|
|
1139
|
-
#
|
|
1415
|
+
# --- Modify pyproject: clean sources, pin versions, add find-links + plugin deps ---
|
|
1140
1416
|
package_name = os.path.basename(pyproject_root)
|
|
1417
|
+
_modify_pyproject_toml(pyproject_dest, package_name, bundled_packages=bundled_packages, plugin_deps=plugin_deps)
|
|
1141
1418
|
|
|
1142
|
-
#
|
|
1143
|
-
_modify_pyproject_toml(pyproject_dest, package_name)
|
|
1144
|
-
|
|
1145
|
-
# Generate the uv.lock file
|
|
1419
|
+
# --- Generate lock file ---
|
|
1146
1420
|
_generate_lock_file(release_dir)
|
|
1147
1421
|
|
|
1148
1422
|
|
|
@@ -86,6 +86,14 @@ class QubxSettings(BaseSettings):
|
|
|
86
86
|
instrument_lookup: LookupConfig = LookupConfig()
|
|
87
87
|
fees_lookup: LookupConfig = LookupConfig()
|
|
88
88
|
|
|
89
|
+
# Platform integration (all settable via QUBX_* env vars or ~/.qubx/config.json)
|
|
90
|
+
account_file: str | None = None
|
|
91
|
+
bot_id: str | None = None
|
|
92
|
+
instance_id: str | None = None
|
|
93
|
+
metrics_port: int | None = None
|
|
94
|
+
health_port: int | None = None
|
|
95
|
+
log_format: str = "text" # "text" or "json"
|
|
96
|
+
|
|
89
97
|
model_config = {"env_prefix": "QUBX_", "env_nested_delimiter": "__"}
|
|
90
98
|
|
|
91
99
|
@classmethod
|