"""模拟盘数据模型(immutable DTOs)。 数据类承载配置与订单/成交/拒单状态,跨模块传递时保持不可变。 费率默认值见 §6.4(Issue #3 费率参数化)。 """ from dataclasses import dataclass from enum import Enum class MatchSession(str, Enum): """撮合时点(spec §6.1)。 - NEXT_OPEN:下一根 bar 的 open(收盘型策略,安全) - CURRENT_CLOSE:当根 bar 的 close(尾盘抓涨停型,策略不得用当根 OHLC) - CALL_AUCTION:集合竞价(首版预留) """ NEXT_OPEN = "next_open" CURRENT_CLOSE = "current_close" CALL_AUCTION = "call_auction" class OrderSide(str, Enum): BUY = "buy" SELL = "sell" @dataclass(frozen=True) class AccountConfig: """账户费率/撮合参数(Issue #3 全字段可配)。""" initial_capital: float rate: float = 0.0003 # 佣金率 min_commission: float = 5.0 # 最低佣金 5 元 stamp_duty_rate: float = 0.0005 # 印花税(仅卖,2023.8.28 起 0.05%) transfer_fee_rate: float = 0.00001 # 过户费率(沪深双向,matcher 出 ×2 总额,review H3) slippage: float = 0.0 pricetick: float = 0.01 size: float = 1.0 @dataclass(frozen=True) class PaperOrder: """策略下单请求。match_session 决定撮合时点。 listing_days:上市天数(新股涨跌停特判,0=已过新股期;spec §6.2)。 current_close 契约:策略 on_bar 内不得访问当根 close/high/low,否则前瞻偏差。 """ strategy_id: str symbol: str side: OrderSide price: float volume: int is_market: bool = True match_session: MatchSession = MatchSession.NEXT_OPEN listing_days: int = 0 def __post_init__(self) -> None: if not isinstance(self.volume, int) or isinstance(self.volume, bool): raise TypeError(f"volume 必须是 int,收到 {type(self.volume).__name__}") @dataclass(frozen=True) class PaperTrade: """已成交记录(含费用拆分)。transfer_fee 为沪深双向总额(review H3,Account 不再 ×2)。""" strategy_id: str symbol: str side: OrderSide price: float volume: int commission: float stamp_duty: float transfer_fee: float bar_date: str match_session: MatchSession @dataclass(frozen=True) class PaperReject: """拒单记录。reason 枚举:limit_up_locked / limit_down_locked / limit_not_touched / volume_below_min_lot / unsupported_match_session / insufficient_cash / blocked_by_strategy=。""" strategy_id: str symbol: str reason: str bar_date: str