Agent-Market-Arena / src /components /CompareChartE.vue
Jimin Huang
Change settings
c795e9d
raw
history blame
5.27 kB
<template>
<div class="chart-wrap">
<v-chart :option="option" autoresize class="h-96 w-full" />
</div>
</template>
<script>
import { defineComponent } from 'vue'
import VChart from 'vue-echarts'
import * as echarts from 'echarts/core'
import { LineChart } from 'echarts/charts'
import { GridComponent, LegendComponent, TooltipComponent, DataZoomComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import { getAllDecisions } from '../lib/dataCache'
import { readAllRawDecisions } from '../lib/idb'
import { filterRowsToNyseTradingDays } from '../lib/marketCalendar'
import { STRATEGIES } from '../lib/strategies'
import { computeBuyHoldEquity, computeStrategyEquity } from '../lib/perf'
import { getStrategyColor } from '../lib/chartColors'
echarts.use([LineChart, GridComponent, LegendComponent, TooltipComponent, DataZoomComponent, CanvasRenderer])
export default defineComponent({
name: 'CompareChartE',
components: { VChart },
props: {
selected: { type: Array, default: () => [] },
visible: { type: Boolean, default: true }
},
data(){ return { option: {} } },
watch: {
selected: { deep: true, handler(){ this.rebuild() } },
visible(v){ if (v) this.$nextTick(() => this.rebuild()) }
},
mounted(){ this.$nextTick(() => this.rebuild()) },
methods: {
async getAll(){
let all = getAllDecisions() || []
if (!all.length) {
try { const cached = await readAllRawDecisions(); if (cached?.length) all = cached } catch {}
}
return all
},
async rebuild(){
if (!this.visible) return
const selected = Array.isArray(this.selected) ? this.selected : []
const all = await this.getAll()
const groupKeyToSeq = new Map()
// 1) Build sequences exactly like CompareChart.vue
for (const sel of selected) {
const { agent_name: agent, asset, model } = sel
const ids = Array.isArray(sel.decision_ids) ? sel.decision_ids : []
let seq = ids.length ? all.filter(r => ids.includes(r.id))
: all.filter(r => r.agent_name === agent && r.asset === asset && r.model === model)
seq.sort((a,b) => (a.date > b.date ? 1 : -1))
const isCrypto = asset === 'BTC' || asset === 'ETH'
const filtered = isCrypto ? seq : await filterRowsToNyseTradingDays(seq)
groupKeyToSeq.set(`${agent}|${asset}|${model}`, { sel, seq: filtered })
}
// 2) Build series using (time,value) pairs => no misalignment
const series = []
const legend = []
const assets = new Set()
const agentColorIndex = new Map()
for (const [_, { sel, seq }] of groupKeyToSeq.entries()) {
if (!seq.length) continue
const agent = sel.agent_name
const asset = sel.asset
assets.add(asset)
const idx = agentColorIndex.get(agent) ?? agentColorIndex.size
agentColorIndex.set(agent, idx)
const cfg = (STRATEGIES || []).find(s => s.id === sel.strategy) || { strategy: 'long_only', tradingMode: 'aggressive', fee: 0.0005, label: 'Selected' }
const stratY = computeStrategyEquity(seq, 100000, cfg.fee, cfg.strategy, cfg.tradingMode) || []
const points = seq.map((row, i) => [row.date, stratY[i]]) // << key change
const name = `${agent}${sel.model}${cfg.label}`
legend.push(name)
series.push({
name,
type: 'line',
showSymbol: false,
smooth: false,
emphasis: { focus: 'series' },
lineStyle: { width: 2 },
areaStyle: { opacity: 0.06 },
data: points
})
}
// 3) Buy & Hold baseline per asset (also time/value points)
for (const asset of assets) {
const entry = [...groupKeyToSeq.values()].find(v => v.sel.asset === asset)
if (!entry) continue
const bhY = computeBuyHoldEquity(entry.seq, 100000) || []
const bhPoints = entry.seq.map((row, i) => [row.date, bhY[i]])
series.push({
name: `${asset} 路 Buy&Hold`,
type: 'line',
showSymbol: false,
lineStyle: { width: 1.5 },
color: getStrategyColor('', true, 0),
data: bhPoints
})
legend.push(`${asset} 路 Buy&Hold`)
}
this.option = {
animation: true,
grid: { left: 56, right: 24, top: 16, bottom: 60 },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'line' },
valueFormatter: v => typeof v === 'number'
? v.toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
: v
},
legend: { type: 'scroll', bottom: 8, icon: 'roundRect', itemGap: 10, data: legend },
xAxis: { type: 'time' }, // << time axis = auto alignment
yAxis: {
type: 'value', scale: true,
axisLabel: { formatter: v => v.toLocaleString(undefined, {style:'currency', currency:'USD', maximumFractionDigits:0 }) }
},
dataZoom: [{ type: 'inside', throttle: 50 }, { type: 'slider', height: 14, bottom: 36 }],
series
}
}
}
})
</script>
<style scoped>
.chart-wrap { width: 100%; }
.h-96 { height: 24rem; }
</style>