diff --git a/agent-office/app/notifiers/telegram_stock.py b/agent-office/app/notifiers/telegram_stock.py
new file mode 100644
index 0000000..00e619b
--- /dev/null
+++ b/agent-office/app/notifiers/telegram_stock.py
@@ -0,0 +1,41 @@
+"""보유종목 인텔리전스 텔레그램 포매터 (advisory)."""
+import logging
+from typing import Any, Dict
+
+logger = logging.getLogger("agent-office")
+
+_ACTION_KR = {"add": "🟢 추가매수", "hold": "⚪ 보유", "trim": "🟡 축소", "sell": "🔴 매도"}
+_SEV = {"high": "🔴", "med": "🟠", "low": "🟡"}
+
+
+def format_holdings_brief(payload: Dict[str, Any]) -> str:
+ date = payload.get("date") or "?"
+ lines = [f"📊 보유종목 인텔리전스 ({date})", ""]
+ ph = payload.get("portfolio_health") or {}
+ if ph:
+ lines.append(f"포트 손익 {ph.get('total_pnl_rate',0):+.1f}% · "
+ f"종목 {ph.get('positions',0)} · 최대비중 {ph.get('max_weight',0)*100:.0f}% · "
+ f"현금 {ph.get('cash_ratio',0)*100:.0f}%")
+ lines.append("")
+ for h in payload.get("holdings", []):
+ act = _ACTION_KR.get(h.get("action"), h.get("action", "?"))
+ pnl = h.get("pnl_rate")
+ pnl_txt = f"{pnl:+.1f}%" if pnl is not None else "—"
+ line = f"{act} {h.get('name') or h.get('ticker')} ({pnl_txt})"
+ if h.get("reasons"):
+ line += f" — {h['reasons']}"
+ lines.append(line)
+ for iss in (h.get("issues") or [])[:3]:
+ lines.append(f" {_SEV.get(iss.get('severity'),'•')} {iss.get('summary','')}")
+ lines.append("")
+ lines.append("ℹ️ 투자 판단 보조용 제안입니다(자동매매 아님).")
+ return "\n".join(lines)
+
+
+async def send_holdings_brief(payload: Dict[str, Any]) -> None:
+ from ..telegram.messaging import send_raw
+ text = format_holdings_brief(payload)
+ try:
+ await send_raw(text)
+ except Exception as e:
+ logger.warning(f"[telegram_stock] holdings brief send failed: {e}")
diff --git a/agent-office/tests/test_holdings_brief_format.py b/agent-office/tests/test_holdings_brief_format.py
new file mode 100644
index 0000000..108518a
--- /dev/null
+++ b/agent-office/tests/test_holdings_brief_format.py
@@ -0,0 +1,82 @@
+import sys, os
+sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
+
+from app.notifiers import telegram_stock as ts
+
+
+def test_format_holdings_brief():
+ payload = {
+ "date": "2026-05-29",
+ "holdings": [
+ {"ticker": "005930", "name": "삼성전자", "action": "trim", "tech_score": 60.0,
+ "exit_flags": {"ma50_break": True}, "issues": [{"type":"news","severity":"high","summary":"악재"}],
+ "pnl_rate": 5.2, "reasons": "MA50 이탈"},
+ {"ticker": "000660", "name": "SK하이닉스", "action": "hold", "tech_score": 75.0,
+ "exit_flags": {}, "issues": [], "pnl_rate": -2.0, "reasons": "특이 신호 없음"},
+ ],
+ "portfolio_health": {"positions": 2, "total_pnl_rate": 3.1, "max_weight": 0.6, "cash_ratio": 0.2},
+ }
+ txt = ts.format_holdings_brief(payload)
+ assert "삼성전자" in txt
+ assert "축소" in txt or "trim" in txt
+ assert "%" in txt
+
+
+def test_format_holdings_brief_empty_holdings():
+ """빈 holdings + None portfolio_health에도 크래시 없음."""
+ payload = {"date": "2026-05-29", "holdings": [], "portfolio_health": None}
+ txt = ts.format_holdings_brief(payload)
+ assert "보유종목 인텔리전스" in txt
+ assert "자동매매" in txt
+
+
+def test_format_holdings_brief_missing_fields():
+ """pnl_rate None·name None·issues None 방어적 처리."""
+ payload = {
+ "date": None,
+ "holdings": [
+ {"ticker": "005930", "name": None, "action": "sell",
+ "pnl_rate": None, "reasons": None, "issues": None},
+ ],
+ "portfolio_health": {},
+ }
+ txt = ts.format_holdings_brief(payload)
+ assert "005930" in txt # ticker fallback
+ assert "🔴 매도" in txt
+
+
+def test_format_holdings_brief_sell_action():
+ """sell 액션은 🔴 매도로 표시."""
+ payload = {
+ "date": "2026-05-29",
+ "holdings": [
+ {"ticker": "000660", "name": "SK하이닉스", "action": "sell",
+ "pnl_rate": -12.5, "reasons": "손절선 이탈", "issues": []},
+ ],
+ "portfolio_health": {"positions": 1, "total_pnl_rate": -12.5,
+ "max_weight": 1.0, "cash_ratio": 0.0},
+ }
+ txt = ts.format_holdings_brief(payload)
+ assert "🔴 매도" in txt
+ assert "-12.5%" in txt
+
+
+def test_format_holdings_brief_issue_severity_icons():
+ """이슈 심각도별 이모지 매핑 확인."""
+ payload = {
+ "date": "2026-05-29",
+ "holdings": [
+ {"ticker": "005930", "name": "삼성전자", "action": "hold", "pnl_rate": 2.0,
+ "reasons": "특이 신호 없음",
+ "issues": [
+ {"type": "news", "severity": "high", "summary": "심각 악재"},
+ {"type": "volume_surge", "severity": "med", "summary": "거래량 급증"},
+ {"type": "price_move", "severity": "low", "summary": "소폭 변동"},
+ ]},
+ ],
+ "portfolio_health": {},
+ }
+ txt = ts.format_holdings_brief(payload)
+ assert "🔴" in txt # high severity
+ assert "🟠" in txt # med severity
+ assert "🟡" in txt # low severity