feat: add ADX as 24th ML feature for trend strength learning

Migrate ADX from hard filter (ADX < 25 blocks entry) to ML feature so
the model can learn optimal ADX thresholds from data. Updates FEATURE_COLS,
build_features(), and corresponding tests from 23 to 24 features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
21in7
2026-03-03 21:11:04 +09:00
parent 038a1f84ec
commit 0b18a0b80d
2 changed files with 10 additions and 7 deletions

View File

@@ -11,6 +11,7 @@ FEATURE_COLS = [
# 시장 미시구조: OI 변화율(z-score), 펀딩비(z-score) # 시장 미시구조: OI 변화율(z-score), 펀딩비(z-score)
# parquet에 oi_change/funding_rate 컬럼이 없으면 dataset_builder에서 0으로 채움 # parquet에 oi_change/funding_rate 컬럼이 없으면 dataset_builder에서 0으로 채움
"oi_change", "funding_rate", "oi_change", "funding_rate",
"adx",
] ]
@@ -39,7 +40,7 @@ def build_features(
) -> pd.Series: ) -> pd.Series:
""" """
기술 지표가 계산된 DataFrame의 마지막 행에서 ML 피처를 추출한다. 기술 지표가 계산된 DataFrame의 마지막 행에서 ML 피처를 추출한다.
btc_df, eth_df가 제공되면 23개 피처를, 없으면 15개 피처를 반환한다. btc_df, eth_df가 제공되면 24개 피처를, 없으면 16개 피처를 반환한다.
signal: "LONG" | "SHORT" signal: "LONG" | "SHORT"
oi_change, funding_rate: 실제 값이 제공되면 사용, 없으면 0.0으로 채운다. oi_change, funding_rate: 실제 값이 제공되면 사용, 없으면 0.0으로 채운다.
""" """
@@ -133,5 +134,6 @@ def build_features(
# 실시간에서 실제 값이 제공되면 사용, 없으면 0으로 채운다 # 실시간에서 실제 값이 제공되면 사용, 없으면 0으로 채운다
base["oi_change"] = float(oi_change) if oi_change is not None else 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 base["funding_rate"] = float(funding_rate) if funding_rate is not None else 0.0
base["adx"] = float(last.get("adx", 0))
return pd.Series(base) return pd.Series(base)

View File

@@ -17,20 +17,21 @@ def _make_df(n=10, base_price=1.0):
"ema21": closes, "ema50": closes, "atr": [0.01] * n, "ema21": closes, "ema50": closes, "atr": [0.01] * n,
"stoch_k": [50.0] * n, "stoch_d": [50.0] * n, "stoch_k": [50.0] * n, "stoch_d": [50.0] * n,
"vol_ma20": [1000.0] * n, "vol_ma20": [1000.0] * n,
"adx": [20.0] * n,
}) })
def test_build_features_with_btc_eth_has_21_features(): def test_build_features_with_btc_eth_has_24_features():
xrp_df = _make_df(10, base_price=1.0) xrp_df = _make_df(10, base_price=1.0)
btc_df = _make_df(10, base_price=50000.0) btc_df = _make_df(10, base_price=50000.0)
eth_df = _make_df(10, base_price=3000.0) eth_df = _make_df(10, base_price=3000.0)
features = build_features(xrp_df, "LONG", btc_df=btc_df, eth_df=eth_df) features = build_features(xrp_df, "LONG", btc_df=btc_df, eth_df=eth_df)
assert len(features) == 23 assert len(features) == 24
def test_build_features_without_btc_eth_has_13_features(): def test_build_features_without_btc_eth_has_16_features():
xrp_df = _make_df(10, base_price=1.0) xrp_df = _make_df(10, base_price=1.0)
features = build_features(xrp_df, "LONG") features = build_features(xrp_df, "LONG")
assert len(features) == 15 assert len(features) == 16
def test_build_features_btc_ret_1_correct(): def test_build_features_btc_ret_1_correct():
xrp_df = _make_df(10, base_price=1.0) xrp_df = _make_df(10, base_price=1.0)
@@ -49,9 +50,9 @@ def test_build_features_rs_zero_when_btc_ret_zero():
features = build_features(xrp_df, "LONG", btc_df=btc_df, eth_df=eth_df) features = build_features(xrp_df, "LONG", btc_df=btc_df, eth_df=eth_df)
assert features["xrp_btc_rs"] == 0.0 assert features["xrp_btc_rs"] == 0.0
def test_feature_cols_has_23_items(): def test_feature_cols_has_24_items():
from src.ml_features import FEATURE_COLS from src.ml_features import FEATURE_COLS
assert len(FEATURE_COLS) == 23 assert len(FEATURE_COLS) == 24
def make_df(n=100): def make_df(n=100):