Files
auto-trade/features/dashboard/hooks/useStockOverview.ts

119 lines
3.5 KiB
TypeScript

import { useCallback, useState, useTransition } from "react";
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
import type {
DashboardMarketPhase,
DashboardPriceSource,
DashboardRealtimeTradeTick,
DashboardStockSearchItem,
DashboardStockItem,
} from "@/features/dashboard/types/dashboard.types";
import { fetchStockOverview } from "@/features/dashboard/apis/kis-stock.api";
interface OverviewMeta {
priceSource: DashboardPriceSource;
marketPhase: DashboardMarketPhase;
fetchedAt: string;
}
export function useStockOverview() {
const [selectedStock, setSelectedStock] = useState<DashboardStockItem | null>(
null,
);
const [meta, setMeta] = useState<OverviewMeta | null>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, startTransition] = useTransition();
const loadOverview = useCallback(
(
symbol: string,
credentials: KisRuntimeCredentials | null,
marketHint?: DashboardStockSearchItem["market"],
) => {
if (!credentials) return;
startTransition(async () => {
try {
setError(null);
const data = await fetchStockOverview(symbol, credentials);
setSelectedStock({
...data.stock,
market: marketHint ?? data.stock.market,
});
setMeta({
priceSource: data.priceSource,
marketPhase: data.marketPhase,
fetchedAt: data.fetchedAt,
});
} catch (err) {
const message =
err instanceof Error
? err.message
: "종목 조회 중 오류가 발생했습니다.";
setError(message);
setMeta(null);
}
});
},
[],
);
// 실시간 체결 데이터 수신 시 헤더/차트 기준 가격을 갱신합니다.
const updateRealtimeTradeTick = useCallback(
(tick: DashboardRealtimeTradeTick) => {
setSelectedStock((prev) => {
if (!prev) return prev;
const { price, accumulatedVolume, change, changeRate, tickTime } = tick;
const pointTime =
tickTime && tickTime.length === 6
? `${tickTime.slice(0, 2)}:${tickTime.slice(2, 4)}`
: "실시간";
const nextChange = change;
const nextChangeRate = Number.isFinite(changeRate)
? changeRate
: prev.prevClose > 0
? (nextChange / prev.prevClose) * 100
: prev.changeRate;
const nextHigh = prev.high > 0 ? Math.max(prev.high, price) : price;
const nextLow = prev.low > 0 ? Math.min(prev.low, price) : price;
const nextCandles =
prev.candles.length > 0 &&
prev.candles[prev.candles.length - 1]?.time === pointTime
? [
...prev.candles.slice(0, -1),
{
...prev.candles[prev.candles.length - 1],
time: pointTime,
price,
},
]
: [...prev.candles, { time: pointTime, price }].slice(-80);
return {
...prev,
currentPrice: price,
change: nextChange,
changeRate: nextChangeRate,
high: nextHigh,
low: nextLow,
volume: accumulatedVolume > 0 ? accumulatedVolume : prev.volume,
candles: nextCandles,
};
});
},
[],
);
return {
selectedStock,
setSelectedStock,
meta,
setMeta,
error,
setError,
isLoading,
loadOverview,
updateRealtimeTradeTick,
};
}