portfolio-agent
by Daegyu519
README.md
<div align="center">
# portfolio-agent
**근거와 무효화 조건이 붙은 투자 판단을 내리는 개인용 의사결정 에이전트**
뉴스 요약기가 아니라 의사결정 시스템이다.<br/>
모든 판단에는 근거 원문 링크와 *"이 판단이 뒤집히려면 무엇이 참이어야 하는가"* 가 붙는다.
<br/>







</div>
> [!WARNING]
> 개인 의사결정 **보조** 도구다. 매매 기능은 없고(토스 API 읽기전용), 출력은 투자 권유가 아니다.
---
## 왜 이렇게 만들었나
LLM에게 포트폴리오를 물으면 그럴듯한 수치를 지어내고, 틀려도 조용하다.
이 시스템은 그 실패 모드를 **구조로** 막는다.
| 원칙 | 의미 |
| :--- | :--- |
| 🔢 **수치는 LLM이 만들지 않는다** | 가격은 토스, 공시는 DART/EDGAR 원문. 계산은 `engines/`의 순수 코드. LLM은 그라운딩된 텍스트 위에서 종합만 한다. |
| ⛔ **근거 + 무효화 조건 강제** | 근거 원문 링크와 무효화 조건이 없는 추천은 **코드 게이트가 반려한다.** 설득이 아니라 검증이다. |
| 👁️ **읽기 전용** | 주문·정정·취소 엔드포인트는 코드에 *존재하지 않는다.* |
| 📍 **사실 하나당 소스 하나** | 정형 상태는 MySQL, 서술은 마크다운 vault. 중복 저장 = drift. |
| 🧪 **관찰 트랙 격리** | 기술적 지표·뉴스 감성은 확률적 신호다. 판단 엔진에 들어가지 않고 사람이 보는 병렬 트랙으로만 존재한다. |
## 어떻게 도는가
```mermaid
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](https://docs.astral.sh/uv/) 권장)
> **LLM API 키는 필요 없다.** 판단 합성은 Claude Code 또는 로컬 CLI로만 실행된다.
```bash
# 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
```
## 사용
```bash
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`으로 갱신한다.
```bash
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` → 없으면 번들만 출력. **어떤 엔진이든 게이트는 동일하다.**
```bash
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 # 헬스 확인
```
## 구조
계층은 위에서 아래로만 의존한다. **관찰 트랙(회색 점선)은 판단 경로에 닿지 않는다** — 확률적 신호가 의사결정에 새어드는 걸 구조로 막는다.
```mermaid
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/` | 매매 함수 없음 · 사실 하나당 소스 하나 |
## 테스트
```bash
pytest # 전부 오프라인 — 외부 API · LLM · MySQL 불필요 (fake/mock 기반)
```
## 라이선스
이 저장소는 [MIT](LICENSE)다. 핵심 의존성(httpx · pymysql · cryptography · python-dotenv · mcp · numpy)은 전부 BSD/MIT/Apache 계열이라 충돌이 없다.
> [!IMPORTANT]
> **선택적 extra `[yolo]` 는 AGPL-3.0 이다.**
> 차트 패턴 탐지에 쓰이는 [ultralytics](https://github.com/ultralytics/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](CLAUDE.md) | 설계 규율 — 행동 계약 |
| [TASKS.md](TASKS.md) | 작업 백로그 |
| [docs/spec.md](docs/spec.md) | 원본 설계 명세 |
| [docs/hallucination.md](docs/hallucination.md) | 그라운딩 게이트 설계 |
| [docs/running.md](docs/running.md) · [docs/docker.md](docs/docker.md) | 운영 |
| [docs/technical.md](docs/technical.md) · [docs/sentiment.md](docs/sentiment.md) | 관찰 트랙 |
| [docs/token-economy.md](docs/token-economy.md) | 토큰 예산 |
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues