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:
mindesbunister
2025-11-24 00:47:09 +01:00
parent 1e64e8145b
commit bfdb0ba779
4 changed files with 85 additions and 23 deletions

View File

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