318 lines
15 KiB
TypeScript
318 lines
15 KiB
TypeScript
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>
|
||
);
|
||
};
|