Skip to content

feat(trading-signals): add AtrTrail indicator (ATR_TRAIL) - #1236

Open
bennycode wants to merge 2 commits into
mainfrom
feat/atr-trail-indicator
Open

feat(trading-signals): add AtrTrail indicator (ATR_TRAIL)#1236
bennycode wants to merge 2 commits into
mainfrom
feat/atr-trail-indicator

Conversation

@bennycode

@bennycode bennycode commented Jul 27, 2026

Copy link
Copy Markdown
Owner

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-signals library.

What it does

  • Trail width = multiplier × ATR% (via NATR), so a volatile name automatically gets room to breathe while a calm one gets a tight stop — no hand-tuned percentage.
  • The peak ratchets upward only and the emitted stop never decreases — unlike ChandelierExit, whose highest high rolls out of a fixed window and whose stop can drop again.
  • Two modes:
    • 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

const trail = new AtrTrail({interval: 14, multiplier: 2, mode: AtrTrailMode.ROLLING});

const result = trail.add({high: 200, low: 190, close: 195});
// => {
//   peak: 200,      // ratcheting highest high
//   stop: 160,      // never-decreasing stop
//   trail: 160,     // raw candidate: peak * (1 - trailPct/100)
//   trailPct: 20,   // width from multiplier x ATR%
// }
// null during warm-up

trail and stop diverge in ROLLING mode when a volatility spike pushes the raw candidate below the committed stop — both are exposed so consumers see the ratchet at work.

Notes

  • Extends TechnicalIndicator<AtrTrailResult, HighLowClose<number>> (multi-value result, like ChandelierExit/SuperTrend), correct bidirectional replace() via a one-deep state snapshot, getRequiredInputs() delegated to the internal NATR.
  • 7 unit tests: exact-value sequences for both modes (including the trail/stop divergence), volatile-vs-calm width comparison, defaults, NotEnoughDataError, and bidirectional replace per mode. 100% coverage maintained.
  • feat(trading-strategies): add AtrTrailStrategy (ATR-sized trailing stop) #1131 (AtrTrailStrategy) can adopt this indicator later instead of carrying the trail logic inline.

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) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude will it only return the stop price and never the trailing up price?

@claude claude Bot Jul 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude finished @bennycode's task in 20s —— View job


Answering the question

  • Read AtrTrail.ts and 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, and stop = 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 FROZEN and ROLLING mode. In ROLLING mode the width (#widthPct) can change with live ATR, but the Math.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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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 AtrTrail with FROZEN (default) and ROLLING sizing modes based on NATR (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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants