일목균형표 전략지표 추가
This commit is contained in:
@@ -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') {
|
||||
|
||||
@@ -135,8 +135,10 @@ function describeConditionType(
|
||||
case 'SPAN1_LT_SPAN2': return '선행스팬1이 선행스팬2보다 아래에 있는 경우';
|
||||
case 'SPAN1_CROSS_UP_SPAN2': return '선행스팬1이 선행스팬2를 상향 돌파하는 경우';
|
||||
case 'SPAN1_CROSS_DOWN_SPAN2': return '선행스팬1이 선행스팬2를 하향 돌파하는 경우';
|
||||
case 'LAGGING_GT_PRICE': return '후행스팬이 종가보다 위에 있는 경우';
|
||||
case 'LAGGING_LT_PRICE': return '후행스팬이 종가보다 아래에 있는 경우';
|
||||
case 'LAGGING_GT_PRICE': return '후행스팬(현재 종가)이 26봉 전 종가보다 위에 있는 경우';
|
||||
case 'LAGGING_LT_PRICE': return '후행스팬(현재 종가)이 26봉 전 종가보다 아래에 있는 경우';
|
||||
case 'LAGGING_ABOVE_PRIOR_CLOUD': return '후행스팬이 26봉 전 구름대(선행스팬)보다 위에 있는 경우';
|
||||
case 'VOLUME_GT_MA_RATIO': return '거래량이 거래량 이동평균 대비 설정 비율 이상인 경우';
|
||||
default: {
|
||||
const label = CONDITION_LABEL[conditionType] ?? conditionType;
|
||||
if (right && right !== '선택안함') return `${left} — ${label} — ${right}`;
|
||||
|
||||
@@ -429,8 +429,9 @@ const ICHIMOKU_CONDS = [
|
||||
{ v: 'SPAN1_LT_SPAN2', l: '선행스팬1 < 선행스팬2' },
|
||||
{ v: 'SPAN1_CROSS_UP_SPAN2', l: '선행스팬1 상향돌파 선행스팬2' },
|
||||
{ v: 'SPAN1_CROSS_DOWN_SPAN2', l: '선행스팬1 하향돌파 선행스팬2' },
|
||||
{ v: 'LAGGING_GT_PRICE', l: '후행스팬 > 가격' },
|
||||
{ v: 'LAGGING_LT_PRICE', l: '후행스팬 < 가격' },
|
||||
{ v: 'LAGGING_GT_PRICE', l: '후행스팬 > 26봉전 종가' },
|
||||
{ v: 'LAGGING_LT_PRICE', l: '후행스팬 < 26봉전 종가' },
|
||||
{ v: 'LAGGING_ABOVE_PRIOR_CLOUD', l: '후행스팬 > 26봉전 구름' },
|
||||
];
|
||||
|
||||
// ─── 지표별 fieldOptions 빌더 ────────────────────────────────────────────────
|
||||
@@ -871,8 +872,16 @@ export const applyCondTypeDefaults = (cond: ConditionDSL, newCondType: string, D
|
||||
updated.leftField = 'LEADING_SPAN1'; updated.rightField = 'LEADING_SPAN2';
|
||||
} else if (['LAGGING_GT_PRICE','LAGGING_LT_PRICE'].includes(newCondType)) {
|
||||
updated.leftField = 'LAGGING_SPAN'; updated.rightField = 'CLOSE_PRICE';
|
||||
} else if (newCondType === 'LAGGING_ABOVE_PRIOR_CLOUD') {
|
||||
updated.leftField = 'NONE'; updated.rightField = 'NONE';
|
||||
}
|
||||
}
|
||||
if (cond.indicatorType === 'VOLUME' && newCondType === 'VOLUME_GT_MA_RATIO') {
|
||||
updated.leftField = 'VOLUME_VALUE';
|
||||
updated.rightField = 'VOLUME_MA';
|
||||
updated.compareValue = updated.compareValue ?? 1.5;
|
||||
updated.params = { length: 5, ...(updated.params ?? {}) };
|
||||
}
|
||||
if (isPriceExtremeIndicator(updated.indicatorType)) {
|
||||
return syncPriceExtremeSimpleFields(updated);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface ConditionDSL {
|
||||
leftField?: string;
|
||||
rightField?: string;
|
||||
compareValue?: number;
|
||||
/** 조건 노드 전용 지표 파라미터 (예: VOLUME MA length) */
|
||||
params?: Record<string, number | string | boolean>;
|
||||
slopePeriod?: number;
|
||||
holdDays?: number;
|
||||
/** LOWEST_LTE/GTE — 최근 N봉 rolling min/max 비교 */
|
||||
@@ -98,7 +100,9 @@ export const CONDITION_LABEL: Record<string, string> = {
|
||||
CLOUD_BREAK_UP: '구름 상향 돌파', CLOUD_BREAK_DOWN: '구름 하향 돌파',
|
||||
SPAN1_GT_SPAN2: '선행1>선행2', SPAN1_LT_SPAN2: '선행1<선행2',
|
||||
SPAN1_CROSS_UP_SPAN2: '선행1 상향교차', SPAN1_CROSS_DOWN_SPAN2: '선행1 하향교차',
|
||||
LAGGING_GT_PRICE: '후행>종가', LAGGING_LT_PRICE: '후행<종가',
|
||||
LAGGING_GT_PRICE: '후행>26봉전 종가', LAGGING_LT_PRICE: '후행<26봉전 종가',
|
||||
LAGGING_ABOVE_PRIOR_CLOUD: '후행>26봉전 구름',
|
||||
VOLUME_GT_MA_RATIO: '거래량 ≥ MA×비율',
|
||||
};
|
||||
|
||||
const COMMON_CONDITIONS = ['GT', 'LT', 'GTE', 'LTE', 'EQ', 'NEQ', 'CROSS_UP', 'CROSS_DOWN', 'SLOPE_UP', 'SLOPE_DOWN', 'DIFF_GT', 'DIFF_LT', 'HOLD_N_DAYS'];
|
||||
@@ -112,7 +116,7 @@ export const INDICATOR_CONDITIONS: Record<string, string[]> = {
|
||||
TRIX: COMMON_CONDITIONS, VOLUME_OSC: COMMON_CONDITIONS, VR: COMMON_CONDITIONS,
|
||||
DISPARITY: COMMON_CONDITIONS, PSYCHOLOGICAL: COMMON_CONDITIONS,
|
||||
NEW_PSYCHOLOGICAL: COMMON_CONDITIONS, INVEST_PSYCHOLOGICAL: COMMON_CONDITIONS,
|
||||
VOLUME: COMMON_CONDITIONS,
|
||||
VOLUME: [...COMMON_CONDITIONS, 'VOLUME_GT_MA_RATIO'],
|
||||
MA: COMMON_CONDITIONS, EMA: COMMON_CONDITIONS,
|
||||
BOLLINGER: COMMON_CONDITIONS,
|
||||
DONCHIAN: COMMON_CONDITIONS,
|
||||
@@ -122,7 +126,7 @@ export const INDICATOR_CONDITIONS: Record<string, string[]> = {
|
||||
...COMMON_CONDITIONS,
|
||||
'ABOVE_CLOUD', 'BELOW_CLOUD', 'IN_CLOUD', 'CLOUD_BREAK_UP', 'CLOUD_BREAK_DOWN',
|
||||
'SPAN1_GT_SPAN2', 'SPAN1_LT_SPAN2', 'SPAN1_CROSS_UP_SPAN2', 'SPAN1_CROSS_DOWN_SPAN2',
|
||||
'LAGGING_GT_PRICE', 'LAGGING_LT_PRICE',
|
||||
'LAGGING_GT_PRICE', 'LAGGING_LT_PRICE', 'LAGGING_ABOVE_PRIOR_CLOUD',
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user