Equity Orders — /api/equity/{orders,positions,account}
페이퍼/라이브 브로커로 주문을 라우팅하고, 포지션 + 계좌 스냅샷을 조회하는 라우트입니다.
공통
- 베이스 경로:
/api/equity - Feature flag:
- 페이퍼 모드 (
live_mode=false):FEATURE_EQUITY_PAPERON - 라이브 모드 (
live_mode=true):FEATURE_EQUITY_LIVEON + 사용자별live_enabledON
- 페이퍼 모드 (
- Rate limit:
POST /orders사용자별 분당 30회 - 멀티유저 격리:
BotRegistry.get_broker_for_user(user_id, asset_class)+broker_order_eventsaudit log 교차 검증
POST /api/equity/orders
신규 주문 제출. 5단계 게이트:
- Feature flag (paper 또는 live)
- Rate limit
- 사용자 브로커 등록 확인 (없으면 403 BROKER_NOT_CONFIGURED)
- RiskManager v2 (1-2% / -5% / 시장 시간)
- 브로커 호출
요청 (PlaceOrderRequest)
| 필드 | 타입 | 필수 | 기본 | 설명 |
|---|---|---|---|---|
asset_class | crypto|us_equity|kr_equity | Y | — | 자산군 |
symbol | string (1–32) | Y | — | 브로커 native 심볼 |
side | buy|sell | Y | — | |
qty | float (>0) | Y | — | 주식 단위 (소수 가능) |
order_type | market|limit|stop|stop_limit | Y | — | |
limit_price | float (>0) | 조건부 | — | limit/stop_limit 시 필수 |
stop_price | float (>0) | 조건부 | — | stop/stop_limit 시 필수 |
bracket | BracketSpec | N | null | TP/SL 동시 설정 |
client_order_id | string (≤64) | N | UUID 자동 | 멱등성 키 |
time_in_force | day|gtc|ioc|fok | N | day | |
report_uuid | UUID | N | null | 출처 보고서 (감사) |
live_mode | bool | N | false | true 시 라이브 라우팅 |
BracketSpec:
{
"take_profit": 205.00,
"stop_loss": 179.00,
"stop_loss_limit": null
}
응답 201 (OrderResponse)
{
"order_id": "user-uuid-xxx",
"broker_order_id": "alp_8e2...",
"client_order_id": "user-uuid-xxx",
"asset_class": "us_equity",
"exchange": "alpaca",
"symbol": "AAPL",
"side": "buy",
"qty": 5.0,
"filled_qty": 0.0,
"avg_fill_price": null,
"order_type": "limit",
"limit_price": 188.20,
"stop_price": null,
"status": "accepted",
"currency": "USD",
"submitted_at": "2026-04-26T13:35:01Z",
"updated_at": "2026-04-26T13:35:01Z",
"bracket": { "take_profit": 205.00, "stop_loss": 179.00 },
"metadata": { "report_uuid": "0c3a5b..." }
}
에러
- 403 —
BROKER_NOT_CONFIGURED,LIVE_NOT_ENABLED - 422 —
RISK_RULE_VIOLATION,MARKET_CLOSED,INSUFFICIENT_FUNDS,BROKER_REJECTED - 429 —
RATE_LIMIT_EXCEEDED,RATE_LIMIT_LIVE_DAILY - 502 —
BROKER_UNAVAILABLE - 503 —
FEATURE_DISABLED
cURL
curl -X POST http://localhost:8000/api/equity/orders \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"asset_class": "us_equity",
"symbol": "AAPL",
"side": "buy",
"qty": 5,
"order_type": "limit",
"limit_price": 188.20,
"time_in_force": "day",
"bracket": {"take_profit": 205.00, "stop_loss": 179.00}
}'
TypeScript
const order = await api.post<OrderResponse>('/api/equity/orders', {
asset_class: 'us_equity',
symbol: 'AAPL',
side: 'buy',
qty: 5,
order_type: 'limit',
limit_price: 188.20,
time_in_force: 'day',
bracket: { take_profit: 205.00, stop_loss: 179.00 },
});
DELETE /api/equity/orders/{broker_order_id}
오픈 주문 취소.
Query 파라미터
| 파라미터 | 필수 | 설명 |
|---|---|---|
asset_class | Y | 브로커 dispatch용 |
symbol | N | 일부 브로커가 요구 (예: KIS) |
응답 204
본문 없음.
에러
- 404 —
ORDER_NOT_FOUND(다른 user 주문 또는 미존재) - 502 —
BROKER_UNAVAILABLE
cURL
curl -X DELETE -H "Authorization: Bearer $JWT" \
"http://localhost:8000/api/equity/orders/alp_8e2...?asset_class=us_equity&symbol=AAPL"
GET /api/equity/orders/{broker_order_id}
단일 주문 스냅샷.
Query 파라미터
| 파라미터 | 필수 |
|---|---|
asset_class | Y |
symbol | N |
응답 200 (OrderResponse)
cURL
curl -H "Authorization: Bearer $JWT" \
"http://localhost:8000/api/equity/orders/alp_8e2...?asset_class=us_equity"
GET /api/equity/orders
호출자의 주문 목록 (브로커 native 데이터 + audit log 교차 필터).
Query 파라미터
| 파라미터 | 기본 | 설명 |
|---|---|---|
asset_class | required | 자산군 |
status | — | pending/open/filled/canceled/rejected |
page | 1 | 1-based |
size | 20 | 1–100 |
응답 200 (OrderListResponse)
{
"items": [{...OrderResponse...}],
"total": 12,
"page": 1,
"size": 20
}
cURL
curl -H "Authorization: Bearer $JWT" \
"http://localhost:8000/api/equity/orders?asset_class=us_equity&status=open"
GET /api/equity/positions
자산군별 오픈 포지션.
Query 파라미터
| 파라미터 | 필수 |
|---|---|
asset_class | Y |
응답 200 (PositionListResponse)
{
"items": [
{
"asset_class": "us_equity",
"exchange": "alpaca",
"symbol": "AAPL",
"qty": 5.0,
"avg_entry_price": 188.20,
"current_price": 191.10,
"unrealized_pnl": 14.50,
"unrealized_pnl_pct": 0.0154,
"currency": "USD",
"side": "LONG",
"metadata": {}
}
]
}
cURL
curl -H "Authorization: Bearer $JWT" \
"http://localhost:8000/api/equity/positions?asset_class=us_equity"
GET /api/equity/account
브로커 계좌 스냅샷 (자산군당 1).
Query 파라미터
| 파라미터 | 필수 |
|---|---|
asset_class | Y |
응답 200 (AccountResponse)
{
"asset_class": "us_equity",
"broker": "alpaca",
"paper": true,
"equity": 10342.10,
"buying_power": 18400.00,
"cash": 9512.30,
"currency": "USD",
"as_of": "2026-04-26T13:55:00Z",
"metadata": {}
}
cURL
curl -H "Authorization: Bearer $JWT" \
"http://localhost:8000/api/equity/account?asset_class=us_equity"
멀티유저 격리
BotRegistry.get_broker_for_user(user_id, asset_class)로 사용자별 브로커 인스턴스 조회- 사용자가 등록하지 않은 자산군 브로커 호출 시 403
BROKER_NOT_CONFIGURED GET /orders//orders/{id}응답은broker_order_eventsaudit log와 교차 검증되어 다른 user 주문 누출 차단- 다른 user의 broker_order_id로 조회/취소 시 404
Audit log
모든 placement / cancel / reject 이벤트는 broker_order_events 테이블에 영속화됩니다 (P3-04).
비고
client_order_id를 명시하지 않으면 서버가 UUID4 hex로 자동 채움 (멱등성 보장)stop_limit은 broker 레이어에서stop으로 collapse +stop_limit_pricemetadata로 전달- 라이브 첫 주문에는
first_live_order=true마커가 audit log에 기록되어 모니터링 알림이 발송됨