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:
123
prisma/migrations/20251026200052_init/migration.sql
Normal file
123
prisma/migrations/20251026200052_init/migration.sql
Normal 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;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal 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
131
prisma/schema.prisma
Normal 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])
|
||||
}
|
||||
Reference in New Issue
Block a user