Skip to content

feat(trading-strategies): assessAtrMultiples — sweep to choose the ATR multiple#1132

Open
bennycode wants to merge 1 commit into
feat/atr-trail-strategyfrom
feat/atr-multiple-sweep
Open

feat(trading-strategies): assessAtrMultiples — sweep to choose the ATR multiple#1132
bennycode wants to merge 1 commit into
feat/atr-trail-strategyfrom
feat/atr-multiple-sweep

Conversation

@bennycode

Copy link
Copy Markdown
Owner

Stacked on #1131 (it imports AtrTrailStrategy). Retargets to main once #1131 merges.

Problem

We had helpers to classify a multiple (classifyAtrMultiple → whippy/balanced/loose) and convert units (atrMultipleToPercent), but nothing to choose the multiple from an instrument's actual behavior.

What this adds

assessAtrMultiples({history, candles, multiples?}) sweeps a range of ATR multiples and, for each, warms AtrTrailStrategy from history, trails it over candles, and reports:

  • band — static whippy/balanced/loose
  • outcomeempirical: held / protective (exited then kept falling) / whipsawed (exited then recovered)
  • trailDownPct, exitPrice/exitDate, finalValue, buyHoldValue, edgeVsHoldPct

So you pick the width from evidence instead of a guess.

On the real STX window (5 shares, buy & hold ≈ 4628)

1.5x  whippy    trail 6.9%   whipsawed  exit@780 05-21  edge -15.8%
2x    balanced  trail 9.2%   whipsawed  exit@761 05-20  edge -17.9%
2.5x  balanced  trail 11.4%  whipsawed  exit@742 05-19  edge -20.0%
3x    balanced  trail 13.7%  held                       edge  0.0%
3.5x  loose     trail 16.0%  held                       edge  0.0%
4x    loose     trail 18.3%  held                       edge  0.0%

The knee is between 2.5× and 3×. Note the empirical outcome corrects the static band: 2× and 2.5× classify as "balanced" but actually whipsawed on this instrument — which is the whole point of measuring instead of trusting a generic threshold.

Scope (MVP)

Outcome + edge vs buy & hold. Deliberately does not pull the richer suitability metrics (Kaufman efficiency ratio, max drawdown, full verdict) from the old assessTrailSuitability — easy to add later if wanted.

4 tests covering the sweep shape, the 2×-whipsaw / 3×-held outcomes, the edge comparison, and the empty-input guard.

Sweeps a range of ATR multiples over an instrument's candles and reports how each
would have behaved (held / protective / whipsawed) plus the static whippy/balanced/
loose band, the resulting trail %, exit, final value, and edge vs buy & hold — so
the trail width is chosen from evidence instead of guessed. On the STX window the
knee sits between 2.5x (whipsawed) and 3x (held), and the empirical outcome
corrects the static band (2-2.5x classify as 'balanced' but whipsaw here).

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.

Pull request overview

Adds an evidence-based helper to choose an ATR trailing-stop multiple by backtesting a sweep of multiples against a window of candles, reporting both the static “band” (whippy/balanced/loose) and the empirical outcome (held/protective/whipsawed) plus edge vs buy & hold.

Changes:

  • Introduces assessAtrMultiples() to sweep ATR multiples, warm AtrTrailStrategy from history, backtest across candles, and report outcome/edge metrics.
  • Adds Vitest coverage exercising sweep shape, a real STX regression outcome (2× whipsawed vs 3× held), value comparison, and empty-input guard.
  • Exports the new API and types from the package entrypoint.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/trading-strategies/src/strategy-atr-trail/assessAtrMultiples.ts New sweep/backtest utility producing per-multiple outcome + performance metrics.
packages/trading-strategies/src/strategy-atr-trail/assessAtrMultiples.test.ts Unit tests validating the sweep report and STX regression outcomes.
packages/trading-strategies/src/index.ts Publicly exports assessAtrMultiples and its related types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +93 to +96
let outcome: AtrMultipleOutcome = 'held';
if (exitPrice !== null) {
outcome = lastClose > exitPrice ? 'whipsawed' : 'protective';
}
Comment on lines +70 to +72
const lastClose = parseFloat(candles[candles.length - 1].close);
const buyHoldValue = parseFloat(baseBalance) * lastClose;

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