Spaces:
Running
Running
File size: 5,290 Bytes
3ac9172 fa77219 3ac9172 fa77219 3ac9172 fa77219 3ac9172 fa77219 3ac9172 fa77219 3ac9172 fa77219 3ac9172 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
<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>
|