Qubx 0.6.42__tar.gz → 0.6.44__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.

Potentially problematic release.


This version of Qubx might be problematic. Click here for more details.

Files changed (164) hide show
  1. {qubx-0.6.42 → qubx-0.6.44}/PKG-INFO +1 -1
  2. {qubx-0.6.42 → qubx-0.6.44}/pyproject.toml +1 -1
  3. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/utils.py +1 -1
  4. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/lookups.py +2 -1
  5. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/metrics.py +2 -2
  6. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/mixins/universe.py +2 -0
  7. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/pandaz/ta.py +18 -5
  8. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/pandaz/utils.py +15 -0
  9. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restorers/balance.py +33 -18
  10. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restorers/position.py +34 -15
  11. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restorers/signal.py +30 -7
  12. {qubx-0.6.42 → qubx-0.6.44}/LICENSE +0 -0
  13. {qubx-0.6.42 → qubx-0.6.44}/README.md +0 -0
  14. {qubx-0.6.42 → qubx-0.6.44}/build.py +0 -0
  15. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/__init__.py +0 -0
  16. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/_nb_magic.py +0 -0
  17. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/__init__.py +0 -0
  18. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/account.py +0 -0
  19. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/broker.py +0 -0
  20. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/data.py +0 -0
  21. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/management.py +0 -0
  22. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/ome.py +0 -0
  23. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/optimization.py +0 -0
  24. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/runner.py +0 -0
  25. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/simulated_data.py +0 -0
  26. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/simulated_exchange.py +0 -0
  27. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/backtester/simulator.py +0 -0
  28. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/cli/__init__.py +0 -0
  29. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/cli/commands.py +0 -0
  30. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/cli/deploy.py +0 -0
  31. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/cli/misc.py +0 -0
  32. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/cli/release.py +0 -0
  33. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/__init__.py +0 -0
  34. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/account.py +0 -0
  35. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/broker.py +0 -0
  36. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/data.py +0 -0
  37. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  38. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  39. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  40. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  41. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  42. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  43. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  44. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/factory.py +0 -0
  45. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/reader.py +0 -0
  46. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/ccxt/utils.py +0 -0
  47. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/tardis/data.py +0 -0
  48. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/connectors/tardis/utils.py +0 -0
  49. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/__init__.py +0 -0
  50. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/account.py +0 -0
  51. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/basics.py +0 -0
  52. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/context.py +0 -0
  53. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/deque.py +0 -0
  54. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/errors.py +0 -0
  55. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/exceptions.py +0 -0
  56. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/helpers.py +0 -0
  57. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/initializer.py +0 -0
  58. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/interfaces.py +0 -0
  59. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/loggers.py +0 -0
  60. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/mixins/__init__.py +0 -0
  61. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/mixins/market.py +0 -0
  62. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/mixins/processing.py +0 -0
  63. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/mixins/subscription.py +0 -0
  64. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/mixins/trading.py +0 -0
  65. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/series.pxd +0 -0
  66. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/series.pyi +0 -0
  67. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/series.pyx +0 -0
  68. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/utils.pyi +0 -0
  69. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/core/utils.pyx +0 -0
  70. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/data/__init__.py +0 -0
  71. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/data/composite.py +0 -0
  72. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/data/helpers.py +0 -0
  73. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/data/hft.py +0 -0
  74. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/data/readers.py +0 -0
  75. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/data/registry.py +0 -0
  76. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/data/tardis.py +0 -0
  77. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/emitters/__init__.py +0 -0
  78. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/emitters/base.py +0 -0
  79. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/emitters/composite.py +0 -0
  80. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/emitters/csv.py +0 -0
  81. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/emitters/prometheus.py +0 -0
  82. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/emitters/questdb.py +0 -0
  83. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/exporters/__init__.py +0 -0
  84. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/exporters/composite.py +0 -0
  85. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/exporters/formatters/__init__.py +0 -0
  86. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/exporters/formatters/base.py +0 -0
  87. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/exporters/formatters/incremental.py +0 -0
  88. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/exporters/formatters/slack.py +0 -0
  89. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/exporters/redis_streams.py +0 -0
  90. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/exporters/slack.py +0 -0
  91. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/features/__init__.py +0 -0
  92. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/features/core.py +0 -0
  93. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/features/orderbook.py +0 -0
  94. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/features/price.py +0 -0
  95. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/features/trades.py +0 -0
  96. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/features/utils.py +0 -0
  97. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/gathering/simplest.py +0 -0
  98. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/health/__init__.py +0 -0
  99. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/health/base.py +0 -0
  100. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/loggers/__init__.py +0 -0
  101. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/loggers/csv.py +0 -0
  102. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/loggers/factory.py +0 -0
  103. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/loggers/inmemory.py +0 -0
  104. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/loggers/mongo.py +0 -0
  105. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/math/__init__.py +0 -0
  106. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/math/stats.py +0 -0
  107. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/notifications/__init__.py +0 -0
  108. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/notifications/composite.py +0 -0
  109. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/notifications/slack.py +0 -0
  110. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/notifications/throttler.py +0 -0
  111. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/pandaz/__init__.py +0 -0
  112. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/resources/_build.py +0 -0
  113. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
  114. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/resources/instruments/symbols-binance.json +0 -0
  115. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
  116. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
  117. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
  118. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
  119. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
  120. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restarts/__init__.py +0 -0
  121. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restarts/state_resolvers.py +0 -0
  122. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restarts/time_finders.py +0 -0
  123. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restorers/__init__.py +0 -0
  124. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restorers/factory.py +0 -0
  125. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restorers/interfaces.py +0 -0
  126. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restorers/state.py +0 -0
  127. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/restorers/utils.py +0 -0
  128. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/ta/__init__.py +0 -0
  129. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/ta/indicators.pxd +0 -0
  130. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/ta/indicators.pyi +0 -0
  131. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/ta/indicators.pyx +0 -0
  132. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/trackers/__init__.py +0 -0
  133. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/trackers/advanced.py +0 -0
  134. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/trackers/composite.py +0 -0
  135. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/trackers/rebalancers.py +0 -0
  136. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/trackers/riskctrl.py +0 -0
  137. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/trackers/sizers.py +0 -0
  138. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/__init__.py +0 -0
  139. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/_pyxreloader.py +0 -0
  140. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/charting/lookinglass.py +0 -0
  141. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  142. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/collections.py +0 -0
  143. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/marketdata/binance.py +0 -0
  144. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/marketdata/ccxt.py +0 -0
  145. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/marketdata/dukas.py +0 -0
  146. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/misc.py +0 -0
  147. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/ntp.py +0 -0
  148. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/numbers_utils.py +0 -0
  149. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/orderbook.py +0 -0
  150. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/plotting/__init__.py +0 -0
  151. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/plotting/dashboard.py +0 -0
  152. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/plotting/data.py +0 -0
  153. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/plotting/interfaces.py +0 -0
  154. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  155. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  156. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/questdb.py +0 -0
  157. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/runner/__init__.py +0 -0
  158. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  159. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/runner/accounts.py +0 -0
  160. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/runner/configs.py +0 -0
  161. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/runner/factory.py +0 -0
  162. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/runner/runner.py +0 -0
  163. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/time.py +0 -0
  164. {qubx-0.6.42 → qubx-0.6.44}/src/qubx/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Qubx
3
- Version: 0.6.42
3
+ Version: 0.6.44
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Author: Dmitry Marienko
6
6
  Author-email: dmitry.marienko@xlydian.com
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "Qubx"
7
- version = "0.6.42"
7
+ version = "0.6.44"
8
8
  description = "Qubx - Quantitative Trading Framework"
9
9
  authors = [ "Dmitry Marienko <dmitry.marienko@xlydian.com>", "Yuriy Arabskyy <yuriy.arabskyy@xlydian.com>",]
10
10
  readme = "README.md"
@@ -218,7 +218,7 @@ def find_instruments_and_exchanges(
218
218
  exchange: ExchangeName_t | None,
219
219
  ) -> tuple[list[Instrument], list[ExchangeName_t]]:
220
220
  _instrs: list[Instrument] = []
221
- _exchanges = [] if exchange is None else [exchange.lower()]
221
+ _exchanges = [] if exchange is None else [exchange]
222
222
  for i in instruments:
223
223
  match i:
224
224
  case str():
@@ -283,13 +283,14 @@ class InstrumentsLookup:
283
283
  query_exchanges=query_exchanges,
284
284
  )
285
285
 
286
+ #todo: temporaty disabled ccxt call to exchange, due to conectivity issues. Revert for bitfinex live usage
286
287
  def _update_bitfinex(self, path: str, query_exchanges: bool = False):
287
288
  self._ccxt_update(
288
289
  path,
289
290
  "bitfinex.f",
290
291
  {"bitfinex.f": "bitfinex"},
291
292
  keep_types=[MarketType.SWAP],
292
- query_exchanges=query_exchanges,
293
+ query_exchanges=False,
293
294
  )
294
295
 
295
296
  def _update_bitmex(self, path: str, query_exchanges: bool = False):
@@ -1406,8 +1406,8 @@ def chart_signals(
1406
1406
  if show_leverage:
1407
1407
  leverage = calculate_leverage(portfolio, result.capital, start, symbol)
1408
1408
  indicators["Leverage"] = ["area", "cyan", leverage]
1409
- symbol_count = len(portfolio.filter(like="_PnL").columns)
1410
- pnl = portfolio.filter(regex=f"{symbol}_PnL").cumsum() + result.capital / symbol_count
1409
+ # symbol_count = len(portfolio.filter(like="_PnL").columns)
1410
+ pnl = portfolio.filter(regex=f"{symbol}_PnL").cumsum() + result.capital # / symbol_count
1411
1411
  pnl = pnl.loc[start:]
1412
1412
  if apply_commissions:
1413
1413
  comm = portfolio.filter(regex=f"{symbol}_Commissions").loc[start:].cumsum()
@@ -109,6 +109,8 @@ class UniverseManager(IUniverseManager):
109
109
  if self._has_position(instr):
110
110
  self._removal_queue[instr] = (if_has_position_then, skip_callback)
111
111
  to_keep.append(instr)
112
+ else:
113
+ to_remove.append(instr)
112
114
  return to_remove, to_keep
113
115
 
114
116
  def __cleanup_removal_queue(self, instruments: list[Instrument]):
@@ -633,6 +633,17 @@ def rolling_std_with_mean(x: pd.Series, mean: float | pd.Series, window: int):
633
633
  return np.sqrt((((x - mean) ** 2).rolling(window=window).sum() / (window - 1)))
634
634
 
635
635
 
636
+ def rolling_zscore(x: pd.Series, window: int):
637
+ """
638
+ Calculates rolling z-score for data from x
639
+ :param x: series data
640
+ :param window: window
641
+ :return: rolling z-score
642
+ """
643
+ r = x.rolling(window=window)
644
+ return (x - r.mean()) / r.std(ddof=0)
645
+
646
+
636
647
  def bollinger(x: pd.Series, window=14, nstd=2, mean="sma") -> pd.DataFrame:
637
648
  """
638
649
  Bollinger Bands indicator
@@ -1326,11 +1337,11 @@ def rolling_rank(x, period, pctls=(25, 50, 75)):
1326
1337
  raise ValueError(f"Period {period} exceeds number of data records {len(x)} ")
1327
1338
 
1328
1339
  if isinstance(x, pd.DataFrame):
1329
- z = pd.DataFrame.from_dict({c: rolling_rank(s, period, pctls) for c, s in x.iteritems()})
1340
+ z = pd.DataFrame.from_dict({c: rolling_rank(s, period, pctls) for c, s in x.items()})
1330
1341
  elif isinstance(x, pd.Series):
1331
- z = pd.Series(_rolling_rank(x.values, period, pctls), x.index, name=x.name)
1342
+ z = pd.Series(_rolling_rank(np.ascontiguousarray(x.values), period, pctls), x.index, name=x.name)
1332
1343
  else:
1333
- z = _rolling_rank(x.values, period, pctls)
1344
+ z = _rolling_rank(np.ascontiguousarray(x), period, pctls)
1334
1345
  return z
1335
1346
 
1336
1347
 
@@ -1532,6 +1543,7 @@ def choppiness(
1532
1543
  volatility_estimator="t",
1533
1544
  volume_adjusting=False,
1534
1545
  identification="strong",
1546
+ with_raw_indicator=False,
1535
1547
  ) -> pd.Series:
1536
1548
  """
1537
1549
  Calculate market choppiness index using volatility-based formula.
@@ -1566,7 +1578,8 @@ def choppiness(
1566
1578
  0 when exiting trending regime (crossing above lower threshold)
1567
1579
  - 'weak': Binary classification focused on choppiness - returns 1 when entering choppy regime (crossing above upper threshold),
1568
1580
  0 when entering trending regime (crossing below lower threshold)
1569
-
1581
+ with_raw_indicator : bool, default False
1582
+ If True, returns the raw indicator value instead of the classification
1570
1583
  Returns
1571
1584
  -------
1572
1585
  pd.Series
@@ -1619,7 +1632,7 @@ def choppiness(
1619
1632
  # f0[(ci < lower) & (ci.shift(1) >= lower)] = 0
1620
1633
  # return f0.ffill().fillna(0)
1621
1634
 
1622
- return f0.ffill().fillna(0)
1635
+ return f0.ffill().fillna(0) if not with_raw_indicator else ci
1623
1636
 
1624
1637
 
1625
1638
  @njit
@@ -590,9 +590,11 @@ class OhlcDict(dict):
590
590
  print(str(d))
591
591
  """
592
592
 
593
+ _orig: dict[str, pd.DataFrame | pd.Series | OHLCV]
593
594
  _fields: Set[str]
594
595
 
595
596
  def __init__(self, orig: dict[str, pd.DataFrame | pd.Series | OHLCV]):
597
+ self._orig = orig
596
598
  _o_copy = {}
597
599
  _lst = []
598
600
  if isinstance(orig, dict):
@@ -639,3 +641,16 @@ class OhlcDict(dict):
639
641
 
640
642
  def __repr__(self) -> str:
641
643
  return self.display(3, 3)
644
+
645
+ def __reduce__(self):
646
+ """
647
+ For joblib Parallel compatibility - defines how to pickle the object
648
+ """
649
+ # Return the class, constructor arguments, and additional state
650
+ data = {k: v for k, v in self.items()}
651
+ return (self.__class__, (data,), {"_orig": self._orig})
652
+
653
+ def __setstate__(self, state):
654
+ """Restore object from pickle state"""
655
+ # Recreate the object using the constructor
656
+ self.__init__(state["_orig"])
@@ -8,6 +8,7 @@ from various sources.
8
8
  import os
9
9
  from pathlib import Path
10
10
  from pymongo import MongoClient
11
+ from datetime import datetime, timedelta
11
12
 
12
13
  import pandas as pd
13
14
 
@@ -152,13 +153,16 @@ class MongoDBBalanceRestorer(IBalanceRestorer):
152
153
  Example: {'USDT': AssetBalance(total=100000.0, locked=0.0)}
153
154
  """
154
155
  try:
155
- match_query = {
156
+ now = datetime.utcnow()
157
+ lookup_range = now - timedelta(days=7)
158
+ base_match = {
156
159
  "log_type": "balance",
157
160
  "strategy_name": self.strategy_name,
161
+ "timestamp": {"$gte": lookup_range}
158
162
  }
159
163
 
160
164
  latest_run_doc = (
161
- self.collection.find(match_query, {"run_id": 1, "timestamp": 1})
165
+ self.collection.find(base_match, {"run_id": 1, "timestamp": 1})
162
166
  .sort("timestamp", -1)
163
167
  .limit(1)
164
168
  )
@@ -172,23 +176,34 @@ class MongoDBBalanceRestorer(IBalanceRestorer):
172
176
 
173
177
  logger.info(f"Restoring balances from MongoDB for run_id: {latest_run_id}")
174
178
 
175
- query = {**match_query, "run_id": latest_run_id}
176
- logs = self.collection.find(query).sort("timestamp", 1)
177
-
178
- balances = {}
179
-
180
- for log in logs:
179
+ pipeline = [
180
+ {"$match": {**base_match, "run_id": latest_run_id}},
181
+ {"$sort": {"timestamp": -1}},
182
+ {
183
+ "$group": {
184
+ "_id": "$currency",
185
+ "doc": {"$first": "$$ROOT"}
186
+ }
187
+ }
188
+ ]
189
+
190
+ cursor = self.collection.aggregate(pipeline)
191
+ balances: dict[str, AssetBalance] = {}
192
+
193
+ for entry in cursor:
194
+ log = entry["doc"]
181
195
  currency = log.get("currency")
182
- if currency:
183
- total = log.get("total", 0.0)
184
- locked = log.get("locked", 0.0)
185
-
186
- balance = AssetBalance(
187
- total=total,
188
- locked=locked,
189
- )
190
- balance.free = total - locked
191
- balances[currency] = balance
196
+ if not currency:
197
+ continue
198
+ total = log.get("total", 0.0)
199
+ locked = log.get("locked", 0.0)
200
+
201
+ balance = AssetBalance(
202
+ total=total,
203
+ locked=locked,
204
+ )
205
+ balance.free = total - locked
206
+ balances[currency] = balance
192
207
 
193
208
  return balances
194
209
  except Exception as e:
@@ -8,6 +8,7 @@ for restoring positions from various sources.
8
8
  import os
9
9
  from pathlib import Path
10
10
  from pymongo import MongoClient
11
+ from datetime import datetime, timedelta
11
12
 
12
13
  import pandas as pd
13
14
 
@@ -168,13 +169,17 @@ class MongoDBPositionRestorer(IPositionRestorer):
168
169
  A dictionary mapping instruments to positions.
169
170
  """
170
171
  try:
171
- match_query = {
172
+ now = datetime.utcnow()
173
+ lookup_range = now - timedelta(days=7)
174
+
175
+ base_match = {
172
176
  "log_type": "positions",
173
177
  "strategy_name": self.strategy_name,
178
+ "timestamp": {"$gte": lookup_range}
174
179
  }
175
180
 
176
181
  latest_run_doc = (
177
- self.collection.find(match_query, {"run_id": 1, "timestamp": 1})
182
+ self.collection.find(base_match, {"run_id": 1, "timestamp": 1})
178
183
  .sort("timestamp", -1)
179
184
  .limit(1)
180
185
  )
@@ -188,20 +193,34 @@ class MongoDBPositionRestorer(IPositionRestorer):
188
193
 
189
194
  logger.info(f"Restoring positions from MongoDB for run_id: {latest_run_id}")
190
195
 
191
- query = {**match_query, "run_id": latest_run_id}
192
- logs = self.collection.find(query).sort("timestamp", 1)
193
-
194
- positions = {}
195
- seen_keys = set()
196
-
197
- for log in logs:
198
- key = (log.get("symbol"), log.get("exchange"), log.get("market_type"))
199
- if None in key or key in seen_keys:
196
+ pipeline = [
197
+ {"$match": {**base_match, "run_id": latest_run_id}},
198
+ {"$sort": {"timestamp": -1}},
199
+ {
200
+ "$group": {
201
+ "_id": {
202
+ "symbol": "$symbol",
203
+ "exchange": "$exchange",
204
+ "market_type": "$market_type"
205
+ },
206
+ "doc": {"$first": "$$ROOT"}
207
+ }
208
+ }
209
+ ]
210
+
211
+ cursor = self.collection.aggregate(pipeline)
212
+
213
+ positions: dict[Instrument, Position] = {}
214
+
215
+ for entry in cursor:
216
+ log = entry["doc"]
217
+
218
+ symbol = log.get("symbol")
219
+ exchange = log.get("exchange")
220
+ market_type = log.get("market_type")
221
+
222
+ if not (symbol and exchange and market_type):
200
223
  continue
201
- seen_keys.add(key)
202
-
203
- symbol = log["symbol"]
204
- exchange = log["exchange"]
205
224
 
206
225
  instrument = lookup.find_symbol(exchange, symbol)
207
226
  if instrument is None:
@@ -208,13 +208,16 @@ class MongoDBSignalRestorer(ISignalRestorer):
208
208
  A dictionary mapping instruments to lists of signals.
209
209
  """
210
210
  try:
211
- match_query = {
211
+ now = datetime.utcnow()
212
+ lookup_range = now - timedelta(days=30)
213
+ base_match = {
212
214
  "log_type": "signals",
213
215
  "strategy_name": self.strategy_name,
216
+ "timestamp": {"$gte": lookup_range}
214
217
  }
215
218
 
216
219
  latest_run_doc = (
217
- self.collection.find(match_query, {"run_id": 1, "timestamp": 1})
220
+ self.collection.find(base_match, {"run_id": 1, "timestamp": 1})
218
221
  .sort("timestamp", -1)
219
222
  .limit(1)
220
223
  )
@@ -228,12 +231,33 @@ class MongoDBSignalRestorer(ISignalRestorer):
228
231
 
229
232
  logger.info(f"Restoring signals from MongoDB for run_id: {latest_run_id}")
230
233
 
231
- query = {**match_query, "run_id": latest_run_id}
232
- logs = self.collection.find(query).sort("timestamp", 1)
233
-
234
+ pipeline = [
235
+ {"$match": {"log_type": "signals", "strategy_name": self.strategy_name, "run_id": latest_run_id}},
236
+ {"$sort": {"timestamp": -1}},
237
+ {
238
+ "$group": {
239
+ "_id": {
240
+ "symbol": "$symbol",
241
+ "exchange": "$exchange",
242
+ "market_type": "$market_type",
243
+ },
244
+ "signals": {"$push": "$$ROOT"}
245
+ }
246
+ },
247
+ {
248
+ "$project": {
249
+ "signals": {"$slice": ["$signals", 20]}
250
+ }
251
+ },
252
+ {"$unwind": "$signals"}
253
+ ]
254
+
255
+ cursor = self.collection.aggregate(pipeline)
256
+
234
257
  result: dict[Instrument, list[TargetPosition]] = {}
235
258
 
236
- for log in logs:
259
+ for entry in cursor:
260
+ log = entry["signals"]
237
261
  try:
238
262
  instrument = lookup.find_symbol(log["exchange"], log["symbol"])
239
263
  if instrument is None:
@@ -281,7 +305,6 @@ class MongoDBSignalRestorer(ISignalRestorer):
281
305
  )
282
306
 
283
307
  result.setdefault(instrument, []).append(target_position)
284
-
285
308
  except Exception as e:
286
309
  logger.exception(f"Failed to process signal document: {e}")
287
310
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes