669 lines
17 KiB
Markdown
669 lines
17 KiB
Markdown
# API Contracts
|
|
|
|
- Related REQ: `REQ-001`, `REQ-002`, `REQ-003`, `REQ-004`
|
|
|
|
## 공통 원칙
|
|
- 응답은 JSON 기반으로 반환한다.
|
|
- 인증이 필요한 엔드포인트는 `Authorization: Bearer <token>`을 사용한다.
|
|
- 권한 계층은 `read` < `trade-write` < `admin`이다.
|
|
- `admin`이 아닌 요청은 포트폴리오 리소스 접근 시 `owner_user_id` 소유권 검사를 추가로 통과해야 한다.
|
|
- 에러 응답은 HTTP 상태 코드와 함께 원인 메시지를 제공한다.
|
|
- Base URL 예시는 `http://localhost:8000`을 기준으로 한다.
|
|
- JSON 예시는 대표 형태이며 일부 선택 필드를 생략할 수 있다.
|
|
|
|
## Contract Surface
|
|
|
|
### 공개 문서 엔드포인트
|
|
- `GET /docs`
|
|
- `GET /redoc`
|
|
- `GET /openapi.json`
|
|
|
|
### 헬스
|
|
- `GET /health` (인증 없음)
|
|
- 정상 응답 예시:
|
|
```json
|
|
{"status": "ok"}
|
|
```
|
|
|
|
### 인증
|
|
- `POST /auth/login`
|
|
- `GET /auth/tokens`
|
|
- `POST /auth/tokens`
|
|
- `DELETE /auth/tokens/{token_id}`
|
|
- `POST /auth/bots`
|
|
- `GET /auth/bots`
|
|
- `POST /auth/bots/{bot_id}/tokens`
|
|
- `GET /auth/bots/{bot_id}/tokens`
|
|
- `DELETE /auth/bots/{bot_id}/tokens/{token_id}`
|
|
- `DELETE /auth/bots/{bot_id}`
|
|
|
|
### 포트폴리오
|
|
- `GET /portfolios`
|
|
- `GET /portfolios/{portfolio_id}`
|
|
- `POST /portfolios`
|
|
- `PATCH /portfolios/{portfolio_id}`
|
|
- `POST /portfolios/{portfolio_id}/buy`
|
|
- `POST /portfolios/{portfolio_id}/sell`
|
|
- `POST /portfolios/{portfolio_id}/invest`
|
|
- `POST /portfolios/{portfolio_id}/transfer`
|
|
- `POST /portfolios/{portfolio_id}/reset`
|
|
- `DELETE /portfolios/{portfolio_id}`
|
|
|
|
### 포트폴리오 댓글 (`REQ-001`)
|
|
- `POST /portfolios/{portfolio_id}/comments`
|
|
- `GET /portfolios/{portfolio_id}/comments`
|
|
- `PATCH /portfolios/{portfolio_id}/comments/{comment_id}`
|
|
- `DELETE /portfolios/{portfolio_id}/comments/{comment_id}`
|
|
|
|
### 전략셋
|
|
- `GET /strategy-sets`
|
|
- `POST /strategy-sets`
|
|
- `GET /strategy-sets/{strategy_set_id}`
|
|
- `PATCH /strategy-sets/{strategy_set_id}`
|
|
- `DELETE /strategy-sets/{strategy_set_id}`
|
|
|
|
### 사용자
|
|
- `GET /users`
|
|
- `GET /users/{user_id}`
|
|
- `POST /users`
|
|
- `PATCH /users/{user_id}`
|
|
- `DELETE /users/{user_id}`
|
|
|
|
## 오류 처리 기준
|
|
|
|
| 시나리오 | HTTP |
|
|
| --- | --- |
|
|
| 인증 헤더 누락/형식 오류 | `401` |
|
|
| 유효하지 않은 토큰 | `401` |
|
|
| 권한 부족 | `403` |
|
|
| 유효성 검증 실패 | `422` |
|
|
| 리소스 없음 | `404` |
|
|
| 내부 예외 | `500` |
|
|
|
|
## 상세 엔드포인트 명세
|
|
|
|
### 헬스
|
|
|
|
#### `GET /health`
|
|
- 인증 없음
|
|
- 응답 `200`:
|
|
|
|
```json
|
|
{"status": "ok"}
|
|
```
|
|
|
|
### 포트폴리오
|
|
|
|
#### `GET /portfolios`
|
|
- Auth: `read`
|
|
- Ownership: `admin`은 전체 조회, 그 외는 본인 소유(`owner_user_id == auth_context.user_id`)만 조회
|
|
- Query:
|
|
- `broker: string | null`
|
|
- `status_filter: string | null`
|
|
- 응답 `200`: `PortfolioListItem[]`
|
|
|
|
예시:
|
|
|
|
```bash
|
|
curl -X GET "http://localhost:8000/portfolios?broker=upbit&status_filter=enabled" \
|
|
-H "Authorization: Bearer <READ_TOKEN>"
|
|
```
|
|
|
|
```json
|
|
[
|
|
{
|
|
"portfolio_id": "krw-btc",
|
|
"broker": "upbit",
|
|
"status": "enabled",
|
|
"name": "Bitcoin",
|
|
"symbol": "KRW-BTC",
|
|
"cash_balance": 1000000.0,
|
|
"asset_quantity": 0.1,
|
|
"price": 100000000.0,
|
|
"profit_buffer": 0.0,
|
|
"active_strategy_id": "stv-001",
|
|
"avabile_strategies": ["stv-001"],
|
|
"created_at": "2026-04-03T00:00:00Z",
|
|
"updated_at": "2026-04-03T00:00:00Z",
|
|
"last_trade_at": "2026-04-03T00:00:00Z",
|
|
"total_value": 11000000.0
|
|
}
|
|
]
|
|
```
|
|
|
|
#### `GET /portfolios/{portfolio_id}`
|
|
- Auth: `read`
|
|
- Ownership: `admin`이 아니고 소유주 불일치 시 `403`
|
|
- Query:
|
|
- `recent_trades: int = 5`
|
|
- 응답 `200`: `PortfolioDetailResponse`
|
|
|
|
```bash
|
|
curl -X GET "http://localhost:8000/portfolios/krw-btc?recent_trades=5" \
|
|
-H "Authorization: Bearer <READ_TOKEN>"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"portfolio": {
|
|
"portfolio_id": "krw-btc",
|
|
"broker": "upbit",
|
|
"status": "enabled",
|
|
"name": "Bitcoin",
|
|
"symbol": "KRW-BTC",
|
|
"owner_user_id": "user-1",
|
|
"cash_balance": 1000000.0,
|
|
"asset_quantity": 0.1,
|
|
"price": 100000000.0,
|
|
"profit_buffer": 0.0,
|
|
"active_strategy_id": "stv-001",
|
|
"avabile_strategies": ["stv-001"],
|
|
"created_at": "2026-04-03T00:00:00Z",
|
|
"updated_at": "2026-04-03T00:00:00Z",
|
|
"last_trade_at": "2026-04-03T00:00:00Z"
|
|
},
|
|
"recent_entries": [
|
|
{
|
|
"portfolio_id": "krw-btc",
|
|
"recorded_at": "2026-04-03T00:00:00Z",
|
|
"action": "buy",
|
|
"price": 100000000.0,
|
|
"quantity": 0.1,
|
|
"fee_amount": 0.0,
|
|
"tax_amount": 0.0,
|
|
"cash_balance_after": 1000000.0,
|
|
"asset_quantity_after": 0.1,
|
|
"total_value_after": 11000000.0,
|
|
"profit_buffer_after": 0.0,
|
|
"strategy_set_id": "stv-001",
|
|
"note": ""
|
|
}
|
|
],
|
|
"strategy_set": {
|
|
"id": "stv-001",
|
|
"name": "target_value",
|
|
"config": "{}",
|
|
"nick": "target_value:99914b932bd3",
|
|
"created_at": "2026-04-03T00:00:00Z"
|
|
},
|
|
"live_quantity": null
|
|
}
|
|
```
|
|
|
|
#### `POST /portfolios`
|
|
- Auth: `trade-write`
|
|
- Ownership: 요청 body로 owner를 받지 않고 인증 컨텍스트 사용자로 `owner_user_id`를 강제 설정
|
|
- Body: `CreatePortfolioRequest`
|
|
- 응답 `201`: `PortfolioDetailResponse`
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8000/portfolios" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"portfolio_id": "krw-btc",
|
|
"broker": "upbit",
|
|
"status": "enabled",
|
|
"name": "Bitcoin",
|
|
"symbol": "KRW-BTC",
|
|
"strategy_name": "target_value",
|
|
"strategy_params": {},
|
|
"active_strategy_id": null,
|
|
"avabile_strategies": null,
|
|
"cash_balance": 1000000,
|
|
"asset_quantity": 0,
|
|
"asset_price": 0,
|
|
"profit_buffer": 0
|
|
}'
|
|
```
|
|
|
|
#### `PATCH /portfolios/{portfolio_id}`
|
|
- Auth: `trade-write`
|
|
- Body: `UpdatePortfolioRequest`
|
|
- 응답 `200`: `PortfolioDetailResponse`
|
|
|
|
```bash
|
|
curl -X PATCH "http://localhost:8000/portfolios/krw-btc" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"broker": "upbit",
|
|
"name": "Bitcoin",
|
|
"symbol": "KRW-BTC"
|
|
}'
|
|
```
|
|
|
|
#### 거래/자금 이동 엔드포인트
|
|
- `POST /portfolios/{portfolio_id}/buy`: `BuyRequest` -> `PortfolioDetailResponse`
|
|
- `POST /portfolios/{portfolio_id}/sell`: `SellRequest` -> `PortfolioDetailResponse`
|
|
- `POST /portfolios/{portfolio_id}/invest`: `InvestRequest` -> `PortfolioDetailResponse`
|
|
- `POST /portfolios/{portfolio_id}/transfer`: `TransferRequest` -> `PortfolioDetailResponse`
|
|
- `POST /portfolios/{portfolio_id}/reset`: `ResetPortfolioRequest` -> `PortfolioDetailResponse`
|
|
- `DELETE /portfolios/{portfolio_id}`: admin only, query `keep_ledger: bool = false`, 응답 `204`
|
|
|
|
대표 요청:
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8000/portfolios/krw-btc/buy" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"quantity": 0.01, "price": 100000000, "fee": 0, "tax": 0, "note": ""}'
|
|
|
|
curl -X POST "http://localhost:8000/portfolios/krw-btc/sell" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"quantity": 0.01, "price": 100000000, "fee": 0, "tax": 0, "note": ""}'
|
|
|
|
curl -X POST "http://localhost:8000/portfolios/krw-btc/invest" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"movement_type": "deposit", "amount": 50000, "note": ""}'
|
|
|
|
curl -X POST "http://localhost:8000/portfolios/krw-btc/transfer" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"direction": "to-buffer", "amount": 10000, "note": ""}'
|
|
|
|
curl -X POST "http://localhost:8000/portfolios/krw-btc/reset" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"infer_symbol": "KRW-BTC"}'
|
|
|
|
curl -X DELETE "http://localhost:8000/portfolios/krw-btc?keep_ledger=false" \
|
|
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
|
```
|
|
|
|
#### 포트폴리오 댓글 (`REQ-001`)
|
|
- `POST /portfolios/{portfolio_id}/comments`
|
|
- Auth: `trade-write`
|
|
- Body: `CommentCreateRequest`
|
|
- Response `201`: `CommentResponse`
|
|
- `GET /portfolios/{portfolio_id}/comments`
|
|
- Auth: `read`
|
|
- Response `200`: `CommentResponse[]`
|
|
- `PATCH /portfolios/{portfolio_id}/comments/{comment_id}`
|
|
- Auth: `trade-write`
|
|
- Body: `CommentUpdateRequest`
|
|
- Response `200`: `CommentResponse`
|
|
- `DELETE /portfolios/{portfolio_id}/comments/{comment_id}`
|
|
- Auth: `trade-write`
|
|
- Response `204`
|
|
|
|
예시:
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8000/portfolios/krw-btc/comments" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"content": "Bought more BTC today.\nLooking good."}'
|
|
|
|
curl -X GET "http://localhost:8000/portfolios/krw-btc/comments" \
|
|
-H "Authorization: Bearer <READ_TOKEN>"
|
|
|
|
curl -X PATCH "http://localhost:8000/portfolios/krw-btc/comments/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"content": "Updated note: BTC rally continues."}'
|
|
|
|
curl -X DELETE "http://localhost:8000/portfolios/krw-btc/comments/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
"portfolio_id": "krw-btc",
|
|
"content": "Bought more BTC today.\nLooking good.",
|
|
"created_at": "2026-04-10T00:00:00Z",
|
|
"updated_at": "2026-04-10T00:00:00Z"
|
|
}
|
|
```
|
|
|
|
### 전략셋
|
|
|
|
#### `GET /strategy-sets`
|
|
- Auth: `read`
|
|
- Response `200`: `StrategySetResponse[]`
|
|
|
|
#### `POST /strategy-sets`
|
|
- Auth: `trade-write`
|
|
- Body: `CreateStrategySetRequest`
|
|
- Response `201`: `StrategySetResponse`
|
|
- 기존 `(name, config)` 쌍이 있으면 기존 strategy set 반환
|
|
|
|
#### `PATCH /strategy-sets/{strategy_set_id}`
|
|
- Auth: `trade-write`
|
|
- Body: `UpdateStrategySetRequest`
|
|
- Response `200`: `StrategySetResponse`
|
|
|
|
#### `DELETE /strategy-sets/{strategy_set_id}`
|
|
- Auth: `trade-write`
|
|
- Response `204`
|
|
- in use 상태면 `409`
|
|
|
|
대표 요청:
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8000/strategy-sets" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "target_value", "config": "{}"}'
|
|
|
|
curl -X PATCH "http://localhost:8000/strategy-sets/stv-001" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"config": "{\"rebalance\": true}"}'
|
|
|
|
curl -X DELETE "http://localhost:8000/strategy-sets/stv-001" \
|
|
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>"
|
|
```
|
|
|
|
### 인증
|
|
|
|
#### `POST /auth/login`
|
|
- Auth 없음
|
|
- Body: `LoginRequest`
|
|
- Response `200`: `TokenResponse`
|
|
- 잘못된 username/password면 `401`
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8000/auth/login" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"username": "admin",
|
|
"password": "bootstrap123"
|
|
}'
|
|
```
|
|
|
|
```json
|
|
{
|
|
"access_token": "plain-token-value",
|
|
"token_type": "bearer",
|
|
"expires_at": "2026-04-03T12:00:00Z",
|
|
"permission": "admin",
|
|
"user": {
|
|
"id": "user-1",
|
|
"username": "admin",
|
|
"email": "admin@example.com",
|
|
"permission": "admin",
|
|
"is_active": true,
|
|
"created_at": "2026-04-03T00:00:00Z",
|
|
"updated_at": "2026-04-03T00:00:00Z",
|
|
"last_login_at": "2026-04-03T00:00:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### `GET /auth/tokens`
|
|
- Auth: DB-issued `read` 이상 토큰
|
|
- Response `200`: `AuthTokenResponse[]`
|
|
|
|
#### `POST /auth/tokens`
|
|
- Auth: DB-issued `read` 이상 토큰
|
|
- Body: `CreateClientTokenRequest`
|
|
- Response `201`: `IssuedClientTokenResponse`
|
|
- 요청 permission이 현재 permission을 넘으면 `403`
|
|
|
|
#### `DELETE /auth/tokens/{token_id}`
|
|
- Auth: DB-issued `read` 이상 토큰
|
|
- Response `204`
|
|
- 미존재 시 `404`
|
|
|
|
대표 요청:
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8000/auth/tokens" \
|
|
-H "Authorization: Bearer <SESSION_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "cli", "permission": "trade-write", "expires_in_days": 30}'
|
|
```
|
|
|
|
#### 봇 주체와 봇 토큰
|
|
- `POST /auth/bots`: admin only, `CreateBotPrincipalRequest` -> `BotPrincipalResponse`
|
|
- `GET /auth/bots`: admin only, `is_active` 필터 지원
|
|
- `GET /auth/bots/{bot_id}`: admin only
|
|
- `DELETE /auth/bots/{bot_id}`: admin only, bot 비활성화 및 연결 토큰 revoke, 응답 `200`
|
|
- `POST /auth/bots/{bot_id}/tokens`: admin only, `CreateBotTokenRequest` -> `IssuedBotTokenResponse`
|
|
- `GET /auth/bots/{bot_id}/tokens`: admin only, `AuthTokenResponse[]`
|
|
- `DELETE /auth/bots/{bot_id}/tokens/{token_id}`: admin only, `204`
|
|
|
|
대표 요청:
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8000/auth/bots" \
|
|
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "rebalance-bot", "permission": "trade-write", "description": "nightly jobs"}'
|
|
|
|
curl -X POST "http://localhost:8000/auth/bots/bot-1/tokens" \
|
|
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "nightly", "permission": "trade-write", "expires_in_days": 90}'
|
|
```
|
|
|
|
### 사용자
|
|
|
|
모든 `/users` 엔드포인트는 admin only이다.
|
|
|
|
- `GET /users`: `UserResponse[]`
|
|
- `GET /users/{user_id}`: `UserResponse`, 미존재 시 `404`
|
|
- `POST /users`: `CreateUserRequest` -> `UserResponse`, duplicate username/email이면 `409`
|
|
- `PATCH /users/{user_id}`: `UpdateUserRequest` -> `UserResponse`, duplicate면 `409`
|
|
- `DELETE /users/{user_id}`: `204`
|
|
|
|
bootstrap 규칙:
|
|
|
|
- DB persistence가 활성이고 `PORTFOLIOHUB_BOOTSTRAP_USERNAME`, `PORTFOLIOHUB_BOOTSTRAP_PASSWORD`가 설정되면, `users` 테이블이 비어 있을 때만 startup에서 초기 admin user를 생성한다.
|
|
|
|
대표 요청:
|
|
|
|
```bash
|
|
curl -X GET "http://localhost:8000/users" \
|
|
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
|
|
|
curl -X POST "http://localhost:8000/users" \
|
|
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"username": "alice",
|
|
"email": "alice@example.com",
|
|
"password": "password123",
|
|
"permission": "read",
|
|
"is_active": true
|
|
}'
|
|
```
|
|
|
|
## 요청/응답 모델 요약
|
|
|
|
### Request Models
|
|
|
|
`CreatePortfolioRequest`
|
|
|
|
- `portfolio_id: str`
|
|
- `broker: str = "kis"`
|
|
- `status: str = "enabled"`
|
|
- `name: str | null`
|
|
- `symbol: str`
|
|
- `strategy_name: str = "target_value"`
|
|
- `strategy_params: object = {}`
|
|
- `active_strategy_id: str | null`
|
|
- `avabile_strategies: string[] | null`
|
|
- `cash_balance: float >= 0`
|
|
- `asset_quantity: float >= 0`
|
|
- `asset_price: float >= 0`
|
|
- `profit_buffer: float >= 0`
|
|
|
|
`UpdatePortfolioRequest`
|
|
|
|
- `broker: str | null`
|
|
- `status: str | null`
|
|
- `name: str | null`
|
|
- `symbol: str | null`
|
|
- `strategy_name: str | null`
|
|
- `strategy_params: object | null`
|
|
- `active_strategy_id: str | null`
|
|
- `avabile_strategies: string[] | null`
|
|
- `asset_price: float >= 0 | null`
|
|
|
|
`BuyRequest` / `SellRequest`
|
|
|
|
- `quantity: float > 0`
|
|
|
|
`InvestRequest`
|
|
|
|
- `movement_type: "deposit" | "profit" | "withdraw"`
|
|
- `amount: float > 0`
|
|
- `note: str = ""`
|
|
|
|
`TransferRequest`
|
|
|
|
- `direction: "to-buffer" | "from-buffer"`
|
|
- `amount: float > 0`
|
|
- `note: str = ""`
|
|
|
|
`ResetPortfolioRequest`
|
|
|
|
- `infer_symbol: str | null`
|
|
|
|
`CommentCreateRequest` / `CommentUpdateRequest`
|
|
|
|
- `content: str`
|
|
|
|
`LoginRequest`
|
|
|
|
- `username: str`
|
|
- `password: str`
|
|
|
|
`CreateClientTokenRequest`
|
|
|
|
- `name: str`
|
|
- `permission: "read" | "trade-write" | "admin" | null`
|
|
- `expires_in_days: int | null`
|
|
|
|
`CreateBotPrincipalRequest`
|
|
|
|
- `name: str`
|
|
- `permission: "read" | "trade-write" | "admin"`
|
|
- `description: str | null`
|
|
|
|
`CreateBotTokenRequest`
|
|
|
|
- `name: str`
|
|
- `permission: "read" | "trade-write" | "admin" | null`
|
|
- `expires_in_days: int | null`
|
|
|
|
`CreateUserRequest`
|
|
|
|
- `username: str`
|
|
- `email: str | null`
|
|
- `password: str`
|
|
- `permission: "read" | "trade-write" | "admin"`
|
|
- `is_active: bool`
|
|
|
|
`UpdateUserRequest`
|
|
|
|
- `username: str | null`
|
|
- `email: str | null`
|
|
- `password: str | null`
|
|
- `permission: "read" | "trade-write" | "admin" | null`
|
|
- `is_active: bool | null`
|
|
|
|
`CreateStrategySetRequest`
|
|
|
|
- `name: str`
|
|
- `config: str`
|
|
|
|
`UpdateStrategySetRequest`
|
|
|
|
- `name: str | null`
|
|
- `config: str | null`
|
|
|
|
### Response Models
|
|
|
|
`PortfolioListItem`
|
|
|
|
- portfolio summary fields + computed `total_value`
|
|
|
|
`PortfolioSummary`
|
|
|
|
- `portfolio_id: str`
|
|
- `broker: str`
|
|
- `status: str`
|
|
- `name: str`
|
|
- `symbol: str`
|
|
- `cash_balance: float`
|
|
- `asset_quantity: float`
|
|
- `price: float`
|
|
- `profit_buffer: float`
|
|
- `active_strategy_id: str | null`
|
|
- `avabile_strategies: string[] | null`
|
|
- `created_at: datetime | null`
|
|
- `updated_at: datetime | null`
|
|
- `last_trade_at: datetime | null`
|
|
|
|
`PortfolioDetailResponse`
|
|
|
|
- `portfolio: PortfolioSummary`
|
|
- `recent_entries: LedgerEntryResponse[]`
|
|
- `strategy_set: StrategySetResponse | null`
|
|
- `live_quantity: float | null`
|
|
|
|
`StrategySetResponse`
|
|
|
|
- `id: str`
|
|
- `name: str`
|
|
- `config: str`
|
|
- `nick: str`
|
|
- `created_at: datetime | null`
|
|
|
|
`CommentResponse`
|
|
|
|
- `id: str`
|
|
- `portfolio_id: str`
|
|
- `content: str`
|
|
- `created_at: datetime | null`
|
|
- `updated_at: datetime | null`
|
|
|
|
`BotPrincipalResponse`
|
|
|
|
- `id: str`
|
|
- `name: str`
|
|
- `permission: "read" | "trade-write" | "admin"`
|
|
- `description: str | null`
|
|
- `is_active: bool`
|
|
- `created_at: datetime | null`
|
|
- `updated_at: datetime | null`
|
|
- `deactivated_at: datetime | null`
|
|
|
|
`ErrorResponse`
|
|
|
|
- `detail: str`
|
|
|
|
## 공통 오류 예시
|
|
|
|
### Missing token
|
|
|
|
```json
|
|
{"detail": "Not authenticated"}
|
|
```
|
|
|
|
### Invalid token
|
|
|
|
```json
|
|
{"detail": "Invalid token"}
|
|
```
|
|
|
|
### Insufficient permission
|
|
|
|
```json
|
|
{"detail": "Insufficient permission"}
|
|
```
|
|
|
|
## 문서 동기화 규칙
|
|
- `app/api/` 또는 `app/schemas/` 변경 시 본 문서를 같은 변경 단위에서 갱신한다.
|
|
- `REQ-002` 태그 검색 API가 구현되면 요청/응답 파라미터와 필터 의미를 본 문서에 추가한다.
|
|
|
|
## Open Implementation Points
|
|
- 에러 응답 body를 공통 포맷(`error_code`, `message`, `details`)으로 강제할지 결정한다.
|
|
- idempotency가 필요한 쓰기 API(예: 재시도 가능 매매 요청)의 키 정책을 확정한다.
|