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.
This commit is contained in:
72
docs/plans/2026-03-03-testnet-1m-125x-design.md
Normal file
72
docs/plans/2026-03-03-testnet-1m-125x-design.md
Normal file
@@ -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=<demo_key>
|
||||
BINANCE_DEMO_API_SECRET=<demo_secret>
|
||||
INTERVAL=1m
|
||||
LEVERAGE=125
|
||||
```
|
||||
|
||||
## 변경하지 않는 것
|
||||
|
||||
- 지표 계산 로직 (RSI, MACD, BB, EMA, StochRSI, ATR, ADX) — 타임프레임 독립
|
||||
- ML 피처 추출 — 캔들 데이터 기반, 그대로 동작
|
||||
- 리스크 매니저 — 비율 기반, 자동 적응
|
||||
- Discord 알림 — 그대로 사용
|
||||
- ONNX 변환 파이프라인 — 동일
|
||||
426
docs/plans/2026-03-03-testnet-1m-125x-plan.md
Normal file
426
docs/plans/2026-03-03-testnet-1m-125x-plan.md
Normal file
@@ -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 필터를 통해 거래 신호를 처리한다.
|
||||
Reference in New Issue
Block a user