자율형 AI 헤지펀드를 진지하게 구축하기: 3.6ns의 Rust 감시견과 Reddit 퀀트 지식 역수입
개인 개발을 통해 오픈 소스 AI 헤지펀드 엔진 "Lumina Capital" 을 개발하고 운영하고 있습니다.
이전 버전에서는 LangGraph를 활용한 멀티 에이전트 합의, Microsoft Qlib의 Alpha158 수리 퀀트, Google TimesFM의 기저 시계열 모델을 통합한 시스템을 공개하여, 세계 최대 규모의 퀀트 리소스 집합소 awesome-quant (⭐️ 15,000+) 에 정식 채택되었습니다.
하지만 실제 실시간 페이퍼 트레이딩 환경에서 24시간 동안 구동해 보니, "시뮬레이션에서는 보이지 않았던 실전 운영의 벽" 에 연이어 부딪히게 되었습니다.
Python의 GIL과 동적 타입으로 인한 레이턴시 한계: 급락 발생 시 스탑로스 판정이 수 밀리초(ms) 지연되어 슬리피지(slippage)가 확대됩니다. -
LLM 에이전트의 '팩터 해석 환각': 원본 수치를 전달했을 때, "PER이 높으니 저평가" 또는 "변동성 급증을 매수 근거"와 같이 역방향으로 해석합니다. -
에이전트 반성 기억의 무거운 부하: 과거 트레이드 실패담을 인출하기 위해 외부 벡터 DB(Embedding API)를 호출하면 느리고(수백 ms), 불안정하며, 비용이 발생합니다. -
비동기 데이터의 상관관계 왜곡 (Epps 효과): 미일 시장 간 또는 호가 업데이트 지연으로 인해 단순한 상관계수가 0에 수렴합니다.
이러한 문제들을 해결하기 위해 해외 퀀트 커뮤니티(Reddit의 r/algotrading나 r/LocalLLaMA 등)의 논의를 철저히 리서치하여, 이번에 v6.4.5 로 시스템 전체를 재설계했습니다.
본고에서는 그 설계 사상과 구체적인 구현 내용, 그리고 실측 벤치마크 결과를 공개합니다.
1. 🦀 왜 Rust인가? 왜 SoA(Structure of Arrays)인가?
Python의 객체 참조와 캐시 미스 문제
Python은 표현력이 풍부하고 라이브러리도 충실하지만, 대량의 포지션이나 호가 정보를 다루며 고속으로 모니터링하는 용도로는 적합하지 않습니다.
Python의 Position 객체를 리스트로 유지하며 반복문을 돌리면, 데이터는 메모리에 흩어진 포인터(힙 영역)에 존재하게 됩니다. CPU는 다음 요소를 찾을 때마다 L1/L2 데이터 캐시 미스를 일으키고, 메인 메모리(DRAM)까지 데이터를 가져와야 합니다.
급락 시 수십~수백 개의 종목에서 동시에 스탑로스나 트레일링 조건을 평가해야 하는 국면에서, 이 캐시 미스와 GIL에 의한 오버헤드는 치명적입니다.
Struct-of-Arrays (SoA)를 통한 제로 코스트 반복(Zero Cost Iteration)
이에 안전장치 역할을 하는 모니터링 엔진 lumina-sentinel-rs를 Rust로 처음부터 구축했습니다.
메모리 레이아웃에는 객체 지향적인 **AoS (Array of Structures)**가 아닌, 데이터 중심 설계인 **SoA (Structure of Arrays)**를 채택했습니다.
// AoS: 메모리에 각 필드가 섞여서 나열됨
// [ID, Price, Stop, High, ...] [ID, Price, Stop, High, ...]
// SoA: 동일한 종류의 연속 슬라이스가 평평하게 메모리에 나열됨
...
SoA 구조를 사용함으로써 CPU는 current_prices나 stop_loss_prices와 같은 64비트 부동소수점(f64) 배열을 프리페치(prefetch)하여 연속 캐시 라인에 로드할 수 있습니다. 컴파일러에 의한 SIMD(자동 벡터화) 효과도 보기 쉽습니다.
#![forbid(unsafe_code)]
메모리 안전성: 금융 시스템에서 충돌은 허용되지 않습니다. unsafe 코드를 코드 베이스 전체에서 엄격하게 금지했습니다.
실측 벤치마크 (Linux 운영 환경)
Linux VPS (Ubuntu)에서 실시한 --bench의 실측 값입니다:
1 포지션 판정 속도: 3.63 나노초(ns)
1,000 포지션 일괄 평가: 3.62 마이크로초(µs)
1,000 바의 Volume Clock 흐름 변환: 3.90 마이크로초(µs)
Python ⇄ Rust 상시 파이프 IPC(왕복 RTT): 2.88 마이크로초
Python 메인 루프에서는 표준 입출력(stdin/stdout)의 바이너리 파이프로 상시 연결되어 있으며, 프로세스 간 통신을 포함하더라도 안전장치(safety valve)가 2.88 마이크로초 만에 작동합니다.
2. 🧠 LLM의 환각(Hallucination)을 '타입 시스템'으로 물리적으로 방지하기
LLM은 수치의 '극성(Polarity)'을 쉽게 조작한다
'LLM에게 퀀트 분석을 시키는' 과정에서 가장 빈번하게 발생하는 사고가 지표 해석의 역전입니다.
프롬프트에 {"PER": 75.0, "ROE": 0.02, "Alpha158_Composite": -1.8}
와 같이만 전달해도, LLM은 문맥에 흘러가 다음과 같은, 퀀트의 기본 원칙을 무시한 환각(Hallucination)을 아무렇지 않게 출력합니다.
"PER가 높은 수준이며, 성장 기대가 시장에서 높게 평가되고 있으므로 Strong Buy입니다!"
LLM에게는 '큰 수치 = 좋은 것'이라는 언어적 편향이 작용하기 쉽기 때문입니다.
Factor Ontology Schema를 이용한 온톨로지 계약
이를 방지하기 위해, 모든 수리적 팩터에 대해 '단위', '적정 범위(Range)', '해석 극성(Polarity)', '기계적 시그널'을 정의한 팩터 온톨로지 사전(factor_ontology.py)을 도입했습니다.
class InterpretationPolarity(str, Enum):
HIGHER_IS_BULLISH = "HIGHER_IS_BULLISH" # 높을수록 강세 (예: ROE, OFI)
LOWER_IS_BULLISH = "LOWER_IS_BULLISH" # 낮을수록 강세 (예: PER, 부채 비율)
...
에이전트에게 데이터를 전달할 때, 원시 수치뿐만 아니라 온톨로지 사전이 기계적으로 판별한 결과까지 강제 삽입합니다:
{
"metric": "PER",
"raw_value": 75.0,
...
LLM에게 '이 수치의 의미를 스스로 해석하지 말고, 확정 시그널과 극성 규칙에 따라 논리를 구성하라'는 계약(Contract)을 걸어줌으로써, 해석의 역전 환각을 물리적으로 근절했습니다.
3. ⚡ 벡터 DB를 버리고 0.3ms를 구현한 SQLite FTS5 (BM25)
LLM의 '반성 기억(Reflexion)'에 임베딩(Embedding)이 정말 필요한가?
에이전트가 과거의 손실 트레이드나 판단 실수로부터 배우는 '자기 반성(Reflexion)' 아키텍처에서는 Chroma나 Pinecone 같은 Vector DB를 사용하는 것이 일반적입니다.
하지만 실제로 운영해 보니 큰 문제가 있었습니다:
- 외부 API 레이턴시:
text-embedding-3-small과 같은 API를 호출하는 것만으로 매번 200ms~500ms를 기다려야 합니다.
- 키워드 검색이 더 정확하다: '반도체', '결산 발표 전', '트레일링 세일(Trailing Sale)', '거래량 급증'과 같은 구체적인 시장 컨텍스트는 고차원 벡터의 코사인 유사도(모호한 의미적 근접성)보다, **어휘 일치(BM25)**가 과거 유사 사례를 훨씬 정확하게 끌어낼 수 있습니다.
SQLite 표준 FTS5 (BM25)만으로 충분하다, 아니 최고다
이에 따라, SQLite에 내장된 전문 검색 엔진인 FTS5를 사용하여 내부 DB 상에서 밀리초 미만의 기억 검색을 구현했습니다.
CREATE VIRTUAL TABLE IF NOT EXISTS agent_reflexions_fts USING fts5(
reflection_id UNINDEXED,
symbol,
...
합의 직전에 현재 종목 코드나 시장 레짐(Regime)을 던져 BM25로 랭킹 검색을 수행합니다:
cursor.execute("""
SELECT r.symbol, r.agent_name, r.lesson, bm25(agent_reflexions_fts) as rank
FROM agent_reflexions_fts f
...
"""
- 검색 속도: (외부 API 대비 1,000배 빠름)
0.3ms ~ 0.5ms
- 추가 비용: 0원
- 외부 의존성: 제로(SQLite 표준 기능만 사용)
4. 📈 Hayashi-Yoshida (2005) 비동기 공분산 추정
고빈도/다중 시장에서의 'Epps 효과'의 장벽
미국과 일본 시장(도쿄 증권거래소의 Nikkei 선물 및 시카고/NY의 S&P 500)의 연동성을 분석할 때, 타임스탬프가 완전히 일치하는 경우는 없습니다.
기존의 퀀트 모델에서는 '1분봉'과 같은 등간격 바(bar)로 리샘플링하여 Pearson 상관관계를 계산하는 경우가 많지만, 샘플링 간격을 세밀하게 할수록 **겉보기 상관관계가 0에 가까워지는 현상(Epps 효과)**이 알려져 있습니다.
관측 인터벌의 중첩을 적분하기
Hayashi-Yoshida(林高樹)와 Yoshida Tomohiro(吉田朋広)가 2005년에 발표한 'Hayashi-Yoshida 추정량(Estimator)'은 리샘플링을 수행하지 않고, 거래 시간 구간이 중첩되는(Overlap하는) 관측치들 간의 수익률 곱을 누적 가산합니다.
def compute_hayashi_yoshida_corr(times_x, prices_x, times_y, prices_y) -> float:
# 관측 시간 인터벌의 중첩을 감지하고 비동기 수익률의 공분산을 산출
...
이를 통해 체결 시점이 제각각인 개별 주식이나 해외 시장 사이에서도 Epps 효과로 인한 상관관계 감소를 받지 않으며, **'진정한 상관관계'**와 **'선행 지연 관계(어느 쪽이 앞서 나가는가)'**를 정확하게 추출할 수 있게 되었습니다.
5. 요약
AI 에이전트를 금융이나 실제 사회의 실시간 시스템에 통합하는 과정에서 절감한 교훈은 **'LLM에게 모든 것을 맡겨서는 안 된다'**는 것입니다.
초저지연 및 확실한 안전장치: 정적 타입 언어(Rust)와 데이터 지향 설계(SoA)에 의존한다 -
환각 방지: 프롬프트의 기교뿐만 아니라, 온톨로지 사전(Ontology Dictionary, 타입 안전 스키마)으로 물리적으로 제약한다 -
기억 및 검색: 쉽게 거대한 Vector DB에 의존하기보다, 검증된 고속 기술(SQLite FTS5)을 적재적소에 사용한다 -
수리적 엄밀성: 현실의 데이터 특성(비동기 샘플링)에 맞는 계량 모델(Hayashi-Yoshida)을 사용한다
본 프로젝트의 코드는 모두 GitHub에서 오픈 소스로 공개하고 있습니다.
🔗 GitHub Repository:
Docker 1줄로 로컬 환경에서 구동할 수 있으니, 아키텍처에 관심 있는 분들은 꼭 살펴보시기 바랍니다. ⭐️ Star와 피드백도 기다립니다!
Discussion