Qubx 1.3.3.dev2__tar.gz → 1.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. {qubx-1.3.3.dev2 → qubx-1.3.4}/PKG-INFO +1 -1
  2. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/_version.py +2 -2
  3. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/commands.py +0 -19
  4. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/release.py +2 -6
  5. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/s3.py +0 -92
  6. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/interfaces.py +3 -2
  7. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/series.pxd +3 -2
  8. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/series.pyi +8 -1
  9. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/series.pyx +30 -5
  10. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/base.py +29 -5
  11. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/composite.py +20 -4
  12. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/csv.py +65 -4
  13. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/prometheus.py +65 -2
  14. {qubx-1.3.3.dev2 → qubx-1.3.4}/.gitignore +0 -0
  15. {qubx-1.3.3.dev2 → qubx-1.3.4}/LICENSE +0 -0
  16. {qubx-1.3.3.dev2 → qubx-1.3.4}/README.md +0 -0
  17. {qubx-1.3.3.dev2 → qubx-1.3.4}/pyproject.toml +0 -0
  18. {qubx-1.3.3.dev2 → qubx-1.3.4}/scripts/build.py +0 -0
  19. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/__init__.py +0 -0
  20. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/_nb_magic.py +0 -0
  21. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/__init__.py +0 -0
  22. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/account.py +0 -0
  23. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/broker.py +0 -0
  24. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/data.py +0 -0
  25. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/iteratedstream.py +0 -0
  26. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/management.py +0 -0
  27. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/ome.py +0 -0
  28. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/optimization.py +0 -0
  29. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/runner.py +0 -0
  30. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/sentinels.py +0 -0
  31. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/simulated_data.py +0 -0
  32. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/simulated_exchange.py +0 -0
  33. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/simulator.py +0 -0
  34. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/transfers.py +0 -0
  35. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/backtester/utils.py +0 -0
  36. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/__init__.py +0 -0
  37. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/deploy.py +0 -0
  38. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/misc.py +0 -0
  39. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/resolver.py +0 -0
  40. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/theme.py +0 -0
  41. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/tui.py +0 -0
  42. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/cli/user_config.py +0 -0
  43. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/config.py +0 -0
  44. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/__init__.py +0 -0
  45. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/__init__.py +0 -0
  46. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/account.py +0 -0
  47. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/adapters/__init__.py +0 -0
  48. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/adapters/polling_adapter.py +0 -0
  49. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/broker.py +0 -0
  50. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/connection_manager.py +0 -0
  51. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/data.py +0 -0
  52. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  53. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchange_manager.py +0 -0
  54. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  55. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/base.py +0 -0
  56. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  57. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  58. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  59. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  60. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/gateio/__init__.py +0 -0
  61. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/gateio/gateio.py +0 -0
  62. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/__init__.py +0 -0
  63. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/account.py +0 -0
  64. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/broker.py +0 -0
  65. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/hyperliquid/hyperliquid.py +0 -0
  66. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  67. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/okx/__init__.py +0 -0
  68. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/okx/account.py +0 -0
  69. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/okx/broker.py +0 -0
  70. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/exchanges/okx/okx.py +0 -0
  71. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/factory.py +0 -0
  72. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/__init__.py +0 -0
  73. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/base.py +0 -0
  74. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/factory.py +0 -0
  75. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/funding_rate.py +0 -0
  76. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/liquidation.py +0 -0
  77. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/ohlc.py +0 -0
  78. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/open_interest.py +0 -0
  79. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/orderbook.py +0 -0
  80. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/quote.py +0 -0
  81. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/handlers/trade.py +0 -0
  82. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/rate_limits.py +0 -0
  83. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/subscription_config.py +0 -0
  84. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/subscription_manager.py +0 -0
  85. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/subscription_orchestrator.py +0 -0
  86. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/utils.py +0 -0
  87. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/ccxt/warmup_service.py +0 -0
  88. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/registry.py +0 -0
  89. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/tardis/data.py +0 -0
  90. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/connectors/tardis/utils.py +0 -0
  91. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/__init__.py +0 -0
  92. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/builtin.py +0 -0
  93. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/decorator.py +0 -0
  94. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/executor.py +0 -0
  95. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/interfaces.py +0 -0
  96. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/server.py +0 -0
  97. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/control/types.py +0 -0
  98. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/__init__.py +0 -0
  99. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/account.py +0 -0
  100. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/basics.py +0 -0
  101. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/context.py +0 -0
  102. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/detectors/__init__.py +0 -0
  103. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/detectors/delisting.py +0 -0
  104. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/detectors/stale.py +0 -0
  105. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/errors.py +0 -0
  106. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/exceptions.py +0 -0
  107. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/helpers.py +0 -0
  108. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/initializer.py +0 -0
  109. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/loggers.py +0 -0
  110. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/lookups.py +0 -0
  111. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/metrics.py +0 -0
  112. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/__init__.py +0 -0
  113. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/market.py +0 -0
  114. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/processing.py +0 -0
  115. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/subscription.py +0 -0
  116. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/trading.py +0 -0
  117. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/universe.py +0 -0
  118. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/mixins/utils.py +0 -0
  119. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/utils.pyi +0 -0
  120. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/core/utils.pyx +0 -0
  121. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/__init__.py +0 -0
  122. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/cache.py +0 -0
  123. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/containers.py +0 -0
  124. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/guards.py +0 -0
  125. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/registry.py +0 -0
  126. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storage.py +0 -0
  127. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/ccxt.py +0 -0
  128. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/csv.py +0 -0
  129. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/handy.py +0 -0
  130. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/multi.py +0 -0
  131. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/questdb.py +0 -0
  132. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/stub.py +0 -0
  133. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/storages/utils.py +0 -0
  134. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/data/transformers.py +0 -0
  135. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/__init__.py +0 -0
  136. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/indicator.py +0 -0
  137. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/inmemory.py +0 -0
  138. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/emitters/questdb.py +0 -0
  139. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/__init__.py +0 -0
  140. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/composite.py +0 -0
  141. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/__init__.py +0 -0
  142. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/base.py +0 -0
  143. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/incremental.py +0 -0
  144. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/slack.py +0 -0
  145. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/formatters/target_position.py +0 -0
  146. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/redis_streams.py +0 -0
  147. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/exporters/slack.py +0 -0
  148. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/gathering/simplest.py +0 -0
  149. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/health/__init__.py +0 -0
  150. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/health/base.py +0 -0
  151. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/health/dummy.py +0 -0
  152. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/__init__.py +0 -0
  153. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/csv.py +0 -0
  154. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/factory.py +0 -0
  155. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/inmemory.py +0 -0
  156. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/mongo.py +0 -0
  157. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/loggers/postgres.py +0 -0
  158. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/notifications/__init__.py +0 -0
  159. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/notifications/composite.py +0 -0
  160. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/notifications/slack.py +0 -0
  161. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/notifications/throttler.py +0 -0
  162. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/pandaz/__init__.py +0 -0
  163. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/pandaz/stats.py +0 -0
  164. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/pandaz/ta.py +0 -0
  165. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/pandaz/utils.py +0 -0
  166. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/plugins/__init__.py +0 -0
  167. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/plugins/loader.py +0 -0
  168. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/__init__.py +0 -0
  169. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/backend.py +0 -0
  170. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/config.py +0 -0
  171. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/engine.py +0 -0
  172. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/ip_resolver.py +0 -0
  173. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/manager.py +0 -0
  174. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/pools.py +0 -0
  175. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/rate_limiting/redis_backend.py +0 -0
  176. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/_build.py +0 -0
  177. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/crypto-fees.ini +0 -0
  178. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/hyperliquid-spot.json +0 -0
  179. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/hyperliquid.f-perpetual.json +0 -0
  180. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance-spot.json +0 -0
  181. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance.cm-future.json +0 -0
  182. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance.cm-perpetual.json +0 -0
  183. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance.um-future.json +0 -0
  184. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-binance.um-perpetual.json +0 -0
  185. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-bitfinex.f-perpetual.json +0 -0
  186. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-kraken-spot.json +0 -0
  187. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-kraken.f-future.json +0 -0
  188. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/resources/instruments/symbols-kraken.f-perpetual.json +0 -0
  189. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restarts/__init__.py +0 -0
  190. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restarts/state_resolvers.py +0 -0
  191. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restarts/time_finders.py +0 -0
  192. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/__init__.py +0 -0
  193. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/balance.py +0 -0
  194. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/factory.py +0 -0
  195. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/interfaces.py +0 -0
  196. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/position.py +0 -0
  197. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/signal.py +0 -0
  198. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/state.py +0 -0
  199. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/restorers/utils.py +0 -0
  200. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/state/__init__.py +0 -0
  201. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/state/dummy.py +0 -0
  202. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/state/redis.py +0 -0
  203. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/ta/__init__.py +0 -0
  204. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/ta/indicators.pxd +0 -0
  205. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/ta/indicators.pyi +0 -0
  206. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/ta/indicators.pyx +0 -0
  207. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/__init__.py +0 -0
  208. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/base.py +0 -0
  209. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/accounts.toml.j2 +0 -0
  210. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/config.yml.j2 +0 -0
  211. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/jlive.sh.j2 +0 -0
  212. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/jpaper.sh.j2 +0 -0
  213. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/pyproject.toml.j2 +0 -0
  214. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  215. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  216. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/project/template.yml +0 -0
  217. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.claude/skills/qubx-cli/SKILL.md +0 -0
  218. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.claude/skills/qubx-indicators/SKILL.md +0 -0
  219. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.claude/skills/simulation-explorer/SKILL.md +0 -0
  220. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.claude/skills/strategy-release/SKILL.md +0 -0
  221. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.github/workflows/ci.yml.j2 +0 -0
  222. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.gitignore.j2 +0 -0
  223. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.python-version +0 -0
  224. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.vscode/launch.json +0 -0
  225. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.vscode/settings.json +0 -0
  226. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/.vscode/tasks.json +0 -0
  227. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/CLAUDE.md.j2 +0 -0
  228. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/README.md.j2 +0 -0
  229. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/configs/.gitkeep +0 -0
  230. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/justfile.j2 +0 -0
  231. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/pyproject.toml.j2 +0 -0
  232. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/research/.gitkeep +0 -0
  233. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/src/{{ strategy_name }}/__init__.py.j2 +0 -0
  234. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/src/{{ strategy_name }}/cli.py.j2 +0 -0
  235. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/src/{{ strategy_name }}/strategy.py.j2 +0 -0
  236. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/template.yml +0 -0
  237. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/repo/tests/test_strategy.py.j2 +0 -0
  238. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/__init__.py.j2 +0 -0
  239. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/accounts.toml.j2 +0 -0
  240. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/config.yml.j2 +0 -0
  241. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/jlive.sh.j2 +0 -0
  242. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/jpaper.sh.j2 +0 -0
  243. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/strategy.py.j2 +0 -0
  244. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/templates/simple/template.yml +0 -0
  245. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/__init__.py +0 -0
  246. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/advanced.py +0 -0
  247. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/composite.py +0 -0
  248. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/rebalancers.py +0 -0
  249. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/riskctrl.py +0 -0
  250. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/trackers/sizers.py +0 -0
  251. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/__init__.py +0 -0
  252. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/_pyxreloader.py +0 -0
  253. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/charting/lookinglass.py +0 -0
  254. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  255. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/charting/orderbook.py +0 -0
  256. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/collections.py +0 -0
  257. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/hft/__init__.py +0 -0
  258. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/hft/numba_utils.py +0 -0
  259. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/hft/orderbook.pyi +0 -0
  260. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/hft/orderbook.pyx +0 -0
  261. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/marketdata/binance.py +0 -0
  262. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/marketdata/ccxt.py +0 -0
  263. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/marketdata/dukas.py +0 -0
  264. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/misc.py +0 -0
  265. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/nonce.py +0 -0
  266. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/ntp.py +0 -0
  267. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/numbers_utils.py +0 -0
  268. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/orderbook.py +0 -0
  269. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/__init__.py +0 -0
  270. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/dashboard.py +0 -0
  271. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/data.py +0 -0
  272. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/interfaces.py +0 -0
  273. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  274. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  275. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/questdb.py +0 -0
  276. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/rate_limiter.py +0 -0
  277. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/results.py +0 -0
  278. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/ringbuffer.pxd +0 -0
  279. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/ringbuffer.pyi +0 -0
  280. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/ringbuffer.pyx +0 -0
  281. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/__init__.py +0 -0
  282. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  283. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/accounts.py +0 -0
  284. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/configs.py +0 -0
  285. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/factory.py +0 -0
  286. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/kernel_service.py +0 -0
  287. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/runner.py +0 -0
  288. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/__init__.py +0 -0
  289. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/app.py +0 -0
  290. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/handlers.py +0 -0
  291. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/init_code.py +0 -0
  292. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/kernel.py +0 -0
  293. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/styles.tcss +0 -0
  294. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/__init__.py +0 -0
  295. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/account_summary.py +0 -0
  296. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/command_input.py +0 -0
  297. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/debug_log.py +0 -0
  298. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/orders_table.py +0 -0
  299. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/positions_table.py +0 -0
  300. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/quotes_table.py +0 -0
  301. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/runner/textual/widgets/repl_output.py +0 -0
  302. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/s3.py +0 -0
  303. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/slack.py +0 -0
  304. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/throttler.py +0 -0
  305. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/time.py +0 -0
  306. {qubx-1.3.3.dev2 → qubx-1.3.4}/src/qubx/utils/websocket_manager.py +0 -0
  307. {qubx-1.3.3.dev2 → qubx-1.3.4}/tests/strategies/macd_crossover/src/macd_crossover/indicators/macd.py +0 -0
  308. {qubx-1.3.3.dev2 → qubx-1.3.4}/tests/strategies/macd_crossover/src/macd_crossover/models/macd_crossover.py +0 -0
  309. {qubx-1.3.3.dev2 → qubx-1.3.4}/tests/strategies/macd_crossover/src/macd_crossover/models/utils.py +0 -0
  310. {qubx-1.3.3.dev2 → qubx-1.3.4}/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.3.3.dev2
3
+ Version: 1.3.4
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.3.3.dev2'
22
- __version_tuple__ = version_tuple = (1, 3, 3, 'dev2')
21
+ __version__ = version = '1.3.4'
22
+ __version_tuple__ = version_tuple = (1, 3, 4)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1060,25 +1060,6 @@ def s3_rm_cmd(path: str, recursive: bool, yes: bool):
1060
1060
  s3_rm(path, recursive=recursive)
1061
1061
 
1062
1062
 
1063
- @s3.command("parquet-stats")
1064
- @click.argument("path", type=str)
1065
- @click.option("--per-column", is_flag=True, help="Show per-column chunk sizes for row group 0.")
1066
- def s3_parquet_stats_cmd(path: str, per_column: bool):
1067
- """
1068
- Print parquet file-level summary and row-group size distribution.
1069
-
1070
- Accepts both S3 paths (account:bucket/key) and local paths. Reads only
1071
- the footer, so the full file is NOT downloaded.
1072
-
1073
- Examples:\n
1074
- qubx s3 parquet-stats r2:dvault-features/.../part-2026-03.parquet\n
1075
- qubx s3 parquet-stats ./local.parquet --per-column
1076
- """
1077
- from .s3 import s3_parquet_stats
1078
-
1079
- s3_parquet_stats(path, per_column=per_column)
1080
-
1081
-
1082
1063
  @s3.command("cp")
1083
1064
  @click.argument("src", type=str)
1084
1065
  @click.argument("dst", type=str)
@@ -1454,16 +1454,12 @@ def _bundle_source_overrides(
1454
1454
  subprocess.run(["git", "clone", git_url, checkout_dir], check=True, capture_output=True, text=True)
1455
1455
  subprocess.run(["git", "checkout", commit_sha], cwd=checkout_dir, check=True, capture_output=True, text=True)
1456
1456
 
1457
- # Honor [tool.uv.sources] `subdirectory` for monorepo git sources
1458
- subdir = source.get("subdirectory")
1459
- build_cwd = os.path.join(checkout_dir, subdir) if subdir else checkout_dir
1460
-
1461
- logger.info(f" Bundling {pkg_name}=={pkg_ver} from {build_cwd} ...")
1457
+ logger.info(f" Bundling {pkg_name}=={pkg_ver} from {checkout_dir} ...")
1462
1458
  os.makedirs(wheels_dir, exist_ok=True)
1463
1459
  try:
1464
1460
  result = subprocess.run(
1465
1461
  ["uv", "build", "--wheel", ".", "--out-dir", wheels_dir],
1466
- cwd=build_cwd,
1462
+ cwd=checkout_dir,
1467
1463
  check=True,
1468
1464
  capture_output=True,
1469
1465
  text=True,
@@ -109,98 +109,6 @@ def s3_cp(src: str, dst: str, recursive: bool = False) -> None:
109
109
  raise click.Abort()
110
110
 
111
111
 
112
- def s3_parquet_stats(path: str, per_column: bool = False) -> None:
113
- """Print file-level summary + row-group size distribution for one parquet file.
114
-
115
- Accepts either an S3 URI (``account:bucket/key``) or a local filesystem path.
116
- Uses pyarrow for metadata parsing so no data pages are fetched.
117
- """
118
- import pyarrow.parquet as pq
119
- from pyarrow.fs import LocalFileSystem
120
-
121
- # Heuristic: an S3 URI has "account:..." where account is non-empty and
122
- # contains no path separator. Fall back to local for anything else.
123
- is_s3 = ":" in path and "/" not in path.split(":", 1)[0] and not path.startswith("/")
124
- if is_s3:
125
- try:
126
- client, s3_path = S3Client.from_uri(path)
127
- except ValueError as e:
128
- raise click.BadParameter(str(e))
129
- fs = client.fs
130
- open_path = s3_path
131
- else:
132
- fs = LocalFileSystem()
133
- open_path = path
134
-
135
- try:
136
- with fs.open_input_file(open_path) as f:
137
- meta = pq.ParquetFile(f).metadata
138
- except Exception as e:
139
- click.echo(click.style(f"Error reading {path}: {e}", fg="red"), err=True)
140
- raise click.Abort()
141
-
142
- click.echo(click.style(f"File: {path}", fg="cyan", bold=True))
143
- click.echo(f" num_rows: {meta.num_rows:,}")
144
- click.echo(f" num_row_groups: {meta.num_row_groups}")
145
- click.echo(f" num_columns: {meta.num_columns}")
146
- click.echo(f" format_version: {meta.format_version}")
147
- if meta.created_by:
148
- click.echo(f" created_by: {meta.created_by}")
149
-
150
- if meta.num_row_groups == 0:
151
- return
152
-
153
- # Parquet spec: RowGroup.total_byte_size is the SUM of uncompressed
154
- # column sizes. The compressed on-disk size is the sum of
155
- # ColumnChunk.total_compressed_size across columns.
156
- rows: list[int] = []
157
- per_rg_compressed: list[int] = []
158
- per_rg_uncompressed: list[int] = []
159
- compressions: set[str] = set()
160
- for i in range(meta.num_row_groups):
161
- rg = meta.row_group(i)
162
- rows.append(rg.num_rows)
163
- c_sz = 0
164
- u_sz = 0
165
- for c in range(rg.num_columns):
166
- col = rg.column(c)
167
- c_sz += col.total_compressed_size
168
- u_sz += col.total_uncompressed_size
169
- compressions.add(str(col.compression))
170
- per_rg_compressed.append(c_sz)
171
- per_rg_uncompressed.append(u_sz)
172
-
173
- total_compressed = sum(per_rg_compressed)
174
- total_uncompressed = sum(per_rg_uncompressed)
175
- click.echo()
176
- click.echo(click.style("Row groups:", fg="cyan", bold=True))
177
- click.echo(f" rows per rg: min={min(rows):,} max={max(rows):,} avg={sum(rows)//len(rows):,}")
178
- click.echo(
179
- f" cmp per rg: min={_human_size(min(per_rg_compressed))} "
180
- f"max={_human_size(max(per_rg_compressed))} avg={_human_size(sum(per_rg_compressed) // len(per_rg_compressed))}"
181
- )
182
- click.echo(f" total: compressed={_human_size(total_compressed)} uncompressed={_human_size(total_uncompressed)}")
183
- if total_uncompressed > 0:
184
- ratio = 100 * total_compressed / total_uncompressed
185
- click.echo(f" compression: {', '.join(sorted(compressions))} ({ratio:.1f}% of uncompressed)")
186
- else:
187
- click.echo(f" compression: {', '.join(sorted(compressions))}")
188
-
189
- if per_column:
190
- click.echo()
191
- click.echo(click.style("Columns (row group 0):", fg="cyan", bold=True))
192
- rg0 = meta.row_group(0)
193
- click.echo(f" {'path':30s} {'type':12s} {'compressed':>11s} {'uncompressed':>12s} pct compression")
194
- for c in range(rg0.num_columns):
195
- col = rg0.column(c)
196
- pct = (100 * col.total_compressed_size / col.total_uncompressed_size) if col.total_uncompressed_size else 0.0
197
- click.echo(
198
- f" {col.path_in_schema:30s} {str(col.physical_type):12s} "
199
- f"{_human_size(col.total_compressed_size):>11s} {_human_size(col.total_uncompressed_size):>12s} "
200
- f"{pct:5.1f}% {col.compression}"
201
- )
202
-
203
-
204
112
  def s3_accounts() -> None:
205
113
  s = get_settings()
206
114
  if not s.s3:
@@ -12,6 +12,7 @@ This module includes:
12
12
  - Strategy lifecycle notifiers
13
13
  """
14
14
 
15
+ import datetime
15
16
  import inspect
16
17
  import traceback
17
18
  from dataclasses import dataclass
@@ -2790,7 +2791,7 @@ class IMetricEmitter:
2790
2791
 
2791
2792
  def emit_signals(
2792
2793
  self,
2793
- time: dt_64,
2794
+ time: dt_64 | pd.Timestamp | datetime.datetime,
2794
2795
  signals: list[Signal],
2795
2796
  account: "IAccountViewer",
2796
2797
  target_positions: list["TargetPosition"] | None = None,
@@ -2810,7 +2811,7 @@ class IMetricEmitter:
2810
2811
 
2811
2812
  def emit_deals(
2812
2813
  self,
2813
- time: dt_64,
2814
+ time: dt_64 | pd.Timestamp | datetime.datetime,
2814
2815
  instrument: Instrument,
2815
2816
  deals: list[Deal],
2816
2817
  account: "IAccountViewer",
@@ -187,8 +187,9 @@ cdef class BundledSeries(TimeSeries):
187
187
  """
188
188
  Virtual series that bundles fields from multiple source TimeSeries.
189
189
  """
190
- cdef dict _fields # field_name -> TimeSeries
191
- cdef list _field_names # ordered list of field names
190
+ cdef dict _fields # field_name -> TimeSeries
191
+ cdef list _field_names # ordered list of field names
192
+ cdef long long _align_timeframe # alignment bin (ns) for cross-source timestamp flooring
192
193
 
193
194
  cdef double _lookup_value(BundledSeries self, TimeSeries series, long long time)
194
195
  cdef TimeSeries _find_root(BundledSeries self, TimeSeries series)
@@ -266,7 +266,14 @@ class BundledSeries(TimeSeries):
266
266
  _fields: dict[str, TimeSeries]
267
267
  _field_names: list[str]
268
268
 
269
- def __init__(self, name: str, timeframe, fields: dict[str, TimeSeries], max_series_length: int | float = ...) -> None: ...
269
+ def __init__(
270
+ self,
271
+ name: str,
272
+ timeframe,
273
+ fields: dict[str, TimeSeries],
274
+ align_timeframe: str | None = ...,
275
+ max_series_length: int | float = ...,
276
+ ) -> None: ...
270
277
  def update(self, time: int, value: Any, new_item_started: bool) -> Any: ...
271
278
  def to_series(self, length: int | None = None) -> pd.DataFrame: ...
272
279
 
@@ -1845,18 +1845,26 @@ cdef class BundledSeries(TimeSeries):
1845
1845
  # When either vtwap or ohlcv updates, bundle gathers values and triggers spread
1846
1846
  """
1847
1847
 
1848
- def __init__(self, str name, timeframe, dict fields, max_series_length=INFINITY):
1848
+ def __init__(self, str name, timeframe, dict fields, align_timeframe=None, max_series_length=INFINITY):
1849
1849
  """
1850
1850
  Args:
1851
1851
  name: Name of this bundled series
1852
1852
  timeframe: Timeframe string (e.g., "1m", "1h")
1853
1853
  fields: Dict mapping field names to TimeSeries
1854
1854
  e.g., {"vwap": vtwap_ts, "close": close_ts}
1855
+ align_timeframe: Optional timeframe string used to bin-align source
1856
+ timestamps before joining (floor_t64(t, align_timeframe)).
1857
+ Defaults to `timeframe` when None. Use this when source series
1858
+ have timestamps offset within the same logical bar (e.g. OHLC
1859
+ at bar-open x:00:00 vs vendor data shifted to bar-close x:14:55
1860
+ for a 15-minute timeframe) — both floor to the same bin.
1855
1861
  max_series_length: Maximum number of values to store
1856
1862
  """
1857
1863
  super().__init__(name, timeframe, max_series_length)
1858
1864
  self._fields = fields
1859
1865
  self._field_names = list(fields.keys())
1866
+ # - alignment bin: explicit align_timeframe if provided, else self.timeframe (already ns after super().__init__)
1867
+ self._align_timeframe = recognize_timeframe(align_timeframe) if align_timeframe is not None else self.timeframe
1860
1868
 
1861
1869
  # Attach to each source series' calculation chain
1862
1870
  for field_name, series in fields.items():
@@ -1883,22 +1891,39 @@ cdef class BundledSeries(TimeSeries):
1883
1891
  return series
1884
1892
 
1885
1893
  def _initial_data_recalculate_bundled(self):
1886
- """Align all sources by timestamp for initial calculation."""
1887
- # Collect all source data as pandas Series
1894
+ """
1895
+ Align all sources by timestamp for initial calculation.
1896
+
1897
+ Each source's timestamps are floored to self._align_timeframe first, then
1898
+ outer-joined and forward-filled so mixed-cadence sources (e.g. 15m OHLC +
1899
+ 1h OI) and offset-stamped sources (e.g. open-stamped OHLC x:00:00 +
1900
+ close-stamped vendor x:14:55 within the same 15m bin) all land in the
1901
+ same row. Rows where any field is still NaN after ffill are dropped —
1902
+ this handles warm-up bars where the slow-cadence source hasn't ticked yet.
1903
+ """
1888
1904
  cdef list dfs = []
1889
1905
  cdef str name
1890
1906
  cdef TimeSeries series
1907
+ cdef long long tf_ns = self._align_timeframe
1891
1908
 
1892
1909
  for name, series in self._fields.items():
1893
1910
  s = series.to_series()
1894
1911
  if len(s) > 0:
1912
+ # - floor each timestamp to the align bin, keep last value within each bin
1913
+ floored = pd.DatetimeIndex(
1914
+ [pd.Timestamp(floor_t64(<long long> t.value, tf_ns)) for t in s.index]
1915
+ )
1916
+ s = s.copy()
1917
+ s.index = floored
1918
+ s = s[~s.index.duplicated(keep='last')]
1895
1919
  dfs.append(s.rename(name))
1896
1920
 
1897
1921
  if not dfs:
1898
1922
  return
1899
1923
 
1900
- # Align by timestamp (inner join - only times present in all sources)
1901
- aligned = pd.concat(dfs, axis=1).dropna()
1924
+ # - outer-join on floored timestamps, ffill to carry slow-cadence sources forward,
1925
+ # - then drop leading rows where any field is still NaN (warm-up)
1926
+ aligned = pd.concat(dfs, axis=1).sort_index().ffill().dropna()
1902
1927
 
1903
1928
  # Process each aligned row
1904
1929
  for t in aligned.index:
@@ -4,10 +4,13 @@ Base Metric Emitter.
4
4
  This module provides a base implementation of IMetricEmitter that can be extended by other emitters.
5
5
  """
6
6
 
7
+ import datetime
7
8
  from typing import Any, Dict, List, Optional, Set
8
9
 
10
+ import pandas as pd
11
+
9
12
  from qubx import logger
10
- from qubx.core.basics import Instrument, Signal, TargetPosition, dt_64
13
+ from qubx.core.basics import Deal, Instrument, Signal, TargetPosition, dt_64
11
14
  from qubx.core.interfaces import IAccountViewer, IMetricEmitter, IStrategyContext
12
15
  from qubx.utils.time import to_timedelta
13
16
 
@@ -199,10 +202,10 @@ class BaseMetricEmitter(IMetricEmitter):
199
202
 
200
203
  def emit_signals(
201
204
  self,
202
- time: dt_64,
203
- signals: list["Signal"],
204
- account: "IAccountViewer",
205
- target_positions: list["TargetPosition"] | None = None,
205
+ time: dt_64 | pd.Timestamp | datetime.datetime,
206
+ signals: list[Signal],
207
+ account: IAccountViewer,
208
+ target_positions: list[TargetPosition] | None = None,
206
209
  ) -> None:
207
210
  """
208
211
  Emit signals to the monitoring system.
@@ -218,6 +221,27 @@ class BaseMetricEmitter(IMetricEmitter):
218
221
  """
219
222
  pass
220
223
 
224
+ def emit_deals(
225
+ self,
226
+ time: dt_64 | pd.Timestamp | datetime.datetime,
227
+ instrument: Instrument,
228
+ deals: list[Deal],
229
+ account: IAccountViewer,
230
+ ) -> None:
231
+ """
232
+ Emit deals to the monitoring system.
233
+
234
+ Base implementation does nothing - subclasses should override this method
235
+ to implement specific deal emission logic.
236
+
237
+ Args:
238
+ time: Timestamp when the deals were generated
239
+ instrument: Instrument the deals belong to
240
+ deals: List of deals to emit
241
+ account: Account viewer to get account information like total capital, leverage, etc.
242
+ """
243
+ pass
244
+
221
245
  def notify(self, context: IStrategyContext) -> None:
222
246
  """
223
247
  Notify the metric emitter of a time update.
@@ -4,10 +4,13 @@ Composite Metric Emitter.
4
4
  This module provides a composite implementation of IMetricEmitter that delegates to multiple emitters.
5
5
  """
6
6
 
7
+ import datetime
7
8
  from typing import Dict, List, Optional
8
9
 
10
+ import pandas as pd
11
+
9
12
  from qubx import logger
10
- from qubx.core.basics import Deal, Instrument, Signal, dt_64
13
+ from qubx.core.basics import Deal, Instrument, Signal, TargetPosition, dt_64
11
14
  from qubx.core.interfaces import IAccountViewer, IMetricEmitter, IStrategyContext
12
15
  from qubx.emitters.base import BaseMetricEmitter
13
16
 
@@ -70,7 +73,13 @@ class CompositeMetricEmitter(BaseMetricEmitter):
70
73
  except Exception as e:
71
74
  logger.error(f"Error emitting strategy stats to {emitter.__class__.__name__}: {e}")
72
75
 
73
- def emit_signals(self, time: dt_64, signals: list["Signal"], account: "IAccountViewer") -> None:
76
+ def emit_signals(
77
+ self,
78
+ time: dt_64 | pd.Timestamp | datetime.datetime,
79
+ signals: list[Signal],
80
+ account: IAccountViewer,
81
+ target_positions: list[TargetPosition] | None = None,
82
+ ) -> None:
74
83
  """
75
84
  Emit signals to all configured emitters.
76
85
 
@@ -78,14 +87,21 @@ class CompositeMetricEmitter(BaseMetricEmitter):
78
87
  time: Timestamp when the signals were generated
79
88
  signals: List of signals to emit
80
89
  account: Account viewer to get account information
90
+ target_positions: List of target positions (optional)
81
91
  """
82
92
  for emitter in self._emitters:
83
93
  try:
84
- emitter.emit_signals(time, signals, account)
94
+ emitter.emit_signals(time, signals, account, target_positions)
85
95
  except Exception as e:
86
96
  logger.error(f"Error emitting signals to {emitter.__class__.__name__}: {e}")
87
97
 
88
- def emit_deals(self, time: dt_64, instrument: Instrument, deals: list[Deal], account: "IAccountViewer") -> None:
98
+ def emit_deals(
99
+ self,
100
+ time: dt_64 | pd.Timestamp | datetime.datetime,
101
+ instrument: Instrument,
102
+ deals: list[Deal],
103
+ account: IAccountViewer,
104
+ ) -> None:
89
105
  """
90
106
  Emit deals to all configured emitters.
91
107
 
@@ -4,12 +4,15 @@ CSV Metric Emitter.
4
4
  This module provides an implementation of IMetricEmitter that exports metrics to a CSV file.
5
5
  """
6
6
 
7
+ import datetime
7
8
  import os
8
9
  from pathlib import Path
9
10
  from typing import Any
10
11
 
12
+ import pandas as pd
13
+
11
14
  from qubx import logger
12
- from qubx.core.basics import Signal, dt_64
15
+ from qubx.core.basics import Deal, Instrument, Signal, TargetPosition, dt_64
13
16
  from qubx.core.interfaces import IAccountViewer
14
17
  from qubx.emitters.base import BaseMetricEmitter
15
18
  from qubx.utils.ntp import time_now
@@ -84,7 +87,13 @@ class CSVMetricEmitter(BaseMetricEmitter):
84
87
  except Exception as e:
85
88
  logger.error(f"[CSVMetricEmitter] Failed to emit metric {name}: {e}")
86
89
 
87
- def emit_signals(self, time: dt_64, signals: list[Signal], account: IAccountViewer) -> None:
90
+ def emit_signals(
91
+ self,
92
+ time: dt_64 | pd.Timestamp | datetime.datetime,
93
+ signals: list[Signal],
94
+ account: IAccountViewer,
95
+ target_positions: list[TargetPosition] | None = None,
96
+ ) -> None:
88
97
  """
89
98
  Emit signals to CSV file.
90
99
 
@@ -92,10 +101,16 @@ class CSVMetricEmitter(BaseMetricEmitter):
92
101
  time: Timestamp when the signals were generated
93
102
  signals: List of signals to emit
94
103
  account: Account viewer to get account information
104
+ target_positions: Optional list of target positions generated from the signals
95
105
  """
96
106
  if not signals:
97
107
  return
98
108
 
109
+ target_positions_map: dict[Instrument, TargetPosition] = {}
110
+ if target_positions:
111
+ for target in target_positions:
112
+ target_positions_map[target.instrument] = target
113
+
99
114
  try:
100
115
  # Create a signals-specific CSV file
101
116
  signals_file_path = self._file_path.parent / f"signals_{self._file_path.stem}.csv"
@@ -104,7 +119,8 @@ class CSVMetricEmitter(BaseMetricEmitter):
104
119
  if not signals_file_path.exists():
105
120
  with open(signals_file_path, "w") as f:
106
121
  f.write(
107
- "timestamp,symbol,exchange,signal,price,stop,take,reference_price,group,comment,is_service\n"
122
+ "timestamp,symbol,exchange,signal,price,stop,take,reference_price,"
123
+ "target_position_size,group,comment,is_service\n"
108
124
  )
109
125
 
110
126
  # Write each signal to the CSV file
@@ -114,13 +130,58 @@ class CSVMetricEmitter(BaseMetricEmitter):
114
130
  stop = signal.stop if signal.stop is not None else ""
115
131
  take = signal.take if signal.take is not None else ""
116
132
  ref_price = signal.reference_price if signal.reference_price is not None else ""
133
+ target = target_positions_map.get(signal.instrument)
134
+ target_size = target.target_position_size if target is not None else ""
117
135
 
118
136
  with open(signals_file_path, "a") as f:
119
137
  f.write(
120
138
  f"{signal_time},{signal.instrument.symbol},{signal.instrument.exchange},"
121
- f"{signal.signal},{price},{stop},{take},{ref_price},"
139
+ f"{signal.signal},{price},{stop},{take},{ref_price},{target_size},"
122
140
  f"{signal.group},{signal.comment},{signal.is_service}\n"
123
141
  )
124
142
 
125
143
  except Exception as e:
126
144
  logger.error(f"[CSVMetricEmitter] Failed to emit signals: {e}")
145
+
146
+ def emit_deals(
147
+ self,
148
+ time: dt_64 | pd.Timestamp | datetime.datetime,
149
+ instrument: Instrument,
150
+ deals: list[Deal],
151
+ account: IAccountViewer,
152
+ ) -> None:
153
+ """
154
+ Emit deals to CSV file.
155
+
156
+ Args:
157
+ time: Timestamp when the deals were generated
158
+ instrument: Instrument the deals belong to
159
+ deals: List of deals to emit
160
+ account: Account viewer to get account information
161
+ """
162
+ if not deals:
163
+ return
164
+
165
+ try:
166
+ deals_file_path = self._file_path.parent / f"deals_{self._file_path.stem}.csv"
167
+
168
+ if not deals_file_path.exists():
169
+ with open(deals_file_path, "w") as f:
170
+ f.write(
171
+ "timestamp,symbol,exchange,amount,price,aggressive,"
172
+ "fee_amount,fee_currency,deal_id,order_id\n"
173
+ )
174
+
175
+ with open(deals_file_path, "a") as f:
176
+ for deal in deals:
177
+ deal_time = str(deal.time) if hasattr(deal.time, "__str__") else str(time)
178
+ fee_amount = deal.fee_amount if deal.fee_amount is not None else ""
179
+ fee_currency = deal.fee_currency if deal.fee_currency is not None else ""
180
+ f.write(
181
+ f"{deal_time},{instrument.symbol},{instrument.exchange},"
182
+ f"{deal.amount},{deal.price},{deal.aggressive},"
183
+ f"{fee_amount},{fee_currency},{deal.id},{deal.order_id}\n"
184
+ )
185
+
186
+ except Exception as e:
187
+ logger.error(f"[CSVMetricEmitter] Failed to emit deals: {e}")
@@ -4,12 +4,14 @@ Prometheus Metric Emitter.
4
4
  This module provides an implementation of IMetricEmitter that exports metrics to Prometheus.
5
5
  """
6
6
 
7
+ import datetime
7
8
  from typing import Any, Dict, List, Literal, Optional
8
9
 
10
+ import pandas as pd
9
11
  from prometheus_client import REGISTRY, Counter, Gauge, Summary, push_to_gateway
10
12
 
11
13
  from qubx import logger
12
- from qubx.core.basics import Instrument, Signal, dt_64
14
+ from qubx.core.basics import Deal, Instrument, Signal, TargetPosition, dt_64
13
15
  from qubx.core.interfaces import IAccountViewer, IStrategyContext
14
16
  from qubx.emitters.base import BaseMetricEmitter
15
17
 
@@ -223,7 +225,13 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
223
225
  except Exception as e:
224
226
  logger.error(f"[PrometheusMetricEmitter] Failed to push metrics to gateway: {e}")
225
227
 
226
- def emit_signals(self, time: dt_64, signals: list[Signal], account: IAccountViewer) -> None:
228
+ def emit_signals(
229
+ self,
230
+ time: dt_64 | pd.Timestamp | datetime.datetime,
231
+ signals: list[Signal],
232
+ account: IAccountViewer,
233
+ target_positions: list[TargetPosition] | None = None,
234
+ ) -> None:
227
235
  """
228
236
  Emit signals as Prometheus metrics.
229
237
 
@@ -231,10 +239,16 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
231
239
  time: Timestamp when the signals were generated
232
240
  signals: List of signals to emit
233
241
  account: Account viewer to get account information
242
+ target_positions: Optional list of target positions generated from the signals
234
243
  """
235
244
  if not signals:
236
245
  return
237
246
 
247
+ target_positions_map: dict[Instrument, TargetPosition] = {}
248
+ if target_positions:
249
+ for target in target_positions:
250
+ target_positions_map[target.instrument] = target
251
+
238
252
  try:
239
253
  for signal in signals:
240
254
  # Create labels for the signal
@@ -266,6 +280,11 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
266
280
  ref_price_gauge = self._get_or_create_gauge("signal_reference_price", labels)
267
281
  ref_price_gauge.labels(**labels).set(signal.reference_price)
268
282
 
283
+ target = target_positions_map.get(signal.instrument)
284
+ if target is not None:
285
+ target_gauge = self._get_or_create_gauge("signal_target_position_size", labels)
286
+ target_gauge.labels(**labels).set(target.target_position_size)
287
+
269
288
  # Push to gateway if configured
270
289
  if self._pushgateway_url:
271
290
  try:
@@ -278,6 +297,50 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
278
297
  except Exception as e:
279
298
  logger.error(f"[PrometheusMetricEmitter] Failed to emit signals: {e}")
280
299
 
300
+ def emit_deals(
301
+ self,
302
+ time: dt_64 | pd.Timestamp | datetime.datetime,
303
+ instrument: Instrument,
304
+ deals: list[Deal],
305
+ account: IAccountViewer,
306
+ ) -> None:
307
+ """
308
+ Emit deals as Prometheus metrics.
309
+
310
+ Args:
311
+ time: Timestamp when the deals were generated
312
+ instrument: Instrument the deals belong to
313
+ deals: List of deals to emit
314
+ account: Account viewer to get account information
315
+ """
316
+ if not deals:
317
+ return
318
+
319
+ try:
320
+ labels = {
321
+ "symbol": instrument.symbol,
322
+ "exchange": instrument.exchange,
323
+ }
324
+ count_counter = self._get_or_create_counter("deal_count", labels)
325
+ amount_gauge = self._get_or_create_gauge("deal_amount", labels)
326
+ price_gauge = self._get_or_create_gauge("deal_price", labels)
327
+
328
+ for deal in deals:
329
+ count_counter.labels(**labels).inc()
330
+ amount_gauge.labels(**labels).set(deal.amount)
331
+ price_gauge.labels(**labels).set(deal.price)
332
+
333
+ if self._pushgateway_url:
334
+ try:
335
+ push_to_gateway(
336
+ self._pushgateway_url, job=f"{self._namespace}_{self._strategy_name}", registry=self._registry
337
+ )
338
+ except Exception as e:
339
+ logger.error(f"[PrometheusMetricEmitter] Failed to push deal metrics to gateway: {e}")
340
+
341
+ except Exception as e:
342
+ logger.error(f"[PrometheusMetricEmitter] Failed to emit deals: {e}")
343
+
281
344
  def stop(self) -> None:
282
345
  """Perform a final push to the gateway if configured."""
283
346
  if self._stopped:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes