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,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