package com.goldenchart.service; import com.goldenchart.dto.BacktestSettingsDto; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertEquals; /** * 체결(ledger) 순손익 기준 승률·승패 집계 검증. * 분석레포트_수정.pdf 28시그널 / 14라운드트립 시나리오. */ class PortfolioLedgerTradeStatsTest { private static final double INIT = 10_000_000.0; private PortfolioLedger newLedger() { BacktestSettingsDto cfg = BacktestSettingsDto.builder() .initialCapital(BigDecimal.valueOf(INIT)) .commissionType("LINEAR") .commissionRate(new BigDecimal("0.00150")) .slippageRate(new BigDecimal("0.00050")) .tradeSizeType("CAPITAL_PCT") .tradeSizeValue(new BigDecimal("100")) .analysisMethod("MARK_TO_MARKET") .build(); return new PortfolioLedger(INIT, cfg); } @Test void pdfScenario_netPnlWinLoss_afterFees() { double[][] pairs = { {93254604, 92806574}, {92876415, 92203875}, {91648802, 92324815}, {92723339, 93019467}, {92258106, 92293830}, {92507231, 92150902}, {92463209, 91909023}, {92117036, 92075939}, {92298126, 92062946}, {92253104, 92280837}, {92306130, 92359797}, {93446700, 93045454}, {93458706, 93657148}, {94142048, 93835059}, }; PortfolioLedger ledger = newLedger(); long t = 1_700_000_000L; int bar = 0; for (double[] pair : pairs) { // PDF 시그널 가격 = 슬리피지 반영 체결가 ledger.executeBuy(pair[0], pair[0], t + bar, bar); bar += 8; ledger.executeSell(pair[1], 1.0, t + bar, bar); bar += 8; } PortfolioLedger.TradeStats ts = ledger.tradeStats(); assertEquals(14, ts.closedCount); assertEquals(2, ts.winning, "수수료 반영 순손익 기준 2승"); assertEquals(12, ts.losing, "수수료 반영 순손익 기준 12패"); assertEquals(0, ts.breakEven); assertEquals(2.0 / 14.0, ts.winRate, 1e-9); double finalEq = ledger.resolveFinalEquity(pairs[pairs.length - 1][1]); assertEquals(-0.0588, (finalEq - INIT) / INIT, 0.002); assertEquals(ledger.grossProfit + ledger.grossLoss, ledger.realizedPnl, 1.0); } }