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:
+191
-23
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user