713 lines
27 KiB
Python
713 lines
27 KiB
Python
"""Pydantic DTO —— 这就是 OpenAPI 契约(前端据此生成类型化客户端)。
|
||
|
||
契约先于业务锁定:字段一旦定下,前端可并行开发,后端实现改动不影响前端。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import date, datetime
|
||
from typing import Literal
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
# ---------- Candle ----------
|
||
class CandleOut(BaseModel):
|
||
ts: datetime
|
||
open: float
|
||
high: float
|
||
low: float
|
||
close: float
|
||
volume: float
|
||
amount: float | None = None # 成交额(元);无数据为 null
|
||
turnover: float | None = None # 换手率 %;无数据为 null
|
||
|
||
model_config = {"from_attributes": True}
|
||
|
||
|
||
# ---------- Backtest ----------
|
||
class BacktestRequest(BaseModel):
|
||
symbol: str = "000001"
|
||
timeframe: str = "1d"
|
||
strategy: str = "macd_cross" # macd_cross | ma_cross | single_ma
|
||
params: dict[str, float] = Field(default_factory=dict) # 各策略参数
|
||
initial_cash: float = 1000000.0
|
||
fast_mode: bool = False # True => 关闭 T+1/费用,交互试探
|
||
start: datetime | None = None
|
||
end: datetime | None = None
|
||
|
||
|
||
class SignalOut(BaseModel):
|
||
ts: datetime
|
||
side: str # "buy" | "sell"
|
||
price: float
|
||
qty: float
|
||
|
||
|
||
class EquityPoint(BaseModel):
|
||
ts: datetime
|
||
value: float
|
||
|
||
|
||
class IndicatorOut(BaseModel):
|
||
strategy: str
|
||
data: dict[str, list[float | None]] = {} # 列名 -> 序列(MACD: macd/signal/hist;均线: fast/slow 或 ma)
|
||
|
||
|
||
class MetricsOut(BaseModel):
|
||
total_return: float
|
||
max_drawdown: float
|
||
sharpe: float
|
||
volatility: float
|
||
num_trades: int = 0
|
||
win_rate: float = 0.0
|
||
|
||
|
||
class BacktestResponse(BaseModel):
|
||
symbol: str
|
||
timeframe: str
|
||
strategy: str
|
||
candles: list[CandleOut]
|
||
indicators: IndicatorOut
|
||
signals: list[SignalOut]
|
||
equity: list[EquityPoint]
|
||
metrics: MetricsOut
|
||
final_cash: float
|
||
final_position: float
|
||
initial_cash: float
|
||
|
||
|
||
class SyncRequest(BaseModel):
|
||
symbol: str
|
||
start: str | None = None # YYYYMMDD
|
||
end: str | None = None
|
||
source: str = "auto" # auto | tushare | akshare
|
||
force: bool = False # True => 忽略缓存重新拉取
|
||
|
||
|
||
# ---------- Event Backtest(自然语言事件回测) ----------
|
||
class EventBacktestSpec(BaseModel):
|
||
"""事件回测参数:entry 条件在信号日 D 收盘确认 -> D+1 买入 -> 持有 N 日卖出。"""
|
||
entry: ScreenConditions
|
||
entry_timing: Literal["next_open", "next_close"] = "next_open" # 次日开盘/收盘买入
|
||
holding_days: int = Field(default=3, ge=1, le=250) # 买入后再持有 N 个交易日
|
||
exit_timing: Literal["close", "open"] = "close" # 到期按收盘/开盘卖出
|
||
|
||
|
||
class EventBacktestRequest(BaseModel):
|
||
text: str = Field(min_length=2, max_length=500)
|
||
spec: EventBacktestSpec | None = None # 直传则跳过 LLM 解析(调参重跑)
|
||
ts_code: str | None = None # 指定则只回测该股;空则全市场
|
||
start: datetime | None = None
|
||
end: datetime | None = None
|
||
|
||
|
||
class EventTradeOut(BaseModel):
|
||
ts_code: str
|
||
name: str | None = None
|
||
entry_date: datetime
|
||
entry_price: float
|
||
exit_date: datetime
|
||
exit_price: float
|
||
ret_pct: float # 区间收益率 %(复权校正)
|
||
|
||
|
||
class EventYearStatOut(BaseModel):
|
||
year: int
|
||
samples: int
|
||
mean_pct: float
|
||
median_pct: float
|
||
win_rate: float
|
||
|
||
|
||
class EventStatsOut(BaseModel):
|
||
samples: int
|
||
stocks: int
|
||
mean_pct: float
|
||
median_pct: float
|
||
win_rate: float # %
|
||
std_pct: float = 0.0
|
||
p10_pct: float = 0.0
|
||
p25_pct: float = 0.0
|
||
p75_pct: float = 0.0
|
||
p90_pct: float = 0.0
|
||
max_pct: float = 0.0
|
||
min_pct: float = 0.0
|
||
by_year: list[EventYearStatOut] = Field(default_factory=list)
|
||
|
||
|
||
class EventBacktestResponse(BaseModel):
|
||
text: str
|
||
spec: EventBacktestSpec
|
||
universe: str # "all" 或 ts_code
|
||
start: datetime
|
||
end: datetime
|
||
stats: EventStatsOut
|
||
trades: list[EventTradeOut] = Field(default_factory=list) # 最好+最差样本(各 100)
|
||
total: int
|
||
|
||
|
||
class SyncResponse(BaseModel):
|
||
symbol: str
|
||
bars: int
|
||
source: str
|
||
|
||
|
||
# ---------- Screener(智能选股) ----------
|
||
Op = Literal["gt", "ge", "lt", "le", "between"]
|
||
|
||
|
||
class IndicatorCondition(BaseModel):
|
||
"""技术指标条件(在最近 lookback 个交易日窗口内判定)。
|
||
|
||
indicator 白名单见 screener/llm.py 的 SYSTEM_PROMPT(kdj_j / rsi / macd_dif…)。
|
||
设置 value_indicator 时为指标间比较(如 DIF > DEA、close < boll_lower),value 填 0 占位。
|
||
"""
|
||
indicator: str
|
||
params: dict[str, float] = Field(default_factory=dict) # 如 {"n": 9, "m1": 3, "m2": 3}
|
||
op: Op
|
||
value: float
|
||
value2: float | None = None # between 上界
|
||
value_indicator: str | None = None # 比较对象为另一指标(同白名单)时使用
|
||
value_params: dict[str, float] = Field(default_factory=dict) # 比较对象指标参数(默认沿用 params/默认值)
|
||
lookback: int = 1 # 检查最近 N 个交易日
|
||
match: Literal["all", "any"] = "all" # all=连续满足;any=任一满足
|
||
|
||
|
||
class SnapshotCondition(BaseModel):
|
||
"""每日快照条件(最新交易日截面)。市值单位亿元;换手率为百分数(5 表示 5%)。"""
|
||
field: str # total_mv|circ_mv|pe_ttm|pb|turnover_rate|close
|
||
op: Op
|
||
value: float
|
||
value2: float | None = None
|
||
|
||
|
||
class ScreenConditions(BaseModel):
|
||
indicator: list[IndicatorCondition] = Field(default_factory=list)
|
||
snapshot: list[SnapshotCondition] = Field(default_factory=list)
|
||
exclude_st: bool = True
|
||
exclude_delisted: bool = True
|
||
exclude_bj: bool = True # 排除北交所
|
||
|
||
|
||
class ScreenerRunRequest(BaseModel):
|
||
text: str = Field(min_length=2, max_length=500)
|
||
# 直传条件则跳过 LLM 解析(预留给"微调再跑")
|
||
conditions: ScreenConditions | None = None
|
||
|
||
|
||
class ScreenerItemOut(BaseModel):
|
||
ts_code: str
|
||
name: str
|
||
close: float | None = None # 最新收盘价(元)
|
||
pct_chg: float | None = None # 日涨跌幅 %
|
||
total_mv: float | None = None # 总市值(亿元)
|
||
circ_mv: float | None = None # 流通市值(亿元)
|
||
pe_ttm: float | None = None
|
||
pb: float | None = None
|
||
turnover_rate: float | None = None
|
||
indicators: dict[str, float | None] = Field(default_factory=dict) # 引用到的指标最新值
|
||
|
||
|
||
class ScreenerRunResponse(BaseModel):
|
||
conditions: ScreenConditions
|
||
trade_date: datetime | None # 数据基准交易日
|
||
total: int # 命中总数(items 可能被截断)
|
||
items: list[ScreenerItemOut]
|
||
indicator_labels: dict[str, str] = Field(default_factory=dict) # "kdj_j" -> "KDJ J(9,3,3)"
|
||
|
||
|
||
class ScreenerSyncRequest(BaseModel):
|
||
days: int = Field(default=90, ge=10, le=250) # 同步最近 N 个交易日
|
||
force: bool = False # True => 全量重拉(幂等)
|
||
|
||
|
||
class ScreenerSyncStatus(BaseModel):
|
||
running: bool
|
||
step: str | None = None # 进行中步骤文案
|
||
total_days: int = 0
|
||
done_days: int = 0
|
||
error: str | None = None
|
||
ready: bool = False # 至少 1 个交易日数据可用于选股
|
||
last_trade_date: datetime | None = None
|
||
last_synced_at: datetime | None = None
|
||
stats: dict[str, int] = Field(default_factory=dict) # stocks/daily_rows/snapshot_rows/dates
|
||
|
||
|
||
# ---------- 个股详情预览(选股结果点入,全屏同花顺/通达信式) ----------
|
||
class PreviewInfoOut(BaseModel):
|
||
ts_code: str
|
||
symbol: str
|
||
name: str
|
||
industry: str | None = None
|
||
area: str | None = None
|
||
market: str | None = None # 主板/创业板/科创板/北交所
|
||
list_date: str | None = None
|
||
trade_date: datetime | None = None # 行情/信息卡数据基准交易日
|
||
open: float | None = None
|
||
high: float | None = None
|
||
low: float | None = None
|
||
close: float | None = None
|
||
pre_close: float | None = None
|
||
pct_chg: float | None = None # 日涨跌幅 %
|
||
volume_hand: float | None = None # 成交量(手)
|
||
amount_yi: float | None = None # 成交额(亿元)
|
||
turnover_rate: float | None = None # 换手率 %
|
||
pe_ttm: float | None = None
|
||
pb: float | None = None
|
||
total_mv: float | None = None # 总市值(亿元)
|
||
circ_mv: float | None = None # 流通市值(亿元)
|
||
|
||
|
||
class PreviewResponse(BaseModel):
|
||
ts_code: str
|
||
symbol: str
|
||
source: str # bfq|qfq|hfq=实际复权口径(本地 adj_factor 换算) | market=近段未复权兜底
|
||
info: PreviewInfoOut
|
||
candles: list[CandleOut]
|
||
indicators: dict[str, dict[str, list[float | None]]] = Field(default_factory=dict)
|
||
# indicators 形如 {"ma": {"ma5": [...], ...}, "macd": {"dif": ...}, "kdj": {...}, "rsi": {...}, "boll": {...}}
|
||
has_more: bool = False # 返回窗口之前是否还有更早历史(前端向左滚动翻页用)
|
||
|
||
|
||
# ---------- 公司简介(tushare stock_company,详情页按需懒加载) ----------
|
||
class StockCompanyOut(BaseModel):
|
||
ts_code: str
|
||
com_name: str | None = None # 公司全称
|
||
com_id: str | None = None # 统一社会信用代码
|
||
chairman: str | None = None # 法人代表
|
||
manager: str | None = None # 总经理
|
||
secretary: str | None = None # 董秘
|
||
reg_capital: float | None = None # 注册资本(万元)
|
||
setup_date: str | None = None # 注册日期 YYYYMMDD(展示层换算)
|
||
province: str | None = None # 所在省份
|
||
city: str | None = None # 所在城市
|
||
introduction: str | None = None # 公司介绍
|
||
website: str | None = None # 公司主页
|
||
email: str | None = None # 电子邮件
|
||
office: str | None = None # 办公地址
|
||
employees: int | None = None # 员工人数
|
||
main_business: str | None = None # 主要业务及产品
|
||
business_scope: str | None = None # 经营范围
|
||
|
||
|
||
# ---------- 财务数据(fina_indicator + 三大报表关键值,详情页按需懒加载) ----------
|
||
class StockFinanceRecordOut(BaseModel):
|
||
"""一行 = 一个报告期。金额单位元(展示层换算亿/万),比率与同比为百分数。"""
|
||
end_date: str # 报告期 YYYYMMDD
|
||
ann_date: str | None = None # 公告日 YYYYMMDD
|
||
eps: float | None = None # 基本每股收益(元)
|
||
bps: float | None = None # 每股净资产(元)
|
||
ocfps: float | None = None # 每股经营现金流净额(元)
|
||
roe: float | None = None # 净资产收益率 %
|
||
roe_dt: float | None = None # 扣非净资产收益率 %
|
||
grossprofit_margin: float | None = None # 销售毛利率 %
|
||
netprofit_margin: float | None = None # 销售净利率 %
|
||
debt_to_assets: float | None = None # 资产负债率 %
|
||
or_yoy: float | None = None # 营业收入同比 %
|
||
netprofit_yoy: float | None = None # 归母净利润同比 %
|
||
dt_netprofit_yoy: float | None = None # 扣非净利润同比 %
|
||
profit_dedt: float | None = None # 扣非净利润(元)
|
||
rd_exp: float | None = None # 研发投入(元)
|
||
total_revenue: float | None = None # 营业总收入(元)
|
||
operate_profit: float | None = None # 营业利润(元)
|
||
n_income_attr_p: float | None = None # 归母净利润(元)
|
||
total_assets: float | None = None # 总资产(元)
|
||
total_hldr_eqy: float | None = None # 归母股东权益(元)
|
||
n_cashflow_act: float | None = None # 经营现金流净额(元)
|
||
|
||
|
||
class StockFinanceOut(BaseModel):
|
||
ts_code: str
|
||
records: list[StockFinanceRecordOut] # 按报告期倒序(records[0] = 最新报告期)
|
||
|
||
|
||
# ---------- 分红送股(tushare dividend,详情页按需懒加载) ----------
|
||
class StockDividendRecordOut(BaseModel):
|
||
end_date: str | None = None # 分红年度 YYYYMMDD
|
||
ann_date: str | None = None # 预案公告日
|
||
div_proc: str | None = None # 实施进度(预案/实施)
|
||
stk_div: float | None = None # 每股送转
|
||
stk_bo_rate: float | None = None # 每股送股比例
|
||
stk_co_rate: float | None = None # 每股转增比例
|
||
cash_div: float | None = None # 每股分红(税后,元)
|
||
cash_div_tax: float | None = None # 每股分红(税前,元)
|
||
base_share: float | None = None # 基准股本(万股)
|
||
record_date: str | None = None # 股权登记日
|
||
ex_date: str | None = None # 除权除息日(K线标记锚点)
|
||
pay_date: str | None = None # 派息日
|
||
div_listdate: str | None = None # 红股上市日
|
||
imp_ann_date: str | None = None # 实施公告日
|
||
|
||
|
||
class StockDividendOut(BaseModel):
|
||
ts_code: str
|
||
records: list[StockDividendRecordOut] # 按分红年度倒序;空列表 = 确认无分红
|
||
|
||
|
||
# ---------- 参考数据(tushare 参考数据版块,详情页按需懒加载) ----------
|
||
class StockReferenceOut(BaseModel):
|
||
"""rows_json 快照直出:records 为清洗后的 tushare 原始行,字段随 kind 而异。"""
|
||
ts_code: str
|
||
kind: str # 白名单见 app/data/reference.py REFERENCE_KINDS
|
||
records: list[dict[str, str | float | None]] = Field(default_factory=list)
|
||
|
||
|
||
# ---------- 打板专题(主页,同花顺口径) ----------
|
||
class LimitStockOut(BaseModel):
|
||
"""涨跌停榜单行:三池共用,涨停池字段最全(炸板/跌停池仅价格类字段)。"""
|
||
ts_code: str
|
||
name: str | None = None
|
||
price: float | None = None # 收盘价(元)
|
||
pct_chg: float | None = None # 涨跌幅 %
|
||
tag: str | None = None # 涨停标签:首板 / 2天2板(仅涨停池)
|
||
status: str | None = None # 涨停状态:一字板 / 换手板 / N连板(仅涨停池)
|
||
lu_desc: str | None = None # 涨停原因(仅涨停池)
|
||
open_num: float | None = None # 打开次数
|
||
limit_amount_yi: float | None = None # 封单额(亿元,仅涨停池)
|
||
turnover_yi: float | None = None # 成交额(亿元,仅涨停池)
|
||
first_lu_time: str | None = None # 首次涨停时间
|
||
last_lu_time: str | None = None # 最后涨停时间(仅炸板池)
|
||
limit_up_suc_rate: float | None = None # 近一年封板率 %(仅涨停池)
|
||
|
||
|
||
class LimitLadderOut(BaseModel):
|
||
ts_code: str
|
||
name: str | None = None
|
||
nums: int # 连板数
|
||
|
||
|
||
class LimitBlockOut(BaseModel):
|
||
name: str | None = None # 同花顺概念板块名
|
||
days: float | None = None # 板块连涨天数
|
||
up_stat: str | None = None # 如「6天3板」
|
||
cons_nums: float | None = None # 连板家数
|
||
up_nums: float | None = None # 涨停家数
|
||
pct_chg: float | None = None # 板块涨跌 %
|
||
|
||
|
||
class LimitSummaryOut(BaseModel):
|
||
up_count: int = 0
|
||
broken_count: int = 0
|
||
down_count: int = 0
|
||
first_board_count: int = 0
|
||
max_ladder: LimitLadderOut | None = None
|
||
ladder_dist: list[dict[str, int]] = Field(default_factory=list) # [{nums, count}] 升序(2板起)
|
||
|
||
|
||
class LimitBoardResponse(BaseModel):
|
||
trade_date: str # YYYY-MM-DD
|
||
updated_at: str
|
||
summary: LimitSummaryOut
|
||
up: list[LimitStockOut] = Field(default_factory=list) # 涨停池(按封单额降序)
|
||
broken: list[LimitStockOut] = Field(default_factory=list) # 炸板池
|
||
down: list[LimitStockOut] = Field(default_factory=list) # 跌停池
|
||
ladder: list[LimitLadderOut] = Field(default_factory=list) # 连板天梯(连板数降序)
|
||
blocks: list[LimitBlockOut] = Field(default_factory=list) # 涨停最强板块
|
||
errors: list[str] = Field(default_factory=list)
|
||
|
||
|
||
# ---------- 概念板块(THS:ths_index 列表 + ths_daily 行情 + ths_member 成分) ----------
|
||
class ThsBoardOut(BaseModel):
|
||
ts_code: str # 885835.TI / 700001.TI
|
||
name: str | None = None
|
||
type: str | None = None # N概念 I行业 TH主题 S特色 R地域 BB宽基 ST风格
|
||
count: float | None = None # 成分个数
|
||
list_date: str | None = None # YYYYMMDD
|
||
close: float | None = None # 板块指数收盘(当日快照)
|
||
pct_change: float | None = None # 涨跌幅 %
|
||
vol: float | None = None # 成交量(手)
|
||
turnover_rate: float | None = None # 换手率 %
|
||
|
||
|
||
class ThsBoardListResponse(BaseModel):
|
||
trade_date: str | None = None
|
||
updated_at: str | None = None
|
||
boards: list[ThsBoardOut] = Field(default_factory=list)
|
||
errors: list[str] = Field(default_factory=list)
|
||
|
||
|
||
class ThsMemberOut(BaseModel):
|
||
con_code: str # 成分股代码 000016.SZ
|
||
con_name: str | None = None
|
||
close: float | None = None # 现价(candles 最新,北交所等无底座为空)
|
||
pct_chg: float | None = None # 涨跌幅 %(最新收盘 / 前收 - 1)
|
||
|
||
|
||
class ThsBoardMembersResponse(BaseModel):
|
||
code: str
|
||
name: str | None = None
|
||
members: list[ThsMemberOut] = Field(default_factory=list)
|
||
|
||
|
||
# ---------- Auth ----------
|
||
class LoginRequest(BaseModel):
|
||
username: str = Field(min_length=1, max_length=64)
|
||
password: str = Field(min_length=1, max_length=1024)
|
||
|
||
|
||
class CurrentUserOut(BaseModel):
|
||
id: int
|
||
username: str
|
||
|
||
model_config = {"from_attributes": True}
|
||
|
||
|
||
class LoginResponse(BaseModel):
|
||
user: CurrentUserOut
|
||
expires_at: datetime
|
||
|
||
|
||
# ---------- 股票列表(全市场浏览) ----------
|
||
class StockListItemOut(BaseModel):
|
||
ts_code: str
|
||
symbol: str
|
||
name: str
|
||
industry: str | None = None
|
||
market: str | None = None
|
||
close: float | None = None # 最新收盘(candles 未复权)
|
||
prev_close: float | None = None
|
||
pct_chg: float | None = None # 最新两根日线计算
|
||
last_ts: datetime | None = None
|
||
turnover_rate: float | None = None # 换手率 %(daily_snapshot)
|
||
pe_ttm: float | None = None # 市盈率 TTM
|
||
pb: float | None = None # 市净率
|
||
total_mv: float | None = None # 总市值(亿元)
|
||
circ_mv: float | None = None # 流通市值(亿元)
|
||
watched: bool = False # 是否自选(当前用户)
|
||
held: bool = False # 是否持仓(当前用户)
|
||
|
||
|
||
class StockListResponse(BaseModel):
|
||
total: int
|
||
items: list[StockListItemOut]
|
||
|
||
|
||
# ---------- 看股页筛选项 ----------
|
||
class FacetItemOut(BaseModel):
|
||
name: str
|
||
count: int
|
||
|
||
|
||
class StockFacetsResponse(BaseModel):
|
||
industries: list[FacetItemOut] = Field(default_factory=list)
|
||
areas: list[FacetItemOut] = Field(default_factory=list)
|
||
|
||
|
||
# ---------- ETF 列表(全市场浏览;行情走 candles 底座,规模走东财快照) ----------
|
||
class EtfListItemOut(BaseModel):
|
||
ts_code: str # 510300.SH
|
||
symbol: str # 510300
|
||
name: str
|
||
exchange: str # SH/SZ
|
||
close: float | None = None # 最新收盘(candles 未复权)
|
||
prev_close: float | None = None
|
||
pct_chg: float | None = None # 最新两根日线计算
|
||
amount: float | None = None # 最新成交额(亿元,candles 最新 bar)
|
||
last_ts: datetime | None = None
|
||
turnover_rate: float | None = None # 换手率 %(东财快照)
|
||
total_mv: float | None = None # 总市值(亿元,东财快照)
|
||
circ_mv: float | None = None # 流通市值(亿元)
|
||
list_date: str | None = None # 上市日 YYYYMMDD(首根K线日)
|
||
watched: bool = False # 是否自选(当前用户)
|
||
|
||
|
||
class EtfListResponse(BaseModel):
|
||
total: int
|
||
items: list[EtfListItemOut]
|
||
|
||
|
||
class EtfSyncRequest(BaseModel):
|
||
full: bool = False # true = 忽略增量起点全量重拉(修数据用)
|
||
|
||
|
||
class EtfSyncStatus(BaseModel):
|
||
running: bool
|
||
step: str | None = None
|
||
total: int = 0 # 本次需拉 K 线的 ETF 数
|
||
done: int = 0
|
||
error: str | None = None
|
||
ready: bool = False # etf_basic 非空且已有日线
|
||
last_trade_date: datetime | None = None
|
||
last_synced_at: datetime | None = None
|
||
stats: dict[str, int] = Field(default_factory=dict) # etfs
|
||
|
||
|
||
# ---------- 用户偏好 / 自选股 / 提问历史 ----------
|
||
class PreferencesOut(BaseModel):
|
||
prefs: dict[str, object] = Field(default_factory=dict) # key -> JSON 值
|
||
|
||
|
||
class PreferencesUpdate(BaseModel):
|
||
prefs: dict[str, object] # 部分更新:只覆盖出现的 key(值为 null 表示删除)
|
||
|
||
|
||
class WatchlistOp(BaseModel):
|
||
ts_code: str = Field(min_length=6, max_length=12)
|
||
|
||
|
||
class HoldingOp(BaseModel):
|
||
ts_code: str = Field(min_length=6, max_length=12)
|
||
|
||
|
||
class ScreenerQueryOut(BaseModel):
|
||
id: int
|
||
text: str
|
||
conditions: ScreenConditions | None = None
|
||
hit_count: int | None = None
|
||
created_at: datetime
|
||
|
||
model_config = {"from_attributes": True}
|
||
|
||
|
||
class ScreenerQueryListResponse(BaseModel):
|
||
items: list[ScreenerQueryOut]
|
||
|
||
|
||
# ---------- 交割单(个人实盘买卖点) ----------
|
||
class UserTradeOut(BaseModel):
|
||
id: int
|
||
ts_code: str
|
||
name: str | None = None
|
||
trade_date: date # 成交日期(ISO YYYY-MM-DD)
|
||
direction: str # buy | sell
|
||
price: float | None = None # 成交价(券商原始价,不复权)
|
||
qty: float # 股数
|
||
amount: float | None = None
|
||
fee: float | None = None
|
||
|
||
|
||
class TradesImportResponse(BaseModel):
|
||
inserted: int # 新入库成交笔数
|
||
skipped_dup: int # 与库内完全一致(重复上传同文件)跳过
|
||
skipped_other: int # 非买卖行(转账/配号/利息等)
|
||
stocks: int # 涉及股票数
|
||
bad: list[str] = Field(default_factory=list) # 解析失败样例(前 5 条)
|
||
sample: list[UserTradeOut] = Field(default_factory=list) # 本次入库的前几笔(核对用)
|
||
|
||
|
||
class TradesClearResponse(BaseModel):
|
||
deleted: int
|
||
|
||
|
||
# ---------- 大盘总览(主页) ----------
|
||
class IndexQuoteOut(BaseModel):
|
||
code: str # 000001.SH / HKTECH / DJI
|
||
name: str # 上证指数 / 恒生科技 / 道琼斯
|
||
region: str # cn | hk | us(前端分组展示)
|
||
close: float | None = None
|
||
change: float | None = None
|
||
pct_chg: float | None = None
|
||
trade_date: date | None = None # 行情归属交易日(实时时为当天)
|
||
spark: list[float] = [] # 近 N 日收盘(旧 -> 新),迷你走势用
|
||
spark_dates: list[str] = [] # 与 spark 对齐的交易日(YYYYMMDD),hover 提示用
|
||
realtime: bool = False # True=盘中实时(腾讯),False=最近收盘(tushare EOD)
|
||
|
||
|
||
class MarketStatsOut(BaseModel):
|
||
"""沪深两市汇总(daily_info:沪 SH_MARKET(主板A+科创+B) + 深 SZ_MARKET,不含基金/北交所,亿元)。"""
|
||
trade_date: date | None = None
|
||
total_mv: float | None = None # 总市值(亿元)
|
||
float_mv: float | None = None # 流通市值(亿元)
|
||
amount: float | None = None # 两市成交额(亿元,EOD)
|
||
turnover: float | None = None # 换手率 %(沪市口径)
|
||
amount_today: float | None = None # 当日实时两市成交额(亿元,腾讯;收盘后为当日终值)
|
||
|
||
|
||
class AmountBarOut(BaseModel):
|
||
"""两市成交额历史的一根柱(亿元)。"""
|
||
date: date
|
||
amount: float
|
||
intraday: bool = False # True=当日实时口径(尚未并入 EOD)
|
||
|
||
|
||
class MarketOverviewResponse(BaseModel):
|
||
updated_at: datetime
|
||
indexes: list[IndexQuoteOut] = []
|
||
stats: MarketStatsOut | None = None
|
||
amount_history: list[AmountBarOut] = [] # 近 N 交易日两市成交额(旧 -> 新,末根可能盘中)
|
||
errors: list[str] = [] # 部分来源失败的说明(透明但不阻塞展示)
|
||
|
||
|
||
# ---------- 指数专题(国际指数卡片 + 指数详情) ----------
|
||
|
||
class GlobalIndexQuoteOut(BaseModel):
|
||
"""国际指数卡片(index_global 最新收盘 + 45 日 spark)。"""
|
||
code: str # DJI / SPX / HSI ...
|
||
name: str # 道琼斯工业指数
|
||
region: str # americas | europe | asia(前端分组)
|
||
country: str # 美国 / 英国 / 日本 ...
|
||
close: float | None = None
|
||
change: float | None = None
|
||
pct_chg: float | None = None
|
||
trade_date: date | None = None
|
||
spark: list[float] = []
|
||
spark_dates: list[str] = []
|
||
|
||
|
||
class GlobalIndexListResponse(BaseModel):
|
||
updated_at: datetime
|
||
items: list[GlobalIndexQuoteOut] = []
|
||
errors: list[str] = []
|
||
|
||
|
||
class IndexBasicOut(BaseModel):
|
||
"""指数基本信息:国内 index_basic(市场/发布方/基期基点),国际由静态表合成(国家/地区)。"""
|
||
ts_code: str
|
||
name: str
|
||
market: str | None = None # SSE / CSI / SZSE ...
|
||
publisher: str | None = None # 中证指数 / 上交所
|
||
category: str | None = None # 规模指数 / 综合指数
|
||
base_date: date | None = None # 基期
|
||
base_point: float | None = None # 基点
|
||
list_date: date | None = None # 发布日期
|
||
country: str | None = None # 国际指数:国家/地区
|
||
region: str | None = None # 国际指数:americas/europe/asia
|
||
|
||
|
||
class IndexValuationPointOut(BaseModel):
|
||
"""大盘指数每日指标的一日(index_dailybasic,仅部分国内指数有数据)。"""
|
||
trade_date: date
|
||
pe: float | None = None
|
||
pe_ttm: float | None = None
|
||
pb: float | None = None
|
||
turnover_rate: float | None = None # 换手率 %
|
||
total_mv: float | None = None # 总市值(元;tushare 文档标注万元,实测按元才对得上量级)
|
||
float_mv: float | None = None # 流通市值(元)
|
||
|
||
|
||
class IndexQuoteBriefOut(BaseModel):
|
||
"""详情页头部最新行情(收盘口径)。"""
|
||
close: float | None = None
|
||
change: float | None = None
|
||
pct_chg: float | None = None
|
||
open: float | None = None
|
||
high: float | None = None
|
||
low: float | None = None
|
||
pre_close: float | None = None
|
||
trade_date: date | None = None
|
||
spark: list[float] = []
|
||
spark_dates: list[str] = []
|
||
|
||
|
||
class IndexDetailResponse(BaseModel):
|
||
code: str
|
||
name: str
|
||
region: str # cn | americas | europe | asia
|
||
quote: IndexQuoteBriefOut
|
||
basic: IndexBasicOut | None = None
|
||
valuation: IndexValuationPointOut | None = None # 最新一日(接口不覆盖时 None)
|
||
valuation_history: list[IndexValuationPointOut] = [] # 近 N 日(PE 走势小图)
|
||
|
||
|
||
class IndexWeightItemOut(BaseModel):
|
||
con_code: str # 成分股 000001.SZ
|
||
name: str | None = None # 平安银行(本地 stock_basic 回填,缺则 None)
|
||
weight: float # 权重 %
|
||
|
||
|
||
class IndexWeightsResponse(BaseModel):
|
||
trade_date: date
|
||
total: int # 成分股总数(返回 items 为按权重降序的子集)
|
||
items: list[IndexWeightItemOut] = []
|
||
|