"""场内基金模拟交易 API Schemas(§T 用户自助端点)。 按 `docs/05` §T 端点清单设计:账户看板、委托、持仓、成交、资金明细。 请求/响应模型只承载字段契约,**业务校验**(产品可用性、适当性、持仓比例、 成交价快照)由 Service 层在调用行情/账户后处理。 """ from __future__ import annotations from datetime import datetime from decimal import Decimal from typing import Literal from pydantic import BaseModel, ConfigDict, Field OrderSide = Literal["buy", "sell"] PriceType = Literal["market"] # 首版只支持市价(基线 §6.2 明确) class AccountSummary(BaseModel): """虚拟资金账户快照。""" model_config = ConfigDict(extra="forbid") account_no: str status: str currency: str initial_balance: Decimal cash_balance: Decimal available_cash: Decimal frozen_cash: Decimal class PortfolioSummary(BaseModel): """组合汇总指标。""" model_config = ConfigDict(extra="forbid") total_asset: Decimal total_market_value: Decimal total_cost: Decimal total_profit_loss: Decimal total_profit_loss_ratio: Decimal today_profit_loss: Decimal today_profit_loss_ratio: Decimal class HoldingItem(BaseModel): """单只基金持仓(前端可直接渲染)。""" model_config = ConfigDict(extra="forbid") product_id: int product_code: str product_name: str total_quantity: Decimal available_quantity: Decimal frozen_quantity: Decimal average_cost: Decimal cost_amount: Decimal latest_price: Decimal market_value: Decimal profit_loss: Decimal profit_loss_ratio: Decimal today_profit_loss: Decimal class AccountDashboardResponse(BaseModel): """T001 `GET /api/v1/users/me/account/dashboard` 响应。""" model_config = ConfigDict(extra="forbid") account: AccountSummary summary: PortfolioSummary holdings: list[HoldingItem] as_of: datetime class OrderCreateRequest(BaseModel): """T002 提交委托请求。""" model_config = ConfigDict(extra="forbid") product_code: str = Field(min_length=1, max_length=32) order_side: OrderSide quantity: Decimal = Field(gt=Decimal("0")) price_type: PriceType = "market" class OrderCreateResponse(BaseModel): """T002 提交委托响应(市价立即成交,首版 status='已成交')。""" model_config = ConfigDict(extra="forbid") order_no: str status: str executed_quantity: Decimal executed_price: Decimal gross_amount: Decimal fee_amount: Decimal net_amount: Decimal executed_at: datetime class OrderSummary(BaseModel): """委托列表项(T003 / T004)。""" model_config = ConfigDict(extra="forbid") order_no: str product_id: int product_code: str product_name: str order_side: OrderSide price_type: PriceType quantity: Decimal limit_price: Decimal | None quote_price: Decimal quote_at: datetime filled_quantity: Decimal average_executed_price: Decimal | None status: str submitted_at: datetime cancelled_at: datetime | None reject_reason: str | None class OrderCancelResponse(BaseModel): """T005 撤单响应。""" model_config = ConfigDict(extra="forbid") order_no: str status: str cancelled_at: datetime class HoldingListResponse(BaseModel): """T006 持仓列表响应。""" model_config = ConfigDict(extra="forbid") holdings: list[HoldingItem] class TransactionItem(BaseModel): """成交记录列表项(T007 / T008)。""" model_config = ConfigDict(extra="forbid") transaction_no: str order_no: str product_id: int product_code: str product_name: str order_side: OrderSide executed_price: Decimal executed_quantity: Decimal gross_amount: Decimal fee_amount: Decimal net_amount: Decimal quote_at: datetime executed_at: datetime class TransactionListResponse(BaseModel): """T007 成交列表响应。""" model_config = ConfigDict(extra="forbid") transactions: list[TransactionItem] next_cursor: str | None = None class CashLedgerItem(BaseModel): """资金明细项(T009)。""" model_config = ConfigDict(extra="forbid") ledger_no: str entry_type: str amount: Decimal balance_after: Decimal available_cash_after: Decimal frozen_cash_after: Decimal transaction_no: str | None occurred_at: datetime class CashLedgerResponse(BaseModel): """T009 资金明细响应。""" model_config = ConfigDict(extra="forbid") entries: list[CashLedgerItem] next_cursor: str | None = None