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.
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/PKG-INFO +62 -2
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/README.md +59 -1
- pwb_toolbox-0.1.9/pwb_toolbox/backtest/__init__.py +3 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox/backtest/base_strategy.py +1 -1
- pwb_toolbox-0.1.9/pwb_toolbox/backtest/engine.py +33 -0
- pwb_toolbox-0.1.9/pwb_toolbox/backtest/ib_connector.py +69 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox/datasets/__init__.py +7 -4
- pwb_toolbox-0.1.9/pwb_toolbox/performance/__init__.py +123 -0
- pwb_toolbox-0.1.9/pwb_toolbox/performance/metrics.py +465 -0
- pwb_toolbox-0.1.9/pwb_toolbox/performance/plots.py +415 -0
- pwb_toolbox-0.1.9/pwb_toolbox/performance/trade_stats.py +138 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/PKG-INFO +62 -2
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/SOURCES.txt +8 -5
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/requires.txt +2 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/setup.cfg +3 -1
- pwb_toolbox-0.1.9/tests/test_backtest.py +29 -0
- pwb_toolbox-0.1.9/tests/test_hf_token.py +24 -0
- pwb_toolbox-0.1.9/tests/test_ib_connector.py +31 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/tests/test_universe_models.py +5 -0
- pwb_toolbox-0.1.7/pwb_toolbox/backtest/__init__.py +0 -51
- pwb_toolbox-0.1.7/pwb_toolbox/backtest/execution_models/__init__.py +0 -153
- pwb_toolbox-0.1.7/pwb_toolbox/backtest/insight.py +0 -21
- pwb_toolbox-0.1.7/pwb_toolbox/backtest/portfolio_models/__init__.py +0 -290
- pwb_toolbox-0.1.7/pwb_toolbox/backtest/risk_models/__init__.py +0 -175
- pwb_toolbox-0.1.7/pwb_toolbox/backtest/universe_models/__init__.py +0 -183
- pwb_toolbox-0.1.7/tests/test_backtest.py +0 -12
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/LICENSE.txt +0 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox/__init__.py +0 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/dependency_links.txt +0 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pwb_toolbox.egg-info/top_level.txt +0 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/pyproject.toml +0 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/tests/test_execution_models.py +0 -0
- {pwb_toolbox-0.1.7 → pwb_toolbox-0.1.9}/tests/test_portfolio_models.py +0 -0
- {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.
|
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
|
-
|
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
|
-
|
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,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
|
-
|
10
|
-
|
11
|
-
|
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=
|
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
|