From c3334e4f7765693792eb21b890d13ac1aa4e8326 Mon Sep 17 00:00:00 2001 From: dsyoon Date: Sat, 13 Jun 2026 17:47:11 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=EB=AA=85=20bithumb=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20futures=20=ED=8C=8C=EC=9D=B4=ED=94=84?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deepcoin 패키지를 bithumb으로 rename하고, 3단계 live 운영·사이징 튜닝·텔레그램 알림을 통합한다. Co-authored-by: Cursor --- .env.example | 23 +- README.md | 45 +- docs/live/index.html | 12 +- docs/spot/2_analysis/stage2_design_guide.md | 20 +- docs/spot/2_analysis/stage2_final_summary.md | 2 +- docs/spot/3_operations/stage3_design_guide.md | 14 +- scripts/00_download_candles.py | 8 +- scripts/0_ground_truth.py | 8 +- scripts/0_ground_truth_futures.py | 153 ---- scripts/1_ground_truth_sim.py | 12 +- scripts/1_tune_order_sizing.py | 96 +++ scripts/2_render_best_technique_chart.py | 8 +- scripts/2_run_causal_sim.py | 10 +- scripts/2_run_mtf_analysis.py | 12 +- scripts/2_run_signal_type_align.py | 14 +- scripts/2_run_stage2_all.sh | 2 +- scripts/2_run_techniques.py | 12 +- scripts/3_init_live_state.py | 90 +++ scripts/3_preflight_live.py | 70 ++ scripts/3_render_live_chart.py | 19 +- scripts/3_run_filtered_backtest.py | 4 +- scripts/3_run_fractal_live.sh | 26 + scripts/3_run_fractal_realistic_backtest.py | 10 +- scripts/3_run_operations.py | 38 +- src/bithumb/__init__.py | 3 + src/{deepcoin => bithumb}/api/__init__.py | 0 src/{deepcoin => bithumb}/api/bithumb.py | 2 +- src/{deepcoin => bithumb}/api/bithumb_auth.py | 0 .../api/bithumb_private.py | 45 +- src/{deepcoin => bithumb}/config.py | 67 +- src/{deepcoin => bithumb}/data/__init__.py | 0 .../data/candle_loader.py | 2 +- .../data/candle_store.py | 2 +- src/{deepcoin => bithumb}/data/downloader.py | 6 +- src/{deepcoin => bithumb}/data/intervals.py | 0 .../evaluation/__init__.py | 8 +- .../evaluation/causal_sim.py | 8 +- .../evaluation/gt_align.py | 2 +- .../evaluation/mtf_report.py | 6 +- .../evaluation/report.py | 6 +- .../evaluation/signal_type_report.py | 8 +- .../ground_truth/__init__.py | 0 .../ground_truth/breakout.py | 2 +- .../ground_truth/chart.py | 6 +- .../ground_truth/divergence.py | 2 +- .../ground_truth/ground_truth.py | 14 +- src/bithumb/ground_truth/order_sizing.py | 122 +++ src/{deepcoin => bithumb}/ground_truth/pnl.py | 41 +- .../ground_truth/pullback.py | 2 +- src/bithumb/ground_truth/sizing_rules.py | 97 +++ src/bithumb/ground_truth/sizing_tune.py | 273 +++++++ .../ground_truth/swing_signals.py | 2 +- .../ground_truth/zigzag.py | 0 src/bithumb/mtf/__init__.py | 23 + src/{deepcoin => bithumb}/mtf/alignment.py | 0 src/{deepcoin => bithumb}/mtf/extractor.py | 6 +- src/{deepcoin => bithumb}/mtf/features.py | 2 +- src/{deepcoin => bithumb}/mtf/filter.py | 6 +- src/{deepcoin => bithumb}/mtf/precompute.py | 6 +- src/{deepcoin => bithumb}/mtf/rules.py | 2 +- src/{deepcoin => bithumb}/mtf/store.py | 6 +- src/{deepcoin => bithumb}/mtf/trend_gate.py | 0 .../notifications/__init__.py | 2 +- .../notifications/telegram.py | 27 +- src/bithumb/operations/__init__.py | 12 + .../operations/backtest.py | 44 +- .../operations/candle_sync.py | 6 +- src/{deepcoin => bithumb}/operations/chart.py | 15 +- .../operations/execution.py | 0 .../operations/executor.py | 62 +- src/bithumb/operations/live_bootstrap.py | 253 ++++++ .../operations/runner.py | 136 +++- .../operations/signal_pipeline.py | 22 +- .../operations/signal_type.py | 0 .../operations/state_store.py | 0 .../operations/trade_engine.py | 60 +- .../techniques/__init__.py | 4 +- .../techniques/adx_trend.py | 6 +- .../techniques/atr_channel.py | 6 +- src/{deepcoin => bithumb}/techniques/base.py | 0 .../techniques/bb_reversal.py | 4 +- .../techniques/bb_squeeze_breakout.py | 6 +- .../techniques/cci_extreme.py | 6 +- .../techniques/composite_base.py | 4 +- .../techniques/composite_breakout.py | 16 +- .../techniques/composite_divergence.py | 14 +- .../techniques/composite_full.py | 6 +- .../techniques/composite_pullback.py | 16 +- .../techniques/composite_swing.py | 18 +- .../techniques/composite_v3.py | 38 +- .../techniques/donchian.py | 2 +- .../techniques/ema_pullback.py | 6 +- .../techniques/fib_pullback.py | 6 +- .../techniques/fractal_swing.py | 4 +- .../techniques/helpers.py | 2 +- .../techniques/ichimoku_trend.py | 6 +- .../techniques/indicators.py | 0 .../techniques/keltner_breakout.py | 6 +- .../techniques/keltner_reversal.py | 6 +- src/{deepcoin => bithumb}/techniques/legs.py | 2 +- .../techniques/local_extrema.py | 2 +- .../techniques/ma_cross.py | 4 +- .../techniques/macd_cross.py | 4 +- .../techniques/macd_divergence.py | 6 +- .../techniques/minor_swing.py | 6 +- .../techniques/obv_divergence.py | 6 +- .../techniques/parabolic_sar_signal.py | 6 +- .../techniques/pivot_points.py | 6 +- .../techniques/pivot_swing.py | 4 +- .../techniques/range_breakout.py | 4 +- .../techniques/registry.py | 80 +- .../techniques/roc_reversal.py | 6 +- .../techniques/rsi_divergence.py | 6 +- .../techniques/rsi_swing.py | 4 +- .../techniques/runner.py | 14 +- .../techniques/stochastic_cross.py | 6 +- .../techniques/supertrend_signal.py | 6 +- .../techniques/support_bounce.py | 4 +- .../techniques/support_resistance.py | 4 +- .../techniques/swing_failure.py | 4 +- .../techniques/volume_breakout.py | 4 +- .../techniques/volume_spike.py | 4 +- .../techniques/zigzag_causal.py | 2 +- src/deepcoin/__init__.py | 3 - src/deepcoin/ground_truth/futures.py | 128 --- src/deepcoin/ground_truth/futures_chart.py | 763 ------------------ src/deepcoin/ground_truth/order_sizing.py | 59 -- src/deepcoin/mtf/__init__.py | 23 - src/deepcoin/operations/__init__.py | 12 - 129 files changed, 1846 insertions(+), 1642 deletions(-) delete mode 100644 scripts/0_ground_truth_futures.py create mode 100644 scripts/1_tune_order_sizing.py create mode 100755 scripts/3_init_live_state.py create mode 100755 scripts/3_preflight_live.py create mode 100755 scripts/3_run_fractal_live.sh create mode 100644 src/bithumb/__init__.py rename src/{deepcoin => bithumb}/api/__init__.py (100%) rename src/{deepcoin => bithumb}/api/bithumb.py (98%) rename src/{deepcoin => bithumb}/api/bithumb_auth.py (100%) rename src/{deepcoin => bithumb}/api/bithumb_private.py (73%) rename src/{deepcoin => bithumb}/config.py (87%) rename src/{deepcoin => bithumb}/data/__init__.py (100%) rename src/{deepcoin => bithumb}/data/candle_loader.py (95%) rename src/{deepcoin => bithumb}/data/candle_store.py (99%) rename src/{deepcoin => bithumb}/data/downloader.py (97%) rename src/{deepcoin => bithumb}/data/intervals.py (100%) rename src/{deepcoin => bithumb}/evaluation/__init__.py (65%) rename src/{deepcoin => bithumb}/evaluation/causal_sim.py (98%) rename src/{deepcoin => bithumb}/evaluation/gt_align.py (99%) rename src/{deepcoin => bithumb}/evaluation/mtf_report.py (98%) rename src/{deepcoin => bithumb}/evaluation/report.py (96%) rename src/{deepcoin => bithumb}/evaluation/signal_type_report.py (97%) rename src/{deepcoin => bithumb}/ground_truth/__init__.py (100%) rename src/{deepcoin => bithumb}/ground_truth/breakout.py (99%) rename src/{deepcoin => bithumb}/ground_truth/chart.py (99%) rename src/{deepcoin => bithumb}/ground_truth/divergence.py (99%) rename src/{deepcoin => bithumb}/ground_truth/ground_truth.py (96%) create mode 100644 src/bithumb/ground_truth/order_sizing.py rename src/{deepcoin => bithumb}/ground_truth/pnl.py (90%) rename src/{deepcoin => bithumb}/ground_truth/pullback.py (98%) create mode 100644 src/bithumb/ground_truth/sizing_rules.py create mode 100644 src/bithumb/ground_truth/sizing_tune.py rename src/{deepcoin => bithumb}/ground_truth/swing_signals.py (98%) rename src/{deepcoin => bithumb}/ground_truth/zigzag.py (100%) create mode 100644 src/bithumb/mtf/__init__.py rename src/{deepcoin => bithumb}/mtf/alignment.py (100%) rename src/{deepcoin => bithumb}/mtf/extractor.py (94%) rename src/{deepcoin => bithumb}/mtf/features.py (98%) rename src/{deepcoin => bithumb}/mtf/filter.py (96%) rename src/{deepcoin => bithumb}/mtf/precompute.py (95%) rename src/{deepcoin => bithumb}/mtf/rules.py (99%) rename src/{deepcoin => bithumb}/mtf/store.py (93%) rename src/{deepcoin => bithumb}/mtf/trend_gate.py (100%) rename src/{deepcoin => bithumb}/notifications/__init__.py (53%) rename src/{deepcoin => bithumb}/notifications/telegram.py (86%) create mode 100644 src/bithumb/operations/__init__.py rename src/{deepcoin => bithumb}/operations/backtest.py (63%) rename src/{deepcoin => bithumb}/operations/candle_sync.py (90%) rename src/{deepcoin => bithumb}/operations/chart.py (81%) rename src/{deepcoin => bithumb}/operations/execution.py (100%) rename src/{deepcoin => bithumb}/operations/executor.py (70%) create mode 100644 src/bithumb/operations/live_bootstrap.py rename src/{deepcoin => bithumb}/operations/runner.py (62%) rename src/{deepcoin => bithumb}/operations/signal_pipeline.py (94%) rename src/{deepcoin => bithumb}/operations/signal_type.py (100%) rename src/{deepcoin => bithumb}/operations/state_store.py (100%) rename src/{deepcoin => bithumb}/operations/trade_engine.py (58%) rename src/{deepcoin => bithumb}/techniques/__init__.py (77%) rename src/{deepcoin => bithumb}/techniques/adx_trend.py (90%) rename src/{deepcoin => bithumb}/techniques/atr_channel.py (91%) rename src/{deepcoin => bithumb}/techniques/base.py (100%) rename src/{deepcoin => bithumb}/techniques/bb_reversal.py (94%) rename src/{deepcoin => bithumb}/techniques/bb_squeeze_breakout.py (91%) rename src/{deepcoin => bithumb}/techniques/cci_extreme.py (90%) rename src/{deepcoin => bithumb}/techniques/composite_base.py (97%) rename src/{deepcoin => bithumb}/techniques/composite_breakout.py (74%) rename src/{deepcoin => bithumb}/techniques/composite_divergence.py (76%) rename src/{deepcoin => bithumb}/techniques/composite_full.py (90%) rename src/{deepcoin => bithumb}/techniques/composite_pullback.py (74%) rename src/{deepcoin => bithumb}/techniques/composite_swing.py (73%) rename src/{deepcoin => bithumb}/techniques/composite_v3.py (66%) rename src/{deepcoin => bithumb}/techniques/donchian.py (96%) rename src/{deepcoin => bithumb}/techniques/ema_pullback.py (92%) rename src/{deepcoin => bithumb}/techniques/fib_pullback.py (95%) rename src/{deepcoin => bithumb}/techniques/fractal_swing.py (90%) rename src/{deepcoin => bithumb}/techniques/helpers.py (99%) rename src/{deepcoin => bithumb}/techniques/ichimoku_trend.py (89%) rename src/{deepcoin => bithumb}/techniques/indicators.py (100%) rename src/{deepcoin => bithumb}/techniques/keltner_breakout.py (90%) rename src/{deepcoin => bithumb}/techniques/keltner_reversal.py (91%) rename src/{deepcoin => bithumb}/techniques/legs.py (98%) rename src/{deepcoin => bithumb}/techniques/local_extrema.py (98%) rename src/{deepcoin => bithumb}/techniques/ma_cross.py (94%) rename src/{deepcoin => bithumb}/techniques/macd_cross.py (94%) rename src/{deepcoin => bithumb}/techniques/macd_divergence.py (93%) rename src/{deepcoin => bithumb}/techniques/minor_swing.py (91%) rename src/{deepcoin => bithumb}/techniques/obv_divergence.py (93%) rename src/{deepcoin => bithumb}/techniques/parabolic_sar_signal.py (87%) rename src/{deepcoin => bithumb}/techniques/pivot_points.py (90%) rename src/{deepcoin => bithumb}/techniques/pivot_swing.py (90%) rename src/{deepcoin => bithumb}/techniques/range_breakout.py (92%) rename src/{deepcoin => bithumb}/techniques/registry.py (50%) rename src/{deepcoin => bithumb}/techniques/roc_reversal.py (89%) rename src/{deepcoin => bithumb}/techniques/rsi_divergence.py (93%) rename src/{deepcoin => bithumb}/techniques/rsi_swing.py (94%) rename src/{deepcoin => bithumb}/techniques/runner.py (92%) rename src/{deepcoin => bithumb}/techniques/stochastic_cross.py (90%) rename src/{deepcoin => bithumb}/techniques/supertrend_signal.py (88%) rename src/{deepcoin => bithumb}/techniques/support_bounce.py (93%) rename src/{deepcoin => bithumb}/techniques/support_resistance.py (93%) rename src/{deepcoin => bithumb}/techniques/swing_failure.py (93%) rename src/{deepcoin => bithumb}/techniques/volume_breakout.py (92%) rename src/{deepcoin => bithumb}/techniques/volume_spike.py (93%) rename src/{deepcoin => bithumb}/techniques/zigzag_causal.py (97%) delete mode 100644 src/deepcoin/__init__.py delete mode 100644 src/deepcoin/ground_truth/futures.py delete mode 100644 src/deepcoin/ground_truth/futures_chart.py delete mode 100644 src/deepcoin/ground_truth/order_sizing.py delete mode 100644 src/deepcoin/mtf/__init__.py delete mode 100644 src/deepcoin/operations/__init__.py diff --git a/.env.example b/.env.example index 84891bc..6aef138 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -# DeepCoin — .env.example (비밀값 없음). 복사: cp .env.example .env +# Bithumb — .env.example (비밀값 없음). 복사: cp .env.example .env # --- 빗썸 API (캔들 수집은 Public API, 키 선택) --- BITHUMB_ACCESS_KEY= @@ -52,14 +52,6 @@ GROUND_TRUTH_CHART_V1_FILE=docs/spot/0_ground_truth/ground_truth_chart_v1.html GROUND_TRUTH_CHART_V2_FILE=docs/spot/0_ground_truth/ground_truth_chart_v2.html GROUND_TRUTH_CHART_V3_FILE=docs/spot/0_ground_truth/ground_truth_chart_v3.html -# --- 0단계: 선물 GT --- -GROUND_TRUTH_FUTURES_FILE=data/futures/ground_truth/ground_truth_trades_v3.json -GROUND_TRUTH_FUTURES_V1_FILE=data/futures/ground_truth/ground_truth_trades_v1.json -GROUND_TRUTH_FUTURES_V2_FILE=data/futures/ground_truth/ground_truth_trades_v2.json -GROUND_TRUTH_FUTURES_CHART_V1_FILE=docs/futures/0_ground_truth/ground_truth_chart_v1.html -GROUND_TRUTH_FUTURES_CHART_V2_FILE=docs/futures/0_ground_truth/ground_truth_chart_v2.html -GROUND_TRUTH_FUTURES_CHART_V3_FILE=docs/futures/0_ground_truth/ground_truth_chart_v3.html - # --- 현물 1단계: GT sim --- GROUND_TRUTH_CHART_SIM_V1_FILE=docs/spot/1_simulation/ground_truth_chart_sim_v1.html GROUND_TRUTH_CHART_SIM_V2_FILE=docs/spot/1_simulation/ground_truth_chart_sim_v2.html @@ -78,13 +70,21 @@ CAUSAL_SIM_REPORT_JSON=docs/spot/2_analysis/causal_sim_report.json CAUSAL_SIM_REPORT_HTML=docs/spot/2_analysis/causal_sim_report.html # --- 현물 3단계: 운영 (기본 paper) --- +# live 전환: OPS_MODE=live → bash scripts/3_run_fractal_live.sh OPS_MODE=paper OPS_TECHNIQUE_ID=fractal_swing OPS_MTF_ENABLED=false OPS_TREND_GATE_ENABLED=false OPS_DAILY_MAX_TRADES=100 OPS_MIN_ORDER_KRW=5000 +# 1회 매수·매도 분할 (0.10=총평가/보유의 10%, 20만원→약 2만원/회) +OPS_BUY_CASH_PCT=0.10 +OPS_SELL_COIN_PCT=0.10 OPS_SLIPPAGE_RATE=0.0005 +# 빗썸 시장가 매수 시 주문금액+예약수수료 lock (전액 주문 400 방지) +OPS_EXCHANGE_FEE_LOCK_RATE=0.0025 +# live 시장가 매수 안전 여유: floor(가용/(1+lock)) - N원 +OPS_BUY_SAFETY_BUFFER_KRW=1000 OPS_ORDER_INTERVAL_SEC=0.35 OPS_SYNC_CANDLES=true # 비우면 DOWNLOAD_INTERVALS 전체 증분 sync @@ -94,9 +94,10 @@ OPS_SIGNAL_TAIL_BARS=800 OPS_STATE_JSON=data/spot/operations/fractal_ops_state.json OPS_REPORT_JSON=docs/spot/3_operations/fractal_ops_report.json OPS_FILTERED_BACKTEST_JSON=docs/spot/3_operations/fractal_filtered_backtest_report.json +# 1_tune_order_sizing.py 학습 결과 (sim/live/backtest 공통) +OPS_SIZING_RULES_JSON=data/spot/operations/sizing_rules.json # composite_v3 운영 시: OPS_TECHNIQUE_ID=composite_v3, OPS_MIN_SCORE=2.5, OPS_MTF_ENABLED=true -# 폴더 구조: data|docs / {common, spot, futures} +# 폴더 구조: data|docs / {common, spot} # common — coins.db 등 공유 리소스 # spot — 현물 GT·기법·분석·운영 -# futures — 선물 GT·분석·운영 diff --git a/README.md b/README.md index d3663e6..4ad9690 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -# DeepCoin +# Bithumb -빗썸 KRW 마켓 암호화폐 캔들 수집 및 **현물**·**선물** 매매 전략 파이프라인. +빗썸 KRW 마켓 암호화폐 캔들 수집 및 **현물** 매매 전략 파이프라인. - **기본 축:** 3분봉 현물 BTC, 최근 **10년** 캔들 (`DOWNLOAD_DAYS=3650`) -- **데이터·문서 분류:** `common` (공유) · `spot` (현물) · `futures` (선물) +- **데이터·문서 분류:** `common` (공유) · `spot` (현물) - **현재 운영 전략:** `fractal_swing` + MTF off — paper/live tick 운영 구현 완료 ## 주요 기능 - 빗썸 Public API(v1) 분·일·주·월봉 캔들 수집 (11개 TF, 1분봉 포함) -- SQLite 공유 DB (`data/common/coins.db`) — 현물·선물·MTF 공용 +- SQLite 공유 DB (`data/common/coins.db`) — 현물·MTF 공용 - Ground Truth(GT) 벤치마크 → 39종 인과 기법 분석 → **실거래 운영(paper/live)** - 운영 tick: 캔들 증분 sync, 신호 tail 갱신, 슬리피지·일 체결 상한, 텔레그램 체결 알림 @@ -21,7 +21,7 @@ ## 설치 ```bash -cd DeepCoin +cd Bithumb conda activate ncue # 또는 xavis pip install -r requirements.txt cp .env.example .env # API 키·텔레그램 등 로컬 설정 @@ -56,7 +56,6 @@ OPS_DAILY_MAX_TRADES=100 | **spot 1단계** | GT 타점 완벽 추종 sim 상한선 | GT 자체가 사후 | 불가 | | **spot 2단계** | 39종 인과 기법 평가·MTF 규칙 | 미사용 | 불가 | | **spot 3단계** | paper/live tick 운영 | 미사용 | **가능** | -| **futures 0단계** | 현물 GT → 선물 롱·숏 마커 | — | — | ### 현물 3단계 운영 아키텍처 (fractal_swing) @@ -93,8 +92,8 @@ flowchart TD ## 폴더 구조 ```text -DeepCoin/ -├── src/deepcoin/ +Bithumb/ +├── src/bithumb/ │ ├── api/ # 빗썸 Public·Private REST │ ├── data/ # 캔들 수집·DB·로더 │ ├── ground_truth/ # GT 타점·sim·차트 @@ -112,16 +111,14 @@ DeepCoin/ │ │ ├── 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 차트 + └── spot/ + ├── 0_ground_truth/ # GT 차트 HTML + ├── 1_simulation/ # 1단계 sim 차트 + ├── 2_analysis/ # 2단계 리포트·설계 가이드 + └── 3_operations/ # 운영·백테스트 JSON 리포트 ``` 테이블명: `{SYMBOL}_{인터벌분}` (예: `BTC_3`, `BTC_1440`). 인터벌: 분봉=분 숫자, 일=`1440`, 주=`10080`, 월=`43200`. @@ -136,7 +133,6 @@ flowchart LR B --> C[1_ground_truth_sim] C --> D[2_run_stage2_all] D --> E[3_run_operations] - B --> F[0_ground_truth_futures] ``` | 순서 | 단계 | 스크립트 | 산출물 | @@ -146,7 +142,6 @@ flowchart LR | 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/` | ### 권장 명령 @@ -163,9 +158,6 @@ python scripts/0_ground_truth.py --interval 3 --days 3650 --tier all python scripts/1_ground_truth_sim.py --tier all bash scripts/2_run_stage2_all.sh -# futures 0단계 -python scripts/0_ground_truth_futures.py --tier all - # spot 3단계 — fractal_swing 운영 python scripts/3_run_filtered_backtest.py # 운영 조건 3년 sim 검증 python scripts/3_render_live_chart.py # docs/live 매매 차트 @@ -274,12 +266,6 @@ 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단계는 예정. - --- ## 환경 변수 @@ -359,7 +345,7 @@ OPS_DAILY_MAX_TRADES=20 ## 현물 2단계 인과 기법 (39종) -`src/deepcoin/techniques/` — 단일 33 + 복합 6, 미래 데이터 미사용. +`src/bithumb/techniques/` — 단일 33 + 복합 6, 미래 데이터 미사용. | ID | 기법 | 유형 | |----|------|------| @@ -412,18 +398,17 @@ OPS_DAILY_MAX_TRADES=20 | common | 캔들 수집·증분 sync | 구현됨 | | spot | 0~2단계 (GT·기법·MTF) | 구현됨 | | spot | 3단계 (fractal paper/live·백테스트·텔레그램) | **구현됨** | -| futures | 0단계 GT | 구현됨 | -| futures | 1~3단계 | 예정 | --- ## 변경 이력 +- 2026-06-13: 프로젝트명 **Bithumb**으로 변경, 선물(futures) 파이프라인 제거 - 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유형 구조 재편 +- 2026-06-12: `data/`·`docs/` common/spot 구조 재편 - 2026-06-12: 3단계 운영 파이프라인 초기 구현 (composite_v3 + MTF paper/live) - 2026-06-12: 2단계 인과 기법 분석 파이프라인 완료 - 2026-06-08: Ground Truth v1/v2/v3 diff --git a/docs/live/index.html b/docs/live/index.html index bb16f02..65d2421 100644 --- a/docs/live/index.html +++ b/docs/live/index.html @@ -2,7 +2,7 @@ - DeepCoin Live — 운영 백테스트 차트 + Bithumb Live — 운영 백테스트 차트 -

DeepCoin Live — 운영 백테스트

+

Bithumb Live — 운영 백테스트

BTC · 프랙탈 스윙 (fractal_swing)
sim 기간: 최근 1095일 · 슬리피지 0.05% · 일 체결 상한 100 · - MTF off + MTF off
학습 비율: 매수 100% · 매도 100% (클러스터별 규칙 적용)

3년 수익률 (운영 규칙 sim)
-
+1874019.75%
+
+1885460.27%

매수·매도 타점 차트 열기

    -
  • 매수 53,521 / 매도 53,449 체결
  • -
  • 초기 200,000원 → 최종 3,748,239,499원
  • +
  • 매수 53,519 / 매도 53,444 체결
  • +
  • 초기 200,000원 → 최종 3,771,120,549원
  • 차트: B=매수 S=매도 마커, 이전/다음 타점 탐색, 기간 줌
diff --git a/docs/spot/2_analysis/stage2_design_guide.md b/docs/spot/2_analysis/stage2_design_guide.md index 2235e6c..cbf63a5 100644 --- a/docs/spot/2_analysis/stage2_design_guide.md +++ b/docs/spot/2_analysis/stage2_design_guide.md @@ -1,6 +1,6 @@ # 현물 2단계 설계 가이드 -> DeepCoin 현물 파이프라인 2단계(인과 기법 분석)의 목적, 구조, 설계 근거를 정리한 문서입니다. +> Bithumb 현물 파이프라인 2단계(인과 기법 분석)의 목적, 구조, 설계 근거를 정리한 문서입니다. > 작성 기준: 2026-06-12 · 기본 TF: 3분봉 · GT: v3 --- @@ -57,7 +57,7 @@ ### 파이프라인 실행 ```bash -cd DeepCoin +cd Bithumb export PYTHONPATH=src bash scripts/2_run_stage2_all.sh ``` @@ -119,7 +119,7 @@ bash scripts/2_run_stage2_all.sh 1, 3, 5, 10, 15, 30, 60, 240분, 일(1440), 주(10080), 월(43200) -**TF별 피처** (`src/deepcoin/mtf/features.py`) +**TF별 피처** (`src/bithumb/mtf/features.py`) | 피처 | 용도 | |------|------| @@ -224,7 +224,7 @@ flowchart TD - 주봉 하락 추세 3분 돌파 매수 → 가짜 돌파 - 월봉 과매도 3분 매도 → 바닥 청산 -등의 문제가 발생합니다. DeepCoin은 이를 **MTF 레이어**로 보완합니다. +등의 문제가 발생합니다. Bithumb은 이를 **MTF 레이어**로 보완합니다. | 상황 | MTF 해석 | 의도 | |------|----------|------| @@ -299,12 +299,12 @@ flowchart TD | 모듈 | 경로 | |------|------| -| 기법 실행 | `scripts/2_run_techniques.py`, `src/deepcoin/techniques/runner.py` | -| GT 정합 | `src/deepcoin/evaluation/gt_align.py` | -| MTF 피처 | `src/deepcoin/mtf/features.py`, `extractor.py`, `store.py` | -| MTF 규칙 | `src/deepcoin/mtf/rules.py` | -| MTF 필터 | `src/deepcoin/mtf/filter.py`, `trend_gate.py` | -| 통합 기법 | `src/deepcoin/techniques/composite_v3.py` | +| 기법 실행 | `scripts/2_run_techniques.py`, `src/bithumb/techniques/runner.py` | +| GT 정합 | `src/bithumb/evaluation/gt_align.py` | +| MTF 피처 | `src/bithumb/mtf/features.py`, `extractor.py`, `store.py` | +| MTF 규칙 | `src/bithumb/mtf/rules.py` | +| MTF 필터 | `src/bithumb/mtf/filter.py`, `trend_gate.py` | +| 통합 기법 | `src/bithumb/techniques/composite_v3.py` | --- diff --git a/docs/spot/2_analysis/stage2_final_summary.md b/docs/spot/2_analysis/stage2_final_summary.md index df85f67..ed85302 100644 --- a/docs/spot/2_analysis/stage2_final_summary.md +++ b/docs/spot/2_analysis/stage2_final_summary.md @@ -1,6 +1,6 @@ # 현물 2단계 최종 정리 — 결과 해석 및 운영 권고 -> DeepCoin 현물 파이프라인 2단계(인과 기법 분석) 완료 후 종합 정리 문서 +> Bithumb 현물 파이프라인 2단계(인과 기법 분석) 완료 후 종합 정리 문서 > 작성 기준: 2026-06-12 · 데이터: BTC · 3분봉 · GT v3 · 분석 기간 3650일 · sim 기간 최근 3년(1095일) --- diff --git a/docs/spot/3_operations/stage3_design_guide.md b/docs/spot/3_operations/stage3_design_guide.md index a6efefb..f85248c 100644 --- a/docs/spot/3_operations/stage3_design_guide.md +++ b/docs/spot/3_operations/stage3_design_guide.md @@ -47,7 +47,7 @@ paper / live 체결 (구간별 매수 상한 동일) | 일괄 | `3_run_stage3_all.sh` | 3-1 + 3-2 paper | ```bash -cd DeepCoin +cd Bithumb export PYTHONPATH=src # MTF 필터 백테스트 @@ -87,12 +87,12 @@ bash scripts/3_run_stage3_all.sh | 모듈 | 경로 | |------|------| -| 신호 파이프라인 | `src/deepcoin/operations/signal_pipeline.py` | -| signal_type 추론 | `src/deepcoin/operations/signal_type.py` | -| 체결 엔진 | `src/deepcoin/operations/trade_engine.py` | -| paper/live | `src/deepcoin/operations/executor.py` | -| 러너 | `src/deepcoin/operations/runner.py` | -| 빗썸 Private | `src/deepcoin/api/bithumb_private.py` | +| 신호 파이프라인 | `src/bithumb/operations/signal_pipeline.py` | +| signal_type 추론 | `src/bithumb/operations/signal_type.py` | +| 체결 엔진 | `src/bithumb/operations/trade_engine.py` | +| paper/live | `src/bithumb/operations/executor.py` | +| 러너 | `src/bithumb/operations/runner.py` | +| 빗썸 Private | `src/bithumb/api/bithumb_private.py` | --- diff --git a/scripts/00_download_candles.py b/scripts/00_download_candles.py index c4c5daf..89a4d6b 100644 --- a/scripts/00_download_candles.py +++ b/scripts/00_download_candles.py @@ -16,10 +16,10 @@ if str(SRC) not in sys.path: from dataclasses import replace -from deepcoin.config import load_settings -from deepcoin.data.candle_store import CandleStore -from deepcoin.data.downloader import CandleDownloader -from deepcoin.data.intervals import INTERVAL_1MIN, estimate_download_requests, interval_label +from bithumb.config import load_settings +from bithumb.data.candle_store import CandleStore +from bithumb.data.downloader import CandleDownloader +from bithumb.data.intervals import INTERVAL_1MIN, estimate_download_requests, interval_label def _configure_logging(verbose: bool) -> None: diff --git a/scripts/0_ground_truth.py b/scripts/0_ground_truth.py index 9bcb374..f62f169 100644 --- a/scripts/0_ground_truth.py +++ b/scripts/0_ground_truth.py @@ -15,10 +15,10 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import Settings, load_settings -from deepcoin.data.intervals import interval_label -from deepcoin.ground_truth.chart import render_ground_truth_chart -from deepcoin.ground_truth.ground_truth import GtParams, build_ground_truth, save_ground_truth +from bithumb.config import Settings, load_settings +from bithumb.data.intervals import interval_label +from bithumb.ground_truth.chart import render_ground_truth_chart +from bithumb.ground_truth.ground_truth import GtParams, build_ground_truth, save_ground_truth TIER_DESCRIPTIONS = { "v1": "스윙만 (최소 매수·매도)", diff --git a/scripts/0_ground_truth_futures.py b/scripts/0_ground_truth_futures.py deleted file mode 100644 index 9fb1e08..0000000 --- a/scripts/0_ground_truth_futures.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -"""0단계: 선물 GT 타점 차트 (현물 GT → 롱·숏 4색 마커).""" - -from __future__ import annotations - -import argparse -import json -import logging -import sys -from copy import deepcopy -from pathlib import Path -from typing import Any - -ROOT = Path(__file__).resolve().parents[1] -SRC = ROOT / "src" -if str(SRC) not in sys.path: - sys.path.insert(0, str(SRC)) - -from deepcoin.config import Settings, load_settings -from deepcoin.ground_truth.futures import futures_events_from_gt_signals -from deepcoin.ground_truth.futures_chart import render_futures_ground_truth_chart -from deepcoin.ground_truth.ground_truth import save_ground_truth - -TIER_DESCRIPTIONS = { - "v1": "스윙만 (최소 매수·매도)", - "v2": "스윙 + 눌림목", - "v3": "스윙 + 눌림목 + 돌파 + 다이버전스", -} - - -def _configure_logging(verbose: bool) -> None: - """로깅 레벨을 설정한다.""" - level = logging.DEBUG if verbose else logging.INFO - logging.basicConfig( - level=level, - format="%(asctime)s [%(levelname)s] %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - ) - - -def _tier_targets( - settings: Settings, - tier_arg: str, -) -> list[tuple[str, Path, Path, Path]]: - """생성할 티어 목록 (tier, spot_json, futures_json, futures_chart).""" - all_tiers: dict[str, tuple[Path, Path, Path]] = { - "v1": ( - settings.ground_truth_v1_file, - settings.ground_truth_futures_v1_file, - settings.ground_truth_futures_chart_v1_file, - ), - "v2": ( - settings.ground_truth_v2_file, - settings.ground_truth_futures_v2_file, - settings.ground_truth_futures_chart_v2_file, - ), - "v3": ( - settings.ground_truth_file, - settings.ground_truth_futures_file, - settings.ground_truth_futures_chart_v3_file, - ), - } - if tier_arg == "all": - return [(t, *paths) for t, paths in all_tiers.items()] - return [(tier_arg, *all_tiers[tier_arg])] - - -def _load_gt(json_path: Path) -> dict[str, Any]: - """GT JSON을 로드한다.""" - with json_path.open(encoding="utf-8") as fp: - return json.load(fp) - - -def _build_futures_gt(spot_gt: dict[str, Any]) -> dict[str, Any]: - """현물 GT JSON을 선물 GT JSON으로 변환한다.""" - signals = spot_gt.get("signals") or [] - futures_gt = deepcopy(spot_gt) - futures_gt["meta"] = { - **spot_gt.get("meta", {}), - "market_type": "futures", - "source": "spot_ground_truth", - } - futures_gt["futures_events"] = futures_events_from_gt_signals(signals) - return futures_gt - - -def _print_summary( - tier: str, - gt_result: dict[str, Any], - json_path: Path, - chart_path: Path, -) -> None: - """티어별 선물 GT 요약을 출력한다.""" - meta = gt_result["meta"] - summary = gt_result["summary"] - print(f"\n=== 선물 GT {tier.upper()} ({TIER_DESCRIPTIONS[tier]}) ===") - print(f"대상: {meta['symbol']} ({meta['interval_label']})") - print(f"GT 기간: {meta['data_from']} ~ {meta['data_to']}") - print( - f"선물 GT 타점: 매수 {summary['buy_count']} / 매도 {summary['sell_count']} " - f"→ 선물 상방·하방 각 {summary['buy_count']}/{summary['sell_count']} 마커" - ) - print(f"JSON: {json_path}") - print(f"차트: {chart_path}") - - -def main() -> int: - """CLI 진입점.""" - parser = argparse.ArgumentParser( - description="선물 GT JSON·차트 생성 (현물 GT → 롱·숏 4색)" - ) - parser.add_argument( - "--tier", - choices=("v1", "v2", "v3", "all"), - default="all", - help="대상 GT 버전", - ) - parser.add_argument("-v", "--verbose", action="store_true") - args = parser.parse_args() - - _configure_logging(args.verbose) - settings = load_settings() - tiers = _tier_targets(settings, args.tier) - - print("\n=== 선물 Ground Truth 생성 ===") - print("현물 GT 타점 → L↑상방매수 L↓상방매도 S↓하방매수 S↑하방매도") - - for tier, spot_json_path, futures_json_path, chart_path in tiers: - if not spot_json_path.exists(): - logging.error( - "현물 GT JSON 없음: %s — 먼저 0_ground_truth.py 실행", - spot_json_path, - ) - return 1 - - spot_gt = _load_gt(spot_json_path) - futures_gt = _build_futures_gt(spot_gt) - save_ground_truth(futures_gt, futures_json_path) - - render_futures_ground_truth_chart( - db_path=settings.db_path, - symbol=settings.symbol, - gt_result=spot_gt, - output_path=chart_path, - chart_lookback_days=settings.download_days, - ) - _print_summary(tier, futures_gt, futures_json_path, chart_path) - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/1_ground_truth_sim.py b/scripts/1_ground_truth_sim.py index fc68dec..215ffd6 100644 --- a/scripts/1_ground_truth_sim.py +++ b/scripts/1_ground_truth_sim.py @@ -16,12 +16,12 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import Settings, load_settings -from deepcoin.data.candle_loader import load_candles -from deepcoin.data.intervals import interval_label -from deepcoin.ground_truth.chart import render_ground_truth_sim_chart -from deepcoin.ground_truth.ground_truth import GtParams, build_ground_truth, save_ground_truth -from deepcoin.ground_truth.pnl import simulate_gt_signals_pnl +from bithumb.config import Settings, load_settings +from bithumb.data.candle_loader import load_candles +from bithumb.data.intervals import interval_label +from bithumb.ground_truth.chart import render_ground_truth_sim_chart +from bithumb.ground_truth.ground_truth import GtParams, build_ground_truth, save_ground_truth +from bithumb.ground_truth.pnl import simulate_gt_signals_pnl TIER_DESCRIPTIONS = { "v1": "스윙만 (최소 매수·매도)", diff --git a/scripts/1_tune_order_sizing.py b/scripts/1_tune_order_sizing.py new file mode 100644 index 0000000..7b26a0c --- /dev/null +++ b/scripts/1_tune_order_sizing.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""1단계: 연속 매수·매도 클러스터별 매수·매도 비율 튜닝 (타점 고정).""" + +from __future__ import annotations + +import argparse +import json +import logging +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SRC = ROOT / "src" +if str(SRC) not in sys.path: + sys.path.insert(0, str(SRC)) + +from bithumb.config import load_settings +from bithumb.ground_truth.sizing_rules import save_sizing_rules +from bithumb.ground_truth.sizing_tune import tune_sizing_rules +from bithumb.operations.signal_pipeline import run_signal_pipeline + + +def _configure_logging(verbose: bool) -> None: + level = logging.DEBUG if verbose else logging.INFO + logging.basicConfig( + level=level, + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + +def main() -> int: + """CLI 진입점.""" + parser = argparse.ArgumentParser( + description="연속 매수·매도 클러스터 상태별 사이징 비율 학습", + ) + parser.add_argument( + "-o", + "--output", + type=str, + default=None, + help="규칙 JSON 경로 (기본: OPS_SIZING_RULES_JSON)", + ) + parser.add_argument( + "--min-bucket-samples", + type=int, + default=5, + help="클러스터 버킷별 최소 샘플 수", + ) + parser.add_argument("-v", "--verbose", action="store_true") + args = parser.parse_args() + _configure_logging(args.verbose) + + settings = load_settings() + output_path = Path(args.output) if args.output else settings.ops_sizing_rules_json + if not output_path.is_absolute(): + output_path = ROOT / output_path + + print("\n=== 매수·매도 비율 튜닝 (타점 고정) ===", flush=True) + print( + f"기법: {settings.ops_technique_id} · sim {settings.gt_sim_lookback_days}일 · " + f"기본 {settings.ops_buy_cash_pct:.0%}/{settings.ops_sell_coin_pct:.0%}", + flush=True, + ) + + pipeline = run_signal_pipeline(settings, use_cache=True) + rules, final_sim = tune_sizing_rules( + settings, + pipeline["kept"], + data_end=pipeline["data_end"], + last_mark_price=pipeline["last_price"], + technique_id=pipeline["technique_id"], + min_bucket_samples=args.min_bucket_samples, + ) + save_sizing_rules(rules, output_path) + + tuning = rules.get("tuning") or {} + print(f"\n기준(고정 10%): {tuning.get('baseline_return_pct'):+.2f}%") + print( + f"학습 후: {final_sim.get('total_return_pct'):+.2f}% · " + f"매수/매도 {final_sim.get('buys_executed')}/{final_sim.get('sells_executed')}" + ) + print( + f"전역 비율: 매수 {rules.get('default_buy_cash_pct', 0):.0%} · " + f"매도 {rules.get('default_sell_coin_pct', 0):.0%}" + ) + by_cluster = rules.get("by_cluster") or {} + if by_cluster.get("buy") or by_cluster.get("sell"): + print("클러스터별:") + print(json.dumps(by_cluster, ensure_ascii=False, indent=2)) + print(f"\n규칙 JSON: {output_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/2_render_best_technique_chart.py b/scripts/2_render_best_technique_chart.py index fd5bfd2..967272f 100644 --- a/scripts/2_render_best_technique_chart.py +++ b/scripts/2_render_best_technique_chart.py @@ -14,15 +14,15 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.data.candle_loader import load_candles -from deepcoin.evaluation.causal_sim import ( +from bithumb.config import load_settings +from bithumb.data.candle_loader import load_candles +from bithumb.evaluation.causal_sim import ( best_technique_chart_path, pick_best_technique_row, render_best_technique_comparison_chart, run_technique_causal_sim, ) -from deepcoin.techniques.runner import load_ground_truth, load_technique_result +from bithumb.techniques.runner import load_ground_truth, load_technique_result def _configure_logging(verbose: bool) -> None: diff --git a/scripts/2_run_causal_sim.py b/scripts/2_run_causal_sim.py index d42947b..444ab93 100644 --- a/scripts/2_run_causal_sim.py +++ b/scripts/2_run_causal_sim.py @@ -14,10 +14,10 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.data.candle_loader import load_candles -from deepcoin.data.intervals import interval_label -from deepcoin.evaluation.causal_sim import ( +from bithumb.config import load_settings +from bithumb.data.candle_loader import load_candles +from bithumb.data.intervals import interval_label +from bithumb.evaluation.causal_sim import ( best_technique_chart_path, build_causal_sim_report, pick_best_technique_row, @@ -28,7 +28,7 @@ from deepcoin.evaluation.causal_sim import ( save_causal_sim_report, technique_sim_chart_path, ) -from deepcoin.techniques.runner import load_ground_truth, load_technique_results +from bithumb.techniques.runner import load_ground_truth, load_technique_results def _configure_logging(verbose: bool) -> None: diff --git a/scripts/2_run_mtf_analysis.py b/scripts/2_run_mtf_analysis.py index dfdadee..53b5b41 100644 --- a/scripts/2_run_mtf_analysis.py +++ b/scripts/2_run_mtf_analysis.py @@ -13,16 +13,16 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.evaluation.mtf_report import ( +from bithumb.config import load_settings +from bithumb.evaluation.mtf_report import ( build_mtf_correlation_report, render_mtf_html, save_mtf_report, ) -from deepcoin.mtf.extractor import MtfFeatureExtractor -from deepcoin.mtf.rules import derive_rules_from_report, save_mtf_rules -from deepcoin.mtf.store import MultiTimeframeStore -from deepcoin.techniques.runner import load_ground_truth +from bithumb.mtf.extractor import MtfFeatureExtractor +from bithumb.mtf.rules import derive_rules_from_report, save_mtf_rules +from bithumb.mtf.store import MultiTimeframeStore +from bithumb.techniques.runner import load_ground_truth def _configure_logging(verbose: bool) -> None: diff --git a/scripts/2_run_signal_type_align.py b/scripts/2_run_signal_type_align.py index ddef3ff..f2ac754 100644 --- a/scripts/2_run_signal_type_align.py +++ b/scripts/2_run_signal_type_align.py @@ -13,21 +13,21 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.data.intervals import interval_label -from deepcoin.evaluation.report import ( +from bithumb.config import load_settings +from bithumb.data.intervals import interval_label +from bithumb.evaluation.report import ( build_comparison_report, render_comparison_html, save_comparison_report, ) -from deepcoin.evaluation.signal_type_report import ( +from bithumb.evaluation.signal_type_report import ( build_signal_type_report, render_signal_type_html, save_signal_type_report, ) -from deepcoin.techniques.base import TechniqueParams -from deepcoin.techniques.registry import list_technique_ids -from deepcoin.techniques.runner import ( +from bithumb.techniques.base import TechniqueParams +from bithumb.techniques.registry import list_technique_ids +from bithumb.techniques.runner import ( load_ground_truth, load_technique_results, run_all_techniques, diff --git a/scripts/2_run_stage2_all.sh b/scripts/2_run_stage2_all.sh index 0902143..dd92669 100755 --- a/scripts/2_run_stage2_all.sh +++ b/scripts/2_run_stage2_all.sh @@ -4,7 +4,7 @@ set -euo pipefail cd "$(dirname "$0")/.." export PYTHONPATH=src PY="${PY:-/opt/anaconda3/envs/ncue/bin/python}" -LOG="${LOG:-/tmp/deepcoin_stage2.log}" +LOG="${LOG:-/tmp/bithumb_stage2.log}" echo "=== 현물 2단계 파이프라인 시작 $(date '+%Y-%m-%d %H:%M:%S') ===" | tee "$LOG" diff --git a/scripts/2_run_techniques.py b/scripts/2_run_techniques.py index ebdec37..0d997aa 100644 --- a/scripts/2_run_techniques.py +++ b/scripts/2_run_techniques.py @@ -13,16 +13,16 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.data.intervals import interval_label -from deepcoin.evaluation.report import ( +from bithumb.config import load_settings +from bithumb.data.intervals import interval_label +from bithumb.evaluation.report import ( build_comparison_report, render_comparison_html, save_comparison_report, ) -from deepcoin.techniques.base import TechniqueParams -from deepcoin.techniques.registry import list_technique_ids -from deepcoin.techniques.runner import ( +from bithumb.techniques.base import TechniqueParams +from bithumb.techniques.registry import list_technique_ids +from bithumb.techniques.runner import ( load_ground_truth, run_all_techniques, save_technique_result, diff --git a/scripts/3_init_live_state.py b/scripts/3_init_live_state.py new file mode 100755 index 0000000..c8e17a1 --- /dev/null +++ b/scripts/3_init_live_state.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""paper → live 상태 전환 — 거래소 잔고 동기화.""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SRC = ROOT / "src" +if str(SRC) not in sys.path: + sys.path.insert(0, str(SRC)) + +from bithumb.config import load_settings +from bithumb.notifications.telegram import create_telegram_notifier +from bithumb.operations.live_bootstrap import init_live_state + + +def main() -> int: + """CLI 진입점.""" + parser = argparse.ArgumentParser(description="Bithumb live 상태 초기화") + parser.add_argument( + "--no-backup", + action="store_true", + help="기존 state JSON 백업 생략", + ) + parser.add_argument( + "--reset-bar-cursor", + action="store_true", + help="last_processed_bar_index 를 -1 로 초기화", + ) + parser.add_argument( + "--yes", + action="store_true", + help="확인 프롬프트 생략", + ) + args = parser.parse_args() + + settings = load_settings() + if settings.ops_mode != "live": + print(f"OPS_MODE={settings.ops_mode} — .env 에 OPS_MODE=live 설정 후 실행하세요.", file=sys.stderr) + return 1 + if not settings.bithumb_access_key or not settings.bithumb_secret_key: + print("BITHUMB API 키가 필요합니다.", file=sys.stderr) + return 1 + + if not args.yes: + print("경고: live 상태 초기화 — 거래소 잔고가 state JSON에 반영됩니다.") + print(f"state: {settings.ops_state_json}") + answer = input("계속하시겠습니까? [y/N]: ").strip().lower() + if answer not in ("y", "yes"): + print("취소됨.") + return 1 + + result = init_live_state( + settings, + backup=not args.no_backup, + preserve_bar_cursor=not args.reset_bar_cursor, + ) + bal = result["balances"] + + print("=== live 상태 초기화 완료 ===") + if result["backup_path"]: + print(f"백업: {result['backup_path']}") + print(f"state: {result['state_path']}") + print( + f"잔고: KRW {bal['cash_krw']:,.0f}원 · " + f"{settings.symbol} {bal['coin_qty']:.8f}" + ) + print(f"bar cursor: {result['last_processed_bar_index']}") + + telegram = create_telegram_notifier( + settings.telegram_bot_token, + settings.telegram_chat_id, + enabled=settings.ops_telegram_enabled, + ) + if telegram.is_active: + telegram.send_message( + "[Bithumb] LIVE 세션 시작\n" + f"{settings.coin_name} ({settings.symbol}) | {settings.ops_technique_id}\n" + f"KRW {bal['cash_krw']:,.0f} · {settings.symbol} {bal['coin_qty']:.8f}\n" + f"일 체결 상한 {settings.ops_daily_max_trades} · 슬리피지 {settings.ops_slippage_rate * 100:.2f}%" + ) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/3_preflight_live.py b/scripts/3_preflight_live.py new file mode 100755 index 0000000..f129319 --- /dev/null +++ b/scripts/3_preflight_live.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""live 운영 전 사전 점검 — API·잔고·캐시·텔레그램.""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SRC = ROOT / "src" +if str(SRC) not in sys.path: + sys.path.insert(0, str(SRC)) + +from bithumb.config import load_settings +from bithumb.operations.live_bootstrap import run_preflight, save_preflight_report + + +def main() -> int: + """CLI 진입점.""" + parser = argparse.ArgumentParser(description="Bithumb live 사전 점검") + parser.add_argument( + "--report", + default="docs/spot/3_operations/live_preflight_report.json", + help="점검 결과 JSON 경로", + ) + parser.add_argument( + "--require-live", + action="store_true", + help="OPS_MODE=live 가 아니면 실패", + ) + args = parser.parse_args() + + settings = load_settings() + if args.require_live and settings.ops_mode != "live": + print(f"OPS_MODE={settings.ops_mode} — live 전환 후 다시 실행하세요.", file=sys.stderr) + return 1 + + report = run_preflight(settings) + report_path = save_preflight_report( + ROOT / args.report if not Path(args.report).is_absolute() else Path(args.report), + report, + ) + + print("=== Bithumb live 사전 점검 ===") + print(f"기법: {report['technique_id']} · 마켓: {report['market']} · 모드: {report['mode']}") + if report.get("balances"): + bal = report["balances"] + print( + f"잔고: KRW {bal['cash_krw']:,.0f}원 · " + f"{report['symbol']} {bal['coin_qty']:.8f}" + ) + print() + for row in report["checks"]: + mark = "OK" if row["passed"] else "NG" + req = "필수" if row["required"] else "선택" + print(f"[{mark}] ({req}) {row['name']}: {row['detail']}") + print(f"\n리포트: {report_path}") + + if report["ok"]: + print("\n사전 점검 통과 — live 운영을 시작할 수 있습니다.") + return 0 + + print("\n사전 점검 실패 — live 시작 전 위 항목을 해결하세요.", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/3_render_live_chart.py b/scripts/3_render_live_chart.py index 32d415d..93e196f 100644 --- a/scripts/3_render_live_chart.py +++ b/scripts/3_render_live_chart.py @@ -13,8 +13,8 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.operations.chart import render_ops_live_chart +from bithumb.config import load_settings +from bithumb.operations.chart import render_ops_live_chart def _configure_logging(verbose: bool) -> None: @@ -34,12 +34,21 @@ def _write_index_html( """docs/live 인덱스 HTML을 생성한다.""" sim = report["filtered_sim"] ret = sim.get("total_return_pct", 0) + sizing_line = "" + if sim.get("sizing_rules_applied"): + lb = sim.get("learned_default_buy_cash_pct") + ls = sim.get("learned_default_sell_coin_pct") + if lb is not None and ls is not None: + sizing_line = ( + f"
학습 비율: 매수 {float(lb) * 100:.0f}% · 매도 {float(ls) * 100:.0f}%" + f" (클러스터별 규칙 적용)" + ) index_path.parent.mkdir(parents=True, exist_ok=True) html = f""" - DeepCoin Live — 운영 백테스트 차트 + Bithumb Live — 운영 백테스트 차트 -

DeepCoin Live — 운영 백테스트

+

Bithumb Live — 운영 백테스트

{report.get("symbol", "BTC")} · {report.get("technique_name", "")} ({report.get("technique_id", "")})
sim 기간: 최근 {report.get("sim_lookback_days", 1095)}일 · 슬리피지 {report.get("slippage_rate", 0) * 100:.2f}% · 일 체결 상한 {report.get("daily_max_trades", "-")} · - MTF {"on" if report.get("mtf_enabled") else "off"} + MTF {"on" if report.get("mtf_enabled") else "off"}{sizing_line}

3년 수익률 (운영 규칙 sim)
diff --git a/scripts/3_run_filtered_backtest.py b/scripts/3_run_filtered_backtest.py index ca77b4e..d8ca614 100644 --- a/scripts/3_run_filtered_backtest.py +++ b/scripts/3_run_filtered_backtest.py @@ -13,8 +13,8 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.operations.backtest import run_filtered_backtest, save_backtest_report +from bithumb.config import load_settings +from bithumb.operations.backtest import run_filtered_backtest, save_backtest_report def _configure_logging(verbose: bool) -> None: diff --git a/scripts/3_run_fractal_live.sh b/scripts/3_run_fractal_live.sh new file mode 100755 index 0000000..aa080af --- /dev/null +++ b/scripts/3_run_fractal_live.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# fractal_swing LIVE 운영 — 사전 점검 → 상태 초기화 → 180초 tick loop +set -euo pipefail +cd "$(dirname "$0")/.." +export PYTHONPATH=src + +if [[ "${OPS_MODE:-}" != "live" ]]; then + if grep -q '^OPS_MODE=live' .env 2>/dev/null; then + : + else + echo "오류: .env 에 OPS_MODE=live 가 필요합니다." >&2 + exit 1 + fi +fi + +echo "=== 1/3 live 사전 점검 ===" +python scripts/3_preflight_live.py --require-live + +echo "" +echo "=== 2/3 live 상태 초기화 (거래소 잔고 동기화) ===" +python scripts/3_init_live_state.py --yes + +echo "" +echo "=== 3/3 LIVE 운영 (180초 loop) — Ctrl+C 종료 ===" +echo "경고: 실제 빗썸 시장가 주문이 발생합니다." +python scripts/3_run_operations.py --mode live --loop 180 diff --git a/scripts/3_run_fractal_realistic_backtest.py b/scripts/3_run_fractal_realistic_backtest.py index 37395b5..d050910 100644 --- a/scripts/3_run_fractal_realistic_backtest.py +++ b/scripts/3_run_fractal_realistic_backtest.py @@ -14,10 +14,10 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.evaluation.causal_sim import normalize_signals_for_sim -from deepcoin.ground_truth.pnl import simulate_gt_signals_pnl -from deepcoin.operations.signal_pipeline import ( +from bithumb.config import load_settings +from bithumb.evaluation.causal_sim import normalize_signals_for_sim +from bithumb.ground_truth.pnl import simulate_gt_signals_pnl +from bithumb.operations.signal_pipeline import ( _signals_in_lookback, generate_raw_signals, load_ops_candles, @@ -64,6 +64,8 @@ def main() -> int: min_order_krw=settings.ops_min_order_krw, slippage_rate=sc["slippage_rate"], daily_max_trades=sc["daily_max_trades"], + buy_cash_pct=settings.ops_buy_cash_pct, + sell_coin_pct=settings.ops_sell_coin_pct, sim_lookback_days=settings.gt_sim_lookback_days, data_end=end, last_mark_price=price, diff --git a/scripts/3_run_operations.py b/scripts/3_run_operations.py index 6463916..2cb4f97 100644 --- a/scripts/3_run_operations.py +++ b/scripts/3_run_operations.py @@ -14,8 +14,8 @@ SRC = ROOT / "src" if str(SRC) not in sys.path: sys.path.insert(0, str(SRC)) -from deepcoin.config import load_settings -from deepcoin.operations.runner import OperationsRunner +from bithumb.config import load_settings +from bithumb.operations.runner import OperationsRunner def _configure_logging(verbose: bool) -> None: @@ -29,7 +29,7 @@ def _configure_logging(verbose: bool) -> None: def main() -> int: """CLI 진입점.""" - parser = argparse.ArgumentParser(description="3단계: DeepCoin 운영 tick") + parser = argparse.ArgumentParser(description="3단계: Bithumb 운영 tick") parser.add_argument( "--mode", choices=("paper", "live"), @@ -67,19 +67,37 @@ def main() -> int: sync = not args.no_sync while True: - report = runner.tick(sync_candles=sync) - port = report["portfolio"] + try: + report = runner.tick(sync_candles=sync) + except Exception as exc: + logging.exception("운영 tick 예외 (복구 후 계속)") + if runner.telegram.is_active: + runner.telegram.notify_ops_error( + mode=settings.ops_mode, + symbol=settings.symbol, + technique_id=settings.ops_technique_id, + stage="main_loop", + error=str(exc), + ) + if args.loop <= 0: + break + time.sleep(args.loop) + continue + + port = report.get("portfolio") or {} print("\n=== 3단계 운영 tick ===") - print(f"모드: {report['mode']}") + if report.get("error"): + print(f"오류: [{report.get('error_stage')}] {report.get('error_message')}") + print(f"모드: {report.get('mode', settings.ops_mode)}") print( f"최신 봉 후보: {report.get('latest_bar_candidates', 0)} · " - f"필터 통과: {report['filtered_signals']} · " + f"필터 통과: {report.get('filtered_signals', 0)} · " f"처리 bar: {report.get('pending_bars', [])}" ) - print(f"이번 체결: {len(report['executions'])}건") + print(f"이번 체결: {len(report.get('executions', []))}건") print( - f"포트폴리오: 현금 {port['cash_krw']:,.0f}원 · " - f"코인 {port['coin_qty']:.8f} {settings.symbol}" + f"포트폴리오: 현금 {port.get('cash_krw', 0):,.0f}원 · " + f"코인 {port.get('coin_qty', 0):.8f} {settings.symbol}" ) print(f"리포트: {settings.ops_report_json}") diff --git a/src/bithumb/__init__.py b/src/bithumb/__init__.py new file mode 100644 index 0000000..f89ee53 --- /dev/null +++ b/src/bithumb/__init__.py @@ -0,0 +1,3 @@ +"""Bithumb — 빗썸 암호화폐 데이터 수집·분석.""" + +__version__ = "0.1.0" diff --git a/src/deepcoin/api/__init__.py b/src/bithumb/api/__init__.py similarity index 100% rename from src/deepcoin/api/__init__.py rename to src/bithumb/api/__init__.py diff --git a/src/deepcoin/api/bithumb.py b/src/bithumb/api/bithumb.py similarity index 98% rename from src/deepcoin/api/bithumb.py rename to src/bithumb/api/bithumb.py index cc3affa..6f36482 100644 --- a/src/deepcoin/api/bithumb.py +++ b/src/bithumb/api/bithumb.py @@ -9,7 +9,7 @@ from typing import Any import requests -from deepcoin.data.intervals import INTERVAL_DAILY, INTERVAL_MONTHLY, INTERVAL_WEEKLY +from bithumb.data.intervals import INTERVAL_DAILY, INTERVAL_MONTHLY, INTERVAL_WEEKLY logger = logging.getLogger(__name__) diff --git a/src/deepcoin/api/bithumb_auth.py b/src/bithumb/api/bithumb_auth.py similarity index 100% rename from src/deepcoin/api/bithumb_auth.py rename to src/bithumb/api/bithumb_auth.py diff --git a/src/deepcoin/api/bithumb_private.py b/src/bithumb/api/bithumb_private.py similarity index 73% rename from src/deepcoin/api/bithumb_private.py rename to src/bithumb/api/bithumb_private.py index 1498fde..9775fe5 100644 --- a/src/deepcoin/api/bithumb_private.py +++ b/src/bithumb/api/bithumb_private.py @@ -3,16 +3,42 @@ from __future__ import annotations import logging +import math import time from typing import Any import requests -from deepcoin.api.bithumb_auth import auth_headers, dumps_params +from bithumb.api.bithumb_auth import auth_headers, dumps_params logger = logging.getLogger(__name__) +class BithumbAPIError(Exception): + """빗썸 Private API 오류.""" + + def __init__(self, status_code: int, error_name: str, message: str) -> None: + self.status_code = status_code + self.error_name = error_name + self.message = message + super().__init__(f"{error_name}: {message} (HTTP {status_code})") + + +def _parse_error_response(response: requests.Response) -> BithumbAPIError: + """HTTP 오류 응답을 BithumbAPIError로 변환한다.""" + error_name = "http_error" + message = response.text[:500] + try: + payload = response.json() + err = payload.get("error", payload) + if isinstance(err, dict): + error_name = str(err.get("name", error_name)) + message = str(err.get("message", message)) + except ValueError: + pass + return BithumbAPIError(response.status_code, error_name, message) + + class BithumbPrivateClient: """빗썸 v2.1 Private API 클라이언트.""" @@ -72,6 +98,18 @@ class BithumbPrivateClient: logger.warning("Rate limit 429 — %ss 대기", wait) time.sleep(wait) continue + if response.status_code >= 400: + api_error = _parse_error_response(response) + logger.warning( + "Private API error (%s/%s): %s", + attempt, + self.retries, + api_error, + ) + last_error = api_error + wait = self.sleep_sec * attempt * 2 + time.sleep(wait) + continue response.raise_for_status() payload = response.json() time.sleep(self.sleep_sec) @@ -116,10 +154,13 @@ class BithumbPrivateClient: Returns: 주문 응답 dict. """ + order_krw = max(math.floor(float(krw_amount)), 0) + if order_krw <= 0: + raise ValueError("매수 원화 금액이 0 이하입니다.") params = { "market": market, "side": "bid", - "price": str(int(krw_amount)), + "price": str(order_krw), "ord_type": "price", } return self._request("POST", "/v1/orders", params=params) diff --git a/src/deepcoin/config.py b/src/bithumb/config.py similarity index 87% rename from src/deepcoin/config.py rename to src/bithumb/config.py index ba09874..75d0cfc 100644 --- a/src/deepcoin/config.py +++ b/src/bithumb/config.py @@ -8,7 +8,7 @@ from pathlib import Path from dotenv import load_dotenv -from deepcoin.data.intervals import DEFAULT_DOWNLOAD_INTERVALS +from bithumb.data.intervals import DEFAULT_DOWNLOAD_INTERVALS _PROJECT_ROOT = Path(__file__).resolve().parents[2] @@ -37,7 +37,7 @@ def _parse_int_list(raw: str) -> list[int]: @dataclass(frozen=True) class Settings: - """DeepCoin 실행 설정.""" + """Bithumb 실행 설정.""" symbol: str coin_name: str @@ -48,7 +48,7 @@ class Settings: db_path: Path request_sleep_sec: float request_retries: int - # 0단계: GT 타점 (현물·선물 공통 파라미터) + # 0단계: GT 타점 (현물) gt_interval_min: int gt_lookback_days: int gt_zigzag_reversal_pct: float @@ -68,12 +68,6 @@ class Settings: ground_truth_chart_v1_file: Path ground_truth_chart_v2_file: Path ground_truth_chart_v3_file: Path - ground_truth_futures_file: Path - ground_truth_futures_v1_file: Path - ground_truth_futures_v2_file: Path - ground_truth_futures_chart_v1_file: Path - ground_truth_futures_chart_v2_file: Path - ground_truth_futures_chart_v3_file: Path # 현물 1단계: GT sim ground_truth_chart_sim_v1_file: Path ground_truth_chart_sim_v2_file: Path @@ -107,6 +101,11 @@ class Settings: ops_daily_max_trades: int ops_min_order_krw: float ops_slippage_rate: float + ops_exchange_fee_lock_rate: float + ops_buy_safety_buffer_krw: float + ops_buy_cash_pct: float + ops_sell_coin_pct: float + ops_sizing_rules_json: Path ops_order_interval_sec: float ops_sync_candles: bool ops_sync_intervals: list[int] @@ -189,42 +188,6 @@ def load_settings(env_path: Path | None = None) -> Settings: ground_truth_chart_v3_file=_resolve_project_path( os.getenv("GROUND_TRUTH_CHART_V3_FILE", "docs/spot/0_ground_truth/ground_truth_chart_v3.html") ), - ground_truth_futures_file=_resolve_project_path( - os.getenv( - "GROUND_TRUTH_FUTURES_FILE", - "data/futures/ground_truth/ground_truth_trades_v3.json", - ) - ), - ground_truth_futures_v1_file=_resolve_project_path( - os.getenv( - "GROUND_TRUTH_FUTURES_V1_FILE", - "data/futures/ground_truth/ground_truth_trades_v1.json", - ) - ), - ground_truth_futures_v2_file=_resolve_project_path( - os.getenv( - "GROUND_TRUTH_FUTURES_V2_FILE", - "data/futures/ground_truth/ground_truth_trades_v2.json", - ) - ), - ground_truth_futures_chart_v1_file=_resolve_project_path( - os.getenv( - "GROUND_TRUTH_FUTURES_CHART_V1_FILE", - "docs/futures/0_ground_truth/ground_truth_chart_v1.html", - ) - ), - ground_truth_futures_chart_v2_file=_resolve_project_path( - os.getenv( - "GROUND_TRUTH_FUTURES_CHART_V2_FILE", - "docs/futures/0_ground_truth/ground_truth_chart_v2.html", - ) - ), - ground_truth_futures_chart_v3_file=_resolve_project_path( - os.getenv( - "GROUND_TRUTH_FUTURES_CHART_V3_FILE", - "docs/futures/0_ground_truth/ground_truth_chart_v3.html", - ) - ), ground_truth_chart_sim_v1_file=_resolve_project_path( os.getenv( "GROUND_TRUTH_CHART_SIM_V1_FILE", @@ -309,6 +272,20 @@ def load_settings(env_path: Path | None = None) -> Settings: ops_daily_max_trades=int(os.getenv("OPS_DAILY_MAX_TRADES", "20")), ops_min_order_krw=float(os.getenv("OPS_MIN_ORDER_KRW", "5000")), ops_slippage_rate=float(os.getenv("OPS_SLIPPAGE_RATE", "0.0005")), + ops_exchange_fee_lock_rate=float( + os.getenv("OPS_EXCHANGE_FEE_LOCK_RATE", "0.0025") + ), + ops_buy_safety_buffer_krw=float( + os.getenv("OPS_BUY_SAFETY_BUFFER_KRW", "1000") + ), + ops_buy_cash_pct=float(os.getenv("OPS_BUY_CASH_PCT", "0.10")), + ops_sell_coin_pct=float(os.getenv("OPS_SELL_COIN_PCT", "0.10")), + ops_sizing_rules_json=_resolve_project_path( + os.getenv( + "OPS_SIZING_RULES_JSON", + "data/spot/operations/sizing_rules.json", + ) + ), ops_order_interval_sec=float(os.getenv("OPS_ORDER_INTERVAL_SEC", "0.35")), ops_sync_candles=os.getenv("OPS_SYNC_CANDLES", "true").strip().lower() in ("1", "true", "yes", "on"), diff --git a/src/deepcoin/data/__init__.py b/src/bithumb/data/__init__.py similarity index 100% rename from src/deepcoin/data/__init__.py rename to src/bithumb/data/__init__.py diff --git a/src/deepcoin/data/candle_loader.py b/src/bithumb/data/candle_loader.py similarity index 95% rename from src/deepcoin/data/candle_loader.py rename to src/bithumb/data/candle_loader.py index 0fa753e..fd92189 100644 --- a/src/deepcoin/data/candle_loader.py +++ b/src/bithumb/data/candle_loader.py @@ -7,7 +7,7 @@ from pathlib import Path import pandas as pd -from deepcoin.data.candle_store import CandleStore +from bithumb.data.candle_store import CandleStore def load_candles( diff --git a/src/deepcoin/data/candle_store.py b/src/bithumb/data/candle_store.py similarity index 99% rename from src/deepcoin/data/candle_store.py rename to src/bithumb/data/candle_store.py index f9f0ff5..fdbbbb0 100644 --- a/src/deepcoin/data/candle_store.py +++ b/src/bithumb/data/candle_store.py @@ -8,7 +8,7 @@ from pathlib import Path import pandas as pd -from deepcoin.api.bithumb import parse_kst_datetime +from bithumb.api.bithumb import parse_kst_datetime class CandleStore: diff --git a/src/deepcoin/data/downloader.py b/src/bithumb/data/downloader.py similarity index 97% rename from src/deepcoin/data/downloader.py rename to src/bithumb/data/downloader.py index 2ff16ed..c41b661 100644 --- a/src/deepcoin/data/downloader.py +++ b/src/bithumb/data/downloader.py @@ -7,9 +7,9 @@ from dataclasses import dataclass from datetime import datetime, timedelta from typing import Any -from deepcoin.api.bithumb import BithumbCandleClient, parse_kst_datetime -from deepcoin.config import Settings -from deepcoin.data.candle_store import CandleStore +from bithumb.api.bithumb import BithumbCandleClient, parse_kst_datetime +from bithumb.config import Settings +from bithumb.data.candle_store import CandleStore logger = logging.getLogger(__name__) diff --git a/src/deepcoin/data/intervals.py b/src/bithumb/data/intervals.py similarity index 100% rename from src/deepcoin/data/intervals.py rename to src/bithumb/data/intervals.py diff --git a/src/deepcoin/evaluation/__init__.py b/src/bithumb/evaluation/__init__.py similarity index 65% rename from src/deepcoin/evaluation/__init__.py rename to src/bithumb/evaluation/__init__.py index d9cd28f..ced57e8 100644 --- a/src/deepcoin/evaluation/__init__.py +++ b/src/bithumb/evaluation/__init__.py @@ -1,13 +1,13 @@ """Ground Truth 정합 평가.""" -from deepcoin.evaluation.gt_align import align_with_ground_truth -from deepcoin.evaluation.mtf_report import ( +from bithumb.evaluation.gt_align import align_with_ground_truth +from bithumb.evaluation.mtf_report import ( build_mtf_correlation_report, render_mtf_html, save_mtf_report, ) -from deepcoin.evaluation.report import build_comparison_report, render_comparison_html, save_comparison_report -from deepcoin.evaluation.signal_type_report import ( +from bithumb.evaluation.report import build_comparison_report, render_comparison_html, save_comparison_report +from bithumb.evaluation.signal_type_report import ( build_signal_type_report, render_signal_type_html, save_signal_type_report, diff --git a/src/deepcoin/evaluation/causal_sim.py b/src/bithumb/evaluation/causal_sim.py similarity index 98% rename from src/deepcoin/evaluation/causal_sim.py rename to src/bithumb/evaluation/causal_sim.py index c166e9f..a5bf94a 100644 --- a/src/deepcoin/evaluation/causal_sim.py +++ b/src/bithumb/evaluation/causal_sim.py @@ -7,9 +7,9 @@ from datetime import datetime from pathlib import Path from typing import Any -from deepcoin.ground_truth.chart import render_ground_truth_sim_chart -from deepcoin.ground_truth.pnl import simulate_gt_signals_pnl -from deepcoin.techniques.base import TechniqueResult +from bithumb.ground_truth.chart import render_ground_truth_sim_chart +from bithumb.ground_truth.pnl import simulate_gt_signals_pnl +from bithumb.techniques.base import TechniqueResult def normalize_signals_for_sim(signals: list[dict[str, Any]]) -> list[dict[str, Any]]: @@ -303,7 +303,7 @@ def render_causal_sim_html(report: dict[str, Any], html_path: Path) -> Path: - DeepCoin 2단계 — 인과 sim + Bithumb 2단계 — 인과 sim -

DeepCoin 2단계 — 인과 기법 Ground Truth 정합

+

Bithumb 2단계 — 인과 기법 Ground Truth 정합

생성: {report.get('generated_at', '')} | {report.get('symbol', '')} | diff --git a/src/deepcoin/evaluation/signal_type_report.py b/src/bithumb/evaluation/signal_type_report.py similarity index 97% rename from src/deepcoin/evaluation/signal_type_report.py rename to src/bithumb/evaluation/signal_type_report.py index 2211008..45fcfb6 100644 --- a/src/deepcoin/evaluation/signal_type_report.py +++ b/src/bithumb/evaluation/signal_type_report.py @@ -7,12 +7,12 @@ from datetime import datetime from pathlib import Path from typing import Any -from deepcoin.evaluation.gt_align import ( +from bithumb.evaluation.gt_align import ( SIGNAL_TYPE_LABELS, SIGNAL_TYPE_PRIMARY_TECHNIQUES, summarize_signal_type_matrix, ) -from deepcoin.techniques.base import TechniqueResult +from bithumb.techniques.base import TechniqueResult def build_signal_type_report( @@ -163,7 +163,7 @@ def render_signal_type_html(report: dict[str, Any], html_path: Path) -> Path: - DeepCoin — v3 신호 유형별 GT 정합 + Bithumb — v3 신호 유형별 GT 정합 -

DeepCoin — v3 신호 유형별 Ground Truth 정합

+

Bithumb — v3 신호 유형별 Ground Truth 정합

생성: {report.get('generated_at', '')} | {report.get('symbol', '')} | diff --git a/src/deepcoin/ground_truth/__init__.py b/src/bithumb/ground_truth/__init__.py similarity index 100% rename from src/deepcoin/ground_truth/__init__.py rename to src/bithumb/ground_truth/__init__.py diff --git a/src/deepcoin/ground_truth/breakout.py b/src/bithumb/ground_truth/breakout.py similarity index 99% rename from src/deepcoin/ground_truth/breakout.py rename to src/bithumb/ground_truth/breakout.py index 02046df..969b902 100644 --- a/src/deepcoin/ground_truth/breakout.py +++ b/src/bithumb/ground_truth/breakout.py @@ -7,7 +7,7 @@ from typing import Protocol import pandas as pd -from deepcoin.ground_truth.zigzag import Pivot +from bithumb.ground_truth.zigzag import Pivot class _LegLike(Protocol): diff --git a/src/deepcoin/ground_truth/chart.py b/src/bithumb/ground_truth/chart.py similarity index 99% rename from src/deepcoin/ground_truth/chart.py rename to src/bithumb/ground_truth/chart.py index 85ba77a..e6ebd4d 100644 --- a/src/deepcoin/ground_truth/chart.py +++ b/src/bithumb/ground_truth/chart.py @@ -9,7 +9,7 @@ from typing import Any import pandas as pd -from deepcoin.data.candle_loader import load_candles +from bithumb.data.candle_loader import load_candles # 0이면 제한 없이 전체 봉 표시 DEFAULT_MAX_CANDLES = 0 @@ -254,7 +254,7 @@ _HTML_TEMPLATE = """ - DeepCoin Chart + Bithumb Chart @@ -284,7 +284,7 @@ __EXTRA_STYLES__
-

DeepCoin Chart

+

Bithumb Chart

__EXTRA_BODY__ diff --git a/src/deepcoin/ground_truth/divergence.py b/src/bithumb/ground_truth/divergence.py similarity index 99% rename from src/deepcoin/ground_truth/divergence.py rename to src/bithumb/ground_truth/divergence.py index f170057..8e32438 100644 --- a/src/deepcoin/ground_truth/divergence.py +++ b/src/bithumb/ground_truth/divergence.py @@ -6,7 +6,7 @@ from dataclasses import dataclass import pandas as pd -from deepcoin.techniques.indicators import macd, rsi +from bithumb.techniques.indicators import macd, rsi @dataclass(frozen=True) diff --git a/src/deepcoin/ground_truth/ground_truth.py b/src/bithumb/ground_truth/ground_truth.py similarity index 96% rename from src/deepcoin/ground_truth/ground_truth.py rename to src/bithumb/ground_truth/ground_truth.py index f9634c7..4ee3cb6 100644 --- a/src/deepcoin/ground_truth/ground_truth.py +++ b/src/bithumb/ground_truth/ground_truth.py @@ -10,13 +10,13 @@ from typing import Any import pandas as pd -from deepcoin.data.candle_loader import load_candles -from deepcoin.data.intervals import interval_label -from deepcoin.ground_truth.pnl import simulate_gt_pnl -from deepcoin.ground_truth.breakout import find_breakout_buy_pivots -from deepcoin.ground_truth.divergence import find_divergence_signals -from deepcoin.ground_truth.pullback import find_pullback_buy_pivots -from deepcoin.ground_truth.zigzag import Pivot, find_zigzag_pivots +from bithumb.data.candle_loader import load_candles +from bithumb.data.intervals import interval_label +from bithumb.ground_truth.pnl import simulate_gt_pnl +from bithumb.ground_truth.breakout import find_breakout_buy_pivots +from bithumb.ground_truth.divergence import find_divergence_signals +from bithumb.ground_truth.pullback import find_pullback_buy_pivots +from bithumb.ground_truth.zigzag import Pivot, find_zigzag_pivots @dataclass(frozen=True) diff --git a/src/bithumb/ground_truth/order_sizing.py b/src/bithumb/ground_truth/order_sizing.py new file mode 100644 index 0000000..8de0de9 --- /dev/null +++ b/src/bithumb/ground_truth/order_sizing.py @@ -0,0 +1,122 @@ +"""총평가금액 구간별 매수(현금) 상한.""" + +from __future__ import annotations + +from typing import Any + +# 총평가금액(원) 구간 — 높은 구간이 우선 적용 +EQUITY_TIER_100M = 100_000_000 +EQUITY_TIER_1B = 1_000_000_000 +EQUITY_TIER_10B = 10_000_000_000 + +BUY_SIZING_RULE_LABEL = "총평가 1억↑ 현금 10% · 10억↑ 5% · 100억↑ 1%" +BASE_BUY_CASH_PCT = 0.10 +BASE_SELL_COIN_PCT = 0.10 + + +def effective_buy_cash_pct( + equity_krw: float, + base_pct: float | None = BASE_BUY_CASH_PCT, +) -> float: + """1회 매수 허용 현금 비율 (구간 tier + 소액 base). + + 1억 미만은 ``base_pct``(기본 10%)로 분할 매수한다. + 1억 이상은 기존 tier(10%/5%/1%)가 적용된다. + """ + tier_pct = buy_cash_pct(equity_krw) + if tier_pct is not None: + return tier_pct + if base_pct is not None: + return max(0.0, min(float(base_pct), 1.0)) + return 1.0 + + +def effective_sell_coin_pct(sell_pct: float | None = BASE_SELL_COIN_PCT) -> float: + """1회 매도 허용 코인 비율 (기본 10%).""" + if sell_pct is None: + return 1.0 + return max(0.0, min(float(sell_pct), 1.0)) + + +def buy_cash_pct(equity_krw: float) -> float | None: + """총평가금액에 따른 1회 매수 허용 현금 비율. + + Args: + equity_krw: 현재 총평가금액(원). + + Returns: + 허용 비율(0~1). 1억 미만이면 None(비율 상한 없음). + """ + if equity_krw >= EQUITY_TIER_10B: + return 0.01 + if equity_krw >= EQUITY_TIER_1B: + return 0.05 + if equity_krw >= EQUITY_TIER_100M: + return 0.10 + return None + + +def max_buy_from_cash( + equity_krw: float, + cash_krw: float, + *, + base_pct: float | None = BASE_BUY_CASH_PCT, +) -> float: + """구간별·base 규칙을 반영한 1회 매수 최대 금액. + + Args: + equity_krw: 현재 총평가금액(원). + cash_krw: 보유 현금(원). + base_pct: 1억 미만 계좌 1회 매수 비율 (None이면 전액). + + Returns: + 매수에 사용 가능한 최대 원화. + """ + cash = max(float(cash_krw), 0.0) + pct = effective_buy_cash_pct(equity_krw, base_pct) + return cash * pct + + +def max_sell_coin_qty( + coin_qty: float, + *, + sell_pct: float | None = BASE_SELL_COIN_PCT, +) -> float: + """1회 매도 최대 코인 수량 (보유 비율 cap + 클러스터 분할 전). + + Args: + coin_qty: 보유 코인 수량. + sell_pct: 1회 매도 허용 비율 (None이면 전량). + + Returns: + 이번 매도 사이클에서 사용 가능한 최대 수량. + """ + qty = max(float(coin_qty), 0.0) + pct = effective_sell_coin_pct(sell_pct) + return qty * pct + + +def buy_sizing_metadata( + *, + base_buy_pct: float | None = BASE_BUY_CASH_PCT, + base_sell_pct: float | None = BASE_SELL_COIN_PCT, +) -> dict[str, Any]: + """시뮬 결과·차트에 포함할 매수·매도 상한 메타.""" + base_label = ( + f" · 1억 미만 현금 {base_buy_pct * 100:.0f}%" + if base_buy_pct is not None + else "" + ) + return { + "buy_sizing_rule": BUY_SIZING_RULE_LABEL + base_label, + "sell_sizing_rule": f"보유 코인 {base_sell_pct * 100:.0f}% 분할 매도" + if base_sell_pct is not None + else "전량 매도", + "base_buy_cash_pct": base_buy_pct, + "base_sell_coin_pct": base_sell_pct, + "buy_sizing_tiers": [ + {"min_equity_krw": EQUITY_TIER_100M, "max_cash_pct": 0.10}, + {"min_equity_krw": EQUITY_TIER_1B, "max_cash_pct": 0.05}, + {"min_equity_krw": EQUITY_TIER_10B, "max_cash_pct": 0.01}, + ], + } diff --git a/src/deepcoin/ground_truth/pnl.py b/src/bithumb/ground_truth/pnl.py similarity index 90% rename from src/deepcoin/ground_truth/pnl.py rename to src/bithumb/ground_truth/pnl.py index 468a03a..930c9c2 100644 --- a/src/deepcoin/ground_truth/pnl.py +++ b/src/bithumb/ground_truth/pnl.py @@ -6,7 +6,14 @@ from dataclasses import asdict, dataclass from datetime import datetime, timedelta from typing import Any -from deepcoin.ground_truth.order_sizing import buy_sizing_metadata, max_buy_from_cash +from bithumb.ground_truth.order_sizing import ( + BASE_BUY_CASH_PCT, + BASE_SELL_COIN_PCT, + buy_sizing_metadata, + max_buy_from_cash, + max_sell_coin_qty, +) +from bithumb.ground_truth.sizing_rules import resolve_buy_cash_pct, resolve_sell_coin_pct def _fill_price(signal_price: float, side: str, slippage_rate: float) -> float: @@ -177,6 +184,9 @@ def simulate_gt_signals_pnl( min_order_krw: float = 5_000.0, slippage_rate: float = 0.0, daily_max_trades: int | None = None, + buy_cash_pct: float | None = BASE_BUY_CASH_PCT, + sell_coin_pct: float | None = BASE_SELL_COIN_PCT, + sizing_rules: dict[str, Any] | None = None, sim_lookback_days: int = 365, data_end: str | None = None, last_mark_price: float | None = None, @@ -186,7 +196,8 @@ def simulate_gt_signals_pnl( - 시뮬 기간: data_end 기준 최근 sim_lookback_days - 연속 매수: 가용 원화를 매수 신호 수로 균등 분할 - 총평가 1억↑ 현금 10% · 10억↑ 5% · 100억↑ 1% 상한 - - 연속 매도: 보유 코인을 매도 신호 수로 균등 분할 (상한 없음) + - 1억 미만: buy_cash_pct(기본 10%) 분할 매수 · sell_coin_pct(기본 10%) 분할 매도 + - 연속 매도: 허용 수량을 매도 신호 수로 균등 분할 - 원화 부족 시 매수 스킵, 코인 없으면 매도 스킵 Args: @@ -251,7 +262,12 @@ def simulate_gt_signals_pnl( for side, cluster in _cluster_signals(period_signals): cluster_size = len(cluster) if side == "buy": - budget = cash + effective_buy_pct = resolve_buy_cash_pct( + sizing_rules, cluster_size, buy_cash_pct + ) + equity = cash + coin_qty * float(cluster[0]["price"]) + cash_cap = max_buy_from_cash(equity, cash, base_pct=effective_buy_pct) + budget = min(cash, cash_cap) per_buy = budget / cluster_size if cluster_size else 0.0 for sig in cluster: trade_id += 1 @@ -282,7 +298,7 @@ def simulate_gt_signals_pnl( ) continue equity = cash + coin_qty * price - cash_cap = max_buy_from_cash(equity, cash) + cash_cap = max_buy_from_cash(equity, cash, base_pct=effective_buy_pct) order_krw = min(per_buy, cash, cash_cap) if order_krw < min_order_krw: @@ -335,7 +351,10 @@ def simulate_gt_signals_pnl( ) ) else: - budget_coin = coin_qty + effective_sell_pct = resolve_sell_coin_pct( + sizing_rules, cluster_size, sell_coin_pct + ) + budget_coin = max_sell_coin_qty(coin_qty, sell_pct=effective_sell_pct) per_sell = budget_coin / cluster_size if cluster_size else 0.0 for sig in cluster: trade_id += 1 @@ -436,7 +455,15 @@ def simulate_gt_signals_pnl( "fee_rate": fee_rate, "slippage_rate": slippage_rate, "daily_max_trades": daily_max_trades, - **buy_sizing_metadata(), + **buy_sizing_metadata(base_buy_pct=buy_cash_pct, base_sell_pct=sell_coin_pct), + "sizing_rules_applied": sizing_rules is not None, + "learned_default_buy_cash_pct": ( + sizing_rules.get("default_buy_cash_pct") if sizing_rules else None + ), + "learned_default_sell_coin_pct": ( + sizing_rules.get("default_sell_coin_pct") if sizing_rules else None + ), + "learned_by_cluster": sizing_rules.get("by_cluster") if sizing_rules else None, "sim_lookback_days": sim_lookback_days, "period_from": start_str, "period_to": end_dt.strftime("%Y-%m-%d %H:%M:%S"), @@ -468,7 +495,7 @@ def _empty_signal_pnl( "total_pnl_krw": 0.0, "total_return_pct": 0.0, "fee_rate": fee_rate, - **buy_sizing_metadata(), + **buy_sizing_metadata(base_buy_pct=buy_cash_pct, base_sell_pct=sell_coin_pct), "sim_lookback_days": sim_lookback_days, "period_from": period_from, "period_to": period_to, diff --git a/src/deepcoin/ground_truth/pullback.py b/src/bithumb/ground_truth/pullback.py similarity index 98% rename from src/deepcoin/ground_truth/pullback.py rename to src/bithumb/ground_truth/pullback.py index e85a5c6..2eeb7c6 100644 --- a/src/deepcoin/ground_truth/pullback.py +++ b/src/bithumb/ground_truth/pullback.py @@ -6,7 +6,7 @@ import pandas as pd from typing import Protocol -from deepcoin.ground_truth.zigzag import Pivot +from bithumb.ground_truth.zigzag import Pivot class _LegLike(Protocol): diff --git a/src/bithumb/ground_truth/sizing_rules.py b/src/bithumb/ground_truth/sizing_rules.py new file mode 100644 index 0000000..91ea6d2 --- /dev/null +++ b/src/bithumb/ground_truth/sizing_rules.py @@ -0,0 +1,97 @@ +"""연속 매수·매도 클러스터 상태별 매수·매도 비율 규칙.""" + +from __future__ import annotations + +import json +from datetime import datetime +from pathlib import Path +from typing import Any + +CLUSTER_SIZE_KEYS: tuple[str, ...] = ("1", "2", "3+") + + +def cluster_size_key(cluster_size: int) -> str: + """클러스터 크기를 규칙 lookup 키로 변환한다.""" + size = max(int(cluster_size), 1) + if size >= 3: + return "3+" + return str(size) + + +def resolve_buy_cash_pct( + rules: dict[str, Any] | None, + cluster_size: int, + default: float | None, +) -> float | None: + """클러스터 상태에 맞는 1회 매수 현금 비율을 반환한다.""" + if not rules: + return default + by_side = (rules.get("by_cluster") or {}).get("buy") or {} + key = cluster_size_key(cluster_size) + if key in by_side: + return float(by_side[key]) + fallback = rules.get("default_buy_cash_pct") + if fallback is not None: + return float(fallback) + return default + + +def resolve_sell_coin_pct( + rules: dict[str, Any] | None, + cluster_size: int, + default: float | None, +) -> float | None: + """클러스터 상태에 맞는 1회 매도 코인 비율을 반환한다.""" + if not rules: + return default + by_side = (rules.get("by_cluster") or {}).get("sell") or {} + key = cluster_size_key(cluster_size) + if key in by_side: + return float(by_side[key]) + fallback = rules.get("default_sell_coin_pct") + if fallback is not None: + return float(fallback) + return default + + +def load_sizing_rules(path: Path) -> dict[str, Any] | None: + """JSON 사이징 규칙을 로드한다. 파일이 없으면 None.""" + if not path.exists(): + return None + with path.open(encoding="utf-8") as fp: + data = json.load(fp) + return data if isinstance(data, dict) else None + + +def save_sizing_rules(rules: dict[str, Any], path: Path) -> Path: + """사이징 규칙 JSON 저장.""" + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="utf-8") as fp: + json.dump(rules, fp, ensure_ascii=False, indent=2) + return path + + +def empty_by_cluster() -> dict[str, dict[str, float]]: + """빈 by_cluster 구조.""" + return {"buy": {}, "sell": {}} + + +def merge_rules( + *, + default_buy: float, + default_sell: float, + by_cluster: dict[str, dict[str, float]] | None = None, + technique_id: str = "", + symbol: str = "", + tuning: dict[str, Any] | None = None, +) -> dict[str, Any]: + """튜닝 결과 dict를 표준 규칙 형식으로 만든다.""" + return { + "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "technique_id": technique_id, + "symbol": symbol, + "default_buy_cash_pct": round(float(default_buy), 4), + "default_sell_coin_pct": round(float(default_sell), 4), + "by_cluster": by_cluster or empty_by_cluster(), + "tuning": tuning or {}, + } diff --git a/src/bithumb/ground_truth/sizing_tune.py b/src/bithumb/ground_truth/sizing_tune.py new file mode 100644 index 0000000..ce214bd --- /dev/null +++ b/src/bithumb/ground_truth/sizing_tune.py @@ -0,0 +1,273 @@ +"""연속 매수·매도 클러스터별 사이징 비율 튜닝.""" + +from __future__ import annotations + +from datetime import datetime, timedelta +from typing import Any + +from bithumb.config import Settings +from bithumb.evaluation.causal_sim import normalize_signals_for_sim +from bithumb.ground_truth.pnl import _cluster_signals, simulate_gt_signals_pnl +from bithumb.ground_truth.sizing_rules import ( + CLUSTER_SIZE_KEYS, + cluster_size_key, + merge_rules, +) + +PCT_CANDIDATES: tuple[float, ...] = ( + 0.10, + 0.15, + 0.20, + 0.25, + 0.30, + 0.40, + 0.50, + 0.60, + 0.70, + 0.80, + 1.0, +) + + +def _parse_signal_dt(value: str) -> datetime: + return datetime.strptime(value, "%Y-%m-%d %H:%M:%S") + + +def _sim_kwargs(settings: Settings, data_end: str, last_mark_price: float) -> dict[str, Any]: + return { + "initial_cash_krw": settings.gt_initial_cash_krw, + "fee_rate": settings.gt_trading_fee_rate, + "min_order_krw": settings.ops_min_order_krw, + "slippage_rate": settings.ops_slippage_rate, + "daily_max_trades": settings.ops_daily_max_trades, + "sim_lookback_days": settings.gt_sim_lookback_days, + "data_end": data_end, + "last_mark_price": last_mark_price, + } + + +def count_cluster_buckets( + signals: list[dict[str, Any]], + *, + sim_lookback_days: int, + data_end: str, +) -> dict[str, dict[str, int]]: + """sim 기간 내 (side, cluster_size) 빈도.""" + end_dt = _parse_signal_dt(data_end) + start_dt = end_dt - timedelta(days=sim_lookback_days) + period = [s for s in signals if _parse_signal_dt(s["datetime"]) >= start_dt] + counts: dict[str, dict[str, int]] = {"buy": {}, "sell": {}} + for side, cluster in _cluster_signals(period): + key = cluster_size_key(len(cluster)) + bucket = counts.setdefault(side, {}) + bucket[key] = bucket.get(key, 0) + 1 + return counts + + +def _rules_for_sim( + default_buy: float, + default_sell: float, + by_cluster: dict[str, dict[str, float]], +) -> dict[str, Any]: + return merge_rules( + default_buy=default_buy, + default_sell=default_sell, + by_cluster=by_cluster, + ) + + +def _run_sim( + signals: list[dict[str, Any]], + settings: Settings, + data_end: str, + last_mark_price: float, + *, + default_buy: float, + default_sell: float, + by_cluster: dict[str, dict[str, float]] | None = None, +) -> dict[str, Any]: + rules = _rules_for_sim(default_buy, default_sell, by_cluster or {"buy": {}, "sell": {}}) + return simulate_gt_signals_pnl( + signals=signals, + buy_cash_pct=default_buy, + sell_coin_pct=default_sell, + sizing_rules=rules, + **_sim_kwargs(settings, data_end, last_mark_price), + ) + + +def _best_pct( + signals: list[dict[str, Any]], + settings: Settings, + data_end: str, + last_mark_price: float, + *, + side: str, + fixed_buy: float, + fixed_sell: float, + by_cluster: dict[str, dict[str, float]], + bucket_key: str | None = None, +) -> tuple[float, float]: + """한 축(side 또는 bucket)에 대해 최적 pct와 sim 수익률을 반환한다.""" + best_pct = fixed_buy if side == "buy" else fixed_sell + best_return = float("-inf") + for pct in PCT_CANDIDATES: + buy = fixed_buy + sell = fixed_sell + cluster = {"buy": dict(by_cluster.get("buy", {})), "sell": dict(by_cluster.get("sell", {}))} + if bucket_key is not None: + cluster.setdefault(side, {})[bucket_key] = pct + else: + if side == "buy": + buy = pct + else: + sell = pct + sim = _run_sim( + signals, + settings, + data_end, + last_mark_price, + default_buy=buy, + default_sell=sell, + by_cluster=cluster, + ) + ret = float(sim.get("total_return_pct") or 0) + if ret > best_return: + best_return = ret + best_pct = pct + return best_pct, best_return + + +def tune_sizing_rules( + settings: Settings, + signals: list[dict[str, Any]], + *, + data_end: str, + last_mark_price: float, + technique_id: str, + min_bucket_samples: int = 5, +) -> tuple[dict[str, Any], dict[str, Any]]: + """타점 고정 상태에서 전역·클러스터별 매수·매도 비율을 탐색한다.""" + normalized = normalize_signals_for_sim(signals) + base_buy = settings.ops_buy_cash_pct + base_sell = settings.ops_sell_coin_pct + by_cluster: dict[str, dict[str, float]] = {"buy": {}, "sell": {}} + history: list[dict[str, Any]] = [] + + buy_pct, buy_ret = _best_pct( + normalized, + settings, + data_end, + last_mark_price, + side="buy", + fixed_buy=base_buy, + fixed_sell=base_sell, + by_cluster=by_cluster, + ) + history.append({"step": "global_buy", "buy_pct": buy_pct, "return_pct": buy_ret}) + + sell_pct, sell_ret = _best_pct( + normalized, + settings, + data_end, + last_mark_price, + side="sell", + fixed_buy=buy_pct, + fixed_sell=base_sell, + by_cluster=by_cluster, + ) + history.append({"step": "global_sell", "sell_pct": sell_pct, "return_pct": sell_ret}) + + buy_pct, buy_ret = _best_pct( + normalized, + settings, + data_end, + last_mark_price, + side="buy", + fixed_buy=buy_pct, + fixed_sell=sell_pct, + by_cluster=by_cluster, + ) + history.append({"step": "refine_buy", "buy_pct": buy_pct, "return_pct": buy_ret}) + + sell_pct, sell_ret = _best_pct( + normalized, + settings, + data_end, + last_mark_price, + side="sell", + fixed_buy=buy_pct, + fixed_sell=sell_pct, + by_cluster=by_cluster, + ) + history.append({"step": "refine_sell", "sell_pct": sell_pct, "return_pct": sell_ret}) + + bucket_counts = count_cluster_buckets( + normalized, + sim_lookback_days=settings.gt_sim_lookback_days, + data_end=data_end, + ) + + for side in ("buy", "sell"): + for key in CLUSTER_SIZE_KEYS: + if bucket_counts.get(side, {}).get(key, 0) < min_bucket_samples: + continue + pct, ret = _best_pct( + normalized, + settings, + data_end, + last_mark_price, + side=side, + fixed_buy=buy_pct, + fixed_sell=sell_pct, + by_cluster=by_cluster, + bucket_key=key, + ) + by_cluster.setdefault(side, {})[key] = pct + history.append( + { + "step": f"bucket_{side}_{key}", + "pct": pct, + "samples": bucket_counts[side][key], + "return_pct": ret, + } + ) + + final_sim = _run_sim( + normalized, + settings, + data_end, + last_mark_price, + default_buy=buy_pct, + default_sell=sell_pct, + by_cluster=by_cluster, + ) + + tuning_meta = { + "objective": "total_return_pct", + "pct_candidates": list(PCT_CANDIDATES), + "min_bucket_samples": min_bucket_samples, + "cluster_counts": bucket_counts, + "history": history, + "baseline_return_pct": _run_sim( + normalized, + settings, + data_end, + last_mark_price, + default_buy=base_buy, + default_sell=base_sell, + ).get("total_return_pct"), + "final_return_pct": final_sim.get("total_return_pct"), + "final_buys_executed": final_sim.get("buys_executed"), + "final_sells_executed": final_sim.get("sells_executed"), + } + + rules = merge_rules( + default_buy=buy_pct, + default_sell=sell_pct, + by_cluster=by_cluster, + technique_id=technique_id, + symbol=settings.symbol, + tuning=tuning_meta, + ) + return rules, final_sim diff --git a/src/deepcoin/ground_truth/swing_signals.py b/src/bithumb/ground_truth/swing_signals.py similarity index 98% rename from src/deepcoin/ground_truth/swing_signals.py rename to src/bithumb/ground_truth/swing_signals.py index ce09a94..f6b47d8 100644 --- a/src/deepcoin/ground_truth/swing_signals.py +++ b/src/bithumb/ground_truth/swing_signals.py @@ -6,7 +6,7 @@ from dataclasses import dataclass import pandas as pd -from deepcoin.ground_truth.zigzag import Pivot, find_zigzag_pivots +from bithumb.ground_truth.zigzag import Pivot, find_zigzag_pivots @dataclass(frozen=True) diff --git a/src/deepcoin/ground_truth/zigzag.py b/src/bithumb/ground_truth/zigzag.py similarity index 100% rename from src/deepcoin/ground_truth/zigzag.py rename to src/bithumb/ground_truth/zigzag.py diff --git a/src/bithumb/mtf/__init__.py b/src/bithumb/mtf/__init__.py new file mode 100644 index 0000000..6eb8ae5 --- /dev/null +++ b/src/bithumb/mtf/__init__.py @@ -0,0 +1,23 @@ +"""멀티 타임프레임(MTF) 인과 피처 추출.""" + +from bithumb.mtf.alignment import as_of_from_signal_bar, last_complete_bar_index +from bithumb.mtf.extractor import MtfFeatureExtractor, MtfSnapshot +from bithumb.mtf.filter import MtfSignalFilter, score_mtf_rules +from bithumb.mtf.rules import MtfRule, MtfRuleSet, derive_rules_from_report, load_mtf_rules, save_mtf_rules +from bithumb.mtf.store import MTF_INTERVALS, MultiTimeframeStore + +__all__ = [ + "MTF_INTERVALS", + "MultiTimeframeStore", + "MtfFeatureExtractor", + "MtfSnapshot", + "MtfSignalFilter", + "MtfRule", + "MtfRuleSet", + "derive_rules_from_report", + "load_mtf_rules", + "save_mtf_rules", + "score_mtf_rules", + "as_of_from_signal_bar", + "last_complete_bar_index", +] diff --git a/src/deepcoin/mtf/alignment.py b/src/bithumb/mtf/alignment.py similarity index 100% rename from src/deepcoin/mtf/alignment.py rename to src/bithumb/mtf/alignment.py diff --git a/src/deepcoin/mtf/extractor.py b/src/bithumb/mtf/extractor.py similarity index 94% rename from src/deepcoin/mtf/extractor.py rename to src/bithumb/mtf/extractor.py index 56891dc..ddbcabf 100644 --- a/src/deepcoin/mtf/extractor.py +++ b/src/bithumb/mtf/extractor.py @@ -7,9 +7,9 @@ from typing import Any import pandas as pd -from deepcoin.mtf.alignment import as_of_from_signal_bar, resolve_bar_index -from deepcoin.mtf.features import snapshot_at_index -from deepcoin.mtf.store import MTF_INTERVALS, MultiTimeframeStore +from bithumb.mtf.alignment import as_of_from_signal_bar, resolve_bar_index +from bithumb.mtf.features import snapshot_at_index +from bithumb.mtf.store import MTF_INTERVALS, MultiTimeframeStore @dataclass diff --git a/src/deepcoin/mtf/features.py b/src/bithumb/mtf/features.py similarity index 98% rename from src/deepcoin/mtf/features.py rename to src/bithumb/mtf/features.py index 1fc3cd7..a480ab2 100644 --- a/src/deepcoin/mtf/features.py +++ b/src/bithumb/mtf/features.py @@ -6,7 +6,7 @@ from typing import Any import pandas as pd -from deepcoin.techniques.indicators import atr, bollinger_bands, ema, macd, rsi +from bithumb.techniques.indicators import atr, bollinger_bands, ema, macd, rsi FEATURE_NAMES: tuple[str, ...] = ( "close", diff --git a/src/deepcoin/mtf/filter.py b/src/bithumb/mtf/filter.py similarity index 96% rename from src/deepcoin/mtf/filter.py rename to src/bithumb/mtf/filter.py index f5af642..b8fe39a 100644 --- a/src/deepcoin/mtf/filter.py +++ b/src/bithumb/mtf/filter.py @@ -4,9 +4,9 @@ from __future__ import annotations from typing import Any -from deepcoin.mtf.trend_gate import HtfTrendGate -from deepcoin.mtf.extractor import MtfFeatureExtractor, MtfSnapshot -from deepcoin.mtf.rules import MtfRule, MtfRuleSet +from bithumb.mtf.trend_gate import HtfTrendGate +from bithumb.mtf.extractor import MtfFeatureExtractor, MtfSnapshot +from bithumb.mtf.rules import MtfRule, MtfRuleSet def evaluate_rule(rule: MtfRule, snapshot: MtfSnapshot) -> bool | None: diff --git a/src/deepcoin/mtf/precompute.py b/src/bithumb/mtf/precompute.py similarity index 95% rename from src/deepcoin/mtf/precompute.py rename to src/bithumb/mtf/precompute.py index 843483b..1dc0e1e 100644 --- a/src/deepcoin/mtf/precompute.py +++ b/src/bithumb/mtf/precompute.py @@ -5,9 +5,9 @@ from __future__ import annotations import numpy as np import pandas as pd -from deepcoin.mtf.alignment import as_of_from_signal_bar -from deepcoin.mtf.features import snapshot_at_index -from deepcoin.mtf.store import MultiTimeframeStore +from bithumb.mtf.alignment import as_of_from_signal_bar +from bithumb.mtf.features import snapshot_at_index +from bithumb.mtf.store import MultiTimeframeStore def _vectorized_tf_indices( diff --git a/src/deepcoin/mtf/rules.py b/src/bithumb/mtf/rules.py similarity index 99% rename from src/deepcoin/mtf/rules.py rename to src/bithumb/mtf/rules.py index be45326..30714bd 100644 --- a/src/deepcoin/mtf/rules.py +++ b/src/bithumb/mtf/rules.py @@ -7,7 +7,7 @@ from dataclasses import dataclass, field from pathlib import Path from typing import Any, Literal -from deepcoin.evaluation.gt_align import GT_SIGNAL_TYPES +from bithumb.evaluation.gt_align import GT_SIGNAL_TYPES Operator = Literal["<=", ">="] diff --git a/src/deepcoin/mtf/store.py b/src/bithumb/mtf/store.py similarity index 93% rename from src/deepcoin/mtf/store.py rename to src/bithumb/mtf/store.py index 9a5763f..f5834df 100644 --- a/src/deepcoin/mtf/store.py +++ b/src/bithumb/mtf/store.py @@ -6,9 +6,9 @@ from pathlib import Path import pandas as pd -from deepcoin.data.candle_loader import load_candles -from deepcoin.data.intervals import DEFAULT_DOWNLOAD_INTERVALS, interval_label -from deepcoin.mtf.features import compute_feature_frame +from bithumb.data.candle_loader import load_candles +from bithumb.data.intervals import DEFAULT_DOWNLOAD_INTERVALS, interval_label +from bithumb.mtf.features import compute_feature_frame MTF_INTERVALS: tuple[int, ...] = tuple(DEFAULT_DOWNLOAD_INTERVALS) diff --git a/src/deepcoin/mtf/trend_gate.py b/src/bithumb/mtf/trend_gate.py similarity index 100% rename from src/deepcoin/mtf/trend_gate.py rename to src/bithumb/mtf/trend_gate.py diff --git a/src/deepcoin/notifications/__init__.py b/src/bithumb/notifications/__init__.py similarity index 53% rename from src/deepcoin/notifications/__init__.py rename to src/bithumb/notifications/__init__.py index e52a8ee..41a82ee 100644 --- a/src/deepcoin/notifications/__init__.py +++ b/src/bithumb/notifications/__init__.py @@ -1,5 +1,5 @@ """알림 채널 (텔레그램 등).""" -from deepcoin.notifications.telegram import TelegramNotifier, create_telegram_notifier +from bithumb.notifications.telegram import TelegramNotifier, create_telegram_notifier __all__ = ["TelegramNotifier", "create_telegram_notifier"] diff --git a/src/deepcoin/notifications/telegram.py b/src/bithumb/notifications/telegram.py similarity index 86% rename from src/deepcoin/notifications/telegram.py rename to src/bithumb/notifications/telegram.py index 1f1a8ba..a7c6c31 100644 --- a/src/deepcoin/notifications/telegram.py +++ b/src/bithumb/notifications/telegram.py @@ -112,7 +112,7 @@ class TelegramNotifier: equity = cash + coin_qty * price lines = [ - f"[DeepCoin] {side_label} 체결 ({mode_label})", + f"[Bithumb] {side_label} 체결 ({mode_label})", f"{coin_name} ({symbol}) | {technique_id}", f"시각: {datetime_str}", f"신호: {signal_type or side}", @@ -145,13 +145,36 @@ class TelegramNotifier: side_label = "매수" if side == "buy" else "매도" mode_label = "LIVE" if mode == "live" else "PAPER" text = ( - f"[DeepCoin] {side_label} 실패 ({mode_label})\n" + f"[Bithumb] {side_label} 실패 ({mode_label})\n" f"{symbol} | {technique_id}\n" f"시각: {datetime_str}\n" f"사유: {reason}" ) return self.send_message(text) + def notify_ops_error( + self, + *, + mode: str, + symbol: str, + technique_id: str, + stage: str, + error: str, + detail: str = "", + ) -> bool: + """운영 tick·체결 등 예외 발생 알림 (프로세스는 계속 실행).""" + mode_label = "LIVE" if mode == "live" else "PAPER" + lines = [ + f"[Bithumb] 운영 오류 ({mode_label})", + f"{symbol} | {technique_id}", + f"단계: {stage}", + f"원인: {error}", + ] + if detail: + lines.append(detail) + lines.append("프로세스는 계속 실행됩니다.") + return self.send_message("\n".join(lines)) + def _fmt_krw(value: float) -> str: """원화 금액 포맷.""" diff --git a/src/bithumb/operations/__init__.py b/src/bithumb/operations/__init__.py new file mode 100644 index 0000000..4659dd1 --- /dev/null +++ b/src/bithumb/operations/__init__.py @@ -0,0 +1,12 @@ +"""현물 3단계 운영 — composite_v3 + MTF.""" + +from bithumb.operations.backtest import run_filtered_backtest, save_backtest_report +from bithumb.operations.runner import OperationsRunner +from bithumb.operations.signal_pipeline import run_signal_pipeline + +__all__ = [ + "OperationsRunner", + "run_filtered_backtest", + "run_signal_pipeline", + "save_backtest_report", +] diff --git a/src/deepcoin/operations/backtest.py b/src/bithumb/operations/backtest.py similarity index 63% rename from src/deepcoin/operations/backtest.py rename to src/bithumb/operations/backtest.py index 6bd4151..f5bd563 100644 --- a/src/deepcoin/operations/backtest.py +++ b/src/bithumb/operations/backtest.py @@ -7,10 +7,11 @@ from datetime import datetime from pathlib import Path from typing import Any -from deepcoin.config import Settings -from deepcoin.evaluation.causal_sim import normalize_signals_for_sim -from deepcoin.ground_truth.pnl import simulate_gt_signals_pnl -from deepcoin.operations.signal_pipeline import run_signal_pipeline +from bithumb.config import Settings +from bithumb.evaluation.causal_sim import normalize_signals_for_sim +from bithumb.ground_truth.pnl import simulate_gt_signals_pnl +from bithumb.ground_truth.sizing_rules import load_sizing_rules +from bithumb.operations.signal_pipeline import run_signal_pipeline def run_filtered_backtest(settings: Settings) -> dict[str, Any]: @@ -22,28 +23,25 @@ def run_filtered_backtest(settings: Settings) -> dict[str, Any]: ) kept = pipeline["kept"] normalized = normalize_signals_for_sim(kept) - sim = simulate_gt_signals_pnl( - signals=normalized, - initial_cash_krw=settings.gt_initial_cash_krw, - fee_rate=settings.gt_trading_fee_rate, - min_order_krw=settings.ops_min_order_krw, - slippage_rate=settings.ops_slippage_rate, - daily_max_trades=settings.ops_daily_max_trades, - sim_lookback_days=settings.gt_sim_lookback_days, - data_end=pipeline["data_end"], - last_mark_price=pipeline["last_price"], - ) + sizing_rules = load_sizing_rules(settings.ops_sizing_rules_json) + sim_kwargs = { + "initial_cash_krw": settings.gt_initial_cash_krw, + "fee_rate": settings.gt_trading_fee_rate, + "min_order_krw": settings.ops_min_order_krw, + "slippage_rate": settings.ops_slippage_rate, + "daily_max_trades": settings.ops_daily_max_trades, + "buy_cash_pct": settings.ops_buy_cash_pct, + "sell_coin_pct": settings.ops_sell_coin_pct, + "sizing_rules": sizing_rules, + "sim_lookback_days": settings.gt_sim_lookback_days, + "data_end": pipeline["data_end"], + "last_mark_price": pipeline["last_price"], + } + sim = simulate_gt_signals_pnl(signals=normalized, **sim_kwargs) raw_sim = simulate_gt_signals_pnl( signals=normalize_signals_for_sim(pipeline["scoped_raw_signals"]), - initial_cash_krw=settings.gt_initial_cash_krw, - fee_rate=settings.gt_trading_fee_rate, - min_order_krw=settings.ops_min_order_krw, - slippage_rate=settings.ops_slippage_rate, - daily_max_trades=settings.ops_daily_max_trades, - sim_lookback_days=settings.gt_sim_lookback_days, - data_end=pipeline["data_end"], - last_mark_price=pipeline["last_price"], + **sim_kwargs, ) return { diff --git a/src/deepcoin/operations/candle_sync.py b/src/bithumb/operations/candle_sync.py similarity index 90% rename from src/deepcoin/operations/candle_sync.py rename to src/bithumb/operations/candle_sync.py index b936422..c4a02b4 100644 --- a/src/deepcoin/operations/candle_sync.py +++ b/src/bithumb/operations/candle_sync.py @@ -5,9 +5,9 @@ from __future__ import annotations import logging from dataclasses import replace -from deepcoin.config import Settings -from deepcoin.data.candle_store import CandleStore -from deepcoin.data.downloader import CandleDownloader, DownloadResult +from bithumb.config import Settings +from bithumb.data.candle_store import CandleStore +from bithumb.data.downloader import CandleDownloader, DownloadResult logger = logging.getLogger(__name__) diff --git a/src/deepcoin/operations/chart.py b/src/bithumb/operations/chart.py similarity index 81% rename from src/deepcoin/operations/chart.py rename to src/bithumb/operations/chart.py index def6ee5..4fb6cce 100644 --- a/src/deepcoin/operations/chart.py +++ b/src/bithumb/operations/chart.py @@ -5,10 +5,10 @@ from __future__ import annotations from pathlib import Path from typing import Any -from deepcoin.config import Settings -from deepcoin.data.intervals import interval_label -from deepcoin.ground_truth.chart import render_ground_truth_sim_chart -from deepcoin.operations.backtest import run_filtered_backtest +from bithumb.config import Settings +from bithumb.data.intervals import interval_label +from bithumb.ground_truth.chart import render_ground_truth_sim_chart +from bithumb.operations.backtest import run_filtered_backtest def _ops_chart_shell( @@ -19,6 +19,12 @@ def _ops_chart_shell( """운영 백테스트 차트용 GT 메타 셸을 구성한다.""" slip_pct = settings.ops_slippage_rate * 100 daily_cap = settings.ops_daily_max_trades + sizing_note = "" + if sim_pnl.get("sizing_rules_applied"): + lb = sim_pnl.get("learned_default_buy_cash_pct") + ls = sim_pnl.get("learned_default_sell_coin_pct") + if lb is not None and ls is not None: + sizing_note = f" · 학습 비율 {float(lb) * 100:.0f}%/{float(ls) * 100:.0f}%" return { "meta": { "symbol": settings.symbol, @@ -34,6 +40,7 @@ def _ops_chart_shell( "ops_note": ( f"슬리피지 {slip_pct:.2f}% · 일 체결 상한 {daily_cap} · " f"MTF {'on' if pipeline.get('mtf_enabled') else 'off'}" + f"{sizing_note}" ), "data_from": sim_pnl.get("period_from"), "data_to": sim_pnl.get("period_to"), diff --git a/src/deepcoin/operations/execution.py b/src/bithumb/operations/execution.py similarity index 100% rename from src/deepcoin/operations/execution.py rename to src/bithumb/operations/execution.py diff --git a/src/deepcoin/operations/executor.py b/src/bithumb/operations/executor.py similarity index 70% rename from src/deepcoin/operations/executor.py rename to src/bithumb/operations/executor.py index 4a1865d..752c842 100644 --- a/src/deepcoin/operations/executor.py +++ b/src/bithumb/operations/executor.py @@ -6,14 +6,16 @@ import logging from abc import ABC, abstractmethod from typing import Any -from deepcoin.api.bithumb_private import BithumbPrivateClient -from deepcoin.config import Settings -from deepcoin.operations.execution import fill_price -from deepcoin.operations.trade_engine import ( +from bithumb.api.bithumb_private import BithumbPrivateClient +from bithumb.config import Settings +from bithumb.ground_truth.sizing_rules import load_sizing_rules +from bithumb.operations.execution import fill_price +from bithumb.operations.trade_engine import ( TradeResult, apply_trade_to_portfolio, compute_buy_order, compute_sell_order, + spendable_cash_for_exchange_buy, ) logger = logging.getLogger(__name__) @@ -38,6 +40,7 @@ class PaperExecutor(OrderExecutor): def __init__(self, settings: Settings) -> None: self.settings = settings + self.sizing_rules = load_sizing_rules(settings.ops_sizing_rules_json) def execute_signal( self, @@ -66,6 +69,8 @@ class PaperExecutor(OrderExecutor): fee_rate=fee_rate, min_order_krw=min_order, cluster_size=cluster_size, + buy_cash_pct=self.settings.ops_buy_cash_pct, + sizing_rules=self.sizing_rules, ) else: trade = compute_sell_order( @@ -74,6 +79,8 @@ class PaperExecutor(OrderExecutor): fee_rate=fee_rate, min_order_krw=min_order, cluster_size=cluster_size, + sell_coin_pct=self.settings.ops_sell_coin_pct, + sizing_rules=self.sizing_rules, ) if trade.executed: @@ -87,6 +94,7 @@ class LiveExecutor(OrderExecutor): def __init__(self, settings: Settings, client: BithumbPrivateClient) -> None: self.settings = settings self.client = client + self.sizing_rules = load_sizing_rules(settings.ops_sizing_rules_json) def _sync_portfolio(self, portfolio: dict[str, Any]) -> None: """거래소 잔고로 포트폴리오를 동기화한다.""" @@ -116,27 +124,46 @@ class LiveExecutor(OrderExecutor): coin = float(portfolio.get("coin_qty", 0)) if side == "buy": + spendable = spendable_cash_for_exchange_buy( + cash, + self.settings.ops_exchange_fee_lock_rate, + self.settings.ops_buy_safety_buffer_krw, + ) trade = compute_buy_order( - cash_krw=cash, + cash_krw=spendable, coin_qty=coin, price=price, fee_rate=fee_rate, min_order_krw=min_order, cluster_size=cluster_size, + buy_cash_pct=self.settings.ops_buy_cash_pct, + sizing_rules=self.sizing_rules, ) if not trade.executed: + if trade.expected_skip: + logger.info( + "live buy skip (%s): cash=%.0f spendable=%.0f coin=%.8f", + trade.skip_reason, + cash, + spendable, + coin, + ) return trade try: resp = self.client.market_buy_krw(self.settings.market, trade.order_krw) trade.api_response = resp self._sync_portfolio(portfolio) except Exception as exc: - logger.exception("live buy failed") + logger.exception( + "live buy failed order_krw=%.0f cash=%.0f", + trade.order_krw, + cash, + ) return TradeResult( executed=False, side="buy", - order_krw=0.0, - order_coin=0.0, + order_krw=trade.order_krw, + order_coin=trade.order_coin, fee_krw=0.0, price=price, skip_reason=str(exc), @@ -149,20 +176,33 @@ class LiveExecutor(OrderExecutor): fee_rate=fee_rate, min_order_krw=min_order, cluster_size=cluster_size, + sell_coin_pct=self.settings.ops_sell_coin_pct, + sizing_rules=self.sizing_rules, ) if not trade.executed: + if trade.expected_skip: + logger.info( + "live sell skip (%s): %s=%.8f cash=%.0f", + trade.skip_reason, + self.settings.symbol, + coin, + cash, + ) return trade try: resp = self.client.market_sell_volume(self.settings.market, trade.order_coin) trade.api_response = resp self._sync_portfolio(portfolio) except Exception as exc: - logger.exception("live sell failed") + logger.exception( + "live sell failed order_coin=%.8f", + trade.order_coin, + ) return TradeResult( executed=False, side="sell", - order_krw=0.0, - order_coin=0.0, + order_krw=trade.order_krw, + order_coin=trade.order_coin, fee_krw=0.0, price=price, skip_reason=str(exc), diff --git a/src/bithumb/operations/live_bootstrap.py b/src/bithumb/operations/live_bootstrap.py new file mode 100644 index 0000000..f41e1c1 --- /dev/null +++ b/src/bithumb/operations/live_bootstrap.py @@ -0,0 +1,253 @@ +"""live 운영 전 사전 점검 및 상태 초기화.""" + +from __future__ import annotations + +import json +import shutil +from datetime import datetime +from pathlib import Path +from typing import Any + +from bithumb.api.bithumb_private import BithumbPrivateClient +from bithumb.config import Settings +from bithumb.notifications.telegram import create_telegram_notifier +from bithumb.operations.state_store import load_state, save_state + + +def create_bithumb_client(settings: Settings) -> BithumbPrivateClient: + """설정 기반 빗썸 Private 클라이언트를 생성한다.""" + return BithumbPrivateClient( + access_key=settings.bithumb_access_key, + secret_key=settings.bithumb_secret_key, + base_url=settings.api_url, + sleep_sec=settings.request_sleep_sec, + retries=settings.request_retries, + ) + + +def sync_portfolio_from_exchange( + portfolio: dict[str, Any], + client: BithumbPrivateClient, + symbol: str, + *, + mode: str = "live", +) -> dict[str, float]: + """거래소 잔고로 포트폴리오를 갱신한다. + + Returns: + {"cash_krw", "coin_qty"} 스냅샷. + """ + krw_avail, krw_locked = client.get_balance("KRW") + coin_avail, coin_locked = client.get_balance(symbol) + portfolio["cash_krw"] = krw_avail + portfolio["coin_qty"] = coin_avail + portfolio["mode"] = mode + return { + "cash_krw": krw_avail, + "cash_locked_krw": krw_locked, + "coin_qty": coin_avail, + "coin_locked_qty": coin_locked, + } + + +def backup_state_file(state_path: Path) -> Path | None: + """기존 운영 상태 JSON을 백업한다.""" + if not state_path.exists(): + return None + stamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup = state_path.with_name(f"{state_path.stem}.backup_{stamp}{state_path.suffix}") + shutil.copy2(state_path, backup) + return backup + + +def init_live_state( + settings: Settings, + *, + backup: bool = True, + preserve_bar_cursor: bool = True, +) -> dict[str, Any]: + """paper 상태를 live로 전환하고 거래소 잔고를 반영한다.""" + state_path = settings.ops_state_json + backup_path: Path | None = None + if backup and state_path.exists(): + backup_path = backup_state_file(state_path) + + state = load_state(state_path, initial_cash_krw=settings.gt_initial_cash_krw) + if not preserve_bar_cursor: + state["last_processed_bar_index"] = -1 + state["last_processed_datetime"] = None + + client = create_bithumb_client(settings) + balances = sync_portfolio_from_exchange( + state["portfolio"], + client, + settings.symbol, + mode="live", + ) + + state["live_initialized_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + state["live_init_backup"] = str(backup_path) if backup_path else None + save_state(state_path, state) + + return { + "state_path": str(state_path), + "backup_path": str(backup_path) if backup_path else None, + "balances": balances, + "last_processed_bar_index": state.get("last_processed_bar_index", -1), + } + + +def run_preflight(settings: Settings) -> dict[str, Any]: + """live 운영 전 점검 항목을 실행한다.""" + checks: list[dict[str, Any]] = [] + ok = True + + def add(name: str, passed: bool, detail: str, *, required: bool = True) -> None: + nonlocal ok + if required and not passed: + ok = False + checks.append( + { + "name": name, + "passed": passed, + "required": required, + "detail": detail, + } + ) + + add( + "ops_mode_live", + settings.ops_mode == "live", + f"OPS_MODE={settings.ops_mode}", + ) + add( + "api_keys", + bool(settings.bithumb_access_key and settings.bithumb_secret_key), + "BITHUMB_ACCESS_KEY / BITHUMB_SECRET_KEY 설정", + ) + add( + "technique", + bool(settings.ops_technique_id), + f"OPS_TECHNIQUE_ID={settings.ops_technique_id}", + ) + + technique_path = settings.techniques_dir / f"{settings.ops_technique_id}.json" + add( + "technique_cache", + technique_path.exists(), + str(technique_path), + ) + + add( + "db_exists", + settings.db_path.exists(), + str(settings.db_path), + ) + + add( + "state_path_writable", + True, + str(settings.ops_state_json), + ) + settings.ops_state_json.parent.mkdir(parents=True, exist_ok=True) + settings.ops_report_json.parent.mkdir(parents=True, exist_ok=True) + + balances: dict[str, float] | None = None + if settings.bithumb_access_key and settings.bithumb_secret_key: + try: + client = create_bithumb_client(settings) + accounts = client.get_accounts() + add( + "api_connectivity", + isinstance(accounts, list), + f"accounts={len(accounts)}", + ) + balances = sync_portfolio_from_exchange( + {"cash_krw": 0.0, "coin_qty": 0.0}, + client, + settings.symbol, + ) + add( + "krw_balance", + balances["cash_krw"] >= settings.ops_min_order_krw, + ( + f"가용 {_fmt_krw(balances['cash_krw'])} " + f"(최소 주문 {_fmt_krw(settings.ops_min_order_krw)})" + ), + ) + add( + "coin_balance", + True, + ( + f"{settings.symbol} {balances['coin_qty']:.8f} " + f"(locked {balances['coin_locked_qty']:.8f})" + ), + required=False, + ) + except Exception as exc: + add("api_connectivity", False, str(exc)) + else: + add("api_connectivity", False, "API 키 없음") + + telegram = create_telegram_notifier( + settings.telegram_bot_token, + settings.telegram_chat_id, + enabled=settings.ops_telegram_enabled, + ) + telegram_ok = False + if telegram.is_active: + telegram_ok = telegram.send_message( + "[Bithumb] live 사전 점검 — 텔레그램 연결 OK" + ) + add( + "telegram", + telegram_ok if telegram.is_active else True, + "활성" if telegram.is_active else "비활성(선택)", + required=telegram.is_active, + ) + + backtest_path = settings.ops_filtered_backtest_json + add( + "backtest_report", + backtest_path.exists(), + str(backtest_path), + required=False, + ) + + add( + "daily_max_trades", + settings.ops_daily_max_trades > 0, + str(settings.ops_daily_max_trades), + ) + add( + "slippage_rate", + settings.ops_slippage_rate >= 0, + f"{settings.ops_slippage_rate * 100:.3f}%", + ) + + return { + "ok": ok, + "symbol": settings.symbol, + "market": settings.market, + "technique_id": settings.ops_technique_id, + "mode": settings.ops_mode, + "balances": balances, + "checks": checks, + } + + +def save_preflight_report(path: Path, report: dict[str, Any]) -> Path: + """사전 점검 결과를 JSON으로 저장한다.""" + path.parent.mkdir(parents=True, exist_ok=True) + payload = { + **report, + "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + } + with path.open("w", encoding="utf-8") as fp: + json.dump(payload, fp, ensure_ascii=False, indent=2) + return path + + +def _fmt_krw(value: float) -> str: + """원화 금액 포맷.""" + return f"{round(value):,}원" diff --git a/src/deepcoin/operations/runner.py b/src/bithumb/operations/runner.py similarity index 62% rename from src/deepcoin/operations/runner.py rename to src/bithumb/operations/runner.py index 5e57344..8861919 100644 --- a/src/deepcoin/operations/runner.py +++ b/src/bithumb/operations/runner.py @@ -5,21 +5,24 @@ from __future__ import annotations import json import logging import time +import traceback from datetime import datetime from pathlib import Path 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 ( +from bithumb.config import Settings +from bithumb.ground_truth.pnl import _cluster_signals +from bithumb.notifications.telegram import create_telegram_notifier +from bithumb.operations.candle_sync import sync_ops_candles +from bithumb.operations.executor import LiveExecutor, create_executor +from bithumb.operations.live_bootstrap import sync_portfolio_from_exchange +from bithumb.operations.signal_pipeline import ( filter_signals_for_ops, generate_raw_signals, load_ops_candles, ) -from deepcoin.operations.state_store import load_state, reset_daily_trade_count, save_state +from bithumb.operations.state_store import load_state, reset_daily_trade_count, save_state +from bithumb.operations.trade_engine import TradeResult logger = logging.getLogger(__name__) @@ -91,9 +94,30 @@ class OperationsRunner: initial_cash_krw=settings.gt_initial_cash_krw, ) self.state["portfolio"]["mode"] = settings.ops_mode + if settings.ops_mode == "live" and isinstance(self.executor, LiveExecutor): + try: + sync_portfolio_from_exchange( + self.state["portfolio"], + self.executor.client, + settings.symbol, + mode="live", + ) + save_state(settings.ops_state_json, self.state) + except Exception as exc: + logger.exception("live 초기 잔고 동기화 실패") + self._notify_ops_error("init_sync", exc) def tick(self, *, sync_candles: bool | None = None) -> dict[str, Any]: - """신호 확인 및 체결 1회.""" + """신호 확인 및 체결 1회. 예외 발생 시 텔레그램 알림 후 error 리포트 반환.""" + try: + return self._tick_impl(sync_candles=sync_candles) + except Exception as exc: + logger.exception("운영 tick 실패") + self._notify_ops_error("tick", exc) + return self._build_error_report(exc, stage="tick") + + def _tick_impl(self, *, sync_candles: bool | None = None) -> dict[str, Any]: + """tick 본체.""" do_sync = sync_candles if sync_candles is not None else self.settings.ops_sync_candles candle_sync_results: list[Any] = [] if do_sync: @@ -109,6 +133,18 @@ class OperationsRunner: kept = filtered["kept"] reset_daily_trade_count(self.state) + if self.settings.ops_mode == "live" and isinstance(self.executor, LiveExecutor): + try: + sync_portfolio_from_exchange( + self.state["portfolio"], + self.executor.client, + self.settings.symbol, + mode="live", + ) + except Exception as exc: + logger.exception("tick 잔고 동기화 실패") + self._notify_ops_error("portfolio_sync", exc) + last_bar = int(self.state.get("last_processed_bar_index", -1)) target_bars = _pending_bar_indices(kept, last_bar, latest_bar) @@ -128,11 +164,32 @@ class OperationsRunner: (k for k in kept if k["datetime"] == sig["datetime"]), sig, ) - trade = self.executor.execute_signal( - full_sig, - self.state["portfolio"], - cluster_size=cluster_size, - ) + try: + trade = self.executor.execute_signal( + full_sig, + self.state["portfolio"], + cluster_size=cluster_size, + ) + except Exception as exc: + logger.exception( + "체결 실행 예외 side=%s datetime=%s", + full_sig.get("side"), + full_sig.get("datetime"), + ) + self._notify_ops_error( + "execute", + exc, + context=f"side={full_sig.get('side')} dt={full_sig.get('datetime')}", + ) + trade = TradeResult( + executed=False, + side=str(full_sig.get("side", "")), + order_krw=0.0, + order_coin=0.0, + fee_krw=0.0, + price=float(full_sig.get("price", 0)), + skip_reason=str(exc), + ) record = { "datetime": full_sig["datetime"], "side": full_sig["side"], @@ -154,6 +211,7 @@ class OperationsRunner: elif ( self.settings.ops_mode == "live" and trade.skip_reason + and not trade.expected_skip and self.telegram.is_active ): self.telegram.notify_trade_failure( @@ -213,6 +271,50 @@ class OperationsRunner: self._save_report(report) return report + def _notify_ops_error( + self, + stage: str, + exc: Exception, + *, + context: str = "", + ) -> None: + """운영 예외를 텔레그램으로 알린다.""" + if not self.telegram.is_active: + return + tb_tail = traceback.format_exc(limit=4).strip() + detail_parts = [p for p in (context, tb_tail) if p] + self.telegram.notify_ops_error( + mode=self.settings.ops_mode, + symbol=self.settings.symbol, + technique_id=self.settings.ops_technique_id, + stage=stage, + error=str(exc), + detail="\n".join(detail_parts)[:800], + ) + + def _build_error_report(self, exc: Exception, *, stage: str) -> dict[str, Any]: + """tick 실패 시 저장·표시용 리포트.""" + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.state["last_run_at"] = now + report = { + "generated_at": now, + "mode": self.settings.ops_mode, + "technique_id": self.settings.ops_technique_id, + "error": True, + "error_stage": stage, + "error_message": str(exc), + "executions": [], + "portfolio": self.state.get("portfolio", {}), + "trades_today_count": self.state.get("trades_today_count", 0), + "last_processed_bar_index": self.state.get("last_processed_bar_index", -1), + } + try: + save_state(self.settings.ops_state_json, self.state) + self._save_report(report) + except Exception: + logger.exception("error 리포트 저장 실패") + return report + def _notify_trade( self, signal: dict[str, Any], @@ -243,5 +345,9 @@ class OperationsRunner: """최신 운영 리포트 저장.""" path = self.settings.ops_report_json path.parent.mkdir(parents=True, exist_ok=True) - with path.open("w", encoding="utf-8") as fp: - json.dump(report, fp, ensure_ascii=False, indent=2) + try: + with path.open("w", encoding="utf-8") as fp: + json.dump(report, fp, ensure_ascii=False, indent=2) + except OSError as exc: + logger.exception("ops_report 저장 실패: %s", path) + self._notify_ops_error("save_report", exc, context=str(path)) diff --git a/src/deepcoin/operations/signal_pipeline.py b/src/bithumb/operations/signal_pipeline.py similarity index 94% rename from src/deepcoin/operations/signal_pipeline.py rename to src/bithumb/operations/signal_pipeline.py index feecce3..607813a 100644 --- a/src/deepcoin/operations/signal_pipeline.py +++ b/src/bithumb/operations/signal_pipeline.py @@ -10,17 +10,17 @@ from typing import Any import pandas as pd -from deepcoin.config import Settings -from deepcoin.data.candle_loader import load_candles -from deepcoin.mtf.extractor import MtfFeatureExtractor -from deepcoin.mtf.filter import MtfSignalFilter -from deepcoin.mtf.rules import load_or_derive_mtf_rules -from deepcoin.mtf.store import MultiTimeframeStore -from deepcoin.mtf.trend_gate import HtfTrendGate -from deepcoin.operations.signal_type import enrich_signal_types -from deepcoin.techniques.base import TechniqueParams, TechniqueResult -from deepcoin.techniques.registry import get_technique -from deepcoin.techniques.runner import load_technique_result, run_technique, save_technique_result +from bithumb.config import Settings +from bithumb.data.candle_loader import load_candles +from bithumb.mtf.extractor import MtfFeatureExtractor +from bithumb.mtf.filter import MtfSignalFilter +from bithumb.mtf.rules import load_or_derive_mtf_rules +from bithumb.mtf.store import MultiTimeframeStore +from bithumb.mtf.trend_gate import HtfTrendGate +from bithumb.operations.signal_type import enrich_signal_types +from bithumb.techniques.base import TechniqueParams, TechniqueResult +from bithumb.techniques.registry import get_technique +from bithumb.techniques.runner import load_technique_result, run_technique, save_technique_result logger = logging.getLogger(__name__) diff --git a/src/deepcoin/operations/signal_type.py b/src/bithumb/operations/signal_type.py similarity index 100% rename from src/deepcoin/operations/signal_type.py rename to src/bithumb/operations/signal_type.py diff --git a/src/deepcoin/operations/state_store.py b/src/bithumb/operations/state_store.py similarity index 100% rename from src/deepcoin/operations/state_store.py rename to src/bithumb/operations/state_store.py diff --git a/src/deepcoin/operations/trade_engine.py b/src/bithumb/operations/trade_engine.py similarity index 58% rename from src/deepcoin/operations/trade_engine.py rename to src/bithumb/operations/trade_engine.py index 1937252..f536c1e 100644 --- a/src/deepcoin/operations/trade_engine.py +++ b/src/bithumb/operations/trade_engine.py @@ -2,10 +2,12 @@ from __future__ import annotations +import math from dataclasses import dataclass from typing import Any -from deepcoin.ground_truth.order_sizing import max_buy_from_cash +from bithumb.ground_truth.order_sizing import max_buy_from_cash, max_sell_coin_qty +from bithumb.ground_truth.sizing_rules import resolve_buy_cash_pct, resolve_sell_coin_pct @dataclass @@ -19,6 +21,7 @@ class TradeResult: fee_krw: float price: float skip_reason: str = "" + expected_skip: bool = False api_response: dict[str, Any] | None = None def to_dict(self) -> dict[str, Any]: @@ -31,10 +34,32 @@ class TradeResult: "fee_krw": round(self.fee_krw, 2), "price": self.price, "skip_reason": self.skip_reason, + "expected_skip": self.expected_skip, "api_response": self.api_response, } +def spendable_cash_for_exchange_buy( + cash_krw: float, + fee_lock_rate: float = 0.0025, + safety_buffer_krw: float = 1000.0, +) -> float: + """거래소 시장가 매수 시 주문 가능 원화 (수수료 lock + floor + 안전 여유). + + 빗썸은 주문금액 + 예약수수료를 KRW에서 동시에 lock하므로 + 가용 원화 전액을 price로 넣으면 insufficient_funds(400)가 발생한다. + ``floor(가용/(1+lock)) - safety_buffer_krw`` 로 주문 상한을 계산한다. + """ + cash = max(float(cash_krw), 0.0) + lock = max(float(fee_lock_rate), 0.0) + buffer = max(float(safety_buffer_krw), 0.0) + if lock <= 0: + spendable = cash + else: + spendable = cash / (1.0 + lock) + return max(0.0, math.floor(spendable) - buffer) + + def compute_buy_order( *, cash_krw: float, @@ -43,13 +68,16 @@ def compute_buy_order( fee_rate: float, min_order_krw: float, cluster_size: int = 1, + buy_cash_pct: float | None = None, + sizing_rules: dict | None = None, ) -> TradeResult: """매수 주문 금액·수량을 계산한다.""" cash = max(float(cash_krw), 0.0) equity = cash + float(coin_qty) * price - cash_cap = max_buy_from_cash(equity, cash) - per_buy = cash / cluster_size if cluster_size > 0 else cash - order_krw = min(per_buy, cash, cash_cap) + effective_pct = resolve_buy_cash_pct(sizing_rules, cluster_size, buy_cash_pct) + cash_cap = max_buy_from_cash(equity, cash, base_pct=effective_pct) + per_buy = cash_cap / cluster_size if cluster_size > 0 else cash_cap + order_krw = math.floor(min(per_buy, cash, cash_cap)) if order_krw < min_order_krw: return TradeResult( @@ -60,6 +88,7 @@ def compute_buy_order( fee_krw=0.0, price=price, skip_reason="원화 부족 또는 최소 주문 미만", + expected_skip=True, ) fee = order_krw * fee_rate @@ -81,13 +110,29 @@ def compute_sell_order( fee_rate: float, min_order_krw: float, cluster_size: int = 1, + sell_coin_pct: float | None = None, + sizing_rules: dict | None = None, ) -> TradeResult: """매도 주문 수량을 계산한다.""" qty = max(float(coin_qty), 0.0) - per_sell = qty / cluster_size if cluster_size > 0 else qty - order_coin = per_sell + effective_pct = resolve_sell_coin_pct(sizing_rules, cluster_size, sell_coin_pct) + sellable = max_sell_coin_qty(qty, sell_pct=effective_pct) + per_sell = sellable / cluster_size if cluster_size > 0 else sellable + order_coin = min(per_sell, qty) order_krw = order_coin * price + if qty <= 0: + return TradeResult( + executed=False, + side="sell", + order_krw=0.0, + order_coin=0.0, + fee_krw=0.0, + price=price, + skip_reason="보유 코인 없음", + expected_skip=True, + ) + if order_coin <= 0 or order_krw < min_order_krw: return TradeResult( executed=False, @@ -96,7 +141,8 @@ def compute_sell_order( order_coin=0.0, fee_krw=0.0, price=price, - skip_reason="코인 부족 또는 최소 주문 미만", + skip_reason="매도 가능 수량 부족(최소 주문 미만)", + expected_skip=True, ) fee = order_krw * fee_rate diff --git a/src/deepcoin/techniques/__init__.py b/src/bithumb/techniques/__init__.py similarity index 77% rename from src/deepcoin/techniques/__init__.py rename to src/bithumb/techniques/__init__.py index 52d9655..979c655 100644 --- a/src/deepcoin/techniques/__init__.py +++ b/src/bithumb/techniques/__init__.py @@ -1,13 +1,13 @@ """2단계: Ground Truth 정합 매매 기법.""" -from deepcoin.techniques.registry import ( +from bithumb.techniques.registry import ( get_all_techniques, get_composite_techniques, get_single_techniques, list_technique_ids, techniques_by_category, ) -from deepcoin.techniques.runner import run_all_techniques, run_technique +from bithumb.techniques.runner import run_all_techniques, run_technique __all__ = [ "get_all_techniques", diff --git a/src/deepcoin/techniques/adx_trend.py b/src/bithumb/techniques/adx_trend.py similarity index 90% rename from src/deepcoin/techniques/adx_trend.py rename to src/bithumb/techniques/adx_trend.py index 0c68df6..b59ecf7 100644 --- a/src/deepcoin/techniques/adx_trend.py +++ b/src/bithumb/techniques/adx_trend.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import adx +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import adx class AdxTrendTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/atr_channel.py b/src/bithumb/techniques/atr_channel.py similarity index 91% rename from src/deepcoin/techniques/atr_channel.py rename to src/bithumb/techniques/atr_channel.py index 648329c..bce02b2 100644 --- a/src/deepcoin/techniques/atr_channel.py +++ b/src/bithumb/techniques/atr_channel.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import atr, ema +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import atr, ema class AtrChannelTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/base.py b/src/bithumb/techniques/base.py similarity index 100% rename from src/deepcoin/techniques/base.py rename to src/bithumb/techniques/base.py diff --git a/src/deepcoin/techniques/bb_reversal.py b/src/bithumb/techniques/bb_reversal.py similarity index 94% rename from src/deepcoin/techniques/bb_reversal.py rename to src/bithumb/techniques/bb_reversal.py index eb04469..15a793f 100644 --- a/src/deepcoin/techniques/bb_reversal.py +++ b/src/bithumb/techniques/bb_reversal.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.indicators import bollinger_bands, ema +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.indicators import bollinger_bands, ema class BbReversalTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/bb_squeeze_breakout.py b/src/bithumb/techniques/bb_squeeze_breakout.py similarity index 91% rename from src/deepcoin/techniques/bb_squeeze_breakout.py rename to src/bithumb/techniques/bb_squeeze_breakout.py index ca87f61..7783a59 100644 --- a/src/deepcoin/techniques/bb_squeeze_breakout.py +++ b/src/bithumb/techniques/bb_squeeze_breakout.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import bollinger_bands +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import bollinger_bands class BbSqueezeBreakoutTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/cci_extreme.py b/src/bithumb/techniques/cci_extreme.py similarity index 90% rename from src/deepcoin/techniques/cci_extreme.py rename to src/bithumb/techniques/cci_extreme.py index f4a34fd..53cf925 100644 --- a/src/deepcoin/techniques/cci_extreme.py +++ b/src/bithumb/techniques/cci_extreme.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal, safe_float -from deepcoin.techniques.indicators import cci +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal, safe_float +from bithumb.techniques.indicators import cci class CciExtremeTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/composite_base.py b/src/bithumb/techniques/composite_base.py similarity index 97% rename from src/deepcoin/techniques/composite_base.py rename to src/bithumb/techniques/composite_base.py index ac73714..22c2f31 100644 --- a/src/deepcoin/techniques/composite_base.py +++ b/src/bithumb/techniques/composite_base.py @@ -6,8 +6,8 @@ from dataclasses import dataclass import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.indicators import ema +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.indicators import ema @dataclass(frozen=True) diff --git a/src/deepcoin/techniques/composite_breakout.py b/src/bithumb/techniques/composite_breakout.py similarity index 74% rename from src/deepcoin/techniques/composite_breakout.py rename to src/bithumb/techniques/composite_breakout.py index 6c976db..ab2b01e 100644 --- a/src/deepcoin/techniques/composite_breakout.py +++ b/src/bithumb/techniques/composite_breakout.py @@ -4,18 +4,18 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.bb_squeeze_breakout import BbSqueezeBreakoutTechnique -from deepcoin.techniques.composite_base import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.bb_squeeze_breakout import BbSqueezeBreakoutTechnique +from bithumb.techniques.composite_base import ( cluster_events, collect_weighted_events, score_clusters_to_signals, ) -from deepcoin.techniques.donchian import DonchianTechnique -from deepcoin.techniques.keltner_breakout import KeltnerBreakoutTechnique -from deepcoin.techniques.macd_cross import MacdCrossTechnique -from deepcoin.techniques.range_breakout import RangeBreakoutTechnique -from deepcoin.techniques.volume_breakout import VolumeBreakoutTechnique +from bithumb.techniques.donchian import DonchianTechnique +from bithumb.techniques.keltner_breakout import KeltnerBreakoutTechnique +from bithumb.techniques.macd_cross import MacdCrossTechnique +from bithumb.techniques.range_breakout import RangeBreakoutTechnique +from bithumb.techniques.volume_breakout import VolumeBreakoutTechnique _SUB = [ DonchianTechnique(), diff --git a/src/deepcoin/techniques/composite_divergence.py b/src/bithumb/techniques/composite_divergence.py similarity index 76% rename from src/deepcoin/techniques/composite_divergence.py rename to src/bithumb/techniques/composite_divergence.py index 0912699..0fcb1de 100644 --- a/src/deepcoin/techniques/composite_divergence.py +++ b/src/bithumb/techniques/composite_divergence.py @@ -4,17 +4,17 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.composite_base import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.composite_base import ( cluster_events, collect_weighted_events, score_clusters_to_signals, ) -from deepcoin.techniques.macd_cross import MacdCrossTechnique -from deepcoin.techniques.macd_divergence import MacdDivergenceTechnique -from deepcoin.techniques.obv_divergence import ObvDivergenceTechnique -from deepcoin.techniques.rsi_divergence import RsiDivergenceTechnique -from deepcoin.techniques.rsi_swing import RsiSwingTechnique +from bithumb.techniques.macd_cross import MacdCrossTechnique +from bithumb.techniques.macd_divergence import MacdDivergenceTechnique +from bithumb.techniques.obv_divergence import ObvDivergenceTechnique +from bithumb.techniques.rsi_divergence import RsiDivergenceTechnique +from bithumb.techniques.rsi_swing import RsiSwingTechnique _SUB = [ RsiDivergenceTechnique(), diff --git a/src/deepcoin/techniques/composite_full.py b/src/bithumb/techniques/composite_full.py similarity index 90% rename from src/deepcoin/techniques/composite_full.py rename to src/bithumb/techniques/composite_full.py index b6bf021..63b15c4 100644 --- a/src/deepcoin/techniques/composite_full.py +++ b/src/bithumb/techniques/composite_full.py @@ -4,15 +4,15 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.composite_base import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.composite_base import ( cluster_events, collect_weighted_events, score_clusters_to_signals, ) def _build_full_sub_techniques() -> list[BaseTechnique]: """복합 제외 단일 기법 목록을 반환한다.""" - from deepcoin.techniques.registry import get_single_techniques + from bithumb.techniques.registry import get_single_techniques return get_single_techniques() diff --git a/src/deepcoin/techniques/composite_pullback.py b/src/bithumb/techniques/composite_pullback.py similarity index 74% rename from src/deepcoin/techniques/composite_pullback.py rename to src/bithumb/techniques/composite_pullback.py index 4c0e286..67cf905 100644 --- a/src/deepcoin/techniques/composite_pullback.py +++ b/src/bithumb/techniques/composite_pullback.py @@ -4,18 +4,18 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.bb_reversal import BbReversalTechnique -from deepcoin.techniques.composite_base import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.bb_reversal import BbReversalTechnique +from bithumb.techniques.composite_base import ( cluster_events, collect_weighted_events, score_clusters_to_signals, ) -from deepcoin.techniques.ema_pullback import EmaPullbackTechnique -from deepcoin.techniques.fib_pullback import FibPullbackTechnique -from deepcoin.techniques.keltner_reversal import KeltnerReversalTechnique -from deepcoin.techniques.local_extrema import LocalExtremaTechnique -from deepcoin.techniques.support_bounce import SupportBounceTechnique +from bithumb.techniques.ema_pullback import EmaPullbackTechnique +from bithumb.techniques.fib_pullback import FibPullbackTechnique +from bithumb.techniques.keltner_reversal import KeltnerReversalTechnique +from bithumb.techniques.local_extrema import LocalExtremaTechnique +from bithumb.techniques.support_bounce import SupportBounceTechnique _SUB = [ EmaPullbackTechnique(), diff --git a/src/deepcoin/techniques/composite_swing.py b/src/bithumb/techniques/composite_swing.py similarity index 73% rename from src/deepcoin/techniques/composite_swing.py rename to src/bithumb/techniques/composite_swing.py index 53ba1c2..f606c34 100644 --- a/src/deepcoin/techniques/composite_swing.py +++ b/src/bithumb/techniques/composite_swing.py @@ -4,19 +4,19 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.composite_base import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.composite_base import ( cluster_events, collect_weighted_events, score_clusters_to_signals, ) -from deepcoin.techniques.donchian import DonchianTechnique -from deepcoin.techniques.fractal_swing import FractalSwingTechnique -from deepcoin.techniques.local_extrema import LocalExtremaTechnique -from deepcoin.techniques.minor_swing import MinorSwingTechnique -from deepcoin.techniques.pivot_swing import PivotSwingTechnique -from deepcoin.techniques.swing_failure import SwingFailureTechnique -from deepcoin.techniques.zigzag_causal import ZigzagCausalTechnique +from bithumb.techniques.donchian import DonchianTechnique +from bithumb.techniques.fractal_swing import FractalSwingTechnique +from bithumb.techniques.local_extrema import LocalExtremaTechnique +from bithumb.techniques.minor_swing import MinorSwingTechnique +from bithumb.techniques.pivot_swing import PivotSwingTechnique +from bithumb.techniques.swing_failure import SwingFailureTechnique +from bithumb.techniques.zigzag_causal import ZigzagCausalTechnique _SUB = [ ZigzagCausalTechnique(), diff --git a/src/deepcoin/techniques/composite_v3.py b/src/bithumb/techniques/composite_v3.py similarity index 66% rename from src/deepcoin/techniques/composite_v3.py rename to src/bithumb/techniques/composite_v3.py index ac9b29f..8d2e456 100644 --- a/src/deepcoin/techniques/composite_v3.py +++ b/src/bithumb/techniques/composite_v3.py @@ -4,29 +4,29 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.bb_reversal import BbReversalTechnique -from deepcoin.techniques.composite_base import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.bb_reversal import BbReversalTechnique +from bithumb.techniques.composite_base import ( cluster_events, collect_weighted_events, score_clusters_to_signals, ) -from deepcoin.techniques.donchian import DonchianTechnique -from deepcoin.techniques.ema_pullback import EmaPullbackTechnique -from deepcoin.techniques.fib_pullback import FibPullbackTechnique -from deepcoin.techniques.fractal_swing import FractalSwingTechnique -from deepcoin.techniques.keltner_breakout import KeltnerBreakoutTechnique -from deepcoin.techniques.local_extrema import LocalExtremaTechnique -from deepcoin.techniques.macd_cross import MacdCrossTechnique -from deepcoin.techniques.macd_divergence import MacdDivergenceTechnique -from deepcoin.techniques.minor_swing import MinorSwingTechnique -from deepcoin.techniques.obv_divergence import ObvDivergenceTechnique -from deepcoin.techniques.pivot_swing import PivotSwingTechnique -from deepcoin.techniques.range_breakout import RangeBreakoutTechnique -from deepcoin.techniques.rsi_divergence import RsiDivergenceTechnique -from deepcoin.techniques.rsi_swing import RsiSwingTechnique -from deepcoin.techniques.support_bounce import SupportBounceTechnique -from deepcoin.techniques.zigzag_causal import ZigzagCausalTechnique +from bithumb.techniques.donchian import DonchianTechnique +from bithumb.techniques.ema_pullback import EmaPullbackTechnique +from bithumb.techniques.fib_pullback import FibPullbackTechnique +from bithumb.techniques.fractal_swing import FractalSwingTechnique +from bithumb.techniques.keltner_breakout import KeltnerBreakoutTechnique +from bithumb.techniques.local_extrema import LocalExtremaTechnique +from bithumb.techniques.macd_cross import MacdCrossTechnique +from bithumb.techniques.macd_divergence import MacdDivergenceTechnique +from bithumb.techniques.minor_swing import MinorSwingTechnique +from bithumb.techniques.obv_divergence import ObvDivergenceTechnique +from bithumb.techniques.pivot_swing import PivotSwingTechnique +from bithumb.techniques.range_breakout import RangeBreakoutTechnique +from bithumb.techniques.rsi_divergence import RsiDivergenceTechnique +from bithumb.techniques.rsi_swing import RsiSwingTechnique +from bithumb.techniques.support_bounce import SupportBounceTechnique +from bithumb.techniques.zigzag_causal import ZigzagCausalTechnique _TECHNIQUE_WEIGHTS: dict[str, tuple[float, float]] = { "zigzag_causal": (2.5, 2.5), diff --git a/src/deepcoin/techniques/donchian.py b/src/bithumb/techniques/donchian.py similarity index 96% rename from src/deepcoin/techniques/donchian.py rename to src/bithumb/techniques/donchian.py index 772f5a6..438b71c 100644 --- a/src/deepcoin/techniques/donchian.py +++ b/src/bithumb/techniques/donchian.py @@ -4,7 +4,7 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal class DonchianTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/ema_pullback.py b/src/bithumb/techniques/ema_pullback.py similarity index 92% rename from src/deepcoin/techniques/ema_pullback.py rename to src/bithumb/techniques/ema_pullback.py index 8647a19..c4e9ca7 100644 --- a/src/deepcoin/techniques/ema_pullback.py +++ b/src/bithumb/techniques/ema_pullback.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import ema +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import ema class EmaPullbackTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/fib_pullback.py b/src/bithumb/techniques/fib_pullback.py similarity index 95% rename from src/deepcoin/techniques/fib_pullback.py rename to src/bithumb/techniques/fib_pullback.py index 707b10c..818126c 100644 --- a/src/deepcoin/techniques/fib_pullback.py +++ b/src/bithumb/techniques/fib_pullback.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import find_confirmed_pivots, make_signal -from deepcoin.techniques.indicators import ema +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import find_confirmed_pivots, make_signal +from bithumb.techniques.indicators import ema class FibPullbackTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/fractal_swing.py b/src/bithumb/techniques/fractal_swing.py similarity index 90% rename from src/deepcoin/techniques/fractal_swing.py rename to src/bithumb/techniques/fractal_swing.py index 6ae0928..a2b6e77 100644 --- a/src/deepcoin/techniques/fractal_swing.py +++ b/src/bithumb/techniques/fractal_swing.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import dedupe_signals, find_fractal_pivots, make_signal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import dedupe_signals, find_fractal_pivots, make_signal class FractalSwingTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/helpers.py b/src/bithumb/techniques/helpers.py similarity index 99% rename from src/deepcoin/techniques/helpers.py rename to src/bithumb/techniques/helpers.py index ef0e9b1..994943f 100644 --- a/src/deepcoin/techniques/helpers.py +++ b/src/bithumb/techniques/helpers.py @@ -4,7 +4,7 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import TechniqueSignal +from bithumb.techniques.base import TechniqueSignal def safe_float(value: object) -> float | None: diff --git a/src/deepcoin/techniques/ichimoku_trend.py b/src/bithumb/techniques/ichimoku_trend.py similarity index 89% rename from src/deepcoin/techniques/ichimoku_trend.py rename to src/bithumb/techniques/ichimoku_trend.py index f45f6da..6bf8d46 100644 --- a/src/deepcoin/techniques/ichimoku_trend.py +++ b/src/bithumb/techniques/ichimoku_trend.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import ichimoku +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import ichimoku class IchimokuTrendTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/indicators.py b/src/bithumb/techniques/indicators.py similarity index 100% rename from src/deepcoin/techniques/indicators.py rename to src/bithumb/techniques/indicators.py diff --git a/src/deepcoin/techniques/keltner_breakout.py b/src/bithumb/techniques/keltner_breakout.py similarity index 90% rename from src/deepcoin/techniques/keltner_breakout.py rename to src/bithumb/techniques/keltner_breakout.py index 24240bf..042c85d 100644 --- a/src/deepcoin/techniques/keltner_breakout.py +++ b/src/bithumb/techniques/keltner_breakout.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import keltner_channels +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import keltner_channels class KeltnerBreakoutTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/keltner_reversal.py b/src/bithumb/techniques/keltner_reversal.py similarity index 91% rename from src/deepcoin/techniques/keltner_reversal.py rename to src/bithumb/techniques/keltner_reversal.py index 2a17975..9a3a464 100644 --- a/src/deepcoin/techniques/keltner_reversal.py +++ b/src/bithumb/techniques/keltner_reversal.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import keltner_channels +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import keltner_channels class KeltnerReversalTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/legs.py b/src/bithumb/techniques/legs.py similarity index 98% rename from src/deepcoin/techniques/legs.py rename to src/bithumb/techniques/legs.py index 5adb1b1..47c5bd5 100644 --- a/src/deepcoin/techniques/legs.py +++ b/src/bithumb/techniques/legs.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import Any -from deepcoin.techniques.base import TechniqueSignal +from bithumb.techniques.base import TechniqueSignal def signals_to_legs( diff --git a/src/deepcoin/techniques/local_extrema.py b/src/bithumb/techniques/local_extrema.py similarity index 98% rename from src/deepcoin/techniques/local_extrema.py rename to src/bithumb/techniques/local_extrema.py index 5b79d26..59c2eb8 100644 --- a/src/deepcoin/techniques/local_extrema.py +++ b/src/bithumb/techniques/local_extrema.py @@ -4,7 +4,7 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal class LocalExtremaTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/ma_cross.py b/src/bithumb/techniques/ma_cross.py similarity index 94% rename from src/deepcoin/techniques/ma_cross.py rename to src/bithumb/techniques/ma_cross.py index 1c2bb27..a6cf005 100644 --- a/src/deepcoin/techniques/ma_cross.py +++ b/src/bithumb/techniques/ma_cross.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.indicators import ema +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.indicators import ema class MaCrossTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/macd_cross.py b/src/bithumb/techniques/macd_cross.py similarity index 94% rename from src/deepcoin/techniques/macd_cross.py rename to src/bithumb/techniques/macd_cross.py index 366ae4c..dce558f 100644 --- a/src/deepcoin/techniques/macd_cross.py +++ b/src/bithumb/techniques/macd_cross.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.indicators import macd +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.indicators import macd class MacdCrossTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/macd_divergence.py b/src/bithumb/techniques/macd_divergence.py similarity index 93% rename from src/deepcoin/techniques/macd_divergence.py rename to src/bithumb/techniques/macd_divergence.py index 7fd2416..89aa232 100644 --- a/src/deepcoin/techniques/macd_divergence.py +++ b/src/bithumb/techniques/macd_divergence.py @@ -4,15 +4,15 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import ( dedupe_signals, detect_bearish_divergence, detect_bullish_divergence, find_confirmed_pivots, make_signal, ) -from deepcoin.techniques.indicators import macd +from bithumb.techniques.indicators import macd class MacdDivergenceTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/minor_swing.py b/src/bithumb/techniques/minor_swing.py similarity index 91% rename from src/deepcoin/techniques/minor_swing.py rename to src/bithumb/techniques/minor_swing.py index c4525e3..16f7322 100644 --- a/src/deepcoin/techniques/minor_swing.py +++ b/src/bithumb/techniques/minor_swing.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.local_extrema import LocalExtremaTechnique -from deepcoin.techniques.zigzag_causal import find_causal_zigzag_signals +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.local_extrema import LocalExtremaTechnique +from bithumb.techniques.zigzag_causal import find_causal_zigzag_signals class MinorSwingTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/obv_divergence.py b/src/bithumb/techniques/obv_divergence.py similarity index 93% rename from src/deepcoin/techniques/obv_divergence.py rename to src/bithumb/techniques/obv_divergence.py index bb9b9f4..1e74640 100644 --- a/src/deepcoin/techniques/obv_divergence.py +++ b/src/bithumb/techniques/obv_divergence.py @@ -4,15 +4,15 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import ( dedupe_signals, detect_bearish_divergence, detect_bullish_divergence, find_confirmed_pivots, make_signal, ) -from deepcoin.techniques.indicators import obv +from bithumb.techniques.indicators import obv class ObvDivergenceTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/parabolic_sar_signal.py b/src/bithumb/techniques/parabolic_sar_signal.py similarity index 87% rename from src/deepcoin/techniques/parabolic_sar_signal.py rename to src/bithumb/techniques/parabolic_sar_signal.py index 8570988..88fabb0 100644 --- a/src/deepcoin/techniques/parabolic_sar_signal.py +++ b/src/bithumb/techniques/parabolic_sar_signal.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import parabolic_sar +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import parabolic_sar class ParabolicSarTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/pivot_points.py b/src/bithumb/techniques/pivot_points.py similarity index 90% rename from src/deepcoin/techniques/pivot_points.py rename to src/bithumb/techniques/pivot_points.py index 0d78150..815ed3e 100644 --- a/src/deepcoin/techniques/pivot_points.py +++ b/src/bithumb/techniques/pivot_points.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import rolling_pivot_points +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import rolling_pivot_points class PivotPointsTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/pivot_swing.py b/src/bithumb/techniques/pivot_swing.py similarity index 90% rename from src/deepcoin/techniques/pivot_swing.py rename to src/bithumb/techniques/pivot_swing.py index ba5fdba..f4e56f2 100644 --- a/src/deepcoin/techniques/pivot_swing.py +++ b/src/bithumb/techniques/pivot_swing.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import dedupe_signals, find_confirmed_pivots, make_signal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import dedupe_signals, find_confirmed_pivots, make_signal class PivotSwingTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/range_breakout.py b/src/bithumb/techniques/range_breakout.py similarity index 92% rename from src/deepcoin/techniques/range_breakout.py rename to src/bithumb/techniques/range_breakout.py index d028ce1..e931a30 100644 --- a/src/deepcoin/techniques/range_breakout.py +++ b/src/bithumb/techniques/range_breakout.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal class RangeBreakoutTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/registry.py b/src/bithumb/techniques/registry.py similarity index 50% rename from src/deepcoin/techniques/registry.py rename to src/bithumb/techniques/registry.py index c28fc29..28ccaf7 100644 --- a/src/deepcoin/techniques/registry.py +++ b/src/bithumb/techniques/registry.py @@ -2,46 +2,46 @@ from __future__ import annotations -from deepcoin.techniques.base import BaseTechnique -from deepcoin.techniques.adx_trend import AdxTrendTechnique -from deepcoin.techniques.atr_channel import AtrChannelTechnique -from deepcoin.techniques.bb_reversal import BbReversalTechnique -from deepcoin.techniques.bb_squeeze_breakout import BbSqueezeBreakoutTechnique -from deepcoin.techniques.cci_extreme import CciExtremeTechnique -from deepcoin.techniques.composite_breakout import CompositeBreakoutTechnique -from deepcoin.techniques.composite_divergence import CompositeDivergenceTechnique -from deepcoin.techniques.composite_full import CompositeFullTechnique -from deepcoin.techniques.composite_pullback import CompositePullbackTechnique -from deepcoin.techniques.composite_swing import CompositeSwingTechnique -from deepcoin.techniques.composite_v3 import CompositeV3Technique -from deepcoin.techniques.donchian import DonchianTechnique -from deepcoin.techniques.ema_pullback import EmaPullbackTechnique -from deepcoin.techniques.fib_pullback import FibPullbackTechnique -from deepcoin.techniques.fractal_swing import FractalSwingTechnique -from deepcoin.techniques.ichimoku_trend import IchimokuTrendTechnique -from deepcoin.techniques.keltner_breakout import KeltnerBreakoutTechnique -from deepcoin.techniques.keltner_reversal import KeltnerReversalTechnique -from deepcoin.techniques.local_extrema import LocalExtremaTechnique -from deepcoin.techniques.ma_cross import MaCrossTechnique -from deepcoin.techniques.macd_cross import MacdCrossTechnique -from deepcoin.techniques.macd_divergence import MacdDivergenceTechnique -from deepcoin.techniques.minor_swing import MinorSwingTechnique -from deepcoin.techniques.obv_divergence import ObvDivergenceTechnique -from deepcoin.techniques.parabolic_sar_signal import ParabolicSarTechnique -from deepcoin.techniques.pivot_points import PivotPointsTechnique -from deepcoin.techniques.pivot_swing import PivotSwingTechnique -from deepcoin.techniques.range_breakout import RangeBreakoutTechnique -from deepcoin.techniques.roc_reversal import RocReversalTechnique -from deepcoin.techniques.rsi_divergence import RsiDivergenceTechnique -from deepcoin.techniques.rsi_swing import RsiSwingTechnique -from deepcoin.techniques.stochastic_cross import StochasticCrossTechnique -from deepcoin.techniques.supertrend_signal import SupertrendTechnique -from deepcoin.techniques.support_bounce import SupportBounceTechnique -from deepcoin.techniques.support_resistance import SupportResistanceTechnique -from deepcoin.techniques.swing_failure import SwingFailureTechnique -from deepcoin.techniques.volume_breakout import VolumeBreakoutTechnique -from deepcoin.techniques.volume_spike import VolumeSpikeTechnique -from deepcoin.techniques.zigzag_causal import ZigzagCausalTechnique +from bithumb.techniques.base import BaseTechnique +from bithumb.techniques.adx_trend import AdxTrendTechnique +from bithumb.techniques.atr_channel import AtrChannelTechnique +from bithumb.techniques.bb_reversal import BbReversalTechnique +from bithumb.techniques.bb_squeeze_breakout import BbSqueezeBreakoutTechnique +from bithumb.techniques.cci_extreme import CciExtremeTechnique +from bithumb.techniques.composite_breakout import CompositeBreakoutTechnique +from bithumb.techniques.composite_divergence import CompositeDivergenceTechnique +from bithumb.techniques.composite_full import CompositeFullTechnique +from bithumb.techniques.composite_pullback import CompositePullbackTechnique +from bithumb.techniques.composite_swing import CompositeSwingTechnique +from bithumb.techniques.composite_v3 import CompositeV3Technique +from bithumb.techniques.donchian import DonchianTechnique +from bithumb.techniques.ema_pullback import EmaPullbackTechnique +from bithumb.techniques.fib_pullback import FibPullbackTechnique +from bithumb.techniques.fractal_swing import FractalSwingTechnique +from bithumb.techniques.ichimoku_trend import IchimokuTrendTechnique +from bithumb.techniques.keltner_breakout import KeltnerBreakoutTechnique +from bithumb.techniques.keltner_reversal import KeltnerReversalTechnique +from bithumb.techniques.local_extrema import LocalExtremaTechnique +from bithumb.techniques.ma_cross import MaCrossTechnique +from bithumb.techniques.macd_cross import MacdCrossTechnique +from bithumb.techniques.macd_divergence import MacdDivergenceTechnique +from bithumb.techniques.minor_swing import MinorSwingTechnique +from bithumb.techniques.obv_divergence import ObvDivergenceTechnique +from bithumb.techniques.parabolic_sar_signal import ParabolicSarTechnique +from bithumb.techniques.pivot_points import PivotPointsTechnique +from bithumb.techniques.pivot_swing import PivotSwingTechnique +from bithumb.techniques.range_breakout import RangeBreakoutTechnique +from bithumb.techniques.roc_reversal import RocReversalTechnique +from bithumb.techniques.rsi_divergence import RsiDivergenceTechnique +from bithumb.techniques.rsi_swing import RsiSwingTechnique +from bithumb.techniques.stochastic_cross import StochasticCrossTechnique +from bithumb.techniques.supertrend_signal import SupertrendTechnique +from bithumb.techniques.support_bounce import SupportBounceTechnique +from bithumb.techniques.support_resistance import SupportResistanceTechnique +from bithumb.techniques.swing_failure import SwingFailureTechnique +from bithumb.techniques.volume_breakout import VolumeBreakoutTechnique +from bithumb.techniques.volume_spike import VolumeSpikeTechnique +from bithumb.techniques.zigzag_causal import ZigzagCausalTechnique # 카테고리별 단일 기법 (인과, 미래 데이터 미사용) _SINGLE_TECHNIQUES: list[BaseTechnique] = [ diff --git a/src/deepcoin/techniques/roc_reversal.py b/src/bithumb/techniques/roc_reversal.py similarity index 89% rename from src/deepcoin/techniques/roc_reversal.py rename to src/bithumb/techniques/roc_reversal.py index d13963a..be41c2c 100644 --- a/src/deepcoin/techniques/roc_reversal.py +++ b/src/bithumb/techniques/roc_reversal.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal, safe_float -from deepcoin.techniques.indicators import roc +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal, safe_float +from bithumb.techniques.indicators import roc class RocReversalTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/rsi_divergence.py b/src/bithumb/techniques/rsi_divergence.py similarity index 93% rename from src/deepcoin/techniques/rsi_divergence.py rename to src/bithumb/techniques/rsi_divergence.py index 3def391..329c778 100644 --- a/src/deepcoin/techniques/rsi_divergence.py +++ b/src/bithumb/techniques/rsi_divergence.py @@ -4,15 +4,15 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import ( +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import ( dedupe_signals, detect_bearish_divergence, detect_bullish_divergence, find_confirmed_pivots, make_signal, ) -from deepcoin.techniques.indicators import rsi +from bithumb.techniques.indicators import rsi class RsiDivergenceTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/rsi_swing.py b/src/bithumb/techniques/rsi_swing.py similarity index 94% rename from src/deepcoin/techniques/rsi_swing.py rename to src/bithumb/techniques/rsi_swing.py index acdb552..170c47f 100644 --- a/src/deepcoin/techniques/rsi_swing.py +++ b/src/bithumb/techniques/rsi_swing.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.indicators import rsi +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.indicators import rsi class RsiSwingTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/runner.py b/src/bithumb/techniques/runner.py similarity index 92% rename from src/deepcoin/techniques/runner.py rename to src/bithumb/techniques/runner.py index 172b023..2331314 100644 --- a/src/deepcoin/techniques/runner.py +++ b/src/bithumb/techniques/runner.py @@ -13,13 +13,13 @@ import pandas as pd logger = logging.getLogger(__name__) -from deepcoin.data.candle_loader import load_candles -from deepcoin.data.intervals import interval_label -from deepcoin.evaluation.gt_align import align_with_ground_truth -from deepcoin.ground_truth.pnl import simulate_gt_pnl -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueResult -from deepcoin.techniques.legs import legs_to_signal_dicts, signals_to_legs, summarize_legs -from deepcoin.techniques.registry import get_all_techniques, get_technique +from bithumb.data.candle_loader import load_candles +from bithumb.data.intervals import interval_label +from bithumb.evaluation.gt_align import align_with_ground_truth +from bithumb.ground_truth.pnl import simulate_gt_pnl +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueResult +from bithumb.techniques.legs import legs_to_signal_dicts, signals_to_legs, summarize_legs +from bithumb.techniques.registry import get_all_techniques, get_technique def run_technique( diff --git a/src/deepcoin/techniques/stochastic_cross.py b/src/bithumb/techniques/stochastic_cross.py similarity index 90% rename from src/deepcoin/techniques/stochastic_cross.py rename to src/bithumb/techniques/stochastic_cross.py index cd9f9fb..44f0925 100644 --- a/src/deepcoin/techniques/stochastic_cross.py +++ b/src/bithumb/techniques/stochastic_cross.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import stochastic +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import stochastic class StochasticCrossTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/supertrend_signal.py b/src/bithumb/techniques/supertrend_signal.py similarity index 88% rename from src/deepcoin/techniques/supertrend_signal.py rename to src/bithumb/techniques/supertrend_signal.py index c2914c3..0a61fbc 100644 --- a/src/deepcoin/techniques/supertrend_signal.py +++ b/src/bithumb/techniques/supertrend_signal.py @@ -4,9 +4,9 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal -from deepcoin.techniques.indicators import supertrend +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal +from bithumb.techniques.indicators import supertrend class SupertrendTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/support_bounce.py b/src/bithumb/techniques/support_bounce.py similarity index 93% rename from src/deepcoin/techniques/support_bounce.py rename to src/bithumb/techniques/support_bounce.py index 9cdb532..c4bbed4 100644 --- a/src/deepcoin/techniques/support_bounce.py +++ b/src/bithumb/techniques/support_bounce.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal class SupportBounceTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/support_resistance.py b/src/bithumb/techniques/support_resistance.py similarity index 93% rename from src/deepcoin/techniques/support_resistance.py rename to src/bithumb/techniques/support_resistance.py index c63cba7..250ca09 100644 --- a/src/deepcoin/techniques/support_resistance.py +++ b/src/bithumb/techniques/support_resistance.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import dedupe_signals, find_confirmed_pivots, make_signal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import dedupe_signals, find_confirmed_pivots, make_signal class SupportResistanceTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/swing_failure.py b/src/bithumb/techniques/swing_failure.py similarity index 93% rename from src/deepcoin/techniques/swing_failure.py rename to src/bithumb/techniques/swing_failure.py index 1b75a82..e75a9f5 100644 --- a/src/deepcoin/techniques/swing_failure.py +++ b/src/bithumb/techniques/swing_failure.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import dedupe_signals, find_confirmed_pivots, make_signal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import dedupe_signals, find_confirmed_pivots, make_signal class SwingFailureTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/volume_breakout.py b/src/bithumb/techniques/volume_breakout.py similarity index 92% rename from src/deepcoin/techniques/volume_breakout.py rename to src/bithumb/techniques/volume_breakout.py index 15349b5..bc865bb 100644 --- a/src/deepcoin/techniques/volume_breakout.py +++ b/src/bithumb/techniques/volume_breakout.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal class VolumeBreakoutTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/volume_spike.py b/src/bithumb/techniques/volume_spike.py similarity index 93% rename from src/deepcoin/techniques/volume_spike.py rename to src/bithumb/techniques/volume_spike.py index 4307311..88e25bb 100644 --- a/src/deepcoin/techniques/volume_spike.py +++ b/src/bithumb/techniques/volume_spike.py @@ -4,8 +4,8 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal -from deepcoin.techniques.helpers import make_signal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.helpers import make_signal class VolumeSpikeTechnique(BaseTechnique): diff --git a/src/deepcoin/techniques/zigzag_causal.py b/src/bithumb/techniques/zigzag_causal.py similarity index 97% rename from src/deepcoin/techniques/zigzag_causal.py rename to src/bithumb/techniques/zigzag_causal.py index 352add9..780f317 100644 --- a/src/deepcoin/techniques/zigzag_causal.py +++ b/src/bithumb/techniques/zigzag_causal.py @@ -4,7 +4,7 @@ from __future__ import annotations import pandas as pd -from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal +from bithumb.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal class ZigzagCausalTechnique(BaseTechnique): diff --git a/src/deepcoin/__init__.py b/src/deepcoin/__init__.py deleted file mode 100644 index 8f97be6..0000000 --- a/src/deepcoin/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""DeepCoin — 빗썸 암호화폐 데이터 수집·분석.""" - -__version__ = "0.1.0" diff --git a/src/deepcoin/ground_truth/futures.py b/src/deepcoin/ground_truth/futures.py deleted file mode 100644 index b08e098..0000000 --- a/src/deepcoin/ground_truth/futures.py +++ /dev/null @@ -1,128 +0,0 @@ -"""현물 Ground Truth 신호를 선물 롱·숏 타점으로 변환한다.""" - -from __future__ import annotations - -from typing import Any - -import pandas as pd - - -def futures_events_from_gt_signals( - signals: list[dict[str, Any]], -) -> list[dict[str, Any]]: - """현물 GT 신호를 선물 이벤트 스트림으로 변환한다. - - 동일 봉에서 청산 이벤트를 먼저, 진입 이벤트를 나중에 배치한다. - - Args: - signals: GT signals 리스트. - - Returns: - position, action, event_order 등이 포함된 이벤트 리스트. - """ - events: list[dict[str, Any]] = [] - for sig in signals: - side = sig["side"] - signal_type = sig.get( - "signal_type", - "swing_low" if side == "buy" else "swing_high", - ) - base = { - "datetime": sig["datetime"], - "price": sig["price"], - "bar_index": sig.get("bar_index", 0), - "marker_id": sig.get("marker_id", sig.get("leg_id")), - "signal_type": signal_type, - "leg_id": sig.get("leg_id"), - } - if side == "buy": - events.append({**base, "position": "short", "action": "close", "event_order": 0}) - events.append({**base, "position": "long", "action": "open", "event_order": 1}) - else: - events.append({**base, "position": "long", "action": "close", "event_order": 0}) - events.append({**base, "position": "short", "action": "open", "event_order": 1}) - return events - - -def futures_markers_from_gt_signals( - gt_result: dict[str, Any], -) -> dict[str, list[dict[str, Any]]]: - """현물 GT 신호를 선물 4종 마커(상·하방 매수·매도)로 변환한다. - - 현물 GT의 스윙 저점(buy)과 고점(sell)을 동일 가격·시각에 - 롱·숏 양방향 선물 타점으로 매핑한다. - - - buy → 상방 매수(롱 진입), 하방 매도(숏 청산) - - sell → 상방 매도(롱 청산), 하방 매수(숏 진입) - - Args: - gt_result: build_ground_truth 결과 또는 동일 스키마 JSON. - - Returns: - long_open, long_close, short_open, short_close 마커 리스트. - """ - long_open: list[dict[str, Any]] = [] - long_close: list[dict[str, Any]] = [] - short_open: list[dict[str, Any]] = [] - short_close: list[dict[str, Any]] = [] - - for sig in gt_result.get("signals") or []: - side = sig["side"] - signal_type = sig.get( - "signal_type", - "swing_low" if side == "buy" else "swing_high", - ) - marker_id = sig.get("marker_id", sig.get("leg_id")) - base = { - "time": int(pd.Timestamp(sig["datetime"]).timestamp()), - "price": sig["price"], - "marker_id": marker_id, - "signal_type": signal_type, - "leg_id": sig.get("leg_id"), - } - if side == "buy": - long_open.append({**base, "position": "long", "action": "open"}) - short_close.append({**base, "position": "short", "action": "close"}) - else: - long_close.append({**base, "position": "long", "action": "close"}) - short_open.append({**base, "position": "short", "action": "open"}) - - return { - "long_open": long_open, - "long_close": long_close, - "short_open": short_open, - "short_close": short_close, - } - - -def futures_markers_from_executed_trades( - sim_pnl: dict[str, Any], -) -> dict[str, list[dict[str, Any]]]: - """시뮬 체결 내역에서 4종 선물 마커를 구성한다.""" - markers: dict[str, list[dict[str, Any]]] = { - "long_open": [], - "long_close": [], - "short_open": [], - "short_close": [], - } - - for trade in sim_pnl.get("trades") or []: - if trade.get("skipped"): - continue - position = trade["position"] - action = trade["action"] - key = f"{position}_{action}" - if key not in ("long_open", "long_close", "short_open", "short_close"): - continue - markers[key].append( - { - "time": int(pd.Timestamp(trade["datetime"]).timestamp()), - "price": trade["price"], - "marker_id": trade.get("marker_id") or trade.get("trade_id"), - "signal_type": trade.get("signal_type", ""), - "position": position, - "action": action, - } - ) - - return markers diff --git a/src/deepcoin/ground_truth/futures_chart.py b/src/deepcoin/ground_truth/futures_chart.py deleted file mode 100644 index 1dcc961..0000000 --- a/src/deepcoin/ground_truth/futures_chart.py +++ /dev/null @@ -1,763 +0,0 @@ -"""선물 Ground Truth 차트 HTML 생성 (롱·숏 4색 마커).""" - -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any - -from deepcoin.data.candle_loader import load_candles -from deepcoin.ground_truth.chart import ( - DEFAULT_MAX_CANDLES, - _data_js_path, - _enrich_markers_chart_price, - _sim_start_marker, - _to_unix_seconds, -) -from deepcoin.ground_truth.futures import futures_markers_from_gt_signals - - -def _stack_marker_positions( - long_open: list[dict[str, Any]], - long_close: list[dict[str, Any]], - short_open: list[dict[str, Any]], - short_close: list[dict[str, Any]], -) -> None: - """동일 시각에 겹치는 마커에 세로 스택 인덱스를 부여한다. - - 같은 봉의 L↓·S↓ 등이 좌우로 벌어지지 않고 동일 X에서 위아래로 쌓이도록 한다. - """ - from collections import defaultdict - - groups: dict[int, list[dict[str, Any]]] = defaultdict(list) - for markers in (long_open, long_close, short_open, short_close): - for marker in markers: - groups[int(marker["time"])].append(marker) - - kind_rank = { - "long_close": 0, - "short_open": 1, - "long_open": 2, - "short_close": 3, - } - - for markers_at_time in groups.values(): - if len(markers_at_time) <= 1: - for marker in markers_at_time: - marker["stack_index"] = 0 - continue - markers_at_time.sort( - key=lambda m: kind_rank.get(f"{m.get('position')}_{m.get('action')}", 9) - ) - for index, marker in enumerate(markers_at_time): - marker["stack_index"] = index - marker.pop("x_offset_px", None) - -_FUTURES_HTML_TEMPLATE = """ - - - - Futures Ground Truth Chart - - - - - - - -
-

Futures Ground Truth Chart

-
-
-
-__EXTRA_BODY__ -
-
- - - - 타점 - / - -
-
- - - - - -
-
- - - - -
-
- - -
- 데이터 로딩 중… -
-
-
-

상세 캔들

-
-
- - -""" - - -def _build_futures_chart_payload( - df, - gt_result: dict[str, Any], - chart_days: int, - gt_lookback_days: int, -) -> dict[str, Any]: - """선물 GT 차트용 JSON payload를 구성한다.""" - markers = futures_markers_from_gt_signals(gt_result) - times = _to_unix_seconds(df["datetime"]) - closes = df["close"].astype(float).tolist() - chart_meta = { - **gt_result["meta"], - "market_type": "futures", - "chart_lookback_days": chart_days, - "gt_lookback_days": gt_lookback_days, - "chart_data_from": str(df["datetime"].min()), - "chart_data_to": str(df["datetime"].max()), - } - payload: dict[str, Any] = { - "times": times, - "open": df["open"].astype(float).tolist(), - "high": df["high"].astype(float).tolist(), - "low": df["low"].astype(float).tolist(), - "close": closes, - "long_open_markers": _enrich_markers_chart_price(markers["long_open"], times, closes), - "long_close_markers": _enrich_markers_chart_price(markers["long_close"], times, closes), - "short_open_markers": _enrich_markers_chart_price(markers["short_open"], times, closes), - "short_close_markers": _enrich_markers_chart_price(markers["short_close"], times, closes), - "meta": chart_meta, - "bar_count": len(df), - } - _stack_marker_positions( - payload["long_open_markers"], - payload["long_close_markers"], - payload["short_open_markers"], - payload["short_close_markers"], - ) - return payload - - -def render_futures_ground_truth_chart( - db_path: Path, - symbol: str, - gt_result: dict[str, Any], - output_path: Path, - chart_lookback_days: int | None = None, - max_candles: int = DEFAULT_MAX_CANDLES, -) -> Path: - """현물 GT 타점을 선물 롱·숏 4색 마커로 표시한 HTML 차트를 생성한다. - - Args: - db_path: SQLite 경로. - symbol: 코인 심볼. - gt_result: build_ground_truth 결과 또는 spot GT JSON. - output_path: HTML 출력 경로. - chart_lookback_days: 차트 표시 일수. None이면 GT lookback과 동일. - max_candles: 0이면 전체, 양수면 최근 N봉만. - - Returns: - HTML 저장 경로. - """ - interval_min = gt_result["meta"]["interval_min"] - gt_lookback_days = gt_result["meta"]["lookback_days"] - chart_days = chart_lookback_days if chart_lookback_days is not None else gt_lookback_days - - df = load_candles(db_path, symbol, interval_min, lookback_days=chart_days) - if max_candles > 0 and len(df) > max_candles: - df = df.iloc[-max_candles:].reset_index(drop=True) - - payload = _build_futures_chart_payload(df, gt_result, chart_days, gt_lookback_days) - - output_path.parent.mkdir(parents=True, exist_ok=True) - data_path = _data_js_path(output_path) - with data_path.open("w", encoding="utf-8") as fp: - fp.write("window.CHART_DATA=") - json.dump(payload, fp, ensure_ascii=False, separators=(",", ":")) - fp.write(";") - - data_js_name = data_path.name - html = _futures_html_template(data_js_name) - output_path.write_text(html, encoding="utf-8") - return output_path - - -def _futures_html_template(data_js_name: str) -> str: - """선물 GT 차트 HTML 템플릿을 생성한다.""" - return ( - _FUTURES_HTML_TEMPLATE.replace("__DATA_JS_NAME__", data_js_name) - .replace("__EXTRA_STYLES__", "") - .replace("__EXTRA_BODY__", "") - .replace("__EXTRA_SCRIPT__", "") - ) diff --git a/src/deepcoin/ground_truth/order_sizing.py b/src/deepcoin/ground_truth/order_sizing.py deleted file mode 100644 index 0e07968..0000000 --- a/src/deepcoin/ground_truth/order_sizing.py +++ /dev/null @@ -1,59 +0,0 @@ -"""총평가금액 구간별 매수(현금) 상한.""" - -from __future__ import annotations - -from typing import Any - -# 총평가금액(원) 구간 — 높은 구간이 우선 적용 -EQUITY_TIER_100M = 100_000_000 -EQUITY_TIER_1B = 1_000_000_000 -EQUITY_TIER_10B = 10_000_000_000 - -BUY_SIZING_RULE_LABEL = "총평가 1억↑ 현금 10% · 10억↑ 5% · 100억↑ 1%" - - -def buy_cash_pct(equity_krw: float) -> float | None: - """총평가금액에 따른 1회 매수 허용 현금 비율. - - Args: - equity_krw: 현재 총평가금액(원). - - Returns: - 허용 비율(0~1). 1억 미만이면 None(비율 상한 없음). - """ - if equity_krw >= EQUITY_TIER_10B: - return 0.01 - if equity_krw >= EQUITY_TIER_1B: - return 0.05 - if equity_krw >= EQUITY_TIER_100M: - return 0.10 - return None - - -def max_buy_from_cash(equity_krw: float, cash_krw: float) -> float: - """구간별 규칙을 반영한 1회 매수 최대 금액. - - Args: - equity_krw: 현재 총평가금액(원). - cash_krw: 보유 현금(원). - - Returns: - 매수에 사용 가능한 최대 원화. - """ - cash = max(float(cash_krw), 0.0) - pct = buy_cash_pct(equity_krw) - if pct is None: - return cash - return cash * pct - - -def buy_sizing_metadata() -> dict[str, Any]: - """시뮬 결과·차트에 포함할 매수 상한 메타.""" - return { - "buy_sizing_rule": BUY_SIZING_RULE_LABEL, - "buy_sizing_tiers": [ - {"min_equity_krw": EQUITY_TIER_100M, "max_cash_pct": 0.10}, - {"min_equity_krw": EQUITY_TIER_1B, "max_cash_pct": 0.05}, - {"min_equity_krw": EQUITY_TIER_10B, "max_cash_pct": 0.01}, - ], - } diff --git a/src/deepcoin/mtf/__init__.py b/src/deepcoin/mtf/__init__.py deleted file mode 100644 index a02d2fa..0000000 --- a/src/deepcoin/mtf/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -"""멀티 타임프레임(MTF) 인과 피처 추출.""" - -from deepcoin.mtf.alignment import as_of_from_signal_bar, last_complete_bar_index -from deepcoin.mtf.extractor import MtfFeatureExtractor, MtfSnapshot -from deepcoin.mtf.filter import MtfSignalFilter, score_mtf_rules -from deepcoin.mtf.rules import MtfRule, MtfRuleSet, derive_rules_from_report, load_mtf_rules, save_mtf_rules -from deepcoin.mtf.store import MTF_INTERVALS, MultiTimeframeStore - -__all__ = [ - "MTF_INTERVALS", - "MultiTimeframeStore", - "MtfFeatureExtractor", - "MtfSnapshot", - "MtfSignalFilter", - "MtfRule", - "MtfRuleSet", - "derive_rules_from_report", - "load_mtf_rules", - "save_mtf_rules", - "score_mtf_rules", - "as_of_from_signal_bar", - "last_complete_bar_index", -] diff --git a/src/deepcoin/operations/__init__.py b/src/deepcoin/operations/__init__.py deleted file mode 100644 index 72590a3..0000000 --- a/src/deepcoin/operations/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""현물 3단계 운영 — composite_v3 + MTF.""" - -from deepcoin.operations.backtest import run_filtered_backtest, save_backtest_report -from deepcoin.operations.runner import OperationsRunner -from deepcoin.operations.signal_pipeline import run_signal_pipeline - -__all__ = [ - "OperationsRunner", - "run_filtered_backtest", - "run_signal_pipeline", - "save_backtest_report", -]