YanAdjeNole commited on
Commit
983f32e
·
verified ·
1 Parent(s): f8e29e1

Update src/App_New.tsx

Browse files
Files changed (1) hide show
  1. src/App_New.tsx +64 -10
src/App_New.tsx CHANGED
@@ -74,9 +74,19 @@ export default function App() {
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) {
@@ -111,20 +121,25 @@ export default function App() {
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
 
@@ -132,7 +147,7 @@ export default function App() {
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;
@@ -144,7 +159,7 @@ export default function App() {
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;
@@ -165,6 +180,8 @@ export default function App() {
165
  setWindowLen(START_DAY);
166
  setSelections([]);
167
  setSelectedAsset(null);
 
 
168
  setMessage(`Loaded example: ${keys.length} assets, ${first.length} days.`);
169
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
170
  }
@@ -176,6 +193,7 @@ export default function App() {
176
  setWindowLen(START_DAY);
177
  setMessage("Session reset.");
178
  setIntradayMode(false);
 
179
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
180
  }
181
 
@@ -233,6 +251,7 @@ export default function App() {
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) {
@@ -370,12 +389,47 @@ export default function App() {
370
  return <FinalSummary assets={assets} selections={selections} />;
371
  }
372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  return (
374
  <div className="p-6 bg-gray-50 min-h-screen flex flex-col gap-6">
375
  <div className="flex flex-wrap justify-between items-center gap-3">
376
  <div className="flex items-center gap-2">
377
  <h1 className="text-xl font-semibold">Asset Choice Simulation</h1>
378
  <span className="text-xs text-gray-500">Day {windowLen} / {MAX_DAYS}</span>
 
379
  </div>
380
  <div className="flex items-center gap-2">
381
  <input type="file" accept="application/json" onChange={onFile} className="text-sm" />
@@ -384,9 +438,9 @@ export default function App() {
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>
 
74
  const [confirming, setConfirming] = useState(false);
75
  const [finalSaved, setFinalSaved] = useState(false);
76
 
77
+ // 分时视图开关 & 所选日期索引
78
  const [intradayMode, setIntradayMode] = useState(false);
79
+ // intradayDayIdx 为“在 dates 数组里的下标”;默认跟随窗口最后一天
80
+ const [intradayDayIdx, setIntradayDayIdx] = useState<number | null>(null);
81
+
82
+ // 当窗口推进或数据变更时,若未选择指定日期,则默认使用窗口最后一天
83
+ useEffect(() => {
84
+ if (!dates.length) return;
85
+ const lastIdx = Math.min(windowLen - 1, dates.length - 1);
86
+ if (intradayDayIdx === null || intradayDayIdx > lastIdx) {
87
+ setIntradayDayIdx(lastIdx);
88
+ }
89
+ }, [dates, windowLen, intradayDayIdx]);
90
 
91
  useEffect(() => {
92
  if (Object.keys(rawData).length === 0) {
 
121
  });
122
  }, [assets, dates, windowLen, rawData]);
123
 
124
+ // 可见日期列表 & 当前分时查看日期
125
+ const visibleDates = useMemo(() => dates.slice(0, Math.max(0, windowLen)), [dates, windowLen]);
126
+ const activeIntradayIdx = intradayDayIdx ?? Math.min(windowLen - 1, dates.length - 1);
127
+ const activeIntradayDate = visibleDates[activeIntradayIdx] ?? "";
128
+
129
+ // 分时数据:展示“所选日期”的分钟级走势(当日内归一)
130
  const intradayData = useMemo(() => {
131
+ if (!dates.length || activeIntradayIdx == null || activeIntradayIdx < 0) return [] as any[];
132
 
133
  // 当日各资产的分钟序列长度最大值
134
  const maxBars = assets.reduce((m, a) => {
135
+ const arr = rawData[a]?.[activeIntradayIdx]?.close ?? [];
136
  return Math.max(m, arr.length);
137
  }, 0);
138
 
139
  // 当日内归一:每个资产以当日首笔为1
140
  const dayFirstPrice: Record<string, number> = {};
141
  assets.forEach(a => {
142
+ const arr = rawData[a]?.[activeIntradayIdx]?.close ?? [];
143
  dayFirstPrice[a] = arr.length ? arr[0] : 1;
144
  });
145
 
 
147
  for (let i = 0; i < maxBars; i++) {
148
  const row: Record<string, any> = { idx: i + 1 };
149
  assets.forEach(a => {
150
+ const arr = rawData[a]?.[activeIntradayIdx]?.close ?? [];
151
  const val = arr[i];
152
  if (typeof val === "number") {
153
  const base = dayFirstPrice[a] || 1;
 
159
  rows.push(row);
160
  }
161
  return rows;
162
+ }, [assets, rawData, dates.length, activeIntradayIdx]);
163
 
164
  function realizedNextDayReturn(asset: string) {
165
  const t = windowLen - 1;
 
180
  setWindowLen(START_DAY);
181
  setSelections([]);
182
  setSelectedAsset(null);
183
+ setIntradayMode(false);
184
+ setIntradayDayIdx(null);
185
  setMessage(`Loaded example: ${keys.length} assets, ${first.length} days.`);
186
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
187
  }
 
193
  setWindowLen(START_DAY);
194
  setMessage("Session reset.");
195
  setIntradayMode(false);
196
+ setIntradayDayIdx(null);
197
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
198
  }
199
 
 
251
  setSelections([]);
252
  setSelectedAsset(null);
253
  setIntradayMode(false);
254
+ setIntradayDayIdx(null);
255
  setMessage(`Loaded file: ${keys.length} assets, ${firstArr.length} days.`);
256
  try { localStorage.removeItem("asset_experiment_selections"); } catch {}
257
  } catch (err: any) {
 
389
  return <FinalSummary assets={assets} selections={selections} />;
390
  }
391
 
392
+ // 分时控制条(只在 intradayMode 下显示)
393
+ const IntradayControls = () => (
394
+ <div className="flex items-center gap-2">
395
+ <span className="text-xs px-2 py-1 rounded bg-indigo-50 text-indigo-700">
396
+ Intraday • {activeIntradayDate || "-"}
397
+ </span>
398
+ <button
399
+ onClick={() => setIntradayDayIdx(i => (i == null ? 0 : Math.max(0, i - 1)))}
400
+ className="text-xs px-2 py-1 rounded-xl border border-gray-200 hover:bg-gray-50"
401
+ disabled={activeIntradayIdx <= 0}
402
+ title="Prev day"
403
+ >
404
+
405
+ </button>
406
+ <select
407
+ className="text-xs px-2 py-1 rounded-xl border border-gray-200"
408
+ value={activeIntradayIdx}
409
+ onChange={(e) => setIntradayDayIdx(Number(e.target.value))}
410
+ >
411
+ {visibleDates.map((d, idx) => (
412
+ <option key={d} value={idx}>{d}</option>
413
+ ))}
414
+ </select>
415
+ <button
416
+ onClick={() => setIntradayDayIdx(i => (i == null ? 0 : Math.min(visibleDates.length - 1, i + 1)))}
417
+ className="text-xs px-2 py-1 rounded-xl border border-gray-200 hover:bg-gray-50"
418
+ disabled={activeIntradayIdx >= visibleDates.length - 1}
419
+ title="Next day"
420
+ >
421
+
422
+ </button>
423
+ </div>
424
+ );
425
+
426
  return (
427
  <div className="p-6 bg-gray-50 min-h-screen flex flex-col gap-6">
428
  <div className="flex flex-wrap justify-between items-center gap-3">
429
  <div className="flex items-center gap-2">
430
  <h1 className="text-xl font-semibold">Asset Choice Simulation</h1>
431
  <span className="text-xs text-gray-500">Day {windowLen} / {MAX_DAYS}</span>
432
+ {intradayMode && <IntradayControls />}
433
  </div>
434
  <div className="flex items-center gap-2">
435
  <input type="file" accept="application/json" onChange={onFile} className="text-sm" />
 
438
  <button
439
  onClick={() => setIntradayMode(m => !m)}
440
  className="text-sm px-3 py-1.5 rounded-xl bg-indigo-100 text-indigo-800 hover:bg-indigo-200"
441
+ title="Toggle intraday view"
442
  >
443
+ {intradayMode ? "View Daily" : "View Intraday"}
444
  </button>
445
  <button onClick={exportLog} className="text-sm px-3 py-1.5 rounded-xl bg-gray-900 text-white hover:bg-black">Export Log</button>
446
  </div>