신심리도 추가, 투자심리도 수정
This commit is contained in:
@@ -101,7 +101,8 @@ public class IndicatorService {
|
||||
case "VolumeOscillator" -> calcVolumeOscillator(series, p);
|
||||
case "VR" -> calcVR(series, p);
|
||||
case "Disparity" -> calcDisparity(series, p);
|
||||
case "Psychological", "NewPsychological" -> calcPsychological(series, p);
|
||||
case "Psychological" -> calcPsychological(series, p);
|
||||
case "NewPsychological" -> calcNewPsychological(series, p);
|
||||
case "InvestPsychological"-> calcInvestPsychological(series, p);
|
||||
|
||||
default -> throw new IllegalArgumentException("지원하지 않는 지표: " + type);
|
||||
@@ -762,9 +763,14 @@ public class IndicatorService {
|
||||
return Map.of("plot0", psychologicalPoints(s, period, false));
|
||||
}
|
||||
|
||||
private Map<String, List<PlotPoint>> calcNewPsychological(BarSeries s, Map<String, Object> p) {
|
||||
int period = intP(p, "length", 10);
|
||||
return Map.of("plot0", newPsychologicalPoints(s, period));
|
||||
}
|
||||
|
||||
private Map<String, List<PlotPoint>> calcInvestPsychological(BarSeries s, Map<String, Object> p) {
|
||||
int period = intP(p, "length", 10);
|
||||
return Map.of("plot0", psychologicalPoints(s, period, true));
|
||||
return Map.of("plot0", psychologicalPoints(s, period, false));
|
||||
}
|
||||
|
||||
private List<PlotPoint> psychologicalPoints(BarSeries s, int period, boolean volumeWeighted) {
|
||||
@@ -805,6 +811,40 @@ public class IndicatorService {
|
||||
return pts;
|
||||
}
|
||||
|
||||
/** 신심리도: [상승일수×상승폭비율 − 하락일수×하락폭비율] × 100 / N */
|
||||
private List<PlotPoint> newPsychologicalPoints(BarSeries s, int period) {
|
||||
int n = s.getBarCount();
|
||||
ClosePriceIndicator close = new ClosePriceIndicator(s);
|
||||
List<PlotPoint> pts = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
Bar bar = s.getBar(i);
|
||||
long t = bar.getEndTime().getEpochSecond() - bar.getTimePeriod().getSeconds();
|
||||
if (i < period) {
|
||||
pts.add(PlotPoint.builder().time(t).value(null).build());
|
||||
continue;
|
||||
}
|
||||
double upSize = 0, downSize = 0;
|
||||
int upDay = 0, downDay = 0;
|
||||
for (int j = i - period + 1; j <= i; j++) {
|
||||
Double c = safe(close.getValue(j));
|
||||
Double cPrev = safe(close.getValue(j - 1));
|
||||
if (c == null || cPrev == null) continue;
|
||||
double diff = c - cPrev;
|
||||
if (diff > 0) {
|
||||
upSize += diff;
|
||||
upDay++;
|
||||
} else if (diff < 0) {
|
||||
downSize += -diff;
|
||||
downDay++;
|
||||
}
|
||||
}
|
||||
double total = upSize + downSize;
|
||||
Double val = total == 0 ? null : (upDay * (upSize / total) - downDay * (downSize / total)) * 100.0 / period;
|
||||
pts.add(PlotPoint.builder().time(t).value(val).build());
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
// ── EMA helper (double[], for TSI manual calc) ────────────────────────────
|
||||
|
||||
private double[] ema(double[] src, int len, int n) {
|
||||
|
||||
@@ -80,7 +80,7 @@ public class StrategyDslToTa4jAdapter {
|
||||
Map.entry("ICHIMOKU", "IchimokuCloud"),
|
||||
Map.entry("VOLUME_OSC", "VolumeOscillator"),
|
||||
Map.entry("PSYCHOLOGICAL", "Psychological"),
|
||||
Map.entry("NEW_PSYCHOLOGICAL", "Psychological"),
|
||||
Map.entry("NEW_PSYCHOLOGICAL", "NewPsychological"),
|
||||
Map.entry("INVEST_PSYCHOLOGICAL", "InvestPsychological"),
|
||||
Map.entry("BWI", "BBBandWidth"),
|
||||
Map.entry("VR", "VR"),
|
||||
@@ -700,17 +700,22 @@ public class StrategyDslToTa4jAdapter {
|
||||
}
|
||||
if (field.startsWith("PSY_VALUE_")) {
|
||||
int period = parseTrailingInt(field, "PSY_VALUE_", intP(p, "length", 12));
|
||||
return newPsychIndicator(s, period, false);
|
||||
return psychIndicator(s, period, false);
|
||||
}
|
||||
if (field.equals("PSY_VALUE") || field.equals("NEW_PSY_VALUE")
|
||||
|| indType.equals("PSYCHOLOGICAL") || indType.equals("NEW_PSYCHOLOGICAL"))
|
||||
return newPsychIndicator(s, intP(p, "length", 12), false);
|
||||
if (field.equals("PSY_VALUE") || indType.equals("PSYCHOLOGICAL"))
|
||||
return psychIndicator(s, intP(p, "length", 12), false);
|
||||
if (field.startsWith("NEW_PSY_VALUE_")) {
|
||||
int period = parseTrailingInt(field, "NEW_PSY_VALUE_", intP(p, "length", 10));
|
||||
return newSentPsyIndicator(s, period);
|
||||
}
|
||||
if (field.equals("NEW_PSY_VALUE") || indType.equals("NEW_PSYCHOLOGICAL"))
|
||||
return newSentPsyIndicator(s, intP(p, "length", 10));
|
||||
if (field.startsWith("INVEST_PSY_VALUE_")) {
|
||||
int period = parseTrailingInt(field, "INVEST_PSY_VALUE_", intP(p, "length", 10));
|
||||
return newPsychIndicator(s, period, true);
|
||||
return psychIndicator(s, period, false);
|
||||
}
|
||||
if (field.equals("INVEST_PSY_VALUE") || indType.equals("INVEST_PSYCHOLOGICAL"))
|
||||
return newPsychIndicator(s, intP(p, "length", 10), true);
|
||||
return psychIndicator(s, intP(p, "length", 10), false);
|
||||
// Williams %R
|
||||
if (field.startsWith("WILLIAMS_R_VALUE_")) {
|
||||
int period = parseTrailingInt(field, "WILLIAMS_R_VALUE_", intP(p, "length", 14));
|
||||
@@ -894,10 +899,14 @@ public class StrategyDslToTa4jAdapter {
|
||||
return new VrIndicator(s, period);
|
||||
}
|
||||
|
||||
private Indicator<Num> newPsychIndicator(BarSeries s, int period, boolean volumeWeighted) {
|
||||
private Indicator<Num> psychIndicator(BarSeries s, int period, boolean volumeWeighted) {
|
||||
return new PsychologicalIndicator(s, period, volumeWeighted);
|
||||
}
|
||||
|
||||
private Indicator<Num> newSentPsyIndicator(BarSeries s, int period) {
|
||||
return new NewSentimentPsychologicalIndicator(s, period);
|
||||
}
|
||||
|
||||
/** VR(거래량비율) = 상승일 거래량합 / 하락일 거래량합 × 100 */
|
||||
private static final class VrIndicator extends CachedIndicator<Num> {
|
||||
private final int period;
|
||||
@@ -933,7 +942,7 @@ public class StrategyDslToTa4jAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/** 신심리도 / 투자심리도 */
|
||||
/** 심리도 / 투자심리도 */
|
||||
private static final class PsychologicalIndicator extends CachedIndicator<Num> {
|
||||
private final int period;
|
||||
private final boolean volumeWeighted;
|
||||
@@ -979,6 +988,47 @@ public class StrategyDslToTa4jAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/** 신심리도 — 상승·하락 일수와 등락폭 반영 */
|
||||
private static final class NewSentimentPsychologicalIndicator extends CachedIndicator<Num> {
|
||||
private final int period;
|
||||
private final ClosePriceIndicator close;
|
||||
|
||||
NewSentimentPsychologicalIndicator(BarSeries series, int period) {
|
||||
super(series);
|
||||
this.period = period;
|
||||
this.close = new ClosePriceIndicator(series);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Num calculate(int index) {
|
||||
if (index < period) return null;
|
||||
double upSize = 0, downSize = 0;
|
||||
int upDay = 0, downDay = 0;
|
||||
for (int j = index - period + 1; j <= index; j++) {
|
||||
Num c = close.getValue(j);
|
||||
Num cPrev = close.getValue(j - 1);
|
||||
if (c == null || cPrev == null) continue;
|
||||
double diff = c.doubleValue() - cPrev.doubleValue();
|
||||
if (diff > 0) {
|
||||
upSize += diff;
|
||||
upDay++;
|
||||
} else if (diff < 0) {
|
||||
downSize += -diff;
|
||||
downDay++;
|
||||
}
|
||||
}
|
||||
double total = upSize + downSize;
|
||||
if (total == 0) return null;
|
||||
double val = (upDay * (upSize / total) - downDay * (downSize / total)) * 100.0 / period;
|
||||
return getBarSeries().numFactory().numOf(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCountOfUnstableBars() {
|
||||
return period + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 직전 N봉 고가 중 최고값 (현재 봉 제외).
|
||||
* index t 에서 max(high[t-N]..high[t-1]).
|
||||
|
||||
Reference in New Issue
Block a user