researcher: 사람과 AI 에이전트를 위한 검색 가능한 퀀트 리서치 아카이브
퀀트 리서치는 어디에나 있으면서 어디에도 없다. 필요한 논문은 arXiv에 있다. 참조 구현은 GitHub에 있다. 직관은 누군가가 2019년에 쓴 블로그 글 속에 묻혀 있다. 실제 진입/청산 로직은 좋아요 400개에 문서화는 전혀 없는 TradingView의 Pine 스크립트다. 네 개의 코퍼스, 네 개의 검색창, 네 가지 관례, 그리고 상호 참조는 전혀 없다. 어떤 아이디어가 일주일치 백테스트를 들일 가치가 있는지 판단하려 할 때, 진짜 비용은 바로 그 파편화다 — 읽는 것이 아니라 찾는 것이 문제다.
그래서 우리는 직접 만들었다. **researcher.marketmaker.cc**는 퀀트 트레이딩 리서치를 위한 큐레이션 아카이브이자 검색 엔진이다. 보통 arXiv, GitHub, 퀀트 블로그, TradingView에 흩어져 있는 자료를 한곳에 모으고, 그 전부를 전체 텍스트 검색이 가능하도록 색인하며, 그리고 — 우리가 가장 중요하게 여기는 부분 — Model Context Protocol(MCP) 엔드포인트와 공개 REST API를 통해 전체 코퍼스를 AI 에이전트에 노출한다. 사람은 키보드로 둘러보고 에이전트는 도구 호출로 질의할 수 있는 리서치 기반이며, 양쪽 모두 정확히 동일한 색인을 사용한다.
이 글은 그 안에 무엇이 들어 있는지, 어떻게 만들어졌는지, 그리고 우리의 AI 에이전트 스택에서 왜 지금 위치에 자리잡고 있는지를 둘러보는 글이다.
코퍼스 안에는 무엇이 있는가

researcher는 네 개의 주요 데이터셋을 통합하며, 각각 자체적인 전체 텍스트 색인을 가진다. 아래 수치는 2026-06-12 기준이며, 계속 변한다 — arXiv 파이프라인이 매일 실행되고 색인이 원본에서 재구축되므로 숫자는 늘어난다.
| 데이터셋 | 출처 | 문서 수 | 검색 대상 |
|---|---|---|---|
| Papers | arXiv q-fin (1997–2026) | 약 18,647 | 제목, 초록, 저자 (카테고리로 필터링) |
| Code | GitHub 저장소 | 약 12,957 | 이름, 설명, 토픽 (언어, 스타로 필터링) |
| Articles | 퀀트 블로그 | 약 4,633 | 제목, 설명 (출처, 날짜로 필터링) |
| Strategies | TradingView Pine 스크립트 | 약 15,180 | 제목, 설명, 태그 (카테고리로 필터링) |
네 개의 검색 가능한 색인을 합치면 51,000개가 조금 넘는 문서가 된다. papers 색인은 단일 코퍼스 중 가장 크며 우리가 가장 많은 공을 들인 것이다. 이는 1997년까지 거슬러 올라가는 arXiv 퀀트 금융 전량(q-fin.*)을 담은 것으로, 손으로 골라낸 일부가 아니다. 초기에는 사이트가 큐레이션된 논문 수백 편만 제공했지만, 현재 색인은 완전한 q-fin 코퍼스이며, 큐레이션된 출처 정보가 그 위에 병합되어 있어 특정 퀀트 블로그에서 함께 참조된 논문은 그 출처 정보를 함께 지닌다.
네 개의 검색 색인 너머로, 사람을 위한 사이트는 더 많은 것을 얹는다. 우리가 직접 쓰는 리서치 노트와 일일 다이제스트, 퀀트 사이트와 저자 디렉터리, 관련 YouTube 채널을 색인하는 videos 섹션, 그리고 funds 디렉터리가 그것이다. 네 개의 색인은 검색 가능한 척추이고, 나머지는 그것을 둘러싼 큐레이션이다.
단일 진실 공급원

초기에 우리에게 조용히 문제를 일으켰던 것 — 그리고 지금 우리가 강하게 방어하도록 설계하는 것 — 은 바로 수치 불일치였다. 홈페이지는 한 숫자를 말하고, 검색은 다른 숫자를 반환하고, API는 세 번째 숫자를 내놓았다. 한때 첫 페이지는 논문이 719편이라고 광고하는데 검색은 18,000편 넘게 반환했다. 리서치 도구에 대한 신뢰를 무너뜨리는 데 코퍼스가 자기 크기에 대해 스스로 합의하지 못하는 것보다 더 치명적인 것은 없다.
해법은 모든 표면이 한곳에서 읽도록 만드는 것이었다. papers 코퍼스의 경우 Meilisearch가 진실 공급원이다. 앱 번들 안에 살아 있는 논문의 두 번째 사본은 존재하지 않는다. 홈페이지의 수치, /papers의 수치, API가 반환하는 수치, 그리고 실제로 검색하는 문서가 모두 같은 색인이다. 코퍼스 자체는 큐레이션된 논문 집합을 가져와 arXiv 전량과 합치고(arXiv id로 중복 제거, 출처 배열 병합), 최신순으로 정렬한 뒤, 색인기가 소비하는 약 25MB짜리 단일 파일을 쓰는 오프라인 수집 단계에서 구축된다. 그 파일은 약 15,000–18,000개의 레코드이며, 의도적으로 클라이언트에 번들되지 않는다 — 그 정도 크기의 코퍼스를 브라우저로 보낼 이유는 전혀 없다.
나머지 세 데이터셋(code, articles, Pine 스크립트)은 서버 측에서 각자의 JSON 파일에서 읽고 같은 파일로부터 색인되며, Meili를 공급원으로 삼는 마이그레이션이 다음 예정 단계다. 전반에 걸친 규칙은 이렇다. 데이터는 서버에서 읽고, 수 메가바이트짜리 데이터셋을 절대 클라이언트 번들로 import 하지 않으며, 색인과 표시되는 수치가 같은 출처에서 나오도록 한다. 그러면 비동기화는 구조적으로 불가능해진다.
검색: 전체 텍스트, 오타 허용, 패싯 기반

검색 엔진은 Meilisearch로, 앱과 같은 서버에서 실행되며 localhost에 바인딩되어 있다 — 외부에 공개되지 않는다. 우리는 이것을 벡터 저장소가 아니라 전체 텍스트 검색 엔진으로 사용한다. 임베딩도 없고, 의미적 유사도 마법도 없다. "이 개념을 언급하는 논문과 저장소를 찾아줘"라는 요구에는, 제목·초록·설명·저자·태그에 대한 오타 허용 어휘 검색이 임베딩 색인으로는 불가능한 방식으로 빠르고 예측 가능하며 디버깅하기 쉽다. 각 색인은 완전한 원본 문서에 더해 추가된 _id를 저장하므로, 검색은 앱이 곧바로 렌더링할 수 있는 완전히 채워진 레코드를 반환한다 — 적중 결과를 다시 채우기 위한 두 번째 요청이 필요 없다.
실무에서 중요한 몇 가지 세부 사항은 다음과 같다.
-
오타 허용과 관련성 순위는 Meilisearch에서 공짜로 제공된다.
momentm을 검색해도 momentum 논문을 여전히 찾는다. 결과는 단순히 필터링되는 게 아니라 순위가 매겨진다. -
패싯 필터링. 논문은 arXiv
category(q-fin.PM,q-fin.TR등)로, 저장소는language와stars로, 글은source와date로, Pine 스크립트는category로 필터링된다./papers페이지는 색인의 실시간 패싯 분포로부터 카테고리 드롭다운을 구성하므로, 필터 옵션은 항상 코퍼스에 실제로 존재하는 것을 반영한다. -
camelCase 분리. Meilisearch는 공백과 구두점에서 토큰화하지만 camelCase에서는 그렇지 않다. 즉, 말 그대로
TradingAgents라는 이름의 저장소는 단일 토큰이 되어, "trading agents"라는 자연스러운 질의로는 도달할 수 없다. 색인 작업 중에 우리는name_split필드를 파생시킨다 —TradingAgents→TradingAgents Trading Agents,ai-hedge-fund→ai-hedge-fund ai hedge fund— 그리고 이를 검색 가능한 속성에 추가한다. 원본 토큰을 먼저 두어 정확한 이름 일치가 여전히 가장 높은 순위에 오르게 하고, 파생된 필드는 클라이언트로 절대 반환되지 않는다. 대표 저장소를 찾느냐 못 찾느냐를 가르는, 작지만 결정적인 장치다. -
둘러보기 = 최신순. 빈 질의는 오류가 아니라 둘러보기 경로다.
/papers에서 빈 질의는published내림차순으로 정렬하므로, 이 페이지는 최신 q-fin 리서치의 역시간순 피드 역할도 겸한다. -
집계를 위한 보조 색인. 어떤 숫자는 Meilisearch가 질의 시점에 저렴하게 계산할 수 없다 — 모든 저장소의 총 스타 수, 총 Python 파일 수, 총 노트북 수가 그렇다. 매 페이지 로드마다 코퍼스 전체를 훑는 대신, 색인기는 그 합계를 색인 시점에 한 번 계산해 데이터셋당 문서 하나를 담는 작은
researcher_meta색인에 기록한다.stats엔드포인트는 그것을 그대로 다시 읽는다. 재색인 시에만 변하는 수치는 재색인 시에만 계산된다. -
실제로 쓸 만한 페이지네이션 상한. papers 색인은 Meilisearch의
maxTotalHits를 50,000으로 올리고published를 정렬 가능으로 표시하므로, 약 18,000개 문서 코퍼스를 깊게 넘기며 전체를 최신순으로 정렬할 수 있다 — 관련성 적중의 첫 페이지에만 머무르지 않는다.
색인기는 멱등적이다. 각 색인이 없으면 생성하고, 설정을 다시 적용하며, 모든 문서를 _id를 키로 2,000개 단위 배치로 업서트한다(논문은 arXiv id에서, 저장소와 글은 URL 해시에서 키를 파생한다). 전체 색인이 원본 파일에서 재구성 가능하므로 관리할 백업이 없다 — 롤백은 그저 재색인이다. 다시 실행해도 구조상 안전하다.
에이전트 접근 가능: MCP와 공개 API

researcher를 우리가 하는 나머지 일과 묶어주는 부분이 여기 있다. 코퍼스는 검색창이 달린 웹사이트일 뿐만 아니라 — AI 에이전트가 호출할 수 있는 도구다.
MCP 엔드포인트
researcher는 Streamable HTTP를 통해 **/api/mcp**에서 Model Context Protocol 서버를 노출한다. MCP 호환 에이전트라면 무엇이든 — Claude든, 우리 자체 스택의 커스텀 에이전트든, 프로토콜을 말하는 무엇이든 — 연결하여 라이브 코퍼스에 대해 읽기 전용 도구를 호출할 수 있다. 도구는 13개이며, 데이터셋별로 묶여 일관된 search / get / list 형태를 따른다.
| 그룹 | 도구 |
|---|---|
| Papers | search_papers, get_paper, list_papers |
| Code | search_repos, get_repo, list_repos |
| Articles | search_articles, get_article, list_articles_by_site |
| Strategies | search_pine, get_pine_script, list_pine |
| Knowledge | knowledge_query (우아한 스텁, 향후 그래프 레이어를 위해 예약됨) |
도구 스키마는 사람이 아니라 에이전트를 위해 작성되었다. 예를 들어 search_papers는 제목·초록·저자에 대한 오타 허용 관련성 순위 검색임을 알리고, 선택적 category 필터(예: q-fin.PM)와 결과 개수 제한을 제공하며, 범위를 좁힌 뒤에는 전체 초록을 얻기 위해 get_paper를 호출하라고 에이전트에 안내한다. search는 간결한 스니펫 크기의 적중 결과를 반환하여 에이전트가 여러 결과를 저렴하게 훑을 수 있게 하고, get은 하나를 고른 뒤 전체 레코드를 반환한다. 이 두 단계 형태는 에이전트의 컨텍스트 윈도가 필요 없는 초록들에 빠져 허우적거리지 않게 막아준다.
구체적으로, 가령 최적 실행을 조사하는 에이전트는 search_papers("optimal execution", category: "q-fin.TR")를 실행해 순위가 매겨진 제목과 스니펫의 후보 목록을 얻고, search_repos("optimal execution", language: "Python")로 관련성순 정렬되고 스타로 필터링 가능한 구현을 찾고, search_pine("VWAP")로 같은 아이디어가 공개된 TradingView 전략으로 어떻게 나타나는지 볼 수 있다 — 한 시간 전만 해도 세 개의 서로 다른 웹사이트였던 세 코퍼스에 대한 세 번의 도구 호출이다. 그런 다음 단 한 번의 get_paper가 유망해 보이는 그 한 편의 전체 초록을 가져온다. 에이전트는 프로토콜을 떠나지 않으며, 모든 결과는 다시 가져와야 하는 검색 결과 스텁이 아니라 실제로 채워진 진짜 레코드다.
공개 REST API
MCP를 쓰지 않는 소비자를 위해 /api/v1/ 아래에 병렬 REST 표면이 있다. papers, repos, articles, pine, 그리고 stats 집계가 그것이다. 이는 q, category, limit, offset 파라미터로 평범한 JSON을 말하고, 각 페이지와 함께 실제 총계와 패싯 분포를 반환하며, CORS가 활성화되어 있다. GET /api/v1/papers?q=optimal+execution&category=q-fin.TR는 어디서든 한 줄이면 된다. 동일한 엔드포인트가 사이트 자체의 /papers 페이지를 구동한다 — 브라우저는 또 하나의 API 클라이언트일 뿐이다.
정직하게 실패하기
거짓말하는 검색 백엔드는 멈춰버린 백엔드보다 나쁘다. 우리는 Meilisearch에 도달할 수 없을 때 무슨 일이 일어나는지에 대해 의도적인 입장을 취했다. 데이터 레이어는 실패 시 조용히 빈 결과를 반환하는 대신 예외를 던지며 — 어떻게 처리할지는 호출자가 정한다. 여전히 인메모리 사본을 유지하는 데이터셋의 경우, 도구는 그 사본에 대한 평범한 .filter()로 폴백하므로 사이트는 계속 살아 있다. Meilisearch가 진실 공급원이고 두 번째 사본이 없는 papers의 경우, 도구와 API는 오래되거나 부분적인 데이터를 내놓는 대신 명시적인 오류를 반환한다(API는 503으로 응답한다). 각 검색 호출에는 짧은 타임아웃이 있어 멈춘 색인이 도구를 정지시키지 못하게 한다. 원칙은 이렇다. 시끄럽게 degrade하되, 절대 조용히 틀린 답을 돌려주지 않는다.
데이터는 어떻게 들어오는가

코퍼스는 스크레이퍼 파이프라인으로 공급되며, 모두 무료의 공개 출처를 대상으로 실행된다.
- Papers는 arXiv Atom API에서 온다. 하베스터가 전체
q-fin코퍼스를 JSONL로 끌어오고, 빌드 단계가 큐레이션 집합과 합치며(arXiv id로 중복 제거, 출처 병합), 그 결과가 색인기에 넘겨진다. 하베스터에는 외부 읽기 목록에서 시드하기 위한 "이 특정 id들을 enrich" 모드도 있다. - Code는 퀀트 관련 GitHub 저장소를 크롤링한 것으로, 필터링에 중요한 메타데이터 — 스타, 포크, 주 언어, 토픽, 그리고 Python 파일과 노트북 개수 — 와 함께 수집된다.
- Articles는 퀀트 블로그와 애그리게이터에서 스크레이핑되며, 더 나은 것들은 링크 소실에 살아남도록 로컬에 미러링된다. 홈페이지는 우리가 로컬 사본을 저장한 글이 어떤 것인지 표시한다.
- Strategies는 TradingView Pine 스크립트와 그 메타데이터 — 저자, 카테고리, 태그, 좋아요, 그리고 목록에 코드·차트·분석이 포함되어 있는지 여부 — 다.
- Videos는 관련 YouTube 채널을 색인하여 강연과 워크스루를 글로 된 자료와 나란히 찾을 수 있게 한다.
프로덕션에서의 재색인은 localhost에 바인딩된 Meilisearch로 가는 SSH 터널을 통해 실행되는데, 엔진은 결코 인터넷에 노출되지 않기 때문이다. 전체 루프 — 하베스트, 빌드, 배포, 색인 — 는 멱등적으로 다시 실행되도록 설계되었으며, 이것이 바로 매일 cron이 하는 일이다.
접근과 호스팅

researcher는 우리의 Server 1에서 작은 Docker Compose 스택으로 실행된다. Traefik 뒤의 Next.js 컨테이너와 localhost에 바인딩된 Meilisearch 컨테이너로 구성된다. Next.js 앱은 데이터셋을 서버 측에서 읽고 내부 네트워크를 통해 Meilisearch와 통신한다.
접근은 우리의 공유 신원 서비스인 **auth.marketmaker.cc**를 통해 게이트된다. 토큰은 auth 서비스의 JWKS에 대해 검증되는 RS256 JWT다 — 모든 인가 결정은 서명을 확인하며(엄격한 발급자 및 알고리즘 검사를 거치고, 키 엔드포인트에 도달할 수 없으면 닫힌 상태로 실패한다), 검증되지 않은 디코드 경로는 내비게이션 바에 이메일을 표시하는 것 같은 외형적 UI에만 쓰인다. auth 서비스는 서비스별 역할을 발급한다. researcher에서는 admin 역할이 내부 관리자 영역(우리가 스크레이퍼를 실행하고 모니터링하는 곳)을 게이트하며, 공개 홈페이지는 토큰이 전혀 필요 없다. 우리의 다른 내부 도구들 앞단을 지키는 것과 동일한 auth 패브릭이므로, 한 번의 로그인이 생태계 전반에 적용된다.
Marketmaker 스택에서의 위치

researcher는 인프라이지 목적지가 아니다. 핵심은 웹사이트가 아니라 — 이제 우리가 사람과 에이전트가 공유하는, 그 분야에 대한 질의 가능한 뷰를 갖게 되었다는 점이다.
사람인 우리에게 이것은 바로 이 블로그의 많은 부분이 나오는 곳이다. VectorBT 같은 도구를 리뷰하거나 TradingAgents나 Fincept Terminal 같은 프레임워크를 해부할 때, 출발점은 흔히 researcher 전반에 대한 검색이다. 이것이 어떤 논문 위에 세워졌는지, 같은 문제를 푸는 다른 저장소는 무엇인지, 누가 그것에 대해 썼는지. 아카이브는 깔때기이고, 블로그 글은 그 깔때기에서 떨어져 나오는 것이다.
우리의 AI 에이전트에게 이것은 더 구조적인 무언가다. MCP를 통해 도달 가능한 리서치 기반이 있다는 것은, 전략 작업을 하는 에이전트가 arXiv를 실시간으로 스크레이핑하거나, 네 개의 서로 다른 API를 저글링하거나, 밖에 무엇이 있는지 추측할 필요가 없다는 뜻이다 — 에이전트는 이미 통합되고 중복 제거되고 색인된 코퍼스에 대해 search_papers, search_repos, search_pine을 호출한다. 그것은 우리의 command-and-operate(cmdop) 및 에이전트 도구화와 같은 방향이다. 에이전트에게 실제 데이터에 대한 타입이 지정된, 읽기 전용의, 잘 문서화된 도구를 주고, 백엔드가 사용 불가일 때 시끄럽게 실패하며, 하나의 공유 백엔드가 동일한 색인으로부터 사람용 UI와 기계용 인터페이스를 함께 제공하게 한다. 사람은 둘러보고 에이전트는 질의한다 — 하지만 둘은 같은 아카이브를 보고 있으며, 그것이 바로 전부의 핵심이다.
결론
researcher는 작고 성가신 문제 — 퀀트 리서치가 서로 대화하지 않는 네 곳에 흩어져 있다는 — 에 대한 해결책으로 시작했고, 우리가 매일 기대는 무언가로 발전했다. 논문, 코드, 글, 전략에 걸쳐 약 51,000개의 문서가 모두 하나의 전체 텍스트 검색 엔진 뒤에 있으며, 브라우저를 든 사람과 MCP 클라이언트를 든 에이전트 양쪽 모두에게 도달 가능하다. 이것은 의도적으로 화려하지 않다. 임베딩이 아니라 전체 텍스트 검색이고, 영리한 캐시가 아니라 단일 진실 공급원이며, 장애를 덮어 가리는 도구가 아니라 정직한 오류를 던지는 도구다.
트레이딩 리서치용 에이전트를 만들고 있다면, 그 교훈은 우리의 특정 코퍼스를 넘어 일반화된다. 에이전트에게 줄 수 있는 가장 레버리지 높은 것은 더 큰 모델이 아니라, 에이전트가 필요로 하는 데이터에 대한 깨끗하고 통합되고 질의 가능한 뷰 — 사람이 신뢰하는 것과 동일한 색인을 통해 노출된 것 — 다. 그것이 바로 researcher다.
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.