Save this Python 3 program as a file and run it. It reconstructs true range, Wilder ATR, both Supertrend bands, direction, rolling highs and the ratchet from the stated seeds. It then runs a separate order ledger with prior-bar stops, next-open market exits, tick rounding, slippage and commission. Assertions check every reported trade, the ATR gap, the retreating raw line and the two state flips.
No market download and no Pine execution. The JSON output is an independent arithmetic and timing check of this seeded fixture; it is not a TradingView execution test.python
"""Independent seeded recurrence and one-trade ledger; synthetic prices only."""
import json
import math
from pathlib import Path
BARS = [
(100, 102, 100, 101), (101, 104, 101, 103),
(103, 106, 103, 105), (105, 106, 102, 104),
(104, 105, 102, 102), (99, 101, 98, 100),
(100, 105, 100, 104), (104, 105, 100, 101),
(101, 106, 101, 105), (105, 106, 100, 101),
]
# State at bar 0 close. These seeds replace chart-history warm-up.
ATR, UPPER, LOWER, PREV_CLOSE, DIRECTION = 2.0, 103.0, 97.0, 100.0, -1
N, K, TICK, FEE = 3, 1.0, 0.25, 0.10
highs = [100.0, 101.0]
ratchet = 99.0 # bar-0 rolling HH 101 minus ATR 2
states = []
for bar, (o, h, low, c) in enumerate(BARS, 1):
assert low <= min(o, c) <= max(o, c) <= h
tr = max(h - low, abs(h - PREV_CLOSE), abs(low - PREV_CLOSE))
ATR = ((N - 1) * ATR + tr) / N
basic_u, basic_l = (h + low) / 2 + K * ATR, (h + low) / 2 - K * ATR
UPPER = basic_u if basic_u < UPPER or PREV_CLOSE > UPPER else UPPER
LOWER = basic_l if basic_l > LOWER or PREV_CLOSE < LOWER else LOWER
previous_direction = DIRECTION
DIRECTION = (-1 if c > UPPER else 1) if DIRECTION == 1 else (1 if c < LOWER else -1)
highs.append(h)
raw = max(highs[-N:]) - K * ATR
ratchet = max(ratchet, raw)
states.append(dict(bar=bar, o=o, h=h, l=low, c=c, tr=tr, atr=ATR,
upper=UPPER, lower=LOWER, direction=DIRECTION,
raw=raw, ratchet=ratchet, flip=DIRECTION != previous_direction))
PREV_CLOSE = c
# Each alternative owns a separate one-unit long, queued at bar 0 close.
entry = BARS[0][0] + TICK
trades = []
for method in ('Supertrend', 'Raw Chandelier', 'Position ratchet'):
for execution in ('close', 'stop'):
prior = 97.0 if method == 'Supertrend' else 99.0
pending_close = False
for s in states:
threshold = s['lower'] if method == 'Supertrend' else s['raw'] if method == 'Raw Chandelier' else s['ratchet']
fill, reason = None, None
if pending_close:
fill, reason = s['o'] - TICK, 'next-open market'
elif execution == 'stop':
stop = math.floor(prior / TICK) * TICK
if s['o'] <= stop:
fill, reason = s['o'] - TICK, 'gap through prior stop'
elif s['l'] <= stop:
fill, reason = stop - TICK, 'touch of prior stop'
if fill is not None:
trades.append(dict(method=method, execution=execution, entry_bar=1,
entry=entry, exit_bar=s['bar'], exit=fill, reason=reason,
gross=fill-entry, fees=2*FEE, net=fill-entry-2*FEE))
break
pending_close = execution == 'close' and (s['direction'] > 0 if method == 'Supertrend' else s['c'] < threshold)
prior = threshold
else:
raise AssertionError('Fixture must close all alternatives')
# Boundary checks use executable prior stops, strict close tests, and gap TR.
assert states[3]['raw'] < states[2]['raw']
assert states[3]['ratchet'] == states[2]['ratchet']
assert states[5]['tr'] == 4
assert [s['bar'] for s in states if s['flip']] == [6, 7]
assert not (100 < 100) # equality does not trigger a close breach
assert 100 <= 100 # equality touches an already-resting stop
expected = {('Supertrend','close'):(7,99.75,-0.70),
('Supertrend','stop'):(6,98.75,-1.70),
('Raw Chandelier','close'):(6,98.75,-1.70),
('Position ratchet','close'):(6,98.75,-1.70),
('Raw Chandelier','stop'):(4,103.0,2.55),
('Position ratchet','stop'):(4,103.0,2.55)}
for t in trades:
b, price, net = expected[t['method'], t['execution']]
assert t['exit_bar'] == b and math.isclose(t['exit'], price) and math.isclose(t['net'], net)
result = dict(assumptions=dict(length=N, multiplier=K, tick=TICK, commission_per_fill=FEE,
quantity=1, point_value=1, entry_order='bar 0 close'),
states=states, trades=trades)
Path(__file__).with_suffix('.json').write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(result, indent=2))