PaperOrder.cs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. using System.ComponentModel.DataAnnotations;
  2. using System.ComponentModel.DataAnnotations.Schema;
  3. using Domain.Entities.Paper.ValueObject;
  4. namespace Domain.Entities.Paper;
  5. /// <summary>
  6. /// 모의투자 주문 — 접수 시 서버가 FillRule/TargetDate/CancelableUntil 을 확정한다 (d4 §③ 체결 규칙).
  7. /// 실제 기표는 TargetDate 시세가 도착하는 배치(M2)에서 수행 (Pending → Filled/Rejected).
  8. /// </summary>
  9. public class PaperOrder
  10. {
  11. [ForeignKey(nameof(AccountID))]
  12. public virtual PaperAccount Account { get; private set; } = null!;
  13. [Key]
  14. public int ID { get; private set; }
  15. public int AccountID { get; private set; }
  16. /// <summary>단축코드 (char 6)</summary>
  17. public string StockCode { get; private set; } = default!;
  18. public PaperOrderSide Side { get; private set; }
  19. public PaperFillRule FillRule { get; private set; }
  20. public int Quantity { get; private set; }
  21. /// <summary>매수 예약금 (매도는 0)</summary>
  22. public decimal ReservedAmount { get; private set; }
  23. /// <summary>체결 기준일</summary>
  24. public DateOnly TargetDate { get; private set; }
  25. /// <summary>취소 가능 시한 — 이후 취소 불가 (look-ahead 어뷰징 차단)</summary>
  26. public DateTime CancelableUntil { get; private set; }
  27. public PaperOrderStatus Status { get; private set; }
  28. /// <summary>거부 사유 (Rejected 시)</summary>
  29. public string? RejectReason { get; private set; }
  30. public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
  31. private PaperOrder() { }
  32. public static PaperOrder Create(
  33. int accountID,
  34. string stockCode,
  35. PaperOrderSide side,
  36. PaperFillRule fillRule,
  37. int quantity,
  38. decimal reservedAmount,
  39. DateOnly targetDate,
  40. DateTime cancelableUntil
  41. ) {
  42. if (accountID <= 0)
  43. {
  44. throw new ArgumentOutOfRangeException(nameof(accountID));
  45. }
  46. if (stockCode is not { Length: 6 })
  47. {
  48. throw new ArgumentException("stockCode must be 6 chars", nameof(stockCode));
  49. }
  50. if (quantity <= 0)
  51. {
  52. throw new ArgumentOutOfRangeException(nameof(quantity));
  53. }
  54. if (reservedAmount < 0)
  55. {
  56. throw new ArgumentOutOfRangeException(nameof(reservedAmount));
  57. }
  58. return new PaperOrder
  59. {
  60. AccountID = accountID,
  61. StockCode = stockCode,
  62. Side = side,
  63. FillRule = fillRule,
  64. Quantity = quantity,
  65. ReservedAmount = reservedAmount,
  66. TargetDate = targetDate,
  67. CancelableUntil = cancelableUntil,
  68. Status = PaperOrderStatus.Pending
  69. };
  70. }
  71. /// <summary>취소 — Pending 만 취소 가능. 성공 시 true. 시한 검증은 호출자.</summary>
  72. public bool Cancel()
  73. {
  74. if (Status != PaperOrderStatus.Pending)
  75. {
  76. return false;
  77. }
  78. Status = PaperOrderStatus.Cancelled;
  79. return true;
  80. }
  81. /// <summary>체결 확정 (배치 M2).</summary>
  82. public void MarkFilled()
  83. {
  84. Status = PaperOrderStatus.Filled;
  85. }
  86. /// <summary>거부 처리 (배치 M2).</summary>
  87. public void MarkRejected(string reason)
  88. {
  89. Status = PaperOrderStatus.Rejected;
  90. RejectReason = reason;
  91. }
  92. }