전략편집기 수정
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
/** 복합지표 — 동일 지표 2개(서로 다른 기간) 간 교차·비교 조건 */
|
||||
import type { ConditionDSL } from './strategyTypes';
|
||||
import { DEFAULT_START_CANDLE, normalizeStartCandleType } from './strategyStartNodes';
|
||||
|
||||
export interface CompositePeriodDef {
|
||||
rsiPeriod: number;
|
||||
@@ -65,8 +66,37 @@ export function compositeValueField(ind: string, period: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
/** K_ 접두 임계값 필드 — 기간 파싱·복합지표 승격에서 제외 */
|
||||
export function isThresholdField(field?: string): boolean {
|
||||
return !!field && field.startsWith('K_');
|
||||
}
|
||||
|
||||
const COMPOSITE_VALUE_PREFIX: Record<string, string> = {
|
||||
RSI: 'RSI_VALUE',
|
||||
CCI: 'CCI_VALUE',
|
||||
ADX: 'ADX_VALUE',
|
||||
WILLIAMS_R: 'WILLIAMS_R_VALUE',
|
||||
TRIX: 'TRIX_VALUE',
|
||||
VR: 'VR_VALUE',
|
||||
PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
INVEST_PSYCHOLOGICAL: 'INVEST_PSY_VALUE',
|
||||
};
|
||||
|
||||
/** 복합지표용 값 라인 필드(CCI_VALUE_9 등)인지 — K_100 임계값은 false */
|
||||
export function isCompositeValueField(ind: string, field?: string): boolean {
|
||||
if (!field || isThresholdField(field)) return false;
|
||||
if (ind === 'DISPARITY') {
|
||||
return field.startsWith('DISPARITY') && field.length > 'DISPARITY'.length
|
||||
&& /^\d+$/.test(field.slice('DISPARITY'.length));
|
||||
}
|
||||
const prefix = COMPOSITE_VALUE_PREFIX[ind];
|
||||
if (!prefix) return false;
|
||||
return field === prefix || field.startsWith(`${prefix}_`);
|
||||
}
|
||||
|
||||
export function parsePeriodFromCompositeField(ind: string, field?: string): number | null {
|
||||
if (!field) return null;
|
||||
if (!field || isThresholdField(field)) return null;
|
||||
if (ind === 'DISPARITY' && field.startsWith('DISPARITY')) {
|
||||
const n = parseInt(field.slice('DISPARITY'.length), 10);
|
||||
return Number.isFinite(n) && n > 0 ? n : null;
|
||||
@@ -93,6 +123,8 @@ export function syncCompositeFields(cond: ConditionDSL): ConditionDSL {
|
||||
...(rightP != null ? { rightPeriod: rightP } : null),
|
||||
};
|
||||
}
|
||||
const leftCt = normalizeStartCandleType(cond.leftCandleType ?? DEFAULT_START_CANDLE);
|
||||
const rightCt = normalizeStartCandleType(cond.rightCandleType ?? DEFAULT_START_CANDLE);
|
||||
return {
|
||||
...cond,
|
||||
composite: true,
|
||||
@@ -100,14 +132,30 @@ export function syncCompositeFields(cond: ConditionDSL): ConditionDSL {
|
||||
rightPeriod: rightP,
|
||||
leftField: compositeValueField(cond.indicatorType, leftP),
|
||||
rightField: compositeValueField(cond.indicatorType, rightP),
|
||||
leftCandleType: leftCt,
|
||||
rightCandleType: rightCt,
|
||||
period: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeCompositeCondition(cond: ConditionDSL): ConditionDSL {
|
||||
if (cond.composite) return syncCompositeFields(cond);
|
||||
const leftP = parsePeriodFromCompositeField(cond.indicatorType, cond.leftField);
|
||||
const rightP = parsePeriodFromCompositeField(cond.indicatorType, cond.rightField);
|
||||
if (cond.composite) {
|
||||
if (isThresholdField(cond.leftField) || isThresholdField(cond.rightField)) {
|
||||
return {
|
||||
...cond,
|
||||
composite: false,
|
||||
leftPeriod: undefined,
|
||||
rightPeriod: undefined,
|
||||
};
|
||||
}
|
||||
return syncCompositeFields(cond);
|
||||
}
|
||||
const ind = cond.indicatorType;
|
||||
if (!isCompositeValueField(ind, cond.leftField) || !isCompositeValueField(ind, cond.rightField)) {
|
||||
return cond;
|
||||
}
|
||||
const leftP = parsePeriodFromCompositeField(ind, cond.leftField);
|
||||
const rightP = parsePeriodFromCompositeField(ind, cond.rightField);
|
||||
if (leftP && rightP && leftP !== rightP) {
|
||||
return syncCompositeFields({
|
||||
...cond,
|
||||
@@ -131,6 +179,8 @@ export function makeCompositeCondition(
|
||||
composite: true,
|
||||
leftPeriod: short,
|
||||
rightPeriod: long,
|
||||
leftCandleType: DEFAULT_START_CANDLE,
|
||||
rightCandleType: DEFAULT_START_CANDLE,
|
||||
candleRange: 1,
|
||||
});
|
||||
}
|
||||
@@ -151,3 +201,8 @@ export function compositeElementLabel(ind: string, slot: 1 | 2): string {
|
||||
const name = item?.label ?? ind;
|
||||
return `${name} ${slot}`;
|
||||
}
|
||||
|
||||
/** 복합지표 조건대상 라벨 — 예: CCI 1 라인(9일) */
|
||||
export function compositeFieldLabel(ind: string, slot: 1 | 2, period: number): string {
|
||||
return `${compositeElementLabel(ind, slot)} 라인(${period}일)`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user