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:
mindesbunister
2025-11-03 08:01:30 +01:00
parent 0ea8773bdc
commit 0ed2e89c7e
2 changed files with 30 additions and 10 deletions

View File

@@ -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({