Qubx 1.1.2.dev11__tar.gz → 1.1.2.dev13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/PKG-INFO +1 -1
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/_version.py +2 -2
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/prometheus.py +4 -3
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/__init__.py +2 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/factory.py +2 -0
- qubx-1.1.2.dev13/src/qubx/loggers/postgres.py +216 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/balance.py +74 -1
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/factory.py +8 -4
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/position.py +86 -1
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/signal.py +117 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/state.py +87 -3
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/runner.py +0 -12
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/.gitignore +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/LICENSE +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/README.md +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/pyproject.toml +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/scripts/build.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/_nb_magic.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/account.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/broker.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/data.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/iteratedstream.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/management.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/ome.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/optimization.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/runner.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/sentinels.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/simulated_data.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/simulated_exchange.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/simulator.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/transfers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/commands.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/deploy.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/misc.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/release.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/resolver.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/s3.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/theme.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/tui.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/user_config.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/config.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/account.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/broker.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/data.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exceptions.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/factory.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/registry.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/tardis/data.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/tardis/utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/account.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/basics.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/context.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/detectors/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/detectors/delisting.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/detectors/stale.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/errors.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/exceptions.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/helpers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/initializer.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/interfaces.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/loggers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/lookups.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/metrics.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/market.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/processing.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/subscription.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/trading.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/universe.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/series.pxd +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/series.pyi +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/series.pyx +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/utils.pyi +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/utils.pyx +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/cache.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/containers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/guards.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/registry.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storage.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/ccxt.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/csv.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/handy.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/multi.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/questdb.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/stub.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/transformers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/base.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/composite.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/csv.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/indicator.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/inmemory.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/questdb.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/composite.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/base.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/incremental.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/slack.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/target_position.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/redis_streams.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/slack.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/gathering/simplest.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/health/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/health/base.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/health/dummy.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/health/server.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/csv.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/inmemory.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/mongo.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/notifications/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/notifications/composite.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/notifications/slack.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/notifications/throttler.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/pandaz/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/pandaz/stats.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/pandaz/ta.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/pandaz/utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/plugins/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/plugins/loader.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/_build.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/crypto-fees.ini +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restarts/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restarts/state_resolvers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restarts/time_finders.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/interfaces.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/state/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/state/dummy.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/state/redis.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/ta/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/ta/indicators.pxd +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/ta/indicators.pyi +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/ta/indicators.pyx +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/base.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/accounts.toml.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/config.yml.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/jlive.sh.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/template.yml +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.gitignore.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.python-version +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.vscode/launch.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.vscode/settings.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/README.md.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/configs/.gitkeep +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/justfile.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/research/.gitkeep +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/template.yml +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/__init__.py.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/config.yml.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/strategy.py.j2 +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/template.yml +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/advanced.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/composite.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/rebalancers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/riskctrl.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/sizers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/_pyxreloader.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/charting/lookinglass.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/charting/mpl_helpers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/charting/orderbook.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/collections.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/hft/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/hft/numba_utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/hft/orderbook.pyi +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/hft/orderbook.pyx +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/marketdata/binance.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/marketdata/ccxt.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/marketdata/dukas.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/misc.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/nonce.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/ntp.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/numbers_utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/orderbook.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/dashboard.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/data.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/interfaces.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/questdb.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/rate_limiter.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/results.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/ringbuffer.pxd +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/ringbuffer.pyi +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/ringbuffer.pyx +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/accounts.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/configs.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/factory.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/kernel_service.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/app.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/handlers.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/init_code.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/kernel.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/styles.tcss +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/s3.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/slack.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/throttler.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/time.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/websocket_manager.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
- {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/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.1.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 1, 2, '
|
|
21
|
+
__version__ = version = '1.1.2.dev13'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 1, 2, 'dev13')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -9,7 +9,7 @@ from typing import Any, Dict, List, Literal, Optional
|
|
|
9
9
|
from prometheus_client import REGISTRY, Counter, Gauge, Summary, push_to_gateway
|
|
10
10
|
|
|
11
11
|
from qubx import logger
|
|
12
|
-
from qubx.core.basics import Signal, dt_64
|
|
12
|
+
from qubx.core.basics import Instrument, Signal, dt_64
|
|
13
13
|
from qubx.core.interfaces import IAccountViewer, IStrategyContext
|
|
14
14
|
from qubx.emitters.base import BaseMetricEmitter
|
|
15
15
|
|
|
@@ -180,6 +180,7 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
|
|
|
180
180
|
value: float,
|
|
181
181
|
tags: dict[str, Any] | None = None,
|
|
182
182
|
timestamp: dt_64 | None = None,
|
|
183
|
+
instrument: Instrument | None = None,
|
|
183
184
|
metric_type: MetricType = "gauge",
|
|
184
185
|
) -> None:
|
|
185
186
|
"""
|
|
@@ -192,8 +193,8 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
|
|
|
192
193
|
timestamp: Optional timestamp for the metric (ignored in Prometheus)
|
|
193
194
|
metric_type: Type of metric (gauge, counter, summary)
|
|
194
195
|
"""
|
|
195
|
-
#
|
|
196
|
-
merged_tags = self._merge_tags(tags)
|
|
196
|
+
# Merge tags with instrument decomposed into symbol/exchange/asset/quote
|
|
197
|
+
merged_tags = self._merge_tags(tags, instrument)
|
|
197
198
|
merged_tags["metric_type"] = metric_type
|
|
198
199
|
|
|
199
200
|
# Call the implementation
|
|
@@ -8,10 +8,12 @@ from qubx.loggers.csv import CsvFileLogsWriter
|
|
|
8
8
|
from qubx.loggers.factory import create_logs_writer
|
|
9
9
|
from qubx.loggers.inmemory import InMemoryLogsWriter
|
|
10
10
|
from qubx.loggers.mongo import MongoDBLogsWriter
|
|
11
|
+
from qubx.loggers.postgres import PostgresLogsWriter
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
"CsvFileLogsWriter",
|
|
14
15
|
"InMemoryLogsWriter",
|
|
15
16
|
"MongoDBLogsWriter",
|
|
17
|
+
"PostgresLogsWriter",
|
|
16
18
|
"create_logs_writer",
|
|
17
19
|
]
|
|
@@ -5,12 +5,14 @@ from qubx.core.loggers import LogsWriter
|
|
|
5
5
|
from qubx.loggers.csv import CsvFileLogsWriter
|
|
6
6
|
from qubx.loggers.inmemory import InMemoryLogsWriter
|
|
7
7
|
from qubx.loggers.mongo import MongoDBLogsWriter
|
|
8
|
+
from qubx.loggers.postgres import PostgresLogsWriter
|
|
8
9
|
|
|
9
10
|
# Registry of logs writer types
|
|
10
11
|
LOGS_WRITER_REGISTRY: dict[str, Type[LogsWriter]] = {
|
|
11
12
|
"CsvFileLogsWriter": CsvFileLogsWriter,
|
|
12
13
|
"MongoDBLogsWriter": MongoDBLogsWriter,
|
|
13
14
|
"InMemoryLogsWriter": InMemoryLogsWriter,
|
|
15
|
+
"PostgresLogsWriter": PostgresLogsWriter,
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from multiprocessing.pool import ThreadPool
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from psycopg import sql
|
|
5
|
+
from psycopg.types.json import Jsonb
|
|
6
|
+
from psycopg_pool import ConnectionPool
|
|
7
|
+
|
|
8
|
+
from qubx import logger
|
|
9
|
+
from qubx.core.loggers import LogsWriter
|
|
10
|
+
|
|
11
|
+
# Column definitions for each log type: (column_name, sql_type)
|
|
12
|
+
_TABLE_SCHEMAS: dict[str, list[tuple[str, str]]] = {
|
|
13
|
+
"positions": [
|
|
14
|
+
("timestamp", "TIMESTAMPTZ NOT NULL"),
|
|
15
|
+
("symbol", "TEXT NOT NULL"),
|
|
16
|
+
("exchange", "TEXT NOT NULL"),
|
|
17
|
+
("market_type", "TEXT NOT NULL"),
|
|
18
|
+
("pnl_quoted", "DOUBLE PRECISION"),
|
|
19
|
+
("funding_pnl_quoted", "DOUBLE PRECISION"),
|
|
20
|
+
("realized_pnl_quoted", "DOUBLE PRECISION"),
|
|
21
|
+
("quantity", "DOUBLE PRECISION"),
|
|
22
|
+
("notional", "DOUBLE PRECISION"),
|
|
23
|
+
("avg_position_price", "DOUBLE PRECISION"),
|
|
24
|
+
("current_price", "DOUBLE PRECISION"),
|
|
25
|
+
("market_value_quoted", "DOUBLE PRECISION"),
|
|
26
|
+
("commissions_quoted", "DOUBLE PRECISION"),
|
|
27
|
+
],
|
|
28
|
+
"portfolio": [
|
|
29
|
+
("timestamp", "TIMESTAMPTZ NOT NULL"),
|
|
30
|
+
("symbol", "TEXT NOT NULL"),
|
|
31
|
+
("exchange", "TEXT NOT NULL"),
|
|
32
|
+
("market_type", "TEXT NOT NULL"),
|
|
33
|
+
("pnl_quoted", "DOUBLE PRECISION"),
|
|
34
|
+
("quantity", "DOUBLE PRECISION"),
|
|
35
|
+
("realized_pnl_quoted", "DOUBLE PRECISION"),
|
|
36
|
+
("avg_position_price", "DOUBLE PRECISION"),
|
|
37
|
+
("current_price", "DOUBLE PRECISION"),
|
|
38
|
+
("market_value_quoted", "DOUBLE PRECISION"),
|
|
39
|
+
("exchange_time", "TIMESTAMPTZ"),
|
|
40
|
+
("commissions_quoted", "DOUBLE PRECISION"),
|
|
41
|
+
("cumulative_funding", "DOUBLE PRECISION"),
|
|
42
|
+
],
|
|
43
|
+
"executions": [
|
|
44
|
+
("timestamp", "TIMESTAMPTZ NOT NULL"),
|
|
45
|
+
("symbol", "TEXT NOT NULL"),
|
|
46
|
+
("exchange", "TEXT NOT NULL"),
|
|
47
|
+
("market_type", "TEXT NOT NULL"),
|
|
48
|
+
("side", "TEXT NOT NULL"),
|
|
49
|
+
("filled_qty", "DOUBLE PRECISION"),
|
|
50
|
+
("price", "DOUBLE PRECISION"),
|
|
51
|
+
("commissions", "DOUBLE PRECISION"),
|
|
52
|
+
("commissions_quoted", "TEXT"),
|
|
53
|
+
("order_id", "TEXT"),
|
|
54
|
+
("order_type", "TEXT"),
|
|
55
|
+
],
|
|
56
|
+
"signals": [
|
|
57
|
+
("timestamp", "TIMESTAMPTZ NOT NULL"),
|
|
58
|
+
("symbol", "TEXT NOT NULL"),
|
|
59
|
+
("exchange", "TEXT NOT NULL"),
|
|
60
|
+
("market_type", "TEXT NOT NULL"),
|
|
61
|
+
("signal", "TEXT"),
|
|
62
|
+
("reference_price", "DOUBLE PRECISION"),
|
|
63
|
+
("price", "DOUBLE PRECISION"),
|
|
64
|
+
("take", "DOUBLE PRECISION"),
|
|
65
|
+
("stop", "DOUBLE PRECISION"),
|
|
66
|
+
("group_name", "TEXT"),
|
|
67
|
+
("comment", "TEXT"),
|
|
68
|
+
("service", "BOOLEAN"),
|
|
69
|
+
("options", "JSONB"),
|
|
70
|
+
],
|
|
71
|
+
"targets": [
|
|
72
|
+
("timestamp", "TIMESTAMPTZ NOT NULL"),
|
|
73
|
+
("symbol", "TEXT NOT NULL"),
|
|
74
|
+
("exchange", "TEXT NOT NULL"),
|
|
75
|
+
("market_type", "TEXT NOT NULL"),
|
|
76
|
+
("target_position", "DOUBLE PRECISION"),
|
|
77
|
+
("entry_price", "DOUBLE PRECISION"),
|
|
78
|
+
("take_price", "DOUBLE PRECISION"),
|
|
79
|
+
("stop_price", "DOUBLE PRECISION"),
|
|
80
|
+
("options", "JSONB"),
|
|
81
|
+
],
|
|
82
|
+
"balance": [
|
|
83
|
+
("timestamp", "TIMESTAMPTZ NOT NULL"),
|
|
84
|
+
("exchange", "TEXT NOT NULL"),
|
|
85
|
+
("currency", "TEXT NOT NULL"),
|
|
86
|
+
("total", "DOUBLE PRECISION"),
|
|
87
|
+
("locked", "DOUBLE PRECISION"),
|
|
88
|
+
],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Mapping from log data dict keys to column names (only where they differ)
|
|
92
|
+
_COLUMN_RENAMES: dict[str, dict[str, str]] = {
|
|
93
|
+
"signals": {"group": "group_name"},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Columns that use JSONB type and need Jsonb() wrapper
|
|
97
|
+
_JSONB_COLUMNS: set[str] = {
|
|
98
|
+
name for schema in _TABLE_SCHEMAS.values() for name, col_type in schema if col_type == "JSONB"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _table_name(prefix: str, log_type: str) -> str:
|
|
103
|
+
return f"{prefix}_{log_type}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class PostgresLogsWriter(LogsWriter):
|
|
107
|
+
"""
|
|
108
|
+
PostgreSQL implementation of LogsWriter interface.
|
|
109
|
+
Writes log data to typed PostgreSQL tables asynchronously using psycopg v3 and a connection pool.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
account_id: str,
|
|
115
|
+
strategy_id: str,
|
|
116
|
+
run_id: str,
|
|
117
|
+
postgres_uri: str = "postgresql://localhost:5432/qubx_logs",
|
|
118
|
+
table_prefix: str = "qubx_logs",
|
|
119
|
+
pool_size: int = 4,
|
|
120
|
+
) -> None:
|
|
121
|
+
super().__init__(account_id, strategy_id, run_id)
|
|
122
|
+
self.table_prefix = table_prefix
|
|
123
|
+
self._pool = ConnectionPool(postgres_uri, min_size=1, max_size=pool_size)
|
|
124
|
+
self._thread_pool = ThreadPool(pool_size)
|
|
125
|
+
self._ensure_tables()
|
|
126
|
+
|
|
127
|
+
def _ensure_tables(self) -> None:
|
|
128
|
+
with self._pool.connection() as conn:
|
|
129
|
+
with conn.cursor() as cur:
|
|
130
|
+
for log_type, columns in _TABLE_SCHEMAS.items():
|
|
131
|
+
tname = _table_name(self.table_prefix, log_type)
|
|
132
|
+
col_defs = ",\n ".join(f"{name} {col_type}" for name, col_type in columns)
|
|
133
|
+
cur.execute(
|
|
134
|
+
sql.SQL(
|
|
135
|
+
"""
|
|
136
|
+
CREATE TABLE IF NOT EXISTS {table} (
|
|
137
|
+
id BIGSERIAL PRIMARY KEY,
|
|
138
|
+
run_id TEXT NOT NULL,
|
|
139
|
+
account_id TEXT NOT NULL,
|
|
140
|
+
strategy_name TEXT NOT NULL,
|
|
141
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
142
|
+
{columns}
|
|
143
|
+
)
|
|
144
|
+
"""
|
|
145
|
+
).format(
|
|
146
|
+
table=sql.Identifier(tname),
|
|
147
|
+
columns=sql.SQL(col_defs),
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
# Index on strategy_name + timestamp for common queries
|
|
151
|
+
idx_name = f"idx_{tname}_strategy_ts"
|
|
152
|
+
cur.execute(
|
|
153
|
+
sql.SQL("CREATE INDEX IF NOT EXISTS {idx} ON {table} (strategy_name, timestamp)").format(
|
|
154
|
+
idx=sql.Identifier(idx_name),
|
|
155
|
+
table=sql.Identifier(tname),
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
conn.commit()
|
|
159
|
+
|
|
160
|
+
def _remap_keys(self, log_type: str, row: dict[str, Any]) -> dict[str, Any]:
|
|
161
|
+
renames = _COLUMN_RENAMES.get(log_type)
|
|
162
|
+
if not renames:
|
|
163
|
+
return row
|
|
164
|
+
return {renames.get(k, k): v for k, v in row.items()}
|
|
165
|
+
|
|
166
|
+
def _do_write(self, log_type: str, data: list[dict[str, Any]]) -> None:
|
|
167
|
+
tname = _table_name(self.table_prefix, log_type)
|
|
168
|
+
schema_cols = [name for name, _ in _TABLE_SCHEMAS[log_type]]
|
|
169
|
+
meta_cols = ["run_id", "account_id", "strategy_name"]
|
|
170
|
+
all_cols = meta_cols + schema_cols
|
|
171
|
+
|
|
172
|
+
rows = []
|
|
173
|
+
for raw_row in data:
|
|
174
|
+
row = self._remap_keys(log_type, raw_row)
|
|
175
|
+
values = [self.run_id, self.account_id, self.strategy_id]
|
|
176
|
+
for col in schema_cols:
|
|
177
|
+
val = row.get(col)
|
|
178
|
+
# Convert numpy datetime / stringified timestamps
|
|
179
|
+
if col in ("timestamp", "exchange_time") and val is not None:
|
|
180
|
+
val = str(val)
|
|
181
|
+
# Convert signal value to string
|
|
182
|
+
if col == "signal" and val is not None:
|
|
183
|
+
val = str(val)
|
|
184
|
+
# Wrap dicts in Jsonb for JSONB columns
|
|
185
|
+
if col in _JSONB_COLUMNS and isinstance(val, dict):
|
|
186
|
+
val = Jsonb(val)
|
|
187
|
+
values.append(val)
|
|
188
|
+
rows.append(tuple(values))
|
|
189
|
+
|
|
190
|
+
col_ids = sql.SQL(", ").join(sql.Identifier(c) for c in all_cols)
|
|
191
|
+
placeholders = sql.SQL(", ").join(sql.Placeholder() * len(all_cols))
|
|
192
|
+
query = sql.SQL("INSERT INTO {table} ({cols}) VALUES ({vals})").format(
|
|
193
|
+
table=sql.Identifier(tname),
|
|
194
|
+
cols=col_ids,
|
|
195
|
+
vals=placeholders,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
with self._pool.connection() as conn:
|
|
200
|
+
with conn.cursor() as cur:
|
|
201
|
+
cur.executemany(query, rows)
|
|
202
|
+
conn.commit()
|
|
203
|
+
except Exception:
|
|
204
|
+
logger.exception(f"PostgresLogsWriter: failed to write {len(rows)} rows to {tname}")
|
|
205
|
+
|
|
206
|
+
def write_data(self, log_type: str, data: list[dict[str, Any]]) -> None:
|
|
207
|
+
if data:
|
|
208
|
+
self._thread_pool.apply_async(self._do_write, (log_type, data))
|
|
209
|
+
|
|
210
|
+
def flush_data(self) -> None:
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
def close(self) -> None:
|
|
214
|
+
self._thread_pool.close()
|
|
215
|
+
self._thread_pool.join()
|
|
216
|
+
self._pool.close()
|
|
@@ -6,10 +6,11 @@ from various sources.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
-
from datetime import datetime, timedelta
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
import pandas as pd
|
|
13
|
+
from psycopg import Connection, sql
|
|
13
14
|
from pymongo import MongoClient
|
|
14
15
|
|
|
15
16
|
from qubx import logger
|
|
@@ -207,3 +208,75 @@ class MongoDBBalanceRestorer(IBalanceRestorer):
|
|
|
207
208
|
except Exception as e:
|
|
208
209
|
logger.error(f"Error restoring balances from MongoDB: {e}")
|
|
209
210
|
return []
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class PostgresBalanceRestorer(IBalanceRestorer):
|
|
214
|
+
"""
|
|
215
|
+
Balance restorer that reads account balances from PostgreSQL tables
|
|
216
|
+
written by PostgresLogsWriter.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
def __init__(
|
|
220
|
+
self,
|
|
221
|
+
strategy_name: str,
|
|
222
|
+
connection: Connection,
|
|
223
|
+
table_name: str = "qubx_logs_balance",
|
|
224
|
+
):
|
|
225
|
+
self.strategy_name = strategy_name
|
|
226
|
+
self.connection = connection
|
|
227
|
+
self.table_name = table_name
|
|
228
|
+
|
|
229
|
+
def restore_balances(self) -> list[AssetBalance]:
|
|
230
|
+
try:
|
|
231
|
+
now = datetime.now(timezone.utc)
|
|
232
|
+
lookup_range = now - timedelta(days=7)
|
|
233
|
+
|
|
234
|
+
with self.connection.cursor() as cur:
|
|
235
|
+
# Find latest run_id
|
|
236
|
+
cur.execute(
|
|
237
|
+
sql.SQL(
|
|
238
|
+
"SELECT run_id FROM {table} WHERE strategy_name = %s AND timestamp >= %s "
|
|
239
|
+
"ORDER BY timestamp DESC LIMIT 1"
|
|
240
|
+
).format(table=sql.Identifier(self.table_name)),
|
|
241
|
+
(self.strategy_name, lookup_range),
|
|
242
|
+
)
|
|
243
|
+
row = cur.fetchone()
|
|
244
|
+
if not row:
|
|
245
|
+
logger.warning("No balance logs found in PostgreSQL for given filters.")
|
|
246
|
+
return []
|
|
247
|
+
|
|
248
|
+
latest_run_id = row[0]
|
|
249
|
+
logger.info(f"Restoring balances from PostgreSQL for run_id: {latest_run_id}")
|
|
250
|
+
|
|
251
|
+
# Get latest balance per (exchange, currency)
|
|
252
|
+
cur.execute(
|
|
253
|
+
sql.SQL(
|
|
254
|
+
"SELECT DISTINCT ON (exchange, currency) "
|
|
255
|
+
"exchange, currency, total, locked "
|
|
256
|
+
"FROM {table} "
|
|
257
|
+
"WHERE strategy_name = %s AND run_id = %s AND timestamp >= %s "
|
|
258
|
+
"ORDER BY exchange, currency, timestamp DESC"
|
|
259
|
+
).format(table=sql.Identifier(self.table_name)),
|
|
260
|
+
(self.strategy_name, latest_run_id, lookup_range),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
balances: list[AssetBalance] = []
|
|
264
|
+
for exchange, currency, total, locked in cur.fetchall():
|
|
265
|
+
if not currency:
|
|
266
|
+
continue
|
|
267
|
+
total = total or 0.0
|
|
268
|
+
locked = locked or 0.0
|
|
269
|
+
balances.append(
|
|
270
|
+
AssetBalance(
|
|
271
|
+
exchange=exchange,
|
|
272
|
+
currency=currency,
|
|
273
|
+
total=total,
|
|
274
|
+
locked=locked,
|
|
275
|
+
free=total - locked,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
return balances
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error(f"Error restoring balances from PostgreSQL: {e}")
|
|
282
|
+
return []
|
|
@@ -8,16 +8,17 @@ based on configuration.
|
|
|
8
8
|
from typing import Type
|
|
9
9
|
|
|
10
10
|
from qubx.core.lookups import LookupsManager
|
|
11
|
-
from qubx.restorers.balance import CsvBalanceRestorer, MongoDBBalanceRestorer
|
|
11
|
+
from qubx.restorers.balance import CsvBalanceRestorer, MongoDBBalanceRestorer, PostgresBalanceRestorer
|
|
12
12
|
from qubx.restorers.interfaces import IBalanceRestorer, IPositionRestorer, ISignalRestorer, IStateRestorer
|
|
13
|
-
from qubx.restorers.position import CsvPositionRestorer, MongoDBPositionRestorer
|
|
14
|
-
from qubx.restorers.signal import CsvSignalRestorer, MongoDBSignalRestorer
|
|
15
|
-
from qubx.restorers.state import CsvStateRestorer, MongoDBStateRestorer
|
|
13
|
+
from qubx.restorers.position import CsvPositionRestorer, MongoDBPositionRestorer, PostgresPositionRestorer
|
|
14
|
+
from qubx.restorers.signal import CsvSignalRestorer, MongoDBSignalRestorer, PostgresSignalRestorer
|
|
15
|
+
from qubx.restorers.state import CsvStateRestorer, MongoDBStateRestorer, PostgresStateRestorer
|
|
16
16
|
|
|
17
17
|
# Registry of position restorer types
|
|
18
18
|
POSITION_RESTORER_REGISTRY: dict[str, Type[IPositionRestorer]] = {
|
|
19
19
|
"CsvPositionRestorer": CsvPositionRestorer,
|
|
20
20
|
"MongoDBPositionRestorer": MongoDBPositionRestorer,
|
|
21
|
+
"PostgresPositionRestorer": PostgresPositionRestorer,
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
|
|
@@ -25,6 +26,7 @@ POSITION_RESTORER_REGISTRY: dict[str, Type[IPositionRestorer]] = {
|
|
|
25
26
|
SIGNAL_RESTORER_REGISTRY: dict[str, Type[ISignalRestorer]] = {
|
|
26
27
|
"CsvSignalRestorer": CsvSignalRestorer,
|
|
27
28
|
"MongoDBSignalRestorer": MongoDBSignalRestorer,
|
|
29
|
+
"PostgresSignalRestorer": PostgresSignalRestorer,
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
|
|
@@ -32,6 +34,7 @@ SIGNAL_RESTORER_REGISTRY: dict[str, Type[ISignalRestorer]] = {
|
|
|
32
34
|
BALANCE_RESTORER_REGISTRY: dict[str, Type[IBalanceRestorer]] = {
|
|
33
35
|
"CsvBalanceRestorer": CsvBalanceRestorer,
|
|
34
36
|
"MongoDBBalanceRestorer": MongoDBBalanceRestorer,
|
|
37
|
+
"PostgresBalanceRestorer": PostgresBalanceRestorer,
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
|
|
@@ -39,6 +42,7 @@ BALANCE_RESTORER_REGISTRY: dict[str, Type[IBalanceRestorer]] = {
|
|
|
39
42
|
STATE_RESTORER_REGISTRY: dict[str, Type[IStateRestorer]] = {
|
|
40
43
|
"CsvStateRestorer": CsvStateRestorer,
|
|
41
44
|
"MongoDBStateRestorer": MongoDBStateRestorer,
|
|
45
|
+
"PostgresStateRestorer": PostgresStateRestorer,
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
|
|
@@ -6,11 +6,12 @@ for restoring positions from various sources.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
-
from datetime import datetime, timedelta
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import cast
|
|
12
12
|
|
|
13
13
|
import pandas as pd
|
|
14
|
+
from psycopg import Connection, sql
|
|
14
15
|
from pymongo import MongoClient
|
|
15
16
|
|
|
16
17
|
from qubx import logger
|
|
@@ -250,3 +251,87 @@ class MongoDBPositionRestorer(IPositionRestorer):
|
|
|
250
251
|
except Exception as e:
|
|
251
252
|
logger.error(f"Error restoring positions from MongoDB: {e}")
|
|
252
253
|
return {}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class PostgresPositionRestorer(IPositionRestorer):
|
|
257
|
+
"""
|
|
258
|
+
Position restorer that reads positions from PostgreSQL tables
|
|
259
|
+
written by PostgresLogsWriter.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(
|
|
263
|
+
self,
|
|
264
|
+
strategy_name: str,
|
|
265
|
+
connection: Connection,
|
|
266
|
+
table_name: str = "qubx_logs_positions",
|
|
267
|
+
):
|
|
268
|
+
self.strategy_name = strategy_name
|
|
269
|
+
self.connection = connection
|
|
270
|
+
self.table_name = table_name
|
|
271
|
+
|
|
272
|
+
def restore_positions(self) -> dict[Instrument, Position]:
|
|
273
|
+
try:
|
|
274
|
+
now = datetime.now(timezone.utc)
|
|
275
|
+
lookup_range = now - timedelta(days=7)
|
|
276
|
+
|
|
277
|
+
with self.connection.cursor() as cur:
|
|
278
|
+
# Find latest run_id
|
|
279
|
+
cur.execute(
|
|
280
|
+
sql.SQL(
|
|
281
|
+
"SELECT run_id FROM {table} WHERE strategy_name = %s AND timestamp >= %s "
|
|
282
|
+
"ORDER BY timestamp DESC LIMIT 1"
|
|
283
|
+
).format(table=sql.Identifier(self.table_name)),
|
|
284
|
+
(self.strategy_name, lookup_range),
|
|
285
|
+
)
|
|
286
|
+
row = cur.fetchone()
|
|
287
|
+
if not row:
|
|
288
|
+
logger.warning("No position logs found in PostgreSQL for given filters.")
|
|
289
|
+
return {}
|
|
290
|
+
|
|
291
|
+
latest_run_id = row[0]
|
|
292
|
+
logger.info(f"Restoring positions from PostgreSQL for run_id: {latest_run_id}")
|
|
293
|
+
|
|
294
|
+
# Get latest position per instrument
|
|
295
|
+
cur.execute(
|
|
296
|
+
sql.SQL(
|
|
297
|
+
"SELECT DISTINCT ON (symbol, exchange, market_type) "
|
|
298
|
+
"symbol, exchange, market_type, quantity, avg_position_price, "
|
|
299
|
+
"realized_pnl_quoted, current_price, funding_pnl_quoted, commissions_quoted, timestamp "
|
|
300
|
+
"FROM {table} "
|
|
301
|
+
"WHERE strategy_name = %s AND run_id = %s AND timestamp >= %s "
|
|
302
|
+
"ORDER BY symbol, exchange, market_type, timestamp DESC"
|
|
303
|
+
).format(table=sql.Identifier(self.table_name)),
|
|
304
|
+
(self.strategy_name, latest_run_id, lookup_range),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
positions: dict[Instrument, Position] = {}
|
|
308
|
+
for log in cur.fetchall():
|
|
309
|
+
symbol, exchange, market_type, quantity, avg_price, r_pnl, current_price, funding, commissions, ts = log
|
|
310
|
+
|
|
311
|
+
if not (symbol and exchange and market_type):
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
instrument = lookup.find_symbol(exchange, symbol)
|
|
315
|
+
if instrument is None:
|
|
316
|
+
logger.warning(f"Instrument not found for {symbol} on {exchange}")
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
position = Position(
|
|
320
|
+
instrument=instrument,
|
|
321
|
+
quantity=cast(float, quantity or 0.0),
|
|
322
|
+
pos_average_price=cast(float, avg_price or 0.0),
|
|
323
|
+
r_pnl=cast(float, r_pnl or 0.0),
|
|
324
|
+
cumulative_funding=cast(float, funding or 0.0),
|
|
325
|
+
commissions=cast(float, commissions or 0.0),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if current_price is not None:
|
|
329
|
+
timestamp = recognize_time(ts)
|
|
330
|
+
position.update_market_price(timestamp, current_price, 1.0)
|
|
331
|
+
|
|
332
|
+
positions[instrument] = position
|
|
333
|
+
|
|
334
|
+
return positions
|
|
335
|
+
except Exception as e:
|
|
336
|
+
logger.error(f"Error restoring positions from PostgreSQL: {e}")
|
|
337
|
+
return {}
|
|
@@ -10,6 +10,7 @@ from datetime import datetime, timedelta
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
import pandas as pd
|
|
13
|
+
from psycopg import Connection, sql
|
|
13
14
|
from pymongo import MongoClient
|
|
14
15
|
from pymongo.command_cursor import CommandCursor
|
|
15
16
|
|
|
@@ -374,3 +375,119 @@ class MongoDBSignalRestorer(ISignalRestorer):
|
|
|
374
375
|
f"Error restoring {log_type} data from MongoDB::{self.collection_name} for {self.strategy_name} : {e}"
|
|
375
376
|
)
|
|
376
377
|
return None
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class PostgresSignalRestorer(ISignalRestorer):
|
|
381
|
+
"""
|
|
382
|
+
Signal restorer that reads signals and targets from PostgreSQL tables
|
|
383
|
+
written by PostgresLogsWriter.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
def __init__(
|
|
387
|
+
self,
|
|
388
|
+
strategy_name: str,
|
|
389
|
+
connection: Connection,
|
|
390
|
+
signals_table_name: str = "qubx_logs_signals",
|
|
391
|
+
targets_table_name: str = "qubx_logs_targets",
|
|
392
|
+
max_restored_records: int = 20,
|
|
393
|
+
):
|
|
394
|
+
self.strategy_name = strategy_name
|
|
395
|
+
self.connection = connection
|
|
396
|
+
self.signals_table_name = signals_table_name
|
|
397
|
+
self.targets_table_name = targets_table_name
|
|
398
|
+
self.max_restored_records = max_restored_records
|
|
399
|
+
|
|
400
|
+
def restore_signals(self) -> dict[Instrument, list[Signal]]:
|
|
401
|
+
logger.info(f"Restoring latest {self.max_restored_records} signals per symbol from PostgreSQL")
|
|
402
|
+
result: dict[Instrument, list[Signal]] = {}
|
|
403
|
+
|
|
404
|
+
rows = self._load_data(self.signals_table_name)
|
|
405
|
+
if rows is None:
|
|
406
|
+
return result
|
|
407
|
+
|
|
408
|
+
for log in rows:
|
|
409
|
+
try:
|
|
410
|
+
instrument = lookup.find_symbol(log["exchange"], log["symbol"])
|
|
411
|
+
if instrument is None:
|
|
412
|
+
logger.warning(f"Instrument not found for {log['symbol']} on {log['exchange']}")
|
|
413
|
+
continue
|
|
414
|
+
|
|
415
|
+
signal_value = log.get("signal")
|
|
416
|
+
if signal_value is not None:
|
|
417
|
+
signal_value = float(signal_value)
|
|
418
|
+
|
|
419
|
+
if signal_value is None:
|
|
420
|
+
logger.warning(f"Missing signal for log: {log}")
|
|
421
|
+
continue
|
|
422
|
+
|
|
423
|
+
price = log.get("price") or log.get("reference_price")
|
|
424
|
+
options = log.get("options") or {}
|
|
425
|
+
|
|
426
|
+
signal = Signal(
|
|
427
|
+
time=recognize_time(log["timestamp"]),
|
|
428
|
+
instrument=instrument,
|
|
429
|
+
signal=signal_value,
|
|
430
|
+
price=price,
|
|
431
|
+
stop=None,
|
|
432
|
+
take=None,
|
|
433
|
+
reference_price=log.get("reference_price"),
|
|
434
|
+
group=log.get("group_name", ""),
|
|
435
|
+
comment=log.get("comment", ""),
|
|
436
|
+
options=options,
|
|
437
|
+
is_service=log.get("service", False),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
result.setdefault(instrument, []).append(signal)
|
|
441
|
+
except Exception as e:
|
|
442
|
+
logger.exception(f"Failed to process signal row: {e}")
|
|
443
|
+
|
|
444
|
+
return result
|
|
445
|
+
|
|
446
|
+
def restore_targets(self) -> dict[Instrument, list[TargetPosition]]:
|
|
447
|
+
logger.info(f"Restoring latest {self.max_restored_records} targets per symbol from PostgreSQL")
|
|
448
|
+
result: dict[Instrument, list[TargetPosition]] = {}
|
|
449
|
+
|
|
450
|
+
rows = self._load_data(self.targets_table_name)
|
|
451
|
+
if rows is None:
|
|
452
|
+
return result
|
|
453
|
+
|
|
454
|
+
for log in rows:
|
|
455
|
+
try:
|
|
456
|
+
instrument = lookup.find_symbol(log["exchange"], log["symbol"])
|
|
457
|
+
if instrument is None:
|
|
458
|
+
logger.warning(f"Instrument not found for {log['symbol']} on {log['exchange']}")
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
target = TargetPosition(
|
|
462
|
+
time=recognize_time(log["timestamp"]),
|
|
463
|
+
instrument=instrument,
|
|
464
|
+
target_position_size=float(log["target_position"]),
|
|
465
|
+
entry_price=log.get("entry_price"),
|
|
466
|
+
stop_price=log.get("stop_price"),
|
|
467
|
+
take_price=log.get("take_price"),
|
|
468
|
+
options=log.get("options") or {},
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
result.setdefault(instrument, []).append(target)
|
|
472
|
+
except Exception as e:
|
|
473
|
+
logger.exception(f"Failed to process target row: {e}")
|
|
474
|
+
|
|
475
|
+
return result
|
|
476
|
+
|
|
477
|
+
def _load_data(self, table_name: str) -> list[dict] | None:
|
|
478
|
+
try:
|
|
479
|
+
query = sql.SQL(
|
|
480
|
+
"SELECT * FROM ("
|
|
481
|
+
" SELECT *, ROW_NUMBER() OVER ("
|
|
482
|
+
" PARTITION BY symbol, exchange, market_type ORDER BY timestamp DESC"
|
|
483
|
+
" ) AS rn FROM {table} WHERE strategy_name = %s"
|
|
484
|
+
") sub WHERE rn <= %s ORDER BY symbol, exchange, market_type, timestamp DESC"
|
|
485
|
+
).format(table=sql.Identifier(table_name))
|
|
486
|
+
|
|
487
|
+
with self.connection.cursor() as cur:
|
|
488
|
+
cur.execute(query, (self.strategy_name, self.max_restored_records))
|
|
489
|
+
columns = [desc.name for desc in cur.description]
|
|
490
|
+
return [dict(zip(columns, row)) for row in cur.fetchall()]
|
|
491
|
+
except Exception as e:
|
|
492
|
+
logger.error(f"Error restoring data from PostgreSQL::{table_name} for {self.strategy_name}: {e}")
|
|
493
|
+
return None
|