Files
trading_bot_v3/lib/drift-trading.ts
mindesbunister 9978760995 🔧 Add settlement lag detection and handling
- Detect when balance is low (.53) but no positions active
- This commonly occurs after position closure before profit settlement
- Add _meta field to API response indicating settlement pending status
- Provide explanatory note for debugging and user understanding
- Backend now correctly shows 0 positions and flags settlement delay

Note: Drift UI shows 16.71 but account data shows .53, likely due to
settlement lag between position closure and balance update on blockchain.
2025-07-14 11:03:33 +02:00

197 lines
5.7 KiB
TypeScript

import { Connection, PublicKey } from '@solana/web3.js';
// Types
export interface TradingBalance {
totalValue: number;
availableBalance: number;
marginUsed: number;
unrealizedPnl: number;
positions: {
symbol: string;
size: number;
notionalValue: number;
unrealizedPnl: number;
side: 'long' | 'short';
}[];
}
export interface Position {
symbol: string;
side: 'long' | 'short';
size: number;
entryPrice: number;
markPrice: number;
unrealizedPnl: number;
notionalValue: number;
}
export interface OrderRequest {
symbol: string;
side: 'buy' | 'sell';
type: 'market' | 'limit';
amount: number;
price?: number;
}
export interface OrderResponse {
success: boolean;
orderId?: string;
error?: string;
}
class DriftTradingService {
private connection: Connection;
private readonly accountPDA = '7LonnWut5i3h36xyMA5jbwnGFbnzXUPY2dsPfNaSsrTk';
constructor() {
this.connection = new Connection(
process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
'confirmed'
);
}
async initialize(): Promise<void> {
console.log('🚀 Initializing Direct Drift Trading Service...');
console.log('✅ Direct service ready - no SDK subscriptions needed');
}
private async getAccountData(): Promise<Buffer> {
try {
const accountInfo = await this.connection.getAccountInfo(
new PublicKey(this.accountPDA)
);
if (!accountInfo) {
throw new Error('Account not found');
}
return accountInfo.data;
} catch (error) {
console.error('❌ Failed to fetch account data:', error);
throw error;
}
}
private parseAccountData(data: Buffer) {
try {
// Extract USDC balance at offset 106
const usdcBalance = data.readBigInt64LE(106);
const usdcValue = Number(usdcBalance) / 1_000_000; // USDC has 6 decimals
// Extract SOL position at offset 432 (most current/accurate location)
const solPosition = data.readBigInt64LE(432);
const solAmount = Number(solPosition) / 1_000_000_000; // SOL has 9 decimals
// Estimate SOL price (you could fetch this from an oracle)
const solPrice = 17.11; // Current approximate price
const solValue = solAmount * solPrice;
// Calculate basic total from current positions
let totalValue = usdcValue + solValue;
// If we have a very low total but no active positions, this might indicate
// settlement lag where profits from closed positions aren't yet reflected
if (totalValue < 10 && solAmount === 0) {
console.log(`⚠️ Low balance detected with no positions - possible settlement lag`);
console.log(` This often happens after position closure before profit settlement`);
// In this case, we might want to use the last known total or a fallback
// For now, we'll note this condition but keep the calculated value
}
console.log(`💰 Parsed account data:`);
console.log(` USDC Balance: $${usdcValue.toFixed(2)}`);
console.log(` SOL Position: ${solAmount.toFixed(6)} SOL`);
console.log(` SOL Value: $${solValue.toFixed(2)} (@ $${solPrice})`);
console.log(` Total Value: $${totalValue.toFixed(2)}`);
return {
usdcBalance: usdcValue,
solPosition: solAmount,
solPrice,
solValue,
totalValue,
settlementPending: totalValue < 10 && solAmount === 0
};
} catch (error) {
console.error('❌ Failed to parse account data:', error);
throw error;
}
}
async getTradingBalance(): Promise<TradingBalance> {
try {
const data = await this.getAccountData();
const parsed = this.parseAccountData(data);
// Calculate unrealized PnL (SOL value minus some baseline)
const unrealizedPnl = parsed.solValue - (parsed.solPosition * 15); // Assuming $15 entry price
return {
totalValue: parsed.totalValue,
availableBalance: parsed.usdcBalance,
marginUsed: 0, // Not available without full account parsing
unrealizedPnl,
positions: parsed.solPosition > 0 ? [{
symbol: 'SOL',
size: parsed.solPosition,
notionalValue: parsed.solValue,
unrealizedPnl,
side: 'long' as const
}] : [],
// Add settlement status info
...(parsed.settlementPending && {
_meta: {
settlementPending: true,
note: 'Low balance with no positions may indicate settlement lag after position closure'
}
})
};
} catch (error) {
console.error('❌ Failed to get trading balance:', error);
throw error;
}
}
async getPositions(): Promise<Position[]> {
try {
const data = await this.getAccountData();
const parsed = this.parseAccountData(data);
if (parsed.solPosition <= 0) {
return [];
}
const unrealizedPnl = parsed.solValue - (parsed.solPosition * 15); // Assuming $15 entry
return [{
symbol: 'SOL',
side: 'long',
size: parsed.solPosition,
entryPrice: 15, // Estimated entry price
markPrice: parsed.solPrice,
unrealizedPnl,
notionalValue: parsed.solValue
}];
} catch (error) {
console.error('❌ Failed to get positions:', error);
throw error;
}
}
async placeOrder(orderRequest: OrderRequest): Promise<OrderResponse> {
console.log('📝 Order placement not implemented in direct mode:', orderRequest);
return {
success: false,
error: 'Order placement requires SDK integration'
};
}
async disconnect(): Promise<void> {
console.log('✅ Direct service disconnected (no cleanup needed)');
}
}
// Export singleton instance
export const driftTradingService = new DriftTradingService();