feat: Event-Study-Script (Forward-Returns je Quelle/Horizont vs. Baseline)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,8 @@
|
|||||||
"grid": "bun run src/server/scripts/grid-walkforward.ts",
|
"grid": "bun run src/server/scripts/grid-walkforward.ts",
|
||||||
"db:generate": "bunx drizzle-kit generate",
|
"db:generate": "bunx drizzle-kit generate",
|
||||||
"db:migrate": "bun run src/server/db/migrate.ts",
|
"db:migrate": "bun run src/server/db/migrate.ts",
|
||||||
"trump:backfill": "bun run src/server/scripts/trump-backfill.ts"
|
"trump:backfill": "bun run src/server/scripts/trump-backfill.ts",
|
||||||
|
"trump:study": "bun run src/server/scripts/trump-event-study.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "^0.44.0",
|
"drizzle-orm": "^0.44.0",
|
||||||
|
|||||||
68
src/server/scripts/trump-event-study.ts
Normal file
68
src/server/scripts/trump-event-study.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { isNotNull } from 'drizzle-orm';
|
||||||
|
import { db, sql } from '../db/client';
|
||||||
|
import { trumpEvents } from '../db/schema';
|
||||||
|
import { getCandles } from '../market/candle-store';
|
||||||
|
import { DEFAULT_EXEC } from '../engine/portfolio';
|
||||||
|
import type { Pair } from '../types';
|
||||||
|
|
||||||
|
const M15 = 15 * 60 * 1000;
|
||||||
|
const HORIZONS_H = [24, 48, 60, 72, 120];
|
||||||
|
const COST = (1 - DEFAULT_EXEC.slippage) * (1 - DEFAULT_EXEC.feeRate) / ((1 + DEFAULT_EXEC.slippage) * (1 + DEFAULT_EXEC.feeRate)); // Round-Trip-Faktor
|
||||||
|
|
||||||
|
const events = await db.select().from(trumpEvents).where(isNotNull(trumpEvents.instrument));
|
||||||
|
type Row = { source: string; horizon: number; ret: number };
|
||||||
|
const rows: Row[] = [];
|
||||||
|
const baselines = new Map<string, Map<number, number>>(); // instrument → horizon → mean ret
|
||||||
|
|
||||||
|
for (const ev of events) {
|
||||||
|
const instrument = ev.instrument as Pair;
|
||||||
|
const evTs = ev.eventTs.getTime();
|
||||||
|
const candles = await getCandles(instrument, evTs - M15, evTs + 130 * 3600_000);
|
||||||
|
const entryIdx = candles.findIndex((c) => c.ts >= evTs);
|
||||||
|
if (entryIdx < 0) continue;
|
||||||
|
const entry = candles[entryIdx].open;
|
||||||
|
for (const h of HORIZONS_H) {
|
||||||
|
const exitTs = candles[entryIdx].ts + h * 3600_000;
|
||||||
|
const exit = candles.findLast((c) => c.ts <= exitTs);
|
||||||
|
if (!exit || exit.ts < exitTs - 2 * M15) continue; // Horizont nicht abgedeckt
|
||||||
|
rows.push({ source: ev.source, horizon: h, ret: (exit.close / entry) * COST - 1 });
|
||||||
|
}
|
||||||
|
// Baseline je Instrument einmalig: unbedingter Mean-Forward-Return über alle 15m-Starts
|
||||||
|
if (!baselines.has(instrument)) {
|
||||||
|
const all = await getCandles(instrument);
|
||||||
|
const byH = new Map<number, number>();
|
||||||
|
for (const h of HORIZONS_H) {
|
||||||
|
const stepIdx = (h * 3600_000) / M15;
|
||||||
|
let s = 0, n = 0;
|
||||||
|
for (let i = 0; i + stepIdx < all.length; i += 16) { // jede 4h ein Sample, deterministisch
|
||||||
|
s += (all[i + stepIdx].close / all[i].open) * COST - 1;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
byH.set(h, n > 0 ? s / n : NaN);
|
||||||
|
}
|
||||||
|
baselines.set(instrument, byH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fmt = (x: number) => (100 * x).toFixed(2) + '%';
|
||||||
|
const lines: string[] = ['# Event-Study Trump-Copy — ' + new Date().toISOString().slice(0, 10), ''];
|
||||||
|
for (const source of ['onchain', 'truth']) {
|
||||||
|
lines.push(`## Quelle: ${source}`, '', '| Horizont | n | Mean | Median | Hit-Rate |', '|---|---|---|---|---|');
|
||||||
|
for (const h of HORIZONS_H) {
|
||||||
|
const rs = rows.filter((r) => r.source === source && r.horizon === h).map((r) => r.ret).sort((a, b) => a - b);
|
||||||
|
if (rs.length === 0) { lines.push(`| ${h}h | 0 | — | — | — |`); continue; }
|
||||||
|
const mean = rs.reduce((a, b) => a + b, 0) / rs.length;
|
||||||
|
const median = rs[Math.floor(rs.length / 2)];
|
||||||
|
const hit = rs.filter((r) => r > 0).length / rs.length;
|
||||||
|
lines.push(`| ${h}h | ${rs.length} | ${fmt(mean)} | ${fmt(median)} | ${(100 * hit).toFixed(0)}% |`);
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
lines.push('## Baselines (unbedingter Mean-Forward-Return, gleiche Kosten)', '');
|
||||||
|
for (const [inst, byH] of baselines) {
|
||||||
|
lines.push(`- ${inst}: ` + HORIZONS_H.map((h) => `${h}h ${fmt(byH.get(h)!)}`).join(' · '));
|
||||||
|
}
|
||||||
|
const out = lines.join('\n') + '\n';
|
||||||
|
await Bun.write('docs/event-study-trump-2026-06-12.md', out);
|
||||||
|
console.log(out);
|
||||||
|
await sql.end();
|
||||||
Reference in New Issue
Block a user