Files
2026-05-31 13:49:36 +08:00

69 lines
2.5 KiB
TypeScript

/**
* Strategy 3 · Tail-Scalp — large-diff entry at the tail of the window
*/
import type {
IStrategy, StrategyKey, StrategyNumber, StrategyDirection,
StrategyTickContext, EntrySignal, ExitSignal, StrategyDescription,
} from "./types.js";
const WINDOW_MAX_REMAINING = 30;
const ENTRY_DIFF = 40;
const ENTRY_PROB_CAP = 80;
const STOP_LOSS_DIFF = 0;
export class D1Sweep implements IStrategy {
readonly key: StrategyKey = "d1";
readonly number: StrategyNumber = 1;
readonly name = "Tail-Scalp";
getDescription(): StrategyDescription {
return {
key: this.key,
number: this.number,
name: this.name,
title: "Diff 1 · Tail-Scalp",
category: { id: "diff", label: "Diff", color: "#58a6ff" },
supportedMarkets: ["btc-5m"],
lines: [
{ text: `⏱ Checked when remaining ${WINDOW_MAX_REMAINING}s~0s (the very tail, highest win rate)` },
{ text: `📈 Buy up: diff >+${ENTRY_DIFF} and up probability <${ENTRY_PROB_CAP}%` },
{ text: `📉 Buy down: diff <-${ENTRY_DIFF} and down probability <${ENTRY_PROB_CAP}%` },
{ text: `Stop loss: diff crosses 0 (buy up diff≤0 / buy down diff≥0)`, color: "#f85149", marginTop: true },
{ text: "No take profit, hold to window end", color: "#3fb950" },
{ text: "Backtest 24 days: ~7 trades/day / 86% win rate / $0.70 per trade (passed all 5 validations)", color: "#888", marginTop: true },
],
};
}
updateGuards(_ctx: StrategyTickContext): void {}
checkEntry(ctx: StrategyTickContext): EntrySignal | null {
const { rem, upPct, dnPct, diff } = ctx;
if (upPct == null || dnPct == null || diff == null) return null;
if (rem > WINDOW_MAX_REMAINING || rem <= 0) return null;
if (diff > ENTRY_DIFF && upPct < ENTRY_PROB_CAP) return { direction: "up" };
if (diff < -ENTRY_DIFF && dnPct < ENTRY_PROB_CAP) return { direction: "down" };
return null;
}
checkExit(ctx: StrategyTickContext, direction: StrategyDirection): ExitSignal {
const { diff } = ctx;
if (diff == null) return null;
if (direction === "up" && diff <= STOP_LOSS_DIFF) {
return { signal: "sl", reason: `stop loss diff=${Math.round(diff)}${STOP_LOSS_DIFF}` };
}
if (direction === "down" && diff >= -STOP_LOSS_DIFF) {
return { signal: "sl", reason: `stop loss diff=${Math.round(diff)}${-STOP_LOSS_DIFF}` };
}
return null;
}
resetState(): void {}
getStatePayload(): Record<string, unknown> {
return {};
}
}