전략템플릿 로직 수정

This commit is contained in:
Macbook
2026-06-24 01:42:52 +09:00
parent 0544971fd8
commit 1ed6a11b36
4 changed files with 63 additions and 42 deletions
+8 -8
View File
@@ -16,7 +16,7 @@ export const DEFAULT_EMA_SLOPE_BARS = 2;
/** 필터 강도 — 전략편집기에서 조절 */
export type Inflection33FilterLevel = 'strict' | 'balanced' | 'relaxed';
export const DEFAULT_INFLECTION33_FILTER_LEVEL: Inflection33FilterLevel = 'balanced';
export const DEFAULT_INFLECTION33_FILTER_LEVEL: Inflection33FilterLevel = 'relaxed';
export const INFLECTION_33_FILTER_OPTIONS: {
value: Inflection33FilterLevel;
@@ -30,13 +30,13 @@ export const INFLECTION_33_FILTER_OPTIONS: {
},
{
value: 'balanced',
label: '보통 (권장)',
desc: 'EMA 2봉 변곡 + 종가 EMA 필터 — 기본 33변곡',
label: '보통',
desc: 'EMA 2봉 변곡 + 33봉 채널 돌파 — 종가 EMA 필터 없음',
},
{
value: 'relaxed',
label: '완화',
desc: 'EMA 1봉 변곡, 종가 EMA 필터 생략 — 매수 빈도↑, 매도는 채널 이탈만',
label: '완화 (권장)',
desc: 'EMA 1봉 변곡 + 채널 돌파 — 매수 빈도↑, 매도는 채널 이탈만',
},
];
@@ -64,7 +64,7 @@ const FILTER_PRESETS: Record<Inflection33FilterLevel, Inflection33Preset> = {
},
balanced: {
slopeBars: 2,
requireCloseAboveEma: true,
requireCloseAboveEma: false,
buyAdx: null,
sellAdx: null,
volume: false,
@@ -428,10 +428,10 @@ export function inflection33SummaryText(
export function inflection33PaletteDesc(value: string): string {
if (isInflection33BuyPaletteValue(value)) {
return '33 EMA 상승 변곡 + 종가 채널 돌파 + 조절 가능 필터 (기본: 보통)';
return '33 EMA 상승 변곡 + 종가 채널 돌파 + 조절 가능 필터 (기본: 완화)';
}
if (isInflection33SellPaletteValue(value)) {
return '33 EMA 하락 이탈 OR 종가 채널 하향 이탈 + 조절 가능 필터 (기본: 보통)';
return '33 EMA 하락 이탈 OR 종가 채널 하향 이탈 + 조절 가능 필터 (기본: 완화)';
}
return '';
}
@@ -15,7 +15,7 @@ export const DEFAULT_LONG_PERIOD = 20;
/** 필터 강도 — 전략편집기에서 조절 */
export type PriceExtremeFilterLevel = 'strict' | 'balanced' | 'relaxed';
export const DEFAULT_FILTER_LEVEL: PriceExtremeFilterLevel = 'balanced';
export const DEFAULT_FILTER_LEVEL: PriceExtremeFilterLevel = 'relaxed';
export const PRICE_EXTREME_FILTER_OPTIONS: {
value: PriceExtremeFilterLevel;
@@ -29,13 +29,13 @@ export const PRICE_EXTREME_FILTER_OPTIONS: {
},
{
value: 'balanced',
label: '보통 (권장)',
desc: 'ADX·거래량 완화 — 돌파 신호와 균형',
label: '보통',
desc: '20일 돌파 + ADX(≥18) — 가격·추세 2조건',
},
{
value: 'relaxed',
label: '완화',
desc: '가격 돌파만 — ADX·거래량 필터 없음',
label: '완화 (권장)',
desc: '20일 돌파만 — ADX·거래량·고저가 확인 없음',
},
];
@@ -50,7 +50,7 @@ type FilterPreset = {
const FILTER_PRESETS: Record<PriceExtremeFilterLevel, FilterPreset> = {
strict: { buyAdx: 25, sellAdx: 20, volume: true, shortCrossConfirm: true, highLowConfirm: true },
balanced: { buyAdx: 20, sellAdx: 15, volume: true, shortCrossConfirm: false, highLowConfirm: true },
balanced: { buyAdx: 18, sellAdx: 15, volume: false, shortCrossConfirm: false, highLowConfirm: false },
relaxed: { buyAdx: null, sellAdx: null, volume: false, shortCrossConfirm: false, highLowConfirm: false },
};
@@ -324,10 +324,10 @@ export function priceExtremePairSummaryText(
export function priceExtremePairPaletteDesc(value: string): string {
if (isNewHigh920BuyPaletteValue(value)) {
return '9·20일 신고가 돌파 + 조절 가능 ADX·거래량 필터 (기본: 보통)';
return '9·20일 신고가 돌파 + 조절 가능 ADX·거래량 필터 (기본: 완화)';
}
if (isNewLow920SellPaletteValue(value)) {
return '9·20일 신저가 이탈 + 조절 가능 ADX·거래량 필터 (기본: 보통)';
return '9·20일 신저가 이탈 + 조절 가능 ADX·거래량 필터 (기본: 완화)';
}
return '';
}
+43 -22
View File
@@ -7,7 +7,7 @@ import { genId } from './strategyNodeIds';
import { donchianUpperField, donchianLowerField } from './compositeIndicators';
export type StableStrategyFilterLevel = 'strict' | 'balanced' | 'relaxed';
export const DEFAULT_STABLE_FILTER_LEVEL: StableStrategyFilterLevel = 'balanced';
export const DEFAULT_STABLE_FILTER_LEVEL: StableStrategyFilterLevel = 'relaxed';
export type StableStrategyId =
| 'TURTLE_S1'
@@ -26,15 +26,15 @@ export const STABLE_STRATEGY_FILTER_OPTIONS: {
desc: string;
}[] = [
{ value: 'strict', label: '강함', desc: 'ADX·거래량 등 필터 최대 — 신호 적음·품질↑' },
{ value: 'balanced', label: '보통 (권장)', desc: '기본 추천 조합' },
{ value: 'relaxed', label: '완화', desc: '핵심 조건만 — 신호 빈도↑' },
{ value: 'balanced', label: '보통', desc: '핵심 + 보조 1~2조건 — 신호와 품질 균형' },
{ value: 'relaxed', label: '완화 (권장)', desc: '핵심 조건만 — 신호 빈도↑' },
];
type FilterFlags = { adx: number | null; volume: boolean };
const FILTER_FLAGS: Record<StableStrategyFilterLevel, FilterFlags> = {
strict: { adx: 25, volume: true },
balanced: { adx: 20, volume: false },
balanced: { adx: null, volume: false },
relaxed: { adx: null, volume: false },
};
@@ -148,7 +148,7 @@ export const STABLE_STRATEGY_PALETTE_ITEMS: StableStrategyPaletteMeta[] = [
},
{
value: ICHIMOKU_SANYAKU_BUY, label: '일목 삼역호전 매수', strategyId: 'ICHIMOKU_SANYAKU', mode: 'buy',
desc: '구름 돌파(현재봉) + 보조필터 N봉 내 존재 — 전후 봉 분산 허용, 트리거는 1회',
desc: '구름 돌파(현재봉) + 종가>전환(보통) — 필터 강도에 따라 전환>기준·후행 추가',
periodHint: '9 / 26 / 52',
},
{
@@ -158,17 +158,17 @@ export const STABLE_STRATEGY_PALETTE_ITEMS: StableStrategyPaletteMeta[] = [
},
{
value: ICHIMOKU_PERFECT_BUY, label: '일목 5원소 매수', strategyId: 'ICHIMOKU_PERFECT', mode: 'buy',
desc: '괘·주가·구름·후행 4중 AND — 전환>기준 + 구름 위 + 양운 + 후행>26봉전 종가',
desc: '괘·주가(구름) — 강함에서만 양운·후행 4중 AND',
periodHint: '9 / 26 / 52',
},
{
value: ICHIMOKU_PERFECT_SELL, label: '일목 5원소 매도', strategyId: 'ICHIMOKU_PERFECT', mode: 'sell',
desc: '괘·주가·구름·후행 4중 AND — 전환<기준 + 구름 아래 + 음운 + 후행<26봉전 종가',
desc: '괘·주가(구름) — 강함에서만 음운·후행 4중 AND',
periodHint: '9 / 26 / 52',
},
{
value: MACD_MOMENTUM_BUY, label: 'MACD 모멘텀 매수', strategyId: 'MACD_MOMENTUM', mode: 'buy',
desc: 'MACD 상향돌파 + EMA60 위 + ADX',
desc: 'MACD 상향돌파 — 보통: EMA60 위, 강함: +ADX·거래량',
periodHint: 'MACD + EMA60',
},
{
@@ -369,7 +369,7 @@ function buildMaTrendBuy(def: IndicatorPeriodDef, level: StableStrategyFilterLev
const nodes: LogicNode[] = [
makeCondition('MA', 'CROSS_UP', maField(short), maField(long), def),
];
if (level !== 'relaxed') {
if (level === 'strict') {
nodes.push(...appendTrendFilters([], level, def, 'buy'));
}
return wrapAnd(nodes, pairMeta('MA_TREND', 'buy', level));
@@ -450,6 +450,12 @@ function ichimokuSanyakuCoreFilters(def: IndicatorPeriodDef, level: StableStrate
if (level === 'relaxed') {
return [makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def)];
}
if (level === 'balanced') {
return [
makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def),
makeExistsIn('ICHIMOKU', 'GT', 'CONVERSION_LINE', 'BASE_LINE', def, w),
];
}
const nodes: LogicNode[] = [
makeExistsIn('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def, w),
makeExistsIn('ICHIMOKU', 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def, w),
@@ -496,7 +502,7 @@ function buildIchimokuPerfectBuy(def: IndicatorPeriodDef, level: StableStrategyF
makeCondition('ICHIMOKU', 'GT', 'CONVERSION_LINE', 'BASE_LINE', def),
makeCondition('ICHIMOKU', 'ABOVE_CLOUD', 'CLOSE_PRICE', 'NONE', def),
];
if (level !== 'relaxed') {
if (level === 'strict') {
nodes.push(makeCondition('ICHIMOKU', 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def));
nodes.push(makeCondition('ICHIMOKU', 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def));
}
@@ -508,7 +514,7 @@ function buildIchimokuPerfectSell(def: IndicatorPeriodDef, level: StableStrategy
makeCondition('ICHIMOKU', 'LT', 'CONVERSION_LINE', 'BASE_LINE', def),
makeCondition('ICHIMOKU', 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE', def),
];
if (level !== 'relaxed') {
if (level === 'strict') {
nodes.push(makeCondition('ICHIMOKU', 'SPAN1_LT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def));
nodes.push(makeCondition('ICHIMOKU', 'LAGGING_LT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def));
}
@@ -519,8 +525,10 @@ function buildMacdMomentumBuy(def: IndicatorPeriodDef, level: StableStrategyFilt
const nodes: LogicNode[] = [
makeCondition('MACD', 'CROSS_UP', 'MACD_LINE', 'SIGNAL_LINE', def),
];
if (level !== 'relaxed') {
if (level === 'balanced' || level === 'strict') {
nodes.push(makeCondition('EMA', 'GT', 'CLOSE_PRICE', emaField(60), def));
}
if (level === 'strict') {
nodes.push(...appendTrendFilters([], level, def, 'buy'));
}
return wrapAnd(nodes, pairMeta('MACD_MOMENTUM', 'buy', level));
@@ -637,15 +645,19 @@ export function stableStrategyFilterSummary(
if (strategyId === 'ICHIMOKU_SANYAKU') {
const w = ichimokuSanyakuWindow(level);
if (level === 'strict') parts.push(`돌파1회·필터N${w}·MA60·거래량`);
else if (level === 'balanced') parts.push(`돌파1회·필터N${w}·(전환>기준 OR 양운)`);
else if (level === 'balanced') parts.push(`돌파1회·종가>전환·전환>기준(N${w})`);
else parts.push('돌파1회·종가>전환');
}
if (strategyId === 'ICHIMOKU_PERFECT') {
if (level === 'relaxed') parts.push('괘·주가(구름) 2조건만');
else parts.push('괘·주가·구름·후행 4중 AND');
if (level === 'strict') parts.push('괘·주가·구름·후행 4중 AND');
else parts.push('괘·주가(구름) 2조건');
}
if (strategyId === 'MACD_MOMENTUM' && level === 'relaxed') parts.push('EMA60·ADX 없음');
if (strategyId === 'MA_TREND' && level === 'relaxed') parts.push('ADX 없음');
if (strategyId === 'MACD_MOMENTUM') {
if (level === 'relaxed') parts.push('MACD만');
else if (level === 'balanced') parts.push('MACD + EMA60');
else parts.push('MACD + EMA60 + ADX·거래량');
}
if (strategyId === 'MA_TREND' && level !== 'strict') parts.push('ADX 없음');
return parts.join(', ');
}
@@ -682,19 +694,28 @@ export function inferStableStrategyFilterLevel(node: LogicNode): StableStrategyF
const hasVolRatio = conds.some(c => c.conditionType === 'VOLUME_GT_MA_RATIO');
const hasMa = conds.some(c => c.indicatorType === 'MA' && c.rightField === maField(60));
const hasLagging = conds.some(c => c.conditionType === 'LAGGING_GT_PRICE');
const hasTenkanKijun = conds.some(c =>
c.conditionType === 'GT' && c.leftField === 'CONVERSION_LINE' && c.rightField === 'BASE_LINE',
);
if (hasVolRatio || hasLaggingCloud) return 'strict';
if (hasMa) return 'strict';
if (hasLagging && (hasCloudBreak || hasChikouCross) && hasWindow) return 'balanced';
if (hasLagging && (hasCloudBreak || hasChikouCross)) return 'balanced';
if (hasLagging && hasWindow) return 'strict';
if (hasTenkanKijun && hasWindow && !hasLagging) return 'balanced';
if (hasCloudBreak || hasChikouCross) return 'relaxed';
}
if (id === 'ICHIMOKU_PERFECT') {
const hasSpan = conds.some(c => c.conditionType === 'SPAN1_GT_SPAN2' || c.conditionType === 'SPAN1_LT_SPAN2');
const hasLagging = conds.some(c => c.conditionType === 'LAGGING_GT_PRICE' || c.conditionType === 'LAGGING_LT_PRICE');
if (!hasSpan && !hasLagging) return 'relaxed';
return 'balanced';
if (hasSpan && hasLagging) return 'strict';
return 'relaxed';
}
if (id === 'MACD_MOMENTUM') {
const hasEma = conds.some(c => c.rightField === emaField(60));
const hasAdx = conds.some(c => c.indicatorType === 'ADX');
if (hasAdx) return 'strict';
if (hasEma) return 'balanced';
return 'relaxed';
}
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') {
const adxLte = conds.find(c => c.indicatorType === 'ADX' && c.conditionType === 'LTE');
+4 -4
View File
@@ -118,7 +118,7 @@ export function getStrategyTemplates(def: DefType): StrategyTemplateDef[] {
{ 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: 'OVERBOUGHT_75', signal: 'buy', label: '심리도 상향 돌파' },
{ 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 중앙선 상향 돌파' },
{
@@ -169,21 +169,21 @@ export function getStrategyTemplates(def: DefType): StrategyTemplateDef[] {
kind: 'composite',
signal: 'buy',
label: '9·20일 신고가 매수',
description: '20일 신고가 돌파 + ADX·거래량 필터 (기본: 보통)',
description: '20일 신고가 돌파 (기본: 완화 — ADX·거래량 없음)',
build: () => buildNewHigh920BuyTree(def),
},
{
kind: 'composite',
signal: 'sell',
label: '9·20일 신저가 매도',
description: '20일 신저가 이탈 + ADX·거래량 필터 (기본: 보통)',
description: '20일 신저가 이탈 (기본: 완화 — ADX·거래량 없음)',
build: () => buildNewLow920SellTree(def),
},
{
kind: 'composite',
signal: 'buy',
label: '33변곡 매수',
description: '33 EMA 상승 변곡 + 종가 EMA 위 + 33봉 종가 채널 상향 돌파',
description: '33 EMA 상승 변곡 + 33봉 종가 채널 상향 돌파 (기본: 완화)',
build: () => build33InflectionBuyTree(def),
},
{