룰 19 사용자 요청 4건 (2026-09-06): [UI 1] 옵투나 실시간 로그 모달 - backtest.html: 「크게 보기」 버튼 + bootstrap 모달 (max-xl · scrollable) - backtest.js: optunaOpenLogModal/RefreshLogModal/FilterLogModal - backend: /api/optuna/log/<job_id>?lines=N 신설 (50~5000줄 tail) - 3초 auto-refresh · 스크롤 하단 유지 · 실시간 검색 필터 - 원인: 기존 pre#opt_log_tail 은 25줄만 tail → pruned 잔뜩이면 완료 로그 밀림 [UI 2] 사후합격 Top5 카드 파라미터 확장 + 한글 캡션 - optuna_web_jobs.py _ob_whip_ui_from_params: 11개 새 필드 flat 추출 - 핵심진입: entry_drop_rate/vol_mult/high_chase - 핵심청산: exit_min_hold_sec/cooldown_min/max_loss_krw - RSI: rsi_period/oversold/overbought - 어깨: shoulder_min_high/cut_pct - backtest.js optunaCalcStats/optunaFormatAvgHtml: - keys 배열 확장 (기존 10 → 21) - 모든 영문 옆 한글 캡션 (spr→(스프레드) 등) - 그룹별 조건부 렌더 (해당 파라미터 있을 때만) [백엔드 3] 과적합 계산기 Y안 (optuna_common.py) - 팩터 1 표본 장일: max 40 → 25 (1일 백테도 견고성으로 상쇄 가능) - 팩터 5 PnL 고원 재해석: 동일 PnL & 파라미터 다양성 계산 - 다양 (핵심 파라미터 unique_ratio >= 50%) → 감점 -8 (견고) - 좁음 → 유지 +12 (TPE 몰빵) - 팩터 6 신설 param_stability: gated pool 파라미터 mode_share - 5개+ ≥60% → -15 (매우 견고) - 3~4개 ≥60% → -8 (다소 견고) [호환 4] 옛 잡 자동 재파싱 fallback (backtest_web.py) - _inject_extended_params_fallback(): - result_summary.top5_gated 각 row 에 확장 파라미터 재파싱 - overfit_diagnostics Y안 재계산 - top5_gated/top5_stable/compare_rows 각 row 의 overfit_risk_pct 재계산 - /api/optuna/status, /api/optuna/topn 양쪽 적용 - 옛 result JSON 을 재파싱하여 trial_number 매칭 검증: - 실측 재계산: 옛 잡 (1일·5거래·100%WR) 40% → 25%·상대적으로낮음 - 브라우저 CDP: 배지 27% · 팩터 6 param_stability 노출 - 로그 모달: 174줄·19.8KB·검색·auto-scroll 확인 - 사후합격 카드: 핵심진입/청산/RSI/어깨 그룹 + 한글 캡션 렌더 - 실매 코어 스모크 통과 (test_live_execution_validation 최종:통과 · 완결) 전략 범위: 4전략 공통 (스캘핑/돌파/모멘텀/꼬리) — 공통 optuna_common - 파라미터 flat 추출은 스캘핑 위주지만 다른 전략에서 해당 파라미터 있으면 자동 표시 - 과적합 Y안은 전략 무관 (전 전략 동일 로직) Co-authored-by: Cursor <cursoragent@cursor.com>
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 → 기본값 순으로 조회 |
실행
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 |
매도 전 실잔고. 보유(hldg_qty)와 매도가능(ord_psbl_qty) 분리. 매도가능 0만으로 유령정리 안 함 |
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 를 읽어서 해당 전략 클래스를
쓰레드로 등록한다. 따라서:
- 꼬리잡기만 끄고 싶을 때 →
env_config또는 OS env 에STRATEGY_SHORT_ENABLED=false - 재시작 필요 (동작 중 on/off 전환은 지원 안 함, 대신 공격적 재시작 스크립트 사용)
백테스트
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 경합).