Qubx 0.6.62__cp312-cp312-manylinux_2_39_x86_64.whl → 0.6.63__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.

qubx/core/account.py CHANGED
@@ -112,10 +112,12 @@ class BasicAccountProcessor(IAccountProcessor):
112
112
  return {s: self.get_leverage(s) for s in self._positions.keys()}
113
113
 
114
114
  def get_net_leverage(self, exchange: str | None = None) -> float:
115
- return sum(self.get_leverages(exchange).values())
115
+ leverages = self.get_leverages(exchange).values()
116
+ return sum(lev for lev in leverages if lev is not None and not np.isnan(lev))
116
117
 
117
118
  def get_gross_leverage(self, exchange: str | None = None) -> float:
118
- return sum(map(abs, self.get_leverages(exchange).values()))
119
+ leverages = self.get_leverages(exchange).values()
120
+ return sum(abs(lev) for lev in leverages if lev is not None and not np.isnan(lev))
119
121
 
120
122
  ########################################################
121
123
  # Margin information
qubx/core/interfaces.py CHANGED
@@ -1935,6 +1935,26 @@ class IMetricEmitter:
1935
1935
  """
1936
1936
  pass
1937
1937
 
1938
+ def emit_signals(
1939
+ self,
1940
+ time: dt_64,
1941
+ signals: list[Signal],
1942
+ account: "IAccountViewer",
1943
+ target_positions: list["TargetPosition"] | None = None,
1944
+ ) -> None:
1945
+ """
1946
+ Emit signals to the monitoring system.
1947
+
1948
+ This method is called to emit trading signals for monitoring and analysis purposes.
1949
+
1950
+ Args:
1951
+ time: Timestamp when the signals were generated
1952
+ signals: List of signals to emit
1953
+ account: Account viewer to get account information like total capital, leverage, etc.
1954
+ target_positions: Optional list of target positions generated from the signals
1955
+ """
1956
+ pass
1957
+
1938
1958
 
1939
1959
  class IStrategyLifecycleNotifier:
1940
1960
  """Interface for notifying about strategy lifecycle events."""
@@ -334,13 +334,6 @@ class ProcessingManager(IProcessingManager):
334
334
  self._instruments_in_init_stage.remove(instr)
335
335
  logger.info(f"Switching tracker for <g>{instr}</g> back to defined position tracker")
336
336
 
337
- # - log all signals
338
- self._logging.save_signals(signals)
339
-
340
- # - export signals if exporter is specified
341
- if self._exporter is not None and signals:
342
- self._exporter.export_signals(self._time_provider.time(), signals, self._account)
343
-
344
337
  return _std_signals, _init_signals, _cancel_init_stage_instruments_tracker
345
338
 
346
339
  def __process_signals(self, signals: list[Signal]):
@@ -372,6 +365,19 @@ class ProcessingManager(IProcessingManager):
372
365
  self._context, self.__preprocess_and_log_target_positions(_targets_from_trackers)
373
366
  )
374
367
 
368
+ # - log all signals and export signals if exporter is specified after processing because trackers can modify the signals
369
+ self._logging.save_signals(signals)
370
+
371
+ # - export signals if exporter is specified
372
+ if self._exporter is not None and signals:
373
+ self._exporter.export_signals(self._time_provider.time(), signals, self._account)
374
+
375
+ # - emit signals to metric emitters if available
376
+ if self._context.emitter is not None and signals:
377
+ self._context.emitter.emit_signals(
378
+ self._time_provider.time(), signals, self._account, _targets_from_trackers
379
+ )
380
+
375
381
  def __invoke_on_fit(self) -> None:
376
382
  with self._health_monitor("ctx.on_fit"):
377
383
  try:
qubx/emitters/base.py CHANGED
@@ -9,8 +9,8 @@ from typing import Dict, List, Optional, Set
9
9
  import pandas as pd
10
10
 
11
11
  from qubx import logger
12
- from qubx.core.basics import Instrument, dt_64
13
- from qubx.core.interfaces import IMetricEmitter, IStrategyContext, ITimeProvider
12
+ from qubx.core.basics import Instrument, Signal, TargetPosition, dt_64
13
+ from qubx.core.interfaces import IAccountViewer, IMetricEmitter, IStrategyContext, ITimeProvider
14
14
 
15
15
 
16
16
  class BaseMetricEmitter(IMetricEmitter):
@@ -182,6 +182,27 @@ class BaseMetricEmitter(IMetricEmitter):
182
182
  except Exception as e:
183
183
  logger.error(f"[BaseMetricEmitter] Failed to emit strategy stats: {e}")
184
184
 
185
+ def emit_signals(
186
+ self,
187
+ time: dt_64,
188
+ signals: list["Signal"],
189
+ account: "IAccountViewer",
190
+ target_positions: list["TargetPosition"] | None = None,
191
+ ) -> None:
192
+ """
193
+ Emit signals to the monitoring system.
194
+
195
+ Base implementation does nothing - subclasses should override this method
196
+ to implement specific signal emission logic.
197
+
198
+ Args:
199
+ time: Timestamp when the signals were generated
200
+ signals: List of signals to emit
201
+ account: Account viewer to get account information like total capital, leverage, etc.
202
+ target_positions: Optional list of target positions generated from the signals
203
+ """
204
+ pass
205
+
185
206
  def notify(self, context: IStrategyContext) -> None:
186
207
  """
187
208
  Notify the metric emitter of a time update.
@@ -7,8 +7,8 @@ This module provides a composite implementation of IMetricEmitter that delegates
7
7
  from typing import Dict, List, Optional
8
8
 
9
9
  from qubx import logger
10
- from qubx.core.basics import dt_64
11
- from qubx.core.interfaces import IMetricEmitter, IStrategyContext
10
+ from qubx.core.basics import Signal, dt_64
11
+ from qubx.core.interfaces import IAccountViewer, IMetricEmitter, IStrategyContext
12
12
  from qubx.emitters.base import BaseMetricEmitter
13
13
 
14
14
 
@@ -70,6 +70,21 @@ class CompositeMetricEmitter(BaseMetricEmitter):
70
70
  except Exception as e:
71
71
  logger.error(f"Error emitting strategy stats to {emitter.__class__.__name__}: {e}")
72
72
 
73
+ def emit_signals(self, time: dt_64, signals: list["Signal"], account: "IAccountViewer") -> None:
74
+ """
75
+ Emit signals to all configured emitters.
76
+
77
+ Args:
78
+ time: Timestamp when the signals were generated
79
+ signals: List of signals to emit
80
+ account: Account viewer to get account information
81
+ """
82
+ for emitter in self._emitters:
83
+ try:
84
+ emitter.emit_signals(time, signals, account)
85
+ except Exception as e:
86
+ logger.error(f"Error emitting signals to {emitter.__class__.__name__}: {e}")
87
+
73
88
  def notify(self, context: IStrategyContext) -> None:
74
89
  for emitter in self._emitters:
75
90
  try:
qubx/emitters/csv.py CHANGED
@@ -8,7 +8,8 @@ import os
8
8
  from pathlib import Path
9
9
 
10
10
  from qubx import logger
11
- from qubx.core.basics import dt_64
11
+ from qubx.core.basics import Signal, dt_64
12
+ from qubx.core.interfaces import IAccountViewer
12
13
  from qubx.emitters.base import BaseMetricEmitter
13
14
  from qubx.utils.ntp import time_now
14
15
 
@@ -81,3 +82,44 @@ class CSVMetricEmitter(BaseMetricEmitter):
81
82
  f.write(f"{str(current_timestamp)},{name},{value},{tags_str}\n")
82
83
  except Exception as e:
83
84
  logger.error(f"[CSVMetricEmitter] Failed to emit metric {name}: {e}")
85
+
86
+ def emit_signals(self, time: dt_64, signals: list[Signal], account: IAccountViewer) -> None:
87
+ """
88
+ Emit signals to CSV file.
89
+
90
+ Args:
91
+ time: Timestamp when the signals were generated
92
+ signals: List of signals to emit
93
+ account: Account viewer to get account information
94
+ """
95
+ if not signals:
96
+ return
97
+
98
+ try:
99
+ # Create a signals-specific CSV file
100
+ signals_file_path = self._file_path.parent / f"signals_{self._file_path.stem}.csv"
101
+
102
+ # Check if file exists, if not create with headers
103
+ if not signals_file_path.exists():
104
+ with open(signals_file_path, "w") as f:
105
+ f.write(
106
+ "timestamp,symbol,exchange,signal,price,stop,take,reference_price,group,comment,is_service\n"
107
+ )
108
+
109
+ # Write each signal to the CSV file
110
+ for signal in signals:
111
+ signal_time = str(signal.time) if hasattr(signal.time, "__str__") else str(time)
112
+ price = signal.price if signal.price is not None else ""
113
+ stop = signal.stop if signal.stop is not None else ""
114
+ take = signal.take if signal.take is not None else ""
115
+ ref_price = signal.reference_price if signal.reference_price is not None else ""
116
+
117
+ with open(signals_file_path, "a") as f:
118
+ f.write(
119
+ f"{signal_time},{signal.instrument.symbol},{signal.instrument.exchange},"
120
+ f"{signal.signal},{price},{stop},{take},{ref_price},"
121
+ f"{signal.group},{signal.comment},{signal.is_service}\n"
122
+ )
123
+
124
+ except Exception as e:
125
+ logger.error(f"[CSVMetricEmitter] Failed to emit signals: {e}")
@@ -9,8 +9,8 @@ from typing import Dict, List, Literal, Optional
9
9
  from prometheus_client import REGISTRY, Counter, Gauge, Summary, push_to_gateway
10
10
 
11
11
  from qubx import logger
12
- from qubx.core.basics import dt_64
13
- from qubx.core.interfaces import IStrategyContext
12
+ from qubx.core.basics import Signal, dt_64
13
+ from qubx.core.interfaces import IAccountViewer, IStrategyContext
14
14
  from qubx.emitters.base import BaseMetricEmitter
15
15
 
16
16
  # Define metric types
@@ -220,3 +220,58 @@ class PrometheusMetricEmitter(BaseMetricEmitter):
220
220
  )
221
221
  except Exception as e:
222
222
  logger.error(f"[PrometheusMetricEmitter] Failed to push metrics to gateway: {e}")
223
+
224
+ def emit_signals(self, time: dt_64, signals: list[Signal], account: IAccountViewer) -> None:
225
+ """
226
+ Emit signals as Prometheus metrics.
227
+
228
+ Args:
229
+ time: Timestamp when the signals were generated
230
+ signals: List of signals to emit
231
+ account: Account viewer to get account information
232
+ """
233
+ if not signals:
234
+ return
235
+
236
+ try:
237
+ for signal in signals:
238
+ # Create labels for the signal
239
+ labels = {
240
+ "symbol": signal.instrument.symbol,
241
+ "exchange": signal.instrument.exchange,
242
+ "group": signal.group if signal.group else "default",
243
+ "is_service": str(signal.is_service).lower(),
244
+ }
245
+
246
+ # Emit the signal value as a gauge
247
+ gauge = self._get_or_create_gauge("signal_value", labels)
248
+ gauge.labels(**labels).set(signal.signal)
249
+
250
+ # Emit price-related metrics if available
251
+ if signal.price is not None:
252
+ price_gauge = self._get_or_create_gauge("signal_price", labels)
253
+ price_gauge.labels(**labels).set(signal.price)
254
+
255
+ if signal.stop is not None:
256
+ stop_gauge = self._get_or_create_gauge("signal_stop", labels)
257
+ stop_gauge.labels(**labels).set(signal.stop)
258
+
259
+ if signal.take is not None:
260
+ take_gauge = self._get_or_create_gauge("signal_take", labels)
261
+ take_gauge.labels(**labels).set(signal.take)
262
+
263
+ if signal.reference_price is not None:
264
+ ref_price_gauge = self._get_or_create_gauge("signal_reference_price", labels)
265
+ ref_price_gauge.labels(**labels).set(signal.reference_price)
266
+
267
+ # Push to gateway if configured
268
+ if self._pushgateway_url:
269
+ try:
270
+ push_to_gateway(
271
+ self._pushgateway_url, job=f"{self._namespace}_{self._strategy_name}", registry=self._registry
272
+ )
273
+ except Exception as e:
274
+ logger.error(f"[PrometheusMetricEmitter] Failed to push signal metrics to gateway: {e}")
275
+
276
+ except Exception as e:
277
+ logger.error(f"[PrometheusMetricEmitter] Failed to emit signals: {e}")
qubx/emitters/questdb.py CHANGED
@@ -11,9 +11,10 @@ import pandas as pd
11
11
  from questdb.ingress import Sender
12
12
 
13
13
  from qubx import logger
14
- from qubx.core.basics import dt_64
15
- from qubx.core.interfaces import IStrategyContext
14
+ from qubx.core.basics import Signal, TargetPosition, dt_64
15
+ from qubx.core.interfaces import IAccountViewer, IStrategyContext
16
16
  from qubx.emitters.base import BaseMetricEmitter
17
+ from qubx.utils.questdb import QuestDBClient
17
18
 
18
19
 
19
20
  class QuestDBMetricEmitter(BaseMetricEmitter):
@@ -28,6 +29,7 @@ class QuestDBMetricEmitter(BaseMetricEmitter):
28
29
  host: str = "localhost",
29
30
  port: int = 9000,
30
31
  table_name: str = "qubx_metrics",
32
+ signals_table_name: str = "qubx_signals",
31
33
  stats_to_emit: list[str] | None = None,
32
34
  stats_interval: str = "1m",
33
35
  flush_interval: str = "5s",
@@ -41,6 +43,7 @@ class QuestDBMetricEmitter(BaseMetricEmitter):
41
43
  host: QuestDB server host
42
44
  port: QuestDB server port
43
45
  table_name: Name of the table to store metrics in
46
+ signals_table_name: Name of the table to store signals in
44
47
  stats_to_emit: Optional list of specific stats to emit
45
48
  stats_interval: Interval for emitting strategy stats (default: "1m")
46
49
  tags: Dictionary of default tags/labels to include with all metrics
@@ -54,12 +57,16 @@ class QuestDBMetricEmitter(BaseMetricEmitter):
54
57
  self._host = host
55
58
  self._port = port
56
59
  self._table_name = table_name
60
+ self._signals_table_name = signals_table_name
57
61
  self._conn_str = f"http::addr={host}:{port};"
58
62
  self._flush_interval = pd.Timedelta(flush_interval)
59
63
  self._sender = self._try_get_sender()
60
64
  self._last_flush = None
61
65
  self._executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="questdb_emitter")
62
66
 
67
+ # Create signals table if it doesn't exist
68
+ self._ensure_signals_table_exists()
69
+
63
70
  def notify(self, context: IStrategyContext) -> None:
64
71
  super().notify(context)
65
72
 
@@ -158,3 +165,125 @@ class QuestDBMetricEmitter(BaseMetricEmitter):
158
165
  logger.error(f"[QuestDBMetricEmitter] Failed to connect to QuestDB: {e}")
159
166
  _sender = None
160
167
  return _sender
168
+
169
+ def _ensure_signals_table_exists(self) -> None:
170
+ """Ensure the signals table exists with the correct schema."""
171
+ try:
172
+ # Use the PostgreSQL interface (port 8812) for DDL operations
173
+ client = QuestDBClient(host=self._host, port=8812)
174
+
175
+ create_table_sql = f"""
176
+ CREATE TABLE IF NOT EXISTS {self._signals_table_name} (
177
+ timestamp TIMESTAMP,
178
+ symbol SYMBOL,
179
+ exchange SYMBOL,
180
+ signal DOUBLE,
181
+ price DOUBLE,
182
+ stop DOUBLE,
183
+ take DOUBLE,
184
+ reference_price DOUBLE,
185
+ target_leverage DOUBLE,
186
+ group_name SYMBOL,
187
+ comment STRING,
188
+ is_service BOOLEAN
189
+ ) TIMESTAMP(timestamp) PARTITION BY DAY;
190
+ """
191
+
192
+ client.execute(create_table_sql)
193
+ logger.info(f"[QuestDBMetricEmitter] Ensured signals table '{self._signals_table_name}' exists")
194
+ except Exception as e:
195
+ logger.error(f"[QuestDBMetricEmitter] Failed to create signals table: {e}")
196
+
197
+ def emit_signals(
198
+ self,
199
+ time: dt_64,
200
+ signals: list[Signal],
201
+ account: IAccountViewer,
202
+ target_positions: list[TargetPosition] | None = None,
203
+ ) -> None:
204
+ """
205
+ Emit signals to QuestDB.
206
+
207
+ Args:
208
+ time: Timestamp when the signals were generated
209
+ signals: List of signals to emit
210
+ account: Account viewer to get account information
211
+ target_positions: Optional list of target positions generated from the signals
212
+ """
213
+ if not signals or self._sender is None:
214
+ return
215
+
216
+ try:
217
+ # Submit the signals emission to the thread pool
218
+ self._executor.submit(self._emit_signals_to_questdb, time, signals, account, target_positions)
219
+ except Exception as e:
220
+ logger.error(f"[QuestDBMetricEmitter] Failed to queue signals emission: {e}")
221
+
222
+ def _emit_signals_to_questdb(
223
+ self,
224
+ time: dt_64,
225
+ signals: list[Signal],
226
+ account: IAccountViewer,
227
+ target_positions: list[TargetPosition] | None = None,
228
+ ) -> None:
229
+ """
230
+ Send signals to QuestDB in a background thread.
231
+
232
+ Args:
233
+ time: Timestamp when the signals were generated
234
+ signals: List of signals to emit
235
+ account: Account viewer to get account information
236
+ target_positions: Optional list of target positions generated from the signals
237
+ """
238
+ if self._sender is None:
239
+ return
240
+
241
+ try:
242
+ # Get total capital for leverage calculations
243
+ total_capital = account.get_total_capital()
244
+
245
+ # Create a mapping of instruments to target positions for easier lookup
246
+ target_positions_map = {}
247
+
248
+ if target_positions:
249
+ for target in target_positions:
250
+ target_positions_map[target.instrument] = target
251
+
252
+ for signal in signals:
253
+ # Get target leverage for this instrument if available
254
+ target_leverage = None
255
+ if signal.instrument in target_positions_map:
256
+ target = target_positions_map[signal.instrument]
257
+ # Use signal.reference_price for notional value calculation
258
+ if signal.reference_price is not None and total_capital > 0:
259
+ notional_value = abs(target.target_position_size * signal.reference_price)
260
+ target_leverage = (notional_value / total_capital) * 100
261
+
262
+ # Use _merge_tags to get properly merged tags
263
+ merged_tags = self._merge_tags({}, signal.instrument)
264
+
265
+ symbols = {
266
+ "group_name": signal.group if signal.group else "",
267
+ }
268
+ symbols.update(merged_tags) # Add merged tags
269
+
270
+ columns = {
271
+ "signal": float(signal.signal),
272
+ "price": float(signal.price) if signal.price is not None else None,
273
+ "stop": float(signal.stop) if signal.stop is not None else None,
274
+ "take": float(signal.take) if signal.take is not None else None,
275
+ "reference_price": float(signal.reference_price) if signal.reference_price is not None else None,
276
+ "target_leverage": float(target_leverage) if target_leverage is not None else None,
277
+ "comment": signal.comment if signal.comment else "",
278
+ # "options": json.dumps(signal.options) if signal.options else "{}",
279
+ "is_service": bool(signal.is_service),
280
+ }
281
+
282
+ # Convert timestamp - signal.time is always dt_64, no need to check for string
283
+ dt_timestamp = self._convert_timestamp(time)
284
+
285
+ # Send the row to QuestDB
286
+ self._sender.row(self._signals_table_name, symbols=symbols, columns=columns, at=dt_timestamp)
287
+
288
+ except Exception as e:
289
+ logger.error(f"[QuestDBMetricEmitter] Failed to emit signals to QuestDB: {e}")
qubx/pandaz/ta.py CHANGED
@@ -1611,28 +1611,18 @@ def choppiness(
1611
1611
  match identification:
1612
1612
  case "mid":
1613
1613
  f0[(ci > lower) & (ci < upper)] = 0
1614
- f0[(ci > upper) & (ci.shift(1) <= upper)] = 1
1615
- f0[(ci < lower) & (ci.shift(1) >= lower)] = -1
1614
+ f0[ci > upper] = 1
1615
+ f0[ci < lower] = -1
1616
1616
  case "strong":
1617
- f0[(ci > lower) & (ci.shift(1) <= lower)] = 1
1618
- f0[(ci < lower) & (ci.shift(1) >= lower)] = 0
1617
+ f0[ci > lower] = 1
1618
+ f0[ci < lower] = 0
1619
1619
  case "weak":
1620
- f0[(ci > upper) & (ci.shift(1) <= upper)] = 1
1621
- f0[(ci < lower) & (ci.shift(1) >= lower)] = 0
1620
+ f0[ci > upper] = 1
1621
+ f0[ci < lower] = 0
1622
1622
  case _:
1623
1623
  raise ValueError(f"Invalid identification: {identification}")
1624
1624
 
1625
- # f0 = pd.Series(np.nan, ci.index, dtype=bool)
1626
- # f0[ci >= upper] = True
1627
- # f0[ci <= lower] = False
1628
- # return f0.ffill().fillna(False)
1629
-
1630
- # f0 = pd.Series(np.nan, ci.index, dtype=int)
1631
- # f0[(ci > upper) & (ci.shift(1) <= upper)] = +1
1632
- # f0[(ci < lower) & (ci.shift(1) >= lower)] = 0
1633
- # return f0.ffill().fillna(0)
1634
-
1635
- return f0.ffill().fillna(0) if not with_raw_indicator else ci
1625
+ return f0 if not with_raw_indicator else ci
1636
1626
 
1637
1627
 
1638
1628
  @njit
qubx/trackers/riskctrl.py CHANGED
@@ -84,15 +84,14 @@ class RiskController(PositionsTracker):
84
84
  )
85
85
  continue
86
86
 
87
- # - calculate risk, here we need to use copy of signa to prevent modifications of original signal
88
- s_copy = s.copy()
89
- signal_with_risk = self._risk_calculator.calculate_risks(ctx, quote, s_copy)
87
+ # - calculate risk, we allow modifications of the original signal
88
+ signal_with_risk = self._risk_calculator.calculate_risks(ctx, quote, s)
90
89
  if signal_with_risk is None:
91
90
  continue
92
91
 
93
92
  # - final step - calculate actual target position and check if tracker can approve it
94
93
  target = self.get_position_sizer().calculate_target_positions(ctx, [signal_with_risk])[0]
95
- if self.handle_new_target(ctx, s_copy, target):
94
+ if self.handle_new_target(ctx, s, target):
96
95
  targets.append(target)
97
96
 
98
97
  return targets
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Qubx
3
- Version: 0.6.62
3
+ Version: 0.6.63
4
4
  Summary: Qubx - Quantitative Trading Framework
5
5
  Author: Dmitry Marienko
6
6
  Author-email: dmitry.marienko@xlydian.com
@@ -35,7 +35,7 @@ qubx/connectors/ccxt/utils.py,sha256=7BRmVT3hbGFbhRpJXTJ3lYX7R6VXi5aPxNYj1EjxObY
35
35
  qubx/connectors/tardis/data.py,sha256=JYkU9PTxIL4m0L2bLROzKtiiJSRbk48GDR4wnrL9ENM,30945
36
36
  qubx/connectors/tardis/utils.py,sha256=epThu9DwqbDb7BgScH6fHa_FVpKUaItOqp3JwtKGc5g,9092
37
37
  qubx/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- qubx/core/account.py,sha256=4_XskMLR9Uh-rpJBDYMrceYiGMvAZw56k1ve-unIW8w,19417
38
+ qubx/core/account.py,sha256=jUiHczTSz8nEOv2D6rmDqesT-4okjJF0XnzhkXepmpI,19584
39
39
  qubx/core/basics.py,sha256=v86Men5OKIve93gSfqRD-6bjI0NUwt7gE1flm9WMul0,35904
40
40
  qubx/core/context.py,sha256=h2KeQnVe514VDSehjEIdl2Pv8FKjYaCOhpvJZPFFON8,24107
41
41
  qubx/core/deque.py,sha256=3PsmJ5LF76JpsK4Wp5LLogyE15rKn6EDCkNOOWT6EOk,6203
@@ -43,21 +43,21 @@ qubx/core/errors.py,sha256=LENtlgmVzxxUFNCsuy4PwyHYhkZkxuZQ2BPif8jaGmw,1411
43
43
  qubx/core/exceptions.py,sha256=11wQC3nnNLsl80zBqbE6xiKCqm31kctqo6W_gdnZkg8,581
44
44
  qubx/core/helpers.py,sha256=nZMe3HVheSkqczZq7-foPHfJ-49HJL69mBirBxynOBQ,20022
45
45
  qubx/core/initializer.py,sha256=YgTBs5LpIk6ZFdmMD8zCnJnVNcMh1oeYvt157jhwyMg,4242
46
- qubx/core/interfaces.py,sha256=OUv56aUNpaBn6zUgHr1k642ZwGgr_o0xb0_Uu3qmrxk,61380
46
+ qubx/core/interfaces.py,sha256=gTC1B7jtYCP_mRVNFu9Bkp3ZCInRfOUX3bYITYYIwi0,62069
47
47
  qubx/core/loggers.py,sha256=wO6UdFasWu5bNeDkN7eRVDhHUQ2Rj3Apkzk9h2q71Rk,14128
48
48
  qubx/core/lookups.py,sha256=2UmODxDeDQqi-xHOvjm2_GckC4piKI3x4ZEf5vny8YI,18275
49
49
  qubx/core/metrics.py,sha256=SLbuG66Y3sH86tDHcdoIHovkhM-oSF5o840BRChrvCE,60849
50
50
  qubx/core/mixins/__init__.py,sha256=AMCLvfNuIb1kkQl3bhCj9jIOEl2eKcVPJeyLgrkB-rk,329
51
51
  qubx/core/mixins/market.py,sha256=f0PJTK5Y9nQkV2XWxytixFG1Oc6ihYVwlxIMraTnmms,4566
52
- qubx/core/mixins/processing.py,sha256=xFM6HkcucbtwQWKTmTVhxjdwVgKYcInI6fr3hnoDSRo,35252
52
+ qubx/core/mixins/processing.py,sha256=eiZcr53LckDpU_BlHgqxSXmzirnijD568HyYgNBB9CM,35622
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
55
  qubx/core/mixins/universe.py,sha256=mzZJA7Me6HNFbAMGg1XOpnYCMtcFKHESTiozjaXyKXY,10100
56
- qubx/core/series.cpython-312-x86_64-linux-gnu.so,sha256=DRIaH5DjiJNU7ZiH68fQE-QqM0e6ujx17bf_Uyl4WZU,982408
56
+ qubx/core/series.cpython-312-x86_64-linux-gnu.so,sha256=QaSbT8C0EiwfaqRY-46popWSYJC3oHPRcQRvX9_Piu8,982408
57
57
  qubx/core/series.pxd,sha256=aI5PG1hbr827xwcnSYgGMF2IBD4GvCRby_i9lrGrJdQ,4026
58
58
  qubx/core/series.pyi,sha256=wRb1_HpZC7KSTyWMi7K0BisaKSEro3LVebr8z5XzoDs,4668
59
59
  qubx/core/series.pyx,sha256=TpKMCqRrzrT0cj1pASlB7mLLgMLQZwDNC5o-QOxqhRQ,46936
60
- qubx/core/utils.cpython-312-x86_64-linux-gnu.so,sha256=dP51I7PhfJPwlZzOQ7zjEG1Ppm3iJGGPftylTrBfDAM,86568
60
+ qubx/core/utils.cpython-312-x86_64-linux-gnu.so,sha256=i6CRJdf197d3vp8MkaFWo5JIWEFCI_nw0m_ABv76muE,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
@@ -68,12 +68,12 @@ qubx/data/readers.py,sha256=g3hSkyKdMAVziMCgcaZadsukidECaLwHyIEtArSVDSc,66203
68
68
  qubx/data/registry.py,sha256=45mjy5maBSO6cf-0zfIRRDs8b0VDW7wHSPn43aRjv-o,3883
69
69
  qubx/data/tardis.py,sha256=O-zglpusmO6vCY3arSOgH6KUbkfPajSAIQfMKlVmh_E,33878
70
70
  qubx/emitters/__init__.py,sha256=11sYi8CHdPlQ5NLR84C2vUnMDkAqNIAmKo_SfqCMj2c,705
71
- qubx/emitters/base.py,sha256=RPjOib02h4l8ndZ4_Q7_BGTPA2YvITLUWpWlSrg2b6A,7984
72
- qubx/emitters/composite.py,sha256=8DsPIUtaJ95Oww9QTVVB6LR7Wcb6TJ-c1jIHMGuttz4,2784
73
- qubx/emitters/csv.py,sha256=lWl6sP0ke0j6kVlEbQsy11vSOHFudYHjWS9iPbq6kmo,3067
71
+ qubx/emitters/base.py,sha256=71u0OKyLU9SN6yybRTC3rRorVPhRWy6_ExxQ7_jT2Cw,8758
72
+ qubx/emitters/composite.py,sha256=JkFch4Tp5q6CaLU2nAmeZnRiVPGkFhGNvzhT255yJfI,3411
73
+ qubx/emitters/csv.py,sha256=S-oQ84rCgP-bb2_q-FWcegACg_Ej_Ik3tXE6aJBlqOk,4963
74
74
  qubx/emitters/indicator.py,sha256=8opNUrkoLLcoTLISCMb7e5mlVZgdH5HduyrBxkA6sg4,7983
75
- qubx/emitters/prometheus.py,sha256=g2hgcV_G77fWVEXtoGJTUs4JLkB2FQXFzVY_x_sEBfc,8100
76
- qubx/emitters/questdb.py,sha256=vGi5r6JbKwwi8SpdXj_oG1FYrI-aEYLHYb633UAwNBk,5962
75
+ qubx/emitters/prometheus.py,sha256=lZJ_Hl-AlkeWJmktxhAiEMiTIc8dTQvBpf3Ih5Fy6pE,10516
76
+ qubx/emitters/questdb.py,sha256=Zc4Vbr9g7Pw9oRSEnPnA1ypDv6nRwUEYAOrT-FFYFos,11526
77
77
  qubx/exporters/__init__.py,sha256=7HeYHCZfKAaBVAByx9wE8DyGv6C55oeED9uUphcyjuc,360
78
78
  qubx/exporters/composite.py,sha256=c45XcMC0dsIDwOyOxxCuiyYQjUNhqPjptAulbaSqttU,2973
79
79
  qubx/exporters/formatters/__init__.py,sha256=La9rMsl3wyplza0xVyAFrUwhFyrGDIMJWmOB_boJyIg,488
@@ -103,7 +103,7 @@ qubx/notifications/composite.py,sha256=fa-rvHEn6k-Fma5N7cT-7Sk7hzVyB0KDs2ktDyoyL
103
103
  qubx/notifications/slack.py,sha256=RWsLyL4lm6tbmrTlXQo3nPlfiLVJ0vCfY5toJ9G8RWU,8316
104
104
  qubx/notifications/throttler.py,sha256=8jnymPQbrgtN1rD7REQa2sA9teSWTqkk_uT9oaknOyc,5618
105
105
  qubx/pandaz/__init__.py,sha256=6BYz6gSgxjNa7WP1XqWflYG7WIq1ppSD9h1XGR5M5YQ,682
106
- qubx/pandaz/ta.py,sha256=sIX9YxxB2S2nWU4vnS4rXFuEI5WSY76Ky1TFwf9RhMw,92154
106
+ qubx/pandaz/ta.py,sha256=lTOE5TX9Qnl5kblCUelojJuC27B4wVt0MIquK3eO4DE,91635
107
107
  qubx/pandaz/utils.py,sha256=rg28KfcbWw4NelaI196OiAq0VM6DsszO5nrGSDPQFWk,23972
108
108
  qubx/resources/_build.py,sha256=XE7XNuDqfXPc2OriLobKXmPMvwa7Z8AKAD-18fnf0e4,8802
109
109
  qubx/resources/crypto-fees.ini,sha256=USvMoAyxqV9xeR223epFZqFs2PJt-k1pw3Rs0DXMQCg,1933
@@ -130,7 +130,7 @@ qubx/restorers/signal.py,sha256=7n7eeRhWGUBPbg179GxFH_ifywcl3pQJbwrcDklw0N0,1460
130
130
  qubx/restorers/state.py,sha256=I1VIN0ZcOjigc3WMHIYTNJeAAbN9YB21MDcMl04ZWmY,8018
131
131
  qubx/restorers/utils.py,sha256=We2gfqwQKWziUYhuUnjb-xo-5tSlbuHWpPQn0CEMTn0,1155
132
132
  qubx/ta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
133
- qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so,sha256=Qmvnny0Tq_XxjtK7eqJgizdxbZGFaefIqrkLvJpT3UA,662632
133
+ qubx/ta/indicators.cpython-312-x86_64-linux-gnu.so,sha256=vd_AR5Ab7Xs2_7V1Iilq45Bfciq-M_JcEnlmyy0IxaQ,662632
134
134
  qubx/ta/indicators.pxd,sha256=Goo0_N0Xnju8XGo3Xs-3pyg2qr_0Nh5C-_26DK8U_IE,4224
135
135
  qubx/ta/indicators.pyi,sha256=19W0uERft49In5bf9jkJHkzJYEyE9gzudN7_DJ5Vdv8,1963
136
136
  qubx/ta/indicators.pyx,sha256=Xgpew46ZxSXsdfSEWYn3A0Q35MLsopB9n7iyCsXTufs,25969
@@ -138,7 +138,7 @@ qubx/trackers/__init__.py,sha256=ThIP1jXaACse5hG3lZqQSlWSKYl6APxFmBHaRcVpPdU,100
138
138
  qubx/trackers/advanced.py,sha256=CONogr5hHHEwfolkegjpEz7NNk4Ruf9pOq5nKifNXVM,12761
139
139
  qubx/trackers/composite.py,sha256=Tjupx78SraXmRKkWhu8n81RkPjOgsDbXLd8yz6PhbaA,6318
140
140
  qubx/trackers/rebalancers.py,sha256=KFY7xuD4fGALiSLMas8MZ3ueRzlWt5wDT9329dlmNng,5150
141
- qubx/trackers/riskctrl.py,sha256=IuxccTgdEnN9lLMoYU8HURqPBqA3Wrk6NiOgL_O6rTM,35094
141
+ qubx/trackers/riskctrl.py,sha256=O6UTk4nK7u5YaT_Sd4aSFBR3dWaxOLLzOzMoeU71hDY,35022
142
142
  qubx/trackers/sizers.py,sha256=OEK-IOuXiXXx8MkZiEsni5zPWFc3kun9AqApY0mIPTY,9527
143
143
  qubx/utils/__init__.py,sha256=FEPBtU3dhfLawBkAfm9FEUW4RuOY7pGCBfzDCtKjn9A,481
144
144
  qubx/utils/_pyxreloader.py,sha256=34kNd8kQi2ey_ZrGdVVUHbPrO1PEiHZDLEDBscIkT_s,12292
@@ -167,8 +167,8 @@ qubx/utils/runner/factory.py,sha256=eM4-Etcq-FewD2AjH_srFGzP413pm8er95KIZixXRpM,
167
167
  qubx/utils/runner/runner.py,sha256=m58a3kEwSs1xfgg_s9FwrQJ3AZV4Lf_VOmKDPQdaWH8,31518
168
168
  qubx/utils/time.py,sha256=J0ZFGjzFL5T6GA8RPAel8hKG0sg2LZXeQ5YfDCfcMHA,10055
169
169
  qubx/utils/version.py,sha256=e52fIHyxzCiIuH7svCF6pkHuDlqL64rklqz-2XjWons,5309
170
- qubx-0.6.62.dist-info/LICENSE,sha256=qwMHOSJ2TD0nx6VUJvFhu1ynJdBfNozRMt6tnSul-Ts,35140
171
- qubx-0.6.62.dist-info/METADATA,sha256=bLqkQBXqzXkkcYiAJAc2B4BYKmyBJrbBB6bXxMgoRG8,4612
172
- qubx-0.6.62.dist-info/WHEEL,sha256=UckHTmFUCaLKpi4yFY8Dewu0c6XkY-KvEAGzGOnaWo8,110
173
- qubx-0.6.62.dist-info/entry_points.txt,sha256=VqilDTe8mVuV9SbR-yVlZJBTjbkHIL2JBgXfQw076HY,47
174
- qubx-0.6.62.dist-info/RECORD,,
170
+ qubx-0.6.63.dist-info/LICENSE,sha256=qwMHOSJ2TD0nx6VUJvFhu1ynJdBfNozRMt6tnSul-Ts,35140
171
+ qubx-0.6.63.dist-info/METADATA,sha256=RFICQLYzaB3Vk6jlGV2tCMXt71SAz4t1eVPqsmoCL3g,4612
172
+ qubx-0.6.63.dist-info/WHEEL,sha256=UckHTmFUCaLKpi4yFY8Dewu0c6XkY-KvEAGzGOnaWo8,110
173
+ qubx-0.6.63.dist-info/entry_points.txt,sha256=VqilDTe8mVuV9SbR-yVlZJBTjbkHIL2JBgXfQw076HY,47
174
+ qubx-0.6.63.dist-info/RECORD,,
File without changes
File without changes