@battlegrid/mcp-server 31.0.3 → 31.1.0
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 +43 -5
- package/SKILL.md +110 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +4 -2
- package/skills/battlegrid-strategy-studio/SKILL.md +201 -0
- package/skills/battlegrid-strategy-studio/references/playbooks.md +370 -0
- package/skills/battlegrid-strategy-studio/references/recipes.md +129 -0
- package/skills/battlegrid-strategy-studio/references/tradingview-ports.md +214 -0
package/README.md
CHANGED
|
@@ -24,16 +24,16 @@ 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 — v12 →
|
|
27
|
+
## Contract history — v12 → v36
|
|
28
28
|
|
|
29
29
|
> **The number in this heading is a CONTRACT version, not this package's version.** The npm badge at the top
|
|
30
30
|
> tracks the proxy's own code; this section tracks the server's wire contract. They move independently **by
|
|
31
31
|
> design**: a contract move needs no release here, because a connected proxy relays the contract out of the
|
|
32
|
-
> upstream handshake rather than declaring it. So a package on `31.x` listing contract history up to `
|
|
32
|
+
> upstream handshake rather than declaring it. So a package on `31.x` listing contract history up to `36.x` is
|
|
33
33
|
> correct — not a version someone forgot to bump. Read the live pair from the startup stderr lines or
|
|
34
34
|
> `GET /mcp/version`; see [Rediscovery & versioning](#rediscovery--versioning) for why.
|
|
35
35
|
|
|
36
|
-
These are the server contract breaks between contract 12 and contract
|
|
36
|
+
These are the server contract breaks between contract 12 and contract 36. Most of the span shipped while the package sat at `11.0.0`; contract 31 landed after this package reached `31.0.0`, and the two numbers matching is coincidence — since v31 the announced contract is relayed from the server, so a package version says nothing about a contract version. They are **contract** history, not package releases: from v31 the announced contract is relayed live and a contract move is no longer a release here. Grouped by what a client observes, with the contract version that introduced each.
|
|
37
37
|
|
|
38
38
|
**The proxy itself is unchanged.** It embeds no schemas, pins no contract version, and forwards `{ request }` verbatim. Every break below lands on whatever *authors* the payload or *reads* the result, never on the proxy.
|
|
39
39
|
|
|
@@ -45,6 +45,14 @@ These are the server contract breaks between contract 12 and contract 33. Most o
|
|
|
45
45
|
|
|
46
46
|
### Rejected input — something you author is no longer accepted
|
|
47
47
|
|
|
48
|
+
- **`upsert_deployment_policy` requires `enabled`** (35.0.0, `add-arena-deployment-pause`). The arena deployment gains an owner-owned pause, and the flag that carries it is **required, not optional** — a body accepted under contract 34 is refused under 35 without one byte of it changing. Required is the whole point: this call replaces the entire policy, so an omitted key and an explicit `true` would be the same request on the wire, and every client that rebuilt a policy without the flag would silently resume a deployment its owner had paused. Read the value from `get_deployment_policy` and send it back. **There is no separate pause verb** — pausing and resuming are this same call with the flag flipped.
|
|
49
|
+
|
|
50
|
+
Do not reach for `enabled: false` to un-deploy. It keeps every slot and stops play, which is the opposite of withdrawing: `delete_deployment_policy` is the withdrawal verb, and it discards the rules permanently. `upsert_deployment_policy` refuses an empty slot set, and its rejection names both routes.
|
|
51
|
+
|
|
52
|
+
- **A signal rule flagged `required` at allocation Off is rejected** (34.0.0, `enforce-required-allocation-invariant`). `required` and `allocation` are two independently editable fields encoding one thing — whether and how a signal participates — so `{ required: true, allocation: 0 }` was representable and meant nothing: the scorecard's triggered set already excludes Off, so such a rule could neither satisfy `minRequiredCount` nor block a trade. It is now refused on **every** rule-writing surface — `compile_strategy_plan`, `apply_strategy_plan` and `update_strategy_signal_rule` alike.
|
|
53
|
+
|
|
54
|
+
Three things make this one easy to trip over. It is an **input-acceptance narrowing**: a payload accepted under contract 33 is refused under 34 without one byte of it changing. It is **invisible in the published schema** — `zod-to-json-schema` drops effects by construction — so you cannot pre-validate it from `tools/list`, and the typed error IS the contract: read `details.inertRequiredSignalIds`, which carries the complete sorted list of offending signals, rather than the message, which names a bounded prefix. And **nothing is repaired for you**: the server will not raise the allocation (that would invent a scoring weight you never chose) nor clear the flag (that would discard your intent silently). Pick one and resend; either satisfies the boundary.
|
|
55
|
+
|
|
48
56
|
- **`apply_strategy_plan` no longer accepts the plan** (33.0.0, `rehydrate-approved-plan-on-apply`). Its input narrows to `{ planToken, confirm }`. A `plan` member is **rejected as an unknown key** — not accepted, not ignored, and with no transitional dual shape — so every client that built the payload breaks on the next connection, which before this is what every published surface told it to do. The server keeps the plan its own compile approved and reads it back, so **you copy nothing out of the compile response**: forward `planToken` byte-for-byte and confirm.
|
|
49
57
|
|
|
50
58
|
Three consequences worth knowing. The 256,000-byte cap on the apply payload is **gone with the payload**, so a large authored surface no longer becomes impossible to apply through a conversational client; the compiled plan is still capped and compile still enforces it. `PLAN_APPROVAL_NOT_FOUND` joins the error vocabulary for a token no approved plan answers to — already applied, lapsed, or never issued, all one code, because the recovery is the same in each case: compile again. And a validation refusal (a quota, a name collision, a bound agent that changed, a moved catalog) now **leaves the approved plan applicable** — clear the cause and confirm again with the same token while it lives, rather than recompiling.
|
|
@@ -94,6 +102,18 @@ These are the server contract breaks between contract 12 and contract 33. Most o
|
|
|
94
102
|
|
|
95
103
|
### Moved or reshaped output — a field you read is somewhere else
|
|
96
104
|
|
|
105
|
+
- **The signal scorecard stops serializing its entries three times over** (36.0.0, `mcp-signal-log-contents`). `get_signal_log` and `get_public_agent_signal_log_detail` drop `scorecard.triggeredSignals`, `scorecard.primarySignals` and `scorecard.supportingSignals`. Every one held the **same entry objects** `allEvaluatedSignals` already carried, so each is one filter over flags every entry still publishes:
|
|
106
|
+
|
|
107
|
+
| Removed | Read instead |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `triggeredSignals` | `allEvaluatedSignals.filter(s => s.triggered)` |
|
|
110
|
+
| `primarySignals` | `allEvaluatedSignals.filter(s => s.triggered && s.isPrimary)` |
|
|
111
|
+
| `supportingSignals` | `allEvaluatedSignals.filter(s => s.triggered && !s.isPrimary)` |
|
|
112
|
+
|
|
113
|
+
**Keep the `triggered` half of those last two predicates.** Both collections were triggered-only by construction, so filtering on `isPrimary` alone surfaces signals that never fired — a silent widening, not an error. No field is removed from an entry: the key set on an `allEvaluatedSignals` member is unchanged, every evaluated signal is still returned whether or not it triggered, and `details` prose and `indicatorValues` are intact. There is no opt-in to get the three back and no default filter. Breaking only if you read one of the three names; on an 84-signal / 16-triggered log the duplication was 10,682 bytes, 28% of the scorecard, carrying no information.
|
|
114
|
+
|
|
115
|
+
- **The fleet roll-up on `list_deployment_policies` drops `unconfigured` and gains `paused`** (35.0.0, `add-arena-deployment-pause`, `fix-arena-deployment-undeploy`). `unconfigured` counted a deployment holding zero slots — a state that can no longer exist, because a stored policy now carries at least one slot and the withdrawn state is the **absence** of a policy rather than an empty one. The bucket was constant `0` at the moment of removal, so no number you read was wrong; a client reading the key still breaks on it, which is why this is a break and not a cleanup. `paused` is the owner's own switch, counted separately from `retired` — an administrator disabling the arena — because conflating them tells an owner to wait for something that will not happen. Every policy lands in exactly one bucket, so the buckets sum to `arenas`: worth asserting if you reconcile these counts.
|
|
116
|
+
|
|
97
117
|
- **`AdminApprovedModelDTO.isActive` became `lifecycle`** (32.0.0, `remove-agent-presets`) — `AVAILABLE` / `DEPRECATED` / `RETIRED`. The boolean conflated "offered in the picker" with "bound agents may run", so there was no way to stop offering a model without hard-blocking every agent already on it. A client switching on `isActive` must switch on `lifecycle`, and **must not treat `DEPRECATED` as blocked**: that is the state which keeps bound agents running. The agent read DTO drops `brainPreset` in the same move — the marker recorded which named bundle an owner clicked, never a value the runtime read, and the model and soul it stamped are unchanged on every agent.
|
|
98
118
|
|
|
99
119
|
- **`approvedPlan` is one object, not an operation union** (31.0.0, resolving #4495). It was published as a discriminated union whose discriminator does not survive JSON-Schema conversion, so what actually shipped was a bare `anyOf`: validating a failing compile response gave you every arm's errors with empty instance paths, and the top one typically complained that an UPDATE was missing `creationSeed` — a CREATE-only key — while the field that really failed went unnamed. It is now one object with a literal `operation` discriminator, and `creationSeed`, `expectedRevision` and `bindingImpact` are **required and nullable on every operation**: a CREATE plan carries a seed and `expectedRevision: null`, an UPDATE/RESTORE plan the reverse. **If you narrowed on the union arms, read `operation` instead and expect explicit `null`s rather than absent keys.** If you read those fields without narrowing, nothing changes except that they may now be null.
|
|
@@ -142,6 +162,8 @@ These are the server contract breaks between contract 12 and contract 33. Most o
|
|
|
142
162
|
|
|
143
163
|
### Widened enum — new members your own copy rejects
|
|
144
164
|
|
|
165
|
+
- **`DeploymentResolutionStatus` gains `PAUSED`** (35.0.0, `add-arena-deployment-pause`), returned by `get_deployment_policy`, `list_deployment_policies` and `preview_deployment_resolution`. A client switching exhaustively on the status must add the branch. Two properties are not obvious from the name: it is answered **before any slot is resolved**, so a paused deployment discloses no agent identity and carries `regimeUsed: null`; and its `targetSession` is nullable, because a deployment can be paused on an arena with no upcoming session and the pause is still the true answer. Do not re-derive the pause from the `enabled` flag beside it — the served status is the answer on every path, and those two disagreeing is the defect this closed.
|
|
166
|
+
|
|
145
167
|
- **Seven plan-token failures became their own error codes** (31.0.0, resolving #4495). `TOKEN_EXPIRED`, `TOKEN_BINDING_MISMATCH`, `INVALID_TOKEN_SIGNATURE`, `INVALID_TOKEN_FORMAT`, `INVALID_TOKEN_CLAIMS`, `INVALID_DIGEST_MATERIAL` and `INVALID_MATERIALIZATION_FENCE` all used to arrive as a bare `INTERNAL_ERROR` — the server wrote the true reason to its own audit log and discarded it at the boundary, so a refused apply told you nothing. They now arrive as themselves, over MCP and HTTP alike, with 409-class status for the two state-conflict codes and 400-class for the five malformed-material codes. A client switching exhaustively on error codes must add the branches; one rendering unknown codes generically is unaffected. Two are worth handling by name: `TOKEN_EXPIRED` means recompile (the token lives five minutes), and `INVALID_TOKEN_SIGNATURE` usually means the token was not forwarded verbatim — it is opaque, so copy it byte-for-byte and never retype or reconstruct it.
|
|
146
168
|
|
|
147
169
|
- `TradeEvaluationAttemptReasonCode` gains `OPEN_POSITION_CHECK_UNAVAILABLE` (19.4.0), splitting a code that previously reported a platform fault as a fact about your account.
|
|
@@ -517,7 +539,6 @@ Tools, prompts, and resources are **discovered live** from the connected server
|
|
|
517
539
|
| 24.0.0 | Server contract v24.0.0, **breaking**: the post-entry exit policy moves from the agent to the strategy. `create_agent`/`update_agent` stop accepting `tradingConfig.positionManagement` on the shared `.strict()` `TradingConfigSchema`, so a client still sending it is rejected rather than ignored — the same input-acceptance narrowing that made v4, v5, v6, v8, v10 and v23 majors. On the read side `AgentTradingConfigDTO` drops the nested block on every agent-returning tool and the explorer trading spec drops it too; `get_trading_config_catalog` drops `positionManagementPresets` and the `defaultPositionMgmt*` trading defaults. The pistol-preset ladder (COLT / WEBLEY / BERETTA / LUGER / WALTHER) is **retired, not renamed** — once the values live on the strategy, the strategy is the named bundle. Additive on the authoring surface in the same bump: `compile_strategy_plan`/`apply_strategy_plan` post-state gains the twelve authored keys beside the trade-level trio, and the plan diff gains a `positionManagement` axis. Behaviourally the umbrella `enabled` flag is **deleted** rather than moved: each mechanism toggle is the whole truth for that mechanism, so a client can no longer express "trailing on, management off". Never published as a package version |
|
|
518
540
|
| 26.0.0 | Server contract v26.0.0, **breaking**: both entry-lifecycle guards stop being agent configuration. `create_agent`/`update_agent` stop accepting `tradingConfig.signalTimeoutMinutes` and `tradingConfig.maxEntryDeviationAtrMultiple` on the shared `.strict()` `TradingConfigSchema`, so a client still sending either is rejected rather than ignored — the same input-acceptance narrowing that made v4, v5, v6, v8, v10, v23 and v24 majors. Neither has a replacement key: one `platform_config` value governs the entry-price drift budget for every decision (read at evaluation time, so an admin edit applies to the next evaluation), and one governs how long an entry may stay unfilled (snapshotted onto the position at creation, so an edit can never cancel an order already resting on the book). On the read side `AgentTradingConfigDTO` drops both fields on every agent-returning tool, and so do the explorer trading spec and the agent-review payload; `get_trading_config_catalog` drops `defaultSignalTimeoutMinutes` and the `minimum_`/`maximum_maxEntryDeviationAtrMultiple` bound pair, while `defaultMaxEntryDeviationAtrMultiple` and `defaultTtlMinutes` stay and become the values that actually govern. Behaviourally a conversational entry and an autonomous entry on the same setup now receive the **identical** unfilled lifetime — the mode-selecting fallback that chose between a per-agent timeout and a hardcoded 15-minute resting window is gone, and the three-way timeout enum with it. Never published as a package version |
|
|
519
541
|
| **31.0.0** | **Proxy change, and the end of the pairing rule.** The version announced downstream is now read from the upstream handshake at connect time and relayed verbatim, instead of being a constant compiled into this package. A local client reads the contract it will actually reach, on every connection, with no release involved. Breaking because the package number now means something different — this proxy's own code, not the server's contract — so `npm view` and the handshake legitimately differ, and code keyed to them being equal is wrong. Retired with it: the publish-time deploy gate (`scripts/assert-deployed-contract.mjs`) and the `MAJOR.MINOR` pairing rule, both of which existed only because the two numbers could disagree. Fails closed if a connected server announces no `serverInfo` rather than substituting its own version. Contract moves no longer produce a release here |
|
|
520
|
-
| 32.0.0 | Server contract v32.0.0, **breaking**: a signal rule flagged `required` at allocation Off is rejected on every rule-writing surface, with a typed error whose `details.inertRequiredSignalIds` names every offending signal. An input-acceptance narrowing invisible in the published schema (conversion drops effects) and unmigrated in storage, so a stored strategy holding the pair is refused on its owner's next write. No proxy code change — the proxy forwards `{ request }` verbatim and relays the announced contract from the handshake |
|
|
521
542
|
|
|
522
543
|
## Maintainer release procedure
|
|
523
544
|
|
|
@@ -588,12 +609,29 @@ If a run fails, inspect it before taking action. An `ENEEDAUTH` failure means th
|
|
|
588
609
|
|
|
589
610
|
## Skills
|
|
590
611
|
|
|
591
|
-
Install the BattleGrid
|
|
612
|
+
Install the BattleGrid skills for AI agent instructions:
|
|
592
613
|
|
|
593
614
|
```bash
|
|
594
615
|
npx skills add playbattlegrid/battlegrid-mcp
|
|
595
616
|
```
|
|
596
617
|
|
|
618
|
+
Two skills ship from this repo, and both are inside the npm tarball (`SKILL.md`, `skills/`):
|
|
619
|
+
|
|
620
|
+
- **`battlegrid`** — connection, scopes, game play, and the strict compile → review → apply
|
|
621
|
+
strategy workflow.
|
|
622
|
+
- **`battlegrid-strategy-studio`** — full-power strategy authoring, for agents that would
|
|
623
|
+
otherwise compile bare template strategies: custom report sections and system-generated header
|
|
624
|
+
grammar, benchmark sections, condition trees (verdict precedence, `required` enforcement
|
|
625
|
+
gates, `N_OF`/`NOT` groups, condition references), tiered signal weights and the
|
|
626
|
+
weighted-aggregate gate math, ATR trade levels, and post-entry position management. Its
|
|
627
|
+
`references/` carry five validated desk-grade playbooks (volatility-compression breakout,
|
|
628
|
+
crowded-positioning fade, benchmark-gated relative-strength rotation, HTF trend pullback,
|
|
629
|
+
perp/spot flow divergence at structure), copy-adaptable recipes, and process-for-process
|
|
630
|
+
ports of the most popular TradingView community scripts (Squeeze Momentum [LazyBear],
|
|
631
|
+
Supertrend/UT Bot, Chandelier Exit, MACD + 200 MA, golden cross, RSI-2, VWAP reversion,
|
|
632
|
+
Donchian/Turtle, ICT FVG/order blocks) with honest named substitutions where the grammar
|
|
633
|
+
lacks a primitive. Shapes are binding; vocabulary stays live-discovered.
|
|
634
|
+
|
|
597
635
|
## License
|
|
598
636
|
|
|
599
637
|
[MIT](LICENSE)
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: battlegrid
|
|
3
|
+
description: MCP skill for BattleGrid — play crypto prediction games (Market Grid), author trading strategies with the strict compile → review → apply workflow, and manage strategy-bound intelligence agents from AI agents.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BattleGrid
|
|
7
|
+
|
|
8
|
+
BattleGrid is a real-time cryptocurrency prediction gaming and trading platform. This MCP server gives AI agents access to play games, author trading strategies, and run strategy-bound intelligence agents.
|
|
9
|
+
|
|
10
|
+
## Discover the live surface first
|
|
11
|
+
|
|
12
|
+
**Tools, prompts, and resources are discovered live from this MCP connection.** Always read the current `tools/list`, `prompts/list`, and `resources/list` before acting — a cached capability list is not authoritative after a server deployment. This skill teaches the workflows and the strict request contracts; it deliberately does not copy the server's tool catalog, metric/transform vocabulary, signal IDs, formulas, or default values. Discover those from the live tools (`list_strategy_categories`, `list_strategy_vocabulary`, `list_strategy_signals`, `get_strategy_signal_definition`, …). Never guess a metric, transform, parameter, template, signal, or enabled-timeframe fact.
|
|
13
|
+
|
|
14
|
+
## Single-account vs multi-account request shape
|
|
15
|
+
|
|
16
|
+
The strategy-authoring tools — `get_strategy_section_template`, `update_strategy_signal_rule`, `compile_strategy_plan`, `apply_strategy_plan` — use one strict server-owned envelope, `{ request: canonicalPayload }`.
|
|
17
|
+
|
|
18
|
+
- **Single account:** call them as the server publishes them, e.g. `compile_strategy_plan({ request })`.
|
|
19
|
+
- **Multiple accounts (proxy):** live discovery adds a sibling `account`, so the shape is exactly `{ account, request }`. Select the account in the outer field; keep `request` exactly as discovered. The proxy strips only `account` and forwards the unchanged `{ request }`.
|
|
20
|
+
|
|
21
|
+
Never put `account` inside `request`, and never flatten request fields beside it. Other tools keep whatever input shape live discovery reports for them.
|
|
22
|
+
|
|
23
|
+
## Scopes
|
|
24
|
+
|
|
25
|
+
- `mcp:read` — strategy discovery **and** non-financial configuration writes (author strategies, edit agents, customize signals). Treat it as configuration authority, not view-only.
|
|
26
|
+
- `mcp:wager` — financial actions (submit paid entries, accept/cancel entry decisions, deployment policies). Enable **Server-Signed Wagers** in Profile → MCP to grant it. Pending entry decisions come from the conversational surface, which waits for approval; an agent deployed to a radar coin or a trading-enabled arena slot executes without one.
|
|
27
|
+
|
|
28
|
+
## Author a strategy: compile → review → apply
|
|
29
|
+
|
|
30
|
+
Strategies are authored through one strict, whole-plan workflow. **Compilation changes no strategy, agent or revision; `apply_strategy_plan` is the only write to the strategy itself** — but compile is not read-only either: it parks the plan its own apply reads, and each call mints a distinct record and token, so compile once per reviewed payload and never retry or parallelise it. Review the exact returned plan before confirming.
|
|
31
|
+
|
|
32
|
+
1. **Choose the operation and revision.**
|
|
33
|
+
- `list_strategies({ includeInactive? })` — visible SYSTEM and owned PRIVATE strategies with lifecycle, quota, usage, and revisions. Use `includeInactive:true` to prepare a RESTORE.
|
|
34
|
+
- `get_strategy({ strategyId, includeInactive? })` — the complete report, dense signal scorecard, gates, usage, and current `revision`. Thread every returned revision into the next revisioned call.
|
|
35
|
+
2. **Discover the report vocabulary progressively.**
|
|
36
|
+
- `list_strategy_categories()` → `list_strategy_vocabulary({ category })` → `get_metric_construction_hints({ metric })` → `get_strategy_column_contract({ column, sectionTimeframe? })`.
|
|
37
|
+
- `get_strategy_section_template({ request })` for a listed template; `preview_strategy_report(payload)` for a point-in-time rendered preview.
|
|
38
|
+
3. **Discover signals at the strategy timeframe.**
|
|
39
|
+
- `list_strategy_signals({ module?, query? })` → `get_strategy_signal_definition({ signalId, timeframe })`. Availability is structural, not a promise of a live trigger.
|
|
40
|
+
4. **(Optional) review draft-only guidance.**
|
|
41
|
+
- `derive_strategy_rule_view({ sections, rules? })` returns report perception, server defaults, and suggestions without reading or writing a strategy. Suggestions/resets only shape the next plan input; they persist only through `apply_strategy_plan`.
|
|
42
|
+
5. **Compile one complete plan.**
|
|
43
|
+
- `compile_strategy_plan({ request })`, where the nested request contains exactly one strict branch plus a bounded `coinSelection`, `intentSummary`, and `assumptions`:
|
|
44
|
+
- **CREATE** — supplies the full new aggregate.
|
|
45
|
+
- **UPDATE** — supplies at least one changed axis and `expectedRevision`. **Send only the
|
|
46
|
+
axes that change.** The server preserves every axis you omit and every signal you do not
|
|
47
|
+
name, and it re-derives the complete post-state, scorecard and diff itself. Restating the
|
|
48
|
+
whole post-state changes nothing about the result and the account pays for those tokens on
|
|
49
|
+
this call and on every later step of the conversation. Three fields are *not* axes and are
|
|
50
|
+
required on every compile regardless — `intentSummary`, `assumptions` and `coinSelection`;
|
|
51
|
+
omitting one is a typed error, not a saving.
|
|
52
|
+
- **RESTORE** — targets an owned inactive revision and may include repair axes.
|
|
53
|
+
- Signal overrides are sparse: an omitted signal/axis stays unchanged; omitted `params` preserves canonical params byte-for-byte; present `params` replaces them only after strict validation.
|
|
54
|
+
- **`required` needs a weight.** A rule with `required: true` at `allocation: 0` is rejected (contract 34): the scorecard's triggered set excludes Off, so the flag could satisfy no gate and block no trade. Either raise the allocation or leave `required: false` — the server picks neither for you. The refusal names every offending signal in `details.inertRequiredSignalIds`, and it can fire on rules you did not touch: a strategy authored before contract 34 may hold the pair, and the merged post-state is what gets checked.
|
|
55
|
+
6. **Review before confirming.**
|
|
56
|
+
- `approvedPlan` — complete post-state, proposed revision, dense scorecard, viability, canonical diff, expiry, and bound-agent impact.
|
|
57
|
+
- `reviewContext` — exact column contracts, point-in-time report preview and coin scope, open-position observation, and provisional quota/name admission (advisory until the write). Open positions are awareness only and do not block an edit.
|
|
58
|
+
- The plan token expires after five minutes. Recompile after expiry, catalog drift, revision drift, or a changed bound-agent fence.
|
|
59
|
+
7. **Apply the plan the server already holds.**
|
|
60
|
+
- After explicit user approval: `apply_strategy_plan({ request: { planToken, confirm: true } })`. **There is no `plan` member** — one is rejected as an unknown key. The server keeps the plan its own compile approved and reads it back, so nothing is copied from the compile response and nothing can be mistyped, truncated or half-reconstructed in transit.
|
|
61
|
+
- Forward `planToken` byte-for-byte exactly as compile returned it. It is an opaque signed value: never retyped, paraphrased, abbreviated or rebuilt from memory. A mangled token addresses no approved plan and is refused.
|
|
62
|
+
- Refusals and their recoveries: `PLAN_APPROVAL_NOT_FOUND` means no approved plan answers to this token — it was already applied, it lapsed, or it was never issued; compile again. `TOKEN_EXPIRED` means the five minutes ran out; compile again. Anything else — a quota, a name collision, a bound agent that changed, a moved catalog — **leaves the plan applicable**: clear the cause and confirm again with the same token while it lives.
|
|
63
|
+
- Changed configuration reaches every bound agent immediately. Report the returned strategy, committed revision, changed axes and applied impact exactly as given, and use that returned revision for the next mutation.
|
|
64
|
+
|
|
65
|
+
**Focused edits & lifecycle:** `update_strategy_signal_rule({ request })` is the thin one-rule edit (requires `required`; omit `params` to preserve them; `required: true` needs `allocation > 0`). `fork_strategy` requires `sourceRevision`; `archive_strategy` requires `expectedRevision` and `confirm:true`; `restore_strategy` is only the thin unchanged-content path — if it reports `REPAIR_REQUIRED`, use the RESTORE compile/review/apply flow instead.
|
|
66
|
+
|
|
67
|
+
**Author the full surface, not a template.** A bare compile — a few platform sections, no conditions, untouched signal weights — uses a fraction of the studio: the strategy aggregate also owns typed conditions (verdicts + `required` enforcement gates), tiered signal allocations with per-signal params, routing gates (`minAggregateScore`, `minRequiredCount`, `minAtrPct`), the ATR stop band + risk-reward floor, post-entry position management (break-even / trailing / time-decay), and marker-bearing Market Read prose. The companion **`battlegrid-strategy-studio` skill** (shipped beside this one) carries the capability map plus validated desk-grade playbooks and recipes for all of it — activate it whenever a strategy is being created or upgraded.
|
|
68
|
+
|
|
69
|
+
## Strategy-bound agents
|
|
70
|
+
|
|
71
|
+
Bind a strategy to an agent at creation time — there is no direct strategy-creation tool.
|
|
72
|
+
|
|
73
|
+
1. `list_approved_models()` — valid `modelId` values for agent creation/update.
|
|
74
|
+
2. `list_strategies()` — pick the `strategyId` to bind.
|
|
75
|
+
3. `create_intelligence_agent({ …, modelId, strategyId })` — create a strategy-bound agent (avatar is server-minted).
|
|
76
|
+
4. `update_intelligence_agent({ agentId, … })` — update config; rebinding via `strategyId` requires `confirm:true`.
|
|
77
|
+
5. `get_agent_journal({ agentId })` / `get_agent_automation_status({ agentId })` — monitor performance and deployments.
|
|
78
|
+
|
|
79
|
+
## Play a game (Market Grid)
|
|
80
|
+
|
|
81
|
+
Predict UP or DOWN for each coin in the pool; exactly one coin is your **Captain** (2x score multiplier). Drive it from the live tools:
|
|
82
|
+
|
|
83
|
+
1. `get_account_state()` — balance, rank, agent slots, wager status.
|
|
84
|
+
2. `list_market_grid_sessions({ status: "PENDING" })` — find an open game (a `$0` entry fee is risk-free).
|
|
85
|
+
3. `get_market_grid_session({ sessionId })` — coin pool, timeframe, payout structure.
|
|
86
|
+
4. `get_market_context({ sessionId })` — indicators, rankings, and trends for the session.
|
|
87
|
+
5. `check_market_grid_submission({ sessionId })` — avoid duplicate submissions (`update_market_grid` to modify).
|
|
88
|
+
6. `submit_market_grid({ sessionId, grid, reasoning, confidenceScore, modelName, pickReasoning })` — submit predictions.
|
|
89
|
+
7. `get_market_grid_results({ sessionId })` — results once the session is `SETTLED`.
|
|
90
|
+
|
|
91
|
+
**Grid validation:** grid size matches the coin pool; each coin appears once; positions are sequential (0,1,2,…); exactly one cell is `isCaptain: true`.
|
|
92
|
+
|
|
93
|
+
The `play-market-grid` prompt (discover via `prompts/list`) provides a guided end-to-end workflow.
|
|
94
|
+
|
|
95
|
+
## Retired operations
|
|
96
|
+
|
|
97
|
+
`create_strategy` (and other legacy direct-authoring/agent-scoped rule tools) are **retired** — they are absent from discovery and cannot be invoked. Author strategies with compile → review → apply, edit rules with `update_strategy_signal_rule`, and bind strategies to agents at agent creation. Do not attempt flat legacy payloads; the server enforces a closed-world request root and the proxy never reconstructs them.
|
|
98
|
+
|
|
99
|
+
## Common errors
|
|
100
|
+
|
|
101
|
+
| Error | Cause | Fix |
|
|
102
|
+
|-------|-------|-----|
|
|
103
|
+
| `BATTLEGRID_API_KEY is required` | Missing API key | Set `BATTLEGRID_API_KEY` (or `BATTLEGRID_API_KEYS`) |
|
|
104
|
+
| `API key must start with "bg_live_"` | Invalid key format | Generate a new key at battlegrid.trade → Profile → MCP |
|
|
105
|
+
| Authentication failed (401/403) | Key revoked/rotated | Generate a new key and **restart** the proxy (keys read once at startup) |
|
|
106
|
+
| `"account" parameter is required` | Multi-account call missing `account` | Add the outer `account`; keep `request` unchanged |
|
|
107
|
+
| Plan token expired / revision drift | >5 min since compile, or upstream changed | Recompile, review the fresh plan, then apply |
|
|
108
|
+
| Required at allocation Off | A rule flags `required` on a signal weighted `0` (contract 34) | Read `details.inertRequiredSignalIds`; per signal either raise `allocation` or set `required: false` |
|
|
109
|
+
| Method not found | Calling a retired/unknown tool | Re-run `tools/list`; use the compile → review → apply flow |
|
|
110
|
+
| `Wager scope required` | `mcp:wager` not enabled | Enable Server-Signed Wagers in Profile → MCP |
|
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.0
|
|
52
|
+
export declare const PACKAGE_VERSION = "31.1.0";
|
|
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.0
|
|
55
|
+
export const PACKAGE_VERSION = '31.1.0';
|
|
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.0
|
|
3
|
+
"version": "31.1.0",
|
|
4
4
|
"description": "BattleGrid MCP server — play crypto prediction games from AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"README.md",
|
|
13
|
-
"LICENSE"
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"SKILL.md",
|
|
15
|
+
"skills"
|
|
14
16
|
],
|
|
15
17
|
"scripts": {
|
|
16
18
|
"build": "tsc",
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: battlegrid-strategy-studio
|
|
3
|
+
description: Author full-power BattleGrid trading strategies over MCP — multi-section reports, benchmark sections, layered conditions with verdicts and enforcement gates, tiered signal weights, routing gates, ATR trade levels, and post-entry position management. Use whenever building or upgrading a strategy so it uses the whole studio, not a bare template. Companion to the `battlegrid` skill, which owns connection and the compile → review → apply workflow.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BattleGrid Strategy Studio — full-power authoring
|
|
7
|
+
|
|
8
|
+
A default strategy — a few platform sections, no conditions, untouched weights — wastes the
|
|
9
|
+
studio. This skill teaches every axis the strategy aggregate owns and how professional desks
|
|
10
|
+
compose them. Workflow, envelopes, and error recovery live in the `battlegrid` skill; this one
|
|
11
|
+
is about *what to author*.
|
|
12
|
+
|
|
13
|
+
**Ground rule: shapes here are binding, tokens are illustrative.** Every metric code, signal id,
|
|
14
|
+
header, and bound in this skill was validated against the live server, but the server's
|
|
15
|
+
vocabulary moves with deployments. Before compiling, re-discover (`list_strategy_categories` →
|
|
16
|
+
`list_strategy_vocabulary` → `get_metric_construction_hints` → `get_strategy_column_contract`,
|
|
17
|
+
`list_strategy_signals` → `get_strategy_signal_definition`) and prefer what discovery returns
|
|
18
|
+
over anything printed here.
|
|
19
|
+
|
|
20
|
+
## What a strategy owns (author all of it, deliberately)
|
|
21
|
+
|
|
22
|
+
| Axis | Fields | What it does |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Identity | `name` ≤50, `tagline` ≤80, `description` ≤500 | How agents and humans find it |
|
|
25
|
+
| Timeframe | `timeframe` (from discovery's enabled list) | Anchor rung; cadence persona and regime rung derive from it |
|
|
26
|
+
| Report | `sections[]` — platform + custom columns | The per-coin table the agent LLM actually reads |
|
|
27
|
+
| Conditions | `conditions[]` — typed boolean trees | Deterministic verdicts (UP/DOWN/NEITHER) + hard enforcement gates |
|
|
28
|
+
| Signal rules | `rules[]` — `{signalId, allocation 0–3, required, params?}` | What scores, how much it counts, what must fire |
|
|
29
|
+
| Routing gates | `minAggregateScore` 0–1, `minRequiredCount` 0–20, `minAtrPct` | Whether a scored setup may route to a trade |
|
|
30
|
+
| Trade levels | `minStopLossAtrMultiple` < `maxStopLossAtrMultiple`, `minRiskRewardRatio` | Where stops/targets may sit |
|
|
31
|
+
| Position management | breakEven / trailing / timeDecay dials | How the stop moves after entry |
|
|
32
|
+
| Market Read | `marketReadText` ≤2000 with `{...}` markers | Standing orders rendered with live values |
|
|
33
|
+
|
|
34
|
+
A compile also always carries `intentSummary`, `assumptions[]`, and `coinSelection` — call
|
|
35
|
+
context, not strategy state (see the `battlegrid` skill).
|
|
36
|
+
|
|
37
|
+
## The full-power checklist
|
|
38
|
+
|
|
39
|
+
Before compiling a CREATE, confirm all six; a "no" is a decision, not an omission:
|
|
40
|
+
|
|
41
|
+
1. Report has at least one **custom section** whose columns encode the thesis (not only platform
|
|
42
|
+
modules), and every column earns its tokens.
|
|
43
|
+
2. **Conditions** encode the entry logic deterministically — building blocks + verdict carriers —
|
|
44
|
+
and at least one `required: true` condition guards spend on obvious disqualifiers.
|
|
45
|
+
3. **Every signal you want scoring is named in `rules`** with a deliberate tier; signals you do
|
|
46
|
+
not name keep server defaults (typically Off). Verify in the compiled scorecard, never assume.
|
|
47
|
+
4. **Gates** are set against the weight budget you chose (see scoring math below).
|
|
48
|
+
5. **Trade levels + position management** match the setup's geometry and holding period.
|
|
49
|
+
6. `marketReadText` states the standing orders with markers so the agent sees live values inline.
|
|
50
|
+
|
|
51
|
+
## Report grammar — sections and columns
|
|
52
|
+
|
|
53
|
+
A custom column is `metric × transform (± chained transform) × timeframe ref (± params)`.
|
|
54
|
+
Headers are **system-generated, never named by you**. Validated affix patterns:
|
|
55
|
+
|
|
56
|
+
| Transform | Header shape | Example (validated) |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `value` | `<code>` | `bbWidthPct`, `RVOL`, `rate` |
|
|
59
|
+
| `trajectory` (window 4) | `<code>_t3 … _t1`, `<code>_now`, `<code>_trend` (rising/falling/flat) | `RSI14_now`, `RSI14_trend` |
|
|
60
|
+
| `distance` (price → level) | `dist_<code>` (signed %) | `dist_SMA50` |
|
|
61
|
+
| `spread` (base vs operand) | `<base>_<operand>_spread` | `mark_oracle_spread`, `EMA5_EMA13_spread` |
|
|
62
|
+
| `aggregate` (window N) | `<code>_mean<N>` | `rate_mean24` |
|
|
63
|
+
| `rank` (ordering hi/lo/far/near) | `<code>_rank_<ordering>` — ordinal, 1 = best; compare with `lte N` for top-N | `bbWidthPct_rank_lo`, `closeChg_rank_far` |
|
|
64
|
+
| `efficiency` (window N) | `<code>_er` (0–1; 1 = straight move, ~0 = chop) | `close_ltf_er` |
|
|
65
|
+
| `maxShare` (window N) | `<code>_maxShare` (0–1 concentration) | `volBase_ltf_maxShare` |
|
|
66
|
+
| `classifyZone` / `classifyState` | `<code>_zone` / `<code>_state` | `RSI14_zone` (overbought/oversold/neutral), `ADX_state` (weak/developing/trending/extreme) |
|
|
67
|
+
|
|
68
|
+
Non-anchor rungs add a rung affix: `_ltf` (lower) / `_htf` (regime), e.g. `MAalign_htf`,
|
|
69
|
+
`zones_htf_support_dist`. Chains are bounded at two: inner `distance`/`spread` → outer
|
|
70
|
+
`trajectory`/`aggregate`/`efficiency`/`maxShare`/`rank` (e.g. EMA5 `spread` EMA13 ×
|
|
71
|
+
`trajectory` → `EMA5_EMA13_spread_now` + `_trend`). Read exact headers from
|
|
72
|
+
`get_strategy_column_contract` or a `preview_strategy_report`'s `conditionColumns` before
|
|
73
|
+
writing conditions against them.
|
|
74
|
+
|
|
75
|
+
**Timeframe references — two families.** *Relative* (`{rel: "anchor" | "lower" | "regime"}`)
|
|
76
|
+
re-resolve when the strategy timeframe changes; `regime` is the anchor's ladder successor (a 4h
|
|
77
|
+
anchor's regime rung is 1d today). *Pinned* (`{abs: "<tf>"}`) is fixed and ignores anchor
|
|
78
|
+
retunes; its legal set is discovery's `rankedTimeframes` — a **superset** of the authorable
|
|
79
|
+
anchor set (`timeframes`), so `{abs: "1d"}` is valid while `1d` is not an anchor. Pinned
|
|
80
|
+
headers suffix the literal: `RSI14_1d`, `dist_SMA200_1d`, `MAalign_1d` (validated). `offset: 1`
|
|
81
|
+
on a pinned `value` column reads the **last closed** bar of that timeframe — the deterministic
|
|
82
|
+
daily-close read; offset does not change the header, so one section carries one offset per
|
|
83
|
+
`metric × timeframe`. This is how higher-timeframe theses (daily-chart strategies included)
|
|
84
|
+
are authored on an intraday anchor — see the daily pattern in `references/tradingview-ports.md`.
|
|
85
|
+
|
|
86
|
+
**Benchmark sections** (`benchmarkTicker: "BTC"` on a custom section) read the *benchmark's*
|
|
87
|
+
values instead of the evaluated coin's — the standard way to gate a whole book on market-leader
|
|
88
|
+
regime. `benchmarkTicker` is required-nullable on every custom section: send `null` for an
|
|
89
|
+
ordinary section, never omit it.
|
|
90
|
+
|
|
91
|
+
Budgets are served by discovery (validated today: 32 sections, 32 custom columns, 8 distinct
|
|
92
|
+
timeframes, 16 conditions, 16 clauses, 16k estimated report tokens). `preview_strategy_report`
|
|
93
|
+
echoes your usage against each cap.
|
|
94
|
+
|
|
95
|
+
## Conditions — deterministic logic over your own report
|
|
96
|
+
|
|
97
|
+
Each condition: `{ conditionKey, name, definition, verdict, required }` — all five required;
|
|
98
|
+
`verdict` and `required` have no defaults.
|
|
99
|
+
|
|
100
|
+
- **Clauses** compare one column: numeric/rank headers take `lt|lte|gte|gt|between`;
|
|
101
|
+
classification/direction/event headers take `is|in` with the column's exact vocabulary
|
|
102
|
+
(served per header in `conditionColumns` / the column contract).
|
|
103
|
+
- **Groups**: `ALL`, `ANY`, `NOT`, `N_OF` (with `n`), depth ≤ 2 (an inner group holds leaves
|
|
104
|
+
only).
|
|
105
|
+
- **References** (`{kind:"conditionRef", conditionKey}`) compose named conditions; cycles are
|
|
106
|
+
rejected, forward references are legal.
|
|
107
|
+
- **Column addressing**: `{sectionKey, header}`. `sectionKey: null` is authoring sugar for a
|
|
108
|
+
header unique across the whole report; a duplicated header (e.g. `ADX` in two sections) must
|
|
109
|
+
be section-qualified. To reference your own custom sections at CREATE time, mint the
|
|
110
|
+
`custom:<uuid>` sectionKey yourself and reuse it in the clauses.
|
|
111
|
+
- **Verdict**: `UP` | `DOWN` | `NEITHER` on deciding conditions, explicit `null` on building
|
|
112
|
+
blocks. Declaration order is precedence: the first TRUE condition with a non-null verdict
|
|
113
|
+
decides the coin's verdict. Put the more specific carrier first.
|
|
114
|
+
- **`required: true`** makes a FALSE reading a hard gate: the compose-trade evaluation is
|
|
115
|
+
blocked *before any billing or LLM call*. This is the cheapest risk control in the studio —
|
|
116
|
+
use it for liquidity floors, regime vetoes, and "never fight the HTF" rules.
|
|
117
|
+
- Evaluation is three-valued: `UNRESOLVED` (missing input) is never collapsed to FALSE, and
|
|
118
|
+
outcomes read from a still-forming bar are marked provisional.
|
|
119
|
+
|
|
120
|
+
## Signal weights — the scorecard is a weighted average, budget it
|
|
121
|
+
|
|
122
|
+
Allocation tiers: `0` Off, `1` Normal, `2` Important, `3` Critical.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
aggregateScore = Σ(score × allocation) / Σ(allocation) over triggered signals
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Consequences worth designing around:
|
|
129
|
+
|
|
130
|
+
- Weights are **relative**: one Critical among Normals dominates; all-Critical equals all-Normal.
|
|
131
|
+
Build a pyramid — 1–2 Critical (the thesis), 2–4 Important (confirmation), a few Normal
|
|
132
|
+
(context) — and turn everything else Off so noise cannot dilute the average.
|
|
133
|
+
- `required: true` on a rule does two things: the signal counts toward `minRequiredCount` when
|
|
134
|
+
it triggers, and the gate blocks routing when too few required signals fired. A rule with
|
|
135
|
+
`required: true` at allocation 0 is **rejected** (contract 34) — raise the allocation or clear
|
|
136
|
+
the flag.
|
|
137
|
+
- `params` are per-signal and replace canonical defaults only when present and valid — tune
|
|
138
|
+
thresholds to the strategy (e.g. an RSI-overbought at 65 for a fade book) after reading
|
|
139
|
+
`get_strategy_signal_definition({signalId, timeframe})`. Omitted `params` preserve defaults
|
|
140
|
+
byte-for-byte.
|
|
141
|
+
- Which signals *can* trigger follows from your report's sections/columns
|
|
142
|
+
(`derive_strategy_rule_view` shows in-report membership for a draft). Weighting a signal your
|
|
143
|
+
report never feeds is dead weight.
|
|
144
|
+
|
|
145
|
+
## Routing gates
|
|
146
|
+
|
|
147
|
+
- `minAggregateScore` (0–1): floor on the weighted average above. Set it from your pyramid: if
|
|
148
|
+
routing should need the Critical thesis plus one Important confirmation, compute that mix's
|
|
149
|
+
aggregate and gate just under it.
|
|
150
|
+
- `minRequiredCount` (0–20): how many `required` signals must be among the triggered set.
|
|
151
|
+
- `minAtrPct`: minimum ATR as % of price — a dead-market filter; bounds come from
|
|
152
|
+
`get_trading_config_catalog` (validated today: 0.1–10).
|
|
153
|
+
|
|
154
|
+
## Trade levels (ATR geometry)
|
|
155
|
+
|
|
156
|
+
`minStopLossAtrMultiple < maxStopLossAtrMultiple` (band where the stop may sit; ceiling capped
|
|
157
|
+
at the structural 3×ATR), `minRiskRewardRatio` (catalog bounds today: 0.5–3). Position size is
|
|
158
|
+
risk-budget based (`riskPct / stopDistancePct` — see the `battlegrid` skill's contract notes),
|
|
159
|
+
so a *wider* stop means a *smaller* position, not more risk. Tight bands suit breakout entries;
|
|
160
|
+
wide bands suit mean reversion that needs room.
|
|
161
|
+
|
|
162
|
+
## Position management (how the stop moves)
|
|
163
|
+
|
|
164
|
+
Validated live bounds: `breakEvenTriggerR` 0.5–2 · `trailingTriggerR` 0–2 step 0.01 (0 = trail
|
|
165
|
+
from entry) · `trailingGivebackPct` 25–55 · `trailingBufferPct` 0.01–1 · `timeDecay` grace
|
|
166
|
+
1–1440 min ≥ interval 1–480 min, tighten 0.1–50%, max 1–100%, stale threshold 0–100% of TP
|
|
167
|
+
progress. Each mechanism has its own enabled flag; there is no umbrella switch. Trend books:
|
|
168
|
+
arm break-even ~1R, trail late with a generous giveback (40–55). Mean-reversion/scalp books:
|
|
169
|
+
break-even early, tight giveback, and **timeDecay on** — a thesis that hasn't paid within its
|
|
170
|
+
horizon should be squeezed out.
|
|
171
|
+
|
|
172
|
+
## Coin selection (per compile, not persisted)
|
|
173
|
+
|
|
174
|
+
`{mode:"ranked", limit, category?}` (categories today: ALL, CRYPTO, L1, MEMES, DEFI, TRADFI,
|
|
175
|
+
STOCKS, INDICES, COMMODITIES) or `{mode:"explicit", tickers[]}`. Choose the cohort the review
|
|
176
|
+
should render over — explicit tickers for a focused edit, ranked for a scanning book.
|
|
177
|
+
|
|
178
|
+
## Market Read markers
|
|
179
|
+
|
|
180
|
+
`marketReadText` renders with live values wherever a `{...}` marker names a column header
|
|
181
|
+
(`{RVOL}`), a condition (`{SQUEEZE_ON}` — renders outcome plus evidence), or a section-qualified
|
|
182
|
+
form on collision (`{custom:<uuid>.MAalign}`). The preview returns `marketReadMarkers` with each
|
|
183
|
+
marker's resolution status — fix `unknown`/`ambiguous` markers before compiling.
|
|
184
|
+
|
|
185
|
+
## References
|
|
186
|
+
|
|
187
|
+
- `references/playbooks.md` — five validated desk-grade playbooks with full payloads: volatility
|
|
188
|
+
compression breakout, crowded-positioning fade, relative-strength rotation with a benchmark
|
|
189
|
+
gate, HTF trend pullback, perp/spot flow divergence at structure.
|
|
190
|
+
- `references/recipes.md` — copy-adaptable column recipes, condition patterns, weight matrices,
|
|
191
|
+
and trade-level/position-management presets per trading persona.
|
|
192
|
+
- `references/tradingview-ports.md` — the most popular TradingView community scripts (Squeeze
|
|
193
|
+
Momentum [LazyBear], Supertrend/UT Bot, Chandelier Exit, MACD + 200 MA, golden cross, RSI-2,
|
|
194
|
+
VWAP reversion, Donchian/Turtle, ICT FVG/order blocks) translated process-for-process onto
|
|
195
|
+
the studio's vocabulary, with an expressibility triage and honest named substitutions for
|
|
196
|
+
what the grammar cannot carry.
|
|
197
|
+
|
|
198
|
+
Validate a draft cheaply before compiling: `derive_strategy_rule_view` (report membership +
|
|
199
|
+
rule defaults, no write) and `preview_strategy_report` (rendered tables, condition outcomes
|
|
200
|
+
with evidence, verdict tally, budget usage, marker resolution). Then compile once, review the
|
|
201
|
+
compiled truth, and apply.
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# Strategy Studio playbooks — validated desk-grade examples
|
|
2
|
+
|
|
3
|
+
Five complete composition patterns, modeled on how systematic desks actually structure these
|
|
4
|
+
trades. Every header, vocabulary label, bound, and shape below was validated against the live
|
|
5
|
+
server (preview + column contracts) on 2026-08-28. Tokens are still illustrative: re-discover
|
|
6
|
+
before compiling, and read exact headers from `preview_strategy_report`'s `conditionColumns`.
|
|
7
|
+
|
|
8
|
+
Playbook 1 shows the **full** `compile_strategy_plan` CREATE request. Playbooks 2–5 show only
|
|
9
|
+
the axes that differ — their envelope (`operation`, `intentSummary`, `assumptions`,
|
|
10
|
+
`coinSelection`, identity, timeframe) follows the same shape. Multi-account proxies wrap each as
|
|
11
|
+
`{ account, request }`.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1 · Volatility Compression Breakout (vol desk, 4h swing)
|
|
16
|
+
|
|
17
|
+
**Thesis.** Coins in the tightest Bollinger compression on the board, with trend strength not yet
|
|
18
|
+
developed, break hard when volume arrives. Trade the expansion, direction decided by the break.
|
|
19
|
+
|
|
20
|
+
**Why this shape.** Cross-sectional `rank` finds compression *relative to the whole universe*
|
|
21
|
+
(cheaper and more robust than an absolute width threshold); a building-block condition holds the
|
|
22
|
+
squeeze state and two verdict carriers decide direction; a required liquidity floor stops the
|
|
23
|
+
agent spending calls on illiquid names; tight stop band + trailing because breakout invalidation
|
|
24
|
+
is nearby and winners should run.
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"request": {
|
|
29
|
+
"operation": "CREATE",
|
|
30
|
+
"intentSummary": "Cross-sectional volatility-compression breakout: enter expansions out of the tightest Bollinger squeezes on the board, direction decided by the breaking bar, volume-confirmed.",
|
|
31
|
+
"assumptions": [
|
|
32
|
+
"4h anchor: swing cadence, structural zones read on the regime rung",
|
|
33
|
+
"Ranked CRYPTO cohort approximates the live tradable universe"
|
|
34
|
+
],
|
|
35
|
+
"coinSelection": { "mode": "ranked", "limit": 20, "category": "CRYPTO" },
|
|
36
|
+
"name": "Compression Breakout",
|
|
37
|
+
"tagline": "Tightest bands on the board, traded on expansion",
|
|
38
|
+
"description": "Scans the universe for Bollinger-width compression (bottom-decile rank), requires real participation, and enters the expansion bar in its own direction. Structure zones frame targets.",
|
|
39
|
+
"timeframe": "4h",
|
|
40
|
+
"sections": [
|
|
41
|
+
{
|
|
42
|
+
"kind": "custom",
|
|
43
|
+
"sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5",
|
|
44
|
+
"title": "Squeeze Scan",
|
|
45
|
+
"benchmarkTicker": null,
|
|
46
|
+
"columns": [
|
|
47
|
+
{ "metric": "BB_WIDTH_PCT", "transformId": "value", "timeframe": { "rel": "anchor" } },
|
|
48
|
+
{ "metric": "BB_WIDTH_PCT", "transformId": "rank", "timeframe": { "rel": "anchor" }, "ordering": "lo" },
|
|
49
|
+
{ "metric": "ADX", "transformId": "value", "timeframe": { "rel": "anchor" } },
|
|
50
|
+
{ "metric": "RVOL", "transformId": "value", "timeframe": { "rel": "anchor" } },
|
|
51
|
+
{ "metric": "BB_PCT_B", "transformId": "value", "timeframe": { "rel": "anchor" } },
|
|
52
|
+
{ "metric": "CLOSE_CHANGE", "transformId": "value", "timeframe": { "rel": "anchor" } },
|
|
53
|
+
{ "metric": "NOTIONAL_VOLUME_1D", "transformId": "value", "timeframe": { "rel": "anchor" } }
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{ "kind": "platform", "sectionKey": "includeBollingerBands" },
|
|
57
|
+
{ "kind": "platform", "sectionKey": "includeStructureZones" }
|
|
58
|
+
],
|
|
59
|
+
"conditions": [
|
|
60
|
+
{
|
|
61
|
+
"conditionKey": "LIQUID_FLOOR",
|
|
62
|
+
"name": "Liquidity floor",
|
|
63
|
+
"definition": { "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "vol24hUsd" }, "op": "gte", "value": 25000000 },
|
|
64
|
+
"verdict": null,
|
|
65
|
+
"required": true
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"conditionKey": "SQUEEZE_ON",
|
|
69
|
+
"name": "Volatility compression",
|
|
70
|
+
"definition": {
|
|
71
|
+
"kind": "group", "op": "ALL", "members": [
|
|
72
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "bbWidthPct_rank_lo" }, "op": "lte", "value": 10 },
|
|
73
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "ADX" }, "op": "lt", "value": 20 }
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"verdict": null,
|
|
77
|
+
"required": false
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"conditionKey": "BREAK_UP",
|
|
81
|
+
"name": "Upside expansion",
|
|
82
|
+
"definition": {
|
|
83
|
+
"kind": "group", "op": "ALL", "members": [
|
|
84
|
+
{ "kind": "conditionRef", "conditionKey": "SQUEEZE_ON" },
|
|
85
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "closeChg" }, "op": "gt", "value": 0 },
|
|
86
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "RVOL" }, "op": "gte", "value": 1.5 },
|
|
87
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "pctB" }, "op": "gte", "value": 0.85 }
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
"verdict": "UP",
|
|
91
|
+
"required": false
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"conditionKey": "BREAK_DOWN",
|
|
95
|
+
"name": "Downside expansion",
|
|
96
|
+
"definition": {
|
|
97
|
+
"kind": "group", "op": "ALL", "members": [
|
|
98
|
+
{ "kind": "conditionRef", "conditionKey": "SQUEEZE_ON" },
|
|
99
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "closeChg" }, "op": "lt", "value": 0 },
|
|
100
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "RVOL" }, "op": "gte", "value": 1.5 },
|
|
101
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:a1a1a1a1-b2b2-4c3c-8d4d-e5e5e5e5e5e5", "header": "pctB" }, "op": "lte", "value": 0.15 }
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"verdict": "DOWN",
|
|
105
|
+
"required": false
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"rules": [
|
|
109
|
+
{ "signalId": "bollinger_squeeze", "allocation": 3, "required": true },
|
|
110
|
+
{ "signalId": "volume_surge", "allocation": 2, "required": true, "params": { "multiplier": 1.5 } },
|
|
111
|
+
{ "signalId": "volatility_atr_expanding", "allocation": 2, "required": false },
|
|
112
|
+
{ "signalId": "sr_resistance_break", "allocation": 2, "required": false },
|
|
113
|
+
{ "signalId": "sr_support_break", "allocation": 2, "required": false },
|
|
114
|
+
{ "signalId": "bollinger_upper_touch", "allocation": 1, "required": false },
|
|
115
|
+
{ "signalId": "bollinger_lower_touch", "allocation": 1, "required": false },
|
|
116
|
+
{ "signalId": "trend_adx_ranging", "allocation": 1, "required": false }
|
|
117
|
+
],
|
|
118
|
+
"minAggregateScore": 0.55,
|
|
119
|
+
"minRequiredCount": 2,
|
|
120
|
+
"minAtrPct": 0.8,
|
|
121
|
+
"minStopLossAtrMultiple": 0.75,
|
|
122
|
+
"maxStopLossAtrMultiple": 1.75,
|
|
123
|
+
"minRiskRewardRatio": 2,
|
|
124
|
+
"breakEvenEnabled": true,
|
|
125
|
+
"breakEvenTriggerR": 1,
|
|
126
|
+
"trailingEnabled": true,
|
|
127
|
+
"trailingTriggerR": 1.2,
|
|
128
|
+
"trailingGivebackPct": 35,
|
|
129
|
+
"trailingBufferPct": 0.3,
|
|
130
|
+
"timeDecayEnabled": false,
|
|
131
|
+
"marketReadText": "Trade only expansions out of compression: {SQUEEZE_ON} must read TRUE on the setup bar. Board standing {bbWidthPct_rank_lo}; participation {RVOL} must be at least 1.5x. Longs on {BREAK_UP}, shorts on {BREAK_DOWN}; skip anything failing {LIQUID_FLOOR}. Frame targets against the regime-rung zones ({zones_htf_resist_dist} up, {zones_htf_support_dist} down)."
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Review focus.** In the compile response: the scorecard should show exactly the eight named
|
|
137
|
+
rules scoring (everything else Off); `reviewContext.reportPreview.conditionOutcomes` should show
|
|
138
|
+
`SQUEEZE_ON` evidence per coin and the verdict tally; check `marketReadMarkers` all resolve.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 2 · Crowded-Positioning Fade (derivatives desk, 1h intraday)
|
|
143
|
+
|
|
144
|
+
**Thesis.** When funding is paying one side heavily, open interest is building, and the OI·price
|
|
145
|
+
regime says the crowd is adding into an extended move, fade it back toward equilibrium.
|
|
146
|
+
|
|
147
|
+
**Sections.** Custom `Positioning` (mint key `custom:b2b2…`):
|
|
148
|
+
`FUNDING_RATE value` → `rate` · `FUNDING_RATE aggregate w24` → `rate_mean24` (one venue print is
|
|
149
|
+
noise; the 24-sample mean is the crowd's bill) · `FUNDING_ANN value` → `ann` · `OI_CHG value` →
|
|
150
|
+
`oiChg` · `OI_PX_REGIME value` → `oiRegime` (vocab: `new longs` / `new shorts` /
|
|
151
|
+
`short covering` / `long liquidation`) · `MARK spread ORACLE` → `mark_oracle_spread` (perp
|
|
152
|
+
premium) · `RSI14 value` · `CHG_24H value` → `chg24h`. Plus platform `includeCvd`.
|
|
153
|
+
|
|
154
|
+
**Conditions** (building blocks carry no verdict; NOT as a required veto):
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
[
|
|
158
|
+
{ "conditionKey": "NO_LIQUIDATION_TAPE", "name": "Not a liquidation knife",
|
|
159
|
+
"definition": { "kind": "group", "op": "NOT", "members": [
|
|
160
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiRegime" }, "op": "is", "label": "long liquidation" } ] },
|
|
161
|
+
"verdict": null, "required": true },
|
|
162
|
+
{ "conditionKey": "CROWDED_LONGS", "name": "Longs overpaying and adding",
|
|
163
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
164
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "ann" }, "op": "gte", "value": 25 },
|
|
165
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiChg" }, "op": "gte", "value": 3 },
|
|
166
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiRegime" }, "op": "is", "label": "new longs" } ] },
|
|
167
|
+
"verdict": null, "required": false },
|
|
168
|
+
{ "conditionKey": "FADE_SHORT", "name": "Fade the crowded long",
|
|
169
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
170
|
+
{ "kind": "conditionRef", "conditionKey": "CROWDED_LONGS" },
|
|
171
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "RSI14" }, "op": "gte", "value": 65 },
|
|
172
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "chg24h" }, "op": "gte", "value": 5 } ] },
|
|
173
|
+
"verdict": "DOWN", "required": false },
|
|
174
|
+
{ "conditionKey": "SQUEEZED_SHORTS", "name": "Shorts overpaying and adding",
|
|
175
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
176
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "ann" }, "op": "lte", "value": -25 },
|
|
177
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiChg" }, "op": "gte", "value": 3 },
|
|
178
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "oiRegime" }, "op": "is", "label": "new shorts" } ] },
|
|
179
|
+
"verdict": null, "required": false },
|
|
180
|
+
{ "conditionKey": "SQUEEZE_LONG", "name": "Squeeze the crowded short",
|
|
181
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
182
|
+
{ "kind": "conditionRef", "conditionKey": "SQUEEZED_SHORTS" },
|
|
183
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:b2b2b2b2-c3c3-4d4d-8e5e-f6f6f6f6f6f6", "header": "RSI14" }, "op": "lte", "value": 35 } ] },
|
|
184
|
+
"verdict": "UP", "required": false }
|
|
185
|
+
]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Rules** (note the tuned params — read `get_strategy_signal_definition` first):
|
|
189
|
+
`funding_extreme_positive` 3 required, params `{"thresholdPct": 0.001}` ·
|
|
190
|
+
`funding_extreme_negative` 3 required, params `{"thresholdPct": 0.001}` · `oi_surge` 2, params
|
|
191
|
+
`{"thresholdPct": 0.03}` · `rsi_overbought` 2, params `{"threshold": 65}` · `rsi_oversold` 2,
|
|
192
|
+
params `{"threshold": 35}` · `cvd_bear_divergence` 2 · `cvd_bull_divergence` 2 ·
|
|
193
|
+
`mfi_overbought` 1 · `mfi_oversold` 1.
|
|
194
|
+
|
|
195
|
+
**Gates & geometry.** `minAggregateScore 0.6`, `minRequiredCount 1` (either funding extreme),
|
|
196
|
+
`minAtrPct 0.5`. Levels `1.0–2.5` ATR (mean reversion needs room), RR `1.5`. Position
|
|
197
|
+
management: break-even at `0.8R`, trailing off, **timeDecay on** (`grace 120`, `interval 60`,
|
|
198
|
+
`tighten 10`, `max 40`, `stale 25`) — a fade that hasn't paid in a couple of hours is wrong.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 3 · Relative-Strength Rotation with a Benchmark Gate (cross-sectional desk, 4h)
|
|
203
|
+
|
|
204
|
+
**Thesis.** Own the leaders while the market leader trends up; press the laggards when it rolls
|
|
205
|
+
over. Selection is cross-sectional (ranks), regime is read off a **benchmark section**.
|
|
206
|
+
|
|
207
|
+
**Sections.** Custom `Leadership` (key `custom:d4d4…`): `CLOSE_CHANGE rank hi` →
|
|
208
|
+
`closeChg_rank_hi` · `CLOSE_CHANGE rank lo` → `closeChg_rank_lo` · `RVOL rank hi` →
|
|
209
|
+
`RVOL_rank_hi` · `EMA5 spread EMA13 × trajectory w4` → `EMA5_EMA13_spread_now` / `_trend` ·
|
|
210
|
+
`NOTIONAL_VOLUME_1D value` → `vol24hUsd`. Custom `BTC Regime` with `"benchmarkTicker": "BTC"`
|
|
211
|
+
(key `custom:c3c3…`): `MA_ALIGN value` → `MAalign` (bullish/bearish/mixed) · `ADX value` ·
|
|
212
|
+
`REGIME_TREND value` → `regTrend`. Every row of that section reads BTC, not the evaluated coin.
|
|
213
|
+
|
|
214
|
+
**Conditions** (refs compose the regime gate with selection; duplicated headers like `ADX`
|
|
215
|
+
must be section-qualified — that is why the section keys are minted client-side):
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
[
|
|
219
|
+
{ "conditionKey": "BTC_RISK_ON", "name": "BTC trending up",
|
|
220
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
221
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:c3c3c3c3-d4d4-4e5e-8f6f-a7a7a7a7a7a7", "header": "MAalign" }, "op": "is", "label": "bullish" },
|
|
222
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:c3c3c3c3-d4d4-4e5e-8f6f-a7a7a7a7a7a7", "header": "ADX" }, "op": "gte", "value": 20 } ] },
|
|
223
|
+
"verdict": null, "required": false },
|
|
224
|
+
{ "conditionKey": "BTC_RISK_OFF", "name": "BTC rolling over",
|
|
225
|
+
"definition": { "kind": "clause", "column": { "sectionKey": "custom:c3c3c3c3-d4d4-4e5e-8f6f-a7a7a7a7a7a7", "header": "MAalign" }, "op": "is", "label": "bearish" },
|
|
226
|
+
"verdict": null, "required": false },
|
|
227
|
+
{ "conditionKey": "LEADER", "name": "Top-5 leader with participation",
|
|
228
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
229
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:d4d4d4d4-e5e5-4f6f-8a7a-b8b8b8b8b8b8", "header": "closeChg_rank_hi" }, "op": "lte", "value": 5 },
|
|
230
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:d4d4d4d4-e5e5-4f6f-8a7a-b8b8b8b8b8b8", "header": "RVOL_rank_hi" }, "op": "lte", "value": 10 },
|
|
231
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:d4d4d4d4-e5e5-4f6f-8a7a-b8b8b8b8b8b8", "header": "vol24hUsd" }, "op": "gte", "value": 100000000 } ] },
|
|
232
|
+
"verdict": null, "required": false },
|
|
233
|
+
{ "conditionKey": "ROTATE_IN", "name": "Rotate into leadership",
|
|
234
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
235
|
+
{ "kind": "conditionRef", "conditionKey": "BTC_RISK_ON" },
|
|
236
|
+
{ "kind": "conditionRef", "conditionKey": "LEADER" } ] },
|
|
237
|
+
"verdict": "UP", "required": false },
|
|
238
|
+
{ "conditionKey": "ROTATE_OUT", "name": "Press laggards in risk-off",
|
|
239
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
240
|
+
{ "kind": "conditionRef", "conditionKey": "BTC_RISK_OFF" },
|
|
241
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:d4d4d4d4-e5e5-4f6f-8a7a-b8b8b8b8b8b8", "header": "closeChg_rank_lo" }, "op": "lte", "value": 5 } ] },
|
|
242
|
+
"verdict": "DOWN", "required": false }
|
|
243
|
+
]
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Rules.** `comparison_sector_momentum` 3 · `rel_roc_positive` 2 · `rel_roc_negative` 2 ·
|
|
247
|
+
`ma_ema_aligned_bull` 2 required · `ma_ema_aligned_bear` 2 required · `rel_ppo_bull_cross` 1 ·
|
|
248
|
+
`rel_ppo_bear_cross` 1 · `volume_surge` 1. Gates: `0.5 / 1 / 0.6`. Levels `1–2` ATR, RR `1.8`.
|
|
249
|
+
PM: break-even 1R; trailing at 1R with giveback 45 (let rotation winners run); no timeDecay.
|
|
250
|
+
`coinSelection` `{"mode":"ranked","limit":40,"category":"CRYPTO"}` — ranks need a wide cohort.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 4 · HTF Trend Pullback (CTA desk, 1h, multi-timeframe confluence)
|
|
255
|
+
|
|
256
|
+
**Thesis.** Only trade with the higher-timeframe trend; enter on anchor-rung pullbacks to value
|
|
257
|
+
while the lower rung has not broken structure. Never spend an LLM call in chop.
|
|
258
|
+
|
|
259
|
+
**Sections.** Platform `includeMtfConfluence` (headers per rung: `MAalign_ltf` / `MAalign` /
|
|
260
|
+
`MAalign_htf`, `RSI14_*_zone`, `ADX_*_state` with vocab weak/developing/trending/extreme),
|
|
261
|
+
platform `includeMovingAverages` (`dist_SMA20`, `dist_EMA20`, …), platform `includeRsi`
|
|
262
|
+
(`RSI14_now`, `RSI14_zone`). Platform section keys are their literal `sectionKey` — qualify
|
|
263
|
+
clauses with e.g. `"sectionKey": "includeMtfConfluence"` where headers collide.
|
|
264
|
+
|
|
265
|
+
**Conditions** — the `ANY`-of-trends required gate is the key move: chop (neither trend) blocks
|
|
266
|
+
compose-trade entirely, before billing:
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
[
|
|
270
|
+
{ "conditionKey": "HTF_UP", "name": "HTF uptrend",
|
|
271
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
272
|
+
{ "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "MAalign_htf" }, "op": "is", "label": "bullish" },
|
|
273
|
+
{ "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "ADX_htf_state" }, "op": "in", "labels": ["trending", "extreme"] } ] },
|
|
274
|
+
"verdict": null, "required": false },
|
|
275
|
+
{ "conditionKey": "HTF_DOWN", "name": "HTF downtrend",
|
|
276
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
277
|
+
{ "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "MAalign_htf" }, "op": "is", "label": "bearish" },
|
|
278
|
+
{ "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "ADX_htf_state" }, "op": "in", "labels": ["trending", "extreme"] } ] },
|
|
279
|
+
"verdict": null, "required": false },
|
|
280
|
+
{ "conditionKey": "TREND_PRESENT", "name": "Some HTF trend exists",
|
|
281
|
+
"definition": { "kind": "group", "op": "ANY", "members": [
|
|
282
|
+
{ "kind": "conditionRef", "conditionKey": "HTF_UP" },
|
|
283
|
+
{ "kind": "conditionRef", "conditionKey": "HTF_DOWN" } ] },
|
|
284
|
+
"verdict": null, "required": true },
|
|
285
|
+
{ "conditionKey": "PULLBACK_LONG", "name": "Pullback to value in uptrend",
|
|
286
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
287
|
+
{ "kind": "conditionRef", "conditionKey": "HTF_UP" },
|
|
288
|
+
{ "kind": "clause", "column": { "sectionKey": "includeRsi", "header": "RSI14_now" }, "op": "between", "low": 35, "high": 55 },
|
|
289
|
+
{ "kind": "clause", "column": { "sectionKey": "includeMovingAverages", "header": "dist_EMA20" }, "op": "between", "low": -3, "high": 0.5 } ] },
|
|
290
|
+
"verdict": "UP", "required": false },
|
|
291
|
+
{ "conditionKey": "PULLBACK_SHORT", "name": "Rally to value in downtrend",
|
|
292
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
293
|
+
{ "kind": "conditionRef", "conditionKey": "HTF_DOWN" },
|
|
294
|
+
{ "kind": "clause", "column": { "sectionKey": "includeRsi", "header": "RSI14_now" }, "op": "between", "low": 45, "high": 65 },
|
|
295
|
+
{ "kind": "clause", "column": { "sectionKey": "includeMovingAverages", "header": "dist_EMA20" }, "op": "between", "low": -0.5, "high": 3 } ] },
|
|
296
|
+
"verdict": "DOWN", "required": false }
|
|
297
|
+
]
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Rules.** `mtf_pullback_long` 3 required · `mtf_pullback_short` 3 required ·
|
|
301
|
+
`htf_ma_aligned_bull` 2 required · `htf_ma_aligned_bear` 2 required · `htf_trend_adx_trending`
|
|
302
|
+
2 · `ma_ema_aligned_bull` 1 · `ma_ema_aligned_bear` 1 · `rsi_oversold` 1 params
|
|
303
|
+
`{"threshold": 40}` (pullback depth, not capitulation). Gates: `minAggregateScore 0.6`,
|
|
304
|
+
`minRequiredCount 2` (a pullback signal *and* an HTF alignment), `minAtrPct 0.7`. Levels
|
|
305
|
+
`1–2.5` ATR, RR `2`. PM: break-even 1R; trailing 1.5R giveback 30 buffer 0.3; no timeDecay.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 5 · Perp/Spot Flow Divergence at Structure (microstructure desk, 15m scalp)
|
|
310
|
+
|
|
311
|
+
**Thesis.** When spot flow leads (accumulation) and price sits just above a standing support
|
|
312
|
+
zone, buy the dip; when a move is perp-led and fragile into resistance, fade it. Structure comes
|
|
313
|
+
from the platform zones section; flow from the perp/spot module.
|
|
314
|
+
|
|
315
|
+
**Sections.** Platform `includePerpSpotFlow` (`perpSpotFlow` vocab: `confirmed_bull` /
|
|
316
|
+
`confirmed_bear` / `perp_led_fragile` / `spot_led_accumulation` / `neutral`; plus
|
|
317
|
+
`perpSpotStr`, `spotCVD`), platform `includeStructureZones` (`zones_htf_support_dist` — signed %
|
|
318
|
+
from price, support below price is negative; `zones_htf_resist_dist`, `_age_h`, `_type`),
|
|
319
|
+
custom `Tape` (key `custom:e5e5…`): `BUY_PRESSURE value` → `buyPres` (0–1) · `RVOL value` ·
|
|
320
|
+
`CLOSE_CHANGE value` → `closeChg`.
|
|
321
|
+
|
|
322
|
+
**Conditions** — `N_OF` requires 2-of-3 tape confirmations instead of a brittle ALL:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
[
|
|
326
|
+
{ "conditionKey": "SPOT_ACCUM", "name": "Spot-led accumulation",
|
|
327
|
+
"definition": { "kind": "clause", "column": { "sectionKey": "includePerpSpotFlow", "header": "perpSpotFlow" }, "op": "is", "label": "spot_led_accumulation" },
|
|
328
|
+
"verdict": null, "required": false },
|
|
329
|
+
{ "conditionKey": "NEAR_SUPPORT", "name": "Sitting on a standing zone",
|
|
330
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
331
|
+
{ "kind": "clause", "column": { "sectionKey": "includeStructureZones", "header": "zones_htf_support_dist" }, "op": "between", "low": -2, "high": 0 },
|
|
332
|
+
{ "kind": "clause", "column": { "sectionKey": "includeStructureZones", "header": "zones_htf_support_age_h" }, "op": "gte", "value": 12 } ] },
|
|
333
|
+
"verdict": null, "required": false },
|
|
334
|
+
{ "conditionKey": "DIP_BID", "name": "Buy the dip into support",
|
|
335
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
336
|
+
{ "kind": "conditionRef", "conditionKey": "SPOT_ACCUM" },
|
|
337
|
+
{ "kind": "conditionRef", "conditionKey": "NEAR_SUPPORT" },
|
|
338
|
+
{ "kind": "group", "op": "N_OF", "n": 2, "members": [
|
|
339
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:e5e5e5e5-f6f6-4a7a-8b8b-c9c9c9c9c9c9", "header": "buyPres" }, "op": "gte", "value": 0.55 },
|
|
340
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:e5e5e5e5-f6f6-4a7a-8b8b-c9c9c9c9c9c9", "header": "RVOL" }, "op": "gte", "value": 1.2 },
|
|
341
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:e5e5e5e5-f6f6-4a7a-8b8b-c9c9c9c9c9c9", "header": "closeChg" }, "op": "gt", "value": 0 } ] } ] },
|
|
342
|
+
"verdict": "UP", "required": false },
|
|
343
|
+
{ "conditionKey": "FRAGILE_POP", "name": "Perp-led pop into resistance",
|
|
344
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
345
|
+
{ "kind": "clause", "column": { "sectionKey": "includePerpSpotFlow", "header": "perpSpotFlow" }, "op": "is", "label": "perp_led_fragile" },
|
|
346
|
+
{ "kind": "clause", "column": { "sectionKey": "includeStructureZones", "header": "zones_htf_resist_dist" }, "op": "between", "low": 0, "high": 2 },
|
|
347
|
+
{ "kind": "clause", "column": { "sectionKey": "custom:e5e5e5e5-f6f6-4a7a-8b8b-c9c9c9c9c9c9", "header": "RVOL" }, "op": "gte", "value": 1.3 } ] },
|
|
348
|
+
"verdict": "DOWN", "required": false }
|
|
349
|
+
]
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Rules.** `flow_perp_spot_bull_divergence` 3 required · `flow_perp_spot_bear_divergence` 3
|
|
353
|
+
required · `cvd_bull_divergence` 2 · `cvd_bear_divergence` 2 · `sr_at_support` 2 ·
|
|
354
|
+
`sr_at_resistance` 2 · `structure_ob_approach` 1 · `volume_surge` 1. Gates: `0.55 / 1 / 0.4`
|
|
355
|
+
(15m: lower ATR floor). Levels `0.5–1.2` ATR, RR `1.5` — scalp geometry. PM: break-even
|
|
356
|
+
`0.7R`; trailing `0.9R`, giveback 30, buffer 0.15; **timeDecay on and aggressive** (`grace 45`,
|
|
357
|
+
`interval 15`, `tighten 15`, `max 60`, `stale 30`). `coinSelection`
|
|
358
|
+
`{"mode":"ranked","limit":15,"category":"CRYPTO"}`.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Using a playbook
|
|
363
|
+
|
|
364
|
+
1. Re-discover the vocabulary; confirm each header via `get_strategy_column_contract` or one
|
|
365
|
+
`preview_strategy_report` over your sections (read `conditionColumns`).
|
|
366
|
+
2. Adapt thresholds to the current market — preview shows live values beside each clause, so
|
|
367
|
+
calibrate literals against what the table actually renders.
|
|
368
|
+
3. Compile once, read `approvedPlan` (scorecard, diff, viability) and
|
|
369
|
+
`reviewContext.reportPreview` (condition outcomes, verdict tally, budgets), fix, recompile.
|
|
370
|
+
4. Apply with `{ "request": { "planToken": "<verbatim>", "confirm": true } }` after review.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Strategy Studio recipes — copy-adaptable fragments
|
|
2
|
+
|
|
3
|
+
Validated against the live server 2026-08-28; shapes binding, tokens illustrative — re-discover
|
|
4
|
+
before compiling. Column recipes are objects for a custom section's `columns[]`; condition
|
|
5
|
+
recipes drop into `conditions[]`.
|
|
6
|
+
|
|
7
|
+
## Column recipes
|
|
8
|
+
|
|
9
|
+
| Intent | Column | Header it generates |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Live traded price | `{ "metric": "LAST", "transformId": "value", "timeframe": { "rel": "anchor" } }` | `last` |
|
|
12
|
+
| Momentum build-up | `{ "metric": "RSI14", "transformId": "trajectory", "timeframe": { "rel": "anchor" }, "window": 4 }` | `RSI14_t3…_now`, `RSI14_trend` |
|
|
13
|
+
| Closed-bar-only trajectory (no forming-bar noise) | add `"bars": "closed"` to any trajectory | same, drawn from closed bars |
|
|
14
|
+
| Extension from trend | `{ "metric": "SMA50", "transformId": "distance", "timeframe": { "rel": "anchor" } }` | `dist_SMA50` (signed %) |
|
|
15
|
+
| Perp premium | `{ "metric": "MARK", "transformId": "spread", "timeframe": { "rel": "anchor" }, "inputs": [{ "metric": "ORACLE" }] }` | `mark_oracle_spread` |
|
|
16
|
+
| Cross-venue basis | `MARK spread SPOT_CLOSE_BN` (or `SPOT_CLOSE_CB`) | `mark_bnClose_spread` |
|
|
17
|
+
| Funding paid over a day | `{ "metric": "FUNDING_RATE", "transformId": "aggregate", "timeframe": { "rel": "anchor" }, "window": 24 }` | `rate_mean24` |
|
|
18
|
+
| Board-wide standing | `{ "metric": "ATR_PCT", "transformId": "rank", "timeframe": { "rel": "anchor" }, "ordering": "hi" }` | `atrPct_rank_hi` (1 = most volatile) |
|
|
19
|
+
| Biggest movers, sign-agnostic | `CLOSE_CHANGE rank far` | `closeChg_rank_far` |
|
|
20
|
+
| Most extended from VWAP on the board | `{ "metric": "VWAP", "transformId": "distance", "chainedTransformId": "rank", "timeframe": { "rel": "anchor" }, "ordering": "far" }` | `dist_VWAP_rank_far` (confirm via contract) |
|
|
21
|
+
| EMA ribbon compression trend | `{ "metric": "EMA5", "transformId": "spread", "chainedTransformId": "trajectory", "timeframe": { "rel": "anchor" }, "inputs": [{ "metric": "EMA13" }], "window": 4 }` | `EMA5_EMA13_spread_now`, `_trend` |
|
|
22
|
+
| Chop filter on the entry rung | `{ "metric": "CLOSE", "transformId": "efficiency", "timeframe": { "rel": "lower" }, "window": 5, "bars": "closed" }` | `close_ltf_er` (≥0.6 = directional) |
|
|
23
|
+
| One-bar volume concentration | `{ "metric": "VOLUME", "transformId": "maxShare", "timeframe": { "rel": "lower" }, "window": 4, "bars": "closed" }` | `volBase_ltf_maxShare` |
|
|
24
|
+
| Room to structure | `{ "metric": "STRUCT_ZONES", "transformId": "nearestZoneDist", "timeframe": { "rel": "regime" }, "side": "resistance" }` | `zones_htf_resist_dist` |
|
|
25
|
+
| Crowd fade input | `{ "metric": "CROWD_UPBIAS", "transformId": "value", "timeframe": { "rel": "anchor" } }` | `upBias` (%, last 4 settled sessions) |
|
|
26
|
+
| Daily trend state on any anchor | `{ "metric": "MA_ALIGN", "transformId": "value", "timeframe": { "abs": "1d" } }` | `MAalign_1d` |
|
|
27
|
+
| Last **closed** daily read (deterministic) | `{ "metric": "RSI14", "transformId": "value", "timeframe": { "abs": "1d" }, "offset": 1 }` | `RSI14_1d` — formula `RSI14[t - 1]` |
|
|
28
|
+
| Daily extension from the 200 | `{ "metric": "SMA200", "transformId": "distance", "timeframe": { "abs": "1d" } }` | `dist_SMA200_1d` |
|
|
29
|
+
| Daily-structure breakout state | `{ "metric": "PRICE_ZONE", "transformId": "value", "timeframe": { "abs": "1d" } }` | `zone_1d` — `is "breakout high"` / `"breakdown low"` |
|
|
30
|
+
| Room to the daily swing high | `{ "metric": "SWING_HIGH", "transformId": "distance", "timeframe": { "abs": "1d" } }` | `dist_swingHi_1d` (signed %; `gte 0` = above it) |
|
|
31
|
+
|
|
32
|
+
Rung suffixes: `{ "rel": "lower" }` → `_ltf`, `{ "rel": "regime" }` → `_htf` (the anchor's
|
|
33
|
+
ladder successor — 1d for a 4h anchor), pinned `{ "abs": "<tf>" }` → `_<tf>` (e.g. `_1d`).
|
|
34
|
+
Pins bind to discovery's `rankedTimeframes` (a superset of the anchor set), are fixed across
|
|
35
|
+
anchor retunes, and `offset` does not change the header — one offset per `metric × timeframe`
|
|
36
|
+
per section. `offset` is a `value`-transform parameter only: `distance` rejects it (typed
|
|
37
|
+
error), so a level is always measured as it stands now — the literal *previous-day* high/low
|
|
38
|
+
(PDH/PDL) is not addressable, and the daily swing levels (`zone_1d`, `dist_swingHi_1d`) are
|
|
39
|
+
the named nearest level for daily-breakout intents. Conditions compare a column against a
|
|
40
|
+
literal, never against another column. Rank ordering semantics: `hi` most positive, `lo` most
|
|
41
|
+
negative, `far`/`near` by magnitude (offered only where sign matters).
|
|
42
|
+
|
|
43
|
+
## Condition patterns
|
|
44
|
+
|
|
45
|
+
**Band (between).** RSI pullback zone rather than a single threshold:
|
|
46
|
+
`{ "kind": "clause", "column": { "sectionKey": null, "header": "RSI14_now" }, "op": "between", "low": 35, "high": 55 }`
|
|
47
|
+
(`sectionKey: null` is legal only while the header is unique report-wide.)
|
|
48
|
+
|
|
49
|
+
**Label membership (in).** Accept two of a classification's states:
|
|
50
|
+
`{ "kind": "clause", "column": { "sectionKey": "includeMtfConfluence", "header": "ADX_htf_state" }, "op": "in", "labels": ["trending", "extreme"] }`
|
|
51
|
+
|
|
52
|
+
**Veto (NOT + required).** Block the whole compose-trade path on a disqualifier — evaluated
|
|
53
|
+
before any billing or LLM call:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{ "conditionKey": "NO_EVENT_RISK", "name": "No liquidation tape", "verdict": null, "required": true,
|
|
57
|
+
"definition": { "kind": "group", "op": "NOT", "members": [
|
|
58
|
+
{ "kind": "clause", "column": { "sectionKey": null, "header": "oiRegime" }, "op": "is", "label": "long liquidation" } ] } }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Quorum (N_OF).** Robust confirmation instead of a brittle ALL:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{ "kind": "group", "op": "N_OF", "n": 2, "members": [
|
|
65
|
+
{ "kind": "clause", "column": { "sectionKey": null, "header": "buyPres" }, "op": "gte", "value": 0.55 },
|
|
66
|
+
{ "kind": "clause", "column": { "sectionKey": null, "header": "RVOL" }, "op": "gte", "value": 1.2 },
|
|
67
|
+
{ "kind": "clause", "column": { "sectionKey": null, "header": "closeChg" }, "op": "gt", "value": 0 } ] }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Building block + carriers.** Factor shared state once (`verdict: null`), reference it from
|
|
71
|
+
directional carriers; the first TRUE carrier **in declaration order** decides — order carriers
|
|
72
|
+
most-specific first.
|
|
73
|
+
|
|
74
|
+
**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.
|
|
76
|
+
|
|
77
|
+
**Required-count trap.** `required: true` at `allocation: 0` on a *signal rule* is rejected
|
|
78
|
+
(contract 34). A required *condition* is independent of signal weights — the two gates stack.
|
|
79
|
+
|
|
80
|
+
## Weight matrices (rules)
|
|
81
|
+
|
|
82
|
+
**Conviction pyramid (default).** 1–2 × Critical (the thesis signals, usually `required`),
|
|
83
|
+
2–4 × Important (independent confirmation — different modules, not correlated oscillators),
|
|
84
|
+
1–3 × Normal (context), everything else Off. With weights 3/2/2/1 and the Critical + one
|
|
85
|
+
Important triggered at score 1.0: aggregate = (3+2)/(3+2+2+1) = 0.625 → a gate of 0.6 means
|
|
86
|
+
"thesis + one confirmation, minimum".
|
|
87
|
+
|
|
88
|
+
**Gate math check.** Before setting `minAggregateScore`, list the trigger combinations that
|
|
89
|
+
should route, compute Σ(score×alloc)/Σ(alloc) for the weakest acceptable one, and gate just
|
|
90
|
+
below it. `simulate_aggregate_score` does this arithmetic for you from compiled values.
|
|
91
|
+
|
|
92
|
+
**Directional symmetry.** A both-ways book weights bull/bear siblings identically
|
|
93
|
+
(`ma_ema_aligned_bull`/`_bear` both 2-required) and lets conditions decide direction.
|
|
94
|
+
|
|
95
|
+
**Param tuning.** Always read `get_strategy_signal_definition({ signalId, timeframe })` first;
|
|
96
|
+
send the full replacement object. Examples validated as canonical defaults: `volume_surge`
|
|
97
|
+
`{"multiplier": 2}`, `rsi_overbought` `{"threshold": 70}`, `funding_extreme_positive`
|
|
98
|
+
`{"thresholdPct": 0.0005}`, `trend_adx_trending` `{"threshold": 25}`,
|
|
99
|
+
`comparison_btc_decorrelation` `{"maxCorrelation": 0.3}`.
|
|
100
|
+
|
|
101
|
+
## Trade-level + position-management presets by persona
|
|
102
|
+
|
|
103
|
+
| Persona (anchor) | Stop band (ATR) | RR | Break-even | Trailing | Time decay |
|
|
104
|
+
|---|---|---|---|---|---|
|
|
105
|
+
| Scalper (5m/15m) | 0.5 – 1.2 | 1.5 | 0.7R | 0.9R, giveback 30, buffer 0.15 | ON: 45/15 min, tighten 15 → max 60, stale 30 |
|
|
106
|
+
| Intraday mean-revert (1h) | 1.0 – 2.5 | 1.5 | 0.8R | off | ON: 120/60 min, tighten 10 → max 40, stale 25 |
|
|
107
|
+
| Swing breakout (4h) | 0.75 – 1.75 | 2 | 1R | 1.2R, giveback 35, buffer 0.3 | off |
|
|
108
|
+
| Swing trend (4h) | 1.0 – 2.5 | 2 | 1R | 1.5R, giveback 45–55, buffer 0.3 | off |
|
|
109
|
+
|
|
110
|
+
Bounds to respect (validated): break-even trigger 0.5–2R; trailing trigger 0–2R step 0.01
|
|
111
|
+
(0 = trail from entry); giveback 25–55%; buffer 0.01–1%; grace ≥ interval; stop-band floor <
|
|
112
|
+
ceiling ≤ the 3×ATR structural cap; RR within the catalog's served range (0.5–3 today). Wider
|
|
113
|
+
stop ⇒ smaller position (risk-budget sizing), never more risk.
|
|
114
|
+
|
|
115
|
+
## Coin selection patterns
|
|
116
|
+
|
|
117
|
+
- Focused edit / review: `{ "mode": "explicit", "tickers": ["BTC", "ETH", "SOL"] }`
|
|
118
|
+
- Scanning book: `{ "mode": "ranked", "limit": 40, "category": "CRYPTO" }`
|
|
119
|
+
- Sector books: `category` from the served list (`L1`, `MEMES`, `DEFI`, `TRADFI`, `STOCKS`,
|
|
120
|
+
`INDICES`, `COMMODITIES`) — e.g. a memes-only fade book previews against `MEMES`.
|
|
121
|
+
- Rank-based conditions need a cohort at least as wide as the rank thresholds they test.
|
|
122
|
+
|
|
123
|
+
## Market Read patterns
|
|
124
|
+
|
|
125
|
+
- Reference the decision, not just data: `"Act only while {SQUEEZE_ON} is TRUE"` renders the
|
|
126
|
+
condition's outcome *with its evidence*.
|
|
127
|
+
- Qualify on collision: `{custom:<uuid>.MAalign}` / `{includeMtfConfluence.MAalign}`.
|
|
128
|
+
- After previewing, check `marketReadMarkers[].status` — fix every `unknown` / `ambiguous`
|
|
129
|
+
marker before compiling; `qualifiedForms` lists the exact replacements.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# TradingView ports — popular scripts, translated to the studio
|
|
2
|
+
|
|
3
|
+
The most-used TradingView community scripts encode processes traders already trust. This file
|
|
4
|
+
ports the **process** of each — regime filter → setup state → trigger → stop/exit engine — onto
|
|
5
|
+
the studio's validated vocabulary. It never claims formula parity: where an indicator does not
|
|
6
|
+
exist in the catalog, the substitution is named (the studio's rule: offer the nearest
|
|
7
|
+
expressible thing **labelled as a substitute**, never as the thing itself). Headers below were
|
|
8
|
+
validated live on 2026-08-28; re-discover before compiling.
|
|
9
|
+
|
|
10
|
+
**Event columns caveat (read first).** `crossDetect` and event metrics (`MACD_cross`,
|
|
11
|
+
`EMA5_13`) print `Bullish`/`Bearish` only on the bar a cross occurs and are null otherwise —
|
|
12
|
+
null resolves a clause UNRESOLVED, not FALSE. Use them as *triggers* inside a carrier, and pair
|
|
13
|
+
them with a persistent *state* (the spread's sign, `MAalign`) for regime, exactly as the source
|
|
14
|
+
scripts separate trigger from filter.
|
|
15
|
+
|
|
16
|
+
## 0 · Daily-chart strategies — the pattern that carries every TV daily script
|
|
17
|
+
|
|
18
|
+
Most TradingView strategies run on the daily chart. The studio's anchors top out intraday
|
|
19
|
+
(discovery's `timeframes`), but **pinned timeframes** carry the daily thesis onto any anchor:
|
|
20
|
+
`{abs: "1d"}` columns are valid wherever discovery's `rankedTimeframes` includes `1d`
|
|
21
|
+
(validated), render with a `_1d` header suffix (`MAalign_1d`, `dist_SMA200_1d`, `RSI14_1d`),
|
|
22
|
+
and ignore anchor retunes. Two dials make the port faithful:
|
|
23
|
+
|
|
24
|
+
- **`offset: 1` on a pinned `value` column reads the last CLOSED daily bar** (`RSI14[t - 1]`,
|
|
25
|
+
validated) — the strategy's daily inputs then change exactly once per daily close, so a
|
|
26
|
+
required condition on them decides "on daily closes" even though the anchor cycle re-checks
|
|
27
|
+
intraday. Offset 0 reads the forming daily bar (marked provisional).
|
|
28
|
+
- **A 4h anchor's regime rung is the daily** (ladder successor), so `{rel: "regime"}` columns
|
|
29
|
+
(`MAalign_htf`) and the `htf_*` signals already evaluate on 1d for 4h strategies.
|
|
30
|
+
|
|
31
|
+
Recipe: anchor 4h → pinned-1d thesis section (trend, RSI, distance-to-MA at `offset: 1`) →
|
|
32
|
+
`required: true` daily conditions gating every carrier → anchor-rung columns only for entry
|
|
33
|
+
timing and risk. The result is arguably stronger than the TV original: decisions bind to daily
|
|
34
|
+
closes while stops, trailing, and time decay keep managing the position intraday.
|
|
35
|
+
|
|
36
|
+
## Expressibility triage
|
|
37
|
+
|
|
38
|
+
| TradingView script | Verdict | Port anchor |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| Squeeze Momentum [LazyBear] / TTM Squeeze | Process port (Keltner not in catalog — substitute named) | `bbWidthPct` + rank, `bollinger_squeeze`, momentum trajectory |
|
|
41
|
+
| Supertrend / UT Bot Alerts | Split port: entry substitute + **native stop engine** | `EMA5_13`, `MAalign`, position-management trailing |
|
|
42
|
+
| Chandelier Exit | Direct port of the *mechanism* | trailing from entry (`trailingTriggerR: 0`) |
|
|
43
|
+
| MACD + 200 MA filter | Direct process port | `MACD_cross`, `dist_SMA200`, `macd_bull_cross` |
|
|
44
|
+
| Golden / Death Cross | Direct process port | `SMA50_SMA200_spread` + `_trend` |
|
|
45
|
+
| RSI-2 (Connors) | Named substitute (no RSI-2; RSI7 is the closest series) | `RSI7`, `dist_SMA200`, time decay |
|
|
46
|
+
| VWAP reversion | Direct process port | `dist_VWAP`, `dist_VWAP_rank_far` |
|
|
47
|
+
| Donchian / Turtle breakout | Process port (N-bar channel → swing structure) | `zone`, `dist_swingHi`, `sr_resistance_break` |
|
|
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 | **Not expressible** — say so; nearest neighbors below | — |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 1 · Squeeze Momentum [LazyBear] (TTM Squeeze) — 4h
|
|
54
|
+
|
|
55
|
+
**Source process.** Squeeze ON while Bollinger Bands sit inside Keltner Channels (volatility
|
|
56
|
+
coiled); wait; when the squeeze releases, enter in the direction of the momentum histogram.
|
|
57
|
+
|
|
58
|
+
**Port.** Keltner Channels are not in the catalog, so squeeze detection substitutes
|
|
59
|
+
*cross-sectional and absolute* Bollinger compression — a stricter, universe-aware read:
|
|
60
|
+
`bbWidthPct_rank_lo lte 10` AND `ADX lt 20` as the `SQUEEZE_ON` building block. Momentum
|
|
61
|
+
direction comes from the MACD histogram trajectory (`MACD_now gt 0` with `MACD_trend rising`
|
|
62
|
+
for longs) instead of the LazyBear linreg histogram. Release = expansion bar with
|
|
63
|
+
participation: `RVOL gte 1.5` and `pctB` beyond 0.85 / 0.15. This is Playbook 1
|
|
64
|
+
(`playbooks.md`) — it *is* the LazyBear process under studio vocabulary; keep
|
|
65
|
+
`bollinger_squeeze` at Critical-required (its `bandwidthPct` param is the absolute half of the
|
|
66
|
+
detection) and `volatility_atr_expanding` as the release confirmation.
|
|
67
|
+
|
|
68
|
+
## 2 · Supertrend / UT Bot Alerts — 1h/4h
|
|
69
|
+
|
|
70
|
+
**Source process.** An ATR-offset trailing line flips below/above price; entries on the flip,
|
|
71
|
+
and the line itself is the stop for the life of the trade.
|
|
72
|
+
|
|
73
|
+
**Port — the key insight: the studio's stop engine is native, not an indicator.** What the
|
|
74
|
+
Pine script simulates with a plotted line, position management executes: enable trailing with
|
|
75
|
+
`trailingTriggerR: 0` (trail from entry — the Supertrend/UT Bot behavior), `giveback` as the
|
|
76
|
+
ATR-offset analog (30–40 tight like factor-2 Supertrend, 45–55 loose like factor-3), plus the
|
|
77
|
+
stop band (`minStopLossAtrMultiple`/`max…`) bounding the initial distance. The *flip entry*
|
|
78
|
+
has no direct equivalent (no supertrend metric) — the named substitute is a trend-state change:
|
|
79
|
+
`MAalign is "bullish"` (state) with the `EMA5_13 is "Bullish"` cross event as the trigger, and
|
|
80
|
+
`ma_ema_bull_cross` / `ma_ema_aligned_bull` Critical/Important-required in `rules`. Say the
|
|
81
|
+
substitution out loud when presenting the strategy.
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
"trailingEnabled": true, "trailingTriggerR": 0, "trailingGivebackPct": 35,
|
|
85
|
+
"trailingBufferPct": 0.2, "breakEvenEnabled": false
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 3 · Chandelier Exit — any persona
|
|
89
|
+
|
|
90
|
+
**Source process.** Stop trails the highest high since entry minus `3 × ATR(22)`; never widens.
|
|
91
|
+
|
|
92
|
+
**Port.** Pure position-management mapping — trail from entry with a giveback sized to the
|
|
93
|
+
chandelier distance: `trailingTriggerR: 0`, `trailingGivebackPct` ~45–55 (a 3×ATR pullback on
|
|
94
|
+
a typical setup surrenders roughly half its open run — tune against the previewed `atrPct`),
|
|
95
|
+
`maxStopLossAtrMultiple` near the structural cap so the initial stop can sit chandelier-wide.
|
|
96
|
+
The studio stop also never widens; time decay stays off (chandelier gives trends room).
|
|
97
|
+
|
|
98
|
+
## 4 · MACD + 200 MA filter — 4h (the classic trend-following combo)
|
|
99
|
+
|
|
100
|
+
**Source process.** Only long while price is above the 200-period average; enter on a MACD
|
|
101
|
+
bullish cross; mirror for shorts below.
|
|
102
|
+
|
|
103
|
+
**Port.** Regime as a required building block, cross as the carrier's trigger:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
[
|
|
107
|
+
{ "conditionKey": "ABOVE_200", "name": "Above the 200 SMA",
|
|
108
|
+
"definition": { "kind": "clause", "column": { "sectionKey": null, "header": "dist_SMA200" }, "op": "gt", "value": 0 },
|
|
109
|
+
"verdict": null, "required": false },
|
|
110
|
+
{ "conditionKey": "MACD_LONG", "name": "MACD bull cross in uptrend",
|
|
111
|
+
"definition": { "kind": "group", "op": "ALL", "members": [
|
|
112
|
+
{ "kind": "conditionRef", "conditionKey": "ABOVE_200" },
|
|
113
|
+
{ "kind": "clause", "column": { "sectionKey": null, "header": "MACD_cross" }, "op": "is", "label": "Bullish" } ] },
|
|
114
|
+
"verdict": "UP", "required": false }
|
|
115
|
+
]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Columns: `SMA200 distance` → `dist_SMA200`; `MACD crossDetect` → `MACD_cross` (event — TRUE
|
|
119
|
+
only on the crossing bar, otherwise UNRESOLVED; that momentariness is faithful to the source,
|
|
120
|
+
which also only fires on the cross). Rules: `macd_bull_cross`/`macd_bear_cross` 3 required ·
|
|
121
|
+
`ma_sma200_above`/`ma_sma200_below` 2 required · `trend_adx_trending` 1. Gates ~0.6 / 2.
|
|
122
|
+
Swing-trend geometry (1–2.5 ATR, RR 2, trail late).
|
|
123
|
+
|
|
124
|
+
## 5 · Golden Cross / Death Cross regime book — 4h position
|
|
125
|
+
|
|
126
|
+
**Source process.** SMA50 crossing above SMA200 defines the bull regime; below, the bear.
|
|
127
|
+
|
|
128
|
+
**Port.** The spread composition gives both the *state* (sign) and the *freshness* (trend):
|
|
129
|
+
`SMA50 spread SMA200 × trajectory w4` → `SMA50_SMA200_spread_now` (>0 = golden regime) and
|
|
130
|
+
`SMA50_SMA200_spread_trend` (`rising` = the cross is developing, not decaying). Carrier:
|
|
131
|
+
`GOLDEN` verdict UP = `spread_now gt 0` AND `trend is "rising"`; add a market-breadth gate —
|
|
132
|
+
the always-addressable `mktBreadth_crypto gte 0` (net % of the crypto field closing up) keeps
|
|
133
|
+
the book out of single-name traps. Rules: `ma_sma200_above` 3 required, `ma_ema_aligned_bull`
|
|
134
|
+
2, `htf_ma_aligned_bull` 2. Position persona: wide stops (1.5–2.5 ATR), RR 2, trail
|
|
135
|
+
1.5R/giveback 50.
|
|
136
|
+
|
|
137
|
+
## 6 · RSI-2 (Larry Connors) — 1h mean reversion
|
|
138
|
+
|
|
139
|
+
**Source process.** In an uptrend (close > 200 SMA), buy panic dips (RSI(2) < 10); exit fast
|
|
140
|
+
(cross of the 5-period average / a few bars).
|
|
141
|
+
|
|
142
|
+
**Port — named substitute.** The catalog carries no RSI(2); `RSI7` is the fastest RSI series
|
|
143
|
+
and `lte 10` on it is a rarer, deeper panic — state that trade-off rather than hiding it.
|
|
144
|
+
`DIP_BUY` verdict UP = `dist_SMA200 gt 0` (ref a required `ABOVE_200`) AND `RSI7 lte 10`.
|
|
145
|
+
Rules: `rsi_oversold` 3 required `{"threshold": 25}` (RSI14 gate tuned toward the fast-dip
|
|
146
|
+
regime — read the signal schema first) · `ma_sma200_above` 2 required · `bollinger_lower_touch`
|
|
147
|
+
1. The source's fast exit is time, not price: `timeDecayEnabled: true`, grace 180, interval
|
|
148
|
+
60, tighten 15 → max 50, stale 20 — the studio's honest port of "if it hasn't bounced in a few
|
|
149
|
+
bars, leave". RR 1.2–1.5, stops 1.5–2.5 ATR (dips need room), break-even 0.8R.
|
|
150
|
+
|
|
151
|
+
## 7 · VWAP reversion — 15m/1h intraday
|
|
152
|
+
|
|
153
|
+
**Source process.** Fade stretches away from session VWAP back toward it; stand aside when the
|
|
154
|
+
stretch is trend, not noise.
|
|
155
|
+
|
|
156
|
+
**Port.** `VWAP distance` → `dist_VWAP` (signed %); the board-relative stretch is
|
|
157
|
+
`VWAP distance × rank far` → `dist_VWAP_rank_far lte 5` (top-5 most stretched either way).
|
|
158
|
+
`FADE_TO_VWAP` verdict from the sign: DOWN when `dist_VWAP gte 2` and `RSI14 gte 65`; UP when
|
|
159
|
+
`dist_VWAP lte -2` and `RSI14 lte 35`; veto trend days with a required
|
|
160
|
+
`NOT [ADX_state in ["trending","extreme"]]`. Rules: `rsi_overbought`/`rsi_oversold` 2 each,
|
|
161
|
+
`bollinger_upper_touch`/`lower_touch` 2, `cvd_bear_divergence`/`bull_divergence` 1. Scalp
|
|
162
|
+
geometry + aggressive time decay (VWAP reversion is a session trade — the anchor resets daily
|
|
163
|
+
at 00:00 UTC, which the column's own gloss states).
|
|
164
|
+
|
|
165
|
+
## 8 · Donchian / Turtle breakout — 4h
|
|
166
|
+
|
|
167
|
+
**Source process.** Buy the N-bar-high breakout, ride with a wide trailing stop, exit on the
|
|
168
|
+
opposite channel.
|
|
169
|
+
|
|
170
|
+
**Port.** The catalog's structural highs replace the fixed N-bar channel: `zone is
|
|
171
|
+
"breakout high"` (price beyond the tracked swing high) with `dist_swingHi gte 0` as the
|
|
172
|
+
numeric confirmation, `RVOL gte 1.5` for participation, and `sr_resistance_break` 3 required
|
|
173
|
+
in rules (its own params define the break). Turtle exits are the trend preset: stops 1.5–2.5
|
|
174
|
+
ATR, RR 2+, trail from 1R with giveback 50, no time decay. Mirror with `"breakdown low"` /
|
|
175
|
+
`dist_swingLo` / `sr_support_break` for shorts. **Daily variant on any anchor:** pin the same
|
|
176
|
+
structure at 1d — `zone_1d is "breakout high"`, `dist_swingHi_1d gte 0` (both validated) —
|
|
177
|
+
which is the studio's daily-breakout condition. Note the substitution: the level is the
|
|
178
|
+
tracked *daily swing* pivot, not the literal previous-day high — `distance` takes no `offset`
|
|
179
|
+
and clauses never compare two columns, so PDH/PDL as exact levels are not addressable (see
|
|
180
|
+
the not-expressible table).
|
|
181
|
+
|
|
182
|
+
## 9 · ICT / Smart Money Concepts: FVG + Order Blocks — 15m/1h
|
|
183
|
+
|
|
184
|
+
**Source process.** Establish higher-timeframe bias; wait for price to return into a fair
|
|
185
|
+
value gap or order block aligned with that bias; enter on the reaction, stop beyond the zone.
|
|
186
|
+
|
|
187
|
+
**Port — the zone half is native.** `STRUCT_ZONES` *is* the platform's FVG/order-block engine:
|
|
188
|
+
`zones_htf_support_type` (`bullish FVG` / `bullish order block`), `zones_htf_support_dist`
|
|
189
|
+
(signed %; support below price is negative — `between -1.5 0` = sitting on the zone),
|
|
190
|
+
`zones_htf_support_age_h gte 12` (a standing zone, not the last move's shadow). Bias =
|
|
191
|
+
`MAalign_htf is "bullish"` as a required building block. Carrier: bias + in-zone + a tape
|
|
192
|
+
quorum (`N_OF(2)`: `buyPres gte 0.55`, `RVOL gte 1.2`, `closeChg gt 0`). Rules:
|
|
193
|
+
`structure_fvg_approach` / `structure_ob_approach` 3/2 required (their `proximityPct` params
|
|
194
|
+
are the "in the zone" dial), `structure_zone_confluence` 2, `sr_at_support` 2. Stops: tight
|
|
195
|
+
band (0.5–1.5 ATR) — the zone's far edge is the invalidation, and the studio places stops by
|
|
196
|
+
ATR/structure natively. **Not expressible, say so:** liquidity sweeps, displacement legs,
|
|
197
|
+
killzone clocks, and Turtle-Soup false-break sequencing (ordering between events is outside
|
|
198
|
+
the grammar) — offer this zone-reaction port as the nearest expressible neighbor, labelled.
|
|
199
|
+
|
|
200
|
+
## Not expressible — and the honest nearest neighbor
|
|
201
|
+
|
|
202
|
+
| Script | Missing primitive | Nearest expressible neighbor |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| WaveTrend [LazyBear] | WT oscillator | `STOCH_K`/`STOCH_D` zones + crosses (`stoch_*` signals) |
|
|
205
|
+
| QQE / QQE MOD | smoothed-RSI ATR bands | `RSI7` trajectory + `rsi_*` signals with tuned thresholds |
|
|
206
|
+
| Hull Suite | Hull MA | `EMA5/13/20` ribbon (`MAalign`, EMA spread trend) |
|
|
207
|
+
| Ichimoku | cloud spans | `MAalign` + `SMA50_SMA200_spread` + `zones_htf_*` levels |
|
|
208
|
+
| Parabolic SAR | SAR dots | trailing stop from entry (`trailingTriggerR: 0`) |
|
|
209
|
+
| Keltner Channels | ATR envelope | `bbWidthPct` compression + `atrPct` band logic |
|
|
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` |
|
|
212
|
+
|
|
213
|
+
Never compile a "port" of these under the source's name without the substitution note — a
|
|
214
|
+
player who asked for WaveTrend and silently got Stochastic has no way to learn otherwise.
|