Qubx 1.1.1.dev3__tar.gz → 1.1.2__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.1.1.dev3 → qubx-1.1.2}/PKG-INFO +1 -1
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/__init__.py +38 -8
- qubx-1.1.2/src/qubx/_version.py +24 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/data.py +3 -4
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/management.py +29 -4
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/runner.py +9 -6
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/simulated_data.py +28 -17
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/utils.py +17 -8
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/commands.py +1 -11
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/release.py +273 -260
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/tui.py +35 -5
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/account.py +19 -6
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/broker.py +2 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/data.py +3 -4
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +2 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/okx/account.py +51 -1
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/ohlc.py +4 -4
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/quote.py +14 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/utils.py +4 -4
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/tardis/data.py +6 -5
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/basics.py +18 -9
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/detectors/delisting.py +4 -5
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/detectors/stale.py +2 -3
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/exceptions.py +4 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/helpers.py +5 -5
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/loggers.py +18 -1
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/lookups.py +2 -1
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/metrics.py +233 -35
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/mixins/market.py +5 -3
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/mixins/trading.py +18 -9
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/mixins/universe.py +3 -3
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/cache.py +12 -10
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/guards.py +7 -6
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/storages/ccxt.py +5 -5
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/storages/multi.py +19 -7
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/storages/questdb.py +8 -4
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/storages/utils.py +3 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/transformers.py +5 -5
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/emitters/base.py +2 -3
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/emitters/indicator.py +2 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/emitters/inmemory.py +3 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/emitters/prometheus.py +4 -3
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/emitters/questdb.py +2 -1
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/formatters/base.py +3 -4
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/loggers/__init__.py +2 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/loggers/factory.py +2 -0
- qubx-1.1.2/src/qubx/loggers/postgres.py +216 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/pandaz/ta.py +7 -7
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restarts/time_finders.py +24 -1
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restorers/balance.py +74 -1
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restorers/factory.py +8 -4
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restorers/position.py +86 -1
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restorers/signal.py +117 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restorers/state.py +87 -3
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/trackers/advanced.py +3 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/trackers/sizers.py +2 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/charting/lookinglass.py +4 -3
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/charting/mpl_helpers.py +2 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/misc.py +4 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/results.py +13 -11
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/configs.py +1 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/runner.py +248 -37
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/app.py +26 -2
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/handlers.py +8 -3
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/init_code.py +32 -16
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/styles.tcss +15 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/widgets/__init__.py +2 -1
- qubx-1.1.2/src/qubx/utils/runner/textual/widgets/account_summary.py +47 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/time.py +19 -1
- qubx-1.1.1.dev3/src/qubx/_version.py +0 -34
- {qubx-1.1.1.dev3 → qubx-1.1.2}/.gitignore +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/LICENSE +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/README.md +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/pyproject.toml +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/scripts/build.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/_nb_magic.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/account.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/broker.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/iteratedstream.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/ome.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/optimization.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/simulator.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/backtester/transfers.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/deploy.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/misc.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/resolver.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/s3.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/theme.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/cli/user_config.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/config.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/registry.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/account.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/context.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/errors.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/initializer.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/interfaces.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/series.pxd +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/series.pyi +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/series.pyx +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/utils.pyi +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/core/utils.pyx +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/containers.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/registry.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/storage.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/storages/csv.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/storages/handy.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/data/storages/stub.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/emitters/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/emitters/composite.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/emitters/csv.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/composite.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/exporters/slack.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/gathering/simplest.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/health/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/health/base.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/health/dummy.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/health/server.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/loggers/csv.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/loggers/mongo.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/notifications/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/notifications/composite.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/notifications/slack.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/notifications/throttler.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/pandaz/stats.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/pandaz/utils.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/plugins/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/plugins/loader.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/_build.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restarts/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restorers/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/restorers/utils.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/state/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/state/dummy.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/state/redis.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/ta/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/base.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/project/template.yml +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.gitignore.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.python-version +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.vscode/launch.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.vscode/settings.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/README.md.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/configs/.gitkeep +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/justfile.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/research/.gitkeep +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/template.yml +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/trackers/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/trackers/composite.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/collections.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/nonce.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/ntp.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/orderbook.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/questdb.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/rate_limiter.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/s3.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/slack.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/throttler.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/src/qubx/utils/websocket_manager.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
- {qubx-1.1.1.dev3 → qubx-1.1.2}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
|
@@ -47,6 +47,16 @@ def format_platform_identity(record) -> str:
|
|
|
47
47
|
return ""
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def format_phase(record) -> str:
|
|
51
|
+
"""Return a colored phase tag from record extras."""
|
|
52
|
+
phase = record["extra"].get("phase")
|
|
53
|
+
if phase == "warmup":
|
|
54
|
+
return "<yellow>[WARMUP]</yellow> "
|
|
55
|
+
elif phase == "live":
|
|
56
|
+
return "<green>[LIVE]</green> "
|
|
57
|
+
return ""
|
|
58
|
+
|
|
59
|
+
|
|
50
60
|
def formatter(record):
|
|
51
61
|
end = record["extra"].get("end", "\n")
|
|
52
62
|
fmt = "<lvl>{message}</lvl>%s" % end
|
|
@@ -54,9 +64,10 @@ def formatter(record):
|
|
|
54
64
|
fmt = "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - %s" % fmt
|
|
55
65
|
|
|
56
66
|
identity = format_platform_identity(record)
|
|
67
|
+
phase = format_phase(record)
|
|
57
68
|
prefix = (
|
|
58
|
-
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> [ <level>%s</level> ] %s<cyan>({module})</cyan> "
|
|
59
|
-
% (record["level"].icon, identity)
|
|
69
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> [ <level>%s</level> ] %s%s<cyan>({module})</cyan> "
|
|
70
|
+
% (record["level"].icon, identity, phase)
|
|
60
71
|
)
|
|
61
72
|
|
|
62
73
|
if record["exception"] is not None:
|
|
@@ -158,17 +169,36 @@ class QubxLogConfig:
|
|
|
158
169
|
)
|
|
159
170
|
logger = logger.opt(colors=colorize)
|
|
160
171
|
|
|
172
|
+
_bot_id: str | None = None
|
|
173
|
+
_instance_id: str | None = None
|
|
174
|
+
_phase: str | None = None
|
|
175
|
+
|
|
161
176
|
@staticmethod
|
|
162
|
-
def
|
|
163
|
-
"""
|
|
177
|
+
def _update_patcher():
|
|
178
|
+
"""Reconfigure loguru with a single patcher that applies all bound fields."""
|
|
164
179
|
def patcher(record):
|
|
165
|
-
if
|
|
166
|
-
record["extra"]["bot_id"] =
|
|
167
|
-
if
|
|
168
|
-
record["extra"]["instance_id"] =
|
|
180
|
+
if QubxLogConfig._bot_id:
|
|
181
|
+
record["extra"]["bot_id"] = QubxLogConfig._bot_id
|
|
182
|
+
if QubxLogConfig._instance_id:
|
|
183
|
+
record["extra"]["instance_id"] = QubxLogConfig._instance_id
|
|
184
|
+
if QubxLogConfig._phase:
|
|
185
|
+
record["extra"]["phase"] = QubxLogConfig._phase
|
|
169
186
|
|
|
170
187
|
logger.configure(patcher=patcher)
|
|
171
188
|
|
|
189
|
+
@staticmethod
|
|
190
|
+
def bind_platform_identity(bot_id: str | None = None, instance_id: str | None = None):
|
|
191
|
+
"""Bind platform identity fields (bot_id, instance_id) to all log messages globally."""
|
|
192
|
+
QubxLogConfig._bot_id = bot_id
|
|
193
|
+
QubxLogConfig._instance_id = instance_id
|
|
194
|
+
QubxLogConfig._update_patcher()
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def bind_phase(phase: str | None):
|
|
198
|
+
"""Bind phase (warmup/live) to all log messages globally."""
|
|
199
|
+
QubxLogConfig._phase = phase
|
|
200
|
+
QubxLogConfig._update_patcher()
|
|
201
|
+
|
|
172
202
|
|
|
173
203
|
QubxLogConfig.setup_logger()
|
|
174
204
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '1.1.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 1, 2)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from typing import TypeVar
|
|
3
3
|
|
|
4
|
-
import pandas as pd
|
|
5
|
-
|
|
6
4
|
from qubx import logger
|
|
7
5
|
from qubx.backtester.simulated_data import SimulatedDataIterator
|
|
8
6
|
from qubx.core.basics import (
|
|
@@ -12,6 +10,7 @@ from qubx.core.basics import (
|
|
|
12
10
|
)
|
|
13
11
|
from qubx.core.interfaces import IDataProvider
|
|
14
12
|
from qubx.core.series import Bar, Quote
|
|
13
|
+
from qubx.utils.time import to_timedelta, to_timestamp
|
|
15
14
|
|
|
16
15
|
from .account import SimulatedAccountProcessor
|
|
17
16
|
from .utils import SimulatedTimeProvider
|
|
@@ -144,8 +143,8 @@ class SimulatedDataProvider(IDataProvider):
|
|
|
144
143
|
self._data_source.set_warmup_period(si[0], warm_period)
|
|
145
144
|
|
|
146
145
|
def get_ohlc(self, instrument: Instrument, timeframe: str, nbarsback: int) -> list[Bar]:
|
|
147
|
-
start =
|
|
148
|
-
end = start - nbarsback *
|
|
146
|
+
start = to_timestamp(self.time_provider.time())
|
|
147
|
+
end = start - nbarsback * to_timedelta(timeframe)
|
|
149
148
|
return self._data_source.get_ohlc(instrument, timeframe, start, end)
|
|
150
149
|
|
|
151
150
|
def get_quote(self, instrument: Instrument) -> Quote | None:
|
|
@@ -573,7 +573,7 @@ class BacktestStorage:
|
|
|
573
573
|
if self._is_cloud:
|
|
574
574
|
self._setup_cloud_duckdb()
|
|
575
575
|
|
|
576
|
-
def get_log(self, backtest_id: str, config_name: str | None = None) -> str | None:
|
|
576
|
+
def get_log(self, backtest_id: str, config_name: str | None = None, max_lines: int = 1000) -> tuple[str, bool] | None:
|
|
577
577
|
"""
|
|
578
578
|
Read the log file for a backtest run.
|
|
579
579
|
|
|
@@ -581,14 +581,29 @@ class BacktestStorage:
|
|
|
581
581
|
backtest_id: Relative path within base_path.
|
|
582
582
|
config_name: Config name (used for log filename).
|
|
583
583
|
If None, searches for any .log file.
|
|
584
|
+
max_lines: Maximum number of lines to return (from the end). 0 = unlimited.
|
|
584
585
|
|
|
585
586
|
Returns:
|
|
586
|
-
|
|
587
|
+
(log_content, truncated) tuple, or None if not found.
|
|
587
588
|
"""
|
|
588
589
|
run_dir = f"{self.base_path}{backtest_id.strip('/')}/"
|
|
589
590
|
|
|
590
591
|
if config_name:
|
|
591
592
|
log_path = f"{run_dir}{config_name}.log"
|
|
593
|
+
elif self._is_cloud:
|
|
594
|
+
try:
|
|
595
|
+
from pyarrow.fs import FileSelector, FileType
|
|
596
|
+
|
|
597
|
+
from qubx.utils.s3 import strip_scheme
|
|
598
|
+
|
|
599
|
+
client = S3Client(storage_options=self._storage_options)
|
|
600
|
+
entries = client.fs.get_file_info(FileSelector(strip_scheme(run_dir)))
|
|
601
|
+
log_files = [e.path for e in entries if e.type == FileType.File and e.path.endswith(".log")]
|
|
602
|
+
if not log_files:
|
|
603
|
+
return None
|
|
604
|
+
log_path = f"s3://{log_files[0]}"
|
|
605
|
+
except Exception:
|
|
606
|
+
return None
|
|
592
607
|
else:
|
|
593
608
|
try:
|
|
594
609
|
files_df = self._conn.execute(f"SELECT file FROM glob('{run_dir}*.log')").df()
|
|
@@ -598,20 +613,30 @@ class BacktestStorage:
|
|
|
598
613
|
except Exception:
|
|
599
614
|
return None
|
|
600
615
|
|
|
616
|
+
def _tail(content: str) -> tuple[str, bool]:
|
|
617
|
+
if not max_lines:
|
|
618
|
+
return content, False
|
|
619
|
+
lines = content.splitlines()
|
|
620
|
+
if len(lines) <= max_lines:
|
|
621
|
+
return content, False
|
|
622
|
+
return "\n".join(lines[-max_lines:]), True
|
|
623
|
+
|
|
601
624
|
if self._is_cloud:
|
|
602
625
|
try:
|
|
603
626
|
from qubx.utils.s3 import strip_scheme
|
|
604
627
|
|
|
605
628
|
client = S3Client(storage_options=self._storage_options)
|
|
606
629
|
with client.fs.open_input_stream(strip_scheme(log_path)) as f:
|
|
607
|
-
return f.read().decode("utf-8")
|
|
630
|
+
return _tail(f.read().decode("utf-8"))
|
|
608
631
|
except Exception:
|
|
609
632
|
return None
|
|
610
633
|
else:
|
|
611
634
|
from pathlib import Path
|
|
612
635
|
|
|
613
636
|
p = Path(log_path)
|
|
614
|
-
|
|
637
|
+
if not p.is_file():
|
|
638
|
+
return None
|
|
639
|
+
return _tail(p.read_text(encoding="utf-8", errors="replace"))
|
|
615
640
|
|
|
616
641
|
def export_backtests_to_markdown(self, backtest_id: str, path: str, tags: tuple[str] | None = None):
|
|
617
642
|
if tsr := self.load(backtest_id):
|
|
@@ -34,7 +34,7 @@ from qubx.health import DummyHealthMonitor
|
|
|
34
34
|
from qubx.loggers.inmemory import InMemoryLogsWriter
|
|
35
35
|
from qubx.pandaz.utils import _frame_to_str
|
|
36
36
|
from qubx.utils.runner.configs import PrefetchConfig
|
|
37
|
-
from qubx.utils.time import now_ns
|
|
37
|
+
from qubx.utils.time import now_ns, to_timedelta
|
|
38
38
|
|
|
39
39
|
from .account import SimulatedAccountProcessor
|
|
40
40
|
from .broker import SimulatedBroker
|
|
@@ -131,6 +131,11 @@ class SimulationRunner:
|
|
|
131
131
|
self._create_backtest_context()
|
|
132
132
|
self._handle_ctx_subscriptions()
|
|
133
133
|
|
|
134
|
+
@property
|
|
135
|
+
def data_time_range(self) -> tuple[int, int] | None:
|
|
136
|
+
"""Time range (ns) of historical data actually processed: (first, last), or None if no data arrived."""
|
|
137
|
+
return self._simulated_data_source.data_time_range
|
|
138
|
+
|
|
134
139
|
def run(self, silent: bool = False, catch_keyboard_interrupt: bool = True, close_data_readers: bool = False):
|
|
135
140
|
"""
|
|
136
141
|
Run the backtest from start to stop.
|
|
@@ -437,7 +442,7 @@ class SimulationRunner:
|
|
|
437
442
|
# - TimeGuard is applied by callers that need it (aux/strategy-facing access).
|
|
438
443
|
# - Main simulation data (DataPump) must NOT be time-guarded: DataPump provides
|
|
439
444
|
# its own explicit [start, end] window and needs to read the full range upfront.
|
|
440
|
-
_prefetch_period = str(min(self.stop - self.start,
|
|
445
|
+
_prefetch_period = str(min(self.stop - self.start, to_timedelta(prefetch_cfg.prefetch_period)))
|
|
441
446
|
return CachedStorage(
|
|
442
447
|
storage, prefetch_period=_prefetch_period, cache_factory=lambda: MemoryCache(prefetch_cfg.cache_size_mb)
|
|
443
448
|
)
|
|
@@ -611,10 +616,8 @@ class SimulationRunner:
|
|
|
611
616
|
|
|
612
617
|
_account_processors = {}
|
|
613
618
|
for exchange in self.setup.exchanges:
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
_initial_capital = _initial_capital[exchange]
|
|
617
|
-
assert isinstance(_initial_capital, (float, int))
|
|
619
|
+
assert isinstance(self.setup.capital, dict)
|
|
620
|
+
_initial_capital = self.setup.capital[exchange]
|
|
618
621
|
_account_processors[exchange] = SimulatedAccountProcessor(
|
|
619
622
|
account_id=self.account_id,
|
|
620
623
|
exchange=_exchange_to_simulated_exchange[exchange],
|
|
@@ -16,7 +16,7 @@ from qubx.data.containers import IRawContainer, RawData, RawMultiData
|
|
|
16
16
|
from qubx.data.storage import IDataTransformer, IReader, IStorage, Transformable
|
|
17
17
|
from qubx.data.storages.utils import find_column_index_in_list
|
|
18
18
|
from qubx.data.transformers import TypedRecords
|
|
19
|
-
from qubx.utils.time import convert_times_to_ns, infer_series_frequency
|
|
19
|
+
from qubx.utils.time import convert_times_to_ns, infer_series_frequency, to_timedelta, to_timestamp
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class EmulatedUpdatesFromOHLC(IDataTransformer):
|
|
@@ -26,7 +26,7 @@ class EmulatedUpdatesFromOHLC(IDataTransformer):
|
|
|
26
26
|
|
|
27
27
|
@staticmethod
|
|
28
28
|
def timedelta_to_numpy(x: str) -> int:
|
|
29
|
-
return
|
|
29
|
+
return to_timedelta(x).to_numpy().item()
|
|
30
30
|
|
|
31
31
|
D1, H1 = timedelta_to_numpy("1D"), timedelta_to_numpy("1h")
|
|
32
32
|
MS1 = 1_000_000
|
|
@@ -329,9 +329,9 @@ class RawSymbolBuffer:
|
|
|
329
329
|
if isinstance(value, pd.Timestamp):
|
|
330
330
|
return value.value
|
|
331
331
|
if isinstance(value, np.datetime64):
|
|
332
|
-
return
|
|
332
|
+
return to_timestamp(value).value
|
|
333
333
|
# - fallback: try to convert via pd.Timestamp
|
|
334
|
-
return
|
|
334
|
+
return to_timestamp(value).value
|
|
335
335
|
|
|
336
336
|
def activate(self):
|
|
337
337
|
self._active = True
|
|
@@ -367,10 +367,10 @@ class RawSymbolBuffer:
|
|
|
367
367
|
# - dedup: filter out rows already in buffer using native pyarrow comparison
|
|
368
368
|
if self._watermark > 0:
|
|
369
369
|
if pa.types.is_timestamp(time_col.type):
|
|
370
|
-
wm_scalar = pa.scalar(
|
|
370
|
+
wm_scalar = pa.scalar(to_timestamp(self._watermark, unit="ns"), type=time_col.type)
|
|
371
371
|
elif pa.types.is_date(time_col.type):
|
|
372
372
|
# - date32 stores days since epoch; watermark is ns, convert to datetime.date
|
|
373
|
-
wm_scalar = pa.scalar(
|
|
373
|
+
wm_scalar = pa.scalar(to_timestamp(self._watermark, unit="ns").date(), type=time_col.type)
|
|
374
374
|
else:
|
|
375
375
|
wm_scalar = pa.scalar(self._watermark, type=time_col.type)
|
|
376
376
|
mask = pa.compute.greater(time_col, wm_scalar)
|
|
@@ -398,7 +398,7 @@ class RawSymbolBuffer:
|
|
|
398
398
|
return len(self._batches)
|
|
399
399
|
|
|
400
400
|
def __repr__(self) -> str:
|
|
401
|
-
_wm =
|
|
401
|
+
_wm = to_timestamp(self._watermark, unit="ns") if self._watermark else "N/A"
|
|
402
402
|
return f"RawSymbolBuffer({self._symbol}, chunks={len(self)}, wm={_wm}, active={self._active})"
|
|
403
403
|
|
|
404
404
|
|
|
@@ -644,13 +644,13 @@ class DataPump:
|
|
|
644
644
|
Starts backend reader, creates MemReaders for all symbols.
|
|
645
645
|
Returns dict suitable for slicer.put().
|
|
646
646
|
"""
|
|
647
|
-
self._end =
|
|
647
|
+
self._end = to_timestamp(end)
|
|
648
648
|
symbols = self._active_symbols()
|
|
649
649
|
if not symbols:
|
|
650
650
|
return {}
|
|
651
651
|
|
|
652
652
|
# - all symbols need warmup on first read
|
|
653
|
-
read_start =
|
|
653
|
+
read_start = to_timestamp(start)
|
|
654
654
|
if self._warmup_period:
|
|
655
655
|
read_start = read_start - self._warmup_period
|
|
656
656
|
self._warmed.update(symbols)
|
|
@@ -686,7 +686,7 @@ class DataPump:
|
|
|
686
686
|
dict of NEW MemReaders only (for slicer.put())
|
|
687
687
|
"""
|
|
688
688
|
if end is not None:
|
|
689
|
-
self._end =
|
|
689
|
+
self._end = to_timestamp(end)
|
|
690
690
|
|
|
691
691
|
symbols = self._active_symbols()
|
|
692
692
|
if not symbols:
|
|
@@ -695,7 +695,7 @@ class DataPump:
|
|
|
695
695
|
|
|
696
696
|
# - determine start time: extend into past if new symbols need warmup
|
|
697
697
|
new_symbols = [s for s in symbols if s not in self._warmed]
|
|
698
|
-
read_start =
|
|
698
|
+
read_start = to_timestamp(current_time)
|
|
699
699
|
|
|
700
700
|
if new_symbols and self._warmup_period:
|
|
701
701
|
read_start = read_start - self._warmup_period
|
|
@@ -846,9 +846,17 @@ class SimulatedDataIterator(Iterator):
|
|
|
846
846
|
self._slicer = None
|
|
847
847
|
self._slicing_iterator = None
|
|
848
848
|
self._current_time = None
|
|
849
|
+
self._first_data_time: int | None = None
|
|
849
850
|
self._start = None
|
|
850
851
|
self._stop = None
|
|
851
852
|
|
|
853
|
+
@property
|
|
854
|
+
def data_time_range(self) -> tuple[int, int] | None:
|
|
855
|
+
"""Time range (ns) of historical data actually processed: (first, last), or None if no data arrived."""
|
|
856
|
+
if self._first_data_time is not None and self._current_time is not None:
|
|
857
|
+
return (self._first_data_time, self._current_time)
|
|
858
|
+
return None
|
|
859
|
+
|
|
852
860
|
@property
|
|
853
861
|
def emulation_time_indent_seconds(self) -> float:
|
|
854
862
|
"""
|
|
@@ -876,7 +884,7 @@ class SimulatedDataIterator(Iterator):
|
|
|
876
884
|
def set_warmup_period(self, subscription: str, warmup_period: str | None = None):
|
|
877
885
|
if warmup_period:
|
|
878
886
|
access_key, _, _ = self._parse_subscription_spec(subscription)
|
|
879
|
-
self._warmups[access_key] =
|
|
887
|
+
self._warmups[access_key] = to_timedelta(warmup_period)
|
|
880
888
|
|
|
881
889
|
def _parse_subscription_spec(self, subscription: str) -> tuple[str, str, dict[str, object]]:
|
|
882
890
|
_subtype, _params = DataType.from_str(subscription)
|
|
@@ -999,7 +1007,7 @@ class SimulatedDataIterator(Iterator):
|
|
|
999
1007
|
|
|
1000
1008
|
# - if simulation is running, restart read to include new symbols
|
|
1001
1009
|
if self.is_running and new_instruments:
|
|
1002
|
-
new_mem_readers = pump.restart_read(
|
|
1010
|
+
new_mem_readers = pump.restart_read(to_timestamp(self._current_time, unit="ns"), self._stop) # type: ignore
|
|
1003
1011
|
if new_mem_readers and self._slicer is not None:
|
|
1004
1012
|
self._slicer.put(new_mem_readers)
|
|
1005
1013
|
|
|
@@ -1101,16 +1109,17 @@ class SimulatedDataIterator(Iterator):
|
|
|
1101
1109
|
return self._current_time is not None
|
|
1102
1110
|
|
|
1103
1111
|
def create_iterable(self, start: str | pd.Timestamp, stop: str | pd.Timestamp) -> Iterator:
|
|
1104
|
-
self._start =
|
|
1105
|
-
self._stop =
|
|
1112
|
+
self._start = to_timestamp(start)
|
|
1113
|
+
self._stop = to_timestamp(stop)
|
|
1106
1114
|
self._current_time = None
|
|
1115
|
+
self._first_data_time = None
|
|
1107
1116
|
self._slicer = IteratedDataStreamsSlicer()
|
|
1108
1117
|
return self
|
|
1109
1118
|
|
|
1110
1119
|
def __iter__(self) -> Iterator:
|
|
1111
1120
|
assert self._start is not None and self._stop is not None
|
|
1112
1121
|
assert self._slicer is not None
|
|
1113
|
-
self._current_time = int(
|
|
1122
|
+
self._current_time = int(to_timestamp(self._start).timestamp() * 1e9)
|
|
1114
1123
|
|
|
1115
1124
|
# - initial read for all pumps
|
|
1116
1125
|
for pump in self._pumps.values():
|
|
@@ -1137,6 +1146,8 @@ class SimulatedDataIterator(Iterator):
|
|
|
1137
1146
|
if t < self._current_time:
|
|
1138
1147
|
is_historical = True
|
|
1139
1148
|
else:
|
|
1149
|
+
if self._first_data_time is None:
|
|
1150
|
+
self._first_data_time = t
|
|
1140
1151
|
self._current_time = t
|
|
1141
1152
|
|
|
1142
1153
|
return instr, data_type, v, is_historical
|
|
@@ -1160,7 +1171,7 @@ class SimulatedDataIterator(Iterator):
|
|
|
1160
1171
|
assert isinstance(t_records, Transformable)
|
|
1161
1172
|
|
|
1162
1173
|
return self._process_bar_records(
|
|
1163
|
-
t_records.transform(TypedRecords()), time_as_nsec(start),
|
|
1174
|
+
t_records.transform(TypedRecords()), time_as_nsec(start), to_timedelta(timeframe).asm8.item()
|
|
1164
1175
|
)
|
|
1165
1176
|
|
|
1166
1177
|
def _process_bar_records(self, records: list[Bar], cut_time_ns: int, timeframe_ns: int) -> list[Bar]:
|
|
@@ -25,6 +25,7 @@ from qubx.core.utils import time_delta_to_str
|
|
|
25
25
|
from qubx.data.storage import IStorage
|
|
26
26
|
from qubx.utils.misc import safe_dtype_timeframe
|
|
27
27
|
from qubx.utils.runner.configs import PrefetchConfig
|
|
28
|
+
from qubx.utils.time import to_timedelta
|
|
28
29
|
|
|
29
30
|
SymbolOrInstrument_t: TypeAlias = str | Instrument
|
|
30
31
|
ExchangeName_t: TypeAlias = str
|
|
@@ -45,7 +46,7 @@ StrategiesDecls_t: TypeAlias = (
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def _timedelta_to_numpy(x: str | int) -> int:
|
|
48
|
-
return
|
|
49
|
+
return to_timedelta(x).to_numpy().item()
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
DEFAULT_DAILY_SESSION = (_timedelta_to_numpy("00:00:00.100"), _timedelta_to_numpy("23:59:59.900"))
|
|
@@ -98,6 +99,13 @@ class SimulationSetup:
|
|
|
98
99
|
accurate_stop_orders_execution: bool = False
|
|
99
100
|
enable_funding: bool = False
|
|
100
101
|
|
|
102
|
+
def __post_init__(self) -> None:
|
|
103
|
+
# Normalize capital to a per-exchange dict: split evenly when a single float is given
|
|
104
|
+
if isinstance(self.capital, (int, float)):
|
|
105
|
+
n = len(self.exchanges)
|
|
106
|
+
per_exchange = float(self.capital) / n if n > 0 else float(self.capital)
|
|
107
|
+
self.capital = {exchange: per_exchange for exchange in self.exchanges}
|
|
108
|
+
|
|
101
109
|
def __str__(self) -> str:
|
|
102
110
|
return f"{self.name} {self.setup_type} capital {self.capital} {self.base_currency} for [{','.join(map(lambda x: x.symbol, self.instruments))}] @ {self.exchanges}[{self.commissions}]"
|
|
103
111
|
|
|
@@ -133,10 +141,11 @@ class SimulatedLogFormatter:
|
|
|
133
141
|
else:
|
|
134
142
|
now = self.time_provider.time().astype("datetime64[us]").item().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
135
143
|
|
|
136
|
-
from qubx import format_platform_identity
|
|
144
|
+
from qubx import format_phase, format_platform_identity
|
|
137
145
|
|
|
138
146
|
identity = format_platform_identity(record)
|
|
139
|
-
|
|
147
|
+
phase = format_phase(record)
|
|
148
|
+
prefix = f"<lc>{now}</lc> [<level>{record['level'].icon}</level>] {identity}{phase}<cyan>({{module}})</cyan> "
|
|
140
149
|
|
|
141
150
|
if record["exception"] is not None:
|
|
142
151
|
record["extra"]["stack"] = stackprinter.format(record["exception"], style="darkbg3")
|
|
@@ -521,15 +530,15 @@ def _adjust_open_close_time_indent_secs(timeframe: pd.Timedelta | None, original
|
|
|
521
530
|
return original_indent_secs
|
|
522
531
|
|
|
523
532
|
# - if it triggers at daily+ bar let's assume this bar is 'closed' 5 min before exact closing time
|
|
524
|
-
if timeframe >=
|
|
533
|
+
if timeframe >= to_timedelta("1d"):
|
|
525
534
|
return max(original_indent_secs, 5 * 60)
|
|
526
535
|
|
|
527
536
|
# - if it triggers at 1Min+ bar let's assume this bar is 'closed' 5 sec before exact closing time
|
|
528
|
-
if timeframe >=
|
|
537
|
+
if timeframe >= to_timedelta("1min"):
|
|
529
538
|
return max(original_indent_secs, 5)
|
|
530
539
|
|
|
531
540
|
# - for all sub-minute timeframes just use 1 sec shift
|
|
532
|
-
if timeframe >
|
|
541
|
+
if timeframe > to_timedelta("1s"):
|
|
533
542
|
return max(original_indent_secs, 1)
|
|
534
543
|
|
|
535
544
|
# - for rest just keep original indent
|
|
@@ -538,9 +547,9 @@ def _adjust_open_close_time_indent_secs(timeframe: pd.Timedelta | None, original
|
|
|
538
547
|
|
|
539
548
|
def _get_default_warmup_period(base_subscription: str, in_timeframe: pd.Timedelta | None) -> pd.Timedelta:
|
|
540
549
|
if in_timeframe is None or base_subscription in [DataType.QUOTE, DataType.TRADE, DataType.ORDERBOOK]:
|
|
541
|
-
return
|
|
550
|
+
return to_timedelta("1Min")
|
|
542
551
|
|
|
543
|
-
if in_timeframe <
|
|
552
|
+
if in_timeframe < to_timedelta("1h"):
|
|
544
553
|
return 5 * in_timeframe
|
|
545
554
|
|
|
546
555
|
return 2 * in_timeframe
|
|
@@ -230,15 +230,6 @@ def run(
|
|
|
230
230
|
@click.option(
|
|
231
231
|
"--report", "-r", default=None, type=str, help="Output directory for simulation reports.", show_default=True
|
|
232
232
|
)
|
|
233
|
-
@click.option(
|
|
234
|
-
"--log",
|
|
235
|
-
"-L",
|
|
236
|
-
"log_to_file",
|
|
237
|
-
is_flag=True,
|
|
238
|
-
default=False,
|
|
239
|
-
help="Write simulation logs to a file in the output directory.",
|
|
240
|
-
show_default=True,
|
|
241
|
-
)
|
|
242
233
|
@click.option(
|
|
243
234
|
"--name",
|
|
244
235
|
"-n",
|
|
@@ -261,7 +252,6 @@ def simulate(
|
|
|
261
252
|
end: str | None,
|
|
262
253
|
output: str | None,
|
|
263
254
|
report: str | None,
|
|
264
|
-
log_to_file: bool,
|
|
265
255
|
name: str | None,
|
|
266
256
|
log_file: str | None,
|
|
267
257
|
):
|
|
@@ -276,7 +266,7 @@ def simulate(
|
|
|
276
266
|
logo()
|
|
277
267
|
logger.info(f"Process PID: <g>{os.getpid()}</g>")
|
|
278
268
|
simulate_strategy(
|
|
279
|
-
config_file, output, start, end, report,
|
|
269
|
+
config_file, output, start, end, report, name=name, log_file=log_file,
|
|
280
270
|
)
|
|
281
271
|
|
|
282
272
|
|