FINAL FIX: Docker API URL resolution for internal service communication
- Fixed internal API calls in Docker environment to use port 3000 instead of 9000 - Added DOCKER_ENV detection to properly route internal fetch requests - Resolves ECONNREFUSED errors when APIs try to call each other within container - Trade validation now works correctly in Docker: 5 USD position validates properly - Successfully tested: amountUSD field properly passed through validation pipeline - Both development and Docker environments now fully functional
This commit is contained in:
218
components/WorkingTradingChart.tsx
Normal file
218
components/WorkingTradingChart.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
'use client'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
|
||||
interface CandlestickData {
|
||||
time: string
|
||||
open: number
|
||||
high: number
|
||||
low: number
|
||||
close: number
|
||||
}
|
||||
|
||||
interface TradingChartProps {
|
||||
symbol?: string
|
||||
positions?: any[]
|
||||
}
|
||||
|
||||
export default function WorkingTradingChart({ symbol = 'SOL/USDC', positions = [] }: TradingChartProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const [status, setStatus] = useState('Initializing...')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
setStatus('Generating data...')
|
||||
|
||||
// Generate sample candlestick data
|
||||
const data: CandlestickData[] = []
|
||||
const basePrice = 166.5
|
||||
let currentPrice = basePrice
|
||||
const today = new Date()
|
||||
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const date = new Date(today)
|
||||
date.setDate(date.getDate() - i)
|
||||
|
||||
const volatility = 0.02
|
||||
const change = (Math.random() - 0.5) * volatility * currentPrice
|
||||
|
||||
const open = currentPrice
|
||||
const close = currentPrice + change
|
||||
const high = Math.max(open, close) + Math.random() * 0.01 * currentPrice
|
||||
const low = Math.min(open, close) - Math.random() * 0.01 * currentPrice
|
||||
|
||||
data.push({
|
||||
time: date.toISOString().split('T')[0],
|
||||
open: Number(open.toFixed(2)),
|
||||
high: Number(high.toFixed(2)),
|
||||
low: Number(low.toFixed(2)),
|
||||
close: Number(close.toFixed(2)),
|
||||
})
|
||||
|
||||
currentPrice = close
|
||||
}
|
||||
|
||||
setStatus('Drawing chart...')
|
||||
drawChart(data)
|
||||
setStatus('Chart ready!')
|
||||
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err)
|
||||
setError(errorMessage)
|
||||
setStatus(`Error: ${errorMessage}`)
|
||||
console.error('Chart error:', err)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const drawChart = (data: CandlestickData[]) => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) {
|
||||
throw new Error('Canvas element not found')
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) {
|
||||
throw new Error('Could not get 2D context')
|
||||
}
|
||||
|
||||
// Set canvas size
|
||||
canvas.width = 800
|
||||
canvas.height = 400
|
||||
|
||||
// Clear canvas
|
||||
ctx.fillStyle = '#1a1a1a'
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
if (data.length === 0) {
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.font = '16px Arial'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillText('No data available', canvas.width / 2, canvas.height / 2)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate price range
|
||||
const prices = data.flatMap(d => [d.open, d.high, d.low, d.close])
|
||||
const minPrice = Math.min(...prices)
|
||||
const maxPrice = Math.max(...prices)
|
||||
const priceRange = maxPrice - minPrice
|
||||
const padding = priceRange * 0.1
|
||||
|
||||
// Chart dimensions
|
||||
const chartLeft = 60
|
||||
const chartRight = canvas.width - 40
|
||||
const chartTop = 40
|
||||
const chartBottom = canvas.height - 60
|
||||
const chartWidth = chartRight - chartLeft
|
||||
const chartHeight = chartBottom - chartTop
|
||||
|
||||
// Draw grid lines
|
||||
ctx.strokeStyle = '#333333'
|
||||
ctx.lineWidth = 1
|
||||
|
||||
// Horizontal grid lines (price levels)
|
||||
for (let i = 0; i <= 5; i++) {
|
||||
const y = chartTop + (chartHeight / 5) * i
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(chartLeft, y)
|
||||
ctx.lineTo(chartRight, y)
|
||||
ctx.stroke()
|
||||
|
||||
// Price labels
|
||||
const price = maxPrice + padding - ((maxPrice + padding - (minPrice - padding)) / 5) * i
|
||||
ctx.fillStyle = '#888888'
|
||||
ctx.font = '12px Arial'
|
||||
ctx.textAlign = 'right'
|
||||
ctx.fillText(price.toFixed(2), chartLeft - 10, y + 4)
|
||||
}
|
||||
|
||||
// Vertical grid lines (time)
|
||||
const timeStep = Math.max(1, Math.floor(data.length / 6))
|
||||
for (let i = 0; i < data.length; i += timeStep) {
|
||||
const x = chartLeft + (chartWidth / (data.length - 1)) * i
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, chartTop)
|
||||
ctx.lineTo(x, chartBottom)
|
||||
ctx.stroke()
|
||||
|
||||
// Time labels
|
||||
ctx.fillStyle = '#888888'
|
||||
ctx.font = '12px Arial'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillText(data[i].time.split('-')[1] + '/' + data[i].time.split('-')[2], x, chartBottom + 20)
|
||||
}
|
||||
|
||||
// Draw candlesticks
|
||||
const candleWidth = Math.max(2, chartWidth / data.length * 0.6)
|
||||
|
||||
data.forEach((candle, index) => {
|
||||
const x = chartLeft + (chartWidth / (data.length - 1)) * index
|
||||
const openY = chartBottom - ((candle.open - (minPrice - padding)) / (maxPrice + padding - (minPrice - padding))) * chartHeight
|
||||
const closeY = chartBottom - ((candle.close - (minPrice - padding)) / (maxPrice + padding - (minPrice - padding))) * chartHeight
|
||||
const highY = chartBottom - ((candle.high - (minPrice - padding)) / (maxPrice + padding - (minPrice - padding))) * chartHeight
|
||||
const lowY = chartBottom - ((candle.low - (minPrice - padding)) / (maxPrice + padding - (minPrice - padding))) * chartHeight
|
||||
|
||||
const isGreen = candle.close > candle.open
|
||||
ctx.strokeStyle = isGreen ? '#26a69a' : '#ef5350'
|
||||
ctx.fillStyle = isGreen ? '#26a69a' : '#ef5350'
|
||||
ctx.lineWidth = 1
|
||||
|
||||
// Draw wick
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, highY)
|
||||
ctx.lineTo(x, lowY)
|
||||
ctx.stroke()
|
||||
|
||||
// Draw body
|
||||
const bodyTop = Math.min(openY, closeY)
|
||||
const bodyHeight = Math.abs(closeY - openY)
|
||||
|
||||
if (bodyHeight < 1) {
|
||||
// Doji - draw a line
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x - candleWidth / 2, openY)
|
||||
ctx.lineTo(x + candleWidth / 2, openY)
|
||||
ctx.stroke()
|
||||
} else {
|
||||
ctx.fillRect(x - candleWidth / 2, bodyTop, candleWidth, bodyHeight)
|
||||
}
|
||||
})
|
||||
|
||||
// Draw chart border
|
||||
ctx.strokeStyle = '#555555'
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeRect(chartLeft, chartTop, chartWidth, chartHeight)
|
||||
|
||||
// Draw title
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.font = 'bold 16px Arial'
|
||||
ctx.textAlign = 'left'
|
||||
ctx.fillText(symbol, chartLeft, 25)
|
||||
|
||||
// Draw current price
|
||||
const currentPrice = data[data.length - 1].close
|
||||
ctx.fillStyle = '#26a69a'
|
||||
ctx.font = 'bold 14px Arial'
|
||||
ctx.textAlign = 'right'
|
||||
ctx.fillText(`$${currentPrice.toFixed(2)}`, chartRight, 25)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4">
|
||||
<div className="text-white text-sm">Status: <span className="text-green-400">{status}</span></div>
|
||||
{error && (
|
||||
<div className="text-red-400 text-sm mt-2">Error: {error}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-800 rounded-lg p-4">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="w-full h-auto border border-gray-600 rounded"
|
||||
style={{ maxWidth: '100%', height: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user