A playbook — named mixes plus rules that switch between them on asset or macro signals, with cash and 'stay put' as targets — through every future, exactly as the playground runs it. It returns the playbook's outcome bands, its base mix held alone, and the comparison that matters: the playbook against its own average mix held still, which separates what the timing added from simply holding a different allocation.
- playbookobject
- { mixes, baseMixId, rules, start?, otherwise? } — up to four mixes and five rules; each rule's conditions must all hold, and the first rule that fires wins.
- checkEverystringoptional
- daily · weekly · monthly · quarterly · yearly. How often the rules are checked. Default monthly.
- slippageBpsnumberoptional
- All-in cost of each trade (spread, market impact, commissions) in bp of the amount traded. Default 25, as in the playground.
- spreadBpsnumberoptional
- What borrowing costs over the cash rate, in bp a year. Default 100. Matters only when levered.
- baseRebalancestringoptional
- How the base mix is rebalanced when held alone for comparison. Default: checkEvery.
- initialnumberoptional
- Starting value. Default 10000.
what it sends
{
"playbook": {
"mixes": [
{ "id": "growth", "name": "Growth", "weights": { "SPY": 0.8, "AGG": 0.2 } },
{ "id": "defensive", "name": "Defensive", "weights": { "AGG": 0.5, "GLD": 0.3, "SHY": 0.2 } }
],
"baseMixId": "growth",
"rules": [
{ "id": "fear", "when": [{ "kind": "macro", "var": "vix", "op": "above", "threshold": 30 }],
"holdMixId": "defensive" }
]
},
"checkEvery": "monthly"
}what comes back
{
"checkEvery": "monthly",
"epochs": 400,
"stats": {
"finalValue": { "p5": 7506.74, "p25": 12501.33, "p50": 16644.95, "p75": 23185.31, "p95": 36986.12 },
"maxDrawdown": { "p5": 0.1377, "p25": 0.2067, "p50": 0.2673, "p75": 0.3561, "p95": 0.5384 },
"turnover": { "p5": 0.0504, "p25": 0.2094, "p50": 0.3762, "p75": 0.6841, "p95": 1.1682 },
… cagr, vol and sharpe …
},
"base": {
"name": "Growth", "rebalance": "monthly",
"stats": { "finalValue": { "p5": 8056.81, "p50": 19027.69, "p95": 42332.74, … }, … }
},
"rulesImpact": {
"epochs": 400, "helped": 39,
"delta": { "p5": -7785.73, "p50": -1220.21, "p95": 447.98 },
"finalWith": 16644.95, "finalWithout": 19027.69
},
"timing": {
"switched": true,
"averageMix": { "SPY": 0.7732, "AGG": 0.21, "GLD": 0.01, "SHY": 0.0067 },
"leverage": 1,
"vsAverage": {
"epochs": 400, "helped": 84,
"delta": { "p5": -7291.86, "p50": -1145.53, "p95": 1089.08 },
"finalWith": 16644.95, "finalWithout": 19016.11
},
"averageVsBase": -46.62,
"maxDD95": { "playbook": 0.5384, "averageMix": 0.4915 }
},
"costs": { "slippageBps": 25, "spreadBps": 100 },
"fan": [ { "date": "2026-10-15", "p5": 10000, "p50": 10000, "p95": 10000 }, … 24 points … ]
}Reading it
Judge the rules by `timing.vsAverage`: the playbook against its own average mix held still, through the same futures at the same costs. `rulesImpact` compares with the base mix, and a playbook that shelters in bonds or cash holds less stock than the base on average -- beating the base on drawdown then says more about that lower exposure than about the timing. If `timing.switched` is false the rules never changed the holding. Here the switching cost money: the playbook beat its own average mix in 84 of 400 futures, by a median of −1,146, and its worst 5% of drawdowns ran deeper (54% against 49%).
Counts as three calls against the rate limit — it runs the ensemble three times: the playbook, its base mix, and its average mix.