Files
goldenChart/투자분석결과 산정방식.md
2026-06-07 15:25:45 +09:00

14 KiB
Raw Permalink Blame History

투자분석결과 산정방식

GoldenChart의 백테스팅·상세분석 레포트·알림 목록 레포트 등에서 표시되는 수익률·손익·승률이 어떻게 계산되는지 정리한 문서입니다.


1. 개요

투자분석 결과는 백엔드 BacktestingService가 캔들 구간을 순회하며 전략 시그널을 재현하고, 포트폴리오 손익을 시뮬레이션한 뒤 BacktestAnalysisDto를 생성합니다. 프론트엔드는 이 DTO를 BacktestAnalysis 타입으로 받아 BacktestDashboard(상세분석 레포트 팝업) 등에 표시합니다.

[설정] gc_backtest_settings.analysis_method
    ↓
[API] POST /backtesting/run  (settings 포함)
    ↓
[백엔드] BacktestingService.runBacktest()
    ├─ 시그널 재현 (Ta4j Rule + TradingRecord)
    ├─ PortfolioLedger (LONG 표준 회계) 또는 레거시 비율 모델 (SHORT/BOTH)
    └─ calcAnalysis() → BacktestAnalysisDto
    ↓
[프론트] BacktestAnalysisReportModal / BacktestDashboard

핵심 원칙

  • 수익률·손익 금액은 사용자가 선택한 투자분석 방식에 따라 달라집니다.
  • 승률·거래 횟수는 청산 완료된 포지션(라운드트립) 기준이며, 분석 방식과 무관하게 동일한 규칙을 사용합니다.
  • 리스크 지표(MDD, Sharpe 등)는 Ta4j AnalysisCriterion 결과를 사용하며, 총 수익률은 포트폴리오 시뮬레이션 결과와 연동됩니다.

2. 투자분석 방식 설정

2.1 설정 항목

DB 컬럼 DTO 필드 기본값 설명
gc_backtest_settings.analysis_method analysisMethod MARK_TO_MARKET 수익률·손익 산정 방식

2.2 선택 가능한 방식

코드 UI 표시명 설명
MARK_TO_MARKET 평가손익 포함 (표준) HTS·MTS 평가금액 방식. 예수금 + 보유주식 시가평가
REALIZED_ONLY 실현손익만 청산 완료된 거래의 실현손익만 수익률에 반영. 기말 미청산 포지션은 평가 제외

2.3 설정 저장·로드

  • 저장소: gc_backtest_settings (디바이스 ID 기준 upsert)
  • API: GET/POST /backtest-settings
  • UI
    • 설정 화면 → 백테스팅 → 투자분석 방식 (BacktestSettingsPanel.tsx)
    • 차트 툴바 백테스트 설정 모달 (BacktestSettingsModal.tsx)

2.4 설정이 적용되는 화면

runBacktest() 호출 시 settings를 함께 전달하는 모든 경로에 동일하게 적용됩니다.

화면 설정 로드 방식
차트 백테스팅 App.tsxloadBacktestSettings()useBacktest()
백테스팅 메뉴 실행 시 당시 저장된 설정 (재실행 시 최신 설정)
분석레포트 좌측 목록 선택 후 레포트 생성 시
알림 목록 상세분석 레포트 loadBacktestSettings()runBacktest()
모의투자 분석 차트 PaperAnalysisChartloadBacktestSettings()

참고: DB에 이미 저장된 과거 백테스트 결과(gc_backtest_result.analysis_json)는 당시 방식 그대로입니다. 새 방식을 보려면 백테스트를 다시 실행해야 합니다.


3. 분석 대상 기간

백테스트는 요청에 포함된 캔들 배열 전체를 대상으로 합니다.

실행 경로 기간 결정
차트 백테스팅 현재 차트에 로드된 캔들 구간
알림 목록 레포트 알림 candleTime을 끝점으로 과거 300봉 (loadAnalysisCandles)
백테스팅 메뉴 저장된 백테스트 결과의 fromTime ~ toTime

기간의 첫 봉 ~ 마지막 봉을 순회하며 매수·매도 시그널을 재현합니다.


4. 포트폴리오 회계 (PortfolioLedger)

LONG 포지션(기본 설정 positionDirection = LONG)에서는 표준 주식 프로그램 방식의 예수금·보유수량 회계를 사용합니다.

구현 파일: backend/src/main/java/com/goldenchart/service/PortfolioLedger.java

4.1 상태 변수

변수 의미
cash 예수금 (초기값 = initialCapital)
shares 보유 수량
costBasis 현재 보유분 취득원가 (매수 수수료 포함)
realizedPnl 누적 실현손익
grossProfit 실현 이익 거래 합계 (양수만 누적)
grossLoss 실현 손실 거래 합계 (음수만 누적)

4.2 평가금액 (기간 중 임의 시점)

평가금액 = cash + shares × markPrice

markPrice는 해당 봉의 종가(또는 진입/청산 가격 설정에 따른 가격)입니다.

4.3 매수 체결 (executeBuy)

  1. 주문 금액 결정
    • tradeSizeType = CAPITAL_PCT: 평가금액 × (tradeSizeValue / 100)
    • tradeSizeType = FIXED_AMOUNT: min(tradeSizeValue, cash)
  2. 수수료: commissionType = LINEAR일 때 commissionRate 적용 (ZERO이면 0)
  3. 슬리피지: 매수 시 effEntry = price × (1 + slippageRate) (applySlippage)
  4. 체결
    sharesToBuy = orderAmount / effEntry
    totalCost   = sharesToBuy × effEntry × (1 + commissionRate)
    cash       -= totalCost
    shares     += sharesToBuy
    costBasis  += totalCost
    

예수금이 부족하면 가용 예수금 범위 내에서만 매수합니다.

4.4 매도 체결 (executeSell)

sellFraction: 청산 비율 (0~1). 전량 매도 = 1, 분할 청산 = partialExitPct / 100.

sharesToSell = shares × sellFraction
proceeds     = sharesToSell × effExit × (1 - commissionRate)
costPortion  = costBasis × (sharesToSell / shares)
pnl          = proceeds - costPortion

cash        += proceeds
realizedPnl += pnl
grossProfit += pnl (pnl ≥ 0)
grossLoss   += pnl (pnl < 0)
shares      -= sharesToSell
costBasis   -= costPortion

effExit는 매도 슬리피지 적용: price × (1 - slippageRate).

4.5 기말 최종 자산 (resolveFinalEquity)

마지막 봉 종가를 lastMarkPrice로 사용합니다.

MARK_TO_MARKET (평가손익 포함)

최종 자산(finalEquity) = cash + shares × lastMarkPrice
평가 손익(unrealizedPnl) = shares × lastMarkPrice - costBasis  (보유 시)

기말에 미청산 포지션이 있어도 보유주식을 종가로 평가하여 수익률에 반영합니다.

REALIZED_ONLY (실현손익만)

최종 자산(finalEquity) = initialCapital + realizedPnl

기말 미청산 포지션의 시가평가는 수익률에 포함하지 않습니다.


5. 수익률·손익 지표 산식

모든 비율 값은 소수로 저장됩니다 (예: 0.0427 = +4.27%). 프론트엔드 pct() 함수가 ×100 하여 %로 표시합니다.

5.1 총 수익률

totalReturnPct = (finalEquity - initialCapital) / initialCapital
  • initialCapital: 백테스트 설정의 초기 자본 (기본 10,000,000원)
  • finalEquity: §4.5 기말 최종 자산

5.2 총 손익 (금액)

totalProfitLoss = finalEquity - initialCapital

5.3 총 이익 / 총 손실

  • grossProfit: 청산 시 실현 이익의 합 (PortfolioLedger 누적)
  • grossLoss: 청산 시 실현 손실의 합 (음수)

두 값의 합은 REALIZED_ONLY 기준 실현손익과 일치합니다. MARK_TO_MARKET에서는 기말 평가손익이 추가로 반영되어 grossProfit + grossLosstotalProfitLoss가 다를 수 있습니다.

5.4 평균 수익률/거래

avgReturnPct = totalReturnPct / numberOfPositions   (거래 1건 이상)

5.5 손익비 (Profit Factor)

profitLossRatio = grossProfit / |grossLoss|   (grossLoss ≠ 0)

6. 승률·거래 통계

승률은 투자분석 방식과 무관하며, Ta4j TradingRecord청산 완료 포지션을 기준으로 합니다.

지표 산식 / 출처
numberOfPositions NumberOfPositionsCriterion (완료 거래 수)
numberOfWinning NumberOfWinningPositionsCriterion 또는 position.getProfit() > 0
numberOfLosing NumberOfLosingPositionsCriterion 또는 position.getProfit() < 0
numberOfBreakEven positions - winning - losing
winRate winning / positions (0~1)

판정 기준

  • 각 포지션 = 매수 진입 후 매도 청산까지의 1회 라운드트립
  • 포지션별 손익 = Ta4j position.getProfit() (진입가·청산가·수량 기준)
  • 기말 미청산 매수는 승률 집계에 포함되지 않음

예시: 13거래 중 4승 9패 → winRate = 4/13 ≈ 0.308 → 화면 30.8%


7. 리스크·벤치마크 지표

지표 계산 방식
maxDrawdownPct Ta4j MaximumDrawdownCriterion
maxRunupPct Ta4j MaximumRunupCriterion
sharpeRatio Ta4j SharpeRatioCriterion
sortinoRatio Ta4j SortinoRatioCriterion
calmarRatio `totalReturnPct /
valueAtRisk95 Ta4j ValueAtRiskCriterion
expectedShortfall Ta4j ExpectedShortfallCriterion
buyAndHoldReturnPct Ta4j EnterAndHoldReturnCriterion 또는 첫봉~마지막봉 종가 변화율
vsBuyAndHold (1 + totalReturnPct) / (1 + buyAndHoldReturnPct)

총 수익률(totalReturnPct)이 포트폴리오 시뮬레이션 기준으로 산출되므로, 칼마 비율·벤치마크 비교·초과 수익도 선택한 분석 방식의 영향을 받습니다.


8. SHORT / BOTH 포지션 (레거시 비율 모델)

positionDirectionLONG이 아닌 경우(SHORT, BOTH)에는 PortfolioLedger 대신 비율 기반 equity 갱신을 사용합니다.

equity += equity × tradeSizePct × sellFraction × (rawReturn - commission×2)

기말 처리 (resolveFinalEquity):

분석 방식 기말 미청산 시
MARK_TO_MARKET 마지막 봉 종가 기준 미실현 수익률을 equity에 반영
REALIZED_ONLY initialCapital + 누적 실현손익 (비율 모델 gross 합계)

9. 백테스트 실행 흐름 (BacktestingService)

1. BarSeries 생성 (요청 캔들)
2. entryRule / exitRule 합성 (DSL + 손절/익절/트레일링)
3. 봉별 루프
   ├─ positionMode = SIGNAL_ONLY → 지표만으로 BUY/SELL 시그널
   └─ positionMode = LONG_ONLY  → Ta4j TradingRecord 포지션 제약
4. 진입 시: enterPosition() → PortfolioLedger.executeBuy() (LONG)
5. 청산 시: applyExit() → PortfolioLedger.executeSell()
6. 기말: resolveFinalEquity(lastMarkPrice)
7. calcAnalysis() → BacktestAnalysisDto
8. (선택) gc_backtest_result 저장

관련 파일:

  • backend/src/main/java/com/goldenchart/service/BacktestingService.java
  • backend/src/main/java/com/goldenchart/service/PortfolioLedger.java
  • backend/src/main/java/com/goldenchart/dto/BacktestAnalysisDto.java
  • backend/src/main/java/com/goldenchart/dto/BacktestSettingsDto.java

10. API 응답 구조 (BacktestAnalysis)

프론트엔드 BacktestAnalysis 인터페이스 (frontend/src/utils/backendApi.ts)와 1:1 대응합니다.

10.1 포트폴리오·방식 (추가 필드)

필드 타입 설명
analysisMethod 'MARK_TO_MARKET' | 'REALIZED_ONLY' 적용된 분석 방식
cashBalance number 기말 예수금
holdingsValue number 기말 보유주식 평가액 (shares × lastMarkPrice)
realizedPnl number 실현 손익
unrealizedPnl number 평가 손익 (미청산 보유분)

10.2 수익성·거래·리스크

§5~§7 절의 totalReturnPct, totalProfitLoss, grossProfit, grossLoss, winRate, maxDrawdownPct, sharpeRatio 등.


11. 프론트엔드 표시

11.1 상세분석 레포트 (BacktestDashboard)

파일: frontend/src/components/BacktestResultModal.tsx

  • 헤더 배지: analysisMethodLabel() — 적용된 분석 방식 표시
  • 수익성 지표 카드:
    • 총 수익률, 총 손익
    • 실현 손익, 평가 손익 (있을 때)
    • 기말 예수금, 보유 평가액 (있을 때)
    • 총 이익, 총 손실
  • 거래 통계: 승률 원형 차트, 수익/손실 거래 수

표시 유틸: frontend/src/components/shared/analysisDashboardUi.tsx

pct(v)    = (v × 100).toFixed(2) + '%'   // 부호 포함
pctAbs(v) = (v × 100).toFixed(1) + '%'   // 승률 등
wonFmt(v) = 원화 단위 (/ 축약)

11.2 분석 방식 라벨

파일: frontend/src/utils/analysisMethodLabels.ts


12. 계산 예시

조건: 초기 자본 1,000만원, 13거래 전량 청산, 최종 평가금액 957만원, 분석 방식 MARK_TO_MARKET

totalReturnPct  = (9,570,000 - 10,000,000) / 10,000,000 = -0.043  →  -4.30%
totalProfitLoss = -430,000원
winRate         = 4 / 13 ≈ 30.8%  (승률은 거래 단위, 수익률과 별개)

동일 데이터에서 REALIZED_ONLY이고 기말 미청산이 없다면 finalEquity가 동일하면 수익률도 동일합니다. 기말 미청산 보유가 있으면 두 방식의 finalEquity·수익률이 달라집니다.


13. 제한 사항 및 유의점

  1. 단일 종목·단일 포지션: maxOpenTrades = 1 기준 단일 심볼 LONG 회계입니다.
  2. 과거 결과 불변: 이미 DB에 저장된 analysis_json은 재계산되지 않습니다.
  3. 승률 vs 수익률: 승률은 거래 건별 손익 부호, 수익률은 포트폴리오 전체 평가금액 변화입니다. 반드시 일치하지 않습니다.
  4. SHORT/BOTH: LONG과 다른 비율 모델을 사용합니다. 정밀한 공매도 회계가 필요하면 향후 확장 대상입니다.
  5. 수수료·슬리피지: 백테스트 설정(commissionRate, slippageRate)에 따르며, 실거래 환경과 차이가 있을 수 있습니다.

14. 관련 파일 목록

구분 경로
포트폴리오 회계 backend/.../service/PortfolioLedger.java
백테스트·분석 엔진 backend/.../service/BacktestingService.java
분석 결과 DTO backend/.../dto/BacktestAnalysisDto.java
설정 DTO backend/.../dto/BacktestSettingsDto.java
DB 마이그레이션 backend/.../db/migration/V63__backtest_analysis_method.sql
프론트 API 타입 frontend/src/utils/backendApi.ts
분석 방식 라벨 frontend/src/utils/analysisMethodLabels.ts
설정 UI frontend/src/components/BacktestSettingsPanel.tsx
레포트 UI frontend/src/components/BacktestResultModal.tsx
알림 레포트 frontend/src/components/TradeNotificationListPage.tsx

문서 작성 기준: GoldenChart 백엔드 PortfolioLedger / BacktestingService 구현 반영