Qubx 1.3.3.dev2__tar.gz → 1.3.4__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.4}/PKG-INFO +1 -1
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/_version.py +2 -2
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/commands.py +0 -19
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/release.py +2 -6
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/s3.py +0 -92
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/interfaces.py +3 -2
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/series.pxd +3 -2
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/series.pyi +8 -1
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/series.pyx +30 -5
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/base.py +29 -5
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/composite.py +20 -4
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/csv.py +65 -4
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/prometheus.py +65 -2
- {qubx-1.3.3.dev2 → qubx-1.3.4}/.gitignore +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/LICENSE +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/README.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/pyproject.toml +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/scripts/build.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/_nb_magic.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/iteratedstream.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/management.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/ome.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/optimization.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/runner.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/simulator.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/transfers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/deploy.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/misc.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/resolver.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/theme.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/tui.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/user_config.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/config.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/rate_limits.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/registry.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/builtin.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/decorator.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/executor.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/interfaces.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/server.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/types.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/account.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/basics.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/context.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/detectors/delisting.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/detectors/stale.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/errors.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/exceptions.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/helpers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/initializer.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/loggers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/lookups.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/metrics.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/market.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/utils.pyi +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/utils.pyx +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/cache.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/containers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/guards.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/registry.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storage.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/ccxt.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/csv.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/handy.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/multi.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/questdb.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/stub.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/transformers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/indicator.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/questdb.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/composite.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/slack.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/gathering/simplest.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/health/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/health/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/health/dummy.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/csv.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/mongo.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/postgres.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/notifications/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/notifications/composite.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/notifications/slack.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/notifications/throttler.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/pandaz/stats.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/pandaz/ta.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/pandaz/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/plugins/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/plugins/loader.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/backend.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/config.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/engine.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/ip_resolver.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/pools.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/redis_backend.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/_build.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restarts/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/balance.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/position.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/signal.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/state.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/state/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/state/dummy.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/state/redis.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/ta/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/base.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/template.yml +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.gitignore.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.python-version +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.vscode/launch.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.vscode/settings.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/README.md.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/configs/.gitkeep +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/justfile.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/research/.gitkeep +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/template.yml +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/advanced.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/composite.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/sizers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/collections.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/misc.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/nonce.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/ntp.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/orderbook.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/questdb.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/rate_limiter.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/results.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/runner.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/app.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/handlers.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/init_code.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/s3.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/slack.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/throttler.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/time.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/websocket_manager.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
- {qubx-1.3.3.dev2 → qubx-1.3.4}/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.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 3, 4)
|
|
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
|
|
|
@@ -1845,18 +1845,26 @@ cdef class BundledSeries(TimeSeries):
|
|
|
1845
1845
|
# When either vtwap or ohlcv updates, bundle gathers values and triggers spread
|
|
1846
1846
|
"""
|
|
1847
1847
|
|
|
1848
|
-
def __init__(self, str name, timeframe, dict fields, max_series_length=INFINITY):
|
|
1848
|
+
def __init__(self, str name, timeframe, dict fields, align_timeframe=None, max_series_length=INFINITY):
|
|
1849
1849
|
"""
|
|
1850
1850
|
Args:
|
|
1851
1851
|
name: Name of this bundled series
|
|
1852
1852
|
timeframe: Timeframe string (e.g., "1m", "1h")
|
|
1853
1853
|
fields: Dict mapping field names to TimeSeries
|
|
1854
1854
|
e.g., {"vwap": vtwap_ts, "close": close_ts}
|
|
1855
|
+
align_timeframe: Optional timeframe string used to bin-align source
|
|
1856
|
+
timestamps before joining (floor_t64(t, align_timeframe)).
|
|
1857
|
+
Defaults to `timeframe` when None. Use this when source series
|
|
1858
|
+
have timestamps offset within the same logical bar (e.g. OHLC
|
|
1859
|
+
at bar-open x:00:00 vs vendor data shifted to bar-close x:14:55
|
|
1860
|
+
for a 15-minute timeframe) — both floor to the same bin.
|
|
1855
1861
|
max_series_length: Maximum number of values to store
|
|
1856
1862
|
"""
|
|
1857
1863
|
super().__init__(name, timeframe, max_series_length)
|
|
1858
1864
|
self._fields = fields
|
|
1859
1865
|
self._field_names = list(fields.keys())
|
|
1866
|
+
# - alignment bin: explicit align_timeframe if provided, else self.timeframe (already ns after super().__init__)
|
|
1867
|
+
self._align_timeframe = recognize_timeframe(align_timeframe) if align_timeframe is not None else self.timeframe
|
|
1860
1868
|
|
|
1861
1869
|
# Attach to each source series' calculation chain
|
|
1862
1870
|
for field_name, series in fields.items():
|
|
@@ -1883,22 +1891,39 @@ cdef class BundledSeries(TimeSeries):
|
|
|
1883
1891
|
return series
|
|
1884
1892
|
|
|
1885
1893
|
def _initial_data_recalculate_bundled(self):
|
|
1886
|
-
"""
|
|
1887
|
-
|
|
1894
|
+
"""
|
|
1895
|
+
Align all sources by timestamp for initial calculation.
|
|
1896
|
+
|
|
1897
|
+
Each source's timestamps are floored to self._align_timeframe first, then
|
|
1898
|
+
outer-joined and forward-filled so mixed-cadence sources (e.g. 15m OHLC +
|
|
1899
|
+
1h OI) and offset-stamped sources (e.g. open-stamped OHLC x:00:00 +
|
|
1900
|
+
close-stamped vendor x:14:55 within the same 15m bin) all land in the
|
|
1901
|
+
same row. Rows where any field is still NaN after ffill are dropped —
|
|
1902
|
+
this handles warm-up bars where the slow-cadence source hasn't ticked yet.
|
|
1903
|
+
"""
|
|
1888
1904
|
cdef list dfs = []
|
|
1889
1905
|
cdef str name
|
|
1890
1906
|
cdef TimeSeries series
|
|
1907
|
+
cdef long long tf_ns = self._align_timeframe
|
|
1891
1908
|
|
|
1892
1909
|
for name, series in self._fields.items():
|
|
1893
1910
|
s = series.to_series()
|
|
1894
1911
|
if len(s) > 0:
|
|
1912
|
+
# - floor each timestamp to the align bin, keep last value within each bin
|
|
1913
|
+
floored = pd.DatetimeIndex(
|
|
1914
|
+
[pd.Timestamp(floor_t64(<long long> t.value, tf_ns)) for t in s.index]
|
|
1915
|
+
)
|
|
1916
|
+
s = s.copy()
|
|
1917
|
+
s.index = floored
|
|
1918
|
+
s = s[~s.index.duplicated(keep='last')]
|
|
1895
1919
|
dfs.append(s.rename(name))
|
|
1896
1920
|
|
|
1897
1921
|
if not dfs:
|
|
1898
1922
|
return
|
|
1899
1923
|
|
|
1900
|
-
#
|
|
1901
|
-
|
|
1924
|
+
# - outer-join on floored timestamps, ffill to carry slow-cadence sources forward,
|
|
1925
|
+
# - then drop leading rows where any field is still NaN (warm-up)
|
|
1926
|
+
aligned = pd.concat(dfs, axis=1).sort_index().ffill().dropna()
|
|
1902
1927
|
|
|
1903
1928
|
# Process each aligned row
|
|
1904
1929
|
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}")
|
|
@@ -4,12 +4,14 @@ Prometheus Metric Emitter.
|
|
|
4
4
|
This module provides an implementation of IMetricEmitter that exports metrics to Prometheus.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import datetime
|
|
7
8
|
from typing import Any, Dict, List, Literal, Optional
|
|
8
9
|
|
|
10
|
+
import pandas as pd
|
|
9
11
|
from prometheus_client import REGISTRY, Counter, Gauge, Summary, push_to_gateway
|
|
10
12
|
|
|
11
13
|
from qubx import logger
|
|
12
|
-
from qubx.core.basics import Instrument, Signal, dt_64
|
|
14
|
+
from qubx.core.basics import Deal, Instrument, Signal, TargetPosition, dt_64
|
|
13
15
|
from qubx.core.interfaces import IAccountViewer, IStrategyContext
|
|
14
16
|
from qubx.emitters.base import BaseMetricEmitter
|
|
15
17
|
|
|
@@ -223,7 +225,13 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
|
|
|
223
225
|
except Exception as e:
|
|
224
226
|
logger.error(f"[PrometheusMetricEmitter] Failed to push metrics to gateway: {e}")
|
|
225
227
|
|
|
226
|
-
def emit_signals(
|
|
228
|
+
def emit_signals(
|
|
229
|
+
self,
|
|
230
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
231
|
+
signals: list[Signal],
|
|
232
|
+
account: IAccountViewer,
|
|
233
|
+
target_positions: list[TargetPosition] | None = None,
|
|
234
|
+
) -> None:
|
|
227
235
|
"""
|
|
228
236
|
Emit signals as Prometheus metrics.
|
|
229
237
|
|
|
@@ -231,10 +239,16 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
|
|
|
231
239
|
time: Timestamp when the signals were generated
|
|
232
240
|
signals: List of signals to emit
|
|
233
241
|
account: Account viewer to get account information
|
|
242
|
+
target_positions: Optional list of target positions generated from the signals
|
|
234
243
|
"""
|
|
235
244
|
if not signals:
|
|
236
245
|
return
|
|
237
246
|
|
|
247
|
+
target_positions_map: dict[Instrument, TargetPosition] = {}
|
|
248
|
+
if target_positions:
|
|
249
|
+
for target in target_positions:
|
|
250
|
+
target_positions_map[target.instrument] = target
|
|
251
|
+
|
|
238
252
|
try:
|
|
239
253
|
for signal in signals:
|
|
240
254
|
# Create labels for the signal
|
|
@@ -266,6 +280,11 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
|
|
|
266
280
|
ref_price_gauge = self._get_or_create_gauge("signal_reference_price", labels)
|
|
267
281
|
ref_price_gauge.labels(**labels).set(signal.reference_price)
|
|
268
282
|
|
|
283
|
+
target = target_positions_map.get(signal.instrument)
|
|
284
|
+
if target is not None:
|
|
285
|
+
target_gauge = self._get_or_create_gauge("signal_target_position_size", labels)
|
|
286
|
+
target_gauge.labels(**labels).set(target.target_position_size)
|
|
287
|
+
|
|
269
288
|
# Push to gateway if configured
|
|
270
289
|
if self._pushgateway_url:
|
|
271
290
|
try:
|
|
@@ -278,6 +297,50 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
|
|
|
278
297
|
except Exception as e:
|
|
279
298
|
logger.error(f"[PrometheusMetricEmitter] Failed to emit signals: {e}")
|
|
280
299
|
|
|
300
|
+
def emit_deals(
|
|
301
|
+
self,
|
|
302
|
+
time: dt_64 | pd.Timestamp | datetime.datetime,
|
|
303
|
+
instrument: Instrument,
|
|
304
|
+
deals: list[Deal],
|
|
305
|
+
account: IAccountViewer,
|
|
306
|
+
) -> None:
|
|
307
|
+
"""
|
|
308
|
+
Emit deals as Prometheus metrics.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
time: Timestamp when the deals were generated
|
|
312
|
+
instrument: Instrument the deals belong to
|
|
313
|
+
deals: List of deals to emit
|
|
314
|
+
account: Account viewer to get account information
|
|
315
|
+
"""
|
|
316
|
+
if not deals:
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
labels = {
|
|
321
|
+
"symbol": instrument.symbol,
|
|
322
|
+
"exchange": instrument.exchange,
|
|
323
|
+
}
|
|
324
|
+
count_counter = self._get_or_create_counter("deal_count", labels)
|
|
325
|
+
amount_gauge = self._get_or_create_gauge("deal_amount", labels)
|
|
326
|
+
price_gauge = self._get_or_create_gauge("deal_price", labels)
|
|
327
|
+
|
|
328
|
+
for deal in deals:
|
|
329
|
+
count_counter.labels(**labels).inc()
|
|
330
|
+
amount_gauge.labels(**labels).set(deal.amount)
|
|
331
|
+
price_gauge.labels(**labels).set(deal.price)
|
|
332
|
+
|
|
333
|
+
if self._pushgateway_url:
|
|
334
|
+
try:
|
|
335
|
+
push_to_gateway(
|
|
336
|
+
self._pushgateway_url, job=f"{self._namespace}_{self._strategy_name}", registry=self._registry
|
|
337
|
+
)
|
|
338
|
+
except Exception as e:
|
|
339
|
+
logger.error(f"[PrometheusMetricEmitter] Failed to push deal metrics to gateway: {e}")
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.error(f"[PrometheusMetricEmitter] Failed to emit deals: {e}")
|
|
343
|
+
|
|
281
344
|
def stop(self) -> None:
|
|
282
345
|
"""Perform a final push to the gateway if configured."""
|
|
283
346
|
if self._stopped:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|