From 01aaa0932a1b6c683cd662efd9d04b9dcfbdc1b1 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sun, 23 Nov 2025 15:01:56 +0100 Subject: [PATCH] feat: Direction-specific quality thresholds (long=90, short=95) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DATA-DRIVEN: 227 trades analysis showed longs 71.4% WR vs shorts 28.6% WR at quality 90-94 - LONG threshold: 90 (captures profitable 90-94 signals: +4.77 total, +.40 avg) - SHORT threshold: 95 (blocks toxic 90-94 signals: -53.76 total, -9.11 avg) - Historical validation: Quality 90+ longs +00.62 vs shorts -77.90 Modified files: - config/trading.ts: Added minSignalQualityScoreLong/Short fields + getMinQualityScoreForDirection() - lib/trading/signal-quality.ts: Accept direction-specific minScore parameter - app/api/trading/check-risk/route.ts: Use direction-specific thresholds - .env: Added MIN_SIGNAL_QUALITY_SCORE_LONG=90 and _SHORT=95 Fallback logic: direction-specific → global → 60 default Backward compatible with existing code --- .env | 2 + .github/copilot-instructions.md | 70 ++++++++++++++++++++++++++++- app/api/trading/check-risk/route.ts | 12 +++-- config/trading.ts | 34 ++++++++++++-- lib/trading/signal-quality.ts | 2 + 5 files changed, 111 insertions(+), 9 deletions(-) diff --git a/.env b/.env index 6840adf..3cfa0e1 100644 --- a/.env +++ b/.env @@ -391,6 +391,8 @@ USE_TRAILING_STOP=true TRAILING_STOP_PERCENT=0.5 TRAILING_STOP_ACTIVATION=0.4 MIN_SIGNAL_QUALITY_SCORE=91 +MIN_SIGNAL_QUALITY_SCORE_LONG=90 # Nov 23, 2025: Longs have 71.4% WR at quality 90-94 (+$44.77 on 7 trades) +MIN_SIGNAL_QUALITY_SCORE_SHORT=95 # Nov 23, 2025: Shorts toxic at quality 90-94 (28.6% WR, -$553.76 on 7 trades) SOLANA_ENABLED=true SOLANA_POSITION_SIZE=100 SOLANA_LEVERAGE=15 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1e16db1..8abb294 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2900,7 +2900,75 @@ trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK * Runner P&L calculated on actual runner size - **Lesson:** Container restarts during active trades are high-risk events. All startup validation MUST use correct schema fields and understand trade lifecycle state (pre-TP1 vs post-TP1). -54. **Settings UI quality score variable name mismatch (CRITICAL - Fixed Nov 19, 2025):** +54. **MFE/MAE storing dollars instead of percentages (CRITICAL - Fixed Nov 23, 2025):** + - **Symptom:** Database showing maxFavorableExcursion = 64.08% when TradingView charts showed 0.48% actual max profit + - **Root Cause:** Position Manager storing DOLLAR amounts instead of PERCENTAGES in MFE/MAE fields + - **Discovery:** User provided TradingView screenshots showing 0.48% max profit, database query showed 64.08% stored value + - **Real incident (Nov 22-23, 2025):** + * Trade cmiahpupc0000pe07g2dh58ow (quality 90 SHORT) + * Actual max profit: 0.48% per TradingView chart + * Database stored: 64.08 (interpreted as 64.08%) + * Actual calculation: $64.08 profit / $7,756 position = 0.83% + * Even 0.83% was wrong - actual TradingView showed 0.48% + * **Discrepancy: 133× inflation (64.08% vs 0.48%)** + - **Bug mechanism:** + ```typescript + // BEFORE (BROKEN - line 1127 of position-manager.ts): + const profitPercent = this.calculateProfitPercent(entry, currentPrice, direction) + const currentPnLDollars = (trade.currentSize * profitPercent) / 100 + + // Track MAE/MFE in DOLLAR amounts (not percentages!) ← WRONG COMMENT + // CRITICAL: Database schema expects DOLLARS ← WRONG ASSUMPTION + if (currentPnLDollars > trade.maxFavorableExcursion) { + trade.maxFavorableExcursion = currentPnLDollars // Storing $64.08 + trade.maxFavorablePrice = currentPrice + } + if (currentPnLDollars < trade.maxAdverseExcursion) { + trade.maxAdverseExcursion = currentPnLDollars // Storing $-82.59 + trade.maxAdversePrice = currentPrice + } + + // AFTER (FIXED): + // Track MAE/MFE in PERCENTAGE (not dollars!) + // CRITICAL FIX (Nov 23, 2025): Schema expects % (0.48 = 0.48%), not dollar amounts + // Bug was storing $64.08 when actual was 0.48%, causing 100× inflation in analysis + if (profitPercent > trade.maxFavorableExcursion) { + trade.maxFavorableExcursion = profitPercent // Storing 0.48% + trade.maxFavorablePrice = currentPrice + } + if (profitPercent < trade.maxAdverseExcursion) { + trade.maxAdverseExcursion = profitPercent // Storing -0.82% + trade.maxAdversePrice = currentPrice + } + ``` + - **Schema confirmation:** + ```prisma + // prisma/schema.prisma lines 54-55 + maxFavorableExcursion Float? // Best profit % reached during trade + maxAdverseExcursion Float? // Worst drawdown % during trade + ``` + - **Impact:** + * All 14 quality 90 trades: MFE/MAE values inflated by 100-133× + * Example: Database 64.08% when actual 0.48% = 133× inflation + * Quality tier analysis: Used wrong MFE values but directional conclusions valid + * TP1-only simulations: Percentages wrong but improvement trend correct + * Historical data: Cannot auto-correct (requires manual TradingView chart review) + * Future trades: Will track correctly with deployed fix + - **User response:** "first we need to find the reason why we store wrong data. thats a big problem" + - **Investigation:** Grep searched position-manager.ts for MFE assignments, found line 1127 storing currentPnLDollars + - **Fix implementation:** + * Changed assignment from currentPnLDollars to profitPercent + * Updated comment explaining percentage storage + * Docker build: Completed successfully (~90 seconds) + * Container restart: 13:18:54 UTC Nov 23, 2025 + * Git commit: 6255662 "critical: Fix MFE/MAE storing dollars instead of percentages" + * Verification: Container timestamp 50 seconds newer than commit ✅ + - **Validation required:** Monitor next trade's MFE/MAE values, compare to TradingView chart + - **Expected behavior:** Should show ~0.5% max profit, not ~50% (percentages not dollars) + - **Status:** ✅ Fix deployed and running in production + - **Lesson:** Always verify data storage units match schema expectations. Comments saying "stores dollars" don't override schema comments saying "stores percentages." When user reports data discrepancies between charts and database, investigate storage logic immediately - don't assume data is correct. All financial metrics need unit validation (dollars vs percentages, tokens vs USD, etc.). + +55. **Settings UI quality score variable name mismatch (CRITICAL - Fixed Nov 19, 2025):** - **Symptom:** User changes "Min Signal Quality" in settings UI (e.g., 60 → 81), but trades continue executing with old threshold - **Root Cause:** Settings API reading/writing wrong ENV variable name - **Variable name inconsistency:** diff --git a/app/api/trading/check-risk/route.ts b/app/api/trading/check-risk/route.ts index ff8b2aa..7ecc883 100644 --- a/app/api/trading/check-risk/route.ts +++ b/app/api/trading/check-risk/route.ts @@ -6,7 +6,7 @@ */ import { NextRequest, NextResponse } from 'next/server' -import { getMergedConfig, TradingConfig } from '@/config/trading' +import { getMergedConfig, TradingConfig, getMinQualityScoreForDirection } from '@/config/trading' import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager' import { getLastTradeTime, getLastTradeTimeForSymbol, getTradesInLastHour, getTodayPnL, createBlockedSignal } from '@/lib/database/trades' import { getPythPriceMonitor } from '@/lib/pyth/price-monitor' @@ -329,6 +329,9 @@ export async function POST(request: NextRequest): Promise { minSignalQualityScore: process.env.MIN_SIGNAL_QUALITY_SCORE ? parseInt(process.env.MIN_SIGNAL_QUALITY_SCORE) : undefined, + minSignalQualityScoreLong: process.env.MIN_SIGNAL_QUALITY_SCORE_LONG + ? parseInt(process.env.MIN_SIGNAL_QUALITY_SCORE_LONG) + : undefined, + minSignalQualityScoreShort: process.env.MIN_SIGNAL_QUALITY_SCORE_SHORT + ? parseInt(process.env.MIN_SIGNAL_QUALITY_SCORE_SHORT) + : undefined, enablePositionScaling: process.env.ENABLE_POSITION_SCALING ? process.env.ENABLE_POSITION_SCALING === 'true' : undefined, @@ -587,6 +599,20 @@ export function getConfigFromEnv(): Partial { return config } +// Get minimum quality score for a specific direction (Nov 23, 2025) +export function getMinQualityScoreForDirection( + direction: 'long' | 'short', + config: TradingConfig +): number { + if (direction === 'long' && config.minSignalQualityScoreLong !== undefined) { + return config.minSignalQualityScoreLong + } + if (direction === 'short' && config.minSignalQualityScoreShort !== undefined) { + return config.minSignalQualityScoreShort + } + return config.minSignalQualityScore +} + // Merge configurations export function getMergedConfig( overrides?: Partial diff --git a/lib/trading/signal-quality.ts b/lib/trading/signal-quality.ts index e9be718..6ca430a 100644 --- a/lib/trading/signal-quality.ts +++ b/lib/trading/signal-quality.ts @@ -263,6 +263,8 @@ export async function scoreSignalQuality(params: { } } + // Direction-specific threshold support (Nov 23, 2025) + // Use provided minScore, or fall back to 60 if not specified const minScore = params.minScore || 60 const passed = score >= minScore