Files
trading_bot_v4/app/api/trading/reduce-position/route.ts
mindesbunister a07bf9f4b2 Add position reduction feature via Telegram
- New endpoint: /api/trading/reduce-position to take partial profits
- Closes specified percentage at market price
- Recalculates and places new TP/SL orders for remaining size
- Entry price stays the same, only size is reduced
- Telegram command: /reduce [percent] (default 50%, range 10-90%)
- Shows realized P&L from the closed portion
- Example: /reduce 25 closes 25% and updates orders for remaining 75%
2025-10-27 20:34:47 +01:00

204 lines
6.5 KiB
TypeScript

/**
* Reduce Position API Endpoint
*
* Partially closes a position and recalculates TP/SL orders
* POST /api/trading/reduce-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 { closePosition, placeExitOrders, cancelAllOrders } from '@/lib/drift/orders'
interface ReducePositionRequest {
tradeId: string
reducePercent?: number // 25 = close 25%, 50 = close 50%
}
interface ReducePositionResponse {
success: boolean
message: string
closedSize?: number
remainingSize?: number
closePrice?: number
realizedPnL?: number
newTP1?: number
newTP2?: number
newSL?: number
}
export async function POST(request: NextRequest): Promise<NextResponse<ReducePositionResponse>> {
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: ReducePositionRequest = await request.json()
console.log('📉 Reducing position:', body)
if (!body.tradeId) {
return NextResponse.json(
{
success: false,
message: 'tradeId is required',
},
{ status: 400 }
)
}
const reducePercent = body.reducePercent || 50 // Default: close 50%
if (reducePercent < 10 || reducePercent > 90) {
return NextResponse.json(
{
success: false,
message: 'Reduce percent must be between 10 and 90',
},
{ status: 400 }
)
}
// 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(` Reducing by: ${reducePercent}%`)
// Initialize Drift service
const driftService = await initializeDriftService()
// Close portion of position at market
console.log(`💰 Closing ${reducePercent}% of position...`)
const closeResult = await closePosition({
symbol: trade.symbol,
percentToClose: reducePercent,
slippageTolerance: config.slippageTolerance,
})
if (!closeResult.success || !closeResult.closePrice) {
throw new Error(`Failed to close position: ${closeResult.error}`)
}
console.log(`✅ Closed at $${closeResult.closePrice}`)
console.log(`💵 Realized P&L: $${closeResult.realizedPnL || 0}`)
// Calculate remaining position size
const remainingPercent = 100 - reducePercent
const remainingSizeUSD = (trade.positionSize * remainingPercent) / 100
console.log(`📊 Remaining position: $${remainingSizeUSD} (${remainingPercent}%)`)
// 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
}
// Calculate TP/SL prices (entry price stays the same)
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(trade.entryPrice, config.takeProfit1Percent, trade.direction)
const newTP2 = calculatePrice(trade.entryPrice, config.takeProfit2Percent, trade.direction)
const newSL = calculatePrice(trade.entryPrice, config.stopLossPercent, trade.direction)
console.log(`🎯 New targets (same entry, reduced size):`)
console.log(` TP1: $${newTP1} (${config.takeProfit1Percent}%)`)
console.log(` TP2: $${newTP2} (${config.takeProfit2Percent}%)`)
console.log(` SL: $${newSL} (${config.stopLossPercent}%)`)
// Place new exit orders with reduced size
console.log('📝 Placing new TP/SL orders...')
const exitOrders = await placeExitOrders({
symbol: trade.symbol,
direction: trade.direction,
positionSizeUSD: remainingSizeUSD,
tp1Price: newTP1,
tp2Price: newTP2,
stopLossPrice: newSL,
tp1SizePercent: config.takeProfit1SizePercent,
tp2SizePercent: config.takeProfit2SizePercent,
useDualStops: config.useDualStops,
softStopPrice: config.useDualStops ? calculatePrice(trade.entryPrice, config.softStopPercent, trade.direction) : undefined,
softStopBuffer: config.useDualStops ? config.softStopBuffer : undefined,
hardStopPrice: config.useDualStops ? calculatePrice(trade.entryPrice, config.hardStopPercent, trade.direction) : undefined,
})
console.log(`✅ New exit orders placed`)
// Update Position Manager with new values
trade.positionSize = remainingSizeUSD
trade.currentSize = remainingSizeUSD
trade.realizedPnL += closeResult.realizedPnL || 0
// Update prices (stay the same but refresh)
trade.tp1Price = newTP1
trade.tp2Price = newTP2
trade.stopLossPrice = newSL
console.log(`💾 Updated Position Manager`)
return NextResponse.json({
success: true,
message: `Reduced position by ${reducePercent}% - Remaining: $${remainingSizeUSD.toFixed(0)}`,
closedSize: (trade.positionSize * reducePercent) / 100,
remainingSize: remainingSizeUSD,
closePrice: closeResult.closePrice,
realizedPnL: closeResult.realizedPnL,
newTP1: newTP1,
newTP2: newTP2,
newSL: newSL,
})
} catch (error) {
console.error('❌ Reduce position error:', error)
return NextResponse.json(
{
success: false,
message: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
)
}
}