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:
mindesbunister
2025-11-14 15:28:07 +01:00
parent d5183514bc
commit 7ff78ee0bd
5 changed files with 193 additions and 50 deletions

View File

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