claude/interesting-mcnulty #3
@@ -47,7 +47,7 @@ bash scripts/deploy_model.sh
|
||||
**5-layer data flow on each 15m candle close:**
|
||||
1. `src/data_stream.py` — Combined WebSocket for XRP/BTC/ETH, deque buffers (200 candles each)
|
||||
2. `src/indicators.py` — RSI, MACD, BB, EMA, StochRSI, ATR; weighted signal aggregation → LONG/SHORT/HOLD
|
||||
3. `src/ml_filter.py` + `src/ml_features.py` — 23-feature extraction, ONNX priority > LightGBM fallback, threshold ≥ 0.60
|
||||
3. `src/ml_filter.py` + `src/ml_features.py` — 24-feature extraction (ADX 포함), ONNX priority > LightGBM fallback, threshold ≥ 0.55
|
||||
4. `src/exchange.py` + `src/risk_manager.py` — Dynamic margin, MARKET orders with SL/TP, daily loss limit (5%)
|
||||
5. `src/user_data_stream.py` + `src/notifier.py` — Real-time TP/SL detection via WebSocket, Discord webhooks
|
||||
|
||||
@@ -113,4 +113,6 @@ All design documents and implementation plans are stored in `docs/plans/` with t
|
||||
| 2026-03-02 | `user-data-stream-tp-sl-detection` (design + plan) | Completed |
|
||||
| 2026-03-02 | `adx-filter-design` | Completed |
|
||||
| 2026-03-02 | `hold-negative-sampling` (design + plan) | Completed |
|
||||
| 2026-03-03 | `position-monitor-logging` | Completed |
|
||||
| 2026-03-03 | `adx-ml-feature-migration` (design + plan) | Completed |
|
||||
| 2026-03-03 | `optuna-precision-objective-plan` | Pending |
|
||||
|
||||
49
docs/plans/2026-03-03-adx-ml-feature-migration-design.md
Normal file
49
docs/plans/2026-03-03-adx-ml-feature-migration-design.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# ADX ML 피처 마이그레이션 설계
|
||||
|
||||
**Goal:** ADX 하드필터(< 25)를 제거하고, ADX를 ML 피처로 추가하여 횡보장 판단을 ML 모델에 위임한다.
|
||||
|
||||
**Background:** 운영 로그 분석 결과, ADX < 25 하드필터가 하루 종일 시그널을 차단하여 ML 필터가 평가할 기회 자체가 없었음. ADX 10~24 구간에서도 수익 가능한 패턴이 존재할 수 있으나, 현재 구조에서는 ML이 이를 학습할 수 없음.
|
||||
|
||||
**Tech Stack:** LightGBM, pandas-ta (기존 사용 중)
|
||||
|
||||
---
|
||||
|
||||
## 변경 사항
|
||||
|
||||
### 1. ML 피처에 ADX 추가 (23 → 24 피처)
|
||||
|
||||
- `src/ml_features.py`: `FEATURE_COLS`에 `"adx"` 추가
|
||||
- `build_features()`: ADX 값 추출 로직 추가
|
||||
|
||||
### 2. 데이터셋 빌더에서 ADX 하드필터 제거
|
||||
|
||||
- `src/dataset_builder.py`: `_calc_signals()`에서 ADX < 25 → HOLD 강제 로직 제거
|
||||
- ADX 낮은 구간의 시그널도 학습 데이터에 포함됨
|
||||
|
||||
### 3. indicators.py ADX 하드필터 제거
|
||||
|
||||
- `src/indicators.py`: `get_signal()`에서 ADX < 25 early-return 제거
|
||||
- ADX 값은 항상 로그에 남김 (대시보드 표시용)
|
||||
|
||||
### 4. ADX 로깅 개선
|
||||
|
||||
- ADX ≥ 25일 때도 로그 출력 → 대시보드에서 ADX 차트 끊김 해소
|
||||
|
||||
### 5. 테스트 업데이트
|
||||
|
||||
- ADX 하드필터 관련 기존 테스트 수정/제거
|
||||
- ML 피처에 ADX 포함 확인 테스트 추가
|
||||
|
||||
## 데이터 흐름 (변경 후)
|
||||
|
||||
```
|
||||
캔들 → get_signal() → 지표 가중치 기반 LONG/SHORT/HOLD (ADX 필터 없음)
|
||||
→ ADX 값 항상 로그 출력
|
||||
→ signal != HOLD → build_features() [24 피처, ADX 포함]
|
||||
→ ML 필터 (threshold ≥ 0.55) → 진입 판단
|
||||
```
|
||||
|
||||
## 주의 사항
|
||||
|
||||
- 기존 학습된 모델(23 피처)은 24 피처 입력과 호환 안 됨 → **재학습 필수**
|
||||
- 재학습 전까지 봇 운영 불가 → 배포 시 `train_and_deploy.sh` 먼저 실행
|
||||
227
docs/plans/2026-03-03-adx-ml-feature-migration-plan.md
Normal file
227
docs/plans/2026-03-03-adx-ml-feature-migration-plan.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# ADX ML 피처 마이그레이션 구현 계획
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** ADX 하드필터를 제거하고 ADX를 24번째 ML 피처로 추가하여, 횡보장 판단을 ML 모델에 위임한다. ADX 값을 항상 로그에 남겨 대시보드 끊김도 해소한다.
|
||||
|
||||
**Architecture:** `indicators.py`에서 ADX < 25 early-return 삭제, `ml_features.py`에 ADX 피처 추가 (23 → 24개), `dataset_builder.py`에서 ADX 하드필터 삭제 + ADX 피처 추출 추가. 기존 모델과 호환 안 되므로 재학습 필수.
|
||||
|
||||
**Tech Stack:** LightGBM, pandas-ta, pytest
|
||||
|
||||
---
|
||||
|
||||
### Task 1: ML 피처 테스트 업데이트 (24개 피처)
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/test_ml_features.py:52-54`
|
||||
|
||||
**Step 1: Update the test**
|
||||
|
||||
`test_feature_cols_has_23_items`를 24개로 변경:
|
||||
|
||||
```python
|
||||
def test_feature_cols_has_24_items():
|
||||
from src.ml_features import FEATURE_COLS
|
||||
assert len(FEATURE_COLS) == 24
|
||||
```
|
||||
|
||||
`test_build_features_with_btc_eth_has_21_features`의 assert도 변경:
|
||||
|
||||
```python
|
||||
def test_build_features_with_btc_eth_has_24_features():
|
||||
xrp_df = _make_df(10, base_price=1.0)
|
||||
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) == 24
|
||||
```
|
||||
|
||||
`test_build_features_without_btc_eth_has_13_features`도 변경:
|
||||
|
||||
```python
|
||||
def test_build_features_without_btc_eth_has_16_features():
|
||||
xrp_df = _make_df(10, base_price=1.0)
|
||||
features = build_features(xrp_df, "LONG")
|
||||
assert len(features) == 16
|
||||
```
|
||||
|
||||
`_make_df`에 `"adx": [20.0] * n` 컬럼 추가.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `pytest tests/test_ml_features.py::test_feature_cols_has_24_items -v`
|
||||
Expected: FAIL — 현재 23개
|
||||
|
||||
---
|
||||
|
||||
### Task 2: FEATURE_COLS에 ADX 추가 + build_features() 수정
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/ml_features.py:4-14` (FEATURE_COLS), `src/ml_features.py:98-112` (base dict)
|
||||
|
||||
**Step 3: Add ADX to FEATURE_COLS**
|
||||
|
||||
```python
|
||||
FEATURE_COLS = [
|
||||
"rsi", "macd_hist", "bb_pct", "ema_align",
|
||||
"stoch_k", "stoch_d", "atr_pct", "vol_ratio",
|
||||
"ret_1", "ret_3", "ret_5", "signal_strength", "side",
|
||||
"btc_ret_1", "btc_ret_3", "btc_ret_5",
|
||||
"eth_ret_1", "eth_ret_3", "eth_ret_5",
|
||||
"xrp_btc_rs", "xrp_eth_rs",
|
||||
"oi_change", "funding_rate",
|
||||
"adx",
|
||||
]
|
||||
```
|
||||
|
||||
**Step 4: Add ADX extraction in build_features()**
|
||||
|
||||
`base` dict 생성 부분 (line 112 이후)에 추가:
|
||||
|
||||
```python
|
||||
base["adx"] = float(last.get("adx", 0))
|
||||
```
|
||||
|
||||
docstring의 "23개 피처"를 "24개 피처"로 변경.
|
||||
|
||||
**Step 5: Run tests to verify they pass**
|
||||
|
||||
Run: `pytest tests/test_ml_features.py -v`
|
||||
Expected: ALL PASS
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add src/ml_features.py tests/test_ml_features.py
|
||||
git commit -m "feat: add ADX as 24th ML feature"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: indicators.py ADX 하드필터 제거 + 항상 로깅
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/indicators.py:63-67`
|
||||
|
||||
**Step 7: Replace ADX hard filter with always-log**
|
||||
|
||||
`get_signal()` 메서드에서 기존 ADX 필터 코드:
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
를 다음으로 교체:
|
||||
|
||||
```python
|
||||
# ADX 로깅 (ML 피처로 위임, 하드필터 제거)
|
||||
adx = last.get("adx", None)
|
||||
if adx is not None and not pd.isna(adx):
|
||||
logger.debug(f"ADX: {adx:.1f}")
|
||||
```
|
||||
|
||||
**Step 8: Run ADX-related tests**
|
||||
|
||||
Run: `pytest tests/test_indicators.py -k "adx" -v`
|
||||
Expected: `test_adx_column_exists` PASS, `test_adx_nan_falls_through` PASS, `test_adx_filter_blocks_low_adx` FAIL (필터 제거됨)
|
||||
|
||||
---
|
||||
|
||||
### Task 4: ADX 필터 테스트 업데이트
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/test_indicators.py:57-71`
|
||||
|
||||
**Step 9: Replace block test with pass-through test**
|
||||
|
||||
`test_adx_filter_blocks_low_adx`를 제거하고 새 테스트로 교체:
|
||||
|
||||
```python
|
||||
def test_adx_low_does_not_block_signal(sample_df):
|
||||
"""ADX < 25여도 시그널이 차단되지 않는다 (ML에 위임)."""
|
||||
ind = Indicators(sample_df)
|
||||
df = ind.calculate_all()
|
||||
# 강한 LONG 신호가 나오도록 지표 조작
|
||||
df.loc[df.index[-1], "rsi"] = 20
|
||||
df.loc[df.index[-2], "macd"] = -1
|
||||
df.loc[df.index[-2], "macd_signal"] = 0
|
||||
df.loc[df.index[-1], "macd"] = 1
|
||||
df.loc[df.index[-1], "macd_signal"] = 0
|
||||
df.loc[df.index[-1], "volume"] = df.loc[df.index[-1], "vol_ma20"] * 2
|
||||
df["adx"] = 15.0
|
||||
signal = ind.get_signal(df)
|
||||
# ADX 낮아도 지표 조건 충족 시 LONG 반환 (ML이 최종 판단)
|
||||
assert signal == "LONG"
|
||||
```
|
||||
|
||||
**Step 10: Run all indicator tests**
|
||||
|
||||
Run: `pytest tests/test_indicators.py -v`
|
||||
Expected: ALL PASS
|
||||
|
||||
**Step 11: Commit**
|
||||
|
||||
```bash
|
||||
git add src/indicators.py tests/test_indicators.py
|
||||
git commit -m "feat: remove ADX hard filter, delegate to ML"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: dataset_builder.py ADX 하드필터 제거 + ADX 피처 추가
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/dataset_builder.py:119-123` (ADX 필터 삭제), `src/dataset_builder.py:215-230` (ADX 피처 추가)
|
||||
|
||||
**Step 12: Remove ADX hard filter in _calc_signals()**
|
||||
|
||||
`_calc_signals()` 함수에서 다음 코드 삭제 (lines 119-123):
|
||||
|
||||
```python
|
||||
# ADX 횡보장 필터: ADX < 25이면 추세 부재로 판단하여 진입 차단
|
||||
if "adx" in d.columns:
|
||||
adx = d["adx"].values
|
||||
low_adx = (~np.isnan(adx)) & (adx < 25)
|
||||
signal_arr[low_adx] = "HOLD"
|
||||
```
|
||||
|
||||
**Step 13: Add ADX feature to _calc_features_vectorized()**
|
||||
|
||||
`_calc_features_vectorized()` 함수의 `result` DataFrame 생성 부분에 `"adx"` 추가:
|
||||
|
||||
```python
|
||||
# ADX (ML 피처로 제공 — rolling z-score 정규화)
|
||||
adx_raw = d["adx"].values.astype(np.float64) if "adx" in d.columns else np.zeros(len(d), dtype=np.float64)
|
||||
adx_z = _rolling_zscore(adx_raw)
|
||||
```
|
||||
|
||||
`result` DataFrame에 `"adx": adx_z,` 추가 (side 다음에).
|
||||
|
||||
**Step 14: Run full test suite**
|
||||
|
||||
Run: `pytest tests/ -v --tb=short`
|
||||
Expected: ALL PASS
|
||||
|
||||
**Step 15: Commit**
|
||||
|
||||
```bash
|
||||
git add src/dataset_builder.py
|
||||
git commit -m "feat: remove ADX hard filter from dataset builder, add ADX as ML feature"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 전체 테스트 + 최종 검증
|
||||
|
||||
**Step 16: Run full test suite**
|
||||
|
||||
Run: `bash scripts/run_tests.sh`
|
||||
Expected: ALL PASS
|
||||
|
||||
**Step 17: Final commit if needed**
|
||||
|
||||
주의: 기존 모델(23 피처)은 24 피처 입력과 호환 안 됨. 배포 전 반드시 `bash scripts/train_and_deploy.sh` 실행하여 재학습 필요.
|
||||
35
docs/plans/2026-03-03-position-monitor-logging.md
Normal file
35
docs/plans/2026-03-03-position-monitor-logging.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 포지션 모니터 로깅 (실시간 가격 추적)
|
||||
|
||||
**Goal:** 포지션 보유 중 5분마다 현재가 기준 미실현 손익을 로그로 출력하여, 봇 운영 중 포지션 상태를 실시간 모니터링할 수 있게 한다.
|
||||
|
||||
**Status:** Completed
|
||||
|
||||
---
|
||||
|
||||
## 변경 사항
|
||||
|
||||
### 1. MultiSymbolStream에 latest_price 속성 추가
|
||||
|
||||
- `src/data_stream.py`: `self.latest_price: float | None = None` 초기화
|
||||
- `handle_message()`에서 **모든 kline 메시지** (미확정 캔들 포함)에 대해 primary symbol(XRPUSDT)의 close 가격으로 업데이트
|
||||
- 기존에는 확정 캔들만 처리했으나, 실시간 가격 추적을 위해 미확정 캔들도 반영
|
||||
- BTC/ETH 등 비주 심볼은 latest_price 갱신 안 함
|
||||
|
||||
### 2. _position_monitor() 코루틴 추가
|
||||
|
||||
- `src/bot.py`: `_MONITOR_INTERVAL = 300` (5분) 클래스 상수 정의
|
||||
- `async def _position_monitor()` 무한 루프: 5분마다 실행
|
||||
- 포지션 없으면(`current_trade_side is None`) skip
|
||||
- 포지션 있으면: `_calc_estimated_pnl(price)`로 미실현 PnL 계산, 퍼센트 산출 후 INFO 로그 출력
|
||||
- `asyncio.gather()`에 추가하여 기존 user_data_stream, candle processing과 병렬 실행
|
||||
|
||||
### 3. 테스트
|
||||
|
||||
- `tests/test_bot.py`: 포지션 보유 시 PnL 로깅 확인, 포지션 없을 때 정상 skip 확인 (2 cases)
|
||||
- `tests/test_data_stream.py`: 미확정 캔들로 latest_price 갱신, 비주 심볼은 무시 확인 (1 case)
|
||||
|
||||
## 설계 결정
|
||||
|
||||
- WebSocket 스트림 재사용 (추가 API 연결 불필요)
|
||||
- `_MONITOR_INTERVAL`은 클래스 상수로 정의 (테스트에서 0으로 오버라이드 가능)
|
||||
- 가격/진입가/수량 중 하나라도 None이면 graceful skip
|
||||
@@ -116,12 +116,6 @@ def _calc_signals(d: pd.DataFrame) -> np.ndarray:
|
||||
# 둘 다 해당하면 HOLD (충돌 방지)
|
||||
signal_arr[long_enter & short_enter] = "HOLD"
|
||||
|
||||
# ADX 횡보장 필터: ADX < 25이면 추세 부재로 판단하여 진입 차단
|
||||
if "adx" in d.columns:
|
||||
adx = d["adx"].values
|
||||
low_adx = (~np.isnan(adx)) & (adx < 25)
|
||||
signal_arr[low_adx] = "HOLD"
|
||||
|
||||
return signal_arr
|
||||
|
||||
|
||||
@@ -212,6 +206,10 @@ def _calc_features_vectorized(
|
||||
|
||||
side = np.where(signal_arr == "LONG", 1.0, 0.0).astype(np.float32)
|
||||
|
||||
# ADX (ML 피처로 제공 — rolling z-score 정규화)
|
||||
adx_raw = d["adx"].values.astype(np.float64) if "adx" in d.columns else np.zeros(len(d), dtype=np.float64)
|
||||
adx_z = _rolling_zscore(adx_raw)
|
||||
|
||||
result = pd.DataFrame({
|
||||
"rsi": rsi.values.astype(np.float32),
|
||||
"macd_hist": macd_hist.values.astype(np.float32),
|
||||
@@ -226,6 +224,7 @@ def _calc_features_vectorized(
|
||||
"ret_5": ret_5_z,
|
||||
"signal_strength": strength,
|
||||
"side": side,
|
||||
"adx": adx_z,
|
||||
"_signal": signal_arr, # 레이블 계산용 임시 컬럼
|
||||
}, index=d.index)
|
||||
|
||||
|
||||
@@ -60,11 +60,10 @@ class Indicators:
|
||||
last = df.iloc[-1]
|
||||
prev = df.iloc[-2]
|
||||
|
||||
# ADX 횡보장 필터: ADX < 25이면 추세 부재로 판단하여 진입 차단
|
||||
# ADX 로깅 (ML 피처로 위임, 하드필터 제거)
|
||||
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"
|
||||
if adx is not None and not pd.isna(adx):
|
||||
logger.debug(f"ADX: {adx:.1f}")
|
||||
|
||||
long_signals = 0
|
||||
short_signals = 0
|
||||
|
||||
@@ -11,6 +11,7 @@ FEATURE_COLS = [
|
||||
# 시장 미시구조: OI 변화율(z-score), 펀딩비(z-score)
|
||||
# parquet에 oi_change/funding_rate 컬럼이 없으면 dataset_builder에서 0으로 채움
|
||||
"oi_change", "funding_rate",
|
||||
"adx",
|
||||
]
|
||||
|
||||
|
||||
@@ -39,7 +40,7 @@ def build_features(
|
||||
) -> pd.Series:
|
||||
"""
|
||||
기술 지표가 계산된 DataFrame의 마지막 행에서 ML 피처를 추출한다.
|
||||
btc_df, eth_df가 제공되면 23개 피처를, 없으면 15개 피처를 반환한다.
|
||||
btc_df, eth_df가 제공되면 24개 피처를, 없으면 16개 피처를 반환한다.
|
||||
signal: "LONG" | "SHORT"
|
||||
oi_change, funding_rate: 실제 값이 제공되면 사용, 없으면 0.0으로 채운다.
|
||||
"""
|
||||
@@ -133,5 +134,6 @@ def build_features(
|
||||
# 실시간에서 실제 값이 제공되면 사용, 없으면 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["adx"] = float(last.get("adx", 0))
|
||||
|
||||
return pd.Series(base)
|
||||
|
||||
@@ -54,21 +54,21 @@ def test_adx_column_exists(sample_df):
|
||||
assert (valid >= 0).all()
|
||||
|
||||
|
||||
def test_adx_filter_blocks_low_adx(sample_df):
|
||||
"""ADX < 25일 때 가중치와 무관하게 HOLD를 반환해야 한다."""
|
||||
def test_adx_low_does_not_block_signal(sample_df):
|
||||
"""ADX < 25여도 시그널이 차단되지 않는다 (ML에 위임)."""
|
||||
ind = Indicators(sample_df)
|
||||
df = ind.calculate_all()
|
||||
# 강한 LONG 신호가 나오도록 지표 조작
|
||||
df.loc[df.index[-1], "rsi"] = 20 # RSI 과매도 → +1
|
||||
df.loc[df.index[-2], "macd"] = -1 # MACD 골든크로스 → +2
|
||||
df.loc[df.index[-1], "rsi"] = 20
|
||||
df.loc[df.index[-2], "macd"] = -1
|
||||
df.loc[df.index[-2], "macd_signal"] = 0
|
||||
df.loc[df.index[-1], "macd"] = 1
|
||||
df.loc[df.index[-1], "macd_signal"] = 0
|
||||
df.loc[df.index[-1], "volume"] = df.loc[df.index[-1], "vol_ma20"] * 2 # 거래량 서지
|
||||
# ADX를 강제로 낮은 값으로 설정
|
||||
df.loc[df.index[-1], "volume"] = df.loc[df.index[-1], "vol_ma20"] * 2
|
||||
df["adx"] = 15.0
|
||||
signal = ind.get_signal(df)
|
||||
assert signal == "HOLD"
|
||||
# ADX 낮아도 지표 조건 충족 시 LONG 반환 (ML이 최종 판단)
|
||||
assert signal == "LONG"
|
||||
|
||||
|
||||
def test_adx_nan_falls_through(sample_df):
|
||||
|
||||
@@ -17,20 +17,21 @@ def _make_df(n=10, base_price=1.0):
|
||||
"ema21": closes, "ema50": closes, "atr": [0.01] * n,
|
||||
"stoch_k": [50.0] * n, "stoch_d": [50.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)
|
||||
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) == 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)
|
||||
features = build_features(xrp_df, "LONG")
|
||||
assert len(features) == 15
|
||||
assert len(features) == 16
|
||||
|
||||
def test_build_features_btc_ret_1_correct():
|
||||
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)
|
||||
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
|
||||
assert len(FEATURE_COLS) == 23
|
||||
assert len(FEATURE_COLS) == 24
|
||||
|
||||
|
||||
def make_df(n=100):
|
||||
|
||||
Reference in New Issue
Block a user