458 lines
16 KiB
TypeScript
458 lines
16 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
|
|
export function TradingTools() {
|
|
const [activeTool, setActiveTool] = useState<'calculator' | 'alerts' | 'notes' | 'charts' | 'sentiment'>('charts')
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Tool Navigation */}
|
|
<div className="flex gap-2 flex-wrap">
|
|
{[
|
|
{ id: 'charts', label: '📈 Charts', count: 0 },
|
|
{ id: 'sentiment', label: '😱 Sentiment', count: 0 },
|
|
{ id: 'calculator', label: '🧮 Calculator', count: 0 },
|
|
{ id: 'alerts', label: '🔔 Alerts', count: 0 },
|
|
{ id: 'notes', label: '📝 Notes', count: 0 },
|
|
].map(tool => (
|
|
<button
|
|
key={tool.id}
|
|
onClick={() => setActiveTool(tool.id as any)}
|
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition ${
|
|
activeTool === tool.id
|
|
? 'bg-brand-pink text-white'
|
|
: 'bg-white/10 text-white/70 hover:bg-white/20'
|
|
}`}
|
|
>
|
|
{tool.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* TradingView Charts */}
|
|
{activeTool === 'charts' && <TradingViewChart />}
|
|
|
|
{/* Market Sentiment */}
|
|
{activeTool === 'sentiment' && <MarketSentiment />}
|
|
|
|
{/* Position Calculator */}
|
|
{activeTool === 'calculator' && <PositionCalculator />}
|
|
|
|
{/* Trade Alerts */}
|
|
{activeTool === 'alerts' && <TradeAlerts />}
|
|
|
|
{/* Trade Notes */}
|
|
{activeTool === 'notes' && <TradeNotes />}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function TradingViewChart() {
|
|
const [symbol, setSymbol] = useState('BTCUSD')
|
|
const [timeframe, setTimeframe] = useState('60')
|
|
|
|
// Map our symbol format to TradingView format
|
|
const getTvSymbol = (sym: string) => {
|
|
const map: Record<string, string> = {
|
|
'BTCUSD': 'BINANCE:BTCUSDT',
|
|
'ETHUSD': 'BINANCE:ETHUSDT',
|
|
'SOLUSD': 'BINANCE:SOLUSDT',
|
|
'EURUSD': 'FX:EURUSD',
|
|
}
|
|
return map[sym] || `BINANCE:${sym}`
|
|
}
|
|
|
|
const symbols = [
|
|
{ id: 'BTCUSD', label: 'BTC/USD' },
|
|
{ id: 'ETHUSD', label: 'ETH/USD' },
|
|
{ id: 'SOLUSD', label: 'SOL/USD' },
|
|
{ id: 'EURUSD', label: 'EUR/USD' },
|
|
]
|
|
|
|
const timeframes = [
|
|
{ id: '15', label: '15m' },
|
|
{ id: '60', label: '1H' },
|
|
{ id: '240', label: '4H' },
|
|
{ id: 'D', label: '1D' },
|
|
{ id: 'W', label: '1W' },
|
|
]
|
|
|
|
const chartUrl = `https://www.tradingview.com/widget/advanced-chart/?symbol=${getTvSymbol(symbol)}&interval=${timeframe}&hideToolbar=false&theme=dark&style=1&locale=en`
|
|
|
|
return (
|
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
|
<div className="flex justify-between items-center mb-4 flex-wrap gap-2">
|
|
<h3 className="text-lg font-bold">📈 TradingView Charts</h3>
|
|
<div className="flex gap-2">
|
|
<select
|
|
value={symbol}
|
|
onChange={e => setSymbol(e.target.value)}
|
|
className="bg-white/10 border border-white/20 rounded-lg px-3 py-1.5 text-white text-sm"
|
|
>
|
|
{symbols.map(s => (
|
|
<option key={s.id} value={s.id}>{s.label}</option>
|
|
))}
|
|
</select>
|
|
<select
|
|
value={timeframe}
|
|
onChange={e => setTimeframe(e.target.value)}
|
|
className="bg-white/10 border border-white/20 rounded-lg px-3 py-1.5 text-white text-sm"
|
|
>
|
|
{timeframes.map(t => (
|
|
<option key={t.id} value={t.id}>{t.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{/* TradingView Widget */}
|
|
<div className="w-full h-[500px] rounded-lg overflow-hidden border border-white/10 bg-black">
|
|
<iframe
|
|
src={chartUrl}
|
|
className="w-full h-full"
|
|
title="TradingView Chart"
|
|
allow="clipboard-write"
|
|
sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
|
|
/>
|
|
</div>
|
|
|
|
<p className="text-xs text-white/50 mt-2 text-center">
|
|
Powered by TradingView • {symbols.find(s => s.id === symbol)?.label}
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function MarketSentiment() {
|
|
const [fearGreed, setFearGreed] = useState<{ value: number; classification: string } | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
fetch('https://api.alternative.me/fng/')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.data?.[0]) {
|
|
setFearGreed({
|
|
value: parseInt(data.data[0].value),
|
|
classification: data.data[0].value_classification
|
|
})
|
|
}
|
|
setLoading(false)
|
|
})
|
|
.catch(() => setLoading(false))
|
|
}, [])
|
|
|
|
const getColor = (val: number) => {
|
|
if (val <= 25) return 'text-red-500 bg-red-500/10 border-red-500/30'
|
|
if (val <= 45) return 'text-orange-500 bg-orange-500/10 border-orange-500/30'
|
|
if (val <= 55) return 'text-yellow-500 bg-yellow-500/10 border-yellow-500/30'
|
|
if (val <= 75) return 'text-blue-500 bg-blue-500/10 border-blue-500/30'
|
|
return 'text-green-500 bg-green-500/10 border-green-500/30'
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Fear & Greed Index */}
|
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
|
<h3 className="text-lg font-bold mb-4">😱 Fear & Greed Index</h3>
|
|
|
|
{loading ? (
|
|
<p className="text-white/50">Loading...</p>
|
|
) : fearGreed ? (
|
|
<div className={`p-6 rounded-lg border ${getColor(fearGreed.value)} text-center`}>
|
|
<p className="text-5xl font-bold mb-2">{fearGreed.value}</p>
|
|
<p className="text-xl font-medium">{fearGreed.classification}</p>
|
|
</div>
|
|
) : (
|
|
<p className="text-white/50">Unable to fetch data</p>
|
|
)}
|
|
|
|
<div className="mt-4 grid grid-cols-5 gap-1 text-xs">
|
|
{['0', '25', '50', '75', '100'].map((v, i) => (
|
|
<div key={v} className={`text-center py-1 rounded ${getColor(parseInt(v)).split(' ')[0]}`}>
|
|
{v}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Long/Short Ratio */}
|
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
|
<h3 className="text-lg font-bold mb-4">📊 Long/Short Ratio</h3>
|
|
<LongShortRatio />
|
|
</div>
|
|
|
|
{/* Funding Rates */}
|
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
|
<h3 className="text-lg font-bold mb-4">💰 Funding Rates</h3>
|
|
<FundingRates />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function LongShortRatio() {
|
|
const [data, setData] = useState<any[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
// Using Binance API for futures data
|
|
Promise.all([
|
|
fetch('https://api.binance.com/api/v3/ticker/24hr?symbol=BTCUSDT'),
|
|
fetch('https://api.binance.com/api/v3/ticker/24hr?symbol=ETHUSDT'),
|
|
fetch('https://api.binance.com/api/v3/ticker/24hr?symbol=SOLUSDT'),
|
|
])
|
|
.then(res => Promise.all(res.map(r => r.json())))
|
|
.then(results => {
|
|
setData(results.map(r => ({
|
|
symbol: r.symbol,
|
|
price: parseFloat(r.lastPrice),
|
|
change: parseFloat(r.priceChangePercent),
|
|
longShort: parseFloat(r.count) > 0 ? '50' : '50' // Simplified - real data needs futures API
|
|
})))
|
|
setLoading(false)
|
|
})
|
|
.catch(() => setLoading(false))
|
|
}, [])
|
|
|
|
if (loading) return <p className="text-white/50">Loading...</p>
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
{data.map(item => (
|
|
<div key={item.symbol} className="flex justify-between items-center p-2 bg-white/5 rounded">
|
|
<span className="font-medium">{item.symbol.replace('USDT', '/USD')}</span>
|
|
<div className="text-right">
|
|
<span className="text-lg font-bold">${item.price.toLocaleString()}</span>
|
|
<span className={`ml-2 text-sm ${item.change >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
|
{item.change >= 0 ? '↑' : '↓'} {Math.abs(item.change).toFixed(2)}%
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function FundingRates() {
|
|
const [rates, setRates] = useState<any[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
// Funding rates from Binance
|
|
Promise.all([
|
|
fetch('https://api.binance.com/api/v3/premiumIndex?symbol=BTCUSDT'),
|
|
fetch('https://api.binance.com/api/v3/premiumIndex?symbol=ETHUSDT'),
|
|
fetch('https://api.binance.com/api/v3/premiumIndex?symbol=SOLUSDT'),
|
|
])
|
|
.then(res => Promise.all(res.map(r => r.json())))
|
|
.then(results => {
|
|
setRates(results.map(r => ({
|
|
symbol: r.symbol,
|
|
fundingRate: parseFloat(r.lastFundingRate) * 100,
|
|
nextFunding: new Date(r.nextFundingTime).toLocaleTimeString()
|
|
})))
|
|
setLoading(false)
|
|
})
|
|
.catch(() => setLoading(false))
|
|
}, [])
|
|
|
|
if (loading) return <p className="text-white/50">Loading...</p>
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
{rates.map(rate => (
|
|
<div key={rate.symbol} className="flex justify-between items-center p-2 bg-white/5 rounded">
|
|
<span className="font-medium">{rate.symbol.replace('USDT', '/USD')}</span>
|
|
<div className="text-right">
|
|
<span className={`text-lg font-bold ${rate.fundingRate >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
|
{rate.fundingRate >= 0 ? '+' : ''}{rate.fundingRate.toFixed(4)}%
|
|
</span>
|
|
<p className="text-xs text-white/50">Next: {rate.nextFunding}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function PositionCalculator() {
|
|
const [form, setForm] = useState({
|
|
accountSize: '10000',
|
|
riskPercent: '2',
|
|
stopLossPercent: '1',
|
|
leverage: '1',
|
|
entryPrice: '',
|
|
direction: 'long' as 'long' | 'short',
|
|
})
|
|
|
|
const calculate = () => {
|
|
const account = parseFloat(form.accountSize) || 0
|
|
const riskPct = parseFloat(form.riskPercent) || 0
|
|
const slPct = parseFloat(form.stopLossPercent) || 0
|
|
const lev = parseFloat(form.leverage) || 1
|
|
|
|
const riskAmount = account * (riskPct / 100)
|
|
const positionSize = lev * (riskAmount / (slPct / 100))
|
|
const margin = positionSize / lev
|
|
const potentialProfit = positionSize * (slPct / 100) * 2
|
|
|
|
return { riskAmount, positionSize, margin, potentialProfit }
|
|
}
|
|
|
|
const result = calculate()
|
|
|
|
return (
|
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
|
<h3 className="text-lg font-bold mb-4">🧮 Position Size Calculator</h3>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm text-white/70 mb-1">Account Size ($)</label>
|
|
<input
|
|
type="number"
|
|
value={form.accountSize}
|
|
onChange={e => setForm({...form, accountSize: e.target.value})}
|
|
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm text-white/70 mb-1">Risk Per Trade (%)</label>
|
|
<input
|
|
type="number"
|
|
value={form.riskPercent}
|
|
onChange={e => setForm({...form, riskPercent: e.target.value})}
|
|
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm text-white/70 mb-1">Stop Loss (%)</label>
|
|
<input
|
|
type="number"
|
|
value={form.stopLossPercent}
|
|
onChange={e => setForm({...form, stopLossPercent: e.target.value})}
|
|
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm text-white/70 mb-1">Leverage</label>
|
|
<input
|
|
type="number"
|
|
value={form.leverage}
|
|
onChange={e => setForm({...form, leverage: e.target.value})}
|
|
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6 grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
<div className="p-3 bg-white/5 rounded-lg text-center">
|
|
<p className="text-xs text-white/50">Max Risk</p>
|
|
<p className="text-xl font-bold text-red-400">${result.riskAmount.toFixed(2)}</p>
|
|
</div>
|
|
<div className="p-3 bg-white/5 rounded-lg text-center">
|
|
<p className="text-xs text-white/50">Position Size</p>
|
|
<p className="text-xl font-bold text-white">${result.positionSize.toFixed(2)}</p>
|
|
</div>
|
|
<div className="p-3 bg-white/5 rounded-lg text-center">
|
|
<p className="text-xs text-white/50">Required Margin</p>
|
|
<p className="text-xl font-bold text-blue-400">${result.margin.toFixed(2)}</p>
|
|
</div>
|
|
<div className="p-3 bg-white/5 rounded-lg text-center">
|
|
<p className="text-xs text-white/50">Potential Profit</p>
|
|
<p className="text-xl font-bold text-green-400">+${result.potentialProfit.toFixed(2)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function TradeAlerts() {
|
|
const [alerts, setAlerts] = useState([
|
|
{ id: '1', pair: 'BTC/USD', type: 'SL Hit', price: 66500, time: '2h ago', triggered: true },
|
|
{ id: '2', pair: 'ETH/USD', type: 'TP Hit', price: 3200, time: '5h ago', triggered: true },
|
|
])
|
|
|
|
return (
|
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
|
<h3 className="text-lg font-bold mb-4">🔔 Trade Alerts</h3>
|
|
|
|
{alerts.length === 0 ? (
|
|
<p className="text-white/50 text-center py-8">No alerts yet</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{alerts.map(alert => (
|
|
<div
|
|
key={alert.id}
|
|
className={`p-3 rounded-lg border flex justify-between items-center ${
|
|
alert.triggered ? 'bg-green-500/10 border-green-500/30' : 'bg-yellow-500/10 border-yellow-500/30'
|
|
}`}
|
|
>
|
|
<div>
|
|
<span className="font-medium">{alert.pair}</span>
|
|
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${
|
|
alert.triggered ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'
|
|
}`}>
|
|
{alert.type}
|
|
</span>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="font-medium">${alert.price.toLocaleString()}</p>
|
|
<p className="text-xs text-white/50">{alert.time}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function TradeNotes() {
|
|
const [notes, setNotes] = useState<{id: string, date: string, content: string}[]>([])
|
|
const [newNote, setNewNote] = useState('')
|
|
|
|
const addNote = () => {
|
|
if (!newNote.trim()) return
|
|
setNotes([{ id: Date.now().toString(), date: new Date().toISOString(), content: newNote }, ...notes])
|
|
setNewNote('')
|
|
}
|
|
|
|
return (
|
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
|
<h3 className="text-lg font-bold mb-4">📝 Trade Notes</h3>
|
|
|
|
<div className="mb-4">
|
|
<textarea
|
|
value={newNote}
|
|
onChange={e => setNewNote(e.target.value)}
|
|
placeholder="Write a note about your trade..."
|
|
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white placeholder-white/50 h-24 resize-none"
|
|
/>
|
|
<button
|
|
onClick={addNote}
|
|
className="mt-2 px-4 py-2 bg-brand-pink rounded-lg text-white font-medium hover:bg-brand-pink/80"
|
|
>
|
|
Add Note
|
|
</button>
|
|
</div>
|
|
|
|
{notes.length === 0 ? (
|
|
<p className="text-white/50 text-center py-8">No notes yet</p>
|
|
) : (
|
|
<div className="space-y-2 max-h-[400px] overflow-y-auto">
|
|
{notes.map(note => (
|
|
<div key={note.id} className="p-3 bg-white/5 rounded-lg border border-white/10">
|
|
<p className="text-sm text-white/80">{note.content}</p>
|
|
<p className="text-xs text-white/50 mt-2">
|
|
{new Date(note.date).toLocaleDateString()} • {new Date(note.date).toLocaleTimeString()}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|