Fix Drift balance calculation and implement multi-RPC failover system
- Fixed balance calculation: corrected precision factor for Drift scaledBalance (5.69 vs wrong 0,678.76) - Implemented multi-RPC failover system with 4 endpoints (Helius, Solana official, Alchemy, Ankr) - Updated automation page with balance sync, leverage-based position sizing, and removed daily trade limits - Added RPC status monitoring endpoint - Updated balance and positions APIs to use failover system - All Drift APIs now working correctly with accurate balance data
This commit is contained in:
@@ -14,7 +14,6 @@ export interface AutomationConfig {
|
||||
maxLeverage: number
|
||||
stopLossPercent: number
|
||||
takeProfitPercent: number
|
||||
maxDailyTrades: number
|
||||
riskPercentage: number
|
||||
dexProvider: 'JUPITER' | 'DRIFT'
|
||||
}
|
||||
@@ -234,10 +233,10 @@ export class AutomationService {
|
||||
private async executeAutomationCycle(config: AutomationConfig) {
|
||||
console.log(`🔄 Executing automation cycle for ${config.symbol} ${config.timeframe}`)
|
||||
|
||||
// Check if we've reached daily trade limit
|
||||
const todayTrades = await this.getTodayTradeCount(config.userId)
|
||||
if (todayTrades >= config.maxDailyTrades) {
|
||||
console.log(`📊 Daily trade limit reached (${todayTrades}/${config.maxDailyTrades})`)
|
||||
// Check for open positions first (instead of daily trade limit)
|
||||
const hasOpenPosition = await this.checkForOpenPositions(config)
|
||||
if (hasOpenPosition) {
|
||||
console.log(`📊 Open position detected for ${config.symbol}, monitoring only`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -367,6 +366,48 @@ export class AutomationService {
|
||||
return true
|
||||
}
|
||||
|
||||
private async checkForOpenPositions(config: AutomationConfig): Promise<boolean> {
|
||||
try {
|
||||
console.log(`🔍 Checking for open positions for ${config.symbol}`)
|
||||
|
||||
// For Jupiter DEX, we don't have persistent positions like in Drift
|
||||
// This method would need to be implemented based on your specific needs
|
||||
// For now, return false to allow trading
|
||||
|
||||
if (config.dexProvider === 'DRIFT') {
|
||||
// Check Drift positions via API
|
||||
const response = await fetch('http://localhost:3000/api/drift/positions')
|
||||
if (!response.ok) {
|
||||
console.warn('⚠️ Could not fetch Drift positions, assuming no open positions')
|
||||
return false
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
if (!data.success || !data.positions) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if there's an open position for the current symbol
|
||||
const symbolBase = config.symbol.replace('USD', '') // SOLUSD -> SOL
|
||||
const openPosition = data.positions.find((pos: any) =>
|
||||
pos.symbol.includes(symbolBase) && pos.size > 0.001
|
||||
)
|
||||
|
||||
if (openPosition) {
|
||||
console.log(`📊 Found open ${openPosition.side} position: ${openPosition.symbol} ${openPosition.size}`)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error checking positions:', error)
|
||||
// On error, assume no positions to allow trading
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private async executeTrade(config: AutomationConfig, analysis: AnalysisResult, screenshotUrl: string) {
|
||||
try {
|
||||
console.log(`🚀 Executing ${config.mode} trade: ${analysis.recommendation} ${config.symbol}`)
|
||||
@@ -560,27 +601,6 @@ export class AutomationService {
|
||||
return intervals[timeframe] || intervals['1h'] // Default to 1 hour
|
||||
}
|
||||
|
||||
private async getTodayTradeCount(userId: string): Promise<number> {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
|
||||
const tomorrow = new Date(today)
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
|
||||
const count = await prisma.trade.count({
|
||||
where: {
|
||||
userId,
|
||||
isAutomated: true,
|
||||
createdAt: {
|
||||
gte: today,
|
||||
lt: tomorrow
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
private async getRecentPerformance(userId: string): Promise<{
|
||||
winRate: number
|
||||
totalTrades: number
|
||||
|
||||
105
lib/rpc-failover.js
Normal file
105
lib/rpc-failover.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Connection } from '@solana/web3.js';
|
||||
|
||||
const RPC_ENDPOINTS = [
|
||||
process.env.SOLANA_RPC_URL_PRIMARY,
|
||||
process.env.SOLANA_RPC_URL_SECONDARY,
|
||||
process.env.SOLANA_RPC_URL_TERTIARY,
|
||||
process.env.SOLANA_RPC_URL_BACKUP,
|
||||
].filter(Boolean); // Remove any undefined/empty URLs
|
||||
|
||||
let currentRpcIndex = 0;
|
||||
let activeConnection = null;
|
||||
|
||||
/**
|
||||
* Creates a Solana connection with automatic failover to backup RPCs
|
||||
*/
|
||||
export async function createConnectionWithFailover() {
|
||||
for (let attempt = 0; attempt < RPC_ENDPOINTS.length; attempt++) {
|
||||
const rpcUrl = RPC_ENDPOINTS[currentRpcIndex];
|
||||
|
||||
if (!rpcUrl) {
|
||||
currentRpcIndex = (currentRpcIndex + 1) % RPC_ENDPOINTS.length;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Attempting to connect to RPC: ${rpcUrl.replace(/\/\/.*@/, '//*****@')}`);
|
||||
|
||||
const connection = new Connection(rpcUrl, 'confirmed');
|
||||
|
||||
// Test the connection by getting the latest blockhash
|
||||
await connection.getLatestBlockhash();
|
||||
|
||||
console.log(`Successfully connected to RPC: ${rpcUrl.replace(/\/\/.*@/, '//*****@')}`);
|
||||
activeConnection = connection;
|
||||
return connection;
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`RPC ${rpcUrl.replace(/\/\/.*@/, '//*****@')} failed:`, error.message);
|
||||
|
||||
// Move to next RPC endpoint
|
||||
currentRpcIndex = (currentRpcIndex + 1) % RPC_ENDPOINTS.length;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('All RPC endpoints failed. Unable to establish connection.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current active connection or creates a new one
|
||||
*/
|
||||
export async function getConnection() {
|
||||
if (activeConnection) {
|
||||
try {
|
||||
// Test if current connection is still working
|
||||
await activeConnection.getLatestBlockhash();
|
||||
return activeConnection;
|
||||
} catch (error) {
|
||||
console.warn('Active connection failed, attempting failover...');
|
||||
activeConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
return createConnectionWithFailover();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a function with automatic RPC failover
|
||||
*/
|
||||
export async function executeWithFailover(operation, maxRetries = 3) {
|
||||
let lastError = null;
|
||||
|
||||
for (let retry = 0; retry < maxRetries; retry++) {
|
||||
try {
|
||||
const connection = await getConnection();
|
||||
return await operation(connection);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.warn(`Operation failed (attempt ${retry + 1}/${maxRetries}):`, error.message);
|
||||
|
||||
// Force connection refresh on next attempt
|
||||
activeConnection = null;
|
||||
|
||||
// Move to next RPC endpoint
|
||||
currentRpcIndex = (currentRpcIndex + 1) % RPC_ENDPOINTS.length;
|
||||
|
||||
// Wait a bit before retrying
|
||||
if (retry < maxRetries - 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * (retry + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Operation failed after ${maxRetries} attempts. Last error: ${lastError?.message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about current RPC status
|
||||
*/
|
||||
export function getRpcStatus() {
|
||||
return {
|
||||
availableEndpoints: RPC_ENDPOINTS.length,
|
||||
currentEndpoint: RPC_ENDPOINTS[currentRpcIndex]?.replace(/\/\/.*@/, '//*****@'),
|
||||
hasActiveConnection: !!activeConnection
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user