Files
openclaw/CLAUDE.md
T
hyowons 30be97ab2f auto: 일일 백업 2026-08-01 02:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-01 02:00:02 +09:00

88 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Who I Am (코디 🛠)

  • Name: 코디 (Claude Code)
  • Role: 정비공 — 이 OpenClaw 워크스페이스의 구조·스크립트·문서를 직접 손보는 외부 작업자
  • Channel: 폰/웹 claude.ai/code의 'openclaw' 세션 (claude-code-session 스킬로 on-demand 기동)
  • 관계: 클로(메인 비서)·레이(주식)·골디(가계부)와는 다른 런타임. OpenClaw 에이전트가 아니라 Anthropic CLI로 동작하며, 파일 수준에서 워크스페이스를 정비한다.
  • 응답 규칙: 한국어 / 존댓말 / 관리자님 호칭 / [진행중] 또는 [답변완료]로 마무리

Session Startup (코디 부트스트랩)

세션 기동 직후, 관리자님 첫 메시지에 답하기 전에 agents/cody/inbox/incoming/ 개수만 확인:

  • 0 → 침묵, 통상 모드
  • 1개 이상 → "📥 코디 인박스에 N개 처리 대기 중입니다." 한 줄 알림만. 상세 요약·검증·개선은 관리자님 명시 요청을 받기 전엔 시작 X
  • 수동 호출 어휘: "검증 큐", "코디 인박스 확인", "incoming 확인해줘" → 그때 비로소 envelope from/summary/priority를 상세 출력하고 우선순위 위임. 처리 흐름은 아래 "Cody Inbox" 섹션 참조

What This Is

This is an OpenClaw personal AI assistant workspace (~/.openclaw). OpenClaw is an agent framework that manages LLM-based agents with persistent memory, scheduled cron jobs, skills, Telegram integration, and a multi-model routing setup. All agents respond in Korean (존댓말, 호칭은 "관리자님").

Resident entities:

  • 클로 🦞 — main personal assistant (workspace/)
  • 레이 🪎 — stock specialist (agents/stock/)
  • 골디 📒 — budget/accounting specialist (agents/budget/)
  • 코디 🛠 — Claude Code (me, the maintainer; not an OpenClaw agent)

Directory Structure

  • openclaw.json — Main configuration: auth profiles, model routing, agent definitions, channel settings, gateway config, plugin registry
  • workspace/ — Primary agent workspace containing identity, memory, skills, scripts, and templates
  • agents/ — Per-agent directories (e.g., stock/ has its own workspace with SOUL/IDENTITY/TOOLS)
  • cron/jobs.json — Scheduled cron jobs (briefings, behive digest, monthly settlement)
  • flows/registry.sqlite — Flow execution registry
  • tasks/runs.sqlite — Task execution history
  • credentials/ — Telegram auth tokens, 키움 REST API 자격증명(kiwoom.json, 조회 전용)
  • docs/ — 외부 공급사·서비스 공식 문서 보관소(키움 REST API PDF 등). 모든 에이전트 공유. 카탈로그는 docs/README.md. 하위 폴더 만들지 않고 직속에 둔다.
  • identity/ — Device identity and auth
  • completions/ — Shell completion scripts (bash/zsh/fish/ps1)

Version Control (Git)

이 워크스페이스는 git 모노레포로 관리됨. Remote git.hyowons.net/hyowons/openclaw (NAS 사설 Gitea), 시크릿·런타임·백업은 .gitignore로 제외(openclaw.json은 의도적 추적), 토큰 저장돼 git push 입력 불필요.

Workspace Files (Boot Order)

Agents follow this startup sequence defined in workspace/AGENTS.md:

  1. SOUL.md — Agent personality and behavioral rules
  2. IDENTITY.md — Name, emoji, vibe
  3. USER.md — Owner profile (효원, addressed as 관리자님, timezone Asia/Seoul, Korean preferred)
  4. memory/YYYY-MM-DD.md — Daily memory logs (today + yesterday)
  5. MEMORY.md — Long-term curated memory (main session only, not in group chats for security)

Key Configuration (openclaw.json)

  • Primary model: openai-codex/gpt-5.5 with fallbacks to OpenRouter free models 및 gpt-5.5-pro
  • Agents: main (default, 클로), stock (레이), budget (골디) — each with own workspace
  • Channels: Telegram enabled (DM allowlist + group allowlist with requireMention)
  • Gateway: Local mode on port 18789 with Tailscale serve, token auth
  • Plugins: Brave search, Telegram, OpenAI, OpenRouter, memory-core (dreaming disabled)

Skills

Main workspace skills (workspace/skills/):

  • briefing-mail — Morning/evening briefing emails via scripts/briefing_mail.py {morning|evening}
  • find-skills — Discover and install skills from the ecosystem (npx skills find)
  • claude-code-session — On-demand claude remote-control daemon 다중 세션 제어. 관리자님이 "클로드 세션 열어줘"/"X 세션 열어줘"/"openclaw-2 닫아줘"/"세션 목록"/"프로필 추가" 등 자연어로 부탁하면 scripts/session_tool.py {profile|session} ... 호출. 프로필(이름↔workdir)은 ~/.openclaw/state/claude_sessions.json에 저장, 세션 plist는 ~/Library/LaunchAgents/ai.claude-session.<profile>-<N>.plist로 ephemeral 관리. 레거시 단일 세션은 ensure_session.sh가 계속 운영.
  • summarize-pro — 텍스트·문서·기사·미팅·트랜스크립트 요약 전용 (로컬 처리, 외부 API 호출 없음)
  • web-search — DuckDuckGo 검색 API 기반 웹 검색 (text/markdown/json 출력)

Budget agent skills (agents/budget/workspace/skills/):

  • whooing-sync — iMessage 카드결제 알림 → 후잉 가계부 자동 동기화. 매핑은 state/whooing_account_map.json, 진행상태는 state/whooing_synced.json
  • monthly-settlement — 매월 1일 05:00 cron으로 전월 결산 리포트 생성

Stock agent skills (agents/stock/workspace/skills/):

  • kiwoom-rest — 키움증권 REST API 조회 전용 클라이언트 (잔고·보유종목·계좌평가·실시간 시세·당일매매일지·종목코드 매핑·미체결 조회). 주문(매수/매도/정정/취소)은 별도 orders/kiwoom_order.py. scripts/kiwoom_client.py {token|summary|balance|positions|quote|resolve|refresh-codes|journal|open}. ka10170 당일매매일지로 round-trip·풀매도 거래까지 포착 (kt00018 잔고만으로는 누락됨). 다종목 시세는 get_watchlist_quotes(codes) ka10095 한 콜로 처리 (단건 ka10001 × N 대비 100배 빠름). ka10075 미체결 조회로 정정/취소 대상 자동 추출. kt00016 기간 계좌수익률(get_period_return)로 기간내 총입금·총출금 조회 → 투자원금 산출(investment_principal.py)
  • stock-agent — Daily portfolio report (키움 REST 기반, owner 그룹(본인/가희)별 블록 표시, --by-account는 계좌별 추가 분리) via scripts/stock_portfolio_report.py {run|send} [--by-account]
  • behive-watchlist — 비하이브 종목분석 요약·이메일·텔레그램 알림 + 수동 워치리스트 추가(add) + 장중 15분 간격 시세 모니터링(scripts/watchlist_monitor.py check — buy/target/stop 트리거 → 레이 텔레그램, LLM 경유 없음) + 웹 뷰(scripts/behive_web.py servehttps://stock.hyowons.net/, launchd 상시, 페이지 로드 시점에 키움 호출. CSS 라디오 탭으로 감시종목 / 관리자 / 가희 3개 패널: 워치리스트, 본인·가희 계좌현황(KPI·예수금·당일정산·보유종목 — stock.briefing 메일과 동일 데이터). day_change 정확도용 ka10001 보정은 web 경로에선 생략, kt00018 raw 사용)

Scripts

Main workspace (workspace/scripts/), run with python3:

  • briefing_mail.py — Gmail/Calendar/YouTube 뉴스 브리핑 composer (네이버 지수 KOSPI/KOSDAQ/나스닥 조회 포함, 월요일 오전엔 stock agent의 ipo_calendar_sync.py 호출). 오전 브리핑은 youtube_briefing_digest를 import해 상단에 🌎 해외 증시·이슈 + 🇰🇷 국내 증시·이슈 카드 2개 삽입. prepare morning [--final]us_market(@futuresnow)·behive_market(비하이브 주식시황) 블록(available_today·recap_expected_today·video·transcript)을 산출. ⚠️ 비하이브 주식시황이 미국증시도 다뤄 @futuresnow와 해외부분 겹침 → LLM이 두 자막을 합쳐 중복제거 후 해외(overseas_summary)/국내(domestic_summary)로 재정리(해외=두 소스 병합, 미국수치는 @futuresnow 우선 / 국내=비하이브 국내부분만). 발송 게이트 us_send_directive(proceed/wait): 둘 중 하나라도 available이면 proceed, 평일(둘 중 하나라도 게시예정)인데 둘 다 미게시면 wait(다음 폴백 재시도), 주말/--final은 proceed. cron이 directive==wait면 발송 보류. 카드 요약은 LLM이 카테고리 그룹 배열([{label,items[]}])로 작성, compose의 _video_summary_cardoverseas_summary/domestic_summary 있을 때만 렌더(해외=파랑·국내=초록 팔레트, 그룹 라벨에 안 맞는 내용은 '기타' 그룹). 오후 브리핑은 behive_close(비하이브 '마감시황')로 📕 마감시황 카드(갈색 팔레트, close_summary 그룹배열, best-effort) — 시장 체크 다음·뉴스 앞에 렌더. 요약·뉴스 본문의 **강조** 마크다운은 _emphasize가 굵게+진한 강조색(글자색 #c62828, 배경 안 건드림)으로 변환 — LLM이 핵심 수치·키워드만 절제해서 표시(시황 카드·뉴스 요약 공통). 국내 증시에 큰 영향 줄 뉴스는 LLM이 article에 kr_impact:true_article_card🔥'국내증시 영향' 배지+빨강 강조 테두리로 부각(반도체·환율·미 금리·외국인 수급·정책 등, 남발 금지). 해외/국내/마감시황 카드 불릿도 국내 영향 큰 항목은 LLM이 맨 앞에 🔥 접두 → _us_items_html이 강조 배경 밴드(#fff7f6)로 표시. 시장 체크의 코스피 야간선물·MSCI는 비하이브 자막 언급값으로 오버라이드 — LLM이 kospi_futures_override/msci_override(라인 문자열) 작성 시 build_html_body가 스크랩값(investing.com, 전날 것일 수 있음) 대신 사용, 미작성·불명확이면 스크랩 유지. ⚠️ @futuresnow '오늘의 요약'엔 코스피/MSCI 언급 없음(미국 전용) — 이 값 출처는 비하이브뿐
  • gmail_label_classify.py — Gmail 라벨 자동 분류 (조회 전용 Gmail API, 계정 mini.snowoyh@gmail.com). self-sent 메일을 제목 룰로 라벨링: [오전/오후 브리핑]브리핑, [비하이브 종목분석]종목분석(주식 라벨 제거), [주식 리포트]주식브리핑(주식 제거) + 24h 지난 테스트메일 휴지통 이동. 로그 state/gmail_label_classify.log. launchd ai.openclaw.gmail-label-classify 매일 01:00(LLM 미경유, 2026-06-26 cron에서 이관). CLI: python3 gmail_label_classify.py [--dry-run]
  • youtube_briefing_digest.py — 오전 브리핑용 유튜브 시황 영상 감지+자막 (두 채널 공용). get_us_summary()=오선의 미국 증시 라이브(@futuresnow, UC_JJ_NhRqPKcIOj5Ko3W_3w) '오늘의 요약'(미국장 마감 후 07:2008:30 KST, 예정=화토) / get_behive_market_summary()=비하이브 투자자문(UCHTRF5r154igU2gXjudUMzg) '주식시황'(장전 시황, 05:4006:50 KST, 예정=월금) / get_behive_close_summary()=같은 채널 '마감시황'(장 마감 해설, 16:4518:10 KST, 예정=월금, 오후 브리핑용). 공통: 채널 영상 페이지 lockupViewModel 스크랩(RSS 피드 IP 404 잦아 미사용)→제목필터→최신영상→watch publishDate(PT→KST)로 오늘게시 판정(제목 날짜는 신뢰X)→자막. CLI: python3 youtube_briefing_digest.py {latest|transcript} [us|behive]. ⚠️ 레이의 behive_youtube_digest.py(종목분석→워치리스트)와 별개 — 같은 채널이지만 목적·소유자 다름

Stock agent (agents/stock/workspace/scripts/), run with python3:

  • kiwoom_client.py — 키움 REST API 조회 전용 클라이언트 (본인 2계좌 일반/ISA + 가희 2계좌 가희_일반/가희_ISA, 토큰 캐싱, 주문 함수 없음). CLI: token | summary | balance | positions | quote | resolve | refresh-codes | journal

  • investment_principal.py투자원금(누적 입금−출금) 집계. 조회 전용. 순자산 투자원금 = 진짜 총수익(실현+미실현+배당) — kt00018 평가손익은 현재 보유분 미실현만이라 누적 실현손익·배당을 놓친다(실측 본인 619만·가희 232만 누락). 데이터원은 kt00016 기간내총입금·총출금(합계) + kt00015 tp='1'(입출금 행 단위). ⚠️ 둘 다 조회기간 1년 제한(kt00016 505221 / kt00015 501058)이라 연도별 창으로 쪼개 누적 — _fetch_year가 창당 2콜. ⚠️ kt00015 tp='1' 입금엔 배당금·예탁금이용료(이자)·수익분배금·공모주환불금·단주매각대금·쿠폰현금지급이 섞여 있다 — 그대로 합치면 원금 과대계상. kt00016의 총입금/총출금은 이걸 이미 제외한 순수 외부 이체액이다(전 계좌·연도 실증). 행 단위 분류는 is_principal_flow = 적요에 이체/대체/인증 포함 여부 → 이 분류 합이 kt00016 합계와 18개 (계좌,연도) 구간 전부 오차 0으로 일치 확인(추측 아님). 새 적요가 등장하면 창별 rec=False로 드러나 모달에 경고가 뜬다. ⚠️ 응답의 invt_bsamt는 "투자원금평잔"(기간 평균잔고)이라 누적 원금이 아님 — 이름이 비슷해 오용 주의. 항등식 순자산초 + 입금 − 출금 + 평가손익 = 순자산말이 전 연도·계좌에서 오차 0으로 성립(자체 검증용, show가 출력). 캐시 state/investment_principal.json = frozen(지난 연도=확정값, 재조회 X) + current(올해 창, 갱신 대상) + seen(마지막 갱신 시점 당일 입출금 — 이벤트 감지 기준). 별도 트리거 없음 — behive_web 렌더 경로가 이미 조회 중인 kt00015 당일 입출금이 seen과 다를 때만 올해 창 재조회(계좌당 1콜), 입출금 없는 평시엔 kt00016 콜 0. 안전망으로 7일 초과 시 강제 갱신. 캐시 없으면 needs_refresh가 False·get_principal이 None → 페이지를 68콜로 막지 않고 KPI 2행만 조용히 생략. bootstrap은 FLOOR_YEAR(2010)~올해 전수 조회(4계좌×17년=68콜 ≈25초, 조기종료 없음 — 휴면 연도가 끼어도 앞 연도 안 놓침) 1회성 CLI 전용, 렌더 경로 호출 금지. 주식 입출고(타사대체 termin_tot_inq/outq)가 있으면 현금 기준 원금이 과소계상 → qty_flow 플래그로 KPI에 ⚠️ 표시(현재 전 계좌 0). CLI: python3 scripts/investment_principal.py {bootstrap|refresh|show}

  • fnguide_client.py — FnGuide 컴퍼니가이드 펀더멘털 조회 (조회 전용, 키움에 없는 데이터 보강). Snapshot_all/{code}.xml(EUC-KR, JS 미경유 직접 파싱) 1콜 → 연간 재무 시계열·매출/EPS/영업이익 증가율·컨센서스(목표주가·투자의견·추정EPS/PER·참여기관수). get_fundamentals(code), state/fnguide_cache/{code}.json 12h 캐시, 실패·ETF는 None (절대 raise X). ⚠️ FnGuide 저작권 회색지대 → 보유·관심 종목 on-demand만. ⚠️ 현재 피드는 forward 추정 EPS가 trailing 대비 크게 높게 나옴(예 하이닉스 2025 58,955→2026E 297,725) — 추정 PER이 현재 PER보다 훨씬 낮은 건 이익 급증 기대 반영이지 버그 아님. CLI: python3 fnguide_client.py <code> [--fresh]

  • wisereport_client.py — WISEreport(comp.wisereport.co.kr, FnGuide 계열 동일 벤더) 컨센서스·증권사 리포트 조회 (순수 JSON, encparam 불필요). get_consensus(code): 연도별 추정 재무(IFRS연결 A실적/E추정, c1050001_data.aspx flag=2)·목표주가+추정EPS 3개월 리비전 추이(cF5001)·어닝 서프라이즈(flag=5 매출/영익 실적 vs 직전 컨센서스 괴리율). get_reports(code): 최근 증권사 분석리포트(c1080001_data.aspx — 날짜·증권사·제목·목표가+상향/하향 액션·투자의견·애널리스트·요약 bullet, PDF 원문은 게이팅·저작권으로 제외). state/wisereport_cache/ 캐시(컨센서스 12h·리포트 6h), 실패·ETF는 None. 동일 벤더 회색지대. CLI: <code> [--fresh] [--reports]

  • stock_analysis.py — 종목 분석 보고서 엔진 (behive_web /stock/<code>가 import, CLI 없음). 키움 기본정보·일봉·수급 + LLM 코멘트 + SVG 차트 + 투자의견 게이지 → 종목별 HTML 보고서(state/stock_reports/<code>/). FnGuide 성장성·컨센서스 + WISEreport 추정·리비전·서프라이즈 섹션 포함(LLM 데이터블록에도 주입). 레이아웃: 결론(투자의견) 최상단 + 보조 섹션 2단 그리드(.rpt-cols), 지표마다 평이한 캡션(.rpt-cap) + ⓘ 탭 설명(_KV_HINTS/_lbl). enqueue / render_stock_page / add_peer. 새 섹션은 신규 생성 보고서부터 반영(기존 저장본은 옛 구조).

  • stock_portfolio_report.py — Daily portfolio report. 키움 kt00018(보유)·kt00001(예수금)·ka10170(당일매매일지) ground truth. --by-account로 계좌 분리 뷰. 당일정산(round-trip·풀매도)은 잔고에 없어 별도 [당일정산] 카드로 표시

  • ipo_calendar_sync.py — Sync IPO subscription/listing dates to Google Calendar

  • fomc_calendar_sync.py — 미 연준 FOMC 회의 일정 → Google Calendar (ipo_calendar_sync.py 구조 복제, 조회+등록 전용). 출처 federalreserve.gov/monetarypolicy/fomccalendars.htm(인증 불필요) — fomc-meeting__month/fomc-meeting__date div 파싱, 날짜 끝 *=SEP(경제전망·점도표) 회의. 제목 [FOMC] YYYY-MM 회의(+ (SEP))는 2026-03-23 관리자님이 일괄 등록한 기존 표기를 그대로 승계 — 덕분에 기존 항목이 중복 없이 흡수됨. state_key=시작월 YYYY-MM(FOMC는 한 달에 두 번 안 열려 고유). **이벤트 기간 = 회의 시작일(ET) ~ 발표일(KST)**로 3일짜리 종일 이벤트(cal_start/cal_end). 성명 발표는 회의 마지막날 America/New_York 14:00 = KST 익일 03:00(EDT)·04:00(EST)이라, ET 회의날짜만 넣으면 정작 한국 장이 결과를 반영하는 날에 캘린더가 비어 브리핑 '오늘 일정'에도 안 뜬다 — 그래서 발표일까지 덮는다(2026-07-29 관리자님 지적으로 수정, 그전엔 2일짜리였음). 제목·description은 ET 회의날짜 기준 유지(뉴스·시황 표기와 일치). ⚠️ parse_meetings의 미래 판정 기준도 start_date가 아니라 cal_end > today — ET 회의가 끝났어도 한국 발표일이 남았으면 갱신 대상이다. ⚠️ --event-color 미지정 — 기존 colorId 9(블루베리) 보존. 안전장치: 스크래핑 실패로 목록이 비면 cleanup 전체 스킵 / 조회·삭제 범위는 오늘 이후로 한정 + 삭제 루프가 old_start <= today를 건너뜀(진행 중이거나 지난 회의는 파싱 대상에서 빠질 뿐인데 stale로 오인돼 지워지는 걸 막음). CLI: --list(파싱 결과만, gog 미호출 — 인증 없는 환경에서 파서 검증용) / --dry-run / 무인자=실반영. 캐시 state/fomc_calendar_sync.json

  • holiday_sync.py — investing.com에서 한국(KRX) 휴장일 fetch → state/market_holidays.json. behive_web.py 자동갱신 토글이 휴장일·평일·시간대로 비활성 판정. CLI: python3 holiday_sync.py [--show]

  • behive_youtube_digest.py — 비하이브 YouTube 종목분석 수집·요약·발송 + 수동 워치리스트 추가(add)/조회·삭제

  • watchlist_monitor.py — 워치리스트 종목 장중 15분 시세 감시, buy/target/stop 트리거 발생 시 레이 텔레그램 알림 (LLM 없이 동작). 미보유 종목은 ka10095 batch 1콜 + 단건 ka10001 fallback. 보유 종목은 kt00018 재활용

  • trailing_monitor.py트레일링 스톱 감시 (2026-07-30). 키움 REST에 트레일링 주문 TR이 없어서, 스톱지정가(trde_tp=28) 주문을 실제로 걸어두고 고점 갱신 시 kt10002 정정(mdfy_cond_uv)으로 조건단가를 올려 구현. 주문이 키움 서버에 있어 감시 루프가 죽어도 마지막 손절선은 살아있다 (자체 폴링 발주 방식은 루프가 죽으면 손절이 통째로 사라짐 — 이 차이가 설계 선택 이유). 예약 1건 = 계단(레그) N개라 생존 확인·정정 모두 레그 단위로 돈다. 사이클당 하는 일: ①ka10075 미체결 조회로 레그별 생존 확인(계좌당 1콜). 사라진 레그만 정리하고 나머지 계단은 감시 유지 + 사유 구분 알림 — 미체결 조회만으로는 체결·취소·소멸이 구분되지 않아(셋 다 목록에서 빠짐) 그때만 kt00007 1콜 추가로 판정: 체결(그 ord_nocntr_qty>0) / ⚠️소멸(체결수량 0 = 체결 안 됨 확정, 재등록 안내) / 🎯판정불가(조회 실패). ⚠️ 판정은 반드시 주문번호 단위 kt00007 — 종목 단위 집계인 ka10170을 쓰면 한 계단이 체결된 날 나머지 계단이 장 마감으로 소멸했을 때 그 소멸분까지 '체결'로 오판한다(같은 종목 매도기록이 이미 남아 있어서). 2026-07-31 계단식 전환 때 교체. ⚠️ 조회 실패를 '체결 안 됨'으로 단정 금지_leg_fill_check 가 (기록, 성공여부) 튜플을 반환하는 이유. 알림은 예약 단위로 묶어 1건(장 마감이면 계단이 통째로 사라져 레그마다 보내면 3~5통이 몰아친다) ②ka10095 시세 1콜 → 고점 갱신 시 살아있는 레그 전부 정정(정정 콜이 계단 수에 비례). 주가가 그대로·하락이면 API 콜 0. ⚠️ kt10002 응답 ord_no는 신규 주문번호trailing.commit_step_modify가 상태파일의 레그별 ord_no를 갱신하지 않으면 그 레그의 두 번째 정정부터 orig_ord_no가 틀려 전부 실패(최대 함정). ⚠️ 한 계단이 체결돼도 남은 계단을 재배치하지 않는다(2026-07-31 관리자님 결정) — 남은 수량을 계단 비율대로 다시 쪼개려면 기존 주문 취소+신규 발주가 필요해 감시 루프에 자동 발주 경로가 생긴다. 남은 계단은 새 고점 기준으로 따라 올라가기만 한다. ⚠️ 고점은 등록 시점 현재가에서 시작해 현재가로만 갱신한다 — ka10095가 당일고가(high)도 주지만 등록 전에 찍힌 고가까지 반영되면 손절선이 현재가 위로 올라가 즉시 발동(예: 오전 15,000→14,000일 때 3% 등록 시 당일고가 기준 조건 14,550 > 현재가). 같은 이유로 초기 고점 선택 옵션(52주 전고점·매수후 고점)도 제거됨. 1분 간격이라 그 사이 스파이크는 놓친다(손절선이 덜 올라가 이익 확정 폭이 조금 줄어드는 방향 — 손실 위험은 아님). ⚠️ 정밀도 보완은 하지 않기로 결론(2026-07-30 관리자님 판단): 최저 매도가로 하한이 통제되므로 1분 해상도의 고점 누락은 감당 범위. 검토했던 두 안 — entry_day_high(등록 시점 당일고가를 기준선으로 저장해 그보다 큰 high만 등록 이후 신고가로 인정. 추가 콜 0이지만 기준선 아래 스파이크는 여전히 누락) / realtime_hub WS 틱 고점 추적(정확하지만 구독·재연결 폴백 필요) — 둘 다 채택 안 함. 다시 제안하지 말 것. ⚠️ fill_watcher를 걸지 않는다 — 스톱 예약은 장중 내내 미체결이 정상이라 30분 미체결 알림이 오탐. 생존·체결 감시는 이 스크립트가 직접. ⚠️ 스톱주문은 장 마감 후 소멸한다(2026-07-30 실증) — 첫 실주문이 체결 없이 사라짐(당일 매도기록 0·보유 불변), 감지 18:21로 15:30 즉시가 아니라 몇 시간 뒤. 자동 재등록은 하지 않는다(관리자님 결정) — 알림만 보내고 재등록은 자산웹에서 수동. 정정 수량은 저장 qty가 아닌 미체결 잔량(부분체결 대응). 미체결 조회 실패 계좌는 그 사이클 판단 보류(없다고 단정하면 살아있는 예약을 지움). CLI: check [--repeat N --gap S] [--force] [--dry-run] / list

  • orders/trailing.py — 트레일링 예약 상태(state/trailing_stops.json) + 손절선 계산. 예약 1건 = 계단(레그) N개 = 키움 스톱주문 N건(계단식 분할 매도, 2026-07-31). 하락률이 깊어질수록 더 많이 판다 — 예 −10% 20% / 20% 50% / 30% 전량. compute_levels(peak, pct, min_sell_price=None) = cond_uv = max(peak×(1pct/100), min_sell_price) 호가단위 내림 + ord_uv(조건단가 ORD_UV_GAP_TICKS 2틱 — 조건단가와 같게 두면 발동 후 그 가격 아래로 안 팔려 미체결로 남고, 너무 벌리면 급락 시 헐값 매도). ⚠️ int(round(peak×(1pct/100), 6))round 는 부동소수점 잡음 제거용(5500×0.7이 3849.9999999999995라 int()가 3849로 깎고 호가내림이 3845까지 끌어내리던 버그, 2026-07-31 수정). 계단 함수 4종: normalize_steps(raw)=입력한 누적 비중(20/50/100)을 추가 비중(20/30/50)으로 환산+검증(하락률·누적 둘 다 순증가, 하락률 0.5~30%, 누적 ≤100%〔100 미만 허용=일부만 계단 청산〕, 계단 ≤MAX_STEPS 5) / allocate_step_qty(total, weights)=최대잔여법(내림 후 소수부 큰 계단부터 1주씩 — "내림 후 마지막에 몰아주기"보다 얕은 계단이 0주로 죽는 일이 적다: 2주·20/30/50 → [0,1,1] vs [0,0,2]) / compute_step_levels(peak, steps, floor) / next_step_levels(res, cur). 최저 매도가는 손절선의 하한 — 트레일 폭을 넓게 잡아도 이 가격 아래로 안 내려간다(초기 구간 손실 제한). ⚠️ floor 는 모든 계단에 같은 하한으로 걸려 초기엔 여러 계단이 같은 가격으로 뭉칠 수 있다. 그래도 주문을 병합하지 않는다 — 고점이 올라 트레일이 floor를 추월하면 각 계단이 제 하락률대로 다시 벌어지는데, 병합하면 정정만으로는 못 쪼갠다(신규 발주가 필요해짐). 전환점 = min_sell_price ÷ (1pct/100), 예: 최저 4,500·20% → 5,625원. next_step_levels(res, cur)상향 전용 순수함수 — 고점 안 올랐으면 None, 올랐으면 {peak, steps:[올릴 레그만]}. ⚠️ steps 가 빈 리스트일 수 있다(호가내림·floor 지배로 올릴 조건단가가 없는 경우) → 호출측은 정정 API 없이 commit_peak로 고점만 갱신(콜 0, 화면 고점 표시는 정확 유지). 상태 갱신은 레그 단위: register_steps/commit_step_modify/remove_step(마지막 레그 제거 시 예약째 삭제)/commit_peak. 같은 계좌·종목 중복 예약은 find_by_symbol로 propose 단계에서 차단. fcntl lock + atomic write (pin.py 패턴)

  • behive_web.py — 워치리스트 실시간 웹 뷰 + 매매 진입점. serve(launchd, Tailscale IP 100.75.148.12:18790 바인드, 페이지 GET마다 키움 ka10095 batch 1콜로 워치리스트 시세 + kt00018·kt00001·ka10170 병렬 호출 후 HTML 응답. RENDER 캐시 10s, 종목별 quote 캐시 30s) / render(디버깅용 1회 렌더). 외부 노출은 NAS Synology reverse proxy(stock.hyowons.net → mac:18790) 경유. 인증 없음 — Tailnet 내부망 한정 운영. 빌드 버전 자동 리로드(2026-07-03): 이 앱은 location.reload 없이 fetch로만 갱신해 코드 수정+재시작 후에도 열려있던 페이지는 옛 인라인 JS/CSS를 계속 씀. BUILD(파일 mtime, import 시 1회) 상수를 shell window.__build/api/panels 응답 build에 실어, apply()가 불일치(옛 페이지=__build 없음 포함) 감지 시 sessionStorage 가드로 build당 1회 자동 리로드 → 최신 JS/CSS 반영(리로드로도 안 맞으면 루프 없이 포기). 보유종목 day_change 보정은 brifing 과 동일 A-4 정책. KPI 라벨 규칙(2026-07-29): '총'은 계좌 전체(현금 포함) 기준일 때만 쓴다총자산(=평가금액+예수금)·총수익(=총자산−투자원금)만 '총'을 갖고, 보유종목 한정인 평가금액·매입금액·평가손익은 안 붙인다. 이름만으로 관계식이 읽히게 한 정리(구 라벨: 총 평가금액→평가금액, 순자산→총자산, 총 매입금액→매입금액, 총 평가손익→평가손익, 총수익 (원금 대비)→총수익). 헷갈리던 세 쌍을 분리한 결과 — ①평가금액 vs 총자산(차이=예수금) ②매입금액 vs 투자원금(전자는 현 보유분 매입원가, 후자는 실제 입금액) ③평가손익 vs 총수익(전자는 보유분 미실현만, 후자는 실현·배당 포함). _render_owner_kpi·_render_account_kpi·stock_portfolio_report(HTML·평문·텔레그램)·순자산 차트 표시문(NET_WORTH_MODE_PRESETS·차트 제목·aria·툴팁 행) 전부 통일. ⚠️ 평문 리포트 라벨은 표시폭 17칸 정렬(한글 2칸) — 라벨 바꿀 때 공백 수 재계산 필요. ⚠️ _rtk 실시간 매핑 키가 라벨 문자열이라 라벨 변경 시 함께 고쳐야 한다. ⚠️ PBR 설명문의 '회사 순자산'은 뜻이 다르니 건드리지 않는다. ⚠️ render셸 HTML만 생성(데이터 fetch 없음) — 패널 내용 검증은 _build_panels_payload() 직접 호출이나 /api/panels curl로 해야 한다. KPI 행 순서(2026-07-29, 관리자님 지정): 합산 owner 카드(_render_owner_kpi)는 구분선·그룹 없는 단일 목록총자산 · **투자원금** · 매입금액 · 평가금액 · 평가손익 · 예수금 · [당일 입출금] · 보유종목/당일매매 · 당일 평가손익 + 조건부 당일 실현손익(non-compact) + 맨 아래 총수익. 당일 입출금은 예수금 바로 아래(현금 잔액↔그날 현금 이동)이고 입출금 없는 날은 행 자체가 없다. 투자원금은 총자산 바로 아래(전체 자산↔넣은 돈 대비), 총수익은 맨 아래 결론. 원금 미집계(부트스트랩 전)면 두 행 모두 사라진다. 보유종목·당일매매는 한 행 결합(3개 / 2개) — compact에도 당일매매가 함께 보인다. 평문 리포트에서 보유종목/당일매매는 표시폭이 정확히 17칸이라 패딩 없이 콜론이 맞는다. 같은 순서·라벨을 _render_account_kpistock_portfolio_report(HTML 소유자별·전체합계, 평문)에도 적용. ⚠️ 2026-07-29 중 4그룹(자산/보유/누적/당일) 구분선 방식을 거쳤다가 단일 목록으로 되돌림 — .kpi-gtop CSS는 제거됨. _rtk 실시간 매핑(value/profit/net/daypl/totret)은 라벨 기준이라 그룹화와 무관하게 동작. 계좌별 pane(_render_account_kpi)은 평면 유지(관리자님 선택). ⚠️ 그룹화로 stock_portfolio_report 메일의 평면 순서와는 배치가 갈렸다(라벨은 동일). 투자원금·총수익 KPI(2026-07-29): 누적 성과 그룹 2행 — 투자원금(값은 금액만, 라벨 옆 작은 원형 [!] 버튼) / 총수익. 버튼을 값(td)이 아니라 라벨(th)에 두는 이유 — 금액 뒤에 붙이면 숫자 우측정렬이 흐트러진다. kpis 튜플의 3번째 원소가 라벨 뒤 HTML(escape 대상 아님)로 렌더된다. KPI 상세 팝업은 공용 1개(kpi-modal + openKpiModal(src,title)): 라벨 옆 .kpi-more-btndata-kpi-src(엔드포인트)·data-kpi-title을 들고 있고, 위임 핸들러가 그걸 읽어 fetch → 본문 innerHTML. 버튼 생성은 _kpi_more_button(src, title). 본문은 서버가 HTML로 조립하므로 새 [!] 버튼을 늘릴 때 JS·모달 추가가 필요 없다 — 렌더 함수 + 엔드포인트 분기만 더하면 된다. 현재 2개: /api/principal?owner=(_render_principal_body — 투자원금·누적 입금/출금·계좌별 + 입출금 내역 233행) / /api/cashflow?owner=(_render_cashflow_body — 당일 입금/출금 합계 + 건별 내역, kt00015 계좌당 1콜 재조회). 두 팝업 모두 .pm-* CSS 공유. ⚠️ 순액과 합계가 같아지는 중복 표시 금지 — 당일 입출금은 한쪽 방향뿐이면 순 입출금 행을 생략한다(+3,000,000원 · 입금 3,000,000원 같은 군더더기가 KPI 행·메일·텔레그램 4곳에 있었음, 2026-07-29 제거). 모달 셸은 shell HTML 직속이라 panels swap 영향 없음. 내역은 ip.get_flows 캐시 읽기라 키움 콜 0. 내역 233행 중 대부분이 배당·이자라 실제 입금이 묻히므로 기본은 원금 반영분만 보이고(본인 49/140·가희 15/93) 체크박스로 나머지를 펼친다 — .pm-all-cb:not(:checked) ~ .pm-list .pm-row.other{display:none} CSS-only, JS 없음. 개념 설명문은 넣지 않는다(관리자님 지시). ⚠️ 계좌 간 대체(내 일반↔ISA)가 있으면 owner 합계는 상쇄되지만 계좌별 금액은 왜곡되므로 안내문(.pm-note-in)을 띄운다(실측 본인 6,000,000·가희 1,000,000 각각 상쇄). 순자산 원금 관계가 눈으로 읽히게 붙여 놓고, 위쪽 총 평가손익(보유분 미실현)과 혼동되지 않게 라벨에 "원금 대비"를 명시. 데이터는 investment_principal.get_principal(lbls)(캐시 읽기, 콜 0) → _build_owner_dataprincipal·principal_cash_in/out·principal_qty_flow로 주입, None이면 2행 생략. 순자산이 WS 틱마다 움직이므로 총수익도 같이 갱신 — 카드 data-rt-principal + td data-rt-kpi="totret"setOwnerKpis가 읽어 T.net principal 재계산(updateScopes/T 구조 무변경). 계좌별 pane(_render_account_kpi)은 미적용. 보유종목 행마다 📋 거래내역 + 💰 거래 + 📝 메모 버튼. 자산정보 탭 sub-tab 3개 (자산보기·차트보기·시장정보) + 우측 별도 [💰 거래] 버튼 (본인 첫 보유종목 자동 선택). 관심·감시종목 행에도 💰 거래 (보유 모드면 매도 default).

    거래 모달 시스템 (order-modal + pin-modal + open-orders-modal) — 매매 진입점. 흐름: 종목 select(상단, 보유/관심/감시 통합) → 매수·매도 토글 → 호가창(ka10004 10단계, 1초 polling, visibility 가드) + 입력(계좌·주문유형 LIMIT/MARKET·단가·금액(매수만 양방향)·수량) → 매수/매도 버튼 → propose → PIN 모달(modal-top z-index) 카드 요약 + PIN 입력(autocomplete="one-time-code") + 만료 카운트다운 → verify → 결과 토스트 + 자동 닫기. [📋 진행중] 탭은 활성 PIN 카드 → PIN 모달, 미체결만 → open-orders 모달(4계좌 통합 + 행별 취소). 매수 시 금액↔수량 양방향 자동(programmatic .value, 무한루프 X). 매도 토글 시 수량 자동 100%(max_qty). 시장 phase 라벨 + NXT 시간대+nxt_enable=false📵 NXT 거래불가 + 매수/매도 버튼 disable. 우상단 X 없음 — 하단 [닫기]/[취소] + overlay 클릭. 2계좌 동시 매도(2026-06-12): 매도 버튼 시 전량매도(입력 수량=max_qty) 이고 같은 소유자 그룹(본인 일반↔ISA / 가희끼리)의 다른 계좌에도 같은 종목 보유(accStatus.trde_able_qty>0)면 sell-choice-modal 팝업 — [두 계좌 모두 매도](두 계좌 모두 매도가능 전량, 단가 동일) / [선택 계좌만] / [취소]. 일부매도는 팝업 없이 단일 진행. 매도 정보영역은 그룹 내 다른 계좌도 보유 시 보유: 141주 / 전체 280주 병기. 모두 매도는 POST /api/order/propose_multihandler.propose_trade_multi(레그별 독립 검증, SELL+LIMIT/MARKET 한정, 같은 그룹만) → 카드 1장+PIN 1개(iMessage 매도(2계좌))가 두 레그 승인 → verify 시 _submit_multi_legs가 레그 독립 제출(idem_hash 계좌별 분리)·독립 fill_watcher, 한 레그 실패해도 나머지 시도(ok=하나라도 접수).

    매도 주문유형 4종 — 지정가(LIMIT) / 시장가(MARKET) / 스톱지정가(STOP_LIMIT, 하락 시 매도·조건단가 고정) / 트레일링 스톱(TRAILING_STOP, 2026-07-30 추가). 뒤 2개는 매도 전용(매수 선택 시 옵션 disabled + LIMIT로 되돌림, refreshStopUI). 트레일링 입력(단가 입력행은 숨김 — 조건단가·지정가·계단별 주수는 서버 계산): ①계단 프리셋 드롭다운 — 코드 상수 TRAIL_PRESETS 고정 4종, 읽기 전용. 표시 순서 = 단일 / 보수 / 표준 / 느슨(기본값은 맨 앞 단일, 그 뒤로 트레일 폭이 좁은 것부터). 옵션은 이름만 — 계단 수치는 바로 아래 편집 표에 그대로 보여 중복이다(2026-07-31 관리자님 지시, 다시 붙이지 말 것). 셸 HTML에 window.__trailPresets로 실려 모달 열 때 fetch 0. ⚠️ 프리셋 저장·삭제 기능은 두지 않는다(2026-07-31 관리자님 지시로 제거 — 고른 뒤 아래 표에서 그 자리에 고쳐 쓰면 되므로 저장소·락·엔드포인트가 불필요했다. state/trailing_presets.json도 폐기). 프리셋을 늘리려면 상수를 고친다 ②그 아래 계단 편집 테이블(하락/누적 헤더 한 줄 + 행마다 [__]% [__]% + ✕, 아래 full-width + 계단 추가, 최대 5) ③최저 매도가(선택, 비우면 제한 없음). ⚠️ 라벨을 행마다 반복하지 말 것(2026-07-31 정리) — .order-inputs 실폭이 모바일 ~200px라 행마다 '하락'·'누적' 글자와 화살표를 넣으면 입력칸이 38px(실텍스트 26px)까지 눌린다. 헤더로 빼고 화살표를 지워 78px로 회복했고, number 스피너도 -webkit-appearance:none으로 없앴다(데스크톱에서 숫자를 가림). ⚠️ 헤더 행도 .trail-step 클래스라 trailSteps():not(.trail-step-head)로 걸러야 한다 — 안 그러면 입력칸 없는 행을 읽어 TypeError. ⚠️ 계단 0개면 헤더도 그리지 않는다. 단위((%)·(원))는 라벨에 박는다 — 입력칸 옆에 붙이면 가로로 벌어져 스크롤이 생기고 number input 안쪽 absolute 접미사는 데스크톱 스피너와 겹친다. 누적 비중으로 입력받는 이유 — 관리자님이 그렇게 생각한다("30% 빠지면 전량"). 주문 수량인 추가분 환산은 normalize_steps가 한다. handler.propose_trade(trail_steps, min_sell_price)가 초기 고점을 md['current_price']로 정하고 compute_step_levels+allocate_step_qty로 계단별 가격·주수를 만든다. 0주가 된 계단은 떨어내되 단계 번호(n)는 유지해 카드에서 몇 단계가 빠졌는지 보이게 한다. ⚠️ 트레일링에 budget(예산 환산) 금지 — 계단 주수는 보유수량을 나눠 만드는데 예산이 끼면 기준이 둘이 된다(TRAIL_BUDGET으로 거부). ⚠️ 고점 기준 선택 옵션은 2026-07-30 제거됨(peak_basis / 52주 전고점 / 매수후 고점) — 과거 고점을 기준으로 잡으면 손절선이 현재가 위로 올라가 등록 즉시 발동한다. 손실 종목은 등록 자체가 불가(실측 제닉스로보틱스 현재가 4,805 · 매수후 고점 6,890 → 필요 폭 30.3% / 52주 19,940 → 75.9%, 둘 다 상한 30% 초과), 이익 종목은 과거 고점 ≈ 현재가라 결과가 같다. 즉 차이가 날 때는 못 쓰고 쓸 수 있을 때는 차이가 없어 옵션을 없앴다. 다시 넣지 말 것. ⚠️ 1단계(가장 얕은 계단) 손절선 ≥ 현재가면 TRAIL_IMMEDIATE로 거부 — 고점=현재가 고정이라 이제 최저 매도가가 현재가보다 높은 경우만 남는다. 거부 시점은 입력 시점(2026-07-30 관리자님 지시로 propose 시점에서 이동) — trailBlockReason()이 입력값을 보고 사유 문자열을 내놓고, 걸리면 매도 버튼이 disable + title 에 사유. 계단 순서·범위 위반(TRAIL_STEPS)과 "이 수량으론 계단을 못 나눔"(TRAIL_QTY)도 같은 함수가 같은 문구로 먼저 막는다. 서버는 최후 방어선으로 유지. ⚠️ 버튼 disabledrefreshSubmitGate() 한 곳에서만 결정한다 — 시장 phase 게이트(updateMarketPhaseDisplay, /api/market_state 주기 폴링)와 트레일 게이트가 같은 버튼을 건드려서, 예전처럼 각자 대입하면 주기 호출이 트레일 게이트를 덮어써 즉시발동 입력이 통과된다. phase 쪽은 state.phaseCanTrade/phaseLabel만 쓰고 판단을 위임. propose 진행 중에는 btn.dataset.busy='1'로 잠가 주기 호출이 버튼을 되살리지 못하게 하고, 응답·에러 후 busy 해제 + 재계산. renderTrailPreview는 조기 반환 경로(폭 미입력·시세 로딩)에서도 게이트가 걸려야 하므로 함수 맨 앞에서 refreshSubmitGate()를 부른다. 클라이언트 미리보기(renderTrailPreview/trailCondPrice/allocStepQty/trailWeights/floorTick)는 서버와 같은 식이지만 실제 발주값은 서버 계산분이 승자 — 한쪽만 바꾸면 표시와 발주가 어긋나니 orders/trailing.py 수정 시 JS도 함께 고칠 것(렌더된 페이지에서 함수 소스를 뽑아 node로 실행해 Python과 대조하는 방식, 현재 418조합: 손절선 360·수량배분 54·비중환산 4). 키움엔 계단마다 스톱지정가로 나가고(_submit_trailing_steps가 레그별 STOP_LIMIT 제출) 트레일링은 우리 쪽 개념. 카드 1장·PIN 1개로 계단 N건을 승인한다. 레그 독립 제출 — 얕은 계단부터 접수해 중간에 막혀도 가장 가까운 방어선이 먼저 걸리고, 한 레그가 실패해도 나머지는 시도하며 접수된 것만 trailing.register_steps로 등록한다(이미 나간 주문을 되돌리지 않는 이유는 취소도 실패할 수 있어 상태가 더 불분명해져서 — 대신 몇 단이 걸리고 몇 단이 실패했는지 메시지에 그대로 적는다). 등록 실패 시 주문은 이미 접수된 상태라 메시지에 ⚠️ 경고를 붙이고 ledger trailing_register_failed 기록(숨기면 트레일링이 안 도는 걸 모른다). 트레일링·스톱지정가는 단일 계좌 예약이라 2계좌 동시 매도 분기에서 제외. 취소는 [📋 진행중]에서 해당 미체결 주문 취소 → 다음 감시 사이클이 사라진 예약을 자동 정리. ⚠️ 트레일링 예약이 미체결 매도로 잡혀 있으면 /api/order/check_pending_sell_qty 차감 덕에 일반 매도 max_qty가 자동으로 줄어든다(이중 매도 구조적 차단).

    텔레그램 발송 정책 (웹 매매) — 거부·검증 에러는 토스트만, 매매등록(submit_with_pin 성공)·매매체결(fill_watcher)만 텔레그램. PIN 메시지는 iMessage (Apple 도메인 바인딩 @stock.hyowons.net #PIN, iOS Safari OTP 자동입력). handler.send_imessage_pin (fire-and-forget Popen, AppleEvent timeout -1712 떠도 메시지는 큐로). credential credentials/admin_imessage.json {"handle": "01012345678"}. 자기 자신 iMessage self-send 가능 (mac → 본인 iCloud handle). 규칙(나→나 iMessage): 관리자 본인 handle로 보내는 iMessage는 항상 --service imessage(파란) 강제 — SMS 셀프발송 금지. 이유: iMessage 셀프발송은 Note-to-Self라 보낸 버블만 뜨고 OTP 도메인 자동입력이 정상 동작하지만, SMS 셀프발송은 통신사 loopback으로 초록·수신버블이 생기고 자동입력이 깨질 수 있음. 타인 대상(예: 가희 리마인더 gahee_reminder._send_imessage--service sms)은 예외.

    신규 endpoint: /api/quote_book?code (ka10004 호가 10단계 + ka10001 현재가 + nxt_enable), /api/order/check?code&account&side&price (잔액·보유·max_qty·매도 손익 미리보기), POST /api/order/propose (handler.propose_trade wrapper, 성공 시만 텔레그램·iMessage), POST /api/order/propose_multi (2계좌 동시 매도 — accounts/qtys 콤마 동수 리스트, handler.propose_trade_multi wrapper), POST /api/order/verify (handler.submit_with_pin wrapper, 성공 시만 텔레그램), POST /api/order/cancel (handler.cancel_active_card, 텔레그램 X), /api/order/active (PinStore.peek + 4계좌 미체결 카운트), /api/orders/open (ka10075 4계좌), POST /api/orders/cancel?ord_no&account (handler.cancel_open_order + 텔레그램), /api/market_state (regular/nxt/closed/holiday/weekend phase), /api/symbols/all (보유+관심+감시 통합 dedup)

    기업정보 모달 (4탭) — 종목 행 기업정보 버튼 → /api/stock_info?code(키움 ka10001 + 네이버 분기영업이익 + fnguide_client 펀더멘털·컨센서스 + wisereport_client 리비전·서프라이즈·최근리포트) fetch → 단일 종목은 4탭 렌더: 요약(현재가·목표주가·리비전·증가율·서프라이즈 + 최신 리포트 1건) / 기업정보·가치(PER·PBR·EPS·BPS·ROE·영업이익·52주·유통비율·외국인) / 성장성·컨센서스(매출/EPS 증가율·목표주가·투자의견·추정PER〔현재PER 병기〕·목표가 리비전·서프라이즈) / 투자리포트(최근 증권사 리포트 목록, 목표가 ▲상향/▼하향). 탭은 모달 전용 클래스(.info-tabs/.info-tabbtn/.info-tabpanel + data-info-tab/data-info-panel)와 독립 핸들러로 자산탭 .sub-tab과 격리(전역 restoreSubs 충돌 회피). 항목별 ⓘ 탭 팝업 설명(INFO_SPECS/INFO_DESC, document 위임 .info-label). 비교 모드(여러 종목)는 탭 없이 기존 비교표 유지. ETF 등 데이터 없으면 해당 탭 "데이터 없어요".

    메모 모달 (note-modal)(2026-06-26) — 보유·관심·감시종목 행 모두에 📝 메모 버튼. 클릭 → 팝업 textarea(최대 1000자, 여러 줄)에 기존 메모 prefill → [저장]은 /notes/set POST(submit 핸들러가 note-form을 fetch로 가로채 현재 탭 유지 — 네이티브 폼이면 303→/ 리다이렉트로 자산정보 탭으로 튐) 후 패널 새로고침, [삭제]는 버튼 왼쪽 위 작은 확인 팝업(예/아니오) 후 빈 텍스트 submit으로 항목 제거(오삭제 방지, note-confirm). 메모 있으면 버튼이 📝 메모 ● 노란 강조(has-note)+hover 툴팁. 저장소 state/behive_stock_notes.json({by_code, by_name}, code 우선→name fallback, stock_tags와 동일한 fcntl lock·atomic write). 종목 단위 공용(계좌 분리 X). 백엔드 _load/_get/_set_stock_note·_note_button_html·_render_note_modal, 태그 모달 패턴 복제.

    관심종목 그룹 (group-assign-modal + group-manage-modal)(2026-07-03) — 관심종목이 39개까지 쌓여 그룹 분류 도입. 관심종목 탭 전용(감시종목 제외), stock_tags독립. 저장소 state/behive_interest_groups.json = {"groups":[순서있는 이름], "members":{종목명:[그룹명]}} — 멤버십 키는 종목명(interests가 종목명 dict 키라 안정적, rename 없음). 다중 그룹 소속·빈 그룹 유지·표시순서·rename/delete 연쇄를 레지스트리 한 파일에서 표현. stock_tags/stock_notes와 동일한 fcntl lock·atomic write. 관심 삭제 시 _apply_interests_action('delete')_interests_lock 밖에서 _set_interest_groups(stock,[])로 멤버십 자동 정리(별도 파일 락, 데드락 회피). 패널(_render_interests_panel): 그룹 풀다운(.igrp-select/.igrp-panel, 전체+보유중+멤버 있는 그룹+미분류 옵션, 다중소속은 각 패널 중복 노출). 보유중은 mode=='held'로 자동 채워짐(레지스트리 미등록, 보유종목도 그룹 배정 시 해당 그룹에 함께 노출). 전체=보유+관심(보유 먼저), 미분류=미보유&미배정만. 보유·그룹 둘 다 없으면 단일 목록으로 폴백. 선택 상태는 자산탭 .sub-tab과 격리(igrpState+restoreIgrp, 3s swap 후 복원, 사라진 키는 전체로 폴백). 같은 종목이 여러 패널에 중복 → details_mutex는 같은 data-row-key끼리 안 닫음(펼침 유지), apply()querySelectorAll로 전 인스턴스 open 복원. 관심종목 행 + 그룹 버튼은 +태그 버튼과 같은 디자인(btn-add-tag)으로 그 옆(detail-name 줄) → 체크박스 배정 모달(+새 그룹 인라인, new_group). 풀다운 오른쪽 + 버튼(.igrp-add-btn, flat·empty 케이스에도 항상 노출)→ 작은 드롭다운 메뉴(.igrp-menu: 종목 추가=interest-modal / 그룹 관리=group-manage-modal). 하단 트리거(관심추가·그룹관리 버튼)는 이 메뉴로 대체·제거. 그룹 관리 모달(그룹당 2줄 카드: 이름 인풋+수 / ▲▼·이름변경·삭제, 하단 새 그룹 행). 관리 모달 open은 트리거 data-group-all+data-group-counts(JSON)로 서버 왕복 없이 즉시 렌더, 이후 op는 keepOpen fetch 응답 {groups,counts}로 재렌더. 엔드포인트 POST /interests/groups/set(stock+groups 반복필드+new_group)·POST /interests/groups(op=create|rename|delete|reorder|listlist는 조회 폴백용 유지〕, in_actions 검사보다 먼저 정확매칭). 백엔드 _load/_save/_get_interest_group(s)·CRUD(_create/_rename/_delete/_reorder_interest_groups_set_interest_groups·_interest_group_button_html·_render_group_assign_modal·_render_group_manage_modal, 태그/메모 모달 패턴 복제. 기존 39개는 미분류로 자동 편입(쓰기 마이그레이션 없음). 레거시 에이피알.tag:"뷰티" 인라인 필드는 미사용 방치.

  • send_balance_to_budget.py — 매월 1일 04:30 launchd. 본인+가희 계좌를 소유자별(self/gahee)로 잔액·예수금·평가액 집계해 agents/budget/inbox/incoming/에 envelope(topic: securities_balance, schema v2 by_owner)로 떨어뜨린다. 골디가 self→증권(효원)·gahee→증권(가희)로 reconcile 분개. 골디 월간 결산(05:00) 입력. LLM 미경유, 실패 시 레이 텔레그램으로 자가 알림.

  • trade_journal.py — 종목별 매매 기록 누적. 키움 REST에 기간 거래내역 API 부재 → 평일 21:00 launchd(NXT 야간 마감 후)로 ka10170 4계좌 호출해 state/trade_journal.jsonl에 적재. (date, account) 단위 idempotent, 휴장일/주말 self-skip(--force로 우회). 적재 시작일 2026-05-13 이전 보유분은 seed 명령(1회)으로 현재 평단가×(보유-당일매수+당일매도) 단일 행으로 압축 적재됨(seed=true 플래그·* 마커). CLI: collect [--date YYYYMMDD] / seed / show <code|name> / query [--from --to --account --code]

  • market_indicators_sync.py — 시장 단위 ADR·투자자별 매매 누적. 네이버 m.stock /api/index/{KOSPI|KOSDAQ}/integration 한 콜로 dealTrendInfo·upDownStockInfo 수집 → state/market_indicators_history.jsonl. (date, market) 단위 idempotent, 휴장/주말 self-skip(--force). 평일 21:00 stock.trade-journal launchd에 통합 발화 (별도 plist 없음). KRX 정보데이터시스템은 응답 패턴 변경으로 백필 보류 — 매일 누적만 시작. behive_web 자산정보 탭의 시장정보 sub-tab이 sparkline 데이터원으로 사용

  • sim/ (스크립트 아닌 별도 패키지agents/stock/workspace/sim/) — 자동매매 시뮬레이션(페이퍼 트레이딩) 엔진. 실주문 orders/와 완전 분리, 가상자본 1억으로만 동작(INITIAL_CAPITAL, 2026-06-10 1천만→1억). 구성: config.py(파라미터)·indicators.py(SMA/RSI/ATR/고저/거래량 순수함수)·universe.py(누적 관찰목록 state/sim/watchlist.json — 비하이브 워치+관심+본인보유를 매 스캔 자동 편입하되 자동 삭제 X(수동만). sync_watchlist/add_manual/removedata.py(시세 batch·일봉 캐시 우선·수급 60m캐시·애널〔fnguide/wisereport 12h 캐시, 캐시 미스 때만 0.4s 페이싱으로 연속 크롤링 차단 회피〕)·signals.py(진입/청산 규칙 + 손절·목표 계산 + dashboard용 체크리스트)·portfolio.py(가상 체결·비용·손익·MDD)·engine.py(스캔 오케스트레이션 — _gather(extra_codes)보유 종목은 관찰목록에서 빠져도 청산 추적benchmark.py(기준지수 대비 알파 — 네이버 m.stock 지수 일별종가를 state/sim/index_history.json에 누적〔KOSPI/KOSDAQ, pageSize≤50, 구간 등락률·초과수익 계산. 스캔마다 update_today 1회 갱신, 실패 무시)·__main__.py(CLI)·sim_web.py(별도 대시보드 서버 18792). 전략: 방향=애널 약한게이트+가점, 타이밍=수급·기술. ETF/ETN은 자동매수 제외(2026-06-09 — 방향성 엔진인 애널 컨센서스가 ETF엔 None이라 전략 부정합. universe.is_etf()가 운용사 프리픽스(KODEX/TIGER/ACE…)로 판별, engine 미보유 매수 분기 직전에 ETF — 자동매매 제외 SKIP. 보유 ETF는 청산까지 추적(매수 분기 전 처리라 무관)). 매수=거래량+전고점 돌파 즉시 / 평소 눌림목(max(20일선, 전일종가−ATR)) 지정가. 시장 국면 필터(6단계): market_regime_map()이 상승비율(폭)+지수추세(방향, 20일선)+당일등락(강도)을 조합해 engine.REGIME 6단계로 분류 — 🔥강세·📈반등(상승비율↑ & 지수 20일선 아래=약세탈출)·🌤완만상승 / 혼조·📉약세·❄️급락. 앞 3개=매수허용, 뒤 3개=보류(market_ok_mapREGIME[g][2]로 파생). 상승비율은 항상 네이버 실시간(_live_breadth 60s 캐시, 실패 시 EOD). 약세장이어도 돌파 진입은 허용, 눌림목만 보류(soft gate). 시장탭에 국면 배지+설명 표시(매수판단과 동일 소스). code_market_map()이 종목→KOSPI/KOSDAQ 매핑. (2026-06-09 강세/약세 2단계→6단계 국면으로 세분화) 2026-06-09 4종 추가: ⓐ 리스크 기반 사이징(signals.target_value — 수량=자산×RISK_PER_TRADE_PCT ÷ 손절폭, 균등비중 자산/MAX_POSITIONS 상한 캡, 0=끔. 변동성 큰 종목 자동 축소·종목당 리스크 균등화. 엔진·백테스트 공용) ⓑ RSI 필터(indicators.rsi 활성화 — 돌파 시 RSI_OVERBOUGHT↑ 과열 추격 보류 / 눌림목 시 RSI_OVERSOLD↓ 낙하 중 보류, 데이터 없으면 통과) ⓒ 시장 국면 6단계(market_regime_mapengine.REGIME, 위 시장 국면 필터 참조. _index_trend_ok·_index_daily_change 보조. ⚠️2026-06-09 강세/약세 2단계가 84% 반등일을 약세로 오판해 6단계 국면으로 세분화) ⓓ 시간손절(MAX_HOLD_DAYS 경과 & 미진전〔트레일링 전+진입가 이하〕 시 청산해 자본 회전, evaluate_holding(held_days) — 엔진=달력일·백테스트=entry_date 달력일). ⓖ 스캔 메모 4종(2026-06-10): 변이 수가 늘어도 스캔이 선형 폭증하지 않게 — 호가(data._BOOK_MEMO, 체결 공정성 겸용)·일봉(engine._SCAN_CANDLES)·지표(engine._IND_MEMO, (code,지표파라미터) 키)·수급/애널(data._FLOW_MEMO_SCAN/_ANL_MEMO_SCAN)을 스캔 1회 동안 공유, _gather가 매 스캔 리셋. 핵심은 애널 캐시 stale 시 0.4s 페이싱이 변이 수만큼 반복되던 것 — 108계좌 판단 38s→2s. ⓕ 2026-06-10 코드리뷰 3종: 재진입 쿨다운(전량청산 종목 당일 재매수 금지Portfolio.last_exit/exited_today, backtest 동일 규칙. 휩쏘 churn 방지)·장중 거래량 환산(_session_elapsed_frac 0.151.0을 ind['vol_time_frac']로 주입 → signals가 돌파 거래량·회전율을 하루치로 환산 비교, '(환산)' 표기. 이전엔 오전 돌파가 구조적으로 불가했음. backtest는 1.0=무영향)·일별 성과이력(state/sim/equity_history.json — 매 스캔 당일 항목 덮어써 EOD값 보존, {date:[{id,ph(파라미터해시),equity,ret}]}. 변이 id 재사용돼도 ph로 식별. 국면별 사후분석용) ⓔ 추세 질 보강(2026-06-09 trend_ok 게이트 강화: 기존 현재가>20일선+정배열(5>20)에 더해 ①20일선 우상향 기울기sma_long_slope_up, TREND_SLOPE_DAYS전 대비〕 ②중기 정배열 5>SMA_MID(60) — 둘 다 종목데이터라 백테스트 반영, 데이터 부족 시 통과. ③상대강도rel_strength=종목−지수 REL_STRENGTH_DAYS(20)일 수익률, engine이 주입하는 라이브 전용, 백테스트 None=중립, 체크표시만〕). 분할매수·추격매수: 목표비중을 ENTRY_TRANCHES(기본 3)로 나눠 1/3씩 진입, 직전 진입가+ATR×ADD_ATR_MULT 돌파 & 추세 지속 시 다음 트랜치 추가매수(평단 가중평균, 손절 위로만 래칫)·트레일링/일부익절 시작 후엔 추가 종료. 매도=손절(max(진입−ATR×2, 10일저점))·분할매도(목표 도달 시 SCALE_OUT_FRAC(기본 50%) 익절 후 잔량 트레일링(고점−ATR×1.5))·추세수급 이탈 시 전량 청산. 부분매도는 closed/win 미카운트(전량 청산 시만 카운트). 결정론적(LLM 미경유). 기준지수 대비(알파): 가상계좌/백테스트 수익률을 같은 기간 KOSPI·KOSDAQ '매수후보유' 대비로 평가(수익률만으론 지수 못 이기면 무의미하므로). 라이브는 계좌 시작일오늘, 백테스트는 윈도우 구간 대비. KPI '코스피/코스닥 대비(알파)' + 백테스트·비교 탭 알파 행. 경과기간 0(시작 당일)·캐시 부재 시 None('데이터 없음'). 상태 state/sim/{portfolio.json,trades.jsonl,last_scan.json,index_history.json}. CLI: python3 -m sim {scan [--force]|status|report|reset --yes|sweep [frac]|backtest|backfill [--pages N] [--force]|backfill-index [--pages N]|variants {seed N|list|reset|clear}} (cwd=workspace). launchd stock.sim-scan(15분)·stock.sim-web(상시).

    • 체크리스트 라벨 동적화(2026-06-15): 추세(N일선 위)·정배열(5>N)의 N은 **변이별 config.SMA_LONG(10/20/40)**을 읽어 표기. 이전엔 "20"으로 하드코딩돼 SMA_LONG=10/40 변이도 "20일선"으로 거짓 표기됨(차트의 5>20 정배열과 어긋나 보이던 원인). 하드코딩이 signals.py(buy_signal·evaluate_holding·trend 보강)와 engine.py 추세미충족 분기 두 곳에 있었음. ⚠️ 과거 trades.jsonl 판단근거는 거래 시점 baked라 옛 라벨 잔존(새 거래부터 정상). 라이브 판단은 judge_view가 변이 params 적용 후 계산하므로 즉시 정확. 추세 체크리스트는 signals.trend_checks(ind)로 추출해 매수판단·제외(SKIP)카드 공유(2026-06-15 — 제외 카드가 추세/정배열 2개만 보여 ①②통과인데 왜 SKIP인지 헷갈리던 것 → 4조건〔현재가>장기선·정배열·장기선 기울기·중기정배열〕+상대강도 전부 표시).
    • 즉시매수 토글 + 40일선 제거(2026-06-15): IMMEDIATE_ENTRY(기본 0) — 추세·정배열·수급·애널 다 통과한 종목이 돌파·눌림목 둘 다 아니어도(=안 내려오는 상승 종목) 강세장·RSI정상이면 현재가 즉시 매수(signals 눌림목 WAIT 직전 분기, buy_path='immediate'). 꾸준한 상승 놓침 방지. 40일선 추세선 제거(어중간·관리자님 지시, 스윕 GRID도 [10,20,60]). 변이: 40 72 후, 백테스트 검증 결과 즉시매수가 명확히 불리(전체기간 10/20/60 모두 즉시ON이 수익률 1/3·승률 −10~25%p·PF 반토막·MDD↑ — 눌림목/돌파 기다림 규칙이 핵심 알파). 그래서 즉시매수는 라이브 강세장 한정 확인용 3개만(10/20/60 안정·보수게이트) 유지 → 총 219개(비즉시216+즉시3). 즉시매수는 GRID 미포함이라 백테스트 기본은 OFF(검증은 위 수동 비교로 수행). 웹 초록 즉시 칩(_immediate_chip_html)·드롭다운 [안정 즉시]. ⚠️ 기존 216은 IMMEDIATE_ENTRY 기본 0이라 불변.
    • 물타기(애버리징) 토글(2026-06-16): AVERAGING_ENTRY(기본 0)·AVERAGING_DROP_PCT(0.10)·CRASH_DROP_PCT(0.10). 켜면 보유 중 직전매수가 대비 -10% 하락 시 손절 대신 추가매수로 평단↓(종목당 10% 한도=ENTRY_TRANCHES 분할). 가드: 당일 -10%↓ 급락=물타기 금지·손절(그림 #3), 약세장(market_ok=False)=물타기 금지(그림 #4). 첫 매수는 기존 검증 진입(추세·정배열+돌파/눌림목) 그대로 — "우량주만 물타기". evaluate_holding(market_ok) 0번 분기, add_kind='down'이면 손절선을 낮아진 평단 기준으로 재설정(추격매수 up은 위로만 래칫, engine·backtest 공용). 백테스트 검증: 즉시매수와 반대로 유리(10/20/60 모두 수익·승률↑, MDD 비슷 — 우량주+10%한도+급락손절 가드 덕). 라이브 변이 3개 추가(v301/302/303=10/20/60 안정·물타기ON) → 223개. 웹 노랑 물타기 칩(_avg_chip_html). ⚠️ 백테스트는 시장중립이라 약세장 가드 미반영 — 진짜 하락장은 라이브 검증.
    • 추세선 60일 추가(2026-06-15): 스윕 GRID SMA_LONG에 60 추가([10,20,40,60], 1152→1536조합). 60일선 상위 12조합 × 약세대처3 × 진입게이트2 = 72변이 추가 → 총 288개(10/20/40/60 각 72). ⚠️ 60일선은 base 정배열(5>60)이 중기게이트(SMA_MID=60, 5>60)와 동일 — 중기게이트가 추가 필터링 없음(무해, 관리자님 인지·승인). ⚠️ 스윕 첫 실행이 워커 조용히 죽는 일회성 deadlock(메인 SN 0%CPU·워커0) → kill 후 재실행으로 정상화. 장중엔 sim-scan/web과 CPU 경쟁으로 20분+ 소요.
    • 추세 게이트 완화 토글(2026-06-15): TREND_SLOPE_GATE·MID_ARRAY_GATE(기본 1=현행). trend_okgetattr로 참조 — 0이면 장기선 우상향/중기정배열(5>60) 게이트 미적용. 보수성(3거래일 매수 2건, 추세미충족이 비ETF의 93% 차단) 완화 실험용. 완화 변이(2026-06-15 최종: 기울기 게이트만 OFF, 중기정배열(5>60)은 유지TREND_SLOPE_GATE=0, MID_ARRAY_GATE=1. 관리자님: "중기정배열은 살려야지". 144개 완화변이 전부 이 규칙, 계좌 초기화). 웹: 보라 완화 칩(_relax_chip_html)·드롭다운 [안정 완화]. trend_checks가 게이트 OFF 조건을 ok=None('게이트 끔')로 표기. ⚠️ TUNABLE에 추가돼 기존 108개는 apply_params에서 기본 1(현행)로 적용돼 불변.
    • 런타임 튜닝: config.TUNABLE 29개 파라미터(분할매수 횟수·추격 ATR 배수·분할매도 익절 비율·종목당 리스크 비율·RSI 과열/과매도 컷·시간손절 일수·약세장 매수모드BEAR_ENTRY_MODE 0현금화/1돌파만/2눌림허용, signals에서 market_ok False일 때 분기, backtest는 market중립이라 무영향, 병렬변이로 라이브 비교〕 포함)를 state/sim/params.json로 오버라이드(웹 튜닝 탭에서 저장). config import 시 기본값 위에 덮어써 엔진이 다음 스캔부터 반영. config._DEFAULTS가 원본 기본값. load_params/save_params/reset_params.
    • 백테스트·스윕 (backtest.py/sweep.py): signals.py 그대로 재사용해 과거 일봉 되감기(지표 t까지·종가체결·애널 중립·수급은 flow_history 사용). sweep은 9파라미터(SMA_LONG·RR·STOP_ATR·PULLBACK_ATR·VOLUME_BREAKOUT + 신규 RSI_OVERBOUGHT·RSI_OVERSOLD·MAX_HOLD_DAYS·RISK_PER_TRADE_PCT) 1152조합을 학습/검증(out-of-sample 0.7) 분리 비교 (애널·시장필터는 backtest 중립이라 스윕 제외) → state/sim/backtest_results.json. 체결 현실화(2026-06-10): backtest가 당일 종가 판단→다음날 시가 체결(pending 주문 큐, look-ahead 제거) + BT_SLIPPAGE 0.2%(매수 비싸게/매도 싸게). 체결 낙관분 5%p 제거 확인. 거래 없는 날 주문은 소멸(신호 지속 시 재큐잉). 3분할 꾸준함 순위(2026-06-10): 전체기간 3분할 구간별 수익(windows/worst_ret)을 측정, 순위 1순위=최악구간 수익(한 구간 운빨 배제)·2순위=검증수익. 웹 행에 '구간별 검증(3분할)' 표시. 멀티프로세싱(2026-06-10): sweep이 조합을 코어에 분배(get_context('spawn'), 워커=코어−2). 일봉·수급은 _hist_cached/_flow_cached 프로세스 메모이즈로 종목당 sqlite 1회만 읽음. **44분 → 약 57분**(10코어 기준). 데이터는 이미 sqlite 캐시 전용(키움 API 0회) — 병목은 네트워크가 아니라 지표 재계산(파이썬). spawn이라 워커가 메인 재import하지만 sim/__main__.py 가드로 안전. ⚠️ GRID 늘릴 때 곱셈 폭발 주의. ⚠️ 종가체결·현 워치리스트(생존편향)라 절대수익 아닌 상대순위로만 해석. 2026-06-09 스윕: 핵심 레버는 VOLUME_BREAKOUT=1.3(거래량 문턱 낮춤, 중앙 +19%) — 신규 4종은 강세 검증창에선 미미(RSI·시간손절=하락장 보험). 적용 균형값 params.json 저장(RISK 0.01 보수 유지, 0.02 고수익은 강세장 레버리지라 채택 X).
    • 수급 백필 (backfill_flow.py): ka10059 연속조회로 종목별 투자자 순매수 다년치(기본 6페이지≈2.4년) → state/sim/flow_history.sqlite(code,date,foreign_net,inst_net,indiv_net 천주). idempotent·rate limit 페이싱+429 백오프. backtest가 이 DB로 수급(외/기) 반영 (없으면 중립). 2026-06-08 54종목 3만행 1회 적재 완료.
    • 병렬 페이퍼 (variants.py + engine.scan_all): 스윕 상위 튜닝들을 각자 가상계좌(state/sim/variants/<id>/)로 실시간 동시 운영 → 비교(variants_compare.json). scan_all이 라이브 데이터 1회 수집 후 메인+모든 변이를 같은 시세로 평가(_gather+_run_scan 분리, Portfolio가 경로 인자 받음). 변이는 애널 게이트까지 실반영(백테스트가 못 보는 부분). variants seed N은 backtest_results 상위 N개 등록. launchd sim-scan은 이제 scan→내부적으로 scan_all 실행(메인+변이 매 스캔 갱신, 트리거·명령 동일).
    • 웹 3메인탭(sim_web — 전략/관심종목/시장 순, 기본=전략·비교. 기준전략(M) 폐지(2026-06-10): scan_all의 메인 패스는 execute=False 판단 전용(last_scan=화면·상태점용, 메인 계좌 매매 동결), 실매매·비교는 변이 36개만. sync_label_tops 메인 제외 규칙 삭제(유형별 1위 전부 변이). 비교탭 행마다 마킹 아이콘 피커(2026-06-10 ☆토글→6종 아이콘 🔥💎🎯👀🚀 선택, FAV_ICONS/favorites.json{아이콘: vid}아이콘 하나당 전략 1개(다른 전략에 쓰면 이동)·전략당 아이콘 1개. 칩 클릭→바텀시트 피커→POST /fav_set(fetch). 구버전 list는 자동 마이그레이션), 관심종목 탭 기본 시각 = 첫 마킹(FAV_ICONS 순) → 없으면 1위 변이. 약세모드 단어 칩(2026-06-10): 비교 행 vid 옆 + 드롭다운 + 펼침 body '약세장대처' 행에 회피(0 현금화)/안정(1 돌파만·기본, 2026-06-11 '돌파'→'안정' 개명 — 적극과 혼동)/적극(2 눌림허용) 색 칩(_BEAR_WORD/_bear_chip_html, 클릭 시 설명 토스트. 아이콘→단어는 관리자님 요청). 전략 이름 = 4카테고리 두글자(2026-06-15 _cat_name/_cat_labels — 추세〔민감10/표준20/둔감40/장기60〕·진입〔적극 vol≤1.5/신중〕·매도〔한방 RR≥2.5/균형/단타 RR≤1.5〕·베팅〔집중 risk≥2%/분산〕. 예 '표준·적극·균형·분산'). 비교 행·드롭다운·상세 헤더에 적용. 펼침/상세에 _cat_detail_html 4요소 풀이(라벨+실제값+쉬운설명). 긴 문장형 _market_fit_summary(상승장에 강한…)는 '시장적합' 행 ⓘ로 유지. bt매칭은 _btkey(swept_params만) — BEAR_ENTRY_MODE 등 백테스트 중립 키가 params에 있어도 같은 bt행에 매칭(모드 3형제는 bt순위 공유, 백테스트 탭은 36종만 표시가 정상). 헤더 상태점 자산줄은 '전략 N개 · 평균/1위 수익률'. 2026-06-10 내계좌 탭 삭제〔계좌 정보는 비교탭 메인 행으로〕·튜닝 보조탭 삭제〔params 변경은 백테스트 "이 튜닝 적용"으로만, /save_params 리다이렉트도 s=bt〕. 탭 라우팅 ?t=+보조탭 ?s=, 레거시 t=acct/tune→lab): 헤더 타이틀 옆 시뮬 상태점(_engine_status#health 점: 🟢 장중 가동(최근 스캔 ≤12분)·🟡 장외 대기·🔴 장중 멈춤 의심, 클릭 시 상태 토스트). 관심종목(종목별 신호 체크리스트 + 각 평가항목 ⓘ 버튼 → 쉬운 설명 토스트_CHK_HELP/_chk_key/_CHK_HELP_JS, 바텀시트 #chk-toast, 열려있으면 자동새로고침 멈춤〕 + 상단 종목 추가 폼〔코드/이름〕 + 카드별 ✕ 삭제〔수동만, 보유 중이면 청산 추적〕 + origin 라벨〔워치/관심/보유/수동〕 + 미스캔분 '스캔 대기' 표시) · 시장(지수·ADR·상승하락·투자자 순매수 — 항상 네이버 m.stock 실시간_get_live_market 60s 캐시, market_indicators_sync.fetch_market 재사용. 네이버가 장 마감 후에도 당일 최종값 제공하므로 세션 무관 최신 사용〕, 조회 실패 시에만 market_indicators_history.jsonl EOD 폴백. note는 장중='장중 실시간'·장외='직전 장 마감 기준'. 2026-06-09 초기엔 장중만 실시간이라 마감 후 어제 EOD를 보이던 빈틈을 항상-실시간으로 수정) · 전략(2026-06-11 보조탭 버튼 제거 — 기본=비교만 표시, 백테스트는 비교 타이틀 우측 [📊 모의결과보기] 라벨(라디오 sub-bt 토글)로 진입·bt 패널 상단 [← 비교로]로 복귀. 그 옆 [📋 거래목록](sub-day/_day_trades_panel(sel_date) — 전 전략 거래를 달력형 날짜 선택(_DAY_CAL_JS — 거래일만 밝게·클릭가능, 비거래일 어둡게, 월 이동 JS 클라이언트〔범위 밖 ‹›disabled〕, 오늘 강조. 컴팩트 max-width 300px. 날짜 클릭 시 달력 안 닫고 fetch로 #day-rows·#day-sub·라벨만 갱신history.replaceState로 ?d= 보존〕, 별도 [닫기] 버튼. 접이식 details, 기본=오늘(거래 없어도 자동선택·클릭가능, today-open 점선 셀), 2026-06-15. 빈 날도 #day-rows 컨테이너 유지해 fetch 전환 정상)으로 (종목,방향)별 아이템(같은 종목 매수·매도 동시면 2아이템 분리, 요약=종목명+🟢매수/🔴매도 배지+전략수)으로 집계: 전략수·전략번호칩(상세링크)·가격대·평균손익·사유. 날짜는 simURL이 &d=로 보존). ?s=bt 라우팅·60s 새로고침 라디오 보존은 유지. 구성 2: 백테스트유형별(세부 라벨 ~36종) 대표 요약 — 2026-06-10 단순화: 시장 성격별 그룹(상승장/추세장/조정장/출렁이는 장/보통 장에 강한 유형) 안에 각 유형 1위만 표시(꾸준함=worst_ret 순), 행=제목+최악·검증수익, 펼치면 구간별 검증·MDD·승률·PF·알파·파라미터·🌐. 비교군 추가/제거·이 튜닝 적용 버튼 제거(읽기전용) — variants.sync_label_tops()수동 전용(2026-06-10 자동 동기화 끔 — sweep은 결과 파일만 갱신, 비교군 변경은 관리자님 명시 요청 시 python3 -m sim variants sync 또는 코디가 수동 등록)(같은 params 변이는 계좌 유지·신규 추가·탈락 삭제, 메인 조합 제외). 메인 변경은 코디/CLI로만. + '↻ 다시 계산'〔백그라운드 sweep — 결과 파일만 갱신, 비교군은 불변〕〕/비교〔병렬 변이 실시간 성적, 변이별 성적초기화/삭제 + 전체 삭제(메인 제외) /variants_clear_allvmod.clear(). 펼침 body 첫 행=유형(긴 문장형+ⓘ→🌐 시장적합 토스트, 2026-06-10 하단 🌐 버튼 대체)→둘째 행 약세장대처(돌파 (기본) 모드명만+ⓘ 토스트). bt 등록근거 행은 모의평가로 개명(1행 N위/36+2행 최악구간·검증, 모드 3형제는 _btkey로 같은 bt행 공유). 전략 상세 페이지(2026-06-11): 행 펼침 [📈 자세히보기] → GET /strategy?id=vN 독립 페이지 — 일별 수익률 SVG 차트(equity_history id+ph 매칭, KOSPI 같은 구간 점선 오버레이) + 결정론 종합 코멘트(순위·알파·과최적 경고) + 현재 성적·모의평가·전략 성격·약세장대처·파라미터 표(_TUNE_HELP 라벨)·보유/관심/거래. 평가순 정렬은 고유번호 오름차순(#1→#108). 행 표기(2026-06-10): 순위 배지 N위가 앞, 고유번호는 #N(_vid_disp — 'v37'→'#37' 표시 전용, 내부 id·URL은 raw. 드롭다운·👁토스트·사유 라벨 동일 표기)〕). 🌐data-detail 통합 토스트 핸들러로 헤더 상태점(#health)과 공유. sticky 헤더, 당겨서 새로고침. 새로고침 상태 보존(2026-06-10): 60s 자동새로고침이 전체 리로드지만 펼침(details open)·스크롤을 sessionStorage(simOpen/simScroll, key=패널id+summary텍스트)로 저장·복원해 보던 화면 유지. 입력 중·토스트 열림엔 새로고침 멈춤. 탭 보존(2026-06-11): 탭/서브탭 라디오 변경 시 URL 즉시 동기화(window._simURL+history.replaceState) — 당겨서·브라우저·마킹저장 등 모든 리로드가 보던 탭에 착지. HTML 응답에 Cache-Control: no-store(Safari 휴리스틱 캐싱이 옛 JS를 재사용해 탭 복원이 안 먹던 문제). 깜빡임 제거(2026-06-12): 활성 탭/서브탭을 render_html(tab,sub)이 라디오 checked로 서버에서 미리 박음(GET이 ?t/?s 파싱+레거시 보정) — 이전엔 기본탭 렌더→JS가 로드 후 전환하며 깜빡였음. do_POST: /save_params·/run_sweep·/variants_{add,reset_one,remove,clear_all}·/watch_{add,remove}·/fav_set(마킹 아이콘 지정/이동/해제 → state/sim/favorites.json) (그 외 읽기전용)
  • Portfolio data: memory/portfolio.json (v2 스키마 참고용 스냅샷, accounts.{일반,ISA}.positions), state/portfolio_daily_snapshot.json, state/kiwoom_tokens/{일반,ISA}.json, state/stock_codes.json(키움 ka10099 lazy 캐시), state/watchlist_alerts.json(알림 중복 방지), state/ipo_calendar_sync.json, state/fomc_calendar_sync.json, state/behive_*.json, state/fnguide_cache/{code}.json(FnGuide 펀더멘털 12h), state/wisereport_cache/{code}.json·{code}_reports.json(컨센서스 12h·리포트 6h), state/stock_reports/<code>/(분석 보고서 HTML)

Scheduled Jobs

OpenClaw 자동화는 두 갈래로 동작한다 (모두 Asia/Seoul):

Cron (cron/jobs.json, OpenClaw 에이전트 세션 — LLM 경유)

  • 오전 브리핑 (main) — Daily 07:30 — 뉴스 브리핑 메일 + 🇺🇸 미국증시 요약(@futuresnow). 영상이 아직 미게시면(KST 화~토 게시예정일) 발송 보류
  • 오전 브리핑 (fallback 0755) (main) — Daily 07:55 — 1차 폴백. 07:30에 미국증시 요약 미게시로 보류된 경우 재시도(여전히 미게시면 또 보류). already_sent로 idempotent
  • 오전 브리핑 (fallback 0830) (main) — Daily 08:30 — 2차 폴백·최종. prepare morning --final로 US 미게시여도 무조건 발송. 07:30·07:55에 정상 발송됐으면 already_sent로 스킵
  • 오후 브리핑 (main) — Daily 19:00 — 뉴스 브리핑 메일
  • 비하이브 종목분석 요약 (stock) — Weekdays 07/12/18시
  • 월간 결산 (budget) — 매월 1일 05:00 — 자산 변동 메일 + 골디 텔레그램

launchd (~/Library/LaunchAgents/ai.openclaw.*.plist — LLM 미경유, 직접 실행)

  • gateway — 상시 daemon (포트 18789)
  • claude-remote-control — on-demand daemon (코디 세션, claude-code-session 스킬이 띄움)
  • stock.behive-web — 상시 daemon (워치리스트 웹뷰, Tailscale 18790)
  • stock.briefing — 평일 20:10 — 일일 포트폴리오 리포트 메일
  • stock.briefing-fallback-2030 — 평일 20:30 — 오늘 스냅샷 없으면 stock.briefing 재실행 (idempotent)
  • stock.briefing-fallback-2100 — 평일 21:00 — 무조건 fresh fetch로 스냅샷 갱신 (briefing_fallback.py force → 스냅샷 있으면 stock_portfolio_report.py run 메일·텔레그램 X, 없으면 send 폴백 + 실패 시 알림). 20:10 데이터 부정확 케이스 보완용
  • stock.watchlist-monitor — 평일 10:00 / 12:00 / 14:00 — 워치리스트 buy/target/stop 알림 (2026-05-12: 15분 간격 → 3회로 축소)
  • stock.trailing-monitor — 평일 09:0015:30 매 1분 (StartCalendarInterval 391엔트리, 스크립트 self-skip) — trailing_monitor.py check: 트레일링 스톱 감시. ⚠️ 매매 API를 자동 호출하는 유일한 트리거 — 매매 자동 트리거 금지 규칙의 예외로 2026-07-30 관리자님 명시 승인. 호출하는 건 kiwoom_order.modify_order 하나뿐이고 신규 발주·수량·방향 변경 경로가 없음(PIN 승인 시 계좌·종목·계단별 수량·하락률 확정, 감시는 조건단가 상향만. 계단이 체결돼도 재배치=취소+신규발주는 하지 않는다). 예약 0건이면 즉시 종료(API 콜 0). ⚠️ 계단식이라 정정 콜이 계단 수에 비례(예약 3건×5계단이면 분당 최대 15콜). 로그 logs/stock-trailing-monitor.{log,err.log}. ⚠️ cadence 이력 2분→30초→1분(전부 2026-07-30) — launchd 최소 단위가 1분이라 30초는 프로세스 내부 --repeat 2 --gap 30으로 구현했고 CLI 옵션은 살아있음(다시 쓸 땐 plist 인자만 추가). StartInterval=30은 GUI idle 시 발화 보류라 사용 금지
  • stock.ipo-calendar-sync — 매주 금요일 17:00 — IPO 청약·상장 일정 캘린더 등록
  • stock.fomc-calendar-sync — 매월 1일 09:10 — fomc_calendar_sync.py: 연준 FOMC 회의 일정 캘린더 등록·갱신. 연준이 1년 이상 앞서 공표하고 변경이 거의 없어 월 1회로 충분(폴백 트리거 없음). LLM 미경유. 로그 logs/fomc-calendar-sync.{log,err.log}
  • stock.holiday-sync — 매주 일요일 03:00 — investing.com KRX 휴장일 → state/market_holidays.json (behive_web 자동갱신 토글이 참조)
  • stock.send-balance — 매월 1일 04:30 — 본인 잔액 → 골디 inbox (securities_balance)
  • stock.trade-journal — 평일 21:00 — EOD 데이터 누적 묶음. ProgramArguments는 /bin/sh -c wrapper로 세 명령 sequential 실행: ①trade_journal.py collect (ka10170 4계좌 → state/trade_journal.jsonl) ②market_indicators_sync.py collect (네이버 m.stock KOSPI/KOSDAQ ADR·투자자별 매매 → state/market_indicators_history.jsonl) ③cd workspace && python3 -m sim.backfill_flow --pages 1 --force (수급 DB flow_history.sqlite 일일 증분 — 2026-06-10 추가, 1회성 백필 후 stale해져 백테스트 수급게이트가 중립으로 비활성되던 문제 해소. INSERT OR REPLACE idempotent, 행수기준 skip이라 --force 필수) ④python3 -m sim universe all (2026-06-15 추가 — sim 관찰목록 자동편입+자동제외. 키움 순위정보 ka90009 외인·기관 순매수 + ka10023 거래량급증 상위에서 ETF·하락·급증률 이상치 제외하고 각 15개·총 120 상한 편입(origin='auto'), origin=auto·미보유·10거래일 무신호는 자동 제외. 보유·watch·interest·manual은 불가침. 신호추적은 scan_all이 universe.mark_signalsstate/sim/auto_seen.json에 기록). 하나 실패해도 나머지 시도. 로그는 logs/stock-trade-journal.{log,err.log} 한 곳에 합쳐짐.
  • stock.sim-scan — 평일 09:0015:30 매 2분 (StartCalendarInterval 196엔트리, 엔진이 장외/휴장 self-skip) — 자동매매 시뮬(페이퍼) 1회 스캔. (2026-06-09 15분→5분, 2026-06-17 5분→2분 단축 — 스캔 1회 실측 ~3.6초라 2분 cadence에 오버랩·rate limit 여유. 1분이 캘린더 트리거 한계지만 일봉 기반 신호라 2분으로 충분 판단) ⚠️ 2026-06-09 StartInterval 900s → StartCalendarInterval 전환: GUI LaunchAgent의 StartInterval은 세션 idle/디스플레이 sleep 시 timer coalescing으로 발화가 보류(pended)돼 장중에 안 도는 문제 발견 (calendar 잡인 git-autopush는 새벽 02:00에도 정상 발화하는 게 비교 증거). 월시간 기준이라 idle 지연 없음. python3 -m sim scan (cwd agents/stock/workspace). 가상자본 1억, 규칙 엔진(LLM 미경유). universe=누적 관찰목록(워치+관심+보유 자동편입·수동삭제), 방향=애널 약한게이트+가점, 타이밍=수급·기술. 결과 state/sim/. 실주문 orders/ 불가침. 상세는 아래 sim 모듈 섹션·레이 MEMORY.md
  • stock.sim-web — 상시 (KeepAlive) — 시뮬 대시보드 별도 서버 포트 18792 (behive-web 18790과 분리). python3 -m sim.sim_web serve. sim state 읽기전용 렌더. sim.hyowons.net → mac:18792 (Synology reverse proxy, 수동 등록 대기). ⚠️ 18791은 node 점유라 18792 사용
  • gmail-label-classify (main/클로) — 매일 01:00 — workspace/scripts/gmail_label_classify.py: self-sent 브리핑·종목분석·주식 리포트 메일을 Gmail 라벨로 자동 분류 + 24h 지난 테스트메일 휴지통 이동. LLM 미경유(gog CLI 직접 호출). 로그 logs/gmail-label-classify.{log,err.log} + 상태 state/gmail_label_classify.log. ⚠️ 2026-06-26 OpenClaw cron(main 01:00)에서 이관 — 본문 생성 없는 단순 스크립트라 모델 세션 불필요(TASKS.md §3-3 원칙)
  • git-autopush — 매일 02:00 — scripts/git_autopush.sh: 변경 있으면 git add -A + 자동 커밋 + git push origin main. 변경 없으면 skip(빈 커밋 X). 인증은 HTTPS + credential.helper=store(~/.git-credentials), GUI 키체인 불필요. 로그 logs/git-autopush.{log,err.log}. 워크스페이스 버전관리 백업용 (시크릿·sqlite는 .gitignore 제외라 별도 백업 필요)
  • codex-fallback-monitor — 평일·주말 매 5분(StartCalendarInterval Minute 0/5/…/55) — workspace/scripts/codex_fallback_monitor.py: codex 주 모델(openai/gpt-5.5)이 죽어 게이트웨이가 조용히 유료 OpenRouter로 폴백하면 토큰비가 새는 걸 감지→클로(main) 텔레그램 즉시 알림. 신호원=agents/*/sessions/*.trajectory.jsonlmodel.completed 이벤트(승자 모델). 승자 provider==openrouter면 폴백으로 판정(정상=openai/gpt-5.5 codex 하네스). 무비용·온디스크·LLM 미경유. dedupe=state/codex_fallback_monitor.json 워터마크(last_ts)+30분 쿨다운(장기 다운 스팸 방지). 첫 실행은 과거 무시하고 워터마크만 세팅. 발송은 텔레그램 Bot API 직접 HTTP POST(openclaw.json channels.telegram.accounts.default 토큰·allowFrom, 레이 send_telegram 스크립트와 동일 패턴, urllib stdlib). 로그 logs/codex-fallback-monitor.{log,err.log}. ⚠️ codex 에러 자체는 로그·audit(metadata-only)에 안 남고 폴백 시 OpenRouter는 codex 하네스 로그를 안 남겨, trajectory model.completed가 유일하게 신뢰 가능한 승자 신호
  • budget.whooing-sync — 매시 0/15/30/45분 — iMessage 결제문자 → 후잉. 매 사이클 끝에 gahee_reminder.run 추가 호출 (매월 25일 10:00 KST 이후 가희님께 iMessage 리마인더 1회 발신 → 답신 폴링 → 텍스트면 후잉 가희주머니 차액 자동분개, 이미지면 골디 텔레그램 알림). 별도 plist 없음

Agent Inbox Convention

에이전트 간 데이터 hand-off는 파일 기반 inbox로만 한다. LLM-to-LLM 자연어 통신은 프롬프트 인젝션·할루시네이션 증폭 위험이 있어 금지.

디렉터리 구조 (수신자 소유)

agents/<recipient>/inbox/
  ├─ incoming/   ← 새 메시지
  ├─ processed/  ← 처리 완료 후 이동
  └─ failed/     ← 처리 실패 (스키마 오류·미등록 topic 등)

수신자는 자기 inbox를 책임진다 (정기 폴링·청소·감사). 송신자는 incoming/에 쓰는 것까지만.

Envelope (불변 — v1)

{
  "message_id": "uuid",
  "from": "stock",
  "to": "budget",
  "topic": "securities_balance",
  "created_at": "2026-04-26T20:10:00+09:00",
  "schema_version": 1,
  "payload": { ... }
}

파일명: <from>__<topic>__<isoTime>.json (정렬·검색 용이)

원칙

  • payload는 순수 데이터 — 자연어 지시문 금지 (프롬프트 인젝션 차단)
  • idempotency — 수신자는 message_id 중복 처리 안 함
  • 새 topic은 INBOX_TOPICS.md에 등록 필수 — 미등록 topic은 자동 failed/
  • 응답 필요 시 — 수신자가 송신자 inbox에 새 메시지 작성 (양방향 ack 메커니즘 없음)
  • GCprocessed/는 30일 후 정리, failed/는 사람이 검토해서 수동 삭제

상세한 topic 스키마와 운영 규칙은 INBOX_TOPICS.md 참조.

Cody Inbox (agents/cody/inbox/)

코디는 OpenClaw 에이전트가 아니지만 한 가지 예외로 inbox를 가진다. 에이전트가 자체 개선한 결과를 코디에게 검증·후속 개선 위탁하는 단방향 채널이다.

  • 토픽: improvement_review (스키마는 INBOX_TOPICS.md)
  • 자연어 허용 예외: payload summary/rationale/self_review_notes/concerns[].question은 자연어 OK. 단 "X 해줘" 류 지시문 금지, 사실·관찰·우려만
  • 처리 흐름:
    1. 코디 세션 기동 시 incoming/ 개수만 확인 → 1개 이상이면 한 줄 알림 ("📥 코디 인박스에 N개 처리 대기 중입니다.")
    2. 관리자님 명시 요청 전엔 상세 보고·검증·개선 시작 X — 자동 처리 금지
    3. 관리자님이 "검증 큐 확인해줘" 등 호출하면 그때 envelope 상세 요약 → 우선순위 위임
    4. 코디가 changed_paths 검증 → 필요 시 직접 개선 (위험 작업은 별도 컨펌)
    5. envelope을 processed/로 이동, 같은 basename + _report.md에 검증 결과·후속 개선·잔여 위험 기록
    6. GC (같은 시점): processed/의 mtime 7일 초과 항목을 trash로 정리 (envelope JSON + report MD 짝으로). failed/는 손대지 않는다
    7. 스키마 위반은 failed/로 이동 후 관리자님에게 보고
  • 헬퍼 미정: 송신측은 에이전트가 직접 envelope JSON 작성. 패턴 굳으면 추후 추출
  • 회신 envelope 없음: 결과는 processed/의 report 파일로만 남는다. 송신 에이전트가 후속 사이클에서 직접 조회

Communication Rules

  • Respond in Korean (한글)
  • Use polite speech (존댓말)
  • Address the owner as 관리자님
  • End responses with status on a new line: [진행중] or [답변완료]
  • Keep responses short, action-oriented, result-first
  • Avoid unnecessary explanation — How > Why
  • Use trash over rm for deletions

Coding Behavior Rules

LLM 흔한 실수를 줄이기 위한 행동 규칙. 사소한 작업은 판단으로 생략 가능하지만, 불확실하면 caution 쪽으로 기운다.

1. 코딩 전에 생각 (Think Before Coding)

가정하지 말고, 혼동을 숨기지 말고, 트레이드오프를 드러낼 것.

  • 가정은 명시적으로 말한다. 불확실하면 질문한다.
  • 해석이 여러 개면 전부 제시한다 — 조용히 하나 고르지 않는다.
  • 더 단순한 길이 보이면 먼저 말한다. 정당하면 반박한다.
  • 모호하면 멈춘다. 무엇이 헷갈리는지 이름 붙이고 묻는다. (선택지는 AskUserQuestion)

2. 단순함 우선 (Simplicity First)

문제를 푸는 최소 코드. 추측성 코드 금지.

  • 요청 범위를 벗어난 기능 X
  • 1회용 코드의 추상화 X
  • 요청되지 않은 "유연성"·"설정 가능성" X
  • 일어날 수 없는 상황 대비 에러 핸들링 X
  • 200줄 짠 게 50줄로 줄겠다 싶으면 다시 쓴다.

자문: "시니어 엔지니어가 이거 과설계라 할까?" 그렇다면 단순화.

3. 외과적 변경 (Surgical Changes)

필요한 것만 건드린다. 자기가 만든 잔재만 정리한다.

  • 인접 코드·주석·포맷 임의 "개선" 금지
  • 안 망가진 것 리팩토링 금지
  • 다르게 하고 싶어도 기존 스타일 유지
  • 무관한 dead code 발견하면 보고만 — 삭제 X
  • 변경 때문에 생긴 import/변수/함수 orphan은 본인이 정리
  • 사전 존재하던 dead code는 요청 없이 삭제 X

테스트: 변경된 모든 라인은 관리자님 요청에 직결되어야 한다.

4. 목표 기반 실행 (Goal-Driven Execution)

성공 기준을 정의하고, 검증될 때까지 루프.

  • "validation 추가" → "잘못된 입력 테스트 작성 → 통과시키기"
  • "버그 고쳐" → "재현 테스트 작성 → 통과시키기"
  • "X 리팩토링" → "전후 테스트 통과 확인"

다단계 작업은 짧은 plan을 먼저 말한다:

1. [단계] → 검증: [확인]
2. [단계] → 검증: [확인]

강한 성공 기준은 독립적 루프를 가능케 하고, 약한 기준("동작하게")은 끊임없는 명세 요청을 부른다.