portfolio-agent
Provides grounded investment decisions with evidence links and invalidation conditions, enabling users to review their portfolio through a structured decision process.
README
<div align="center">
portfolio-agent
근거와 무효화 조건이 붙은 투자 판단을 내리는 개인용 의사결정 에이전트
뉴스 요약기가 아니라 의사결정 시스템이다.<br/> 모든 판단에는 근거 원문 링크와 "이 판단이 뒤집히려면 무엇이 참이어야 하는가" 가 붙는다.
<br/>
</div>
[!WARNING] 개인 의사결정 보조 도구다. 매매 기능은 없고(토스 API 읽기전용), 출력은 투자 권유가 아니다.
왜 이렇게 만들었나
LLM에게 포트폴리오를 물으면 그럴듯한 수치를 지어내고, 틀려도 조용하다. 이 시스템은 그 실패 모드를 구조로 막는다.
| 원칙 | 의미 |
|---|---|
| 🔢 수치는 LLM이 만들지 않는다 | 가격은 토스, 공시는 DART/EDGAR 원문. 계산은 engines/의 순수 코드. LLM은 그라운딩된 텍스트 위에서 종합만 한다. |
| ⛔ 근거 + 무효화 조건 강제 | 근거 원문 링크와 무효화 조건이 없는 추천은 코드 게이트가 반려한다. 설득이 아니라 검증이다. |
| 👁️ 읽기 전용 | 주문·정정·취소 엔드포인트는 코드에 존재하지 않는다. |
| 📍 사실 하나당 소스 하나 | 정형 상태는 MySQL, 서술은 마크다운 vault. 중복 저장 = drift. |
| 🧪 관찰 트랙 격리 | 기술적 지표·뉴스 감성은 확률적 신호다. 판단 엔진에 들어가지 않고 사람이 보는 병렬 트랙으로만 존재한다. |
어떻게 도는가
flowchart LR
subgraph SRC["📥 sources · 읽기전용"]
T["토스증권<br/>보유·시세"]
D["DART / EDGAR<br/>공시 원문"]
end
subgraph ENG["⚙️ engines · LLM 없음"]
Q["quant<br/>집중도·상관·리스크"]
TH["thesis<br/>논지 상태머신"]
end
subgraph JUD["🧠 판단"]
B["근거 번들 E#<br/>(그라운딩 원장)"]
L["Claude Code<br/>종합"]
G{"게이트<br/>근거·무효화·수치 대조"}
end
ST[("MySQL<br/>SoT")]
V["📓 vault<br/>Obsidian 미러"]
T --> Q --> B
D --> TH --> B
B --> L --> G
G -->|"통과"| ST
G -->|"반려"| L
ST --> V
style G fill:#D97757,stroke:#8B3A2B,color:#fff
style ST fill:#4479A1,stroke:#2C5378,color:#fff
게이트는 ① 인용된 근거 E#이 실제로 원장에 있는지 ② 무효화 조건이 붙었는지 ③ 수치가 원본과 일치하는지 ④ 보유 종목 전원에 판단이 있는지를 검사한다. 통과분만 저장된다.
셋업
요구사항 — Python 3.11+, MySQL 8.x (uv 권장) LLM API 키는 필요 없다. 판단 합성은 Claude Code 또는 로컬 CLI로만 실행된다.
# 1) 의존성
uv venv --python 3.12 && source .venv/bin/activate
uv pip install -e ".[dev]"
# 2) 시크릿 — MYSQL_DSN · TOSS_* · DART_API_KEY 를 채운다
cp .env.example .env
# 3) 스키마 + 점검
python -m src.cli.brief init-db
python -m src.cli.brief doctor
사용
python -m src.cli.brief review # 온디맨드 심층 판단
python -m src.cli.brief review --mock # 토스 키 없이 mock 픽스처로 파이프라인 확인
python -m src.cli.brief daily # 일일 다이제스트 (코드 수치만, LLM 없음)
python -m src.cli.brief watch # 백그라운드 논지 감시 — 무효화 트리거 시에만 알림
python -m src.cli.brief retrospect # 제안 vs 실제 행동 vs 결과 회고
<details> <summary><b>논지 등록하기</b> — 시스템의 심장</summary>
<br/>
논지 레코드가 판단의 상태머신이다. 신규 공시·실적이 들어오면 thesis.py가 무효화 조건을 점검해 status를 valid → watch → broken으로 갱신한다.
python -m src.cli.brief thesis add <TICKER> --entry-price <가격> --weight <비중> \
--thesis "<왜 샀는가 한 줄>" --assumption "<핵심 가정>" \
--cond "<서술>|price<<임계값>|broken" --cond "<질적 조건>||watch"
--cond 는 설명|룰|심각도 형식이다. 룰을 비우면 코드가 아니라 사람이 판정하는 질적 조건이 된다.
</details>
<details> <summary><b>Obsidian 지식 계층</b> — 옵션</summary>
<br/>
.env에 OBSIDIAN_VAULT_PATH를 주면 review 성공 시 Companies/·Thesis/·Daily/·Decision/ 마크다운이 생성된다.
MySQL이 유일한 SoT이고 vault는 생성물 미러다 — 비워두면 조용히 비활성화되고(경고 없음), Obsidian 설치도 불필요하다. 보유에서 빠진 종목 문서는 삭제되지 않고 Archived로 표시되어 이력이 남는다.
</details>
판단 LLM = Claude Code
1차 경로 — Claude Code 안에서 /review. portfolio MCP 서버의 review_begin이 그라운딩 근거 번들을 만들고, Claude Code가 내부 추론으로 판단 JSON을 작성해 review_submit에 제출한다.
폴백 — 터미널 단독 실행 시 SYNTHESIS_PROVIDER=auto가 로컬 CLI를 탐지한다: claude -p → gemini → ollama → 없으면 번들만 출력. 어떤 엔진이든 게이트는 동일하다.
claude mcp add portfolio -s project -- python -m src.mcp.portfolio_server
claude mcp add sources -s project -- python -m src.mcp.sources_server
claude mcp list # 헬스 확인
구조
계층은 위에서 아래로만 의존한다. 관찰 트랙(회색 점선)은 판단 경로에 닿지 않는다 — 확률적 신호가 의사결정에 새어드는 걸 구조로 막는다.
flowchart TB
subgraph L1[" ① 진입 · <i>Claude Code · 터미널</i> "]
direction LR
CLI["<b>cli/</b><br/>brief.py<br/><sub>review · daily · watch</sub>"]
MCPS["<b>mcp/</b><br/>portfolio · sources<br/><sub>stdio 서버</sub>"]
end
subgraph L2[" ② 판단 · <i>여기서만 LLM</i> "]
SYN["<b>synthesis/</b><br/>decision · prompts · llm<br/><sub>근거·무효화 게이트</sub>"]
end
subgraph L3[" ③ 도메인 · <i>LLM 없음 · 순수 코드</i> "]
direction LR
ENG["<b>engines/</b><br/>quant · thesis<br/><sub>계산 · 상태머신</sub>"]
OBSV["<b>engines/</b><br/>technical · sentiment<br/><sub>관찰 트랙 — 격리</sub>"]
end
subgraph L4[" ④ 데이터 "]
direction LR
SRC["<b>sources/</b><br/>toss · dart · edgar<br/><sub>읽기전용</sub>"]
STO["<b>storage/</b><br/>Repository → MySQL<br/><sub>정형 SoT</sub>"]
KNW["<b>knowledge/</b><br/>vault export<br/><sub>생성물 미러</sub>"]
end
CLI --> SYN
MCPS --> SYN
SYN --> ENG
ENG --> SRC
ENG --> STO
STO --> KNW
OBSV -.->|"판단 우회"| KNW
SRC -.-> OBSV
style SYN fill:#D97757,stroke:#8B3A2B,stroke-width:2px,color:#fff
style STO fill:#4479A1,stroke:#2C5378,stroke-width:2px,color:#fff
style OBSV fill:#F0F1F3,stroke:#8C959F,stroke-dasharray:4 3,color:#57606A
style KNW fill:#F0F1F3,stroke:#8C959F,color:#24292F
linkStyle 6,7 stroke:#8C959F,stroke-dasharray:4 3
| 계층 | 모듈 | 불변식 |
|---|---|---|
| ① | cli/ · mcp/ |
MCP 서버는 stdout에 프로토콜 메시지만 — 로그는 stderr |
| ② | synthesis/ |
근거 E# · 무효화 조건이 없으면 저장을 거부한다 |
| ③ | engines/ |
LLM 호출이 존재하지 않는다. 같은 입력 → 같은 출력 |
| ④ | sources/ · storage/ · knowledge/ |
매매 함수 없음 · 사실 하나당 소스 하나 |
테스트
pytest # 전부 오프라인 — 외부 API · LLM · MySQL 불필요 (fake/mock 기반)
라이선스
이 저장소는 MIT다. 핵심 의존성(httpx · pymysql · cryptography · python-dotenv · mcp · numpy)은 전부 BSD/MIT/Apache 계열이라 충돌이 없다.
[!IMPORTANT] 선택적 extra
[yolo]는 AGPL-3.0 이다. 차트 패턴 탐지에 쓰이는 ultralytics는 AGPL-3.0 이며, 이는 이 저장소의 MIT 와 다른 조건이다. 이 저장소는 ultralytics 코드도 모델 가중치도 포함하지 않는다 —engines/patterns.py에서 lazy import 할 뿐이고, 미설치 시 조용히 비활성화된다.uv pip install -e ".[yolo]"를 실행하는 순간부터 AGPL-3.0 조건이 적용되며, 준수 책임은 설치한 쪽에 있다. AGPL 이 곤란하다면 이 extra 를 설치하지 마라 — 의사결정 코어는 YOLO 없이 완전히 동작한다 (애초에 관찰 트랙이라 판단 경로에 닿지 않는다).
[sentiment] extra(transformers, Apache-2.0)와 [technical] extra(matplotlib, PSF)는 허용적 라이선스다.
문서
| CLAUDE.md | 설계 규율 — 행동 계약 |
| TASKS.md | 작업 백로그 |
| docs/spec.md | 원본 설계 명세 |
| docs/hallucination.md | 그라운딩 게이트 설계 |
| docs/running.md · docs/docker.md | 운영 |
| docs/technical.md · docs/sentiment.md | 관찰 트랙 |
| docs/token-economy.md | 토큰 예산 |
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.