diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..d12b170
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,16 @@
+{
+ "extends": [
+ "next/core-web-vitals",
+ "next/typescript"
+ ],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "warn",
+ "@typescript-eslint/no-explicit-any": "warn",
+ "react/no-unescaped-entities": "warn",
+ "@next/next/no-html-link-for-pages": "warn",
+ "@next/next/no-img-element": "warn",
+ "react-hooks/exhaustive-deps": "warn",
+ "@typescript-eslint/no-require-imports": "warn",
+ "prefer-const": "warn"
+ }
+}
diff --git a/app/analysis/page.js b/app/analysis/page.js
index 0760a63..5a8560b 100644
--- a/app/analysis/page.js
+++ b/app/analysis/page.js
@@ -1,6 +1,7 @@
'use client'
import React, { useState } from 'react'
import AIAnalysisPanel from '../../components/AIAnalysisPanel.tsx'
+import TradeExecutionPanel from '../../components/TradeExecutionPanel.js'
export default function AnalysisPage() {
const [analysisResult, setAnalysisResult] = useState(null)
@@ -15,13 +16,70 @@ export default function AnalysisPage() {
-
AI Analysis
-
Get comprehensive market insights powered by AI analysis
+
AI Analysis & Trading
+
Get comprehensive market insights powered by AI analysis and execute trades
-
-
+
+ {/* Left Column - AI Analysis */}
+
+
+
๐ AI Market Analysis
+
+
+
+
+ {/* Right Column - Trading Panel */}
+
+
+
๐ฐ Execute Trade
+
+
+
+ {/* Analysis Summary */}
+ {analysisResult && (
+
+
๐ฏ Analysis Summary
+
+
+ Symbol:
+ {currentSymbol}
+
+
+ Sentiment:
+
+ {analysisResult.sentiment}
+
+
+ {analysisResult.entryPrice && (
+
+ Entry Price:
+ ${analysisResult.entryPrice}
+
+ )}
+ {analysisResult.stopLoss && (
+
+ Stop Loss:
+ ${analysisResult.stopLoss}
+
+ )}
+ {analysisResult.takeProfit && (
+
+ Take Profit:
+ ${analysisResult.takeProfit}
+
+ )}
+
+
+ )}
+
)
diff --git a/app/chart-test/page.tsx b/app/chart-test/page.tsx
deleted file mode 100644
index dbb1798..0000000
--- a/app/chart-test/page.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-'use client'
-import React from 'react'
-import TradingChart from '../../components/TradingChart'
-
-export default function SimpleChartTest() {
- return (
-
- )
-}
diff --git a/app/chart-trading-demo/page.tsx b/app/chart-trading-demo/page.tsx
new file mode 100644
index 0000000..3e46069
--- /dev/null
+++ b/app/chart-trading-demo/page.tsx
@@ -0,0 +1,595 @@
+'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([
+ // Mock position data for demo
+ {
+ id: 'demo_pos_1',
+ symbol: 'SOL/USDC',
+ side: 'BUY' as 'BUY' | 'SELL',
+ amount: 0.5,
+ entryPrice: 165.50,
+ stopLoss: 160.00,
+ takeProfit: 170.00,
+ currentPrice: 166.21,
+ unrealizedPnl: 0.355,
+ leverage: 2,
+ }
+ ])
+
+ const symbols = [
+ { symbol: 'SOL', name: 'Solana', price: 166.21, change: 2.45, icon: '๐ฃ' },
+ { symbol: 'BTC', name: 'Bitcoin', price: 42150.00, change: -1.23, icon: '๐ ' },
+ { symbol: 'ETH', name: 'Ethereum', price: 2580.50, change: 3.12, icon: 'โฌ' },
+ { symbol: 'BONK', name: 'Bonk', price: 0.00003456, change: 15.67, icon: '๐' },
+ { symbol: 'JUP', name: 'Jupiter', price: 0.8234, change: 5.43, icon: '๐ช' },
+ { symbol: 'WIF', name: 'dogwifhat', price: 3.42, change: -8.21, icon: '๐งข' },
+ ]
+
+ const payingTokens = [
+ { symbol: 'USDC', name: 'USD Coin', balance: 1234.56, icon: '๐ต' },
+ { symbol: 'USDT', name: 'Tether', balance: 892.34, icon: '๐ฐ' },
+ { symbol: 'SOL', name: 'Solana', balance: 5.42, icon: '๐ฃ' },
+ { symbol: 'BTC', name: 'Bitcoin', balance: 0.025, icon: '๐ ' },
+ { symbol: 'ETH', name: 'Ethereum', balance: 1.85, 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)
+ }
+ }, [])
+
+ const getCurrentSymbolData = () => {
+ return symbols.find(s => s.symbol === selectedSymbol) || symbols[0]
+ }
+
+ const getCurrentPayingTokenData = () => {
+ return payingTokens.find(t => t.symbol === payingToken) || payingTokens[0]
+ }
+
+ // Calculate receiving amount based on paying amount
+ useEffect(() => {
+ if (payingAmount && payingToken === 'USDC') {
+ const symbolPrice = getCurrentSymbolData().price
+ const receiving = parseFloat(payingAmount) / symbolPrice
+ setReceivingAmount(receiving.toFixed(6))
+ } else if (payingAmount && payingToken === selectedSymbol) {
+ setReceivingAmount(payingAmount)
+ } else {
+ setReceivingAmount('')
+ }
+ }, [payingAmount, payingToken, selectedSymbol])
+
+ const handleTrade = (side: 'BUY' | 'SELL') => {
+ const amount = parseFloat(payingAmount)
+ const symbolData = getCurrentSymbolData()
+
+ if (!amount || amount <= 0) {
+ alert('Please enter a valid amount')
+ return
+ }
+
+ const newPosition: Position = {
+ id: `pos_${Date.now()}`,
+ symbol: `${selectedSymbol}/USDC`,
+ side,
+ amount,
+ entryPrice: symbolData.price,
+ stopLoss: stopLoss ? parseFloat(stopLoss) : symbolData.price * (side === 'BUY' ? 0.95 : 1.05),
+ takeProfit: takeProfit ? parseFloat(takeProfit) : symbolData.price * (side === 'BUY' ? 1.05 : 0.95),
+ currentPrice: symbolData.price,
+ unrealizedPnl: 0,
+ leverage,
+ }
+
+ setPositions(prev => [...prev, newPosition])
+ setPayingAmount('')
+ setReceivingAmount('')
+ setStopLoss('')
+ setTakeProfit('')
+ alert(`${side} order placed (demo)`)
+ }
+
+ const handleClosePosition = (positionId: string) => {
+ setPositions(prev => prev.filter(pos => pos.id !== positionId))
+ alert('Position closed (demo)')
+ }
+
+ return (
+ <>
+
+
+ {/* Top Bar */}
+
+
+
+
Chart Trading Terminal
+
+ {/* Symbol Selector Dropdown */}
+
+
+
+ {/* Dropdown Menu */}
+ {isSymbolDropdownOpen && (
+
+
+
+ Select Trading Pair
+
+ {symbols.map(({ symbol, name, price, change, icon }) => (
+
+ ))}
+
+
+ )}
+
+
+
+ {/* Market Status */}
+
+
+
+ {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.toLocaleString()} {payingToken}
+
+
+
+ {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}
+
+
+
+
+
+
+ {/* 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 */}
+
+ {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/app/error.tsx b/app/error.tsx
new file mode 100644
index 0000000..0bb7d15
--- /dev/null
+++ b/app/error.tsx
@@ -0,0 +1,25 @@
+'use client'
+import React from 'react'
+
+export default function Error({
+ error,
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset: () => void
+}) {
+ return (
+
+
+
Something went wrong!
+
An error occurred while loading this page.
+
+
+
+ )
+}
diff --git a/app/not-found.tsx b/app/not-found.tsx
new file mode 100644
index 0000000..0e704d3
--- /dev/null
+++ b/app/not-found.tsx
@@ -0,0 +1,21 @@
+'use client'
+import React from 'react'
+import Link from 'next/link'
+
+export default function NotFound() {
+ return (
+
+
+
404
+
Page Not Found
+
The page you're looking for doesn't exist.
+
+ Go Back Home
+
+
+
+ )
+}
diff --git a/app/page.js b/app/page.js
index 5833dbd..928e976 100644
--- a/app/page.js
+++ b/app/page.js
@@ -54,6 +54,59 @@ export default function HomePage() {
+
+ {/* Featured: Chart Trading Demo */}
+
+
+
+
๐ Professional Chart Trading
+
Advanced trading interface with leverage, charting, and position management
+
+
+ NEW
+
+
+
+
+
+
+ ๐
+
+
+
Real-time Charts
+
Professional candlestick visualization
+
+
+
+
+
+ โก
+
+
+
Leverage Trading
+
1x to 100x leverage options
+
+
+
+
+
+ ๐ฏ
+
+
+
Position Management
+
Stop loss & take profit
+
+
+
+
+
+ ๐
+ Launch Chart Trading Terminal
+
+
)
}
diff --git a/app/simple-chart/page.tsx b/app/simple-chart/page.tsx
deleted file mode 100644
index 7990537..0000000
--- a/app/simple-chart/page.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-'use client'
-import React, { useEffect, useRef, useState } from 'react'
-
-export default function SimpleChart() {
- const chartContainerRef = useRef(null)
- const [status, setStatus] = useState('Initializing...')
-
- useEffect(() => {
- if (!chartContainerRef.current) return
-
- const initChart = async () => {
- try {
- setStatus('Loading lightweight-charts...')
- console.log('Importing lightweight-charts...')
-
- const LightweightCharts = await import('lightweight-charts')
- console.log('Lightweight charts imported successfully')
- setStatus('Creating chart...')
-
- const { createChart, ColorType, CandlestickSeries } = LightweightCharts
-
- const chart = createChart(chartContainerRef.current!, {
- layout: {
- background: { type: ColorType.Solid, color: '#1a1a1a' },
- textColor: '#ffffff',
- },
- width: chartContainerRef.current!.clientWidth || 800,
- height: 400,
- grid: {
- vertLines: { color: 'rgba(42, 46, 57, 0.5)' },
- horzLines: { color: 'rgba(42, 46, 57, 0.5)' },
- },
- })
-
- setStatus('Adding candlestick series...')
- console.log('Chart created, adding candlestick series...')
-
- const candlestickSeries = chart.addSeries(CandlestickSeries, {
- upColor: '#26a69a',
- downColor: '#ef5350',
- borderDownColor: '#ef5350',
- borderUpColor: '#26a69a',
- wickDownColor: '#ef5350',
- wickUpColor: '#26a69a',
- })
-
- // Generate sample data
- const data = []
- const baseTime = new Date(Date.now() - 100 * 60 * 1000) // 100 minutes ago
- let price = 166.5
-
- for (let i = 0; i < 100; i++) {
- const currentTime = new Date(baseTime.getTime() + i * 60 * 1000) // 1 minute intervals
- const timeString = currentTime.toISOString().split('T')[0] // YYYY-MM-DD format
- const change = (Math.random() - 0.5) * 2 // Random price change
- const open = price
- const close = price + change
- const high = Math.max(open, close) + Math.random() * 1
- const low = Math.min(open, close) - Math.random() * 1
-
- data.push({
- time: timeString,
- open: Number(open.toFixed(2)),
- high: Number(high.toFixed(2)),
- low: Number(low.toFixed(2)),
- close: Number(close.toFixed(2)),
- })
-
- price = close
- }
-
- console.log('Setting chart data...', data.length, 'points')
- candlestickSeries.setData(data)
-
- setStatus('Chart loaded successfully!')
- console.log('Chart created successfully!')
-
- // Handle resize
- const handleResize = () => {
- if (chartContainerRef.current) {
- chart.applyOptions({
- width: chartContainerRef.current.clientWidth,
- })
- }
- }
-
- window.addEventListener('resize', handleResize)
-
- return () => {
- window.removeEventListener('resize', handleResize)
- chart.remove()
- }
-
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error)
- console.error('Error creating chart:', error)
- setStatus(`Error: ${errorMessage}`)
- }
- }
-
- initChart()
- }, [])
-
- return (
-
-
Lightweight Charts Test
-
Status: {status}
-
-
- )
-}
diff --git a/components/SimpleChart.tsx b/components/SimpleChart.tsx
index 5e4c406..76b9023 100644
--- a/components/SimpleChart.tsx
+++ b/components/SimpleChart.tsx
@@ -1,100 +1,289 @@
'use client'
-import React, { useRef, useEffect } from 'react'
+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?: any[]
+ 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(null)
+ const [candleData, setCandleData] = useState([])
+ 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) return
+ if (!canvas || candleData.length === 0) return
const ctx = canvas.getContext('2d')
if (!ctx) return
- // Set canvas size
- canvas.width = 800
- canvas.height = 400
+ // 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)
- // Clear canvas
- ctx.fillStyle = '#1a1a1a'
- ctx.fillRect(0, 0, canvas.width, canvas.height)
+ 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 = '#333'
+ ctx.strokeStyle = '#1a1a1a'
ctx.lineWidth = 1
- // Vertical lines
- for (let x = 0; x < canvas.width; x += 40) {
+ // 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(x, 0)
- ctx.lineTo(x, canvas.height)
+ 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()
}
- // Horizontal lines
- for (let y = 0; y < canvas.height; y += 40) {
+ // 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(0, y)
- ctx.lineTo(canvas.width, y)
- ctx.stroke()
- }
-
- // Draw sample candlesticks
- const candleWidth = 20
- const basePrice = 166.5
- const priceScale = 2
-
- for (let i = 0; i < 30; i++) {
- const x = 40 + i * 25
- const open = basePrice + (Math.random() - 0.5) * 10
- const close = open + (Math.random() - 0.5) * 5
- const high = Math.max(open, close) + Math.random() * 3
- const low = Math.min(open, close) - Math.random() * 3
-
- const openY = canvas.height - (open - 150) * priceScale
- const closeY = canvas.height - (close - 150) * priceScale
- const highY = canvas.height - (high - 150) * priceScale
- const lowY = canvas.height - (low - 150) * priceScale
-
- // Determine color
- const isGreen = close > open
- ctx.fillStyle = isGreen ? '#26a69a' : '#ef5350'
- ctx.strokeStyle = isGreen ? '#26a69a' : '#ef5350'
-
- // Draw wick
- ctx.lineWidth = 2
- ctx.beginPath()
- ctx.moveTo(x + candleWidth / 2, highY)
- ctx.lineTo(x + candleWidth / 2, lowY)
+ ctx.moveTo(x, highY)
+ ctx.lineTo(x, lowY)
ctx.stroke()
- // Draw body
+ // Draw candle body
const bodyTop = Math.min(openY, closeY)
const bodyHeight = Math.abs(closeY - openY)
- ctx.fillRect(x, bodyTop, candleWidth, Math.max(bodyHeight, 2))
- }
+
+ 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 price labels
- ctx.fillStyle = '#ffffff'
+ // 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.fillText(`${symbol} - $${basePrice.toFixed(2)}`, 10, 30)
- ctx.fillStyle = '#26a69a'
- ctx.fillText('+2.45%', 10, 50)
+ ctx.textAlign = 'center'
+ ctx.fillText(currentPrice.toFixed(2), chartRight - 30, currentPriceY + 4)
- }, [symbol, positions])
+ // Chart title
+ ctx.fillStyle = '#ffffff'
+ ctx.font = 'bold 16px Arial'
+ ctx.textAlign = 'left'
+ ctx.fillText(`${symbol} - ${timeframe}`, 20, 25)
+
+ }, [symbol, positions, candleData, timeframe])
return (
-
-
+
+ {/* Chart Controls */}
+
+
+
{symbol}
+
+ {timeframes.map(tf => (
+
+ ))}
+
+
+
+
+ {candleData.length > 0 && (
+
+ Last: ${candleData[candleData.length - 1]?.close.toFixed(2)} โข
+ 24h Vol: ${(Math.random() * 1000000).toFixed(0)}M
+
+ )}
+
+
+
+ {/* Chart Canvas */}
+
+
+
+ {/* Legend */}
+ {positions.length > 0 && (
+
+
โ Current Price
+
โฏ Take Profit
+
โฏ Stop Loss
+
โฏ Entry Price
+
+ )}
+
)
}
diff --git a/components/TradeExecutionPanel.js b/components/TradeExecutionPanel.js
index b835215..14baa03 100644
--- a/components/TradeExecutionPanel.js
+++ b/components/TradeExecutionPanel.js
@@ -48,9 +48,27 @@ export default function TradeExecutionPanel({ analysis, symbol = 'SOL' }) {
setTakeProfit(analysis.takeProfits.tp1.price.toString())
setEnableTakeProfit(true)
}
+ // Set trade type based on analysis recommendation
+ if (analysis.recommendation === 'BUY' || analysis.sentiment === 'BULLISH') {
+ setTradeType('BUY')
+ } else if (analysis.recommendation === 'SELL' || analysis.sentiment === 'BEARISH') {
+ setTradeType('SELL')
+ }
}
}, [analysis])
+ // Initialize coin selection based on symbol prop
+ useEffect(() => {
+ if (symbol && availableCoins.find(coin => coin.symbol === symbol)) {
+ setFromCoin(symbol)
+ setPerpCoin(symbol)
+ // If it's not a stablecoin, trade it against USDC
+ if (symbol !== 'USDC' && symbol !== 'USDT') {
+ setToCoin('USDC')
+ }
+ }
+ }, [symbol])
+
// Get recommended price from analysis
const getRecommendedPrice = () => {
if (!analysis) return null
diff --git a/components/TradingChart.tsx b/components/TradingChart.tsx
deleted file mode 100644
index 492a2e8..0000000
--- a/components/TradingChart.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-'use client'
-import React, { useEffect, useRef, useState } from 'react'
-
-interface Position {
- id: string
- symbol: string
- side: 'LONG' | 'SHORT'
- size: number
- entryPrice: number
- stopLoss?: number
- takeProfit?: number
- pnl: number
- pnlPercentage: number
-}
-
-interface TradingChartProps {
- symbol?: string
- positions?: Position[]
-}
-
-export default function TradingChart({ symbol = 'SOL/USDC', positions = [] }: TradingChartProps) {
- const chartContainerRef = useRef
(null)
- const chart = useRef(null)
- const candlestickSeries = useRef(null)
- const positionLines = useRef([])
- const [isLoading, setIsLoading] = useState(true)
-
- // Initialize chart with dynamic import
- useEffect(() => {
- if (!chartContainerRef.current) return
-
- const initChart = async () => {
- try {
- // Dynamic import to avoid SSR issues
- const LightweightCharts = await import('lightweight-charts')
- const { createChart, ColorType, CrosshairMode, LineStyle, CandlestickSeries } = LightweightCharts
-
- chart.current = createChart(chartContainerRef.current!, {
- layout: {
- background: { type: ColorType.Solid, color: '#1a1a1a' },
- textColor: '#ffffff',
- },
- width: chartContainerRef.current!.clientWidth,
- height: 600,
- grid: {
- vertLines: { color: 'rgba(42, 46, 57, 0.5)' },
- horzLines: { color: 'rgba(42, 46, 57, 0.5)' },
- },
- crosshair: {
- mode: CrosshairMode.Normal,
- },
- rightPriceScale: {
- borderColor: 'rgba(197, 203, 206, 0.8)',
- },
- timeScale: {
- borderColor: 'rgba(197, 203, 206, 0.8)',
- },
- })
-
- // Create candlestick series
- candlestickSeries.current = chart.current.addSeries(CandlestickSeries, {
- upColor: '#26a69a',
- downColor: '#ef5350',
- borderDownColor: '#ef5350',
- borderUpColor: '#26a69a',
- wickDownColor: '#ef5350',
- wickUpColor: '#26a69a',
- })
-
- // Generate sample data
- console.log('Generating sample data...')
- const data = generateSampleData()
- console.log('Sample data generated:', data.length, 'points')
- console.log('First few data points:', data.slice(0, 3))
-
- console.log('Setting chart data...')
- candlestickSeries.current.setData(data)
- console.log('Chart data set successfully')
-
- // Add position overlays
- console.log('Adding position overlays...')
- addPositionOverlays(LineStyle)
- console.log('Position overlays added')
-
- console.log('Chart initialization complete')
- setIsLoading(false)
-
- // Handle resize
- const handleResize = () => {
- if (chart.current && chartContainerRef.current) {
- chart.current.applyOptions({
- width: chartContainerRef.current.clientWidth,
- })
- }
- }
-
- window.addEventListener('resize', handleResize)
-
- return () => {
- window.removeEventListener('resize', handleResize)
- if (chart.current) {
- chart.current.remove()
- }
- }
- } catch (error) {
- console.error('Failed to initialize chart:', error)
- console.error('Error details:', {
- message: error instanceof Error ? error.message : String(error),
- stack: error instanceof Error ? error.stack : undefined
- })
- setIsLoading(false)
- }
- }
-
- const addPositionOverlays = (LineStyle: any) => {
- if (!chart.current) return
-
- // Clear existing lines
- positionLines.current.forEach(line => {
- if (line && chart.current) {
- chart.current.removePriceLine(line)
- }
- })
- positionLines.current = []
-
- // Add new position lines
- positions.forEach(position => {
- // Entry price line
- const entryLine = chart.current.addPriceLine({
- price: position.entryPrice,
- color: '#2196F3',
- lineWidth: 2,
- lineStyle: LineStyle.Solid,
- axisLabelVisible: true,
- title: `Entry: $${position.entryPrice.toFixed(2)}`,
- })
- positionLines.current.push(entryLine)
-
- // Stop loss line
- if (position.stopLoss) {
- const slLine = chart.current.addPriceLine({
- price: position.stopLoss,
- color: '#f44336',
- lineWidth: 2,
- lineStyle: LineStyle.Dashed,
- axisLabelVisible: true,
- title: `SL: $${position.stopLoss.toFixed(2)}`,
- })
- positionLines.current.push(slLine)
- }
-
- // Take profit line
- if (position.takeProfit) {
- const tpLine = chart.current.addPriceLine({
- price: position.takeProfit,
- color: '#4caf50',
- lineWidth: 2,
- lineStyle: LineStyle.Dashed,
- axisLabelVisible: true,
- title: `TP: $${position.takeProfit.toFixed(2)}`,
- })
- positionLines.current.push(tpLine)
- }
- })
- }
-
- const generateSampleData = () => {
- const data = []
- const basePrice = 166.5
- let currentPrice = basePrice
- const baseDate = new Date()
-
- for (let i = 0; i < 100; i++) {
- // Generate data for the last 100 days, one point per day
- const currentTime = new Date(baseDate.getTime() - (99 - i) * 24 * 60 * 60 * 1000)
- const timeString = currentTime.toISOString().split('T')[0] // YYYY-MM-DD format
- 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: timeString,
- open: Number(open.toFixed(2)),
- high: Number(high.toFixed(2)),
- low: Number(low.toFixed(2)),
- close: Number(close.toFixed(2)),
- })
-
- currentPrice = close
- }
-
- return data
- }
-
- initChart()
- }, [])
-
- // Update position overlays when positions change
- useEffect(() => {
- if (chart.current && !isLoading && positions.length > 0) {
- import('lightweight-charts').then(({ LineStyle }) => {
- // Re-add position overlays (this is a simplified version)
- // In a full implementation, you'd want to properly manage line updates
- })
- }
- }, [positions, isLoading])
-
- if (isLoading) {
- return (
-
- )
- }
-
- return (
-
- {/* Chart Header */}
-
-
-
{symbol}
-
$166.21
-
+1.42%
-
-
-
- {/* Position Info */}
- {positions.length > 0 && (
-
-
- {positions.map(position => (
-
-
-
{position.side} {position.size}
-
= 0 ? 'text-green-400' : 'text-red-400'}>
- {position.pnl >= 0 ? '+' : ''}${position.pnl.toFixed(2)}
-
-
- ))}
-
-
- )}
-
- {/* Chart Container */}
-
-
- )
-}
diff --git a/docker-compose.yml b/docker-compose.yml
index d272d91..c2ace2f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,3 +1,5 @@
+version: "2"
+
services:
app:
build:
diff --git a/next.config.ts b/next.config.ts
index 0cec214..50380fb 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,14 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- transpilePackages: ['lightweight-charts'],
- webpack: (config) => {
- config.resolve.fallback = {
- ...config.resolve.fallback,
- fs: false,
- };
- return config;
- },
+ // Simple configuration for stability
};
export default nextConfig;
diff --git a/package.json b/package.json
index eba69ad..be8b17f 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,8 @@
"dev:docker": "next dev --port 3000 --hostname 0.0.0.0",
"build": "next build",
"start": "next start",
+ "lint": "next lint",
+ "lint:fix": "next lint --fix",
"docker:build": "docker compose build",
"docker:build:optimized": "DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 COMPOSE_BAKE=true docker compose --progress=plain build --parallel",
"docker:build:dev": "DOCKER_BUILDKIT=1 COMPOSE_BAKE=true docker compose --progress=plain build --target development",