FuturesDailyTrade.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. using System.ComponentModel.DataAnnotations;
  2. using Domain.Entities.Stocks.ValueObject;
  3. namespace Domain.Entities.Stocks;
  4. /// <summary>
  5. /// 선물(일반선물/주식선물유가/주식선물코스닥) 일별매매 시세 (KRX OpenAPI drv 엔드포인트) — (FuturesKind, Code, TradeDate, Session) UNIQUE.
  6. /// 세 상품군을 한 테이블에 담고 FuturesKind 로 구분한다. 선물은 옵션과 달리 정산가(SETL_PRC)·현물가(SPOT_PRC)·미결제약정(ACC_OPNINT_QTY)을 갖는다:
  7. /// - 가격/전일대비: TDD_CLSPRC/OPNPRC/HGPRC/LWPRC + CMPPREVDD_PRC (주식과 동일). 미체결 시 종가/시고저는 "" → 0.
  8. /// - 선물 고유: ProductName(PROD_NM 상품구분 예: "10년국채 선물"), MarketName(MKT_NM 정규/야간), SpotPrice(SPOT_PRC 현물가, nullable),
  9. /// SettlePrice(SETL_PRC 정산가, nullable — 스프레드 종목은 미제공), OpenInterest(ACC_OPNINT_QTY 미결제약정, nullable).
  10. /// 일반선물(fut_bydd_trd)은 동일 ISU_CD 로 정규·야간 세션 행을 각각 반환하므로(라이브 재현: 385행 = 정규 303 + 야간 82)
  11. /// Session(MKT_NM 파싱)을 UQ 에 포함해 두 세션을 각각 저장한다 — 야간 행이 정규 종가·거래량을 덮어쓰던 결함 수정.
  12. /// 종목코드(ISU_CD)는 8자리 영숫자(예: 167W3000) — 6자리 정규화 없이 원문 유지. 가격은 decimal(18,2).
  13. /// </summary>
  14. public class FuturesDailyTrade
  15. {
  16. [Key]
  17. public long ID { get; private set; }
  18. /// <summary>선물 상품군 구분 (1=일반선물, 2=주식선물유가, 3=주식선물코스닥)</summary>
  19. public FuturesKind FuturesKind { get; private set; }
  20. /// <summary>종목코드 (ISU_CD) — 8자리 영숫자(예: 167W3000)</summary>
  21. public string IsuCode { get; private set; } = default!;
  22. /// <summary>종목명 (ISU_NM)</summary>
  23. public string IsuName { get; private set; } = default!;
  24. /// <summary>거래일 (BAS_DD)</summary>
  25. public DateOnly TradeDate { get; private set; }
  26. /// <summary>매매 세션 (1=정규, 2=야간) — MKT_NM 파싱. 동일 종목코드가 세션별 별도 행</summary>
  27. public MarketSession Session { get; private set; } = MarketSession.Regular;
  28. /// <summary>종가 (TDD_CLSPRC) — 미체결 시 0</summary>
  29. public decimal Close { get; private set; }
  30. /// <summary>시가 (TDD_OPNPRC) — 미체결 시 0</summary>
  31. public decimal Open { get; private set; }
  32. /// <summary>고가 (TDD_HGPRC) — 미체결 시 0</summary>
  33. public decimal High { get; private set; }
  34. /// <summary>저가 (TDD_LWPRC) — 미체결 시 0</summary>
  35. public decimal Low { get; private set; }
  36. /// <summary>전일 대비 (CMPPREVDD_PRC) — 미제공("") 시 0</summary>
  37. public decimal ChangeAmount { get; private set; }
  38. /// <summary>현물가 (SPOT_PRC) — 미제공 시 null</summary>
  39. public decimal? SpotPrice { get; private set; }
  40. /// <summary>정산가 (SETL_PRC) — 스프레드 종목 등 미제공 시 null</summary>
  41. public decimal? SettlePrice { get; private set; }
  42. /// <summary>거래량 (ACC_TRDVOL)</summary>
  43. public long Volume { get; private set; }
  44. /// <summary>거래대금 (ACC_TRDVAL, 원)</summary>
  45. public long TradeValue { get; private set; }
  46. /// <summary>미결제약정 (ACC_OPNINT_QTY) — 미제공("") 시 null</summary>
  47. public long? OpenInterest { get; private set; }
  48. /// <summary>상품구분 (PROD_NM 예: "10년국채 선물")</summary>
  49. public string? ProductName { get; private set; }
  50. /// <summary>시장구분 (MKT_NM 정규/야간)</summary>
  51. public string? MarketName { get; private set; }
  52. public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
  53. private FuturesDailyTrade() { }
  54. public static FuturesDailyTrade Create(
  55. FuturesKind futuresKind,
  56. string isuCode,
  57. string isuName,
  58. DateOnly tradeDate,
  59. MarketSession session,
  60. decimal close,
  61. decimal open,
  62. decimal high,
  63. decimal low,
  64. decimal changeAmount,
  65. decimal? spotPrice,
  66. decimal? settlePrice,
  67. long volume,
  68. long tradeValue,
  69. long? openInterest,
  70. string? productName = null,
  71. string? marketName = null
  72. ) {
  73. if (!Enum.IsDefined(futuresKind))
  74. {
  75. throw new ArgumentOutOfRangeException(nameof(futuresKind));
  76. }
  77. if (!Enum.IsDefined(session))
  78. {
  79. throw new ArgumentOutOfRangeException(nameof(session));
  80. }
  81. if (string.IsNullOrWhiteSpace(isuCode))
  82. {
  83. throw new ArgumentException("isuCode required", nameof(isuCode));
  84. }
  85. if (string.IsNullOrWhiteSpace(isuName))
  86. {
  87. throw new ArgumentException("isuName required", nameof(isuName));
  88. }
  89. return new FuturesDailyTrade
  90. {
  91. FuturesKind = futuresKind,
  92. IsuCode = isuCode.Trim(),
  93. IsuName = isuName.Trim(),
  94. TradeDate = tradeDate,
  95. Session = session,
  96. Close = close,
  97. Open = open,
  98. High = high,
  99. Low = low,
  100. ChangeAmount = changeAmount,
  101. SpotPrice = spotPrice,
  102. SettlePrice = settlePrice,
  103. Volume = volume,
  104. TradeValue = tradeValue,
  105. OpenInterest = openInterest,
  106. ProductName = productName,
  107. MarketName = marketName
  108. };
  109. }
  110. /// <summary>동일 (FuturesKind, IsuCode, TradeDate, Session) 재수집 시 값 갱신 (upsert) — Session 은 키의 일부라 갱신하지 않는다</summary>
  111. public void Update(
  112. string isuName,
  113. decimal close,
  114. decimal open,
  115. decimal high,
  116. decimal low,
  117. decimal changeAmount,
  118. decimal? spotPrice,
  119. decimal? settlePrice,
  120. long volume,
  121. long tradeValue,
  122. long? openInterest,
  123. string? productName = null,
  124. string? marketName = null
  125. ) {
  126. IsuName = isuName.Trim();
  127. Close = close;
  128. Open = open;
  129. High = high;
  130. Low = low;
  131. ChangeAmount = changeAmount;
  132. SpotPrice = spotPrice;
  133. SettlePrice = settlePrice;
  134. Volume = volume;
  135. TradeValue = tradeValue;
  136. OpenInterest = openInterest;
  137. ProductName = productName;
  138. MarketName = marketName;
  139. }
  140. }