Qubx 1.3.3.dev2__tar.gz → 1.3.5__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.3.3.dev2 → qubx-1.3.5}/PKG-INFO +1 -1
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/_version.py +2 -2
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/commands.py +0 -19
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/release.py +2 -6
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/s3.py +0 -92
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/interfaces.py +3 -2
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/series.pxd +3 -2
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/series.pyi +8 -1
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/series.pyx +69 -9
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/emitters/base.py +29 -5
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/emitters/composite.py +20 -4
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/emitters/csv.py +65 -4
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/emitters/prometheus.py +65 -2
- {qubx-1.3.3.dev2 → qubx-1.3.5}/.gitignore +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/LICENSE +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/README.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/pyproject.toml +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/scripts/build.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/_nb_magic.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/iteratedstream.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/management.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/ome.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/optimization.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/runner.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/simulator.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/transfers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/backtester/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/deploy.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/misc.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/resolver.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/theme.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/tui.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/cli/user_config.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/config.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/rate_limits.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/registry.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/control/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/control/builtin.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/control/decorator.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/control/executor.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/control/interfaces.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/control/server.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/control/types.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/basics.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/context.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/detectors/delisting.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/detectors/stale.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/errors.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/exceptions.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/helpers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/initializer.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/loggers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/lookups.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/metrics.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/mixins/market.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/utils.pyi +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/core/utils.pyx +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/cache.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/containers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/guards.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/registry.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/storage.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/storages/ccxt.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/storages/csv.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/storages/handy.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/storages/multi.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/storages/questdb.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/storages/stub.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/storages/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/data/transformers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/emitters/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/emitters/indicator.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/emitters/questdb.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/composite.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/exporters/slack.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/gathering/simplest.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/health/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/health/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/health/dummy.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/loggers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/loggers/csv.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/loggers/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/loggers/mongo.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/loggers/postgres.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/notifications/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/notifications/composite.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/notifications/slack.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/notifications/throttler.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/pandaz/stats.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/pandaz/ta.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/pandaz/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/plugins/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/plugins/loader.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/rate_limiting/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/rate_limiting/backend.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/rate_limiting/config.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/rate_limiting/engine.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/rate_limiting/ip_resolver.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/rate_limiting/manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/rate_limiting/pools.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/rate_limiting/redis_backend.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/_build.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restarts/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restorers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restorers/balance.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restorers/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restorers/position.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restorers/signal.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restorers/state.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/restorers/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/state/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/state/dummy.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/state/redis.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/ta/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/project/template.yml +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.gitignore.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.python-version +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.vscode/launch.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.vscode/settings.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/README.md.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/configs/.gitkeep +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/justfile.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/research/.gitkeep +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/template.yml +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/trackers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/trackers/advanced.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/trackers/composite.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/trackers/sizers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/collections.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/misc.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/nonce.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/ntp.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/orderbook.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/questdb.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/rate_limiter.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/results.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/runner.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/app.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/handlers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/init_code.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/s3.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/slack.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/throttler.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/time.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/src/qubx/utils/websocket_manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.5}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 3,
|
|
21
|
+
__version__ = version = '1.3.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 3, 5)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1060,25 +1060,6 @@ def s3_rm_cmd(path: str, recursive: bool, yes: bool):
|
|
|
1060
1060
|
s3_rm(path, recursive=recursive)
|
|
1061
1061
|
|
|
1062
1062
|
|
|
1063
|
-
@s3.command("parquet-stats")
|
|
1064
|
-
@click.argument("path", type=str)
|
|
1065
|
-
@click.option("--per-column", is_flag=True, help="Show per-column chunk sizes for row group 0.")
|
|
1066
|
-
def s3_parquet_stats_cmd(path: str, per_column: bool):
|
|
1067
|
-
"""
|
|
1068
|
-
Print parquet file-level summary and row-group size distribution.
|
|
1069
|
-
|
|
1070
|
-
Accepts both S3 paths (account:bucket/key) and local paths. Reads only
|
|
1071
|
-
the footer, so the full file is NOT downloaded.
|
|
1072
|
-
|
|
1073
|
-
Examples:\n
|
|
1074
|
-
qubx s3 parquet-stats r2:dvault-features/.../part-2026-03.parquet\n
|
|
1075
|
-
qubx s3 parquet-stats ./local.parquet --per-column
|
|
1076
|
-
"""
|
|
1077
|
-
from .s3 import s3_parquet_stats
|
|
1078
|
-
|
|
1079
|
-
s3_parquet_stats(path, per_column=per_column)
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
1063
|
@s3.command("cp")
|
|
1083
1064
|
@click.argument("src", type=str)
|
|
1084
1065
|
@click.argument("dst", type=str)
|
|
@@ -1454,16 +1454,12 @@ def _bundle_source_overrides(
|
|
|
1454
1454
|
subprocess.run(["git", "clone", git_url, checkout_dir], check=True, capture_output=True, text=True)
|
|
1455
1455
|
subprocess.run(["git", "checkout", commit_sha], cwd=checkout_dir, check=True, capture_output=True, text=True)
|
|
1456
1456
|
|
|
1457
|
-
|
|
1458
|
-
subdir = source.get("subdirectory")
|
|
1459
|
-
build_cwd = os.path.join(checkout_dir, subdir) if subdir else checkout_dir
|
|
1460
|
-
|
|
1461
|
-
logger.info(f" Bundling {pkg_name}=={pkg_ver} from {build_cwd} ...")
|
|
1457
|
+
logger.info(f" Bundling {pkg_name}=={pkg_ver} from {checkout_dir} ...")
|
|
1462
1458
|
os.makedirs(wheels_dir, exist_ok=True)
|
|
1463
1459
|
try:
|
|
1464
1460
|
result = subprocess.run(
|
|
1465
1461
|
["uv", "build", "--wheel", ".", "--out-dir", wheels_dir],
|
|
1466
|
-
cwd=
|
|
1462
|
+
cwd=checkout_dir,
|
|
1467
1463
|
check=True,
|
|
1468
1464
|
capture_output=True,
|
|
1469
1465
|
text=True,
|
|
@@ -109,98 +109,6 @@ def s3_cp(src: str, dst: str, recursive: bool = False) -> None:
|
|
|
109
109
|
raise click.Abort()
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
def s3_parquet_stats(path: str, per_column: bool = False) -> None:
|
|
113
|
-
"""Print file-level summary + row-group size distribution for one parquet file.
|
|
114
|
-
|
|
115
|
-
Accepts either an S3 URI (``account:bucket/key``) or a local filesystem path.
|
|
116
|
-
Uses pyarrow for metadata parsing so no data pages are fetched.
|
|
117
|
-
"""
|
|
118
|
-
import pyarrow.parquet as pq
|
|
119
|
-
from pyarrow.fs import LocalFileSystem
|
|
120
|
-
|
|
121
|
-
# Heuristic: an S3 URI has "account:..." where account is non-empty and
|
|
122
|
-
# contains no path separator. Fall back to local for anything else.
|
|
123
|
-
is_s3 = ":" in path and "/" not in path.split(":", 1)[0] and not path.startswith("/")
|
|
124
|
-
if is_s3:
|
|
125
|
-
try:
|
|
126
|
-
client, s3_path = S3Client.from_uri(path)
|
|
127
|
-
except ValueError as e:
|
|
128
|
-
raise click.BadParameter(str(e))
|
|
129
|
-
fs = client.fs
|
|
130
|
-
open_path = s3_path
|
|
131
|
-
else:
|
|
132
|
-
fs = LocalFileSystem()
|
|
133
|
-
open_path = path
|
|
134
|
-
|
|
135
|
-
try:
|
|
136
|
-
with fs.open_input_file(open_path) as f:
|
|
137
|
-
meta = pq.ParquetFile(f).metadata
|
|
138
|
-
except Exception as e:
|
|
139
|
-
click.echo(click.style(f"Error reading {path}: {e}", fg="red"), err=True)
|
|
140
|
-
raise click.Abort()
|
|
141
|
-
|
|
142
|
-
click.echo(click.style(f"File: {path}", fg="cyan", bold=True))
|
|
143
|
-
click.echo(f" num_rows: {meta.num_rows:,}")
|
|
144
|
-
click.echo(f" num_row_groups: {meta.num_row_groups}")
|
|
145
|
-
click.echo(f" num_columns: {meta.num_columns}")
|
|
146
|
-
click.echo(f" format_version: {meta.format_version}")
|
|
147
|
-
if meta.created_by:
|
|
148
|
-
click.echo(f" created_by: {meta.created_by}")
|
|
149
|
-
|
|
150
|
-
if meta.num_row_groups == 0:
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
# Parquet spec: RowGroup.total_byte_size is the SUM of uncompressed
|
|
154
|
-
# column sizes. The compressed on-disk size is the sum of
|
|
155
|
-
# ColumnChunk.total_compressed_size across columns.
|
|
156
|
-
rows: list[int] = []
|
|
157
|
-
per_rg_compressed: list[int] = []
|
|
158
|
-
per_rg_uncompressed: list[int] = []
|
|
159
|
-
compressions: set[str] = set()
|
|
160
|
-
for i in range(meta.num_row_groups):
|
|
161
|
-
rg = meta.row_group(i)
|
|
162
|
-
rows.append(rg.num_rows)
|
|
163
|
-
c_sz = 0
|
|
164
|
-
u_sz = 0
|
|
165
|
-
for c in range(rg.num_columns):
|
|
166
|
-
col = rg.column(c)
|
|
167
|
-
c_sz += col.total_compressed_size
|
|
168
|
-
u_sz += col.total_uncompressed_size
|
|
169
|
-
compressions.add(str(col.compression))
|
|
170
|
-
per_rg_compressed.append(c_sz)
|
|
171
|
-
per_rg_uncompressed.append(u_sz)
|
|
172
|
-
|
|
173
|
-
total_compressed = sum(per_rg_compressed)
|
|
174
|
-
total_uncompressed = sum(per_rg_uncompressed)
|
|
175
|
-
click.echo()
|
|
176
|
-
click.echo(click.style("Row groups:", fg="cyan", bold=True))
|
|
177
|
-
click.echo(f" rows per rg: min={min(rows):,} max={max(rows):,} avg={sum(rows)//len(rows):,}")
|
|
178
|
-
click.echo(
|
|
179
|
-
f" cmp per rg: min={_human_size(min(per_rg_compressed))} "
|
|
180
|
-
f"max={_human_size(max(per_rg_compressed))} avg={_human_size(sum(per_rg_compressed) // len(per_rg_compressed))}"
|
|
181
|
-
)
|
|
182
|
-
click.echo(f" total: compressed={_human_size(total_compressed)} uncompressed={_human_size(total_uncompressed)}")
|
|
183
|
-
if total_uncompressed > 0:
|
|
184
|
-
ratio = 100 * total_compressed / total_uncompressed
|
|
185
|
-
click.echo(f" compression: {', '.join(sorted(compressions))} ({ratio:.1f}% of uncompressed)")
|
|
186
|
-
else:
|
|
187
|
-
click.echo(f" compression: {', '.join(sorted(compressions))}")
|
|
188
|
-
|
|
189
|
-
if per_column:
|
|
190
|
-
click.echo()
|
|
191
|
-
click.echo(click.style("Columns (row group 0):", fg="cyan", bold=True))
|
|
192
|
-
rg0 = meta.row_group(0)
|
|
193
|
-
click.echo(f" {'path':30s} {'type':12s} {'compressed':>11s} {'uncompressed':>12s} pct compression")
|
|
194
|
-
for c in range(rg0.num_columns):
|
|
195
|
-
col = rg0.column(c)
|
|
196
|
-
pct = (100 * col.total_compressed_size / col.total_uncompressed_size) if col.total_uncompressed_size else 0.0
|
|
197
|
-
click.echo(
|
|
198
|
-
f" {col.path_in_schema:30s} {str(col.physical_type):12s} "
|
|
199
|
-
f"{_human_size(col.total_compressed_size):>11s} {_human_size(col.total_uncompressed_size):>12s} "
|
|
200
|
-
f"{pct:5.1f}% {col.compression}"
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
|
|
204
112
|
def s3_accounts() -> None:
|
|
205
113
|
s = get_settings()
|
|
206
114
|
if not s.s3:
|
|
@@ -12,6 +12,7 @@ This module includes:
|
|
|
12
12
|
- Strategy lifecycle notifiers
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
+
import datetime
|
|
15
16
|
import inspect
|
|
16
17
|
import traceback
|
|
17
18
|
from dataclasses import dataclass
|
|
@@ -2790,7 +2791,7 @@ class IMetricEmitter:
|
|
|
2790
2791
|
|
|
2791
2792
|
def emit_signals(
|
|
2792
2793
|
self,
|
|
2793
|
-
time: dt_64,
|
|
2794
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
2794
2795
|
signals: list[Signal],
|
|
2795
2796
|
account: "IAccountViewer",
|
|
2796
2797
|
target_positions: list["TargetPosition"] | None = None,
|
|
@@ -2810,7 +2811,7 @@ class IMetricEmitter:
|
|
|
2810
2811
|
|
|
2811
2812
|
def emit_deals(
|
|
2812
2813
|
self,
|
|
2813
|
-
time: dt_64,
|
|
2814
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
2814
2815
|
instrument: Instrument,
|
|
2815
2816
|
deals: list[Deal],
|
|
2816
2817
|
account: "IAccountViewer",
|
|
@@ -187,8 +187,9 @@ cdef class BundledSeries(TimeSeries):
|
|
|
187
187
|
"""
|
|
188
188
|
Virtual series that bundles fields from multiple source TimeSeries.
|
|
189
189
|
"""
|
|
190
|
-
cdef dict _fields
|
|
191
|
-
cdef list _field_names
|
|
190
|
+
cdef dict _fields # field_name -> TimeSeries
|
|
191
|
+
cdef list _field_names # ordered list of field names
|
|
192
|
+
cdef long long _align_timeframe # alignment bin (ns) for cross-source timestamp flooring
|
|
192
193
|
|
|
193
194
|
cdef double _lookup_value(BundledSeries self, TimeSeries series, long long time)
|
|
194
195
|
cdef TimeSeries _find_root(BundledSeries self, TimeSeries series)
|
|
@@ -266,7 +266,14 @@ class BundledSeries(TimeSeries):
|
|
|
266
266
|
_fields: dict[str, TimeSeries]
|
|
267
267
|
_field_names: list[str]
|
|
268
268
|
|
|
269
|
-
def __init__(
|
|
269
|
+
def __init__(
|
|
270
|
+
self,
|
|
271
|
+
name: str,
|
|
272
|
+
timeframe,
|
|
273
|
+
fields: dict[str, TimeSeries],
|
|
274
|
+
align_timeframe: str | None = ...,
|
|
275
|
+
max_series_length: int | float = ...,
|
|
276
|
+
) -> None: ...
|
|
270
277
|
def update(self, time: int, value: Any, new_item_started: bool) -> Any: ...
|
|
271
278
|
def to_series(self, length: int | None = None) -> pd.DataFrame: ...
|
|
272
279
|
|
|
@@ -126,15 +126,50 @@ cdef class Indexed:
|
|
|
126
126
|
|
|
127
127
|
def lookup_idx(self, value, str method) -> int:
|
|
128
128
|
"""
|
|
129
|
-
Find value's index in series using specified method
|
|
129
|
+
Find value's index in series using specified method.
|
|
130
|
+
|
|
131
|
+
- ffill: previous index — equivalent to np.searchsorted(side='right') - 1.
|
|
132
|
+
Returns -1 if value is below all stored items.
|
|
133
|
+
- bfill: next index — equivalent to np.searchsorted(side='left').
|
|
134
|
+
Returns -1 if value is above all stored items.
|
|
135
|
+
|
|
136
|
+
Implementation note: this is a pure-Cython binary search over the Python
|
|
137
|
+
list `self.values`. It replaces a previous `np.searchsorted` call which
|
|
138
|
+
spent the bulk of its time in `np.asarray(list)` conversion (O(n) per
|
|
139
|
+
call) — `_wrapit` was the dominant hotspot in profiles. Element
|
|
140
|
+
comparisons here go through Python rich comparison, which is fast for
|
|
141
|
+
the int/float scalars actually stored. Behavior matches the original
|
|
142
|
+
for sorted ascending input (the only contract the rest of the codebase
|
|
143
|
+
relies on).
|
|
130
144
|
"""
|
|
145
|
+
cdef int n = len(self.values)
|
|
146
|
+
cdef int lo, hi, mid
|
|
131
147
|
cdef int i0
|
|
148
|
+
|
|
132
149
|
if method == 'ffill':
|
|
133
|
-
|
|
150
|
+
# - rightmost insertion point, then -1
|
|
151
|
+
lo = 0
|
|
152
|
+
hi = n
|
|
153
|
+
while lo < hi:
|
|
154
|
+
mid = (lo + hi) >> 1
|
|
155
|
+
if self.values[mid] <= value:
|
|
156
|
+
lo = mid + 1
|
|
157
|
+
else:
|
|
158
|
+
hi = mid
|
|
159
|
+
i0 = lo
|
|
134
160
|
return max(-1, i0 - 1)
|
|
135
161
|
elif method == 'bfill':
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
# - leftmost insertion point
|
|
163
|
+
lo = 0
|
|
164
|
+
hi = n
|
|
165
|
+
while lo < hi:
|
|
166
|
+
mid = (lo + hi) >> 1
|
|
167
|
+
if self.values[mid] < value:
|
|
168
|
+
lo = mid + 1
|
|
169
|
+
else:
|
|
170
|
+
hi = mid
|
|
171
|
+
i0 = lo
|
|
172
|
+
return -1 if i0 >= n else i0
|
|
138
173
|
else:
|
|
139
174
|
raise ValueError(f"Unsupported method {method}")
|
|
140
175
|
|
|
@@ -1845,18 +1880,26 @@ cdef class BundledSeries(TimeSeries):
|
|
|
1845
1880
|
# When either vtwap or ohlcv updates, bundle gathers values and triggers spread
|
|
1846
1881
|
"""
|
|
1847
1882
|
|
|
1848
|
-
def __init__(self, str name, timeframe, dict fields, max_series_length=INFINITY):
|
|
1883
|
+
def __init__(self, str name, timeframe, dict fields, align_timeframe=None, max_series_length=INFINITY):
|
|
1849
1884
|
"""
|
|
1850
1885
|
Args:
|
|
1851
1886
|
name: Name of this bundled series
|
|
1852
1887
|
timeframe: Timeframe string (e.g., "1m", "1h")
|
|
1853
1888
|
fields: Dict mapping field names to TimeSeries
|
|
1854
1889
|
e.g., {"vwap": vtwap_ts, "close": close_ts}
|
|
1890
|
+
align_timeframe: Optional timeframe string used to bin-align source
|
|
1891
|
+
timestamps before joining (floor_t64(t, align_timeframe)).
|
|
1892
|
+
Defaults to `timeframe` when None. Use this when source series
|
|
1893
|
+
have timestamps offset within the same logical bar (e.g. OHLC
|
|
1894
|
+
at bar-open x:00:00 vs vendor data shifted to bar-close x:14:55
|
|
1895
|
+
for a 15-minute timeframe) — both floor to the same bin.
|
|
1855
1896
|
max_series_length: Maximum number of values to store
|
|
1856
1897
|
"""
|
|
1857
1898
|
super().__init__(name, timeframe, max_series_length)
|
|
1858
1899
|
self._fields = fields
|
|
1859
1900
|
self._field_names = list(fields.keys())
|
|
1901
|
+
# - alignment bin: explicit align_timeframe if provided, else self.timeframe (already ns after super().__init__)
|
|
1902
|
+
self._align_timeframe = recognize_timeframe(align_timeframe) if align_timeframe is not None else self.timeframe
|
|
1860
1903
|
|
|
1861
1904
|
# Attach to each source series' calculation chain
|
|
1862
1905
|
for field_name, series in fields.items():
|
|
@@ -1883,22 +1926,39 @@ cdef class BundledSeries(TimeSeries):
|
|
|
1883
1926
|
return series
|
|
1884
1927
|
|
|
1885
1928
|
def _initial_data_recalculate_bundled(self):
|
|
1886
|
-
"""
|
|
1887
|
-
|
|
1929
|
+
"""
|
|
1930
|
+
Align all sources by timestamp for initial calculation.
|
|
1931
|
+
|
|
1932
|
+
Each source's timestamps are floored to self._align_timeframe first, then
|
|
1933
|
+
outer-joined and forward-filled so mixed-cadence sources (e.g. 15m OHLC +
|
|
1934
|
+
1h OI) and offset-stamped sources (e.g. open-stamped OHLC x:00:00 +
|
|
1935
|
+
close-stamped vendor x:14:55 within the same 15m bin) all land in the
|
|
1936
|
+
same row. Rows where any field is still NaN after ffill are dropped —
|
|
1937
|
+
this handles warm-up bars where the slow-cadence source hasn't ticked yet.
|
|
1938
|
+
"""
|
|
1888
1939
|
cdef list dfs = []
|
|
1889
1940
|
cdef str name
|
|
1890
1941
|
cdef TimeSeries series
|
|
1942
|
+
cdef long long tf_ns = self._align_timeframe
|
|
1891
1943
|
|
|
1892
1944
|
for name, series in self._fields.items():
|
|
1893
1945
|
s = series.to_series()
|
|
1894
1946
|
if len(s) > 0:
|
|
1947
|
+
# - floor each timestamp to the align bin, keep last value within each bin
|
|
1948
|
+
floored = pd.DatetimeIndex(
|
|
1949
|
+
[pd.Timestamp(floor_t64(<long long> t.value, tf_ns)) for t in s.index]
|
|
1950
|
+
)
|
|
1951
|
+
s = s.copy()
|
|
1952
|
+
s.index = floored
|
|
1953
|
+
s = s[~s.index.duplicated(keep='last')]
|
|
1895
1954
|
dfs.append(s.rename(name))
|
|
1896
1955
|
|
|
1897
1956
|
if not dfs:
|
|
1898
1957
|
return
|
|
1899
1958
|
|
|
1900
|
-
#
|
|
1901
|
-
|
|
1959
|
+
# - outer-join on floored timestamps, ffill to carry slow-cadence sources forward,
|
|
1960
|
+
# - then drop leading rows where any field is still NaN (warm-up)
|
|
1961
|
+
aligned = pd.concat(dfs, axis=1).sort_index().ffill().dropna()
|
|
1902
1962
|
|
|
1903
1963
|
# Process each aligned row
|
|
1904
1964
|
for t in aligned.index:
|
|
@@ -4,10 +4,13 @@ Base Metric Emitter.
|
|
|
4
4
|
This module provides a base implementation of IMetricEmitter that can be extended by other emitters.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import datetime
|
|
7
8
|
from typing import Any, Dict, List, Optional, Set
|
|
8
9
|
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
9
12
|
from qubx import logger
|
|
10
|
-
from qubx.core.basics import Instrument, Signal, TargetPosition, dt_64
|
|
13
|
+
from qubx.core.basics import Deal, Instrument, Signal, TargetPosition, dt_64
|
|
11
14
|
from qubx.core.interfaces import IAccountViewer, IMetricEmitter, IStrategyContext
|
|
12
15
|
from qubx.utils.time import to_timedelta
|
|
13
16
|
|
|
@@ -199,10 +202,10 @@ class BaseMetricEmitter(IMetricEmitter):
|
|
|
199
202
|
|
|
200
203
|
def emit_signals(
|
|
201
204
|
self,
|
|
202
|
-
time: dt_64,
|
|
203
|
-
signals: list[
|
|
204
|
-
account:
|
|
205
|
-
target_positions: list[
|
|
205
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
206
|
+
signals: list[Signal],
|
|
207
|
+
account: IAccountViewer,
|
|
208
|
+
target_positions: list[TargetPosition] | None = None,
|
|
206
209
|
) -> None:
|
|
207
210
|
"""
|
|
208
211
|
Emit signals to the monitoring system.
|
|
@@ -218,6 +221,27 @@ class BaseMetricEmitter(IMetricEmitter):
|
|
|
218
221
|
"""
|
|
219
222
|
pass
|
|
220
223
|
|
|
224
|
+
def emit_deals(
|
|
225
|
+
self,
|
|
226
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
227
|
+
instrument: Instrument,
|
|
228
|
+
deals: list[Deal],
|
|
229
|
+
account: IAccountViewer,
|
|
230
|
+
) -> None:
|
|
231
|
+
"""
|
|
232
|
+
Emit deals to the monitoring system.
|
|
233
|
+
|
|
234
|
+
Base implementation does nothing - subclasses should override this method
|
|
235
|
+
to implement specific deal emission logic.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
time: Timestamp when the deals were generated
|
|
239
|
+
instrument: Instrument the deals belong to
|
|
240
|
+
deals: List of deals to emit
|
|
241
|
+
account: Account viewer to get account information like total capital, leverage, etc.
|
|
242
|
+
"""
|
|
243
|
+
pass
|
|
244
|
+
|
|
221
245
|
def notify(self, context: IStrategyContext) -> None:
|
|
222
246
|
"""
|
|
223
247
|
Notify the metric emitter of a time update.
|
|
@@ -4,10 +4,13 @@ Composite Metric Emitter.
|
|
|
4
4
|
This module provides a composite implementation of IMetricEmitter that delegates to multiple emitters.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import datetime
|
|
7
8
|
from typing import Dict, List, Optional
|
|
8
9
|
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
9
12
|
from qubx import logger
|
|
10
|
-
from qubx.core.basics import Deal, Instrument, Signal, dt_64
|
|
13
|
+
from qubx.core.basics import Deal, Instrument, Signal, TargetPosition, dt_64
|
|
11
14
|
from qubx.core.interfaces import IAccountViewer, IMetricEmitter, IStrategyContext
|
|
12
15
|
from qubx.emitters.base import BaseMetricEmitter
|
|
13
16
|
|
|
@@ -70,7 +73,13 @@ class CompositeMetricEmitter(BaseMetricEmitter):
|
|
|
70
73
|
except Exception as e:
|
|
71
74
|
logger.error(f"Error emitting strategy stats to {emitter.__class__.__name__}: {e}")
|
|
72
75
|
|
|
73
|
-
def emit_signals(
|
|
76
|
+
def emit_signals(
|
|
77
|
+
self,
|
|
78
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
79
|
+
signals: list[Signal],
|
|
80
|
+
account: IAccountViewer,
|
|
81
|
+
target_positions: list[TargetPosition] | None = None,
|
|
82
|
+
) -> None:
|
|
74
83
|
"""
|
|
75
84
|
Emit signals to all configured emitters.
|
|
76
85
|
|
|
@@ -78,14 +87,21 @@ class CompositeMetricEmitter(BaseMetricEmitter):
|
|
|
78
87
|
time: Timestamp when the signals were generated
|
|
79
88
|
signals: List of signals to emit
|
|
80
89
|
account: Account viewer to get account information
|
|
90
|
+
target_positions: List of target positions (optional)
|
|
81
91
|
"""
|
|
82
92
|
for emitter in self._emitters:
|
|
83
93
|
try:
|
|
84
|
-
emitter.emit_signals(time, signals, account)
|
|
94
|
+
emitter.emit_signals(time, signals, account, target_positions)
|
|
85
95
|
except Exception as e:
|
|
86
96
|
logger.error(f"Error emitting signals to {emitter.__class__.__name__}: {e}")
|
|
87
97
|
|
|
88
|
-
def emit_deals(
|
|
98
|
+
def emit_deals(
|
|
99
|
+
self,
|
|
100
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
101
|
+
instrument: Instrument,
|
|
102
|
+
deals: list[Deal],
|
|
103
|
+
account: IAccountViewer,
|
|
104
|
+
) -> None:
|
|
89
105
|
"""
|
|
90
106
|
Emit deals to all configured emitters.
|
|
91
107
|
|
|
@@ -4,12 +4,15 @@ CSV Metric Emitter.
|
|
|
4
4
|
This module provides an implementation of IMetricEmitter that exports metrics to a CSV file.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import datetime
|
|
7
8
|
import os
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
11
14
|
from qubx import logger
|
|
12
|
-
from qubx.core.basics import Signal, dt_64
|
|
15
|
+
from qubx.core.basics import Deal, Instrument, Signal, TargetPosition, dt_64
|
|
13
16
|
from qubx.core.interfaces import IAccountViewer
|
|
14
17
|
from qubx.emitters.base import BaseMetricEmitter
|
|
15
18
|
from qubx.utils.ntp import time_now
|
|
@@ -84,7 +87,13 @@ class CSVMetricEmitter(BaseMetricEmitter):
|
|
|
84
87
|
except Exception as e:
|
|
85
88
|
logger.error(f"[CSVMetricEmitter] Failed to emit metric {name}: {e}")
|
|
86
89
|
|
|
87
|
-
def emit_signals(
|
|
90
|
+
def emit_signals(
|
|
91
|
+
self,
|
|
92
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
93
|
+
signals: list[Signal],
|
|
94
|
+
account: IAccountViewer,
|
|
95
|
+
target_positions: list[TargetPosition] | None = None,
|
|
96
|
+
) -> None:
|
|
88
97
|
"""
|
|
89
98
|
Emit signals to CSV file.
|
|
90
99
|
|
|
@@ -92,10 +101,16 @@ class CSVMetricEmitter(BaseMetricEmitter):
|
|
|
92
101
|
time: Timestamp when the signals were generated
|
|
93
102
|
signals: List of signals to emit
|
|
94
103
|
account: Account viewer to get account information
|
|
104
|
+
target_positions: Optional list of target positions generated from the signals
|
|
95
105
|
"""
|
|
96
106
|
if not signals:
|
|
97
107
|
return
|
|
98
108
|
|
|
109
|
+
target_positions_map: dict[Instrument, TargetPosition] = {}
|
|
110
|
+
if target_positions:
|
|
111
|
+
for target in target_positions:
|
|
112
|
+
target_positions_map[target.instrument] = target
|
|
113
|
+
|
|
99
114
|
try:
|
|
100
115
|
# Create a signals-specific CSV file
|
|
101
116
|
signals_file_path = self._file_path.parent / f"signals_{self._file_path.stem}.csv"
|
|
@@ -104,7 +119,8 @@ class CSVMetricEmitter(BaseMetricEmitter):
|
|
|
104
119
|
if not signals_file_path.exists():
|
|
105
120
|
with open(signals_file_path, "w") as f:
|
|
106
121
|
f.write(
|
|
107
|
-
"timestamp,symbol,exchange,signal,price,stop,take,reference_price,
|
|
122
|
+
"timestamp,symbol,exchange,signal,price,stop,take,reference_price,"
|
|
123
|
+
"target_position_size,group,comment,is_service\n"
|
|
108
124
|
)
|
|
109
125
|
|
|
110
126
|
# Write each signal to the CSV file
|
|
@@ -114,13 +130,58 @@ class CSVMetricEmitter(BaseMetricEmitter):
|
|
|
114
130
|
stop = signal.stop if signal.stop is not None else ""
|
|
115
131
|
take = signal.take if signal.take is not None else ""
|
|
116
132
|
ref_price = signal.reference_price if signal.reference_price is not None else ""
|
|
133
|
+
target = target_positions_map.get(signal.instrument)
|
|
134
|
+
target_size = target.target_position_size if target is not None else ""
|
|
117
135
|
|
|
118
136
|
with open(signals_file_path, "a") as f:
|
|
119
137
|
f.write(
|
|
120
138
|
f"{signal_time},{signal.instrument.symbol},{signal.instrument.exchange},"
|
|
121
|
-
f"{signal.signal},{price},{stop},{take},{ref_price},"
|
|
139
|
+
f"{signal.signal},{price},{stop},{take},{ref_price},{target_size},"
|
|
122
140
|
f"{signal.group},{signal.comment},{signal.is_service}\n"
|
|
123
141
|
)
|
|
124
142
|
|
|
125
143
|
except Exception as e:
|
|
126
144
|
logger.error(f"[CSVMetricEmitter] Failed to emit signals: {e}")
|
|
145
|
+
|
|
146
|
+
def emit_deals(
|
|
147
|
+
self,
|
|
148
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
149
|
+
instrument: Instrument,
|
|
150
|
+
deals: list[Deal],
|
|
151
|
+
account: IAccountViewer,
|
|
152
|
+
) -> None:
|
|
153
|
+
"""
|
|
154
|
+
Emit deals to CSV file.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
time: Timestamp when the deals were generated
|
|
158
|
+
instrument: Instrument the deals belong to
|
|
159
|
+
deals: List of deals to emit
|
|
160
|
+
account: Account viewer to get account information
|
|
161
|
+
"""
|
|
162
|
+
if not deals:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
deals_file_path = self._file_path.parent / f"deals_{self._file_path.stem}.csv"
|
|
167
|
+
|
|
168
|
+
if not deals_file_path.exists():
|
|
169
|
+
with open(deals_file_path, "w") as f:
|
|
170
|
+
f.write(
|
|
171
|
+
"timestamp,symbol,exchange,amount,price,aggressive,"
|
|
172
|
+
"fee_amount,fee_currency,deal_id,order_id\n"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
with open(deals_file_path, "a") as f:
|
|
176
|
+
for deal in deals:
|
|
177
|
+
deal_time = str(deal.time) if hasattr(deal.time, "__str__") else str(time)
|
|
178
|
+
fee_amount = deal.fee_amount if deal.fee_amount is not None else ""
|
|
179
|
+
fee_currency = deal.fee_currency if deal.fee_currency is not None else ""
|
|
180
|
+
f.write(
|
|
181
|
+
f"{deal_time},{instrument.symbol},{instrument.exchange},"
|
|
182
|
+
f"{deal.amount},{deal.price},{deal.aggressive},"
|
|
183
|
+
f"{fee_amount},{fee_currency},{deal.id},{deal.order_id}\n"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.error(f"[CSVMetricEmitter] Failed to emit deals: {e}")
|