File size: 5,273 Bytes
3ac9172
 
 
 
 
 
 
 
 
 
 
 
 
 
2d10b00
 
 
 
 
 
3ac9172
 
 
 
 
 
 
fa77219
3ac9172
 
 
 
 
 
 
 
 
 
 
 
fa77219
3ac9172
 
 
 
 
 
 
 
 
6c34ff7
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
<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>