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.
Files changed (295) hide show
  1. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/PKG-INFO +1 -1
  2. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/_version.py +2 -2
  3. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/prometheus.py +4 -3
  4. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/__init__.py +2 -0
  5. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/factory.py +2 -0
  6. qubx-1.1.2.dev13/src/qubx/loggers/postgres.py +216 -0
  7. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/balance.py +74 -1
  8. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/factory.py +8 -4
  9. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/position.py +86 -1
  10. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/signal.py +117 -0
  11. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/state.py +87 -3
  12. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/runner.py +0 -12
  13. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/.gitignore +0 -0
  14. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/LICENSE +0 -0
  15. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/README.md +0 -0
  16. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/pyproject.toml +0 -0
  17. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/scripts/build.py +0 -0
  18. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/__init__.py +0 -0
  19. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/_nb_magic.py +0 -0
  20. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/__init__.py +0 -0
  21. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/account.py +0 -0
  22. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/broker.py +0 -0
  23. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/data.py +0 -0
  24. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/iteratedstream.py +0 -0
  25. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/management.py +0 -0
  26. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/ome.py +0 -0
  27. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/optimization.py +0 -0
  28. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/runner.py +0 -0
  29. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/sentinels.py +0 -0
  30. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/simulated_data.py +0 -0
  31. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/simulated_exchange.py +0 -0
  32. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/simulator.py +0 -0
  33. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/transfers.py +0 -0
  34. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/backtester/utils.py +0 -0
  35. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/__init__.py +0 -0
  36. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/commands.py +0 -0
  37. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/deploy.py +0 -0
  38. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/misc.py +0 -0
  39. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/release.py +0 -0
  40. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/resolver.py +0 -0
  41. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/s3.py +0 -0
  42. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/theme.py +0 -0
  43. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/tui.py +0 -0
  44. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/cli/user_config.py +0 -0
  45. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/config.py +0 -0
  46. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/__init__.py +0 -0
  47. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/__init__.py +0 -0
  48. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/account.py +0 -0
  49. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  50. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  51. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/broker.py +0 -0
  52. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  53. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/data.py +0 -0
  54. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  55. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  56. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  57. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  58. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  59. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  60. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  61. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  62. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
  63. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
  64. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  65. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  66. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  67. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  68. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  69. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
  70. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
  71. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
  72. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
  73. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/factory.py +0 -0
  74. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  75. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  76. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  77. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  78. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  79. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  80. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  81. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  82. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  83. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  84. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  85. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  86. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  87. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/utils.py +0 -0
  88. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  89. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/registry.py +0 -0
  90. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/tardis/data.py +0 -0
  91. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/connectors/tardis/utils.py +0 -0
  92. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/__init__.py +0 -0
  93. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/account.py +0 -0
  94. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/basics.py +0 -0
  95. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/context.py +0 -0
  96. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/detectors/__init__.py +0 -0
  97. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/detectors/delisting.py +0 -0
  98. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/detectors/stale.py +0 -0
  99. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/errors.py +0 -0
  100. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/exceptions.py +0 -0
  101. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/helpers.py +0 -0
  102. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/initializer.py +0 -0
  103. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/interfaces.py +0 -0
  104. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/loggers.py +0 -0
  105. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/lookups.py +0 -0
  106. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/metrics.py +0 -0
  107. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/__init__.py +0 -0
  108. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/market.py +0 -0
  109. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/processing.py +0 -0
  110. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/subscription.py +0 -0
  111. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/trading.py +0 -0
  112. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/universe.py +0 -0
  113. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/mixins/utils.py +0 -0
  114. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/series.pxd +0 -0
  115. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/series.pyi +0 -0
  116. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/series.pyx +0 -0
  117. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/utils.pyi +0 -0
  118. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/core/utils.pyx +0 -0
  119. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/__init__.py +0 -0
  120. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/cache.py +0 -0
  121. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/containers.py +0 -0
  122. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/guards.py +0 -0
  123. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/registry.py +0 -0
  124. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storage.py +0 -0
  125. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/ccxt.py +0 -0
  126. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/csv.py +0 -0
  127. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/handy.py +0 -0
  128. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/multi.py +0 -0
  129. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/questdb.py +0 -0
  130. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/stub.py +0 -0
  131. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/storages/utils.py +0 -0
  132. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/data/transformers.py +0 -0
  133. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/__init__.py +0 -0
  134. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/base.py +0 -0
  135. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/composite.py +0 -0
  136. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/csv.py +0 -0
  137. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/indicator.py +0 -0
  138. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/inmemory.py +0 -0
  139. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/emitters/questdb.py +0 -0
  140. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/__init__.py +0 -0
  141. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/composite.py +0 -0
  142. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/__init__.py +0 -0
  143. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/base.py +0 -0
  144. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/incremental.py +0 -0
  145. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/slack.py +0 -0
  146. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/formatters/target_position.py +0 -0
  147. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/redis_streams.py +0 -0
  148. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/exporters/slack.py +0 -0
  149. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/gathering/simplest.py +0 -0
  150. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/health/__init__.py +0 -0
  151. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/health/base.py +0 -0
  152. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/health/dummy.py +0 -0
  153. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/health/server.py +0 -0
  154. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/csv.py +0 -0
  155. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/inmemory.py +0 -0
  156. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/loggers/mongo.py +0 -0
  157. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/notifications/__init__.py +0 -0
  158. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/notifications/composite.py +0 -0
  159. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/notifications/slack.py +0 -0
  160. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/notifications/throttler.py +0 -0
  161. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/pandaz/__init__.py +0 -0
  162. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/pandaz/stats.py +0 -0
  163. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/pandaz/ta.py +0 -0
  164. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/pandaz/utils.py +0 -0
  165. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/plugins/__init__.py +0 -0
  166. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/plugins/loader.py +0 -0
  167. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/_build.py +0 -0
  168. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/crypto-fees.ini +0 -0
  169. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  170. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  171. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  172. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  173. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  174. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  175. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  176. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  177. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  178. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  179. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  180. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restarts/__init__.py +0 -0
  181. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restarts/state_resolvers.py +0 -0
  182. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restarts/time_finders.py +0 -0
  183. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/__init__.py +0 -0
  184. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/interfaces.py +0 -0
  185. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/restorers/utils.py +0 -0
  186. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/state/__init__.py +0 -0
  187. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/state/dummy.py +0 -0
  188. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/state/redis.py +0 -0
  189. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/ta/__init__.py +0 -0
  190. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/ta/indicators.pxd +0 -0
  191. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/ta/indicators.pyi +0 -0
  192. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/ta/indicators.pyx +0 -0
  193. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/__init__.py +0 -0
  194. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/base.py +0 -0
  195. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  196. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/config.yml.j2 +0 -0
  197. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  198. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  199. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  200. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  201. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  202. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/project/template.yml +0 -0
  203. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
  204. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
  205. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
  206. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
  207. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
  208. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.gitignore.j2 +0 -0
  209. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.python-version +0 -0
  210. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.vscode/launch.json +0 -0
  211. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.vscode/settings.json +0 -0
  212. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
  213. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
  214. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/README.md.j2 +0 -0
  215. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/configs/.gitkeep +0 -0
  216. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/justfile.j2 +0 -0
  217. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
  218. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/research/.gitkeep +0 -0
  219. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  220. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
  221. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  222. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/template.yml +0 -0
  223. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
  224. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  225. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  226. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/config.yml.j2 +0 -0
  227. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  228. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  229. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  230. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/templates/simple/template.yml +0 -0
  231. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/__init__.py +0 -0
  232. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/advanced.py +0 -0
  233. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/composite.py +0 -0
  234. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/rebalancers.py +0 -0
  235. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/riskctrl.py +0 -0
  236. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/trackers/sizers.py +0 -0
  237. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/__init__.py +0 -0
  238. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/_pyxreloader.py +0 -0
  239. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/charting/lookinglass.py +0 -0
  240. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  241. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/charting/orderbook.py +0 -0
  242. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/collections.py +0 -0
  243. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/hft/__init__.py +0 -0
  244. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/hft/numba_utils.py +0 -0
  245. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/hft/orderbook.pyi +0 -0
  246. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/hft/orderbook.pyx +0 -0
  247. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/marketdata/binance.py +0 -0
  248. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/marketdata/ccxt.py +0 -0
  249. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/marketdata/dukas.py +0 -0
  250. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/misc.py +0 -0
  251. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/nonce.py +0 -0
  252. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/ntp.py +0 -0
  253. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/numbers_utils.py +0 -0
  254. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/orderbook.py +0 -0
  255. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/__init__.py +0 -0
  256. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/dashboard.py +0 -0
  257. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/data.py +0 -0
  258. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/interfaces.py +0 -0
  259. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  260. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  261. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/questdb.py +0 -0
  262. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/rate_limiter.py +0 -0
  263. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/results.py +0 -0
  264. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/ringbuffer.pxd +0 -0
  265. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/ringbuffer.pyi +0 -0
  266. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/ringbuffer.pyx +0 -0
  267. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/__init__.py +0 -0
  268. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  269. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/accounts.py +0 -0
  270. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/configs.py +0 -0
  271. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/factory.py +0 -0
  272. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/kernel_service.py +0 -0
  273. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/__init__.py +0 -0
  274. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/app.py +0 -0
  275. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/handlers.py +0 -0
  276. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/init_code.py +0 -0
  277. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/kernel.py +0 -0
  278. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  279. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  280. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
  281. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  282. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  283. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  284. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  285. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  286. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  287. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/s3.py +0 -0
  288. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/slack.py +0 -0
  289. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/throttler.py +0 -0
  290. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/time.py +0 -0
  291. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/src/qubx/utils/websocket_manager.py +0 -0
  292. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  293. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
  294. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  295. {qubx-1.1.2.dev11 → qubx-1.1.2.dev13}/tests/strategies/obi_trader/src/obi_trader/models/obi_trader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Qubx
3
- Version: 1.1.2.dev11
3
+ Version: 1.1.2.dev13
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Project-URL: homepage, https://xlydian.com
6
6
  Project-URL: repository, https://github.com/xLydianSoftware/Qubx
@@ -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.dev11'
22
- __version_tuple__ = version_tuple = (1, 1, 2, 'dev11')
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
- # Add metric type to tags
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