Yan Wang commited on
Commit
f8e29e1
·
1 Parent(s): 6ccada6

revised App_New

Browse files
Files changed (1) hide show
  1. src/App_New.tsx +103 -23
src/App_New.tsx CHANGED
@@ -74,6 +74,10 @@ export default function App() {
74
  const [confirming, setConfirming] = useState(false);
75
  const [finalSaved, setFinalSaved] = useState(false);
76
 
 
 
 
 
77
  useEffect(() => {
78
  if (Object.keys(rawData).length === 0) {
79
  const example = generateFakeJSON();
@@ -92,6 +96,7 @@ export default function App() {
92
 
93
  const isFinal = windowLen >= MAX_DAYS || selections.length >= maxSteps;
94
 
 
95
  const windowData = useMemo(() => {
96
  if (!dates.length || windowLen < 2) return [] as any[];
97
  const sliceDates = dates.slice(0, windowLen);
@@ -106,6 +111,41 @@ export default function App() {
106
  });
107
  }, [assets, dates, windowLen, rawData]);
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  function realizedNextDayReturn(asset: string) {
110
  const t = windowLen - 1;
111
  if (t + 1 >= dates.length) return null as any;
@@ -135,6 +175,7 @@ export default function App() {
135
  setStep(1);
136
  setWindowLen(START_DAY);
137
  setMessage("Session reset.");
 
138
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
139
  }
140
 
@@ -191,6 +232,7 @@ export default function App() {
191
  setWindowLen(START_DAY);
192
  setSelections([]);
193
  setSelectedAsset(null);
 
194
  setMessage(`Loaded file: ${keys.length} assets, ${firstArr.length} days.`);
195
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
196
  } catch (err: any) {
@@ -339,11 +381,19 @@ export default function App() {
339
  <input type="file" accept="application/json" onChange={onFile} className="text-sm" />
340
  <button onClick={loadExample} className="text-sm px-3 py-1.5 rounded-xl bg-blue-100 text-blue-800 hover:bg-blue-200">Load Example</button>
341
  <button onClick={resetSession} className="text-sm px-3 py-1.5 rounded-xl bg-gray-200 hover:bg-gray-300">Reset</button>
 
 
 
 
 
 
 
342
  <button onClick={exportLog} className="text-sm px-3 py-1.5 rounded-xl bg-gray-900 text-white hover:bg-black">Export Log</button>
343
  </div>
344
  </div>
345
 
346
  <div className="bg-white p-4 rounded-2xl shadow">
 
347
  <div className="flex flex-wrap items-center gap-2 mb-3">
348
  {assets.map((a, i) => (
349
  <button
@@ -360,35 +410,65 @@ export default function App() {
360
  <span className="text-xs text-gray-500 ml-1">Hotkeys: 1 to {assets.length}, Enter to confirm</span>
361
  )}
362
  </div>
 
 
363
  <div className="h-80">
364
  <ResponsiveContainer width="100%" height="100%">
365
- <LineChart data={windowData}>
366
- <CartesianGrid strokeDasharray="3 3" />
367
- <XAxis dataKey="date" tick={{ fontSize: 10 }} />
368
- <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
369
- <Tooltip contentStyle={{ fontSize: 12 }} />
370
- <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} />
371
- {assets.map((a, i) => (
372
- <Line
373
- key={a}
374
- type="monotone"
375
- dataKey={a}
376
- strokeWidth={selectedAsset === a ? 5 : hoverAsset === a ? 4 : 2.5}
377
- strokeOpacity={selectedAsset && selectedAsset !== a ? 0.3 : 1}
378
- dot={false}
379
- isAnimationActive={false}
380
- stroke={`hsl(${(360 / assets.length) * i},70%,50%)`}
381
- onMouseEnter={() => setHoverAsset(a)}
382
- onMouseLeave={() => setHoverAsset(null)}
383
- onClick={() => setSelectedAsset((p) => (p === a ? null : a))}
384
- />
385
- ))}
386
- </LineChart>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  </ResponsiveContainer>
388
  </div>
389
 
390
  <div className="flex justify-between items-center mt-3">
391
- <div className="text-sm text-gray-600">Selected: {selectedAsset ?? "(none)"} {message && <span className="ml-2 text-gray-500">{message}</span>}</div>
 
 
392
  <button
393
  onClick={confirmSelection}
394
  disabled={!selectedAsset || windowLen >= MAX_DAYS}
 
74
  const [confirming, setConfirming] = useState(false);
75
  const [finalSaved, setFinalSaved] = useState(false);
76
 
77
+ // 方案A:分时视图切换
78
+ const [intradayMode, setIntradayMode] = useState(false);
79
+ const currentDayIdx = Math.min(windowLen - 1, dates.length - 1);
80
+
81
  useEffect(() => {
82
  if (Object.keys(rawData).length === 0) {
83
  const example = generateFakeJSON();
 
96
 
97
  const isFinal = windowLen >= MAX_DAYS || selections.length >= maxSteps;
98
 
99
+ // 日线数据(归一到第0天)
100
  const windowData = useMemo(() => {
101
  if (!dates.length || windowLen < 2) return [] as any[];
102
  const sliceDates = dates.slice(0, windowLen);
 
111
  });
112
  }, [assets, dates, windowLen, rawData]);
113
 
114
+ // 分时数据:展示“当前窗口最后一天”的分钟级走势(当日内归一)
115
+ const intradayData = useMemo(() => {
116
+ if (!dates.length || currentDayIdx < 0) return [] as any[];
117
+
118
+ // 当日各资产的分钟序列长度最大值
119
+ const maxBars = assets.reduce((m, a) => {
120
+ const arr = rawData[a]?.[currentDayIdx]?.close ?? [];
121
+ return Math.max(m, arr.length);
122
+ }, 0);
123
+
124
+ // 当日内归一:每个资产以当日首笔为1
125
+ const dayFirstPrice: Record<string, number> = {};
126
+ assets.forEach(a => {
127
+ const arr = rawData[a]?.[currentDayIdx]?.close ?? [];
128
+ dayFirstPrice[a] = arr.length ? arr[0] : 1;
129
+ });
130
+
131
+ const rows: any[] = [];
132
+ for (let i = 0; i < maxBars; i++) {
133
+ const row: Record<string, any> = { idx: i + 1 };
134
+ assets.forEach(a => {
135
+ const arr = rawData[a]?.[currentDayIdx]?.close ?? [];
136
+ const val = arr[i];
137
+ if (typeof val === "number") {
138
+ const base = dayFirstPrice[a] || 1;
139
+ row[a] = base ? val / base : null;
140
+ } else {
141
+ row[a] = null; // 该分钟无数据则断线
142
+ }
143
+ });
144
+ rows.push(row);
145
+ }
146
+ return rows;
147
+ }, [assets, rawData, currentDayIdx, dates.length]);
148
+
149
  function realizedNextDayReturn(asset: string) {
150
  const t = windowLen - 1;
151
  if (t + 1 >= dates.length) return null as any;
 
175
  setStep(1);
176
  setWindowLen(START_DAY);
177
  setMessage("Session reset.");
178
+ setIntradayMode(false);
179
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
180
  }
181
 
 
232
  setWindowLen(START_DAY);
233
  setSelections([]);
234
  setSelectedAsset(null);
235
+ setIntradayMode(false);
236
  setMessage(`Loaded file: ${keys.length} assets, ${firstArr.length} days.`);
237
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
238
  } catch (err: any) {
 
381
  <input type="file" accept="application/json" onChange={onFile} className="text-sm" />
382
  <button onClick={loadExample} className="text-sm px-3 py-1.5 rounded-xl bg-blue-100 text-blue-800 hover:bg-blue-200">Load Example</button>
383
  <button onClick={resetSession} className="text-sm px-3 py-1.5 rounded-xl bg-gray-200 hover:bg-gray-300">Reset</button>
384
+ <button
385
+ onClick={() => setIntradayMode(m => !m)}
386
+ className="text-sm px-3 py-1.5 rounded-xl bg-indigo-100 text-indigo-800 hover:bg-indigo-200"
387
+ title="Toggle intraday view for the last visible day"
388
+ >
389
+ {intradayMode ? "View Daily" : "View Intraday (current day)"}
390
+ </button>
391
  <button onClick={exportLog} className="text-sm px-3 py-1.5 rounded-xl bg-gray-900 text-white hover:bg-black">Export Log</button>
392
  </div>
393
  </div>
394
 
395
  <div className="bg-white p-4 rounded-2xl shadow">
396
+ {/* 快捷选择器 */}
397
  <div className="flex flex-wrap items-center gap-2 mb-3">
398
  {assets.map((a, i) => (
399
  <button
 
410
  <span className="text-xs text-gray-500 ml-1">Hotkeys: 1 to {assets.length}, Enter to confirm</span>
411
  )}
412
  </div>
413
+
414
+ {/* 图表:日线 / 分时切换 */}
415
  <div className="h-80">
416
  <ResponsiveContainer width="100%" height="100%">
417
+ {intradayMode ? (
418
+ <LineChart data={intradayData}>
419
+ <CartesianGrid strokeDasharray="3 3" />
420
+ <XAxis dataKey="idx" tick={{ fontSize: 10 }} />
421
+ <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
422
+ <Tooltip contentStyle={{ fontSize: 12 }} />
423
+ <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} />
424
+ {assets.map((a, i) => (
425
+ <Line
426
+ key={a}
427
+ type="monotone"
428
+ dataKey={a}
429
+ strokeWidth={selectedAsset === a ? 5 : hoverAsset === a ? 4 : 2.5}
430
+ strokeOpacity={selectedAsset && selectedAsset !== a ? 0.3 : 1}
431
+ dot={false}
432
+ isAnimationActive={false}
433
+ stroke={`hsl(${(360 / assets.length) * i},70%,50%)`}
434
+ onMouseEnter={() => setHoverAsset(a)}
435
+ onMouseLeave={() => setHoverAsset(null)}
436
+ onClick={() => setSelectedAsset((p) => (p === a ? null : a))}
437
+ connectNulls={false}
438
+ />
439
+ ))}
440
+ </LineChart>
441
+ ) : (
442
+ <LineChart data={windowData}>
443
+ <CartesianGrid strokeDasharray="3 3" />
444
+ <XAxis dataKey="date" tick={{ fontSize: 10 }} />
445
+ <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
446
+ <Tooltip contentStyle={{ fontSize: 12 }} />
447
+ <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} />
448
+ {assets.map((a, i) => (
449
+ <Line
450
+ key={a}
451
+ type="monotone"
452
+ dataKey={a}
453
+ strokeWidth={selectedAsset === a ? 5 : hoverAsset === a ? 4 : 2.5}
454
+ strokeOpacity={selectedAsset && selectedAsset !== a ? 0.3 : 1}
455
+ dot={false}
456
+ isAnimationActive={false}
457
+ stroke={`hsl(${(360 / assets.length) * i},70%,50%)`}
458
+ onMouseEnter={() => setHoverAsset(a)}
459
+ onMouseLeave={() => setHoverAsset(null)}
460
+ onClick={() => setSelectedAsset((p) => (p === a ? null : a))}
461
+ />
462
+ ))}
463
+ </LineChart>
464
+ )}
465
  </ResponsiveContainer>
466
  </div>
467
 
468
  <div className="flex justify-between items-center mt-3">
469
+ <div className="text-sm text-gray-600">
470
+ Selected: {selectedAsset ?? "(none)"} {message && <span className="ml-2 text-gray-500">{message}</span>}
471
+ </div>
472
  <button
473
  onClick={confirmSelection}
474
  disabled={!selectedAsset || windowLen >= MAX_DAYS}