Add WhaleTrades tab + Trading Tools (Calculator, Alerts, Notes)
This commit is contained in:
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { TradingChart } from './TradingChart'
|
import { TradingChart } from './TradingChart'
|
||||||
|
import { TradingTools } from './TradingTools'
|
||||||
|
|
||||||
type TradingTab = 'research' | 'strategies' | 'execution' | 'journal'
|
type TradingTab = 'research' | 'strategies' | 'execution' | 'journal' | 'whaletrades' | 'tools'
|
||||||
|
|
||||||
interface Trader {
|
interface Trader {
|
||||||
id: string
|
id: string
|
||||||
@@ -273,6 +274,8 @@ export function TradingPanel() {
|
|||||||
{ id: 'strategies', label: '🎯 Strategies', count: traders.filter(t => t.status === 'active').length },
|
{ id: 'strategies', label: '🎯 Strategies', count: traders.filter(t => t.status === 'active').length },
|
||||||
{ id: 'execution', label: '⚡ Execution', count: trades.filter(t => t.status === 'open').length },
|
{ id: 'execution', label: '⚡ Execution', count: trades.filter(t => t.status === 'open').length },
|
||||||
{ id: 'journal', label: '📔 Journal', count: trades.length },
|
{ id: 'journal', label: '📔 Journal', count: trades.length },
|
||||||
|
{ id: 'whaletrades', label: '🐋 WhaleTrades', count: 0 },
|
||||||
|
{ id: 'tools', label: '🧮 Tools', count: 3 },
|
||||||
]
|
]
|
||||||
|
|
||||||
const filteredTrades = trades.filter(t => {
|
const filteredTrades = trades.filter(t => {
|
||||||
@@ -557,6 +560,25 @@ export function TradingPanel() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* WhaleTrades Tab */}
|
||||||
|
{activeTab === 'whaletrades' && (
|
||||||
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
||||||
|
<h3 className="text-lg font-bold mb-4">🐋 WhaleTrades Tools</h3>
|
||||||
|
<p className="text-white/60 mb-4">Professional trading tools embedded from WhaleTrades.io</p>
|
||||||
|
|
||||||
|
<iframe
|
||||||
|
src="https://whaletrades.io/"
|
||||||
|
className="w-full h-[600px] rounded-lg border border-white/10"
|
||||||
|
title="WhaleTrades"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tools Tab */}
|
||||||
|
{activeTab === 'tools' && (
|
||||||
|
<TradingTools />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Full Analysis Modal */}
|
{/* Full Analysis Modal */}
|
||||||
{showFullAnalysis && (
|
{showFullAnalysis && (
|
||||||
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export function TradingTools() {
|
||||||
|
const [activeTool, setActiveTool] = useState<'calculator' | 'alerts' | 'notes'>('calculator')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Tool Navigation */}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{[
|
||||||
|
{ id: 'calculator', label: '🧮 Position Calculator', count: 0 },
|
||||||
|
{ id: 'alerts', label: '🔔 Trade Alerts', count: 0 },
|
||||||
|
{ id: 'notes', label: '📝 Trade 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>
|
||||||
|
|
||||||
|
{/* Position Calculator */}
|
||||||
|
{activeTool === 'calculator' && <PositionCalculator />}
|
||||||
|
|
||||||
|
{/* Trade Alerts */}
|
||||||
|
{activeTool === 'alerts' && <TradeAlerts />}
|
||||||
|
|
||||||
|
{/* Trade Notes */}
|
||||||
|
{activeTool === 'notes' && <TradeNotes />}
|
||||||
|
</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 entry = parseFloat(form.entryPrice) || 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 // 2:1
|
||||||
|
const potentialLoss = riskAmount
|
||||||
|
|
||||||
|
return {
|
||||||
|
riskAmount,
|
||||||
|
positionSize,
|
||||||
|
margin,
|
||||||
|
potentialProfit,
|
||||||
|
potentialLoss,
|
||||||
|
rr: '2:1',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
<label className="block text-sm text-white/70 mb-1">Entry Price</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={form.entryPrice}
|
||||||
|
onChange={e => setForm({...form, entryPrice: e.target.value})}
|
||||||
|
placeholder="0.00"
|
||||||
|
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">Direction</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setForm({...form, direction: 'long'})}
|
||||||
|
className={`flex-1 py-2 rounded-lg ${
|
||||||
|
form.direction === 'long' ? 'bg-green-500' : 'bg-white/10'
|
||||||
|
} text-white`}
|
||||||
|
>
|
||||||
|
LONG
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setForm({...form, direction: 'short'})}
|
||||||
|
className={`flex-1 py-2 rounded-lg ${
|
||||||
|
form.direction === 'short' ? 'bg-red-500' : 'bg-white/10'
|
||||||
|
} text-white`}
|
||||||
|
>
|
||||||
|
SHORT
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Results */}
|
||||||
|
<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, tradeId?: 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>
|
||||||
|
|
||||||
|
{/* Add Note */}
|
||||||
|
<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 List */}
|
||||||
|
{notes.length === 0 ? (
|
||||||
|
<p className="text-white/50 text-center py-8">No notes yet. Start journaling your trades!</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1771867021116",
|
||||||
|
"trader": "🤖 Thoth (AI Agent)",
|
||||||
|
"pair": "BTC/USD",
|
||||||
|
"direction": "long",
|
||||||
|
"entryPrice": 18,
|
||||||
|
"status": "open",
|
||||||
|
"isDemo": true,
|
||||||
|
"openedAt": "2026-02-23T17:17:01.116Z",
|
||||||
|
"setup": "🤖 Thoth (AI Agent) setup",
|
||||||
|
"timeframe": "4H",
|
||||||
|
"reason": "",
|
||||||
|
"notes": "",
|
||||||
|
"traderStyle": "thoth"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user