mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
Feature: Add multi-language support and UI improvements
- Add language context and translation system (Chinese/English) - Enhance UI components with i18n support - Update AILearning, EquityChart, and CompetitionPage - Add language toggle in header - Improve user experience with localized text
This commit is contained in:
@@ -27,4 +27,3 @@ config.json
|
|||||||
# 决策日志
|
# 决策日志
|
||||||
decision_logs/
|
decision_logs/
|
||||||
coin_pool_cache/
|
coin_pool_cache/
|
||||||
nofx-auto
|
|
||||||
|
|||||||
+123
-38
@@ -4,6 +4,8 @@ import { api } from './lib/api';
|
|||||||
import { EquityChart } from './components/EquityChart';
|
import { EquityChart } from './components/EquityChart';
|
||||||
import { CompetitionPage } from './components/CompetitionPage';
|
import { CompetitionPage } from './components/CompetitionPage';
|
||||||
import AILearning from './components/AILearning';
|
import AILearning from './components/AILearning';
|
||||||
|
import { LanguageProvider, useLanguage } from './contexts/LanguageContext';
|
||||||
|
import { t, type Language } from './i18n/translations';
|
||||||
import type {
|
import type {
|
||||||
SystemStatus,
|
SystemStatus,
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
@@ -16,6 +18,7 @@ import type {
|
|||||||
type Page = 'competition' | 'trader';
|
type Page = 'competition' | 'trader';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const { language, setLanguage } = useLanguage();
|
||||||
const [currentPage, setCurrentPage] = useState<Page>('competition');
|
const [currentPage, setCurrentPage] = useState<Page>('competition');
|
||||||
const [selectedTraderId, setSelectedTraderId] = useState<string | undefined>();
|
const [selectedTraderId, setSelectedTraderId] = useState<string | undefined>();
|
||||||
const [lastUpdate, setLastUpdate] = useState<string>('--:--:--');
|
const [lastUpdate, setLastUpdate] = useState<string>('--:--:--');
|
||||||
@@ -106,14 +109,62 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
||||||
AI Trading Competition
|
{t('appTitle', language)}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs mono" style={{ color: '#848E9C' }}>
|
<p className="text-xs mono" style={{ color: '#848E9C' }}>
|
||||||
Qwen vs DeepSeek · Real-time
|
{t('subtitle', language)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
{/* GitHub Link */}
|
||||||
|
<a
|
||||||
|
href="https://github.com/tinkle-community/nofx"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center gap-2 px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||||
|
style={{ background: '#1E2329', color: '#848E9C', border: '1px solid #2B3139' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = '#2B3139';
|
||||||
|
e.currentTarget.style.color = '#EAECEF';
|
||||||
|
e.currentTarget.style.borderColor = '#F0B90B';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = '#1E2329';
|
||||||
|
e.currentTarget.style.color = '#848E9C';
|
||||||
|
e.currentTarget.style.borderColor = '#2B3139';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||||
|
</svg>
|
||||||
|
<span>GitHub</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Language Toggle */}
|
||||||
|
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setLanguage('zh')}
|
||||||
|
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
|
||||||
|
style={language === 'zh'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
中文
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setLanguage('en')}
|
||||||
|
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
|
||||||
|
style={language === 'en'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
EN
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Page Toggle */}
|
{/* Page Toggle */}
|
||||||
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||||
<button
|
<button
|
||||||
@@ -126,7 +177,7 @@ function App() {
|
|||||||
: { background: 'transparent', color: '#848E9C' }
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Competition
|
{t('competition', language)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentPage('trader')}
|
onClick={() => setCurrentPage('trader')}
|
||||||
@@ -136,7 +187,7 @@ function App() {
|
|||||||
: { background: 'transparent', color: '#848E9C' }
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Details
|
{t('details', language)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -170,7 +221,7 @@ function App() {
|
|||||||
style={{ background: status.is_running ? '#0ECB81' : '#F6465D' }}
|
style={{ background: status.is_running ? '#0ECB81' : '#F6465D' }}
|
||||||
/>
|
/>
|
||||||
<span className="font-semibold mono text-xs">
|
<span className="font-semibold mono text-xs">
|
||||||
{status.is_running ? 'RUNNING' : 'STOPPED'}
|
{t(status.is_running ? 'running' : 'stopped', language)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -192,6 +243,7 @@ function App() {
|
|||||||
decisions={decisions}
|
decisions={decisions}
|
||||||
stats={stats}
|
stats={stats}
|
||||||
lastUpdate={lastUpdate}
|
lastUpdate={lastUpdate}
|
||||||
|
language={language}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
@@ -199,8 +251,32 @@ function App() {
|
|||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="mt-16" style={{ borderTop: '1px solid #2B3139', background: '#181A20' }}>
|
<footer className="mt-16" style={{ borderTop: '1px solid #2B3139', background: '#181A20' }}>
|
||||||
<div className="max-w-[1920px] mx-auto px-6 py-6 text-center text-sm" style={{ color: '#5E6673' }}>
|
<div className="max-w-[1920px] mx-auto px-6 py-6 text-center text-sm" style={{ color: '#5E6673' }}>
|
||||||
<p>NOFX - AI Trading Competition System</p>
|
<p>{t('footerTitle', language)}</p>
|
||||||
<p className="mt-1">⚠️ Trading involves risk. Use at your own discretion.</p>
|
<p className="mt-1">{t('footerWarning', language)}</p>
|
||||||
|
<div className="mt-4 flex items-center justify-center gap-2">
|
||||||
|
<a
|
||||||
|
href="https://github.com/tinkle-community/nofx"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||||
|
style={{ background: '#1E2329', color: '#848E9C', border: '1px solid #2B3139' }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = '#2B3139';
|
||||||
|
e.currentTarget.style.color = '#EAECEF';
|
||||||
|
e.currentTarget.style.borderColor = '#F0B90B';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = '#1E2329';
|
||||||
|
e.currentTarget.style.color = '#848E9C';
|
||||||
|
e.currentTarget.style.borderColor = '#2B3139';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||||
|
</svg>
|
||||||
|
<span>Star on GitHub</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@@ -216,6 +292,7 @@ function TraderDetailsPage({
|
|||||||
decisions,
|
decisions,
|
||||||
stats,
|
stats,
|
||||||
lastUpdate,
|
lastUpdate,
|
||||||
|
language,
|
||||||
}: {
|
}: {
|
||||||
selectedTrader?: TraderInfo;
|
selectedTrader?: TraderInfo;
|
||||||
status?: SystemStatus;
|
status?: SystemStatus;
|
||||||
@@ -224,6 +301,7 @@ function TraderDetailsPage({
|
|||||||
decisions?: DecisionRecord[];
|
decisions?: DecisionRecord[];
|
||||||
stats?: Statistics;
|
stats?: Statistics;
|
||||||
lastUpdate: string;
|
lastUpdate: string;
|
||||||
|
language: Language;
|
||||||
}) {
|
}) {
|
||||||
if (!selectedTrader) {
|
if (!selectedTrader) {
|
||||||
return (
|
return (
|
||||||
@@ -290,26 +368,26 @@ function TraderDetailsPage({
|
|||||||
{/* Account Overview */}
|
{/* Account Overview */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Total Equity"
|
title={t('totalEquity', language)}
|
||||||
value={`${account?.total_equity.toFixed(2) || '0.00'} USDT`}
|
value={`${account?.total_equity.toFixed(2) || '0.00'} USDT`}
|
||||||
change={account?.total_pnl_pct || 0}
|
change={account?.total_pnl_pct || 0}
|
||||||
positive={account ? account.total_pnl > 0 : false}
|
positive={account ? account.total_pnl > 0 : false}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Available Balance"
|
title={t('availableBalance', language)}
|
||||||
value={`${account?.available_balance.toFixed(2) || '0.00'} USDT`}
|
value={`${account?.available_balance.toFixed(2) || '0.00'} USDT`}
|
||||||
subtitle={`${((account?.available_balance / account?.total_equity) * 100 || 0).toFixed(1)}% Free`}
|
subtitle={`${((account?.available_balance / account?.total_equity) * 100 || 0).toFixed(1)}% ${t('free', language)}`}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Total P&L"
|
title={t('totalPnL', language)}
|
||||||
value={`${account?.total_pnl >= 0 ? '+' : ''}${account?.total_pnl.toFixed(2) || '0.00'} USDT`}
|
value={`${account?.total_pnl >= 0 ? '+' : ''}${account?.total_pnl.toFixed(2) || '0.00'} USDT`}
|
||||||
change={account?.total_pnl_pct || 0}
|
change={account?.total_pnl_pct || 0}
|
||||||
positive={account ? account.total_pnl >= 0 : false}
|
positive={account ? account.total_pnl >= 0 : false}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Positions"
|
title={t('positions', language)}
|
||||||
value={`${account?.position_count || 0}`}
|
value={`${account?.position_count || 0}`}
|
||||||
subtitle={`Margin: ${account?.margin_used_pct.toFixed(1) || '0.0'}%`}
|
subtitle={`${t('margin', language)}: ${account?.margin_used_pct.toFixed(1) || '0.0'}%`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -326,11 +404,11 @@ function TraderDetailsPage({
|
|||||||
<div className="binance-card p-6 animate-slide-in" style={{ animationDelay: '0.15s' }}>
|
<div className="binance-card p-6 animate-slide-in" style={{ animationDelay: '0.15s' }}>
|
||||||
<div className="flex items-center justify-between mb-5">
|
<div className="flex items-center justify-between mb-5">
|
||||||
<h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
📈 Current Positions
|
📈 {t('currentPositions', language)}
|
||||||
</h2>
|
</h2>
|
||||||
{positions && positions.length > 0 && (
|
{positions && positions.length > 0 && (
|
||||||
<div className="text-xs px-3 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
|
<div className="text-xs px-3 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
|
||||||
{positions.length} Active
|
{positions.length} {t('active', language)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -339,15 +417,15 @@ function TraderDetailsPage({
|
|||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead className="text-left border-b border-gray-800">
|
<thead className="text-left border-b border-gray-800">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Symbol</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('symbol', language)}</th>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Side</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('side', language)}</th>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Entry Price</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('entryPrice', language)}</th>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Mark Price</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('markPrice', language)}</th>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Quantity</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('quantity', language)}</th>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Position Value</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('positionValue', language)}</th>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Leverage</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('leverage', language)}</th>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Unrealized P&L</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('unrealizedPnL', language)}</th>
|
||||||
<th className="pb-3 font-semibold text-gray-400">Liq. Price</th>
|
<th className="pb-3 font-semibold text-gray-400">{t('liqPrice', language)}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -362,7 +440,7 @@ function TraderDetailsPage({
|
|||||||
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{pos.side.toUpperCase()}
|
{t(pos.side === 'long' ? 'long' : 'short', language)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 font-mono" style={{ color: '#EAECEF' }}>{pos.entry_price.toFixed(4)}</td>
|
<td className="py-3 font-mono" style={{ color: '#EAECEF' }}>{pos.entry_price.toFixed(4)}</td>
|
||||||
@@ -391,8 +469,8 @@ function TraderDetailsPage({
|
|||||||
) : (
|
) : (
|
||||||
<div className="text-center py-16" style={{ color: '#848E9C' }}>
|
<div className="text-center py-16" style={{ color: '#848E9C' }}>
|
||||||
<div className="text-6xl mb-4 opacity-50">📊</div>
|
<div className="text-6xl mb-4 opacity-50">📊</div>
|
||||||
<div className="text-lg font-semibold mb-2">无持仓</div>
|
<div className="text-lg font-semibold mb-2">{t('noPositions', language)}</div>
|
||||||
<div className="text-sm">当前没有活跃的交易持仓</div>
|
<div className="text-sm">{t('noActivePositions', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -410,10 +488,10 @@ function TraderDetailsPage({
|
|||||||
🧠
|
🧠
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold" style={{ color: '#EAECEF' }}>Recent Decisions</h2>
|
<h2 className="text-xl font-bold" style={{ color: '#EAECEF' }}>{t('recentDecisions', language)}</h2>
|
||||||
{decisions && decisions.length > 0 && (
|
{decisions && decisions.length > 0 && (
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||||
Last {decisions.length} trading cycles
|
{t('lastCycles', language, { count: decisions.length })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -423,13 +501,13 @@ function TraderDetailsPage({
|
|||||||
<div className="space-y-4 overflow-y-auto pr-2" style={{ maxHeight: 'calc(100vh - 280px)' }}>
|
<div className="space-y-4 overflow-y-auto pr-2" style={{ maxHeight: 'calc(100vh - 280px)' }}>
|
||||||
{decisions && decisions.length > 0 ? (
|
{decisions && decisions.length > 0 ? (
|
||||||
decisions.map((decision, i) => (
|
decisions.map((decision, i) => (
|
||||||
<DecisionCard key={i} decision={decision} />
|
<DecisionCard key={i} decision={decision} language={language} />
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="py-16 text-center">
|
<div className="py-16 text-center">
|
||||||
<div className="text-6xl mb-4 opacity-30">🧠</div>
|
<div className="text-6xl mb-4 opacity-30">🧠</div>
|
||||||
<div className="text-lg font-semibold mb-2" style={{ color: '#EAECEF' }}>No Decisions Yet</div>
|
<div className="text-lg font-semibold mb-2" style={{ color: '#EAECEF' }}>{t('noDecisionsYet', language)}</div>
|
||||||
<div className="text-sm" style={{ color: '#848E9C' }}>AI trading decisions will appear here</div>
|
<div className="text-sm" style={{ color: '#848E9C' }}>{t('aiDecisionsWillAppear', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -480,7 +558,7 @@ function StatCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decision Card Component with CoT Trace - Binance Style
|
// Decision Card Component with CoT Trace - Binance Style
|
||||||
function DecisionCard({ decision }: { decision: DecisionRecord }) {
|
function DecisionCard({ decision, language }: { decision: DecisionRecord; language: Language }) {
|
||||||
const [showCoT, setShowCoT] = useState(false);
|
const [showCoT, setShowCoT] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -488,7 +566,7 @@ function DecisionCard({ decision }: { decision: DecisionRecord }) {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between mb-3">
|
<div className="flex items-start justify-between mb-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>Cycle #{decision.cycle_number}</div>
|
<div className="font-semibold" style={{ color: '#EAECEF' }}>{t('cycle', language)} #{decision.cycle_number}</div>
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||||
{new Date(decision.timestamp).toLocaleString()}
|
{new Date(decision.timestamp).toLocaleString()}
|
||||||
</div>
|
</div>
|
||||||
@@ -500,7 +578,7 @@ function DecisionCard({ decision }: { decision: DecisionRecord }) {
|
|||||||
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{decision.success ? 'Success' : 'Failed'}
|
{t(decision.success ? 'success' : 'failed', language)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -512,8 +590,8 @@ function DecisionCard({ decision }: { decision: DecisionRecord }) {
|
|||||||
className="flex items-center gap-2 text-sm transition-colors"
|
className="flex items-center gap-2 text-sm transition-colors"
|
||||||
style={{ color: '#F0B90B' }}
|
style={{ color: '#F0B90B' }}
|
||||||
>
|
>
|
||||||
<span className="font-semibold">💭 AI思维链分析</span>
|
<span className="font-semibold">{t('aiThinking', language)}</span>
|
||||||
<span className="text-xs">{showCoT ? '▼ 收起' : '▶ 展开'}</span>
|
<span className="text-xs">{showCoT ? t('collapse', language) : t('expand', language)}</span>
|
||||||
</button>
|
</button>
|
||||||
{showCoT && (
|
{showCoT && (
|
||||||
<div className="mt-2 rounded p-4 text-sm font-mono whitespace-pre-wrap max-h-96 overflow-y-auto" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}>
|
<div className="mt-2 rounded p-4 text-sm font-mono whitespace-pre-wrap max-h-96 overflow-y-auto" style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}>
|
||||||
@@ -586,4 +664,11 @@ function DecisionCard({ decision }: { decision: DecisionRecord }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
// Wrap App with LanguageProvider
|
||||||
|
export default function AppWithLanguage() {
|
||||||
|
return (
|
||||||
|
<LanguageProvider>
|
||||||
|
<App />
|
||||||
|
</LanguageProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
|
import { t } from '../i18n/translations';
|
||||||
|
|
||||||
interface TradeOutcome {
|
interface TradeOutcome {
|
||||||
symbol: string;
|
symbol: string;
|
||||||
@@ -52,6 +54,7 @@ interface DecisionRecord {
|
|||||||
const fetcher = (url: string) => fetch(url).then(res => res.json());
|
const fetcher = (url: string) => fetch(url).then(res => res.json());
|
||||||
|
|
||||||
export default function AILearning({ traderId }: AILearningProps) {
|
export default function AILearning({ traderId }: AILearningProps) {
|
||||||
|
const { language } = useLanguage();
|
||||||
const { data: performance, error } = useSWR<PerformanceAnalysis>(
|
const { data: performance, error } = useSWR<PerformanceAnalysis>(
|
||||||
`http://localhost:8080/api/performance?trader_id=${traderId}`,
|
`http://localhost:8080/api/performance?trader_id=${traderId}`,
|
||||||
fetcher,
|
fetcher,
|
||||||
@@ -68,7 +71,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||||
<div style={{ color: '#F6465D' }}>⚠️ 加载AI学习数据失败</div>
|
<div style={{ color: '#F6465D' }}>{t('loadingError', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -76,7 +79,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
if (!performance) {
|
if (!performance) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||||
<div style={{ color: '#848E9C' }}>📊 加载中...</div>
|
<div style={{ color: '#848E9C' }}>📊 {t('loading', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -86,10 +89,10 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<span className="text-xl">🧠</span>
|
<span className="text-xl">🧠</span>
|
||||||
<h2 className="text-lg font-bold" style={{ color: '#EAECEF' }}>AI 学习分析</h2>
|
<h2 className="text-lg font-bold" style={{ color: '#EAECEF' }}>{t('aiLearning', language)}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: '#848E9C' }}>
|
<div style={{ color: '#848E9C' }}>
|
||||||
暂无完整交易数据(需要完成开仓→平仓的完整周期)
|
{t('noCompleteData', language)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -115,9 +118,9 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
🧠
|
🧠
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>AI Learning & Reflection</h2>
|
<h2 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>{t('aiLearning', language)}</h2>
|
||||||
<p className="text-sm" style={{ color: '#848E9C' }}>
|
<p className="text-sm" style={{ color: '#848E9C' }}>
|
||||||
{performance.total_trades} trades analyzed · Real-time evolution
|
{t('tradesAnalyzed', language, { count: performance.total_trades })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,10 +153,10 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-bold" style={{ color: '#C4B5FD' }}>
|
<h3 className="text-base font-bold" style={{ color: '#C4B5FD' }}>
|
||||||
Latest Reflection
|
{t('latestReflection', language)}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs" style={{ color: '#94A3B8' }}>
|
<p className="text-xs" style={{ color: '#94A3B8' }}>
|
||||||
Cycle #{latestDecisions[0].cycle_number} · {new Date(latestDecisions[0].timestamp).toLocaleTimeString()}
|
{t('cycle', language)} #{latestDecisions[0].cycle_number} · {new Date(latestDecisions[0].timestamp).toLocaleTimeString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +174,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
{latestDecisions[0].cot_trace && (
|
{latestDecisions[0].cot_trace && (
|
||||||
<details className="mt-4">
|
<details className="mt-4">
|
||||||
<summary className="cursor-pointer text-sm font-semibold flex items-center gap-2 hover:opacity-80 transition-opacity" style={{ color: '#A78BFA' }}>
|
<summary className="cursor-pointer text-sm font-semibold flex items-center gap-2 hover:opacity-80 transition-opacity" style={{ color: '#A78BFA' }}>
|
||||||
<span>📋 Full Chain of Thought</span>
|
<span>{t('fullCoT', language)}</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="mt-3 rounded-xl p-4 text-xs leading-relaxed whitespace-pre-wrap max-h-80 overflow-y-auto" style={{
|
<div className="mt-3 rounded-xl p-4 text-xs leading-relaxed whitespace-pre-wrap max-h-80 overflow-y-auto" style={{
|
||||||
background: 'rgba(0, 0, 0, 0.5)',
|
background: 'rgba(0, 0, 0, 0.5)',
|
||||||
@@ -195,7 +198,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
border: '1px solid rgba(99, 102, 241, 0.2)',
|
border: '1px solid rgba(99, 102, 241, 0.2)',
|
||||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
||||||
}}>
|
}}>
|
||||||
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>Total Trades</div>
|
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>{t('totalTrades', language)}</div>
|
||||||
<div className="text-3xl font-bold mono" style={{ color: '#E0E7FF' }}>
|
<div className="text-3xl font-bold mono" style={{ color: '#E0E7FF' }}>
|
||||||
{performance.total_trades}
|
{performance.total_trades}
|
||||||
</div>
|
</div>
|
||||||
@@ -209,7 +212,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
border: `1px solid ${(performance.win_rate || 0) >= 50 ? 'rgba(14, 203, 129, 0.3)' : 'rgba(246, 70, 93, 0.3)'}`,
|
border: `1px solid ${(performance.win_rate || 0) >= 50 ? 'rgba(14, 203, 129, 0.3)' : 'rgba(246, 70, 93, 0.3)'}`,
|
||||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
||||||
}}>
|
}}>
|
||||||
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>Win Rate</div>
|
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>{t('winRate', language)}</div>
|
||||||
<div className="text-3xl font-bold mono" style={{
|
<div className="text-3xl font-bold mono" style={{
|
||||||
color: (performance.win_rate || 0) >= 50 ? '#10B981' : '#F87171'
|
color: (performance.win_rate || 0) >= 50 ? '#10B981' : '#F87171'
|
||||||
}}>
|
}}>
|
||||||
@@ -226,7 +229,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
border: '1px solid rgba(14, 203, 129, 0.2)',
|
border: '1px solid rgba(14, 203, 129, 0.2)',
|
||||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
||||||
}}>
|
}}>
|
||||||
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>Avg Win</div>
|
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>{t('avgWin', language)}</div>
|
||||||
<div className="text-3xl font-bold mono" style={{ color: '#10B981' }}>
|
<div className="text-3xl font-bold mono" style={{ color: '#10B981' }}>
|
||||||
+{(performance.avg_win || 0).toFixed(2)}%
|
+{(performance.avg_win || 0).toFixed(2)}%
|
||||||
</div>
|
</div>
|
||||||
@@ -238,7 +241,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
border: '1px solid rgba(246, 70, 93, 0.2)',
|
border: '1px solid rgba(246, 70, 93, 0.2)',
|
||||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
|
||||||
}}>
|
}}>
|
||||||
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>Avg Loss</div>
|
<div className="text-xs font-semibold mb-2" style={{ color: '#94A3B8' }}>{t('avgLoss', language)}</div>
|
||||||
<div className="text-3xl font-bold mono" style={{ color: '#F87171' }}>
|
<div className="text-3xl font-bold mono" style={{ color: '#F87171' }}>
|
||||||
{(performance.avg_loss || 0).toFixed(2)}%
|
{(performance.avg_loss || 0).toFixed(2)}%
|
||||||
</div>
|
</div>
|
||||||
@@ -258,9 +261,9 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
|
|
||||||
<div className="relative flex items-center justify-between">
|
<div className="relative flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold mb-1" style={{ color: '#FCD34D' }}>Profit Factor</div>
|
<div className="text-sm font-semibold mb-1" style={{ color: '#FCD34D' }}>{t('profitFactor', language)}</div>
|
||||||
<div className="text-xs" style={{ color: '#94A3B8' }}>
|
<div className="text-xs" style={{ color: '#94A3B8' }}>
|
||||||
Avg Win ÷ Avg Loss
|
{t('avgWinDivLoss', language)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-5xl font-bold mono" style={{
|
<div className="text-5xl font-bold mono" style={{
|
||||||
@@ -276,10 +279,10 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
color: (performance.profit_factor || 0) >= 2.0 ? '#10B981' :
|
color: (performance.profit_factor || 0) >= 2.0 ? '#10B981' :
|
||||||
(performance.profit_factor || 0) >= 1.5 ? '#F0B90B' : '#94A3B8'
|
(performance.profit_factor || 0) >= 1.5 ? '#F0B90B' : '#94A3B8'
|
||||||
}}>
|
}}>
|
||||||
{(performance.profit_factor || 0) >= 2.0 && '🔥 Excellent - Strong profitability'}
|
{(performance.profit_factor || 0) >= 2.0 && t('excellent', language)}
|
||||||
{(performance.profit_factor || 0) >= 1.5 && (performance.profit_factor || 0) < 2.0 && '✓ Good - Stable profits'}
|
{(performance.profit_factor || 0) >= 1.5 && (performance.profit_factor || 0) < 2.0 && t('good', language)}
|
||||||
{(performance.profit_factor || 0) >= 1.0 && (performance.profit_factor || 0) < 1.5 && '⚠️ Fair - Needs optimization'}
|
{(performance.profit_factor || 0) >= 1.0 && (performance.profit_factor || 0) < 1.5 && t('fair', language)}
|
||||||
{(performance.profit_factor || 0) > 0 && (performance.profit_factor || 0) < 1.0 && '❌ Poor - Losses exceed gains'}
|
{(performance.profit_factor || 0) > 0 && (performance.profit_factor || 0) < 1.0 && t('poor', language)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -298,7 +301,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
}}>
|
}}>
|
||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<span className="text-xl">🏆</span>
|
<span className="text-xl">🏆</span>
|
||||||
<span className="text-xs font-semibold" style={{ color: '#6EE7B7' }}>Best Performer</span>
|
<span className="text-xs font-semibold" style={{ color: '#6EE7B7' }}>{t('bestPerformer', language)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold mono mb-1" style={{ color: '#10B981' }}>
|
<div className="text-2xl font-bold mono mb-1" style={{ color: '#10B981' }}>
|
||||||
{performance.best_symbol}
|
{performance.best_symbol}
|
||||||
@@ -306,7 +309,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
{symbolStats[performance.best_symbol] && (
|
{symbolStats[performance.best_symbol] && (
|
||||||
<div className="text-sm font-semibold" style={{ color: '#6EE7B7' }}>
|
<div className="text-sm font-semibold" style={{ color: '#6EE7B7' }}>
|
||||||
{symbolStats[performance.best_symbol].total_pn_l > 0 ? '+' : ''}
|
{symbolStats[performance.best_symbol].total_pn_l > 0 ? '+' : ''}
|
||||||
{symbolStats[performance.best_symbol].total_pn_l.toFixed(2)}% P&L
|
{symbolStats[performance.best_symbol].total_pn_l.toFixed(2)}% {t('pnl', language)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -320,7 +323,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
}}>
|
}}>
|
||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<span className="text-xl">📉</span>
|
<span className="text-xl">📉</span>
|
||||||
<span className="text-xs font-semibold" style={{ color: '#FCA5A5' }}>Worst Performer</span>
|
<span className="text-xs font-semibold" style={{ color: '#FCA5A5' }}>{t('worstPerformer', language)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold mono mb-1" style={{ color: '#F87171' }}>
|
<div className="text-2xl font-bold mono mb-1" style={{ color: '#F87171' }}>
|
||||||
{performance.worst_symbol}
|
{performance.worst_symbol}
|
||||||
@@ -328,7 +331,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
{symbolStats[performance.worst_symbol] && (
|
{symbolStats[performance.worst_symbol] && (
|
||||||
<div className="text-sm font-semibold" style={{ color: '#FCA5A5' }}>
|
<div className="text-sm font-semibold" style={{ color: '#FCA5A5' }}>
|
||||||
{symbolStats[performance.worst_symbol].total_pn_l > 0 ? '+' : ''}
|
{symbolStats[performance.worst_symbol].total_pn_l > 0 ? '+' : ''}
|
||||||
{symbolStats[performance.worst_symbol].total_pn_l.toFixed(2)}% P&L
|
{symbolStats[performance.worst_symbol].total_pn_l.toFixed(2)}% {t('pnl', language)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -345,7 +348,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
}}>
|
}}>
|
||||||
<div className="p-5 border-b" style={{ borderColor: 'rgba(99, 102, 241, 0.2)', background: 'rgba(30, 35, 41, 0.6)' }}>
|
<div className="p-5 border-b" style={{ borderColor: 'rgba(99, 102, 241, 0.2)', background: 'rgba(30, 35, 41, 0.6)' }}>
|
||||||
<h3 className="font-bold flex items-center gap-2" style={{ color: '#E0E7FF' }}>
|
<h3 className="font-bold flex items-center gap-2" style={{ color: '#E0E7FF' }}>
|
||||||
📊 Symbol Performance
|
{t('symbolPerformance', language)}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
@@ -411,11 +414,11 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xl">📜</span>
|
<span className="text-xl">📜</span>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-sm" style={{ color: '#FCD34D' }}>Trade History</h3>
|
<h3 className="font-bold text-sm" style={{ color: '#FCD34D' }}>{t('tradeHistory', language)}</h3>
|
||||||
<p className="text-xs" style={{ color: '#94A3B8' }}>
|
<p className="text-xs" style={{ color: '#94A3B8' }}>
|
||||||
{performance?.recent_trades && performance.recent_trades.length > 0
|
{performance?.recent_trades && performance.recent_trades.length > 0
|
||||||
? `Recent ${performance.recent_trades.length} completed trades`
|
? t('completedTrades', language, { count: performance.recent_trades.length })
|
||||||
: 'Completed trades will appear here'}
|
: t('completedTradesWillAppear', language)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -459,7 +462,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
background: 'rgba(240, 185, 11, 0.2)',
|
background: 'rgba(240, 185, 11, 0.2)',
|
||||||
color: '#FCD34D'
|
color: '#FCD34D'
|
||||||
}}>
|
}}>
|
||||||
Latest
|
{t('latest', language)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -473,13 +476,13 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
{/* 价格信息 */}
|
{/* 价格信息 */}
|
||||||
<div className="grid grid-cols-2 gap-2 mb-3 text-xs">
|
<div className="grid grid-cols-2 gap-2 mb-3 text-xs">
|
||||||
<div>
|
<div>
|
||||||
<div style={{ color: '#94A3B8' }}>Entry</div>
|
<div style={{ color: '#94A3B8' }}>{t('entry', language)}</div>
|
||||||
<div className="font-mono font-semibold" style={{ color: '#CBD5E1' }}>
|
<div className="font-mono font-semibold" style={{ color: '#CBD5E1' }}>
|
||||||
{trade.open_price.toFixed(4)}
|
{trade.open_price.toFixed(4)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div style={{ color: '#94A3B8' }}>Exit</div>
|
<div style={{ color: '#94A3B8' }}>{t('exit', language)}</div>
|
||||||
<div className="font-mono font-semibold" style={{ color: '#CBD5E1' }}>
|
<div className="font-mono font-semibold" style={{ color: '#CBD5E1' }}>
|
||||||
{trade.close_price.toFixed(4)}
|
{trade.close_price.toFixed(4)}
|
||||||
</div>
|
</div>
|
||||||
@@ -508,7 +511,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
background: 'rgba(248, 113, 113, 0.2)',
|
background: 'rgba(248, 113, 113, 0.2)',
|
||||||
color: '#FCA5A5'
|
color: '#FCA5A5'
|
||||||
}}>
|
}}>
|
||||||
Stop Loss
|
{t('stopLoss', language)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -531,7 +534,7 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="p-6 text-center">
|
<div className="p-6 text-center">
|
||||||
<div className="text-4xl mb-2 opacity-50">📜</div>
|
<div className="text-4xl mb-2 opacity-50">📜</div>
|
||||||
<div style={{ color: '#94A3B8' }}>No completed trades yet</div>
|
<div style={{ color: '#94A3B8' }}>{t('noCompletedTrades', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -556,23 +559,23 @@ export default function AILearning({ traderId }: AILearningProps) {
|
|||||||
💡
|
💡
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold mb-3 text-base" style={{ color: '#FCD34D' }}>How AI Learns & Evolves</h3>
|
<h3 className="font-bold mb-3 text-base" style={{ color: '#FCD34D' }}>{t('howAILearns', language)}</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 text-sm">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 text-sm">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span style={{ color: '#F0B90B' }}>•</span>
|
<span style={{ color: '#F0B90B' }}>•</span>
|
||||||
<span style={{ color: '#CBD5E1' }}>Analyzes last 20 trading cycles before each decision</span>
|
<span style={{ color: '#CBD5E1' }}>{t('aiLearningPoint1', language)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span style={{ color: '#F0B90B' }}>•</span>
|
<span style={{ color: '#F0B90B' }}>•</span>
|
||||||
<span style={{ color: '#CBD5E1' }}>Identifies best & worst performing symbols</span>
|
<span style={{ color: '#CBD5E1' }}>{t('aiLearningPoint2', language)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span style={{ color: '#F0B90B' }}>•</span>
|
<span style={{ color: '#F0B90B' }}>•</span>
|
||||||
<span style={{ color: '#CBD5E1' }}>Optimizes position sizing based on win rate</span>
|
<span style={{ color: '#CBD5E1' }}>{t('aiLearningPoint3', language)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span style={{ color: '#F0B90B' }}>•</span>
|
<span style={{ color: '#F0B90B' }}>•</span>
|
||||||
<span style={{ color: '#CBD5E1' }}>Avoids repeating past mistakes</span>
|
<span style={{ color: '#CBD5E1' }}>{t('aiLearningPoint4', language)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import useSWR from 'swr';
|
|||||||
import { api } from '../lib/api';
|
import { api } from '../lib/api';
|
||||||
import type { CompetitionData } from '../types';
|
import type { CompetitionData } from '../types';
|
||||||
import { ComparisonChart } from './ComparisonChart';
|
import { ComparisonChart } from './ComparisonChart';
|
||||||
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
|
import { t } from '../i18n/translations';
|
||||||
|
|
||||||
export function CompetitionPage() {
|
export function CompetitionPage() {
|
||||||
|
const { language } = useLanguage();
|
||||||
const { data: competition } = useSWR<CompetitionData>(
|
const { data: competition } = useSWR<CompetitionData>(
|
||||||
'competition',
|
'competition',
|
||||||
api.getCompetition,
|
api.getCompetition,
|
||||||
@@ -57,18 +60,18 @@ export function CompetitionPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
AI Competition
|
{t('aiCompetition', language)}
|
||||||
<span className="text-xs font-normal px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.15)', color: '#F0B90B' }}>
|
<span className="text-xs font-normal px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.15)', color: '#F0B90B' }}>
|
||||||
{competition.count} traders
|
{competition.count} {t('traders', language)}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs" style={{ color: '#848E9C' }}>
|
<p className="text-xs" style={{ color: '#848E9C' }}>
|
||||||
Qwen vs DeepSeek · Live Battle
|
{t('liveBattle', language)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>🥇 Leader</div>
|
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>{t('leader', language)}</div>
|
||||||
<div className="text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
|
<div className="text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
|
||||||
<div className="text-sm font-semibold" style={{ color: leader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}>
|
<div className="text-sm font-semibold" style={{ color: leader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}>
|
||||||
{leader.total_pnl >= 0 ? '+' : ''}{leader.total_pnl_pct.toFixed(2)}%
|
{leader.total_pnl >= 0 ? '+' : ''}{leader.total_pnl_pct.toFixed(2)}%
|
||||||
@@ -82,10 +85,10 @@ export function CompetitionPage() {
|
|||||||
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.1s' }}>
|
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.1s' }}>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-lg font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h2 className="text-lg font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
📈 Performance Comparison
|
{t('performanceComparison', language)}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||||
Real-time PnL %
|
{t('realTimePnL', language)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ComparisonChart traders={sortedTraders} />
|
<ComparisonChart traders={sortedTraders} />
|
||||||
@@ -95,10 +98,10 @@ export function CompetitionPage() {
|
|||||||
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.1s' }}>
|
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.1s' }}>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-lg font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h2 className="text-lg font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
🥇 Leaderboard
|
{t('leaderboard', language)}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="text-xs px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
|
<div className="text-xs px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
|
||||||
LIVE
|
{t('live', language)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -134,7 +137,7 @@ export function CompetitionPage() {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{/* Total Equity */}
|
{/* Total Equity */}
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>Equity</div>
|
<div className="text-xs" style={{ color: '#848E9C' }}>{t('equity', language)}</div>
|
||||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{trader.total_equity.toFixed(2)}
|
{trader.total_equity.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +145,7 @@ export function CompetitionPage() {
|
|||||||
|
|
||||||
{/* P&L */}
|
{/* P&L */}
|
||||||
<div className="text-right min-w-[90px]">
|
<div className="text-right min-w-[90px]">
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>P&L</div>
|
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pnl', language)}</div>
|
||||||
<div
|
<div
|
||||||
className="text-lg font-bold mono"
|
className="text-lg font-bold mono"
|
||||||
style={{ color: trader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}
|
style={{ color: trader.total_pnl >= 0 ? '#0ECB81' : '#F6465D' }}
|
||||||
@@ -157,7 +160,7 @@ export function CompetitionPage() {
|
|||||||
|
|
||||||
{/* Positions */}
|
{/* Positions */}
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>Pos</div>
|
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pos', language)}</div>
|
||||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{trader.position_count}
|
{trader.position_count}
|
||||||
</div>
|
</div>
|
||||||
@@ -191,7 +194,7 @@ export function CompetitionPage() {
|
|||||||
{competition.traders.length === 2 && (
|
{competition.traders.length === 2 && (
|
||||||
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.3s' }}>
|
<div className="binance-card p-5 animate-slide-in" style={{ animationDelay: '0.3s' }}>
|
||||||
<h2 className="text-lg font-bold mb-4 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h2 className="text-lg font-bold mb-4 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
⚔️ Head-to-Head Battle
|
{t('headToHead', language)}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
{sortedTraders.map((trader, index) => {
|
{sortedTraders.map((trader, index) => {
|
||||||
@@ -228,12 +231,12 @@ export function CompetitionPage() {
|
|||||||
</div>
|
</div>
|
||||||
{isWinning && gap > 0 && (
|
{isWinning && gap > 0 && (
|
||||||
<div className="text-xs font-semibold" style={{ color: '#0ECB81' }}>
|
<div className="text-xs font-semibold" style={{ color: '#0ECB81' }}>
|
||||||
Leading by {gap.toFixed(2)}%
|
{t('leadingBy', language, { gap: gap.toFixed(2) })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isWinning && gap < 0 && (
|
{!isWinning && gap < 0 && (
|
||||||
<div className="text-xs font-semibold" style={{ color: '#F6465D' }}>
|
<div className="text-xs font-semibold" style={{ color: '#F6465D' }}>
|
||||||
Behind by {Math.abs(gap).toFixed(2)}%
|
{t('behindBy', language, { gap: Math.abs(gap).toFixed(2) })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { api } from '../lib/api';
|
import { api } from '../lib/api';
|
||||||
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
|
import { t } from '../i18n/translations';
|
||||||
|
|
||||||
interface EquityPoint {
|
interface EquityPoint {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
@@ -25,6 +27,7 @@ interface EquityChartProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function EquityChart({ traderId }: EquityChartProps) {
|
export function EquityChart({ traderId }: EquityChartProps) {
|
||||||
|
const { language } = useLanguage();
|
||||||
const [displayMode, setDisplayMode] = useState<'dollar' | 'percent'>('dollar');
|
const [displayMode, setDisplayMode] = useState<'dollar' | 'percent'>('dollar');
|
||||||
|
|
||||||
const { data: history, error } = useSWR<EquityPoint[]>(
|
const { data: history, error } = useSWR<EquityPoint[]>(
|
||||||
@@ -49,7 +52,7 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
<div className="flex items-center gap-3 p-4 rounded" style={{ background: 'rgba(246, 70, 93, 0.1)', border: '1px solid rgba(246, 70, 93, 0.2)' }}>
|
<div className="flex items-center gap-3 p-4 rounded" style={{ background: 'rgba(246, 70, 93, 0.1)', border: '1px solid rgba(246, 70, 93, 0.2)' }}>
|
||||||
<div className="text-2xl">⚠️</div>
|
<div className="text-2xl">⚠️</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold" style={{ color: '#F6465D' }}>加载失败</div>
|
<div className="font-semibold" style={{ color: '#F6465D' }}>{t('loadingError', language)}</div>
|
||||||
<div className="text-sm" style={{ color: '#848E9C' }}>{error.message}</div>
|
<div className="text-sm" style={{ color: '#848E9C' }}>{error.message}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,11 +63,11 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
if (!history || history.length === 0) {
|
if (!history || history.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="binance-card p-6">
|
<div className="binance-card p-6">
|
||||||
<h3 className="text-lg font-semibold mb-6" style={{ color: '#EAECEF' }}>账户净值曲线</h3>
|
<h3 className="text-lg font-semibold mb-6" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3>
|
||||||
<div className="text-center py-16" style={{ color: '#848E9C' }}>
|
<div className="text-center py-16" style={{ color: '#848E9C' }}>
|
||||||
<div className="text-6xl mb-4 opacity-50">📊</div>
|
<div className="text-6xl mb-4 opacity-50">📊</div>
|
||||||
<div className="text-lg font-semibold mb-2">暂无历史数据</div>
|
<div className="text-lg font-semibold mb-2">{t('noHistoricalData', language)}</div>
|
||||||
<div className="text-sm">运行几个周期后将显示收益率曲线</div>
|
<div className="text-sm">{t('dataWillAppear', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -153,7 +156,7 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold mb-2" style={{ color: '#EAECEF' }}>账户净值曲线</h3>
|
<h3 className="text-lg font-bold mb-2" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3>
|
||||||
<div className="flex items-baseline gap-4">
|
<div className="flex items-baseline gap-4">
|
||||||
<span className="text-3xl font-bold mono" style={{ color: '#EAECEF' }}>
|
<span className="text-3xl font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{account?.total_equity.toFixed(2) || '0.00'}
|
{account?.total_equity.toFixed(2) || '0.00'}
|
||||||
@@ -239,7 +242,7 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
stroke="#474D57"
|
stroke="#474D57"
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
label={{
|
label={{
|
||||||
value: displayMode === 'dollar' ? '初始' : '0%',
|
value: displayMode === 'dollar' ? t('initialBalance', language).split(' ')[0] : '0%',
|
||||||
fill: '#848E9C',
|
fill: '#848E9C',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
}}
|
}}
|
||||||
@@ -259,27 +262,27 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
{/* Footer Stats */}
|
{/* Footer Stats */}
|
||||||
<div className="mt-3 grid grid-cols-4 gap-3 pt-3" style={{ borderTop: '1px solid #2B3139' }}>
|
<div className="mt-3 grid grid-cols-4 gap-3 pt-3" style={{ borderTop: '1px solid #2B3139' }}>
|
||||||
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>初始余额</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('initialBalance', language)}</div>
|
||||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{initialBalance.toFixed(2)} USDT
|
{initialBalance.toFixed(2)} USDT
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>当前净值</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('currentEquity', language)}</div>
|
||||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{currentValue.raw_equity.toFixed(2)} USDT
|
{currentValue.raw_equity.toFixed(2)} USDT
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>历史周期</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('historicalCycles', language)}</div>
|
||||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>{history.length} 个</div>
|
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>{history.length} {t('cycles', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>显示范围</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('displayRange', language)}</div>
|
||||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{history.length > MAX_DISPLAY_POINTS
|
{history.length > MAX_DISPLAY_POINTS
|
||||||
? `最近 ${MAX_DISPLAY_POINTS}`
|
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
|
||||||
: '全部数据'
|
: t('allData', language)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { createContext, useContext, useState, ReactNode } from 'react';
|
||||||
|
import type { Language } from '../i18n/translations';
|
||||||
|
|
||||||
|
interface LanguageContextType {
|
||||||
|
language: Language;
|
||||||
|
setLanguage: (lang: Language) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function LanguageProvider({ children }: { children: ReactNode }) {
|
||||||
|
// Initialize language from localStorage or default to English
|
||||||
|
const [language, setLanguage] = useState<Language>(() => {
|
||||||
|
const saved = localStorage.getItem('language');
|
||||||
|
return (saved === 'en' || saved === 'zh') ? saved : 'en';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save language to localStorage whenever it changes
|
||||||
|
const handleSetLanguage = (lang: Language) => {
|
||||||
|
setLanguage(lang);
|
||||||
|
localStorage.setItem('language', lang);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LanguageContext.Provider value={{ language, setLanguage: handleSetLanguage }}>
|
||||||
|
{children}
|
||||||
|
</LanguageContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLanguage() {
|
||||||
|
const context = useContext(LanguageContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useLanguage must be used within LanguageProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
export type Language = 'en' | 'zh';
|
||||||
|
|
||||||
|
export const translations = {
|
||||||
|
en: {
|
||||||
|
// Header
|
||||||
|
appTitle: 'AI Trading Competition',
|
||||||
|
subtitle: 'Qwen vs DeepSeek · Real-time',
|
||||||
|
competition: 'Competition',
|
||||||
|
details: 'Details',
|
||||||
|
running: 'RUNNING',
|
||||||
|
stopped: 'STOPPED',
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
footerTitle: 'NOFX - AI Trading Competition System',
|
||||||
|
footerWarning: '⚠️ Trading involves risk. Use at your own discretion.',
|
||||||
|
|
||||||
|
// Stats Cards
|
||||||
|
totalEquity: 'Total Equity',
|
||||||
|
availableBalance: 'Available Balance',
|
||||||
|
totalPnL: 'Total P&L',
|
||||||
|
positions: 'Positions',
|
||||||
|
margin: 'Margin',
|
||||||
|
free: 'Free',
|
||||||
|
|
||||||
|
// Positions Table
|
||||||
|
currentPositions: 'Current Positions',
|
||||||
|
active: 'Active',
|
||||||
|
symbol: 'Symbol',
|
||||||
|
side: 'Side',
|
||||||
|
entryPrice: 'Entry Price',
|
||||||
|
markPrice: 'Mark Price',
|
||||||
|
quantity: 'Quantity',
|
||||||
|
positionValue: 'Position Value',
|
||||||
|
leverage: 'Leverage',
|
||||||
|
unrealizedPnL: 'Unrealized P&L',
|
||||||
|
liqPrice: 'Liq. Price',
|
||||||
|
long: 'LONG',
|
||||||
|
short: 'SHORT',
|
||||||
|
noPositions: 'No Positions',
|
||||||
|
noActivePositions: 'No active trading positions',
|
||||||
|
|
||||||
|
// Recent Decisions
|
||||||
|
recentDecisions: 'Recent Decisions',
|
||||||
|
lastCycles: 'Last {count} trading cycles',
|
||||||
|
noDecisionsYet: 'No Decisions Yet',
|
||||||
|
aiDecisionsWillAppear: 'AI trading decisions will appear here',
|
||||||
|
cycle: 'Cycle',
|
||||||
|
success: 'Success',
|
||||||
|
failed: 'Failed',
|
||||||
|
aiThinking: 'AI Chain of Thought',
|
||||||
|
collapse: 'Collapse',
|
||||||
|
expand: 'Expand',
|
||||||
|
|
||||||
|
// Equity Chart
|
||||||
|
accountEquityCurve: 'Account Equity Curve',
|
||||||
|
noHistoricalData: 'No Historical Data',
|
||||||
|
dataWillAppear: 'Equity curve will appear after running a few cycles',
|
||||||
|
initialBalance: 'Initial Balance',
|
||||||
|
currentEquity: 'Current Equity',
|
||||||
|
historicalCycles: 'Historical Cycles',
|
||||||
|
displayRange: 'Display Range',
|
||||||
|
recent: 'Recent',
|
||||||
|
allData: 'All Data',
|
||||||
|
cycles: 'Cycles',
|
||||||
|
|
||||||
|
// Competition Page
|
||||||
|
aiCompetition: 'AI Competition',
|
||||||
|
traders: 'traders',
|
||||||
|
liveBattle: 'Qwen vs DeepSeek · Live Battle',
|
||||||
|
leader: 'Leader',
|
||||||
|
leaderboard: 'Leaderboard',
|
||||||
|
live: 'LIVE',
|
||||||
|
performanceComparison: 'Performance Comparison',
|
||||||
|
realTimePnL: 'Real-time PnL %',
|
||||||
|
headToHead: 'Head-to-Head Battle',
|
||||||
|
leadingBy: 'Leading by {gap}%',
|
||||||
|
behindBy: 'Behind by {gap}%',
|
||||||
|
equity: 'Equity',
|
||||||
|
pnl: 'P&L',
|
||||||
|
pos: 'Pos',
|
||||||
|
|
||||||
|
// AI Learning
|
||||||
|
aiLearning: 'AI Learning & Reflection',
|
||||||
|
tradesAnalyzed: '{count} trades analyzed · Real-time evolution',
|
||||||
|
latestReflection: 'Latest Reflection',
|
||||||
|
fullCoT: 'Full Chain of Thought',
|
||||||
|
totalTrades: 'Total Trades',
|
||||||
|
winRate: 'Win Rate',
|
||||||
|
avgWin: 'Avg Win',
|
||||||
|
avgLoss: 'Avg Loss',
|
||||||
|
profitFactor: 'Profit Factor',
|
||||||
|
avgWinDivLoss: 'Avg Win ÷ Avg Loss',
|
||||||
|
excellent: '🔥 Excellent - Strong profitability',
|
||||||
|
good: '✓ Good - Stable profits',
|
||||||
|
fair: '⚠️ Fair - Needs optimization',
|
||||||
|
poor: '❌ Poor - Losses exceed gains',
|
||||||
|
bestPerformer: 'Best Performer',
|
||||||
|
worstPerformer: 'Worst Performer',
|
||||||
|
symbolPerformance: 'Symbol Performance',
|
||||||
|
tradeHistory: 'Trade History',
|
||||||
|
completedTrades: 'Recent {count} completed trades',
|
||||||
|
noCompletedTrades: 'No completed trades yet',
|
||||||
|
completedTradesWillAppear: 'Completed trades will appear here',
|
||||||
|
entry: 'Entry',
|
||||||
|
exit: 'Exit',
|
||||||
|
stopLoss: 'Stop Loss',
|
||||||
|
latest: 'Latest',
|
||||||
|
|
||||||
|
// AI Learning Description
|
||||||
|
howAILearns: 'How AI Learns & Evolves',
|
||||||
|
aiLearningPoint1: 'Analyzes last 20 trading cycles before each decision',
|
||||||
|
aiLearningPoint2: 'Identifies best & worst performing symbols',
|
||||||
|
aiLearningPoint3: 'Optimizes position sizing based on win rate',
|
||||||
|
aiLearningPoint4: 'Avoids repeating past mistakes',
|
||||||
|
|
||||||
|
// Loading & Error
|
||||||
|
loading: 'Loading...',
|
||||||
|
loadingError: '⚠️ Failed to load AI learning data',
|
||||||
|
noCompleteData: 'No complete trading data (needs to complete open → close cycle)',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
// Header
|
||||||
|
appTitle: 'AI交易竞赛',
|
||||||
|
subtitle: 'Qwen vs DeepSeek · 实时',
|
||||||
|
competition: '竞赛',
|
||||||
|
details: '详情',
|
||||||
|
running: '运行中',
|
||||||
|
stopped: '已停止',
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
footerTitle: 'NOFX - AI交易竞赛系统',
|
||||||
|
footerWarning: '⚠️ 交易有风险,请谨慎使用。',
|
||||||
|
|
||||||
|
// Stats Cards
|
||||||
|
totalEquity: '总净值',
|
||||||
|
availableBalance: '可用余额',
|
||||||
|
totalPnL: '总盈亏',
|
||||||
|
positions: '持仓',
|
||||||
|
margin: '保证金',
|
||||||
|
free: '空闲',
|
||||||
|
|
||||||
|
// Positions Table
|
||||||
|
currentPositions: '当前持仓',
|
||||||
|
active: '活跃',
|
||||||
|
symbol: '币种',
|
||||||
|
side: '方向',
|
||||||
|
entryPrice: '入场价',
|
||||||
|
markPrice: '标记价',
|
||||||
|
quantity: '数量',
|
||||||
|
positionValue: '仓位价值',
|
||||||
|
leverage: '杠杆',
|
||||||
|
unrealizedPnL: '未实现盈亏',
|
||||||
|
liqPrice: '强平价',
|
||||||
|
long: '多头',
|
||||||
|
short: '空头',
|
||||||
|
noPositions: '无持仓',
|
||||||
|
noActivePositions: '当前没有活跃的交易持仓',
|
||||||
|
|
||||||
|
// Recent Decisions
|
||||||
|
recentDecisions: '最近决策',
|
||||||
|
lastCycles: '最近 {count} 个交易周期',
|
||||||
|
noDecisionsYet: '暂无决策',
|
||||||
|
aiDecisionsWillAppear: 'AI交易决策将显示在这里',
|
||||||
|
cycle: '周期',
|
||||||
|
success: '成功',
|
||||||
|
failed: '失败',
|
||||||
|
aiThinking: '💭 AI思维链分析',
|
||||||
|
collapse: '▼ 收起',
|
||||||
|
expand: '▶ 展开',
|
||||||
|
|
||||||
|
// Equity Chart
|
||||||
|
accountEquityCurve: '账户净值曲线',
|
||||||
|
noHistoricalData: '暂无历史数据',
|
||||||
|
dataWillAppear: '运行几个周期后将显示收益率曲线',
|
||||||
|
initialBalance: '初始余额',
|
||||||
|
currentEquity: '当前净值',
|
||||||
|
historicalCycles: '历史周期',
|
||||||
|
displayRange: '显示范围',
|
||||||
|
recent: '最近',
|
||||||
|
allData: '全部数据',
|
||||||
|
cycles: '个',
|
||||||
|
|
||||||
|
// Competition Page
|
||||||
|
aiCompetition: 'AI竞赛',
|
||||||
|
traders: '位交易者',
|
||||||
|
liveBattle: 'Qwen vs DeepSeek · 实时对战',
|
||||||
|
leader: '🥇 领先者',
|
||||||
|
leaderboard: '🥇 排行榜',
|
||||||
|
live: '直播',
|
||||||
|
performanceComparison: '📈 表现对比',
|
||||||
|
realTimePnL: '实时盈亏百分比',
|
||||||
|
headToHead: '⚔️ 正面对决',
|
||||||
|
leadingBy: '领先 {gap}%',
|
||||||
|
behindBy: '落后 {gap}%',
|
||||||
|
equity: '净值',
|
||||||
|
pnl: '盈亏',
|
||||||
|
pos: '仓位',
|
||||||
|
|
||||||
|
// AI Learning
|
||||||
|
aiLearning: 'AI学习与反思',
|
||||||
|
tradesAnalyzed: '已分析 {count} 笔交易 · 实时演化',
|
||||||
|
latestReflection: '最新反思',
|
||||||
|
fullCoT: '📋 完整思维链',
|
||||||
|
totalTrades: '总交易数',
|
||||||
|
winRate: '胜率',
|
||||||
|
avgWin: '平均盈利',
|
||||||
|
avgLoss: '平均亏损',
|
||||||
|
profitFactor: '盈亏比',
|
||||||
|
avgWinDivLoss: '平均盈利 ÷ 平均亏损',
|
||||||
|
excellent: '🔥 优秀 - 盈利能力强',
|
||||||
|
good: '✓ 良好 - 稳定盈利',
|
||||||
|
fair: '⚠️ 一般 - 需要优化',
|
||||||
|
poor: '❌ 较差 - 亏损超过盈利',
|
||||||
|
bestPerformer: '最佳表现',
|
||||||
|
worstPerformer: '最差表现',
|
||||||
|
symbolPerformance: '📊 币种表现',
|
||||||
|
tradeHistory: '历史成交',
|
||||||
|
completedTrades: '最近 {count} 笔已完成交易',
|
||||||
|
noCompletedTrades: '暂无完成的交易',
|
||||||
|
completedTradesWillAppear: '已完成的交易将显示在这里',
|
||||||
|
entry: '入场',
|
||||||
|
exit: '出场',
|
||||||
|
stopLoss: '止损',
|
||||||
|
latest: '最新',
|
||||||
|
|
||||||
|
// AI Learning Description
|
||||||
|
howAILearns: '💡 AI如何学习和进化',
|
||||||
|
aiLearningPoint1: '每次决策前分析最近20个交易周期',
|
||||||
|
aiLearningPoint2: '识别表现最好和最差的币种',
|
||||||
|
aiLearningPoint3: '根据胜率优化仓位大小',
|
||||||
|
aiLearningPoint4: '避免重复过去的错误',
|
||||||
|
|
||||||
|
// Loading & Error
|
||||||
|
loading: '加载中...',
|
||||||
|
loadingError: '⚠️ 加载AI学习数据失败',
|
||||||
|
noCompleteData: '暂无完整交易数据(需要完成开仓→平仓的完整周期)',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function t(key: string, lang: Language, params?: Record<string, string | number>): string {
|
||||||
|
let text = translations[lang][key as keyof typeof translations['en']] || key;
|
||||||
|
|
||||||
|
// Replace parameters like {count}, {gap}, etc.
|
||||||
|
if (params) {
|
||||||
|
Object.entries(params).forEach(([param, value]) => {
|
||||||
|
text = text.replace(`{${param}}`, String(value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user