diff --git a/.claude/commands/midday.md b/.claude/commands/midday.md new file mode 100644 index 0000000..2ab14cc --- /dev/null +++ b/.claude/commands/midday.md @@ -0,0 +1,71 @@ +# claude_midday — 장중 분석 + +11:20 스케줄러가 자동 실행. 오전 결과 분석 후 점심 세션 파라미터 결정 → midday_context.json 저장. + +## 실행 순서 + +### 1. 데이터 수집 +```bash +python app/ai/midday.py --print +``` +수집 항목: +- **오전 거래 결과**: 체결 내역, 승/패, 연속 손절 수, 순손익 +- **현재 포지션**: 보유 중인 종목 및 현재 손익 +- **시장 스냅샷**: 거래량 순위(현재), 업종별 등락률 +- **아침 컨텍스트**: daily_context.json (오전 예측 비교용) + +### 2. 분석 + +오전 daily_context 예측과 실제 결과를 비교해 점심 세션 파라미터를 결정한다. + +**비교 포인트:** +- 오전 예측 hot_sectors ↔ 실제 업종 등락률 — 예측이 맞았는가? +- 거래량 순위 변화 — 새로 뜨는 종목 / 사라진 종목 +- 오전 거래 결과 — 전략이 작동했는가, 연속 손절 상태인가? + +**판단 항목:** +- **lunch_trade_allowed**: false이면 점심 세션 진입 없음 + - sentiment_score < 40이거나, 연속 손절 3회 이상이면 false 권장 +- **position_size_multiplier**: B안 연속 손절 배율 × 시장 판단 + - 연속 손절 0회: 1.0 / 1회: 0.7 / 2회: 0.5 / 3회+: 0.3 + - 시장이 오전 예측보다 좋으면 상향, 나쁘면 하향 +- **hot_sectors**: 오전 대비 업데이트 (실제 강한 업종) +- **avoid_sectors**: 오전 대비 업데이트 (실제 약한 업종) +- **strategy_note**: 전략 조정이 필요하면 메모 (없으면 빈 문자열) +- **reason**: 50자 이내 장중 요약 + +### 3. midday_context.json 저장 +분석 결과를 `data/midday_context.json`에 저장한다. 형식: +```json +{ + "date": "YYYY-MM-DD", + "generated_at": "HH:MM:SS", + "lunch_trade_allowed": true, + "position_size_multiplier": 0.8, + "hot_sectors": ["반도체"], + "avoid_sectors": ["건설업"], + "strategy_note": "", + "reason": "50자 이내 요약" +} +``` +파일이 저장되는 즉시 봇이 감지해 점심 세션을 시작한다. + +### 4. Discord 알림 전송 +```bash +python -c " +import asyncio, json, os, sys +sys.path.insert(0, '.') +from app.main import load_env; load_env() +from app.monitor.notifier import send +ctx = json.load(open('data/midday_context.json', encoding='utf-8')) +hot = ', '.join(ctx.get('hot_sectors', [])) or '없음' +avoid = ', '.join(ctx.get('avoid_sectors', [])) or '없음' +flag = '✅ 점심진입허용' if ctx.get('lunch_trade_allowed', True) else '🚫 점심진입중단' +msg = f'[장중분석] {ctx[\"date\"]} {ctx.get(\"generated_at\",\"\")}\n{flag} | 포지션배율: x{ctx.get(\"position_size_multiplier\",1.0)}\n주목: {hot} | 회피: {avoid}\n📝 {ctx.get(\"reason\",\"\")}' +asyncio.run(send(msg)) +print('Discord 전송 완료') +" +``` + +### 5. 완료 +분석 요약을 한 줄로 출력하고 종료한다. diff --git a/CLAUDE.md b/CLAUDE.md index 3a24ef0..5a95f50 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # StockBot v3.0 — Claude Code 운영 가이드 -> 최종 수정: 2026-05-15 +> 최종 수정: 2026-05-19 > 인프라: 로컬 Windows → Synology NAS Docker 이전 예정 > 현재 모드: 모의투자 (KIS_MOCK=true, DRY_RUN=true) @@ -41,21 +41,29 @@ ## 하루 자동화 흐름 ``` -07:30 StockBot_Morning → run_morning.ps1 → claude /morning → RSS+네이버 뉴스+수급 분석 → daily_context.json +07:30 StockBot_Morning → run_morning.ps1 → claude /morning + RSS+네이버 뉴스+KIS 수급 분석 → daily_context.json 완료 후 자동으로 /start-bot 호출 → 봇 백그라운드 시작 -08:30 봇이 daily_context.json 로드 → Discord에 분석 결과 전송 → 유니버스 30종목 확정 +08:30 봇이 daily_context.json 로드 → Discord 전송 → 유니버스 30종목 확정 08:50 목표가 계산 -09:00 매매 루프 시작 (변동성 돌파 신호 + AI 필터) +09:00 아침 세션 시작 (변동성 돌파 신호 + AI 필터) + B안: 연속 손절 시 포지션 크기 자동 축소 (0회→1.0× / 1회→0.7× / 2회→0.5× / 3+→0.3×) +11:00 midday_context.json 미로드 시 신규 진입 일시 중단 +11:20 StockBot_Midday → run_midday.ps1 → claude /midday + 오전 결과+시장 스냅샷 수집 → midday_context.json 저장 + 파일 생성 즉시 봇이 감지 → 점심 세션 자동 시작 +14:00 신규 진입 마감 14:50 강제 전량 청산 (절대 불변) 15:10 일일 결산 → Discord 전송 -15:30 StockBot_Evening → run_evening.ps1 → claude /evening → 결과 분석 + 리포트 저장 +15:30 StockBot_Evening → run_evening.ps1 → claude /evening + 결과 분석 + 리포트 저장 ``` ### 스케줄러 스크립트 주의사항 (scripts/run_*.ps1) - 경로: `$PROJECT = Split-Path -Parent $PSScriptRoot` (한글 경로 인코딩 문제 방지) - Claude 실행: `$CLAUDE = "C:\Users\whdwo\AppData\Roaming\npm\claude.cmd"` (전체 경로 필수) - 인코딩: `New-Object System.Text.UTF8Encoding $false` + UTF-8 BOM으로 저장 -- 로그: `logs/bot_start.log`, `logs/morning.log`, `logs/evening.log` +- 로그: `logs/bot_start.log`, `logs/morning.log`, `logs/midday.log`, `logs/evening.log` --- @@ -65,10 +73,19 @@ ``` 1. python app/ai/morning.py --print (뉴스 크롤링 + KIS 수급 수집) 2. Claude가 데이터 분석 → 시장 분위기/섹터/boosted_tickers 판단 -3. app/ai/daily_context.json 저장 +3. data/daily_context.json 저장 4. Discord로 분석 요약 전송 ``` +### 장중 분석 — `/midday` 슬래시 커맨드 +``` +1. python app/ai/midday.py --print (오전 거래 결과 + 현재 시장 스냅샷 수집) +2. 오전 daily_context 예측 vs 실제 결과 비교 분석 +3. 점심 세션 파라미터 결정 (진입 허용 여부, 포지션 배율, 섹터 업데이트) +4. data/midday_context.json 저장 → 봇이 즉시 감지해 점심 세션 시작 +5. Discord로 장중 분석 전송 +``` + ### 장 후 피드백 — `/evening` 슬래시 커맨드 ``` 1. python app/ai/evening.py --print (오늘 매매 내역 조회) @@ -115,6 +132,7 @@ stockbot_v3/ │ ├── config.py ← 전략 파라미터 (수정 가능) │ ├── ai/ │ │ ├── morning.py ← 장 전 데이터 수집 +│ │ ├── midday.py ← 장중 데이터 수집 │ │ └── evening.py ← 장 후 데이터 수집 │ ├── strategy/ │ │ └── volatility_breakout.py ← 전략 로직 (수정 가능) @@ -131,6 +149,7 @@ stockbot_v3/ ├── scripts/ │ ├── run_bot.ps1 ← 스케줄러용 봇 시작 │ ├── run_morning.ps1 ← 스케줄러용 morning +│ ├── run_midday.ps1 ← 스케줄러용 midday (11:20) │ ├── run_evening.ps1 ← 스케줄러용 evening │ └── setup_scheduler.ps1 ← 스케줄러 전체 재등록 ├── reports/ @@ -139,6 +158,7 @@ stockbot_v3/ ├── data/ │ ├── stockbot.db │ ├── daily_context.json ← 매일 /morning이 갱신, 봇이 08:30에 로드 +│ ├── midday_context.json ← 매일 /midday가 갱신, 봇이 파일 감지 즉시 로드 │ ├── news/ │ └── market/ └── logs/ @@ -188,10 +208,19 @@ stockbot_v3/ |------|------|------|---------| | L1 | 1회 -1.5% | 즉시 손절 | [손절] | | L2 | 일일 -3% | 당일 신규 진입 중단 | [경고] | -| L3 | 3연속 손절 | 당일 매매 중단 | [경고] | +| L3-B | 연속 손절 | 포지션 크기 단계 축소 (전면 중단 없음) | [경고] | | L4 | 주간 -7% | 주말까지 중단 | [경고] | | L5 | 월간 -15% | 전략 폐기 + 재검토 | [긴급] | +**L3-B 포지션 배율** (익절 시 한 단계 회복): + +| 연속 손절 | 포지션 크기 | +|-----------|------------| +| 0회 | 1.0× (정상) | +| 1회 | 0.7× | +| 2회 | 0.5× | +| 3회+ | 0.3× (최소) | + --- ## 실전 전환 조건 (claude_evening 자동 체크) @@ -202,7 +231,7 @@ stockbot_v3/ | 승률 | 최근 30일 > 48% | | MDD | 최근 30일 < -10% | | 샤프지수 | 최근 30일 > 1.0 | -| L3 발동 | 월 2회 이하 | +| L3-B 최소배율(0.3×) 도달 | 월 2회 이하 | 전부 충족 시 → `reports/live_ready/날짜_READY.md` 생성 + Discord 🚀 알림 전환 방법: `.env`에서 `KIS_MOCK=false`, `DRY_RUN=false` 로 변경 diff --git a/app/ai/midday.py b/app/ai/midday.py new file mode 100644 index 0000000..f054e3f --- /dev/null +++ b/app/ai/midday.py @@ -0,0 +1,229 @@ +""" +app/ai/midday.py +장중 데이터 수집 스크립트 (claude_midday 헬퍼) + +Claude Code headless가 이 스크립트를 실행해 장중 스냅샷을 수집한 뒤, +오전 daily_context와 비교 분석해 midday_context.json을 작성한다. + +실행: + python app/ai/midday.py --print +""" + +import asyncio +import json +import logging +import os +import sys +from datetime import datetime +from pathlib import Path + + +def _load_env(): + env_path = Path(".env") + if not env_path.exists(): + return + with open(env_path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + k, _, v = line.partition("=") + k = k.strip() + v = v.strip() + if " #" in v: + v = v[: v.index(" #")] + v = v.strip().strip('"').strip("'") + if k and v and k not in os.environ: + os.environ[k] = v + + +_load_env() + +ROOT = Path(__file__).parent.parent.parent +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +os.makedirs("logs", exist_ok=True) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", + handlers=[ + logging.StreamHandler(sys.stderr), + logging.FileHandler("logs/stockbot.log", encoding="utf-8"), + ], +) +logger = logging.getLogger(__name__) + +from app.execution.kis_client import KISClient +from app.db.models import get_conn + +TODAY = datetime.now().strftime("%Y-%m-%d") +DAILY_CONTEXT_PATH = Path("data/daily_context.json") +MIDDAY_CONTEXT_PATH = Path("data/midday_context.json") + + +# ── DB 조회 ──────────────────────────────────────────────────────────────────── + +def get_today_trades() -> list: + """오늘 체결된 거래 내역 (오전 결과)""" + try: + with get_conn() as conn: + rows = conn.execute( + """SELECT ticker, name, entry_time, exit_time, + entry_price, exit_price, quantity, + exit_reason, pnl + FROM trades + WHERE date=? AND exit_time IS NOT NULL + ORDER BY exit_time""", + (TODAY,), + ).fetchall() + return [ + { + "ticker" : r[0], + "name" : r[1], + "entry_time" : r[2], + "exit_time" : r[3], + "entry_price": r[4], + "exit_price" : r[5], + "qty" : r[6], + "reason" : r[7], + "pnl" : r[8], + } + for r in rows + ] + except Exception as e: + logger.warning(f"거래 내역 조회 실패: {e}") + return [] + + +def get_current_positions() -> list: + """현재 보유 포지션""" + try: + with get_conn() as conn: + rows = conn.execute( + """SELECT ticker, name, entry_time, entry_price, quantity, + target_price, stop_price + FROM positions""", + ).fetchall() + return [ + { + "ticker" : r[0], + "name" : r[1], + "entry_time" : r[2], + "entry_price" : r[3], + "qty" : r[4], + "target_price": r[5], + "stop_price" : r[6], + } + for r in rows + ] + except Exception as e: + logger.warning(f"포지션 조회 실패: {e}") + return [] + + +def get_morning_context() -> dict: + """아침 daily_context.json 로드""" + if not DAILY_CONTEXT_PATH.exists(): + return {} + try: + return json.loads(DAILY_CONTEXT_PATH.read_text(encoding="utf-8")) + except Exception: + return {} + + +# ── KIS 시장 스냅샷 ──────────────────────────────────────────────────────────── + +async def fetch_market_snapshot(kis: KISClient) -> dict: + """거래량 순위 + 업종 동향 현재 시점 스냅샷""" + data: dict = {"volume_rank": [], "sectors": []} + + try: + data["volume_rank"] = await kis.get_volume_rank(top_n=20) + logger.info(f"장중 거래량 순위 {len(data['volume_rank'])}종목") + await asyncio.sleep(1.1) + except Exception as e: + logger.warning(f"거래량 순위 실패: {e}") + + try: + data["sectors"] = await kis.get_sector_trend() + logger.info(f"업종 동향 {len(data['sectors'])}개") + await asyncio.sleep(1.1) + except Exception as e: + logger.warning(f"업종 동향 실패: {e}") + + return data + + +# ── 통계 계산 ────────────────────────────────────────────────────────────────── + +def _calc_session_stats(trades: list) -> dict: + closed = [t for t in trades if t["pnl"] is not None] + wins = [t for t in closed if t["pnl"] > 0] + losses = [t for t in closed if t["pnl"] <= 0] + net = sum(t["pnl"] for t in closed) + + # 현재 연속 손절 수 계산 (역순으로 순회) + consec = 0 + for t in reversed(closed): + if t["pnl"] < 0: + consec += 1 + else: + break + + return { + "total" : len(closed), + "wins" : len(wins), + "losses" : len(losses), + "net_pnl" : round(net, 0), + "win_rate_pct": round(len(wins) / len(closed) * 100, 1) if closed else 0, + "consec_loss" : consec, + } + + +# ── 메인 ────────────────────────────────────────────────────────────────────── + +async def main(print_mode: bool = False): + logger.info(f"장중 데이터 수집 시작 [{TODAY}]") + + # 실거래 API로 KISClient 생성 + _orig = os.environ.get("KIS_MOCK", "true") + os.environ["KIS_MOCK"] = "false" + kis = KISClient() + os.environ["KIS_MOCK"] = _orig + + market: dict = {"volume_rank": [], "sectors": []} + try: + await kis.get_access_token() + market = await fetch_market_snapshot(kis) + except Exception as e: + logger.warning(f"KIS 장중 수집 실패: {e}") + + trades = get_today_trades() + positions = get_current_positions() + morning = get_morning_context() + stats = _calc_session_stats(trades) + + logger.info(f"오전 결과: {stats['total']}건 / 승{stats['wins']} 패{stats['losses']} " + f"/ 연속손절 {stats['consec_loss']}회") + + if print_mode: + print(json.dumps( + { + "date" : TODAY, + "generated_at" : datetime.now().strftime("%H:%M:%S"), + "morning_context" : morning, + "session_stats" : stats, + "trades" : trades, + "current_positions": positions, + "volume_rank" : market["volume_rank"], + "sectors" : market["sectors"], + }, + ensure_ascii=False, + indent=2, + )) + + +if __name__ == "__main__": + print_mode = "--print" in sys.argv + asyncio.run(main(print_mode=print_mode)) diff --git a/app/main.py b/app/main.py index 43a942b..d629de0 100644 --- a/app/main.py +++ b/app/main.py @@ -12,6 +12,7 @@ main.py DRY_RUN=true → 신호만 확인, 주문 전송 안 함 """ +import json import os import sys import asyncio @@ -86,10 +87,58 @@ class StockBot: self.risk = None # RiskManager (잔고 확인 후 초기화) self.running = False + # 장중 컨텍스트 (midday_context.json 갱신 감지용) + self._midday_ctx_mtime : float = 0.0 + self._midday_pos_mult : float = 1.0 # midday position_size_multiplier + self._midday_loaded : bool = False + mode = "모의투자" if self.kis.is_mock else "실거래" dry = " [DRY_RUN]" if os.getenv("DRY_RUN","true")=="true" else "" logger.info(f"StockBot 시작 [{mode}]{dry}") + # ───────────────────────────────────────── + # 장중 컨텍스트 감시 + # ───────────────────────────────────────── + + def _check_midday_context(self): + """midday_context.json 갱신 감지 → 즉시 점심 세션 파라미터 반영""" + path = Path("data/midday_context.json") + if not path.exists(): + return + try: + mtime = path.stat().st_mtime + except OSError: + return + if mtime <= self._midday_ctx_mtime: + return + try: + ctx = json.loads(path.read_text(encoding="utf-8")) + if ctx.get("date") != datetime.now().strftime("%Y-%m-%d"): + return + if not ctx.get("lunch_trade_allowed", True): + logger.warning("midday_context: 점심 세션 진입 중단 설정") + self._midday_pos_mult = float(ctx.get("position_size_multiplier", 1.0)) + # 섹터·블랙리스트 업데이트 + if "hot_sectors" in ctx: + self.strategy.context["hot_sectors"] = ctx["hot_sectors"] + if "avoid_sectors" in ctx: + self.strategy.context["avoid_sectors"] = ctx["avoid_sectors"] + for t in ctx.get("blacklist_tickers", []): + bl = self.strategy.context.setdefault("blacklist_tickers", []) + if t not in bl: + bl.append(t) + # lunch_trade_allowed=false이면 진입 자체를 막는 플래그 저장 + self.strategy.context["lunch_trade_allowed"] = ctx.get("lunch_trade_allowed", True) + self._midday_ctx_mtime = mtime + self._midday_loaded = True + logger.info( + f"midday_context 로드 완료 — 점심 세션 시작 " + f"(포지션 배율: ×{self._midday_pos_mult}, " + f"진입허용: {ctx.get('lunch_trade_allowed', True)})" + ) + except Exception as e: + logger.warning(f"midday_context 로드 실패: {e}") + # ───────────────────────────────────────── # 초기화 # ───────────────────────────────────────── @@ -276,7 +325,7 @@ class StockBot: self.running = False break - # 14:00 이후 신규 진입 중단 (강제청산 50분 전) + # 14:00 이후 신규 진입 중단 if now_str > "14:00": await asyncio.sleep(1) continue @@ -286,12 +335,10 @@ class StockBot: await asyncio.sleep(1) continue - # 점심 (11:30~13:00) 신규 진입 중단 - if "11:30" <= now_str < "13:00": - await asyncio.sleep(1) - continue + # midday_context.json 갱신 감지 (점심 세션 이벤트 기반 시작) + self._check_midday_context() - # 리스크 체크 + # 리스크 체크 (L2/L4/L5 하드 중단) if not self.risk.can_trade(): await asyncio.sleep(5) continue @@ -311,8 +358,16 @@ class StockBot: async def check_entries(self): """유니버스 전체 진입 신호 확인""" - # check_exits 처리 중 14:00을 넘었을 경우 진입 차단 - if datetime.now().strftime("%H:%M") > "14:00": + now_str = datetime.now().strftime("%H:%M") + # 14:00 이후 진입 차단 + if now_str > "14:00": + return + # midday_context 로드 전(11:20~) 11:00 이후 신규 진입 일시 중단 + # — midday_context.json이 생성되면 _check_midday_context()가 자동 해제 + if now_str >= "11:00" and not self._midday_loaded: + return + # lunch_trade_allowed=false이면 점심 세션 진입 차단 + if self._midday_loaded and not self.strategy.context.get("lunch_trade_allowed", True): return for ticker in self.universe: if ticker in self.positions: @@ -341,7 +396,13 @@ class StockBot: balance = await self.kis.get_balance() cash = balance["cash"] - invest = self.risk.get_pos_size(cash, signal.get("multiplier", 1.0)) + # AI 신호 배율 × B안(연속 손절) 배율 × midday 배율 + combined_mult = ( + signal.get("multiplier", 1.0) + * self.risk.get_consec_multiplier() + * self._midday_pos_mult + ) + invest = self.risk.get_pos_size(cash, combined_mult) qty = max(1, int(invest // current)) result = await self.executor.buy( @@ -435,6 +496,16 @@ class StockBot: self.risk.record_trade(pnl) + # B안: 연속 손절 2회·3회 도달 시 Discord 알림 + if reason == "SL": + consec = self.risk.consec_loss + if consec in (2, 3): + mult = self.risk.get_consec_multiplier() + await notify_risk( + "L3-B", + f"{consec}연속 손절 — 포지션 크기 {int(mult * 100)}%로 축소" + ) + if reason == "TP1": pos["tp1_done"] = True pos["qty"] -= qty diff --git a/app/risk/manager.py b/app/risk/manager.py index c3a5516..ed4ae77 100644 --- a/app/risk/manager.py +++ b/app/risk/manager.py @@ -36,6 +36,14 @@ class RiskManager: # ── 손실 기록 ── + # B안: 연속 손절 수 → 포지션 크기 배율 + _CONSEC_MULT = {0: 1.0, 1: 0.7, 2: 0.5} + _CONSEC_MIN = 0.3 # 3회 이상 최소값 + + def get_consec_multiplier(self) -> float: + """연속 손절 수에 따른 포지션 크기 배율 (B안)""" + return self._CONSEC_MULT.get(self.consec_loss, self._CONSEC_MIN) + def record_trade(self, pnl: float): """매매 결과 기록 및 손실 한도 체크""" self.daily_pnl += pnl @@ -45,20 +53,17 @@ class RiskManager: if pnl < 0: self.consec_loss += 1 else: - self.consec_loss = 0 + # 익절 시 한 단계만 회복 (0으로 리셋 아님) + self.consec_loss = max(0, self.consec_loss - 1) self._check_limits() def _check_limits(self): - """L1~L5 손실 한도 체크""" + """L2/L4/L5 손실 한도 체크 (L3는 B안 포지션 축소로 대체)""" # L2: 일일 누적 손실 -3% if self.daily_pnl / self.init_cash < -DAILY_SL_PCT: self._stop("L2", f"일일 손실 {self.daily_pnl/self.init_cash*100:.1f}% 도달") - # L3: 연속 손절 3회 - if self.consec_loss >= CONSEC_LOSS: - self._stop("L3", f"{CONSEC_LOSS}연속 손절 발생") - # L4: 주간 누적 -7% if self.weekly_pnl / self.init_cash < -0.07: self._stop("L4", f"주간 손실 {self.weekly_pnl/self.init_cash*100:.1f}%") diff --git a/scripts/run_midday.ps1 b/scripts/run_midday.ps1 new file mode 100644 index 0000000..bff0d3a --- /dev/null +++ b/scripts/run_midday.ps1 @@ -0,0 +1,22 @@ +# claude_midday 실행 스크립트 +# 작업 스케줄러에서 11:20에 실행 (평일) + +$OutputEncoding = [System.Text.Encoding]::UTF8 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +$env:PYTHONIOENCODING = "utf-8" + +$PROJECT = Split-Path -Parent $PSScriptRoot +$LOG = "$PROJECT\logs\midday.log" +$CLAUDE = "C:\Users\whdwo\AppData\Roaming\npm\claude.cmd" +$utf8 = New-Object System.Text.UTF8Encoding $false + +Set-Location $PROJECT + +$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" +[System.IO.File]::AppendAllText($LOG, "[$timestamp] claude_midday 시작`n", $utf8) + +& $CLAUDE -p "/midday" --dangerously-skip-permissions 2>&1 | + ForEach-Object { [System.IO.File]::AppendAllText($LOG, "$_`n", $utf8) } + +$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" +[System.IO.File]::AppendAllText($LOG, "[$timestamp] claude_midday 완료`n", $utf8) diff --git a/scripts/setup_scheduler.ps1 b/scripts/setup_scheduler.ps1 index 0ba1ad5..c0c4a43 100644 --- a/scripts/setup_scheduler.ps1 +++ b/scripts/setup_scheduler.ps1 @@ -21,13 +21,16 @@ function Register-StockTask($name, $time, $script, $limitMin) { Write-Host "[OK] $name 등록 완료 (평일 $time)" -ForegroundColor Green } -# ── 3개 태스크 등록 ────────────────────────────────────────────────────────── +# ── 4개 태스크 등록 ────────────────────────────────────────────────────────── # 07:55 claude /start-bot → Python 봇 백그라운드 시작 Register-StockTask "StockBot_Bot" "07:55" "run_bot.ps1" 10 # 08:15 claude /morning → 뉴스+KIS 수집 → daily_context.json Register-StockTask "StockBot_Morning" "08:15" "run_morning.ps1" 20 +# 11:20 claude /midday → 장중 스냅샷 → midday_context.json → 점심 세션 시작 +Register-StockTask "StockBot_Midday" "11:20" "run_midday.ps1" 20 + # 15:30 claude /evening → 결과 분석 → 리포트 → Discord Register-StockTask "StockBot_Evening" "15:30" "run_evening.ps1" 30