From a6e889a579f135a48ed69b7b4e28cb2f14f47f9b Mon Sep 17 00:00:00 2001 From: Macbook Date: Tue, 16 Jun 2026 23:56:17 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=84=EB=9E=B5=ED=8F=89=EA=B0=80=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=84=A4=EB=AA=85=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StrategyEvaluationDetailPanel.tsx | 71 ++++++++++ .../StrategyEvaluationInterpretContent.tsx | 59 ++++++++ .../StrategyEvaluationInterpretModal.tsx | 53 +------- .../StrategyEvaluationSignalPanel.tsx | 119 ++++++---------- frontend/src/styles/strategyEvaluation.css | 128 ++++++++++++++++-- 5 files changed, 294 insertions(+), 136 deletions(-) create mode 100644 frontend/src/components/strategyEvaluation/StrategyEvaluationDetailPanel.tsx create mode 100644 frontend/src/components/strategyEvaluation/StrategyEvaluationInterpretContent.tsx diff --git a/frontend/src/components/strategyEvaluation/StrategyEvaluationDetailPanel.tsx b/frontend/src/components/strategyEvaluation/StrategyEvaluationDetailPanel.tsx new file mode 100644 index 0000000..0d472a9 --- /dev/null +++ b/frontend/src/components/strategyEvaluation/StrategyEvaluationDetailPanel.tsx @@ -0,0 +1,71 @@ +import React, { useMemo } from 'react'; +import type { StrategyDto } from '../../utils/backendApi'; +import type { ConditionMetric } from '../../utils/virtualSignalMetrics'; +import { + buildConditionContextMap, + buildSideInterpretation, + formatBarTimeLabel, +} from '../../utils/strategyEvaluationInterpret'; +import StrategyEvaluationInterpretContent from './StrategyEvaluationInterpretContent'; + +interface Props { + buyMetrics: ConditionMetric[]; + sellMetrics: ConditionMetric[]; + buyOverallMet?: boolean | null; + sellOverallMet?: boolean | null; + strategy?: StrategyDto | null; + barTimeSec?: number | null; + getParams?: ( + type: string, + defaults?: Record, + ) => Record; +} + +const StrategyEvaluationDetailPanel: React.FC = ({ + buyMetrics, + sellMetrics, + buyOverallMet, + sellOverallMet, + strategy, + barTimeSec, + getParams, +}) => { + const contextMap = useMemo( + () => buildConditionContextMap(strategy, getParams), + [strategy, getParams], + ); + const buyInterpretation = useMemo( + () => buildSideInterpretation('buy', buyMetrics, buyOverallMet, contextMap), + [buyMetrics, buyOverallMet, contextMap], + ); + const sellInterpretation = useMemo( + () => buildSideInterpretation('sell', sellMetrics, sellOverallMet, contextMap), + [sellMetrics, sellOverallMet, contextMap], + ); + + const barLabel = formatBarTimeLabel(barTimeSec); + + return ( +
+
+ 상세 분석 + {barLabel} +
+
+
+

매수

+ +
+
+

매도

+ +
+
+ ※ 조건별 결과는 Ta4j Rule 평가값이며, AND/OR/NOT 전체 결합 결과는 「전체 충족/미충족」을 참고하세요. +
+
+
+ ); +}; + +export default StrategyEvaluationDetailPanel; diff --git a/frontend/src/components/strategyEvaluation/StrategyEvaluationInterpretContent.tsx b/frontend/src/components/strategyEvaluation/StrategyEvaluationInterpretContent.tsx new file mode 100644 index 0000000..c50ffda --- /dev/null +++ b/frontend/src/components/strategyEvaluation/StrategyEvaluationInterpretContent.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import type { SideInterpretation } from '../../utils/strategyEvaluationInterpret'; + +interface Props { + interpretation: SideInterpretation; + compact?: boolean; +} + +/** 매수/매도 조건 상세 해석 본문 (모달·우측 패널 공용) */ +const StrategyEvaluationInterpretContent: React.FC = ({ + interpretation, + compact = false, +}) => { + const overallClass = interpretation.overallMet === true + ? 'seval-interpret-overall--met' + : interpretation.overallMet === false + ? 'seval-interpret-overall--unmet' + : 'seval-interpret-overall--unknown'; + + return ( +
+
+ + {interpretation.overallMet === true + ? '전체 충족' + : interpretation.overallMet === false + ? '전체 미충족' + : '전체 평가 불명'} + +

{interpretation.overallSummary}

+
+ + {interpretation.conditions.length === 0 ? ( +

표시할 조건이 없습니다.

+ ) : ( +
    + {interpretation.conditions.map(item => ( +
  • +
    + + {item.statusLabel} + + + {item.summary || item.label} + +
    +

    {item.reason}

    +
  • + ))} +
+ )} +
+ ); +}; + +export default StrategyEvaluationInterpretContent; diff --git a/frontend/src/components/strategyEvaluation/StrategyEvaluationInterpretModal.tsx b/frontend/src/components/strategyEvaluation/StrategyEvaluationInterpretModal.tsx index 79cea3d..a215380 100644 --- a/frontend/src/components/strategyEvaluation/StrategyEvaluationInterpretModal.tsx +++ b/frontend/src/components/strategyEvaluation/StrategyEvaluationInterpretModal.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import DraggableModalFrame from '../DraggableModalFrame'; import type { SideInterpretation } from '../../utils/strategyEvaluationInterpret'; import { formatBarTimeLabel } from '../../utils/strategyEvaluationInterpret'; +import StrategyEvaluationInterpretContent from './StrategyEvaluationInterpretContent'; interface Props { side: 'buy' | 'sell'; @@ -21,12 +22,6 @@ const StrategyEvaluationInterpretModal: React.FC = ({ const title = side === 'buy' ? '매수 조건 해석' : '매도 조건 해석'; const barLabel = formatBarTimeLabel(barTimeSec); - const overallClass = interpretation.overallMet === true - ? 'seval-interpret-overall--met' - : interpretation.overallMet === false - ? 'seval-interpret-overall--unmet' - : 'seval-interpret-overall--unknown'; - const intro = useMemo(() => { const parts = [`${barLabel} 시점`, strategyName ? `「${strategyName}」` : null].filter(Boolean); return parts.join(' · '); @@ -44,47 +39,11 @@ const StrategyEvaluationInterpretModal: React.FC = ({ bodyClassName="seval-interpret-body app-popup-body" zIndex={11000} > -
-

{intro}

- -
- - {interpretation.overallMet === true - ? '전체 충족' - : interpretation.overallMet === false - ? '전체 미충족' - : '전체 평가 불명'} - -

{interpretation.overallSummary}

-
- - {interpretation.conditions.length === 0 ? ( -

표시할 조건이 없습니다.

- ) : ( -
    - {interpretation.conditions.map(item => ( -
  • -
    - - {item.statusLabel} - - - {item.summary || item.label} - -
    -

    {item.reason}

    -
  • - ))} -
- )} - -
- ※ 조건별 결과는 Ta4j Rule 평가값이며, AND/OR/NOT 전체 결합 결과는 상단 「전체 충족/미충족」을 참고하세요. -
-
+

{intro}

+ +
+ ※ 조건별 결과는 Ta4j Rule 평가값이며, AND/OR/NOT 전체 결합 결과는 상단 「전체 충족/미충족」을 참고하세요. +
); }; diff --git a/frontend/src/components/strategyEvaluation/StrategyEvaluationSignalPanel.tsx b/frontend/src/components/strategyEvaluation/StrategyEvaluationSignalPanel.tsx index 2ab64c3..e4dc33a 100644 --- a/frontend/src/components/strategyEvaluation/StrategyEvaluationSignalPanel.tsx +++ b/frontend/src/components/strategyEvaluation/StrategyEvaluationSignalPanel.tsx @@ -1,16 +1,9 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import type { StrategyDto } from '../../utils/backendApi'; import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSnapshots'; -import { - buildConditionMetrics, - type ConditionMetric, -} from '../../utils/virtualSignalMetrics'; -import { - buildConditionContextMap, - buildSideInterpretation, -} from '../../utils/strategyEvaluationInterpret'; +import { buildConditionMetrics } from '../../utils/virtualSignalMetrics'; import StrategyEvaluationMatchVisual from './StrategyEvaluationMatchVisual'; -import StrategyEvaluationInterpretModal from './StrategyEvaluationInterpretModal'; +import StrategyEvaluationDetailPanel from './StrategyEvaluationDetailPanel'; import type { BarSignalHighlight } from '../../utils/strategyEvaluationBarSignals'; interface Props { @@ -29,12 +22,9 @@ interface Props { interface SideCardProps { side: 'buy' | 'sell'; - metrics: ConditionMetric[]; + metrics: ReturnType; overallMet?: boolean | null; signalActive?: boolean; - strategy?: StrategyDto | null; - barTimeSec?: number | null; - getParams?: Props['getParams']; } const SideCard: React.FC = ({ @@ -42,70 +32,38 @@ const SideCard: React.FC = ({ metrics, overallMet, signalActive = false, - strategy, - barTimeSec, - getParams, }) => { - const [interpretOpen, setInterpretOpen] = useState(false); const sideLabel = side === 'buy' ? '매수' : '매도'; const title = side === 'buy' ? '매수 조건' : '매도 조건'; const overallLabel = overallMet === true ? '충족' : overallMet === false ? '미충족' : null; - const contextMap = useMemo(() => buildConditionContextMap(strategy, getParams), [strategy, getParams]); - const interpretation = useMemo( - () => buildSideInterpretation(side, metrics, overallMet, contextMap), - [side, metrics, overallMet, contextMap], - ); - return ( - <> -
-
-
- {sideLabel} - {overallLabel && ( - - {overallLabel} - - )} -
- -
-
- +
+
+
+ {sideLabel} + {overallLabel && ( + + {overallLabel} + + )}
-
- - {interpretOpen && ( - setInterpretOpen(false)} - /> - )} - + +
+ +
+
); }; @@ -141,20 +99,23 @@ const StrategyEvaluationSignalPanel: React.FC = ({ return (
- + +