610 lines
62 KiB
TypeScript
610 lines
62 KiB
TypeScript
import type { OHLCVBar } from '../types';
|
||
import type { Bar } from 'oakscriptjs';
|
||
import {
|
||
createDefaultSmaParams,
|
||
createDefaultSmaPlots,
|
||
calculateSmaMulti,
|
||
normalizeSmaConfig,
|
||
} from './smaConfig';
|
||
import { normalizeIchimokuConfig, mergeIchimokuPlots } from './ichimokuConfig';
|
||
import {
|
||
normalizePsychologicalParams,
|
||
resolvePsychologicalIndicatorType,
|
||
} from './psychologicalConfig';
|
||
import {
|
||
normalizeBollingerParams,
|
||
resolveBollingerBars,
|
||
resolveBbBandBackground,
|
||
mergeBbPlots,
|
||
} from './bollingerConfig';
|
||
import type { Timeframe } from '../types';
|
||
|
||
export type IndicatorCategory =
|
||
| 'Moving Averages'
|
||
| 'Oscillators'
|
||
| 'Momentum'
|
||
| 'Trend'
|
||
| 'Volatility'
|
||
| 'Channels & Bands'
|
||
| 'Volume'
|
||
| 'Candlestick Patterns';
|
||
|
||
export interface PlotDef {
|
||
id: string;
|
||
title: string;
|
||
color: string;
|
||
type: 'line' | 'histogram' | 'area' | 'scatter';
|
||
lineWidth?: number;
|
||
/** 라인 시리즈 선 유형 (기본 solid) */
|
||
lineStyle?: HLineStyle;
|
||
}
|
||
|
||
export type HLineStyle = 'solid' | 'dashed' | 'dotted';
|
||
|
||
export interface HLineDef {
|
||
price: number;
|
||
color: string;
|
||
/** 표시 이름: '과열선' | '중앙선' | '침체선' | '기준선' 등 */
|
||
label?: string;
|
||
visible?: boolean; // false 이면 차트에서 숨김 (기본 true)
|
||
lineStyle?: HLineStyle; // 선 유형 (기본 'dashed')
|
||
lineWidth?: number; // 선 굵기 1~4 (기본 1)
|
||
}
|
||
|
||
/**
|
||
* price 값과 같은 hlines 배열 내 다른 값들을 기반으로
|
||
* 기본 레이블을 자동 결정합니다.
|
||
*/
|
||
/** 가격 값으로 hline 역할 레이블을 결정 — 용어 통일: 과열선/중앙선/침체선 */
|
||
export function getHLineLabel(price: number, allPrices: number[]): string {
|
||
if (price === 0) return '중앙선';
|
||
const uniq = [...new Set(allPrices)].sort((a, b) => b - a);
|
||
if (uniq.length === 1) return '중앙선';
|
||
if (price === uniq[0]) return '과열선';
|
||
if (price === uniq[uniq.length - 1]) return '침체선';
|
||
return '중앙선';
|
||
}
|
||
|
||
function migrateHLineLabel(label: string | undefined): string | undefined {
|
||
if (!label) return undefined;
|
||
if (label === '과매수' || label === '상한선') return '과열선';
|
||
if (label === '과매도' || label === '하한선') return '침체선';
|
||
if (label === '0선' || label === '중립선' || label === '중간선' || label === '기준선') return '중앙선';
|
||
return label;
|
||
}
|
||
|
||
/** normalizeHLines 합성 중앙선(0) 등 레거시 저장값을 registry 기본 가격으로 교정 */
|
||
function resolveMergedHLinePrice(
|
||
roleLabel: string,
|
||
savedPrice: number,
|
||
defPrice: number,
|
||
): number {
|
||
if (roleLabel === '중앙선' && savedPrice === 0 && defPrice !== 0) {
|
||
return defPrice;
|
||
}
|
||
return savedPrice;
|
||
}
|
||
|
||
/** 지표별 누락 수평선 합성 기본값 */
|
||
const HL_SYNTH_DEFAULTS: Record<string, { over: number; mid: number; under: number }> = {
|
||
ADX: { over: 40, mid: 25, under: 20 },
|
||
DMI: { over: 30, mid: 20, under: 10 },
|
||
Stochastic: { over: 80, mid: 50, under: 20 },
|
||
StochRSI: { over: 80, mid: 50, under: 20 },
|
||
};
|
||
|
||
/** Stochastic Slow 기본 수평선 — 과열 80 / 중앙 50(기본 off) / 침체 20 */
|
||
export const STOCHASTIC_DEFAULT_HLINES: HLineDef[] = [
|
||
{ price: 80, color: '#EF5350', label: '과열선', lineStyle: 'dashed', lineWidth: 1 },
|
||
{ price: 50, color: '#607D8B', label: '중앙선', lineStyle: 'dashed', lineWidth: 1, visible: false },
|
||
{ price: 20, color: '#4CAF50', label: '침체선', lineStyle: 'dashed', lineWidth: 1 },
|
||
];
|
||
|
||
/** ADX 기본 수평선 — 강한 추세(과열) / 기준(중앙) / 약한 추세(침체) */
|
||
export const ADX_DEFAULT_HLINES: HLineDef[] = [
|
||
{ price: 40, color: '#EF5350', label: '과열선', lineStyle: 'dashed', lineWidth: 1 },
|
||
{ price: 25, color: '#607D8B', label: '중앙선', lineStyle: 'dashed', lineWidth: 1 },
|
||
{ price: 20, color: '#4CAF50', label: '침체선', lineStyle: 'dashed', lineWidth: 1 },
|
||
];
|
||
|
||
/** DMI 기본 수평선 (업비트: 과열 30 / 중간 20 / 침체 10) */
|
||
export const DMI_DEFAULT_HLINES: HLineDef[] = [
|
||
{ price: 30, color: '#EF5350', label: '과열선', lineStyle: 'dashed', lineWidth: 1 },
|
||
{ price: 20, color: '#607D8B', label: '중앙선', lineStyle: 'dashed', lineWidth: 1 },
|
||
{ price: 10, color: '#4CAF50', label: '침체선', lineStyle: 'dashed', lineWidth: 1 },
|
||
];
|
||
|
||
export function normalizeDmiParams(
|
||
params: Record<string, number | string | boolean>,
|
||
): Record<string, number | string | boolean> {
|
||
const diLength = Math.max(1, Number(params.diLength ?? params.length ?? 14));
|
||
const adxSmoothing = Math.max(1, Number(params.adxSmoothing ?? 14));
|
||
return { ...params, diLength, adxSmoothing };
|
||
}
|
||
|
||
/** 저장된 hlines를 registry 기본값과 역할(라벨) 기준으로 병합 */
|
||
export function mergeHlines(
|
||
saved: HLineDef[] | undefined,
|
||
defaults: HLineDef[] | undefined,
|
||
indicatorType?: string,
|
||
): HLineDef[] {
|
||
const defs = (defaults ?? []).map(h => ({ ...h }));
|
||
if (!defs.length) return (saved ?? []).map(h => ({ ...h }));
|
||
|
||
const savedList = (saved ?? []).map(h => {
|
||
const label = migrateHLineLabel(h.label);
|
||
return label ? { ...h, label } : { ...h };
|
||
});
|
||
|
||
const prices = savedList.map(h => h.price);
|
||
const labelOf = (h: HLineDef) => h.label ?? getHLineLabel(h.price, prices);
|
||
|
||
const legacyMid =
|
||
(indicatorType === 'ADX' || indicatorType === 'DMI') && savedList.length === 1
|
||
? savedList[0]
|
||
: undefined;
|
||
|
||
return defs
|
||
.map(def => {
|
||
const wantLabel = def.label ?? getHLineLabel(def.price, defs.map(d => d.price));
|
||
const match =
|
||
savedList.find(h => labelOf(h) === wantLabel)
|
||
?? (legacyMid && wantLabel === '중앙선' ? legacyMid : undefined);
|
||
if (match) {
|
||
const price = resolveMergedHLinePrice(wantLabel, match.price, def.price);
|
||
return { ...def, ...match, label: wantLabel, price };
|
||
}
|
||
return { ...def, label: wantLabel };
|
||
})
|
||
.concat(
|
||
savedList.filter(h => {
|
||
const lbl = labelOf(h);
|
||
return !defs.some(d => (d.label ?? getHLineLabel(d.price, defs.map(x => x.price))) === lbl);
|
||
}).map(h => ({ ...h, label: labelOf(h) })),
|
||
)
|
||
.sort((a, b) => b.price - a.price);
|
||
}
|
||
|
||
/** 수평선 배열을 과열·중앙·침체 3종으로 정규화 (설정 모달·차트 공통) */
|
||
export function normalizeHLines(
|
||
hlines: HLineDef[],
|
||
indicatorType?: string,
|
||
defaults?: HLineDef[],
|
||
): HLineDef[] {
|
||
let list = defaults?.length ? mergeHlines(hlines, defaults, indicatorType) : [...hlines];
|
||
if (list.length === 0) return list;
|
||
|
||
list = list.map(h => {
|
||
const migrated = migrateHLineLabel(h.label);
|
||
return migrated ? { ...h, label: migrated } : h;
|
||
});
|
||
|
||
const initPrices = list.map(h => h.price);
|
||
const labeled = list.map(h => ({
|
||
...h,
|
||
label: h.label ?? getHLineLabel(h.price, initPrices),
|
||
}));
|
||
const withFixedLabels = labeled.map(h =>
|
||
h.label === '기준선' ? { ...h, label: '중앙선' } : h,
|
||
);
|
||
|
||
const prices = withFixedLabels.map(h => h.price);
|
||
const getLabel = (h: HLineDef) => h.label ?? getHLineLabel(h.price, prices);
|
||
|
||
const hasOver = withFixedLabels.some(h => getLabel(h) === '과열선');
|
||
const hasCenter = withFixedLabels.some(h => getLabel(h) === '중앙선');
|
||
const hasUnder = withFixedLabels.some(h => getLabel(h) === '침체선');
|
||
|
||
if (hasOver && hasCenter && hasUnder) {
|
||
return withFixedLabels.sort((a, b) => b.price - a.price);
|
||
}
|
||
|
||
const result = [...withFixedLabels];
|
||
const spec = indicatorType ? HL_SYNTH_DEFAULTS[indicatorType] : undefined;
|
||
const maxAbsPrice = prices.reduce((m, p) => Math.max(m, Math.abs(p)), 0);
|
||
const synthUpper = maxAbsPrice > 0 ? parseFloat((maxAbsPrice * 1.5).toPrecision(3)) : 100;
|
||
const synthLower = maxAbsPrice > 0 ? -parseFloat((maxAbsPrice * 1.5).toPrecision(3)) : -100;
|
||
|
||
if (!hasOver) {
|
||
result.push({
|
||
price: spec?.over ?? synthUpper,
|
||
color: '#EF5350',
|
||
label: '과열선',
|
||
visible: false,
|
||
lineStyle: 'dashed',
|
||
lineWidth: 1,
|
||
});
|
||
}
|
||
if (!hasCenter) {
|
||
result.push({
|
||
price: spec?.mid ?? 0,
|
||
color: '#607D8B',
|
||
label: '중앙선',
|
||
visible: false,
|
||
lineStyle: 'dashed',
|
||
lineWidth: 1,
|
||
});
|
||
}
|
||
if (!hasUnder) {
|
||
result.push({
|
||
price: spec?.under ?? synthLower,
|
||
color: '#4CAF50',
|
||
label: '침체선',
|
||
visible: false,
|
||
lineStyle: 'dashed',
|
||
lineWidth: 1,
|
||
});
|
||
}
|
||
|
||
return result.sort((a, b) => b.price - a.price);
|
||
}
|
||
|
||
export interface IndicatorDef {
|
||
type: string;
|
||
name: string;
|
||
koreanName: string;
|
||
shortName: string;
|
||
category: IndicatorCategory;
|
||
overlay: boolean;
|
||
defaultParams: Record<string, number | string | boolean>;
|
||
plots: PlotDef[];
|
||
hlines?: HLineDef[];
|
||
returnsMarkers?: boolean;
|
||
description: string;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// Full Indicator Registry (82 standard + 44 candlestick patterns)
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
export const INDICATOR_REGISTRY: IndicatorDef[] = [
|
||
|
||
// ── Moving Averages ──────────────────────────────────────────────────────
|
||
{ type:'SMA', name:'Simple Moving Average', koreanName:'단순이동평균', shortName:'SMA', category:'Moving Averages', overlay:true, defaultParams:createDefaultSmaParams(), plots:createDefaultSmaPlots(), description:'단순 이동평균 (MA1~MA11)' },
|
||
{ type:'EMA', name:'Exponential Moving Average', koreanName:'지수이동평균', shortName:'EMA', category:'Moving Averages', overlay:true, defaultParams:{length:21, src:'close'}, plots:[{id:'plot0',title:'EMA', color:'#FF6D00',type:'line',lineWidth:2}], description:'지수 이동평균' },
|
||
{ type:'WMA', name:'Weighted Moving Average', koreanName:'가중이동평균', shortName:'WMA', category:'Moving Averages', overlay:true, defaultParams:{length:20, src:'close'}, plots:[{id:'plot0',title:'WMA', color:'#00BCD4',type:'line',lineWidth:2}], description:'가중 이동평균' },
|
||
{ type:'HMA', name:'Hull Moving Average', koreanName:'헐이동평균', shortName:'HMA', category:'Moving Averages', overlay:true, defaultParams:{length:16, src:'close'}, plots:[{id:'plot0',title:'HMA', color:'#4CAF50',type:'line',lineWidth:2}], description:'헐 이동평균 (래그 최소)' },
|
||
{ type:'VWMA', name:'Volume Weighted Moving Average', koreanName:'거래량가중이동평균', shortName:'VWMA', category:'Moving Averages', overlay:true, defaultParams:{length:20, src:'close'}, plots:[{id:'plot0',title:'VWMA', color:'#9C27B0',type:'line',lineWidth:2}], description:'거래량 가중 이동평균' },
|
||
{ type:'DEMA', name:'Double Exponential MA', koreanName:'이중지수이동평균', shortName:'DEMA', category:'Moving Averages', overlay:true, defaultParams:{length:21, src:'close'}, plots:[{id:'plot0',title:'DEMA', color:'#F44336',type:'line',lineWidth:2}], description:'이중 지수 이동평균' },
|
||
{ type:'TEMA', name:'Triple Exponential MA', koreanName:'삼중지수이동평균', shortName:'TEMA', category:'Moving Averages', overlay:true, defaultParams:{length:21, src:'close'}, plots:[{id:'plot0',title:'TEMA', color:'#FF9800',type:'line',lineWidth:2}], description:'삼중 지수 이동평균' },
|
||
{ type:'LSMA', name:'Least Squares MA', koreanName:'최소제곱이동평균', shortName:'LSMA', category:'Moving Averages', overlay:true, defaultParams:{length:25, src:'close', offset:0}, plots:[{id:'plot0',title:'LSMA', color:'#00E5FF',type:'line',lineWidth:2}], description:'최소제곱 이동평균' },
|
||
{ type:'ALMA', name:'Arnaud Legoux MA', koreanName:'아르노-레구이동평균', shortName:'ALMA', category:'Moving Averages', overlay:true, defaultParams:{length:21, offset:0.85, sigma:6}, plots:[{id:'plot0',title:'ALMA', color:'#E040FB',type:'line',lineWidth:2}], description:'아르노 레구 이동평균' },
|
||
{ type:'RMA', name:'Smoothed MA (SMMA/RMA)', koreanName:'윌더스무딩이동평균', shortName:'RMA', category:'Moving Averages', overlay:true, defaultParams:{length:14, src:'close'}, plots:[{id:'plot0',title:'RMA', color:'#8BC34A',type:'line',lineWidth:2}], description:'와일더 스무딩 이동평균' },
|
||
{ type:'SMMA', name:'Smoothed Moving Average', koreanName:'스무딩이동평균', shortName:'SMMA', category:'Moving Averages', overlay:true, defaultParams:{length:14, src:'close'}, plots:[{id:'plot0',title:'SMMA', color:'#CDDC39',type:'line',lineWidth:2}], description:'스무딩 이동평균' },
|
||
{ type:'McGinleyDynamic', name:'McGinley Dynamic', koreanName:'맥긴리다이나믹', shortName:'MGD', category:'Moving Averages', overlay:true, defaultParams:{length:14}, plots:[{id:'plot0',title:'MGD', color:'#FF5722',type:'line',lineWidth:2}], description:'맥긴리 다이나믹 MA' },
|
||
{ type:'Median', name:'Median Price', koreanName:'중간값이동평균', shortName:'Med', category:'Moving Averages', overlay:true, defaultParams:{length:14}, plots:[{id:'plot0',title:'Median',color:'#607D8B',type:'line',lineWidth:1}], description:'중간값 (Median) 이동평균' },
|
||
{ type:'TWAP', name:'Time Weighted Average Price', koreanName:'시간가중평균가격', shortName:'TWAP', category:'Moving Averages', overlay:true, defaultParams:{}, plots:[{id:'plot0',title:'TWAP', color:'#795548',type:'line',lineWidth:2}], description:'시간 가중 평균 가격' },
|
||
{ type:'MACross',name:'MA Cross', koreanName:'이동평균교차', shortName:'MACross',category:'Moving Averages', overlay:true, defaultParams:{fastLength:9, slowLength:21, src:'close'},plots:[{id:'plot0',title:'Fast', color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Slow',color:'#FF6D00',type:'line',lineWidth:2}], description:'이동평균 교차 (골든크로스/데드크로스)' },
|
||
|
||
// ── Channels & Bands ──────────────────────────────────────────────────────
|
||
{ type:'BollingerBands', name:'Bollinger Bands', koreanName:'볼린저밴드', shortName:'BB', category:'Channels & Bands', overlay:true, defaultParams:{symbolMode:'main', refSymbol:'', length:20, mult:2, offset:0, src:'close', maType:'SMA'}, plots:[{id:'plot0',title:'중앙값',color:'#FF9800',type:'line',lineWidth:1},{id:'plot1',title:'어퍼',color:'#42A5F5',type:'line',lineWidth:1},{id:'plot2',title:'로우어',color:'#42A5F5',type:'line',lineWidth:1}], description:'볼린저 밴드 (SMA ± σ×곱)' },
|
||
{ type:'KeltnerChannels',name:'Keltner Channels', koreanName:'켈트너채널', shortName:'KC', category:'Channels & Bands', overlay:true, defaultParams:{length:20, multiplier:2}, plots:[{id:'plot0',title:'Upper', color:'#7E57C2',type:'line',lineWidth:1},{id:'plot1',title:'Middle',color:'#EF5350',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#7E57C2',type:'line',lineWidth:1}], description:'켈트너 채널' },
|
||
{ type:'DonchianChannels',name:'Donchian Channels', koreanName:'돈치안채널', shortName:'DC', category:'Channels & Bands', overlay:true, defaultParams:{length:20}, plots:[{id:'plot0',title:'Upper', color:'#4CAF50',type:'line',lineWidth:1},{id:'plot1',title:'Basis',color:'#FFC107',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#4CAF50',type:'line',lineWidth:1}], description:'돈치안 채널' },
|
||
{ type:'Envelope', name:'Envelope', koreanName:'엔벨로프', shortName:'Env', category:'Channels & Bands', overlay:true, defaultParams:{length:20, percent:0.1, src:'close'}, plots:[{id:'plot0',title:'Upper', color:'#009688',type:'line',lineWidth:1},{id:'plot1',title:'Basis',color:'#607D8B',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#009688',type:'line',lineWidth:1}], description:'엔벨로프 채널' },
|
||
{ type:'BBPercentB', name:'BB %B', koreanName:'볼린저밴드 %B', shortName:'BB%B', category:'Channels & Bands', overlay:false,defaultParams:{length:20, mult:2, src:'close'}, plots:[{id:'plot0',title:'%B', color:'#2196F3',type:'line',lineWidth:2}], hlines:[{price:1,color:'#EF5350'},{price:0.5,color:'#607D8B'},{price:0,color:'#4CAF50'}], description:'볼린저밴드 내 가격 위치 (%B)' },
|
||
{ type:'BBBandWidth', name:'BB BandWidth', koreanName:'볼린저밴드폭', shortName:'BBW', category:'Channels & Bands', overlay:false,defaultParams:{length:20, mult:2, src:'close'}, plots:[{id:'plot0',title:'BBW', color:'#9C27B0',type:'line',lineWidth:2}], description:'볼린저밴드 폭 (변동성 측정)' },
|
||
|
||
// ── Oscillators ───────────────────────────────────────────────────────────
|
||
{ type:'RSI', name:'Relative Strength Index', koreanName:'상대강도지수', shortName:'RSI', category:'Oscillators', overlay:false, defaultParams:{length:14, src:'close', maType:'SMA', maLength:14}, plots:[{id:'plot0',title:'RSI',color:'#7E57C2',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#F23645',type:'line',lineWidth:1}], hlines:[{price:70,color:'#EF5350'},{price:50,color:'#607D8B'},{price:30,color:'#4CAF50'}], description:'상대 강도 지수 (0~100)' },
|
||
{ type:'Stochastic', name:'Stochastic Slow', koreanName:'스토케스틱 슬로우', shortName:'Stoch', category:'Oscillators', overlay:false, defaultParams:{kLength:14, dSmoothing:3, smooth:3}, plots:[{id:'plot0',title:'%K',color:'#2196F3',type:'line',lineWidth:1},{id:'plot1',title:'%D',color:'#FF6D00',type:'line',lineWidth:1}], hlines:STOCHASTIC_DEFAULT_HLINES, description:'Stochastic Slow %K/%D (Slowing 적용)' },
|
||
{ type:'StochRSI', name:'Stochastic RSI', koreanName:'스토캐스틱RSI', shortName:'StochRSI',category:'Oscillators',overlay:false, defaultParams:{lengthRSI:14, lengthStoch:14, smoothK:3, smoothD:3, src:'close'}, plots:[{id:'plot0',title:'%K',color:'#2196F3',type:'line',lineWidth:1},{id:'plot1',title:'%D',color:'#FF6D00',type:'line',lineWidth:1}], hlines:STOCHASTIC_DEFAULT_HLINES, description:'스토캐스틱 RSI' },
|
||
{ type:'CCI', name:'Commodity Channel Index', koreanName:'상품채널지수', shortName:'CCI', category:'Oscillators', overlay:false, defaultParams:{length:13, src:'hlc3', maType:'SMA', maLength:20}, plots:[{id:'plot0',title:'CCI',color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#F23645',type:'line',lineWidth:1}], hlines:[{price:100,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-100,color:'#4CAF50'}], description:'상품 채널 지수' },
|
||
{ type:'WilliamsPercentRange',name:'Williams %R', koreanName:'윌리엄스 %R', shortName:'W%R', category:'Oscillators', overlay:false, defaultParams:{length:14, src:'close'}, plots:[{id:'plot0',title:'%R', color:'#9C27B0',type:'line',lineWidth:2}], hlines:[{price:-20,color:'#EF5350'},{price:-50,color:'#607D8B'},{price:-80,color:'#4CAF50'}], description:'윌리엄스 %R 오실레이터' },
|
||
{ type:'AwesomeOscillator', name:'Awesome Oscillator', koreanName:'어썸오실레이터', shortName:'AO', category:'Oscillators', overlay:false, defaultParams:{}, plots:[{id:'plot0',title:'AO',color:'#26A69A',type:'histogram'}], hlines:[{price:0,color:'#607D8B'}], description:'AO 오실레이터' },
|
||
{ type:'ChandeMO', name:'Chande Momentum Oscillator', koreanName:'챈드모멘텀오실레이터', shortName:'CMO', category:'Oscillators', overlay:false, defaultParams:{length:9, src:'close'}, plots:[{id:'plot0',title:'CMO',color:'#4DB6AC',type:'line',lineWidth:2}], hlines:[{price:50,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-50,color:'#4CAF50'}], description:'챈드 모멘텀 오실레이터' },
|
||
{ type:'DPO', name:'Detrended Price Oscillator', koreanName:'비트렌드가격오실레이터',shortName:'DPO', category:'Oscillators', overlay:false, defaultParams:{length:21, src:'close'}, plots:[{id:'plot0',title:'DPO',color:'#FF7043',type:'line',lineWidth:2}], hlines:[{price:0,color:'#607D8B'}], description:'비트렌드 가격 오실레이터' },
|
||
{ type:'RVI', name:'Relative Vigor Index', koreanName:'상대활력지수', shortName:'RVI', category:'Oscillators', overlay:false, defaultParams:{length:10}, plots:[{id:'plot0',title:'RVI', color:'#26A69A',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#EF5350',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B'}], description:'상대 활력 지수' },
|
||
{ type:'FisherTransform', name:'Fisher Transform', koreanName:'피셔변환', shortName:'Fish', category:'Oscillators', overlay:false, defaultParams:{length:9}, plots:[{id:'plot0',title:'Fisher', color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:2.5,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-2.5,color:'#4CAF50'}], description:'피셔 변환 오실레이터' },
|
||
{ type:'UltimateOscillator', name:'Ultimate Oscillator', koreanName:'울티메이트오실레이터', shortName:'UO', category:'Oscillators', overlay:false, defaultParams:{length1:7, length2:14, length3:28}, plots:[{id:'plot0',title:'UO', color:'#7E57C2',type:'line',lineWidth:2}], hlines:[{price:70,color:'#EF5350'},{price:50,color:'#607D8B'},{price:30,color:'#4CAF50'}], description:'울티메이트 오실레이터' },
|
||
{ type:'ConnorsRSI', name:'Connors RSI', koreanName:'코너스RSI', shortName:'CRSI', category:'Oscillators', overlay:false, defaultParams:{rsiLength:3, upDownLength:2, rocLength:100}, plots:[{id:'plot0',title:'CRSI',color:'#AB47BC',type:'line',lineWidth:2}], hlines:[{price:90,color:'#EF5350'},{price:10,color:'#4CAF50'}], description:'코너스 RSI' },
|
||
{ type:'SMIErgodic', name:'SMI Ergodic', koreanName:'SMI에르고딕', shortName:'SMI', category:'Oscillators', overlay:false, defaultParams:{longLength:20, shortLength:5, signalLength:5}, plots:[{id:'plot0',title:'SMI',color:'#26A69A',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#EF5350',type:'line',lineWidth:1}], hlines:[{price:0.4,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-0.4,color:'#4CAF5080'}], description:'SMI 에르고딕' },
|
||
{ type:'SMIErgodicOscillator',name:'SMI Ergodic Oscillator', koreanName:'SMI에르고딕오실레이터', shortName:'SMIO', category:'Oscillators', overlay:false, defaultParams:{longLength:20, shortLength:5, signalLength:5}, plots:[{id:'plot0',title:'SMIO',color:'#00BCD4',type:'histogram'}], hlines:[{price:0.4,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-0.4,color:'#4CAF5080'}], description:'SMI 에르고딕 오실레이터' },
|
||
{ type:'WoodiesCCI', name:'Woodies CCI', koreanName:'우디CCI', shortName:'WCCI', category:'Oscillators', overlay:false, defaultParams:{ccilength:14, turbolen:6}, plots:[{id:'plot0',title:'Turbo',color:'#FF6D00',type:'histogram'},{id:'plot1',title:'CCI',color:'#2962FF',type:'line',lineWidth:2}], hlines:[{price:100,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-100,color:'#4CAF5080'}], description:'우디 CCI' },
|
||
{ type:'RelativeVolatilityIndex',name:'Relative Volatility Index',koreanName:'상대변동성지수', shortName:'RVI2', category:'Oscillators', overlay:false, defaultParams:{length:14, stdevLength:10}, plots:[{id:'plot0',title:'RVI',color:'#FF7043',type:'line',lineWidth:2}], hlines:[{price:60,color:'#EF5350'},{price:40,color:'#4CAF50'}], description:'상대 변동성 지수' },
|
||
{ type:'ChopZone', name:'Chop Zone', koreanName:'변동성구분지표', shortName:'CZ', category:'Oscillators', overlay:false, defaultParams:{length:30, atrLength:1}, plots:[{id:'plot0',title:'CZ', color:'#FF9800',type:'histogram'}], hlines:[], description:'변동성 vs 추세 구분 지표' },
|
||
|
||
// ── Momentum ──────────────────────────────────────────────────────────────
|
||
{ type:'MACD', name:'MACD', koreanName:'이동평균수렴발산', shortName:'MACD', category:'Momentum', overlay:false, defaultParams:{fastLength:12, slowLength:26, signalLength:9, src:'close'}, plots:[{id:'plot0',title:'Histogram',color:'#26A69A',type:'histogram'},{id:'plot1',title:'MACD',color:'#2962FF',type:'line',lineWidth:1},{id:'plot2',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B'}], description:'이동평균 수렴·발산' },
|
||
{ type:'Momentum', name:'Momentum', koreanName:'모멘텀', shortName:'MOM', category:'Momentum', overlay:false, defaultParams:{length:10, src:'close'}, plots:[{id:'plot0',title:'MOM', color:'#26A69A',type:'line',lineWidth:2}], hlines:[{price:0,color:'#607D8B'}], description:'모멘텀 지표' },
|
||
{ type:'ROC', name:'Rate of Change', koreanName:'가격변화율', shortName:'ROC', category:'Momentum', overlay:false, defaultParams:{length:9, src:'close'}, plots:[{id:'plot0',title:'ROC', color:'#00BCD4',type:'line',lineWidth:2}], hlines:[{price:10,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-10,color:'#4CAF5080'}], description:'가격 변화율 (%)' },
|
||
{ type:'TSI', name:'True Strength Index', koreanName:'참강도지수', shortName:'TSI', category:'Momentum', overlay:false, defaultParams:{longLength:25, shortLength:13, signalLength:13, src:'close'}, plots:[{id:'plot0',title:'TSI', color:'#2196F3',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:25,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-25,color:'#4CAF50'}], description:'참 강도 지수' },
|
||
{ type:'TRIX', name:'TRIX', koreanName:'삼중지수변화율', shortName:'TRIX', category:'Momentum', overlay:false, defaultParams:{length:12, signalLength:9}, plots:[{id:'plot0',title:'TRIX', color:'#26A69A',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B', label:'중앙선'}], description:'삼중 지수 변화율 (ln→3×EMA→Δ×10000)' },
|
||
{ type:'KnowSureThing', name:'Know Sure Thing (KST)', koreanName:'KST모멘텀', shortName:'KST', category:'Momentum', overlay:false, defaultParams:{}, plots:[{id:'plot0',title:'KST', color:'#9C27B0',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF9800',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B'}], description:'KST 모멘텀 지표' },
|
||
{ type:'CoppockCurve', name:'Coppock Curve', koreanName:'콥독커브', shortName:'Copp', category:'Momentum', overlay:false, defaultParams:{}, plots:[{id:'plot0',title:'Coppock',color:'#E91E63',type:'line',lineWidth:2}], hlines:[{price:0,color:'#607D8B'}], description:'콥독 커브 장기 모멘텀' },
|
||
{ type:'BullBearPower', name:'Bull Bear Power', koreanName:'매수매도세력', shortName:'BBP', category:'Momentum', overlay:false, defaultParams:{length:13, src:'close'}, plots:[{id:'plot0',title:'BBP', color:'#26A69A',type:'histogram'}], hlines:[{price:0,color:'#607D8B'}], description:'매수/매도 세력 비교' },
|
||
{ type:'ElderForceIndex', name:'Elder Force Index', koreanName:'엘더강도지수', shortName:'EFI', category:'Momentum', overlay:false, defaultParams:{length:13}, plots:[{id:'plot0',title:'EFI', color:'#00BCD4',type:'histogram'}], hlines:[{price:0,color:'#607D8B'}], description:'엘더 강도 지수' },
|
||
{ type:'PriceOscillator', name:'Price Oscillator (PPO)', koreanName:'가격오실레이터', shortName:'PPO', category:'Momentum', overlay:false, defaultParams:{fastLength:12, slowLength:26, src:'close'}, plots:[{id:'plot0',title:'PPO', color:'#FF7043',type:'line',lineWidth:2}], hlines:[{price:2,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-2,color:'#4CAF5080'}], description:'가격 오실레이터 (MACD의 %)' },
|
||
{ type:'EaseOfMovement', name:'Ease of Movement', koreanName:'이동용이성', shortName:'EOM', category:'Momentum', overlay:false, defaultParams:{length:14}, plots:[{id:'plot0',title:'EOM', color:'#009688',type:'line',lineWidth:2}], hlines:[{price:0,color:'#607D8B'}], description:'이동 용이성 지표' },
|
||
|
||
// ── Trend ──────────────────────────────────────────────────────────────
|
||
{ type:'ADX', name:'Average Directional Index', koreanName:'평균방향성지수', shortName:'ADX', category:'Trend', overlay:false, defaultParams:{adxSmoothing:14, diLength:14}, plots:[{id:'plot0',title:'ADX',color:'#FF6D00',type:'line',lineWidth:2},{id:'plot1',title:'+DI',color:'#4CAF50',type:'line',lineWidth:1},{id:'plot2',title:'-DI',color:'#EF5350',type:'line',lineWidth:1}], hlines:ADX_DEFAULT_HLINES, description:'평균 방향성 지수 (추세 강도)' },
|
||
{ type:'DMI', name:'Directional Movement Index', koreanName:'방향성이동지수', shortName:'DMI', category:'Trend', overlay:false, defaultParams:{diLength:14, adxSmoothing:14}, plots:[{id:'plot0',title:'+DI',color:'#2962FF',type:'line',lineWidth:1},{id:'plot1',title:'-DI',color:'#FF6D00',type:'line',lineWidth:1},{id:'plot2',title:'DX',color:'#FF9800',type:'line',lineWidth:1},{id:'plot3',title:'ADX',color:'#E91E63',type:'line',lineWidth:1},{id:'plot4',title:'ADXR',color:'#9C27B0',type:'line',lineWidth:1}], hlines:DMI_DEFAULT_HLINES, description:'방향성 이동 지수 (+DI·-DI·DX·ADX·ADXR)' },
|
||
{ type:'Aroon', name:'Aroon', koreanName:'아룬지표', shortName:'Aroon', category:'Trend', overlay:false, defaultParams:{length:14}, plots:[{id:'plot0',title:'Up', color:'#4CAF50',type:'line',lineWidth:1},{id:'plot1',title:'Down',color:'#EF5350',type:'line',lineWidth:1}], hlines:[{price:70,color:'#4CAF5060'},{price:30,color:'#EF535060'}], description:'아룬 추세 지표' },
|
||
{ type:'IchimokuCloud', name:'Ichimoku Cloud', koreanName:'일목균형표', shortName:'Ichi', category:'Trend', overlay:true, defaultParams:{conversionPeriods:9, basePeriods:26, laggingSpan2Periods:52, displacement:26}, plots:[{id:'plot0',title:'전환선',color:'#2196F3',type:'line',lineWidth:1},{id:'plot1',title:'기준선',color:'#EF5350',type:'line',lineWidth:1},{id:'plot2',title:'후행스팬',color:'#4CAF50',type:'line',lineWidth:1},{id:'plot3',title:'선행스팬1',color:'#EF5350',type:'line',lineWidth:1},{id:'plot4',title:'선행스팬2',color:'#9C27B0',type:'line',lineWidth:1}], description:'일목균형표' },
|
||
{ type:'Supertrend', name:'Supertrend', koreanName:'슈퍼트렌드', shortName:'ST', category:'Trend', overlay:true, defaultParams:{atrPeriod:10, factor:3}, plots:[{id:'plot0',title:'ST', color:'#4CAF50',type:'line',lineWidth:2}], description:'슈퍼트렌드 (ATR 기반)' },
|
||
{ type:'ParabolicSAR', name:'Parabolic SAR', koreanName:'파라볼릭SAR', shortName:'SAR', category:'Trend', overlay:true, defaultParams:{start:0.02, increment:0.02, maximum:0.2}, plots:[{id:'plot0',title:'SAR', color:'#FF6D00',type:'scatter'}], description:'파라볼릭 SAR' },
|
||
{ type:'Choppiness', name:'Choppiness Index', koreanName:'불규칙성지수', shortName:'CHOP', category:'Trend', overlay:false, defaultParams:{length:14}, plots:[{id:'plot0',title:'CHOP',color:'#FFC107',type:'line',lineWidth:2}], hlines:[{price:61.8,color:'#EF535080'},{price:38.2,color:'#4CAF5080'}], description:'변동성 vs 추세 구분 (61.8이하=추세)' },
|
||
{ type:'MassIndex', name:'Mass Index', koreanName:'매스인덱스', shortName:'MI', category:'Trend', overlay:false, defaultParams:{length:25, length2:9}, plots:[{id:'plot0',title:'MI', color:'#E91E63',type:'line',lineWidth:2}], hlines:[{price:27,color:'#EF535080'},{price:26.5,color:'#4CAF5080'}], description:'매스 인덱스 (반전 탐지)' },
|
||
{ type:'VortexIndicator', name:'Vortex Indicator', koreanName:'보텍스지표', shortName:'VI', category:'Trend', overlay:false, defaultParams:{length:14}, plots:[{id:'plot0',title:'VI+', color:'#4CAF50',type:'line',lineWidth:1},{id:'plot1',title:'VI-',color:'#EF5350',type:'line',lineWidth:1}], hlines:[{price:1,color:'#607D8B'}], description:'보텍스 지표 (추세 전환 확인)' },
|
||
{ type:'WilliamsAlligator',name:'Williams Alligator', koreanName:'윌리엄스악어', shortName:'Alli', category:'Trend', overlay:true, defaultParams:{}, plots:[{id:'plot0',title:'Jaw', color:'#2196F3',type:'line',lineWidth:1},{id:'plot1',title:'Teeth',color:'#4CAF50',type:'line',lineWidth:1},{id:'plot2',title:'Lips',color:'#EF5350',type:'line',lineWidth:1}], description:'윌리엄스 악어 (3개 SMMA)' },
|
||
{ type:'TrendStrengthIndex',name:'Trend Strength Index', koreanName:'추세강도지수', shortName:'TSI2', category:'Trend', overlay:false, defaultParams:{}, plots:[{id:'plot0',title:'TSI', color:'#9C27B0',type:'line',lineWidth:2}], description:'추세 강도 지수' },
|
||
{ type:'BBTrend', name:'BB Trend', koreanName:'볼린저밴드추세', shortName:'BBT', category:'Trend', overlay:false, defaultParams:{length:20, mult:2}, plots:[{id:'plot0',title:'BBTrend',color:'#FF7043',type:'histogram'}], hlines:[{price:0,color:'#607D8B'}], description:'볼린저밴드 기반 추세 방향' },
|
||
{ type:'ChandeKrollStop', name:'Chande Kroll Stop', koreanName:'챈드크롤스탑', shortName:'CKS', category:'Trend', overlay:true, defaultParams:{p:10, q:9, x:3}, plots:[{id:'plot0',title:'Stop+',color:'#4CAF50',type:'line',lineWidth:2},{id:'plot1',title:'Stop-',color:'#EF5350',type:'line',lineWidth:2}], description:'챈드 크롤 스탑' },
|
||
{ type:'ZigZag', name:'ZigZag', koreanName:'지그재그', shortName:'ZZ', category:'Trend', overlay:true, defaultParams:{deviation:5, depth:10, backstep:3}, plots:[{id:'plot0',title:'ZZ', color:'#FF5722',type:'line',lineWidth:2}], description:'지그재그 (스윙 고점·저점 연결)' },
|
||
{ type:'WilliamsFractals',name:'Williams Fractals', koreanName:'윌리엄스프랙탈', shortName:'Frac', category:'Trend', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'윌리엄스 프랙탈 (고점·저점 마커)' },
|
||
{ type:'RankCorrelationIndex',name:'Rank Correlation Index (RCI)',koreanName:'순위상관지수', shortName:'RCI', category:'Oscillators', overlay:false, defaultParams:{length1:9, length2:26, length3:52}, plots:[{id:'plot0',title:'RCI9',color:'#2196F3',type:'line',lineWidth:2},{id:'plot1',title:'RCI26',color:'#FF9800',type:'line',lineWidth:1}], hlines:[{price:80,color:'#EF535080'},{price:-80,color:'#4CAF5080'}], description:'순위 상관 지수 (추세 강도)' },
|
||
|
||
// ── Volatility ────────────────────────────────────────────────────────────
|
||
{ type:'ATR', name:'Average True Range', koreanName:'평균실제범위', shortName:'ATR', category:'Volatility', overlay:false, defaultParams:{length:14, smoothing:'RMA'}, plots:[{id:'plot0',title:'ATR', color:'#FF9800',type:'line',lineWidth:2}], description:'평균 진범위 (변동성)' },
|
||
{ type:'ADR', name:'Average Day Range', koreanName:'평균일일범위', shortName:'ADR', category:'Volatility', overlay:false, defaultParams:{length:14}, plots:[{id:'plot0',title:'ADR', color:'#FF5722',type:'line',lineWidth:2}], description:'평균 일일 범위' },
|
||
{ type:'StandardDeviation', name:'Standard Deviation', koreanName:'표준편차', shortName:'StdDev', category:'Volatility', overlay:false, defaultParams:{length:20, src:'close'}, plots:[{id:'plot0',title:'StdDev',color:'#9C27B0',type:'line',lineWidth:2}], description:'표준 편차 (가격 변동성)' },
|
||
{ type:'HistoricalVolatility',name:'Historical Volatility', koreanName:'역사적변동성', shortName:'HV', category:'Volatility', overlay:false, defaultParams:{length:20, annualBars:252}, plots:[{id:'plot0',title:'HV', color:'#E91E63',type:'line',lineWidth:2}], description:'역사적 변동성 (연화)' },
|
||
|
||
// ── Volume ────────────────────────────────────────────────────────────────
|
||
{ type:'OBV', name:'On Balance Volume', koreanName:'잔량균형지수', shortName:'OBV', category:'Volume', overlay:false, defaultParams:{maType:'SMA', maLength:9}, plots:[{id:'plot0',title:'OBV', color:'#2196F3',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF9800',type:'line',lineWidth:1}], hlines:[], description:'잔량 균형 지수 (OBV + 이동평균 신호선)' },
|
||
{ 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:'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:'차이킨 오실레이터' },
|
||
{ type:'PVT', name:'Price Volume Trend', koreanName:'가격거래량추세', shortName:'PVT', category:'Volume', overlay:false, defaultParams:{}, plots:[{id:'plot0',title:'PVT', color:'#7E57C2',type:'line',lineWidth:2}], description:'가격 거래량 추세' },
|
||
{ type:'NetVolume', name:'Net Volume', koreanName:'순거래량', shortName:'NV', category:'Volume', overlay:false, defaultParams:{}, plots:[{id:'plot0',title:'NV', color:'#26A69A',type:'histogram'}], hlines:[{price:0,color:'#607D8B'}], description:'순 거래량 (매수-매도)' },
|
||
{ type:'VolumeDelta', name:'Volume Delta', koreanName:'거래량델타', shortName:'VD', category:'Volume', overlay:false, defaultParams:{}, plots:[{id:'plot0',title:'VD', color:'#2196F3',type:'histogram'}], hlines:[{price:0,color:'#607D8B'}], description:'볼륨 델타 (매수/매도 차이)' },
|
||
{ type:'CumulativeVolumeDelta',name:'Cumulative Volume Delta', koreanName:'누적거래량델타', shortName:'CVD', category:'Volume', overlay:false, defaultParams:{}, plots:[{id:'plot0',title:'CVD', color:'#E91E63',type:'line',lineWidth:2}], hlines:[{price:0,color:'#607D8B'}], description:'누적 볼륨 델타' },
|
||
{ type:'VolumeOscillator', name:'Volume Oscillator', koreanName:'거래량오실레이터', shortName:'VO', category:'Volume', overlay:false, defaultParams:{shortLength:5, longLength:10}, plots:[{id:'plot0',title:'VO', color:'#FF7043',type:'histogram'}], hlines:[{price:10,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-10,color:'#4CAF5080'}], description:'볼륨 오실레이터' },
|
||
{ type:'KlingerOscillator', name:'Klinger Oscillator', koreanName:'클링거오실레이터', shortName:'KVO', category:'Volume', overlay:false, defaultParams:{fastLength:34, slowLength:55, signalLength:13}, plots:[{id:'plot0',title:'KVO',color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B'}], description:'클링거 오실레이터' },
|
||
{ type:'RelativeVolumeAtTime',name:'Relative Volume at Time', koreanName:'시간대별상대거래량', shortName:'RVAT', category:'Volume', overlay:false, defaultParams:{length:24, sessionType:'Regular'}, plots:[{id:'plot0',title:'RVAT', color:'#9C27B0',type:'histogram'}], hlines:[{price:1,color:'#607D8B'}], description:'시간대별 상대 거래량' },
|
||
|
||
// ── Candlestick Patterns ───────────────────────────────────────────────────
|
||
{ type:'Hammer', name:'Hammer', koreanName:'망치형', shortName:'H', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'망치형 (상승 반전)' },
|
||
{ type:'InvertedHammer', name:'Inverted Hammer', koreanName:'역망치형', shortName:'IH', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'역망치형 (상승 반전)' },
|
||
{ type:'HangingMan', name:'Hanging Man', koreanName:'교수형', shortName:'HM', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'교수형 (하락 반전)' },
|
||
{ type:'ShootingStar', name:'Shooting Star', koreanName:'슈팅스타', shortName:'SS', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'슈팅스타 (하락 반전)' },
|
||
{ type:'Doji', name:'Doji', koreanName:'도지형', shortName:'Doji', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'도지형' },
|
||
{ type:'DragonflyDoji', name:'Dragonfly Doji', koreanName:'잠자리도지', shortName:'DDoji', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'잠자리 도지' },
|
||
{ type:'GravestoneDoji', name:'Gravestone Doji', koreanName:'비석도지', shortName:'GDoji', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'비석 도지' },
|
||
{ type:'EngulfingBullish', name:'Bullish Engulfing', koreanName:'강세포용형', shortName:'BE', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'강세 포용형' },
|
||
{ type:'EngulfingBearish', name:'Bearish Engulfing', koreanName:'약세포용형', shortName:'BrE', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'약세 포용형' },
|
||
{ type:'HaramiBullish', name:'Bullish Harami', koreanName:'강세하라미', shortName:'BH', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'강세 하라미' },
|
||
{ type:'HaramiBearish', name:'Bearish Harami', koreanName:'약세하라미', shortName:'BrH', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'약세 하라미' },
|
||
{ type:'MorningStar', name:'Morning Star', koreanName:'샛별형', shortName:'MS', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'샛별형 (강세 반전)' },
|
||
{ type:'EveningStar', name:'Evening Star', koreanName:'저녁별형', shortName:'ES', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'저녁별형 (약세 반전)' },
|
||
{ type:'MorningDojiStar', name:'Morning Doji Star', koreanName:'도지샛별형', shortName:'MDS', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'도지 샛별형' },
|
||
{ type:'EveningDojiStar', name:'Evening Doji Star', koreanName:'도지저녁별형', shortName:'EDS', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'도지 저녁별형' },
|
||
{ type:'ThreeWhiteSoldiers', name:'Three White Soldiers', koreanName:'삼백병사형', shortName:'3WS', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'삼병 (강세 패턴)' },
|
||
{ type:'ThreeBlackCrows', name:'Three Black Crows', koreanName:'흑삼병형', shortName:'3BC', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'흑삼병 (약세 패턴)' },
|
||
{ type:'Piercing', name:'Piercing Line', koreanName:'관통형', shortName:'PL', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'관통형 (상승 반전)' },
|
||
{ type:'DarkCloudCover', name:'Dark Cloud Cover', koreanName:'먹구름형', shortName:'DCC', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'먹구름형 (하락 반전)' },
|
||
{ type:'TweezerBottom', name:'Tweezer Bottom', koreanName:'집게바닥형', shortName:'TwB', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'집게바닥형 (상승 반전)' },
|
||
{ type:'TweezerTop', name:'Tweezer Top', koreanName:'집게천장형', shortName:'TwT', category:'Candlestick Patterns', overlay:true, defaultParams:{}, plots:[], returnsMarkers:true, description:'집게천장형 (하락 반전)' },
|
||
];
|
||
|
||
export function getIndicatorDef(type: string): IndicatorDef | undefined {
|
||
return INDICATOR_REGISTRY.find(d => d.type === type);
|
||
}
|
||
|
||
/** 실시간 차트 pane·범례 타이틀 (심리도·투자심리도·이격도 등 한글명 우선) */
|
||
export function getIndicatorChartTitle(type: string): string {
|
||
const resolved = type === 'NewPsychological' ? 'Psychological' : type;
|
||
const def = getIndicatorDef(resolved);
|
||
if (!def) return type;
|
||
if (
|
||
resolved === 'Psychological'
|
||
|| resolved === 'InvestPsychological'
|
||
|| resolved === 'Disparity'
|
||
) {
|
||
return def.koreanName;
|
||
}
|
||
return def.shortName;
|
||
}
|
||
|
||
/**
|
||
* DB/슬롯에 일부 플롯만 저장된 경우 registry 기본 플롯과 병합.
|
||
* (예: CCI plot0만 있으면 신호선 plot1 이 설정·차트에서 누락됨)
|
||
*/
|
||
export function mergePlotDefs(saved: PlotDef[] | undefined, defaults: PlotDef[]): PlotDef[] {
|
||
if (!defaults.length) return (saved ?? []).map(p => ({ ...p }));
|
||
const savedMap = new Map((saved ?? []).map(p => [p.id, p]));
|
||
return defaults.map(def => {
|
||
const s = savedMap.get(def.id);
|
||
return s ? { ...def, ...s, id: def.id, title: def.title } : { ...def };
|
||
});
|
||
}
|
||
|
||
/** 업비트 OBV 신호선 MA 종류 (단순·지수·가중만) */
|
||
export const OBV_MA_TYPE_OPTIONS = ['SMA', 'EMA', 'WMA'] as const;
|
||
export type ObvMaType = (typeof OBV_MA_TYPE_OPTIONS)[number];
|
||
|
||
export function normalizeObvParams(
|
||
params: Record<string, number | string | boolean>,
|
||
): Record<string, number | string | boolean> {
|
||
const raw = String(params.maType ?? 'SMA');
|
||
const maType: ObvMaType = raw === 'EMA' || raw === 'WMA' ? raw : 'SMA';
|
||
const maLength = Math.max(1, Number(params.maLength ?? 9) || 9);
|
||
return { ...params, maType, maLength };
|
||
}
|
||
|
||
/** 차트·설정 모달용 — 파라미터·플롯·가시성을 registry 기본값과 병합 */
|
||
export function enrichIndicatorConfig(
|
||
cfg: import('../types').IndicatorConfig
|
||
): import('../types').IndicatorConfig {
|
||
const resolvedType = resolvePsychologicalIndicatorType(cfg.type);
|
||
const cfgNorm = resolvedType !== cfg.type ? { ...cfg, type: resolvedType } : cfg;
|
||
const def = getIndicatorDef(cfgNorm.type);
|
||
if (!def) return cfgNorm;
|
||
|
||
if (cfgNorm.type === 'SMA') {
|
||
return normalizeSmaConfig({
|
||
...cfgNorm,
|
||
params: {
|
||
...(def.defaultParams as Record<string, number | string | boolean>),
|
||
...cfg.params,
|
||
},
|
||
plots: mergePlotDefs(cfg.plots, def.plots),
|
||
});
|
||
}
|
||
|
||
if (cfgNorm.type === 'IchimokuCloud') {
|
||
return normalizeIchimokuConfig({
|
||
...cfgNorm,
|
||
params: {
|
||
...(def.defaultParams as Record<string, number | string | boolean>),
|
||
...cfgNorm.params,
|
||
},
|
||
plots: mergeIchimokuPlots(cfgNorm.plots, def.plots),
|
||
});
|
||
}
|
||
|
||
let params = {
|
||
...(def.defaultParams as Record<string, number | string | boolean>),
|
||
...cfgNorm.params,
|
||
};
|
||
if (cfgNorm.type === 'OBV') {
|
||
params = normalizeObvParams(params);
|
||
}
|
||
if (cfgNorm.type === 'Psychological' || cfgNorm.type === 'InvestPsychological') {
|
||
params = normalizePsychologicalParams(cfgNorm.type, params);
|
||
}
|
||
if (cfgNorm.type === 'BollingerBands') {
|
||
params = normalizeBollingerParams(params);
|
||
}
|
||
if (cfgNorm.type === 'DMI') {
|
||
params = normalizeDmiParams(params);
|
||
}
|
||
const plots = cfgNorm.type === 'BollingerBands'
|
||
? mergeBbPlots(cfgNorm.plots, def.plots)
|
||
: mergePlotDefs(cfgNorm.plots, def.plots);
|
||
const plotVisibility: Record<string, boolean> = { ...cfgNorm.plotVisibility };
|
||
for (const p of plots) {
|
||
if (plotVisibility[p.id] === undefined) plotVisibility[p.id] = true;
|
||
}
|
||
if (cfgNorm.type === 'OBV') {
|
||
plotVisibility.plot1 = plotVisibility.plot1 !== false;
|
||
} else if ((cfgNorm.type === 'CCI' || cfgNorm.type === 'RSI') && params.maType === 'None') {
|
||
plotVisibility.plot1 = false;
|
||
}
|
||
|
||
const out: import('../types').IndicatorConfig = {
|
||
...cfgNorm,
|
||
params,
|
||
plots,
|
||
plotVisibility,
|
||
hlines: normalizeHLines(cfgNorm.hlines ?? [], cfgNorm.type, def.hlines),
|
||
};
|
||
if (cfgNorm.type === 'BollingerBands') {
|
||
out.bandBackground = resolveBbBandBackground(cfgNorm.bandBackground);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
export function toBars(ohlcv: OHLCVBar[]): Bar[] {
|
||
return ohlcv as unknown as Bar[];
|
||
}
|
||
|
||
export type PlotData = Array<{ time: number; value: number; color?: string }>;
|
||
export type IndicatorResult = Record<string, PlotData>;
|
||
|
||
export interface MarkerData {
|
||
time: number;
|
||
position: 'aboveBar' | 'belowBar';
|
||
shape: 'arrowUp' | 'arrowDown' | 'circle' | 'square';
|
||
color: string;
|
||
text: string;
|
||
}
|
||
|
||
/**
|
||
* 프로그램 내부 파라미터 키 → lightweight-charts-indicators 라이브러리 파라미터 키 변환.
|
||
*
|
||
* 라이브러리가 기대하는 파라미터 이름이 내부 registry/DB 키와 다른 경우만 등록.
|
||
* indicatorRegistry.ts defaultParams 와 라이브러리 defaultInputs 를 대조하여 작성.
|
||
*/
|
||
const PARAM_KEY_MAP: Record<string, Record<string, string>> = {
|
||
// Stochastic: 내부 {kLength, smooth, dSmoothing} → 라이브러리 {periodK, smoothK, periodD}
|
||
Stochastic: {
|
||
kLength: 'periodK',
|
||
smooth: 'smoothK',
|
||
dSmoothing: 'periodD',
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 지표 파라미터를 라이브러리가 인식하는 키 이름으로 변환한다.
|
||
* 매핑이 없는 지표는 원본을 그대로 반환.
|
||
*/
|
||
function translateParams(
|
||
type: string,
|
||
params: Record<string, number | string | boolean>
|
||
): Record<string, number | string | boolean> {
|
||
const keyMap = PARAM_KEY_MAP[type];
|
||
if (!keyMap) return params;
|
||
|
||
const translated: Record<string, number | string | boolean> = {};
|
||
for (const [k, v] of Object.entries(params)) {
|
||
translated[keyMap[k] ?? k] = v;
|
||
}
|
||
return translated;
|
||
}
|
||
|
||
const CUSTOM_CALCULATORS: Record<
|
||
string,
|
||
(bars: OHLCVBar[], params: Record<string, number | string | boolean>) => Record<string, PlotData>
|
||
> = {};
|
||
|
||
async function loadCustomCalculators() {
|
||
if (Object.keys(CUSTOM_CALCULATORS).length > 0) return;
|
||
const c = await import('./customIndicators');
|
||
CUSTOM_CALCULATORS.TRIX = c.calcTRIXWithSignal;
|
||
CUSTOM_CALCULATORS.OBV = c.calcOBVWithMA;
|
||
CUSTOM_CALCULATORS.VR = c.calcVR;
|
||
CUSTOM_CALCULATORS.Disparity = c.calcDisparityUpbit;
|
||
CUSTOM_CALCULATORS.Psychological = c.calcPsychological;
|
||
CUSTOM_CALCULATORS.NewPsychological = c.calcPsychological;
|
||
CUSTOM_CALCULATORS.InvestPsychological = c.calcInvestPsychological;
|
||
CUSTOM_CALCULATORS.BollingerBands = c.calcBollingerBands;
|
||
CUSTOM_CALCULATORS.DMI = c.calcDMI;
|
||
}
|
||
|
||
/** 차트 심볼·타임프레임 (BB 다른 심볼용) */
|
||
let chartContext: { market: string; timeframe: Timeframe } = { market: '', timeframe: '1D' };
|
||
|
||
export function setIndicatorChartContext(market: string, timeframe: Timeframe): void {
|
||
chartContext = { market, timeframe };
|
||
}
|
||
|
||
export async function calculateIndicator(
|
||
type: string,
|
||
bars: OHLCVBar[],
|
||
params: Record<string, number | string | boolean>
|
||
): Promise<{ plots: IndicatorResult; markers?: MarkerData[] }> {
|
||
if (type === 'SMA') {
|
||
return { plots: calculateSmaMulti(bars, params) };
|
||
}
|
||
|
||
let calcBars = bars;
|
||
let calcParams = params;
|
||
if (type === 'BollingerBands') {
|
||
calcParams = normalizeBollingerParams(params);
|
||
calcBars = await resolveBollingerBars(bars, calcParams, chartContext.timeframe);
|
||
}
|
||
|
||
await loadCustomCalculators();
|
||
const custom = CUSTOM_CALCULATORS[type];
|
||
if (custom) {
|
||
return { plots: custom(calcBars, calcParams) as IndicatorResult };
|
||
}
|
||
|
||
const b = toBars(bars);
|
||
const lib = await import('lightweight-charts-indicators');
|
||
const ind = (lib as unknown as Record<string, {
|
||
calculate: (b: Bar[], p: unknown) => { plots?: Record<string, PlotData>; markers?: MarkerData[] }
|
||
}>)[type];
|
||
if (!ind?.calculate) throw new Error(`Unknown indicator: ${type}`);
|
||
// 파라미터 키 이름을 라이브러리 기대값으로 변환 후 전달
|
||
const result = ind.calculate(b, translateParams(type, params));
|
||
return {
|
||
plots: (result.plots ?? {}) as IndicatorResult,
|
||
markers: result.markers as MarkerData[] | undefined,
|
||
};
|
||
}
|