@battlegrid/mcp-server 31.0.3 → 31.1.5

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.
@@ -0,0 +1,254 @@
1
+ ---
2
+ name: battlegrid-strategy-studio
3
+ description: Author full-power BattleGrid trading strategies over MCP — multi-section reports, benchmark sections, layered conditions with verdicts and enforcement gates, tiered signal weights, routing gates, ATR trade levels, and post-entry position management. Use whenever building or upgrading a strategy so it uses the whole studio, not a bare template. Companion to the `battlegrid` skill, which owns connection and the compile → review → apply workflow.
4
+ ---
5
+
6
+ # BattleGrid Strategy Studio — full-power authoring
7
+
8
+ A default strategy — a few platform sections, no conditions, untouched weights — wastes the
9
+ studio. This skill teaches every axis the strategy aggregate owns and how professional desks
10
+ compose them. Workflow, envelopes, and error recovery live in the `battlegrid` skill; this one
11
+ is about *what to author*.
12
+
13
+ **Ground rule: shapes here are binding, tokens are illustrative.** Every metric code, signal id,
14
+ header, and bound in this skill was validated against the live server, but the server's
15
+ vocabulary moves with deployments. Before compiling, re-discover (`list_strategy_categories` →
16
+ `list_strategy_vocabulary` → `get_metric_construction_hints` → `get_strategy_column_contract`,
17
+ `list_strategy_signals` → `get_strategy_signal_definition`) and prefer what discovery returns
18
+ over anything printed here.
19
+
20
+ ## What a strategy owns (author all of it, deliberately)
21
+
22
+ | Axis | Fields | What it does |
23
+ |---|---|---|
24
+ | Identity | `name` ≤50, `tagline` ≤80, `description` ≤500 | How agents and humans find it |
25
+ | Timeframe | `timeframe` (from discovery's enabled list) | Anchor rung; cadence persona and regime rung derive from it |
26
+ | Report | `sections[]` — platform + custom columns | The per-coin table the agent LLM actually reads |
27
+ | Conditions | `conditions[]` — typed boolean trees | Deterministic verdicts (UP/DOWN/NEITHER) + hard enforcement gates |
28
+ | Signal rules | `rules[]` — `{signalId, allocation 0–3, required, params?}` | What scores, how much it counts, what must fire |
29
+ | Routing gates | `minAggregateScore` 0–1, `minRequiredCount` 0–20, `minAtrPct` | Whether a scored setup may route to a trade |
30
+ | Trade levels | `minStopLossAtrMultiple` < `maxStopLossAtrMultiple`, `minRiskRewardRatio` | Where stops/targets may sit |
31
+ | Position management | breakEven / trailing / timeDecay dials | How the stop moves after entry |
32
+ | Market Read | `marketReadText` ≤2000 with `{...}` markers | Standing orders rendered with live values |
33
+
34
+ A compile also always carries `intentSummary`, `assumptions[]`, and `coinSelection` — call
35
+ context, not strategy state (see the `battlegrid` skill).
36
+
37
+ ## The full-power checklist
38
+
39
+ Before compiling a CREATE, confirm all six; a "no" is a decision, not an omission:
40
+
41
+ 1. Report has at least one **custom section** whose columns encode the thesis (not only platform
42
+ modules), and every column earns its tokens.
43
+ 2. **Conditions** encode the entry logic deterministically — building blocks + verdict carriers —
44
+ and at least one `required: true` condition guards spend on obvious disqualifiers.
45
+ 3. **Every signal you want scoring is named in `rules`** with a deliberate tier; signals you do
46
+ not name keep server defaults (typically Off). Verify in the compiled scorecard, never assume.
47
+ 4. **Gates** are set against the weight budget you chose (see scoring math below).
48
+ 5. **Trade levels + position management** match the setup's geometry and holding period.
49
+ 6. `marketReadText` states the standing orders with markers so the agent sees live values inline.
50
+
51
+ ## Report grammar — sections and columns
52
+
53
+ A custom column is `metric × transform (± chained transform) × timeframe ref (± params)`.
54
+ Headers are **system-generated, never named by you**. Validated affix patterns:
55
+
56
+ | Transform | Header shape | Example (validated) |
57
+ |---|---|---|
58
+ | `value` | `<code>` | `bbWidthPct`, `RVOL`, `rate` |
59
+ | `trajectory` (window 4) | `<code>_t3 … _t1`, `<code>_now`, `<code>_trend` (rising/falling/flat) | `RSI14_now`, `RSI14_trend` |
60
+ | `distance` (price → level) | `dist_<code>` (signed %) | `dist_SMA50` |
61
+ | `spread` (base vs operand) | `<base>_<operand>_spread` | `mark_oracle_spread`, `EMA5_EMA13_spread` |
62
+ | `aggregate` (window N) | `<code>_mean<N>` | `rate_mean24` |
63
+ | `rank` (ordering hi/lo/far/near) | `<code>_rank_<ordering>` — ordinal, 1 = best; compare with `lte N` for top-N | `bbWidthPct_rank_lo`, `closeChg_rank_far` |
64
+ | `efficiency` (window N) | `<code>_er` (0–1; 1 = straight move, ~0 = chop) | `close_ltf_er` |
65
+ | `maxShare` (window N) | `<code>_maxShare` (0–1 concentration) | `volBase_ltf_maxShare` |
66
+ | `classifyZone` / `classifyState` | `<code>_zone` / `<code>_state` | `RSI14_zone` (overbought/oversold/neutral), `ADX_state` (weak/developing/trending/extreme) |
67
+
68
+ Non-anchor rungs add a rung affix: `_ltf` (lower) / `_htf` (regime), e.g. `MAalign_htf`,
69
+ `zones_htf_support_dist`. Chains are bounded at two: inner `distance`/`spread` → outer
70
+ `trajectory`/`aggregate`/`efficiency`/`maxShare`/`rank` (e.g. EMA5 `spread` EMA13 ×
71
+ `trajectory` → `EMA5_EMA13_spread_now` + `_trend`). Read exact headers from
72
+ `get_strategy_column_contract` or a `preview_strategy_report`'s `conditionColumns` before
73
+ writing conditions against them.
74
+
75
+ **Timeframe references — two families.** *Relative* (`{rel: "anchor" | "lower" | "regime"}`)
76
+ re-resolve when the strategy timeframe changes; `regime` is the anchor's ladder successor (a 4h
77
+ anchor's regime rung is 1d today). *Pinned* (`{abs: "<tf>"}`) is fixed and ignores anchor
78
+ retunes; its legal set is discovery's `rankedTimeframes` — a **superset** of the authorable
79
+ anchor set (`timeframes`), so `{abs: "1d"}` is valid while `1d` is not an anchor. Pinned
80
+ headers suffix the literal: `RSI14_1d`, `dist_SMA200_1d`, `MAalign_1d` (validated). `offset: 1`
81
+ on a pinned `value` column reads the **last closed** bar of that timeframe — the deterministic
82
+ daily-close read; offset does not change the header, so one section carries one offset per
83
+ `metric × timeframe`. This is how higher-timeframe theses (daily-chart strategies included)
84
+ are authored on an intraday anchor — see the daily pattern in `references/tradingview-ports.md`.
85
+
86
+ **Benchmark sections** (`benchmarkTicker: "BTC"` on a custom section) read the *benchmark's*
87
+ values instead of the evaluated coin's — the standard way to gate a whole book on market-leader
88
+ regime. `benchmarkTicker` is required-nullable on every custom section: send `null` for an
89
+ ordinary section, never omit it.
90
+
91
+ A bound section takes **indicator** columns only. Crowd/session metrics (the `CROWD_*` family,
92
+ `FLOW_ALIGN`, `SMART_RETAIL`, `CAPTAIN_CONF`, `CONFIDENCE`, `SETTLED_AT`, `PERP_SPOT_*`) and any
93
+ `rank` transform are refused there (contract 49), because both are defined relative to the cohort
94
+ being evaluated and a benchmark sits outside it — a crowd reading is what this session's players
95
+ did, a rank is a position among the coins under evaluation. Neither has a value for BTC-as-yardstick.
96
+ Put those columns on an ordinary section and `conditionRef` across.
97
+
98
+ Budgets are served by discovery (validated today: 32 sections, 32 custom columns, 8 distinct
99
+ timeframes, 16 conditions, 16 clauses, 16k estimated report tokens). `preview_strategy_report`
100
+ echoes your usage against each cap.
101
+
102
+ ## Conditions — deterministic logic over your own report
103
+
104
+ Each condition: `{ conditionKey, name, definition, verdict, required, exit, clock, closes }` —
105
+ all eight required, no defaults. The conditions axis saves by whole-set replacement, so an omitted
106
+ key would silently un-clock a money gate on the next unrelated edit rather than be refused.
107
+
108
+ - **Clauses** compare one column: numeric/rank headers take `lt|lte|gte|gt|between`;
109
+ classification/direction/event headers take `is|in` with the column's exact vocabulary
110
+ (served per header in `conditionColumns` / the column contract).
111
+ - **Groups**: `ALL`, `ANY`, `NOT`, `N_OF` (with `n`), depth ≤ 2 (an inner group holds leaves
112
+ only).
113
+ - **References** (`{kind:"conditionRef", conditionKey}`) compose named conditions; cycles are
114
+ rejected, forward references are legal.
115
+ - **Column addressing**: `{sectionKey, header}`. `sectionKey: null` is authoring sugar for a
116
+ header unique across the whole report; a duplicated header (e.g. `ADX` in two sections) must
117
+ be section-qualified. On a **CREATE you omit `sectionKey` entirely** — the server derives it
118
+ from the section itself, so the same submitted section yields the same key on every compile, and
119
+ minting a `custom:<uuid>` yourself is refused. On an UPDATE or RESTORE, send back the keys
120
+ `get_strategy` returned. To qualify a duplicated header, read the key from a preview's
121
+ `conditionColumns` or from the candidates a `CONDITION_COLUMN_AMBIGUOUS` refusal offers.
122
+ - **Verdict**: `UP` | `DOWN` | `NEITHER` on deciding conditions, explicit `null` on building
123
+ blocks. Declaration order is precedence: the first TRUE condition with a non-null verdict
124
+ decides the coin's verdict. Put the more specific carrier first.
125
+ - **`required: true`** makes a FALSE reading a hard gate: the compose-trade evaluation is
126
+ blocked *before any billing or LLM call*. This is the cheapest risk control in the studio —
127
+ use it for liquidity floors, regime vetoes, and "never fight the HTF" rules.
128
+ - Evaluation is three-valued: `UNRESOLVED` (missing input) is never collapsed to FALSE, and
129
+ outcomes read from a still-forming bar are marked provisional.
130
+ - **The evidence clock.** `clock: "LIVE"` reads the forming bar; `clock: "CLOSE"` reads settled
131
+ bars, and `closes` (1–5) is how many consecutive closed bars must read TRUE — always `1` under
132
+ LIVE, which has exactly one frame. A CLOSE clock is legal **only** over a header resolved from
133
+ the coin's own candle series at offset 0; frame-inert operands (perp-payload scalars, published
134
+ rolling changes, ranks, zone entities, regime labels, enrichment metrics, session scalars) are
135
+ refused with `CONDITION_CLOCK_OPERAND_ILLEGAL`. The remedy is a split, not a re-clock: move that
136
+ clause into its own LIVE condition and `conditionRef` it.
137
+ - **The exit role.** `exit: true` makes a settled TRUE reading close open positions its verdict
138
+ opposes — UP exits SHORTs, DOWN exits LONGs, a `NEITHER` or `null` verdict exits both. Legal only
139
+ under `clock: "CLOSE"`, because an exit fired on a forming bar is an intrabar exit. Orthogonal to
140
+ `required`: the two act on disjoint lifecycles, pre-entry versus open.
141
+
142
+ ## Entry — when, and where, an entry is taken
143
+
144
+ `{ trigger, confirmTf, closes, bandAtrMultiple, levelSource, levelOffsetAtrMultiple, validForBars }`
145
+ — all seven required on every CREATE, no defaults. Replaced WHOLE on save, so an omitted key
146
+ reverts an author's discipline silently rather than being refused.
147
+
148
+ | `trigger` | What it does |
149
+ |---|---|
150
+ | `AT_SIGNAL` | Fire on the qualification flip, at whatever bar is on the tape. The platform's flat wall-clock entry window applies. |
151
+ | `ON_CANDLE_CLOSE` | The flip ARMS the pair; entry is taken only after a `confirmTf` close that still reads the conditions true and has not displaced beyond the band. |
152
+ | `STOP_THROUGH_LEVEL` | A TRIGGER order rests at the level ± offset; the exchange book is the watcher. |
153
+ | `ON_RETEST` | A LIMIT order rests at the broken level. Not filling is a correct outcome, not a failure. |
154
+
155
+ - `confirmTf` — exactly two values are legal: the strategy's own timeframe and the rung below it
156
+ (one only, at the ladder floor). Not the authorable main-candle set.
157
+ - `closes` — 1–5 consecutive confirming closes. Must be `1` under any trigger but `ON_CANDLE_CLOSE`.
158
+ - `bandAtrMultiple` — the veto width. The entry is VOIDED when the confirming close moved at or
159
+ beyond that many ATR against the armed verdict. Strictly `> 0` (zero voids on any adverse move,
160
+ which is a filter, not "off"), and at or below the platform's own entry-deviation gate — read it
161
+ from `get_trading_config_catalog`.
162
+ - `levelSource` — `SWING_HIGH` | `SWING_LOW` | `BOLLINGER_UPPER` | `BOLLINGER_LOWER`, resolved once
163
+ at decision time.
164
+ - `levelOffsetAtrMultiple` — 0–2, UNSIGNED. Direction is implied by the trigger and the verdict, so
165
+ a signed value would invert the trigger's meaning. Must be `0` under a non-level trigger.
166
+ - `validForBars` — 1–24 of the strategy's OWN bars, not minutes: a 1h setup waiting for a retest has
167
+ not failed after fifteen minutes.
168
+
169
+ The legality matrix runs **one way**: all seven keys are always present, so the question is never
170
+ "is it set" but "is it set to something that MEANS anything under this trigger". Leave a dial at its
171
+ inert value rather than setting one the platform will ignore.
172
+
173
+ ## Signal weights — the scorecard is a weighted average, budget it
174
+
175
+ Allocation tiers: `0` Off, `1` Normal, `2` Important, `3` Critical.
176
+
177
+ ```
178
+ aggregateScore = Σ(score × allocation) / Σ(allocation) over triggered signals
179
+ ```
180
+
181
+ Consequences worth designing around:
182
+
183
+ - Weights are **relative**: one Critical among Normals dominates; all-Critical equals all-Normal.
184
+ Build a pyramid — 1–2 Critical (the thesis), 2–4 Important (confirmation), a few Normal
185
+ (context) — and turn everything else Off so noise cannot dilute the average.
186
+ - `required: true` on a rule does two things: the signal counts toward `minRequiredCount` when
187
+ it triggers, and the gate blocks routing when too few required signals fired. A rule with
188
+ `required: true` at allocation 0 is **rejected** (contract 34) — raise the allocation or clear
189
+ the flag.
190
+ - `params` are per-signal and replace canonical defaults only when present and valid — tune
191
+ thresholds to the strategy (e.g. an RSI-overbought at 65 for a fade book) after reading
192
+ `get_strategy_signal_definition({signalId, timeframe})`. Omitted `params` preserve defaults
193
+ byte-for-byte.
194
+ - Which signals *can* trigger follows from your report's sections/columns
195
+ (`derive_strategy_rule_view` shows in-report membership for a draft). Weighting a signal your
196
+ report never feeds is dead weight.
197
+
198
+ ## Routing gates
199
+
200
+ - `minAggregateScore` (0–1): floor on the weighted average above. Set it from your pyramid: if
201
+ routing should need the Critical thesis plus one Important confirmation, compute that mix's
202
+ aggregate and gate just under it.
203
+ - `minRequiredCount` (0–20): how many `required` signals must be among the triggered set.
204
+ - `minAtrPct`: minimum ATR as % of price — a dead-market filter; bounds come from
205
+ `get_trading_config_catalog` (validated today: 0.1–10).
206
+
207
+ ## Trade levels (ATR geometry)
208
+
209
+ `minStopLossAtrMultiple < maxStopLossAtrMultiple` (band where the stop may sit; ceiling capped
210
+ at the structural 3×ATR), `minRiskRewardRatio` (catalog bounds today: 0.5–3). Position size is
211
+ risk-budget based (`riskPct / stopDistancePct` — see the `battlegrid` skill's contract notes),
212
+ so a *wider* stop means a *smaller* position, not more risk. Tight bands suit breakout entries;
213
+ wide bands suit mean reversion that needs room.
214
+
215
+ ## Position management (how the stop moves)
216
+
217
+ Validated live bounds: `breakEvenTriggerR` 0.5–2 · `trailingTriggerR` 0–2 step 0.01 (0 = trail
218
+ from entry) · `trailingGivebackPct` 25–55 · `trailingBufferPct` 0.01–1 · `timeDecay` grace
219
+ 1–1440 min ≥ interval 1–480 min, tighten 0.1–50%, max 1–100%, stale threshold 0–100% of TP
220
+ progress. Each mechanism has its own enabled flag; there is no umbrella switch. Trend books:
221
+ arm break-even ~1R, trail late with a generous giveback (40–55). Mean-reversion/scalp books:
222
+ break-even early, tight giveback, and **timeDecay on** — a thesis that hasn't paid within its
223
+ horizon should be squeezed out.
224
+
225
+ ## Coin selection (per compile, not persisted)
226
+
227
+ `{mode:"ranked", limit, category?}` (categories today: ALL, CRYPTO, L1, MEMES, DEFI, TRADFI,
228
+ STOCKS, INDICES, COMMODITIES) or `{mode:"explicit", tickers[]}`. Choose the cohort the review
229
+ should render over — explicit tickers for a focused edit, ranked for a scanning book.
230
+
231
+ ## Market Read markers
232
+
233
+ `marketReadText` renders with live values wherever a `{...}` marker names a column header
234
+ (`{RVOL}`), a condition (`{SQUEEZE_ON}` — renders outcome plus evidence), or a section-qualified
235
+ form on collision (`{custom:<uuid>.MAalign}`). The preview returns `marketReadMarkers` with each
236
+ marker's resolution status — fix `unknown`/`ambiguous` markers before compiling.
237
+
238
+ ## References
239
+
240
+ - `references/playbooks.md` — five validated desk-grade playbooks with full payloads: volatility
241
+ compression breakout, crowded-positioning fade, relative-strength rotation with a benchmark
242
+ gate, HTF trend pullback, perp/spot flow divergence at structure.
243
+ - `references/recipes.md` — copy-adaptable column recipes, condition patterns, weight matrices,
244
+ and trade-level/position-management presets per trading persona.
245
+ - `references/tradingview-ports.md` — the most popular TradingView community scripts (Squeeze
246
+ Momentum [LazyBear], Supertrend/UT Bot, Chandelier Exit, MACD + 200 MA, golden cross, RSI-2,
247
+ VWAP reversion, Donchian/Turtle, ICT FVG/order blocks) translated process-for-process onto
248
+ the studio's vocabulary, with an expressibility triage and honest named substitutions for
249
+ what the grammar cannot carry.
250
+
251
+ Validate a draft cheaply before compiling: `derive_strategy_rule_view` (report membership +
252
+ rule defaults, no write) and `preview_strategy_report` (rendered tables, condition outcomes
253
+ with evidence, verdict tally, budget usage, marker resolution). Then compile once, review the
254
+ compiled truth, and apply.
@@ -0,0 +1,370 @@
1
+ # Strategy Studio playbooks — validated desk-grade examples
2
+
3
+ Five complete composition patterns, modeled on how systematic desks actually structure these
4
+ trades. Every header, vocabulary label, bound, and shape below was validated against the live
5
+ server (preview + column contracts) on 2026-08-28. Tokens are still illustrative: re-discover
6
+ before compiling, and read exact headers from `preview_strategy_report`'s `conditionColumns`.
7
+
8
+ Playbook 1 shows the **full** `compile_strategy_plan` CREATE request. Playbooks 2–5 show only
9
+ the axes that differ — their envelope (`operation`, `intentSummary`, `assumptions`,
10
+ `coinSelection`, identity, timeframe) follows the same shape. Multi-account proxies wrap each as
11
+ `{ account, request }`.
12
+
13
+ ---
14
+
15
+ ## 1 · Volatility Compression Breakout (vol desk, 4h swing)
16
+
17
+ **Thesis.** Coins in the tightest Bollinger compression on the board, with trend strength not yet
18
+ developed, break hard when volume arrives. Trade the expansion, direction decided by the break.
19
+
20
+ **Why this shape.** Cross-sectional `rank` finds compression *relative to the whole universe*
21
+ (cheaper and more robust than an absolute width threshold); a building-block condition holds the
22
+ squeeze state and two verdict carriers decide direction; a required liquidity floor stops the
23
+ agent spending calls on illiquid names; tight stop band + trailing because breakout invalidation
24
+ is nearby and winners should run.
25
+
26
+ ```json
27
+ {
28
+ "request": {
29
+ "operation": "CREATE",
30
+ "intentSummary": "Cross-sectional volatility-compression breakout: enter expansions out of the tightest Bollinger squeezes on the board, direction decided by the breaking bar, volume-confirmed.",
31
+ "assumptions": [
32
+ "4h anchor: swing cadence, structural zones read on the regime rung",
33
+ "Ranked CRYPTO cohort approximates the live tradable universe"
34
+ ],
35
+ "coinSelection": { "mode": "ranked", "limit": 20, "category": "CRYPTO" },
36
+ "name": "Compression Breakout",
37
+ "tagline": "Tightest bands on the board, traded on expansion",
38
+ "description": "Scans the universe for Bollinger-width compression (bottom-decile rank), requires real participation, and enters the expansion bar in its own direction. Structure zones frame targets.",
39
+ "timeframe": "4h",
40
+ "sections": [
41
+ {
42
+ "kind": "custom",
43
+ "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5",
44
+ "title": "Squeeze Scan",
45
+ "benchmarkTicker": null,
46
+ "columns": [
47
+ { "metric": "BB_WIDTH_PCT", "transformId": "value", "timeframe": { "rel": "anchor" } },
48
+ { "metric": "BB_WIDTH_PCT", "transformId": "rank", "timeframe": { "rel": "anchor" }, "ordering": "lo" },
49
+ { "metric": "ADX", "transformId": "value", "timeframe": { "rel": "anchor" } },
50
+ { "metric": "RVOL", "transformId": "value", "timeframe": { "rel": "anchor" } },
51
+ { "metric": "BB_PCT_B", "transformId": "value", "timeframe": { "rel": "anchor" } },
52
+ { "metric": "CLOSE_CHANGE", "transformId": "value", "timeframe": { "rel": "anchor" } },
53
+ { "metric": "NOTIONAL_VOLUME_1D", "transformId": "value", "timeframe": { "rel": "anchor" } }
54
+ ]
55
+ },
56
+ { "kind": "platform", "sectionKey": "includeBollingerBands" },
57
+ { "kind": "platform", "sectionKey": "includeStructureZones" }
58
+ ],
59
+ "conditions": [
60
+ {
61
+ "conditionKey": "LIQUID_FLOOR",
62
+ "name": "Liquidity floor",
63
+ "definition": { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "vol24hUsd" }, "op": "gte", "value": 25000000 },
64
+ "verdict": null,
65
+ "required": true
66
+ },
67
+ {
68
+ "conditionKey": "SQUEEZE_ON",
69
+ "name": "Volatility compression",
70
+ "definition": {
71
+ "kind": "group", "op": "ALL", "members": [
72
+ { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "bbWidthPct_rank_lo" }, "op": "lte", "value": 10 },
73
+ { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "ADX" }, "op": "lt", "value": 20 }
74
+ ]
75
+ },
76
+ "verdict": null,
77
+ "required": false
78
+ },
79
+ {
80
+ "conditionKey": "BREAK_UP",
81
+ "name": "Upside expansion",
82
+ "definition": {
83
+ "kind": "group", "op": "ALL", "members": [
84
+ { "kind": "conditionRef", "conditionKey": "SQUEEZE_ON" },
85
+ { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "closeChg" }, "op": "gt", "value": 0 },
86
+ { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "RVOL" }, "op": "gte", "value": 1.5 },
87
+ { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "pctB" }, "op": "gte", "value": 0.85 }
88
+ ]
89
+ },
90
+ "verdict": "UP",
91
+ "required": false
92
+ },
93
+ {
94
+ "conditionKey": "BREAK_DOWN",
95
+ "name": "Downside expansion",
96
+ "definition": {
97
+ "kind": "group", "op": "ALL", "members": [
98
+ { "kind": "conditionRef", "conditionKey": "SQUEEZE_ON" },
99
+ { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "closeChg" }, "op": "lt", "value": 0 },
100
+ { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "RVOL" }, "op": "gte", "value": 1.5 },
101
+ { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "pctB" }, "op": "lte", "value": 0.15 }
102
+ ]
103
+ },
104
+ "verdict": "DOWN",
105
+ "required": false
106
+ }
107
+ ],
108
+ "rules": [
109
+ { "signalId": "bollinger_squeeze", "allocation": 3, "required": true },
110
+ { "signalId": "volume_surge", "allocation": 2, "required": true, "params": { "multiplier": 1.5 } },
111
+ { "signalId": "volatility_atr_expanding", "allocation": 2, "required": false },
112
+ { "signalId": "sr_resistance_break", "allocation": 2, "required": false },
113
+ { "signalId": "sr_support_break", "allocation": 2, "required": false },
114
+ { "signalId": "bollinger_upper_touch", "allocation": 1, "required": false },
115
+ { "signalId": "bollinger_lower_touch", "allocation": 1, "required": false },
116
+ { "signalId": "trend_adx_ranging", "allocation": 1, "required": false }
117
+ ],
118
+ "minAggregateScore": 0.55,
119
+ "minRequiredCount": 2,
120
+ "minAtrPct": 0.8,
121
+ "minStopLossAtrMultiple": 0.75,
122
+ "maxStopLossAtrMultiple": 1.75,
123
+ "minRiskRewardRatio": 2,
124
+ "breakEvenEnabled": true,
125
+ "breakEvenTriggerR": 1,
126
+ "trailingEnabled": true,
127
+ "trailingTriggerR": 1.2,
128
+ "trailingGivebackPct": 35,
129
+ "trailingBufferPct": 0.3,
130
+ "timeDecayEnabled": false,
131
+ "marketReadText": "Trade only expansions out of compression: {SQUEEZE_ON} must read TRUE on the setup bar. Board standing {bbWidthPct_rank_lo}; participation {RVOL} must be at least 1.5x. Longs on {BREAK_UP}, shorts on {BREAK_DOWN}; skip anything failing {LIQUID_FLOOR}. Frame targets against the regime-rung zones ({zones_htf_resist_dist} up, {zones_htf_support_dist} down)."
132
+ }
133
+ }
134
+ ```
135
+
136
+ **Review focus.** In the compile response: the scorecard should show exactly the eight named
137
+ rules scoring (everything else Off); `reviewContext.reportPreview.conditionOutcomes` should show
138
+ `SQUEEZE_ON` evidence per coin and the verdict tally; check `marketReadMarkers` all resolve.
139
+
140
+ ---
141
+
142
+ ## 2 · Crowded-Positioning Fade (derivatives desk, 1h intraday)
143
+
144
+ **Thesis.** When funding is paying one side heavily, open interest is building, and the OI·price
145
+ regime says the crowd is adding into an extended move, fade it back toward equilibrium.
146
+
147
+ **Sections.** Custom `Positioning` (mint key `custom:b2b2…`):
148
+ `FUNDING_RATE value` → `rate` · `FUNDING_RATE aggregate w24` → `rate_mean24` (one venue print is
149
+ noise; the 24-sample mean is the crowd's bill) · `FUNDING_ANN value` → `ann` · `OI_CHG value` →
150
+ `oiChg` · `OI_PX_REGIME value` → `oiRegime` (vocab: `new longs` / `new shorts` /
151
+ `short covering` / `long liquidation`) · `MARK spread ORACLE` → `mark_oracle_spread` (perp
152
+ premium) · `RSI14 value` · `CHG_24H value` → `chg24h`. Plus platform `includeCvd`.
153
+
154
+ **Conditions** (building blocks carry no verdict; NOT as a required veto):
155
+
156
+ ```json
157
+ [
158
+ { "conditionKey": "NO_LIQUIDATION_TAPE", "name": "Not a liquidation knife",
159
+ "definition": { "kind": "group", "op": "NOT", "members": [
160
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiRegime" }, "op": "is", "label": "long liquidation" } ] },
161
+ "verdict": null, "required": true },
162
+ { "conditionKey": "CROWDED_LONGS", "name": "Longs overpaying and adding",
163
+ "definition": { "kind": "group", "op": "ALL", "members": [
164
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "ann" }, "op": "gte", "value": 25 },
165
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiChg" }, "op": "gte", "value": 3 },
166
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiRegime" }, "op": "is", "label": "new longs" } ] },
167
+ "verdict": null, "required": false },
168
+ { "conditionKey": "FADE_SHORT", "name": "Fade the crowded long",
169
+ "definition": { "kind": "group", "op": "ALL", "members": [
170
+ { "kind": "conditionRef", "conditionKey": "CROWDED_LONGS" },
171
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "RSI14" }, "op": "gte", "value": 65 },
172
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "chg24h" }, "op": "gte", "value": 5 } ] },
173
+ "verdict": "DOWN", "required": false },
174
+ { "conditionKey": "SQUEEZED_SHORTS", "name": "Shorts overpaying and adding",
175
+ "definition": { "kind": "group", "op": "ALL", "members": [
176
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "ann" }, "op": "lte", "value": -25 },
177
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiChg" }, "op": "gte", "value": 3 },
178
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiRegime" }, "op": "is", "label": "new shorts" } ] },
179
+ "verdict": null, "required": false },
180
+ { "conditionKey": "SQUEEZE_LONG", "name": "Squeeze the crowded short",
181
+ "definition": { "kind": "group", "op": "ALL", "members": [
182
+ { "kind": "conditionRef", "conditionKey": "SQUEEZED_SHORTS" },
183
+ { "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "RSI14" }, "op": "lte", "value": 35 } ] },
184
+ "verdict": "UP", "required": false }
185
+ ]
186
+ ```
187
+
188
+ **Rules** (note the tuned params — read `get_strategy_signal_definition` first):
189
+ `funding_extreme_positive` 3 required, params `{"thresholdPct": 0.001}` ·
190
+ `funding_extreme_negative` 3 required, params `{"thresholdPct": 0.001}` · `oi_surge` 2, params
191
+ `{"thresholdPct": 0.03}` · `rsi_overbought` 2, params `{"threshold": 65}` · `rsi_oversold` 2,
192
+ params `{"threshold": 35}` · `cvd_bear_divergence` 2 · `cvd_bull_divergence` 2 ·
193
+ `mfi_overbought` 1 · `mfi_oversold` 1.
194
+
195
+ **Gates & geometry.** `minAggregateScore 0.6`, `minRequiredCount 1` (either funding extreme),
196
+ `minAtrPct 0.5`. Levels `1.0–2.5` ATR (mean reversion needs room), RR `1.5`. Position
197
+ management: break-even at `0.8R`, trailing off, **timeDecay on** (`grace 120`, `interval 60`,
198
+ `tighten 10`, `max 40`, `stale 25`) — a fade that hasn't paid in a couple of hours is wrong.
199
+
200
+ ---
201
+
202
+ ## 3 · Relative-Strength Rotation with a Benchmark Gate (cross-sectional desk, 4h)
203
+
204
+ **Thesis.** Own the leaders while the market leader trends up; press the laggards when it rolls
205
+ over. Selection is cross-sectional (ranks), regime is read off a **benchmark section**.
206
+
207
+ **Sections.** Custom `Leadership` (key `custom:d4d4…`): `CLOSE_CHANGE rank hi` →
208
+ `closeChg_rank_hi` · `CLOSE_CHANGE rank lo` → `closeChg_rank_lo` · `RVOL rank hi` →
209
+ `RVOL_rank_hi` · `EMA5 spread EMA13 × trajectory w4` → `EMA5_EMA13_spread_now` / `_trend` ·
210
+ `NOTIONAL_VOLUME_1D value` → `vol24hUsd`. Custom `BTC Regime` with `"benchmarkTicker": "BTC"`
211
+ (key `custom:c3c3…`): `MA_ALIGN value` → `MAalign` (bullish/bearish/mixed) · `ADX value` ·
212
+ `REGIME_TREND value` → `regTrend`. Every row of that section reads BTC, not the evaluated coin.
213
+
214
+ **Conditions** (refs compose the regime gate with selection; duplicated headers like `ADX`
215
+ must be section-qualified — that is why the section keys are minted client-side):
216
+
217
+ ```json
218
+ [
219
+ { "conditionKey": "BTC_RISK_ON", "name": "BTC trending up",
220
+ "definition": { "kind": "group", "op": "ALL", "members": [
221
+ { "kind": "clause", "column": { "sectionKey": "custom:c3c3c3c3-d4d4-4e5e-8f6f-a7a7a7a7a7a7", "header": "MAalign" }, "op": "is", "label": "bullish" },
222
+ { "kind": "clause", "column": { "sectionKey": "custom:c3c3c3c3-d4d4-4e5e-8f6f-a7a7a7a7a7a7", "header": "ADX" }, "op": "gte", "value": 20 } ] },
223
+ "verdict": null, "required": false },
224
+ { "conditionKey": "BTC_RISK_OFF", "name": "BTC rolling over",
225
+ "definition": { "kind": "clause", "column": { "sectionKey": "custom:c3c3c3c3-d4d4-4e5e-8f6f-a7a7a7a7a7a7", "header": "MAalign" }, "op": "is", "label": "bearish" },
226
+ "verdict": null, "required": false },
227
+ { "conditionKey": "LEADER", "name": "Top-5 leader with participation",
228
+ "definition": { "kind": "group", "op": "ALL", "members": [
229
+ { "kind": "clause", "column": { "sectionKey": "custom:d4d4d4d4-e5e5-4f6f-8a7a-b8b8b8b8b8b8", "header": "closeChg_rank_hi" }, "op": "lte", "value": 5 },
230
+ { "kind": "clause", "column": { "sectionKey": "custom:d4d4d4d4-e5e5-4f6f-8a7a-b8b8b8b8b8b8", "header": "RVOL_rank_hi" }, "op": "lte", "value": 10 },
231
+ { "kind": "clause", "column": { "sectionKey": "custom:d4d4d4d4-e5e5-4f6f-8a7a-b8b8b8b8b8b8", "header": "vol24hUsd" }, "op": "gte", "value": 100000000 } ] },
232
+ "verdict": null, "required": false },
233
+ { "conditionKey": "ROTATE_IN", "name": "Rotate into leadership",
234
+ "definition": { "kind": "group", "op": "ALL", "members": [
235
+ { "kind": "conditionRef", "conditionKey": "BTC_RISK_ON" },
236
+ { "kind": "conditionRef", "conditionKey": "LEADER" } ] },
237
+ "verdict": "UP", "required": false },
238
+ { "conditionKey": "ROTATE_OUT", "name": "Press laggards in risk-off",
239
+ "definition": { "kind": "group", "op": "ALL", "members": [
240
+ { "kind": "conditionRef", "conditionKey": "BTC_RISK_OFF" },
241
+ { "kind": "clause", "column": { "sectionKey": "custom:d4d4d4d4-e5e5-4f6f-8a7a-b8b8b8b8b8b8", "header": "closeChg_rank_lo" }, "op": "lte", "value": 5 } ] },
242
+ "verdict": "DOWN", "required": false }
243
+ ]
244
+ ```
245
+
246
+ **Rules.** `comparison_sector_momentum` 3 · `rel_roc_positive` 2 · `rel_roc_negative` 2 ·
247
+ `ma_ema_aligned_bull` 2 required · `ma_ema_aligned_bear` 2 required · `rel_ppo_bull_cross` 1 ·
248
+ `rel_ppo_bear_cross` 1 · `volume_surge` 1. Gates: `0.5 / 1 / 0.6`. Levels `1–2` ATR, RR `1.8`.
249
+ PM: break-even 1R; trailing at 1R with giveback 45 (let rotation winners run); no timeDecay.
250
+ `coinSelection` `{"mode":"ranked","limit":40,"category":"CRYPTO"}` — ranks need a wide cohort.
251
+
252
+ ---
253
+
254
+ ## 4 · HTF Trend Pullback (CTA desk, 1h, multi-timeframe confluence)
255
+
256
+ **Thesis.** Only trade with the higher-timeframe trend; enter on anchor-rung pullbacks to value
257
+ while the lower rung has not broken structure. Never spend an LLM call in chop.
258
+
259
+ **Sections.** Platform `includeMtfConfluence` (headers per rung: `MAalign_ltf` / `MAalign` /
260
+ `MAalign_htf`, `RSI14_*_zone`, `ADX_*_state` with vocab weak/developing/trending/extreme),
261
+ platform `includeMovingAverages` (`dist_SMA20`, `dist_EMA20`, …), platform `includeRsi`
262
+ (`RSI14_now`, `RSI14_zone`). Platform section keys are their literal `sectionKey` — qualify
263
+ clauses with e.g. `"sectionKey": "includeMtfConfluence"` where headers collide.
264
+
265
+ **Conditions** — the `ANY`-of-trends required gate is the key move: chop (neither trend) blocks
266
+ compose-trade entirely, before billing:
267
+
268
+ ```json
269
+ [
270
+ { "conditionKey": "HTF_UP", "name": "HTF uptrend",
271
+ "definition": { "kind": "group", "op": "ALL", "members": [
272
+ { "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "MAalign_htf" }, "op": "is", "label": "bullish" },
273
+ { "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "ADX_htf_state" }, "op": "in", "labels": ["trending", "extreme"] } ] },
274
+ "verdict": null, "required": false },
275
+ { "conditionKey": "HTF_DOWN", "name": "HTF downtrend",
276
+ "definition": { "kind": "group", "op": "ALL", "members": [
277
+ { "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "MAalign_htf" }, "op": "is", "label": "bearish" },
278
+ { "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "ADX_htf_state" }, "op": "in", "labels": ["trending", "extreme"] } ] },
279
+ "verdict": null, "required": false },
280
+ { "conditionKey": "TREND_PRESENT", "name": "Some HTF trend exists",
281
+ "definition": { "kind": "group", "op": "ANY", "members": [
282
+ { "kind": "conditionRef", "conditionKey": "HTF_UP" },
283
+ { "kind": "conditionRef", "conditionKey": "HTF_DOWN" } ] },
284
+ "verdict": null, "required": true },
285
+ { "conditionKey": "PULLBACK_LONG", "name": "Pullback to value in uptrend",
286
+ "definition": { "kind": "group", "op": "ALL", "members": [
287
+ { "kind": "conditionRef", "conditionKey": "HTF_UP" },
288
+ { "kind": "clause", "column": { "sectionKey": "includeRsi", "header": "RSI14_now" }, "op": "between", "low": 35, "high": 55 },
289
+ { "kind": "clause", "column": { "sectionKey": "includeMovingAverages", "header": "dist_EMA20" }, "op": "between", "low": -3, "high": 0.5 } ] },
290
+ "verdict": "UP", "required": false },
291
+ { "conditionKey": "PULLBACK_SHORT", "name": "Rally to value in downtrend",
292
+ "definition": { "kind": "group", "op": "ALL", "members": [
293
+ { "kind": "conditionRef", "conditionKey": "HTF_DOWN" },
294
+ { "kind": "clause", "column": { "sectionKey": "includeRsi", "header": "RSI14_now" }, "op": "between", "low": 45, "high": 65 },
295
+ { "kind": "clause", "column": { "sectionKey": "includeMovingAverages", "header": "dist_EMA20" }, "op": "between", "low": -0.5, "high": 3 } ] },
296
+ "verdict": "DOWN", "required": false }
297
+ ]
298
+ ```
299
+
300
+ **Rules.** `mtf_pullback_long` 3 required · `mtf_pullback_short` 3 required ·
301
+ `htf_ma_aligned_bull` 2 required · `htf_ma_aligned_bear` 2 required · `htf_trend_adx_trending`
302
+ 2 · `ma_ema_aligned_bull` 1 · `ma_ema_aligned_bear` 1 · `rsi_oversold` 1 params
303
+ `{"threshold": 40}` (pullback depth, not capitulation). Gates: `minAggregateScore 0.6`,
304
+ `minRequiredCount 2` (a pullback signal *and* an HTF alignment), `minAtrPct 0.7`. Levels
305
+ `1–2.5` ATR, RR `2`. PM: break-even 1R; trailing 1.5R giveback 30 buffer 0.3; no timeDecay.
306
+
307
+ ---
308
+
309
+ ## 5 · Perp/Spot Flow Divergence at Structure (microstructure desk, 15m scalp)
310
+
311
+ **Thesis.** When spot flow leads (accumulation) and price sits just above a standing support
312
+ zone, buy the dip; when a move is perp-led and fragile into resistance, fade it. Structure comes
313
+ from the platform zones section; flow from the perp/spot module.
314
+
315
+ **Sections.** Platform `includePerpSpotFlow` (`perpSpotFlow` vocab: `confirmed_bull` /
316
+ `confirmed_bear` / `perp_led_fragile` / `spot_led_accumulation` / `neutral`; plus
317
+ `perpSpotStr`, `spotCVD`), platform `includeStructureZones` (`zones_htf_support_dist` — signed %
318
+ from price, support below price is negative; `zones_htf_resist_dist`, `_age_h`, `_type`),
319
+ custom `Tape` (key `custom:e5e5…`): `BUY_PRESSURE value` → `buyPres` (0–1) · `RVOL value` ·
320
+ `CLOSE_CHANGE value` → `closeChg`.
321
+
322
+ **Conditions** — `N_OF` requires 2-of-3 tape confirmations instead of a brittle ALL:
323
+
324
+ ```json
325
+ [
326
+ { "conditionKey": "SPOT_ACCUM", "name": "Spot-led accumulation",
327
+ "definition": { "kind": "clause", "column": { "sectionKey": "includePerpSpotFlow", "header": "perpSpotFlow" }, "op": "is", "label": "spot_led_accumulation" },
328
+ "verdict": null, "required": false },
329
+ { "conditionKey": "NEAR_SUPPORT", "name": "Sitting on a standing zone",
330
+ "definition": { "kind": "group", "op": "ALL", "members": [
331
+ { "kind": "clause", "column": { "sectionKey": "includeStructureZones", "header": "zones_htf_support_dist" }, "op": "between", "low": -2, "high": 0 },
332
+ { "kind": "clause", "column": { "sectionKey": "includeStructureZones", "header": "zones_htf_support_age_h" }, "op": "gte", "value": 12 } ] },
333
+ "verdict": null, "required": false },
334
+ { "conditionKey": "DIP_BID", "name": "Buy the dip into support",
335
+ "definition": { "kind": "group", "op": "ALL", "members": [
336
+ { "kind": "conditionRef", "conditionKey": "SPOT_ACCUM" },
337
+ { "kind": "conditionRef", "conditionKey": "NEAR_SUPPORT" },
338
+ { "kind": "group", "op": "N_OF", "n": 2, "members": [
339
+ { "kind": "clause", "column": { "sectionKey": "custom:e5e5e5e5-f6f6-4a7a-8b8b-c9c9c9c9c9c9", "header": "buyPres" }, "op": "gte", "value": 0.55 },
340
+ { "kind": "clause", "column": { "sectionKey": "custom:e5e5e5e5-f6f6-4a7a-8b8b-c9c9c9c9c9c9", "header": "RVOL" }, "op": "gte", "value": 1.2 },
341
+ { "kind": "clause", "column": { "sectionKey": "custom:e5e5e5e5-f6f6-4a7a-8b8b-c9c9c9c9c9c9", "header": "closeChg" }, "op": "gt", "value": 0 } ] } ] },
342
+ "verdict": "UP", "required": false },
343
+ { "conditionKey": "FRAGILE_POP", "name": "Perp-led pop into resistance",
344
+ "definition": { "kind": "group", "op": "ALL", "members": [
345
+ { "kind": "clause", "column": { "sectionKey": "includePerpSpotFlow", "header": "perpSpotFlow" }, "op": "is", "label": "perp_led_fragile" },
346
+ { "kind": "clause", "column": { "sectionKey": "includeStructureZones", "header": "zones_htf_resist_dist" }, "op": "between", "low": 0, "high": 2 },
347
+ { "kind": "clause", "column": { "sectionKey": "custom:e5e5e5e5-f6f6-4a7a-8b8b-c9c9c9c9c9c9", "header": "RVOL" }, "op": "gte", "value": 1.3 } ] },
348
+ "verdict": "DOWN", "required": false }
349
+ ]
350
+ ```
351
+
352
+ **Rules.** `flow_perp_spot_bull_divergence` 3 required · `flow_perp_spot_bear_divergence` 3
353
+ required · `cvd_bull_divergence` 2 · `cvd_bear_divergence` 2 · `sr_at_support` 2 ·
354
+ `sr_at_resistance` 2 · `structure_ob_approach` 1 · `volume_surge` 1. Gates: `0.55 / 1 / 0.4`
355
+ (15m: lower ATR floor). Levels `0.5–1.2` ATR, RR `1.5` — scalp geometry. PM: break-even
356
+ `0.7R`; trailing `0.9R`, giveback 30, buffer 0.15; **timeDecay on and aggressive** (`grace 45`,
357
+ `interval 15`, `tighten 15`, `max 60`, `stale 30`). `coinSelection`
358
+ `{"mode":"ranked","limit":15,"category":"CRYPTO"}`.
359
+
360
+ ---
361
+
362
+ ## Using a playbook
363
+
364
+ 1. Re-discover the vocabulary; confirm each header via `get_strategy_column_contract` or one
365
+ `preview_strategy_report` over your sections (read `conditionColumns`).
366
+ 2. Adapt thresholds to the current market — preview shows live values beside each clause, so
367
+ calibrate literals against what the table actually renders.
368
+ 3. Compile once, read `approvedPlan` (scorecard, diff, viability) and
369
+ `reviewContext.reportPreview` (condition outcomes, verdict tally, budgets), fix, recompile.
370
+ 4. Apply with `{ "request": { "planToken": "<verbatim>", "confirm": true } }` after review.