KrxCommodityParserTests.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. using Domain.Entities.Stocks.ValueObject;
  2. using Infrastructure.StockData;
  3. namespace Application.Tests;
  4. [TestClass]
  5. public sealed class KrxCommodityParserTests
  6. {
  7. [TestMethod]
  8. public void ParseDaily_Gold_MapsIsuCodeOhlcChangeAndRate()
  9. {
  10. // KRX OpenAPI(gold_bydd_trd) 실검증 샘플 — ISU_CD/ISU_NM + 주식형 OHLC(TDD_*) + 전일대비(CMPPREVDD_PRC)·등락률(FLUC_RT). 협의가격은 금 미제공 → null
  11. const string json = """
  12. {"OutBlock_1":[
  13. {"BAS_DD":"20250102","ISU_CD":"04020000","ISU_NM":"금 99.99_1Kg","TDD_CLSPRC":"128790","CMPPREVDD_PRC":"940","FLUC_RT":"0.74","TDD_OPNPRC":"128600","TDD_HGPRC":"129060","TDD_LWPRC":"128000","ACC_TRDVOL":"219723","ACC_TRDVAL":"28313945030"}
  14. ]}
  15. """;
  16. var rows = KrxCommodityParser.ParseDaily(json, CommodityMarket.Gold);
  17. Assert.AreEqual(1, rows.Count);
  18. var c = rows[0];
  19. Assert.AreEqual(CommodityMarket.Gold, c.CommodityMarket, "시장은 호출 인자로 결정");
  20. Assert.AreEqual("04020000", c.Code, "ISU_CD");
  21. Assert.AreEqual("금 99.99_1Kg", c.Name);
  22. Assert.AreEqual(new DateOnly(2025, 1, 2), c.TradeDate);
  23. Assert.AreEqual(128790m, c.Close, "TDD_CLSPRC");
  24. Assert.AreEqual(128600m, c.Open, "TDD_OPNPRC");
  25. Assert.AreEqual(129060m, c.High, "TDD_HGPRC");
  26. Assert.AreEqual(128000m, c.Low, "TDD_LWPRC");
  27. Assert.AreEqual(940m, c.ChangeAmount, "CMPPREVDD_PRC");
  28. Assert.AreEqual(0.74m, c.ChangeRate, "FLUC_RT");
  29. Assert.IsNull(c.WeightedDiscussionPrice, "협의가격은 금 미제공 → null");
  30. Assert.AreEqual(219723L, c.Volume);
  31. Assert.AreEqual(28313945030L, c.TradeValue);
  32. }
  33. [TestMethod]
  34. public void ParseDaily_Emission_MapsFields_WithNegativeChangeAndThousandsCommas()
  35. {
  36. // KRX OpenAPI(ets_bydd_trd) 실검증 샘플 — 배출권(KAU/KCU/KOC). 음수 전일대비/등락률 + 천단위 콤마 방어
  37. const string json = """
  38. {"OutBlock_1":[
  39. {"BAS_DD":"20250102","ISU_CD":"05003240","ISU_NM":"KAU24","TDD_CLSPRC":"9,280","CMPPREVDD_PRC":"-210","FLUC_RT":"-2.21","TDD_OPNPRC":"9,300","TDD_HGPRC":"9,500","TDD_LWPRC":"9,280","ACC_TRDVOL":"859,512","ACC_TRDVAL":"8,011,657,000"}
  40. ]}
  41. """;
  42. var rows = KrxCommodityParser.ParseDaily(json, CommodityMarket.Emission);
  43. Assert.AreEqual(1, rows.Count);
  44. var c = rows[0];
  45. Assert.AreEqual(CommodityMarket.Emission, c.CommodityMarket);
  46. Assert.AreEqual("05003240", c.Code);
  47. Assert.AreEqual("KAU24", c.Name);
  48. Assert.AreEqual(9280m, c.Close, "종가 콤마 제거");
  49. Assert.AreEqual(9300m, c.Open);
  50. Assert.AreEqual(9500m, c.High);
  51. Assert.AreEqual(9280m, c.Low);
  52. Assert.AreEqual(-210m, c.ChangeAmount, "음수 전일대비");
  53. Assert.AreEqual(-2.21m, c.ChangeRate, "음수 등락률");
  54. Assert.IsNull(c.WeightedDiscussionPrice);
  55. Assert.AreEqual(859512L, c.Volume, "거래량 콤마 제거");
  56. Assert.AreEqual(8011657000L, c.TradeValue, "거래대금 콤마 제거");
  57. }
  58. [TestMethod]
  59. public void ParseDaily_Oil_UsesOilNameAsCode_WeightedPrices_NoOhlcNoChange()
  60. {
  61. // KRX OpenAPI(oil_bydd_trd) 실검증 샘플 — ISU_CD 없음, OIL_NM(유종구분)을 Code·Name 으로. 가중평균가격 경쟁→Close / 협의→WeightedDiscussionPrice. OHLC 0, 등락 null
  62. const string json = """
  63. {"OutBlock_1":[
  64. {"BAS_DD":"20250102","OIL_NM":"휘발유","WT_AVG_PRC":"1595.88","WT_DIS_AVG_PRC":"1549.24","ACC_TRDVOL":"3365180","ACC_TRDVAL":"5292762275"}
  65. ]}
  66. """;
  67. var rows = KrxCommodityParser.ParseDaily(json, CommodityMarket.Oil);
  68. Assert.AreEqual(1, rows.Count);
  69. var c = rows[0];
  70. Assert.AreEqual(CommodityMarket.Oil, c.CommodityMarket);
  71. Assert.AreEqual("휘발유", c.Code, "석유는 OIL_NM 을 코드로 사용");
  72. Assert.AreEqual("휘발유", c.Name);
  73. Assert.AreEqual(new DateOnly(2025, 1, 2), c.TradeDate);
  74. Assert.AreEqual(1595.88m, c.Close, "가중평균가격_경쟁(WT_AVG_PRC) → Close");
  75. Assert.AreEqual(1549.24m, c.WeightedDiscussionPrice, "가중평균가격_협의(WT_DIS_AVG_PRC)");
  76. Assert.AreEqual(0m, c.Open, "석유 OHLC 미제공 → 0");
  77. Assert.AreEqual(0m, c.High);
  78. Assert.AreEqual(0m, c.Low);
  79. Assert.IsNull(c.ChangeAmount, "석유 전일대비 미제공 → null");
  80. Assert.IsNull(c.ChangeRate, "석유 등락률 미제공 → null");
  81. Assert.AreEqual(3365180L, c.Volume);
  82. Assert.AreEqual(5292762275L, c.TradeValue);
  83. }
  84. [TestMethod]
  85. public void ParseDaily_NullOrDashValues_AreHandled()
  86. {
  87. // 미반영 행 — 금/배출권의 전일대비/등락률이 ""/"-" → null (0 과 구분), 가격은 "-" → 0.
  88. const string goldJson = """
  89. {"OutBlock_1":[
  90. {"BAS_DD":"20250102","ISU_CD":"04020100","ISU_NM":"미니금 99.99_100g","TDD_CLSPRC":"-","CMPPREVDD_PRC":"","FLUC_RT":"-","TDD_OPNPRC":"-","TDD_HGPRC":"-","TDD_LWPRC":"-","ACC_TRDVOL":"0","ACC_TRDVAL":"0"}
  91. ]}
  92. """;
  93. var goldRows = KrxCommodityParser.ParseDaily(goldJson, CommodityMarket.Gold);
  94. Assert.AreEqual(1, goldRows.Count);
  95. Assert.AreEqual(0m, goldRows[0].Close, "\"-\" 가격은 0");
  96. Assert.IsNull(goldRows[0].ChangeAmount, "\"\" 전일대비는 null");
  97. Assert.IsNull(goldRows[0].ChangeRate, "\"-\" 등락률은 null");
  98. // 석유 — 협의가격 미체결("") → null
  99. const string oilJson = """
  100. {"OutBlock_1":[
  101. {"BAS_DD":"20250102","OIL_NM":"등유","WT_AVG_PRC":"1000.00","WT_DIS_AVG_PRC":"","ACC_TRDVOL":"3963954","ACC_TRDVAL":"3973954000"}
  102. ]}
  103. """;
  104. var oilRows = KrxCommodityParser.ParseDaily(oilJson, CommodityMarket.Oil);
  105. Assert.AreEqual(1, oilRows.Count);
  106. Assert.AreEqual(1000.00m, oilRows[0].Close);
  107. Assert.IsNull(oilRows[0].WeightedDiscussionPrice, "\"\" 협의가격은 null");
  108. }
  109. [TestMethod]
  110. public void ParseDaily_EmptyOrMissingBlock_ReturnsEmpty()
  111. {
  112. // basDd 미반영 — OutBlock_1 이 빈 배열
  113. Assert.AreEqual(0, KrxCommodityParser.ParseDaily("""{"OutBlock_1":[]}""", CommodityMarket.Gold).Count);
  114. // 인증 실패 등 OutBlock_1 노드 자체가 없는 응답도 안전하게 0건
  115. Assert.AreEqual(0, KrxCommodityParser.ParseDaily("""{"respMsg":"Unauthorized API Call","respCode":"401"}""", CommodityMarket.Oil).Count);
  116. }
  117. }