신심리도 추가, 투자심리도 수정
This commit is contained in:
@@ -86,7 +86,7 @@ public class IndicatorController {
|
||||
"ATR", "StandardDeviation", "HistoricalVolatility",
|
||||
"OBV", "MFI", "ChaikinMF",
|
||||
"VolumeOscillator", "VR", "Disparity",
|
||||
"Psychological", "InvestPsychological",
|
||||
"Psychological", "NewPsychological", "InvestPsychological",
|
||||
"TRIX", "ADX", "DMI"
|
||||
)
|
||||
));
|
||||
|
||||
@@ -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]).
|
||||
|
||||
@@ -85,7 +85,7 @@ const DEF_DEFAULTS = {
|
||||
obvPeriod: 9, obvSignal: 9,
|
||||
bbPeriod: 20, bbStdDev: 2,
|
||||
ichTenkan: 9, ichKijun: 26, ichSenkouB: 52,
|
||||
newPsy: 12, investPsy: 10,
|
||||
newPsy: 10, psyPeriod: 12, investPsy: 10,
|
||||
williamsR: 14,
|
||||
bwiPeriod: 20,
|
||||
vrPeriod: 10,
|
||||
@@ -101,7 +101,7 @@ const DEF_DEFAULTS = {
|
||||
BWI: { over: 80, mid: 50, under: 20 },
|
||||
VR: { over: 200, mid: 100, under: 50 },
|
||||
PSYCHOLOGICAL: { over: 75, mid: 50, under: 25 },
|
||||
NEW_PSYCHOLOGICAL: { over: 75, mid: 50, under: 25 },
|
||||
NEW_PSYCHOLOGICAL: { over: 50, mid: 0, under: -50 },
|
||||
INVEST_PSYCHOLOGICAL: { over: 75, mid: 50, under: 25 },
|
||||
MACD: { mid: 0 },
|
||||
ADX: { over: 40, mid: 25, under: 20 },
|
||||
@@ -147,7 +147,8 @@ function buildDef(activeIndicators: IndicatorConfig[]): DefType {
|
||||
const vo = p('VolumeOscillator');
|
||||
const vrInd = p('VR');
|
||||
const disp = p('Disparity');
|
||||
const nPsy = p('Psychological') ?? p('NewPsychological');
|
||||
const psy = p('Psychological');
|
||||
const nPsy = p('NewPsychological');
|
||||
const iPsy = p('InvestPsychological');
|
||||
const obvP = p('OBV');
|
||||
// SMA (MA lines)→ type:'SMA', params keys depend on smaConfig
|
||||
@@ -175,7 +176,7 @@ function buildDef(activeIndicators: IndicatorConfig[]): DefType {
|
||||
VR: 'VR',
|
||||
DISPARITY: 'Disparity',
|
||||
PSYCHOLOGICAL: 'Psychological',
|
||||
NEW_PSYCHOLOGICAL: 'Psychological',
|
||||
NEW_PSYCHOLOGICAL: 'NewPsychological',
|
||||
INVEST_PSYCHOLOGICAL: 'InvestPsychological',
|
||||
OBV: 'OBV',
|
||||
BOLLINGER: 'BollingerBands',
|
||||
@@ -230,6 +231,7 @@ function buildDef(activeIndicators: IndicatorConfig[]): DefType {
|
||||
ichTenkan: num(ich, 'conversionPeriods', DEF_DEFAULTS.ichTenkan),
|
||||
ichKijun: num(ich, 'basePeriods', DEF_DEFAULTS.ichKijun),
|
||||
ichSenkouB: num(ich, 'laggingSpan2Periods', DEF_DEFAULTS.ichSenkouB),
|
||||
psyPeriod: num(psy, 'length', DEF_DEFAULTS.psyPeriod),
|
||||
newPsy: num(nPsy, 'length', DEF_DEFAULTS.newPsy),
|
||||
investPsy: num(iPsy, 'length', DEF_DEFAULTS.investPsy),
|
||||
williamsR: num(wr, 'length', DEF_DEFAULTS.williamsR),
|
||||
@@ -385,11 +387,19 @@ const getFieldOpts = (ind: string, signalType: 'buy'|'sell', DEF: DefType): Opt[
|
||||
{ value:`DISPARITY${DEF.dispLong}`, label:`장기 이격도(${DEF.dispLong}일)` },
|
||||
{ value:kv(100), label:'중앙선(100)' },
|
||||
]);
|
||||
case 'PSYCHOLOGICAL':
|
||||
case 'NEW_PSYCHOLOGICAL': {
|
||||
case 'PSYCHOLOGICAL': {
|
||||
const { over, mid, under } = th({ over:75, mid:50, under:25 });
|
||||
return condOpts([
|
||||
{ value:'PSY_VALUE', label:`심리도 값(${DEF.newPsy}일)` },
|
||||
{ value:'PSY_VALUE', label:`심리도 값(${DEF.psyPeriod}일)` },
|
||||
{ value:kv(over), label:`${overTerm}(${over})` },
|
||||
{ value:kv(mid), label:`중앙선(${mid})` },
|
||||
{ value:kv(under), label:`${underTerm}(${under})` },
|
||||
]);
|
||||
}
|
||||
case 'NEW_PSYCHOLOGICAL': {
|
||||
const { over, mid, under } = th({ over:50, mid:0, under:-50 });
|
||||
return condOpts([
|
||||
{ value:'NEW_PSY_VALUE', label:`신심리도 값(${DEF.newPsy}일)` },
|
||||
{ value:kv(over), label:`${overTerm}(${over})` },
|
||||
{ value:kv(mid), label:`중앙선(${mid})` },
|
||||
{ value:kv(under), label:`${underTerm}(${under})` },
|
||||
@@ -502,7 +512,7 @@ const getDefaultFields = (ind: string, signalType: 'buy'|'sell', DEF: DefType):
|
||||
EMA: { l:`EMA${DEF.maLines[0]}`, r:`EMA${DEF.maLines[1]}` },
|
||||
DISPARITY: { l:`DISPARITY${DEF.dispUltra}`, r: mid(100) },
|
||||
PSYCHOLOGICAL: { l:'PSY_VALUE', r: over(75) },
|
||||
NEW_PSYCHOLOGICAL: { l:'PSY_VALUE', r: over(75) },
|
||||
NEW_PSYCHOLOGICAL: { l:'NEW_PSY_VALUE', r: over(50) },
|
||||
INVEST_PSYCHOLOGICAL: { l:'INVEST_PSY_VALUE', r: over(75) },
|
||||
WILLIAMS_R: { l:'WILLIAMS_R_VALUE', r: over(-20) },
|
||||
BWI: { l:'BWI_VALUE', r: over(80) },
|
||||
@@ -1233,7 +1243,7 @@ export const StrategyPage: React.FC<Props> = ({ activeIndicators = [] }) => {
|
||||
{ type:'indicator' as const, value:'ICHIMOKU', label:'일목균형표', color:'primary' },
|
||||
];
|
||||
const indicatorItems = [
|
||||
'PSYCHOLOGICAL:심리도', 'NEW_PSYCHOLOGICAL:심리도', 'INVEST_PSYCHOLOGICAL:투자심리도',
|
||||
'PSYCHOLOGICAL:심리도', 'NEW_PSYCHOLOGICAL:신심리도', 'INVEST_PSYCHOLOGICAL:투자심리도',
|
||||
'MACD:MACD', 'CCI:CCI', 'ADX:ADX', 'BWI:BWI', 'DMI:DMI', 'OBV:OBV',
|
||||
'RSI:RSI', 'STOCHASTIC:Stochastic', 'WILLIAMS_R:Williams %R',
|
||||
'TRIX:TRIX', 'VOLUME_OSC:VolumeOSC', 'VR:VR', 'DISPARITY:이격도', 'VOLUME:거래량',
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface CompositePeriodDef {
|
||||
williamsR: number;
|
||||
trixPeriod: number;
|
||||
vrPeriod: number;
|
||||
psyPeriod: number;
|
||||
newPsy: number;
|
||||
investPsy: number;
|
||||
dispShort: number;
|
||||
@@ -77,8 +78,8 @@ export function getCompositeDefaultPeriods(ind: string, def: CompositePeriodDef)
|
||||
case 'WILLIAMS_R': return { short: def.williamsR, long: Math.max(def.williamsR * 2, 28) };
|
||||
case 'TRIX': return { short: def.trixPeriod, long: Math.max(def.trixPeriod * 2, 24) };
|
||||
case 'VR': return { short: def.vrPeriod, long: Math.max(def.vrPeriod * 2, 20) };
|
||||
case 'PSYCHOLOGICAL':
|
||||
case 'NEW_PSYCHOLOGICAL': return { short: def.newPsy, long: Math.max(def.newPsy * 2, 24) };
|
||||
case 'PSYCHOLOGICAL': return { short: def.psyPeriod, long: Math.max(def.psyPeriod * 2, 24) };
|
||||
case 'NEW_PSYCHOLOGICAL': return { short: def.newPsy, long: Math.max(def.newPsy * 2, 20) };
|
||||
case 'INVEST_PSYCHOLOGICAL': return { short: def.investPsy, long: Math.max(def.investPsy * 2, 20) };
|
||||
case 'DISPARITY': return { short: def.dispShort, long: def.dispMid };
|
||||
default: return { short: 9, long: 20 };
|
||||
@@ -95,8 +96,8 @@ export function compositeValueField(ind: string, period: number): string {
|
||||
case 'WILLIAMS_R': return `WILLIAMS_R_VALUE_${period}`;
|
||||
case 'TRIX': return `TRIX_VALUE_${period}`;
|
||||
case 'VR': return `VR_VALUE_${period}`;
|
||||
case 'PSYCHOLOGICAL':
|
||||
case 'NEW_PSYCHOLOGICAL': return `PSY_VALUE_${period}`;
|
||||
case 'PSYCHOLOGICAL': return `PSY_VALUE_${period}`;
|
||||
case 'NEW_PSYCHOLOGICAL': return `NEW_PSY_VALUE_${period}`;
|
||||
case 'INVEST_PSYCHOLOGICAL': return `INVEST_PSY_VALUE_${period}`;
|
||||
case 'DISPARITY': return `DISPARITY${period}`;
|
||||
default: return `${ind}_VALUE_${period}`;
|
||||
@@ -117,7 +118,7 @@ const COMPOSITE_VALUE_PREFIX: Record<string, string> = {
|
||||
TRIX: 'TRIX_VALUE',
|
||||
VR: 'VR_VALUE',
|
||||
PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'NEW_PSY_VALUE',
|
||||
INVEST_PSYCHOLOGICAL: 'INVEST_PSY_VALUE',
|
||||
};
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface IndicatorPeriodDef {
|
||||
trixPeriod: number;
|
||||
vrPeriod: number;
|
||||
newPsy: number;
|
||||
psyPeriod: number;
|
||||
investPsy: number;
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ export const VALUE_FIELD_PREFIX: Record<string, string> = {
|
||||
TRIX: 'TRIX_VALUE',
|
||||
VR: 'VR_VALUE',
|
||||
PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'NEW_PSY_VALUE',
|
||||
INVEST_PSYCHOLOGICAL: 'INVEST_PSY_VALUE',
|
||||
};
|
||||
|
||||
@@ -439,9 +440,10 @@ export function getThresholdPresetOptions(indicatorType: string): number[] {
|
||||
switch (indicatorType) {
|
||||
case 'RSI':
|
||||
case 'PSYCHOLOGICAL':
|
||||
case 'NEW_PSYCHOLOGICAL':
|
||||
case 'INVEST_PSYCHOLOGICAL':
|
||||
return [20, 25, 30, 50, 70, 75, 80];
|
||||
case 'NEW_PSYCHOLOGICAL':
|
||||
return [-50, -25, 0, 25, 50];
|
||||
case 'STOCHASTIC':
|
||||
return [20, 50, 80];
|
||||
case 'CCI':
|
||||
@@ -462,10 +464,11 @@ export function getThresholdBounds(indicatorType: string): { min: number; max: n
|
||||
switch (indicatorType) {
|
||||
case 'RSI':
|
||||
case 'PSYCHOLOGICAL':
|
||||
case 'NEW_PSYCHOLOGICAL':
|
||||
case 'INVEST_PSYCHOLOGICAL':
|
||||
case 'STOCHASTIC':
|
||||
return { min: 0, max: 100 };
|
||||
case 'NEW_PSYCHOLOGICAL':
|
||||
return { min: -100, max: 100 };
|
||||
case 'WILLIAMS_R':
|
||||
return { min: -100, max: 0 };
|
||||
case 'CCI':
|
||||
|
||||
@@ -113,7 +113,7 @@ export function calcVR(
|
||||
return { plot0: toPlotData(bars, vals) };
|
||||
}
|
||||
|
||||
/** 심리도(업비트): N일 중 전일 대비 상승일 비율 × 100 */
|
||||
/** 심리도: N일 중 전일 대비 상승일 비율 × 100 */
|
||||
export function calcPsychological(
|
||||
bars: OHLCVBar[],
|
||||
params: Record<string, number | string | boolean>,
|
||||
@@ -131,27 +131,53 @@ export function calcPsychological(
|
||||
return { plot0: toPlotData(bars, vals) };
|
||||
}
|
||||
|
||||
/** @deprecated Psychological 사용 */
|
||||
export const calcNewPsychological = calcPsychological;
|
||||
/**
|
||||
* 신심리도(업비트): [상승일수×상승폭비율 − 하락일수×하락폭비율] × 100 / N
|
||||
* 상승폭비율 = 상승폭합 / (상승폭합+하락폭합)
|
||||
*/
|
||||
export function calcNewPsychological(
|
||||
bars: OHLCVBar[],
|
||||
params: Record<string, number | string | boolean>,
|
||||
): Record<string, PlotData> {
|
||||
const period = Number(params.length ?? 10);
|
||||
const closes = bars.map(b => b.close);
|
||||
const vals = closes.map((_, i) => {
|
||||
if (i < period) return NaN;
|
||||
let upSize = 0;
|
||||
let downSize = 0;
|
||||
let upDay = 0;
|
||||
let downDay = 0;
|
||||
for (let j = i - period + 1; j <= i; j++) {
|
||||
const diff = closes[j] - closes[j - 1];
|
||||
if (diff > 0) {
|
||||
upSize += diff;
|
||||
upDay++;
|
||||
} else if (diff < 0) {
|
||||
downSize += -diff;
|
||||
downDay++;
|
||||
}
|
||||
}
|
||||
const total = upSize + downSize;
|
||||
if (total === 0) return NaN;
|
||||
return (upDay * (upSize / total) - downDay * (downSize / total)) * 100 / period;
|
||||
});
|
||||
return { plot0: toPlotData(bars, vals) };
|
||||
}
|
||||
|
||||
/** 투자심리도(업비트): N일 중 상승일 거래량 비중 × 100 */
|
||||
/** 투자심리도(업비트): N일 중 전일 대비 상승일 비율 × 100 */
|
||||
export function calcInvestPsychological(
|
||||
bars: OHLCVBar[],
|
||||
params: Record<string, number | string | boolean>,
|
||||
): Record<string, PlotData> {
|
||||
const period = Number(params.length ?? 10);
|
||||
const closes = bars.map(b => b.close);
|
||||
const volumes = bars.map(b => b.volume);
|
||||
const vals = closes.map((_, i) => {
|
||||
if (i < period) return NaN;
|
||||
let upVol = 0;
|
||||
let total = 0;
|
||||
let up = 0;
|
||||
for (let j = i - period + 1; j <= i; j++) {
|
||||
total += volumes[j];
|
||||
if (closes[j] > closes[j - 1]) upVol += volumes[j];
|
||||
if (closes[j] > closes[j - 1]) up++;
|
||||
}
|
||||
if (total === 0) return NaN;
|
||||
return (upVol / total) * 100;
|
||||
return (up / period) * 100;
|
||||
});
|
||||
return { plot0: toPlotData(bars, vals) };
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ const PLOT_LABELS: Record<string, LabelPair> = {
|
||||
HV: { ko: '역사적변동성', en: 'HV' },
|
||||
OBV: { ko: '잔량균형', en: 'OBV' },
|
||||
심리도: { ko: '심리도', en: 'Psychological' },
|
||||
신심리도: { ko: '신심리도', en: 'New Psychological' },
|
||||
투자심리도: { ko: '투자심리도', en: 'Invest Psychological' },
|
||||
MFI: { ko: '자금흐름지수', en: 'MFI' },
|
||||
CMF: { ko: '차이킨자금흐름', en: 'CMF' },
|
||||
|
||||
@@ -30,6 +30,7 @@ export const MAIN_INDICATOR_TYPES: readonly string[] = [
|
||||
'VR',
|
||||
'Disparity',
|
||||
'Psychological',
|
||||
'NewPsychological',
|
||||
'InvestPsychological',
|
||||
'SMA',
|
||||
] as const;
|
||||
@@ -51,6 +52,7 @@ export const MAIN_INDICATOR_LABELS: Record<string, { ko: string; en: string }> =
|
||||
VR: { ko: 'VR (거래량비율)', en: 'Volume Ratio' },
|
||||
Disparity: { ko: '이격도', en: 'Disparity' },
|
||||
Psychological: { ko: '심리도', en: 'Psychological Line' },
|
||||
NewPsychological: { ko: '신심리도', en: 'New Psychological Line' },
|
||||
InvestPsychological: { ko: '투자심리도', en: 'Invest Psychological Line' },
|
||||
SMA: { ko: '단순 이동평균 (MA1~MA11)', en: 'Simple Moving Average' },
|
||||
};
|
||||
|
||||
@@ -180,7 +180,7 @@ export function getHlinePriceSpec(indicatorType: string, current: number): Numer
|
||||
Disparity: [90, 95, 100, 105, 110],
|
||||
VR: [50, 100, 150, 200, 250],
|
||||
Psychological: [25, 50, 75],
|
||||
NewPsychological: [25, 50, 75],
|
||||
NewPsychological: [-50, 0, 50],
|
||||
InvestPsychological: [25, 50, 75],
|
||||
};
|
||||
|
||||
|
||||
@@ -352,7 +352,8 @@ export const INDICATOR_REGISTRY: IndicatorDef[] = [
|
||||
{ type:'VR', name:'Volume Ratio', koreanName:'거래량비율', shortName:'VR', category:'Volume', overlay:false, defaultParams:{length:10}, plots:[{id:'plot0',title:'VR', color:'#AB47BC',type:'line',lineWidth:2}], hlines:[{price:200,color:'#EF5350'},{price:100,color:'#607D8B'},{price:50,color:'#4CAF50'}], description:'거래량 비율 (VR)' },
|
||||
{ type:'Disparity', name:'Disparity Index', koreanName:'이격도', shortName:'Disp', category:'Oscillators', overlay:false, defaultParams:{ultraLength:5, shortLength:10, midLength:20, longLength:60, src:'close'}, plots:[{id:'plot0',title:'초단기',color:'#E91E63',type:'line',lineWidth:1},{id:'plot1',title:'단기',color:'#FF9800',type:'line',lineWidth:1},{id:'plot2',title:'중기',color:'#4CAF50',type:'line',lineWidth:1},{id:'plot3',title:'장기',color:'#2196F3',type:'line',lineWidth:1}], hlines:[{price:100,color:'#607D8B'}], description:'이격도 (종가÷이평×100)' },
|
||||
{ type:'Psychological', name:'Psychological Line', koreanName:'심리도', shortName:'Psy', category:'Oscillators', overlay:false, defaultParams:{length:12}, plots:[{id:'plot0',title:'심리도',color:'#00BCD4',type:'line',lineWidth:2}], hlines:[{price:75,color:'#EF5350',label:'과열선'},{price:50,color:'#607D8B',label:'중앙선'},{price:25,color:'#4CAF50',label:'침체선'}], description:'심리도 (상승일 비율)' },
|
||||
{ type:'InvestPsychological', name:'Invest Psychological Line', koreanName:'투자심리도', shortName:'IPsych',category:'Oscillators', overlay:false, defaultParams:{length:10}, plots:[{id:'plot0',title:'투자심리도',color:'#7E57C2',type:'line',lineWidth:2}], hlines:[{price:75,color:'#EF5350',label:'과열선'},{price:50,color:'#607D8B',label:'중앙선'},{price:25,color:'#4CAF50',label:'침체선'}], description:'투자심리도 (상승일 거래량 비중)' },
|
||||
{ type:'NewPsychological', name:'New Psychological Line', koreanName:'신심리도', shortName:'NPsy', category:'Oscillators', overlay:false, defaultParams:{length:10}, plots:[{id:'plot0',title:'신심리도',color:'#F44336',type:'line',lineWidth:2}], hlines:[{price:50,color:'#EF5350',label:'과열선'},{price:0,color:'#607D8B',label:'중앙선'},{price:-50,color:'#4CAF50',label:'침체선'}], description:'신심리도 (상승·하락 폭 반영)' },
|
||||
{ type:'InvestPsychological', name:'Invest Psychological Line', koreanName:'투자심리도', shortName:'IPsych',category:'Oscillators', overlay:false, defaultParams:{length:10}, plots:[{id:'plot0',title:'투자심리도',color:'#7E57C2',type:'line',lineWidth:2}], hlines:[{price:75,color:'#EF5350',label:'과열선'},{price:50,color:'#607D8B',label:'중앙선'},{price:25,color:'#4CAF50',label:'침체선'}], description:'투자심리도 (상승일 비율)' },
|
||||
{ type:'MFI', name:'Money Flow Index', koreanName:'자금흐름지수', shortName:'MFI', category:'Volume', overlay:false, defaultParams:{length:14}, plots:[{id:'plot0',title:'MFI', color:'#00BCD4',type:'line',lineWidth:2}], hlines:[{price:80,color:'#EF5350'},{price:20,color:'#4CAF50'}], description:'자금 흐름 지수' },
|
||||
{ type:'ChaikinMF', name:'Chaikin Money Flow', koreanName:'차이킨자금흐름', shortName:'CMF', category:'Volume', overlay:false, defaultParams:{length:20}, plots:[{id:'plot0',title:'CMF', color:'#4CAF50',type:'histogram'}], hlines:[{price:0.25,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-0.25,color:'#4CAF50'}], description:'차이킨 자금 흐름' },
|
||||
{ type:'ChaikinOscillator', name:'Chaikin Oscillator', koreanName:'차이킨오실레이터', shortName:'CO', category:'Volume', overlay:false, defaultParams:{fastLength:3, slowLength:10}, plots:[{id:'plot0',title:'CO', color:'#FF9800',type:'histogram'}], hlines:[{price:0,color:'#607D8B'}], description:'차이킨 오실레이터' },
|
||||
@@ -414,13 +415,13 @@ export function formatIndicatorDisplayLabel(indicatorType: string): string {
|
||||
|
||||
/** 실시간 차트 pane·범례 타이틀 (심리도·투자심리도·이격도 등 한글명 우선) */
|
||||
export function getIndicatorChartTitle(type: string): string {
|
||||
const resolved = type === 'NewPsychological' ? 'Psychological' : type;
|
||||
const def = getIndicatorDef(resolved);
|
||||
const def = getIndicatorDef(type);
|
||||
if (!def) return type;
|
||||
if (
|
||||
resolved === 'Psychological'
|
||||
|| resolved === 'InvestPsychological'
|
||||
|| resolved === 'Disparity'
|
||||
type === 'Psychological'
|
||||
|| type === 'NewPsychological'
|
||||
|| type === 'InvestPsychological'
|
||||
|| type === 'Disparity'
|
||||
) {
|
||||
return def.koreanName;
|
||||
}
|
||||
@@ -491,7 +492,7 @@ export function enrichIndicatorConfig(
|
||||
if (cfgNorm.type === 'OBV') {
|
||||
params = normalizeObvParams(params);
|
||||
}
|
||||
if (cfgNorm.type === 'Psychological' || cfgNorm.type === 'InvestPsychological') {
|
||||
if (cfgNorm.type === 'Psychological' || cfgNorm.type === 'NewPsychological' || cfgNorm.type === 'InvestPsychological') {
|
||||
params = normalizePsychologicalParams(cfgNorm.type, params);
|
||||
}
|
||||
if (cfgNorm.type === 'BollingerBands') {
|
||||
@@ -587,7 +588,7 @@ async function loadCustomCalculators() {
|
||||
CUSTOM_CALCULATORS.VR = c.calcVR;
|
||||
CUSTOM_CALCULATORS.Disparity = c.calcDisparityUpbit;
|
||||
CUSTOM_CALCULATORS.Psychological = c.calcPsychological;
|
||||
CUSTOM_CALCULATORS.NewPsychological = c.calcPsychological;
|
||||
CUSTOM_CALCULATORS.NewPsychological = c.calcNewPsychological;
|
||||
CUSTOM_CALCULATORS.InvestPsychological = c.calcInvestPsychological;
|
||||
CUSTOM_CALCULATORS.BollingerBands = c.calcBollingerBands;
|
||||
CUSTOM_CALCULATORS.DMI = c.calcDMI;
|
||||
|
||||
@@ -36,6 +36,7 @@ export const INDICATOR_PLOT_PARAMS: Record<string, Record<string, string[]>> = {
|
||||
UltimateOscillator: { plot0: ['length1', 'length2', 'length3'] },
|
||||
ConnorsRSI: { plot0: ['rsiLength', 'upDownLength', 'rocLength'] },
|
||||
Psychological: { plot0: ['length'] },
|
||||
NewPsychological: { plot0: ['length'] },
|
||||
InvestPsychological: { plot0: ['length'] },
|
||||
Disparity: {
|
||||
plot0: ['ultraLength'],
|
||||
|
||||
@@ -7,8 +7,8 @@ import { MAIN_INDICATOR_TYPES, MAIN_INDICATOR_LABELS, isMainIndicatorType } from
|
||||
|
||||
const MAIN_SET = new Set<string>(MAIN_INDICATOR_TYPES);
|
||||
|
||||
/** 설정 UI에서 제외 (Psychological 과 동일) */
|
||||
const EXCLUDED_TYPES = new Set(['NewPsychological']);
|
||||
/** 설정 UI에서 제외할 타입 없음 */
|
||||
const EXCLUDED_TYPES = new Set<string>();
|
||||
|
||||
/** Main 탭 지표 + 그 외 모든 보조지표 type (순서 고정, Main 항목은 하단 registry 에서 제외) */
|
||||
export function getSettingsIndicatorTypes(): string[] {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
/**
|
||||
* 심리도 / 투자심리도 (업비트)
|
||||
* - 심리도: N일 중 종가 상승일 비율 × 100
|
||||
* - 투자심리도: N일 중 상승일 거래량 비중 × 100
|
||||
* 심리도 / 신심리도 / 투자심리도 (업비트)
|
||||
* - 심리도: N일 중 종가 상승일 비율 × 100 (0~100)
|
||||
* - 신심리도: N일 상승·하락 일수와 등락폭 반영, 0 중심 (미래에셋 공식)
|
||||
* - 투자심리도: N일 중 종가 상승일 비율 × 100 (업비트·한국경제, 기본 10일)
|
||||
*/
|
||||
|
||||
export const PSYCHOLOGICAL_DEFAULT_LENGTH = 12;
|
||||
export const NEW_PSYCHOLOGICAL_DEFAULT_LENGTH = 10;
|
||||
export const INVEST_PSYCHOLOGICAL_DEFAULT_LENGTH = 10;
|
||||
|
||||
/** 레거시 type → 업비트 명칭 type */
|
||||
/** 레거시 type 정규화 — NewPsychological 은 별도 지표로 유지 */
|
||||
export function resolvePsychologicalIndicatorType(type: string): string {
|
||||
if (type === 'NewPsychological') return 'Psychological';
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -20,6 +21,8 @@ export function normalizePsychologicalParams(
|
||||
const defaultLen =
|
||||
type === 'InvestPsychological'
|
||||
? INVEST_PSYCHOLOGICAL_DEFAULT_LENGTH
|
||||
: type === 'NewPsychological'
|
||||
? NEW_PSYCHOLOGICAL_DEFAULT_LENGTH
|
||||
: PSYCHOLOGICAL_DEFAULT_LENGTH;
|
||||
const length = Math.max(1, Number(params.length ?? defaultLen) || defaultLen);
|
||||
return { ...params, length };
|
||||
|
||||
@@ -102,7 +102,7 @@ const DEF_DEFAULTS = {
|
||||
obvPeriod: 9, obvSignal: 9,
|
||||
bbPeriod: 20, bbStdDev: 2,
|
||||
ichTenkan: 9, ichKijun: 26, ichSenkouB: 52,
|
||||
newPsy: 12, investPsy: 10,
|
||||
newPsy: 10, psyPeriod: 12, investPsy: 10,
|
||||
williamsR: 14,
|
||||
bwiPeriod: 20,
|
||||
vrPeriod: 10,
|
||||
@@ -118,7 +118,7 @@ const DEF_DEFAULTS = {
|
||||
BWI: { over: 80, mid: 50, under: 20 },
|
||||
VR: { over: 200, mid: 100, under: 50 },
|
||||
PSYCHOLOGICAL: { over: 75, mid: 50, under: 25 },
|
||||
NEW_PSYCHOLOGICAL: { over: 75, mid: 50, under: 25 },
|
||||
NEW_PSYCHOLOGICAL: { over: 50, mid: 0, under: -50 },
|
||||
INVEST_PSYCHOLOGICAL: { over: 75, mid: 50, under: 25 },
|
||||
MACD: { mid: 0 },
|
||||
ADX: { over: 40, mid: 25, under: 20 },
|
||||
@@ -165,7 +165,8 @@ export function buildDef(activeIndicators: IndicatorConfig[]): DefType {
|
||||
const vo = p('VolumeOscillator');
|
||||
const vrInd = p('VR');
|
||||
const disp = p('Disparity');
|
||||
const nPsy = p('Psychological') ?? p('NewPsychological');
|
||||
const psy = p('Psychological');
|
||||
const nPsy = p('NewPsychological');
|
||||
const iPsy = p('InvestPsychological');
|
||||
const obvP = p('OBV');
|
||||
// SMA (MA lines)→ type:'SMA', params keys depend on smaConfig
|
||||
@@ -193,7 +194,7 @@ export function buildDef(activeIndicators: IndicatorConfig[]): DefType {
|
||||
VR: 'VR',
|
||||
DISPARITY: 'Disparity',
|
||||
PSYCHOLOGICAL: 'Psychological',
|
||||
NEW_PSYCHOLOGICAL: 'Psychological',
|
||||
NEW_PSYCHOLOGICAL: 'NewPsychological',
|
||||
INVEST_PSYCHOLOGICAL: 'InvestPsychological',
|
||||
OBV: 'OBV',
|
||||
BOLLINGER: 'BollingerBands',
|
||||
@@ -251,6 +252,7 @@ export function buildDef(activeIndicators: IndicatorConfig[]): DefType {
|
||||
ichTenkan: num(ich, 'conversionPeriods', DEF_DEFAULTS.ichTenkan),
|
||||
ichKijun: num(ich, 'basePeriods', DEF_DEFAULTS.ichKijun),
|
||||
ichSenkouB: num(ich, 'laggingSpan2Periods', DEF_DEFAULTS.ichSenkouB),
|
||||
psyPeriod: num(psy, 'length', DEF_DEFAULTS.psyPeriod),
|
||||
newPsy: num(nPsy, 'length', DEF_DEFAULTS.newPsy),
|
||||
investPsy: num(iPsy, 'length', DEF_DEFAULTS.investPsy),
|
||||
williamsR: num(wr, 'length', DEF_DEFAULTS.williamsR),
|
||||
@@ -448,12 +450,11 @@ export const getFieldOpts = (
|
||||
{ value:THRESHOLD_HL_MID, label:`중앙선(${mid})` },
|
||||
]);
|
||||
}
|
||||
case 'PSYCHOLOGICAL':
|
||||
case 'NEW_PSYCHOLOGICAL': {
|
||||
case 'PSYCHOLOGICAL': {
|
||||
const { over, mid, under } = th({ over:75, mid:50, under:25 });
|
||||
const psyP = cond
|
||||
? getConditionValuePeriod({ ...cond, indicatorType: ind }, DEF)
|
||||
: DEF.newPsy;
|
||||
: DEF.psyPeriod;
|
||||
return condOpts([
|
||||
{ value:'PSY_VALUE', label:`심리도 라인(${psyP}일)` },
|
||||
{ value:THRESHOLD_HL_OVER, label:`${overTerm}(${over})` },
|
||||
@@ -461,6 +462,18 @@ export const getFieldOpts = (
|
||||
{ value:THRESHOLD_HL_UNDER, label:`${underTerm}(${under})` },
|
||||
]);
|
||||
}
|
||||
case 'NEW_PSYCHOLOGICAL': {
|
||||
const { over, mid, under } = th({ over:50, mid:0, under:-50 });
|
||||
const nPsyP = cond
|
||||
? getConditionValuePeriod({ ...cond, indicatorType: ind }, DEF)
|
||||
: DEF.newPsy;
|
||||
return condOpts([
|
||||
{ value:'NEW_PSY_VALUE', label:`신심리도 라인(${nPsyP}일)` },
|
||||
{ value:THRESHOLD_HL_OVER, label:`${overTerm}(${over})` },
|
||||
{ value:THRESHOLD_HL_MID, label:`중앙선(${mid})` },
|
||||
{ value:THRESHOLD_HL_UNDER, label:`${underTerm}(${under})` },
|
||||
]);
|
||||
}
|
||||
case 'INVEST_PSYCHOLOGICAL': {
|
||||
const { over, mid, under } = th({ over:75, mid:50, under:25 });
|
||||
const ipsyP = cond
|
||||
@@ -591,7 +604,7 @@ const getDefaultConditionFields = (ind: string, signalType: 'buy'|'sell', DEF: D
|
||||
EMA: { l:`EMA${DEF.maLines[0]}`, r:`EMA${DEF.maLines[1]}` },
|
||||
DISPARITY: { l:`DISPARITY${DEF.dispUltra}`, r: THRESHOLD_HL_MID },
|
||||
PSYCHOLOGICAL: { l:'PSY_VALUE', r: THRESHOLD_HL_OVER },
|
||||
NEW_PSYCHOLOGICAL: { l:'PSY_VALUE', r: THRESHOLD_HL_OVER },
|
||||
NEW_PSYCHOLOGICAL: { l:'NEW_PSY_VALUE', r: THRESHOLD_HL_OVER },
|
||||
INVEST_PSYCHOLOGICAL: { l:'INVEST_PSY_VALUE', r: THRESHOLD_HL_OVER },
|
||||
WILLIAMS_R: { l:'WILLIAMS_R_VALUE', r: THRESHOLD_HL_OVER },
|
||||
BWI: { l:'BWI_VALUE', r: THRESHOLD_HL_OVER },
|
||||
@@ -669,7 +682,7 @@ export function resolveFieldOptionValue(indicatorType: string, field?: string):
|
||||
TRIX: 'TRIX_VALUE',
|
||||
VR: 'VR_VALUE',
|
||||
PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'NEW_PSY_VALUE',
|
||||
INVEST_PSYCHOLOGICAL: 'INVEST_PSY_VALUE',
|
||||
};
|
||||
const base = bases[indicatorType];
|
||||
@@ -1020,7 +1033,7 @@ export const CondEditor: React.FC<CondEditorProps> = ({ cond, signalType, onChan
|
||||
TRIX: 'TRIX_VALUE',
|
||||
VR: 'VR_VALUE',
|
||||
PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'NEW_PSY_VALUE',
|
||||
INVEST_PSYCHOLOGICAL: 'INVEST_PSY_VALUE',
|
||||
};
|
||||
const base = bases[normalized.indicatorType];
|
||||
|
||||
@@ -36,6 +36,7 @@ export function formatIndicatorPeriodLabel(indicator: string, def: DefType): str
|
||||
case 'DISPARITY':
|
||||
return `${def.dispUltra}/${def.dispShort}/${def.dispMid}/${def.dispLong}`;
|
||||
case 'PSYCHOLOGICAL':
|
||||
return String(def.psyPeriod);
|
||||
case 'NEW_PSYCHOLOGICAL':
|
||||
return String(def.newPsy);
|
||||
case 'INVEST_PSYCHOLOGICAL':
|
||||
@@ -88,6 +89,7 @@ export function getIndicatorPrimaryPeriod(indicator: string, def: DefType): numb
|
||||
case 'DISPARITY':
|
||||
return def.dispShort;
|
||||
case 'PSYCHOLOGICAL':
|
||||
return def.psyPeriod;
|
||||
case 'NEW_PSYCHOLOGICAL':
|
||||
return def.newPsy;
|
||||
case 'INVEST_PSYCHOLOGICAL':
|
||||
|
||||
@@ -33,6 +33,7 @@ export const DEFAULT_AUXILIARY_ITEMS: Omit<PaletteItem, 'id' | 'kind'>[] = [
|
||||
{ value: 'VR', label: 'VR', desc: '거래량 비율', builtIn: true },
|
||||
{ value: 'DISPARITY', label: '이격도', desc: 'Disparity', builtIn: true },
|
||||
{ value: 'PSYCHOLOGICAL', label: '심리도', desc: 'Psychological', builtIn: true },
|
||||
{ value: 'NEW_PSYCHOLOGICAL', label: '신심리도', desc: 'New Psychological', builtIn: true },
|
||||
{ value: 'INVEST_PSYCHOLOGICAL', label: '투자심리도', desc: 'Invest PSY', builtIn: true },
|
||||
{ value: 'VOLUME', label: '거래량', desc: 'Volume', builtIn: true },
|
||||
{ value: 'NEW_HIGH', label: '신고가', desc: '직전 N봉 최고가 돌파 — 종가/고가 vs N일 신고가선 (9·20일)', builtIn: true, period: 9 },
|
||||
@@ -145,9 +146,7 @@ export function resetPaletteItems(kind: PaletteItemKind): PaletteItem[] {
|
||||
}
|
||||
|
||||
/** 전략편집기·전략빌더에 표시할 지표명 (우측 팔레트 label 우선) */
|
||||
const STRATEGY_INDICATOR_TYPE_ALIASES: Record<string, string> = {
|
||||
NEW_PSYCHOLOGICAL: 'PSYCHOLOGICAL',
|
||||
};
|
||||
const STRATEGY_INDICATOR_TYPE_ALIASES: Record<string, string> = {};
|
||||
|
||||
export function getStrategyIndicatorDisplayName(indicatorType: string): string {
|
||||
const resolved = STRATEGY_INDICATOR_TYPE_ALIASES[indicatorType] ?? indicatorType;
|
||||
|
||||
@@ -31,7 +31,7 @@ const DSL_TO_REGISTRY: Record<string, string> = {
|
||||
VR: 'VR',
|
||||
DISPARITY: 'Disparity',
|
||||
PSYCHOLOGICAL: 'Psychological',
|
||||
NEW_PSYCHOLOGICAL: 'Psychological',
|
||||
NEW_PSYCHOLOGICAL: 'NewPsychological',
|
||||
INVEST_PSYCHOLOGICAL: 'InvestPsychological',
|
||||
BWI: 'BBBandWidth',
|
||||
DONCHIAN: 'DonchianChannels',
|
||||
|
||||
@@ -109,7 +109,7 @@ export const LEFT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
|
||||
VR: [{ value: 'CURRENT', label: 'VR' }],
|
||||
DISPARITY: [{ value: 'CURRENT', label: '이격도' }],
|
||||
PSYCHOLOGICAL: [{ value: 'CURRENT', label: '심리도' }],
|
||||
NEW_PSYCHOLOGICAL: [{ value: 'CURRENT', label: '심리도' }],
|
||||
NEW_PSYCHOLOGICAL: [{ value: 'CURRENT', label: '신심리도' }],
|
||||
INVEST_PSYCHOLOGICAL: [{ value: 'CURRENT', label: '투자심리도' }],
|
||||
VOLUME: [{ value: 'CURRENT', label: '거래량' }],
|
||||
MA: [
|
||||
@@ -181,8 +181,8 @@ export const RIGHT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
|
||||
{ value: 'OVERSOLD_25', label: '과매도(25)' }, { value: 'CUSTOM', label: '직접 입력' },
|
||||
],
|
||||
NEW_PSYCHOLOGICAL: [
|
||||
{ value: 'OVERBOUGHT_75', label: '과매수(75)' }, { value: 'NEUTRAL_50', label: '중립(50)' },
|
||||
{ value: 'OVERSOLD_25', label: '과매도(25)' }, { value: 'CUSTOM', label: '직접 입력' },
|
||||
{ value: 'OVERBOUGHT_50', label: '과매수(50)' }, { value: 'NEUTRAL_0', label: '중립(0)' },
|
||||
{ value: 'OVERSOLD_NEG50', label: '과매도(-50)' }, { value: 'CUSTOM', label: '직접 입력' },
|
||||
],
|
||||
INVEST_PSYCHOLOGICAL: [
|
||||
{ value: 'OVERBOUGHT_75', label: '과매수(75)' }, { value: 'NEUTRAL_50', label: '중립(50)' },
|
||||
|
||||
Reference in New Issue
Block a user