Files
tradeing/SOURCE_ANALYSIS.md
T
chpark c4e6aab7b2 React + FastAPI 풀 마이그레이션 — Streamlit 제거
- backend/ — FastAPI + JWT + 모든 REST 엔드포인트
- frontend/ — Next.js 14 + Tailwind + 7페이지 (대시보드/트레이드/거래소/자동매매/설정/내정보/로그인)
- core_logic.py — 신호계산/알림 로직 분리 (기존 app_streamlit.py 에서 추출)
- users_db.py + bcrypt 인증, exchange_keys.py + Fernet 암호화
- trades_db.py — 진입/청산 lifecycle 추적, signal_events raw 로그
- settings_db.py — 모든 운영 파라미터 DB 영속 저장 (RSI/거래량/펀딩비 임계값 포함)
- docker-compose: frontend / backend / postgres + Traefik 라우팅
- assets/logo.svg — JUNGGOMOA 그라디언트 로고

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:27:11 +09:00

231 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BTC/ETH Futures Dashboard — 소스 분석
Streamlit 기반 Binance Futures 실시간 모니터링 대시보드. 차트 시각화 + 다중 시간축 자동 알림 (Telegram) + 일일 신호 통계 리포트를 한 프로세스에서 처리한다.
---
## 1. 파일 구성
| 파일 | 역할 |
|---|---|
| [app_streamlit.py](app_streamlit.py) | 메인 앱 — 데이터 수집 / 지표 계산 / 신호 생성 / 차트 빌드 / 알림 스레드 / 사이드바 메뉴 (대시보드 / 트레이드 이력 / 설정) |
| [alert_state.py](alert_state.py) | 멀티 rerun 환경에서 살아남는 mutable 상태 (`sys.modules` 캐싱 활용) |
| [settings_db.py](settings_db.py) | SQLite key-value 설정 영속 저장 (텔레그램 토큰, 심볼, 시간축, 쿨다운, 손절가 비율, 알림 ON/OFF) |
| [trades_db.py](trades_db.py) | PostgreSQL 트레이드 lifecycle — `trades` (진입→청산), `signal_events` (raw 신호 로그). DATABASE_URL 미설정 시 silent no-op |
| [Dockerfile](Dockerfile) / [docker-compose.yml](docker-compose.yml) | python:3.11-slim 컨테이너 + Postgres 16 + Traefik labels (junggomoa.com) |
| [DEPLOY.md](DEPLOY.md) | 서버 배포 절차 |
| [requirements.txt](requirements.txt) | streamlit, pandas, numpy, plotly, ta, requests, python-dotenv, urllib3, psycopg2-binary |
| [assets/override.css](assets/override.css) | 라이트모드 강제 CSS |
| [.env.example](.env.example) | 환경변수 템플릿 (`TELEGRAM_TOKEN`, `TELEGRAM_CHAT_ID`) |
---
## 2. 아키텍처 전체 흐름
```
┌─────────────── Streamlit 메인 프로세스 ───────────────┐
│ │
│ ┌── UI Thread (main()) ──┐ ┌── Background Threads ─┐
│ │ 매 rerun 마다 실행: │ │ daemon thread × 2 │
│ │ - build_chart() │ │ │
│ │ - st.plotly_chart │ │ _alert_loop │
│ │ - time.sleep + rerun │ │ (30초 주기) │
│ └────────────────────────┘ │ │
│ │ │ _daily_report_loop │
│ ▼ │ (60초 주기, KST 자정)│
│ ┌─────────────┐ └───────┬───────────────┘
│ │ alert_state │ ◀───────────────────┘
│ │ (sys.modules│
│ │ 캐싱 보존) │
│ └─────────────┘
│ │
│ 외부 호출: Binance Futures API + Telegram Bot API │
└─────────────────────────────────────────────────────────┘
```
**핵심 설계 결정**: Streamlit 의 매 rerun 은 메인 스크립트를 새 namespace 에서 재실행해 모듈 최상단 globals 가 모두 초기화된다. mutable 상태 ( dedup, 진입 추적, 스레드 가드 ) 는 [alert_state.py](alert_state.py) 라는 별도 모듈에 두어 `sys.modules` 캐싱으로 process lifetime 동안 보존되도록 분리.
---
## 3. 모듈 상세
### 3.1 `alert_state.py` — 보존 상태
| 변수 | 타입 | 용도 |
|---|---|---|
| `last_alert` | `dict[(interval, key), float]` | 알림 cooldown (default 600s) |
| `last_fired_candle` | `dict[(interval, key), Timestamp]` | per-candle dedup |
| `long_entry` / `short_entry` | `dict[interval, record]` | 진입 추적 (손절/청산 권고용) |
| `pending_groups` | `list[dict]` | forming candle 발사 후 신호 재검증 큐 |
| `synced_intervals` | `set[str]` | 재시작 직후 역사적 신호 burst 차단용 sync 플래그 |
| `signal_seen_count` | `dict[(interval, sig), {candle_time, count}]` | forming candle 의 연속 True polling 카운트 (false alert 차단) |
| `alert_lock` | `threading.Lock` | UI ↔ alert thread 공유 변수 보호 |
| `alert_started` / `daily_report_started` | `bool` | 스레드 중복 기동 가드 |
### 3.2 `app_streamlit.py` 섹션 분해
#### (1) 환경 설정 & 페이지 셋업 — [L1L70](app_streamlit.py#L1-L70)
- `st.set_page_config` (반드시 import 직후)
- 라이트모드 강제 CSS inline
- 텔레그램 토큰 / 채팅 ID 는 `os.getenv` 로 로드
- 핵심 상수:
- `STOP_LOSS_PCT = 0.0075` — 10x 레버리지 기준 ROI -7.5%
- `LONG_SIGNALS` / `SHORT_SIGNALS` — 신호 분류 set
- `TF_LABEL_MAP` — 11개 시간축 한글 라벨
#### (2) 텔레그램 송신 + 알림 코어 — [L74L263](app_streamlit.py#L74-L263)
`SIG_DEFS` (9종 신호):
- `strong_long_signal` / `strong_short_signal` — 다중 필터 통과
- `long_signal` / `short_signal` — 일반 진입
- `vol_long_signal` / `vol_short_signal` — 볼륨 급등
- `reversal_long_signal` / `reversal_short_signal` — 추세 꺾임 감지
- `short_caution_signal` — 극단 펀딩비 + OI 하락 (숏 주의)
`check_and_alert(df, symbol, interval)` — 알림 코어 로직:
1. **Phase 0 — silent sync** (재시작 후 첫 polling): `synced_intervals` 미포함 시 모든 `last_fired_candle` 만 채우고 알림 skip → 역사적 신호 burst 방지
2. **Phase 1 — pending 검증**: forming candle 에서 발사된 알림은 매 polling 마다 신호 잔존 여부 확인. 사라지면 즉시 `[취소 알림]` 발사 (캔들 마감까지 기다리지 않음)
3. **Phase 2 — 신호 검사**:
- `tail(3)` 만 검사
- cooldown (10분) + per-candle dedup
- **forming candle 안정성**: 연속 2 polls True 만 발사 (`signal_seen_count`) — 깜빡임 false alert 차단
- 닫힌 캔들은 즉시 발사 (data 확정)
4. **Phase 3 — group 발사**: long / short / caution 그룹별로 한번에 묶어서 송신. 진입가 / 손절가 표시
5. **Phase 4 — 청산 권고**: 30m / 1h 의 새 진입 신호만 반대 방향 진입에 대한 `[반대 신호 감지 - 청산 권장]` 트리거 (5m / 15m 은 노이즈 多 → 폭주 방지)
6. **Phase 5 — 손절가 알림**: 현재가가 추적 중 진입의 stop 을 침범하면 `[손절가알림]` 송신
#### (3) Binance 데이터 수집 — [L267L311](app_streamlit.py#L267-L311)
| 함수 | 엔드포인트 | 반환 |
|---|---|---|
| `get_klines` | `/fapi/v1/klines` | OHLCV + taker buy/sell volume |
| `get_funding_rate` | `/fapi/v1/fundingRate` | FR (%, 100배 환산) |
| `get_open_interest_history` | `/futures/data/openInterestHist` | OI |
| `get_long_short_ratio` | `/futures/data/topLongShortPositionRatio` | 탑트레이더 L/S ratio |
| `get_taker_buy_sell_ratio` | `/futures/data/takerlongshortRatio` | (코드상 정의되나 차트엔 미사용) |
모든 시각은 KST (`+9h`).
#### (4) 지표 + 신호 계산 — [L315L450](app_streamlit.py#L315-L450)
`compute_indicators(df, interval)` — 표준 TA:
- MA 7 / 25 / 99 / 200
- BB (20, 2σ) — mid / upper / lower
- RSI(14), StochRSI(14, 3, 3)
- MACD(12, 26, 9) — line / signal / histogram
- ATR(14)
`compute_signals(df, interval)` — 신호 정의:
| 신호 | 조건 |
|---|---|
| `long_signal` | `bull_ma_2` (close > MA7 & MA25) & RSI<75 & MACD_hist↑ & close > BB_mid & body% ≥ +0.2% |
| `short_signal` | `bear_ma_2` & RSI>25 & MACD_hist↓ & close < BB_mid & body% ≤ -0.2% |
| `strong_long_signal` | `bull_ma_2` & RSI<65 & MACD_hist↑ & oi_up_2 & taker_buy_2 & fr_long_favor & 양봉 |
| `strong_short_signal` | `bear_ma_2` & RSI>35 & MACD_hist↓ & oi_down_2 & taker_sell_2 & fr_short_favor & 음봉 |
| `vol_long_signal` | buy_net > avg×2 & taker_buy_vol > min × oi_active (interval 별 min 가변) |
| `vol_short_signal` | sell_net 동일 미러 |
| `short_caution_signal` | `oi_down_2` & FR ≤ -0.007% (극단 음수) |
| `reversal_long/short_signal` | 직전 3봉 추세 반대 + |body|≥0.3% + 거래량 1.3× |
| `exhaustion_long/short` | 직전봉 거래량 spike (vol > avg×3) + 매도/매수 우세 |
쿨다운: 신호별로 `rolling(N, sum).shift(1)==0` 패턴으로 N봉 내 중복 차단.
> **MA 정렬 요구 완화**: 추세 반전 직후엔 MA7>MA25 정렬이 늦게 형성되어 `bull_ma` (3중 정렬) 대신 `bull_ma_2` (2중 정렬) 만 요구. (커밋 [d49ac84](app_streamlit.py))
#### (5) 차트 빌드 — [L455L741](app_streamlit.py#L455-L741)
`build_chart(symbol, interval, candle_limit)`:
- 7-row Plotly subplot:
1. **메인** — Candlestick + BB + MA7/25/99/200 + 모든 신호 마커 + Taker buy/sell 점 + L/S / FR / OI 오버레이
2. Taker Buy/Sell Volume (Net Bar)
3. Open Interest
4. Funding Rate Bar (±0.5% / -0.7% 가이드 라인)
5. Long/Short Ratio (탑트레이더)
6. RSI / StochRSI (20/50/80 가이드)
7. MACD (Line + Signal + Histogram)
데이터 머지: OI / FR / L/S 는 시간 정렬 후 `floor()` + `merge` + `ffill`. FR 은 1h 기준, 나머지는 interval (혹은 5m fallback) 기준.
**1시간 추세 컨텍스트**: 비-1h 시간축에서 별도로 1h 캔들 가져와 `h1_bull/bear` 컬럼을 생성, 같은 일자 안에서 ffill 로 채움 (현재 코드에선 차트 표시 X — 보존만).
신호 마커:
- 롱 → `low × 0.9998` 위치, 위 화살표
- 숏 → `high × 1.0002` 위치, 아래 화살표
- caution → diamond, exhaustion → star
축 자동 스케일: `tight()` + 분위수 기반.
#### (6) 알림 백그라운드 스레드 — [L774L786](app_streamlit.py#L774-L786)
```python
ALERT_TIMEFRAMES = ["5m", "15m", "30m", "1h"]
def _alert_loop():
while True:
for interval in ALERT_TIMEFRAMES:
df = _build_signal_df(symbol, interval, 200)
check_and_alert(df, symbol, interval)
time.sleep(30)
```
UI 에서 선택한 symbol 만 추적 (`alert_state.alert_symbol`, lock 으로 보호). 4개 시간축 모두 동시 모니터링.
#### (7) 일일 리포트 — [L791L1005](app_streamlit.py#L791-L1005)
KST 자정 통과 감지 시 4종 텔레그램 메시지 발송:
1. **24h 신호 통계 (1배 시간)** — 다음 봉에 반대 신호 떴는지 `T/F` 카운트 + 승률
2. **24h 신호 통계 (2배 시간)** — 2번째 봉 검증
3. **손절가 터치 횟수** — 진입 시 `±STOP_LOSS_PCT` 가격이 이후 3봉 안에 터치됐는지
4. **추세 꺾임 감지 통계** — 3봉 후 close 가 의도한 방향으로 갔는지
리포트 시간축: `["5m", "15m", "30m", "1h", "4h"]`. 각 시간축 별 `DAILY_REPORT_KLINES_LIMIT` 만큼 가져와 24h 윈도우 분석.
#### (8) 메인 UI — [L1010L1085](app_streamlit.py#L1010-L1085)
- 5-column 헤더: 심볼 / 시간축 / 갱신주기(초) / 새로고침·자동갱신·범례·모바일 토글
- **Candle 수**: 데스크톱 53, 모바일 14 (`mobile_mode` 토글)
- 펀딩비 경고 배너 (≤-0.7% 위험 / ≤-0.5% 경보 / ≥+0.5% 롱 과열)
- Plotly 차트: scrollZoom, doubleClick reset
- `auto` 활성화 시 `time.sleep(refresh_sec)` + `st.rerun()`
---
## 4. 동시성 / 안전성 설계
| 위험 요소 | 대응 |
|---|---|
| Streamlit rerun 마다 globals 초기화 | `alert_state` 모듈 분리 (`sys.modules` 캐싱) |
| 스레드 중복 기동 | `alert_started` / `daily_report_started` bool 가드 |
| UI ↔ alert thread 변수 충돌 | `alert_state.alert_lock` |
| 재시작 직후 역사적 신호 burst | `synced_intervals` silent sync (첫 polling 은 dedup 만 채우고 alert 미발사) |
| forming candle 깜빡임 false alert | `signal_seen_count` 연속 2 polls True 요구 |
| forming candle 발사 후 신호 사라짐 | `pending_groups` Phase 1 검증 → `[취소 알림]` 즉시 발사 |
| 청산 권고 폭주 (변동성 큰 날) | 30m / 1h 만 트리거 |
| Binance API 일시 오류 | `try/except` 로 모든 보조 API 감싸고 `pass` (메인 klines 만 필수) |
---
## 5. 환경 / 실행
```bash
pip install -r requirements.txt
cp .env.example .env # TELEGRAM_TOKEN, TELEGRAM_CHAT_ID 채우기
streamlit run app_streamlit.py
```
기본 포트 `8501`. 환경변수 미설정 시 알림은 silent fail (콘솔에만 에러 출력).
---
## 6. 개선 가능 지점 (관찰만)
- **신호 컬럼 마커 분기 로직 (L604–L641)** — `pd.Series([False]*len(df))` fallback 패턴 반복. helper 추출 가능.
- **OI / FR / L/S 머지 로직 (L479L510, L750L770)** — 4번 비슷하게 반복. `merge_external_metric()` 함수로 추출 가능.
- **`get_taker_buy_sell_ratio`** — 정의돼 있으나 호출처 없음 (dead code).
- **`exhaustion_long/short`** — `compute_signals` 에서 계산되나 SIG_DEFS / 알림 대상엔 미포함 (차트 마커로만 표시).
- **`h1_bull/bear` 컨텍스트** — 계산되나 차트/신호에서 사용 X.
- **`requests.get(verify=False)` + `urllib3.disable_warnings`** — TLS 검증 비활성. 운영 환경에선 검토 필요.
- **`recent = df.tail(3)`** — 30초 폴링 + N분 캔들이라 충분하지만, 순간적 네트워크 지연 시 초과 시간축에서는 신호 누락 가능.