일목-BB 수정
This commit is contained in:
@@ -446,6 +446,7 @@ export function initConditionPeriods(
|
||||
export function hasNodeSettings(cond: ConditionDSL): boolean {
|
||||
return usesValuePeriodField(cond)
|
||||
|| hasEditableThreshold(cond)
|
||||
|| cond.indicatorType === 'ICHIMOKU_BB'
|
||||
|| !!cond.composite;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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봉 전 후행스팬(종가)' },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user