국채 금리 데이터를 RuleFit으로 분석해 조건식 전략을 뽑아본 실험.
머신러닝 기반 퀀트 아이디어 탐구 기록.
신세 한탄
채권 시계열 데이터를 한아름 준비하였다.
그러나 막상 전략을 뽑아내려니 머리가 하얗다.
운용회의 들어가면 다들 능숙하다.
- "이번주는 매크로가 어떻고..."
- "모레 나올 지표 컨센은 어떻고..."
- 금리, 스프레드, 투자자별 수급 추이도 보고
- "그래서 이번 주는 뭘 사겠습니다"
나는 뭘 돌려봐도 반타작. 동전던지기이다.
딜러들에게는 마법 공식이라도 있는 것인가.
아니면 짬에서 나오는 직관인가.
Rulefit이란?
"전략을 내놓아라 내놓지 않으면 구워먹으리"
매일같이 구지(피티)가를 부르면서
오늘도 GPT를 갈구다 보니 GPT놈이 뱉은 Rulefit.
머신러닝에서 흔히 쓰는 랜덤포레스트, 부스팅 같은 트리(tree) 기반 모델은 블랙박스라 해석이 어렵다.
RuleFit은 이 단점을 보완한다.
- 트리에서 조건문(rule)을 추출
- 그걸 선형 회귀에 태워 계수(coef)를 부여
- 최종적으로 IF ~ THEN ~ 형태의 룰셋으로 정리
즉 트리처럼 비선형 패턴은 잡으면서도, 결과는 사람이 읽을 수 있는 조건문으로 뽑아준다.
설명 가능한 블랙박스.
전략보다는 가설 도출용에 가깝다.
머신러닝이 참 그래프나 결과값은 fancy한데,
아무래도 economic implication이나
잘 정의된 조건이 나오지 않으면
회의때 설득이 불가능하다.
DataSet - 채권 시계열

순수 노가다를 통해 일자별 × 종목별로 feature를 준비했다.
총 36개.
[기본 정보]
price_date, bond_code, bond_name, alias, issue_date, maturity_date, days_to_maturity
[금리/잔차 관련]
ytm, interpolated_rate, bp_diff
[잔차 변화 / Z-score]
residual_delta_1d, residual_delta_5d,
residual_zscore_3d, residual_zscore_5d, residual_zscore_10d
[커브 특성]
slope_10y_3y, curvature_10_5_3
[커브 변화량]
slope_10y_3y_delta_1d, slope_10y_3y_delta_3d,
slope_10y_3y_delta_5d, slope_10y_3y_delta_10d,
curvature_10_5_3_delta_1d, curvature_10_5_3_delta_3d,
curvature_10_5_3_delta_5d, curvature_10_5_3_delta_10d
[수급 - 외국인]
foreigner_diff_1d, foreigner_sum_3d, foreigner_sum_5d, foreigner_sum_10d
[수급 - 은행]
bank_diff_1d, bank_sum_3d, bank_sum_5d, bank_sum_10d
[수급 - 보험]
insurance_diff_1d, insurance_sum_3d, insurance_sum_5d, insurance_sum_10d
[수급 - 자산운용]
asset_mgmt_diff_1d, asset_mgmt_sum_3d, asset_mgmt_sum_5d, asset_mgmt_sum_10d
CSV로 정리한 뒤 Python에서 RuleFit에 투입했다.
요약 코드
위 데이터를 ktb_features.csv에 저장하고,
아래 파이썬 코드를 돌려 학습을 시킨다.
import pandas as pd
from rulefit import RuleFit
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import joblib
import re
import os
from datetime import datetime
# ================================
# 0. 사용자 설정
# ================================
BASE_PATH = r"나의 경로"
CSV_PATH = os.path.join(BASE_PATH, "ktb_features.csv")
RUN_TIME = datetime.now().strftime("%Y%m%d_%H%M")
MODEL_PATH = os.path.join(BASE_PATH, f"rulefit_model_{RUN_TIME}.pkl")
RULES_CSV_PATH = os.path.join(BASE_PATH, f"top30_rules_{RUN_TIME}.csv")
FROM_DATE = "2024-01-01" # 분석 시작일
TO_DATE = None # 분석 종료일 (None이면 전체)
# 선택할 컬럼
keep_cols = [
"days_to_maturity", "ytm", "interpolated_rate", "bp_diff",
"residual_delta_5d", "residual_zscore_5d",
"slope_10y_3y", "curvature_10_5_3",
"slope_10y_3y_delta_3d", "slope_10y_3y_delta_5d",
"curvature_10_5_3_delta_3d", "curvature_10_5_3_delta_5d",
"foreigner_sum_3d", "foreigner_sum_5d",
"bank_sum_3d", "bank_sum_5d",
"insurance_sum_3d", "insurance_sum_5d",
"asset_mgmt_sum_3d", "asset_mgmt_sum_5d"
]
# ================================
# 1. 데이터 로딩 및 필터링
# ================================
df = pd.read_csv(CSV_PATH, encoding="cp949", parse_dates=["price_date"])
df = df[df["price_date"] >= pd.to_datetime(FROM_DATE)]
if TO_DATE:
df = df[df["price_date"] <= pd.to_datetime(TO_DATE)]
df["target"] = df["bp_diff"].shift(-5) - df["bp_diff"]
df = df.dropna(subset=["target"])
X = df[keep_cols].copy()
y = df["target"]
X = X.dropna()
y = y.loc[X.index]
X_np = X.to_numpy()
y_np = y.to_numpy()
feature_names = X.columns.tolist()
# ================================
# 2. 학습 분리
# ================================
X_train, X_test = X_np[:int(0.8*len(X_np))], X_np[int(0.8*len(X_np)):]
y_train, y_test = y_np[:int(0.8*len(y_np))], y_np[int(0.8*len(y_np)):]
# ================================
# 3. RuleFit 학습
# ================================
rf = RuleFit(tree_size=3, max_rules=1000, rfmode='regress')
rf.fit(X_train, y_train, feature_names=feature_names)
# ================================
# 4. 성능 평가
# ================================
y_pred = rf.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print(f"Test MSE: {mse:.6f}")
# ================================
# 5. 룰 정제
# ================================
rules = rf.get_rules()
rules = rules[rules.coef != 0].sort_values("importance", ascending=False)
# 정제함수: 소수점 3자리 반올림 + 조건 붙은 경우 정리
def clean_rule(rule_str):
# 숫자 반올림
rule_str = re.sub(r"([0-9]+\.[0-9]{5,})", lambda m: str(round(float(m.group(1)), 3)), rule_str)
# "feature1 > x & feature2 <= y" → 줄바꿈용 등 필요한 경우 추가 가능
return rule_str.replace(" & ", " AND ")
rules["rule"] = rules["rule"].apply(clean_rule)
# ================================
# 6. 저장
# ================================
top_rules = rules[["rule", "coef", "support", "importance"]].head(30)
top_rules.to_csv(RULES_CSV_PATH, index=False, encoding="utf-8-sig")
joblib.dump(rf, MODEL_PATH)
print(f"Top 30 rules saved to: {RULES_CSV_PATH}")
print(f"Model saved to: {MODEL_PATH}")
결과
| rule | coef | support | importance |
| slope_10y_3y > 15.25 & days_to_maturity > 371.0 | - 0.5078 | 0.4540 | 0.2528 |
| days_to_maturity | - 0.0001 | 1.0000 | 0.2295 |
| ytm | - 0.6708 | 1.0000 | 0.2157 |
| days_to_maturity > 388.5 & residual_delta_1d <= 6.8351643 & days_to_maturity > 374.5 & days_to_maturity > 367.0 | - 2.1447 | 0.9916 | 0.1953 |
| days_to_maturity > 642.0 | - 0.7091 | 0.9188 | 0.1937 |
| bp_diff > -3.248849391937256 | - 0.6586 | 0.9044 | 0.1936 |
| days_to_maturity > 378.0 & days_to_maturity <= 470.0 & days_to_maturity > 365.5 & days_to_maturity <= 462.5 | 1.1266 | 0.0251 | 0.1762 |
| days_to_maturity > 370.5 & days_to_maturity <= 592.5 & curvature_10_5_3 > 8.949999809265137 | - 1.7403 | 0.0096 | 0.1693 |
| days_to_maturity <= 416.0 & ytm > 2.3209999799728394 & days_to_maturity > 374.5 | - 1.9856 | 0.0072 | 0.1675 |
| bp_diff <= -6.603178024291992 & days_to_maturity > 371.5 & foreigner_sum_10d <= 1009.7449951171875 | - 0.8339 | 0.0346 | 0.1525 |
대표적인 룰은 이런 식이다
- slope_10y_3y > 15bp AND 잔존만기 > 1년 → 스프레드 축소
- 스프레드 < -6.6bp AND 외국인 10일 순매수 < 1,000억 → 스프레드 축소
- 잔존만기 378일 이상 AND 470일 이하 → 스프레드 확대
RuleFit 결과 해석
- coef: 룰의 방향과 강도. (+면 금리 상승, -면 하락 쪽으로 기여)
- support: 데이터 중 이 조건을 만족하는 비율. (높을수록 자주 등장)
- importance: 모델 내 기여도 = |coef| × support. (실질적으로 영향 큰 룰)
당장 결과표만 봐서는 예측력이 있어보이진 않는다.
허나 설명력만큼은 미친 듯이 직관적이다.
조건문 IF ~ THEN ~으로 딱딱 꽂히는 맛이 있다.
“야, 이럴 땐 그냥 내려.” 같은 느낌.
개꿀.
발전 방향
RuleFit은 간결한 룰 뽑는 데는 탁월해 보인다.
하지만 시계열에 특화된 모델은 아니고,
채권은 커브 움직임은 있긴하지만 인접 일자/테너별 상관관계가 강해
의미있는 결과가 잘 나오기 쉽지 않아 보인다.
- 테너별 subset을 나눠 각각 돌려보기
- 더 정교한 feature 가공 필요
- 예측보다는 전략 후보 스크리닝·보고용 해석 자료로 활용
요약: RuleFit은 해석은 잘 되는데, 실전 전략으로는 아직 글쎄… 하지만 룰 뽑히는 맛은 분명히 있다.
| 구분 | 내용 |
| 모델명 | Python RuleFitClassifier (imodels 라이브러리) |
| 데이터 | 금리 레벨 + 변화율 + 수급 등 시계열 feature |
| 결과 | importance 순 상위 규칙 추출, 해석 직관적 |
| 주요 인사이트 | 아직 결과가 구려서 쓸만한 규칙은 못찾음 |
| 활용 방안 | 전략개발 이전 가설 도출 or 보고용 해석 |
'Trading & Coding' 카테고리의 다른 글
| [채권 5] 채권시장 데이터, 시작부터 꼬이는 구조적 난제 (9) | 2025.08.27 |
|---|---|
| [초고급엑셀 3] RunQuery 한 줄이면 끝! 엑셀로 실시간 SQL 쿼리 실행하기 (3) | 2025.08.26 |
| [채권 3+초고급엑셀 2] 국채 Tenor별 상대가치분석 자동화 구현(Overview) (5) | 2025.08.19 |
| [엑셀 1] 함수 안먹힐 때, 쉼표 포함 숫자 1초 변환법(텍스트 나누기 활용) (7) | 2025.08.12 |
| [채권 2] K-Bond 호가분석 - 국고·통안 선별기 제작기 (2) | 2025.07.01 |