From 4669d08cb4f5ea626d5e6fd03db5b44c957fd01e Mon Sep 17 00:00:00 2001 From: 21in7 Date: Mon, 2 Mar 2026 13:50:39 +0900 Subject: [PATCH] feat: build_features accepts oi_change and funding_rate params Made-with: Cursor --- src/ml_features.py | 11 +++++++---- tests/test_ml_features.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/ml_features.py b/src/ml_features.py index 86ba00a..8a73960 100644 --- a/src/ml_features.py +++ b/src/ml_features.py @@ -34,11 +34,14 @@ def build_features( signal: str, btc_df: pd.DataFrame | None = None, eth_df: pd.DataFrame | None = None, + oi_change: float | None = None, + funding_rate: float | None = None, ) -> pd.Series: """ 기술 지표가 계산된 DataFrame의 마지막 행에서 ML 피처를 추출한다. - btc_df, eth_df가 제공되면 21개 피처를, 없으면 13개 피처를 반환한다. + btc_df, eth_df가 제공되면 23개 피처를, 없으면 15개 피처를 반환한다. signal: "LONG" | "SHORT" + oi_change, funding_rate: 실제 값이 제공되면 사용, 없으면 0.0으로 채운다. """ last = df.iloc[-1] close = last["close"] @@ -127,8 +130,8 @@ def build_features( "xrp_eth_rs": float(_calc_rs(ret_1, eth_ret_1)), }) - # 실시간에서는 OI/펀딩비를 수집하지 않으므로 0으로 채워 학습 피처(23개)와 일치시킨다 - base.setdefault("oi_change", 0.0) - base.setdefault("funding_rate", 0.0) + # 실시간에서 실제 값이 제공되면 사용, 없으면 0으로 채운다 + base["oi_change"] = float(oi_change) if oi_change is not None else 0.0 + base["funding_rate"] = float(funding_rate) if funding_rate is not None else 0.0 return pd.Series(base) diff --git a/tests/test_ml_features.py b/tests/test_ml_features.py index 1f3ffff..7a05b3e 100644 --- a/tests/test_ml_features.py +++ b/tests/test_ml_features.py @@ -25,12 +25,12 @@ def test_build_features_with_btc_eth_has_21_features(): btc_df = _make_df(10, base_price=50000.0) eth_df = _make_df(10, base_price=3000.0) features = build_features(xrp_df, "LONG", btc_df=btc_df, eth_df=eth_df) - assert len(features) == 21 + assert len(features) == 23 def test_build_features_without_btc_eth_has_13_features(): xrp_df = _make_df(10, base_price=1.0) features = build_features(xrp_df, "LONG") - assert len(features) == 13 + assert len(features) == 15 def test_build_features_btc_ret_1_correct(): xrp_df = _make_df(10, base_price=1.0) @@ -111,3 +111,30 @@ def test_side_encoding(): short_feat = build_features(df_ind, signal="SHORT") assert long_feat["side"] == 1 assert short_feat["side"] == 0 + + +@pytest.fixture +def sample_df_with_indicators(): + from src.indicators import Indicators + df = make_df(100) + ind = Indicators(df) + return ind.calculate_all() + + +def test_build_features_uses_provided_oi_funding(sample_df_with_indicators): + """oi_change, funding_rate 파라미터가 제공되면 실제 값이 피처에 반영된다.""" + feat = build_features( + sample_df_with_indicators, + signal="LONG", + oi_change=0.05, + funding_rate=0.0002, + ) + assert feat["oi_change"] == pytest.approx(0.05) + assert feat["funding_rate"] == pytest.approx(0.0002) + + +def test_build_features_defaults_to_zero_when_not_provided(sample_df_with_indicators): + """oi_change, funding_rate 파라미터 미제공 시 0.0으로 채워진다.""" + feat = build_features(sample_df_with_indicators, signal="LONG") + assert feat["oi_change"] == pytest.approx(0.0) + assert feat["funding_rate"] == pytest.approx(0.0)