feat: add HOLD negative sampling to dataset_builder
Add negative_ratio parameter to generate_dataset_vectorized() that
samples HOLD candles as label=0 negatives alongside signal candles.
This increases training data from ~535 to ~3,200 samples when enabled.
- Split valid_rows into base_valid (shared) and sig_valid (signal-only)
- Add 'source' column ("signal" vs "hold_negative") for traceability
- HOLD samples get label=0 and random 50/50 side assignment
- Default negative_ratio=0 preserves backward compatibility
- Fix incorrect column count assertion in existing test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,29 +24,29 @@ CoinTrader는 **Binance Futures 자동매매 봇**입니다. 기술 지표 신
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph 외부["외부 데이터 소스 (Binance)"]
|
||||
WS1["Combined WebSocket\nXRP/BTC/ETH 15분봉 캔들"]
|
||||
WS2["User Data Stream WebSocket\nORDER_TRADE_UPDATE 이벤트"]
|
||||
REST["REST API\nOI·펀딩비·잔고·포지션 조회"]
|
||||
WS1["Combined WebSocket<br/>XRP/BTC/ETH 15분봉 캔들"]
|
||||
WS2["User Data Stream WebSocket<br/>ORDER_TRADE_UPDATE 이벤트"]
|
||||
REST["REST API<br/>OI·펀딩비·잔고·포지션 조회"]
|
||||
end
|
||||
|
||||
subgraph 실시간봇["실시간 봇 (bot.py — asyncio)"]
|
||||
DS["data_stream.py\nMultiSymbolStream\n캔들 버퍼 (deque 200개)"]
|
||||
IND["indicators.py\n기술 지표 계산\nRSI·MACD·BB·EMA·StochRSI·ATR"]
|
||||
MF["ml_features.py\n23개 피처 추출\n(XRP 13 + BTC/ETH 8 + OI/FR 2)"]
|
||||
ML["ml_filter.py\nMLFilter\nONNX 우선 / LightGBM 폴백\n확률 ≥ 0.60 시 진입 허용"]
|
||||
RM["risk_manager.py\nRiskManager\n일일 손실 5% 한도\n동적 증거금 비율"]
|
||||
EX["exchange.py\nBinanceFuturesClient\n주문·레버리지·잔고 API"]
|
||||
UDS["user_data_stream.py\nUserDataStream\nTP/SL 즉시 감지"]
|
||||
NT["notifier.py\nDiscordNotifier\n진입·청산·오류 알림"]
|
||||
DS["data_stream.py<br/>MultiSymbolStream<br/>캔들 버퍼 (deque 200개)"]
|
||||
IND["indicators.py<br/>기술 지표 계산<br/>RSI·MACD·BB·EMA·StochRSI·ATR·ADX"]
|
||||
MF["ml_features.py<br/>23개 피처 추출<br/>(XRP 13 + BTC/ETH 8 + OI/FR 2)"]
|
||||
ML["ml_filter.py<br/>MLFilter<br/>ONNX 우선 / LightGBM 폴백<br/>확률 ≥ 0.60 시 진입 허용"]
|
||||
RM["risk_manager.py<br/>RiskManager<br/>일일 손실 5% 한도<br/>동적 증거금 비율"]
|
||||
EX["exchange.py<br/>BinanceFuturesClient<br/>주문·레버리지·잔고 API"]
|
||||
UDS["user_data_stream.py<br/>UserDataStream<br/>TP/SL 즉시 감지"]
|
||||
NT["notifier.py<br/>DiscordNotifier<br/>진입·청산·오류 알림"]
|
||||
end
|
||||
|
||||
subgraph mlops["MLOps 파이프라인 (맥미니 — 수동/크론)"]
|
||||
FH["fetch_history.py\n과거 캔들 + OI/펀딩비\nParquet Upsert"]
|
||||
DB["dataset_builder.py\n벡터화 데이터셋 생성\n레이블: ATR SL/TP 6시간 룩어헤드"]
|
||||
TM["train_model.py\nLightGBM 학습\nWalk-Forward 5폴드 검증"]
|
||||
TN["tune_hyperparams.py\nOptuna 50 trials\nTPE + MedianPruner"]
|
||||
AP["active_lgbm_params.json\nActive Config 패턴\n승인된 파라미터 저장"]
|
||||
DM["deploy_model.sh\nrsync → LXC 서버\n봇 핫리로드 트리거"]
|
||||
FH["fetch_history.py<br/>과거 캔들 + OI/펀딩비<br/>Parquet Upsert"]
|
||||
DB["dataset_builder.py<br/>벡터화 데이터셋 생성<br/>레이블: ATR SL/TP 6시간 룩어헤드"]
|
||||
TM["train_model.py<br/>LightGBM 학습<br/>Walk-Forward 5폴드 검증"]
|
||||
TN["tune_hyperparams.py<br/>Optuna 50 trials<br/>TPE + MedianPruner"]
|
||||
AP["active_lgbm_params.json<br/>Active Config 패턴<br/>승인된 파라미터 저장"]
|
||||
DM["deploy_model.sh<br/>rsync → LXC 서버<br/>봇 핫리로드 트리거"]
|
||||
end
|
||||
|
||||
WS1 -->|캔들 마감 이벤트| DS
|
||||
@@ -152,12 +152,16 @@ Combined WebSocket
|
||||
| EMA | (9, 21, 50) | 추세 방향 (정배열/역배열) |
|
||||
| Stochastic RSI | (14, 14, 3, 3) | 단기 과매수/과매도 |
|
||||
| ATR | length=14 | 변동성 측정 → SL/TP 계산에 사용 |
|
||||
| ADX | length=14 | 추세 강도 측정 → 횡보장 필터 (ADX < 25 시 진입 차단) |
|
||||
| Volume MA | length=20 | 거래량 급증 감지 |
|
||||
|
||||
**신호 생성 로직 (가중치 합산):**
|
||||
**신호 생성 로직 (ADX 필터 + 가중치 합산):**
|
||||
|
||||
```
|
||||
롱 신호 점수:
|
||||
[1단계] ADX 횡보장 필터:
|
||||
ADX < 25 → 즉시 HOLD 반환 (추세 부재로 진입 차단)
|
||||
|
||||
[2단계] 롱 신호 점수:
|
||||
RSI < 35 → +1
|
||||
MACD 골든크로스 (전봉→현봉) → +2 ← 강한 신호
|
||||
종가 < 볼린저 하단 → +1
|
||||
@@ -313,13 +317,13 @@ RSI: 32.50 | MACD Hist: -0.000123 | ATR: 0.023400
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["주말 수동 트리거\ntune_hyperparams.py\n(Optuna 50 trials, ~30분)"]
|
||||
B["결과 검토\ntune_results_YYYYMMDD.json\nBest AUC vs Baseline 비교"]
|
||||
C{"개선폭 충분?\n(AUC +0.01 이상\n폴드 분산 낮음)"}
|
||||
D["active_lgbm_params.json\n업데이트\n(Active Config 패턴)"]
|
||||
E["새벽 2시 크론탭\ntrain_and_deploy.sh\n(데이터 수집 → 학습 → 배포)"]
|
||||
F["LXC 서버\nlgbm_filter.pkl 교체"]
|
||||
G["봇 핫리로드\n다음 캔들 mtime 감지\n→ 자동 리로드"]
|
||||
A["주말 수동 트리거<br/>tune_hyperparams.py<br/>(Optuna 50 trials, ~30분)"]
|
||||
B["결과 검토<br/>tune_results_YYYYMMDD.json<br/>Best AUC vs Baseline 비교"]
|
||||
C{"개선폭 충분?<br/>(AUC +0.01 이상<br/>폴드 분산 낮음)"}
|
||||
D["active_lgbm_params.json<br/>업데이트<br/>(Active Config 패턴)"]
|
||||
E["새벽 2시 크론탭<br/>train_and_deploy.sh<br/>(데이터 수집 → 학습 → 배포)"]
|
||||
F["LXC 서버<br/>lgbm_filter.pkl 교체"]
|
||||
G["봇 핫리로드<br/>다음 캔들 mtime 감지<br/>→ 자동 리로드"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
@@ -465,7 +469,7 @@ sequenceDiagram
|
||||
BOT->>RM: is_trading_allowed() [일일 손실 한도 확인]
|
||||
|
||||
BOT->>IND: calculate_all(xrp_df) [지표 계산]
|
||||
IND-->>BOT: df_with_indicators (RSI, MACD, BB, EMA, StochRSI, ATR)
|
||||
IND-->>BOT: df_with_indicators (RSI, MACD, BB, EMA, StochRSI, ATR, ADX)
|
||||
BOT->>IND: get_signal(df) [신호 생성]
|
||||
IND-->>BOT: "LONG" | "SHORT" | "HOLD"
|
||||
|
||||
@@ -543,7 +547,7 @@ sequenceDiagram
|
||||
|
||||
### 5.1 테스트 파일 구성
|
||||
|
||||
`tests/` 폴더에 14개 테스트 파일, 총 **80개 이상의 테스트 케이스**가 작성되어 있습니다.
|
||||
`tests/` 폴더에 13개 테스트 파일, 총 **83개의 테스트 케이스**가 작성되어 있습니다.
|
||||
|
||||
```bash
|
||||
pytest tests/ -v # 전체 실행
|
||||
@@ -554,13 +558,13 @@ bash scripts/run_tests.sh # 래퍼 스크립트 실행
|
||||
|
||||
| 테스트 파일 | 대상 모듈 | 테스트 케이스 | 주요 검증 항목 |
|
||||
|------------|----------|:------------:|--------------|
|
||||
| `test_bot.py` | `src/bot.py` | 10 | 반대 시그널 재진입 흐름, ML 차단 시 재진입 스킵, OI/펀딩비 피처 전달, OI 변화율 계산 |
|
||||
| `test_indicators.py` | `src/indicators.py` | 4 | RSI 범위(0~100), MACD 컬럼 존재, 볼린저 밴드 상하단 대소관계, 신호 반환값 유효성 |
|
||||
| `test_bot.py` | `src/bot.py` | 11 | 반대 시그널 재진입 흐름, ML 차단 시 재진입 스킵, OI/펀딩비 피처 전달, OI 변화율 계산 |
|
||||
| `test_indicators.py` | `src/indicators.py` | 7 | RSI 범위(0~100), MACD 컬럼 존재, 볼린저 밴드 상하단 대소관계, 신호 반환값 유효성, ADX 컬럼 존재, ADX<25 횡보장 차단, ADX NaN 폴스루 |
|
||||
| `test_ml_features.py` | `src/ml_features.py` | 11 | 23개 피처 수, BTC/ETH 포함 시 피처 수, RS 분모 0 처리, NaN 없음, side 인코딩, OI/펀딩비 파라미터 반영 |
|
||||
| `test_ml_filter.py` | `src/ml_filter.py` | 5 | 모델 없을 때 폴백 허용, 임계값 이상/미만 판단, 핫리로드 후 상태 변화 |
|
||||
| `test_risk_manager.py` | `src/risk_manager.py` | 8 | 일일 손실 한도 초과 차단, 최대 포지션 수 제한, 동적 증거금 비율 상한/하한 클램핑 |
|
||||
| `test_exchange.py` | `src/exchange.py` | 8 | 수량 계산(기본/최소명목금액/잔고0), OI·펀딩비 조회 정상/오류 시 반환값 |
|
||||
| `test_data_stream.py` | `src/data_stream.py` | 5 | 3심볼 버퍼 존재, 빈 버퍼 None 반환, 캔들 파싱, 마감 캔들 콜백 호출, 프리로드 200개 |
|
||||
| `test_data_stream.py` | `src/data_stream.py` | 6 | 3심볼 버퍼 존재, 빈 버퍼 None 반환, 캔들 파싱, 마감 캔들 콜백 호출, 프리로드 200개 |
|
||||
| `test_label_builder.py` | `src/label_builder.py` | 4 | LONG TP 도달 → 1, LONG SL 도달 → 0, 미결 → None, SHORT TP 도달 → 1 |
|
||||
| `test_dataset_builder.py` | `src/dataset_builder.py` | 9 | DataFrame 반환, 필수 컬럼 존재, 레이블 이진값, BTC/ETH 포함 시 23개 피처, inf/NaN 없음, OI nan 마스킹, RS 분모 0 처리 |
|
||||
| `test_mlx_filter.py` | `src/mlx_filter.py` | 5 | GPU 디바이스 확인, 학습 전 예측 형태, 학습 후 유효 확률, NaN 피처 처리, 저장/로드 후 동일 예측 |
|
||||
@@ -579,6 +583,7 @@ bash scripts/run_tests.sh # 래퍼 스크립트 실행
|
||||
|------|:----------:|:--------------:|------|
|
||||
| 기술 지표 계산 (RSI/MACD/BB/EMA/StochRSI) | ✅ | ✅ | `test_indicators` + `test_ml_features` |
|
||||
| 신호 생성 (가중치 합산) | ✅ | ✅ | `test_indicators` + `test_dataset_builder` |
|
||||
| ADX 횡보장 필터 (ADX < 25 차단) | ✅ | — | `test_indicators` (ADX 컬럼·차단·NaN 3개) |
|
||||
| ML 피처 추출 (23개) | ✅ | — | `test_ml_features` |
|
||||
| ML 필터 추론 (임계값 판단) | ✅ | — | `test_ml_filter` |
|
||||
| MLX 신경망 학습/저장/로드 | ✅ | — | `test_mlx_filter` (Apple Silicon 전용) |
|
||||
|
||||
Reference in New Issue
Block a user