feat libs,sdk,trend

This commit is contained in:
2026-09-07 14:04:26 +08:00
parent d37f9edefc
commit e320de3241
30 changed files with 515 additions and 507 deletions

View File

@@ -13,118 +13,75 @@ def _number(value: Any, kind: type = float) -> Any:
@dataclass(slots=True)
class OrderItem:
"""由 QMT 委托明细解析得到的标准订单记录。"""
"""Fields match format_orders() exactly."""
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
stock_code: str = ""
order_sys_id: str = ""
ref: int = 0
order_ref: str = ""
direction: int = 0
offset_flag: int = 0
limit_price: float = 0.0
volume_total_original: int = 0
volume_traded: int = 0
volume_total: int = 0
traded_price: float = 0.0
trade_amount: float = 0.0
insert_date: str = ""
insert_time: str = ""
remark: str = ""
order_status: int = 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")),
)
@property
def side(self) -> str:
return _side(self.offset_flag)
@property
def local_order_id(self) -> str:
return self.remark.split("|", 1)[0]
@property
def created_at(self) -> datetime | None:
return _parse_datetime(self.insert_date, self.insert_time)
@dataclass(slots=True)
class DealItem:
"""Execution data from the API's fixed order-detail fields."""
"""Fields match format_deals() exactly."""
sys_order_id: str = ""
local_order_id: str = ""
code: str = ""
instrument_id: str = ""
exchange_id: str = ""
name: str = ""
offset_flag: str = ""
side: str = ""
status: str = ""
remaining_volume: int = 0
traded_volume: int = 0
order_time: int = 0
insert_date: str = ""
insert_time: str = ""
remark: str = ""
stock_code: str = ""
order_sys_id: str = ""
ref: int = 0
order_ref: str = ""
direction: int = 0
offset_flag: int = 0
price: float = 0.0
trade_price: float = 0.0
volume: int = 0
trade_amount: float = 0.0
trade_date: str = ""
trade_time: str = ""
remark: str = ""
close_profit: float = 0.0
@classmethod
def from_trade_detail(cls, data: dict[str, Any]) -> "DealItem":
instrument_id = str(data.get("m_strInstrumentID") or "")
exchange_id = str(data.get("m_strExchangeID") or "")
remark = str(data.get("m_strRemark") or "")
offset_flag = str(data.get("m_nOffsetFlag") or "")
return cls(
sys_order_id=str(data.get("m_strOrderSysID") or ""),
local_order_id=remark.split("|", 1)[0] if remark else "",
code=f"{instrument_id}.{exchange_id}" if instrument_id and exchange_id else instrument_id,
instrument_id=instrument_id,
exchange_id=exchange_id,
name=str(data.get("m_strInstrumentName") or ""),
offset_flag=offset_flag,
side={"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"}.get(offset_flag, ""),
status=str(data.get("m_nOrderStatus") or ""),
remaining_volume=_number(data.get("m_nVolumeTotal"), int),
traded_volume=_number(data.get("m_nVolumeTraded"), int),
order_time=_number(data.get("m_nOrderTime"), int),
insert_date=str(data.get("m_strInsertDate") or ""),
insert_time=str(data.get("m_strInsertTime") or ""),
remark=remark,
price=_number(data.get("m_dPrice")),
trade_price=_number(data.get("m_dTradePrice")),
trade_amount=_number(data.get("m_dTradeAmount")),
)
@property
def side(self) -> str:
return _side(self.offset_flag)
@property
def local_order_id(self) -> str:
return self.remark.split("|", 1)[0]
@dataclass(slots=True)
class PositionItem:
"""Fields match format_holding() exactly."""
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
open_cost: float = 0.0
float_profit: float = 0.0
market_value: float = 0.0
stock_holder: str = ""
@@ -137,67 +94,14 @@ class PositionItem:
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:
"""Fields match format_assets() exactly."""
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:
@@ -206,17 +110,14 @@ class Portfolio:
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 _side(offset_flag: int) -> str:
return {"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"}.get(str(offset_flag), "")
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")
return datetime.strptime(date.replace("-", "") + clock, "%Y%m%d%H%M%S")
except ValueError:
return None

View File

@@ -17,33 +17,33 @@ class PortfolioMixin:
def portfolio(self) -> Portfolio:
data = self._get_json("/api/portfolio") or {}
positions = {
code: PositionItem.from_dict(value, code)
code: PositionItem(**value)
for code, value in data.get("positions", {}).items()
}
return Portfolio(
assets=Assets.from_dict(data.get("assets", {})),
assets=Assets(**data.get("assets", {})),
positions=positions,
orders=[OrderItem.from_trade_detail(row) for row in data.get("orders", [])],
orders=[OrderItem(**row) for row in data.get("orders", [])],
)
def positions(self) -> tuple[list[str], list[PositionItem]]:
data = self._get_json("/api/portfolio/positions") or {}
positions = [
PositionItem.from_dict(value, code)
for code, value in data.get("data", {}).items()
PositionItem(**value)
for value in data.values()
]
return [item.stock_code for item in positions], positions
return list(data), positions
def assets(self) -> Assets:
return Assets.from_dict(self._get_json("/api/portfolio/assets") or {})
return Assets(**(self._get_json("/api/portfolio/assets") or {}))
def orders(self) -> list[OrderItem]:
data = self._get_json("/api/portfolio/order") or []
return [OrderItem.from_trade_detail(row) for row in data]
return [OrderItem(**row) for row in data]
def deals(self) -> list[DealItem]:
data = self._get_json("/api/portfolio/deal") or []
return [DealItem.from_trade_detail(row) for row in data]
return [DealItem(**row) for row in data]
def trade_detail_data(self, datatype: str) -> Any:
datatype = str(datatype).strip().lower()