| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- using Domain.Entities.Stocks.ValueObject;
- using Infrastructure.StockData;
- namespace Application.Tests;
- [TestClass]
- public sealed class KrxBondParserTests
- {
- [TestMethod]
- public void ParseDaily_KtsGovt_MapsPriceYieldAndGovBondFields()
- {
- // KRX OpenAPI(kts_bydd_trd) 실검증 샘플 — ISU_CD 12자리 ISIN, 가격 CLSPRC/OPNPRC/HGPRC/LWPRC, 수익률 *_YD, 국채전문 전용 BND_EXP_TP_NM/GOVBND_ISU_TP_NM
- const string json = """
- {"OutBlock_1":[
- {"BAS_DD":"20250102","MKT_NM":"국채전문유통시장","ISU_CD":"KR103503GE96","ISU_NM":"국고02875-2609(24-9)","BND_EXP_TP_NM":"2","GOVBND_ISU_TP_NM":"지표","CLSPRC":"10122.5","CMPPREVDD_PRC":"14.5","CLSPRC_YD":"2.681","OPNPRC":"10110.5","OPNPRC_YD":"2.754","HGPRC":"10123.0","HGPRC_YD":"2.678","LWPRC":"10110.5","LWPRC_YD":"2.754","ACC_TRDVOL":"70000000000","ACC_TRDVAL":"70834100000"}
- ]}
- """;
- var rows = KrxBondParser.ParseDaily(json, BondMarket.KtsGovt);
- Assert.AreEqual(1, rows.Count);
- var b = rows[0];
- Assert.AreEqual(BondMarket.KtsGovt, b.BondMarket, "시장은 호출 인자로 결정");
- Assert.AreEqual("KR103503GE96", b.Code, "12자리 ISIN 원문 유지 (6자리 정규화 안 함)");
- Assert.AreEqual("국고02875-2609(24-9)", b.Name);
- Assert.AreEqual(new DateOnly(2025, 1, 2), b.TradeDate);
- Assert.AreEqual(10122.5m, b.Close, "CLSPRC 소수 파싱");
- Assert.AreEqual(10110.5m, b.Open);
- Assert.AreEqual(10123.0m, b.High);
- Assert.AreEqual(10110.5m, b.Low);
- Assert.AreEqual(14.5m, b.ChangeAmount, "CMPPREVDD_PRC");
- Assert.AreEqual(2.681m, b.YieldToMaturity, "CLSPRC_YD = 만기수익률(종가수익률)");
- Assert.AreEqual(2.754m, b.OpenYield);
- Assert.AreEqual(2.678m, b.HighYield);
- Assert.AreEqual(2.754m, b.LowYield);
- Assert.AreEqual(70000000000L, b.Volume);
- Assert.AreEqual(70834100000L, b.TradeValue);
- Assert.AreEqual("2", b.MaturityYears, "국채전문 BND_EXP_TP_NM");
- Assert.AreEqual("지표", b.IssueType, "국채전문 GOVBND_ISU_TP_NM");
- }
- [TestMethod]
- public void ParseDaily_General_EmptyChangeAmount_IsZero_NoGovBondFields()
- {
- // KRX OpenAPI(bnd_bydd_trd) 실검증 샘플 — CMPPREVDD_PRC 빈값("") → 0, 국채전문 전용 필드 없음 → null
- const string json = """
- {"OutBlock_1":[
- {"BAS_DD":"20250102","MKT_NM":"일반채권시장","ISU_CD":"KR101501DAB5","ISU_NM":"국민주택1종20-11","CLSPRC":"10230.0","CMPPREVDD_PRC":"","CLSPRC_YD":"3.009","OPNPRC":"10230.0","OPNPRC_YD":"3.009","HGPRC":"10230.0","HGPRC_YD":"3.009","LWPRC":"10230.0","LWPRC_YD":"3.009","ACC_TRDVOL":"29000","ACC_TRDVAL":"29667"}
- ]}
- """;
- var rows = KrxBondParser.ParseDaily(json, BondMarket.General);
- Assert.AreEqual(1, rows.Count);
- var b = rows[0];
- Assert.AreEqual(BondMarket.General, b.BondMarket);
- Assert.AreEqual("KR101501DAB5", b.Code);
- Assert.AreEqual(10230.0m, b.Close);
- Assert.AreEqual(0m, b.ChangeAmount, "빈 CMPPREVDD_PRC 는 0");
- Assert.AreEqual(3.009m, b.YieldToMaturity);
- Assert.AreEqual(29000L, b.Volume);
- Assert.AreEqual(29667L, b.TradeValue);
- Assert.IsNull(b.MaturityYears, "일반채권은 국채전문 전용 필드 없음");
- Assert.IsNull(b.IssueType);
- }
- [TestMethod]
- public void ParseDaily_Small_MapsFields_WithThousandsCommas()
- {
- // KRX OpenAPI(smb_bydd_trd) 실검증 샘플 — 소액채권, 천단위 콤마 포함 방어
- const string json = """
- {"OutBlock_1":[
- {"BAS_DD":"20250102","MKT_NM":"소액채권시장","ISU_CD":"KR101501DEC5","ISU_NM":"국민주택1종24-12","CLSPRC":"9,253.0","CMPPREVDD_PRC":"79.0","CLSPRC_YD":"2.888","OPNPRC":"9,221.0","OPNPRC_YD":"2.959","HGPRC":"9,264.0","HGPRC_YD":"2.863","LWPRC":"9,221.0","LWPRC_YD":"2.959","ACC_TRDVOL":"117,311,623,000","ACC_TRDVAL":"108,262,446,570"}
- ]}
- """;
- var rows = KrxBondParser.ParseDaily(json, BondMarket.Small);
- Assert.AreEqual(1, rows.Count);
- var b = rows[0];
- Assert.AreEqual(BondMarket.Small, b.BondMarket);
- Assert.AreEqual("KR101501DEC5", b.Code);
- Assert.AreEqual(9253.0m, b.Close, "종가 콤마 제거");
- Assert.AreEqual(79.0m, b.ChangeAmount);
- Assert.AreEqual(2.888m, b.YieldToMaturity);
- Assert.AreEqual(117311623000L, b.Volume, "거래량 콤마 제거");
- Assert.AreEqual(108262446570L, b.TradeValue, "거래대금 콤마 제거");
- Assert.IsNull(b.MaturityYears, "소액채권은 국채전문 전용 필드 없음");
- Assert.IsNull(b.IssueType);
- }
- [TestMethod]
- public void ParseDaily_NullYields_AreHandled()
- {
- // 미체결 채권 — 수익률(*_YD) 빈값("")/"-" → null (0 과 구분). KtsGovt 전용 필드가 빈값이어도 null.
- const string json = """
- {"OutBlock_1":[
- {"BAS_DD":"20250102","MKT_NM":"국채전문유통시장","ISU_CD":"KR103502GEC4","ISU_NM":"국고03000-3412(24-13)","BND_EXP_TP_NM":"","GOVBND_ISU_TP_NM":"-","CLSPRC":"10231.0","CMPPREVDD_PRC":"-","CLSPRC_YD":"","OPNPRC":"0","OPNPRC_YD":"-","HGPRC":"0","HGPRC_YD":"","LWPRC":"0","LWPRC_YD":"","ACC_TRDVOL":"0","ACC_TRDVAL":"0"}
- ]}
- """;
- var rows = KrxBondParser.ParseDaily(json, BondMarket.KtsGovt);
- Assert.AreEqual(1, rows.Count);
- var b = rows[0];
- Assert.IsNull(b.YieldToMaturity, "\"\" 만기수익률은 null");
- Assert.IsNull(b.OpenYield, "\"-\" 시가수익률은 null");
- Assert.IsNull(b.HighYield);
- Assert.IsNull(b.LowYield);
- Assert.AreEqual(0m, b.ChangeAmount, "\"-\" 전일대비는 0");
- Assert.IsNull(b.MaturityYears, "빈 BND_EXP_TP_NM 은 null");
- Assert.IsNull(b.IssueType, "\"-\" GOVBND_ISU_TP_NM 은 null");
- }
- [TestMethod]
- public void ParseDaily_EmptyOrMissingBlock_ReturnsEmpty()
- {
- // basDd 미반영 — OutBlock_1 이 빈 배열
- Assert.AreEqual(0, KrxBondParser.ParseDaily("""{"OutBlock_1":[]}""", BondMarket.KtsGovt).Count);
- // 인증 실패 등 OutBlock_1 노드 자체가 없는 응답도 안전하게 0건
- Assert.AreEqual(0, KrxBondParser.ParseDaily("""{"respMsg":"Unauthorized API Call","respCode":"401"}""", BondMarket.General).Count);
- }
- }
|