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
+4 -1
View File
@@ -150,14 +150,17 @@ Highest-leverage fixes:
After /morning exits, run_morning.ps1 calls python scripts/start_bot.py.
08:30 Bot loads daily_context.json and builds the watch universe.
08:50 Bot calculates volatility breakout targets.
08:50 Bot calculates volatility breakout targets. If restarted after 08:50,
the bot recalculates targets immediately; open=0 is ignored.
09:00 Morning trading session starts.
09:00-15:05 StockBot_Watchdog checks bot liveness every 5 minutes.
11:00 New entries pause if midday_context.json has not loaded.
11:20 StockBot_Midday -> scripts/run_midday.ps1 -> /midday
Build data/midday_context.json; bot detects it and starts lunch controls.
14:00 New entries stop; exits continue.
14:50 Force exit all positions. This time is immutable.
15:10 Daily settlement and Discord summary.
Watchdog must not restart the bot at or after 15:10.
15:30 StockBot_Evening -> scripts/run_evening.ps1 -> /evening
Write daily report and proposal report when needed.
16:00 StockBot_Training -> scripts/run_training_pipeline.ps1
+4 -1
View File
@@ -150,14 +150,17 @@ Highest-leverage fixes:
After /morning exits, run_morning.ps1 calls python scripts/start_bot.py.
08:30 Bot loads daily_context.json and builds the watch universe.
08:50 Bot calculates volatility breakout targets.
08:50 Bot calculates volatility breakout targets. If restarted after 08:50,
the bot recalculates targets immediately; open=0 is ignored.
09:00 Morning trading session starts.
09:00-15:05 StockBot_Watchdog checks bot liveness every 5 minutes.
11:00 New entries pause if midday_context.json has not loaded.
11:20 StockBot_Midday -> scripts/run_midday.ps1 -> /midday
Build data/midday_context.json; bot detects it and starts lunch controls.
14:00 New entries stop; exits continue.
14:50 Force exit all positions. This time is immutable.
15:10 Daily settlement and Discord summary.
Watchdog must not restart the bot at or after 15:10.
15:30 StockBot_Evening -> scripts/run_evening.ps1 -> /evening
Write daily report and proposal report when needed.
16:00 StockBot_Training -> scripts/run_training_pipeline.ps1
+5 -1
View File
@@ -24,8 +24,10 @@ AI에게 맡기지는 않습니다. AI는 장 전/장중/장후 시장을 분석
| 시간 | 흐름 | 내용 |
|---|---|---|
| 08:15 | 장 전 분석 | 뉴스, 수급, 업종 분위기를 분석해 `daily_context.json` 생성 |
| 08:30 | 봇 시작/준비 | 유니버스 선정, 목표가 계산, Discord 알림 |
| 08:30 | 봇 시작/준비 | AI 컨텍스트 로드, 유니버스 선정 |
| 08:50 | 목표가 계산 | 전일 고저와 당일 시가 기반, `open=0`이면 계산 제외 |
| 09:00 | 오전 매매 | 변동성 돌파 조건과 AI 컨텍스트 필터를 함께 확인 |
| 09:00-15:05 | Watchdog | 5분마다 봇 생존 감시, 15:10 결산 직후 재시작 금지 |
| 11:20 | 장중 분석 | 오전 결과와 현재 시장을 비교해 `midday_context.json` 생성 |
| 14:00 | 신규 진입 종료 | 새 진입은 막고 보유 포지션 청산 감시만 계속 |
| 14:50 | 강제 청산 | 모든 포지션 정리 |
@@ -38,6 +40,8 @@ AI에게 맡기지는 않습니다. AI는 장 전/장중/장후 시장을 분석
- 전략: 변동성 돌파(`K=0.5`)
- 진입 시작: `09:20`
- 강제 청산: `14:50`
- 일일 결산: `15:10`
- Watchdog: `09:00-15:05`, 5분 간격
- DB: SQLite (`data/stockbot.db`)
- 알림: Discord Webhook
- AI/ML: 시장 분석과 관찰용 점수 기록까지만 사용
+16 -3
View File
@@ -55,9 +55,17 @@ class KISClient:
)
self._load_token_from_file()
# rate limit: 모의투자 1건/초보다 보수적, 실거래 5건/초 이하
self._rate_limit = 1 if self.is_mock else 5
self._request_spacing = 1.2 if self.is_mock else 0.22
# rate limit: KIS occasionally rejects even nominally safe bursts.
# Keep defaults conservative and allow local override from .env.
self._rate_limit = int(os.getenv(
"KIS_MOCK_RATE_LIMIT" if self.is_mock else "KIS_REAL_RATE_LIMIT",
"1" if self.is_mock else "3",
))
self._request_spacing = float(os.getenv(
"KIS_MOCK_REQUEST_SPACING" if self.is_mock else "KIS_REAL_REQUEST_SPACING",
"1.7" if self.is_mock else "0.35",
))
self._cooldown_until = 0.0
self._semaphore = asyncio.Semaphore(1)
self._req_times : list = []
@@ -175,6 +183,9 @@ class KISClient:
async with self._semaphore:
now = time.monotonic()
if now < self._cooldown_until:
await asyncio.sleep(self._cooldown_until - now)
now = time.monotonic()
if self._req_times:
wait = self._request_spacing - (now - self._req_times[-1])
if wait > 0:
@@ -204,6 +215,8 @@ class KISClient:
rt_cd = data.get("rt_cd", "")
if rt_cd != "0":
msg = data.get("msg1", "알 수 없는 오류")
if "초당" in msg or "거래건수" in msg or "rate" in msg.lower():
self._cooldown_until = time.monotonic() + max(2.5, self._request_spacing * 2)
logger.error(f"KIS API 오류 [{tr_id}]: {rt_cd} - {msg}")
raise RuntimeError(f"KIS API 오류: {msg}")
+20 -3
View File
@@ -844,15 +844,28 @@ class StockBot:
async def calc_targets(self):
"""당일 시가 기반 목표가 계산"""
logger.info("목표가 계산 시작")
self.strategy.targets.clear()
self.strategy.today_open.clear()
now_str = datetime.now().strftime("%H:%M")
valid_count = 0
for ticker in self.universe:
try:
price_info = await self._get_price_with_retry(ticker, "TARGET")
self.strategy.set_today_open(ticker, price_info["open"])
target = self.strategy.get_target(ticker)
open_price = price_info.get("open") or 0
name = self.ticker_names.get(ticker, ticker)
if open_price <= 0:
current = price_info.get("current") or 0
if now_str >= "09:00" and current > 0:
open_price = current
logger.warning(f"시가 0 감지({name}/{ticker}) → 현재가 {current:,}를 임시 시가로 사용")
else:
logger.info(f"목표가 제외({name}/{ticker}): 시가 미확정(open=0)")
await asyncio.sleep(1.1)
continue
self.strategy.set_today_open(ticker, open_price)
target = self.strategy.get_target(ticker)
if target > 0:
logger.info(f"목표가: {name}({ticker}) {target:,.0f}원 [시가 {price_info['open']:,}]")
logger.info(f"목표가: {name}({ticker}) {target:,.0f}원 [시가 {open_price:,}]")
valid_count += 1
await asyncio.sleep(1.1)
except Exception as e:
@@ -1329,6 +1342,9 @@ async def run():
ctx = bot.strategy.load_ai_context()
bot.risk.set_risk_level(ctx.get("risk_level", "보통"))
await bot.update_universe()
if now >= "08:50":
logger.info("08:50 이후 장 전 재시작 감지 → 목표가 즉시 계산")
await bot.calc_targets()
while True:
now = datetime.now().strftime("%H:%M")
@@ -1354,6 +1370,7 @@ async def run():
# 09:00 매매 루프 시작
elif now == "09:00":
await bot.calc_targets()
await bot.trading_loop()
# 15:10 결산
+20 -4
View File
@@ -47,6 +47,7 @@ class VolatilityBreakout:
self._entry_times: dict = {} # ticker → 마지막 진입 datetime (쿨다운 추적)
self._exit_times: dict = {} # ticker -> 마지막 최종 청산 datetime (쿨다운 추적)
self._tp_closed_tickers: set[str] = set() # TP로 전량 청산된 당일 재진입 차단
self._rebreak_required_tickers: set[str] = set() # TIME/FORCE 후 목표가 재돌파 대기
# ── AI 컨텍스트 로드 ──
@@ -103,6 +104,9 @@ class VolatilityBreakout:
if not prev:
logger.info(f"목표가 제외({ticker}): 전일 데이터 없음")
return
if open_price <= 0:
logger.info(f"목표가 제외({ticker}): 당일 시가 미확정({open_price})")
return
if prev["amount"] < MIN_TRADE_AMOUNT:
logger.info(
f"목표가 제외({ticker}): 전일 거래대금 {prev['amount']/1e8:.0f}"
@@ -126,8 +130,10 @@ class VolatilityBreakout:
exit_time = exit_time or datetime.now()
if reason in ("TIME", "FORCE"):
self._exit_times[ticker] = exit_time
self._rebreak_required_tickers.add(ticker)
elif reason in ("TP1", "TP2"):
self._tp_closed_tickers.add(ticker)
self._rebreak_required_tickers.discard(ticker)
# ── 진입 신호 판단 ──
@@ -151,6 +157,16 @@ class VolatilityBreakout:
result["reason"] = "TP 당일 재진입 차단"
return result
# 목표가 확인
target = self.targets.get(ticker, 0)
if target <= 0:
result["reason"] = "목표가 없음"
return result
# TIME/FORCE 이후 쿨다운 중이라도 목표가 아래로 내려온 사실은 기록한다.
if ticker in self._rebreak_required_tickers and current_price < target:
self._rebreak_required_tickers.discard(ticker)
# TIME/FORCE 청산 후 쿨다운은 진입 시각이 아니라 청산 시각 기준이다.
last_exit = self._exit_times.get(ticker)
if last_exit is not None:
@@ -159,10 +175,10 @@ class VolatilityBreakout:
result["reason"] = f"재진입 쿨다운 ({elapsed:.0f}분 / {TICKER_REENTRY_COOLDOWN_MIN}분)"
return result
# 목표가 확인
target = self.targets.get(ticker, 0)
if target <= 0:
result["reason"] = "목표가 없음"
# TIME/FORCE 청산 뒤에는 남아 있는 당일 돌파 신호를 그대로 재사용하지 않는다.
# 목표가 아래로 식은 뒤 다시 돌파해야 새로운 진입 신호로 인정한다.
if ticker in self._rebreak_required_tickers:
result["reason"] = f"재돌파 대기 ({current_price:,} >= {target:,.0f})"
return result
# 기술적 조건: 현재가 >= 목표가
+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 우선순위 변경 없음.
+1 -1
View File
@@ -85,7 +85,7 @@ async def main():
now = datetime.now()
now_str = now.strftime("%H:%M")
if not ("09:00" <= now_str <= "15:10"):
if not ("09:00" <= now_str < "15:10"):
print(f"[{now_str}] outside trading window - watchdog skipped")
return
+6
View File
@@ -1,5 +1,11 @@
$ErrorActionPreference = "Stop"
chcp 65001 | Out-Null
$OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$env:PYTHONUTF8 = "1"
$env:PYTHONIOENCODING = "utf-8"
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
Set-Location $Root
. "$Root\scripts\stockbot_env.ps1"
+1 -1
View File
@@ -27,7 +27,7 @@ if ($LASTEXITCODE -ne 0) {
$now = Get-Date
$start = Get-Date -Hour 9 -Minute 0 -Second 0
$end = Get-Date -Hour 15 -Minute 10 -Second 59
$end = Get-Date -Hour 15 -Minute 9 -Second 59
if ($now -lt $start -or $now -gt $end) {
Write-WatchdogLog "outside watchdog window - skipped"
exit 0
+10 -3
View File
@@ -30,6 +30,7 @@ function Register-StockTask {
$Settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Minutes $LimitMinutes) `
-StartWhenAvailable `
-WakeToRun `
-DontStopIfGoingOnBatteries `
-RunOnlyIfNetworkAvailable:$false
$Settings.DisallowStartIfOnBatteries = $false
@@ -47,15 +48,21 @@ function Register-StockTask {
}
function Register-WatchdogTask {
$TaskName = "\StockBot\StockBot_Watchdog"
$ScriptPath = Join-Path $Project "scripts\run_watchdog.ps1"
$Command = 'schtasks /Create /TN "\StockBot\StockBot_Watchdog" /TR "\"powershell.exe\" -NonInteractive -ExecutionPolicy Bypass -File \"' + $ScriptPath + '\"" /SC MINUTE /MO 5 /ST 09:00 /ET 15:10 /F'
$Command = 'schtasks /Create /TN "\StockBot\StockBot_Watchdog" /TR "\"powershell.exe\" -NonInteractive -ExecutionPolicy Bypass -File \"' + $ScriptPath + '\"" /SC WEEKLY /D MON,TUE,WED,THU,FRI /ST 09:00 /RI 5 /DU 06:05 /F'
cmd.exe /c $Command | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "StockBot_Watchdog registration failed"
}
Write-Host "[OK] StockBot_Watchdog registered at 09:00-15:10 every 5 minutes" -ForegroundColor Green
$Task = Get-ScheduledTask -TaskName "StockBot_Watchdog" -TaskPath $TaskPath
$Task.Settings.StartWhenAvailable = $true
$Task.Settings.WakeToRun = $true
$Task.Settings.DisallowStartIfOnBatteries = $false
$Task.Settings.StopIfGoingOnBatteries = $false
Set-ScheduledTask -TaskName "StockBot_Watchdog" -TaskPath $TaskPath -Settings $Task.Settings | Out-Null
Write-Host "[OK] StockBot_Watchdog registered weekdays at 09:00-15:05 every 5 minutes" -ForegroundColor Green
}
Register-StockTask "StockBot_Morning" "08:15" "run_morning.ps1" 20
+8 -6
View File
@@ -155,8 +155,8 @@ AI는 두 종류로 나뉜다.
| 08:15 | `StockBot_Morning` | `/morning`, 뉴스/시장 분석, `daily_context.json` 생성 |
| 08:30 | 봇 컨텍스트 로드 | AI 컨텍스트 로드, 유니버스 갱신 |
| 08:50 | 목표가 계산 | 전일 고저와 당일 시가 기반 |
| 09:00 | 매매 루프 시작 | 실제 신규 진입은 `ENTRY_START=09:15` 이후 |
| 09:00-15:10 | `StockBot_Watchdog` | 5분마다 봇 생존 감시 |
| 09:00 | 매매 루프 시작 | 실제 신규 진입은 `ENTRY_START=09:20` 이후 |
| 09:00-15:05 | `StockBot_Watchdog` | 5분마다 봇 생존 감시, 15:10 결산 직후 재시작 금지 |
| 11:00 | 점심 컨텍스트 대기 | `midday_context.json` 전까지 신규 진입 중지 |
| 11:20 | `StockBot_Midday` | `/midday`, 점심 세션 조건 생성 |
| 14:00 | 신규 진입 중단 | 보유 포지션 청산 체크는 계속 |
@@ -177,9 +177,10 @@ AI는 두 종류로 나뉜다.
| `StockBot_Midday` | 11:20 | `scripts/run_midday.ps1` |
| `StockBot_Evening` | 15:30 | `scripts/run_evening.ps1` |
| `StockBot_Training` | 16:00 | `scripts/run_training_pipeline.ps1` |
| `StockBot_Watchdog` | 09:00-15:10, 5분마다 | `scripts/run_watchdog.ps1` |
| `StockBot_Watchdog` | 09:00-15:05, 5분마다 | `scripts/run_watchdog.ps1` |
모든 실행 스크립트는 프로젝트 내부 `.venv`의 Python을 우선 사용한다.
08:50 이후 재시작 시 봇은 목표가를 즉시 재계산하며, KIS 시가가 `0`이면 목표가 계산에서 제외한다.
---
@@ -273,7 +274,7 @@ Restore_StockBot.bat
2. KIS REST 요청 제한 초과와 타임아웃이 장초반에 발생할 수 있다.
3. WebSocket/Redis 기반 실시간 구조는 아직 미완성이다.
4. 실거래 전환 전에는 체결, 부분체결, 미체결, 취소/정정, 재시작 복구 로직이 더 필요하다.
5. 초반 09:15 이후에도 손실 집중이 반복되는지 추가 검증이 필요하다.
5. 초반 09:20 이후에도 손실 집중이 반복되는지 추가 검증이 필요하다.
6. `AI_RISK_SL_MAP`의 한글 키 인코딩은 점검이 필요하다. 정상 risk level과 매핑되지 않으면 리스크별 SL 조정이 무력화될 수 있다.
7. 기존 로그와 일부 문서는 인코딩 깨짐이 남아 있어 장기적으로 정리해야 한다.
@@ -286,10 +287,11 @@ Restore_StockBot.bat
| 제안 | 상태 |
|------|------|
| `ENTRY_START` 09:05 -> 09:15 | 승인 및 적용 |
| `ENTRY_START` 09:15 -> 09:20 | 승인 및 적용 |
| 장초반 포지션 축소 | 보류 |
| 시간대별 SL 강화 | 보류 |
`ENTRY_START=09:15` 변경 후 최소 5거래 이상 관찰한 뒤 다음 조정을 판단한다.
`ENTRY_START=09:20` 변경 후 최소 5거래 이상 관찰한 뒤 다음 조정을 판단한다.
---
@@ -323,7 +325,7 @@ DRY_RUN=false
1. `AI_RISK_SL_MAP` 인코딩/키 매핑 점검
2. KIS API rate-limit 완화
3. KIS minute-bar 실응답 검증
4. 장초반 09:15 이후 손익 데이터 축적
4. 장초반 09:20 이후 손익 데이터 축적
5. WebSocket 시세 구조 도입
6. 실거래용 주문 복구/부분체결/미체결 처리 강화
7. NAS Docker 이전