Files
group_fqcd_jr/app/api/schemas/trading.py
T
张胜宇 e239eb778b docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富
   统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」;
   同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。
2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本),
   新增《文档规整方案与开发前待决事项-2026-09-17》。
3) 客服agent 四份交付文档首次纳入本分支。
2026-09-17 15:15:22 +08:00

196 lines
4.6 KiB
Python

"""场内基金模拟交易 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