가상투자 종목카드박스 레이아웃 수정
This commit is contained in:
@@ -9,9 +9,17 @@ import {
|
||||
compositeFieldLabel,
|
||||
makeCompositeCondition,
|
||||
normalizeCompositeCondition,
|
||||
parseDonchianPeriod,
|
||||
parsePeriodFromCompositeField,
|
||||
syncCompositeFields,
|
||||
} from '../utils/compositeIndicators';
|
||||
import {
|
||||
isPriceExtremeIndicator,
|
||||
nhPriorField,
|
||||
nlPriorField,
|
||||
parsePriorExtremePeriod,
|
||||
syncPriceExtremeSimpleFields,
|
||||
} from '../utils/priceExtremeIndicators';
|
||||
import ComboFieldSelect from '../components/strategyEditor/ComboFieldSelect';
|
||||
import {
|
||||
getCompositeFieldOpts,
|
||||
@@ -177,6 +185,8 @@ export function buildDef(activeIndicators: IndicatorConfig[]): DefType {
|
||||
OBV: 'OBV',
|
||||
BOLLINGER: 'BollingerBands',
|
||||
DONCHIAN: 'DonchianChannels',
|
||||
NEW_HIGH: 'PriceExtreme',
|
||||
NEW_LOW: 'PriceExtreme',
|
||||
};
|
||||
|
||||
const extractThresh = (dslType: string): HLThresh => {
|
||||
@@ -469,13 +479,35 @@ export const getFieldOpts = (
|
||||
{ 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([
|
||||
{ value: 'CLOSE_PRICE', label: '종가' },
|
||||
{ value: 'HIGH_PRICE', label: '고가' },
|
||||
{ value: 'OPEN_PRICE', label: '시가' },
|
||||
...periods.map(n => ({ value: nhPriorField(n), label: `${n}일 신고가선(직전 N봉)` })),
|
||||
]);
|
||||
}
|
||||
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([
|
||||
{ value: 'CLOSE_PRICE', label: '종가' },
|
||||
{ value: 'LOW_PRICE', label: '저가' },
|
||||
{ value: 'OPEN_PRICE', label: '시가' },
|
||||
...periods.map(n => ({ value: nlPriorField(n), label: `${n}일 신저가선(직전 N봉)` })),
|
||||
]);
|
||||
}
|
||||
case 'DONCHIAN': return condOpts([
|
||||
{ value:'DC_UPPER_9', label:'상단(9일 최고가·신고가)' },
|
||||
{ value:'DC_UPPER_10', label:'상단(10일 최고가)' },
|
||||
{ value:'DC_UPPER_20', label:'상단(20일 최고가)' },
|
||||
{ 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_10', label:'하단(10일 최저가)' },
|
||||
{ value:'DC_LOWER_20', label:'하단(20일 최저가)' },
|
||||
{ value:'DC_LOWER_20', label:'하단(20일 최저가·신저가)' },
|
||||
{ value:'DC_LOWER_55', label:'하단(55일 최저가)' },
|
||||
{ value:'CLOSE_PRICE', label:'종가' },
|
||||
{ value:'LOW_PRICE', label:'저가' },
|
||||
@@ -526,6 +558,8 @@ const getDefaultFields = (ind: string, signalType: 'buy'|'sell', DEF: DefType):
|
||||
DONCHIAN: signalType === 'buy'
|
||||
? { l:'CLOSE_PRICE', r:'DC_UPPER_20' }
|
||||
: { l:'CLOSE_PRICE', r:'DC_LOWER_20' },
|
||||
NEW_HIGH: { l:'CLOSE_PRICE', r: nhPriorField(9) },
|
||||
NEW_LOW: { l:'CLOSE_PRICE', r: nlPriorField(9) },
|
||||
ICHIMOKU: { l:'CONVERSION_LINE', r:'BASE_LINE' },
|
||||
};
|
||||
return map[ind] ?? { l:'NONE', r:'NONE' };
|
||||
@@ -569,6 +603,9 @@ const applyCondTypeDefaults = (cond: ConditionDSL, newCondType: string, DEF: Def
|
||||
updated.leftField = 'LAGGING_SPAN'; updated.rightField = 'CLOSE_PRICE';
|
||||
}
|
||||
}
|
||||
if (isPriceExtremeIndicator(updated.indicatorType)) {
|
||||
return syncPriceExtremeSimpleFields(updated);
|
||||
}
|
||||
return updated;
|
||||
};
|
||||
|
||||
@@ -751,8 +788,12 @@ export const CondEditor: React.FC<CondEditorProps> = ({ cond, signalType, onChan
|
||||
OVERBOUGHT_200: 200, NEUTRAL_100: 100, OVERSOLD_50: 50,
|
||||
ZERO_LINE: 0,
|
||||
};
|
||||
const upd: ConditionDSL = { ...normalized, rightField: v };
|
||||
let upd: ConditionDSL = { ...normalized, rightField: v };
|
||||
if (thresholds[v] !== undefined) upd.targetValue = thresholds[v];
|
||||
const priorP = parsePriorExtremePeriod(v);
|
||||
if (priorP != null && isPriceExtremeIndicator(normalized.indicatorType)) {
|
||||
upd = syncPriceExtremeSimpleFields({ ...upd, period: priorP });
|
||||
}
|
||||
onChange(upd);
|
||||
};
|
||||
|
||||
@@ -769,7 +810,8 @@ export const CondEditor: React.FC<CondEditorProps> = ({ cond, signalType, onChan
|
||||
const periodPresetsRight = getCompositePeriodPresetOptions(normalized.indicatorType, def, 'right');
|
||||
|
||||
const handleCompositeField = (side: 'left' | 'right', field: string) => {
|
||||
const p = parsePeriodFromCompositeField(normalized.indicatorType, field);
|
||||
const p = parseDonchianPeriod(field)
|
||||
?? parsePeriodFromCompositeField(normalized.indicatorType, field);
|
||||
if (p == null) return;
|
||||
if (side === 'left') {
|
||||
onChange(syncCompositeFields({ ...normalized, leftPeriod: p, leftField: field }));
|
||||
@@ -1035,16 +1077,22 @@ export const makeNode = (
|
||||
}
|
||||
const def = getDefaultFields(value, signalType, DEF);
|
||||
const period = options?.period ?? getDefaultIndicatorPeriod(value, DEF);
|
||||
let conditionType = signalType === 'buy' ? 'CROSS_UP' : 'CROSS_DOWN';
|
||||
if (value === 'NEW_HIGH' && signalType === 'buy') conditionType = 'CROSS_UP';
|
||||
if (value === 'NEW_LOW' && signalType === 'sell') conditionType = 'CROSS_DOWN';
|
||||
const baseCondition: ConditionDSL = {
|
||||
indicatorType: value,
|
||||
conditionType: signalType === 'buy' ? 'CROSS_UP' : 'CROSS_DOWN',
|
||||
conditionType,
|
||||
leftField: def.l,
|
||||
rightField: def.r,
|
||||
candleRange: 1,
|
||||
period,
|
||||
};
|
||||
const condition = initConditionPeriods(value, baseCondition, DEF);
|
||||
return {
|
||||
id: genId(), type: 'CONDITION',
|
||||
condition: initConditionPeriods(value, baseCondition, DEF),
|
||||
condition: isPriceExtremeIndicator(value)
|
||||
? syncPriceExtremeSimpleFields(condition)
|
||||
: condition,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user