Files
trading_bot_v3/lib/automation-service-simple.ts
mindesbunister 118e0269f1 Fix automation startup issue and add AI learning documentation
- Fixed foreign key constraint violation in automation service
- Added user upsert to ensure user exists before creating automation session
- Enhanced error reporting in automation start API
- Added comprehensive AI learning system documentation
- Automation now starts successfully in simulation mode
2025-07-18 20:18:38 +02:00

539 lines
16 KiB
TypeScript

import { PrismaClient } from '@prisma/client'
import { aiAnalysisService, AnalysisResult } from './ai-analysis'
import { jupiterDEXService } from './jupiter-dex-service'
import { enhancedScreenshotService } from './enhanced-screenshot-simple'
const prisma = new PrismaClient()
export interface AutomationConfig {
userId: string
mode: 'SIMULATION' | 'LIVE'
symbol: string
timeframe: string
tradingAmount: number
maxLeverage: number
stopLossPercent: number
takeProfitPercent: number
maxDailyTrades: number
riskPercentage: number
}
export interface AutomationStatus {
isActive: boolean
mode: 'SIMULATION' | 'LIVE'
symbol: string
timeframe: string
totalTrades: number
successfulTrades: number
winRate: number
totalPnL: number
lastAnalysis?: Date
lastTrade?: Date
nextScheduled?: Date
errorCount: number
lastError?: string
}
export class AutomationService {
private isRunning = false
private config: AutomationConfig | null = null
private intervalId: NodeJS.Timeout | null = null
private stats = {
totalTrades: 0,
successfulTrades: 0,
winRate: 0,
totalPnL: 0,
errorCount: 0,
lastError: null as string | null
}
async startAutomation(config: AutomationConfig): Promise<boolean> {
try {
if (this.isRunning) {
throw new Error('Automation is already running')
}
this.config = config
this.isRunning = true
console.log(`🤖 Starting automation for ${config.symbol} ${config.timeframe} in ${config.mode} mode`)
// Ensure user exists in database
await prisma.user.upsert({
where: { id: config.userId },
update: {},
create: {
id: config.userId,
email: `${config.userId}@example.com`,
name: config.userId,
createdAt: new Date(),
updatedAt: new Date()
}
})
// Create automation session in database
await prisma.automationSession.create({
data: {
userId: config.userId,
status: 'ACTIVE',
mode: config.mode,
symbol: config.symbol,
timeframe: config.timeframe,
settings: {
tradingAmount: config.tradingAmount,
maxLeverage: config.maxLeverage,
stopLossPercent: config.stopLossPercent,
takeProfitPercent: config.takeProfitPercent,
maxDailyTrades: config.maxDailyTrades,
riskPercentage: config.riskPercentage
},
startBalance: config.tradingAmount,
currentBalance: config.tradingAmount,
createdAt: new Date(),
updatedAt: new Date()
}
})
// Start automation cycle
this.startAutomationCycle()
return true
} catch (error) {
console.error('Failed to start automation:', error)
this.stats.errorCount++
this.stats.lastError = error instanceof Error ? error.message : 'Unknown error'
return false
}
}
private startAutomationCycle(): void {
if (!this.config) return
// Get interval in milliseconds based on timeframe
const intervalMs = this.getIntervalFromTimeframe(this.config.timeframe)
console.log(`🔄 Starting automation cycle every ${intervalMs/1000} seconds`)
this.intervalId = setInterval(async () => {
if (this.isRunning && this.config) {
await this.runAutomationCycle()
}
}, intervalMs)
// Run first cycle immediately
this.runAutomationCycle()
}
private getIntervalFromTimeframe(timeframe: string): number {
const intervals: { [key: string]: number } = {
'1m': 60 * 1000,
'3m': 3 * 60 * 1000,
'5m': 5 * 60 * 1000,
'15m': 15 * 60 * 1000,
'30m': 30 * 60 * 1000,
'1h': 60 * 60 * 1000,
'2h': 2 * 60 * 60 * 1000,
'4h': 4 * 60 * 60 * 1000,
'1d': 24 * 60 * 60 * 1000
}
return intervals[timeframe] || intervals['1h'] // Default to 1 hour
}
private async runAutomationCycle(): Promise<void> {
if (!this.config) return
try {
console.log(`🔍 Running automation cycle for ${this.config.symbol} ${this.config.timeframe}`)
// Step 1: Check daily trade limit
const todayTrades = await this.getTodayTradeCount(this.config.userId)
if (todayTrades >= this.config.maxDailyTrades) {
console.log(`📊 Daily trade limit reached (${todayTrades}/${this.config.maxDailyTrades})`)
return
}
// Step 2: Take screenshot and analyze
const analysisResult = await this.performAnalysis()
if (!analysisResult) {
console.log('❌ Analysis failed, skipping cycle')
return
}
// Step 3: Store analysis for learning
await this.storeAnalysisForLearning(analysisResult)
// Step 4: Make trading decision
const tradeDecision = await this.makeTradeDecision(analysisResult)
if (!tradeDecision) {
console.log('📊 No trading opportunity found')
return
}
// Step 5: Execute trade
await this.executeTrade(tradeDecision)
} catch (error) {
console.error('Error in automation cycle:', error)
this.stats.errorCount++
this.stats.lastError = error instanceof Error ? error.message : 'Unknown error'
}
}
private async performAnalysis(): Promise<{
screenshots: string[]
analysis: AnalysisResult | null
} | null> {
try {
console.log('📸 Taking screenshot and analyzing...')
const screenshotConfig = {
symbol: this.config!.symbol,
timeframe: this.config!.timeframe,
layouts: ['ai', 'diy']
}
const result = await aiAnalysisService.captureAndAnalyzeWithConfig(screenshotConfig)
if (!result.analysis || result.screenshots.length === 0) {
console.log('❌ No analysis or screenshots captured')
return null
}
console.log(`✅ Analysis completed: ${result.analysis.recommendation} with ${result.analysis.confidence}% confidence`)
return result
} catch (error) {
console.error('Error performing analysis:', error)
return null
}
}
private async storeAnalysisForLearning(result: {
screenshots: string[]
analysis: AnalysisResult | null
}): Promise<void> {
try {
if (!result.analysis) return
await prisma.aILearningData.create({
data: {
userId: this.config!.userId,
symbol: this.config!.symbol,
timeframe: this.config!.timeframe,
screenshot: result.screenshots[0] || '',
analysisData: JSON.stringify(result.analysis),
marketConditions: JSON.stringify({
marketSentiment: result.analysis.marketSentiment,
keyLevels: result.analysis.keyLevels,
timestamp: new Date().toISOString()
}),
confidenceScore: result.analysis.confidence,
createdAt: new Date()
}
})
} catch (error) {
console.error('Error storing analysis for learning:', error)
}
}
private async makeTradeDecision(result: {
screenshots: string[]
analysis: AnalysisResult | null
}): Promise<any | null> {
try {
const analysis = result.analysis
if (!analysis) return null
// Only trade if confidence is high enough
if (analysis.confidence < 70) {
console.log(`📊 Confidence too low: ${analysis.confidence}%`)
return null
}
// Only trade if direction is clear
if (analysis.recommendation === 'HOLD') {
console.log('📊 No clear direction signal')
return null
}
// Calculate position size based on risk percentage
const positionSize = this.calculatePositionSize(analysis)
return {
direction: analysis.recommendation,
confidence: analysis.confidence,
positionSize,
stopLoss: this.calculateStopLoss(analysis),
takeProfit: this.calculateTakeProfit(analysis),
marketSentiment: analysis.marketSentiment
}
} catch (error) {
console.error('Error making trade decision:', error)
return null
}
}
private calculatePositionSize(analysis: any): number {
const baseAmount = this.config!.tradingAmount
const riskAdjustment = this.config!.riskPercentage / 100
const confidenceAdjustment = analysis.confidence / 100
return baseAmount * riskAdjustment * confidenceAdjustment
}
private calculateStopLoss(analysis: any): number {
const currentPrice = analysis.currentPrice || 0
const stopLossPercent = this.config!.stopLossPercent / 100
if (analysis.direction === 'LONG') {
return currentPrice * (1 - stopLossPercent)
} else {
return currentPrice * (1 + stopLossPercent)
}
}
private calculateTakeProfit(analysis: any): number {
const currentPrice = analysis.currentPrice || 0
const takeProfitPercent = this.config!.takeProfitPercent / 100
if (analysis.direction === 'LONG') {
return currentPrice * (1 + takeProfitPercent)
} else {
return currentPrice * (1 - takeProfitPercent)
}
}
private async executeTrade(decision: any): Promise<void> {
try {
console.log(`🎯 Executing ${this.config!.mode} trade: ${decision.direction} ${decision.positionSize} ${this.config!.symbol}`)
let tradeResult: any
if (this.config!.mode === 'SIMULATION') {
// Execute simulation trade
tradeResult = await this.executeSimulationTrade(decision)
} else {
// Execute live trade via Jupiter
tradeResult = await this.executeLiveTrade(decision)
}
// Store trade in database
await this.storeTrade(decision, tradeResult)
// Update stats
this.updateStats(tradeResult)
console.log(`✅ Trade executed successfully: ${tradeResult.transactionId || 'SIMULATION'}`)
} catch (error) {
console.error('Error executing trade:', error)
this.stats.errorCount++
this.stats.lastError = error instanceof Error ? error.message : 'Trade execution failed'
}
}
private async executeSimulationTrade(decision: any): Promise<any> {
// Simulate trade execution with realistic parameters
const currentPrice = decision.currentPrice || 100 // Mock price
const slippage = Math.random() * 0.005 // 0-0.5% slippage
const executionPrice = currentPrice * (1 + (Math.random() > 0.5 ? slippage : -slippage))
return {
transactionId: `SIM_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
executionPrice,
amount: decision.positionSize,
direction: decision.direction,
status: 'COMPLETED',
timestamp: new Date(),
fees: decision.positionSize * 0.001, // 0.1% fee
slippage: slippage * 100
}
}
private async executeLiveTrade(decision: any): Promise<any> {
// Execute real trade via Jupiter DEX
const inputToken = decision.direction === 'BUY' ? 'USDC' : 'SOL'
const outputToken = decision.direction === 'BUY' ? 'SOL' : 'USDC'
const tokens = {
SOL: 'So11111111111111111111111111111111111111112',
USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
}
return await jupiterDEXService.executeSwap(
tokens[inputToken as keyof typeof tokens],
tokens[outputToken as keyof typeof tokens],
decision.positionSize,
50 // 0.5% slippage
)
}
private async storeTrade(decision: any, result: any): Promise<void> {
try {
await prisma.trade.create({
data: {
userId: this.config!.userId,
symbol: this.config!.symbol,
side: decision.direction,
amount: decision.positionSize,
price: result.executionPrice,
status: result.status,
driftTxId: result.transactionId || result.txId,
fees: result.fees || 0,
stopLoss: decision.stopLoss,
takeProfit: decision.takeProfit,
isAutomated: true,
tradingMode: this.config!.mode,
confidence: decision.confidence,
marketSentiment: decision.marketSentiment,
createdAt: new Date()
}
})
} catch (error) {
console.error('Error storing trade:', error)
}
}
private updateStats(result: any): void {
this.stats.totalTrades++
if (result.status === 'COMPLETED') {
this.stats.successfulTrades++
this.stats.winRate = (this.stats.successfulTrades / this.stats.totalTrades) * 100
// Update PnL (simplified calculation)
const pnl = result.amount * 0.01 * (Math.random() > 0.5 ? 1 : -1) // Random PnL for demo
this.stats.totalPnL += pnl
}
}
private async getTodayTradeCount(userId: string): Promise<number> {
const today = new Date()
today.setHours(0, 0, 0, 0)
const count = await prisma.trade.count({
where: {
userId,
isAutomated: true,
createdAt: {
gte: today
}
}
})
return count
}
async stopAutomation(): Promise<boolean> {
try {
this.isRunning = false
this.config = null
console.log('🛑 Automation stopped')
return true
} catch (error) {
console.error('Failed to stop automation:', error)
return false
}
}
async pauseAutomation(): Promise<boolean> {
try {
if (!this.isRunning) {
return false
}
this.isRunning = false
console.log('⏸️ Automation paused')
return true
} catch (error) {
console.error('Failed to pause automation:', error)
return false
}
}
async resumeAutomation(): Promise<boolean> {
try {
if (!this.config) {
return false
}
this.isRunning = true
console.log('▶️ Automation resumed')
return true
} catch (error) {
console.error('Failed to resume automation:', error)
return false
}
}
async getStatus(): Promise<AutomationStatus | null> {
try {
if (!this.config) {
return null
}
return {
isActive: this.isRunning,
mode: this.config.mode,
symbol: this.config.symbol,
timeframe: this.config.timeframe,
totalTrades: this.stats.totalTrades,
successfulTrades: this.stats.successfulTrades,
winRate: this.stats.winRate,
totalPnL: this.stats.totalPnL,
errorCount: this.stats.errorCount,
lastError: this.stats.lastError || undefined,
lastAnalysis: new Date(),
lastTrade: new Date()
}
} catch (error) {
console.error('Failed to get automation status:', error)
return null
}
}
async getLearningInsights(userId: string): Promise<{
totalAnalyses: number
avgAccuracy: number
bestTimeframe: string
worstTimeframe: string
commonFailures: string[]
recommendations: string[]
}> {
try {
// For now, return mock data
return {
totalAnalyses: 150,
avgAccuracy: 0.72,
bestTimeframe: '1h',
worstTimeframe: '15m',
commonFailures: [
'Low confidence predictions',
'Missed support/resistance levels',
'Timeframe misalignment'
],
recommendations: [
'Focus on 1h timeframe for better accuracy',
'Wait for higher confidence signals (>75%)',
'Use multiple timeframe confirmation'
]
}
} catch (error) {
console.error('Failed to get learning insights:', error)
return {
totalAnalyses: 0,
avgAccuracy: 0,
bestTimeframe: 'Unknown',
worstTimeframe: 'Unknown',
commonFailures: [],
recommendations: []
}
}
}
}
export const automationService = new AutomationService()