TypeScript를 사용한 Robinhood 트레이딩 MCP 리스크 게이트웨이 구축하기
요약
본 글은 AI 에이전트가 외부 금융 서비스(Robinhood)와 상호작용할 때 발생하는 새로운 엔지니어링 문제를 다룹니다. 특히, LLM의 제안을 그대로 실행하는 것이 아니라, TypeScript를 사용하여 '리스크 게이트웨이'라는 결정론적 코드를 배치하여 트레이딩 의도를 검증하고 통제하는 아키텍처를 구축하는 방법을 설명합니다.
핵심 포인트
- AI 에이전트와 실제 거래 사이에 리스크 게이트웨이를 두는 것이 핵심입니다.
- 에이전트는 행동을 제안할 뿐, 최종 실행 권한은 결정론적 애플리케이션 코드가 가집니다.
- 게이트웨이는 최대 주문액 등 비즈니스 정책에 따라 요청을 거부하는 역할을 합니다.
- 읽기(Read)와 쓰기(Write) 기능을 분리하여 보안 경계를 명확히 하는 것이 중요합니다.
AI 에이전트는 이제 단순히 질문에 답하는 것 이상을 할 수 있습니다.
도구를 통해 외부 애플리케이션과 상호작용할 수 있게 된 것입니다.
Robinhood의 Trading MCP가 그 예시 중 하나입니다. 외부 AI 에이전트가 Robinhood에 연결하여 지원되는 계정, 포트폴리오, 시장 데이터(market-data), 관심 목록(watchlist), 주식(equities), 옵션(options), 암호화폐(crypto), 스캐너(scanner), 알림(alert) 및 주문 관련 도구를 사용할 수 있습니다. Robinhood는 또한 에이전트 기반 트레이딩을 위한 거래 승인 제어 기능도 제공합니다.
이는 새로운 엔지니어링 문제를 만듭니다.
어려운 부분은 다음 자체가 아닙니다:
AI
↓
주문하기(Place Order)
어려운 부분은 다음과 같습니다:
AI 에이전트 (AI Agent)
↓
트레이딩 MCP (Trading MCP)
...
이 프로젝트를 위해, 저는 TypeScript를 사용하여 재사용 가능한 Robinhood 트레이딩 MCP 리스크 게이트웨이를 구축하고 있습니다.
목표는 AI 에이전트와 트레이딩 실행 사이에 결정론적(deterministic) 제어 방식을 어떻게 배치할 수 있는지 시연하는 것입니다.
이것은 LLM 주가 예측 시스템이 아닙니다.
이는 실행 제어 시스템입니다.
에이전트와 브로커 사이에 리스크 게이트웨이를 두는 이유?
AI 모델은 지침을 해석하고 도구를 선택할 수 있습니다.
하지만 트레이딩 한도에 대한 최종 권한자가 자동으로 되어서는 안 됩니다.
다음 상황을 고려해 보세요:
사용자:
"이 주식 $5,000어치 사줘."
에이전트는 다음과 같이 생성할 수 있습니다:
symbol = XYZ
side = buy
amount = $5,000
하지만 애플리케이션의 리스크 정책은 다음과 같다고 가정해 봅시다:
최대 주문액(maximum order) = $1,000
게이트웨이는 이 요청을 거부해야 합니다.
AI 에이전트 (AI Agent)
↓
거래 의도 (Trade Intent)
...
이것이 핵심 설계 원칙입니다:
에이전트는 행동을 제안할 수 있지만, 결정론적 애플리케이션 코드가 그 행동이 허용되는지 여부를 결정합니다.
아키텍처
초기 아키텍처는 다음과 같습니다:
+----------------------+
| AI 에이전트 (AI Agent) |
+----------+-----------+
...
게이트웨이가 중간에 위치합니다.
이는 실행 경계(execution boundary)를 명확하게 만듭니다.
Robinhood의 외부 에이전트 모델
Robinhood의 현재 Trading MCP 문서는 MCP를 외부 AI 에이전트를 Robinhood에 연결하는 메커니즘으로 설명하며, 이를 통해 에이전트는 정보를 접근하고 지원되는 액션을 수행할 수 있습니다. Robinhood는 Claude, ChatGPT, Codex, Cursor, Grok, Perplexity, OpenClaw, Replit 등 다양한 플랫폼을 포함한 연결 방식을 문서화하고 있습니다.
외부 에이전트는 트레이딩을 위해 전용 MCP 계정을 사용합니다.
에이전트는 사용자 Robinhood 계정에서 정보를 읽어올 수 있으며, 전용 Agentic/MCP 계정을 통해 트레이딩을 수행할 수 있습니다.
이러한 구분은 통합을 중심으로 애플리케이션을 설계할 때 중요합니다.
외부 에이전트 모델 (External Agent Model)
읽기 도구 대 쓰기 도구 (Read tools versus write tools)
제가 원하는 첫 번째 권한 경계는 읽기와 상태 변경의 분리입니다.
개념적으로:
Read
----
get_accounts
...
대하여:
Write
-----
place order
...
Robinhood의 현재 도구 문서는 계정(account), 포트폴리오(portfolio), 시장 데이터(market-data), 트레이딩(trading) 및 고급 주문(advanced-order) 기능에 대한 별도의 범주를 노출합니다.
이를 통해 애플리케이션이 권한을 독립적으로 정의할 수 있습니다.
연구 에이전트는 읽기 권한만 받을 수 있습니다.
포트폴리오 어시스턴트는 읽기 및 제안(proposal) 권한을 받을 수 있습니다.
트레이딩 에이전트는 추가 정책 확인 후에야 실행(execution) 권한을 받을 수 있습니다.
프로젝트 구조 (The project structure)
저는 리포지토리가 간단하게 유지되기를 바랍니다:
robinhood-mcp-risk-gateway/
├── src/
│ ├── agent/
...
핵심 비즈니스 규칙은 AI 모델에 직접 결합되어서는 안 됩니다.
구조화된 거래 의도 (Structured trade intent)
저는 애플리케이션의 나머지 부분이 자유 형식(free-form) 모델 출력을 소비하는 것을 원하지 않습니다.
대신, 이를 타입이 지정된 의도(typed intent)로 변환합니다.
예를 들어:
type TradeIntent = {
symbol: string;
side:
자유 형식(Free-form):
"XYZ의 모멘텀이 강해 보여서 의미 있는 양을 매수해야겠어."
구조화된(Structured):
{
symbol: "XYZ",
side: "buy",
...
두 번째 방식은 검증하기가 쉽습니다.
예를 들어:
최대 주문 금액 = $1,000
요청 금액 = $500
결과 = 허용(ALLOWED)
하지만:
최대 주문 금액 = $1,000
요청 금액 = $5,000
결과 = 거부(REJECTED)
이것은 테스트하기가 훨씬 쉽습니다.
## 리스크 정책 (Risk policy)
리스크 정책은 결정론적(deterministic)이어야 합니다.
기본 모델:
type RiskPolicy = {
maxOrderNotional: number;
maxPositionNotional: number;
...
리스크 엔진은 다음을 평가할 수 있습니다:
주문 규모 (order size)
포지션 규모 (position size)
포트폴리오 노출도 (portfolio exposure)
...
정확한 규칙은 나중에 확장될 수 있습니다.
## 리스크 평가 (Risk evaluation)
핵심 API는 작을 수 있습니다:
type RiskDecision =
|
{
allowed: true;
...
그리고 다음 함수를 정의할 수 있습니다:
function evaluateTrade(
trade: TradeIntent,
policy: RiskPolicy,
...)
출력은 거래가 승인되거나 거부된 이유를 설명해야 합니다.
## 예시 리스크 검사 (Example risk checks)
거래는 다음과 같은 과정을 거칠 수 있습니다:
트레이드 인텐트 (Trade Intent)
|
+--> 심볼 허용 여부? (symbol allowed?)
...
필요한 모든 검사를 통과했을 때만 거래가 계속됩니다.
모든 검사 통과
↓
실행 허용 (Execution allowed)
## 심볼 화이트리스트 (Symbol allowlists)
간단한 제어 방법 중 하나는 에이전트가 거래할 수 있는 자산을 제한하는 것입니다.
예를 들어:
const allowedSymbols = new Set([
"AAPL",
"NVDA",
...
그리고 다음 로직을 사용할 수 있습니다:
if (!policy.allowedSymbols.has(trade.symbol)) {
return {
allowed: false,
...
이것은 의도적으로 지루합니다.
리스크 제어는 지루해야 합니다.
## 포지션 제한 (Position limits)
만약 애플리케이션에 다음과 같은 것이 있다고 가정해 봅시다:
최대 포지션 = $2,500
그리고 현재 포지션이 다음과 같다고 합시다:
$2,200
새로운 주문인:
$500
은 노출도를 설정된 한도 이상으로 증가시키기 때문에 실패해야 합니다.
현재 포지션 (Current position)
$2,200
+
...
중요한 부분은 결정이 결정론적이라는 것입니다.
## 포트폴리오 노출도 (Portfolio exposure)
게이트웨이는 또한 포트폴리오 수준의 제약 조건을 강제할 수 있습니다.
예를 들어:
기술 섹터 노출도 (Technology exposure): 48%
최대치 (Maximum): 40%
에이전트는 여전히 다른 기술 거래를 제안할 수 있습니다.
위험 엔진(risk engine)이 이를 거부합니다.
이는 포트폴리오 인지 실행 경계(portfolio-aware execution boundary)를 만듭니다:
Agent
↓
Trade
...
## 일일 손실 통제 (Daily loss controls)
또 다른 유용한 안전장치는 일일 손실 한도입니다.
예를 들어:
maximum daily loss = $500
애플리케이션이 임계값에 이미 도달했음을 감지하면:
Trade Intent
↓
Risk Engine
...
에이전트는 정책을 무시할 수 없습니다.
## 도구 권한 (Tool permissions)
위험(risk)은 한 가지 계층일 뿐입니다.
에이전트는 또한 권한이 필요합니다.
저는 다음과 같은 기능을 모델링합니다:
READ_ACCOUNT
READ_PORTFOLIO
READ_MARKET_DATA
...
그런 다음 어떤 에이전트가 어떤 기능을 가지고 있는지 정의합니다.
예를 들어:
type AgentPermissions = {
readAccount: boolean;
readPortfolio: boolean;
...}
리서치 에이전트는 다음과 같을 수 있습니다:
readAccount = true
readPortfolio = true
readMarketData = true
...
실행(execution) 에이전트에게는 명시적으로 필요한 쓰기 권한(write permissions)의 더 좁은 세트를 부여할 수 있습니다.
## 사용자 승인 (Human approval)
Robinhood는 현재 에이전트 기반 거래를 위한 거래 승인을 지원합니다. 승인이 활성화된 경우, 에이전트는 거래를 제안할 수 있지만 사용자가 Robinhood에서 검토하고 수동으로 체결해야 합니다. 승인이 비활성화된 경우, 적격한 거래는 각 주문에 대한 수동 확인 없이 에이전트에 의해 체결될 수 있습니다. Robinhood는 특정 거래의 경우 여전히 승인이 필요할 수 있다고 언급합니다.
이는 두 가지 애플리케이션 모드와 자연스럽게 매핑됩니다.
### 지원 모드 (Assisted mode)
Agent
↓
Trade Intent
...
### 자동화 모드 (Automated mode)
Agent
↓
Trade Intent
...
저는 지원 모드를 먼저 구축할 것입니다.
## 승인 상태 (Approval state)
애플리케이션은 승인을 명시적으로 나타낼 수 있습니다:
type ApprovalStatus =
| "NOT_REQUIRED"
| "PENDING"
...
거래 생명주기(trade lifecycle)는 다음과 같아집니다:
PROPOSED
↓
VALIDATED
...
또는:
RISK_CHECKED
↓
REJECTED
이것은 실행 경로를 감사(audit)하기 쉽게 만듭니다.
## 주문 상태 (Order state)
주문이 제출되면, 애플리케이션은 여전히 이를 추적해야 합니다.
유용한 상태 기계(state machine):
id="robinhood-order-state"
PROPOSED
↓
...
Failure paths:
SUBMITTED
├── REJECTED
├── CANCELED
...
중요한 구별점은 다음과 같습니다:
order submitted
대 비(versus):
order filled
후자는 실제 실행 상태를 요구합니다.
## 감사 로깅 (Audit logging)
의미 있는 모든 에이전트 액션은 감사 기록을 생성해야 합니다.
예를 들어:
type AuditRecord = {
id: string;
timestamp: string;
...
목표는 다음 질문에 답하는 것입니다:
> 이 주문이 왜 발생했나요?
감사 추적(audit trail)은 우리가 다음을 재구성할 수 있도록 해야 합니다:
User Request
↓
Agent Decision
...
## AI는 게이트웨이를 우회해서는 안 됩니다 (The AI should not bypass the gateway)
아키텍처는 하나의 실행 경로를 강제해야 합니다.
다음과 같지 않게:
Agent
├── MCP
├── Direct API
...
대신:
Agent
↓
Trading MCP
...
거래 액션으로 가는 명확하게 정의된 경로가 하나 있어야 합니다.
이렇게 하면 권한 관리, 로깅, 테스트 및 모니터링이 훨씬 쉬워집니다.
## 시장 데이터 및 계정 데이터 (Market data and account data)
Robinhood의 현재 에이전트 도구에는 계정(account), 포트폴리오(portfolio), 관심 종목(watchlist), 시장 데이터(market-data), 주식(equities), 옵션(options), 암호화폐(crypto), 스캐너(scanner), 알림(alerts) 및 고급 주문 기능이 포함됩니다.
이는 에이전트가 여러 출처로부터 잠재적으로 결정을 구축할 수 있음을 의미합니다:
Portfolio
+
Market Data
...
중요한 엔지니어링 경계는 여전히 다음과 같습니다:
Data
↓
Analysis
...
## 포트폴리오 인식 실행 (Portfolio-aware execution)
단순한 트레이딩 봇은 다음만 알 수도 있습니다:
symbol
price
signal
포트폴리오 인식이 가능한 에이전트는 다음을 통합할 수 있습니다:
current positions
buying power
existing exposure
...
예를 들어:
Agent:
"XYZ 주식 500달러어치 매수하세요."
...
에이전트가 한도를 기억하도록 신뢰할 필요는 없습니다. 정책 엔진이 이를 강제합니다.
## 예시 워크플로우 (Example workflow)
다음은 시작부터 끝까지의 예시입니다:
User
|
|
"모멘텀 기회를 찾으세요...
이것이 제가 GitHub 프로젝트에서 시연하고 싶은 아키텍처입니다.
## TypeScript 서비스 경계 (TypeScript service boundary)
핵심 서비스는 하나의 주요 작업을 가질 수 있습니다:
async function processTradeIntent(intent: TradeIntent): Promise<TradeDecision> {...
가장 중요한 부분은 AI 모델이 브로커에게 직접 호출하지 않는다는 것입니다.
대신, 애플리케이션의 통제된 파이프라인에 타입이 지정된 요청을 제출합니다.
## 리스크 규칙 테스트 (Testing risk rules)
리스크 로직은 높은 수준으로 테스트 가능해야 합니다.
예를 들어:
max order = $1,000
$500 → 허용됨(allowed)
...
포지션 제한:
current = $2,000
limit = $2,500
...
심볼 제한:
AAPL → 허용됨(allowed)
XYZ → 거부됨(rejected)
일일 손실액:
daily loss = $490
limit = $500
...
테스트는 경계 값(boundary values)을 포함해야 합니다.
## 권한 테스트 (Testing permissions)
권한 테스트는 에이전트가 가지고 있지 않은 도구를 사용할 수 없음을 검증해야 합니다.
예를 들어:
Research Agent
↓
get_portfolio
...
하지만:
Research Agent
↓
place_order
...
이는 권한 계층에 간단하고 결정론적인 계약(deterministic contract)을 제공합니다.
## 승인 흐름 테스트 (Testing approval flows)
두 가지 모드를 모두 테스트해야 합니다.
지원됨(Assisted):
Trade
↓
Risk passes
...
거부됨(Rejected):
Trade
↓
Risk passes
...
자동화됨(Automated):
Trade
↓
Risk passes
...
이러한 테스트는 실제 AI 모델과 독립적이어야 합니다.
## 실패 경로 테스트 (Testing failure paths)
시스템은 다음 상황도 처리해야 합니다:
MCP unavailable
tool timeout
invalid tool result
...
목표는 AI가 완벽해 보이게 만드는 것이 아닙니다.
목표는 AI 또는 외부 인프라가 불완전할 때 주변 시스템이 예측 가능하게 동작하도록 만드는 것입니다.
## 시크릿 및 계정 보안 (Secrets and account security)
게이트웨이는 비밀(secrets)을 무심코 저장하는 장소가 되어서는 안 됩니다.
다음은 하지 마세요:
log credentials
commit .env files
store private secrets in audit records
...
환경 변수나 적절한 비밀 관리 계층(secret-management layer)을 사용하세요.
리포지토리는 다음을 포함해야 합니다:
.env.example
하지만 실제 자격 증명(actual credentials)은 절대 포함해서는 안 됩니다.
## 이 아키텍처가 재사용 가능한 이유 (Why this architecture is reusable)
리스크 게이트웨이는 하나의 전략에 국한될 필요가 없습니다.
다음 아래에 위치할 수 있습니다:
Momentum Agent
↓
Risk Gateway
...
에이전트만 변경됩니다.
제어 장치는 그대로 유지됩니다.
## 더 큰 아키텍처
이 프로젝트는 더 큰 트레이딩 시스템의 일부입니다:
사용자 (USER)
|
v
...
게이트웨이는 제어 지점(control point)입니다.
## 이것이 전통적인 트레이딩 봇과 다른 이유
전통적인 봇은 종종 다음과 같은 형태를 보입니다:
시그널 (Signal)
↓
주문 (Order)
제어된 에이전트 기반(agentic) 트레이딩 시스템은 다음과 같은 형태에 더 가깝습니다:
사용자 의도 (User Intent)
↓
AI 에이전트 (AI Agent)
...
## 이것은 근본적으로 다른 엔지니어링 문제입니다.
## 고객에게 증명할 수 있는 것
가치 있는 진술은 다음과 같지 않습니다:
> "저는 ChatGPT를 Robinhood에 연결할 수 있습니다."
더 강력한 진술은 다음과 같습니다:
> **"저는 AI 트레이딩 에이전트를 둘러싼 제어된 인프라를 구축할 수 있습니다."**
여기에는 다음이 포함됩니다:
AI 통합 (AI integration)
+
MCP
...
이는 실제 트레이딩 제품이 요구하는 것과 훨씬 가깝습니다.
## 최종 아키텍처
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기