Harden scheduler and stale breakout reentry

This commit is contained in:
whdwo
2026-06-15 18:52:42 +09:00
parent eac4ece01e
commit 901243348e
16 changed files with 181 additions and 61 deletions
+2 -2
View File
@@ -57,7 +57,7 @@
### 5. 시스템 이슈
- **15:10 봇 재시작 감지**: 로그에서 결산(15:10:01) 직후 봇이 재초기화됨(15:10:04). 이후 KIS API "초당 거래건수 초과" 오류 발생(15:10:04). 결산 완료 후 자동 또는 수동 재시작이 있었던 것으로 보임. 정산 직후 재시작 시 KIS 토큰 재사용 정상 동작 확인.
- **15:10 봇 재시작 감지(수정 완료)**: 로그에서 결산(15:10:01) 직후 watchdog이 종료된 봇을 재시작함(15:10:04). 이후 KIS API "초당 거래건수 초과" 오류 발생. 원인은 watchdog 감시 조건이 `15:10`을 포함한 것이며, `scripts/_watchdog.py`, `scripts/run_watchdog.ps1`, `scripts/setup_scheduler.ps1`을 수정해 watchdog을 09:00-15:05로 제한했다.
- **점심 섹터 변경**: 오전 어보이드(건설, 금융) → 점심 어보이드(기계, 운수창고, 2차전지). 흥아해운(해운업/운수창고)은 오전 진입이므로 룰 위반 없음. 그러나 점심 이후 해당 종목 재진입 시 차단이 정상 작동했는지 확인 필요.
---
@@ -90,5 +90,5 @@
## 다음 체크 포인트
- 대한광통신 형태(고변동성 즉시 SL) 재발 시 진입 슬리피지/변동성 필터 강화 검토
- 15:10 봇 재시작 원인 확인 및 필요 시 결산 후 자동 재시작 억제
- 2026-06-10 15:10에 watchdog이 결산 봇을 재시작하지 않는지 확인
- KIS API 초당 거래건수 초과 오류 빈도 모니터링
+7 -5
View File
@@ -73,15 +73,16 @@ TP 청산 비율 66.7%, FORCE/TIME 0%. 강제청산 없이 깔끔하게 마감.
## AI 필터 품질
- 오늘 전 거래 `ai_boosted = 0`. AI 모델은 관찰 모드만.
- 학습 데이터 부족(53 봇 거래 행)으로 AI 판단 의존은 아직 부적절.
- 학습 데이터는 아직 대부분 외부 분봉 기반 후보 행이며, 실제 봇 거래 표본은 부족해 AI 판단 의존은 아직 부적절.
- 오후 신호 진단: 목표가 미달 종목 다수, SL 차단 2종, TP 재진입 차단 2종 — 필터링 정상.
## 실행 품질
- 제로 가격 행 없음, 가격 불일치 없음.
- 라이콤/광전자 진입 후 초단시간 TP 달성 — 변동성 돌파 로직 정상.
- KIS 율한도 초과 경고 2회 (13:39, 13:42): `ENTRY price retry 1/4` 로그 확인.
- KIS 율한도 초과 경고가 장중 여러 차례 발생: `ENTRY price retry 1/4` 로그 확인.
- retry 후 정상 재개. 치명적 장애 아님.
- 장 마감 후 KISClient 기본 조회 간격 확대 및 rate-limit 전역 쿨다운 추가.
- 14:00 이후 ENTRY 차단 정상 동작 확인.
- 14:50 강제 청산 시작 → 완료 정상 (미청산 포지션 없음).
- 결산 중복 처리 방어 정상: `결산 이미 처리됨: 2026-06-10` 로그 확인.
@@ -89,9 +90,10 @@ TP 청산 비율 66.7%, FORCE/TIME 0%. 강제청산 없이 깔끔하게 마감.
## 운영 이슈
### KIS 율한도 초과 (경미)
- 13:39, 13:42 가격 조회 시 `초당 거래건수를 초과하였습니다` 2회 발생.
- 가격 조회 시 `초당 거래건수를 초과하였습니다`가 여러 차례 발생.
- 현행 retry 로직으로 자동 복구됨.
- 오후 감시 루프에서 다수 종목 동시 조회 시 빈도 집중 가능성. 지속 모니터링 필요.
- 오후 감시 루프에서 다수 종목 순차 조회 시 빈도 집중 가능성.
- 2026-06-10 장 마감 후 `app/execution/kis_client.py`에서 기본 조회 간격을 보수화하고 rate-limit 응답 후 전역 쿨다운을 추가함.
## 30일 누적 지표 (2거래일)
@@ -117,6 +119,6 @@ TP 청산 비율 66.7%, FORCE/TIME 0%. 강제청산 없이 깔끔하게 마감.
## 다음 체크사항
- KIS 율한도 초과 빈도가 주 2회 이상 지속되면 조회 간격 확대 검토.
- 2026-06-11에 KIS 율한도 초과 빈도가 줄었는지 확인.
- 대우건설 80분 보유 후 SL: `MAX_HOLD_MIN=90` 경계에 근접. 현행 유지.
- 운영 데이터 누적 지속. 30거래일 도달 시 라이브 준비 재점검.
+10 -8
View File
@@ -120,20 +120,22 @@ TP 청산 비율 66.7%, SL 비율 11.1%. 강제청산 없음.
- KIS 타임아웃: 라이콤(388790) 12:30 2회 재시도, 현대건설(000720) 12:57 1회 재시도.
모두 정상 복구. 실거래 영향 없음.
## 구조 이슈 — 삼성전자 진입가 > 목표가
## 구조 이슈 — 삼성전자 TIME 후 동일 신호 재진입
| 항목 | 1차 진입 | 2차 진입 |
|---|---|---|
| 진입가 | 340,000 | 338,500 |
| 목표가(TP) | 334,000 | 334,000 |
| 돌파 목표가 | 334,000 | 334,000 |
| 차이 | **-6,000원** | **-4,500원** |
| 청산 | TIME | TIME |
- 아침 변동성 돌파 목표가는 전일 종가 기준 계산. 삼성전자가 개장 시 갭업하여
목표가(334,000)를 이미 초과한 가격(340,000)에 진입.
- 이 구조에서는 TP 달성이 원천 불가. 결국 TIME 또는 SL만 가능.
- 현행 코드에 `current_price < tp_target` 진입 차단 로직 없음.
- **별도 제안서 작성** (`reports/proposals/2026-06-15_strategy_proposal.md`).
- 코드상 `목표가`는 익절가가 아니라 변동성 돌파 진입 기준가.
따라서 `현재가 >= 목표가` 자체는 정상 진입 조건.
- 실제 문제는 1차 `TIME` 청산 후에도 가격이 목표가 위에 머물러,
60분 쿨다운 종료만으로 같은 돌파 신호를 재사용해 2차 진입한 점.
- **적용 완료**: `TIME/FORCE` 청산 후에는 목표가 아래로 한 번 내려왔다가
다시 돌파해야 재진입 가능하도록 `재돌파 대기` 필터 추가.
- 적용 문서: `reports/proposals/2026-06-15_strategy_proposal.md`.
## 30일 누적 지표 (5거래일)
@@ -151,7 +153,7 @@ TP 청산 비율 66.7%, SL 비율 11.1%. 강제청산 없음.
## 다음 체크사항
- `진입가 > 목표가` 필터 제안서 검토 및 수동 승인 여부 결정.
- `TIME/FORCE` 후 재돌파 대기 필터 내일 로그에서 정상 차단 여부 확인.
- AI 부스트 누적 손익 별도 집계 시작 권장.
- 에이팩트 대량 포지션 사이징(193주) — 리스크 대비 포지션 계산 재확인.
- 운영 데이터 누적 지속. 30거래일 도달 시 라이브 준비 재점검.
+42
View File
@@ -1,5 +1,32 @@
# Implementation Log
## 2026-06-10
- Enabled wake-from-sleep behavior for Scheduler tasks:
- `scripts/setup_scheduler.ps1` now registers stock tasks with `WakeToRun`.
- Re-registered tasks and verified `WakeToRun=True` and `StartWhenAvailable=True`.
- Hardened KIS request throttling:
- Mock request spacing default: 1.7s.
- Real request spacing default: 0.35s, rate limit default: 3/sec.
- Added local `.env` override support for request spacing/rate limits.
- Added global cooldown after rate-limit responses.
- Updated the 2026-06-10 daily report to reflect repeated KIS rate-limit retries.
## 2026-06-09
- Re-registered all Windows Scheduler tasks from the live project path:
- `C:\Users\whdwo\Desktop\coding\stockbot_v3`
- Verified every task action script exists at that path.
- Fixed watchdog end-of-day behavior:
- `StockBot_Watchdog` now runs 09:00-15:05 every 5 minutes.
- `scripts/_watchdog.py` excludes 15:10 so normal daily settlement shutdown is not restarted.
- `scripts/run_watchdog.ps1` skips after 15:09:59.
- Hardened target calculation:
- Targets are cleared before recalculation.
- `open=0` is ignored before market open.
- Delayed restarts after 08:50 recalculate targets immediately.
- Updated operational docs and the 2026-06-09 daily report.
## 2026-05-28
- Applied the approved 2026-05-28 strategy update:
@@ -120,3 +147,18 @@ Open risks:
- Verification:
- Python compile check passed.
- Runtime import confirmed `ENTRY_START == "09:15"`.
## 2026-06-15
- Applied a stale breakout re-entry guard after reviewing the Samsung Electronics `TIME` re-entry.
- Changed `app/strategy/volatility_breakout.py`:
- `TIME` and `FORCE` final exits now mark the ticker as requiring a fresh breakout.
- While that marker is active, a ticker is blocked with `재돌파 대기` if it remains above the same volatility breakout target.
- The marker clears only after price moves back below the target, allowing a later fresh breakout entry.
- Rationale:
- The existing `current_price >= target` condition is the normal volatility breakout entry rule.
- The bug was reusing a still-active same-day breakout signal after `TIME/FORCE` cooldown, not the first breakout itself.
- This would have blocked the 2026-06-15 Samsung Electronics second entry after the first `TIME` exit.
- Updated docs:
- `reports/daily/2026-06-15.md`
- `reports/proposals/2026-06-15_strategy_proposal.md`
@@ -2,39 +2,44 @@
## 요약
진입 시점에 현재가가 목표가(TP)를 이미 초과한 경우 진입을 차단하는 필터 추가.
`TIME/FORCE` 청산 후에는 같은 당일 돌파 신호를 그대로 재사용하지 않고,
목표가 아래로 한 번 식었다가 다시 돌파할 때만 재진입하도록 필터 추가.
**수동 승인 필수.**
**적용 완료:** 2026-06-15
---
## 관찰된 문제
오늘(2026-06-15) 삼성전자(005930)가 두 차례 진입됐으나 두 번 모두 목표가(334,000)가
진입가(340,000 / 338,500)보다 낮았다.
오늘(2026-06-15) 삼성전자(005930)가 두 차례 진입됐다. 1차는 09:20 돌파 진입이었고,
10:51 `TIME` 청산 후 60분 쿨다운이 끝난 11:51에 다시 진입됐다.
| 진입 | 진입가 | 목표가(TP) | 차이 | 결과 |
|---|---|---|---|---|
| 1차 09:20 | 340,000 | 334,000 | -6,000 | TIME -21,681원 |
| 2차 11:51 | 338,500 | 334,000 | -4,500 | TIME -1,422원 |
원인: 변동성 돌파 목표가는 전일 종가 기준으로 계산되는데, 삼성전자가 개장 시 갭업하여
목표가를 이미 상회한 가격에 진입 트리거가 발동했다.
주의: 코드상 `목표가`는 익절가가 아니라 변동성 돌파 진입 기준가다.
따라서 `현재가 >= 목표가`를 무조건 막으면 전략 전체 진입이 중단된다.
구조에서는 TP 달성이 원천 불가능하다. TIME 또는 SL 청산만 남는다.
실제 구조적 문제는 `TIME/FORCE` 청산 후에도 현재가가 목표가 위에 머물면,
새로운 돌파가 없는데도 쿨다운 종료만으로 같은 신호를 재사용해 재진입할 수 있다는 점이다.
---
## 제안 내용
### 진입 차단 조건 추가
### TIME/FORCE 후 재돌파 조건 추가
`check_entry()` 내부에 다음 조건을 hard gate로 추가:
`mark_final_exit()`에서 `TIME` 또는 `FORCE` 청산 종목을 재돌파 대기 목록에 넣고,
`check_entry()`에서 해당 종목이 목표가 아래로 내려오기 전까지 진입을 차단한다.
```python
# 현재가가 TP1 목표가 이상이면 진입 차단 (갭업 후 목표가 무효화)
if current_price >= tp_target:
return False, f"현재가({current_price:,})가 목표가({tp_target:,}) 이상 — 진입 차단"
if ticker in self._rebreak_required_tickers:
if current_price >= target:
result["reason"] = f"재돌파 대기 ({current_price:,} >= {target:,.0f})"
return result
self._rebreak_required_tickers.discard(ticker)
```
적용 위치: `app/strategy/volatility_breakout.py``check_entry()` 함수 내
@@ -44,19 +49,17 @@ if current_price >= tp_target:
## 기대 효과
- 오늘 기준: 삼성전자 2건(-23,103원) 방어 가능.
- 갭업 종목이 TP를 이미 소화한 상태로 진입하는 구조적 실수 차단.
- 오늘 기준: 삼성전자 2차 TIME 재진입(-1,422원) 방어 가능.
- 같은 날 같은 돌파 신호를 쿨다운 후 반복 사용하는 구조 차단.
- SL/TIME 낭비 거래 제거 → R:R 개선.
---
## 위험 및 주의사항
- TP 목표가 계산 로직이 정확해야 필터가 올바르게 동작한다.
(`tp_target`이 진입 가능 구간 안에 있을 때만 진입하는 원래 의도와 동일.)
- 극히 드문 케이스: 목표가 재계산(장중 업데이트) 여부 확인 필요.
현재 구현이 고정 목표가라면 문제없음; 장중 재계산이 있다면 로직 검토 추가 필요.
- 샘플: 오늘 2건 관찰. 통계적 근거로는 부족하나, 이는 파라미터 조정이 아니라
- 최초 돌파 진입은 기존과 동일하게 허용된다.
- TIME/FORCE 뒤에도 가격이 목표가 아래로 내려갔다가 다시 돌파하면 재진입 가능하다.
- 샘플: 오늘 1건의 명확한 재진입 사례 관찰. 통계적 근거로는 부족하나, 이는 파라미터 조정이 아니라
**논리적 버그 수정**에 해당하므로 소량 샘플로도 충분히 정당화됨.
---
@@ -72,8 +75,8 @@ if current_price >= tp_target:
## 승인 조건
- [ ] `volatility_breakout.py``tp_target` 변수가 진입 시점에 접근 가능한지 확인.
- [ ] 장중 목표가 재계산 여부 확인.
- [ ] 수동 코드 검토 후 적용.
- [x] `volatility_breakout.py`돌파 목표가 변수(`target`) 접근 확인.
- [x] `TIME/FORCE` 청산 후 같은 신호 재사용 경로 확인.
- [x] 수동 코드 검토 후 적용.
**FORCE_EXIT = "14:50"** 변경 없음. SL 우선순위 변경 없음.