Qubx 0.2.74__tar.gz → 0.2.76__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 (58) hide show
  1. {qubx-0.2.74 → qubx-0.2.76}/PKG-INFO +1 -1
  2. {qubx-0.2.74 → qubx-0.2.76}/pyproject.toml +1 -1
  3. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/__init__.py +4 -0
  4. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/_nb_magic.py +40 -4
  5. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/helpers.py +1 -1
  6. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/lookups.py +2 -2
  7. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/metrics.py +29 -11
  8. qubx-0.2.76/src/qubx/data/__init__.py +11 -0
  9. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/data/readers.py +19 -2
  10. qubx-0.2.76/src/qubx/pandaz/__init__.py +15 -0
  11. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/pandaz/utils.py +4 -5
  12. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/utils/__init__.py +1 -1
  13. qubx-0.2.74/src/qubx/pandaz/__init__.py +0 -4
  14. {qubx-0.2.74 → qubx-0.2.76}/README.md +0 -0
  15. {qubx-0.2.74 → qubx-0.2.76}/build.py +0 -0
  16. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/backtester/__init__.py +0 -0
  17. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/backtester/ome.py +0 -0
  18. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/backtester/optimization.py +0 -0
  19. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/backtester/queue.py +0 -0
  20. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/backtester/simulator.py +0 -0
  21. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/__init__.py +0 -0
  22. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/account.py +0 -0
  23. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/basics.py +0 -0
  24. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/context.py +0 -0
  25. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/exceptions.py +0 -0
  26. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/loggers.py +0 -0
  27. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/series.pxd +0 -0
  28. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/series.pyi +0 -0
  29. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/series.pyx +0 -0
  30. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/strategy.py +0 -0
  31. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/utils.pyi +0 -0
  32. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/core/utils.pyx +0 -0
  33. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/data/helpers.py +0 -0
  34. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/gathering/simplest.py +0 -0
  35. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/impl/ccxt_connector.py +0 -0
  36. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/impl/ccxt_customizations.py +0 -0
  37. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/impl/ccxt_trading.py +0 -0
  38. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/impl/ccxt_utils.py +0 -0
  39. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/math/__init__.py +0 -0
  40. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/math/stats.py +0 -0
  41. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/pandaz/ta.py +0 -0
  42. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/ta/__init__.py +0 -0
  43. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/ta/indicators.pxd +0 -0
  44. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/ta/indicators.pyi +0 -0
  45. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/ta/indicators.pyx +0 -0
  46. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/trackers/__init__.py +0 -0
  47. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/trackers/composite.py +0 -0
  48. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/trackers/rebalancers.py +0 -0
  49. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/trackers/riskctrl.py +0 -0
  50. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/trackers/sizers.py +0 -0
  51. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/utils/_pyxreloader.py +0 -0
  52. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/utils/charting/lookinglass.py +0 -0
  53. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/utils/charting/mpl_helpers.py +0 -0
  54. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/utils/marketdata/binance.py +0 -0
  55. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/utils/misc.py +0 -0
  56. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/utils/ntp.py +0 -0
  57. {qubx-0.2.74 → qubx-0.2.76}/src/qubx/utils/runner.py +0 -0
  58. {qubx-0.2.74 → qubx-0.2.76}/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.74
3
+ Version: 0.2.76
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.74"
3
+ version = "0.2.76"
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"
@@ -70,6 +70,10 @@ if runtime_env() in ["notebook", "shell"]:
70
70
  # process data manager
71
71
  __manager = None
72
72
 
73
+ @line_magic
74
+ def qubx(self, line: str):
75
+ self.qubx_setup("dark" + " " + line)
76
+
73
77
  @line_magic
74
78
  def qubxd(self, line: str):
75
79
  self.qubx_setup("dark" + " " + line)
@@ -1,5 +1,5 @@
1
1
  """"
2
- Here stuff we want to have in every Jupyter notebook after calling %qube magic
2
+ Here stuff we want to have in every Jupyter notebook after calling %qubx magic
3
3
  """
4
4
 
5
5
  import qubx
@@ -29,7 +29,7 @@ def np_fmt_reset():
29
29
  if runtime_env() in ["notebook", "shell"]:
30
30
 
31
31
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
32
- # -- all imports below will appear in notebook after calling %%alphalab magic ---
32
+ # -- all imports below will appear in notebook after calling %%qubx magic ---
33
33
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
34
34
 
35
35
  # - - - - Common stuff - - - -
@@ -40,16 +40,52 @@ if runtime_env() in ["notebook", "shell"]:
40
40
 
41
41
  # - - - - TA stuff and indicators - - - -
42
42
  import qubx.pandaz.ta as pta
43
+ import qubx.ta.indicators as ta
43
44
 
44
45
  # - - - - Portfolio analysis - - - -
46
+ from qubx.core.metrics import (
47
+ tearsheet,
48
+ chart_signals,
49
+ get_symbol_pnls,
50
+ get_equity,
51
+ portfolio_metrics,
52
+ pnl,
53
+ drop_symbols,
54
+ pick_symbols,
55
+ )
56
+
57
+ # - - - - Data reading - - - -
58
+ from qubx.data.readers import (
59
+ CsvStorageDataReader,
60
+ MultiQdbConnector,
61
+ QuestDBConnector,
62
+ AsOhlcvSeries,
63
+ AsPandasFrame,
64
+ AsQuotes,
65
+ AsTimestampedRecords,
66
+ RestoreTicksFromOHLC,
67
+ )
68
+
45
69
  # - - - - Simulator stuff - - - -
46
- # - - - - Learn stuff - - - -
70
+ from qubx.backtester.simulator import simulate
71
+ from qubx.backtester.optimization import variate
72
+
47
73
  # - - - - Charting stuff - - - -
48
74
  from matplotlib import pyplot as plt
49
75
  from qubx.utils.charting.mpl_helpers import fig, subplot, sbp, plot_trends, ohlc_plot
76
+ from qubx.utils.charting.lookinglass import LookingGlass
50
77
 
51
78
  # - - - - Utils - - - -
52
- from qubx.pandaz.utils import scols, srows, ohlc_resample, continuous_periods, generate_equal_date_ranges
79
+ from qubx.pandaz.utils import (
80
+ scols,
81
+ srows,
82
+ ohlc_resample,
83
+ continuous_periods,
84
+ generate_equal_date_ranges,
85
+ drop_duplicated_indexes,
86
+ retain_columns_and_join,
87
+ rolling_forward_test_split,
88
+ )
53
89
 
54
90
  # - setup short numpy output format
55
91
  np_fmt_short()
@@ -357,4 +357,4 @@ def set_parameters_to_object(strategy: Any, **kwargs):
357
357
  _log_info += f"\n\tset <green>{k}</green> <- <red>{v_str}</red>"
358
358
 
359
359
  if _log_info:
360
- logger.info(f"<yellow>{strategy.__class__.__name__}</yellow> new parameters:" + _log_info)
360
+ logger.debug(f"<yellow>{strategy.__class__.__name__}</yellow> new parameters:" + _log_info)
@@ -152,8 +152,8 @@ class InstrumentsLookup:
152
152
  v["base"],
153
153
  v["quote"],
154
154
  v["settle"],
155
- min_tick=float(info["tickSize"]),
156
- min_size_step=float(v["precision"]["price"]),
155
+ min_tick=float(v["precision"]["price"]),
156
+ min_size_step=float(v["precision"]["amount"]),
157
157
  min_size=v["precision"]["amount"],
158
158
  futures_info=FuturesInfo(
159
159
  contract_type=info["type"],
@@ -33,6 +33,9 @@ MINUTELY = HOURLY * 60
33
33
  HOURLY_FX = DAILY * 24
34
34
  MINUTELY_FX = HOURLY_FX * 60
35
35
 
36
+ _D1 = pd.Timedelta("1D")
37
+ _W1 = pd.Timedelta("1W")
38
+
36
39
 
37
40
  def absmaxdd(data: List | Tuple | pd.Series | np.ndarray) -> Tuple[float, int, int, int, pd.Series]:
38
41
  """
@@ -113,7 +116,7 @@ def max_drawdown_pct(returns):
113
116
  return np.nanmin((cumrets - max_return) / max_return)
114
117
 
115
118
 
116
- def portfolio_returns(portfolio_log: pd.DataFrame, method="pct", init_cash=0) -> pd.Series:
119
+ def portfolio_returns(portfolio_log: pd.DataFrame, method="pct", init_cash: float = 0.0) -> pd.Series:
117
120
  """
118
121
  Calculates returns based on specified method.
119
122
 
@@ -323,7 +326,7 @@ def omega_ratio(returns, risk_free=0.0, required_return=0.0, periods=DAILY):
323
326
  return (numer / denom) if denom > 0.0 else np.nan
324
327
 
325
328
 
326
- def aggregate_returns(returns, convert_to):
329
+ def aggregate_returns(returns: pd.Series, convert_to: str) -> pd.DataFrame | pd.Series:
327
330
  """
328
331
  Aggregates returns by specified time period
329
332
  :param returns: pd.Series or np.ndarray periodic returns of the strategy, noncumulative
@@ -583,17 +586,21 @@ def monthly_returns(
583
586
  return pd.concat((100 * r_month, acc_balance), axis=1, keys=["Returns", "Balance"])
584
587
 
585
588
 
586
- def portfolio_symbols(df: pd.DataFrame) -> List[str]:
589
+ def portfolio_symbols(src: pd.DataFrame | TradingSessionResult) -> List[str]:
587
590
  """
588
591
  Get list of symbols from portfolio log
589
592
  """
593
+ df = src.portfolio_log if isinstance(src, TradingSessionResult) else src
590
594
  return list(df.columns[::5].str.split("_").str.get(0).values)
591
595
 
592
596
 
593
- def pnl(x: pd.DataFrame, c=1, cum=False, total=False, resample=None):
597
+ def pnl(
598
+ src: pd.DataFrame | TradingSessionResult, c=1, cum=False, total=False, resample=None
599
+ ) -> pd.Series | pd.DataFrame:
594
600
  """
595
601
  Extract PnL from portfolio log
596
602
  """
603
+ x = src.portfolio_log if isinstance(src, TradingSessionResult) else src
597
604
  pl = x.filter(regex=".*_PnL").rename(lambda x: x.split("_")[0], axis=1)
598
605
  comms = x.filter(regex=".*_Commissions").rename(lambda x: x.split("_")[0], axis=1)
599
606
  r = pl - c * comms
@@ -603,18 +610,21 @@ def pnl(x: pd.DataFrame, c=1, cum=False, total=False, resample=None):
603
610
  return r.sum(axis=1) if total else r
604
611
 
605
612
 
606
- def drop_symbols(df: pd.DataFrame, *args, quoted="USDT"):
613
+ def drop_symbols(src: pd.DataFrame | TradingSessionResult, *args, quoted="USDT") -> pd.DataFrame:
607
614
  """
608
615
  Drop symbols (is quoted currency) from portfolio log
609
616
  """
610
617
  s = "|".join([f"{a}{quoted}" if not a.endswith(quoted) else a for a in args])
618
+ df = src.portfolio_log if isinstance(src, TradingSessionResult) else src
611
619
  return df.filter(filter(lambda si: not re.match(f"^{s}_.*", si), df.columns))
612
620
 
613
621
 
614
- def pick_symbols(df: pd.DataFrame, *args, quoted="USDT"):
622
+ def pick_symbols(src: pd.DataFrame | TradingSessionResult, *args, quoted="USDT") -> pd.DataFrame:
615
623
  """
616
624
  Select symbols (is quoted currency) from portfolio log
617
625
  """
626
+ df = src.portfolio_log if isinstance(src, TradingSessionResult) else src
627
+
618
628
  # - pick up from execution report
619
629
  if "instrument" in df.columns and "quantity" in df.columns:
620
630
  rx = "|".join([f"{a}.*" for a in args])
@@ -663,11 +673,19 @@ def portfolio_metrics(
663
673
  returns = returns[:end]
664
674
  returns_on_init_bp = returns_on_init_bp[:end]
665
675
 
666
- # aggregate them to daily (if we have intraday portfolio)
676
+ # - aggregate returns to higher timeframe
667
677
  try:
668
- if infer_series_frequency(returns) < pd.Timedelta("1D").to_timedelta64():
669
- returns_daily = aggregate_returns(returns, "daily")
670
- returns_on_init_bp = aggregate_returns(returns_on_init_bp, "daily")
678
+ _conversion = "daily"
679
+ match (_s_freq := infer_series_frequency(returns)):
680
+ case _ if _s_freq <= _D1.to_timedelta64():
681
+ _conversion = "daily"
682
+ case _ if _s_freq > _D1.to_timedelta64() and _s_freq <= _W1.to_timedelta64():
683
+ _conversion = "weekly"
684
+ case _:
685
+ _conversion = "monthly"
686
+
687
+ returns_daily = aggregate_returns(returns, _conversion)
688
+ returns_on_init_bp = aggregate_returns(returns_on_init_bp, _conversion)
671
689
  except:
672
690
  returns_daily = returns
673
691
 
@@ -792,7 +810,7 @@ def get_equity(
792
810
  sessions: TradingSessionResult | list[TradingSessionResult],
793
811
  account_transactions: bool = True,
794
812
  timeframe: str | None = None,
795
- ) -> pd.DataFrame:
813
+ ) -> pd.DataFrame | pd.Series:
796
814
  if timeframe is None:
797
815
  timeframe = _estimate_timeframe(sessions)
798
816
 
@@ -0,0 +1,11 @@
1
+ from .readers import (
2
+ DataReader,
3
+ CsvStorageDataReader,
4
+ MultiQdbConnector,
5
+ QuestDBConnector,
6
+ AsOhlcvSeries,
7
+ AsPandasFrame,
8
+ AsQuotes,
9
+ AsTimestampedRecords,
10
+ RestoreTicksFromOHLC,
11
+ )
@@ -903,7 +903,7 @@ class QuestDBConnector(DataReader):
903
903
  where = f"where symbol in ({', '.join(quoted_symbols)}) and timestamp >= '{start}' and timestamp < '{stop}'"
904
904
  table_name = QuestDBSqlCandlesBuilder().get_table_name(f"{exchange}:{symbols[0]}")
905
905
 
906
- _rsmpl = f"sample by {timeframe}"
906
+ _rsmpl = f"sample by {QuestDBSqlCandlesBuilder._convert_time_delta_to_qdb_resample_format(timeframe)}"
907
907
 
908
908
  query = f"""
909
909
  select timestamp,
@@ -934,7 +934,7 @@ class QuestDBConnector(DataReader):
934
934
  select timestamp, symbol, sum(quote_volume) as qvolume
935
935
  from "{table_name}"
936
936
  where timestamp >= '{start}' and timestamp < '{stop}'
937
- SAMPLE BY {timeframe}
937
+ SAMPLE BY {QuestDBSqlCandlesBuilder._convert_time_delta_to_qdb_resample_format(timeframe)}
938
938
  )
939
939
  select symbol, avg(qvolume) as quote_volume from sampled
940
940
  group by symbol
@@ -945,6 +945,23 @@ class QuestDBConnector(DataReader):
945
945
  return pd.Series()
946
946
  return vol_stats.set_index("symbol")["quote_volume"]
947
947
 
948
+ def get_fundamental_data(
949
+ self, exchange: str, start: str | pd.Timestamp | None = None, stop: str | pd.Timestamp | None = None
950
+ ) -> pd.DataFrame:
951
+ table_name = {"BINANCE.UM": "binance.umfutures.fundamental"}[exchange]
952
+ query = f"select * from {table_name}"
953
+ if start or stop:
954
+ conditions = []
955
+ if start:
956
+ conditions.append(f"timestamp >= '{start}'")
957
+ if stop:
958
+ conditions.append(f"timestamp < '{stop}'")
959
+ query += " where " + " and ".join(conditions)
960
+ df = self.execute(query)
961
+ if df.empty:
962
+ return pd.DataFrame()
963
+ return df.set_index(["timestamp", "symbol", "metric"]).value.unstack("metric")
964
+
948
965
  def get_names(self) -> List[str]:
949
966
  return self._get_names(self._builder)
950
967
 
@@ -0,0 +1,15 @@
1
+ from .utils import (
2
+ srows,
3
+ scols,
4
+ continuous_periods,
5
+ ohlc_resample,
6
+ retain_columns_and_join,
7
+ select_column_and_join,
8
+ dict_to_frame,
9
+ drop_duplicated_indexes,
10
+ process_duplicated_indexes,
11
+ generate_equal_date_ranges,
12
+ rolling_forward_test_split,
13
+ shift_series,
14
+ check_frame_columns,
15
+ )
@@ -90,11 +90,11 @@ def rolling_forward_test_split(
90
90
  yield (np.array(range(i - training_period, i)), np.array(range(i, i + test_period)))
91
91
 
92
92
 
93
- def generate_equal_date_ranges(start: str | pd.Timestamp, end: str | pd.Timestamp, freq: int, units: str) -> Iterable:
93
+ def generate_equal_date_ranges(start: str, end: str, freq, units):
94
94
  """
95
95
  Generator for date ranges:
96
96
 
97
- for s,e in generate_equal_date_ranges('2019-01-01', '2022-05-17', 1, 'Y'):
97
+ for s,e in generate_ranges('2019-01-01', '2022-05-17', 1, 'Y'):
98
98
  print(s, e)
99
99
  ------------------
100
100
  2019-01-01 2019-12-31
@@ -103,9 +103,8 @@ def generate_equal_date_ranges(start: str | pd.Timestamp, end: str | pd.Timestam
103
103
  2022-01-01 2022-05-17
104
104
  """
105
105
  _as_f = lambda x: pd.Timestamp(x).strftime("%Y-%m-%d")
106
-
107
- # - for case when end - start < freq it won't got into the loop
108
- b = [start, start] if _as_f(start) > _as_f(end) else [start, end]
106
+ if pd.Timestamp(end) - pd.Timestamp(start) < freq * pd.Timedelta(f"1{units}"):
107
+ b = [start, end]
109
108
 
110
109
  for a, b in rolling_forward_test_split(pd.Series(0, pd.date_range(start, end)), freq, freq, units=units):
111
110
  yield _as_f(a[0]), _as_f(a[-1])
@@ -1,4 +1,4 @@
1
1
  from .time import convert_seconds_to_str, convert_tf_str_td64, floor_t64, time_to_str, infer_series_frequency
2
2
  from .misc import runtime_env, version, Struct, Stopwatch
3
- from .charting.mpl_helpers import set_mpl_theme, fig, sbp, vline, hline, ellips, set_mpl_theme, ohlc_plot
3
+ from .charting.mpl_helpers import fig, sbp, vline, hline, ellips, set_mpl_theme, ohlc_plot, plot_trends
4
4
  from .charting.lookinglass import LookingGlass
@@ -1,4 +0,0 @@
1
- from .utils import (
2
- srows, scols, continuous_periods, ohlc_resample, retain_columns_and_join, dict_to_frame,
3
- generate_equal_date_ranges, rolling_forward_test_split
4
- )
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