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,14 @@ export interface SymbolSettings {
|
||||
enabled: boolean
|
||||
positionSize: number
|
||||
leverage: number
|
||||
usePercentageSize?: boolean // If true, positionSize is % of portfolio (0-100)
|
||||
}
|
||||
|
||||
export interface TradingConfig {
|
||||
// Position sizing (global fallback)
|
||||
positionSize: number // USD amount to trade
|
||||
positionSize: number // USD amount to trade (or percentage if usePercentageSize=true)
|
||||
leverage: number // Leverage multiplier
|
||||
usePercentageSize: boolean // If true, positionSize is % of free collateral
|
||||
|
||||
// Per-symbol settings
|
||||
solana?: SymbolSettings
|
||||
@@ -93,19 +95,22 @@ export interface MarketConfig {
|
||||
// Default configuration for 5-minute scalping with $1000 capital and 10x leverage
|
||||
export const DEFAULT_TRADING_CONFIG: TradingConfig = {
|
||||
// Position sizing (global fallback)
|
||||
positionSize: 50, // $50 base capital (SAFE FOR TESTING)
|
||||
positionSize: 50, // $50 base capital (SAFE FOR TESTING) OR percentage if usePercentageSize=true
|
||||
leverage: 10, // 10x leverage = $500 position size
|
||||
usePercentageSize: false, // False = fixed USD, True = percentage of portfolio
|
||||
|
||||
// Per-symbol settings
|
||||
solana: {
|
||||
enabled: true,
|
||||
positionSize: 210, // $210 base capital
|
||||
positionSize: 210, // $210 base capital OR percentage if usePercentageSize=true
|
||||
leverage: 10, // 10x leverage = $2100 notional
|
||||
usePercentageSize: false,
|
||||
},
|
||||
ethereum: {
|
||||
enabled: true,
|
||||
positionSize: 4, // $4 base capital (DATA ONLY - minimum size)
|
||||
leverage: 1, // 1x leverage = $4 notional
|
||||
usePercentageSize: false,
|
||||
},
|
||||
|
||||
// Risk parameters (wider for DEX slippage/wicks)
|
||||
@@ -248,6 +253,85 @@ export function getPositionSizeForSymbol(symbol: string, baseConfig: TradingConf
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate actual USD position size from percentage or fixed amount
|
||||
* @param configuredSize - The configured size (USD or percentage)
|
||||
* @param usePercentage - Whether configuredSize is a percentage
|
||||
* @param freeCollateral - Available collateral in USD (from Drift account)
|
||||
* @returns Actual USD size to use for the trade
|
||||
*/
|
||||
export function calculateActualPositionSize(
|
||||
configuredSize: number,
|
||||
usePercentage: boolean,
|
||||
freeCollateral: number
|
||||
): number {
|
||||
if (!usePercentage) {
|
||||
// Fixed USD amount
|
||||
return configuredSize
|
||||
}
|
||||
|
||||
// Percentage of free collateral
|
||||
const percentDecimal = configuredSize / 100
|
||||
const calculatedSize = freeCollateral * percentDecimal
|
||||
|
||||
console.log(`📊 Percentage sizing: ${configuredSize}% of $${freeCollateral.toFixed(2)} = $${calculatedSize.toFixed(2)}`)
|
||||
|
||||
return calculatedSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actual position size for symbol with percentage support
|
||||
* This is the main function to use when opening positions
|
||||
*/
|
||||
export async function getActualPositionSizeForSymbol(
|
||||
symbol: string,
|
||||
baseConfig: TradingConfig,
|
||||
freeCollateral: number
|
||||
): Promise<{ size: number; leverage: number; enabled: boolean; usePercentage: boolean }> {
|
||||
let symbolSettings: { size: number; leverage: number; enabled: boolean }
|
||||
let usePercentage = false
|
||||
|
||||
// Get symbol-specific settings
|
||||
if (symbol === 'SOL-PERP' && baseConfig.solana) {
|
||||
symbolSettings = {
|
||||
size: baseConfig.solana.positionSize,
|
||||
leverage: baseConfig.solana.leverage,
|
||||
enabled: baseConfig.solana.enabled,
|
||||
}
|
||||
usePercentage = baseConfig.solana.usePercentageSize ?? false
|
||||
} else if (symbol === 'ETH-PERP' && baseConfig.ethereum) {
|
||||
symbolSettings = {
|
||||
size: baseConfig.ethereum.positionSize,
|
||||
leverage: baseConfig.ethereum.leverage,
|
||||
enabled: baseConfig.ethereum.enabled,
|
||||
}
|
||||
usePercentage = baseConfig.ethereum.usePercentageSize ?? false
|
||||
} else {
|
||||
// Fallback to market-specific or global config
|
||||
const marketConfig = getMarketConfig(symbol)
|
||||
symbolSettings = {
|
||||
size: marketConfig.positionSize ?? baseConfig.positionSize,
|
||||
leverage: marketConfig.leverage ?? baseConfig.leverage,
|
||||
enabled: true,
|
||||
}
|
||||
usePercentage = baseConfig.usePercentageSize
|
||||
}
|
||||
|
||||
// Calculate actual size
|
||||
const actualSize = calculateActualPositionSize(
|
||||
symbolSettings.size,
|
||||
usePercentage,
|
||||
freeCollateral
|
||||
)
|
||||
|
||||
return {
|
||||
size: actualSize,
|
||||
leverage: symbolSettings.leverage,
|
||||
enabled: symbolSettings.enabled,
|
||||
usePercentage,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate dynamic TP2 level based on ATR (Average True Range)
|
||||
* Higher ATR = higher volatility = larger TP2 target to capture big moves
|
||||
@@ -324,6 +408,10 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
|
||||
? parseFloat(process.env.MAX_POSITION_SIZE_USD)
|
||||
: undefined,
|
||||
|
||||
usePercentageSize: process.env.USE_PERCENTAGE_SIZE
|
||||
? process.env.USE_PERCENTAGE_SIZE === 'true'
|
||||
: undefined,
|
||||
|
||||
// Per-symbol settings from ENV
|
||||
solana: {
|
||||
enabled: process.env.SOLANA_ENABLED !== 'false',
|
||||
@@ -333,6 +421,9 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
|
||||
leverage: process.env.SOLANA_LEVERAGE
|
||||
? parseInt(process.env.SOLANA_LEVERAGE)
|
||||
: 10,
|
||||
usePercentageSize: process.env.SOLANA_USE_PERCENTAGE_SIZE
|
||||
? process.env.SOLANA_USE_PERCENTAGE_SIZE === 'true'
|
||||
: false,
|
||||
},
|
||||
ethereum: {
|
||||
enabled: process.env.ETHEREUM_ENABLED !== 'false',
|
||||
@@ -342,6 +433,9 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
|
||||
leverage: process.env.ETHEREUM_LEVERAGE
|
||||
? parseInt(process.env.ETHEREUM_LEVERAGE)
|
||||
: 1,
|
||||
usePercentageSize: process.env.ETHEREUM_USE_PERCENTAGE_SIZE
|
||||
? process.env.ETHEREUM_USE_PERCENTAGE_SIZE === 'true'
|
||||
: false,
|
||||
},
|
||||
leverage: process.env.LEVERAGE
|
||||
? parseInt(process.env.LEVERAGE)
|
||||
|
||||
Reference in New Issue
Block a user