키움 REST API 퀀트 종목교체 리밸런싱 자동화
— V2 시스템 설계
보유 종목 균등배분에서 전략적 종목교체로 — 두 번째 버전을 만들게 된 이유
V1을 넘어 V2를 만든 이유
V1(1~6편)은 보유 종목을 균등 배분하는 자동 리밸런싱 프로그램이었습니다. 잘 동작했지만, 한 가지 한계가 있었습니다.
V1은 현재 보유 종목을 유지하면서 비중만 맞춥니다. 퀀트 투자자라면 주기적으로 팩터 기반 분석을 통해 종목 자체를 교체해야 합니다. 수동으로 하면 실수가 잦고 감정이 개입됩니다.
| 항목 | V1 균등배분 | V2 퀀트전략 |
|---|---|---|
| 종목 결정 | 현재 보유 종목 유지 | 사용자 지정 목표 종목 B로 교체 |
| 비중 | 전 종목 균등 | 목표 종목 기준 균등 |
| 엔진 | RebalancingEngine | QuantEngine + RebalancingEngine |
| 모드 | 단일 | 듀얼 (quant / rebalance) |
| 종목 관리 | 제외/편입 (portfolio_manager) | 목표 집합 B (target_manager) |
| 예약 실행 | 날짜만 저장 | 날짜 + 모드 함께 저장 |
| 수익률 표시 | 없음 | 종목별 평가손익 색상 표시 |
| 실행 포트 | 8000 | 8001 |
V2 시스템 구조 — FastAPI + QuantEngine + 키움 REST API
종목교체 리밸런싱 핵심 개념 — 집합 연산
퀀트 종목교체 리밸런싱의 핵심은 집합 이론입니다. 현재 보유 종목을 A, 신규 목표 종목을 B라 하면 세 가지로 분류됩니다.
| 구분 | 조건 | 처리 |
|---|---|---|
| A − B (매도 전용) | 기존 보유, 목표에 없음 | 전량 매도 (분할) |
| A ∩ B, 초과 보유 | 중복, 평가금액 > 목표금액 | 초과분만 매도 |
| A ∩ B, 부족 보유 | 중복, 평가금액 < 목표금액 | 부족분 매수 |
| A ∩ B, 균형 | 중복, 차이 1주 미만 | 건너뜀 (hold) |
| B − A (매수 전용) | 신규 목표 종목 | 목표금액 전체 매수 (분할) |
목표 균등금액은 (전체 평가금액 + 예수금) ÷ 목표 종목 수로 계산합니다.
8편에서는 이 로직이 실제 Python 코드로 구현된 QuantEngine.classify()를 살펴봅니다.
8편 — QuantEngine 집합 분류 알고리즘
V2 파일 구조 — V1 인프라 재활용
개발 시간을 줄이기 위해 V1 인프라 코드 8개 파일을 그대로 복사해 사용했습니다. 신규로 작성한 파일은 4개입니다.
quant_rebalancing/
├── main.py # 신규: 진입점, 의존성 조립
├── kiwoom_client.py # V1 복사 (변경 없음)
├── account_manager.py # V1 복사 (변경 없음)
├── order_queue.py # V1 복사 (변경 없음)
├── order_manager.py # V1 복사 (변경 없음)
├── history_manager.py # V1 복사 (변경 없음)
├── telegram_notifier.py # V1 복사 (변경 없음)
├── scheduled_notifier.py # V1 복사 (변경 없음)
├── config.py # V1 복사, 기본 포트 8001
├── quant_engine.py # 신규: 집합 분류 + 계획 생성
├── target_manager.py # 신규: 목표 종목 B 관리
├── quant_schedule_manager.py # 신규: 듀얼 모드 스케줄
├── web_server.py # 신규: FastAPI + quant 라우트
└── frontend/
V1의 OrderQueue, OrderManager 등 검증된 주문 인프라를 그대로 씁니다. 주문 실행 로직을 다시 짤 필요 없이 상위 전략 레이어만 새로 구현했습니다.
듀얼 모드 — 하나의 앱으로 두 전략 전환
V2의 독특한 설계 포인트는 듀얼 모드입니다. 단 하나의 설정값으로 두 전략을 전환합니다.
# settings.json
"mode": "quant" # 퀀트 종목교체 모드
"mode": "rebalance" # 보유 종목 리밸런싱 모드 (V1 로직)
웹 UI의 라디오 버튼으로 모드를 실시간 전환하면 settings.json에 즉시 저장됩니다.
예약 실행 시 모드가 날짜와 함께 저장되는 이유: 예약 설정 이후 UI에서 모드를 바꿔도 예약은 설정 시점 모드로 실행되어야 하기 때문입니다.
def set_reserved_date(self, date_str: str, mode: str = "") -> None:
self._reserved_date = date_str
self._reserved_mode = mode or self.mode # 설정 시점 모드 고정
# 예약 실행 시
exec_mode = self._reserved_mode or self.mode
self.mode = exec_mode
await self.start() # 고정된 모드로 실행
settings.json 구조 변화
V1 대비 가장 크게 바뀐 부분입니다. excluded_stocks, added_stocks가 사라지고 target_stocks가 추가됐습니다.
{
"mode": "quant",
"accounts": {
"계좌번호": {
"target_stocks": [
{
"code": "005930", "name": "삼성전자",
"price": 75400, "valid": true,
"added_at": "2026-05-01T09:00:00"
}
]
}
},
"schedule": { ... },
"reservation": {
"date": "2026-05-06",
"mode": "quant"
}
}
다음 편에서는 이 모든 것의 핵심인 QuantEngine 코드를 공개합니다.
시리즈 전체 목차
1~6편 — V1 균등배분 리밸런싱
7편 — V2 기획 & 시스템 설계
8편 — QuantEngine 집합 분류 알고리즘
9편 — 웹 UI & 실제 화면 공개
댓글 없음:
댓글 쓰기