feat: implement per-symbol cooldown period
CRITICAL: Cooldown was global across ALL symbols, causing missed opportunities Example: ETH trade at 10:00 blocked SOL trade at 10:04 (5min cooldown) Changes: - Added getLastTradeTimeForSymbol() function to query last trade per symbol - Updated check-risk endpoint to use symbol-specific cooldown - Each coin (SOL/ETH/BTC) now has independent cooldown timer - Cooldown message shows symbol: 'Must wait X min before next SOL-PERP trade' Result: Can trade ETH and SOL simultaneously without interference Example: ETH LONG at 10:00, SOL SHORT at 10:01 = both allowed
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getMergedConfig } from '@/config/trading'
|
||||
import { getInitializedPositionManager } from '@/lib/trading/position-manager'
|
||||
import { getLastTradeTime, getTradesInLastHour, getTodayPnL } from '@/lib/database/trades'
|
||||
import { getLastTradeTime, getLastTradeTimeForSymbol, getTradesInLastHour, getTodayPnL } from '@/lib/database/trades'
|
||||
|
||||
export interface RiskCheckRequest {
|
||||
symbol: string
|
||||
@@ -118,18 +118,18 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
|
||||
})
|
||||
}
|
||||
|
||||
// 3. Check cooldown period
|
||||
const lastTradeTime = await getLastTradeTime()
|
||||
if (lastTradeTime && config.minTimeBetweenTrades > 0) {
|
||||
const timeSinceLastTrade = Date.now() - lastTradeTime.getTime()
|
||||
// 3. Check cooldown period PER SYMBOL (not global)
|
||||
const lastTradeTimeForSymbol = await getLastTradeTimeForSymbol(body.symbol)
|
||||
if (lastTradeTimeForSymbol && config.minTimeBetweenTrades > 0) {
|
||||
const timeSinceLastTrade = Date.now() - lastTradeTimeForSymbol.getTime()
|
||||
const cooldownMs = config.minTimeBetweenTrades * 60 * 1000 // Convert minutes to milliseconds
|
||||
|
||||
if (timeSinceLastTrade < cooldownMs) {
|
||||
const remainingMs = cooldownMs - timeSinceLastTrade
|
||||
const remainingMinutes = Math.ceil(remainingMs / 60000)
|
||||
|
||||
console.log('🚫 Risk check BLOCKED: Cooldown period active', {
|
||||
lastTradeTime: lastTradeTime.toISOString(),
|
||||
console.log('🚫 Risk check BLOCKED: Cooldown period active for', body.symbol, {
|
||||
lastTradeTime: lastTradeTimeForSymbol.toISOString(),
|
||||
timeSinceLastTradeMs: timeSinceLastTrade,
|
||||
cooldownMs,
|
||||
remainingMinutes,
|
||||
@@ -138,7 +138,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
|
||||
return NextResponse.json({
|
||||
allowed: false,
|
||||
reason: 'Cooldown period',
|
||||
details: `Must wait ${remainingMinutes} more minute(s) before next trade (cooldown: ${config.minTimeBetweenTrades} min)`,
|
||||
details: `Must wait ${remainingMinutes} more minute(s) before next ${body.symbol} trade (cooldown: ${config.minTimeBetweenTrades} min)`,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
|
||||
console.log(`✅ Risk check PASSED: All checks passed`, {
|
||||
todayPnL: todayPnL.toFixed(2),
|
||||
tradesLastHour: tradesInLastHour,
|
||||
cooldownPassed: lastTradeTime ? 'yes' : 'no previous trades',
|
||||
cooldownPassed: lastTradeTimeForSymbol ? 'yes' : `no previous ${body.symbol} trades`,
|
||||
qualityScore: qualityScore.score,
|
||||
qualityReasons: qualityScore.reasons
|
||||
})
|
||||
@@ -191,7 +191,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
|
||||
console.log(`✅ Risk check PASSED: All checks passed`, {
|
||||
todayPnL: todayPnL.toFixed(2),
|
||||
tradesLastHour: tradesInLastHour,
|
||||
cooldownPassed: lastTradeTime ? 'yes' : 'no previous trades',
|
||||
cooldownPassed: lastTradeTimeForSymbol ? 'yes' : `no previous ${body.symbol} trades`,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -276,6 +276,26 @@ export async function getLastTradeTime(): Promise<Date | null> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recent trade time for a specific symbol
|
||||
*/
|
||||
export async function getLastTradeTimeForSymbol(symbol: string): Promise<Date | null> {
|
||||
const prisma = getPrismaClient()
|
||||
|
||||
try {
|
||||
const lastTrade = await prisma.trade.findFirst({
|
||||
where: { symbol },
|
||||
orderBy: { entryTime: 'desc' },
|
||||
select: { entryTime: true },
|
||||
})
|
||||
|
||||
return lastTrade?.entryTime || null
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to get last trade time for ${symbol}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recent trade with full details
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user