# 주문

모든 주문 API는 **인증 필요**예요.

| 메서드 | 경로                          | 설명                                      |
| ------ | ----------------------------- | ----------------------------------------- |
| POST   | `/v1/orders`                  | 주문(시장가·지정가 매수/매도)             |
| GET    | `/v1/orders`                  | 주문 이력 / 매매 대기열(`status=pending`) |
| GET    | `/v1/orders/{orderId}`        | 주문 단건 조회                            |
| POST   | `/v1/orders/{orderId}/cancel` | 대기 중인 지정가 주문 취소                |

## 주문하기

`POST /v1/orders`

| 필드          | 필수           | 설명                                                                               |
| ------------- | -------------- | ---------------------------------------------------------------------------------- |
| `symbol`      | ✅             | 종목                                                                               |
| `side`        | ✅             | `buy` 매수 / `sell` 매도                                                           |
| `type`        |                | `market` 시장가(기본) / `limit` 지정가                                             |
| `quantity`    | ✅             | 수량(1 이상 정수)                                                                  |
| `limitPrice`  | 지정가일 때 ✅ | 지정 가격(원, 정수)                                                                |
| `clientToken` | ✅             | UUID. 주문마다 새로 만들고, 재시도할 땐 같은 값([멱등성](/developers/conventions)) |

### 시장가 매수

```json
{
  "symbol": "FOXX",
  "side": "buy",
  "type": "market",
  "quantity": 10,
  "clientToken": "7f1c0a52-3c1e-4b0e-9a57-6f1d2b8e4c11"
}
```

### 시장가 매도

```json
{ "symbol": "FOXX", "side": "sell", "type": "market", "quantity": 10, "clientToken": "0c2e7a61-..." }
```

### 지정가 매수 (1,100원 이하가 되면 사기)

```json
{ "symbol": "FOXX", "side": "buy", "type": "limit", "limitPrice": 1100, "quantity": 5, "clientToken": "a8d1..." }
```

### 지정가 매도 (1,500원 이상이 되면 팔기)

```json
{ "symbol": "FOXX", "side": "sell", "type": "limit", "limitPrice": 1500, "quantity": 5, "clientToken": "b93f..." }
```

**응답 200** `Order`

```json
{
  "orderId": "3f0e8f7a-...",
  "symbol": "FOXX",
  "sessionId": "0f6c...",
  "side": "buy",
  "type": "market",
  "limitPrice": null,
  "status": "filled",
  "rejectReason": null,
  "quantityRequested": 10,
  "quantityFilled": 10,
  "avgPrice": 1241,
  "notional": 12410,
  "fee": 18,
  "tax": 0,
  "cashBalanceAfter": 275022,
  "lastPriceAfter": 1249,
  "createdAt": "2026-09-24T05:12:03.120Z"
}
```

| 필드               | 설명                                        |
| ------------------ | ------------------------------------------- |
| `status`           | 아래 상태표 참고                            |
| `quantityFilled`   | 체결된 수량                                 |
| `avgPrice`         | 평균 체결가. 미체결이면 0                   |
| `notional`         | 체결 금액(`avgPrice × quantityFilled` 근사) |
| `fee` / `tax`      | 수수료(0.15%) / 매도 세금(0.2%)             |
| `cashBalanceAfter` | 주문 직후 현금 잔고                         |
| `lastPriceAfter`   | 주문 직후 현재가                            |

### 체결 방식

- **시장가**는 즉시 체결돼요. 퍼스톡은 가격 곡선 모델이라 많이 살수록 평균 체결가가 올라가요(파는 쪽은 내려가요). 상한가·하한가(세션 시작가 ±30%)에 닿으면 거기까지만 체결되고(`partial`) 나머지는 거부돼요.
- **지정가**는 `pending`으로 대기열에 들어가요. 매수는 현재가가 `limitPrice` 이하이고 **전량을 샀을 때의 평균 체결가도** `limitPrice` 이하일 때(매도는 둘 다 이상일 때) **전량 한 번에** 체결돼요(부분체결 없음). 조건은 그 종목에 거래가 일어날 때마다 다시 확인하고, 이미 맞으면 주문하자마자 체결될 수도 있어요.
- 지정가 대기 중엔 돈·주식을 묶어두지 않아요. 체결 순간 잔고나 보유 수량이 모자라면 체결되지 않고 계속 대기해요.
- 세션 정산(KST 3시간마다) 때까지 체결되지 않은 지정가 주문은 자동 취소돼요(`cancelled`, `session_settlement`).

### 주문 상태

| `status`    | 의미                                     |
| ----------- | ---------------------------------------- |
| `filled`    | 전량 체결                                |
| `partial`   | 일부 체결, 나머지 거부(가격 제한폭 도달) |
| `pending`   | 지정가 대기 중                           |
| `rejected`  | 거부(`rejectReason` 참고)                |
| `cancelled` | 취소(세션 정산, 거래정지, 직접 취소)     |

### 거부·취소 사유 (`rejectReason`)

| 값                       | 의미                                                                         |
| ------------------------ | ---------------------------------------------------------------------------- |
| `insufficient_cash`      | 매수 잔고 부족                                                               |
| `insufficient_shares`    | 매도할 보유 수량 부족                                                        |
| `price_band_reached`     | 상한가·하한가 도달                                                           |
| `circuit_breaker`        | 서킷브레이커 발동 중(종목 5분 정지)                                          |
| `admin_halted`           | 관리자가 거래정지한 종목                                                     |
| `account_frozen`         | 관리자가 내 계좌를 동결함(매수·매도 모두 거부, 미체결 주문도 이 사유로 취소) |
| `market_closed`          | 일시적으로 거래 불가(세션 전환 순간 등). 잠시 후 재시도                      |
| `stock_delisted`         | 상장폐지된 종목                                                              |
| `qty_not_positive`       | 수량이 1 미만                                                                |
| `duplicate_client_token` | 이미 쓴 clientToken                                                          |
| `session_settlement`     | 세션 정산으로 지정가 자동 취소                                               |
| `user_cancelled`         | 직접 취소한 지정가                                                           |

> 주문 거부는 HTTP 오류가 아니라 `200`에 `status: "rejected"`로 와요. 봇은 HTTP 상태뿐 아니라 **`status`를 꼭 확인**하세요.

## 주문 이력

`GET /v1/orders`

| 쿼리     | 설명                                                                                           |
| -------- | ---------------------------------------------------------------------------------------------- |
| `status` | `filled` · `partial` · `rejected` · `pending` · `cancelled` 중 하나로 거르기                   |
| `symbol` | 종목으로 거르기                                                                                |
| `limit`  | 1~200. 기본 50                                                                                 |
| `before` | 이 시각보다 오래된 것만(ISO 8601). 다음 페이지를 볼 때 이전 페이지 마지막 `createdAt`을 넣어요 |

최신순 `Order[]`. 목록에서는 `cashBalanceAfter`/`lastPriceAfter`가 항상 0이에요(주문 직후 값이라 이력에선 의미가 없어요).

```bash
# 다음 페이지
curl -s "https://api.furstock.trade/v1/orders?limit=200&before=2026-09-24T05:12:03.120Z" \
  -H "Authorization: Bearer $TOKEN"
```

오래된 주문은 목록에서 빠질 수 있어요. 아카이브된 주문도 `GET /v1/orders/{orderId}`로는 조회돼요.

## 매매 대기열

`GET /v1/orders?status=pending`

아직 체결되지 않은 내 지정가 주문 목록이에요. 웹의 "내 투자 → 주문내역 → 대기" 탭과 같아요.

## 주문 단건

`GET /v1/orders/{orderId}` → `Order`. 내 주문이 아니거나 없으면 `404`.

## 지정가 취소

`POST /v1/orders/{orderId}/cancel`

`pending` 상태인 내 지정가 주문을 취소해요. 성공하면 `status: "cancelled"`, `rejectReason: "user_cancelled"`인 `Order`를 돌려줘요.

| 상태 | `error`       | 의미                                                  |
| ---- | ------------- | ----------------------------------------------------- |
| 404  | `not_found`   | 없거나 내 주문이 아님                                 |
| 409  | `not_pending` | 이미 체결·취소·거부됨. 취소 직전에 체결됐을 수 있어요 |

지정가를 **정정**하는 API는 따로 없어요. 취소하고 새로 주문하세요.
