1
This commit is contained in:
parent
861f1dc548
commit
d5ef525224
|
|
@ -1208,9 +1208,32 @@ const GlobalConfig = () => {
|
|||
},
|
||||
]
|
||||
|
||||
// 统计过滤可视化:解析全局 STATS_* 配置
|
||||
const rawStatsSymbolFilters = configs?.STATS_SYMBOL_FILTERS
|
||||
const rawStatsHourFilters = configs?.STATS_HOUR_FILTERS
|
||||
|
||||
const statsSymbolFiltersValue =
|
||||
rawStatsSymbolFilters && typeof rawStatsSymbolFilters === 'object'
|
||||
? (rawStatsSymbolFilters.value ?? rawStatsSymbolFilters)
|
||||
: null
|
||||
|
||||
const statsHourFiltersValue =
|
||||
rawStatsHourFilters && typeof rawStatsHourFilters === 'object'
|
||||
? (rawStatsHourFilters.value ?? rawStatsHourFilters)
|
||||
: null
|
||||
|
||||
const statsBlacklist = Array.isArray(statsSymbolFiltersValue?.blacklist)
|
||||
? statsSymbolFiltersValue.blacklist
|
||||
: []
|
||||
const statsWhitelist = Array.isArray(statsSymbolFiltersValue?.whitelist)
|
||||
? statsSymbolFiltersValue.whitelist
|
||||
: []
|
||||
const statsHours = Array.isArray(statsHourFiltersValue?.hours)
|
||||
? statsHourFiltersValue.hours
|
||||
: []
|
||||
|
||||
const statsGeneratedAt = statsSymbolFiltersValue?.generated_at || statsHourFiltersValue?.generated_at || null
|
||||
const statsDays = statsSymbolFiltersValue?.days || statsHourFiltersValue?.days || null
|
||||
|
||||
return (
|
||||
<div className="global-config">
|
||||
|
|
@ -1645,15 +1668,16 @@ const GlobalConfig = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="config-tabs" style={{ display: 'flex', gap: '10px', overflowX: 'auto', paddingBottom: '5px' }}>
|
||||
{[
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'risk', label: '风险控制' },
|
||||
{ key: 'strategy', label: '策略参数' },
|
||||
{ key: 'scan', label: '市场扫描' },
|
||||
{ key: 'position', label: '仓位控制' },
|
||||
].map(tab => (
|
||||
{/* Tabs */}
|
||||
<div className="config-tabs" style={{ display: 'flex', gap: '10px', overflowX: 'auto', paddingBottom: '5px' }}>
|
||||
{[
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'risk', label: '风险控制' },
|
||||
{ key: 'strategy', label: '策略参数' },
|
||||
{ key: 'scan', label: '市场扫描' },
|
||||
{ key: 'position', label: '仓位控制' },
|
||||
{ key: 'stats', label: '统计过滤' },
|
||||
].map(tab => (
|
||||
<button
|
||||
key={tab.key}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
|
|
@ -1676,13 +1700,169 @@ const GlobalConfig = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* 统计过滤可视化(仅在“统计过滤” Tab 展示) */}
|
||||
{activeTab === 'stats' && (
|
||||
<div style={{ marginBottom: '24px', padding: '16px', borderRadius: '8px', background: '#f8f9fb', border: '1px solid #e1e5ee' }}>
|
||||
<div style={{ marginBottom: '8px', display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: '12px', flexWrap: 'wrap' }}>
|
||||
<div>
|
||||
<h4 style={{ margin: 0, fontSize: '15px' }}>统计过滤总览</h4>
|
||||
<div style={{ fontSize: '12px', color: '#6c757d', marginTop: '4px' }}>
|
||||
基于最近 {statsDays || 7} 天的交易统计自动生成:差标的与差时段会被自动降权并提高信号门槛,仅影响自动交易,不会一刀切禁止。
|
||||
</div>
|
||||
</div>
|
||||
{statsGeneratedAt && (
|
||||
<div style={{ fontSize: '12px', color: '#6c757d' }}>生成时间(UTC):{String(statsGeneratedAt).replace('T', ' ').slice(0, 19)}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 2fr) minmax(0, 1.6fr)', gap: '16px' }}>
|
||||
{/* Symbol 白/黑名单 */}
|
||||
<div style={{ background: '#ffffff', borderRadius: '6px', padding: '12px', border: '1px solid #e5e9f0' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
||||
<strong style={{ fontSize: '13px' }}>交易对白名单 / 黑名单</strong>
|
||||
<span style={{ fontSize: '11px', color: '#6c757d' }}>
|
||||
白名单 {statsWhitelist.length} · 黑名单 {statsBlacklist.length}
|
||||
</span>
|
||||
</div>
|
||||
{(statsWhitelist.length === 0 && statsBlacklist.length === 0) ? (
|
||||
<div style={{ fontSize: '12px', color: '#999' }}>暂无统计结果(需先定时跑 7 天聚合脚本)。</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: '10px' }}>
|
||||
<div>
|
||||
<div style={{ fontSize: '12px', fontWeight: 600, marginBottom: '4px', color: '#155724' }}>白名单(近期表现较好)</div>
|
||||
<div style={{ maxHeight: '220px', overflowY: 'auto', border: '1px solid #e9ecef', borderRadius: '4px' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '12px' }}>
|
||||
<thead>
|
||||
<tr style={{ background: '#f1f3f5' }}>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'left' }}>Symbol</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>笔数</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>净盈亏</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>胜率%</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{statsWhitelist.slice(0, 20).map((item) => (
|
||||
<tr key={item.symbol}>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', fontWeight: 600 }}>{item.symbol}</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', textAlign: 'right' }}>{item.trade_count}</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', textAlign: 'right', color: Number(item.net_pnl) >= 0 ? '#28a745' : '#dc3545' }}>
|
||||
{Number(item.net_pnl) >= 0 ? '+' : ''}{Number(item.net_pnl).toFixed(2)}
|
||||
</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', textAlign: 'right' }}>{Number(item.win_rate_pct).toFixed(1)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: '12px', fontWeight: 600, marginBottom: '4px', color: '#721c24' }}>黑名单(软降权,考核期中)</div>
|
||||
<div style={{ maxHeight: '220px', overflowY: 'auto', border: '1px solid #f5c6cb', borderRadius: '4px' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '12px' }}>
|
||||
<thead>
|
||||
<tr style={{ background: '#f8d7da' }}>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'left' }}>Symbol</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>笔数</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>净盈亏</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>胜率%</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{statsBlacklist.slice(0, 20).map((item) => (
|
||||
<tr key={item.symbol}>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f5c6cb', fontWeight: 600 }}>{item.symbol}</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f5c6cb', textAlign: 'right' }}>{item.trade_count}</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f5c6cb', textAlign: 'right', color: '#dc3545' }}>
|
||||
{Number(item.net_pnl).toFixed(2)}
|
||||
</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f5c6cb', textAlign: 'right' }}>{Number(item.win_rate_pct).toFixed(1)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 按小时时段过滤 */}
|
||||
<div style={{ background: '#ffffff', borderRadius: '6px', padding: '12px', border: '1px solid #e5e9f0' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
||||
<strong style={{ fontSize: '13px' }}>按小时过滤(北京时间)</strong>
|
||||
<span style={{ fontSize: '11px', color: '#6c757d' }}>bucket=bad → 降仓 & 提高信号门槛</span>
|
||||
</div>
|
||||
{statsHours.length === 0 ? (
|
||||
<div style={{ fontSize: '12px', color: '#999' }}>暂无时段统计结果。</div>
|
||||
) : (
|
||||
<div style={{ maxHeight: '260px', overflowY: 'auto', border: '1px solid #e9ecef', borderRadius: '4px' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '12px' }}>
|
||||
<thead>
|
||||
<tr style={{ background: '#f1f3f5' }}>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'left' }}>小时</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>笔数</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>净盈亏</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'center' }}>分组</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>仓位系数</th>
|
||||
<th style={{ padding: '4px 6px', textAlign: 'right' }}>信号+档数</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{statsHours
|
||||
.slice()
|
||||
.sort((a, b) => Number(a.hour) - Number(b.hour))
|
||||
.map((item) => {
|
||||
const bucket = String(item.bucket || '').toLowerCase()
|
||||
const rowBg =
|
||||
bucket === 'bad'
|
||||
? '#fff5f5'
|
||||
: bucket === 'good'
|
||||
? '#f0fff4'
|
||||
: '#ffffff'
|
||||
const badgeColor =
|
||||
bucket === 'bad'
|
||||
? '#dc3545'
|
||||
: bucket === 'good'
|
||||
? '#28a745'
|
||||
: '#6c757d'
|
||||
return (
|
||||
<tr key={item.hour} style={{ background: rowBg }}>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5' }}>{item.hour}:00</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', textAlign: 'right' }}>{item.trade_count}</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', textAlign: 'right', color: Number(item.net_pnl) >= 0 ? '#28a745' : '#dc3545' }}>
|
||||
{Number(item.net_pnl) >= 0 ? '+' : ''}{Number(item.net_pnl).toFixed(2)}
|
||||
</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', textAlign: 'center' }}>
|
||||
<span style={{ display: 'inline-block', padding: '1px 6px', borderRadius: '10px', fontSize: '11px', color: badgeColor, border: `1px solid ${badgeColor}33` }}>
|
||||
{bucket || 'unknown'}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', textAlign: 'right' }}>
|
||||
{item.position_factor !== undefined ? Number(item.position_factor).toFixed(2) : '1.00'}
|
||||
</td>
|
||||
<td style={{ padding: '3px 6px', borderTop: '1px solid #f1f3f5', textAlign: 'right' }}>
|
||||
{item.signal_boost || 0}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{Object.keys(configs).length > 0 ? (
|
||||
(() => {
|
||||
const configCategories = {
|
||||
'risk': '风险控制',
|
||||
'strategy': '策略参数',
|
||||
'scan': '市场扫描',
|
||||
'position': '仓位控制',
|
||||
risk: '风险控制',
|
||||
strategy: '策略参数',
|
||||
scan: '市场扫描',
|
||||
position: '仓位控制',
|
||||
stats: '统计过滤',
|
||||
}
|
||||
|
||||
// 过滤逻辑
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user