feat(trading-signals): add AtrTrail indicator (ATR_TRAIL) - #1236
feat(trading-signals): add AtrTrail indicator (ATR_TRAIL)#1236bennycode wants to merge 2 commits into
Conversation
A trailing stop for long positions whose width is sized from the instrument's own volatility (multiplier x ATR%) instead of a hand-tuned percentage. Unlike the Chandelier Exit, the peak ratchets upward only and the emitted stop never decreases. FROZEN mode (default) sizes the width once at ATR warm-up; ROLLING mode keeps adapting it to the live ATR without loosening a stop already in place.
| return this.#natr.getRequiredInputs(); | ||
| } | ||
|
|
||
| update(candle: HighLowClose<number>, replace: boolean) { |
There was a problem hiding this comment.
@claude will it only return the stop price and never the trailing up price?
There was a problem hiding this comment.
Claude finished @bennycode's task in 20s —— View job
Answering the question
- Read
AtrTrail.tsand confirm the return behavior
Yes — AtrTrail only ever exposes the stop price, and that stop never decreases.
Looking at update() in AtrTrail.ts:
this.#peak = this.#peak === undefined ? candle.high : Math.max(this.#peak, candle.high);
const candidate = this.#peak * (1 - this.#widthPct / 100);
const previousStop = replace ? this.previousResult : this.result;
const stop = previousStop === undefined ? candidate : Math.max(previousStop, candidate);
return this.setResult(stop, replace);#peak(the trailing high) is purely internal state — it's used to compute the candidate stop but is never returned or exposed on the public API. There's no getter for it.- The only public result is
stop, andstop = Math.max(previousStop, candidate)— it's a running max, so the emitted value is monotonically non-decreasing. It can hold steady (when the candidate stop is below the previous one) or step up, but it can never step down. - This holds in both
FROZENandROLLINGmode. InROLLINGmode the width (#widthPct) can change with live ATR, but theMath.max(previousStop, candidate)guard still prevents the emitted stop itself from ever loosening.
So in short: add()/getResult() give you the ratcheting stop price only, never the peak/high-water-mark price, and that stop is guaranteed to never trail back down.
Switch from IndicatorSeries (single number) to TechnicalIndicator so the result exposes peak, stop, trail and trailPct. The raw trail candidate and the never-decreasing stop can diverge in ROLLING mode; both are now visible to consumers.
There was a problem hiding this comment.
🟢 Approval recommended
The implementation aligns with existing indicator patterns, is correctly exported/documented, and includes targeted tests for both modes and replace semantics.
Pull request overview
Adds a new volatility-sized trailing-stop indicator (AtrTrail, code ATR_TRAIL) to the trading-signals package, exposing it as a public trend indicator and documenting it among supported indicators.
Changes:
- Introduces
AtrTrailwithFROZEN(default) andROLLINGsizing modes based onNATR(ATR%). - Adds unit tests covering both modes, replace behavior, defaults, and warm-up error handling.
- Exports the indicator from the trend barrel and lists it in the package README.
File summaries
| File | Description |
|---|---|
| packages/trading-signals/src/trend/index.ts | Re-exports AtrTrail from the trend barrel for public consumption. |
| packages/trading-signals/src/trend/ATR_TRAIL/AtrTrail.ts | Implements the new AtrTrail indicator using NATR and ratcheting peak/stop logic. |
| packages/trading-signals/src/trend/ATR_TRAIL/AtrTrail.test.ts | Adds coverage for sizing behavior, stop ratchet guarantees, and replace() correctness. |
| packages/trading-signals/README.md | Documents ATR_TRAIL in the supported indicators list. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The feature at the heart of #1131 — a trailing stop whose width is sized from the instrument's own volatility — extracted as a standalone, publicly exported indicator in the
trading-signalslibrary.What it does
multiplier × ATR%(viaNATR), so a volatile name automatically gets room to breathe while a calm one gets a tight stop — no hand-tuned percentage.ChandelierExit, whose highest high rolls out of a fixed window and whose stop can drop again.FROZEN(default): the width is measured once when the ATR warms up and stays fixed — a plain percentage trail sized sensibly from recent history.ROLLING: the width keeps adapting to the live ATR, but a volatility spike only widens the trail for future peaks — it never loosens a stop already in place.Usage
trailandstopdiverge inROLLINGmode when a volatility spike pushes the raw candidate below the committed stop — both are exposed so consumers see the ratchet at work.Notes
TechnicalIndicator<AtrTrailResult, HighLowClose<number>>(multi-value result, likeChandelierExit/SuperTrend), correct bidirectionalreplace()via a one-deep state snapshot,getRequiredInputs()delegated to the internalNATR.trail/stopdivergence), volatile-vs-calm width comparison, defaults,NotEnoughDataError, and bidirectional replace per mode. 100% coverage maintained.AtrTrailStrategy) can adopt this indicator later instead of carrying the trail logic inline.