feat: Implement adaptive leverage based on signal quality score
- Quality-based risk adjustment: 95+ = 15x, 90-94 = 10x, <90 = blocked - Data-driven decision: v8 quality 95+ = 100% WR (4/4 wins) - Config fields: useAdaptiveLeverage, highQualityLeverage, lowQualityLeverage, qualityLeverageThreshold - Helper function: getLeverageForQualityScore() returns appropriate leverage tier - Position sizing: Modified getActualPositionSizeForSymbol() to accept optional qualityScore param - Execute endpoint: Calculate quality score early (before sizing) for leverage determination - Test endpoint: Uses quality 100 for maximum leverage on manual test trades - ENV variables: USE_ADAPTIVE_LEVERAGE, HIGH_QUALITY_LEVERAGE, LOW_QUALITY_LEVERAGE, QUALITY_LEVERAGE_THRESHOLD - Impact: 33% less exposure on borderline quality signals (90-94) - Example: $540 × 10x = $5,400 vs $8,100 (saves $2,700 exposure on volatile signals) - Files changed: * config/trading.ts (interface, config, ENV, helper function, position sizing) * app/api/trading/execute/route.ts (early quality calculation, pass to sizing) * app/api/trading/test/route.ts (quality 100 for test trades)
This commit is contained in:
@@ -14,9 +14,15 @@ export interface SymbolSettings {
|
||||
export interface TradingConfig {
|
||||
// Position sizing (global fallback)
|
||||
positionSize: number // USD amount to trade (or percentage if usePercentageSize=true)
|
||||
leverage: number // Leverage multiplier
|
||||
leverage: number // Leverage multiplier (LEGACY - used when adaptive disabled)
|
||||
usePercentageSize: boolean // If true, positionSize is % of free collateral
|
||||
|
||||
// Adaptive Leverage (Quality-based risk adjustment - Nov 24, 2025)
|
||||
useAdaptiveLeverage: boolean // Enable quality-based leverage tiers
|
||||
highQualityLeverage: number // Leverage for signals >= threshold (e.g., 15 for quality 95+)
|
||||
lowQualityLeverage: number // Leverage for signals < threshold (e.g., 10 for quality 90-94)
|
||||
qualityLeverageThreshold: number // Quality score threshold (e.g., 95)
|
||||
|
||||
// Per-symbol settings
|
||||
solana?: SymbolSettings
|
||||
ethereum?: SymbolSettings
|
||||
@@ -104,9 +110,16 @@ export interface MarketConfig {
|
||||
export const DEFAULT_TRADING_CONFIG: TradingConfig = {
|
||||
// Position sizing (global fallback)
|
||||
positionSize: 50, // $50 base capital (SAFE FOR TESTING) OR percentage if usePercentageSize=true
|
||||
leverage: 10, // 10x leverage = $500 position size
|
||||
leverage: 10, // 10x leverage = $500 position size (LEGACY - used when adaptive disabled)
|
||||
usePercentageSize: false, // False = fixed USD, True = percentage of portfolio
|
||||
|
||||
// Adaptive Leverage (Quality-based risk adjustment - Nov 24, 2025)
|
||||
// Data-driven: v8 quality 95+ = 100% WR (4/4 wins), quality 90-94 more volatile
|
||||
useAdaptiveLeverage: true, // Enable quality-based leverage tiers
|
||||
highQualityLeverage: 15, // For signals >= 95 quality (high confidence)
|
||||
lowQualityLeverage: 10, // For signals 90-94 quality (reduced risk)
|
||||
qualityLeverageThreshold: 95, // Threshold for high vs low leverage
|
||||
|
||||
// Per-symbol settings
|
||||
solana: {
|
||||
enabled: true,
|
||||
@@ -308,12 +321,14 @@ export function calculateActualPositionSize(
|
||||
|
||||
/**
|
||||
* Get actual position size for symbol with percentage support
|
||||
* Now supports adaptive leverage based on quality score (Nov 24, 2025)
|
||||
* This is the main function to use when opening positions
|
||||
*/
|
||||
export async function getActualPositionSizeForSymbol(
|
||||
symbol: string,
|
||||
baseConfig: TradingConfig,
|
||||
freeCollateral: number
|
||||
freeCollateral: number,
|
||||
qualityScore?: number // NEW: Optional quality score for adaptive leverage
|
||||
): Promise<{ size: number; leverage: number; enabled: boolean; usePercentage: boolean }> {
|
||||
let symbolSettings: { size: number; leverage: number; enabled: boolean }
|
||||
let usePercentage = false
|
||||
@@ -351,9 +366,16 @@ export async function getActualPositionSizeForSymbol(
|
||||
freeCollateral
|
||||
)
|
||||
|
||||
// NEW (Nov 24, 2025): Apply adaptive leverage based on quality score
|
||||
let finalLeverage = symbolSettings.leverage
|
||||
if (qualityScore !== undefined && baseConfig.useAdaptiveLeverage) {
|
||||
finalLeverage = getLeverageForQualityScore(qualityScore, baseConfig)
|
||||
console.log(`📊 Adaptive leverage: Quality ${qualityScore} → ${finalLeverage}x leverage (threshold: ${baseConfig.qualityLeverageThreshold})`)
|
||||
}
|
||||
|
||||
return {
|
||||
size: actualSize,
|
||||
leverage: symbolSettings.leverage,
|
||||
leverage: finalLeverage, // Use adaptive leverage if quality score provided
|
||||
enabled: symbolSettings.enabled,
|
||||
usePercentage,
|
||||
}
|
||||
@@ -468,6 +490,21 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
|
||||
leverage: process.env.LEVERAGE
|
||||
? parseInt(process.env.LEVERAGE)
|
||||
: undefined,
|
||||
|
||||
// Adaptive Leverage (Quality-based risk adjustment - Nov 24, 2025)
|
||||
useAdaptiveLeverage: process.env.USE_ADAPTIVE_LEVERAGE
|
||||
? process.env.USE_ADAPTIVE_LEVERAGE === 'true'
|
||||
: undefined,
|
||||
highQualityLeverage: process.env.HIGH_QUALITY_LEVERAGE
|
||||
? parseInt(process.env.HIGH_QUALITY_LEVERAGE)
|
||||
: undefined,
|
||||
lowQualityLeverage: process.env.LOW_QUALITY_LEVERAGE
|
||||
? parseInt(process.env.LOW_QUALITY_LEVERAGE)
|
||||
: undefined,
|
||||
qualityLeverageThreshold: process.env.QUALITY_LEVERAGE_THRESHOLD
|
||||
? parseInt(process.env.QUALITY_LEVERAGE_THRESHOLD)
|
||||
: undefined,
|
||||
|
||||
stopLossPercent: process.env.STOP_LOSS_PERCENT
|
||||
? parseFloat(process.env.STOP_LOSS_PERCENT)
|
||||
: undefined,
|
||||
@@ -613,6 +650,26 @@ export function getMinQualityScoreForDirection(
|
||||
return config.minSignalQualityScore
|
||||
}
|
||||
|
||||
// Get leverage based on signal quality score (Nov 24, 2025)
|
||||
// Data-driven: v8 quality 95+ = 100% WR (4/4 wins), quality 90-94 more volatile
|
||||
export function getLeverageForQualityScore(
|
||||
qualityScore: number,
|
||||
config: TradingConfig
|
||||
): number {
|
||||
// If adaptive leverage disabled, use fixed leverage
|
||||
if (!config.useAdaptiveLeverage) {
|
||||
return config.leverage
|
||||
}
|
||||
|
||||
// High quality signals get maximum leverage
|
||||
if (qualityScore >= config.qualityLeverageThreshold) {
|
||||
return config.highQualityLeverage
|
||||
}
|
||||
|
||||
// Lower quality signals get reduced leverage
|
||||
return config.lowQualityLeverage
|
||||
}
|
||||
|
||||
// Merge configurations
|
||||
export function getMergedConfig(
|
||||
overrides?: Partial<TradingConfig>
|
||||
|
||||
Reference in New Issue
Block a user