Jimin Huang commited on
Commit
b55b694
·
1 Parent(s): 61cc343

Change settings

Browse files
Files changed (1) hide show
  1. src/views/RequestView.vue +144 -302
src/views/RequestView.vue CHANGED
@@ -1,12 +1,13 @@
1
  <template>
2
  <div class="arena-page">
 
3
  <!-- ============== ASSETS ============== -->
4
  <section class="section">
5
  <header class="section-head">
6
  <h2 class="section-title">
7
  <span class="ama-gradient">Assets in the Arena</span>
8
  </h2>
9
- <p class="section-sub">Live from dataServiceB&H sparkline • 1M change • coverage</p>
10
  </header>
11
 
12
  <div class="grid grid-assets-4">
@@ -24,77 +25,22 @@
24
 
25
  <div class="asset-body">
26
  <div class="spark-wrap">
27
- <Sparkline :data="a.month" :color="a.sparkColor" />
28
  </div>
29
 
30
  <div class="asset-stats">
31
- <div class="stat">
32
- <div class="stat-label">1M</div>
33
- <div class="stat-value mono" :class="a.change1m >= 0 ? 'pos' : 'neg'">
34
- {{ signedPct(a.change1m) }}
35
- </div>
36
- </div>
37
- <div class="stat">
38
- <div class="stat-label">Runs</div>
39
- <div class="stat-value mono">{{ a.runs }}</div>
40
- </div>
41
- <div class="stat">
42
- <div class="stat-label">Agents</div>
43
- <div class="stat-value mono">{{ a.agents }}</div>
44
- </div>
45
- <div class="stat">
46
- <div class="stat-label">Days</div>
47
- <div class="stat-value mono">{{ a.days }}</div>
48
- </div>
49
  </div>
50
-
51
- <p class="asset-desc muted clamp-2" :title="a.desc">{{ a.desc }}</p>
52
- </div>
53
-
54
- <div class="asset-foot">
55
- <div class="asset-bar" :style="{ '--pct': a.coveragePct + '%' }"></div>
56
- <div class="asset-foot-row">
57
- <span class="muted">Coverage</span>
58
- <span class="mono">{{ Math.round(a.coveragePct) }}%</span>
59
- </div>
60
- <div class="asset-foot-row">
61
- <span class="muted">EOD</span>
62
- <span class="mono">{{ a.eod || '—' }}</span>
63
  </div>
64
- </div>
65
- </article>
66
- </div>
67
-
68
- <div class="cta-row">
69
- <a :href="assetFormUrl" target="_blank" rel="noopener" class="btn-cta link-btn">
70
- <i class="pi pi-plus mr-2" /> Vote to Add Asset
71
- </a>
72
- </div>
73
- </section>
74
-
75
- <!-- ============== AGENTS (no performance) ============== -->
76
- <section class="section">
77
- <header class="section-head">
78
- <h2 class="section-title">
79
- <span class="ama-gradient">Agents in the Arena</span>
80
- </h2>
81
- <p class="section-sub">Tournament cards • GitHub & arXiv links • no performance</p>
82
- </header>
83
-
84
- <div class="grid grid-agents">
85
- <article v-for="g in agents" :key="g.name" class="card agent-card">
86
- <div class="agent-top">
87
- <div class="agent-badge" :style="{ borderColor: g.color }">
88
- <div class="agent-ring" :style="{ background: g.color }"></div>
89
- <img :src="g.logo" :alt="g.name" class="agent-logo" />
90
  </div>
91
- <div class="agent-title">
92
- <div class="agent-name truncate" :title="g.name">{{ g.name }}</div>
93
- <div class="agent-links">
94
- <a :href="g.github" target="_blank" rel="noopener" class="link">GitHub</a>
95
- <span class="dot">•</span>
96
- <a :href="g.arxiv" target="_blank" rel="noopener" class="link">arXiv</a>
97
- </div>
98
  </div>
99
  </div>
100
 
@@ -105,7 +51,7 @@
105
  </article>
106
  </div>
107
 
108
- <!-- ========== Info-only Agent Integration Guide + Form ========== -->
109
  <div class="card integration-guide">
110
  <h3>Agent Integration Guide</h3>
111
 
@@ -116,7 +62,7 @@
116
 
117
  <div class="guide-section">
118
  <h4>Input (What we send to your agent)</h4>
119
- <pre class="code-block">{
120
  "date": "2025-10-24",
121
  "price": {"BTC": 67890.50},
122
  "news": {"BTC": "Bitcoin news..."},
@@ -139,114 +85,132 @@
139
 
140
  <div class="guide-section">
141
  <h4>Output (What we expect from your agent)</h4>
142
- <pre class="code-block">{
143
  "recommended_action": "BUY"
144
  }</pre>
145
  <p class="note"><strong>Valid actions:</strong> <code>"BUY"</code>, <code>"SELL"</code>, or <code>"HOLD"</code> (uppercase)</p>
146
  </div>
147
  </div>
148
 
149
- <div class="card form-container">
150
- <h3 class="form-title">Submit Your Agent</h3>
151
- <form @submit.prevent="submitAgent">
152
- <div class="field">
153
- <label for="agentName">Agent Name</label>
154
- <input id="agentName" v-model.trim="agentForm.name" placeholder="e.g., MyTradingAgent" />
155
- </div>
156
- <div class="field">
157
- <label for="agentEndpoint">API Endpoint</label>
158
- <input id="agentEndpoint" v-model.trim="agentForm.endpoint" placeholder="https://api.example.com/trading-agent" />
159
- </div>
160
- <div class="field">
161
- <label for="agentDescription">Description (Optional)</label>
162
- <textarea id="agentDescription" v-model.trim="agentForm.description" placeholder="Brief description of your agent..." rows="3"></textarea>
163
- </div>
164
- <button type="submit" class="btn-cta w-full" :disabled="!agentForm.name || !agentForm.endpoint">
165
- <span class="mr-2">📨</span> Submit Agent
166
- </button>
167
- <p v-if="agentForm.message" class="form-message is-success">
168
- {{ agentForm.message }}
169
- </p>
170
- </form>
171
- </div>
172
  </section>
 
173
  </div>
174
  </template>
175
 
176
  <script>
177
- import { dataService } from '../lib/dataService'
178
- import { getAllDecisions } from '../lib/dataCache'
179
- import { readAllRawDecisions } from '../lib/idb'
180
- import { filterRowsToNyseTradingDays } from '../lib/marketCalendar'
181
- import { computeBuyHoldEquity } from '../lib/perf'
182
-
183
  /**
184
  * Minimal inline sparkline component (SVG) — no external deps.
185
- * Responsive via viewBox/preserveAspectRatio.
186
  */
187
- const Sparkline = {
188
- name: 'Sparkline',
 
 
 
 
 
 
189
  props: {
190
- data: { type: Array, required: true }, // array of numbers
191
- color: { type: String, default: '#22c55e' },
192
- width: { type: Number, default: 420 },
193
- height: { type: Number, default: 90 },
194
- strokeWidth: { type: Number, default: 2 }
195
  },
196
- computed: {
197
- path() {
198
- const d = this.data || []
199
- if (!d.length) return ''
200
- const w = this.width
201
- const h = this.height
202
- const min = Math.min(...d), max = Math.max(...d)
203
- const range = max - min || 1
204
- const step = d.length === 1 ? w : w / (d.length - 1)
205
- const points = d.map((v, i) => {
206
- const x = i * step
207
- const y = h - ((v - min) / range) * h
208
- return [x, y]
209
- })
210
- return points.reduce((acc, [x, y], i) => (i ? acc + ` L ${x},${y}` : `M ${x},${y}`), '')
211
- },
212
- lastIsUp() {
213
- const d = this.data || []
214
- return d.length >= 2 ? d[d.length - 1] >= d[0] : true
 
 
 
 
 
 
 
215
  }
216
  },
217
- template: `
218
- <svg :viewBox="\`0 0 \${width} \${height}\`" class="spark-svg" preserveAspectRatio="none">
219
- <defs>
220
- <linearGradient id="sparkFill" x1="0" y1="0" x2="0" y2="1">
221
- <stop :stop-color="color" stop-opacity="0.22" offset="0%"/>
222
- <stop :stop-color="color" stop-opacity="0.02" offset="100%"/>
223
- </linearGradient>
224
- </defs>
225
- <path :d="path + ' L ' + width + ',' + height + ' L 0,' + height + ' Z'"
226
- fill="url(#sparkFill)" />
227
- <path :d="path" :stroke="color" :stroke-width="strokeWidth" fill="none"
228
- :class="lastIsUp ? 'spark-up' : 'spark-down'"/>
229
- </svg>
230
- `
231
  }
232
 
233
  export default {
234
  name: 'RequestView',
235
- components: { Sparkline },
236
  data() {
237
  return {
238
  assetFormUrl: 'https://forms.gle/your-asset-request-form',
239
 
240
- // dynamic, filled from dataService
241
- assets: [],
242
- // keep the agent section perf-free
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  agents: [
244
  {
245
  name: 'InvestorAgent',
246
  github: 'https://github.com/your-org/investor-agent',
247
  arxiv: 'https://arxiv.org/abs/xxxyyy',
248
- strategy: 'Aggressive Long Only',
249
- focus: 'BTC, MSFT',
250
  color: 'linear-gradient(90deg,#ffd700,#eab308)',
251
  logo: new URL('../assets/images/agents_images/investor.png', import.meta.url).href
252
  },
@@ -254,8 +218,7 @@ export default {
254
  name: 'TradeAgent',
255
  github: 'https://github.com/your-org/trade-agent',
256
  arxiv: 'https://arxiv.org/abs/aaabbb',
257
- strategy: 'Aggressive Long Only',
258
- focus: 'BTC, ETH',
259
  color: 'linear-gradient(90deg,#4338ca,#6d28d9)',
260
  logo: new URL('../assets/images/agents_images/tradeagent.png', import.meta.url).href
261
  },
@@ -263,8 +226,7 @@ export default {
263
  name: 'DeepFundAgent',
264
  github: 'https://github.com/your-org/deepfund-agent',
265
  arxiv: 'https://arxiv.org/abs/cccdde',
266
- strategy: 'Aggressive Long Only',
267
- focus: '—',
268
  color: 'linear-gradient(90deg,#0ea5e9,#22d3ee)',
269
  logo: new URL('../assets/images/agents_images/deepfund.png', import.meta.url).href
270
  },
@@ -272,51 +234,13 @@ export default {
272
  name: 'HedgeFundAgent',
273
  github: 'https://github.com/your-org/hedgefund-agent',
274
  arxiv: 'https://arxiv.org/abs/fffggg',
275
- strategy: 'Aggressive Long Only',
276
- focus: 'BMRN, TSLA',
277
  color: 'linear-gradient(90deg,#f43f5e,#ef4444)',
278
  logo: new URL('../assets/images/agents_images/hedgefund.png', import.meta.url).href
279
  }
280
- ],
281
-
282
- // internal live state
283
- rowsRef: [],
284
- allDecisions: [],
285
- unsubscribe: null,
286
-
287
- // icon map
288
- ASSET_ICONS: {
289
- BTC: new URL('../assets/images/assets_images/BTC.png', import.meta.url).href,
290
- ETH: new URL('../assets/images/assets_images/ETH.png', import.meta.url).href,
291
- MSFT: new URL('../assets/images/assets_images/MSFT.png', import.meta.url).href,
292
- BMRN: new URL('../assets/images/assets_images/BMRN.png', import.meta.url).href,
293
- TSLA: new URL('../assets/images/assets_images/TSLA.png', import.meta.url).href,
294
- }
295
  }
296
  },
297
-
298
- mounted() {
299
- this.unsubscribe = dataService.subscribe((state) => {
300
- this.rowsRef = Array.isArray(state.tableRows) ? state.tableRows : []
301
- this.rebuildAssets().catch(() => {})
302
- })
303
- this.rowsRef = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
304
- if (!dataService.loaded && !dataService.loading) {
305
- dataService.load(false).catch(e => console.error('RequestView: load failed', e))
306
- }
307
-
308
- // warm decision cache
309
- this.allDecisions = getAllDecisions() || []
310
- if (!this.allDecisions.length) {
311
- readAllRawDecisions().then(cached => { if (cached?.length) this.allDecisions = cached })
312
- }
313
- this.rebuildAssets().catch(() => {})
314
- },
315
-
316
- beforeUnmount() {
317
- if (this.unsubscribe) { this.unsubscribe(); this.unsubscribe = null }
318
- },
319
-
320
  methods: {
321
  signedMoney(n) {
322
  const v = Number(n || 0)
@@ -329,109 +253,13 @@ export default {
329
  const v = Number(p || 0) * 100
330
  const s = v >= 0 ? '+' : '−'
331
  return `${s}${Math.abs(v).toFixed(2)}%`
332
- },
333
- fmtUSD(n) {
334
- return (n ?? 0).toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
335
- },
336
-
337
- /* ===== Dynamic assets built from B&H equity ===== */
338
- async buildAssetSeq(assetCode) {
339
- let seq = (this.allDecisions || []).filter(r => r.asset === assetCode)
340
- seq.sort((a,b) => (a.date > b.date ? 1 : -1))
341
- const isCrypto = assetCode === 'BTC' || assetCode === 'ETH'
342
- if (!isCrypto) {
343
- try { seq = await filterRowsToNyseTradingDays(seq) } catch {}
344
- }
345
- return seq
346
- },
347
- sampleSeries(values, n = 12) {
348
- if (!Array.isArray(values) || !values.length) return []
349
- if (values.length <= n) return values.slice(-n)
350
- const step = (values.length - 1) / (n - 1)
351
- const out = []
352
- for (let i = 0; i < n; i++) out.push(values[Math.round(i * step)])
353
- return out
354
- },
355
- pctChange(a, b) {
356
- if (a == null || b == null || a === 0) return 0
357
- return (b / a) - 1
358
- },
359
-
360
- async rebuildAssets() {
361
- const assetsInRows = Array.from(new Set((this.rowsRef || []).map(r => r.asset))).filter(Boolean)
362
- if (!assetsInRows.length) { this.assets = []; return }
363
-
364
- const cards = []
365
- for (const code of assetsInRows) {
366
- const seq = await this.buildAssetSeq(code)
367
- if (!seq.length) continue
368
-
369
- // Use B&H equity as the normalized “price/equity” series for spark and change
370
- let bh = []
371
- try { bh = computeBuyHoldEquity(seq, 100000) || [] } catch { bh = [] }
372
- const lastIdx = bh.length - 1
373
- if (lastIdx < 0) continue
374
-
375
- const lastDate = seq[lastIdx]?.date || null
376
- const spark = this.sampleSeries(bh.map(v => v), 12)
377
- const change1m = this.pctChange(spark[0], spark[spark.length - 1])
378
-
379
- // trading day span
380
- let days = 0
381
- try {
382
- const start = new Date(seq[0].date)
383
- const end = new Date(seq[lastIdx].date)
384
- days = Math.max(1, Math.round((end - start) / 86400000) + 1)
385
- } catch {}
386
-
387
- // runs & agents from rows table
388
- const rowsForAsset = (this.rowsRef || []).filter(r => r.asset === code)
389
- const runs = rowsForAsset.length
390
- const agents = new Set(rowsForAsset.map(r => r.agent_name)).size
391
-
392
- // coverage ~ unique dates present / span
393
- const uniqueDates = new Set(seq.map(r => r.date)).size
394
- const coveragePct = Math.max(0, Math.min(100, Math.round((uniqueDates / Math.max(1, days)) * 100)))
395
-
396
- cards.push({
397
- code,
398
- name: code,
399
- type: (code === 'BTC' || code === 'ETH') ? 'Crypto' : 'Stock',
400
- icon: this.ASSET_ICONS[code] || null,
401
- month: spark,
402
- sparkColor: (code === 'BTC') ? '#f59e0b' : (code === 'ETH' ? '#6366f1' : '#22c55e'),
403
- change1m,
404
- runs, agents, days,
405
- eod: lastDate,
406
- coveragePct,
407
- desc: (code === 'BTC') ? 'Blue-chip crypto asset; 24/7 trading.'
408
- : (code === 'ETH') ? 'Smart-contract platform; high on-chain activity.'
409
- : 'Equity; regular trading days.'
410
- })
411
- }
412
-
413
- // Prefer a stable display order
414
- const order = ['BTC','ETH','MSFT','BMRN','TSLA']
415
- cards.sort((a,b) => (order.indexOf(a.code) - order.indexOf(b.code)))
416
- this.assets = cards
417
- },
418
-
419
- submitAgent() {
420
- if (!this.agentForm.name || !this.agentForm.endpoint) return
421
- this.agentForm.message = `Thanks, ${this.agentForm.name}! We will review your endpoint: ${this.agentForm.endpoint}`
422
- this.agentForm.description = ''
423
- }
424
- },
425
-
426
- // simple local form state
427
- created() {
428
- this.agentForm = { name: '', endpoint: '', description: '', message: '' }
429
- }
430
  }
431
  </script>
432
 
433
  <style scoped>
434
  /* ===== AMA Core ===== */
 
435
  .ama-gradient{
436
  background: linear-gradient(90deg, rgb(0,0,185), #7b2cbf, #d946ef, rgb(240,0,15));
437
  -webkit-background-clip: text; background-clip: text; color: transparent;
@@ -439,7 +267,8 @@ export default {
439
 
440
  .arena-page{
441
  max-width:1280px; margin:0 auto;
442
- padding:16px 20px 120px; background:#fff;
 
443
  }
444
 
445
  /* ===== Helpers ===== */
@@ -453,26 +282,29 @@ export default {
453
  .section-title{ font-size:clamp(1.2rem, 1.2rem + 0.4vw, 1.6rem); font-weight:800; letter-spacing:-0.02em; display:inline-block; }
454
  .section-sub{ margin-top:4px; color:#6b7280; }
455
 
456
- /* ===== Grids ===== */
457
  .grid{ display:grid; gap:14px; min-width:0; }
458
  .grid-assets-4{ grid-template-columns: repeat(4, minmax(0,1fr)); }
459
  .grid-agents{ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
 
460
  @media (max-width: 1200px){ .grid-assets-4{ grid-template-columns: repeat(3, minmax(0,1fr)); } }
461
  @media (max-width: 900px){ .grid-assets-4{ grid-template-columns: repeat(2, minmax(0,1fr)); } }
462
  @media (max-width: 620px){ .grid-assets-4{ grid-template-columns: 1fr; } }
463
 
464
  /* ===== Card base ===== */
465
  .card{
466
- background:#ffffff; border:1px solid #E7ECF3; border-radius:14px;
467
  box-shadow:0 1px 2px rgba(16,24,40,.03), 0 4px 12px rgba(16,24,40,.04);
468
  transition: transform .18s ease, box-shadow .2s ease, border-color .2s ease;
469
- min-width:0;
470
  }
471
  .card:hover{ transform:translateY(-2px); box-shadow:0 10px 26px rgba(16,24,40,.08); border-color:#D9E2EF; }
472
 
473
  /* ===== Assets ===== */
474
- .asset-card{ padding:12px; display:flex; flex-direction:column; gap:10px; }
475
- .asset-head{ display:grid; grid-template-columns:44px 1fr auto; gap:10px; align-items:center; min-width:0; }
 
 
476
  .asset-logo-wrap{ width:44px; height:44px; border-radius:12px; background:#f3f4f6; display:grid; place-items:center; border:1px solid #E7ECF3; overflow:hidden; }
477
  .asset-logo{ width:70%; height:70%; object-fit:contain; }
478
  .asset-name{ min-width:0; }
@@ -480,13 +312,15 @@ export default {
480
  .asset-full{ font-size:12px; color:#64748b; }
481
  .type-badge{
482
  border-radius:999px; padding:4px 8px; font-size:12px; font-weight:700;
483
- border:1px solid #E7ECF3; background:#F6F8FB; color:#0f172a; white-space:nowrap;
484
  }
485
  .type-crypto{ background:#f8fbff; border-color:#e0e7ff; color:#1e3a8a; }
486
  .type-stock { background:#f6fffb; border-color:#d9fbe6; color:#064e3b; }
 
487
 
488
- .asset-body{ display:flex; flex-direction:column; gap:10px; }
489
  .spark-wrap{ width:100%; height:90px; overflow:hidden; border-radius:8px; }
 
490
  .spark-svg{ width:100%; height:100%; display:block; }
491
  .spark-up{ filter: drop-shadow(0 0 0 rgba(0,0,0,0)); }
492
  .spark-down{ opacity:.95; }
@@ -505,33 +339,42 @@ export default {
505
  .asset-bar::after{ content:''; position:absolute; left:0; top:0; height:100%; width:var(--pct,0%); background:linear-gradient(90deg,#16a34a,#22c55e); }
506
  .asset-foot-row{ margin-top:6px; display:flex; align-items:center; justify-content:space-between; color:#6b7280; }
507
 
508
- /* ===== Agents (perf-free) ===== */
509
- .agent-card{ padding:12px; display:flex; flex-direction:column; gap:8px; }
510
- .agent-top{ display:grid; grid-template-columns:52px 1fr; gap:10px; align-items:center; min-width:0; }
 
 
511
  .agent-badge{ width:52px; height:52px; border-radius:16px; border:2px solid #e5e7eb; position:relative; display:grid; place-items:center; overflow:hidden; }
512
  .agent-ring{ position:absolute; inset:0; opacity:.18; }
513
  .agent-logo{ width:70%; height:70%; object-fit:contain; position:relative; z-index:1; }
 
514
  .agent-title{ min-width:0; }
515
  .agent-name{ font-weight:900; letter-spacing:.02em; color:#0f172a; text-transform:uppercase; }
516
  .agent-links{ display:flex; align-items:center; gap:8px; color:#64748b; flex-wrap:wrap; min-width:0; }
517
  .link{ color:#334155; font-weight:600; text-decoration:none; }
518
  .link:hover{ color:#0f172a; text-decoration:underline; }
519
- .dot{ color:#94a3b8; }
 
520
  .agent-foot{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:2px; }
521
  .chip{ display:inline-flex; align-items:center; gap:6px; padding:4px 8px; border-radius:999px; font-size:12px; font-weight:700; background:#F6F8FB; color:#0f172a; border:1px solid #E7ECF3; white-space:nowrap; }
522
  .chip-outline{ background:#fff; }
 
 
523
 
524
- /* ===== Integration Guide / Form ===== */
525
  .integration-guide{ margin-top:12px; padding:16px; }
526
  .integration-guide h3{ font-weight:800; font-size:1.1rem; color:#0f172a; margin-bottom:8px; }
527
  .guide-section{ margin:10px 0; }
528
  .code-block{ background:#0b1020; color:#E6EDF3; border-radius:10px; padding:12px; overflow:auto; font-size:12.5px; line-height:1.5; }
529
  .note{ color:#334155; margin-top:6px; }
530
 
 
531
  .form-container{ margin-top:12px; padding:16px; }
532
  .form-title{ font-weight:800; margin-bottom:8px; color:#0f172a; }
533
  .field{ display:flex; flex-direction:column; gap:6px; margin:10px 0; }
534
- .field input, .field textarea{ border:1px solid #E7ECF3; border-radius:10px; padding:10px 12px; font-size:14px; width:100%; }
 
 
535
  .field input:focus, .field textarea:focus{ outline:none; border-color:#C7D2FE; box-shadow:0 0 0 3px rgba(59,130,246,.15); }
536
  .btn-cta{ background:linear-gradient(90deg, rgb(0,0,185), #7b2cbf, #d946ef, rgb(240,0,15)); border:none; color:#fff; font-weight:800; letter-spacing:.02em; padding:10px 16px; border-radius:12px; cursor:pointer; }
537
  .btn-cta:disabled{ opacity:.5; cursor:not-allowed; }
@@ -542,5 +385,4 @@ export default {
542
  .cta-row{ margin-top:12px; display:flex; justify-content:center; }
543
  .link-btn{ display:inline-flex; align-items:center; justify-content:center; gap:.5rem; text-decoration:none; }
544
  .mr-2{ margin-right:.5rem; }
545
- .pos{ color:#0e7a3a; } .neg{ color:#B91C1C; }
546
  </style>
 
1
  <template>
2
  <div class="arena-page">
3
+
4
  <!-- ============== ASSETS ============== -->
5
  <section class="section">
6
  <header class="section-head">
7
  <h2 class="section-title">
8
  <span class="ama-gradient">Assets in the Arena</span>
9
  </h2>
10
+ <p class="section-sub">Four-up grid type 1-month price sparkline • quick stats</p>
11
  </header>
12
 
13
  <div class="grid grid-assets-4">
 
25
 
26
  <div class="asset-body">
27
  <div class="spark-wrap">
28
+ <MiniEchart :data="a.month" :color="a.sparkColor" />
29
  </div>
30
 
31
  <div class="asset-stats">
32
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  </div>
34
+
35
+ <div class="stat-value mono" :class="g.winRate >= 0.5 ? 'pos' : 'neg'">
36
+ {{ Math.round(g.winRate * 100) }}%
37
+ </div>
 
 
 
 
 
 
 
 
 
38
  </div>
39
+
40
+ <div class="stat-value mono">{{ g.trades }}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  </div>
42
+
43
+ <div class="stat-value mono">{{ g.days }}</div>
 
 
 
 
 
44
  </div>
45
  </div>
46
 
 
51
  </article>
52
  </div>
53
 
54
+ <!-- ================= Integration Guide (info-only, no external UI libs) ================= -->
55
  <div class="card integration-guide">
56
  <h3>Agent Integration Guide</h3>
57
 
 
62
 
63
  <div class="guide-section">
64
  <h4>Input (What we send to your agent)</h4>
65
+ <pre class="code-block">{
66
  "date": "2025-10-24",
67
  "price": {"BTC": 67890.50},
68
  "news": {"BTC": "Bitcoin news..."},
 
85
 
86
  <div class="guide-section">
87
  <h4>Output (What we expect from your agent)</h4>
88
+ <pre class="code-block">{
89
  "recommended_action": "BUY"
90
  }</pre>
91
  <p class="note"><strong>Valid actions:</strong> <code>"BUY"</code>, <code>"SELL"</code>, or <code>"HOLD"</code> (uppercase)</p>
92
  </div>
93
  </div>
94
 
95
+ <!-- ================= Submit Agent (button only) ================= -->
96
+ <div class="cta-row">
97
+ <a :href="agentFormUrl" target="_blank" rel="noopener" class="btn-cta link-btn">
98
+ <i class=\"pi pi-send mr-2\" /> Submit Agent (Google Form)
99
+ </a>
100
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  </section>
102
+
103
  </div>
104
  </template>
105
 
106
  <script>
 
 
 
 
 
 
107
  /**
108
  * Minimal inline sparkline component (SVG) — no external deps.
109
+ * NOTE: width/height handled via viewBox for responsive fit.
110
  */
111
+ import * as echarts from 'echarts/core'
112
+ import { LineChart } from 'echarts/charts'
113
+ import { GridComponent, TooltipComponent } from 'echarts/components'
114
+ import { CanvasRenderer } from 'echarts/renderers'
115
+ echarts.use([LineChart, GridComponent, TooltipComponent, CanvasRenderer])
116
+
117
+ const MiniEchart = {
118
+ name: 'MiniEchart',
119
  props: {
120
+ data: { type: Array, required: true },
121
+ color: { type: String, default: '#22c55e' }
 
 
 
122
  },
123
+ mounted() {
124
+ const el = this.$refs.c
125
+ this._chart = echarts.init(el)
126
+ const d = Array.isArray(this.data) ? this.data : []
127
+ this._chart.setOption({
128
+ animation: true,
129
+ grid: { left: 0, right: 0, top: 0, bottom: 0 },
130
+ xAxis: { type: 'category', show: false, boundaryGap: false, data: d.map((_, i) => i) },
131
+ yAxis: { type: 'value', show: false, scale: true },
132
+ tooltip: { show: false },
133
+ color: [this.color || '#22c55e'],
134
+ series: [{
135
+ type: 'line',
136
+ data: d,
137
+ showSymbol: false,
138
+ smooth: 0.35,
139
+ lineStyle: { width: 2 },
140
+ areaStyle: { opacity: 0.15 }
141
+ }]
142
+ })
143
+ },
144
+ watch: {
145
+ data(newVal) {
146
+ if (!this._chart) return
147
+ const d = Array.isArray(newVal) ? newVal : []
148
+ this._chart.setOption({ xAxis: { data: d.map((_, i) => i) }, series: [{ data: d }] })
149
  }
150
  },
151
+ beforeUnmount() { if (this._chart) { this._chart.dispose(); this._chart = null } },
152
+ template: `<div ref="c" class="echart-mini"></div>`
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
154
 
155
  export default {
156
  name: 'RequestView',
157
+ components: { MiniEchart },
158
  data() {
159
  return {
160
  assetFormUrl: 'https://forms.gle/your-asset-request-form',
161
 
162
+ agentFormUrl: 'https://forms.gle/your-agent-submit-form',
163
+
164
+ // ===== Demo data (replace with live) =====
165
+ assets: [
166
+ {
167
+ code: 'BTC',
168
+ name: 'Bitcoin',
169
+ type: 'Crypto',
170
+ icon: new URL('../assets/images/assets_images/BTC.png', import.meta.url).href,
171
+ month: [62000, 63500, 62800, 64200, 65000, 66100, 65500, 67000, 67800, 67550, 68220, 69000],
172
+ sparkColor: '#f59e0b',
173
+ change1m: 0.112, runs: 86, agents: 4, eod: '10/25/2025',
174
+ coveragePct: 92, desc: 'Blue-chip crypto asset; 24/7 trading.'
175
+ },
176
+ {
177
+ code: 'ETH',
178
+ name: 'Ethereum',
179
+ type: 'Crypto',
180
+ icon: new URL('../assets/images/assets_images/ETH.png', import.meta.url).href,
181
+ month: [2500, 2480, 2525, 2600, 2580, 2630, 2680, 2700, 2660, 2720, 2735, 2790],
182
+ sparkColor: '#6366f1',
183
+ change1m: 0.116, runs: 84, agents: 3, eod: '10/25/2025',
184
+ coveragePct: 88, desc: 'Smart-contract platform; high on-chain activity.'
185
+ },
186
+ {
187
+ code: 'MSFT',
188
+ name: 'Microsoft',
189
+ type: 'Stock',
190
+ icon: new URL('../assets/images/assets_images/MSFT.png', import.meta.url).href,
191
+ month: [406, 409, 404, 415, 420, 422, 418, 425, 430, 432, 429, 435],
192
+ sparkColor: '#22c55e',
193
+ change1m: 0.071, runs: 60, agents: 4, eod: '10/25/2025',
194
+ coveragePct: 77, desc: 'Megacap technology equity; NYSE trading days.'
195
+ },
196
+ {
197
+ code: 'BMRN',
198
+ name: 'BioMarin',
199
+ type: 'Stock',
200
+ icon: new URL('../assets/images/assets_images/BMRN.png', import.meta.url).href,
201
+ month: [82, 83, 82.5, 84, 85, 84.4, 86.2, 85.8, 87.1, 88, 87.6, 89.2],
202
+ sparkColor: '#06b6d4',
203
+ change1m: 0.086, runs: 51, agents: 3, eod: '10/25/2025',
204
+ coveragePct: 71, desc: 'Biotech equity; catalyst-driven volatility.'
205
+ },
206
+ ],
207
+
208
  agents: [
209
  {
210
  name: 'InvestorAgent',
211
  github: 'https://github.com/your-org/investor-agent',
212
  arxiv: 'https://arxiv.org/abs/xxxyyy',
213
+ strategy: 'Aggressive Long Only', focus: 'BTC, MSFT',
 
214
  color: 'linear-gradient(90deg,#ffd700,#eab308)',
215
  logo: new URL('../assets/images/agents_images/investor.png', import.meta.url).href
216
  },
 
218
  name: 'TradeAgent',
219
  github: 'https://github.com/your-org/trade-agent',
220
  arxiv: 'https://arxiv.org/abs/aaabbb',
221
+ strategy: 'Aggressive Long Only', focus: 'BTC, ETH',
 
222
  color: 'linear-gradient(90deg,#4338ca,#6d28d9)',
223
  logo: new URL('../assets/images/agents_images/tradeagent.png', import.meta.url).href
224
  },
 
226
  name: 'DeepFundAgent',
227
  github: 'https://github.com/your-org/deepfund-agent',
228
  arxiv: 'https://arxiv.org/abs/cccdde',
229
+ strategy: 'Aggressive Long Only', focus: '—',
 
230
  color: 'linear-gradient(90deg,#0ea5e9,#22d3ee)',
231
  logo: new URL('../assets/images/agents_images/deepfund.png', import.meta.url).href
232
  },
 
234
  name: 'HedgeFundAgent',
235
  github: 'https://github.com/your-org/hedgefund-agent',
236
  arxiv: 'https://arxiv.org/abs/fffggg',
237
+ strategy: 'Aggressive Long Only', focus: 'BMRN, TSLA',
 
238
  color: 'linear-gradient(90deg,#f43f5e,#ef4444)',
239
  logo: new URL('../assets/images/agents_images/hedgefund.png', import.meta.url).href
240
  }
241
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  }
243
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  methods: {
245
  signedMoney(n) {
246
  const v = Number(n || 0)
 
253
  const v = Number(p || 0) * 100
254
  const s = v >= 0 ? '+' : '−'
255
  return `${s}${Math.abs(v).toFixed(2)}%`
256
+ } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  }
258
  </script>
259
 
260
  <style scoped>
261
  /* ===== AMA Core ===== */
262
+ :root{ --card-pad:12px; --radius:14px; }
263
  .ama-gradient{
264
  background: linear-gradient(90deg, rgb(0,0,185), #7b2cbf, #d946ef, rgb(240,0,15));
265
  -webkit-background-clip: text; background-clip: text; color: transparent;
 
267
 
268
  .arena-page{
269
  max-width:1280px; margin:0 auto;
270
+ padding:16px 20px 120px; /* leave space for fixed footer */
271
+ background:#fff;
272
  }
273
 
274
  /* ===== Helpers ===== */
 
282
  .section-title{ font-size:clamp(1.2rem, 1.2rem + 0.4vw, 1.6rem); font-weight:800; letter-spacing:-0.02em; display:inline-block; }
283
  .section-sub{ margin-top:4px; color:#6b7280; }
284
 
285
+ /* ===== Grid layouts ===== */
286
  .grid{ display:grid; gap:14px; min-width:0; }
287
  .grid-assets-4{ grid-template-columns: repeat(4, minmax(0,1fr)); }
288
  .grid-agents{ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
289
+
290
  @media (max-width: 1200px){ .grid-assets-4{ grid-template-columns: repeat(3, minmax(0,1fr)); } }
291
  @media (max-width: 900px){ .grid-assets-4{ grid-template-columns: repeat(2, minmax(0,1fr)); } }
292
  @media (max-width: 620px){ .grid-assets-4{ grid-template-columns: 1fr; } }
293
 
294
  /* ===== Card base ===== */
295
  .card{
296
+ background:#ffffff; border:1px solid #E7ECF3; border-radius:var(--radius);
297
  box-shadow:0 1px 2px rgba(16,24,40,.03), 0 4px 12px rgba(16,24,40,.04);
298
  transition: transform .18s ease, box-shadow .2s ease, border-color .2s ease;
299
+ min-width:0; /* allow children to shrink in grid */
300
  }
301
  .card:hover{ transform:translateY(-2px); box-shadow:0 10px 26px rgba(16,24,40,.08); border-color:#D9E2EF; }
302
 
303
  /* ===== Assets ===== */
304
+ .asset-card{ padding:var(--card-pad); display:flex; flex-direction:column; gap:10px; }
305
+ .asset-head{
306
+ display:grid; grid-template-columns:44px 1fr auto; gap:10px; align-items:center; min-width:0;
307
+ }
308
  .asset-logo-wrap{ width:44px; height:44px; border-radius:12px; background:#f3f4f6; display:grid; place-items:center; border:1px solid #E7ECF3; overflow:hidden; }
309
  .asset-logo{ width:70%; height:70%; object-fit:contain; }
310
  .asset-name{ min-width:0; }
 
312
  .asset-full{ font-size:12px; color:#64748b; }
313
  .type-badge{
314
  border-radius:999px; padding:4px 8px; font-size:12px; font-weight:700;
315
+ border:1px solid #E7ECF3; background:#F6F8FB; color:#0f172a; white-space:nowrap; max-width:100%;
316
  }
317
  .type-crypto{ background:#f8fbff; border-color:#e0e7ff; color:#1e3a8a; }
318
  .type-stock { background:#f6fffb; border-color:#d9fbe6; color:#064e3b; }
319
+ .type-etf { background:#fff7f7; border-color:#ffe4e6; color:#7f1d1d; }
320
 
321
+ .asset-body{ display:flex; flex-direction:column; gap:10px; min-height:0; }
322
  .spark-wrap{ width:100%; height:90px; overflow:hidden; border-radius:8px; }
323
+ .echart-mini{ width:100%; height:90px; }
324
  .spark-svg{ width:100%; height:100%; display:block; }
325
  .spark-up{ filter: drop-shadow(0 0 0 rgba(0,0,0,0)); }
326
  .spark-down{ opacity:.95; }
 
339
  .asset-bar::after{ content:''; position:absolute; left:0; top:0; height:100%; width:var(--pct,0%); background:linear-gradient(90deg,#16a34a,#22c55e); }
340
  .asset-foot-row{ margin-top:6px; display:flex; align-items:center; justify-content:space-between; color:#6b7280; }
341
 
342
+ /* ===== Agents ===== */
343
+ .agent-card{ padding:var(--card-pad); display:flex; flex-direction:column; gap:10px; }
344
+ .agent-top{
345
+ display:grid; grid-template-columns:52px 1fr auto; gap:10px; align-items:center; min-width:0;
346
+ }
347
  .agent-badge{ width:52px; height:52px; border-radius:16px; border:2px solid #e5e7eb; position:relative; display:grid; place-items:center; overflow:hidden; }
348
  .agent-ring{ position:absolute; inset:0; opacity:.18; }
349
  .agent-logo{ width:70%; height:70%; object-fit:contain; position:relative; z-index:1; }
350
+
351
  .agent-title{ min-width:0; }
352
  .agent-name{ font-weight:900; letter-spacing:.02em; color:#0f172a; text-transform:uppercase; }
353
  .agent-links{ display:flex; align-items:center; gap:8px; color:#64748b; flex-wrap:wrap; min-width:0; }
354
  .link{ color:#334155; font-weight:600; text-decoration:none; }
355
  .link:hover{ color:#0f172a; text-decoration:underline; }
356
+ .dot{ color:#94a3b8; }.pos{ color:#0e7a3a; } .neg{ color:#B91C1C; }
357
+
358
  .agent-foot{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:2px; }
359
  .chip{ display:inline-flex; align-items:center; gap:6px; padding:4px 8px; border-radius:999px; font-size:12px; font-weight:700; background:#F6F8FB; color:#0f172a; border:1px solid #E7ECF3; white-space:nowrap; }
360
  .chip-outline{ background:#fff; }
361
+ .chip-pos{ background:#E9F7EF; border-color:#d7f0e0; color:#0e7a3a; }
362
+ .chip-neg{ background:#FBEAEA; border-color:#F3DADA; color:#B91C1C; }
363
 
364
+ /* ===== Integration Guide ===== */
365
  .integration-guide{ margin-top:12px; padding:16px; }
366
  .integration-guide h3{ font-weight:800; font-size:1.1rem; color:#0f172a; margin-bottom:8px; }
367
  .guide-section{ margin:10px 0; }
368
  .code-block{ background:#0b1020; color:#E6EDF3; border-radius:10px; padding:12px; overflow:auto; font-size:12.5px; line-height:1.5; }
369
  .note{ color:#334155; margin-top:6px; }
370
 
371
+ /* ===== Form (info-only) ===== */
372
  .form-container{ margin-top:12px; padding:16px; }
373
  .form-title{ font-weight:800; margin-bottom:8px; color:#0f172a; }
374
  .field{ display:flex; flex-direction:column; gap:6px; margin:10px 0; }
375
+ .field input, .field textarea{
376
+ border:1px solid #E7ECF3; border-radius:10px; padding:10px 12px; font-size:14px; width:100%;
377
+ }
378
  .field input:focus, .field textarea:focus{ outline:none; border-color:#C7D2FE; box-shadow:0 0 0 3px rgba(59,130,246,.15); }
379
  .btn-cta{ background:linear-gradient(90deg, rgb(0,0,185), #7b2cbf, #d946ef, rgb(240,0,15)); border:none; color:#fff; font-weight:800; letter-spacing:.02em; padding:10px 16px; border-radius:12px; cursor:pointer; }
380
  .btn-cta:disabled{ opacity:.5; cursor:not-allowed; }
 
385
  .cta-row{ margin-top:12px; display:flex; justify-content:center; }
386
  .link-btn{ display:inline-flex; align-items:center; justify-content:center; gap:.5rem; text-decoration:none; }
387
  .mr-2{ margin-right:.5rem; }
 
388
  </style>