| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- using System.ComponentModel.DataAnnotations;
- using System.ComponentModel.DataAnnotations.Schema;
- using Domain.Entities.Members;
- namespace Domain.Entities.Paper;
- /// <summary>
- /// 모의투자 계좌 — 회원당 1개(상시 단일 계좌). 토큰을 입금해 매매, 출금해 회수한다.
- /// 좌수제(펀드 NAV): 입출금 왜곡 없이 순수 운용 성과를 측정 (d4 §③ 좌수제).
- /// Equity/nav 등 평가값은 핸들러가 시세로 계산 — 엔티티에 저장하지 않는다.
- /// </summary>
- public class PaperAccount
- {
- [ForeignKey(nameof(MemberID))]
- public virtual Member Member { get; private set; } = null!;
- [Key]
- public int ID { get; private set; }
- public int MemberID { get; private set; }
- /// <summary>자유 현금 토큰 (매수 예약 제외)</summary>
- public decimal Token { get; private set; }
- /// <summary>매수 주문으로 예약된 토큰</summary>
- public decimal ReservedToken { get; private set; }
- /// <summary>좌수 (18,8) — 발행/소각으로만 변동, 매매는 좌수 불변</summary>
- public decimal Units { get; private set; }
- /// <summary>누적 입금 토큰</summary>
- public decimal TotalDeposited { get; private set; }
- /// <summary>누적 출금 토큰</summary>
- public decimal TotalWithdrawn { get; private set; }
- public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
- [Timestamp]
- public byte[] RowVersion { get; private set; } = default!;
- private PaperAccount() { }
- public static PaperAccount Create(int memberID)
- {
- if (memberID <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(memberID));
- }
- return new PaperAccount
- {
- MemberID = memberID,
- Token = 0,
- ReservedToken = 0,
- Units = 0,
- TotalDeposited = 0,
- TotalWithdrawn = 0
- };
- }
- /// <summary>입금 — 토큰/좌수 증가. 좌수 발행 계산은 호출자(nav 필요).</summary>
- public void Deposit(decimal tokenAmount, decimal unitsIssued)
- {
- if (tokenAmount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(tokenAmount));
- }
- if (unitsIssued <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(unitsIssued));
- }
- Token += tokenAmount;
- Units += unitsIssued;
- TotalDeposited += tokenAmount;
- }
- /// <summary>출금 — 자유 현금 토큰만 인출 가능. 좌수 소각.</summary>
- public void Withdraw(decimal tokenAmount, decimal unitsBurned)
- {
- if (tokenAmount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(tokenAmount));
- }
- if (Token < tokenAmount)
- {
- throw new InvalidOperationException("출금 가능한 자유 토큰이 부족합니다.");
- }
- if (unitsBurned <= 0 || unitsBurned > Units)
- {
- throw new ArgumentOutOfRangeException(nameof(unitsBurned));
- }
- Token -= tokenAmount;
- Units -= unitsBurned;
- TotalWithdrawn += tokenAmount;
- }
- /// <summary>매수 예약 — 자유 토큰을 예약금으로 이동.</summary>
- public void ReserveForBuy(decimal amount)
- {
- if (amount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(amount));
- }
- if (Token < amount)
- {
- throw new InvalidOperationException("매수 예약 가능한 토큰이 부족합니다.");
- }
- Token -= amount;
- ReservedToken += amount;
- }
- /// <summary>매수 예약 환원 (주문 취소·거부).</summary>
- public void ReleaseBuyReserve(decimal amount)
- {
- if (amount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(amount));
- }
- if (ReservedToken < amount)
- {
- throw new InvalidOperationException("환원할 예약금이 부족합니다.");
- }
- ReservedToken -= amount;
- Token += amount;
- }
- /// <summary>
- /// 매수 체결 정산 (배치 M2) — 예약금 전액을 해제하고 실제 사용분(actualCost)을 제외한 잔여를 자유 토큰으로 환급한다.
- /// ReservedToken -= reservedAmount; Token += (reservedAmount - actualCost). 좌수는 불변.
- /// </summary>
- public void SettleBuy(decimal reservedAmount, decimal actualCost)
- {
- if (reservedAmount <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(reservedAmount));
- }
- if (actualCost < 0 || actualCost > reservedAmount)
- {
- throw new ArgumentOutOfRangeException(nameof(actualCost));
- }
- if (ReservedToken < reservedAmount)
- {
- throw new InvalidOperationException("정산할 예약금이 부족합니다.");
- }
- ReservedToken -= reservedAmount;
- Token += reservedAmount - actualCost;
- }
- /// <summary>매도 대금·청산 대금 입금 (배치 M2) — 자유 토큰 증가. 좌수는 불변.</summary>
- public void AddCash(decimal amount)
- {
- if (amount < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(amount));
- }
- Token += amount;
- }
- }
|