Integrated MA gap analysis into signal quality evaluation pipeline: BACKEND SCORING (lib/trading/signal-quality.ts): - Added maGap?: number parameter to scoreSignalQuality interface - Implemented convergence/divergence scoring logic: * LONG: +15pts tight bullish (0-2%), +12pts converging (-2-0%), +8pts early momentum (-5--2%) * SHORT: +15pts tight bearish (-2-0%), +12pts converging (0-2%), +8pts early momentum (2-5%) * Penalties: -5pts for misaligned MA structure (>5% wrong direction) N8N PARSER (workflows/trading/parse_signal_enhanced.json): - Added MAGAP:([-\d.]+) regex pattern for negative number support - Extracts maGap from TradingView v9 alert messages - Returns maGap in parsed output (backward compatible with v8) - Updated comment to show v9 format API ENDPOINTS: - app/api/trading/check-risk/route.ts: Pass maGap to scoreSignalQuality (2 calls) - app/api/trading/execute/route.ts: Pass maGap to scoreSignalQuality (2 calls) FULL PIPELINE NOW COMPLETE: 1. TradingView v9 → Generates signal with MAGAP field 2. n8n webhook → Extracts maGap from alert message 3. Backend scoring → Evaluates MA gap convergence (+8 to +15 pts) 4. Quality threshold → Borderline signals (75-85) can reach 91+ 5. Execute decision → Only signals scoring ≥91 are executed MOTIVATION: Helps borderline quality signals reach execution threshold without overriding safety rules. Addresses Nov 25 missed opportunity where good signal had MA convergence but borderline quality score. TESTING REQUIRED: - Verify n8n parses MAGAP correctly from v9 alerts - Confirm backend receives maGap parameter - Validate MA gap scoring applied to quality calculation - Monitor first 10-20 v9 signals for scoring accuracy
12 KiB
Indicator v9: MA Gap Quality Enhancement
Status: 📋 PLANNED
Priority: HIGH (addresses $380 missed profit from Nov 25 blocked signal)
Motivation: v8 indicator catches trend changes BEFORE classic MA crossovers, but quality filter blocks these early signals
Problem Statement
Real Incident (Nov 25, 2025 21:15 UTC):
- v8 generated LONG signal at $136.91 (quality 75, ADX 17.9)
- Signal BLOCKED by quality threshold (75 < 90 required for LONGs)
- Chart showed 50 MA converging toward 200 MA (gap ≈ -1% to 0%)
- Golden cross occurred a few bars AFTER entry signal
- Price moved to $142+ = $380 missed profit (~4% move)
Key Insight:
- v8 indicator is BETTER than MA crossover timing - catches moves earlier
- BUT quality filter doesn't recognize when MAs are positioned for breakout
- Need to reward MA convergence/proximity, not just crossover events
v9 Enhancement: MA Gap Analysis
Core Concept
Instead of detecting exact crossover moment (lagging), measure MA gap percentage:
- Tight gap (0-2%) = Strong trend with momentum or imminent crossover
- Converging gap (-2% to 0%) = Potential golden cross brewing
- Wide gap (>2%) = Established trend, less explosive but stable
TradingView Indicator Changes
Add after context metrics calculation (~line 221):
// ═══════════════════════════════════════════════════════════
// 🎯 MA GAP ANALYSIS (v9 - for quality scoring)
// ═══════════════════════════════════════════════════════════
// Calculate 50 and 200 period moving averages
ma50 = ta.sma(calcC, 50)
ma200 = ta.sma(calcC, 200)
// Calculate MA gap as percentage (negative = 50 below 200)
maGap = ((ma50 - ma200) / ma200) * 100
// Detect convergence (MAs getting closer = potential crossover)
maConverging = math.abs(maGap) < 2.0 // Within 2% = tight squeeze
// Current alignment
maAlignmentBullish = ma50 > ma200
// Optional: Plot MAs on chart for visual confirmation
plot(ma50, title="MA 50", color=color.yellow, linewidth=1)
plot(ma200, title="MA 200", color=color.red, linewidth=1)
Update alert messages (~lines 257-258):
longAlertMsg = baseCurrency + " buy " + timeframe.period + " | ATR:" + str.tostring(atrPercent, "#.##") + " | ADX:" + str.tostring(adxVal, "#.#") + " | RSI:" + str.tostring(rsi14, "#.#") + " | VOL:" + str.tostring(volumeRatio, "#.##") + " | POS:" + str.tostring(pricePosition, "#.#") + " | MAGAP:" + str.tostring(maGap, "#.##") + " | IND:v9"
shortAlertMsg = baseCurrency + " sell " + timeframe.period + " | ATR:" + str.tostring(atrPercent, "#.##") + " | ADX:" + str.tostring(adxVal, "#.#") + " | RSI:" + str.tostring(rsi14, "#.#") + " | VOL:" + str.tostring(volumeRatio, "#.##") + " | POS:" + str.tostring(pricePosition, "#.#") + " | MAGAP:" + str.tostring(maGap, "#.##") + " | IND:v9"
Backend Quality Scoring Changes
Update scoreSignalQuality() function
File: lib/trading/signal-quality.ts
Add parameters:
export async function scoreSignalQuality(params: {
// ... existing params ...
maGap?: number // NEW: % gap between 50 and 200 MA
maAlignmentBullish?: boolean // NEW: is 50 above 200?
}): Promise<SignalQualityResult> {
Add scoring logic (after existing metrics):
// ═══════════════════════════════════════════════════════════
// MA Gap Analysis (v9 - Nov 26, 2025)
// ═══════════════════════════════════════════════════════════
if (params.maGap !== undefined) {
if (params.direction === 'long') {
// LONG scenarios
if (params.maGap >= 0 && params.maGap < 2.0) {
// 50 MA above 200 MA, tight gap (0-2%)
// = Bullish trend with momentum OR fresh golden cross
score += 15
reasons.push(`🎯 MA bullish + tight gap (${params.maGap.toFixed(2)}%) = strong momentum (+15 pts)`)
} else if (params.maGap < 0 && params.maGap > -2.0) {
// 50 MA below 200 MA but converging (-2% to 0%)
// = Potential golden cross brewing (early detection like Nov 25 signal!)
score += 12
reasons.push(`🌟 MA converging (${params.maGap.toFixed(2)}%) = golden cross potential (+12 pts)`)
} else if (params.maGap >= 2.0 && params.maGap < 5.0) {
// 50 MA well above 200 MA (2-5%)
// = Established bullish trend, stable but less explosive
score += 8
reasons.push(`📈 MA strong bullish trend (${params.maGap.toFixed(2)}%) (+8 pts)`)
} else if (params.maGap >= 5.0) {
// 50 MA far above 200 MA (>5%)
// = Very extended, potential exhaustion
score += 5
reasons.push(`⚠️ MA extended bullish (${params.maGap.toFixed(2)}%) = overbought risk (+5 pts)`)
} else if (params.maGap <= -2.0) {
// 50 MA significantly below 200 MA
// = Bearish trend, LONG signal is counter-trend (risky)
score -= 10
reasons.push(`❌ MA bearish divergence (${params.maGap.toFixed(2)}%) = counter-trend risk (-10 pts)`)
}
} else if (params.direction === 'short') {
// SHORT scenarios (inverse of LONG logic)
if (params.maGap <= 0 && params.maGap > -2.0) {
// 50 MA below 200 MA, tight gap (-2% to 0%)
// = Bearish trend with momentum OR fresh death cross
score += 15
reasons.push(`🎯 MA bearish + tight gap (${params.maGap.toFixed(2)}%) = strong momentum (+15 pts)`)
} else if (params.maGap > 0 && params.maGap < 2.0) {
// 50 MA above 200 MA but converging (0% to 2%)
// = Potential death cross brewing
score += 12
reasons.push(`🌟 MA converging (${params.maGap.toFixed(2)}%) = death cross potential (+12 pts)`)
} else if (params.maGap <= -2.0 && params.maGap > -5.0) {
// 50 MA well below 200 MA (-5% to -2%)
// = Established bearish trend
score += 8
reasons.push(`📉 MA strong bearish trend (${params.maGap.toFixed(2)}%) (+8 pts)`)
} else if (params.maGap <= -5.0) {
// 50 MA far below 200 MA (<-5%)
// = Very extended, potential bounce risk
score += 5
reasons.push(`⚠️ MA extended bearish (${params.maGap.toFixed(2)}%) = oversold risk (+5 pts)`)
} else if (params.maGap >= 2.0) {
// 50 MA significantly above 200 MA
// = Bullish trend, SHORT signal is counter-trend (risky)
score -= 10
reasons.push(`❌ MA bullish divergence (${params.maGap.toFixed(2)}%) = counter-trend risk (-10 pts)`)
}
}
}
Expected Impact
Nov 25 21:15 Signal Reanalysis
Original (v8):
- Quality: 75 (blocked)
- ADX: 17.9 (weak)
- MA Gap: ≈ -1.0% (50 below 200, converging)
With v9 MA Gap Enhancement:
- Base quality: 75
- MA converging bonus: +12
- New quality: 87 (closer but still blocked)
Note: Would still need ADX improvement OR slightly lower threshold (88-89?) to catch this specific signal. But the +12 points get us much closer and would catch signals with ADX 18-20.
Alternative Scenarios
Scenario A: MA Gap -0.5% (very tight convergence)
- Quality 75 + 12 = 87 (close)
Scenario B: MA Gap +0.5% (just crossed, tight)
- Quality 75 + 15 = 90 ✅ PASSES!
Scenario C: MA Gap +1.8% (recent golden cross, momentum strong)
- Quality 75 + 15 = 90 ✅ PASSES!
Implementation Checklist
Phase 1: TradingView Indicator
- Add MA50 and MA200 calculations
- Calculate maGap percentage
- Add maGap to alert message payload
- Optional: Plot MA lines on chart
- Update indicatorVer from "v8" to "v9"
- Test on SOL-PERP 5min chart
Phase 2: Backend Integration
- Update TypeScript interfaces for maGap parameter
- Modify scoreSignalQuality() with MA gap logic
- Update check-risk endpoint to accept maGap
- Update execute endpoint to accept maGap
- Add maGap to database fields (Trade table, BlockedSignal table)
Phase 3: Testing & Validation
- Deploy v9 indicator to TradingView
- Trigger test signals manually
- Verify maGap calculation matches chart visual
- Check quality scores increase appropriately
- Monitor first 20-30 signals for validation
Phase 4: Data Collection
- Collect 50+ v9 signals
- Compare v8 vs v9 win rates
- Analyze: Did MA gap bonus catch missed winners?
- SQL queries to validate improvement
- Adjust bonus points if needed (12/15 → 10/12 or 15/18)
Success Metrics
Target Improvements:
- Catch signals like Nov 25 21:15: Quality 75 + MA converging → 87-90 range
- Reduce false negatives: Fewer blocked signals that would have been winners
- Maintain safety: Don't add too many low-quality signals
- Win rate: v9 should maintain or improve v8's 57.1% WR
Validation Queries:
-- Compare v9 MA gap bonus impact
SELECT
CASE
WHEN "signalQualityScore" >= 90 THEN 'Passed'
WHEN "signalQualityScore" >= 80 THEN 'Close (80-89)'
ELSE 'Blocked (<80)'
END as category,
COUNT(*) as signals,
AVG("scoreBreakdown"->>'maGap')::numeric as avg_ma_gap
FROM "BlockedSignal"
WHERE "indicatorVersion" = 'v9'
AND "scoreBreakdown"->>'maGap' IS NOT NULL
GROUP BY category;
-- Find signals that would pass with v9 but were blocked in v8
SELECT
TO_CHAR("createdAt", 'MM-DD HH24:MI') as time,
symbol, direction,
"signalQualityScore" as original_score,
-- Simulate v9 score (add 12 for converging, 15 for tight)
CASE
WHEN ("scoreBreakdown"->>'maGap')::numeric >= 0 AND ("scoreBreakdown"->>'maGap')::numeric < 2.0
THEN "signalQualityScore" + 15
WHEN ("scoreBreakdown"->>'maGap')::numeric < 0 AND ("scoreBreakdown"->>'maGap')::numeric > -2.0
THEN "signalQualityScore" + 12
ELSE "signalQualityScore"
END as v9_score,
"blockReason"
FROM "BlockedSignal"
WHERE "signalQualityScore" < 90 -- Was blocked
AND "indicatorVersion" = 'v8'
ORDER BY "createdAt" DESC
LIMIT 20;
Risk Mitigation
Potential Issues
-
MA calculation lag: 200-period MA requires significant history
- Mitigation: TradingView has full history, no issue
-
Whipsaw during sideways markets: MAs converge often in chop
- Mitigation: ADX filter still applies (weak ADX = less bonus effect)
-
Over-optimization on single signal: Nov 25 may be outlier
- Mitigation: Collect 50+ v9 signals before final judgment
-
Bonus points too generous: Could inflate scores artificially
- Mitigation: Start conservative (12/15), adjust based on data
Rollback Plan
If v9 performs worse than v8:
- Revert TradingView indicator to v8
- Keep backend code but disable MA gap bonus
- Analyze what went wrong (false positives? whipsaw signals?)
- Redesign MA gap logic with tighter conditions
Timeline
Estimated Implementation Time:
- TradingView changes: 30 minutes
- Backend integration: 1 hour
- Testing & deployment: 30 minutes
- Total: ~2 hours
Data Collection:
- Minimum 50 signals: 2-3 weeks (at ~3-5 signals/day)
- Comparative analysis: 1 week after 50 signals
Decision Point:
- After 50 v9 signals: Keep, adjust, or revert based on performance data
Notes
- This enhancement preserves v8's early detection advantage
- Adds context awareness of MA positioning
- Rewards both imminent crossovers (converging) AND fresh crossovers (tight gap)
- Balances explosive potential (tight gaps) with trend stability (wider gaps)
- Counter-trend penalties prevent chasing wrong direction
Key Insight: v8 catches momentum shifts BEFORE visible MA crossovers. v9 validates those shifts by checking if MA structure supports the move.
Created: Nov 26, 2025
Motivation: $380 missed profit from Nov 25 21:15 blocked signal
Expected Impact: Catch 15-25% more profitable signals while maintaining quality standards