feat: Trump-Engine in Loop und API verdrahtet (/api/trump, Gap-Fetch neue Pairs)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { and, desc, eq, gte } from 'drizzle-orm';
|
||||
import { db } from '../db/client';
|
||||
import { botState, candles, decisionLogs, equitySnapshots, gridLots, gridState, paperTrades, positions } from '../db/schema';
|
||||
import { botState, candles, decisionLogs, equitySnapshots, gridLots, gridState, paperTrades, positions, trumpEvents, trumpPositions } from '../db/schema';
|
||||
import { aggregate4h } from '../market/aggregate';
|
||||
import { computeMetrics, type EquityPoint } from '../backtest/metrics';
|
||||
import type { ClosedTrade } from '../engine/portfolio';
|
||||
@@ -9,6 +9,7 @@ import { PAIRS } from '../types';
|
||||
import type { LiveEngine } from '../live/engine';
|
||||
import type { GridEngine } from '../live/grid-engine';
|
||||
import { GRID_CYCLE_CONFIG } from '../live/grid-engine';
|
||||
import type { TrumpEngine } from '../live/trump-engine';
|
||||
|
||||
function json(data: unknown, status = 200): Response {
|
||||
return new Response(JSON.stringify(data), { status, headers: { 'content-type': 'application/json' } });
|
||||
@@ -181,7 +182,7 @@ async function getGrid(gridEngine: GridEngine) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createServer(engine: LiveEngine, gridEngine: GridEngine, port: number) {
|
||||
export function createServer(engine: LiveEngine, gridEngine: GridEngine, trumpEngine: TrumpEngine, port: number) {
|
||||
const indexHtml = Bun.file(new URL('../../../public/index.html', import.meta.url));
|
||||
|
||||
return Bun.serve({
|
||||
@@ -221,6 +222,18 @@ export function createServer(engine: LiveEngine, gridEngine: GridEngine, port: n
|
||||
return json(await getStats());
|
||||
case '/api/grid':
|
||||
return json(await getGrid(gridEngine));
|
||||
case '/api/trump': {
|
||||
const [state] = await db.select().from(botState).where(eq(botState.id, 3));
|
||||
const positions_trump = await db.select().from(trumpPositions);
|
||||
const events = await db.select().from(trumpEvents).orderBy(desc(trumpEvents.eventTs)).limit(50);
|
||||
return Response.json({
|
||||
status: trumpEngine.status,
|
||||
cash: state?.cash ?? null,
|
||||
startCapital: state?.startCapital ?? null,
|
||||
positions: positions_trump,
|
||||
events,
|
||||
});
|
||||
}
|
||||
case '/api/candles': {
|
||||
const pair = url.searchParams.get('pair') ?? 'BTC_USDT';
|
||||
if (!(PAIRS as readonly string[]).includes(pair)) return json({ error: 'unbekanntes Pair' }, 400);
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import { env } from './config';
|
||||
import { LiveEngine } from './live/engine';
|
||||
import { GridEngine } from './live/grid-engine';
|
||||
import { TrumpEngine } from './live/trump-engine';
|
||||
import { createServer } from './api/server';
|
||||
|
||||
const CYCLE_MS = 5 * 60 * 1000;
|
||||
|
||||
const engine = new LiveEngine();
|
||||
const gridEngine = new GridEngine();
|
||||
const trumpEngine = new TrumpEngine();
|
||||
await engine.init();
|
||||
await gridEngine.init();
|
||||
createServer(engine, gridEngine, env.PORT);
|
||||
console.log(`trade-kuns Live-Paper-Engines (trend + grid) laufen auf :${env.PORT}`);
|
||||
await trumpEngine.init();
|
||||
createServer(engine, gridEngine, trumpEngine, env.PORT);
|
||||
console.log(`trade-kuns Live-Paper-Engines (trend + grid + trump) laufen auf :${env.PORT}`);
|
||||
|
||||
// Grid läuft nach der Trend-Engine — deren Gap-Fetch füllt die Candle-DB für beide.
|
||||
// Grid/Trump laufen nach der Trend-Engine — deren Gap-Fetch füllt die Candle-DB für PAIRS.
|
||||
const cycle = async () => {
|
||||
await engine.runCycle();
|
||||
await gridEngine.runCycle();
|
||||
await trumpEngine.runCycle();
|
||||
};
|
||||
void cycle();
|
||||
setInterval(() => void cycle(), CYCLE_MS);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { and, eq, isNotNull, isNull } from 'drizzle-orm';
|
||||
import { db } from '../db/client';
|
||||
import { botState, equitySnapshots, paperTrades, trumpEvents, trumpPositions } from '../db/schema';
|
||||
import { getCandles } from '../market/candle-store';
|
||||
import { fetchCandles } from '../market/cryptocom';
|
||||
import { getCandles, insertCandles } from '../market/candle-store';
|
||||
import { DEFAULT_EXEC } from '../engine/portfolio';
|
||||
import { pollSignals } from '../signals/poller';
|
||||
import { TRUMP_PAIRS } from '../types';
|
||||
import { PAIRS, TRUMP_PAIRS } from '../types';
|
||||
import type { Candle, Pair } from '../types';
|
||||
import {
|
||||
processTrumpCycle,
|
||||
@@ -60,11 +61,40 @@ export class TrumpEngine {
|
||||
this.status.cursorTs = cursor;
|
||||
}
|
||||
|
||||
/** Holt fehlende 15m-Candles für Nicht-Trend-Pairs (TRUMP_CYCLE_CONFIG.pairs minus PAIRS). */
|
||||
private async fillCandleGaps(): Promise<void> {
|
||||
const trumpOnlyPairs = TRUMP_CYCLE_CONFIG.pairs.filter(
|
||||
(p) => !(PAIRS as readonly string[]).includes(p),
|
||||
);
|
||||
const state = await db.select().from(botState).where(eq(botState.id, BOT_STATE_ID));
|
||||
const cursorTs = state[0]?.cursorTs.getTime() ?? Date.now();
|
||||
const now = Date.now();
|
||||
for (const pair of trumpOnlyPairs) {
|
||||
try {
|
||||
const fresh: Candle[] = [];
|
||||
let endTs: number | undefined;
|
||||
for (let i = 0; i < 40; i++) {
|
||||
const batch = await fetchCandles(pair, '15m', 300, endTs);
|
||||
if (batch.length === 0) break;
|
||||
fresh.push(...batch);
|
||||
const oldest = Math.min(...batch.map((c) => c.ts));
|
||||
if (oldest <= cursorTs) break;
|
||||
endTs = oldest - 1;
|
||||
}
|
||||
const closed = fresh.filter((c) => c.ts + M15 <= now && c.ts > cursorTs);
|
||||
if (closed.length > 0) await insertCandles(pair, closed);
|
||||
} catch (err) {
|
||||
console.warn(`Trump-Engine Gap-Fetch Fehler für ${pair}:`, err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async runCycle(): Promise<void> {
|
||||
if (this.cycling) return;
|
||||
this.cycling = true;
|
||||
try {
|
||||
await pollSignals(); // non-fatal intern; wirft nicht
|
||||
await this.fillCandleGaps();
|
||||
|
||||
const state = await this.loadState();
|
||||
const from = state.cursorTs - WARMUP_BARS_15M * M15;
|
||||
|
||||
Reference in New Issue
Block a user