This is a prompt you paste into an AI you already use, together with your Pine Script® or MQL5 strategy. It teaches that AI this platform’s strategy language — the schema, the indicator families and their parameters, and the rules a strategy here has to obey — and tells it to say plainly what it could not express rather than approximate it. You bring its whole answer back, and we check it.
Why this exists
Most published Pine strategies, we cannot. Not because they are bad — because they use Pine's imperative core (:=, var, loops, functions that remember things) and this platform's strategy language deliberately has none of that. That restriction is what lets us promise a backtest here has no lookahead in it. So the honest answer for most scripts is “no”, and the useful part is the rest of this page: which lines, why, and what you could write instead.
Where a line has no rewrite that means exactly the same thing, we will not invent one — so this prompt is the one onward path we can honestly offer: a model you control attempts the translation, and we check its work.
What we prove about what comes back, and what we don’t
We check that a spec is valid, not that it is your strategy. When you bring one back we check that it compiles, that its indicators exist, that its conditions are well-formed and that it has a stop. If you give us your script too, we also check that every number in the spec came from your script and that nothing in your script was quietly dropped. What we cannot check is that it means the same thing — no automated check can — and we will not pretend to. A strategy built this way carries no badge: you are its author.
- With the spec alone — we check that it compiles, that its indicators exist, that its conditions are well-formed and that it has a stop. We have not seen your script, so we cannot tell you whether the spec is a faithful translation of it, including whether the AI invented a number.
- With your script as well — we also check that every value in the spec appears in your script, by name, and that no trade-affecting line of your script was quietly dropped. That removes the most common and most damaging failure. It does not remove all of them: an
andwhere your script saidorwould pass both checks. - What no check can do — tell you the spec meanswhat your script means. The last human check is you: we show the strategy back to you in sentences, and the button you press reads “This describes my strategy”.
A strategy built this way carries no badge and records you as its author. Checking a spec costs nothing and there is no limit on it; only running one is recorded as a trial.
The prompt
Version 1.d23394430b29 · generated from this platform’s own schema (docs/translator_prompt.md), never written by hand — so it cannot describe a schema we do not run.
# Translate your strategy into a Kurzharr spec
Copy everything below into the AI you use — ChatGPT, Claude, Gemini,
whichever — and paste your Pine Script® or MQL5 script after it. Bring the
AI's whole answer back to us and we will check it.
Two things first. **Your script goes to that AI, under their terms, not
ours.** We never send it anywhere. And what comes back is **your**
strategy, written by a model you chose. What we can check is that it is
valid, and — if you give us your script too — that every number in it came
from your script and that nothing in your script was quietly dropped. We
cannot check that it *means* the same thing, and we will not pretend to.
This page is generated from the platform's own code, so it cannot describe
a schema we do not run. Its version is `1.d23394430b29`.
---
## 1. Your job
You are converting a trading strategy into a declarative JSON spec for a
platform that will refuse anything it cannot verify. **Your job is to
convert what you can and to list what you cannot — not to approximate it.**
Never invent. No threshold, length, indicator, stop distance, target or
direction that is not in the source. If the source does not give something
the schema needs — a stop, most often — leave it out and say so in
`could_not_express`. Do not choose a sensible default. A number you chose
will be caught by the platform's provenance check, which looks for every
spec value in the script, and the trader will be told you invented it.
## 2. The platform's rules
These are rules, not schema notes, and each one has a reason. A strategy
that breaks one cannot be expressed here at all.
- **Decisions are taken at a bar's close and fill at the next bar's open.**
No intrabar logic, and no order that fills at the price that triggered
it.
- **No state.** No variable that remembers a value between bars, no
counter, no flag, no loop, no function you define. Every condition is a
function of bars that have already closed. Multi-bar relationships are
still expressible, through the operators in the grammar below — never
through a variable you keep.
- **No reading the strategy's own state.** Not position size, entry price,
equity, trade count or the last trade's result.
- **No future data.** Nothing may read a bar that has not closed, and a
higher-timeframe value is usable only once that bar has closed.
- **A stop is mandatory** and results are measured in **R**, multiples of
the initial stop distance. Sizing, capital, lot size, margin and account
currency are not part of a spec: `geometry.sizing` is `fixed_r`, a unit
of record, not a money calculation.
- **Costs are the platform's**, calibrated per instrument and selected by
`universe.cost_class`. Do not carry over commission, slippage or spread
settings from the script.
- **One position at a time.** No pyramiding, scale-in, hedging or
simultaneous long and short. A partial exit is `geometry.scale_out`,
never a second order.
## 3. The schema
A spec is one JSON object with five top-level keys: `strategy`,
`universe`, `signals`, `geometry`, `preregistration`. Section 6 is a
complete, valid example — follow its shape exactly. Throughout, a family
is written `family(required, optional=default)`; `name=?` is optional with
no simple default.
### strategy, universe, preregistration
`strategy` is `{"name": "...", "version": 1}` and `preregistration` is
`{"mode": "platform_default"}` — leave that as it is, do not invent
floors. `universe` is:
- `symbols` — a list; every symbol must be the same asset class.
- `timeframe` — one of `D1`, `H1`, `H4`, `M1`, `M15`, `M30`, `M5`, `S1`, `S10`, `S15`, `S30`, `S5`, `W1`.
- `date_range` — `{"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}`.
- `data_source` — a label, e.g. `platform_fx`, `platform_crypto_perp`.
- `cost_class` — one of `cme_index_futures`, `crypto_perp_taker_with_funding`, `retail_cfd_fx`, `us_equities_retail`;
it selects the real cost model, so it must match the asset class.
- `higher_timeframes` — optional list; an indicator computed on a higher
timeframe declares it here and sets its own `timeframe` to it.
Most scripts do not say what to trade or over what dates. When yours does
not, put those three fields in `provenance` with empty `source_lines` and
a note that the trader chose them. Do not claim they came from the script.
### signals.indicators
A list of `{"name": ..., "family": ..., "params": {...}}`. `name` is
yours to choose and is how conditions refer to it. `family` must be one in
section 4, and `params` may only use that family's own parameter names —
an unrecognised one is refused, not ignored.
### signals.entries / signals.exits
Keyed by `long` and `short` only. Either shape is accepted:
`{"condition": "<expr>"}` enters on any bar the expression is true;
`{"setup": "<expr>", "trigger": "<expr>", "window": N, "invalidation":
"<expr>"}` arms on the setup and enters only if the trigger fires within
`N` bars (`window: 0` means the same bar), with the optional invalidation
cancelling an armed setup. Use the second for "wait for X, then buy when
Y". `signals.exits` is optional and closes an open position on a signal.
**The condition grammar, in full.** Anything not on this list does not
exist:
- values: numbers, the bar fields `open`, `high`, `low`, `close`,
`volume`, an indicator by `name`, a multi-output indicator's field as
`name.field`, and `shift(X, N)` for X as of N bars ago (N ≥ 0 — there is
no negative shift)
- arithmetic: `+ - * /`, unary minus, parentheses
- comparisons: `> < >= <= == !=`
- booleans: `and`, `or`, `not`, and the literals `true` / `false`
- crosses: `A crosses_above B`, `A crosses_below B`
- over a window: `<bool> for at least N bars`, `<bool> within last N bars`,
`rolling_max(X, N)`, `rolling_min(X, N)`,
`count_true_in_window(<bool>, N)`, `percentile_rank(X, N)` (the share
of the last N bars at or below this bar's X: "top 30% of its yearly
range" is `percentile_rank(X, 252) > 0.7`)
- since an event — how a script's `var` / `:=` state is written when it is
a remembered value, a running high or low, a total or a count. Each
restarts at every bar its `<bool>` is true and never fires before the
first: `hold(X, <bool>)` (X as of the last such bar, Pine's
`ta.valuewhen`; a third `<bool>` forgets it again),
`max_since(X, <bool>)`, `min_since(X, <bool>)`, `sum_since(X, <bool>)`,
`count_since(<bool>, <bool>)`. State that reads the open position is NOT
this: it goes in `geometry` or in `could_not_express`
- timing: `bar_since(<bool>)` as a number of bars, or
`bar_since(<bool>) between M and N`; `sequence(<bool>, <bool>, N)` (the
second became true within N bars after the first); `latch(<bool>,
<bool>)` (true from the first firing until the second)
### geometry
`stop` and `sizing` are required. Each is
`{"family": ..., "params": {...}}`.
**`stop` families**
- `atr_multiple(atr_source, multiplier)`
- `fixed_distance(distance_price)`
- `snapshot_at_entry(series_source)`
- `structural_from_series(buffer_atr=?, buffer_atr_source=?, min_distance_atr=?, min_distance_atr_source=?, series_source=?, series_source_long=?, series_source_short=?)`
**`target` families** (optional, but only omittable when a `timeout`, a
trailing stop, or a `signals.exits` rule for every side that enters can
close a winning trade)
- `atr_multiple(atr_source, multiplier)`
- `fixed_distance(distance_price)`
- `r_multiple(r)`
- `snapshot_at_entry(series_source)`
- `structural_from_series(buffer_atr=?, buffer_atr_source=?, fallback_r=?, min_reward_atr=?, min_rr=?, on_insignificant='discard', reward_atr_source=?, search_max_pivots=0, series_source=?, series_source_long=?, series_source_short=?)`
**`sizing` families**
- `fixed_r(r=1.0)`
Any `*_source` parameter names one of your declared indicators, or one of
its fields. `structural_from_series` takes either `series_source` or both
`series_source_long` and `series_source_short`.
**`timeout`** — `{"max_bars_in_trade": N}`.
**`stop_management`** — optional, `{"breakeven": ..., "trail": ...}`.
`breakeven` is `{"trigger_r": R, "offset_r": R}` (move the stop to
`offset_r` from entry once the trade is `trigger_r` in profit) or
`{"after_part": N, "offset_r": R}` (move it once scale-out part N fills).
`trail` is `{"family": ..., "params": {...}}`, optionally with its own
`after_part`:
- `atr(atr_source, multiplier, activate_r=?)`
- `r_steps(step_r)`
- `series(activate_r=?, buffer_atr=?, buffer_atr_source=?, series_source=?, series_source_long=?, series_source_short=?)`
**`scale_out`** — partial take-profits, and the only way to express one:
`{"parts": [{"percent": P, "target": {"family": ..., "params": {...}}}]}`.
At most 3 parts, nearest level first, each `percent` a
whole percent of the **original** position, totalling at most
99% — something must stay open for the target, a trail,
an exit signal or the timeout. A part's `target.family` is one of
`atr_multiple`, `fixed_distance`, `r_multiple`, `snapshot_at_entry`, `structural_from_series`.
### What is simply not expressible
Put each of these in `could_not_express` rather than working around it:
- assignment and reassignment (`:=`), `var` / `varip` state, loops,
`switch`, `if`/`else` blocks that assign, functions you define, arrays,
matrices and maps
- anything reading the strategy's own state or trade history
- inputs that change while the strategy runs
- orders that fill inside the bar that decided them:
`process_orders_on_close`, stop/limit entries resting intrabar, anything
depending on tick sequence within a bar
- more than one open position, hedging, pyramiding, scale-in
- alerts, drawing, plotting, labels — these do not affect trades, so say
that in the `why` and do not treat them as a reason to refuse
- any indicator not in section 4. There is no way to define one inline,
and no `custom` family in a translation.
## 4. The indicator families
These are all of them. If a family is not here, it does not exist; if a
parameter is not listed for a family, that family does not read it and the
spec will be refused. A trailing `→ .a .b` means the indicator has several
outputs and a condition must name one (`macd.line`, not `macd`).
`adx(length=14)` → .adx .plus_di .minus_di
`aroon(length=25)` → .up .down .oscillator
`atr(length=14)`
`atr_expansion(atr_source, lookback=20, threshold=1.5)`
`bars_since_last_pivot_high(pivot_source)`
`bars_since_last_pivot_low(pivot_source)`
`bars_since_session_open(end_hour=None, name=None, start_hour=None, tz=None)`
`bars_until_session_close(bar_interval_minutes, end_hour=None, name=None, start_hour=None, tz=None)`
`bollinger_bands(length=20, num_std=2.0, source='close')` → .upper .middle .lower
`bollinger_squeeze(bb_source, width_threshold, lookback=120, percentile=20.0)`
`bos_event(structure_source)`
`breaker_block(ob_source)` → .bullish_event .bullish_upper .bullish_lower .bearish_event .bearish_upper .bearish_lower
`chandelier_exit(atr_length=22, length=22, multiplier=3.0)` → .long .short
`choch_event(structure_source, side='any')`
`cmf(length=20)`
`cointegration_spread(symbol_a, symbol_b, timeframe, window)`
`confirmed_pivots(n=5)` → .pivot_high .pivot_low .last_high_price .last_high_bar .last_low_price .last_low_bar .bars_since_last_high .bars_since_last_low
`corwin_schultz_spread()`
`cross_asset_correlation(symbol_a, symbol_b, timeframe, window)`
`day_of_week()`
`ema(length, source='close')`
`engulfing(direction='bullish')`
`equal_highs_pool(structure_source)`
`equal_lows_pool(structure_source)`
`fair_value_gap(atr_length=14, gk_multiplier=1.5, gk_window=20, min_conviction=0.65, min_expansion=1.3, min_gap_atr=0.25, require_active_session=False, require_displacement=False, require_gk_spike=False)` → .bullish_event .bullish_upper .bullish_lower .bearish_event .bearish_upper .bearish_lower
`formula(expr=None)`
`funding_rate(symbol)`
`higher_highs(pivot_source)`
`higher_lows(pivot_source)`
`hma(length, source='close')`
`hmm_regime(vol_source, min_train_bars=100, n_states=2, refit_every=250, train_window=2000)`
`ichimoku(displacement=26, kijun_length=26, senkou_b_length=52, tenkan_length=9)` → .tenkan .kijun .senkou_a .senkou_b .chikou
`inside_bar()`
`is_weekend()`
`keltner_channels(atr_length=10, length=20, multiplier=2.0)` → .upper .middle .lower
`lower_highs(pivot_source)`
`lower_lows(pivot_source)`
`macd(fast=12, signal=9, slow=26, source='close')` → .line .signal .hist
`macd_divergence(direction, macd_source, pivot_source, lookback=100)`
`market_structure_state(atr_length=14, tolerance_atr=0.1)` → .state .bos .bos_direction .bos_ref_bar .choch .choch_direction .sweep_high .sweep_low .last_swing_high .last_swing_low .eqh_level .eql_level .eqh_touches .eql_touches
`mfi(length=14)`
`obv()`
`open_interest(symbol, period='1d')`
`order_block(structure_source, max_lookback=20)` → .bullish_event .bullish_upper .bullish_lower .bearish_event .bearish_upper .bearish_lower
`outside_bar()`
`parabolic_sar(af_max=0.2, af_start=0.02, af_step=0.02)` → .sar .trend
`pin_bar(direction='bullish', min_tail_fraction=0.6666666666666666, tail_ratio=2.0)`
`relative_equal_highs(pivot_source, atr_length=14, atr_multiplier=0.1, confirm_hold_bars=10, dedup_window=50, max_active_zones=20, max_bars_between=300, max_mitigated_zones=20, min_bars_between_touches=0, min_touches=2, mitigation_mode='close_through', search_depth=200)`
`relative_equal_lows(pivot_source, atr_length=14, atr_multiplier=0.1, confirm_hold_bars=10, dedup_window=50, max_active_zones=20, max_bars_between=300, max_mitigated_zones=20, min_bars_between_touches=0, min_touches=2, mitigation_mode='close_through', search_depth=200)`
`roc(length=12, source='close')`
`rsi(length=14, source='close')`
`rsi_divergence(direction, pivot_source, rsi_source, lookback=100)`
`session(end_hour=None, name=None, start_hour=None, tz=None)`
`sma(length, source='close')`
`stdev(length=20, source='close')`
`stochastic(d_length=3, k_length=14)` → .k .d
`supertrend(length=10, multiplier=3.0)` → .supertrend .trend
`sweep_event(structure_source, side='high')`
`swing_high(pivot_source)`
`swing_low(pivot_source)`
`three_line_strike(direction='bullish')`
`variance_ratio(k=5, length=100)`
`volume_profile(hvn_min_share=0.5, lookback=150, rows=24, value_area_pct=70.0)` → .poc .vah .val .poc_low .poc_high .hvn_above .hvn_below
`volume_ratio(length=20)`
`williams_r(length=14)`
`wma(length, source='close')`
`yang_zhang_volatility(length=20)`
Families reading `volume` (`obv`, `cmf`, `mfi`, `volume_ratio`,
`volume_profile`) only work where the platform has volume — crypto,
equities and futures, not FX/CFD. The session families take either a
`name` or a `start_hour` and `end_hour` pair, optionally with an IANA
`tz`. `cointegration_spread`, `cross_asset_correlation`, `funding_rate`
and `open_interest` reach live data sources and are not part of an
ordinary single-symbol translation.
## 5. What to output
**Output only JSON.** No prose, no explanation, no commentary around the
fence. One object with exactly these four keys:
- `prompt_version` — copy `1.d23394430b29` verbatim.
- `spec` — the StrategySpec above.
- `provenance` — one entry **per spec value**, not per block. Every
numeric literal, every indicator parameter, every threshold names the
source lines it came from: `{"spec_path": ..., "source_lines": [...],
"literal": ...}`. A value with `"source_lines": []` must carry a
`"note"` saying where it came from instead.
- `could_not_express` — everything you did not express, with its source
lines: `{"source_lines": [...], "construct": ..., "why": ...}`.
Three rules on top of that:
- **Never invent** — repeated because it is the one that matters. Not a
threshold, not a length, not a stop, not a direction.
- **Never silently drop.** If it is in the script and not in the spec, it
is in `could_not_express`. An empty `could_not_express` on a script that
uses `var` or `:=` is itself treated as a warning sign.
- **If you cannot produce a valid spec at all, output `could_not_express`
alone and an empty `spec`.** A partial honest answer is worth more than a
complete invented one.
## 6. A worked example
This source script is ours, written for this page. Line numbers are shown
so the provenance map can be read against it.
```
1 //@version=5
2 strategy("EMA cross with an ATR stop", overlay=true)
3 fastLen = input.int(20, "Fast EMA")
4 slowLen = input.int(50, "Slow EMA")
5 atrLen = input.int(14, "ATR length")
6 atrMult = input.float(2.0, "ATR stop multiple")
7 fast = ta.ema(close, fastLen)
8 slow = ta.ema(close, slowLen)
9 atrValue = ta.atr(atrLen)
10 var int winStreak = 0
11 if strategy.wintrades > winStreak
12 winStreak := winStreak + 1
13 if ta.crossover(fast, slow)
14 strategy.entry("L", strategy.long)
15 strategy.exit("X", "L", stop = close - atrMult * atrValue, limit = close + 2 * atrMult * atrValue)
16 plot(fast, color=color.blue)
```
A correct answer:
```json
{
"prompt_version": "1.d23394430b29",
"spec": {
"strategy": {"name": "ema-cross-with-an-atr-stop", "version": 1},
"universe": {
"symbols": ["EURUSD"],
"timeframe": "H1",
"date_range": {"start": "2015-01-01", "end": "2024-12-31"},
"data_source": "platform_fx",
"cost_class": "retail_cfd_fx"
},
"signals": {
"indicators": [
{
"name": "ema_fast",
"family": "ema",
"params": {"length": 20, "source": "close"}
},
{
"name": "ema_slow",
"family": "ema",
"params": {"length": 50, "source": "close"}
},
{"name": "atr14", "family": "atr", "params": {"length": 14}}
],
"entries": {
"long": {"condition": "ema_fast crosses_above ema_slow"}
}
},
"geometry": {
"stop": {
"family": "atr_multiple",
"params": {"atr_source": "atr14", "multiplier": 2.0}
},
"target": {"family": "r_multiple", "params": {"r": 2.0}},
"sizing": {"family": "fixed_r", "params": {"r": 1.0}}
},
"preregistration": {"mode": "platform_default"}
},
"provenance": [
{
"spec_path": "universe.symbols",
"source_lines": [],
"note": "not in the source; the trader chose it"
},
{
"spec_path": "universe.timeframe",
"source_lines": [],
"note": "not in the source; the trader chose it"
},
{
"spec_path": "signals.indicators[0].params.length",
"source_lines": [3, 7],
"literal": 20
},
{
"spec_path": "signals.indicators[1].params.length",
"source_lines": [4, 8],
"literal": 50
},
{
"spec_path": "signals.indicators[2].params.length",
"source_lines": [5, 9],
"literal": 14
},
{"spec_path": "signals.entries.long.condition", "source_lines": [13]},
{
"spec_path": "geometry.stop.params.multiplier",
"source_lines": [6, 15],
"literal": 2.0
},
{
"spec_path": "geometry.target.params.r",
"source_lines": [15],
"literal": 2
},
{
"spec_path": "geometry.sizing.params.r",
"source_lines": [],
"note": "not in the source; 1R is the platform unit"
}
],
"could_not_express": [
{
"source_lines": [10, 11, 12],
"construct": "var winStreak / winStreak := winStreak + 1",
"why": "a value remembered between bars, and it reads the strategy's own trade record; the schema has neither"
},
{
"source_lines": [16],
"construct": "plot(fast)",
"why": "drawing only; it does not affect trades"
}
]
}
```
Note what happened there. The `2.0` became the stop multiple because line
15 uses it as a stop, and the `2` on the same line became a 2R target
because the script's target is twice its stop distance — which is what R
means here, so nothing was invented. The symbol, the timeframe and the
dates are not in the script, so they are declared as the trader's choices
rather than claimed from it. And the counter on lines 10–12 changes not one
trade in this script, yet it is still listed: what is not expressed is
always said out loud.
The worked example, from the prompt itself
## 6. A worked example
This source script is ours, written for this page. Line numbers are shown
so the provenance map can be read against it.
```
1 //@version=5
2 strategy("EMA cross with an ATR stop", overlay=true)
3 fastLen = input.int(20, "Fast EMA")
4 slowLen = input.int(50, "Slow EMA")
5 atrLen = input.int(14, "ATR length")
6 atrMult = input.float(2.0, "ATR stop multiple")
7 fast = ta.ema(close, fastLen)
8 slow = ta.ema(close, slowLen)
9 atrValue = ta.atr(atrLen)
10 var int winStreak = 0
11 if strategy.wintrades > winStreak
12 winStreak := winStreak + 1
13 if ta.crossover(fast, slow)
14 strategy.entry("L", strategy.long)
15 strategy.exit("X", "L", stop = close - atrMult * atrValue, limit = close + 2 * atrMult * atrValue)
16 plot(fast, color=color.blue)
```
A correct answer:
```json
{
"prompt_version": "1.d23394430b29",
"spec": {
"strategy": {"name": "ema-cross-with-an-atr-stop", "version": 1},
"universe": {
"symbols": ["EURUSD"],
"timeframe": "H1",
"date_range": {"start": "2015-01-01", "end": "2024-12-31"},
"data_source": "platform_fx",
"cost_class": "retail_cfd_fx"
},
"signals": {
"indicators": [
{
"name": "ema_fast",
"family": "ema",
"params": {"length": 20, "source": "close"}
},
{
"name": "ema_slow",
"family": "ema",
"params": {"length": 50, "source": "close"}
},
{"name": "atr14", "family": "atr", "params": {"length": 14}}
],
"entries": {
"long": {"condition": "ema_fast crosses_above ema_slow"}
}
},
"geometry": {
"stop": {
"family": "atr_multiple",
"params": {"atr_source": "atr14", "multiplier": 2.0}
},
"target": {"family": "r_multiple", "params": {"r": 2.0}},
"sizing": {"family": "fixed_r", "params": {"r": 1.0}}
},
"preregistration": {"mode": "platform_default"}
},
"provenance": [
{
"spec_path": "universe.symbols",
"source_lines": [],
"note": "not in the source; the trader chose it"
},
{
"spec_path": "universe.timeframe",
"source_lines": [],
"note": "not in the source; the trader chose it"
},
{
"spec_path": "signals.indicators[0].params.length",
"source_lines": [3, 7],
"literal": 20
},
{
"spec_path": "signals.indicators[1].params.length",
"source_lines": [4, 8],
"literal": 50
},
{
"spec_path": "signals.indicators[2].params.length",
"source_lines": [5, 9],
"literal": 14
},
{"spec_path": "signals.entries.long.condition", "source_lines": [13]},
{
"spec_path": "geometry.stop.params.multiplier",
"source_lines": [6, 15],
"literal": 2.0
},
{
"spec_path": "geometry.target.params.r",
"source_lines": [15],
"literal": 2
},
{
"spec_path": "geometry.sizing.params.r",
"source_lines": [],
"note": "not in the source; 1R is the platform unit"
}
],
"could_not_express": [
{
"source_lines": [10, 11, 12],
"construct": "var winStreak / winStreak := winStreak + 1",
"why": "a value remembered between bars, and it reads the strategy's own trade record; the schema has neither"
},
{
"source_lines": [16],
"construct": "plot(fast)",
"why": "drawing only; it does not affect trades"
}
]
}
```
Note what happened there. The `2.0` became the stop multiple because line
15 uses it as a stop, and the `2` on the same line became a 2R target
because the script's target is twice its stop distance — which is what R
means here, so nothing was invented. The symbol, the timeframe and the
dates are not in the script, so they are declared as the trader's choices
rather than claimed from it. And the counter on lines 10–12 changes not one
trade in this script, yet it is still listed: what is not expressed is
always said out loud.What a report looks like
Before any of that, it is usually worth having us read the script itself. Below is a real report on a short S&P 500 mean-reversion script — an example, not a live run, rendered from a fixture we keep in the test suite so it cannot drift from what the tool actually produces. It is the same report a signed-in trader gets, in the same components.
We cannot validate this strategy, and here is the shortest version of why: 2 lines use strategy.position_size, which we cannot hold without giving up the guarantee this platform exists for.
Every line, by class
- Unsafe
- The line is legal Pine, but its meaning here would not be its meaning there.
- Forbidden
- Translating this would break the thing that makes a backtest here worth trusting.
- Not yet
- Nothing in principle stops this; we have not built it.
- Cosmetic
- No effect on which trades happen.
- Translatable
- This line means the same thing here.
22 statements over 22 lines. One line can carry several constructs, so these count findings, not lines — and there is no percentage here on purpose: 80% translated is not 80% of a strategy.
About the script as a whole
These are true of the strategy, not of any one line — so no line below is marked for them.
- Platform
a script with no stop
Your script has no stop — it closes on a signal or a reversal and holds otherwise — and every result here is measured against one.
This is a legitimate design and TradingView will backtest it; 56% of the scripts we measured are built this way. This platform will not, and the reason is arithmetic rather than taste: results here are in R, multiples of what the trade risked, and without a stop there is no denominator. Measuring that way is item P5 in the list of platform-owned differences at 5.4, and it is also how the validation stack compares your strategy against a null. So bringing this strategy here means adding a stop — yours, not your script's — and the report and the badge both say the stop was your addition.
What that means for you. Your numbers here will differ from your TradingView numbers because of the stop, and that difference is information: a strategy whose result depends on having no stop is a strategy with an unbounded worst trade. That is the single most useful thing a backtest can tell you about a reversal system.
Written up at 5.3 T28, 2.5
Where we saw it: line 18.
- Platform
our price history and our bar boundaries
Your strategy runs on this platform's own price feed, and its daily and four-hour bars start where we start them, not where TradingView does.
TradingView's daily and four-hour bars follow the symbol's exchange session. Ours are anchored to the EET trading day, about 17:00 New York, with a disclosed one-to-two week a year drift around daylight saving. On FX, TradingView shows a broker's quotes and we hold mid prices with bid/ask fills on top. So the bars are not the same bars and the ticks inside them are not the same ticks — item P6 at 5.4. This is which series you are testing on, not a question about translating your script.
What that means for you. Your numbers here will not match your TradingView numbers, and they are not meant to. What is being checked is whether the logic you wrote survives an honest test on one consistent set of bars. A strategy whose result moves a lot when the daily boundary moves a few hours is telling you something about itself.
Written up at 5.4 P6, 5.3 T13
Run this on your own script
Create a free account to run this on your own script. It stays free and we don't keep the script.
We don't keep your script. Your code is read in memory and thrown away — never written to disk, never logged, and never sent to any AI, not ours and not anyone's. What we keep is a count of what the reader found, and a one-way fingerprint of the file so the same script read twice isn't counted twice. A fingerprint can't be turned back into your code. A report runs no backtest, so it never uses one of your monthly audits either — see tiers & limits.
Read my script — free on every plan, and you can come back as often as you like.