전략템플릿 로직 수정
This commit is contained in:
@@ -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 '';
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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: '9·20일 신고가 돌파 + ADX·거래량 필터 (기본: 보통)',
|
||||
description: '20일 신고가 돌파 (기본: 완화 — ADX·거래량 없음)',
|
||||
build: () => buildNewHigh920BuyTree(def),
|
||||
},
|
||||
{
|
||||
kind: 'composite',
|
||||
signal: 'sell',
|
||||
label: '9·20일 신저가 매도',
|
||||
description: '9·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),
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user