feat: implement real Drift trading history using SDK methods
- Updated getTradingHistory to fetch actual Drift order records - Added fallback to local database for trade history - Enhanced executeTrade to store trades in database for history tracking - Fixed hydration issues in AutoTradingPanel and TradingHistory components - Improved error handling and logging for trading history retrieval
This commit is contained in:
@@ -28,8 +28,20 @@ export interface TradeParams {
|
||||
stopLossType?: 'PRICE' | 'PERCENTAGE'
|
||||
takeProfitType?: 'PRICE' | 'PERCENTAGE'
|
||||
}
|
||||
|
||||
export interface TradeResult {
|
||||
if (localTrades.length > 0) {
|
||||
console.log(`📊 Found ${localTrades.length} trades in local database`)
|
||||
return localTrades.map((trade: any) => ({
|
||||
id: trade.id.toString(),
|
||||
symbol: trade.symbol,
|
||||
side: trade.side as 'BUY' | 'SELL',
|
||||
amount: trade.amount,
|
||||
price: trade.price,
|
||||
status: trade.status as 'FILLED' | 'PENDING' | 'CANCELLED',
|
||||
executedAt: trade.executedAt ? trade.executedAt.toISOString() : trade.createdAt.toISOString(),
|
||||
pnl: trade.profit || 0,
|
||||
txId: trade.driftTxId || trade.id
|
||||
}))
|
||||
}ace TradeResult {
|
||||
success: boolean
|
||||
txId?: string
|
||||
error?: string
|
||||
@@ -431,11 +443,51 @@ export class DriftTradingService {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const result = {
|
||||
success: true,
|
||||
txId: txSig,
|
||||
conditionalOrders: conditionalOrders.length > 0 ? conditionalOrders : undefined
|
||||
}
|
||||
|
||||
// Store the trade in local database for history tracking
|
||||
try {
|
||||
const { default: prisma } = await import('./prisma')
|
||||
|
||||
// Get current market price (simplified - using a default for now)
|
||||
let currentPrice = 160; // Default SOL price
|
||||
try {
|
||||
// Try to get actual market price from the market
|
||||
const perpMarket = this.driftClient.getPerpMarketAccount(marketIndex)
|
||||
if (perpMarket && perpMarket.amm) {
|
||||
// Use oracle price or mark price if available
|
||||
const oraclePrice = perpMarket.amm.historicalOracleData?.lastOraclePrice ||
|
||||
perpMarket.amm.lastMarkPriceTwap ||
|
||||
new BN(160 * PRICE_PRECISION.toNumber())
|
||||
currentPrice = convertToNumber(oraclePrice, PRICE_PRECISION)
|
||||
}
|
||||
} catch (priceError) {
|
||||
console.log('⚠️ Could not get current market price, using default')
|
||||
}
|
||||
|
||||
await prisma.trade.create({
|
||||
data: {
|
||||
userId: 'default-user', // TODO: Implement proper user management
|
||||
symbol: params.symbol,
|
||||
side: params.side,
|
||||
amount: params.amount,
|
||||
price: currentPrice,
|
||||
status: 'FILLED',
|
||||
executedAt: new Date(),
|
||||
driftTxId: txSig
|
||||
}
|
||||
})
|
||||
console.log(`💾 Trade saved to database: ${params.side} ${params.amount} ${params.symbol} at $${currentPrice}`)
|
||||
} catch (dbError) {
|
||||
console.log('⚠️ Failed to save trade to database:', (dbError as Error).message)
|
||||
// Don't fail the trade if database save fails
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (e: any) {
|
||||
return { success: false, error: e.message }
|
||||
} finally {
|
||||
@@ -576,116 +628,122 @@ export class DriftTradingService {
|
||||
|
||||
async getTradingHistory(limit: number = 50): Promise<TradeHistory[]> {
|
||||
try {
|
||||
console.log('📊 Fetching trading history...')
|
||||
console.log('📊 Fetching trading history from Drift...')
|
||||
|
||||
// Try to get order records from Drift SDK if available
|
||||
if (this.driftClient && this.isInitialized) {
|
||||
try {
|
||||
console.log('🔍 Attempting to get order records from Drift SDK...')
|
||||
await this.driftClient.subscribe()
|
||||
|
||||
const user = this.driftClient.getUser()
|
||||
const trades: TradeHistory[] = []
|
||||
|
||||
// Get order history - try different approaches
|
||||
try {
|
||||
// Method 1: Try to get order history directly
|
||||
if ('getOrderHistory' in user) {
|
||||
const orderHistory = (user as any).getOrderHistory()
|
||||
console.log('📋 Found order history method, processing orders...')
|
||||
|
||||
// Process order history into our format
|
||||
for (const order of orderHistory.slice(0, limit)) {
|
||||
trades.push({
|
||||
id: order.orderId?.toString() || Date.now().toString(),
|
||||
symbol: this.getSymbolFromMarketIndex(order.marketIndex || 0),
|
||||
side: order.direction === 0 ? 'BUY' : 'SELL', // Assuming 0 = LONG/BUY
|
||||
amount: convertToNumber(order.baseAssetAmount || new BN(0), BASE_PRECISION),
|
||||
price: convertToNumber(order.price || new BN(0), PRICE_PRECISION),
|
||||
status: order.status === 'FILLED' ? 'FILLED' : 'PENDING',
|
||||
executedAt: new Date(order.timestamp || Date.now()).toISOString(),
|
||||
txId: order.txSig
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Try to get recent transactions/fills
|
||||
if (trades.length === 0 && 'getRecentFills' in user) {
|
||||
console.log('📋 Trying recent fills method...')
|
||||
const recentFills = (user as any).getRecentFills(limit)
|
||||
|
||||
for (const fill of recentFills) {
|
||||
trades.push({
|
||||
id: fill.fillId?.toString() || Date.now().toString(),
|
||||
symbol: this.getSymbolFromMarketIndex(fill.marketIndex || 0),
|
||||
side: fill.direction === 0 ? 'BUY' : 'SELL',
|
||||
amount: convertToNumber(fill.baseAssetAmount || new BN(0), BASE_PRECISION),
|
||||
price: convertToNumber(fill.fillPrice || new BN(0), PRICE_PRECISION),
|
||||
status: 'FILLED',
|
||||
executedAt: new Date(fill.timestamp || Date.now()).toISOString(),
|
||||
txId: fill.txSig
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`📊 Found ${trades.length} trades from Drift SDK`)
|
||||
|
||||
} catch (sdkError: any) {
|
||||
console.log('⚠️ SDK order history methods failed:', sdkError.message)
|
||||
} finally {
|
||||
await this.driftClient.unsubscribe()
|
||||
}
|
||||
|
||||
if (trades.length > 0) {
|
||||
return trades.sort((a, b) => new Date(b.executedAt).getTime() - new Date(a.executedAt).getTime())
|
||||
}
|
||||
|
||||
} catch (sdkError: any) {
|
||||
console.log('⚠️ SDK trading history failed, using fallback:', sdkError.message)
|
||||
}
|
||||
if (!this.driftClient || !this.isInitialized) {
|
||||
console.log('⚠️ Drift client not initialized, trying local database...')
|
||||
return await this.getLocalTradingHistory(limit)
|
||||
}
|
||||
|
||||
// Fallback: Check if we have any trades in local database (Prisma)
|
||||
|
||||
try {
|
||||
console.log('📊 Checking local trade database...')
|
||||
// Subscribe to get access to user data
|
||||
await this.driftClient.subscribe()
|
||||
const user = this.driftClient.getUser()
|
||||
|
||||
// Import Prisma here to avoid issues if it's not available
|
||||
try {
|
||||
const { default: prisma } = await import('./prisma')
|
||||
const localTrades = await prisma.trade.findMany({
|
||||
orderBy: { executedAt: 'desc' },
|
||||
take: limit
|
||||
})
|
||||
|
||||
if (localTrades.length > 0) {
|
||||
console.log(`📊 Found ${localTrades.length} trades in local database`)
|
||||
return localTrades.map((trade: any) => ({
|
||||
id: trade.id.toString(),
|
||||
symbol: trade.symbol,
|
||||
side: trade.side as 'BUY' | 'SELL',
|
||||
amount: trade.amount,
|
||||
price: trade.price,
|
||||
status: trade.status as 'FILLED' | 'PENDING' | 'CANCELLED',
|
||||
executedAt: trade.executedAt.toISOString(),
|
||||
pnl: trade.pnl,
|
||||
txId: trade.txId
|
||||
}))
|
||||
console.log('📊 Getting user order records from Drift SDK...')
|
||||
|
||||
console.log('📊 Getting user account data from Drift SDK...')
|
||||
|
||||
// Get user account which contains order and trade history
|
||||
const userAccount = user.getUserAccount()
|
||||
console.log(`📊 User account found with ${userAccount.orders?.length || 0} orders`)
|
||||
|
||||
// Convert orders to trade history
|
||||
const trades: TradeHistory[] = []
|
||||
|
||||
if (userAccount.orders) {
|
||||
for (const order of userAccount.orders.slice(0, limit)) {
|
||||
try {
|
||||
// Only include filled orders (status 2 = filled)
|
||||
if (order.status === 2) {
|
||||
const marketIndex = order.marketIndex
|
||||
const symbol = this.getSymbolFromMarketIndex(marketIndex)
|
||||
const side = order.direction === 0 ? 'BUY' : 'SELL' // 0 = PositionDirection.LONG
|
||||
const baseAmount = order.baseAssetAmountFilled || order.baseAssetAmount
|
||||
const quoteAmount = order.quoteAssetAmountFilled || order.quoteAssetAmount
|
||||
|
||||
// Calculate executed price from filled amounts
|
||||
const amount = Number(baseAmount.toString()) / 1e9 // Convert from base precision
|
||||
const totalValue = Number(quoteAmount.toString()) / 1e6 // Convert from quote precision
|
||||
const price = amount > 0 ? totalValue / amount : 0
|
||||
|
||||
const trade: TradeHistory = {
|
||||
id: order.orderId?.toString() || `order_${Date.now()}_${trades.length}`,
|
||||
symbol,
|
||||
side,
|
||||
amount,
|
||||
price,
|
||||
status: 'FILLED',
|
||||
executedAt: new Date().toISOString(), // Use current time as fallback
|
||||
txId: order.orderId?.toString() || '',
|
||||
pnl: 0 // PnL calculation would require more complex logic
|
||||
}
|
||||
|
||||
trades.push(trade)
|
||||
console.log(`✅ Processed trade: ${symbol} ${side} ${amount.toFixed(4)} @ $${price.toFixed(2)}`)
|
||||
}
|
||||
} catch (orderError) {
|
||||
console.warn('⚠️ Error processing order:', orderError)
|
||||
continue
|
||||
}
|
||||
}
|
||||
} catch (prismaError) {
|
||||
console.log('⚠️ Local database not available:', (prismaError as Error).message)
|
||||
}
|
||||
|
||||
// Return empty array instead of demo data
|
||||
console.log('📊 No trading history found - returning empty array')
|
||||
return []
|
||||
// Sort by execution time (newest first)
|
||||
trades.sort((a, b) => new Date(b.executedAt).getTime() - new Date(a.executedAt).getTime())
|
||||
|
||||
} catch (dbError: any) {
|
||||
console.log('⚠️ Database query failed:', dbError.message)
|
||||
return []
|
||||
console.log(`✅ Successfully fetched ${trades.length} trades from Drift`)
|
||||
return trades
|
||||
|
||||
} catch (sdkError: any) {
|
||||
console.error('❌ Error fetching from Drift SDK:', sdkError.message)
|
||||
return await this.getLocalTradingHistory(limit)
|
||||
} finally {
|
||||
if (this.driftClient) {
|
||||
try {
|
||||
await this.driftClient.unsubscribe()
|
||||
} catch (e) {
|
||||
// Ignore unsubscribe errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Error getting trading history:', error)
|
||||
return await this.getLocalTradingHistory(limit)
|
||||
}
|
||||
}
|
||||
|
||||
private async getLocalTradingHistory(limit: number): Promise<TradeHistory[]> {
|
||||
try {
|
||||
console.log('📊 Checking local trade database...')
|
||||
|
||||
const { default: prisma } = await import('./prisma')
|
||||
const localTrades = await prisma.trade.findMany({
|
||||
orderBy: { executedAt: 'desc' },
|
||||
take: limit
|
||||
})
|
||||
|
||||
if (localTrades.length > 0) {
|
||||
console.log(`📊 Found ${localTrades.length} trades in local database`)
|
||||
return localTrades.map((trade: any) => ({
|
||||
id: trade.id.toString(),
|
||||
symbol: trade.symbol,
|
||||
side: trade.side as 'BUY' | 'SELL',
|
||||
amount: trade.amount,
|
||||
price: trade.price,
|
||||
status: trade.status as 'FILLED' | 'PENDING' | 'CANCELLED',
|
||||
executedAt: trade.executedAt.toISOString(),
|
||||
pnl: trade.pnl || 0,
|
||||
txId: trade.driftTxId || trade.txId || ''
|
||||
}))
|
||||
}
|
||||
|
||||
console.log('📊 No local trades found')
|
||||
return []
|
||||
|
||||
} catch (prismaError) {
|
||||
console.log('⚠️ Local database not available:', (prismaError as Error).message)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user