Spaces:
Running
Running
File size: 5,630 Bytes
3ac9172 2d10b00 3ac9172 986a6ce 3ac9172 fa77219 3ac9172 fa77219 3ac9172 6c34ff7 3ac9172 787af51 986a6ce 3ac9172 6c34ff7 3ac9172 fa77219 3ac9172 6c34ff7 3ac9172 6c34ff7 3ac9172 6c34ff7 3ac9172 6c34ff7 3ac9172 6c34ff7 3ac9172 6c34ff7 3ac9172 6c34ff7 3ac9172 6c34ff7 3ac9172 6c34ff7 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 143 144 145 146 147 148 149 150 151 152 153 |
<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])
const ASSET_CUTOFF = {
BTC: '2025-08-01',
// ETH: '2025-08-15', // example if you add others later
};
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'
let filtered = isCrypto ? seq : await filterRowsToNyseTradingDays(seq)
// --- asset-specific cutoff ---
const cutoff = ASSET_CUTOFF[asset]
if (cutoff) {
const t0 = new Date(cutoff + 'T00:00:00Z')
filtered = filtered.filter(r => new Date(r.date + 'T00:00:00Z') >= t0)
}
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>
|