From c5d71bba749d202ffe66589d12ed0ad81b389b4e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 06:11:44 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Schema=20f=C3=BCr=20Live-Engine=20(posi?= =?UTF-8?q?tions,=20paper=5Ftrades,=20decision=5Flogs,=20bot=5Fstate,=20eq?= =?UTF-8?q?uity=5Fsnapshots)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- drizzle/0001_certain_omega_red.sql | 58 ++++ drizzle/meta/0001_snapshot.json | 466 +++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/server/db/schema.ts | 63 +++- 4 files changed, 593 insertions(+), 1 deletion(-) create mode 100644 drizzle/0001_certain_omega_red.sql create mode 100644 drizzle/meta/0001_snapshot.json diff --git a/drizzle/0001_certain_omega_red.sql b/drizzle/0001_certain_omega_red.sql new file mode 100644 index 0000000..34b725a --- /dev/null +++ b/drizzle/0001_certain_omega_red.sql @@ -0,0 +1,58 @@ +CREATE TABLE "bot_state" ( + "id" integer PRIMARY KEY NOT NULL, + "cash" double precision NOT NULL, + "start_capital" double precision NOT NULL, + "cursor_ts" timestamp with time zone NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "decision_logs" ( + "id" serial PRIMARY KEY NOT NULL, + "pair" varchar(16) NOT NULL, + "bar_ts" timestamp with time zone NOT NULL, + "signal" text, + "blocked_by" text, + "close" double precision NOT NULL, + "atr" double precision, + "adx" double precision, + "donchian_high" double precision, + "trend_ema" double precision, + "price_after_4h" double precision, + "price_after_24h" double precision, + "price_after_72h" double precision +); +--> statement-breakpoint +CREATE TABLE "equity_snapshots" ( + "ts" timestamp with time zone PRIMARY KEY NOT NULL, + "equity" double precision NOT NULL, + "cash" double precision NOT NULL +); +--> statement-breakpoint +CREATE TABLE "paper_trades" ( + "id" serial PRIMARY KEY NOT NULL, + "pair" varchar(16) NOT NULL, + "side" text NOT NULL, + "entry_ts" timestamp with time zone NOT NULL, + "entry_price" double precision NOT NULL, + "exit_ts" timestamp with time zone NOT NULL, + "exit_price" double precision NOT NULL, + "qty" double precision NOT NULL, + "pnl" double precision NOT NULL, + "r" double precision NOT NULL, + "exit_reason" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "positions" ( + "pair" varchar(16) PRIMARY KEY NOT NULL, + "side" text NOT NULL, + "qty" double precision NOT NULL, + "entry_ts" timestamp with time zone NOT NULL, + "entry_price" double precision NOT NULL, + "entry_cost" double precision NOT NULL, + "initial_stop" double precision NOT NULL, + "stop" double precision NOT NULL, + "trail_extreme" double precision NOT NULL, + "risk_amount" double precision NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX "decision_logs_pair_bar_ts" ON "decision_logs" USING btree ("pair","bar_ts"); \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..52544e9 --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,466 @@ +{ + "id": "38fbc5fc-4ef1-4dae-b408-21bcafa513b7", + "prevId": "00b411bc-669e-4667-881c-c9161fa42bb0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.backtest_runs": { + "name": "backtest_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_state": { + "name": "bot_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "cash": { + "name": "cash", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "start_capital": { + "name": "start_capital", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "cursor_ts": { + "name": "cursor_ts", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.candles": { + "name": "candles", + "schema": "", + "columns": { + "pair": { + "name": "pair", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "ts": { + "name": "ts", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "open": { + "name": "open", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "high": { + "name": "high", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "low": { + "name": "low", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "close": { + "name": "close", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "volume": { + "name": "volume", + "type": "double precision", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "candles_pair_ts_pk": { + "name": "candles_pair_ts_pk", + "columns": [ + "pair", + "ts" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_logs": { + "name": "decision_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pair": { + "name": "pair", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "bar_ts": { + "name": "bar_ts", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "close": { + "name": "close", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "atr": { + "name": "atr", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "adx": { + "name": "adx", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "donchian_high": { + "name": "donchian_high", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "trend_ema": { + "name": "trend_ema", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "price_after_4h": { + "name": "price_after_4h", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "price_after_24h": { + "name": "price_after_24h", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "price_after_72h": { + "name": "price_after_72h", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_logs_pair_bar_ts": { + "name": "decision_logs_pair_bar_ts", + "columns": [ + { + "expression": "pair", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bar_ts", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.equity_snapshots": { + "name": "equity_snapshots", + "schema": "", + "columns": { + "ts": { + "name": "ts", + "type": "timestamp with time zone", + "primaryKey": true, + "notNull": true + }, + "equity": { + "name": "equity", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "cash": { + "name": "cash", + "type": "double precision", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paper_trades": { + "name": "paper_trades", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pair": { + "name": "pair", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "side": { + "name": "side", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entry_ts": { + "name": "entry_ts", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "entry_price": { + "name": "entry_price", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "exit_ts": { + "name": "exit_ts", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "exit_price": { + "name": "exit_price", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "qty": { + "name": "qty", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "pnl": { + "name": "pnl", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "r": { + "name": "r", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "exit_reason": { + "name": "exit_reason", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.positions": { + "name": "positions", + "schema": "", + "columns": { + "pair": { + "name": "pair", + "type": "varchar(16)", + "primaryKey": true, + "notNull": true + }, + "side": { + "name": "side", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "qty": { + "name": "qty", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "entry_ts": { + "name": "entry_ts", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "entry_price": { + "name": "entry_price", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "entry_cost": { + "name": "entry_cost", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "initial_stop": { + "name": "initial_stop", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "stop": { + "name": "stop", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "trail_extreme": { + "name": "trail_extreme", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "risk_amount": { + "name": "risk_amount", + "type": "double precision", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index aefe02e..0c4934a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1781038570957, "tag": "0000_nifty_brood", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1781071452889, + "tag": "0001_certain_omega_red", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index b534cde..276f50e 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -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(),