@battlegrid/mcp-server 31.2.6 → 31.2.8

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.
package/README.md CHANGED
@@ -24,7 +24,7 @@ Seeing package `31.x` alongside handshake `battlegrid@33.x` — the package **be
24
24
 
25
25
  **What this changes for you:** nothing about how you call anything. Upgrading the package no longer waits on a server deploy, and a server deploy no longer strands you on a package that names the wrong contract — reconnect and the announcement follows. **Contract breaking-change notes are no longer keyed to package versions**, since a contract move is no longer a release here; the v11-and-earlier notes below are kept as history, and the live vocabulary is always discovery.
26
26
 
27
- ## Contract history — v37 → v49.5
27
+ ## Contract history — v37 → v51
28
28
 
29
29
  Eleven majors reached authors while this section stopped at v36. That gap is the mechanism, not an
30
30
  oversight: since v31 a contract move needs no release here, so nothing forced a note to be written —
@@ -32,11 +32,43 @@ and the documentation ships inside the tarball, so a note written but unpublishe
32
32
  Both halves are now closed by a rule keyed to the *served* contract rather than to a release of this
33
33
  package.
34
34
 
35
+ **50.0.0 and 51.0.0 arrived late, and the reason is worth naming.** The re-vendoring errand that
36
+ used to carry these notes is now a generated export
37
+ (`battlegrid-app/server/scripts/export-mcp-skills.mjs`), and it owns three paths — `skills/`,
38
+ `skills/EXPORT.json`, and the vendored digest. It deliberately does not touch this file. So the
39
+ digest kept arriving on time while the note stopped travelling with it, and this section sat at
40
+ v49.5 against a served contract of 51.0.0. Nothing a client could observe was wrong; what was
41
+ missing was the sentence telling them so. **A contract move still needs a human-authored entry
42
+ here, and the export lane will not remind you.**
43
+
35
44
  ### Accepted again — input that was rejected now compiles
36
45
 
37
46
  Nothing to migrate. This is the one direction that cannot break a client: a body the server used to
38
47
  refuse is now stored. Listed because a client that special-cased the refusal can delete that branch.
39
48
 
49
+ - **A signal rule patch no longer forces you to restate `allocation` and `required`** (51.0.0,
50
+ `fix-signal-rule-patch-semantics`). On `update_strategy_signal_rule`, and on the `rules` element
51
+ of `compile_strategy_plan`, both fields become optional and join `params` under ONE omission
52
+ rule: **an omitted mutable field preserves the stored value for that signal.** "Raise this
53
+ signal's weight" is now expressible.
54
+
55
+ ```jsonc
56
+ { "strategyId": "…", "expectedRevision": 7, "signalId": "volume_surge",
57
+ "allocation": 3 } // `required` and `params` keep exactly what is stored
58
+ ```
59
+
60
+ **Every existing client keeps working** — a complete payload is still a valid patch — so this is
61
+ listed for what you can now STOP sending. Before it, both fields were mandatory on every rule
62
+ surface, so a caller that had not first read the current rule had to invent a value it was never
63
+ asked about. That is not hypothetical: revision 5 of a production strategy flipped `required`
64
+ false → true unasked while moving a weight 2 → 3, turning a scoring signal into a **mandatory
65
+ gate** — which changes whether the agent takes trades at all.
66
+
67
+ One boundary on the newly legal ground, and it breaks nothing: a patch carrying **no** mutable
68
+ field is refused — *"A rule patch must change something: supply at least one of allocation,
69
+ required or params."* — rather than minting a no-op revision. Under 50.0.0 that request could not
70
+ be formed at all, so nothing that used to work is now refused.
71
+
40
72
  - **An arming trigger no longer constrains its required conditions' clock** (49.4.0,
41
73
  `restore-arming-trigger-authoring`). `compile_strategy_plan`, `apply_strategy_plan` and
42
74
  `fork_strategy` accept a strategy whose entry trigger is `ON_CANDLE_CLOSE`, `STOP_THROUGH_LEVEL`
@@ -55,6 +87,14 @@ refuse is now stored. Listed because a client that special-cased the refusal can
55
87
 
56
88
  ### Changed meaning, unchanged shape
57
89
 
90
+ - **`blocksScanGate` reads `true` for a class it did not** (50.0.0, `own-scan-served-set-once`), on
91
+ `preview_radar_resolution`. Blocking is now derived from the lane's **served set** rather than
92
+ switched over the reach reason, so a `FEED`-reason refusal on an operand no reader in the lane
93
+ serves BLOCKS instead of deferring. No field changes shape, and a client that already renders the
94
+ key renders the new answer — but a client that treated `blocksScanGate: false` as "this will
95
+ resolve once data arrives" now sees a deployment that will not fire. Nothing in the payload tells
96
+ you this moved.
97
+
58
98
  - **Three tools serve different values for identical input** (47.3.0, `derive-scan-fetch-from-report`).
59
99
  The radar scan leg now derives its timeframe fetch from the strategy's **report** rather than the
60
100
  on-duty agent's three perception rungs, so a required condition addressing an absolute timeframe
@@ -72,6 +112,44 @@ refuse is now stored. Listed because a client that special-cased the refusal can
72
112
 
73
113
  ### Rejected input — something you author is no longer accepted
74
114
 
115
+ - **`update_strategy_signal_rule` requires `confirm: true` when the strategy has bound agents**
116
+ (51.0.0, `fix-signal-rule-patch-semantics`). The write re-materializes scoring configuration onto
117
+ every bound agent immediately — including agents holding open USDC positions — and until now
118
+ nothing on the server asked. A rule edit on a strategy with one or more bound agents is refused
119
+ without the flag, and the message names the count. An edit on a strategy with **nothing bound is
120
+ unaffected**, and so is every path through the web editor.
121
+
122
+ **Send `confirm: true`.** That is the whole migration, and `confirm` is published on the input
123
+ schema — but nothing in the schema says WHEN it becomes mandatory, because the condition is the
124
+ bound-agent count rather than the shape of your body. The refusal rides the existing
125
+ `VALIDATION_ERROR` code, so a client that omits it discovers the rule at the refusal.
126
+
127
+ Why it moved to the server: the guard existed, but only as served prose the calling model could
128
+ decline — and did, twice in production on 2026-08-24. `archive_strategy` and
129
+ `rebind_intelligence_agent` have taken a server-enforced `confirm` all along; single-rule tuning
130
+ was the outlier among its own siblings, and it is the one that writes to scoring.
131
+
132
+ - **A radar deployment is refused when its strategy reads a session-field scalar** (50.0.0,
133
+ `own-scan-served-set-once`). `upsert_radar_deployment` refuses a deployment whose slot agents'
134
+ bound strategy carries a condition reading one of five SESSION-FIELD scalars — `fieldPlayers`,
135
+ `fieldUpBias`, `fieldBiasDir`, `captConc`, `picksSpread` — with
136
+ `CONDITION_OPERAND_UNSERVED_IN_LANE`. A body accepted under 49.5.0 is refused under 50.0.0
137
+ without one byte of it changing.
138
+
139
+ **Why a refusal and not a warning.** Those five describe a game SESSION, and radar runs outside a
140
+ session at BOTH its stages — so such a condition can never resolve there. The deployment formed
141
+ no fire edge and the agent did nothing on that coin, silently, forever. The refusal converts a
142
+ permanent silence into an error at the moment you author it.
143
+
144
+ **Migrate** by moving the clause to a scalar radar reads — the Market Breadth or Reference Pairs
145
+ families, which are market-wide reads with no session dimension — or by binding the strategy to
146
+ an arena agent instead. The error carries both halves: `allowedDomain` enumerates every servable
147
+ header, and the message names the sections.
148
+
149
+ **Strategy authoring is untouched by this bump.** The same strategy is legal, and reads those
150
+ scalars correctly, on an arena agent — which is why the refusal is on the DEPLOYMENT and not on
151
+ `compile_strategy_plan` / `apply_strategy_plan`.
152
+
75
153
  - **A benchmark-bound section no longer accepts crowd metrics or rank transforms** (49.0.0,
76
154
  `fix-benchmark-legality-save-path`). On a custom section carrying a non-null `benchmarkTicker`, a
77
155
  column whose metric is enrichment-stage (the `CROWD_*` family, `FLOW_ALIGN`, `SMART_RETAIL`,
@@ -179,6 +257,22 @@ refuse is now stored. Listed because a client that special-cased the refusal can
179
257
 
180
258
  ### Reshaped output — the same call returns a different shape
181
259
 
260
+ - **`update_strategy_signal_rule` gains its own response envelope** (51.0.0,
261
+ `fix-signal-rule-patch-semantics`). It no longer shares `{ strategy }` with its siblings. The
262
+ response is `{ strategy, ruleChanges }`, where `ruleChanges` is the server's own before/after
263
+ pair for the edited signal — `[{ signalId, before, after }]`, each side a full rule object. It is
264
+ `null` when the mutation changed no rule, **never `[]`**.
265
+
266
+ **Report the change from that pair, not from memory.** The planner always computed the diff and
267
+ the tool discarded it, so a caller narrating what it just did had only its own recollection of
268
+ the before-value. One production edit shipped a wrong receipt on top of a wrong write that way,
269
+ and the write was unreconstructable from the audit trail afterwards.
270
+
271
+ Additive, but published on a `.strict()` shape — a decoder pinned to the old two-key object
272
+ rejects the new key. `fork_strategy`, `archive_strategy` and `restore_strategy` keep the shared
273
+ `StrategyResponseSchema` and publish exactly what they did; it was deliberately NOT widened for
274
+ them, so this reshape reaches one tool only.
275
+
182
276
  - **An entry void now names the gate that refused it** (49.5.0,
183
277
  `fix-arming-trigger-clock-authority`), on `get_radar_activity_summary`. In the cause rollup, the
184
278
  `ENTRY_VOID` group's `gateCode` widens from always-`null` to `QualificationGateCode | null`: a
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ import { type Implementation, type Prompt, type Resource } from '@modelcontextpr
49
49
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
50
50
  * correction. Never move it to track the server.
51
51
  */
52
- export declare const PACKAGE_VERSION = "31.2.6";
52
+ export declare const PACKAGE_VERSION = "31.2.8";
53
53
  export declare const DEFAULT_URL = "https://mcp.battlegrid.trade/mcp";
54
54
  export interface EnvConfig {
55
55
  apiKeys: string[];
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema
52
52
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
53
53
  * correction. Never move it to track the server.
54
54
  */
55
- export const PACKAGE_VERSION = '31.2.6';
55
+ export const PACKAGE_VERSION = '31.2.8';
56
56
  export const DEFAULT_URL = 'https://mcp.battlegrid.trade/mcp';
57
57
  const MAX_RETRIES = 3;
58
58
  const RETRY_DELAYS_MS = [2000, 4000, 8000];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@battlegrid/mcp-server",
3
- "version": "31.2.6",
3
+ "version": "31.2.8",
4
4
  "description": "BattleGrid MCP server — play crypto prediction games from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "generator": "battlegrid-app/server/scripts/export-mcp-skills.mjs",
3
- "contractVersion": "50.0.0",
3
+ "contractVersion": "51.0.0",
4
4
  "files": {
5
5
  "battlegrid-agent-management/SKILL.md": "b6ea75b3d838c1dbaaf3984d7e99a11c81686de2c1a1910c35699ee7e70dafa5",
6
6
  "battlegrid-arena-play/SKILL.md": "03fa153bf82be18bf5ed01e3ba12e2cd9f99a81b08d903a77b52169e7519a182",
7
7
  "battlegrid-market-analysis/SKILL.md": "22fde9c4eac0261c89a8056ab8b20b66fcbe1fd58447fa0ac84fd410b4d134d7",
8
8
  "battlegrid-radar-deployment/SKILL.md": "d2a012b154e1e529db19529bd7c3d75d069f4e410d5804add51165f0f0902b2e",
9
- "battlegrid-strategy-authoring/SKILL.md": "93680b329c158b353fdc7e1ec9058dfd8ab69904cd7f1ae14cf411870c695bf1",
9
+ "battlegrid-strategy-authoring/SKILL.md": "6857956221375f45c42bdadbf032f6858f6534f91fa4d733f6f572cb4c53a1ff",
10
10
  "battlegrid-strategy-doctor/SKILL.md": "9ab3f79128ec81d3e90167c3f1ea4bd6f6b570290e5ebe2bf855195263c58239",
11
- "battlegrid-strategy-examples/SKILL.md": "3e418df3590054b6da75435de79770ebdbdb7b88564534d9050ef209e0476a08",
11
+ "battlegrid-strategy-examples/SKILL.md": "0d6d9611d10bd7683c9bc5186d3db26f2fd4123a5b57330f827ff45cd9ef82c7",
12
+ "battlegrid-strategy-examples/references/playbooks.md": "35c543c090d00ba19e4bcf0a80502afac7a4954643fc535de0b24381c91ecbf4",
13
+ "battlegrid-strategy-examples/references/recipes.md": "53887daece17995543e7d3fe1bf2577fc41a6254c291acacde48d7de63853641",
14
+ "battlegrid-strategy-examples/references/tradingview-ports.md": "2f84758ba6ec015c7db3cf996f663971a7017eddbebe42d6ecba6386c7b78362",
12
15
  "battlegrid-trade-analysis/SKILL.md": "32573a67e55d735e6ee4e36b3b48c815694102704bc453c4d1c13db39551b3da"
13
16
  }
14
17
  }
@@ -223,6 +223,22 @@ with the player:
223
223
  - **Tuning a single rule** — how many agents are bound, and that the change reaches every one of
224
224
  them immediately.
225
225
 
226
+ **Your `ask_user` is the explanation, not the mechanism.** For single-rule tuning the server
227
+ independently requires `confirm:true` whenever the strategy has bound agents, so stating the radius
228
+ and calling anyway is refused, not committed. Send `confirm:true` only on a turn where the player
229
+ made an explicit confirming pick — never because you judged the edit safe. Free text typed while a
230
+ confirmation is open is not consent: answer it, then present the same confirmation again.
231
+
232
+ **Send only the fields you were asked to change.** `allocation`, `required` and `params` are each
233
+ optional and each preserves on omission. "Raise volume_surge to Critical" is
234
+ `{ signalId, allocation: 3 }` — nothing else. Do not read the current rule merely to restate a value
235
+ you are not changing, and never supply `required` unless the player asked about the Required flag.
236
+ Restating a remembered value is how a scoring signal silently becomes a mandatory trade gate.
237
+
238
+ **Report the change from the response.** The success payload carries `ruleChanges` with the server's
239
+ own `before` and `after` for the edited signal. State those. Never describe the prior value from
240
+ memory or from an earlier read — the response is the only record that cannot be stale.
241
+
226
242
  ## When apply is refused
227
243
 
228
244
  Each of these is a specific typed code. Read it and take the cheapest correct step — never retry
@@ -241,6 +257,12 @@ the same call blindly.
241
257
  moved, a shifted catalog. The approved plan **survives** these: clear the cause and confirm again
242
258
  with the same token while it still lives. Recompiling works too, but costs the player a second
243
259
  review they did not need.
260
+ - **A single-rule tuning refused for missing confirmation** — the strategy has bound agents and the
261
+ server will not write without `confirm:true`. The refusal names the count. Present *that* count to
262
+ the player and re-issue the identical call only after an explicit confirming pick. **Never add
263
+ `confirm:true` and retry on your own judgement** — the refusal exists precisely because the
264
+ decision is not yours, and a retry that supplies it unasked is the defect this rule was written
265
+ for. Nothing was written, so there is no partial state to reconcile.
244
266
  - **`TOKEN_BINDING_MISMATCH` while the token is still fresh** — there is nothing left to mis-copy,
245
267
  so this is real drift: a bound agent moved, the catalog changed, or the signing key rotated. Do
246
268
  not resubmit. Recompile, re-present, re-confirm.
@@ -70,6 +70,18 @@ the keys `get_strategy` returned** for the sections you are keeping. Either way
70
70
  one. To section-qualify a duplicated header, read the key from a preview's `conditionColumns` or
71
71
  from the qualified candidates a `CONDITION_COLUMN_AMBIGUOUS` refusal offers.
72
72
 
73
+ **Event columns print only on their event.** `MACD_cross` and `EMA5_13` (Bullish/Bearish) carry a
74
+ value on the crossing bar and are null on every other one, which reads as UNRESOLVED. Use an event
75
+ column as a TRIGGER inside a carrier, and pair it with a persistent state — a spread sign,
76
+ `MAalign` — for regime. A condition that treats an event column as a standing state is unresolved on
77
+ nearly every bar, which is a gate that never gates.
78
+
79
+ **`PDH` and `PDL` are anchored to `1d`.** They are catalogued price levels, so `dist_PDH gte 0`
80
+ composes directly — but bind them `{abs: '1d'}`, which is the only reference the save path accepts on
81
+ them, and read them from any anchor that way. (`distance` still rejects an `offset`, and a clause
82
+ still compares a column against a literal; neither of those shapes is what a previous-day level
83
+ needed.)
84
+
73
85
  ## Conditions
74
86
 
75
87
  `{ conditionKey, name, definition, verdict, required, exit, clock, closes }` — all eight
@@ -109,6 +121,17 @@ under `clock: "CLOSE"`: a LIVE reading is the forming bar, and an exit fired on
109
121
  intrabar exit. Orthogonal to `required` — the two act on disjoint lifecycles, pre-entry versus
110
122
  open — so a condition may carry both, either, or neither.
111
123
 
124
+ **A state column is not a flip event.** `ST_DIR` reads the same on every bar of a trend, so
125
+ `ST_DIR is "bullish"` is a regime filter and never an entry signal. The flip needs an event column
126
+ beside it — an `EMA5_13` cross, or a `ST_DIR` trajectory whose `_trend` changes. The same distinction
127
+ applies to every persisting classification: `MAalign`, `ADX_state`, `zone`.
128
+
129
+ **Name the anchor when a metric is calibrated for one.** `KC_SQUEEZE is "on"` reads *on* about 57% of
130
+ 1h crypto bars even at canonical parameters — Bollinger σ is close-to-close while ATR captures
131
+ intrabar range, so σ/ATR runs low here — against about 19% at 15m. It is a selective filter at 15m and
132
+ below and close to useless at 1h. A metric whose selectivity depends on the anchor is stated with the
133
+ anchor, or the author gates on something that admits most bars.
134
+
112
135
  ## Entry
113
136
 
114
137
  `{ trigger, confirmTf, closes, bandAtrMultiple, levelSource, levelOffsetAtrMultiple,
@@ -218,167 +241,6 @@ BE 0.8R, no trail, timeDecay ON (120/60, 10→40, stale 25) · swing breakout (4
218
241
  RR 2, BE 1R, trail 1.2R/giveback 35 · swing trend (4h) 1.0–2.5 ATR, RR 2, BE 1R, trail
219
242
  1.5R/giveback 45–55, no timeDecay.
220
243
 
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
244
  ## Not expressible — the catalog keys this needs
383
245
 
384
246
  The one place a claim that the catalog LACKS something may live, and every row names the key it
@@ -390,12 +252,19 @@ permanent. A claim that a metric is absent belongs here, or nowhere.
390
252
  |---|---|---|
391
253
  | 100-period SMA | `SMA100` | the shipped 50- or 200-period simple average, whichever the thesis leans on |
392
254
 
393
- ## Using a playbook
255
+ ## Where the worked material lives
256
+
257
+ This skill's body is the contract — the axes, the header grammar, how conditions and weights behave,
258
+ and the absence section above. The worked material is disclosed on demand, so it costs nothing until
259
+ you ask for it. Read a reference with `read_skill_reference` when you reach the work it covers:
260
+
261
+ - **`references/recipes.md`** — copy-adaptable column objects and condition fragments: cross
262
+ detection, board-relative ranks, cross-venue basis, crowd positioning, coin selection, and the
263
+ discovery fields worth reading before you compose.
264
+ - **`references/playbooks.md`** — five validated desk-grade compositions end to end, and how to
265
+ adapt one rather than copy it.
266
+ - **`references/tradingview-ports.md`** — per-script port recipes for the popular TradingView
267
+ strategies, each naming its substitutions where the catalog lacks a primitive.
394
268
 
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.
269
+ Every rule about how a column *behaves* is in this body, not in a reference. If a reference seems to
270
+ state one, the body is the authority.
@@ -0,0 +1,110 @@
1
+ # Playbooks — validated desk-grade compositions
2
+
3
+ Worked compositions for the strategy studio, each validated against the live grammar. Shapes are
4
+ binding; the tokens are illustrations and stay live-discovered.
5
+
6
+ **Contents**
7
+ - Playbooks (validated compositions)
8
+ - Using a playbook
9
+
10
+ Read `## Header grammar` and `## Conditions` in the skill body first — the rules about how columns
11
+ behave live there, not here.
12
+
13
+ ## Playbooks (validated compositions)
14
+
15
+ *Validated* is a gate, not a claim: every playbook below is compiled against the live catalog by
16
+ `strategy-playbooks.compile.test.ts`, which fails the build if one stops assembling or if a header
17
+ its conditions name stops resolving. Edit a playbook and the gate re-checks it; move the grammar
18
+ underneath one and the gate catches that too.
19
+
20
+ ### 1 · Volatility Compression Breakout — 4h swing
21
+
22
+ Custom `Squeeze Scan` (no `sectionKey` — this is a CREATE): `BB_WIDTH_PCT value` + `rank lo`, `ADX value`,
23
+ `RVOL value`, `BB_PCT_B value`, `CLOSE_CHANGE value`, `NOTIONAL_VOLUME_1D value`; plus platform
24
+ `includeStructureZones`. (No `includeBollingerBands`: it renders `bbWidthPct` and
25
+ `bbWidthPct_rank_lo` a second time, which makes both of `SQUEEZE_ON`'s clauses ambiguous, and the
26
+ custom section already carries them.) Conditions: `LIQUID_FLOOR` (required —
27
+ `vol24hUsd gte 25000000`); `SQUEEZE_ON` building block (`bbWidthPct_rank_lo lte 10` AND
28
+ `ADX lt 20`); `BREAK_UP` verdict UP (ref SQUEEZE_ON + `closeChg gt 0` + `RVOL gte 1.5` +
29
+ `pctB gte 0.85`); `BREAK_DOWN` verdict DOWN (mirror with `pctB lte 0.15`). Rules:
30
+ `bollinger_squeeze` 3 required · `volume_surge` 2 required `{"multiplier":1.5}` ·
31
+ `volatility_atr_expanding` 2 · `sr_resistance_break` 2 · `sr_support_break` 2 ·
32
+ `bollinger_upper_touch` 1 · `bollinger_lower_touch` 1 · `trend_adx_ranging` 1. Gates
33
+ 0.55 / 2 / 0.8. Levels 0.75–1.75 ATR, RR 2. PM: BE 1R; trail 1.2R giveback 35 buffer 0.3.
34
+ Market Read: "Trade only expansions out of compression: {SQUEEZE_ON} must read TRUE… skip
35
+ anything failing {LIQUID_FLOOR}."
36
+
37
+ ### 2 · Crowded-Positioning Fade — 1h intraday
38
+
39
+ Custom `Positioning`: `FUNDING_RATE value`→`rate`, `aggregate w24`→`rate_mean24`,
40
+ `FUNDING_ANN value`→`ann`, `OI_CHG value`→`oiChg`, `OI_PX_REGIME value`→`oiRegime` (vocab:
41
+ new longs / new shorts / short covering / long liquidation), `MARK spread ORACLE`→
42
+ `mark_oracle_spread`, `RSI14 value`, `CHG_24H value`→`chg24h`; plus `includeCvd`. Conditions:
43
+ `NO_LIQUIDATION_TAPE` required NOT-veto (`NOT [oiRegime is "long liquidation"]`);
44
+ `CROWDED_LONGS` building block (`ann gte 25` + `oiChg gte 3` + `oiRegime is "new longs"`);
45
+ `FADE_SHORT` verdict DOWN (ref + `RSI14 gte 65` + `chg24h gte 5`); mirrored `SQUEEZED_SHORTS`
46
+ / `SQUEEZE_LONG` verdict UP. Rules: `funding_extreme_positive`/`_negative` 3 required
47
+ `{"thresholdPct":0.001}` · `oi_surge` 2 `{"thresholdPct":0.03}` · `rsi_overbought` 2
48
+ `{"threshold":65}` · `rsi_oversold` 2 `{"threshold":35}` · `cvd_bear_divergence` 2 ·
49
+ `cvd_bull_divergence` 2 · `mfi_overbought` 1 · `mfi_oversold` 1. Gates 0.6 / 1 / 0.5. Levels
50
+ 1.0–2.5 ATR, RR 1.5. PM: BE 0.8R, no trail, timeDecay ON (120/60, 10→40, stale 25).
51
+
52
+ ### 3 · Relative-Strength Rotation with a Benchmark Gate — 4h cross-sectional
53
+
54
+ Custom `Leadership`: `ROC12 rank hi` + `rank lo`, `RVOL rank hi`,
55
+ `EMA5 spread EMA13 × trajectory w4`, `NOTIONAL_VOLUME_1D value`. **Rank `ROC12`, not
56
+ `CLOSE_CHANGE`:** `closeChg` is the one magnitude-only rankable code, so `hi`/`lo` on it are
57
+ refused, and its legal `far` ordering sorts by |change| — which would put the session's biggest
58
+ losers in a "leaders" cohort. Custom `BTC Regime` with
59
+ `"benchmarkTicker": "BTC"`: `MA_ALIGN value`→`MAalign`, `ADX value`, `REGIME_TREND value` —
60
+ every row reads BTC. Conditions: `BTC_RISK_ON` building block (`MAalign is "bullish"` +
61
+ `ADX gte 20` — both read bare, since only this section renders them); `BTC_RISK_OFF`
62
+ (`MAalign is "bearish"`); `LEADER` (`roc12_rank_hi lte 5` + `RVOL_rank_hi lte 10` +
63
+ `vol24hUsd gte 100000000`); `ROTATE_IN` verdict UP (`ALL[ref BTC_RISK_ON, ref LEADER]`);
64
+ `ROTATE_OUT` verdict DOWN (`ALL[ref BTC_RISK_OFF, roc12_rank_lo lte 5]`). Rules:
65
+ `comparison_sector_momentum` 3 · `rel_roc_positive`/`_negative` 2 · `ma_ema_aligned_bull`/
66
+ `_bear` 2 required · `rel_ppo_bull_cross`/`_bear_cross` 1 · `volume_surge` 1. Gates
67
+ 0.5 / 1 / 0.6. Levels 1–2 ATR, RR 1.8. PM: BE 1R; trail 1R giveback 45. Ranked cohort ≥ 40 —
68
+ rank conditions need a cohort wider than their thresholds.
69
+
70
+ ### 4 · HTF Trend Pullback — 1h, multi-timeframe confluence
71
+
72
+ Platform `includeMtfConfluence` (`MAalign_ltf/…/_htf`, `RSI14_*_zone`, `ADX_*_state` vocab
73
+ weak/developing/trending/extreme), `includeMovingAverages` (`dist_EMA20`, …), `includeRsi`
74
+ (`RSI14_now`, `RSI14_zone`). These three render `MAalign` and `RSI14_zone` twice between them, so
75
+ every condition below reads a suffixed form — never bare `MAalign` or bare `RSI14_zone`, which
76
+ would be ambiguous. Conditions: `HTF_UP` building block (`MAalign_htf is "bullish"` +
77
+ `ADX_htf_state in ["trending","extreme"]`); `HTF_DOWN` mirror; `TREND_PRESENT` **required**
78
+ `ANY[ref HTF_UP, ref HTF_DOWN]` — chop blocks compose-trade entirely, before billing;
79
+ `PULLBACK_LONG` verdict UP (ref HTF_UP + `RSI14_now between 35 55` +
80
+ `dist_EMA20 between -3 0.5`); `PULLBACK_SHORT` verdict DOWN (mirror, 45–65 / −0.5–3). Rules:
81
+ `mtf_pullback_long`/`_short` 3 required · `htf_ma_aligned_bull`/`_bear` 2 required ·
82
+ `htf_trend_adx_trending` 2 · `ma_ema_aligned_bull`/`_bear` 1 · `rsi_oversold` 1
83
+ `{"threshold":40}`. Gates 0.6 / 2 / 0.7 (a pullback signal AND an HTF alignment). Levels
84
+ 1–2.5 ATR, RR 2. PM: BE 1R; trail 1.5R giveback 30 buffer 0.3.
85
+
86
+ ### 5 · Perp/Spot Flow Divergence at Structure — 15m scalp
87
+
88
+ Platform `includePerpSpotFlow` (`perpSpotFlow` vocab: confirmed_bull / confirmed_bear /
89
+ perp_led_fragile / spot_led_accumulation / neutral), `includeStructureZones`
90
+ (`zones_htf_support_dist` signed %, support below price is negative; `_age_h`), custom `Tape`
91
+ (`BUY_PRESSURE value`→`buyPres` 0–1, `RVOL value`, `CLOSE_CHANGE value`). Conditions:
92
+ `SPOT_ACCUM` (`perpSpotFlow is "spot_led_accumulation"`); `NEAR_SUPPORT`
93
+ (`zones_htf_support_dist between -2 0` + `zones_htf_support_age_h gte 12`); `DIP_BID` verdict
94
+ UP — refs plus an `N_OF(2)` quorum over `buyPres gte 0.55` / `RVOL gte 1.2` / `closeChg gt 0`
95
+ (quorum beats a brittle ALL); `FRAGILE_POP` verdict DOWN (`perp_led_fragile` +
96
+ `zones_htf_resist_dist between 0 2` + `RVOL gte 1.3`). Rules:
97
+ `flow_perp_spot_bull_divergence`/`_bear_divergence` 3 required · `cvd_bull_divergence`/
98
+ `_bear_divergence` 2 · `sr_at_support`/`_at_resistance` 2 · `structure_ob_approach` 1 ·
99
+ `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
100
+ giveback 30 buffer 0.15; timeDecay ON aggressive (45/15, 15→60, stale 30).
101
+
102
+ ## Using a playbook
103
+
104
+ Discover → confirm headers (`get_strategy_column_contract`, or one `preview_strategy_report`
105
+ whose `conditionColumns` lists every addressable header with operators and vocabulary) →
106
+ calibrate literals against the previewed live values → compile once → review the compiled
107
+ scorecard, condition outcomes, verdict tally, and `marketReadMarkers` (fix `unknown` /
108
+ `ambiguous` markers) → apply per the strategy-authoring flow. Coin selection is call context,
109
+ not strategy state: explicit tickers for focused work, `ranked` (with an optional category) for
110
+ scanning books.
@@ -0,0 +1,118 @@
1
+ # Recipes — copy-adaptable fragments
2
+
3
+ Column objects for a custom section's `columns[]`, condition fragments for `conditions[]`, and the
4
+ coin-selection and discovery shapes an author reaches for while writing a payload.
5
+
6
+ **Shapes are binding. Tokens are illustrations** — re-discover every metric, transform and parameter
7
+ against the live tools (`list_strategy_categories`, `list_strategy_vocabulary`,
8
+ `get_metric_construction_hints`, `get_strategy_column_contract`) before compiling. A recipe is a
9
+ worked shape, never a substitute for discovery.
10
+
11
+ Read `## Header grammar` and `## Conditions` in the skill body first: how a column's header is
12
+ generated, how event columns resolve, what `{abs: …}` binds, and which classifications are states
13
+ rather than events all live there. This document assumes them.
14
+
15
+ **Contents**
16
+ - Column recipes
17
+ - Cross detection
18
+ - Board-relative rank recipes
19
+ - Cross-venue and perp/spot basis
20
+ - Crowd positioning
21
+ - Coin selection
22
+ - Discovery fields worth reading
23
+
24
+ ## Column recipes
25
+
26
+ | Intent | Column | Header it generates |
27
+ |---|---|---|
28
+ | Live traded price | `{ "metric": "LAST", "transformId": "value", "timeframe": { "rel": "anchor" } }` | `last` |
29
+ | Momentum build-up | `{ "metric": "RSI14", "transformId": "trajectory", "timeframe": { "rel": "anchor" }, "window": 4 }` | `RSI14_t3…_now`, `RSI14_trend` |
30
+ | Closed-bar-only trajectory | add `"bars": "closed"` to any trajectory | same, drawn from closed bars |
31
+ | Extension from trend | `{ "metric": "SMA50", "transformId": "distance", "timeframe": { "rel": "anchor" } }` | `dist_SMA50` (signed %) |
32
+ | Funding paid over a day | `{ "metric": "FUNDING_RATE", "transformId": "aggregate", "timeframe": { "rel": "anchor" }, "window": 24 }` | `rate_mean24` |
33
+ | Chop filter on the entry rung | `{ "metric": "CLOSE", "transformId": "efficiency", "timeframe": { "rel": "lower" }, "window": 5, "bars": "closed" }` | `close_ltf_er` (≥0.6 directional) |
34
+ | One-bar volume concentration | `{ "metric": "VOLUME", "transformId": "maxShare", "timeframe": { "rel": "lower" }, "window": 4, "bars": "closed" }` | `volBase_ltf_maxShare` |
35
+ | Room to structure | `{ "metric": "STRUCT_ZONES", "transformId": "nearestZoneDist", "timeframe": { "rel": "regime" }, "side": "resistance" }` | `zones_htf_resist_dist` |
36
+ | Last **closed** daily read | `{ "metric": "RSI14", "transformId": "value", "timeframe": { "abs": "1d" }, "offset": 1 }` | `RSI14_1d` — `RSI14[t - 1]` |
37
+
38
+ ## Cross detection
39
+
40
+ `crossDetect` is the transform that turns a pair into an EVENT column — it prints on the crossing bar
41
+ and is null otherwise:
42
+
43
+ ```json
44
+ { "metric": "MACD", "transformId": "crossDetect", "timeframe": { "rel": "anchor" } }
45
+ ```
46
+
47
+ An event column is a trigger inside a carrier, never a standing state; pair it with a persistent
48
+ state for regime. The skill body's `## Header grammar` states that rule and is the authority on it —
49
+ a condition that reads a `crossDetect` column as a state is UNRESOLVED on nearly every bar.
50
+
51
+ ## Board-relative rank recipes
52
+
53
+ `rank` turns any column into an ordinal across the scanned board. Ordering semantics: `hi` most
54
+ positive, `lo` most negative, `far`/`near` by magnitude (offered only where sign matters), and
55
+ `lte N` reads as "top N".
56
+
57
+ | Intent | Column | Header |
58
+ |---|---|---|
59
+ | Most volatile on the board | `{ "metric": "ATR_PCT", "transformId": "rank", "timeframe": { "rel": "anchor" }, "ordering": "hi" }` | `atrPct_rank_hi` (1 = most volatile) |
60
+ | Biggest movers, sign-agnostic | `{ "metric": "CLOSE_CHANGE", "transformId": "rank", "timeframe": { "rel": "anchor" }, "ordering": "far" }` | `closeChg_rank_far` |
61
+ | Most extended from VWAP | `{ "metric": "VWAP", "transformId": "distance", "chainedTransformId": "rank", "timeframe": { "rel": "anchor" }, "ordering": "far" }` | `dist_VWAP_rank_far` |
62
+
63
+ Confirm every rank ordering against `get_metric_construction_hints({ metric }).rankOrderings` — it is
64
+ present only where rank is composable on that metric, and is already range-gated server-side. Read
65
+ the offered set; never derive one from the metric's native output.
66
+
67
+ ## Cross-venue and perp/spot basis
68
+
69
+ | Intent | Column | Header |
70
+ |---|---|---|
71
+ | Perp premium to oracle | `{ "metric": "MARK", "transformId": "spread", "timeframe": { "rel": "anchor" }, "inputs": [{ "metric": "ORACLE" }] }` | `mark_oracle_spread` |
72
+ | Basis to Binance spot | `{ "metric": "MARK", "transformId": "spread", "timeframe": { "rel": "anchor" }, "inputs": [{ "metric": "SPOT_CLOSE_BN" }] }` | `mark_bnClose_spread` |
73
+ | Basis to Coinbase spot | same with `{ "metric": "SPOT_CLOSE_CB" }` | `mark_cbClose_spread` |
74
+
75
+ A positive basis that is widening while price stalls is perp-led; the flow playbooks use it as the
76
+ fragility read.
77
+
78
+ ## Crowd positioning
79
+
80
+ `CROWD_UPBIAS` is the platform's own crowd read — the share of recent settled sessions predicting UP,
81
+ as a percentage:
82
+
83
+ ```json
84
+ { "metric": "CROWD_UPBIAS", "transformId": "value", "timeframe": { "rel": "anchor" } }
85
+ ```
86
+
87
+ Header `upBias`. It is a fade input, not a trend input: it says what the board expects, which is the
88
+ thing a crowded-positioning playbook trades against.
89
+
90
+ ## Coin selection
91
+
92
+ `coinSelection` is required on every compile and is discriminated on `mode`:
93
+
94
+ ```json
95
+ { "mode": "ranked", "limit": 25, "category": "DEFI" }
96
+ { "mode": "explicit", "tickers": ["BTC", "ETH", "SOL"] }
97
+ ```
98
+
99
+ `ranked` is top-N by 24h volume with an optional sector filter; `explicit` names a ticker set
100
+ validated against the active universe. The sector vocabulary is
101
+ `ALL` / `CRYPTO` / `L1` / `MEMES` / `DEFI` / `TRADFI` / `STOCKS` / `INDICES` / `COMMODITIES` —
102
+ discover the live set rather than assuming this list has not moved. An explicit set is capped; the
103
+ refusal names the cap.
104
+
105
+ ## Discovery fields worth reading
106
+
107
+ Two answers are frequently fetched and then left unread, and each has cost a refusal that a
108
+ single already-served field would have prevented:
109
+
110
+ - `get_strategy_column_contract({ column }).outputs[].conditionOperators` — an empty array means that
111
+ rendered header has no comparison semantics and **cannot appear in a condition clause at all**.
112
+ Legality is per rendered header, not per column: a trajectory's slot header and its `_trend` header
113
+ answer differently.
114
+ - `get_metric_construction_hints({ metric }).rankOrderings` and `.qualifiedForms` — the composable
115
+ rank orderings, and the section-qualified forms available when a header is duplicated report-wide.
116
+ - `get_trading_config_catalog()` carries the platform defaults an omitted dial inherits, including
117
+ `defaultMaxEntryDeviationAtrMultiple` — the band an entry is still admitted within. Read it before
118
+ authoring an entry band, rather than restating a number the platform already owns.
@@ -0,0 +1,83 @@
1
+ # TradingView ports — familiar processes in studio vocabulary
2
+
3
+ Per-script port recipes. Each ports the PROCESS (regime filter -> setup state -> trigger -> stop
4
+ engine) and names its substitutions where the catalog lacks a primitive.
5
+
6
+ **Contents**
7
+ - Squeeze Momentum [LazyBear] / TTM Squeeze
8
+ - Supertrend / UT Bot / Chandelier Exit
9
+ - MACD + 200 MA filter
10
+ - Golden / Death Cross
11
+ - RSI-2 (Connors)
12
+ - VWAP reversion
13
+ - Donchian / Turtle breakout
14
+ - ICT / SMC (FVG + order blocks)
15
+ - Now native, formerly substituted
16
+
17
+ ## TradingView ports — familiar processes, studio vocabulary
18
+
19
+ Players often ask for strategies by the name of a popular TradingView script. Port the
20
+ **process** (regime filter → setup state → trigger → stop engine), and where the catalog lacks
21
+ the primitive, name the substitution in the spec-lock question — never present a substitute as
22
+ the thing itself. Most TV strategies run on the daily chart: carry that with the
23
+ daily-strategy pattern above (pinned-1d thesis at `offset: 1` on an intraday anchor), which
24
+ binds decisions to daily closes while the studio keeps managing risk intraday.
25
+
26
+ Event-column behaviour, state-vs-event, `PDH`/`PDL` binding and the squeeze's anchor calibration are
27
+ rules about the columns themselves, not about porting — they live in the skill body's
28
+ `## Header grammar` and `## Conditions`. Read those first; this document is the per-script recipes.
29
+
30
+ - **Squeeze Momentum [LazyBear] / TTM Squeeze** → `KC_SQUEEZE is "on"` is the native one-condition
31
+ read: the Bollinger pair sitting inside the Keltner channel, at the script's own multiplier.
32
+ The four boundaries stay addressable, so `BB_UPPER spread KC_UPPER` and
33
+ `BB_LOWER spread KC_LOWER` still express the same reading at a threshold you choose. Release
34
+ direction from the MACD trajectory, `bollinger_squeeze` Critical-required.
35
+ Its selectivity is anchor-dependent — see `## Conditions` in the skill body before gating on it.
36
+ (`bbWidthPct_rank_lo` + `ADX lt 20` remains a serviceable board-relative compression proxy, but it
37
+ is no longer the only option.)
38
+ - **Supertrend / UT Bot / Chandelier Exit** → `ST_LINE` and `ST_DIR` are native, so the regime
39
+ half is a direct port: `ST_DIR is "bullish"` as the persistent state, `dist_ST_LINE` for
40
+ distance to the plotted stop. The plotted trailing line is still best executed by the studio's
41
+ own stop engine — `trailingTriggerR: 0` (trail from entry), giveback ~30–40 (tight factor) or
42
+ 45–55 (loose/chandelier) — because a trailing stop is a position-management mechanism, not a
43
+ column. `ST_DIR` is a persisting state and not a flip event — see `## Conditions` in the
44
+ skill body — so the FLIP needs an event column beside it. Name that as a substitution.
45
+ - **MACD + 200 MA filter** → `ABOVE_200` building block (`dist_SMA200 gt 0`) referenced by a
46
+ carrier with `MACD_cross is "Bullish"`; rules `macd_bull/bear_cross` 3 required +
47
+ `ma_sma200_above/below` 2 required; swing-trend geometry.
48
+ - **Golden / Death Cross** → `SMA50 spread SMA200 × trajectory` gives state and freshness:
49
+ `SMA50_SMA200_spread_now gt 0` AND `_trend is "rising"`; optional breadth gate on
50
+ `mktBreadth_crypto gte 0` — every published scope resolves, not only `all`, because the leg reads
51
+ the `(timeframe, scope)` pairs a condition names; position-persona geometry. `SMA50 × SMA200` is
52
+ the canonical definition and stays the default port; `EMA50` is available as the crypto variant
53
+ when a player asks for it by name.
54
+ - **RSI-2 (Connors)** → `RSI2` is native, so this ports exactly: `RSI2 lte 10` gated by required
55
+ `ABOVE_200`; the source's fast exit is time — timeDecay ON (180/60, 15→50, stale 20);
56
+ `rsi_oversold` 3 required with a tuned threshold. Keep the literal `lte 10` as the GATE:
57
+ `RSI2 × classifyZone` exists and reads on the same Connors bands, but a zone label is a fixed
58
+ reading while the literal is a threshold the author can see and tune. Use the zone as a report
59
+ column, never as the substitute for the gate.
60
+ - **VWAP reversion** → `dist_VWAP` band + `dist_VWAP_rank_far lte 5` for board-relative
61
+ stretch; required `NOT [ADX_state in ["trending","extreme"]]` veto; scalp geometry +
62
+ aggressive timeDecay (VWAP anchors daily at 00:00 UTC).
63
+ - **Donchian / Turtle breakout** → `zone is "breakout high"` + `dist_swingHi gte 0` +
64
+ `RVOL gte 1.5`; `sr_resistance_break` 3 required; turtle exits = trend preset (trail from
65
+ 1R, giveback 50). Mirror with `"breakdown low"`. **Daily-breakout variant on any anchor:**
66
+ pin the structure at 1d — `zone_1d is "breakout high"`, `dist_swingHi_1d gte 0` (validated).
67
+ **Literal previous-day levels are native**: `dist_PDH gte 0` composes directly. Their `{abs: '1d'}`
68
+ binding is stated in `## Header grammar` in the skill body.
69
+ - **ICT / SMC (FVG + order blocks)** → `STRUCT_ZONES` is the native zone engine:
70
+ `zones_htf_support_type` (`bullish FVG`/`bullish order block`), `zones_htf_support_dist
71
+ between -1.5 0`, `_age_h gte 12`, HTF bias required via `MAalign_htf`; rules
72
+ `structure_fvg_approach`/`structure_ob_approach` required (their `proximityPct` is the
73
+ in-zone dial). Liquidity sweeps, displacement, killzones and event *sequencing* are shapes the
74
+ grammar does not have — a clause compares one column against a literal, so an ordered sequence
75
+ of events cannot be stated at all. That is a grammar limit, not a missing metric; name it as
76
+ one.
77
+ - **Now native, formerly substituted** — WaveTrend (`WT1`/`WT2`), QQE (`QQE_RSI_MA`/`QQE_STOP`),
78
+ Hull (`HMA20`), Ichimoku (`ICHI_CONV`/`ICHI_BASE`/`ICHI_SPAN_A`/`ICHI_SPAN_B`/`ICHI_LAG`),
79
+ Parabolic SAR (`PSAR`), Keltner (`KC_UPPER`/`KC_MID`/`KC_LOWER`), daily pivots
80
+ (`PIVOT_P`/`PIVOT_R1`–`R3`/`PIVOT_S1`–`S3`), Williams %R (`WILLR14`), Stochastic RSI
81
+ (`STOCH_RSI14`), the TTM squeeze (`KC_SQUEEZE`), Connors RSI-2 (`RSI2`), the 9/21/50 EMAs
82
+ (`EMA9`/`EMA21`/`EMA50`), and literal previous-day levels (`PDH`/`PDL`). Port these directly —
83
+ do not offer a substitute for a primitive the catalog serves.