pwb-toolbox 0.1.9__py3-none-any.whl → 0.1.11__py3-none-any.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.
@@ -1,3 +1,5 @@
1
1
  from .base_strategy import BaseStrategy
2
2
  from .engine import run_strategy
3
3
  from .ib_connector import IBConnector, run_ib_strategy
4
+ from .portfolio import MonthlyEqualWeightPortfolio
5
+ from .universe import get_most_liquid_symbols, get_least_volatile_symbols
@@ -3,7 +3,16 @@ import pandas as pd
3
3
  import pwb_toolbox.datasets as pwb_ds
4
4
 
5
5
 
6
- def run_strategy(signal, signal_kwargs, portfolio, symbols, start_date, leverage, cash):
6
+ def run_strategy(
7
+ signal,
8
+ signal_kwargs,
9
+ portfolio,
10
+ portfolio_kwargs,
11
+ symbols,
12
+ start_date,
13
+ leverage,
14
+ cash,
15
+ ):
7
16
  """Run a tactical asset allocation strategy with Backtrader."""
8
17
  # Load the data from https://paperswithbacktest.com/datasets
9
18
  pivot_df = pwb_ds.get_pricing(
@@ -18,7 +27,7 @@ def run_strategy(signal, signal_kwargs, portfolio, symbols, start_date, leverage
18
27
  pivot_df.ffill(inplace=True) # forward-fill holidays
19
28
  pivot_df.bfill(inplace=True) # back-fill leading IPO gaps
20
29
  cerebro = bt.Cerebro()
21
- for symbol in symbols:
30
+ for symbol in pivot_df.columns.levels[0]:
22
31
  data = bt.feeds.PandasData(dataname=pivot_df[symbol].copy())
23
32
  cerebro.adddata(data, name=symbol)
24
33
  cerebro.addstrategy(
@@ -27,6 +36,7 @@ def run_strategy(signal, signal_kwargs, portfolio, symbols, start_date, leverage
27
36
  leverage=0.9,
28
37
  signal_cls=signal,
29
38
  signal_kwargs=signal_kwargs,
39
+ **portfolio_kwargs,
30
40
  )
31
41
  cerebro.broker.set_cash(cash)
32
42
  strategy = cerebro.run()[0]
@@ -0,0 +1,30 @@
1
+ from .base_strategy import BaseStrategy
2
+
3
+
4
+ class MonthlyEqualWeightPortfolio(BaseStrategy):
5
+ params = (
6
+ ("leverage", 0.9),
7
+ ("signal_cls", None),
8
+ ("signal_kwargs", {}),
9
+ )
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.sig = {
14
+ d._name: self.p.signal_cls(d, **self.p.signal_kwargs) for d in self.datas
15
+ }
16
+ self.last_month = -1
17
+
18
+ def next(self):
19
+ """Rebalance portfolio at the start of each month."""
20
+ super().next()
21
+ today = self.datas[0].datetime.date(0)
22
+ if today.month == self.last_month:
23
+ return
24
+ self.last_month = today.month
25
+ longs = [
26
+ d for d in self.datas if self.is_tradable(d) and self.sig[d._name][0] == 1
27
+ ]
28
+ wt = (self.p.leverage / len(longs)) if longs else 0.0
29
+ for d in self.datas:
30
+ self.order_target_percent(d, target=wt if d in longs else 0)
@@ -0,0 +1,37 @@
1
+ from typing import List
2
+ import pandas as pd
3
+ import pwb_toolbox.datasets as pwb_ds
4
+
5
+
6
+ def get_most_liquid_symbols(n: int = 1_200) -> List[str]:
7
+ """Return the `n` most liquid stock symbols (volume × price on last bar)."""
8
+ df = pwb_ds.load_dataset("Stocks-Daily-Price", adjust=True, extend=True)
9
+ last_date = df["date"].max()
10
+ today = df[df["date"] == last_date].copy()
11
+ today["liquidity"] = today["volume"] * today["close"]
12
+ liquid = today.sort_values("liquidity", ascending=False)
13
+ return liquid["symbol"].tolist()[:n]
14
+
15
+
16
+ def get_least_volatile_symbols(symbols=["sp500"], start="1990-01-01") -> List[str]:
17
+ pivot = pwb_ds.get_pricing(
18
+ symbol_list=symbols,
19
+ fields=["open", "high", "low", "close"],
20
+ start_date=start,
21
+ extend=True,
22
+ )
23
+ td = pd.bdate_range(pivot.index.min(), pivot.index.max())
24
+ pivot = pivot.reindex(td).ffill().bfill()
25
+ symbols = []
26
+ for sym in pivot.columns.levels[0]:
27
+ df = (
28
+ pivot[sym]
29
+ .copy()
30
+ .reset_index()
31
+ .rename(columns={"index": "date"})
32
+ .set_index("date")
33
+ )
34
+ if df.close.pct_change().abs().max() > 3: # same volatility filter
35
+ continue
36
+ symbols.append(sym)
37
+ return symbols
@@ -921,6 +921,9 @@ def get_pricing(
921
921
  fields = ["close"]
922
922
  if isinstance(symbol_list, str):
923
923
  symbol_list = [symbol_list]
924
+ if isinstance(symbol_list, list) and "sp500" in symbol_list:
925
+ symbol_list.remove("sp500")
926
+ symbol_list += SP500_SYMBOLS
924
927
 
925
928
  fields = [f.lower() for f in fields]
926
929
  bad = [f for f in fields if f not in ALLOWED_FIELDS]
@@ -928,25 +931,32 @@ def get_pricing(
928
931
  raise ValueError(f"Invalid field(s): {bad}. Allowed: {sorted(ALLOWED_FIELDS)}")
929
932
 
930
933
  # --------------------------------------------------------------- download
931
- DATASETS = [
932
- ("Stocks-Daily-Price", extend),
933
- ("ETFs-Daily-Price", extend),
934
- ("Cryptocurrencies-Daily-Price", extend),
935
- ("Bonds-Daily-Price", extend),
936
- ("Commodities-Daily-Price", extend),
937
- ("Forex-Daily-Price", extend),
938
- ("Indices-Daily-Price", False), # indices generally have no proxy data
939
- ]
940
- remaining = set(symbol_list) # symbols still to fetch
934
+ universe = ds.load_dataset(
935
+ "paperswithbacktest/Universe-Daily-Price",
936
+ token=_get_hf_token(),
937
+ )
938
+ mapping = universe["train"].to_pandas()
939
+ mapping = mapping.set_index("symbol")["repo_id"].to_dict()
940
+
941
+ grouped = defaultdict(list)
942
+ remaining = []
943
+ for sym in symbol_list:
944
+ repo_id = mapping.get(sym)
945
+ repo_id = repo_id.split("/")[1] if isinstance(repo_id, str) else None
946
+ if repo_id:
947
+ grouped[repo_id].append(sym)
948
+ else:
949
+ print(f"Warning: No dataset found for symbol '{sym}'")
950
+ remaining.append(sym)
951
+
941
952
  frames = []
942
- for dataset_name, ext_flag in DATASETS:
943
- if not remaining: # all symbols resolved stop early
944
- break
945
- df_part = load_dataset(dataset_name, list(remaining), extend=ext_flag)
953
+ for repo_id, syms in grouped.items():
954
+ ext_flag = extend if repo_id != "Indices-Daily-Price" else False
955
+ df_part = load_dataset(repo_id, syms, extend=ext_flag)
946
956
  if not df_part.empty:
947
957
  frames.append(df_part)
948
- remaining -= set(df_part["symbol"].unique())
949
- df = pd.concat(frames, ignore_index=True)
958
+
959
+ df = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
950
960
 
951
961
  df["date"] = pd.to_datetime(df["date"])
952
962
  df.set_index("date", inplace=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pwb-toolbox
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: A toolbox library for quant traders
5
5
  Home-page: https://github.com/paperswithbacktest/pwb-toolbox
6
6
  Author: Your Name
@@ -0,0 +1,17 @@
1
+ pwb_toolbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pwb_toolbox/backtest/__init__.py,sha256=TZYVTvK-i0sjzVACvIfarfCZtoFtuPAZtia0rZ9RPm4,253
3
+ pwb_toolbox/backtest/base_strategy.py,sha256=aKQ7XNxWkljaR7TQ-Tbs_uNWBDLdbmaLADcCsclBwuU,990
4
+ pwb_toolbox/backtest/engine.py,sha256=LQXMw-CQrhOymVWwOA5wZXOBoo5L7ZNy9locJfDV7uU,1352
5
+ pwb_toolbox/backtest/ib_connector.py,sha256=5T-pgT_MrDOxqdvXgT_hceIeewPs-rN3j4n-Wr-6JGU,2120
6
+ pwb_toolbox/backtest/portfolio.py,sha256=e_hhy2695FOgDCFcHU8DhhSXc5We7sRC8O2NpDFdg48,930
7
+ pwb_toolbox/backtest/universe.py,sha256=LzFBCOuKBVhgM4yMXOlTvPIrbevGkTS1itHIRxMHRRY,1272
8
+ pwb_toolbox/datasets/__init__.py,sha256=gaPQDsQAdhEo-6_GuSPwrF6aHy9GTKENO-VJv8j9VU8,22527
9
+ pwb_toolbox/performance/__init__.py,sha256=ds47RiOSL3iIwRE0S8dnGINcVPlZw_I9D21ueTSVP-I,2925
10
+ pwb_toolbox/performance/metrics.py,sha256=szY8m45dZeJHciF4NxPxXlDyc78_5cLyIweRQJ_8lCE,15255
11
+ pwb_toolbox/performance/plots.py,sha256=R6OV-SxJaJnBuJGh8XmsF58a7ERwn2Irf4zEqzGMRz4,12886
12
+ pwb_toolbox/performance/trade_stats.py,sha256=I-iboKMwVLij6pc2r-KfNDnyF3LZV_LzzpgjIcJtgFw,4940
13
+ pwb_toolbox-0.1.11.dist-info/licenses/LICENSE.txt,sha256=_Wjz7o7St3iVSPBRzE0keS8XSqSJ03A3NZ6cMlTaSK8,1079
14
+ pwb_toolbox-0.1.11.dist-info/METADATA,sha256=67N8fbKy9je3OtCmdwL2NfkrQZ3s4JTPaEe5zmq87Fw,7131
15
+ pwb_toolbox-0.1.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ pwb_toolbox-0.1.11.dist-info/top_level.txt,sha256=TZcXcF2AMkKkibZOuq6AYsHjajPgddHAGjQUT64OYGY,12
17
+ pwb_toolbox-0.1.11.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- pwb_toolbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- pwb_toolbox/backtest/__init__.py,sha256=EDqoQ-P4ADV91F2oCgtSfFK1R_65BMVsFWDlk-zNHnM,128
3
- pwb_toolbox/backtest/base_strategy.py,sha256=aKQ7XNxWkljaR7TQ-Tbs_uNWBDLdbmaLADcCsclBwuU,990
4
- pwb_toolbox/backtest/engine.py,sha256=v5mO5HUhsQnxmBeSdkE0KwbYZnS6rB1TM_A3AYERa4s,1252
5
- pwb_toolbox/backtest/ib_connector.py,sha256=5T-pgT_MrDOxqdvXgT_hceIeewPs-rN3j4n-Wr-6JGU,2120
6
- pwb_toolbox/datasets/__init__.py,sha256=o2Q6nw8HmV_gTFfovhPJkoGdFsADBunFC4KqBl9Tpaw,22259
7
- pwb_toolbox/performance/__init__.py,sha256=ds47RiOSL3iIwRE0S8dnGINcVPlZw_I9D21ueTSVP-I,2925
8
- pwb_toolbox/performance/metrics.py,sha256=szY8m45dZeJHciF4NxPxXlDyc78_5cLyIweRQJ_8lCE,15255
9
- pwb_toolbox/performance/plots.py,sha256=R6OV-SxJaJnBuJGh8XmsF58a7ERwn2Irf4zEqzGMRz4,12886
10
- pwb_toolbox/performance/trade_stats.py,sha256=I-iboKMwVLij6pc2r-KfNDnyF3LZV_LzzpgjIcJtgFw,4940
11
- pwb_toolbox-0.1.9.dist-info/licenses/LICENSE.txt,sha256=_Wjz7o7St3iVSPBRzE0keS8XSqSJ03A3NZ6cMlTaSK8,1079
12
- pwb_toolbox-0.1.9.dist-info/METADATA,sha256=_Abqeq866im1F_F_x3iTEvV-Fg-QA0ri7d5xtAQ-NUk,7130
13
- pwb_toolbox-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- pwb_toolbox-0.1.9.dist-info/top_level.txt,sha256=TZcXcF2AMkKkibZOuq6AYsHjajPgddHAGjQUT64OYGY,12
15
- pwb_toolbox-0.1.9.dist-info/RECORD,,