| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- using System.Globalization;
- using System.Text.Json;
- namespace Infrastructure.StockData;
- /// <summary>
- /// KRX OpenAPI(data-dbg.krx.co.kr) ESG 지수(esg/esg_index_info) 응답 파서 — 순수 C# (System.Text.Json).
- /// 응답 골격: { "OutBlock_1": [ { BAS_DD, IDX_NM, CLSPRC_IDX, PRV_DD_CMPR, UPDN_RATE, TRD_ISU_CNT, ACC_TRDVOL, ACC_TRDVAL }, ... ] }.
- /// 모든 값은 문자열이며 천단위 콤마 포함 가능. 시장/파생 지수와 달리 OHLC·시가총액이 없고 구성종목수(TRD_ISU_CNT)를 제공한다.
- /// 등락률(UPDN_RATE, %)은 basis point(%×100, 반올림)로 저장한다 (IndexDailyPrice.FlucRateBp 와 동일 규약, "-.39"/".16" 형태도 흡수).
- /// </summary>
- public static class KrxEsgIndexParser
- {
- /// <summary>ESG 지수 1행 (등락률은 Bp = %×100)</summary>
- public sealed record EsgIndexRow(
- string IndexName,
- DateOnly TradeDate,
- decimal Close,
- decimal ChangeVal,
- int FlucRateBp,
- int ConstituentCount,
- long Volume,
- long Value
- );
- public static IReadOnlyList<EsgIndexRow> ParseDaily(string json)
- {
- using var doc = JsonDocument.Parse(json);
- var rows = new List<EsgIndexRow>();
- if (!doc.RootElement.TryGetProperty("OutBlock_1", out var block) || block.ValueKind != JsonValueKind.Array)
- {
- return rows;
- }
- foreach (var item in block.EnumerateArray())
- {
- var name = GetString(item, "IDX_NM");
- var tradeDate = ParseDate(GetString(item, "BAS_DD"));
- if (name.Length == 0 || tradeDate is null)
- {
- continue;
- }
- rows.Add(new EsgIndexRow(
- name,
- tradeDate.Value,
- ParseDecimal(GetString(item, "CLSPRC_IDX")),
- ParseDecimal(GetString(item, "PRV_DD_CMPR")),
- ParseRateToBp(GetString(item, "UPDN_RATE")),
- ParseInt(GetString(item, "TRD_ISU_CNT")),
- ParseLong(GetString(item, "ACC_TRDVOL")),
- ParseLong(GetString(item, "ACC_TRDVAL"))
- ));
- }
- return rows;
- }
- private static string GetString(JsonElement element, string name)
- {
- if (!element.TryGetProperty(name, out var value))
- {
- return string.Empty;
- }
- return value.ValueKind switch
- {
- JsonValueKind.String => value.GetString()?.Trim() ?? string.Empty,
- JsonValueKind.Number => value.GetRawText(),
- _ => string.Empty
- };
- }
- private static DateOnly? ParseDate(string raw)
- {
- return DateOnly.TryParseExact(raw, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) ? date : null;
- }
- /// <summary>등락률(%) 문자열 → basis point (×100, 반올림). "-" 나 파싱 실패는 0.</summary>
- private static int ParseRateToBp(string raw)
- {
- return decimal.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var pct)
- ? (int)Math.Round(pct * 100m, MidpointRounding.AwayFromZero)
- : 0;
- }
- private static int ParseInt(string raw)
- {
- return int.TryParse(raw, NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value) ? value : 0;
- }
- private static long ParseLong(string raw)
- {
- return long.TryParse(raw, NumberStyles.Integer | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value) ? value : 0L;
- }
- private static decimal ParseDecimal(string raw)
- {
- return decimal.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value) ? value : 0m;
- }
- }
|