feat: log technical indicators on entry and add dashboard docs to README
- Include RSI, MACD_H, ATR in bot entry log so the log parser can extract and store them in the trades DB for dashboard display - Update log parser regex and _handle_entry() to persist indicator values - Add dashboard section to README (tech stack, screens, API endpoints) - Add dashboard/ directory to project structure in README Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
43
README.md
43
README.md
@@ -57,6 +57,9 @@ cointrader/
|
||||
│ ├── tune_hyperparams.py # Optuna 하이퍼파라미터 자동 탐색 (수동 트리거)
|
||||
│ ├── deploy_model.sh # 모델 파일 LXC 서버 전송
|
||||
│ └── run_tests.sh # 전체 테스트 실행
|
||||
├── dashboard/
|
||||
│ ├── api/ # FastAPI 백엔드 (로그 파서 + REST API)
|
||||
│ └── ui/ # React 프론트엔드 (Vite + Recharts)
|
||||
├── models/ # 학습된 모델 저장 (.pkl / .onnx)
|
||||
├── data/ # 과거 데이터 캐시 (.parquet)
|
||||
├── logs/ # 로그 파일
|
||||
@@ -241,6 +244,46 @@ MLX로 학습한 모델은 ONNX 포맷으로 export되어 Linux 서버에서 `on
|
||||
|
||||
---
|
||||
|
||||
## 대시보드
|
||||
|
||||
봇 로그를 실시간으로 파싱하여 거래 내역, 수익 통계, 차트를 웹에서 조회할 수 있는 모니터링 대시보드입니다.
|
||||
|
||||
### 기술 스택
|
||||
|
||||
- **프론트엔드**: React 18 + Vite + Recharts, Nginx 정적 서빙
|
||||
- **백엔드**: FastAPI + SQLite, 로그 파서(5초 주기 폴링)
|
||||
- **배포**: Docker Compose 3컨테이너 (`dashboard-ui`, `dashboard-api`, `cointrader`)
|
||||
|
||||
### 주요 화면
|
||||
|
||||
| 탭 | 내용 |
|
||||
|----|------|
|
||||
| **Overview** | 총 수익, 승률, 거래 수, 최대 수익/손실 KPI + 일별 PnL 차트 + 누적 수익 곡선 |
|
||||
| **Trades** | 전체 거래 내역 — 진입/청산가, 방향, 레버리지, 기술 지표(RSI, MACD, ATR), SL/TP, 순익 상세 |
|
||||
| **Chart** | XRP/USDT 15분봉 가격 차트 + RSI 지표 + ADX 추세 강도 |
|
||||
|
||||
### API 엔드포인트
|
||||
|
||||
| 엔드포인트 | 설명 |
|
||||
|-----------|------|
|
||||
| `GET /api/position` | 현재 포지션 + 봇 상태 |
|
||||
| `GET /api/trades` | 청산 거래 내역 (페이지네이션) |
|
||||
| `GET /api/daily` | 일별 PnL 집계 |
|
||||
| `GET /api/stats` | 전체 통계 (총 거래, 승률, 수수료 등) |
|
||||
| `GET /api/candles` | 최근 캔들 + 기술 지표 |
|
||||
| `GET /api/health` | 헬스 체크 |
|
||||
| `POST /api/reset` | DB 초기화 + 로그 파서 재시작 |
|
||||
|
||||
### 실행
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
대시보드는 `http://<서버IP>:8080`에서 접속할 수 있습니다. 봇 로그를 읽기 전용으로 마운트하여 봇 코드를 수정하지 않는 디커플드 설계입니다.
|
||||
|
||||
---
|
||||
|
||||
## 테스트
|
||||
|
||||
```bash
|
||||
|
||||
@@ -46,7 +46,7 @@ PATTERNS = {
|
||||
r".*기존 포지션 복구: (?P<direction>\w+) \| 진입가=(?P<entry_price>[\d.]+) \| 수량=(?P<qty>[\d.]+)"
|
||||
),
|
||||
|
||||
# SHORT 진입: 가격=1.3940, 수량=86.8, SL=1.4040, TP=1.3840
|
||||
# SHORT 진입: 가격=1.3940, 수량=86.8, SL=1.4040, TP=1.3840, RSI=42.31, MACD_H=-0.001234, ATR=0.005678
|
||||
"entry": re.compile(
|
||||
r"(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})"
|
||||
r".*(?P<direction>SHORT|LONG) 진입: "
|
||||
@@ -54,6 +54,9 @@ PATTERNS = {
|
||||
r"수량=(?P<qty>[\d.]+), "
|
||||
r"SL=(?P<sl>[\d.]+), "
|
||||
r"TP=(?P<tp>[\d.]+)"
|
||||
r"(?:, RSI=(?P<rsi>[\d.]+))?"
|
||||
r"(?:, MACD_H=(?P<macd_hist>[+\-\d.]+))?"
|
||||
r"(?:, ATR=(?P<atr>[\d.]+))?"
|
||||
),
|
||||
|
||||
# 청산 감지(MANUAL): exit=1.3782, rp=+2.9859, commission=0.0598, net_pnl=+2.9261
|
||||
@@ -286,7 +289,7 @@ class LogParser:
|
||||
)
|
||||
return
|
||||
|
||||
# 포지션 진입: SHORT 진입: 가격=X, 수량=Y, SL=Z, TP=W
|
||||
# 포지션 진입: SHORT 진입: 가격=X, 수량=Y, SL=Z, TP=W, RSI=R, MACD_H=M, ATR=A
|
||||
m = PATTERNS["entry"].search(line)
|
||||
if m:
|
||||
self._handle_entry(
|
||||
@@ -296,6 +299,9 @@ class LogParser:
|
||||
qty=float(m.group("qty")),
|
||||
sl=float(m.group("sl")),
|
||||
tp=float(m.group("tp")),
|
||||
rsi=float(m.group("rsi")) if m.group("rsi") else None,
|
||||
macd_hist=float(m.group("macd_hist")) if m.group("macd_hist") else None,
|
||||
atr=float(m.group("atr")) if m.group("atr") else None,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -381,7 +387,8 @@ class LogParser:
|
||||
|
||||
# ── 포지션 진입 핸들러 ───────────────────────────────────────
|
||||
def _handle_entry(self, ts, direction, entry_price, qty,
|
||||
leverage=None, sl=None, tp=None, is_recovery=False):
|
||||
leverage=None, sl=None, tp=None, is_recovery=False,
|
||||
rsi=None, macd_hist=None, atr=None):
|
||||
if leverage is None:
|
||||
leverage = self._bot_config.get("leverage", 10)
|
||||
|
||||
@@ -405,11 +412,12 @@ class LogParser:
|
||||
|
||||
cur = self.conn.execute(
|
||||
"""INSERT INTO trades(symbol, direction, entry_time, entry_price,
|
||||
quantity, leverage, sl, tp, status, extra)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?)""",
|
||||
quantity, leverage, sl, tp, status, extra, rsi, macd_hist, atr)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(self._bot_config.get("symbol", "XRPUSDT"), direction, ts,
|
||||
entry_price, qty, leverage, sl, tp, "OPEN",
|
||||
json.dumps({"recovery": is_recovery})),
|
||||
json.dumps({"recovery": is_recovery}),
|
||||
rsi, macd_hist, atr),
|
||||
)
|
||||
self.conn.commit()
|
||||
self._current_position = {
|
||||
|
||||
@@ -205,7 +205,10 @@ class TradingBot:
|
||||
)
|
||||
logger.success(
|
||||
f"{signal} 진입: 가격={price}, 수량={quantity}, "
|
||||
f"SL={stop_loss:.4f}, TP={take_profit:.4f}"
|
||||
f"SL={stop_loss:.4f}, TP={take_profit:.4f}, "
|
||||
f"RSI={signal_snapshot['rsi']:.2f}, "
|
||||
f"MACD_H={signal_snapshot['macd_hist']:.6f}, "
|
||||
f"ATR={signal_snapshot['atr']:.6f}"
|
||||
)
|
||||
|
||||
sl_side = "SELL" if signal == "LONG" else "BUY"
|
||||
|
||||
Reference in New Issue
Block a user