Merge remote tracking branch into local development

- Resolved conflicts in README.md: Combined web-based config updates with multi-exchange support
- Resolved conflicts in main.go: Fixed database initialization and default coin settings
- Resolved conflicts in manager/trader_manager.go: Updated trader management for new database structure
- Resolved conflicts in web/src/App.tsx: Combined UI improvements with responsive design
- Resolved conflicts in web/.dockerignore: Merged dependency exclusions
- Removed deprecated files: Dockerfile, config/config.go, web/Dockerfile, ComparisonChart.tsx, CompetitionPage.tsx
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
icy
2025-10-30 20:57:57 +08:00
42 changed files with 3768 additions and 593 deletions

View File

@@ -1,12 +1,17 @@
import useSWR from 'swr';
import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations';
import { api } from '../lib/api';
interface TradeOutcome {
symbol: string;
side: string;
quantity: number;
leverage: number;
open_price: number;
close_price: number;
position_value: number;
margin_used: number;
pn_l: number;
pn_l_pct: number;
duration: string;
@@ -44,14 +49,16 @@ interface AILearningProps {
traderId: string;
}
const fetcher = (url: string) => fetch(url).then(res => res.json());
export default function AILearning({ traderId }: AILearningProps) {
const { language } = useLanguage();
const { data: performance, error } = useSWR<PerformanceAnalysis>(
`http://localhost:8080/api/performance?trader_id=${traderId}`,
fetcher,
{ refreshInterval: 10000 }
traderId ? `performance-${traderId}` : 'performance',
() => api.getPerformance(traderId),
{
refreshInterval: 30000, // 30秒刷新AI学习分析数据更新频率较低
revalidateOnFocus: false,
dedupingInterval: 20000,
}
);
if (error) {
@@ -555,6 +562,34 @@ export default function AILearning({ traderId }: AILearningProps) {
</div>
</div>
{/* Position Details */}
<div className="grid grid-cols-2 gap-2 mb-3 text-xs">
<div>
<div style={{ color: '#94A3B8' }}>Quantity</div>
<div className="font-mono font-semibold" style={{ color: '#CBD5E1' }}>
{trade.quantity ? trade.quantity.toFixed(4) : '-'}
</div>
</div>
<div className="text-right">
<div style={{ color: '#94A3B8' }}>Leverage</div>
<div className="font-mono font-semibold" style={{ color: '#FCD34D' }}>
{trade.leverage ? `${trade.leverage}x` : '-'}
</div>
</div>
<div>
<div style={{ color: '#94A3B8' }}>Position Value</div>
<div className="font-mono font-semibold" style={{ color: '#CBD5E1' }}>
{trade.position_value ? `$${trade.position_value.toFixed(2)}` : '-'}
</div>
</div>
<div className="text-right">
<div style={{ color: '#94A3B8' }}>Margin Used</div>
<div className="font-mono font-semibold" style={{ color: '#A78BFA' }}>
{trade.margin_used ? `$${trade.margin_used.toFixed(2)}` : '-'}
</div>
</div>
</div>
<div className="rounded-lg p-2 mb-2" style={{
background: isProfitable ? 'rgba(16, 185, 129, 0.1)' : 'rgba(248, 113, 113, 0.1)'
}}>

View File

@@ -34,7 +34,9 @@ export function EquityChart({ traderId }: EquityChartProps) {
traderId ? `equity-history-${traderId}` : 'equity-history',
() => api.getEquityHistory(traderId),
{
refreshInterval: 10000, // 每10秒刷新
refreshInterval: 30000, // 30秒刷新(历史数据更新频率较低)
revalidateOnFocus: false,
dedupingInterval: 20000,
}
);
@@ -42,7 +44,9 @@ export function EquityChart({ traderId }: EquityChartProps) {
traderId ? `account-${traderId}` : 'account',
() => api.getAccount(traderId),
{
refreshInterval: 5000,
refreshInterval: 15000, // 15秒刷新配合后端缓存
revalidateOnFocus: false,
dedupingInterval: 10000,
}
);
@@ -60,7 +64,10 @@ export function EquityChart({ traderId }: EquityChartProps) {
);
}
if (!history || history.length === 0) {
// 过滤掉无效数据total_equity为0或小于1的数据点API失败导致
const validHistory = history?.filter(point => point.total_equity > 1) || [];
if (!validHistory || validHistory.length === 0) {
return (
<div className="binance-card p-6">
<h3 className="text-lg font-semibold mb-6" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3>
@@ -76,12 +83,14 @@ export function EquityChart({ traderId }: EquityChartProps) {
// 限制显示最近的数据点(性能优化)
// 如果数据超过2000个点只显示最近2000个
const MAX_DISPLAY_POINTS = 2000;
const displayHistory = history.length > MAX_DISPLAY_POINTS
? history.slice(-MAX_DISPLAY_POINTS)
: history;
const displayHistory = validHistory.length > MAX_DISPLAY_POINTS
? validHistory.slice(-MAX_DISPLAY_POINTS)
: validHistory;
// 计算初始余额(使用第一个数据点)
const initialBalance = history[0]?.total_equity || 1000;
// 计算初始余额(使用第一个有效数据点如果无数据则从account获取最后才用默认值
const initialBalance = validHistory[0]?.total_equity
|| account?.total_equity
|| 100; // 默认值改为100与常见配置一致
// 转换数据格式
const chartData = displayHistory.map((point) => {
@@ -152,19 +161,19 @@ export function EquityChart({ traderId }: EquityChartProps) {
};
return (
<div className="binance-card p-5 animate-fade-in">
<div className="binance-card p-3 sm:p-5 animate-fade-in">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-bold mb-2" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3>
<div className="flex items-baseline gap-4">
<span className="text-3xl font-bold mono" style={{ color: '#EAECEF' }}>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4">
<div className="flex-1">
<h3 className="text-base sm:text-lg font-bold mb-2" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3>
<div className="flex flex-col sm:flex-row sm:items-baseline gap-2 sm:gap-4">
<span className="text-2xl sm:text-3xl font-bold mono" style={{ color: '#EAECEF' }}>
{account?.total_equity.toFixed(2) || '0.00'}
<span className="text-lg ml-1" style={{ color: '#848E9C' }}>USDT</span>
<span className="text-base sm:text-lg ml-1" style={{ color: '#848E9C' }}>USDT</span>
</span>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 flex-wrap">
<span
className="text-lg font-bold mono px-3 py-1 rounded"
className="text-sm sm:text-lg font-bold mono px-2 sm:px-3 py-1 rounded"
style={{
color: isProfit ? '#0ECB81' : '#F6465D',
background: isProfit ? 'rgba(14, 203, 129, 0.1)' : 'rgba(246, 70, 93, 0.1)',
@@ -174,7 +183,7 @@ export function EquityChart({ traderId }: EquityChartProps) {
{isProfit ? '▲' : '▼'} {isProfit ? '+' : ''}
{currentValue.raw_pnl_pct}%
</span>
<span className="text-sm mono" style={{ color: '#848E9C' }}>
<span className="text-xs sm:text-sm mono" style={{ color: '#848E9C' }}>
({isProfit ? '+' : ''}{currentValue.raw_pnl.toFixed(2)} USDT)
</span>
</div>
@@ -182,10 +191,10 @@ export function EquityChart({ traderId }: EquityChartProps) {
</div>
{/* Display Mode Toggle */}
<div className="flex gap-1 rounded p-1" style={{ background: '#0B0E11', border: '1px solid #2B3139' }}>
<div className="flex gap-0.5 sm:gap-1 rounded p-0.5 sm:p-1 self-start sm:self-auto" style={{ background: '#0B0E11', border: '1px solid #2B3139' }}>
<button
onClick={() => setDisplayMode('dollar')}
className="px-4 py-2 rounded text-sm font-bold transition-all"
className="px-3 sm:px-4 py-1.5 sm:py-2 rounded text-xs sm:text-sm font-bold transition-all"
style={displayMode === 'dollar'
? { background: '#F0B90B', color: '#000', boxShadow: '0 2px 8px rgba(240, 185, 11, 0.4)' }
: { background: 'transparent', color: '#848E9C' }
@@ -195,7 +204,7 @@ export function EquityChart({ traderId }: EquityChartProps) {
</button>
<button
onClick={() => setDisplayMode('percent')}
className="px-4 py-2 rounded text-sm font-bold transition-all"
className="px-3 sm:px-4 py-1.5 sm:py-2 rounded text-xs sm:text-sm font-bold transition-all"
style={displayMode === 'percent'
? { background: '#F0B90B', color: '#000', boxShadow: '0 2px 8px rgba(240, 185, 11, 0.4)' }
: { background: 'transparent', color: '#848E9C' }
@@ -248,39 +257,40 @@ export function EquityChart({ traderId }: EquityChartProps) {
}}
/>
<Line
type="monotone"
type="natural"
dataKey="value"
stroke="url(#colorGradient)"
strokeWidth={2.5}
strokeWidth={3}
dot={chartData.length > 50 ? false : { fill: '#F0B90B', r: 3 }}
activeDot={{ r: 6, fill: '#FCD535', stroke: '#F0B90B', strokeWidth: 2 }}
connectNulls={true}
/>
</LineChart>
</ResponsiveContainer>
</div>
{/* 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-2 sm:grid-cols-4 gap-2 sm: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="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-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{initialBalance.toFixed(2)} USDT
</div>
</div>
<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' }}>{t('currentEquity', language)}</div>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{currentValue.raw_equity.toFixed(2)} USDT
</div>
</div>
<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' }}>{t('historicalCycles', language)}</div>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>{history.length} {t('cycles', language)}</div>
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>{validHistory.length} {t('cycles', language)}</div>
</div>
<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' }}>{t('displayRange', language)}</div>
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{history.length > MAX_DISPLAY_POINTS
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
{validHistory.length > MAX_DISPLAY_POINTS
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
: t('allData', language)
}