update api,libs
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user