Qubx 0.2.64__tar.gz → 0.2.65__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 (56) hide show
  1. {qubx-0.2.64 → qubx-0.2.65}/PKG-INFO +1 -1
  2. {qubx-0.2.64 → qubx-0.2.65}/pyproject.toml +1 -1
  3. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/metrics.py +27 -1
  4. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/series.pxd +6 -6
  5. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/series.pyi +6 -1
  6. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/ta/indicators.pxd +1 -0
  7. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/ta/indicators.pyi +12 -0
  8. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/ta/indicators.pyx +9 -0
  9. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/charting/lookinglass.py +9 -4
  10. {qubx-0.2.64 → qubx-0.2.65}/README.md +0 -0
  11. {qubx-0.2.64 → qubx-0.2.65}/build.py +0 -0
  12. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/__init__.py +0 -0
  13. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/_nb_magic.py +0 -0
  14. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/backtester/__init__.py +0 -0
  15. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/backtester/ome.py +0 -0
  16. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/backtester/optimization.py +0 -0
  17. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/backtester/queue.py +0 -0
  18. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/backtester/simulator.py +0 -0
  19. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/__init__.py +0 -0
  20. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/account.py +0 -0
  21. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/basics.py +0 -0
  22. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/context.py +0 -0
  23. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/exceptions.py +0 -0
  24. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/helpers.py +0 -0
  25. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/loggers.py +0 -0
  26. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/lookups.py +0 -0
  27. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/series.pyx +0 -0
  28. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/strategy.py +0 -0
  29. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/utils.pyi +0 -0
  30. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/core/utils.pyx +0 -0
  31. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/data/helpers.py +0 -0
  32. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/data/readers.py +0 -0
  33. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/gathering/simplest.py +0 -0
  34. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/impl/ccxt_connector.py +0 -0
  35. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/impl/ccxt_customizations.py +0 -0
  36. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/impl/ccxt_trading.py +0 -0
  37. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/impl/ccxt_utils.py +0 -0
  38. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/math/__init__.py +0 -0
  39. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/math/stats.py +0 -0
  40. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/pandaz/__init__.py +0 -0
  41. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/pandaz/ta.py +0 -0
  42. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/pandaz/utils.py +0 -0
  43. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/ta/__init__.py +0 -0
  44. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/trackers/__init__.py +0 -0
  45. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/trackers/composite.py +0 -0
  46. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/trackers/rebalancers.py +0 -0
  47. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/trackers/riskctrl.py +0 -0
  48. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/trackers/sizers.py +0 -0
  49. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/__init__.py +0 -0
  50. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/_pyxreloader.py +0 -0
  51. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  52. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/marketdata/binance.py +0 -0
  53. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/misc.py +0 -0
  54. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/ntp.py +0 -0
  55. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/runner.py +0 -0
  56. {qubx-0.2.64 → qubx-0.2.65}/src/qubx/utils/time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Qubx
3
- Version: 0.2.64
3
+ Version: 0.2.65
4
4
  Summary: Qubx - quantitative trading framework
5
5
  Home-page: https://github.com/dmarienko/Qubx
6
6
  Author: Dmitry Marienko
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "Qubx"
3
- version = "0.2.64"
3
+ version = "0.2.65"
4
4
  description = "Qubx - quantitative trading framework"
5
5
  authors = ["Dmitry Marienko <dmitry@gmail.com>", "Yuriy Arabskyy <yuriy.arabskyy@gmail.com>"]
6
6
  readme = "README.md"
@@ -11,6 +11,8 @@ import re
11
11
  from scipy import stats
12
12
  from scipy.stats import norm
13
13
  from statsmodels.regression.linear_model import OLS
14
+ from copy import copy
15
+ from itertools import chain
14
16
 
15
17
  import plotly.graph_objects as go
16
18
 
@@ -918,10 +920,13 @@ def chart_signals(
918
920
  end = executions.index[-1]
919
921
 
920
922
  if portfolio is not None:
921
- pnl = portfolio.filter(regex=f"{symbol}_PnL").loc[start:].cumsum()
923
+ symbol_count = len(portfolio.filter(like="_PnL").columns)
924
+ pnl = portfolio.filter(regex=f"{symbol}_PnL").cumsum() + result.capital / symbol_count
925
+ pnl = pnl.loc[start:]
922
926
  if apply_commissions:
923
927
  comm = portfolio.filter(regex=f"{symbol}_Commissions").loc[start:].cumsum()
924
928
  pnl -= comm.values
929
+ pnl = (pnl / pnl.iloc[0] - 1) * 100
925
930
  indicators["PnL"] = ["area", "green", pnl]
926
931
  if show_quantity:
927
932
  pos = portfolio.filter(regex=f"{symbol}_Pos").loc[start:]
@@ -1015,3 +1020,24 @@ def get_symbol_pnls(
1015
1020
  pnls.append(s.portfolio_log.filter(like="_PnL").cumsum().iloc[-1])
1016
1021
 
1017
1022
  return pd.DataFrame(pnls, index=[s.name for s in session])
1023
+
1024
+
1025
+ def combine_sessions(sessions: list[TradingSessionResult], name: str = "Portfolio") -> TradingSessionResult:
1026
+ session = copy(sessions[0])
1027
+ session.name = name
1028
+ session.instruments = list(set(chain.from_iterable([e.instruments for e in sessions])))
1029
+ session.portfolio_log = pd.concat(
1030
+ [e.portfolio_log.loc[:, (e.portfolio_log != 0).any(axis=0)] for e in sessions], axis=1
1031
+ )
1032
+ # remove duplicated columns, keep first
1033
+ session.portfolio_log = session.portfolio_log.loc[:, ~session.portfolio_log.columns.duplicated()]
1034
+ session.executions_log = pd.concat([s.executions_log for s in sessions], axis=0).sort_index()
1035
+ session.signals_log = pd.concat([s.signals_log for s in sessions], axis=0).sort_index()
1036
+ # remove duplicated rows
1037
+ session.executions_log = (
1038
+ session.executions_log.set_index("instrument_id", append=True).drop_duplicates().reset_index("instrument_id")
1039
+ )
1040
+ session.signals_log = (
1041
+ session.signals_log.set_index("instrument_id", append=True).drop_duplicates().reset_index("instrument_id")
1042
+ )
1043
+ return session
@@ -36,11 +36,6 @@ cdef class Indicator(TimeSeries):
36
36
  cdef public TimeSeries series
37
37
  cdef public TimeSeries parent
38
38
 
39
-
40
- cdef class IndicatorOHLC(Indicator):
41
- pass
42
-
43
-
44
39
  cdef class RollingSum:
45
40
  """
46
41
  Rolling fast summator
@@ -49,7 +44,7 @@ cdef class RollingSum:
49
44
  cdef np.ndarray __s
50
45
  cdef unsigned int __i
51
46
  cdef double rsum
52
- cdef unsigned short is_init_stage
47
+ cdef public unsigned short is_init_stage
53
48
 
54
49
  cpdef double update(RollingSum self, double value, short new_item_started)
55
50
 
@@ -110,3 +105,8 @@ cdef class Quote:
110
105
  cdef public double ask_size
111
106
 
112
107
  cpdef double mid_price(Quote self)
108
+
109
+
110
+ cdef class IndicatorOHLC(Indicator):
111
+ pass
112
+
@@ -83,6 +83,11 @@ class Indicator(TimeSeries):
83
83
 
84
84
  def update(self, time: int, value, new_item_started: bool) -> object: ...
85
85
 
86
- class IndicatorOHLC(Indicator): ...
86
+ class IndicatorOHLC(Indicator):
87
+ series: OHLCV
87
88
 
88
89
  def time_as_nsec(time: Any) -> np.datetime64: ...
90
+
91
+ class RollingSum:
92
+ def __init__(self, period: int) -> None: ...
93
+ def update(self, value: float, new_item_started: bool) -> float: ...
@@ -134,5 +134,6 @@ cdef class Swings(IndicatorOHLC):
134
134
  # tops_detection_lag contain time lag when top was actually spotted
135
135
  cdef public TimeSeries tops, tops_detection_lag
136
136
  cdef public TimeSeries bottoms, bottoms_detection_lag
137
+ cdef public TimeSeries middles, deltas
137
138
 
138
139
  cpdef double calculate(self, long long time, Bar bar, short new_item_started)
@@ -15,8 +15,20 @@ def smooth(series: TimeSeries, smoother: str, *args, **kwargs) -> Indicator: ...
15
15
  def atr(series: OHLCV, period: int = 14, smoother="sma", percentage: bool = False): ...
16
16
  def swings(series: OHLCV, trend_indicator, **indicator_args): ...
17
17
 
18
+ class Sma(Indicator):
19
+ def __init__(self, name: str, series: TimeSeries, period: int): ...
20
+
21
+ class Ema(Indicator):
22
+ def __init__(self, name: str, series: TimeSeries, period: int, init_mean: bool = True): ...
23
+
18
24
  class Kama(Indicator):
19
25
  def __init__(self, name: str, series: TimeSeries, period: int, fast_span: int = 2, slow_span: int = 30): ...
20
26
 
21
27
  class Atr(IndicatorOHLC):
22
28
  def __init__(self, name: str, series: OHLCV, period: int, smoother: str, percentage: bool): ...
29
+
30
+ class Swings(IndicatorOHLC):
31
+ tops: TimeSeries
32
+ bottoms: TimeSeries
33
+ middles: TimeSeries
34
+ deltas: TimeSeries
@@ -612,6 +612,9 @@ cdef class Swings(IndicatorOHLC):
612
612
  self.bottoms = TimeSeries("bottoms", series.timeframe, series.max_series_length)
613
613
  self.bottoms_detection_lag = TimeSeries("bottoms_lag", series.timeframe, series.max_series_length)
614
614
 
615
+ self.middles = TimeSeries("middles", series.timeframe, series.max_series_length)
616
+ self.deltas = TimeSeries("deltas", series.timeframe, series.max_series_length)
617
+
615
618
  self._min_l = +np.inf
616
619
  self._max_h = -np.inf
617
620
  self._max_t = 0
@@ -630,6 +633,9 @@ cdef class Swings(IndicatorOHLC):
630
633
  if self._max_t > 0:
631
634
  self.tops.update(self._max_t, self._max_h)
632
635
  self.tops_detection_lag.update(self._max_t, time - self._max_t)
636
+ if len(self.bottoms) > 0:
637
+ self.middles.update(time, (self.tops[0] + self.bottoms[0]) / 2)
638
+ self.deltas.update(time, self.tops[0] - self.bottoms[0])
633
639
 
634
640
  if bar.low <= self._min_l:
635
641
  self._min_l = bar.low
@@ -642,6 +648,9 @@ cdef class Swings(IndicatorOHLC):
642
648
  if self._min_t > 0:
643
649
  self.bottoms.update(self._min_t, self._min_l)
644
650
  self.bottoms_detection_lag.update(self._min_t, time - self._min_t)
651
+ if len(self.tops) > 0:
652
+ self.middles.update(time, (self.tops[0] + self.bottoms[0]) / 2)
653
+ self.deltas.update(time, self.tops[0] - self.bottoms[0])
645
654
 
646
655
  if bar.high >= self._max_h:
647
656
  self._max_h = bar.high
@@ -4,6 +4,7 @@ import plotly.graph_objects as go
4
4
  from plotly.graph_objs.graph_objs import FigureWidget
5
5
  from plotly.subplots import make_subplots
6
6
 
7
+ from qubx.core.series import OHLCV, TimeSeries
7
8
  from qubx.utils.charting.mpl_helpers import ohlc_plot
8
9
  from qubx.utils.charting.mpl_helpers import plot_trends
9
10
  from qubx.utils.charting.mpl_helpers import subplot
@@ -110,7 +111,7 @@ def install_plotly_helpers():
110
111
  arrowcolor=c,
111
112
  )
112
113
 
113
- def custom_hover(v, h=600, n=2, legend=False, show_info=True):
114
+ def hover(v, h=600, n=2, legend=False, show_info=True):
114
115
  return (
115
116
  v.update_traces(xaxis="x1")
116
117
  .update_layout(
@@ -140,7 +141,7 @@ def install_plotly_helpers():
140
141
  )
141
142
  )
142
143
 
143
- FigureWidget.hover = custom_hover # type: ignore
144
+ FigureWidget.hover = hover # type: ignore
144
145
  FigureWidget.rline = rline # type: ignore
145
146
  FigureWidget.rlinex = rlinex # type: ignore
146
147
  FigureWidget.rline_ = rline_ # type: ignore
@@ -287,7 +288,8 @@ class LookingGlassMatplotLib(AbstractLookingGlass):
287
288
  else:
288
289
  _lbl = y.name if hasattr(y, "name") and y.name else ("%s_%d" % (study_name, k))
289
290
 
290
- if isinstance(y, pd.DataFrame):
291
+ if isinstance(y, (pd.DataFrame, OHLCV)):
292
+ y = y.pd() if isinstance(y, OHLCV) else y
291
293
 
292
294
  yy = y[zoom] if zoom else y
293
295
 
@@ -429,6 +431,7 @@ class LookingGlassMatplotLib(AbstractLookingGlass):
429
431
  for _col in yy.columns:
430
432
  self.__plot_as_type(yy[_col], plot_style, self._n_style, _col)
431
433
  else:
434
+ y = y.pd() if isinstance(y, TimeSeries) else y
432
435
  yy = y[zoom] if zoom else y
433
436
  self.__plot_as_type(yy, plot_style, self._n_style, _lbl)
434
437
 
@@ -561,7 +564,8 @@ class LookingGlassPlotly(AbstractLookingGlass):
561
564
  )
562
565
  self.fig.add_trace(go.Scatter(**_args), row=row, col=col)
563
566
 
564
- if isinstance(y, pd.DataFrame):
567
+ if isinstance(y, (pd.DataFrame, OHLCV)):
568
+ y = y.pd() if isinstance(y, OHLCV) else y
565
569
  yy = y[zoom] if zoom else y
566
570
 
567
571
  # candlesticks
@@ -812,6 +816,7 @@ class LookingGlassPlotly(AbstractLookingGlass):
812
816
  for _col in yy.columns:
813
817
  self.__plot_as_type(yy[_col], row, col, plot_style, _col)
814
818
  else:
819
+ y = y.pd() if isinstance(y, TimeSeries) else y
815
820
  yy = y[zoom] if zoom else y
816
821
  self.__plot_as_type(yy, row, col, plot_style, _lbl)
817
822
 
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