가상 수정
This commit is contained in:
@@ -1083,6 +1083,7 @@ export interface LiveConditionRowDto {
|
||||
satisfied: boolean | null;
|
||||
timeframe: string;
|
||||
side: 'buy' | 'sell';
|
||||
plotKey?: string;
|
||||
}
|
||||
|
||||
export interface LiveConditionStatusDto {
|
||||
|
||||
@@ -384,6 +384,16 @@ export function getIndicatorDef(type: string): IndicatorDef | undefined {
|
||||
return INDICATOR_REGISTRY.find(d => d.type === type);
|
||||
}
|
||||
|
||||
/** 가상투자·조건 목록 — 한글명 (영문약어) 예: 상품채널지수 (CCI) */
|
||||
export function formatIndicatorDisplayLabel(indicatorType: string): string {
|
||||
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;
|
||||
|
||||
@@ -9,9 +9,23 @@ export type ConditionStatus = 'match' | 'pending' | 'nomatch' | 'unknown';
|
||||
export interface ConditionMetric {
|
||||
row: VirtualConditionRow & { currentValue: number | null };
|
||||
status: ConditionStatus;
|
||||
/** 조건 매칭율 0~100 */
|
||||
matchRate: number;
|
||||
/** @deprecated matchRate 와 동일 — 하위 호환 */
|
||||
progress: number | null;
|
||||
}
|
||||
|
||||
export type HeatTier = 'nomatch' | 'pending' | 'match';
|
||||
|
||||
/** Heatmap 색상 — Mismatch(빨강) · Pending(오렌지) · Match(푸른색) */
|
||||
export function resolveHeatTier(matchRate: number, status: ConditionStatus): HeatTier {
|
||||
if (status === 'match' || matchRate >= 100) return 'match';
|
||||
if (status === 'pending') return 'pending';
|
||||
if (status === 'nomatch') return 'nomatch';
|
||||
if (matchRate >= 45) return 'pending';
|
||||
return 'nomatch';
|
||||
}
|
||||
|
||||
export type TrafficLightState = 'red' | 'yellow' | 'blue';
|
||||
|
||||
const PENDING_PROGRESS = 72;
|
||||
@@ -53,6 +67,35 @@ export function computeConditionProgress(
|
||||
}
|
||||
}
|
||||
|
||||
const CROSS_CONDITION_TYPES = new Set([
|
||||
'CROSS_UP', 'CROSS_DOWN', 'SLOPE_UP', 'SLOPE_DOWN',
|
||||
]);
|
||||
|
||||
/** 조건별 매칭율 (0~100) — 백엔드 Ta4j satisfied 우선 */
|
||||
export function computeConditionMatchRate(
|
||||
row: VirtualConditionRow & { currentValue: number | null },
|
||||
): number {
|
||||
if (row.satisfied === true) return 100;
|
||||
if (row.satisfied === false) {
|
||||
if (CROSS_CONDITION_TYPES.has(row.conditionType)) return 0;
|
||||
const progress = computeConditionProgress(
|
||||
row.conditionType,
|
||||
row.currentValue,
|
||||
row.targetValue,
|
||||
);
|
||||
if (progress == null) return 0;
|
||||
return Math.min(99, Math.max(0, Math.round(progress)));
|
||||
}
|
||||
if (row.currentValue == null) return 0;
|
||||
const progress = computeConditionProgress(
|
||||
row.conditionType,
|
||||
row.currentValue,
|
||||
row.targetValue,
|
||||
);
|
||||
if (progress == null) return 0;
|
||||
return Math.min(99, Math.max(0, Math.round(progress)));
|
||||
}
|
||||
|
||||
export function getConditionStatus(
|
||||
conditionType: string,
|
||||
current: number | null,
|
||||
@@ -61,6 +104,7 @@ export function getConditionStatus(
|
||||
): ConditionStatus {
|
||||
if (satisfied === true) return 'match';
|
||||
if (satisfied === false) {
|
||||
if (CROSS_CONDITION_TYPES.has(conditionType)) return 'nomatch';
|
||||
const progress = computeConditionProgress(conditionType, current, target);
|
||||
if (progress != null && progress >= PENDING_PROGRESS) return 'pending';
|
||||
return 'nomatch';
|
||||
@@ -105,20 +149,20 @@ export function formatStrategyThreshold(
|
||||
export function buildConditionMetrics(
|
||||
rows: Array<VirtualConditionRow & { currentValue: number | null }>,
|
||||
): ConditionMetric[] {
|
||||
return rows.map(row => ({
|
||||
row,
|
||||
status: getConditionStatus(
|
||||
row.conditionType,
|
||||
row.currentValue,
|
||||
row.targetValue,
|
||||
row.satisfied,
|
||||
),
|
||||
progress: row.satisfied === true
|
||||
? 100
|
||||
: row.satisfied === false
|
||||
? (computeConditionProgress(row.conditionType, row.currentValue, row.targetValue) ?? 0)
|
||||
: computeConditionProgress(row.conditionType, row.currentValue, row.targetValue),
|
||||
}));
|
||||
return rows.map(row => {
|
||||
const matchRate = computeConditionMatchRate(row);
|
||||
return {
|
||||
row,
|
||||
status: getConditionStatus(
|
||||
row.conditionType,
|
||||
row.currentValue,
|
||||
row.targetValue,
|
||||
row.satisfied,
|
||||
),
|
||||
matchRate,
|
||||
progress: matchRate,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** 전체 매매조건 일치율 — 백엔드 matchRate 우선 */
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import type { LogicNode } from './strategyTypes';
|
||||
import { CONDITION_LABEL } from './strategyTypes';
|
||||
import type { StrategyDto } from './backendApi';
|
||||
import { getIndicatorDef } from './indicatorRegistry';
|
||||
import { formatIndicatorDisplayLabel } from './indicatorRegistry';
|
||||
|
||||
export interface VirtualConditionRow {
|
||||
id: string;
|
||||
@@ -38,13 +38,18 @@ function walk(
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === 'NOT') {
|
||||
const notChild = node.children?.[0] ?? (node as LogicNode & { child?: LogicNode }).child;
|
||||
if (notChild) walk(notChild, timeframe, side, out, seen);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === 'CONDITION' && node.condition) {
|
||||
const c = node.condition;
|
||||
const key = `${side}:${timeframe}:${c.indicatorType}:${c.conditionType}:${c.targetValue ?? ''}:${c.leftField ?? ''}`;
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
|
||||
const def = getIndicatorDef(c.indicatorType);
|
||||
const condLabel = CONDITION_LABEL[c.conditionType] ?? c.conditionType;
|
||||
const target = c.targetValue ?? c.compareValue ?? null;
|
||||
const plotKey = c.leftField && c.leftField !== 'none' ? c.leftField : c.indicatorType;
|
||||
@@ -52,7 +57,7 @@ function walk(
|
||||
out.push({
|
||||
id: `${node.id}-${side}`,
|
||||
indicatorType: c.indicatorType,
|
||||
displayName: def?.koreanName ?? def?.shortName ?? c.indicatorType,
|
||||
displayName: formatIndicatorDisplayLabel(c.indicatorType),
|
||||
conditionType: c.conditionType,
|
||||
conditionLabel: condLabel,
|
||||
targetValue: target,
|
||||
|
||||
Reference in New Issue
Block a user