- Added /close Telegram command for full position closure - Updated /reduce to accept 10-100% (was 10-90%) - Implemented auto-flip logic: automatically closes opposite position when signal reverses - Fixed risk check to allow opposite direction trades (signal flips) - Enhanced Position Manager to cancel orders when removing trades - Added startup initialization for Position Manager (restores trades on restart) - Fixed analytics to show stopped-out trades (manual DB update for orphaned trade) - Updated reduce endpoint to route 100% closes through closePosition for proper cleanup - All position closures now guarantee TP/SL order cancellation on Drift
108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
/**
|
|
* Risk Check API Endpoint
|
|
*
|
|
* Called by n8n workflow before executing trade
|
|
* POST /api/trading/check-risk
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getMergedConfig } from '@/config/trading'
|
|
import { getInitializedPositionManager } from '@/lib/trading/position-manager'
|
|
|
|
export interface RiskCheckRequest {
|
|
symbol: string
|
|
direction: 'long' | 'short'
|
|
}
|
|
|
|
export interface RiskCheckResponse {
|
|
allowed: boolean
|
|
reason?: string
|
|
details?: string
|
|
}
|
|
|
|
export async function POST(request: NextRequest): Promise<NextResponse<RiskCheckResponse>> {
|
|
try {
|
|
// Verify authorization
|
|
const authHeader = request.headers.get('authorization')
|
|
const expectedAuth = `Bearer ${process.env.API_SECRET_KEY}`
|
|
|
|
if (!authHeader || authHeader !== expectedAuth) {
|
|
return NextResponse.json(
|
|
{
|
|
allowed: false,
|
|
reason: 'Unauthorized',
|
|
},
|
|
{ status: 401 }
|
|
)
|
|
}
|
|
|
|
const body: RiskCheckRequest = await request.json()
|
|
|
|
console.log('🔍 Risk check for:', body)
|
|
|
|
const config = getMergedConfig()
|
|
|
|
// Check for existing positions on the same symbol
|
|
const positionManager = await getInitializedPositionManager()
|
|
const existingTrades = Array.from(positionManager.getActiveTrades().values())
|
|
const existingPosition = existingTrades.find(trade => trade.symbol === body.symbol)
|
|
|
|
if (existingPosition) {
|
|
// Check if it's the SAME direction (duplicate - block it)
|
|
if (existingPosition.direction === body.direction) {
|
|
console.log('🚫 Risk check BLOCKED: Duplicate position (same direction)', {
|
|
symbol: body.symbol,
|
|
existingDirection: existingPosition.direction,
|
|
requestedDirection: body.direction,
|
|
existingEntry: existingPosition.entryPrice,
|
|
})
|
|
|
|
return NextResponse.json({
|
|
allowed: false,
|
|
reason: 'Duplicate position',
|
|
details: `Already have ${existingPosition.direction} position on ${body.symbol} (entry: $${existingPosition.entryPrice})`,
|
|
})
|
|
}
|
|
|
|
// OPPOSITE direction - this is a signal flip/reversal (ALLOW IT)
|
|
console.log('🔄 Risk check: Signal flip detected', {
|
|
symbol: body.symbol,
|
|
existingDirection: existingPosition.direction,
|
|
newDirection: body.direction,
|
|
note: 'Will close existing and open opposite',
|
|
})
|
|
|
|
return NextResponse.json({
|
|
allowed: true,
|
|
reason: 'Signal flip',
|
|
details: `Signal reversed from ${existingPosition.direction} to ${body.direction} - will flip position`,
|
|
})
|
|
}
|
|
|
|
// TODO: Implement additional risk checks:
|
|
// 1. Check daily drawdown
|
|
// 2. Check trades per hour limit
|
|
// 3. Check cooldown period
|
|
// 4. Check account health
|
|
|
|
console.log(`✅ Risk check PASSED: No existing positions`)
|
|
|
|
return NextResponse.json({
|
|
allowed: true,
|
|
details: 'All risk checks passed',
|
|
})
|
|
|
|
} catch (error) {
|
|
console.error('❌ Risk check error:', error)
|
|
|
|
return NextResponse.json(
|
|
{
|
|
allowed: false,
|
|
reason: 'Risk check failed',
|
|
details: error instanceof Error ? error.message : 'Unknown error',
|
|
},
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|