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
|
// Save currentSize before it becomes 0
|
||||||
const sizeBeforeClosure = trade.currentSize
|
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'
|
let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' = 'SL'
|
||||||
|
|
||||||
if (trade.direction === 'long') {
|
// Calculate P&L first
|
||||||
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
|
|
||||||
const profitPercent = this.calculateProfitPercent(
|
const profitPercent = this.calculateProfitPercent(
|
||||||
trade.entryPrice,
|
trade.entryPrice,
|
||||||
currentPrice,
|
currentPrice,
|
||||||
@@ -438,6 +420,27 @@ export class PositionManager {
|
|||||||
const accountPnL = profitPercent * trade.leverage
|
const accountPnL = profitPercent * trade.leverage
|
||||||
const realizedPnL = (sizeBeforeClosure * accountPnL) / 100
|
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
|
// Update database
|
||||||
const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000)
|
const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000)
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user