worldIndex.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // 세계 주요국 증시지수 도메인 타입 — Backend Application/Features/Api/Stocks/GetWorldIndices/Response.cs 대응
  2. // 직렬화 주의: 필드명 camelCase. flucRateBp = 등락률 basis point(%×100). enum 없음(전부 primitive).
  3. // ── GET /api/market/world-indices ──
  4. export interface WorldIndexRow {
  5. countryCode: string; // ISO2 대문자 — EXCHANGE_GEO 좌표맵 키
  6. name: string; // 지수명 (예: "코스피", "S&P 500")
  7. exchangeName: string;
  8. close: number;
  9. changeVal: number;
  10. flucRateBp: number; // %×100 (예: -70 = -0.70%)
  11. tradeDate: string; // yyyy-MM-dd
  12. }
  13. export interface WorldIndicesResponse {
  14. list: WorldIndexRow[];
  15. }
  16. // ── 지역(흥국 지역 필터 대응) ──
  17. // 'major'(주요국)는 지역 교차 큐레이션 탭 — 지리적 region 과 별개로 major 플래그로 표기.
  18. export type MarketRegion = 'asia'|'europe'|'america'|'mideast';
  19. export const REGION_LABELS: Record<'major'|MarketRegion, string> = {
  20. major: '주요국',
  21. asia: '아시아',
  22. america: '아메리카',
  23. europe: '유럽',
  24. mideast: '중동·아프리카'
  25. };
  26. // 지역 탭 노출 순서 (major 우선, 이후 데이터 있는 지역만 렌더)
  27. export const REGION_ORDER: ('major'|MarketRegion)[] = ['major', 'asia', 'america', 'europe', 'mideast'];
  28. // ── 국가 → 지도 좌표(위경도) + 표시명. 나라당 1개(대표 지수) 기준 ──
  29. // 프론트가 국가 코드로 세계지도 핀 위치를 정한다(백엔드는 좌표를 보내지 않음 — 응답 경량화).
  30. export interface ExchangeGeo {
  31. lat: number;
  32. lng: number;
  33. label: string; // 국가 표시명 (한글)
  34. region: MarketRegion; // 지리적 지역 탭
  35. major?: boolean; // 주요국 탭 포함 여부
  36. }
  37. // 40개국(주요국 16 + 지역별 확장). Yahoo v8 유효 심볼만 시드 — KR 은 IndexDailyPrice 병합.
  38. export const EXCHANGE_GEO: Record<string, ExchangeGeo> = {
  39. KR: { lat: 37.5, lng: 127.0, label: '한국', region: 'asia', major: true },
  40. // 아메리카
  41. US: { lat: 40.7, lng: -74.0, label: '미국', region: 'america', major: true },
  42. CA: { lat: 43.7, lng: -79.4, label: '캐나다', region: 'america', major: true },
  43. MX: { lat: 19.43, lng: -99.13, label: '멕시코', region: 'america', major: true },
  44. BR: { lat: -23.5, lng: -46.6, label: '브라질', region: 'america', major: true },
  45. AR: { lat: -34.6, lng: -58.4, label: '아르헨티나', region: 'america' },
  46. CL: { lat: -33.45, lng: -70.66, label: '칠레', region: 'america' },
  47. // 아시아
  48. JP: { lat: 35.7, lng: 139.7, label: '일본', region: 'asia', major: true },
  49. CN: { lat: 31.2, lng: 121.5, label: '중국', region: 'asia', major: true },
  50. HK: { lat: 22.3, lng: 114.2, label: '홍콩', region: 'asia', major: true },
  51. TW: { lat: 25.0, lng: 121.6, label: '대만', region: 'asia', major: true },
  52. IN: { lat: 19.1, lng: 72.9, label: '인도', region: 'asia', major: true },
  53. AU: { lat: -33.9, lng: 151.2, label: '호주', region: 'asia', major: true },
  54. SG: { lat: 1.35, lng: 103.8, label: '싱가포르', region: 'asia' },
  55. ID: { lat: -6.2, lng: 106.85, label: '인도네시아', region: 'asia' },
  56. TH: { lat: 13.75, lng: 100.52, label: '태국', region: 'asia' },
  57. MY: { lat: 3.14, lng: 101.69, label: '말레이시아', region: 'asia' },
  58. PH: { lat: 14.6, lng: 120.98, label: '필리핀', region: 'asia' },
  59. NZ: { lat: -41.29, lng: 174.78, label: '뉴질랜드', region: 'asia' },
  60. // 유럽
  61. GB: { lat: 51.5, lng: -0.1, label: '영국', region: 'europe', major: true },
  62. DE: { lat: 50.1, lng: 8.7, label: '독일', region: 'europe', major: true },
  63. FR: { lat: 48.9, lng: 2.3, label: '프랑스', region: 'europe', major: true },
  64. IT: { lat: 45.46, lng: 9.19, label: '이탈리아', region: 'europe', major: true },
  65. ES: { lat: 40.42, lng: -3.70, label: '스페인', region: 'europe' },
  66. CH: { lat: 47.37, lng: 8.54, label: '스위스', region: 'europe' },
  67. NL: { lat: 52.37, lng: 4.90, label: '네덜란드', region: 'europe' },
  68. SE: { lat: 59.33, lng: 18.07, label: '스웨덴', region: 'europe' },
  69. BE: { lat: 50.85, lng: 4.35, label: '벨기에', region: 'europe' },
  70. AT: { lat: 48.2, lng: 16.37, label: '오스트리아', region: 'europe' },
  71. DK: { lat: 55.68, lng: 12.57, label: '덴마크', region: 'europe' },
  72. FI: { lat: 60.17, lng: 24.94, label: '핀란드', region: 'europe' },
  73. PL: { lat: 52.23, lng: 21.01, label: '폴란드', region: 'europe' },
  74. PT: { lat: 38.72, lng: -9.14, label: '포르투갈', region: 'europe' },
  75. IE: { lat: 53.35, lng: -6.26, label: '아일랜드', region: 'europe' },
  76. NO: { lat: 59.91, lng: 10.75, label: '노르웨이', region: 'europe' },
  77. GR: { lat: 37.98, lng: 23.73, label: '그리스', region: 'europe' },
  78. // 중동·아프리카
  79. ZA: { lat: -26.20, lng: 28.05, label: '남아공', region: 'mideast', major: true },
  80. SA: { lat: 24.71, lng: 46.68, label: '사우디', region: 'mideast' },
  81. TR: { lat: 41.0, lng: 28.98, label: '튀르키예', region: 'mideast' },
  82. EG: { lat: 30.04, lng: 31.24, label: '이집트', region: 'mideast' }
  83. };
  84. // ── 국가 → TradingView 대표지수 심볼 (드릴다운 차트). best-effort 매핑 ──
  85. // 없는 국가(예: CA)는 차트 버튼이 지도 하이라이트로 폴백. 심볼은 운영에서 조정 가능.
  86. export const TV_SYMBOL: Record<string, string> = {
  87. US: 'SPX',
  88. KR: 'KRX:KOSPI',
  89. JP: 'TVC:NI225',
  90. CN: 'SSE:000001',
  91. HK: 'TVC:HSI',
  92. TW: 'TVC:TWII',
  93. SG: 'TVC:STI',
  94. IN: 'BSE:SENSEX',
  95. GB: 'TVC:UKX',
  96. DE: 'XETR:DAX',
  97. FR: 'EURONEXT:PX1',
  98. AU: 'ASX:XJO',
  99. BR: 'BMFBOVESPA:IBOV'
  100. };
  101. // ── 등락 상태 ──
  102. export type MoveDir = 'up' | 'down' | 'flat';
  103. export function moveDir(flucRateBp: number): MoveDir {
  104. if (flucRateBp > 0) {
  105. return 'up';
  106. }
  107. if (flucRateBp < 0) {
  108. return 'down';
  109. }
  110. return 'flat';
  111. }
  112. // 등락률 Bp → "+1.45%" / "-0.70%" / "0.00%"
  113. export function formatFlucRate(flucRateBp: number): string {
  114. const pct = flucRateBp / 100;
  115. const sign = flucRateBp > 0 ? '+' : '';
  116. return `${sign}${pct.toFixed(2)}%`;
  117. }
  118. // 종가 표시 — 천단위 콤마 + 소수 2자리
  119. export function formatIndexClose(close: number): string {
  120. return close.toLocaleString('ko-KR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  121. }