전략편집기 수정

This commit is contained in:
Macbook
2026-05-25 17:56:25 +09:00
parent aac6454724
commit 02d14e4b2b
39 changed files with 1974 additions and 288 deletions
+72 -2
View File
@@ -1,6 +1,15 @@
/** 조건 노드별 지표 기간·임계값 — 차트 DEF 기본값 대비 노드 단위 오버라이드 */
import type { ConditionDSL } from './strategyTypes';
import { getCompositeDefaultPeriods, parsePeriodFromCompositeField, syncCompositeFields, type CompositePeriodDef } from './compositeIndicators';
import {
compositeFieldLabel,
compositeValueField,
getCompositeDefaultPeriods,
isThresholdField,
parsePeriodFromCompositeField,
syncCompositeFields,
type CompositePeriodDef,
} from './compositeIndicators';
import { DEFAULT_START_CANDLE, normalizeStartCandleType } from './strategyStartNodes';
export interface IndicatorPeriodDef {
rsiPeriod: number;
@@ -41,7 +50,7 @@ export function getDefaultIndicatorPeriod(indicatorType: string, def: IndicatorP
}
function parseFieldPeriod(field?: string): number | null {
if (!field) return null;
if (!field || isThresholdField(field)) return null;
const m = field.match(/_(\d+)$/);
if (!m) return null;
const n = parseInt(m[1], 10);
@@ -72,6 +81,24 @@ export function getConditionRightPeriod(cond: ConditionDSL, def: IndicatorPeriod
return getDefaultIndicatorPeriod(cond.indicatorType, def);
}
/** 조건대상 직접입력 — 기간(일) 또는 임계값 */
export function resolveFieldCustomKind(
indicatorType: string,
field: string | undefined,
): 'period' | 'threshold' | 'none' {
if (!field) return 'none';
if (isThresholdField(field)) return 'threshold';
const prefix = VALUE_FIELD_PREFIX[indicatorType];
if (prefix && (field === prefix || field.startsWith(`${prefix}_`))) return 'period';
return 'none';
}
export function isValuePeriodFieldStored(indicatorType: string, field?: string): boolean {
const prefix = VALUE_FIELD_PREFIX[indicatorType];
if (!prefix || !field) return false;
return field.startsWith(`${prefix}_`) && field.length > prefix.length + 1;
}
export function usesValuePeriodField(cond: ConditionDSL): boolean {
if (cond.composite) return true;
const prefix = VALUE_FIELD_PREFIX[cond.indicatorType];
@@ -178,6 +205,49 @@ export function getCompositePeriodPresetOptions(
return [...new Set([base, short, long, ...COMMON_PERIODS])].sort((a, b) => a - b);
}
/** 복합지표 조건대상1/2 드롭다운 — CCI 1 라인(9일) 형식 */
export function getCompositeLeftCandleType(cond: ConditionDSL): string {
return normalizeStartCandleType(cond.leftCandleType ?? DEFAULT_START_CANDLE);
}
export function getCompositeRightCandleType(cond: ConditionDSL): string {
return normalizeStartCandleType(cond.rightCandleType ?? DEFAULT_START_CANDLE);
}
export function setCompositeLeftCandleType(cond: ConditionDSL, candleType: string): ConditionDSL {
return syncCompositeFields({
...cond,
composite: true,
leftCandleType: normalizeStartCandleType(candleType),
});
}
export function setCompositeRightCandleType(cond: ConditionDSL, candleType: string): ConditionDSL {
return syncCompositeFields({
...cond,
composite: true,
rightCandleType: normalizeStartCandleType(candleType),
});
}
export function getCompositeFieldOpts(
indicatorType: string,
slot: 1 | 2,
def: IndicatorPeriodDef,
cond: ConditionDSL,
): { value: string; label: string }[] {
const side = slot === 1 ? 'left' : 'right';
const current = slot === 1
? getConditionValuePeriod(cond, def)
: getConditionRightPeriod(cond, def);
const presets = getCompositePeriodPresetOptions(indicatorType, def, side);
const periods = [...new Set([...presets, current])].sort((a, b) => a - b);
return periods.map(p => ({
value: compositeValueField(indicatorType, p),
label: compositeFieldLabel(indicatorType, slot, p),
}));
}
export function getThresholdPresetOptions(indicatorType: string): number[] {
switch (indicatorType) {
case 'RSI':