Files
cointrader/docs/plans/2026-03-02-adx-filter-design.md
21in7 99fa508db7 feat: add CLAUDE.md and settings.json for project documentation and plugin configuration
Introduced CLAUDE.md to provide comprehensive guidance on the CoinTrader project, including architecture, common commands, testing, and deployment details. Added settings.json to enable the superpowers plugin for Claude. This enhances the project's documentation and configuration management.
2026-03-02 20:01:18 +09:00

4.1 KiB

ADX 횡보장 필터 구현 계획

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: ADX < 25일 때 get_signal()에서 즉시 HOLD를 반환하여 횡보장 진입을 차단한다.

Architecture: calculate_all()에서 pandas_ta.adx()로 ADX 컬럼을 추가하고, get_signal()에서 가중치 계산 전 ADX < 25이면 early-return HOLD. NaN(초기 캔들)은 기존 로직으로 폴백.

Tech Stack: pandas-ta (이미 사용 중), pytest


Task 1: ADX 계산 테스트 추가

Files:

  • Test: tests/test_indicators.py

Step 1: Write the failing test

def test_adx_column_exists(sample_df):
    """calculate_all()이 adx 컬럼을 생성하는지 확인."""
    ind = Indicators(sample_df)
    df = ind.calculate_all()
    assert "adx" in df.columns
    valid = df["adx"].dropna()
    assert (valid >= 0).all()

tests/test_indicators.py에 위 테스트 함수를 추가한다.

Step 2: Run test to verify it fails

Run: pytest tests/test_indicators.py::test_adx_column_exists -v Expected: FAIL — "adx" not in df.columns


Task 2: calculate_all()에 ADX 계산 추가

Files:

  • Modify: src/indicators.py:46-48 (vol_ma20 계산 바로 앞에 추가)

Step 3: Write minimal implementation

calculate_all()의 Stochastic RSI 계산 뒤, vol_ma20 계산 앞에 추가:

        # ADX (14) — 횡보장 필터
        adx_df = ta.adx(df["high"], df["low"], df["close"], length=14)
        df["adx"] = adx_df["ADX_14"]

Step 4: Run test to verify it passes

Run: pytest tests/test_indicators.py::test_adx_column_exists -v Expected: PASS

Step 5: Commit

git add src/indicators.py tests/test_indicators.py
git commit -m "feat: add ADX calculation to indicators"

Task 3: ADX 필터 테스트 추가 (차단 케이스)

Files:

  • Test: tests/test_indicators.py

Step 6: Write the failing test

def test_adx_filter_blocks_low_adx(sample_df):
    """ADX < 25일 때 가중치와 무관하게 HOLD를 반환해야 한다."""
    ind = Indicators(sample_df)
    df = ind.calculate_all()
    # ADX를 강제로 낮은 값으로 설정
    df["adx"] = 15.0
    signal = ind.get_signal(df)
    assert signal == "HOLD"

Step 7: Run test to verify it fails

Run: pytest tests/test_indicators.py::test_adx_filter_blocks_low_adx -v Expected: FAIL — signal이 LONG 또는 SHORT 반환 (ADX 필터 미구현)


Task 4: ADX 필터 테스트 추가 (NaN 폴백 케이스)

Files:

  • Test: tests/test_indicators.py

Step 8: Write the failing test

def test_adx_nan_falls_through(sample_df):
    """ADX가 NaN(초기 캔들)이면 기존 가중치 로직으로 폴백해야 한다."""
    ind = Indicators(sample_df)
    df = ind.calculate_all()
    df["adx"] = float("nan")
    signal = ind.get_signal(df)
    # NaN이면 차단하지 않고 기존 로직 실행 → LONG/SHORT/HOLD 중 하나
    assert signal in ("LONG", "SHORT", "HOLD")

Step 9: Run test to verify it passes (이 테스트는 현재도 통과)

Run: pytest tests/test_indicators.py::test_adx_nan_falls_through -v Expected: PASS (ADX 컬럼이 무시되므로 기존 로직 그대로)


Task 5: get_signal()에 ADX early-return 구현

Files:

  • Modify: src/indicators.py:51-56 (get_signal 메서드 시작부)

Step 10: Write minimal implementation

get_signal() 메서드의 last = df.iloc[-1] 바로 다음에 추가:

        # ADX 횡보장 필터: ADX < 25이면 추세 부재로 판단하여 진입 차단
        adx = last.get("adx", None)
        if adx is not None and not pd.isna(adx) and adx < 25:
            logger.debug(f"ADX 필터: {adx:.1f} < 25 — HOLD")
            return "HOLD"

Step 11: Run all ADX-related tests

Run: pytest tests/test_indicators.py -k "adx" -v Expected: 3 tests PASS

Step 12: Run full test suite to check for regressions

Run: pytest tests/ -v --tb=short Expected: All tests PASS

Step 13: Commit

git add src/indicators.py tests/test_indicators.py
git commit -m "feat: add ADX filter to block sideways market entries"