From 0700daf8ffc2299ffaea115b81d2c0525754f03a Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Tue, 11 Nov 2025 12:53:33 +0100 Subject: [PATCH] feat: add indicator version tracking system Database changes: - Added indicatorVersion field to Trade table - Added indicatorVersion field to BlockedSignal table - Tracks which Pine Script version (v5, v6, etc.) generated each signal Pine Script changes: - v6 now includes '| IND:v6' in alert messages - Enables differentiation between v5 and v6 signals in database Documentation: - Created INDICATOR_VERSION_TRACKING.md with full implementation guide - Includes n8n workflow update instructions - Includes SQL analysis queries for v5 vs v6 comparison - Includes rollback plan if needed Next steps (manual): 1. Update n8n workflow Parse Signal Enhanced node to extract IND field 2. Update n8n HTTP requests to pass indicatorVersion 3. Update API endpoints to accept and save indicatorVersion 4. Rebuild Docker container Benefits: - Compare v5 vs v6 Pine Script effectiveness - Track which version generated winning/losing trades - Validate that v6 price position filter reduces blocked signals - Data-driven decisions on Pine Script improvements --- INDICATOR_VERSION_TRACKING.md | 225 ++++++++++++++++++ prisma/schema.prisma | 4 +- .../trading/moneyline_v6_improved.pinescript | 9 +- 3 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 INDICATOR_VERSION_TRACKING.md diff --git a/INDICATOR_VERSION_TRACKING.md b/INDICATOR_VERSION_TRACKING.md new file mode 100644 index 0000000..6a3e2b9 --- /dev/null +++ b/INDICATOR_VERSION_TRACKING.md @@ -0,0 +1,225 @@ +# Indicator Version Tracking System + +**Date:** November 11, 2025 +**Purpose:** Track which Pine Script version generated each signal for comparative analysis + +## Changes Made + +### 1. Database Schema (`prisma/schema.prisma`) +Added `indicatorVersion` field to both tables: + +```prisma +model Trade { + // ... existing fields ... + indicatorVersion String? // Pine Script version (v5, v6, etc.) +} + +model BlockedSignal { + // ... existing fields ... + indicatorVersion String? // Pine Script version (v5, v6, etc.) +} +``` + +### 2. Pine Script v6 (`moneyline_v6_improved.pinescript`) +Added version identifier to alert messages: + +```pinescript +// Line 245-247 +indicatorVer = "v6" + +// Alert messages now include: | IND:v6 +longAlertMsg = "SOL buy 5 | ATR:0.45 | ADX:28.3 | RSI:62.5 | VOL:1.45 | POS:75.3 | IND:v6" +shortAlertMsg = "SOL sell 5 | ATR:0.45 | ADX:28.3 | RSI:62.5 | VOL:1.45 | POS:75.3 | IND:v6" +``` + +### 3. n8n Workflow Update (REQUIRED) + +**File:** `workflows/trading/Money_Machine.json` +**Node:** `Parse Signal Enhanced` (JavaScript code) + +**Add this code after the pricePosition extraction:** + +```javascript +// Extract indicator version (v5, v6, etc.) +const indicatorMatch = body.match(/IND:([a-z0-9]+)/i); +const indicatorVersion = indicatorMatch ? indicatorMatch[1] : 'v5'; // Default to v5 for old signals + +return { + rawMessage: body, + symbol, + direction, + timeframe, + // Context fields + atr, + adx, + rsi, + volumeRatio, + pricePosition, + // NEW: Indicator version + indicatorVersion +}; +``` + +**Then update the HTTP request nodes to include it:** + +**Check Risk Request:** +```json +{ + "symbol": "{{ $('Parse Signal Enhanced').item.json.symbol }}", + "direction": "{{ $('Parse Signal Enhanced').item.json.direction }}", + "timeframe": "{{ $('Parse Signal Enhanced').item.json.timeframe }}", + "atr": {{ $('Parse Signal Enhanced').item.json.atr }}, + "adx": {{ $('Parse Signal Enhanced').item.json.adx }}, + "rsi": {{ $('Parse Signal Enhanced').item.json.rsi }}, + "volumeRatio": {{ $('Parse Signal Enhanced').item.json.volumeRatio }}, + "pricePosition": {{ $('Parse Signal Enhanced').item.json.pricePosition }}, + "indicatorVersion": "{{ $('Parse Signal Enhanced').item.json.indicatorVersion }}" +} +``` + +**Execute Trade Request:** (same addition) + +### 4. API Endpoints Update (REQUIRED) + +**Files to update:** +- `app/api/trading/check-risk/route.ts` +- `app/api/trading/execute/route.ts` + +**Add to request body interface:** +```typescript +interface RequestBody { + symbol: string + direction: 'long' | 'short' + timeframe?: string + atr?: number + adx?: number + rsi?: number + volumeRatio?: number + pricePosition?: number + indicatorVersion?: string // NEW +} +``` + +**Pass to database functions:** +```typescript +await createTrade({ + // ... existing params ... + indicatorVersion: body.indicatorVersion || 'v5' +}) + +await createBlockedSignal({ + // ... existing params ... + indicatorVersion: body.indicatorVersion || 'v5' +}) +``` + +## Database Migration + +Run this to apply schema changes: + +```bash +# Generate Prisma client with new fields +npx prisma generate + +# Push schema to database +npx prisma db push + +# Rebuild Docker container +docker compose build trading-bot +docker compose up -d trading-bot +``` + +## Analysis Queries + +### Compare v5 vs v6 Performance + +```sql +-- Executed trades by indicator version +SELECT + indicatorVersion, + COUNT(*) as trades, + ROUND(AVG(realizedPnL)::numeric, 2) as avg_pnl, + ROUND(SUM(realizedPnL)::numeric, 2) as total_pnl, + ROUND(100.0 * SUM(CASE WHEN realizedPnL > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate +FROM "Trade" +WHERE exitReason IS NOT NULL + AND indicatorVersion IS NOT NULL +GROUP BY indicatorVersion +ORDER BY indicatorVersion; +``` + +### Blocked signals by version + +```sql +-- Blocked signals by indicator version +SELECT + indicatorVersion, + COUNT(*) as blocked_count, + ROUND(AVG(signalQualityScore)::numeric, 1) as avg_score, + blockReason, + COUNT(*) as count_per_reason +FROM "BlockedSignal" +WHERE indicatorVersion IS NOT NULL +GROUP BY indicatorVersion, blockReason +ORDER BY indicatorVersion, count_per_reason DESC; +``` + +### v6 effectiveness check + +```sql +-- Did v6 reduce blocked signals at range extremes? +SELECT + indicatorVersion, + CASE + WHEN pricePosition < 15 OR pricePosition > 85 THEN 'Range Extreme' + ELSE 'Normal Range' + END as position_type, + COUNT(*) as count +FROM "BlockedSignal" +WHERE indicatorVersion IN ('v5', 'v6') + AND pricePosition IS NOT NULL +GROUP BY indicatorVersion, position_type +ORDER BY indicatorVersion, position_type; +``` + +## Expected Results + +**v5 signals:** +- Should show more blocked signals at range extremes (< 15% or > 85%) +- Higher percentage of signals blocked for QUALITY_SCORE_TOO_LOW + +**v6 signals:** +- Should show fewer/zero blocked signals at range extremes (filtered in Pine Script) +- Higher average quality scores +- Most signals should score 70+ + +## Rollback Plan + +If v6 performs worse: + +1. **Revert Pine Script:** Change `indicatorVer = "v5"` in v6 script +2. **Or use v5 script:** Just switch back to `moneyline_v5_final.pinescript` +3. **Database keeps working:** Old signals tagged as v5, new as v6 +4. **Analysis remains valid:** Can compare both versions historically + +## Testing Checklist + +- [ ] Database schema updated (`npx prisma db push`) +- [ ] Prisma client regenerated (`npx prisma generate`) +- [ ] Docker container rebuilt +- [ ] n8n workflow updated (Parse Signal Enhanced node) +- [ ] n8n HTTP requests updated (Check Risk + Execute Trade) +- [ ] v6 Pine Script deployed to TradingView +- [ ] Test signal fires and `indicatorVersion` appears in database +- [ ] SQL queries return v6 data correctly + +## Notes + +- **Backward compatible:** Old signals without version default to 'v5' +- **No data loss:** Existing trades remain unchanged +- **Immediate effect:** Once n8n updated, all new signals tagged with version +- **Analysis ready:** Can compare v5 vs v6 after 10+ signals each + +--- + +**Status:** Database and Pine Script updated. n8n workflow update REQUIRED before v6 tracking works. diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2e3d710..8ac685c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -77,7 +77,8 @@ model Trade { volumeAtEntry Float? // Volume relative to MA pricePositionAtEntry Float? // Price position in range (0-100%) signalQualityScore Int? // Calculated quality score (0-100) - signalQualityVersion String? @default("v1") // Tracks which scoring logic was used + signalQualityVersion String? @default("v4") // Tracks which scoring logic was used + indicatorVersion String? // Pine Script version (v5, v6, etc.) fundingRateAtEntry Float? // Perp funding rate at entry basisAtEntry Float? // Perp-spot basis at entry @@ -177,6 +178,7 @@ model BlockedSignal { signalQualityVersion String? // Which scoring version scoreBreakdown Json? // Detailed breakdown of score components minScoreRequired Int // What threshold was used (e.g., 65) + indicatorVersion String? // Pine Script version (v5, v6, etc.) // Block reason blockReason String // "QUALITY_SCORE_TOO_LOW", "DUPLICATE", "COOLDOWN", etc. diff --git a/workflows/trading/moneyline_v6_improved.pinescript b/workflows/trading/moneyline_v6_improved.pinescript index 80b8965..f91efd3 100644 --- a/workflows/trading/moneyline_v6_improved.pinescript +++ b/workflows/trading/moneyline_v6_improved.pinescript @@ -220,10 +220,13 @@ baseCurrency = str.replace(syminfo.ticker, "USD", "") baseCurrency := str.replace(baseCurrency, "USDT", "") baseCurrency := str.replace(baseCurrency, "PERP", "") -// Build enhanced alert messages with context (timeframe.period is dynamic) -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, "#.#") +// Indicator version for tracking in database +indicatorVer = "v6" -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, "#.#") +// Build enhanced alert messages with context (timeframe.period is dynamic) +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, "#.#") + " | IND:" + indicatorVer + +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, "#.#") + " | IND:" + indicatorVer // Fire alerts with dynamic messages (use alert() not alertcondition() for dynamic content) if finalLongSignal