diff --git a/frontend/src/pages/backtest/charts/StrategyNavChart.tsx b/frontend/src/pages/backtest/charts/StrategyNavChart.tsx
index 30a998b..06c4fdf 100644
--- a/frontend/src/pages/backtest/charts/StrategyNavChart.tsx
+++ b/frontend/src/pages/backtest/charts/StrategyNavChart.tsx
@@ -25,6 +25,18 @@ export function StrategyNavChart({ result }: Props) {
const benchmarkValues = dates.map(d => benchmarkByDate.get(d) ?? null)
const hasBenchmark = benchmarkValues.some(v => v != null)
const ddValues = result.drawdown_curve.map(r => r.value * 100)
+ const positionValues = result.equity_curve.map(r => {
+ if (r.exposure != null) return r.exposure * 100
+ if (r.cash != null && r.value > 0) {
+ return Math.max(0, Math.min(100, (1 - r.cash / r.value) * 100))
+ }
+ return null
+ })
+ const hasPosition = positionValues.some(v => v != null)
+ const navColor = '#3b82f6'
+ const benchmarkColor = '#64748b'
+ const drawdownColor = '#f04438'
+ const positionColor = '#f59e0b'
return {
animation: false,
@@ -91,6 +103,15 @@ export function StrategyNavChart({ result }: Props) {
splitLine: { lineStyle: { color: ct.grid } },
axisLine: { show: false },
},
+ ...(hasPosition ? [{
+ type: 'value',
+ gridIndex: 0,
+ min: 0,
+ max: 100,
+ show: false,
+ splitLine: { show: false },
+ axisLine: { show: false },
+ }] : []),
],
dataZoom: [
{
@@ -127,10 +148,11 @@ export function StrategyNavChart({ result }: Props) {
if (p.value == null) continue
const isDrawdown = p.seriesName === '回撤'
const isBenchmark = p.seriesName === '同期上证指数'
+ const isPosition = p.seriesName === '仓位'
html += `
${p.seriesName}
${
- isDrawdown
+ isDrawdown || isPosition
? `${(p.value as number).toFixed(2)}%`
: isBenchmark
? `${valueFmt.format(p.value as number)} 点`
@@ -149,7 +171,8 @@ export function StrategyNavChart({ result }: Props) {
yAxisIndex: hasBenchmark ? 1 : 0,
data: navValues,
symbol: 'none',
- lineStyle: { color: '#3b82f6', width: 2.2 },
+ itemStyle: { color: navColor },
+ lineStyle: { color: navColor, width: 2.2 },
areaStyle: {
color: {
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
@@ -168,7 +191,8 @@ export function StrategyNavChart({ result }: Props) {
data: benchmarkValues,
symbol: 'none',
connectNulls: true,
- lineStyle: { color: 'rgba(148,163,184,0.45)', width: 1, type: 'dashed' },
+ itemStyle: { color: benchmarkColor },
+ lineStyle: { color: benchmarkColor, opacity: 0.55, width: 1, type: 'dashed' },
}] : []),
{
name: '回撤',
@@ -177,9 +201,23 @@ export function StrategyNavChart({ result }: Props) {
yAxisIndex: 2,
data: ddValues,
symbol: 'none',
- lineStyle: { color: 'rgba(240,68,56,0.6)', width: 1 },
+ itemStyle: { color: drawdownColor },
+ lineStyle: { color: drawdownColor, opacity: 0.65, width: 1 },
areaStyle: { color: 'rgba(240,68,56,0.12)' },
},
+ ...(hasPosition ? [{
+ name: '仓位',
+ type: 'line',
+ step: 'end',
+ xAxisIndex: 0,
+ yAxisIndex: 3,
+ data: positionValues,
+ symbol: 'none',
+ itemStyle: { color: positionColor },
+ lineStyle: { color: positionColor, width: 1.2 },
+ areaStyle: { color: 'rgba(245,158,11,0.08)' },
+ z: 1,
+ }] : []),
],
} as any
}, [result.equity_curve, result.drawdown_curve, result.benchmark_curve, result.run_id, ct])
@@ -190,16 +228,22 @@ export function StrategyNavChart({ result }: Props) {
-
+
策略净值
-
+
回撤
+ {result.equity_curve.some(r => r.exposure != null || (r.cash != null && r.value > 0)) && (
+
+
+ 仓位
+
+ )}
{(result.benchmark_curve?.length ?? 0) > 0 && (
-
+
同期上证指数
)}