pwb-toolbox 0.1.7__py3-none-any.whl → 0.1.9__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,51 +1,3 @@
1
1
  from .base_strategy import BaseStrategy
2
-
3
- from .insight import Direction, Insight
4
-
5
- from .portfolio_models import (
6
- PortfolioConstructionModel,
7
- EqualWeightingPortfolioConstructionModel,
8
- InsightWeightingPortfolioConstructionModel,
9
- MeanVarianceOptimizationPortfolioConstructionModel,
10
- BlackLittermanOptimizationPortfolioConstructionModel,
11
- RiskParityPortfolioConstructionModel,
12
- UnconstrainedMeanVariancePortfolioConstructionModel,
13
- TargetPercentagePortfolioConstructionModel,
14
- DollarCostAveragingPortfolioConstructionModel,
15
- InsightRatioPortfolioConstructionModel,
16
- )
17
-
18
- __all__ = [
19
- "Direction",
20
- "Insight",
21
- "PortfolioConstructionModel",
22
- "EqualWeightingPortfolioConstructionModel",
23
- "InsightWeightingPortfolioConstructionModel",
24
- "MeanVarianceOptimizationPortfolioConstructionModel",
25
- "BlackLittermanOptimizationPortfolioConstructionModel",
26
- "RiskParityPortfolioConstructionModel",
27
- "UnconstrainedMeanVariancePortfolioConstructionModel",
28
- "TargetPercentagePortfolioConstructionModel",
29
- "DollarCostAveragingPortfolioConstructionModel",
30
- "InsightRatioPortfolioConstructionModel",
31
- "RiskManagementModel",
32
- "TrailingStopRiskManagementModel",
33
- "MaximumDrawdownPercentPerSecurity",
34
- "MaximumDrawdownPercentPortfolio",
35
- "MaximumUnrealizedProfitPercentPerSecurity",
36
- "MaximumTotalPortfolioExposure",
37
- "SectorExposureRiskManagementModel",
38
- "MaximumOrderQuantityPercentPerSecurity",
39
- "CompositeRiskManagementModel",
40
- ]
41
- from .risk_models import (
42
- RiskManagementModel,
43
- TrailingStopRiskManagementModel,
44
- MaximumDrawdownPercentPerSecurity,
45
- MaximumDrawdownPercentPortfolio,
46
- MaximumUnrealizedProfitPercentPerSecurity,
47
- MaximumTotalPortfolioExposure,
48
- SectorExposureRiskManagementModel,
49
- MaximumOrderQuantityPercentPerSecurity,
50
- CompositeRiskManagementModel,
51
- )
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