258 lines
9.2 KiB
TypeScript
258 lines
9.2 KiB
TypeScript
import type { LogicNode } from './strategyTypes';
|
|
import { genId, type DefType } from './strategyEditorShared';
|
|
import { nhPriorField, nlPriorField } from './priceExtremeIndicators';
|
|
import {
|
|
buildInflection33BuyTree,
|
|
buildInflection33SellTree,
|
|
} from './inflection33Strategy';
|
|
import {
|
|
buildNewHigh920BuyTree,
|
|
buildNewLow920SellTree,
|
|
} from './priceExtremeBreakoutPair';
|
|
|
|
export type SimpleStrategyTemplate = {
|
|
kind: 'simple';
|
|
signal: 'buy' | 'sell';
|
|
label: string;
|
|
description?: string;
|
|
ind: string;
|
|
cond: string;
|
|
l: string;
|
|
r: string;
|
|
period?: number;
|
|
};
|
|
|
|
export type CompositeStrategyTemplate = {
|
|
kind: 'composite';
|
|
signal: 'buy' | 'sell';
|
|
label: string;
|
|
description: string;
|
|
build: () => LogicNode;
|
|
};
|
|
|
|
export type StrategyTemplateDef = SimpleStrategyTemplate | CompositeStrategyTemplate;
|
|
|
|
/** 내장 템플릿 숨김·복원용 안정 ID */
|
|
export function getBuiltinTemplateId(tmpl: StrategyTemplateDef): string {
|
|
const slug = tmpl.label.trim().replace(/\s+/g, '_');
|
|
return `builtin:${tmpl.signal}:${slug}`;
|
|
}
|
|
|
|
function condition(
|
|
indicatorType: string,
|
|
conditionType: string,
|
|
leftField: string,
|
|
rightField: string,
|
|
candleRange = 1,
|
|
period?: number,
|
|
): LogicNode {
|
|
return {
|
|
id: genId(),
|
|
type: 'CONDITION',
|
|
condition: {
|
|
indicatorType,
|
|
conditionType,
|
|
leftField,
|
|
rightField,
|
|
candleRange,
|
|
...(period != null ? { period } : null),
|
|
},
|
|
};
|
|
}
|
|
|
|
function andTree(...children: LogicNode[]): LogicNode {
|
|
return { id: genId(), type: 'AND', children };
|
|
}
|
|
|
|
/** 돈천 채널 — N일 최고가 상향 돌파 */
|
|
function donchianBreakoutBuy(period: number): LogicNode {
|
|
return condition('DONCHIAN', 'CROSS_UP', 'CLOSE_PRICE', `DC_UPPER_${period}`);
|
|
}
|
|
|
|
/** 돈천 채널 — N일 최저가 하향 이탈 */
|
|
function donchianBreakdownSell(period: number): LogicNode {
|
|
return condition('DONCHIAN', 'CROSS_DOWN', 'CLOSE_PRICE', `DC_LOWER_${period}`);
|
|
}
|
|
|
|
/** N일 신고가 돌파 — 종가가 직전 N봉 최고가를 상향 돌파 */
|
|
function newHighBreakoutBuy(period: number): LogicNode {
|
|
return condition('NEW_HIGH', 'CROSS_UP', 'CLOSE_PRICE', nhPriorField(period), 1, period);
|
|
}
|
|
|
|
/** N일 신저가 이탈 — 종가가 직전 N봉 최저가를 하향 이탈 */
|
|
function newLowBreakdownSell(period: number): LogicNode {
|
|
return condition('NEW_LOW', 'CROSS_DOWN', 'CLOSE_PRICE', nlPriorField(period), 1, period);
|
|
}
|
|
|
|
/** 일목균형표 — 전환선(9일) / 기준선(26일) 골든·데드크로스 */
|
|
function ichimokuTenkanKijunCross(signal: 'buy' | 'sell'): LogicNode {
|
|
return condition(
|
|
'ICHIMOKU',
|
|
signal === 'buy' ? 'CROSS_UP' : 'CROSS_DOWN',
|
|
'CONVERSION_LINE',
|
|
'BASE_LINE',
|
|
);
|
|
}
|
|
|
|
/** @deprecated inflection33Strategy.buildInflection33BuyTree 사용 */
|
|
export function build33InflectionBuyTree(def?: DefType): LogicNode {
|
|
return buildInflection33BuyTree(def ?? { maLines: [5, 10, 20, 60, 120] } as DefType);
|
|
}
|
|
|
|
/** @deprecated inflection33Strategy.buildInflection33SellTree 사용 */
|
|
export function build33InflectionSellTree(def?: DefType): LogicNode {
|
|
return buildInflection33SellTree(def ?? { maLines: [5, 10, 20, 60, 120] } as DefType);
|
|
}
|
|
|
|
export function getStrategyTemplates(def: DefType): StrategyTemplateDef[] {
|
|
const maShort = `MA${def.maLines[0]}`;
|
|
const maMid = `MA${def.maLines[1]}`;
|
|
|
|
return [
|
|
{ kind: 'simple', ind: 'MA', cond: 'CROSS_UP', l: maShort, r: maMid, signal: 'buy', label: 'MA 골든크로스 매수' },
|
|
{ kind: 'simple', ind: 'MA', cond: 'CROSS_DOWN', l: maShort, r: maMid, signal: 'sell', label: 'MA 데드크로스 매도' },
|
|
{ kind: 'simple', ind: 'RSI', cond: 'LTE', l: 'RSI_VALUE', r: 'OVERSOLD_30', signal: 'buy', label: 'RSI 과매도 매수' },
|
|
{ kind: 'simple', ind: 'RSI', cond: 'GTE', l: 'RSI_VALUE', r: 'OVERBOUGHT_70', signal: 'sell', label: 'RSI 과매수 매도' },
|
|
{ kind: 'simple', ind: 'MACD', cond: 'CROSS_UP', l: 'MACD_LINE', r: 'SIGNAL_LINE', signal: 'buy', label: 'MACD 상향 돌파' },
|
|
{ kind: 'simple', ind: 'MACD', cond: 'CROSS_DOWN', l: 'MACD_LINE', r: 'SIGNAL_LINE', signal: 'sell', label: 'MACD 하향 돌파' },
|
|
{ kind: 'simple', ind: 'BOLLINGER', cond: 'CROSS_UP', l: 'CLOSE_PRICE', r: 'LOWER_BAND', signal: 'buy', label: '볼린저 하단 돌파 매수' },
|
|
{ kind: 'simple', ind: 'BOLLINGER', cond: 'CROSS_UP', l: 'CLOSE_PRICE', r: 'UPPER_BAND', signal: 'sell', label: '볼린저 상단 돌파 매도' },
|
|
{ kind: 'simple', ind: 'CCI', cond: 'CROSS_UP', l: 'CCI_VALUE', r: 'OVERSOLD_NEG100', signal: 'buy', label: 'CCI 과매도 반등' },
|
|
{ kind: 'simple', ind: 'PSYCHOLOGICAL', cond: 'CROSS_UP', l: 'PSY_VALUE', r: 'OVERSOLD_25', signal: 'buy', label: '심리도 과매도 반등 매수' },
|
|
{ kind: 'simple', ind: 'STOCHASTIC', cond: 'CROSS_UP', l: 'STOCH_K', r: 'STOCH_D', signal: 'buy', label: '스토캐스틱 골든크로스' },
|
|
{ kind: 'simple', ind: 'ADX', cond: 'CROSS_UP', l: 'ADX_VALUE', r: 'ADX_25', signal: 'buy', label: 'ADX 중앙선 상향 돌파' },
|
|
{
|
|
kind: 'simple',
|
|
ind: 'NEW_HIGH',
|
|
cond: 'CROSS_UP',
|
|
l: 'CLOSE_PRICE',
|
|
r: nhPriorField(9),
|
|
period: 9,
|
|
signal: 'buy',
|
|
label: '9일 신고가 돌파',
|
|
description: '종가가 직전 9봉 최고가 이상 — 단기 박스권 상단 돌파 매수',
|
|
},
|
|
{
|
|
kind: 'simple',
|
|
ind: 'NEW_HIGH',
|
|
cond: 'CROSS_UP',
|
|
l: 'CLOSE_PRICE',
|
|
r: nhPriorField(20),
|
|
period: 20,
|
|
signal: 'buy',
|
|
label: '20일 신고가 돌파',
|
|
description: '종가가 직전 20봉 최고가 이상 — 한 달 박스 돌파 매수',
|
|
},
|
|
{
|
|
kind: 'simple',
|
|
ind: 'NEW_LOW',
|
|
cond: 'CROSS_DOWN',
|
|
l: 'CLOSE_PRICE',
|
|
r: nlPriorField(9),
|
|
period: 9,
|
|
signal: 'sell',
|
|
label: '9일 신저가 이탈',
|
|
description: '종가가 직전 9봉 최저가 이하 — 단기 지지 붕괴 매도/손절',
|
|
},
|
|
{
|
|
kind: 'simple',
|
|
ind: 'NEW_LOW',
|
|
cond: 'CROSS_DOWN',
|
|
l: 'CLOSE_PRICE',
|
|
r: nlPriorField(20),
|
|
period: 20,
|
|
signal: 'sell',
|
|
label: '20일 신저가 이탈',
|
|
description: '종가가 직전 20봉 최저가 이하 — 추세 이탈 청산',
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'buy',
|
|
label: '9·20일 신고가 매수',
|
|
description: '20일 신고가 돌파 (기본: 완화 — ADX·거래량 없음)',
|
|
build: () => buildNewHigh920BuyTree(def),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'sell',
|
|
label: '9·20일 신저가 매도',
|
|
description: '20일 신저가 이탈 (기본: 완화 — ADX·거래량 없음)',
|
|
build: () => buildNewLow920SellTree(def),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'buy',
|
|
label: '33변곡 매수',
|
|
description: '33 EMA 상승 변곡 + 33봉 종가 채널 상향 돌파 (기본: 완화)',
|
|
build: () => build33InflectionBuyTree(def),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'sell',
|
|
label: '33변곡 매도',
|
|
description: '33 EMA 하락 변곡 이탈 OR 33봉 종가 채널 하향 이탈',
|
|
build: () => build33InflectionSellTree(def),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'buy',
|
|
label: '돈천 채널(20일) 매수',
|
|
description: '종가가 직전 20거래일 최고가(상단선)를 상향 돌파 — 한 달 박스권 돌파',
|
|
build: () => donchianBreakoutBuy(20),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'sell',
|
|
label: '돈천 채널(20일) 매도',
|
|
description: '종가가 직전 20거래일 최저가(하단선)를 하향 이탈 — 추세 이탈 청산',
|
|
build: () => donchianBreakdownSell(20),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'buy',
|
|
label: '터틀 S1 매수',
|
|
description: '단기 시스템 — 20일 최고가 돌파 진입 (리처드 데니스 터틀 트레이딩)',
|
|
build: () => donchianBreakoutBuy(20),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'sell',
|
|
label: '터틀 S1 청산',
|
|
description: '단기 시스템 — 10일 최저가 이탈 시 즉시 청산 (진입보다 짧은 손절)',
|
|
build: () => donchianBreakdownSell(10),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'buy',
|
|
label: '터틀 S2 매수',
|
|
description: '장기 시스템 — 55일 최고가 돌파 진입 (큰 추세 포착)',
|
|
build: () => donchianBreakoutBuy(55),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'sell',
|
|
label: '터틀 S2 청산',
|
|
description: '장기 시스템 — 20일 최저가 이탈 시 청산 (S1보다 여유, S2보다 빠른 탈출)',
|
|
build: () => donchianBreakdownSell(20),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'buy',
|
|
label: '일목 전환/기준선 매수',
|
|
description: '전환선(9일 중간값)이 기준선(26일 중간값)을 상향 돌파 — 골든크로스',
|
|
build: () => ichimokuTenkanKijunCross('buy'),
|
|
},
|
|
{
|
|
kind: 'composite',
|
|
signal: 'sell',
|
|
label: '일목 전환/기준선 매도',
|
|
description: '전환선(9일)이 기준선(26일)을 하향 이탈 — 데드크로스',
|
|
build: () => ichimokuTenkanKijunCross('sell'),
|
|
},
|
|
];
|
|
}
|
|
|
|
export function simpleTemplateToNode(tmpl: SimpleStrategyTemplate): LogicNode {
|
|
return condition(tmpl.ind, tmpl.cond, tmpl.l, tmpl.r, 1, tmpl.period);
|
|
}
|