diff --git a/api/qmt_rest_new.py b/api/qmt_rest_new.py index ae337fc..c952772 100644 --- a/api/qmt_rest_new.py +++ b/api/qmt_rest_new.py @@ -186,6 +186,24 @@ class HoldingHandler(BaseHandler): holding = format_holding(positions) self.write_json({"data": holding}) +class OrgHandler(BaseHandler): + def get(self, handler_type): + result = safe_call( + get_trade_detail_data, self.acc(), 'stock', handler_type + ) or [] + + data = [] + for item in result: + fields = {} + for name in dir(item): + if name.startswith('_'): + continue + value = getattr(item, name) + if not callable(value): + fields[name] = value + data.append(fields) + self.write_json({"data": data}) + # get_trade_detail_data('account') - Query account assets class AssetsHandler(BaseHandler): def get(self): @@ -318,6 +336,7 @@ def format_holding(positions): 'Direction': position.m_nDirection, 'Volume': position.m_nVolume, 'OpenPrice': position.m_dOpenPrice, + 'OpenCost':position.m_dOpenCost, 'FloatProfit': position.m_dFloatProfit, 'MarketValue': position.m_dMarketValue, 'StockHolder': position.m_strStockHolder, @@ -372,6 +391,7 @@ def make_app(): # Portfolio (r"/api/portfolio", PortfolioHandler), (r"/api/portfolio/positions", HoldingHandler), + (r"/api/portfolio/org/(account|order|deal|position)",OrgHandler), (r"/api/portfolio/assets", AssetsHandler), (r"/api/portfolio/order", OrderHandler), (r"/api/portfolio/deal", DealHandler), diff --git a/api/qmt_rest_rele.py b/api/qmt_rest_rele.py index 26c36f0..c952772 100644 --- a/api/qmt_rest_rele.py +++ b/api/qmt_rest_rele.py @@ -2,17 +2,18 @@ import json import locale import os -import datetime -from pathlib import Path +import sys +from urllib.request import Request, urlopen from tornado.web import Application, RequestHandler, HTTPError from tornado.ioloop import IOLoop import logging # Configuration ACCOUNT_ID = os.environ.get('QMT_ACCOUNT_ID', '') -DATA_DIR = os.environ.get('QMT_DATA_DIR', 'D:\qmt_strategy_data') +DATA_DIR = os.environ.get('QMT_DATA_DIR', r'D:\qmt_strategy_data') TOKEN="QMTbyYanweidong" PORT = 10086 +PASS_CODES_URL = "http://139.224.247.176:13499/a/pass_codes" # =================================== logging.basicConfig(level=logging.INFO) @@ -32,6 +33,36 @@ def safe_call(func, *args, **kwargs): ) from e +def get_pass_codes(account_id): + request = Request( + PASS_CODES_URL, + headers={"Accept": "application/json", "User-Agent": "big-qmt/1"}, + ) + with urlopen(request, timeout=10) as response: + payload = json.load(response) + + remote_codes = payload.get("data") + if not isinstance(remote_codes, list): + raise ValueError("pass_codes response data must be an array") + + positions = safe_call( + get_trade_detail_data, account_id, 'stock', 'position' + ) or [] + position_codes = [ + position.m_strInstrumentID + '.' + position.m_strExchangeID + for position in positions + ] + + codes = [] + seen = set() + for code in remote_codes + position_codes: + code = str(code).strip() + if code and code not in seen: + seen.add(code) + codes.append(code) + return codes + + # ============= BaseHandler ============= AUTH_EXEMPT = set() @@ -61,6 +92,14 @@ class BaseHandler(RequestHandler): def acc(self): return self.application.accountID + def write_json(self, data, default=None): + self.write(json.dumps( + data, + separators=(',', ':'), + ensure_ascii=False, + default=default, + )) + # ============= 1. ContextInfo properties ============= # "/api/v2/context/info" @@ -80,7 +119,7 @@ class ContextInfoHandler(BaseHandler): "timetag":ctx.timetag, "universe": ctx.get_universe(), } - self.write(data, separators=(',', ':'), ensure_ascii=False) + self.write_json(data) # ============= 2. Data queries (ContextInfo get_*) ============= STOCK_HANDLER = { @@ -118,25 +157,26 @@ class StockGetHandler(BaseHandler): # 响应 - self.write(json.dumps({ + self.write_json({ "stock_code": query_vals, "ref": result - }, separators=(',', ':'), ensure_ascii=False, default=str)) + }, default=str) # Aggregate assets, positions, and orders in one request. class PortfolioHandler(BaseHandler): def get(self): - account_data = safe_call(get_trade_detail_data, self.acc(), 'stock', 'account') - positions = safe_call(get_trade_detail_data, self.acc(), 'stock', 'position') or [] - orders = safe_call(get_trade_detail_data, self.acc(), 'stock', 'order') or [] + account_id = self.acc() + account_data = safe_call(get_trade_detail_data, account_id, 'stock', 'account') + positions = safe_call(get_trade_detail_data, account_id, 'stock', 'position') or [] + orders = safe_call(get_trade_detail_data, account_id, 'stock', 'order') or [] result = { "assets": format_assets(account_data), "positions": format_holding(positions), "orders": [fixed_fields(order) for order in orders], } - self.write(json.dumps(result, separators=(',', ':'), ensure_ascii=False)) + self.write_json(result) # get_trade_detail_data('position') - Query positions in the wrapped format @@ -144,13 +184,31 @@ class HoldingHandler(BaseHandler): def get(self): positions = safe_call(get_trade_detail_data, self.acc(), 'stock', 'position') or [] holding = format_holding(positions) - self.write(json.dumps({"data": holding}, separators=(',', ':'), ensure_ascii=False)) + self.write_json({"data": holding}) + +class OrgHandler(BaseHandler): + def get(self, handler_type): + result = safe_call( + get_trade_detail_data, self.acc(), 'stock', handler_type + ) or [] + + data = [] + for item in result: + fields = {} + for name in dir(item): + if name.startswith('_'): + continue + value = getattr(item, name) + if not callable(value): + fields[name] = value + data.append(fields) + self.write_json({"data": data}) # get_trade_detail_data('account') - Query account assets class AssetsHandler(BaseHandler): def get(self): _data = safe_call(get_trade_detail_data, self.acc(), 'stock', 'account') - self.write(json.dumps(format_assets(_data), separators=(',', ':'), ensure_ascii=False)) + self.write_json(format_assets(_data)) class OrderHandler(BaseHandler): def get(self): @@ -158,13 +216,13 @@ class OrderHandler(BaseHandler): if ret is None: ret = [] result = [fixed_fields(obj) for obj in ret] - self.write(json.dumps(result, separators=(',', ':'), ensure_ascii=False)) + self.write_json(result) class DealHandler(BaseHandler): def get(self): deals = safe_call(get_trade_detail_data, self.acc(), 'stock', 'deal') or [] rets = [fixed_fields(deal) for deal in deals] - self.write(json.dumps({"deals": rets}, separators=(',', ':'), ensure_ascii=False)) + self.write_json({"deals": rets}) # ContextInfo.get_full_tick() - Get full tick data class FullTickHandler(BaseHandler): @@ -176,7 +234,7 @@ class FullTickHandler(BaseHandler): ret = safe_call(self.ctx().get_full_tick, stocks) if not ret: raise HTTPError(500, "Failed to get tick data") - self.write(json.dumps(ret, separators=(',', ':'), ensure_ascii=False, default=str)) + self.write_json(ret, default=str) # passorder() - Submit a general trading order class PassorderHandler(BaseHandler): @@ -204,14 +262,14 @@ class PassorderHandler(BaseHandler): logger.exception("passorder failed") raise HTTPError(502, reason="QMT order submission failed") from e - self.write(json.dumps({ + self.write_json({ "status": "success", "opType": opType, "stockCode": stockCode, "strategy_name": strategy_name, "local_order_id": order_id, "order_ref": str(order_ref) - }, separators=(',', ':'), ensure_ascii=False)) + }) class CancelByIdHandler(BaseHandler): @@ -223,21 +281,28 @@ class CancelByIdHandler(BaseHandler): raise HTTPError(400, "order_id cannot be empty") cancelable = safe_call(can_cancel_order, order_id, self.acc(), 'stock') if not cancelable: - self.write(json.dumps({ + self.write_json({ "status": "failed", "order_id": order_id, "message": "Order does not exist or cannot currently be canceled" - }, separators=(',', ':'), ensure_ascii=False)) + }) return result = safe_call(cancel, order_id, self.acc(), 'stock', self.ctx()) - self.write(json.dumps({ + self.write_json({ "status": "success" if result is not False else "failed", "order_id": order_id, - }, separators=(',', ':'), ensure_ascii=False)) + }) + +# get_ipo_data() - Get today's new stock and bond offerings +class IpoDataHandler(BaseHandler): + def post(self): + data = json.loads(self.request.body) + typ = data.get('type', 'STOCK') + ret = safe_call(get_ipo_data, typ) + self.write_json(ret) # sys: Python version information class PythonVersionHandler(BaseHandler): def get(self): - import sys version_info = { "python_version": sys.version, "python_version_info": { @@ -248,7 +313,73 @@ class PythonVersionHandler(BaseHandler): "serial": sys.version_info.serial, } } - self.write(json.dumps(version_info, separators=(',', ':'), ensure_ascii=False)) + self.write_json(version_info) + + +def format_assets(account_data): + info = account_data[0] if account_data else None + if not info: + raise HTTPError(500, "Failed to get account data") + return { + "total": round(info.m_dBalance, 2), + "available": round(info.m_dAvailable, 2), + } + +def format_holding(positions): + holding = {} + for position in positions: + stock = position.m_strInstrumentID + '.' + position.m_strExchangeID + holding[stock] = { + 'StockCode': stock, + 'TradeID':position.m_strTradeID, + 'StockName': position.m_strInstrumentName, + 'Direction': position.m_nDirection, + 'Volume': position.m_nVolume, + 'OpenPrice': position.m_dOpenPrice, + 'OpenCost':position.m_dOpenCost, + 'FloatProfit': position.m_dFloatProfit, + 'MarketValue': position.m_dMarketValue, + 'StockHolder': position.m_strStockHolder, + 'FrozenVolume': position.m_nFrozenVolume, + 'CanUseVolume': position.m_nCanUseVolume, + 'OnRoadVolume': position.m_nOnRoadVolume, + 'YesterdayVolume': position.m_nYesterdayVolume, + 'LastPrice': position.m_dLastPrice, + 'ProfitRate': position.m_dProfitRate, + 'FutureTradeType': position.m_eFutureTradeType, + 'ExpireDate': position.m_strExpireDate + } + return holding + +TRADE_DETAIL_FIELDS = ( + 'm_strOrderSysID', 'm_strInstrumentID', 'm_strExchangeID', + 'm_strInstrumentName', 'm_nOffsetFlag', 'm_nOrderStatus', + 'm_nVolumeTotal', 'm_nVolumeTraded', 'm_nOrderTime', + 'm_strInsertDate', 'm_strInsertTime', 'm_strRemark', + 'm_dPrice', 'm_dTradePrice', 'm_dTradeAmount', +) +MISSING = object() + + +def fixed_fields(obj, fields=TRADE_DETAIL_FIELDS): + result = {} + for field in fields: + try: + value = getattr(obj, field, MISSING) + except TypeError: + continue + if value is MISSING: + continue + if not callable(value): + result[field] = str(value) + if not result: + attrs = getattr(obj, '__dict__', {}) + result = { + key: str(value) for key, value in attrs.items() + if not key.startswith('_') and not callable(value) + } + return result + # ============= Route registration ============= def make_app(): @@ -256,15 +387,17 @@ def make_app(): # ContextInfo properties (r"/api/context/info", ContextInfoHandler), (r"/api/get/(stock_name|open_date|last_volume|total_share|svol|bvol|divid_factors|etf_info|etf_iopv|instrumentdetail|his_st_data)", StockGetHandler), - - # V2 + + # Portfolio (r"/api/portfolio", PortfolioHandler), (r"/api/portfolio/positions", HoldingHandler), + (r"/api/portfolio/org/(account|order|deal|position)",OrgHandler), (r"/api/portfolio/assets", AssetsHandler), (r"/api/portfolio/order", OrderHandler), (r"/api/portfolio/deal", DealHandler), - + (r"/api/data/full_tick", FullTickHandler), + (r"/api/trade/ipo_data", IpoDataHandler), (r"/api/trade/cancel_by_id", CancelByIdHandler), (r"/api/trade/passorder", PassorderHandler), @@ -286,11 +419,9 @@ def init(ContextInfo): try: ContextInfo.accountID = ACCOUNT_ID ContextInfo.set_account(ACCOUNT_ID) - # Load the symbol universe only when configured. - pass_codes_path = Path(DATA_DIR) / "pass_codes.json" - with pass_codes_path.open("r", encoding="utf-8") as stream: - codes = json.load(stream) - ContextInfo.set_universe(list(codes)) + + codes = get_pass_codes(ContextInfo.accountID) + ContextInfo.set_universe(list(codes)) # Api App app = make_app() diff --git a/docs/arch/state.py b/docs/arch/state.py index 267230a..cff2b61 100644 --- a/docs/arch/state.py +++ b/docs/arch/state.py @@ -15,7 +15,7 @@ from sdk import OrderItem, PositionItem PENDING_TIME_OUT = 3600 -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class StateItem: code: str base_order_id: str = "" @@ -27,7 +27,7 @@ class StateItem: added_cost: float = 0.0 -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class PendingOrder: order_id: str code: str diff --git a/py-client/config/__init__.py b/py-client/config/__init__.py index 01d4b98..43e68f4 100644 --- a/py-client/config/__init__.py +++ b/py-client/config/__init__.py @@ -7,7 +7,7 @@ from pathlib import Path import yaml -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class SignalConfig: """单个交易信号的数据源及开仓限制配置。""" @@ -21,7 +21,7 @@ class SignalConfig: gt_last_price_is_open: bool = False -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class GlobalConfig: """所有主机共享的系统配置。""" @@ -37,7 +37,7 @@ class GlobalConfig: signals: dict[str, SignalConfig] = field(default_factory=dict) -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class AccountConfig: """当前主机所使用的账户及交易策略参数。""" diff --git a/py-client/config/__pycache__/__init__.cpython-311.pyc b/py-client/config/__pycache__/__init__.cpython-311.pyc index 309fe19..afff991 100644 Binary files a/py-client/config/__pycache__/__init__.cpython-311.pyc and b/py-client/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/py-client/libs/__pycache__/grid_take_profit.cpython-311.pyc b/py-client/libs/__pycache__/grid_take_profit.cpython-311.pyc index 3e88613..1ab8cd0 100644 Binary files a/py-client/libs/__pycache__/grid_take_profit.cpython-311.pyc and b/py-client/libs/__pycache__/grid_take_profit.cpython-311.pyc differ diff --git a/py-client/libs/__pycache__/signal.cpython-311.pyc b/py-client/libs/__pycache__/signal.cpython-311.pyc index 2a7c860..9362086 100644 Binary files a/py-client/libs/__pycache__/signal.cpython-311.pyc and b/py-client/libs/__pycache__/signal.cpython-311.pyc differ diff --git a/py-client/libs/grid_take_profit.py b/py-client/libs/grid_take_profit.py index e2e74ca..cc75a02 100644 --- a/py-client/libs/grid_take_profit.py +++ b/py-client/libs/grid_take_profit.py @@ -20,7 +20,7 @@ class GridState(str, Enum): STEADY = "steady" # 仍处于当前峰值网格,继续持有 -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class GridObservation: """一次网格观察的不可变结果。""" diff --git a/py-client/libs/signal.py b/py-client/libs/signal.py index d0a892c..2ce60a9 100644 --- a/py-client/libs/signal.py +++ b/py-client/libs/signal.py @@ -4,12 +4,12 @@ import secrets from .http import get_json -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class SignalItem: signal_key: str = ""; code: str = ""; name: str = ""; desc: str = ""; last_close: float = 0 tech_indicator: dict[str, float] = field(default_factory=dict) -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class SignalResult: code: str = ""; total: int = 0; updated: str = ""; data: dict[str, SignalItem] = field(default_factory=dict); message: str = "" diff --git a/py-client/main.py b/py-client/main.py index 787dc9f..0ca4dab 100644 --- a/py-client/main.py +++ b/py-client/main.py @@ -38,7 +38,7 @@ from strategy.trend.boot import StartTrend from strategy.zt.boot import StartZT from strategy.ipo import AutoBuyIpo -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class StrategyDefinition: mutex_scope: str start_strategy: object diff --git a/py-client/sdk/__pycache__/models.cpython-311.pyc b/py-client/sdk/__pycache__/models.cpython-311.pyc index ae084b3..556142d 100644 Binary files a/py-client/sdk/__pycache__/models.cpython-311.pyc and b/py-client/sdk/__pycache__/models.cpython-311.pyc differ diff --git a/py-client/sdk/__pycache__/portfolio.cpython-311.pyc b/py-client/sdk/__pycache__/portfolio.cpython-311.pyc index 664573f..665186b 100644 Binary files a/py-client/sdk/__pycache__/portfolio.cpython-311.pyc and b/py-client/sdk/__pycache__/portfolio.cpython-311.pyc differ diff --git a/py-client/sdk/__pycache__/trade.cpython-311.pyc b/py-client/sdk/__pycache__/trade.cpython-311.pyc index f261d4b..a24d207 100644 Binary files a/py-client/sdk/__pycache__/trade.cpython-311.pyc and b/py-client/sdk/__pycache__/trade.cpython-311.pyc differ diff --git a/py-client/sdk/models.py b/py-client/sdk/models.py index b3d02de..5b1c335 100644 --- a/py-client/sdk/models.py +++ b/py-client/sdk/models.py @@ -11,7 +11,7 @@ def _number(value: Any, kind: type = float) -> Any: return kind() -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class OrderItem: """由 QMT 委托明细解析得到的标准订单记录。""" id: str @@ -64,13 +64,14 @@ class OrderItem: ) -@dataclass(frozen=True, slots=True) +@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 @@ -88,7 +89,7 @@ class PositionItem: 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 ""), + 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 ""), @@ -107,6 +108,7 @@ class PositionItem: 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")), @@ -122,7 +124,7 @@ class PositionItem: ) -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class Assets: total: float = 0.0 available: float = 0.0 @@ -136,7 +138,7 @@ class Assets: ) -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class Portfolio: assets: Assets positions: dict[str, PositionItem] @@ -152,7 +154,7 @@ def _trade_datetime(data: dict[str, Any]) -> datetime | None: return None -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class Tick: last_price: float = 0.0 last_close: float = 0.0 @@ -169,7 +171,7 @@ class Tick: ) -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class HistoryDataRequest: length: int = 10 period: str = "" @@ -178,7 +180,7 @@ class HistoryDataRequest: skip_paused: bool = True -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class MarketDataRequest: fields: list[str] = field(default_factory=list) stocks: list[str] = field(default_factory=list) @@ -189,7 +191,7 @@ class MarketDataRequest: count: int = 0 -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class FinancialDataRequest: tabname: str = ""; colname: str = ""; market: str = ""; code: str = "" report_type: str = ""; barpos: int = 0 @@ -197,22 +199,22 @@ class FinancialDataRequest: start_date: str = ""; end_date: str = "" -@dataclass(frozen=True, slots=True) +@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(frozen=True, slots=True) +@dataclass(slots=True) class BSMPriceRequest: option_type: str; object_prices: Any; strike_price: float; risk_free: float; sigma: float; days: int; dividend: float -@dataclass(frozen=True, slots=True) +@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(frozen=True, slots=True) +@dataclass(slots=True) class LocalDataRequest: stock_code: str; start_time: str = ""; end_time: str = ""; period: str = ""; divid_type: str = ""; count: int = 0 diff --git a/py-client/sdk/portfolio.py b/py-client/sdk/portfolio.py index e5b90e5..e4e3df2 100644 --- a/py-client/sdk/portfolio.py +++ b/py-client/sdk/portfolio.py @@ -6,6 +6,14 @@ from .models import Assets, OrderItem, Portfolio, PositionItem class PortfolioMixin: + def org(self, datatype: str) -> list[dict[str, Any]]: + """查询 account、order、deal 或 position,返回原始字段字典列表。""" + datatype = str(datatype).strip().lower() + if datatype not in {"account", "order", "deal", "position"}: + raise ValueError(f"unsupported org datatype: {datatype}") + response = self._get_json(f"/api/portfolio/org/{datatype}") + return response["data"] + def portfolio(self) -> Portfolio: data = self._get_json("/api/portfolio") or {} positions = { diff --git a/py-client/strategy/ipo/__pycache__/boot.cpython-311.pyc b/py-client/strategy/ipo/__pycache__/boot.cpython-311.pyc index 6c6e81c..129bc60 100644 Binary files a/py-client/strategy/ipo/__pycache__/boot.cpython-311.pyc and b/py-client/strategy/ipo/__pycache__/boot.cpython-311.pyc differ diff --git a/py-client/strategy/trend/__pycache__/boot.cpython-311.pyc b/py-client/strategy/trend/__pycache__/boot.cpython-311.pyc index fc08c1f..aea9cea 100644 Binary files a/py-client/strategy/trend/__pycache__/boot.cpython-311.pyc and b/py-client/strategy/trend/__pycache__/boot.cpython-311.pyc differ diff --git a/py-client/strategy/trend/__pycache__/open.cpython-311.pyc b/py-client/strategy/trend/__pycache__/open.cpython-311.pyc index 9159fce..2cd8ab3 100644 Binary files a/py-client/strategy/trend/__pycache__/open.cpython-311.pyc and b/py-client/strategy/trend/__pycache__/open.cpython-311.pyc differ diff --git a/py-client/strategy/trend/__pycache__/order.cpython-311.pyc b/py-client/strategy/trend/__pycache__/order.cpython-311.pyc index ca55959..94c5d5b 100644 Binary files a/py-client/strategy/trend/__pycache__/order.cpython-311.pyc and b/py-client/strategy/trend/__pycache__/order.cpython-311.pyc differ diff --git a/py-client/strategy/trend/__pycache__/positions.cpython-311.pyc b/py-client/strategy/trend/__pycache__/positions.cpython-311.pyc index 777bd75..6b08e8c 100644 Binary files a/py-client/strategy/trend/__pycache__/positions.cpython-311.pyc and b/py-client/strategy/trend/__pycache__/positions.cpython-311.pyc differ diff --git a/py-client/strategy/trend/__pycache__/runtime.cpython-311.pyc b/py-client/strategy/trend/__pycache__/runtime.cpython-311.pyc index 5659c4d..092b15f 100644 Binary files a/py-client/strategy/trend/__pycache__/runtime.cpython-311.pyc and b/py-client/strategy/trend/__pycache__/runtime.cpython-311.pyc differ diff --git a/py-client/strategy/trend/__pycache__/watch.cpython-311.pyc b/py-client/strategy/trend/__pycache__/watch.cpython-311.pyc index 01ae40b..aafc0fe 100644 Binary files a/py-client/strategy/trend/__pycache__/watch.cpython-311.pyc and b/py-client/strategy/trend/__pycache__/watch.cpython-311.pyc differ diff --git a/py-client/strategy/trend/boot.py b/py-client/strategy/trend/boot.py index 2cf4418..55c8074 100644 --- a/py-client/strategy/trend/boot.py +++ b/py-client/strategy/trend/boot.py @@ -39,7 +39,7 @@ def Overview(assets, positions, account_cfg=None) -> None: for position in positions: if position.volume <= 0: continue - log.info("[启动] %s %s,持仓=%d,可用=%d,成本=%.2f,现价=%.2f,盈亏=%.2f%%", position.stock_code, position.stock_name, position.volume, position.can_use_volume, position.open_price, position.last_price, position.profit_rate * 100) + log.info("[启动] %s %s %s,持仓=%d,可用=%d,成本=%.2f(%.2f),现价=%.2f,盈亏=%.2f%%",position.trade_id, position.stock_code, position.stock_name, position.volume, position.can_use_volume, position.open_price,position.open_cost, position.last_price, position.profit_rate * 100) diff --git a/py-client/strategy/trend/order.py b/py-client/strategy/trend/order.py index de8bef6..ef0065d 100644 --- a/py-client/strategy/trend/order.py +++ b/py-client/strategy/trend/order.py @@ -19,7 +19,7 @@ TRACKED_STATUSES = BUSY_STATUSES | COMPLETED_STATUSES CANCELABLE_STATUSES = {"49", "50", "51", "52"} -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class PlaceOrderRequest: """``OrderBook.place`` 提交委托所需的全部参数。""" op: int diff --git a/py-client/strategy/trend/positions.py b/py-client/strategy/trend/positions.py index 9904f20..d5e4cdd 100644 --- a/py-client/strategy/trend/positions.py +++ b/py-client/strategy/trend/positions.py @@ -15,7 +15,7 @@ import logging as log LOSS_TIERS = [-50.0] -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class TradeDecision: """一次止盈或补仓判断的统一结果。""" diff --git a/py-client/strategy/trend/runtime.py b/py-client/strategy/trend/runtime.py index 0bd2189..f650c15 100644 --- a/py-client/strategy/trend/runtime.py +++ b/py-client/strategy/trend/runtime.py @@ -13,7 +13,7 @@ from .order import OrderBook from .watch import DipWatch -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class Runtime: """集中保存趋势策略运行期间共享的依赖和状态。 diff --git a/py-client/strategy/trend/watch.py b/py-client/strategy/trend/watch.py index d883e3e..b5f3626 100644 --- a/py-client/strategy/trend/watch.py +++ b/py-client/strategy/trend/watch.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from threading import Lock -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class _Entry: last_close: float expires_at: datetime diff --git a/py-client/strategy/zt/__pycache__/runtime.cpython-311.pyc b/py-client/strategy/zt/__pycache__/runtime.cpython-311.pyc index bcde782..53255cb 100644 Binary files a/py-client/strategy/zt/__pycache__/runtime.cpython-311.pyc and b/py-client/strategy/zt/__pycache__/runtime.cpython-311.pyc differ diff --git a/py-client/strategy/zt/__pycache__/state.cpython-311.pyc b/py-client/strategy/zt/__pycache__/state.cpython-311.pyc index ffe2e43..2636d00 100644 Binary files a/py-client/strategy/zt/__pycache__/state.cpython-311.pyc and b/py-client/strategy/zt/__pycache__/state.cpython-311.pyc differ diff --git a/py-client/strategy/zt/runtime.py b/py-client/strategy/zt/runtime.py index ab685f5..cc86012 100644 --- a/py-client/strategy/zt/runtime.py +++ b/py-client/strategy/zt/runtime.py @@ -11,7 +11,7 @@ from strategy.trend.watch import DipWatch from .state import TState -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class Runtime: client: Client global_cfg: GlobalConfig diff --git a/py-client/strategy/zt/state.py b/py-client/strategy/zt/state.py index 9f3531f..84de7b1 100644 --- a/py-client/strategy/zt/state.py +++ b/py-client/strategy/zt/state.py @@ -17,7 +17,7 @@ BUYING = "BUYING" DONE = "DONE" -@dataclass(frozen=True, slots=True) +@dataclass(slots=True) class TStateItem: code: str base_qty: int = 0 diff --git a/py-client/test.py b/py-client/test.py index 378686e..0343948 100644 --- a/py-client/test.py +++ b/py-client/test.py @@ -1,4 +1,5 @@ from datetime import datetime +import json from sdk import Client @@ -8,8 +9,20 @@ TOKEN = "QMTbyYanweidong" STOCK_CODE = "000021.SZ" VOLUME = 100 - def main() -> None: + """只读查询四类原始数据,输出 JSON 便于核对字段。""" + with Client(BASE_URL, TOKEN) as client: + for datatype in ("account", "order", "deal", "position"): + print(f"\n=== {datatype} ===") + try: + result = client.org(datatype) + print(f"记录数:{len(result)}") + print(json.dumps(result, ensure_ascii=False, indent=2, default=str)) + except Exception as exc: + print(f"查询失败:{exc}") + + +def main1() -> None: order = { "opType": 23, "orderType": 1101,