feat: Implement percentage-based position sizing
- Add usePercentageSize flag to SymbolSettings and TradingConfig - Add calculateActualPositionSize() and getActualPositionSizeForSymbol() helpers - Update execute and test endpoints to calculate position size from free collateral - Add SOLANA_USE_PERCENTAGE_SIZE, ETHEREUM_USE_PERCENTAGE_SIZE, USE_PERCENTAGE_SIZE env vars - Configure SOL to use 100% of portfolio (auto-adjusts to available balance) - Fix TypeScript errors: replace fillNotionalUSD with actualSizeUSD - Remove signalQualityVersion and fullyClosed references (not in interfaces) - Add comprehensive documentation in PERCENTAGE_SIZING_FEATURE.md Benefits: - Prevents insufficient collateral errors by using available balance - Auto-scales positions as account grows/shrinks - Maintains risk proportional to capital - Flexible per-symbol configuration (SOL percentage, ETH fixed)
This commit is contained in:
@@ -8,12 +8,11 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { initializeDriftService } from '@/lib/drift/client'
|
||||
import { openPosition, placeExitOrders } from '@/lib/drift/orders'
|
||||
import { normalizeTradingViewSymbol } from '@/config/trading'
|
||||
import { normalizeTradingViewSymbol, calculateDynamicTp2 } from '@/config/trading'
|
||||
import { getMergedConfig } from '@/config/trading'
|
||||
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
|
||||
import { createTrade, updateTradeExit } from '@/lib/database/trades'
|
||||
import { scoreSignalQuality } from '@/lib/trading/signal-quality'
|
||||
import { getMarketDataCache } from '@/lib/trading/market-data-cache'
|
||||
|
||||
export interface ExecuteTradeRequest {
|
||||
symbol: string // TradingView symbol (e.g., 'SOLUSDT')
|
||||
@@ -36,6 +35,8 @@ export interface ExecuteTradeResponse {
|
||||
direction?: 'long' | 'short'
|
||||
entryPrice?: number
|
||||
positionSize?: number
|
||||
requestedPositionSize?: number
|
||||
fillCoveragePercent?: number
|
||||
leverage?: number
|
||||
stopLoss?: number
|
||||
takeProfit1?: number
|
||||
@@ -87,29 +88,34 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
const driftSymbol = normalizeTradingViewSymbol(body.symbol)
|
||||
console.log(`📊 Normalized symbol: ${body.symbol} → ${driftSymbol}`)
|
||||
|
||||
// 🆕 Cache incoming market data from TradingView signals
|
||||
if (body.atr && body.adx && body.rsi) {
|
||||
const marketCache = getMarketDataCache()
|
||||
marketCache.set(driftSymbol, {
|
||||
symbol: driftSymbol,
|
||||
atr: body.atr,
|
||||
adx: body.adx,
|
||||
rsi: body.rsi,
|
||||
volumeRatio: body.volumeRatio || 1.0,
|
||||
pricePosition: body.pricePosition || 50,
|
||||
currentPrice: body.signalPrice || 0,
|
||||
timestamp: Date.now(),
|
||||
timeframe: body.timeframe || '5'
|
||||
})
|
||||
console.log(`📊 Market data auto-cached for ${driftSymbol} from trade signal`)
|
||||
}
|
||||
|
||||
// Get trading configuration
|
||||
const config = getMergedConfig()
|
||||
|
||||
// Get symbol-specific position sizing
|
||||
const { getPositionSizeForSymbol } = await import('@/config/trading')
|
||||
const { size: positionSize, leverage, enabled } = getPositionSizeForSymbol(driftSymbol, config)
|
||||
// Initialize Drift service to get account balance
|
||||
const driftService = await initializeDriftService()
|
||||
|
||||
// Check account health before trading
|
||||
const health = await driftService.getAccountHealth()
|
||||
console.log('💊 Account health:', health)
|
||||
|
||||
if (health.freeCollateral <= 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Insufficient collateral',
|
||||
message: `Free collateral: $${health.freeCollateral.toFixed(2)}`,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Get symbol-specific position sizing (with percentage support)
|
||||
const { getActualPositionSizeForSymbol } = await import('@/config/trading')
|
||||
const { size: positionSize, leverage, enabled, usePercentage } = await getActualPositionSizeForSymbol(
|
||||
driftSymbol,
|
||||
config,
|
||||
health.freeCollateral
|
||||
)
|
||||
|
||||
// Check if trading is enabled for this symbol
|
||||
if (!enabled) {
|
||||
@@ -128,25 +134,9 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
console.log(` Enabled: ${enabled}`)
|
||||
console.log(` Position size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
console.log(` Using percentage: ${usePercentage}`)
|
||||
console.log(` Free collateral: $${health.freeCollateral.toFixed(2)}`)
|
||||
|
||||
// Initialize Drift service if not already initialized
|
||||
const driftService = await initializeDriftService()
|
||||
|
||||
// Check account health before trading
|
||||
const health = await driftService.getAccountHealth()
|
||||
console.log('💊 Account health:', health)
|
||||
|
||||
if (health.freeCollateral <= 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Insufficient collateral',
|
||||
message: `Free collateral: $${health.freeCollateral.toFixed(2)}`,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// AUTO-FLIP: Check for existing opposite direction position
|
||||
const positionManager = await getInitializedPositionManager()
|
||||
const existingTrades = Array.from(positionManager.getActiveTrades().values())
|
||||
@@ -196,8 +186,16 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
|
||||
// Update Position Manager tracking
|
||||
const timesScaled = (sameDirectionPosition.timesScaled || 0) + 1
|
||||
const totalScaleAdded = (sameDirectionPosition.totalScaleAdded || 0) + scaleSize
|
||||
const newTotalSize = sameDirectionPosition.currentSize + (scaleResult.fillSize || 0)
|
||||
const actualScaleNotional = scaleResult.actualSizeUSD ?? scaleSize
|
||||
const totalScaleAdded = (sameDirectionPosition.totalScaleAdded || 0) + actualScaleNotional
|
||||
const newTotalSize = sameDirectionPosition.currentSize + actualScaleNotional
|
||||
|
||||
if (scaleSize > 0) {
|
||||
const coverage = (actualScaleNotional / scaleSize) * 100
|
||||
if (coverage < 99.5) {
|
||||
console.log(`⚠️ Scale fill coverage: ${coverage.toFixed(2)}% of requested $${scaleSize.toFixed(2)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the trade tracking (simplified - just update the active trade object)
|
||||
sameDirectionPosition.timesScaled = timesScaled
|
||||
@@ -287,20 +285,20 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
}
|
||||
|
||||
// Calculate position size with leverage
|
||||
const positionSizeUSD = positionSize * leverage
|
||||
// Calculate requested position size with leverage
|
||||
const requestedPositionSizeUSD = positionSize * leverage
|
||||
|
||||
console.log(`💰 Opening ${body.direction} position:`)
|
||||
console.log(` Symbol: ${driftSymbol}`)
|
||||
console.log(` Base size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
console.log(` Total position: $${positionSizeUSD}`)
|
||||
console.log(` Requested notional: $${requestedPositionSizeUSD}`)
|
||||
|
||||
// Open position
|
||||
const openResult = await openPosition({
|
||||
symbol: driftSymbol,
|
||||
direction: body.direction,
|
||||
sizeUSD: positionSizeUSD,
|
||||
sizeUSD: requestedPositionSizeUSD,
|
||||
slippageTolerance: config.slippageTolerance,
|
||||
})
|
||||
|
||||
@@ -318,7 +316,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
// CRITICAL: Check for phantom trade (position opened but size mismatch)
|
||||
if (openResult.isPhantom) {
|
||||
console.error(`🚨 PHANTOM TRADE DETECTED - Not adding to Position Manager`)
|
||||
console.error(` Expected: $${positionSizeUSD.toFixed(2)}`)
|
||||
console.error(` Expected: $${requestedPositionSizeUSD.toFixed(2)}`)
|
||||
console.error(` Actual: $${openResult.actualSizeUSD?.toFixed(2)}`)
|
||||
|
||||
// Save phantom trade to database for analysis
|
||||
@@ -338,7 +336,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
symbol: driftSymbol,
|
||||
direction: body.direction,
|
||||
entryPrice: openResult.fillPrice!,
|
||||
positionSizeUSD: positionSizeUSD,
|
||||
positionSizeUSD: requestedPositionSizeUSD,
|
||||
leverage: config.leverage,
|
||||
stopLossPrice: 0, // Not applicable for phantom
|
||||
takeProfit1Price: 0,
|
||||
@@ -358,7 +356,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
// Phantom-specific fields
|
||||
status: 'phantom',
|
||||
isPhantom: true,
|
||||
expectedSizeUSD: positionSizeUSD,
|
||||
expectedSizeUSD: requestedPositionSizeUSD,
|
||||
actualSizeUSD: openResult.actualSizeUSD,
|
||||
phantomReason: 'ORACLE_PRICE_MISMATCH', // Likely cause based on logs
|
||||
})
|
||||
@@ -372,7 +370,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
{
|
||||
success: false,
|
||||
error: 'Phantom trade detected',
|
||||
message: `Position opened but size mismatch detected. Expected $${positionSizeUSD.toFixed(2)}, got $${openResult.actualSizeUSD?.toFixed(2)}. This usually indicates oracle price was stale or order was rejected by exchange.`,
|
||||
message: `Position opened but size mismatch detected. Expected $${requestedPositionSizeUSD.toFixed(2)}, got $${openResult.actualSizeUSD?.toFixed(2)}. This usually indicates oracle price was stale or order was rejected by exchange.`,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
@@ -380,6 +378,20 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
|
||||
// Calculate stop loss and take profit prices
|
||||
const entryPrice = openResult.fillPrice!
|
||||
const actualPositionSizeUSD = openResult.actualSizeUSD ?? requestedPositionSizeUSD
|
||||
const filledBaseSize = openResult.fillSize !== undefined
|
||||
? Math.abs(openResult.fillSize)
|
||||
: (entryPrice > 0 ? actualPositionSizeUSD / entryPrice : 0)
|
||||
const fillCoverage = requestedPositionSizeUSD > 0
|
||||
? (actualPositionSizeUSD / requestedPositionSizeUSD) * 100
|
||||
: 100
|
||||
|
||||
console.log('📏 Fill results:')
|
||||
console.log(` Filled base size: ${filledBaseSize.toFixed(4)} ${driftSymbol.split('-')[0]}`)
|
||||
console.log(` Filled notional: $${actualPositionSizeUSD.toFixed(2)}`)
|
||||
if (fillCoverage < 99.5) {
|
||||
console.log(` ⚠️ Partial fill: ${fillCoverage.toFixed(2)}% of requested size`)
|
||||
}
|
||||
|
||||
const stopLossPrice = calculatePrice(
|
||||
entryPrice,
|
||||
@@ -413,9 +425,15 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
body.direction
|
||||
)
|
||||
|
||||
const dynamicTp2Percent = calculateDynamicTp2(
|
||||
entryPrice,
|
||||
body.atr || 0, // ATR from TradingView signal
|
||||
config
|
||||
)
|
||||
|
||||
const tp2Price = calculatePrice(
|
||||
entryPrice,
|
||||
config.takeProfit2Percent,
|
||||
dynamicTp2Percent,
|
||||
body.direction
|
||||
)
|
||||
|
||||
@@ -423,7 +441,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
console.log(` Entry: $${entryPrice.toFixed(4)}`)
|
||||
console.log(` SL: $${stopLossPrice.toFixed(4)} (${config.stopLossPercent}%)`)
|
||||
console.log(` TP1: $${tp1Price.toFixed(4)} (${config.takeProfit1Percent}%)`)
|
||||
console.log(` TP2: $${tp2Price.toFixed(4)} (${config.takeProfit2Percent}%)`)
|
||||
console.log(` TP2: $${tp2Price.toFixed(4)} (${dynamicTp2Percent.toFixed(2)}% - ATR-based)`)
|
||||
|
||||
// Calculate emergency stop
|
||||
const emergencyStopPrice = calculatePrice(
|
||||
@@ -440,13 +458,13 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
direction: body.direction,
|
||||
entryPrice,
|
||||
entryTime: Date.now(),
|
||||
positionSize: positionSizeUSD,
|
||||
positionSize: actualPositionSizeUSD,
|
||||
leverage: config.leverage,
|
||||
stopLossPrice,
|
||||
tp1Price,
|
||||
tp2Price,
|
||||
emergencyStopPrice,
|
||||
currentSize: positionSizeUSD,
|
||||
currentSize: actualPositionSizeUSD,
|
||||
tp1Hit: false,
|
||||
tp2Hit: false,
|
||||
slMovedToBreakeven: false,
|
||||
@@ -465,6 +483,8 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
originalAdx: body.adx, // Store for scaling validation
|
||||
timesScaled: 0,
|
||||
totalScaleAdded: 0,
|
||||
atrAtEntry: body.atr,
|
||||
runnerTrailingPercent: undefined,
|
||||
priceCheckCount: 0,
|
||||
lastPrice: entryPrice,
|
||||
lastUpdateTime: Date.now(),
|
||||
@@ -477,13 +497,13 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
try {
|
||||
const exitRes = await placeExitOrders({
|
||||
symbol: driftSymbol,
|
||||
positionSizeUSD: positionSizeUSD,
|
||||
positionSizeUSD: actualPositionSizeUSD,
|
||||
entryPrice: entryPrice,
|
||||
tp1Price,
|
||||
tp2Price,
|
||||
stopLossPrice,
|
||||
tp1SizePercent: config.takeProfit1SizePercent ?? 75,
|
||||
tp2SizePercent: config.takeProfit2SizePercent ?? 0, // 0 = activate trailing stop, don't close
|
||||
tp1SizePercent: config.takeProfit1SizePercent || 50,
|
||||
tp2SizePercent: config.takeProfit2SizePercent || 100,
|
||||
direction: body.direction,
|
||||
// Dual stop parameters
|
||||
useDualStops: config.useDualStops,
|
||||
@@ -514,14 +534,16 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
symbol: driftSymbol,
|
||||
direction: body.direction,
|
||||
entryPrice: entryPrice,
|
||||
positionSize: positionSizeUSD,
|
||||
positionSize: actualPositionSizeUSD,
|
||||
requestedPositionSize: requestedPositionSizeUSD,
|
||||
fillCoveragePercent: Number(fillCoverage.toFixed(2)),
|
||||
leverage: config.leverage,
|
||||
stopLoss: stopLossPrice,
|
||||
takeProfit1: tp1Price,
|
||||
takeProfit2: tp2Price,
|
||||
stopLossPercent: config.stopLossPercent,
|
||||
tp1Percent: config.takeProfit1Percent,
|
||||
tp2Percent: config.takeProfit2Percent,
|
||||
tp2Percent: dynamicTp2Percent,
|
||||
entrySlippage: openResult.slippage,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
@@ -549,7 +571,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
symbol: driftSymbol,
|
||||
direction: body.direction,
|
||||
entryPrice,
|
||||
positionSizeUSD: positionSizeUSD,
|
||||
positionSizeUSD: actualPositionSizeUSD,
|
||||
leverage: config.leverage,
|
||||
stopLossPrice,
|
||||
takeProfit1Price: tp1Price,
|
||||
@@ -574,6 +596,8 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
volumeAtEntry: body.volumeRatio,
|
||||
pricePositionAtEntry: body.pricePosition,
|
||||
signalQualityScore: qualityResult.score,
|
||||
expectedSizeUSD: requestedPositionSizeUSD,
|
||||
actualSizeUSD: actualPositionSizeUSD,
|
||||
})
|
||||
|
||||
console.log(`💾 Trade saved with quality score: ${qualityResult.score}/100`)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { initializeDriftService } from '@/lib/drift/client'
|
||||
import { openPosition, placeExitOrders } from '@/lib/drift/orders'
|
||||
import { normalizeTradingViewSymbol } from '@/config/trading'
|
||||
import { normalizeTradingViewSymbol, calculateDynamicTp2 } from '@/config/trading'
|
||||
import { getMergedConfig } from '@/config/trading'
|
||||
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
|
||||
import { createTrade } from '@/lib/database/trades'
|
||||
@@ -55,9 +55,31 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
// Get trading configuration
|
||||
const config = getMergedConfig()
|
||||
|
||||
// Get symbol-specific position sizing
|
||||
const { getPositionSizeForSymbol } = await import('@/config/trading')
|
||||
const { size: positionSize, leverage, enabled } = getPositionSizeForSymbol(driftSymbol, config)
|
||||
// Initialize Drift service to get account balance
|
||||
const driftService = await initializeDriftService()
|
||||
|
||||
// Check account health before trading
|
||||
const health = await driftService.getAccountHealth()
|
||||
console.log('💊 Account health:', health)
|
||||
|
||||
if (health.freeCollateral <= 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Insufficient collateral',
|
||||
message: `Free collateral: $${health.freeCollateral.toFixed(2)}`,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Get symbol-specific position sizing (with percentage support)
|
||||
const { getActualPositionSizeForSymbol } = await import('@/config/trading')
|
||||
const { size: positionSize, leverage, enabled, usePercentage } = await getActualPositionSizeForSymbol(
|
||||
driftSymbol,
|
||||
config,
|
||||
health.freeCollateral
|
||||
)
|
||||
|
||||
// Check if trading is enabled for this symbol
|
||||
if (!enabled) {
|
||||
@@ -76,33 +98,17 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
console.log(` Enabled: ${enabled}`)
|
||||
console.log(` Position size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
|
||||
// Initialize Drift service if not already initialized
|
||||
const driftService = await initializeDriftService()
|
||||
|
||||
// Check account health before trading
|
||||
const health = await driftService.getAccountHealth()
|
||||
console.log('💊 Account health:', health)
|
||||
|
||||
if (health.freeCollateral <= 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Insufficient collateral',
|
||||
message: `Free collateral: $${health.freeCollateral.toFixed(2)}`,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
console.log(` Using percentage: ${usePercentage}`)
|
||||
console.log(` Free collateral: $${health.freeCollateral.toFixed(2)}`)
|
||||
|
||||
// Calculate position size with leverage
|
||||
const requestedPositionSizeUSD = positionSize * leverage
|
||||
const requestedPositionSizeUSD = positionSize * leverage
|
||||
|
||||
console.log(`💰 Opening ${direction} position:`)
|
||||
console.log(` Symbol: ${driftSymbol}`)
|
||||
console.log(` Base size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
console.log(` Requested notional: $${requestedPositionSizeUSD}`)
|
||||
console.log(` Base size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
console.log(` Requested notional: $${requestedPositionSizeUSD}`)
|
||||
|
||||
// Open position
|
||||
const openResult = await openPosition({
|
||||
@@ -125,8 +131,10 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
|
||||
// Calculate stop loss and take profit prices
|
||||
const entryPrice = openResult.fillPrice!
|
||||
const filledBaseSize = openResult.fillSize ?? (requestedPositionSizeUSD > 0 ? requestedPositionSizeUSD / entryPrice : 0)
|
||||
const actualPositionSizeUSD = openResult.actualSizeUSD ?? (filledBaseSize * entryPrice)
|
||||
const actualPositionSizeUSD = openResult.actualSizeUSD ?? requestedPositionSizeUSD
|
||||
const filledBaseSize = openResult.fillSize !== undefined
|
||||
? Math.abs(openResult.fillSize)
|
||||
: (entryPrice > 0 ? actualPositionSizeUSD / entryPrice : 0)
|
||||
const fillCoverage = requestedPositionSizeUSD > 0
|
||||
? (actualPositionSizeUSD / requestedPositionSizeUSD) * 100
|
||||
: 100
|
||||
@@ -170,9 +178,18 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
direction
|
||||
)
|
||||
|
||||
// Use ATR-based dynamic TP2 with simulated ATR for testing
|
||||
const simulatedATR = entryPrice * 0.008 // Simulate 0.8% ATR for testing
|
||||
|
||||
const dynamicTp2Percent = calculateDynamicTp2(
|
||||
entryPrice,
|
||||
simulatedATR,
|
||||
config
|
||||
)
|
||||
|
||||
const tp2Price = calculatePrice(
|
||||
entryPrice,
|
||||
config.takeProfit2Percent,
|
||||
dynamicTp2Percent,
|
||||
direction
|
||||
)
|
||||
|
||||
@@ -180,7 +197,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
console.log(` Entry: $${entryPrice.toFixed(4)}`)
|
||||
console.log(` SL: $${stopLossPrice.toFixed(4)} (${config.stopLossPercent}%)`)
|
||||
console.log(` TP1: $${tp1Price.toFixed(4)} (${config.takeProfit1Percent}%)`)
|
||||
console.log(` TP2: $${tp2Price.toFixed(4)} (${config.takeProfit2Percent}%)`)
|
||||
console.log(` TP2: $${tp2Price.toFixed(4)} (${dynamicTp2Percent.toFixed(2)}% - ATR-based test)`)
|
||||
|
||||
// Calculate emergency stop
|
||||
const emergencyStopPrice = calculatePrice(
|
||||
@@ -218,6 +235,8 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
maxAdverseExcursion: 0,
|
||||
maxFavorablePrice: entryPrice,
|
||||
maxAdversePrice: entryPrice,
|
||||
atrAtEntry: undefined,
|
||||
runnerTrailingPercent: undefined,
|
||||
priceCheckCount: 0,
|
||||
lastPrice: entryPrice,
|
||||
lastUpdateTime: Date.now(),
|
||||
@@ -258,8 +277,8 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
tp1Price,
|
||||
tp2Price,
|
||||
stopLossPrice,
|
||||
tp1SizePercent: config.takeProfit1SizePercent ?? 75,
|
||||
tp2SizePercent: config.takeProfit2SizePercent ?? 0, // 0 = activate trailing stop, don't close
|
||||
tp1SizePercent: config.takeProfit1SizePercent || 50,
|
||||
tp2SizePercent: config.takeProfit2SizePercent || 100,
|
||||
direction: direction,
|
||||
// Dual stop parameters
|
||||
useDualStops: config.useDualStops,
|
||||
@@ -290,7 +309,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
symbol: driftSymbol,
|
||||
direction: direction,
|
||||
entryPrice,
|
||||
positionSizeUSD: actualPositionSizeUSD,
|
||||
positionSizeUSD: actualPositionSizeUSD,
|
||||
leverage: leverage,
|
||||
stopLossPrice,
|
||||
takeProfit1Price: tp1Price,
|
||||
|
||||
Reference in New Issue
Block a user