feat: Hybrid RPC fallback system (Alchemy → Helius)
- Automatic fallback after 2 consecutive rate limits - Primary: Alchemy (300M CU/month, stable for normal ops) - Fallback: Helius (10 req/sec, backup for startup bursts) - Reduced startup validation: 6h window, 5 trades (was 24h, 20 trades) - Multi-position safety check (prevents order cancellation conflicts) - Rate limit-aware retry logic with exponential backoff Implementation: - lib/drift/client.ts: Added fallbackConnection, switchToFallbackRpc() - .env: SOLANA_FALLBACK_RPC_URL configuration - lib/startup/init-position-manager.ts: Reduced validation scope - lib/trading/position-manager.ts: Multi-position order protection Tested: System switched to fallback on startup, Position Manager active Result: 1 active trade being monitored after automatic RPC switch
This commit is contained in:
@@ -717,36 +717,48 @@ export class PositionManager {
|
||||
console.log(`🔒 SL moved to +${this.config.breakEvenTriggerPercent}% (${this.config.takeProfit1SizePercent}% closed, ${100 - this.config.takeProfit1SizePercent}% remaining): ${newStopLossPrice.toFixed(4)}`)
|
||||
|
||||
// CRITICAL: Cancel old on-chain SL orders and place new ones at updated price
|
||||
// BUT: Only if this is the ONLY active trade on this symbol
|
||||
// Multiple positions on same symbol = can't distinguish which orders belong to which trade
|
||||
try {
|
||||
console.log('🗑️ Cancelling old stop loss orders...')
|
||||
const { cancelAllOrders, placeExitOrders } = await import('../drift/orders')
|
||||
const cancelResult = await cancelAllOrders(trade.symbol)
|
||||
if (cancelResult.success) {
|
||||
console.log(`✅ Cancelled ${cancelResult.cancelledCount || 0} old orders`)
|
||||
|
||||
// Place new SL orders at breakeven/profit level for remaining position
|
||||
console.log(`🛡️ Placing new SL orders at $${newStopLossPrice.toFixed(4)} for remaining position...`)
|
||||
const exitOrdersResult = await placeExitOrders({
|
||||
symbol: trade.symbol,
|
||||
positionSizeUSD: trade.currentSize,
|
||||
entryPrice: trade.entryPrice,
|
||||
tp1Price: trade.tp2Price, // Only TP2 remains
|
||||
tp2Price: trade.tp2Price, // Dummy, won't be used
|
||||
stopLossPrice: newStopLossPrice,
|
||||
tp1SizePercent: 100, // Close remaining 25% at TP2
|
||||
tp2SizePercent: 0,
|
||||
direction: trade.direction,
|
||||
useDualStops: this.config.useDualStops,
|
||||
softStopPrice: trade.direction === 'long'
|
||||
? newStopLossPrice * 1.005 // 0.5% above for long
|
||||
: newStopLossPrice * 0.995, // 0.5% below for short
|
||||
hardStopPrice: newStopLossPrice,
|
||||
})
|
||||
|
||||
if (exitOrdersResult.success) {
|
||||
console.log('✅ New SL orders placed on-chain at updated price')
|
||||
} else {
|
||||
console.error('❌ Failed to place new SL orders:', exitOrdersResult.error)
|
||||
const otherTradesOnSymbol = Array.from(this.activeTrades.values()).filter(
|
||||
t => t.symbol === trade.symbol && t.id !== trade.id
|
||||
)
|
||||
|
||||
if (otherTradesOnSymbol.length > 0) {
|
||||
console.log(`⚠️ Multiple trades on ${trade.symbol} detected (${otherTradesOnSymbol.length + 1} total)`)
|
||||
console.log(`⚠️ Skipping order cancellation to avoid wiping other positions' orders`)
|
||||
console.log(`⚠️ Relying on Position Manager software monitoring for remaining ${100 - this.config.takeProfit1SizePercent}%`)
|
||||
} else {
|
||||
console.log('🗑️ Cancelling old stop loss orders...')
|
||||
const { cancelAllOrders, placeExitOrders } = await import('../drift/orders')
|
||||
const cancelResult = await cancelAllOrders(trade.symbol)
|
||||
if (cancelResult.success) {
|
||||
console.log(`✅ Cancelled ${cancelResult.cancelledCount || 0} old orders`)
|
||||
|
||||
// Place new SL orders at breakeven/profit level for remaining position
|
||||
console.log(`🛡️ Placing new SL orders at $${newStopLossPrice.toFixed(4)} for remaining position...`)
|
||||
const exitOrdersResult = await placeExitOrders({
|
||||
symbol: trade.symbol,
|
||||
positionSizeUSD: trade.currentSize,
|
||||
entryPrice: trade.entryPrice,
|
||||
tp1Price: trade.tp2Price, // Only TP2 remains
|
||||
tp2Price: trade.tp2Price, // Dummy, won't be used
|
||||
stopLossPrice: newStopLossPrice,
|
||||
tp1SizePercent: 100, // Close remaining 25% at TP2
|
||||
tp2SizePercent: 0,
|
||||
direction: trade.direction,
|
||||
useDualStops: this.config.useDualStops,
|
||||
softStopPrice: trade.direction === 'long'
|
||||
? newStopLossPrice * 1.005 // 0.5% above for long
|
||||
: newStopLossPrice * 0.995, // 0.5% below for short
|
||||
hardStopPrice: newStopLossPrice,
|
||||
})
|
||||
|
||||
if (exitOrdersResult.success) {
|
||||
console.log('✅ New SL orders placed on-chain at updated price')
|
||||
} else {
|
||||
console.error('❌ Failed to place new SL orders:', exitOrdersResult.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user