Spaces:
Running
Running
Jimin Huang
commited on
Commit
·
049df43
1
Parent(s):
3c4f27f
Change settings
Browse files- src/views/LiveView.vue +35 -48
src/views/LiveView.vue
CHANGED
|
@@ -91,20 +91,16 @@
|
|
| 91 |
</template>
|
| 92 |
|
| 93 |
<script setup>
|
| 94 |
-
import { ref, computed, onMounted
|
| 95 |
import AssetTabs from '../components/AssetTabs.vue'
|
| 96 |
import CompareChartE from '../components/CompareChartE.vue'
|
| 97 |
import { dataService } from '../lib/dataService'
|
| 98 |
-
import { getAllDecisions } from '../lib/dataCache'
|
| 99 |
-
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 |
-
// Plug in
|
| 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,
|
|
@@ -123,8 +119,6 @@ const ASSET_ICONS = {
|
|
| 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 () => {
|
|
@@ -157,6 +151,27 @@ const filteredRows = computed(() =>
|
|
| 157 |
})
|
| 158 |
)
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
/* Best model per agent (by balance) */
|
| 161 |
const winners = computed(() => {
|
| 162 |
const byAgent = new Map()
|
|
@@ -179,42 +194,6 @@ const winnersForChart = computed(() =>
|
|
| 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)
|
|
@@ -253,7 +232,7 @@ const cards = computed(() => {
|
|
| 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'
|
|
@@ -348,12 +327,20 @@ function nameFontSize(name='') {
|
|
| 348 |
display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
|
| 349 |
}
|
| 350 |
|
| 351 |
-
/* title shrinks
|
| 352 |
.card__title {
|
| 353 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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; }
|
| 359 |
.card__date { margin-top: 2px; }
|
|
|
|
| 91 |
</template>
|
| 92 |
|
| 93 |
<script setup>
|
| 94 |
+
import { ref, computed, onMounted } from 'vue'
|
| 95 |
import AssetTabs from '../components/AssetTabs.vue'
|
| 96 |
import CompareChartE from '../components/CompareChartE.vue'
|
| 97 |
import { dataService } from '../lib/dataService'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
/* ---------- config ---------- */
|
| 100 |
const orderedAssets = ['BTC','ETH','MSFT','BMRN','TSLA'] // MRNA removed
|
| 101 |
const EXCLUDED_AGENT_NAMES = new Set(['vanilla', 'vinilla']) // case-insensitive
|
| 102 |
|
| 103 |
+
// Plug in real logos if you have them
|
| 104 |
const AGENT_LOGOS = {
|
| 105 |
// 'DeepFundAgent': new URL('../assets/images/agents/deepfund.png', import.meta.url).href,
|
| 106 |
// 'InvestorAgent': new URL('../assets/images/agents/investor.png', import.meta.url).href,
|
|
|
|
| 119 |
const mode = ref('usd') // 'usd' | 'pct'
|
| 120 |
const asset = ref('BTC')
|
| 121 |
const rowsRef = ref([])
|
|
|
|
|
|
|
| 122 |
|
| 123 |
/* ---------- bootstrap ---------- */
|
| 124 |
onMounted(async () => {
|
|
|
|
| 151 |
})
|
| 152 |
)
|
| 153 |
|
| 154 |
+
/* Baseline (Buy & Hold) directly from tableRows */
|
| 155 |
+
const baselineRow = computed(() => {
|
| 156 |
+
const rows = (rowsRef.value || []).filter(r => r.asset === asset.value)
|
| 157 |
+
|
| 158 |
+
// heuristics: prefer explicit strategy markers, else name/model contains buy+hold
|
| 159 |
+
const candidates = rows.filter(r => {
|
| 160 |
+
const s = (r.strategy || '').toLowerCase()
|
| 161 |
+
const an = (r.agent_name || '').toLowerCase()
|
| 162 |
+
const m = (r.model || '').toLowerCase()
|
| 163 |
+
return s === 'buy_hold' || s === 'buy&hold' ||
|
| 164 |
+
(an.includes('buy') && an.includes('hold')) ||
|
| 165 |
+
(m.includes('buy') && m.includes('hold'))
|
| 166 |
+
})
|
| 167 |
+
|
| 168 |
+
if (!candidates.length) return null
|
| 169 |
+
const ts = (r) => new Date(r.end_date || r.last_nav_ts || 0).getTime()
|
| 170 |
+
return candidates.sort((a, b) => ts(b) - ts(a))[0]
|
| 171 |
+
})
|
| 172 |
+
const bhBalance = computed(() => baselineRow.value?.balance ?? null)
|
| 173 |
+
const bhDate = computed(() => baselineRow.value?.end_date || baselineRow.value?.last_nav_ts || '')
|
| 174 |
+
|
| 175 |
/* Best model per agent (by balance) */
|
| 176 |
const winners = computed(() => {
|
| 177 |
const byAgent = new Map()
|
|
|
|
| 194 |
}))
|
| 195 |
)
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
/* Build 5 cards: Buy&Hold + top 4 agents */
|
| 198 |
const cards = computed(() => {
|
| 199 |
const base = Number(bhBalance.value ?? 100000)
|
|
|
|
| 232 |
return [bhCard, ...agentCards]
|
| 233 |
})
|
| 234 |
|
| 235 |
+
/* Dynamic font size for long names (no clipping). */
|
| 236 |
function nameFontSize(name='') {
|
| 237 |
const len = name.length
|
| 238 |
if (len <= 12) return '20px'
|
|
|
|
| 327 |
display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
|
| 328 |
}
|
| 329 |
|
| 330 |
+
/* title shrinks and never pushes the number off the card */
|
| 331 |
.card__title {
|
| 332 |
+
flex: 1 1 auto; /* take available space */
|
| 333 |
+
min-width: 0; /* allow shrink */
|
| 334 |
+
font-weight: 800; color: #0F172A;
|
| 335 |
+
white-space: nowrap;
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
.card__balance {
|
| 339 |
+
flex: 0 0 auto; /* never shrink */
|
| 340 |
+
white-space: nowrap;
|
| 341 |
+
font-weight: 900; color: #0F172A; font-size: 20px;
|
| 342 |
}
|
| 343 |
|
|
|
|
| 344 |
.card__sub { font-size: 12px; color: #5B6476; opacity: .85; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 345 |
.card__meta { margin-top: 4px; align-items: center; }
|
| 346 |
.card__date { margin-top: 2px; }
|