가상투자 종목카드박스 레이아웃 수정
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/** 신고가·신저가 — 단일(보조) 지표: 종가/고가 vs 직전 N봉 최고·최저 */
|
||||
import type { ConditionDSL } from './strategyTypes';
|
||||
import { parseDonchianPeriod } from './compositeIndicators';
|
||||
|
||||
export function isPriceExtremeIndicator(ind?: string): boolean {
|
||||
return ind === 'NEW_HIGH' || ind === 'NEW_LOW';
|
||||
}
|
||||
|
||||
export function nhPriorField(period: number): string {
|
||||
return `NH_PRIOR_${Math.max(1, Math.round(period))}`;
|
||||
}
|
||||
|
||||
export function nlPriorField(period: number): string {
|
||||
return `NL_PRIOR_${Math.max(1, Math.round(period))}`;
|
||||
}
|
||||
|
||||
export function parsePriorExtremePeriod(field?: string): number | null {
|
||||
if (!field) return null;
|
||||
const m = field.match(/^(?:NH|NL)_PRIOR_(\d+)$/);
|
||||
if (m) {
|
||||
const n = parseInt(m[1], 10);
|
||||
return Number.isFinite(n) && n > 0 ? n : null;
|
||||
}
|
||||
return parseDonchianPeriod(field);
|
||||
}
|
||||
|
||||
export function priorExtremeField(ind: string, period: number): string {
|
||||
return ind === 'NEW_LOW' ? nlPriorField(period) : nhPriorField(period);
|
||||
}
|
||||
|
||||
/** 단일 지표 — 기간에 맞춰 직전 N봉 신고가/신저가선 필드 동기화 */
|
||||
export function syncPriceExtremeSimpleFields(cond: ConditionDSL): ConditionDSL {
|
||||
if (!isPriceExtremeIndicator(cond.indicatorType)) return cond;
|
||||
const period = cond.period
|
||||
?? parsePriorExtremePeriod(cond.rightField)
|
||||
?? parsePriorExtremePeriod(cond.leftField)
|
||||
?? 9;
|
||||
const p = Math.max(1, Math.min(500, Math.round(period)));
|
||||
const band = priorExtremeField(cond.indicatorType, p);
|
||||
const priceFields = new Set(['CLOSE_PRICE', 'HIGH_PRICE', 'LOW_PRICE', 'OPEN_PRICE']);
|
||||
let leftField = cond.leftField;
|
||||
let rightField = cond.rightField;
|
||||
if (cond.indicatorType === 'NEW_HIGH') {
|
||||
leftField = leftField && priceFields.has(leftField) ? leftField : 'CLOSE_PRICE';
|
||||
rightField = band;
|
||||
} else {
|
||||
leftField = leftField && priceFields.has(leftField) ? leftField : 'CLOSE_PRICE';
|
||||
rightField = band;
|
||||
}
|
||||
return {
|
||||
...cond,
|
||||
composite: false,
|
||||
period: p,
|
||||
leftPeriod: undefined,
|
||||
rightPeriod: undefined,
|
||||
leftCandleType: undefined,
|
||||
rightCandleType: undefined,
|
||||
leftField,
|
||||
rightField,
|
||||
};
|
||||
}
|
||||
|
||||
/** 저장 DSL — 복합지표 승격·DC_UPPER_N 필드를 단일 신고가/신저가로 정규화 */
|
||||
export function migratePriceExtremeCondition(cond: ConditionDSL): ConditionDSL {
|
||||
const ind = cond.indicatorType;
|
||||
if (!isPriceExtremeIndicator(ind)) return cond;
|
||||
|
||||
if (cond.composite && isPriceExtremeIndicator(ind)) {
|
||||
const period = cond.leftPeriod ?? cond.rightPeriod ?? 9;
|
||||
const priceFields = new Set(['CLOSE_PRICE', 'HIGH_PRICE', 'LOW_PRICE', 'OPEN_PRICE']);
|
||||
const leftField = cond.leftField && priceFields.has(cond.leftField)
|
||||
? cond.leftField
|
||||
: 'CLOSE_PRICE';
|
||||
return syncPriceExtremeSimpleFields({
|
||||
...cond,
|
||||
composite: false,
|
||||
leftField,
|
||||
period,
|
||||
conditionType: cond.conditionType === 'CROSS_UP' ? 'GTE'
|
||||
: cond.conditionType === 'CROSS_DOWN' ? 'LTE'
|
||||
: cond.conditionType,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isPriceExtremeIndicator(ind)) return cond;
|
||||
|
||||
const fromRight = parsePriorExtremePeriod(cond.rightField);
|
||||
const fromLeft = parsePriorExtremePeriod(cond.leftField);
|
||||
const period = cond.period ?? fromRight ?? fromLeft ?? 9;
|
||||
let migrated = syncPriceExtremeSimpleFields({ ...cond, period });
|
||||
|
||||
if (migrated.rightField?.startsWith('DC_UPPER_') || migrated.rightField?.startsWith('DC_LOWER_')) {
|
||||
migrated = syncPriceExtremeSimpleFields({
|
||||
...migrated,
|
||||
period: parseDonchianPeriod(migrated.rightField) ?? period,
|
||||
});
|
||||
}
|
||||
|
||||
if (migrated.conditionType === 'CROSS_UP' && ind === 'NEW_HIGH') {
|
||||
migrated = { ...migrated, conditionType: 'GTE' };
|
||||
}
|
||||
if (migrated.conditionType === 'CROSS_DOWN' && ind === 'NEW_LOW') {
|
||||
migrated = { ...migrated, conditionType: 'LTE' };
|
||||
}
|
||||
|
||||
return migrated;
|
||||
}
|
||||
|
||||
export function priceExtremePeriodLabel(ind: string, period: number): string {
|
||||
return ind === 'NEW_LOW' ? `${period}일 신저가선` : `${period}일 신고가선`;
|
||||
}
|
||||
Reference in New Issue
Block a user