critical: Fix exit order token sizing - TP/SL now use exact position size
BUG #92: Exit orders (TP1, TP2, SL) had different token sizes than position - Position: 142.91 SOL but TP1=140.87 SOL, SL=147.03 SOL (WRONG) - Root cause: usdToBase() calculated tokens as USD/price per order - Each exit order price produced different token amounts FIX: Pass actual token count via positionSizeTokens parameter - Added positionSizeTokens to PlaceExitOrdersOptions interface - Added tokensToBase() helper (tokens * 1e9 directly) - All exit sections now use token-based calculation when available Files updated to pass positionSizeTokens: - app/api/trading/execute/route.ts: openResult.fillSize - lib/trading/smart-entry-timer.ts: openResult.fillSize - lib/trading/sync-helper.ts: Math.abs(driftPos.size) - lib/trading/position-manager.ts: Math.abs(position.size) + fetch patterns - lib/startup/init-position-manager.ts: Math.abs(position.size) - lib/health/position-manager-health.ts: Drift position fetch + token size Result: When position = X tokens, ALL exit orders close portions of X tokens - TP1: X * tp1SizePercent / 100 tokens - TP2: remaining * tp2SizePercent / 100 tokens - SL: X tokens (full position) Backward compatible: Falls back to USD calculation if positionSizeTokens not provided
This commit is contained in:
@@ -848,6 +848,7 @@ export class PositionManager {
|
||||
const placeResult = await placeExitOrders({
|
||||
symbol: trade.symbol,
|
||||
positionSizeUSD: trade.currentSize,
|
||||
positionSizeTokens: Math.abs(position.size), // CRITICAL FIX (Jan 6, 2026): Use actual token count
|
||||
entryPrice: trade.entryPrice,
|
||||
tp1Price: trade.tp2Price || trade.entryPrice * (trade.direction === 'long' ? 1.02 : 0.98),
|
||||
tp2Price: trade.tp2Price || trade.entryPrice * (trade.direction === 'long' ? 1.04 : 0.96),
|
||||
@@ -911,6 +912,7 @@ export class PositionManager {
|
||||
direction: trade.direction,
|
||||
entryPrice: trade.entryPrice,
|
||||
positionSizeUSD: trade.currentSize, // Runner size
|
||||
positionSizeTokens: Math.abs(position.size), // CRITICAL FIX (Jan 6, 2026): Use actual token count
|
||||
stopLossPrice: trade.stopLossPrice, // At breakeven now
|
||||
tp1Price: trade.tp2Price, // TP2 becomes new TP1 for runner
|
||||
tp2Price: 0, // No TP2 for runner
|
||||
@@ -1580,6 +1582,22 @@ export class PositionManager {
|
||||
if (cancelResult.success) {
|
||||
logger.log(`✅ Cancelled ${cancelResult.cancelledCount || 0} old orders`)
|
||||
|
||||
// CRITICAL FIX (Jan 6, 2026): Get current position tokens for accurate SL sizing
|
||||
let currentPositionTokens: number | undefined
|
||||
try {
|
||||
const driftService = getDriftService()
|
||||
if (driftService && (driftService as any).isInitialized) {
|
||||
const marketConfig = getMarketConfig(trade.symbol)
|
||||
const position = await driftService.getPosition(marketConfig.driftMarketIndex)
|
||||
if (position && Math.abs(position.size) > 0.01) {
|
||||
currentPositionTokens = Math.abs(position.size)
|
||||
logger.log(`📊 Current position size for SL: ${currentPositionTokens.toFixed(4)} tokens`)
|
||||
}
|
||||
}
|
||||
} catch (posError) {
|
||||
console.warn('⚠️ Could not fetch position for token size, using USD fallback')
|
||||
}
|
||||
|
||||
// Place ONLY new SL orders at breakeven/profit level for remaining position
|
||||
// DO NOT place TP2 order - trailing stop is software-only (Position Manager monitors)
|
||||
logger.log(`🛡️ Placing only SL orders at $${newStopLossPrice.toFixed(4)} for remaining position...`)
|
||||
@@ -1587,6 +1605,7 @@ export class PositionManager {
|
||||
const exitOrdersResult = await placeExitOrders({
|
||||
symbol: trade.symbol,
|
||||
positionSizeUSD: trade.currentSize,
|
||||
positionSizeTokens: currentPositionTokens, // CRITICAL FIX (Jan 6, 2026): Use actual token count
|
||||
entryPrice: trade.entryPrice,
|
||||
tp1Price: trade.tp2Price, // Dummy value, won't be used (tp1SizePercent=0)
|
||||
tp2Price: trade.tp2Price, // Dummy value, won't be used (tp2SizePercent=0)
|
||||
@@ -1683,6 +1702,22 @@ export class PositionManager {
|
||||
if (cancelResult.success) {
|
||||
logger.log(`✅ Old SL orders cancelled`)
|
||||
|
||||
// CRITICAL FIX (Jan 6, 2026): Get current position tokens for accurate SL sizing
|
||||
let currentPositionTokens: number | undefined
|
||||
try {
|
||||
const driftService = getDriftService()
|
||||
if (driftService && (driftService as any).isInitialized) {
|
||||
const marketConfig = getMarketConfig(trade.symbol)
|
||||
const position = await driftService.getPosition(marketConfig.driftMarketIndex)
|
||||
if (position && Math.abs(position.size) > 0.01) {
|
||||
currentPositionTokens = Math.abs(position.size)
|
||||
logger.log(`📊 Current position size for trailing SL: ${currentPositionTokens.toFixed(4)} tokens`)
|
||||
}
|
||||
}
|
||||
} catch (posError) {
|
||||
console.warn('⚠️ Could not fetch position for token size, using USD fallback')
|
||||
}
|
||||
|
||||
// Calculate initial trailing SL price
|
||||
const trailingDistancePercent = this.config.trailingStopPercent || 0.5
|
||||
const initialTrailingSL = this.calculatePrice(
|
||||
@@ -1697,6 +1732,7 @@ export class PositionManager {
|
||||
const exitOrdersResult = await placeExitOrders({
|
||||
symbol: trade.symbol,
|
||||
positionSizeUSD: trade.currentSize,
|
||||
positionSizeTokens: currentPositionTokens, // CRITICAL FIX (Jan 6, 2026): Use actual token count
|
||||
entryPrice: trade.entryPrice,
|
||||
tp1Price: trade.tp2Price, // No TP1 (already hit)
|
||||
tp2Price: trade.tp2Price, // No TP2 (trigger only)
|
||||
@@ -1894,10 +1930,27 @@ export class PositionManager {
|
||||
if (cancelResult.success) {
|
||||
logger.log(`✅ Old SL orders cancelled`)
|
||||
|
||||
// Fetch current position size in tokens for accurate SL sizing
|
||||
let currentPositionTokens: number | undefined
|
||||
try {
|
||||
const driftService = getDriftService()
|
||||
if (driftService && (driftService as any).isInitialized) {
|
||||
const marketConfig = getMarketConfig(trade.symbol)
|
||||
const position = await driftService.getPosition(marketConfig.driftMarketIndex)
|
||||
if (position && Math.abs(position.size) > 0.01) {
|
||||
currentPositionTokens = Math.abs(position.size)
|
||||
logger.log(`📊 Current position size for trailing SL: ${currentPositionTokens.toFixed(4)} tokens`)
|
||||
}
|
||||
}
|
||||
} catch (posError) {
|
||||
console.warn('⚠️ Could not fetch position for trailing SL token size, using USD fallback')
|
||||
}
|
||||
|
||||
// Place new SL orders at trailing stop price
|
||||
const exitOrdersResult = await placeExitOrders({
|
||||
symbol: trade.symbol,
|
||||
positionSizeUSD: trade.currentSize,
|
||||
positionSizeTokens: currentPositionTokens,
|
||||
entryPrice: trade.entryPrice,
|
||||
tp1Price: trade.tp2Price, // No TP1 (already hit)
|
||||
tp2Price: trade.tp2Price, // No TP2 (already hit)
|
||||
|
||||
Reference in New Issue
Block a user