| name | market-summary |
| description | market_summary 프로젝트의 Market Story(일간/주간/월간 시장 해설) 작성 스킬. 해당 날짜의 _data.json과 웹 검색을 기반으로 시간순 인과관계가 정확한 시장 해설을 작성하고 HTML에 주입한다. 사용 시점: 일간/주간/월간 보고서의 Story 탭을 작성·수정할 때, '4/8 스토리 써줘', '이번 주 주간 스토리 작성', '3월 월간 스토리' 같은 요청이 들어올 때. |
| argument-hint | [target_date: YYYY-MM-DD] [period: daily|weekly|monthly] |
| metadata | {"author":"lifesailor","version":"1.1.0"} |
Market Story 작성 스킬
market_summary 프로젝트의 일간/주간/월간 Market Story를 작성한다. Data Dashboard는 generate.py가 만들고, Story만 Claude가 작성한다. 이 스킬은 작성 규칙·절차·주입 방법을 담는다.
When to Use
- 사용자가 특정 날짜의 일간/주간/월간 Market Story 작성을 요청할 때
/market-full 커맨드의 일부로 호출될 때 (Step 3, 5, 7)
- 기존 Story를 검증·수정할 때
When NOT to use: 데이터 수집이나 HTML 대시보드 생성만 필요한 경우 → /market-data 커맨드 사용
프로젝트 위치
├── output/summary/YYYY-MM/YYYY-MM-DD.html # 일간 보고서 (Story 주입 대상)
├── output/summary/YYYY-MM/YYYY-MM-DD_data.json # 일간 원시 데이터 (Story 입력)
├── output/summary/YYYY-MM/YYYY-MM-DD_story.html # 일간 Story 별도 저장
├── output/summary/weekly/YYYY-WNN.html # 주간 보고서
├── output/summary/weekly/YYYY-WNN_story.html # 주간 Story
├── output/summary/monthly/YYYY-MM.html # 월간 보고서
└── output/summary/monthly/YYYY-MM_story.html # 월간 Story
핵심 규칙 (반드시 준수)
0. 문체: 존댓말(합니다체)
- Market Story는 **항상 존댓말(~했습니다, ~됐습니다, ~입니다)**로 작성한다
- 반말(~했다, ~됐다, ~이다) 금지
- Session Grid의
<li> 이벤트 항목도 동일하게 존댓말 적용
- 예: "코스피는 +0.75%로 마감했습니다" (O) / "코스피는 +0.75%로 마감했다" (X)
1. Forward Looking 금지
- 일간: 보고서 날짜 다음날 08:00 KST 이전까지의 정보만 사용
- 주간: 해당 주 금요일(또는 마지막 영업일)까지의 정보만 사용
- 월간: 해당 월 마지막 영업일까지의 정보만 사용
- 이후 날짜의 사건/데이터/결과를 절대 참조하지 않는다
- 허용: "~할 수 있다", "~가능성이 있다" (분석·전망)
- 금지: "이후 실제로 ~했다", "~의 서막이었다" (사후 참조)
2. 08:00 KST 생성 시점 기준
- 보고서는 매일 08:00 KST에 생성된다고 가정
- 예: 2026-04-07 보고서 → 2026-04-08 08:00 KST에 생성
- 이 시점 기준으로 사용 가능한 데이터:
- 4/7 아시아·유럽·미국 세션 전체 (이미 마감)
- 4/8 아시아 프리마켓 뉴스 (08시 이전만)
- 4/7까지의 가격 데이터 (
_data.json)
- 사용 불가: 4/8 09시 이후 아시아 장중, 유럽/미국 세션 데이터
3. 세션별 마감 시각 (KST) — 세션 간 미래 참조 금지
| 세션 | 시장 | 마감 시각 |
|---|
| 아시아 | KOSPI | 15:30 |
| 아시아 | Nikkei | 15:00 |
| 아시아 | Shanghai | 16:00 |
| 유럽 | STOXX/DAX/CAC | 01:30 (서머타임 00:30) |
| 미국 | S&P/NASDAQ | 06:00 (서머타임 05:00) |
- 아시아 세션 서술: 같은 날 유럽/미국 이벤트 참조 금지
- 유럽 세션 서술: 유럽 마감 이후 발생한 미국 이벤트 참조 금지
- 미국 세션 서술: 같은 날 아시아/유럽 참조 가능 (시간순 OK)
흔한 위반:
- "유럽 시장은 유가 급락을 소화하며 하락" — 유가 급락이 미국 세션에 일어났다면 위반
- "KOSPI는 트럼프 관세 유예 소식에 반등" — 발표가 미국 세션이었다면 KOSPI 마감 이후
4. 인과관계 방향 (과거 → 현재)
- 금지: "월요일의 하락은 수요일 대반등의 서막이었다" (월요일 시점에서 수요일을 알 수 없음)
- 금지: "이 하락은 시작에 불과했다" (미래 하락 암시)
- 금지: "~의 전초전이었다", "~을 예고하는 듯했다" (사후적 프레이밍)
- 허용: "수요일은 월·화요일의 과매도를 되돌리는 반등이었다" (과거 참조)
- 허용: "이 수준이 지속될 경우 추가 조정 가능성" (전망)
5. 주간/월간 내 일간 간 미래 참조 금지
- 전체 기간 요약은 허용 (예: "롤러코스터 같은 한 주")
- 특정 날짜를 설명할 때 그 날짜 이후 이벤트를 원인·맥락으로 사용 금지
- 금지: "4/2의 유가 폭등을 고려하면 3/30의 하락은 시작에 불과했다"
- 허용: 전체 주를 시간순으로 나열하며 각 날짜의 팩트를 기술
6. 요일·휴일 정확성 — 반드시 데이터로 검증
7. 고점·저점 표현 전 반드시 CSV 검증
"사상 최고치", "연내 신고점", "YTD 최고", "52주 고점" 등 기간 고점·저점 표현은 history/market_data.csv를 직접 조회해 검증한 후에만 사용한다.
검증 절차 (고점 주장 전 필수 실행):
grep "EQ_KOSPI" history/market_data.csv | grep "^2026" | sort | awk -F',' '{print $1, $5}' | sort -k2 -rn | head -5
- 해당일 종가가 조회 결과 1위일 때만 "연내 신고점" 사용
- 1위가 아니면 "4월 신고점", "월간 최고치", "최근 N일 최고치" 등 실제로 맞는 범위로 축약
_data.json의 spark·YTD·weekly·monthly 필드는 상대 변동률이므로 고점 증명 불가
- verdict 배지·causal node·헤드라인 어디에 쓰든 동일 규칙 적용
흔한 오류 패턴:
- KOSPI +2%대 랠리 → "연내 신고점" (❌ — 2월 고점이 더 높을 수 있음)
- YTD +8% → "사상 최고치" (❌ — YTD 수익률과 절대 고점은 무관)
- 최근 급등 → "역대 최고" (❌ — 반드시 전체 시계열 확인)
8. Story Hero 세션 간 <br><br> 여백
<div class="story-hero"> 내부 <div class="story-text">에서 아시아 → 유럽 → 미국 세션 문단을 <br><br>로 분리
- 단일
<br>만 쓰면 세션이 한 덩어리로 붙어 가독성이 떨어진다
- 세션 도입부(서두 → 첫 세션)와 세션 종료 후(마지막 세션 → VIX/마무리 단락)에도
<br><br> 유지
- Session Grid 영역(
session-grid)은 CSS로 이미 분리돼 있어 불필요. Story Hero 텍스트 블록에만 해당
9. Story Hero 세션별 간결성
Story Hero의 아시아/유럽/미국 세션 서술은 세션 수준의 핵심 요약만 작성한다:
금지 패턴 (시간별 micro-detail):
- ❌ "09:00 코스피 6,619.00 출발 → 11:00 장중 고점 6,702.38 → 15:30 마감 6,690.90"
- ❌ "아침 서울 외환시장에서 원/달러는 1,474원으로 출발해 UAE OPEC 탈퇴 발표에 일시 강세..."
- ❌ "삼성전자 +1.80%(226,000원), SK하이닉스 −0.54%(1,293,000원)는..."
권장 패턴 (핵심 흐름 중심):
- ✅ "코스피는 에너지/화학 섹터 +5.03% 급등에 힘입어 3거래일 연속 사상 최고 종가 6,690.90(+0.75%)를 기록"
- ✅ "원/달러는 유가 상승 영향으로 1,479원 수준으로 약세 전환"
- ✅ "상하이 +0.71%, 항셍 +1.68%, 알리바바·메이퇀·텐센트 등 인터넷주 일제 강세"
세션당 길이: 3-5 문장 (현재 평균 10-12 문장 → 절반 축약)
Session Grid와 역할 분담:
- Story Hero: 세션별 핵심만 (why, what happened)
- Session Grid: 시간별 타임라인 상세 (
09:00, 11:00, 15:30 이벤트)
일간 Story 작성 절차
Step 1: 입력 확인
output/summary/{YYYY-MM}/{YYYY-MM-DD}_data.json # 해당일 가격·변동률 데이터
output/summary/{YYYY-MM}/{YYYY-MM-DD}.html # 주입 대상 HTML (이미 존재)
_data.json의 holiday 필드, 각 자산의 종가/변동률 확인
- KOSPI/KOSDAQ 휴장일이면 해당 사실을 명시하고 Story 작성
Step 2: 시간순 웹 데이터 수집
반드시 시간 순서로 수집:
- 아시아 세션 (09:00~15:30 KST): 한국/일본/중국 시장 + 경제지표 + 아시아 지정학
- 유럽 세션 (16:00~01:30 KST): 유럽 시장 + ECB/BOE 발언 + 유럽 경제지표
- 미국 세션 (22:30~06:00 KST): 미국 경제지표 + Fed 발언 + 기업 실적 + 장중 흐름
검색 시 주의:
- 쿼리에 정확한 날짜를 넣어 미래 데이터 차단 (
"April 7 2026" 같은 식)
- 당일 09시 이후 장중 데이터 검색 금지
- 훅(
PreToolUse WebSearch|WebFetch)이 자동 검증하므로 block되면 쿼리 수정
Step 3: Story 작성 (6개 섹션 템플릿)
표기 규칙:
- 지수·종목·통화명은 한국어 단독 사용:
코스피, 나스닥, 금, 달러/원, WTI유
- 영문 약어도 한국어로:
사상 최고가, 연초 대비, 주간 대비, 월간 대비, 장단기 스프레드
- 색상 컨벤션: 상승=빨간(
hl-up), 하락=파란(hl-down) — 한국 주식창 기준
Story는 정확히 6개 섹션을 이 순서대로 구성합니다:
- Story Hero · 2. Causal Chain · 3. Session Grid · 4. Insight Grid · 5. Risk Section · 6. WTD/MTD Progress
제외된 섹션 (사용 금지): Cross-Asset Flow Map (.cross-asset, .af-map) — 억지 인과 유발로 제거됨.
기존 일간 _story.html을 Read로 확인해 구조 파악 후 작성.
템플릿: 일간 Story 구조
<div class="story-hero">
<h2>오늘의 시장 이야기</h2>
<div class="story-text">
<strong>[한 줄 헤드라인: 오늘의 극단 이벤트 또는 테마]</strong><br><br>
<strong>아시아 세션</strong>은 [시간 범위(09:00~15:30)] [주요 흐름을 한 문장으로].
[지수/섹터별 상세 1-2 문장] [수치와 함께 전개] ... <br><br>
<strong>유럽 세션</strong>은 [시간 범위(16:00~01:30)] [주요 이벤트].
[지수·상품·통화 전개] ... <br><br>
<strong>미국 세션</strong>은 [시간 범위(22:30~06:00)] [주요 이벤트].
[지수·금리·상품 마감] ... [다음날 기대사항도 가능]
</div>
</div>
<div style="margin-bottom:12px;font-size:15px;font-weight:600;color:#1a1d2e;">오늘의 핵심 흐름 — 하나의 체인으로 이해하기</div>
<div style="font-size:12px;color:var(--muted);margin-bottom:16px;">[원인 1] → [중간 결과] → [최종 결과] 형태로 한 줄 요약</div>
<div class="causal-chain">
<div class="cause-node">
<div class="node-label">[카테고리: 지정학/거시/기업 등]</div>
<div class="node-title">[핵심 이벤트]</div>
<div class="node-detail">[상세 설명 1-2 문장]</div>
<div class="node-impact up/down/flat">[영향 방향]</div>
</div>
<div class="cause-arrow">→</div>
[2번째~5번째 노드 반복]
</div>
<div style="margin-bottom:12px;font-size:15px;font-weight:600;color:#1a1d2e;">세계 시장은 릴레이처럼 돌아갑니다</div>
<div style="font-size:12px;color:var(--muted);margin-bottom:16px;">[날짜 요약] — [3 세션을 한 문장으로 대조]</div>
<div class="session-grid">
<div class="session-block asia">
<div class="session-header">
<div class="session-icon asia">🇰🇷</div>
<div>
<div class="session-name">아시아 세션</div>
<div class="session-time">한국 09:00 ~ 15:30</div>
</div>
</div>
<span class="session-verdict verdict-up/down">오늘의 아시아 시황 한 줄</span>
<ul class="session-events">
<li><span class="ev-time">09:00</span> [시간별 사건 1]</li>
<li><span class="ev-time">12:00</span> [시간별 사건 2]</li>
<li><span class="ev-time">15:30</span> [시간별 사건 3 + 마감]</li>
</ul>
<div class="session-kpi">
<div class="s-kpi"><div class="s-kpi-label">KOSPI</div><div class="s-kpi-value up/down">+0.39% / −0.49%</div></div>
<div class="s-kpi"><div class="s-kpi-label">[2번째 지수]</div><div class="s-kpi-value up/down">값</div></div>
<div class="s-kpi"><div class="s-kpi-label">[3번째 지수]</div><div class="s-kpi-value up/down">값</div></div>
</div>
</div>
</div>
<div style="margin-bottom:12px;font-size:15px;font-weight:600;color:#1a1d2e;">오늘의 핵심 학습</div>
<div class="insight-grid">
<div class="insight-card">
<div class="badge">인사이트 1</div>
<div style="font-weight:600;margin-bottom:6px;">[제목]</div>
<div style="font-size:13px;">[1-2 문장 설명]</div>
</div>
[2번째~4번째 카드 반복]
</div>
### Insight Grid 작성 원칙
1. **제목 배치**: `<div class="insight-grid">` **밖**에 제목 div 배치 (grid item 충돌 방지)
2. **카드 개수**: 정확히 4개 (2열 × 2행 배치)
3. **카드 내용**: badge + 제목(bold) + **본문 3-4문장**(투자자 관점 해설) + metric-row(핵심 수치 2개)
4. **Badge**: 해당 인사이트의 키워드 (예: "Apple", "BOJ", "UAE 탈퇴", "코스피 8위")
5. **metric-row 필수**: 각 카드 하단에 관련 핵심 수치 2개를 `metric-row` > `metric-item` 구조로 표시
**품질 기준 예시** (2026-04-28 참고):
```html
<div style="margin-bottom:12px;font-size:15px;font-weight:600;color:#1a1d2e;">오늘의 핵심 학습</div>
<div class="insight-grid">
<div class="insight-card">
<span class="badge">코스피 8위</span>
<div style="font-weight:600;color:#1a1d2e;margin-bottom:8px;">코스피 6,700 첫 돌파 — 한국 시총 세계 8위 부상</div>
<div style="font-size:13px;color:#2d3148;line-height:1.7;">장중 6,712.73까지 올라 사상 첫 6,700선 돌파. 종가 6,641.02로 다시 사상 최고 갱신. Bloomberg은 한국 시총이 $4조를 넘어 영국을 추월, 세계 8위로 올라섰다고 보도. 삼성전자·SK하이닉스 2사가 코스피 시총의 40%+ 차지.</div>
<div class="metric-row"><div class="metric-item"><div class="metric-label">코스피 장중 최고</div><div class="metric-value up">6,712.73</div></div><div class="metric-item"><div class="metric-label">연초 대비</div><div class="metric-value up">+54.10%</div></div></div>
</div>
</div>
부실 카드 금지: "1-2 문장으로 끝나는 얕은 설명"은 부실로 간주. 각 카드는 왜 중요한지(So what?)를 투자자 관점에서 설명해야 함.
잘못된 구조 (금지):
<div class="insight-grid">
<div style="...">오늘의 핵심 학습</div>
<div class="insight-card">...</div>
...
</div>
⚠️ 이번 주 주목할 리스크
-
[위험도]
[리스크 요인]: [설명 1-2 문장] — [대응 또는 전망]
-
[2번째~3번째 리스크]
주간 누적 (W## · 전주 금요일 종가 대비 · MM/DD 기준)
- 핵심 지수: KOSPI [%], S&P500 [%], ...
- 원자재: [상품] [%], [상품] [%], ...
- 채권: US 10Y [bp], US 2Y [bp], KR 10Y [bp]
- FX: DXY [%], 원/달러 [%]
월간 누적 (#월 · 전월 말 종가 대비 · MM/DD 기준)
- 핵심 지수: KOSPI [%], S&P500 [%], ...
- 원자재: [상품] [%], [상품] [%], ...
- 채권: US 10Y [bp], KR 10Y [bp]
- FX: DXY [%], 원/달러 [%]
WTD/MTD 카드 작성 원칙
- 카드 스타일 적용: 배경색, 테두리, 그림자로 시각적 구분
- 제목 간결화: "전주 금요일 종가 기준" 같은 중복 설명 제거
- 데이터 정렬: 핵심 지수 → 원자재 → 채권 → FX 순서 유지
- 간격 개선:
line-height:1.7, gap:16px로 가독성 향상
스타일 필수 요소:
- 카드 배경:
background:var(--card)
- 테두리:
border:1px solid var(--border)
- 둥근 모서리:
border-radius:12px
- 그림자:
box-shadow:0 1px 3px rgba(0,0,0,0.04)
- 제목 하단선:
border-bottom:1px solid var(--border)
금지 패턴 (이전 구조):
<div style="margin-top:24px;display:grid;grid-template-columns:1fr 1fr;gap:20px;">
<div>
<h3>주간 누적 (W## · #/5 영업일 경과)</h3>
<ul style="font-size:13px;line-height:1.6;">
권장 패턴:
<div style="margin-top:24px;display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div style="background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.04);">
<h3 style="font-size:14px;font-weight:600;color:#1a1d2e;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--border);">주간 누적 (W## · 전주 금요일 종가 대비 · MM/DD 기준)</h3>
<ul style="font-size:13px;line-height:1.7;margin:0;padding-left:18px;">
---
#### 작성 규칙 (템플릿 사용 시)
1. **모든 6개 섹션 필수** — 하나라도 빠지면 구조 검증 실패 (Cross-Asset Flow Map 제외)
2. **Verdict 배지 필수** (Session Grid 내 각 세션):
- `verdict-up`: 강세/긍정적 흐름
- `verdict-down`: 약세/부정적 흐름
- `verdict-flat`: 보합/중립
3. **시간 정확성**:
- 아시아: 09:00~15:30
- 유럽: 16:00~01:30 (또는 17:00~00:30, 서머타임 차이)
- 미국: 22:30~06:00 (또는 23:30~05:00, 서머타임 차이)
4. **세션 간 미래 참조 금지** (위의 "3. 세션별 마감 시각 규칙" 참조)
5. **WTD/MTD 수치 직접 계산** (Step 3-2 참조)
#### Step 3-1: 필수 섹션 체크리스트 (반드시 확인)
일간 Story는 **반드시 다음 6개 섹션을 모두 포함**해야 합니다. 하나라도 빠지면 구조 검증 실패.
- [ ] **1. Story Hero** — `<div class="story-hero">` + `<div class="story-text">`
- 한 줄 헤드라인 (강조: 최고·최저·주요 이벤트)
- 세션별 상세 서술 (아시아 → 유럽 → 미국, `<br><br>` 구분)
- [ ] **2. Causal Chain** — `<div class="causal-chain">` + `<div class="cause-node">` ×3~5
- 원인→결과 체인 (3~5단계)
- 각 노드: label, title, detail, node-impact(up/down/flat)
- [ ] **3. Session Grid** — `<div class="session-grid">` + `<div class="session-block">` ×3 (asia/europe/us)
- 각 세션 블록: header(아이콘+시간대) + verdict 배지 + events + s-kpi 그리드
- **Verdict 배지** (필수): `<span class="session-verdict verdict-up/down/flat">` (한 줄 평가)
- [ ] **4. Insight Grid** — `<div class="insight-grid">` + `<div class="insight-card">` ×4
- 4개 교육 카드 (투자자 관점 핵심 학습)
- [ ] **5. Risk Section** — `<div class="risk-section">` + `<ul class="risk-items">` + `<li class="risk-item">` ×2~3
- 2~3개 리스크 요인 (전망/가능성 표현)
- [ ] **6. WTD/MTD Progress** — `<h3>` + `<ul>` 2세트
- WTD/MTD 누적 지수 + 테마 (별도 섹션)
---
#### Step 3-2: Weekly & Monthly Progress 단락 작성
주간/월간 Story는 **마지막 영업일에만** 작성되므로, 일간 Story 안에 WTD(Week-to-Date)·MTD(Month-to-Date) 한 단락씩을 포함해 주 중간에도 누적 흐름을 볼 수 있게 한다.
**데이터 소스**: `_data.json`의 `weekly` 필드는 **ISO WTD** (전주 금요일 종가 대비), `monthly` 필드는 **Calendar MTD** (전월 말 종가 대비)입니다. `verify_report_numbers.py`와 동일 기준이므로 그대로 사용 가능합니다.
**WTD(Week-to-Date) 계산 규칙**:
- 기준선: **ISO 주의 전주 마지막 영업일 종가** (보통 전주 금요일)
- WTD = `(target_date 종가 / 전주 금요일 종가 - 1) × 100`
- 예: W17 화요일(4/21) 보고서 → 기준선 = 4/17 금요일 종가
- 직접 `history/market_data.csv` 에서 두 날짜 종가를 추출해 계산
```python
# 예시 스니펫 (Python)
import pandas as pd
df = pd.read_csv('history/market_data.csv')
df['DATE'] = pd.to_datetime(df['DATE'])
# 전주 금요일 찾기: target_date 의 ISO 주 1일(월) 의 전일(= 전주 일요일) 이전의 가장 가까운 영업일
# 간단히: target_date 의 weekday() + 3 을 빼면 바로 전주 금요일
import datetime as dt
t = dt.date(2026, 4, 21) # target = 화요일
prev_fri = t - dt.timedelta(days=t.weekday() + 3) # → 4/17 금
fri_close = df[(df['INDICATOR_CODE']=='EQ_KOSPI') & (df['DATE']==str(prev_fri))]['CLOSE'].values[0]
tue_close = df[(df['INDICATOR_CODE']=='EQ_KOSPI') & (df['DATE']==str(t))]['CLOSE'].values[0]
wtd = (tue_close/fri_close - 1) * 100
MTD(Month-to-Date) 계산 규칙:
- 기준선: 전월 마지막 영업일 종가
- MTD =
(target_date 종가 / 전월 마지막 영업일 종가 - 1) × 100
_data.json 의 monthly 필드도 롤링 30일 기준일 수 있어 직접 계산 권장
작성 형식 (예시):
<h3>주간 누적 (W15 · 3/5 영업일 경과 · 전주 금요일 종가 기준)</h3>
<ul>
<li>핵심 지수 (Fri 4/17 → Wed 4/22): KOSPI +2.5%, S&P500 −0.6%, NASDAQ −1.2%, DXY +0.4%</li>
<li>이번 주 흐름: (시간순 사실 나열, 사후적 프레이밍 금지)</li>
</ul>
<h3>월간 누적 (4월 · 7/22 영업일 경과 · 3월 말 종가 기준)</h3>
<ul>
<li>핵심 지수: KOSPI +3.2%, S&P500 +0.4%, Gold +2.1%, WTI −5.8%</li>
<li>월초 이후 테마: (시간순 서술)</li>
</ul>
규칙:
- 경과 영업일 수 표기 필수: "3/5 영업일 경과" + 기준선 명시("전주 금요일 종가 기준")
- 수치는 직접 계산:
_data.json 의 weekly/monthly 필드는 WTD/MTD 와 다른 롤링 값. 신뢰 금지.
- 테마 한 줄: 일간 Narrative와 중복 피하며 주/월 전체 관점에서 한 문장 압축
- Forward looking 금지: 오늘 이후 이벤트 참조 금지
- 마지막 영업일의 일간 Story: "W15, 5/5 영업일 경과 — 주간 마감" 표기. 주간 Story 가 같은 날 작성되므로 이 단락은 간결하게.
작성 중 자가 검증:
- 각 문장의 인과관계가 시간순인가?
- 세션별 서술에서 해당 세션 마감 이후 이벤트를 참조하지 않았는가?
- 요일·휴일이
_data.json과 일치하는가?
Step 4: HTML 주입
(A) 신규 일간 생성 — generate.py가 자동 처리
.venv/bin/python generate.py {date} 실행 시 내부적으로 _inject_existing_story()가 호출되어 Story 탭 placeholder 처리와 _story.html 저장까지 모두 자동이다. 외부에서 이 함수를 직접 호출할 필요 없다.
(B) 이미 존재하는 Story를 수정할 때
방법 1 (권장) — tab-story 블록 직접 Edit:
output/summary/YYYY-MM/YYYY-MM-DD.html에서 <div id="tab-story" class="tab-panel"> ~ </div><!-- /tab-story --> 사이 블록을 Edit 도구로 교체
- 같은 내용으로
output/summary/YYYY-MM/YYYY-MM-DD_story.html도 Edit (두 파일 동기화)
- 장점: 대시보드·CSS·탭 구조 손상 위험 없음
방법 2 — placeholder 복원 후 치환:
.venv/bin/python generate.py {date} 실행 → 쉘 재생성 (기존 HTML이 건강하면 Story 보존됨)
_story.html을 수정한 뒤 짧은 Python 스니펫으로 daily HTML의 <!-- STORY_CONTENT_PLACEHOLDER --> 를 치환
함정: _inject_existing_story() 외부 직접 호출 금지
_inject_existing_story(path, new_html)의 두 번째 인자는 **반드시 <!-- STORY_CONTENT_PLACEHOLDER --> 마커를 포함한 "새 HTML 템플릿 전체"**여야 한다. Story fragment만 넘기면 함수는 placeholder를 찾지 못하고 fragment 자체를 path 파일에 통째로 덮어써서 대시보드·CSS·탭이 모두 사라진다. 이 함수는 generate.py 내부에서만 쓰고, 외부에서 Story를 수정할 때는 위의 (B) 방법 1 또는 2를 사용할 것.
과거 사고 사례 (2026-04-08): _inject_existing_story('.../2026-04-08.html', story_html)을 외부에서 호출해 960줄 daily HTML이 345줄 fragment로 덮어써진 사고 발생. 복구를 위해 generate.py 재실행 + placeholder 치환이 필요했음.
주입 후 검증 필수 — 보고서 작성 끝에 항상 실행
(1) 구조 검증
{date}.html이 <!DOCTYPE html>, tab-story, tab-data, <style> 블록을 모두 포함하는지 확인
{date}_story.html 파일이 생성/갱신되었는지 확인
- 두 파일의 Story 내용이 동일한지(동기화) 확인
- 이전 영업일
{prev_date}.html 과 라인 수 비교 (수백 줄 차이 시 의심)
(2) 필수 섹션 체크 — 모든 섹션이 Story 에 있어야 한다
실제 검증 스니펫:
.venv/bin/python -c "
import re
html = open('output/summary/YYYY-MM/YYYY-MM-DD.html').read()
required = ['story-hero','causal-chain','session-grid','insight-grid','risk-section','risk-items']
missing = [c for c in required if f'class=\"{c}' not in html and f'class=\"... {c}' not in html]
print('missing:', missing if missing else 'none ✓')
"
(3) CSS 클래스 검증 — Story 에서 쓰는 클래스가 <style> 에 정의돼 있는가
이것은 매번 필수. 과거 사례(2026-04-21): Claude 가 임의로 key-insights, insight-title, risk-cards, risk-card high/medium/low 같은 존재하지 않는 클래스를 써서 CSS 가 적용 안 된 채 발행됨.
허용된 Story 전용 클래스 화이트리스트 (이외는 금지):
story-hero, story-text
causal-chain, cause-node, cause-arrow, node-label, node-title, node-detail, node-impact (up|down|flat)
session-grid, session-block (asia|europe|us), session-header, session-icon, session-name, session-time,
session-verdict (verdict-up|verdict-down|verdict-flat), session-events, ev-time, session-kpi, s-kpi, s-kpi-label, s-kpi-value
insight-grid, insight-card, badge, metric-row, metric-item, metric-label, metric-value (up|down)
risk-section, risk-items, risk-item, risk-tag (high|med|low)
hl-up, hl-down, hl-warn, hl-accent
커스텀 클래스 도입 금지. 새 클래스가 정말 필요하면 먼저 <style> 블록에 정의 추가 후 사용.
실제 검증 스니펫:
.venv/bin/python -c "
import re
html = open('output/summary/YYYY-MM/YYYY-MM-DD.html').read()
css_block = re.search(r'<style>(.*?)</style>', html, re.DOTALL).group(1)
story_block = re.search(r'id=\"tab-story\"(.*?)</div><!-- /tab-story', html, re.DOTALL).group(1)
used = set(re.findall(r'class=\"([^\"]+)\"', story_block))
used_classes = set(c for cs in used for c in cs.split())
defined = set(re.findall(r'\.([a-z][a-z0-9_-]*)', css_block))
undefined = [c for c in sorted(used_classes) if c not in defined]
print('undefined:', undefined if undefined else 'none ✓')
"
(4) WTD/MTD 수치 일치 검증
history/market_data.csv 에서 전주 금요일 종가 + 오늘 종가 두 값을 뽑아 직접 계산
- Story 에 쓴 수치와 일치하는지 확인 (소수점 2자리 허용 오차)
주간/월간 Story 작성 절차
주간 Story (마지막 영업일 작성)
구조 (템플릿)
<div class="story-hero">
<h2>이번 주 시장 이야기</h2>
<div class="story-text">
<strong>[한 줄 주간 테마: 관통하는 하나의 내러티브]</strong><br><br>
[월요일부터 금요일까지 시간순 서술]
- 월요일: [주요 이벤트 + 영향]
- 화요일: [연쇄 반응]
- ...
- 금요일: [주간 마감]
</div>
</div>
주간 작성 규칙
- 일간 Story 5개 수집 (월~금, 공휴일 제외)
- 주간 관점: 일간 세부를 거둬내고 한 주를 관통하는 테마 강조
- 특정 날짜 서술 금지: "월요일의 하락은 수요일 랠리의 서막이었다" (사후 참조) ❌
- 허용: "월요일 하락 후 수요일이 과매도를 되돌리며 반등" ✅
- WTD 수치 (Step 3-2 참조): 전주 금요일 종가 기준 계산
- 선택 섹션: Top Movers, Sector Rotation 등은 필요시만 추가
월간 Story (마지막 영업일 작성)
구조 (템플릿)
<div class="story-hero">
<h2>이번 달 시장 이야기</h2>
<div class="story-text">
<strong>[한 줄 월간 테마: 한 달을 관통하는 거시 내러티브]</strong><br><br>
[월초 ~ 월말 시간순 서술]
- 상반부(1~10일): [주요 이벤트]
- 중반부(11~20일): [연쇄 반응]
- 하반부(21~말): [마무리]
</div>
</div>
월간 작성 규칙
- 일간 Story 20개+ 수집 (월초 ~ 월말, 공휴일 제외)
- 월간 관점: 주간 이슈들을 거둬내고 달을 관통하는 거시 테마 강조
- 특정 주/날짜 서술 금지: "초반 하락은 말미 반등의 기초였다" (사후 참조) ❌
- 허용: "3주 연속 하락 후 4주째 반등" (팩트 나열) ✅
- MTD 수치 (Step 3-2 참조): 전월 말 종가 기준 계산
- 섹터/국가 회전: 월간에만 "S&P 톱 5/바텀 5 섹터" 추가 고려
공통 규칙 (일간/주간/월간)
| 항목 | 일간 | 주간 | 월간 |
|---|
| 세션 구분 | ✅ 3 세션 (Asia/EU/US) | ❌ (통합) | ❌ (통합) |
| 시간별 상세 | ✅ ev-time | ❌ | ❌ |
| Causal Chain | ✅ 3~5 노드 | ✅ 4~6 노드 | ✅ 4~6 노드 |
| Insight Grid | ✅ 4개 | ✅ 4개 | ✅ 4개 |
| Risk Section | ✅ 2~3개 | ✅ 2~3개 | ✅ 2~3개 |
| 기간 진행 | ✅ WTD/MTD | ✅ WTD | ✅ MTD |
검증 체크리스트 (일간/주간/월간 공통)
작성 후 반드시 다음을 확인:
Step 3: HTML 주입
- 대상:
output/summary/weekly/YYYY-WNN.html
- 주간 보고서가 아직 없으면 먼저
.venv/bin/python generate_periodic.py {year}로 생성
- 일간과 동일하게
_inject_existing_story() 사용, _story.html 저장 확인
월간 Story 작성 절차
Step 1: 해당 월 일간 Story 수집
- 해당 월의 모든 영업일 식별
- 각 날짜
_story.html Read
- 주차별 요약(각 주의 테마)을 중간 단위로 활용 가능
Step 2: 월간 관점 종합
- 월 전체 테마 도출
- 월초·월중·월말 구분하여 흐름 서술
- 월간 누적 수익률, 최대 낙폭, 주요 터닝 포인트
- 월간 주요 이벤트(FOMC, 고용보고서, 실적 시즌 등) 맥락화
- 주의: 월말 관점에서 월초를 설명할 때도 당시 시점에서 알 수 없던 정보 금지
Step 3: HTML 주입
- 대상:
output/summary/monthly/YYYY-MM.html
- 월간 보고서가 없으면
.venv/bin/python generate_periodic.py {year} 선행
- 일간·주간과 동일한 주입 방식
분기 Story 작성 절차
분기(Quarterly) Story는 월간 3개를 종합한 상위 서사. 월간 절차의 확장판으로, 기간만 3개월로 확대.
Step 1: 해당 분기 월간 Story 수집
- 3개월치
output/summary/monthly/YYYY-MM_story.html Read (분기 = 캘린더 기준 3개월: Q1=13월, Q2=46월 …)
- 3개월치
_pm.html, _macro.html 도 필요 시 참조 (사실·수치 정합)
Step 2: 분기 관점 종합
- 분기 전체 테마 도출 ("1~2월 X 주도 → 3월 Y 반전" 같은 월별 리듬)
- 월별 흐름 요약 (3개월을 한 화면에): 각 월의 대표 이벤트 2~3개씩
- 분기 누적 수익률 (QoQ), 최대 낙폭, 분기 내 ATH/ATL, 자산 분화
- 분기 핵심 이벤트 맥락화 (FOMC 2~3회, 주요 고용 발표 3회, CPI 3회, 지정학 이벤트)
- 주의: 분기 말 관점에서 분기 초를 설명할 때도 당시 시점에서 알 수 없던 정보 금지
Step 3: HTML 주입
- 대상:
output/summary/quarterly/YYYY-QN.html (파일명 2026-Q1.html 형식)
- 분기 보고서가 없으면
.venv/bin/python generate_periodic.py {year} --only quarterly --quarter N 선행
- 주입 방식은 일간·주간·월간과 동일 — sibling 파일(
_story.html, _pm.html, _macro.html) 저장 후 generate_periodic.py 재실행하면 _inject_existing_*() 가 자동 주입
데이터 소스
- Snowflake MKT100 / MKT200 단일 정본 (via
market_source.load_long / load_macro_long). CSV (history/market_data.csv, history/macro_indicators.csv) 는 legacy fallback
- 분기 매크로 데이터 백필이 필요하면
.venv/bin/python -m collectors.macro --start YYYY-MM-DD 으로 FRED + ECOS 재수집 → MKT200 upsert
CS Story 작성 절차
CS(Customer Success) 관점 스토리는 기존 Market Story를 재작성 하여 수치를 최대한 제거하고 맥락·흐름 위주로 서술한다. 일반 Story가 투자 의사결정용이라면 CS Story 는 고객에게 시장 상황을 "이야기로" 전달하는 용도다.
전제
- 선행 조건: 해당 날짜의 일반 Story 가 이미
_story.html 에 존재
- 대상 탭:
<div id="tab-cs"> / placeholder <!-- CS_STORY_PLACEHOLDER -->
- 별도 파일 저장:
YYYY-MM-DD_cs.html (generate.py 가 sibling 으로 자동 저장)
- 적용 범위: 일간·주간·월간 모두 동일 패턴
Step 1: 기존 Story 읽기
output/summary/YYYY-MM/YYYY-MM-DD_story.html 을 Read. 사실관계(이벤트·종목·정책·인과)는 그대로 유지한다. 시간순 인과·세션 간 forward-looking 금지 규칙은 일반 Story 와 동일하게 적용된다.
Step 2: 수치 제거 규칙
제거 대상:
- 퍼센트 수치 (
+0.46%, -1.22%)
- 가격·지수 숫자 (
1,224,000원, 6,417.93, 59,586)
- 시가총액·거래량·OHLC 숫자
- 섹터별 · 종목별 퍼센트 나열 (
중공업 +4.05%, 산업재 +2.34%)
- KPI 테이블·metric-item 블록 내부 숫자 (UI 블록 자체를 들어내고 서술로 대체)
유지 가능 (맥락상 필수일 때만):
- 심리적 앵커가 되는 정수 이정표: "10거래일 연속", "5,000조 돌파", "사상 최고치" (숫자 없는 표현 OK)
- 날짜·요일
- 종목명·지수명·ETF 티커·이벤트명·정책 키워드·인명
대체 표현 가이드:
| 수치 중심 (원본) | 맥락 중심 (CS) |
|---|
| 코스피 +0.46%(6,417.93)로 사상 최고치 | 코스피가 사상 최고가를 이틀째 경신 |
| SK하이닉스 +4.97%(1,224,000원) 급등 | SK하이닉스가 다시 강하게 반등하며 주도주 입지를 굳혔습니다 |
| 닛케이 +0.40%, 상하이 +0.52%, 항셍 -1.22% | 닛케이·상하이는 강세, 항셍은 약세로 아시아 내 차별화 |
| VIX 18.3 → 16.5 (-9.8%) | 변동성지수가 한 단계 내려앉으며 변동성 경보가 풀렸습니다 |
Step 3: 표기 규칙 (Market Story 와 동일)
- 지수·종목·통화명은 한국어 단독:
코스피, 나스닥, 금, 달러/원, WTI유
- 영문 약어도 한국어로:
사상 최고가, 연초 대비, 주간 대비, 월간 대비, 장단기 스프레드
- 색상 컨벤션: 상승=빨간(
hl-up), 하락=파란(hl-down) — 한국 주식창 기준
Step 4: 톤 조정
- 흐름-앵커로 문장 연결: "전일 상승세를 이어받아 아시아 장에서 한국이 다시 앞장섰습니다"
- 비유·스토리텔링 허용: "랠리가 반도체에서 전통 산업으로 바통을 넘기는 모습"
- 전문 용어는 짧은 풀이: "브레드스(breadth, 상승 종목 수)", "레인지 상단(최근 고점 부근)"
- 의사결정 권유 금지: "비중 확대 권장", "매수 타이밍" 같은 표현은 쓰지 않는다. 관찰·설명만.
Step 5: HTML 주입
HTML 골격 — cs-hero + cs-section 조합 (Market Story 의 .story-hero 와 시각적으로 구분되는 오렌지 계열, CSS 는 tab-cs 블록 안에 인라인 포함해 과거 보고서에도 포터블):
<style>
.cs-hero{background:linear-gradient(135deg,#fff5eb,#fde9d3);border:1px solid var(--border);border-left:4px solid var(--accent);border-radius:12px;padding:28px 32px;margin-bottom:24px}
.cs-hero h2{font-size:13px;color:var(--accent);letter-spacing:2px;text-transform:uppercase;margin-bottom:12px}
.cs-hero .cs-subtitle{font-size:12px;color:var(--muted);margin-bottom:16px}
.cs-text{font-size:16px;color:#2d3148;line-height:1.9}
.cs-text p{margin-bottom:14px}
.cs-text p:last-child{margin-bottom:0}
.cs-section{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:24px 28px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,0.04)}
.cs-section h3{font-size:17px;font-weight:600;color:#1a1d2e;margin-bottom:10px}
.cs-section p{font-size:15px;color:#2d3148;line-height:1.85;margin-bottom:10px}
.cs-section p:last-child{margin-bottom:0}
.cs-footer{font-size:12px;color:var(--muted);border-top:1px solid var(--border);padding-top:12px;margin-top:8px}
</style>
<div class="cs-hero">
<h2>CS Story — 고객 설명용</h2>
<div class="cs-subtitle">{date} ({요일}) · 수치 대신 맥락·흐름 중심</div>
<div class="cs-text">
<p>{아시아 세션 흐름 — 수치 없이}</p>
<p>{유럽 세션 흐름}</p>
<p>{미국 세션 흐름}</p>
</div>
</div>
<div class="cs-section">
<h3>{국기 + 주요 테마 1}</h3>
<p>{맥락·배경·의미}</p>
</div>
<div class="cs-section">
<h3>📅 이번 주·이번 달 관점</h3>
<p>{WTD/MTD 맥락 서술}</p>
<p class="cs-footer">CS Story 는 Market Story 를 수치 대신 맥락·흐름 중심으로 재구성한 고객 설명용 버전입니다. 구체적 수치는 Market Story / Data Dashboard 탭을 참고하세요.</p>
</div>
절대 쓰지 말 것: .story-section, .story-content — Market Summary HTML 에 정의돼 있지 않은 클래스 (research 보고서의 CSS 이므로 여기선 무스타일 상태가 된다).
주입 단계:
- 일간:
output/summary/YYYY-MM/YYYY-MM-DD.html Read → <div id="tab-cs" class="tab-panel"> ~ </div><!-- /tab-cs --> 블록 Edit → 같은 내용으로 YYYY-MM-DD_cs.html Edit (동기화)
- 주간/월간: 같은 패턴 (
weekly/YYYY-WNN.html + _cs.html, monthly/YYYY-MM.html + _cs.html).
placeholder (<!-- CS_STORY_PLACEHOLDER -->) 가 남아있는 HTML 이면 generate.py / generate_periodic.py 를 한 번 재실행해 탭 구조를 최신화한 뒤 주입한다. _inject_existing_story() 외부 직접 호출은 여전히 금지 — Story 탭 규칙과 동일.
주간 / 월간 CS Story 적용 가이드
기간이 늘어남에 따라 CS Story 의 구조와 톤을 조정한다.
주간 (작성 시점: 해당 주 마지막 영업일, 보통 금요일)
- 헤로 단락: 5 영업일을 한 흐름으로 압축 — 월·화·수·목·금 각 하루를 한 문장씩 (수치 없이)
- 테마 섹션: 그 주의 핵심 테마 3~5 개. 각 섹션은 cs-section 박스 + 한국기·미국기·매크로·원자재 등 카테고리 이모지로 구분
- 주차 위치 표기: 헤로 부제에 "ISO 주차 W{NN} · 5/5 영업일 경과" 명시
- 마무리 카드: 📅 이번 달 관점 (MTD 흐름을 자연어로)
- 본체 HTML(
weekly/YYYY-WNN.html)·sibling(_cs.html) 동시 갱신
월간 (작성 시점: 해당 월 마지막 영업일)
- 헤로 단락: 한 달 전체를 1~2 문단으로 — 월초/월중/월말 흐름 (수치 없이)
- 테마 섹션: 그 달의 핵심 테마 5~7 개 (CS 이므로 짧고 평이하게). 주간 CS 보다 약간 더 길게.
- 월말 시점 표기: 헤로 부제에 "YYYY년 M월 N 영업일 종합" 명시
- 마무리 카드: 📅 다음 달·분기 관점 (전망은 OK, 사후 참조 X)
- 본체(
monthly/YYYY-MM.html)·sibling(_cs.html) 동시 갱신
공통 — 주간/월간 모두
- 일간 CS 와 동일 CSS(cs-hero, cs-section, cs-footer) 재사용. 기간 단위만 헤로 부제로 명시.
- 수치 제거 규칙·톤 조정 규칙은 일간과 동일.
- "이 주에 일어난 일을 한 호흡으로 이어서" 라는 흐름-앵커 톤 유지.
자가 검증 체크리스트
PM Story 작성 절차
PM(Portfolio Manager) Story 는 포트폴리오 매니저·운용역을 위한 지역·자산군별 브리프. 일반 Market Story 가 시간순 내러티브라면 PM Story 는 **지역·자산 컷으로 재편집한 "오늘의 요지"**로, 수치를 적극적으로 포함해 의사결정 참고용으로 쓴다.
전제
- 선행 조건: 해당 날짜의 일반 Story(
_story.html) + _data.json 존재. CS Story 는 필수 아님 (독립 작성).
- 대상 탭:
<div id="tab-pm"> / placeholder <!-- PM_STORY_PLACEHOLDER -->
- 별도 파일 저장:
YYYY-MM-DD_pm.html (generate.py 가 sibling 으로 자동 저장)
- 적용 범위: 일간·주간·월간 모두 동일 패턴
- tab-pm 블록이 없는 이전 보고서:
.venv/bin/python generate.py {date} 로 먼저 탭 구조 최신화
Step 1: 원본 Story + Data 읽기
YYYY-MM-DD_story.html — 사실관계·이벤트·인과 확인 (원본과 모순 금지)
YYYY-MM-DD_data.json — 각 자산 daily/weekly/monthly/ytd 수치 + holiday 확인
history/market_data.csv (필요 시) — 크레딧 스프레드·채권 ETF 실제 종가·YTD 범위 검증
Step 2: 6개 섹션 구성
PM Story 는 고정된 6개 섹션 으로 구성된다 (순서·제목 변경 금지):
| 순서 | 섹션 | 제목 | 다루는 자산/지표 |
|---|
| 1 | 🇰🇷 한국 | 한국 | KOSPI, KOSDAQ, 주도주(삼성전자·SK하이닉스 등), USD/KRW, KR 금리, 외국인 수급 |
| 2 | 🌐 매크로 | 매크로 | Fed/한은/ECB 통화정책, US/KR 경제지표(GDP/CPI/고용), 원자재(WTI/Gold), DXY |
| 3 | 🌏 아시아 및 중국 | 아시아 및 중국 | Nikkei, Shanghai, HSI, NIFTY, 일본·중국 경제지표·정책, 인접 아시아 통화 |
| 4 | 🇺🇸 미국 | 미국 | S&P500, NASDAQ, Russell2K, 빅테크 실적·주가, VIX, 정책·Fed 관련 시장 반응 |
| 5 | 🇪🇺 유럽 | 유럽 | STOXX50, DAX, CAC40, FTSE100, ECB·BOE, EUR/USD·GBP/USD, 유럽 주요 테마 |
| 6 | 💵 채권 | 채권 | US 2Y/10Y/30Y, KR CD91D/3Y/10Y, Yield Curve, IG/HY 스프레드, TLT/AGG/HYG/LQD/EMB |
각 섹션은 3~5개 불릿 또는 짧은 문단으로 구성. 수치는 적극적으로 포함한다.
Step 3: 작성 원칙
포함 권장:
- 종가 + 일변동률 (
KOSPI 6,417.93 (+0.46%))
- 주간/월간/YTD 누적 (
WTD +2.5%, MTD +3.2%, YTD +8.1%)
- 스프레드·커브 (
10Y-2Y +48bp, IG spread 98bp)
- FX 절대 레벨 + 변동 (
USD/KRW 1,392 (+0.3%))
- 주도주별 등락률 top 1~2개
- 수급·거래대금·외국인 순매수 (가능한 경우)
피해야 할 것:
- 의사결정 권유 금지 (
매수 권장, 비중 확대 권장 같은 직접 권유 — "OW/UW 관점에서 ~"는 허용)
- 미래 이벤트 참조 (Forward-looking 금지 — Market Story 와 동일 규칙)
- 세션 간 시간 역전 참조
- 원본 Story 에 없는 사실 창작
톤:
- 건조한 브리프 톤 (헤드라인 + 수치 + 한 줄 해석)
- "~에 반응", "~로 마감", "~로 상승" 같은 관찰 동사 위주
- 분석은
스프레드 축소 국면 지속, 금리 상승 압력 수준의 짧은 레이블 허용
Step 4: HTML 골격
PM 스타일은 차가운 블루/네이비 계열 (CS 의 오렌지와 시각적으로 분리). CSS 는 tab-pm 블록에 인라인 포함해 과거 보고서에도 포터블 적용.
<style>
.pm-hero{background:linear-gradient(135deg,#eef4fb,#dde9f6);border:1px solid var(--border);border-left:4px solid #043B72;border-radius:12px;padding:24px 28px;margin-bottom:20px}
.pm-hero h2{font-size:13px;color:#043B72;letter-spacing:2px;text-transform:uppercase;margin-bottom:10px}
.pm-hero .pm-subtitle{font-size:12px;color:var(--muted);margin-bottom:14px}
.pm-tl{font-size:15px;color:#1a1d2e;line-height:1.8}
.pm-tl p{margin-bottom:8px}
.pm-tl p:last-child{margin-bottom:0}
.pm-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-bottom:16px}
@media (max-width:800px){.pm-grid{grid-template-columns:1fr}}
.pm-section{background:var(--card);border:1px solid var(--border);border-left:3px solid #043B72;border-radius:10px;padding:18px 22px;box-shadow:0 1px 3px rgba(0,0,0,0.04)}
.pm-section h3{font-size:15px;font-weight:700;color:#043B72;margin-bottom:10px;display:flex;align-items:center;gap:8px}
.pm-section ul{list-style:none;padding:0;margin:0}
.pm-section li{font-size:13.5px;color:#2d3148;line-height:1.75;margin-bottom:6px;padding-left:12px;position:relative}
.pm-section li::before{content:'·';position:absolute;left:0;color:#043B72;font-weight:700}
.pm-section li:last-child{margin-bottom:0}
.pm-section .pm-num{font-weight:600;color:#1a1d2e}
.pm-section .pm-up{color:#d92b2b;font-weight:600}
.pm-section .pm-dn{color:#1a5fb4;font-weight:600}
.pm-section .pm-note{font-size:12px;color:var(--muted);margin-top:8px;padding-top:8px;border-top:1px dashed var(--border)}
.pm-footer{font-size:12px;color:var(--muted);border-top:1px solid var(--border);padding-top:10px;margin-top:12px;text-align:center}
</style>
<div class="pm-hero">
<h2>PM Story — 포트폴리오 매니저 브리프</h2>
<div class="pm-subtitle">{date} ({요일}) · 지역·자산군별 요지 + 핵심 수치</div>
<div class="pm-tl">
<p><strong>Top-line:</strong> {하루를 한 문장으로 + 핵심 수치 2~3개}</p>
<p><strong>Key drivers:</strong> {시장을 움직인 원인 — 세미콜론 구분 1~2개}</p>
<p><strong>Watch:</strong> {남은 모니터링 포인트 — 차주/익일 이벤트 또는 지속 관찰 지표}</p>
</div>
</div>
<div class="pm-grid">
<div class="pm-section">
<h3>🇰🇷 한국</h3>
<ul>
<li>KOSPI <span class="pm-num">6,417.93</span> <span class="pm-up">+0.46%</span> · WTD <span class="pm-up">+2.5%</span> · YTD <span class="pm-up">+8.1%</span></li>
<li>{주도주·수급·주요 테마 — 1~2줄}</li>
<li>{USD/KRW, KR 금리, 외국인 순매수}</li>
</ul>
<div class="pm-note">{해석·코멘트 — 선택}</div>
</div>
<div class="pm-section">
<h3>🌐 매크로</h3>
<ul>
<li>{Fed/한은/ECB 정책 or 경제지표 — 수치 포함}</li>
<li>{원자재·DXY — WTI $XX.XX (±N%), Gold $X,XXX}</li>
<li>{인플레이션·성장·고용 트렌드}</li>
</ul>
</div>
<div class="pm-section">
<h3>🌏 아시아 및 중국</h3>
<ul>
<li>Nikkei225 <span class="pm-num">XX,XXX</span> <span class="pm-up/pm-dn">±X.XX%</span>, Shanghai ..., HSI ...</li>
<li>{일본·중국 정책·경제지표}</li>
<li>{기타 아시아 — NIFTY, 대만 등}</li>
</ul>
</div>
<div class="pm-section">
<h3>🇺🇸 미국</h3>
<ul>
<li>S&P500 <span class="pm-num">X,XXX.XX</span> <span class="pm-up/pm-dn">±X.XX%</span>, NASDAQ ..., Russell2K ...</li>
<li>{빅테크 실적·주가 — NVIDIA, AAPL, MSFT 등}</li>
<li>VIX <span class="pm-num">XX.X</span> {레짐 한 줄}</li>
</ul>
</div>
<div class="pm-section">
<h3>🇪🇺 유럽</h3>
<ul>
<li>STOXX50 / DAX / CAC40 / FTSE100 — 종가 + 변동률</li>
<li>{ECB·BOE 코멘트 · 유럽 주요 테마}</li>
<li>EUR/USD, GBP/USD — 레벨 + 변동</li>
</ul>
</div>
<div class="pm-section">
<h3>💵 채권</h3>
<ul>
<li>US 2Y <span class="pm-num">X.XX%</span> ({±Nbp}), US 10Y <span class="pm-num">X.XX%</span> ({±Nbp}), 10Y-2Y <span class="pm-num">±XXbp</span></li>
<li>KR CD91D / 3Y / 10Y — 레벨 + 변동</li>
<li>IG spread / HY spread / HYG·LQD·TLT·EMB 종가 + 변동</li>
</ul>
<div class="pm-note">{커브·크레딧 해석 — "불스팁/베어플랫" 류}</div>
</div>
</div>
<div class="pm-footer">
PM Story 는 Market Story 를 지역·자산군별로 재편집한 매니저용 브리프입니다. 상세 내러티브는 Market Story, 고객 설명은 CS Story 탭을 참고하세요.
</div>
규칙:
<style> 블록은 인라인 포함 (과거 보고서에도 주입 가능하도록)
- 6개 섹션은
<div class="pm-grid"> 안에 2열로 배치. 각 pm-section 은 <h3> + <ul> 필수.
- 수치는
<span class="pm-num"> / .pm-up / .pm-dn 로 감싸 강조
- 섹션당 3~5 불릿. 과도하게 길어지면 압축.
- 해당일 휴장 섹션(예: 한국 공휴일)은 첫 불릿에
{KOSPI 휴장 (공휴일)} 명시 후 빈 자리 남기지 말고 FX·관련 이슈로 채움.
Step 5: HTML 주입
output/summary/YYYY-MM/YYYY-MM-DD.html Read
<div id="tab-pm" class="tab-panel"> ~ </div><!-- /tab-pm --> 사이 블록을 Edit 으로 새 PM 본문으로 치환
- 같은 내용으로
YYYY-MM-DD_pm.html 를 Edit (동기화)
_inject_existing_story() 외부 직접 호출 금지
placeholder 만 있는 상태(<!-- PM_STORY_PLACEHOLDER -->): 정상. 이 블록을 PM 본문으로 치환하면 됨.
tab-pm 블록이 없는 이전 버전 HTML: generate.py {date} 재실행 후 재시도.
Step 6: 주입 검증
grep -c 'id="tab-pm"\|PM_STORY_PLACEHOLDER' {html_path}
id="tab-pm" 1개
PM_STORY_PLACEHOLDER 0개
수치 존재 확인 (CS 와 반대 — 수치 있어야 함):
grep -oE '[0-9]+\.[0-9]+%|[0-9]{3,}\.[0-9]{2}' {pm_file} | wc -l
10 건 미만이면 수치 보강 필요.
자가 검증 체크리스트
주간 / 월간 PM Story 적용 가이드
PM 의 6 섹션 구조(🇰🇷 한국 · 🌐 매크로 · 🌏 아시아 및 중국 · 🇺🇸 미국 · 🇪🇺 유럽 · 💵 채권)는 그대로 유지. 기간 단위에 따라 수치 스케일·맥락만 조정한다.
주간 (해당 주 마지막 영업일 작성)
- pm-hero Top-line: 한 주 흐름 1
2 문장 + 핵심 수치 23 개
- pm-hero Key drivers: 그 주의 인과 핵심 (NFP·CPI·금통위·이벤트)
- pm-hero Watch: 익주 또는 익월 모니터링 포인트
- 6 섹션 각 4~6 불릿: 종가 + WTD 수익률 (전주 금요일 종가 기준) + 주중 최대 상승·하락일 + 주요 이벤트 (필요 시 MTD/YTD 보조)
- 채권 섹션: 금리 +/− bp 변화 (주초 vs 주말), 커브 변형, 크레딧 스프레드 변화
- 본체(
weekly/YYYY-WNN.html) + sibling(_pm.html) 동시 갱신
월간 (해당 월 마지막 영업일 작성)
- pm-hero Top-line: 한 달 흐름 2
3 문장 + 핵심 수치 34 개 (가장 큰 mover, 변동성, FX)
- pm-hero Key drivers: FOMC/한은/CPI/NFP/지정학 등 그 달의 큰 흐름
- pm-hero Watch: 익월·익분기 모니터링
- 6 섹션 각 5~7 불릿: 월말 종가 + MTD + YTD + 월간 최대 상승·하락일 + 주차별 리듬 (W 주차로 짧게) + 주요 이벤트
- 채권 섹션: 금리 +/− bp 변화 (월초 vs 월말), 커브 V/스팁/플랫 패턴, 크레딧 스프레드 월간 변화
- 본체(
monthly/YYYY-MM.html) + sibling(_pm.html) 동시 갱신
공통 — 주간/월간 모두
- 일간 PM 과 동일 CSS(pm-hero, pm-grid, pm-section, pm-num, pm-up, pm-dn, pm-note, pm-footer) 재사용
- 수치 적극 포함 원칙은 동일 (검증 grep 임계값: 주간 30+, 월간 50+, 분기 80+)
- 기존 Market Story / Macro 탭과 사실관계 정합 — 수치 어긋나면 Market Story 가 정본
- 톤은 존댓말("~입니다/습니다") 또는 명사형 종결로 통일. 매수/매도 직접 권유 금지
- 주간/월간/분기 PM 본문은 [회고 6섹션] 직후 [Outlook 블록] 을 추가 작성한다 (아래 "PM Outlook 작성 절차" 참조)
PM Outlook 작성 절차
PM Outlook 은 PM Story (회고) 바로 아래에 붙는 forward-looking 전망 섹션입니다. 일간 PM 의 pm-hero Watch 와는 별개로, 주간 / 월간 / 분기 PM Story 마지막에 통합 — 매니저가 한 페이지에서 회고 + 전망 모두 봅니다.
전제
- 선행 조건: 해당 기간 PM Story 회고 6섹션이 이미 작성됨 (또는 같은 작업 세션에 함께 작성)
- 대상 영역: PM 탭 본문 마지막. 새 placeholder/탭 X — 회고 섹션 직후 신규 블록으로 추가
- 별도 파일 저장: PM sibling 파일(
_pm.html) 안에 회고 + Outlook 모두 포함. 별도 outlook sibling 만들지 않음
- 적용 범위: 주간 / 월간 / 분기 (일간 제외 — 일간은 pm-hero Watch 활용)
- Forward-looking 허용 영역: PM 작성 컨텍스트 또는
_pm.html 편집 시점에는 forward 표현 통과 (settings.json 훅 조정 참조)
Step 1: 사실 기반 수집
- 회고 PM Story 의 6섹션 수치·이벤트 Read
- 해당 시점 (보고서 작성일 기준) 까지 발표된 매크로 데이터·실적·지정학 이벤트만 사용
- 다음 기간 예정 이벤트 캘린더 WebSearch (FOMC, CPI, NFP, 한은 금통위, 대형주 실적, 지정학 일정)
- 금지: 보고서 시점 이후 실제 발생한 사건 참조 (Q1 분기 보고서라면 4월 실제 발생 사건 X)
Step 2: Hybrid 4 파트 구성
Part 1 — 시나리오 헤로 (Bull / Base / Bear 3카드)
각 카드 8~12 줄, 가운데 Base 강조(테두리·배경 진하게):
- 시나리오 라벨 (Bull · Base · Bear)
- 확률 정성 라벨 ("유력 / 중간 / 낮음" — 정량 확률 금지)
- 트리거 조건 2~3개 (
if X happens 식 조건문)
- 자산 함의 4~5줄 — KOSPI · S&P500 · USD/KRW · 유가 · 금리 (10Y) 방향
Part 2 — PM 6섹션 Watch & Trigger
🇰🇷 한국 · 🌐 매크로 · 🌏 아시아 및 중국 · 🇺🇸 미국 · 🇪🇺 유럽 · 💵 채권 — 각 섹션 4~6 불릿:
- 다음 기간 핵심 이벤트 (날짜 + 컨센서스/예상)
- Watch points: 가격·지표 레벨 (예: KOSPI 5,000 지지, 10Y-2Y 70bp 재침투, 유가 $110)
- Trigger / If-Then: 조건문 (예: "VIX 25 재돌파 → 방어자산 우위", "원/달러 1,520 돌파 → 한은 정책 부담")
- 금지: "비중 확대 권장" 같은 직접 매수/매도 권유. 관점·트리거 서술만.
Part 3 — 통합 리스크 (3~5개)
- 심각도 태그 (
risk-tag high|med|low)
- 트리거 조건 + 자산군별 영향 한 줄
- 회고 PM Story 의 리스크와 다른 시점·관점 (회고 = "이미 발생한 우려", Outlook = "앞으로 닥칠 가능성")
Part 4 — 포지셔닝 시사점 박스
- 지역·자산군별 OW / N / UW 한 줄씩 (Base 시나리오 하 합리적 포지션)
- "권유" 가 아닌 "Base 시나리오 하에서 합리적이라고 판단되는 포지션"
- 분기 Outlook 한정 추가: 분기 테마 후보 2~3개 (예: "AI 인프라 후반전", "지정학 프리미엄 재가격")
Step 3: HTML 골격
<div class="outlook-divider">
<h2>📈 다음 {기간} Outlook</h2>
<div class="cs-subtitle">{시나리오·Watch·리스크·포지셔닝 — 작성 시점 기준 forward 시각}</div>
</div>
<div class="scenario-grid">
<div class="scenario-card bull">
<h3>🟢 Bull</h3>
<div class="scen-prob">확률: 낮음</div>
<div class="scen-trigger">
<strong>트리거:</strong>
<ul>
<li>{트리거 조건 1}</li>
<li>{트리거 조건 2}</li>
</ul>
</div>
<div class="scen-impact">
<strong>자산 함의:</strong>
<ul>
<li>KOSPI ...</li>
<li>S&P500 ...</li>
<li>USD/KRW ...</li>
<li>유가·금리 ...</li>
</ul>
</div>
</div>
<div class="scenario-card base">
<h3>🟡 Base (유력)</h3>
...
</div>
<div class="scenario-card bear">
<h3>🔴 Bear</h3>
...
</div>
</div>
<h3 class="outlook-section-title">📍 자산군별 Watch & Trigger</h3>
<div class="pm-grid">
<div class="pm-section">
<h3>🇰🇷 한국</h3>
<ul>
<li><strong>4/9 한은 금통위</strong> — 컨센서스 2.50% 동결</li>
<li>Watch: KOSPI 5,000 지지, USD/KRW 1,500 라인</li>
<li>Trigger: 외국인 5조+ 추가 매도 → 패닉 모드 재진입</li>
...
</ul>
</div>
</div>
<div class="risk-section">
<h3>⚠ 주요 리스크</h3>
<ul class="risk-items">
<li class="risk-item"><span class="risk-tag high">High</span> {리스크 1} — {트리거 + 영향}</li>
...
</ul>
</div>
<div class="outlook-position">
<h3>🎯 포지셔닝 시사점 (Base 시나리오)</h3>
<ul>
<li>한국 — N (Watch: KOSPI 5,000 지지)</li>
<li>미국 — N (실적 시즌 결과 확인 후 판단)</li>
<li>...</li>
</ul>
<div class="quarterly-themes">
<strong>분기 테마 후보:</strong>
<ul>
<li>{테마 1}</li>
<li>{테마 2}</li>
</ul>
</div>
</div>
CSS 화이트리스트 (Outlook 신규 클래스)
기존 PM CSS 재사용 + 다음 신규:
outlook-divider, outlook-section-title, outlook-position
scenario-grid, scenario-card (bull|base|bear), scen-prob, scen-trigger, scen-impact
quarterly-themes
CSS 정의는 PM 본문 첫 머리에 인라인 <style> 으로 추가 (기존 PM _pm.html 의 인라인 CSS 패턴 재사용 — 과거 보고서에도 포터블 적용).
.outlook-divider{border-top:2px solid #043B72;margin-top:32px;padding-top:24px;margin-bottom:20px}
.outlook-divider h2{font-size:18px;color:#043B72;margin-bottom:6px}
.scenario-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:24px}
@media (max-width:900px){.scenario-grid{grid-template-columns:1fr}}
.scenario-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:18px 22px}
.scenario-card.bull{border-left:4px solid #d92b2b}
.scenario-card.base{border-left:4px solid #F58220;background:linear-gradient(180deg,#fff5eb 0%,#fff 60%);box-shadow:0 2px 8px rgba(245,130,32,0.08)}
.scenario-card.bear{border-left:4px solid #1a5fb4}
.scenario-card h3{font-size:15px;font-weight:700;margin-bottom:8px}
.scen-prob{font-size:12px;color:var(--muted);margin-bottom:10px}
.scen-trigger,.scen-impact{font-size:13.5px;color:#2d3148;margin-bottom:8px;line-height:1.7}
.scen-trigger ul,.scen-impact ul{list-style:none;padding-left:0;margin:4px 0 0 0}
.scen-trigger li,.scen-impact li{padding-left:10px;position:relative;margin-bottom:3px}
.scen-trigger li::before,.scen-impact li::before{content:'·';position:absolute;left:0;color:#043B72}
.outlook-section-title{font-size:15px;font-weight:700;color:#043B72;margin:20px 0 12px}
.outlook-position{background:#eef4fb;border:1px solid var(--border);border-left:4px solid #043B72;border-radius:10px;padding:18px 24px;margin-top:20px}
.outlook-position h3{font-size:14px;color:#043B72;margin-bottom:10px}
.outlook-position ul{list-style:none;padding-left:0}
.outlook-position li{padding:4px 0;font-size:13.5px}
.quarterly-themes{margin-top:14px;padding-top:12px;border-top:1px dashed var(--border)}
주간 / 월간 / 분기 깊이 가이드
| 항목 | 주간 | 월간 | 분기 |
|---|
| 시나리오 카드 길이 | 6~8줄 | 8~10줄 | 10~14줄 |
| 6섹션 불릿 | 3~4 | 4~5 | 5~6 |
| 리스크 개수 | 3 | 4 | 5 |
| 포지셔닝 항목 | 4~5 | 6 (지역+자산) | 6 + 분기 테마 2~3 |
| 검색 쿼리 범위 | next week W{N+1} | next month {YYYY-MM+1} | next quarter Q{N+1} |
자가 검증 체크리스트
훅(Hook) 연동
.claude/settings.json에 세 가지 훅이 이미 설정되어 있다:
- PreToolUse WebSearch|WebFetch: 시간순 수집 규칙 강제, forward-looking 쿼리 block
- PreToolUse Edit|Write: Story 작성 전 시간 규칙 주입
- PostToolUse Write|Edit: 작성 후 4단계 검증 (forward-looking, 세션 간 참조, 인과방향, 기간 내 참조)
훅이 block하면: 사유를 읽고 해당 문장을 수정. 훅과 싸우지 말 것 — 훅이 틀렸다고 느끼면 사용자에게 확인 요청.
자가 검증 체크리스트 (작성 완료 전 실행)
Sources 탭 작성 절차
Story 작성 시 참조한 뉴스·데이터 소스를 tab-sources 탭에 기록한다. 팩트체크 추적성 확보가 목적.
수집 시점
Story 작성을 위한 웹 검색(Tavily/WebSearch) 수행 시, 각 결과의 URL · 제목 · 매체명 · 발행일을 즉시 기록한다. Story 완성 후 한꺼번에 주입.
HTML 구조
<div id="tab-sources" class="tab-panel">
<div class="sources-header">
<h2>참조 출처</h2>
<div class="sources-sub">본 보고서 작성에 참조된 뉴스 및 데이터 소스 목록입니다.</div>
</div>
<div class="sources-section">
<h3>🌏 아시아 세션</h3>
<ul class="sources-list">
<li><a href="URL" target="_blank">기사 제목</a> — <span class="source-meta">매체명 · YYYY-MM-DD</span></li>
</ul>
</div>
<div class="sources-section">
<h3>🇪🇺 유럽 세션</h3>
<ul class="sources-list">
<li><a href="URL" target="_blank">기사 제목</a> — <span class="source-meta">매체명 · YYYY-MM-DD</span></li>
</ul>
</div>
<div class="sources-section">
<h3>🇺🇸 미국 세션</h3>
<ul class="sources-list">
<li><a href="URL" target="_blank">매체명 · YYYY-MM-DD</span></li>
</ul>
</div>
<div class="sources-section">
<h3>📊 데이터 소스</h3>
<ul class="sources-list">
<li>history/market_data.csv — Snowflake MKT100_MARKET_DAILY</li>
<li>history/macro_indicators.csv — FRED · ECOS</li>
</ul>
</div>
</div>
작성 규칙
- 세션별 그룹: 아시아 → 유럽 → 미국 → 데이터 소스 순서
- 주간/월간: 요일별 또는 주요 이벤트별 그룹 (세션 구분 대신 날짜별 구분 가능)
- URL 필수: 링크 없는 출처는 매체명+날짜+제목으로 텍스트 기재
- 데이터 소스 고정: 매 보고서마다 CSV/Snowflake 기본 소스 표기
- 최소 건수: 일간 5건 이상, 주간 10건 이상, 월간 15건 이상
- 저장 파일:
YYYY-MM-DD_sources.html (일간) / YYYY-WNN_sources.html (주간) / YYYY-MM_sources.html (월간)