Files
big-qmt/api/QMT_API.py
2026-08-25 18:59:18 +08:00

1550 lines
68 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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")
ret = safe_call(self.ctx().get_full_tick, stocks)
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):
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")
if hasattr(obj, 'to_dict'):
return obj.to_dict()
attrs = {}
for name in dir(obj):
if name.startswith('_'):
continue
try:
val = getattr(obj, name)
except Exception:
continue
if not callable(val):
attrs[name] = val
if attrs:
return attrs
return str(obj)
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}")