GITHUB 레포 딥다이브 · 2026-08-03 · SIMONLIN1212/VIBE-RESEARCH · 종목을 "추천하지 않는 것"이 핵심 기능인 투자 리서치 시스템

Vibe-Research 딥다이브
종목 추천을 "안 하는" 것을 설계 원칙으로 삼은 개인 투자 리서치 시스템

simonlin1212/Vibe-Research는 A주·미국·홍콩·한국 주식의 객관적 공개 데이터를 하나의 대시보드로 손질해 올려 주되, "사라 / 팔아라 / 목표가 얼마" 같은 결론은 절대 내지 않는 오픈소스 개인 투자 리서치 시스템이다. 슬로건이 그대로다 — "모델은 네가 가져와, 데이터는 내가 가져올게(You bring the model, it brings the data)."

대부분의 "AI 투자 봇"은 데이터를 읽고 결론(매수/매도/점수)까지 뱉는다. Vibe-Research는 정반대다. 밸류에이션·자금흐름·재무·공시·뉴스 같은 객관 데이터만 정확히 모아 접시에 담고, 그 데이터로 판단을 내리는 일은 사용자 자신의 AI(구독 CLI·API·MCP)에게 넘긴다. "예측하지 않는다·점수 매기지 않는다"는 규제 준수(合规)가 자랑거리이자 1급 설계 목표다.

(저장소 simonlin1212/Vibe-Research · 라이선스 MIT · 백엔드 Python 3.10+ / FastAPI(:8900) · 프런트 React 19 + TypeScript + Vite + Tailwind(:5899) · 저장소 파일 97개 · 태그 v0.3.0(2026-07-31) · 저자 Simon(@linsizhen) · 공식 사이트 viberesearch.wiki · TrendShift Daily #16)
목차
  1. 한 줄 정체
  2. 왜 주목받는가 — "추천 안 함"이라는 역발상
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 — 데이터층 하나 + AI 출구 셋
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 기술별 배울 것
  7. 시스템 / 실행 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 로드맵 (주차별)
  10. 핵심 키워드 사전
  11. 참고 링크

1한 줄 정체

이 프로젝트가 정확히 무엇인지 한 문장으로

Vibe-Research는 "A주·미국·홍콩 주식의 객관적 공개 데이터(시세·밸류에이션·자금흐름·재무·공시·뉴스)를 로컬 대시보드로 모아 주되, 매수·매도·목표가 같은 결론은 절대 내지 않고 그 판단을 사용자 자신의 AI에게 넘기는 개인 투자 리서치 시스템"이다. 한마디로 "결론을 아웃소싱한 투자 리서치 대시보드"다. 스스로 판단하는 트레이딩 봇이 아니라, 판단에 필요한 재료만 완벽히 손질해 주는 주방이다.

한 장의 비유

"재료만 완벽하게 손질해 주는 주방 — 요리(결론)는 네가 한다"

좋은 레스토랑 주방에는 재료 손질 담당(prep cook)이 있다. 그는 채소를 씻고 다듬고, 고기를 손질하고, 계량까지 끝내 깨끗한 접시에 재료를 가지런히 올려 준다. 하지만 그는 "이 요리를 하세요"라고 절대 말하지 않는다. 어떤 요리를 할지, 간을 어떻게 볼지는 주방장(head chef)의 몫이다.

Vibe-Research가 정확히 이 손질 담당이다. 시세·재무·자금흐름·공시·뉴스라는 재료를 정확히 씻고 다듬어(수집·정제·토큰 절약용 압축) 대시보드에 올려 주지만, "이 종목을 사라"는 요리(결론)는 하지 않는다. 그 판단은 주방장 = 사용자 자신의 AI(구독 CLI·API·MCP)가 내린다. "재료만 손질하고 요리는 안 한다"는 이 의도적 절제가 이 프로젝트를 TradingAgents·ai-hedge-fund 같은 "봇이 결론까지 내는" 부류와 갈라놓는 핵심이다.

용어
투research(投研) · 개인 투자 리서치
중국어 投研(투옌)은 "投资研究(투자 리서치)"의 줄임말로, 종목을 사고팔기 전에 데이터를 모아 근거를 만드는 과정을 말한다. 증권사 애널리스트가 하는 일을 개인이 스스로 하도록 도구화한 것이 Vibe-Research다. 매매 실행(주문)은 하지 않는다 — 어디까지나 "조사·정리" 단계만 담당한다.
용어
"모델은 네가, 데이터는 내가" (BYOM · Bring Your Own Model)
Vibe-Research는 스스로 LLM을 호출하지 않는다. AI 두뇌를 내장하는 대신, 사용자가 이미 쓰는 AI(월 구독 Claude Code·API 키·MCP 클라이언트)를 가져와 꽂도록 설계했다. 그래서 이 앱 자체에는 API 키가 필요 없고(구독 모드), 데이터 수집·정리에만 집중한다. LLM 비용·모델 선택의 자유가 전적으로 사용자에게 있다.

사용자가 "600519(구이저우 마오타이) 좀 봐줘"라고 하면 Vibe-Research 안에서 벌어지는 일을 압축하면 이렇다:

사용자 "이 종목, 객관 데이터로 정리해줘" (종목코드 입력) │ ▼ ① 프런트(React :5899) /api/* 로 백엔드에 데이터 요청 (Vite가 프록시) │ ▼ ② 백엔드(FastAPI :8900) 시세=텐센트, 재무·자금흐름·공시=동방재부(东财) │ 에서 공개 데이터만 긁어와 → 토큰 절약용으로 압축 │ (표 전체가 아니라 "최근 N행 + 핵심 필드 + 요약통계") ▼ ③ 대시보드에 표시 시세·밸류에이션·재무·자금흐름·용호방·공시·뉴스… │ 여기까지 LLM은 전혀 개입하지 않는다 (순수 데이터) ▼ ④ "AI에게 물어보기" 클릭 이제서야 사용자의 AI가 등장 │ · 구독 모드: 로컬 CLI(Claude Code 등) 프로세스 실행 │ · API 모드: OpenAI 호환 엔드포인트로 함수호출 │ · MCP 모드: Claude Code가 백엔드를 도구로 사용 ▼ ⑤ AI가 결론 단, 앱은 "사라/팔아라"를 강요하지 않는다 — 시스템 프롬프트가 "객관 데이터 기반 분석"만 지시

여기서 핵심은 ③까지는 AI가 전혀 없다는 점이다. 대시보드에 뜨는 숫자는 전부 검증 가능한 공개 데이터이지 LLM이 지어낸 값이 아니다. AI는 ④에서 사용자가 원할 때만 등장하고, 그마저도 "결론을 강요받지 않는" 재료 위에서 스스로 판단한다. 이 "데이터와 AI의 분리"가 Vibe-Research 설계 철학의 뼈대다.

2왜 주목받는가 — "추천 안 함"이라는 역발상

투자 AI가 넘쳐나는 판에서 굳이 상위에 오른 이유

첫째, "봇이 결론까지 내는" 피로감을 정면으로 때린다. TradingAgents, ai-hedge-fund 같은 화제작들은 "AI가 알아서 매수/매도 신호를 준다"를 판다. 그런데 실제로 돈을 걸어 본 사람은 안다 — 근거 없이 "사라"고만 하는 AI는 못 믿는다. Vibe-Research는 신뢰의 방향을 뒤집었다. "결론은 못 믿어도 데이터는 믿을 수 있게" 만들고, 결론은 사용자(와 그의 AI)에게 돌려준다. 이 절제가 오히려 실무 투자자의 공감을 산다.

비유

점집 vs 종합병원 영상의학과. 점집은 "이 주식 사"라고 결론만 주지만 근거를 검증할 수 없다. 반면 영상의학과는 CT·MRI·혈액검사 수치와 영상(객관 데이터)을 정확히 뽑아 주되, "이 병이다·이 약을 먹어라"라는 처방(결론)은 주치의에게 넘긴다. Vibe-Research는 후자를 택했다 — 정확한 검사 결과를 주고, 진단은 네(네 AI)가 내리게 한다. 검사 수치가 정확하면, 오진의 책임은 판독이 아니라 진단에 있다.

둘째, 규제 준수(合规)를 마케팅이 아니라 아키텍처로 박았다. 중국·한국을 포함해 많은 나라에서 "특정 종목 매수 추천"은 투자자문업 인허가 영역이라 함부로 하면 위법 소지가 있다. Vibe-Research는 이 위험을 설계 단계에서 회피한다 — 앱이 절대 매수/매도/목표가/등급을 출력하지 않고, 오직 공개 데이터·공개 순위만 보여 준다. 뉴스 수집기(newsradar)조차 도박·예측시장·코인·성인물 같은 레드라인 키워드를 필터링한다. "합법적으로 배포 가능한 투자 도구"라는 점이 개발자·창작자에게 매력적이다.

셋째, "내 AI를 그대로 꽂는다"는 개방성이다. 대부분의 투자 SaaS는 자기네 모델·자기네 요금제에 가둔다. Vibe-Research는 구독 CLI(Claude Code·Qwen·DeepSeek·Codex) · OpenAI 호환 API · MCP 세 가지 경로를 모두 열어, 사용자가 이미 내는 AI 구독료 안에서 리서치를 돌릴 수 있게 한다. 앱 자체는 API 키 없이도 데이터가 즉시 나오고, AI는 원할 때만 붙인다. 벤더 종속(lock-in)이 없다는 점이 오픈소스 사용자에게 크게 어필한다.

넷째, 중화권 개인투자자(散户)라는 거대한 미충족 수요다. A주 시장은 개인 비중이 압도적으로 높지만, 흩어진 데이터(동방재부·텐센트·용호방·자금흐름·공시)를 한 화면에 모아 주는 무료·로컬·프라이버시 우선 도구는 드물다. Vibe-Research는 이 틈을 정확히 노렸고, 그래서 A주 데이터에 특화된 기능(연판/봉판율/용호방/북향자금 등)이 유독 촘촘하다. 미국·홍콩·한국 종목도 지원하지만, 무게중심은 A주 개인투자자다.

이 레포의 진짜 강점
"데이터"와 "판단"을 아키텍처로 분리했다

대부분의 투자 AI는 데이터 수집과 결론 생성을 한 덩어리로 묶어, 어디까지가 사실이고 어디부터가 AI의 상상인지 흐릿하다. Vibe-Research는 이 둘을 물리적으로 다른 계층으로 갈랐다 — 백엔드(FastAPI)는 검증 가능한 공개 데이터만 다루는 무상태(stateless) 계층이고, AI는 그 위에 선택적으로 얹히는 별도 출구다. "숫자는 데이터층에서, 해석은 AI층에서"라는 이 분리가 신뢰성과 규제 준수를 동시에 잡는다.

오해 주의
자동매매 봇이 아니다 · 투자 조언을 주지 않는다

이름에 "Trading"이 들어가고 금융 카테고리라 자동매매·시그널 봇으로 오해하기 쉽지만, Vibe-Research는 주문을 넣지 않고, 매수/매도 신호도 주지 않는다. 증권사 계좌에 연결되지도 않는다. 어디까지나 "데이터를 모아 보여 주는 리서치 대시보드"이며, 매매 판단과 실행은 전적으로 사용자 책임이다. 이 문서 역시 특정 종목·투자 전략을 권하지 않으며, 오직 소프트웨어 아키텍처 학습 관점에서 이 레포를 해부한다.

3기술 스택 전체 지도

Python 백엔드 + React 프런트 — 둘 다 "가볍게, 의존성은 게으르게"

Vibe-Research는 전형적인 2-티어 웹앱이다: FastAPI 백엔드(데이터)와 React 프런트(화면). 그런데 스택 선택 곳곳에 "무겁지 않게, 필요할 때만 로드"라는 실용주의가 배어 있다. GPU도, 데이터베이스도, Docker도 없다. LLM 추론은 사용자 몫이라 서버는 데이터 심부름과 조율만 한다.

백엔드 (backend/ · Python 3.10+ · FastAPI :8900)

구성요소무엇왜 이걸 썼나
fastapi ≥0.110
uvicorn[standard] ≥0.29
웹 프레임워크 + ASGI 서버44개 /api/* REST 엔드포인트를 얇게 노출. 무상태·읽기전용이라 가벼움
requestsHTTP 클라이언트(핵심 의존)이것만 있으면 시세·공시가 즉시 동작. 무거운 데이터 라이브러리 없이도 기본 기능이 뜬다
akshare ≥1.10
mootdx ≥0.10
pandas
보조 데이터 소스(게으른 의존)lazy-import — 필요한 엔드포인트에서만 로드. 없으면 그 엔드포인트만 501 + 설치안내를 주고 서비스는 안 죽는다
텐센트 qt.gtimg.cnA주 시세 1차 소스(표준 urllib, GBK)v3.0부터 akshare 의존을 걷어내고 텐센트로 이동 — "IP를 밴하지 않는다"가 선택 이유
동방재부(东财, Eastmoney)재무·자금흐름·신용융자·공시em_get 쓰로틀러로 감싸 밴 방지. 자금흐름·용호방·락업 같은 깊은 데이터의 출처
pytest · httpx테스트(dev 의존)마커로 오프라인(-m "not live") / 네트워크 형태검증(-m live) 분리
용어
게으른 의존성 (lazy import) · 티어드 디펜던시
무거운 라이브러리(akshare·pandas 등)를 파일 맨 위에서 import하지 않고, 그걸 실제로 쓰는 함수 안에서 호출되는 순간에만 import하는 기법. 덕분에 그 라이브러리가 설치 안 돼 있어도 앱 전체는 정상 기동하고, 해당 기능만 "설치하세요(501)"로 응답한다. "핵심 기능은 가볍게, 부가 기능은 필요할 때만 무겁게"를 구현하는 실전 패턴이다.

프런트엔드 (frontend/ · :5899)

구성요소버전역할
React + TypeScript19 · 5.7SPA 본체. 12개 라우트(每日复盘·个股·板块·多空辩论·持仓…)
Vite6.4번들러/개발서버. /apilocalhost:8900으로 프록시해 CORS 회피
Tailwind CSS3.4글래스(반투명) 웜오렌지 테마. 유틸리티 클래스 기반 스타일
zustand5가벼운 전역 상태관리(Redux 대안). 자선주·설정 등
echarts6차트(캔들·자금흐름·밸류에이션 히스토리 등)
react-markdown + remark-gfm9AI 응답을 마크다운으로 렌더(v0.3.0 신규)
react-router-dom · lucide-react · sonner7 · — · —라우팅 · 아이콘 · 토스트 알림

AI 계층 — 이 앱은 LLM을 "직접" 부르지 않는다

가장 독특한 스택 결정이다. Vibe-Research는 모델 비종속(model-agnostic)이라, 자기가 OpenAI/Claude를 호출하는 코드가 없다. 대신 사용자가 frontend/src/lib/ai-models.ts에서 자기 AI를 골라 꽂는다. 세 가지 경로가 있다:

소스 vs README 함정
requirements.txt는 akshare를 아직 남겨 뒀지만, A주 시세 1차 소스는 텐센트다

requirements.txt에는 여전히 akshare가 적혀 있어 "akshare 기반이구나"로 오해하기 쉽다. 하지만 README v3.0 이후 A주 시세 엔진은 akshare 의존을 걷어냈고, 1차 시세는 텐센트(qt.gtimg.cn, 표준 urllib), 재무·자금흐름은 동방재부다. akshare·mootdx는 이제 보조·폴백일 뿐이며 게으르게 로드된다. 스택을 파악할 땐 requirements.txt보다 astock.py의 실제 호출부를 진실로 삼아야 한다.

4아키텍처 심화 — 데이터층 하나 + AI 출구 셋

이 레포의 진짜 알맹이. "하나의 객관 데이터층, 세 개의 AI 출구"

축 1 — 전체 데이터 흐름 (한눈에)

구조를 한 문장으로 하면 "공개 데이터 소스 → 무상태 백엔드(23개 객관 도구) → 프런트 대시보드, 그리고 원할 때만 AI 출구 3종"이다.

공개 데이터 소스(무키·무료) 백엔드 FastAPI :8900 AI 출구(선택적) ────────────────────────── ───────────────────── ──────────────────── 텐센트 qt.gtimg.cn (시세) ─┐ astock.py / gstock.py ┐ ① 앱 내부 채팅 (chat.py) 동방재부 东财 (재무·흐름) ─┼──────▶ market.py/newsradar.py ├─tools.py─▶ OpenAI 호환 함수호출 루프 akshare/mootdx (게으름, ─┘ (객관 도구 23개) ┘ (23개) 없으면 501) │ ② MCP 서버 (mcp_server.py) 108개 RSS 피드(표준 urllib)─────────▶ 44개 REST /api/* 엔드포인트 → Claude Code 등이 도구로 사용 │ ③ 구독 CLI 브리지 (cli_runtime.py) 프런트(React 19 SPA) ◀─ Vite가 /api 프록시 ┘ → 로컬 CLI를 서브프로세스로 실행 localStorage: 보유종목·자선주·LLM키(절대 업로드 안 함)

핵심 성질 셋. (1) 백엔드는 무상태·읽기전용이다 — 6자리 종목코드를 받아 공개 데이터를 돌려줄 뿐, 사용자 데이터를 서버에 쌓지 않는다. (2) 데이터베이스가 없다 — 보유종목·자선주·LLM 키는 브라우저 localStorage에, 업로드한 리포트·보유 내역은 로컬 디스크 ~/.vibe-research/에 저장되고 서버로 올라가지 않는다. (3) AI는 필수가 아니다 — 대시보드 데이터는 AI 없이도 다 나오고, AI 출구 3종은 사용자가 원할 때만 붙는다.

축 2 — 밴을 피하는 데이터 수집: em_get 쓰로틀러

중화권 금융 사이트(동방재부 등)를 자주 긁으면 IP가 차단된다. Vibe-Research의 astock.py는 이걸 타임스탬프 기반 쓰로틀러로 막는다. 이 작은 함수가 뒤에 나올 "토론(debate)" 설계까지 좌우하는 숨은 주춧돌이다:

# astock.py — 동방재부 요청 사이 최소 간격 강제
_EM_MIN_INTERVAL = 1.0   # 두 요청 사이 최소 1초
def em_get(url, params=None, headers=None, timeout=15):
    wait = _EM_MIN_INTERVAL - (time.time() - _em_last_call[0])
    if wait > 0:
        time.sleep(wait + random.uniform(0.1, 0.5))   # 락(lock)이 아니라 시각 기반!
    # 자동 전략: 먼저 직결(direct) 시도 → VPN/Clash 프록시가
    #           본토 금융사이트를 깨는 걸 우회 → 안 되면 시스템 프록시로
    #           폴백하고, 성공한 모드를 이후 고정(pin)
용어
쓰로틀링 (throttling) · 시각 기반 vs 락 기반
요청을 일부러 늦춰 상대 서버가 화내지(밴하지) 않게 하는 것. Vibe-Research는 "마지막 호출 시각"을 기억해 다음 호출을 늦추는 시각(timestamp) 기반 방식을 쓴다. 이건 락(lock)이 아니라서, 여러 요청이 동시에 들어오면 간격을 뚫고 나가 밴을 부를 수 있다. 그래서 뒤의 debate가 동방재부 호출을 일부러 직렬(serial)로 묶는다 — 쓰로틀러의 약점을 아키텍처로 보완한 것.

또 하나 실전적인 디테일: "직결 먼저, 프록시는 폴백"이다. 중국 본토 사용자는 VPN/Clash를 켜 두는 경우가 많은데, 그 프록시가 오히려 본토 금융 사이트 접속을 깬다. 그래서 em_get은 시스템 프록시를 무시하고 직결을 먼저 시도한 뒤, 실패하면 프록시로 폴백하고 성공한 모드를 고정한다. 사용자의 네트워크 환경 차이를 코드가 흡수하는 영리한 처리다.

축 3 — 23개 "객관 도구"와 토큰 절약 (tools.py)

AI 출구 3종(채팅·MCP·토론)은 모두 같은 23개 도구 세트를 공유한다. 각 도구는 종목코드를 받아 한 종류의 객관 데이터를 돌려주는데, 설계에 두 가지 규율이 있다:

도구 목록 일부: query_quote(시세) · query_valuation_percentile(밸류에이션 히스토리 백분위) · query_fund_flow(자금흐름) · query_dragon_tiger(용호방) · query_lockup(락업 해제) · query_market(지수·글로벌·시장심리·거래대금·개요) · query_news_radar(뉴스) · query_global_stock(미·홍콩·한국) · query_hk_cashflow(홍콩 현금흐름표).

축 4 — 서명 기능: "결론을 안 내는" 다공/다공 토론 (debate.py)

이 프로젝트의 정체성을 가장 잘 보여 주는 모듈이다. 일반적인 "AI 애널리스트"는 하나의 AI가 결론을 낸다. Vibe-Research의 多空辩论(다공 = 롱/숏 토론)은 여러 역할로 나누되, 일부러 "트레이더(결정자)" 역할을 두지 않는다. 흐름은 이렇다:

① 사실 도시에(dossier) 백엔드가 객관 데이터 13종을 정해진 목록대로 수집 ★ 이 단계에는 LLM이 전혀 없다 (순수 데이터) │ ▼ ② 강세(Bull) 리서처 오직 도시에만 근거로 "강세 논거"를 편다 │ — 각 논거는 반드시 구체적 데이터를 인용 ▼ ③ 약세(Bear) 리서처 같은 제약으로 "약세 논거"를 편다 │ (선택: 2라운드에서 상호 반박) ▼ ④ 중립 사회자(moderator) 다음만 출력한다: · 합의점 / 진짜 의견충돌(데이터 부족? 해석 차이?) · 검증 체크리스트 / 데이터 공백 ★ 누가 옳은지 판정하지 않는다 · 등급·목표가 없다

포인트는 ④의 사회자가 승자를 가리지 않는다는 것이다. "강세가 맞다"가 아니라 "둘의 진짜 이견은 무엇이고, 그걸 판가름하려면 어떤 데이터를 더 봐야 하는가"를 정리해 준다. 결론을 내는 대신 "무엇을 더 확인해야 하는지"를 돌려주는 것 — 이것이 "추천 안 함" 철학이 코드로 구현된 모습이다.

용어
도시에 (dossier, 사실 자료집) · _DOSSIER_SPEC
토론 전에 백엔드가 LLM 없이 모아 두는 객관 데이터 13종 묶음. 각 항목에 parallel 플래그가 붙어, 동방재부(em_get) 기반 항목(밸류에이션·자금흐름·신용융자·주주·락업·개념)은 강제로 직렬(serial) 실행해 쓰로틀러를 지키고, 텐센트·직결 엔드포인트만 4-워커 스레드풀로 병렬 처리한다. "데이터를 먼저 확정하고, 그 위에서만 토론한다"는 순서를 강제하는 장치.

여기에 환각 방지 장치가 하나 더 있다. _payload_empty() 헬퍼는 응답에서 메타키를 벗겨 내 "껍데기뿐(빈 값)"인지 판별한다. 정말 비었지만 유효한 항목에는 "기록 없음 — 진짜 없을 수도, 소스가 죽었을 수도 있으니 넘겨짚지 마라"는 명시 주석을 붙인다. AI가 빈 데이터를 보고 지어내지 못하게 데이터의 "없음"까지 정직하게 전달하는 것이다. (비용도 문서화돼 있다: 1라운드 ≈ 모델 호출 3회·약 100~120초, 2라운드 ≈ 5회·약 3분.)

축 5 — 세 번째 AI 출구: 구독 CLI 브리지 (cli_runtime.py)

API 키 없이 월 구독만으로 AI를 쓰게 해 주는 다리다. 로컬에 이미 로그인된 CLI를 subprocess로 띄워 답을 받는다. CLI마다 전달 방식이 달라 세심하게 분기한다:

단, CLI들은 함수호출(function-calling)을 못 하므로 이 모드는 "데이터가 이미 프롬프트에 다 담긴" 시나리오에만 맞는다. 그래서 도구를 여러 번 오가야 하는 심층 분석은 API 모드나 MCP 모드를 쓴다.

축 6 — 공개 배포를 위한 안전장치: SSRF 가드 & API 키

로컬 전용이면 문제없지만, 누군가 이 앱을 공개 서버에 올리면 위험이 생긴다. chat.py는 사용자가 baseURL을 넣을 수 있게 열어 두는데, 악의적 baseURL이 클라우드 메타데이터(169.254.x.x)나 내부망을 가리키면 서버가 대신 접속해 버리는 SSRF가 된다. 그래서 _check_base_url가 이를 차단하고, 공개 배포 시(VR_API_KEY 설정 시)엔 DNS를 재해석해 우회까지 막는다. 또 VR_API_KEY가 설정되면 /api/health를 뺀 모든 엔드포인트가 Authorization: Bearer를 요구한다.

용어
SSRF (Server-Side Request Forgery, 서버측 요청 위조)
공격자가 서버로 하여금 내부 주소로 대신 요청하게 만드는 취약점. 예: 사용자가 "이 주소로 접속해"라고 넣은 값이 클라우드 메타데이터(169.254.169.254, 여기에 접속하면 서버의 자격증명이 샐 수 있다)를 가리키는 경우. Vibe-Research는 사용자 입력 baseURL을 허용 대상인지 검사하고 사설/메타데이터 대역을 막아 이를 방어한다. 사용자 입력을 서버가 대신 fetch하는 모든 앱이 신경 써야 할 고전 위협이다.

5디렉토리 구조 해부

파일 97개. 백엔드 모듈이 "한 파일 = 한 관심사"로 또렷하게 갈려 있다
Vibe-Research/ ├─ README.md / README_en.md 저장소 소개(중/영) ├─ ROADMAP.md / VISION.md 방향성·철학 문서 ├─ a-stock-data/ ★ 번들된 A주 데이터 툴킷 (v3.6.0, SKILL.md에 복붙 코드) ├─ global-stock-data/ ★ 번들된 미·홍콩 데이터 툴킷 (v2.0.3) │ ├─ backend/ ★ FastAPI :8900 — "한 파일 한 관심사" │ ├─ app.py (678줄) 44개 REST 엔드포인트 (/api 하위) │ ├─ astock.py (816줄) A주 데이터층 · 5티어 · 동방재부 쓰로틀러 │ ├─ gstock.py (245줄) 미·홍콩·한국 데이터 (global-stock-data 이식) │ ├─ newsradar.py (171줄) RSS 뉴스 레이더 (investment-news에서 이식) │ ├─ market.py (186줄) 시장심리 · 섹터 자금흐름 · 글로벌 지수 │ ├─ tools.py (384줄) AI 도구층: 함수호출 도구 23개 │ ├─ chat.py (320줄) 앱 내부 AI · OpenAI 호환 함수호출 루프 │ ├─ debate.py (306줄) 다공 토론 오케스트레이션(결론 없음) │ ├─ reflection.py (72줄) 추론 감사(reasoning audit) 패스 │ ├─ cli_runtime.py (244줄) 구독 CLI 서브프로세스 브리지 │ ├─ mcp_server.py (106줄) JSON-RPC over stdio MCP 서버 (표준 라이브러리만) │ ├─ portfolio.py / myreports.py 로컬 보유내역 + 리포트 업로드 │ ├─ news_sources.json 12트랙 / 108개 RSS + 레드라인 키워드 │ ├─ .env.example / requirements.txt / requirements-dev.txt │ └─ tests/ pytest 6종 (test_agents · test_api · test_live …) │ └─ frontend/ ★ Vite + React 19 + TS + Tailwind :5899 └─ src/ ├─ router.tsx 12개 라우트 ├─ pages/ DailyReview · Intel · Sectors · SectorDetail · │ Debate · Portfolio · StockData · Watchlist · │ MyReports · Notes · Settings ├─ lib/ api · llm · agents · ai-models · ndjson · │ watchlist · notes · storage ├─ hooks/ useLiveQuotes · useDarkMode └─ components/ layout · ui(GlassCard · AskAiButton · EarningsSnapshot…)

구조가 알려주는 힌트 셋. 첫째, 가장 큰 두 파일이 astock.py(816줄)와 app.py(678줄)다 — 이 앱의 무게중심이 "AI"가 아니라 "A주 데이터를 정확히·안전하게 긁어오는 일"에 실려 있음을 그대로 보여 준다. 둘째, chat.py·debate.py·mcp_server.py·cli_runtime.py모두 tools.py를 공유한다 — "객관 도구 23개"라는 한 우물을 세 AI 출구가 함께 쓰는 DRY(중복 제거) 설계다. 셋째, newsradar.py·gstock.py 주석에 "investment-news에서 이식", "global-stock-data 이식"이 적혀 있다 — 저자의 기존 소형 레포들을 흡수해 만든 통합작임을 코드가 증언한다.

설계에서 배우는 것
"한 파일 = 한 관심사(single responsibility)"의 교과서

백엔드를 보면 시세(astock)·해외(gstock)·뉴스(newsradar)·시장(market)·도구(tools)·채팅(chat)·토론(debate)·감사(reflection)·CLI(cli_runtime)·MCP(mcp_server)가 각각 한 파일로 딱 갈려 있다. 파일 이름만 봐도 어디를 열어야 할지 안다. 초·중급 개발자가 "기능을 어떻게 파일로 나눌까"를 배우기에 이보다 깔끔한 표본이 드물다 — 거대 프레임워크의 미로가 아니라, 읽으면 바로 이해되는 크기의 모듈들이다.

6학습 포인트 — 기술별 배울 것

"금융"이 아니라 "데이터 엔지니어링 + AI 통합"으로 읽으면 배울 게 많다

① 모델 비종속 AI 통합 (가장 값진 배움)

앱이 LLM을 내장하지 않고 세 경로(구독 CLI · OpenAI 호환 API · MCP)로 사용자의 AI를 꽂게 한 설계. "우리 모델에 가두지 않는다"는 방향은 오픈소스 도구의 채택률을 크게 높인다. 특히 OpenAI 호환 규격 하나로 10여 개 공급자를 다 받는 방식, 그리고 함수호출 되는 API함수호출 안 되는 CLI를 용도별로 나눠 쓰는 판단이 배울 점이다. 어떤 앱이든 "AI를 붙이되 특정 벤더에 종속되지 않는 법"의 표본.

② 게으른 의존성 & 우아한 성능 저하(graceful degradation)

무거운 라이브러리를 함수 안에서만 import하고, 없으면 그 기능만 501 + 설치안내로 응답해 서비스는 살려 두는 패턴. "핵심은 requests만으로 즉시 동작, 부가는 필요할 때만"이라는 티어드 디펜던시는 배포·온보딩 마찰을 줄인다. 사용자가 전체 의존성을 깔지 않아도 앱이 뜨는 경험을 만드는 실전 기법이다.

③ 밴을 피하는 데이터 수집 (throttle + 직결/프록시 자동전환)

외부 API를 자주 긁을 때 IP 차단을 피하는 현실적 기술 — 요청 간 최소 간격 강제, 랜덤 지터(random.uniform) 추가, 직결 먼저·프록시 폴백의 자동 전환. 그리고 "시각 기반 쓰로틀러는 락이 아니라서 동시성에 뚫린다"는 한계를 알고, 그 위험한 호출을 직렬로 묶어 보완하는 사고방식. 스크래핑·데이터 파이프라인을 짜는 누구에게나 직접 쓸모 있다.

④ LLM 도구층의 토큰 위생 & 환각 방지

도구가 원본을 그대로 뱉지 않고 "최근 N행 + 핵심 필드 + 요약통계"로 압축해 맥락을 아끼는 것, 도구가 절대 예외를 던지지 않고 {"error":...}를 돌려줘 AI가 스스로 복구하게 하는 것, 그리고 _payload_empty()로 "빈 데이터"를 명시해 AI가 없는 데이터를 지어내지 못하게 막는 것. 함수호출 에이전트를 만들 때 두고두고 참고할 세 가지 규율이다.

⑤ "결정자 없는" 멀티에이전트 토론 (반직관 설계)

강세/약세 리서처를 두되 승자를 가리는 트레이더 역할을 일부러 뺀 구조. 사회자가 결론 대신 "진짜 이견 + 검증 체크리스트 + 데이터 공백"을 돌려준다. "AI에게 결론을 시키지 말고, 무엇을 더 확인해야 하는지를 시켜라"는 발상은, 신뢰성이 중요한 어떤 분석 도구에도 이식할 수 있는 값진 패턴이다.

⑥ 로컬 우선 프라이버시 & 공개 배포 안전장치

DB 없이 localStorage + ~/.vibe-research/로 민감정보를 로컬에만 두는 설계, 그리고 그럼에도 누군가 공개 배포할 때를 대비한 SSRF 가드 · VR_API_KEY 인증 · DNS 재해석. "로컬에선 편하게, 공개 땐 안전하게"를 환경변수 하나로 전환하는 방식은 실무 배포 감각을 길러 준다.

실습 아이디어(가볍게)
"결론을 안 내는 AI"를 직접 흉내 내 보기

이 레포의 핵심은 A주 데이터가 아니라 "AI에게 결론을 강요하지 않는 프롬프트 설계"다. 아무 주제(예: 새 노트북 살까)로, 자기 AI에게 두 번 물어보라 — (a) "사는 게 나아?"라고 결론을 요구, (b) "강세 논거·약세 논거·더 확인할 것만 정리하고 결론은 내지 마"라고 요구. 후자가 얼마나 더 검증 가능한 사고를 끌어내는지 곧바로 체감된다. Vibe-Research의 debate.py가 정확히 이걸 코드로 강제한 것이다.

7시스템 / 실행 요구사항

가볍다 — GPU도 DB도 Docker도 없다
항목필요한 것
런타임Python 3.10+(백엔드) · Node.js(프런트). 그게 전부 — GPU·데이터베이스·Docker 불필요
LLM추론은 사용자의 AI가 한다 — 로컬 CLI 구독(Claude Code 등) 또는 OpenAI 호환 API 키 또는 MCP. 앱 자체엔 모델이 없다
네트워크텐센트·동방재부·108개 RSS에 접속 가능해야. 본토 밖이면 일부 소스가 느리거나 막힐 수 있음(직결/프록시 자동전환이 흡수 시도)
백엔드 기동cd backend && python3 -m venv .venv && .venv/bin/pip install -r requirements.txt.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8900
프런트 기동cd frontend && npm install && npm run dev → 브라우저에서 http://localhost:5899
AI 연결웹 UI의 "接入 AI(AI 연결)" 페이지에서 구독/API/MCP 중 선택. API 키는 브라우저 localStorage에만 저장(서버 업로드 없음)

주요 환경변수 (backend/.env.example · 로컬 자체호스팅은 전부 선택)

변수의미
VR_API_KEY설정 시 /api/health를 뺀 모든 API가 Authorization: Bearer 요구 — 공개 배포엔 필수(openssl rand -hex 32로 생성)
VR_ALLOW_ORIGINSCORS 화이트리스트(기본 *)
VR_DATA_DIR · VR_REPORTS_DIR로컬 저장 루트(기본 ~/.vibe-research/) — 보유내역·업로드 리포트가 여기 저장, 서버로 안 올라감
VR_DATA_PROXY=1시스템 프록시 강제(기본은 직결 우선 — 본토 VPN 우회용)
IWENCAI_API_KEY선택 — iwencai(问财) 시맨틱 리포트 검색에만 사용

8직접 해볼 수 있는 실습 과제

난이도별로. A주 지식 없이도 "구조"를 배우게 구성했다
과제 1 난이도 ●○○○○

backend만 띄우고 REST 엔드포인트 직접 두드리기

git clone --depth 1 후 백엔드만 기동한다(프런트 없이). 브라우저나 curlhttp://localhost:8900/api/health, 그리고 시세 엔드포인트를 종목코드와 함께 호출해 JSON 응답을 눈으로 본다. "프런트 없이도 데이터 계층이 독립적으로 동작한다"를 체감하는 게 목표. app.py의 44개 라우트를 훑어 어떤 데이터가 있는지 지도부터 그리자.

과제 2 난이도 ●●○○○

tools.py의 "토큰 절약 압축" 지점 찾기

tools.py의 23개 도구 중 query_fund_flow처럼 큰 표를 다루는 걸 골라, 원본을 어떻게 잘라 내는지(최근 N행·핵심 필드·요약통계)를 코드에서 짚는다. 그리고 도구가 실패할 때 raise 대신 {"error": ...}를 반환하는 부분을 찾아, "왜 예외를 안 던지는가(=AI가 복구하게)"를 한 줄로 설명해 보자.

과제 3 난이도 ●●●○○

em_get 쓰로틀러를 내 스크래퍼에 이식하기

astock.pyem_get(최소 간격 + 랜덤 지터 + 직결/프록시 전환)을 참고해, 아무 공개 API를 밴당하지 않게 긁는 작은 파이썬 스크래퍼를 짜 본다. 일부러 간격을 0으로 줄여 보고(밴 위험), 다시 1초로 늘려 안정화되는지 관찰하라. "시각 기반 쓰로틀러는 동시 호출에 뚫린다"를 스레드 2개로 재현해 보는 게 하이라이트.

과제 4 난이도 ●●●●○

debate.py의 "결정자 없는 토론"을 다른 도메인에 적용

강세/약세 리서처 + 중립 사회자(승자 판정 없음) 구조를 투자가 아닌 주제(예: "이 기술 스택을 도입할까")로 옮겨 프롬프트를 짜 본다. 핵심 제약 두 개를 지켜라 — (1) 각 논거는 구체적 근거를 인용, (2) 사회자는 결론 대신 "진짜 이견 + 더 확인할 것"만 출력. 결론을 요구했을 때와 결과가 어떻게 달라지는지 비교하라.

과제 5 난이도 ●●●●●

mcp_server.py를 읽고 "내 데이터"를 MCP 도구로 노출하기

106줄짜리 mcp_server.py(표준 라이브러리만, JSON-RPC over stdio)를 정독해, OpenAI 도구 스키마를 MCP {name, description, inputSchema}로 변환하는 부분을 이해한다. 그다음, 자기만의 작은 데이터(예: 로컬 CSV 조회)를 MCP 도구 하나로 감싸 Claude Code에 붙여 본다. claude mcp add ...로 등록해 실제로 호출되는지 확인하는 게 최종 목표. "내 도구를 에이전트에 꽂는" 감각을 완성한다.

9관련 기술 심화 로드맵 (주차별)

이 레포를 계기로 "데이터 + AI 통합" 공부 계획을 짠다면
주차주제구체적으로
1주FastAPI로 데이터 API 짓기라우팅·의존성 주입·비동기·CORS·프록시. app.py의 44개 라우트를 표본으로, 작은 "시세 조회 API"를 직접 만들어 본다
2주안전한 웹 스크래핑 / 데이터 수집쓰로틀링·랜덤 지터·재시도·직결/프록시 전환·인코딩(GBK/UTF-8). astock.pyem_get 정독 + 내 스크래퍼 이식
3주LLM 함수호출(툴 유스) 에이전트OpenAI 호환 /chat/completions 함수호출 루프, 도구 스키마, 토큰 절약 압축, 에러를 값으로 반환. chat.py·tools.py
4주멀티에이전트 & MCP역할 분리(강세/약세/사회자), "결정자 없는" 토론, MCP 서버(JSON-RPC over stdio)로 도구 노출. debate.py·mcp_server.py
5주React 19 데이터 대시보드Vite 프록시·zustand 상태·echarts 차트·localStorage 프라이버시·NDJSON 스트리밍 소비. frontend/src/lib·pages
6주(선택)배포 보안SSRF 가드·Bearer 인증·환경변수로 로컬/공개 전환·DNS 재해석. chat.py_check_base_url + VR_API_KEY 경로

10핵심 키워드 사전

이 문서에 나온 개념 압축 정리
용어
BYOM · 모델 비종속
"Bring Your Own Model." 앱이 LLM을 내장하지 않고 사용자의 AI(구독 CLI·API·MCP)를 꽂게 하는 설계. 벤더 종속이 없고, 앱은 데이터에만 집중한다.
용어
투research(投研) / 개인 투자 리서치
매매 전 데이터를 모아 근거를 만드는 조사 과정. Vibe-Research는 이 "조사·정리"만 담당하고 주문·추천은 하지 않는다.
용어
게으른 의존성(lazy import) / 티어드 디펜던시
무거운 라이브러리를 함수 안에서만 import해, 없어도 앱은 기동하고 그 기능만 501을 반환. "핵심은 가볍게, 부가는 필요할 때만."
용어
em_get 쓰로틀러
동방재부 요청 사이 최소 간격(+랜덤 지터)을 강제하는 밴 방지 장치. 시각 기반이라 락이 아님 → 동시 호출에 뚫려, debate가 관련 호출을 직렬로 묶어 보완.
용어
직결 우선 · 프록시 폴백
본토 VPN/Clash 프록시가 금융 사이트를 깨는 걸 우회하려, 시스템 프록시를 무시하고 직결을 먼저 시도한 뒤 실패 시 프록시로 폴백·고정.
용어
객관 도구 23개(tools.py)
채팅·MCP·토론이 공유하는 데이터 도구 세트. 원본을 "최근 N행+핵심 필드+요약통계"로 압축(토큰 절약)하고, 실패해도 예외 대신 {"error":...}를 반환.
용어
다공 토론(多空辩论) · 결정자 없음
강세/약세 리서처 + 중립 사회자로 구성하되 승자 판정 트레이더가 없다. 사회자는 결론 대신 "진짜 이견 + 검증 체크리스트 + 데이터 공백"을 출력.
용어
도시에(dossier) · _DOSSIER_SPEC
토론 전에 LLM 없이 모으는 객관 데이터 13종. em_get 항목은 직렬, 텐센트/직결은 4-워커 병렬. "데이터 먼저 확정 → 그 위에서만 토론."
용어
환각 방지(_payload_empty)
응답이 "껍데기뿐(빈 값)"인지 판별해, 빈 항목엔 "진짜 없을 수도·소스가 죽었을 수도"라는 명시 주석을 붙임 → AI가 없는 데이터를 지어내지 못하게.
용어
구독 CLI 브리지(cli_runtime.py)
로컬에 로그인된 CLI(Claude Code·Qwen·DeepSeek·Codex)를 subprocess로 실행해 API 키 없이 월 구독으로 AI를 쓰게 함. CLI는 함수호출 불가라 "데이터가 프롬프트에 다 담긴" 경우에만 사용.
용어
SSRF 가드(_check_base_url)
사용자 입력 baseURL이 클라우드 메타데이터(169.254)·내부망을 가리키면 차단. 공개 배포 시 DNS 재해석까지 검사해 우회 방지.
용어
로컬 우선 프라이버시
DB 없이 보유종목·자선주·LLM 키는 브라우저 localStorage, 업로드 리포트·보유내역은 ~/.vibe-research/ 디스크에만. 서버로 업로드하지 않음.

11참고 링크

더 파고들 때