변경 사항 ---- - _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>
148 lines
7.6 KiB
Markdown
148 lines
7.6 KiB
Markdown
# 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 경합).
|