Yan Wang commited on
Commit
be72531
·
1 Parent(s): aa090f2

update App_New

Browse files
Files changed (1) hide show
  1. src/App_New.tsx +52 -59
src/App_New.tsx CHANGED
@@ -69,7 +69,6 @@ function generateFakeJSON(maxLen = 500): DataDict {
69
  intraday.push(Number(p.toFixed(2)));
70
  }
71
  series.push({ date: dates[i], close: intraday });
72
- // update end-of-day anchor for next day open drift
73
  const drift = mu + (sigma / Math.sqrt(252)) * ((Math.random() - 0.5) * 2.0);
74
  price = intraday[intraday.length - 1] * (1 + drift);
75
  }
@@ -315,83 +314,62 @@ export default function App() {
315
  return rows;
316
  }, [assets, rawData, dates.length, activeIntradayIdx]);
317
 
318
- /* ---------- Multi-day intraday with hard carry-forward ---------- */
319
  const { multiRows, dayStarts } = useMemo(() => {
320
  const rows: any[] = [];
321
  const starts: { x: number; date: string }[] = [];
322
  if (!dates.length || windowLen < 1) return { multiRows: rows, dayStarts: starts };
323
 
324
- // last seen price across minutes & days
325
- const lastSeen: Record<string, number | null> = {};
326
- // cumulative index per asset (for normalized mode)
327
  const cumIdx: Record<string, number> = {};
328
- assets.forEach(a => { lastSeen[a] = null; cumIdx[a] = 1; });
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
  let x = 0;
331
- const maxD = Math.min(windowLen, dates.length);
 
 
332
 
333
- for (let d = 0; d < maxD; d++) {
334
- const dayLen = assets.reduce((m, a) => Math.max(m, rawData[a]?.[d]?.close?.length ?? 0), 0);
335
- if (dayLen === 0) continue;
336
-
337
- // first valid price of the day per asset (fallback for day start)
338
- const firstOfDay: Record<string, number | null> = {};
339
- assets.forEach(a => {
340
- const arr = rawData[a]?.[d]?.close ?? [];
341
- let v: number | null = null;
342
- for (let j = 0; j < arr.length; j++) {
343
- if (typeof arr[j] === "number" && Number.isFinite(arr[j])) { v = arr[j]!; break; }
344
- }
345
- firstOfDay[a] = v;
346
- });
347
-
348
- const dayStartX = x + 1;
349
 
350
  for (let i = 0; i < dayLen; i++) {
351
  const row: Record<string, any> = { x: x + 1, day: dates[d], minute: i + 1 };
352
- let anyValue = false;
353
 
354
  assets.forEach(a => {
355
- const arr = rawData[a]?.[d]?.close ?? [];
356
- let v: number | null = null;
357
-
358
- // minute value
359
- if (typeof arr[i] === "number" && Number.isFinite(arr[i])) {
360
- v = arr[i]!;
361
- } else {
362
- // carry-forward from last seen
363
- if (lastSeen[a] != null) v = lastSeen[a] as number;
364
- else if (firstOfDay[a] != null) v = firstOfDay[a] as number; // fallback to first valid of the day
365
- }
366
-
367
- if (v != null) {
368
- if (multiNormalize) {
369
- if (lastSeen[a] != null && v !== lastSeen[a]) {
370
- const r = v / (lastSeen[a] as number);
371
- if (Number.isFinite(r) && r > 0) cumIdx[a] *= r;
372
- }
373
- row[a] = cumIdx[a];
374
- } else {
375
- row[a] = v;
376
  }
377
- lastSeen[a] = v;
378
- anyValue = true;
379
  } else {
380
  row[a] = null;
381
  }
382
  });
383
 
384
- if (anyValue) {
385
- rows.push(row);
386
- x++;
387
- }
388
- }
389
-
390
- if (x >= dayStartX) {
391
- starts.push({ x: dayStartX, date: dates[d] });
392
  }
393
  }
394
-
395
  return { multiRows: rows, dayStarts: starts };
396
  }, [assets, rawData, dates, windowLen, multiNormalize]);
397
 
@@ -557,6 +535,20 @@ export default function App() {
557
  </div>
558
  );
559
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  return (
561
  <div className="p-6 bg-gray-50 min-h-screen flex flex-col gap-6">
562
  <div className="flex flex-wrap justify-between items-center gap-3">
@@ -647,8 +639,8 @@ export default function App() {
647
  tick={{ fontSize: 10 }}
648
  label={{ value: "Minute (concatenated days)", position: "insideBottomRight", offset: -2, fontSize: 10 }}
649
  />
650
- {/* IMPORTANT: keep auto to avoid empty scale at early minutes */}
651
- <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
652
  <Tooltip
653
  contentStyle={{ fontSize: 12 }}
654
  labelFormatter={(_, payload: any) => {
@@ -680,7 +672,8 @@ export default function App() {
680
  onMouseEnter={() => setHoverAsset(a)}
681
  onMouseLeave={() => setHoverAsset(null)}
682
  onClick={() => setSelectedAsset((p) => (p === a ? null : a))}
683
- connectNulls={true} // KEY: draw across sparse nulls
 
684
  />
685
  ))}
686
  </LineChart>
 
69
  intraday.push(Number(p.toFixed(2)));
70
  }
71
  series.push({ date: dates[i], close: intraday });
 
72
  const drift = mu + (sigma / Math.sqrt(252)) * ((Math.random() - 0.5) * 2.0);
73
  price = intraday[intraday.length - 1] * (1 + drift);
74
  }
 
314
  return rows;
315
  }, [assets, rawData, dates.length, activeIntradayIdx]);
316
 
317
+ // Multi-day intraday with robust carry-forward
318
  const { multiRows, dayStarts } = useMemo(() => {
319
  const rows: any[] = [];
320
  const starts: { x: number; date: string }[] = [];
321
  if (!dates.length || windowLen < 1) return { multiRows: rows, dayStarts: starts };
322
 
323
+ // previous minute price & cumulative index across days
324
+ const prevPrice: Record<string, number | null> = {};
 
325
  const cumIdx: Record<string, number> = {};
326
+ assets.forEach(a => { prevPrice[a] = null; cumIdx[a] = 1; });
327
+
328
+ // helper: forward-fill within the day if possible; otherwise use prevPrice
329
+ function resolvePriceForMinute(arr: number[] | undefined, i: number, a: string): number | null {
330
+ if (arr && typeof arr[i] === "number") return arr[i]!;
331
+ if (prevPrice[a] != null) return prevPrice[a] as number;
332
+ // look ahead within the same day to find the first future tick to backfill start-of-day
333
+ if (arr && arr.length) {
334
+ for (let j = i + 1; j < arr.length; j++) {
335
+ if (typeof arr[j] === "number") return arr[j]!;
336
+ }
337
+ }
338
+ return null;
339
+ }
340
 
341
  let x = 0;
342
+ for (let d = 0; d < Math.min(windowLen, dates.length); d++) {
343
+ const startX = x + 1;
344
+ starts.push({ x: startX, date: dates[d] });
345
 
346
+ const dayLenRaw = assets.reduce((m, a) => Math.max(m, rawData[a]?.[d]?.close?.length ?? 0), 0);
347
+ const dayLen = Math.max(1, dayLenRaw); // ensure at least one row per day
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
  for (let i = 0; i < dayLen; i++) {
350
  const row: Record<string, any> = { x: x + 1, day: dates[d], minute: i + 1 };
 
351
 
352
  assets.forEach(a => {
353
+ const arr = rawData[a]?.[d]?.close;
354
+ const resolved = resolvePriceForMinute(arr, i, a);
355
+
356
+ if (resolved != null) {
357
+ // update cumIdx only when a "new real tick" occurs
358
+ if (prevPrice[a] != null && resolved !== prevPrice[a]) {
359
+ const r = resolved / (prevPrice[a] as number);
360
+ if (Number.isFinite(r) && r > 0) cumIdx[a] *= r;
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  }
362
+ row[a] = multiNormalize ? cumIdx[a] : resolved;
363
+ prevPrice[a] = resolved;
364
  } else {
365
  row[a] = null;
366
  }
367
  });
368
 
369
+ rows.push(row);
370
+ x++;
 
 
 
 
 
 
371
  }
372
  }
 
373
  return { multiRows: rows, dayStarts: starts };
374
  }, [assets, rawData, dates, windowLen, multiNormalize]);
375
 
 
535
  </div>
536
  );
537
 
538
+ // ------- helper to give Y-axis safe padding even when dataMin == dataMax -------
539
+ const multiYAxisDomain: any = [
540
+ (min: number) => {
541
+ if (!Number.isFinite(min)) return "auto";
542
+ const pad = Math.abs(min) * 0.005 || 0.01;
543
+ return min - pad;
544
+ },
545
+ (max: number) => {
546
+ if (!Number.isFinite(max)) return "auto";
547
+ const pad = Math.abs(max) * 0.005 || 0.01;
548
+ return max + pad;
549
+ }
550
+ ];
551
+
552
  return (
553
  <div className="p-6 bg-gray-50 min-h-screen flex flex-col gap-6">
554
  <div className="flex flex-wrap justify-between items-center gap-3">
 
639
  tick={{ fontSize: 10 }}
640
  label={{ value: "Minute (concatenated days)", position: "insideBottomRight", offset: -2, fontSize: 10 }}
641
  />
642
+ {/* FIX 1: robust Y domain with padding so flat lines are visible */}
643
+ <YAxis domain={multiYAxisDomain} tick={{ fontSize: 10 }} allowDataOverflow />
644
  <Tooltip
645
  contentStyle={{ fontSize: 12 }}
646
  labelFormatter={(_, payload: any) => {
 
672
  onMouseEnter={() => setHoverAsset(a)}
673
  onMouseLeave={() => setHoverAsset(null)}
674
  onClick={() => setSelectedAsset((p) => (p === a ? null : a))}
675
+ /* FIX 2: connect null gaps so lines render across sparse minutes */
676
+ connectNulls={true}
677
  />
678
  ))}
679
  </LineChart>