Handler.cs 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Stocks.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. namespace Application.Features.Api.Stocks.GetExchangeRates;
  6. /// <summary>
  7. /// 환율 목록 — Yahoo Finance 수집분(MarketQuoteSnapshot, Category=Fx·GroupCode="macro")에서 통화별 최신 스냅샷을 반환 (익명).
  8. /// koreaexim AP01 은 봇차단 WAF(TLS 리셋+쿠키챌린지)로 이 서버망에서 수집 불가 → 이미 수집 중인 Yahoo 매크로 시세로 소스 전환 (2026-07-14).
  9. /// Yahoo 는 단일가(regularMarketPrice)만 제공하므로 DealBasRate=Close, Ttb/Tts=null. USDKRW=X 가 KRW=X 로 정규화돼 중복 저장되는
  10. /// 케이스는 통화코드 파생 후 통화별 최신 TradeDate 로 dedup 한다. Date 파라미터는 스냅샷 특성상 무시(항상 최신)한다.
  11. /// </summary>
  12. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  13. {
  14. public async Task<Response> Handle(Query request, CancellationToken ct)
  15. {
  16. var rows = await db.MarketQuoteSnapshot.AsNoTracking()
  17. .Where(c => c.Category == QuoteCategory.Fx && c.GroupCode == "macro")
  18. .Select(c => new { c.Symbol, c.Name, c.Close, c.TradeDate })
  19. .ToListAsync(ct);
  20. if (rows.Count == 0)
  21. {
  22. return new Response
  23. {
  24. Total = 0,
  25. TradeDate = null,
  26. List = []
  27. };
  28. }
  29. // 통화코드 파생 + 통화별 최신 TradeDate 로 dedup (USDKRW=X ↔ KRW=X 정규화 중복 방어)
  30. var list = rows
  31. .GroupBy(c => ToCurrencyUnit(c.Symbol))
  32. .Select(g => g.OrderByDescending(x => x.TradeDate).First())
  33. .OrderBy(c => ToCurrencyUnit(c.Symbol))
  34. .Select(c => new Response.Row
  35. {
  36. CurrencyUnit = ToCurrencyUnit(c.Symbol),
  37. CurrencyName = c.Name,
  38. DealBasRate = c.Close,
  39. Ttb = null,
  40. Tts = null
  41. })
  42. .ToList();
  43. return new Response
  44. {
  45. Total = list.Count,
  46. TradeDate = rows.Max(c => c.TradeDate),
  47. List = list
  48. };
  49. }
  50. /// <summary>Yahoo FX 심볼 → 통화코드. USDKRW=X 는 Yahoo 가 KRW=X 로 정규화하므로 둘 다 USD 로 매핑.</summary>
  51. private static string ToCurrencyUnit(string symbol)
  52. {
  53. var s = symbol.Trim().ToUpperInvariant();
  54. if (s is "KRW=X" or "USDKRW=X")
  55. {
  56. return "USD";
  57. }
  58. var idx = s.IndexOf("KRW=X", StringComparison.Ordinal);
  59. return idx > 0 ? s[..idx] : s.Replace("=X", "");
  60. }
  61. }