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.
This commit is contained in:
150
docs/plans/2026-03-02-adx-filter-design.md
Normal file
150
docs/plans/2026-03-02-adx-filter-design.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# 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**
|
||||
|
||||
```python
|
||||
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` 계산 앞에 추가:
|
||||
|
||||
```python
|
||||
# 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**
|
||||
|
||||
```bash
|
||||
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**
|
||||
|
||||
```python
|
||||
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**
|
||||
|
||||
```python
|
||||
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]` 바로 다음에 추가:
|
||||
|
||||
```python
|
||||
# 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**
|
||||
|
||||
```bash
|
||||
git add src/indicators.py tests/test_indicators.py
|
||||
git commit -m "feat: add ADX filter to block sideways market entries"
|
||||
```
|
||||
Reference in New Issue
Block a user