본문으로 건너뛰기

Equity Orders — /api/equity/{orders,positions,account}

페이퍼/라이브 브로커로 주문을 라우팅하고, 포지션 + 계좌 스냅샷을 조회하는 라우트입니다.

공통

  • 베이스 경로: /api/equity
  • Feature flag:
    • 페이퍼 모드 (live_mode=false): FEATURE_EQUITY_PAPER ON
    • 라이브 모드 (live_mode=true): FEATURE_EQUITY_LIVE ON + 사용자별 live_enabled ON
  • Rate limit: POST /orders 사용자별 분당 30회
  • 멀티유저 격리: BotRegistry.get_broker_for_user(user_id, asset_class) + broker_order_events audit log 교차 검증

POST /api/equity/orders

신규 주문 제출. 5단계 게이트:

  1. Feature flag (paper 또는 live)
  2. Rate limit
  3. 사용자 브로커 등록 확인 (없으면 403 BROKER_NOT_CONFIGURED)
  4. RiskManager v2 (1-2% / -5% / 시장 시간)
  5. 브로커 호출

요청 (PlaceOrderRequest)

필드타입필수기본설명
asset_classcrypto|us_equity|kr_equityY자산군
symbolstring (1–32)Y브로커 native 심볼
sidebuy|sellY
qtyfloat (>0)Y주식 단위 (소수 가능)
order_typemarket|limit|stop|stop_limitY
limit_pricefloat (>0)조건부limit/stop_limit 시 필수
stop_pricefloat (>0)조건부stop/stop_limit 시 필수
bracketBracketSpecNnullTP/SL 동시 설정
client_order_idstring (≤64)NUUID 자동멱등성 키
time_in_forceday|gtc|ioc|fokNday
report_uuidUUIDNnull출처 보고서 (감사)
live_modeboolNfalsetrue 시 라이브 라우팅

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_classY브로커 dispatch용
symbolN일부 브로커가 요구 (예: 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_classY
symbolN

응답 200 (OrderResponse)

POST 응답과 동일 형식.

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_classrequired자산군
statuspending/open/filled/canceled/rejected
page11-based
size201–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_classY

응답 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_classY

응답 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_events audit 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_price metadata로 전달
  • 라이브 첫 주문에는 first_live_order=true 마커가 audit log에 기록되어 모니터링 알림이 발송됨