From 49a09ef04e516e5b604bfaddd4b1b6a0869b3623 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sat, 1 Nov 2025 02:41:26 +0100 Subject: [PATCH] Add 'Clear Manual Closes' button to analytics - New button in analytics page to clear orphaned trades - API endpoint /api/trading/clear-manual-closes - Intelligently checks Drift positions before deleting - Only removes trades with no matching position or mismatched entry price - Safe operation: keeps trades on error (false positives better than deletions) - User-friendly confirmation dialog --- .env | 2 +- app/analytics/page.tsx | 34 +++++- app/api/trading/clear-manual-closes/route.ts | 105 +++++++++++++++++++ 3 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 app/api/trading/clear-manual-closes/route.ts diff --git a/.env b/.env index 4d96fab..96d637e 100644 --- a/.env +++ b/.env @@ -352,4 +352,4 @@ NEW_RELIC_LICENSE_KEY= USE_TRAILING_STOP=true TRAILING_STOP_PERCENT=0.3 TRAILING_STOP_ACTIVATION=0.5 -MIN_QUALITY_SCORE=40 \ No newline at end of file +MIN_QUALITY_SCORE=50 \ No newline at end of file diff --git a/app/analytics/page.tsx b/app/analytics/page.tsx index 3ca991d..1d9c5a4 100644 --- a/app/analytics/page.tsx +++ b/app/analytics/page.tsx @@ -100,6 +100,29 @@ export default function AnalyticsPage() { setLoading(false) } + const clearManuallyClosed = async () => { + if (!confirm('Clear all open trades from database? Use this if you manually closed positions in Drift UI.')) { + return + } + + try { + const res = await fetch('/api/trading/clear-manual-closes', { + method: 'POST', + }) + + if (res.ok) { + alert('✅ Manually closed trades cleared from database') + loadData() // Reload data + } else { + const error = await res.json() + alert(`❌ Failed to clear: ${error.error}`) + } + } catch (error) { + console.error('Failed to clear trades:', error) + alert('❌ Failed to clear trades') + } + } + if (loading) { return (
@@ -160,7 +183,16 @@ export default function AnalyticsPage() { {/* Position Summary */} {positions && (
-

📍 Current Positions

+
+

📍 Current Positions

+ +
Open Trades
diff --git a/app/api/trading/clear-manual-closes/route.ts b/app/api/trading/clear-manual-closes/route.ts new file mode 100644 index 0000000..6de9545 --- /dev/null +++ b/app/api/trading/clear-manual-closes/route.ts @@ -0,0 +1,105 @@ +/** + * Clear Manually Closed Trades + * + * Deletes all "open" trades from database when user manually closed them in Drift UI + */ + +import { NextRequest, NextResponse } from 'next/server' +import { getPrismaClient } from '@/lib/database/trades' +import { initializeDriftService } from '@/lib/drift/client' +import { getMarketConfig } from '@/config/trading' + +export async function POST(request: NextRequest) { + try { + // Initialize Drift to check actual positions + const driftService = await initializeDriftService() + const prisma = getPrismaClient() + + // Get all "open" trades from database + const openTrades = await prisma.trade.findMany({ + where: { + status: 'open', + }, + select: { + id: true, + symbol: true, + direction: true, + entryPrice: true, + positionId: true, + }, + }) + + if (openTrades.length === 0) { + return NextResponse.json({ + message: 'No open trades to clear', + cleared: 0, + }) + } + + console.log(`🔍 Checking ${openTrades.length} open trades against Drift positions...`) + + // Check each trade against actual Drift position + const toClear: string[] = [] + + for (const trade of openTrades) { + try { + const marketConfig = getMarketConfig(trade.symbol) + const position = await driftService.getPosition(marketConfig.driftMarketIndex) + + if (position === null || position.size === 0) { + // No position on Drift = manually closed + console.log(`✅ Trade ${trade.symbol} has no Drift position - marking for deletion`) + toClear.push(trade.id) + } else { + // Position exists - check if entry price matches (within 0.5%) + const entryPriceDiff = Math.abs(position.entryPrice - trade.entryPrice) + const entryPriceDiffPercent = (entryPriceDiff / trade.entryPrice) * 100 + + if (entryPriceDiffPercent > 0.5) { + // Entry prices don't match = different position = old trade was closed + console.log(`✅ Trade ${trade.symbol} entry mismatch (DB: $${trade.entryPrice.toFixed(4)}, Drift: $${position.entryPrice.toFixed(4)}) - marking for deletion`) + toClear.push(trade.id) + } else { + console.log(`⏭️ Trade ${trade.symbol} still has matching position on Drift - keeping`) + } + } + } catch (error) { + console.error(`⚠️ Failed to check ${trade.symbol}:`, error) + // On error, don't delete (safer to keep false positives than delete real trades) + } + } + + // Delete the orphaned trades + if (toClear.length > 0) { + const result = await prisma.trade.deleteMany({ + where: { + id: { + in: toClear, + }, + }, + }) + + console.log(`🗑️ Cleared ${result.count} manually closed trades`) + + return NextResponse.json({ + message: `Cleared ${result.count} manually closed trade${result.count > 1 ? 's' : ''}`, + cleared: result.count, + tradeIds: toClear, + }) + } else { + return NextResponse.json({ + message: 'All open trades have matching positions on Drift', + cleared: 0, + }) + } + + } catch (error) { + console.error('❌ Failed to clear manually closed trades:', error) + return NextResponse.json( + { + error: error instanceof Error ? error.message : 'Unknown error', + }, + { status: 500 } + ) + } +}