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