From b4388c959c14e365e5c147bd6dc74f399a712b6d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Jun 2026 20:22:46 +0000 Subject: [PATCH] feat: Chandelier-Trailing-Stop --- src/server/strategy/chandelier.test.ts | 22 ++++++++++++++++++++++ src/server/strategy/chandelier.ts | 11 +++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/server/strategy/chandelier.test.ts create mode 100644 src/server/strategy/chandelier.ts diff --git a/src/server/strategy/chandelier.test.ts b/src/server/strategy/chandelier.test.ts new file mode 100644 index 0000000..c35fb26 --- /dev/null +++ b/src/server/strategy/chandelier.test.ts @@ -0,0 +1,22 @@ +import { expect, test } from 'bun:test'; +import { updateChandelier } from './chandelier'; + +test('Stop steigt mit neuem Hoch', () => { + // hh 100 → 110, ATR 2, Mult 3 → Stop 110−6 = 104 + const r = updateChandelier({ highestHigh: 100, stop: 94 }, 110, 2, 3); + expect(r.highestHigh).toBe(110); + expect(r.stop).toBe(104); +}); + +test('Stop fällt NIE — auch wenn ATR explodiert', () => { + // hh bleibt 110, ATR springt auf 10 → Kandidat 80, aber Stop bleibt 104 + const r = updateChandelier({ highestHigh: 110, stop: 104 }, 105, 10, 3); + expect(r.highestHigh).toBe(110); + expect(r.stop).toBe(104); +}); + +test('NaN-ATR lässt Stop unverändert', () => { + const r = updateChandelier({ highestHigh: 110, stop: 104 }, 120, NaN, 3); + expect(r.highestHigh).toBe(120); + expect(r.stop).toBe(104); +}); diff --git a/src/server/strategy/chandelier.ts b/src/server/strategy/chandelier.ts new file mode 100644 index 0000000..4174a6a --- /dev/null +++ b/src/server/strategy/chandelier.ts @@ -0,0 +1,11 @@ +export interface TrailState { + highestHigh: number; + stop: number; +} + +/** Chandelier-Stop: hh − mult×ATR, wandert nur aufwärts. Aufruf pro abgeschlossener 4h-Candle. */ +export function updateChandelier(state: TrailState, barHigh: number, atrValue: number, mult: number): TrailState { + const highestHigh = Math.max(state.highestHigh, barHigh); + const candidate = Number.isNaN(atrValue) ? -Infinity : highestHigh - mult * atrValue; + return { highestHigh, stop: Math.max(state.stop, candidate) }; +}