From 2b92fd0dce2e3a00ea35cdd073af89c2630e2623 Mon Sep 17 00:00:00 2001 From: 21in7 Date: Wed, 4 Mar 2026 20:43:05 +0900 Subject: [PATCH] feat: implement testnet and demo trading setup for XRPUSDT with 125x leverage - Added support for demo trading on Binance Futures with a new configuration for 1-minute candles and 125x leverage. - Updated various components including Config, Exchange, DataStream, UserDataStream, and Bot to handle demo and testnet flags. - Enhanced the training pipeline to collect 1-minute data and adjusted the lookahead for model training. - Updated environment variables in .env and .env.example to include demo settings. This commit lays the groundwork for testing ML-based automated trading strategies in a controlled environment. --- .../2026-03-03-testnet-1m-125x-design.md | 72 +++ docs/plans/2026-03-03-testnet-1m-125x-plan.md | 426 ++++++++++++++++++ scripts/train_and_deploy.sh | 21 +- 3 files changed, 516 insertions(+), 3 deletions(-) create mode 100644 docs/plans/2026-03-03-testnet-1m-125x-design.md create mode 100644 docs/plans/2026-03-03-testnet-1m-125x-plan.md diff --git a/docs/plans/2026-03-03-testnet-1m-125x-design.md b/docs/plans/2026-03-03-testnet-1m-125x-design.md new file mode 100644 index 0000000..ae818bc --- /dev/null +++ b/docs/plans/2026-03-03-testnet-1m-125x-design.md @@ -0,0 +1,72 @@ +# Demo 1분봉 125x 트레이딩 설계 + +**날짜**: 2026-03-03 +**상태**: Approved (testnet → demo 변경 반영) + +## 목적 + +바이낸스 선물 데모(`demo-fapi.binance.com`)에서 XRPUSDT 1분봉, 125x 레버리지로 ML 기반 자동매매를 테스트한다. +로컬 맥미니의 워크트리에서 격리하여 메인 코드베이스에 영향 없이 실험한다. + +## 환경 설정 + +| 항목 | 값 | +|------|-----| +| 네트워크 | Binance Futures Demo (`demo-fapi.binance.com`) | +| 심볼 | XRPUSDT | +| 타임프레임 | 1m (1분봉) | +| 레버리지 | 125x | +| ML Lookahead | 60캔들 (1시간) | +| 작업 방식 | git worktree (격리) | +| 실행 환경 | 로컬 맥미니 (서버 배포 없음) | + +## 코드 변경 사항 + +### 1. Config (`src/config.py`) + +- `demo: bool` 플래그 추가 +- `BINANCE_DEMO=true`이면 `BINANCE_DEMO_API_KEY/SECRET` 사용 +- `INTERVAL` 환경변수 추가 (기본값 `15m` → 데모에서 `1m`) + +### 2. Exchange (`src/exchange.py`) + +- `config.demo=True`이면 Client의 `FUTURES_URL`을 `demo-fapi.binance.com`으로 오버라이드 +- `testnet=True` 미사용 (demo 엔드포인트는 라이브러리 미지원) + +### 3. DataStream (`src/data_stream.py`) + +- `AsyncClient.create()` 후 demo이면 `FUTURES_URL` 오버라이드 +- interval을 Config에서 받도록 수정 + +### 4. UserDataStream (`src/user_data_stream.py`) + +- `AsyncClient.create()` 후 demo이면 `FUTURES_URL` 오버라이드 + +### 5. Bot (`src/bot.py`) + +- `demo` 플래그를 각 stream/exchange에 전달 + +### 6. 학습 파이프라인 + +- `fetch_history.py`로 1분봉 데이터 수집 (30일+, 프로덕션 API 사용) +- `dataset_builder.py`에서 `LOOKAHEAD=60` (1시간) +- SL/TP: ATR 기반이므로 자동 적응 +- LightGBM 학습 → 로컬 models/ 저장 (서버 배포 없음) + +### 7. 환경변수 (`.env`) + +``` +BINANCE_DEMO=true +BINANCE_DEMO_API_KEY= +BINANCE_DEMO_API_SECRET= +INTERVAL=1m +LEVERAGE=125 +``` + +## 변경하지 않는 것 + +- 지표 계산 로직 (RSI, MACD, BB, EMA, StochRSI, ATR, ADX) — 타임프레임 독립 +- ML 피처 추출 — 캔들 데이터 기반, 그대로 동작 +- 리스크 매니저 — 비율 기반, 자동 적응 +- Discord 알림 — 그대로 사용 +- ONNX 변환 파이프라인 — 동일 diff --git a/docs/plans/2026-03-03-testnet-1m-125x-plan.md b/docs/plans/2026-03-03-testnet-1m-125x-plan.md new file mode 100644 index 0000000..88be7f2 --- /dev/null +++ b/docs/plans/2026-03-03-testnet-1m-125x-plan.md @@ -0,0 +1,426 @@ +# Testnet 1분봉 125x 트레이딩 Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** 바이낸스 테스트넷에서 XRPUSDT 1분봉, 125x 레버리지로 ML 기반 자동매매를 실행한다. + +**Architecture:** Config에 `testnet` 플래그를 추가하고, Exchange/DataStream/UserDataStream에 `testnet=True`를 전달한다. 학습 파이프라인은 LOOKAHEAD=60(1시간)으로 조정하여 1분봉 데이터로 새 모델을 학습한다. + +**Tech Stack:** python-binance (testnet=True), LightGBM, asyncio + +--- + +## Task 1: Config에 testnet 지원 추가 + +**Files:** +- Modify: `src/config.py:8-33` +- Test: `tests/test_config.py` (기존 테스트 수정 필요시) + +**Step 1: Config에 testnet, interval 필드 추가** + +`src/config.py`에서 `Config` dataclass에 `testnet`, `interval` 필드를 추가하고, `__post_init__`에서 `BINANCE_TESTNET=true`이면 테스트넷 키를 사용하도록 변경: + +```python +@dataclass +class Config: + api_key: str = "" + api_secret: str = "" + symbol: str = "XRPUSDT" + leverage: int = 10 + testnet: bool = False + interval: str = "15m" + max_positions: int = 3 + stop_loss_pct: float = 0.015 # 1.5% + take_profit_pct: float = 0.045 # 4.5% (3:1 RR) + trailing_stop_pct: float = 0.01 # 1% + discord_webhook_url: str = "" + margin_max_ratio: float = 0.50 + margin_min_ratio: float = 0.20 + margin_decay_rate: float = 0.0006 + ml_threshold: float = 0.55 + + def __post_init__(self): + self.testnet = os.getenv("BINANCE_TESTNET", "").lower() in ("true", "1", "yes") + self.interval = os.getenv("INTERVAL", "15m") + + if self.testnet: + self.api_key = os.getenv("BINANCE_TESTNET_API_KEY", "") + self.api_secret = os.getenv("BINANCE_TESTNET_API_SECRET", "") + else: + self.api_key = os.getenv("BINANCE_API_KEY", "") + self.api_secret = os.getenv("BINANCE_API_SECRET", "") + + self.symbol = os.getenv("SYMBOL", "XRPUSDT") + self.leverage = int(os.getenv("LEVERAGE", "10")) + self.discord_webhook_url = os.getenv("DISCORD_WEBHOOK_URL", "") + self.margin_max_ratio = float(os.getenv("MARGIN_MAX_RATIO", "0.50")) + self.margin_min_ratio = float(os.getenv("MARGIN_MIN_RATIO", "0.20")) + self.margin_decay_rate = float(os.getenv("MARGIN_DECAY_RATE", "0.0006")) + self.ml_threshold = float(os.getenv("ML_THRESHOLD", "0.55")) +``` + +**Step 2: 테스트 실행** + +Run: `pytest tests/ -v --tb=short -x` +Expected: 기존 테스트 모두 PASS (testnet 미설정 시 기존 동작 유지) + +**Step 3: Commit** + +```bash +git add src/config.py +git commit -m "feat: add testnet and interval support to Config" +``` + +--- + +## Task 2: Exchange에 testnet 전달 + +**Files:** +- Modify: `src/exchange.py:9-14` + +**Step 1: Client 생성자에 testnet 전달** + +`src/exchange.py`에서 `BinanceFuturesClient.__init__`을 수정: + +```python +class BinanceFuturesClient: + def __init__(self, config: Config): + self.config = config + self.client = Client( + api_key=config.api_key, + api_secret=config.api_secret, + testnet=config.testnet, + ) +``` + +**Step 2: 테스트 실행** + +Run: `pytest tests/test_exchange.py -v --tb=short -x` +Expected: PASS + +**Step 3: Commit** + +```bash +git add src/exchange.py +git commit -m "feat: pass testnet flag to Binance Client" +``` + +--- + +## Task 3: DataStream에 testnet 전달 + +**Files:** +- Modify: `src/data_stream.py:78-82,185-189` + +**Step 1: KlineStream.start()에 testnet 파라미터 추가** + +`src/data_stream.py`의 `KlineStream.start()` (line 78)을 수정: + +```python + async def start(self, api_key: str, api_secret: str, testnet: bool = False): + client = await AsyncClient.create( + api_key=api_key, + api_secret=api_secret, + testnet=testnet, + ) +``` + +**Step 2: MultiSymbolStream.start()에 testnet 파라미터 추가** + +`src/data_stream.py`의 `MultiSymbolStream.start()` (line 185)을 수정: + +```python + async def start(self, api_key: str, api_secret: str, testnet: bool = False): + client = await AsyncClient.create( + api_key=api_key, + api_secret=api_secret, + testnet=testnet, + ) +``` + +**Step 3: 테스트 실행** + +Run: `pytest tests/test_data_stream.py -v --tb=short -x` +Expected: PASS (testnet 기본값 False이므로 기존 동작 유지) + +**Step 4: Commit** + +```bash +git add src/data_stream.py +git commit -m "feat: pass testnet flag to AsyncClient in data streams" +``` + +--- + +## Task 4: UserDataStream에 testnet 전달 + +**Files:** +- Modify: `src/user_data_stream.py:28-33` + +**Step 1: start()에 testnet 파라미터 추가** + +```python + async def start(self, api_key: str, api_secret: str, testnet: bool = False) -> None: + """User Data Stream 메인 루프 — 봇 종료 시까지 실행.""" + client = await AsyncClient.create( + api_key=api_key, + api_secret=api_secret, + testnet=testnet, + ) +``` + +**Step 2: 테스트 실행** + +Run: `pytest tests/test_user_data_stream.py -v --tb=short -x` +Expected: PASS + +**Step 3: Commit** + +```bash +git add src/user_data_stream.py +git commit -m "feat: pass testnet flag to AsyncClient in user data stream" +``` + +--- + +## Task 5: Bot에서 testnet/interval 전달 + +**Files:** +- Modify: `src/bot.py:27-31,300-322` + +**Step 1: MultiSymbolStream에 config.interval 전달** + +`src/bot.py` line 27-31을 수정: + +```python + self.stream = MultiSymbolStream( + symbols=[config.symbol, "BTCUSDT", "ETHUSDT"], + interval=config.interval, + on_candle=self._on_candle_closed, + ) +``` + +**Step 2: run()에서 testnet 전달** + +`src/bot.py` line 300-322의 `run()` 메서드에서 stream.start() 호출 시 testnet 전달: + +```python + async def run(self): + logger.info(f"봇 시작: {self.config.symbol}, 레버리지 {self.config.leverage}x, 테스트넷={self.config.testnet}") + await self._recover_position() + balance = await self.exchange.get_balance() + self.risk.set_base_balance(balance) + logger.info(f"기준 잔고 설정: {balance:.2f} USDT (동적 증거금 비율 기준점)") + + user_stream = UserDataStream( + symbol=self.config.symbol, + on_order_filled=self._on_position_closed, + ) + + await asyncio.gather( + self.stream.start( + api_key=self.config.api_key, + api_secret=self.config.api_secret, + testnet=self.config.testnet, + ), + user_stream.start( + api_key=self.config.api_key, + api_secret=self.config.api_secret, + testnet=self.config.testnet, + ), + self._position_monitor(), + ) +``` + +**Step 3: 테스트 실행** + +Run: `pytest tests/test_bot.py -v --tb=short -x` +Expected: PASS + +**Step 4: Commit** + +```bash +git add src/bot.py +git commit -m "feat: pass testnet and interval from config to streams" +``` + +--- + +## Task 6: .env 설정 및 전체 테스트 + +**Files:** +- Modify: `.env` +- Modify: `.env.example` + +**Step 1: .env.example 업데이트** + +`.env.example`에 새 변수 추가: + +``` +BINANCE_API_KEY= +BINANCE_API_SECRET= +BINANCE_TESTNET=false +BINANCE_TESTNET_API_KEY= +BINANCE_TESTNET_API_SECRET= +SYMBOL=XRPUSDT +LEVERAGE=10 +INTERVAL=15m +RISK_PER_TRADE=0.02 +DISCORD_WEBHOOK_URL= +ML_THRESHOLD=0.55 +``` + +**Step 2: 워크트리의 .env에 테스트넷 설정** + +`.env` 파일에 테스트넷 키와 설정 적용: + +``` +BINANCE_TESTNET=true +BINANCE_TESTNET_API_KEY=<사용자의_테스트넷_키> +BINANCE_TESTNET_API_SECRET=<사용자의_테스트넷_시크릿> +SYMBOL=XRPUSDT +LEVERAGE=125 +INTERVAL=1m +ML_THRESHOLD=0.55 +``` + +**Step 3: 전체 테스트 실행** + +Run: `bash scripts/run_tests.sh` +Expected: 모든 테스트 PASS + +**Step 4: Commit** + +```bash +git add .env.example +git commit -m "feat: add testnet and interval env vars to .env.example" +``` + +--- + +## Task 7: 학습 파이프라인 — LOOKAHEAD 조정 및 1분봉 데이터 수집 + +**Files:** +- Modify: `src/dataset_builder.py:14` (LOOKAHEAD 변경) +- Modify: `scripts/train_model.py:56` (LOOKAHEAD 변경) +- Modify: `scripts/train_and_deploy.sh:32-50` (1분봉 데이터 경로) + +**Step 1: dataset_builder.py LOOKAHEAD 변경** + +`src/dataset_builder.py` line 14: + +```python +# 변경 전: +LOOKAHEAD = 24 # 15분봉 × 24 = 6시간 뷰 + +# 변경 후: +LOOKAHEAD = 60 # 1분봉 × 60 = 1시간 뷰 +``` + +**Step 2: train_model.py LOOKAHEAD 변경** + +`scripts/train_model.py` line 56: + +```python +# 변경 전: +LOOKAHEAD = 24 # 15분봉 × 24 = 6시간 (dataset_builder.py와 동기화) + +# 변경 후: +LOOKAHEAD = 60 # 1분봉 × 60 = 1시간 (dataset_builder.py와 동기화) +``` + +**Step 3: train_and_deploy.sh 수정 — 1분봉 파이프라인** + +`scripts/train_and_deploy.sh`의 데이터 경로와 수집 파라미터를 1분봉으로 변경: + +```bash +# line 32: 파일명 변경 +PARQUET_FILE="data/combined_1m.parquet" + +# line 46-50: --interval 1m으로 변경 +python scripts/fetch_history.py \ + --symbols XRPUSDT BTCUSDT ETHUSDT \ + --interval 1m \ + --days "$FETCH_DAYS" \ + $UPSERT_FLAG \ + --output "$PARQUET_FILE" + +# line 57, 60: --data 경로 변경 +python scripts/train_mlx_model.py --data data/combined_1m.parquet --decay "$DECAY" +# ... +python scripts/train_model.py --data data/combined_1m.parquet --decay "$DECAY" + +# walk-forward 섹션도 동일하게 --data data/combined_1m.parquet로 변경 +``` + +**Step 4: Commit** + +```bash +git add src/dataset_builder.py scripts/train_model.py scripts/train_and_deploy.sh +git commit -m "feat: adjust LOOKAHEAD to 60 for 1m candles, update training pipeline" +``` + +--- + +## Task 8: 1분봉 데이터 수집 + +**Step 1: 1분봉 데이터 수집 (30일)** + +Run: +```bash +python scripts/fetch_history.py \ + --symbols XRPUSDT BTCUSDT ETHUSDT \ + --interval 1m \ + --days 30 \ + --no-oi \ + --no-upsert \ + --output data/combined_1m.parquet +``` + +Expected: `data/combined_1m.parquet` 생성 (약 43,000행 × 15컬럼) + +> Note: 테스트넷 학습용이므로 OI/펀딩비는 건너뜀 (--no-oi). 1분봉 30일 데이터면 약 43,200개 캔들. + +--- + +## Task 9: ML 모델 학습 + +**Step 1: LightGBM 모델 학습** + +Run: +```bash +python scripts/train_model.py --data data/combined_1m.parquet --decay 2.0 +``` + +Expected: `models/lgbm_filter.pkl` 생성, AUC 출력 + +**Step 2: 학습 결과 확인** + +학습 결과의 AUC가 0.50 이상인지 확인. 모델이 생성되었는지 확인: + +Run: `ls -la models/lgbm_filter.pkl` +Expected: 파일 존재 + +--- + +## Task 10: 테스트넷 봇 실행 + +**Step 1: 최종 확인** + +`.env`에 테스트넷 설정이 올바른지 확인: +- `BINANCE_TESTNET=true` +- `BINANCE_TESTNET_API_KEY` 설정됨 +- `BINANCE_TESTNET_API_SECRET` 설정됨 +- `LEVERAGE=125` +- `INTERVAL=1m` + +**Step 2: 봇 실행** + +Run: `python main.py` + +Expected output: +``` +봇 시작: XRPUSDT, 레버리지 125x, 테스트넷=True +``` + +봇이 정상 시작되면 1분봉 캔들을 수신하고 ML 필터를 통해 거래 신호를 처리한다. diff --git a/scripts/train_and_deploy.sh b/scripts/train_and_deploy.sh index 6428334..e6a2fa3 100755 --- a/scripts/train_and_deploy.sh +++ b/scripts/train_and_deploy.sh @@ -11,6 +11,10 @@ set -euo pipefail +# cron 환경에서 sysctl 경로 누락 방지 +export PATH="/usr/sbin:/usr/bin:/bin:/opt/homebrew/bin:$PATH" +export LOKY_MAX_CPU_COUNT="${LOKY_MAX_CPU_COUNT:-$(sysctl -n hw.physicalcpu 2>/dev/null || echo 4)}" + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" @@ -31,6 +35,12 @@ mkdir -p data PARQUET_FILE="data/combined_15m.parquet" +echo "" +echo "========================================" +echo " 학습 파이프라인 시작: $(date '+%Y-%m-%d %H:%M:%S %Z')" +echo "========================================" +echo "" + echo "=== [1/3] 데이터 수집 (XRP + BTC + ETH 3심볼 + OI/펀딩비) ===" if [ ! -f "$PARQUET_FILE" ]; then echo " [최초 실행] 기존 데이터 없음 → 1년치(365일) 전체 수집 (--no-upsert)" @@ -49,9 +59,14 @@ python scripts/fetch_history.py \ $UPSERT_FLAG \ --output "$PARQUET_FILE" -echo "" -echo "=== [2/3] 모델 학습 (23개 피처: XRP 13 + BTC/ETH 8 + OI/펀딩비 2) ===" DECAY="${TIME_WEIGHT_DECAY:-2.0}" + +echo "" +echo "=== [1.5/3] OI 파생 피처 A/B 비교 ===" +python scripts/train_model.py --compare --data "$PARQUET_FILE" --decay "$DECAY" || true + +echo "" +echo "=== [2/3] 모델 학습 (26개 피처: XRP 13 + BTC/ETH 8 + OI/펀딩비 2 + OI파생 2 + ADX) ===" if [ "$BACKEND" = "mlx" ]; then echo " 백엔드: MLX (Apple Silicon GPU), decay=${DECAY}" python scripts/train_mlx_model.py --data data/combined_15m.parquet --decay "$DECAY" @@ -84,7 +99,7 @@ echo "=== [3/3] LXC 배포 ===" bash scripts/deploy_model.sh "$BACKEND" echo "" -echo "=== 전체 파이프라인 완료 ===" +echo "=== 전체 파이프라인 완료: $(date '+%Y-%m-%d %H:%M:%S %Z') ===" echo "" echo "봇 재시작이 필요하면:" echo " ssh root@10.1.10.24 'cd /root/cointrader && docker compose restart cointrader'"