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 = { 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, ): Record { 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; 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); } const DSL_DISPLAY_ALIASES: Record = { NEW_HIGH: { koreanName: '신고가', shortName: 'NH' }, NEW_LOW: { koreanName: '신저가', shortName: 'NL' }, DONCHIAN: { koreanName: '돈치안채널', shortName: 'DC' }, }; /** 가상투자·조건 목록 — 한글명 (영문약어) 예: 상품채널지수 (CCI) */ export function formatIndicatorDisplayLabel(indicatorType: string): string { const alias = DSL_DISPLAY_ALIASES[indicatorType]; if (alias) { return `${alias.koreanName} (${alias.shortName})`; } const def = getIndicatorDef(indicatorType); const abbr = def?.shortName ?? indicatorType; const ko = def?.koreanName ?? abbr; if (!ko || ko === abbr) return abbr; if (ko.includes(`(${abbr})`)) return ko; return `${ko} (${abbr})`; } /** 실시간 차트 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, ): Record { 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), ...cfg.params, }, plots: mergePlotDefs(cfg.plots, def.plots), }); } if (cfgNorm.type === 'IchimokuCloud') { return normalizeIchimokuConfig({ ...cfgNorm, params: { ...(def.defaultParams as Record), ...cfgNorm.params, }, plots: mergeIchimokuPlots(cfgNorm.plots, def.plots), }); } let params = { ...(def.defaultParams as Record), ...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 = { ...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; 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> = { // Stochastic: 내부 {kLength, smooth, dSmoothing} → 라이브러리 {periodK, smoothK, periodD} Stochastic: { kLength: 'periodK', smooth: 'smoothK', dSmoothing: 'periodD', }, }; /** * 지표 파라미터를 라이브러리가 인식하는 키 이름으로 변환한다. * 매핑이 없는 지표는 원본을 그대로 반환. */ function translateParams( type: string, params: Record ): Record { const keyMap = PARAM_KEY_MAP[type]; if (!keyMap) return params; const translated: Record = {}; for (const [k, v] of Object.entries(params)) { translated[keyMap[k] ?? k] = v; } return translated; } const CUSTOM_CALCULATORS: Record< string, (bars: OHLCVBar[], params: Record) => Record > = {}; 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 ): 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 { plots?: Record; 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, }; }