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

Change settings

Browse files
Files changed (1) hide show
  1. src/views/RequestView.vue +274 -162
src/views/RequestView.vue CHANGED
@@ -1,13 +1,12 @@
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 gridtype1-month price sparkline quick stats</p>
11
  </header>
12
 
13
  <div class="grid grid-assets-4">
@@ -17,8 +16,8 @@
17
  <img :src="a.icon" :alt="a.code" class="asset-logo" />
18
  </div>
19
  <div class="asset-name">
20
- <div class="asset-code">{{ a.code }}</div>
21
- <div class="asset-full">{{ a.name }}</div>
22
  </div>
23
  <span class="type-badge" :class="'type-' + a.type.toLowerCase()">{{ a.type }}</span>
24
  </div>
@@ -44,12 +43,12 @@
44
  <div class="stat-value mono">{{ a.agents }}</div>
45
  </div>
46
  <div class="stat">
47
- <div class="stat-label">EOD</div>
48
- <div class="stat-value mono">{{ a.eod }}</div>
49
  </div>
50
  </div>
51
 
52
- <p class="asset-desc muted">{{ a.desc }}</p>
53
  </div>
54
 
55
  <div class="asset-foot">
@@ -58,6 +57,10 @@
58
  <span class="muted">Coverage</span>
59
  <span class="mono">{{ Math.round(a.coveragePct) }}%</span>
60
  </div>
 
 
 
 
61
  </div>
62
  </article>
63
  </div>
@@ -69,13 +72,13 @@
69
  </div>
70
  </section>
71
 
72
- <!-- ============== AGENTS ============== -->
73
  <section class="section">
74
  <header class="section-head">
75
  <h2 class="section-title">
76
  <span class="ama-gradient">Agents in the Arena</span>
77
  </h2>
78
- <p class="section-sub">Tournament cards • performance • GitHub & arXiv links</p>
79
  </header>
80
 
81
  <div class="grid grid-agents">
@@ -86,41 +89,13 @@
86
  <img :src="g.logo" :alt="g.name" class="agent-logo" />
87
  </div>
88
  <div class="agent-title">
89
- <div class="agent-name">{{ g.name }}</div>
90
  <div class="agent-links">
91
- <a :href="g.github" target="_blank" rel="noopener" class="link">
92
- <i class="pi pi-github"></i> GitHub
93
- </a>
94
  <span class="dot">•</span>
95
- <a :href="g.arxiv" target="_blank" rel="noopener" class="link">
96
- <i class="pi pi-external-link"></i> arXiv
97
- </a>
98
  </div>
99
  </div>
100
- <span class="chip" :class="g.pnl >= 0 ? 'chip-pos' : 'chip-neg'">
101
- P&L <b class="mono">{{ signedMoney(g.pnl) }}</b>
102
- </span>
103
- </div>
104
-
105
- <div class="agent-stats">
106
- <div class="stat">
107
- <div class="stat-label">Sharpe</div>
108
- <div class="stat-value mono">{{ g.sharpe }}</div>
109
- </div>
110
- <div class="stat">
111
- <div class="stat-label">Win Rate</div>
112
- <div class="stat-value mono" :class="g.winRate >= 0.5 ? 'pos' : 'neg'">
113
- {{ Math.round(g.winRate * 100) }}%
114
- </div>
115
- </div>
116
- <div class="stat">
117
- <div class="stat-label">Trades</div>
118
- <div class="stat-value mono">{{ g.trades }}</div>
119
- </div>
120
- <div class="stat">
121
- <div class="stat-label">Days</div>
122
- <div class="stat-value mono">{{ g.days }}</div>
123
- </div>
124
  </div>
125
 
126
  <div class="agent-foot">
@@ -130,27 +105,84 @@
130
  </article>
131
  </div>
132
 
133
- <div class="guide-card card">
134
- <div class="guide-title"><i class="pi pi-bolt"></i><span>How to Join</span></div>
135
- <ol class="guide-steps">
136
- <li>Expose an HTTPS endpoint that returns a trading decision <code>BUY | SELL | HOLD</code>.</li>
137
- <li>We send the current snapshot (prices, recent history, news) as JSON.</li>
138
- <li>Keep latency reasonable (&lt; 5s). We’ll handle retries/timeouts.</li>
139
- </ol>
140
- <div class="cta-row">
141
- <a :href="agentFormUrl" target="_blank" rel="noopener" class="btn-cta link-btn">
142
- <i class="pi pi-send mr-2" /> Submit Agent (Google Form)
143
- </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  </div>
145
  </div>
146
- </section>
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  </div>
149
  </template>
150
 
151
  <script>
 
 
 
 
 
 
152
  /**
153
  * Minimal inline sparkline component (SVG) — no external deps.
 
154
  */
155
  const Sparkline = {
156
  name: 'Sparkline',
@@ -183,7 +215,7 @@ const Sparkline = {
183
  }
184
  },
185
  template: `
186
- <svg :viewBox="\`0 0 \${width} \${height}\`" class="spark-svg">
187
  <defs>
188
  <linearGradient id="sparkFill" x1="0" y1="0" x2="0" y2="1">
189
  <stop :stop-color="color" stop-opacity="0.22" offset="0%"/>
@@ -204,60 +236,17 @@ export default {
204
  data() {
205
  return {
206
  assetFormUrl: 'https://forms.gle/your-asset-request-form',
207
- agentFormUrl: 'https://forms.gle/your-agent-submit-form',
208
-
209
- // ===== Demo data (replace with live) =====
210
- assets: [
211
- {
212
- code: 'BTC',
213
- name: 'Bitcoin',
214
- type: 'Crypto',
215
- icon: new URL('../assets/images/assets_images/BTC.png', import.meta.url).href,
216
- month: [62000, 63500, 62800, 64200, 65000, 66100, 65500, 67000, 67800, 67550, 68220, 69000],
217
- sparkColor: '#f59e0b',
218
- change1m: 0.112, runs: 86, agents: 4, eod: '10/25/2025',
219
- coveragePct: 92, desc: 'Blue-chip crypto asset; 24/7 trading.'
220
- },
221
- {
222
- code: 'ETH',
223
- name: 'Ethereum',
224
- type: 'Crypto',
225
- icon: new URL('../assets/images/assets_images/ETH.png', import.meta.url).href,
226
- month: [2500, 2480, 2525, 2600, 2580, 2630, 2680, 2700, 2660, 2720, 2735, 2790],
227
- sparkColor: '#6366f1',
228
- change1m: 0.116, runs: 84, agents: 3, eod: '10/25/2025',
229
- coveragePct: 88, desc: 'Smart-contract platform; high on-chain activity.'
230
- },
231
- {
232
- code: 'MSFT',
233
- name: 'Microsoft',
234
- type: 'Stock',
235
- icon: new URL('../assets/images/assets_images/MSFT.png', import.meta.url).href,
236
- month: [406, 409, 404, 415, 420, 422, 418, 425, 430, 432, 429, 435],
237
- sparkColor: '#22c55e',
238
- change1m: 0.071, runs: 60, agents: 4, eod: '10/25/2025',
239
- coveragePct: 77, desc: 'Megacap technology equity; NYSE trading days.'
240
- },
241
- {
242
- code: 'BMRN',
243
- name: 'BioMarin',
244
- type: 'Stock',
245
- icon: new URL('../assets/images/assets_images/BMRN.png', import.meta.url).href,
246
- month: [82, 83, 82.5, 84, 85, 84.4, 86.2, 85.8, 87.1, 88, 87.6, 89.2],
247
- sparkColor: '#06b6d4',
248
- change1m: 0.086, runs: 51, agents: 3, eod: '10/25/2025',
249
- coveragePct: 71, desc: 'Biotech equity; catalyst-driven volatility.'
250
- },
251
- // If you add more, the grid will wrap to next row.
252
- ],
253
 
 
 
 
254
  agents: [
255
  {
256
  name: 'InvestorAgent',
257
  github: 'https://github.com/your-org/investor-agent',
258
  arxiv: 'https://arxiv.org/abs/xxxyyy',
259
- sharpe: '0.75', winRate: 0.58, trades: 12, days: 86,
260
- pnl: 3191.97, strategy: 'Aggressive Long Only', focus: 'BTC, MSFT',
261
  color: 'linear-gradient(90deg,#ffd700,#eab308)',
262
  logo: new URL('../assets/images/agents_images/investor.png', import.meta.url).href
263
  },
@@ -265,8 +254,8 @@ export default {
265
  name: 'TradeAgent',
266
  github: 'https://github.com/your-org/trade-agent',
267
  arxiv: 'https://arxiv.org/abs/aaabbb',
268
- sharpe: '0.45', winRate: 0.52, trades: 23, days: 86,
269
- pnl: 1698.83, strategy: 'Aggressive Long Only', focus: 'BTC, ETH',
270
  color: 'linear-gradient(90deg,#4338ca,#6d28d9)',
271
  logo: new URL('../assets/images/agents_images/tradeagent.png', import.meta.url).href
272
  },
@@ -274,8 +263,8 @@ export default {
274
  name: 'DeepFundAgent',
275
  github: 'https://github.com/your-org/deepfund-agent',
276
  arxiv: 'https://arxiv.org/abs/cccdde',
277
- sharpe: '0.00', winRate: 0.0, trades: 0, days: 86,
278
- pnl: 0.00, strategy: 'Aggressive Long Only', focus: '—',
279
  color: 'linear-gradient(90deg,#0ea5e9,#22d3ee)',
280
  logo: new URL('../assets/images/agents_images/deepfund.png', import.meta.url).href
281
  },
@@ -283,14 +272,51 @@ export default {
283
  name: 'HedgeFundAgent',
284
  github: 'https://github.com/your-org/hedgefund-agent',
285
  arxiv: 'https://arxiv.org/abs/fffggg',
286
- sharpe: '-1.32', winRate: 0.50, trades: 11, days: 86,
287
- pnl: -5375.91, strategy: 'Aggressive Long Only', focus: 'BMRN, TSLA',
288
  color: 'linear-gradient(90deg,#f43f5e,#ef4444)',
289
  logo: new URL('../assets/images/agents_images/hedgefund.png', import.meta.url).href
290
  }
291
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  }
293
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  methods: {
295
  signedMoney(n) {
296
  const v = Number(n || 0)
@@ -303,7 +329,103 @@ export default {
303
  const v = Number(p || 0) * 100
304
  const s = v >= 0 ? '+' : '−'
305
  return `${s}${Math.abs(v).toFixed(2)}%`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  }
 
 
 
 
 
307
  }
308
  }
309
  </script>
@@ -317,67 +439,62 @@ export default {
317
 
318
  .arena-page{
319
  max-width:1280px; margin:0 auto;
320
- padding:16px 20px 120px; /* leave space for fixed footer */
321
- background:#fff;
322
  }
323
 
 
 
 
 
 
324
  /* ===== Sections ===== */
325
  .section{ margin-top:18px; }
326
  .section-head{ margin-bottom:8px; }
327
- .section-title{ font-size:1.6rem; font-weight:800; letter-spacing:-0.02em; display:inline-block; }
328
  .section-sub{ margin-top:4px; color:#6b7280; }
329
 
330
- /* ===== Grid layouts ===== */
331
- .grid{ display:grid; gap:14px; }
332
  .grid-assets-4{ grid-template-columns: repeat(4, minmax(0,1fr)); }
333
  .grid-agents{ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
334
-
335
- @media (max-width: 1200px){
336
- .grid-assets-4{ grid-template-columns: repeat(3, minmax(0,1fr)); }
337
- }
338
- @media (max-width: 900px){
339
- .grid-assets-4{ grid-template-columns: repeat(2, minmax(0,1fr)); }
340
- }
341
- @media (max-width: 620px){
342
- .grid-assets-4{ grid-template-columns: 1fr; }
343
- }
344
 
345
  /* ===== Card base ===== */
346
  .card{
347
  background:#ffffff; border:1px solid #E7ECF3; border-radius:14px;
348
  box-shadow:0 1px 2px rgba(16,24,40,.03), 0 4px 12px rgba(16,24,40,.04);
349
  transition: transform .18s ease, box-shadow .2s ease, border-color .2s ease;
 
350
  }
351
  .card:hover{ transform:translateY(-2px); box-shadow:0 10px 26px rgba(16,24,40,.08); border-color:#D9E2EF; }
352
 
353
  /* ===== Assets ===== */
354
- .asset-card{ padding:12px 12px 12px; display:flex; flex-direction:column; gap:8px; }
355
- .asset-head{
356
- display:grid; grid-template-columns:44px 1fr auto; gap:10px; align-items:center;
357
- }
358
- .asset-logo-wrap{ width:44px; height:44px; border-radius:12px; background:#f3f4f6; display:grid; place-items:center; border:1px solid #E7ECF3; }
359
  .asset-logo{ width:70%; height:70%; object-fit:contain; }
360
  .asset-name{ min-width:0; }
361
  .asset-code{ font-weight:900; color:#0f172a; letter-spacing:.02em; }
362
  .asset-full{ font-size:12px; color:#64748b; }
363
  .type-badge{
364
  border-radius:999px; padding:4px 8px; font-size:12px; font-weight:700;
365
- border:1px solid #E7ECF3; background:#F6F8FB; color:#0f172a;
366
  }
367
  .type-crypto{ background:#f8fbff; border-color:#e0e7ff; color:#1e3a8a; }
368
  .type-stock { background:#f6fffb; border-color:#d9fbe6; color:#064e3b; }
369
- .type-etf { background:#fff7f7; border-color:#ffe4e6; color:#7f1d1d; }
370
 
371
  .asset-body{ display:flex; flex-direction:column; gap:10px; }
372
- .spark-wrap{ width:100%; height:90px; }
373
- .spark-svg{ width:100%; height:100%; }
374
  .spark-up{ filter: drop-shadow(0 0 0 rgba(0,0,0,0)); }
375
  .spark-down{ opacity:.95; }
376
 
377
- .asset-stats{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px; }
378
- .stat{ background:#F6F8FB; border:1px solid #E7ECF3; border-radius:10px; padding:8px; }
379
  .stat-label{ font-size:11px; color:#6b7280; margin-bottom:2px; }
380
- .stat-value{ font-weight:800; color:#0f172a; }
381
  .mono{ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; }
382
  .muted{ color:#6b7280; }
383
 
@@ -388,47 +505,42 @@ export default {
388
  .asset-bar::after{ content:''; position:absolute; left:0; top:0; height:100%; width:var(--pct,0%); background:linear-gradient(90deg,#16a34a,#22c55e); }
389
  .asset-foot-row{ margin-top:6px; display:flex; align-items:center; justify-content:space-between; color:#6b7280; }
390
 
391
- /* ===== Agents ===== */
392
- .agent-card{ padding:12px 12px 12px; display:flex; flex-direction:column; gap:8px; }
393
- .agent-top{
394
- display:grid; grid-template-columns:52px 1fr auto; gap:10px; align-items:center;
395
- }
396
  .agent-badge{ width:52px; height:52px; border-radius:16px; border:2px solid #e5e7eb; position:relative; display:grid; place-items:center; overflow:hidden; }
397
  .agent-ring{ position:absolute; inset:0; opacity:.18; }
398
  .agent-logo{ width:70%; height:70%; object-fit:contain; position:relative; z-index:1; }
399
-
400
  .agent-title{ min-width:0; }
401
  .agent-name{ font-weight:900; letter-spacing:.02em; color:#0f172a; text-transform:uppercase; }
402
- .agent-links{ display:flex; align-items:center; gap:8px; color:#64748b; flex-wrap:wrap; }
403
  .link{ color:#334155; font-weight:600; text-decoration:none; }
404
  .link:hover{ color:#0f172a; text-decoration:underline; }
405
  .dot{ color:#94a3b8; }
406
-
407
- .agent-stats{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px; }
408
- .pos{ color:#0e7a3a; } .neg{ color:#B91C1C; }
409
-
410
  .agent-foot{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:2px; }
411
- .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; }
412
  .chip-outline{ background:#fff; }
413
- .chip-pos{ background:#E9F7EF; border-color:#d7f0e0; color:#0e7a3a; }
414
- .chip-neg{ background:#FBEAEA; border-color:#F3DADA; color:#B91C1C; }
415
 
416
- /* ===== Guide ===== */
417
- .guide-card{ margin-top:12px; padding:14px; }
418
- .guide-title{ display:flex; align-items:center; gap:10px; font-weight:800; font-size:1.1rem; color:#0f172a; margin-bottom:6px; }
419
- .guide-title i{ color:#4338ca; }
420
- .guide-steps{ margin:6px 0 0 18px; color:#334155; }
421
- .guide-steps li{ margin:6px 0; }
422
- .code{ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; }
 
 
 
 
 
 
 
 
 
423
 
424
  /* ===== Buttons ===== */
425
  .cta-row{ margin-top:12px; display:flex; justify-content:center; }
426
  .link-btn{ display:inline-flex; align-items:center; justify-content:center; gap:.5rem; text-decoration:none; }
427
- .btn-cta{
428
- background:linear-gradient(90deg, rgb(0,0,185), #7b2cbf, #d946ef, rgb(240,0,15));
429
- border:none; color:#fff; font-weight:800; letter-spacing:.02em;
430
- padding:10px 16px; border-radius:12px;
431
- }
432
- .btn-cta:hover{ filter:brightness(1.02); transform:translateY(-1px); }
433
  .mr-2{ margin-right:.5rem; }
 
434
  </style>
 
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 dataService B&H sparkline 1M changecoverage</p>
10
  </header>
11
 
12
  <div class="grid grid-assets-4">
 
16
  <img :src="a.icon" :alt="a.code" class="asset-logo" />
17
  </div>
18
  <div class="asset-name">
19
+ <div class="asset-code truncate">{{ a.code }}</div>
20
+ <div class="asset-full truncate" :title="a.name">{{ a.name }}</div>
21
  </div>
22
  <span class="type-badge" :class="'type-' + a.type.toLowerCase()">{{ a.type }}</span>
23
  </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">
 
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>
 
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">
 
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
 
101
  <div class="agent-foot">
 
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
+
112
+ <div class="guide-section">
113
+ <h4>Quick Summary</h4>
114
+ <p><strong>What you need:</strong> Create an API that receives market data and returns trading decisions.</p>
115
+ </div>
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..."},
123
+ "model": "gpt-4o",
124
+ "history_price": {
125
+ "BTC": [
126
+ {"date": "2025-10-14", "price": 65000.00},
127
+ {"date": "2025-10-15", "price": 65500.00},
128
+ {"date": "2025-10-16", "price": 66000.00},
129
+ {"date": "2025-10-17", "price": 66200.00},
130
+ {"date": "2025-10-18", "price": 66500.00},
131
+ {"date": "2025-10-21", "price": 66800.00},
132
+ {"date": "2025-10-22", "price": 67000.00},
133
+ {"date": "2025-10-23", "price": 67500.00}
134
+ ]
135
+ }
136
+ }</pre>
137
+ <p class="note"><strong>Note:</strong> <code>history_price</code> contains the last 10 days of price data (if available).</p>
138
+ </div>
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',
 
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%"/>
 
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
  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
  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
  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
  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>
 
439
 
440
  .arena-page{
441
  max-width:1280px; margin:0 auto;
442
+ padding:16px 20px 120px; background:#fff;
 
443
  }
444
 
445
+ /* ===== Helpers ===== */
446
+ .truncate{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
447
+ .clamp-2{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
448
+ .w-full{ width:100%; }
449
+
450
  /* ===== Sections ===== */
451
  .section{ margin-top:18px; }
452
  .section-head{ margin-bottom:8px; }
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; }
479
  .asset-code{ font-weight:900; color:#0f172a; letter-spacing:.02em; }
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; }
493
 
494
+ .asset-stats{ display:grid; grid-template-columns:repeat(4, minmax(0, 1fr)); gap:10px; }
495
+ .stat{ background:#F6F8FB; border:1px solid #E7ECF3; border-radius:10px; padding:8px; min-width:0; }
496
  .stat-label{ font-size:11px; color:#6b7280; margin-bottom:2px; }
497
+ .stat-value{ font-weight:800; color:#0f172a; overflow:hidden; text-overflow:ellipsis; }
498
  .mono{ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; }
499
  .muted{ color:#6b7280; }
500
 
 
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; }
538
+ .form-message{ margin-top:10px; padding:8px 10px; border-radius:10px; font-size:14px; }
539
+ .form-message.is-success{ background:#E9F7EF; color:#0e7a3a; border:1px solid #d7f0e0; }
540
 
541
  /* ===== Buttons ===== */
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>