상세투자분석 계산로직 수정
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package com.goldenchart.service;
|
||||
|
||||
import com.goldenchart.dto.BacktestSettingsDto;
|
||||
|
||||
/**
|
||||
* 표준 주식 프로그램 방식의 포트폴리오 회계.
|
||||
* <ul>
|
||||
* <li>MARK_TO_MARKET: 예수금 + 보유주식 시가평가 (평가손익 포함)</li>
|
||||
* <li>REALIZED_ONLY: 청산 완료 실현손익만 반영 (기말 미청산 평가 제외)</li>
|
||||
* </ul>
|
||||
*/
|
||||
final class PortfolioLedger {
|
||||
|
||||
static final String MARK_TO_MARKET = "MARK_TO_MARKET";
|
||||
static final String REALIZED_ONLY = "REALIZED_ONLY";
|
||||
|
||||
final double initialCapital;
|
||||
final String analysisMethod;
|
||||
final BacktestSettingsDto cfg;
|
||||
|
||||
double cash;
|
||||
double shares;
|
||||
/** 현재 보유분 취득원가(수수료 포함) */
|
||||
double costBasis;
|
||||
double realizedPnl;
|
||||
double grossProfit;
|
||||
double grossLoss;
|
||||
|
||||
PortfolioLedger(double initialCapital, BacktestSettingsDto cfg) {
|
||||
this.initialCapital = initialCapital;
|
||||
this.cfg = cfg;
|
||||
this.analysisMethod = normalizeMethod(cfg.getAnalysisMethod());
|
||||
this.cash = initialCapital;
|
||||
}
|
||||
|
||||
static String normalizeMethod(String raw) {
|
||||
return REALIZED_ONLY.equalsIgnoreCase(raw) ? REALIZED_ONLY : MARK_TO_MARKET;
|
||||
}
|
||||
|
||||
boolean hasPosition() {
|
||||
return shares > 1e-12;
|
||||
}
|
||||
|
||||
/** 평가금액 = 예수금 + 보유주식 평가액 */
|
||||
double portfolioValue(double markPrice) {
|
||||
return cash + shares * markPrice;
|
||||
}
|
||||
|
||||
double unrealizedPnl(double markPrice) {
|
||||
if (shares <= 1e-12) return 0.0;
|
||||
return shares * markPrice - costBasis;
|
||||
}
|
||||
|
||||
double resolveFinalEquity(double lastMarkPrice) {
|
||||
if (REALIZED_ONLY.equals(analysisMethod)) {
|
||||
return initialCapital + realizedPnl;
|
||||
}
|
||||
return cash + shares * lastMarkPrice;
|
||||
}
|
||||
|
||||
/** LONG 매수 체결 */
|
||||
void executeBuy(double effEntry, double markPriceForSizing) {
|
||||
if (effEntry <= 0 || cash <= 0) return;
|
||||
double commRate = commissionRate();
|
||||
double orderAmount = computeOrderAmount(markPriceForSizing);
|
||||
if (orderAmount <= 0) return;
|
||||
|
||||
double maxSpend = cash;
|
||||
orderAmount = Math.min(orderAmount, maxSpend / (1 + commRate));
|
||||
if (orderAmount <= 0) return;
|
||||
|
||||
double sharesToBuy = orderAmount / effEntry;
|
||||
double totalCost = sharesToBuy * effEntry * (1 + commRate);
|
||||
if (totalCost > cash + 1e-6) {
|
||||
sharesToBuy = cash / (effEntry * (1 + commRate));
|
||||
totalCost = sharesToBuy * effEntry * (1 + commRate);
|
||||
}
|
||||
if (sharesToBuy <= 1e-12) return;
|
||||
|
||||
cash -= totalCost;
|
||||
shares += sharesToBuy;
|
||||
costBasis += totalCost;
|
||||
}
|
||||
|
||||
/** LONG 매도 체결 — sellFraction: 0~1 (전량=1) */
|
||||
void executeSell(double effExit, double sellFraction) {
|
||||
if (effExit <= 0 || shares <= 1e-12) return;
|
||||
double fraction = Math.max(0.0, Math.min(1.0, sellFraction));
|
||||
double sharesToSell = shares * fraction;
|
||||
if (sharesToSell <= 1e-12) return;
|
||||
|
||||
double commRate = commissionRate();
|
||||
double proceeds = sharesToSell * effExit * (1 - commRate);
|
||||
double costPortion = costBasis * (sharesToSell / shares);
|
||||
double pnl = proceeds - costPortion;
|
||||
|
||||
cash += proceeds;
|
||||
realizedPnl += pnl;
|
||||
if (pnl >= 0) grossProfit += pnl;
|
||||
else grossLoss += pnl;
|
||||
|
||||
shares -= sharesToSell;
|
||||
costBasis -= costPortion;
|
||||
if (shares <= 1e-12) {
|
||||
shares = 0;
|
||||
costBasis = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private double computeOrderAmount(double markPrice) {
|
||||
double tradeSizePct = cfg.getTradeSizeValue() != null
|
||||
? cfg.getTradeSizeValue().doubleValue() / 100.0 : 1.0;
|
||||
if ("FIXED_AMOUNT".equals(cfg.getTradeSizeType())) {
|
||||
double fixed = cfg.getTradeSizeValue() != null ? cfg.getTradeSizeValue().doubleValue() : 0;
|
||||
return Math.min(fixed, cash);
|
||||
}
|
||||
double equity = portfolioValue(markPrice);
|
||||
return equity * tradeSizePct;
|
||||
}
|
||||
|
||||
private double commissionRate() {
|
||||
if ("ZERO".equals(cfg.getCommissionType())) return 0.0;
|
||||
return cfg.getCommissionRate() != null ? cfg.getCommissionRate().doubleValue() : 0.0015;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user