diff --git a/config.yaml b/config.yaml index 36c3dc3..d8a20ae 100644 --- a/config.yaml +++ b/config.yaml @@ -6,15 +6,14 @@ ecos: api_key: "C5220CGY8FYFDN43B7ON" base_url: "https://ecos.bok.or.kr/api" - # 주요 통계코드 + # 주요 통계코드 (검증 완료 2026-03-10) stat_codes: - gdp_growth: "111Y002" # 국내총생산(실질성장률) - unemployment: "901Y027" # 실업률 - base_rate: "722Y001" # 한국은행 기준금리 - cd_rate: "817Y002" # CD(91일) 금리 - treasury_3y: "817Y002" # 국고채(3년) 수익률 - cpi: "901Y009" # 소비자물가지수 - composite_leading: "901Y067" # 경기선행지수 + gdp_growth: "902Y015" # 국제 주요국 경제성장률 / ITEM: KOR + unemployment: "901Y027" # 경제활동인구 / ITEM: I61BC (실업률) + base_rate: "722Y001" # 한국은행 기준금리 / ITEM: 0101000 + cd_rate: "721Y001" # 시장금리 / ITEM: 2010000 (CD 91일) + cpi: "901Y009" # 소비자물가지수 / ITEM: 0 (총지수, level→YoY% 변환) + composite_leading: "901Y067" # 경기종합지수 / ITEM: I16A (선행, 월별→연평균) # 모형 파라미터 model: diff --git a/data/macro_data.py b/data/macro_data.py index e41270d..b65b2b3 100644 --- a/data/macro_data.py +++ b/data/macro_data.py @@ -136,10 +136,10 @@ def collect_macro_data( # ------------------------------------------------------- # 1) GDP 실질성장률 (%) - # 통계표: 111Y002 (국민계정 - 주요지표 - 경제성장률) + # 통계표: 902Y015 (국제 주요국 경제성장률) / 항목: KOR # ------------------------------------------------------- logger.info("GDP 성장률 조회 중...") - df_gdp = api.fetch_stat("111Y002", "A", start, end, "10111") + df_gdp = api.fetch_stat("902Y015", "A", start, end, "KOR") if not df_gdp.empty: gdp_series = df_gdp.set_index("TIME")["DATA_VALUE"].astype(float) gdp_series.index = gdp_series.index.astype(int) @@ -148,10 +148,10 @@ def collect_macro_data( # ------------------------------------------------------- # 2) 실업률 (%) - # 통계표: 901Y027 (고용 - 주요고용지표) + # 통계표: 901Y027 (경제활동인구) / 항목: I61BC (실업률) # ------------------------------------------------------- logger.info("실업률 조회 중...") - df_unemp = api.fetch_stat("901Y027", "A", start, end, "3", " ") + df_unemp = api.fetch_stat("901Y027", "A", start, end, "I61BC") if not df_unemp.empty: unemp_series = df_unemp.set_index("TIME")["DATA_VALUE"].astype(float) unemp_series.index = unemp_series.index.astype(int) @@ -172,10 +172,10 @@ def collect_macro_data( # ------------------------------------------------------- # 4) CD(91일) 금리 (%) - # 통계표: 817Y002 + # 통계표: 721Y001 (시장금리) / 항목: 2010000 (CD 91일) # ------------------------------------------------------- logger.info("CD 금리 조회 중...") - df_cd = api.fetch_stat("817Y002", "A", start, end, "010502000") + df_cd = api.fetch_stat("721Y001", "A", start, end, "2010000") if not df_cd.empty: cd_series = df_cd.set_index("TIME")["DATA_VALUE"].astype(float) cd_series.index = cd_series.index.astype(int) @@ -184,32 +184,57 @@ def collect_macro_data( # ------------------------------------------------------- # 5) 소비자물가지수 상승률 (%) - # 통계표: 901Y009 + # 통계표: 901Y009 / 항목: 0 (총지수) + # 지수(level)로 조회 후 전년대비 상승률(%) 계산 # ------------------------------------------------------- logger.info("소비자물가 상승률 조회 중...") - df_cpi = api.fetch_stat("901Y009", "A", start, end, "0") + # 전년도까지 필요 → start를 1년 앞당겨 조회 + df_cpi = api.fetch_stat("901Y009", "A", str(start_year - 1), end, "0") if not df_cpi.empty: - cpi_series = df_cpi.set_index("TIME")["DATA_VALUE"].astype(float) - cpi_series.index = cpi_series.index.astype(int) - macro_vars["CPI_GROWTH"] = cpi_series + cpi_level = df_cpi.set_index("TIME")["DATA_VALUE"].astype(float) + cpi_level.index = cpi_level.index.astype(int) + cpi_level = cpi_level.sort_index() + # 전년대비 증가율 (%) + cpi_growth = cpi_level.pct_change() * 100 + cpi_growth = cpi_growth.loc[start_year:end_year] + macro_vars["CPI_GROWTH"] = cpi_growth time.sleep(0.5) # ------------------------------------------------------- - # 6) 경기선행지수 순환변동치 - # 통계표: 901Y067 + # 6) 경기선행종합지수 + # 통계표: 901Y067 / 항목: I16A (선행종합지수) + # 월별만 존재 → 월별 조회 후 연평균 산출 # ------------------------------------------------------- logger.info("경기선행지수 조회 중...") - df_leading = api.fetch_stat("901Y067", "A", start, end, "I16A") + df_leading = api.fetch_stat( + "901Y067", "M", + f"{start_year}01", f"{end_year}12", + "I16A" + ) if not df_leading.empty: - leading_series = df_leading.set_index("TIME")["DATA_VALUE"].astype(float) - leading_series.index = leading_series.index.astype(int) - macro_vars["LEADING_INDEX"] = leading_series + monthly = df_leading[["TIME", "DATA_VALUE"]].copy() + monthly["DATA_VALUE"] = monthly["DATA_VALUE"].astype(float) + monthly["YEAR"] = monthly["TIME"].str[:4].astype(int) + annual_avg = monthly.groupby("YEAR")["DATA_VALUE"].mean() + annual_avg = annual_avg.loc[start_year:end_year] + macro_vars["LEADING_INDEX"] = annual_avg - # DataFrame 결합 + # DataFrame 결합 (각 Series의 인덱스를 정리하여 결합) if macro_vars: - result = pd.DataFrame(macro_vars) + # 각 Series의 인덱스를 정수로 통일, 중복 제거 + clean_vars = {} + for name, series in macro_vars.items(): + s = series.copy() + s.index = s.index.astype(int) + s = s[~s.index.duplicated(keep='first')] # 중복 제거 + s = s.dropna() + clean_vars[name] = s + + result = pd.DataFrame(clean_vars) result.index.name = "YEAR" result = result.sort_index() + + logger.info(f"ECOS API 데이터 수집 완료: {len(result)}개 연도, {len(result.columns)}개 변수") return result else: logger.warning("거시경제 데이터 수집 실패. 내장 fallback 데이터 사용.") diff --git a/doc/ECOS_API/API개발명세서_100대.xls b/doc/ECOS_API/API개발명세서_100대.xls new file mode 100644 index 0000000..88bdde9 Binary files /dev/null and b/doc/ECOS_API/API개발명세서_100대.xls differ diff --git a/doc/ECOS_API/API개발명세서_서비스통계목록.xls b/doc/ECOS_API/API개발명세서_서비스통계목록.xls new file mode 100644 index 0000000..c0833bf Binary files /dev/null and b/doc/ECOS_API/API개발명세서_서비스통계목록.xls differ diff --git a/doc/ECOS_API/API개발명세서_통계메타DB.xls b/doc/ECOS_API/API개발명세서_통계메타DB.xls new file mode 100644 index 0000000..1e86206 Binary files /dev/null and b/doc/ECOS_API/API개발명세서_통계메타DB.xls differ diff --git a/doc/ECOS_API/API개발명세서_통계세부항목목록.xls b/doc/ECOS_API/API개발명세서_통계세부항목목록.xls new file mode 100644 index 0000000..da1e1d2 Binary files /dev/null and b/doc/ECOS_API/API개발명세서_통계세부항목목록.xls differ diff --git a/doc/ECOS_API/API개발명세서_통계용어사전.xls b/doc/ECOS_API/API개발명세서_통계용어사전.xls new file mode 100644 index 0000000..a7653fd Binary files /dev/null and b/doc/ECOS_API/API개발명세서_통계용어사전.xls differ diff --git a/doc/ECOS_API/API개발명세서_통계조회조건.xls b/doc/ECOS_API/API개발명세서_통계조회조건.xls new file mode 100644 index 0000000..8ad30f5 Binary files /dev/null and b/doc/ECOS_API/API개발명세서_통계조회조건.xls differ diff --git a/doc/ECOS_API/통계표_10221547.xlsx b/doc/ECOS_API/통계표_10221547.xlsx new file mode 100644 index 0000000..10384b5 Binary files /dev/null and b/doc/ECOS_API/통계표_10221547.xlsx differ diff --git a/scenarios/scenario_engine.py b/scenarios/scenario_engine.py index 4531864..35a67ce 100644 --- a/scenarios/scenario_engine.py +++ b/scenarios/scenario_engine.py @@ -107,7 +107,8 @@ class ScenarioEngine: # Phase 1: PIT 기간 (1~pit_horizon년) for t in range(min(n_short, self.total_horizon)): - z_path[t] = z_short[t] if t < len(z_short) else z_scenario + val = z_short[t] if t < len(z_short) else z_scenario + z_path[t] = val if np.isfinite(val) else z_scenario # Phase 2: Mean-reversion 기간 (pit_horizon+1 ~ transition_horizon년) for t in range(self.pit_horizon, min(self.transition_horizon, self.total_horizon)):