Phase 1: Configuration - Added pyramiding config to trading.ts interface and defaults - Added 6 ENV variables: ENABLE_PYRAMIDING, BASE_LEVERAGE, STACK_LEVERAGE, MAX_LEVERAGE_TOTAL, MAX_PYRAMID_LEVELS, STACKING_WINDOW_MINUTES Phase 2: Database Schema - Added 5 Trade fields: pyramidLevel, parentTradeId, stackedAt, totalLeverageAtEntry, isStackedPosition - Added index on parentTradeId for pyramid group queries Phase 3: Execute Endpoint - Added findExistingPyramidBase() - finds active base trade within window - Added canAddPyramidLevel() - validates pyramid conditions - Stores pyramid metadata on new trades Phase 4: Position Manager Core - Added pyramidGroups Map for trade ID grouping - Added addToPyramidGroup() - groups stacked trades by parent - Added closeAllPyramidLevels() - unified exit for all levels - Added getTotalPyramidLeverage() - calculates combined leverage - All exit triggers now close entire pyramid group Phase 5: Telegram Notifications - Added sendPyramidStackNotification() - notifies on stack entry - Added sendPyramidCloseNotification() - notifies on unified exit Phase 6: Testing (25 tests, ALL PASSING) - Pyramid Detection: 5 tests - Pyramid Group Tracking: 4 tests - Unified Exit: 4 tests - Leverage Calculation: 4 tests - Notification Context: 2 tests - Edge Cases: 6 tests Phase 7: Documentation - Updated .github/copilot-instructions.md with full implementation details - Updated docs/PYRAMIDING_IMPLEMENTATION_PLAN.md status to COMPLETE Parameters: 4h window, 7x base/stack leverage, 14x max total, 2 max levels Data-driven: 100% win rate for signals ≤72 bars apart in backtesting
253 lines
7.4 KiB
Plaintext
253 lines
7.4 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
model Trade {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
positionId String @unique
|
|
symbol String
|
|
direction String
|
|
entryPrice Float
|
|
entryTime DateTime
|
|
entrySlippage Float?
|
|
positionSizeUSD Float
|
|
leverage Float
|
|
stopLossPrice Float
|
|
softStopPrice Float?
|
|
hardStopPrice Float?
|
|
takeProfit1Price Float
|
|
takeProfit2Price Float
|
|
tp1SizePercent Float
|
|
tp2SizePercent Float
|
|
exitPrice Float?
|
|
exitTime DateTime?
|
|
exitReason String?
|
|
realizedPnL Float?
|
|
realizedPnLPercent Float?
|
|
holdTimeSeconds Int?
|
|
maxDrawdown Float?
|
|
maxGain Float?
|
|
entryOrderTx String
|
|
tp1OrderTx String?
|
|
tp2OrderTx String?
|
|
slOrderTx String?
|
|
softStopOrderTx String?
|
|
hardStopOrderTx String?
|
|
exitOrderTx String?
|
|
configSnapshot Json
|
|
signalSource String?
|
|
signalStrength String?
|
|
timeframe String?
|
|
status String @default("open")
|
|
isTestTrade Boolean @default(false)
|
|
adxAtEntry Float?
|
|
atrAtEntry Float?
|
|
basisAtEntry Float?
|
|
entrySlippagePct Float?
|
|
exitSlippagePct Float?
|
|
expectedEntryPrice Float?
|
|
expectedExitPrice Float?
|
|
fundingRateAtEntry Float?
|
|
hardSlFilled Boolean @default(false)
|
|
maxAdverseExcursion Float?
|
|
maxAdversePrice Float?
|
|
maxFavorableExcursion Float?
|
|
maxFavorablePrice Float?
|
|
slFillPrice Float?
|
|
softSlFilled Boolean @default(false)
|
|
timeToSl Int?
|
|
timeToTp1 Int?
|
|
timeToTp2 Int?
|
|
tp1FillPrice Float?
|
|
tp1Filled Boolean @default(false)
|
|
tp2FillPrice Float?
|
|
tp2Filled Boolean @default(false)
|
|
volumeAtEntry Float?
|
|
pricePositionAtEntry Float?
|
|
rsiAtEntry Float?
|
|
signalQualityScore Int?
|
|
actualSizeUSD Float?
|
|
expectedSizeUSD Float?
|
|
isPhantom Boolean @default(false)
|
|
phantomReason String?
|
|
collateralUSD Float?
|
|
signalQualityVersion String? @default("v4")
|
|
indicatorVersion String?
|
|
closeAttempts Int?
|
|
spreadBps Float?
|
|
imbalanceRatio Float?
|
|
depthBid1Usd Float?
|
|
depthAsk1Usd Float?
|
|
priceImpact1Usd Float?
|
|
bidWall Float?
|
|
askWall Float?
|
|
priceUpdates PriceUpdate[]
|
|
|
|
// Pyramiding / Position Stacking (Jan 6, 2026)
|
|
pyramidLevel Int? // 1 = base entry, 2 = first stack, etc.
|
|
parentTradeId String? // Links stacked trades to base trade
|
|
stackedAt DateTime? // When stack was added to base position
|
|
totalLeverageAtEntry Float? // Running total leverage at time of this entry
|
|
isStackedPosition Boolean @default(false) // True for stacked trades, false for base
|
|
|
|
@@index([symbol])
|
|
@@index([createdAt])
|
|
@@index([status])
|
|
@@index([exitReason])
|
|
@@index([parentTradeId]) // Index for finding stacked trades by parent
|
|
}
|
|
|
|
model PriceUpdate {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
tradeId String
|
|
price Float
|
|
pnl Float
|
|
pnlPercent Float
|
|
trade Trade @relation(fields: [tradeId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([tradeId])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model SystemEvent {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
eventType String
|
|
message String
|
|
details Json?
|
|
|
|
@@index([eventType])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model BlockedSignal {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
symbol String
|
|
direction String
|
|
timeframe String?
|
|
signalPrice Float
|
|
atr Float?
|
|
adx Float?
|
|
rsi Float?
|
|
volumeRatio Float?
|
|
pricePosition Float?
|
|
signalQualityScore Int
|
|
signalQualityVersion String?
|
|
scoreBreakdown Json?
|
|
minScoreRequired Int
|
|
blockReason String
|
|
blockDetails String?
|
|
priceAfter1Min Float?
|
|
priceAfter5Min Float?
|
|
priceAfter15Min Float?
|
|
priceAfter30Min Float?
|
|
wouldHitTP1 Boolean?
|
|
wouldHitTP2 Boolean?
|
|
wouldHitSL Boolean?
|
|
analysisComplete Boolean @default(false)
|
|
indicatorVersion String?
|
|
entryPrice Float @default(0)
|
|
maxFavorablePrice Float?
|
|
maxAdversePrice Float?
|
|
maxFavorableExcursion Float?
|
|
maxAdverseExcursion Float?
|
|
priceAfter1Hr Float?
|
|
priceAfter2Hr Float?
|
|
priceAfter4Hr Float?
|
|
priceAfter8Hr Float?
|
|
tp1HitTime DateTime? @map("tp1_hit_time")
|
|
tp2HitTime DateTime? @map("tp2_hit_time")
|
|
slHitTime DateTime? @map("sl_hit_time")
|
|
|
|
@@index([symbol])
|
|
@@index([createdAt])
|
|
@@index([signalQualityScore])
|
|
@@index([blockReason])
|
|
}
|
|
|
|
model StopHunt {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
originalTradeId String
|
|
symbol String
|
|
direction String
|
|
stopHuntPrice Float
|
|
originalEntryPrice Float
|
|
originalQualityScore Int
|
|
originalADX Float?
|
|
originalATR Float?
|
|
stopLossAmount Float
|
|
stopHuntTime DateTime
|
|
revengeTradeId String?
|
|
revengeExecuted Boolean @default(false)
|
|
revengeEntryPrice Float?
|
|
revengeTime DateTime?
|
|
revengeWindowExpired Boolean @default(false)
|
|
revengeExpiresAt DateTime
|
|
highestPriceAfterStop Float?
|
|
lowestPriceAfterStop Float?
|
|
revengeFailedReason String?
|
|
revengeOutcome String?
|
|
revengePnL Float?
|
|
slDistanceAtEntry Float?
|
|
firstCrossTime DateTime?
|
|
highestInZone Float?
|
|
lowestInZone Float?
|
|
zoneResetCount Int @default(0)
|
|
|
|
@@index([symbol])
|
|
@@index([revengeExecuted])
|
|
@@index([revengeWindowExpired])
|
|
@@index([stopHuntTime])
|
|
@@index([revengeOutcome])
|
|
}
|
|
|
|
model MarketData {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
symbol String
|
|
timeframe String
|
|
price Float
|
|
atr Float
|
|
adx Float
|
|
rsi Float
|
|
volumeRatio Float
|
|
pricePosition Float
|
|
maGap Float?
|
|
volume Float?
|
|
timestamp DateTime
|
|
|
|
@@index([symbol, timestamp])
|
|
@@index([createdAt])
|
|
@@index([timestamp])
|
|
}
|
|
|
|
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])
|
|
}
|