Qubx 0.6.50__cp312-cp312-manylinux_2_39_x86_64.whl → 0.6.52__cp312-cp312-manylinux_2_39_x86_64.whl

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.

@@ -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,
@@ -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
  )
@@ -695,6 +696,14 @@ def _run_warmup(
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.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Qubx
3
- Version: 0.6.50
3
+ Version: 0.6.52
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Author: Dmitry Marienko
6
6
  Author-email: dmitry.marienko@xlydian.com
@@ -49,15 +49,15 @@ qubx/core/lookups.py,sha256=aEuyZqd_N4cQ-oHz3coEHcdX9Yb0cP5-NwDuj-DQyNk,19477
49
49
  qubx/core/metrics.py,sha256=74xIecCvlxVXl0gy0JvgjJ2X5gg-RMmVZw9hQikkHE0,60269
50
50
  qubx/core/mixins/__init__.py,sha256=AMCLvfNuIb1kkQl3bhCj9jIOEl2eKcVPJeyLgrkB-rk,329
51
51
  qubx/core/mixins/market.py,sha256=lBappEimPhIuI0vmUvwVlIztkYjlEjJBpP-AdpfudII,3948
52
- qubx/core/mixins/processing.py,sha256=1aXHr2gDkZSGELoZXshCzAbw2hqUmEGbDqLZv9dg4Ck,27398
52
+ qubx/core/mixins/processing.py,sha256=VEaK6ZjXTa8jvavj_VpCYfGvLFTHpNoL1AKdRAeear8,27394
53
53
  qubx/core/mixins/subscription.py,sha256=V_g9wCPQ8S5SHkU-qOZ84cV5nReAUrV7DoSNAGG0LPY,10372
54
54
  qubx/core/mixins/trading.py,sha256=idfRPaqrvkfMxzu9mXr9i_xfqLee-ZAOrERxkxv6Ruo,7256
55
- qubx/core/mixins/universe.py,sha256=V8Hs9I9gxSCftWlaVepjlIyAji7R_99OyjlincaOBpU,9758
56
- qubx/core/series.cpython-312-x86_64-linux-gnu.so,sha256=ZcPL248PvVWOO59DSKoR6BaoAsfM2qpAt7Nz73PyCJc,978280
55
+ qubx/core/mixins/universe.py,sha256=tsMpBriLHwK9lAVYvIrO94EIx8_ETSXUlzxN_sDOsL8,9838
56
+ qubx/core/series.cpython-312-x86_64-linux-gnu.so,sha256=SpozS5G2UEoeCbgVsaorjvwbKo5U4d5EqxP3pSktzyM,978280
57
57
  qubx/core/series.pxd,sha256=jBdMwgO8J4Zrue0e_xQ5RlqTXqihpzQNu6V3ckZvvpY,3978
58
58
  qubx/core/series.pyi,sha256=RaHm_oHHiWiNUMJqVfx5FXAXniGLsHxUFOUpacn7GC0,4604
59
59
  qubx/core/series.pyx,sha256=7cM3zZThW59waHiYcZmMxvYj-HYD7Ej_l7nKA4emPjE,46477
60
- qubx/core/utils.cpython-312-x86_64-linux-gnu.so,sha256=XUACaJvhdbuf-9lFv0sDL9SCXVZEEQezI4HfFknBp5A,86568
60
+ qubx/core/utils.cpython-312-x86_64-linux-gnu.so,sha256=c3ZAL_JGRFpOPUlSITmuQ_jjxv4bA1CQAV9c13wtUfI,86568
61
61
  qubx/core/utils.pyi,sha256=a-wS13V2p_dM1CnGq40JVulmiAhixTwVwt0ah5By0Hc,348
62
62
  qubx/core/utils.pyx,sha256=k5QHfEFvqhqWfCob89ANiJDKNG8gGbOh-O4CVoneZ8M,1696
63
63
  qubx/data/__init__.py,sha256=ELZykvpPGWc5rX7QoNyNQwMLgdKMG8MACOByA4pM5hA,549
@@ -99,7 +99,7 @@ qubx/math/__init__.py,sha256=ltHSQj40sCBm3owcvtoZp34h6ws7pZCFcSZgUkTsUCY,114
99
99
  qubx/math/stats.py,sha256=uXm4NpBRxuHFTjXERv8rjM0MAJof8zr1Cklyra4CcBA,4056
100
100
  qubx/notifications/__init__.py,sha256=cb3DGxuiA8UwSTlTeF5pQKy4-vBef3gMeKtfcxEjdN4,547
101
101
  qubx/notifications/composite.py,sha256=fa-rvHEn6k-Fma5N7cT-7Sk7hzVyB0KDs2ktDyoyLxM,2689
102
- qubx/notifications/slack.py,sha256=FZ0zfTA-zRHOsOsIVBtM7mkt4m-adiuiV5yrwliS9RM,7976
102
+ qubx/notifications/slack.py,sha256=RWsLyL4lm6tbmrTlXQo3nPlfiLVJ0vCfY5toJ9G8RWU,8316
103
103
  qubx/notifications/throttler.py,sha256=8jnymPQbrgtN1rD7REQa2sA9teSWTqkk_uT9oaknOyc,5618
104
104
  qubx/pandaz/__init__.py,sha256=6BYz6gSgxjNa7WP1XqWflYG7WIq1ppSD9h1XGR5M5YQ,682
105
105
  qubx/pandaz/ta.py,sha256=sIX9YxxB2S2nWU4vnS4rXFuEI5WSY76Ky1TFwf9RhMw,92154
@@ -113,7 +113,7 @@ qubx/resources/instruments/symbols-bitfinex.json,sha256=CpzoVgWzGZRN6RpUNhtJVxa3
113
113
  qubx/resources/instruments/symbols-kraken.f.json,sha256=lwNqml3H7lNUl1h3siySSyE1MRcGfqfhb6BcxLsiKr0,212258
114
114
  qubx/resources/instruments/symbols-kraken.json,sha256=RjUTvkQuuu7V1HfSQREvnA4qqkdkB3-rzykDaQds2rQ,456544
115
115
  qubx/restarts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
- qubx/restarts/state_resolvers.py,sha256=GJ617qwpulqMp_-WhpmsLozQobxgt5lU4ZGOIUaUzas,5606
116
+ qubx/restarts/state_resolvers.py,sha256=xzQEFPGbYFYShSSb0cB3RvWl-8npRau_HrrMgTccSc0,5614
117
117
  qubx/restarts/time_finders.py,sha256=r7yyRhJByV2uqdgamDRX2XClwpWWI9BNpc80t9nk6c0,2448
118
118
  qubx/restorers/__init__.py,sha256=vrnZBPJHR0-6knAccj4bK0tkjUPNRl32qiLr5Mv4aR0,911
119
119
  qubx/restorers/balance.py,sha256=yLV1vBki0XhBxrOhgaJBHuuL8VmIii82LAWgLxusbcE,6967
@@ -124,7 +124,7 @@ qubx/restorers/signal.py,sha256=0QFoy7OzDkK6AAmJEbbmSsHwmAhjMJYYggVFuLraKjk,1089
124
124
  qubx/restorers/state.py,sha256=dLaVnUwRCNRkUqbYyi0RfZs3Q3AdglkI_qTtQ8GDD5Y,7289
125
125
  qubx/restorers/utils.py,sha256=We2gfqwQKWziUYhuUnjb-xo-5tSlbuHWpPQn0CEMTn0,1155
126
126
  qubx/ta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
- qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so,sha256=0LYFj6RfhO043dFJAjzteuNnUyqjXVXRNDGo6ccUrHk,654440
127
+ qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so,sha256=PwEMehN97zxTw_faYmjrDUHu_8-QlfsjOxo3h7bPQKo,654440
128
128
  qubx/ta/indicators.pxd,sha256=Goo0_N0Xnju8XGo3Xs-3pyg2qr_0Nh5C-_26DK8U_IE,4224
129
129
  qubx/ta/indicators.pyi,sha256=19W0uERft49In5bf9jkJHkzJYEyE9gzudN7_DJ5Vdv8,1963
130
130
  qubx/ta/indicators.pyx,sha256=Xgpew46ZxSXsdfSEWYn3A0Q35MLsopB9n7iyCsXTufs,25969
@@ -157,12 +157,12 @@ qubx/utils/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
157
157
  qubx/utils/runner/_jupyter_runner.pyt,sha256=fDj4AUs25jsdGmY9DDeSFufH1JkVhLFwy0BOmVO7nIU,9609
158
158
  qubx/utils/runner/accounts.py,sha256=mpiv6oxr5z97zWt7STYyARMhWQIpc_XFKungb_pX38U,3270
159
159
  qubx/utils/runner/configs.py,sha256=snVZJun6rBC09QZVaUd7BhqNlDZqmDMG7R8gHJeuSkU,3713
160
- qubx/utils/runner/factory.py,sha256=mmhPnrPusxCCuf9q7KyXk6HLD2ibmBvM-SCVtkjh8_8,15091
161
- qubx/utils/runner/runner.py,sha256=MDvASHq_em6g70CaZtDtAQzi5VdFt-W0k5YqZiigFlk,30663
160
+ qubx/utils/runner/factory.py,sha256=eM4-Etcq-FewD2AjH_srFGzP413pm8er95KIZixXRpM,15152
161
+ qubx/utils/runner/runner.py,sha256=9s7mu84U29jCE7FtdW_yKKTzQfaXmCSvANF5cb7xd_Y,31399
162
162
  qubx/utils/time.py,sha256=J0ZFGjzFL5T6GA8RPAel8hKG0sg2LZXeQ5YfDCfcMHA,10055
163
163
  qubx/utils/version.py,sha256=e52fIHyxzCiIuH7svCF6pkHuDlqL64rklqz-2XjWons,5309
164
- qubx-0.6.50.dist-info/LICENSE,sha256=qwMHOSJ2TD0nx6VUJvFhu1ynJdBfNozRMt6tnSul-Ts,35140
165
- qubx-0.6.50.dist-info/METADATA,sha256=5367D5CT3MHInXQZV5XypmRuw6XZ74VrpSYB6gMr5DQ,4612
166
- qubx-0.6.50.dist-info/WHEEL,sha256=UckHTmFUCaLKpi4yFY8Dewu0c6XkY-KvEAGzGOnaWo8,110
167
- qubx-0.6.50.dist-info/entry_points.txt,sha256=VqilDTe8mVuV9SbR-yVlZJBTjbkHIL2JBgXfQw076HY,47
168
- qubx-0.6.50.dist-info/RECORD,,
164
+ qubx-0.6.52.dist-info/LICENSE,sha256=qwMHOSJ2TD0nx6VUJvFhu1ynJdBfNozRMt6tnSul-Ts,35140
165
+ qubx-0.6.52.dist-info/METADATA,sha256=Hor3x6zOv1rKp_mKkMdFc6HqE5bWIh14oHlX8ignRLk,4612
166
+ qubx-0.6.52.dist-info/WHEEL,sha256=UckHTmFUCaLKpi4yFY8Dewu0c6XkY-KvEAGzGOnaWo8,110
167
+ qubx-0.6.52.dist-info/entry_points.txt,sha256=VqilDTe8mVuV9SbR-yVlZJBTjbkHIL2JBgXfQw076HY,47
168
+ qubx-0.6.52.dist-info/RECORD,,
File without changes
File without changes