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); }