pwb-toolbox 0.1.7__tar.gz → 0.1.9__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.
Files changed (34) hide show
  1. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/PKG-INFO +62 -2
  2. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/README.md +59 -1
  3. pwb_toolbox-0.1.9/pwb_toolbox/backtest/__init__.py +3 -0
  4. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox/backtest/base_strategy.py +1 -1
  5. pwb_toolbox-0.1.9/pwb_toolbox/backtest/engine.py +33 -0
  6. pwb_toolbox-0.1.9/pwb_toolbox/backtest/ib_connector.py +69 -0
  7. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox/datasets/__init__.py +7 -4
  8. pwb_toolbox-0.1.9/pwb_toolbox/performance/__init__.py +123 -0
  9. pwb_toolbox-0.1.9/pwb_toolbox/performance/metrics.py +465 -0
  10. pwb_toolbox-0.1.9/pwb_toolbox/performance/plots.py +415 -0
  11. pwb_toolbox-0.1.9/pwb_toolbox/performance/trade_stats.py +138 -0
  12. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/PKG-INFO +62 -2
  13. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/SOURCES.txt +8 -5
  14. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/requires.txt +2 -0
  15. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/setup.cfg +3 -1
  16. pwb_toolbox-0.1.9/tests/test_backtest.py +29 -0
  17. pwb_toolbox-0.1.9/tests/test_hf_token.py +24 -0
  18. pwb_toolbox-0.1.9/tests/test_ib_connector.py +31 -0
  19. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/tests/test_universe_models.py +5 -0
  20. pwb_toolbox-0.1.7/pwb_toolbox/backtest/__init__.py +0 -51
  21. pwb_toolbox-0.1.7/pwb_toolbox/backtest/execution_models/__init__.py +0 -153
  22. pwb_toolbox-0.1.7/pwb_toolbox/backtest/insight.py +0 -21
  23. pwb_toolbox-0.1.7/pwb_toolbox/backtest/portfolio_models/__init__.py +0 -290
  24. pwb_toolbox-0.1.7/pwb_toolbox/backtest/risk_models/__init__.py +0 -175
  25. pwb_toolbox-0.1.7/pwb_toolbox/backtest/universe_models/__init__.py +0 -183
  26. pwb_toolbox-0.1.7/tests/test_backtest.py +0 -12
  27. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/LICENSE.txt +0 -0
  28. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox/__init__.py +0 -0
  29. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/dependency_links.txt +0 -0
  30. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/top_level.txt +0 -0
  31. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pyproject.toml +0 -0
  32. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/tests/test_execution_models.py +0 -0
  33. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/tests/test_portfolio_models.py +0 -0
  34. {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/tests/test_risk_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pwb-toolbox
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: A toolbox library for quant traders
5
5
  Home-page: https://github.com/paperswithbacktest/pwb-toolbox
6
6
  Author: Your Name
@@ -16,6 +16,8 @@ Description-Content-Type: text/markdown
16
16
  License-File: LICENSE.txt
17
17
  Requires-Dist: datasets
18
18
  Requires-Dist: pandas
19
+ Requires-Dist: ibapi
20
+ Requires-Dist: ib_insync
19
21
  Dynamic: license-file
20
22
 
21
23
  <div align="center">
@@ -128,10 +130,68 @@ into portfolio weights and executed via Backtrader orders.
128
130
  ```python
129
131
  from pwb_toolbox.backtest.examples import GoldenCrossAlpha, EqualWeightPortfolio
130
132
  from pwb_toolbox.backtest import run_backtest
133
+ from pwb_toolbox.backtest.execution_models import ImmediateExecutionModel
134
+ from pwb_toolbox.backtest.risk_models import MaximumTotalPortfolioExposure
135
+ from pwb_toolbox.backtest.universe_models import ManualUniverseSelectionModel
136
+
137
+ run_backtest(
138
+ ManualUniverseSelectionModel(["SPY", "QQQ"]),
139
+ GoldenCrossAlpha(),
140
+ EqualWeightPortfolio(),
141
+ execution=ImmediateExecutionModel(),
142
+ risk=MaximumTotalPortfolioExposure(max_exposure=1.0),
143
+ start="2015-01-01",
144
+ )
145
+ ```
146
+
147
+ ## Performance Analysis
148
+
149
+ After running a backtest you can analyze the returned equity series using the
150
+ `pwb_toolbox.performance` module.
151
+
152
+ ```python
153
+ from pwb_toolbox.backtest.examples import GoldenCrossAlpha, EqualWeightPortfolio
154
+ from pwb_toolbox.backtest import run_backtest
155
+ from pwb_toolbox.backtest.execution_models import ImmediateExecutionModel
156
+ from pwb_toolbox.performance import total_return, cagr
157
+ from pwb_toolbox.performance.plots import plot_equity_curve
158
+
159
+ result, equity = run_backtest(
160
+ ManualUniverseSelectionModel(["SPY", "QQQ"]),
161
+ GoldenCrossAlpha(),
162
+ EqualWeightPortfolio(),
163
+ execution=ImmediateExecutionModel(),
164
+ start="2015-01-01",
165
+ )
131
166
 
132
- run_backtest(["SPY", "QQQ"], GoldenCrossAlpha(), EqualWeightPortfolio(), start="2015-01-01")
167
+ print("Total return:", total_return(equity))
168
+ print("CAGR:", cagr(equity))
169
+
170
+ plot_equity_curve(equity)
133
171
  ```
134
172
 
173
+ Plotting utilities require `matplotlib`; some metrics also need `pandas`.
174
+
175
+ ## Live trading with Interactive Brokers
176
+
177
+ `run_ib_strategy` streams Interactive Brokers data and orders. Install `ibapi` and either `atreyu-backtrader-api` or `ib_insync`.
178
+
179
+ ```python
180
+ from pwb_toolbox.backtest import IBConnector, run_ib_strategy
181
+ from pwb_toolbox.backtest.example.engine import SimpleIBStrategy
182
+
183
+ data_cfg = [{"dataname": "AAPL", "name": "AAPL"}]
184
+ run_ib_strategy(
185
+ SimpleIBStrategy,
186
+ data_cfg,
187
+ host="127.0.0.1",
188
+ port=7497,
189
+ client_id=1,
190
+ )
191
+ ```
192
+
193
+ Configure `host`, `port`, and `client_id` to match your TWS or Gateway settings. Test with an Interactive Brokers paper account before trading live.
194
+
135
195
  ## Contributing
136
196
 
137
197
  Contributions to the `pwb-toolbox` package are welcome! If you have any improvements, new datasets, or strategy ideas to share, please follow these guidelines:
@@ -108,10 +108,68 @@ into portfolio weights and executed via Backtrader orders.
108
108
  ```python
109
109
  from pwb_toolbox.backtest.examples import GoldenCrossAlpha, EqualWeightPortfolio
110
110
  from pwb_toolbox.backtest import run_backtest
111
+ from pwb_toolbox.backtest.execution_models import ImmediateExecutionModel
112
+ from pwb_toolbox.backtest.risk_models import MaximumTotalPortfolioExposure
113
+ from pwb_toolbox.backtest.universe_models import ManualUniverseSelectionModel
114
+
115
+ run_backtest(
116
+ ManualUniverseSelectionModel(["SPY", "QQQ"]),
117
+ GoldenCrossAlpha(),
118
+ EqualWeightPortfolio(),
119
+ execution=ImmediateExecutionModel(),
120
+ risk=MaximumTotalPortfolioExposure(max_exposure=1.0),
121
+ start="2015-01-01",
122
+ )
123
+ ```
124
+
125
+ ## Performance Analysis
126
+
127
+ After running a backtest you can analyze the returned equity series using the
128
+ `pwb_toolbox.performance` module.
129
+
130
+ ```python
131
+ from pwb_toolbox.backtest.examples import GoldenCrossAlpha, EqualWeightPortfolio
132
+ from pwb_toolbox.backtest import run_backtest
133
+ from pwb_toolbox.backtest.execution_models import ImmediateExecutionModel
134
+ from pwb_toolbox.performance import total_return, cagr
135
+ from pwb_toolbox.performance.plots import plot_equity_curve
136
+
137
+ result, equity = run_backtest(
138
+ ManualUniverseSelectionModel(["SPY", "QQQ"]),
139
+ GoldenCrossAlpha(),
140
+ EqualWeightPortfolio(),
141
+ execution=ImmediateExecutionModel(),
142
+ start="2015-01-01",
143
+ )
111
144
 
112
- run_backtest(["SPY", "QQQ"], GoldenCrossAlpha(), EqualWeightPortfolio(), start="2015-01-01")
145
+ print("Total return:", total_return(equity))
146
+ print("CAGR:", cagr(equity))
147
+
148
+ plot_equity_curve(equity)
113
149
  ```
114
150
 
151
+ Plotting utilities require `matplotlib`; some metrics also need `pandas`.
152
+
153
+ ## Live trading with Interactive Brokers
154
+
155
+ `run_ib_strategy` streams Interactive Brokers data and orders. Install `ibapi` and either `atreyu-backtrader-api` or `ib_insync`.
156
+
157
+ ```python
158
+ from pwb_toolbox.backtest import IBConnector, run_ib_strategy
159
+ from pwb_toolbox.backtest.example.engine import SimpleIBStrategy
160
+
161
+ data_cfg = [{"dataname": "AAPL", "name": "AAPL"}]
162
+ run_ib_strategy(
163
+ SimpleIBStrategy,
164
+ data_cfg,
165
+ host="127.0.0.1",
166
+ port=7497,
167
+ client_id=1,
168
+ )
169
+ ```
170
+
171
+ Configure `host`, `port`, and `client_id` to match your TWS or Gateway settings. Test with an Interactive Brokers paper account before trading live.
172
+
115
173
  ## Contributing
116
174
 
117
175
  Contributions to the `pwb-toolbox` package are welcome! If you have any improvements, new datasets, or strategy ideas to share, please follow these guidelines:
@@ -0,0 +1,3 @@
1
+ from .base_strategy import BaseStrategy
2
+ from .engine import run_strategy
3
+ from .ib_connector import IBConnector, run_ib_strategy
@@ -18,7 +18,7 @@ class BaseStrategy(bt.Strategy):
18
18
  return False
19
19
  return data.close[0] != data.close[-2]
20
20
 
21
- def __next__(self):
21
+ def next(self):
22
22
  """Update progress bar and log current value."""
23
23
  self.pbar.update(1)
24
24
  self.log_data.append(
@@ -0,0 +1,33 @@
1
+ import backtrader as bt
2
+ import pandas as pd
3
+ import pwb_toolbox.datasets as pwb_ds
4
+
5
+
6
+ def run_strategy(signal, signal_kwargs, portfolio, symbols, start_date, leverage, cash):
7
+ """Run a tactical asset allocation strategy with Backtrader."""
8
+ # Load the data from https://paperswithbacktest.com/datasets
9
+ pivot_df = pwb_ds.get_pricing(
10
+ symbol_list=symbols,
11
+ fields=["open", "high", "low", "close"],
12
+ start_date=start_date,
13
+ extend=True, # Extend the dataset with proxy data
14
+ )
15
+ # Create trading-day index (optional but keeps Cerebro happy)
16
+ trading_days = pd.bdate_range(pivot_df.index.min(), pivot_df.index.max())
17
+ pivot_df = pivot_df.reindex(trading_days)
18
+ pivot_df.ffill(inplace=True) # forward-fill holidays
19
+ pivot_df.bfill(inplace=True) # back-fill leading IPO gaps
20
+ cerebro = bt.Cerebro()
21
+ for symbol in symbols:
22
+ data = bt.feeds.PandasData(dataname=pivot_df[symbol].copy())
23
+ cerebro.adddata(data, name=symbol)
24
+ cerebro.addstrategy(
25
+ portfolio,
26
+ total_days=len(trading_days),
27
+ leverage=0.9,
28
+ signal_cls=signal,
29
+ signal_kwargs=signal_kwargs,
30
+ )
31
+ cerebro.broker.set_cash(cash)
32
+ strategy = cerebro.run()[0]
33
+ return strategy
@@ -0,0 +1,69 @@
1
+ """Lightweight helpers for running Interactive Brokers backtests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterable, Mapping, Type
6
+
7
+ import backtrader as bt
8
+
9
+
10
+ class IBConnector:
11
+ """Utility for creating Backtrader IB stores and data feeds."""
12
+
13
+ def __init__(
14
+ self,
15
+ host: str = "127.0.0.1",
16
+ port: int = 7497,
17
+ client_id: int = 1,
18
+ store_class: Type[bt.stores.IBStore] | None = None,
19
+ feed_class: Type[bt.feeds.IBData] | None = None,
20
+ ) -> None:
21
+ self.host = host
22
+ self.port = port
23
+ self.client_id = client_id
24
+ self.store_class = store_class or bt.stores.IBStore
25
+ self.feed_class = feed_class or bt.feeds.IBData
26
+
27
+ def get_store(self) -> bt.stores.IBStore:
28
+ """Instantiate and return an ``IBStore``."""
29
+ return self.store_class(host=self.host, port=self.port, clientId=self.client_id)
30
+
31
+ def create_feed(self, **kwargs) -> bt.feeds.IBData:
32
+ """Create an ``IBData`` feed bound to the connector's store."""
33
+ store = kwargs.pop("store", None) or self.get_store()
34
+ return self.feed_class(store=store, **kwargs)
35
+
36
+
37
+ def run_ib_strategy(
38
+ strategy: type[bt.Strategy],
39
+ data_config: Iterable[Mapping[str, object]],
40
+ **ib_kwargs,
41
+ ):
42
+ """Run ``strategy`` with Interactive Brokers data feeds.
43
+
44
+ Parameters
45
+ ----------
46
+ strategy:
47
+ The ``bt.Strategy`` subclass to execute.
48
+ data_config:
49
+ Iterable of dictionaries passed to ``IBData`` for each feed.
50
+ ib_kwargs:
51
+ Arguments forwarded to :class:`IBConnector`.
52
+ Examples
53
+ --------
54
+ >>> data_cfg = [{"dataname": "AAPL", "name": "AAPL", "what": "MIDPOINT"}]
55
+ >>> run_ib_strategy(MyStrategy, data_cfg, host="127.0.0.1")
56
+
57
+ """
58
+ connector = IBConnector(**ib_kwargs)
59
+ cerebro = bt.Cerebro()
60
+ store = connector.get_store()
61
+ cerebro.broker = store.getbroker()
62
+
63
+ for cfg in data_config:
64
+ data = connector.create_feed(store=store, **cfg)
65
+ name = cfg.get("name")
66
+ cerebro.adddata(data, name=name)
67
+
68
+ cerebro.addstrategy(strategy)
69
+ return cerebro.run()
@@ -6,9 +6,12 @@ import re
6
6
  import datasets as ds
7
7
  import pandas as pd
8
8
 
9
- HF_ACCESS_TOKEN = os.environ["HF_ACCESS_TOKEN"]
10
- if not HF_ACCESS_TOKEN:
11
- raise ValueError("Hugging Face access token not found in environment variables")
9
+
10
+ def _get_hf_token() -> str:
11
+ token = os.getenv("HF_ACCESS_TOKEN")
12
+ if not token:
13
+ raise ValueError("HF_ACCESS_TOKEN not set")
14
+ return token
12
15
 
13
16
 
14
17
  DAILY_PRICE_DATASETS = [
@@ -552,7 +555,7 @@ def load_dataset(
552
555
  to_usd=True,
553
556
  rate_to_price=True,
554
557
  ):
555
- dataset = ds.load_dataset(f"paperswithbacktest/{path}", token=HF_ACCESS_TOKEN)
558
+ dataset = ds.load_dataset(f"paperswithbacktest/{path}", token=_get_hf_token())
556
559
  df = dataset["train"].to_pandas()
557
560
 
558
561
  if path in DAILY_PRICE_DATASETS or path in DAILY_FINANCIAL_DATASETS:
@@ -0,0 +1,123 @@
1
+ from .metrics import (
2
+ total_return,
3
+ cagr,
4
+ returns_table,
5
+ rolling_cumulative_return,
6
+ annualized_volatility,
7
+ max_drawdown,
8
+ ulcer_index,
9
+ ulcer_performance_index,
10
+ parametric_var,
11
+ parametric_expected_shortfall,
12
+ tail_ratio,
13
+ sharpe_ratio,
14
+ sortino_ratio,
15
+ calmar_ratio,
16
+ omega_ratio,
17
+ information_ratio,
18
+ capm_alpha_beta,
19
+ skewness,
20
+ kurtosis,
21
+ variance_ratio,
22
+ acf,
23
+ pacf,
24
+ fama_french_3factor,
25
+ fama_french_5factor,
26
+ cumulative_excess_return,
27
+ )
28
+
29
+ from .trade_stats import (
30
+ hit_rate,
31
+ average_win_loss,
32
+ expectancy,
33
+ profit_factor,
34
+ trade_duration_distribution,
35
+ turnover,
36
+ trade_implementation_shortfall,
37
+ cumulative_implementation_shortfall,
38
+ slippage_stats,
39
+ latency_stats,
40
+ )
41
+
42
+ __all__ = [
43
+ "total_return",
44
+ "cagr",
45
+ "returns_table",
46
+ "rolling_cumulative_return",
47
+ "annualized_volatility",
48
+ "max_drawdown",
49
+ "ulcer_index",
50
+ "ulcer_performance_index",
51
+ "parametric_var",
52
+ "parametric_expected_shortfall",
53
+ "tail_ratio",
54
+ "sharpe_ratio",
55
+ "sortino_ratio",
56
+ "calmar_ratio",
57
+ "omega_ratio",
58
+ "information_ratio",
59
+ "capm_alpha_beta",
60
+ "skewness",
61
+ "kurtosis",
62
+ "variance_ratio",
63
+ "acf",
64
+ "pacf",
65
+ "fama_french_3factor",
66
+ "fama_french_5factor",
67
+ "cumulative_excess_return",
68
+ "hit_rate",
69
+ "average_win_loss",
70
+ "expectancy",
71
+ "profit_factor",
72
+ "trade_duration_distribution",
73
+ "turnover",
74
+ "trade_implementation_shortfall",
75
+ "cumulative_implementation_shortfall",
76
+ "slippage_stats",
77
+ "latency_stats",
78
+ ]
79
+
80
+ try: # pragma: no cover - optional plotting deps
81
+ from .plots import (
82
+ plot_equity_curve,
83
+ plot_return_heatmap,
84
+ plot_underwater,
85
+ plot_rolling_volatility,
86
+ plot_rolling_var,
87
+ plot_rolling_sharpe,
88
+ plot_rolling_sortino,
89
+ plot_return_scatter,
90
+ plot_cumulative_excess_return,
91
+ plot_factor_exposures,
92
+ plot_trade_return_hist,
93
+ plot_return_by_holding_period,
94
+ plot_exposure_ts,
95
+ plot_cumulative_shortfall,
96
+ plot_alpha_vs_return,
97
+ plot_qq_returns,
98
+ plot_rolling_skewness,
99
+ plot_rolling_kurtosis,
100
+ )
101
+
102
+ __all__ += [
103
+ "plot_equity_curve",
104
+ "plot_return_heatmap",
105
+ "plot_underwater",
106
+ "plot_rolling_volatility",
107
+ "plot_rolling_var",
108
+ "plot_rolling_sharpe",
109
+ "plot_rolling_sortino",
110
+ "plot_return_scatter",
111
+ "plot_cumulative_excess_return",
112
+ "plot_factor_exposures",
113
+ "plot_trade_return_hist",
114
+ "plot_return_by_holding_period",
115
+ "plot_exposure_ts",
116
+ "plot_cumulative_shortfall",
117
+ "plot_alpha_vs_return",
118
+ "plot_qq_returns",
119
+ "plot_rolling_skewness",
120
+ "plot_rolling_kurtosis",
121
+ ]
122
+ except Exception: # pragma: no cover - matplotlib may be missing
123
+ pass