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
This commit is contained in:
mindesbunister
2025-11-01 02:41:26 +01:00
parent c82da51bdc
commit 49a09ef04e
3 changed files with 139 additions and 2 deletions

2
.env
View File

@@ -352,4 +352,4 @@ NEW_RELIC_LICENSE_KEY=
USE_TRAILING_STOP=true USE_TRAILING_STOP=true
TRAILING_STOP_PERCENT=0.3 TRAILING_STOP_PERCENT=0.3
TRAILING_STOP_ACTIVATION=0.5 TRAILING_STOP_ACTIVATION=0.5
MIN_QUALITY_SCORE=40 MIN_QUALITY_SCORE=50

View File

@@ -100,6 +100,29 @@ export default function AnalyticsPage() {
setLoading(false) 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) { if (loading) {
return ( return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 flex items-center justify-center"> <div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 flex items-center justify-center">
@@ -160,7 +183,16 @@ export default function AnalyticsPage() {
{/* Position Summary */} {/* Position Summary */}
{positions && ( {positions && (
<div className="mb-8"> <div className="mb-8">
<h2 className="text-xl font-bold text-white mb-4">📍 Current Positions</h2> <div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold text-white">📍 Current Positions</h2>
<button
onClick={clearManuallyClosed}
className="px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg text-white text-sm font-semibold transition-colors"
title="Clear open trades from database if you manually closed them in Drift UI"
>
🗑 Clear Manual Closes
</button>
</div>
<div className="grid md:grid-cols-4 gap-4"> <div className="grid md:grid-cols-4 gap-4">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700"> <div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Open Trades</div> <div className="text-sm text-gray-400 mb-1">Open Trades</div>

View File

@@ -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 }
)
}
}