Files
trading_bot_v3/components/SimpleChart.tsx
mindesbunister 0e3a2d7255 feat: Implement Jupiter-style trading interface with token selection
- Add 'You're paying' and 'You're receiving' sections with proper token dropdowns
- Implement balance display and MAX button functionality
- Add automatic receiving amount calculation based on paying amount
- Enhance token selector with icons, names, and balance information
- Improve leverage position value calculations and risk warnings
- Update trade execution to use new paying/receiving token structure
- Maintain all existing functionality including stop loss, take profit, and position management

This creates a more intuitive and professional trading interface that matches Jupiter's UX patterns.
2025-07-16 14:56:53 +02:00

290 lines
9.1 KiB
TypeScript

'use client'
import React, { useRef, useEffect, useState } from 'react'
interface Position {
id: string
symbol: string
side: 'BUY' | 'SELL'
amount: number
entryPrice: number
stopLoss: number
takeProfit: number
currentPrice: number
unrealizedPnl: number
leverage: number
}
interface SimpleChartProps {
symbol?: string
positions?: Position[]
}
interface CandleData {
open: number
high: number
low: number
close: number
time: number
}
export default function SimpleChart({ symbol = 'SOL/USDC', positions = [] }: SimpleChartProps) {
const canvasRef = useRef<HTMLCanvasElement>(null)
const [candleData, setCandleData] = useState<CandleData[]>([])
const [timeframe, setTimeframe] = useState('5m')
const timeframes = ['1m', '5m', '15m', '1h', '4h', '1d']
// Generate realistic candlestick data
const generateCandleData = React.useCallback(() => {
const data: CandleData[] = []
const basePrice = symbol === 'SOL' ? 166.5 : symbol === 'BTC' ? 42150 : 2580
let currentPrice = basePrice
const now = Date.now()
for (let i = 60; i >= 0; i--) {
const timeOffset = i * 5 * 60 * 1000 // 5-minute intervals
const time = now - timeOffset
const volatility = basePrice * 0.002 // 0.2% volatility
const open = currentPrice
const change = (Math.random() - 0.5) * volatility * 2
const close = open + change
const high = Math.max(open, close) + Math.random() * volatility
const low = Math.min(open, close) - Math.random() * volatility
data.push({ open, high, low, close, time })
currentPrice = close
}
return data
}, [symbol])
useEffect(() => {
setCandleData(generateCandleData())
}, [symbol, timeframe, generateCandleData])
useEffect(() => {
const canvas = canvasRef.current
if (!canvas || candleData.length === 0) return
const ctx = canvas.getContext('2d')
if (!ctx) return
// Set canvas size for high DPI displays
const rect = canvas.getBoundingClientRect()
const dpr = window.devicePixelRatio || 1
canvas.width = rect.width * dpr
canvas.height = rect.height * dpr
ctx.scale(dpr, dpr)
const width = rect.width
const height = rect.height
// Clear canvas with dark background
ctx.fillStyle = '#0f0f0f'
ctx.fillRect(0, 0, width, height)
// Calculate price range
const prices = candleData.flatMap(d => [d.high, d.low])
const maxPrice = Math.max(...prices)
const minPrice = Math.min(...prices)
const priceRange = maxPrice - minPrice
const padding = priceRange * 0.1
// Chart dimensions
const chartLeft = 60
const chartRight = width - 20
const chartTop = 40
const chartBottom = height - 60
const chartWidth = chartRight - chartLeft
const chartHeight = chartBottom - chartTop
// Draw grid
ctx.strokeStyle = '#1a1a1a'
ctx.lineWidth = 1
// Horizontal grid lines (price levels)
const priceStep = (maxPrice - minPrice + padding * 2) / 8
for (let i = 0; i <= 8; i++) {
const price = minPrice - padding + i * priceStep
const y = chartBottom - ((price - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
ctx.beginPath()
ctx.moveTo(chartLeft, y)
ctx.lineTo(chartRight, y)
ctx.stroke()
// Price labels
ctx.fillStyle = '#666'
ctx.font = '11px Arial'
ctx.textAlign = 'right'
ctx.fillText(price.toFixed(2), chartLeft - 5, y + 4)
}
// Vertical grid lines (time)
const timeStep = chartWidth / 12
for (let i = 0; i <= 12; i++) {
const x = chartLeft + i * timeStep
ctx.beginPath()
ctx.moveTo(x, chartTop)
ctx.lineTo(x, chartBottom)
ctx.stroke()
}
// Draw candlesticks
const candleWidth = Math.max(2, chartWidth / candleData.length - 2)
candleData.forEach((candle, index) => {
const x = chartLeft + (index / (candleData.length - 1)) * chartWidth
const openY = chartBottom - ((candle.open - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
const closeY = chartBottom - ((candle.close - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
const highY = chartBottom - ((candle.high - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
const lowY = chartBottom - ((candle.low - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
const isGreen = candle.close > candle.open
const color = isGreen ? '#26a69a' : '#ef5350'
ctx.strokeStyle = color
ctx.fillStyle = color
ctx.lineWidth = 1
// Draw wick (high-low line)
ctx.beginPath()
ctx.moveTo(x, highY)
ctx.lineTo(x, lowY)
ctx.stroke()
// Draw candle body
const bodyTop = Math.min(openY, closeY)
const bodyHeight = Math.abs(closeY - openY)
if (isGreen) {
ctx.strokeRect(x - candleWidth / 2, bodyTop, candleWidth, Math.max(bodyHeight, 1))
} else {
ctx.fillRect(x - candleWidth / 2, bodyTop, candleWidth, Math.max(bodyHeight, 1))
}
})
// Draw position overlays
positions.forEach((position) => {
if (!position.symbol.includes(symbol.replace('/USDC', ''))) return
const entryY = chartBottom - ((position.entryPrice - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
const stopLossY = chartBottom - ((position.stopLoss - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
const takeProfitY = chartBottom - ((position.takeProfit - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
// Entry price line
ctx.strokeStyle = position.side === 'BUY' ? '#26a69a' : '#ef5350'
ctx.lineWidth = 2
ctx.setLineDash([5, 5])
ctx.beginPath()
ctx.moveTo(chartLeft, entryY)
ctx.lineTo(chartRight, entryY)
ctx.stroke()
// Stop loss line
ctx.strokeStyle = '#ef5350'
ctx.lineWidth = 1
ctx.setLineDash([3, 3])
ctx.beginPath()
ctx.moveTo(chartLeft, stopLossY)
ctx.lineTo(chartRight, stopLossY)
ctx.stroke()
// Take profit line
ctx.strokeStyle = '#26a69a'
ctx.lineWidth = 1
ctx.setLineDash([3, 3])
ctx.beginPath()
ctx.moveTo(chartLeft, takeProfitY)
ctx.lineTo(chartRight, takeProfitY)
ctx.stroke()
ctx.setLineDash([]) // Reset line dash
})
// Draw current price line
const currentPrice = candleData[candleData.length - 1]?.close || 0
const currentPriceY = chartBottom - ((currentPrice - (minPrice - padding)) / (maxPrice - minPrice + padding * 2)) * chartHeight
ctx.strokeStyle = '#ffa726'
ctx.lineWidth = 2
ctx.setLineDash([])
ctx.beginPath()
ctx.moveTo(chartLeft, currentPriceY)
ctx.lineTo(chartRight, currentPriceY)
ctx.stroke()
// Current price label
ctx.fillStyle = '#ffa726'
ctx.fillRect(chartRight - 60, currentPriceY - 10, 60, 20)
ctx.fillStyle = '#000'
ctx.font = '12px Arial'
ctx.textAlign = 'center'
ctx.fillText(currentPrice.toFixed(2), chartRight - 30, currentPriceY + 4)
// Chart title
ctx.fillStyle = '#ffffff'
ctx.font = 'bold 16px Arial'
ctx.textAlign = 'left'
ctx.fillText(`${symbol} - ${timeframe}`, 20, 25)
}, [symbol, positions, candleData, timeframe])
return (
<div className="w-full bg-gray-900 rounded-lg border border-gray-700">
{/* Chart Controls */}
<div className="flex items-center justify-between p-3 border-b border-gray-700">
<div className="flex items-center space-x-4">
<h3 className="text-white font-medium">{symbol}</h3>
<div className="flex space-x-1">
{timeframes.map(tf => (
<button
key={tf}
onClick={() => setTimeframe(tf)}
className={`px-2 py-1 text-xs rounded transition-all ${
timeframe === tf
? 'bg-blue-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
{tf}
</button>
))}
</div>
</div>
<div className="text-sm text-gray-400">
{candleData.length > 0 && (
<span>
Last: ${candleData[candleData.length - 1]?.close.toFixed(2)}
24h Vol: ${(Math.random() * 1000000).toFixed(0)}M
</span>
)}
</div>
</div>
{/* Chart Canvas */}
<div className="relative">
<canvas
ref={canvasRef}
className="w-full"
style={{ height: '400px', display: 'block' }}
/>
{/* Legend */}
{positions.length > 0 && (
<div className="absolute top-2 right-2 bg-gray-800/90 rounded p-2 text-xs space-y-1">
<div className="text-yellow-400"> Current Price</div>
<div className="text-green-400"> Take Profit</div>
<div className="text-red-400"> Stop Loss</div>
<div className="text-blue-400"> Entry Price</div>
</div>
)}
</div>
</div>
)
}