发布于vn.py社区公众号【vnpy-community】

 
《30天解锁Python量化开发》课程已经更新过半(至25集),计划在9月底全部更新完毕,通过概念讲解和实践操作结合的方式,加上vn.py框架内部代码细节的梳理学习,来帮助你快速掌握Python量化开发能力,详情请戳
 

编者荐语:
vn.py项目的定位始终是开源量化交易【框架】,在掌握基础的使用后(可能满足了你95%的需求),更进一步还可以针对特定功能进行扩展开发,从而实现100%的需求覆盖,变成真正【属于你自己】的量化平台!

 

原文作者:高庆峰 | 发布时间:2020-10-15

 
天下武功唯快不破


description
CTA策略是一类追涨杀跌,以低胜率换高赔率的策略,下单到成交的低延时很重要。行情像一个漂亮女孩,感觉到位了就要尽快下手,错过便是一生,只能在脑海里回味那惊鸿一瞥多么令人惋惜;但你行动了之后也可能发现彼此并不合适,不是甜甜的恋爱而是无尽的痛苦。

怎么办?当然是快速止损!竹杖芒鞋轻胜马,一蓑烟雨任平生,有绝对的掌控力才能游刃有余,哈哈,我指的是交易。所以,提高下单速度,降低交易滑点是精细化优化策略非常重要的一部分。

此篇先聊聊如何提高下单速度,天下武功为快不破,在CTA策略中如何能做到下单快人一步呢?需要tick级别的bar内开仓!通道类CTA策略用停止单,是个不错的选择,通道上轨作为做多的触发价格,通道下轨作为做空的触发价格,当前一根k线结束下一根k线开始前预埋好停止单,等待被触发,当tick最新成交价向上突破做多触发价格马上追多,向下突破做空触发价格马上追空,而不是等待K线结束才追。

vn.py默认支持本地停止单,通过订阅Websocket推送的tick行情,在本地判断是否达到触发价格,如果达到触发价格迅速向交易所发送高于买1的买单或低于卖1的卖单,迅速成交。

但本地停止单在数字货币市场的缺点很明显:

  • 首先数字货币交易所的服务器一般部署在海外的云服务器上,如果运行策略的服务器和交易所的服务器不在同一区域,网络通信稳定性非常差,网络抖动引起的短时间内websocket频繁异常断开重连问题会导致本地停止单被异常触发。即使我使用了和某交易所同区的服务器,依然会出现这种情况,只是不常出现,但是一旦出现也是致命的;
  • 其次当价格超过触发价格订单从策略服务器被发向交易所服务器是有延迟的,这个延迟可能会加大滑点,甚至可能因为行情变化迅速发出的订单不能成交而错过行情。

问题清楚了,那么上解决方案!很简单,把停止单预埋到交易所,这样上述的两个问题全部都能迎刃而解。vn.py的引擎层是支持服务端停止单的挂撤的,只是目前版本的Bybit接口不支持,那么着手升级接口即可,以下是需要升级的代码。

如果使用VN Studio作为运行环境(默认安装),那么Bybit接口的代码文件位于:

C:\vnstudio\Lib\site-packages\vnpy\gateway\bybit\

打开该目录下的bybit_gateway.py后,按照步骤一步步来修改:

  1. 增加停止单的订单状态映射:
STATUS_BYBIT2VT: Dict[str, Status] = {    
    "Created": Status.NOTTRADED,    
    "New": Status.NOTTRADED,    
    "PartiallyFilled": Status.PARTTRADED,    
    "Filled": Status.ALLTRADED,    
    "Cancelled": Status.CANCELLED,    
    "Rejected": Status.REJECTED,    
    "Active": Status.ALLTRADED,    
    "Untriggered": Status.NOTTRADED,    
    "Triggered":  Status.PARTTRADED,    
    "Deactivated": Status.CANCELLED,
}
  1. 增加停止单的委托类型映射:
ORDER_TYPE_VT2BYBIT: Dict[OrderType, str] = {    
    OrderType.LIMIT: "Limit",    
    OrderType.MARKET: "Market",    
    OrderType.STOP: "Stop"
}
  1. 在BybitRestApi类下,增加一个成员字典self.orders缓
class BybitRestApi(RestClient):    
    """    
    ByBit REST API    
    """
    def __init__(self, gateway: BybitGateway):        
        """"""        
        super().__init__()

        self.gateway: BybitGateway = gateway        
        self.gateway_name: str = gateway.gateway_name

        self.usdt_base: bool = False        
        self.key: str = ""        
        self.secret: bytes = b""

        self.order_count: int = 0        
        self.contract_codes: set = set()        
        self.orders: dict = {}
  1. 重中之重来啦,send_order函数中修改的地方比较多,首先需要添加停止单委托的下单链接,其次在发出委托后将请求参数保存到self.orders字典中缓存:
    def send_order(self, req: OrderRequest) -> str:        
        """"""        
        order_data: dict = {}        
        orderid = self.new_orderid()        
        order = req.create_order_data(orderid, self.gateway_name)

        if req.type == OrderType.STOP:            
            data = {       
                "side": DIRECTION_VT2BYBIT[req.direction],     
                "symbol": req.symbol,                
                "order_type": "Market",                
                "qty": int(req.volume),                
                "base_price": self.gateway.last_price,                
                "stop_px": req.price,                
                "time_in_force": "GoodTillCancel", 
                "close_on_trigger": False,                
                "order_link_id": orderid,            
            }

            if self.usdt_base: 
                path = "/private/linear/stop-order/create"          
            else:               
                path = "/open-api/stop-order/create"        
        else:           
            data = {             
                "symbol": req.symbol,        
                "side": DIRECTION_VT2BYBIT[req.direction],    
                "qty": int(req.volume),             
                "order_link_id": orderid,           
                "time_in_force": "GoodTillCancel",   
                "reduce_only": False,         
                "close_on_trigger": False     
            }

            data["order_type"] = ORDER_TYPE_VT2BYBIT[req.type]     
            data["price"] = req.price

            if self.usdt_base:    
                path = "/private/linear/order/create"      
            else:           
                path = "/v2/private/order/create"

        self.add_request(       
            "POST",    
            path,   
            callback=self.on_send_order,        
            data=data,          
            extra=order,          
            on_failed=self.on_send_order_failed,   
            on_error=self.on_send_order_error,      
        )       
        data["req_order_type"] = req.type  
        self.orders[orderid] = data        
        self.gateway.on_order(order)     
        return order.vt_orderid
  1. 停止单委托的参数中,有一个必传字段为base_price,需要获取tick级的市场最新成交价格,此数据可以通过BybitPublicWebsocketApi类下的on_tick回调函数来获取:
    def on_tick(self, packet: dict) -> None: 
        """"""       
        ......        
        ......       
        self.gateway.last_price = tick.last_price
  1. 增加撤单链接,通过获取self.orders字典中缓存的委托参数判断是否需要撤销的订单是不是停止单:
    def cancel_order(self, req: CancelRequest) -> Request:  
        """"""       
        data = {        
            "symbol": req.symbol,
            "order_link_id": req.orderid   
        }       
        order = self.orders[req.orderid]    
        if order["req_order_type"] == OrderType.STOP:    
            if self.usdt_base:           
                path = "/private/linear/stop-order/cancel"    
            else:            
                path = "/open-api/stop-order/cancel"   
        else:          
            if self.usdt_base:          
                path = "/private/linear/order/cancel"     
            else:            
                path = "/v2/private/order/cancel"       

        self.gateway.write_log(path)    
        self.add_request(   
            "POST",      
            path,         
            data=data,    
            callback=self.on_cancel_order   
        )
  1. 在on_query_contract函数中的ContractData 数据容器里面增加一个字段stop_supported=True,以便引擎层判断此交易所是否支持停止单,如果是,则发送交易所停止单:
    def on_query_contract(self, data: dict, request: Request) -> None:      
        """"""    
        if self.check_error("查询合约", data):   
            return
        for d in data["result"]:       
            self.contract_codes.add(d["name"])

            contract = ContractData(       
                symbol=d["name"],            
                exchange=Exchange.BYBIT,    
                name=d["name"],           
                product=Product.FUTURES,    
                size=1,                
                pricetick=float(d["price_filter"]["tick_size"]),     
                min_volume=d["lot_size_filter"]["min_trading_qty"],   
                net_position=True,          
                history_data=True,            
                stop_supported=True,             
                gateway_name=self.gateway_name 
            )

            if self.usdt_base and "USDT" in contract.symbol:      
                self.gateway.on_contract(contract)   
            elif not self.usdt_base and "USDT" not in contract.symbol:                           self.gateway.on_contract(contract)

  1. 最后,VN Trader的行情界面的订单状态需要主动请求获取,但是请求频率不能过高,否则会造成线程阻塞,所以需要加上限速。首先在connect函数下增加self.timer_count = 0 ,然后在process_timer_event 函数下增加计数判断并查询订单:
class BybitGateway(BaseGateway): 
    """    
    VN Trader Gateway for ByBit connection.    
    """    
    .......    
    .......

    def connect(self, setting: dict) -> None:   
        """"""        
        ......        
        ......        
        self.timer_count = 0      
        self.event_engine.register(EVENT_TIMER, self.process_timer_event)
    ......    
    ......

    def process_timer_event(self, event):  
        """"""    
        self.query_position()    
        self.timer_count += 1    
        if self.timer_count < 5:         
            return       
        self.timer_count = 0   
        self.rest_api.query_order()

至此,Bybit交易所停止单已经完成,目前已经实盘,未发现问题,如果升级过程中遇到问题,欢迎加我交流,微信号:515119049。

 

写在最后:发现微信公众号的转载功能真心方便,后面我们争取保持每个月2-3篇的社区精选文章,让大家不只可以看到vn.py官方团队的文章,也能看到更多vn.py社区用户的分享内容,欢迎投稿~~