From 497e9ed0befb8734b57e23d414031512cb1671d0 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Wed, 16 Jul 2025 15:22:19 +0200 Subject: [PATCH] fix: implement trade button integration with AI analysis - Add trade button next to each analysis result - Fix TradeModal to properly receive and display analysis data - Update TypeScript interfaces to match actual data structure - Pre-fill Entry Price, Stop Loss, and Take Profit values from AI analysis - Fix duplicate variable declarations causing build errors - Remove TradeExecutionPanel from analysis page (reverted to original design) Trade button now opens modal with correct pre-filled values Analysis data properly passed between components Build errors resolved --- app/analysis/page.js | 29 +- app/chart-trading-demo/page.tsx | 770 ++++++++++++++++++++++++++++++++ components/TradeModal.tsx | 24 +- 3 files changed, 802 insertions(+), 21 deletions(-) create mode 100644 app/chart-trading-demo/page.tsx diff --git a/app/analysis/page.js b/app/analysis/page.js index 0760a63..82d377f 100644 --- a/app/analysis/page.js +++ b/app/analysis/page.js @@ -1,28 +1,21 @@ 'use client' -import React, { useState } from 'react' -import AIAnalysisPanel from '../../components/AIAnalysisPanel.tsx' + +import AIAnalysisPanel from '../../components/AIAnalysisPanel' export default function AnalysisPage() { - const [analysisResult, setAnalysisResult] = useState(null) - const [currentSymbol, setCurrentSymbol] = useState('SOL') - - const handleAnalysisComplete = (analysis, symbol) => { - setAnalysisResult(analysis) - setCurrentSymbol(symbol || 'SOL') - } - return (
-
-
-

AI Analysis

-

Get comprehensive market insights powered by AI analysis

-
+
+

+ ๐Ÿค– AI-Powered Market Analysis +

+

+ Get professional trading insights with multi-timeframe analysis, precise entry/exit levels, + and institutional-quality recommendations powered by OpenAI. +

-
- -
+
) } diff --git a/app/chart-trading-demo/page.tsx b/app/chart-trading-demo/page.tsx new file mode 100644 index 0000000..f6b964c --- /dev/null +++ b/app/chart-trading-demo/page.tsx @@ -0,0 +1,770 @@ +'use client' +import React, { useState, useRef, useEffect } from 'react' +import SimpleChart from '../../components/SimpleChart' + +interface Position { + id: string + symbol: string + side: 'BUY' | 'SELL' + amount: number + entryPrice: number + stopLoss: number + takeProfit: number + currentPrice: number + unrealizedPnl: number + leverage: number +} + +export default function ChartTradingDemo() { + const [selectedSymbol, setSelectedSymbol] = useState('SOL') + const [leverage, setLeverage] = useState(1) + const [payingAmount, setPayingAmount] = useState('') + const [payingToken, setPayingToken] = useState('USDC') + const [receivingAmount, setReceivingAmount] = useState('') + const [stopLoss, setStopLoss] = useState('') + const [takeProfit, setTakeProfit] = useState('') + const [isSymbolDropdownOpen, setIsSymbolDropdownOpen] = useState(false) + const [isPayingTokenDropdownOpen, setIsPayingTokenDropdownOpen] = useState(false) + const dropdownRef = useRef(null) + const payingTokenDropdownRef = useRef(null) + const [positions, setPositions] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [tradeStatus, setTradeStatus] = useState('') + const [liveBalances, setLiveBalances] = useState<{[key: string]: number}>({}) + const [marketPrices, setMarketPrices] = useState<{[key: string]: {price: number, change: number}}>({}); + + const symbols = [ + { symbol: 'SOL', name: 'Solana', price: marketPrices.SOL?.price || 166.21, change: marketPrices.SOL?.change || 2.45, icon: '๐ŸŸฃ' }, + { symbol: 'BTC', name: 'Bitcoin', price: marketPrices.BTC?.price || 42150.00, change: marketPrices.BTC?.change || -1.23, icon: '๐ŸŸ ' }, + { symbol: 'ETH', name: 'Ethereum', price: marketPrices.ETH?.price || 2580.50, change: marketPrices.ETH?.change || 3.12, icon: 'โฌœ' }, + { symbol: 'BONK', name: 'Bonk', price: marketPrices.BONK?.price || 0.00003456, change: marketPrices.BONK?.change || 15.67, icon: '๐Ÿ•' }, + { symbol: 'JUP', name: 'Jupiter', price: marketPrices.JUP?.price || 0.8234, change: marketPrices.JUP?.change || 5.43, icon: '๐Ÿช' }, + { symbol: 'WIF', name: 'dogwifhat', price: marketPrices.WIF?.price || 3.42, change: marketPrices.WIF?.change || -8.21, icon: '๐Ÿงข' }, + ] + + const payingTokens = [ + { symbol: 'USDC', name: 'USD Coin', balance: liveBalances.USDC || 0, icon: '๐Ÿ’ต' }, + { symbol: 'USDT', name: 'Tether', balance: liveBalances.USDT || 0, icon: '๐Ÿ’ฐ' }, + { symbol: 'SOL', name: 'Solana', balance: liveBalances.SOL || 0, icon: '๐ŸŸฃ' }, + { symbol: 'BTC', name: 'Bitcoin', balance: liveBalances.BTC || 0, icon: '๐ŸŸ ' }, + { symbol: 'ETH', name: 'Ethereum', balance: liveBalances.ETH || 0, icon: 'โฌœ' }, + ] + + // Close dropdown when clicking outside + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsSymbolDropdownOpen(false) + } + if (payingTokenDropdownRef.current && !payingTokenDropdownRef.current.contains(event.target as Node)) { + setIsPayingTokenDropdownOpen(false) + } + } + + document.addEventListener('mousedown', handleClickOutside) + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, []) + + // Load real wallet balances and positions + useEffect(() => { + const loadRealData = async () => { + try { + // Fetch real wallet balances + const balanceResponse = await fetch('/api/wallet/balance') + if (balanceResponse.ok) { + const balanceData = await balanceResponse.json() + if (balanceData.success && balanceData.balance?.positions) { + // Convert positions array to balances object + const balances: {[key: string]: number} = {} + balanceData.balance.positions.forEach((position: any) => { + balances[position.symbol] = position.amount + }) + setLiveBalances(balances) + console.log('โœ… Loaded wallet balances:', balances) + } + } + + // Fetch real positions + const positionsResponse = await fetch('/api/trading/positions') + if (positionsResponse.ok) { + const positionsData = await positionsResponse.json() + if (positionsData.success) { + setPositions(positionsData.positions || []) + } + } + + // Fetch market prices + const pricesResponse = await fetch('/api/prices') + if (pricesResponse.ok) { + const pricesData = await pricesResponse.json() + if (pricesData.success) { + setMarketPrices(pricesData.prices || {}) + } + } + } catch (error) { + console.error('Error loading real data:', error) + setTradeStatus('Error loading wallet data') + } + } + + loadRealData() + + // Refresh data every 30 seconds + const interval = setInterval(loadRealData, 30000) + return () => clearInterval(interval) + }, []) + + const getCurrentSymbolData = () => { + return symbols.find(s => s.symbol === selectedSymbol) || symbols[0] + } + + const getCurrentPayingTokenData = () => { + const tokenData = payingTokens.find(t => t.symbol === payingToken) || payingTokens[0] + console.log(`Getting token data for ${payingToken}:`, tokenData, 'Live balances:', liveBalances) + return tokenData + } + + // Calculate receiving amount based on paying amount + useEffect(() => { + if (payingAmount && payingToken === 'USDC') { + const symbolData = getCurrentSymbolData() + const symbolPrice = symbolData.price + const receiving = parseFloat(payingAmount) / symbolPrice + setReceivingAmount(receiving.toFixed(6)) + } else if (payingAmount && payingToken === selectedSymbol) { + setReceivingAmount(payingAmount) + } else { + setReceivingAmount('') + } + }, [payingAmount, payingToken, selectedSymbol]) + + const handleTrade = async (side: 'BUY' | 'SELL') => { + const amount = parseFloat(payingAmount) + + if (!amount || amount <= 0) { + alert('Please enter a valid amount') + return + } + + setIsLoading(true) + setTradeStatus(`Executing ${side} order...`) + + try { + // Execute real trade based on leverage + const tradeEndpoint = leverage > 1 ? '/api/trading/execute-perp' : '/api/trading/execute-dex' + + const tradePayload = { + symbol: selectedSymbol, + side, + amount, + stopLoss: stopLoss ? parseFloat(stopLoss) : undefined, + takeProfit: takeProfit ? parseFloat(takeProfit) : undefined, + leverage: leverage > 1 ? leverage : undefined, + fromCoin: payingToken, + toCoin: selectedSymbol, + useRealDEX: true + } + + console.log('Executing trade:', tradePayload) + + const response = await fetch(tradeEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(tradePayload) + }) + + const result = await response.json() + + if (result.success) { + setTradeStatus(`${side} order executed successfully!`) + + // Refresh positions and balances + const positionsResponse = await fetch('/api/trading/positions') + if (positionsResponse.ok) { + const positionsData = await positionsResponse.json() + if (positionsData.success) { + setPositions(positionsData.positions || []) + } + } + + const balanceResponse = await fetch('/api/wallet/balance') + if (balanceResponse.ok) { + const balanceData = await balanceResponse.json() + if (balanceData.success && balanceData.balance?.positions) { + // Convert positions array to balances object + const balances: {[key: string]: number} = {} + balanceData.balance.positions.forEach((position: any) => { + balances[position.symbol] = position.amount + }) + setLiveBalances(balances) + } + } + + // Clear form + setPayingAmount('') + setReceivingAmount('') + setStopLoss('') + setTakeProfit('') + + alert(`โœ… ${side} order executed successfully!\nTransaction: ${result.signature || 'Completed'}`) + } else { + setTradeStatus(`Trade failed: ${result.error || 'Unknown error'}`) + alert(`โŒ Trade failed: ${result.message || result.error || 'Unknown error'}`) + } + } catch (error) { + console.error('Trade execution error:', error) + setTradeStatus('Trade execution failed') + alert('โŒ Trade execution failed. Please try again.') + } finally { + setIsLoading(false) + setTimeout(() => setTradeStatus(''), 5000) + } + } + + const handleClosePosition = async (positionId: string) => { + const position = positions.find(pos => pos.id === positionId) + if (!position) return + + if (!confirm(`Are you sure you want to close ${position.symbol} ${position.side} position?`)) { + return + } + + setIsLoading(true) + setTradeStatus('Closing position...') + + try { + const response = await fetch(`/api/trading/positions/${positionId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }) + + const result = await response.json() + + if (result.success) { + setTradeStatus('Position closed successfully!') + + // Refresh positions and balances + const positionsResponse = await fetch('/api/trading/positions') + if (positionsResponse.ok) { + const positionsData = await positionsResponse.json() + if (positionsData.success) { + setPositions(positionsData.positions || []) + } + } + + const balanceResponse = await fetch('/api/wallet/balance') + if (balanceResponse.ok) { + const balanceData = await balanceResponse.json() + if (balanceData.success && balanceData.balance?.positions) { + // Convert positions array to balances object + const balances: {[key: string]: number} = {} + balanceData.balance.positions.forEach((position: any) => { + balances[position.symbol] = position.amount + }) + setLiveBalances(balances) + } + } + + alert(`โœ… Position closed successfully!\nP&L: ${result.realizedPnl ? (result.realizedPnl >= 0 ? '+' : '') + '$' + result.realizedPnl.toFixed(2) : 'Calculated at settlement'}`) + } else { + setTradeStatus(`Failed to close position: ${result.error}`) + alert(`โŒ Failed to close position: ${result.message || result.error}`) + } + } catch (error) { + console.error('Position close error:', error) + setTradeStatus('Failed to close position') + alert('โŒ Failed to close position. Please try again.') + } finally { + setIsLoading(false) + setTimeout(() => setTradeStatus(''), 5000) + } + } + + return ( + <> + +
+ {/* Top Bar */} +
+
+
+

Live Trading Terminal

+ + {/* Symbol Selector Dropdown */} +
+ + + {/* Dropdown Menu */} + {isSymbolDropdownOpen && ( +
+
+
+ Select Trading Pair +
+ {symbols.map(({ symbol, name, price, change, icon }) => ( + + ))} +
+
+ )} +
+
+ + {/* Market Status */} +
+
+
+ {isLoading ? 'Processing...' : 'Live Trading'} +
+ {tradeStatus && ( +
+ {tradeStatus} +
+ )} +
+ {new Date().toLocaleTimeString()} +
+
+
+
+ + {/* Main Trading Interface */} +
+ {/* Chart Area (70% width) */} +
+ +
+ + {/* Trading Panel (30% width) */} +
+
+

{selectedSymbol}/USDC

+
+ ${getCurrentSymbolData().price.toLocaleString()} +
+
= 0 ? 'text-green-400' : 'text-red-400'}`}> + {getCurrentSymbolData().change >= 0 ? '+' : ''}{getCurrentSymbolData().change}% +
+ + {/* Leverage Selector */} +
+
+ +
+ {leverage}x +
+
+ + {/* Leverage Slider */} +
+ setLeverage(parseInt(e.target.value))} + className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer slider" + style={{ + background: `linear-gradient(to right, #eab308 0%, #eab308 ${((leverage - 1) / 99) * 100}%, #374151 ${((leverage - 1) / 99) * 100}%, #374151 100%)` + }} + /> + + {/* Leverage Marks */} +
+ {[1, 2, 5, 10, 20, 50, 100].map((mark) => ( + + ))} +
+
+ + {/* Leverage Warning */} + {leverage > 10 && ( +
+ + + + High leverage increases liquidation risk +
+ )} +
+ + {/* Quick Trade Buttons */} +
+ + +
+ + {/* Trade Form */} +
+ {/* You're Paying Section */} +
+ +
+
+ setPayingAmount(e.target.value)} + placeholder="0.00" + step="0.01" + min="0" + className="bg-transparent text-white text-lg font-medium focus:outline-none flex-1 mr-3" + /> + + {/* Paying Token Selector */} +
+ + + {/* Paying Token Dropdown */} + {isPayingTokenDropdownOpen && ( +
+
+ {payingTokens.map(({ symbol, name, balance, icon }) => ( + + ))} +
+
+ )} +
+
+ + {/* Balance and MAX button */} +
+ Balance: {getCurrentPayingTokenData().balance.toFixed(getCurrentPayingTokenData().balance < 1 ? 6 : 2)} {payingToken} + +
+ {getCurrentPayingTokenData().balance === 0 && ( +
+ โš ๏ธ No {payingToken} balance available +
+ )} + {payingAmount && parseFloat(payingAmount) > getCurrentPayingTokenData().balance && ( +
+ โš ๏ธ Insufficient balance ({getCurrentPayingTokenData().balance.toFixed(getCurrentPayingTokenData().balance < 1 ? 6 : 2)} {payingToken} available) +
+ )} +
+ {leverage > 1 && payingAmount && ( +
+ Position value: ${(parseFloat(payingAmount) * leverage).toLocaleString()} ({leverage}x leverage) +
+ )} +
+ + {/* You're Receiving Section */} +
+ +
+
+
+ {receivingAmount || '0.00'} +
+
+ {getCurrentSymbolData().icon} + {selectedSymbol} +
+
+
+ Rate: 1 {payingToken} = {payingToken === 'USDC' ? (1 / getCurrentSymbolData().price).toFixed(8) : '1.00'} {selectedSymbol} +
+
+
+ +
+
+ + setStopLoss(e.target.value)} + placeholder="Optional" + step="0.01" + min="0" + className="w-full bg-gray-700 text-white rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+
+ + setTakeProfit(e.target.value)} + placeholder="Optional" + step="0.01" + min="0" + className="w-full bg-gray-700 text-white rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+
+ + {/* Risk Information */} + {payingAmount && leverage > 1 && ( +
+
โš ๏ธ Leveraged Position Risk
+
+ Position value: ${(parseFloat(payingAmount) * leverage).toLocaleString()} ({leverage}x leverage) +
+
+ Liquidation risk: High with {leverage}x leverage +
+
+ )} +
+
+
+
+ + {/* Bottom Panel - Positions */} +
+
+
+
+ + + +
+ + {positions.length > 0 && ( +
+ Total P&L: sum + pos.unrealizedPnl, 0) >= 0 + ? 'text-green-400' : 'text-red-400' + }`}> + {positions.reduce((sum, pos) => sum + pos.unrealizedPnl, 0) >= 0 ? '+' : ''} + ${positions.reduce((sum, pos) => sum + pos.unrealizedPnl, 0).toFixed(2)} + +
+ )} +
+ + {/* Positions Table */} +
+ {isLoading && positions.length === 0 && ( +
+
+ Loading positions... +
+ )} + {!isLoading && positions.length === 0 ? ( +
+ No open positions +
+ ) : ( +
+ {positions.map((position: Position) => ( +
+
+
+
+
+ {position.symbol} โ€ข {position.side} + {position.leverage > 1 && ( + + {position.leverage}x + + )} +
+
+ Size: {position.amount} โ€ข Entry: ${position.entryPrice?.toFixed(2)} +
+
+ SL: ${position.stopLoss.toFixed(2)} +
+
+ TP: ${position.takeProfit.toFixed(2)} +
+
+
+ +
+
+ ${(position.amount * position.currentPrice).toFixed(2)} +
+
= 0 ? 'text-green-400' : 'text-red-400' + }`}> + {position.unrealizedPnl >= 0 ? '+' : ''}${(position.unrealizedPnl || 0).toFixed(2)} +
+ {position.leverage > 1 && ( +
+ {position.leverage}x Leveraged +
+ )} +
+ +
+ +
+
+ ))} +
+ )} +
+
+
+
+ + ) +} \ No newline at end of file diff --git a/components/TradeModal.tsx b/components/TradeModal.tsx index fb765c7..3b3e599 100644 --- a/components/TradeModal.tsx +++ b/components/TradeModal.tsx @@ -7,10 +7,13 @@ interface TradeModalProps { tradeData: { entry: string tp: string + tp2?: string sl: string - risk: string - reward: string - action: 'BUY' | 'SELL' + symbol?: string + timeframe?: string + risk?: string + reward?: string + action?: 'BUY' | 'SELL' } | null onExecute: (data: any) => void } @@ -55,6 +58,21 @@ export default function TradeModal({ isOpen, onClose, tradeData, onExecute }: Tr } }, [isOpen]) + // Update form data when tradeData changes + useEffect(() => { + if (tradeData) { + console.log('๐Ÿ”„ TradeModal updating form with new tradeData:', tradeData) + setFormData(prev => ({ + ...prev, + entry: tradeData.entry || '', + tp1: tradeData.tp || '', + tp2: tradeData.tp2 || '', + sl: tradeData.sl || '', + tradingCoin: tradeData.symbol ? tradeData.symbol.replace('USD', '') : 'SOL' + })) + } + }, [tradeData]) + const fetchWalletBalance = async () => { try { const response = await fetch('/api/wallet/balance')