feat: implement dynamic position calculator with leverage slider

- Added comprehensive PositionCalculator component with real-time PnL calculations
- Implemented dynamic leverage adjustment with slider (1x to 100x)
- Added investment amount input for position sizing
- Integrated liquidation price calculations based on leverage and maintenance margin
- Added real-time price fetching from multiple sources (CoinGecko, CoinCap, Binance)
- Implemented automatic stop loss and take profit extraction from AI analysis
- Added risk/reward ratio calculations and position metrics
- Included trading fee calculations and net investment display
- Added position type selection (Long/Short) with dynamic PnL calculation
- Integrated high leverage warning system for risk management
- Added advanced settings for customizable trading fees and maintenance margins
- Automatically updates calculations when analysis parameters change
- Supports both manual price input and real-time market data
- Fully responsive design with gradient styling matching app theme
This commit is contained in:
mindesbunister
2025-07-18 13:16:11 +02:00
parent 56409b1161
commit ba354c609d
3 changed files with 601 additions and 0 deletions

View File

@@ -0,0 +1,407 @@
// 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)
// Trading parameters
const [tradingFee, setTradingFee] = useState<number>(0.1) // 0.1% fee
const [maintenanceMargin, setMaintenanceMargin] = useState<number>(0.5) // 0.5% maintenance margin
// Calculate position metrics
const calculatePosition = () => {
if (!currentPrice || currentPrice <= 0) return null
const positionSize = investmentAmount * leverage
const marginRequired = investmentAmount
const fee = positionSize * (tradingFee / 100)
const netInvestment = investmentAmount + fee
// Get AI analysis targets if available
let entryPrice = currentPrice
let stopLoss = 0
let takeProfit = 0
if (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])
}
// Look for stop loss
const stopMatch = analysisText.match(/stop[:\s]*[\$]?(\d+\.?\d*)/i)
if (stopMatch) {
stopLoss = parseFloat(stopMatch[1])
}
// Look for take profit
const profitMatch = analysisText.match(/(?:take profit|target)[:\s]*[\$]?(\d+\.?\d*)/i)
if (profitMatch) {
takeProfit = parseFloat(profitMatch[1])
}
// 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
}
if (!takeProfit) {
takeProfit = positionType === 'long'
? entryPrice * 1.10 // 10% take profit for long
: entryPrice * 0.90 // 10% take profit for short
}
} else {
// Default targets if no analysis
stopLoss = positionType === 'long'
? currentPrice * 0.95
: currentPrice * 1.05
takeProfit = positionType === 'long'
? currentPrice * 1.10
: currentPrice * 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
}
setCalculation(result)
onPositionChange?.(result)
return result
}
// Recalculate when parameters change
useEffect(() => {
calculatePosition()
}, [investmentAmount, leverage, positionType, currentPrice, analysis, tradingFee, maintenanceMargin])
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>
{/* 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
</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>
)
}