투자관리 차트탭 기능 수정

This commit is contained in:
Macbook
2026-05-31 16:31:25 +09:00
parent 1b7c39e11f
commit 78f00dbaa5
14 changed files with 935 additions and 111 deletions
+22 -7
View File
@@ -18,6 +18,8 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import type { RefObject, MutableRefObject } from 'react';
import type { ChartManager } from '../utils/ChartManager';
import { toHistoryApiIso } from '../utils/analysisChartData';
import { timeframeToCandleType } from '../utils/chartCandleType';
import { fetchUpbitCandlesBeforeCached } from '../utils/requestCache';
import type { OHLCVBar, Timeframe } from '../types';
@@ -38,6 +40,8 @@ interface UseHistoryLoaderOptions {
timeframe: Timeframe;
isUpbit: boolean;
managerRef: RefObject<ChartManager | null>;
/** 과거 캔들 prepend 완료 후 (추가된 봉 수, 전체 봉 수) */
onBarsPrepended?: (addedCount: number, totalBars: number) => void;
}
interface UseHistoryLoaderReturn {
@@ -91,6 +95,7 @@ export function useHistoryLoader({
timeframe,
isUpbit,
managerRef,
onBarsPrepended,
}: UseHistoryLoaderOptions): UseHistoryLoaderReturn {
const [isLoadingMore, setIsLoadingMore] = useState(false);
@@ -103,6 +108,8 @@ export function useHistoryLoader({
/** false 가 되면 더 이상 API 를 호출하지 않음 */
const hasMoreRef = useRef(true);
const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const onPrependedRef = useRef(onBarsPrepended);
onPrependedRef.current = onBarsPrepended;
// 심볼·타임프레임이 바뀌면 상태 초기화
useEffect(() => {
@@ -125,10 +132,15 @@ export function useHistoryLoader({
const oldestTime = mgr.getOldestBarTime();
if (oldestTime === null) return;
// Unix 타임스탬프(초) → ISO 8601 UTC ('Z' 없이 전달, 백엔드에서 처리)
const beforeIso = new Date(oldestTime * 1000)
.toISOString()
.replace('.000Z', ''); // '2026-05-20T10:00:00'
// exclusive to — 가장 오래된 봉 시각 그대로 쓰면 중복·0건이 될 수 있어 한 봉 이전으로
const raw = mgr.getRawBars();
let beforeSec = oldestTime;
if (raw.length >= 2) {
const step = Math.max(1, (raw[1].time as number) - (raw[0].time as number));
beforeSec = oldestTime - step;
}
const beforeIso = toHistoryApiIso(beforeSec);
const candleType = timeframeToCandleType(timeframe);
// ★ 락 설정: 이 시점부터 동일 타임스탬프 중복 요청 차단
loadingRef.current = true;
@@ -139,8 +151,8 @@ export function useHistoryLoader({
// ── 1. 백엔드 /api/candles/history 우선 시도 ──────────────────────────
try {
olderBars = await fetchHistoryFromBackend(symbol, timeframe, beforeIso, LOAD_BATCH);
console.debug(`[HistoryLoader] 백엔드에서 ${olderBars.length}개 수신`);
olderBars = await fetchHistoryFromBackend(symbol, candleType, beforeIso, LOAD_BATCH);
console.debug(`[HistoryLoader] 백엔드에서 ${olderBars.length}개 수신 (${candleType})`);
} catch (backendErr) {
// ── 2. 백엔드 실패 시 업비트 REST 직접 폴백 ───────────────────────
console.warn('[HistoryLoader] 백엔드 실패, 업비트 직접 폴백:', backendErr);
@@ -153,7 +165,10 @@ export function useHistoryLoader({
hasMoreRef.current = false; // 더 이상 데이터 없음
} else {
// ── 3. 차트 앞부분에 부드럽게 병합 (Prepend) ──────────────────────
await mgr.prependBars(olderBars);
const added = await mgr.prependBars(olderBars);
if (added > 0) {
onPrependedRef.current?.(added, mgr.getRawBarsLength());
}
}
} catch (e) {
console.warn('[HistoryLoader] 과거 데이터 로드 실패:', e);