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)