Files
kis_bot/kis_trader/README.md
Hwang 61c72a8a4c feat(tests): 신규 키움 웹소켓 조건검색 및 실시간 조건검색 테스트 추가
변경 사항
----
- _test_kiwoom_condition_list.py: 키움 웹소켓 조건검색 '목록조회' 기능을 단독으로 테스트하는 스크립트 추가
- _test_kiwoom_condition_realtime.py: 'momentum' 조건식을 실시간으로 등록하고 초기 매칭 종목 리스트 및 실시간 편입/이탈을 수신하는 테스트 스크립트 추가
- _verify_columnar_bitid.py, _verify_shared_e2e_breakout.py, _verify_shared_e2e.py: 공유 메모리 및 dict 간의 데이터 일관성을 검증하는 테스트 추가

영향
----
- 신규 테스트 스크립트 추가로 키움 웹소켓 API의 기능 검증 및 안정성을 높임
- 기존 기능에 대한 영향 없음

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 01:27:00 +09:00

148 lines
7.6 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.
# kis_trader — 통합 트레이딩 봇
스캘핑(`SCALP`) + 꼬리잡기(`SHORT`, Tail Catch) 두 전략을 **단일 프로세스**에서
**각자 독립 쓰레드**로 돌리는 통합 봇. KIS REST/WebSocket 호출을 단일 허브로
묶고, 주문은 단일 `OrderManager` 를 경유해 ODNO 기반으로 추적한다.
## 디렉터리
```
kis_trader/
├── main.py # 오케스트레이터 (각 전략을 쓰레드로 기동)
├── utils/
│ ├── env.py # env_config (DB) + os.environ 통합 조회
│ ├── logger.py # 공용 로거 / 안전 JSON 저장 / 알림(MM/TG)
│ └── request_handler.py # SafeRequest: rate-limit + 재시도
├── network/
│ └── ws_manager.py # 단일 WS 허브 (kis_ws 재사용, 구독 ref-counting)
├── strategies/
│ ├── base.py # BaseStrategy (threading.Thread)
│ ├── scalping.py # scalping_engine 래퍼
│ └── tail_catch.py # tail_engine 래퍼
├── execution/
│ ├── kis_client.py # 통합 KIS REST 클라이언트 (ODNO 반환)
│ └── order_manager.py # Master Executor (종목 Lock + 실잔고검증)
├── database/
│ └── db_manager.py # TradeDBExt: 기존 TradeDB + orders 테이블
└── backtest/
└── backtest_web.py # 기존 backtest_web.py 재호출 래퍼
```
## 아키텍처 한 눈에
```
[SCALP 쓰레드] ──┐
│─▶ OrderRequest ─▶ OrderManager ─▶ KISClient ─▶ KIS API
[SHORT 쓰레드] ──┘ │ │ │
│ │ └── DB(orders) ← ODNO PK
│ └───── DB(active_trades, trade_history)
└──────── 실잔고 검증 (get_broker_holdings)
공유 인프라 (1개 인스턴스)
• KISClient : 토큰/Throttle/재시도
• WSManager : 단일 WS 세션, 구독 ref counting
• TradeDBExt : orders 테이블 + 기존 TradeDB
• OrderManager : 종목 Lock, 동일 종목 다전략 정책, ODNO 저장
```
## 왜 이 구조인가
기존 문제 | 새 구조에서의 해결
-- | --
봇이 잡고 있는 `holdings` 가 실제 계좌와 어긋남 | 매도/매수 직전 `OrderManager.get_broker_holdings(force=True)` 호출로 실잔고 검증
두 봇이 같은 종목에 동시 매수 → 이중 포지션 | `OrderManager._code_locks[code]` 로 직렬화 + `orders` 테이블 `UNIQUE(strategy,code,side,date)`
주문번호(ODNO) 미저장 → 사후 추적 불가 | `kis_client._order()` 가 ODNO 반환 → `TradeDBExt.insert_order()` 로 PK 저장
토큰 재발급 경합 / REST 호출 폭주 | `KISClient` 하나 + `kis_token_manager` 공유 + `SafeRequest` 쿨다운
두 봇이 WS 각자 → 구독 수/approval_key 경합 | `WSManager` 단일 세션 + 구독 레퍼런스 카운팅
설정 하드코딩 | 모든 값은 `env_config``os.environ` → 기본값 순으로 조회
## 실행
```bash
cd ~/kis_bot
# 기본(두 전략 모두 ON)
python -m kis_trader.main
# 스캘핑만
STRATEGY_SCALP_ENABLED=true STRATEGY_SHORT_ENABLED=false python -m kis_trader.main
# 꼬리잡기만
STRATEGY_SCALP_ENABLED=false STRATEGY_SHORT_ENABLED=true python -m kis_trader.main
```
## DB 설정 테이블 (2026-05 분리)
| 테이블 | 내용 |
|--------|------|
| `env_config` | 공통 — KIS/키움 API, MM, WS, 전략 ON/OFF, 수수료, `SLOT_MONEY_DEFAULT` 등 |
| `config_scalp` | 스캘핑 `SCALP_*` |
| `config_short` | 꼬리 `SHORT_*` / `TAIL_*` / `SHOULDER_*` / 익절 호가 `SELL_ORDERBOOK_*` |
| `config_momentum` | 모멘텀 `MOMENTUM_*` |
| `config_breakout` | 돌파 `BREAKOUT_*` |
| `config_updow` | 하락매수 `UPDOW_*` |
- 코드는 `get_latest_env()` / `get_env_from_db()`**병합 flat dict** 를 그대로 씁니다 (하위 호환).
- 최초 분리·재마이그레이션: `python3 -m kis_trader.scripts.migrate_split_env_config`
- 키 분류 규칙: 프로젝트 루트 `config_schema.py`
## 주요 환경변수
이름 | 기본 | 설명
-- | -- | --
`STRATEGY_SCALP_ENABLED` | `true` | 스캘핑 전략 활성화
`STRATEGY_SHORT_ENABLED` | `true` | 꼬리잡기 전략 활성화
`STRATEGY_SAME_CODE_POLICY` | `allow` | 같은 종목을 다른 전략이 `active_trades`에 있어도 `allow`(허용) / `block`(차단)
`REAL_BALANCE_VERIFY_BEFORE_BUY` | `false` | `false`=타 전략·수동 보유 있어도 매수 가능 / `true`+`MODE=strategy`면 동일 전략 DB만 차단
`REAL_BALANCE_VERIFY_BEFORE_BUY_MODE` | `strategy` | `strategy` \| `global`(실계좌 1주라도 있으면 매수 차단, 레거시)
`REAL_BALANCE_VERIFY_BEFORE_SELL` | `true` | 매도 전 실 잔고 재조회 (0주면 active_trades 정리)
`ORDER_CASH_PCT` | `0.95` | 예수금 부족 시에만 가용금×N%÷MAX_STOCKS 로 qty 축소 (기존 산식 우선)
`ORDER_CASH_FEE_BUFFER` | `1.01` | 주문금액 대비 수수료 여유 (1.01=1%)
`ORDER_CASH_DIVIDE_BY_MAX_STOCKS` | `1` | 1=종목당 예수금÷MAX_STOCKS, 0=나누지 않음
`ACCOUNT_CASH_PERSIST_SEC` | `60` | 예수금 캐시 kv_store 저장 주기(체결 시 즉시 저장)
`SCALP_LIVE_BACKTEST_ALIGN` | `true` | 스캘핑: 신호봉(직전 확정) → 진입봉(현재 확정)=백테 다음봉 시가
`SHORT_LIVE_BACKTEST_ALIGN` | `true` | 꼬리(3분): 동일 패턴
`MOMENTUM_LIVE_BACKTEST_ALIGN` | `true` | 모멘텀(1분): 동일 패턴
`BREAKOUT_LIVE_BACKTEST_ALIGN` | `true` | 돌파(1분): 동일 + 진입가 진입봉 시가 우선
`*_LIVE_SIGNAL_LOOKBACK_BARS` | `1` | 직전 N개 신호봉까지 소급 검사
`MAX_STOCKS` | `3` | 전략당 동시 보유 최대 종목 수
`REENTRY_COOLDOWN_SEC` | `300` | 매도 후 같은 종목 재진입 쿨다운(초)
`WS_TIMEFRAMES` | `1,3,15,60` | WS 봉 집계 타임프레임(분)
`PERMANENT_WS_CODES` | `069500,229200` | WS 영구 구독 코드(쉼표)
`SCALP_STOP_LOSS_PCT` / `SCALP_TAKE_PROFIT_PCT` | `-0.015` / `0.015` | 스캘핑 손절/익절 비율
`STOP_LOSS_PCT` / `TAKE_PROFIT_PCT` | `-0.04` / `0.05` | 꼬리잡기 손절/익절 비율
`MAX_LOSS_PER_TRADE_KRW` | `200000` | 1회 거래 최대 손실 허용액(원)
`SLOT_MONEY_DEFAULT` | `3000000` | 1슬롯 기본 투자 금액(원)
## 전략 ON/OFF 동작 방식
`TradingOrchestrator` 는 시작 시 `STRATEGY_*_ENABLED` 를 읽어서 해당 전략 클래스를
쓰레드로 등록한다. 따라서:
1. 꼬리잡기만 끄고 싶을 때 → `env_config` 또는 OS env 에 `STRATEGY_SHORT_ENABLED=false`
2. 재시작 필요 (동작 중 on/off 전환은 지원 안 함, 대신 공격적 재시작 스크립트 사용)
## 백테스트
```bash
python -m kis_trader.backtest.backtest_web
```
엔진별로 백테·실매가 동일 함수를 호출한다 (체결·유니버스는 별도).
| 전략 | 엔진 | 청산 |
|------|------|------|
| SCALP reversal | `scalping_engine` | 어깨 → 익절 → 손절 |
| MOMENTUM | `momentum_engine` | 어깨·트레일 → 손절 → tp_max 상한 |
| SHORT(꼬리) | `tail_engine` | ATR·어깨·EOD |
| BREAKOUT | `breakout.py` | 익절 우선 (돌파 전용) |
## 주의사항
- 프로젝트 루트의 기존 파일 (`scalping_engine.py`, `tail_engine.py`, `kis_ws.py`,
`kis_token_manager.py`, `risk_manager.py`, `database.py` 등) 을 **그대로** 사용한다.
`kis_trader/` 는 이들을 **조립**하는 얇은 래퍼 층이다.
- DB 스키마에 `orders` 테이블이 자동 생성된다 (`TradeDBExt._ensure_orders_table`).
- 기존 `kis_scalping_ver2.py` / `kis_short_ver3.py` 는 남겨두되, 중복 실행 금지
(계좌/토큰/WS 경합).