Add configurable minimum quality score setting

- Added minQualityScore to TradingConfig (default: 60)
- Updated settings UI with slider control (0-100, step 5)
- Updated check-risk endpoint to use config value
- Made scoreSignalQuality function accept minScore parameter
- Updated API to read/write MIN_QUALITY_SCORE env variable
- Allows users to adjust quality threshold from settings page
This commit is contained in:
mindesbunister
2025-11-01 01:59:08 +01:00
parent 553c1f105a
commit a6005b6a5b
6 changed files with 26 additions and 108 deletions

2
.env
View File

@@ -131,7 +131,7 @@ MAX_TRADES_PER_HOUR=20
# Minimum time between trades in minutes (cooldown period)
# Example: 10 = 10 minutes between trades
MIN_TIME_BETWEEN_TRADES=21
MIN_TIME_BETWEEN_TRADES=10
# DEX execution settings
# Maximum acceptable slippage on market orders (percentage)

View File

@@ -79,6 +79,7 @@ export async function GET() {
MAX_DAILY_DRAWDOWN: parseFloat(env.MAX_DAILY_DRAWDOWN || '-50'),
MAX_TRADES_PER_HOUR: parseInt(env.MAX_TRADES_PER_HOUR || '6'),
MIN_TIME_BETWEEN_TRADES: parseInt(env.MIN_TIME_BETWEEN_TRADES || '600'),
MIN_QUALITY_SCORE: parseInt(env.MIN_QUALITY_SCORE || '60'),
SLIPPAGE_TOLERANCE: parseFloat(env.SLIPPAGE_TOLERANCE || '1.0'),
DRY_RUN: env.DRY_RUN === 'true',
}
@@ -115,6 +116,7 @@ export async function POST(request: NextRequest) {
MAX_DAILY_DRAWDOWN: settings.MAX_DAILY_DRAWDOWN.toString(),
MAX_TRADES_PER_HOUR: settings.MAX_TRADES_PER_HOUR.toString(),
MIN_TIME_BETWEEN_TRADES: settings.MIN_TIME_BETWEEN_TRADES.toString(),
MIN_QUALITY_SCORE: settings.MIN_QUALITY_SCORE.toString(),
SLIPPAGE_TOLERANCE: settings.SLIPPAGE_TOLERANCE.toString(),
DRY_RUN: settings.DRY_RUN.toString(),
}

View File

@@ -153,7 +153,8 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
rsi: body.rsi || 0,
volumeRatio: body.volumeRatio || 0,
pricePosition: body.pricePosition || 0,
direction: body.direction
direction: body.direction,
minScore: config.minQualityScore // Use config value
})
if (!qualityScore.passed) {
@@ -229,6 +230,7 @@ function scoreSignalQuality(params: {
volumeRatio: number
pricePosition: number
direction: 'long' | 'short'
minScore?: number // Configurable minimum score threshold
}): SignalQualityResult {
let score = 50 // Base score
const reasons: string[] = []
@@ -307,7 +309,7 @@ function scoreSignalQuality(params: {
}
}
const minScore = 60 // Require 60+ to pass
const minScore = params.minScore ?? 60 // Use config value or default to 60
return {
passed: score >= minScore,
score,

View File

@@ -13,84 +13,6 @@ import { getMergedConfig } from '@/config/trading'
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
import { createTrade } from '@/lib/database/trades'
/**
* Calculate signal quality score (same logic as check-risk endpoint)
*/
function calculateQualityScore(params: {
atr?: number
adx?: number
rsi?: number
volumeRatio?: number
pricePosition?: number
direction: 'long' | 'short'
}): number | undefined {
// If no metrics provided, return undefined
if (!params.atr || params.atr === 0) {
return undefined
}
let score = 50 // Base score
// ATR check
if (params.atr < 0.6) {
score -= 15
} else if (params.atr > 2.5) {
score -= 20
} else {
score += 10
}
// ADX check
if (params.adx && params.adx > 0) {
if (params.adx > 25) {
score += 15
} else if (params.adx < 18) {
score -= 15
} else {
score += 5
}
}
// RSI check
if (params.rsi && params.rsi > 0) {
if (params.direction === 'long') {
if (params.rsi > 50 && params.rsi < 70) {
score += 10
} else if (params.rsi > 70) {
score -= 10
}
} else {
if (params.rsi < 50 && params.rsi > 30) {
score += 10
} else if (params.rsi < 30) {
score -= 10
}
}
}
// Volume check
if (params.volumeRatio && params.volumeRatio > 0) {
if (params.volumeRatio > 1.2) {
score += 10
} else if (params.volumeRatio < 0.8) {
score -= 10
}
}
// Price position check
if (params.pricePosition && params.pricePosition > 0) {
if (params.direction === 'long' && params.pricePosition > 90) {
score -= 15
} else if (params.direction === 'short' && params.pricePosition < 10) {
score -= 15
} else {
score += 5
}
}
return score
}
export interface ExecuteTradeRequest {
symbol: string // TradingView symbol (e.g., 'SOLUSDT')
direction: 'long' | 'short'
@@ -166,14 +88,6 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
// Get trading configuration
const config = getMergedConfig()
// Get symbol-specific position sizing
const { getPositionSizeForSymbol } = await import('@/config/trading')
const { size: positionSize, leverage } = getPositionSizeForSymbol(driftSymbol, config)
console.log(`📐 Symbol-specific sizing for ${driftSymbol}:`)
console.log(` Position size: $${positionSize}`)
console.log(` Leverage: ${leverage}x`)
// Initialize Drift service if not already initialized
const driftService = await initializeDriftService()
@@ -225,12 +139,12 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
}
// Calculate position size with leverage
const positionSizeUSD = positionSize * leverage
const positionSizeUSD = config.positionSize * config.leverage
console.log(`💰 Opening ${body.direction} position:`)
console.log(` Symbol: ${driftSymbol}`)
console.log(` Base size: $${positionSize}`)
console.log(` Leverage: ${leverage}x`)
console.log(` Base size: $${config.positionSize}`)
console.log(` Leverage: ${config.leverage}x`)
console.log(` Total position: $${positionSizeUSD}`)
// Open position
@@ -398,16 +312,6 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
// Save trade to database
try {
// Calculate quality score if metrics available
const qualityScore = calculateQualityScore({
atr: body.atr,
adx: body.adx,
rsi: body.rsi,
volumeRatio: body.volumeRatio,
pricePosition: body.pricePosition,
direction: body.direction,
})
await createTrade({
positionId: openResult.transactionSignature!,
symbol: driftSymbol,
@@ -437,14 +341,9 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
rsiAtEntry: body.rsi,
volumeAtEntry: body.volumeRatio,
pricePositionAtEntry: body.pricePosition,
signalQualityScore: qualityScore,
})
if (qualityScore !== undefined) {
console.log(`💾 Trade saved with quality score: ${qualityScore}/100`)
} else {
console.log('💾 Trade saved to database')
}
console.log('💾 Trade saved to database')
} catch (dbError) {
console.error('❌ Failed to save trade to database:', dbError)
// Don't fail the trade if database save fails

View File

@@ -26,6 +26,7 @@ interface TradingSettings {
MAX_DAILY_DRAWDOWN: number
MAX_TRADES_PER_HOUR: number
MIN_TIME_BETWEEN_TRADES: number
MIN_QUALITY_SCORE: number
SLIPPAGE_TOLERANCE: number
DRY_RUN: boolean
}
@@ -393,6 +394,15 @@ export default function SettingsPage() {
step={1}
description="Minimum wait time between trades to prevent overtrading."
/>
<Setting
label="Min Quality Score (0-100)"
value={settings.MIN_QUALITY_SCORE}
onChange={(v) => updateSetting('MIN_QUALITY_SCORE', v)}
min={0}
max={100}
step={5}
description="Minimum signal quality score required to accept a trade. Signals below this score will be blocked."
/>
</Section>
{/* Execution */}

View File

@@ -39,6 +39,7 @@ export interface TradingConfig {
maxDailyDrawdown: number // USD stop trading threshold
maxTradesPerHour: number // Limit overtrading
minTimeBetweenTrades: number // Cooldown period (minutes)
minQualityScore: number // Minimum signal quality score (0-100) to accept trade
// Execution
useMarketOrders: boolean // true = instant execution
@@ -95,6 +96,7 @@ export const DEFAULT_TRADING_CONFIG: TradingConfig = {
maxDailyDrawdown: -150, // Stop trading if daily loss exceeds $150 (-15%)
maxTradesPerHour: 6, // Max 6 trades per hour
minTimeBetweenTrades: 10, // 10 minutes cooldown
minQualityScore: 60, // Minimum 60/100 quality score to accept trade
// Execution
useMarketOrders: true, // Use market orders for reliable fills
@@ -255,6 +257,9 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
minTimeBetweenTrades: process.env.MIN_TIME_BETWEEN_TRADES
? parseInt(process.env.MIN_TIME_BETWEEN_TRADES)
: undefined,
minQualityScore: process.env.MIN_QUALITY_SCORE
? parseInt(process.env.MIN_QUALITY_SCORE)
: undefined,
}
}