@battlegrid/mcp-server 32.0.18 → 32.0.19

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/dist/index.d.ts CHANGED
@@ -47,7 +47,7 @@ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/cli
47
47
  * being asked. Move it for a change to THIS package — a proxy fix, a dependency bump, a docs
48
48
  * correction. Never move it to track the server.
49
49
  */
50
- export declare const PACKAGE_VERSION = "32.0.18";
50
+ export declare const PACKAGE_VERSION = "32.0.19";
51
51
  export declare const DEFAULT_URL = "https://mcp.battlegrid.trade/mcp";
52
52
  export interface EnvConfig {
53
53
  apiKeys: string[];
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import { Client, SdkHttpError, StreamableHTTPClientTransport } from '@modelconte
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 const PACKAGE_VERSION = '32.0.18';
52
+ export const PACKAGE_VERSION = '32.0.19';
53
53
  export const DEFAULT_URL = 'https://mcp.battlegrid.trade/mcp';
54
54
  const MAX_RETRIES = 3;
55
55
  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": "32.0.18",
3
+ "version": "32.0.19",
4
4
  "description": "BattleGrid MCP server — play crypto prediction games from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "generator": "battlegrid-app/server/scripts/export-mcp-skills.mjs",
3
- "contractVersion": "122.0.0",
3
+ "contractVersion": "123.1.0",
4
4
  "files": {
5
5
  "battlegrid-agent-management/SKILL.md": "4c2cb5cfec27cf11769d66763e024e6900c392e27a978de3080cf55b2ece7a68",
6
6
  "battlegrid-agent-management/references/agent-stage-examples.md": "dffcc353d61dfc7b5bb6a2b3077fc6bf8c760a34c287cafb3b59b99306f24195",
@@ -15,10 +15,10 @@
15
15
  "battlegrid-strategy-authoring/SKILL.md": "39cfa46b1f640e6800edb7e85a8f2c4b6b8786cddf71bb8244d172a44fc11d75",
16
16
  "battlegrid-strategy-authoring/references/strategy-stage-examples.md": "f10ecdc91f3660a708eee7a7bacefce2acbb46c9cf7e0ac1454bd54f839f39fd",
17
17
  "battlegrid-strategy-doctor/SKILL.md": "e34e055b0cc9b355bc1adeb5d49d464eaa0756ca3f48dc89ca2ccdda94e1311f",
18
- "battlegrid-strategy-examples/SKILL.md": "5e0d3244ed0592b8af933c0925346b6bd3bbaeef6c7acb35329925a026e7cd05",
19
- "battlegrid-strategy-examples/references/playbooks.md": "275352be43652ca0fd36a35981ea7ffcfabdc4f9b33293826656ee500d3795e9",
18
+ "battlegrid-strategy-examples/SKILL.md": "9d18bf0e4bf85a28508e2881a4cdf77b823ec831e7cec5d82f82dc251402f41c",
19
+ "battlegrid-strategy-examples/references/playbooks.md": "a9464789df2e762faf9dbd7845101564bc1847cd193f709f8ac4b6bff08f09dd",
20
20
  "battlegrid-strategy-examples/references/recipes.md": "806aae0b4842408f6e6ed3e108293073f4f31d1c27fa419ad57df9e1134f23e7",
21
- "battlegrid-strategy-examples/references/tradingview-ports.md": "9eaa926cbe8606c6a09f91007a0606042afbfdc50173a5b13ae9336d2e9e9b08",
21
+ "battlegrid-strategy-examples/references/tradingview-ports.md": "c62ce72b741abe5a92e1dec6615f26b19a54959aa1d29542518cd9a189390b98",
22
22
  "battlegrid-trade-analysis/SKILL.md": "3f927b4f9796178526fa1673a2043c5f14358c39aa955de33192ea58641f18fb",
23
23
  "battlegrid-trade-proposal/SKILL.md": "e761fc6bddc0d1db79fcb90046d03a18439562c8658de726ec286830edd6369b"
24
24
  }
@@ -32,7 +32,7 @@ Before committing a CREATE, every "no" here is a decision to state, not an omiss
32
32
 
33
33
  | Transform | Header | Validated example |
34
34
  |---|---|---|
35
- | `value` | `<code>` | `bbWidthPct`, `RVOL`, `rate` |
35
+ | `value` | `<code>` | `bbWidthPct`, `RVOL20`, `rate` |
36
36
  | `trajectory` w4 | `<code>_t3…_t1`, `_now`, `_trend` (rising/falling/flat) | `RSI14_now`, `RSI14_trend` |
37
37
  | `distance` | `dist_<code>` (signed % from price) | `dist_SMA50` |
38
38
  | `spread` | `<base>_<operand>_spread` | `mark_oracle_spread` |
@@ -40,8 +40,8 @@ Before committing a CREATE, every "no" here is a decision to state, not an omiss
40
40
  | `rank` | `<code>_rank_<hi\|lo\|far\|near>` — ordinal, `lte N` = top-N | `bbWidthPct_rank_lo` |
41
41
  | `efficiency` wN | `<code>_er` (1 straight, ~0 chop) | `close_ltf_er` |
42
42
  | `maxShare` wN | `<code>_maxShare` | `volBase_ltf_maxShare` |
43
- | `classifyZone` / `classifyState` | `<code>_zone` / `<code>_state` | `RSI14_zone`, `ADX_state` |
44
- | `levelInteraction` | `<code>_lvl`, `<code>_lvl_b<N>` at a buffer of N bps | `bbUpper_lvl`, `bbUpper_lvl_b25` |
43
+ | `classifyZone` / `classifyState` | `<code>_zone` / `<code>_state` | `RSI14_zone`, `ADX14_state` |
44
+ | `levelInteraction` | `<code>_lvl`, `<code>_lvl_b<N>` at a buffer of N bps | `bbUpper20_lvl`, `bbUpper20_lvl_b25` |
45
45
  | any, stored `"bars": "all"` | `<code>_dev`, then the transform's suffix | `RSI14_dev_t1` |
46
46
  | `value` at `offset` N > 0 | `<code>_o<N>` | `RSI14_1d_o1` |
47
47
 
@@ -151,16 +151,16 @@ Recipes — each one clause:
151
151
  `dist_SMA20` is the close's distance from the average, so crossing 0 is the close crossing it.
152
152
  - **RSI entered overbought** — `RSI14_zone enters "overbought"` (the RSI module).
153
153
  - **Squeeze released** — `kcSqueeze exits "on"`, over a `KC_SQUEEZE` value column.
154
- - **New 20-bar high** — `donchianHi increased`, over a `DONCHIAN_UPPER` value column: the 20-bar
154
+ - **New 20-bar high** — `donchianHi20 increased`, over a `DONCHIAN_UPPER` value column: the 20-bar
155
155
  upper channel rises exactly when the decided bar's high exceeds every high of the 20 bars before it.
156
- - **Close above the prior 20-bar high (Donchian / Turtle breakout)** — `dist_donchianHiPrior crossesAbove 0`,
156
+ - **Close above the prior 20-bar high (Donchian / Turtle breakout)** — `dist_donchianHiPrior20 crossesAbove 0`,
157
157
  over a `DONCHIAN_UPPER_PRIOR × distance` column: Pine's `ta.crossover(close, ta.highest(high, 20)[1])`,
158
- TRUE on the first close of a breakout run. `dist_donchianHiPrior gt 0` is Pine's
158
+ TRUE on the first close of a breakout run. `dist_donchianHiPrior20 gt 0` is Pine's
159
159
  `close > ta.highest(high, 20)[1]` and holds on every close of the run. Mirror with
160
- `dist_donchianLoPrior crossesBelow 0` / `lt 0` over `DONCHIAN_LOWER_PRIOR`.
161
- - **Failed breakout of the prior 20-bar high** — `donchianHiPrior_lvl is "reject_from_below"`, over a
160
+ `dist_donchianLoPrior20 crossesBelow 0` / `lt 0` over `DONCHIAN_LOWER_PRIOR`.
161
+ - **Failed breakout of the prior 20-bar high** — `donchianHiPrior20_lvl is "reject_from_below"`, over a
162
162
  `DONCHIAN_UPPER_PRIOR × levelInteraction` column: the high pierced the prior 20-bar high and the close
163
- came back under it. `dist_donchianHiPrior crossesBelow 0` is NOT "fell back into the channel": the rail
163
+ came back under it. `dist_donchianHiPrior20 crossesBelow 0` is NOT "fell back into the channel": the rail
164
164
  rises to each breakout bar's high, so it fires once on the first bar of a breakout run that does not
165
165
  close above the raised rail — the run of new closing highs ended.
166
166
  - **RSI crossed above 50 within the last 3 closes** — `RSI14_now crossesAbove 50` holding
@@ -218,8 +218,8 @@ Inline it when:
218
218
  `conditionRef`, so "together on 3 closes in a row" over a sub-rule needs its clauses inline.
219
219
 
220
220
  Inlining a `NOT` is not always a flipped operator. A clause whose read ran and found no value reads
221
- FALSE, so `NOT [dist_donchianHi gt -0.52]` reads TRUE on a coin with no channel value while
222
- `dist_donchianHi lte -0.52` reads FALSE; `NOT [CVD_state is "bearish"]` and
221
+ FALSE, so `NOT [dist_donchianHi20 gt -0.52]` reads TRUE on a coin with no channel value while
222
+ `dist_donchianHi20 lte -0.52` reads FALSE; `NOT [CVD_state is "bearish"]` and
223
223
  `CVD_state in ["bullish", "neutral"]` part the same way. Flip only a header that always carries a
224
224
  value, or choose deliberately which answer a missing value gives.
225
225
 
@@ -370,7 +370,7 @@ refuses a commit or a rebind.
370
370
  reads the same on every bar of a trend, so `ST_DIR is "bullish"` is a regime filter and never an
371
371
  entry signal. The flip is `ST_DIR enters "bullish"`: TRUE on the one completed bar the direction
372
372
  turned, with no event column needed. The same distinction applies to every persisting
373
- classification: `MAalign`, `ADX_state`, `zone` — `is` for the regime, `enters` / `exits` for the
373
+ classification: `MAalign`, `ADX14_state`, `zone` — `is` for the regime, `enters` / `exits` for the
374
374
  bar it changed.
375
375
 
376
376
  **Name the anchor when a metric is calibrated for one.** `KC_SQUEEZE is "on"` reads *on* about 57% of
@@ -397,12 +397,12 @@ reached the level and whose close came back under it is `reject_from_below`; a b
397
397
  within the buffer of the level is `touch_from_below`. From above is the mirror on the low.
398
398
 
399
399
  - **A reject bar reports reject.** It is never also reported as a touch, so "touched or rejected" is
400
- an `in` clause over both labels of a side: `bbUpper_lvl in ["touch_from_below","reject_from_below"]`.
400
+ an `in` clause over both labels of a side: `bbUpper20_lvl in ["touch_from_below","reject_from_below"]`.
401
401
  - **`buffer`** is whole basis points of the level, 0–500, default 0 — `buffer: 25` is 0.25%. Only the
402
- touch reads it: a reject must pierce the level itself. Two buffers are two columns (`bbUpper_lvl`,
403
- `bbUpper_lvl_b25`).
402
+ touch reads it: a reject must pierce the level itself. Two buffers are two columns (`bbUpper20_lvl`,
403
+ `bbUpper20_lvl_b25`).
404
404
  - **A break is a crossing, never a label.** The break of a level is `distance` over the same level
405
- crossing 0: `dist_bbUpper crossesAbove 0` from below, `crossesBelow 0` from above. On the strategy's
405
+ crossing 0: `dist_bbUpper20 crossesAbove 0` from below, `crossesBelow 0` from above. On the strategy's
406
406
  own rung a `…_from_below` label never holds on a bar where `crossesAbove 0` holds.
407
407
  - **Which levels take it.**
408
408
  - **Offered:** a candle-homed level — a band (`BB_UPPER`, `BB_LOWER`, `KC_UPPER`, `KC_LOWER`),
@@ -420,9 +420,9 @@ within the buffer of the level is `touch_from_below`. From above is the mirror o
420
420
  (`DONCHIAN_UPPER`, `DONCHIAN_LOWER`), each the extreme of the window ending at the very bar it would
421
421
  classify, which a close can never pass — read `DONCHIAN_UPPER_PRIOR` / `DONCHIAN_LOWER_PRIOR`. A
422
422
  price fact (`CLOSE`, `HLC3`, `MARK`) is refused: it is the price, not a level.
423
- - **A higher-rung level reads that rung's completed bar.** `bbUpper_htf_lvl` classifies the last
423
+ - **A higher-rung level reads that rung's completed bar.** `bbUpper20_htf_lvl` classifies the last
424
424
  completed regime-rung bar from its own high, low and close, never the strategy bar's price — so
425
- compose its break on the same rung, `dist_bbUpper_htf crossesAbove 0`.
425
+ compose its break on the same rung, `dist_bbUpper20_htf crossesAbove 0`.
426
426
 
427
427
  **`BB_TOUCH` and `BB_UPPER × levelInteraction` answer different questions.** `BB_TOUCH` (`BBtouch`)
428
428
  says where the CLOSE sits in %B — inside the band edge or not. `BB_UPPER × levelInteraction` says what
@@ -464,7 +464,7 @@ within N reads each of the N bars against its own predecessor.
464
464
  Recipes — the level recipes on candle-homed levels:
465
465
 
466
466
  - **Price rejected the upper Bollinger band** — a `BB_UPPER × levelInteraction` column, header
467
- `bbUpper_lvl`; the clause `bbUpper_lvl is "reject_from_below"`.
467
+ `bbUpper20_lvl`; the clause `bbUpper20_lvl is "reject_from_below"`.
468
468
  - **Price touched or rejected VWAP from above** — `VWAP_lvl in ["touch_from_above","reject_from_above"]`,
469
469
  with `buffer: 10` for a touch within 0.1% (header `VWAP_lvl_b10`).
470
470
  - **Price broke the 20 EMA** — `dist_EMA20 crossesAbove 0`, the `distance` column over the same level.
@@ -25,8 +25,8 @@ Custom `Squeeze Scan` (no `sectionKey` — this is a CREATE): `BB_WIDTH_PCT valu
25
25
  `bbWidthPct_rank_lo` a second time, which makes both of `SQUEEZE_ON`'s clauses ambiguous, and the
26
26
  custom section already carries them.) Conditions: `LIQUID_FLOOR` (required —
27
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`). Levels 0.75–1.75 ATR,
28
+ `ADX14 lt 20`); `BREAK_UP` verdict UP (ref SQUEEZE_ON + `closeChg gt 0` + `RVOL20 gte 1.5` +
29
+ `pctB20 gte 0.85`); `BREAK_DOWN` verdict DOWN (mirror with `pctB20 lte 0.15`). Levels 0.75–1.75 ATR,
30
30
  RR 2, ATR floor 0.8% — a compression play needs the expansion to be worth the stop. PM: BE 1R; trail 1.2R giveback 35 buffer 0.3.
31
31
  Market Read: "Trade only expansions out of compression: {SQUEEZE_ON} must read TRUE… skip
32
32
  anything failing {LIQUID_FLOOR}."
@@ -54,8 +54,8 @@ refused, and its legal `far` ordering sorts by |change| — which would put the
54
54
  losers in a "leaders" cohort. Custom `BTC Regime` with
55
55
  `"benchmarkTicker": "BTC"`: `MA_ALIGN value`→`MAalign`, `ADX value`, `REGIME_TREND value` —
56
56
  every row reads BTC. Conditions: `BTC_RISK_ON` building block (`MAalign is "bullish"` +
57
- `ADX gte 20` — both read bare, since only this section renders them); `BTC_RISK_OFF`
58
- (`MAalign is "bearish"`); `LEADER` (`roc12_rank_hi lte 5` + `RVOL_rank_hi lte 10` +
57
+ `ADX14 gte 20` — both read bare, since only this section renders them); `BTC_RISK_OFF`
58
+ (`MAalign is "bearish"`); `LEADER` (`roc12_rank_hi lte 5` + `RVOL20_rank_hi lte 10` +
59
59
  `vol24hUsd gte 100000000`); `ROTATE_IN` verdict UP (`ALL[ref BTC_RISK_ON, ref LEADER]`);
60
60
  `ROTATE_OUT` verdict DOWN (`ALL[ref BTC_RISK_OFF, roc12_rank_lo lte 5]`). Levels 1–2 ATR, RR 1.8,
61
61
  ATR floor 0.6%. PM: BE 1R; trail 1R giveback 45. Ranked cohort ≥ 40 —
@@ -63,12 +63,12 @@ rank conditions need a cohort wider than their thresholds.
63
63
 
64
64
  ### 4 · HTF Trend Pullback — 1h, multi-timeframe confluence
65
65
 
66
- Platform `includeMtfConfluence` (`MAalign_ltf/…/_htf`, `RSI14_*_zone`, `ADX_*_state` vocab
66
+ Platform `includeMtfConfluence` (`MAalign_ltf/…/_htf`, `RSI14_*_zone`, `ADX14_*_state` vocab
67
67
  weak/developing/trending/extreme), `includeMovingAverages` (`dist_EMA20`, …), `includeRsi`
68
68
  (`RSI14_now`, `RSI14_zone`). These three render `MAalign` and `RSI14_zone` twice between them, so
69
69
  every condition below reads a suffixed form — never bare `MAalign` or bare `RSI14_zone`, which
70
70
  would be ambiguous. Conditions: `HTF_UP` building block (`MAalign_htf is "bullish"` +
71
- `ADX_htf_state in ["trending","extreme"]`); `HTF_DOWN` mirror; `TREND_PRESENT` **required**
71
+ `ADX14_htf_state in ["trending","extreme"]`); `HTF_DOWN` mirror; `TREND_PRESENT` **required**
72
72
  `ANY[ref HTF_UP, ref HTF_DOWN]` — chop blocks compose-trade entirely, before billing;
73
73
  `PULLBACK_LONG` verdict UP (ref HTF_UP + `RSI14_now between 35 55` +
74
74
  `dist_EMA20 between -3 0.5`); `PULLBACK_SHORT` verdict DOWN (mirror, 45–65 / −0.5–3). Levels
@@ -82,9 +82,9 @@ perp_led_fragile / spot_led_accumulation / neutral), `includeStructureZones`
82
82
  (`BUY_PRESSURE value`→`buyPres` 0–1, `RVOL value`, `CLOSE_CHANGE value`). Conditions:
83
83
  `SPOT_ACCUM` (`perpSpotFlow is "spot_led_accumulation"`); `NEAR_SUPPORT`
84
84
  (`zones_htf_support_dist between 0 2` + `zones_htf_support_age_h gte 12`); `DIP_BID` verdict
85
- UP — refs plus an `N_OF(2)` quorum over `buyPres gte 0.55` / `RVOL gte 1.2` / `closeChg gt 0`
85
+ UP — refs plus an `N_OF(2)` quorum over `buyPres gte 0.55` / `RVOL20 gte 1.2` / `closeChg gt 0`
86
86
  (quorum beats a brittle ALL); `FRAGILE_POP` verdict DOWN (`perp_led_fragile` +
87
- `zones_htf_resist_dist between -2 0` + `RVOL gte 1.3`). Levels 0.5–1.2 ATR, RR 1.5, ATR floor
87
+ `zones_htf_resist_dist between -2 0` + `RVOL20 gte 1.3`). Levels 0.5–1.2 ATR, RR 1.5, ATR floor
88
88
  0.4%. PM: BE 0.7R; trail 0.9R
89
89
  giveback 30 buffer 0.15; timeDecay ON aggressive (45/15, 15→60, stale 30).
90
90
 
@@ -13,6 +13,7 @@ engine) and names its substitutions where the catalog lacks a primitive.
13
13
  - Donchian / Turtle breakout
14
14
  - ICT / SMC (FVG + order blocks)
15
15
  - Pivot highs / lows, HH-HL structure and break of structure
16
+ - Chaikin Money Flow
16
17
  - Now native, formerly substituted
17
18
 
18
19
  ## TradingView ports — familiar processes, studio vocabulary
@@ -34,7 +35,7 @@ rules about the columns themselves, not about porting — they live in the skill
34
35
  `BB_LOWER spread KC_LOWER` still express the same reading at a threshold you choose. Release
35
36
  direction from the MACD trajectory, `bollinger_squeeze` Critical-required.
36
37
  Its selectivity is anchor-dependent — see `## Conditions` in the skill body before gating on it.
37
- (`bbWidthPct_rank_lo` + `ADX lt 20` remains a serviceable board-relative compression proxy, but it
38
+ (`bbWidthPct_rank_lo` + `ADX14 lt 20` remains a serviceable board-relative compression proxy, but it
38
39
  is no longer the only option.)
39
40
  - **Supertrend / UT Bot / Chandelier Exit** → `ST_LINE` and `ST_DIR` are native, so the regime
40
41
  half is a direct port: `ST_DIR is "bullish"` as the persistent state, `dist_ST_LINE` for
@@ -58,16 +59,16 @@ rules about the columns themselves, not about porting — they live in the skill
58
59
  reading while the literal is a threshold the author can see and tune. Use the zone as a report
59
60
  column, never as the substitute for the gate.
60
61
  - **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
+ stretch; required `NOT [ADX14_state in ["trending","extreme"]]` veto; scalp geometry +
62
63
  aggressive timeDecay (VWAP anchors daily at 00:00 UTC).
63
- - **Donchian / Turtle breakout** (`close > ta.highest(high, 20)[1]`) → `dist_donchianHiPrior crossesAbove 0`
64
- over `DONCHIAN_UPPER_PRIOR × distance` (the breakout bar; `dist_donchianHiPrior gt 0` holds on every
65
- close of the run) + `RVOL gte 1.5`; turtle exits = trend preset (trail from 1R, giveback 50). Mirror with
66
- `dist_donchianLoPrior crossesBelow 0`. The same-bar rail (`dist_donchianHi`) includes the breakout bar's
64
+ - **Donchian / Turtle breakout** (`close > ta.highest(high, 20)[1]`) → `dist_donchianHiPrior20 crossesAbove 0`
65
+ over `DONCHIAN_UPPER_PRIOR × distance` (the breakout bar; `dist_donchianHiPrior20 gt 0` holds on every
66
+ close of the run) + `RVOL20 gte 1.5`; turtle exits = trend preset (trail from 1R, giveback 50). Mirror with
67
+ `dist_donchianLoPrior20 crossesBelow 0`. The same-bar rail (`dist_donchianHi20`) includes the breakout bar's
67
68
  own high, so it can never confirm a close-based break — do not AND it in. `zone is "breakout high"` reads
68
- the prior-bar rail: it is the label form of the `dist_donchianHiPrior gt 0` state, holds on every close of
69
+ the prior-bar rail: it is the label form of the `dist_donchianHiPrior20 gt 0` state, holds on every close of
69
70
  the run, and adds nothing to the crossing; `"breakdown low"` mirrors it.
70
- **Daily-breakout variant on any anchor:** pin the rail at 1d — `dist_donchianHiPrior_1d crossesAbove 0`
71
+ **Daily-breakout variant on any anchor:** pin the rail at 1d — `dist_donchianHiPrior20_1d crossesAbove 0`
71
72
  (`gt 0` for the held state).
72
73
  **Literal previous-day levels are native**: `dist_PDH gte 0` composes directly. They are TIMELESS
73
74
  and take `{rel: 'anchor'}` — see `## Header grammar` in the skill body.
@@ -85,8 +86,20 @@ rules about the columns themselves, not about porting — they live in the skill
85
86
  not), and each level is the stepped, non-repainting series — held from the bar that completes the
86
87
  pivot's right side, not plotted back on the pivot bar. Break of structure is
87
88
  `dist_fractalHi crossesAbove 0` / `dist_fractalLo crossesBelow 0`; "structure is bullish" is
88
- `fractalLabel in ["HH","HL"]`. Only two bars each side is native: a `ta.pivothigh(5, 5)` or longer
89
- script ports onto the two-bar swing, and the spec-lock question says so.
89
+ `fractalLabel in ["HH","HL"]`. The pivot each latest one is compared with is native too:
90
+ `PRIOR_SWING_FRACTAL_HIGH` / `PRIOR_SWING_FRACTAL_LOW` (`pFractalHi` / `pFractalLo`, Pine's
91
+ `ta.valuewhen(not na(ph), ph, 1)`) are price levels held while the prior pivot is in reach, so
92
+ `dist_pFractalHi crossesAbove 0` is a close through the swing high before the last one. A pivot's age is
93
+ `SWING_FRACTAL_HIGH_BARS_AGO` / `SWING_FRACTAL_LOW_BARS_AGO` (`fractalHiAgo` / `fractalLoAgo`), counted
94
+ from the pivot's own bar: 2 on the bar that completes it, so it reads Pine's
95
+ `ta.barssince(not na(ph)) + 2`, and `fractalLoAgo lte 5` keeps a setup to a fresh swing low. Only two
96
+ bars each side is native: a `ta.pivothigh(5, 5)` or longer script ports onto the two-bar swing, and the
97
+ spec-lock question says so.
98
+ - **Chaikin Money Flow** (TradingView's built-in "Chaikin Money Flow" script, length 20; Pine has no
99
+ `ta.cmf`) → `CMF20` is native: each bar's volume weighted by where it closed in its range, summed over
100
+ 20 closed bars and divided by their volume, −1 to +1. `CMF20 gt 0` is the accumulation state and
101
+ `CMF20_cross is "Bullish"` the zero-line cross; a flat bar weighs 0, and a window that traded no volume
102
+ reads no value, so a clause over it reads FALSE. It reads closed bars only, never the forming one.
90
103
  - **Now native, formerly substituted** — WaveTrend (`WT1`/`WT2`), QQE (`QQE_RSI_MA`/`QQE_STOP`),
91
104
  Hull (`HMA20`), Ichimoku (`ICHI_CONV`/`ICHI_BASE`/`ICHI_SPAN_A`/`ICHI_SPAN_B`/`ICHI_LAG`),
92
105
  Parabolic SAR (`PSAR`), Keltner (`KC_UPPER`/`KC_LOWER`, its basis is `EMA20`), the MACD and PPO lines
@@ -95,7 +108,10 @@ rules about the columns themselves, not about porting — they live in the skill
95
108
  line minus signal), the 15-bar rate of change (`ROC15`), the Donchian break memory
96
109
  (`DONCHIAN_BROKEN_HI`/`DONCHIAN_BROKEN_LO`), daily pivots
97
110
  (`PIVOT_P`/`PIVOT_R1`–`R3`/`PIVOT_S1`–`S3`), Williams %R (`WILLR14`), Stochastic RSI
98
- (`STOCH_RSI14`), the TTM squeeze (`KC_SQUEEZE`), Connors RSI-2 (`RSI2`), the 9/21/50 EMAs
99
- (`EMA9`/`EMA21`/`EMA50`), literal previous-session levels (`PDH`/`PDL`/`PDO`), and fractal swing
100
- structure (`SWING_FRACTAL_HIGH`/`SWING_FRACTAL_LOW`/`SWING_FRACTAL_LABEL`). Port these directly —
101
- do not offer a substitute for a primitive the catalog serves.
111
+ (`STOCH_RSI14`), the TTM squeeze (`KC_SQUEEZE`), Connors RSI-2 (`RSI2`), the 8/9/21/50 EMAs
112
+ (`EMA8`/`EMA9`/`EMA21`/`EMA50` — `ta.ema(close, 8)` is `EMA8`, its distance `dist_EMA8`), literal
113
+ previous-session levels (`PDH`/`PDL`/`PDO`), fractal swing structure
114
+ (`SWING_FRACTAL_HIGH`/`SWING_FRACTAL_LOW`/`SWING_FRACTAL_LABEL`, the prior pivots
115
+ `PRIOR_SWING_FRACTAL_HIGH`/`PRIOR_SWING_FRACTAL_LOW` and their ages
116
+ `SWING_FRACTAL_HIGH_BARS_AGO`/`SWING_FRACTAL_LOW_BARS_AGO`), and Chaikin Money Flow (`CMF20`). Port
117
+ these directly — do not offer a substitute for a primitive the catalog serves.