diff --git a/config.default.json b/config.default.json index 9b1dcf8..2c03d3e 100644 --- a/config.default.json +++ b/config.default.json @@ -5,6 +5,7 @@ }, "symbols": { "ASTERUSDT": { + "trade_side": "OPPOSITE", "longVolumeThresholdUSDT": 1000, "shortVolumeThresholdUSDT": 2500, "tradeSize": 0.69, diff --git a/src/components/SymbolConfigForm.tsx b/src/components/SymbolConfigForm.tsx index bb180e3..8bb6b20 100644 --- a/src/components/SymbolConfigForm.tsx +++ b/src/components/SymbolConfigForm.tsx @@ -79,6 +79,7 @@ export default function SymbolConfigForm({ onSave, currentConfig }: SymbolConfig priceOffsetBps: 5, // 5 basis points offset for limit orders maxSlippageBps: 50, // 50 basis points max slippage orderType: 'LIMIT' as const, + trade_side: 'OPPOSITE' as const, // Default to contrarian strategy vwapProtection: false, // VWAP protection disabled by default vwapTimeframe: '1m', // Default to 1 minute timeframe vwapLookback: 100, // Default to 100 candles @@ -1065,6 +1066,43 @@ export default function SymbolConfigForm({ onSave, currentConfig }: SymbolConfig

+ {/* Trade Direction Strategy */} +
+ + +

+ {config.symbols[selectedSymbol].trade_side === 'SAME' + ? 'Buy on buy liquidations, sell on sell liquidations (momentum strategy)' + : 'Buy on sell liquidations, sell on buy liquidations (contrarian strategy - default)'} +

+
+ {/* VWAP Protection Settings */}
@@ -1251,4 +1289,4 @@ export default function SymbolConfigForm({ onSave, currentConfig }: SymbolConfig
); -} \ No newline at end of file +} diff --git a/src/lib/bot/hunter.ts b/src/lib/bot/hunter.ts index 3c7e890..0e94512 100644 --- a/src/lib/bot/hunter.ts +++ b/src/lib/bot/hunter.ts @@ -436,11 +436,22 @@ export class Hunter extends EventEmitter { // Check if we should use threshold system or instant trigger if (useThresholdSystem && thresholdStatus) { - // NEW THRESHOLD SYSTEM - Cumulative volume in 60-second window - // SELL liquidation means longs are getting liquidated, we might want to BUY - // BUY liquidation means shorts are getting liquidated, we might want to SELL - const isLongOpportunity = liquidation.side === 'SELL'; - const isShortOpportunity = liquidation.side === 'BUY'; + // * NEW THRESHOLD SYSTEM - Cumulative volume in 60-second window + // * Determine opportunity direction based on trade_side parameter + const tradeDirection = symbolConfig.trade_side || 'OPPOSITE'; + + let isLongOpportunity: boolean; + let isShortOpportunity: boolean; + + if (tradeDirection === 'OPPOSITE') { + // * Contrarian: SELL liquidation → BUY opportunity, BUY liquidation → SELL opportunity + isLongOpportunity = liquidation.side === 'SELL'; + isShortOpportunity = liquidation.side === 'BUY'; + } else { + // * Momentum: SELL liquidation → SELL opportunity, BUY liquidation → BUY opportunity + isLongOpportunity = liquidation.side === 'BUY'; + isShortOpportunity = liquidation.side === 'SELL'; + } let shouldTrade = false; let tradeSide: 'BUY' | 'SELL' | null = null; @@ -490,17 +501,26 @@ export class Hunter extends EventEmitter { await this.analyzeAndTrade(liquidation, symbolConfig, tradeSide); } } else { - // ORIGINAL INSTANT TRIGGER SYSTEM - // Check direction-specific volume thresholds - // SELL liquidation means longs are getting liquidated, we might want to BUY - // BUY liquidation means shorts are getting liquidated, we might want to SELL - const thresholdToCheck = liquidation.side === 'SELL' - ? (symbolConfig.longVolumeThresholdUSDT ?? symbolConfig.volumeThresholdUSDT ?? 0) - : (symbolConfig.shortVolumeThresholdUSDT ?? symbolConfig.volumeThresholdUSDT ?? 0); + // * ORIGINAL INSTANT TRIGGER SYSTEM - Check direction-specific volume thresholds + // * Determine threshold based on trade_side parameter + const tradeDirection = symbolConfig.trade_side || 'OPPOSITE'; + + let thresholdToCheck: number; + if (tradeDirection === 'OPPOSITE') { + // * Contrarian: Use thresholds based on liquidation side + thresholdToCheck = liquidation.side === 'SELL' + ? (symbolConfig.longVolumeThresholdUSDT ?? symbolConfig.volumeThresholdUSDT ?? 0) + : (symbolConfig.shortVolumeThresholdUSDT ?? symbolConfig.volumeThresholdUSDT ?? 0); + } else { + // * Momentum: Use thresholds based on opposite of liquidation side + thresholdToCheck = liquidation.side === 'BUY' + ? (symbolConfig.longVolumeThresholdUSDT ?? symbolConfig.volumeThresholdUSDT ?? 0) + : (symbolConfig.shortVolumeThresholdUSDT ?? symbolConfig.volumeThresholdUSDT ?? 0); + } if (volumeUSDT < thresholdToCheck) return; // Too small - console.log(`Hunter: Liquidation detected - ${liquidation.symbol} ${liquidation.side} ${volumeUSDT.toFixed(2)} USDT`); + console.log(`Hunter: [${tradeDirection}] Liquidation detected - ${liquidation.symbol} ${liquidation.side} ${volumeUSDT.toFixed(2)} USDT`); // Analyze and trade with instant trigger await this.analyzeAndTrade(liquidation, symbolConfig); @@ -516,11 +536,24 @@ export class Hunter extends EventEmitter { const markPrice = parseFloat(markPriceData.markPrice); - // Simple analysis: If SELL liquidation and price is > 0.99 * mark, buy - // If BUY liquidation, sell + // Determine trade direction based on trade_side parameter + // * OPPOSITE (default): SELL liquidation → BUY, BUY liquidation → SELL (contrarian strategy) + // * SAME: SELL liquidation → SELL, BUY liquidation → BUY (momentum strategy) + const tradeSide = symbolConfig.trade_side || 'OPPOSITE'; const priceRatio = liquidation.price / markPrice; - const triggerBuy = liquidation.side === 'SELL' && priceRatio < 1.01; // 1% below - const triggerSell = liquidation.side === 'BUY' && priceRatio > 0.99; // 1% above + + let triggerBuy: boolean; + let triggerSell: boolean; + + if (tradeSide === 'OPPOSITE') { + // * Contrarian: Trade opposite to liquidation direction + triggerBuy = liquidation.side === 'SELL' && priceRatio < 1.01; // SELL liquidation → BUY + triggerSell = liquidation.side === 'BUY' && priceRatio > 0.99; // BUY liquidation → SELL + } else { + // * Momentum: Trade in same direction as liquidation + triggerBuy = liquidation.side === 'BUY' && priceRatio > 0.99; // BUY liquidation → BUY + triggerSell = liquidation.side === 'SELL' && priceRatio < 1.01; // SELL liquidation → SELL + } // Check VWAP protection if enabled if (symbolConfig.vwapProtection) { @@ -619,32 +652,48 @@ export class Hunter extends EventEmitter { if (triggerBuy) { const volumeUSDT = liquidation.qty * liquidation.price; + // * Generate reason based on trade side mode + let reason: string; + if (tradeSide === 'OPPOSITE') { + reason = `SELL liquidation at ${((1 - priceRatio) * 100).toFixed(2)}% below mark price (contrarian)`; + } else { + reason = `BUY liquidation at ${((priceRatio - 1) * 100).toFixed(2)}% above mark price (momentum)`; + } + // Emit trade opportunity this.emit('tradeOpportunity', { symbol: liquidation.symbol, side: 'BUY', - reason: `SELL liquidation at ${((1 - priceRatio) * 100).toFixed(2)}% below mark price`, + reason: reason, liquidationVolume: volumeUSDT, - priceImpact: (1 - priceRatio) * 100, + priceImpact: Math.abs((priceRatio - 1) * 100), confidence: Math.min(95, 50 + (volumeUSDT / 1000) * 10) // Higher confidence for larger volumes }); - console.log(`Hunter: Triggering BUY for ${liquidation.symbol} at ${liquidation.price}`); + console.log(`Hunter: [${tradeSide}] Triggering BUY for ${liquidation.symbol} at ${liquidation.price}`); await this.placeTrade(liquidation.symbol, 'BUY', symbolConfig, liquidation.price); } else if (triggerSell) { const volumeUSDT = liquidation.qty * liquidation.price; + // * Generate reason based on trade side mode + let reason: string; + if (tradeSide === 'OPPOSITE') { + reason = `BUY liquidation at ${((priceRatio - 1) * 100).toFixed(2)}% above mark price (contrarian)`; + } else { + reason = `SELL liquidation at ${((1 - priceRatio) * 100).toFixed(2)}% below mark price (momentum)`; + } + // Emit trade opportunity this.emit('tradeOpportunity', { symbol: liquidation.symbol, side: 'SELL', - reason: `BUY liquidation at ${((priceRatio - 1) * 100).toFixed(2)}% above mark price`, + reason: reason, liquidationVolume: volumeUSDT, - priceImpact: (priceRatio - 1) * 100, + priceImpact: Math.abs((priceRatio - 1) * 100), confidence: Math.min(95, 50 + (volumeUSDT / 1000) * 10) }); - console.log(`Hunter: Triggering SELL for ${liquidation.symbol} at ${liquidation.price}`); + console.log(`Hunter: [${tradeSide}] Triggering SELL for ${liquidation.symbol} at ${liquidation.price}`); await this.placeTrade(liquidation.symbol, 'SELL', symbolConfig, liquidation.price); } } catch (error) { diff --git a/src/lib/config/types.ts b/src/lib/config/types.ts index 1cc24ca..1ca0b39 100644 --- a/src/lib/config/types.ts +++ b/src/lib/config/types.ts @@ -28,6 +28,9 @@ export const symbolConfigSchema = z.object({ vwapTimeframe: z.string().optional(), vwapLookback: z.number().min(10).max(500).optional(), + // Trade direction settings + trade_side: z.enum(['OPPOSITE', 'SAME']).optional(), + // Threshold system settings useThreshold: z.boolean().optional(), }).refine(data => { diff --git a/src/lib/services/thresholdMonitor.ts b/src/lib/services/thresholdMonitor.ts index d8c6516..3006eab 100644 --- a/src/lib/services/thresholdMonitor.ts +++ b/src/lib/services/thresholdMonitor.ts @@ -153,10 +153,18 @@ export class ThresholdMonitor extends EventEmitter { const now = Date.now(); - // Determine which side this liquidation affects - // SELL liquidation means longs are getting liquidated, we might want to BUY (long) - // BUY liquidation means shorts are getting liquidated, we might want to SELL (short) - const isLongOpportunity = liquidation.side === 'SELL'; + // * Determine which side this liquidation affects based on trade_side parameter + const symbolConfigForTradeSide = this.config.symbols[liquidation.symbol]; + const tradeSide = symbolConfigForTradeSide?.trade_side || 'OPPOSITE'; + + let isLongOpportunity: boolean; + if (tradeSide === 'OPPOSITE') { + // * Contrarian: SELL liquidation → BUY opportunity, BUY liquidation → SELL opportunity + isLongOpportunity = liquidation.side === 'SELL'; + } else { + // * Momentum: SELL liquidation → SELL opportunity, BUY liquidation → BUY opportunity + isLongOpportunity = liquidation.side === 'BUY'; + } if (isLongOpportunity) { status.recentLiquidations.long.push(liquidation); @@ -370,4 +378,4 @@ export class ThresholdMonitor extends EventEmitter { } // Export singleton instance with 30-second cooldown -export const thresholdMonitor = new ThresholdMonitor(undefined, 60 * 1000, 30 * 1000); \ No newline at end of file +export const thresholdMonitor = new ThresholdMonitor(undefined, 60 * 1000, 30 * 1000); diff --git a/src/lib/types.ts b/src/lib/types.ts index 3f2a0f6..74f9076 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -26,6 +26,9 @@ export interface SymbolConfig { vwapTimeframe?: string; // Timeframe for VWAP calculation: 1m, 5m, 15m, 30m, 1h (default: '1m') vwapLookback?: number; // Number of candles to use for VWAP calculation (default: 100) + // Trade direction settings + trade_side?: 'OPPOSITE' | 'SAME'; // Trade direction relative to liquidation: OPPOSITE (default) or SAME + // Threshold system settings (60-second rolling window) useThreshold?: boolean; // Enable threshold-based triggering for this symbol (default: false) thresholdTimeWindow?: number; // Time window in ms for volume accumulation (default: 60000)