111 lines
3.3 KiB
TypeScript
111 lines
3.3 KiB
TypeScript
import { generateNodeId } from './strategyTypes';
|
|
|
|
export const START_NODE_ID = '__strategy_start__';
|
|
export const DEFAULT_START_CANDLE = '1m';
|
|
/** 새 전략 START 노드 기본 평가 분봉 */
|
|
export const DEFAULT_NEW_STRATEGY_START_CANDLE = '3m';
|
|
|
|
export const STRATEGY_CANDLE_TYPES = [
|
|
'1m', '3m', '5m', '10m', '15m', '30m', '1h', '4h', '1w', '1d',
|
|
] as const;
|
|
|
|
/** 그래프 START 노드 — 시간봉 체크박스 2행 배치 */
|
|
export const START_TIMEFRAME_GRAPH_ROWS: readonly (readonly StrategyCandleType[])[] = [
|
|
['1m', '3m', '5m', '10m', '15m'],
|
|
['30m', '1h', '4h', '1w', '1d'],
|
|
] as const;
|
|
|
|
export type StrategyCandleType = (typeof STRATEGY_CANDLE_TYPES)[number];
|
|
|
|
export type StartNodeMeta = {
|
|
/** @deprecated 단일 분봉 — candleTypes 우선 */
|
|
candleType?: string;
|
|
/** 선택된 평가 시간봉 (마감봉마다 독립 체크) */
|
|
candleTypes?: string[];
|
|
};
|
|
|
|
/** START가 2개 이상일 때 분기 간 결합 연산 */
|
|
export type StartCombineOp = 'AND' | 'OR';
|
|
|
|
export const DEFAULT_START_COMBINE_OP: StartCombineOp = 'AND';
|
|
|
|
export function isStartNodeId(id: string): boolean {
|
|
return id === START_NODE_ID || id.startsWith('__strategy_start_');
|
|
}
|
|
|
|
export function createStartNodeId(): string {
|
|
return `__strategy_start_${generateNodeId()}__`;
|
|
}
|
|
|
|
export function defaultStartMeta(): Record<string, StartNodeMeta> {
|
|
return {
|
|
[START_NODE_ID]: {
|
|
candleTypes: [DEFAULT_NEW_STRATEGY_START_CANDLE],
|
|
candleType: DEFAULT_NEW_STRATEGY_START_CANDLE,
|
|
},
|
|
};
|
|
}
|
|
|
|
/** START 메타에서 정렬된 평가 분봉 목록 (최소 1개) */
|
|
export function getStartCandleTypes(meta?: StartNodeMeta | null): string[] {
|
|
const raw = meta?.candleTypes?.length
|
|
? meta.candleTypes
|
|
: meta?.candleType
|
|
? [meta.candleType]
|
|
: [DEFAULT_START_CANDLE];
|
|
const seen = new Set<string>();
|
|
const out: string[] = [];
|
|
for (const ct of STRATEGY_CANDLE_TYPES) {
|
|
if (raw.some(r => normalizeStartCandleType(r) === ct) && !seen.has(ct)) {
|
|
seen.add(ct);
|
|
out.push(ct);
|
|
}
|
|
}
|
|
for (const r of raw) {
|
|
const n = normalizeStartCandleType(r);
|
|
if (!seen.has(n)) {
|
|
seen.add(n);
|
|
out.push(n);
|
|
}
|
|
}
|
|
return out.length > 0 ? out : [DEFAULT_START_CANDLE];
|
|
}
|
|
|
|
export function formatStartCandleTypesLabel(types: string[]): string {
|
|
const list = getStartCandleTypes({ candleTypes: types });
|
|
return list.map(formatStrategyCandleLabel).join(' · ');
|
|
}
|
|
|
|
export function normalizeStartCandleType(value: string | undefined | null): string {
|
|
const raw = (value ?? DEFAULT_START_CANDLE).trim().toLowerCase();
|
|
if (raw === '1d' || raw === '1w') return raw;
|
|
return (STRATEGY_CANDLE_TYPES as readonly string[]).includes(raw) ? raw : DEFAULT_START_CANDLE;
|
|
}
|
|
|
|
export function formatStartCandleLabel(candleType: string): string {
|
|
return normalizeStartCandleType(candleType);
|
|
}
|
|
|
|
const CANDLE_TYPE_LABELS: Record<string, string> = {
|
|
'1m': '1분봉',
|
|
'3m': '3분봉',
|
|
'5m': '5분봉',
|
|
'10m': '10분봉',
|
|
'15m': '15분봉',
|
|
'30m': '30분봉',
|
|
'1h': '1시간봉',
|
|
'4h': '4시간봉',
|
|
'1w': '주간봉',
|
|
'1d': '일봉',
|
|
};
|
|
|
|
export function formatStrategyCandleLabel(candleType: string): string {
|
|
const ct = normalizeStartCandleType(candleType);
|
|
return CANDLE_TYPE_LABELS[ct] ?? ct;
|
|
}
|
|
|
|
export const STRATEGY_CANDLE_TYPE_OPTIONS = STRATEGY_CANDLE_TYPES.map(ct => ({
|
|
value: ct,
|
|
label: CANDLE_TYPE_LABELS[ct] ?? ct,
|
|
}));
|