From 4890abd9a66c4ee353fb8cc9cd35f1064e90a7c7 Mon Sep 17 00:00:00 2001 From: xavis Date: Sat, 13 Jun 2026 08:34:30 +0900 Subject: [PATCH] =?UTF-8?q?feat(spot):=20=ED=85=94=EB=A0=88=EA=B7=B8?= =?UTF-8?q?=EB=9E=A8=20=EC=B2=B4=EA=B2=B0=20=EC=95=8C=EB=A6=BC=20=EB=B0=8F?= =?UTF-8?q?=20README=20=EC=84=A4=EA=B3=84=20=EB=AC=B8=EC=84=9C=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 매수·매도 체결 시 TelegramNotifier로 가격·잔고·일 체결 건수를 발송하고, fractal_swing 3단계 운영 아키텍처를 README에 반영한다. Co-authored-by: Cursor --- .env.example | 4 +- README.md | 456 ++++++++++++++----------- src/deepcoin/config.py | 20 ++ src/deepcoin/notifications/__init__.py | 5 + src/deepcoin/notifications/telegram.py | 168 +++++++++ src/deepcoin/operations/runner.py | 46 +++ 6 files changed, 503 insertions(+), 196 deletions(-) create mode 100644 src/deepcoin/notifications/__init__.py create mode 100644 src/deepcoin/notifications/telegram.py diff --git a/.env.example b/.env.example index 6d0c7d0..84891bc 100644 --- a/.env.example +++ b/.env.example @@ -8,9 +8,11 @@ BITHUMB_API_CANDLE_COUNT=200 BITHUMB_MINUTE_INTERVALS=1,3,5,10,15,30,60,240 HTS_API_RETRY_SLEEP_SEC=0.5 -# --- 텔레그램 (선택) --- +# --- 텔레그램 (매매 체결 알림) --- COIN_TELEGRAM_BOT_TOKEN= COIN_TELEGRAM_CHAT_ID= +# 토큰·chat_id 설정 시 자동 on. 명시 off: OPS_TELEGRAM_ENABLED=false +# OPS_TELEGRAM_ENABLED=true # --- 거래 대상 --- SYMBOL=BTC diff --git a/README.md b/README.md index 49260fe..d3663e6 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ # DeepCoin -빗썸 KRW 마켓 암호화폐 캔들 데이터 수집 및 **현물**·**선물** 매매 전략 파이프라인. +빗썸 KRW 마켓 암호화폐 캔들 수집 및 **현물**·**선물** 매매 전략 파이프라인. -기본 전략 축: **3분봉 현물**, 최근 **10년** 데이터. `data/`·`docs/`는 **공통(common)** · **현물(spot)** · **선물(futures)** 세 유형으로 구분합니다. +- **기본 축:** 3분봉 현물 BTC, 최근 **10년** 캔들 (`DOWNLOAD_DAYS=3650`) +- **데이터·문서 분류:** `common` (공유) · `spot` (현물) · `futures` (선물) +- **현재 운영 전략:** `fractal_swing` + MTF off — paper/live tick 운영 구현 완료 ## 주요 기능 -- 빗썸 Public API(v1) 기반 분·일·주·월봉 캔들 수집 (1분봉 포함) -- SQLite 캔들 DB — 현물·선물 **공통** (`data/common/coins.db`) -- Ground Truth(GT) 기반 현물·선물 벤치마크·인과 기법 분석·(예정) 실거래 운영 +- 빗썸 Public API(v1) 분·일·주·월봉 캔들 수집 (11개 TF, 1분봉 포함) +- SQLite 공유 DB (`data/common/coins.db`) — 현물·선물·MTF 공용 +- Ground Truth(GT) 벤치마크 → 39종 인과 기법 분석 → **실거래 운영(paper/live)** +- 운영 tick: 캔들 증분 sync, 신호 tail 갱신, 슬리피지·일 체결 상한, 텔레그램 체결 알림 ## 요구사항 @@ -21,10 +24,10 @@ cd DeepCoin conda activate ncue # 또는 xavis pip install -r requirements.txt -cp .env.example .env +cp .env.example .env # API 키·텔레그램 등 로컬 설정 ``` -`.env` 권장값 (현물 3분봉·10년): +`.env` 핵심값 (현물 3분봉·10년·fractal 운영): ```env SYMBOL=BTC @@ -32,105 +35,142 @@ DB_PATH=data/common/coins.db DOWNLOAD_DAYS=3650 GT_INTERVAL_MIN=3 GT_LOOKBACK_DAYS=3650 +GT_INITIAL_CASH_KRW=200000 +GT_SIM_LOOKBACK_DAYS=1095 +OPS_TECHNIQUE_ID=fractal_swing +OPS_MTF_ENABLED=false +OPS_SLIPPAGE_RATE=0.0005 +OPS_DAILY_MAX_TRADES=100 ``` --- -## 폴더 구조 (공통 · 현물 · 선물) +## 설계 개요 -`data/`와 `docs/` 최상위는 동일하게 **common / spot / futures** 세 갈래입니다. +### 파이프라인 단계 -```text -DeepCoin/ -├── src/deepcoin/ # 소스 코드 -├── scripts/ # 파이프라인 스크립트 -│ -├── data/ -│ ├── common/ # 공통 — 현물·선물 공유 리소스 -│ │ └── coins.db # 캔들 OHLCV (유일한 공유 DB) -│ ├── spot/ # 현물 전용 데이터 -│ │ ├── ground_truth/ # 0단계 GT JSON -│ │ ├── techniques/ # 2단계 기법 결과 -│ │ ├── mtf/ # 2단계 MTF 규칙 -│ │ └── operations/ # 3단계 운영 상태 -│ └── futures/ # 선물 전용 데이터 -│ ├── ground_truth/ # 0단계 선물 GT JSON -│ ├── techniques/ # (예정) 2단계 -│ └── mtf/ # (예정) 2단계 -│ -└── docs/ - ├── live/ # live 운영 백테스트 매매 차트 - ├── common/ # 공통 문서 (예정) - ├── spot/ # 현물 리포트·차트 - │ ├── 0_ground_truth/ # 0단계 GT 차트 - │ ├── 1_simulation/ # 1단계 sim 차트 - │ ├── 2_analysis/ # 2단계 분석 리포트 - │ └── 3_operations/ # 3단계 운영 리포트·백테스트 - └── futures/ # 선물 리포트·차트 - ├── 0_ground_truth/ # 0단계 선물 GT 차트 - ├── 1_simulation/ # (예정) 1단계 - ├── 2_analysis/ # (예정) 2단계 - └── 3_operations/ # (예정) 3단계 -``` +| 단계 | 목적 | 미래 데이터 | 실거래 | +|------|------|-------------|--------| +| **common** | 캔들 DB 구축 | — | — | +| **spot 0단계** | GT v3 사후 최적 타점 (정답지) | 사용 (연구용) | 불가 | +| **spot 1단계** | GT 타점 완벽 추종 sim 상한선 | GT 자체가 사후 | 불가 | +| **spot 2단계** | 39종 인과 기법 평가·MTF 규칙 | 미사용 | 불가 | +| **spot 3단계** | paper/live tick 운영 | 미사용 | **가능** | +| **futures 0단계** | 현물 GT → 선물 롱·숏 마커 | — | — | -### 유형별 역할 - -| 유형 | `data/` | `docs/` | 설명 | -|------|---------|---------|------| -| **common** | `coins.db` | (예정) | 현물·선물이 공유하는 캔들 DB | -| **spot** | GT·기법·MTF JSON | 단계별 HTML·리포트 | 현물 파이프라인 산출물 | -| **futures** | 선물 GT JSON | 단계별 HTML·리포트 | 선물 파이프라인 산출물 | - -테이블명: `{SYMBOL}_{인터벌}` (예: `BTC_3`, `BTC_1440`) - ---- - -## 현물 파이프라인 전체 순서 +### 현물 3단계 운영 아키텍처 (fractal_swing) ```mermaid flowchart TD - A[common: 캔들 수집] --> B[spot 0단계: GT 타점] - B --> C[spot 1단계: GT sim] - C --> D[spot 2단계: 인과 기법] - D --> E[spot 3단계: 실거래 운영] - B --> F[futures 0단계: 선물 GT] + subgraph tick["3_run_operations.py tick (권장 180초)"] + A[sync_ops_candles
전 TF 증분 INSERT] --> B[generate_raw_signals
캐시 + tail 800봉 갱신] + B --> C[filter_signals_for_ops
MTF·TrendGate 선택] + C --> D[OperationsRunner
bar 단위 클러스터 체결] + D --> E{paper / live} + E -->|paper| F[PaperExecutor
모델 슬리피지 체결] + E -->|live| G[LiveExecutor
빗썸 시장가] + F --> H[TelegramNotifier
체결 알림] + G --> H + H --> I[state.json + ops_report.json] + end + J[3_run_filtered_backtest.py] --> K[simulate_gt_signals_pnl
동일 체결 규칙 3년 sim] ``` -| 순서 | 단계 | 유형 | 스크립트 | 산출물 | -|------|------|------|----------|--------| -| 0 | **사전** | common | `00_download.py` | `data/common/coins.db` | -| 1 | **0단계** | spot | `0_ground_truth.py` | `data/spot/ground_truth/`, `docs/spot/0_ground_truth/` | -| 2 | **1단계** | spot | `1_ground_truth_sim.py` | `docs/spot/1_simulation/` | -| 3 | **2단계** | spot | `2_run_*.py`, `2_run_stage2_all.sh` | `data/spot/techniques/`, `docs/spot/2_analysis/` | -| 4 | **3단계** | spot | `3_run_*.py`, `3_run_stage3_all.sh` | `data/spot/operations/`, `docs/spot/3_operations/` | -| — | **0단계** | futures | `0_ground_truth_futures.py` | `data/futures/ground_truth/`, `docs/futures/0_ground_truth/` | +**백테스트 vs live 정합:** 슬리피지·수수료·일 체결 상한·매수 상한(`max_buy_from_cash`)·클러스터 분할이 `pnl.py` ↔ `trade_engine` ↔ `executor`에서 동일 규칙을 사용합니다. -### 권장 실행 명령 (현물 + 선물 0단계) +### 운영 전략 비교 (2단계 결론 반영) + +| 전략 | 3년 sim (운영 조건) | 체결 빈도 | 현재 `.env` | +|------|---------------------|-----------|-------------| +| **fractal_swing** (MTF off) | **+1,873,140%** (슬리피지 0.05%, 일 100회) | 일 ~50회 매수 | **기본값** | +| fractal_swing ideal (2단계) | +7,560,826% (슬리피지 0, 상한 없음) | 일 ~52회 | 연구용 | +| composite_v3 + MTF on | +3.37% | 낮음 | `.env.example` 주석 참고 | + +상세 해석: [`docs/spot/2_analysis/stage2_final_summary.md`](docs/spot/2_analysis/stage2_final_summary.md) + +--- + +## 폴더 구조 + +```text +DeepCoin/ +├── src/deepcoin/ +│ ├── api/ # 빗썸 Public·Private REST +│ ├── data/ # 캔들 수집·DB·로더 +│ ├── ground_truth/ # GT 타점·sim·차트 +│ ├── techniques/ # 39종 인과 기법 +│ ├── mtf/ # MTF 피처·필터·규칙 +│ ├── evaluation/ # 2단계 리포트·인과 sim +│ ├── operations/ # 3단계 운영 (runner·executor·sync·backtest) +│ └── notifications/ # 텔레그램 체결 알림 +├── scripts/ # 단계별 CLI +│ +├── data/ +│ ├── common/coins.db # 공유 캔들 OHLCV +│ ├── spot/ +│ │ ├── ground_truth/ # 0단계 GT JSON +│ │ ├── techniques/ # 2단계 기법 결과 (fractal_swing.json 등) +│ │ ├── mtf/ # mtf_rules_v3.json +│ │ └── operations/ # fractal_ops_state.json +│ └── futures/ground_truth/ # 선물 GT JSON +│ +└── docs/ + ├── live/ # 운영 백테스트 매매 차트 (index.html) + ├── spot/ + │ ├── 0_ground_truth/ # GT 차트 HTML + │ ├── 1_simulation/ # 1단계 sim 차트 + │ ├── 2_analysis/ # 2단계 리포트·설계 가이드 + │ └── 3_operations/ # 운영·백테스트 JSON 리포트 + └── futures/0_ground_truth/ # 선물 GT 차트 +``` + +테이블명: `{SYMBOL}_{인터벌분}` (예: `BTC_3`, `BTC_1440`). 인터벌: 분봉=분 숫자, 일=`1440`, 주=`10080`, 월=`43200`. + +--- + +## 파이프라인 실행 순서 + +```mermaid +flowchart LR + A[00_download] --> B[0_ground_truth] + B --> C[1_ground_truth_sim] + C --> D[2_run_stage2_all] + D --> E[3_run_operations] + B --> F[0_ground_truth_futures] +``` + +| 순서 | 단계 | 스크립트 | 산출물 | +|------|------|----------|--------| +| 0 | common | `00_download.py` | `data/common/coins.db` | +| 1 | spot 0단계 | `0_ground_truth.py` | `data/spot/ground_truth/`, `docs/spot/0_ground_truth/` | +| 2 | spot 1단계 | `1_ground_truth_sim.py` | `docs/spot/1_simulation/` | +| 3 | spot 2단계 | `2_run_*.py`, `2_run_stage2_all.sh` | `data/spot/techniques/`, `docs/spot/2_analysis/` | +| 4 | spot 3단계 | `3_run_*.py`, `3_run_fractal_ops.sh` | `data/spot/operations/`, `docs/spot/3_operations/` | +| — | futures 0단계 | `0_ground_truth_futures.py` | `data/futures/ground_truth/`, `docs/futures/0_ground_truth/` | + +### 권장 명령 ```bash conda activate ncue export PYTHONPATH=src -# ── common: 캔들 수집 ───────────────────────────────────────── +# common python scripts/00_download.py # 증분 갱신 -python scripts/00_download.py --full # 최초 1회·재구축 +python scripts/00_download.py --full # 최초·재구축 -# ── spot 0단계: GT 타점 (3분봉·10년) ────────────────────────── +# spot 0~2단계 (분석·기법 캐시 생성) python scripts/0_ground_truth.py --interval 3 --days 3650 --tier all - -# ── spot 1단계: GT sim (최근 3년) ─────────────────────────── python scripts/1_ground_truth_sim.py --tier all - -# ── spot 2단계: 인과 기법 (일괄) ────────────────────────────── bash scripts/2_run_stage2_all.sh -# ── futures 0단계: 선물 GT (현물 GT 기반) ─────────────────── +# futures 0단계 python scripts/0_ground_truth_futures.py --tier all -# ── spot 3단계: fractal_swing 운영 ─────────────────────────── -bash scripts/3_run_fractal_ops.sh # 백테스트 + paper loop -python scripts/3_run_fractal_realistic_backtest.py -python scripts/3_run_filtered_backtest.py # 운영 조건 3년 sim +# spot 3단계 — fractal_swing 운영 +python scripts/3_run_filtered_backtest.py # 운영 조건 3년 sim 검증 +python scripts/3_render_live_chart.py # docs/live 매매 차트 +python scripts/3_run_fractal_realistic_backtest.py # 슬리피지 시나리오 +bash scripts/3_run_fractal_ops.sh # 백테스트 + paper 180초 loop python scripts/3_run_operations.py --loop 180 --mode live # live (API 키 필요) ``` @@ -138,157 +178,182 @@ python scripts/3_run_operations.py --loop 180 --mode live # live (API 키 필 ## 단계별 상세 -### common — 캔들 수집 (사전) +### common — 캔들 수집 | 항목 | 내용 | |------|------| -| DB 경로 | `data/common/coins.db` (`DB_PATH`) | -| 기본 동작 | DB 최신 시각 이후 증분 갱신 | +| DB | `data/common/coins.db` (`DB_PATH`) | +| 증분 갱신 | DB 최신 시각 이후만 API 조회·INSERT | | 전체 재수집 | `--full` | -| 1분봉만 풀 다운 | `00_download_candles.py --full --days 3650 --intervals 1` | +| TF | `DOWNLOAD_INTERVALS` (기본 11개) | + +운영 tick에서는 `sync_ops_candles()`가 subprocess 대신 **in-process** 증분 sync를 수행합니다 (`OPS_SYNC_CANDLES=true`). ### spot 0단계 — GT 타점 -사후 최적 매매 타점. **실거래 불가**, 이후 단계의 정답지(기준선). +사후 최적 매매 타점. 실거래 불가, 이후 단계의 벤치마크. -| 티어 | 포함 신호 | -|------|-----------| +| 티어 | 신호 | +|------|------| | v1 | 스윙 B/S | | v2 | + 눌림목 B* | | v3 | + 돌파 B^ + 다이버전스 Bd/Sd | -| 산출물 | 경로 | -|--------|------| -| JSON | `data/spot/ground_truth/ground_truth_trades_v{1,2,3}.json` | -| 차트 | `docs/spot/0_ground_truth/ground_truth_chart_v{1,2,3}.html` | +산출: `data/spot/ground_truth/ground_truth_trades_v{1,2,3}.json`, `docs/spot/0_ground_truth/ground_truth_chart_v*.html` -### spot 1단계 — GT sim (벤치마크) +### spot 1단계 — GT sim -GT 타점 완벽 추종 시 수익 상한선. 최근 3년·초기 20만 원. +GT 타점 완벽 추종 시 3년 수익 상한선. 초기 20만 원, `GT_SIM_LOOKBACK_DAYS=1095`. -| 산출물 | 경로 | -|--------|------| -| sim 차트 | `docs/spot/1_simulation/ground_truth_chart_sim_v{1,2,3}.html` | +산출: `docs/spot/1_simulation/ground_truth_chart_sim_v*.html` ### spot 2단계 — 인과 기법 분석 -설계·목적·MTF 역할 등 상세: [`docs/spot/2_analysis/stage2_design_guide.md`](docs/spot/2_analysis/stage2_design_guide.md) +39종 기법의 GT 정합·3년 sim·신호 유형·MTF 상관 분석. -| 순서 | 스크립트 | 산출물 | -|------|----------|--------| -| 2-1 | `2_run_techniques.py` | `data/spot/techniques/`, `docs/spot/2_analysis/comparison_report.html` | -| 2-2 | `2_run_causal_sim.py` | `docs/spot/2_analysis/causal_sim_report.html` | -| 2-3 | `2_run_signal_type_align.py` | `docs/spot/2_analysis/signal_type_report.html` | -| 2-4 | `2_run_mtf_analysis.py` | `data/spot/mtf/mtf_rules_v3.json`, `docs/spot/2_analysis/mtf_correlation_report.html` | +| 스크립트 | 산출물 | +|----------|--------| +| `2_run_techniques.py` | `data/spot/techniques/`, `comparison_report.html` | +| `2_run_causal_sim.py` | `causal_sim_report.html`, `technique_chart_sim_*.html` | +| `2_run_signal_type_align.py` | `signal_type_report.html` | +| `2_run_mtf_analysis.py` | `mtf_rules_v3.json`, `mtf_correlation_report.html` | + +설계: [`docs/spot/2_analysis/stage2_design_guide.md`](docs/spot/2_analysis/stage2_design_guide.md) +결과 정리: [`docs/spot/2_analysis/stage2_final_summary.md`](docs/spot/2_analysis/stage2_final_summary.md) ### spot 3단계 — fractal_swing live 운영 -설계 가이드: [`docs/spot/3_operations/stage3_design_guide.md`](docs/spot/3_operations/stage3_design_guide.md) +설계 가이드: [`docs/spot/3_operations/stage3_design_guide.md`](docs/spot/3_operations/stage3_design_guide.md) +(가이드 초版은 composite_v3 중심 — **현재 운영 기본값은 fractal_swing**) -**운영 전략:** `fractal_swing` + MTF off. 백테스트(운영 조건) **3년 +1,873,140%** (초기 20만원 → 약 37.5억, 슬리피지 0.05%·일 100회 상한 반영). +#### 백테스트 실적 (BTC, 3년, 초기 20만원) -| 순서 | 스크립트 | 산출물 | -|------|----------|--------| -| 백테스트 | `3_run_filtered_backtest.py` | `fractal_filtered_backtest_report.json` | -| **매매 차트** | `3_render_live_chart.py` | `docs/live/index.html`, `fractal_swing_ops_chart.html` | -| 시나리오 | `3_run_fractal_realistic_backtest.py` | `fractal_realistic_backtest.json` | -| 운영 | `3_run_operations.py --loop 180` | `fractal_ops_report.json`, `fractal_ops_state.json` | -| 일괄 | `3_run_fractal_ops.sh` | 백테스트 + paper loop | +| 조건 | 수익률 | 매수 체결 | 비고 | +|------|--------|-----------|------| +| 운영 백테스트 | **+1,873,140%** | ~53,500 | 슬리피지 0.05%, 일 100회, MTF off | +| 2단계 ideal | +7,560,826% | ~56,893 | 슬리피지 0, 상한 없음 | -#### 백테스트 vs live 코드 정합 (라이브 직전 확인) +#### 스크립트·산출물 -| 항목 | 백테스트 | live/paper 코드 | 상태 | -|------|----------|-----------------|------| -| 기법 | `fractal_swing` | `OPS_TECHNIQUE_ID` | 일치 | -| MTF | off | `OPS_MTF_ENABLED=false` | 일치 | -| 슬리피지 0.05% | `simulate_gt_signals_pnl` | `fill_price` → `executor` | 일치 | -| 수수료 0.05% | `GT_TRADING_FEE_RATE` | 동일 | 일치 | -| 최소 주문 5,000원 | `OPS_MIN_ORDER_KRW` | `trade_engine` | 일치 | -| 일 체결 100회 | `daily_max_trades` in sim | `runner` `trades_today_count` | 일치 | -| 매수 상한 | `max_buy_from_cash` | `compute_buy_order` | 일치 | -| 신호 | 캐시 JSON 전기간 | tail 800봉 갱신 + tick 체결 | 일치 (갱신 로직 추가) | -| 캔들 DB | 로컬 DB | `sync_ops_candles` 전 TF 증분 INSERT | 일치 | +| 스크립트 | 산출물 | +|----------|--------| +| `3_run_filtered_backtest.py` | `fractal_filtered_backtest_report.json` | +| `3_render_live_chart.py` | `docs/live/index.html`, `fractal_swing_ops_chart.html` | +| `3_run_fractal_realistic_backtest.py` | `fractal_realistic_backtest.json` | +| `3_run_operations.py` | `fractal_ops_report.json`, `fractal_ops_state.json` | +| `3_run_fractal_ops.sh` | 백테스트 + paper 180초 loop | -**live 전환 전 체크** +#### 운영 tick 동작 -1. `.env`: `OPS_MODE=live`, API 키 설정 -2. `python scripts/3_run_filtered_backtest.py` → 필터 sim **약 +1,873,140%** 확인 -3. `python scripts/3_run_operations.py --loop 180` (paper로 1~2일 모니터링 권장) -4. `fractal_ops_report.json`에서 `candle_sync`, `signal_refresh`, 체결 건수 확인 +1. **캔들 sync** — `OPS_SYNC_INTERVALS` 비우면 `DOWNLOAD_INTERVALS` 전체 TF, `db_max` 이후만 INSERT +2. **신호** — 2단계 캐시 JSON 로드; DB 최신 봉 > 캐시 max bar 시 **tail 800봉** fractal 재계산·병합 (`OPS_SIGNAL_TAIL_BARS`) +3. **필터** — tick당 **최신 봉** 신호만 MTF 평가 (`OPS_MTF_ENABLED=false` 시 스킵) +4. **체결** — bar 단위 클러스터 분할, 일 `OPS_DAILY_MAX_TRADES` 상한 +5. **알림** — 체결 성공 시 텔레그램; live 실패 시 사유 포함 알림 +6. **저장** — `OPS_STATE_JSON`, `OPS_REPORT_JSON` -**주의:** 백테스트는 **3년 전기간 재생** sim이고, live는 **시간에 따라 누적**합니다. 2단계 ideal **+7,560,826%**(슬리피지 0)와는 다릅니다. 실거래 체결가는 모델 슬리피지보다 불리할 수 있습니다. +#### live 전환 체크리스트 -#### 운영 환경 변수 (fractal 기본) +1. `python scripts/3_run_filtered_backtest.py` → **약 +1,873,140%** 확인 +2. `.env`: `OPS_MODE=live`, `BITHUMB_ACCESS_KEY` / `BITHUMB_SECRET_KEY` +3. `python scripts/3_run_operations.py --loop 180` (paper 1~2일 모니터링 권장) +4. `fractal_ops_report.json` — `candle_sync`, `signal_refresh`, 체결 건수 확인 +5. 텔레그램 체결 알림 동작 확인 (`COIN_TELEGRAM_*`) + +**주의:** 백테스트는 3년 **일괄 재생** sim, live는 **tick 누적**. 실거래 체결가는 모델 슬리피지보다 불리할 수 있습니다. + +#### composite_v3 + MTF (대안 운영 프로필) + +`.env.example` 주석 참고: + +```env +OPS_TECHNIQUE_ID=composite_v3 +OPS_MIN_SCORE=2.5 +OPS_MTF_ENABLED=true +OPS_TREND_GATE_ENABLED=true +OPS_DAILY_MAX_TRADES=20 +``` + +### futures 0단계 — 선물 GT + +현물 GT buy/sell → 롱·숏 4색 마커 (L↑/L↓/S↑/S↓). +산출: `data/futures/ground_truth/`, `docs/futures/0_ground_truth/` +선물 1~3단계는 예정. + +--- + +## 환경 변수 + +전체 목록: `.env.example`. 주요 항목만 정리합니다. + +### 공통·GT + +| 변수 | 설명 | 기본값 | +|------|------|--------| +| `SYMBOL` | 코인 심볼 | `BTC` | +| `DB_PATH` | 캔들 DB | `data/common/coins.db` | +| `DOWNLOAD_DAYS` | 수집·GT 기간(일) | `3650` | +| `DOWNLOAD_INTERVALS` | 수집 TF 목록 | 11개 TF | +| `GT_INTERVAL_MIN` | GT·기법·운영 기준 봉(분) | `3` | +| `GT_LOOKBACK_DAYS` | GT·기법 lookback | `3650` | +| `GT_SIM_LOOKBACK_DAYS` | sim·백테스트 기간 | `1095` (3년) | +| `GT_INITIAL_CASH_KRW` | sim·paper 초기 자본 | `200000` | +| `GT_TRADING_FEE_RATE` | 편도 수수료 | `0.0005` | + +### spot 3단계 운영 (fractal 기본) | 변수 | 설명 | 기본값 | |------|------|--------| | `OPS_MODE` | `paper` / `live` | `paper` | | `OPS_TECHNIQUE_ID` | 운영 기법 | `fractal_swing` | | `OPS_MTF_ENABLED` | MTF 필터 | `false` | +| `OPS_TREND_GATE_ENABLED` | 고TF trend gate | `false` | | `OPS_DAILY_MAX_TRADES` | 일일 체결 상한 | `100` | +| `OPS_MIN_ORDER_KRW` | 최소 주문(원) | `5000` | | `OPS_SLIPPAGE_RATE` | 편도 슬리피지 | `0.0005` (0.05%) | -| `OPS_MIN_ORDER_KRW` | 최소 주문 | `5000` | | `OPS_ORDER_INTERVAL_SEC` | live 주문 간격(초) | `0.35` | -| `OPS_SYNC_CANDLES` | tick마다 캔들 증분 sync | `true` | -| `OPS_SYNC_INTERVALS` | sync TF (비우면 `DOWNLOAD_INTERVALS` 전체) | 전체 | -| `OPS_SIGNAL_TAIL_BARS` | 신호 tail 재계산 봉 수 | `800` | -| `OPS_PERSIST_SIGNAL_CACHE` | tail 갱신 후 JSON 저장 | `false` | +| `OPS_SYNC_CANDLES` | tick 캔들 증분 sync | `true` | +| `OPS_SYNC_INTERVALS` | sync TF (비우면 전체) | 전체 | +| `OPS_SIGNAL_TAIL_BARS` | 신호 tail 재계산 봉 | `800` | +| `OPS_PERSIST_SIGNAL_CACHE` | tail 후 JSON 저장 | `false` | | `OPS_STATE_JSON` | 운영 상태 | `fractal_ops_state.json` | -| `OPS_REPORT_JSON` | 운영 리포트 | `fractal_ops_report.json` | - -`composite_v3` + MTF 운영은 `.env.example` 주석 참고. - -### futures 0단계 — 선물 GT - -현물 GT를 롱·숏 4색 마커로 변환. - -| 현물 GT | 선물 마커 | 의미 | -|---------|-----------|------| -| buy | L↑ / S↑ | 롱 진입 / 숏 청산 | -| sell | L↓ / S↓ | 롱 청산 / 숏 진입 | - -| 산출물 | 경로 | -|--------|------| -| JSON | `data/futures/ground_truth/ground_truth_trades_v{1,2,3}.json` | -| 차트 | `docs/futures/0_ground_truth/ground_truth_chart_v{1,2,3}.html` | - -선물 1~3단계는 `docs/futures/{1_simulation,2_analysis,3_operations}/` (예정). - ---- - -## 환경 변수 - -| 변수 | 설명 | 기본값 | -|------|------|--------| -| `DB_PATH` | 공통 캔들 DB | `data/common/coins.db` | -| `SYMBOL` | 코인 심볼 | `BTC` | -| `DOWNLOAD_DAYS` | 수집·차트 일수 | `3650` | -| `GT_INTERVAL_MIN` | GT·기법 기준 인터벌(분) | `3` | -| `GT_LOOKBACK_DAYS` | GT 타점 기간(일) | `3650` | -| `GT_SIM_LOOKBACK_DAYS` | sim 거래 기간(일) | `1095` | -| `GT_INITIAL_CASH_KRW` | sim 초기 자본(원) | `200000` | -| `OPS_MODE` | 운영 모드 | `paper` | -| `OPS_TECHNIQUE_ID` | 운영 기법 | `fractal_swing` | -| `OPS_SLIPPAGE_RATE` | 편도 슬리피지 | `0.0005` | -| `OPS_DAILY_MAX_TRADES` | 일일 체결 상한 | `100` | -| `OPS_SYNC_CANDLES` | tick 캔들 sync | `true` | +| `OPS_REPORT_JSON` | tick 리포트 | `fractal_ops_report.json` | +| `OPS_FILTERED_BACKTEST_JSON` | 백테스트 리포트 | `fractal_filtered_backtest_report.json` | +| `COIN_TELEGRAM_BOT_TOKEN` | 텔레그램 Bot | (비우면 알림 off) | +| `COIN_TELEGRAM_CHAT_ID` | 텔레그램 chat ID | | +| `OPS_TELEGRAM_ENABLED` | 체결 알림 | 토큰·chat_id 있으면 자동 on | +| `BITHUMB_ACCESS_KEY` | live API | — | +| `BITHUMB_SECRET_KEY` | live API | — | ### 경로 변수 요약 -| 용도 | 변수 예시 | 기본 경로 | -|------|-----------|-----------| -| spot GT JSON | `GROUND_TRUTH_FILE` | `data/spot/ground_truth/...` | -| spot GT 차트 | `GROUND_TRUTH_CHART_V3_FILE` | `docs/spot/0_ground_truth/...` | -| spot sim 차트 | `GROUND_TRUTH_CHART_SIM_V3_FILE` | `docs/spot/1_simulation/...` | -| spot 2단계 | `TECHNIQUES_DIR` | `data/spot/techniques/` | -| spot 3단계 운영 | `OPS_STATE_JSON` | `data/spot/operations/fractal_ops_state.json` | -| spot 3단계 리포트 | `OPS_REPORT_JSON` | `docs/spot/3_operations/fractal_ops_report.json` | -| futures GT JSON | `GROUND_TRUTH_FUTURES_FILE` | `data/futures/ground_truth/...` | -| futures GT 차트 | `GROUND_TRUTH_FUTURES_CHART_V3_FILE` | `docs/futures/0_ground_truth/...` | +| 용도 | 변수 | 기본 경로 | +|------|------|-----------| +| spot GT | `GROUND_TRUTH_FILE` | `data/spot/ground_truth/...` | +| spot 기법 | `TECHNIQUES_DIR` | `data/spot/techniques/` | +| spot MTF | `MTF_RULES_JSON` | `data/spot/mtf/mtf_rules_v3.json` | +| spot 운영 상태 | `OPS_STATE_JSON` | `data/spot/operations/fractal_ops_state.json` | +| spot 운영 리포트 | `OPS_REPORT_JSON` | `docs/spot/3_operations/fractal_ops_report.json` | +| live 차트 | `3_render_live_chart.py` | `docs/live/` | -전체 목록: `.env.example` +--- -인터벌 코드: 분봉=분 단위 숫자, 일봉=`1440`, 주봉=`10080`, 월봉=`43200` +## 소스 모듈 (spot 3단계) + +| 모듈 | 역할 | +|------|------| +| `operations/runner.py` | tick 오케스트레이션 | +| `operations/candle_sync.py` | 전 TF 증분 캔들 sync | +| `operations/signal_pipeline.py` | 신호 생성·캐시·tail 갱신·MTF 필터 | +| `operations/executor.py` | paper/live 체결 | +| `operations/execution.py` | 슬리피지 `fill_price` | +| `operations/trade_engine.py` | 매수·매도 사이징·포트폴리오 | +| `operations/backtest.py` | 운영 조건 3년 sim | +| `operations/chart.py` | `docs/live` 백테스트 차트 | +| `operations/state_store.py` | 운영 상태 JSON | +| `ground_truth/pnl.py` | sim 엔진 (백테스트·2단계 공용) | +| `api/bithumb_private.py` | live 잔고·시장가 주문 | +| `notifications/telegram.py` | 체결 텔레그램 알림 | --- @@ -344,21 +409,22 @@ GT 타점 완벽 추종 시 수익 상한선. 최근 3년·초기 20만 원. | 유형 | 단계 | 상태 | |------|------|------| -| common | 사전 (캔들) | 구현됨 | -| spot | 0~3단계 | 구현됨 (3단계 fractal_swing live 준비) | -| futures | 0단계 | 구현됨 | +| common | 캔들 수집·증분 sync | 구현됨 | +| spot | 0~2단계 (GT·기법·MTF) | 구현됨 | +| spot | 3단계 (fractal paper/live·백테스트·텔레그램) | **구현됨** | +| futures | 0단계 GT | 구현됨 | | futures | 1~3단계 | 예정 | --- ## 변경 이력 -- 2026-06-13: `docs/live/` — 운영 백테스트 매수·매도 타점 HTML 차트 (`3_render_live_chart.py`) +- 2026-06-13: 텔레그램 매수·매도 체결 알림 (`notifications/telegram.py`) +- 2026-06-13: `docs/live/` 운영 백테스트 매매 차트 (`3_render_live_chart.py`) +- 2026-06-13: fractal_swing live 운영 — 슬리피지·일 체결 상한·전 TF 증분 sync·신호 tail 갱신 - 2026-06-13: 운영 백테스트 **+1,873,140%** (3년, 슬리피지 0.05%, 일 100회) 검증 -- 2026-06-12: `data/`·`docs/`를 **common / spot / futures** 3유형 구조로 재편, `coins.db` → `data/common/`, 0단계 차트 → `docs/{spot,futures}/0_ground_truth/` -- 2026-06-12: `0_ground_truth_futures.py` — 현물 GT → 선물 JSON·차트 변환 로직 보완 -- 2026-06-12: README 현물 파이프라인 전체 순서 갱신 -- 2026-06-12: `src/deepcoin/data/` 모듈 복원 -- 2026-06-11: 파이프라인 단계별 상세 설명 추가 +- 2026-06-12: `data/`·`docs/` common/spot/futures 3유형 구조 재편 +- 2026-06-12: 3단계 운영 파이프라인 초기 구현 (composite_v3 + MTF paper/live) +- 2026-06-12: 2단계 인과 기법 분석 파이프라인 완료 - 2026-06-08: Ground Truth v1/v2/v3 - 2026-06-07: 캔들 수집 모듈 초기 구현 diff --git a/src/deepcoin/config.py b/src/deepcoin/config.py index e85fe04..ba09874 100644 --- a/src/deepcoin/config.py +++ b/src/deepcoin/config.py @@ -112,6 +112,9 @@ class Settings: ops_sync_intervals: list[int] ops_signal_tail_bars: int ops_persist_signal_cache: bool + telegram_bot_token: str + telegram_chat_id: str + ops_telegram_enabled: bool @property def market(self) -> str: @@ -316,9 +319,26 @@ def load_settings(env_path: Path | None = None) -> Settings: ops_signal_tail_bars=int(os.getenv("OPS_SIGNAL_TAIL_BARS", "800")), ops_persist_signal_cache=os.getenv("OPS_PERSIST_SIGNAL_CACHE", "false").strip().lower() in ("1", "true", "yes", "on"), + telegram_bot_token=os.getenv("COIN_TELEGRAM_BOT_TOKEN", "").strip(), + telegram_chat_id=os.getenv("COIN_TELEGRAM_CHAT_ID", "").strip(), + ops_telegram_enabled=_parse_ops_telegram_enabled( + os.getenv("OPS_TELEGRAM_ENABLED", "").strip(), + bot_token=os.getenv("COIN_TELEGRAM_BOT_TOKEN", "").strip(), + chat_id=os.getenv("COIN_TELEGRAM_CHAT_ID", "").strip(), + ), ) +def _parse_ops_telegram_enabled(raw: str, *, bot_token: str, chat_id: str) -> bool: + """운영 텔레그램 알림 on/off. + + OPS_TELEGRAM_ENABLED 비어 있으면 토큰·chat_id가 모두 있을 때 자동 on. + """ + if raw: + return raw.lower() in ("1", "true", "yes", "on") + return bool(bot_token and chat_id) + + def _parse_ops_sync_intervals( raw: str, fallback_intervals: list[int], diff --git a/src/deepcoin/notifications/__init__.py b/src/deepcoin/notifications/__init__.py new file mode 100644 index 0000000..e52a8ee --- /dev/null +++ b/src/deepcoin/notifications/__init__.py @@ -0,0 +1,5 @@ +"""알림 채널 (텔레그램 등).""" + +from deepcoin.notifications.telegram import TelegramNotifier, create_telegram_notifier + +__all__ = ["TelegramNotifier", "create_telegram_notifier"] diff --git a/src/deepcoin/notifications/telegram.py b/src/deepcoin/notifications/telegram.py new file mode 100644 index 0000000..1f1a8ba --- /dev/null +++ b/src/deepcoin/notifications/telegram.py @@ -0,0 +1,168 @@ +"""텔레그램 Bot API 알림.""" + +from __future__ import annotations + +import logging +from typing import Any + +import requests + +logger = logging.getLogger(__name__) + +_TELEGRAM_API = "https://api.telegram.org/bot{token}/sendMessage" + + +class TelegramNotifier: + """매매 체결 등 운영 알림을 텔레그램으로 전송한다.""" + + def __init__( + self, + bot_token: str, + chat_id: str, + *, + enabled: bool = True, + timeout_sec: float = 10.0, + ) -> None: + """알림 클라이언트를 초기화한다. + + Args: + bot_token: Bot API 토큰. + chat_id: 대상 채팅 ID. + enabled: False면 전송하지 않음. + timeout_sec: HTTP 타임아웃(초). + """ + self.bot_token = (bot_token or "").strip() + self.chat_id = (chat_id or "").strip() + self.enabled = enabled + self.timeout_sec = timeout_sec + self._session = requests.Session() + + @property + def is_active(self) -> bool: + """토큰·채팅 ID가 있고 enabled일 때 True.""" + return bool(self.enabled and self.bot_token and self.chat_id) + + def send_message(self, text: str) -> bool: + """텍스트 메시지를 전송한다. + + Args: + text: 본문 (HTML 미사용, plain text). + + Returns: + 성공 시 True. + """ + if not self.is_active: + return False + url = _TELEGRAM_API.format(token=self.bot_token) + try: + resp = self._session.post( + url, + json={ + "chat_id": self.chat_id, + "text": text, + "disable_web_page_preview": True, + }, + timeout=self.timeout_sec, + ) + if resp.status_code != 200: + logger.warning( + "텔레그램 전송 실패 status=%s body=%s", + resp.status_code, + resp.text[:200], + ) + return False + data = resp.json() + if not data.get("ok"): + logger.warning("텔레그램 API 오류: %s", data) + return False + return True + except requests.RequestException as exc: + logger.warning("텔레그램 전송 예외: %s", exc) + return False + + def notify_trade_execution( + self, + *, + mode: str, + symbol: str, + coin_name: str, + technique_id: str, + side: str, + signal_type: str, + datetime_str: str, + signal_price: float, + trade: dict[str, Any], + portfolio: dict[str, Any], + trades_today_count: int, + daily_max_trades: int, + cluster_size: int, + ) -> bool: + """체결 1건 알림을 포맷하여 전송한다.""" + if not trade.get("executed"): + return False + + side_label = "매수" if side == "buy" else "매도" + mode_label = "LIVE" if mode == "live" else "PAPER" + price = float(trade.get("price", signal_price)) + order_krw = float(trade.get("order_krw", 0)) + order_coin = float(trade.get("order_coin", 0)) + fee_krw = float(trade.get("fee_krw", 0)) + cash = float(portfolio.get("cash_krw", 0)) + coin_qty = float(portfolio.get("coin_qty", 0)) + equity = cash + coin_qty * price + + lines = [ + f"[DeepCoin] {side_label} 체결 ({mode_label})", + f"{coin_name} ({symbol}) | {technique_id}", + f"시각: {datetime_str}", + f"신호: {signal_type or side}", + f"체결가: {_fmt_krw(price)}", + ] + if side == "buy": + lines.append(f"주문: {_fmt_krw(order_krw)} ({order_coin:.8f} {symbol})") + else: + lines.append(f"주문: {order_coin:.8f} {symbol} ({_fmt_krw(order_krw)})") + lines.append(f"수수료: {_fmt_krw(fee_krw)}") + if cluster_size > 1: + lines.append(f"클러스터 분할: {cluster_size}건") + lines.append(f"잔고: 현금 {_fmt_krw(cash)} | {symbol} {coin_qty:.8f}") + lines.append(f"평가(추정): {_fmt_krw(equity)}") + lines.append(f"오늘 체결: {trades_today_count}/{daily_max_trades}") + + return self.send_message("\n".join(lines)) + + def notify_trade_failure( + self, + *, + mode: str, + symbol: str, + technique_id: str, + side: str, + datetime_str: str, + reason: str, + ) -> bool: + """live 체결 실패 알림.""" + side_label = "매수" if side == "buy" else "매도" + mode_label = "LIVE" if mode == "live" else "PAPER" + text = ( + f"[DeepCoin] {side_label} 실패 ({mode_label})\n" + f"{symbol} | {technique_id}\n" + f"시각: {datetime_str}\n" + f"사유: {reason}" + ) + return self.send_message(text) + + +def _fmt_krw(value: float) -> str: + """원화 금액 포맷.""" + return f"{round(value):,}원" + + +def create_telegram_notifier( + bot_token: str, + chat_id: str, + *, + enabled: bool = True, +) -> TelegramNotifier: + """설정값으로 TelegramNotifier를 생성한다.""" + return TelegramNotifier(bot_token, chat_id, enabled=enabled) diff --git a/src/deepcoin/operations/runner.py b/src/deepcoin/operations/runner.py index 46e6b97..5e57344 100644 --- a/src/deepcoin/operations/runner.py +++ b/src/deepcoin/operations/runner.py @@ -11,6 +11,7 @@ from typing import Any from deepcoin.config import Settings from deepcoin.ground_truth.pnl import _cluster_signals +from deepcoin.notifications.telegram import create_telegram_notifier from deepcoin.operations.candle_sync import sync_ops_candles from deepcoin.operations.executor import create_executor from deepcoin.operations.signal_pipeline import ( @@ -80,6 +81,11 @@ class OperationsRunner: def __init__(self, settings: Settings) -> None: self.settings = settings self.executor = create_executor(settings) + self.telegram = create_telegram_notifier( + settings.telegram_bot_token, + settings.telegram_chat_id, + enabled=settings.ops_telegram_enabled, + ) self.state = load_state( settings.ops_state_json, initial_cash_krw=settings.gt_initial_cash_krw, @@ -139,11 +145,25 @@ class OperationsRunner: executions.append(record) if trade.executed: self.state["trades_today_count"] += 1 + self._notify_trade(full_sig, trade, cluster_size) if ( self.settings.ops_mode == "live" and self.settings.ops_order_interval_sec > 0 ): time.sleep(self.settings.ops_order_interval_sec) + elif ( + self.settings.ops_mode == "live" + and trade.skip_reason + and self.telegram.is_active + ): + self.telegram.notify_trade_failure( + mode=self.settings.ops_mode, + symbol=self.settings.symbol, + technique_id=gen["technique_id"], + side=str(full_sig["side"]), + datetime_str=str(full_sig["datetime"]), + reason=trade.skip_reason, + ) if bar_idx > last_bar: last_bar = bar_idx self.state["last_processed_bar_index"] = bar_idx @@ -193,6 +213,32 @@ class OperationsRunner: self._save_report(report) return report + def _notify_trade( + self, + signal: dict[str, Any], + trade: Any, + cluster_size: int, + ) -> None: + """체결 성공 시 텔레그램 알림.""" + if not self.telegram.is_active: + return + trade_dict = trade.to_dict() + self.telegram.notify_trade_execution( + mode=self.settings.ops_mode, + symbol=self.settings.symbol, + coin_name=self.settings.coin_name, + technique_id=self.settings.ops_technique_id, + side=str(signal["side"]), + signal_type=str(signal.get("signal_type", "")), + datetime_str=str(signal["datetime"]), + signal_price=float(signal["price"]), + trade=trade_dict, + portfolio=self.state["portfolio"], + trades_today_count=self.state["trades_today_count"], + daily_max_trades=self.settings.ops_daily_max_trades, + cluster_size=cluster_size, + ) + def _save_report(self, report: dict[str, Any]) -> None: """최신 운영 리포트 저장.""" path = self.settings.ops_report_json