Main_Window可以手动下单,也可以在StrategyManager中启动用户策略自动下单。这些下单动作无论怎么变化,都大致经过上述的过程。
如果用户使用不同的CTA策略,交易相同的合约,那么在当前的主界面(Main_Window)中它们如何区分:哪一笔委托单是属于哪一个策略的?哪一笔成交单是属于哪一个策略的?
也就是说你看到主界面(Main_Window)中有一个成交单,你无法确定它是手动下单的还是自动下单的,如果有不同策略交易同一合约,你可能也无法确定是属于哪个策略的。
def send_server_order(
self,
strategy: CtaTemplate,
contract: ContractData,
direction: Direction,
offset: Offset,
price: float,
volume: float,
type: OrderType,
lock: bool
):
"""
Send a new order to server.
"""
# Create request and send order.
original_req = OrderRequest(
symbol=contract.symbol,
exchange=contract.exchange,
direction=direction,
offset=offset,
type=type,
price=price,
volume=volume,
)
# Convert with offset converter
req_list = self.offset_converter.convert_order_request(original_req, lock)
# Send Orders
vt_orderids = []
for req in req_list:
req.reference = strategy.strategy_name # Add strategy name as order reference
vt_orderid = self.main_engine.send_order(
req, contract.gateway_name)
# Check if sending order successful
if not vt_orderid:
continue
vt_orderids.append(vt_orderid)
self.offset_converter.update_order_request(req, vt_orderid)
# Save relationship between orderid and strategy.
self.orderid_strategy_map[vt_orderid] = strategy
self.strategy_orderid_map[strategy.strategy_name].add(vt_orderid)
return vt_orderids
这是CtaEngine的send_server_order()函数,是把委托申请发送给交易服务器的函数,其中的这一句:
req.reference = strategy.strategy_name # Add strategy name as order reference
这是原来系统就有的一句,但是不知道为什么没有利用起来,现在我把它利用起来,它可以决定一个申请是手动的还是自动的。
手动的req.reference是空字符串,自动申请的req.reference是非空的策略名称!
def send_order(self, req: OrderRequest):
"""
Send new order.
"""
if req.offset not in OFFSET_VT2CTP:
self.gateway.write_log("请选择开平方向")
return ""
self.order_ref += 1
ctp_req = {
"InstrumentID": req.symbol,
"ExchangeID": req.exchange.value,
"LimitPrice": req.price,
"VolumeTotalOriginal": int(req.volume),
"OrderPriceType": ORDERTYPE_VT2CTP.get(req.type, ""),
"Direction": DIRECTION_VT2CTP.get(req.direction, ""),
"CombOffsetFlag": OFFSET_VT2CTP.get(req.offset, ""),
"OrderRef": str(self.order_ref),
"InvestorID": self.userid,
"UserID": self.userid,
"BrokerID": self.brokerid,
"CombHedgeFlag": THOST_FTDC_HF_Speculation,
"ContingentCondition": THOST_FTDC_CC_Immediately,
"ForceCloseReason": THOST_FTDC_FCC_NotForceClose,
"IsAutoSuspend": 0,
"TimeCondition": THOST_FTDC_TC_GFD,
"VolumeCondition": THOST_FTDC_VC_AV,
"MinVolume": 1
}
if req.type == OrderType.FAK:
ctp_req["OrderPriceType"] = THOST_FTDC_OPT_LimitPrice
ctp_req["TimeCondition"] = THOST_FTDC_TC_IOC
ctp_req["VolumeCondition"] = THOST_FTDC_VC_AV
elif req.type == OrderType.FOK:
ctp_req["OrderPriceType"] = THOST_FTDC_OPT_LimitPrice
ctp_req["TimeCondition"] = THOST_FTDC_TC_IOC
ctp_req["VolumeCondition"] = THOST_FTDC_VC_CV
self.reqid += 1
# print(f"!!!3 ctp_req={ctp_req}") # hxxjava debug
self.reqOrderInsert(ctp_req, self.reqid)
orderid = f"{self.frontid}_{self.sessionid}_{self.order_ref}"
order = req.create_order_data(orderid, self.gateway_name)
self.gateway.on_order(order)
return order.vt_orderid
这个函数是委托申请后最终执行的函数,这里的这一句非常重要:
order = req.create_order_data(orderid, self.gateway_name)
就是在create_order_data()里,完成了req.reference到order.reference的传递!
@dataclass
class OrderData(BaseData):
"""
Order data contains information for tracking lastest status
of a specific order.
"""
symbol: str
exchange: Exchange
orderid: str
type: OrderType = OrderType.LIMIT
direction: Direction = None
offset: Offset = Offset.NONE
price: float = 0
volume: float = 0
traded: float = 0
status: Status = Status.SUBMITTING
datetime: datetime = None
reference:str = "" # hxxjava add
def __post_init__(self):
""""""
self.vt_symbol = f"{self.symbol}.{self.exchange.value}"
self.vt_orderid = f"{self.gateway_name}.{self.orderid}"
def is_active(self) -> bool:
"""
Check if the order is active.
"""
if self.status in ACTIVE_STATUSES:
return True
else:
return False
def create_cancel_request(self) -> "CancelRequest":
"""
Create cancel request object from order.
"""
req = CancelRequest(
orderid=self.orderid, symbol=self.symbol, exchange=self.exchange
)
return req
@dataclass
class OrderRequest:
"""
Request sending to specific gateway for creating a new order.
"""
symbol: str
exchange: Exchange
direction: Direction
type: OrderType
volume: float
price: float = 0
offset: Offset = Offset.NONE
reference: str = ""
def __post_init__(self):
""""""
self.vt_symbol = f"{self.symbol}.{self.exchange.value}"
def create_order_data(self, orderid: str, gateway_name: str) -> OrderData:
"""
Create order data from request.
"""
order = OrderData(
symbol=self.symbol,
exchange=self.exchange,
orderid=orderid,
type=self.type,
direction=self.direction,
offset=self.offset,
price=self.price,
volume=self.volume,
gateway_name=gateway_name,
reference = self.reference # hxxjava add
)
return order
OrderData.reference表示它属于哪个策略,或者是不是自动委托的
TradeData中通过orderid找到成交单属于哪个委托单的,进而可以间接确定它属于哪个策略
class OrderMonitor(BaseMonitor):
"""
Monitor for order data.
"""
event_type = EVENT_ORDER
data_key = "vt_orderid"
sorting = True
headers: Dict[str, dict] = {
"orderid": {"display": "委托号", "cell": BaseCell, "update": False},
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
"type": {"display": "类型", "cell": EnumCell, "update": False},
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
"offset": {"display": "开平", "cell": EnumCell, "update": False},
"price": {"display": "价格", "cell": BaseCell, "update": False},
"volume": {"display": "总数量", "cell": BaseCell, "update": True},
"traded": {"display": "已成交", "cell": BaseCell, "update": True},
"status": {"display": "状态", "cell": EnumCell, "update": True},
"datetime": {"display": "时间", "cell": TimeCell, "update": True},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
"reference": {"display": "策略", "cell": BaseCell, "update": False},
}
def init_ui(self):
"""
Connect signal.
"""
super(OrderMonitor, self).init_ui()
self.setToolTip("双击单元格撤单")
self.itemDoubleClicked.connect(self.cancel_order)
def cancel_order(self, cell: BaseCell) -> None:
"""
Cancel order if cell double clicked.
"""
order = cell.get_data()
req = order.create_cancel_request()
self.main_engine.cancel_order(req, order.gateway_name)
from typing import List,Dict,Tuple,Optional,Sequence
from vnpy.trader.object import ContractData
from datetime import datetime,timedelta
import rqdatac as rq
def left_alphas(instr:str):
"""
得到字符串左边的字符部分
"""
ret_str = ''
for s in instr:
if s.isalpha():
ret_str += s
else:
break
return ret_str
def get_contract_kinds(contracts:List[ContractData]):
"""
从合约列表中获取合约的品种和品种列表
"""
kinds:Dict[str,List[ContractData]] = {}
for contract in contracts:
vt_symbol = contract.vt_symbol
kind = left_alphas(vt_symbol)
if kind not in kinds:
kinds[kind] = [vt_symbol]
else:
if vt_symbol not in kinds[kind]:
kinds[kind].append(vt_symbol)
return kinds
def get_all_dominants(kinds:List[str], which_day:datetime=None):
""" 主力合约排序 """
if not which_day:
which_day = datetime.today()-timedelta(days=3)
dominants1 = []
for kind in kinds:
# 查询指定日期的合约品种的主力合约
df = rq.futures.get_dominant(underlying_symbol=kind.upper(),start_date=which_day)
vt_symbol = df.values[0]
# 查询主力合约在指定交易日的成交额度
df = rq.get_price(vt_symbol,frequency='1d',start_date=which_day,end_date=which_day)
total_turnover = df['total_turnover'].values[0]
dominants1.append({"kind":kind,"vt_symbol":vt_symbol,"total_turnover":total_turnover})
# 按照成交额度降序排序
dominants = sorted(dominants1,key=lambda x:x["total_turnover"],reverse=True)
return dominants
在vnpy\trader\ui\main_window.py中增加一个菜单,然后为其增加下面的处理函数:
def query_contracts(self) -> None: # hxxjava add
"""
Query contracts test.
"""
oms_engine:SamEngine = self.main_engine.get_engine('oms')
contracts = oms_engine.contracts.values()
# for row,contract in enumerate(contracts):
# print(f"【{row}】 {contract}")
contract_kinds = get_contract_kinds(contracts)
print("==============全市场品种及在交易合约列表========================")
for kind,vt_symbols in contract_kinds.items():
print(f"kind={kind} vt_symbols={vt_symbols}")
kinds = [kind for kind in contract_kinds]
dominants = get_all_dominants(kinds)
print("=====================全市场主力合约排序========================")
for dominant in dominants:
print(f"{dominant}")
==============全市场品种及在交易合约列表========================
kind=CF vt_symbols=['CF109.CZCE', 'CF105.CZCE', 'CF107.CZCE', 'CF101.CZCE', 'CF103.CZCE', 'CF011.CZCE']
kind=TA vt_symbols=['TA109.CZCE', 'TA105.CZCE', 'TA104.CZCE', 'TA107.CZCE', 'TA101.CZCE', 'TA106.CZCE', 'TA108.CZCE', 'TA102.CZCE', 'TA103.CZCE', 'TA012.CZCE', 'TA011.CZCE', 'TA010.CZCE']
kind=SR vt_symbols=['SR109.CZCE', 'SR105.CZCE', 'SR107.CZCE', 'SR101.CZCE', 'SR103.CZCE', 'SR011.CZCE']
kind=CY vt_symbols=['CY109.CZCE', 'CY105.CZCE', 'CY104.CZCE', 'CY107.CZCE', 'CY101.CZCE', 'CY106.CZCE', 'CY108.CZCE', 'CY102.CZCE', 'CY103.CZCE', 'CY012.CZCE', 'CY011.CZCE', 'CY010.CZCE']
kind=FG vt_symbols=['FG109.CZCE', 'FG105.CZCE', 'FG104.CZCE', 'FG107.CZCE', 'FG101.CZCE', 'FG106.CZCE', 'FG108.CZCE', 'FG102.CZCE', 'FG103.CZCE', 'FG012.CZCE', 'FG011.CZCE', 'FG010.CZCE']
kind=sc vt_symbols=['sc2109.INE', 'sc2106.INE', 'sc2303.INE', 'sc2101.INE', 'sc2309.INE', 'sc2102.INE', 'sc2107.INE', 'sc2104.INE', 'sc2306.INE', 'sc2105.INE', 'sc2011.INE', 'sc2108.INE', 'sc2212.INE', 'sc2209.INE', 'sc2103.INE', 'sc2112.INE', 'sc2010.INE', 'sc2206.INE', 'sc2203.INE', 'sc2012.INE']
kind=ag vt_symbols=['ag2103.SHFE', 'ag2102.SHFE', 'ag2104.SHFE', 'ag2101.SHFE', 'ag2105.SHFE', 'ag2106.SHFE', 'ag2108.SHFE', 'ag2109.SHFE', 'ag2107.SHFE', 'ag2011.SHFE', 'ag2010.SHFE', 'ag2012.SHFE']
kind=al vt_symbols=['al2103.SHFE', 'al2102.SHFE', 'al2104.SHFE', 'al2101.SHFE', 'al2105.SHFE', 'al2106.SHFE', 'al2108.SHFE', 'al2109.SHFE', 'al2107.SHFE', 'al2011.SHFE', 'al2010.SHFE', 'al2012.SHFE']
kind=au vt_symbols=['au2104.SHFE', 'au2102.SHFE', 'au2106.SHFE', 'au2011.SHFE', 'au2110.SHFE', 'au2108.SHFE', 'au2012.SHFE', 'au2010.SHFE']
kind=bu vt_symbols=['bu2203.SHFE', 'bu2010.SHFE', 'bu2011.SHFE', 'bu2206.SHFE', 'bu2102.SHFE', 'bu2209.SHFE', 'bu2101.SHFE', 'bu2112.SHFE', 'bu2109.SHFE', 'bu2103.SHFE', 'bu2106.SHFE', 'bu2012.SHFE']
kind=cu vt_symbols=['cu2103.SHFE', 'cu2102.SHFE', 'cu2104.SHFE', 'cu2101.SHFE', 'cu2105.SHFE', 'cu2106.SHFE', 'cu2108.SHFE', 'cu2109.SHFE', 'cu2107.SHFE', 'cu2011.SHFE', 'cu2010.SHFE', 'cu2012.SHFE']
kind=hc vt_symbols=['hc2103.SHFE', 'hc2102.SHFE', 'hc2104.SHFE', 'hc2101.SHFE', 'hc2105.SHFE', 'hc2106.SHFE', 'hc2108.SHFE', 'hc2109.SHFE', 'hc2107.SHFE', 'hc2011.SHFE', 'hc2010.SHFE', 'hc2012.SHFE']
kind=ni vt_symbols=['ni2103.SHFE', 'ni2102.SHFE', 'ni2104.SHFE', 'ni2101.SHFE', 'ni2105.SHFE', 'ni2106.SHFE', 'ni2108.SHFE', 'ni2109.SHFE', 'ni2107.SHFE', 'ni2011.SHFE', 'ni2010.SHFE', 'ni2012.SHFE']
kind=pb vt_symbols=['pb2103.SHFE', 'pb2102.SHFE', 'pb2104.SHFE', 'pb2101.SHFE', 'pb2105.SHFE', 'pb2106.SHFE', 'pb2108.SHFE', 'pb2109.SHFE', 'pb2107.SHFE', 'pb2011.SHFE', 'pb2010.SHFE', 'pb2012.SHFE']
kind=rb vt_symbols=['rb2103.SHFE', 'rb2102.SHFE', 'rb2104.SHFE', 'rb2101.SHFE', 'rb2105.SHFE', 'rb2106.SHFE', 'rb2108.SHFE', 'rb2109.SHFE', 'rb2107.SHFE', 'rb2010.SHFE', 'rb2011.SHFE', 'rb2012.SHFE']
kind=ru vt_symbols=['ru2103.SHFE', 'ru2104.SHFE', 'ru2101.SHFE', 'ru2105.SHFE', 'ru2106.SHFE', 'ru2108.SHFE', 'ru2109.SHFE', 'ru2107.SHFE', 'ru2010.SHFE', 'ru2011.SHFE']
kind=sn vt_symbols=['sn2103.SHFE', 'sn2102.SHFE', 'sn2104.SHFE', 'sn2101.SHFE', 'sn2105.SHFE', 'sn2106.SHFE', 'sn2108.SHFE', 'sn2109.SHFE', 'sn2107.SHFE', 'sn2010.SHFE', 'sn2011.SHFE', 'sn2012.SHFE']
kind=sp vt_symbols=['sp2103.SHFE', 'sp2102.SHFE', 'sp2104.SHFE', 'sp2101.SHFE', 'sp2105.SHFE', 'sp2106.SHFE', 'sp2108.SHFE', 'sp2109.SHFE', 'sp2107.SHFE', 'sp2010.SHFE', 'sp2011.SHFE', 'sp2012.SHFE']
kind=wr vt_symbols=['wr2103.SHFE', 'wr2102.SHFE', 'wr2104.SHFE', 'wr2101.SHFE', 'wr2105.SHFE', 'wr2106.SHFE', 'wr2108.SHFE', 'wr2109.SHFE', 'wr2107.SHFE', 'wr2010.SHFE', 'wr2011.SHFE', 'wr2012.SHFE']
kind=zn vt_symbols=['zn2103.SHFE', 'zn2102.SHFE', 'zn2104.SHFE', 'zn2101.SHFE', 'zn2105.SHFE', 'zn2106.SHFE', 'zn2108.SHFE', 'zn2109.SHFE', 'zn2107.SHFE', 'zn2010.SHFE', 'zn2011.SHFE', 'zn2012.SHFE']
kind=PM vt_symbols=['PM103.CZCE', 'PM105.CZCE', 'PM101.CZCE', 'PM107.CZCE', 'PM109.CZCE', 'PM011.CZCE']
kind=RI vt_symbols=['RI103.CZCE', 'RI105.CZCE', 'RI101.CZCE', 'RI107.CZCE', 'RI109.CZCE', 'RI011.CZCE']
kind=RM vt_symbols=['RM103.CZCE', 'RM105.CZCE', 'RM101.CZCE', 'RM107.CZCE', 'RM108.CZCE', 'RM109.CZCE', 'RM011.CZCE']
kind=SF vt_symbols=['SF103.CZCE', 'SF105.CZCE', 'SF104.CZCE', 'SF101.CZCE', 'SF106.CZCE', 'SF107.CZCE', 'SF108.CZCE', 'SF109.CZCE', 'SF102.CZCE', 'SF012.CZCE', 'SF011.CZCE', 'SF010.CZCE']
kind=SM vt_symbols=['SM103.CZCE', 'SM105.CZCE', 'SM104.CZCE', 'SM101.CZCE', 'SM106.CZCE', 'SM107.CZCE', 'SM108.CZCE', 'SM109.CZCE', 'SM102.CZCE', 'SM012.CZCE', 'SM011.CZCE', 'SM010.CZCE']
kind=WH vt_symbols=['WH103.CZCE', 'WH105.CZCE', 'WH101.CZCE', 'WH107.CZCE', 'WH109.CZCE', 'WH011.CZCE']
kind=a vt_symbols=['a2103.DCE', 'a2105.DCE', 'a2107.DCE', 'a2101.DCE', 'a2109.DCE', 'a2011.DCE']
kind=b vt_symbols=['b2103.DCE', 'b2105.DCE', 'b2104.DCE', 'b2107.DCE', 'b2106.DCE', 'b2101.DCE', 'b2108.DCE', 'b2109.DCE', 'b2102.DCE', 'b2012.DCE', 'b2011.DCE', 'b2010.DCE']
kind=bb vt_symbols=['bb2103.DCE', 'bb2105.DCE', 'bb2104.DCE', 'bb2107.DCE', 'bb2106.DCE', 'bb2101.DCE', 'bb2108.DCE', 'bb2109.DCE', 'bb2102.DCE', 'bb2012.DCE', 'bb2011.DCE', 'bb2010.DCE']
kind=c vt_symbols=['c2103.DCE', 'c2105.DCE', 'c2107.DCE', 'c2101.DCE', 'c2109.DCE', 'c2011.DCE']
kind=cs vt_symbols=['cs2103.DCE', 'cs2105.DCE', 'cs2107.DCE', 'cs2101.DCE', 'cs2109.DCE', 'cs2011.DCE']
kind=fb vt_symbols=['fb2103.DCE', 'fb2105.DCE', 'fb2104.DCE', 'fb2107.DCE', 'fb2106.DCE', 'fb2101.DCE', 'fb2108.DCE', 'fb2109.DCE', 'fb2102.DCE', 'fb2012.DCE', 'fb2011.DCE', 'fb2010.DCE']
kind=i vt_symbols=['i2103.DCE', 'i2105.DCE', 'i2104.DCE', 'i2107.DCE', 'i2106.DCE', 'i2101.DCE', 'i2108.DCE', 'i2010.DCE', 'i2109.DCE', 'i2102.DCE', 'i2012.DCE', 'i2011.DCE']
kind=j vt_symbols=['j2103.DCE', 'j2105.DCE', 'j2104.DCE', 'j2107.DCE', 'j2106.DCE', 'j2101.DCE', 'j2108.DCE', 'j2010.DCE', 'j2109.DCE', 'j2102.DCE', 'j2012.DCE', 'j2011.DCE']
kind=jm vt_symbols=['jm2103.DCE', 'jm2105.DCE', 'jm2104.DCE', 'jm2107.DCE', 'jm2106.DCE', 'jm2101.DCE', 'jm2108.DCE', 'jm2010.DCE', 'jm2109.DCE', 'jm2102.DCE', 'jm2012.DCE', 'jm2011.DCE']
kind=l vt_symbols=['l2103.DCE', 'l2105.DCE', 'l2104.DCE', 'l2107.DCE', 'l2101.DCE', 'l2106.DCE', 'l2108.DCE', 'l2010.DCE', 'l2109.DCE', 'l2102.DCE', 'l2012.DCE', 'l2011.DCE']
kind=m vt_symbols=['m2103.DCE', 'm2105.DCE', 'm2107.DCE', 'm2101.DCE', 'm2108.DCE', 'm2109.DCE', 'm2012.DCE', 'm2011.DCE']
kind=p vt_symbols=['p2103.DCE', 'p2105.DCE', 'p2104.DCE', 'p2107.DCE', 'p2101.DCE', 'p2106.DCE', 'p2108.DCE', 'p2010.DCE', 'p2109.DCE', 'p2102.DCE', 'p2012.DCE', 'p2011.DCE']
kind=pp vt_symbols=['pp2103.DCE', 'pp2105.DCE', 'pp2104.DCE', 'pp2107.DCE', 'pp2101.DCE', 'pp2106.DCE', 'pp2108.DCE', 'pp2010.DCE', 'pp2109.DCE', 'pp2102.DCE', 'pp2012.DCE', 'pp2011.DCE']
kind=v vt_symbols=['v2103.DCE', 'v2105.DCE', 'v2109.DCE', 'v2104.DCE', 'v2107.DCE', 'v2101.DCE', 'v2106.DCE', 'v2108.DCE', 'v2010.DCE', 'v2102.DCE', 'v2012.DCE', 'v2011.DCE']
kind=y vt_symbols=['y2103.DCE', 'y2105.DCE', 'y2109.DCE', 'y2107.DCE', 'y2101.DCE', 'y2108.DCE', 'y2012.DCE', 'y2011.DCE']
kind=IC vt_symbols=['IC2012.CFFEX', 'IC2103.CFFEX', 'IC2010.CFFEX', 'IC2011.CFFEX']
kind=IF vt_symbols=['IF2012.CFFEX', 'IF2103.CFFEX', 'IF2010.CFFEX', 'IF2011.CFFEX']
kind=IH vt_symbols=['IH2012.CFFEX', 'IH2103.CFFEX', 'IH2010.CFFEX', 'IH2011.CFFEX']
kind=ZC vt_symbols=['ZC105.CZCE', 'ZC109.CZCE', 'ZC106.CZCE', 'ZC104.CZCE', 'ZC102.CZCE', 'ZC103.CZCE', 'ZC107.CZCE', 'ZC108.CZCE', 'ZC101.CZCE', 'ZC011.CZCE', 'ZC010.CZCE', 'ZC012.CZCE']
kind=eg vt_symbols=['eg2106.DCE', 'eg2105.DCE', 'eg2101.DCE', 'eg2102.DCE', 'eg2104.DCE', 'eg2010.DCE', 'eg2103.DCE', 'eg2107.DCE', 'eg2108.DCE', 'eg2011.DCE', 'eg2012.DCE', 'eg2109.DCE']
kind=jd vt_symbols=['jd2106.DCE', 'jd2105.DCE', 'jd2101.DCE', 'jd2102.DCE', 'jd2104.DCE', 'jd2010.DCE', 'jd2103.DCE', 'jd2107.DCE', 'jd2108.DCE', 'jd2011.DCE', 'jd2012.DCE', 'jd2109.DCE']
kind=fu vt_symbols=['fu2103.SHFE', 'fu2101.SHFE', 'fu2109.SHFE', 'fu2102.SHFE', 'fu2107.SHFE', 'fu2104.SHFE', 'fu2106.SHFE', 'fu2105.SHFE', 'fu2011.SHFE', 'fu2108.SHFE', 'fu2012.SHFE', 'fu2010.SHFE']
kind=AP vt_symbols=['AP105.CZCE', 'AP107.CZCE', 'AP101.CZCE', 'AP103.CZCE', 'AP012.CZCE', 'AP011.CZCE', 'AP010.CZCE']
kind=JR vt_symbols=['JR105.CZCE', 'JR107.CZCE', 'JR101.CZCE', 'JR109.CZCE', 'JR103.CZCE', 'JR011.CZCE']
kind=LR vt_symbols=['LR105.CZCE', 'LR011.CZCE', 'LR101.CZCE', 'LR107.CZCE', 'LR109.CZCE', 'LR103.CZCE']
kind=MA vt_symbols=['MA105.CZCE', 'MA104.CZCE', 'MA101.CZCE', 'MA106.CZCE', 'MA107.CZCE', 'MA108.CZCE', 'MA109.CZCE', 'MA102.CZCE', 'MA103.CZCE', 'MA012.CZCE', 'MA011.CZCE', 'MA010.CZCE']
kind=OI vt_symbols=['OI105.CZCE', 'OI101.CZCE', 'OI107.CZCE', 'OI109.CZCE', 'OI103.CZCE', 'OI011.CZCE']
kind=eb vt_symbols=['eb2010.DCE', 'eb2011.DCE', 'eb2012.DCE', 'eb2101.DCE', 'eb2102.DCE', 'eb2103.DCE', 'eb2104.DCE', 'eb2105.DCE', 'eb2106.DCE', 'eb2107.DCE', 'eb2108.DCE', 'eb2109.DCE']
kind=rr vt_symbols=['rr2010.DCE', 'rr2011.DCE', 'rr2012.DCE', 'rr2101.DCE', 'rr2102.DCE', 'rr2103.DCE', 'rr2104.DCE', 'rr2105.DCE', 'rr2106.DCE', 'rr2107.DCE', 'rr2108.DCE', 'rr2109.DCE']
kind=pg vt_symbols=['pg2011.DCE', 'pg2012.DCE', 'pg2101.DCE', 'pg2102.DCE', 'pg2103.DCE', 'pg2104.DCE', 'pg2105.DCE', 'pg2106.DCE', 'pg2107.DCE', 'pg2108.DCE', 'pg2109.DCE']
kind=T vt_symbols=['T2103.CFFEX', 'T2106.CFFEX', 'T2012.CFFEX']
kind=TF vt_symbols=['TF2103.CFFEX', 'TF2106.CFFEX', 'TF2012.CFFEX']
kind=TS vt_symbols=['TS2103.CFFEX', 'TS2106.CFFEX', 'TS2012.CFFEX']
kind=RS vt_symbols=['RS107.CZCE', 'RS108.CZCE', 'RS109.CZCE', 'RS011.CZCE']
kind=UR vt_symbols=['UR107.CZCE', 'UR108.CZCE', 'UR109.CZCE', 'UR010.CZCE', 'UR011.CZCE', 'UR012.CZCE', 'UR101.CZCE', 'UR102.CZCE', 'UR103.CZCE', 'UR104.CZCE', 'UR105.CZCE', 'UR106.CZCE']
=====================全市场主力合约排序========================
{'kind': 'ag', 'vt_symbol': 'AG2012', 'total_turnover': 214143898665.0}
{'kind': 'IF', 'vt_symbol': 'IF2010', 'total_turnover': 115089726720.0}
{'kind': 'IC', 'vt_symbol': 'IC2010', 'total_turnover': 108709168960.0}
{'kind': 'au', 'vt_symbol': 'AU2012', 'total_turnover': 97275079200.0}
{'kind': 'i', 'vt_symbol': 'I2101', 'total_turnover': 70141287600.0}
{'kind': 'T', 'vt_symbol': 'T2012', 'total_turnover': 66779643250.0}
{'kind': 'ru', 'vt_symbol': 'RU2101', 'total_turnover': 62175496100.0}
{'kind': 'y', 'vt_symbol': 'Y2101', 'total_turnover': 62036693940.0}
{'kind': 'p', 'vt_symbol': 'P2101', 'total_turnover': 60139173280.0}
{'kind': 'OI', 'vt_symbol': 'OI2101', 'total_turnover': 58055810880.0}
{'kind': 'cu', 'vt_symbol': 'CU2011', 'total_turnover': 53450265650.0}
{'kind': 'FG', 'vt_symbol': 'FG2101', 'total_turnover': 45607836000.0}
{'kind': 'm', 'vt_symbol': 'M2101', 'total_turnover': 35834947080.0}
{'kind': 'rb', 'vt_symbol': 'RB2101', 'total_turnover': 35004834540.0}
{'kind': 'j', 'vt_symbol': 'J2101', 'total_turnover': 32930215200.0}
{'kind': 'IH', 'vt_symbol': 'IH2010', 'total_turnover': 28645460100.0}
{'kind': 'sc', 'vt_symbol': 'SC2011', 'total_turnover': 28389715200.0}
{'kind': 'zn', 'vt_symbol': 'ZN2011', 'total_turnover': 25126207275.0}
{'kind': 'fu', 'vt_symbol': 'FU2101', 'total_turnover': 24160222910.0}
{'kind': 'ZC', 'vt_symbol': 'ZC2011', 'total_turnover': 21100200000.0}
{'kind': 'pp', 'vt_symbol': 'PP2101', 'total_turnover': 20908030425.0}
{'kind': 'AP', 'vt_symbol': 'AP2101', 'total_turnover': 20720715330.0}
{'kind': 'bu', 'vt_symbol': 'BU2012', 'total_turnover': 20684842140.0}
{'kind': 'TF', 'vt_symbol': 'TF2012', 'total_turnover': 20387290900.0}
{'kind': 'ni', 'vt_symbol': 'NI2011', 'total_turnover': 18114747280.0}
{'kind': 'SR', 'vt_symbol': 'SR2101', 'total_turnover': 17356500420.0}
{'kind': 'MA', 'vt_symbol': 'MA2101', 'total_turnover': 17291912680.0}
{'kind': 'CF', 'vt_symbol': 'CF2101', 'total_turnover': 14776293750.0}
{'kind': 'jd', 'vt_symbol': 'JD2011', 'total_turnover': 14047527750.0}
{'kind': 'c', 'vt_symbol': 'C2101', 'total_turnover': 13462882980.0}
{'kind': 'RM', 'vt_symbol': 'RM2101', 'total_turnover': 12374485580.0}
{'kind': 'al', 'vt_symbol': 'AL2011', 'total_turnover': 12072076375.0}
{'kind': 'l', 'vt_symbol': 'L2101', 'total_turnover': 11536233225.0}
{'kind': 'TS', 'vt_symbol': 'TS2012', 'total_turnover': 9449540000.0}
{'kind': 'TA', 'vt_symbol': 'TA2101', 'total_turnover': 9398714690.0}
{'kind': 'eg', 'vt_symbol': 'EG2101', 'total_turnover': 8941426960.0}
{'kind': 'pg', 'vt_symbol': 'PG2011', 'total_turnover': 8926672280.0}
{'kind': 'sp', 'vt_symbol': 'SP2012', 'total_turnover': 7528736540.0}
{'kind': 'v', 'vt_symbol': 'V2101', 'total_turnover': 7187423175.0}
{'kind': 'a', 'vt_symbol': 'A2101', 'total_turnover': 6813036460.0}
{'kind': 'hc', 'vt_symbol': 'HC2101', 'total_turnover': 6807265320.0}
{'kind': 'jm', 'vt_symbol': 'JM2101', 'total_turnover': 4624123680.0}
{'kind': 'sn', 'vt_symbol': 'SN2012', 'total_turnover': 4067929330.0}
{'kind': 'pb', 'vt_symbol': 'PB2011', 'total_turnover': 3304319225.0}
{'kind': 'SM', 'vt_symbol': 'SM2101', 'total_turnover': 2529065430.0}
{'kind': 'cs', 'vt_symbol': 'CS2101', 'total_turnover': 2402811690.0}
{'kind': 'b', 'vt_symbol': 'B2011', 'total_turnover': 2338601160.0}
{'kind': 'eb', 'vt_symbol': 'EB2101', 'total_turnover': 1968052130.0}
{'kind': 'UR', 'vt_symbol': 'UR2101', 'total_turnover': 1476473040.0}
{'kind': 'CY', 'vt_symbol': 'CY2101', 'total_turnover': 707752500.0}
{'kind': 'SF', 'vt_symbol': 'SF2101', 'total_turnover': 507498280.0}
{'kind': 'rr', 'vt_symbol': 'RR2012', 'total_turnover': 505222820.0}
{'kind': 'fb', 'vt_symbol': 'FB2010', 'total_turnover': 38542900.0}
{'kind': 'WH', 'vt_symbol': 'WH2101', 'total_turnover': 1927700.0}
{'kind': 'LR', 'vt_symbol': 'LR2011', 'total_turnover': 346200.0}
{'kind': 'RS', 'vt_symbol': 'RS2011', 'total_turnover': 264500.0}
{'kind': 'wr', 'vt_symbol': 'WR2103', 'total_turnover': 0.0}
{'kind': 'PM', 'vt_symbol': 'PM2105', 'total_turnover': 0.0}
{'kind': 'RI', 'vt_symbol': 'RI2101', 'total_turnover': 0.0}
{'kind': 'bb', 'vt_symbol': 'BB2107', 'total_turnover': 0.0}
{'kind': 'JR', 'vt_symbol': 'JR2101', 'total_turnover': 0.0}
database_manager:"BaseDatabaseManager"是个什么东东?
这个东西是python 3.6之后增加的新特性,看看这篇文章就知道了:Python 3 新特性:类型注解
本来应该是:
database_manager:BaseDatabaseManager,但是代码的作者可能是遇到了python包包含问题,
所以改成了:
database_manager:"BaseDatabaseManager",其实还是一个类型注解,只对类编辑环境有意义,对执行没有影响。
就比如:
>>> a : int = 10
>>> b : int = 1.2
>>> c:"int" = 3
>>> d:"int" = 3.1
>>> type(a)
<class 'int'>
>>> type(b)
<class 'float'>
>>> type(c)
<class 'int'>
>>> type(d)
<class 'float'>
无论a,b,c,d的类型注解为什么,随着它们的赋值不同,它们变量的类型是不一样的。
变量类型注解只是给代码编辑器使用和方便读者理解用的,不会限制实际赋值之后的数据类型。
def register_event(self) -> None:
""""""
self.event_engine.register(EVENT_TICK, self.process_tick_event)
self.event_engine.register(EVENT_ORDER, self.process_order_event)
self.event_engine.register(EVENT_TRADE, self.process_trade_event)
self.event_engine.register(EVENT_POSITION, self.process_position_event)
self.event_engine.register(EVENT_ACCOUNT, self.process_account_event)
self.event_engine.register(EVENT_CONTRACT, self.process_contract_event)
def process_position_event(self,event:Event) -> None:
""" """
position:PositionData = event.data
self.positions[position.vt_positionid] = position
print(f"oms process_position_event ! {datetime.now()}")
运行方式:python -m vnstation
oms process_position_event ! 2020-09-25 08:22:09.572302
oms process_position_event ! 2020-09-25 08:22:13.575021
oms process_position_event ! 2020-09-25 08:22:17.572799
oms process_position_event ! 2020-09-25 08:22:21.576441
oms process_position_event ! 2020-09-25 08:22:25.580222
...
...
保存文件:vnpy\usertools\chart_items.py
其中包含:
from datetime import datetime
from typing import List, Tuple, Dict
from vnpy.trader.ui import create_qapp, QtCore, QtGui, QtWidgets
from pyqtgraph import ScatterPlotItem
import pyqtgraph as pg
import numpy as np
import talib
import copy
from vnpy.chart import ChartWidget, VolumeItem, CandleItem
from vnpy.chart.item import ChartItem
from vnpy.chart.manager import BarManager
from vnpy.trader.object import (
BarData,
OrderData,
TradeData
)
from vnpy.trader.object import Direction, Exchange, Interval, Offset, Status, Product, OptionType, OrderType
from collections import OrderedDict
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")
class LineItem(CandleItem):
""""""
def __init__(self, manager: BarManager):
""""""
super().__init__(manager)
self.white_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 255), width=1)
def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
""""""
last_bar = self._manager.get_bar(ix - 1)
# Create objects
picture = QtGui.QPicture()
painter = QtGui.QPainter(picture)
# Set painter color
painter.setPen(self.white_pen)
# Draw Line
end_point = QtCore.QPointF(ix, bar.close_price)
if last_bar:
start_point = QtCore.QPointF(ix - 1, last_bar.close_price)
else:
start_point = end_point
painter.drawLine(start_point, end_point)
# Finish
painter.end()
return picture
def get_info_text(self, ix: int) -> str:
""""""
text = ""
bar = self._manager.get_bar(ix)
if bar:
text = f"Close:{bar.close_price}"
return text
class SmaItem(CandleItem):
""""""
def __init__(self, manager: BarManager):
""""""
super().__init__(manager)
self.blue_pen: QtGui.QPen = pg.mkPen(color=(100, 100, 255), width=2)
self.sma_window = 10
self.sma_data: Dict[int, float] = {}
def get_sma_value(self, ix: int) -> float:
""""""
if ix < 0:
return 0
# When initialize, calculate all rsi value
if not self.sma_data:
bars = self._manager.get_all_bars()
close_data = [bar.close_price for bar in bars]
sma_array = talib.SMA(np.array(close_data), self.sma_window)
for n, value in enumerate(sma_array):
self.sma_data[n] = value
# Return if already calcualted
if ix in self.sma_data:
return self.sma_data[ix]
# Else calculate new value
close_data = []
for n in range(ix - self.sma_window, ix + 1):
bar = self._manager.get_bar(n)
close_data.append(bar.close_price)
sma_array = talib.SMA(np.array(close_data), self.sma_window)
sma_value = sma_array[-1]
self.sma_data[ix] = sma_value
return sma_value
def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
""""""
sma_value = self.get_sma_value(ix)
last_sma_value = self.get_sma_value(ix - 1)
# Create objects
picture = QtGui.QPicture()
painter = QtGui.QPainter(picture)
# Set painter color
painter.setPen(self.blue_pen)
# Draw Line
start_point = QtCore.QPointF(ix-1, last_sma_value)
end_point = QtCore.QPointF(ix, sma_value)
painter.drawLine(start_point, end_point)
# Finish
painter.end()
return picture
def get_info_text(self, ix: int) -> str:
""""""
if ix in self.sma_data:
sma_value = self.sma_data[ix]
text = f"SMA {sma_value:.1f}"
else:
text = "SMA -"
return text
class RsiItem(ChartItem):
""""""
def __init__(self, manager: BarManager):
""""""
super().__init__(manager)
self.white_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 255), width=1)
self.yellow_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 0), width=2)
self.rsi_window = 14
self.rsi_data: Dict[int, float] = {}
def get_rsi_value(self, ix: int) -> float:
""""""
if ix < 0:
return 50
# When initialize, calculate all rsi value
if not self.rsi_data:
bars = self._manager.get_all_bars()
close_data = [bar.close_price for bar in bars]
rsi_array = talib.RSI(np.array(close_data), self.rsi_window)
for n, value in enumerate(rsi_array):
self.rsi_data[n] = value
# Return if already calcualted
if ix in self.rsi_data:
return self.rsi_data[ix]
# Else calculate new value
close_data = []
for n in range(ix - self.rsi_window, ix + 1):
bar = self._manager.get_bar(n)
close_data.append(bar.close_price)
rsi_array = talib.RSI(np.array(close_data), self.rsi_window)
rsi_value = rsi_array[-1]
self.rsi_data[ix] = rsi_value
return rsi_value
def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
""""""
rsi_value = self.get_rsi_value(ix)
last_rsi_value = self.get_rsi_value(ix - 1)
# Create objects
picture = QtGui.QPicture()
painter = QtGui.QPainter(picture)
# Draw RSI line
painter.setPen(self.yellow_pen)
if np.isnan(last_rsi_value) or np.isnan(rsi_value):
# print(ix - 1, last_rsi_value,ix, rsi_value,)
pass
else:
end_point = QtCore.QPointF(ix, rsi_value)
start_point = QtCore.QPointF(ix - 1, last_rsi_value)
painter.drawLine(start_point, end_point)
# Draw oversold/overbought line
painter.setPen(self.white_pen)
painter.drawLine(
QtCore.QPointF(ix, 70),
QtCore.QPointF(ix - 1, 70),
)
painter.drawLine(
QtCore.QPointF(ix, 30),
QtCore.QPointF(ix - 1, 30),
)
# Finish
painter.end()
return picture
def boundingRect(self) -> QtCore.QRectF:
""""""
# min_price, max_price = self._manager.get_price_range()
rect = QtCore.QRectF(
0,
0,
len(self._bar_picutures),
100
)
return rect
def get_y_range( self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
""" """
return 0, 100
def get_info_text(self, ix: int) -> str:
""""""
if ix in self.rsi_data:
rsi_value = self.rsi_data[ix]
text = f"RSI {rsi_value:.1f}"
# print(text)
else:
text = "RSI -"
return text
def to_int(value: float) -> int:
""""""
return int(round(value, 0))
""" 将y方向的显示范围扩大到1.1 """
def adjust_range(in_range:Tuple[float, float])->Tuple[float, float]:
ret_range:Tuple[float, float]
diff = abs(in_range[0] - in_range[1])
ret_range = (in_range[0]-diff*0.05,in_range[1]+diff*0.05)
return ret_range
class MacdItem(ChartItem):
""""""
_values_ranges: Dict[Tuple[int, int], Tuple[float, float]] = {}
last_range:Tuple[int, int] = (-1,-1) # 最新显示K线索引范围
def __init__(self, manager: BarManager):
""""""
super().__init__(manager)
self.white_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 255), width=1)
self.yellow_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 0), width=1)
self.red_pen: QtGui.QPen = pg.mkPen(color=(255, 0, 0), width=1)
self.green_pen: QtGui.QPen = pg.mkPen(color=(0, 255, 0), width=1)
self.short_window = 12
self.long_window = 26
self.M = 9
self.macd_data: Dict[int, Tuple[float,float,float]] = {}
def get_macd_value(self, ix: int) -> Tuple[float,float,float]:
""""""
if ix < 0:
return (0.0,0.0,0.0)
# When initialize, calculate all macd value
if not self.macd_data:
bars = self._manager.get_all_bars()
close_data = [bar.close_price for bar in bars]
diffs,deas,macds = talib.MACD(np.array(close_data),
fastperiod=self.short_window,
slowperiod=self.long_window,
signalperiod=self.M)
for n in range(0,len(diffs)):
self.macd_data[n] = (diffs[n],deas[n],macds[n])
# Return if already calcualted
if ix in self.macd_data:
return self.macd_data[ix]
# Else calculate new value
close_data = []
for n in range(ix-self.long_window-self.M+1, ix + 1):
bar = self._manager.get_bar(n)
close_data.append(bar.close_price)
diffs,deas,macds = talib.MACD(np.array(close_data),
fastperiod=self.short_window,
slowperiod=self.long_window,
signalperiod=self.M)
diff,dea,macd = diffs[-1],deas[-1],macds[-1]
self.macd_data[ix] = (diff,dea,macd)
return (diff,dea,macd)
def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
""""""
macd_value = self.get_macd_value(ix)
last_macd_value = self.get_macd_value(ix - 1)
# # Create objects
picture = QtGui.QPicture()
painter = QtGui.QPainter(picture)
# # Draw macd lines
if np.isnan(macd_value[0]) or np.isnan(last_macd_value[0]):
# print("略过macd lines0")
pass
else:
end_point0 = QtCore.QPointF(ix, macd_value[0])
start_point0 = QtCore.QPointF(ix - 1, last_macd_value[0])
painter.setPen(self.white_pen)
painter.drawLine(start_point0, end_point0)
if np.isnan(macd_value[1]) or np.isnan(last_macd_value[1]):
# print("略过macd lines1")
pass
else:
end_point1 = QtCore.QPointF(ix, macd_value[1])
start_point1 = QtCore.QPointF(ix - 1, last_macd_value[1])
painter.setPen(self.yellow_pen)
painter.drawLine(start_point1, end_point1)
if not np.isnan(macd_value[2]):
if (macd_value[2]>0):
painter.setPen(self.red_pen)
painter.setBrush(pg.mkBrush(255,0,0))
else:
painter.setPen(self.green_pen)
painter.setBrush(pg.mkBrush(0,255,0))
painter.drawRect(QtCore.QRectF(ix-0.3,0,0.6,macd_value[2]))
else:
# print("略过macd lines2")
pass
painter.end()
return picture
def boundingRect(self) -> QtCore.QRectF:
""""""
min_y, max_y = self.get_y_range()
rect = QtCore.QRectF(
0,
min_y,
len(self._bar_picutures),
max_y
)
return rect
def get_y_range(self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
# 获得3个指标在y轴方向的范围
# hxxjava 修改,2020-6-29
# 当显示范围改变时,min_ix,max_ix的值不为None,当显示范围不变时,min_ix,max_ix的值不为None,
offset = max(self.short_window,self.long_window) + self.M - 1
if not self.macd_data or len(self.macd_data) < offset:
return 0.0, 1.0
# print("len of range dict:",len(self._values_ranges),",macd_data:",len(self.macd_data),(min_ix,max_ix))
if min_ix != None: # 调整最小K线索引
min_ix = max(min_ix,offset)
if max_ix != None: # 调整最大K线索引
max_ix = min(max_ix, len(self.macd_data)-1)
last_range = (min_ix,max_ix) # 请求的最新范围
if last_range == (None,None): # 当显示范围不变时
if self.last_range in self._values_ranges:
# 如果y方向范围已经保存
# 读取y方向范围
result = self._values_ranges[self.last_range]
# print("1:",self.last_range,result)
return adjust_range(result)
else:
# 如果y方向范围没有保存
# 从macd_data重新计算y方向范围
min_ix,max_ix = 0,len(self.macd_data)-1
macd_list = list(self.macd_data.values())[min_ix:max_ix + 1]
ndarray = np.array(macd_list)
max_price = np.nanmax(ndarray)
min_price = np.nanmin(ndarray)
# 保存y方向范围,同时返回结果
result = (min_price, max_price)
self.last_range = (min_ix,max_ix)
self._values_ranges[self.last_range] = result
# print("2:",self.last_range,result)
return adjust_range(result)
""" 以下为显示范围变化时 """
if last_range in self._values_ranges:
# 该范围已经保存过y方向范围
# 取得y方向范围,返回结果
result = self._values_ranges[last_range]
# print("3:",last_range,result)
return adjust_range(result)
# 该范围没有保存过y方向范围
# 从macd_data重新计算y方向范围
macd_list = list(self.macd_data.values())[min_ix:max_ix + 1]
ndarray = np.array(macd_list)
max_price = np.nanmax(ndarray)
min_price = np.nanmin(ndarray)
# 取得y方向范围,返回结果
result = (min_price, max_price)
self.last_range = last_range
self._values_ranges[self.last_range] = result
# print("4:",self.last_range,result)
return adjust_range(result)
def get_info_text(self, ix: int) -> str:
# """"""
if ix in self.macd_data:
diff,dea,macd = self.macd_data[ix]
words = [
f"diff {diff:.3f}"," ",
f"dea {dea:.3f}"," ",
f"macd {macd:.3f}"
]
text = "\n".join(words)
else:
text = "diff - \ndea - \nmacd -"
return text
class TradeItem(ScatterPlotItem,CandleItem):
"""
成交单绘图部件
"""
def __init__(self, manager: BarManager):
""""""
ScatterPlotItem.__init__(self)
# CandleItem.__init__(self,manager)
# super(TradeItem,self).__init__(manager)
super(CandleItem,self).__init__(manager)
self.blue_pen: QtGui.QPen = pg.mkPen(color=(100, 100, 255), width=2)
self.trades : Dict[int,Dict[str,TradeData]] = {} # {ix:{tradeid:trade}}
def add_trades(self,trades:List[TradeData]):
""" 增加成交单列表到TradeItem """
for trade in trades:
self.add_trade(trade)
self.set_scatter_data()
self.update()
def add_trade(self,trade:TradeData,draw:bool=False):
""" 增加一个成交单到TradeItem """
# 这里使用reverse=True,是考虑到实盘成交往往发生在最新的bar里,可以加快搜索速度
od = OrderedDict(sorted(self._manager._datetime_index_map.items(),key = lambda t:t[0],reverse=True))
idx = self._manager.get_count() - 1
for dt,ix in od.items():
# print(f"dt={dt}\ntrade.datetime {trade.datetime}")
dt1 = CHINA_TZ.localize(datetime.combine(dt.date(),dt.time()))
if dt1 <= trade.datetime:
# print(f"【dt={dt},dt1={dt1},dt2={trade.datetime} ix={ix}】")
idx = ix
break
# 注意:一个bar期间可能发生多个成交单
if idx in self.trades:
self.trades[idx][trade.tradeid] = trade
else:
self.trades[idx] = {trade.tradeid:trade}
if draw:
self.set_scatter_data()
self.update()
# print(f"add_trade idx={idx} trade={trade}")
def set_scatter_data(self):
""" 把成交单列表绘制到ScatterPlotItem上 """
scatter_datas = []
for ix in self.trades:
for trade in self.trades[ix].values():
scatter = {
"pos" : (ix, trade.price),
"data": 1,
"size": 14,
"pen": pg.mkPen((255, 255, 255)),
}
if trade.direction == Direction.LONG:
scatter_symbol = "t1" # Up arrow
else:
scatter_symbol = "t" # Down arrow
if trade.offset == Offset.OPEN:
scatter_brush = pg.mkBrush((255, 255, 0)) # Yellow
else:
scatter_brush = pg.mkBrush((0, 0, 255)) # Blue
scatter["symbol"] = scatter_symbol
scatter["brush"] = scatter_brush
scatter_datas.append(scatter)
self.setData(scatter_datas)
def get_info_text(self, ix: int) -> str:
""""""
if ix in self.trades:
text = "成交:"
for tradeid,trade in self.trades[ix].items():
# TradeData
text += f"\n{trade.price}{trade.direction.value}{trade.offset.value}{trade.volume}手"
else:
text = "成交:-"
return text
class OrderItem(ScatterPlotItem,CandleItem):
"""
委托单绘图部件
"""
def __init__(self, manager: BarManager):
""""""
ScatterPlotItem.__init__(self)
super(CandleItem,self).__init__(manager)
self.orders : Dict[int,Dict[str,Order]] = {} # {ix:{orderid:order}}
def add_orders(self,orders:List[OrderData]):
""" 增加委托单列表到OrderItem """
for order in orders:
if order.datetime:
self.add_order(order)
self.set_scatter_data()
self.update()
def add_order(self,order:OrderData,draw:bool=False):
""" 增加一个委托单到OrderItem """
# 这里使用reverse=True,是考虑到实盘成交往往发生在最新的bar里,可以加快搜索速度
od = OrderedDict(sorted(self._manager._datetime_index_map.items(),key = lambda t:t[0],reverse=True))
idx = self._manager.get_count() - 1
for dt,ix in od.items():
# print(f"dt={dt}\ntrade.datetime {trade.datetime}")
dt1 = CHINA_TZ.localize(datetime.combine(dt.date(),dt.time()))
if dt1 <= order.datetime:
# print(f"【dt={dt},dt1={dt1},dt2={order.datetime} ix={ix}】")
idx = ix
break
# 注意:一个bar期间可能发生多个委托单
if idx in self.orders:
self.orders[idx][order.orderid] = order
else:
self.orders[idx] = {order.orderid:order}
if draw:
self.set_scatter_data()
self.update()
def set_scatter_data(self):
""" 把委托单列表绘制到ScatterPlotItem上 """
scatter_datas = []
for ix in self.orders:
lowest,highest=self.get_y_range()
# print(f"range={lowest,highest}")
for order in self.orders[ix].values():
# 处理委托报价超出显示范围的问题
if order.price>highest:
show_price = highest - 7
elif order.price<lowest:
show_price = lowest + 7
else:
show_price = order.price
scatter = {
"pos" : (ix, show_price),
"data": 1,
"size": 14,
"pen": pg.mkPen((255, 255, 255)),
}
if order.direction == Direction.LONG:
scatter_symbol = "t1" # Up arrow
else:
scatter_symbol = "t" # Down arrow
if order.offset == Offset.OPEN:
scatter_brush = pg.mkBrush((0, 128, 128)) # Yellow
else:
scatter_brush = pg.mkBrush((128, 128, 0)) # Blue
scatter["symbol"] = scatter_symbol
scatter["brush"] = scatter_brush
scatter_datas.append(scatter)
self.setData(scatter_datas)
def get_info_text(self, ix: int) -> str:
""""""
if ix in self.orders:
text = "委托:"
for orderid,order in self.orders[ix].items():
# OrderData
text += f"\n{order.price}{order.direction.value}{order.offset.value}{order.volume}手"
else:
text = "委托:-"
return text
为ChartWidget类增加下面的函数:
def get_item(self,item_name:str): # hxxjava add
"""
Get chart item by item's name.
"""
return self._items.get(item_name,None)
保存文件:vnpy\usertools\kx_chart.py
from datetime import datetime
from typing import List, Tuple, Dict
import numpy as np
import pyqtgraph as pg
import talib
import copy
from vnpy.trader.ui import create_qapp, QtCore, QtGui, QtWidgets
from vnpy.trader.database import database_manager
from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.object import BarData
from vnpy.chart import ChartWidget, VolumeItem, CandleItem
from vnpy.chart.item import ChartItem
from vnpy.chart.manager import BarManager
from vnpy.chart.base import NORMAL_FONT
from vnpy.trader.engine import MainEngine
from vnpy.event import Event, EventEngine
from vnpy.trader.event import (
EVENT_TICK,
EVENT_TRADE,
EVENT_ORDER,
EVENT_POSITION,
EVENT_ACCOUNT,
EVENT_LOG
)
from vnpy.app.cta_strategy.base import (
EVENT_CTA_TICK,
EVENT_CTA_BAR,
EVENT_CTA_ORDER,
EVENT_CTA_TRADE,
EVENT_CTA_HISTORY_BAR
)
from vnpy.trader.object import (
Direction,
Exchange,
Interval,
Offset,
Status,
Product,
OptionType,
OrderType,
OrderData,
TradeData,
)
from vnpy.usertools.chart_items import (
LineItem,
RsiItem,
SmaItem,
MacdItem,
TradeItem,
OrderItem,
)
class NewChartWidget(ChartWidget):
"""
基于ChartWidget的K线图表
"""
MIN_BAR_COUNT = 100
signal_cta_history_bar:QtCore.pyqtSignal = QtCore.pyqtSignal(Event)
signal_cta_tick: QtCore.pyqtSignal = QtCore.pyqtSignal(Event)
signal_cta_bar:QtCore.pyqtSignal = QtCore.pyqtSignal(Event)
def __init__(self, parent: QtWidgets.QWidget = None,event_engine: EventEngine = None,strategy_name:str=""):
""" 初始化 """
super().__init__(parent)
self.strategy_name = strategy_name
self.event_engine = event_engine
# 创建K线主图及多个绘图部件
self.add_plot("candle", hide_x_axis=True)
self.add_item(CandleItem, "candle", "candle")
self.add_item(LineItem, "line", "candle")
self.add_item(SmaItem, "sma", "candle")
self.add_item(OrderItem, "order", "candle")
self.add_item(TradeItem, "trade", "candle")
# 创建成交量附图及绘图部件
self.add_plot("volume", maximum_height=150)
self.add_item(VolumeItem, "volume", "volume")
# 创建RSI附图及绘图部件
self.add_plot("rsi", maximum_height=150)
self.add_item(RsiItem, "rsi", "rsi")
# 创建MACD附图及绘图部件
self.add_plot("macd", maximum_height=150)
self.add_item(MacdItem, "macd", "macd")
# 创建最新价格线、光标
self.add_last_price_line()
self.add_cursor()
self.setWindowTitle(f"K线图表——{symbol}.{exchange.value},{interval},{start}-{end}")
# 委托单列表
self.orders:List[str,OrderData] = {}
# 成交单列表
self.trades:List[str,TradeData] = {}
# self.register_event()
# self.event_engine.start()
def register_event(self) -> None:
""""""
self.signal_cta_history_bar.connect(self.process_cta_history_bar)
self.event_engine.register(EVENT_CTA_HISTORY_BAR, self.signal_cta_history_bar.emit)
self.signal_cta_tick.connect(self.process_tick_event)
self.event_engine.register(EVENT_CTA_TICK, self.signal_cta_tick.emit)
self.signal_cta_bar.connect(self.process_cta_bar)
self.event_engine.register(EVENT_CTA_BAR, self.signal_cta_bar.emit)
def process_cta_history_bar(self, event:Event) -> None:
""" 处理历史K线推送 """
strategy_name,history_bars = event.data
if strategy_name == self.strategy_name:
self.update_history(history_bars)
# print(f" {strategy_name} got an EVENT_CTA_HISTORY_BAR")
def process_tick_event(self, event: Event) -> None:
""" 处理tick数据推送 """
strategy_name,tick = event.data
if strategy_name == self.strategy_name:
if self.last_price_line:
self.last_price_line.setValue(tick.last_price)
#print(f" {strategy_name} got an EVENT_CTA_TICK")
def process_cta_bar(self, event:Event)-> None:
""" 处理K线数据推送 """
strategy_name,bar = event.data
if strategy_name == self.strategy_name:
self.update_bar(bar)
# print(f"{strategy_name} got an EVENT_CTA_BAR")
def add_last_price_line(self):
""""""
plot = list(self._plots.values())[0]
color = (255, 255, 255)
self.last_price_line = pg.InfiniteLine(
angle=0,
movable=False,
label="{value:.1f}",
pen=pg.mkPen(color, width=1),
labelOpts={
"color": color,
"position": 1,
"anchors": [(1, 1), (1, 1)]
}
)
self.last_price_line.label.setFont(NORMAL_FONT)
plot.addItem(self.last_price_line)
def update_history(self, history: List[BarData]) -> None:
"""
Update a list of bar data.
"""
self._manager.update_history(history)
for item in self._items.values():
item.update_history(history)
self._update_plot_limits()
self.move_to_right()
self.update_last_price_line(history[-1])
def update_bar(self, bar: BarData) -> None:
"""
Update single bar data.
"""
self._manager.update_bar(bar)
for item in self._items.values():
item.update_bar(bar)
self._update_plot_limits()
if self._right_ix >= (self._manager.get_count() - self._bar_count / 2):
self.move_to_right()
self.update_last_price_line(bar)
def update_last_price_line(self, bar: BarData) -> None:
""""""
if self.last_price_line:
self.last_price_line.setValue(bar.close_price)
def add_orders(self,orders:List[OrderData]) -> None:
"""
增加委托单列表到委托单绘图部件
"""
for order in orders:
self.orders[order.orderid] = order
order_item : OrderItem = self.get_item('order')
if order_item:
order_item.add_orders(self.orders.values())
def add_trades(self,trades:List[TradeData]) -> None:
"""
增加成交单列表到委托单绘图部件
"""
for trade in trades:
self.trades[trade.tradeid] = trade
trade_item : TradeItem = self.get_item('trade')
if trade_item:
trade_item.add_trades(self.trades.values())
################################################################
# 以下为测试代码
if __name__ == "__main__":
def make_trades():
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")
from vnpy.trader.object import Direction, Exchange, Interval, Offset, Status, Product, OptionType, OrderType,TradeData
trades = [
TradeData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', tradeid=' 455', direction=Direction.LONG, offset=Offset.OPEN, price=6131.0, volume=3, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 0, 1))),
TradeData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', tradeid=' 12738', direction=Direction.LONG, offset=Offset.OPEN, price=6142.0, volume=3, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 14, 46))),
TradeData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', tradeid=' 16233', direction=Direction.LONG, offset=Offset.OPEN, price=6158.0, volume=3, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 21, 59))),
TradeData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', tradeid=' 22815', direction=Direction.LONG, offset=Offset.OPEN, price=6180.0, volume=3, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 39, 53))),
TradeData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', tradeid=' 67570', direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=6400.0, volume=12, datetime=CHINA_TZ.localize(datetime(2020, 8, 14, 1, 44,35))),
]
return trades
def make_orders():
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")
orders = [
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 0, 1)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 0, 1)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=3, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 0, 1)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 14, 46)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 14, 46)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=3, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 14, 46)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 21, 59)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 21, 59)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=3, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 21, 59)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 39, 53)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 39, 53)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=3, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 39, 53)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', type=OrderType.LIMIT, direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=5870.0, volume=12.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', type=OrderType.LIMIT, direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=5870.0, volume=12, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 14, 1, 44,20)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', type=OrderType.LIMIT, direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=5870.0, volume=12, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 14, 1, 44,25)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', type=OrderType.LIMIT, direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=5870.0, volume=12, traded=12, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 14, 1, 44,35)), reference=''),
]
return orders
# 开始测试代码
app = create_qapp()
symbol = "ag2012"
exchange = Exchange.SHFE
interval=Interval.MINUTE
start=datetime(2020, 8, 13)
end=datetime(2020, 8, 15)
dynamic = False # 是否动态演示
n = 1000 # 缓冲K线根数
bars = database_manager.load_bar_data(
symbol=symbol,
exchange=exchange,
interval=interval,
start=start,
end=end
)
print(f"一共读取{len(bars)}根K线")
event_engine = EventEngine()
widget = NewChartWidget(event_engine = event_engine)
if dynamic:
history = bars[:n] # 先取得最早的n根bar作为历史
new_data = bars[n:] # 其它留着演示
else:
history = bars # 先取得最新的n根bar作为历史
new_data = [] # 演示的为空
# 绘制历史K线主图及各个副图
widget.update_history(history)
# 绘制委托单到主图
orders = make_orders()
widget.add_orders(orders)
# 绘制成交单到主图
trades = make_trades()
widget.add_trades(trades)
def update_bar():
if new_data:
bar = new_data.pop(0)
widget.update_bar(bar)
timer = QtCore.QTimer()
timer.timeout.connect(update_bar)
if dynamic:
timer.start(100)
widget.show()
event_engine.start()
app.exec_()
kx_chart.py中自动测试代码,直接用VSCode打开就可以运行。
在vnpy中使用数据管理模块,从米筐下载ag2012.SHFE的1分钟历史数据,必须包含8月13日~8月15日。
OrderItem是ChartWidget的一个主图绘制部件,它的功能:
1)在委托发生K线绘制委托单图形
2)当鼠标移动到委托单所在K线,在信息提示区提示成交的最新委托信息,注意:是最新的!
OrderItem也是一个伪装成CandleItem的ScatterPlotItem。
class OrderItem(ScatterPlotItem,CandleItem):
"""
委托单绘图部件
"""
def __init__(self, manager: BarManager):
""""""
ScatterPlotItem.__init__(self)
super(CandleItem,self).__init__(manager)
self.orders : Dict[int,Dict[str,Order]] = {} # {ix:{orderid:order}}
def add_orders(self,orders:List[OrderData]):
""" 增加委托单列表到OrderItem """
for order in orders:
if order.datetime:
self.add_order(order)
self.set_scatter_data()
self.update()
def add_order(self,order:OrderData,draw:bool=False):
""" 增加一个委托单到OrderItem """
# 这里使用reverse=True,是考虑到实盘成交往往发生在最新的bar里,可以加快搜索速度
od = OrderedDict(sorted(self._manager._datetime_index_map.items(),key = lambda t:t[0],reverse=True))
idx = self._manager.get_count() - 1
for dt,ix in od.items():
# print(f"dt={dt}\ntrade.datetime {trade.datetime}")
dt1 = CHINA_TZ.localize(datetime.combine(dt.date(),dt.time()))
if dt1 <= order.datetime:
print(f"【dt={dt},dt1={dt1},dt2={order.datetime} ix={ix}】")
idx = ix
break
# 注意:一个bar期间可能发生多个委托单
if idx in self.orders:
self.orders[idx][order.orderid] = order
else:
self.orders[idx] = {order.orderid:order}
if draw:
self.set_scatter_data()
self.update()
def set_scatter_data(self):
""" 把委托单列表绘制到ScatterPlotItem上 """
scatter_datas = []
for ix in self.orders:
lowest,highest=self.get_y_range()
# print(f"range={lowest,highest}")
for order in self.orders[ix].values():
# 处理委托报价超出显示范围的问题
if order.price>highest:
show_price = highest - 7
elif order.price<lowest:
show_price = lowest + 7
else:
show_price = order.price
scatter = {
"pos" : (ix, show_price),
"data": 1,
"size": 14,
"pen": pg.mkPen((255, 255, 255)),
}
if order.direction == Direction.LONG:
scatter_symbol = "t1" # Up arrow
else:
scatter_symbol = "t" # Down arrow
if order.offset == Offset.OPEN:
scatter_brush = pg.mkBrush((0, 128, 128)) # Yellow
else:
scatter_brush = pg.mkBrush((128, 128, 0)) # Blue
scatter["symbol"] = scatter_symbol
scatter["brush"] = scatter_brush
scatter_datas.append(scatter)
self.setData(scatter_datas)
def get_info_text(self, ix: int) -> str:
""""""
text = ""
if ix in self.orders:
for orderid,order in self.orders[ix].items():
# OrderData
text += f"委托:\n{order.price}{order.direction.value}{order.offset.value}{order.volume}手\n"
return text
请看看下面的委托单记录可以明白了:
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 0, 1)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 0, 1)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_1', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=3, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 0, 1)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 14, 46)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 14, 46)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_2', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=3, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 14, 46)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 21, 59)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 21, 59)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_3', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=3, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 21, 59)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 39, 53)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 39, 53)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_753490688_4', type=OrderType.LIMIT, direction=Direction.LONG, offset=Offset.OPEN, price=6494.0, volume=3, traded=3, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 13, 21, 39, 53)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', type=OrderType.LIMIT, direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=5870.0, volume=12.0, traded=0, status=Status.SUBMITTING, datetime=None, reference='TTS-ag2012'),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', type=OrderType.LIMIT, direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=5870.0, volume=12, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 14, 1, 44,20)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', type=OrderType.LIMIT, direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=5870.0, volume=12, traded=0, status=Status.SUBMITTING, datetime=CHINA_TZ.localize(datetime(2020, 8, 14, 1, 44,25)), reference=''),
OrderData(gateway_name='CTP', symbol='ag2012', exchange=Exchange.SHFE, orderid='3_1962356227_1', type=OrderType.LIMIT, direction=Direction.SHORT, offset=Offset.CLOSEYESTERDAY, price=5870.0, volume=12, traded=12, status=Status.ALLTRADED, datetime=CHINA_TZ.localize(datetime(2020, 8, 14, 1, 44,35)), reference=''),
add_order()中在查找委托单所属bar的ix的时候,代码效率不高,当self._manager._datetime_index_map很大的时候,可能存在效率问题。
希望有兴趣的大侠可以帮忙改进,让后贡献vnpy社区,代表社区先行谢过!
TradeItem是ChartWidget的一个主图绘制部件,它的功能:
1)在成交单发生K线绘制成交单图形
2)当鼠标移动到成交单所在K线,在信息提示区提示成交的成交信息
它是一个伪装成CandleItem的ScatterPlotItem。
class TradeItem(ScatterPlotItem,CandleItem):
"""
成交单绘图部件
"""
def __init__(self, manager: BarManager):
""""""
ScatterPlotItem.__init__(self)
# CandleItem.__init__(self,manager)
# super(TradeItem,self).__init__(manager)
super(CandleItem,self).__init__(manager)
self.blue_pen: QtGui.QPen = pg.mkPen(color=(100, 100, 255), width=2)
self.trades : Dict[int,Dict[str,TradeData]] = {} # {ix:{tradeid:trade}}
def add_trades(self,trades:List[TradeData]):
""" 增加成交单列表到TradeItem """
for trade in trades:
self.add_trade(trade)
self.set_scatter_data()
self.update()
def add_trade(self,trade:TradeData,draw:bool=False):
""" 增加一个成交单到TradeItem """
# 这里使用reverse=True,是考虑到实盘成交往往发生在最新的bar里,可以加快搜索速度
od = OrderedDict(sorted(self._manager._datetime_index_map.items(),key = lambda t:t[0],reverse=True))
idx = self._manager.get_count() - 1
for dt,ix in od.items():
# print(f"dt={dt}\ntrade.datetime {trade.datetime}")
dt1 = CHINA_TZ.localize(datetime.combine(dt.date(),dt.time()))
if dt1 <= trade.datetime:
# print(f"【dt={dt},dt1={dt1},dt2={trade.datetime} ix={ix}】")
idx = ix
break
# 注意:一个bar期间可能发生多个成交单
if idx in self.trades:
self.trades[idx][trade.tradeid] = trade
else:
self.trades[idx] = {trade.tradeid:trade}
if draw:
self.set_scatter_data()
self.update()
# print(f"add_trade idx={idx} trade={trade}")
def set_scatter_data(self):
""" 把成交单列表绘制到ScatterPlotItem上 """
scatter_datas = []
for ix in self.trades:
for trade in self.trades[ix].values():
scatter = {
"pos" : (ix, trade.price),
"data": 1,
"size": 14,
"pen": pg.mkPen((255, 255, 255)),
}
if trade.direction == Direction.LONG:
scatter_symbol = "t1" # Up arrow
else:
scatter_symbol = "t" # Down arrow
if trade.offset == Offset.OPEN:
scatter_brush = pg.mkBrush((255, 255, 0)) # Yellow
else:
scatter_brush = pg.mkBrush((0, 0, 255)) # Blue
scatter["symbol"] = scatter_symbol
scatter["brush"] = scatter_brush
scatter_datas.append(scatter)
self.setData(scatter_datas)
def get_info_text(self, ix: int) -> str:
""""""
text = ""
if ix in self.trades:
for tradeid,trade in self.trades[ix].items():
# TradeData
text += f"成交:\n{trade.price}{trade.direction.value}{trade.offset.value}{trade.volume}手\n"
# else:
# text = f"{self.__class__}"
return text
add_trade()中在查找成交单所属bar的ix的时候,代码效率不高,当self._manager._datetime_index_map很大的时候,可能存在效率问题。
希望有兴趣的大侠可以帮忙改进,然后贡献vnpy社区,代表社区先行谢过!
现在绝大部分模块用、应用都已经对使用到的时间做了本地化处理,因此,如果你有一个新构造的datetime变量,应该先在使用CHINA_TZ.localize(),然后再使用,就会避免因为时区不同导致的时间比较上的错误。方法如下:
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")
CHINA_TZ.localize(dt_var)
数据下载模块从米筐下载的数据,下载到本地本地供仿真和实盘使用,可是vnpy没有对其数据中的时间做本地化处理,直接使用会有问题。
虽然他们看来是一样的时间刻度,可和实盘中的时间下单和成交时间有几分钟的差距,
同样的时间面值,因为时区的不同导致比较得不到想要的结果:
下面的dt1和dt2是为把数据库中的时间做比较得到,其中:
dt1是数据库中数据的时间,它们的时区是+08:06,
dt2是成交单中数据的时间,它们的时区是+08:00
【dt1=2020-08-13 21:00:00+08:06,dt2=2020-08-13 21:00:01+08:00 ix=375】
【dt1=2020-08-13 21:14:00+08:06,dt2=2020-08-13 21:14:46+08:00 ix=389】
【dt1=2020-08-13 21:21:00+08:06,dt2=2020-08-13 21:21:59+08:00 ix=396】
【dt1=2020-08-13 21:39:00+08:06,dt2=2020-08-13 21:39:53+08:00 ix=414】
【dt1=2020-08-14 01:44:00+08:06,dt2=2020-08-14 01:44:35+08:00 ix=659】
它们带来的问题是
1)不可以直接比较,必须先转化为相同是时区配置才可以比较
2)很迷糊人,看似相同的时间,可重新就是判断是不想等
class CandleChartDialog(QtWidgets.QDialog):
"""
"""
def __init__(self):
""""""
super().__init__()
self.dt_ix_map = {}
self.updated = False
self.init_ui()
def init_ui(self):
""""""
self.setWindowTitle("回测K线图表")
self.resize(1400, 800)
# Create chart widget
self.chart = ChartWidget()
self.chart.add_plot("candle", hide_x_axis=True)
self.chart.add_plot("volume", maximum_height=200)
self.chart.add_item(CandleItem, "candle", "candle")
self.chart.add_item(VolumeItem, "volume", "volume")
self.chart.add_cursor()
# Add scatter item for showing tradings
self.trade_scatter = pg.ScatterPlotItem()
candle_plot = self.chart.get_plot("candle")
candle_plot.addItem(self.trade_scatter)
# Set layout
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self.chart)
self.setLayout(vbox)
def update_history(self, history: list):
""""""
self.updated = True
self.chart.update_history(history)
for ix, bar in enumerate(history):
self.dt_ix_map[bar.datetime] = ix
def update_trades(self, trades: list):
""""""
trade_data = []
for trade in trades:
ix = self.dt_ix_map[trade.datetime]
scatter = {
"pos": (ix, trade.price),
"data": 1,
"size": 14,
"pen": pg.mkPen((255, 255, 255))
}
if trade.direction == Direction.LONG:
scatter_symbol = "t1" # Up arrow
else:
scatter_symbol = "t" # Down arrow
if trade.offset == Offset.OPEN:
scatter_brush = pg.mkBrush((255, 255, 0)) # Yellow
else:
scatter_brush = pg.mkBrush((0, 0, 255)) # Blue
scatter["symbol"] = scatter_symbol
scatter["brush"] = scatter_brush
trade_data.append(scatter)
self.trade_scatter.setData(trade_data)
def clear_data(self):
""""""
self.updated = False
self.chart.clear_all()
self.dt_ix_map.clear()
self.trade_scatter.clear()
def is_updated(self):
""""""
return self.updated
它和其他的绘图部件不同,其他的都是ChartItem类型继承得到,只有它例外,它是一个ScatterPlotItem。
self.trade_scatter = pg.ScatterPlotItem()
看看update_trades() 的代码中
for trade in trades:
ix = self.dt_ix_map[trade.datetime] # 查找一个成交单(trade)是属于哪个BarData的索引
这里能够不出错,完全是因为在回测中,人为固定地把发出交易信号的那个bar的开始时间datetime赋值给了trade.datetime!
让我们来看看app\cta_strategy\backtesting.py中的class BacktestingEngine,它在bar模式的时候,是这样生成成交单的:
trade = TradeData(
symbol=order.symbol,
exchange=order.exchange,
orderid=order.orderid,
tradeid=str(self.trade_count),
direction=order.direction,
offset=order.offset,
price=trade_price,
volume=order.volume,
datetime=self.datetime,
gateway_name=self.gateway_name,
)
其中trade中的datetime=self.datetime,而self.datetime是这样赋值的:
def run_backtesting(self):
""""""
if self.mode == BacktestingMode.BAR:
func = self.new_bar
else:
func = self.new_tick
self.strategy.on_init()
# Use the first [days] of history data for initializing strategy
day_count = 1
ix = 0
for ix, data in enumerate(self.history_data):
if self.datetime and data.datetime.day != self.datetime.day:
day_count += 1
if day_count >= self.days:
break
self.datetime = data.datetime # 它是用self.history_data中代表bar的data.datetime赋值的!
... ...
可是我们知道trade.datetime是不可能总是恰好等于bar的开始时间datetime的,成交时间可能是一个bar形成期间的任何时间。
回到CandleChartDialog,由上面可知self.dt_ix_map的维护是update_history()维护的,它是由bar的开始时间datetime和其顺序ix构成的一个字典。
原因是trade.datetime在self.dt_ix_map字典的键值中大概率是不存在的,因此ix = self.dt_ix_map[trade.datetime]语句会出错!而ScatterPlotItem是通过"pos": (ix, trade.price)来确定代表买卖的上下三角形来绘图的,因此CandleChartDialog是不可以简单地在显示实盘成交单的地方引用的。
思路:
self.trades:Dict[int,Dict[str,TradeData]] = {} # 其键值为成交发生bar的索引,内容为成交单字典
根据trade.datetime字段在self.dt_ix_map中所在位置的两个相邻时间dt0和dt1
满足:
def add_trade(self,trade:TradeData):
# 这里使用reverse=True,是考虑到实盘成交往往发生在最新的bar里,可以加快搜索速度
od = OrderedDict(sorted(self.dt_ix_map.items(),key = lambda t:t[0],reverse=True))
idx = 0
for dt,ix in od.items():
if dt <= trade.datetime:
idx = idx
break
# 注意:一个bar期间可能发生多个成交单
if idx not in self.trades:
self.trades[idx] = {trade.tradeid: trade}
else:
self.trades[idx][trade.tradeid] = trade
def update_trades(self, trades: list):
""""""
for trade in trades:
# 寻找每个成交单对应的bar的索引,并且保证为字典
self.add_trade(trade)
trade_data = []
for ix in self.trades:
for tradeid,trade in self.trades[ix].items():
scatter = {
"pos": (ix, trade.price),
"data": 1,
"size": 14,
"pen": pg.mkPen((255, 255, 255))
}
if trade.direction == Direction.LONG:
scatter_symbol = "t1" # Up arrow
else:
scatter_symbol = "t" # Down arrow
if trade.offset == Offset.OPEN:
scatter_brush = pg.mkBrush((255, 255, 0)) # Yellow
else:
scatter_brush = pg.mkBrush((0, 0, 255)) # Blue
scatter["symbol"] = scatter_symbol
scatter["brush"] = scatter_brush
trade_data.append(scatter)
self.trade_scatter.setData(trade_data)
参见:典型绘图部件及使用方法
@dataclass
class BarData(BaseData):
"""
Candlestick bar data of a certain trading period.
"""
symbol: str
exchange: Exchange
datetime: datetime
interval: Interval = None
volume: float = 0
open_interest: float = 0
open_price: float = 0
high_price: float = 0
low_price: float = 0
close_price: float = 0
def __post_init__(self):
""""""
self.vt_symbol = f"{self.symbol}.{self.exchange.value}"
datetime:是BarData的开始时间
interval的类型是Interval枚举值,应该是时间单位,取值只能够是:
MINUTE = "1m":表示1分钟
HOUR = "1h":表示1小时
DAILY = "d":表示1日
WEEKLY = "w":表示1周
如果给你两个BarData对象,怎么区分它是5分钟的和30分钟的呢?它们的Interval只能为MINUTE,也不可能是其他的更大的单位。这样它们岂不是都长的一个样了吗?
是否还需要增加表示时长的width或者表示结束时间的endtime来作为区分呢?
如果是使用width的话,可以这样表示
datetime='2020-9-8 9:00:00',interval = Interval.MINUTE,width=5 —— 5分钟
datetime='2020-9-8 9:00:00',interval = Interval.MINUTE,width=15 —— 5分钟
datetime='2020-9-8 9:00:00',interval = Interval.MINUTE,width=30 —— 30分钟
如果使用endtime的话,可以这样表示:(interval参数就有些多余)
datetime='2020-9-8 9:00:00',endtime='2020-9-8 9:05:00',interval = Interval.MINUTE —— 5分钟
datetime='2020-9-8 9:00:00',endtime='2020-9-8 9:15:00',interval = Interval.MINUTE —— 5分钟
datetime='2020-9-8 9:00:00',endtime='2020-9-8 9:30:00',interval = Interval.MINUTE —— 30分钟
MY FOLDER :我的目录
COMPARED FOLDER:被比较目录
开户在中信建投期货,申请做CTP认证,填了个表格发过去。第二天说要先做CTP认证测试。好,做吧!
网关选择CTP测试
输入测评给用户名、密码、经纪商代码、交易服务器ip及端口、行情服务器ip及端口 和 认证码等项:
按连接按钮,却出现下面的错误,连续3天解决不了。也不知道时期货公司的问题,还是咱自己的问题。
找到一台未安装过vnpy的电脑,安装vnpy后直接重复上面的步骤,OK,一次就通过测评了!
TEXT_TRADER_CONFIG = """
1. 请勾选需要使用的底层接口和上层应用模块
2. 配置完毕后,点击“启动”按钮来打开VN Trader
3. VN Trader运行时请勿关闭VN Station(会造成退出)
4. CTP、CTP测试接口不能同时加载(会导致API版本错误)
5. CTP Mini和CTP Mini测试接口不能同时加载(会导致API版本错误)
"""
那就找到vnpy\gateway\ctp__init__.py,按照下面修改:
from .ctp_gateway import CtpGateway
# 增加主动出错定位的代码
print("【CtpGateway inited】")
abc = not_exist_var # 制造一个错误,以便发现错误
再次在VNStation Prompt窗口中输入 python -m vnstation命令,启动VNStation,如果不能顺利启动到主界面,并且出现了错误,说明你的修改导致了CtpGateway模块被引用过(因为我们制作了一个错误,只要你加载了CTP模块,就会出错!),根据报出的出错语句,你就可以顺利地将问题找到。目标就是不可以在没有寻找CTP网关接口的情况下,加载CTP模块,from,import语句是最容易出错的地方,
如:
from vnpy.gateway.ctp.ctp_gateway import xxxxx
哪怕是import的符号不是CtpGateway也不可以。
把制造错误的语句注释掉。
from .ctp_gateway import CtpGateway
# 增加主动出错定位的代码
print("【CtpGateway inited】")
# abc = not_exist_var # 制造一个错误,以便发现错误
网关接口的状态感知
网关分很多种,如CTP,XTP等 。其中CTP中又包含行情(MD)接口和交易(TD)接口。它们在连接和断开的时候,都有推送接口。这些接口是:
MD的onFrontConnected()和onFrontDisconnected(),TD的onFrontConnected()和onFrontDisconnected()。行情和交易服务器的接口状态,vnpy已经做了log输出,用户是可以阅读的,可是不方便软件使用,于是对接口和主引擎做了如下修改,以便于上层应用可以使用这些消息,编写出针对行情服务器通断情况的处理方法,也可以编写针对交易服务器连接的处理方法。
下面的代码修改时,需要相互引用的部分,如from... import... 之类的,就不再逐一指出了,太基础了。
EVENT_CONNECT = "eConnected" # hxxjava add
EVENT_DISCONNECT = "eDisconnected" # hxxjava add
@dataclass
class GatewayData(): # hxxjava add
"""
Gateway data
"""
name:str = "" # 网关名称,如 'CTP'
type:str = "" # 接口类型,如 'TD','MD'
reason:int = 0 # 状态或者原因
def on_connect(self,gateway:GatewayData) -> None: # hxxjava add
"""
gateway connect enent
"""
self.on_event(EVENT_CONNECT, gateway)
def on_disconnect(self,gateway:GatewayData) -> None: # hxxjava add
"""
gateway disconnect enent
"""
self.on_event(EVENT_DISCONNECT, gateway)
1 CtpMdApi类的两个函数:
def onFrontConnected(self):
"""
Callback when front server is connected.
"""
self.gateway.on_connect(GatewayData("CTP",'MD')) # hxxjava add
self.gateway.write_log("行情服务器连接成功")
self.login()
def onFrontDisconnected(self, reason: int):
"""
Callback when front server is disconnected.
"""
self.login_status = False
self.gateway.on_disconnect(GatewayData(name="CTP",type='MD',reason=reason)) # hxxjava add
self.gateway.write_log(f"行情服务器连接断开,原因{reason}")
2 CtpTdApi类的两个函数:
def onFrontConnected(self):
""""""
self.gateway.on_connect(GatewayData("CTP",'TD')) # hxxjava add
self.gateway.write_log("交易服务器连接成功")
if self.auth_code:
self.authenticate()
else:
self.login()
def onFrontDisconnected(self, reason: int):
""""""
self.login_status = False
self.gateway.on_disconnect(GatewayData(name="CTP",type='TD',reason=reason)) # hxxjava add
self.gateway.write_log(f"交易服务器连接断开,原因{reason}")
修改register_event()函数:
def register_event(self):
... ...
# 添加下面两句
self.event_engine.register(EVENT_CONNECT, self.process_connect_event)
self.event_engine.register(EVENT_DISCONNECT, self.process_disconnect_event)
添加下面两个消息处理函数:
def process_connect_event(self, event: Event) -> None:
gateway:GatewayData = event.data
print(f"gateway connect event {gateway}")
def process_disconnect_event(self, event: Event) -> None:
gateway:GatewayData = event.data
print(f"gateway disconnect_event {gateway}")
在VN Studio Prompt窗口输入python - m vnstation命令,启动vnpy系统后,选择连接CTP接口,输入用户名和密码等信息后,连接CTP网关后可以在VN Studio Prompt窗口见到如下:
gateway connect event GatewayData(name='CTP', type='TD', reason=0)
gateway connect event GatewayData(name='CTP', type='MD', reason=0)
当然你也可以制造一下CTP网关的故障,如故意拔掉你的路由器的电,或者网线,你应该可以看到
gateway disconnect event GatewayData(name='CTP', type='TD', reason=?)
gateway disconnect event GatewayData(name='CTP', type='MD', reason=?)
上面使用问号,是因为我不知道你软件会提示什么错误原因。
def load_algo_template(self):
""""""
from .algos.twap_algo import TwapAlgo
from .algos.iceberg_algo import IcebergAlgo
from .algos.sniper_algo import SniperAlgo
from .algos.stop_algo import StopAlgo
from .algos.best_limit_algo import BestLimitAlgo
from .algos.grid_algo import GridAlgo
from .algos.dma_algo import DmaAlgo
from .algos.arbitrage_algo import ArbitrageAlgo
from .algos.test_algo import TestAlgo # hxxjava add
self.add_algo_template(TwapAlgo)
self.add_algo_template(IcebergAlgo)
self.add_algo_template(SniperAlgo)
self.add_algo_template(StopAlgo)
self.add_algo_template(BestLimitAlgo)
self.add_algo_template(GridAlgo)
self.add_algo_template(DmaAlgo)
self.add_algo_template(ArbitrageAlgo)
self.add_algo_template(TestAlgo) # hxxjava add
from .genus import (
GenusVWAP,
GenusTWAP,
GenusPercent,
GenusPxInline,
GenusSniper,
GenusDMA
)
self.add_algo_template(GenusVWAP)
self.add_algo_template(GenusTWAP)
self.add_algo_template(GenusPercent)
self.add_algo_template(GenusPxInline)
self.add_algo_template(GenusSniper)
self.add_algo_template(GenusDMA)
考虑到这一特点,只能像上面带注释的行这么添加algo策略。
from vnpy.trader.constant import Offset, Direction
from vnpy.trader.object import TradeData, OrderData, TickData
from vnpy.trader.engine import BaseEngine
from vnpy.app.algo_trading import AlgoTemplate
class TestAlgo(AlgoTemplate):
""""""
display_name = "TestAlgo 测试算法"
default_setting = {
"vt_symbol": "",
"direction": [Direction.LONG.value, Direction.SHORT.value],
"price": 0.0,
"volume": 0.0,
"display_volume": 0.0,
"interval": 0,
"offset": [
Offset.NONE.value,
Offset.OPEN.value,
Offset.CLOSE.value,
Offset.CLOSETODAY.value,
Offset.CLOSEYESTERDAY.value
]
}
variables = [
"traded",
"timer_count",
"vt_orderid"
]
def __init__(
self,
algo_engine: BaseEngine,
algo_name: str,
setting: dict
):
""""""
super().__init__(algo_engine, algo_name, setting)
# Parameters
self.vt_symbol = setting["vt_symbol"]
self.direction = Direction(setting["direction"])
self.price = setting["price"]
self.volume = setting["volume"]
self.display_volume = setting["display_volume"]
self.interval = setting["interval"]
self.offset = Offset(setting["offset"])
# Variables
self.timer_count = 0
self.vt_orderid = ""
self.traded = 0
self.last_tick = None
self.subscribe(self.vt_symbol)
self.put_parameters_event()
self.put_variables_event()
def on_stop(self):
""""""
self.write_log("停止算法")
def on_tick(self, tick: TickData):
""""""
self.last_tick = tick
def on_order(self, order: OrderData):
""""""
msg = f"委托号:{order.vt_orderid},委托状态:{order.status.value}"
self.write_log(msg)
if not order.is_active():
self.vt_orderid = ""
self.put_variables_event()
def on_trade(self, trade: TradeData):
""""""
self.traded += trade.volume
if self.traded >= self.volume:
self.write_log(f"已交易数量:{self.traded},总数量:{self.volume}")
self.stop()
else:
self.put_variables_event()
def on_timer(self):
""""""
self.timer_count += 1
if self.timer_count < self.interval:
self.put_variables_event()
return
self.timer_count = 0
contract = self.get_contract(self.vt_symbol)
if not contract:
return
print(f"last_tick={self.last_tick}")
# # If order already finished, just send new order
# if not self.vt_orderid:
# order_volume = self.volume - self.traded
# order_volume = min(order_volume, self.display_volume)
# if self.direction == Direction.LONG:
# self.vt_orderid = self.buy(
# self.vt_symbol,
# self.price,
# order_volume,
# offset=self.offset
# )
# else:
# self.vt_orderid = self.sell(
# self.vt_symbol,
# self.price,
# order_volume,
# offset=self.offset
# )
# # Otherwise check for cancel
# else:
# if self.direction == Direction.LONG:
# if self.last_tick.ask_price_1 <= self.price:
# self.cancel_order(self.vt_orderid)
# self.vt_orderid = ""
# self.write_log(u"最新Tick卖一价,低于买入委托价格,之前委托可能丢失,强制撤单")
# else:
# if self.last_tick.bid_price_1 >= self.price:
# self.cancel_order(self.vt_orderid)
# self.vt_orderid = ""
# self.write_log(u"最新Tick买一价,高于卖出委托价格,之前委托可能丢失,强制撤单")
self.put_variables_event()
def get_next_holiday(self,dt:datetime):
"""
计算下一个节假日的起止日期
注意:dt必须为交易时间,否则返回为(0,(None,None))
"""
days,holday_start,holday_stop = 0,None,None
index,trade_times = self.get_trade_datetimes(dt)
if trade_times:
# dt在交易时间段内
day_count = len(self.trade_index_date)
for idx in range(index,day_count-1):
# 寻找间隔超过一天的前后两个交易日
date1 = self.trade_index_date[idx]
date2 = self.trade_index_date[idx+1]
date_diff = date2 - date1
if date_diff > datetime.timedelta(days=1):
# 找到了间隔超过一天的前后两个交易日
days = date_diff.days - 1
# 前1日加一天为节假日开始
holday_start = date1 + datetime.timedelta(days=1)
# 后1日减一天为节假日结束
holday_stop = date2 + datetime.timedelta(days=-1)
break
return days,(holday_start,holday_stop)
def to_holiday_time(self,dt:datetime):
"""
计算交易时间距离节假日的时间。
无夜盘合约 : 到节假日前一交易日的日盘收盘时间
有夜盘合约 : 到节假日后一交易日的夜盘收盘时间
注意:dt必须为交易时间,否则返回为None
返回值:
"""
index,trade_times = self.get_trade_datetimes(dt,True)
if trade_times:
# dt在交易时间段内
day_count = len(self.trade_index_date)
for idx in range(index,day_count-1):
# 寻找间隔超过一天的前后两个交易日
date1 = self.trade_index_date[idx]
date2 = self.trade_index_date[idx+1]
date_diff = date2 - date1
if date_diff > datetime.timedelta(days=1):
if not self.has_night_trading:
days,trade_times = self.get_date_tradetimes(date1)
stop_dt = trade_times[-1][1]
# print(f"{stop_dt} ")
else:
days,trade_times = self.get_date_tradetimes(date2)
dt1 = trade_times[0][0]
has,(start_dt,stop_dt) = self.get_night_start_stop(dt1)
# print(f"dt1={dt1},{(start_dt,stop_dt)} ")
# print(f"has_night_trading ={self.has_night_trading}, stop_dt={stop_dt}")
time_diff = stop_dt - dt
return time_diff
return None
保存文件:vnpy\usertools\trade_hour.py
"""
本文件主要实现合约的交易时间段
作者:hxxjava
日期:2020-8-1
"""
from typing import Callable,List,Dict, Tuple, Union
from enum import Enum
import datetime
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")
from vnpy.trader.utility import extract_vt_symbol
from vnpy.trader.constant import Interval
from rqdatac.utils import to_date
import rqdatac as rq
def get_listed_date(symbol:str):
'''
获得上市日期
'''
info = rq.instruments(symbol)
return to_date(info.listed_date)
def get_de_listed_date(symbol:str):
'''
获得交割日期
'''
info = rq.instruments(symbol)
return to_date(info.de_listed_date)
class Timeunit(Enum):
"""
时间单位
"""
SECOND = '1s'
MINUTE = '1m'
HOUR = '1h'
class TradeHours(object):
""" 合约交易时间段 """
def __init__(self,symbol:str):
self.symbol = symbol.upper()
self.init()
def init(self):
"""
初始化交易日字典及交易时间段数据列表
"""
self.listed_date = get_listed_date(self.symbol)
self.de_listed_date = get_de_listed_date(self.symbol)
self.trade_date_index = {} # 合约的交易日索引字典
self.trade_index_date = {} # 交易天数与交易日字典
trade_dates = rq.get_trading_dates(self.listed_date,self.de_listed_date) # 合约的所有的交易日
days = 0
for td in trade_dates:
self.trade_date_index[td] = days
self.trade_index_date[days] = td
days += 1
trading_hours = rq.get_trading_hours(self.symbol,date=self.listed_date,frequency='tick',expected_fmt='datetime')
self.has_night_trading = False
self.time_dn_pairs = self._get_trading_times_dn(trading_hours)
trading_hours0 = [(CHINA_TZ.localize(start),CHINA_TZ.localize(stop)) for start,stop in trading_hours]
self.trade_date_index[self.listed_date] = (0,trading_hours0)
for day in range(1,days):
td = self.trade_index_date[day]
trade_datetimes = []
for (start,dn1),(stop,dn2) in self.time_dn_pairs:
#start:开始时间,dn1:相对交易日前推天数,
#stop :开始时间,dn2:相对开始时间后推天数
d = self.trade_index_date[day+dn1]
start_dt = CHINA_TZ.localize(datetime.datetime.combine(d,start))
stop_dt = CHINA_TZ.localize(datetime.datetime.combine(d,stop))
trade_datetimes.append((start_dt,stop_dt+datetime.timedelta(days=dn2)))
self.trade_date_index[td] = (day,trade_datetimes)
def _get_trading_times_dn(self,trading_hours:List[Tuple[datetime.datetime,datetime.datetime]]):
"""
交易时间跨天处理,不推荐外部使用 。
产生的结果:[((start1,dn11),(stop1,dn21)),((start2,dn12),(stop2,dn22)),...,((startN,dn1N),(stopN,dn2N))]
其中:
startN:开始时间,dn1N:相对交易日前推天数,
stopN:开始时间,dn2N:相对开始时间后推天数
"""
ilen = len(trading_hours)
if ilen == 0:
return []
start_stops = []
for start,stop in trading_hours:
start_stops.insert(0,(start.time(),stop.time()))
pre_start,pre_stop = start_stops[0]
dn1 = 0
dn2 = 1 if pre_start > pre_stop else 0
time_dn_pairs = [((pre_start,dn1),(pre_stop,dn2))]
for start,stop in start_stops[1:]:
if start > pre_start:
self.has_night_trading = True
dn1 -= 1
dn2 = 1 if start > stop else 0
time_dn_pairs.insert(0,((start,dn1),(stop,dn2)))
pre_start,pre_stop = start,stop
return time_dn_pairs
def get_date_tradetimes(self,date:datetime.date):
"""
得到合约date日期的交易时间段
"""
idx,trade_times = self.trade_date_index.get(date,(None,[]))
return idx,trade_times
def get_trade_datetimes(self,dt:datetime,allday:bool=False):
"""
得到合约date日期的交易时间段
"""
# 得到最早的交易时间
idx0,trade_times0 = self.get_date_tradetimes(self.listed_date)
start0,stop0 = trade_times0[0]
if dt < start0:
return None,[]
# 首先找到dt日期自上市以来的交易天数
date,dn = dt.date(),0
days = None
while date < self.de_listed_date:
days,ths = self.trade_date_index.get(date,(None,[]))
if not days:
dn += 1
date = (dt+datetime.timedelta(days=dn)).date()
else:
break
# 如果超出交割日也没有找到,那这就不是一个有效的交易时间
if days is None:
return (None,[])
index_3 = [days,days+1,days-1] # 前后三天的
date_3d = []
for day in index_3:
date = self.trade_index_date.get(day,None)
date_3d.append(date)
# print(date_3d)
for date in date_3d:
if not date:
# print(f"{date} is not trade date")
continue
idx,trade_dts = self.get_date_tradetimes(date)
# print(f"{date} tradetimes {trade_dts}")
ilen = len(trade_dts)
if ilen > 0:
start0,stop = trade_dts[0] # start0 是date交易日的开始时间
start,stop0 = trade_dts[-1]
if dt<start0 or dt>stop0:
continue
for start,stop in trade_dts:
if dt>=start and dt < stop:
if allday:
return idx,trade_dts
else:
return idx,[(start,stop)]
return None,[]
def get_trade_time_perday(self):
"""
计算每日的交易总时长(单位:分钟)
"""
TTPD = datetime.timedelta(0,0,0)
datetimes = []
today = datetime.datetime.now().date()
for (start,dn1),(stop,dn2) in self.time_dn_pairs:
start_dt = CHINA_TZ.localize(datetime.datetime.combine(today,start)) + datetime.timedelta(days=dn1)
stop_dt = CHINA_TZ.localize(datetime.datetime.combine(today,stop)) + datetime.timedelta(days=dn2)
time_delta = stop_dt - start_dt
TTPD = TTPD + time_delta
return int(TTPD.seconds/60)
def get_trade_time_inday(self,dt:datetime,unit:Timeunit=Timeunit.MINUTE):
"""
计算dt在交易日内的分钟数
unit: '1s':second;'1m':minute;'1h';1h
"""
TTID = datetime.timedelta(0,0,0)
day,trade_times = self.get_trade_datetimes(dt,allday=True)
if not trade_times:
return None
for start,stop in trade_times:
if dt > stop:
time_delta = stop - start
TTID += time_delta
elif dt > start:
time_delta = dt - start
TTID += time_delta
break
else:
break
if unit == Timeunit.SECOND:
return TTID.seconds
elif unit == Timeunit.MINUTE:
return int(TTID.seconds/60)
elif unit == Timeunit.HOUR:
return int(TTID.seconds/3600)
else:
return TTID
def get_day_tradetimes(self,dt:datetime):
"""
得到合约日盘的交易时间段
"""
index,trade_times = self.get_trade_datetimes(dt,allday=True)
trade_times1 = []
if trade_times:
for start_dt,stop_dt in trade_times:
if start_dt.time() < datetime.time(18,0,0):
trade_times1.append((start_dt,stop_dt))
return index,trade_times1
return (index,trade_times1)
def get_night_tradetimes(self,dt:datetime):
"""
得到合约夜盘的交易时间段
"""
index,trade_times = self.get_trade_datetimes(dt,allday=True)
trade_times1 = []
if trade_times:
for start_dt,stop_dt in trade_times:
if start_dt.time() > datetime.time(18,0,0):
trade_times1.append((start_dt,stop_dt))
return index,trade_times1
return (index,trade_times1)
def convet_to_datetime(self,day:int,minutes:int):
"""
计算minutes在第day交易日内的datetime形式的时间
"""
date = self.trade_index_date.get(day,None)
if date is None:
return None
idx,trade_times = self.trade_date_index.get(date,(None,[]))
if not trade_times: # 不一定必要
return None
for (start,stop) in trade_times:
timedelta = stop - start
if minutes < int(timedelta.seconds/60):
return start + datetime.timedelta(minutes=minutes)
else:
minutes -= int(timedelta.seconds/60)
return None
def get_bar_window(self,dt:datetime,window:int,interval:Interval=Interval.MINUTE):
"""
计算dt所在K线的起止时间
"""
bar_windows = (None,None)
day,trade_times = self.get_trade_datetimes(dt,allday=True)
if not trade_times:
# print(f"day={day} trade_times={trade_times}")
return bar_windows
# 求每个交易日的交易时间分钟数
TTPD = self.get_trade_time_perday()
# 求dt在交易日内的分钟数
TTID = self.get_trade_time_inday(dt,unit=Timeunit.MINUTE)
# 得到dt时刻K线的起止时间
total_minites = day*TTPD + TTID
# 计算K线宽度(分钟数)
if interval == Interval.MINUTE:
bar_width = window
elif interval == Interval.HOUR:
bar_width = 60*window
elif interval == Interval.DAILY:
bar_width = TTPD*window
elif interval == Interval.WEEKLY:
bar_width = TTPD*window*5
else:
return bar_windows
# 求K线的开始时间的和结束的分钟形式
start_m = int(total_minites/bar_width)*bar_width
stop_m = start_m + bar_width
# 计算K开始时间的datetime形式
start_d = int(start_m / TTPD)
minites = start_m % TTPD
start_dt = self.convet_to_datetime(start_d,minites)
# print(f"start_d={start_d} minites={minites}---->{start_dt}")
# 计算K结束时间的datetime形式
stop_d = int(stop_m / TTPD)
minites = stop_m % TTPD
stop_dt = self.convet_to_datetime(stop_d,minites)
# print(f"stop_d={stop_d} minites={minites}---->{stop_dt}")
return start_dt,stop_dt
def get_date_start_stop(self,dt:datetime):
"""
获得dt所在交易日的开始和停止时间
"""
index,trade_times = self.get_trade_datetimes(dt,allday=True)
if trade_times:
valid_dt = False
for t1,t2 in trade_times:
if t1 < dt and dt < t2:
valid_dt = True
break
if valid_dt:
start_dt = trade_times[0][0]
stop_dt = trade_times[-1][1]
return True,(start_dt,stop_dt)
return False,(None,None)
def get_day_start_stop(self,dt:datetime):
"""
获得dt所在交易日日盘的开始和停止时间
"""
index,trade_times = self.get_day_tradetimes(dt)
if trade_times:
valid_dt = False
for t1,t2 in trade_times:
if t1 <= dt and dt < t2:
valid_dt = True
break
if valid_dt:
start_dt = trade_times[0][0]
stop_dt = trade_times[-1][1]
return True,(start_dt,stop_dt)
return False,(None,None)
def get_night_start_stop(self,dt:datetime):
"""
获得dt所在交易日夜盘的开始和停止时间
"""
index,trade_times = self.get_night_tradetimes(dt)
if trade_times:
valid_dt = False
for t1,t2 in trade_times:
if t1 <= dt and dt < t2:
valid_dt = True
break
if valid_dt:
start_dt = trade_times[0][0]
stop_dt = trade_times[-1][1]
return True,(start_dt,stop_dt)
return False,(None,None)
def get_next_holiday(self,dt:datetime):
"""
计算下一个节假日的起止日期
注意:dt必须为交易时间,否则返回为(0,(None,None))
"""
days,holday_start,holday_stop = 0,None,None
index,trade_times = self.get_trade_datetimes(dt)
if trade_times:
# dt在交易时间段内
day_count = len(self.trade_index_date)
for idx in range(index,day_count-1):
# 寻找间隔超过一天的前后两个交易日
date1 = self.trade_index_date[idx]
date2 = self.trade_index_date[idx+1]
date_diff = date2 - date1
if date_diff > datetime.timedelta(days=1):
# 找到了间隔超过一天的前后两个交易日
days = date_diff.days - 1
# 前1日加一天为节假日开始
holday_start = date1 + datetime.timedelta(days=1)
# 后1日减一天为节假日结束
holday_stop = date2 + datetime.timedelta(days=-1)
break
return days,(holday_start,holday_stop)
def to_holiday_time(self,dt:datetime):
"""
计算交易时间距离节假日的时间。
无夜盘合约 : 到节假日前一交易日的日盘收盘时间
有夜盘合约 : 到节假日后一交易日的夜盘收盘时间
注意:dt必须为交易时间,否则返回为None
返回值:
"""
index,trade_times = self.get_trade_datetimes(dt,True)
if trade_times:
# dt在交易时间段内
day_count = len(self.trade_index_date)
for idx in range(index,day_count-1):
# 寻找间隔超过一天的前后两个交易日
date1 = self.trade_index_date[idx]
date2 = self.trade_index_date[idx+1]
date_diff = date2 - date1
if date_diff > datetime.timedelta(days=1):
if not self.has_night_trading:
days,trade_times = self.get_date_tradetimes(date1)
stop_dt = trade_times[-1][1]
# print(f"{stop_dt} ")
else:
days,trade_times = self.get_date_tradetimes(date2)
dt1 = trade_times[0][0]
has,(start_dt,stop_dt) = self.get_night_start_stop(dt1)
# print(f"dt1={dt1},{(start_dt,stop_dt)} ")
# print(f"has_night_trading ={self.has_night_trading}, stop_dt={stop_dt}")
time_diff = stop_dt - dt
return time_diff
return None
if __name__ == "__main__":
def test1():
# vt_symbols = ["rb2010.SHFE","ag2012.SHFE","i2010.DCE"]
vt_symbols = ["ag2012.SHFE"]
date0 = datetime.date(2020,8,31)
dt0 = CHINA_TZ.localize(datetime.datetime(2020,8,31,9,20,15))
for vt_symbol in vt_symbols:
symbol,exchange = extract_vt_symbol(vt_symbol)
th = TradeHours(symbol)
# trade_hours = th.get_date_tradetimes(date0)
# print(f"\n{vt_symbol} {date0} trade_hours={trade_hours}")
days,trade_hours = th.get_trade_datetimes(dt0,allday=True)
print(f"\n{vt_symbol} {dt0} days:{days} trade_hours={trade_hours}")
if trade_hours:
day_start = trade_hours[0][0]
day_end = trade_hours[-1][1]
print(f"day_start={day_start} day_end={day_end}")
exit_time = day_end + datetime.timedelta(minutes=-5)
print(f"exit_time={exit_time}")
dt1 = CHINA_TZ.localize(datetime.datetime(2020,8,31,9,20,15))
dt2 = CHINA_TZ.localize(datetime.datetime(2020,9,1,1,1,15))
for dt in [dt1,dt2]:
in_trade,(start,stop) = th.get_date_start_stop(dt)
if (in_trade):
print(f"\n{vt_symbol} 时间 {dt} 交易日起止:{start,stop}")
else:
print(f"\n{vt_symbol} 时间 {dt} 非交易时间")
in_day,(start,stop) = th.get_day_start_stop(dt)
if (in_day):
print(f"\n{vt_symbol} 时间 {dt} 日盘起止:{start,stop}")
else:
print(f"\n{vt_symbol} 时间 {dt} 非日盘时间")
in_night,(start,stop) = th.get_night_start_stop(dt)
if in_night:
print(f"\n{vt_symbol} 时间 {dt} 夜盘起止:{start,stop}")
else:
print(f"\n{vt_symbol} 时间 {dt} 非夜盘时间")
def test2():
vt_symbols = ["ag2012.SHFE"]
date0 = datetime.date(2020,8,31)
dt0 = CHINA_TZ.localize(datetime.datetime(2019,12,10,9,20,15))
for vt_symbol in vt_symbols:
symbol,exchange = extract_vt_symbol(vt_symbol)
th = TradeHours(symbol)
for i in range(365):
td = th.listed_date + datetime.timedelta(days=i)
dt = CHINA_TZ.localize(
datetime.datetime.combine(td,datetime.time(9,20,15)))
days,(date1,date2) = th.get_next_holiday(dt)
print(f"dt={dt},days={days},holiday={date1,date2}")
def test3():
"""
TradeHour.to_holiday_time()验证
"""
vt_symbol = "ag2012.SHFE"
date0 = datetime.date(2020,8,29)
dt0 = CHINA_TZ.localize(datetime.datetime(2020,8,28,9,20,15))
symbol,exchange = extract_vt_symbol(vt_symbol)
th = TradeHours(symbol)
for i in range(1200):
dt = dt0 + datetime.timedelta(minutes=i)
time_diff = th.to_holiday_time(dt)
if time_diff:
print(f"dt={dt},time_diff,time_diff={time_diff}")
# 开始测试
rq.init('xxxxxx','******',("rqdatad-pro.ricequant.com",16011))
# test1()
# test2()
test3()
Traceback (most recent call last):
File "D:\ProgramFiles\VnStudio\lib\site-packages\vnstation\cli.py", line 90, in run_trader
module = importlib.import_module(d["module"])
File "D:\ProgramFiles\VnStudio\lib\importlib\__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
File "<frozen importlib._bootstrap>", line 983, in _find_and_load
File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 728, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "D:\ProgramFiles\VnStudio\lib\site-packages\vnpy\app\algo_trading\__init__.py", line 5, in <module>
from .engine import AlgoEngine, APP_NAME
File "D:\ProgramFiles\VnStudio\lib\site-packages\vnpy\app\algo_trading\engine.py", line 17, in <module>
from .genus import GenusClient
File "D:\ProgramFiles\VnStudio\lib\site-packages\vnpy\app\algo_trading\genus.py", line 6, in <module>
import quickfix as fix
ModuleNotFoundError: No module named 'quickfix'
从错误提示看是缺少了quickfix模块,那就安装
于是进入VN Staudio Prompt窗口下输入:
pip install quickfix
那就先安装Ms Visual C++ V14.0 build tools,
搜索到Ms Visual C++ V14.0 build tools,
也装上了,
提示重新启动,
那就重新启动
命令是:
pip install quickfix
D:\ProgramFiles\VnStudio>pip install quickfix
Collecting quickfix
Using cached quickfix-1.15.1.tar.gz (1.5 MB)
Using legacy 'setup.py install' for quickfix, since package 'wheel' is not installed.
Installing collected packages: quickfix
Running setup.py install for quickfix ... error
ERROR: Command errored out with exit status 1:
command: 'd:\programfiles\vnstudio\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\hxxja\\AppData\\Local\\Temp\\pip-install-1sa6_klk\\quickfix\\setup.py'"'"'; __file__='"'"'C:\\Users\\hxxja\\AppData\\Local\\Temp\\pip-install-1sa6_klk\\quickfix\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\hxxja\AppData\Local\Temp\pip-record-qavjungc\install-record.txt' --single-version-externally-managed --compile --install-headers 'd:\programfiles\vnstudio\Include\quickfix'
cwd: C:\Users\hxxja\AppData\Local\Temp\pip-install-1sa6_klk\quickfix\
Complete output (38 lines):
running install
running build
running build_py
creating build
creating build\lib.win-amd64-3.7
copying quickfix.py -> build\lib.win-amd64-3.7
copying quickfixt11.py -> build\lib.win-amd64-3.7
copying quickfix40.py -> build\lib.win-amd64-3.7
copying quickfix41.py -> build\lib.win-amd64-3.7
copying quickfix42.py -> build\lib.win-amd64-3.7
copying quickfix43.py -> build\lib.win-amd64-3.7
copying quickfix44.py -> build\lib.win-amd64-3.7
copying quickfix50.py -> build\lib.win-amd64-3.7
copying quickfix50sp1.py -> build\lib.win-amd64-3.7
copying quickfix50sp2.py -> build\lib.win-amd64-3.7
running build_ext
Testing for std::tr1::shared_ptr...
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -DPYTHON_MAJOR_VERSION=3 -IC++ -Id:\programfiles\vnstudio\include -Id:\programfiles\vnstudio\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /EHsc /Tptest_std_tr1_shared_ptr.cpp /Fotest_std_tr1_shared_ptr.obj
test_std_tr1_shared_ptr.cpp
test_std_tr1_shared_ptr.cpp(1): fatal error C1083: Cannot open include file: 'tr1/memory': No such file or directory
...not found
Testing for std::shared_ptr...
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe -std=c++0x /c /nologo /Ox /W3 /GL /DNDEBUG /MD -DPYTHON_MAJOR_VERSION=3 -IC++ -Id:\programfiles\vnstudio\include -Id:\programfiles\vnstudio\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /EHsc /Tptest_std_shared_ptr.cpp /Fotest_std_shared_ptr.obj
cl : Command line warning D9002 : ignoring unknown option '-std=c++0x'
test_std_shared_ptr.cpp
...found
Testing for std::unique_ptr...
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe -std=c++0x /c /nologo /Ox /W3 /GL /DNDEBUG /MD -DPYTHON_MAJOR_VERSION=3 -DHAVE_STD_SHARED_PTR -IC++ -Id:\programfiles\vnstudio\include -Id:\programfiles\vnstudio\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /EHsc /Tptest_std_unique_ptr.cpp /Fotest_std_unique_ptr.obj
cl : Command line warning D9002 : ignoring unknown option '-std=c++0x'
test_std_unique_ptr.cpp
...found
building '_quickfix' extension
creating build\temp.win-amd64-3.7
creating build\temp.win-amd64-3.7\Release
creating build\temp.win-amd64-3.7\Release\C++
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -DPYTHON_MAJOR_VERSION=3 -DHAVE_STD_SHARED_PTR -DHAVE_STD_UNIQUE_PTR -IC++ -Id:\programfiles\vnstudio\include -Id:\programfiles\vnstudio\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /EHsc /TpC++\Acceptor.cpp /Fobuild\temp.win-amd64-3.7\Release\C++\Acceptor.obj -std=c++0x -Wno-deprecated -Wno-unused-variable -Wno-deprecated-declarations -Wno-maybe-uninitialized
cl : Command line error D8021 : invalid numeric argument '/Wno-deprecated'
error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BIN\\x86_amd64\\cl.exe' failed with exit status 2
----------------------------------------
ERROR: Command errored out with exit status 1: 'd:\programfiles\vnstudio\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\hxxja\\AppData\\Local\\Temp\\pip-install-1sa6_klk\\quickfix\\setup.py'"'"'; __file__='"'"'C:\\Users\\hxxja\\AppData\\Local\\Temp\\pip-install-1sa6_klk\\quickfix\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\hxxja\AppData\Local\Temp\pip-record-qavjungc\install-record.txt' --single-version-externally-managed --compile --install-headers 'd:\programfiles\vnstudio\Include\quickfix' Check the logs for full command output.
在cta_gateway.py中send_order()函数中,找到出错语句前增加下面的调试打印:
self.reqid += 1
print(f"ctp_req={ctp_req}") # 调试打印
self.reqOrderInsert(ctp_req, self.reqid)
得到ctp_req的内容如下:
ctp_req={
'InstrumentID': 'ag2012',
'ExchangeID': 'SHFE',
'LimitPrice': 6093.0,
'VolumeTotalOriginal': 8,
'OrderPriceType': '',
'Direction': '1',
'CombOffsetFlag': '3',
'OrderRef': '1',
'InvestorID': '147102',
'UserID': '147102',
'BrokerID': '9999',
'CombHedgeFlag': '1',
'ContingentCondition': '1',
'ForceCloseReason': '0',
'IsAutoSuspend': 0,
'TimeCondition': '3',
'VolumeCondition': '1',
'MinVolume': 1
}
这里面的内容只有最为可疑:
'OrderPriceType': '',
ORDERTYPE_VT2CTP = {
OrderType.LIMIT: THOST_FTDC_OPT_LimitPrice, # 限价类型
OrderType.MARKET: THOST_FTDC_OPT_AnyPrice # 市价类型
}
ORDERTYPE_CTP2VT = {v: k for k, v in ORDERTYPE_VT2CTP.items()}
用户的CTA策略发送委托停止单的过程:
目前在主界面中是不可以发送任何停止单的!!!
目前在主界面中是不可以发送任何停止单的!!!
目前在主界面中是不可以发送任何停止单的!!!
重要的话说三遍
已经分析过了,那么是否可以改进呢?
交易日内等自然时长K线生成器,可以实现周以下的周期单位的时间就间隔:
x=window,interval的取:
Interval.MINUTE :x分钟宽度的K线,对齐交易日开始时间,不可以超过日交易时长
Interval.HOUR : x小时宽度的K线,对齐交易日开始时间,不可以超过日交易时长
Interval.DAILY : x日宽度的K线,对齐上市交易日开始时间开始的整x日,x可以任意值
Interval.WEEKLY : x周宽度的K线,对齐上市交易日开始时间整x周,x可以任意值
再策略中的应用举例:
保存文件:vnpy\usertools\equal_nature_time.py
内容如下:
"""
实现一个等自然时长机制的K线生成器
作者:hxxjava
日期:2020-9-2
"""
from typing import Callable,List,Dict, Tuple, Union
from vnpy.app.cta_strategy import BarGenerator
from vnpy.trader.constant import Interval
from vnpy.trader.utility import extract_vt_symbol
from vnpy.trader.object import BarData
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")
from vnpy.usertools.trade_hour import (
TradeHours,
Timeunit,
get_listed_date,
get_de_listed_date,
)
import datetime
import rqdatac as rq
class MyError(Exception):
def __init__(self,ErrorInfo):
super().__init__(self) #初始化父类
self.errorinfo=ErrorInfo
def __str__(self):
return self.errorinfo
class ENTBarGenerator(BarGenerator):
"""
交易日内等自然时长K线生成器,可以实现周以下的周期单位的时间就间隔:
x=window,interval的取:
Interval.MINUTE :x分钟宽度的K线,对齐交易日开始时间,不可以超过日交易时长
Interval.HOUR : x小时宽度的K线,对齐交易日开始时间,不可以超过日交易时长
Interval.DAILY : x日宽度的K线,对齐上市交易日开始时间开始的整x日,x可以任意值
Interval.WEEKLY : x周宽度的K线,对齐上市交易日开始时间整x周,x可以任意值
ENT:equal nature time ———— 等自然时长的缩写
"""
def __init__(
self,
on_bar: Callable,
window: int = 0,
on_window_bar: Callable = None,
interval: Interval = Interval.MINUTE,
vt_symbol:str=''
):
super().__init__(on_bar,window,on_window_bar,interval)
symbol,exchange = extract_vt_symbol(vt_symbol)
self.trade_hours = TradeHours(symbol.upper())
self.trade_start,self.trade_stop = None,None
self.bar_width = self.get_bar_witdh()
self.bar_index = None
if self.interval in [Interval.DAILY,Interval.WEEKLY]:
self.kx_start,self.kx_stop = (None,None)
def get_bar_witdh(self):
"""
求window_bar的宽度
"""
# 求每日的交易时长
TTPD = self.trade_hours.get_trade_time_perday()
try:
if self.interval == Interval.MINUTE:
# 以分钟作为单位
bar_width = self.window
if bar_width>TTPD:
raise MyError(f'window_bar宽度不可以大于1日的交易总时长')
elif self.interval == Interval.HOUR:
# 以小时作为单位
bar_width = self.window*60
if bar_width>TTPD:
raise MyError(f'window_bar宽度不可以大于1日的交易总时长')
elif self.interval == Interval.DAILY:
# 以日作为单位
bar_width = self.window * TTPD
elif self.interval == Interval.WEEKLY:
# 以周作为单位
bar_width = self.window * TTPD * 5
else:
raise MyError(f'不可以使用{self.interval}作为interval参数')
return bar_width
except MyError as e:
print(e)
def get_bar_index(self,bar:BarData):
"""
计算当前分钟bar所属window_bar的日内索引
"""
time_diff = bar.datetime - self.trade_start
days = time_diff.days
sec = time_diff.seconds
us = time_diff.microseconds
minutes = (days*24*3600 +sec+us*0.000001)/60.0
index = int(minutes/self.bar_width)
return index
def update_bar(self,bar:BarData) -> None:
if self.interval in [Interval.MINUTE,Interval.HOUR]:
self.update_bar1(bar)
elif self.interval in [Interval.DAILY,Interval.WEEKLY]:
self.update_bar2(bar)
def update_bar1(self,bar:BarData) -> None:
"""
Update 1 minute bar into generator
"""
# 如果bar的时间戳笔windows的时间戳还早,丢弃bar
if self.window_bar and bar.datetime < self.window_bar.datetime:
return
in_trade,(trade_start,trade_stop) = self.trade_hours.get_date_start_stop(bar.datetime)
if not in_trade:
# 无效K线,不可以处理
# print(f"bar.datetime= {bar.datetime} 无效K线,不可以处理")
return
if (self.trade_start,self.trade_stop) == (None,None):
# 计算当日的开始和停止交易时间
self.trade_start,self.trade_stop = trade_start,trade_stop
elif trade_start != self.trade_start:
# 判断是否跨日,更新当日的开始和停止交易时间
self.trade_start,self.trade_stop = trade_start,trade_stop
# print(f"!1 {self.trade_start,self.trade_stop}")
# 计算当前window_bar的日内索引
bar_index = self.get_bar_index(bar)
if self.bar_index is None or bar_index != self.bar_index:
# 产生新window_barK线
self.bar_index = bar_index
if self.window_bar:
# 推送已经走完的window_bar
self.on_window_bar(self.window_bar)
self.window_bar = None
# 计算K线的交易起止时间
bar_datetime = self.trade_start + datetime.timedelta(minutes=self.bar_index*self.bar_width)
# 生成新的window_bar
self.window_bar = BarData(
symbol=bar.symbol,
exchange=bar.exchange,
datetime= bar_datetime,
gateway_name=bar.gateway_name,
open_price=bar.open_price,
high_price=bar.high_price,
low_price=bar.low_price,
)
# 更新window_bar的high和low
self.window_bar.high_price = max(
self.window_bar.high_price, bar.high_price)
self.window_bar.low_price = min(
self.window_bar.low_price, bar.low_price)
# 更新 close price/volume到window bar
self.window_bar.close_price = bar.close_price
self.window_bar.volume += int(bar.volume)
self.window_bar.open_interest = bar.open_interest
def update_bar2(self,bar:BarData) -> None:
"""
Update 1 minute bar into generator
"""
# 如果bar的时间戳笔windows的时间戳还早,丢弃bar
if self.window_bar and bar.datetime < self.window_bar.datetime:
return
if (self.kx_start,self.kx_stop) == (None,None):
self.kx_start,self.kx_stop = self.trade_hours.get_bar_window(bar.datetime,self.window,self.interval)
if (self.kx_start,self.kx_stop) == (None,None):
return
# If not inited, creaate window bar object
if (not self.window_bar):
# 获得K线的交易起止时间
self.window_bar = BarData(
symbol=bar.symbol,
exchange=bar.exchange,
datetime=self.kx_start,
gateway_name=bar.gateway_name,
open_price=bar.open_price,
high_price=bar.high_price,
low_price=bar.low_price,
)
elif self.kx_start <= bar.datetime and bar.datetime < self.kx_stop:
# 1分钟K线属于当前K线
self.window_bar.high_price = max(
self.window_bar.high_price, bar.high_price)
self.window_bar.low_price = min(
self.window_bar.low_price, bar.low_price)
elif bar.datetime >= self.kx_stop: # Check if window bar completed
self.on_window_bar(self.window_bar)
self.window_bar = None
self.kx_start,self.kx_stop = self.trade_hours.get_bar_window(bar.datetime,self.window,self.interval)
if (self.kx_start,self.kx_stop) == (None,None):
# 不在交易时段
return
self.window_bar = BarData(
symbol=bar.symbol,
exchange=bar.exchange,
datetime=self.kx_start,
gateway_name=bar.gateway_name,
open_price=bar.open_price,
high_price=bar.high_price,
low_price=bar.low_price,
)
# Update close price/volume into window bar
self.window_bar.close_price = bar.close_price
self.window_bar.volume += int(bar.volume)
self.window_bar.open_interest = bar.open_interest
# 下面是ENTBarGenerator类的测试代码
def test():
from vnpy.trader.constant import Exchange
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")
def on_bar(bar: BarData):
pass
def on_xbar_bar(bar: BarData):
print(bar)
bar_gen = ENTBarGenerator(on_bar,30,on_xbar_bar,Interval.MINUTE,vt_symbol='rb2010.SHFE')
dt0 = CHINA_TZ.localize(datetime.datetime(2020,8,31,9,20,1))
for i in range(60*24):
# print(f"{[dt0 + datetime.timedelta(minutes=i)]}")
bar = BarData(
gateway_name = 'CTP',
symbol = 'rb2010',
exchange = Exchange.SHFE,
datetime = dt0 + datetime.timedelta(minutes=i),
interval=Interval.MINUTE,
volume=2,
open_price=3560.0,
high_price= 3565.0,
close_price=3558.0,
low_price=3555.0,
open_interest=10,
)
bar_gen.update_bar(bar)
if __name__ == "__main__":
# RQDatac初始化
rq.init('xxxxxx','******',("rqdatad-pro.ricequant.com",16011))
test()
1)self.bg = BarGenerator(slef.on_bar,7,self.on_day_bar,interval=Interval.Hour)生成日线对部分期货合约适用,如RB,AP,I之类的,对另外的却是错误的,因为:
ag(白银)11根:
21:00~22:00 ,22:00~23:00 ,23:00~00:00,00:00~01:00,01:00~02:00, 02:00~02:30,9:00~10:00 , 10:00~11:00, 11:00~11:30, 13:30~14:00 , 14:00~15:00
IF(股指)5根:
9:30~10:00 , 10:00~11:00, 11:00~11:30, 13:30~14:00 , 14:00~15:00
你是如何确定你生成K线open,high,low,close和实际值不一致?和谁比较?我猜你是用第三方软件来比较的吧!
那你知道你使用的第三方软件它生成的日K线规则是什么?大智慧、通达信、博弈、文华财经?它们本身的日K线的产生规则都不完全相同,有的是区分日盘和夜盘的,有的却不区分,有的是等自然时长,有的等交易时长。
不同的产生机制,产生不同K线,不可以说谁是对的,谁是错误的。
1)大智慧、通达信、博弈、文华财经和米筐都是从交易所采集数据(tick),然后自己合成各自K线。
2)它们可能使用了交易所的不同的数据采集通道(这些通道都是真实有效的)。
3)这些不同的数据采集通道,交易所保证真实性,不保证一致性,也就是说可能是不一样的
4)你可以去比较大智慧、通达信、文华财经,选择同一个合约的同一个周期,比较一下看看,都可能是不一样的
因为测不准原理,世界本来就是没有绝对的准确,只有相对准确。你永远无法知道1米的绝对长度是多少!你不可以说哪个数据采集是正确的,哪个是错误的。
准备好这些,如果你还是发现K线不一样,你可以怀疑的元素:
策略账户有点类似文华赢智和库安软件中的模组账户的概念,但它根植vnpy的系统架构,完全用python写成。
本人已经提供长时间的构思和设计,经过一段时间的调试和运行,目前策略账户已经可以正常运行。现在展示如下: