330 lines
10 KiB
Python
330 lines
10 KiB
Python
from __future__ import annotations
|
|
from datetime import datetime
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
|
|
def _number(value: Any, kind: type = float) -> Any:
|
|
try:
|
|
return kind(value or 0)
|
|
except (TypeError, ValueError):
|
|
return kind()
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class OrderItem:
|
|
"""由 QMT 委托明细解析得到的标准订单记录。"""
|
|
|
|
id: str
|
|
code: str
|
|
side: str
|
|
remark: str
|
|
status: str
|
|
created_at: datetime | None
|
|
volume: int
|
|
local_order_id: str = ""
|
|
traded_volume: int = 0
|
|
remaining_volume: int = 0
|
|
exchange_id: str = ""
|
|
name: str = ""
|
|
price: float = 0.0
|
|
trade_price: float = 0.0
|
|
trade_amount: float = 0.0
|
|
|
|
@classmethod
|
|
def from_trade_detail(cls, data: dict[str, Any]) -> "OrderItem":
|
|
"""从 TradeDetailData 的 QMT 原始字段创建订单。"""
|
|
instrument_id = str(data.get("m_strInstrumentID") or "")
|
|
exchange_id = str(data.get("m_strExchangeID") or "")
|
|
code = (
|
|
f"{instrument_id}.{exchange_id}"
|
|
if instrument_id and exchange_id
|
|
else instrument_id
|
|
)
|
|
remaining_volume = _number(data.get("m_nVolumeTotal"), int)
|
|
traded_volume = _number(data.get("m_nVolumeTraded"), int)
|
|
remark = str(data.get("m_strRemark") or "")
|
|
return cls(
|
|
id=str(data.get("m_strOrderSysID") or ""),
|
|
code=code,
|
|
side={"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"}.get(
|
|
str(data.get("m_nOffsetFlag")), ""
|
|
),
|
|
remark=remark,
|
|
status=str(data.get("m_nOrderStatus") or ""),
|
|
created_at=_trade_datetime(data),
|
|
volume=remaining_volume + traded_volume,
|
|
local_order_id=remark.split("|", 1)[0] if remark else "",
|
|
traded_volume=traded_volume,
|
|
remaining_volume=remaining_volume,
|
|
exchange_id=exchange_id,
|
|
name=str(data.get("m_strInstrumentName") or ""),
|
|
price=_number(data.get("m_dPrice")),
|
|
trade_price=_number(data.get("m_dTradePrice")),
|
|
trade_amount=_number(data.get("m_dTradeAmount")),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class DealItem:
|
|
"""由 QMT Deal 成交对象解析得到的标准成交记录。"""
|
|
|
|
id: str
|
|
order_id: str
|
|
code: str
|
|
side: str
|
|
remark: str
|
|
traded_at: datetime | None
|
|
volume: int
|
|
price: float
|
|
amount: float
|
|
local_order_id: str = ""
|
|
order_ref: str = ""
|
|
exchange_id: str = ""
|
|
name: str = ""
|
|
account_id: str = ""
|
|
commission: float = 0.0
|
|
trade_date: str = ""
|
|
trade_time: str = ""
|
|
|
|
@classmethod
|
|
def from_trade_detail(cls, data: dict[str, Any]) -> "DealItem":
|
|
"""从 TradeDetailData 的 QMT Deal 原始字段创建成交记录。"""
|
|
instrument_id = str(data.get("m_strInstrumentID") or "")
|
|
exchange_id = str(data.get("m_strExchangeID") or "")
|
|
code = (
|
|
f"{instrument_id}.{exchange_id}"
|
|
if instrument_id and exchange_id
|
|
else instrument_id
|
|
)
|
|
remark = str(data.get("m_strRemark") or "")
|
|
trade_date = str(data.get("m_strTradeDate") or "")
|
|
trade_time = str(data.get("m_strTradeTime") or "")
|
|
return cls(
|
|
id=str(data.get("m_strTradeID") or ""),
|
|
order_id=str(data.get("m_strOrderSysID") or ""),
|
|
code=code,
|
|
side={"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"}.get(
|
|
str(data.get("m_nOffsetFlag")), ""
|
|
),
|
|
remark=remark,
|
|
traded_at=_parse_datetime(trade_date, trade_time),
|
|
volume=_number(data.get("m_nVolume"), int),
|
|
price=_number(data.get("m_dPrice")),
|
|
amount=_number(data.get("m_dTradeAmount")),
|
|
local_order_id=remark.split("|", 1)[0] if remark else "",
|
|
order_ref=str(data.get("m_strOrderRef") or ""),
|
|
exchange_id=exchange_id,
|
|
name=str(data.get("m_strInstrumentName") or ""),
|
|
account_id=str(data.get("m_strAccountID") or ""),
|
|
commission=_number(
|
|
data.get(
|
|
"m_dCommission",
|
|
data.get("m_dComission", data.get("m_dComssion")),
|
|
)
|
|
),
|
|
trade_date=trade_date,
|
|
trade_time=trade_time,
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class PositionItem:
|
|
stock_code: str = ""
|
|
stock_name: str = ""
|
|
trade_id: str = ""
|
|
direction: Any = None
|
|
volume: int = 0
|
|
open_cost: float = 0.0
|
|
open_price: float = 0.0
|
|
float_profit: float = 0.0
|
|
market_value: float = 0.0
|
|
stock_holder: str = ""
|
|
frozen_volume: int = 0
|
|
can_use_volume: int = 0
|
|
on_road_volume: int = 0
|
|
yesterday_volume: int = 0
|
|
last_price: float = 0.0
|
|
profit_rate: float = 0.0
|
|
future_trade_type: Any = None
|
|
expire_date: str = ""
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any], code: str = "") -> "PositionItem":
|
|
return cls(
|
|
stock_code=str(data.get("StockCode") or code),
|
|
stock_name=str(data.get("StockName") or ""),
|
|
trade_id=str(data.get("TradeID") or ""),
|
|
open_cost=_number(data.get("OpenCost")),
|
|
direction=data.get("Direction"),
|
|
volume=_number(data.get("Volume"), int),
|
|
open_price=_number(data.get("OpenPrice")),
|
|
float_profit=_number(data.get("FloatProfit")),
|
|
market_value=_number(data.get("MarketValue")),
|
|
stock_holder=str(data.get("StockHolder") or ""),
|
|
frozen_volume=_number(data.get("FrozenVolume"), int),
|
|
can_use_volume=_number(data.get("CanUseVolume"), int),
|
|
on_road_volume=_number(data.get("OnRoadVolume"), int),
|
|
yesterday_volume=_number(data.get("YesterdayVolume"), int),
|
|
last_price=_number(data.get("LastPrice")),
|
|
profit_rate=_number(data.get("ProfitRate")),
|
|
future_trade_type=data.get("FutureTradeType"),
|
|
expire_date=str(data.get("ExpireDate") or ""),
|
|
)
|
|
|
|
@classmethod
|
|
def from_trade_detail(cls, data: dict[str, Any]) -> "PositionItem":
|
|
"""从 TradeDetailData/Holding 的 QMT 原始字段创建持仓。"""
|
|
return cls(
|
|
stock_code=str(data.get("StockCode") or ""),
|
|
stock_name=str(data.get("StockName") or ""),
|
|
trade_id=str(data.get("TradeID") or ""),
|
|
direction=data.get("Direction"),
|
|
volume=_number(data.get("Volume"), int),
|
|
open_cost=_number(data.get("OpenCost")),
|
|
open_price=_number(data.get("OpenPrice")),
|
|
float_profit=_number(data.get("FloatProfit")),
|
|
market_value=_number(data.get("MarketValue")),
|
|
stock_holder=str(data.get("StockHolder") or ""),
|
|
frozen_volume=_number(data.get("FrozenVolume"), int),
|
|
can_use_volume=_number(data.get("CanUseVolume"), int),
|
|
on_road_volume=_number(data.get("OnRoadVolume"), int),
|
|
yesterday_volume=_number(data.get("YesterdayVolume"), int),
|
|
last_price=_number(data.get("LastPrice")),
|
|
profit_rate=_number(data.get("ProfitRate")),
|
|
future_trade_type=data.get("FutureTradeType"),
|
|
expire_date=str(data.get("ExpireDate") or ""),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Assets:
|
|
total: float = 0.0
|
|
available: float = 0.0
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> "Assets":
|
|
"""兼容新版 QMT 原始资金字段及旧版简化字段。"""
|
|
return cls(
|
|
total=_number(data.get("m_dBalance", data.get("total"))),
|
|
available=_number(data.get("m_dAvailable", data.get("available"))),
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Portfolio:
|
|
assets: Assets
|
|
positions: dict[str, PositionItem]
|
|
orders: list[OrderItem]
|
|
|
|
|
|
def _trade_datetime(data: dict[str, Any]) -> datetime | None:
|
|
return _parse_datetime(
|
|
str(data.get("m_strInsertDate") or ""),
|
|
str(data.get("m_strInsertTime") or ""),
|
|
)
|
|
|
|
|
|
def _parse_datetime(date: str, clock: str) -> datetime | None:
|
|
clock = clock.replace(":", "").zfill(6)
|
|
try:
|
|
return datetime.strptime(date + clock, "%Y%m%d%H%M%S")
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Tick:
|
|
last_price: float = 0.0
|
|
last_close: float = 0.0
|
|
raw: dict[str, Any] = field(default_factory=dict)
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Any) -> "Tick":
|
|
if not isinstance(data, dict):
|
|
return cls()
|
|
return cls(
|
|
last_price=_number(
|
|
data.get("lastPrice", data.get("last_price", data.get("LastPrice")))
|
|
),
|
|
last_close=_number(
|
|
data.get("lastClose", data.get("last_close", data.get("LastClose")))
|
|
),
|
|
raw=data,
|
|
)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class HistoryDataRequest:
|
|
length: int = 10
|
|
period: str = ""
|
|
field: str = ""
|
|
dividend_type: int = 0
|
|
skip_paused: bool = True
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class MarketDataRequest:
|
|
fields: list[str] = field(default_factory=list)
|
|
stocks: list[str] = field(default_factory=list)
|
|
start_time: str = ""
|
|
end_time: str = ""
|
|
period: str = ""
|
|
dividend_type: str = ""
|
|
count: int = 0
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class FinancialDataRequest:
|
|
tabname: str = ""
|
|
colname: str = ""
|
|
market: str = ""
|
|
code: str = ""
|
|
report_type: str = ""
|
|
barpos: int = 0
|
|
field_list: list[str] = field(default_factory=list)
|
|
stock_list: list[str] = field(default_factory=list)
|
|
start_date: str = ""
|
|
end_date: str = ""
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class FactorDataRequest:
|
|
field_list: list[str] = field(default_factory=list)
|
|
stock_list: list[str] = field(default_factory=list)
|
|
stock_code: str = ""
|
|
start_date: str = ""
|
|
end_date: str = ""
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class BSMPriceRequest:
|
|
option_type: str
|
|
object_prices: Any
|
|
strike_price: float
|
|
risk_free: float
|
|
sigma: float
|
|
days: int
|
|
dividend: float
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class BSMIVRequest:
|
|
option_type: str
|
|
object_prices: float
|
|
strike_price: float
|
|
option_price: float
|
|
risk_free: float
|
|
days: int
|
|
dividend: float
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class LocalDataRequest:
|
|
stock_code: str
|
|
start_time: str = ""
|
|
end_time: str = ""
|
|
period: str = ""
|
|
divid_type: str = ""
|
|
count: int = 0
|