일목균형표 전략조건 수정
This commit is contained in:
@@ -695,13 +695,17 @@ public class LiveConditionStatusService {
|
|||||||
primarySeries.getEndIndex() - evalCount + 1);
|
primarySeries.getEndIndex() - evalCount + 1);
|
||||||
|
|
||||||
List<BacktestResponse.Signal> signals = new ArrayList<>();
|
List<BacktestResponse.Signal> signals = new ArrayList<>();
|
||||||
|
boolean inPosition = false;
|
||||||
for (int i = startIdx; i <= primarySeries.getEndIndex(); i++) {
|
for (int i = startIdx; i <= primarySeries.getEndIndex(); i++) {
|
||||||
long barTimeSec = barOpenEpochSec(primarySeries, i);
|
long barTimeSec = barOpenEpochSec(primarySeries, i);
|
||||||
double close = primarySeries.getBar(i).getClosePrice().doubleValue();
|
double close = primarySeries.getBar(i).getClosePrice().doubleValue();
|
||||||
|
|
||||||
Boolean entryMet = evaluateOverallRuleOnChart(
|
Boolean entryMet = evaluateOverallRuleOnChart(
|
||||||
buyDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
|
buyDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
|
||||||
if (Boolean.TRUE.equals(entryMet)) {
|
Boolean exitMet = evaluateOverallRuleOnChart(
|
||||||
|
sellDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
|
||||||
|
|
||||||
|
if (!inPosition && Boolean.TRUE.equals(entryMet)) {
|
||||||
signals.add(BacktestResponse.Signal.builder()
|
signals.add(BacktestResponse.Signal.builder()
|
||||||
.time(barTimeSec)
|
.time(barTimeSec)
|
||||||
.type("BUY")
|
.type("BUY")
|
||||||
@@ -709,11 +713,8 @@ public class LiveConditionStatusService {
|
|||||||
.barIndex(i)
|
.barIndex(i)
|
||||||
.quantity(0)
|
.quantity(0)
|
||||||
.build());
|
.build());
|
||||||
}
|
inPosition = true;
|
||||||
|
} else if (inPosition && Boolean.TRUE.equals(exitMet)) {
|
||||||
Boolean exitMet = evaluateOverallRuleOnChart(
|
|
||||||
sellDsl, primarySeries, seriesOverrides, market, params, visual, barTimeSec);
|
|
||||||
if (Boolean.TRUE.equals(exitMet)) {
|
|
||||||
signals.add(BacktestResponse.Signal.builder()
|
signals.add(BacktestResponse.Signal.builder()
|
||||||
.time(barTimeSec)
|
.time(barTimeSec)
|
||||||
.type("SELL")
|
.type("SELL")
|
||||||
@@ -721,6 +722,7 @@ public class LiveConditionStatusService {
|
|||||||
.barIndex(i)
|
.barIndex(i)
|
||||||
.quantity(0)
|
.quantity(0)
|
||||||
.build());
|
.build());
|
||||||
|
inPosition = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return signals;
|
return signals;
|
||||||
|
|||||||
@@ -504,6 +504,7 @@ public class StrategyDslToTa4jAdapter {
|
|||||||
case "LAGGING_GT_PRICE" -> buildChikouVsPriorCloseRule(series, indParams, true);
|
case "LAGGING_GT_PRICE" -> buildChikouVsPriorCloseRule(series, indParams, true);
|
||||||
case "LAGGING_LT_PRICE" -> buildChikouVsPriorCloseRule(series, indParams, false);
|
case "LAGGING_LT_PRICE" -> buildChikouVsPriorCloseRule(series, indParams, false);
|
||||||
case "LAGGING_ABOVE_PRIOR_CLOUD" -> buildChikouAbovePriorCloudRule(series, indParams);
|
case "LAGGING_ABOVE_PRIOR_CLOUD" -> buildChikouAbovePriorCloudRule(series, indParams);
|
||||||
|
case "CHIKOU_CROSS_ABOVE_PRIOR_CLOUD" -> buildChikouCrossAbovePriorCloudRule(series, indParams);
|
||||||
case "VOLUME_GT_MA_RATIO" -> {
|
case "VOLUME_GT_MA_RATIO" -> {
|
||||||
double ratio = cond.path("compareValue").asDouble(1.5);
|
double ratio = cond.path("compareValue").asDouble(1.5);
|
||||||
int len = intP(indParams, "length", 5);
|
int len = intP(indParams, "length", 5);
|
||||||
@@ -1054,6 +1055,15 @@ public class StrategyDslToTa4jAdapter {
|
|||||||
return new OverIndicatorRule(close, priorCloudTop);
|
return new OverIndicatorRule(close, priorCloudTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 후행스팬이 26봉 전 구름 상단을 상향 돌파하는 순간 (1회성 진입 트리거) */
|
||||||
|
private Rule buildChikouCrossAbovePriorCloudRule(BarSeries s, Map<String, Object> p) {
|
||||||
|
int d = ichimokuChikouDisplacement(p);
|
||||||
|
ClosePriceIndicator close = new ClosePriceIndicator(s);
|
||||||
|
NumericIndicator cloudTop = NumericIndicator.of(ichimokuSpanA(s, p)).max(ichimokuSpanB(s, p));
|
||||||
|
Indicator<Num> priorCloudTop = new PreviousValueIndicator(cloudTop, d);
|
||||||
|
return new CrossedUpIndicatorRule(close, priorCloudTop);
|
||||||
|
}
|
||||||
|
|
||||||
/** 종가가 구름 위 */
|
/** 종가가 구름 위 */
|
||||||
private Rule buildCloudAboveRule(BarSeries s, Map<String, Object> p) {
|
private Rule buildCloudAboveRule(BarSeries s, Map<String, Object> p) {
|
||||||
NumericIndicator cloudTop = NumericIndicator.of(ichimokuSpanA(s, p)).max(ichimokuSpanB(s, p));
|
NumericIndicator cloudTop = NumericIndicator.of(ichimokuSpanA(s, p)).max(ichimokuSpanB(s, p));
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ const ICHIMOKU_CONDS = [
|
|||||||
{ v: 'LAGGING_GT_PRICE', l: '후행스팬 > 26봉전 종가' },
|
{ v: 'LAGGING_GT_PRICE', l: '후행스팬 > 26봉전 종가' },
|
||||||
{ v: 'LAGGING_LT_PRICE', l: '후행스팬 < 26봉전 종가' },
|
{ v: 'LAGGING_LT_PRICE', l: '후행스팬 < 26봉전 종가' },
|
||||||
{ v: 'LAGGING_ABOVE_PRIOR_CLOUD', l: '후행스팬 > 26봉전 구름' },
|
{ v: 'LAGGING_ABOVE_PRIOR_CLOUD', l: '후행스팬 > 26봉전 구름' },
|
||||||
|
{ v: 'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD', l: '후행스팬 26봉전 구름 상향돌파' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// ─── 지표별 fieldOptions 빌더 ────────────────────────────────────────────────
|
// ─── 지표별 fieldOptions 빌더 ────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -143,12 +143,12 @@ export const STABLE_STRATEGY_PALETTE_ITEMS: StableStrategyPaletteMeta[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: ICHIMOKU_SANYAKU_BUY, label: '일목 삼역호전 매수', strategyId: 'ICHIMOKU_SANYAKU', mode: 'buy',
|
value: ICHIMOKU_SANYAKU_BUY, label: '일목 삼역호전 매수', strategyId: 'ICHIMOKU_SANYAKU', mode: 'buy',
|
||||||
desc: '4단계 Full-AND — 전환·기준·구름·후행·MA60·거래량 (일목 산인 삼역好轉)',
|
desc: '4단계 Full-AND — 후행 구름 돌파 1회 트리거 + 삼역호전 필터',
|
||||||
periodHint: '9 / 26 / 52',
|
periodHint: '9 / 26 / 52',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: ICHIMOKU_SANYAKU_SELL, label: '일목 삼역호전 매도', strategyId: 'ICHIMOKU_SANYAKU', mode: 'sell',
|
value: ICHIMOKU_SANYAKU_SELL, label: '일목 삼역호전 매도', strategyId: 'ICHIMOKU_SANYAKU', mode: 'sell',
|
||||||
desc: '분할 청산 OR — 전환선 이탈 / 기준선·데드크로스 / 구름 하방',
|
desc: '전환·기준·구름 하향 돌파 이벤트 OR (유지조건 제외)',
|
||||||
periodHint: '9 / 26 / 52',
|
periodHint: '9 / 26 / 52',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -371,22 +371,23 @@ function buildIchimokuTrendSell(def: IndicatorPeriodDef, level: StableStrategyFi
|
|||||||
return wrapOrRoot(branches, pairMeta('ICHIMOKU_TREND', 'sell', level));
|
return wrapOrRoot(branches, pairMeta('ICHIMOKU_TREND', 'sell', level));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 일목 삼역호전(三役好轉) — 4단계 Full-AND 매수 */
|
/** 일목 삼역호전(三役好轉) — 4단계 Full-AND 매수 (1회성 트리거) */
|
||||||
function buildIchimokuSanyakuBuy(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
|
function buildIchimokuSanyakuBuy(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
|
||||||
const nodes: LogicNode[] = [
|
const nodes: LogicNode[] = [
|
||||||
// [1단계] 주가·단기 추세
|
// [트리거] 후행스팬 26봉전 구름 상향 돌파 — 삼역호전 최종 잠금 순간 1회
|
||||||
|
makeCondition('ICHIMOKU', 'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD', 'NONE', 'NONE', def),
|
||||||
|
// [1단계] 주가·단기 추세 (진입 봉 동시 충족)
|
||||||
makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def),
|
makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def),
|
||||||
makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'BASE_LINE', def),
|
makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'BASE_LINE', def),
|
||||||
makeCondition('ICHIMOKU', 'GT', 'CONVERSION_LINE', 'BASE_LINE', def),
|
makeCondition('ICHIMOKU', 'GT', 'CONVERSION_LINE', 'BASE_LINE', def),
|
||||||
// [2단계] 구름대·양운
|
// [2단계] 구름대·양운
|
||||||
makeCondition('ICHIMOKU', 'ABOVE_CLOUD', 'CLOSE_PRICE', 'NONE', def),
|
makeCondition('ICHIMOKU', 'ABOVE_CLOUD', 'CLOSE_PRICE', 'NONE', def),
|
||||||
makeCondition('ICHIMOKU', 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def),
|
makeCondition('ICHIMOKU', 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def),
|
||||||
// [3단계] 후행스팬 2중 잠금
|
// [3단계] 후행스팬 호전 유지
|
||||||
makeCondition('ICHIMOKU', 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def),
|
makeCondition('ICHIMOKU', 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def),
|
||||||
makeCondition('ICHIMOKU', 'LAGGING_ABOVE_PRIOR_CLOUD', 'NONE', 'NONE', def),
|
makeCondition('ICHIMOKU', 'LAGGING_ABOVE_PRIOR_CLOUD', 'NONE', 'NONE', def),
|
||||||
];
|
];
|
||||||
if (level !== 'relaxed') {
|
if (level !== 'relaxed') {
|
||||||
// [4단계] MA60 대세 + 거래량
|
|
||||||
nodes.push(makeCondition('MA', 'GT', 'CLOSE_PRICE', maField(60), def));
|
nodes.push(makeCondition('MA', 'GT', 'CLOSE_PRICE', maField(60), def));
|
||||||
if (level === 'strict') {
|
if (level === 'strict') {
|
||||||
nodes.push(makeVolumeMaRatioFilter(def, 1.5, 5));
|
nodes.push(makeVolumeMaRatioFilter(def, 1.5, 5));
|
||||||
@@ -397,16 +398,16 @@ function buildIchimokuSanyakuBuy(def: IndicatorPeriodDef, level: StableStrategyF
|
|||||||
return wrapAnd(nodes, pairMeta('ICHIMOKU_SANYAKU', 'buy', level));
|
return wrapAnd(nodes, pairMeta('ICHIMOKU_SANYAKU', 'buy', level));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 일목 삼역호전 — 분할 매도 OR (전환선 → 기준선/데드 → 구름) */
|
/** 일목 삼역호전 — 분할 청산 OR (돌파 이벤트만, 유지조건 제외) */
|
||||||
function buildIchimokuSanyakuSell(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
|
function buildIchimokuSanyakuSell(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
|
||||||
void level;
|
void level;
|
||||||
return wrapOrRoot([
|
return wrapOrRoot([
|
||||||
makeCondition('ICHIMOKU', 'LT', 'CLOSE_PRICE', 'CONVERSION_LINE', def),
|
makeCondition('ICHIMOKU', 'CROSS_DOWN', 'CLOSE_PRICE', 'CONVERSION_LINE', def),
|
||||||
wrapOr([
|
wrapOr([
|
||||||
makeCondition('ICHIMOKU', 'LT', 'CLOSE_PRICE', 'BASE_LINE', def),
|
makeCondition('ICHIMOKU', 'CROSS_DOWN', 'CLOSE_PRICE', 'BASE_LINE', def),
|
||||||
makeCondition('ICHIMOKU', 'CROSS_DOWN', 'CONVERSION_LINE', 'BASE_LINE', def),
|
makeCondition('ICHIMOKU', 'CROSS_DOWN', 'CONVERSION_LINE', 'BASE_LINE', def),
|
||||||
]),
|
]),
|
||||||
makeCondition('ICHIMOKU', 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE', def),
|
makeCondition('ICHIMOKU', 'CLOUD_BREAK_DOWN', 'CLOSE_PRICE', 'NONE', def),
|
||||||
], pairMeta('ICHIMOKU_SANYAKU', 'sell', level));
|
], pairMeta('ICHIMOKU_SANYAKU', 'sell', level));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,9 +564,11 @@ export function inferStableStrategyFilterLevel(node: LogicNode): StableStrategyF
|
|||||||
return 'relaxed';
|
return 'relaxed';
|
||||||
}
|
}
|
||||||
if (id === 'ICHIMOKU_SANYAKU') {
|
if (id === 'ICHIMOKU_SANYAKU') {
|
||||||
|
const hasTrigger = conds.some(c => c.conditionType === 'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD');
|
||||||
const hasMa = conds.some(c => c.indicatorType === 'MA' && c.rightField === maField(60));
|
const hasMa = conds.some(c => c.indicatorType === 'MA' && c.rightField === maField(60));
|
||||||
const hasVolRatio = conds.some(c => c.conditionType === 'VOLUME_GT_MA_RATIO');
|
const hasVolRatio = conds.some(c => c.conditionType === 'VOLUME_GT_MA_RATIO');
|
||||||
const hasVol = conds.some(c => c.indicatorType === 'VOLUME');
|
const hasVol = conds.some(c => c.indicatorType === 'VOLUME');
|
||||||
|
if (!hasTrigger) return 'balanced';
|
||||||
if (!hasMa && !hasVol) return 'relaxed';
|
if (!hasMa && !hasVol) return 'relaxed';
|
||||||
if (hasVolRatio) return 'strict';
|
if (hasVolRatio) return 'strict';
|
||||||
if (hasMa && hasVol) return 'balanced';
|
if (hasMa && hasVol) return 'balanced';
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ function describeConditionType(
|
|||||||
case 'LAGGING_GT_PRICE': return '후행스팬(현재 종가)이 26봉 전 종가보다 위에 있는 경우';
|
case 'LAGGING_GT_PRICE': return '후행스팬(현재 종가)이 26봉 전 종가보다 위에 있는 경우';
|
||||||
case 'LAGGING_LT_PRICE': return '후행스팬(현재 종가)이 26봉 전 종가보다 아래에 있는 경우';
|
case 'LAGGING_LT_PRICE': return '후행스팬(현재 종가)이 26봉 전 종가보다 아래에 있는 경우';
|
||||||
case 'LAGGING_ABOVE_PRIOR_CLOUD': return '후행스팬이 26봉 전 구름대(선행스팬)보다 위에 있는 경우';
|
case 'LAGGING_ABOVE_PRIOR_CLOUD': return '후행스팬이 26봉 전 구름대(선행스팬)보다 위에 있는 경우';
|
||||||
|
case 'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD': return '후행스팬이 26봉 전 구름 상단을 상향 돌파하는 순간';
|
||||||
case 'VOLUME_GT_MA_RATIO': return '거래량이 거래량 이동평균 대비 설정 비율 이상인 경우';
|
case 'VOLUME_GT_MA_RATIO': return '거래량이 거래량 이동평균 대비 설정 비율 이상인 경우';
|
||||||
default: {
|
default: {
|
||||||
const label = CONDITION_LABEL[conditionType] ?? conditionType;
|
const label = CONDITION_LABEL[conditionType] ?? conditionType;
|
||||||
|
|||||||
@@ -432,6 +432,7 @@ const ICHIMOKU_CONDS = [
|
|||||||
{ v: 'LAGGING_GT_PRICE', l: '후행스팬 > 26봉전 종가' },
|
{ v: 'LAGGING_GT_PRICE', l: '후행스팬 > 26봉전 종가' },
|
||||||
{ v: 'LAGGING_LT_PRICE', l: '후행스팬 < 26봉전 종가' },
|
{ v: 'LAGGING_LT_PRICE', l: '후행스팬 < 26봉전 종가' },
|
||||||
{ v: 'LAGGING_ABOVE_PRIOR_CLOUD', l: '후행스팬 > 26봉전 구름' },
|
{ v: 'LAGGING_ABOVE_PRIOR_CLOUD', l: '후행스팬 > 26봉전 구름' },
|
||||||
|
{ v: 'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD', l: '후행스팬 26봉전 구름 상향돌파' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// ─── 지표별 fieldOptions 빌더 ────────────────────────────────────────────────
|
// ─── 지표별 fieldOptions 빌더 ────────────────────────────────────────────────
|
||||||
@@ -874,6 +875,8 @@ export const applyCondTypeDefaults = (cond: ConditionDSL, newCondType: string, D
|
|||||||
updated.leftField = 'LAGGING_SPAN'; updated.rightField = 'CLOSE_PRICE';
|
updated.leftField = 'LAGGING_SPAN'; updated.rightField = 'CLOSE_PRICE';
|
||||||
} else if (newCondType === 'LAGGING_ABOVE_PRIOR_CLOUD') {
|
} else if (newCondType === 'LAGGING_ABOVE_PRIOR_CLOUD') {
|
||||||
updated.leftField = 'NONE'; updated.rightField = 'NONE';
|
updated.leftField = 'NONE'; updated.rightField = 'NONE';
|
||||||
|
} else if (newCondType === 'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD') {
|
||||||
|
updated.leftField = 'NONE'; updated.rightField = 'NONE';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cond.indicatorType === 'VOLUME' && newCondType === 'VOLUME_GT_MA_RATIO') {
|
if (cond.indicatorType === 'VOLUME' && newCondType === 'VOLUME_GT_MA_RATIO') {
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export const CONDITION_LABEL: Record<string, string> = {
|
|||||||
SPAN1_CROSS_UP_SPAN2: '선행1 상향교차', SPAN1_CROSS_DOWN_SPAN2: '선행1 하향교차',
|
SPAN1_CROSS_UP_SPAN2: '선행1 상향교차', SPAN1_CROSS_DOWN_SPAN2: '선행1 하향교차',
|
||||||
LAGGING_GT_PRICE: '후행>26봉전 종가', LAGGING_LT_PRICE: '후행<26봉전 종가',
|
LAGGING_GT_PRICE: '후행>26봉전 종가', LAGGING_LT_PRICE: '후행<26봉전 종가',
|
||||||
LAGGING_ABOVE_PRIOR_CLOUD: '후행>26봉전 구름',
|
LAGGING_ABOVE_PRIOR_CLOUD: '후행>26봉전 구름',
|
||||||
|
CHIKOU_CROSS_ABOVE_PRIOR_CLOUD: '후행 구름 상향돌파',
|
||||||
VOLUME_GT_MA_RATIO: '거래량 ≥ MA×비율',
|
VOLUME_GT_MA_RATIO: '거래량 ≥ MA×비율',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,6 +128,7 @@ export const INDICATOR_CONDITIONS: Record<string, string[]> = {
|
|||||||
'ABOVE_CLOUD', 'BELOW_CLOUD', 'IN_CLOUD', 'CLOUD_BREAK_UP', 'CLOUD_BREAK_DOWN',
|
'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',
|
'SPAN1_GT_SPAN2', 'SPAN1_LT_SPAN2', 'SPAN1_CROSS_UP_SPAN2', 'SPAN1_CROSS_DOWN_SPAN2',
|
||||||
'LAGGING_GT_PRICE', 'LAGGING_LT_PRICE', 'LAGGING_ABOVE_PRIOR_CLOUD',
|
'LAGGING_GT_PRICE', 'LAGGING_LT_PRICE', 'LAGGING_ABOVE_PRIOR_CLOUD',
|
||||||
|
'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user