# 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 ``` ## 주요 환경변수 이름 | 기본 | 설명 -- | -- | -- `STRATEGY_SCALP_ENABLED` | `true` | 스캘핑 전략 활성화 `STRATEGY_SHORT_ENABLED` | `true` | 꼬리잡기 전략 활성화 `STRATEGY_SAME_CODE_POLICY` | `block` | 같은 종목을 다른 전략이 이미 보유 중일 때 `block`(차단) / `allow`(허용) `REAL_BALANCE_VERIFY_BEFORE_BUY` | `true` | 매수 전 실 잔고 재조회 `REAL_BALANCE_VERIFY_BEFORE_SELL` | `true` | 매도 전 실 잔고 재조회 (0주면 active_trades 정리) `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 ``` 엔진(`scalping_engine.py`, `tail_engine.py`) 자체를 공유하므로 백테스트와 실매매 신호는 100% 동일 로직이다. ## 주의사항 - 프로젝트 루트의 기존 파일 (`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 경합).