Agent-Market-Arena / src /components /CompareChartE.vue
Jimin Huang
Change settings
fa77219
raw
history blame
5.29 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()
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 })
}
const keys = Array.from(groupKeyToSeq.keys())
const labels = keys.length ? (groupKeyToSeq.get(keys[0]).seq || []).map(s => s.date) : []
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 stratSeries = computeStrategyEquity(seq, 100000, cfg.fee, cfg.strategy, cfg.tradingMode)
if (stratSeries?.length) {
const color = getStrategyColor(sel.strategy || 'aggressive_lo', false, idx)
const vals = labels.length ? stratSeries.slice(0, labels.length) : stratSeries
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 },
data: vals
})
}
}
for (const asset of assets) {
const entry = [...groupKeyToSeq.values()].find(v => v.sel.asset === asset)
if (!entry) continue
const bh = computeBuyHoldEquity(entry.seq, 100000) || []
const baseline = labels.length ? bh.slice(0, labels.length) : bh
series.push({
name: `${asset} 路 Buy&Hold`,
type: 'line',
showSymbol: false,
lineStyle: { width: 1.5 },
color: getStrategyColor('', true, 0),
data: baseline
})
legend.push(`${asset} 路 Buy&Hold`)
}
this.option = {
animation: true,
grid: { left: 40, right: 20, top: 30, bottom: 40 },
tooltip: {
trigger: 'axis',
valueFormatter: v => typeof v === 'number'
? v.toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
: v
},
legend: { type: 'scroll', bottom: 0, data: legend },
xAxis: { type: 'category', data: labels, axisLabel: { formatter: v => v?.slice?.(2) } },
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: 16, bottom: 22 }],
series
}
}
}
})
</script>
<style scoped>
.chart-wrap { width: 100%; }
.h-96 { height: 24rem; }
</style>