fix(ecos): correct all 6 ECOS API stat/item codes #task-292

- GDP: 111Y002/10111 -> 902Y015/KOR (international comparative stats)
- Unemployment: 901Y027/3 -> 901Y027/I61BC (correct item for rate)
- CD rate: 817Y002/010502000 -> 721Y001/2010000 (market interest rates)
- CPI: now computes YoY growth from level index (pct_change)
- Leading index: monthly (M) fetch + annual average (no annual data available)
- Fix DataFrame merge: dedup index, dropna before concat
- Fix NaN in scenario Z paths: fallback to z_scenario
- Update config.yaml with verified stat codes
This commit is contained in:
Variet Agent
2026-03-10 22:53:51 +09:00
parent 3a9374c61a
commit 9fba224623
10 changed files with 53 additions and 28 deletions

View File

@@ -6,15 +6,14 @@
ecos: ecos:
api_key: "C5220CGY8FYFDN43B7ON" api_key: "C5220CGY8FYFDN43B7ON"
base_url: "https://ecos.bok.or.kr/api" base_url: "https://ecos.bok.or.kr/api"
# 주요 통계코드 # 주요 통계코드 (검증 완료 2026-03-10)
stat_codes: stat_codes:
gdp_growth: "111Y002" # 국내총생산(실질성장률) gdp_growth: "902Y015" # 국제 주요국 경제성장률 / ITEM: KOR
unemployment: "901Y027" # 실업률 unemployment: "901Y027" # 경제활동인구 / ITEM: I61BC (실업률)
base_rate: "722Y001" # 한국은행 기준금리 base_rate: "722Y001" # 한국은행 기준금리 / ITEM: 0101000
cd_rate: "817Y002" # CD(91일) 금리 cd_rate: "721Y001" # 시장금리 / ITEM: 2010000 (CD 91일)
treasury_3y: "817Y002" # 국고채(3년) 수익률 cpi: "901Y009" # 소비자물가지수 / ITEM: 0 (총지수, level→YoY% 변환)
cpi: "901Y009" # 소비자물가지수 composite_leading: "901Y067" # 경기종합지수 / ITEM: I16A (선행, 월별→연평균)
composite_leading: "901Y067" # 경기선행지수
# 모형 파라미터 # 모형 파라미터
model: model:

View File

@@ -136,10 +136,10 @@ def collect_macro_data(
# ------------------------------------------------------- # -------------------------------------------------------
# 1) GDP 실질성장률 (%) # 1) GDP 실질성장률 (%)
# 통계표: 111Y002 (국민계정 - 주요지표 - 경제성장률) # 통계표: 902Y015 (국제 주요국 경제성장률) / 항목: KOR
# ------------------------------------------------------- # -------------------------------------------------------
logger.info("GDP 성장률 조회 중...") 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: if not df_gdp.empty:
gdp_series = df_gdp.set_index("TIME")["DATA_VALUE"].astype(float) gdp_series = df_gdp.set_index("TIME")["DATA_VALUE"].astype(float)
gdp_series.index = gdp_series.index.astype(int) gdp_series.index = gdp_series.index.astype(int)
@@ -148,10 +148,10 @@ def collect_macro_data(
# ------------------------------------------------------- # -------------------------------------------------------
# 2) 실업률 (%) # 2) 실업률 (%)
# 통계표: 901Y027 (고용 - 주요고용지표) # 통계표: 901Y027 (경제활동인구) / 항목: I61BC (실업률)
# ------------------------------------------------------- # -------------------------------------------------------
logger.info("실업률 조회 중...") 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: if not df_unemp.empty:
unemp_series = df_unemp.set_index("TIME")["DATA_VALUE"].astype(float) unemp_series = df_unemp.set_index("TIME")["DATA_VALUE"].astype(float)
unemp_series.index = unemp_series.index.astype(int) unemp_series.index = unemp_series.index.astype(int)
@@ -172,10 +172,10 @@ def collect_macro_data(
# ------------------------------------------------------- # -------------------------------------------------------
# 4) CD(91일) 금리 (%) # 4) CD(91일) 금리 (%)
# 통계표: 817Y002 # 통계표: 721Y001 (시장금리) / 항목: 2010000 (CD 91일)
# ------------------------------------------------------- # -------------------------------------------------------
logger.info("CD 금리 조회 중...") 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: if not df_cd.empty:
cd_series = df_cd.set_index("TIME")["DATA_VALUE"].astype(float) cd_series = df_cd.set_index("TIME")["DATA_VALUE"].astype(float)
cd_series.index = cd_series.index.astype(int) cd_series.index = cd_series.index.astype(int)
@@ -184,32 +184,57 @@ def collect_macro_data(
# ------------------------------------------------------- # -------------------------------------------------------
# 5) 소비자물가지수 상승률 (%) # 5) 소비자물가지수 상승률 (%)
# 통계표: 901Y009 # 통계표: 901Y009 / 항목: 0 (총지수)
# 지수(level)로 조회 후 전년대비 상승률(%) 계산
# ------------------------------------------------------- # -------------------------------------------------------
logger.info("소비자물가 상승률 조회 중...") 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: if not df_cpi.empty:
cpi_series = df_cpi.set_index("TIME")["DATA_VALUE"].astype(float) cpi_level = df_cpi.set_index("TIME")["DATA_VALUE"].astype(float)
cpi_series.index = cpi_series.index.astype(int) cpi_level.index = cpi_level.index.astype(int)
macro_vars["CPI_GROWTH"] = cpi_series 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) time.sleep(0.5)
# ------------------------------------------------------- # -------------------------------------------------------
# 6) 경기선행지수 순환변동치 # 6) 경기선행종합지수
# 통계표: 901Y067 # 통계표: 901Y067 / 항목: I16A (선행종합지수)
# 월별만 존재 → 월별 조회 후 연평균 산출
# ------------------------------------------------------- # -------------------------------------------------------
logger.info("경기선행지수 조회 중...") 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: if not df_leading.empty:
leading_series = df_leading.set_index("TIME")["DATA_VALUE"].astype(float) monthly = df_leading[["TIME", "DATA_VALUE"]].copy()
leading_series.index = leading_series.index.astype(int) monthly["DATA_VALUE"] = monthly["DATA_VALUE"].astype(float)
macro_vars["LEADING_INDEX"] = leading_series 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: 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.index.name = "YEAR"
result = result.sort_index() result = result.sort_index()
logger.info(f"ECOS API 데이터 수집 완료: {len(result)}개 연도, {len(result.columns)}개 변수")
return result return result
else: else:
logger.warning("거시경제 데이터 수집 실패. 내장 fallback 데이터 사용.") logger.warning("거시경제 데이터 수집 실패. 내장 fallback 데이터 사용.")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -107,7 +107,8 @@ class ScenarioEngine:
# Phase 1: PIT 기간 (1~pit_horizon년) # Phase 1: PIT 기간 (1~pit_horizon년)
for t in range(min(n_short, self.total_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년) # Phase 2: Mean-reversion 기간 (pit_horizon+1 ~ transition_horizon년)
for t in range(self.pit_horizon, min(self.transition_horizon, self.total_horizon)): for t in range(self.pit_horizon, min(self.transition_horizon, self.total_horizon)):