Files
trading_bot_v3/components/PositionCalculator.tsx
mindesbunister 2bdf9e2b41 fix: implement proper multi-timeframe batch analysis
- Create /api/batch-analysis endpoint that collects ALL screenshots first
- Then sends all screenshots to AI for comprehensive analysis
- Fixes issue where individual timeframes were analyzed immediately
- Multi-timeframe analysis now provides cross-timeframe consensus
- Update AIAnalysisPanel to use batch analysis for multiple timeframes
- Maintains backward compatibility with single timeframe analysis
2025-07-18 18:32:08 +02:00

504 lines
19 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Dynamic Position Calculator Component
"use client"
import React, { useState, useEffect } from 'react'
interface PositionCalculatorProps {
analysis?: any // The AI analysis results
currentPrice?: number
symbol?: string
onPositionChange?: (position: PositionCalculation) => void
}
interface PositionCalculation {
investmentAmount: number
leverage: number
positionSize: number
entryPrice: number
stopLoss: number
takeProfit: number
liquidationPrice: number
maxLoss: number
maxProfit: number
riskRewardRatio: number
marginRequired: number
tradingFee: number
netInvestment: number
}
export default function PositionCalculator({
analysis,
currentPrice = 0,
symbol = 'BTCUSD',
onPositionChange
}: PositionCalculatorProps) {
const [investmentAmount, setInvestmentAmount] = useState<number>(100)
const [leverage, setLeverage] = useState<number>(10)
const [positionType, setPositionType] = useState<'long' | 'short'>('long')
const [calculation, setCalculation] = useState<PositionCalculation | null>(null)
const [showAdvanced, setShowAdvanced] = useState(false)
const [marketPrice, setMarketPrice] = useState<number>(currentPrice)
// Trading parameters
const [tradingFee, setTradingFee] = useState<number>(0.1) // 0.1% fee
const [maintenanceMargin, setMaintenanceMargin] = useState<number>(0.5) // 0.5% maintenance margin
// Debug logging
console.log('📊 PositionCalculator mounted with:', { analysis, currentPrice, symbol })
// Auto-detect position type from analysis
useEffect(() => {
if (analysis?.recommendation) {
const rec = analysis.recommendation.toLowerCase()
if (rec.includes('buy') || rec.includes('long') || rec.includes('bullish')) {
setPositionType('long')
} else if (rec.includes('sell') || rec.includes('short') || rec.includes('bearish')) {
setPositionType('short')
}
}
}, [analysis])
// Fetch current market price if not provided
useEffect(() => {
const fetchPrice = async () => {
console.log('🔍 Fetching price for:', symbol, 'currentPrice:', currentPrice)
if (currentPrice > 0) {
console.log('✅ Using provided currentPrice:', currentPrice)
setMarketPrice(currentPrice)
return
}
try {
const response = await fetch(`/api/price?symbol=${symbol}`)
const data = await response.json()
console.log('📊 Price API response:', data)
if (data.price) {
console.log('✅ Setting market price to:', data.price)
setMarketPrice(data.price)
} else {
console.error('❌ No price in API response')
setMarketPrice(symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100)
}
} catch (error) {
console.error('❌ Failed to fetch price:', error)
// Fallback to a reasonable default for testing
const fallbackPrice = symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100
console.log('🔄 Using fallback price:', fallbackPrice)
setMarketPrice(fallbackPrice)
}
}
fetchPrice()
}, [currentPrice, symbol])
// Calculate position metrics
const calculatePosition = () => {
let priceToUse = marketPrice || currentPrice
console.log('📊 Calculating position with:', { priceToUse, marketPrice, currentPrice, investmentAmount, leverage })
// Use fallback price if no price is available
if (!priceToUse || priceToUse <= 0) {
priceToUse = symbol.includes('BTC') ? 117000 : symbol.includes('ETH') ? 4000 : symbol.includes('SOL') ? 200 : 100
console.log('🔄 Using fallback price:', priceToUse)
}
const positionSize = investmentAmount * leverage
const marginRequired = investmentAmount
const fee = positionSize * (tradingFee / 100)
const netInvestment = investmentAmount + fee
console.log('📈 Position calculation:', { positionSize, marginRequired, fee, netInvestment })
// Get AI analysis targets if available
let entryPrice = priceToUse
let stopLoss = 0
let takeProfit = 0
if (analysis && analysis.analysis) {
console.log('🤖 Using AI analysis:', analysis.analysis)
// Try to extract entry, stop loss, and take profit from AI analysis
const analysisText = analysis.analysis.toLowerCase()
// Look for entry price
const entryMatch = analysisText.match(/entry[:\s]*[\$]?(\d+\.?\d*)/i)
if (entryMatch) {
entryPrice = parseFloat(entryMatch[1])
console.log('📍 Found entry price:', entryPrice)
}
// Look for stop loss
const stopMatch = analysisText.match(/stop[:\s]*[\$]?(\d+\.?\d*)/i)
if (stopMatch) {
stopLoss = parseFloat(stopMatch[1])
console.log('🛑 Found stop loss:', stopLoss)
}
// Look for take profit
const profitMatch = analysisText.match(/(?:take profit|target)[:\s]*[\$]?(\d+\.?\d*)/i)
if (profitMatch) {
takeProfit = parseFloat(profitMatch[1])
console.log('🎯 Found take profit:', takeProfit)
}
// If no specific targets found, use percentage-based defaults
if (!stopLoss) {
stopLoss = positionType === 'long'
? entryPrice * 0.95 // 5% stop loss for long
: entryPrice * 1.05 // 5% stop loss for short
console.log('🔄 Using default stop loss:', stopLoss)
}
if (!takeProfit) {
takeProfit = positionType === 'long'
? entryPrice * 1.10 // 10% take profit for long
: entryPrice * 0.90 // 10% take profit for short
console.log('🔄 Using default take profit:', takeProfit)
}
} else {
console.log('📊 No AI analysis, using defaults')
// Default targets if no analysis
stopLoss = positionType === 'long'
? priceToUse * 0.95
: priceToUse * 1.05
takeProfit = positionType === 'long'
? priceToUse * 1.10
: priceToUse * 0.90
}
// Calculate liquidation price
const liquidationPrice = positionType === 'long'
? entryPrice * (1 - (1 / leverage) + (maintenanceMargin / 100))
: entryPrice * (1 + (1 / leverage) - (maintenanceMargin / 100))
// Calculate PnL
const stopLossChange = positionType === 'long'
? (stopLoss - entryPrice) / entryPrice
: (entryPrice - stopLoss) / entryPrice
const takeProfitChange = positionType === 'long'
? (takeProfit - entryPrice) / entryPrice
: (entryPrice - takeProfit) / entryPrice
const maxLoss = positionSize * Math.abs(stopLossChange)
const maxProfit = positionSize * Math.abs(takeProfitChange)
const riskRewardRatio = maxProfit / maxLoss
const result: PositionCalculation = {
investmentAmount,
leverage,
positionSize,
entryPrice,
stopLoss,
takeProfit,
liquidationPrice,
maxLoss,
maxProfit,
riskRewardRatio,
marginRequired,
tradingFee: fee,
netInvestment
}
console.log('✅ Position calculation result:', result)
setCalculation(result)
onPositionChange?.(result)
return result
}
// Recalculate when parameters change
useEffect(() => {
console.log('🔄 Recalculating position...')
calculatePosition()
}, [investmentAmount, leverage, positionType, currentPrice, analysis, tradingFee, maintenanceMargin, marketPrice])
// Force initial calculation
useEffect(() => {
console.log('🚀 Position Calculator initialized, forcing calculation...')
calculatePosition()
}, [])
const formatCurrency = (amount: number, decimals: number = 2) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
}).format(amount)
}
const formatPrice = (price: number) => {
return new Intl.NumberFormat('en-US', {
style: 'decimal',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(price)
}
return (
<div className="bg-gradient-to-br from-gray-800/50 to-gray-900/50 border border-gray-700 rounded-lg p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-bold text-white flex items-center">
<span className="w-6 h-6 bg-gradient-to-br from-green-400 to-green-600 rounded-lg flex items-center justify-center mr-3 text-sm">
📊
</span>
Position Calculator
</h3>
<button
onClick={() => setShowAdvanced(!showAdvanced)}
className="text-sm text-gray-400 hover:text-white transition-colors"
>
{showAdvanced ? '🔽 Hide Advanced' : '🔼 Show Advanced'}
</button>
</div>
{/* Current Price Display */}
<div className="mb-6 p-4 bg-gray-800/30 rounded-lg border border-gray-600">
<div className="flex items-center justify-between">
<div className="text-sm text-gray-400">Current Market Price</div>
<div className="text-lg font-bold text-white">
{symbol}: ${formatPrice(marketPrice || currentPrice || (symbol.includes('BTC') ? 117000 : symbol.includes('ETH') ? 4000 : symbol.includes('SOL') ? 200 : 100))}
{(!marketPrice && !currentPrice) && (
<span className="text-xs text-yellow-400 ml-2">(fallback)</span>
)}
</div>
</div>
{analysis?.recommendation && (
<div className="mt-2 text-sm text-cyan-400">
AI Recommendation: {analysis.recommendation}
</div>
)}
</div>
{/* Input Controls */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* Investment Amount */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Investment Amount ($)
</label>
<input
type="number"
value={investmentAmount}
onChange={(e) => setInvestmentAmount(Number(e.target.value))}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
min="1"
step="1"
/>
</div>
{/* Position Type */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Position Type
{analysis?.recommendation && (
<span className="ml-2 text-xs text-cyan-400">(Auto-detected from analysis)</span>
)}
</label>
<div className="flex space-x-2">
<button
onClick={() => setPositionType('long')}
className={`flex-1 px-3 py-2 rounded-lg text-sm font-medium transition-all ${
positionType === 'long'
? 'bg-green-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
📈 Long
</button>
<button
onClick={() => setPositionType('short')}
className={`flex-1 px-3 py-2 rounded-lg text-sm font-medium transition-all ${
positionType === 'short'
? 'bg-red-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
📉 Short
</button>
</div>
</div>
{/* Leverage Slider */}
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-300 mb-2">
Leverage: {leverage}x
</label>
<div className="relative">
<input
type="range"
min="1"
max="100"
value={leverage}
onChange={(e) => setLeverage(Number(e.target.value))}
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer slider"
style={{
background: `linear-gradient(to right, #10B981 0%, #10B981 ${leverage}%, #374151 ${leverage}%, #374151 100%)`
}}
/>
<div className="flex justify-between text-xs text-gray-400 mt-1">
<span>1x</span>
<span>25x</span>
<span>50x</span>
<span>100x</span>
</div>
</div>
</div>
</div>
{/* Advanced Settings */}
{showAdvanced && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6 p-4 bg-gray-800/30 rounded-lg border border-gray-600">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Trading Fee (%)
</label>
<input
type="number"
value={tradingFee}
onChange={(e) => setTradingFee(Number(e.target.value))}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
min="0"
max="1"
step="0.01"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Maintenance Margin (%)
</label>
<input
type="number"
value={maintenanceMargin}
onChange={(e) => setMaintenanceMargin(Number(e.target.value))}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
min="0.1"
max="5"
step="0.1"
/>
</div>
</div>
)}
{/* Calculation Results */}
{calculation && (
<div className="space-y-4">
{/* Position Summary */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-gray-800/30 rounded-lg p-4 border border-gray-600">
<div className="text-sm text-gray-400 mb-1">Position Size</div>
<div className="text-lg font-bold text-white">
{formatCurrency(calculation.positionSize)}
</div>
</div>
<div className="bg-gray-800/30 rounded-lg p-4 border border-gray-600">
<div className="text-sm text-gray-400 mb-1">Entry Price</div>
<div className="text-lg font-bold text-white">
${formatPrice(calculation.entryPrice)}
</div>
</div>
<div className="bg-gray-800/30 rounded-lg p-4 border border-gray-600">
<div className="text-sm text-gray-400 mb-1">Margin Required</div>
<div className="text-lg font-bold text-white">
{formatCurrency(calculation.marginRequired)}
</div>
</div>
</div>
{/* Risk Metrics */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-red-900/20 border border-red-700 rounded-lg p-4">
<div className="text-sm text-red-400 mb-2">🚨 Risk Metrics</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-300">Stop Loss:</span>
<span className="text-red-400 font-bold">${formatPrice(calculation.stopLoss)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-300">Max Loss:</span>
<span className="text-red-400 font-bold">{formatCurrency(calculation.maxLoss)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-300">Liquidation:</span>
<span className="text-red-500 font-bold">${formatPrice(calculation.liquidationPrice)}</span>
</div>
</div>
</div>
<div className="bg-green-900/20 border border-green-700 rounded-lg p-4">
<div className="text-sm text-green-400 mb-2">💰 Profit Potential</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-300">Take Profit:</span>
<span className="text-green-400 font-bold">${formatPrice(calculation.takeProfit)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-300">Max Profit:</span>
<span className="text-green-400 font-bold">{formatCurrency(calculation.maxProfit)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-300">Risk/Reward:</span>
<span className="text-green-400 font-bold">1:{calculation.riskRewardRatio.toFixed(2)}</span>
</div>
</div>
</div>
</div>
{/* Fee Breakdown */}
<div className="bg-gray-800/30 rounded-lg p-4 border border-gray-600">
<div className="text-sm text-gray-400 mb-2">💸 Fee Breakdown</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="flex justify-between">
<span className="text-gray-300">Trading Fee:</span>
<span className="text-yellow-400">{formatCurrency(calculation.tradingFee)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-300">Net Investment:</span>
<span className="text-white font-bold">{formatCurrency(calculation.netInvestment)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-300">Leverage:</span>
<span className="text-blue-400 font-bold">{leverage}x</span>
</div>
</div>
</div>
{/* Risk Warning */}
{leverage > 50 && (
<div className="bg-red-900/30 border border-red-600 rounded-lg p-4">
<div className="flex items-center space-x-2">
<span className="text-red-400 text-lg"></span>
<span className="text-red-400 font-bold">High Leverage Warning</span>
</div>
<p className="text-red-300 text-sm mt-2">
Using {leverage}x leverage is extremely risky. A small price movement against your position could result in liquidation.
</p>
</div>
)}
</div>
)}
<style jsx>{`
.slider::-webkit-slider-thumb {
appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: #10B981;
cursor: pointer;
border: 2px solid #065F46;
box-shadow: 0 0 0 1px #065F46;
}
.slider::-moz-range-thumb {
height: 20px;
width: 20px;
border-radius: 50%;
background: #10B981;
cursor: pointer;
border: 2px solid #065F46;
box-shadow: 0 0 0 1px #065F46;
}
`}</style>
</div>
)
}