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