diff --git a/api/qmt_rest_new.py b/api/qmt_rest_new.py index bedf133..fb705aa 100644 --- a/api/qmt_rest_new.py +++ b/api/qmt_rest_new.py @@ -26,10 +26,9 @@ def safe_call(func, *args, **kwargs): except HTTPError: raise except Exception as e: - logger.exception("%s call failed", func.__name__) raise HTTPError( - 502, - reason="QMT upstream call failed: %s" % func.__name__, + 500, + reason="QMT: %s call failed." % func.__name__, ) from e @@ -48,16 +47,13 @@ class BaseHandler(RequestHandler): if self.__class__ not in AUTH_EXEMPT: token = self.request.headers.get('X-Token') if token != TOKEN: - raise HTTPError(401, "Authentication failed: invalid or missing token") + raise HTTPError(500, "Authentication failed: invalid or missing token") def set_default_headers(self): self.set_header("Content-Type", "application/json; charset=utf-8") - def write_error(self, status_code, **kwargs): - self.finish(json.dumps({ - "error": self._reason, - "status_code": status_code - }, separators=(',', ':'), ensure_ascii=False)) + def write_error(self, **kwargs): + self.finish(self._reason) def ctx(self): return self.application.ContextInfo @@ -67,6 +63,7 @@ class BaseHandler(RequestHandler): # ============= 1. ContextInfo properties ============= +# "/api/v2/context/info" class ContextInfoHandler(BaseHandler): def get(self): ctx = self.ctx() @@ -80,38 +77,47 @@ class ContextInfoHandler(BaseHandler): "do_back_test": ctx.do_back_test, "benchmark": ctx.benchmark, "capital": ctx.capital, + "timetag":ctx.timetag, "universe": ctx.get_universe(), } self.write(data, separators=(',', ':'), ensure_ascii=False) - - # ============= 2. Data queries (ContextInfo get_*) ============= -# ContextInfo.get_stock_name() - Get a stock name by symbol -class StockNameHandler(BaseHandler): - def post(self): - data = json.loads(self.request.body) - stockcode = data.get('stockcode', '') - ret = safe_call(self.ctx().get_stock_name, stockcode) - self.write(json.dumps({"stockcode": stockcode, "name": ret}, separators=(',', ':'), ensure_ascii=False)) - -# get_open_date() - Get the listing date by symbol -class OpenDateHandler(BaseHandler): - def post(self): - data = json.loads(self.request.body) - stockcode = data.get('stockcode', '') - ret = safe_call(get_open_date, stockcode) - self.write(json.dumps({"stockcode": stockcode, "open_date": ret}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.get_last_volume() - Get the latest outstanding shares -class LastVolumeHandler(BaseHandler): - def post(self): - data = json.loads(self.request.body) - stockcode = data.get('stockcode', '') - ret = safe_call(self.ctx().get_last_volume, stockcode) - if ret is None: - raise HTTPError(500, "Failed to get outstanding shares") - self.write(json.dumps({"stockcode": stockcode, "last_volume": ret}, separators=(',', ':'), ensure_ascii=False)) +STOCK_HANDLER = { + # stock_name|open_date|last_volume|total_share|svol|bvol + "stock_name": ("get_stock_name", "stock_code", True), + "open_date": ("get_open_date", "stock_code", True), + "last_volume": ("get_last_volume", "stock_code", True), + "total_share": ("get_total_share", "stock_code", True), + "svol": ("get_svol", "stock_code", True), + "bvol": ("get_bvol", "stock_code", True), +} +# "/api/v2/get/*" ContextInfo.* Get a stock name by symbol +class StockGetHandler(BaseHandler): + def get(self,handler_type): + # 快速路径:配置查找 + cfg = STOCK_HANDLER.get(handler_type) + if not cfg: + self.write_error(500, reason="Unknown API") + return + + # 参数验证 + + query_vals = self.get_query_argument(cfg.index[1], "").strip() + if not query_vals: + self.write_error(500, reason=f"{cfg.index[1]} required") + return + + # 方法调用 + method = getattr(self.ctx(), cfg.index[0]) + result = safe_call(method, query_vals) + + + # 响应 + self.finish({ + "stock_code": query_vals, + "ref": result + }) # ContextInfo.get_bar_timetag() - Get the bar timestamp class BarTimetagHandler(BaseHandler): @@ -121,12 +127,6 @@ class BarTimetagHandler(BaseHandler): ret = safe_call(self.ctx().get_bar_timetag, index) self.write(json.dumps({"index": index, "timetag": ret}, separators=(',', ':'), ensure_ascii=False)) -# ContextInfo.get_tick_timetag() - Get the latest tick timestamp -class TickTimetagHandler(BaseHandler): - def get(self): - ret = safe_call(self.ctx().get_tick_timetag) - self.write(json.dumps({"timetag": ret}, separators=(',', ':'), ensure_ascii=False)) - # ContextInfo.get_sector() - Get index constituents class SectorHandler(BaseHandler): def post(self): @@ -285,14 +285,6 @@ class TimetagToDatetimeHandler(BaseHandler): ret = safe_call(timetag_to_datetime, timetag, fmt) self.write(json.dumps({"timetag": timetag, "datetime": ret}, separators=(',', ':'), ensure_ascii=False)) -# ContextInfo.get_total_share() - Get total shares -class TotalShareHandler(BaseHandler): - def post(self): - data = json.loads(self.request.body) - stockcode = data.get('stockcode', '') - ret = safe_call(self.ctx().get_total_share, stockcode) - self.write(json.dumps({"stockcode": stockcode, "total_share": ret}, separators=(',', ':'), ensure_ascii=False)) - # ContextInfo.get_trading_dates() - Get the trading-day list class TradingDatesHandler(BaseHandler): def post(self): @@ -306,22 +298,6 @@ class TradingDatesHandler(BaseHandler): ret = safe_call(self.ctx().get_trading_dates, stockcode, start_date, end_date, count_int, period) self.write(json.dumps({"dates": ret or []}, separators=(',', ':'), ensure_ascii=False)) -# ContextInfo.get_svol() - Get sell-side volume -class SvolHandler(BaseHandler): - def post(self): - data = json.loads(self.request.body) - stockcode = data.get('stockcode', '') - ret = safe_call(self.ctx().get_svol, stockcode) - self.write(json.dumps({"stockcode": stockcode, "svol": ret}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.get_bvol() - Get buy-side volume -class BvolHandler(BaseHandler): - def post(self): - data = json.loads(self.request.body) - stockcode = data.get('stockcode', '') - ret = safe_call(self.ctx().get_bvol, stockcode) - self.write(json.dumps({"stockcode": stockcode, "bvol": ret}, separators=(',', ':'), ensure_ascii=False)) - # ContextInfo.get_longhubang() - Get Dragon-Tiger List data class LonghubangHandler(BaseHandler): def post(self): @@ -1272,6 +1248,9 @@ def make_app(): # V2 (r"/api/v2/positions", HoldingHandler), (r"/api/v2/assets", AssetsHandler), + # ContextInfo properties + (r"/api/v2/context/info", ContextInfoHandler), + (r"/api/v2/get/(stock_name|open_date|last_volume|total_share|svol|bvol)", StockGetHandler), # Legacy compatibility routes (r"/api/holding", HoldingHandler), @@ -1283,15 +1262,8 @@ def make_app(): (r"/api/order/cancel_by_id", CancelByIdHandler), (r"/api/order/deal", DealHandler), - # ContextInfo properties - (r"/api/context/info", ContextInfoHandler), - # Data queries - (r"/api/data/stock_name", StockNameHandler), - (r"/api/data/open_date", OpenDateHandler), - (r"/api/data/last_volume", LastVolumeHandler), (r"/api/data/bar_timetag", BarTimetagHandler), - (r"/api/data/tick_timetag", TickTimetagHandler), (r"/api/data/sector", SectorHandler), (r"/api/data/industry", IndustryHandler), (r"/api/data/stock_list_in_sector", StockListInSectorHandler), @@ -1306,10 +1278,7 @@ def make_app(): (r"/api/data/divid_factors", DividFactorsHandler), (r"/api/data/main_contract", MainContractHandler), (r"/api/data/timetag_to_datetime", TimetagToDatetimeHandler), - (r"/api/data/total_share", TotalShareHandler), (r"/api/data/trading_dates", TradingDatesHandler), - (r"/api/data/svol", SvolHandler), - (r"/api/data/bvol", BvolHandler), (r"/api/data/longhubang", LonghubangHandler), (r"/api/data/top10_share_holder", Top10ShareHolderHandler), (r"/api/data/option_detail", OptionDetailHandler), diff --git a/api/qmt_rest_rele.py b/api/qmt_rest_rele.py index 0f14d60..bedf133 100644 --- a/api/qmt_rest_rele.py +++ b/api/qmt_rest_rele.py @@ -67,55 +67,23 @@ class BaseHandler(RequestHandler): # ============= 1. ContextInfo properties ============= -# ContextInfo.period - Get the current period -class ContextPeriodHandler(BaseHandler): +class ContextInfoHandler(BaseHandler): def get(self): - self.write(json.dumps({"period": self.ctx().period}, separators=(',', ':'), ensure_ascii=False)) + ctx = self.ctx() + data = { + "period": ctx.period, + "barpos": ctx.barpos, + "time_tick_size": ctx.time_tick_size, + "stockcode": ctx.stockcode, + "dividend_type": ctx.dividend_type, + "market": ctx.market, + "do_back_test": ctx.do_back_test, + "benchmark": ctx.benchmark, + "capital": ctx.capital, + "universe": ctx.get_universe(), + } + self.write(data, separators=(',', ':'), ensure_ascii=False) -# ContextInfo.barpos - Get the current bar index -class ContextBarposHandler(BaseHandler): - def get(self): - self.write(json.dumps({"barpos": self.ctx().barpos}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.time_tick_size - Get the current bar count -class ContextTimeTickSizeHandler(BaseHandler): - def get(self): - self.write(json.dumps({"time_tick_size": self.ctx().time_tick_size}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.stockcode - Get the current chart symbol -class ContextStockCodeHandler(BaseHandler): - def get(self): - self.write(json.dumps({"stockcode": self.ctx().stockcode}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.dividend_type - Get the current adjustment mode -class ContextDividendTypeHandler(BaseHandler): - def get(self): - self.write(json.dumps({"dividend_type": self.ctx().dividend_type}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.market - Get the current chart market -class ContextMarketHandler(BaseHandler): - def get(self): - self.write(json.dumps({"market": self.ctx().market}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.do_back_test - Check whether backtesting is enabled -class ContextDoBackTestHandler(BaseHandler): - def get(self): - self.write(json.dumps({"do_back_test": self.ctx().do_back_test}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.benchmark - Get the backtest benchmark -class ContextBenchmarkHandler(BaseHandler): - def get(self): - self.write(json.dumps({"benchmark": self.ctx().benchmark}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.capital - Get the initial backtest capital -class ContextCapitalHandler(BaseHandler): - def get(self): - self.write(json.dumps({"capital": self.ctx().capital}, separators=(',', ':'), ensure_ascii=False)) - -# ContextInfo.get_universe() - Get symbols in the universe -class ContextUniverseHandler(BaseHandler): - def get(self): - self.write(json.dumps({"universe": self.ctx().get_universe()}, separators=(',', ':'), ensure_ascii=False)) # ============= 2. Data queries (ContextInfo get_*) ============= @@ -966,7 +934,8 @@ class TradeDetailDataHandler(BaseHandler): ret = safe_call(get_trade_detail_data, self.acc(), account, datatype) if ret is None: ret = [] - self.write(json.dumps({"data": ret}, separators=(',', ':'), ensure_ascii=False)) + result = [fixed_fields(obj) for obj in ret] + self.write(json.dumps({"data": result}, separators=(',', ':'), ensure_ascii=False)) # get_value_by_order_id() - Get order or trade details by order ID class ValueByOrderIdHandler(BaseHandler): @@ -1112,7 +1081,28 @@ class HoldingHandler(BaseHandler): data = json.loads(self.request.body) account = data.get('account', 'stock') positions = safe_call(get_trade_detail_data, self.acc(), account, 'position') or [] - self.write(json.dumps({"data": positions}, separators=(',', ':'), ensure_ascii=False)) + holding = {} + for position in positions: + stock = position.m_strInstrumentID + '.' + position.m_strExchangeID + holding[stock] = { + 'StockCode': stock, + 'StockName': position.m_strInstrumentName, + 'Direction': position.m_nDirection, + 'Volume': position.m_nVolume, + 'OpenPrice': position.m_dOpenPrice, + '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 + } + self.write(json.dumps({"data": holding}, separators=(',', ':'), ensure_ascii=False)) # get_trade_detail_data('account') - Query account assets class AssetsHandler(BaseHandler): @@ -1126,28 +1116,6 @@ class AssetsHandler(BaseHandler): self.write(json.dumps({"total": round(info.m_dBalance, 2),"available": round(info.m_dAvailable, 2)}, separators=(',', ':'), ensure_ascii=False)) -# get_trade_detail_data('account') - Query total assets -class TotalMoneyHandler(BaseHandler): - def post(self): - data = json.loads(self.request.body) - account = data.get('account', 'stock') - _data = safe_call(get_trade_detail_data, self.acc(), account, 'account') - info = _data[0] if _data else None - if not info: - raise HTTPError(500, "Failed to get account data") - self.write(json.dumps({"total_money": round(info.m_dBalance, 2)}, separators=(',', ':'), ensure_ascii=False)) - -# get_trade_detail_data('account') - Query available cash -class AvailableMoneyHandler(BaseHandler): - def post(self): - data = json.loads(self.request.body) - account = data.get('account', 'stock') - _data = safe_call(get_trade_detail_data, self.acc(), account, 'account') - info = _data[0] if _data else None - if not info: - raise HTTPError(500, "Failed to get account data") - self.write(json.dumps({"available_money": round(info.m_dAvailable, 2)}, separators=(',', ':'), ensure_ascii=False)) - # passorder(23) - Simplified buy order wrapper class BuyHandler(BaseHandler): def post(self): @@ -1307,8 +1275,6 @@ def make_app(): # Legacy compatibility routes (r"/api/holding", HoldingHandler), - (r"/api/money/total", TotalMoneyHandler), - (r"/api/money/available", AvailableMoneyHandler), (r"/api/order/buy", BuyHandler), (r"/api/order/sell", SellHandler), (r"/api/order/status", OrderStatusHandler), @@ -1318,16 +1284,7 @@ def make_app(): (r"/api/order/deal", DealHandler), # ContextInfo properties - (r"/api/context/period", ContextPeriodHandler), - (r"/api/context/barpos", ContextBarposHandler), - (r"/api/context/time_tick_size", ContextTimeTickSizeHandler), - (r"/api/context/stockcode", ContextStockCodeHandler), - (r"/api/context/dividend_type", ContextDividendTypeHandler), - (r"/api/context/market", ContextMarketHandler), - (r"/api/context/do_back_test", ContextDoBackTestHandler), - (r"/api/context/benchmark", ContextBenchmarkHandler), - (r"/api/context/capital", ContextCapitalHandler), - (r"/api/context/universe", ContextUniverseHandler), + (r"/api/context/info", ContextInfoHandler), # Data queries (r"/api/data/stock_name", StockNameHandler), diff --git a/py-client/sdk/__pycache__/account.cpython-311.pyc b/py-client/sdk/__pycache__/account.cpython-311.pyc index e2aaf11..a32c7d0 100644 Binary files a/py-client/sdk/__pycache__/account.cpython-311.pyc and b/py-client/sdk/__pycache__/account.cpython-311.pyc differ diff --git a/py-client/sdk/__pycache__/client.cpython-311.pyc b/py-client/sdk/__pycache__/client.cpython-311.pyc index 34ba3db..96d9136 100644 Binary files a/py-client/sdk/__pycache__/client.cpython-311.pyc and b/py-client/sdk/__pycache__/client.cpython-311.pyc differ diff --git a/py-client/sdk/__pycache__/data.cpython-311.pyc b/py-client/sdk/__pycache__/data.cpython-311.pyc index bc6d313..4c6b982 100644 Binary files a/py-client/sdk/__pycache__/data.cpython-311.pyc and b/py-client/sdk/__pycache__/data.cpython-311.pyc differ diff --git a/py-client/sdk/__pycache__/misc.cpython-311.pyc b/py-client/sdk/__pycache__/misc.cpython-311.pyc index 7ce8e3c..9afa90a 100644 Binary files a/py-client/sdk/__pycache__/misc.cpython-311.pyc and b/py-client/sdk/__pycache__/misc.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 3871555..035a006 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/trade.py b/py-client/sdk/trade.py index af19abd..7eec68e 100644 --- a/py-client/sdk/trade.py +++ b/py-client/sdk/trade.py @@ -1,7 +1,8 @@ from .models import * from typing import Any -OP_BUY, OP_SELL = 23, 24 +OP_BUY = 23 +OP_SELL = 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"} @@ -16,11 +17,11 @@ class TradeMixin: return self._post("/api/trade/passorder", body) def passorder_latest(self, side, stock, volume): return self.passorder_latest_tagged(side, stock, volume, "", "") - def passorder_latest_tagged(self, side, stock, volume, strategy_name, order_id): + def passorder_latest_tagged(self, side, stock_code, volume, strategy_name, order_id): body = { "opType": side, "orderType": ORDER_TYPE_VOLUME, - "stock": stock, + "stockCode": stock_code, "prType": PR_TYPE_LATEST, "price": -1, "volume": volume, diff --git a/py-client/strategy/trend/__pycache__/open.cpython-311.pyc b/py-client/strategy/trend/__pycache__/open.cpython-311.pyc index f90d40f..4eb4679 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 f6692ab..fa69a5b 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/boot.py b/py-client/strategy/trend/boot.py index bc3df77..7f276b9 100644 --- a/py-client/strategy/trend/boot.py +++ b/py-client/strategy/trend/boot.py @@ -108,7 +108,6 @@ def StartTrend() -> None: def RunOnce(run: Runtime, signals:list[SignalItem]) -> None: """按固定步骤执行一轮趋势策略, ``RunOnce``。""" if not trading_time(datetime.now()): - log.info("[运行] 非交易时间,跳过本轮") return print("=" * 40 + f" Ticker {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} " +"=" * 40) diff --git a/py-client/strategy/trend/open.py b/py-client/strategy/trend/open.py index bfbdddb..d5bf07a 100644 --- a/py-client/strategy/trend/open.py +++ b/py-client/strategy/trend/open.py @@ -71,6 +71,7 @@ def do_open(run:Runtime,code:str,volume:int,signal_key:str)->None: run.client, OP_BUY, code, + -1, volume, order_id, signal_key, diff --git a/py-client/strategy/trend/order.py b/py-client/strategy/trend/order.py index 59fc0db..51785f4 100644 --- a/py-client/strategy/trend/order.py +++ b/py-client/strategy/trend/order.py @@ -25,6 +25,7 @@ class PlaceOrderRequest: client: Any op: int code: str + price: float volume: int order_id: str strategy_name: str