| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Domain.Entities.Stocks.ValueObject;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Features.Api.Stocks.GetExchangeRates;
- /// <summary>
- /// 환율 목록 — Yahoo Finance 수집분(MarketQuoteSnapshot, Category=Fx·GroupCode="macro")에서 통화별 최신 스냅샷을 반환 (익명).
- /// koreaexim AP01 은 봇차단 WAF(TLS 리셋+쿠키챌린지)로 이 서버망에서 수집 불가 → 이미 수집 중인 Yahoo 매크로 시세로 소스 전환 (2026-07-14).
- /// Yahoo 는 단일가(regularMarketPrice)만 제공하므로 DealBasRate=Close, Ttb/Tts=null. USDKRW=X 가 KRW=X 로 정규화돼 중복 저장되는
- /// 케이스는 통화코드 파생 후 통화별 최신 TradeDate 로 dedup 한다. Date 파라미터는 스냅샷 특성상 무시(항상 최신)한다.
- /// </summary>
- internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
- {
- public async Task<Response> Handle(Query request, CancellationToken ct)
- {
- var rows = await db.MarketQuoteSnapshot.AsNoTracking()
- .Where(c => c.Category == QuoteCategory.Fx && c.GroupCode == "macro")
- .Select(c => new { c.Symbol, c.Name, c.Close, c.TradeDate })
- .ToListAsync(ct);
- if (rows.Count == 0)
- {
- return new Response
- {
- Total = 0,
- TradeDate = null,
- List = []
- };
- }
- // 통화코드 파생 + 통화별 최신 TradeDate 로 dedup (USDKRW=X ↔ KRW=X 정규화 중복 방어)
- var list = rows
- .GroupBy(c => ToCurrencyUnit(c.Symbol))
- .Select(g => g.OrderByDescending(x => x.TradeDate).First())
- .OrderBy(c => ToCurrencyUnit(c.Symbol))
- .Select(c => new Response.Row
- {
- CurrencyUnit = ToCurrencyUnit(c.Symbol),
- CurrencyName = c.Name,
- DealBasRate = c.Close,
- Ttb = null,
- Tts = null
- })
- .ToList();
- return new Response
- {
- Total = list.Count,
- TradeDate = rows.Max(c => c.TradeDate),
- List = list
- };
- }
- /// <summary>Yahoo FX 심볼 → 통화코드. USDKRW=X 는 Yahoo 가 KRW=X 로 정규화하므로 둘 다 USD 로 매핑.</summary>
- private static string ToCurrencyUnit(string symbol)
- {
- var s = symbol.Trim().ToUpperInvariant();
- if (s is "KRW=X" or "USDKRW=X")
- {
- return "USD";
- }
- var idx = s.IndexOf("KRW=X", StringComparison.Ordinal);
- return idx > 0 ? s[..idx] : s.Replace("=X", "");
- }
- }
|