Handler.cs 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  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.GetWorldIndices;
  6. /// <summary>
  7. /// 세계 주요국 증시지수 병합 — 국내(코스피)는 IndexDailyPrice 최신 거래일 실데이터, 해외는 WorldIndexSnapshot(나라당 1개).
  8. /// 국가 코드(ISO2)로 프론트가 세계지도 핀 좌표를 매핑한다. 해외 스냅샷에 KR 이 섞여도 중복 방지로 제외.
  9. /// </summary>
  10. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  11. {
  12. public async Task<Response> Handle(Query request, CancellationToken ct)
  13. {
  14. var rows = new List<Response.Row>();
  15. // 국내(코스피) — IndexDailyPrice 최신 거래일. 한국 핀은 KRX 실데이터 사용.
  16. var latestKr = await db.IndexDailyPrice.AsNoTracking().OrderByDescending(c => c.TradeDate).Select(c => (DateOnly?)c.TradeDate).FirstOrDefaultAsync(ct);
  17. if (latestKr is not null)
  18. {
  19. var kospi = await db.IndexDailyPrice.AsNoTracking()
  20. .Where(c => c.TradeDate == latestKr.Value && c.Series == MarketIndexSeries.KOSPI && c.IndexName == "코스피")
  21. .Select(c => new
  22. {
  23. c.Close,
  24. c.ChangeVal,
  25. c.FlucRateBp,
  26. c.TradeDate
  27. })
  28. .FirstOrDefaultAsync(ct);
  29. if (kospi is not null)
  30. {
  31. rows.Add(new Response.Row
  32. {
  33. CountryCode = "KR",
  34. Name = "코스피",
  35. ExchangeName = "KRX",
  36. Close = kospi.Close,
  37. ChangeVal = kospi.ChangeVal,
  38. FlucRateBp = kospi.FlucRateBp,
  39. TradeDate = kospi.TradeDate
  40. });
  41. }
  42. }
  43. // 해외 — WorldIndexSnapshot. KR 은 위에서 병합했으므로 제외. 한 국가에 지수가 여러 개면(예: US ^gspc/^ixic)
  44. // 지도는 나라당 1핀이므로 심볼 오름차순 첫 행만 남긴다(US → ^gspc=S&P500). 요약 카드는 별도 엔드포인트에서 심볼로 조회.
  45. var foreignRaw = await db.WorldIndexSnapshot.AsNoTracking()
  46. .Where(c => c.CountryCode != "KR")
  47. .OrderBy(c => c.CountryCode).ThenBy(c => c.Symbol)
  48. .Select(c => new
  49. {
  50. c.CountryCode,
  51. c.Name,
  52. c.ExchangeName,
  53. c.Close,
  54. c.ChangeVal,
  55. c.FlucRateBp,
  56. c.TradeDate
  57. })
  58. .ToListAsync(ct);
  59. var seenCountry = new HashSet<string>();
  60. foreach (var c in foreignRaw)
  61. {
  62. if (!seenCountry.Add(c.CountryCode))
  63. {
  64. continue;
  65. }
  66. rows.Add(new Response.Row
  67. {
  68. CountryCode = c.CountryCode,
  69. Name = c.Name,
  70. ExchangeName = c.ExchangeName,
  71. Close = c.Close,
  72. ChangeVal = c.ChangeVal,
  73. FlucRateBp = c.FlucRateBp,
  74. TradeDate = c.TradeDate
  75. });
  76. }
  77. return new Response
  78. {
  79. List = rows
  80. };
  81. }
  82. }