feat: implement dual stop system and database tracking

- Add PostgreSQL database with Prisma ORM
  - Trade model: tracks entry/exit, P&L, order signatures, config snapshots
  - PriceUpdate model: tracks price movements for drawdown analysis
  - SystemEvent model: logs errors and system events
  - DailyStats model: aggregated performance metrics

- Implement dual stop loss system (enabled by default)
  - Soft stop (TRIGGER_LIMIT) at -1.5% to avoid wicks
  - Hard stop (TRIGGER_MARKET) at -2.5% to guarantee exit
  - Configurable via USE_DUAL_STOPS, SOFT_STOP_PERCENT, HARD_STOP_PERCENT
  - Backward compatible with single stop modes

- Add database service layer (lib/database/trades.ts)
  - createTrade(): save new trades with all details
  - updateTradeExit(): close trades with P&L calculations
  - addPriceUpdate(): track price movements during trade
  - getTradeStats(): calculate win rate, profit factor, avg win/loss
  - logSystemEvent(): log errors and system events

- Update execute endpoint to use dual stops and save to database
  - Calculate dual stop prices when enabled
  - Pass dual stop parameters to placeExitOrders
  - Save complete trade record to database after execution

- Add test trade button to settings page
  - New /api/trading/test endpoint for executing test trades
  - Displays detailed results including dual stop prices
  - Confirmation dialog before execution
  - Shows entry price, position size, stops, and TX signature

- Generate Prisma client in Docker build
- Update DATABASE_URL for container networking
This commit is contained in:
mindesbunister
2025-10-26 21:29:27 +01:00
parent 33821eae0c
commit d64f6d84c4
13 changed files with 2616 additions and 78 deletions

View File

@@ -0,0 +1,123 @@
-- CreateTable
CREATE TABLE "Trade" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"positionId" TEXT NOT NULL,
"symbol" TEXT NOT NULL,
"direction" TEXT NOT NULL,
"entryPrice" DOUBLE PRECISION NOT NULL,
"entryTime" TIMESTAMP(3) NOT NULL,
"entrySlippage" DOUBLE PRECISION,
"positionSizeUSD" DOUBLE PRECISION NOT NULL,
"leverage" DOUBLE PRECISION NOT NULL,
"stopLossPrice" DOUBLE PRECISION NOT NULL,
"softStopPrice" DOUBLE PRECISION,
"hardStopPrice" DOUBLE PRECISION,
"takeProfit1Price" DOUBLE PRECISION NOT NULL,
"takeProfit2Price" DOUBLE PRECISION NOT NULL,
"tp1SizePercent" DOUBLE PRECISION NOT NULL,
"tp2SizePercent" DOUBLE PRECISION NOT NULL,
"exitPrice" DOUBLE PRECISION,
"exitTime" TIMESTAMP(3),
"exitReason" TEXT,
"realizedPnL" DOUBLE PRECISION,
"realizedPnLPercent" DOUBLE PRECISION,
"holdTimeSeconds" INTEGER,
"maxDrawdown" DOUBLE PRECISION,
"maxGain" DOUBLE PRECISION,
"entryOrderTx" TEXT NOT NULL,
"tp1OrderTx" TEXT,
"tp2OrderTx" TEXT,
"slOrderTx" TEXT,
"softStopOrderTx" TEXT,
"hardStopOrderTx" TEXT,
"exitOrderTx" TEXT,
"configSnapshot" JSONB NOT NULL,
"signalSource" TEXT,
"signalStrength" TEXT,
"timeframe" TEXT,
"status" TEXT NOT NULL DEFAULT 'open',
CONSTRAINT "Trade_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PriceUpdate" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"tradeId" TEXT NOT NULL,
"price" DOUBLE PRECISION NOT NULL,
"pnl" DOUBLE PRECISION NOT NULL,
"pnlPercent" DOUBLE PRECISION NOT NULL,
CONSTRAINT "PriceUpdate_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SystemEvent" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"eventType" TEXT NOT NULL,
"message" TEXT NOT NULL,
"details" JSONB,
CONSTRAINT "SystemEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DailyStats" (
"id" TEXT NOT NULL,
"date" TIMESTAMP(3) NOT NULL,
"tradesCount" INTEGER NOT NULL,
"winningTrades" INTEGER NOT NULL,
"losingTrades" INTEGER NOT NULL,
"totalPnL" DOUBLE PRECISION NOT NULL,
"totalPnLPercent" DOUBLE PRECISION NOT NULL,
"winRate" DOUBLE PRECISION NOT NULL,
"avgWin" DOUBLE PRECISION NOT NULL,
"avgLoss" DOUBLE PRECISION NOT NULL,
"profitFactor" DOUBLE PRECISION NOT NULL,
"maxDrawdown" DOUBLE PRECISION NOT NULL,
"sharpeRatio" DOUBLE PRECISION,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DailyStats_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Trade_positionId_key" ON "Trade"("positionId");
-- CreateIndex
CREATE INDEX "Trade_symbol_idx" ON "Trade"("symbol");
-- CreateIndex
CREATE INDEX "Trade_createdAt_idx" ON "Trade"("createdAt");
-- CreateIndex
CREATE INDEX "Trade_status_idx" ON "Trade"("status");
-- CreateIndex
CREATE INDEX "Trade_exitReason_idx" ON "Trade"("exitReason");
-- CreateIndex
CREATE INDEX "PriceUpdate_tradeId_idx" ON "PriceUpdate"("tradeId");
-- CreateIndex
CREATE INDEX "PriceUpdate_createdAt_idx" ON "PriceUpdate"("createdAt");
-- CreateIndex
CREATE INDEX "SystemEvent_eventType_idx" ON "SystemEvent"("eventType");
-- CreateIndex
CREATE INDEX "SystemEvent_createdAt_idx" ON "SystemEvent"("createdAt");
-- CreateIndex
CREATE UNIQUE INDEX "DailyStats_date_key" ON "DailyStats"("date");
-- CreateIndex
CREATE INDEX "DailyStats_date_idx" ON "DailyStats"("date");
-- AddForeignKey
ALTER TABLE "PriceUpdate" ADD CONSTRAINT "PriceUpdate_tradeId_fkey" FOREIGN KEY ("tradeId") REFERENCES "Trade"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

131
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,131 @@
// Prisma Schema for Trading Bot v4
// Database: PostgreSQL
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Trade records for analysis and performance tracking
model Trade {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Trade identification
positionId String @unique // Transaction signature from entry order
symbol String // e.g., "SOL-PERP"
direction String // "long" or "short"
// Entry details
entryPrice Float
entryTime DateTime
entrySlippage Float?
positionSizeUSD Float
leverage Float
// Exit targets (planned)
stopLossPrice Float
softStopPrice Float? // Dual stop: soft stop-limit trigger
hardStopPrice Float? // Dual stop: hard stop-market trigger
takeProfit1Price Float
takeProfit2Price Float
tp1SizePercent Float
tp2SizePercent Float
// Exit details (actual)
exitPrice Float?
exitTime DateTime?
exitReason String? // "TP1", "TP2", "SL", "SOFT_SL", "HARD_SL", "manual", "emergency"
// Performance metrics
realizedPnL Float?
realizedPnLPercent Float?
holdTimeSeconds Int?
maxDrawdown Float? // Peak to valley during trade
maxGain Float? // Peak gain reached
// Order signatures
entryOrderTx String
tp1OrderTx String?
tp2OrderTx String?
slOrderTx String?
softStopOrderTx String? // Dual stop: soft stop tx
hardStopOrderTx String? // Dual stop: hard stop tx
exitOrderTx String?
// Configuration snapshot
configSnapshot Json // Store settings used for this trade
// Signal data
signalSource String? // "tradingview", "manual", etc.
signalStrength String? // "strong", "moderate", "weak"
timeframe String? // "5", "15", "60"
// Status
status String @default("open") // "open", "closed", "failed"
// Relations
priceUpdates PriceUpdate[]
@@index([symbol])
@@index([createdAt])
@@index([status])
@@index([exitReason])
}
// Real-time price updates during trade (for analysis)
model PriceUpdate {
id String @id @default(cuid())
createdAt DateTime @default(now())
tradeId String
trade Trade @relation(fields: [tradeId], references: [id], onDelete: Cascade)
price Float
pnl Float
pnlPercent Float
@@index([tradeId])
@@index([createdAt])
}
// System events and errors
model SystemEvent {
id String @id @default(cuid())
createdAt DateTime @default(now())
eventType String // "error", "warning", "info", "trade_executed", etc.
message String
details Json?
@@index([eventType])
@@index([createdAt])
}
// Performance analytics (daily aggregates)
model DailyStats {
id String @id @default(cuid())
date DateTime @unique
tradesCount Int
winningTrades Int
losingTrades Int
totalPnL Float
totalPnLPercent Float
winRate Float
avgWin Float
avgLoss Float
profitFactor Float
maxDrawdown Float
sharpeRatio Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([date])
}