| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- using System.Globalization;
- using System.Text.Json;
- namespace Infrastructure.StockData;
- /// <summary>
- /// KRX OpenAPI(data-dbg.krx.co.kr) 채권지수 일별시세(idx/bon_dd_trd) 응답 파서 — 순수 C# (System.Text.Json).
- /// 응답 골격: { "OutBlock_1": [ { BAS_DD, BND_IDX_GRP_NM, TOT_EARNG_IDX(+_CMPPREVDD), NETPRC_IDX(+..), ZERO_REINVST_IDX(+..), CALL_REINVST_IDX(+..), MKT_PRC_IDX(+..), AVG_DURATION, AVG_CONVEXITY_PRC, BND_IDX_AVG_YD }, ... ] }.
- /// 모든 값은 문자열이며 천단위 콤마 포함 가능, 결측치는 "" 또는 "-" 로 온다(일부 지수 그룹은 제로/Call재투자·시장가격 미제공). 결측은 null 로 흡수한다.
- /// 채권지수는 OHLC/거래량이 없어 IndexDailyPrice 로 재사용 불가 → BondIndexDailyPrice 전용 파서.
- /// </summary>
- public static class KrxBondIndexParser
- {
- /// <summary>채권지수 일별시세 1행 (모든 지수값은 결측 시 null)</summary>
- public sealed record BondIndexRow(
- string GroupName,
- DateOnly TradeDate,
- decimal? TotalEarningIndex,
- decimal? TotalEarningChange,
- decimal? NetPriceIndex,
- decimal? NetPriceChange,
- decimal? ZeroReinvestIndex,
- decimal? ZeroReinvestChange,
- decimal? CallReinvestIndex,
- decimal? CallReinvestChange,
- decimal? MarketPriceIndex,
- decimal? MarketPriceChange,
- decimal? AvgDuration,
- decimal? AvgConvexity,
- decimal? AvgYield
- );
- public static IReadOnlyList<BondIndexRow> ParseBondIndexPrices(string json)
- {
- using var doc = JsonDocument.Parse(json);
- var rows = new List<BondIndexRow>();
- if (!doc.RootElement.TryGetProperty("OutBlock_1", out var block) || block.ValueKind != JsonValueKind.Array)
- {
- return rows;
- }
- foreach (var item in block.EnumerateArray())
- {
- var groupName = GetString(item, "BND_IDX_GRP_NM");
- var tradeDate = ParseDate(GetString(item, "BAS_DD"));
- if (groupName.Length == 0 || tradeDate is null)
- {
- continue;
- }
- rows.Add(new BondIndexRow(
- groupName,
- tradeDate.Value,
- ParseNullableDecimal(GetString(item, "TOT_EARNG_IDX")),
- ParseNullableDecimal(GetString(item, "TOT_EARNG_IDX_CMPPREVDD")),
- ParseNullableDecimal(GetString(item, "NETPRC_IDX")),
- ParseNullableDecimal(GetString(item, "NETPRC_IDX_CMPPREVDD")),
- ParseNullableDecimal(GetString(item, "ZERO_REINVST_IDX")),
- ParseNullableDecimal(GetString(item, "ZERO_REINVST_IDX_CMPPREVDD")),
- ParseNullableDecimal(GetString(item, "CALL_REINVST_IDX")),
- ParseNullableDecimal(GetString(item, "CALL_REINVST_IDX_CMPPREVDD")),
- ParseNullableDecimal(GetString(item, "MKT_PRC_IDX")),
- ParseNullableDecimal(GetString(item, "MKT_PRC_IDX_CMPPREVDD")),
- ParseNullableDecimal(GetString(item, "AVG_DURATION")),
- ParseNullableDecimal(GetString(item, "AVG_CONVEXITY_PRC")),
- ParseNullableDecimal(GetString(item, "BND_IDX_AVG_YD"))
- ));
- }
- 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>지수값/전일대비/듀레이션·컨벡시티·YTM — "" 나 "-" 등 파싱 실패는 null</summary>
- private static decimal? ParseNullableDecimal(string raw)
- {
- return decimal.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value) ? value : null;
- }
- }
|