Compare commits
3 Commits
e985a9ec6f
...
8e0d7f0969
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e0d7f0969 | ||
|
|
2fcd3b1120 | ||
|
|
6e75a7175e |
30
app/api/drift/trading-history/route.ts
Normal file
30
app/api/drift/trading-history/route.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import { driftTradingService } from '../../../../lib/drift-trading'
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url)
|
||||||
|
const limit = parseInt(searchParams.get('limit') || '50')
|
||||||
|
|
||||||
|
console.log('📊 API: Getting Drift trading history...')
|
||||||
|
|
||||||
|
const tradingHistory = await driftTradingService.getTradingHistory(limit)
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
trades: tradingHistory,
|
||||||
|
count: tradingHistory.length
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('❌ API: Error getting trading history:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
trades: []
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,124 +1,47 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { tradingViewAutomation } from '../../../lib/tradingview-automation'
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
console.log('📊 Getting TradingView session status...')
|
console.log('📊 Session status temporarily disabled due to TradingView automation parsing issues')
|
||||||
|
|
||||||
// Initialize if not already done (Docker-safe initialization)
|
// Return a basic response instead of using TradingView automation
|
||||||
if (!tradingViewAutomation['browser']) {
|
|
||||||
console.log('🐳 Initializing TradingView automation in Docker environment...')
|
|
||||||
await tradingViewAutomation.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get lightweight session information without navigation
|
|
||||||
const sessionInfo = await tradingViewAutomation.getQuickSessionStatus()
|
|
||||||
|
|
||||||
// Determine connection status based on browser state and URL
|
|
||||||
let connectionStatus = 'unknown'
|
|
||||||
if (sessionInfo.browserActive) {
|
|
||||||
if (sessionInfo.currentUrl.includes('tradingview.com')) {
|
|
||||||
connectionStatus = 'connected'
|
|
||||||
} else if (sessionInfo.currentUrl) {
|
|
||||||
connectionStatus = 'disconnected'
|
|
||||||
} else {
|
|
||||||
connectionStatus = 'unknown'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
connectionStatus = 'disconnected'
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = {
|
|
||||||
success: true,
|
|
||||||
session: {
|
|
||||||
...sessionInfo,
|
|
||||||
connectionStatus,
|
|
||||||
lastChecked: new Date().toISOString(),
|
|
||||||
dockerEnv: process.env.DOCKER_ENV === 'true',
|
|
||||||
environment: process.env.NODE_ENV || 'development'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('✅ Session status retrieved:', response.session)
|
|
||||||
return NextResponse.json(response)
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to get session status:', error)
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: false,
|
isAuthenticated: false,
|
||||||
error: error instanceof Error ? error.message : 'Failed to get session status',
|
status: 'disabled',
|
||||||
|
message: 'TradingView session status temporarily disabled - focusing on Drift integration',
|
||||||
session: {
|
session: {
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
hasSavedCookies: false,
|
hasSavedCookies: false,
|
||||||
hasSavedStorage: false,
|
hasSavedStorage: false,
|
||||||
cookiesCount: 0,
|
cookiesCount: 0,
|
||||||
currentUrl: '',
|
currentUrl: '',
|
||||||
connectionStatus: 'error',
|
connectionStatus: 'disabled',
|
||||||
lastChecked: new Date().toISOString(),
|
lastChecked: new Date().toISOString(),
|
||||||
dockerEnv: process.env.DOCKER_ENV === 'true',
|
dockerEnv: process.env.DOCKER_ENV === 'true',
|
||||||
environment: process.env.NODE_ENV || 'development'
|
environment: process.env.NODE_ENV || 'development'
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('❌ Session status error:', error.message)
|
||||||
|
return NextResponse.json({
|
||||||
|
isAuthenticated: false,
|
||||||
|
status: 'error',
|
||||||
|
message: error.message
|
||||||
}, { status: 500 })
|
}, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { action } = await request.json()
|
|
||||||
console.log(`🔧 Session action requested: ${action} (Docker: ${process.env.DOCKER_ENV === 'true'})`)
|
|
||||||
|
|
||||||
// Initialize if not already done (Docker-safe initialization)
|
|
||||||
if (!tradingViewAutomation['browser']) {
|
|
||||||
console.log('🐳 Initializing TradingView automation for session action in Docker...')
|
|
||||||
await tradingViewAutomation.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: any = { success: true }
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case 'refresh':
|
|
||||||
const refreshed = await tradingViewAutomation.refreshSession()
|
|
||||||
result.refreshed = refreshed
|
|
||||||
result.message = refreshed ? 'Session refreshed successfully' : 'Failed to refresh session'
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'clear':
|
|
||||||
await tradingViewAutomation.clearSession()
|
|
||||||
result.message = 'Session data cleared successfully'
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'test':
|
|
||||||
const testResult = await tradingViewAutomation.testSessionPersistence()
|
|
||||||
result.testResult = testResult
|
|
||||||
result.message = testResult.isValid ? 'Session is valid' : 'Session is invalid or expired'
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'login-status':
|
|
||||||
const isLoggedIn = await tradingViewAutomation.checkLoginStatus()
|
|
||||||
result.isLoggedIn = isLoggedIn
|
|
||||||
result.message = isLoggedIn ? 'User is logged in' : 'User is not logged in'
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
result.success = false
|
|
||||||
result.error = `Unknown action: ${action}`
|
|
||||||
return NextResponse.json(result, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`✅ Session action '${action}' completed:`, result)
|
|
||||||
return NextResponse.json({
|
|
||||||
...result,
|
|
||||||
dockerEnv: process.env.DOCKER_ENV === 'true',
|
|
||||||
environment: process.env.NODE_ENV || 'development'
|
|
||||||
})
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Session action failed:', error)
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : 'Session action failed',
|
message: 'TradingView automation temporarily disabled - focusing on Drift integration'
|
||||||
dockerEnv: process.env.DOCKER_ENV === 'true',
|
})
|
||||||
environment: process.env.NODE_ENV || 'development'
|
} catch (error: any) {
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: error.message
|
||||||
}, { status: 500 })
|
}, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ export default function Dashboard() {
|
|||||||
dailyPnL: 0,
|
dailyPnL: 0,
|
||||||
winRate: 0,
|
winRate: 0,
|
||||||
totalTrades: 0,
|
totalTrades: 0,
|
||||||
accountValue: 0
|
accountValue: 0,
|
||||||
|
netUsdValue: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -25,11 +26,11 @@ export default function Dashboard() {
|
|||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
// Try to get Drift positions first
|
// Get Drift positions
|
||||||
const driftRes = await fetch('/api/drift/positions')
|
const driftRes = await fetch('/api/drift/positions')
|
||||||
if (driftRes.ok) {
|
if (driftRes.ok) {
|
||||||
const driftData = await driftRes.json()
|
const driftData = await driftRes.json()
|
||||||
if (driftData.positions && driftData.positions.length > 0) {
|
if (driftData.positions) {
|
||||||
setPositions(driftData.positions)
|
setPositions(driftData.positions)
|
||||||
|
|
||||||
// Calculate stats from Drift positions
|
// Calculate stats from Drift positions
|
||||||
@@ -41,58 +42,57 @@ export default function Dashboard() {
|
|||||||
totalTrades: driftData.positions.length
|
totalTrades: driftData.positions.length
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Try to get account balance for account value
|
// Get account balance for account value
|
||||||
try {
|
try {
|
||||||
const balanceRes = await fetch('/api/drift/balance')
|
const balanceRes = await fetch('/api/drift/balance')
|
||||||
if (balanceRes.ok) {
|
if (balanceRes.ok) {
|
||||||
const balanceData = await balanceRes.json()
|
const balanceData = await balanceRes.json()
|
||||||
setStats(prev => ({
|
setStats(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
accountValue: balanceData.accountValue || 0
|
accountValue: balanceData.accountValue || 0,
|
||||||
|
netUsdValue: balanceData.netUsdValue || 0
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Could not fetch balance:', e)
|
console.warn('Could not fetch balance:', e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to legacy trading API
|
// No positions available - set empty state
|
||||||
const res = await fetch('/api/trading')
|
setPositions([])
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json()
|
|
||||||
setPositions(data.positions || [])
|
|
||||||
// Calculate some mock stats for demo
|
|
||||||
setStats({
|
setStats({
|
||||||
totalPnL: 1247.50,
|
totalPnL: 0,
|
||||||
dailyPnL: 67.25,
|
dailyPnL: 0,
|
||||||
winRate: 73.2,
|
winRate: 0,
|
||||||
totalTrades: 156,
|
totalTrades: 0,
|
||||||
accountValue: 10000
|
accountValue: 0,
|
||||||
|
netUsdValue: 0
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
setError('Failed to load positions')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to legacy trading API
|
// API failed - set empty state
|
||||||
const res = await fetch('/api/trading')
|
setError('Failed to connect to Drift')
|
||||||
if (res.ok) {
|
setPositions([])
|
||||||
const data = await res.json()
|
|
||||||
setPositions(data.positions || [])
|
|
||||||
// Calculate some mock stats for demo
|
|
||||||
setStats({
|
setStats({
|
||||||
totalPnL: 1247.50,
|
totalPnL: 0,
|
||||||
dailyPnL: 67.25,
|
dailyPnL: 0,
|
||||||
winRate: 73.2,
|
winRate: 0,
|
||||||
totalTrades: 156,
|
totalTrades: 0,
|
||||||
accountValue: 10000
|
accountValue: 0,
|
||||||
|
netUsdValue: 0
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
setError('Failed to load positions')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError('Error loading positions')
|
setError('Error connecting to Drift')
|
||||||
console.error('Error:', e)
|
console.error('Error:', e)
|
||||||
|
setPositions([])
|
||||||
|
setStats({
|
||||||
|
totalPnL: 0,
|
||||||
|
dailyPnL: 0,
|
||||||
|
winRate: 0,
|
||||||
|
totalTrades: 0,
|
||||||
|
accountValue: 0,
|
||||||
|
netUsdValue: 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,21 @@ export default function Dashboard() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-6 gap-6">
|
||||||
|
<div className="card card-gradient">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-400 text-sm font-medium">Net USD Value</p>
|
||||||
|
<p className="text-2xl font-bold text-emerald-400">
|
||||||
|
${stats.netUsdValue.toFixed(2)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-12 h-12 bg-emerald-500/20 rounded-full flex items-center justify-center">
|
||||||
|
<span className="text-emerald-400 text-xl">💎</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="card card-gradient">
|
<div className="card card-gradient">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -238,10 +252,10 @@ export default function Dashboard() {
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="w-8 h-8 bg-gradient-to-br from-orange-400 to-orange-600 rounded-full flex items-center justify-center mr-3">
|
<div className="w-8 h-8 bg-gradient-to-br from-orange-400 to-orange-600 rounded-full flex items-center justify-center mr-3">
|
||||||
<span className="text-white text-xs font-bold">
|
<span className="text-white text-xs font-bold">
|
||||||
{pos.symbol?.slice(0, 2) || 'BT'}
|
{pos.symbol?.slice(0, 2) || '--'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium text-white">{pos.symbol || 'BTC/USD'}</span>
|
<span className="font-medium text-white">{pos.symbol || '--'}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-4 px-4">
|
<td className="py-4 px-4">
|
||||||
@@ -250,20 +264,20 @@ export default function Dashboard() {
|
|||||||
? 'bg-green-500/20 text-green-400'
|
? 'bg-green-500/20 text-green-400'
|
||||||
: 'bg-red-500/20 text-red-400'
|
: 'bg-red-500/20 text-red-400'
|
||||||
}`}>
|
}`}>
|
||||||
{pos.side || 'Long'}
|
{pos.side || '--'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-4 px-4 text-right font-mono text-gray-300">
|
<td className="py-4 px-4 text-right font-mono text-gray-300">
|
||||||
{typeof pos.size === 'number' ? pos.size.toFixed(4) : (pos.size || '0.1 BTC')}
|
{typeof pos.size === 'number' ? pos.size.toFixed(4) : '--'}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-4 px-4 text-right font-mono text-gray-300">
|
<td className="py-4 px-4 text-right font-mono text-gray-300">
|
||||||
${typeof pos.entryPrice === 'number' ? pos.entryPrice.toFixed(2) : (pos.entryPrice || '45,230.00')}
|
${typeof pos.entryPrice === 'number' ? pos.entryPrice.toFixed(2) : '--'}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-4 px-4 text-right">
|
<td className="py-4 px-4 text-right">
|
||||||
<span className={`font-mono font-medium ${
|
<span className={`font-mono font-medium ${
|
||||||
(pos.unrealizedPnl || 125.50) >= 0 ? 'text-green-400' : 'text-red-400'
|
(pos.unrealizedPnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'
|
||||||
}`}>
|
}`}>
|
||||||
{(pos.unrealizedPnl || 125.50) >= 0 ? '+' : ''}${typeof pos.unrealizedPnl === 'number' ? pos.unrealizedPnl.toFixed(2) : '125.50'}
|
{(pos.unrealizedPnl || 0) >= 0 ? '+' : ''}${typeof pos.unrealizedPnl === 'number' ? pos.unrealizedPnl.toFixed(2) : '0.00'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -19,44 +19,26 @@ export default function TradingHistory() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchTrades() {
|
async function fetchTrades() {
|
||||||
try {
|
try {
|
||||||
|
// Try Drift trading history first
|
||||||
|
const driftRes = await fetch('/api/drift/trading-history')
|
||||||
|
if (driftRes.ok) {
|
||||||
|
const data = await driftRes.json()
|
||||||
|
if (data.success && data.trades) {
|
||||||
|
setTrades(data.trades)
|
||||||
|
} else {
|
||||||
|
// No trades available
|
||||||
|
setTrades([])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// API failed - try fallback to local database
|
||||||
const res = await fetch('/api/trading-history')
|
const res = await fetch('/api/trading-history')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
setTrades(data)
|
setTrades(data || [])
|
||||||
} else {
|
} else {
|
||||||
// Mock data for demonstration
|
// Both APIs failed - show empty state
|
||||||
setTrades([
|
setTrades([])
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
symbol: 'BTCUSD',
|
|
||||||
side: 'BUY',
|
|
||||||
amount: 0.1,
|
|
||||||
price: 45230.50,
|
|
||||||
status: 'FILLED',
|
|
||||||
executedAt: new Date().toISOString(),
|
|
||||||
pnl: 125.50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
symbol: 'ETHUSD',
|
|
||||||
side: 'SELL',
|
|
||||||
amount: 2.5,
|
|
||||||
price: 2856.75,
|
|
||||||
status: 'FILLED',
|
|
||||||
executedAt: new Date(Date.now() - 3600000).toISOString(),
|
|
||||||
pnl: -67.25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
symbol: 'SOLUSD',
|
|
||||||
side: 'BUY',
|
|
||||||
amount: 10,
|
|
||||||
price: 95.80,
|
|
||||||
status: 'FILLED',
|
|
||||||
executedAt: new Date(Date.now() - 7200000).toISOString(),
|
|
||||||
pnl: 89.75
|
|
||||||
}
|
}
|
||||||
])
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch trades:', error)
|
console.error('Failed to fetch trades:', error)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
network: host
|
||||||
|
|
||||||
# Base environment variables (common to all environments)
|
# Base environment variables (common to all environments)
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -50,6 +50,20 @@ export interface AccountBalance {
|
|||||||
accountValue: number
|
accountValue: number
|
||||||
leverage: number
|
leverage: number
|
||||||
availableBalance: number
|
availableBalance: number
|
||||||
|
netUsdValue: number
|
||||||
|
unrealizedPnl: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TradeHistory {
|
||||||
|
id: string
|
||||||
|
symbol: string
|
||||||
|
side: 'BUY' | 'SELL'
|
||||||
|
amount: number
|
||||||
|
price: number
|
||||||
|
status: 'FILLED' | 'PENDING' | 'CANCELLED'
|
||||||
|
executedAt: string
|
||||||
|
pnl?: number
|
||||||
|
txId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginStatus {
|
export interface LoginStatus {
|
||||||
@@ -169,8 +183,11 @@ export class DriftTradingService {
|
|||||||
async getAccountBalance(): Promise<AccountBalance> {
|
async getAccountBalance(): Promise<AccountBalance> {
|
||||||
try {
|
try {
|
||||||
if (this.isInitialized && this.driftClient) {
|
if (this.isInitialized && this.driftClient) {
|
||||||
// Try to use SDK without subscription
|
// Subscribe to user account to access balance data
|
||||||
try {
|
try {
|
||||||
|
console.log('🔍 Subscribing to user account for balance...')
|
||||||
|
await this.driftClient.subscribe()
|
||||||
|
|
||||||
const user = this.driftClient.getUser()
|
const user = this.driftClient.getUser()
|
||||||
|
|
||||||
// Get account equity and collateral information using proper SDK methods
|
// Get account equity and collateral information using proper SDK methods
|
||||||
@@ -184,6 +201,47 @@ export class DriftTradingService {
|
|||||||
QUOTE_PRECISION
|
QUOTE_PRECISION
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Try to get net USD value using more comprehensive methods
|
||||||
|
let calculatedNetUsdValue = totalCollateral
|
||||||
|
try {
|
||||||
|
// Check if there's a direct method for net USD value or equity
|
||||||
|
// Try different possible method names
|
||||||
|
let directNetValue = null
|
||||||
|
if ('getNetUsdValue' in user) {
|
||||||
|
directNetValue = convertToNumber((user as any).getNetUsdValue(), QUOTE_PRECISION)
|
||||||
|
} else if ('getEquity' in user) {
|
||||||
|
directNetValue = convertToNumber((user as any).getEquity(), QUOTE_PRECISION)
|
||||||
|
} else if ('getTotalAccountValue' in user) {
|
||||||
|
directNetValue = convertToNumber((user as any).getTotalAccountValue(), QUOTE_PRECISION)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directNetValue !== null) {
|
||||||
|
calculatedNetUsdValue = directNetValue
|
||||||
|
console.log(`📊 Direct net USD value: $${calculatedNetUsdValue.toFixed(2)}`)
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ No direct net USD method found, will calculate manually')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('⚠️ Direct net USD method failed:', (e as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get unsettled PnL and funding
|
||||||
|
let unsettledBalance = 0
|
||||||
|
try {
|
||||||
|
// Try different approaches to get unsettled amounts
|
||||||
|
if ('getUnsettledPnl' in user) {
|
||||||
|
unsettledBalance += convertToNumber((user as any).getUnsettledPnl(), QUOTE_PRECISION)
|
||||||
|
}
|
||||||
|
if ('getPendingFundingPayments' in user) {
|
||||||
|
unsettledBalance += convertToNumber((user as any).getPendingFundingPayments(), QUOTE_PRECISION)
|
||||||
|
}
|
||||||
|
if (unsettledBalance !== 0) {
|
||||||
|
console.log(`📊 Unsettled balance: $${unsettledBalance.toFixed(2)}`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('⚠️ Unsettled balance calculation failed:', (e as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate margin requirement using proper method
|
// Calculate margin requirement using proper method
|
||||||
let marginRequirement = 0
|
let marginRequirement = 0
|
||||||
try {
|
try {
|
||||||
@@ -201,17 +259,74 @@ export class DriftTradingService {
|
|||||||
const leverage = marginRequirement > 0 ? totalCollateral / marginRequirement : 1
|
const leverage = marginRequirement > 0 ? totalCollateral / marginRequirement : 1
|
||||||
const availableBalance = freeCollateral
|
const availableBalance = freeCollateral
|
||||||
|
|
||||||
|
// Calculate unrealized PnL from all positions
|
||||||
|
let totalUnrealizedPnl = 0
|
||||||
|
try {
|
||||||
|
// Get all perp positions to calculate total unrealized PnL
|
||||||
|
const mainMarkets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] // Check more markets for PnL
|
||||||
|
|
||||||
|
for (const marketIndex of mainMarkets) {
|
||||||
|
try {
|
||||||
|
const position = user.getPerpPosition(marketIndex)
|
||||||
|
if (!position || position.baseAssetAmount.isZero()) continue
|
||||||
|
|
||||||
|
// Calculate unrealized PnL manually
|
||||||
|
const marketData = this.driftClient.getPerpMarketAccount(marketIndex)
|
||||||
|
const markPrice = convertToNumber(marketData?.amm.lastMarkPriceTwap || new BN(0), PRICE_PRECISION)
|
||||||
|
|
||||||
|
const entryPrice = convertToNumber(position.quoteEntryAmount.abs(), PRICE_PRECISION) /
|
||||||
|
convertToNumber(position.baseAssetAmount.abs(), BASE_PRECISION)
|
||||||
|
const size = convertToNumber(position.baseAssetAmount.abs(), BASE_PRECISION)
|
||||||
|
const isLong = position.baseAssetAmount.gt(new BN(0))
|
||||||
|
|
||||||
|
const unrealizedPnl = isLong ?
|
||||||
|
(markPrice - entryPrice) * size :
|
||||||
|
(entryPrice - markPrice) * size
|
||||||
|
|
||||||
|
totalUnrealizedPnl += unrealizedPnl
|
||||||
|
} catch (e) {
|
||||||
|
// Skip markets that don't exist
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not calculate unrealized PnL:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Net USD Value calculation with enhanced accuracy
|
||||||
|
let finalNetUsdValue = calculatedNetUsdValue
|
||||||
|
|
||||||
|
// If we got a direct value, use it, otherwise calculate manually
|
||||||
|
if (calculatedNetUsdValue === totalCollateral) {
|
||||||
|
// Manual calculation: Total Collateral + Unrealized PnL + Unsettled
|
||||||
|
finalNetUsdValue = totalCollateral + totalUnrealizedPnl + unsettledBalance
|
||||||
|
console.log(`📊 Manual calculation: Collateral($${totalCollateral.toFixed(2)}) + PnL($${totalUnrealizedPnl.toFixed(2)}) + Unsettled($${unsettledBalance.toFixed(2)}) = $${finalNetUsdValue.toFixed(2)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`💰 Account balance: $${accountValue.toFixed(2)}, Net USD: $${finalNetUsdValue.toFixed(2)}, PnL: $${totalUnrealizedPnl.toFixed(2)}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalCollateral,
|
totalCollateral,
|
||||||
freeCollateral,
|
freeCollateral,
|
||||||
marginRequirement,
|
marginRequirement,
|
||||||
accountValue,
|
accountValue,
|
||||||
leverage,
|
leverage,
|
||||||
availableBalance
|
availableBalance,
|
||||||
|
netUsdValue: finalNetUsdValue,
|
||||||
|
unrealizedPnl: totalUnrealizedPnl
|
||||||
}
|
}
|
||||||
} catch (sdkError: any) {
|
} catch (sdkError: any) {
|
||||||
console.log('⚠️ SDK method failed, using fallback:', sdkError.message)
|
console.log('⚠️ SDK balance method failed, using fallback:', sdkError.message)
|
||||||
// Fall through to fallback method
|
// Fall through to fallback method
|
||||||
|
} finally {
|
||||||
|
// Always unsubscribe to clean up
|
||||||
|
if (this.driftClient) {
|
||||||
|
try {
|
||||||
|
await this.driftClient.unsubscribe()
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore unsubscribe errors
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +340,9 @@ export class DriftTradingService {
|
|||||||
marginRequirement: 0,
|
marginRequirement: 0,
|
||||||
accountValue: balance / 1e9, // SOL balance
|
accountValue: balance / 1e9, // SOL balance
|
||||||
leverage: 0,
|
leverage: 0,
|
||||||
availableBalance: 0
|
availableBalance: 0,
|
||||||
|
netUsdValue: balance / 1e9, // Use SOL balance as fallback
|
||||||
|
unrealizedPnl: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -267,18 +384,22 @@ export class DriftTradingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getPositions(): Promise<Position[]> {
|
async getPositions(): Promise<Position[]> {
|
||||||
if (!this.driftClient || !this.isInitialized) {
|
try {
|
||||||
throw new Error('Client not logged in. Call login() first.')
|
if (this.isInitialized && this.driftClient) {
|
||||||
}
|
// Subscribe to user account to access positions
|
||||||
|
try {
|
||||||
|
console.log('🔍 Subscribing to user account for positions...')
|
||||||
await this.driftClient.subscribe()
|
await this.driftClient.subscribe()
|
||||||
|
|
||||||
const user = this.driftClient.getUser()
|
const user = this.driftClient.getUser()
|
||||||
|
|
||||||
// Get all available markets
|
// Get all available markets
|
||||||
const positions: Position[] = []
|
const positions: Position[] = []
|
||||||
|
|
||||||
// Check perp positions
|
// Check perp positions - limit to main markets to avoid timeouts
|
||||||
for (let marketIndex = 0; marketIndex < 20; marketIndex++) { // Check first 20 markets
|
const mainMarkets = [0, 1, 2, 3, 4, 5]; // SOL, BTC, ETH and a few others
|
||||||
|
|
||||||
|
for (const marketIndex of mainMarkets) {
|
||||||
try {
|
try {
|
||||||
const p = user.getPerpPosition(marketIndex)
|
const p = user.getPerpPosition(marketIndex)
|
||||||
if (!p || p.baseAssetAmount.isZero()) continue
|
if (!p || p.baseAssetAmount.isZero()) continue
|
||||||
@@ -306,16 +427,79 @@ export class DriftTradingService {
|
|||||||
marketIndex,
|
marketIndex,
|
||||||
marketType: 'PERP'
|
marketType: 'PERP'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log(`✅ Found position: ${this.getSymbolFromMarketIndex(marketIndex)} ${isLong ? 'LONG' : 'SHORT'} ${size}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Skip markets that don't exist or have errors
|
// Skip markets that don't exist or have errors
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.driftClient) {
|
console.log(`📊 Found ${positions.length} total positions`)
|
||||||
await this.driftClient.unsubscribe()
|
|
||||||
}
|
|
||||||
return positions
|
return positions
|
||||||
|
|
||||||
|
} catch (sdkError: any) {
|
||||||
|
console.log('⚠️ SDK positions method failed, using fallback:', sdkError.message)
|
||||||
|
// Fall through to fallback method
|
||||||
|
} finally {
|
||||||
|
// Always unsubscribe to clean up
|
||||||
|
if (this.driftClient) {
|
||||||
|
try {
|
||||||
|
await this.driftClient.unsubscribe()
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore unsubscribe errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Return empty array instead of demo data
|
||||||
|
console.log('📊 Using fallback positions method - returning empty positions')
|
||||||
|
return []
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('❌ Error getting positions:', error)
|
||||||
|
return [] // Return empty array instead of throwing error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTradingHistory(limit: number = 50): Promise<TradeHistory[]> {
|
||||||
|
try {
|
||||||
|
console.log('📊 Fetching trading history...')
|
||||||
|
|
||||||
|
// 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...')
|
||||||
|
|
||||||
|
// For now, return empty array as Drift SDK trading history is complex
|
||||||
|
// and requires parsing transaction logs. This would be implemented
|
||||||
|
// by analyzing on-chain transaction history for the user account.
|
||||||
|
console.log('⚠️ Drift SDK order history not implemented yet - using fallback')
|
||||||
|
|
||||||
|
} catch (sdkError: any) {
|
||||||
|
console.log('⚠️ SDK order history failed, using fallback:', sdkError.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Check if we have any trades in local database
|
||||||
|
try {
|
||||||
|
// This would normally query Prisma for any executed trades
|
||||||
|
console.log('📊 Checking local trade database...')
|
||||||
|
|
||||||
|
// For now, return empty array to show "No trading history"
|
||||||
|
// rather than demo data
|
||||||
|
return []
|
||||||
|
|
||||||
|
} catch (dbError: any) {
|
||||||
|
console.log('⚠️ Database query failed:', dbError.message)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('❌ Error getting trading history:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: map symbol to market index using Drift market data
|
// Helper: map symbol to market index using Drift market data
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user