feat: implement AI-driven DCA (Dollar Cost Averaging) system
AI-powered DCA manager with sophisticated reversal detection Multi-factor analysis: price movements, RSI, support/resistance, 24h trends Real example: SOL position analysis shows 5.2:1 risk/reward improvement lib/ai-dca-manager.ts - Complete DCA analysis engine with risk management Intelligent scaling: adds to positions when AI detects 50%+ reversal confidence Account-aware: uses up to 50% available balance with conservative 3x leverage Dynamic SL/TP: adjusts stop loss and take profit for new average position lib/automation-service-simple.ts - DCA monitoring in main trading cycle prisma/schema.prisma - DCARecord model for comprehensive tracking Checks DCA opportunities before new trade analysis (priority system) test-ai-dca-simple.js - Real SOL position test from screenshot data Entry: 85.98, Current: 83.87 (-1.13% underwater) AI recommendation: 1.08 SOL DCA → 4.91 profit potential Risk level: LOW with 407% liquidation safety margin LOGIC Price movement analysis: 1-10% against position optimal for DCA Market sentiment: 24h trends must align with DCA direction Technical indicators: RSI oversold (<35) for longs, overbought (>65) for shorts Support/resistance: proximity to key levels increases confidence Risk management: respects leverage limits and liquidation distances Complete error handling and fallback mechanisms Database persistence for DCA tracking and performance analysis Seamless integration with existing AI leverage calculator Real-time market data integration for accurate decision making
This commit is contained in:
340
test-ai-dca-simple.js
Normal file
340
test-ai-dca-simple.js
Normal file
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* Simple DCA Test - Direct Implementation
|
||||
* Tests DCA logic without TypeScript compilation issues
|
||||
*/
|
||||
|
||||
// Simplified DCA analysis logic (extracted from TypeScript version)
|
||||
function analyzeDCAOpportunity(params) {
|
||||
const { currentPosition, accountStatus, marketData, maxLeverageAllowed } = params
|
||||
|
||||
console.log('🔄 AI DCA Analysis:', {
|
||||
position: `${currentPosition.side} ${currentPosition.size} @ $${currentPosition.entryPrice}`,
|
||||
currentPrice: `$${marketData.price}`,
|
||||
pnl: `$${currentPosition.unrealizedPnl.toFixed(2)}`,
|
||||
availableBalance: `$${accountStatus.availableBalance.toFixed(2)}`
|
||||
})
|
||||
|
||||
// Step 1: Analyze reversal potential
|
||||
const reversalAnalysis = analyzeReversalPotential(currentPosition, marketData)
|
||||
|
||||
if (!reversalAnalysis.hasReversalPotential) {
|
||||
return {
|
||||
shouldDCA: false,
|
||||
dcaAmount: 0,
|
||||
newAveragePrice: currentPosition.entryPrice,
|
||||
newStopLoss: currentPosition.stopLoss,
|
||||
newTakeProfit: currentPosition.takeProfit,
|
||||
newLeverage: accountStatus.leverage,
|
||||
newLiquidationPrice: accountStatus.liquidationPrice,
|
||||
riskAssessment: 'HIGH',
|
||||
reasoning: reversalAnalysis.reasoning,
|
||||
confidence: 0
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Calculate safe DCA amount
|
||||
const dcaCalculation = calculateSafeDCAAmount(currentPosition, accountStatus, marketData, maxLeverageAllowed)
|
||||
|
||||
if (dcaCalculation.dcaAmount === 0) {
|
||||
return {
|
||||
shouldDCA: false,
|
||||
dcaAmount: 0,
|
||||
newAveragePrice: currentPosition.entryPrice,
|
||||
newStopLoss: currentPosition.stopLoss,
|
||||
newTakeProfit: currentPosition.takeProfit,
|
||||
newLeverage: accountStatus.leverage,
|
||||
newLiquidationPrice: accountStatus.liquidationPrice,
|
||||
riskAssessment: 'HIGH',
|
||||
reasoning: dcaCalculation.reasoning,
|
||||
confidence: 0
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Calculate new position parameters
|
||||
const newPositionSize = currentPosition.size + dcaCalculation.dcaAmount
|
||||
const newAveragePrice = calculateNewAveragePrice(
|
||||
currentPosition.size,
|
||||
currentPosition.entryPrice,
|
||||
dcaCalculation.dcaAmount,
|
||||
marketData.price
|
||||
)
|
||||
|
||||
// Step 4: Calculate new stop loss and take profit
|
||||
const newSLTP = calculateNewStopLossAndTakeProfit(
|
||||
currentPosition.side,
|
||||
newAveragePrice,
|
||||
newPositionSize,
|
||||
marketData,
|
||||
reversalAnalysis.confidence
|
||||
)
|
||||
|
||||
// Step 5: Calculate new leverage and liquidation price
|
||||
const newLeverage = calculateNewLeverage(newPositionSize, newAveragePrice, accountStatus.accountValue, dcaCalculation.dcaAmount)
|
||||
const newLiquidationPrice = calculateNewLiquidationPrice(newAveragePrice, newLeverage, currentPosition.side)
|
||||
|
||||
// Step 6: Final risk assessment
|
||||
const riskAssessment = assessDCARisk(newLeverage, newLiquidationPrice, newSLTP.stopLoss, accountStatus.availableBalance, dcaCalculation.dcaAmount)
|
||||
|
||||
return {
|
||||
shouldDCA: true,
|
||||
dcaAmount: dcaCalculation.dcaAmount,
|
||||
newAveragePrice,
|
||||
newStopLoss: newSLTP.stopLoss,
|
||||
newTakeProfit: newSLTP.takeProfit,
|
||||
newLeverage,
|
||||
newLiquidationPrice,
|
||||
riskAssessment,
|
||||
reasoning: `${reversalAnalysis.reasoning}. ${dcaCalculation.reasoning}. New average: $${newAveragePrice.toFixed(4)} with ${newLeverage.toFixed(1)}x leverage.`,
|
||||
confidence: reversalAnalysis.confidence
|
||||
}
|
||||
}
|
||||
|
||||
function analyzeReversalPotential(position, marketData) {
|
||||
const priceMovement = ((marketData.price - position.entryPrice) / position.entryPrice) * 100
|
||||
const isMovingAgainstPosition = (position.side === 'long' && priceMovement < 0) ||
|
||||
(position.side === 'short' && priceMovement > 0)
|
||||
|
||||
if (!isMovingAgainstPosition) {
|
||||
return {
|
||||
hasReversalPotential: false,
|
||||
reasoning: "Price moving in our favor - no DCA needed",
|
||||
confidence: 0
|
||||
}
|
||||
}
|
||||
|
||||
let confidence = 0
|
||||
const reasons = []
|
||||
|
||||
// Factor 1: Price drop magnitude
|
||||
const movementMagnitude = Math.abs(priceMovement)
|
||||
if (movementMagnitude >= 1 && movementMagnitude <= 5) {
|
||||
confidence += 30
|
||||
reasons.push(`${movementMagnitude.toFixed(1)}% movement creates DCA opportunity`)
|
||||
} else if (movementMagnitude > 5 && movementMagnitude <= 10) {
|
||||
confidence += 40
|
||||
reasons.push(`${movementMagnitude.toFixed(1)}% movement shows strong discount`)
|
||||
} else if (movementMagnitude > 10) {
|
||||
confidence += 20
|
||||
reasons.push(`${movementMagnitude.toFixed(1)}% movement may indicate trend change`)
|
||||
}
|
||||
|
||||
// Factor 2: 24h price change context
|
||||
if (marketData.priceChange24h !== undefined) {
|
||||
if (position.side === 'long' && marketData.priceChange24h < -3) {
|
||||
confidence += 25
|
||||
reasons.push("24h downtrend creates long DCA opportunity")
|
||||
} else if (position.side === 'short' && marketData.priceChange24h > 3) {
|
||||
confidence += 25
|
||||
reasons.push("24h uptrend creates short DCA opportunity")
|
||||
}
|
||||
}
|
||||
|
||||
// Factor 3: Support/Resistance levels
|
||||
if (marketData.support && position.side === 'long' && marketData.price <= marketData.support * 1.02) {
|
||||
confidence += 20
|
||||
reasons.push("Price near support level")
|
||||
}
|
||||
if (marketData.resistance && position.side === 'short' && marketData.price >= marketData.resistance * 0.98) {
|
||||
confidence += 20
|
||||
reasons.push("Price near resistance level")
|
||||
}
|
||||
|
||||
// Factor 4: RSI oversold/overbought
|
||||
if (marketData.rsi !== undefined) {
|
||||
if (position.side === 'long' && marketData.rsi < 35) {
|
||||
confidence += 15
|
||||
reasons.push("RSI oversold - reversal likely")
|
||||
} else if (position.side === 'short' && marketData.rsi > 65) {
|
||||
confidence += 15
|
||||
reasons.push("RSI overbought - reversal likely")
|
||||
}
|
||||
}
|
||||
|
||||
const hasReversalPotential = confidence >= 50
|
||||
|
||||
return {
|
||||
hasReversalPotential,
|
||||
reasoning: hasReversalPotential
|
||||
? reasons.join(", ")
|
||||
: `Insufficient reversal signals (${confidence}% confidence)`,
|
||||
confidence
|
||||
}
|
||||
}
|
||||
|
||||
function calculateSafeDCAAmount(position, accountStatus, marketData, maxLeverageAllowed) {
|
||||
// Simple leverage calculation (using basic 3x leverage for DCA)
|
||||
const maxDCAUSD = accountStatus.availableBalance * 0.3 // Use 30% of available balance
|
||||
const leveragedDCAAmount = maxDCAUSD * 3 // 3x leverage for safety
|
||||
const dcaTokenAmount = leveragedDCAAmount / marketData.price
|
||||
|
||||
// Ensure DCA doesn't make position too large
|
||||
const currentPositionValue = position.size * position.entryPrice
|
||||
const maxPositionValue = accountStatus.accountValue * 2 // Max 2x account value
|
||||
const remainingCapacity = maxPositionValue - currentPositionValue
|
||||
|
||||
const finalDCAAmount = Math.min(
|
||||
dcaTokenAmount,
|
||||
remainingCapacity / marketData.price,
|
||||
position.size * 0.4 // Max 40% of current position size
|
||||
)
|
||||
|
||||
if (finalDCAAmount < 0.01) {
|
||||
return {
|
||||
dcaAmount: 0,
|
||||
reasoning: "Insufficient available balance or position capacity for safe DCA"
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dcaAmount: finalDCAAmount,
|
||||
reasoning: `Safe DCA: ${finalDCAAmount.toFixed(4)} tokens using 3x leverage`
|
||||
}
|
||||
}
|
||||
|
||||
function calculateNewAveragePrice(currentSize, currentPrice, dcaSize, dcaPrice) {
|
||||
const totalValue = (currentSize * currentPrice) + (dcaSize * dcaPrice)
|
||||
const totalSize = currentSize + dcaSize
|
||||
return totalValue / totalSize
|
||||
}
|
||||
|
||||
function calculateNewStopLossAndTakeProfit(side, newAveragePrice, newPositionSize, marketData, confidence) {
|
||||
const baseStopLossPercent = 2.5 // Base 2.5% stop loss
|
||||
const baseTakeProfitPercent = confidence > 70 ? 7 : 5 // Higher TP if very confident
|
||||
|
||||
let stopLoss, takeProfit
|
||||
|
||||
if (side === 'long') {
|
||||
stopLoss = newAveragePrice * (1 - baseStopLossPercent / 100)
|
||||
takeProfit = newAveragePrice * (1 + baseTakeProfitPercent / 100)
|
||||
} else {
|
||||
stopLoss = newAveragePrice * (1 + baseStopLossPercent / 100)
|
||||
takeProfit = newAveragePrice * (1 - baseTakeProfitPercent / 100)
|
||||
}
|
||||
|
||||
return { stopLoss, takeProfit }
|
||||
}
|
||||
|
||||
function calculateNewLeverage(newPositionSize, newAveragePrice, accountValue, dcaAmount) {
|
||||
const totalPositionValue = newPositionSize * newAveragePrice
|
||||
return totalPositionValue / accountValue
|
||||
}
|
||||
|
||||
function calculateNewLiquidationPrice(averagePrice, leverage, side) {
|
||||
if (side === 'long') {
|
||||
return averagePrice * (1 - 1/leverage)
|
||||
} else {
|
||||
return averagePrice * (1 + 1/leverage)
|
||||
}
|
||||
}
|
||||
|
||||
function assessDCARisk(newLeverage, liquidationPrice, stopLoss, availableBalance, dcaAmount) {
|
||||
const liquidationBuffer = Math.abs(liquidationPrice - stopLoss) / stopLoss * 100
|
||||
const balanceUsagePercent = (dcaAmount * liquidationPrice) / availableBalance * 100
|
||||
|
||||
if (newLeverage <= 4 && liquidationBuffer >= 15 && balanceUsagePercent <= 30) return 'LOW'
|
||||
if (newLeverage <= 8 && liquidationBuffer >= 10 && balanceUsagePercent <= 50) return 'MEDIUM'
|
||||
|
||||
return 'HIGH'
|
||||
}
|
||||
|
||||
// Test the DCA system with SOL screenshot data
|
||||
async function testSOLDCA() {
|
||||
console.log('🧪 Testing AI DCA Manager with SOL Position Data')
|
||||
console.log('=' .repeat(60))
|
||||
|
||||
// Real data from the screenshot
|
||||
const testParams = {
|
||||
currentPosition: {
|
||||
side: 'long',
|
||||
size: 2.69, // SOL amount from screenshot
|
||||
entryPrice: 185.98,
|
||||
currentPrice: 183.87,
|
||||
unrealizedPnl: -6.73, // From screenshot (red, losing position)
|
||||
stopLoss: 181.485, // From screenshot
|
||||
takeProfit: 187.12 // From screenshot
|
||||
},
|
||||
accountStatus: {
|
||||
accountValue: 2789.44, // Max amount from screenshot
|
||||
availableBalance: 1200, // Estimated available (conservative)
|
||||
leverage: 15.2, // Current leverage from screenshot
|
||||
liquidationPrice: 177.198 // From screenshot
|
||||
},
|
||||
marketData: {
|
||||
price: 183.87,
|
||||
priceChange24h: -6.75, // From screenshot (red, declining)
|
||||
volume: 245000000,
|
||||
rsi: 30, // Oversold territory (estimated)
|
||||
support: 180.0, // Support level estimate
|
||||
resistance: 189.0 // Resistance level estimate
|
||||
},
|
||||
maxLeverageAllowed: 20
|
||||
}
|
||||
|
||||
console.log('📊 Current SOL Position Analysis:')
|
||||
console.log(` Position: ${testParams.currentPosition.side.toUpperCase()} ${testParams.currentPosition.size} SOL`)
|
||||
console.log(` Entry Price: $${testParams.currentPosition.entryPrice}`)
|
||||
console.log(` Current Price: $${testParams.currentPosition.currentPrice}`)
|
||||
console.log(` Price Drop: ${(((testParams.currentPosition.currentPrice - testParams.currentPosition.entryPrice) / testParams.currentPosition.entryPrice) * 100).toFixed(2)}%`)
|
||||
console.log(` Unrealized PnL: $${testParams.currentPosition.unrealizedPnl.toFixed(2)} 📉`)
|
||||
console.log(` Current Leverage: ${testParams.accountStatus.leverage}x`)
|
||||
console.log(` Available Balance: $${testParams.accountStatus.availableBalance}`)
|
||||
console.log(` 24h Change: ${testParams.marketData.priceChange24h}% 📉`)
|
||||
console.log()
|
||||
|
||||
// Run DCA analysis
|
||||
console.log('🔍 Running AI DCA Analysis...')
|
||||
const dcaResult = analyzeDCAOpportunity(testParams)
|
||||
|
||||
console.log()
|
||||
console.log('📈 AI DCA Decision:')
|
||||
console.log('=' .repeat(30))
|
||||
console.log(` Should DCA: ${dcaResult.shouldDCA ? '✅ YES' : '❌ NO'}`)
|
||||
console.log(` Confidence: ${dcaResult.confidence}%`)
|
||||
console.log(` Risk Level: ${dcaResult.riskAssessment}`)
|
||||
console.log(` AI Reasoning: ${dcaResult.reasoning}`)
|
||||
console.log()
|
||||
|
||||
if (dcaResult.shouldDCA) {
|
||||
console.log('💰 DCA Execution Plan:')
|
||||
console.log('=' .repeat(25))
|
||||
console.log(` DCA Amount: ${dcaResult.dcaAmount?.toFixed(4)} SOL`)
|
||||
console.log(` DCA Price: $${testParams.currentPosition.currentPrice} (current market)`)
|
||||
console.log(` New Average Price: $${dcaResult.newAveragePrice?.toFixed(4)}`)
|
||||
console.log(` New Stop Loss: $${dcaResult.newStopLoss?.toFixed(4)}`)
|
||||
console.log(` New Take Profit: $${dcaResult.newTakeProfit?.toFixed(4)}`)
|
||||
console.log(` New Leverage: ${dcaResult.newLeverage?.toFixed(1)}x`)
|
||||
console.log(` New Liquidation: $${dcaResult.newLiquidationPrice?.toFixed(4)}`)
|
||||
console.log()
|
||||
|
||||
// Calculate impact
|
||||
const originalLoss = testParams.currentPosition.unrealizedPnl
|
||||
const totalPositionAfterDCA = testParams.currentPosition.size + dcaResult.dcaAmount
|
||||
const breakEvenMove = ((dcaResult.newAveragePrice - testParams.currentPosition.currentPrice) / testParams.currentPosition.currentPrice * 100)
|
||||
const potentialProfitAtTP = ((dcaResult.newTakeProfit - dcaResult.newAveragePrice) * totalPositionAfterDCA)
|
||||
|
||||
console.log('📊 Impact Analysis:')
|
||||
console.log('=' .repeat(20))
|
||||
console.log(` Original Loss: $${originalLoss.toFixed(2)}`)
|
||||
console.log(` Total Position After DCA: ${totalPositionAfterDCA.toFixed(4)} SOL`)
|
||||
console.log(` Break-Even Price: $${dcaResult.newAveragePrice?.toFixed(4)}`)
|
||||
console.log(` Price Move Needed: +${breakEvenMove.toFixed(2)}% from current`)
|
||||
console.log(` Potential Profit at TP: $${potentialProfitAtTP.toFixed(2)}`)
|
||||
console.log(` Risk/Reward: ${(potentialProfitAtTP / Math.abs(originalLoss)).toFixed(1)}:1`)
|
||||
|
||||
const liquidationBuffer = Math.abs(dcaResult.newLiquidationPrice - dcaResult.newStopLoss) / dcaResult.newStopLoss * 100
|
||||
console.log(` Liquidation Buffer: ${liquidationBuffer.toFixed(1)}% safety margin`)
|
||||
}
|
||||
|
||||
console.log('\n🧠 Market Context Analysis:')
|
||||
console.log('=' .repeat(30))
|
||||
console.log(` SOL down ${Math.abs(testParams.marketData.priceChange24h)}% in 24h (oversold conditions)`)
|
||||
console.log(` Current price near estimated support at $${testParams.marketData.support}`)
|
||||
console.log(` RSI at ${testParams.marketData.rsi} indicates oversold (DCA opportunity)`)
|
||||
console.log(` Position is ${((testParams.currentPosition.entryPrice - testParams.currentPosition.currentPrice) / testParams.currentPosition.entryPrice * 100).toFixed(1)}% underwater`)
|
||||
|
||||
console.log('\n✅ DCA Analysis Complete')
|
||||
console.log(' This represents a sophisticated AI-driven position scaling strategy')
|
||||
console.log(' that could turn the current losing position into a profitable one!')
|
||||
}
|
||||
|
||||
testSOLDCA().catch(console.error)
|
||||
Reference in New Issue
Block a user