@battlegrid/mcp-server 31.1.0 → 31.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/battlegrid-strategy-studio/SKILL.md +57 -4
- package/skills/battlegrid-strategy-studio/references/recipes.md +42 -1
- package/skills/battlegrid-strategy-studio/references/tradingview-ports.md +38 -17
package/README.md
CHANGED
|
@@ -24,6 +24,195 @@ 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 → v48.1
|
|
28
|
+
|
|
29
|
+
Eleven majors reached authors while this section stopped at v36. That gap is the mechanism, not an
|
|
30
|
+
oversight: since v31 a contract move needs no release here, so nothing forced a note to be written —
|
|
31
|
+
and the documentation ships inside the tarball, so a note written but unpublished reaches nobody.
|
|
32
|
+
Both halves are now closed by a rule keyed to the *served* contract rather than to a release of this
|
|
33
|
+
package.
|
|
34
|
+
|
|
35
|
+
### Changed meaning, unchanged shape
|
|
36
|
+
|
|
37
|
+
- **Three tools serve different values for identical input** (47.3.0, `derive-scan-fetch-from-report`).
|
|
38
|
+
The radar scan leg now derives its timeframe fetch from the strategy's **report** rather than the
|
|
39
|
+
on-duty agent's three perception rungs, so a required condition addressing an absolute timeframe
|
|
40
|
+
outside those rungs — never evaluated at scan before — now is. No field moves; the values do:
|
|
41
|
+
|
|
42
|
+
| Tool | What moves |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `preview_radar_resolution` | `conditionReach[].reachReason` goes `AGENT_TIMEFRAME` → `null` |
|
|
45
|
+
| `get_radar_activity` | `scanReachReason` moves the same way, **on new rows only** — rows already written keep what they were recorded with |
|
|
46
|
+
| `get_agent_coin_qualification` | `reachReason` moves the same way; its sibling `verdict` moves `UNMEASURABLE` → a decided verdict |
|
|
47
|
+
|
|
48
|
+
**Read the last one carefully:** a client treating `UNMEASURABLE` as "this gate is switched off"
|
|
49
|
+
will now see that gate **BLOCK**. `AGENT_TIMEFRAME` keeps its member and narrows to the one cause
|
|
50
|
+
no fetch can discharge. Nothing in the payload tells you this moved.
|
|
51
|
+
|
|
52
|
+
### Rejected input — something you author is no longer accepted
|
|
53
|
+
|
|
54
|
+
- **A benchmark-bound section no longer accepts crowd metrics or rank transforms** (49.0.0,
|
|
55
|
+
`fix-benchmark-legality-save-path`). On a custom section carrying a non-null `benchmarkTicker`, a
|
|
56
|
+
column whose metric is enrichment-stage (the `CROWD_*` family, `FLOW_ALIGN`, `SMART_RETAIL`,
|
|
57
|
+
`CAPTAIN_CONF`, `CONFIDENCE`, `SETTLED_AT`, the `PERP_SPOT_*` trio) is refused with
|
|
58
|
+
`REPORT_COLUMN_BENCHMARK_METRIC_UNSUPPORTED`, and one carrying a `rank` transform in either the
|
|
59
|
+
direct or the chained position with `REPORT_COLUMN_BENCHMARK_TRANSFORM_UNSUPPORTED`. Every tool
|
|
60
|
+
accepting a section array is affected, and no other byte of your body changes.
|
|
61
|
+
|
|
62
|
+
**Fix it by moving the column, not by retrying.** Both readings are defined *relative to the
|
|
63
|
+
cohort being evaluated* — a crowd reading is what this session's players did, a rank is a position
|
|
64
|
+
among the coins under evaluation — and a benchmark is deliberately outside that cohort. Neither
|
|
65
|
+
has a value there, which is why such a column could never render. Put it on an ordinary section
|
|
66
|
+
(`benchmarkTicker: null`), or drop it.
|
|
67
|
+
|
|
68
|
+
**This is a fix, not a new rule.** The restriction shipped with benchmark sections and was already
|
|
69
|
+
enforced by the column builder, by report materialization, and by `get_strategy_column_contract` —
|
|
70
|
+
so an author who checked a column against discovery first has been seeing this refusal all along.
|
|
71
|
+
What changed is that the SAVE path now asks the same question. Previously it did not, so such a
|
|
72
|
+
section persisted and then failed at every evaluation instead of at authoring.
|
|
73
|
+
|
|
74
|
+
- **A custom report section no longer accepts `timeframe`** (48.0.0,
|
|
75
|
+
`remove-section-anchor-override`). The per-section anchor override is gone. Every tool accepting a
|
|
76
|
+
section array — `compile_strategy_plan`, `preview_strategy_report`, `derive_strategy_rule_view`,
|
|
77
|
+
and `apply_strategy_plan` — refuses a section carrying it: `sections[N]: Unrecognized key(s)`,
|
|
78
|
+
with no other byte of your body changing. Drop the key. A section's columns resolve against the
|
|
79
|
+
**strategy** timeframe, and a column reaches any other timeframe by pinning it on the column
|
|
80
|
+
(`timeframe: { abs: '4h' }`) — which it could always do. Relative column references
|
|
81
|
+
(`anchor`/`lower`/`regime`) are untouched, and are the point: they track the strategy.
|
|
82
|
+
|
|
83
|
+
- **A custom report section requires `notes`** (43.0.0, `add-authored-section-notes`). Every tool
|
|
84
|
+
accepting a section array — `compile_strategy_plan`, `preview_strategy_report`,
|
|
85
|
+
`derive_strategy_rule_view` — refuses a section without it: `sections[N].notes: Required`, with no
|
|
86
|
+
byte of your body changing. Send explicit `null` for "no note". It is required rather than optional
|
|
87
|
+
because these payloads are a FULL REPLACE: an omitted key and an explicit `null` would be the same
|
|
88
|
+
request on the wire, so every rebuild site would silently clear a note its author wrote.
|
|
89
|
+
`benchmarkTicker` carries the same required-nullable discipline for the same reason.
|
|
90
|
+
|
|
91
|
+
- **Every condition requires `clock` and `closes`** (44.0.0, `add-condition-clock`), and **`exit`**
|
|
92
|
+
arrives with them. A condition entry now carries eight keys, not five. `clock` is `"LIVE"` (the
|
|
93
|
+
previous behaviour — the forming bar) or `"CLOSE"` (settled bars); `closes` is how many consecutive
|
|
94
|
+
closed bars must read true, 1–5, and is always `1` under LIVE. No wire default, for the same
|
|
95
|
+
whole-set-replacement reason as `notes`: a defaulted key would let an unrelated re-save silently
|
|
96
|
+
un-clock an enforced money gate back to forming-bar evidence.
|
|
97
|
+
|
|
98
|
+
A `CLOSE` clock is accepted only where a closed frame can change the reading — the clause must
|
|
99
|
+
resolve from the coin's own candle series at offset 0. Frame-inert operands (perp-payload scalars,
|
|
100
|
+
published rolling changes, ranks, zone entities, regime labels, enrichment metrics, session
|
|
101
|
+
scalars) are refused with `CONDITION_CLOCK_OPERAND_ILLEGAL` naming the header and the remedy: split
|
|
102
|
+
that clause into its own LIVE condition and `conditionRef` it. `exit: true` is legal only under
|
|
103
|
+
`clock: "CLOSE"` — an exit fired on a forming bar is an intrabar exit.
|
|
104
|
+
|
|
105
|
+
- **A strategy requires a seven-key `entry` object** (44.0.0 for four keys, 47.0.0 for three more —
|
|
106
|
+
`add-entry-on-close`, `add-level-trigger-execution`). Required on every CREATE, on
|
|
107
|
+
`compile_strategy_plan`, `apply_strategy_plan` and `update_intelligence_agent`. A client sending
|
|
108
|
+
44.0.0's four-key object is refused under 47 with `entry.levelSource: Required` without one byte of
|
|
109
|
+
it changing.
|
|
110
|
+
|
|
111
|
+
```jsonc
|
|
112
|
+
"entry": {
|
|
113
|
+
"trigger": "AT_SIGNAL", // | ON_CANDLE_CLOSE | STOP_THROUGH_LEVEL | ON_RETEST
|
|
114
|
+
"confirmTf": "4h", // the strategy timeframe or the rung below it — nothing else
|
|
115
|
+
"closes": 1, // 1–5; must be 1 unless ON_CANDLE_CLOSE
|
|
116
|
+
"bandAtrMultiple": 1.0, // > 0, and <= the platform's entry-deviation gate
|
|
117
|
+
"levelSource": "SWING_HIGH", // | SWING_LOW | BOLLINGER_UPPER | BOLLINGER_LOWER
|
|
118
|
+
"levelOffsetAtrMultiple": 0, // 0–2, UNSIGNED; must be 0 unless a level trigger
|
|
119
|
+
"validForBars": 4 // 1–24 of the strategy's own bars
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`AT_SIGNAL` with those values is byte-identical to pre-44 behaviour. The legality matrix runs one
|
|
124
|
+
way: all seven keys are always present, so a MEANINGFUL value under a trigger that ignores it is
|
|
125
|
+
refused rather than accepted-and-dropped — a dial never silently does nothing.
|
|
126
|
+
|
|
127
|
+
- **A `strategyTimeframe` the platform does not ingest is refused** (39.0.0,
|
|
128
|
+
`move-renderer-to-rendered-section`) on `get_coin_market_context`, where it was previously
|
|
129
|
+
accepted. The number is the only signal a client gets.
|
|
130
|
+
|
|
131
|
+
- **`eventType` gains `ENTRY_EXPIRED_UNCONFIRMED`** (48.1.0, `fix-entry-arming-lifecycle`) on
|
|
132
|
+
`get_radar_activity` and `get_radar_activity_summary` — an armed entry that reached its episode
|
|
133
|
+
lifetime without ever receiving a confirming close. A client holding its own closed copy of that
|
|
134
|
+
enum rejects the new member; one that switches exhaustively on it needs the branch.
|
|
135
|
+
|
|
136
|
+
`entryVoidCause` is deliberately **unchanged** and still carries exactly `BAND`, `CONDITIONS`,
|
|
137
|
+
`STRUCTURAL`. Each of those is a measurement taken *at* a close, so an episode that reached no
|
|
138
|
+
close gets its own event type rather than a fourth cause — `ENTRY_VOIDED` means "called off at the
|
|
139
|
+
close", a claim this outcome must not make.
|
|
140
|
+
|
|
141
|
+
The authoring boundary also gains a refusal with **no schema change**: a strategy declaring an
|
|
142
|
+
arming trigger whose `required` conditions read the `LIVE` clock is rejected on the existing
|
|
143
|
+
`VALIDATION_ERROR` code, naming the offending condition key. Same code, same shape, new reason —
|
|
144
|
+
so nothing in the published schema tells you it can now happen.
|
|
145
|
+
|
|
146
|
+
### Removed — no alias exists
|
|
147
|
+
|
|
148
|
+
- **`get_coin_market_context` is REMOVED** (40.0.0, `retire-get-coin-market-context`). Calling it
|
|
149
|
+
returns an unknown-tool error. There is deliberately **no alias**: a silent redirect would hide a
|
|
150
|
+
payload shape change from a client that never asked for one. Use `get_market_context`.
|
|
151
|
+
|
|
152
|
+
- **`get_macd_heatmap` leaves the published surface** (41.0.0). Same shape of break, same absence of
|
|
153
|
+
an alias.
|
|
154
|
+
|
|
155
|
+
- **`isPrimary` is removed from every published `EvaluatedSignal`** (38.0.0) — `get_signal_log`,
|
|
156
|
+
`get_public_agent_signal_log_detail`, and every other tool returning a signal scorecard. Reading it
|
|
157
|
+
now finds the key absent rather than false.
|
|
158
|
+
|
|
159
|
+
### Reshaped output — the same call returns a different shape
|
|
160
|
+
|
|
161
|
+
- **The normalized report section loses `timeframe`** (48.0.0, `remove-section-anchor-override`), on
|
|
162
|
+
every tool that publishes a strategy: `get_strategy`, `fork_strategy`, `archive_strategy`,
|
|
163
|
+
`restore_strategy`, `update_strategy_signal_rule`, `apply_strategy_plan`, and
|
|
164
|
+
`compile_strategy_plan`'s post-state. A strict parser rejects the shorter object; a lenient one
|
|
165
|
+
reads a section whose anchor is the strategy timeframe, which it now always is.
|
|
166
|
+
|
|
167
|
+
- **`get_strategy_column_contract` renames its anchor, both ways** (48.0.0). The request field
|
|
168
|
+
`sectionTimeframe` becomes `anchorTimeframe` — same meaning, and still optional. On the response,
|
|
169
|
+
`timeframe.requiresSectionTimeframe` becomes `requiresAnchorTimeframe`, and
|
|
170
|
+
`timeframe.sectionTimeframeOverrideAllowed` is **removed**: it published whether a column could go
|
|
171
|
+
in an anchor-overridden section, and no section can be overridden. One call also stops being
|
|
172
|
+
refused — a timeframe-inert metric supplied with an anchor now compiles.
|
|
173
|
+
`REPORT_COLUMN_SECTION_TIMEFRAME_UNSUPPORTED` leaves the `authoringCode` vocabulary.
|
|
174
|
+
|
|
175
|
+
- **`RenderedSection.notes` stops carrying provenance** (42.0.0, `separate-section-facts-from-read`),
|
|
176
|
+
on `preview_strategy_report`, `compile_strategy_plan` and `get_market_context`. A new REQUIRED
|
|
177
|
+
`provenance: string[]` carries it instead. A client reading provenance out of `notes` now reads an
|
|
178
|
+
author's prose, or nothing — which is a silent misread, not an error.
|
|
179
|
+
|
|
180
|
+
- **`preview_strategy_report.renderedSections[]` gains `authoredNote`** (43.0.0), the author's read
|
|
181
|
+
for a custom row and `null` on a platform row. Additive, but published on a `.strict()` shape.
|
|
182
|
+
|
|
183
|
+
- **`get_radar_activity` serves its evaluation curve on the FIRST PAGE ONLY** (37.0.0), and
|
|
184
|
+
`get_radar_activity_summary` is added. A client reading the curve off a later page finds it absent.
|
|
185
|
+
|
|
186
|
+
### Widened enum — new members your own copy rejects
|
|
187
|
+
|
|
188
|
+
- **The authorable metric vocabulary widens by 29 keys** (46.1.0, `add-indicator-catalog-coverage`):
|
|
189
|
+
Keltner (`KC_UPPER`/`KC_MID`/`KC_LOWER`), Supertrend (`ST_LINE`/`ST_DIR`), Hull (`HMA20`),
|
|
190
|
+
WaveTrend (`WT1`/`WT2`), QQE (`QQE_RSI_MA`/`QQE_STOP`), Parabolic SAR (`PSAR`), Ichimoku
|
|
191
|
+
(`ICHI_CONV`/`ICHI_BASE`/`ICHI_SPAN_A`/`ICHI_SPAN_B`/`ICHI_LAG`), Williams %R (`WILLR14`),
|
|
192
|
+
Stochastic RSI (`STOCH_RSI14`), session pivots (`PIVOT_P`/`PIVOT_R1`–`R3`/`PIVOT_S1`–`S3`), plus
|
|
193
|
+
four already-published fields that became addressable: `BB_UPPER`, `BB_LOWER`, `DI_PLUS`,
|
|
194
|
+
`DI_MINUS`.
|
|
195
|
+
|
|
196
|
+
Additive on the wire — every request you can send today is still accepted. It is called out here
|
|
197
|
+
because a client holding its own closed copy of the metric enum rejects the new members, and
|
|
198
|
+
because **an agent holding a cached belief that these are inexpressible will substitute for a
|
|
199
|
+
primitive the platform now serves**. That failure raises no error at all: the author is simply told
|
|
200
|
+
a strategy cannot be built.
|
|
201
|
+
|
|
202
|
+
- **`EntryTrigger` gains `STOP_THROUGH_LEVEL` and `ON_RETEST`, and `EntryLevelSource` is published
|
|
203
|
+
for the first time** with four members (47.0.0). Additive on their own; the required keys above are
|
|
204
|
+
what make that bump a major.
|
|
205
|
+
|
|
206
|
+
- **Each signal-checklist item's `measured` object gains a required `triggered` boolean** (47.2.0,
|
|
207
|
+
`fix-entry-prompt-signal-evidence`) on `get_signal_log` and `get_public_agent_signal_log_detail`,
|
|
208
|
+
on the `numeric` and `categorical` arms. Additive and MINOR — a client ignoring it is unaffected —
|
|
209
|
+
but it is named here because a client parsing that output strictly rejects the new key, and because
|
|
210
|
+
leaving 47.2 unlisted would make a reader wonder what happened to it.
|
|
211
|
+
|
|
212
|
+
The `unavailable` arm deliberately does **not** carry `triggered`: "the claim could not be joined to
|
|
213
|
+
a stored result" and "the signal did not fire" are different states, and leaving the key off that
|
|
214
|
+
arm makes conflating them a type error rather than a convention.
|
|
215
|
+
|
|
27
216
|
## Contract history — v12 → v36
|
|
28
217
|
|
|
29
218
|
> **The number in this heading is a CONTRACT version, not this package's version.** The npm badge at the top
|
|
@@ -176,6 +365,38 @@ Nothing in the proxy changes. No configuration, no environment variable, no call
|
|
|
176
365
|
|
|
177
366
|
### Additive in the same span
|
|
178
367
|
|
|
368
|
+
- **`get_regime_snapshot` publishes the evidence behind the verdict** (47.1.0,
|
|
369
|
+
`publish-regime-classification-evidence`). The snapshot gains `evidence`: the quantities the
|
|
370
|
+
classifier read, the gates it tested them against, the signed margin to the gate deciding whether
|
|
371
|
+
the current label survives, and the two decision facts only the classifier holds — `gateState`
|
|
372
|
+
(`cleared` / `held` / `dropped`) and `directionSource` (`di` / `ema`). Nothing narrows; a client
|
|
373
|
+
that ignores the field is unaffected.
|
|
374
|
+
|
|
375
|
+
Read `gateState` before you trust a trend label: **`held` means the ADX hysteresis buffer is
|
|
376
|
+
carrying the PREVIOUS bar's label rather than this bar re-confirming it** — a materially weaker
|
|
377
|
+
claim wearing the same word, and one no client could previously detect. `directionSource: 'ema'`
|
|
378
|
+
is the same shape of warning: the direction came from the fallback that fires precisely when the
|
|
379
|
+
DI spread is indecisive. The margin is signed so **positive always means "the current label
|
|
380
|
+
survives by this much"**, in every gate state, so it is safe to branch on its sign.
|
|
381
|
+
|
|
382
|
+
`conviction` is a BRANCH DISCRIMINATOR, not a confidence: it encodes *which* rule in the priority
|
|
383
|
+
ladder matched, not how comfortably it matched. The margins carry comfort. A client reading
|
|
384
|
+
conviction as a strength score is reading it wrong, and always was — this release just makes the
|
|
385
|
+
alternative available.
|
|
386
|
+
|
|
387
|
+
- **Thirteen metric keys join the catalog** (47.1.0) — the `regime` family gains `REGIME_STATE`,
|
|
388
|
+
`REGIME_CONVICTION`, `REGIME_RUN_BARS`, `REGIME_TREND_GATE`, `REGIME_TREND_MARGIN`,
|
|
389
|
+
`REGIME_TREND_SOURCE`, `REGIME_DI_SPREAD`, `REGIME_VOL_ATR_RATIO`, `REGIME_VOL_BBW_RATIO`,
|
|
390
|
+
`REGIME_MOM_BULL_VOTES`, `REGIME_MOM_BEAR_VOTES`, `REGIME_CRASH_MARGIN` and `REGIME_CRASH_LATCH`,
|
|
391
|
+
making the composite regime and its evidence addressable in a report column or condition for the
|
|
392
|
+
first time. Only a client that switches exhaustively on `MetricKey` needs new branches.
|
|
393
|
+
|
|
394
|
+
Not a contract change, but worth knowing if you author conditions: the report grammar's regime
|
|
395
|
+
metrics now resolve from the **confirmed close** on every path. They previously resolved from the
|
|
396
|
+
forming bar when a report was rendered and the confirmed close when the scan swept, so the same
|
|
397
|
+
condition could read differently in preview than in production. Same wire shape; same bar
|
|
398
|
+
everywhere now.
|
|
399
|
+
|
|
179
400
|
`27.1.0` exit-policy authoring input on `compile_strategy_plan` · `19.2.0` `get_account_state` account identity · `19.1.0` Standing Orders marker authoring · `18.4.0` `list_gate_blocks` summary groups · `18.3.0` radar maintenance pause · `18.1.0` protection geometry · `17.2.0` break-even/trailing status · `17.1.0` `get_signal_log` condition evaluation · `13.1.0` four owner-scoped read tools · `12.1.0` cross-venue spot price metrics · `11.1.0` discoverable rate limit.
|
|
180
401
|
|
|
181
402
|
## v11 and earlier — contract history (v6 → v11)
|
package/dist/index.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ import { type Implementation } from '@modelcontextprotocol/sdk/types.js';
|
|
|
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.1.
|
|
52
|
+
export declare const PACKAGE_VERSION = "31.1.5";
|
|
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.1.
|
|
55
|
+
export const PACKAGE_VERSION = '31.1.5';
|
|
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
|
@@ -88,14 +88,22 @@ values instead of the evaluated coin's — the standard way to gate a whole book
|
|
|
88
88
|
regime. `benchmarkTicker` is required-nullable on every custom section: send `null` for an
|
|
89
89
|
ordinary section, never omit it.
|
|
90
90
|
|
|
91
|
+
A bound section takes **indicator** columns only. Crowd/session metrics (the `CROWD_*` family,
|
|
92
|
+
`FLOW_ALIGN`, `SMART_RETAIL`, `CAPTAIN_CONF`, `CONFIDENCE`, `SETTLED_AT`, `PERP_SPOT_*`) and any
|
|
93
|
+
`rank` transform are refused there (contract 49), because both are defined relative to the cohort
|
|
94
|
+
being evaluated and a benchmark sits outside it — a crowd reading is what this session's players
|
|
95
|
+
did, a rank is a position among the coins under evaluation. Neither has a value for BTC-as-yardstick.
|
|
96
|
+
Put those columns on an ordinary section and `conditionRef` across.
|
|
97
|
+
|
|
91
98
|
Budgets are served by discovery (validated today: 32 sections, 32 custom columns, 8 distinct
|
|
92
99
|
timeframes, 16 conditions, 16 clauses, 16k estimated report tokens). `preview_strategy_report`
|
|
93
100
|
echoes your usage against each cap.
|
|
94
101
|
|
|
95
102
|
## Conditions — deterministic logic over your own report
|
|
96
103
|
|
|
97
|
-
Each condition: `{ conditionKey, name, definition, verdict, required }` —
|
|
98
|
-
|
|
104
|
+
Each condition: `{ conditionKey, name, definition, verdict, required, exit, clock, closes }` —
|
|
105
|
+
all eight required, no defaults. The conditions axis saves by whole-set replacement, so an omitted
|
|
106
|
+
key would silently un-clock a money gate on the next unrelated edit rather than be refused.
|
|
99
107
|
|
|
100
108
|
- **Clauses** compare one column: numeric/rank headers take `lt|lte|gte|gt|between`;
|
|
101
109
|
classification/direction/event headers take `is|in` with the column's exact vocabulary
|
|
@@ -106,8 +114,11 @@ Each condition: `{ conditionKey, name, definition, verdict, required }` — all
|
|
|
106
114
|
rejected, forward references are legal.
|
|
107
115
|
- **Column addressing**: `{sectionKey, header}`. `sectionKey: null` is authoring sugar for a
|
|
108
116
|
header unique across the whole report; a duplicated header (e.g. `ADX` in two sections) must
|
|
109
|
-
be section-qualified.
|
|
110
|
-
|
|
117
|
+
be section-qualified. On a **CREATE you omit `sectionKey` entirely** — the server derives it
|
|
118
|
+
from the section itself, so the same submitted section yields the same key on every compile, and
|
|
119
|
+
minting a `custom:<uuid>` yourself is refused. On an UPDATE or RESTORE, send back the keys
|
|
120
|
+
`get_strategy` returned. To qualify a duplicated header, read the key from a preview's
|
|
121
|
+
`conditionColumns` or from the candidates a `CONDITION_COLUMN_AMBIGUOUS` refusal offers.
|
|
111
122
|
- **Verdict**: `UP` | `DOWN` | `NEITHER` on deciding conditions, explicit `null` on building
|
|
112
123
|
blocks. Declaration order is precedence: the first TRUE condition with a non-null verdict
|
|
113
124
|
decides the coin's verdict. Put the more specific carrier first.
|
|
@@ -116,6 +127,48 @@ Each condition: `{ conditionKey, name, definition, verdict, required }` — all
|
|
|
116
127
|
use it for liquidity floors, regime vetoes, and "never fight the HTF" rules.
|
|
117
128
|
- Evaluation is three-valued: `UNRESOLVED` (missing input) is never collapsed to FALSE, and
|
|
118
129
|
outcomes read from a still-forming bar are marked provisional.
|
|
130
|
+
- **The evidence clock.** `clock: "LIVE"` reads the forming bar; `clock: "CLOSE"` reads settled
|
|
131
|
+
bars, and `closes` (1–5) is how many consecutive closed bars must read TRUE — always `1` under
|
|
132
|
+
LIVE, which has exactly one frame. A CLOSE clock is legal **only** over a header resolved from
|
|
133
|
+
the coin's own candle series at offset 0; frame-inert operands (perp-payload scalars, published
|
|
134
|
+
rolling changes, ranks, zone entities, regime labels, enrichment metrics, session scalars) are
|
|
135
|
+
refused with `CONDITION_CLOCK_OPERAND_ILLEGAL`. The remedy is a split, not a re-clock: move that
|
|
136
|
+
clause into its own LIVE condition and `conditionRef` it.
|
|
137
|
+
- **The exit role.** `exit: true` makes a settled TRUE reading close open positions its verdict
|
|
138
|
+
opposes — UP exits SHORTs, DOWN exits LONGs, a `NEITHER` or `null` verdict exits both. Legal only
|
|
139
|
+
under `clock: "CLOSE"`, because an exit fired on a forming bar is an intrabar exit. Orthogonal to
|
|
140
|
+
`required`: the two act on disjoint lifecycles, pre-entry versus open.
|
|
141
|
+
|
|
142
|
+
## Entry — when, and where, an entry is taken
|
|
143
|
+
|
|
144
|
+
`{ trigger, confirmTf, closes, bandAtrMultiple, levelSource, levelOffsetAtrMultiple, validForBars }`
|
|
145
|
+
— all seven required on every CREATE, no defaults. Replaced WHOLE on save, so an omitted key
|
|
146
|
+
reverts an author's discipline silently rather than being refused.
|
|
147
|
+
|
|
148
|
+
| `trigger` | What it does |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `AT_SIGNAL` | Fire on the qualification flip, at whatever bar is on the tape. The platform's flat wall-clock entry window applies. |
|
|
151
|
+
| `ON_CANDLE_CLOSE` | The flip ARMS the pair; entry is taken only after a `confirmTf` close that still reads the conditions true and has not displaced beyond the band. |
|
|
152
|
+
| `STOP_THROUGH_LEVEL` | A TRIGGER order rests at the level ± offset; the exchange book is the watcher. |
|
|
153
|
+
| `ON_RETEST` | A LIMIT order rests at the broken level. Not filling is a correct outcome, not a failure. |
|
|
154
|
+
|
|
155
|
+
- `confirmTf` — exactly two values are legal: the strategy's own timeframe and the rung below it
|
|
156
|
+
(one only, at the ladder floor). Not the authorable main-candle set.
|
|
157
|
+
- `closes` — 1–5 consecutive confirming closes. Must be `1` under any trigger but `ON_CANDLE_CLOSE`.
|
|
158
|
+
- `bandAtrMultiple` — the veto width. The entry is VOIDED when the confirming close moved at or
|
|
159
|
+
beyond that many ATR against the armed verdict. Strictly `> 0` (zero voids on any adverse move,
|
|
160
|
+
which is a filter, not "off"), and at or below the platform's own entry-deviation gate — read it
|
|
161
|
+
from `get_trading_config_catalog`.
|
|
162
|
+
- `levelSource` — `SWING_HIGH` | `SWING_LOW` | `BOLLINGER_UPPER` | `BOLLINGER_LOWER`, resolved once
|
|
163
|
+
at decision time.
|
|
164
|
+
- `levelOffsetAtrMultiple` — 0–2, UNSIGNED. Direction is implied by the trigger and the verdict, so
|
|
165
|
+
a signed value would invert the trigger's meaning. Must be `0` under a non-level trigger.
|
|
166
|
+
- `validForBars` — 1–24 of the strategy's OWN bars, not minutes: a 1h setup waiting for a retest has
|
|
167
|
+
not failed after fifteen minutes.
|
|
168
|
+
|
|
169
|
+
The legality matrix runs **one way**: all seven keys are always present, so the question is never
|
|
170
|
+
"is it set" but "is it set to something that MEANS anything under this trigger". Leave a dial at its
|
|
171
|
+
inert value rather than setting one the platform will ignore.
|
|
119
172
|
|
|
120
173
|
## Signal weights — the scorecard is a weighted average, budget it
|
|
121
174
|
|
|
@@ -54,10 +54,16 @@ before any billing or LLM call:
|
|
|
54
54
|
|
|
55
55
|
```json
|
|
56
56
|
{ "conditionKey": "NO_EVENT_RISK", "name": "No liquidation tape", "verdict": null, "required": true,
|
|
57
|
+
"exit": false, "clock": "LIVE", "closes": 1,
|
|
57
58
|
"definition": { "kind": "group", "op": "NOT", "members": [
|
|
58
59
|
{ "kind": "clause", "column": { "sectionKey": null, "header": "oiRegime" }, "op": "is", "label": "long liquidation" } ] } }
|
|
59
60
|
```
|
|
60
61
|
|
|
62
|
+
All eight keys are required on every condition — `exit`, `clock` and `closes` included. `clock`
|
|
63
|
+
selects the evidence frame (`LIVE` reads the forming bar, `CLOSE` reads settled bars) and `closes`
|
|
64
|
+
(1–5) is how many consecutive closed bars must hold; `closes` is always `1` under `LIVE`. `exit:
|
|
65
|
+
true` closes open positions the verdict opposes and is legal only under `clock: "CLOSE"`.
|
|
66
|
+
|
|
61
67
|
**Quorum (N_OF).** Robust confirmation instead of a brittle ALL:
|
|
62
68
|
|
|
63
69
|
```json
|
|
@@ -72,11 +78,42 @@ directional carriers; the first TRUE carrier **in declaration order** decides
|
|
|
72
78
|
most-specific first.
|
|
73
79
|
|
|
74
80
|
**Benchmark gate.** Read regime off a `benchmarkTicker` section (all rows are the benchmark's
|
|
75
|
-
values) and `conditionRef` it from every carrier — one place to flip the book risk-on/off.
|
|
81
|
+
values) and `conditionRef` it from every carrier — one place to flip the book risk-on/off. Keep that
|
|
82
|
+
section to indicators: crowd/session metrics and `rank` transforms are refused on a bound section
|
|
83
|
+
(contract 49) — rank the coins on an ordinary section and `conditionRef` the two together.
|
|
76
84
|
|
|
77
85
|
**Required-count trap.** `required: true` at `allocation: 0` on a *signal rule* is rejected
|
|
78
86
|
(contract 34). A required *condition* is independent of signal weights — the two gates stack.
|
|
79
87
|
|
|
88
|
+
## Entry discipline recipes
|
|
89
|
+
|
|
90
|
+
The `entry` object is required on every CREATE, all seven keys, no defaults.
|
|
91
|
+
|
|
92
|
+
| Intent | Object |
|
|
93
|
+
|---|---|
|
|
94
|
+
| Today's behaviour, unchanged | `{ "trigger": "AT_SIGNAL", "confirmTf": "<strategy tf>", "closes": 1, "bandAtrMultiple": 1.0, "levelSource": "SWING_HIGH", "levelOffsetAtrMultiple": 0, "validForBars": 4 }` |
|
|
95
|
+
| Wait for the bar to close, skip a runaway | `{ "trigger": "ON_CANDLE_CLOSE", "confirmTf": "<strategy tf>", "closes": 1, "bandAtrMultiple": 1.0, … }` |
|
|
96
|
+
| Two-close confirmation on the lower rung | `{ "trigger": "ON_CANDLE_CLOSE", "confirmTf": "<lower rung>", "closes": 2, "bandAtrMultiple": 0.75, … }` |
|
|
97
|
+
| Break of the swing high, half an ATR through | `{ "trigger": "STOP_THROUGH_LEVEL", "levelSource": "SWING_HIGH", "levelOffsetAtrMultiple": 0.5, "validForBars": 8, "closes": 1, … }` |
|
|
98
|
+
| Buy the retest of the broken level | `{ "trigger": "ON_RETEST", "levelSource": "SWING_HIGH", "levelOffsetAtrMultiple": 0, "validForBars": 12, "closes": 1, … }` |
|
|
99
|
+
|
|
100
|
+
Bounds: `closes` 1–5 and `1` unless `ON_CANDLE_CLOSE`; `bandAtrMultiple` > 0 and ≤ the platform's
|
|
101
|
+
`defaultMaxEntryDeviationAtrMultiple`; `levelOffsetAtrMultiple` 0–2 unsigned and `0` unless a level
|
|
102
|
+
trigger; `validForBars` 1–24. `confirmTf` is the strategy timeframe or the rung below it, nothing
|
|
103
|
+
else. A meaningful value under a trigger that ignores it is REFUSED, not ignored — that is
|
|
104
|
+
deliberate, so a dial never silently does nothing.
|
|
105
|
+
|
|
106
|
+
## Custom section shape
|
|
107
|
+
|
|
108
|
+
`{ kind, sectionKey, title, benchmarkTicker, notes, columns }`. `benchmarkTicker` and
|
|
109
|
+
`notes` are **required-nullable** — send explicit `null` rather than omitting them, because the
|
|
110
|
+
section is rebuilt whole on save and an omitted key clears the value. **Omit `sectionKey` on a
|
|
111
|
+
CREATE**: the server derives it from the section.
|
|
112
|
+
|
|
113
|
+
A section carries **no `timeframe`** (48.0.0). Its relative columns resolve against the strategy
|
|
114
|
+
timeframe; a column reaches any other timeframe by pinning it — `timeframe: { abs: '4h' }` on the
|
|
115
|
+
column, which it could always do.
|
|
116
|
+
|
|
80
117
|
## Weight matrices (rules)
|
|
81
118
|
|
|
82
119
|
**Conviction pyramid (default).** 1–2 × Critical (the thesis signals, usually `required`),
|
|
@@ -107,6 +144,10 @@ send the full replacement object. Examples validated as canonical defaults: `vol
|
|
|
107
144
|
| Swing breakout (4h) | 0.75 – 1.75 | 2 | 1R | 1.2R, giveback 35, buffer 0.3 | off |
|
|
108
145
|
| Swing trend (4h) | 1.0 – 2.5 | 2 | 1R | 1.5R, giveback 45–55, buffer 0.3 | off |
|
|
109
146
|
|
|
147
|
+
Post-entry invalidation: `decisionInvalidationExitEnabled: true` closes a filled position when a
|
|
148
|
+
**closed** strategy-timeframe candle finishes beyond the decision's invalidation level, reduce-only.
|
|
149
|
+
Bar-close only — the protective stop still owns intrabar moves.
|
|
150
|
+
|
|
110
151
|
Bounds to respect (validated): break-even trigger 0.5–2R; trailing trigger 0–2R step 0.01
|
|
111
152
|
(0 = trail from entry); giveback 25–55%; buffer 0.01–1%; grace ≥ interval; stop-band floor <
|
|
112
153
|
ceiling ≤ the 3×ATR structural cap; RR within the catalog's served range (0.5–3 today). Wider
|
|
@@ -37,8 +37,8 @@ closes while stops, trailing, and time decay keep managing the position intraday
|
|
|
37
37
|
|
|
38
38
|
| TradingView script | Verdict | Port anchor |
|
|
39
39
|
|---|---|---|
|
|
40
|
-
| Squeeze Momentum [LazyBear] / TTM Squeeze |
|
|
41
|
-
| Supertrend / UT Bot Alerts |
|
|
40
|
+
| Squeeze Momentum [LazyBear] / TTM Squeeze | **Direct port** — Keltner is native | `BB_UPPER spread KC_UPPER`, `BB_LOWER spread KC_LOWER`, `bollinger_squeeze` |
|
|
41
|
+
| Supertrend / UT Bot Alerts | **Direct port** for the state; flip still substituted | `ST_LINE`, `ST_DIR`, `EMA5_13` for the flip, position-management trailing |
|
|
42
42
|
| Chandelier Exit | Direct port of the *mechanism* | trailing from entry (`trailingTriggerR: 0`) |
|
|
43
43
|
| MACD + 200 MA filter | Direct process port | `MACD_cross`, `dist_SMA200`, `macd_bull_cross` |
|
|
44
44
|
| Golden / Death Cross | Direct process port | `SMA50_SMA200_spread` + `_trend` |
|
|
@@ -46,7 +46,7 @@ closes while stops, trailing, and time decay keep managing the position intraday
|
|
|
46
46
|
| VWAP reversion | Direct process port | `dist_VWAP`, `dist_VWAP_rank_far` |
|
|
47
47
|
| Donchian / Turtle breakout | Process port (N-bar channel → swing structure) | `zone`, `dist_swingHi`, `sr_resistance_break` |
|
|
48
48
|
| ICT/SMC: FVG + Order Blocks | Direct process port of the zone logic | `STRUCT_ZONES` columns + `structure_*` signals |
|
|
49
|
-
| WaveTrend, QQE, Hull Suite, Ichimoku, Parabolic SAR, Keltner | **
|
|
49
|
+
| WaveTrend, QQE, Hull Suite, Ichimoku, Parabolic SAR, Keltner | **All native since contract 46.1** — port directly | `WT1`/`WT2`, `QQE_RSI_MA`/`QQE_STOP`, `HMA20`, `ICHI_*`, `PSAR`, `KC_*` |
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
@@ -55,7 +55,10 @@ closes while stops, trailing, and time decay keep managing the position intraday
|
|
|
55
55
|
**Source process.** Squeeze ON while Bollinger Bands sit inside Keltner Channels (volatility
|
|
56
56
|
coiled); wait; when the squeeze releases, enter in the direction of the momentum histogram.
|
|
57
57
|
|
|
58
|
-
**Port.** Keltner Channels are
|
|
58
|
+
**Port.** Keltner Channels are native (`KC_UPPER`/`KC_MID`/`KC_LOWER`), so the squeeze is the
|
|
59
|
+
source's own definition: `BB_UPPER spread KC_UPPER` negative AND `BB_LOWER spread KC_LOWER`
|
|
60
|
+
positive is bands-inside-Keltner. The board-relative proxy below remains serviceable but is no
|
|
61
|
+
longer the only option — it substitutes
|
|
59
62
|
*cross-sectional and absolute* Bollinger compression — a stricter, universe-aware read:
|
|
60
63
|
`bbWidthPct_rank_lo lte 10` AND `ADX lt 20` as the `SQUEEZE_ON` building block. Momentum
|
|
61
64
|
direction comes from the MACD histogram trajectory (`MACD_now gt 0` with `MACD_trend rising`
|
|
@@ -75,7 +78,10 @@ Pine script simulates with a plotted line, position management executes: enable
|
|
|
75
78
|
`trailingTriggerR: 0` (trail from entry — the Supertrend/UT Bot behavior), `giveback` as the
|
|
76
79
|
ATR-offset analog (30–40 tight like factor-2 Supertrend, 45–55 loose like factor-3), plus the
|
|
77
80
|
stop band (`minStopLossAtrMultiple`/`max…`) bounding the initial distance. The *flip entry*
|
|
78
|
-
|
|
81
|
+
is native: `ST_DIR` carries the direction and `ST_LINE` the plotted stop. **`ST_DIR` is a
|
|
82
|
+
persisting state, not a flip event** — it reads the same on every bar of a trend, so the FLIP
|
|
83
|
+
itself still needs an event column beside it. The named substitute for the flip is a trend-state
|
|
84
|
+
change:
|
|
79
85
|
`MAalign is "bullish"` (state) with the `EMA5_13 is "Bullish"` cross event as the trigger, and
|
|
80
86
|
`ma_ema_bull_cross` / `ma_ema_aligned_bull` Critical/Important-required in `rules`. Say the
|
|
81
87
|
substitution out loud when presenting the strategy.
|
|
@@ -139,7 +145,8 @@ the book out of single-name traps. Rules: `ma_sma200_above` 3 required, `ma_ema_
|
|
|
139
145
|
**Source process.** In an uptrend (close > 200 SMA), buy panic dips (RSI(2) < 10); exit fast
|
|
140
146
|
(cross of the 5-period average / a few bars).
|
|
141
147
|
|
|
142
|
-
**Port — named substitute.** The catalog carries
|
|
148
|
+
**Port — named substitute.** The catalog carries `RSI14` and `RSI7` only, so there is no `RSI2`;
|
|
149
|
+
`RSI7` is the fastest RSI series
|
|
143
150
|
and `lte 10` on it is a rarer, deeper panic — state that trade-off rather than hiding it.
|
|
144
151
|
`DIP_BUY` verdict UP = `dist_SMA200 gt 0` (ref a required `ABOVE_200`) AND `RSI7 lte 10`.
|
|
145
152
|
Rules: `rsi_oversold` 3 required `{"threshold": 25}` (RSI14 gate tuned toward the fast-dip
|
|
@@ -193,22 +200,36 @@ quorum (`N_OF(2)`: `buyPres gte 0.55`, `RVOL gte 1.2`, `closeChg gt 0`). Rules:
|
|
|
193
200
|
`structure_fvg_approach` / `structure_ob_approach` 3/2 required (their `proximityPct` params
|
|
194
201
|
are the "in the zone" dial), `structure_zone_confluence` 2, `sr_at_support` 2. Stops: tight
|
|
195
202
|
band (0.5–1.5 ATR) — the zone's far edge is the invalidation, and the studio places stops by
|
|
196
|
-
ATR/structure natively. **
|
|
203
|
+
ATR/structure natively. **A grammar limit, not a missing metric** — a clause compares one column
|
|
204
|
+
against a literal, so an ordered sequence of events has no expressible form at all: liquidity
|
|
205
|
+
sweeps, displacement legs,
|
|
197
206
|
killzone clocks, and Turtle-Soup false-break sequencing (ordering between events is outside
|
|
198
207
|
the grammar) — offer this zone-reaction port as the nearest expressible neighbor, labelled.
|
|
199
208
|
|
|
200
|
-
##
|
|
209
|
+
## Now native — do not substitute for these
|
|
201
210
|
|
|
202
|
-
|
|
211
|
+
WaveTrend (`WT1`/`WT2`), QQE (`QQE_RSI_MA`/`QQE_STOP`), Hull (`HMA20`), Ichimoku
|
|
212
|
+
(`ICHI_CONV`/`ICHI_BASE`/`ICHI_SPAN_A`/`ICHI_SPAN_B`/`ICHI_LAG`), Parabolic SAR (`PSAR`), Keltner
|
|
213
|
+
(`KC_UPPER`/`KC_MID`/`KC_LOWER`), daily pivots (`PIVOT_P`/`PIVOT_R1`–`R3`/`PIVOT_S1`–`S3`),
|
|
214
|
+
Williams %R (`WILLR14`), Stochastic RSI (`STOCH_RSI14`), Bollinger boundaries
|
|
215
|
+
(`BB_UPPER`/`BB_LOWER`), ADX components (`DI_PLUS`/`DI_MINUS`). All arrived with contract 46.1 —
|
|
216
|
+
every one of them was listed as inexpressible in this file before it.
|
|
217
|
+
|
|
218
|
+
## Not expressible — the catalog keys this needs
|
|
219
|
+
|
|
220
|
+
The one section where a claim that the catalog LACKS something may live, and every row names the key
|
|
221
|
+
it denies so the claim stays checkable. A claim about the grammar's SHAPE (a clause compares one
|
|
222
|
+
column against a literal; `distance` rejects an `offset`) is permanent and belongs in prose above.
|
|
223
|
+
A claim that a metric is absent belongs here, or nowhere.
|
|
224
|
+
|
|
225
|
+
| Script / primitive | Key the catalog would need | Nearest expressible neighbour |
|
|
203
226
|
|---|---|---|
|
|
204
|
-
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
| Pivot Points (daily) | session pivots | `VWAP` + `swingHi`/`swingLo` distances |
|
|
211
|
-
| Previous-day / previous-week high-low (PDH/PDL) | offset-able levels (`distance` takes no `offset`; clauses are column-vs-literal) | daily swing structure: `zone_1d is "breakout high"`, `dist_swingHi_1d` / `dist_swingLo_1d` |
|
|
227
|
+
| RSI-2 (Connors) | `RSI2` | `RSI7 lte 10` as a named fast-dip substitute |
|
|
228
|
+
|
|
229
|
+
**PDH/PDL** stays inexpressible for a different reason, and the distinction matters: `distance`
|
|
230
|
+
rejects an `offset` and a clause never compares two columns, so the *grammar* has no form for it.
|
|
231
|
+
No metric would fix that. Use the daily swing structure (`zone_1d`, `dist_swingHi_1d`) and name the
|
|
232
|
+
substitution.
|
|
212
233
|
|
|
213
234
|
Never compile a "port" of these under the source's name without the substitution note — a
|
|
214
235
|
player who asked for WaveTrend and silently got Stochastic has no way to learn otherwise.
|