4월 28일 매매 일지 -- 표면 +22,550원, 그 뒤에 가려진 봇 hang과 F5 도입
화요일. KOSPI/KOSDAQ 모두 BULL(BOTH_BULL) 환경에서 청산 3건·매수 7건. 순손익 +22,550원(PF 2.02, 승률 33.3%) 으로 표면적으론 흑자 마감했지만, 실제로는 09:00~10:16 봇이 70분간 hang으로 정지했다가 재시작 후 매수 6건이 몰린, 시스템 안정성 측면에서 가장 위험했던 날. pension 유니버스 빌드에서 발견한 pykrx hang을 timeout 패치로 잡았고, 14:10 대우건설 매수를 계기로 안전 필터 F5(당일 고가 -5% 낙폭)를 추가했다.
시장 환경
| 지수 | 현재가 (09:00) | 20일선 | 판정 |
|---|---|---|---|
| KOSPI (KODEX 200) | 100,240 | 90,782 | BULL |
| KOSDAQ (KODEX 코스닥150) | 20,670 | 19,161 | BULL |
3팩터 모두 BULL → ALL_BULL 100% 예산 스케일 적용 가능 환경. 그러나 pension은 09:00~09:06 매수 윈도우에 봇이 hang 상태였기에 사실상 비가동.
🚨 시스템 경보 -- 09:00~10:16 봇 hang
오전 발생한 사건:
- 09:00:43 키움 웹소켓 로그인 응답 타임아웃 → reader 태스크 예외 처리 중 _reconnect()도 같은 RuntimeError를 던져 reader가 영구 사망. 실시간 시세 피드 단절.
- 09:00:45 pension
_analyze_stock에서 K필터 통과 1,753종목 sem=8 동시 조회 진입 직후 hang. 이후 70분간 어떤 로그도 추가되지 않음. VB 사이클·청산 감시까지 모두 정지. - 10:16 사용자 발견 → 패치 3종 적용 후 재시작.
근본 원인은 pykrx의 webio.Get/Post.read가 requests.Session.get/post에 timeout을 안 넘긴다는 것. KRX 서버가 응답하지 않으면 OS TCP 기본값(수 시간)까지 블록된다. asyncio.wait_for로 future를 취소해도 underlying executor 스레드와 connection은 그대로 누수되어, 누적되면 워커 풀이 고갈된다.
적용한 패치 3종
core/logger.py--install_pykrx_timeout(15s)를 모듈 로드 시 자동 호출.webio.Get.read/Post.read를 monkey-patch해 모든 KRX HTTP 호출에 socket timeout 강제. 봇·tools/·market_data 일괄 보호.strategy/bunt_pension.py--_analyze_stock의 pykrx 호출에asyncio.wait_for10s/5s 이중 안전망.core/kiwoom_realtime.py--_reader_loop가 reconnect 실패에도 죽지 않도록 1→30s exponential backoff 재시도.
드라이런 시뮬(1,753종목 sem=8 동일 패턴)에서 패치 전 49분+ hang → 패치 후 124.8초 완주, 96.7% 성공, 57건 ReadTimeout 정상 해제로 검증.
청산 내역 (3건)
| 시각 | 종목 | 진입가 | 청산가 | PnL | 사유 | 보유 | K |
|---|---|---|---|---|---|---|---|
| 10:30:34 | 삼성전자 (005930) | 228,500 | 225,000 | -21,000 (-1.53%) | TIME_STOP | D+5 | 0.48 |
| 14:04:27 | HD현대에너지솔루션 (322000) | 203,625 | 203,500 | -1,000 (-0.06%) | BREAKEVEN_STOP | D+0 | 0.60 |
| 14:37:11 | 대한전선 (001440) | 44,500 | 45,850 | +44,550 (+3.03%) | TRAILING_STOP | D+4 | 0.45 |
청산 손익 합계: +22,550원 (1승 2패, 승률 33.3%, PF 2.02, 평균 +0.48%)
- 삼성전자 TIME_STOP -- 4/23 진입 후 5영업일 보유, +1% 미만 횡보로 시간 손절. 진입가 대비 -1.53%로 확대 손절(ATR×2.0)이나 본전 손절상향(BE)이 발동하지 않은 상태에서 시간 룰이 깨끗하게 작동. 자본 회수 측면에서 정상 동작.
- HD현대에너지솔루션 BREAKEVEN_STOP -- 10:18 진입 후 일시 +3% 이상 상승해 BE 손절상향이 잡혔으나, 14:04에 진입가까지 회귀해 본전 컷. -0.06% 손실은 호가 스프레드 수준. 당일 진입·당일 청산은 변동성 회수 시그널이 약했다는 뜻.
- 대한전선 TRAILING_STOP -- 4/24 진입 후 +5% 트레일링 진입, 4영업일 만에 -2% 컷. 단일 종목이 오늘 PnL을 사실상 단독으로 결정한 셈(승률 33.3%인데 PF가 2.02인 비대칭 분포의 전형).
매수 내역 (7건)
| 시각 | 종목 | 진입가 | 수량 | 금액 | K | 비고 |
|---|---|---|---|---|---|---|
| 10:18:16 | 삼성E&A (028050) | 56,800 | 28 | 1,590,400 | 0.56 | 재시작 직후 첫 진입 |
| 10:18:18 | 현대차 (005380) | 562,000 | 2 | 1,124,000 | 0.57 | 대형주 |
| 10:18:19 | HD현대에너지솔루션 (322000) | 203,625 | 8 | 1,629,000 | 0.60 | 당일 BE 청산 |
| 10:22:25 | NAVER (035420) | 222,000 | 8 | 1,776,000 | 0.65 | K 최고치(노이즈 큼) |
| 11:45:29 | 삼성전기 (009150) | 836,000 | 1 | 836,000 | 0.53 | 단가↑로 1주만 |
| 14:10:35 | 대우건설 (047040) | 37,950 | 50 | 1,897,500 | 0.55 | ⚠️ F5 트리거 케이스 |
| 14:43:20 | 대한전선 (001440) | 46,000 | 38 | 1,748,000 | 0.45 | 🔁 6분 만에 재진입 |
총 매수 금액 약 10,600,000원. 봇 재시작(10:16) 직후 4분 안에 4건이 몰렸는데, 이는 hang 동안 누적된 돌파 신호가 한 번에 뚫린 결과. 대형주(현대차·삼성전기) 진입은 PER_SLOT_CAP=200만 동적 분배 + per-slot 예산 150만 환경에서 1주 단위로 가능하게 된 것이 시각적으로 확인됨.
🔭 특이 분석
1. 대한전선 6분 재진입
14:37 TRAILING_STOP으로 +3.03% 익절(D+4) → 14:43 재돌파로 즉시 재진입. TRAILING_STOP 사유의 자동 복귀 쿨다운이 0일이라 가능한 흐름이고, 진입가가 청산가(45,850)보다 +0.33% 높은 수준으로 추세가 유지되고 있다는 시그널. 동일 종목이 같은 날 두 번 등장하는 케이스로 K값(0.45 → 0.45)·VB trigger(44,766)·진입 사이즈(50주 → 38주)는 슬롯당 잔액 차이가 반영된 결과.
2. 14:10 대우건설 -- F5 도입의 직접 동기
대우건설(047040)은 09:00 시가 33,850원에서 09:45 장중 고가 40,350원까지 +19% 갭상승 후 종일 하락. 14:10 시점 37,950원은 당일 고점 대비 -5.95% 자리에서의 매수.
봇 로그 추적 결과 F4(현재가/20일고가 1.05x 상한)는 09:00~14:06까지 1.05~1.08x 범위에서 계속 차단했으나, 가격이 지속 하락하며 ratio가 줄어 14:06에 1.047x로 자연 통과. 즉 F1~F4 모두 일봉/누적 데이터 기반이라, 장중 고점 후 종일 빠지는 종목은 가격이 빠질수록 오히려 통과 쉬워지는 역설이 있다.
대응: MAX_INTRADAY_DRAWDOWN = 0.05 상수를 추가하고 F5(당일 고가 대비 -5% 낙폭이면 차단)를 F4 다음에 삽입(strategy/volatility_breakout.py·main.py). 시간대 cutoff 단축(예: 14:50→14:00)도 검토했으나 14:10 케이스에 fit한 후행 선택이고 다른 시간대 패턴은 못 잡으므로 over-fit으로 간주해 기각. 설계 원칙은 case-avoidance가 아니라 mechanism-block.
보유 종목 현황 (10종목, 슬롯 10/12)
| 종목 | 진입가 | 진입일 | D+ | K | 비고 |
|---|---|---|---|---|---|
| HD한국조선해양 (009540) | 473,000 | 04/24 | D+4 | 0.61 | 방산·조선 |
| 두산에너빌리티 (034020) | 126,900 | 04/24 | D+4 | 0.54 | 에너지·발전 |
| 현대로템 (064350) | 247,000 | 04/24 | D+4 | 0.55 | 방산 |
| 알테오젠 (196170) | 385,000 | 04/27 | D+1 | 0.50 | 바이오 |
| 삼성E&A (028050) | 56,800 | 04/28 | D+0 | 0.56 | 건설·엔지니어링 |
| 현대차 (005380) | 562,000 | 04/28 | D+0 | 0.57 | 자동차 |
| NAVER (035420) | 222,000 | 04/28 | D+0 | 0.65 | IT |
| 삼성전기 (009150) | 836,000 | 04/28 | D+0 | 0.53 | IT 부품 |
| 대우건설 (047040) | 37,950 | 04/28 | D+0 | 0.55 | 건설 (F5 트리거 case) |
| 대한전선 (001440) | 46,000 | 04/28 | D+0 | 0.45 | 전기·케이블 (재진입) |
섹터 분포는 방산·조선·에너지·자동차·건설·IT가 고르게 섞여 분산이 어느 정도 자연 발생. 04/24 방산·중공업 묶음(HD한국조선해양·두산에너빌리티·현대로템) 3건이 동일 시기 진입한 점은 섹터 쿼터(현재 비활성)의 재검토 여지로 남겨두던 부분.
📊 인사이트
- PF 2.02·승률 33.3%는 전형적인 비대칭 분포로, 대한전선 단일 +44,550이 결과를 단독 결정. 시스템 신호가 약한 날엔 한 종목의 트레일링 폭이 PnL을 좌우한다는 점이 다시 확인됨.
- K구간 성과(daily_summary insight): K≤0.5 평균 +0.75% (2건) vs K>0.5 평균 -0.06% (1건). 표본 적지만 노이즈가 큰 환경(K가 높음)에서 진입한 HD현대에너지솔루션이 즉시 BE로 빠진 흐름과 일치.
- 평균 보유일 약 3.1일(5/0.16/4.17). TIME_STOP 한 건이 평균을 끌어올렸고, BE 즉시 컷이 오늘 한 건 발생.
- 주간 누계(4/21~4/28 6영업일): 27건 청산, 21승, 순손익 +657,298원. 4/22 -139,350 한 번을 제외하면 5/6일 흑자. PF가 누적 안정 구간에 진입.
🐛 미해결 숙제
- pension universe pre-build 분리 -- 오늘 hang의 직접 원인은 pension의 09:00 즉시 빌드. 하지만 K/C/D 필터·ATR 모두 전일 일봉 데이터만 사용하므로 장중에 돌릴 필요가 없다.
tools/build_pension_universe.py를 신규 작성해 평일 08:30 또는 16:35 cron으로 캐시화하면 매수 윈도우 6분을 100% 활용할 수 있음. 다음 세션 과제로 메모리에 기록. - F5 임계값 -5% 적정성 -- 단일 케이스(대우건설 -5.95%)만으로 도입한 보수적 기본값. 1~2주 라이브 데이터로 false positive 분포를 확인 후 조정(-3%/-7% 검토). 분봉 백테스트는 ohlcv_1min 인프라(2026-04-22~ 수집중) 누적 후 별도 트랙.
- 대우건설(047040) 보유 50주 -- F5는 신규 진입에만 영향, 기존 보유 청산 로직(STOP_LOSS·TIME_STOP·TRAILING)은 그대로. 후속 처리 필요할 수 있음.
오늘의 교훈
확인한 것
- pykrx의
webio가 timeout 미설정인 점이 모든 KRX 의존 코드의 잠재적 데드락 포인트였다는 사실.install_pykrx_timeout()로 일괄 보호 가능. - 웹소켓 reader 사망 시 reconnect 자체가 예외를 던지면 영구 사망하는 패턴은 backoff 중첩 try/except로 해결 가능.
- 드라이런 시뮬레이션(1,753 sem=8)이 실측 정량 검증 도구로 유용. 49분+ hang vs 124.8s 완주의 차이가 명확.
- F5는 mechanism-block이라 시간 무관 일반화 가능. case-fit 로직(시간 cutoff)과 구별됨.
경계할 것
- 봇 hang 70분의 탐지 지연 자체가 가장 큰 위험이었다. 사용자 발견까지 자동 알림이 없었음. heartbeat/idle 감시 알림이 별도 작업으로 필요.
- 매수 7건 중 6건이 재시작 직후 4분에 몰렸다는 점은 burst load가 정상 관측이지만, 그동안 막혔던 신호가 한 번에 뚫리는 환경에서는 F5 같은 안전망이 더 중요하게 작용함.
- 승률 33%·PF 2.02는 표면적으로 양호하나, 1건이 결과를 지배할 때는 승률·PF 단독 해석보다 비대칭 분포 자체를 봐야 함.
내일 체크포인트
- 🔴 09:00 봇 정상 가동 확인. 특히
[PENSION] 유니버스 구축 완료로그가 처음으로 찍히는지 확인 (지금까지 한 번도 완료 로그 없었음). - 🔴 09:00 키움 웹소켓 로그인 성공 여부 확인. 실패 시 새 backoff 경로(1→30s)가 동작하는지 로그로 검증.
- 🟡 F5 트리거 케이스 발생 시 로그 검토(
당일 고가 이탈 매수 스킵메시지). 어떤 종목이 어느 시점에 차단됐는지 누적해 임계값 적정성 평가 시드 데이터로 활용. - 대우건설(047040) 추세 모니터. F5 안전망이 없던 시점에 들어간 포지션이라 별도 주시.
- pension universe pre-build 분리 작업 검토 (다음 과제).
소감
표면적으론 흑자 +22,550원에 PF 2.02로 마감했지만, 오늘은 결과가 아니라 봇 안정성을 갈아 넣은 날이다. pykrx hang은 4일째 기록만 남아 있던 잠재 버그가 오늘 처음 fully blocking 형태로 노출된 것이고, F5는 case-fit 유혹을 한 번 거치고 mechanism으로 재정식화된 결과물이다. 시스템이 위험을 명시적으로 보여주고, 그 위험을 봉합하는 패치를 검증된 형태로 적용한 하루로 정리한다.


'Project Archive > Quant & Auto Trading' 카테고리의 다른 글
| [자동매매] 실전운영 15일차 - TIME_STOP 4건 동시 발동, 반도체 듀얼 트레일링이 적자를 메우다 (0) | 2026.04.28 |
|---|---|
| [자동매매] 실전운영 14일차 - 4승 무패 +29.4만원, 쿨다운 재진입 대덕전자 +7.82%와 구조 변경 첫날 (0) | 2026.04.24 |
| [자동매매] 실전운영 13일차 - 삼성중공업 TARGET +9.95%, 조선→반도체 섹터 로테이션으로 +14.7만원 회복 (0) | 2026.04.23 |
| [자동매매] 실전운영 12일차 - 우리로 손절 예정대로 발동, 그러나 5분 공백이 남긴 -21만원의 숙제 (0) | 2026.04.22 |
| [자동매매] 실전운영 11일차 - 승률 100% / 4건 모두 수익 청산, 포트폴리오 대규모 전환일 (0) | 2026.04.21 |
댓글