mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 21:12:00 +08:00
Merge branch 'main' of github.com:Icyoung/nofx
# Conflicts: # config.json.example # config/config.go # main.go # trader/auto_trader.go # web/src/App.tsx # web/src/components/CompetitionPage.tsx
This commit is contained in:
@@ -47,10 +47,38 @@ function App() {
|
||||
const { user, token, logout, isLoading } = useAuth();
|
||||
const { config: systemConfig, loading: configLoading } = useSystemConfig();
|
||||
const [route, setRoute] = useState(window.location.pathname);
|
||||
const [currentPage, setCurrentPage] = useState<Page>('competition');
|
||||
|
||||
// 从URL hash读取初始页面状态(支持刷新保持页面)
|
||||
const getInitialPage = (): Page => {
|
||||
const hash = window.location.hash.slice(1); // 去掉 #
|
||||
return hash === 'trader' || hash === 'details' ? 'trader' : 'competition';
|
||||
};
|
||||
|
||||
const [currentPage, setCurrentPage] = useState<Page>(getInitialPage());
|
||||
const [selectedTraderId, setSelectedTraderId] = useState<string | undefined>();
|
||||
const [lastUpdate, setLastUpdate] = useState<string>('--:--:--');
|
||||
|
||||
// 监听URL hash变化,同步页面状态
|
||||
useEffect(() => {
|
||||
const handleHashChange = () => {
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (hash === 'trader' || hash === 'details') {
|
||||
setCurrentPage('trader');
|
||||
} else if (hash === 'competition' || hash === '') {
|
||||
setCurrentPage('competition');
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('hashchange', handleHashChange);
|
||||
return () => window.removeEventListener('hashchange', handleHashChange);
|
||||
}, []);
|
||||
|
||||
// 切换页面时更新URL hash
|
||||
const navigateToPage = (page: Page) => {
|
||||
setCurrentPage(page);
|
||||
window.location.hash = page === 'competition' ? '' : 'trader';
|
||||
};
|
||||
|
||||
// 获取trader列表
|
||||
const { data: traders } = useSWR<TraderInfo[]>('traders', api.getTraders, {
|
||||
refreshInterval: 10000,
|
||||
@@ -274,25 +302,6 @@ function App() {
|
||||
退出
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Status Indicator (only show on trader page) */}
|
||||
{currentPage === 'trader' && status && (
|
||||
<div
|
||||
className="flex items-center gap-2 px-3 py-2 rounded"
|
||||
style={status.is_running
|
||||
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81', border: '1px solid rgba(14, 203, 129, 0.2)' }
|
||||
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D', border: '1px solid rgba(246, 70, 93, 0.2)' }
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${status.is_running ? 'pulse-glow' : ''}`}
|
||||
style={{ background: status.is_running ? '#0ECB81' : '#F6465D' }}
|
||||
/>
|
||||
<span className="font-semibold mono text-xs">
|
||||
{t(status.is_running ? 'running' : 'stopped', language)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import useSWR from 'swr';
|
||||
import { api } from '../lib/api';
|
||||
import type { CompetitionTraderData } from '../types';
|
||||
import { getTraderColor } from '../utils/traderColors';
|
||||
|
||||
interface ComparisonChartProps {
|
||||
traders: CompetitionTraderData[];
|
||||
@@ -171,15 +172,8 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
];
|
||||
};
|
||||
|
||||
// Trader颜色配置 - 使用更鲜艳对比度更高的颜色
|
||||
const getTraderColor = (traderId: string) => {
|
||||
const trader = traders.find((t) => t.trader_id === traderId);
|
||||
if (trader?.ai_model === 'qwen') {
|
||||
return '#c084fc'; // purple-400 (更亮)
|
||||
} else {
|
||||
return '#60a5fa'; // blue-400 (更亮)
|
||||
}
|
||||
};
|
||||
// 使用统一的颜色分配逻辑(与Leaderboard保持一致)
|
||||
const traderColor = (traderId: string) => getTraderColor(traders, traderId);
|
||||
|
||||
// 自定义Tooltip - Binance Style
|
||||
const CustomTooltip = ({ active, payload }: any) => {
|
||||
@@ -199,7 +193,7 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
<div key={trader.trader_id} className="mb-1.5 last:mb-0">
|
||||
<div
|
||||
className="text-xs font-semibold mb-0.5"
|
||||
style={{ color: getTraderColor(trader.trader_id) }}
|
||||
style={{ color: traderColor(trader.trader_id) }}
|
||||
>
|
||||
{trader.trader_name}
|
||||
</div>
|
||||
@@ -240,8 +234,8 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop offset="5%" stopColor={getTraderColor(trader.trader_id)} stopOpacity={0.9} />
|
||||
<stop offset="95%" stopColor={getTraderColor(trader.trader_id)} stopOpacity={0.2} />
|
||||
<stop offset="5%" stopColor={traderColor(trader.trader_id)} stopOpacity={0.9} />
|
||||
<stop offset="95%" stopColor={traderColor(trader.trader_id)} stopOpacity={0.2} />
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
@@ -288,10 +282,10 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
key={trader.trader_id}
|
||||
type="monotone"
|
||||
dataKey={`${trader.trader_id}_pnl_pct`}
|
||||
stroke={getTraderColor(trader.trader_id)}
|
||||
stroke={traderColor(trader.trader_id)}
|
||||
strokeWidth={3}
|
||||
dot={displayData.length < 50 ? { fill: getTraderColor(trader.trader_id), r: 3 } : false}
|
||||
activeDot={{ r: 6, fill: getTraderColor(trader.trader_id), stroke: '#fff', strokeWidth: 2 }}
|
||||
dot={displayData.length < 50 ? { fill: traderColor(trader.trader_id), r: 3 } : false}
|
||||
activeDot={{ r: 6, fill: traderColor(trader.trader_id), stroke: '#fff', strokeWidth: 2 }}
|
||||
name={trader.trader_name}
|
||||
connectNulls
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import useSWR from 'swr';
|
||||
import { api } from '../lib/api';
|
||||
import type { CompetitionData } from '../types';
|
||||
import { ComparisonChart } from './ComparisonChart';
|
||||
import { getTraderColor } from '../utils/traderColors';
|
||||
|
||||
export function CompetitionPage() {
|
||||
const { data: competition } = useSWR<CompetitionData>(
|
||||
@@ -105,7 +106,7 @@ export function CompetitionPage() {
|
||||
<div className="space-y-2">
|
||||
{sortedTraders.map((trader, index) => {
|
||||
const isLeader = index === 0;
|
||||
const aiModelColor = trader.ai_model === 'qwen' ? '#c084fc' : '#60a5fa';
|
||||
const traderColor = getTraderColor(sortedTraders, trader.trader_id);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -125,7 +126,7 @@ export function CompetitionPage() {
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-sm" style={{ color: '#EAECEF' }}>{trader.trader_name}</div>
|
||||
<div className="text-xs mono font-semibold" style={{ color: aiModelColor }}>
|
||||
<div className="text-xs mono font-semibold" style={{ color: traderColor }}>
|
||||
{trader.ai_model.toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
@@ -220,7 +221,7 @@ export function CompetitionPage() {
|
||||
<div className="text-center">
|
||||
<div
|
||||
className="text-base font-bold mb-2"
|
||||
style={{ color: trader.ai_model === 'qwen' ? '#c084fc' : '#60a5fa' }}
|
||||
style={{ color: getTraderColor(sortedTraders, trader.trader_id) }}
|
||||
>
|
||||
{trader.trader_name}
|
||||
</div>
|
||||
|
||||
31
web/src/utils/traderColors.ts
Normal file
31
web/src/utils/traderColors.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
// Trader颜色配置 - 统一的颜色分配逻辑
|
||||
// 用于 ComparisonChart 和 Leaderboard,确保颜色一致性
|
||||
|
||||
export const TRADER_COLORS = [
|
||||
'#60a5fa', // blue-400
|
||||
'#c084fc', // purple-400
|
||||
'#34d399', // emerald-400
|
||||
'#fb923c', // orange-400
|
||||
'#f472b6', // pink-400
|
||||
'#fbbf24', // amber-400
|
||||
'#38bdf8', // sky-400
|
||||
'#a78bfa', // violet-400
|
||||
'#4ade80', // green-400
|
||||
'#fb7185', // rose-400
|
||||
];
|
||||
|
||||
/**
|
||||
* 根据trader的索引位置获取颜色
|
||||
* @param traders - trader列表
|
||||
* @param traderId - 当前trader的ID
|
||||
* @returns 对应的颜色值
|
||||
*/
|
||||
export function getTraderColor(
|
||||
traders: Array<{ trader_id: string }>,
|
||||
traderId: string
|
||||
): string {
|
||||
const traderIndex = traders.findIndex((t) => t.trader_id === traderId);
|
||||
if (traderIndex === -1) return TRADER_COLORS[0]; // 默认返回第一个颜色
|
||||
// 如果超出颜色池大小,循环使用
|
||||
return TRADER_COLORS[traderIndex % TRADER_COLORS.length];
|
||||
}
|
||||
Reference in New Issue
Block a user