diff options
Diffstat (limited to 'frontend/src/pages/ProjectResultsTab.svelte')
| -rw-r--r-- | frontend/src/pages/ProjectResultsTab.svelte | 108 |
1 files changed, 38 insertions, 70 deletions
diff --git a/frontend/src/pages/ProjectResultsTab.svelte b/frontend/src/pages/ProjectResultsTab.svelte index 4149213..5ff03c0 100644 --- a/frontend/src/pages/ProjectResultsTab.svelte +++ b/frontend/src/pages/ProjectResultsTab.svelte @@ -1,8 +1,8 @@ <script lang="ts"> import Status from '../components/Status.svelte'; import Chart from '../components/Chart.svelte'; - import type { EChartsOption, LineSeriesOption } from 'echarts'; -import { listRuns, getRun } from '../lib/client'; + import { Button } from '$lib/components/ui/button'; + import { listRuns, getRun } from '../lib/client'; import { extractStatusMessage } from '../lib/state'; import { fmtDateTime, @@ -55,73 +55,42 @@ import { listRuns, getRun } from '../lib/client'; const result = $derived<BacktestResult | null>(run?.result ?? null); const hasEquity = $derived(!!result && result.equity.length > 0); - const equityOption: EChartsOption = $derived.by(() => { - if (!result || !hasEquity) return {}; - const dates = result.equity.map((p) => p.date); - const series: LineSeriesOption[] = [ - { - name: "策略权益", - type: "line" as const, - data: result.equity.map((p) => p.equity), - showSymbol: false, - lineStyle: { width: 1.6 }, - itemStyle: { color: "#0f766e" } - }, - { - name: "现金", - type: "line" as const, - data: result.equity.map((p) => p.cash), - showSymbol: false, - lineStyle: { type: "dashed", opacity: 0.65 }, - itemStyle: { color: "#94a3b8" } - } + // long-format rows for the layerchart renderer ({ i, date, ...columns }) + const equityData = $derived.by(() => { + if (!result || !hasEquity) return []; + return result.equity.map((p, i) => ({ + i, + date: p.date, + equity: p.equity, + cash: p.cash, + benchmark: p.benchmark ?? undefined + })); + }); + + const hasBench = $derived(equityData.some((r) => r.benchmark !== undefined && r.benchmark !== null)); + + const equitySeries = $derived.by(() => { + if (!result || !hasEquity) return []; + const s = [ + { key: 'equity', label: '策略权益', value: 'equity', color: '#0f766e' }, + { key: 'cash', label: '现金', value: 'cash', color: '#94a3b8', dashed: true } ]; - const bench = result.equity.map((p) => (p.benchmark === null || p.benchmark === undefined ? null : p.benchmark)); - if (bench.some((x) => x !== null && x !== undefined)) { - series.push({ - name: "基准", - type: "line" as const, - data: bench as number[], - showSymbol: false, - itemStyle: { color: "#eab308" } - }); - } - return { - title: { text: undefined }, - legend: { top: 0 }, - grid: { top: 32, left: 60, right: 24, bottom: 54 }, - tooltip: { trigger: "axis" }, - xAxis: { type: "category", data: dates }, - yAxis: { type: "value", scale: true, name: "金额" }, - series - }; + if (hasBench) s.push({ key: 'benchmark', label: '基准', value: 'benchmark', color: '#eab308' }); + return s; }); - const drawdownOption: EChartsOption = $derived.by(() => { - if (!result || !hasEquity) return {}; - const dates = result.equity.map((p) => p.date); + const drawdownData = $derived.by(() => { + if (!result || !hasEquity) return []; let peak = -Infinity; - const dd = result.equity.map((p) => { + return result.equity.map((p, i) => { peak = Math.max(peak, p.equity); - return -((peak - p.equity) / peak) * 100; + return { i, date: p.date, dd: -((peak - p.equity) / peak) * 100 }; }); - return { - grid: { top: 24, left: 60, right: 24, bottom: 54 }, - tooltip: { trigger: "axis", valueFormatter: (v) => `${(v as number).toFixed(2)}%` }, - xAxis: { type: "category", data: dates }, - yAxis: { type: "value", name: "回撤 %", max: 0 }, - series: [ - { - name: "回撤", - type: "line" as const, - data: dd, - showSymbol: false, - areaStyle: { color: "rgba(30, 74, 66, 0.25)" }, - lineStyle: { width: 1 }, - itemStyle: { color: "#134e4a" } - } - ] - }; + }); + + const drawdownSeries = $derived.by(() => { + if (!result || !hasEquity) return []; + return [{ key: 'dd', label: '回撤', value: 'dd', color: '#134e4a', area: true }]; }); function switchToRun(id: string) { @@ -220,13 +189,13 @@ import { listRuns, getRun } from '../lib/client'; <div class="card stack"> <div class="row"> - <button class="btn small" aria-pressed={chartMode === "equity"} onclick={() => (chartMode = "equity")}>权益曲线({result.equity.length} 点)</button> - <button class="btn small" aria-pressed={chartMode === "drawdown"} onclick={() => (chartMode = "drawdown")}>回撤曲线</button> + <Button variant={chartMode === "equity" ? "default" : "outline"} size="sm" aria-pressed={chartMode === "equity"} onclick={() => (chartMode = "equity")}>权益曲线({result.equity.length} 点)</Button> + <Button variant={chartMode === "drawdown" ? "default" : "outline"} size="sm" aria-pressed={chartMode === "drawdown"} onclick={() => (chartMode = "drawdown")}>回撤曲线</Button> </div> {#if chartMode === "equity"} - <Chart option={equityOption} ariaLabel="策略权益曲线" height={340} /> + <Chart data={equityData} series={equitySeries} ariaLabel="策略权益曲线" height={340} /> {:else} - <Chart option={drawdownOption} ariaLabel="回撤曲线" height={300} /> + <Chart data={drawdownData} series={drawdownSeries} ariaLabel="回撤曲线" height={300} /> {/if} </div> @@ -273,10 +242,9 @@ import { listRuns, getRun } from '../lib/client'; </div> <div> <h3>策略日志</h3> - <button class="btn small" onclick={() => (logsOpen = !logsOpen)} aria-expanded={logsOpen}> + <Button variant="outline" size="sm" onclick={() => (logsOpen = !logsOpen)} aria-expanded={logsOpen}> {logsOpen ? "收起日志" : `展开日志(${result.logs.length} 行)`} - </button> - {#if logsOpen} + </Button> {#if logsOpen} <pre class="logs">{result.logs.join("\n")}</pre> {/if} </div> |
