@battlegrid/mcp-server 31.2.5 → 31.2.6

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,401 @@
1
+ ---
2
+ name: battlegrid-strategy-examples
3
+ description: Full-surface composition patterns for the strategy studio — validated desk-grade examples of custom report sections, benchmark sections, condition trees with verdicts and enforcement gates, tiered signal weights, routing gates, ATR trade levels, and position management. Activate beside strategy-authoring whenever a strategy is being composed or upgraded beyond a basic template.
4
+ ---
5
+
6
+ # Strategy Studio — full-power composition patterns
7
+
8
+ `strategy-authoring` owns the flow (evidence → locked spec → discover → compile → review →
9
+ apply). This skill owns **what to compose**: a default build — a few platform sections, no
10
+ conditions, untouched weights — wastes the studio. The playbooks below are compiled in CI, so their
11
+ shapes are binding in the sense that matters — they are checked, not merely asserted. Loose tokens
12
+ elsewhere in this file are not covered by that gate, and vocabulary moves with deploys, so discovery
13
+ in the conversation stays the authority — prefer what `list_strategy_vocabulary`,
14
+ `get_strategy_column_contract`, and `get_strategy_signal_definition` return over anything
15
+ printed here, and read exact headers from a preview's `conditionColumns` before conditioning on
16
+ them.
17
+
18
+ ## The full-power checklist
19
+
20
+ Before compiling a CREATE, every "no" here is a decision to state, not an omission:
21
+
22
+ 1. At least one **custom section** whose columns encode the thesis — not only platform modules.
23
+ 2. **Conditions** encode the entry logic: building blocks (`verdict: null`) + verdict carriers,
24
+ and at least one `required: true` condition vetoing obvious disqualifiers **before any
25
+ billing or LLM call**.
26
+ 3. **Every signal meant to score is named in `rules`** with a deliberate tier — unnamed signals
27
+ keep server defaults (typically Off). Verify in the compiled scorecard, never assume.
28
+ 4. **Gates** (`minAggregateScore`, `minRequiredCount`, `minAtrPct`) are computed against the
29
+ chosen weight budget, not guessed.
30
+ 5. **Trade levels + position management** match the setup's geometry and holding period.
31
+ 6. `marketReadText` states standing orders with `{...}` markers so live values render inline.
32
+
33
+ ## Header grammar (system-generated — never named by the author)
34
+
35
+ | Transform | Header | Validated example |
36
+ |---|---|---|
37
+ | `value` | `<code>` | `bbWidthPct`, `RVOL`, `rate` |
38
+ | `trajectory` w4 | `<code>_t3…_t1`, `_now`, `_trend` (rising/falling/flat) | `RSI14_now`, `RSI14_trend` |
39
+ | `distance` | `dist_<code>` (signed % from price) | `dist_SMA50` |
40
+ | `spread` | `<base>_<operand>_spread` | `mark_oracle_spread` |
41
+ | `aggregate` wN | `<code>_mean<N>` | `rate_mean24` |
42
+ | `rank` | `<code>_rank_<hi\|lo\|far\|near>` — ordinal, `lte N` = top-N | `bbWidthPct_rank_lo` |
43
+ | `efficiency` wN | `<code>_er` (1 straight, ~0 chop) | `close_ltf_er` |
44
+ | `maxShare` wN | `<code>_maxShare` | `volBase_ltf_maxShare` |
45
+ | `classifyZone` / `classifyState` | `<code>_zone` / `<code>_state` | `RSI14_zone`, `ADX_state` |
46
+
47
+ Non-anchor rungs affix `_ltf` (lower) / `_htf` (regime): `MAalign_htf`,
48
+ `zones_htf_support_dist`. Chains are bounded at two: inner `distance`/`spread` → outer
49
+ `trajectory`/`aggregate`/`efficiency`/`maxShare`/`rank` (`EMA5 spread EMA13 × trajectory` →
50
+ `EMA5_EMA13_spread_now` + `_trend`).
51
+
52
+ **Timeframe references are two families.** *Relative* (`anchor`/`lower`/`regime`) re-resolve
53
+ when the strategy timeframe changes — `regime` is the anchor's ladder successor (1d for a 4h
54
+ anchor). *Pinned* (`{abs: "<tf>"}`) is fixed, ignores anchor retunes, suffixes the literal
55
+ (`RSI14_1d`, `dist_SMA200_1d`, `MAalign_1d` — validated), and binds to discovery's
56
+ `rankedTimeframes`, a **superset** of the authorable anchor set — `{abs: "1d"}` is valid while
57
+ `1d` is not an anchor. `offset: 1` on a pinned `value` column reads the last **closed** bar of
58
+ that timeframe (`RSI14[t - 1]`) — the deterministic daily-close read; offset 0 reads the
59
+ forming bar (provisional), and offset does not change the header, so one offset per
60
+ `metric × timeframe` per section. **Daily-strategy pattern:** anchor 4h, pinned-1d thesis
61
+ columns at `offset: 1`, `required: true` daily conditions gating every carrier, anchor-rung
62
+ columns only for entry timing and risk — daily inputs then move once per daily close while
63
+ stops and time decay keep managing intraday. **Benchmark sections** (`benchmarkTicker: "BTC"`,
64
+ required-nullable on every custom section) read the benchmark's values on every row — the
65
+ standard market-leader regime gate. Platform `sectionKey`s are their literal keys
66
+ (`includeMtfConfluence`). Custom `sectionKey`s are the **server's** to issue, and which of the two
67
+ things you do depends on the operation: on a **CREATE, omit `sectionKey` entirely** — supplying one
68
+ is refused, since a new strategy owns no custom sections yet; on an **UPDATE or RESTORE, send back
69
+ the keys `get_strategy` returned** for the sections you are keeping. Either way you never invent
70
+ one. To section-qualify a duplicated header, read the key from a preview's `conditionColumns` or
71
+ from the qualified candidates a `CONDITION_COLUMN_AMBIGUOUS` refusal offers.
72
+
73
+ ## Conditions
74
+
75
+ `{ conditionKey, name, definition, verdict, required, exit, clock, closes }` — all eight
76
+ required, no defaults. Clauses: numeric/rank headers take `lt|lte|gte|gt|between`;
77
+ classification/direction headers take `is|in` with the served vocabulary. Groups:
78
+ `ALL | ANY | NOT | N_OF` (with `n`), depth ≤ 2. `conditionRef` composes named conditions (no
79
+ cycles; forward refs legal). `sectionKey: null` is sugar for a report-unique header only.
80
+ Verdicts: first TRUE carrier **in declaration order** decides UP/DOWN/NEITHER — order carriers
81
+ most-specific first; building blocks carry `null`. `required: true` = FALSE blocks compose-trade
82
+ before billing. Evaluation is three-valued: UNRESOLVED never collapses to FALSE; forming-bar
83
+ reads are provisional.
84
+
85
+ **The evidence clock.** `clock: "LIVE"` reads the forming bar; `clock: "CLOSE"` reads settled
86
+ bars, and `closes` is how many consecutive closed bars must read TRUE (1–5) — always `1` under
87
+ LIVE, which has exactly one frame. A CLOSE clock is legal **only** over a header resolved from
88
+ this coin's own candle series at offset 0. Frame-inert operands are refused
89
+ (`CONDITION_CLOCK_OPERAND_ILLEGAL`): perp-payload scalars, published rolling changes, ranks, zone
90
+ entities, MDS regime labels, enrichment metrics, session scalars, and any clause authored at a
91
+ non-zero offset. A closed frame cannot move them, so "held for N closes" would describe reads
92
+ that never happened. The remedy is a split, not a re-clock: move that clause into its own LIVE
93
+ condition and `conditionRef` it. **Worked liquidity floor:** `LIQUID_FLOOR` is LIVE because
94
+ `vol24hUsd` is a bundle scalar; the carrier that refs it may be CLOSE over its own candle-series
95
+ clauses.
96
+
97
+ **The lane a strategy is deployed to.** Report-level scalars split by LANE, and the split is not a
98
+ quality of the header — it is which reader runs. Market breadth and the reference pairs are ordinary
99
+ market-wide reads with no session dimension, so they resolve everywhere. The five **session-field**
100
+ scalars (`fieldPlayers`, `fieldUpBias`, `fieldBiasDir`, `captConc`, `picksSpread`) describe a game
101
+ session the agent is playing in, and **radar runs outside one** — so a radar deployment whose
102
+ strategy reads one is refused outright (`CONDITION_OPERAND_UNSERVED_IN_LANE`), naming the scalars it
103
+ can read instead. The refusal lands at DEPLOY rather than at save, because a strategy carries no lane
104
+ of its own: the same strategy is legal, and reads those scalars correctly, on an arena agent.
105
+
106
+ **The exit role.** `exit: true` makes a settled TRUE reading close open positions its verdict
107
+ opposes — UP exits SHORTs, DOWN exits LONGs, a NEITHER or `null` verdict exits both. Legal only
108
+ under `clock: "CLOSE"`: a LIVE reading is the forming bar, and an exit fired on one is an
109
+ intrabar exit. Orthogonal to `required` — the two act on disjoint lifecycles, pre-entry versus
110
+ open — so a condition may carry both, either, or neither.
111
+
112
+ ## Entry
113
+
114
+ `{ trigger, confirmTf, closes, bandAtrMultiple, levelSource, levelOffsetAtrMultiple,
115
+ validForBars }` — all seven required on every CREATE, no defaults. This axis is replaced WHOLE
116
+ on save, so an omitted key would silently revert an author's discipline rather than be refused.
117
+
118
+ **The trigger decides WHEN, and for two of them WHERE, an entry is taken.**
119
+
120
+ - `AT_SIGNAL` — fire the moment the radar observes the qualification flip, at whatever bar is on
121
+ the tape, keeping the platform's flat wall-clock entry window. Today's behaviour.
122
+ - `ON_CANDLE_CLOSE` — the flip ARMS the pair; the entry is taken only after a close on
123
+ `confirmTf` that still reads the conditions true and has not displaced beyond the band.
124
+ - `STOP_THROUGH_LEVEL` — a TRIGGER order rests at the authored level ± offset and the exchange
125
+ book is the watcher; the entry is taken when price trades through, not when the platform
126
+ notices.
127
+ - `ON_RETEST` — a LIMIT order rests at the broken level, waiting for a return to it. Not filling
128
+ is a correct outcome, not a failure.
129
+
130
+ `confirmTf` is the bar whose close confirms. Exactly two values are legal: the strategy's own
131
+ timeframe and the rung below it — one only, when the strategy sits on the ladder floor. It is
132
+ NOT the authorable main-candle set; a rung further down names a bar nothing else in the strategy
133
+ observes and makes the radar sweep on every one of its closes.
134
+
135
+ `closes` (1–5) is how many consecutive confirming closes are required, and `bandAtrMultiple` is
136
+ the veto width: the entry is VOIDED when the confirming close has moved at or beyond that many
137
+ ATR against the armed verdict. Strictly greater than zero — zero is not "no filter" but a filter
138
+ that voids on any adverse move — and at or below the platform's own entry-deviation gate, since
139
+ a wider band cannot refuse anything the platform will not refuse anyway.
140
+
141
+ `levelSource` is one of `SWING_HIGH`, `SWING_LOW`, `BOLLINGER_UPPER`, `BOLLINGER_LOWER`,
142
+ resolved once at decision time. `levelOffsetAtrMultiple` (0–2) is an UNSIGNED magnitude — the
143
+ direction is implied by the trigger and the verdict, so a signed value would invert the
144
+ trigger's meaning. `validForBars` (1–24) denominates validity in the strategy's OWN bars rather
145
+ than minutes, because a 1h setup waiting for a retest has not failed after fifteen minutes.
146
+
147
+ **The legality matrix runs one way.** All seven keys are always present, so the question is
148
+ never "is it set" but "is it set to something that MEANS anything under this trigger".
149
+ `closes` ≠ 1 is refused under any trigger but `ON_CANDLE_CLOSE`; `levelOffsetAtrMultiple` ≠ 0 is
150
+ refused under a non-level trigger. Leave a dial at its inert value rather than setting one the
151
+ platform will ignore.
152
+
153
+ ## Report sections
154
+
155
+ `{ kind, sectionKey, title, benchmarkTicker, notes, columns }` — the custom section shape. A
156
+ section carries no `timeframe`: its relative columns resolve against the strategy timeframe, and a
157
+ column reaches any other timeframe by pinning it (`timeframe: { abs: '4h' }`). `benchmarkTicker` and `notes` are **required-nullable**: send an explicit `null` rather
158
+ than omitting them, because the section is rebuilt whole on save and an omitted key clears the
159
+ author's value silently. On a CREATE, omit `sectionKey` — it is derived from the section itself.
160
+
161
+ ## Signal rules
162
+
163
+ `{ signalId, allocation, required, params }` — one entry per signal you want scoring.
164
+ `allocation` is the tier (0–3) and `params` replaces canonical defaults only when present.
165
+
166
+ ## Signal weights and gate math
167
+
168
+ Tiers: 0 Off · 1 Normal · 2 Important · 3 Critical.
169
+
170
+ ```
171
+ aggregateScore = Σ(score × allocation) / Σ(allocation) over triggered signals
172
+ ```
173
+
174
+ Weights are relative — build a pyramid: 1–2 Critical (thesis, usually `required`), 2–4
175
+ Important (independent confirmation, different modules), 1–3 Normal (context), rest Off so
176
+ noise cannot dilute the average. Gate check: with 3/2/2/1 weights, Critical + one Important at
177
+ score 1.0 → (3+2)/8 = 0.625, so a 0.6 gate means "thesis plus one confirmation".
178
+ `simulate_aggregate_score` does this arithmetic from compiled values at review time.
179
+ `required: true` counts the signal toward `minRequiredCount` when triggered; at
180
+ `allocation: 0` it is rejected (contract 34). `params` replace canonical defaults only when
181
+ present — read `get_strategy_signal_definition({ signalId, timeframe })` before tuning (e.g.
182
+ `rsi_overbought {"threshold": 65}` for a fade book; `volume_surge {"multiplier": 1.5}`).
183
+ `derive_strategy_rule_view` shows which signals a draft report feeds — weighting a signal the
184
+ report never feeds is dead weight.
185
+
186
+ ## Routing gates
187
+
188
+ `{ minAggregateScore, minRequiredCount, minAtrPct }` — whether a scored setup may route to a
189
+ trade at all. `minAggregateScore` 0–1 · `minRequiredCount` 0–20 · `minAtrPct` is the dead-market
190
+ floor, with bounds from `get_trading_config_catalog`.
191
+
192
+ ## Trade levels
193
+
194
+ `{ minStopLossAtrMultiple, maxStopLossAtrMultiple, minRiskRewardRatio }` — where stops and
195
+ targets may sit. `minStopLossAtrMultiple < maxStopLossAtrMultiple` (≤ the 3×ATR structural cap),
196
+ `minRiskRewardRatio` within the served range. Sizing is risk-budget based — a wider stop means a
197
+ smaller position, never more risk.
198
+
199
+ ## Position management
200
+
201
+ `{ breakEvenEnabled, breakEvenTriggerR, trailingEnabled, trailingTriggerR, trailingGivebackPct,
202
+ trailingBufferPct, timeDecayEnabled, timeDecayGracePeriodMinutes, timeDecayIntervalMinutes,
203
+ timeDecayTightenPct, timeDecayMaxTightenPct, timeDecayStaleThresholdTpProgressPct,
204
+ decisionInvalidationExitEnabled }` — how the stop moves after entry, and when a position is
205
+ closed for reasons other than its stop.
206
+
207
+ Validated bounds: break-even trigger 0.5–2R; trailing trigger 0–2R step 0.01 (0 = trail from
208
+ entry), giveback 25–55%, buffer 0.01–1%; timeDecay grace ≥ interval, tighten 0.1–50% to max
209
+ 1–100%, stale threshold 0–100% of TP progress. Per-mechanism flags; no umbrella switch.
210
+
211
+ `decisionInvalidationExitEnabled` is the post-fill invalidation exit: a **closed**
212
+ strategy-timeframe candle beyond the decision's invalidation level closes the position
213
+ reduce-only. Bar-close only — the protective stop still owns intrabar moves.
214
+
215
+ Persona presets: scalper (5m/15m) stops 0.5–1.2 ATR, RR 1.5, BE 0.7R, trail 0.9R/giveback 30,
216
+ timeDecay ON (45/15, tighten 15→60, stale 30) · intraday mean-revert (1h) 1.0–2.5 ATR, RR 1.5,
217
+ BE 0.8R, no trail, timeDecay ON (120/60, 10→40, stale 25) · swing breakout (4h) 0.75–1.75 ATR,
218
+ RR 2, BE 1R, trail 1.2R/giveback 35 · swing trend (4h) 1.0–2.5 ATR, RR 2, BE 1R, trail
219
+ 1.5R/giveback 45–55, no timeDecay.
220
+
221
+ ## Playbooks (validated compositions)
222
+
223
+ *Validated* is a gate, not a claim: every playbook below is compiled against the live catalog by
224
+ `strategy-playbooks.compile.test.ts`, which fails the build if one stops assembling or if a header
225
+ its conditions name stops resolving. Edit a playbook and the gate re-checks it; move the grammar
226
+ underneath one and the gate catches that too.
227
+
228
+ ### 1 · Volatility Compression Breakout — 4h swing
229
+
230
+ Custom `Squeeze Scan` (no `sectionKey` — this is a CREATE): `BB_WIDTH_PCT value` + `rank lo`, `ADX value`,
231
+ `RVOL value`, `BB_PCT_B value`, `CLOSE_CHANGE value`, `NOTIONAL_VOLUME_1D value`; plus platform
232
+ `includeStructureZones`. (No `includeBollingerBands`: it renders `bbWidthPct` and
233
+ `bbWidthPct_rank_lo` a second time, which makes both of `SQUEEZE_ON`'s clauses ambiguous, and the
234
+ custom section already carries them.) Conditions: `LIQUID_FLOOR` (required —
235
+ `vol24hUsd gte 25000000`); `SQUEEZE_ON` building block (`bbWidthPct_rank_lo lte 10` AND
236
+ `ADX lt 20`); `BREAK_UP` verdict UP (ref SQUEEZE_ON + `closeChg gt 0` + `RVOL gte 1.5` +
237
+ `pctB gte 0.85`); `BREAK_DOWN` verdict DOWN (mirror with `pctB lte 0.15`). Rules:
238
+ `bollinger_squeeze` 3 required · `volume_surge` 2 required `{"multiplier":1.5}` ·
239
+ `volatility_atr_expanding` 2 · `sr_resistance_break` 2 · `sr_support_break` 2 ·
240
+ `bollinger_upper_touch` 1 · `bollinger_lower_touch` 1 · `trend_adx_ranging` 1. Gates
241
+ 0.55 / 2 / 0.8. Levels 0.75–1.75 ATR, RR 2. PM: BE 1R; trail 1.2R giveback 35 buffer 0.3.
242
+ Market Read: "Trade only expansions out of compression: {SQUEEZE_ON} must read TRUE… skip
243
+ anything failing {LIQUID_FLOOR}."
244
+
245
+ ### 2 · Crowded-Positioning Fade — 1h intraday
246
+
247
+ Custom `Positioning`: `FUNDING_RATE value`→`rate`, `aggregate w24`→`rate_mean24`,
248
+ `FUNDING_ANN value`→`ann`, `OI_CHG value`→`oiChg`, `OI_PX_REGIME value`→`oiRegime` (vocab:
249
+ new longs / new shorts / short covering / long liquidation), `MARK spread ORACLE`→
250
+ `mark_oracle_spread`, `RSI14 value`, `CHG_24H value`→`chg24h`; plus `includeCvd`. Conditions:
251
+ `NO_LIQUIDATION_TAPE` required NOT-veto (`NOT [oiRegime is "long liquidation"]`);
252
+ `CROWDED_LONGS` building block (`ann gte 25` + `oiChg gte 3` + `oiRegime is "new longs"`);
253
+ `FADE_SHORT` verdict DOWN (ref + `RSI14 gte 65` + `chg24h gte 5`); mirrored `SQUEEZED_SHORTS`
254
+ / `SQUEEZE_LONG` verdict UP. Rules: `funding_extreme_positive`/`_negative` 3 required
255
+ `{"thresholdPct":0.001}` · `oi_surge` 2 `{"thresholdPct":0.03}` · `rsi_overbought` 2
256
+ `{"threshold":65}` · `rsi_oversold` 2 `{"threshold":35}` · `cvd_bear_divergence` 2 ·
257
+ `cvd_bull_divergence` 2 · `mfi_overbought` 1 · `mfi_oversold` 1. Gates 0.6 / 1 / 0.5. Levels
258
+ 1.0–2.5 ATR, RR 1.5. PM: BE 0.8R, no trail, timeDecay ON (120/60, 10→40, stale 25).
259
+
260
+ ### 3 · Relative-Strength Rotation with a Benchmark Gate — 4h cross-sectional
261
+
262
+ Custom `Leadership`: `ROC12 rank hi` + `rank lo`, `RVOL rank hi`,
263
+ `EMA5 spread EMA13 × trajectory w4`, `NOTIONAL_VOLUME_1D value`. **Rank `ROC12`, not
264
+ `CLOSE_CHANGE`:** `closeChg` is the one magnitude-only rankable code, so `hi`/`lo` on it are
265
+ refused, and its legal `far` ordering sorts by |change| — which would put the session's biggest
266
+ losers in a "leaders" cohort. Custom `BTC Regime` with
267
+ `"benchmarkTicker": "BTC"`: `MA_ALIGN value`→`MAalign`, `ADX value`, `REGIME_TREND value` —
268
+ every row reads BTC. Conditions: `BTC_RISK_ON` building block (`MAalign is "bullish"` +
269
+ `ADX gte 20` — both read bare, since only this section renders them); `BTC_RISK_OFF`
270
+ (`MAalign is "bearish"`); `LEADER` (`roc12_rank_hi lte 5` + `RVOL_rank_hi lte 10` +
271
+ `vol24hUsd gte 100000000`); `ROTATE_IN` verdict UP (`ALL[ref BTC_RISK_ON, ref LEADER]`);
272
+ `ROTATE_OUT` verdict DOWN (`ALL[ref BTC_RISK_OFF, roc12_rank_lo lte 5]`). Rules:
273
+ `comparison_sector_momentum` 3 · `rel_roc_positive`/`_negative` 2 · `ma_ema_aligned_bull`/
274
+ `_bear` 2 required · `rel_ppo_bull_cross`/`_bear_cross` 1 · `volume_surge` 1. Gates
275
+ 0.5 / 1 / 0.6. Levels 1–2 ATR, RR 1.8. PM: BE 1R; trail 1R giveback 45. Ranked cohort ≥ 40 —
276
+ rank conditions need a cohort wider than their thresholds.
277
+
278
+ ### 4 · HTF Trend Pullback — 1h, multi-timeframe confluence
279
+
280
+ Platform `includeMtfConfluence` (`MAalign_ltf/…/_htf`, `RSI14_*_zone`, `ADX_*_state` vocab
281
+ weak/developing/trending/extreme), `includeMovingAverages` (`dist_EMA20`, …), `includeRsi`
282
+ (`RSI14_now`, `RSI14_zone`). These three render `MAalign` and `RSI14_zone` twice between them, so
283
+ every condition below reads a suffixed form — never bare `MAalign` or bare `RSI14_zone`, which
284
+ would be ambiguous. Conditions: `HTF_UP` building block (`MAalign_htf is "bullish"` +
285
+ `ADX_htf_state in ["trending","extreme"]`); `HTF_DOWN` mirror; `TREND_PRESENT` **required**
286
+ `ANY[ref HTF_UP, ref HTF_DOWN]` — chop blocks compose-trade entirely, before billing;
287
+ `PULLBACK_LONG` verdict UP (ref HTF_UP + `RSI14_now between 35 55` +
288
+ `dist_EMA20 between -3 0.5`); `PULLBACK_SHORT` verdict DOWN (mirror, 45–65 / −0.5–3). Rules:
289
+ `mtf_pullback_long`/`_short` 3 required · `htf_ma_aligned_bull`/`_bear` 2 required ·
290
+ `htf_trend_adx_trending` 2 · `ma_ema_aligned_bull`/`_bear` 1 · `rsi_oversold` 1
291
+ `{"threshold":40}`. Gates 0.6 / 2 / 0.7 (a pullback signal AND an HTF alignment). Levels
292
+ 1–2.5 ATR, RR 2. PM: BE 1R; trail 1.5R giveback 30 buffer 0.3.
293
+
294
+ ### 5 · Perp/Spot Flow Divergence at Structure — 15m scalp
295
+
296
+ Platform `includePerpSpotFlow` (`perpSpotFlow` vocab: confirmed_bull / confirmed_bear /
297
+ perp_led_fragile / spot_led_accumulation / neutral), `includeStructureZones`
298
+ (`zones_htf_support_dist` signed %, support below price is negative; `_age_h`), custom `Tape`
299
+ (`BUY_PRESSURE value`→`buyPres` 0–1, `RVOL value`, `CLOSE_CHANGE value`). Conditions:
300
+ `SPOT_ACCUM` (`perpSpotFlow is "spot_led_accumulation"`); `NEAR_SUPPORT`
301
+ (`zones_htf_support_dist between -2 0` + `zones_htf_support_age_h gte 12`); `DIP_BID` verdict
302
+ UP — refs plus an `N_OF(2)` quorum over `buyPres gte 0.55` / `RVOL gte 1.2` / `closeChg gt 0`
303
+ (quorum beats a brittle ALL); `FRAGILE_POP` verdict DOWN (`perp_led_fragile` +
304
+ `zones_htf_resist_dist between 0 2` + `RVOL gte 1.3`). Rules:
305
+ `flow_perp_spot_bull_divergence`/`_bear_divergence` 3 required · `cvd_bull_divergence`/
306
+ `_bear_divergence` 2 · `sr_at_support`/`_at_resistance` 2 · `structure_ob_approach` 1 ·
307
+ `volume_surge` 1. Gates 0.55 / 1 / 0.4. Levels 0.5–1.2 ATR, RR 1.5. PM: BE 0.7R; trail 0.9R
308
+ giveback 30 buffer 0.15; timeDecay ON aggressive (45/15, 15→60, stale 30).
309
+
310
+ ## TradingView ports — familiar processes, studio vocabulary
311
+
312
+ Players often ask for strategies by the name of a popular TradingView script. Port the
313
+ **process** (regime filter → setup state → trigger → stop engine), and where the catalog lacks
314
+ the primitive, name the substitution in the spec-lock question — never present a substitute as
315
+ the thing itself. Most TV strategies run on the daily chart: carry that with the
316
+ daily-strategy pattern above (pinned-1d thesis at `offset: 1` on an intraday anchor), which
317
+ binds decisions to daily closes while the studio keeps managing risk intraday. Event columns (`MACD_cross`, `EMA5_13`: Bullish/Bearish) print only on the
318
+ crossing bar and are otherwise null → UNRESOLVED; use them as triggers inside a carrier and
319
+ pair with a persistent state (spread sign, `MAalign`) for regime — as the source scripts do.
320
+
321
+ - **Squeeze Momentum [LazyBear] / TTM Squeeze** → `KC_SQUEEZE is "on"` is the native one-condition
322
+ read: the Bollinger pair sitting inside the Keltner channel, at the script's own multiplier.
323
+ The four boundaries stay addressable, so `BB_UPPER spread KC_UPPER` and
324
+ `BB_LOWER spread KC_LOWER` still express the same reading at a threshold you choose. Release
325
+ direction from the MACD trajectory, `bollinger_squeeze` Critical-required.
326
+ **Name the anchor when you port it:** on crypto the squeeze reads *on* about 57% of 1h bars even
327
+ at canonical parameters — σ/ATR runs low here because Bollinger σ is close-to-close while ATR
328
+ captures intrabar range — against about 19% at 15m. It is a selective filter at 15m and below,
329
+ not at 1h. (`bbWidthPct_rank_lo` + `ADX lt 20` remains
330
+ a serviceable board-relative compression proxy, but it is no longer the only option.)
331
+ - **Supertrend / UT Bot / Chandelier Exit** → `ST_LINE` and `ST_DIR` are native, so the regime
332
+ half is a direct port: `ST_DIR is "bullish"` as the persistent state, `dist_ST_LINE` for
333
+ distance to the plotted stop. The plotted trailing line is still best executed by the studio's
334
+ own stop engine — `trailingTriggerR: 0` (trail from entry), giveback ~30–40 (tight factor) or
335
+ 45–55 (loose/chandelier) — because a trailing stop is a position-management mechanism, not a
336
+ column. **`ST_DIR` is a persisting state, not a flip event**: it reads the same on every bar of
337
+ a trend, so the FLIP still needs an event column beside it (`EMA5_13` cross, or a `ST_DIR`
338
+ trajectory whose `_trend` changes). Name that as a substitution.
339
+ - **MACD + 200 MA filter** → `ABOVE_200` building block (`dist_SMA200 gt 0`) referenced by a
340
+ carrier with `MACD_cross is "Bullish"`; rules `macd_bull/bear_cross` 3 required +
341
+ `ma_sma200_above/below` 2 required; swing-trend geometry.
342
+ - **Golden / Death Cross** → `SMA50 spread SMA200 × trajectory` gives state and freshness:
343
+ `SMA50_SMA200_spread_now gt 0` AND `_trend is "rising"`; optional breadth gate on
344
+ `mktBreadth_crypto gte 0` — every published scope resolves, not only `all`, because the leg reads
345
+ the `(timeframe, scope)` pairs a condition names; position-persona geometry. `SMA50 × SMA200` is
346
+ the canonical definition and stays the default port; `EMA50` is available as the crypto variant
347
+ when a player asks for it by name.
348
+ - **RSI-2 (Connors)** → `RSI2` is native, so this ports exactly: `RSI2 lte 10` gated by required
349
+ `ABOVE_200`; the source's fast exit is time — timeDecay ON (180/60, 15→50, stale 20);
350
+ `rsi_oversold` 3 required with a tuned threshold. Keep the literal `lte 10` as the GATE:
351
+ `RSI2 × classifyZone` exists and reads on the same Connors bands, but a zone label is a fixed
352
+ reading while the literal is a threshold the author can see and tune. Use the zone as a report
353
+ column, never as the substitute for the gate.
354
+ - **VWAP reversion** → `dist_VWAP` band + `dist_VWAP_rank_far lte 5` for board-relative
355
+ stretch; required `NOT [ADX_state in ["trending","extreme"]]` veto; scalp geometry +
356
+ aggressive timeDecay (VWAP anchors daily at 00:00 UTC).
357
+ - **Donchian / Turtle breakout** → `zone is "breakout high"` + `dist_swingHi gte 0` +
358
+ `RVOL gte 1.5`; `sr_resistance_break` 3 required; turtle exits = trend preset (trail from
359
+ 1R, giveback 50). Mirror with `"breakdown low"`. **Daily-breakout variant on any anchor:**
360
+ pin the structure at 1d — `zone_1d is "breakout high"`, `dist_swingHi_1d gte 0` (validated).
361
+ **Literal previous-day levels are native**: `PDH` and `PDL` are catalogued price levels, so
362
+ `dist_PDH gte 0` composes directly. They are ANCHORED to `1d` — bind `{abs: '1d'}`, which is the
363
+ only reference the save path accepts on them, and read them from any anchor that way. `distance`
364
+ still rejects an `offset` and a clause still compares a column against a literal; neither of
365
+ those shapes is what a previous-day level needed.
366
+ - **ICT / SMC (FVG + order blocks)** → `STRUCT_ZONES` is the native zone engine:
367
+ `zones_htf_support_type` (`bullish FVG`/`bullish order block`), `zones_htf_support_dist
368
+ between -1.5 0`, `_age_h gte 12`, HTF bias required via `MAalign_htf`; rules
369
+ `structure_fvg_approach`/`structure_ob_approach` required (their `proximityPct` is the
370
+ in-zone dial). Liquidity sweeps, displacement, killzones and event *sequencing* are shapes the
371
+ grammar does not have — a clause compares one column against a literal, so an ordered sequence
372
+ of events cannot be stated at all. That is a grammar limit, not a missing metric; name it as
373
+ one.
374
+ - **Now native, formerly substituted** — WaveTrend (`WT1`/`WT2`), QQE (`QQE_RSI_MA`/`QQE_STOP`),
375
+ Hull (`HMA20`), Ichimoku (`ICHI_CONV`/`ICHI_BASE`/`ICHI_SPAN_A`/`ICHI_SPAN_B`/`ICHI_LAG`),
376
+ Parabolic SAR (`PSAR`), Keltner (`KC_UPPER`/`KC_MID`/`KC_LOWER`), daily pivots
377
+ (`PIVOT_P`/`PIVOT_R1`–`R3`/`PIVOT_S1`–`S3`), Williams %R (`WILLR14`), Stochastic RSI
378
+ (`STOCH_RSI14`), the TTM squeeze (`KC_SQUEEZE`), Connors RSI-2 (`RSI2`), the 9/21/50 EMAs
379
+ (`EMA9`/`EMA21`/`EMA50`), and literal previous-day levels (`PDH`/`PDL`). Port these directly —
380
+ do not offer a substitute for a primitive the catalog serves.
381
+
382
+ ## Not expressible — the catalog keys this needs
383
+
384
+ The one place a claim that the catalog LACKS something may live, and every row names the key it
385
+ denies so the claim can be checked. A claim about the grammar's shape (a clause compares one
386
+ column against a literal; `distance` rejects an `offset`) belongs in prose above — those are
387
+ permanent. A claim that a metric is absent belongs here, or nowhere.
388
+
389
+ | Script / primitive | Key the catalog would need | Nearest expressible neighbour |
390
+ |---|---|---|
391
+ | 100-period SMA | `SMA100` | the shipped 50- or 200-period simple average, whichever the thesis leans on |
392
+
393
+ ## Using a playbook
394
+
395
+ Discover → confirm headers (`get_strategy_column_contract`, or one `preview_strategy_report`
396
+ whose `conditionColumns` lists every addressable header with operators and vocabulary) →
397
+ calibrate literals against the previewed live values → compile once → review the compiled
398
+ scorecard, condition outcomes, verdict tally, and `marketReadMarkers` (fix `unknown` /
399
+ `ambiguous` markers) → apply per the strategy-authoring flow. Coin selection is call context,
400
+ not strategy state: explicit tickers for focused work, `ranked` (with an optional category) for
401
+ scanning books.
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: battlegrid-trade-analysis
3
+ description: Read the player's own trading position — where their money actually is, whether each agent is doing the job it was given, what is open right now and how close it sits to its protections, and whether the automation is actually running. Activate whenever the player asks how they are doing, how an agent is performing, what is open, where their funds are, or why something did or did not happen.
4
+ ---
5
+
6
+ # Trade Analysis
7
+
8
+ You are answering "how am I actually doing?" — for a player whose money is deployed through
9
+ agents they configured and largely cannot watch.
10
+
11
+ ## 1. The money map, with a reconciliation line
12
+
13
+ Start with where the money is, always, even when the question is narrower — a per-agent answer
14
+ means nothing without the whole.
15
+
16
+ - `get_account_state` for the account total.
17
+ - `get_agent_fund_allocation` and `get_agent_budget` for what is committed per agent.
18
+
19
+ Then state a **reconciliation line**: the account total against the sum of its parts. If they
20
+ agree, say they agree. **If they do not, state the gap as a gap** — name the amount and say you
21
+ cannot account for it. Never quietly present a total that does not add up, and never adjust a
22
+ figure to make it add up.
23
+
24
+ ## 2. Per agent: "is it doing its job?"
25
+
26
+ Not "what were its returns" — whether it did *the thing it was told to do*.
27
+
28
+ - `get_intelligence_agent` — restate the agent's mandate in one line, in the player's terms. This
29
+ is the yardstick; without it "up 4%" means nothing.
30
+ - `get_agent_performance` and `list_trade_outcomes` — judge against that mandate.
31
+ - `get_trade_outcome_by_decision` / `get_trade_chart` when a specific trade needs explaining.
32
+ - `get_signal_performance` when the question is whether the agent's signals are working, as
33
+ distinct from whether its trades made money.
34
+
35
+ **Name the blemishes.** A verdict with no flaw in it is not a verdict, it is a summary. The trade
36
+ that went against the mandate, the streak, the position held past its thesis — say it. A player
37
+ reading a clean report about a messy account learns nothing.
38
+
39
+ ## 3. Open positions, with protections and distance to trigger
40
+
41
+ - `get_agent_open_positions` per agent, or `list_user_active_positions` for everything at once.
42
+ - For each open position, report its protections **and how far price sits from each trigger** —
43
+ a stop is a number the player cannot act on; "3.1% from the stop" is one they can.
44
+ - `get_deployment_policy` / `get_radar_deployment` when the protection state comes from standing
45
+ policy rather than the position itself. `list_pending_approvals` and `list_gate_blocks` when
46
+ something looks like it should have fired and did not.
47
+
48
+ ## 4. Automation health — unprompted
49
+
50
+ Call `get_agent_automation_status` and surface anything degraded **even when the player did not
51
+ ask about automation**. A player asking "how's my portfolio?" while an agent has silently stopped
52
+ trading is being answered wrongly if you only answer what they asked.
53
+
54
+ Flag it plainly and **offer to diagnose**. The offer is yours; the diagnosis is
55
+ `strategy-doctor`'s — activate it and let its arc run, rather than reading the gate blocks and
56
+ reason codes from here.
57
+
58
+ ## 5. UNDETERMINED, never "no issue"
59
+
60
+ This is the discipline that matters most on this surface.
61
+
62
+ If a tool call fails, returns nothing, or does not cover the thing being asked about, report that
63
+ item as **UNDETERMINED** and say what you could not check. Never convert an absence of data into a
64
+ clean bill of health. "Automation status: UNDETERMINED — the status read failed" is honest and
65
+ actionable. "No issues found" in the same situation is a false statement about the player's money.
66
+
67
+ The same applies to any figure you could not reconcile, any position whose protections did not
68
+ resolve, and any agent whose mandate you could not read.
69
+
70
+ ## Reporting discipline
71
+
72
+ - Report numbers exactly as the tools return them. Never recompute, re-derive, or round.
73
+ - Lead with the money map, then agents, then positions, then automation. The player scans top-down.
74
+ - Be concise, and put the worst news first. Do not bury a degraded agent under a good return.