fix: detect exit reason using trade state flags instead of current price
CRITICAL BUG: Position Manager was using current price to determine exit reason, but on-chain orders filled at a DIFFERENT price in the past! Example: LONG entry $184.55, TP1 filled at $184.66, but when Position Manager checked later (price dropped), it saw currentPrice < TP1 and defaulted to 'SL' Result: Profitable trades incorrectly labeled as SL exits in database Fix: - Use trade.tp1Hit and trade.tp2Hit flags to determine exit reason - If no TP flags set, use realized P&L to distinguish: - Profit >0.5% = TP1 filled - Negative P&L = SL filled - Remove duplicate P&L calculation This ensures exit reasons match actual on-chain order fills
This commit is contained in:
@@ -407,29 +407,11 @@ export class PositionManager {
|
||||
// Save currentSize before it becomes 0
|
||||
const sizeBeforeClosure = trade.currentSize
|
||||
|
||||
// Determine exit reason based on price
|
||||
// Determine exit reason based on TP flags and realized P&L
|
||||
// CRITICAL: Use trade state flags, not current price (on-chain orders filled in the past!)
|
||||
let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' = 'SL'
|
||||
|
||||
if (trade.direction === 'long') {
|
||||
if (currentPrice >= trade.tp2Price) {
|
||||
exitReason = 'TP2'
|
||||
} else if (currentPrice >= trade.tp1Price) {
|
||||
exitReason = 'TP1'
|
||||
} else if (currentPrice <= trade.stopLossPrice) {
|
||||
exitReason = 'HARD_SL' // Assume hard stop if below SL
|
||||
}
|
||||
} else {
|
||||
// Short
|
||||
if (currentPrice <= trade.tp2Price) {
|
||||
exitReason = 'TP2'
|
||||
} else if (currentPrice <= trade.tp1Price) {
|
||||
exitReason = 'TP1'
|
||||
} else if (currentPrice >= trade.stopLossPrice) {
|
||||
exitReason = 'HARD_SL' // Assume hard stop if above SL
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate final P&L using size BEFORE closure
|
||||
// Calculate P&L first
|
||||
const profitPercent = this.calculateProfitPercent(
|
||||
trade.entryPrice,
|
||||
currentPrice,
|
||||
@@ -438,6 +420,27 @@ export class PositionManager {
|
||||
const accountPnL = profitPercent * trade.leverage
|
||||
const realizedPnL = (sizeBeforeClosure * accountPnL) / 100
|
||||
|
||||
// Determine exit reason from trade state and P&L
|
||||
if (trade.tp2Hit) {
|
||||
// TP2 was hit, full position closed (runner stopped or hit target)
|
||||
exitReason = 'TP2'
|
||||
} else if (trade.tp1Hit) {
|
||||
// TP1 was hit, position should be 25% size, but now fully closed
|
||||
// This means either TP2 filled or runner got stopped out
|
||||
exitReason = realizedPnL > 0 ? 'TP2' : 'SL'
|
||||
} else {
|
||||
// No TPs hit yet - either SL or TP1 filled just now
|
||||
// Use P&L to determine: positive = TP, negative = SL
|
||||
if (realizedPnL > trade.positionSize * 0.005) {
|
||||
// More than 0.5% profit - must be TP1
|
||||
exitReason = 'TP1'
|
||||
} else if (realizedPnL < 0) {
|
||||
// Loss - must be SL
|
||||
exitReason = 'SL'
|
||||
}
|
||||
// else: small profit/loss near breakeven, default to SL (could be manual close)
|
||||
}
|
||||
|
||||
// Update database
|
||||
const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000)
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user