Reorganize UI for external trading workflow with manual trade logging

- Restructure tabs to analysis-focused workflow:
  * Analysis Hub: AI analysis, risk management, manual trade logger
  * Daily Prep: Market summary, alerts, checklist, news, trading plan
  * Journal & Review: Trading journal, habit tracker, advanced analytics
  * Live Charts: Technical analysis with streaming charts

- Add ManualTradeLogger component for logging trades from MT5/TradingView/cTrader
- Remove execution-focused components (TradeControls, PortfolioTracker)
- Update XAU/USD price to realistic ,084.99
- Add indicator preferences and AI plan service
- Add comprehensive documentation on decision coverage and implementation
This commit is contained in:
Krikorios
2025-11-16 07:50:00 +02:00
parent 73a26ea9b7
commit b5e2b02cb8
20 changed files with 4347 additions and 29 deletions
+191 -23
View File
@@ -2,8 +2,6 @@ import { useEffect, useState } from 'react'
import LiveMarketPanel from './components/LiveMarketPanel'
import MultiChartSSEPanel from './components/MultiChartSSEPanel'
import AccountPositionsPanel from './components/AccountPositionsPanel'
import EquityPerformancePanel from './components/EquityPerformancePanel'
import DecisionLogPanel from './components/DecisionLogPanel'
import SettingsPanel from './components/SettingsPanel'
import PromptTemplatesPanel from './components/PromptTemplatesPanel'
import { statusApi } from './services/api'
@@ -14,6 +12,17 @@ import UserProfileSetup from './components/UserProfileSetup'
import HabitTracker from './components/HabitTracker'
import DailyChecklistPanel from './components/DailyChecklistPanel'
// Analysis & Decision Components
import AIAnalysisPanel from './components/AIAnalysisPanel'
import DailyTradingPlan from './components/DailyTradingPlan'
import RiskManagement from './components/RiskManagement'
import TradingJournal from './components/TradingJournal'
import DailyMarketSummary from './components/DailyMarketSummary'
import NewsFeed from './components/NewsFeed'
import AlertsPanel from './components/AlertsPanel'
import AdvancedAnalytics from './components/AdvancedAnalytics'
import ManualTradeLogger from './components/ManualTradeLogger'
function Tabs({ tabs, active, onChange }: { tabs: string[]; active: string; onChange: (t: string) => void }) {
return (
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
@@ -27,9 +36,15 @@ function Tabs({ tabs, active, onChange }: { tabs: string[]; active: string; onCh
}
export default function App() {
const [activeTab, setActiveTab] = useState<'Live' | 'Account' | 'Equity' | 'Decisions' | 'Settings' | 'Prompts' | 'Daily Helper'>('Live')
const [activeTab, setActiveTab] = useState<'Analysis Hub' | 'Daily Prep' | 'Journal & Review' | 'Live Charts' | 'Account' | 'Settings' | 'Prompts'>('Analysis Hub')
const [backendStatus, setBackendStatus] = useState<any>(null)
const [showProfileSetup, setShowProfileSetup] = useState(false)
// Trading state for logged trades
const [loggedTrades, setLoggedTrades] = useState<any[]>([])
const [currentPrice, setCurrentPrice] = useState<number>(4084.99)
const [aiAnalysis, setAiAnalysis] = useState<any>(null)
const [isAnalyzing, setIsAnalyzing] = useState(false)
useEffect(() => {
let mounted = true
@@ -43,8 +58,55 @@ export default function App() {
})()
return () => { mounted = false }
}, [])
// Simulate price updates (in real app, this would come from WebSocket/SSE)
useEffect(() => {
const interval = setInterval(() => {
setCurrentPrice(prev => {
const change = (Math.random() - 0.5) * 8 // Realistic tick size for gold at ~$4000 level
return Number((prev + change).toFixed(2))
})
}, 3000)
return () => clearInterval(interval)
}, [])
const tabs = ['Live', 'Account', 'Equity', 'Decisions', 'Daily Helper', 'Settings', 'Prompts']
const tabs = ['Analysis Hub', 'Daily Prep', 'Journal & Review', 'Live Charts', 'Account', 'Settings', 'Prompts']
// Load logged trades from localStorage
useEffect(() => {
const stored = localStorage.getItem('logged-trades')
if (stored) {
try {
setLoggedTrades(JSON.parse(stored))
} catch (e) {
console.error('Failed to load logged trades:', e)
}
}
}, [])
// Handle new trade logged
const handleTradeLogged = (trade: any) => {
setLoggedTrades([...loggedTrades, trade])
}
const handleAIAnalysis = async () => {
setIsAnalyzing(true)
// Simulate AI analysis
setTimeout(() => {
const mockAnalysis = {
recommendation: Math.random() > 0.5 ? 'BUY' : 'SELL',
confidence: Math.floor(Math.random() * 30 + 60),
riskLevel: 'MEDIUM',
reasoning: 'Based on technical analysis and market sentiment, the current market conditions suggest...',
supportResistance: {
support: [currentPrice - 20, currentPrice - 40],
resistance: [currentPrice + 20, currentPrice + 40]
}
}
setAiAnalysis(mockAnalysis)
setIsAnalyzing(false)
}, 2000)
}
return (
<div className="min-h-screen bg-dark-bg p-6">
@@ -70,33 +132,139 @@ export default function App() {
<Tabs tabs={tabs} active={activeTab} onChange={(t) => setActiveTab(t as any)} />
{activeTab === 'Live' && (
{/* ANALYSIS HUB - Pre-Trade Analysis & Trade Logging */}
{activeTab === 'Analysis Hub' && (
<div style={{ display: 'grid', gap: 16 }}>
<div className="bg-blue-500/10 border border-blue-500/30 rounded-lg p-4">
<h3 className="text-lg font-semibold mb-2">🎯 Analysis Hub</h3>
<p className="text-sm text-gray-300">
<strong>Workflow:</strong> Analyze Plan on platform (MT5/TradingView) Execute there Log trade here Monitor & Journal
</p>
</div>
{/* Analysis Tools */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(450px, 1fr))', gap: 16 }}>
{/* AI Analysis - Get recommendation BEFORE trading */}
<div>
<AIAnalysisPanel analysis={aiAnalysis} isLoading={isAnalyzing} />
<button
onClick={handleAIAnalysis}
className="btn-primary w-full mt-4"
disabled={isAnalyzing}
>
{isAnalyzing ? 'Analyzing...' : '🤖 Get AI Analysis'}
</button>
</div>
{/* Risk Calculator - Calculate position size BEFORE trading */}
<RiskManagement
currentPrice={currentPrice}
cash={100000}
position={null}
trades={loggedTrades}
/>
</div>
{/* Trade Logger - Log trades from external platform */}
<ManualTradeLogger onTradeLogged={handleTradeLogged} />
{/* Current Price Reference */}
<div className="card">
<h3 className="text-lg font-semibold mb-4">📊 Current Market Price</h3>
<div className="grid grid-cols-3 gap-4">
<div className="bg-dark-bg rounded-lg p-4 text-center">
<div className="text-sm text-gray-400 mb-1">XAU/USD</div>
<div className="text-3xl font-bold text-gold-500">${currentPrice.toFixed(2)}</div>
<div className="text-xs text-green-500 mt-1">Live Price</div>
</div>
<div className="bg-dark-bg rounded-lg p-4">
<div className="text-xs text-gray-400">24h High</div>
<div className="text-xl font-semibold text-green-500">${(currentPrice + 15).toFixed(2)}</div>
</div>
<div className="bg-dark-bg rounded-lg p-4">
<div className="text-xs text-gray-400">24h Low</div>
<div className="text-xl font-semibold text-red-500">${(currentPrice - 12).toFixed(2)}</div>
</div>
</div>
</div>
</div>
)}
{/* DAILY PREP - Morning Routine */}
{activeTab === 'Daily Prep' && (
<div style={{ display: 'grid', gap: 16 }}>
<div className="bg-green-500/10 border border-green-500/30 rounded-lg p-4">
<h3 className="text-lg font-semibold mb-2">🌅 Daily Preparation</h3>
<p className="text-sm text-gray-300">
Start your day here: Review market, check news, create trading plan
</p>
</div>
{/* Pre-Market Section */}
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 16 }}>
<DailyMarketSummary currentPrice={currentPrice} />
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<button
onClick={() => setShowProfileSetup(true)}
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded transition-colors"
>
Setup Profile
</button>
<AlertsPanel />
</div>
</div>
{/* Daily Workflow */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))', gap: 16 }}>
<DailyChecklistPanel checklistType="morning" />
<NewsFeed />
</div>
{/* Trading Plan */}
<DailyTradingPlan currentPrice={currentPrice} />
</div>
)}
{/* JOURNAL & REVIEW - Post-Trade Analysis */}
{activeTab === 'Journal & Review' && (
<div style={{ display: 'grid', gap: 16 }}>
<div className="bg-purple-500/10 border border-purple-500/30 rounded-lg p-4">
<h3 className="text-lg font-semibold mb-2">📖 Journal & Review</h3>
<p className="text-sm text-gray-300">
Document trades, track performance, identify patterns, improve strategy
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
<TradingJournal />
<HabitTracker />
</div>
<AdvancedAnalytics
portfolio={{ cash: 100000, initialCapital: 100000, totalValue: 100000, totalPnl: 0, totalPnlPercent: 0, position: null, trades: loggedTrades }}
trades={loggedTrades}
/>
</div>
)}
{/* LIVE CHARTS - Technical Analysis */}
{activeTab === 'Live Charts' && (
<div style={{ display: 'grid', gap: 16 }}>
<div className="bg-orange-500/10 border border-orange-500/30 rounded-lg p-4">
<h3 className="text-lg font-semibold mb-2">📈 Live Charts</h3>
<p className="text-sm text-gray-300">
Technical analysis with live streaming charts
</p>
</div>
<LiveMarketPanel />
<MultiChartSSEPanel />
</div>
)}
{activeTab === 'Account' && <AccountPositionsPanel />}
{activeTab === 'Equity' && <EquityPerformancePanel />}
{activeTab === 'Decisions' && <DecisionLogPanel />}
{activeTab === 'Daily Helper' && (
<div style={{ display: 'grid', gap: 16, gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))' }}>
<div>
<button
onClick={() => setShowProfileSetup(true)}
className="mb-4 bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded transition-colors"
>
Setup Profile
</button>
<DailyChecklistPanel checklistType="morning" />
</div>
<div>
<HabitTracker />
</div>
</div>
)}
{activeTab === 'Settings' && <SettingsPanel />}
{activeTab === 'Prompts' && <PromptTemplatesPanel />}
{showProfileSetup && (
<UserProfileSetup
+58 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { Target, DollarSign, TrendingUp, TrendingDown, AlertTriangle, Save, Edit2 } from 'lucide-react';
import { Target, DollarSign, TrendingUp, TrendingDown, AlertTriangle, Save, Edit2, Sparkles } from 'lucide-react';
import { aiApi } from '../services/api';
interface TradingPlan {
date: string;
@@ -27,6 +28,7 @@ interface DailyTradingPlanProps {
export default function DailyTradingPlan({ currentPrice, onPlanUpdate }: DailyTradingPlanProps) {
const [isEditing, setIsEditing] = useState(false);
const [generatingAI, setGeneratingAI] = useState(false);
const [plan, setPlan] = useState<TradingPlan>(() => {
const stored = localStorage.getItem('daily-trading-plan');
const today = new Date().toDateString();
@@ -101,6 +103,53 @@ export default function DailyTradingPlan({ currentPrice, onPlanUpdate }: DailyTr
}
};
const handleGenerateWithAI = async () => {
if (!confirm('Generate a trading plan using AI? This will use your indicator preferences.')) {
return;
}
setGeneratingAI(true);
try {
const aiPlan = await aiApi.generateTradingPlan({
current_price: currentPrice,
risk_tolerance: 'moderate',
use_indicator_preferences: true,
});
// Map AI response to our plan structure
const today = new Date().toDateString();
setPlan({
date: today,
bias: aiPlan.market_bias,
dailyTarget: aiPlan.daily_target || 500,
maxLoss: aiPlan.max_loss || 250,
entryZone: {
min: aiPlan.entry_zone_min || currentPrice - 10,
max: aiPlan.entry_zone_max || currentPrice + 10,
},
targetPrice: aiPlan.target_price || currentPrice + 20,
stopLoss: aiPlan.stop_loss || currentPrice - 15,
keyLevels: {
support: aiPlan.support_levels || [],
resistance: aiPlan.resistance_levels || [],
},
tradingNotes: aiPlan.trading_notes || '',
maxTrades: aiPlan.max_trades || 3,
actualTrades: 0,
actualPnL: 0,
planFollowed: true,
});
setIsEditing(true);
alert(`AI Plan Generated!\n\nBias: ${aiPlan.market_bias}\nConfidence: ${aiPlan.confidence}%\n\nYou can now review and edit the plan.`);
} catch (error) {
console.error('Failed to generate AI plan:', error);
alert('Failed to generate AI plan. Please try again or create a manual plan.');
} finally {
setGeneratingAI(false);
}
};
const addSupport = () => {
setPlan(prev => ({
...prev,
@@ -186,6 +235,14 @@ export default function DailyTradingPlan({ currentPrice, onPlanUpdate }: DailyTr
<div className="flex gap-2">
{!isEditing ? (
<>
<button
onClick={handleGenerateWithAI}
disabled={generatingAI}
className="btn-primary text-sm flex items-center gap-2 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 disabled:from-gray-700 disabled:to-gray-700"
>
<Sparkles className="w-4 h-4" />
{generatingAI ? 'Generating...' : 'AI Plan'}
</button>
<button
onClick={() => setIsEditing(true)}
className="btn-secondary text-sm flex items-center gap-2"
@@ -0,0 +1,296 @@
import { useState, useEffect } from 'react';
import { TrendingUp, Star, Save, Plus, Trash2, Info } from 'lucide-react';
import { settingsApi } from '../services/api';
interface IndicatorPreference {
id?: number;
indicator_name: string;
enabled: boolean;
parameters?: any;
priority: number;
notes?: string;
}
// Available indicators in the system
const AVAILABLE_INDICATORS = [
{ name: 'SMA', label: 'Simple Moving Average', description: 'Smooths price data to identify trends' },
{ name: 'EMA', label: 'Exponential Moving Average', description: 'More weight to recent prices' },
{ name: 'RSI', label: 'Relative Strength Index', description: 'Momentum oscillator (0-100)' },
{ name: 'MACD', label: 'Moving Average Convergence Divergence', description: 'Trend-following momentum indicator' },
{ name: 'BB', label: 'Bollinger Bands', description: 'Volatility bands around price' },
{ name: 'ATR', label: 'Average True Range', description: 'Measures market volatility' },
{ name: 'Stochastic', label: 'Stochastic Oscillator', description: 'Momentum indicator comparing price to range' },
{ name: 'Fibonacci', label: 'Fibonacci Retracement', description: 'Support/resistance levels' },
{ name: 'VWAP', label: 'Volume Weighted Average Price', description: 'Average price weighted by volume' },
{ name: 'Pivot', label: 'Pivot Points', description: 'Key support and resistance levels' },
];
export default function IndicatorPreferences() {
const [preferences, setPreferences] = useState<IndicatorPreference[]>([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
useEffect(() => {
loadPreferences();
}, []);
const loadPreferences = async () => {
try {
setLoading(true);
const response = await settingsApi.getIndicatorPreferences();
setPreferences(response.preferences || []);
} catch (error) {
console.error('Failed to load indicator preferences:', error);
setMessage({ type: 'error', text: 'Failed to load preferences' });
} finally {
setLoading(false);
}
};
const addIndicator = (indicatorName: string) => {
const existing = preferences.find(p => p.indicator_name === indicatorName);
if (existing) {
setMessage({ type: 'error', text: 'Indicator already added' });
return;
}
const newPref: IndicatorPreference = {
indicator_name: indicatorName,
enabled: true,
priority: 1,
parameters: {},
notes: '',
};
setPreferences([...preferences, newPref]);
};
const removeIndicator = (index: number) => {
const pref = preferences[index];
if (pref.id) {
// Delete from backend if it has an ID
settingsApi.deleteIndicatorPreference(pref.id).catch(console.error);
}
setPreferences(preferences.filter((_, i) => i !== index));
};
const updatePreference = (index: number, updates: Partial<IndicatorPreference>) => {
const updated = [...preferences];
updated[index] = { ...updated[index], ...updates };
setPreferences(updated);
};
const savePreferences = async () => {
try {
setSaving(true);
setMessage(null);
// Separate new preferences from existing ones
const newPrefs = preferences.filter(p => !p.id);
const existingPrefs = preferences.filter(p => p.id);
// Create new preferences in bulk
if (newPrefs.length > 0) {
await settingsApi.createBulkIndicatorPreferences(newPrefs);
}
// Update existing preferences
for (const pref of existingPrefs) {
if (pref.id) {
await settingsApi.updateIndicatorPreference(pref.id, {
enabled: pref.enabled,
parameters: pref.parameters,
priority: pref.priority,
notes: pref.notes,
});
}
}
setMessage({ type: 'success', text: 'Preferences saved successfully!' });
await loadPreferences(); // Reload to get IDs for new items
} catch (error: any) {
console.error('Failed to save preferences:', error);
setMessage({ type: 'error', text: error.response?.data?.detail || 'Failed to save preferences' });
} finally {
setSaving(false);
}
};
const getUnusedIndicators = () => {
return AVAILABLE_INDICATORS.filter(
ind => !preferences.some(p => p.indicator_name === ind.name)
);
};
if (loading) {
return (
<div className="bg-dark-panel rounded-xl border border-gray-800 p-6">
<div className="flex items-center justify-center py-8">
<div className="text-gray-400">Loading preferences...</div>
</div>
</div>
);
}
return (
<div className="bg-dark-panel rounded-xl border border-gray-800 p-6">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-500/20 rounded-lg">
<TrendingUp className="w-5 h-5 text-blue-500" />
</div>
<div>
<h3 className="text-lg font-semibold">Indicator Preferences</h3>
<p className="text-sm text-gray-400">
Select indicators to use in AI trading plan generation
</p>
</div>
</div>
<button
onClick={savePreferences}
disabled={saving}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded-lg transition-colors"
>
<Save className="w-4 h-4" />
{saving ? 'Saving...' : 'Save'}
</button>
</div>
{/* Message */}
{message && (
<div
className={`mb-4 p-3 rounded-lg ${
message.type === 'success'
? 'bg-green-500/10 border border-green-500/30 text-green-500'
: 'bg-red-500/10 border border-red-500/30 text-red-500'
}`}
>
{message.text}
</div>
)}
{/* Info Box */}
<div className="mb-6 p-4 bg-blue-500/10 border border-blue-500/30 rounded-lg flex gap-3">
<Info className="w-5 h-5 text-blue-500 flex-shrink-0 mt-0.5" />
<div className="text-sm text-blue-200">
<p className="font-semibold mb-1">How it works:</p>
<ul className="list-disc list-inside space-y-1 text-blue-300">
<li>Select your preferred indicators for analysis</li>
<li>Set priority (higher = more important in AI analysis)</li>
<li>AI will focus on these indicators when generating trading plans</li>
</ul>
</div>
</div>
{/* Selected Indicators */}
<div className="space-y-3 mb-6">
{preferences.length === 0 ? (
<div className="text-center py-8 text-gray-400">
<TrendingUp className="w-12 h-12 mx-auto mb-3 opacity-50" />
<p>No indicators selected</p>
<p className="text-sm mt-1">Add indicators below to get started</p>
</div>
) : (
preferences.map((pref, index) => {
const indicatorInfo = AVAILABLE_INDICATORS.find(i => i.name === pref.indicator_name);
return (
<div
key={index}
className="p-4 bg-dark-bg border border-gray-700 rounded-lg"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-semibold">{indicatorInfo?.label || pref.indicator_name}</h4>
<button
onClick={() => updatePreference(index, { enabled: !pref.enabled })}
className={`text-xs px-2 py-1 rounded ${
pref.enabled
? 'bg-green-500/20 text-green-500'
: 'bg-gray-700 text-gray-400'
}`}
>
{pref.enabled ? 'Enabled' : 'Disabled'}
</button>
</div>
<p className="text-xs text-gray-400">{indicatorInfo?.description}</p>
</div>
<button
onClick={() => removeIndicator(index)}
className="p-2 hover:bg-red-500/20 rounded-lg text-red-500 transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs text-gray-400 mb-1">
Priority (1-10)
</label>
<div className="flex items-center gap-2">
<input
type="range"
min="1"
max="10"
value={pref.priority}
onChange={(e) =>
updatePreference(index, { priority: parseInt(e.target.value) })
}
className="flex-1"
/>
<div className="flex gap-0.5">
{[...Array(pref.priority)].map((_, i) => (
<Star key={i} className="w-3 h-3 text-yellow-500 fill-yellow-500" />
))}
</div>
</div>
</div>
<div>
<label className="block text-xs text-gray-400 mb-1">Notes</label>
<input
type="text"
value={pref.notes || ''}
onChange={(e) => updatePreference(index, { notes: e.target.value })}
placeholder="Why this indicator?"
className="w-full px-3 py-1.5 bg-dark-panel border border-gray-700 rounded text-sm"
/>
</div>
</div>
</div>
);
})
)}
</div>
{/* Add Indicator Section */}
<div className="border-t border-gray-700 pt-6">
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<Plus className="w-4 h-4" />
Add Indicators
</h4>
<div className="grid grid-cols-2 gap-2">
{getUnusedIndicators().map((indicator) => (
<button
key={indicator.name}
onClick={() => addIndicator(indicator.name)}
className="p-3 bg-dark-bg hover:bg-dark-hover border border-gray-700 hover:border-blue-500/50 rounded-lg text-left transition-all group"
>
<div className="font-medium text-sm group-hover:text-blue-500 transition-colors">
{indicator.label}
</div>
<div className="text-xs text-gray-400 mt-0.5">{indicator.description}</div>
</button>
))}
</div>
{getUnusedIndicators().length === 0 && (
<p className="text-sm text-gray-400 text-center py-4">
All indicators have been added
</p>
)}
</div>
</div>
);
}
@@ -0,0 +1,269 @@
import { useState } from 'react';
import { Plus, TrendingUp, TrendingDown, Save, X } from 'lucide-react';
interface ManualTradeLoggerProps {
onTradeLogged?: (trade: any) => void;
}
export default function ManualTradeLogger({ onTradeLogged }: ManualTradeLoggerProps) {
const [isOpen, setIsOpen] = useState(false);
const [trade, setTrade] = useState({
symbol: 'XAU/USD',
action: 'BUY' as 'BUY' | 'SELL',
entryPrice: '',
quantity: '',
stopLoss: '',
takeProfit: '',
entryTime: new Date().toISOString().slice(0, 16),
platform: 'MT5',
notes: ''
});
const handleSubmit = () => {
if (!trade.entryPrice || !trade.quantity) {
alert('Please enter at least entry price and quantity');
return;
}
const loggedTrade = {
...trade,
id: Date.now(),
entryPrice: parseFloat(trade.entryPrice),
quantity: parseFloat(trade.quantity),
stopLoss: trade.stopLoss ? parseFloat(trade.stopLoss) : null,
takeProfit: trade.takeProfit ? parseFloat(trade.takeProfit) : null,
status: 'OPEN',
loggedAt: new Date().toISOString()
};
// Save to localStorage
const existingTrades = JSON.parse(localStorage.getItem('logged-trades') || '[]');
existingTrades.push(loggedTrade);
localStorage.setItem('logged-trades', JSON.stringify(existingTrades));
if (onTradeLogged) {
onTradeLogged(loggedTrade);
}
// Reset form
setTrade({
symbol: 'XAU/USD',
action: 'BUY',
entryPrice: '',
quantity: '',
stopLoss: '',
takeProfit: '',
entryTime: new Date().toISOString().slice(0, 16),
platform: 'MT5',
notes: ''
});
setIsOpen(false);
alert('Trade logged successfully!');
};
return (
<div className="card">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">📝 Log External Trade</h3>
<button
onClick={() => setIsOpen(!isOpen)}
className="btn-primary flex items-center gap-2"
>
<Plus className="w-4 h-4" />
Log Trade
</button>
</div>
{isOpen && (
<div className="bg-dark-bg rounded-lg p-6 border-2 border-blue-500">
<div className="flex items-center justify-between mb-4">
<h4 className="font-semibold text-lg">Log Trade from External Platform</h4>
<button onClick={() => setIsOpen(false)} className="text-gray-400 hover:text-white">
<X className="w-5 h-5" />
</button>
</div>
<div className="grid grid-cols-2 gap-4">
{/* Symbol */}
<div>
<label className="block text-sm text-gray-400 mb-2">Symbol</label>
<select
value={trade.symbol}
onChange={(e) => setTrade({ ...trade, symbol: e.target.value })}
className="input w-full"
>
<option value="XAU/USD">XAU/USD (Gold)</option>
<option value="EUR/USD">EUR/USD</option>
<option value="GBP/USD">GBP/USD</option>
<option value="BTC/USD">BTC/USD</option>
<option value="Other">Other</option>
</select>
</div>
{/* Action */}
<div>
<label className="block text-sm text-gray-400 mb-2">Action</label>
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => setTrade({ ...trade, action: 'BUY' })}
className={`py-2 px-4 rounded font-medium ${
trade.action === 'BUY'
? 'bg-green-500 text-white'
: 'bg-dark-surface text-gray-400'
}`}
>
<TrendingUp className="w-4 h-4 inline mr-1" />
BUY
</button>
<button
onClick={() => setTrade({ ...trade, action: 'SELL' })}
className={`py-2 px-4 rounded font-medium ${
trade.action === 'SELL'
? 'bg-red-500 text-white'
: 'bg-dark-surface text-gray-400'
}`}
>
<TrendingDown className="w-4 h-4 inline mr-1" />
SELL
</button>
</div>
</div>
{/* Entry Price */}
<div>
<label className="block text-sm text-gray-400 mb-2">Entry Price *</label>
<input
type="number"
step="0.01"
value={trade.entryPrice}
onChange={(e) => setTrade({ ...trade, entryPrice: e.target.value })}
className="input w-full"
placeholder="2030.50"
required
/>
</div>
{/* Quantity */}
<div>
<label className="block text-sm text-gray-400 mb-2">Quantity (lots/oz) *</label>
<input
type="number"
step="0.01"
value={trade.quantity}
onChange={(e) => setTrade({ ...trade, quantity: e.target.value })}
className="input w-full"
placeholder="1.0"
required
/>
</div>
{/* Stop Loss */}
<div>
<label className="block text-sm text-gray-400 mb-2">Stop Loss</label>
<input
type="number"
step="0.01"
value={trade.stopLoss}
onChange={(e) => setTrade({ ...trade, stopLoss: e.target.value })}
className="input w-full"
placeholder="2020.00"
/>
</div>
{/* Take Profit */}
<div>
<label className="block text-sm text-gray-400 mb-2">Take Profit</label>
<input
type="number"
step="0.01"
value={trade.takeProfit}
onChange={(e) => setTrade({ ...trade, takeProfit: e.target.value })}
className="input w-full"
placeholder="2050.00"
/>
</div>
{/* Entry Time */}
<div>
<label className="block text-sm text-gray-400 mb-2">Entry Time</label>
<input
type="datetime-local"
value={trade.entryTime}
onChange={(e) => setTrade({ ...trade, entryTime: e.target.value })}
className="input w-full"
/>
</div>
{/* Platform */}
<div>
<label className="block text-sm text-gray-400 mb-2">Platform</label>
<select
value={trade.platform}
onChange={(e) => setTrade({ ...trade, platform: e.target.value })}
className="input w-full"
>
<option value="MT4">MetaTrader 4</option>
<option value="MT5">MetaTrader 5</option>
<option value="TradingView">TradingView</option>
<option value="cTrader">cTrader</option>
<option value="Broker Platform">Broker Platform</option>
<option value="Other">Other</option>
</select>
</div>
{/* Notes */}
<div className="col-span-2">
<label className="block text-sm text-gray-400 mb-2">Trade Notes</label>
<textarea
value={trade.notes}
onChange={(e) => setTrade({ ...trade, notes: e.target.value })}
className="input w-full"
rows={3}
placeholder="Why did you enter this trade? What's your plan?"
/>
</div>
</div>
<div className="flex gap-3 mt-6">
<button
onClick={handleSubmit}
className="btn-primary flex items-center gap-2 flex-1"
>
<Save className="w-4 h-4" />
Log Trade
</button>
<button
onClick={() => setIsOpen(false)}
className="btn bg-dark-surface text-gray-300 hover:bg-dark-hover"
>
Cancel
</button>
</div>
</div>
)}
{/* Quick Stats */}
<div className="mt-4 grid grid-cols-3 gap-3">
<div className="bg-dark-bg rounded p-3">
<div className="text-xs text-gray-400">Today's Trades</div>
<div className="text-xl font-bold">0</div>
</div>
<div className="bg-dark-bg rounded p-3">
<div className="text-xs text-gray-400">Open Positions</div>
<div className="text-xl font-bold text-blue-500">0</div>
</div>
<div className="bg-dark-bg rounded p-3">
<div className="text-xs text-gray-400">Win Rate</div>
<div className="text-xl font-bold text-green-500">--</div>
</div>
</div>
<div className="mt-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded">
<p className="text-sm text-blue-300">
💡 <strong>Tip:</strong> Log your trades from MT5/TradingView/Broker platform here for
continuous analysis and journaling.
</p>
</div>
</div>
);
}
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'
import { settingsApi } from '@/services/api'
import IndicatorPreferences from './IndicatorPreferences'
export default function SettingsPanel() {
const [models, setModels] = useState<any>({})
@@ -60,6 +61,10 @@ export default function SettingsPanel() {
<button className="btn-primary" onClick={saveExchanges} disabled={saving}>{saving ? 'Saving…' : 'Save Exchanges'}</button>
</div>
</section>
<section>
<IndicatorPreferences />
</section>
</div>
)}
</div>
+71
View File
@@ -96,6 +96,35 @@ export const aiApi = {
});
return response.data;
},
generateTradingPlan: async (data: {
current_price: number;
user_capital?: number;
risk_tolerance?: string;
use_indicator_preferences?: boolean;
price_data?: any[];
indicators_data?: any;
}): Promise<any> => {
const response = await api.post('/ai/generate-plan', data);
return response.data;
},
getPlanHistory: async (userId?: string, limit: number = 10): Promise<any[]> => {
const response = await api.get('/ai/plans/history', {
params: { user_id: userId, limit },
});
return response.data;
},
submitPlanFeedback: async (data: {
plan_id: number;
accepted: boolean;
modified?: boolean;
feedback?: string;
}): Promise<any> => {
const response = await api.post('/ai/plans/feedback', data);
return response.data;
},
};
export const newsApi = {
@@ -147,6 +176,48 @@ export const settingsApi = {
putModels: async (patch: any): Promise<any> => (await api.put('/settings/models', patch)).data,
getExchanges: async (): Promise<any> => (await api.get('/settings/exchanges')).data,
putExchanges: async (patch: any): Promise<any> => (await api.put('/settings/exchanges', patch)).data,
// Indicator Preferences
getIndicatorPreferences: async (userId?: string, enabledOnly: boolean = false): Promise<any> => {
const response = await api.get('/settings/indicators/preferences', {
params: { user_id: userId, enabled_only: enabledOnly },
});
return response.data;
},
createIndicatorPreference: async (data: {
indicator_name: string;
enabled?: boolean;
parameters?: any;
priority?: number;
notes?: string;
}, userId?: string): Promise<any> => {
const response = await api.post('/settings/indicators/preferences', data, {
params: { user_id: userId },
});
return response.data;
},
updateIndicatorPreference: async (preferenceId: number, data: {
enabled?: boolean;
parameters?: any;
priority?: number;
notes?: string;
}): Promise<any> => {
const response = await api.put(`/settings/indicators/preferences/${preferenceId}`, data);
return response.data;
},
deleteIndicatorPreference: async (preferenceId: number): Promise<void> => {
await api.delete(`/settings/indicators/preferences/${preferenceId}`);
},
createBulkIndicatorPreferences: async (preferences: any[], userId?: string): Promise<any> => {
const response = await api.post('/settings/indicators/preferences/bulk', preferences, {
params: { user_id: userId },
});
return response.data;
},
}
export const promptsApi = {