Files
trading_bot_v4/app/api/trading/scale-position/route.ts
mindesbunister 797e80b56a CRITICAL FIX: TP/SL orders using wrong size calculation
**ROOT CAUSE:** placeExitOrders() calculated position size using TP/SL prices instead of entry price

**Problem:**
- TP1 order size: 85 / TP1_price (00.746) = 2.914 SOL
- Actual position: 80 / entry_price (99.946) = 3.901 SOL
- TP1 should close: 3.901 * 75% = 2.926 SOL
- But it only closed: 2.914 SOL = 74.7%  WRONG!

**Result:** TP1 closed ~25% instead of 75%, no runner left

**Fix:**
- Changed usdToBase() to use entryPrice for ALL size calculations
- Added entryPrice param to PlaceExitOrdersOptions interface
- Updated all API routes to pass entryPrice

**Testing:** Next trade will have correctly sized TP/SL orders
2025-10-29 17:34:10 +01:00

226 lines
7.1 KiB
TypeScript

/**
* Scale Position API Endpoint
*
* Adds to an existing position and recalculates TP/SL orders
* POST /api/trading/scale-position
*/
import { NextRequest, NextResponse } from 'next/server'
import { getMergedConfig } from '@/config/trading'
import { getInitializedPositionManager } from '@/lib/trading/position-manager'
import { initializeDriftService } from '@/lib/drift/client'
import { openPosition, placeExitOrders, cancelAllOrders } from '@/lib/drift/orders'
interface ScalePositionRequest {
tradeId: string
scalePercent?: number // 50 = add 50%, 100 = double position
}
interface ScalePositionResponse {
success: boolean
message: string
oldEntry?: number
newEntry?: number
oldSize?: number
newSize?: number
newTP1?: number
newTP2?: number
newSL?: number
}
export async function POST(request: NextRequest): Promise<NextResponse<ScalePositionResponse>> {
try {
// Verify authorization
const authHeader = request.headers.get('authorization')
const expectedAuth = `Bearer ${process.env.API_SECRET_KEY}`
if (!authHeader || authHeader !== expectedAuth) {
return NextResponse.json(
{
success: false,
message: 'Unauthorized',
},
{ status: 401 }
)
}
const body: ScalePositionRequest = await request.json()
console.log('📈 Scaling position:', body)
if (!body.tradeId) {
return NextResponse.json(
{
success: false,
message: 'tradeId is required',
},
{ status: 400 }
)
}
const scalePercent = body.scalePercent || 50 // Default: add 50%
// Get current configuration
const config = getMergedConfig()
// Get Position Manager
const positionManager = await getInitializedPositionManager()
const activeTrades = positionManager.getActiveTrades()
const trade = activeTrades.find(t => t.id === body.tradeId)
if (!trade) {
return NextResponse.json(
{
success: false,
message: `Position ${body.tradeId} not found`,
},
{ status: 404 }
)
}
console.log(`📊 Current position: ${trade.symbol} ${trade.direction}`)
console.log(` Entry: $${trade.entryPrice}`)
console.log(` Size: ${trade.currentSize} (${trade.positionSize} USD)`)
console.log(` Scaling by: ${scalePercent}%`)
// Initialize Drift service
const driftService = await initializeDriftService()
// Check account health before scaling
const healthData = await driftService.getAccountHealth()
const healthPercent = healthData.marginRatio
console.log(`💊 Account health: ${healthPercent}%`)
if (healthPercent < 30) {
return NextResponse.json(
{
success: false,
message: `Account health too low (${healthPercent}%) to scale position`,
},
{ status: 400 }
)
}
// Calculate additional position size
const additionalSizeUSD = (trade.positionSize * scalePercent) / 100
console.log(`💰 Adding $${additionalSizeUSD} to position...`)
// Open additional position at market
const addResult = await openPosition({
symbol: trade.symbol,
direction: trade.direction,
sizeUSD: additionalSizeUSD,
slippageTolerance: config.slippageTolerance,
})
if (!addResult.success || !addResult.fillPrice) {
throw new Error(`Failed to open additional position: ${addResult.error}`)
}
console.log(`✅ Additional position opened at $${addResult.fillPrice}`)
// Calculate new average entry price
const oldTotalValue = trade.positionSize
const newTotalValue = oldTotalValue + additionalSizeUSD
const oldEntry = trade.entryPrice
const newEntryContribution = addResult.fillPrice
// Weighted average: (old_size * old_price + new_size * new_price) / total_size
const newAvgEntry = (
(oldTotalValue * oldEntry) + (additionalSizeUSD * newEntryContribution)
) / newTotalValue
console.log(`📊 New average entry: $${oldEntry}$${newAvgEntry}`)
console.log(`📊 New position size: $${oldTotalValue}$${newTotalValue}`)
// Cancel all existing exit orders
console.log('🗑️ Cancelling old TP/SL orders...')
try {
await cancelAllOrders(trade.symbol)
console.log('✅ Old orders cancelled')
} catch (cancelError) {
console.error('⚠️ Failed to cancel orders:', cancelError)
// Continue anyway - might not have any orders
}
// Calculate new TP/SL prices based on new average entry
const calculatePrice = (entry: number, percent: number, direction: 'long' | 'short') => {
if (direction === 'long') {
return entry * (1 + percent / 100)
} else {
return entry * (1 - percent / 100)
}
}
const newTP1 = calculatePrice(newAvgEntry, config.takeProfit1Percent, trade.direction)
const newTP2 = calculatePrice(newAvgEntry, config.takeProfit2Percent, trade.direction)
const newSL = calculatePrice(newAvgEntry, config.stopLossPercent, trade.direction)
console.log(`🎯 New targets:`)
console.log(` TP1: $${newTP1} (${config.takeProfit1Percent}%)`)
console.log(` TP2: $${newTP2} (${config.takeProfit2Percent}%)`)
console.log(` SL: $${newSL} (${config.stopLossPercent}%)`)
// Place new exit orders
console.log('📝 Placing new TP/SL orders...')
const exitOrders = await placeExitOrders({
symbol: trade.symbol,
direction: trade.direction,
positionSizeUSD: newTotalValue,
entryPrice: newAvgEntry,
tp1Price: newTP1,
tp2Price: newTP2,
stopLossPrice: newSL,
tp1SizePercent: config.takeProfit1SizePercent,
tp2SizePercent: config.takeProfit2SizePercent,
useDualStops: config.useDualStops,
softStopPrice: config.useDualStops ? calculatePrice(newAvgEntry, config.softStopPercent, trade.direction) : undefined,
softStopBuffer: config.useDualStops ? config.softStopBuffer : undefined,
hardStopPrice: config.useDualStops ? calculatePrice(newAvgEntry, config.hardStopPercent, trade.direction) : undefined,
})
console.log(`✅ New exit orders placed`)
// Update Position Manager with new values
trade.entryPrice = newAvgEntry
trade.positionSize = newTotalValue
trade.currentSize = newTotalValue
trade.tp1Price = newTP1
trade.tp2Price = newTP2
trade.stopLossPrice = newSL
// Reset tracking values
trade.tp1Hit = false
trade.slMovedToBreakeven = false
trade.slMovedToProfit = false
trade.peakPnL = 0
trade.peakPrice = newAvgEntry
console.log(`💾 Updated Position Manager`)
return NextResponse.json({
success: true,
message: `Position scaled by ${scalePercent}% - New entry: $${newAvgEntry.toFixed(2)}`,
oldEntry: oldEntry,
newEntry: newAvgEntry,
oldSize: oldTotalValue,
newSize: newTotalValue,
newTP1: newTP1,
newTP2: newTP2,
newSL: newSL,
})
} catch (error) {
console.error('❌ Scale position error:', error)
return NextResponse.json(
{
success: false,
message: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
)
}
}