전략평가 화면 전략 일괄평가 기능
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { OHLCVBar } from '../types';
|
||||
import type { StrategyDto } from '../utils/backendApi';
|
||||
import {
|
||||
buildBarsFingerprint,
|
||||
buildStrategyBulkEvalScanKey,
|
||||
evaluateStrategyBulkReturns,
|
||||
strategyBulkEvalKeys,
|
||||
type StrategyBulkEvalResult,
|
||||
} from '../utils/strategyBulkEvaluation';
|
||||
import { mergeEvalGetParams } from '../utils/strategyEvaluationParams';
|
||||
|
||||
interface UseStrategyBulkEvaluationOpts {
|
||||
strategies: StrategyDto[];
|
||||
market: string;
|
||||
timeframe: string;
|
||||
bars: OHLCVBar[];
|
||||
evaluationBarCount: number;
|
||||
windowEndTimeSec: number | null;
|
||||
initialCapital: number;
|
||||
selectedStrategyId: number | null;
|
||||
getParams: (type: string) => Record<string, number | string | boolean>;
|
||||
appliedIndicatorParams: Record<string, Record<string, number | string | boolean>> | null;
|
||||
}
|
||||
|
||||
export function useStrategyBulkEvaluation({
|
||||
strategies,
|
||||
market,
|
||||
timeframe,
|
||||
bars,
|
||||
evaluationBarCount,
|
||||
windowEndTimeSec,
|
||||
initialCapital,
|
||||
selectedStrategyId,
|
||||
getParams,
|
||||
appliedIndicatorParams,
|
||||
}: UseStrategyBulkEvaluationOpts) {
|
||||
const queryClient = useQueryClient();
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [currentStrategyId, setCurrentStrategyId] = useState<number | null>(null);
|
||||
const runGenRef = useRef(0);
|
||||
|
||||
const scanKey = useMemo(
|
||||
() => buildStrategyBulkEvalScanKey({
|
||||
market,
|
||||
timeframe,
|
||||
windowEndTimeSec,
|
||||
evaluationBarCount,
|
||||
barsFingerprint: buildBarsFingerprint(bars),
|
||||
}),
|
||||
[market, timeframe, windowEndTimeSec, evaluationBarCount, bars],
|
||||
);
|
||||
|
||||
const getParamsForStrategy = useCallback((strategy: StrategyDto) => {
|
||||
if (strategy.id === selectedStrategyId && appliedIndicatorParams) {
|
||||
return mergeEvalGetParams(getParams, appliedIndicatorParams);
|
||||
}
|
||||
return getParams;
|
||||
}, [selectedStrategyId, appliedIndicatorParams, getParams]);
|
||||
|
||||
const startBulkEvaluation = useCallback(async (options?: { restart?: boolean }) => {
|
||||
if (!options?.restart && isRunning) return;
|
||||
if (bars.length < 10) return;
|
||||
const list = strategies.filter((s): s is StrategyDto & { id: number } => s.id != null);
|
||||
if (list.length === 0) return;
|
||||
|
||||
const gen = ++runGenRef.current;
|
||||
setIsRunning(true);
|
||||
setCurrentStrategyId(null);
|
||||
|
||||
for (const strategy of list) {
|
||||
if (gen !== runGenRef.current) break;
|
||||
setCurrentStrategyId(strategy.id);
|
||||
|
||||
const queryKey = strategyBulkEvalKeys.returns(scanKey, strategy.id);
|
||||
await queryClient.fetchQuery({
|
||||
queryKey,
|
||||
queryFn: () => evaluateStrategyBulkReturns({
|
||||
strategy,
|
||||
market,
|
||||
timeframe,
|
||||
bars,
|
||||
evaluationBarCount,
|
||||
initialCapital,
|
||||
getParams: getParamsForStrategy(strategy),
|
||||
}),
|
||||
staleTime: 0,
|
||||
gcTime: 10 * 60_000,
|
||||
}).catch(() => {
|
||||
queryClient.setQueryData(queryKey, null);
|
||||
});
|
||||
|
||||
await new Promise<void>(resolve => { setTimeout(resolve, 0); });
|
||||
}
|
||||
|
||||
if (gen === runGenRef.current) {
|
||||
setCurrentStrategyId(null);
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [
|
||||
isRunning,
|
||||
bars,
|
||||
strategies,
|
||||
queryClient,
|
||||
scanKey,
|
||||
market,
|
||||
timeframe,
|
||||
evaluationBarCount,
|
||||
initialCapital,
|
||||
getParamsForStrategy,
|
||||
]);
|
||||
|
||||
const cancelBulkEvaluation = useCallback(() => {
|
||||
runGenRef.current += 1;
|
||||
setCurrentStrategyId(null);
|
||||
setIsRunning(false);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isRunning,
|
||||
currentStrategyId,
|
||||
scanKey,
|
||||
startBulkEvaluation,
|
||||
cancelBulkEvaluation,
|
||||
canStart: bars.length >= 10 && strategies.some(s => s.id != null),
|
||||
};
|
||||
}
|
||||
|
||||
/** 목록 아이템 — React Query 캐시 구독 */
|
||||
export function useStrategyBulkEvalReturns(
|
||||
strategyId: number | null | undefined,
|
||||
scanKey: string,
|
||||
) {
|
||||
return useQuery<StrategyBulkEvalResult | null>({
|
||||
queryKey: strategyId != null
|
||||
? strategyBulkEvalKeys.returns(scanKey, strategyId)
|
||||
: ['strategyBulkEval', 'idle'],
|
||||
queryFn: async () => null,
|
||||
enabled: false,
|
||||
staleTime: 60_000,
|
||||
gcTime: 10 * 60_000,
|
||||
placeholderData: (prev) => prev,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user