This commit is contained in:
2026-09-06 11:34:23 +08:00
parent b38ff12f2a
commit ac9e9193ad
32 changed files with 235 additions and 61 deletions

View File

@@ -186,6 +186,24 @@ class HoldingHandler(BaseHandler):
holding = format_holding(positions) holding = format_holding(positions)
self.write_json({"data": holding}) 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 # get_trade_detail_data('account') - Query account assets
class AssetsHandler(BaseHandler): class AssetsHandler(BaseHandler):
def get(self): def get(self):
@@ -318,6 +336,7 @@ def format_holding(positions):
'Direction': position.m_nDirection, 'Direction': position.m_nDirection,
'Volume': position.m_nVolume, 'Volume': position.m_nVolume,
'OpenPrice': position.m_dOpenPrice, 'OpenPrice': position.m_dOpenPrice,
'OpenCost':position.m_dOpenCost,
'FloatProfit': position.m_dFloatProfit, 'FloatProfit': position.m_dFloatProfit,
'MarketValue': position.m_dMarketValue, 'MarketValue': position.m_dMarketValue,
'StockHolder': position.m_strStockHolder, 'StockHolder': position.m_strStockHolder,
@@ -372,6 +391,7 @@ def make_app():
# Portfolio # Portfolio
(r"/api/portfolio", PortfolioHandler), (r"/api/portfolio", PortfolioHandler),
(r"/api/portfolio/positions", HoldingHandler), (r"/api/portfolio/positions", HoldingHandler),
(r"/api/portfolio/org/(account|order|deal|position)",OrgHandler),
(r"/api/portfolio/assets", AssetsHandler), (r"/api/portfolio/assets", AssetsHandler),
(r"/api/portfolio/order", OrderHandler), (r"/api/portfolio/order", OrderHandler),
(r"/api/portfolio/deal", DealHandler), (r"/api/portfolio/deal", DealHandler),

View File

@@ -2,17 +2,18 @@
import json import json
import locale import locale
import os import os
import datetime import sys
from pathlib import Path from urllib.request import Request, urlopen
from tornado.web import Application, RequestHandler, HTTPError from tornado.web import Application, RequestHandler, HTTPError
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
import logging import logging
# Configuration # Configuration
ACCOUNT_ID = os.environ.get('QMT_ACCOUNT_ID', '') 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" TOKEN="QMTbyYanweidong"
PORT = 10086 PORT = 10086
PASS_CODES_URL = "http://139.224.247.176:13499/a/pass_codes"
# =================================== # ===================================
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@@ -32,6 +33,36 @@ def safe_call(func, *args, **kwargs):
) from e ) 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 ============= # ============= BaseHandler =============
AUTH_EXEMPT = set() AUTH_EXEMPT = set()
@@ -61,6 +92,14 @@ class BaseHandler(RequestHandler):
def acc(self): def acc(self):
return self.application.accountID 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 ============= # ============= 1. ContextInfo properties =============
# "/api/v2/context/info" # "/api/v2/context/info"
@@ -80,7 +119,7 @@ class ContextInfoHandler(BaseHandler):
"timetag":ctx.timetag, "timetag":ctx.timetag,
"universe": ctx.get_universe(), "universe": ctx.get_universe(),
} }
self.write(data, separators=(',', ':'), ensure_ascii=False) self.write_json(data)
# ============= 2. Data queries (ContextInfo get_*) ============= # ============= 2. Data queries (ContextInfo get_*) =============
STOCK_HANDLER = { STOCK_HANDLER = {
@@ -118,25 +157,26 @@ class StockGetHandler(BaseHandler):
# 响应 # 响应
self.write(json.dumps({ self.write_json({
"stock_code": query_vals, "stock_code": query_vals,
"ref": result "ref": result
}, separators=(',', ':'), ensure_ascii=False, default=str)) }, default=str)
# Aggregate assets, positions, and orders in one request. # Aggregate assets, positions, and orders in one request.
class PortfolioHandler(BaseHandler): class PortfolioHandler(BaseHandler):
def get(self): def get(self):
account_data = safe_call(get_trade_detail_data, self.acc(), 'stock', 'account') account_id = self.acc()
positions = safe_call(get_trade_detail_data, self.acc(), 'stock', 'position') or [] account_data = safe_call(get_trade_detail_data, account_id, 'stock', 'account')
orders = safe_call(get_trade_detail_data, self.acc(), 'stock', 'order') or [] 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 = { result = {
"assets": format_assets(account_data), "assets": format_assets(account_data),
"positions": format_holding(positions), "positions": format_holding(positions),
"orders": [fixed_fields(order) for order in orders], "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 # get_trade_detail_data('position') - Query positions in the wrapped format
@@ -144,13 +184,31 @@ class HoldingHandler(BaseHandler):
def get(self): def get(self):
positions = safe_call(get_trade_detail_data, self.acc(), 'stock', 'position') or [] positions = safe_call(get_trade_detail_data, self.acc(), 'stock', 'position') or []
holding = format_holding(positions) 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 # get_trade_detail_data('account') - Query account assets
class AssetsHandler(BaseHandler): class AssetsHandler(BaseHandler):
def get(self): def get(self):
_data = safe_call(get_trade_detail_data, self.acc(), 'stock', 'account') _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): class OrderHandler(BaseHandler):
def get(self): def get(self):
@@ -158,13 +216,13 @@ class OrderHandler(BaseHandler):
if ret is None: if ret is None:
ret = [] ret = []
result = [fixed_fields(obj) for obj in 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): class DealHandler(BaseHandler):
def get(self): def get(self):
deals = safe_call(get_trade_detail_data, self.acc(), 'stock', 'deal') or [] deals = safe_call(get_trade_detail_data, self.acc(), 'stock', 'deal') or []
rets = [fixed_fields(deal) for deal in deals] 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 # ContextInfo.get_full_tick() - Get full tick data
class FullTickHandler(BaseHandler): class FullTickHandler(BaseHandler):
@@ -176,7 +234,7 @@ class FullTickHandler(BaseHandler):
ret = safe_call(self.ctx().get_full_tick, stocks) ret = safe_call(self.ctx().get_full_tick, stocks)
if not ret: if not ret:
raise HTTPError(500, "Failed to get tick data") 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 # passorder() - Submit a general trading order
class PassorderHandler(BaseHandler): class PassorderHandler(BaseHandler):
@@ -204,14 +262,14 @@ class PassorderHandler(BaseHandler):
logger.exception("passorder failed") logger.exception("passorder failed")
raise HTTPError(502, reason="QMT order submission failed") from e raise HTTPError(502, reason="QMT order submission failed") from e
self.write(json.dumps({ self.write_json({
"status": "success", "status": "success",
"opType": opType, "opType": opType,
"stockCode": stockCode, "stockCode": stockCode,
"strategy_name": strategy_name, "strategy_name": strategy_name,
"local_order_id": order_id, "local_order_id": order_id,
"order_ref": str(order_ref) "order_ref": str(order_ref)
}, separators=(',', ':'), ensure_ascii=False)) })
class CancelByIdHandler(BaseHandler): class CancelByIdHandler(BaseHandler):
@@ -223,21 +281,28 @@ class CancelByIdHandler(BaseHandler):
raise HTTPError(400, "order_id cannot be empty") raise HTTPError(400, "order_id cannot be empty")
cancelable = safe_call(can_cancel_order, order_id, self.acc(), 'stock') cancelable = safe_call(can_cancel_order, order_id, self.acc(), 'stock')
if not cancelable: if not cancelable:
self.write(json.dumps({ self.write_json({
"status": "failed", "order_id": order_id, "status": "failed", "order_id": order_id,
"message": "Order does not exist or cannot currently be canceled" "message": "Order does not exist or cannot currently be canceled"
}, separators=(',', ':'), ensure_ascii=False)) })
return return
result = safe_call(cancel, order_id, self.acc(), 'stock', self.ctx()) 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", "status": "success" if result is not False else "failed",
"order_id": order_id, "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 # sys: Python version information
class PythonVersionHandler(BaseHandler): class PythonVersionHandler(BaseHandler):
def get(self): def get(self):
import sys
version_info = { version_info = {
"python_version": sys.version, "python_version": sys.version,
"python_version_info": { "python_version_info": {
@@ -248,7 +313,73 @@ class PythonVersionHandler(BaseHandler):
"serial": sys.version_info.serial, "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 ============= # ============= Route registration =============
def make_app(): def make_app():
@@ -257,14 +388,16 @@ def make_app():
(r"/api/context/info", ContextInfoHandler), (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), (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", PortfolioHandler),
(r"/api/portfolio/positions", HoldingHandler), (r"/api/portfolio/positions", HoldingHandler),
(r"/api/portfolio/org/(account|order|deal|position)",OrgHandler),
(r"/api/portfolio/assets", AssetsHandler), (r"/api/portfolio/assets", AssetsHandler),
(r"/api/portfolio/order", OrderHandler), (r"/api/portfolio/order", OrderHandler),
(r"/api/portfolio/deal", DealHandler), (r"/api/portfolio/deal", DealHandler),
(r"/api/data/full_tick", FullTickHandler), (r"/api/data/full_tick", FullTickHandler),
(r"/api/trade/ipo_data", IpoDataHandler),
(r"/api/trade/cancel_by_id", CancelByIdHandler), (r"/api/trade/cancel_by_id", CancelByIdHandler),
(r"/api/trade/passorder", PassorderHandler), (r"/api/trade/passorder", PassorderHandler),
@@ -286,10 +419,8 @@ def init(ContextInfo):
try: try:
ContextInfo.accountID = ACCOUNT_ID ContextInfo.accountID = ACCOUNT_ID
ContextInfo.set_account(ACCOUNT_ID) ContextInfo.set_account(ACCOUNT_ID)
# Load the symbol universe only when configured.
pass_codes_path = Path(DATA_DIR) / "pass_codes.json" codes = get_pass_codes(ContextInfo.accountID)
with pass_codes_path.open("r", encoding="utf-8") as stream:
codes = json.load(stream)
ContextInfo.set_universe(list(codes)) ContextInfo.set_universe(list(codes))
# Api App # Api App

View File

@@ -15,7 +15,7 @@ from sdk import OrderItem, PositionItem
PENDING_TIME_OUT = 3600 PENDING_TIME_OUT = 3600
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class StateItem: class StateItem:
code: str code: str
base_order_id: str = "" base_order_id: str = ""
@@ -27,7 +27,7 @@ class StateItem:
added_cost: float = 0.0 added_cost: float = 0.0
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class PendingOrder: class PendingOrder:
order_id: str order_id: str
code: str code: str

View File

@@ -7,7 +7,7 @@ from pathlib import Path
import yaml import yaml
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class SignalConfig: class SignalConfig:
"""单个交易信号的数据源及开仓限制配置。""" """单个交易信号的数据源及开仓限制配置。"""
@@ -21,7 +21,7 @@ class SignalConfig:
gt_last_price_is_open: bool = False gt_last_price_is_open: bool = False
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class GlobalConfig: class GlobalConfig:
"""所有主机共享的系统配置。""" """所有主机共享的系统配置。"""
@@ -37,7 +37,7 @@ class GlobalConfig:
signals: dict[str, SignalConfig] = field(default_factory=dict) signals: dict[str, SignalConfig] = field(default_factory=dict)
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class AccountConfig: class AccountConfig:
"""当前主机所使用的账户及交易策略参数。""" """当前主机所使用的账户及交易策略参数。"""

View File

@@ -20,7 +20,7 @@ class GridState(str, Enum):
STEADY = "steady" # 仍处于当前峰值网格,继续持有 STEADY = "steady" # 仍处于当前峰值网格,继续持有
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class GridObservation: class GridObservation:
"""一次网格观察的不可变结果。""" """一次网格观察的不可变结果。"""

View File

@@ -4,12 +4,12 @@ import secrets
from .http import get_json from .http import get_json
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class SignalItem: class SignalItem:
signal_key: str = ""; code: str = ""; name: str = ""; desc: str = ""; last_close: float = 0 signal_key: str = ""; code: str = ""; name: str = ""; desc: str = ""; last_close: float = 0
tech_indicator: dict[str, float] = field(default_factory=dict) tech_indicator: dict[str, float] = field(default_factory=dict)
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class SignalResult: class SignalResult:
code: str = ""; total: int = 0; updated: str = ""; data: dict[str, SignalItem] = field(default_factory=dict); message: str = "" code: str = ""; total: int = 0; updated: str = ""; data: dict[str, SignalItem] = field(default_factory=dict); message: str = ""

View File

@@ -38,7 +38,7 @@ from strategy.trend.boot import StartTrend
from strategy.zt.boot import StartZT from strategy.zt.boot import StartZT
from strategy.ipo import AutoBuyIpo from strategy.ipo import AutoBuyIpo
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class StrategyDefinition: class StrategyDefinition:
mutex_scope: str mutex_scope: str
start_strategy: object start_strategy: object

View File

@@ -11,7 +11,7 @@ def _number(value: Any, kind: type = float) -> Any:
return kind() return kind()
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class OrderItem: class OrderItem:
"""由 QMT 委托明细解析得到的标准订单记录。""" """由 QMT 委托明细解析得到的标准订单记录。"""
id: str id: str
@@ -64,13 +64,14 @@ class OrderItem:
) )
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class PositionItem: class PositionItem:
stock_code: str = "" stock_code: str = ""
stock_name: str = "" stock_name: str = ""
trade_id:str = "" trade_id:str = ""
direction: Any = None direction: Any = None
volume: int = 0 volume: int = 0
open_cost: float = 0.0
open_price: float = 0.0 open_price: float = 0.0
float_profit: float = 0.0 float_profit: float = 0.0
market_value: 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": def from_dict(cls, data: dict[str, Any], code: str = "") -> "PositionItem":
return cls( return cls(
stock_code=str(data.get("StockCode") or code), stock_name=str(data.get("StockName") or ""), 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), direction=data.get("Direction"), volume=_number(data.get("Volume"), int),
open_price=_number(data.get("OpenPrice")), float_profit=_number(data.get("FloatProfit")), 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 ""), 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 ""), trade_id=str(data.get("TradeID") or ""),
direction=data.get("Direction"), direction=data.get("Direction"),
volume=_number(data.get("Volume"), int), volume=_number(data.get("Volume"), int),
open_cost=_number(data.get("OpenCost")),
open_price=_number(data.get("OpenPrice")), open_price=_number(data.get("OpenPrice")),
float_profit=_number(data.get("FloatProfit")), float_profit=_number(data.get("FloatProfit")),
market_value=_number(data.get("MarketValue")), market_value=_number(data.get("MarketValue")),
@@ -122,7 +124,7 @@ class PositionItem:
) )
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class Assets: class Assets:
total: float = 0.0 total: float = 0.0
available: float = 0.0 available: float = 0.0
@@ -136,7 +138,7 @@ class Assets:
) )
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class Portfolio: class Portfolio:
assets: Assets assets: Assets
positions: dict[str, PositionItem] positions: dict[str, PositionItem]
@@ -152,7 +154,7 @@ def _trade_datetime(data: dict[str, Any]) -> datetime | None:
return None return None
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class Tick: class Tick:
last_price: float = 0.0 last_price: float = 0.0
last_close: float = 0.0 last_close: float = 0.0
@@ -169,7 +171,7 @@ class Tick:
) )
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class HistoryDataRequest: class HistoryDataRequest:
length: int = 10 length: int = 10
period: str = "" period: str = ""
@@ -178,7 +180,7 @@ class HistoryDataRequest:
skip_paused: bool = True skip_paused: bool = True
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class MarketDataRequest: class MarketDataRequest:
fields: list[str] = field(default_factory=list) fields: list[str] = field(default_factory=list)
stocks: list[str] = field(default_factory=list) stocks: list[str] = field(default_factory=list)
@@ -189,7 +191,7 @@ class MarketDataRequest:
count: int = 0 count: int = 0
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class FinancialDataRequest: class FinancialDataRequest:
tabname: str = ""; colname: str = ""; market: str = ""; code: str = "" tabname: str = ""; colname: str = ""; market: str = ""; code: str = ""
report_type: str = ""; barpos: int = 0 report_type: str = ""; barpos: int = 0
@@ -197,22 +199,22 @@ class FinancialDataRequest:
start_date: str = ""; end_date: str = "" start_date: str = ""; end_date: str = ""
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class FactorDataRequest: class FactorDataRequest:
field_list: list[str] = field(default_factory=list); stock_list: list[str] = field(default_factory=list) 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 = "" stock_code: str = ""; start_date: str = ""; end_date: str = ""
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class BSMPriceRequest: class BSMPriceRequest:
option_type: str; object_prices: Any; strike_price: float; risk_free: float; sigma: float; days: int; dividend: float 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: class BSMIVRequest:
option_type: str; object_prices: float; strike_price: float; option_price: float; risk_free: float; days: int; dividend: float 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: class LocalDataRequest:
stock_code: str; start_time: str = ""; end_time: str = ""; period: str = ""; divid_type: str = ""; count: int = 0 stock_code: str; start_time: str = ""; end_time: str = ""; period: str = ""; divid_type: str = ""; count: int = 0

View File

@@ -6,6 +6,14 @@ from .models import Assets, OrderItem, Portfolio, PositionItem
class PortfolioMixin: 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: def portfolio(self) -> Portfolio:
data = self._get_json("/api/portfolio") or {} data = self._get_json("/api/portfolio") or {}
positions = { positions = {

View File

@@ -39,7 +39,7 @@ def Overview(assets, positions, account_cfg=None) -> None:
for position in positions: for position in positions:
if position.volume <= 0: if position.volume <= 0:
continue 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)

View File

@@ -19,7 +19,7 @@ TRACKED_STATUSES = BUSY_STATUSES | COMPLETED_STATUSES
CANCELABLE_STATUSES = {"49", "50", "51", "52"} CANCELABLE_STATUSES = {"49", "50", "51", "52"}
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class PlaceOrderRequest: class PlaceOrderRequest:
"""``OrderBook.place`` 提交委托所需的全部参数。""" """``OrderBook.place`` 提交委托所需的全部参数。"""
op: int op: int

View File

@@ -15,7 +15,7 @@ import logging as log
LOSS_TIERS = [-50.0] LOSS_TIERS = [-50.0]
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class TradeDecision: class TradeDecision:
"""一次止盈或补仓判断的统一结果。""" """一次止盈或补仓判断的统一结果。"""

View File

@@ -13,7 +13,7 @@ from .order import OrderBook
from .watch import DipWatch from .watch import DipWatch
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class Runtime: class Runtime:
"""集中保存趋势策略运行期间共享的依赖和状态。 """集中保存趋势策略运行期间共享的依赖和状态。

View File

@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
from threading import Lock from threading import Lock
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class _Entry: class _Entry:
last_close: float last_close: float
expires_at: datetime expires_at: datetime

View File

@@ -11,7 +11,7 @@ from strategy.trend.watch import DipWatch
from .state import TState from .state import TState
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class Runtime: class Runtime:
client: Client client: Client
global_cfg: GlobalConfig global_cfg: GlobalConfig

View File

@@ -17,7 +17,7 @@ BUYING = "BUYING"
DONE = "DONE" DONE = "DONE"
@dataclass(frozen=True, slots=True) @dataclass(slots=True)
class TStateItem: class TStateItem:
code: str code: str
base_qty: int = 0 base_qty: int = 0

View File

@@ -1,4 +1,5 @@
from datetime import datetime from datetime import datetime
import json
from sdk import Client from sdk import Client
@@ -8,8 +9,20 @@ TOKEN = "QMTbyYanweidong"
STOCK_CODE = "000021.SZ" STOCK_CODE = "000021.SZ"
VOLUME = 100 VOLUME = 100
def main() -> None: 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 = { order = {
"opType": 23, "opType": 23,
"orderType": 1101, "orderType": 1101,