전략평가 화면 전략 일괄평가 기능

This commit is contained in:
Macbook
2026-06-19 20:26:59 +09:00
parent 994a2024c7
commit cc30d317ec
8 changed files with 683 additions and 21 deletions
@@ -0,0 +1,78 @@
import type { OHLCVBar } from '../types';
import type { StrategyDto } from './backendApi';
import { fetchLiveConditionScanSignals } from './backendApi';
import { buildEvalParamsFromStrategy } from './strategyEvaluationParams';
import { buildStrategyEvaluationToolbarSummary } from './strategyEvaluationReport';
export interface StrategyBulkEvalResult {
fullBuyReturnPct: number;
splitBuyReturnPct: number;
}
export interface StrategyBulkEvalContext {
market: string;
timeframe: string;
windowEndTimeSec: number | null;
evaluationBarCount: number;
barsFingerprint: string;
}
export function buildStrategyBulkEvalScanKey(ctx: StrategyBulkEvalContext): string {
const windowKey = ctx.windowEndTimeSec ?? 'latest';
return `${ctx.market}|${ctx.timeframe}|${windowKey}|${ctx.evaluationBarCount}|${ctx.barsFingerprint}`;
}
export function buildBarsFingerprint(bars: OHLCVBar[]): string {
if (bars.length === 0) return '0';
const first = bars[0];
const last = bars[bars.length - 1];
return `${bars.length}:${first.time}:${last.time}`;
}
export const strategyBulkEvalKeys = {
all: ['strategyBulkEval'] as const,
returns: (scanKey: string, strategyId: number) =>
[...strategyBulkEvalKeys.all, scanKey, strategyId] as const,
};
export async function evaluateStrategyBulkReturns(opts: {
strategy: StrategyDto;
market: string;
timeframe: string;
bars: OHLCVBar[];
evaluationBarCount: number;
initialCapital: number;
getParams: (type: string) => Record<string, number | string | boolean>;
}): Promise<StrategyBulkEvalResult> {
const strategyId = opts.strategy.id;
if (!strategyId || opts.bars.length < 10) {
throw new Error('평가 데이터 부족');
}
const indicatorParams = buildEvalParamsFromStrategy(opts.strategy, opts.getParams);
const signals = await fetchLiveConditionScanSignals(
opts.market,
strategyId,
opts.bars.map(b => ({
time: b.time,
open: b.open,
high: b.high,
low: b.low,
close: b.close,
volume: b.volume,
})),
opts.timeframe,
indicatorParams,
opts.evaluationBarCount,
);
const summary = buildStrategyEvaluationToolbarSummary(signals, {
initialCapital: opts.initialCapital,
symbol: opts.market.replace(/^KRW-/, ''),
});
return {
fullBuyReturnPct: summary.fullBuyReturnPct,
splitBuyReturnPct: summary.splitBuyReturnPct,
};
}