Jimin Huang commited on
Commit
6c34ff7
·
1 Parent(s): edd5045

Change settings

Browse files
src/components/AssetTabs.vue ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="flex gap-2 flex-wrap">
3
+ <button
4
+ v-for="a in orderedAssets" :key="a"
5
+ class="px-3 py-1 rounded-full border flex items-center gap-2"
6
+ :class="a===modelValue ? 'bg-black text-white' : 'bg-white'"
7
+ @click="$emit('update:modelValue', a)"
8
+ >
9
+ <img v-if="logos[a]" :src="logos[a]" alt="" class="h-4 w-4 object-contain" />
10
+ <span>{{ a }}</span>
11
+ </button>
12
+ </div>
13
+ </template>
14
+
15
+ <script setup>
16
+ import { computed } from 'vue'
17
+ import { dataService } from '@/lib/dataService'
18
+
19
+ // import the logos you actually have in /src/assets (add/remove as needed)
20
+ import AAPL from '@/assets/AAPL.png'
21
+ import MSFT from '@/assets/MSFT.png'
22
+ import BTC from '@/assets/BTC.png'
23
+ import ETH from '@/assets/ETH.png'
24
+ import BMRN from '@/assets/BMRN.png'
25
+ import MRNA from '@/assets/MRNA.png'
26
+ import TSLA from '@/assets/TSLA.png'
27
+
28
+ const props = defineProps({
29
+ modelValue: { type: String, default: '' },
30
+ preferredOrder: { type: Array, default: () => ['BTC','ETH','SOL','BNB','DOGE','XRP','AAPL','MSFT','BMRN','MRNA','TSLA'] }
31
+ })
32
+ const emit = defineEmits(['update:modelValue'])
33
+
34
+ const logos = {
35
+ AAPL, MSFT, BTC, ETH, BMRN, MRNA, TSLA
36
+ }
37
+
38
+ const availableAssets = computed(() => {
39
+ const rows = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
40
+ return [...new Set(rows.map(r => r.asset))] // only assets you actually have
41
+ })
42
+
43
+ const orderedAssets = computed(() => {
44
+ const present = new Set(availableAssets.value)
45
+ const primary = props.preferredOrder.filter(a => present.has(a))
46
+ const extras = [...present].filter(a => !props.preferredOrder.includes(a)).sort()
47
+ const list = [...primary, ...extras]
48
+ // keep current selection if possible, else default to first
49
+ if (!list.includes(props.modelValue) && list.length) emit('update:modelValue', list[0])
50
+ return list
51
+ })
52
+ </script>
src/components/CompareChartE.vue CHANGED
@@ -48,6 +48,7 @@ export default defineComponent({
48
  const all = await this.getAll()
49
  const groupKeyToSeq = new Map()
50
 
 
51
  for (const sel of selected) {
52
  const { agent_name: agent, asset, model } = sel
53
  const ids = Array.isArray(sel.decision_ids) ? sel.decision_ids : []
@@ -59,9 +60,7 @@ export default defineComponent({
59
  groupKeyToSeq.set(`${agent}|${asset}|${model}`, { sel, seq: filtered })
60
  }
61
 
62
- const keys = Array.from(groupKeyToSeq.keys())
63
- const labels = keys.length ? (groupKeyToSeq.get(keys[0]).seq || []).map(s => s.date) : []
64
-
65
  const series = []
66
  const legend = []
67
  const assets = new Set()
@@ -77,57 +76,57 @@ export default defineComponent({
77
  agentColorIndex.set(agent, idx)
78
 
79
  const cfg = (STRATEGIES || []).find(s => s.id === sel.strategy) || { strategy: 'long_only', tradingMode: 'aggressive', fee: 0.0005, label: 'Selected' }
80
- const stratSeries = computeStrategyEquity(seq, 100000, cfg.fee, cfg.strategy, cfg.tradingMode)
81
- if (stratSeries?.length) {
82
- const color = getStrategyColor(sel.strategy || 'aggressive_lo', false, idx)
83
- const vals = labels.length ? stratSeries.slice(0, labels.length) : stratSeries
84
- const name = `${agent} · ${sel.model} · ${cfg.label}`
85
- legend.push(name)
86
- series.push({
87
- name,
88
- type: 'line',
89
- showSymbol: false,
90
- smooth: false,
91
- emphasis: { focus: 'series' },
92
- lineStyle: { width: 2 },
93
- data: vals
94
- })
95
- }
96
  }
97
 
 
98
  for (const asset of assets) {
99
  const entry = [...groupKeyToSeq.values()].find(v => v.sel.asset === asset)
100
  if (!entry) continue
101
- const bh = computeBuyHoldEquity(entry.seq, 100000) || []
102
- const baseline = labels.length ? bh.slice(0, labels.length) : bh
103
  series.push({
104
  name: `${asset} · Buy&Hold`,
105
  type: 'line',
106
  showSymbol: false,
107
  lineStyle: { width: 1.5 },
108
  color: getStrategyColor('', true, 0),
109
- data: baseline
110
  })
111
  legend.push(`${asset} · Buy&Hold`)
112
  }
113
 
114
  this.option = {
115
  animation: true,
116
- grid: { left: 40, right: 20, top: 30, bottom: 40 },
117
  tooltip: {
118
  trigger: 'axis',
 
119
  valueFormatter: v => typeof v === 'number'
120
  ? v.toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
121
  : v
122
  },
123
- legend: { type: 'scroll', bottom: 0, data: legend },
124
- xAxis: { type: 'category', data: labels, axisLabel: { formatter: v => v?.slice?.(2) } },
125
  yAxis: {
126
- type: 'value',
127
- scale: true,
128
- axisLabel: { formatter: v => v.toLocaleString(undefined, { style:'currency', currency:'USD', maximumFractionDigits:0 }) }
129
  },
130
- dataZoom: [{ type: 'inside', throttle: 50 }, { type: 'slider', height: 16, bottom: 22 }],
131
  series
132
  }
133
  }
 
48
  const all = await this.getAll()
49
  const groupKeyToSeq = new Map()
50
 
51
+ // 1) Build sequences exactly like CompareChart.vue
52
  for (const sel of selected) {
53
  const { agent_name: agent, asset, model } = sel
54
  const ids = Array.isArray(sel.decision_ids) ? sel.decision_ids : []
 
60
  groupKeyToSeq.set(`${agent}|${asset}|${model}`, { sel, seq: filtered })
61
  }
62
 
63
+ // 2) Build series using (time,value) pairs => no misalignment
 
 
64
  const series = []
65
  const legend = []
66
  const assets = new Set()
 
76
  agentColorIndex.set(agent, idx)
77
 
78
  const cfg = (STRATEGIES || []).find(s => s.id === sel.strategy) || { strategy: 'long_only', tradingMode: 'aggressive', fee: 0.0005, label: 'Selected' }
79
+ const stratY = computeStrategyEquity(seq, 100000, cfg.fee, cfg.strategy, cfg.tradingMode) || []
80
+ const points = seq.map((row, i) => [row.date, stratY[i]]) // << key change
81
+
82
+ const name = `${agent} · ${sel.model} · ${cfg.label}`
83
+ legend.push(name)
84
+ series.push({
85
+ name,
86
+ type: 'line',
87
+ showSymbol: false,
88
+ smooth: false,
89
+ emphasis: { focus: 'series' },
90
+ lineStyle: { width: 2 },
91
+ areaStyle: { opacity: 0.06 },
92
+ data: points
93
+ })
 
94
  }
95
 
96
+ // 3) Buy & Hold baseline per asset (also time/value points)
97
  for (const asset of assets) {
98
  const entry = [...groupKeyToSeq.values()].find(v => v.sel.asset === asset)
99
  if (!entry) continue
100
+ const bhY = computeBuyHoldEquity(entry.seq, 100000) || []
101
+ const bhPoints = entry.seq.map((row, i) => [row.date, bhY[i]])
102
  series.push({
103
  name: `${asset} · Buy&Hold`,
104
  type: 'line',
105
  showSymbol: false,
106
  lineStyle: { width: 1.5 },
107
  color: getStrategyColor('', true, 0),
108
+ data: bhPoints
109
  })
110
  legend.push(`${asset} · Buy&Hold`)
111
  }
112
 
113
  this.option = {
114
  animation: true,
115
+ grid: { left: 56, right: 24, top: 16, bottom: 60 },
116
  tooltip: {
117
  trigger: 'axis',
118
+ axisPointer: { type: 'line' },
119
  valueFormatter: v => typeof v === 'number'
120
  ? v.toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
121
  : v
122
  },
123
+ legend: { type: 'scroll', bottom: 8, icon: 'roundRect', itemGap: 10, data: legend },
124
+ xAxis: { type: 'time' }, // << time axis = auto alignment
125
  yAxis: {
126
+ type: 'value', scale: true,
127
+ axisLabel: { formatter: v => v.toLocaleString(undefined, {style:'currency', currency:'USD', maximumFractionDigits:0 }) }
 
128
  },
129
+ dataZoom: [{ type: 'inside', throttle: 50 }, { type: 'slider', height: 14, bottom: 36 }],
130
  series
131
  }
132
  }
src/views/LiveView.vue CHANGED
@@ -2,6 +2,7 @@
2
  <script setup>
3
  import { ref, computed, onMounted } from 'vue'
4
  import CompareChartE from '../components/CompareChartE.vue'
 
5
  import { dataService } from '../lib/dataService.js'
6
 
7
  const ASSETS = ['BTC','ETH','SOL','BNB','DOGE','XRP']
@@ -50,14 +51,7 @@ onMounted(async () => {
50
 
51
  <template>
52
  <div class="p-4 space-y-4">
53
- <div class="flex gap-2 flex-wrap">
54
- <button
55
- v-for="a in ASSETS" :key="a"
56
- class="px-3 py-1 rounded-full border"
57
- :class="a===asset ? 'bg-black text-white' : 'bg-white'"
58
- @click="asset=a"
59
- >{{ a }}</button>
60
- </div>
61
 
62
  <CompareChartE v-if="winnersForChart.length" :selected="winnersForChart" :visible="true" />
63
  <div v-else class="p-4 text-sm text-gray-500 border rounded-lg">
@@ -65,17 +59,17 @@ onMounted(async () => {
65
  <code>dataService.load()</code> completes. Check console for “LiveView: loaded rows” count.
66
  </div>
67
 
68
- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
69
- <div v-for="row in winnersSorted" :key="row.agent_name+'|'+row.asset+'|'+row.model" class="p-3 rounded-xl border flex justify-between items-center">
70
- <div>
71
- <div class="font-semibold">{{ row.agent_name }}</div>
72
- <div class="text-xs opacity-70">{{ row.model }}</div>
 
 
73
  </div>
74
  <div class="text-right">
75
- <div class="font-bold text-lg">{{ fmtUSD(row.balance) }}</div>
76
- <div class="text-xs opacity-70">
77
- EOD {{ (row.end_date || row.last_nav_ts) ? new Date(row.end_date || row.last_nav_ts).toLocaleDateString() : '-' }}
78
- </div>
79
  </div>
80
  </div>
81
  </div>
 
2
  <script setup>
3
  import { ref, computed, onMounted } from 'vue'
4
  import CompareChartE from '../components/CompareChartE.vue'
5
+ import AssetTabs from '../components/AssetTabs.vue'
6
  import { dataService } from '../lib/dataService.js'
7
 
8
  const ASSETS = ['BTC','ETH','SOL','BNB','DOGE','XRP']
 
51
 
52
  <template>
53
  <div class="p-4 space-y-4">
54
+ <AssetTabs v-model="asset" />
 
 
 
 
 
 
 
55
 
56
  <CompareChartE v-if="winnersForChart.length" :selected="winnersForChart" :visible="true" />
57
  <div v-else class="p-4 text-sm text-gray-500 border rounded-lg">
 
59
  <code>dataService.load()</code> completes. Check console for “LiveView: loaded rows” count.
60
  </div>
61
 
62
+
63
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
64
+ <div v-for="row in winnersSorted" :key="row.agent_name+'|'+row.asset+'|'+row.model"
65
+ class="p-4 rounded-2xl border shadow-sm flex justify-between items-center">
66
+ <div class="min-w-0">
67
+ <div class="font-semibold truncate">{{ row.agent_name }}</div>
68
+ <div class="text-xs opacity-70 truncate">{{ row.model }}</div>
69
  </div>
70
  <div class="text-right">
71
+ <div class="font-bold text-xl leading-6">{{ fmtUSD(row.balance) }}</div>
72
+ <div class="text-xs opacity-70">EOD {{ (row.end_date || row.last_nav_ts) ? new Date(row.end_date || row.last_nav_ts).toLocaleDateString() : '-' }}</div>
 
 
73
  </div>
74
  </div>
75
  </div>