@battlegrid/mcp-server 32.0.30 → 32.0.32

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.30";
50
+ export declare const PACKAGE_VERSION = "32.0.32";
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.30';
52
+ export const PACKAGE_VERSION = '32.0.32';
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.30",
3
+ "version": "32.0.32",
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": "134.0.0",
3
+ "contractVersion": "135.1.0",
4
4
  "files": {
5
5
  "battlegrid-agent-management/SKILL.md": "91d1be4de1e33d54e1852c970af66ad176068fd16c30b4326e49053b7a35eb32",
6
6
  "battlegrid-agent-management/references/agent-stage-examples.md": "dffcc353d61dfc7b5bb6a2b3077fc6bf8c760a34c287cafb3b59b99306f24195",
@@ -9,16 +9,16 @@
9
9
  "battlegrid-market-analysis/SKILL.md": "fcbe98e30e1edce39bb170c224cdf20181934f62b5f3a26b293787c70eeb6229",
10
10
  "battlegrid-officers/SKILL.md": "0e5b4fd7a11fc1b6643eecd0008dfe1528b915ee00d0a99be9aca1e3934d57f7",
11
11
  "battlegrid-officers/references/officer-stage-examples.md": "ef46ba26f4b36c9964b5bde95fdf98e5952c3786b77be5f136056cac792f0aec",
12
- "battlegrid-radar-deployment/SKILL.md": "42900772033d799ce8eeeea9a433d76dd54fb5b153c83951753f73859dcfc021",
12
+ "battlegrid-radar-deployment/SKILL.md": "cd058d887e1342f42ce855708178d5f746400835a204e92238b63ec1fe57bf30",
13
13
  "battlegrid-radar-deployment/references/arena-stage-examples.md": "d27cfed1f4663f267c4a7ecebe4a6e65a450fc74bd14ce8987fb61af5bbf2ca5",
14
14
  "battlegrid-radar-deployment/references/radar-stage-examples.md": "13fc3f55d91d3da9754e1ed7fb27493b7b7ba4d40a6a7e8d1ee0365f90ea10de",
15
- "battlegrid-strategy-authoring/SKILL.md": "39cfa46b1f640e6800edb7e85a8f2c4b6b8786cddf71bb8244d172a44fc11d75",
15
+ "battlegrid-strategy-authoring/SKILL.md": "e2422b0e02de6d240bbab261ed1181b32d175bf877f499d7df556eca31ce5133",
16
16
  "battlegrid-strategy-authoring/references/strategy-stage-examples.md": "f10ecdc91f3660a708eee7a7bacefce2acbb46c9cf7e0ac1454bd54f839f39fd",
17
17
  "battlegrid-strategy-doctor/SKILL.md": "24c2208cf6832f1fc34e1b1c52acd8dae10f56b9860aaf27b450a62a9fb74634",
18
- "battlegrid-strategy-examples/SKILL.md": "8b86ab1bbc4fbd743ef56c0fbcb0134d4238f9a72641e5eb66dfbf20cfc139ed",
18
+ "battlegrid-strategy-examples/SKILL.md": "cbbc7a7271aaa9f530a11d91511f4bf35a9632aa3ab67ecd996975c65ae72625",
19
19
  "battlegrid-strategy-examples/references/playbooks.md": "59996c37eea08e7ce398e49304171939e55c968400a1603597d6737faf9e1771",
20
- "battlegrid-strategy-examples/references/recipes.md": "806aae0b4842408f6e6ed3e108293073f4f31d1c27fa419ad57df9e1134f23e7",
21
- "battlegrid-strategy-examples/references/tradingview-ports.md": "c62ce72b741abe5a92e1dec6615f26b19a54959aa1d29542518cd9a189390b98",
20
+ "battlegrid-strategy-examples/references/recipes.md": "95966649a68ec488a232b150328d94e9f80c45eff30be4da92caf305fba20fb0",
21
+ "battlegrid-strategy-examples/references/tradingview-ports.md": "00f2ba287ff8fadf356273f61ad19630cceb3a706018c2088be40570ff1a899d",
22
22
  "battlegrid-trade-analysis/SKILL.md": "140d847f24e98845736d4f0c2e72b318a4d235413f72f072d2fe6d0daa5f64db",
23
23
  "battlegrid-trade-proposal/SKILL.md": "bf8f3794478cdecc989020bd775c3c91744f72b440b1a90f88f6700c9a61ca24"
24
24
  }
@@ -127,6 +127,10 @@ from what the player asked for:
127
127
  | only in named hours and days | a rule with an hours condition |
128
128
  | in named regimes during named hours | a rule with one hours condition and one regime condition |
129
129
 
130
+ Radar's market hours answer *when may this agent trade*, and shift with daylight saving; a strategy's
131
+ `TRADING_SESSION` condition answers *which session is this bar in*, over fixed UTC windows — the two are
132
+ different questions, and a player asking for one is not asking for the other.
133
+
130
134
  When the player names an exchange session — New York, the New York open, London, the London open,
131
135
  the London–New York overlap, Tokyo, the Tokyo open — call `get_radar_market_hours` and stage that session's `hours`
132
136
  exactly as served, merged with any other session they name; never work session hours out yourself,
@@ -171,9 +171,13 @@ question you would otherwise guess — and a refusal you would otherwise earn:
171
171
  transform (and a chained transform) reads its series: the read shape, the window's unit, the price
172
172
  basis and what an absent bar does. `seriesHome` says where the metric's series is kept — `candle`,
173
173
  `history`, or `null` when the metric has no series, which is why a series transform does not
174
- compose on it. Window and offset carry a declared `minimum` and a served `maximum` on each metric's
175
- transform entry — the largest value whose read size fits the lookback cap, or `null` when none
176
- does. Stage at most that maximum.
174
+ compose on it. Window and offset carry a declared `minimum` and a served `maximum` on the transform
175
+ entry — the largest value whose read size fits the lookback cap, or `null` when none does. For a
176
+ chained column the outer's declaration is `chainOuterParameters[outer]` (served beside `metric`)
177
+ and its bound is `chainedOuters[outer].windowMaximum`. The window carries a `unit`
178
+ (`seriesElements`, `seriesChanges` for `efficiency`, which reads one bar more than its window, or
179
+ `priorElements` for `pctl`). Stage at most that maximum. A chained column's operands come from
180
+ `chainedOuters[outer].operands`, never from the metric's full `spreadOperands`.
177
181
  - `get_metric_construction_hints` → `rankOrderings`. Present only when rank is composable on that
178
182
  metric, already range-gated server-side — read the offered set rather than deriving one from
179
183
  the metric's native output.
@@ -38,8 +38,13 @@ Before committing a CREATE, every "no" here is a decision to state, not an omiss
38
38
  | `spread` | `<base>_<operand>_spread` | `mark_oracle_spread` |
39
39
  | `aggregate` wN | `<code>_mean<N>` | `rate_mean24` |
40
40
  | `rank` | `<code>_rank_<hi\|lo\|far\|near>` — ordinal, `lte N` = top-N | `bbWidthPct_rank_lo` |
41
- | `efficiency` wN | `<code>_er` (1 straight, ~0 chop) | `close_ltf_er` |
41
+ | `efficiency` wN | `<code>_er` (1 straight, ~0 chop) — N changes, reading N + 1 bars | `close_ltf_er` |
42
42
  | `maxShare` wN | `<code>_maxShare` | `volBase_ltf_maxShare` |
43
+ | `zscore` wN | `<code>_z<N>` (population σ; bands ±1, ±2) | `RSI14_z20` |
44
+ | `stddev` wN | `<code>_sd<N>` (population σ, the input's units) | `close_sd20` |
45
+ | `linregSlope` wN | `<code>_slope<N>` (least-squares slope per bar) | `EMA20_slope20` |
46
+ | `pctl` wN | `<code>_pctl<N>` (0–100 against the N prior bars) | `RSI14_pctl20` |
47
+ | `rollingMin` / `rollingMax` wN | `<code>_min<N>` / `<code>_max<N>` | `low_min20`, `high_max20` |
43
48
  | `classifyZone` / `classifyState` | `<code>_zone` / `<code>_state` | `RSI14_zone`, `ADX14_state` |
44
49
  | `levelInteraction` | `<code>_lvl`, `<code>_lvl_b<N>` at a buffer of N bps | `bbUpper20_lvl`, `bbUpper20_lvl_b25` |
45
50
  | any, stored `"bars": "all"` | `<code>_dev`, then the transform's suffix | `RSI14_dev_t1` |
@@ -77,20 +82,30 @@ add. Either way you never invent one. To section-qualify a duplicated header, re
77
82
  `detail: "detailed"` preview's `authoring.conditionColumns` or from the qualified candidates a
78
83
  `CONDITION_COLUMN_AMBIGUOUS` refusal offers.
79
84
 
85
+ **A session window is an ordinary condition.** `TRADING_SESSION` (`asia` 00–08, `london` 08–13,
86
+ `newYork` 13–21, `late` 21–24 UTC) and `DAY_OF_WEEK` (0 = Sunday … 6 = Saturday, UTC) are bar facts of the
87
+ bar's open, fixed in UTC with no daylight-saving rule — their glosses state the windows. A weekday London
88
+ or New York window is `ALL(TRADING_SESSION in ["london","newYork"], DAY_OF_WEEK between 1 and 5)`, and a
89
+ weekend is `NOT(DAY_OF_WEEK between 1 and 5)`; no strategy setting expresses one.
90
+
80
91
  **Event columns print only on their event.** `MACDhist_cross` and `EMA5_13` (Bullish/Bearish) carry a
81
92
  value on the crossing bar and are null on every other one, and a null reads FALSE. Use an event
82
93
  column as a TRIGGER inside a carrier, and pair it with a persistent state — a spread sign,
83
94
  `MAalign` — for regime. A condition that treats an event column as a standing state reads FALSE on
84
95
  nearly every bar, so it is TRUE only on the crossing bar itself.
85
96
 
86
- **The previous-session levels are TIMELESS.** `PDH`, `PDL`, `PDO` and the seven floor pivots
87
- (`pivotP`, `pivotR1`–`R3`, `pivotS1`–`S3`) read the session profile, not a candle rung, so they take
97
+ **The previous-UTC-day levels are TIMELESS.** `PDH`, `PDL`, `PDO` and the seven floor pivots
98
+ (`pivotP`, `pivotR1`–`R3`, `pivotS1`–`S3`) read the UTC-day profile, not a candle rung, so they take
88
99
  no timeframe reference at all: bind them `{rel: 'anchor'}` — the default — and they serve the same
89
- previous UTC session from any anchor. An absolute reference is REFUSED at save, including `1d`,
100
+ previous UTC day from any anchor. The trading-session levels (`asiaHi`, `pLondonHi`, `nyOpen`, …) read
101
+ the trading-session profile the same way, each session fixed in UTC with the window its label names. An
102
+ absolute reference is REFUSED at save, including `1d`,
90
103
  because there is no rung for one to select. They are catalogued price levels, so `dist_PDH gte 0`
91
104
  composes directly. (`distance` still rejects an `offset`, and a clause still compares a column
92
105
  with a literal or with its own previous completed bar, never with another column; neither of those
93
- shapes is what a previous-session level needed.) The same holds
106
+ shapes is what a previous-UTC-day level needed.) A `spread` of a bar's own value against one of these
107
+ levels reads `null` on a bar outside the UTC day (or trading session) the level belongs to, so the first
108
+ bars after 00:00 UTC are never measured against the next day's level. The same holds
94
109
  for every timeless metric — these levels, funding, the regime and the other bundle
95
110
  reads: a `value` column over one accepts only `offset: 0`, and a larger offset is REFUSED at save,
96
111
  because there is no earlier bar to cut back to.
@@ -392,7 +407,7 @@ below when it closed at or under it. For a level computed from its own bar (a ba
392
407
  average, `ST_LINE`, `PSAR`, the Ichimoku conversion and base lines) that is the prior bar's level, so a
393
408
  level that moved between the two bars is judged on the bar it stood at; for a level whose price was fixed
394
409
  before the bar (a prior-bar Donchian rail, the break memory, a swing level, the displaced Ichimoku lines, a
395
- prior-session level or pivot) it is the bar's own level. From below: a bar that closes through the level is a break and reads `none`; a bar whose high
410
+ prior-UTC-day or previous trading-session level or pivot) it is the bar's own level. From below: a bar that closes through the level is a break and reads `none`; a bar whose high
396
411
  reached the level and whose close came back under it is `reject_from_below`; a bar whose high came
397
412
  within the buffer of the level is `touch_from_below`. From above is the mirror on the low.
398
413
 
@@ -409,12 +424,15 @@ within the buffer of the level is `touch_from_below`. From above is the mirror o
409
424
  `VWAP`, a moving average (`SMA20`, `EMA20`, …), `ST_LINE`, `PSAR`, the prior-bar Donchian rails
410
425
  (`DONCHIAN_UPPER_PRIOR`, `DONCHIAN_LOWER_PRIOR`), the Donchian break memory (`DONCHIAN_BROKEN_HI`,
411
426
  `DONCHIAN_BROKEN_LO`), the Ichimoku lines, the swing levels (`SWING_FRACTAL_HIGH`,
412
- `SWING_FRACTAL_LOW`) — reads its own value at each bar. A prior-session level or pivot (`PDH`, `PDL`,
413
- `PDO`, `PIVOT_P`, `PRIOR_TPO_POC`, …) has one reading for the session; a clause over it is a single
414
- read with no hold, because the session profile keeps no earlier reading. On `PSAR` a wick that
427
+ `SWING_FRACTAL_LOW`) — reads its own value at each bar. A prior-UTC-day level or pivot (`PDH`, `PDL`,
428
+ `PDO`, `PIVOT_P`, `PRIOR_TPO_POC`, …) or a previous trading-session level (`PRIOR_LONDON_HIGH`, …) has
429
+ one reading for its UTC day or session; a clause over it is a single read with no hold, because the
430
+ profile keeps no earlier reading, and on a bar outside that day or session it reads `null`, so the
431
+ day's last bar is never classified against the next day's level. On `PSAR` a wick that
415
432
  trades through the SAR flips it on that bar, so a rejection needs the low (high) to meet the SAR
416
433
  exactly and a touch needs a buffer; its flip is `dist_PSAR crossesAbove 0` / `crossesBelow 0`.
417
- - **Not offered:** a developing-session level (`TPO_POC`, `TPO_VAH`, …), the naked points of
434
+ - **Not offered:** a developing-UTC-day level (`TPO_POC`, `TPO_VAH`, …), the current trading
435
+ session's levels, the opening ranges and the IB extensions, the naked points of
418
436
  control and the single prints (`TPO_SINGLE_PRINT_ABOVE`/`_BELOW`, `NAKED_SINGLE_PRINT_ABOVE`/`_BELOW`),
419
437
  which move between bars with no earlier reading kept; and the same-bar Donchian rails
420
438
  (`DONCHIAN_UPPER`, `DONCHIAN_LOWER`), each the extreme of the window ending at the very bar it would
@@ -502,7 +520,8 @@ its side replaces it.
502
520
  clause over it reads FALSE.
503
521
  - **A swing level does not remember a broken level.** Two to four bars after a break the level steps
504
522
  to the next swing, so a retest of the BROKEN level is not a swing-level read.
505
- - **The two levels take** `distance`, `spread`, `aggregate`, `trajectory`, `efficiency` and
523
+ - **The two levels take** `distance`, `spread`, `aggregate`, `trajectory`, `efficiency`, the
524
+ statistics (`zscore`, `stddev`, `linregSlope`, `pctl`, `rollingMin`, `rollingMax`) and
506
525
  `levelInteraction`, and both rank across the board
507
526
  (`dist_fractalHi_rank_near`: the coins closest to their own last swing high).
508
527
  - **The break is the crossing toward the level.** `dist_fractalHi crossesAbove 0` is a close through the
@@ -30,7 +30,7 @@ rather than events all live there. This document assumes them.
30
30
  | Closed-bar-only trajectory | add `"bars": "closed"` to any trajectory | same, drawn from closed bars |
31
31
  | Extension from trend | `{ "metric": "SMA50", "transformId": "distance", "timeframe": { "rel": "anchor" } }` | `dist_SMA50` (signed %) |
32
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) |
33
+ | Chop filter on the entry rung | `{ "metric": "CLOSE", "transformId": "efficiency", "timeframe": { "rel": "lower" }, "window": 4, "bars": "closed" }` | `close_ltf_er` (≥0.6 directional; 4 changes, the last 5 closes) |
34
34
  | One-bar volume concentration | `{ "metric": "VOLUME", "transformId": "maxShare", "timeframe": { "rel": "lower" }, "window": 4, "bars": "closed" }` | `volBase_ltf_maxShare` |
35
35
  | Room to structure | `{ "metric": "STRUCT_ZONES", "transformId": "nearestZoneDist", "timeframe": { "rel": "regime" }, "side": "resistance" }` | `zones_htf_resist_dist` |
36
36
  | Last **closed** daily read | `{ "metric": "RSI14", "transformId": "value", "timeframe": { "abs": "1d" }, "bars": "closed" }` | `RSI14_1d` — the newest completed daily bar |
@@ -15,6 +15,7 @@ engine) and names its substitutions where the catalog lacks a primitive.
15
15
  - Pivot highs / lows, HH-HL structure and break of structure
16
16
  - Chaikin Money Flow
17
17
  - Now native, formerly substituted
18
+ - Statistics over a series' own history
18
19
 
19
20
  ## TradingView ports — familiar processes, studio vocabulary
20
21
 
@@ -25,7 +26,7 @@ the thing itself. Most TV strategies run on the daily chart: carry that with the
25
26
  daily-strategy pattern above (pinned-1d thesis with `"bars": "closed"` at offset 0 on an intraday anchor), which
26
27
  binds decisions to daily closes while the studio keeps managing risk intraday.
27
28
 
28
- Event-column behaviour, state-vs-event, previous-session-level binding and the squeeze's anchor calibration are
29
+ Event-column behaviour, state-vs-event, previous-UTC-day-level binding and the squeeze's anchor calibration are
29
30
  rules about the columns themselves, not about porting — they live in the skill body's
30
31
  `## Header grammar` and `## Conditions`. Read those first; this document is the per-script recipes.
31
32
 
@@ -71,10 +72,19 @@ rules about the columns themselves, not about porting — they live in the skill
71
72
  **Daily-breakout variant on any anchor:** pin the rail at 1d — `dist_donchianHiPrior20_1d crossesAbove 0`
72
73
  (`gt 0` for the held state).
73
74
  **Literal previous-day levels are native**: `dist_PDH gte 0` composes directly. They are TIMELESS
74
- and take `{rel: 'anchor'}` — see `## Header grammar` in the skill body.
75
+ and take `{rel: 'anchor'}` — see `## Header grammar` in the skill body. The previous UTC day is the
76
+ 00:00–24:00 UTC day, and a level read against a bar of the next day is `null`.
77
+ - **Session highs / lows, opening range, IB extensions** → the trading-session levels are native:
78
+ `ASIA_HIGH`, `LONDON_LOW`, `NEW_YORK_OPEN` and their previous instances (`PRIOR_LONDON_HIGH`, …), each
79
+ session fixed in UTC (asia 00–08, london 08–13, newYork 13–21) with no daylight-saving rule, the UTC
80
+ day's opening ranges (`OR15_HIGH`/`OR15_LOW`, `OR30_HIGH`/`OR30_LOW`) and the IB extensions
81
+ (`IB_EXT_UP_150` … `IB_EXT_DOWN_200`). A session window (`TRADING_SESSION in ["london","newYork"]`,
82
+ `DAY_OF_WEEK between 1 and 5`) is an ordinary condition; the glosses state the windows.
75
83
  - **ICT / SMC (FVG + order blocks)** → `STRUCT_ZONES` is the native zone engine:
76
84
  `zones_htf_support_type` (`bullish FVG`/`bullish order block`), `zones_htf_support_dist
77
- between 0 1.5`, `_age_h gte 12`, HTF bias required via `MAalign_htf`. Liquidity sweeps, displacement, killzones and event *sequencing* are shapes the
85
+ between 0 1.5`, `_age_h gte 12`, HTF bias required via `MAalign_htf`. A killzone is a session window
86
+ (`TRADING_SESSION`, `UTC_HOUR`) and a sweep of a previous session's extreme is `PRIOR_<S>_HIGH_SWEPT` /
87
+ `_LOW_SWEPT`; displacement and event *sequencing* are shapes the
78
88
  grammar does not have — a clause compares one column with a literal or with its own previous
79
89
  completed bar, never with another column or an earlier event, so an ordered sequence of events
80
90
  cannot be stated at all. That is a grammar limit, not a missing metric; name it as
@@ -110,8 +120,25 @@ rules about the columns themselves, not about porting — they live in the skill
110
120
  (`PIVOT_P`/`PIVOT_R1`–`R3`/`PIVOT_S1`–`S3`), Williams %R (`WILLR14`), Stochastic RSI
111
121
  (`STOCH_RSI14`), the TTM squeeze (`KC_SQUEEZE`), Connors RSI-2 (`RSI2`), the 8/9/21/50 EMAs
112
122
  (`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
123
+ previous-UTC-day levels (`PDH`/`PDL`/`PDO`), fractal swing structure
114
124
  (`SWING_FRACTAL_HIGH`/`SWING_FRACTAL_LOW`/`SWING_FRACTAL_LABEL`, the prior pivots
115
125
  `PRIOR_SWING_FRACTAL_HIGH`/`PRIOR_SWING_FRACTAL_LOW` and their ages
116
126
  `SWING_FRACTAL_HIGH_BARS_AGO`/`SWING_FRACTAL_LOW_BARS_AGO`), and Chaikin Money Flow (`CMF20`). Port
117
127
  these directly — do not offer a substitute for a primitive the catalog serves.
128
+ - **Statistics over a series' own history** — the windowed transforms port the common Pine idioms
129
+ one for one, over any metric with a series and over a `distance` or `spread`:
130
+ - the `er` function of the common Pine libraries, `math.abs(x - x[n]) / math.sum(math.abs(x - x[1]), n)`,
131
+ is `x × efficiency` at window `n` — the window counts CHANGES, so `er(close, 10)` is window 10 and
132
+ reads 11 bars;
133
+ - the z-score idiom `(x - ta.sma(x, n)) / ta.stdev(x, n)` is `x × zscore` at window `n`
134
+ (`RSI14_z20`), population σ as `ta.stdev` defaults to, banded at ±1 and ±2;
135
+ - `ta.stdev(x, n)` is `x × stddev` (`close_sd20`, in the input's own units);
136
+ - `ta.percentrank(x, n)` is `x × pctl` at window `n` — the `n` bars BEFORE the newest, ties at or below;
137
+ - `ta.highest(x, n)` / `ta.lowest(x, n)` are `x × rollingMax` / `x × rollingMin` (`high_max20`
138
+ is `DONCHIAN_UPPER`'s same-bar rail);
139
+ - a linear-regression slope over `n` bars (TA-Lib's `LINEARREG_SLOPE`) is `x × linregSlope`, the
140
+ least-squares slope per bar — not Pine's `ta.linreg(x, n, 0) - ta.linreg(x, n, 1)`, which differs
141
+ two fits.
142
+
143
+ Each reads null where its newest or oldest bar holds no value, or where fewer bars exist than the
144
+ window reads.