CRITICAL FIX: Use ?? instead of || for tp2SizePercent to allow 0 value

BUG FOUND:
Line 558: tp2SizePercent: config.takeProfit2SizePercent || 100

When config.takeProfit2SizePercent = 0 (TP2-as-runner system), JavaScript's ||
operator treats 0 as falsy and falls back to 100, causing TP2 to close 100%
of remaining position instead of activating trailing stop.

IMPACT:
- On-chain orders placed correctly (line 481 uses ?? correctly)
- Position Manager reads from DB and expects TP2 to close position
- Result: User sees TWO take-profit orders instead of runner system

FIX:
Changed both tp1SizePercent and tp2SizePercent to use ?? operator:
- tp1SizePercent: config.takeProfit1SizePercent ?? 75
- tp2SizePercent: config.takeProfit2SizePercent ?? 0

This allows 0 value to be saved correctly for TP2-as-runner system.

VERIFICATION NEEDED:
Current open SHORT position in database has tp2SizePercent=100 from before
this fix. Next trade will use correct runner system.
This commit is contained in:
mindesbunister
2025-11-10 19:46:03 +01:00
parent 089308a07e
commit c3a053df63
9 changed files with 411 additions and 529 deletions

View File

@@ -293,8 +293,8 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
// For orders that close a long, the order direction should be SHORT (sell)
const orderDirection = options.direction === 'long' ? PositionDirection.SHORT : PositionDirection.LONG
// Place TP1 LIMIT reduce-only (skip if tp1Price is 0 - runner system)
if (tp1USD > 0 && options.tp1Price > 0) {
// Place TP1 LIMIT reduce-only
if (tp1USD > 0) {
const baseAmount = usdToBase(tp1USD)
if (baseAmount >= Math.floor(marketConfig.minOrderSize * 1e9)) {
const orderParams: any = {
@@ -315,8 +315,8 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
}
}
// Place TP2 LIMIT reduce-only (skip if tp2Price is 0 - runner system)
if (tp2USD > 0 && options.tp2Price > 0) {
// Place TP2 LIMIT reduce-only
if (tp2USD > 0) {
const baseAmount = usdToBase(tp2USD)
if (baseAmount >= Math.floor(marketConfig.minOrderSize * 1e9)) {
const orderParams: any = {
@@ -517,23 +517,19 @@ export async function closePosition(
if (isDryRun) {
console.log('🧪 DRY RUN MODE: Simulating close order (not executing on blockchain)')
// Calculate realized P&L with leverage
// Calculate realized P&L with leverage (default 10x in dry run)
const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1)
const closedNotional = sizeToClose * oraclePrice
const realizedPnL = (closedNotional * profitPercent) / 100
const accountPnLPercent = profitPercent * 10 // display using default leverage
// CRITICAL FIX: closedNotional is leveraged position size, must calculate P&L on collateral
const leverage = 10 // Default for dry run
const collateralUsed = closedNotional / leverage
const accountPnLPercent = profitPercent * leverage
const realizedPnL = (collateralUsed * accountPnLPercent) / 100
const mockTxSig = `DRY_RUN_CLOSE_${Date.now()}_${Math.random().toString(36).substring(7)}`
console.log(`💰 Simulated close:`)
console.log(` Close price: $${oraclePrice.toFixed(4)}`)
console.log(` Profit %: ${profitPercent.toFixed(3)}% → Account P&L (${leverage}x): ${accountPnLPercent.toFixed(2)}%`)
console.log(` Profit %: ${profitPercent.toFixed(3)}% → Account P&L (10x): ${accountPnLPercent.toFixed(2)}%`)
console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`)
const mockTxSig = `DRY_RUN_CLOSE_${Date.now()}_${Math.random().toString(36).substring(7)}`
return {
success: true,
transactionSignature: mockTxSig,
@@ -573,7 +569,7 @@ export async function closePosition(
console.log('✅ Transaction confirmed on-chain')
// Calculate realized P&L with leverage
// CRITICAL: P&L must account for leverage and be calculated on collateral, not notional
// CRITICAL: P&L must account for leverage and be calculated on USD notional, not base asset size
const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1)
// Get leverage from user account (defaults to 10x if not found)
@@ -588,11 +584,10 @@ export async function closePosition(
console.log('⚠️ Could not determine leverage from account, using 10x default')
}
// Calculate closed notional value (USD) and convert to collateral
// Calculate closed notional value (USD)
const closedNotional = sizeToClose * oraclePrice
const collateralUsed = closedNotional / leverage // CRITICAL FIX: Calculate P&L on collateral
const accountPnLPercent = profitPercent * leverage // Account P&L includes leverage
const realizedPnL = (collateralUsed * accountPnLPercent) / 100
const realizedPnL = (closedNotional * profitPercent) / 100
const accountPnLPercent = profitPercent * leverage
console.log(`💰 Close details:`)
console.log(` Close price: $${oraclePrice.toFixed(4)}`)