gcx/frontend/admin-web/src/views/analytics/MarketMakerPage.tsx

318 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
/**
* 做市商管理仪表盘
*
* 做市商列表、流动性池、订单簿深度、市场健康指标、风险预警
*/
const stats = [
{ label: '活跃做市商', value: '12', change: '+2', trend: 'up' as const, color: 'var(--color-primary)' },
{ label: '总流动性', value: '$5.2M', change: '+8.3%', trend: 'up' as const, color: 'var(--color-success)' },
{ label: '日均交易量', value: '$320K', change: '+12.5%', trend: 'up' as const, color: 'var(--color-info)' },
{ label: '平均价差', value: '1.8%', change: '-0.3%', trend: 'down' as const, color: 'var(--color-warning)' },
];
const marketMakers = [
{ name: 'AlphaLiquidity', status: 'active' as const, tvl: '$1,250,000', spread: '1.2%', volume: '$85,000', pnl: '+$12,340' },
{ name: 'BetaMarkets', status: 'active' as const, tvl: '$980,000', spread: '1.5%', volume: '$72,000', pnl: '+$8,920' },
{ name: 'GammaTrading', status: 'active' as const, tvl: '$850,000', spread: '1.8%', volume: '$65,400', pnl: '+$6,780' },
{ name: 'DeltaCapital', status: 'paused' as const, tvl: '$620,000', spread: '2.1%', volume: '$0', pnl: '-$1,230' },
{ name: 'EpsilonFund', status: 'active' as const, tvl: '$540,000', spread: '1.6%', volume: '$43,200', pnl: '+$5,410' },
{ name: 'ZetaPartners', status: 'active' as const, tvl: '$430,000', spread: '2.0%', volume: '$31,800', pnl: '+$3,670' },
{ name: 'EtaVentures', status: 'suspended' as const, tvl: '$0', spread: '-', volume: '$0', pnl: '-$4,560' },
{ name: 'ThetaQuant', status: 'active' as const, tvl: '$280,000', spread: '1.9%', volume: '$22,600', pnl: '+$2,890' },
];
const statusConfig: Record<string, { bg: string; color: string; label: string }> = {
active: { bg: 'var(--color-success-light)', color: 'var(--color-success)', label: '活跃' },
paused: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)', label: '暂停' },
suspended: { bg: 'var(--color-error-light)', color: 'var(--color-error)', label: '停用' },
};
const liquidityPools = [
{ category: '餐饮', tvl: '$1,560,000', percent: 30, makers: 8, color: 'var(--color-primary)' },
{ category: '零售', tvl: '$1,300,000', percent: 25, makers: 7, color: 'var(--color-success)' },
{ category: '娱乐', tvl: '$1,040,000', percent: 20, makers: 6, color: 'var(--color-info)' },
{ category: '旅游', tvl: '$780,000', percent: 15, makers: 5, color: 'var(--color-warning)' },
{ category: '数码', tvl: '$520,000', percent: 10, makers: 4, color: 'var(--color-error)' },
];
const healthIndicators = [
{ name: 'Bid-Ask 价差', value: '1.8%', target: '< 3.0%', status: 'good' as const },
{ name: '滑点 (Slippage)', value: '0.42%', target: '< 1.0%', status: 'good' as const },
{ name: '成交率 (Fill Rate)', value: '94.7%', target: '> 90%', status: 'good' as const },
{ name: '流动性深度', value: '$5.2M', target: '> $3M', status: 'good' as const },
{ name: '价格偏差', value: '2.1%', target: '< 2.0%', status: 'warning' as const },
{ name: '做市商覆盖率', value: '87%', target: '> 85%', status: 'good' as const },
];
const riskAlerts = [
{ time: '14:25', maker: 'DeltaCapital', type: '流动性撤出', desc: '30分钟内撤出65%流动性,已自动暂停', severity: 'high' as const },
{ time: '13:40', maker: 'EtaVentures', type: '异常交易', desc: '检测到自成交行为,账户已停用待审', severity: 'high' as const },
{ time: '12:15', maker: 'ZetaPartners', type: '价差偏高', desc: '餐饮品类价差达3.2%,超出阈值', severity: 'medium' as const },
{ time: '11:00', maker: 'ThetaQuant', type: 'API延迟', desc: '报价延迟升至800ms可能影响做市质量', severity: 'low' as const },
];
const severityConfig: Record<string, { bg: string; color: string; label: string }> = {
high: { bg: 'var(--color-error-light)', color: 'var(--color-error)', label: '高' },
medium: { bg: 'var(--color-warning-light)', color: 'var(--color-warning)', label: '中' },
low: { bg: 'var(--color-info-light)', color: 'var(--color-info)', label: '低' },
};
export const MarketMakerPage: React.FC = () => {
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ font: 'var(--text-h1)', color: 'var(--color-text-primary)' }}></h1>
<button style={{
padding: '8px 16px', border: 'none', borderRadius: 'var(--radius-sm)',
background: 'var(--color-primary)', color: 'white', cursor: 'pointer', font: 'var(--text-label)',
}}></button>
</div>
{/* Stats Grid */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
{stats.map(stat => (
<div key={stat.label} style={{
background: 'var(--color-surface)',
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)',
padding: 20,
}}>
<div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)', marginBottom: 8 }}>
{stat.label}
</div>
<div style={{ font: 'var(--text-h1)', color: stat.color }}>
{stat.value}
</div>
<div style={{
font: 'var(--text-label-sm)',
color: stat.trend === 'up'
? (stat.label === '平均价差' ? 'var(--color-error)' : 'var(--color-success)')
: (stat.label === '平均价差' ? 'var(--color-success)' : 'var(--color-error)'),
marginTop: 4,
}}>
{stat.change}
</div>
</div>
))}
</div>
{/* Market Maker Table */}
<div style={{
background: 'var(--color-surface)',
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)',
overflow: 'hidden',
marginBottom: 24,
}}>
<div style={{ padding: '16px 20px', font: 'var(--text-h3)', borderBottom: '1px solid var(--color-border-light)' }}>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ background: 'var(--color-gray-50)' }}>
{['做市商', '状态', 'TVL', '价差', '日交易量', 'P&L', '操作'].map(h => (
<th key={h} style={{
font: 'var(--text-label-sm)',
color: 'var(--color-text-tertiary)',
padding: '10px 14px',
textAlign: 'left',
}}>{h}</th>
))}
</tr>
</thead>
<tbody>
{marketMakers.map(mm => {
const s = statusConfig[mm.status];
return (
<tr key={mm.name} style={{ borderBottom: '1px solid var(--color-border-light)' }}>
<td style={{ font: 'var(--text-label-sm)', padding: '10px 14px' }}>{mm.name}</td>
<td style={{ padding: '10px 14px' }}>
<span style={{
padding: '2px 8px',
borderRadius: 'var(--radius-full)',
background: s.bg,
color: s.color,
font: 'var(--text-caption)',
}}>{s.label}</span>
</td>
<td style={{ font: 'var(--text-body-sm)', padding: '10px 14px' }}>{mm.tvl}</td>
<td style={{ font: 'var(--text-body-sm)', padding: '10px 14px' }}>{mm.spread}</td>
<td style={{ font: 'var(--text-body-sm)', padding: '10px 14px' }}>{mm.volume}</td>
<td style={{
font: 'var(--text-label-sm)',
padding: '10px 14px',
color: mm.pnl.startsWith('+') ? 'var(--color-success)' : 'var(--color-error)',
}}>{mm.pnl}</td>
<td style={{ padding: '10px 14px', display: 'flex', gap: 4 }}>
<button style={{ padding: '4px 10px', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', background: 'none', cursor: 'pointer', font: 'var(--text-caption)', color: 'var(--color-text-secondary)' }}></button>
{mm.status === 'active' && (
<button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-warning)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}></button>
)}
{mm.status === 'paused' && (
<button style={{ padding: '4px 10px', border: 'none', borderRadius: 'var(--radius-sm)', background: 'var(--color-success)', cursor: 'pointer', font: 'var(--text-caption)', color: 'white' }}></button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{/* Liquidity Pools + Order Book Depth */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 24 }}>
{/* Liquidity Pool Distribution */}
<div style={{
background: 'var(--color-surface)',
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)',
padding: 20,
}}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div>
{liquidityPools.map(pool => (
<div key={pool.category} style={{ marginBottom: 14 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
<span style={{ font: 'var(--text-label-sm)', color: 'var(--color-text-primary)' }}>
{pool.category}
<span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)', marginLeft: 8 }}>
{pool.makers}
</span>
</span>
<span style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-tertiary)' }}>
{pool.tvl} ({pool.percent}%)
</span>
</div>
<div style={{
width: '100%', height: 8,
background: 'var(--color-gray-100)',
borderRadius: 4,
overflow: 'hidden',
}}>
<div style={{
width: `${pool.percent}%`,
height: '100%',
background: pool.color,
borderRadius: 4,
}} />
</div>
</div>
))}
</div>
{/* Order Book Depth */}
<div style={{
background: 'var(--color-surface)',
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)',
padding: 20,
}}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}>簿</div>
<div style={{
height: 260,
background: 'var(--color-gray-50)',
borderRadius: 'var(--radius-sm)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--color-text-tertiary)',
}}>
Recharts (Bid/Ask )
</div>
</div>
</div>
{/* Market Health + Risk Alerts */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
{/* Market Health Indicators */}
<div style={{
background: 'var(--color-surface)',
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)',
padding: 20,
}}>
<div style={{ font: 'var(--text-h3)', marginBottom: 16 }}></div>
{healthIndicators.map(ind => (
<div key={ind.name} style={{
display: 'flex',
alignItems: 'center',
padding: '10px 0',
borderBottom: '1px solid var(--color-border-light)',
}}>
<span style={{
width: 8, height: 8,
borderRadius: '50%',
background: ind.status === 'good' ? 'var(--color-success)' : 'var(--color-warning)',
marginRight: 10,
flexShrink: 0,
}} />
<span style={{ flex: 1, font: 'var(--text-body-sm)' }}>{ind.name}</span>
<span style={{
font: 'var(--text-label-sm)',
color: ind.status === 'good' ? 'var(--color-success)' : 'var(--color-warning)',
marginRight: 12,
}}>{ind.value}</span>
<span style={{
font: 'var(--text-caption)',
color: 'var(--color-text-tertiary)',
}}>{ind.target}</span>
</div>
))}
</div>
{/* Risk Alerts */}
<div style={{
background: 'var(--color-surface)',
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border-light)',
padding: 20,
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<div style={{ font: 'var(--text-h3)' }}></div>
<span style={{
padding: '2px 8px',
borderRadius: 'var(--radius-full)',
background: 'var(--color-error-light)',
color: 'var(--color-error)',
font: 'var(--text-caption)',
}}>
{riskAlerts.filter(a => a.severity === 'high').length}
</span>
</div>
{riskAlerts.map((alert, i) => {
const sev = severityConfig[alert.severity];
return (
<div key={i} style={{
padding: 12,
background: 'var(--color-gray-50)',
borderRadius: 'var(--radius-sm)',
marginBottom: i < riskAlerts.length - 1 ? 8 : 0,
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{
padding: '2px 6px',
borderRadius: 'var(--radius-full)',
background: sev.bg,
color: sev.color,
font: 'var(--text-caption)',
}}>{sev.label}</span>
<span style={{ font: 'var(--text-label-sm)' }}>{alert.maker}</span>
<span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{alert.type}</span>
</div>
<span style={{ font: 'var(--text-caption)', color: 'var(--color-text-tertiary)' }}>{alert.time}</span>
</div>
<div style={{ font: 'var(--text-body-sm)', color: 'var(--color-text-secondary)' }}>
{alert.desc}
</div>
</div>
);
})}
</div>
</div>
</div>
);
};