feat: Schema für Live-Engine (positions, paper_trades, decision_logs, bot_state, equity_snapshots)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 06:11:44 +00:00
parent 69a0a7bee3
commit c5d71bba74
4 changed files with 593 additions and 1 deletions

View File

@@ -1,4 +1,4 @@
import { doublePrecision, jsonb, pgTable, primaryKey, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core';
import { doublePrecision, integer, jsonb, pgTable, primaryKey, serial, text, timestamp, uniqueIndex, varchar } from 'drizzle-orm/pg-core';
export const candles = pgTable(
'candles',
@@ -14,6 +14,67 @@ export const candles = pgTable(
(t) => [primaryKey({ columns: [t.pair, t.ts] })],
);
export const positions = pgTable('positions', {
pair: varchar('pair', { length: 16 }).primaryKey(),
side: text('side').notNull(), // 'long' | 'short'
qty: doublePrecision('qty').notNull(),
entryTs: timestamp('entry_ts', { withTimezone: true }).notNull(),
entryPrice: doublePrecision('entry_price').notNull(),
entryCost: doublePrecision('entry_cost').notNull(),
initialStop: doublePrecision('initial_stop').notNull(),
stop: doublePrecision('stop').notNull(),
trailExtreme: doublePrecision('trail_extreme').notNull(),
riskAmount: doublePrecision('risk_amount').notNull(),
});
export const paperTrades = pgTable('paper_trades', {
id: serial('id').primaryKey(),
pair: varchar('pair', { length: 16 }).notNull(),
side: text('side').notNull(),
entryTs: timestamp('entry_ts', { withTimezone: true }).notNull(),
entryPrice: doublePrecision('entry_price').notNull(),
exitTs: timestamp('exit_ts', { withTimezone: true }).notNull(),
exitPrice: doublePrecision('exit_price').notNull(),
qty: doublePrecision('qty').notNull(),
pnl: doublePrecision('pnl').notNull(),
r: doublePrecision('r').notNull(),
exitReason: text('exit_reason').notNull(),
});
export const decisionLogs = pgTable(
'decision_logs',
{
id: serial('id').primaryKey(),
pair: varchar('pair', { length: 16 }).notNull(),
barTs: timestamp('bar_ts', { withTimezone: true }).notNull(), // Start der 4h-Bar
signal: text('signal'), // 'long' | null
blockedBy: text('blocked_by'), // Evaluation.blockedBy | 'position_open' | 'max_positions' | Sizing-Block
close: doublePrecision('close').notNull(),
atr: doublePrecision('atr'),
adx: doublePrecision('adx'),
donchianHigh: doublePrecision('donchian_high'),
trendEma: doublePrecision('trend_ema'),
priceAfter4h: doublePrecision('price_after_4h'),
priceAfter24h: doublePrecision('price_after_24h'),
priceAfter72h: doublePrecision('price_after_72h'),
},
(t) => [uniqueIndex('decision_logs_pair_bar_ts').on(t.pair, t.barTs)],
);
export const botState = pgTable('bot_state', {
id: integer('id').primaryKey(), // immer 1
cash: doublePrecision('cash').notNull(),
startCapital: doublePrecision('start_capital').notNull(),
cursorTs: timestamp('cursor_ts', { withTimezone: true }).notNull(), // letzte verarbeitete 15m-Candle
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
});
export const equitySnapshots = pgTable('equity_snapshots', {
ts: timestamp('ts', { withTimezone: true }).primaryKey(), // 4h-Bucket
equity: doublePrecision('equity').notNull(),
cash: doublePrecision('cash').notNull(),
});
export const backtestRuns = pgTable('backtest_runs', {
id: serial('id').primaryKey(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),