update api,libs

This commit is contained in:
2026-08-30 00:34:27 +08:00
parent 9a43aaba23
commit cdccc48d8c
21 changed files with 2616 additions and 179 deletions

View File

@@ -11,4 +11,4 @@ class Client(AccountMixin, DataMixin, TradeMixin, MiscMixin, _HTTPClient):
"""big-qmt 同步 HTTP 客户端。"""
__all__ = ["Client", "APIError", "BusinessError", "OP_BUY", "OP_SELL", "ORDER_TYPE_VOLUME", "PR_TYPE_LATEST", "QUICK_TRADE_NOW"]
__all__ = ["Client", "APIError", "BusinessError", "OP_BUY", "OP_SELL", "ORDER_TYPE_VOLUME", "PR_TYPE_LATEST", "QUICK_TRADE_NOW", "ORDER_SIDE_BY_OFFSET", "OrderItem", "PositionItem", "parse_order"]

View File

@@ -1,21 +1,26 @@
from typing import Any
from .models import Assets, Position
from .models import Assets, PositionItem
class AccountMixin:
account_type: str
def _positions(self, path: str) -> tuple[list[str], list[Position]]:
raw = self._post(path, {"account": self.account_type}) or {}
return list(raw), [Position.from_dict(value, code) for code, value in raw.items()]
def _positions(self, path: str) -> tuple[list[str], list[PositionItem]]:
payload = self._post(path, {"account": self.account_type}) or {}
raw = payload.get("data", payload) if isinstance(payload, dict) else payload
if isinstance(raw, list):
positions = [PositionItem.from_trade_detail(item) for item in raw]
return [item.stock_code for item in positions], positions
return list(raw), [PositionItem.from_dict(value, code) for code, value in raw.items()]
def positions(self): return self._positions("/api/v2/positions")
def holding(self): return self._positions("/api/holding")
def assets(self) -> Assets:
data = self._post("/api/v2/assets", {"account": self.account_type})
return Assets(float(data.get("total", 0)), float(data.get("available", 0)))
payload = self._post("/api/v2/assets", {"account": self.account_type}) or {}
data = payload.get("data", payload) if isinstance(payload, dict) else {}
return Assets.from_dict(data)
def total_money(self) -> float: return float(self._post("/api/money/total", {"account": self.account_type}).get("total_money", 0))
def available_money(self) -> float: return float(self._post("/api/money/available", {"account": self.account_type}).get("available_money", 0))

View File

@@ -1,5 +1,5 @@
from __future__ import annotations
from datetime import datetime
from dataclasses import dataclass, field
from typing import Any
@@ -12,7 +12,60 @@ def _number(value: Any, kind: type = float) -> Any:
@dataclass(slots=True)
class Position:
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 PositionItem:
stock_code: str = ""
stock_name: str = ""
direction: Any = None
@@ -31,7 +84,7 @@ class Position:
expire_date: str = ""
@classmethod
def from_dict(cls, data: dict[str, Any], code: str = "") -> "Position":
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 ""),
direction=data.get("Direction"), volume=_number(data.get("Volume"), int),
@@ -43,12 +96,58 @@ class Position:
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 原始字段创建持仓。"""
instrument_id = str(data.get("m_strInstrumentID") or "")
exchange_id = str(data.get("m_strExchangeID") or "")
stock_code = (
f"{instrument_id}.{exchange_id}"
if instrument_id and exchange_id
else instrument_id
)
return cls(
stock_code=stock_code,
stock_name=str(data.get("m_strInstrumentName") or ""),
direction=data.get("m_nDirection"),
volume=_number(data.get("m_nVolume"), int),
open_price=_number(data.get("m_dOpenPrice")),
float_profit=_number(data.get("m_dFloatProfit")),
market_value=_number(data.get("m_dMarketValue")),
stock_holder=str(data.get("m_strStockHolder") or ""),
frozen_volume=_number(data.get("m_nFrozenVolume"), int),
can_use_volume=_number(data.get("m_nCanUseVolume"), int),
on_road_volume=_number(data.get("m_nOnRoadVolume"), int),
yesterday_volume=_number(data.get("m_nYesterdayVolume"), int),
last_price=_number(data.get("m_dLastPrice")),
profit_rate=_number(data.get("m_dProfitRate")),
future_trade_type=data.get("m_eFutureTradeType"),
expire_date=str(data.get("m_strExpireDate") 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"))),
)
def _trade_datetime(data: dict[str, Any]) -> datetime | None:
date = str(data.get("m_strInsertDate") or "")
clock = str(data.get("m_strInsertTime") or "").replace(":", "").zfill(6)
try:
return datetime.strptime(date + clock, "%Y%m%d%H%M%S")
except ValueError:
return None
@dataclass(slots=True)
class Tick:

View File

@@ -1,7 +1,9 @@
from .models import *
from typing import Any
OP_BUY, OP_SELL = 23, 24
ORDER_TYPE_VOLUME, PR_TYPE_LATEST, QUICK_TRADE_NOW = 1101, 5, 2
ORDER_SIDE_BY_OFFSET = {"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"}
class TradeMixin:
@@ -53,7 +55,20 @@ class TradeMixin:
def pause_task(self, task_id): return self._task("pause", task_id)
def resume_task(self, task_id): return self._task("resume", task_id)
def do_order(self): return self._post("/api/trade/do_order")
def trade_detail_data(self, datatype): return self._post("/api/trade/trade_detail_data", {"account": self.account_type, "datatype": datatype}).get("data", [])
def trade_detail_data(self, datatype):
datatype = str(datatype).strip().lower()
data = self._post(
"/api/trade/trade_detail_data",
{"account": self.account_type, "datatype": datatype},
).get("data", [])
rows = data if isinstance(data, list) else [data] if isinstance(data, dict) else []
if datatype == "order":
return [OrderItem.from_trade_detail(row) for row in rows]
if datatype == "position":
return [PositionItem.from_trade_detail(row) for row in rows]
if datatype == "account":
return [Assets.from_dict(row) for row in rows]
return data
def value_by_order_id(self, order_id, datatype): return self._post("/api/trade/value_by_order_id", {"orderId": order_id, "accountType": self.account_type, "datatype": datatype}).get("data")
def last_order_id(self, datatype): return self._post("/api/trade/last_order_id", {"account": self.account_type, "datatype": datatype}).get("last_order_id")
def can_cancel_order(self, order_id): return self._post("/api/trade/can_cancel_order", {"orderId": order_id, "accountType": self.account_type}).get("can_cancel")