This commit is contained in:
2026-09-03 11:33:43 +08:00
parent d5303cc22b
commit e6a5096353
38 changed files with 1911 additions and 2742 deletions

View File

@@ -1,15 +1,27 @@
from .account import AccountMixin
from .client import Client as _HTTPClient
from .data import DataMixin
from .client import Client
from .errors import APIError, BusinessError
from .misc import MiscMixin
from .models import *
from .trade import *
from .v2 import Client as V2Client, Portfolio
class Client(AccountMixin, DataMixin, TradeMixin, MiscMixin, _HTTPClient):
"""big-qmt 同步 HTTP 客户端。"""
__all__ = ["Client", "V2Client", "Portfolio", "APIError", "BusinessError", "OP_BUY", "OP_SELL", "ORDER_TYPE_VOLUME", "PR_TYPE_LATEST", "QUICK_TRADE_NOW", "ORDER_SIDE_BY_OFFSET", "OrderItem", "PositionItem", "parse_order"]
from .models import Assets, OrderItem, Portfolio, PositionItem, Tick
from .trade import (
OP_BUY,
OP_SELL,
ORDER_SIDE_BY_OFFSET,
ORDER_TYPE_VOLUME,
PR_TYPE_LATEST,
QUICK_TRADE_NOW,
)
__all__ = [
"APIError",
"Assets",
"BusinessError",
"Client",
"OP_BUY",
"OP_SELL",
"ORDER_SIDE_BY_OFFSET",
"ORDER_TYPE_VOLUME",
"OrderItem",
"PR_TYPE_LATEST",
"Portfolio",
"PositionItem",
"QUICK_TRADE_NOW",
"Tick",
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,38 +0,0 @@
from typing import Any
from .models import Assets, PositionItem
class AccountMixin:
account_type: str
def _positions(self, path: str) -> tuple[list[str], list[PositionItem]]:
payload = self._post_json(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:
payload = self._post_json("/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_json("/api/money/total", {"account": self.account_type}).get("total_money", 0))
def available_money(self) -> float: return float(self._post_json("/api/money/available", {"account": self.account_type}).get("available_money", 0))
def buy(self, stock: str, price: float, volume: int, pr_type: int = 0): return self._order("/api/order/buy", stock, price, volume, pr_type)
def sell(self, stock: str, price: float, volume: int, pr_type: int = 0): return self._order("/api/order/sell", stock, price, volume, pr_type)
def _order(self, path, stock, price, volume, pr_type):
body = {"stock": stock, "price": price, "volume": volume}
if pr_type: body["prType"] = pr_type
return self._post_json(path, body)
def order_status_list(self): return self._post_json("/api/order/status", {"account": self.account_type}).get("orders", [])
def cancel_all(self): return self._post_json("/api/order/cancel_all", {"account": self.account_type})
def cancel_by_rule(self, stock: str, volume: int): return self._post_json("/api/order/cancel_order", {"stock": stock, "volume": volume, "account": self.account_type})
def deals(self): return self._post_json("/api/order/deal", {"account": self.account_type}).get("deals", [])

View File

@@ -6,14 +6,16 @@ from typing import Any
import httpx
from .errors import APIError, BusinessError
from .context import ContextMixin
from .data import DataMixin
from .errors import APIError
from .get import GetMixin
from .portfolio import PortfolioMixin
from .sys import SysMixin
from .trade import TradeMixin
def csv_join(items: list[str]) -> str:
return ",".join(item.strip() for item in items if item.strip())
class Client:
class HTTPClient:
"""复用连接池的同步 QMT HTTP 客户端。"""
def __init__(self, base_url: str, token: str, timeout: float = 15.0) -> None:
@@ -31,13 +33,13 @@ class Client:
def close(self) -> None:
self.http.close()
def __enter__(self) -> "Client":
def __enter__(self) -> "HTTPClient":
return self
def __exit__(self, *_args: object) -> None:
self.close()
def set_account_type(self, account_type: str) -> "Client":
def set_account_type(self, account_type: str) -> "HTTPClient":
if account_type.strip():
self.account_type = account_type
return self
@@ -91,28 +93,19 @@ class Client:
f"invalid JSON from {path}: {content[:512]!r}"
) from exc
def _get_field(self, path: str, key: str) -> Any:
return self._get_json(path).get(key)
def _post_field(self, path: str, body: Any, key: str) -> Any:
result = self._post_json(path, body)
if isinstance(result, dict) and result.get("error"):
raise BusinessError(result["error"])
return result.get(key, result) if key and isinstance(result, dict) else result
class Client(
ContextMixin,
GetMixin,
PortfolioMixin,
DataMixin,
TradeMixin,
SysMixin,
HTTPClient,
):
"""Client for the API exposed by ``qmt_rest_new.py``."""
def _is_idempotent(method: str, path: str) -> bool:
if method == "GET":
return True
prefixes = (
"/api/v2/",
"/api/holding",
"/api/money/",
"/api/context/",
"/api/check/",
"/api/data/",
"/api/trade/trade_detail_data",
"/api/order/deal",
)
unsafe = ("subscribe", "unsubscribe")
return path.startswith(prefixes) and not any(word in path for word in unsafe)
return path == "/api/data/full_tick"

8
py-client/sdk/context.py Normal file
View File

@@ -0,0 +1,8 @@
from __future__ import annotations
from typing import Any
class ContextMixin:
def context_info(self) -> dict[str, Any]:
return self._get_json("/api/context/info") or {}

View File

@@ -1,83 +1,9 @@
from dataclasses import asdict
from typing import Any
from __future__ import annotations
from .client import csv_join
from .models import *
from .models import Tick
class DataMixin:
def _one(self, endpoint, arg, value, key): return self._post_field(f"/api/data/{endpoint}", {arg: value}, key)
def stock_name(self, code): return self._one("stock_name", "stockcode", code, "name")
def open_date(self, code): return self._one("open_date", "stockcode", code, "open_date")
def last_volume(self, code): return self._one("last_volume", "stockcode", code, "last_volume")
def bar_timetag(self, index): return self._one("bar_timetag", "index", index, "timetag")
def tick_timetag(self): return self._get_field("/api/data/tick_timetag", "timetag")
def sector(self, sector, realtime): return self._post_json("/api/data/sector", {"sector": sector, "realtime": realtime}).get("stocks", [])
def industry(self, industry): return self._post_json("/api/data/industry", {"industry": industry}).get("stocks", [])
def stock_list_in_sector(self, name): return self._post_json("/api/data/stock_list_in_sector", {"sectorname": name}).get("stocks", [])
def weight_in_index(self, indexcode, stockcode): return self._post_field("/api/data/weight_in_index", locals_body(indexcode=indexcode, stockcode=stockcode), "weight")
def contract_multiplier(self, code): return self._one("contract_multiplier", "contractcode", code, "multiplier")
def risk_free_rate(self, index): return self._one("risk_free_rate", "index", index, "risk_free_rate")
def date_location(self, date): return self._one("date_location", "strdate", date, "location")
def history_data(self, req: HistoryDataRequest):
return self._post_field("/api/data/history_data", {"len": req.length or 10, "period": req.period, "field": req.field, "dividend_type": req.dividend_type, "skip_paused": str(req.skip_paused).lower()}, "data")
def _market_body(self, req): return {"fields": csv_join(req.fields), "stock_code": csv_join(req.stocks), "start_time": req.start_time, "end_time": req.end_time, "period": req.period, "dividend_type": req.dividend_type, "count": req.count}
def market_data(self, req): return self._post_field("/api/data/market_data", self._market_body(req), "data")
def market_data_ex(self, req): return self._post_field("/api/data/market_data_ex", self._market_body(req), "data")
def full_tick(self, stocks):
raw = self._post_json("/api/data/full_tick", {"stocks": stocks}) or {}
def number(data, *names):
for name in names:
try: return float(data[name])
except (KeyError, TypeError, ValueError): pass
return 0.0
return {code: Tick(number(value, "lastPrice", "last_price", "LastPrice"), number(value, "lastClose", "last_close", "LastClose"), value if isinstance(value, dict) else {}) for code, value in raw.items()}
def divid_factors(self, code): return self._one("divid_factors", "stockcode", code, "factors")
def main_contract(self, code): return self._one("main_contract", "codemarket", code, "main_contract")
def timetag_to_datetime(self, timetag, format=""):
body = {"timetag": timetag}
if format: body["format"] = format
return self._post_field("/api/data/timetag_to_datetime", body, "datetime")
def total_share(self, code): return self._one("total_share", "stockcode", code, "total_share")
def trading_dates(self, stockcode, start_date, end_date, period, count=0):
body = locals_body(stockcode=stockcode, start_date=start_date, end_date=end_date, period=period)
if count: body["count"] = count
return self._post_json("/api/data/trading_dates", body).get("dates", [])
def svol(self, code): return self._one("svol", "stockcode", code, "svol")
def bvol(self, code): return self._one("bvol", "stockcode", code, "bvol")
def longhubang(self, stocks, start, end): return self._post_field("/api/data/longhubang", {"stock_list": csv_join(stocks), "startTime": start, "endTime": end}, "data")
def top10_share_holder(self, stocks, name, start, end): return self._post_field("/api/data/top10_share_holder", {"stock_list": csv_join(stocks), "data_name": name, "start_time": start, "end_time": end}, "data")
def option_detail(self, code): return self._one("option_detail", "optioncode", code, "detail")
def turnover_rate(self, stocks, start, end): return self._post_field("/api/data/turnover_rate", {"stock_list": csv_join(stocks), "startTime": start, "endTime": end}, "data")
def etf_info(self, code): return self._one("etf_info", "stockcode", code, "info")
def etf_iopv(self, code): return self._one("etf_iopv", "stockcode", code, "iopv")
def instrument_detail(self, code): return self._one("instrumentdetail", "stockcode", code, "detail")
def contract_expire_date(self, code): return self._one("contract_expire_date", "codemarket", code, "expire_date")
def option_undl_data(self, code): return self._one("option_undl_data", "undl_code_ref", code, "data")
def financial_data(self, req):
return self._post_field("/api/data/financial_data", {"tabname": req.tabname, "colname": req.colname, "market": req.market, "code": req.code, "report_type": req.report_type, "barpos": req.barpos, "fieldList": csv_join(req.field_list), "stockList": csv_join(req.stock_list), "startDate": req.start_date, "endDate": req.end_date}, "data")
def factor_data(self, req): return self._post_field("/api/data/factor_data", {"fieldList": csv_join(req.field_list), "stockList": csv_join(req.stock_list), "stockCode": req.stock_code, "startDate": req.start_date, "endDate": req.end_date}, "data")
def his_st_data(self, code): return self._one("his_st_data", "stockCode", code, "data")
def his_index_data(self, index): return self._one("his_index_data", "index", index, "data")
def all_subscription(self): return self._get_field("/api/data/all_subscription", "subscriptions")
def option_list(self, code, dedate, opttype, available): return self._post_field("/api/data/option_list", {"undl_code": code, "dedate": dedate, "opttype": opttype, "isavailable": str(available).lower()}, "option_list")
def his_contract_list(self, market): return self._one("his_contract_list", "market", market, "contracts")
def option_iv(self, code): return self._one("option_iv", "optioncode", code, "iv")
def bsm_price(self, req):
prices = ",".join(str(v) for v in req.object_prices) if isinstance(req.object_prices, list) else req.object_prices
return self._post_field("/api/data/bsm_price", {"optionType": req.option_type, "objectPrices": prices, "strikePrice": req.strike_price, "riskFree": req.risk_free, "sigma": req.sigma, "days": req.days, "dividend": req.dividend}, "price")
def bsm_iv(self, req): return self._post_field("/api/data/bsm_iv", camel_request(req), "iv")
def local_data(self, req): return self._post_field("/api/data/local_data", {"stock_code": req.stock_code, "start_time": req.start_time, "end_time": req.end_time, "period": req.period, "divid_type": req.divid_type, "count": req.count}, "data")
def subscribe_quote(self, code, period, dividend_type): return self._post_json("/api/data/subscribe_quote", {"stock_code": code, "period": period, "dividend_type": dividend_type})
def unsubscribe_quote(self, sub_id): return self._post_json("/api/data/unsubscribe_quote", {"sub_id": sub_id})
def locals_body(**kwargs): return kwargs
def camel_request(req):
data = asdict(req)
return {"optionType": data["option_type"], "objectPrices": data["object_prices"], "strikePrice": data["strike_price"], "optionPrice": data["option_price"], "riskFree": data["risk_free"], "days": data["days"], "dividend": data["dividend"]}
def full_tick(self, stocks: list[str]) -> dict[str, Tick]:
data = self._post_json("/api/data/full_tick", {"stocks": stocks}) or {}
return {code: Tick.from_dict(value) for code, value in data.items()}

44
py-client/sdk/get.py Normal file
View File

@@ -0,0 +1,44 @@
from __future__ import annotations
from typing import Any
from urllib.parse import urlencode
class GetMixin:
def stock_name(self, stock_code: str) -> Any:
return self._stock_ref("stock_name", stock_code)
def open_date(self, stock_code: str) -> Any:
return self._stock_ref("open_date", stock_code)
def last_volume(self, stock_code: str) -> Any:
return self._stock_ref("last_volume", stock_code)
def total_share(self, stock_code: str) -> Any:
return self._stock_ref("total_share", stock_code)
def svol(self, stock_code: str) -> Any:
return self._stock_ref("svol", stock_code)
def bvol(self, stock_code: str) -> Any:
return self._stock_ref("bvol", stock_code)
def divid_factors(self, stock_code: str) -> Any:
return self._stock_ref("divid_factors", stock_code)
def etf_info(self, stock_code: str) -> Any:
return self._stock_ref("etf_info", stock_code)
def etf_iopv(self, stock_code: str) -> Any:
return self._stock_ref("etf_iopv", stock_code)
def instrument_detail(self, stock_code: str) -> Any:
return self._stock_ref("instrumentdetail", stock_code)
def his_st_data(self, stock_code: str) -> Any:
return self._stock_ref("his_st_data", stock_code)
def _stock_ref(self, endpoint: str, stock_code: str) -> Any:
query = urlencode({"stock_code": stock_code})
data = self._get_json(f"/api/get/{endpoint}?{query}") or {}
return data.get("ref")

View File

@@ -1,31 +0,0 @@
from typing import Any
class MiscMixin:
def context_period(self): return self._get_field("/api/context/period", "period")
def context_barpos(self): return self._get_field("/api/context/barpos", "barpos")
def context_time_tick_size(self): return self._get_field("/api/context/time_tick_size", "time_tick_size")
def context_stockcode(self): return self._get_field("/api/context/stockcode", "stockcode")
def context_dividend_type(self): return self._get_field("/api/context/dividend_type", "dividend_type")
def context_market(self): return self._get_field("/api/context/market", "market")
def context_do_back_test(self): return self._get_field("/api/context/do_back_test", "do_back_test")
def context_benchmark(self): return self._get_field("/api/context/benchmark", "benchmark")
def context_capital(self): return self._get_field("/api/context/capital", "capital")
def context_universe(self):
value = self._get_field("/api/context/universe", "universe")
if value is None: return []
return [str(v) for v in value if str(v)] if isinstance(value, list) else [str(value)]
def is_last_bar(self): return self._get_field("/api/check/is_last_bar", "is_last_bar")
def is_new_bar(self): return self._get_field("/api/check/is_new_bar", "is_new_bar")
def is_suspended_stock(self, stockcode): return self._post_field("/api/check/is_suspended_stock", {"stockcode": stockcode}, "is_suspended")
def is_sector_stock(self, sectorname, market, stockcode): return self._post_field("/api/check/is_sector_stock", {"sectorname": sectorname, "market": market, "stockcode": stockcode}, "is_in_sector")
def is_typed_stock(self, stocktypenum, market, stockcode): return self._post_field("/api/check/is_typed_stock", {"stocktypenum": stocktypenum, "market": market, "stockcode": stockcode}, "result")
def industry_name_of_stock(self, industry_type, stockcode): return self._post_field("/api/check/get_industry_name_of_stock", {"industryType": industry_type, "stockcode": stockcode}, "industry_name")
def ext_data(self, name, stockcode, deviation): return self._post_field("/api/ext/ext_data", {"extdataname": name, "stockcode": stockcode, "deviation": deviation}, "value")
def ext_data_rank(self, name, stockcode, deviation): return self._post_field("/api/ext/ext_data_rank", {"extdataname": name, "stockcode": stockcode, "deviation": deviation}, "rank")
def get_factor_value(self, name, stockcode, deviation): return self._post_field("/api/ext/get_factor_value", {"factorname": name, "stockcode": stockcode, "deviation": deviation}, "value")
def get_factor_rank(self, name, stockcode, deviation): return self._post_field("/api/ext/get_factor_rank", {"factorname": name, "stockcode": stockcode, "deviation": deviation}, "rank")
def python_version(self): return self._get_json("/api/sys/python_version")
def shutdown(self): return self._post_json("/api/sys/shutdown", {})

View File

@@ -133,6 +133,13 @@ class Assets:
)
@dataclass(slots=True)
class Portfolio:
assets: Assets
positions: dict[str, PositionItem]
orders: list[OrderItem]
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)
@@ -148,6 +155,16 @@ class Tick:
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:

View File

@@ -0,0 +1,52 @@
from __future__ import annotations
from typing import Any
from .models import Assets, OrderItem, Portfolio, PositionItem
class PortfolioMixin:
def portfolio(self) -> Portfolio:
data = self._get_json("/api/portfolio") or {}
positions = {
code: PositionItem.from_dict(value, code)
for code, value in data.get("positions", {}).items()
}
return Portfolio(
assets=Assets.from_dict(data.get("assets", {})),
positions=positions,
orders=[OrderItem.from_trade_detail(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()
]
return [item.stock_code for item in positions], positions
def assets(self) -> Assets:
return Assets.from_dict(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]
def deals(self) -> list[dict[str, Any]]:
data = self._get_json("/api/portfolio/deal") or {}
return data.get("deals", [])
def trade_detail_data(self, datatype: str) -> Any:
datatype = str(datatype).strip().lower()
handlers = {
"account": self.assets,
"position": lambda: self.positions()[1],
"order": self.orders,
"deal": self.deals,
}
handler = handlers.get(datatype)
if handler is None:
raise ValueError(f"unsupported trade detail datatype: {datatype}")
result = handler()
return [result] if datatype == "account" else result

8
py-client/sdk/sys.py Normal file
View File

@@ -0,0 +1,8 @@
from __future__ import annotations
from typing import Any
class SysMixin:
def python_version(self) -> dict[str, Any]:
return self._get_json("/api/sys/python_version") or {}

View File

@@ -1,82 +1,62 @@
from .models import *
from __future__ import annotations
from typing import Any
OP_BUY = 23
OP_SELL = 24
ORDER_TYPE_VOLUME, PR_TYPE_LATEST, QUICK_TRADE_NOW = 1101, 5, 2
ORDER_TYPE_VOLUME = 1101
PR_TYPE_LATEST = 5
QUICK_TRADE_NOW = 2
ORDER_SIDE_BY_OFFSET = {"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"}
class TradeMixin:
account_type: str
def passorder(
self,
op_type: int,
stock_code: str,
volume: int,
order_type: int = ORDER_TYPE_VOLUME,
pr_type: int = PR_TYPE_LATEST,
price: float = -1,
quick_trade: int = QUICK_TRADE_NOW,
strategy_name: str = "",
order_id: str = "",
) -> dict[str, Any]:
return self._post_json(
"/api/trade/passorder",
{
"opType": op_type,
"orderType": order_type,
"stockCode": stock_code,
"prType": pr_type,
"price": price,
"volume": volume,
"quickTrade": quick_trade,
"strategyName": strategy_name,
"orderId": order_id,
},
)
def passorder(self, op_type, stock, volume, order_type=0, pr_type=0, price=0.0, quick_trade=0, strategy_name=""):
body = {"opType": op_type, "stock": stock, "price": price, "volume": volume}
for key, value in (("orderType", order_type), ("prType", pr_type), ("quickTrade", quick_trade), ("strategyName", strategy_name)):
if value: body[key] = value
return self._post_json("/api/trade/passorder", body)
def passorder_latest(self, side: int, stock_code: str, volume: int) -> dict[str, Any]:
return self.passorder(side, stock_code, volume)
def passorder_latest(self, side, stock, volume): return self.passorder_latest_tagged(side, stock, volume, "", "")
def passorder_latest_tagged(self, side, stock_code, volume, strategy_name, order_id):
body = {
"opType": side,
"orderType": ORDER_TYPE_VOLUME,
"stockCode": stock_code,
"prType": PR_TYPE_LATEST,
"price": -1,
"volume": volume,
"quickTrade": QUICK_TRADE_NOW,
"strategyName": strategy_name,
"orderId": order_id,
}
return self._post_json("/api/trade/passorder", body)
def passorder_latest_tagged(
self,
side: int,
stock_code: str,
volume: int,
strategy_name: str,
order_id: str,
) -> dict[str, Any]:
return self.passorder(
side,
stock_code,
volume,
strategy_name=strategy_name,
order_id=order_id,
)
def algo_passorder(self, **kwargs): return self._post_json("/api/trade/algo_passorder", kwargs)
def smart_algo_passorder(self, **kwargs): return self._post_json("/api/trade/smart_algo_passorder", kwargs)
def _style_order(self, path, stock, value_key, value, style, price):
return self._post_json(path, {"stock": stock, value_key: value, "style": style, "price": price})
def order_lots(self, stock, lots, style, price): return self._style_order("/api/trade/order_lots", stock, "lots", lots, style, price)
def order_value(self, stock, value, style, price): return self._style_order("/api/trade/order_value", stock, "value", value, style, price)
def order_percent(self, stock, percent, style, price): return self._style_order("/api/trade/order_percent", stock, "percent", percent, style, price)
def order_target_value(self, stock, value, style, price): return self._style_order("/api/trade/order_target_value", stock, "tar_value", value, style, price)
def order_target_percent(self, stock, percent, style, price): return self._style_order("/api/trade/order_target_percent", stock, "tar_percent", percent, style, price)
def order_shares(self, stock, shares, style, price): return self._style_order("/api/trade/order_shares", stock, "shares", shares, style, price)
def _future(self, action, stock, amount, style, price): return self._style_order(f"/api/trade/futures/{action}", stock, "amount", amount, style, price)
def futures_buy_open(self, *args): return self._future("buy_open", *args)
def futures_buy_close_tdayfirst(self, *args): return self._future("buy_close_tdayfirst", *args)
def futures_buy_close_ydayfirst(self, *args): return self._future("buy_close_ydayfirst", *args)
def futures_sell_open(self, *args): return self._future("sell_open", *args)
def futures_sell_close_tdayfirst(self, *args): return self._future("sell_close_tdayfirst", *args)
def futures_sell_close_ydayfirst(self, *args): return self._future("sell_close_ydayfirst", *args)
def _task(self, action, task_id): return self._post_json(f"/api/trade/{action}_task", {"taskId": task_id, "accountType": self.account_type})
def cancel_task(self, task_id): return self._task("cancel", task_id)
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_json("/api/trade/do_order")
def trade_detail_data(self, datatype):
datatype = str(datatype).strip().lower()
data = self._post_json(
"/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_json("/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_json("/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_json("/api/trade/can_cancel_order", {"orderId": order_id, "accountType": self.account_type}).get("can_cancel")
def cancel_by_id(self, order_id): return self._post_json("/api/order/cancel_by_id", {"order_id": order_id, "account_type": self.account_type})
def debt_contract(self): return self._contract("debt_contract")
def assure_contract(self): return self._contract("assure_contract")
def enable_short_contract(self): return self._contract("enable_short_contract")
def _contract(self, name): return self._post_json(f"/api/trade/{name}").get("data", [])
def ipo_data(self, typ): return self._post_field("/api/trade/ipo_data", {"type": typ}, "data")
def new_purchase_limit(self): return self._post_field("/api/trade/new_purchase_limit", None, "data")
def cancel_by_id(self, order_id: str) -> dict[str, Any]:
return self._post_json("/api/trade/cancel_by_id", {"order_id": order_id})

View File

@@ -1,86 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from urllib.parse import urlencode
from .client import Client as HTTPClient
from .models import Assets, OrderItem, PositionItem
@dataclass(slots=True)
class Portfolio:
assets: Assets
positions: list[PositionItem]
orders: list[OrderItem]
class Client_V2(HTTPClient):
"""QMT ``/api/v2`` synchronous client."""
def portfolio(self) -> Portfolio:
data = self._get_json("/api/v2/portfolio", ) or {}
positions = data.get("positions", {})
orders = data.get("orders", {})
return Portfolio(
assets=Assets.from_dict(data.get("assets", {})),
positions=[PositionItem.from_trade_detail(row) for row in positions],
orders=[OrderItem.from_trade_detail(value) for value in orders],
)
def positions(self) -> list[PositionItem]:
data = self._post("/api/v2/positions", {"account": self.account_type}) or {}
return [
PositionItem.from_dict(value, code)
for code, value in data.get("data", {}).items()
]
def assets(self) -> dict[str, Any]:
return self._post("/api/v2/assets", {"account": self.account_type}) or {}
def context_info(self) -> dict[str, Any]:
return self._get("/api/v2/context/info") or {}
def stock_name(self, stock_code: str) -> Any:
return self._get_ref("stock_name", stock_code)
def open_date(self, stock_code: str) -> Any:
return self._get_ref("open_date", stock_code)
def last_volume(self, stock_code: str) -> Any:
return self._get_ref("last_volume", stock_code)
def total_share(self, stock_code: str) -> Any:
return self._get_ref("total_share", stock_code)
def svol(self, stock_code: str) -> Any:
return self._get_ref("svol", stock_code)
def bvol(self, stock_code: str) -> Any:
return self._get_ref("bvol", stock_code)
def divid_factors(self, stock_code: str) -> Any:
return self._get_ref("divid_factors", stock_code)
def etf_info(self, stock_code: str) -> Any:
return self._get_ref("etf_info", stock_code)
def etf_iopv(self, stock_code: str) -> Any:
return self._get_ref("etf_iopv", stock_code)
def instrument_detail(self, stock_code: str) -> Any:
return self._get_ref("instrumentdetail", stock_code)
def his_st_data(self, stock_code: str) -> Any:
return self._get_ref("his_st_data", stock_code)
def _get_ref(self, endpoint: str, stock_code: str) -> Any:
query = urlencode({"stock_code": stock_code})
payload = self._get(f"/api/v2/get/{endpoint}?{query}") or {}
return payload.get("ref")
Client = Client_V2
__all__ = ["Client", "Client_V2", "Portfolio"]