@@ -88,6 +88,7 @@ import {
isValuePeriodFieldStored ,
parseThresholdField ,
resolveFieldCustomKind ,
resolveRightFieldCustomKind ,
getCompositeLeftCandleType ,
getCompositeRightCandleType ,
setCompositeLeftCandleType ,
@@ -143,9 +144,11 @@ interface HLThresh { over?: number; mid?: number; under?: number; }
/** 하드코딩 기본값 — activeIndicators가 없을 때 폴백 */
export const DEF_DEFAULTS = {
rsiPeriod : 9 ,
rsiSignal : 14 ,
macdFast : 12 , macdSlow : 26 , macdSignal : 9 ,
cciPeriod : 13 ,
stochK : 12 , stochD : 5 ,
cciSignal : 10 ,
stochK : 12 , stochSmooth : 3 , stochD : 5 ,
adxPeriod : 14 ,
trixPeriod : 12 , trixSignal : 9 ,
dmiPeriod : 14 ,
@@ -285,11 +288,14 @@ export function buildDef(activeIndicators: IndicatorConfig[]): DefType {
return {
rsiPeriod : num ( rsi , 'length' , DEF_DEFAULTS . rsiPeriod ) ,
rsiSignal : num ( rsi , 'maLength' , DEF_DEFAULTS . rsiSignal ) ,
macdFast : num ( macd , 'fastLength' , DEF_DEFAULTS . macdFast ) ,
macdSlow : num ( macd , 'slowLength' , DEF_DEFAULTS . macdSlow ) ,
macdSignal : num ( macd , 'signalLength' , DEF_DEFAULTS . macdSignal ) ,
cciPeriod : num ( cci , 'length' , DEF_DEFAULTS . cciPeriod ) ,
cciSignal : num ( cci , 'maLength' , DEF_DEFAULTS . cciSignal ) ,
stochK : num ( stoch , 'kLength' , DEF_DEFAULTS . stochK ) ,
stochSmooth : num ( stoch , 'smooth' , DEF_DEFAULTS . stochSmooth ) ,
stochD : num ( stoch , 'dSmoothing' , DEF_DEFAULTS . stochD ) ,
adxPeriod : num ( adx , 'adxSmoothing' , DEF_DEFAULTS . adxPeriod ) ,
trixPeriod : num ( trix , 'length' , DEF_DEFAULTS . trixPeriod ) ,
@@ -431,18 +437,44 @@ const ICHIMOKU_CONDS = [
type Opt = { value : string ; label : string } ;
const NONE : Opt = { value : 'NONE' , label : '선택안함' } ;
export const getFieldOpts = (
export type FieldOptSlot = 'left' | 'right' ;
function mergeFieldOpts ( . . . lists : Opt [ ] [ ] ) : Opt [ ] {
const seen = new Set < string > ( ) ;
const out : Opt [ ] = [ ] ;
for ( const list of lists ) {
for ( const o of list ) {
if ( seen . has ( o . value ) ) continue ;
seen . add ( o . value ) ;
out . push ( o ) ;
}
}
return out ;
}
function buildThresholdOpts (
th : { over : number ; mid : number ; under : number } ,
overTerm : string ,
underTerm : string ,
includeMid = true ,
) : Opt [ ] {
const opts : Opt [ ] = [ { value : THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ th . over } ) ` } ] ;
if ( includeMid ) opts . push ( { value : THRESHOLD_HL_MID , label : ` 중앙선( ${ th . mid } ) ` } ) ;
opts . push ( { value : THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ th . under } ) ` } ) ;
return opts ;
}
function buildFieldOptsForSlot (
ind : string ,
signalType : 'buy' | 'sell' ,
signalType : 'buy' | 'sell' ,
DEF : DefType ,
cond? : ConditionDSL ,
) : Opt [ ] = > {
cond : ConditionDSL | undefined ,
slot : FieldOptSlot ,
) : Opt [ ] {
const overTerm = signalType === 'buy' ? '과열진입' : '과열이탈' ;
const underTerm = signalType === 'buy' ? '침체이탈' : '침체진입' ;
const condOpts = ( conds : Opt [ ] ) : Opt [ ] = > [ NONE , . . . conds ] ;
/** 해당 지표의 hline 임계값 — 없으면 기본값 */
const th = ( defaults : HLThresh ) : Required < HLThresh > = > {
const th = ( defaults : { over? : number ; mid? : number ; under? : number } ) : { over : number ; mid : number ; under : number } = > {
const saved = DEF . hlThresh [ ind ] ? ? { } ;
return {
over : saved.over ? ? defaults . over ? ? 100 ,
@@ -451,77 +483,137 @@ export const getFieldOpts = (
} ;
} ;
switch ( ind ) {
switch ( ind ) {
case 'MACD' : {
const { mid } = th ( { mid : 0 } ) ;
if ( slot === 'left' ) {
return [
{ value : 'MACD_LINE' , label : 'MACD 선' } ,
{ value : 'SIGNAL_LINE' , label : ` 신호선 ${ DEF . macdSignal } ` } ,
{ value : THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
] ;
}
return [
{ value : 'SIGNAL_LINE' , label : ` 신호선 ${ DEF . macdSignal } ` } ,
NONE ,
{ value : THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
] ;
}
case 'RSI' : {
const { over , mid , under } = th ( { over :70 , mid :50 , under :30 } ) ;
const rsiP = cond ? getConditionValuePeriod ( { . . . cond , indicatorType : 'RSI' } , DEF ) : DEF . rsiPeriod ;
return condOpts ( [
{ value : 'RSI_VALUE' , label : ` RSI 라인( ${ rsiP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 70 , mid : 50 , under : 30 } ) , overTerm , underTerm ) ;
if ( slot === 'left' ) {
return [
{ value : 'RSI_VALUE' , label : ` RSI 기간 ${ rsiP } ` } ,
{ value : 'RSI_SIGNAL' , label : ` 신호선 ${ DEF . rsiSignal } ` } ,
] ;
}
return [
{ value : 'RSI_SIGNAL' , label : ` 신호선 ${ DEF . rsiSignal } ` } ,
NONE ,
. . . thresholds ,
] ;
}
case 'STOCHASTIC' : {
const { over , mid , under } = th ( { over :80 , mid :50 , under :20 } ) ;
return condOpts ( [
{ value : 'STOCH_K' , label : ` Stochastic %K( ${ DEF . stochK } 일) ` } ,
{ value : 'STOCH_D ', label : ` Stochastic %D( ${ DEF . stochD } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 80 , mid : 50 , under : 20 } ) , overTerm , underTerm ) ;
const kLabel = ` Stochastic 기간 ${ DEF . stochK } 일 %K ${ DEF . stochSmooth } 일 ` ;
const dLabel = ` Stochastic %D ${ DEF . stochD } ` ;
if ( slot === 'left ') {
return [
{ value : 'STOCH_K' , label : kLabel } ,
{ value : 'STOCH_D' , label : dLabel } ,
] ;
}
return [
{ value : 'STOCH_D' , label : ` ${ dLabel } 일 ` } ,
NONE ,
. . . thresholds ,
] ;
}
case 'CCI' : {
const { over , mid , under } = th ( { over :100 , mid :0 , under : - 100 } ) ;
const cciP = cond ? getConditionValuePeriod ( { . . . cond , indicatorType : 'CCI' } , DEF ) : DEF . cciPeriod ;
return condOpts ( [
{ value : 'CCI_VALUE' , label : ` CCI 라인( ${ cciP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 100 , mid : 0 , under : - 100 } ) , overTerm , underTerm ) ;
if ( slot === 'left' ) {
return [
{ value : 'CCI_VALUE' , label : ` CCI 기간 ${ cciP } ` } ,
{ value : 'CCI_SIGNAL' , label : ` 신호선 ${ DEF . cciSignal } ` } ,
] ;
}
return [
{ value : 'CCI_SIGNAL' , label : ` 신호선 ${ DEF . cciSignal } ` } ,
NONE ,
. . . thresholds ,
] ;
}
case 'ADX' : {
const { over , mid , under } = th ( { over : 40 , mid : 25 , under : 20 } ) ;
const adxP = cond ? getConditionValuePeriod ( { . . . cond , indicatorType : 'ADX' } , DEF ) : DEF . adxPeriod ;
return condOpts ( [
{ value : 'ADX_VALUE ', label : ` ADX 라인( ${ adxP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 40 , mid : 25 , under : 20 } ) , overTerm , underTerm ) ;
if ( slot === 'left ') {
return [ { value : 'ADX_VALUE' , label : ` ADX 기간 ${ adxP } ` } ] ;
}
return [ NONE , . . . thresholds ] ;
}
case 'TRIX' : {
const { mid } = th ( { mid :0 } ) ;
const trixP = cond ? getConditionValuePeriod ( { . . . cond , indicatorType : 'TRIX' } , DEF ) : DEF . trixPeriod ;
return condOpts ( [
{ value : 'TRIX_VALUE' , label : ` TRIX 라인( ${ trixP } 일) ` } ,
{ value : 'TRIX_SIGNAL' , label : ` TRIX 시그널( ${ DEF . trixSignal } 일) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
] ) ;
const { mid } = th ( { mid : 0 } ) ;
if ( slot === 'left' ) {
return [
{ value : 'TRIX_VALUE' , label : ` TRIX ${ trixP } ` } ,
{ value : 'TRIX_SIGNAL' , label : ` TRMA ${ DEF . trixSignal } ` } ,
] ;
}
return [
{ value : 'TRIX_SIGNAL' , label : ` TRMA ${ DEF . trixSignal } ` } ,
NONE ,
{ value : THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
] ;
}
case 'DMI' : {
const { over , mid , under } = th ( { over :30 , mid :20 , under :10 } ) ;
return condOpts ( [
{ value : 'PDI' , label : ` DI+( ${ DEF . dmiPeriod } 일) ` } ,
{ value : 'MDI' , label : ` DI-( ${ DEF . dmiPeriod } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` 과열( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중간값( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` 침체( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 30 , mid : 20 , under : 10 } ) , '과열' , '침체' ) ;
const plusLabel = ` +DI 기간 ${ DEF . dmiPeriod } 일 ` ;
const minusLabel = ` - DI 기간 ${ DEF . dmiPeriod } 일 ` ;
if ( slot === 'left' ) {
return [
{ value : 'PDI' , label : plusLabel } ,
{ value : 'MDI' , label : minusLabel } ,
] ;
}
return [
{ value : 'MDI' , label : minusLabel } ,
{ value : 'PDI' , label : plusLabel } ,
NONE ,
. . . thresholds ,
] ;
}
case 'OBV' : return condOpts ( [
{ value : 'OBV_LINE ', label : ` OBV선( ${ DEF . obvPeriod } 일) ` } ,
{ value : 'OBV_SIGNAL' , label : ` 신호선( ${ DEF . obvSignal } 일) ` } ,
] ) ;
case 'VOLUME' : return condOpts ( [
{ value : 'VOLUME_VALUE' , label : '거래량' } ,
{ value : 'VOLUME_MA' , label : '거래량 이동평균' } ,
] ) ;
case 'OBV' :
if ( slot === 'left ') {
return [
{ value : 'OBV_LINE' , label : 'OBV선' } ,
{ value : 'OBV_SIGNAL' , label : ` 신호선 ${ DEF . obvSignal } 일 ` } ,
] ;
}
return [
{ value : 'OBV_SIGNAL' , label : ` 신호선 ${ DEF . obvSignal } 일 ` } ,
NONE ,
] ;
case 'WILLIAMS_R' : {
const wrP = cond ? getConditionValuePeriod ( { . . . cond , indicatorType : 'WILLIAMS_R' } , DEF ) : DEF . williamsR ;
const thresholds = buildThresholdOpts ( th ( { over : - 20 , mid : - 50 , under : - 80 } ) , overTerm , underTerm ) ;
if ( slot === 'left' ) {
return [ { value : 'WILLIAMS_R_VALUE' , label : ` williams%R 기간 ${ wrP } ` } ] ;
}
return [ NONE , . . . thresholds ] ;
}
case 'VOLUME' :
return slot === 'left'
? [
{ value : 'VOLUME_VALUE' , label : '거래량' } ,
{ value : 'VOLUME_MA' , label : '거래량 이동평균' } ,
]
: [ NONE , { value : 'VOLUME_MA' , label : '거래량 이동평균' } ] ;
case 'MA' : {
let opts = [
NONE ,
. . . ( slot === 'right' ? [ NONE ] : [ ] ) ,
. . . buildMaFieldOptions ( DEF . maLines ) ,
{ value : 'CLOSE_PRICE' , label : '종가' } ,
] ;
@@ -531,7 +623,7 @@ export const getFieldOpts = (
}
case 'EMA' : {
let opts = [
NONE ,
. . . ( slot === 'right' ? [ NONE ] : [ ] ) ,
. . . buildEmaFieldOptions ( DEF . maLines , 4 ) ,
{ value : 'CLOSE_PRICE' , label : '종가' } ,
] ;
@@ -541,159 +633,175 @@ export const getFieldOpts = (
}
case 'DISPARITY' : {
const { mid } = th ( { mid : 100 } ) ;
return condOpts ( [
{ value : ` DISPARITY ${ DEF . dispUltra } ` , label : ` 초단기 이격도( ${ DEF . dispUltra } 일) ` } ,
{ value : ` DISPARITY ${ DEF . dispShort } ` , label : ` 단기 이격도( ${ DEF . dispShort } 일) ` } ,
{ value : ` DISPARITY ${ DEF . dispMid } ` , label : ` 중기 이격도( ${ DEF . dispMid } 일) ` } ,
{ value : ` DISPARITY ${ DEF . dispLong } ` , label : ` 장기 이격도( ${ DEF . dispLong } 일) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
] ) ;
const lines = [
{ value : ` DISPARITY ${ DEF . dispUltra } ` , label : ` 초단기 이격도( ${ DEF . dispUltra } 일) ` } ,
{ value : ` DISPARITY ${ DEF . dispShort } ` , label : ` 단기 이격도( ${ DEF . dispShort } 일) ` } ,
{ value : ` DISPARITY ${ DEF . dispMid } ` , label : ` 중기 이격도( ${ DEF . dispMid } 일) ` } ,
{ value : ` DISPARITY ${ DEF . dispLong } ` , label : ` 장기 이격도( ${ DEF . dispLong } 일) ` } ,
{ value : THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
] ;
return slot === 'right' ? [ NONE , . . . lines ] : lines ;
}
case 'PSYCHOLOGICAL' : {
const { over , mid , under } = th ( { over :75 , mid :50 , under :25 } ) ;
const psyP = cond
? getConditionValuePeriod ( { . . . cond , indicatorType : ind } , DEF )
: DEF . psyPeriod ;
return condOpts ( [
{ value : 'PSY_VALUE ', label : ` 심리도 라인( ${ psyP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 75 , mid : 50 , under : 25 } ) , overTerm , underTerm ) ;
if ( slot === 'left ') {
return [ { value : 'PSY_VALUE' , label : ` 심리도 기간 ${ psyP } ` } ] ;
}
return [ NONE , . . . thresholds ] ;
}
case 'NEW_PSYCHOLOGICAL' : {
const { over , mid , under } = th ( { over :50 , mid :0 , under : - 50 } ) ;
const nPsyP = cond
? getConditionValuePeriod ( { . . . cond , indicatorType : ind } , DEF )
: DEF . newPsy ;
return condOpts ( [
{ value : 'NEW_PSY_VALUE' , label : ` 신심리도 라인( ${ nPsyP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 50 , mid : 0 , under : - 50 } ) , overTerm , underTerm ) ;
if ( slot === 'left' ) {
return [ { value : 'NEW_PSY_VALUE' , label : ` 신심리도 기간 ${ nPsyP } ` } ] ;
}
return [ NONE , . . . thresholds ] ;
}
case 'INVEST_PSYCHOLOGICAL' : {
const { over , mid , under } = th ( { over :75 , mid :50 , under :25 } ) ;
const ipsyP = cond
? getConditionValuePeriod ( { . . . cond , indicatorType : 'INVEST_PSYCHOLOGICAL' } , DEF )
: DEF . investPsy ;
return condOpts ( [
{ value : 'INVEST_PSY_VALUE' , label : ` 투자심리도 라인( ${ ipsyP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
}
case 'WILLIAMS_R' : {
const { over , mid , under } = th ( { over : - 20 , mid : - 50 , under : - 80 } ) ;
const wrP = cond ? getConditionValuePeriod ( { . . . cond , indicatorType : 'WILLIAMS_R' } , DEF ) : DEF . williamsR ;
return condOpts ( [
{ value : 'WILLIAMS_R_VALUE' , label : ` Williams %R 라인( ${ wrP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 75 , mid : 50 , under : 25 } ) , overTerm , underTerm ) ;
if ( slot === 'left' ) {
return [ { value : 'INVEST_PSY_VALUE' , label : ` 투자심리도 기간 ${ ipsyP } ` } ] ;
}
return [ NONE , . . . thresholds ] ;
}
case 'BWI' : {
const { over , mid , under } = th ( { over :80 , mid :50 , under :20 } ) ;
const bwiP = DEF . bwiPeriod ;
return condOpts ( [
{ value : 'BWI_VALUE' , label : ` BWI 라인( ${ bwiP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 80 , mid : 50 , under : 20 } ) , overTerm , underTerm ) ;
if ( slot === 'left' ) {
return [ { value : 'BWI_VALUE' , label : ` BWI 기간 ${ DEF . bwiPeriod } ` } ] ;
}
return [ NONE , . . . thresholds ] ;
}
case 'VR' : {
const { over , mid , under } = th ( { over :200 , mid :100 , under :50 } ) ;
const vrP = cond ? getConditionValuePeriod ( { . . . cond , indicatorType : 'VR' } , DEF ) : DEF . vrPeriod ;
return condOpts ( [
{ value : 'VR_VALUE ', label : ` VR 라인( ${ vrP } 일) ` } ,
{ value :THRESHOLD_HL_OVER , label : ` ${ overTerm } ( ${ over } ) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
{ value :THRESHOLD_HL_UNDER , label : ` ${ underTerm } ( ${ under } ) ` } ,
] ) ;
const thresholds = buildThresholdOpts ( th ( { over : 200 , mid : 100 , under : 50 } ) , overTerm , underTerm ) ;
if ( slot === 'left ') {
return [ { value : 'VR_VALUE' , label : ` VR 기간 ${ vrP } ` } ] ;
}
return [ NONE , . . . thresholds ] ;
}
case 'VOLUME_OSC' : {
const { mid } = th ( { mid :0 } ) ;
return condOpts ( [
{ value : 'VOLUME_OSC_VALUE' , label : ` 거래량 오실레이터 값 ( ${ DEF . volOscShort } 일/ ${ DEF . volOscLong } 일) ` } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ,
] ) ;
const { mid } = th ( { mid : 0 } ) ;
if ( slot === 'left' ) {
return [ { value : 'VOLUME_OSC_VALUE' , label : ` 거래량 오실레이터( ${ DEF . volOscShort } 일/ ${ DEF . volOscLong } 일) ` } ] ;
}
return [ NONE , { value : THRESHOLD_HL_MID , label : ` 중앙선( ${ mid } ) ` } ] ;
}
case 'MACD ' : {
const { mid } = th ( { mid :0 } ) ;
return condOpts ( [
{ value : 'MACD_LINE' , label : ` MACD 라인( ${ DEF . macdFast } 일/ ${ DEF . macdSlow } 일) ` } ,
{ value : 'SIGNAL_LINE ' , label : ` 시그널 라인 (${ DEF . macdSignal } 일) ` } ,
{ value : 'HISTOGRAM ' , label : '히스토그램' } ,
{ value :THRESHOLD_HL_MID , label : ` 중앙선 (${ mid } ) ` } ,
] ) ;
}
case 'BOLLINGER' : return condOpts ( [
{ value : 'UPPER_BAND ' , label : ` 상단밴드( ${ DEF . bbPeriod } 일, σ ${ DEF . bbStdDev } ) ` } ,
{ value : 'MIDDLE_BAND' , label : ` 중심선( ${ DEF . bbPeriod } 일) ` } ,
{ value : 'LOWER_BAND' , label : ` 하단밴드( ${ DEF . bbPeriod } 일, σ ${ DEF . bbStdDev } ) ` } ,
{ value : 'CLOSE_PRICE' , label : '종가' } ,
{ value : 'OPEN_PRICE ' , label : '시가' } ,
{ value : 'HIGH_PRICE ' , label : '고가' } ,
{ value : 'LOW _PRICE' , label : '저 가' } ,
] ) ;
case 'BOLLINGER ' :
return slot === 'right'
? [
NONE ,
{ value : 'UPPER_BAND ' , label : ` 상단밴드 (${ DEF . bbPeriod } 일, σ ${ DEF . bbStdDev } ) ` } ,
{ value : 'MIDDLE_BAND ' , label : ` 중심선( ${ DEF . bbPeriod } 일) ` } ,
{ value : 'LOWER_BAND' , label : ` 하단밴드 (${ DEF . bbPeriod } 일, σ ${ DEF . bbStdDev } ) ` } ,
{ value : 'CLOSE_PRICE' , label : '종가' } ,
{ value : 'OPEN_PRICE' , label : '시가' } ,
{ value : 'HIGH_PRICE' , label : '고가' } ,
{ value : 'LOW_PRICE ' , label : '저가' } ,
]
: [
{ value : 'UPPER_BAND' , label : ` 상단밴드( ${ DEF . bbPeriod } 일, σ ${ DEF . bbStdDev } ) ` } ,
{ value : 'MIDDLE_BAND ' , label : ` 중심선( ${ DEF . bbPeriod } 일) ` } ,
{ value : 'LOWER_BAND ' , label : ` 하단밴드( ${ DEF . bbPeriod } 일, σ ${ DEF . bbStdDev } ) ` } ,
{ value : 'C LOSE _PRICE' , label : '종 가' } ,
{ value : 'OPEN_PRICE' , label : '시가' } ,
{ value : 'HIGH_PRICE' , label : '고가' } ,
{ value : 'LOW_PRICE' , label : '저가' } ,
] ;
case 'NEW_HIGH' : {
const p = cond ? . period ? ? 9 ;
const periods = [ . . . new Set ( [ p , 5 , 9 , 10 , 20 , 55 ] ) ] . sort ( ( a , b ) = > a - b ) ;
return condOpts ( [
const lines = [
{ value : 'CLOSE_PRICE' , label : '종가' } ,
{ value : 'HIGH_PRICE' , label : '고가' } ,
{ value : 'OPEN_PRICE' , label : '시가' } ,
. . . periods . map ( n = > ( { value : nhPriorField ( n ) , label : ` ${ n } 일 신고가선(직전 N봉) ` } ) ) ,
] ) ;
] ;
return slot === 'right' ? [ NONE , . . . lines ] : lines ;
}
case 'NEW_LOW' : {
const p = cond ? . period ? ? 9 ;
const periods = [ . . . new Set ( [ p , 5 , 9 , 10 , 20 , 55 ] ) ] . sort ( ( a , b ) = > a - b ) ;
return condOpts ( [
const lines = [
{ value : 'CLOSE_PRICE' , label : '종가' } ,
{ value : 'LOW_PRICE' , label : '저가' } ,
{ value : 'OPEN_PRICE' , label : '시가' } ,
. . . periods . map ( n = > ( { value : nlPriorField ( n ) , label : ` ${ n } 일 신저가선(직전 N봉) ` } ) ) ,
] ) ;
] ;
return slot === 'right' ? [ NONE , . . . lines ] : lines ;
}
case 'DONCHIAN' : return condOpts ( [
{ value : 'DC_UPPER_9' , label : '상단(9일 최고가·신고가)' } ,
{ value : 'DC_UPPER_10 ' , label : '상단(10 일 최고가)' } ,
{ value : 'DC_UPPER_2 0' , label : '상단(2 0일 최고가·신고가 )' } ,
{ value : 'DC_UPPER_55 ' , label : '상단(55 일 최고가)' } ,
{ value : 'DC_MIDDLE_20 ' , label : '중심(20일 )' } ,
{ value : 'DC_LOWER_9 ' , label : '하단(9일 최저가·신저가 )' } ,
{ value : 'DC_LOWER_10 ' , label : '하단(10 일 최저가)' } ,
{ value : 'DC_LOWER_2 0' , label : '하단(2 0일 최저가·신저가 )' } ,
{ value : 'DC_LOWER_55 ' , label : '하단(55 일 최저가)' } ,
{ value : 'CLOSE_PRICE ' , label : '종가 ' } ,
{ value : 'LOW _PRICE' , label : '저 가' } ,
{ value : 'HIGH _PRICE' , label : '고 가' } ,
] ) ;
case 'ICHIMOKU' : return condOpts ( [
{ value : 'CONVERSION_LINE' , label : ` 전환선( ${ DEF . ichTenkan } 일) ` } ,
{ value : 'BASE_LINE' , label : ` 기준선( ${ DEF . ichKijun } 일) ` } ,
{ value : 'LEADING_SPAN1' , label : '선행스팬1' } ,
{ value : 'LEADING_SPAN2' , label : ` 선행스팬2( ${ DEF . ichSenkouB } 일) ` } ,
{ value : 'LAGGING_SPAN ' , label : '후행스팬' } ,
{ value : 'CLOSE_PRICE' , label : '종가' } ,
{ value : 'OPEN_PRICE ' , label : '시가 ' } ,
{ value : 'HIGH_PRICE' , label : '고가' } ,
{ value : 'LOW_PRICE' , label : '저가 ' } ,
] ) ;
default : return [ NONE ] ;
case 'DONCHIAN' : {
const lines = [
{ value : 'DC_UPPER_9 ' , label : '상단(9 일 최고가·신고가 )' } ,
{ value : 'DC_UPPER_1 0' , label : '상단(1 0일 최고가)' } ,
{ value : 'DC_UPPER_20 ' , label : '상단(20 일 최고가·신고가 )' } ,
{ value : 'DC_UPPER_55 ' , label : '상단(55일 최고가 )' } ,
{ value : 'DC_MIDDLE_20 ' , label : '중심(20일 )' } ,
{ value : 'DC_LOWER_9 ' , label : '하단(9 일 최저가·신저가 )' } ,
{ value : 'DC_LOWER_1 0' , label : '하단(1 0일 최저가)' } ,
{ value : 'DC_LOWER_20 ' , label : '하단(20 일 최저가·신저가 )' } ,
{ value : 'DC_LOWER_55 ' , label : '하단(55일 최저가) ' } ,
{ value : 'C LOSE _PRICE' , label : '종 가' } ,
{ value : 'LOW _PRICE' , label : '저 가' } ,
{ value : 'HIGH_PRICE' , label : '고가' } ,
] ;
return slot === 'right' ? [ NONE , . . . lines ] : lines ;
}
case 'ICHIMOKU' : {
const lines = [
{ value : 'CONVERSION_LINE ' , label : ` 전환선( ${ DEF . ichTenkan } 일) ` } ,
{ value : 'BASE_LINE' , label : ` 기준선( ${ DEF . ichKijun } 일) ` } ,
{ value : 'LEADING_SPAN1 ' , label : '선행스팬1 ' } ,
{ value : 'LEADING_SPAN2' , label : ` 선행스팬2( ${ DEF . ichSenkouB } 일) ` } ,
{ value : 'LAGGING_SPAN' , label : '후행스팬 ' } ,
{ value : 'CLOSE_PRICE' , label : '종가' } ,
{ value : 'OPEN_PRICE' , label : '시가' } ,
{ value : 'HIGH_PRICE' , label : '고가' } ,
{ value : 'LOW_PRICE' , label : '저가' } ,
] ;
return slot === 'right' ? [ NONE , . . . lines ] : lines ;
}
default :
return slot === 'right' ? [ NONE ] : [ ] ;
}
} ;
}
export const getLeftFieldOpts = (
ind : string ,
signalType : 'buy' | 'sell' ,
DEF : DefType ,
cond? : ConditionDSL ,
) : Opt [ ] = > buildFieldOptsForSlot ( ind , signalType , DEF , cond , 'left' ) ;
export const getRightFieldOpts = (
ind : string ,
signalType : 'buy' | 'sell' ,
DEF : DefType ,
cond? : ConditionDSL ,
) : Opt [ ] = > buildFieldOptsForSlot ( ind , signalType , DEF , cond , 'right' ) ;
export const getFieldOpts = (
ind : string ,
signalType : 'buy' | 'sell' ,
DEF : DefType ,
cond? : ConditionDSL ,
) : Opt [ ] = > mergeFieldOpts (
getLeftFieldOpts ( ind , signalType , DEF , cond ) ,
getRightFieldOpts ( ind , signalType , DEF , cond ) ,
) ;
const getDefaultConditionFields = ( ind : string , signalType : 'buy' | 'sell' , DEF : DefType ) : { l : string ; r : string } = > {
const map : Record < string , { l : string ; r : string } > = {
RSI : { l : 'RSI_VALUE' , r : THRESHOLD_HL_OVER } ,
STOCHASTIC : { l : 'STOCH_K' , r : 'STOCH_D' } ,
CCI : { l : 'CCI_VALUE' , r : THRESHOLD_HL_OVER } ,
CCI : { l : 'CCI_VALUE' , r : 'CCI_SIGNAL' } ,
ADX : { l : 'ADX_VALUE' , r : THRESHOLD_HL_MID } ,
TRIX : { l : 'TRIX_VALUE' , r : 'TRIX_SIGNAL' } ,
DMI : { l : 'PDI' , r : 'MDI' } ,
@@ -968,7 +1076,9 @@ export const CondEditor: React.FC<CondEditorProps> = ({
cond , signalType , onChange , def , stochPairEdit ,
} ) = > {
const normalized = normalizeCompositeCondition ( cond ) ;
const fieldOpts = getFieldOpts ( normalized . indicatorType , signalType , def , normalized ) ;
const leftFieldOpts = getLeftFieldOpts ( normalized . indicatorType , signalType , def , normalized ) ;
const rightFieldOpts = getRightFieldOpts ( normalized . indicatorType , signalType , def , normalized ) ;
const fieldOpts = mergeFieldOpts ( leftFieldOpts , rightFieldOpts ) ;
const condOpts = getCondOptionsForIndicator ( normalized . indicatorType ) ;
const isValid = ( v : string | undefined ) = > ! ! v && fieldOpts . some ( o = > o . value === v ) ;
@@ -1220,11 +1330,11 @@ export const CondEditor: React.FC<CondEditorProps> = ({
< div className = "sp-cond-field" >
< label className = "sp-cond-lbl" > 조 건 대 상 1 < / label >
< ComboFieldSelect
options = { fieldOpts }
options = { leftFieldOpts }
fieldValue = { isHoldType ? 'NONE' : ( normalized . leftField ? ? getLeftValue ( ) ) }
isPresetField = { ! isHoldType && (
fieldOpts . some ( o = > o . value === normalized . leftField )
|| ( fieldOpts . some ( o = > o . value === getLeftValue ( ) )
leftFieldOpts . some ( o = > o . value === normalized . leftField )
|| ( leftFieldOpts . some ( o = > o . value === getLeftValue ( ) )
&& ! isValuePeriodFieldStored ( normalized . indicatorType , normalized . leftField ) )
) }
customKind = { resolveFieldCustomKind ( normalized . indicatorType , normalized . leftField ) }
@@ -1260,10 +1370,14 @@ export const CondEditor: React.FC<CondEditorProps> = ({
< div className = "sp-cond-field" >
< label className = "sp-cond-lbl" > 조 건 대 상 2 < / label >
< ComboFieldSelect
options = { fieldOpts }
options = { rightFieldOpts }
fieldValue = { rightValue }
isPresetField = { fieldOpts . some ( o = > o . value === normalized . rightField ) }
customKind = { resolveFieldCustomKind ( normalized . indicatorType , normalized . rightField ) }
isPresetField = {
! isThresholdOverridden ( normalized )
&& parseThresholdField ( normalized . rightField ) == null
&& rightFieldOpts . some ( o = > o . value === getRightValue ( ) )
}
customKind = { resolveRightFieldCustomKind ( normalized . indicatorType , normalized . rightField ) }
customNumber = { getConditionThreshold ( normalized , 'right' , inheritedThresholdField , def )
? ? getChartReferenceThreshold ( normalized , 'right' , inheritedThresholdField , def )
? ? parseThresholdField ( normalized . rightField )