Jimin Huang commited on
Commit
a2b7663
·
1 Parent(s): 379564a

Change settings

Browse files
Files changed (1) hide show
  1. src/components/CompareChartE.vue +85 -74
src/components/CompareChartE.vue CHANGED
@@ -35,6 +35,7 @@
35
  class="card"
36
  :class="{ 'card--bh': c.kind==='bh', 'card--winner': c.isWinner }"
37
  >
 
38
  <div class="card__header">
39
  <div class="card__logo">
40
  <img v-if="c.logo" :src="c.logo" alt="" />
@@ -46,6 +47,7 @@
46
  <div class="card__balance">{{ fmtUSD(c.balance) }}</div>
47
  </div>
48
 
 
49
  <div class="card__meta">
50
  <div class="card__sub ellipsize" :title="c.subtitle">{{ c.subtitle }}</div>
51
 
@@ -61,6 +63,7 @@
61
  </template>
62
  </div>
63
 
 
64
  <div class="card__footer">
65
  <div class="card__sub">EOD {{ c.date ? new Date(c.date).toLocaleDateString() : '–' }}</div>
66
  </div>
@@ -71,12 +74,12 @@
71
  </template>
72
 
73
  <script setup>
74
- import { ref, computed, onMounted, watchEffect } from 'vue'
75
  import AssetTabs from '../components/AssetTabs.vue'
76
  import CompareChartE from '../components/CompareChartE.vue'
77
  import { dataService } from '../lib/dataService'
78
 
79
- /* —— use the same helpers as the chart —— */
80
  import { getAllDecisions } from '../lib/dataCache'
81
  import { readAllRawDecisions } from '../lib/idb'
82
  import { filterRowsToNyseTradingDays } from '../lib/marketCalendar'
@@ -84,15 +87,12 @@ import { STRATEGIES } from '../lib/strategies'
84
  import { computeBuyHoldEquity, computeStrategyEquity } from '../lib/perf'
85
 
86
  /* ---------- config ---------- */
87
- const orderedAssets = ['BTC','ETH','MSFT','BMRN','TSLA'] // (MRNA removed)
88
  const EXCLUDED_AGENT_NAMES = new Set(['vanilla', 'vinilla']) // case-insensitive
89
 
90
  // optional logos
91
  const AGENT_LOGOS = {
92
  // 'DeepFundAgent': new URL('../assets/images/agents/deepfund.png', import.meta.url).href,
93
- // 'InvestorAgent': new URL('../assets/images/agents/investor.png', import.meta.url).href,
94
- // 'TradeAgent': new URL('../assets/images/agents/trade.png', import.meta.url).href,
95
- // 'HedgeFundAgent': new URL('../assets/images/agents/hedge.png', import.meta.url).href,
96
  }
97
  const ASSET_ICONS = {
98
  BTC: new URL('../assets/images/assets_images/BTC.png', import.meta.url).href,
@@ -102,7 +102,7 @@ const ASSET_ICONS = {
102
  TSLA: new URL('../assets/images/assets_images/TSLA.png', import.meta.url).href,
103
  }
104
 
105
- /* match the chart’s cutoff so numbers align */
106
  const ASSET_CUTOFF = { BTC: '2025-08-01' }
107
 
108
  /* ---------- state ---------- */
@@ -110,7 +110,7 @@ const mode = ref('usd') // 'usd' | 'pct'
110
  const asset = ref('BTC')
111
  const rowsRef = ref([])
112
 
113
- let allDecisions = [] // in-memory decisions for perf calc
114
 
115
  /* ---------- bootstrap ---------- */
116
  onMounted(async () => {
@@ -124,7 +124,7 @@ onMounted(async () => {
124
  rowsRef.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
125
  if (!orderedAssets.includes(asset.value)) asset.value = orderedAssets[0]
126
 
127
- // pull the same decisions cache the chart uses
128
  allDecisions = getAllDecisions() || []
129
  if (!allDecisions.length) {
130
  try {
@@ -132,6 +132,7 @@ onMounted(async () => {
132
  if (cached?.length) allDecisions = cached
133
  } catch {}
134
  }
 
135
  })
136
 
137
  /* ---------- helpers ---------- */
@@ -151,7 +152,7 @@ const filteredRows = computed(() =>
151
  })
152
  )
153
 
154
- /* Best model per agent (by balance) — still from leaderboard rows for picking winners */
155
  const winners = computed(() => {
156
  const byAgent = new Map()
157
  for (const r of filteredRows.value) {
@@ -162,7 +163,7 @@ const winners = computed(() => {
162
  return [...byAgent.values()]
163
  })
164
 
165
- /* Chart selections built from winners */
166
  const winnersForChart = computed(() =>
167
  winners.value.map(w => ({
168
  agent_name: w.agent_name,
@@ -173,10 +174,10 @@ const winnersForChart = computed(() =>
173
  }))
174
  )
175
 
176
- /* ---------- PERF: compute B&H + strategy the same way as the chart ---------- */
177
 
178
- /** build the ordered decision seq for a selection (same logic as chart) */
179
- function buildSeq(sel) {
180
  const { agent_name: agent, asset, model } = sel
181
  const ids = Array.isArray(sel.decision_ids) ? sel.decision_ids : []
182
  let seq = ids.length
@@ -186,7 +187,10 @@ function buildSeq(sel) {
186
  seq.sort((a, b) => (a.date > b.date ? 1 : -1))
187
 
188
  const isCrypto = asset === 'BTC' || asset === 'ETH'
189
- let filtered = isCrypto ? seq : filterRowsToNyseTradingDays(seq)
 
 
 
190
 
191
  const cutoff = ASSET_CUTOFF[asset]
192
  if (cutoff) {
@@ -196,12 +200,11 @@ function buildSeq(sel) {
196
  return filtered
197
  }
198
 
199
- /** compute final equity & aligned B&H for a selection */
200
- function computeEquities(sel) {
201
- const seq = buildSeq(sel)
202
  if (!seq.length) return null
203
 
204
- // strategy params (mirror CompareChartE)
205
  const cfg =
206
  (STRATEGIES || []).find(s => s.id === sel.strategy) ||
207
  { strategy: 'long_only', tradingMode: 'aggressive', fee: 0.0005 }
@@ -216,66 +219,74 @@ function computeEquities(sel) {
216
  date: seq[lastIdx].date,
217
  stratLast: stratY[lastIdx],
218
  bhLast: bhY[lastIdx],
219
- seq, stratY, bhY
220
  }
221
  }
222
 
223
- /* compute cards from perf (no leaderboard math) */
224
- const cards = ref([])
225
-
226
- watchEffect(() => {
227
- if (!winnersForChart.value.length) {
228
- cards.value = []
229
- return
230
- }
231
-
232
- // compute perf for each winner
233
- const perfs = winnersForChart.value
234
- .map(sel => ({ sel, perf: computeEquities(sel) }))
235
- .filter(x => x.perf)
236
-
237
- if (!perfs.length) {
238
- cards.value = []
239
- return
240
- }
241
-
242
- // Buy & Hold card: use the first winner’s BH last for the asset
243
- const first = perfs[0]
244
- const assetCode = first.sel.asset
245
- const bhCard = {
246
- key: `bh|${assetCode}`,
247
- kind: 'bh',
248
- title: 'Buy & Hold',
249
- subtitle: assetCode,
250
- balance: first.perf.bhLast,
251
- date: first.perf.date,
252
- logo: ASSET_ICONS[assetCode] || null,
253
- isWinner: false
254
- }
255
 
256
- // agent cards and winner flag
257
- const agentCards = perfs.map(({ sel, perf }) => {
258
- const gapUsd = perf.stratLast - perf.bhLast
259
- const gapPct = perf.bhLast > 0 ? (perf.stratLast / perf.bhLast - 1) : 0
260
- return {
261
- key: `agent|${sel.agent_name}|${sel.model}`,
262
- kind: 'agent',
263
- title: sel.agent_name,
264
- subtitle: sel.model,
265
- balance: perf.stratLast,
266
- date: perf.date,
267
- logo: AGENT_LOGOS[sel.agent_name] || null,
268
- gapUsd, gapPct,
269
- isWinner: false // set below
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  }
271
- })
272
-
273
- const maxBal = Math.max(...agentCards.map(c => c.balance ?? -Infinity))
274
- agentCards.forEach(c => { c.isWinner = c.balance === maxBal })
275
-
276
- // top 4 agents + BH card
277
- cards.value = [bhCard, ...agentCards.sort((a,b) => b.balance - a.balance).slice(0,4)]
278
- })
279
  </script>
280
 
281
  <style scoped>
 
35
  class="card"
36
  :class="{ 'card--bh': c.kind==='bh', 'card--winner': c.isWinner }"
37
  >
38
+ <!-- header: logo | title | balance -->
39
  <div class="card__header">
40
  <div class="card__logo">
41
  <img v-if="c.logo" :src="c.logo" alt="" />
 
47
  <div class="card__balance">{{ fmtUSD(c.balance) }}</div>
48
  </div>
49
 
50
+ <!-- meta row -->
51
  <div class="card__meta">
52
  <div class="card__sub ellipsize" :title="c.subtitle">{{ c.subtitle }}</div>
53
 
 
63
  </template>
64
  </div>
65
 
66
+ <!-- date row -->
67
  <div class="card__footer">
68
  <div class="card__sub">EOD {{ c.date ? new Date(c.date).toLocaleDateString() : '–' }}</div>
69
  </div>
 
74
  </template>
75
 
76
  <script setup>
77
+ import { ref, computed, onMounted, nextTick, watch, shallowRef } from 'vue'
78
  import AssetTabs from '../components/AssetTabs.vue'
79
  import CompareChartE from '../components/CompareChartE.vue'
80
  import { dataService } from '../lib/dataService'
81
 
82
+ /* —— same helpers as the chart —— */
83
  import { getAllDecisions } from '../lib/dataCache'
84
  import { readAllRawDecisions } from '../lib/idb'
85
  import { filterRowsToNyseTradingDays } from '../lib/marketCalendar'
 
87
  import { computeBuyHoldEquity, computeStrategyEquity } from '../lib/perf'
88
 
89
  /* ---------- config ---------- */
90
+ const orderedAssets = ['BTC','ETH','MSFT','BMRN','TSLA'] // MRNA removed
91
  const EXCLUDED_AGENT_NAMES = new Set(['vanilla', 'vinilla']) // case-insensitive
92
 
93
  // optional logos
94
  const AGENT_LOGOS = {
95
  // 'DeepFundAgent': new URL('../assets/images/agents/deepfund.png', import.meta.url).href,
 
 
 
96
  }
97
  const ASSET_ICONS = {
98
  BTC: new URL('../assets/images/assets_images/BTC.png', import.meta.url).href,
 
102
  TSLA: new URL('../assets/images/assets_images/TSLA.png', import.meta.url).href,
103
  }
104
 
105
+ /* match the chart’s cutoff so numbers align exactly */
106
  const ASSET_CUTOFF = { BTC: '2025-08-01' }
107
 
108
  /* ---------- state ---------- */
 
110
  const asset = ref('BTC')
111
  const rowsRef = ref([])
112
 
113
+ let allDecisions = [] // decisions cache used for perf
114
 
115
  /* ---------- bootstrap ---------- */
116
  onMounted(async () => {
 
124
  rowsRef.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
125
  if (!orderedAssets.includes(asset.value)) asset.value = orderedAssets[0]
126
 
127
+ // pull decisions used by chart
128
  allDecisions = getAllDecisions() || []
129
  if (!allDecisions.length) {
130
  try {
 
132
  if (cached?.length) allDecisions = cached
133
  } catch {}
134
  }
135
+ await nextTick()
136
  })
137
 
138
  /* ---------- helpers ---------- */
 
152
  })
153
  )
154
 
155
+ /* Best model per agent (by balance) — from leaderboard rows for winners list */
156
  const winners = computed(() => {
157
  const byAgent = new Map()
158
  for (const r of filteredRows.value) {
 
163
  return [...byAgent.values()]
164
  })
165
 
166
+ /* Chart selections mirror winners */
167
  const winnersForChart = computed(() =>
168
  winners.value.map(w => ({
169
  agent_name: w.agent_name,
 
174
  }))
175
  )
176
 
177
+ /* ---------- PERF: compute B&H + strategy like the chart (ASYNC & SERIALIZED) ---------- */
178
 
179
+ /** async: build ordered decision seq for a selection (await calendar filter) */
180
+ async function buildSeq(sel) {
181
  const { agent_name: agent, asset, model } = sel
182
  const ids = Array.isArray(sel.decision_ids) ? sel.decision_ids : []
183
  let seq = ids.length
 
187
  seq.sort((a, b) => (a.date > b.date ? 1 : -1))
188
 
189
  const isCrypto = asset === 'BTC' || asset === 'ETH'
190
+ let filtered = seq
191
+ if (!isCrypto) {
192
+ filtered = await filterRowsToNyseTradingDays(seq) // ← await was missing before
193
+ }
194
 
195
  const cutoff = ASSET_CUTOFF[asset]
196
  if (cutoff) {
 
200
  return filtered
201
  }
202
 
203
+ /** async: compute final equity & aligned B&H for a selection */
204
+ async function computeEquities(sel) {
205
+ const seq = await buildSeq(sel)
206
  if (!seq.length) return null
207
 
 
208
  const cfg =
209
  (STRATEGIES || []).find(s => s.id === sel.strategy) ||
210
  { strategy: 'long_only', tradingMode: 'aggressive', fee: 0.0005 }
 
219
  date: seq[lastIdx].date,
220
  stratLast: stratY[lastIdx],
221
  bhLast: bhY[lastIdx],
 
222
  }
223
  }
224
 
225
+ /* cards are computed via a serialized async watcher (avoid re-entrancy) */
226
+ const cards = shallowRef([])
227
+ let computing = false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
+ watch(
230
+ () => [asset.value, winnersForChart.value], // deps
231
+ async () => {
232
+ if (!allDecisions.length) { cards.value = []; return }
233
+ if (computing) return
234
+ computing = true
235
+ try {
236
+ const sels = winnersForChart.value || []
237
+ if (!sels.length) { cards.value = []; return }
238
+
239
+ // run all perf computations in parallel
240
+ const perfs = (await Promise.all(
241
+ sels.map(async sel => ({ sel, perf: await computeEquities(sel) }))
242
+ )).filter(x => x.perf)
243
+
244
+ if (!perfs.length) { cards.value = []; return }
245
+
246
+ // B&H card: use first selection's BH (same asset & cutoff as chart)
247
+ const assetCode = perfs[0].sel.asset
248
+ const bhCard = {
249
+ key: `bh|${assetCode}`,
250
+ kind: 'bh',
251
+ title: 'Buy & Hold',
252
+ subtitle: assetCode,
253
+ balance: perfs[0].perf.bhLast,
254
+ date: perfs[0].perf.date,
255
+ logo: ASSET_ICONS[assetCode] || null,
256
+ isWinner: false
257
+ }
258
+
259
+ // Agent cards and winner flag
260
+ const agentCards = perfs.map(({ sel, perf }) => {
261
+ const gapUsd = perf.stratLast - perf.bhLast
262
+ const gapPct = perf.bhLast > 0 ? (perf.stratLast / perf.bhLast - 1) : 0
263
+ return {
264
+ key: `agent|${sel.agent_name}|${sel.model}`,
265
+ kind: 'agent',
266
+ title: sel.agent_name,
267
+ subtitle: sel.model,
268
+ balance: perf.stratLast,
269
+ date: perf.date,
270
+ logo: AGENT_LOGOS[sel.agent_name] || null,
271
+ gapUsd, gapPct,
272
+ isWinner: false
273
+ }
274
+ })
275
+
276
+ const maxBal = Math.max(...agentCards.map(c => c.balance ?? -Infinity))
277
+ agentCards.forEach(c => { c.isWinner = c.balance === maxBal })
278
+
279
+ // Top 4 agents + BH
280
+ cards.value = [bhCard, ...agentCards.sort((a,b) => b.balance - a.balance).slice(0,4)]
281
+ } catch (e) {
282
+ console.error('LiveView: compute cards failed', e)
283
+ cards.value = []
284
+ } finally {
285
+ computing = false
286
  }
287
+ },
288
+ { immediate: true, deep: true }
289
+ )
 
 
 
 
 
290
  </script>
291
 
292
  <style scoped>