Qubx 0.6.50__tar.gz → 0.6.53__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 (165) hide show
  1. {qubx-0.6.50 → qubx-0.6.53}/PKG-INFO +1 -1
  2. {qubx-0.6.50 → qubx-0.6.53}/pyproject.toml +1 -1
  3. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/mixins/processing.py +1 -1
  4. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/mixins/universe.py +4 -3
  5. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/data/composite.py +9 -0
  6. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/notifications/slack.py +14 -8
  7. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restarts/state_resolvers.py +2 -2
  8. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/runner/factory.py +3 -1
  9. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/runner/runner.py +15 -1
  10. {qubx-0.6.50 → qubx-0.6.53}/LICENSE +0 -0
  11. {qubx-0.6.50 → qubx-0.6.53}/README.md +0 -0
  12. {qubx-0.6.50 → qubx-0.6.53}/build.py +0 -0
  13. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/__init__.py +0 -0
  14. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/_nb_magic.py +0 -0
  15. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/__init__.py +0 -0
  16. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/account.py +0 -0
  17. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/broker.py +0 -0
  18. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/data.py +0 -0
  19. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/management.py +0 -0
  20. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/ome.py +0 -0
  21. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/optimization.py +0 -0
  22. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/runner.py +0 -0
  23. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/simulated_data.py +0 -0
  24. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/simulated_exchange.py +0 -0
  25. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/simulator.py +0 -0
  26. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/backtester/utils.py +0 -0
  27. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/cli/__init__.py +0 -0
  28. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/cli/commands.py +0 -0
  29. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/cli/deploy.py +0 -0
  30. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/cli/misc.py +0 -0
  31. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/cli/release.py +0 -0
  32. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/cli/tui.py +0 -0
  33. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/__init__.py +0 -0
  34. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/account.py +0 -0
  35. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/broker.py +0 -0
  36. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/data.py +0 -0
  37. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/exceptions.py +0 -0
  38. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/exchanges/__init__.py +0 -0
  39. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/exchanges/binance/broker.py +0 -0
  40. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/exchanges/binance/exchange.py +0 -0
  41. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex.py +0 -0
  42. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/exchanges/bitfinex/bitfinex_account.py +0 -0
  43. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/exchanges/kraken/kraken.py +0 -0
  44. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/factory.py +0 -0
  45. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/reader.py +0 -0
  46. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/ccxt/utils.py +0 -0
  47. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/tardis/data.py +0 -0
  48. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/connectors/tardis/utils.py +0 -0
  49. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/__init__.py +0 -0
  50. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/account.py +0 -0
  51. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/basics.py +0 -0
  52. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/context.py +0 -0
  53. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/deque.py +0 -0
  54. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/errors.py +0 -0
  55. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/exceptions.py +0 -0
  56. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/helpers.py +0 -0
  57. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/initializer.py +0 -0
  58. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/interfaces.py +0 -0
  59. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/loggers.py +0 -0
  60. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/lookups.py +0 -0
  61. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/metrics.py +0 -0
  62. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/mixins/__init__.py +0 -0
  63. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/mixins/market.py +0 -0
  64. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/mixins/subscription.py +0 -0
  65. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/mixins/trading.py +0 -0
  66. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/series.pxd +0 -0
  67. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/series.pyi +0 -0
  68. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/series.pyx +0 -0
  69. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/utils.pyi +0 -0
  70. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/core/utils.pyx +0 -0
  71. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/data/__init__.py +0 -0
  72. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/data/helpers.py +0 -0
  73. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/data/hft.py +0 -0
  74. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/data/readers.py +0 -0
  75. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/data/registry.py +0 -0
  76. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/data/tardis.py +0 -0
  77. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/emitters/__init__.py +0 -0
  78. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/emitters/base.py +0 -0
  79. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/emitters/composite.py +0 -0
  80. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/emitters/csv.py +0 -0
  81. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/emitters/prometheus.py +0 -0
  82. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/emitters/questdb.py +0 -0
  83. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/exporters/__init__.py +0 -0
  84. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/exporters/composite.py +0 -0
  85. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/exporters/formatters/__init__.py +0 -0
  86. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/exporters/formatters/base.py +0 -0
  87. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/exporters/formatters/incremental.py +0 -0
  88. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/exporters/formatters/slack.py +0 -0
  89. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/exporters/redis_streams.py +0 -0
  90. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/exporters/slack.py +0 -0
  91. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/features/__init__.py +0 -0
  92. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/features/core.py +0 -0
  93. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/features/orderbook.py +0 -0
  94. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/features/price.py +0 -0
  95. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/features/trades.py +0 -0
  96. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/features/utils.py +0 -0
  97. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/gathering/simplest.py +0 -0
  98. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/health/__init__.py +0 -0
  99. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/health/base.py +0 -0
  100. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/loggers/__init__.py +0 -0
  101. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/loggers/csv.py +0 -0
  102. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/loggers/factory.py +0 -0
  103. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/loggers/inmemory.py +0 -0
  104. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/loggers/mongo.py +0 -0
  105. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/math/__init__.py +0 -0
  106. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/math/stats.py +0 -0
  107. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/notifications/__init__.py +0 -0
  108. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/notifications/composite.py +0 -0
  109. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/notifications/throttler.py +0 -0
  110. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/pandaz/__init__.py +0 -0
  111. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/pandaz/ta.py +0 -0
  112. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/pandaz/utils.py +0 -0
  113. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/resources/_build.py +0 -0
  114. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/resources/instruments/symbols-binance.cm.json +0 -0
  115. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/resources/instruments/symbols-binance.json +0 -0
  116. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/resources/instruments/symbols-binance.um.json +0 -0
  117. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/resources/instruments/symbols-bitfinex.f.json +0 -0
  118. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/resources/instruments/symbols-bitfinex.json +0 -0
  119. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/resources/instruments/symbols-kraken.f.json +0 -0
  120. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/resources/instruments/symbols-kraken.json +0 -0
  121. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restarts/__init__.py +0 -0
  122. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restarts/time_finders.py +0 -0
  123. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restorers/__init__.py +0 -0
  124. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restorers/balance.py +0 -0
  125. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restorers/factory.py +0 -0
  126. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restorers/interfaces.py +0 -0
  127. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restorers/position.py +0 -0
  128. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restorers/signal.py +0 -0
  129. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restorers/state.py +0 -0
  130. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/restorers/utils.py +0 -0
  131. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/ta/__init__.py +0 -0
  132. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/ta/indicators.pxd +0 -0
  133. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/ta/indicators.pyi +0 -0
  134. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/ta/indicators.pyx +0 -0
  135. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/trackers/__init__.py +0 -0
  136. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/trackers/advanced.py +0 -0
  137. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/trackers/composite.py +0 -0
  138. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/trackers/rebalancers.py +0 -0
  139. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/trackers/riskctrl.py +0 -0
  140. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/trackers/sizers.py +0 -0
  141. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/__init__.py +0 -0
  142. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/_pyxreloader.py +0 -0
  143. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/charting/lookinglass.py +0 -0
  144. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  145. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/collections.py +0 -0
  146. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/marketdata/binance.py +0 -0
  147. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/marketdata/ccxt.py +0 -0
  148. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/marketdata/dukas.py +0 -0
  149. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/misc.py +0 -0
  150. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/ntp.py +0 -0
  151. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/numbers_utils.py +0 -0
  152. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/orderbook.py +0 -0
  153. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/plotting/__init__.py +0 -0
  154. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/plotting/dashboard.py +0 -0
  155. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/plotting/data.py +0 -0
  156. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/plotting/interfaces.py +0 -0
  157. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/plotting/renderers/__init__.py +0 -0
  158. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/plotting/renderers/plotly.py +0 -0
  159. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/questdb.py +0 -0
  160. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/runner/__init__.py +0 -0
  161. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/runner/_jupyter_runner.pyt +0 -0
  162. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/runner/accounts.py +0 -0
  163. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/runner/configs.py +0 -0
  164. {qubx-0.6.50 → qubx-0.6.53}/src/qubx/utils/time.py +0 -0
  165. {qubx-0.6.50 → qubx-0.6.53}/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.50
3
+ Version: 0.6.53
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.50"
7
+ version = "0.6.53"
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"
@@ -383,7 +383,7 @@ class ProcessingManager(IProcessingManager):
383
383
  missing_symbols = [inst.symbol for inst in missing_instruments]
384
384
  logger.info(
385
385
  f"Phase 1: Waiting for all instruments ({ready_instruments}/{total_instruments} ready). "
386
- f"Missing: {missing_symbols}. Timeout in {self.DATA_READY_TIMEOUT_SECONDS - elapsed_time_seconds:.1f}s"
386
+ f"Missing: {missing_symbols}. Timeout in {self.DATA_READY_TIMEOUT_SECONDS - elapsed_time_seconds}s"
387
387
  )
388
388
  return False
389
389
  else:
@@ -114,11 +114,12 @@ class UniverseManager(IUniverseManager):
114
114
  self._removal_queue.pop(instr)
115
115
 
116
116
  def add_instruments(self, instruments: list[Instrument]):
117
- self.__do_add_instruments(instruments)
117
+ to_add = list(set([instr for instr in instruments if instr not in self._instruments]))
118
+ self.__do_add_instruments(to_add)
118
119
  self.__cleanup_removal_queue(instruments)
119
- self._strategy.on_universe_change(self._context, instruments, [])
120
+ self._strategy.on_universe_change(self._context, to_add, [])
120
121
  self._subscription_manager.commit()
121
- self._instruments.update(instruments)
122
+ self._instruments.update(to_add)
122
123
 
123
124
  def remove_instruments(
124
125
  self,
@@ -128,6 +128,12 @@ class IteratedDataStreamsSlicer(Iterator[SlicerOutData]):
128
128
  data.extend(self._load_next_chunk_to_buffer(key)) # - get next chunk of data
129
129
  except StopIteration:
130
130
  self._remove_iterator(key)
131
+ # Return empty list if no data is available
132
+ return values
133
+
134
+ # Check if data is still empty after attempting to load
135
+ if not data:
136
+ return values
131
137
 
132
138
  # pull most past elements
133
139
  v = data[-1]
@@ -139,6 +145,9 @@ class IteratedDataStreamsSlicer(Iterator[SlicerOutData]):
139
145
  except StopIteration:
140
146
  self._remove_iterator(key)
141
147
  break
148
+ # Check if data is still empty after loading attempt
149
+ if not data:
150
+ break
142
151
  v = data[-1]
143
152
 
144
153
  return values
@@ -5,6 +5,7 @@ This module provides a Slack implementation of IStrategyLifecycleNotifier.
5
5
  """
6
6
 
7
7
  import datetime
8
+ import threading
8
9
  from concurrent.futures import ThreadPoolExecutor
9
10
  from typing import Any
10
11
 
@@ -52,6 +53,9 @@ class SlackLifecycleNotifier(IStrategyLifecycleNotifier):
52
53
  self._emoji_error = emoji_error
53
54
  self._throttler = throttler if throttler is not None else NoThrottling()
54
55
 
56
+ # Add a lock for thread-safe throttling operations
57
+ self._throttler_lock = threading.Lock()
58
+
55
59
  self._executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="slack_notifier")
56
60
 
57
61
  logger.info(f"[SlackLifecycleNotifier] Initialized for environment '{environment}'")
@@ -75,10 +79,16 @@ class SlackLifecycleNotifier(IStrategyLifecycleNotifier):
75
79
  throttle_key: Optional key for throttling (if None, no throttling is applied)
76
80
  """
77
81
  try:
78
- # Check if the message should be throttled
79
- if throttle_key is not None and not self._throttler.should_send(throttle_key):
80
- logger.debug(f"[SlackLifecycleNotifier] Throttled message with key '{throttle_key}': {message}")
81
- return
82
+ # Thread-safe throttling check and registration
83
+ if throttle_key is not None:
84
+ with self._throttler_lock:
85
+ if not self._throttler.should_send(throttle_key):
86
+ logger.debug(f"[SlackLifecycleNotifier] Throttled message with key '{throttle_key}': {message}")
87
+ return
88
+ # Immediately register that we're about to send this message
89
+ # This prevents race conditions where multiple threads check should_send
90
+ # before any of them call register_sent
91
+ self._throttler.register_sent(throttle_key)
82
92
 
83
93
  # Submit the task to the executor
84
94
  self._executor.submit(self._post_to_slack_impl, message, emoji, color, metadata, throttle_key)
@@ -132,10 +142,6 @@ class SlackLifecycleNotifier(IStrategyLifecycleNotifier):
132
142
  response = requests.post(self._webhook_url, json=data)
133
143
  response.raise_for_status()
134
144
 
135
- # Register that we sent the message (for throttling)
136
- if throttle_key is not None:
137
- self._throttler.register_sent(throttle_key)
138
-
139
145
  logger.debug(f"[SlackLifecycleNotifier] Successfully posted message: {message}")
140
146
  return True
141
147
  except requests.RequestException as e:
@@ -55,7 +55,7 @@ class StateResolver:
55
55
  elif abs(live_qty) > abs(sim_qty) and abs(live_qty) > instrument.lot_size:
56
56
  qty_diff = sim_qty - live_qty
57
57
  logger.info(
58
- f"Reducing position for {instrument.symbol}: {live_qty} -> {sim_qty} (diff: {qty_diff})"
58
+ f"Reducing position for {instrument.symbol}: {live_qty} -> {sim_qty} (diff: {qty_diff:.4f})"
59
59
  )
60
60
  ctx.trade(instrument, qty_diff)
61
61
 
@@ -123,7 +123,7 @@ class StateResolver:
123
123
  # Only trade if there's a difference
124
124
  if abs(qty_diff) > instrument.lot_size:
125
125
  logger.info(
126
- f"Syncing position for {instrument.symbol}: {live_qty} -> {sim_pos.quantity} (diff: {qty_diff})"
126
+ f"Syncing position for {instrument.symbol}: {live_qty} -> {sim_pos.quantity} (diff: {qty_diff:.4f})"
127
127
  )
128
128
  ctx.trade(instrument, qty_diff)
129
129
 
@@ -298,7 +298,9 @@ def create_lifecycle_notifiers(
298
298
  params[key] = resolve_env_vars(value)
299
299
 
300
300
  # Create throttler if configured or use default TimeWindowThrottler
301
- if "SlackLifecycleNotifier" in notifier_class_name and "throttler" not in params:
301
+ if "SlackLifecycleNotifier" in notifier_class_name and (
302
+ "throttle" not in params or params["throttle"] is None
303
+ ):
302
304
  # Import here to avoid circular imports
303
305
  from qubx.notifications.throttler import TimeWindowThrottler
304
306
 
@@ -28,6 +28,7 @@ from qubx.core.basics import (
28
28
  CtrlChannel,
29
29
  Instrument,
30
30
  LiveTimeProvider,
31
+ Position,
31
32
  RestoredState,
32
33
  TransactionCostsCalculator,
33
34
  )
@@ -688,13 +689,21 @@ def _run_warmup(
688
689
  # - get the instruments from the warmup runner context
689
690
  _instruments = warmup_runner.ctx.instruments
690
691
  _positions = warmup_account.get_positions()
691
- _positions = {k: v for k, v in _positions.items() if k in _instruments}
692
+ _positions = {k: v for k, v in _positions.items() if k in _instruments and v is not None and v.quantity is not None}
692
693
  _orders = warmup_account.get_orders()
693
694
  instrument_to_orders = defaultdict(list)
694
695
  for o in _orders.values():
695
696
  if o.instrument in _instruments:
696
697
  instrument_to_orders[o.instrument].append(o)
697
698
 
699
+ # - find instruments with nonzero positions from restored state and add them to the context
700
+ if restored_state is not None:
701
+ restored_positions = {k: p for k, p in restored_state.positions.items() if p.is_open()}
702
+ # - if there is no warmup position for a restored position, then create a new zero position
703
+ for pos in restored_positions.values():
704
+ if pos.instrument not in _positions:
705
+ _positions[pos.instrument] = Position(pos.instrument)
706
+
698
707
  # - set the warmup positions and orders
699
708
  ctx.set_warmup_positions(_positions)
700
709
  ctx.set_warmup_orders(instrument_to_orders)
@@ -766,6 +775,11 @@ def simulate_strategy(
766
775
  for a, c in cond.items():
767
776
  conditions.append(dict2lambda(a, c))
768
777
 
778
+ # - if a parameter is of type list, then transform it to list of lists to avoid invalid variation
779
+ for k, v in cfg.parameters.items():
780
+ if isinstance(v, list):
781
+ cfg.parameters[k] = [v]
782
+
769
783
  experiments = variate(stg_cls, **(cfg.parameters | cfg.simulation.variate), conditions=conditions)
770
784
  experiments = {f"{simulation_name}.{_v_id}.[{k}]": v for k, v in experiments.items()}
771
785
  print(f"Parameters variation is configured. There are {len(experiments)} simulations to run.")
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
File without changes
File without changes
File without changes
File without changes