You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#### Weighted multi-metric ranking with `BacktestEvaluationFocus`
347
+
348
+
Instead of sorting by a single column, use a **focus preset** to score every bundle across multiple metrics at once — profit, risk, consistency, win rate — weighted by what matters most to your workflow:
349
+
350
+
```python
351
+
from investing_algorithm_framework import (
352
+
BacktestReport, BacktestEvaluationFocus,
353
+
)
354
+
from investing_algorithm_framework.cli.index_command import (
355
+
build_index, rank_index,
356
+
)
357
+
from investing_algorithm_framework.services.backtest_store import (
358
+
LocalDirStore,
359
+
)
360
+
361
+
# 1. Build (or refresh) the Tier-1 SQLite index.
362
+
build_index("./my-backtests/")
363
+
364
+
# 2. Rank with a built-in focus preset (BALANCED, PROFIT, FREQUENCY, RISK_ADJUSTED).
Volume-aware model with quadratic price impact. Limits fills to a fraction of bar volume, producing partial fills for large orders relative to liquidity.
81
+
82
+
```python
83
+
from investing_algorithm_framework import SimulationBlotter, VolumeShareSlippage
84
+
85
+
app.set_blotter(SimulationBlotter(
86
+
slippage_model=VolumeShareSlippage(
87
+
volume_limit=0.025, # max 2.5% of bar volume
88
+
price_impact=0.1, # quadratic impact coefficient
89
+
)
90
+
))
91
+
```
92
+
93
+
### FixedBasisPointsSlippage
94
+
95
+
Slippage expressed in basis points (1 bp = 0.01 % of price).
96
+
97
+
```python
98
+
from investing_algorithm_framework import SimulationBlotter, FixedBasisPointsSlippage
Slippage models can also be attached directly to a `TradingCost` via the `slippage_model` parameter, without needing a custom blotter. See [TradingCost — Slippage Models](../Risk%20Rules/trading-cost.md#slippage-models) for details.
Copy file name to clipboardExpand all lines: docusaurus/docs/Risk Rules/overview.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -66,7 +66,7 @@ class MyStrategy(TradingStrategy):
66
66
|`take_profits`|[`TakeProfitRule`](./take-profit-rule.md)| Bar-end exit when price rises a fixed or trailing percentage from entry / peak. |
67
67
|`scaling_rules`|[`ScalingRule`](./scaling-rule.md)| Pyramid into winners and partially close — `max_entries`, `scale_in_percentage`, `scale_out_percentage`, optional `max_position_percentage` cap. |
68
68
|`cooldowns`|[`CooldownRule`](./cooldown-rule.md)| Side-aware, per-symbol or portfolio-wide signal throttling after fills. |
69
-
|`trading_costs`|[`TradingCost`](./trading-cost.md)| Per-symbol fees and slippage applied during fill simulation. |
69
+
|`trading_costs`|[`TradingCost`](./trading-cost.md)| Per-symbol fees and slippage applied during fill simulation. Supports [pluggable slippage models](./trading-cost.md#slippage-models) (volume-based, fixed spread, basis points). |
Copy file name to clipboardExpand all lines: docusaurus/docs/Risk Rules/trading-cost.md
+134-1Lines changed: 134 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,15 +18,17 @@ TradingCost(
18
18
fee_percentage: float=0.0,
19
19
slippage_percentage: float=0.0,
20
20
fee_fixed: float=0.0,
21
+
slippage_model: SlippageModel |None=None,
21
22
)
22
23
```
23
24
24
25
| Parameter | Type | Default | Description |
25
26
|---|---|---|---|
26
27
|`symbol`|`str \| None`|`None`| Target symbol (e.g. `"BTC"`). `None` means "market default" when used at the portfolio level. Symbol matching is case-insensitive. |
27
28
|`fee_percentage`|`float`|`0.0`| Variable fee in **percent of trade value** (e.g. `0.1` = 0.1 %). |
28
-
|`slippage_percentage`|`float`|`0.0`| Slippage in **percent of price**. Buys fill higher, sells fill lower. |
29
+
|`slippage_percentage`|`float`|`0.0`| Slippage in **percent of price**. Buys fill higher, sells fill lower. Ignored when `slippage_model` is set. |
29
30
|`fee_fixed`|`float`|`0.0`| Flat fee per trade in the trading currency, added on top of `fee_percentage`. |
31
+
|`slippage_model`|`SlippageModel \| None`|`None`| Pluggable slippage model. When set, **overrides**`slippage_percentage`. See [Slippage Models](#slippage-models) below. |
When a `slippage_model` is set, the model's `calculate_slippage()` method replaces the percentage formula above. The fee calculation stays the same.
45
+
42
46
`trade_value` is computed at the slippage-adjusted price, so fees compound on top of slippage — matching how real exchanges quote post-trade cost.
43
47
44
48
## Resolution Order
@@ -118,8 +122,137 @@ PortfolioConfiguration(
118
122
-**`StopLossRule` / `TakeProfitRule`** — slippage is applied to the exit fill (`sell` direction), so reported PnL already reflects realistic exits.
119
123
-**`ScalingRule`** — every scale-in and scale-out is a separate fill and pays fees independently.
120
124
125
+
## Slippage Models
126
+
127
+
The `slippage_model` parameter lets you plug in sophisticated slippage behavior that goes beyond a flat percentage. When set, it **overrides** the `slippage_percentage` field.
128
+
129
+
```python
130
+
from investing_algorithm_framework import (
131
+
TradingCost,
132
+
VolumeShareSlippage,
133
+
FixedSlippage,
134
+
FixedBasisPointsSlippage,
135
+
)
136
+
```
137
+
138
+
### VolumeShareSlippage
139
+
140
+
Models slippage as a function of the order's share of bar volume with a **quadratic** price impact. Also enforces a volume limit — at most `volume_limit` fraction of a bar's volume can be filled per bar. Orders exceeding this limit are partially filled and re-evaluated on subsequent bars.
141
+
142
+
```python
143
+
TradingCost(
144
+
symbol="BTC",
145
+
fee_percentage=0.1,
146
+
slippage_model=VolumeShareSlippage(
147
+
volume_limit=0.025, # max 2.5% of bar volume
148
+
price_impact=0.1, # price impact coefficient
149
+
),
150
+
)
151
+
```
152
+
153
+
| Parameter | Default | Description |
154
+
|---|---|---|
155
+
|`volume_limit`|`0.025`| Max fraction of bar volume that can fill per bar (0.025 = 2.5 %). |
This is the most realistic built-in model — strategies that trade illiquid assets or large positions relative to volume will see significant market impact, and orders larger than the volume limit will be partially filled.
169
+
170
+
### FixedSlippage
171
+
172
+
Adds or subtracts a fixed amount from the order price. Useful for markets with a known, relatively stable spread.
|`calculate_slippage(price, order_side, amount, volume)`| Yes | Return the adjusted fill price after slippage. |
237
+
|`max_fill_amount(order_amount, volume)`| No | Return the maximum fillable amount per bar. Default returns the full `order_amount` (no volume limit). |
238
+
239
+
### Choosing a Slippage Approach
240
+
241
+
| Approach | When to use |
242
+
|---|---|
243
+
|`slippage_percentage`| Quick approximation, don't need volume awareness. |
244
+
|`FixedSlippage`| Known fixed spread (e.g. a specific venue). |
245
+
|`FixedBasisPointsSlippage`| Proportional slippage in familiar units (basis points). |
246
+
|`VolumeShareSlippage`| Realistic simulation — large orders impact price, fills are volume-limited. |
247
+
| Custom `SlippageModel`| Any other behavior (e.g. time-of-day effects, asymmetric slippage). |
248
+
249
+
:::tip Backward Compatibility
250
+
Setting `slippage_model` is fully optional. Existing strategies using `slippage_percentage` continue to work unchanged.
Copy file name to clipboardExpand all lines: examples/tutorial/notebooks/03_param_sweep.ipynb
+16-5Lines changed: 16 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -484,7 +484,14 @@
484
484
"id": "9",
485
485
"metadata": {},
486
486
"source": [
487
-
"## Quick filter via SQLite index & open report"
487
+
"## Pruning & Ranking Backtests\n",
488
+
"\n",
489
+
"After the parameter sweep completes, we often have dozens (or hundreds) of backtest bundles on disk. Rather than decoding every `.iafbt` file to compare them, we use the **Tier-1 SQLite index** to rank and prune in milliseconds:\n",
490
+
"\n",
491
+
"1. **`build_index()`** — scans the results directory and promotes every scalar metric from each bundle into a single SQLite table (one row per backtest). This only touches the lightweight Parquet header, so it scales to 10k+ bundles.\n",
492
+
"2. **`rank_index()`** — runs a pure-SQL sort/filter on that index. Here we rank by Sharpe ratio and require at least 5 closed trades, returning the top 20 instantly — no full bundle decode needed.\n",
493
+
"3. **`prune_backtests()`** — moves every bundle *not* in the top-N list to an archive folder (`_pruned/`). With `flatten=True` the archive is a flat directory for easy browsing. Bundles are never deleted, so you can always recover them.\n",
494
+
"4. **`LocalDirStore.open()`** — materialises only the winning bundles into full `Backtest` objects for the HTML dashboard."
0 commit comments