Files
trading_bot_v4/lib/trading/stop-hunt-tracker.ts
mindesbunister 9b0b1d46ca fix: Change revenge system to 1.0x position sizing (same as original)
**ISSUE:** User operates at 100% capital allocation - no room for 1.2x sizing
- 1.2x would require 120% of capital (mathematically impossible)
- User: 'thats not gonna work. we are already using 100% of our portfolio'

**FIX:** Changed from 1.2x to 1.0x (same size as original trade)
- Focus on capturing reversal, not sizing bigger
- Maintains aggressive 15x leverage
- Example: Original ,350 → Revenge ,350 (not 0,020)

**FILES CHANGED:**
- lib/trading/stop-hunt-tracker.ts: sizingMultiplier 1.2 → 1.0
- Telegram notification: Updated to show 'same as original'
- Documentation: Updated all references to 1.0x strategy

**DEPLOYED:** Nov 20, 2025 ~20:30 CET
**BUILD TIME:** 71.8s, compiled successfully
**STATUS:** Container running stable, stop hunt tracker operational
2025-11-20 19:45:22 +01:00

409 lines
13 KiB
TypeScript

/**
* Stop Hunt Revenge Tracker (Nov 20, 2025)
*
* Tracks high-quality stop-outs (score 85+) and automatically re-enters
* when the stop hunt reverses and the real move begins.
*
* How it works:
* 1. When quality 85+ trade gets stopped out → Create StopHunt record
* 2. Monitor price for 4 hours
* 3. If price crosses back through original entry + ADX rebuilds → Auto re-enter with 1.2x size
* 4. Send "🔥 REVENGE TRADE" notification
*
* Purpose: Catch the real move after getting swept by stop hunters
*/
import { getPrismaClient } from '../database/trades'
import { initializeDriftService } from '../drift/client'
import { getPythPriceMonitor } from '../pyth/price-monitor'
interface StopHuntRecord {
id: string
originalTradeId: string
symbol: string
direction: 'long' | 'short'
stopHuntPrice: number
originalEntryPrice: number
originalQualityScore: number
originalADX: number | null
originalATR: number | null
stopLossAmount: number
stopHuntTime: Date
revengeExecuted: boolean
revengeWindowExpired: boolean
revengeExpiresAt: Date
highestPriceAfterStop: number | null
lowestPriceAfterStop: number | null
}
let trackerInstance: StopHuntTracker | null = null
let monitoringInterval: NodeJS.Timeout | null = null
export class StopHuntTracker {
private prisma = getPrismaClient()
private isMonitoring = false
/**
* Create stop hunt record when quality 85+ trade gets stopped out
*/
async recordStopHunt(params: {
originalTradeId: string
symbol: string
direction: 'long' | 'short'
stopHuntPrice: number
originalEntryPrice: number
originalQualityScore: number
originalADX?: number
originalATR?: number
stopLossAmount: number
}): Promise<void> {
// Only track quality 85+ stop-outs (high-confidence trades)
if (params.originalQualityScore < 85) {
console.log(`⚠️ Stop hunt not tracked: Quality ${params.originalQualityScore} < 85 threshold`)
return
}
const revengeExpiresAt = new Date(Date.now() + 4 * 60 * 60 * 1000) // 4 hours
try {
await this.prisma.stopHunt.create({
data: {
originalTradeId: params.originalTradeId,
symbol: params.symbol,
direction: params.direction,
stopHuntPrice: params.stopHuntPrice,
originalEntryPrice: params.originalEntryPrice,
originalQualityScore: params.originalQualityScore,
originalADX: params.originalADX || null,
originalATR: params.originalATR || null,
stopLossAmount: params.stopLossAmount,
stopHuntTime: new Date(),
revengeExpiresAt,
}
})
console.log(`🎯 STOP HUNT RECORDED: ${params.symbol} ${params.direction.toUpperCase()}`)
console.log(` Quality: ${params.originalQualityScore}, Loss: $${params.stopLossAmount.toFixed(2)}`)
console.log(` Revenge window: 4 hours (expires ${revengeExpiresAt.toLocaleTimeString()})`)
// Start monitoring if not already running
if (!this.isMonitoring) {
this.startMonitoring()
}
} catch (error) {
console.error('❌ Failed to record stop hunt:', error)
}
}
/**
* Start monitoring active stop hunts for revenge opportunities
*/
startMonitoring(): void {
if (this.isMonitoring) return
this.isMonitoring = true
console.log('🔍 Stop Hunt Revenge Tracker: Monitoring started')
// Check every 30 seconds
monitoringInterval = setInterval(async () => {
await this.checkRevengeOpportunities()
}, 30 * 1000)
}
/**
* Stop monitoring (cleanup on shutdown)
*/
stopMonitoring(): void {
if (monitoringInterval) {
clearInterval(monitoringInterval)
monitoringInterval = null
}
this.isMonitoring = false
console.log('🛑 Stop Hunt Revenge Tracker: Monitoring stopped')
}
/**
* Check all active stop hunts for revenge entry conditions
*/
private async checkRevengeOpportunities(): Promise<void> {
try {
// Get active stop hunts (not executed, not expired)
const activeStopHunts = await this.prisma.stopHunt.findMany({
where: {
revengeExecuted: false,
revengeWindowExpired: false,
revengeExpiresAt: {
gt: new Date() // Not expired yet
}
}
})
if (activeStopHunts.length === 0) {
// No active stop hunts, stop monitoring to save resources
if (this.isMonitoring) {
console.log('📊 No active stop hunts - pausing monitoring')
this.stopMonitoring()
}
return
}
console.log(`🔍 Checking ${activeStopHunts.length} active stop hunt(s)...`)
for (const stopHunt of activeStopHunts) {
await this.checkStopHunt(stopHunt as StopHuntRecord)
}
// Expire old stop hunts
await this.expireOldStopHunts()
} catch (error) {
console.error('❌ Error checking revenge opportunities:', error)
}
}
/**
* Check individual stop hunt for revenge entry
*/
private async checkStopHunt(stopHunt: StopHuntRecord): Promise<void> {
try {
// Get current price
const priceMonitor = getPythPriceMonitor()
const latestPrice = priceMonitor.getCachedPrice(stopHunt.symbol)
if (!latestPrice || !latestPrice.price) {
return // Price not available, skip
}
const currentPrice = latestPrice.price
// Update high/low tracking
const highestPrice = Math.max(currentPrice, stopHunt.highestPriceAfterStop || currentPrice)
const lowestPrice = Math.min(currentPrice, stopHunt.lowestPriceAfterStop || currentPrice)
await this.prisma.stopHunt.update({
where: { id: stopHunt.id },
data: {
highestPriceAfterStop: highestPrice,
lowestPriceAfterStop: lowestPrice,
}
})
// Check revenge conditions
const shouldRevenge = this.shouldExecuteRevenge(stopHunt, currentPrice)
if (shouldRevenge) {
console.log(`🔥 REVENGE CONDITIONS MET: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`)
await this.executeRevengeTrade(stopHunt, currentPrice)
}
} catch (error) {
console.error(`❌ Error checking stop hunt ${stopHunt.id}:`, error)
}
}
/**
* Determine if revenge entry conditions are met
*/
private shouldExecuteRevenge(stopHunt: StopHuntRecord, currentPrice: number): boolean {
const { direction, stopHuntPrice, originalEntryPrice } = stopHunt
// REVENGE CONDITION: Price must cross back through original entry
// This confirms the stop hunt has reversed and the real move is starting
if (direction === 'long') {
// Long stopped out above entry → price spiked up (stop hunt)
// Revenge: Price drops back below original entry (confirms down move)
const crossedBackDown = currentPrice < originalEntryPrice
const movedEnoughFromStop = currentPrice < stopHuntPrice * 0.995 // 0.5% below stop
if (crossedBackDown && movedEnoughFromStop) {
console.log(` ✅ LONG revenge: Price ${currentPrice.toFixed(2)} crossed back below entry ${originalEntryPrice.toFixed(2)}`)
return true
}
} else {
// Short stopped out below entry → price spiked down (stop hunt)
// Revenge: Price rises back above original entry (confirms up move)
const crossedBackUp = currentPrice > originalEntryPrice
const movedEnoughFromStop = currentPrice > stopHuntPrice * 1.005 // 0.5% above stop
if (crossedBackUp && movedEnoughFromStop) {
console.log(` ✅ SHORT revenge: Price ${currentPrice.toFixed(2)} crossed back above entry ${originalEntryPrice.toFixed(2)}`)
return true
}
}
return false
}
/**
* Execute revenge trade automatically
*/
private async executeRevengeTrade(stopHunt: StopHuntRecord, currentPrice: number): Promise<void> {
try {
console.log(`🔥 EXECUTING REVENGE TRADE: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`)
console.log(` Original loss: $${stopHunt.stopLossAmount.toFixed(2)}`)
console.log(` Revenge size: 1.2x (getting our money back!)`)
// Call execute endpoint with revenge parameters
const response = await fetch('http://localhost:3000/api/trading/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_SECRET_KEY}`
},
body: JSON.stringify({
symbol: stopHunt.symbol,
direction: stopHunt.direction,
currentPrice,
timeframe: 'revenge', // Special timeframe for revenge trades
signalSource: 'stop_hunt_revenge',
// Use original quality metrics
atr: stopHunt.originalATR || 0.45,
adx: stopHunt.originalADX || 32,
rsi: stopHunt.direction === 'long' ? 58 : 42,
volumeRatio: 1.2,
pricePosition: 50,
// Metadata
revengeMetadata: {
originalTradeId: stopHunt.originalTradeId,
stopHuntId: stopHunt.id,
originalLoss: stopHunt.stopLossAmount,
sizingMultiplier: 1.0 // Same size as original (user at 100% allocation)
}
})
})
const result = await response.json()
if (result.success) {
// Mark revenge as executed
await this.prisma.stopHunt.update({
where: { id: stopHunt.id },
data: {
revengeExecuted: true,
revengeTradeId: result.trade?.id,
revengeEntryPrice: currentPrice,
revengeTime: new Date(),
}
})
console.log(`✅ REVENGE TRADE EXECUTED: ${result.trade?.id}`)
console.log(`🔥 LET'S GET OUR MONEY BACK!`)
// Send special Telegram notification
await this.sendRevengeNotification(stopHunt, result.trade)
} else {
console.error(`❌ Revenge trade failed:`, result.error)
}
} catch (error) {
console.error(`❌ Error executing revenge trade:`, error)
}
}
/**
* Send special Telegram notification for revenge trades
*/
private async sendRevengeNotification(stopHunt: StopHuntRecord, trade: any): Promise<void> {
try {
const message = `
🔥 <b>REVENGE TRADE ACTIVATED</b> 🔥
<b>${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}</b>
💀 Original Stop Hunt: -$${stopHunt.stopLossAmount.toFixed(2)}
🎯 Revenge Entry: $${trade.entryPrice.toFixed(2)}
💪 Position Size: $${trade.positionSizeUSD.toFixed(2)} (same as original)
⚔️ <b>TIME FOR PAYBACK!</b>
Original Quality: ${stopHunt.originalQualityScore}/100
Stop Hunt Price: $${stopHunt.stopHuntPrice.toFixed(4)}
Reversal Confirmed: Price crossed back through entry
<i>Let's get our money back! 💰</i>
`.trim()
await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: process.env.TELEGRAM_CHAT_ID,
text: message,
parse_mode: 'HTML'
})
})
} catch (error) {
console.error('❌ Failed to send revenge notification:', error)
}
}
/**
* Expire stop hunts that are past their 4-hour window
*/
private async expireOldStopHunts(): Promise<void> {
try {
const expired = await this.prisma.stopHunt.updateMany({
where: {
revengeExecuted: false,
revengeWindowExpired: false,
revengeExpiresAt: {
lte: new Date()
}
},
data: {
revengeWindowExpired: true
}
})
if (expired.count > 0) {
console.log(`⏰ Expired ${expired.count} stop hunt revenge window(s)`)
}
} catch (error) {
console.error('❌ Error expiring stop hunts:', error)
}
}
}
/**
* Get singleton instance
*/
export function getStopHuntTracker(): StopHuntTracker {
if (!trackerInstance) {
trackerInstance = new StopHuntTracker()
}
return trackerInstance
}
/**
* Start tracking (called on server startup)
*/
export async function startStopHuntTracking(): Promise<void> {
try {
const tracker = getStopHuntTracker()
const prisma = getPrismaClient()
const activeCount = await prisma.stopHunt.count({
where: {
revengeExecuted: false,
revengeWindowExpired: false,
revengeExpiresAt: {
gt: new Date()
}
}
})
if (activeCount > 0) {
console.log(`🎯 Found ${activeCount} active stop hunt(s) - starting revenge tracker`)
tracker.startMonitoring()
} else {
console.log('📊 No active stop hunts - tracker will start when needed')
}
} catch (error) {
console.error('❌ Error starting stop hunt tracker:', error)
}
}