Yan Wang commited on
Commit
36b100b
·
1 Parent(s): 46472e0

add a normalized function

Browse files
Files changed (1) hide show
  1. src/App_New.tsx +43 -13
src/App_New.tsx CHANGED
@@ -12,7 +12,7 @@ import {
12
  } from "./api";
13
 
14
  type SeriesPoint = { date: string; close: number[] };
15
- type ViewMode = "daily" | "intraday"; // 移除 multi
16
 
17
  const START_DAY = 7;
18
 
@@ -59,7 +59,6 @@ function generateFakeJSON(maxLen = 500): DataDict {
59
  const sigma = 0.15 + Math.random() * 0.35;
60
  const series: SeriesPoint[] = [];
61
  for (let i = 0; i < dates.length; i++) {
62
- // Fake minute series (varying length)
63
  const mins = 200 + Math.floor(Math.random() * 100);
64
  const intraday: number[] = [];
65
  let p = price;
@@ -104,6 +103,9 @@ export default function App() {
104
  const [viewMode, setViewMode] = useState<ViewMode>("daily");
105
  const [intradayDayIdx, setIntradayDayIdx] = useState<number | null>(null);
106
 
 
 
 
107
  /* ---------- localStorage keys ---------- */
108
  const LS_DATASET_KEY = "annot_dataset_meta";
109
  const LS_DATA_PREFIX = (id: string) => `annot_dataset_${id}`;
@@ -274,7 +276,7 @@ export default function App() {
274
  /* ---------- computed ---------- */
275
  const isFinal = windowLen >= maxDays || selections.length >= maxSteps;
276
 
277
- // Daily (normalized to day 0)
278
  const windowData = useMemo(() => {
279
  if (!dates.length || windowLen < 2) return [] as any[];
280
  const sliceDates = dates.slice(0, windowLen);
@@ -282,14 +284,18 @@ export default function App() {
282
  const row: Record<string, any> = { date };
283
  assets.forEach((a) => {
284
  const base = rawData[a]?.[0] ? latestClose(rawData[a][0]) : 1;
285
- const val = rawData[a]?.[idx] ? latestClose(rawData[a][idx]) : base;
286
- row[a] = base ? val / base : 1;
 
 
 
 
287
  });
288
  return row;
289
  });
290
- }, [assets, dates, windowLen, rawData]);
291
 
292
- // Intraday (single day, normalized to first tick of the day)
293
  const visibleDates = useMemo(() => dates.slice(0, Math.max(0, windowLen)), [dates, windowLen]);
294
  const activeIntradayIdx = intradayDayIdx ?? Math.min(windowLen - 1, dates.length - 1);
295
  const intradayData = useMemo(() => {
@@ -306,12 +312,16 @@ export default function App() {
306
  assets.forEach(a => {
307
  const arr = rawData[a]?.[activeIntradayIdx]?.close ?? [];
308
  const val = arr[i];
309
- row[a] = typeof val === "number" ? (dayFirstPrice[a] ? val / dayFirstPrice[a] : null) : null;
 
 
 
 
310
  });
311
  rows.push(row);
312
  }
313
  return rows;
314
- }, [assets, rawData, dates.length, activeIntradayIdx]);
315
 
316
  function realizedNextDayReturn(asset: string) {
317
  const t = windowLen - 1;
@@ -440,7 +450,7 @@ export default function App() {
440
  useEffect(() => {
441
  function onKey(e: KeyboardEvent) {
442
  const tag = (e.target && (e.target as HTMLElement).tagName) || "";
443
- if (tag === "INPUT" || tag === "TEXTAREA") return;
444
  const idx = parseInt((e as any).key, 10) - 1;
445
  if (!Number.isNaN(idx) && idx >= 0 && idx < assets.length) setSelectedAsset(assets[idx]);
446
  if ((e as any).key === "Enter" && Boolean(selectedAsset) && windowLen < maxDays) confirmSelection();
@@ -475,12 +485,20 @@ export default function App() {
475
  </div>
476
  );
477
 
 
 
 
 
 
 
 
 
478
  return (
479
  <div className="p-6 bg-gray-50 min-h-screen flex flex-col gap-6">
480
  <div className="flex flex-wrap justify-between items-center gap-3">
481
  <div className="flex items-center gap-2">
482
  <h1 className="text-xl font-semibold">Asset Choice Simulation</h1>
483
- <span className="text-[10px] ml-2 px-1.5 py-0.5 rounded bg-amber-200 text-amber-900">BUILD_TAG: 2025-10-28</span>
484
  <span className="text-xs text-gray-500">Dataset: {datasetName || datasetId}</span>
485
  <span className="text-xs text-gray-500">User: {userId.slice(0,8)}…</span>
486
  <span className="text-xs text-gray-500">Day {windowLen} / {maxDays}</span>
@@ -495,6 +513,15 @@ export default function App() {
495
  <button className={`text-xs px-3 py-1.5 ${viewMode==="intraday" ? "bg-gray-900 text-white" : "bg-white text-gray-700 hover:bg-gray-50"}`} onClick={()=>setViewMode("intraday")}>Intraday (1 day)</button>
496
  </div>
497
 
 
 
 
 
 
 
 
 
 
498
  <button onClick={exportLog} className="text-sm px-3 py-1.5 rounded-xl bg-gray-900 text-white hover:bg-black">Export My Log</button>
499
  </div>
500
  </div>
@@ -513,6 +540,9 @@ export default function App() {
513
  </button>
514
  ))}
515
  {assets.length>0 && <span className="text-xs text-gray-500 ml-1">Hotkeys: 1..{assets.length}, Enter confirm</span>}
 
 
 
516
  </div>
517
 
518
  <div className="h-80 relative">
@@ -522,7 +552,7 @@ export default function App() {
522
  <CartesianGrid strokeDasharray="3 3" />
523
  <XAxis dataKey="idx" tick={{ fontSize: 10 }} />
524
  <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
525
- <Tooltip contentStyle={{ fontSize: 12 }} formatter={(v: any, n: any) => [v, aliasMap[n] || n]} />
526
  <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} formatter={(v: any) => aliasMap[v] || v} />
527
  {assets.map((a, i) => (
528
  <Line
@@ -547,7 +577,7 @@ export default function App() {
547
  <CartesianGrid strokeDasharray="3 3" />
548
  <XAxis dataKey="date" tick={{ fontSize: 10 }} />
549
  <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
550
- <Tooltip contentStyle={{ fontSize: 12 }} formatter={(v: any, n: any) => [v, aliasMap[n] || n]} />
551
  <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} formatter={(v: any) => aliasMap[v] || v} />
552
  {assets.map((a, i) => (
553
  <Line
 
12
  } from "./api";
13
 
14
  type SeriesPoint = { date: string; close: number[] };
15
+ type ViewMode = "daily" | "intraday";
16
 
17
  const START_DAY = 7;
18
 
 
59
  const sigma = 0.15 + Math.random() * 0.35;
60
  const series: SeriesPoint[] = [];
61
  for (let i = 0; i < dates.length; i++) {
 
62
  const mins = 200 + Math.floor(Math.random() * 100);
63
  const intraday: number[] = [];
64
  let p = price;
 
103
  const [viewMode, setViewMode] = useState<ViewMode>("daily");
104
  const [intradayDayIdx, setIntradayDayIdx] = useState<number | null>(null);
105
 
106
+ // NEW: global normalize toggle
107
+ const [normalize, setNormalize] = useState<boolean>(true);
108
+
109
  /* ---------- localStorage keys ---------- */
110
  const LS_DATASET_KEY = "annot_dataset_meta";
111
  const LS_DATA_PREFIX = (id: string) => `annot_dataset_${id}`;
 
276
  /* ---------- computed ---------- */
277
  const isFinal = windowLen >= maxDays || selections.length >= maxSteps;
278
 
279
+ // Daily
280
  const windowData = useMemo(() => {
281
  if (!dates.length || windowLen < 2) return [] as any[];
282
  const sliceDates = dates.slice(0, windowLen);
 
284
  const row: Record<string, any> = { date };
285
  assets.forEach((a) => {
286
  const base = rawData[a]?.[0] ? latestClose(rawData[a][0]) : 1;
287
+ const val = rawData[a]?.[idx] ? latestClose(rawData[a][idx]) : (normalize ? base : null);
288
+ if (normalize) {
289
+ row[a] = base ? (val as number) / base : 1; // normalize to day 0
290
+ } else {
291
+ row[a] = typeof val === "number" ? val : null; // raw price
292
+ }
293
  });
294
  return row;
295
  });
296
+ }, [assets, dates, windowLen, rawData, normalize]);
297
 
298
+ // Intraday (single day)
299
  const visibleDates = useMemo(() => dates.slice(0, Math.max(0, windowLen)), [dates, windowLen]);
300
  const activeIntradayIdx = intradayDayIdx ?? Math.min(windowLen - 1, dates.length - 1);
301
  const intradayData = useMemo(() => {
 
312
  assets.forEach(a => {
313
  const arr = rawData[a]?.[activeIntradayIdx]?.close ?? [];
314
  const val = arr[i];
315
+ if (normalize) {
316
+ row[a] = typeof val === "number" ? (dayFirstPrice[a] ? val / dayFirstPrice[a] : null) : null; // normalized to first tick
317
+ } else {
318
+ row[a] = typeof val === "number" ? val : null; // raw price
319
+ }
320
  });
321
  rows.push(row);
322
  }
323
  return rows;
324
+ }, [assets, rawData, dates.length, activeIntradayIdx, normalize]);
325
 
326
  function realizedNextDayReturn(asset: string) {
327
  const t = windowLen - 1;
 
450
  useEffect(() => {
451
  function onKey(e: KeyboardEvent) {
452
  const tag = (e.target && (e.target as HTMLElement).tagName) || "";
453
+ if (tag === "INPUT" || "TEXTAREA") return;
454
  const idx = parseInt((e as any).key, 10) - 1;
455
  if (!Number.isNaN(idx) && idx >= 0 && idx < assets.length) setSelectedAsset(assets[idx]);
456
  if ((e as any).key === "Enter" && Boolean(selectedAsset) && windowLen < maxDays) confirmSelection();
 
485
  </div>
486
  );
487
 
488
+ // Tooltip 格式化:normalized 用“×”,raw 用价格两位小数
489
+ function tooltipFormatter(value: any, name: any) {
490
+ if (normalize) {
491
+ return [Number.isFinite(value) ? `${Number(value).toFixed(4)}×` : value, aliasMap[name] || name];
492
+ }
493
+ return [Number.isFinite(value) ? Number(value).toFixed(2) : value, aliasMap[name] || name];
494
+ }
495
+
496
  return (
497
  <div className="p-6 bg-gray-50 min-h-screen flex flex-col gap-6">
498
  <div className="flex flex-wrap justify-between items-center gap-3">
499
  <div className="flex items-center gap-2">
500
  <h1 className="text-xl font-semibold">Asset Choice Simulation</h1>
501
+ <span className="text-[10px] ml-2 px-1.5 py-0.5 rounded bg-amber-200 text-amber-900">BUILD_TAG: 2025-10-29</span>
502
  <span className="text-xs text-gray-500">Dataset: {datasetName || datasetId}</span>
503
  <span className="text-xs text-gray-500">User: {userId.slice(0,8)}…</span>
504
  <span className="text-xs text-gray-500">Day {windowLen} / {maxDays}</span>
 
513
  <button className={`text-xs px-3 py-1.5 ${viewMode==="intraday" ? "bg-gray-900 text-white" : "bg-white text-gray-700 hover:bg-gray-50"}`} onClick={()=>setViewMode("intraday")}>Intraday (1 day)</button>
514
  </div>
515
 
516
+ {/* NEW: Normalize/Raw toggle */}
517
+ <button
518
+ className="text-xs px-3 py-1.5 rounded-xl border border-gray-200 hover:bg-gray-50"
519
+ onClick={() => setNormalize(v => !v)}
520
+ title="Toggle normalized ratio vs raw price"
521
+ >
522
+ {normalize ? "Normalized" : "Raw Price"}
523
+ </button>
524
+
525
  <button onClick={exportLog} className="text-sm px-3 py-1.5 rounded-xl bg-gray-900 text-white hover:bg-black">Export My Log</button>
526
  </div>
527
  </div>
 
540
  </button>
541
  ))}
542
  {assets.length>0 && <span className="text-xs text-gray-500 ml-1">Hotkeys: 1..{assets.length}, Enter confirm</span>}
543
+ <span className="text-[10px] px-2 py-0.5 rounded bg-black/60 text-white">
544
+ {normalize ? "Viewing: Normalized (×)" : "Viewing: Raw Price"}
545
+ </span>
546
  </div>
547
 
548
  <div className="h-80 relative">
 
552
  <CartesianGrid strokeDasharray="3 3" />
553
  <XAxis dataKey="idx" tick={{ fontSize: 10 }} />
554
  <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
555
+ <Tooltip contentStyle={{ fontSize: 12 }} formatter={tooltipFormatter as any} />
556
  <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} formatter={(v: any) => aliasMap[v] || v} />
557
  {assets.map((a, i) => (
558
  <Line
 
577
  <CartesianGrid strokeDasharray="3 3" />
578
  <XAxis dataKey="date" tick={{ fontSize: 10 }} />
579
  <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
580
+ <Tooltip contentStyle={{ fontSize: 12 }} formatter={tooltipFormatter as any} />
581
  <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} formatter={(v: any) => aliasMap[v] || v} />
582
  {assets.map((a, i) => (
583
  <Line