Jimin Huang commited on
Commit
3c4f27f
·
1 Parent(s): 3b104a4

Change settings

Browse files
Files changed (1) hide show
  1. src/views/LiveView.vue +72 -64
src/views/LiveView.vue CHANGED
@@ -36,7 +36,7 @@
36
  </div>
37
  </section>
38
 
39
- <!-- Cards: Buy & Hold + top 4 agents = 5 in a row -->
40
  <section class="panel panel--cards" v-if="cards.length">
41
  <div class="cards5">
42
  <div
@@ -66,14 +66,14 @@
66
  <div class="card__sub" :title="c.subtitle">{{ c.subtitle }}</div>
67
 
68
  <template v-if="c.kind==='agent'">
69
- <div class="gap pill" :class="{ neg: c.gapUsd < 0 }">
70
- <span>{{ signedMoney(c.gapUsd) }}</span>
71
- <span class="muted"> ({{ signedPct(c.gapPct) }})</span>
72
  </div>
73
  </template>
74
 
75
  <template v-else>
76
- <div class="bh pill">Buy&nbsp;&amp;&nbsp;Hold</div>
77
  </template>
78
  </div>
79
 
@@ -100,11 +100,11 @@ import { readAllRawDecisions } from '../lib/idb'
100
  import { filterRowsToNyseTradingDays } from '../lib/marketCalendar'
101
  import { computeBuyHoldEquity } from '../lib/perf'
102
 
103
- // ------- config -------
104
  const orderedAssets = ['BTC','ETH','MSFT','BMRN','TSLA'] // MRNA removed
105
  const EXCLUDED_AGENT_NAMES = new Set(['vanilla', 'vinilla']) // case-insensitive
106
 
107
- // Logos (hook up your real paths)
108
  const AGENT_LOGOS = {
109
  // 'DeepFundAgent': new URL('../assets/images/agents/deepfund.png', import.meta.url).href,
110
  // 'InvestorAgent': new URL('../assets/images/agents/investor.png', import.meta.url).href,
@@ -119,14 +119,14 @@ const ASSET_ICONS = {
119
  TSLA: new URL('../assets/images/assets_images/TSLA.png', import.meta.url).href,
120
  }
121
 
122
- // ------- state -------
123
- const mode = ref('usd')
124
  const asset = ref('BTC')
125
  const rowsRef = ref([])
126
- const bhBalance = ref(null) // baseline Buy & Hold final balance
127
  const bhDate = ref('')
128
 
129
- // load table rows once
130
  onMounted(async () => {
131
  try {
132
  if (!dataService.loaded && !dataService.loading) {
@@ -136,46 +136,19 @@ onMounted(async () => {
136
  console.error('LiveView: dataService.load failed', e)
137
  }
138
  rowsRef.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
 
139
  })
140
 
141
- // recompute Buy&Hold when asset changes
142
- watch(asset, async () => { await recomputeBH() }, { immediate: true })
143
-
144
- async function recomputeBH(){
145
- // Build a clean sequence of decisions for the selected asset, then compute Buy&Hold equity.
146
- // We'll reuse the longest sequence among available decisions for that asset.
147
- let all = getAllDecisions() || []
148
- if (!all.length) {
149
- try { const cached = await readAllRawDecisions(); if (cached?.length) all = cached } catch {}
150
- }
151
- let seq = all.filter(r => r.asset === asset.value)
152
- seq.sort((a,b) => (a.date > b.date ? 1 : -1))
153
-
154
- // Filter to trading days (NYSE for equities; pass-through for BTC/ETH)
155
- const isCrypto = asset.value === 'BTC' || asset.value === 'ETH'
156
- if (!isCrypto) seq = await filterRowsToNyseTradingDays(seq)
157
-
158
- const bh = computeBuyHoldEquity(seq, 100000) || []
159
- if (bh.length) {
160
- bhBalance.value = bh[bh.length - 1]
161
- bhDate.value = seq[seq.length - 1]?.date || ''
162
- } else {
163
- bhBalance.value = null
164
- bhDate.value = ''
165
- }
166
- }
167
-
168
- // helpers
169
- const fmtUSD = (n) => (n ?? 0).toLocaleString(
170
- undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 }
171
- )
172
- const signedMoney = (n) => `${n >= 0 ? '+' : ''}${fmtUSD(Math.abs(n))}`
173
- const signedPct = (p) =>
174
- `${(p >= 0 ? '+' : '')}${Number(p * 100).toFixed(2)}%`
175
-
176
  function score(row) {
177
  return typeof row.balance === 'number' ? row.balance : -Infinity
178
  }
 
 
 
 
 
 
179
  const filteredRows = computed(() =>
180
  (rowsRef.value || []).filter(r => {
181
  if (r.asset !== asset.value) return false
@@ -184,7 +157,7 @@ const filteredRows = computed(() =>
184
  })
185
  )
186
 
187
- // winners: best per agent (by balance)
188
  const winners = computed(() => {
189
  const byAgent = new Map()
190
  for (const r of filteredRows.value) {
@@ -195,23 +168,58 @@ const winners = computed(() => {
195
  return [...byAgent.values()]
196
  })
197
 
198
- // chart selections
199
  const winnersForChart = computed(() =>
200
  winners.value.map(w => ({
201
- agent_name: w.agent_name,
202
- asset: w.asset,
203
- model: w.model,
204
- strategy: w.strategy,
205
  decision_ids: Array.isArray(w.decision_ids) ? w.decision_ids : undefined
206
  }))
207
  )
208
 
209
- // build 5 cards: Buy & Hold + top 4 agents
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  const cards = computed(() => {
211
  const base = Number(bhBalance.value ?? 100000)
212
  const baseDate = bhDate.value
213
 
214
- // Buy & Hold card
215
  const bhCard = {
216
  key: `bh|${asset.value}`,
217
  kind: 'bh',
@@ -222,7 +230,6 @@ const cards = computed(() => {
222
  logo: ASSET_ICONS[asset.value] || null,
223
  }
224
 
225
- // Top 4 agents
226
  const top = [...winners.value].sort((a,b) => score(b) - score(a)).slice(0, 4)
227
  const agentCards = top.map(r => {
228
  const gapUsd = (typeof r.balance === 'number' && Number.isFinite(base))
@@ -231,7 +238,6 @@ const cards = computed(() => {
231
  const gapPct = (Number.isFinite(base) && base > 0)
232
  ? (r.balance / base - 1)
233
  : 0
234
-
235
  return {
236
  key: `agent|${r.agent_name}|${r.model}`,
237
  kind: 'agent',
@@ -247,13 +253,14 @@ const cards = computed(() => {
247
  return [bhCard, ...agentCards]
248
  })
249
 
250
- // dynamic font size for long names
251
  function nameFontSize(name='') {
252
  const len = name.length
253
- if (len <= 14) return '20px'
254
- if (len <= 20) return '18px'
255
- if (len <= 26) return '16px'
256
- return '15px'
 
257
  }
258
  </script>
259
 
@@ -340,10 +347,12 @@ function nameFontSize(name='') {
340
  .card__row {
341
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
342
  }
 
 
343
  .card__title {
344
- font-weight: 800; color: #0F172A;
345
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
346
  }
 
347
  .card__balance { font-weight: 900; color: #0F172A; font-size: 20px; }
348
  .card__sub { font-size: 12px; color: #5B6476; opacity: .85; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
349
  .card__meta { margin-top: 4px; align-items: center; }
@@ -355,7 +364,6 @@ function nameFontSize(name='') {
355
  background: #EEF2F7; color: #0F172A;
356
  }
357
  .pill.neg { background: #FEE2E2; color: #B91C1C; }
358
- .pill .muted { opacity: .7; }
359
 
360
  /* utility */
361
  .spacer { flex: 1 1 auto; }
 
36
  </div>
37
  </section>
38
 
39
+ <!-- Cards: Buy & Hold + top 4 agents -->
40
  <section class="panel panel--cards" v-if="cards.length">
41
  <div class="cards5">
42
  <div
 
66
  <div class="card__sub" :title="c.subtitle">{{ c.subtitle }}</div>
67
 
68
  <template v-if="c.kind==='agent'">
69
+ <div class="pill" :class="{ neg: c.gapUsd < 0 }">
70
+ <span v-if="mode==='usd'">{{ signedMoney(c.gapUsd) }}</span>
71
+ <span v-else>{{ signedPct(c.gapPct) }}</span>
72
  </div>
73
  </template>
74
 
75
  <template v-else>
76
+ <div class="pill">Buy&nbsp;&amp;&nbsp;Hold</div>
77
  </template>
78
  </div>
79
 
 
100
  import { filterRowsToNyseTradingDays } from '../lib/marketCalendar'
101
  import { computeBuyHoldEquity } from '../lib/perf'
102
 
103
+ /* ---------- config ---------- */
104
  const orderedAssets = ['BTC','ETH','MSFT','BMRN','TSLA'] // MRNA removed
105
  const EXCLUDED_AGENT_NAMES = new Set(['vanilla', 'vinilla']) // case-insensitive
106
 
107
+ // Plug in your real logos here
108
  const AGENT_LOGOS = {
109
  // 'DeepFundAgent': new URL('../assets/images/agents/deepfund.png', import.meta.url).href,
110
  // 'InvestorAgent': new URL('../assets/images/agents/investor.png', import.meta.url).href,
 
119
  TSLA: new URL('../assets/images/assets_images/TSLA.png', import.meta.url).href,
120
  }
121
 
122
+ /* ---------- state ---------- */
123
+ const mode = ref('usd') // 'usd' | 'pct'
124
  const asset = ref('BTC')
125
  const rowsRef = ref([])
126
+ const bhBalance = ref(null) // Buy&Hold final balance (100k start)
127
  const bhDate = ref('')
128
 
129
+ /* ---------- bootstrap ---------- */
130
  onMounted(async () => {
131
  try {
132
  if (!dataService.loaded && !dataService.loading) {
 
136
  console.error('LiveView: dataService.load failed', e)
137
  }
138
  rowsRef.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
139
+ if (!orderedAssets.includes(asset.value)) asset.value = orderedAssets[0]
140
  })
141
 
142
+ /* ---------- helpers ---------- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  function score(row) {
144
  return typeof row.balance === 'number' ? row.balance : -Infinity
145
  }
146
+ const fmtUSD = (n) =>
147
+ (n ?? 0).toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
148
+ const signedMoney = (n) => `${n >= 0 ? '+' : '−'}${fmtUSD(Math.abs(n))}`
149
+ const signedPct = (p) => `${(p >= 0 ? '+' : '−')}${Math.abs(p * 100).toFixed(2)}%`
150
+
151
+ /* Filter rows: selected asset, exclude “vanilla/vinilla” */
152
  const filteredRows = computed(() =>
153
  (rowsRef.value || []).filter(r => {
154
  if (r.asset !== asset.value) return false
 
157
  })
158
  )
159
 
160
+ /* Best model per agent (by balance) */
161
  const winners = computed(() => {
162
  const byAgent = new Map()
163
  for (const r of filteredRows.value) {
 
168
  return [...byAgent.values()]
169
  })
170
 
171
+ /* Chart selections */
172
  const winnersForChart = computed(() =>
173
  winners.value.map(w => ({
174
+ agent_name: w.agent_name,
175
+ asset: w.asset,
176
+ model: w.model,
177
+ strategy: w.strategy,
178
  decision_ids: Array.isArray(w.decision_ids) ? w.decision_ids : undefined
179
  }))
180
  )
181
 
182
+ /* === Buy&Hold: recompute from the SAME sequence the chart uses === */
183
+ watch(
184
+ () => winnersForChart.value,
185
+ async (list) => { await recomputeBHFromSelection(list) },
186
+ { immediate: true, deep: true }
187
+ )
188
+
189
+ async function recomputeBHFromSelection(list) {
190
+ const sel = Array.isArray(list) && list.length ? list[0] : null
191
+ if (!sel) { bhBalance.value = null; bhDate.value = ''; return }
192
+
193
+ // fetch decisions once
194
+ let all = getAllDecisions() || []
195
+ if (!all.length) {
196
+ try { const cached = await readAllRawDecisions(); if (cached?.length) all = cached } catch {}
197
+ }
198
+
199
+ const ids = Array.isArray(sel.decision_ids) ? sel.decision_ids : []
200
+ let seq = ids.length
201
+ ? all.filter(r => ids.includes(r.id))
202
+ : all.filter(r => r.agent_name === sel.agent_name && r.asset === sel.asset && r.model === sel.model)
203
+ seq.sort((a,b) => (a.date > b.date ? 1 : -1))
204
+
205
+ const isCrypto = sel.asset === 'BTC' || sel.asset === 'ETH'
206
+ if (!isCrypto) seq = await filterRowsToNyseTradingDays(seq)
207
+
208
+ const bh = computeBuyHoldEquity(seq, 100000) || []
209
+ if (bh.length) {
210
+ bhBalance.value = bh[bh.length - 1]
211
+ bhDate.value = seq[seq.length - 1]?.date || ''
212
+ } else {
213
+ bhBalance.value = null
214
+ bhDate.value = ''
215
+ }
216
+ }
217
+
218
+ /* Build 5 cards: Buy&Hold + top 4 agents */
219
  const cards = computed(() => {
220
  const base = Number(bhBalance.value ?? 100000)
221
  const baseDate = bhDate.value
222
 
 
223
  const bhCard = {
224
  key: `bh|${asset.value}`,
225
  kind: 'bh',
 
230
  logo: ASSET_ICONS[asset.value] || null,
231
  }
232
 
 
233
  const top = [...winners.value].sort((a,b) => score(b) - score(a)).slice(0, 4)
234
  const agentCards = top.map(r => {
235
  const gapUsd = (typeof r.balance === 'number' && Number.isFinite(base))
 
238
  const gapPct = (Number.isFinite(base) && base > 0)
239
  ? (r.balance / base - 1)
240
  : 0
 
241
  return {
242
  key: `agent|${r.agent_name}|${r.model}`,
243
  kind: 'agent',
 
253
  return [bhCard, ...agentCards]
254
  })
255
 
256
+ /* Dynamic font size for long names (no clipping) */
257
  function nameFontSize(name='') {
258
  const len = name.length
259
+ if (len <= 12) return '20px'
260
+ if (len <= 16) return '18px'
261
+ if (len <= 22) return '16px'
262
+ if (len <= 28) return '15px'
263
+ return '14px'
264
  }
265
  </script>
266
 
 
347
  .card__row {
348
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
349
  }
350
+
351
+ /* title shrinks (no ellipsis) */
352
  .card__title {
353
+ font-weight: 800; color: #0F172A; white-space: nowrap;
 
354
  }
355
+
356
  .card__balance { font-weight: 900; color: #0F172A; font-size: 20px; }
357
  .card__sub { font-size: 12px; color: #5B6476; opacity: .85; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
358
  .card__meta { margin-top: 4px; align-items: center; }
 
364
  background: #EEF2F7; color: #0F172A;
365
  }
366
  .pill.neg { background: #FEE2E2; color: #B91C1C; }
 
367
 
368
  /* utility */
369
  .spacer { flex: 1 1 auto; }