feat: No-Stop-Grid-Variante (--no-stop, --pair) — erstes Edge-Signal auf XRP

XRP-Datenanalyse: nie enge Ranges, breite Bänder → No-Stop-Design.
OOS-PF 2.0-2.74, Volldurchlauf +49% bei 10% MaxDD. Gate formal 
(Worst-Window + Ratio), Tail-Risiko dokumentiert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 07:17:33 +00:00
parent 3d16b76f23
commit f754b91acd
3 changed files with 56 additions and 5 deletions

View File

@@ -23,6 +23,41 @@ Entscheidungs-Timeframe parametrisiert (`--tf` Minuten; ATR/ADX/Aktivierung auf
**Monoton schlechter, je kürzer der Timeframe** — exakt die Fee-Mathematik: ATR(1h) ≈ ⅓, ATR(15m) ≈ ⅙ von ATR(4h) → das Spacing schrumpft auf die Größenordnung der 0.3 % Round-Trip-Kosten, jeder TP verdient fast nichts, die Breakdown-Verluste bleiben gleich groß. tf 15m ist Totalverlust (MaxDD 97 %). Das war auch der Todesmechanismus des krypto-kuns-v1-Bots (115-min-Signale).
## Nachtrag 2: XRP-Datenanalyse + No-Stop-Grid (User-Ziel „good working GridBot")
**XRP-Historie (3 Jahre, 4h):** Nur **1 von 106** rollierenden 30d-Fenstern ist eine enge
Range (<15 % Spanne) — XRP „ranged" im klassischen Sinn praktisch nie. Es oszilliert
aber in **breiten Bändern** über lange Strecken (2023-Q42024-Q3: 0.380.75;
2025: 1.63.7). 25 % der Fenster fallen >10 %, schlimmste 30d: 32 % (Jan 2026).
ATR%(4h) median 1.43 %. → Engmaschige Grids mit hartem Stop sterben an den Rändern;
wenn Grid, dann **breit + ohne Verlust-Verkäufe**.
**No-Stop-Variante** (`--no-stop`: Lots werden nie mit Verlust verkauft, nur TP;
leeres Grid re-centert verlustfrei; `--pair` für Einzel-Pair):
| Variante | OOS-PF | Trades | WinRate | MaxDD | Gate |
|---|---|---|---|---|---|
| H: no-stop, 2×ATR, 6 Levels, XRP only | 2.00 | 195 | 80.0 % | 31.6 % | ❌ DD + Fenster + Ratio |
| I: no-stop, 2×ATR, 6 Levels, 4 Pairs | 1.33 | 719 | 77.6 % | 31.9 % | ❌ DD + Fenster + Ratio |
| J: no-stop, 3×ATR, 8 Levels, XRP only | **2.74** | 104 | 77.9 % | **17.5 %** | ❌ Fenster + Ratio |
**Volldurchlauf ohne Fenster-Artefakte** (J, 3 Jahre am Stück): **+49 %** (1000 → 1491),
MaxDD **10.2 %**, PF 4.18, 89 % WinRate — aber nur 27 Trades. Buy&Hold XRP: +130 %
bei 71 % Drawdown. Risk-adjusted schlägt das Grid Buy&Hold deutlich.
**Ehrliche Einordnung:**
- Erste Grid-Variante mit echtem Edge-Signal — aber Params J wurden nach Sichtung
von H/I gewählt (Selektionsrisiko), und 27 Trades/3J sind statistisch dünn.
- Der Worst-Window-Fail ist teils Artefakt (30d-Fenster zwangsliquidieren Inventar
am Fensterende), teils echt: Crash-Monate erwischen das Inventar voll.
- **Strukturelles Tail-Risiko:** Ein No-Stop-Grid verkauft nie mit Verlust — bei
einem Absturz ohne Erholung (2018-Stil, 95 %) hält es Bags bis zum Ende. Voll
gefüllt liegt ~100 % des Pair-Budgets im Asset. Die 10 % MaxDD der letzten
3 Jahre unterschätzen dieses Risiko systematisch.
**Status:** Gate formal nicht bestanden (Fenster + Ratio). Paper-Probelauf als
zweite Engine wäre — wie beim Trend-Bot — eine bewusste User-Entscheidung.
## Befund
Klassische Grid-Pathologie, durch Regime-Filter abgemildert, aber nicht behoben:

View File

@@ -13,6 +13,12 @@ export interface GridParams {
adxMax: number; // Grid nur aktiv im Seitwärtsregime (ADX < adxMax)
atrPeriod: number;
tfMs: number; // Entscheidungs-Timeframe (Aktivierung/Deaktivierung, ATR/ADX-Basis)
/**
* true: Range-Breakdown/Ausbruch/Trendbeginn liquidiert alle Lots (harter Stop).
* false: Lots werden nie mit Verlust verkauft (nur TP oder end_of_data);
* Re-Center nur, wenn das Grid leer ist und der Preis die Range verlassen hat.
*/
hardStop: boolean;
}
export const DEFAULT_GRID_PARAMS: GridParams = {
@@ -21,6 +27,7 @@ export const DEFAULT_GRID_PARAMS: GridParams = {
adxMax: 20,
atrPeriod: 14,
tfMs: H4,
hardStop: true,
};
export interface GridConfig {
@@ -136,9 +143,12 @@ export function runGridBacktest(candles15ByPair: Map<Pair, Candle[]>, cfg: GridC
const g = grids.get(pair);
if (g) {
const trendStart = !Number.isNaN(ctx.adx[i]) && ctx.adx[i] >= p.adxMax + 5; // Hysterese
if (bar.close < g.stopPrice || bar.close > g.upperExit || trendStart) {
liquidate(pair, barCloseTs, bar.close, 'grid_stop');
const outOfRange = bar.close < g.stopPrice || bar.close > g.upperExit;
if (p.hardStop) {
const trendStart = !Number.isNaN(ctx.adx[i]) && ctx.adx[i] >= p.adxMax + 5; // Hysterese
if (outOfRange || trendStart) liquidate(pair, barCloseTs, bar.close, 'grid_stop');
} else if (outOfRange && g.lots.every((l) => !l)) {
grids.delete(pair); // leeres Grid folgt dem Preis (Re-Center ohne Verlust)
}
} else if (
!Number.isNaN(ctx.atr[i]) &&
@@ -146,7 +156,7 @@ export function runGridBacktest(candles15ByPair: Map<Pair, Candle[]>, cfg: GridC
ctx.adx[i] < p.adxMax
) {
const spacing = p.spacingAtrMult * ctx.atr[i];
const budgetPerLevel = equity() / PAIRS.length / p.gridLevels;
const budgetPerLevel = equity() / contexts.length / p.gridLevels;
if (spacing > 0 && budgetPerLevel >= cfg.minNotionalUsdt) {
grids.set(pair, {
center: bar.close,

View File

@@ -19,7 +19,12 @@ const PARAMS = {
gridLevels: argNum('--levels', DEFAULT_GRID_PARAMS.gridLevels),
adxMax: argNum('--adx', DEFAULT_GRID_PARAMS.adxMax),
tfMs: argNum('--tf', DEFAULT_GRID_PARAMS.tfMs / 60000) * 60000, // Minuten
hardStop: !process.argv.includes('--no-stop'),
};
const ONLY_PAIR = (() => {
const i = process.argv.indexOf('--pair');
return i >= 0 ? (process.argv[i + 1] as Pair) : null;
})();
const START_CAPITAL = 1000;
const EXEC = DEFAULT_EXEC;
const MIN_NOTIONAL = 10;
@@ -29,6 +34,7 @@ let dataFrom = 0;
let dataTo = Number.MAX_SAFE_INTEGER;
for (const pair of PAIRS) {
if (ONLY_PAIR && pair !== ONLY_PAIR) continue;
const cov = await getCoverage(pair);
if (!cov.from || !cov.to) throw new Error(`Keine Candles für ${pair} — erst 'bun run backfill' ausführen.`);
candles15ByPair.set(pair, await getCandles(pair));
@@ -37,7 +43,7 @@ for (const pair of PAIRS) {
console.log(`${pair}: ${cov.count} Candles (${cov.from.toISOString()}${cov.to.toISOString()})`);
}
console.log(`\nATR-Grid (fix: spacing ${PARAMS.spacingAtrMult}×ATR, ${PARAMS.gridLevels} Levels, ADX < ${PARAMS.adxMax}, tf ${PARAMS.tfMs / 60000}m, long-only)`);
console.log(`\nATR-Grid (fix: spacing ${PARAMS.spacingAtrMult}×ATR, ${PARAMS.gridLevels} Levels, ADX < ${PARAMS.adxMax}, tf ${PARAMS.tfMs / 60000}m, ${PARAMS.hardStop ? 'hard-stop' : 'NO-STOP'}${ONLY_PAIR ? ', nur ' + ONLY_PAIR : ''}, long-only)`);
console.log(`Walk-Forward über ${((dataTo - dataFrom) / 86400000).toFixed(0)} Tage…\n`);
const windows = buildWindows(dataFrom, dataTo);