community selection 21 - Tick level entrusted fine management - Zhihu

Posted by verbalkint81 on Sun, 26 Apr 2020 12:35:31 +0200

Published in the community official account [vnpy-community]

Original author: the first quarter of the moon 𞓜 release time: March 20, 2020

With the increasing popularity of quantitative trading in the domestic financial market, the competition between CTA strategies has become more and more fierce. In addition to the core transaction signals of the strategy, it is also reflected in the actual execution of the strategy.

In most of the official CTA policy samples provided by, two steps are used in the K-line callback function on bar:

  1. Call the cancel all function to cancel all the previous K-line's hung delegates
  2. Check the core transaction signal and send out a new round of commission order

This simple and crude writing method is more to simplify the state machine control in the implementation of the strategy, and help the beginners of to reduce the learning difficulty. However, due to more repetitive operations, the running effect in the real disk may not be the best.

Fortunately, in the strategy template CtaTemplate, the Commission function (buy/sell/short/cover) can directly return the Commission number information, and the on ﹐ order / on ﹐ trade callback function will push the Commission and transaction status changes. Combined with a small amount of bottom-level code transformation, we can achieve more refined Tick level commission and cancellation management, making the core transaction signal of the strategy and the Commission execution algorithm more organic Together.

Extend OrderData object

Find the path where the source code of is located. If VN Studio is used, it should be located in C: \ vnstudio \ lib \ site packages \ vnpy. Go to the directory vnpy\trader and find the file

First of all, you need to extend the OrderData class, which is mainly manifested in:

  1. In addition to the time attribute, the date and cancel time attributes are added to class initialization
  2. In the ﹣ post ﹣ init ﹣ function, the outstanding volume untraded and the specific date time of the delegation datetime are added.

It should be noted that because the time format pushed by API interface of each quotation is different, it is based on:

  • "date format is"% Y-%m-%d "or"% Y%m%d ";
  • time has microsecond level data;

There are four different ways to get datetime.

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 = ""    
    offset: Offset = Offset.NONE    
    price: float = 0    
    volume: float = 0    
    traded: float = 0    
    status: Status = Status.SUBMITTING

    date: str = ""    
    time: str = ""    
    cancel_time: str = ""

    def __post_init__(self):   
        self.vt_symbol = f"{self.symbol}.{}"     
        self.vt_orderid = f"{self.gateway_name}.{self.orderid}"

        self.untraded = self.volume - self.traded

        # With millisecond    
        if and "." in self.time:   
            if "-"in          
                self.datetime = datetime.strptime(" ".join([, self.time]), 
                "%Y-%m-%d %H:%M:%S.%f")    
                self.datetime = datetime.strptime(" ".join([, self.time]),
                "%Y%m%d %H:%M:%S.%f")   
        # Without millisecond        
            if "-" in       
                self.datetime = datetime.strptime(" ".join([, self.time]),
                "%Y-%m-%d %H:%M:%S")        
                self.datetime = datetime.strptime(" ".join([, self.time]),
                "%Y%m%d %H:%M:%S")

    def is_active(self): 
        Check if the order is active.      
        if self.status in ACTIVE_STATUSES:        
            return True       
            return False

    def create_cancel_request(self):    
        Create cancel request object from order.  
        req = CancelRequest(            
            orderid=self.orderid, symbol=self.symbol, 
        return req

Extended position details query

Modify PositionHolding object

Enter the directory vnpy\trader to open the file. This time, we modified the PositionHolding class. The main changes are as follows:

  1. __Initialization of the init ﹣ class, adding the four attributes of long ﹣ PNL, long ﹣ price, short ﹣ PNL, and short ﹣ price. That is, increase the profit and loss of position and the average opening price;
  2. The update position function adds the profit and loss of cache position and the average opening price;
  3. The update order function first modifies the definition of the activity delegation and removes the delegation in submission. That is to say, in the update order function, only the outstanding or partially closed delegates are processed.
class PositionHolding:    

    def __init__(self, contract: ContractData):   
        self.vt_symbol = contract.vt_symbol   =

        self.active_orders = {}          

        self.long_pos = 0    
        self.long_pnl = 0     
        self.long_price = 0   
        self.long_yd = 0      
        self.long_td = 0

        self.short_pos = 0        
        self.short_pnl = 0        
        self.short_price = 0      
        self.short_yd = 0       
        self.short_td = 0

        self.long_pos_frozen = 0        
        self.long_yd_frozen = 0      
        self.long_td_frozen = 0

        self.short_pos_frozen = 0    
        self.short_yd_frozen = 0    
        self.short_td_frozen = 0

    def update_position(self, position: PositionData):      
        if position.direction == Direction.LONG:  
            self.long_pos = position.volume   
            self.long_pnl = position.pnl      
            self.long_price = position.price    
            self.long_yd = position.yd_volume   
            self.long_td = self.long_pos - self.long_yd     
            self.long_pos_frozen = position.frozen   
            self.short_pos = position.volume        
            self.short_pnl = position.pnl           
            self.short_price = position.price         
            self.short_yd = position.yd_volume         
            self.short_td = self.short_pos - self.short_yd                
            self.short_pos_frozen = position.frozen

    def update_order(self, order: OrderData):    
        #Active orders only records the outstanding and partial orders   
        if order.status in [Status.NOTTRADED, Status.PARTTRADED]:   
            self.active_orders[order.vt_orderid] = order     
            if order.vt_orderid in self.active_orders:        


New get position detail function

Enter the directory vnpy \ app \ CTA ﹣ strategy and open the file This time we will add a new function get ﹣ position ﹣ detail. The function is to obtain our modified PositionHolding object, so as to know more detailed position information, such as opening average price, position profit and loss, etc

from collections import defaultdict,OrderedDict

    def get_position_detail(self, vt_symbol):    
        //Query long ﹣ POS, short ﹣ POS, long ﹣ PNL, short ﹣ PNL, active ﹣ order      
        //Received PositionHolding class data     
            return self.offset_converter.get_position_holding(vt_symbol)     
            self.write_log(f"The current position information obtained is:
            {self.offset_converter.get_position_holding(vt_symbol)},Waiting for position information") 
            position_detail = OrderedDict()            
            position_detail.active_orders = {}            
            position_detail.long_pos = 0           
            position_detail.long_pnl = 0       
            position_detail.long_yd = 0            
            position_detail.long_td = 0          
            position_detail.long_pos_frozen = 0        
            position_detail.long_price = 0            
            position_detail.short_pos = 0            
            position_detail.short_pnl = 0          
            position_detail.short_yd = 0      
            position_detail.short_td = 0         
            position_detail.short_price = 0      
            position_detail.short_pos_frozen = 0         
            return position_detail

Then, in order for the transaction strategy to call the get position detail function directly from the engine, a call function must be added to the CTA strategy template. Find the file in the same directory. After typing, add the following code to the CtaTemplate class:

def get_position_detail(self, vt_symbol: str):        
        return self.cta_engine.get_position_detail(vt_symbol)

Modify CTA policy code

The underlying functions have been added, so now it's the turn to change the specific transaction strategy logic, so as to realize the tracking and cancellation based on the real-time tick market.

Add policy runtime variable

It includes the trigger controller in the delegation status, the specific delegation amount and the order splitting interval:

def __init__(self, cta_engine, strategy_name, vt_symbol, setting):  
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)    

        #State control initialization     
        self.chase_long_trigger = False   
        self.chase_sell_trigger = False     
        self.chase_short_trigger = False   
        self.chase_cover_trigger = False    
        self.long_trade_volume = 0    
        self.short_trade_volume = 0    
        self.sell_trade_volume = 0       
        self.cover_trade_volume = 0    
        self.chase_interval = 10    #Order splitting interval: seconds

Modify on tick function logic

The management logic of the delegated hanging and canceling order that needs to be added in the on tick function is as follows:

  1. Call the get position detail function added in the cta policy template, and get the active order dictionary through the engine. Note that the dictionary only caches uncommitted or partially closed delegates, where key is vt_ordered in string format, and value corresponds to OrderData object;
  2. If the activity delegation obtained by engine is empty, it means that the delegation has been completed, that is, order_finished=True; otherwise, it means that the delegation has not been completed, that is, order_finished=False, which requires fine-grained management;
  3. The first step of fine management is to first process the oldest activity delegation, first obtain the delegation number VT [OrderID] and the OrderData object, and then determine whether the open and close position attribute (i.e. offer) of the OrderData object is to carry out open and close position pursuit: a. in the case of open and close position pursuit, first obtain the uncommitted quantity order.untraded, if the current delegation is more than 10 seconds and has not been completed (phase_interval= 10) , and the order chasing is not triggered (chase_long_trigger = False), first cancel the delegation, and then start the trigger chaser; b. in the case of closing order chasing, also get the outstanding volume first, if the delegation is overdue and the closing trigger is not started, cancel the order first, and then start the closing trigger;
  4. When all outstanding orders have been processed, the active Commission dictionary will be cleared. At this time, the order finished status will change from False to True. With the latest trading market, the order opened position of the order opened position, the order closed position of the order closed position will be closed immediately, and the initial status of the Commission trigger will be restored after each operation:
def on_tick(self, tick: TickData):        
        active_orders = self.get_position_detail(tick.vt_symbol).active_orders

        if active_orders:          
            #Delegation completion status            
            order_finished = False      
            vt_orderid = list(active_orders.keys())[0]   #Vt_ordered                     
            order = list(active_orders.values())[0]      #Commission list dictionary

            #Open positions and chase orders. Some transactions have no close out instructions (Offset.NONE)       
            if order.offset in (Offset.NONE, Offset.OPEN):               
                if order.direction == Direction.LONG:     
                    self.long_trade_volume = order.untraded     
                    if (tick.datetime - order.datetime).seconds > self.chase_interval and 
self.long_trade_volume > 0 and (not self.chase_long_trigger) and vt_orderid: 
                        #Cancellation of previous outstanding orders    
                        self.chase_long_trigger = True      
                elif order.direction == Direction.SHORT:     
                    self.short_trade_volume = order.untraded                 
                    if (tick.datetime - order.datetime).seconds > self.chase_interval and 
self.short_trade_volume > 0 and (not self.chase_short_trigger) and vt_orderid:   
                        self.chase_short_trigger = True   
            #Close out           
            elif order.offset in (Offset.CLOSE, Offset.CLOSETODAY):    
                if order.direction == Direction.SHORT:    
                    self.sell_trade_volume = order.untraded   
                    if (tick.datetime - order.datetime).seconds > self.chase_interval and 
self.sell_trade_volume > 0 and (not self.chase_sell_trigger) and vt_orderid:      
                        self.chase_sell_trigger = True 
                if order.direction == Direction.LONG: 
                    self.cover_trade_volume = order.untraded        
                    if (tick.datetime - order.datetime).seconds > self.chase_interval and 
self.cover_trade_volume > 0 and (not self.chase_cover_trigger) and vt_orderid:        

                        self.chase_cover_trigger = True  
            order_finished = True   
        if self.chase_long_trigger and order_finished:    
  , self.long_trade_volume)      
            self.chase_long_trigger = False       
        elif self.chase_short_trigger and order_finished:         
            self.short(tick.bid_price_1, self.short_trade_volume)     
            self.chase_short_trigger = False        
        elif self.chase_sell_trigger and order_finished:     
            self.sell(tick.bid_price_1, self.sell_trade_volume)  
            self.chase_sell_trigger = False      
        elif self.chase_cover_trigger and order_finished:  
            self.cover(tick.ask_price_1, self.cover_trade_volume) 
            self.chase_cover_trigger = False

Finally, it should be noted that the management logic of Tick level fine hanging and cancellation cannot be tested back through the K-line, so the most important thing is to fully test through simulation trading (such as futures based on SimNow).

More than half of the course " advanced in full practice - zero basis of options" has been updated! This course is specially designed for novices who have never been exposed to expiration rights trading. There are 30 courses in total, which will take you step by step to master the basic knowledge of options, understand the contract characteristics and varieties details, learn the direction of trading and arbitrage portfolio and other common options trading strategies. Please contact us for details New course online: introduction to zero basis of options.

Topics: Attribute