| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- using System.ComponentModel.DataAnnotations;
- namespace Domain.Entities.Stocks;
- /// <summary>
- /// 세계 주요국 증시지수 최신 스냅샷 (메인 세계지도 핀). Symbol UNIQUE — 심볼당 1행을 하루 1회 upsert 한다.
- /// 외부 소스(Stooq)는 전일 대비/등락률을 직접 주지 않으므로, 직전 저장 종가(PrevClose)로부터 ChangeVal·FlucRateBp 를 산출한다.
- /// 등락률은 정수 보관을 위해 %×100 basis point(Bp)로 저장 (예: -0.70% → -70). IndexDailyPrice 규약과 동일.
- /// </summary>
- public class WorldIndexSnapshot
- {
- [Key]
- public long ID { get; private set; }
- /// <summary>외부 소스(Stooq) 심볼 — 예: "^spx" (소문자 정규화 보관)</summary>
- public string Symbol { get; private set; } = default!;
- /// <summary>지수 표시명 — 예: "S&P 500"</summary>
- public string Name { get; private set; } = default!;
- /// <summary>국가 코드 (ISO2, 대문자) — 프론트 좌표맵 키</summary>
- public string CountryCode { get; private set; } = default!;
- /// <summary>거래소·시장 표기 — 예: "NYSE·Nasdaq"</summary>
- public string ExchangeName { get; private set; } = default!;
- /// <summary>거래일 (소스 최신 종가 기준일)</summary>
- public DateOnly TradeDate { get; private set; }
- /// <summary>종가</summary>
- public decimal Close { get; private set; }
- /// <summary>직전 거래일 종가 — ChangeVal 산출 기준 (첫 수집 시 Close 와 동일 → 등락 0)</summary>
- public decimal PrevClose { get; private set; }
- /// <summary>전일 대비 (Close - PrevClose)</summary>
- public decimal ChangeVal { get; private set; }
- /// <summary>등락률 basis point = %×100</summary>
- public int FlucRateBp { get; private set; }
- /// <summary>시가</summary>
- public decimal Open { get; private set; }
- /// <summary>고가</summary>
- public decimal High { get; private set; }
- /// <summary>저가</summary>
- public decimal Low { get; private set; }
- /// <summary>최근 종가 스파크라인 — JSON decimal 배열(과거→최신, 예: "[7250.1,7260.3,...]"). 수집 소스 시계열에서 생성, 없으면 null.</summary>
- public string? Spark { get; private set; }
- public DateTime UpdatedAt { get; private set; } = DateTime.UtcNow;
- private WorldIndexSnapshot() { }
- public static WorldIndexSnapshot Create(
- string symbol,
- string name,
- string countryCode,
- string exchangeName,
- DateOnly tradeDate,
- decimal close,
- decimal open,
- decimal high,
- decimal low
- ) {
- if (string.IsNullOrWhiteSpace(symbol))
- {
- throw new ArgumentException("symbol required", nameof(symbol));
- }
- if (string.IsNullOrWhiteSpace(countryCode))
- {
- throw new ArgumentException("countryCode required", nameof(countryCode));
- }
- return new WorldIndexSnapshot
- {
- Symbol = symbol.Trim().ToLowerInvariant(),
- Name = name.Trim(),
- CountryCode = countryCode.Trim().ToUpperInvariant(),
- ExchangeName = exchangeName.Trim(),
- TradeDate = tradeDate,
- Close = close,
- PrevClose = close, // 첫 수집 — 직전값 없음 → 등락 0
- ChangeVal = 0m,
- FlucRateBp = 0,
- Open = open,
- High = high,
- Low = low,
- UpdatedAt = DateTime.UtcNow
- };
- }
- /// <summary>메타(이름·국가·거래소) 최신화 — config 변경 반영</summary>
- public void UpdateMeta(string name, string countryCode, string exchangeName)
- {
- Name = name.Trim();
- CountryCode = countryCode.Trim().ToUpperInvariant();
- ExchangeName = exchangeName.Trim();
- }
- /// <summary>
- /// 새 시세 적용 (upsert). 거래일이 진행되면 직전 종가를 PrevClose 로 승계하고, 같은/이전 거래일 재수집이면 PrevClose 를 유지(재실행 안전).
- /// 어느 경우든 ChangeVal·FlucRateBp 는 (Close - PrevClose) 로 재산출한다.
- /// </summary>
- public void Apply(DateOnly tradeDate, decimal close, decimal open, decimal high, decimal low)
- {
- if (tradeDate > TradeDate)
- {
- PrevClose = Close;
- TradeDate = tradeDate;
- }
- Close = close;
- Open = open;
- High = high;
- Low = low;
- ChangeVal = Close - PrevClose;
- FlucRateBp = PrevClose != 0m ? (int)Math.Round((Close - PrevClose) / PrevClose * 10000m, MidpointRounding.AwayFromZero) : 0;
- UpdatedAt = DateTime.UtcNow;
- }
- /// <summary>스파크라인 종가 시계열(JSON) 최신화. null/빈값이면 미변경(수집 실패 시 기존 값 보존).</summary>
- public void SetSpark(string? sparkJson)
- {
- if (!string.IsNullOrWhiteSpace(sparkJson))
- {
- Spark = sparkJson;
- }
- }
- }
|