일목균형표 전략지표 추가

This commit is contained in:
Macbook
2026-06-16 20:40:42 +09:00
parent bf208a7ace
commit 1bc275fc53
6 changed files with 136 additions and 11 deletions
+81
View File
@@ -15,6 +15,7 @@ export type StableStrategyId =
| 'DONCHIAN_20'
| 'MA_TREND'
| 'ICHIMOKU_TREND'
| 'ICHIMOKU_SANYAKU'
| 'MACD_MOMENTUM'
| 'BOLLINGER_RANGE';
@@ -54,6 +55,8 @@ export const MA_TREND_BUY = 'MA_TREND_BUY';
export const MA_TREND_SELL = 'MA_TREND_SELL';
export const ICHIMOKU_TREND_BUY = 'ICHIMOKU_TREND_BUY';
export const ICHIMOKU_TREND_SELL = 'ICHIMOKU_TREND_SELL';
export const ICHIMOKU_SANYAKU_BUY = 'ICHIMOKU_SANYAKU_BUY';
export const ICHIMOKU_SANYAKU_SELL = 'ICHIMOKU_SANYAKU_SELL';
export const MACD_MOMENTUM_BUY = 'MACD_MOMENTUM_BUY';
export const MACD_MOMENTUM_SELL = 'MACD_MOMENTUM_SELL';
export const BOLLINGER_RANGE_BUY = 'BOLLINGER_RANGE_BUY';
@@ -70,6 +73,8 @@ const VALUE_TO_META: Record<string, { id: StableStrategyId; mode: 'buy' | 'sell'
[MA_TREND_SELL]: { id: 'MA_TREND', mode: 'sell' },
[ICHIMOKU_TREND_BUY]: { id: 'ICHIMOKU_TREND', mode: 'buy' },
[ICHIMOKU_TREND_SELL]: { id: 'ICHIMOKU_TREND', mode: 'sell' },
[ICHIMOKU_SANYAKU_BUY]: { id: 'ICHIMOKU_SANYAKU', mode: 'buy' },
[ICHIMOKU_SANYAKU_SELL]: { id: 'ICHIMOKU_SANYAKU', mode: 'sell' },
[MACD_MOMENTUM_BUY]: { id: 'MACD_MOMENTUM', mode: 'buy' },
[MACD_MOMENTUM_SELL]: { id: 'MACD_MOMENTUM', mode: 'sell' },
[BOLLINGER_RANGE_BUY]: { id: 'BOLLINGER_RANGE', mode: 'buy' },
@@ -136,6 +141,16 @@ export const STABLE_STRATEGY_PALETTE_ITEMS: StableStrategyPaletteMeta[] = [
desc: '전환/기준선 데드크로스 또는 구름 아래',
periodHint: '9 / 26',
},
{
value: ICHIMOKU_SANYAKU_BUY, label: '일목 삼역호전 매수', strategyId: 'ICHIMOKU_SANYAKU', mode: 'buy',
desc: '4단계 Full-AND — 전환·기준·구름·후행·MA60·거래량 (일목 산인 삼역好轉)',
periodHint: '9 / 26 / 52',
},
{
value: ICHIMOKU_SANYAKU_SELL, label: '일목 삼역호전 매도', strategyId: 'ICHIMOKU_SANYAKU', mode: 'sell',
desc: '분할 청산 OR — 전환선 이탈 / 기준선·데드크로스 / 구름 하방',
periodHint: '9 / 26 / 52',
},
{
value: MACD_MOMENTUM_BUY, label: 'MACD 모멘텀 매수', strategyId: 'MACD_MOMENTUM', mode: 'buy',
desc: 'MACD 상향돌파 + EMA60 위 + ADX',
@@ -213,6 +228,17 @@ function makeCondition(
};
}
function makeVolumeMaRatioFilter(
def: IndicatorPeriodDef,
ratio: number,
maLength = 5,
): LogicNode {
return makeCondition('VOLUME', 'VOLUME_GT_MA_RATIO', 'VOLUME_VALUE', 'VOLUME_MA', def, {
compareValue: ratio,
params: { length: maLength },
});
}
function makeAdxFilter(threshold: number, def: IndicatorPeriodDef): LogicNode {
return makeCondition('ADX', 'GTE', 'ADX_VALUE', adxField(threshold), def, { targetValue: threshold });
}
@@ -345,6 +371,45 @@ function buildIchimokuTrendSell(def: IndicatorPeriodDef, level: StableStrategyFi
return wrapOrRoot(branches, pairMeta('ICHIMOKU_TREND', 'sell', level));
}
/** 일목 삼역호전(三役好轉) — 4단계 Full-AND 매수 */
function buildIchimokuSanyakuBuy(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
const nodes: LogicNode[] = [
// [1단계] 주가·단기 추세
makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def),
makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'BASE_LINE', def),
makeCondition('ICHIMOKU', 'GT', 'CONVERSION_LINE', 'BASE_LINE', def),
// [2단계] 구름대·양운
makeCondition('ICHIMOKU', 'ABOVE_CLOUD', 'CLOSE_PRICE', 'NONE', def),
makeCondition('ICHIMOKU', 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def),
// [3단계] 후행스팬 2중 잠금
makeCondition('ICHIMOKU', 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def),
makeCondition('ICHIMOKU', 'LAGGING_ABOVE_PRIOR_CLOUD', 'NONE', 'NONE', def),
];
if (level !== 'relaxed') {
// [4단계] MA60 대세 + 거래량
nodes.push(makeCondition('MA', 'GT', 'CLOSE_PRICE', maField(60), def));
if (level === 'strict') {
nodes.push(makeVolumeMaRatioFilter(def, 1.5, 5));
} else {
nodes.push(makeCondition('VOLUME', 'GT', 'VOLUME_VALUE', 'VOLUME_MA', def, { params: { length: 5 } }));
}
}
return wrapAnd(nodes, pairMeta('ICHIMOKU_SANYAKU', 'buy', level));
}
/** 일목 삼역호전 — 분할 매도 OR (전환선 → 기준선/데드 → 구름) */
function buildIchimokuSanyakuSell(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
void level;
return wrapOrRoot([
makeCondition('ICHIMOKU', 'LT', 'CLOSE_PRICE', 'CONVERSION_LINE', def),
wrapOr([
makeCondition('ICHIMOKU', 'LT', 'CLOSE_PRICE', 'BASE_LINE', def),
makeCondition('ICHIMOKU', 'CROSS_DOWN', 'CONVERSION_LINE', 'BASE_LINE', def),
]),
makeCondition('ICHIMOKU', 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE', def),
], pairMeta('ICHIMOKU_SANYAKU', 'sell', level));
}
function buildMacdMomentumBuy(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
const nodes: LogicNode[] = [
makeCondition('MACD', 'CROSS_UP', 'MACD_LINE', 'SIGNAL_LINE', def),
@@ -394,6 +459,7 @@ const BUILDERS: Record<StableStrategyId, {
DONCHIAN_20: { buy: buildDonchian20Buy, sell: buildDonchian20Sell },
MA_TREND: { buy: buildMaTrendBuy, sell: buildMaTrendSell },
ICHIMOKU_TREND: { buy: buildIchimokuTrendBuy, sell: buildIchimokuTrendSell },
ICHIMOKU_SANYAKU: { buy: buildIchimokuSanyakuBuy, sell: buildIchimokuSanyakuSell },
MACD_MOMENTUM: { buy: buildMacdMomentumBuy, sell: buildMacdMomentumSell },
BOLLINGER_RANGE: { buy: buildBollingerRangeBuy, sell: buildBollingerRangeSell },
};
@@ -431,6 +497,8 @@ export function stableStrategySummaryText(
CROSS_UP: '상향돌파', CROSS_DOWN: '하향돌파',
GT: '>', LT: '<', GTE: '≥', LTE: '≤',
ABOVE_CLOUD: '구름 위', BELOW_CLOUD: '구름 아래',
SPAN1_GT_SPAN2: '양운(선행1>2)', LAGGING_GT_PRICE: '후행>26봉전 종가',
LAGGING_ABOVE_PRIOR_CLOUD: '후행>26봉전 구름', VOLUME_GT_MA_RATIO: '거래량≥MA×비율',
};
const fmt = (n: LogicNode): string => {
if (n.type === 'CONDITION' && n.condition) {
@@ -459,6 +527,11 @@ export function stableStrategyFilterSummary(
else parts.push('ADX 횡보필터 없음');
}
if (strategyId === 'ICHIMOKU_TREND' && level === 'relaxed') parts.push('구름필터 없음');
if (strategyId === 'ICHIMOKU_SANYAKU') {
if (level === 'strict') parts.push('MA60·거래량150%');
else if (level === 'balanced') parts.push('MA60·거래량>MA5');
else parts.push('MA60·거래량 없음');
}
if (strategyId === 'MACD_MOMENTUM' && level === 'relaxed') parts.push('EMA60·ADX 없음');
if (strategyId === 'MA_TREND' && level === 'relaxed') parts.push('ADX 없음');
return parts.join(', ');
@@ -489,6 +562,14 @@ export function inferStableStrategyFilterLevel(node: LogicNode): StableStrategyF
if (id === 'ICHIMOKU_TREND' && !conds.some(c => c.conditionType === 'ABOVE_CLOUD' || c.conditionType === 'BELOW_CLOUD')) {
return 'relaxed';
}
if (id === 'ICHIMOKU_SANYAKU') {
const hasMa = conds.some(c => c.indicatorType === 'MA' && c.rightField === maField(60));
const hasVolRatio = conds.some(c => c.conditionType === 'VOLUME_GT_MA_RATIO');
const hasVol = conds.some(c => c.indicatorType === 'VOLUME');
if (!hasMa && !hasVol) return 'relaxed';
if (hasVolRatio) return 'strict';
if (hasMa && hasVol) return 'balanced';
}
if (id === 'MACD_MOMENTUM' && !conds.some(c => c.rightField === emaField(60))) return 'relaxed';
if (id === 'MA_TREND' && !conds.some(c => c.indicatorType === 'ADX')) return 'relaxed';
if (id === 'BOLLINGER_RANGE') {