pwb-toolbox 0.1.6__tar.gz → 0.1.8__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 (32) hide show
  1. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/PKG-INFO +78 -3
  2. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/README.md +73 -2
  3. pwb_toolbox-0.1.8/pwb_toolbox/backtest/__init__.py +57 -0
  4. pwb_toolbox-0.1.8/pwb_toolbox/backtest/base_strategy.py +33 -0
  5. pwb_toolbox-0.1.8/pwb_toolbox/backtest/execution_models/__init__.py +153 -0
  6. pwb_toolbox-0.1.8/pwb_toolbox/backtest/ib_connector.py +69 -0
  7. pwb_toolbox-0.1.8/pwb_toolbox/backtest/insight.py +21 -0
  8. pwb_toolbox-0.1.8/pwb_toolbox/backtest/portfolio_models/__init__.py +290 -0
  9. pwb_toolbox-0.1.8/pwb_toolbox/backtest/risk_models/__init__.py +175 -0
  10. pwb_toolbox-0.1.8/pwb_toolbox/backtest/universe_models/__init__.py +183 -0
  11. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/pwb_toolbox/datasets/__init__.py +8 -5
  12. pwb_toolbox-0.1.8/pwb_toolbox/performance/__init__.py +123 -0
  13. pwb_toolbox-0.1.8/pwb_toolbox/performance/metrics.py +465 -0
  14. pwb_toolbox-0.1.8/pwb_toolbox/performance/plots.py +415 -0
  15. pwb_toolbox-0.1.8/pwb_toolbox/performance/trade_stats.py +138 -0
  16. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/pwb_toolbox.egg-info/PKG-INFO +78 -3
  17. pwb_toolbox-0.1.8/pwb_toolbox.egg-info/SOURCES.txt +30 -0
  18. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/pwb_toolbox.egg-info/requires.txt +2 -0
  19. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/setup.cfg +6 -2
  20. pwb_toolbox-0.1.8/tests/test_backtest.py +29 -0
  21. pwb_toolbox-0.1.8/tests/test_execution_models.py +114 -0
  22. pwb_toolbox-0.1.8/tests/test_hf_token.py +24 -0
  23. pwb_toolbox-0.1.8/tests/test_ib_connector.py +31 -0
  24. pwb_toolbox-0.1.8/tests/test_portfolio_models.py +111 -0
  25. pwb_toolbox-0.1.8/tests/test_risk_models.py +77 -0
  26. pwb_toolbox-0.1.8/tests/test_universe_models.py +54 -0
  27. pwb_toolbox-0.1.6/pwb_toolbox.egg-info/SOURCES.txt +0 -11
  28. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/LICENSE.txt +0 -0
  29. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/pwb_toolbox/__init__.py +0 -0
  30. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/pwb_toolbox.egg-info/dependency_links.txt +0 -0
  31. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/pwb_toolbox.egg-info/top_level.txt +0 -0
  32. {pwb_toolbox-0.1.6 → pwb_toolbox-0.1.8}/pyproject.toml +0 -0
@@ -1,19 +1,23 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pwb-toolbox
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: A toolbox library for quant traders
5
5
  Home-page: https://github.com/paperswithbacktest/pwb-toolbox
6
6
  Author: Your Name
7
7
  Author-email: hello@paperswithbacktest.com
8
8
  License: MIT
9
9
  Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
10
12
  Classifier: License :: OSI Approved :: MIT License
11
13
  Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.7
14
+ Requires-Python: >=3.10
13
15
  Description-Content-Type: text/markdown
14
16
  License-File: LICENSE.txt
15
17
  Requires-Dist: datasets
16
18
  Requires-Dist: pandas
19
+ Requires-Dist: ibapi
20
+ Requires-Dist: ib_insync
17
21
  Dynamic: license-file
18
22
 
19
23
  <div align="center">
@@ -31,6 +35,7 @@ To install the pwb-toolbox package:
31
35
  ```bash
32
36
  pip install pwb-toolbox
33
37
  ```
38
+ This package requires Python 3.10 or higher.
34
39
 
35
40
  To login to Huggingface Hub with Access Token
36
41
 
@@ -116,6 +121,77 @@ df = pwb_ds.load_dataset(
116
121
  )
117
122
  ```
118
123
 
124
+ ## Backtest engine
125
+
126
+ The `pwb_toolbox.backtest` module offers simple building blocks for running
127
+ Backtrader simulations. Alpha models generate `Insight` objects which are turned
128
+ into portfolio weights and executed via Backtrader orders.
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.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
+ )
166
+
167
+ print("Total return:", total_return(equity))
168
+ print("CAGR:", cagr(equity))
169
+
170
+ plot_equity_curve(equity)
171
+ ```
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
+
119
195
  ## Contributing
120
196
 
121
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:
@@ -150,5 +226,4 @@ The `pwb-toolbox` package is released under the MIT license. See the LICENSE fil
150
226
  ## Contact
151
227
 
152
228
  For any questions, issues, or suggestions regarding the `pwb-toolbox` package, please contact the maintainers or create an issue on the repository. We appreciate your feedback and involvement in improving the package.
153
-
154
229
  Happy trading!
@@ -13,6 +13,7 @@ To install the pwb-toolbox package:
13
13
  ```bash
14
14
  pip install pwb-toolbox
15
15
  ```
16
+ This package requires Python 3.10 or higher.
16
17
 
17
18
  To login to Huggingface Hub with Access Token
18
19
 
@@ -98,6 +99,77 @@ df = pwb_ds.load_dataset(
98
99
  )
99
100
  ```
100
101
 
102
+ ## Backtest engine
103
+
104
+ The `pwb_toolbox.backtest` module offers simple building blocks for running
105
+ Backtrader simulations. Alpha models generate `Insight` objects which are turned
106
+ into portfolio weights and executed via Backtrader orders.
107
+
108
+ ```python
109
+ from pwb_toolbox.backtest.examples import GoldenCrossAlpha, EqualWeightPortfolio
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
+ )
144
+
145
+ print("Total return:", total_return(equity))
146
+ print("CAGR:", cagr(equity))
147
+
148
+ plot_equity_curve(equity)
149
+ ```
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
+
101
173
  ## Contributing
102
174
 
103
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:
@@ -132,5 +204,4 @@ The `pwb-toolbox` package is released under the MIT license. See the LICENSE fil
132
204
  ## Contact
133
205
 
134
206
  For any questions, issues, or suggestions regarding the `pwb-toolbox` package, please contact the maintainers or create an issue on the repository. We appreciate your feedback and involvement in improving the package.
135
-
136
- Happy trading!
207
+ Happy trading!
@@ -0,0 +1,57 @@
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
+ from .ib_connector import IBConnector, run_ib_strategy
18
+ from .example.engine import run_backtest, run_ib_backtest
19
+
20
+ __all__ = [
21
+ "Direction",
22
+ "Insight",
23
+ "PortfolioConstructionModel",
24
+ "EqualWeightingPortfolioConstructionModel",
25
+ "InsightWeightingPortfolioConstructionModel",
26
+ "MeanVarianceOptimizationPortfolioConstructionModel",
27
+ "BlackLittermanOptimizationPortfolioConstructionModel",
28
+ "RiskParityPortfolioConstructionModel",
29
+ "UnconstrainedMeanVariancePortfolioConstructionModel",
30
+ "TargetPercentagePortfolioConstructionModel",
31
+ "DollarCostAveragingPortfolioConstructionModel",
32
+ "InsightRatioPortfolioConstructionModel",
33
+ "RiskManagementModel",
34
+ "TrailingStopRiskManagementModel",
35
+ "MaximumDrawdownPercentPerSecurity",
36
+ "MaximumDrawdownPercentPortfolio",
37
+ "MaximumUnrealizedProfitPercentPerSecurity",
38
+ "MaximumTotalPortfolioExposure",
39
+ "SectorExposureRiskManagementModel",
40
+ "MaximumOrderQuantityPercentPerSecurity",
41
+ "CompositeRiskManagementModel",
42
+ "IBConnector",
43
+ "run_ib_strategy",
44
+ "run_backtest",
45
+ "run_ib_backtest",
46
+ ]
47
+ from .risk_models import (
48
+ RiskManagementModel,
49
+ TrailingStopRiskManagementModel,
50
+ MaximumDrawdownPercentPerSecurity,
51
+ MaximumDrawdownPercentPortfolio,
52
+ MaximumUnrealizedProfitPercentPerSecurity,
53
+ MaximumTotalPortfolioExposure,
54
+ SectorExposureRiskManagementModel,
55
+ MaximumOrderQuantityPercentPerSecurity,
56
+ CompositeRiskManagementModel,
57
+ )
@@ -0,0 +1,33 @@
1
+ import backtrader as bt
2
+ from tqdm import tqdm
3
+
4
+
5
+ class BaseStrategy(bt.Strategy):
6
+ """Base strategy providing progress logging utilities."""
7
+
8
+ params = (("total_days", 0),)
9
+
10
+ def __init__(self):
11
+ super().__init__()
12
+ self.pbar = tqdm(total=self.params.total_days)
13
+ self.log_data = []
14
+
15
+ def is_tradable(self, data):
16
+ """Return True if the instrument's price is not constant."""
17
+ if len(data.close) < 3:
18
+ return False
19
+ return data.close[0] != data.close[-2]
20
+
21
+ def __next__(self):
22
+ """Update progress bar and log current value."""
23
+ self.pbar.update(1)
24
+ self.log_data.append(
25
+ {
26
+ "date": self.datas[0].datetime.date(0).isoformat(),
27
+ "value": self.broker.getvalue(),
28
+ }
29
+ )
30
+
31
+ def get_latest_positions(self):
32
+ """Get a dictionary of the latest positions."""
33
+ return {data._name: self.broker.getposition(data).size for data in self.datas}
@@ -0,0 +1,153 @@
1
+ """Execution models for order placement using Backtrader."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Dict
6
+ import backtrader as bt
7
+
8
+
9
+ class ExecutionModel:
10
+ """Base execution model class."""
11
+
12
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
13
+ """Place orders on the given strategy."""
14
+ raise NotImplementedError
15
+
16
+
17
+ class ImmediateExecutionModel(ExecutionModel):
18
+ """Immediately send market orders using ``order_target_percent``."""
19
+
20
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
21
+ for data in strategy.datas:
22
+ target = weights.get(data._name, 0.0)
23
+ strategy.order_target_percent(data=data, target=target)
24
+
25
+
26
+ class StandardDeviationExecutionModel(ExecutionModel):
27
+ """Only trade when recent volatility exceeds a threshold."""
28
+
29
+ def __init__(self, lookback: int = 20, threshold: float = 0.01):
30
+ self.lookback = lookback
31
+ self.threshold = threshold
32
+
33
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
34
+ prices = strategy.prices
35
+ for data in strategy.datas:
36
+ symbol = data._name
37
+ series = prices[symbol]["close"] if (symbol, "close") in prices.columns else prices[symbol]
38
+ if len(series) < self.lookback:
39
+ continue
40
+ vol = series.pct_change().rolling(self.lookback).std().iloc[-1]
41
+ if vol is not None and vol > self.threshold:
42
+ target = weights.get(symbol, 0.0)
43
+ strategy.order_target_percent(data=data, target=target)
44
+
45
+
46
+ class VolumeWeightedAveragePriceExecutionModel(ExecutionModel):
47
+ """Split orders evenly over a number of steps to approximate VWAP."""
48
+
49
+ def __init__(self, steps: int = 3):
50
+ self.steps = steps
51
+ self._progress: Dict[str, int] = {}
52
+
53
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
54
+ for data in strategy.datas:
55
+ symbol = data._name
56
+ step = self._progress.get(symbol, 0)
57
+ if step >= self.steps:
58
+ continue
59
+ target = weights.get(symbol, 0.0) * (step + 1) / self.steps
60
+ strategy.order_target_percent(data=data, target=target)
61
+ self._progress[symbol] = step + 1
62
+
63
+
64
+ class VolumePercentageExecutionModel(ExecutionModel):
65
+ """Execute only a percentage of the target each call."""
66
+
67
+ def __init__(self, percentage: float = 0.25):
68
+ self.percentage = percentage
69
+ self._filled: Dict[str, float] = {}
70
+
71
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
72
+ for data in strategy.datas:
73
+ symbol = data._name
74
+ current = self._filled.get(symbol, 0.0)
75
+ target = weights.get(symbol, 0.0)
76
+ remaining = target - current
77
+ if abs(remaining) < 1e-6:
78
+ continue
79
+ step_target = current + remaining * self.percentage
80
+ self._filled[symbol] = step_target
81
+ strategy.order_target_percent(data=data, target=step_target)
82
+
83
+
84
+ class TimeProfileExecutionModel(ExecutionModel):
85
+ """Execute orders based on a predefined time profile (e.g. TWAP)."""
86
+
87
+ def __init__(self, profile: Dict[int, float] | None = None):
88
+ self.profile = profile or {0: 1.0}
89
+ self._called = 0
90
+
91
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
92
+ factor = self.profile.get(self._called, 0.0)
93
+ for data in strategy.datas:
94
+ target = weights.get(data._name, 0.0) * factor
95
+ strategy.order_target_percent(data=data, target=target)
96
+ self._called += 1
97
+
98
+
99
+ class TrailingLimitExecutionModel(ExecutionModel):
100
+ """Use trailing limit orders for execution."""
101
+
102
+ def __init__(self, trail: float = 0.01):
103
+ self.trail = trail
104
+
105
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
106
+ prices = strategy.prices
107
+ for data in strategy.datas:
108
+ symbol = data._name
109
+ price = prices[symbol]["close"].iloc[-1] if (symbol, "close") in prices.columns else prices[symbol].iloc[-1]
110
+ target = weights.get(symbol, 0.0)
111
+ if target > 0:
112
+ strategy.buy(data=data, exectype=bt.Order.Limit, price=price * (1 - self.trail))
113
+ elif target < 0:
114
+ strategy.sell(data=data, exectype=bt.Order.Limit, price=price * (1 + self.trail))
115
+
116
+
117
+ class AdaptiveExecutionModel(ExecutionModel):
118
+ """Switch between immediate and VWAP execution based on volatility."""
119
+
120
+ def __init__(self, threshold: float = 0.02, steps: int = 3):
121
+ self.threshold = threshold
122
+ self.vwap = VolumeWeightedAveragePriceExecutionModel(steps=steps)
123
+ self.immediate = ImmediateExecutionModel()
124
+
125
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
126
+ prices = strategy.prices
127
+ for data in strategy.datas:
128
+ symbol = data._name
129
+ series = prices[symbol]["close"] if (symbol, "close") in prices.columns else prices[symbol]
130
+ if len(series) < 2:
131
+ continue
132
+ vol = series.pct_change().iloc[-1]
133
+ if abs(vol) > self.threshold:
134
+ self.immediate.execute(strategy, {symbol: weights.get(symbol, 0.0)})
135
+ else:
136
+ self.vwap.execute(strategy, {symbol: weights.get(symbol, 0.0)})
137
+
138
+
139
+ class BufferedExecutionModel(ExecutionModel):
140
+ """Only execute when target differs sufficiently from last order."""
141
+
142
+ def __init__(self, buffer: float = 0.05):
143
+ self.buffer = buffer
144
+ self._last: Dict[str, float] = {}
145
+
146
+ def execute(self, strategy: bt.Strategy, weights: Dict[str, float]):
147
+ for data in strategy.datas:
148
+ symbol = data._name
149
+ target = weights.get(symbol, 0.0)
150
+ last = self._last.get(symbol)
151
+ if last is None or abs(target - last) > self.buffer:
152
+ strategy.order_target_percent(data=data, target=target)
153
+ self._last[symbol] = target
@@ -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()
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum, auto
3
+ from datetime import datetime
4
+
5
+
6
+ class Direction(Enum):
7
+ """Possible directions for an Insight."""
8
+
9
+ UP = auto()
10
+ DOWN = auto()
11
+ FLAT = auto()
12
+
13
+
14
+ @dataclass
15
+ class Insight:
16
+ """Simple trading signal produced by an Alpha model."""
17
+
18
+ symbol: str
19
+ direction: Direction
20
+ timestamp: datetime
21
+ weight: float = 1.0