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;
}
}