/** * Test Trade API Endpoint * * Executes a test trade with current settings (no authentication required from settings page) * POST /api/trading/test */ import { NextRequest, NextResponse } from 'next/server' import { initializeDriftService } from '@/lib/drift/client' import { openPosition, placeExitOrders } from '@/lib/drift/orders' import { normalizeTradingViewSymbol } from '@/config/trading' import { getMergedConfig } from '@/config/trading' import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager' import { createTrade } from '@/lib/database/trades' export interface TestTradeRequest { symbol?: string // Default: SOLUSDT direction?: 'long' | 'short' // Default: long } export interface TestTradeResponse { success: boolean positionId?: string symbol?: string direction?: 'long' | 'short' entryPrice?: number positionSize?: number stopLoss?: number takeProfit1?: number takeProfit2?: number softStopPrice?: number hardStopPrice?: number useDualStops?: boolean timestamp?: string error?: string message?: string } export async function POST(request: NextRequest): Promise> { try { // Parse request body const body: TestTradeRequest = await request.json().catch(() => ({})) const symbol = body.symbol || 'SOLUSDT' const direction = body.direction || 'long' console.log('๐Ÿงช Test trade request:', { symbol, direction }) // Normalize symbol const driftSymbol = normalizeTradingViewSymbol(symbol) console.log(`๐Ÿ“Š Normalized symbol: ${symbol} โ†’ ${driftSymbol}`) // Get trading configuration const config = getMergedConfig() // Get symbol-specific position sizing const { getPositionSizeForSymbol } = await import('@/config/trading') const { size: positionSize, leverage, enabled } = getPositionSizeForSymbol(driftSymbol, config) // Check if trading is enabled for this symbol if (!enabled) { console.log(`โ›” Trading disabled for ${driftSymbol}`) return NextResponse.json( { success: false, error: 'Symbol trading disabled', message: `Trading is currently disabled for ${driftSymbol}. Enable it in settings.`, }, { status: 400 } ) } console.log(`๐Ÿ“ Symbol-specific sizing for ${driftSymbol}:`) console.log(` Enabled: ${enabled}`) console.log(` Position size: $${positionSize}`) console.log(` Leverage: ${leverage}x`) // Initialize Drift service if not already initialized const driftService = await initializeDriftService() // Check account health before trading const health = await driftService.getAccountHealth() console.log('๐Ÿ’Š Account health:', health) if (health.freeCollateral <= 0) { return NextResponse.json( { success: false, error: 'Insufficient collateral', message: `Free collateral: $${health.freeCollateral.toFixed(2)}`, }, { status: 400 } ) } // Calculate position size with leverage const positionSizeUSD = positionSize * leverage console.log(`๐Ÿ’ฐ Opening ${direction} position:`) console.log(` Symbol: ${driftSymbol}`) console.log(` Base size: $${positionSize}`) console.log(` Leverage: ${leverage}x`) console.log(` Total position: $${positionSizeUSD}`) // Open position const openResult = await openPosition({ symbol: driftSymbol, direction: direction, sizeUSD: positionSizeUSD, slippageTolerance: config.slippageTolerance, }) if (!openResult.success) { return NextResponse.json( { success: false, error: 'Position open failed', message: openResult.error, }, { status: 500 } ) } // Calculate stop loss and take profit prices const entryPrice = openResult.fillPrice! const stopLossPrice = calculatePrice( entryPrice, config.stopLossPercent, direction ) // Calculate dual stop prices if enabled let softStopPrice: number | undefined let hardStopPrice: number | undefined if (config.useDualStops) { softStopPrice = calculatePrice( entryPrice, config.softStopPercent, direction ) hardStopPrice = calculatePrice( entryPrice, config.hardStopPercent, direction ) console.log('๐Ÿ›ก๏ธ๐Ÿ›ก๏ธ Dual stop system enabled:') console.log(` Soft stop: $${softStopPrice.toFixed(4)} (${config.softStopPercent}%)`) console.log(` Hard stop: $${hardStopPrice.toFixed(4)} (${config.hardStopPercent}%)`) } const tp1Price = calculatePrice( entryPrice, config.takeProfit1Percent, direction ) const tp2Price = calculatePrice( entryPrice, config.takeProfit2Percent, direction ) console.log('๐Ÿ“Š Trade targets:') console.log(` Entry: $${entryPrice.toFixed(4)}`) console.log(` SL: $${stopLossPrice.toFixed(4)} (${config.stopLossPercent}%)`) console.log(` TP1: $${tp1Price.toFixed(4)} (${config.takeProfit1Percent}%)`) console.log(` TP2: $${tp2Price.toFixed(4)} (${config.takeProfit2Percent}%)`) // Calculate emergency stop const emergencyStopPrice = calculatePrice( entryPrice, config.emergencyStopPercent, direction ) // Create active trade object const activeTrade: ActiveTrade = { id: `test-trade-${Date.now()}`, positionId: openResult.transactionSignature!, symbol: driftSymbol, direction: direction, entryPrice, entryTime: Date.now(), positionSize: positionSizeUSD, leverage: leverage, stopLossPrice, tp1Price, tp2Price, emergencyStopPrice, currentSize: positionSizeUSD, tp1Hit: false, tp2Hit: false, slMovedToBreakeven: false, slMovedToProfit: false, trailingStopActive: false, realizedPnL: 0, unrealizedPnL: 0, peakPnL: 0, peakPrice: entryPrice, // MAE/MFE tracking maxFavorableExcursion: 0, maxAdverseExcursion: 0, maxFavorablePrice: entryPrice, maxAdversePrice: entryPrice, priceCheckCount: 0, lastPrice: entryPrice, lastUpdateTime: Date.now(), } // Add to position manager for monitoring const positionManager = await getInitializedPositionManager() await positionManager.addTrade(activeTrade) console.log('โœ… Trade added to position manager for monitoring') // Create response object const response: TestTradeResponse = { success: true, positionId: openResult.transactionSignature, symbol: driftSymbol, direction: direction, entryPrice: entryPrice, positionSize: positionSizeUSD, stopLoss: stopLossPrice, takeProfit1: tp1Price, takeProfit2: tp2Price, softStopPrice: softStopPrice, hardStopPrice: hardStopPrice, useDualStops: config.useDualStops, timestamp: new Date().toISOString(), } // Place on-chain TP/SL orders so they appear in Drift UI let exitOrderSignatures: string[] = [] try { const exitRes = await placeExitOrders({ symbol: driftSymbol, positionSizeUSD: positionSizeUSD, entryPrice: entryPrice, tp1Price, tp2Price, stopLossPrice, tp1SizePercent: config.takeProfit1SizePercent || 50, tp2SizePercent: config.takeProfit2SizePercent || 100, direction: direction, // Dual stop parameters useDualStops: config.useDualStops, softStopPrice: softStopPrice, softStopBuffer: config.softStopBuffer, hardStopPrice: hardStopPrice, }) if (!exitRes.success) { console.error('โŒ Failed to place on-chain exit orders:', exitRes.error) } else { console.log('๐Ÿ“จ Exit orders placed on-chain:', exitRes.signatures) exitOrderSignatures = exitRes.signatures || [] } // Attach signatures to response when available if (exitRes.signatures && exitRes.signatures.length > 0) { ;(response as any).exitOrderSignatures = exitRes.signatures } } catch (err) { console.error('โŒ Unexpected error placing exit orders:', err) } // Save trade to database try { await createTrade({ positionId: openResult.transactionSignature!, symbol: driftSymbol, direction: direction, entryPrice, positionSizeUSD: positionSizeUSD, leverage: leverage, stopLossPrice, takeProfit1Price: tp1Price, takeProfit2Price: tp2Price, tp1SizePercent: config.takeProfit1SizePercent || 50, tp2SizePercent: config.takeProfit2SizePercent || 100, configSnapshot: config, entryOrderTx: openResult.transactionSignature!, tp1OrderTx: exitOrderSignatures[0], tp2OrderTx: exitOrderSignatures[1], slOrderTx: config.useDualStops ? undefined : exitOrderSignatures[2], softStopOrderTx: config.useDualStops ? exitOrderSignatures[2] : undefined, hardStopOrderTx: config.useDualStops ? exitOrderSignatures[3] : undefined, softStopPrice, hardStopPrice, signalStrength: 'test', timeframe: 'manual', }) console.log('๐Ÿ’พ Trade saved to database') } catch (dbError) { console.error('โŒ Failed to save trade to database:', dbError) // Don't fail the trade if database save fails } console.log('โœ… Test trade executed successfully!') return NextResponse.json(response) } catch (error) { console.error('โŒ Test trade execution error:', error) return NextResponse.json( { success: false, error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error', }, { status: 500 } ) } } /** * Helper function to calculate price based on percentage */ function calculatePrice( entryPrice: number, percent: number, direction: 'long' | 'short' ): number { if (direction === 'long') { return entryPrice * (1 + percent / 100) } else { return entryPrice * (1 - percent / 100) } }