- Add originalPositionSize tracking to prevent stale size usage - Add price validation to TP1 detection (prevents manual closes misidentified as TP1) - Fix external closure P&L to use originalPositionSize not currentSize - Add handleManualClosure method for proper exit reason detection - Add isPriceAtTarget helper for TP/SL price validation (0.2% tolerance) - Update all ActiveTrade creation points (execute, test, sync-positions, test-db) Bug fixes: - Manual close at 42.34 was detected as TP1 (target 40.71) - FIXED - P&L showed -$1.71 instead of actual -$2.92 - FIXED - Exit reason showed SL instead of manual - FIXED Root cause: Position Manager detected size reduction without validating price was actually at TP1 level. Used stale currentSize for P&L calculation. Files modified: - lib/trading/position-manager.ts (core fixes) - app/api/trading/execute/route.ts - app/api/trading/test/route.ts - app/api/trading/sync-positions/route.ts - app/api/trading/test-db/route.ts
268 lines
8.7 KiB
TypeScript
268 lines
8.7 KiB
TypeScript
/**
|
|
* Test Database Trade Endpoint
|
|
*
|
|
* Creates small test trades to verify database functionality
|
|
* POST /api/trading/test-db
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
import { initializeDriftService } from '@/lib/drift/client'
|
|
import { openPosition, placeExitOrders } from '@/lib/drift/orders'
|
|
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: 'SOL-PERP'
|
|
direction?: 'long' | 'short' // Default: 'long'
|
|
sizeUSD?: number // Default: $10
|
|
leverage?: number // Default: 1x
|
|
}
|
|
|
|
export interface TestTradeResponse {
|
|
success: boolean
|
|
message?: string
|
|
trade?: {
|
|
id: string
|
|
positionId: string
|
|
symbol: string
|
|
direction: 'long' | 'short'
|
|
entryPrice: number
|
|
positionSize: number
|
|
leverage: number
|
|
}
|
|
error?: string
|
|
}
|
|
|
|
export async function POST(request: NextRequest): Promise<NextResponse<TestTradeResponse>> {
|
|
try {
|
|
console.log('🧪 Test trade request received')
|
|
|
|
// Parse request body
|
|
const body: TestTradeRequest = await request.json().catch(() => ({}))
|
|
|
|
// Use minimal settings for test trade
|
|
const symbol = body.symbol || 'SOL-PERP'
|
|
const direction = body.direction || 'long'
|
|
const baseSize = body.sizeUSD || 10 // $10 base collateral
|
|
const leverage = body.leverage || 1 // 1x leverage (safe)
|
|
|
|
// IMPORTANT: For Drift, we pass the BASE size (collateral), not notional
|
|
// Drift internally applies the leverage
|
|
const positionSizeUSD = baseSize // This is the actual collateral/margin used
|
|
const notionalSize = baseSize * leverage // This is what shows in Drift UI
|
|
|
|
console.log(`🧪 Creating TEST trade:`)
|
|
console.log(` Symbol: ${symbol}`)
|
|
console.log(` Direction: ${direction}`)
|
|
console.log(` Collateral: $${baseSize}`)
|
|
console.log(` Leverage: ${leverage}x`)
|
|
console.log(` Notional size: $${notionalSize} (what you'll see in Drift)`)
|
|
console.log(` ⚠️ Marked as TEST TRADE`)
|
|
|
|
// Get base config but override with test settings
|
|
const config = getMergedConfig({
|
|
positionSize: baseSize,
|
|
leverage: leverage,
|
|
stopLossPercent: -1.5,
|
|
takeProfit1Percent: 0.7,
|
|
takeProfit2Percent: 1.5,
|
|
})
|
|
|
|
// Initialize Drift service
|
|
const driftService = await initializeDriftService()
|
|
|
|
// Check account health
|
|
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 }
|
|
)
|
|
}
|
|
|
|
// Open position
|
|
const openResult = await openPosition({
|
|
symbol,
|
|
direction,
|
|
sizeUSD: positionSizeUSD,
|
|
slippageTolerance: config.slippageTolerance,
|
|
})
|
|
|
|
if (!openResult.success) {
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: 'Position open failed',
|
|
message: openResult.error,
|
|
},
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
|
|
const entryPrice = openResult.fillPrice!
|
|
|
|
// Calculate exit prices
|
|
const calculatePrice = (entry: number, percent: number, dir: 'long' | 'short') => {
|
|
if (dir === 'long') {
|
|
return entry * (1 + percent / 100)
|
|
} else {
|
|
return entry * (1 - percent / 100)
|
|
}
|
|
}
|
|
|
|
const stopLossPrice = calculatePrice(entryPrice, config.stopLossPercent, direction)
|
|
const tp1Price = calculatePrice(entryPrice, config.takeProfit1Percent, direction)
|
|
const tp2Price = calculatePrice(entryPrice, config.takeProfit2Percent, direction)
|
|
const emergencyStopPrice = calculatePrice(entryPrice, config.emergencyStopPercent, direction)
|
|
|
|
console.log('📊 Test 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}%)`)
|
|
|
|
// Place exit orders
|
|
let exitOrderSignatures: string[] = []
|
|
try {
|
|
const exitRes = await placeExitOrders({
|
|
symbol,
|
|
positionSizeUSD,
|
|
entryPrice,
|
|
tp1Price,
|
|
tp2Price,
|
|
stopLossPrice,
|
|
tp1SizePercent: config.takeProfit1SizePercent || 50,
|
|
tp2SizePercent: config.takeProfit2SizePercent || 100,
|
|
direction,
|
|
useDualStops: config.useDualStops,
|
|
softStopPrice: config.useDualStops ? calculatePrice(entryPrice, config.softStopPercent, direction) : undefined,
|
|
softStopBuffer: config.softStopBuffer,
|
|
hardStopPrice: config.useDualStops ? calculatePrice(entryPrice, config.hardStopPercent, direction) : undefined,
|
|
})
|
|
|
|
if (exitRes.success && exitRes.signatures) {
|
|
exitOrderSignatures = exitRes.signatures
|
|
console.log('📨 Exit orders placed:', exitRes.signatures)
|
|
}
|
|
} catch (err) {
|
|
console.error('❌ Failed to place exit orders:', err)
|
|
}
|
|
|
|
// Create active trade object for position manager
|
|
const activeTrade: ActiveTrade = {
|
|
id: `test-trade-${Date.now()}`,
|
|
positionId: openResult.transactionSignature!,
|
|
symbol,
|
|
direction,
|
|
entryPrice,
|
|
entryTime: Date.now(),
|
|
positionSize: positionSizeUSD,
|
|
leverage,
|
|
stopLossPrice,
|
|
tp1Price,
|
|
tp2Price,
|
|
emergencyStopPrice,
|
|
currentSize: positionSizeUSD,
|
|
originalPositionSize: positionSizeUSD, // Store original size for P&L
|
|
takeProfitPrice1: tp1Price,
|
|
takeProfitPrice2: tp2Price,
|
|
tp1Hit: false,
|
|
tp2Hit: false,
|
|
slMovedToBreakeven: false,
|
|
slMovedToProfit: false,
|
|
trailingStopActive: false,
|
|
realizedPnL: 0,
|
|
unrealizedPnL: 0,
|
|
peakPnL: 0,
|
|
peakPrice: entryPrice,
|
|
maxFavorableExcursion: 0,
|
|
maxAdverseExcursion: 0,
|
|
maxFavorablePrice: entryPrice,
|
|
maxAdversePrice: entryPrice,
|
|
priceCheckCount: 0,
|
|
lastPrice: entryPrice,
|
|
lastUpdateTime: Date.now(),
|
|
}
|
|
|
|
// Add to position manager
|
|
const positionManager = await getInitializedPositionManager()
|
|
await positionManager.addTrade(activeTrade)
|
|
console.log('✅ Test trade added to position manager')
|
|
|
|
// Save to database with TEST flag
|
|
try {
|
|
const dbTrade = await createTrade({
|
|
positionId: openResult.transactionSignature!,
|
|
symbol,
|
|
direction,
|
|
entryPrice,
|
|
entrySlippage: openResult.slippage,
|
|
positionSizeUSD,
|
|
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,
|
|
signalSource: 'test-api',
|
|
signalStrength: 'test',
|
|
timeframe: 'test',
|
|
isTestTrade: true, // Mark as test trade
|
|
})
|
|
|
|
console.log('💾✅ Test trade saved to database with ID:', dbTrade.id)
|
|
console.log('🏷️ Trade marked as TEST - will be excluded from analytics')
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: `✅ Test trade created! Collateral: $${baseSize} | Leverage: ${leverage}x | Notional: $${notionalSize} ${direction} on ${symbol}`,
|
|
trade: {
|
|
id: dbTrade.id,
|
|
positionId: openResult.transactionSignature!,
|
|
symbol,
|
|
direction,
|
|
entryPrice,
|
|
positionSize: positionSizeUSD,
|
|
notionalSize: notionalSize,
|
|
leverage,
|
|
},
|
|
})
|
|
|
|
} catch (dbError) {
|
|
console.error('❌ Failed to save test trade to database:', dbError)
|
|
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: 'Database save failed',
|
|
message: dbError instanceof Error ? dbError.message : 'Unknown database error',
|
|
}, { status: 500 })
|
|
}
|
|
|
|
} 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 }
|
|
)
|
|
}
|
|
}
|