| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- using System.Text.Json;
- namespace Infrastructure.StockData;
- /// <summary>
- /// Yahoo Finance v8 chart JSON 파서 — 순수 C#(System.Text.Json). 지수·종목 공용.
- /// chart.result[0].meta 에서 종가(regularMarketPrice)·고저(regularMarketDayHigh/Low)·거래량(regularMarketVolume)·
- /// 거래시각(regularMarketTime, gmtoffset)을 뽑는다. Open 은 meta 에 없어 종가로 대체(화면 미표시).
- /// 전일 대비는 스냅샷 엔티티가 직전 저장 종가(PrevClose)로 산출하므로 여기서 다루지 않는다(Stooq 파서와 동일 계약).
- /// </summary>
- public static class YahooChartParser
- {
- /// <summary>시세 1행 (외부 소스 최신 종가 기준)</summary>
- public sealed record YahooQuote(
- string Symbol,
- DateOnly TradeDate,
- decimal Open,
- decimal High,
- decimal Low,
- decimal Close,
- long Volume
- );
- /// <summary>파싱 실패(빈 응답·result 없음·종가 없음·시각 없음)는 null. requestedSymbol 은 meta.symbol 결측 시 폴백.</summary>
- public static YahooQuote? Parse(string? json, string requestedSymbol)
- {
- if (string.IsNullOrWhiteSpace(json))
- {
- return null;
- }
- try
- {
- using var doc = JsonDocument.Parse(json);
- if (!doc.RootElement.TryGetProperty("chart", out var chart) || !chart.TryGetProperty("result", out var result))
- {
- return null;
- }
- if (result.ValueKind != JsonValueKind.Array || result.GetArrayLength() == 0)
- {
- return null;
- }
- if (!result[0].TryGetProperty("meta", out var meta))
- {
- return null;
- }
- if (!TryDecimal(meta, "regularMarketPrice", out var close))
- {
- return null;
- }
- if (!TryInt64(meta, "regularMarketTime", out var epoch) || epoch <= 0)
- {
- return null;
- }
- var high = TryDecimal(meta, "regularMarketDayHigh", out var h) ? h : close;
- var low = TryDecimal(meta, "regularMarketDayLow", out var l) ? l : close;
- var volume = TryInt64(meta, "regularMarketVolume", out var v) ? v : 0L;
- var gmt = TryInt64(meta, "gmtoffset", out var g) ? g : 0L;
- var open = close; // meta 에 시가 없음 — 화면 미표시라 종가로 대체
- var tradeDate = DateOnly.FromDateTime(DateTimeOffset.FromUnixTimeSeconds(epoch).ToOffset(TimeSpan.FromSeconds(gmt)).DateTime);
- var symbol = meta.TryGetProperty("symbol", out var s) && s.ValueKind == JsonValueKind.String
- ? s.GetString()!
- : requestedSymbol;
- return new YahooQuote(symbol.Trim().ToLowerInvariant(), tradeDate, open, high, low, close, volume);
- }
- catch (JsonException)
- {
- return null;
- }
- }
- private static bool TryDecimal(JsonElement obj, string name, out decimal value)
- {
- value = 0m;
- return obj.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.Number && e.TryGetDecimal(out value);
- }
- private static bool TryInt64(JsonElement obj, string name, out long value)
- {
- value = 0L;
- return obj.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.Number && e.TryGetInt64(out value);
- }
- }
|