feat: Fix position calculator visibility and add auto-detection
- Fixed position calculator not showing when analysis entry price is 0 - Added auto-detection of Long/Short position type from AI analysis recommendation - Implemented independent price fetching from CoinGecko API - Added current market price display with AI recommendation - Enhanced position calculator with fallback prices for testing - Added API endpoint /api/price for real-time price data - Position calculator now works even when analysis lacks specific entry prices - Shows 'Auto-detected from analysis' label when position type is determined from AI The position calculator is now always visible and uses: 1. Current market price from CoinGecko API 2. Auto-detected position type from analysis (Long/Short) 3. Fallback prices for testing when API is unavailable 4. Default stop loss/take profit levels when not specified in analysis
This commit is contained in:
75
app/api/price/route.js
Normal file
75
app/api/price/route.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
|
||||||
|
export async function GET(request) {
|
||||||
|
const { searchParams } = new URL(request.url)
|
||||||
|
const symbol = searchParams.get('symbol') || 'BTCUSD'
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Map symbols to CoinGecko IDs
|
||||||
|
const symbolMap = {
|
||||||
|
'BTCUSD': 'bitcoin',
|
||||||
|
'ETHUSD': 'ethereum',
|
||||||
|
'SOLUSD': 'solana',
|
||||||
|
'SUIUSD': 'sui',
|
||||||
|
'ADAUSD': 'cardano',
|
||||||
|
'DOGEUSD': 'dogecoin',
|
||||||
|
'XRPUSD': 'ripple',
|
||||||
|
'AVAXUSD': 'avalanche-2',
|
||||||
|
'LINKUSD': 'chainlink',
|
||||||
|
'MATICUSD': 'matic-network'
|
||||||
|
}
|
||||||
|
|
||||||
|
const coinId = symbolMap[symbol.toUpperCase()] || 'bitcoin'
|
||||||
|
|
||||||
|
// Fetch from CoinGecko API
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=usd`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`CoinGecko API error: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
const price = data[coinId]?.usd
|
||||||
|
|
||||||
|
if (!price) {
|
||||||
|
throw new Error('Price not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
symbol: symbol.toUpperCase(),
|
||||||
|
price: price,
|
||||||
|
source: 'coingecko'
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Price fetch error:', error)
|
||||||
|
|
||||||
|
// Return fallback prices for testing
|
||||||
|
const fallbackPrices = {
|
||||||
|
'BTCUSD': 100000,
|
||||||
|
'ETHUSD': 4000,
|
||||||
|
'SOLUSD': 200,
|
||||||
|
'SUIUSD': 4.5,
|
||||||
|
'ADAUSD': 1.2,
|
||||||
|
'DOGEUSD': 0.4,
|
||||||
|
'XRPUSD': 2.5,
|
||||||
|
'AVAXUSD': 45,
|
||||||
|
'LINKUSD': 20,
|
||||||
|
'MATICUSD': 1.1
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
symbol: symbol.toUpperCase(),
|
||||||
|
price: fallbackPrices[symbol.toUpperCase()] || 100,
|
||||||
|
source: 'fallback',
|
||||||
|
error: error.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,14 +36,52 @@ export default function PositionCalculator({
|
|||||||
const [positionType, setPositionType] = useState<'long' | 'short'>('long')
|
const [positionType, setPositionType] = useState<'long' | 'short'>('long')
|
||||||
const [calculation, setCalculation] = useState<PositionCalculation | null>(null)
|
const [calculation, setCalculation] = useState<PositionCalculation | null>(null)
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||||
|
const [marketPrice, setMarketPrice] = useState<number>(currentPrice)
|
||||||
|
|
||||||
// Trading parameters
|
// Trading parameters
|
||||||
const [tradingFee, setTradingFee] = useState<number>(0.1) // 0.1% fee
|
const [tradingFee, setTradingFee] = useState<number>(0.1) // 0.1% fee
|
||||||
const [maintenanceMargin, setMaintenanceMargin] = useState<number>(0.5) // 0.5% maintenance margin
|
const [maintenanceMargin, setMaintenanceMargin] = useState<number>(0.5) // 0.5% maintenance margin
|
||||||
|
|
||||||
|
// 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 () => {
|
||||||
|
if (currentPrice > 0) {
|
||||||
|
setMarketPrice(currentPrice)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/price?symbol=${symbol}`)
|
||||||
|
const data = await response.json()
|
||||||
|
if (data.price) {
|
||||||
|
setMarketPrice(data.price)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch price:', error)
|
||||||
|
// Fallback to a reasonable default for testing
|
||||||
|
setMarketPrice(symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPrice()
|
||||||
|
}, [currentPrice, symbol])
|
||||||
|
|
||||||
// Calculate position metrics
|
// Calculate position metrics
|
||||||
const calculatePosition = () => {
|
const calculatePosition = () => {
|
||||||
if (!currentPrice || currentPrice <= 0) return null
|
const priceToUse = marketPrice || currentPrice
|
||||||
|
if (!priceToUse || priceToUse <= 0) return null
|
||||||
|
|
||||||
const positionSize = investmentAmount * leverage
|
const positionSize = investmentAmount * leverage
|
||||||
const marginRequired = investmentAmount
|
const marginRequired = investmentAmount
|
||||||
@@ -51,7 +89,7 @@ export default function PositionCalculator({
|
|||||||
const netInvestment = investmentAmount + fee
|
const netInvestment = investmentAmount + fee
|
||||||
|
|
||||||
// Get AI analysis targets if available
|
// Get AI analysis targets if available
|
||||||
let entryPrice = currentPrice
|
let entryPrice = priceToUse
|
||||||
let stopLoss = 0
|
let stopLoss = 0
|
||||||
let takeProfit = 0
|
let takeProfit = 0
|
||||||
|
|
||||||
@@ -92,11 +130,11 @@ export default function PositionCalculator({
|
|||||||
} else {
|
} else {
|
||||||
// Default targets if no analysis
|
// Default targets if no analysis
|
||||||
stopLoss = positionType === 'long'
|
stopLoss = positionType === 'long'
|
||||||
? currentPrice * 0.95
|
? priceToUse * 0.95
|
||||||
: currentPrice * 1.05
|
: priceToUse * 1.05
|
||||||
takeProfit = positionType === 'long'
|
takeProfit = positionType === 'long'
|
||||||
? currentPrice * 1.10
|
? priceToUse * 1.10
|
||||||
: currentPrice * 0.90
|
: priceToUse * 0.90
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate liquidation price
|
// Calculate liquidation price
|
||||||
@@ -176,6 +214,21 @@ export default function PositionCalculator({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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 || 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{analysis?.recommendation && (
|
||||||
|
<div className="mt-2 text-sm text-cyan-400">
|
||||||
|
AI Recommendation: {analysis.recommendation}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Input Controls */}
|
{/* Input Controls */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||||
{/* Investment Amount */}
|
{/* Investment Amount */}
|
||||||
@@ -197,6 +250,9 @@ export default function PositionCalculator({
|
|||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
Position Type
|
Position Type
|
||||||
|
{analysis?.recommendation && (
|
||||||
|
<span className="ml-2 text-xs text-cyan-400">(Auto-detected from analysis)</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user