using System.ComponentModel.DataAnnotations; using Domain.Entities.Stocks.ValueObject; namespace Domain.Entities.Stocks; /// /// 증권상품(ETF/ETN/ELW) 일별매매 시세 (KRX OpenAPI etp 엔드포인트) — (EtpType, Code, TradeDate) UNIQUE. /// 세 상품을 한 테이블에 담고 EtpType 으로 구분한다. 공통 컬럼(가격/거래/시총) + 유형별 nullable 컬럼: /// - ETF/ETN 공통: Nav(ETF NAV / ETN 지표가치 IV), NetAssetTotal(순자산총액 / 지표가치총액), BaseIndexName·BaseIndexClose(기초지수명·종가) /// - ELW: Underlying·UnderlyingClose(기초자산명·종가) /// 등락률(ChangeRate)은 KrxStockParser 와 동일하게 소수 % 그대로 저장(Bp 아님). ELW 는 자체 등락률 필드가 없어 0. /// 가격은 decimal(18,2) — ETF NAV·기초지수 종가에 소수가 있어 int 대신 decimal 로 통일. /// public class EtpDailyTrade { [Key] public long ID { get; private set; } /// 증권상품 구분 (1=ETF, 2=ETN, 3=ELW) public EtpType EtpType { get; private set; } /// 단축코드 (ISU_CD) — ETF/ETN 은 6자리 숫자, ELW 는 영숫자 6자리(예: 58K001) public string Code { get; private set; } = default!; /// 종목명 (ISU_NM) public string Name { get; private set; } = default!; /// 거래일 (BAS_DD) public DateOnly TradeDate { get; private set; } /// 종가 (TDD_CLSPRC) public decimal Close { get; private set; } /// 시가 (TDD_OPNPRC) public decimal Open { get; private set; } /// 고가 (TDD_HGPRC) public decimal High { get; private set; } /// 저가 (TDD_LWPRC) public decimal Low { get; private set; } /// 전일 대비 (CMPPREVDD_PRC) public decimal ChangeAmount { get; private set; } /// 등락률 % (FLUC_RT) — ELW 는 자체 등락률이 없어 0 public decimal ChangeRate { get; private set; } /// 거래량 (ACC_TRDVOL) public long Volume { get; private set; } /// 거래대금 (ACC_TRDVAL, 원) public long TradeValue { get; private set; } /// 시가총액 (MKTCAP, 원) — 미제공 시 null public long? MarketCap { get; private set; } /// 상장증권수/상장좌수 (LIST_SHRS) — 미제공 시 null public long? ListedShares { get; private set; } /// ETF NAV / ETN 지표가치(IV, PER1SECU_INDIC_VAL). ELW 는 null public decimal? Nav { get; private set; } /// ETF 순자산총액(INVSTASST_NETASST_TOTAMT) / ETN 지표가치총액(INDIC_VAL_AMT), 원. ELW 는 null public long? NetAssetTotal { get; private set; } /// 기초지수명 (IDX_IND_NM) — ETF/ETN. ELW 는 null public string? BaseIndexName { get; private set; } /// 기초지수 종가 (OBJ_STKPRC_IDX) — ETF/ETN. 미제공("")·ELW 는 null public decimal? BaseIndexClose { get; private set; } /// ELW 기초자산명 (ULY_NM). ETF/ETN 은 null public string? Underlying { get; private set; } /// ELW 기초자산 종가 (ULY_PRC). ETF/ETN 은 null public decimal? UnderlyingClose { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; private EtpDailyTrade() { } public static EtpDailyTrade Create( EtpType etpType, string code, string name, DateOnly tradeDate, decimal close, decimal open, decimal high, decimal low, decimal changeAmount, decimal changeRate, long volume, long tradeValue, long? marketCap = null, long? listedShares = null, decimal? nav = null, long? netAssetTotal = null, string? baseIndexName = null, decimal? baseIndexClose = null, string? underlying = null, decimal? underlyingClose = null ) { if (!Enum.IsDefined(etpType)) { throw new ArgumentOutOfRangeException(nameof(etpType)); } if (string.IsNullOrWhiteSpace(code)) { throw new ArgumentException("code required", nameof(code)); } if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name required", nameof(name)); } return new EtpDailyTrade { EtpType = etpType, Code = code.Trim(), Name = name.Trim(), TradeDate = tradeDate, Close = close, Open = open, High = high, Low = low, ChangeAmount = changeAmount, ChangeRate = changeRate, Volume = volume, TradeValue = tradeValue, MarketCap = marketCap, ListedShares = listedShares, Nav = nav, NetAssetTotal = netAssetTotal, BaseIndexName = baseIndexName, BaseIndexClose = baseIndexClose, Underlying = underlying, UnderlyingClose = underlyingClose }; } /// 동일 (EtpType, Code, TradeDate) 재수집 시 값 갱신 (upsert) public void Update( string name, decimal close, decimal open, decimal high, decimal low, decimal changeAmount, decimal changeRate, long volume, long tradeValue, long? marketCap = null, long? listedShares = null, decimal? nav = null, long? netAssetTotal = null, string? baseIndexName = null, decimal? baseIndexClose = null, string? underlying = null, decimal? underlyingClose = null ) { Name = name.Trim(); Close = close; Open = open; High = high; Low = low; ChangeAmount = changeAmount; ChangeRate = changeRate; Volume = volume; TradeValue = tradeValue; MarketCap = marketCap; ListedShares = listedShares; Nav = nav; NetAssetTotal = netAssetTotal; BaseIndexName = baseIndexName; BaseIndexClose = baseIndexClose; Underlying = underlying; UnderlyingClose = underlyingClose; } }