# -*- coding: gbk -*- import json import locale import os import datetime from pathlib import Path from tornado.web import Application, RequestHandler, HTTPError from tornado.ioloop import IOLoop import logging # 定义 ACCOUNT_ID = os.environ.get('QMT_ACCOUNT_ID', '') DATA_DIR = os.environ.get('QMT_DATA_DIR', 'D:\qmt_strategy_data') TOKEN="QMTbyYanweidong" PORT = 10086 # =================================== logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) locale.setlocale(locale.LC_CTYPE, 'chinese') def safe_call(func, *args, **kwargs): try: return func(*args, **kwargs) except Exception as e: logger.error(f"{func.__name__} 调用失败: {e}") return None # ============= BaseHandler ============= AUTH_EXEMPT = set() def no_auth(cls): AUTH_EXEMPT.add(cls) return cls class BaseHandler(RequestHandler): def prepare(self): if self.__class__ not in AUTH_EXEMPT: token = self.request.headers.get('X-Token') if token != TOKEN: raise HTTPError(401, "认证失败: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 }, ensure_ascii=False)) def ctx(self): return self.application.ContextInfo def acc(self): return self.application.accountID # ============= 1. ContextInfo 属性 ============= # ContextInfo.period - 获取当前周期 class ContextPeriodHandler(BaseHandler): def get(self): self.write(json.dumps({"period": self.ctx().period}, ensure_ascii=False)) # ContextInfo.barpos - 获取当前K线索引号 class ContextBarposHandler(BaseHandler): def get(self): self.write(json.dumps({"barpos": self.ctx().barpos}, ensure_ascii=False)) # ContextInfo.time_tick_size - 获取当前K线数目 class ContextTimeTickSizeHandler(BaseHandler): def get(self): self.write(json.dumps({"time_tick_size": self.ctx().time_tick_size}, ensure_ascii=False)) # ContextInfo.stockcode - 获取当前主图品种代码 class ContextStockCodeHandler(BaseHandler): def get(self): self.write(json.dumps({"stockcode": self.ctx().stockcode}, ensure_ascii=False)) # ContextInfo.dividend_type - 获取当前复权方式 class ContextDividendTypeHandler(BaseHandler): def get(self): self.write(json.dumps({"dividend_type": self.ctx().dividend_type}, ensure_ascii=False)) # ContextInfo.market - 获取当前主图市场 class ContextMarketHandler(BaseHandler): def get(self): self.write(json.dumps({"market": self.ctx().market}, ensure_ascii=False)) # ContextInfo.do_back_test - 是否开启回测模式 class ContextDoBackTestHandler(BaseHandler): def get(self): self.write(json.dumps({"do_back_test": self.ctx().do_back_test}, ensure_ascii=False)) # ContextInfo.benchmark - 获取回测基准 class ContextBenchmarkHandler(BaseHandler): def get(self): self.write(json.dumps({"benchmark": self.ctx().benchmark}, ensure_ascii=False)) # ContextInfo.capital - 获取回测初始资金 class ContextCapitalHandler(BaseHandler): def get(self): self.write(json.dumps({"capital": self.ctx().capital}, ensure_ascii=False)) # ContextInfo.get_universe() - 获取股票池中的股票 class ContextUniverseHandler(BaseHandler): def get(self): self.write(json.dumps({"universe": self.ctx().get_universe()}, ensure_ascii=False)) # ============= 2. 数据查询 (ContextInfo get_*) ============= # ContextInfo.get_stock_name() - 根据代码获取股票名称 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}, ensure_ascii=False)) # get_open_date() - 根据代码获取上市时间 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}, ensure_ascii=False)) # ContextInfo.get_last_volume() - 获取最新流通股本 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, "获取流通股本失败") self.write(json.dumps({"stockcode": stockcode, "last_volume": ret}, ensure_ascii=False)) # ContextInfo.get_bar_timetag() - 获取K线时间戳 class BarTimetagHandler(BaseHandler): def post(self): data = json.loads(self.request.body) index = int(data.get('index', -1)) ret = safe_call(self.ctx().get_bar_timetag, index) self.write(json.dumps({"index": index, "timetag": ret}, ensure_ascii=False)) # ContextInfo.get_tick_timetag() - 获取最新分笔时间戳 class TickTimetagHandler(BaseHandler): def get(self): ret = safe_call(self.ctx().get_tick_timetag) self.write(json.dumps({"timetag": ret}, ensure_ascii=False)) # ContextInfo.get_sector() - 获取指数成份股 class SectorHandler(BaseHandler): def post(self): data = json.loads(self.request.body) sector = data.get('sector', '') realtime = data.get('realtime', '0') if not sector: raise HTTPError(400, "need args sector") ret = safe_call(self.ctx().get_sector, sector, int(realtime) if realtime != '0' else 0) self.write(json.dumps({"sector": sector, "stocks": ret or []}, ensure_ascii=False)) # ContextInfo.get_industry() - 获取行业成份股 class IndustryHandler(BaseHandler): def post(self): data = json.loads(self.request.body) industry = data.get('industry', '') if not industry: raise HTTPError(400, "need args industry") print(industry) ret = safe_call(self.ctx().get_industry, industry) self.write(json.dumps({"industry": industry, "stocks": ret or []}, ensure_ascii=False)) # ContextInfo.get_stock_list_in_sector() - 获取板块成份股 class StockListInSectorHandler(BaseHandler): def post(self): data = json.loads(self.request.body) sectorname = data.get('sectorname', '') if not sectorname: raise HTTPError(400, "need args sectorname") ret = safe_call(self.ctx().get_stock_list_in_sector, sectorname) self.write(json.dumps({"sectorname": sectorname, "stocks": ret or []}, ensure_ascii=False)) # ContextInfo.get_weight_in_index() - 获取指数中权重 class WeightInIndexHandler(BaseHandler): def post(self): data = json.loads(self.request.body) indexcode = data.get('indexcode', '') stockcode = data.get('stockcode', '') ret = safe_call(self.ctx().get_weight_in_index, indexcode, stockcode) self.write(json.dumps({"indexcode": indexcode, "stockcode": stockcode, "weight": ret}, ensure_ascii=False)) # ContextInfo.get_contract_multiplier() - 获取合约乘数 class ContractMultiplierHandler(BaseHandler): def post(self): data = json.loads(self.request.body) contractcode = data.get('contractcode', '') ret = safe_call(self.ctx().get_contract_multiplier, contractcode) self.write(json.dumps({"contractcode": contractcode, "multiplier": ret}, ensure_ascii=False)) # ContextInfo.get_risk_free_rate() - 获取无风险利率 class RiskFreeRateHandler(BaseHandler): def post(self): data = json.loads(self.request.body) index = int(data.get('index', '-1')) ret = safe_call(self.ctx().get_risk_free_rate, index) self.write(json.dumps({"index": index, "risk_free_rate": ret}, ensure_ascii=False)) # ContextInfo.get_date_location() - 获取日期对应的K线索引 class DateLocationHandler(BaseHandler): def post(self): data = json.loads(self.request.body) strdate = data.get('strdate', '') ret = safe_call(self.ctx().get_date_location, strdate) self.write(json.dumps({"strdate": strdate, "location": ret}, ensure_ascii=False)) # ContextInfo.get_history_data() - 获取历史行情数据(多品种字典) class HistoryDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) length = int(data.get('len', '10')) period = data.get('period', '1d') field = data.get('field', 'close') dividend_type = int(data.get('dividend_type', '0')) skip_paused = data.get('skip_paused', 'true').lower() == 'true' ret = safe_call(self.ctx().get_history_data, length, period, field, dividend_type, skip_paused) self.write(json.dumps({"data": ret} if ret else {"error": "获取历史数据失败"}, ensure_ascii=False)) # ContextInfo.get_market_data() - 获取行情数据(DataFrame) class MarketDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) fields = data.get('fields', '') stock_code = data.get('stock_code', '') start_time = data.get('start_time', '') end_time = data.get('end_time', '') period = data.get('period', '1d') dividend_type = data.get('dividend_type', 'none') count = int(data.get('count', '-1')) fields_list = [f.strip() for f in fields.split(',')] if fields else [] stock_list = [s.strip() for s in stock_code.split(',')] if stock_code else [] ret = safe_call(self.ctx().get_market_data, fields_list, stock_list, start_time, end_time, True, period, dividend_type, count) if ret is None: raise HTTPError(500, "获取行情数据失败") if hasattr(ret, 'to_dict'): ret = ret.to_dict() self.write(json.dumps({"data": ret}, ensure_ascii=False, default=str)) # ContextInfo.get_market_data_ex() - 获取扩展行情(Level2) class MarketDataExHandler(BaseHandler): def post(self): data = json.loads(self.request.body) fields = data.get('fields', '') stock_code = data.get('stock_code', '') period = data.get('period', 'follow') start_time = data.get('start_time', '') end_time = data.get('end_time', '') count = int(data.get('count', '-1')) dividend_type = data.get('dividend_type', 'follow') fields_list = [f.strip() for f in fields.split(',')] if fields else [] stock_list = [s.strip() for s in stock_code.split(',')] if stock_code else [] ret = safe_call(self.ctx().get_market_data_ex, fields_list, stock_list, period, start_time, end_time, count, dividend_type) if ret is None: raise HTTPError(500, "获取扩展行情失败") result = {} for k, v in ret.items(): if hasattr(v, 'to_dict'): result[k] = v.to_dict() else: result[k] = str(v) self.write(json.dumps({"data": result}, ensure_ascii=False, default=str)) # ContextInfo.get_full_tick() - 获取分笔数据 class FullTickHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stocks = data.get('stocks', '') if not stocks: raise HTTPError(400, "need args stocks") code_list = [s.strip() for s in stocks.split(',')] ret = safe_call(self.ctx().get_full_tick, code_list) if not ret: raise HTTPError(500, "获取分笔行情失败") self.write(json.dumps(ret, ensure_ascii=False, default=str)) # ContextInfo.get_divid_factors() - 获取除权除息和复权因子 class DividFactorsHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stockcode = data.get('stockcode', '') ret = safe_call(self.ctx().get_divid_factors, stockcode) self.write(json.dumps({"stockcode": stockcode, "factors": ret or {}}, ensure_ascii=False)) # ContextInfo.get_main_contract() - 获取期货主力合约 class MainContractHandler(BaseHandler): def post(self): data = json.loads(self.request.body) codemarket = data.get('codemarket', '') ret = safe_call(self.ctx().get_main_contract, codemarket) self.write(json.dumps({"codemarket": codemarket, "main_contract": ret}, ensure_ascii=False)) # timetag_to_datetime() - 毫秒时间戳转日期时间 class TimetagToDatetimeHandler(BaseHandler): def post(self): data = json.loads(self.request.body) timetag = int(data.get('timetag', '0')) fmt = data.get('format', '%Y-%m-%d %H:%M:%S') ret = safe_call(timetag_to_datetime, timetag, fmt) self.write(json.dumps({"timetag": timetag, "datetime": ret}, ensure_ascii=False)) # ContextInfo.get_total_share() - 获取总股本 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}, ensure_ascii=False)) # ContextInfo.get_trading_dates() - 获取交易日列表 class TradingDatesHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stockcode = data.get('stockcode', '') start_date = data.get('start_date', '') end_date = data.get('end_date', '') count = data.get('count', '') period = data.get('period', '1d') count_int = int(count) if count else -1 ret = safe_call(self.ctx().get_trading_dates, stockcode, start_date, end_date, count_int, period) self.write(json.dumps({"dates": ret or []}, ensure_ascii=False)) # ContextInfo.get_svol() - 获取内盘成交量 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}, ensure_ascii=False)) # ContextInfo.get_bvol() - 获取外盘成交量 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}, ensure_ascii=False)) # ContextInfo.get_longhubang() - 获取龙虎榜数据 class LonghubangHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stock_list = data.get('stock_list', '') startTime = data.get('startTime', '') endTime = data.get('endTime', '') slist = [s.strip() for s in stock_list.split(',')] if stock_list else [] ret = safe_call(self.ctx().get_longhubang, slist, startTime, endTime) if hasattr(ret, 'to_dict'): ret = ret.to_dict() self.write(json.dumps({"data": ret} if ret else {"error": "获取龙虎榜数据失败"}, ensure_ascii=False, default=str)) # get_top10_share_holder() - 获取十大股东数据 class Top10ShareHolderHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stock_list = data.get('stock_list', '') data_name = data.get('data_name', 'holder') start_time = data.get('start_time', '') end_time = data.get('end_time', '') slist = [s.strip() for s in stock_list.split(',')] if stock_list else [] ret = safe_call(get_top10_share_holder, slist, data_name, start_time, end_time) if hasattr(ret, 'to_dict'): ret = ret.to_dict() self.write(json.dumps({"data": ret} if ret else {"error": "获取十大股东数据失败"}, ensure_ascii=False, default=str)) # ContextInfo.get_option_detail_data() - 获取期权详细信息 class OptionDetailHandler(BaseHandler): def post(self): data = json.loads(self.request.body) optioncode = data.get('optioncode', '') ret = safe_call(self.ctx().get_option_detail_data, optioncode) self.write(json.dumps({"optioncode": optioncode, "detail": ret or {}}, ensure_ascii=False)) # ContextInfo.get_turnover_rate() - 获取换手率 class TurnoverRateHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stock_list = data.get('stock_list', '') startTime = data.get('startTime', '') endTime = data.get('endTime', '') slist = [s.strip() for s in stock_list.split(',')] if stock_list else [] ret = safe_call(self.ctx().get_turnover_rate, slist, startTime, endTime) if hasattr(ret, 'to_dict'): ret = ret.to_dict() self.write(json.dumps({"data": ret} if ret else {"error": "获取换手率失败"}, ensure_ascii=False, default=str)) # get_etf_info() - 获取ETF申赎清单及成分股 class EtfInfoHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stockcode = data.get('stockcode', '') ret = safe_call(get_etf_info, stockcode) self.write(json.dumps({"stockcode": stockcode, "info": ret or {}}, ensure_ascii=False, default=str)) # get_etf_iopv() - 获取ETF基金份额参考净值 class EtfIopvHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stockcode = data.get('stockcode', '') ret = safe_call(get_etf_iopv, stockcode) self.write(json.dumps({"stockcode": stockcode, "iopv": ret}, ensure_ascii=False)) # ContextInfo.get_instrumentdetail() - 获取合约详细信息 class InstrumentDetailHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stockcode = data.get('stockcode', '') ret = safe_call(self.ctx().get_instrumentdetail, stockcode) self.write(json.dumps({"stockcode": stockcode, "detail": ret or {}}, ensure_ascii=False, default=str)) # ContextInfo.get_contract_expire_date() - 获取期货合约到期日 class ContractExpireDateHandler(BaseHandler): def post(self): data = json.loads(self.request.body) codemarket = data.get('codemarket', '') ret = safe_call(self.ctx().get_contract_expire_date, codemarket) self.write(json.dumps({"codemarket": codemarket, "expire_date": ret}, ensure_ascii=False)) # ContextInfo.get_option_undl_data() - 获取期权标的对应的期权品种列表 class OptionUndlDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) undl_code_ref = data.get('undl_code_ref', '') ret = safe_call(self.ctx().get_option_undl_data, undl_code_ref) self.write(json.dumps({"data": ret or []}, ensure_ascii=False, default=str)) # ContextInfo.get_financial_data() - 获取财务数据 class FinancialDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) tabname = data.get('tabname', '') colname = data.get('colname', '') market = data.get('market', '') code = data.get('code', '') report_type = data.get('report_type', 'report_time') barpos = int(data.get('barpos', '-1')) if tabname and colname and market and code: ret = safe_call(self.ctx().get_financial_data, tabname, colname, market, code, report_type, barpos) else: field_list = data.get('fieldList', '') stock_list = data.get('stockList', '') start_date = data.get('startDate', '') end_date = data.get('endDate', '') fields = [f.strip() for f in field_list.split(',')] if field_list else [] stocks = [s.strip() for s in stock_list.split(',')] if stock_list else [] rtype = data.get('report_type', 'announce_time') ret = safe_call(self.ctx().get_financial_data, fields, stocks, start_date, end_date, rtype) if hasattr(ret, 'to_dict'): ret = ret.to_dict() self.write(json.dumps({"data": ret} if ret is not None else {"error": "获取财务数据失败"}, ensure_ascii=False, default=str)) # ContextInfo.get_factor_data() - 获取多因子数据 class FactorDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) field_list = data.get('fieldList', '') stock_list = data.get('stockList', '') stock_code = data.get('stockCode', '') start_date = data.get('startDate', '') end_date = data.get('endDate', '') fields = [f.strip() for f in field_list.split(',')] if field_list else [] if stock_code: ret = safe_call(self.ctx().get_factor_data, fields, stock_code, start_date, end_date) else: stocks = [s.strip() for s in stock_list.split(',')] if stock_list else [] ret = safe_call(self.ctx().get_factor_data, fields, stocks, start_date, end_date) if hasattr(ret, 'to_dict'): ret = ret.to_dict() self.write(json.dumps({"data": ret} if ret is not None else {"error": "获取因子数据失败"}, ensure_ascii=False, default=str)) # ContextInfo.get_his_st_data() - 获取历史ST数据 class HisStDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stockCode = data.get('stockCode', '') ret = safe_call(self.ctx().get_his_st_data, stockCode) self.write(json.dumps({"stockCode": stockCode, "data": ret or {}}, ensure_ascii=False)) # ContextInfo.get_his_index_data() - 获取历史指数数据 class HisIndexDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) index = data.get('index', '') ret = safe_call(self.ctx().get_his_index_data, index) self.write(json.dumps({"index": index, "data": ret or {}}, ensure_ascii=False, default=str)) # ContextInfo.get_all_subscription() - 获取当前所有行情订阅信息 class AllSubscriptionHandler(BaseHandler): def get(self): ret = safe_call(self.ctx().get_all_subscription) self.write(json.dumps({"subscriptions": ret or {}}, ensure_ascii=False, default=str)) # ContextInfo.get_option_list() - 获取指定期权列表 class OptionListHandler(BaseHandler): def post(self): data = json.loads(self.request.body) undl_code = data.get('undl_code', '') dedate = data.get('dedate', '') opttype = data.get('opttype', '') isavailable = data.get('isavailable', 'true').lower() == 'true' ret = safe_call(self.ctx().get_option_list, undl_code, dedate, opttype, isavailable) self.write(json.dumps({"option_list": ret or []}, ensure_ascii=False)) # ContextInfo.get_his_contract_list() - 获取过期合约列表 class HisContractListHandler(BaseHandler): def post(self): data = json.loads(self.request.body) market = data.get('market', '') ret = safe_call(self.ctx().get_his_contract_list, market) self.write(json.dumps({"market": market, "contracts": ret or []}, ensure_ascii=False)) # ContextInfo.get_option_iv() - 获取期权实时隐含波动率 class OptionIvHandler(BaseHandler): def post(self): data = json.loads(self.request.body) optioncode = data.get('optioncode', '') ret = safe_call(self.ctx().get_option_iv, optioncode) self.write(json.dumps({"optioncode": optioncode, "iv": ret}, ensure_ascii=False)) # ContextInfo.bsm_price() - BS模型计算欧式期权理论价格 class BsmPriceHandler(BaseHandler): def post(self): data = json.loads(self.request.body) optionType = data.get('optionType', 'C') objectPrices = data.get('objectPrices', '') strikePrice = float(data.get('strikePrice', '0')) riskFree = float(data.get('riskFree', '0')) sigma = float(data.get('sigma', '0')) days = int(data.get('days', '0')) dividend = float(data.get('dividend', '0')) try: op = float(objectPrices) except ValueError: op = [float(x) for x in objectPrices.split(',')] ret = safe_call(self.ctx().bsm_price, optionType, op, strikePrice, riskFree, sigma, days, dividend) self.write(json.dumps({"price": ret}, ensure_ascii=False, default=str)) # ContextInfo.bsm_iv() - BS模型计算欧式期权隐含波动率 class BsmIvHandler(BaseHandler): def post(self): data = json.loads(self.request.body) optionType = data.get('optionType', 'C') objectPrices = float(data.get('objectPrices', '0')) strikePrice = float(data.get('strikePrice', '0')) optionPrice = float(data.get('optionPrice', '0')) riskFree = float(data.get('riskFree', '0')) days = int(data.get('days', '0')) dividend = float(data.get('dividend', '0')) ret = safe_call(self.ctx().bsm_iv, optionType, objectPrices, strikePrice, optionPrice, riskFree, days, dividend) self.write(json.dumps({"iv": ret}, ensure_ascii=False)) # ContextInfo.get_local_data() - 从本地获取行情数据 class LocalDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stock_code = data.get('stock_code', '') start_time = data.get('start_time', '') end_time = data.get('end_time', '') period = data.get('period', '1d') divid_type = data.get('divid_type', 'none') count = int(data.get('count', '-1')) ret = safe_call(self.ctx().get_local_data, stock_code, start_time, end_time, period, divid_type, count) if ret is None: raise HTTPError(500, "获取本地行情失败") self.write(json.dumps({"data": ret}, ensure_ascii=False, default=str)) # ContextInfo.subscribe_quote() - 订阅行情数据 class SubscribeQuoteHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stock_code = data.get('stock_code', '') period = data.get('period', 'follow') dividend_type = data.get('dividend_type', 'follow') ret = safe_call(self.ctx().subscribe_quote, stock_code, period, dividend_type) self.write(json.dumps({"status": "success" if ret is not None else "failed", "sub_id": ret}, ensure_ascii=False)) # ContextInfo.unsubscribe_quote() - 反订阅行情数据 class UnsubscribeQuoteHandler(BaseHandler): def post(self): data = json.loads(self.request.body) sub_id = int(data.get('sub_id', '0')) safe_call(self.ctx().unsubscribe_quote, sub_id) self.write(json.dumps({"status": "success", "sub_id": sub_id}, ensure_ascii=False)) # ============= 3. 判定函数 (is_*) ============= # ContextInfo.is_last_bar() - 判定是否为最后一根K线 class IsLastBarHandler(BaseHandler): def get(self): ret = safe_call(self.ctx().is_last_bar) self.write(json.dumps({"is_last_bar": ret}, ensure_ascii=False)) # ContextInfo.is_new_bar() - 判定是否为新的K线 class IsNewBarHandler(BaseHandler): def get(self): ret = safe_call(self.ctx().is_new_bar) self.write(json.dumps({"is_new_bar": ret}, ensure_ascii=False)) # ContextInfo.is_suspended_stock() - 判定股票是否停牌 class IsSuspendedStockHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stockcode = data.get('stockcode', '') ret = safe_call(self.ctx().is_suspended_stock, stockcode) self.write(json.dumps({"stockcode": stockcode, "is_suspended": ret}, ensure_ascii=False)) # is_sector_stock() - 判定股票是否在指定板块中 class IsSectorStockHandler(BaseHandler): def post(self): data = json.loads(self.request.body) sectorname = data.get('sectorname', '') market = data.get('market', '') stockcode = data.get('stockcode', '') ret = safe_call(is_sector_stock, sectorname, market, stockcode) self.write(json.dumps({"sectorname": sectorname, "stockcode": stockcode, "is_in_sector": ret}, ensure_ascii=False)) # is_typed_stock() - 判定股票是否属于某个类别 class IsTypedStockHandler(BaseHandler): def post(self): data = json.loads(self.request.body) stocktypenum = int(data.get('stocktypenum', '0')) market = data.get('market', '') stockcode = data.get('stockcode', '') ret = safe_call(is_typed_stock, stocktypenum, market, stockcode) self.write(json.dumps({"stocktypenum": stocktypenum, "stockcode": stockcode, "result": ret}, ensure_ascii=False)) # get_industry_name_of_stock() - 获取股票行业分类名称 class GetIndustryNameOfStockHandler(BaseHandler): def post(self): data = json.loads(self.request.body) industryType = data.get('industryType', '') stockcode = data.get('stockcode', '') ret = safe_call(get_industry_name_of_stock, industryType, stockcode) self.write(json.dumps({"industryType": industryType, "stockcode": stockcode, "industry_name": ret}, ensure_ascii=False)) # ============= 4. 交易函数 ============= # passorder() - 综合交易下单(支持股票买卖等) class PassorderHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) opType = int(data['opType']) orderType = int(data.get('orderType', 1101)) stock = data['stock'] pr_type = int(data.get('prType', 11)) price = float(data['price']) volume = int(data['volume']) quickTrade = int(data.get('quickTrade', 2)) order_ref = passorder(opType, orderType, self.acc(), stock, pr_type, price, volume, 'qmt', quickTrade, self.ctx()) self.write(json.dumps({ "status": "success", "opType": opType, "stock": stock, "order_ref": str(order_ref) if order_ref else "unknown" }, ensure_ascii=False)) except Exception as e: logger.exception("passorder下单异常") raise HTTPError(400, f"下单失败: {str(e)}") # algo_passorder() - 算法交易下单 class AlgoPassorderHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) order_ref = algo_passorder( int(data['opType']), int(data.get('orderType', 1101)), self.acc(), data['stock'], int(data.get('prType', -1)), float(data['price']), int(data['volume']), data.get('strategyName', ''), int(data.get('quickTrade', 2)), data.get('userOrderId', ''), data.get('userOrderParam', {}), self.ctx() ) self.write(json.dumps({"status": "success", "order_ref": str(order_ref) if order_ref else "unknown"}, ensure_ascii=False)) except Exception as e: logger.exception("algo_passorder异常") raise HTTPError(400, f"算法下单失败: {str(e)}") # smart_algo_passorder() - 智能算法交易下单 class SmartAlgoPassorderHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) order_ref = smart_algo_passorder( int(data['opType']), int(data.get('orderType', 1101)), self.acc(), data['stock'], int(data.get('prType', -1)), float(data['price']), int(data['volume']), data['smartAlgoType'], int(data.get('limitOverRate', 0)), int(data.get('minAmountPerOrder', 0)), data.get('startTime', ''), data.get('endTime', ''), self.ctx() ) self.write(json.dumps({"status": "success", "order_ref": str(order_ref) if order_ref else "unknown"}, ensure_ascii=False)) except Exception as e: logger.exception("smart_algo_passorder异常") raise HTTPError(400, f"智能算法下单失败: {str(e)}") # order_lots() - 指定手数交易 class OrderLotsHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) order_lots(data['stock'], int(data['lots']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "order_lots", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("order_lots异常") raise HTTPError(400, f"下单失败: {str(e)}") # order_value() - 指定价值交易 class OrderValueHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) order_value(data['stock'], float(data['value']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "order_value", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("order_value异常") raise HTTPError(400, f"下单失败: {str(e)}") # order_percent() - 指定比例交易 class OrderPercentHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) order_percent(data['stock'], float(data['percent']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "order_percent", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("order_percent异常") raise HTTPError(400, f"下单失败: {str(e)}") # order_target_value() - 指定目标价值交易 class OrderTargetValueHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) order_target_value(data['stock'], float(data['tar_value']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "order_target_value", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("order_target_value异常") raise HTTPError(400, f"下单失败: {str(e)}") # order_target_percent() - 指定目标比例交易 class OrderTargetPercentHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) order_target_percent(data['stock'], float(data['tar_percent']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "order_target_percent", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("order_target_percent异常") raise HTTPError(400, f"下单失败: {str(e)}") # order_shares() - 指定股数交易 class OrderSharesHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) order_shares(data['stock'], int(data['shares']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "order_shares", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("order_shares异常") raise HTTPError(400, f"下单失败: {str(e)}") # ============= 5. 期货交易 ============= # buy_open() - 期货买入开仓 class FuturesBuyOpenHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) buy_open(data['stock'], int(data['amount']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "buy_open", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("buy_open异常") raise HTTPError(400, f"期货买入开仓失败: {str(e)}") # buy_close_tdayfirst() - 期货买入平仓(平今优先) class FuturesBuyCloseTdayFirstHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) buy_close_tdayfirst(data['stock'], int(data['amount']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "buy_close_tdayfirst", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("buy_close_tdayfirst异常") raise HTTPError(400, f"期货买入平仓(平今)失败: {str(e)}") # buy_close_ydayfirst() - 期货买入平仓(平昨优先) class FuturesBuyCloseYdayFirstHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) buy_close_ydayfirst(data['stock'], int(data['amount']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "buy_close_ydayfirst", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("buy_close_ydayfirst异常") raise HTTPError(400, f"期货买入平仓(平昨)失败: {str(e)}") # sell_open() - 期货卖出开仓 class FuturesSellOpenHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) sell_open(data['stock'], int(data['amount']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "sell_open", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("sell_open异常") raise HTTPError(400, f"期货卖出开仓失败: {str(e)}") # sell_close_tdayfirst() - 期货卖出平仓(平今优先) class FuturesSellCloseTdayFirstHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) sell_close_tdayfirst(data['stock'], int(data['amount']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "sell_close_tdayfirst", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("sell_close_tdayfirst异常") raise HTTPError(400, f"期货卖出平仓(平今)失败: {str(e)}") # sell_close_ydayfirst() - 期货卖出平仓(平昨优先) class FuturesSellCloseYdayFirstHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) sell_close_ydayfirst(data['stock'], int(data['amount']), data.get('style', 'LATEST'), float(data.get('price', 0)), self.ctx(), data.get('accId', self.acc())) self.write(json.dumps({"status": "success", "action": "sell_close_ydayfirst", "stock": data['stock']}, ensure_ascii=False)) except Exception as e: logger.exception("sell_close_ydayfirst异常") raise HTTPError(400, f"期货卖出平仓(平昨)失败: {str(e)}") # ============= 6. 任务管理 ============= # cancel_task() - 撤销任务 class CancelTaskHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) taskId = data['taskId'] accountType = data.get('accountType', 'stock') ret = cancel_task(taskId, self.acc(), accountType, self.ctx()) self.write(json.dumps({"status": "success" if ret else "failed", "taskId": taskId}, ensure_ascii=False)) except Exception as e: logger.exception("cancel_task异常") raise HTTPError(400, f"撤销任务失败: {str(e)}") # pause_task() - 暂停任务 class PauseTaskHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) taskId = data['taskId'] accountType = data.get('accountType', 'stock') ret = pause_task(taskId, self.acc(), accountType, self.ctx()) self.write(json.dumps({"status": "success" if ret else "failed", "taskId": taskId}, ensure_ascii=False)) except Exception as e: logger.exception("pause_task异常") raise HTTPError(400, f"暂停任务失败: {str(e)}") # resume_task() - 继续任务 class ResumeTaskHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) taskId = data['taskId'] accountType = data.get('accountType', 'stock') ret = resume_task(taskId, self.acc(), accountType, self.ctx()) self.write(json.dumps({"status": "success" if ret else "failed", "taskId": taskId}, ensure_ascii=False)) except Exception as e: logger.exception("resume_task异常") raise HTTPError(400, f"继续任务失败: {str(e)}") # do_order() - 实时触发前一根bar信号函数 class DoOrderHandler(BaseHandler): def post(self): try: do_order(self.ctx()) self.write(json.dumps({"status": "success", "message": "信号已触发"}, ensure_ascii=False)) except Exception as e: logger.exception("do_order异常") raise HTTPError(400, f"触发信号失败: {str(e)}") # ============= 7. 账户/订单查询 ============= # get_trade_detail_data() - 获取交易明细(持仓/委托/成交/资金) class TradeDetailDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) account = data.get('account', 'stock') datatype = data.get('datatype', 'position') ret = safe_call(get_trade_detail_data, self.acc(), account, datatype, 'qmt') if ret is None: ret = [] result = [] for obj in ret: attrs = {} for attr in dir(obj): if not attr.startswith('_'): try: val = getattr(obj, attr) if not callable(val): attrs[attr] = str(val) except Exception: pass result.append(attrs) self.write(json.dumps({"data": result}, ensure_ascii=False)) # get_value_by_order_id() - 根据委托号获取委托/成交信息 class ValueByOrderIdHandler(BaseHandler): def post(self): data = json.loads(self.request.body) orderId = data.get('orderId', '') accountType = data.get('accountType', 'stock') datatype = data.get('datatype', 'ORDER') ret = safe_call(get_value_by_order_id, orderId, self.acc(), accountType, datatype) attrs = {} if ret: for attr in dir(ret): if not attr.startswith('_'): try: val = getattr(ret, attr) if not callable(val): attrs[attr] = str(val) except Exception: pass self.write(json.dumps({"orderId": orderId, "data": attrs}, ensure_ascii=False)) # get_last_order_id() - 获取最新委托/成交的委托号 class LastOrderIdHandler(BaseHandler): def post(self): data = json.loads(self.request.body) account = data.get('account', 'stock') datatype = data.get('datatype', 'ORDER') ret = safe_call(get_last_order_id, self.acc(), account, datatype, 'qmt') self.write(json.dumps({"last_order_id": ret}, ensure_ascii=False)) # can_cancel_order() - 查询委托是否可撤销 class CanCancelOrderHandler(BaseHandler): def post(self): data = json.loads(self.request.body) orderId = data.get('orderId', '') accountType = data.get('accountType', 'stock') ret = safe_call(can_cancel_order, orderId, self.acc(), accountType) self.write(json.dumps({"orderId": orderId, "can_cancel": ret}, ensure_ascii=False)) # get_debt_contract() - 获取两融负债合约明细 class DebtContractHandler(BaseHandler): def post(self): data = json.loads(self.request.body) accId = data.get('accId', self.acc()) ret = safe_call(get_debt_contract, accId) result = [] if ret: for obj in ret: attrs = {} for attr in dir(obj): if not attr.startswith('_'): try: val = getattr(obj, attr) if not callable(val): attrs[attr] = str(val) except Exception: pass result.append(attrs) self.write(json.dumps({"data": result}, ensure_ascii=False)) # get_assure_contract() - 获取两融担保标的明细 class AssureContractHandler(BaseHandler): def post(self): data = json.loads(self.request.body) accId = data.get('accId', self.acc()) ret = safe_call(get_assure_contract, accId) result = [] if ret: for obj in ret: attrs = {} for attr in dir(obj): if not attr.startswith('_'): try: val = getattr(obj, attr) if not callable(val): attrs[attr] = str(val) except Exception: pass result.append(attrs) self.write(json.dumps({"data": result}, ensure_ascii=False)) # get_enable_short_contract() - 获取可融券明细 class EnableShortContractHandler(BaseHandler): def post(self): data = json.loads(self.request.body) accId = data.get('accId', self.acc()) ret = safe_call(get_enable_short_contract, accId) result = [] if ret: for obj in ret: attrs = {} for attr in dir(obj): if not attr.startswith('_'): try: val = getattr(obj, attr) if not callable(val): attrs[attr] = str(val) except Exception: pass result.append(attrs) self.write(json.dumps({"data": result}, ensure_ascii=False)) # get_ipo_data() - 获取当日新股新债信息 class IpoDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) typ = data.get('type', '') ret = safe_call(get_ipo_data, typ) self.write(json.dumps({"data": ret or {}}, ensure_ascii=False, default=str)) # get_new_purchase_limit() - 获取新股申购额度 class NewPurchaseLimitHandler(BaseHandler): def post(self): data = json.loads(self.request.body) accid = data.get('accid', self.acc()) ret = safe_call(get_new_purchase_limit, accid) self.write(json.dumps({"data": ret or {}}, ensure_ascii=False, default=str)) # ============= 8. 引用函数 (ext_data) ============= # ext_data() - 获取扩展数据数值 class ExtDataHandler(BaseHandler): def post(self): data = json.loads(self.request.body) extdataname = data.get('extdataname', '') stockcode = data.get('stockcode', '') deviation = int(data.get('deviation', '0')) ret = safe_call(ext_data, extdataname, stockcode, deviation, self.ctx()) self.write(json.dumps({"extdataname": extdataname, "stockcode": stockcode, "value": ret}, ensure_ascii=False)) # ext_data_rank() - 获取扩展数据排名 class ExtDataRankHandler(BaseHandler): def post(self): data = json.loads(self.request.body) extdataname = data.get('extdataname', '') stockcode = data.get('stockcode', '') deviation = int(data.get('deviation', '0')) ret = safe_call(ext_data_rank, extdataname, stockcode, deviation, self.ctx()) self.write(json.dumps({"extdataname": extdataname, "stockcode": stockcode, "rank": ret}, ensure_ascii=False)) # get_factor_value() - 获取因子数据 class GetFactorValueHandler(BaseHandler): def post(self): data = json.loads(self.request.body) factorname = data.get('factorname', '') stockcode = data.get('stockcode', '') deviation = int(data.get('deviation', '0')) ret = safe_call(get_factor_value, factorname, stockcode, deviation, self.ctx()) self.write(json.dumps({"factorname": factorname, "stockcode": stockcode, "value": ret}, ensure_ascii=False)) # get_factor_rank() - 获取因子数据排名 class GetFactorRankHandler(BaseHandler): def post(self): data = json.loads(self.request.body) factorname = data.get('factorname', '') stockcode = data.get('stockcode', '') deviation = int(data.get('deviation', '0')) ret = safe_call(get_factor_rank, factorname, stockcode, deviation, self.ctx()) self.write(json.dumps({"factorname": factorname, "stockcode": stockcode, "rank": ret}, ensure_ascii=False)) # ============= 9. 原有 Handler(保持兼容) ============= # get_trade_detail_data('position') - 查询持仓列表(封装格式) class HoldingHandler(BaseHandler): def post(self): data = json.loads(self.request.body) account = data.get('account', 'stock') positions = safe_call(get_trade_detail_data, self.acc(), account, 'position') or [] 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(holding, ensure_ascii=False)) # get_trade_detail_data('account') - 查询资产 class AssetsHandler(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, "资金数据获取失败") self.write(json.dumps({"total": round(info.m_dBalance, 2),"available": round(info.m_dAvailable, 2)}, ensure_ascii=False)) # get_trade_detail_data('account') - 查询总资产 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, "资金数据获取失败") self.write(json.dumps({"total_money": round(info.m_dBalance, 2)}, ensure_ascii=False)) # get_trade_detail_data('account') - 查询可用资金 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, "资金数据获取失败") self.write(json.dumps({"available_money": round(info.m_dAvailable, 2)}, ensure_ascii=False)) # passorder(23) - 简化买入下单(封装passorder) class BuyHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) stock = data['stock'] price = float(data['price']) volume = int(data['volume']) pr_type = data.get('prType', 11) order_ref = passorder(23, 1101, self.acc(), stock, pr_type, price, volume, 'qmt', 2, self.ctx()) self.write(json.dumps({ "status": "success", "action": "buy", "stock": stock, "order_ref": str(order_ref) if order_ref else "unknown" }, ensure_ascii=False)) except Exception as e: logger.exception("买入下单异常") raise HTTPError(400, f"下单失败: {str(e)}") # passorder(24) - 简化卖出下单(封装passorder) class SellHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) stock = data['stock'] price = float(data['price']) volume = int(data['volume']) pr_type = data.get('prType', 11) order_ref = passorder(24, 1101, self.acc(), stock, pr_type, price, volume, 'qmt', 2, self.ctx()) self.write(json.dumps({ "status": "success", "action": "sell", "stock": stock, "order_ref": str(order_ref) if order_ref else "unknown" }, ensure_ascii=False)) except Exception as e: logger.exception("卖出下单异常") raise HTTPError(400, f"下单失败: {str(e)}") # get_trade_detail_data('order') - 查询委托状态列表 class OrderStatusHandler(BaseHandler): def post(self): data = json.loads(self.request.body) account = data.get('account', 'stock') orders = safe_call(get_trade_detail_data, self.acc(), account, 'order', 'qmt') or [] rets = [] for order in orders: rets.append({ "order_sys_id": order.m_strOrderSysID, "status": order.m_nOrderStatus, "volume_left": order.m_nVolumeTotal, "volume_traded": order.m_nVolumeTraded, }) self.write(json.dumps({"orders": rets}, ensure_ascii=False)) # cancel() - 全部撤单 class CancelAllHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) account = data.get('account', 'stock') orders = safe_call(get_trade_detail_data, self.acc(), account, 'order', 'qmt') or [] canceled_list = [] for order in orders: if can_cancel_order(order.m_strOrderSysID, self.acc(), account): cancel(order.m_strOrderSysID, self.acc(), account, self.ctx()) canceled_list.append({ "order_sys_id": order.m_strOrderSysID, "stock": order.m_strInstrumentID, "volume_left": order.m_nVolumeTotal }) self.write(json.dumps({ "status": "success", "message": f"已发出 {len(canceled_list)} 笔撤单请求", "canceled_orders": canceled_list }, ensure_ascii=False)) except Exception as e: logger.exception("全部撤单异常") raise HTTPError(500, f"撤单失败: {str(e)}") class CancelByRuleHandler(BaseHandler): def post(self): try: data = json.loads(self.request.body) stock = data.get('stock') cancel_volume = int(data.get('volume', 0)) account = data.get('account', 'stock') if not stock or cancel_volume <= 0: raise HTTPError(400, "参数错误:必须提供 stock 且 volume > 0") orders = safe_call(get_trade_detail_data, self.acc(), account, 'order', 'qmt') or [] target_orders = [] for order in orders: order_code = f"{order.m_strInstrumentID}.{order.m_strExchangeID}" if order.m_nVolumeTotal + order.m_nVolumeTraded == cancel_volume and order_code == stock and can_cancel_order(order.m_strOrderSysID, self.acc(), account): target_orders.append(order) if not target_orders: self.write(json.dumps({"status": "failed", "message": "未找到符合条件的活跃订单"}, ensure_ascii=False)) return canceled_ids = [] for t_order in target_orders: cancel(t_order.m_strOrderSysID, self.acc(), account, self.ctx()) canceled_ids.append(t_order.m_strOrderSysID) self.write(json.dumps({ "status": "success", "message": f"匹配到 {len(target_orders)} 笔订单并发出撤单请求", "canceled_sys_ids": canceled_ids }, ensure_ascii=False)) except Exception as e: logger.exception("规则撤单异常") raise HTTPError(500, f"撤单失败: {str(e)}") # cancel() - 按股票+数量匹配规则撤单 # sys: Python版本信息 class PythonVersionHandler(BaseHandler): def get(self): import sys version_info = { "python_version": sys.version, "python_version_info": { "major": sys.version_info.major, "minor": sys.version_info.minor, "micro": sys.version_info.micro, "releaselevel": sys.version_info.releaselevel, "serial": sys.version_info.serial, } } self.write(json.dumps(version_info, ensure_ascii=False)) # sys: 关闭HTTP服务 class ShutdownHandler(BaseHandler): def post(self): logger.info("收到关闭请求,服务器即将停止...") self.write(json.dumps({"status": "success", "message": "服务器正在关闭..."}, ensure_ascii=False)) self.finish() IOLoop.current().add_callback(IOLoop.current().stop) # get_trade_detail_data('deal') - 查询成交明细 class DealHandler(BaseHandler): def post(self): data = json.loads(self.request.body) account = data.get('account', 'stock') deals = safe_call(get_trade_detail_data, self.acc(), account, 'deal', 'qmt') or [] rets = [] for deal in deals: attrs = {} for attr in dir(deal): if not attr.startswith('_'): try: val = getattr(deal, attr) if not callable(val): attrs[attr] = str(val) except Exception: pass rets.append(attrs) self.write(json.dumps({"deals": rets}, ensure_ascii=False)) # ============= 路由注册 ============= def make_app(): return Application([ # V2 (r"/api/v2/positions", HoldingHandler), (r"/api/v2/assets", AssetsHandler), # 原有兼容路由 (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), (r"/api/order/cancel_all", CancelAllHandler), (r"/api/order/cancel_order", CancelByRuleHandler), (r"/api/order/deal", DealHandler), # ContextInfo 属性 (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/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), (r"/api/data/weight_in_index", WeightInIndexHandler), (r"/api/data/contract_multiplier", ContractMultiplierHandler), (r"/api/data/risk_free_rate", RiskFreeRateHandler), (r"/api/data/date_location", DateLocationHandler), (r"/api/data/history_data", HistoryDataHandler), (r"/api/data/market_data", MarketDataHandler), (r"/api/data/market_data_ex", MarketDataExHandler), (r"/api/data/full_tick", FullTickHandler), (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), (r"/api/data/turnover_rate", TurnoverRateHandler), (r"/api/data/etf_info", EtfInfoHandler), (r"/api/data/etf_iopv", EtfIopvHandler), (r"/api/data/instrumentdetail", InstrumentDetailHandler), (r"/api/data/contract_expire_date", ContractExpireDateHandler), (r"/api/data/option_undl_data", OptionUndlDataHandler), (r"/api/data/financial_data", FinancialDataHandler), (r"/api/data/factor_data", FactorDataHandler), (r"/api/data/his_st_data", HisStDataHandler), (r"/api/data/his_index_data", HisIndexDataHandler), (r"/api/data/all_subscription", AllSubscriptionHandler), (r"/api/data/option_list", OptionListHandler), (r"/api/data/his_contract_list", HisContractListHandler), (r"/api/data/option_iv", OptionIvHandler), (r"/api/data/bsm_price", BsmPriceHandler), (r"/api/data/bsm_iv", BsmIvHandler), (r"/api/data/local_data", LocalDataHandler), # 订阅 (r"/api/data/subscribe_quote", SubscribeQuoteHandler), (r"/api/data/unsubscribe_quote", UnsubscribeQuoteHandler), # 判定函数 (r"/api/check/is_last_bar", IsLastBarHandler), (r"/api/check/is_new_bar", IsNewBarHandler), (r"/api/check/is_suspended_stock", IsSuspendedStockHandler), (r"/api/check/is_sector_stock", IsSectorStockHandler), (r"/api/check/is_typed_stock", IsTypedStockHandler), (r"/api/check/get_industry_name_of_stock", GetIndustryNameOfStockHandler), # 交易 (r"/api/trade/passorder", PassorderHandler), (r"/api/trade/algo_passorder", AlgoPassorderHandler), (r"/api/trade/smart_algo_passorder", SmartAlgoPassorderHandler), (r"/api/trade/order_lots", OrderLotsHandler), (r"/api/trade/order_value", OrderValueHandler), (r"/api/trade/order_percent", OrderPercentHandler), (r"/api/trade/order_target_value", OrderTargetValueHandler), (r"/api/trade/order_target_percent", OrderTargetPercentHandler), (r"/api/trade/order_shares", OrderSharesHandler), # 期货交易 (r"/api/trade/futures/buy_open", FuturesBuyOpenHandler), (r"/api/trade/futures/buy_close_tdayfirst", FuturesBuyCloseTdayFirstHandler), (r"/api/trade/futures/buy_close_ydayfirst", FuturesBuyCloseYdayFirstHandler), (r"/api/trade/futures/sell_open", FuturesSellOpenHandler), (r"/api/trade/futures/sell_close_tdayfirst", FuturesSellCloseTdayFirstHandler), (r"/api/trade/futures/sell_close_ydayfirst", FuturesSellCloseYdayFirstHandler), # 任务管理 (r"/api/trade/cancel_task", CancelTaskHandler), (r"/api/trade/pause_task", PauseTaskHandler), (r"/api/trade/resume_task", ResumeTaskHandler), (r"/api/trade/do_order", DoOrderHandler), # 账户/订单查询 (r"/api/trade/trade_detail_data", TradeDetailDataHandler), (r"/api/trade/value_by_order_id", ValueByOrderIdHandler), (r"/api/trade/last_order_id", LastOrderIdHandler), (r"/api/trade/can_cancel_order", CanCancelOrderHandler), (r"/api/trade/debt_contract", DebtContractHandler), (r"/api/trade/assure_contract", AssureContractHandler), (r"/api/trade/enable_short_contract", EnableShortContractHandler), (r"/api/trade/ipo_data", IpoDataHandler), (r"/api/trade/new_purchase_limit", NewPurchaseLimitHandler), # 引用函数 (r"/api/ext/ext_data", ExtDataHandler), (r"/api/ext/ext_data_rank", ExtDataRankHandler), (r"/api/ext/get_factor_value", GetFactorValueHandler), (r"/api/ext/get_factor_rank", GetFactorRankHandler), # 系统 (r"/api/sys/python_version", PythonVersionHandler), (r"/api/sys/shutdown", ShutdownHandler), ], debug=False) # ============= Callback 注册 ============= def json_serializer(obj): """自定义 JSON 序列化器""" if isinstance(obj, datetime.datetime): return obj.strftime("%Y-%m-%d %H:%M:%S") if isinstance(obj, datetime.date): return obj.strftime("%Y-%m-%d") raise TypeError(f"Type {type(obj)} not serializable") def write_json(file_key, data,order_id:str=''): """ 将数据写入 JSON 文件 参数: file_key (str): 文件名模板 data: 要写入的数据 order_id (str): 订单ID,可选 """ now=datetime.datetime.now() day = now.strftime("%Y%m%d") body = { "created_at": now, "body":data } # 构建路径 if order_id: file_path = Path(DATA_DIR) / (file_key % (day,order_id)) else: file_path = Path(DATA_DIR) / (file_key % day) # 创建目录 file_path.parent.mkdir(parents=True, exist_ok=True) with open(file_path, 'w', encoding='utf-8') as f: json.dump(body, f, ensure_ascii=False,indent=4, default=json_serializer) # 资金账号主推函数 def account_callback(ContextInfo, accountInfo): write_json("acount_%s.json",accountInfo) # 委托主推函数 def order_callback(ContextInfo, orderInfo): write_json("order_%s_%s.json",orderInfo,orderInfo.m_strOrderSysID) # 成交主推函数 def deal_callback(ContextInfo, dealInfo): write_json("deal_%s_%s.json",dealInfo,dealInfo.m_strOrderSysID) # 持仓主推函数 def position_callback(ContextInfo, positonInfo): write_json("position_%s.json",positonInfo) #下单出错回调函数 def orderError_callback(ContextInfo, passOrderInfo, msg): print('orderError_callback') #输出下单信息以及错误信息 print (passOrderInfo.orderCode) print (msg) def init(ContextInfo): if not (ACCOUNT_ID or "").strip(): msg = "ACCOUNT_ID 为空,无法启动" logger.error(msg) raise ValueError(msg) if not (DATA_DIR or "").strip(): msg = "DATA_DIR 为空,无法启动" logger.error(msg) raise ValueError(msg) try: ContextInfo.accountID = ACCOUNT_ID ContextInfo.set_account(ACCOUNT_ID) # 按需加载股票池 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)) # Api App app = make_app() app.ContextInfo = ContextInfo app.accountID = ContextInfo.accountID app.listen(PORT, address='0.0.0.0') logger.info(f"ACCOUNT_ID: {ACCOUNT_ID}") logger.info(f"DATA_DIR: {DATA_DIR}") logger.info(f"TOKEN: {TOKEN}") logger.info(f"初始化股票池: {len(codes)} 支标的") logger.info(f"QMT HTTP Server 启动于 http://0.0.0.0:{PORT} (全部API已加载)") IOLoop.current().start() except Exception as e: logger.exception(f"server start failed: {e}")