fix: timeframe handling and progress tracking improvements
- Fix timeframe parameter handling in enhanced-screenshot API route - Support both 'timeframe' (singular) and 'timeframes' (array) parameters - Add proper sessionId propagation for real-time progress tracking - Enhance MACD analysis prompt with detailed crossover definitions - Add progress tracker service with Server-Sent Events support - Fix Next.js build errors in chart components (module variable conflicts) - Change dev environment port from 9000:3000 to 9001:3000 - Improve AI analysis layout detection logic - Add comprehensive progress tracking through all service layers
This commit is contained in:
@@ -64,7 +64,7 @@
|
||||
"value": "*",
|
||||
"domain": ".tradingview.com",
|
||||
"path": "/",
|
||||
"expires": 1760180481,
|
||||
"expires": 1760464150,
|
||||
"httpOnly": false,
|
||||
"secure": true,
|
||||
"sameSite": "None"
|
||||
@@ -79,24 +79,24 @@
|
||||
"secure": false,
|
||||
"sameSite": "Lax"
|
||||
},
|
||||
{
|
||||
"name": "_sp_id.cf1a",
|
||||
"value": ".1752404481.1.1752404482..f540bb6e-fa0b-496b-9640-1d4fec1e4c8e..656514fd-a0d5-4c9c-9763-be49bfa3bb6e.1752404481740.1",
|
||||
"domain": ".tradingview.com",
|
||||
"path": "/",
|
||||
"expires": 1786964481.740595,
|
||||
"httpOnly": false,
|
||||
"secure": true,
|
||||
"sameSite": "None"
|
||||
},
|
||||
{
|
||||
"name": "sp",
|
||||
"value": "476371e7-d9df-4cab-9f6c-9af104097490",
|
||||
"domain": "snowplow-pixel.tradingview.com",
|
||||
"path": "/",
|
||||
"expires": 1783940482.306256,
|
||||
"expires": 1784224154.521872,
|
||||
"httpOnly": true,
|
||||
"secure": true,
|
||||
"sameSite": "None"
|
||||
},
|
||||
{
|
||||
"name": "_sp_id.cf1a",
|
||||
"value": ".1752404481.1.1752688150..f540bb6e-fa0b-496b-9640-1d4fec1e4c8e..656514fd-a0d5-4c9c-9763-be49bfa3bb6e.1752404481740.3",
|
||||
"domain": ".tradingview.com",
|
||||
"path": "/",
|
||||
"expires": 1787248149.806463,
|
||||
"httpOnly": false,
|
||||
"secure": true,
|
||||
"sameSite": "None"
|
||||
}
|
||||
]
|
||||
@@ -1,17 +1,11 @@
|
||||
{
|
||||
"localStorage": {
|
||||
"cookie_dialog_tracked": "1",
|
||||
"snowplowOutQueue_tv_cf_post2.expires": "1815476481741",
|
||||
"tvlocalstorage.available": "true",
|
||||
"featuretoggle_seed": "983912",
|
||||
"first_visit_time": "1752404384075",
|
||||
"snowplowOutQueue_tv_cf_post2": "[]",
|
||||
"auto-show-email-for-signin": "1",
|
||||
"snowplowOutQueue_tv_cf_post2.expires": "1815760150522",
|
||||
"tvlocalstorage.available": "true",
|
||||
"last-crosstab-monotonic-timestamp": "1752688195853",
|
||||
"trial_availiable": "0",
|
||||
"_grecaptcha": "09ANMylNBaIGTVsUxDDAB11tBaVKRevHvUG6E16KDP4nm97sYkmHshpfmxoAkcFfbj7mFb3zg4rncfzqc6A9g-20ErWdRAoBN59yOsTLvoV3Oc39otwCTzVeNmXMQoHwHs",
|
||||
"last-crosstab-monotonic-timestamp": "1752404482390",
|
||||
"last_username": "mindesbunister",
|
||||
"signupSource": "auth page tvd"
|
||||
"last_username": "mindesbunister"
|
||||
},
|
||||
"sessionStorage": {}
|
||||
}
|
||||
57
RESTART_DOCKER_INSTRUCTIONS.md
Normal file
57
RESTART_DOCKER_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 🔄 Docker Container Restart Required
|
||||
|
||||
## Why Restart is Needed
|
||||
|
||||
You need to restart your Docker container because we made the following changes:
|
||||
|
||||
1. **Changed API imports**: Updated `/app/api/enhanced-screenshot/route.js` to use `enhanced-screenshot.ts` instead of `enhanced-screenshot-simple.ts`
|
||||
2. **Added progress tracking**: The new implementation uses real-time progress tracking with EventEmitter
|
||||
3. **New API endpoint**: Added `/app/api/progress/[sessionId]/stream/route.ts` for Server-Sent Events
|
||||
4. **Updated service logic**: Modified core screenshot capture logic with progress tracking
|
||||
|
||||
## Restart Commands
|
||||
|
||||
### For Development Environment:
|
||||
```bash
|
||||
# Stop current containers
|
||||
npm run docker:down
|
||||
|
||||
# Rebuild and start with development config
|
||||
npm run docker:dev
|
||||
|
||||
# Or if you want to see logs immediately:
|
||||
npm run docker:up:build
|
||||
```
|
||||
|
||||
### For Production Environment:
|
||||
```bash
|
||||
# Stop production containers
|
||||
npm run docker:prod:down
|
||||
|
||||
# Rebuild and start production
|
||||
npm run docker:prod:build
|
||||
npm run docker:prod:up
|
||||
```
|
||||
|
||||
## What to Expect After Restart
|
||||
|
||||
✅ **Real-time progress tracking**: Progress bar will update as each step completes
|
||||
✅ **Smooth animations**: CSS animations will work with the new progress data
|
||||
✅ **Live step updates**: Each step (init, auth, navigation, loading, capture, analysis) will show in real-time
|
||||
✅ **Better user experience**: No more waiting for the entire process to complete before seeing progress
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If animations still don't work after restart:
|
||||
|
||||
1. **Check browser console** for JavaScript errors
|
||||
2. **Verify EventSource connection** in Network tab (should see `progress/[sessionId]/stream`)
|
||||
3. **Check Docker logs**: `npm run docker:logs`
|
||||
4. **Force rebuild**: `npm run docker:build:no-cache && npm run docker:up`
|
||||
|
||||
## Test the Fix
|
||||
|
||||
1. Go to `/analysis` page
|
||||
2. Select a symbol (e.g., BTCUSD)
|
||||
3. Click "Analyze"
|
||||
4. You should see progress steps moving in real-time as they complete
|
||||
59
SCREENSHOT_PATH_FIXES.md
Normal file
59
SCREENSHOT_PATH_FIXES.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 🔧 Screenshot Path & SSE Fixes
|
||||
|
||||
## Issues Fixed
|
||||
|
||||
### 1. Screenshot File Path Duplication
|
||||
**Problem**: Screenshot paths were showing `/app/screenshots/app/screenshots/...` causing ENOENT errors.
|
||||
|
||||
**Root Cause**: The `takeScreenshot()` method returns a full absolute path, but `analyzeScreenshot()` was treating it as a filename and joining it with the screenshots directory again.
|
||||
|
||||
**Solution**: Updated both `analyzeScreenshot()` and `analyzeMultipleScreenshots()` methods to handle both full paths and filenames:
|
||||
|
||||
```typescript
|
||||
// Check if it's already a full path or just a filename
|
||||
if (path.isAbsolute(filenameOrPath)) {
|
||||
// It's already a full path
|
||||
imagePath = filenameOrPath
|
||||
} else {
|
||||
// It's just a filename, construct the full path
|
||||
const screenshotsDir = path.join(process.cwd(), 'screenshots')
|
||||
imagePath = path.join(screenshotsDir, filenameOrPath)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Next.js Async Params Issue
|
||||
**Problem**: SSE endpoint was using `params.sessionId` without awaiting it, causing Next.js 15 error.
|
||||
|
||||
**Solution**: Updated the SSE endpoint to properly await params:
|
||||
|
||||
```typescript
|
||||
// Before
|
||||
{ params }: { params: { sessionId: string } }
|
||||
const { sessionId } = params
|
||||
|
||||
// After
|
||||
{ params }: { params: Promise<{ sessionId: string }> }
|
||||
const { sessionId } = await params
|
||||
```
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **`/lib/ai-analysis.ts`**:
|
||||
- Fixed `analyzeScreenshot()` to handle full paths
|
||||
- Fixed `analyzeMultipleScreenshots()` to handle full paths
|
||||
- Updated variable references
|
||||
|
||||
2. **`/app/api/progress/[sessionId]/stream/route.ts`**:
|
||||
- Added proper async/await for params in Next.js 15
|
||||
|
||||
## Testing
|
||||
|
||||
After these fixes:
|
||||
✅ Screenshots should save and load correctly
|
||||
✅ AI analysis should find screenshot files
|
||||
✅ SSE progress tracking should work without Next.js warnings
|
||||
✅ Real-time progress updates should function properly
|
||||
|
||||
## No Docker Restart Required
|
||||
|
||||
These are runtime fixes that don't require a Docker container restart. The application should work immediately after the files are updated.
|
||||
@@ -1,19 +1,70 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { enhancedScreenshotService } from '../../../lib/enhanced-screenshot-simple'
|
||||
import { enhancedScreenshotService } from '../../../lib/enhanced-screenshot'
|
||||
import { aiAnalysisService } from '../../../lib/ai-analysis'
|
||||
import { progressTracker } from '../../../lib/progress-tracker'
|
||||
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { symbol, layouts, timeframes, selectedLayouts, analyze = true } = body
|
||||
const { symbol, layouts, timeframe, timeframes, selectedLayouts, analyze = true } = body
|
||||
|
||||
console.log('📊 Enhanced screenshot request:', { symbol, layouts, timeframes, selectedLayouts })
|
||||
console.log('📊 Enhanced screenshot request:', { symbol, layouts, timeframe, timeframes, selectedLayouts })
|
||||
|
||||
// Generate unique session ID for progress tracking
|
||||
const sessionId = `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
console.log('🔍 Created session ID:', sessionId)
|
||||
|
||||
// Create progress tracking session with initial steps
|
||||
const initialSteps = [
|
||||
{
|
||||
id: 'init',
|
||||
title: 'Initializing Analysis',
|
||||
description: 'Starting AI-powered trading analysis...',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'auth',
|
||||
title: 'TradingView Authentication',
|
||||
description: 'Logging into TradingView accounts',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'navigation',
|
||||
title: 'Chart Navigation',
|
||||
description: 'Navigating to chart layouts',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'loading',
|
||||
title: 'Chart Data Loading',
|
||||
description: 'Waiting for chart data and indicators',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'capture',
|
||||
title: 'Screenshot Capture',
|
||||
description: 'Capturing high-quality screenshots',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'analysis',
|
||||
title: 'AI Analysis',
|
||||
description: 'Analyzing screenshots with AI',
|
||||
status: 'pending'
|
||||
}
|
||||
]
|
||||
|
||||
// Create the progress session
|
||||
console.log('🔍 Creating progress session with steps:', initialSteps.length)
|
||||
progressTracker.createSession(sessionId, initialSteps)
|
||||
console.log('🔍 Progress session created successfully')
|
||||
|
||||
// Prepare configuration for screenshot service
|
||||
const config = {
|
||||
symbol: symbol || 'BTCUSD',
|
||||
timeframe: timeframes?.[0] || '60', // Use first timeframe for now
|
||||
timeframe: timeframe || timeframes?.[0] || '60', // Use single timeframe, fallback to first of array, then default
|
||||
layouts: layouts || selectedLayouts || ['ai'],
|
||||
sessionId, // Pass session ID for progress tracking
|
||||
credentials: {
|
||||
email: process.env.TRADINGVIEW_EMAIL,
|
||||
password: process.env.TRADINGVIEW_PASSWORD
|
||||
@@ -22,35 +73,32 @@ export async function POST(request) {
|
||||
|
||||
console.log('🔧 Using config:', config)
|
||||
|
||||
// Capture screenshots using the working service
|
||||
const screenshots = await enhancedScreenshotService.captureWithLogin(config)
|
||||
console.log('📸 Screenshots captured:', screenshots)
|
||||
|
||||
let screenshots = []
|
||||
let analysis = null
|
||||
|
||||
// Perform AI analysis if requested and screenshots were captured
|
||||
if (analyze && screenshots.length > 0) {
|
||||
// Perform AI analysis if requested
|
||||
if (analyze) {
|
||||
try {
|
||||
console.log('🤖 Starting AI analysis...')
|
||||
|
||||
// Extract just the filenames from full paths
|
||||
const filenames = screenshots.map(path => path.split('/').pop())
|
||||
|
||||
if (filenames.length === 1) {
|
||||
analysis = await aiAnalysisService.analyzeScreenshot(filenames[0])
|
||||
} else {
|
||||
analysis = await aiAnalysisService.analyzeMultipleScreenshots(filenames)
|
||||
}
|
||||
|
||||
console.log('✅ AI analysis completed')
|
||||
console.log('🤖 Starting automated capture and analysis...')
|
||||
const result = await aiAnalysisService.captureAndAnalyzeWithConfig(config, sessionId)
|
||||
screenshots = result.screenshots
|
||||
analysis = result.analysis
|
||||
console.log('✅ Automated capture and analysis completed')
|
||||
} catch (analysisError) {
|
||||
console.error('❌ AI analysis failed:', analysisError)
|
||||
// Continue without analysis rather than failing the whole request
|
||||
console.error('❌ Automated capture and analysis failed:', analysisError)
|
||||
// Fall back to screenshot only
|
||||
screenshots = await enhancedScreenshotService.captureWithLogin(config, sessionId)
|
||||
}
|
||||
} else {
|
||||
// Capture screenshots only
|
||||
screenshots = await enhancedScreenshotService.captureWithLogin(config, sessionId)
|
||||
}
|
||||
|
||||
console.log('📸 Final screenshots:', screenshots)
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
sessionId, // Return session ID for progress tracking
|
||||
timestamp: Date.now(),
|
||||
symbol: config.symbol,
|
||||
layouts: config.layouts,
|
||||
|
||||
58
app/api/progress/[sessionId]/stream/route.ts
Normal file
58
app/api/progress/[sessionId]/stream/route.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { progressTracker } from '../../../../../lib/progress-tracker'
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ sessionId: string }> }
|
||||
) {
|
||||
const { sessionId } = await params
|
||||
|
||||
// Create a readable stream for Server-Sent Events
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
// Send initial progress if session exists
|
||||
const initialProgress = progressTracker.getProgress(sessionId)
|
||||
if (initialProgress) {
|
||||
const data = `data: ${JSON.stringify(initialProgress)}\n\n`
|
||||
controller.enqueue(encoder.encode(data))
|
||||
}
|
||||
|
||||
// Listen for progress updates
|
||||
const progressHandler = (progress: any) => {
|
||||
const data = `data: ${JSON.stringify(progress)}\n\n`
|
||||
controller.enqueue(encoder.encode(data))
|
||||
}
|
||||
|
||||
// Listen for completion
|
||||
const completeHandler = () => {
|
||||
const data = `data: ${JSON.stringify({ type: 'complete' })}\n\n`
|
||||
controller.enqueue(encoder.encode(data))
|
||||
controller.close()
|
||||
}
|
||||
|
||||
// Subscribe to events
|
||||
progressTracker.on(`progress:${sessionId}`, progressHandler)
|
||||
progressTracker.on(`progress:${sessionId}:complete`, completeHandler)
|
||||
|
||||
// Cleanup on stream close
|
||||
request.signal.addEventListener('abort', () => {
|
||||
progressTracker.off(`progress:${sessionId}`, progressHandler)
|
||||
progressTracker.off(`progress:${sessionId}:complete`, completeHandler)
|
||||
controller.close()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return new NextResponse(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET',
|
||||
'Access-Control-Allow-Headers': 'Cache-Control'
|
||||
}
|
||||
})
|
||||
}
|
||||
52
app/api/progress/route.ts
Normal file
52
app/api/progress/route.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { progressTracker } from '../../../lib/progress-tracker'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const sessionId = searchParams.get('sessionId')
|
||||
|
||||
if (!sessionId) {
|
||||
return new Response('Session ID required', { status: 400 })
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
// Send initial progress if session exists
|
||||
const currentProgress = progressTracker.getProgress(sessionId)
|
||||
if (currentProgress) {
|
||||
const data = `data: ${JSON.stringify(currentProgress)}\n\n`
|
||||
controller.enqueue(new TextEncoder().encode(data))
|
||||
}
|
||||
|
||||
// Listen for progress updates
|
||||
const progressHandler = (progress: any) => {
|
||||
const data = `data: ${JSON.stringify(progress)}\n\n`
|
||||
controller.enqueue(new TextEncoder().encode(data))
|
||||
}
|
||||
|
||||
const completeHandler = () => {
|
||||
const data = `data: ${JSON.stringify({ type: 'complete' })}\n\n`
|
||||
controller.enqueue(new TextEncoder().encode(data))
|
||||
controller.close()
|
||||
}
|
||||
|
||||
progressTracker.on(`progress:${sessionId}`, progressHandler)
|
||||
progressTracker.on(`progress:${sessionId}:complete`, completeHandler)
|
||||
|
||||
// Cleanup on close
|
||||
return () => {
|
||||
progressTracker.off(`progress:${sessionId}`, progressHandler)
|
||||
progressTracker.off(`progress:${sessionId}:complete`, completeHandler)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Cache-Control'
|
||||
}
|
||||
})
|
||||
}
|
||||
230
app/api/trading/execute-leverage/route.js
Normal file
230
app/api/trading/execute-leverage/route.js
Normal file
@@ -0,0 +1,230 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { jupiterDEXService } from '@/lib/jupiter-dex-service'
|
||||
import { jupiterTriggerService } from '@/lib/jupiter-trigger-service'
|
||||
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const {
|
||||
symbol,
|
||||
side,
|
||||
amount,
|
||||
leverage = 1,
|
||||
stopLoss,
|
||||
takeProfit,
|
||||
useRealDEX = false
|
||||
} = body
|
||||
|
||||
console.log('🚀 Jupiter Leveraged Spot Trade request:', {
|
||||
symbol,
|
||||
side,
|
||||
amount,
|
||||
leverage,
|
||||
stopLoss,
|
||||
takeProfit,
|
||||
useRealDEX
|
||||
})
|
||||
|
||||
// Validate inputs
|
||||
if (!symbol || !side || !amount) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Missing required fields: symbol, side, amount'
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (!['BUY', 'SELL'].includes(side.toUpperCase())) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Invalid side. Must be BUY or SELL'
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Amount must be greater than 0'
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (leverage < 1 || leverage > 10) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Leverage must be between 1x and 10x'
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (!useRealDEX) {
|
||||
// Simulation mode
|
||||
console.log('🎮 Executing SIMULATED leveraged spot trade')
|
||||
|
||||
const currentPrice = symbol === 'SOL' ? 166.75 : symbol === 'BTC' ? 121819 : 3041.66
|
||||
const leveragedAmount = amount * leverage
|
||||
const estimatedTokens = side === 'BUY' ? leveragedAmount / currentPrice : leveragedAmount
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1200))
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
trade: {
|
||||
txId: `jupiter_leverage_sim_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`,
|
||||
symbol: symbol.toUpperCase(),
|
||||
side: side.toUpperCase(),
|
||||
amount: leveragedAmount,
|
||||
leverage: leverage,
|
||||
originalAmount: amount,
|
||||
estimatedTokens: estimatedTokens,
|
||||
entryPrice: currentPrice,
|
||||
timestamp: Date.now(),
|
||||
status: 'FILLED',
|
||||
platform: 'Jupiter DEX (Leveraged Spot)',
|
||||
stopLoss: stopLoss,
|
||||
takeProfit: takeProfit,
|
||||
triggerOrders: stopLoss || takeProfit ? 'PENDING' : 'NONE'
|
||||
},
|
||||
message: `${side.toUpperCase()} ${leveragedAmount} USD worth of ${symbol} (${leverage}x leveraged spot trade) - SIMULATED`
|
||||
})
|
||||
}
|
||||
|
||||
// Real trading with Jupiter DEX + Trigger Orders
|
||||
console.log('💰 Executing REAL leveraged spot trade via Jupiter DEX + Trigger Orders')
|
||||
|
||||
// Step 1: Execute the main trade with leveraged amount
|
||||
const leveragedAmount = amount * leverage
|
||||
const tradingPair = symbol === 'SOL' ? (side === 'BUY' ? 'USDC/SOL' : 'SOL/USDC') : 'SOL/USDC'
|
||||
|
||||
const tradeResult = await jupiterDEXService.executeTrade({
|
||||
symbol,
|
||||
side,
|
||||
amount: leveragedAmount,
|
||||
tradingPair,
|
||||
quickSwap: false
|
||||
})
|
||||
|
||||
if (!tradeResult.success) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: `Main trade failed: ${tradeResult.error}`
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
console.log('✅ Main leveraged trade executed:', tradeResult.txId)
|
||||
|
||||
// Step 2: Calculate position size for trigger orders
|
||||
const currentPrice = symbol === 'SOL' ? 166.75 : 3041.66 // Get from price API in production
|
||||
const tokenAmount = side === 'BUY'
|
||||
? leveragedAmount / currentPrice // If buying SOL, calculate SOL amount
|
||||
: leveragedAmount // If selling, amount is already in the token
|
||||
|
||||
// Step 3: Create trigger orders for stop loss and take profit
|
||||
let triggerResults = null
|
||||
if (stopLoss || takeProfit) {
|
||||
console.log('📋 Creating trigger orders for TP/SL...')
|
||||
|
||||
triggerResults = await jupiterTriggerService.createTradingOrders({
|
||||
tokenSymbol: symbol,
|
||||
amount: tokenAmount,
|
||||
stopLoss: stopLoss,
|
||||
takeProfit: takeProfit,
|
||||
slippageBps: 50, // 0.5% slippage for trigger orders
|
||||
expiredAt: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60) // 30 days expiry
|
||||
})
|
||||
|
||||
if (triggerResults.success) {
|
||||
console.log('✅ Trigger orders created:', {
|
||||
stopLoss: triggerResults.stopLossOrder,
|
||||
takeProfit: triggerResults.takeProfitOrder
|
||||
})
|
||||
} else {
|
||||
console.warn('⚠️ Trigger orders failed:', triggerResults.error)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Return comprehensive result
|
||||
const result = {
|
||||
success: true,
|
||||
trade: {
|
||||
txId: tradeResult.txId,
|
||||
orderId: tradeResult.orderId,
|
||||
symbol: symbol.toUpperCase(),
|
||||
side: side.toUpperCase(),
|
||||
amount: leveragedAmount,
|
||||
leverage: leverage,
|
||||
originalAmount: amount,
|
||||
tokenAmount: tokenAmount,
|
||||
entryPrice: currentPrice,
|
||||
timestamp: Date.now(),
|
||||
status: 'FILLED',
|
||||
platform: 'Jupiter DEX (Leveraged Spot)',
|
||||
dex: 'JUPITER_DEX_REAL',
|
||||
stopLoss: stopLoss,
|
||||
takeProfit: takeProfit
|
||||
},
|
||||
triggerOrders: triggerResults ? {
|
||||
stopLossOrderId: triggerResults.stopLossOrder,
|
||||
takeProfitOrderId: triggerResults.takeProfitOrder,
|
||||
status: triggerResults.success ? 'CREATED' : 'FAILED',
|
||||
error: triggerResults.error
|
||||
} : null,
|
||||
message: `${side.toUpperCase()} $${leveragedAmount} worth of ${symbol} executed successfully`,
|
||||
explanation: [
|
||||
`🔥 Leveraged Spot Trade: Used ${leverage}x leverage to trade $${leveragedAmount} instead of $${amount}`,
|
||||
`💰 Main Trade: ${side === 'BUY' ? 'Bought' : 'Sold'} ~${tokenAmount.toFixed(6)} ${symbol} via Jupiter DEX`,
|
||||
stopLoss ? `🛑 Stop Loss: Trigger order created at $${stopLoss}` : null,
|
||||
takeProfit ? `🎯 Take Profit: Trigger order created at $${takeProfit}` : null,
|
||||
`📈 This gives you ${leverage}x exposure to ${symbol} price movements using spot trading`
|
||||
].filter(Boolean)
|
||||
}
|
||||
|
||||
return NextResponse.json(result)
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Leveraged spot trade execution error:', error)
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to execute leveraged spot trade. Please try again.'
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
message: 'Jupiter Leveraged Spot Trading API',
|
||||
description: 'Leveraged trading using Jupiter DEX spot swaps + Trigger Orders for TP/SL',
|
||||
endpoints: {
|
||||
'POST /api/trading/execute-leverage': 'Execute leveraged spot trades with trigger orders',
|
||||
},
|
||||
features: [
|
||||
'Leveraged spot trading (1x-10x)',
|
||||
'Direct wallet trading (no deposits needed)',
|
||||
'Jupiter Trigger Orders for Stop Loss & Take Profit',
|
||||
'Real-time execution via Jupiter DEX',
|
||||
'Automatic position monitoring'
|
||||
],
|
||||
advantages: [
|
||||
'✅ No fund deposits required (unlike Drift)',
|
||||
'✅ Real leverage effect through increased position size',
|
||||
'✅ Professional stop loss & take profit via Jupiter Triggers',
|
||||
'✅ Best execution through Jupiter routing',
|
||||
'✅ Low fees (0.03% for stables, 0.1% others)'
|
||||
],
|
||||
note: 'Uses Jupiter DEX for main trades and Jupiter Trigger API for stop loss/take profit orders.'
|
||||
})
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export default function StandaloneTest() {
|
||||
setStatus('Testing CDN version...')
|
||||
|
||||
// Try using the CDN version instead
|
||||
if (typeof window !== 'undefined' && !window.LightweightCharts) {
|
||||
if (typeof window !== 'undefined' && !(window as any).LightweightCharts) {
|
||||
setStatus('Loading CDN script...')
|
||||
|
||||
const script = document.createElement('script')
|
||||
@@ -24,7 +24,7 @@ export default function StandaloneTest() {
|
||||
setStatus('CDN load failed')
|
||||
}
|
||||
document.head.appendChild(script)
|
||||
} else if (window.LightweightCharts) {
|
||||
} else if ((window as any).LightweightCharts) {
|
||||
setStatus('CDN already loaded, creating chart...')
|
||||
createChartWithCDN()
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ export default function ChartDebug() {
|
||||
const LightweightCharts = await import('lightweight-charts')
|
||||
addLog('Lightweight charts imported successfully')
|
||||
|
||||
const { createChart, CandlestickSeries } = LightweightCharts
|
||||
addLog('createChart and CandlestickSeries extracted')
|
||||
const { createChart } = LightweightCharts
|
||||
addLog('createChart extracted')
|
||||
|
||||
// Create chart with minimal options
|
||||
const chart = createChart(chartContainerRef.current!, {
|
||||
@@ -37,7 +37,7 @@ export default function ChartDebug() {
|
||||
setChartCreated(true)
|
||||
|
||||
// Add candlestick series with the correct v5 API
|
||||
const candlestickSeries = chart.addSeries(CandlestickSeries, {
|
||||
const candlestickSeries = chart.addCandlestickSeries({
|
||||
upColor: '#26a69a',
|
||||
downColor: '#ef5350',
|
||||
borderDownColor: '#ef5350',
|
||||
|
||||
@@ -31,8 +31,8 @@ export default function DebugChart() {
|
||||
|
||||
addLog('Available exports: ' + Object.keys(LightweightChartsModule).join(', '))
|
||||
|
||||
const { createChart, CandlestickSeries } = LightweightChartsModule
|
||||
addLog('Extracted createChart and CandlestickSeries')
|
||||
const { createChart, ColorType, CrosshairMode } = LightweightChartsModule
|
||||
addLog('Extracted createChart and other components')
|
||||
|
||||
addLog('Creating chart...')
|
||||
const chart = createChart(chartContainerRef.current!, {
|
||||
@@ -46,7 +46,7 @@ export default function DebugChart() {
|
||||
addLog('Chart created successfully')
|
||||
|
||||
addLog('Adding candlestick series...')
|
||||
const candlestickSeries = chart.addSeries(CandlestickSeries, {
|
||||
const candlestickSeries = chart.addCandlestickSeries({
|
||||
upColor: '#26a69a',
|
||||
downColor: '#ef5350',
|
||||
})
|
||||
|
||||
@@ -11,10 +11,10 @@ export default function DirectChart() {
|
||||
console.log('Starting direct chart...')
|
||||
|
||||
// Import with explicit .mjs extension
|
||||
const module = await import('lightweight-charts/dist/lightweight-charts.production.mjs')
|
||||
console.log('Module loaded:', module)
|
||||
const chartModule = await import('lightweight-charts')
|
||||
console.log('Module loaded:', chartModule)
|
||||
|
||||
const { createChart, CandlestickSeries } = module
|
||||
const { createChart } = chartModule
|
||||
console.log('Functions extracted')
|
||||
|
||||
const chart = createChart(container, {
|
||||
@@ -27,7 +27,7 @@ export default function DirectChart() {
|
||||
})
|
||||
console.log('Chart created')
|
||||
|
||||
const series = chart.addSeries(CandlestickSeries, {
|
||||
const series = chart.addCandlestickSeries({
|
||||
upColor: '#26a69a',
|
||||
downColor: '#ef5350',
|
||||
})
|
||||
|
||||
@@ -21,9 +21,8 @@ export default function MinimalChartTest() {
|
||||
console.log('Lightweight charts loaded:', LightweightCharts)
|
||||
setStatus('Charts library loaded')
|
||||
|
||||
const { createChart, CandlestickSeries } = LightweightCharts
|
||||
const { createChart } = LightweightCharts
|
||||
console.log('createChart:', typeof createChart)
|
||||
console.log('CandlestickSeries:', CandlestickSeries)
|
||||
|
||||
setStatus('Creating chart...')
|
||||
const chart = createChart(chartContainerRef.current!, {
|
||||
@@ -34,7 +33,7 @@ export default function MinimalChartTest() {
|
||||
setStatus('Chart created')
|
||||
|
||||
setStatus('Adding series...')
|
||||
const series = chart.addSeries(CandlestickSeries, {})
|
||||
const series = chart.addCandlestickSeries({})
|
||||
console.log('Series created:', series)
|
||||
setStatus('Series added')
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function SimpleChart() {
|
||||
console.log('Lightweight charts imported successfully')
|
||||
setStatus('Creating chart...')
|
||||
|
||||
const { createChart, ColorType, CandlestickSeries } = LightweightCharts
|
||||
const { createChart, ColorType } = LightweightCharts
|
||||
|
||||
const chart = createChart(chartContainerRef.current!, {
|
||||
layout: {
|
||||
@@ -35,7 +35,7 @@ export default function SimpleChart() {
|
||||
setStatus('Adding candlestick series...')
|
||||
console.log('Chart created, adding candlestick series...')
|
||||
|
||||
const candlestickSeries = chart.addSeries(CandlestickSeries, {
|
||||
const candlestickSeries = chart.addCandlestickSeries({
|
||||
upColor: '#26a69a',
|
||||
downColor: '#ef5350',
|
||||
borderDownColor: '#ef5350',
|
||||
|
||||
@@ -11,11 +11,11 @@ export default function SimpleTest() {
|
||||
setStatus('Importing library...')
|
||||
|
||||
// Test if we can import the library
|
||||
const module = await import('lightweight-charts')
|
||||
const chartModule = await import('lightweight-charts')
|
||||
setStatus('Library imported')
|
||||
|
||||
// Test if we can extract functions
|
||||
const { createChart, CandlestickSeries } = module
|
||||
const { createChart } = chartModule
|
||||
setStatus('Functions extracted')
|
||||
|
||||
if (!chartContainerRef.current) {
|
||||
@@ -38,7 +38,7 @@ export default function SimpleTest() {
|
||||
setStatus('Chart created')
|
||||
|
||||
// Add series
|
||||
const series = chart.addSeries(CandlestickSeries, {
|
||||
const series = chart.addCandlestickSeries({
|
||||
upColor: '#00ff00',
|
||||
downColor: '#ff0000',
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function WorkingChart() {
|
||||
|
||||
const initChart = async () => {
|
||||
try {
|
||||
const { createChart, CandlestickSeries } = await import('lightweight-charts')
|
||||
const { createChart } = await import('lightweight-charts')
|
||||
|
||||
const chart = createChart(chartContainerRef.current!, {
|
||||
width: 800,
|
||||
@@ -20,7 +20,7 @@ export default function WorkingChart() {
|
||||
},
|
||||
})
|
||||
|
||||
const candlestickSeries = chart.addSeries(CandlestickSeries, {
|
||||
const candlestickSeries = chart.addCandlestickSeries({
|
||||
upColor: '#26a69a',
|
||||
downColor: '#ef5350',
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client"
|
||||
import React, { useState } from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import TradeModal from './TradeModal'
|
||||
import ScreenshotGallery from './ScreenshotGallery'
|
||||
|
||||
@@ -38,6 +38,7 @@ interface ProgressStep {
|
||||
}
|
||||
|
||||
interface AnalysisProgress {
|
||||
sessionId: string
|
||||
currentStep: number
|
||||
totalSteps: number
|
||||
steps: ProgressStep[]
|
||||
@@ -60,6 +61,7 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
const [result, setResult] = useState<any>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [progress, setProgress] = useState<AnalysisProgress | null>(null)
|
||||
const [eventSource, setEventSource] = useState<EventSource | null>(null)
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [modalData, setModalData] = useState<any>(null)
|
||||
const [enlargedScreenshot, setEnlargedScreenshot] = useState<string | null>(null)
|
||||
@@ -77,6 +79,47 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// Real-time progress tracking
|
||||
const startProgressTracking = (sessionId: string) => {
|
||||
// Close existing connection
|
||||
if (eventSource) {
|
||||
eventSource.close()
|
||||
}
|
||||
|
||||
const es = new EventSource(`/api/progress/${sessionId}/stream`)
|
||||
|
||||
es.onmessage = (event) => {
|
||||
try {
|
||||
const progressData = JSON.parse(event.data)
|
||||
if (progressData.type === 'complete') {
|
||||
es.close()
|
||||
setEventSource(null)
|
||||
} else {
|
||||
setProgress(progressData)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing progress data:', error)
|
||||
}
|
||||
}
|
||||
|
||||
es.onerror = (error) => {
|
||||
console.error('EventSource error:', error)
|
||||
es.close()
|
||||
setEventSource(null)
|
||||
}
|
||||
|
||||
setEventSource(es)
|
||||
}
|
||||
|
||||
// Cleanup event source on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (eventSource) {
|
||||
eventSource.close()
|
||||
}
|
||||
}
|
||||
}, [eventSource])
|
||||
|
||||
const toggleLayout = (layout: string) => {
|
||||
setSelectedLayouts(prev =>
|
||||
prev.includes(layout)
|
||||
@@ -93,104 +136,11 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
)
|
||||
}
|
||||
|
||||
// Helper function to create initial progress steps
|
||||
const createProgressSteps = (timeframes: string[], layouts: string[]): ProgressStep[] => {
|
||||
const steps: ProgressStep[] = []
|
||||
// Helper function to create initial progress steps (no longer used - using real-time progress)
|
||||
// const createProgressSteps = ...removed for real-time implementation
|
||||
|
||||
if (timeframes.length > 1) {
|
||||
steps.push({
|
||||
id: 'init',
|
||||
title: 'Initializing Multi-Timeframe Analysis',
|
||||
description: `Preparing to analyze ${timeframes.length} timeframes`,
|
||||
status: 'pending'
|
||||
})
|
||||
} else {
|
||||
steps.push({
|
||||
id: 'init',
|
||||
title: 'Initializing Analysis',
|
||||
description: `Setting up screenshot service for ${layouts.join(', ')} layout(s)`,
|
||||
status: 'pending'
|
||||
})
|
||||
}
|
||||
|
||||
steps.push({
|
||||
id: 'browser',
|
||||
title: 'Starting Browser Sessions',
|
||||
description: `Launching ${layouts.length} browser session(s)`,
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
steps.push({
|
||||
id: 'auth',
|
||||
title: 'TradingView Authentication',
|
||||
description: 'Logging into TradingView accounts',
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
steps.push({
|
||||
id: 'navigation',
|
||||
title: 'Chart Navigation',
|
||||
description: 'Navigating to chart layouts and timeframes',
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
steps.push({
|
||||
id: 'loading',
|
||||
title: 'Chart Data Loading',
|
||||
description: 'Waiting for chart data and indicators to load',
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
steps.push({
|
||||
id: 'capture',
|
||||
title: 'Screenshot Capture',
|
||||
description: 'Capturing high-quality chart screenshots',
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
steps.push({
|
||||
id: 'analysis',
|
||||
title: 'AI Analysis',
|
||||
description: 'Analyzing screenshots with AI for trading insights',
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
return steps
|
||||
}
|
||||
|
||||
// Helper function to update progress
|
||||
const updateProgress = (stepId: string, status: ProgressStep['status'], details?: string) => {
|
||||
setProgress(prev => {
|
||||
if (!prev) return null
|
||||
|
||||
const updatedSteps = prev.steps.map(step => {
|
||||
if (step.id === stepId) {
|
||||
const updatedStep = {
|
||||
...step,
|
||||
status,
|
||||
details: details || step.details
|
||||
}
|
||||
|
||||
if (status === 'active' && !step.startTime) {
|
||||
updatedStep.startTime = Date.now()
|
||||
} else if ((status === 'completed' || status === 'error') && !step.endTime) {
|
||||
updatedStep.endTime = Date.now()
|
||||
}
|
||||
|
||||
return updatedStep
|
||||
}
|
||||
return step
|
||||
})
|
||||
|
||||
const currentStepIndex = updatedSteps.findIndex(step => step.status === 'active')
|
||||
|
||||
return {
|
||||
...prev,
|
||||
steps: updatedSteps,
|
||||
currentStep: currentStepIndex >= 0 ? currentStepIndex + 1 : prev.currentStep
|
||||
}
|
||||
})
|
||||
}
|
||||
// Helper function to update progress (no longer used - using real-time progress)
|
||||
// const updateProgress = ...removed for real-time implementation
|
||||
|
||||
const performAnalysis = async (analysisSymbol = symbol, analysisTimeframes = selectedTimeframes) => {
|
||||
if (loading || selectedLayouts.length === 0 || analysisTimeframes.length === 0) return
|
||||
@@ -199,12 +149,50 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
setError(null)
|
||||
setResult(null)
|
||||
|
||||
// Initialize progress tracking
|
||||
const steps = createProgressSteps(analysisTimeframes, selectedLayouts)
|
||||
// Set initial progress state to show animation immediately
|
||||
setProgress({
|
||||
currentStep: 0,
|
||||
totalSteps: steps.length,
|
||||
steps,
|
||||
sessionId: 'initializing',
|
||||
currentStep: 1,
|
||||
totalSteps: 6,
|
||||
steps: [
|
||||
{
|
||||
id: 'init',
|
||||
title: 'Initializing Analysis',
|
||||
description: 'Starting AI-powered trading analysis...',
|
||||
status: 'active',
|
||||
startTime: Date.now()
|
||||
},
|
||||
{
|
||||
id: 'auth',
|
||||
title: 'TradingView Authentication',
|
||||
description: 'Logging into TradingView accounts',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'navigation',
|
||||
title: 'Chart Navigation',
|
||||
description: 'Navigating to chart layouts',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'loading',
|
||||
title: 'Chart Data Loading',
|
||||
description: 'Waiting for chart data and indicators',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'capture',
|
||||
title: 'Screenshot Capture',
|
||||
description: 'Capturing high-quality screenshots',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'analysis',
|
||||
title: 'AI Analysis',
|
||||
description: 'Analyzing screenshots with AI',
|
||||
status: 'pending'
|
||||
}
|
||||
],
|
||||
timeframeProgress: analysisTimeframes.length > 1 ? {
|
||||
current: 0,
|
||||
total: analysisTimeframes.length
|
||||
@@ -212,14 +200,8 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
})
|
||||
|
||||
try {
|
||||
updateProgress('init', 'active')
|
||||
|
||||
if (analysisTimeframes.length === 1) {
|
||||
// Single timeframe analysis
|
||||
await new Promise(resolve => setTimeout(resolve, 500)) // Brief pause for UI
|
||||
updateProgress('init', 'completed')
|
||||
updateProgress('browser', 'active', 'Starting browser session...')
|
||||
|
||||
// Single timeframe analysis with real-time progress
|
||||
const response = await fetch('/api/enhanced-screenshot', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -231,35 +213,16 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
})
|
||||
})
|
||||
|
||||
// Since we can't track internal API progress in real-time, we'll simulate logical progression
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
updateProgress('browser', 'completed')
|
||||
updateProgress('auth', 'active', 'Authenticating with TradingView...')
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
updateProgress('auth', 'completed')
|
||||
updateProgress('navigation', 'active', `Navigating to ${analysisSymbol} chart...`)
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
updateProgress('navigation', 'completed')
|
||||
updateProgress('loading', 'active', 'Loading chart data and indicators...')
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||
updateProgress('loading', 'completed')
|
||||
updateProgress('capture', 'active', 'Capturing screenshots...')
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
updateProgress('capture', 'error', data.error || 'Screenshot capture failed')
|
||||
throw new Error(data.error || 'Analysis failed')
|
||||
}
|
||||
|
||||
updateProgress('capture', 'completed', `Captured ${data.screenshots?.length || 0} screenshot(s)`)
|
||||
updateProgress('analysis', 'active', 'Running AI analysis...')
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
updateProgress('analysis', 'completed', 'Analysis complete!')
|
||||
// Start real-time progress tracking if sessionId is provided
|
||||
if (data.sessionId) {
|
||||
startProgressTracking(data.sessionId)
|
||||
}
|
||||
|
||||
setResult(data)
|
||||
|
||||
@@ -269,31 +232,14 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
}
|
||||
} else {
|
||||
// Multiple timeframe analysis
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
updateProgress('init', 'completed', `Starting analysis for ${analysisTimeframes.length} timeframes`)
|
||||
|
||||
const results = []
|
||||
|
||||
for (let i = 0; i < analysisTimeframes.length; i++) {
|
||||
const tf = analysisTimeframes[i]
|
||||
const timeframeLabel = timeframes.find(t => t.value === tf)?.label || tf
|
||||
|
||||
// Update timeframe progress
|
||||
setProgress(prev => prev ? {
|
||||
...prev,
|
||||
timeframeProgress: {
|
||||
...prev.timeframeProgress!,
|
||||
current: i + 1,
|
||||
currentTimeframe: timeframeLabel
|
||||
}
|
||||
} : null)
|
||||
|
||||
console.log(`🧪 Analyzing timeframe: ${timeframeLabel}`)
|
||||
|
||||
if (i === 0) {
|
||||
updateProgress('browser', 'active', `Processing ${timeframeLabel} - Starting browser...`)
|
||||
}
|
||||
|
||||
const response = await fetch('/api/enhanced-screenshot', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -305,21 +251,6 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
})
|
||||
})
|
||||
|
||||
if (i === 0) {
|
||||
updateProgress('browser', 'completed')
|
||||
updateProgress('auth', 'active', `Processing ${timeframeLabel} - Authenticating...`)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
updateProgress('auth', 'completed')
|
||||
}
|
||||
|
||||
updateProgress('navigation', 'active', `Processing ${timeframeLabel} - Navigating to chart...`)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
updateProgress('loading', 'active', `Processing ${timeframeLabel} - Loading chart data...`)
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
|
||||
updateProgress('capture', 'active', `Processing ${timeframeLabel} - Capturing screenshots...`)
|
||||
|
||||
const result = await response.json()
|
||||
results.push({
|
||||
timeframe: tf,
|
||||
@@ -328,16 +259,24 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
result
|
||||
})
|
||||
|
||||
updateProgress('analysis', 'active', `Processing ${timeframeLabel} - Running AI analysis...`)
|
||||
|
||||
// Small delay between requests
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
// Start progress tracking for the first timeframe session
|
||||
if (i === 0 && result.sessionId) {
|
||||
startProgressTracking(result.sessionId)
|
||||
}
|
||||
|
||||
updateProgress('navigation', 'completed')
|
||||
updateProgress('loading', 'completed')
|
||||
updateProgress('capture', 'completed', `Captured screenshots for all ${analysisTimeframes.length} timeframes`)
|
||||
updateProgress('analysis', 'completed', `Completed analysis for all timeframes!`)
|
||||
// Update timeframe progress manually for multi-timeframe
|
||||
setProgress(prev => prev ? {
|
||||
...prev,
|
||||
timeframeProgress: {
|
||||
current: i + 1,
|
||||
total: analysisTimeframes.length,
|
||||
currentTimeframe: timeframeLabel
|
||||
}
|
||||
} : null)
|
||||
|
||||
// Small delay between requests
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
|
||||
const multiResult = {
|
||||
type: 'multi_timeframe',
|
||||
@@ -493,7 +432,7 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
const isLeveraged = leverage > 1
|
||||
|
||||
// Route to appropriate API based on leverage
|
||||
const apiEndpoint = isLeveraged ? '/api/trading/execute-perp' : '/api/trading/execute-dex'
|
||||
const apiEndpoint = isLeveraged ? '/api/trading/execute-drift' : '/api/trading/execute-dex'
|
||||
const tradingMode = isLeveraged ? 'PERP' : 'SPOT'
|
||||
|
||||
console.log(`🎯 Executing ${tradingMode} trade with ${leverage}x leverage via ${apiEndpoint}`)
|
||||
@@ -522,20 +461,27 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
// Show detailed success message based on trading type
|
||||
const leverage = parseFloat(tradeData.leverage) || 1
|
||||
const isLeveraged = leverage > 1
|
||||
const tradeType = isLeveraged ? 'Leveraged Position' : 'Spot Trade'
|
||||
const tradeType = isLeveraged ? 'Leveraged Perpetual Position' : 'Spot Trade'
|
||||
const platform = isLeveraged ? 'Drift Protocol' : 'Jupiter DEX'
|
||||
|
||||
let message = `✅ ${tradeType} executed successfully!\n\n`
|
||||
message += `📊 Transaction ID: ${result.trade?.txId || result.txId}\n`
|
||||
message += `💰 Symbol: ${tradeData.symbol || symbol}\n`
|
||||
message += `📈 Size: ${tradeData.positionSize || tradeData.size} USDC\n`
|
||||
if (isLeveraged) message += `⚡ Leverage: ${leverage}x\n`
|
||||
message += `🏪 Platform: ${platform}\n`
|
||||
if (isLeveraged) {
|
||||
message += `⚡ Leverage: ${leverage}x (via increased position size)\n`
|
||||
message += `<EFBFBD> Actual Trade Size: $${(parseFloat(tradeData.positionSize || tradeData.size) * leverage).toFixed(2)}\n`
|
||||
}
|
||||
message += `<EFBFBD>🏪 Platform: ${platform}\n`
|
||||
|
||||
if (tradeData.sl) message += `🛑 Stop Loss: $${tradeData.sl}\n`
|
||||
if (tradeData.tp1) message += `🎯 Take Profit: $${tradeData.tp1}\n`
|
||||
if (tradeData.sl) message += `🛑 Stop Loss: $${tradeData.sl} (Jupiter Trigger Order)\n`
|
||||
if (tradeData.tp1) message += `🎯 Take Profit: $${tradeData.tp1} (Jupiter Trigger Order)\n`
|
||||
|
||||
if (result.trade?.monitoring || result.position) {
|
||||
if (result.triggerOrders?.status === 'CREATED') {
|
||||
message += `\n🔄 Trigger Orders: ACTIVE\n`
|
||||
if (result.triggerOrders.stopLossOrderId) message += `🛑 SL Order: ${result.triggerOrders.stopLossOrderId.substring(0, 8)}...\n`
|
||||
if (result.triggerOrders.takeProfitOrderId) message += `🎯 TP Order: ${result.triggerOrders.takeProfitOrderId.substring(0, 8)}...\n`
|
||||
} else if (result.trade?.monitoring || result.position) {
|
||||
message += `\n🔄 Position monitoring: ACTIVE`
|
||||
}
|
||||
|
||||
@@ -550,6 +496,8 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
alert(`❌ Trade Failed: Insufficient Balance\n\nPlease ensure you have enough tokens in your wallet.\n\nError: ${errorMsg}`)
|
||||
} else if (errorMsg.includes('Real Jupiter Perpetuals trading not yet implemented')) {
|
||||
alert(`❌ Real Trading Not Available\n\nReal Jupiter Perpetuals trading is still in development. This trade will be simulated instead.\n\nTo use real spot trading, reduce the leverage to 1x.`)
|
||||
} else if (errorMsg.includes('Trigger API error') || errorMsg.includes('trigger orders failed')) {
|
||||
alert(`⚠️ Trade Executed, But Trigger Orders Failed\n\nYour main trade was successful, but stop loss/take profit orders could not be created.\n\nError: ${errorMsg}\n\nPlease monitor your position manually.`)
|
||||
} else {
|
||||
alert(`❌ Trade Failed\n\nError: ${errorMsg}`)
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@ interface Position {
|
||||
interface TradingChartProps {
|
||||
symbol?: string
|
||||
positions?: Position[]
|
||||
onPriceUpdate?: (price: number) => void
|
||||
}
|
||||
|
||||
export default function TradingChart({ symbol = 'SOL/USDC', positions = [] }: TradingChartProps) {
|
||||
export default function TradingChart({ symbol = 'SOL/USDC', positions = [], onPriceUpdate }: TradingChartProps) {
|
||||
const chartContainerRef = useRef<HTMLDivElement>(null)
|
||||
const chart = useRef<any>(null)
|
||||
const candlestickSeries = useRef<any>(null)
|
||||
@@ -33,7 +34,7 @@ export default function TradingChart({ symbol = 'SOL/USDC', positions = [] }: Tr
|
||||
try {
|
||||
// Dynamic import to avoid SSR issues
|
||||
const LightweightCharts = await import('lightweight-charts')
|
||||
const { createChart, ColorType, CrosshairMode, LineStyle, CandlestickSeries } = LightweightCharts
|
||||
const { createChart, ColorType, CrosshairMode, LineStyle } = LightweightCharts
|
||||
|
||||
chart.current = createChart(chartContainerRef.current!, {
|
||||
layout: {
|
||||
@@ -58,7 +59,7 @@ export default function TradingChart({ symbol = 'SOL/USDC', positions = [] }: Tr
|
||||
})
|
||||
|
||||
// Create candlestick series
|
||||
candlestickSeries.current = chart.current.addSeries(CandlestickSeries, {
|
||||
candlestickSeries.current = chart.current.addCandlestickSeries({
|
||||
upColor: '#26a69a',
|
||||
downColor: '#ef5350',
|
||||
borderDownColor: '#ef5350',
|
||||
@@ -77,6 +78,12 @@ export default function TradingChart({ symbol = 'SOL/USDC', positions = [] }: Tr
|
||||
candlestickSeries.current.setData(data)
|
||||
console.log('Chart data set successfully')
|
||||
|
||||
// Call onPriceUpdate with the latest price if provided
|
||||
if (onPriceUpdate && data.length > 0) {
|
||||
const latestPrice = data[data.length - 1].close
|
||||
onPriceUpdate(latestPrice)
|
||||
}
|
||||
|
||||
// Add position overlays
|
||||
console.log('Adding position overlays...')
|
||||
addPositionOverlays(LineStyle)
|
||||
|
||||
@@ -2,7 +2,8 @@ services:
|
||||
app:
|
||||
container_name: trader_dev
|
||||
build:
|
||||
target: development # Use development target for faster builds
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- BUILDKIT_INLINE_CACHE=1
|
||||
- NODE_VERSION=20.11.1
|
||||
@@ -50,7 +51,7 @@ services:
|
||||
|
||||
# Port mapping for development
|
||||
ports:
|
||||
- "9000:3000"
|
||||
- "9001:3000"
|
||||
|
||||
# X11 and display configuration for manual CAPTCHA solving
|
||||
privileged: true
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
import { enhancedScreenshotService, ScreenshotConfig } from './enhanced-screenshot'
|
||||
import { TradingViewCredentials } from './tradingview-automation'
|
||||
import { progressTracker } from './progress-tracker'
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
@@ -70,16 +71,61 @@ export interface AnalysisResult {
|
||||
}
|
||||
|
||||
export class AIAnalysisService {
|
||||
async analyzeScreenshot(filename: string): Promise<AnalysisResult | null> {
|
||||
async analyzeScreenshot(filenameOrPath: string): Promise<AnalysisResult | null> {
|
||||
try {
|
||||
let imagePath: string
|
||||
|
||||
// Check if it's already a full path or just a filename
|
||||
if (path.isAbsolute(filenameOrPath)) {
|
||||
// It's already a full path
|
||||
imagePath = filenameOrPath
|
||||
} else {
|
||||
// It's just a filename, construct the full path
|
||||
const screenshotsDir = path.join(process.cwd(), 'screenshots')
|
||||
const imagePath = path.join(screenshotsDir, filename)
|
||||
imagePath = path.join(screenshotsDir, filenameOrPath)
|
||||
}
|
||||
|
||||
// Read image file
|
||||
const imageBuffer = await fs.readFile(imagePath)
|
||||
const base64Image = imageBuffer.toString('base64')
|
||||
|
||||
const prompt = `You are now a professional trading assistant. You behave with the precision and decisiveness of a top proprietary desk trader. No vagueness, no fluff.
|
||||
|
||||
⚠️ CRITICAL RSI READING INSTRUCTION: The RSI indicator shows a numerical value AND a line position. IGNORE the number if it conflicts with the visual line position. If the RSI line appears in the top area of the indicator (above the 70 horizontal line), report it as OVERBOUGHT regardless of what number is displayed.
|
||||
|
||||
**CRITICAL: FIRST IDENTIFY THE LAYOUT TYPE**
|
||||
|
||||
Before analyzing any indicators, you MUST determine which layout you are looking at:
|
||||
|
||||
**AI Layout identification:**
|
||||
- Has RSI at the TOP of the chart
|
||||
- Has MACD at the BOTTOM of the chart
|
||||
- Has EMAs (9, 20, 50, 200) visible on the main chart
|
||||
- Does NOT have VWAP or OBV
|
||||
|
||||
**DIY Layout identification:**
|
||||
- Has Stochastic RSI at the TOP of the chart
|
||||
- Has OBV (On-Balance Volume) at the BOTTOM of the chart
|
||||
- Has VWAP (thick line) visible on the main chart
|
||||
- Does NOT have regular RSI or MACD
|
||||
|
||||
**LAYOUT-SPECIFIC INDICATOR INFORMATION:**
|
||||
|
||||
If this is an AI Layout screenshot, it contains:
|
||||
- TOP: RSI indicator (overbought above 70, oversold below 30)
|
||||
- MIDDLE (on chart): SVP, ATR Bands, EMA 9, EMA 20, EMA 50, EMA 200
|
||||
- BOTTOM: MACD indicator (NOT AT TOP - this is at the bottom of the chart)
|
||||
* MACD has two lines: MACD line (usually blue/faster) and Signal line (usually red/slower)
|
||||
* Bullish crossover = MACD line crosses ABOVE signal line (upward momentum)
|
||||
* Bearish crossover = MACD line crosses BELOW signal line (downward momentum)
|
||||
* Histogram bars: Green = bullish momentum, Red = bearish momentum
|
||||
* Zero line: Above = overall bullish trend, Below = overall bearish trend
|
||||
|
||||
If this is a DIY Module Layout screenshot, it contains:
|
||||
- TOP: Stochastic RSI indicator
|
||||
- MIDDLE (on chart): VWAP, Smart Money Concepts by Algo
|
||||
- BOTTOM: OBV (On-Balance Volume) indicator
|
||||
|
||||
**TRADING ANALYSIS REQUIREMENTS:**
|
||||
|
||||
1. **TIMEFRAME RISK ASSESSMENT**: Based on the timeframe shown in the screenshot, adjust risk accordingly:
|
||||
@@ -104,9 +150,23 @@ export class AIAnalysisService {
|
||||
|
||||
4. **CONFIRMATION TRIGGERS**: Exact signals to wait for:
|
||||
- Specific candle patterns, indicator crosses, volume confirmations
|
||||
- RSI behavior: "If RSI crosses above 70 while price is under resistance → wait"
|
||||
- RSI/Stoch RSI behavior:
|
||||
* MANDATORY: State if RSI is "OVERBOUGHT" (line above 70), "OVERSOLD" (line below 30), or "NEUTRAL" (between 30-70)
|
||||
* Do NOT say "above 50 line" - only report overbought/oversold/neutral status
|
||||
* If RSI line appears in upper area of indicator box, it's likely overbought regardless of number
|
||||
- VWAP: "If price retakes VWAP with bullish momentum → consider invalidation"
|
||||
- OBV: "If OBV starts climbing while price stays flat → early exit or reconsider bias"
|
||||
- MACD: Analyze MACD crossovers at the BOTTOM indicator panel.
|
||||
* Bullish crossover = MACD line (faster line) crosses ABOVE signal line (slower line) - indicates upward momentum
|
||||
* Bearish crossover = MACD line crosses BELOW signal line - indicates downward momentum
|
||||
* Histogram: Green bars = increasing bullish momentum, Red bars = increasing bearish momentum
|
||||
* Report specific crossover direction and current momentum state
|
||||
- EMA alignment: Check 9/20/50/200 EMA positioning and price relationship
|
||||
- Smart Money Concepts: Identify supply/demand zones and market structure
|
||||
|
||||
5. **LAYOUT-SPECIFIC ANALYSIS**:
|
||||
- AI Layout: Focus on RSI momentum (MUST identify overbought/oversold status), EMA alignment, MACD signals, and ATR bands for volatility
|
||||
- DIY Layout: Emphasize VWAP positioning, Stoch RSI oversold/overbought levels, OBV volume confirmation, and Smart Money Concepts structure
|
||||
|
||||
Examine the chart and identify:
|
||||
- Current price action and trend direction
|
||||
@@ -118,6 +178,7 @@ Examine the chart and identify:
|
||||
Provide your analysis in this exact JSON format (replace values with your analysis):
|
||||
|
||||
{
|
||||
"layoutDetected": "AI Layout|DIY Layout",
|
||||
"summary": "Objective technical analysis with timeframe risk assessment and specific trading setup",
|
||||
"marketSentiment": "BULLISH|BEARISH|NEUTRAL",
|
||||
"keyLevels": {
|
||||
@@ -153,10 +214,14 @@ Provide your analysis in this exact JSON format (replace values with your analys
|
||||
"riskToReward": "1:2",
|
||||
"confirmationTrigger": "Specific signal: Bearish engulfing candle on rejection from VWAP zone with RSI under 50",
|
||||
"indicatorAnalysis": {
|
||||
"rsi": "Specific RSI level and precise interpretation with action triggers",
|
||||
"vwap": "VWAP relationship to price with exact invalidation levels",
|
||||
"obv": "Volume analysis with specific behavioral expectations",
|
||||
"macd": "MACD signal line crosses and momentum analysis"
|
||||
"rsi": "ONLY if AI Layout detected: RSI status - MUST be 'OVERBOUGHT' (above 70 line), 'OVERSOLD' (below 30 line), or 'NEUTRAL' (30-70). Do NOT reference 50 line position.",
|
||||
"stochRsi": "ONLY if DIY Layout detected: Stochastic RSI oversold/overbought conditions - check both %K and %D lines",
|
||||
"vwap": "ONLY if DIY Layout detected: VWAP relationship to price with exact invalidation levels",
|
||||
"obv": "ONLY if DIY Layout detected: OBV volume analysis with specific behavioral expectations",
|
||||
"macd": "ONLY if AI Layout detected: MACD analysis - The MACD is located at the BOTTOM of the chart. Analyze: 1) Histogram bars (green = bullish momentum, red = bearish), 2) Signal line crossover (MACD line crossing ABOVE signal line = bullish crossover, BELOW = bearish crossover), 3) Zero line position. Report specific crossover direction and current momentum state.",
|
||||
"emaAlignment": "If AI Layout: EMA 9/20/50/200 positioning and price relationship - note stack order and price position",
|
||||
"atrBands": "If AI Layout: ATR bands for volatility and support/resistance",
|
||||
"smartMoney": "If DIY Layout: Smart Money Concepts supply/demand zones and structure"
|
||||
},
|
||||
"timeframeRisk": {
|
||||
"assessment": "Risk level based on detected timeframe",
|
||||
@@ -245,14 +310,23 @@ Return only the JSON object with your technical analysis.`
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeMultipleScreenshots(filenames: string[]): Promise<AnalysisResult | null> {
|
||||
async analyzeMultipleScreenshots(filenamesOrPaths: string[]): Promise<AnalysisResult | null> {
|
||||
try {
|
||||
const screenshotsDir = path.join(process.cwd(), 'screenshots')
|
||||
|
||||
// Read all image files and convert to base64
|
||||
const images = await Promise.all(
|
||||
filenames.map(async (filename) => {
|
||||
const imagePath = path.join(screenshotsDir, filename)
|
||||
filenamesOrPaths.map(async (filenameOrPath) => {
|
||||
let imagePath: string
|
||||
|
||||
// Check if it's already a full path or just a filename
|
||||
if (path.isAbsolute(filenameOrPath)) {
|
||||
// It's already a full path
|
||||
imagePath = filenameOrPath
|
||||
} else {
|
||||
// It's just a filename, construct the full path
|
||||
const screenshotsDir = path.join(process.cwd(), 'screenshots')
|
||||
imagePath = path.join(screenshotsDir, filenameOrPath)
|
||||
}
|
||||
|
||||
const imageBuffer = await fs.readFile(imagePath)
|
||||
const base64Image = imageBuffer.toString('base64')
|
||||
return {
|
||||
@@ -265,15 +339,40 @@ Return only the JSON object with your technical analysis.`
|
||||
})
|
||||
)
|
||||
|
||||
const layoutInfo = filenames.map(f => {
|
||||
if (f.includes('_ai_')) return 'AI Layout'
|
||||
const layoutInfo = filenamesOrPaths.map(f => {
|
||||
const filename = path.basename(f) // Extract filename from path
|
||||
if (filename.includes('_ai_')) return 'AI Layout'
|
||||
if (f.includes('_diy_') || f.includes('_Diy module_')) return 'DIY Module Layout'
|
||||
return 'Unknown Layout'
|
||||
}).join(' and ')
|
||||
|
||||
const prompt = `You are now a professional trading assistant. You behave with the precision and decisiveness of a top proprietary desk trader. No vagueness, no fluff.
|
||||
|
||||
I'm providing you with ${filenames.length} TradingView chart screenshots from different layouts: ${layoutInfo}.
|
||||
I'm providing you with ${filenamesOrPaths.length} TradingView chart screenshots from different layouts: ${layoutInfo}.
|
||||
|
||||
⚠️ CRITICAL RSI READING INSTRUCTION: The RSI indicator shows a numerical value AND a line position. IGNORE the number if it conflicts with the visual line position. If the RSI line appears in the top area of the indicator (above the 70 horizontal line), report it as OVERBOUGHT regardless of what number is displayed.
|
||||
|
||||
**LAYOUT-SPECIFIC INDICATOR INFORMATION:**
|
||||
|
||||
**AI Layout Structure:**
|
||||
- TOP: RSI indicator (14-period) - Look for EXACT numerical value displayed and visual position relative to 30/50/70 levels
|
||||
- MIDDLE (on chart): SVP, ATR Bands, EMA 9 (yellow), EMA 20 (orange), EMA 50 (blue), EMA 200 (red)
|
||||
- BOTTOM: MACD indicator with signal line and histogram
|
||||
|
||||
**DIY Module Layout Structure:**
|
||||
- TOP: Stochastic RSI indicator - Check both %K and %D lines relative to 20/50/80 levels
|
||||
- MIDDLE (on chart): VWAP (thick line), Smart Money Concepts by Algo (supply/demand zones)
|
||||
- BOTTOM: OBV (On-Balance Volume) indicator showing volume flow
|
||||
|
||||
**CRITICAL: ACCURATE INDICATOR READING:**
|
||||
- RSI: IGNORE the numerical value if it conflicts with visual position. The RSI line position on the chart is what matters:
|
||||
* If RSI line is visually ABOVE the 70 horizontal line = OVERBOUGHT (regardless of number shown)
|
||||
* If RSI line is visually BELOW the 30 horizontal line = OVERSOLD (regardless of number shown)
|
||||
* If RSI line is between 30-70 = NEUTRAL zone
|
||||
* Example: If number shows "56.61" but line appears above 70 level, report as "RSI OVERBOUGHT at 70+ level"
|
||||
- MACD: Check histogram bars (green/red) and signal line crossovers
|
||||
- EMA Alignment: Note price position relative to each EMA and EMA stack order
|
||||
- VWAP: Identify if price is above/below VWAP and by how much
|
||||
|
||||
**TRADING ANALYSIS REQUIREMENTS:**
|
||||
|
||||
@@ -299,12 +398,23 @@ I'm providing you with ${filenames.length} TradingView chart screenshots from di
|
||||
|
||||
4. **CONFIRMATION TRIGGERS**: Exact signals to wait for:
|
||||
- Specific candle patterns, indicator crosses, volume confirmations
|
||||
- RSI behavior: "If RSI crosses above 70 while price is under resistance → wait"
|
||||
- RSI/Stoch RSI behavior: "If RSI crosses above 70 while price is under resistance → wait"
|
||||
* CRITICAL: Read RSI visually - if the line appears above 70 level regardless of numerical display, treat as overbought
|
||||
* If RSI line appears below 30 level visually, treat as oversold regardless of number shown
|
||||
- VWAP: "If price retakes VWAP with bullish momentum → consider invalidation"
|
||||
- OBV: "If OBV starts climbing while price stays flat → early exit or reconsider bias"
|
||||
- MACD: Analyze MACD crossovers at the BOTTOM indicator panel.
|
||||
* Bullish crossover = MACD line (faster line) crosses ABOVE signal line (slower line) - indicates upward momentum
|
||||
* Bearish crossover = MACD line crosses BELOW signal line - indicates downward momentum
|
||||
* Histogram: Green bars = increasing bullish momentum, Red bars = increasing bearish momentum
|
||||
* Report specific crossover direction and current momentum state
|
||||
- EMA alignment: Check 9/20/50/200 EMA positioning and price relationship
|
||||
- Smart Money Concepts: Identify supply/demand zones and market structure
|
||||
|
||||
5. **CROSS-LAYOUT ANALYSIS**:
|
||||
- Compare insights from different chart layouts for confirmations/contradictions
|
||||
- AI Layout insights: RSI momentum + EMA alignment + MACD signals
|
||||
- DIY Layout insights: VWAP positioning + Stoch RSI + OBV volume + Smart Money structure
|
||||
- Use multiple perspectives to increase confidence
|
||||
- Note which layout provides clearest signals
|
||||
|
||||
@@ -348,10 +458,14 @@ I'm providing you with ${filenames.length} TradingView chart screenshots from di
|
||||
"riskToReward": "1:2.5",
|
||||
"confirmationTrigger": "Specific signal: Bearish engulfing candle on rejection from VWAP zone with RSI under 50",
|
||||
"indicatorAnalysis": {
|
||||
"rsi": "Specific RSI level and precise interpretation with action triggers",
|
||||
"vwap": "VWAP relationship to price with exact invalidation levels",
|
||||
"obv": "Volume analysis with specific behavioral expectations",
|
||||
"macd": "MACD signal line crosses and momentum analysis",
|
||||
"rsi": "AI Layout: RSI status - MUST be 'OVERBOUGHT' (above 70 line), 'OVERSOLD' (below 30 line), or 'NEUTRAL' (30-70). Do NOT reference 50 line position.",
|
||||
"stochRsi": "DIY Layout: Stochastic RSI oversold/overbought conditions",
|
||||
"vwap": "DIY Layout: VWAP relationship to price with exact invalidation levels",
|
||||
"obv": "DIY Layout: OBV volume analysis with specific behavioral expectations",
|
||||
"macd": "AI Layout: MACD signal line crosses and histogram momentum analysis - green/red bars and signal line position",
|
||||
"emaAlignment": "AI Layout: EMA 9/20/50/200 positioning and price relationship - note stack order and price position",
|
||||
"atrBands": "AI Layout: ATR bands for volatility and support/resistance",
|
||||
"smartMoney": "DIY Layout: Smart Money Concepts supply/demand zones and structure",
|
||||
"crossLayoutConsensus": "Detailed comparison of how different layouts confirm or contradict signals"
|
||||
},
|
||||
"layoutComparison": {
|
||||
@@ -384,7 +498,7 @@ Analyze all provided screenshots comprehensively and return only the JSON respon
|
||||
}
|
||||
]
|
||||
|
||||
console.log(`🤖 Sending ${filenames.length} screenshots to OpenAI for multi-layout analysis...`)
|
||||
console.log(`🤖 Sending ${filenamesOrPaths.length} screenshots to OpenAI for multi-layout analysis...`)
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-4o-mini", // Cost-effective model with vision capabilities
|
||||
@@ -491,10 +605,12 @@ Analyze all provided screenshots comprehensively and return only the JSON respon
|
||||
screenshots: string[]
|
||||
analysis: AnalysisResult | null
|
||||
}> {
|
||||
const { sessionId } = config
|
||||
|
||||
try {
|
||||
console.log(`Starting automated capture with config for ${config.symbol} ${config.timeframe}`)
|
||||
|
||||
// Capture screenshots using enhanced service
|
||||
// Capture screenshots using enhanced service (this will handle its own progress)
|
||||
const screenshots = await enhancedScreenshotService.captureWithLogin(config)
|
||||
|
||||
if (screenshots.length === 0) {
|
||||
@@ -503,6 +619,11 @@ Analyze all provided screenshots comprehensively and return only the JSON respon
|
||||
|
||||
console.log(`${screenshots.length} screenshot(s) captured`)
|
||||
|
||||
// Add AI analysis step to progress if sessionId exists
|
||||
if (sessionId) {
|
||||
progressTracker.updateStep(sessionId, 'analysis', 'active', 'Running AI analysis on screenshots...')
|
||||
}
|
||||
|
||||
let analysis: AnalysisResult | null = null
|
||||
|
||||
if (screenshots.length === 1) {
|
||||
@@ -514,11 +635,20 @@ Analyze all provided screenshots comprehensively and return only the JSON respon
|
||||
}
|
||||
|
||||
if (!analysis) {
|
||||
if (sessionId) {
|
||||
progressTracker.updateStep(sessionId, 'analysis', 'error', 'AI analysis failed to generate results')
|
||||
}
|
||||
throw new Error('Failed to analyze screenshots')
|
||||
}
|
||||
|
||||
console.log(`Analysis completed for ${config.symbol} ${config.timeframe}`)
|
||||
|
||||
if (sessionId) {
|
||||
progressTracker.updateStep(sessionId, 'analysis', 'completed', 'AI analysis completed successfully!')
|
||||
// Mark session as complete
|
||||
setTimeout(() => progressTracker.deleteSession(sessionId), 1000)
|
||||
}
|
||||
|
||||
return {
|
||||
screenshots,
|
||||
analysis
|
||||
@@ -526,6 +656,20 @@ Analyze all provided screenshots comprehensively and return only the JSON respon
|
||||
|
||||
} catch (error) {
|
||||
console.error('Automated capture and analysis with config failed:', error)
|
||||
|
||||
if (sessionId) {
|
||||
// Find the active step and mark it as error
|
||||
const progress = progressTracker.getProgress(sessionId)
|
||||
if (progress) {
|
||||
const activeStep = progress.steps.find(step => step.status === 'active')
|
||||
if (activeStep) {
|
||||
progressTracker.updateStep(sessionId, activeStep.id, 'error', error instanceof Error ? error.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
// Clean up session
|
||||
setTimeout(() => progressTracker.deleteSession(sessionId), 5000)
|
||||
}
|
||||
|
||||
return {
|
||||
screenshots: [],
|
||||
analysis: null
|
||||
|
||||
@@ -3,12 +3,14 @@ import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
import puppeteer from 'puppeteer'
|
||||
import { Browser, Page } from 'puppeteer'
|
||||
import { progressTracker, ProgressStep } from './progress-tracker'
|
||||
|
||||
export interface ScreenshotConfig {
|
||||
symbol: string
|
||||
timeframe: string
|
||||
layouts?: string[] // Multiple chart layouts if needed
|
||||
credentials?: TradingViewCredentials // Optional if using .env
|
||||
sessionId?: string // For progress tracking
|
||||
}
|
||||
|
||||
// Layout URL mappings for direct navigation
|
||||
@@ -28,6 +30,13 @@ export class EnhancedScreenshotService {
|
||||
console.log('📋 Config:', config)
|
||||
|
||||
const screenshotFiles: string[] = []
|
||||
const { sessionId } = config
|
||||
console.log('🔍 Enhanced Screenshot Service received sessionId:', sessionId)
|
||||
|
||||
// Progress tracking (session already created in API)
|
||||
if (sessionId) {
|
||||
progressTracker.updateStep(sessionId, 'init', 'active', 'Starting browser sessions...')
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure screenshots directory exists
|
||||
@@ -39,8 +48,13 @@ export class EnhancedScreenshotService {
|
||||
|
||||
console.log(`\n🔄 Starting parallel capture of ${layoutsToCapture.length} layouts...`)
|
||||
|
||||
if (sessionId) {
|
||||
progressTracker.updateStep(sessionId, 'init', 'completed', `Started ${layoutsToCapture.length} browser sessions`)
|
||||
progressTracker.updateStep(sessionId, 'auth', 'active', 'Authenticating with TradingView...')
|
||||
}
|
||||
|
||||
// Create parallel session promises for true dual-session approach
|
||||
const sessionPromises = layoutsToCapture.map(async (layout) => {
|
||||
const sessionPromises = layoutsToCapture.map(async (layout, index) => {
|
||||
const layoutKey = layout.toLowerCase()
|
||||
let layoutSession: TradingViewAutomation | null = null
|
||||
|
||||
@@ -71,6 +85,9 @@ export class EnhancedScreenshotService {
|
||||
const isLoggedIn = await layoutSession.isLoggedIn()
|
||||
if (!isLoggedIn) {
|
||||
console.log(`🔐 Logging in to ${layout} session...`)
|
||||
if (sessionId && index === 0) {
|
||||
progressTracker.updateStep(sessionId, 'auth', 'active', `Logging into ${layout} session...`)
|
||||
}
|
||||
const loginSuccess = await layoutSession.smartLogin(config.credentials)
|
||||
if (!loginSuccess) {
|
||||
throw new Error(`Failed to login to ${layout} session`)
|
||||
@@ -79,6 +96,12 @@ export class EnhancedScreenshotService {
|
||||
console.log(`✅ ${layout} session already logged in`)
|
||||
}
|
||||
|
||||
// Update auth progress when first session completes auth
|
||||
if (sessionId && index === 0) {
|
||||
progressTracker.updateStep(sessionId, 'auth', 'completed', 'TradingView authentication successful')
|
||||
progressTracker.updateStep(sessionId, 'navigation', 'active', `Navigating to ${config.symbol} chart...`)
|
||||
}
|
||||
|
||||
// Navigate directly to the specific layout URL with symbol and timeframe
|
||||
const directUrl = `https://www.tradingview.com/chart/${layoutUrl}/?symbol=${config.symbol}&interval=${config.timeframe}`
|
||||
console.log(`🌐 ${layout.toUpperCase()}: Navigating directly to ${directUrl}`)
|
||||
@@ -132,6 +155,12 @@ export class EnhancedScreenshotService {
|
||||
|
||||
console.log(`✅ ${layout.toUpperCase()}: Successfully navigated to layout`)
|
||||
|
||||
// Update navigation progress when first session completes navigation
|
||||
if (sessionId && index === 0) {
|
||||
progressTracker.updateStep(sessionId, 'navigation', 'completed', 'Chart navigation successful')
|
||||
progressTracker.updateStep(sessionId, 'loading', 'active', 'Loading chart data and indicators...')
|
||||
}
|
||||
|
||||
// Progressive loading strategy: shorter initial wait, then chart-specific wait
|
||||
console.log(`⏳ ${layout.toUpperCase()}: Initial page stabilization (2s)...`)
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
@@ -171,6 +200,12 @@ export class EnhancedScreenshotService {
|
||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||
}
|
||||
|
||||
// Update loading progress when first session completes loading
|
||||
if (sessionId && index === 0) {
|
||||
progressTracker.updateStep(sessionId, 'loading', 'completed', 'Chart data loaded successfully')
|
||||
progressTracker.updateStep(sessionId, 'capture', 'active', 'Capturing screenshots...')
|
||||
}
|
||||
|
||||
// Take screenshot with better error handling
|
||||
const filename = `${config.symbol}_${config.timeframe}_${layout}_${timestamp}.png`
|
||||
console.log(`📸 Taking ${layout} screenshot: ${filename}`)
|
||||
@@ -237,11 +272,27 @@ export class EnhancedScreenshotService {
|
||||
}
|
||||
})
|
||||
|
||||
if (sessionId) {
|
||||
progressTracker.updateStep(sessionId, 'capture', 'completed', `Captured ${screenshotFiles.length}/${layoutsToCapture.length} screenshots`)
|
||||
}
|
||||
|
||||
console.log(`\n🎯 Parallel capture completed: ${screenshotFiles.length}/${layoutsToCapture.length} screenshots`)
|
||||
return screenshotFiles
|
||||
|
||||
} catch (error) {
|
||||
console.error('Enhanced parallel screenshot capture failed:', error)
|
||||
|
||||
if (sessionId) {
|
||||
// Mark the current active step as error
|
||||
const progress = progressTracker.getProgress(sessionId)
|
||||
if (progress) {
|
||||
const activeStep = progress.steps.find(step => step.status === 'active')
|
||||
if (activeStep) {
|
||||
progressTracker.updateStep(sessionId, activeStep.id, 'error', error instanceof Error ? error.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
490
lib/jupiter-trigger-service.ts
Normal file
490
lib/jupiter-trigger-service.ts
Normal file
@@ -0,0 +1,490 @@
|
||||
import { Connection, Keypair, VersionedTransaction } from '@solana/web3.js'
|
||||
import fetch from 'cross-fetch'
|
||||
|
||||
export interface TriggerOrder {
|
||||
orderId: string
|
||||
inputMint: string
|
||||
outputMint: string
|
||||
makingAmount: string
|
||||
takingAmount: string
|
||||
targetPrice: number
|
||||
side: 'BUY' | 'SELL'
|
||||
orderType: 'STOP_LOSS' | 'TAKE_PROFIT' | 'LIMIT'
|
||||
status: 'PENDING' | 'EXECUTED' | 'CANCELLED' | 'EXPIRED'
|
||||
createdAt: number
|
||||
executedAt?: number
|
||||
txId?: string
|
||||
requestId?: string
|
||||
}
|
||||
|
||||
class JupiterTriggerService {
|
||||
private connection: Connection
|
||||
private keypair: Keypair | null = null
|
||||
private activeOrders: TriggerOrder[] = []
|
||||
|
||||
// Token mint addresses
|
||||
private tokens = {
|
||||
SOL: 'So11111111111111111111111111111111111111112', // Wrapped SOL
|
||||
USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||
USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const rpcUrl = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
|
||||
this.connection = new Connection(rpcUrl, 'confirmed')
|
||||
this.initializeWallet()
|
||||
}
|
||||
|
||||
private initializeWallet() {
|
||||
try {
|
||||
if (process.env.SOLANA_PRIVATE_KEY) {
|
||||
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY)
|
||||
this.keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray))
|
||||
console.log('✅ Jupiter Trigger wallet initialized:', this.keypair.publicKey.toString())
|
||||
} else {
|
||||
console.warn('⚠️ No SOLANA_PRIVATE_KEY found for Jupiter Trigger')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to initialize Jupiter Trigger wallet:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stop loss order
|
||||
* When current price drops to stopPrice, sell the token
|
||||
*/
|
||||
async createStopLossOrder(params: {
|
||||
tokenSymbol: string
|
||||
amount: number // Amount of tokens to sell
|
||||
stopPrice: number // Price at which to trigger the sale
|
||||
slippageBps?: number // Optional slippage (default 0 for exact execution)
|
||||
expiredAt?: number // Optional expiry timestamp
|
||||
}): Promise<{
|
||||
success: boolean
|
||||
orderId?: string
|
||||
requestId?: string
|
||||
transaction?: string
|
||||
error?: string
|
||||
}> {
|
||||
if (!this.keypair) {
|
||||
return { success: false, error: 'Wallet not initialized' }
|
||||
}
|
||||
|
||||
try {
|
||||
const { tokenSymbol, amount, stopPrice, slippageBps = 0, expiredAt } = params
|
||||
|
||||
console.log('🛑 Creating stop loss order:', params)
|
||||
|
||||
// Determine mint addresses
|
||||
const inputMint = tokenSymbol === 'SOL' ? this.tokens.SOL : this.tokens.USDC
|
||||
const outputMint = tokenSymbol === 'SOL' ? this.tokens.USDC : this.tokens.SOL
|
||||
|
||||
// Calculate amounts
|
||||
const makingAmount = tokenSymbol === 'SOL'
|
||||
? Math.floor(amount * 1_000_000_000).toString() // SOL has 9 decimals
|
||||
: Math.floor(amount * 1_000_000).toString() // USDC has 6 decimals
|
||||
|
||||
const takingAmount = tokenSymbol === 'SOL'
|
||||
? Math.floor(amount * stopPrice * 1_000_000).toString() // Convert to USDC
|
||||
: Math.floor(amount / stopPrice * 1_000_000_000).toString() // Convert to SOL
|
||||
|
||||
const orderParams: any = {
|
||||
inputMint,
|
||||
outputMint,
|
||||
maker: this.keypair.publicKey.toString(),
|
||||
payer: this.keypair.publicKey.toString(),
|
||||
params: {
|
||||
makingAmount,
|
||||
takingAmount,
|
||||
},
|
||||
computeUnitPrice: "auto"
|
||||
}
|
||||
|
||||
// Add optional parameters
|
||||
if (slippageBps > 0) {
|
||||
orderParams.params.slippageBps = slippageBps.toString()
|
||||
}
|
||||
if (expiredAt) {
|
||||
orderParams.params.expiredAt = expiredAt.toString()
|
||||
}
|
||||
|
||||
// Create the trigger order
|
||||
const response = await fetch('https://api.jup.ag/trigger/v1/createOrder', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(orderParams)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(`Trigger API error: ${error.error || response.status}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Store the order locally
|
||||
const order: TriggerOrder = {
|
||||
orderId: result.order,
|
||||
inputMint,
|
||||
outputMint,
|
||||
makingAmount,
|
||||
takingAmount,
|
||||
targetPrice: stopPrice,
|
||||
side: 'SELL',
|
||||
orderType: 'STOP_LOSS',
|
||||
status: 'PENDING',
|
||||
createdAt: Date.now(),
|
||||
requestId: result.requestId
|
||||
}
|
||||
|
||||
this.activeOrders.push(order)
|
||||
|
||||
console.log('✅ Stop loss order created:', result.order)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
orderId: result.order,
|
||||
requestId: result.requestId,
|
||||
transaction: result.transaction
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Failed to create stop loss order:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a take profit order
|
||||
* When current price rises to targetPrice, sell the token
|
||||
*/
|
||||
async createTakeProfitOrder(params: {
|
||||
tokenSymbol: string
|
||||
amount: number // Amount of tokens to sell
|
||||
targetPrice: number // Price at which to trigger the sale
|
||||
slippageBps?: number // Optional slippage (default 0 for exact execution)
|
||||
expiredAt?: number // Optional expiry timestamp
|
||||
}): Promise<{
|
||||
success: boolean
|
||||
orderId?: string
|
||||
requestId?: string
|
||||
transaction?: string
|
||||
error?: string
|
||||
}> {
|
||||
if (!this.keypair) {
|
||||
return { success: false, error: 'Wallet not initialized' }
|
||||
}
|
||||
|
||||
try {
|
||||
const { tokenSymbol, amount, targetPrice, slippageBps = 0, expiredAt } = params
|
||||
|
||||
console.log('🎯 Creating take profit order:', params)
|
||||
|
||||
// Determine mint addresses
|
||||
const inputMint = tokenSymbol === 'SOL' ? this.tokens.SOL : this.tokens.USDC
|
||||
const outputMint = tokenSymbol === 'SOL' ? this.tokens.USDC : this.tokens.SOL
|
||||
|
||||
// Calculate amounts
|
||||
const makingAmount = tokenSymbol === 'SOL'
|
||||
? Math.floor(amount * 1_000_000_000).toString() // SOL has 9 decimals
|
||||
: Math.floor(amount * 1_000_000).toString() // USDC has 6 decimals
|
||||
|
||||
const takingAmount = tokenSymbol === 'SOL'
|
||||
? Math.floor(amount * targetPrice * 1_000_000).toString() // Convert to USDC
|
||||
: Math.floor(amount / targetPrice * 1_000_000_000).toString() // Convert to SOL
|
||||
|
||||
const orderParams: any = {
|
||||
inputMint,
|
||||
outputMint,
|
||||
maker: this.keypair.publicKey.toString(),
|
||||
payer: this.keypair.publicKey.toString(),
|
||||
params: {
|
||||
makingAmount,
|
||||
takingAmount,
|
||||
},
|
||||
computeUnitPrice: "auto"
|
||||
}
|
||||
|
||||
// Add optional parameters
|
||||
if (slippageBps > 0) {
|
||||
orderParams.params.slippageBps = slippageBps.toString()
|
||||
}
|
||||
if (expiredAt) {
|
||||
orderParams.params.expiredAt = expiredAt.toString()
|
||||
}
|
||||
|
||||
// Create the trigger order
|
||||
const response = await fetch('https://api.jup.ag/trigger/v1/createOrder', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(orderParams)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(`Trigger API error: ${error.error || response.status}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Store the order locally
|
||||
const order: TriggerOrder = {
|
||||
orderId: result.order,
|
||||
inputMint,
|
||||
outputMint,
|
||||
makingAmount,
|
||||
takingAmount,
|
||||
targetPrice: targetPrice,
|
||||
side: 'SELL',
|
||||
orderType: 'TAKE_PROFIT',
|
||||
status: 'PENDING',
|
||||
createdAt: Date.now(),
|
||||
requestId: result.requestId
|
||||
}
|
||||
|
||||
this.activeOrders.push(order)
|
||||
|
||||
console.log('✅ Take profit order created:', result.order)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
orderId: result.order,
|
||||
requestId: result.requestId,
|
||||
transaction: result.transaction
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Failed to create take profit order:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute (sign and send) a trigger order transaction
|
||||
*/
|
||||
async executeOrder(transaction: string, requestId: string): Promise<{
|
||||
success: boolean
|
||||
txId?: string
|
||||
error?: string
|
||||
}> {
|
||||
if (!this.keypair) {
|
||||
return { success: false, error: 'Wallet not initialized' }
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('⚡ Executing trigger order transaction')
|
||||
|
||||
// Deserialize and sign transaction
|
||||
const transactionBuf = Buffer.from(transaction, 'base64')
|
||||
const versionedTransaction = VersionedTransaction.deserialize(transactionBuf)
|
||||
versionedTransaction.sign([this.keypair])
|
||||
|
||||
// Send via Jupiter's execute endpoint
|
||||
const response = await fetch('https://api.jup.ag/trigger/v1/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
requestId,
|
||||
transaction: Buffer.from(versionedTransaction.serialize()).toString('base64')
|
||||
})
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(`Execute API error: ${error.error || response.status}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
console.log('✅ Trigger order executed:', result.txId)
|
||||
|
||||
// Update local order status
|
||||
const order = this.activeOrders.find(o => o.requestId === requestId)
|
||||
if (order) {
|
||||
order.status = 'PENDING'
|
||||
order.txId = result.txId
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
txId: result.txId
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Failed to execute trigger order:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a trigger order
|
||||
*/
|
||||
async cancelOrder(orderId: string): Promise<{
|
||||
success: boolean
|
||||
txId?: string
|
||||
error?: string
|
||||
}> {
|
||||
if (!this.keypair) {
|
||||
return { success: false, error: 'Wallet not initialized' }
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('❌ Cancelling trigger order:', orderId)
|
||||
|
||||
const response = await fetch('https://api.jup.ag/trigger/v1/cancelOrder', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order: orderId,
|
||||
owner: this.keypair.publicKey.toString(),
|
||||
computeUnitPrice: "auto"
|
||||
})
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(`Cancel API error: ${error.error || response.status}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Sign and send the cancel transaction
|
||||
const transactionBuf = Buffer.from(result.transaction, 'base64')
|
||||
const versionedTransaction = VersionedTransaction.deserialize(transactionBuf)
|
||||
versionedTransaction.sign([this.keypair])
|
||||
|
||||
const txId = await this.connection.sendTransaction(versionedTransaction)
|
||||
const confirmation = await this.connection.confirmTransaction(txId, 'confirmed')
|
||||
|
||||
if (confirmation.value.err) {
|
||||
throw new Error(`Cancel transaction failed: ${confirmation.value.err}`)
|
||||
}
|
||||
|
||||
// Update local order status
|
||||
const order = this.activeOrders.find(o => o.orderId === orderId)
|
||||
if (order) {
|
||||
order.status = 'CANCELLED'
|
||||
}
|
||||
|
||||
console.log('✅ Trigger order cancelled:', txId)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
txId
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Failed to cancel trigger order:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active trigger orders for the wallet
|
||||
*/
|
||||
async getTriggerOrders(): Promise<TriggerOrder[]> {
|
||||
if (!this.keypair) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://api.jup.ag/trigger/v1/getTriggerOrders?wallet=${this.keypair.publicKey.toString()}&active=true`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Get orders API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
console.log('📊 Retrieved trigger orders:', result.orders?.length || 0)
|
||||
|
||||
return result.orders || []
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Failed to get trigger orders:', error)
|
||||
return this.activeOrders // Fallback to local orders
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create both stop loss and take profit orders for a trade
|
||||
*/
|
||||
async createTradingOrders(params: {
|
||||
tokenSymbol: string
|
||||
amount: number
|
||||
stopLoss?: number
|
||||
takeProfit?: number
|
||||
slippageBps?: number
|
||||
expiredAt?: number
|
||||
}): Promise<{
|
||||
success: boolean
|
||||
stopLossOrder?: string
|
||||
takeProfitOrder?: string
|
||||
transactions?: string[]
|
||||
error?: string
|
||||
}> {
|
||||
const { tokenSymbol, amount, stopLoss, takeProfit, slippageBps, expiredAt } = params
|
||||
const results: any = { success: true, transactions: [] }
|
||||
|
||||
try {
|
||||
// Create stop loss order
|
||||
if (stopLoss) {
|
||||
const slResult = await this.createStopLossOrder({
|
||||
tokenSymbol,
|
||||
amount,
|
||||
stopPrice: stopLoss,
|
||||
slippageBps,
|
||||
expiredAt
|
||||
})
|
||||
|
||||
if (slResult.success) {
|
||||
results.stopLossOrder = slResult.orderId
|
||||
results.transactions.push(slResult.transaction)
|
||||
} else {
|
||||
console.warn('⚠️ Failed to create stop loss order:', slResult.error)
|
||||
}
|
||||
}
|
||||
|
||||
// Create take profit order
|
||||
if (takeProfit) {
|
||||
const tpResult = await this.createTakeProfitOrder({
|
||||
tokenSymbol,
|
||||
amount,
|
||||
targetPrice: takeProfit,
|
||||
slippageBps,
|
||||
expiredAt
|
||||
})
|
||||
|
||||
if (tpResult.success) {
|
||||
results.takeProfitOrder = tpResult.orderId
|
||||
results.transactions.push(tpResult.transaction)
|
||||
} else {
|
||||
console.warn('⚠️ Failed to create take profit order:', tpResult.error)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Failed to create trading orders:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
getLocalOrders(): TriggerOrder[] {
|
||||
return this.activeOrders
|
||||
}
|
||||
|
||||
isConfigured(): boolean {
|
||||
return this.keypair !== null
|
||||
}
|
||||
}
|
||||
|
||||
export const jupiterTriggerService = new JupiterTriggerService()
|
||||
export default JupiterTriggerService
|
||||
115
lib/progress-tracker.ts
Normal file
115
lib/progress-tracker.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
export type ProgressStatus = 'pending' | 'active' | 'completed' | 'error'
|
||||
|
||||
export interface ProgressStep {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
status: ProgressStatus
|
||||
startTime?: number
|
||||
endTime?: number
|
||||
details?: string
|
||||
}
|
||||
|
||||
export interface AnalysisProgress {
|
||||
sessionId: string
|
||||
currentStep: number
|
||||
totalSteps: number
|
||||
steps: ProgressStep[]
|
||||
timeframeProgress?: {
|
||||
current: number
|
||||
total: number
|
||||
currentTimeframe?: string
|
||||
}
|
||||
}
|
||||
|
||||
class ProgressTracker extends EventEmitter {
|
||||
private sessions: Map<string, AnalysisProgress> = new Map()
|
||||
|
||||
createSession(sessionId: string, steps: ProgressStep[]): AnalysisProgress {
|
||||
const progress: AnalysisProgress = {
|
||||
sessionId,
|
||||
currentStep: 0,
|
||||
totalSteps: steps.length,
|
||||
steps: steps.map(step => ({ ...step, status: 'pending' }))
|
||||
}
|
||||
|
||||
this.sessions.set(sessionId, progress)
|
||||
this.emit(`progress:${sessionId}`, progress)
|
||||
return progress
|
||||
}
|
||||
|
||||
updateStep(sessionId: string, stepId: string, status: ProgressStatus, details?: string): void {
|
||||
console.log(`🔍 Progress Update: ${sessionId} -> ${stepId} -> ${status}${details ? ` (${details})` : ''}`)
|
||||
const progress = this.sessions.get(sessionId)
|
||||
if (!progress) {
|
||||
console.log(`🔍 Warning: No session found for ${sessionId}`)
|
||||
return
|
||||
}
|
||||
|
||||
const updatedSteps = progress.steps.map(step => {
|
||||
if (step.id === stepId) {
|
||||
const updatedStep = {
|
||||
...step,
|
||||
status,
|
||||
details: details || step.details
|
||||
}
|
||||
|
||||
if (status === 'active' && !step.startTime) {
|
||||
updatedStep.startTime = Date.now()
|
||||
} else if ((status === 'completed' || status === 'error') && !step.endTime) {
|
||||
updatedStep.endTime = Date.now()
|
||||
}
|
||||
|
||||
return updatedStep
|
||||
}
|
||||
return step
|
||||
})
|
||||
|
||||
const currentStepIndex = updatedSteps.findIndex(step => step.status === 'active')
|
||||
|
||||
const updatedProgress: AnalysisProgress = {
|
||||
...progress,
|
||||
steps: updatedSteps,
|
||||
currentStep: currentStepIndex >= 0 ? currentStepIndex + 1 : progress.currentStep
|
||||
}
|
||||
|
||||
this.sessions.set(sessionId, updatedProgress)
|
||||
console.log(`🔍 Emitting progress event for ${sessionId}, currentStep: ${updatedProgress.currentStep}`)
|
||||
this.emit(`progress:${sessionId}`, updatedProgress)
|
||||
}
|
||||
|
||||
updateTimeframeProgress(sessionId: string, current: number, total: number, currentTimeframe?: string): void {
|
||||
const progress = this.sessions.get(sessionId)
|
||||
if (!progress) return
|
||||
|
||||
const updatedProgress: AnalysisProgress = {
|
||||
...progress,
|
||||
timeframeProgress: {
|
||||
current,
|
||||
total,
|
||||
currentTimeframe
|
||||
}
|
||||
}
|
||||
|
||||
this.sessions.set(sessionId, updatedProgress)
|
||||
this.emit(`progress:${sessionId}`, updatedProgress)
|
||||
}
|
||||
|
||||
getProgress(sessionId: string): AnalysisProgress | undefined {
|
||||
return this.sessions.get(sessionId)
|
||||
}
|
||||
|
||||
deleteSession(sessionId: string): void {
|
||||
this.sessions.delete(sessionId)
|
||||
this.emit(`progress:${sessionId}:complete`)
|
||||
}
|
||||
|
||||
// Get all active sessions (for debugging)
|
||||
getActiveSessions(): string[] {
|
||||
return Array.from(this.sessions.keys())
|
||||
}
|
||||
}
|
||||
|
||||
export const progressTracker = new ProgressTracker()
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@solana/web3.js": "^1.98.2",
|
||||
"bs58": "^6.0.0",
|
||||
"dotenv": "^17.2.0",
|
||||
"lightweight-charts": "^4.1.3",
|
||||
"next": "15.3.5",
|
||||
"node-fetch": "^3.3.2",
|
||||
"openai": "^5.8.3",
|
||||
@@ -5744,6 +5745,12 @@
|
||||
"node": "> 0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/fancy-canvas": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-2.1.0.tgz",
|
||||
"integrity": "sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -7397,6 +7404,15 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightweight-charts": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-4.2.3.tgz",
|
||||
"integrity": "sha512-5kS/2hY3wNYNzhnS8Gb+GAS07DX8GPF2YVDnd2NMC85gJVQ6RLU6YrXNgNJ6eg0AnWPwCnvaGtYmGky3HiLQEw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fancy-canvas": "2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"@solana/web3.js": "^1.98.2",
|
||||
"bs58": "^6.0.0",
|
||||
"dotenv": "^17.2.0",
|
||||
"lightweight-charts": "^4.1.3",
|
||||
"next": "15.3.5",
|
||||
"node-fetch": "^3.3.2",
|
||||
"openai": "^5.8.3",
|
||||
|
||||
109
test-drift-funds.mjs
Normal file
109
test-drift-funds.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Connection, PublicKey } from '@solana/web3.js';
|
||||
|
||||
async function testDriftAccount() {
|
||||
try {
|
||||
console.log('🔍 Testing Drift account access...');
|
||||
|
||||
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
|
||||
const accountPDA = '7LonnWut5i3h36xyMA5jbwnGFbnzXUPY2dsPfNaSsrTk';
|
||||
|
||||
console.log('📡 Connecting to Solana...');
|
||||
const accountInfo = await connection.getAccountInfo(new PublicKey(accountPDA));
|
||||
|
||||
if (!accountInfo) {
|
||||
console.log('❌ Drift account not found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Drift account found!');
|
||||
console.log(`📊 Account data size: ${accountInfo.data.length} bytes`);
|
||||
console.log(`💰 Account lamports: ${accountInfo.lamports}`);
|
||||
console.log(`👤 Owner: ${accountInfo.owner.toBase58()}`);
|
||||
|
||||
// Try to parse balance data from multiple known offsets
|
||||
const data = accountInfo.data;
|
||||
|
||||
console.log(`\n🔍 Scanning for USDC balance...`);
|
||||
|
||||
// Try multiple offsets where USDC balance might be stored
|
||||
const offsets = [106, 114, 122, 130, 138, 146, 154];
|
||||
|
||||
for (const offset of offsets) {
|
||||
try {
|
||||
const balance = data.readBigInt64LE(offset);
|
||||
const value = Number(balance) / 1_000_000; // USDC has 6 decimals
|
||||
if (value > 0 && value < 1000000) { // Reasonable range
|
||||
console.log(` Offset ${offset}: $${value.toFixed(6)}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid offsets
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n🔍 Scanning for SOL position...`);
|
||||
|
||||
// Try multiple offsets for SOL position
|
||||
const solOffsets = [432, 440, 448, 456, 464, 472];
|
||||
|
||||
for (const offset of solOffsets) {
|
||||
try {
|
||||
const position = data.readBigInt64LE(offset);
|
||||
const amount = Number(position) / 1_000_000_000; // SOL has 9 decimals
|
||||
if (Math.abs(amount) > 0.001 && Math.abs(amount) < 1000) { // Reasonable range
|
||||
console.log(` Offset ${offset}: ${amount.toFixed(6)} SOL`);
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid offsets
|
||||
}
|
||||
}
|
||||
|
||||
// Find the best USDC balance (likely the $23 you mentioned)
|
||||
let bestUsdcValue = 0;
|
||||
let bestSolAmount = 0;
|
||||
|
||||
for (const offset of offsets) {
|
||||
try {
|
||||
const balance = data.readBigInt64LE(offset);
|
||||
const value = Number(balance) / 1_000_000; // USDC has 6 decimals
|
||||
if (value > bestUsdcValue && value < 1000000) { // Take the highest reasonable value
|
||||
bestUsdcValue = value;
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid offsets
|
||||
}
|
||||
}
|
||||
|
||||
for (const offset of solOffsets) {
|
||||
try {
|
||||
const position = data.readBigInt64LE(offset);
|
||||
const amount = Number(position) / 1_000_000_000; // SOL has 9 decimals
|
||||
if (Math.abs(amount) > Math.abs(bestSolAmount) && Math.abs(amount) < 1000) {
|
||||
bestSolAmount = amount;
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid offsets
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n💵 Best parsed balances:`);
|
||||
console.log(` USDC: $${bestUsdcValue.toFixed(6)}`);
|
||||
console.log(` SOL: ${bestSolAmount.toFixed(6)} SOL`);
|
||||
|
||||
if (bestUsdcValue > 20) {
|
||||
console.log(`\n🎉 Found your $${bestUsdcValue.toFixed(2)} USDC!`);
|
||||
}
|
||||
|
||||
if (bestUsdcValue > 1 || Math.abs(bestSolAmount) > 0.01) {
|
||||
console.log('\n✅ Sufficient funds for leveraged trading!');
|
||||
console.log('🎯 Ready to implement real Drift perpetual trading');
|
||||
console.log(`📊 With $${bestUsdcValue.toFixed(2)} you can open positions up to $${(bestUsdcValue * 10).toFixed(2)} with 10x leverage`);
|
||||
} else {
|
||||
console.log('\n⚠️ Limited funds detected');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Test failed:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
testDriftAccount();
|
||||
Reference in New Issue
Block a user