Published in the vn.py 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 vn.py, two steps are used in the K-line callback function on bar:
- Call the cancel all function to cancel all the previous K-line's hung delegates
- 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 vn.py 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 vn.py 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 object.py file
First of all, you need to extend the OrderData class, which is mainly manifested in:
- In addition to the time attribute, the date and cancel time attributes are added to class initialization
- 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.
@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 = "" 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.exchange.value}" self.vt_orderid = f"{self.gateway_name}.{self.orderid}" self.untraded = self.volume - self.traded # With millisecond if self.date and "." in self.time: if "-"in self.date: self.datetime = datetime.strptime(" ".join([self.date, self.time]), "%Y-%m-%d %H:%M:%S.%f") else: self.datetime = datetime.strptime(" ".join([self.date, self.time]), "%Y%m%d %H:%M:%S.%f") # Without millisecond elif self.date: if "-" in self.date: self.datetime = datetime.strptime(" ".join([self.date, self.time]), "%Y-%m-%d %H:%M:%S") else: self.datetime = datetime.strptime(" ".join([self.date, 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 else: return False def create_cancel_request(self): """ Create cancel request object from order. """ req = CancelRequest( orderid=self.orderid, symbol=self.symbol, exchange=self.exchange ) return req
Extended position details query
Modify PositionHolding object
Enter the directory vnpy\trader to open the converter.py file. This time, we modified the PositionHolding class. The main changes are as follows:
- __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;
- The update position function adds the profit and loss of cache position and the average opening price;
- 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.exchange = contract.exchange 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 else: 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 else: if order.vt_orderid in self.active_orders: self.active_orders.pop(order.vt_orderid) self.calculate_frozen() ......
New get position detail function
Enter the directory vnpy \ app \ CTA ﹣ strategy and open the file engine.py. 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 """ try: return self.offset_converter.get_position_holding(vt_symbol) except: 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 template.py 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:
- 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;
- 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;
- 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;
- 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.cancel_order(vt_orderid) 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.cancel_order(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.cancel_order(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.cancel_order(vt_orderid) self.chase_cover_trigger = True else: order_finished = True if self.chase_long_trigger and order_finished: self.buy(tick.ask_price_1, 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 "vn.py 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.