researcher: Kho lưu trữ nghiên cứu quant có thể tìm kiếm dành cho con người và AI agent
Nghiên cứu quant ở khắp mọi nơi mà cũng chẳng ở đâu cả. Bài báo bạn cần thì nằm trên arXiv. Bản triển khai tham chiếu thì ở trên GitHub. Trực giác về ý tưởng thì bị chôn vùi trong một bài blog ai đó viết hồi 2019. Còn logic vào/ra lệnh thực sự lại là một script Pine trên TradingView với 400 lượt thích mà chẳng có tài liệu nào. Bốn kho dữ liệu, bốn ô tìm kiếm, bốn bộ quy ước, không có tham chiếu chéo nào. Khi bạn đang cố quyết định xem một ý tưởng có đáng dành cả tuần để backtest hay không, chính sự phân mảnh đó mới là cái giá thực sự — không phải việc đọc, mà là việc tìm ra.
Vì vậy chúng tôi tự xây dựng kho riêng. researcher.marketmaker.cc là một kho lưu trữ được tuyển chọn và một công cụ tìm kiếm cho nghiên cứu giao dịch định lượng. Nó tập hợp lại tất cả tài liệu vốn thường nằm rải rác trên arXiv, GitHub, các blog quant và TradingView vào một nơi duy nhất, lập chỉ mục toàn bộ để tìm kiếm toàn văn, và — đây là phần chúng tôi quan tâm nhất — phơi bày toàn bộ kho dữ liệu cho các AI agent thông qua một endpoint Model Context Protocol (MCP) và một REST API công khai. Đó là một nền tảng nghiên cứu mà con người có thể duyệt bằng bàn phím và agent có thể truy vấn bằng một lệnh gọi công cụ, đều dựa trên cùng những chỉ mục giống hệt nhau.
Bài viết này là một chuyến tham quan những gì bên trong nó, cách nó được xây dựng, và lý do nó nằm ở vị trí đó trong ngăn xếp AI-agent của chúng tôi.
Có gì trong kho dữ liệu

researcher hợp nhất bốn tập dữ liệu chính, mỗi tập có chỉ mục toàn văn riêng. Các con số dưới đây tính đến ngày 2026-06-12, và chúng luôn thay đổi — pipeline arXiv chạy hằng ngày và chỉ mục được dựng lại từ nguồn, nên các con số ngày càng tăng.
| Tập dữ liệu | Nguồn | Số tài liệu | Bạn tìm kiếm gì |
|---|---|---|---|
| Bài báo | arXiv q-fin (1997–2026) | ~18.647 | tiêu đề, tóm tắt, tác giả (lọc theo danh mục) |
| Mã nguồn | Repo GitHub | ~12.957 | tên, mô tả, chủ đề (lọc theo ngôn ngữ, số sao) |
| Bài viết | Blog quant | ~4.633 | tiêu đề, mô tả (lọc theo nguồn, ngày) |
| Chiến lược | Script Pine của TradingView | ~15.180 | tiêu đề, mô tả, thẻ (lọc theo danh mục) |
Như vậy là hơn 51.000 tài liệu một chút trải khắp bốn chỉ mục có thể tìm kiếm. Chỉ mục bài báo là kho đơn lẻ lớn nhất và là cái chúng tôi đầu tư công sức nhiều nhất: nó là toàn bộ luồng tài liệu tài chính định lượng của arXiv (q-fin.*) trải dài về tận năm 1997, chứ không phải một tập con được chọn lọc thủ công. Trước đây trang web chỉ vận chuyển vài trăm bài báo được tuyển chọn; chỉ mục hiện tại là toàn bộ kho q-fin, với nguồn gốc đã tuyển chọn được hợp nhất chồng lên trên, để một bài báo đồng thời được một blog quant cụ thể trích dẫn vẫn mang theo thông tin ghi nhận đó.
Ngoài bốn chỉ mục tìm kiếm, trang dành cho con người còn xếp chồng thêm nhiều thứ nữa: ghi chú nghiên cứu và bản tổng hợp hằng ngày do chính chúng tôi viết, một thư mục các trang web quant và tác giả, một mục video lập chỉ mục các kênh YouTube liên quan, và một thư mục quỹ. Bốn chỉ mục là xương sống có thể tìm kiếm; mọi thứ khác là phần tuyển chọn xoay quanh nó.
Nguồn chân lý duy nhất

Thứ âm thầm hỏng hóc với chúng tôi từ những ngày đầu — và là thứ mà bây giờ chúng tôi quyết liệt thiết kế để chống lại — chính là sự sai lệch về số đếm. Trang chủ ghi một con số, kết quả tìm kiếm trả về một con số khác, còn API lại trả về con số thứ ba. Có lúc trang đầu quảng cáo 719 bài báo trong khi tìm kiếm trả về hơn 18.000 bài. Không gì làm xói mòn lòng tin vào một công cụ nghiên cứu nhanh hơn một kho dữ liệu không thể tự thống nhất với chính mình về việc nó lớn cỡ nào.
Cách khắc phục là khiến mọi bề mặt đều đọc từ cùng một nơi. Đối với kho bài báo, Meilisearch là nguồn chân lý. Không hề có bản sao thứ hai của các bài báo nằm trong bundle ứng dụng; con số trên trang chủ, con số trên /papers, con số do API trả về, và những tài liệu bạn thực sự tìm kiếm đều là cùng một chỉ mục. Bản thân kho dữ liệu được dựng offline bởi một bước nạp (ingestion) lấy tập bài báo đã tuyển chọn, hợp nó với luồng arXiv (đã khử trùng lặp theo id arXiv, với các mảng nguồn gốc được hợp nhất), sắp xếp mới nhất trước, rồi ghi ra một tệp duy nhất ~25 MB để bộ lập chỉ mục tiêu thụ. Tệp đó có khoảng 15.000–18.000 bản ghi và cố tình không được nhồi vào client — một kho cỡ đó chẳng việc gì phải vận chuyển xuống trình duyệt.
Ba tập dữ liệu còn lại (mã nguồn, bài viết, script Pine) được đọc phía máy chủ từ các tệp JSON của chúng và được lập chỉ mục từ chính các tệp đó, với việc chuyển sang dùng Meili-làm-nguồn là bước kế tiếp đã hoạch định. Quy tắc xuyên suốt là: đọc dữ liệu trên máy chủ, không bao giờ import một tập dữ liệu vài megabyte vào bundle client, và để chỉ mục lẫn các con số hiển thị đều đến từ cùng một gốc. Việc mất đồng bộ trở nên bất khả thi về mặt cấu trúc.
Tìm kiếm: Toàn văn, chịu được lỗi gõ, có phân mặt (faceted)

Công cụ tìm kiếm là Meilisearch, chạy trên cùng máy chủ với ứng dụng và bị ràng vào localhost — nó không được phơi bày công khai. Chúng tôi dùng nó như một công cụ tìm kiếm toàn văn, không phải một kho vector. Không embedding, không phép màu tương đồng ngữ nghĩa. Đối với nhu cầu "tìm cho tôi những bài báo và repo có nhắc đến khái niệm này," tìm kiếm từ vựng chịu được lỗi gõ trên tiêu đề, tóm tắt, mô tả, tác giả và thẻ là nhanh, dễ đoán và dễ gỡ lỗi theo cách mà một chỉ mục embedding không thể có. Mỗi chỉ mục lưu trữ toàn bộ tài liệu gốc cộng thêm một trường _id được thêm vào, nên một lượt tìm kiếm trả về các bản ghi đã được nạp đầy đủ để ứng dụng render trực tiếp — không cần một lượt fetch thứ hai để bù dữ liệu cho các kết quả.
Một vài chi tiết có ý nghĩa trong thực tế:
-
Khả năng chịu lỗi gõ và xếp hạng độ liên quan có sẵn miễn phí từ Meilisearch. Gõ
momentmvẫn tìm thấy các bài báo về momentum; kết quả được xếp hạng, chứ không chỉ được lọc. -
Lọc phân mặt (faceted). Bài báo lọc theo
categorycủa arXiv (q-fin.PM,q-fin.TR, …), repo theolanguagevàstars, bài viết theosourcevàdate, script Pine theocategory. Trang/papersdựng menu thả xuống danh mục của nó từ phân bố mặt (facet) trực tiếp của chỉ mục, nên các tùy chọn lọc luôn phản ánh đúng những gì thực sự có trong kho. -
Tách camelCase. Meilisearch tách token theo khoảng trắng và dấu câu nhưng không tách theo camelCase. Điều đó nghĩa là một repo có tên nguyên văn
TradingAgentssẽ là một token đơn, không thể tiếp cận được bằng truy vấn tự nhiên "trading agents." Trong lúc lập chỉ mục, chúng tôi suy ra một trườngname_split—TradingAgents→TradingAgents Trading Agents,ai-hedge-fund→ai-hedge-fund ai hedge fund— và thêm nó vào các thuộc tính có thể tìm kiếm. Token gốc được giữ ở vị trí đầu để các kết quả khớp tên chính xác vẫn xếp hạng cao nhất, còn trường suy ra thì không bao giờ được trả về cho client. Đó là một điều nhỏ tạo nên khác biệt giữa việc tìm thấy hay không tìm thấy repo chủ lực. -
Duyệt = mới nhất trước. Một truy vấn rỗng không phải là lỗi; nó là con đường duyệt. Trên
/papers, một truy vấn rỗng sắp xếp theopublishedgiảm dần, nên trang đó kiêm luôn vai trò một dòng tin (feed) đảo ngược theo thời gian của các nghiên cứu q-fin mới nhất. -
Một chỉ mục phụ cho các giá trị tổng hợp. Một số con số mà Meilisearch không thể tính rẻ tại thời điểm truy vấn — tổng số sao trên tất cả các repo, tổng số tệp Python, tổng số notebook. Thay vì quét toàn bộ kho mỗi lần tải trang, bộ lập chỉ mục ghi các tổng đó một lần, tại thời điểm lập chỉ mục, vào một chỉ mục nhỏ
researcher_metachứa một tài liệu cho mỗi tập dữ liệu. Endpointstatsđọc thẳng chúng ra. Các con số chỉ thay đổi khi lập chỉ mục lại thì chỉ được tính khi lập chỉ mục lại. -
Một trần phân trang thực sự dùng được. Chỉ mục bài báo nâng
maxTotalHitscủa Meilisearch lên 50.000 và đánh dấupublishedlà có thể sắp xếp, nên bạn có thể phân trang sâu vào một kho ~18.000 tài liệu và sắp xếp toàn bộ theo mới nhất trước — chứ không chỉ trang đầu tiên của các kết quả theo độ liên quan.
Bộ lập chỉ mục có tính idempotent: nó tạo từng chỉ mục nếu chưa có, áp dụng lại các thiết lập, và upsert mọi tài liệu theo từng lô 2.000 bản ghi được khóa theo _id (bài báo suy ra của mình từ id arXiv, repo và bài viết từ một hash của URL). Bởi toàn bộ chỉ mục có thể được tái dựng từ tệp nguồn, nên chẳng có bản sao lưu nào phải quản lý — một lần rollback chỉ đơn giản là lập chỉ mục lại. Việc chạy lại nó an toàn theo bản chất thiết kế.
Truy cập được bởi agent: MCP và một API công khai

Đây là phần gắn kết researcher với phần còn lại trong những gì chúng tôi làm. Kho dữ liệu không chỉ là một trang web với một ô tìm kiếm — nó là một công cụ mà một AI agent có thể gọi.
Endpoint MCP
researcher phơi bày một máy chủ Model Context Protocol tại /api/mcp qua Streamable HTTP. Bất kỳ agent nào tương thích MCP — Claude, một agent tùy chỉnh trong ngăn xếp của chính chúng tôi, bất cứ thứ gì nói được giao thức này — đều có thể kết nối và gọi các công cụ chỉ-đọc trên kho dữ liệu trực tiếp. Có 13 công cụ, được nhóm theo tập dữ liệu, tuân theo một hình dạng nhất quán search / get / list:
| Nhóm | Công cụ |
|---|---|
| Bài báo | search_papers, get_paper, list_papers |
| Mã nguồn | search_repos, get_repo, list_repos |
| Bài viết | search_articles, get_article, list_articles_by_site |
| Chiến lược | search_pine, get_pine_script, list_pine |
| Tri thức | knowledge_query (stub có xử lý mềm mại, dành riêng cho một lớp đồ thị trong tương lai) |
Các schema công cụ được viết vì lợi ích của agent, chứ không phải của con người. search_papers chẳng hạn, tự giới thiệu mình là tìm kiếm xếp hạng theo độ liên quan, chịu được lỗi gõ, trên tiêu đề, tóm tắt và tác giả, với một bộ lọc category tùy chọn (ví dụ q-fin.PM) và một giới hạn số kết quả — và bảo agent gọi get_paper để lấy phần tóm tắt đầy đủ một khi nó đã thu hẹp mọi thứ. search trả về các kết quả gọn, cỡ đoạn trích để agent có thể quét nhiều kết quả với chi phí rẻ; get trả về bản ghi đầy đủ một khi nó đã chọn được một cái. Hình dạng hai bước đó giữ cho cửa sổ ngữ cảnh của agent khỏi chìm trong những bản tóm tắt mà nó không cần.
Cụ thể, một agent đang khảo sát, chẳng hạn, vấn đề thực thi tối ưu (optimal execution) có thể chạy search_papers("optimal execution", category: "q-fin.TR") để có một danh sách rút gọn đã xếp hạng gồm tiêu đề và đoạn trích, chạy search_repos("optimal execution", language: "Python") để tìm các bản triển khai được sắp xếp theo độ liên quan và có thể lọc theo số sao, và chạy search_pine("VWAP") để xem cùng ý tưởng đó hiện diện thế nào dưới dạng một chiến lược TradingView đã công bố — ba lệnh gọi công cụ trên ba kho dữ liệu mà cách đây một giờ vẫn còn là ba trang web khác nhau. Sau đó một lệnh get_paper duy nhất kéo về phần tóm tắt đầy đủ cho cái trông có triển vọng. Agent không bao giờ rời khỏi giao thức, và mỗi kết quả là một bản ghi thật, đã được nạp đầy đủ, chứ không phải một mẩu kết quả tìm kiếm mà nó phải đi fetch lại.
REST API công khai
Đối với những bên tiêu thụ không dùng MCP thì có một bề mặt REST song song nằm dưới /api/v1/: papers, repos, articles, pine, và một tổng hợp stats. Nó nói JSON thuần với các tham số q, category, limit và offset, trả về tổng số thật và phân bố mặt (facet) kèm theo mỗi trang, và bật CORS. GET /api/v1/papers?q=optimal+execution&category=q-fin.TR là một câu lệnh một dòng từ bất cứ đâu. Cùng endpoint đó cũng vận hành trang /papers của chính trang web — trình duyệt chỉ là một client API khác.
Báo lỗi một cách trung thực
Một backend tìm kiếm nói dối còn tệ hơn một backend đang chết. Chúng tôi có một lập trường rõ ràng về việc gì xảy ra khi không thể tiếp cận Meilisearch. Lớp dữ liệu ném lỗi khi thất bại thay vì âm thầm trả về kết quả rỗng — và bên gọi quyết định cách xử lý. Đối với các tập dữ liệu vẫn còn giữ một bản sao trong bộ nhớ, các công cụ lùi về một lệnh .filter() thuần trên bản sao đó, nên trang web vẫn hoạt động. Đối với bài báo, nơi Meilisearch là nguồn chân lý và không có bản sao thứ hai, các công cụ và API trả về một lỗi tường minh (API phản hồi 503) thay vì phục vụ dữ liệu cũ hay không đầy đủ. Mỗi lệnh gọi tìm kiếm có một timeout ngắn để một chỉ mục bị treo không thể làm đình trệ một công cụ. Nguyên tắc là: suy giảm một cách ồn ào, không bao giờ lặng lẽ trả về câu trả lời sai.
Dữ liệu được nạp vào như thế nào

Kho dữ liệu được nuôi bằng một pipeline gồm các bộ scraper, tất cả đều chạy trên các nguồn công khai, miễn phí.
- Bài báo đến từ Atom API của arXiv. Một bộ harvester kéo toàn bộ kho
q-finvào JSONL, một bước build hợp nó với tập đã tuyển chọn (khử trùng lặp theo id arXiv, nguồn gốc được hợp nhất), và kết quả được giao cho bộ lập chỉ mục. Bộ harvester cũng có một chế độ "làm giàu những id cụ thể này" để gieo mầm từ các danh sách đọc bên ngoài. - Mã nguồn là một lượt crawl các repo GitHub liên quan đến quant, được thu thập kèm theo metadata quan trọng cho việc lọc — số sao, số fork, ngôn ngữ chính, chủ đề, và số đếm các tệp Python và notebook.
- Bài viết được scrape từ các blog quant và các trang tổng hợp, với những bài tốt hơn được sao chép cục bộ để chúng sống sót qua hiện tượng link rot. Trang chủ đánh dấu những bài viết nào chúng tôi đã lưu một bản sao cục bộ.
- Chiến lược là các script Pine của TradingView kèm metadata của chúng — tác giả, danh mục, thẻ, số lượt thích, và việc danh mục đó có kèm mã, biểu đồ hay phân tích hay không.
- Video lập chỉ mục các kênh YouTube liên quan để các bài nói và bài hướng dẫn có thể được tìm thấy bên cạnh tài liệu viết.
Việc lập chỉ mục lại trên môi trường production chạy qua một đường hầm SSH tới Meilisearch bị ràng vào localhost, bởi engine này không bao giờ được phơi bày ra internet. Toàn bộ vòng lặp — harvest, build, deploy, index — được thiết kế để có thể chạy lại một cách idempotent, và đó chính xác là điều cron hằng ngày thực hiện.
Truy cập và lưu trữ (hosting)

researcher chạy trên Server 1 của chúng tôi dưới dạng một ngăn xếp Docker Compose nhỏ: một container Next.js đứng sau Traefik và một container Meilisearch bị ràng vào localhost. Ứng dụng Next.js đọc các tập dữ liệu của nó ở phía máy chủ và giao tiếp với Meilisearch qua mạng nội bộ.
Truy cập được kiểm soát thông qua auth.marketmaker.cc, dịch vụ định danh dùng chung của chúng tôi. Token là các JWT RS256 được xác minh đối chiếu với JWKS của dịch vụ xác thực — mỗi quyết định cấp quyền đều kiểm tra chữ ký (với kiểm tra nghiêm ngặt về issuer và thuật toán, đóng chặt cửa nếu không thể tiếp cận endpoint khóa), còn con đường giải mã chưa xác minh chỉ được dùng cho phần giao diện mang tính trang trí như hiển thị email của bạn trên thanh điều hướng. Dịch vụ xác thực cấp các vai trò riêng cho từng dịch vụ; trên researcher, vai trò admin kiểm soát khu vực quản trị nội bộ (nơi chúng tôi chạy và giám sát các scraper), còn trang chủ công khai thì không cần token nào cả. Đó là cùng một kết cấu xác thực đứng trước các công cụ nội bộ khác của chúng tôi, nên một lần đăng nhập sẽ áp dụng xuyên suốt cả hệ sinh thái.
Nó nằm ở đâu trong ngăn xếp Marketmaker

researcher là hạ tầng, không phải là một điểm đến. Vấn đề không phải bản thân trang web — mà là bây giờ chúng tôi đã có một khung nhìn có thể truy vấn về lĩnh vực này mà cả con người lẫn agent đều chia sẻ.
Với chúng tôi với tư cách con người, đó là nơi rất nhiều phần của chính cái blog này bắt nguồn. Khi chúng tôi đánh giá một công cụ như VectorBT hay mổ xẻ một framework như TradingAgents hoặc Fincept Terminal, điểm khởi đầu thường là một lượt tìm kiếm trên researcher: bài này xây dựng dựa trên những bài báo nào, những repo nào khác giải quyết cùng vấn đề, ai đã viết về nó. Kho lưu trữ là phễu; còn các bài blog là những gì rơi ra từ đó.
Với các AI agent của chúng tôi, đó là thứ gì đó mang tính cấu trúc hơn. Một nền tảng nghiên cứu có thể tiếp cận qua MCP nghĩa là một agent đang làm việc với chiến lược không phải scrape arXiv trực tiếp, không phải tung hứng bốn API khác nhau, hay đoán mò xem ngoài kia có gì — nó gọi search_papers, search_repos, search_pine trên một kho dữ liệu vốn đã được hợp nhất, khử trùng lặp và lập chỉ mục sẵn. Đó cũng chính là hướng đi của bộ công cụ command-and-operate (cmdop) và agent của chúng tôi: trao cho agent những công cụ có kiểu (typed), chỉ-đọc, được tài liệu hóa tốt trên dữ liệu thật, báo lỗi một cách ồn ào khi backend không khả dụng, và để một backend dùng chung phục vụ cả giao diện con người lẫn giao diện máy từ những chỉ mục giống hệt nhau. Con người thì duyệt còn agent thì truy vấn — nhưng họ đang nhìn vào cùng một kho lưu trữ, và đó chính là toàn bộ ý tưởng.
Kết luận
researcher khởi đầu như một cách khắc phục một vấn đề nhỏ, khó chịu — rằng nghiên cứu quant nằm rải rác khắp bốn nơi mà không nói chuyện với nhau — rồi trở thành thứ chúng tôi dựa vào hằng ngày. Khoảng 51.000 tài liệu trải khắp bài báo, mã nguồn, bài viết và chiến lược, tất cả đều đứng sau một công cụ tìm kiếm toàn văn, tất cả đều có thể tiếp cận được cả bởi một con người với một trình duyệt lẫn bởi một agent với một client MCP. Nó cố tình không hào nhoáng: tìm kiếm toàn văn, không phải embedding; một nguồn chân lý duy nhất, không phải một bộ nhớ đệm khôn khéo; những công cụ ném ra lỗi trung thực, chứ không phải những công cụ che đậy sự cố.
Nếu bạn đang xây dựng agent cho nghiên cứu giao dịch, bài học này khái quát vượt ra khỏi kho dữ liệu cụ thể của chúng tôi: thứ có đòn bẩy cao nhất mà bạn có thể trao cho một agent không phải là một mô hình lớn hơn, mà là một khung nhìn sạch sẽ, hợp nhất, có thể truy vấn về dữ liệu nó cần — được phơi bày thông qua chính những chỉ mục mà con người tin tưởng. Đó chính là researcher.
Tác Giả
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.