53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
import type { BacktestSignal } from './backendApi';
|
|
import { normalizeEpochSecOptional } from './backtestUiUtils';
|
|
|
|
export interface BarSignalHighlight {
|
|
buy: boolean;
|
|
sell: boolean;
|
|
}
|
|
|
|
const isBuySignalType = (type: BacktestSignal['type']): boolean =>
|
|
type === 'BUY' || type === 'SHORT_EXIT';
|
|
|
|
const isSellSignalType = (type: BacktestSignal['type']): boolean =>
|
|
type === 'SELL' || type === 'SHORT_ENTRY' || type === 'PARTIAL_SELL';
|
|
|
|
function signalMatchesBar(
|
|
signal: BacktestSignal,
|
|
barIndex: number,
|
|
barTimeSec: number,
|
|
): boolean {
|
|
if (signal.barIndex === barIndex) return true;
|
|
const signalTime = normalizeEpochSecOptional(signal.time);
|
|
const barTime = normalizeEpochSecOptional(barTimeSec);
|
|
if (signalTime > 0 && barTime > 0 && signalTime === barTime) return true;
|
|
return false;
|
|
}
|
|
|
|
/** 선택 봉에 백테스트 매매 시그널이 있는지 (차트 마커와 동일 기준) */
|
|
export function resolveBarSignalHighlight(
|
|
signals: BacktestSignal[],
|
|
barTimeSec: number | null | undefined,
|
|
barIndex?: number | null,
|
|
): BarSignalHighlight {
|
|
if (signals.length === 0) {
|
|
return { buy: false, sell: false };
|
|
}
|
|
if (barIndex == null || barIndex < 0) {
|
|
if (barTimeSec == null) return { buy: false, sell: false };
|
|
const t = normalizeEpochSecOptional(barTimeSec);
|
|
const atBar = signals.filter(s => normalizeEpochSecOptional(s.time) === t);
|
|
return {
|
|
buy: atBar.some(s => isBuySignalType(s.type)),
|
|
sell: atBar.some(s => isSellSignalType(s.type)),
|
|
};
|
|
}
|
|
|
|
const barTime = barTimeSec ?? 0;
|
|
const atBar = signals.filter(s => signalMatchesBar(s, barIndex, barTime));
|
|
return {
|
|
buy: atBar.some(s => isBuySignalType(s.type)),
|
|
sell: atBar.some(s => isSellSignalType(s.type)),
|
|
};
|
|
}
|