using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Domain.Entities.Paper; /// /// 계좌 일별 스냅샷 — (AccountID, TradeDate) UNIQUE. 좌수 NAV·수익률·MDD 를 배치(M2)가 산출/upsert 한다 (d4 §③). /// 리더보드 기간 수익률은 스냅샷 nav 로 조회한다. /// public class PaperDailySnapshot { [ForeignKey(nameof(AccountID))] public virtual PaperAccount Account { get; private set; } = null!; [Key] public int ID { get; private set; } public int AccountID { get; private set; } /// 거래일 public DateOnly TradeDate { get; private set; } /// 자유 + 예약 토큰 public decimal Token { get; private set; } /// 포지션 평가액 public decimal PositionsValue { get; private set; } /// 순자산 = Token + PositionsValue public decimal Equity { get; private set; } /// 좌당 순자산 (18,8) = Equity / Units public decimal UnitNav { get; private set; } /// 일간 수익률 Bp public int DailyReturnBp { get; private set; } /// 누적 수익률 Bp = (nav - 1) × 10000 public int CumReturnBp { get; private set; } /// 최고 순자산 (MDD 산출용) public decimal PeakEquity { get; private set; } /// 최대 낙폭 Bp public int MddBp { get; private set; } /// 누적 체결 수 (리더보드 등재 요건) public int FillCountCum { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; private PaperDailySnapshot() { } public static PaperDailySnapshot Create( int accountID, DateOnly tradeDate, decimal token, decimal positionsValue, decimal equity, decimal unitNav, int dailyReturnBp, int cumReturnBp, decimal peakEquity, int mddBp, int fillCountCum ) { if (accountID <= 0) { throw new ArgumentOutOfRangeException(nameof(accountID)); } return new PaperDailySnapshot { AccountID = accountID, TradeDate = tradeDate, Token = token, PositionsValue = positionsValue, Equity = equity, UnitNav = unitNav, DailyReturnBp = dailyReturnBp, CumReturnBp = cumReturnBp, PeakEquity = peakEquity, MddBp = mddBp, FillCountCum = fillCountCum }; } /// 동일 (AccountID, TradeDate) 재실행 시 값 갱신 (upsert, 배치 멱등). public void Update( decimal token, decimal positionsValue, decimal equity, decimal unitNav, int dailyReturnBp, int cumReturnBp, decimal peakEquity, int mddBp, int fillCountCum ) { Token = token; PositionsValue = positionsValue; Equity = equity; UnitNav = unitNav; DailyReturnBp = dailyReturnBp; CumReturnBp = cumReturnBp; PeakEquity = peakEquity; MddBp = mddBp; FillCountCum = fillCountCum; } }