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>