using System.ComponentModel.DataAnnotations; using Domain.Entities.Stocks.ValueObject; namespace Domain.Entities.Stocks; /// /// 일반상품(석유·금·배출권) 일별매매 시세 (KRX OpenAPI gen 엔드포인트) — (CommodityMarket, Code, TradeDate) UNIQUE. /// 세 시장을 한 테이블에 담고 CommodityMarket 으로 구분한다. 시장별 응답 shape 이 다르다: /// - 금(Gold)/배출권(Emission): ISU_CD/ISU_NM + 주식형 OHLC(TDD_CLSPRC/OPNPRC/HGPRC/LWPRC) + 전일대비(CMPPREVDD_PRC)·등락률(FLUC_RT). /// - 석유(Oil): ISU_CD 없음 → OIL_NM(유종구분)을 Code·Name 으로 사용. OHLC 미제공(Open/High/Low=0), 전일대비·등락률 없음 → null. /// 가격은 가중평균가격_경쟁(WT_AVG_PRC)을 Close 로, 가중평균가격_협의(WT_DIS_AVG_PRC)를 WeightedDiscussionPrice(석유 전용 nullable)로 담는다. /// 공통 필드: Code/Name, TradeDate, Close/Open/High/Low(decimal 18,2), Volume/TradeValue(long). 등락은 금/배출권만, 협의가격은 석유만. /// public class CommodityDailyTrade { [Key] public long ID { get; private set; } /// 일반상품 시장 구분 (1=석유, 2=금, 3=배출권) public CommodityMarket CommodityMarket { get; private set; } /// 단축코드 (ISU_CD) — 금/배출권. 석유는 OIL_NM(유종구분)을 코드로 사용 public string Code { get; private set; } = default!; /// 종목명 (ISU_NM) — 석유는 OIL_NM(유종구분) public string Name { get; private set; } = default!; /// 거래일 (BAS_DD) public DateOnly TradeDate { get; private set; } /// 종가 (TDD_CLSPRC) — 석유는 가중평균가격_경쟁 (WT_AVG_PRC) public decimal Close { get; private set; } /// 시가 (TDD_OPNPRC) — 석유 미제공 시 0 public decimal Open { get; private set; } /// 고가 (TDD_HGPRC) — 석유 미제공 시 0 public decimal High { get; private set; } /// 저가 (TDD_LWPRC) — 석유 미제공 시 0 public decimal Low { get; private set; } /// 전일 대비 (CMPPREVDD_PRC) — 금/배출권만. 석유는 미제공 → null public decimal? ChangeAmount { get; private set; } /// 등락률 % (FLUC_RT) — 금/배출권만. 석유는 미제공 → null public decimal? ChangeRate { get; private set; } /// 가중평균가격_협의 (WT_DIS_AVG_PRC) — 석유만. 금/배출권은 null public decimal? WeightedDiscussionPrice { get; private set; } /// 거래량 (ACC_TRDVOL) public long Volume { get; private set; } /// 거래대금 (ACC_TRDVAL, 원) public long TradeValue { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; private CommodityDailyTrade() { } public static CommodityDailyTrade Create( CommodityMarket commodityMarket, string code, string name, DateOnly tradeDate, decimal close, decimal open, decimal high, decimal low, decimal? changeAmount, decimal? changeRate, decimal? weightedDiscussionPrice, long volume, long tradeValue ) { if (!Enum.IsDefined(commodityMarket)) { throw new ArgumentOutOfRangeException(nameof(commodityMarket)); } if (string.IsNullOrWhiteSpace(code)) { throw new ArgumentException("code required", nameof(code)); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name required", nameof(name)); } return new CommodityDailyTrade { CommodityMarket = commodityMarket, Code = code.Trim(), Name = name.Trim(), TradeDate = tradeDate, Close = close, Open = open, High = high, Low = low, ChangeAmount = changeAmount, ChangeRate = changeRate, WeightedDiscussionPrice = weightedDiscussionPrice, Volume = volume, TradeValue = tradeValue }; } /// 동일 (CommodityMarket, Code, TradeDate) 재수집 시 값 갱신 (upsert) public void Update( string name, decimal close, decimal open, decimal high, decimal low, decimal? changeAmount, decimal? changeRate, decimal? weightedDiscussionPrice, long volume, long tradeValue ) { Name = name.Trim(); Close = close; Open = open; High = high; Low = low; ChangeAmount = changeAmount; ChangeRate = changeRate; WeightedDiscussionPrice = weightedDiscussionPrice; Volume = volume; TradeValue = tradeValue; } }