일목-BB 수정

This commit is contained in:
Macbook
2026-06-23 21:24:37 +09:00
parent 3e26373bfe
commit 5d90d1a74e
13 changed files with 226 additions and 239 deletions
+1
View File
@@ -446,6 +446,7 @@ export function initConditionPeriods(
export function hasNodeSettings(cond: ConditionDSL): boolean {
return usesValuePeriodField(cond)
|| hasEditableThreshold(cond)
|| cond.indicatorType === 'ICHIMOKU_BB'
|| !!cond.composite;
}
+63 -5
View File
@@ -1,8 +1,8 @@
/**
* 일목 후행스팬 × 볼린저밴드 복합 전략
*
* 현재 후행스팬(종가) vs N봉(chikouDisplacement) 전 볼린저 상한선·중앙값·하한선
* — 상향/하향 돌파·위/아래 유지 조건
* 현재 봉 기준 N봉(chikouDisplacement) 전 시점에서
* 후행스팬(종가) vs 볼린저 상한선·중앙값·하한선 — 상·하향 돌파·위/아래 유지
*/
import type { ConditionDSL, LogicNode } from './strategyTypes';
import { initConditionPeriodsInherit, type IndicatorPeriodDef } from './conditionPeriods';
@@ -136,15 +136,16 @@ export function ichimokuBbDisplayName(mode: 'buy' | 'sell', displacement = DEFAU
}
export function ichimokuBbSummaryText(config: IchimokuBbPairConfig): string {
return `후행스팬 ${config.chikouDisplacement}봉 전 ${bandLabel(config.bbBand)} ${conditionLabel(config.conditionType)}`;
const n = config.chikouDisplacement;
return `${n}봉 전 후행스팬(종가) — ${n}봉 전 ${bandLabel(config.bbBand)} ${conditionLabel(config.conditionType)}`;
}
export function ichimokuBbPaletteDesc(value: string): string {
if (isIchimokuBbBuyPaletteValue(value)) {
return `후행스팬(종가)이 ${DEFAULT_CHIKOU_DISPLACEMENT}봉 전 볼린저 상한선 상향 돌파 — 밴드·N·조건 변경 가능`;
return `${DEFAULT_CHIKOU_DISPLACEMENT}봉 전 후행스팬(종가)이 ${DEFAULT_CHIKOU_DISPLACEMENT}봉 전 볼린저 상한선 상향 돌파 — 밴드·N·조건 변경 가능`;
}
if (isIchimokuBbSellPaletteValue(value)) {
return `후행스팬(종가)이 ${DEFAULT_CHIKOU_DISPLACEMENT}봉 전 볼린저 하한선 하향 돌파 — 밴드·N·조건 변경 가능`;
return `${DEFAULT_CHIKOU_DISPLACEMENT}봉 전 후행스팬(종가)이 ${DEFAULT_CHIKOU_DISPLACEMENT}봉 전 볼린저 하한선 하향 돌파 — 밴드·N·조건 변경 가능`;
}
return '';
}
@@ -196,3 +197,60 @@ export function patchIchimokuBbPairInTree(
});
return changed ? { ...root, children } : root;
}
export function getChikouDisplacement(cond: ConditionDSL): number {
const n = Number(cond.params?.chikouDisplacement);
return Number.isFinite(n) && n > 0 ? n : DEFAULT_CHIKOU_DISPLACEMENT;
}
export function setChikouDisplacement(cond: ConditionDSL, displacement: number): ConditionDSL {
const n = Math.max(1, Math.round(displacement));
return {
...cond,
params: { ...(cond.params ?? {}), chikouDisplacement: n },
};
}
export function ichimokuBbPairMetaFromCondition(
mode: 'buy' | 'sell',
cond: ConditionDSL,
): Omit<IchimokuBbPairConfig, 'mode'> {
const defaults = mode === 'buy' ? DEFAULT_BUY : DEFAULT_SELL;
return {
chikouDisplacement: getChikouDisplacement(cond),
bbBand: (cond.rightField as IchimokuBbBand) ?? defaults.bbBand,
conditionType: (cond.conditionType as IchimokuBbConditionType) ?? defaults.conditionType,
};
}
/** 자식 CONDITION 변경 시 게이트 ichimokuBbPair 메타 동기화 */
export function syncIchimokuBbPairFromChild(node: LogicNode): LogicNode {
if (!isIchimokuBbPairRoot(node) || !node.ichimokuBbPair) return node;
const cond = node.children?.[0]?.condition;
if (!cond || cond.indicatorType !== 'ICHIMOKU_BB') return node;
const nextMeta: IchimokuBbPairConfig = {
mode: node.ichimokuBbPair.mode,
...ichimokuBbPairMetaFromCondition(node.ichimokuBbPair.mode, cond),
};
const prev = node.ichimokuBbPair;
if (
prev.chikouDisplacement === nextMeta.chikouDisplacement
&& prev.bbBand === nextMeta.bbBand
&& prev.conditionType === nextMeta.conditionType
) {
return node;
}
return { ...node, ichimokuBbPair: nextMeta };
}
export function syncIchimokuBbPairsInTree(root: LogicNode | null): LogicNode | null {
if (!root) return null;
let node = root;
if (node.children?.length) {
const children = node.children.map(c => syncIchimokuBbPairsInTree(c)!);
if (children.some((c, i) => c !== node.children![i])) {
node = { ...node, children };
}
}
return syncIchimokuBbPairFromChild(node);
}
@@ -51,6 +51,7 @@ import {
ichimokuBbDisplayName,
ichimokuBbSummaryText,
inferIchimokuBbConfig,
getChikouDisplacement,
} from '../utils/ichimokuBbStrategy';
import {
buildStableStrategyTree,
@@ -782,6 +783,17 @@ function buildFieldOptsForSlot(
];
return slot === 'right' ? [NONE, ...lines] : lines;
}
case 'ICHIMOKU_BB': {
const chikouN = getChikouDisplacement(cond ?? { indicatorType: 'ICHIMOKU_BB' } as ConditionDSL);
if (slot === 'left') {
return [{ value: 'LAGGING_SPAN', label: `${chikouN}봉 전 후행스팬(종가)` }];
}
return [
{ value: 'UPPER_BAND', label: `${chikouN}봉 전 상한선(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
{ value: 'MIDDLE_BAND', label: `${chikouN}봉 전 중앙값(${DEF.bbPeriod}일)` },
{ value: 'LOWER_BAND', label: `${chikouN}봉 전 하한선(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
];
}
default:
return slot === 'right' ? [NONE] : [];
}
@@ -839,6 +851,9 @@ const getDefaultConditionFields = (ind: string, signalType: 'buy'|'sell', DEF: D
NEW_HIGH: { l:'CLOSE_PRICE', r: nhPriorField(9) },
NEW_LOW: { l:'CLOSE_PRICE', r: nlPriorField(9) },
ICHIMOKU: { l:'CONVERSION_LINE', r:'BASE_LINE' },
ICHIMOKU_BB: signalType === 'buy'
? { l: 'LAGGING_SPAN', r: 'UPPER_BAND' }
: { l: 'LAGGING_SPAN', r: 'LOWER_BAND' },
};
return map[ind] ?? { l:'NONE', r:'NONE' };
};
@@ -231,7 +231,14 @@ export function resetPaletteItems(kind: PaletteItemKind): PaletteItem[] {
/** 전략편집기·전략빌더에 표시할 지표명 (우측 팔레트 label 우선) */
const STRATEGY_INDICATOR_TYPE_ALIASES: Record<string, string> = {};
const STATIC_INDICATOR_DISPLAY_NAMES: Record<string, string> = {
ICHIMOKU_BB: '일목×BB',
};
export function getStrategyIndicatorDisplayName(indicatorType: string): string {
if (STATIC_INDICATOR_DISPLAY_NAMES[indicatorType]) {
return STATIC_INDICATOR_DISPLAY_NAMES[indicatorType];
}
const resolved = STRATEGY_INDICATOR_TYPE_ALIASES[indicatorType] ?? indicatorType;
for (const kind of ['auxiliary', 'composite'] as const) {
+1 -1
View File
@@ -198,7 +198,7 @@ export const LEFT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
{ value: 'LAGGING_SPAN', label: '후행스팬' },
],
ICHIMOKU_BB: [
{ value: 'LAGGING_SPAN', label: '후행스팬' },
{ value: 'LAGGING_SPAN', label: 'N봉 전 후행스팬(종가)' },
],
};