diff --git a/app/api/drift/trading-history/route.ts b/app/api/drift/trading-history/route.ts
new file mode 100644
index 0000000..71643c3
--- /dev/null
+++ b/app/api/drift/trading-history/route.ts
@@ -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 }
+ )
+ }
+}
diff --git a/components/Dashboard.tsx b/components/Dashboard.tsx
index 507889c..4f0add5 100644
--- a/components/Dashboard.tsx
+++ b/components/Dashboard.tsx
@@ -25,11 +25,11 @@ export default function Dashboard() {
try {
setLoading(true)
- // Try to get Drift positions first
+ // Get Drift positions
const driftRes = await fetch('/api/drift/positions')
if (driftRes.ok) {
const driftData = await driftRes.json()
- if (driftData.positions && driftData.positions.length > 0) {
+ if (driftData.positions) {
setPositions(driftData.positions)
// Calculate stats from Drift positions
@@ -41,7 +41,7 @@ export default function Dashboard() {
totalTrades: driftData.positions.length
}))
- // Try to get account balance for account value
+ // Get account balance for account value
try {
const balanceRes = await fetch('/api/drift/balance')
if (balanceRes.ok) {
@@ -55,44 +55,39 @@ export default function Dashboard() {
console.warn('Could not fetch balance:', e)
}
} else {
- // Fallback to legacy trading API
- const res = await fetch('/api/trading')
- if (res.ok) {
- const data = await res.json()
- setPositions(data.positions || [])
- // Calculate some mock stats for demo
- setStats({
- totalPnL: 1247.50,
- dailyPnL: 67.25,
- winRate: 73.2,
- totalTrades: 156,
- accountValue: 10000
- })
- } else {
- setError('Failed to load positions')
- }
+ // No positions available - set empty state
+ setPositions([])
+ setStats({
+ totalPnL: 0,
+ dailyPnL: 0,
+ winRate: 0,
+ totalTrades: 0,
+ accountValue: 0
+ })
}
} else {
- // Fallback to legacy trading API
- const res = await fetch('/api/trading')
- if (res.ok) {
- const data = await res.json()
- setPositions(data.positions || [])
- // Calculate some mock stats for demo
- setStats({
- totalPnL: 1247.50,
- dailyPnL: 67.25,
- winRate: 73.2,
- totalTrades: 156,
- accountValue: 10000
- })
- } else {
- setError('Failed to load positions')
- }
+ // API failed - set empty state
+ setError('Failed to connect to Drift')
+ setPositions([])
+ setStats({
+ totalPnL: 0,
+ dailyPnL: 0,
+ winRate: 0,
+ totalTrades: 0,
+ accountValue: 0
+ })
}
} catch (e) {
- setError('Error loading positions')
+ setError('Error connecting to Drift')
console.error('Error:', e)
+ setPositions([])
+ setStats({
+ totalPnL: 0,
+ dailyPnL: 0,
+ winRate: 0,
+ totalTrades: 0,
+ accountValue: 0
+ })
}
setLoading(false)
}
@@ -238,10 +233,10 @@ export default function Dashboard() {
- {pos.symbol?.slice(0, 2) || 'BT'}
+ {pos.symbol?.slice(0, 2) || '--'}
-
{pos.symbol || 'BTC/USD'}
+
{pos.symbol || '--'}
@@ -250,20 +245,20 @@ export default function Dashboard() {
? 'bg-green-500/20 text-green-400'
: 'bg-red-500/20 text-red-400'
}`}>
- {pos.side || 'Long'}
+ {pos.side || '--'}
|
- {typeof pos.size === 'number' ? pos.size.toFixed(4) : (pos.size || '0.1 BTC')}
+ {typeof pos.size === 'number' ? pos.size.toFixed(4) : '--'}
|
- ${typeof pos.entryPrice === 'number' ? pos.entryPrice.toFixed(2) : (pos.entryPrice || '45,230.00')}
+ ${typeof pos.entryPrice === 'number' ? pos.entryPrice.toFixed(2) : '--'}
|
= 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'}
|
diff --git a/components/TradingHistory.tsx b/components/TradingHistory.tsx
index 2d38f45..00d6e2f 100644
--- a/components/TradingHistory.tsx
+++ b/components/TradingHistory.tsx
@@ -19,44 +19,26 @@ export default function TradingHistory() {
useEffect(() => {
async function fetchTrades() {
try {
- const res = await fetch('/api/trading-history')
- if (res.ok) {
- const data = await res.json()
- setTrades(data)
+ // 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 {
- // Mock data for demonstration
- 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
- }
- ])
+ // API failed - try fallback to local database
+ const res = await fetch('/api/trading-history')
+ if (res.ok) {
+ const data = await res.json()
+ setTrades(data || [])
+ } else {
+ // Both APIs failed - show empty state
+ setTrades([])
+ }
}
} catch (error) {
console.error('Failed to fetch trades:', error)
diff --git a/docker-compose.yml b/docker-compose.yml
index 6850dd1..8e105d1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,7 @@ services:
build:
context: .
dockerfile: Dockerfile
+ network: host
# Base environment variables (common to all environments)
environment:
diff --git a/lib/drift-trading.ts b/lib/drift-trading.ts
index 93cafb6..6ded764 100644
--- a/lib/drift-trading.ts
+++ b/lib/drift-trading.ts
@@ -52,6 +52,18 @@ export interface AccountBalance {
availableBalance: 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 {
isLoggedIn: boolean
publicKey: string
@@ -267,55 +279,106 @@ export class DriftTradingService {
}
async getPositions(): Promise {
- if (!this.driftClient || !this.isInitialized) {
- throw new Error('Client not logged in. Call login() first.')
- }
+ try {
+ if (this.isInitialized && this.driftClient) {
+ // Try to use SDK without subscription
+ try {
+ const user = this.driftClient.getUser()
+
+ // Get all available markets
+ const positions: Position[] = []
+
+ // Check perp positions - limit to main markets to avoid timeouts
+ const mainMarkets = [0, 1, 2, 3, 4, 5]; // SOL, BTC, ETH and a few others
+
+ for (const marketIndex of mainMarkets) {
+ try {
+ const p = user.getPerpPosition(marketIndex)
+ if (!p || p.baseAssetAmount.isZero()) continue
+
+ // Get market price without subscription
+ const marketData = this.driftClient.getPerpMarketAccount(marketIndex)
+ const markPrice = convertToNumber(marketData?.amm.lastMarkPriceTwap || new BN(0), PRICE_PRECISION)
+
+ // Calculate unrealized PnL
+ const entryPrice = convertToNumber(p.quoteEntryAmount.abs(), PRICE_PRECISION) /
+ convertToNumber(p.baseAssetAmount.abs(), BASE_PRECISION)
+ const size = convertToNumber(p.baseAssetAmount.abs(), BASE_PRECISION)
+ const isLong = p.baseAssetAmount.gt(new BN(0))
+ const unrealizedPnl = isLong ?
+ (markPrice - entryPrice) * size :
+ (entryPrice - markPrice) * size
- await this.driftClient.subscribe()
- const user = this.driftClient.getUser()
-
- // Get all available markets
- const positions: Position[] = []
-
- // Check perp positions
- for (let marketIndex = 0; marketIndex < 20; marketIndex++) { // Check first 20 markets
- try {
- const p = user.getPerpPosition(marketIndex)
- if (!p || p.baseAssetAmount.isZero()) continue
-
- // Get market price
- const marketData = this.driftClient.getPerpMarketAccount(marketIndex)
- const markPrice = convertToNumber(marketData?.amm.lastMarkPriceTwap || new BN(0), PRICE_PRECISION)
-
- // Calculate unrealized PnL
- const entryPrice = convertToNumber(p.quoteEntryAmount.abs(), PRICE_PRECISION) /
- convertToNumber(p.baseAssetAmount.abs(), BASE_PRECISION)
- const size = convertToNumber(p.baseAssetAmount.abs(), BASE_PRECISION)
- const isLong = p.baseAssetAmount.gt(new BN(0))
- const unrealizedPnl = isLong ?
- (markPrice - entryPrice) * size :
- (entryPrice - markPrice) * size
-
- positions.push({
- symbol: this.getSymbolFromMarketIndex(marketIndex),
- side: isLong ? 'LONG' : 'SHORT',
- size,
- entryPrice,
- markPrice,
- unrealizedPnl,
- marketIndex,
- marketType: 'PERP'
- })
- } catch (error) {
- // Skip markets that don't exist or have errors
- continue
+ positions.push({
+ symbol: this.getSymbolFromMarketIndex(marketIndex),
+ side: isLong ? 'LONG' : 'SHORT',
+ size,
+ entryPrice,
+ markPrice,
+ unrealizedPnl,
+ marketIndex,
+ marketType: 'PERP'
+ })
+ } catch (error) {
+ // Skip markets that don't exist or have errors
+ continue
+ }
+ }
+
+ return positions
+ } catch (sdkError: any) {
+ console.log('⚠️ SDK positions method failed, using fallback:', sdkError.message)
+ // Fall through to fallback method
+ }
}
+
+ // 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
}
-
- if (this.driftClient) {
- await this.driftClient.unsubscribe()
+ }
+
+ async getTradingHistory(limit: number = 50): Promise {
+ 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 []
}
- return positions
}
// Helper: map symbol to market index using Drift market data
diff --git a/lib/tradingview-automation.ts b/lib/tradingview-automation.ts
index ea84149..09f82cb 100644
--- a/lib/tradingview-automation.ts
+++ b/lib/tradingview-automation.ts
@@ -695,7 +695,7 @@ export class TradingViewAutomation {
const text = await button.textContent() || ''
const trimmedText = text.trim().toLowerCase()
- console.log(`📝 Button ${i + 1}: "${trimmedText}"`)
+ console.log(`📝
if (trimmedText.includes('email') ||
trimmedText.includes('continue with email') ||
@@ -877,21 +877,30 @@ export class TradingViewAutomation {
// Handle potential captcha
console.log('🤖 Checking for captcha...')
try {
- // Look for different types of captcha
+ // Look for different types of captcha and robot confirmation
const captchaSelectors = [
'iframe[src*="recaptcha"]',
'iframe[src*="captcha"]',
'.recaptcha-checkbox',
'[data-testid="captcha"]',
- '.captcha-container'
+ '.captcha-container',
+ 'text="Please confirm that you are not a robot"',
+ 'text="Are you human?"',
+ 'text="Please verify you are human"',
+ 'text="Security check"',
+ '.tv-dialog__error:has-text("robot")',
+ '.alert:has-text("robot")',
+ '.error:has-text("robot")'
]
let captchaFound = false
+ let captchaType = ''
for (const selector of captchaSelectors) {
try {
if (await this.page.locator(selector).isVisible({ timeout: 2000 })) {
- console.log(`🤖 Captcha detected: ${selector}`)
+ console.log(`🤖 Captcha/Robot check detected: ${selector}`)
captchaFound = true
+ captchaType = selector
break
}
} catch (e) {
@@ -900,15 +909,32 @@ export class TradingViewAutomation {
}
if (captchaFound) {
- console.log('⚠️ Captcha detected - this requires manual intervention')
- console.log('🖱️ Please solve the captcha manually within 30 seconds...')
+ console.log('⚠️ CAPTCHA/Robot verification detected!')
+ console.log('🚫 This indicates TradingView has flagged this as automated behavior.')
+ console.log('� In a Docker environment, automated captcha solving is not feasible.')
- // Wait for captcha to be solved
- await this.page.waitForTimeout(30000)
- console.log('⏳ Proceeding after captcha wait period')
+ // Take a screenshot for debugging
+ await this.takeDebugScreenshot('captcha_detected')
+
+ // Instead of waiting, we should fail fast and suggest alternatives
+ console.log('❌ Cannot proceed with automated login due to captcha protection.')
+ console.log('🔧 Possible solutions:')
+ console.log(' 1. Use a different IP address or VPN')
+ console.log(' 2. Wait some time before retrying (rate limiting)')
+ console.log(' 3. Use session persistence from a manually authenticated browser')
+ console.log(' 4. Contact TradingView support if this persists')
+
+ // Mark captcha detection for future reference
+ await this.markCaptchaDetected()
+
+ // Return false immediately instead of waiting
+ throw new Error(`Captcha detected (${captchaType}) - automated login blocked`)
}
} catch (captchaError: any) {
+ if (captchaError.message.includes('Captcha detected')) {
+ throw captchaError
+ }
console.log('⚠️ Captcha check failed:', captchaError?.message)
}
@@ -1106,17 +1132,64 @@ export class TradingViewAutomation {
return true
}
- console.log('🔐 Not logged in, starting automated login process...')
+ console.log('🔐 Not logged in, checking session persistence options...')
- // Try automated login first
+ // Before attempting login, check if we have any saved session data
+ const sessionInfo = await this.testSessionPersistence()
+ console.log('📊 Session persistence check:', sessionInfo)
+
+ if (sessionInfo.cookiesCount > 0) {
+ console.log('🍪 Found saved session data, attempting to restore...')
+
+ // Try navigating to TradingView with saved session first
+ try {
+ await this.page.goto('https://www.tradingview.com/', {
+ waitUntil: 'domcontentloaded',
+ timeout: 30000
+ })
+
+ // Restore session storage after navigation
+ await this.restoreSessionStorage()
+
+ // Wait for page to settle and check login status
+ await this.page.waitForTimeout(5000)
+
+ const nowLoggedIn = await this.checkLoginStatus()
+ if (nowLoggedIn) {
+ console.log('✅ Session restoration successful! Login confirmed.')
+ this.isAuthenticated = true
+ await this.saveSession()
+ return true
+ } else {
+ console.log('⚠️ Session restoration failed, saved session may be expired')
+ }
+ } catch (e) {
+ console.log('❌ Session restoration attempt failed:', e)
+ }
+ }
+
+ // Only attempt automated login if we don't have valid session data
console.log('🤖 Attempting automated login...')
- const autoLoginSuccess = await this.login(credentials)
-
- if (autoLoginSuccess) {
- console.log('✅ Automated login successful! Saving session for future use.')
- this.isAuthenticated = true
- await this.saveSession()
- return true
+ try {
+ const autoLoginSuccess = await this.login(credentials)
+
+ if (autoLoginSuccess) {
+ console.log('✅ Automated login successful! Saving session for future use.')
+ this.isAuthenticated = true
+ await this.saveSession()
+ return true
+ }
+ } catch (loginError: any) {
+ if (loginError.message.includes('Captcha detected')) {
+ console.log('🚫 Captcha protection encountered during automated login.')
+ console.log('💡 To resolve this issue:')
+ console.log(' 1. Clear any existing session data: docker-compose exec app rm -f /app/session_*.json')
+ console.log(' 2. Wait 1-2 hours before retrying (to clear rate limiting)')
+ console.log(' 3. Consider using a different IP address or VPN')
+ console.log(' 4. Manually log in to TradingView in a browser to establish a valid session')
+ return false
+ }
+ throw loginError
}
console.log('❌ Automated login failed, this is likely due to captcha protection.')
@@ -2193,6 +2266,34 @@ export class TradingViewAutomation {
}
}
+ /**
+ * Mark that a captcha was detected (to implement cooldown)
+ */
+ private async markCaptchaDetected(): Promise {
+ try {
+ const captchaMarkerFile = path.join(process.cwd(), 'captcha_detected.json')
+ const markerData = {
+ timestamp: new Date().toISOString(),
+ count: 1
+ }
+
+ // If marker already exists, increment count
+ if (await this.fileExists(captchaMarkerFile)) {
+ try {
+ const existing = JSON.parse(await fs.readFile(captchaMarkerFile, 'utf8'))
+ markerData.count = (existing.count || 0) + 1
+ } catch (e) {
+ // Use defaults if can't read existing file
+ }
+ }
+
+ await fs.writeFile(captchaMarkerFile, JSON.stringify(markerData, null, 2))
+ console.log(`📝 Marked captcha detection #${markerData.count} at ${markerData.timestamp}`)
+ } catch (error) {
+ console.log('⚠️ Error marking captcha detection:', error)
+ }
+ }
+
/**
* Check if file exists
*/