vn.py source interpretation (six main engine code analysis --- policy module)

      Before talking about MainEngine when there is such a code is:

 me.addApp(ctaStrategy)

       Here, we take a look inside this addApp MainEngine function code:

    def addApp(self, appModule):
        """添加上层应用"""
        appName = appModule.appName
        
        # 创建应用实例
        self.appDict[appName] = appModule.appEngine(self, self.eventEngine)
        
        # 将应用引擎实例添加到主引擎的属性中
        self.__dict__[appName] = self.appDict[appName]
        
        # 保存应用信息
        d = {
            'appName': appModule.appName,
            'appDisplayName': appModule.appDisplayName,
            'appWidget': appModule.appWidget,
            'appIco': appModule.appIco
        }
        self.appDetailList.append(d)

      I found that, regardless of the variable naming or the entire process, and gateway are like, so the analysis behind it should be very easy.

      Similarly, we look at the contents of ctaStrategy. Similarly, in vnpy / trader / app / ctaStrategy init file folder in the following, this is the code:

from .ctaEngine import CtaEngine
from .uiCtaWidget import CtaEngineManager

appName = 'CtaStrategy'
appDisplayName = u'CTA策略'
appEngine = CtaEngine
appWidget = CtaEngineManager
appIco = 'cta.ico'

         We look, appEngine in the end is kind of how a class. In fact CtaEngine,

         We generally look at is how to achieve this class.

class CtaEngine(AppEngine):
    """CTA策略引擎"""
    settingFileName = 'CTA_setting.json'
    settingfilePath = getJsonPath(settingFileName, __file__)
    
    STATUS_FINISHED = set([STATUS_REJECTED, STATUS_CANCELLED, STATUS_ALLTRADED])

    #----------------------------------------------------------------------
    def __init__(self, mainEngine, eventEngine):
        """Constructor"""
        self.mainEngine = mainEngine
        self.eventEngine = eventEngine
        
        # 当前日期
        self.today = todayDate()
        
        # 保存策略实例的字典
        # key为策略名称,value为策略实例,注意策略名称不允许重复
        self.strategyDict = {}
        
        # 保存vtSymbol和策略实例映射的字典(用于推送tick数据)
        # 由于可能多个strategy交易同一个vtSymbol,因此key为vtSymbol
        # value为包含所有相关strategy对象的list
        self.tickStrategyDict = {}
        
        # 保存vtOrderID和strategy对象映射的字典(用于推送order和trade数据)
        # key为vtOrderID,value为strategy对象
        self.orderStrategyDict = {}     
        
        # 本地停止单编号计数
        self.stopOrderCount = 0
        # stopOrderID = STOPORDERPREFIX + str(stopOrderCount)
        
        # 本地停止单字典
        # key为stopOrderID,value为stopOrder对象
        self.stopOrderDict = {}             # 停止单撤销后不会从本字典中删除
        self.workingStopOrderDict = {}      # 停止单撤销后会从本字典中删除
        
        # 保存策略名称和委托号列表的字典
        # key为name,value为保存orderID(限价+本地停止)的集合
        self.strategyOrderDict = {}
        
        # 成交号集合,用来过滤已经收到过的成交推送
        self.tradeSet = set()
        
        # 引擎类型为实盘
        self.engineType = ENGINETYPE_TRADING
        
        # 注册日式事件类型
        self.mainEngine.registerLogEvent(EVENT_CTA_LOG)
        
        # 注册事件监听
        self.registerEvent()

         We found that the class initialization function ctaEngine actually have to pass MainEngine and EventEngine, feel there is so little messy. . . init function, the front are relatively simple, is to initialize some variables internal use, starting from the last function, to register event listeners. We look at this method:

    def registerEvent(self):
        """注册事件监听"""
        self.eventEngine.register(EVENT_TICK, self.processTickEvent)
        self.eventEngine.register(EVENT_ORDER, self.processOrderEvent)
        self.eventEngine.register(EVENT_TRADE, self.processTradeEvent)

         So, we now examine these events as long as the callback method on it.

         The specific contents of these methods will not say. Among this class to the processing of these functions at the beginning of the callback process, there are other methods for producing a single, withdrawals and the like events. These methods include: sendOrder, cancelOrder, sendStopOrder, cancelStopOrder.

         We sendOrder example,

    def sendOrder(self, vtSymbol, orderType, price, volume, strategy):
        """发单"""
        contract = self.mainEngine.getContract(vtSymbol)
        
        req = VtOrderReq()
        req.symbol = contract.symbol
        req.exchange = contract.exchange
        req.vtSymbol = contract.vtSymbol
        req.price = self.roundToPriceTick(contract.priceTick, price)
        req.volume = volume
        
        req.productClass = strategy.productClass
        req.currency = strategy.currency        
        
        # 设计为CTA引擎发出的委托只允许使用限价单
        req.priceType = PRICETYPE_LIMITPRICE    
        
        # CTA委托类型映射
        if orderType == CTAORDER_BUY:
            req.direction = DIRECTION_LONG
            req.offset = OFFSET_OPEN
            
        elif orderType == CTAORDER_SELL:
            req.direction = DIRECTION_SHORT
            req.offset = OFFSET_CLOSE
                
        elif orderType == CTAORDER_SHORT:
            req.direction = DIRECTION_SHORT
            req.offset = OFFSET_OPEN
            
        elif orderType == CTAORDER_COVER:
            req.direction = DIRECTION_LONG
            req.offset = OFFSET_CLOSE
            
        # 委托转换
        reqList = self.mainEngine.convertOrderReq(req)
        vtOrderIDList = []
        
        if not reqList:
            return vtOrderIDList
        
        for convertedReq in reqList:
            vtOrderID = self.mainEngine.sendOrder(convertedReq, contract.gatewayName)    # 发单
            self.orderStrategyDict[vtOrderID] = strategy                                 # 保存vtOrderID和策略的映射关系
            self.strategyOrderDict[strategy.name].add(vtOrderID)                         # 添加到策略委托号集合中
            vtOrderIDList.append(vtOrderID)
            
        self.writeCtaLog(u'策略%s发送委托,%s,%s,%s@%s' 
                         %(strategy.name, vtSymbol, req.direction, volume, price))
        
        return vtOrderIDList

         First, the data obtained from all contracts MainEngine then constructs a class for transmitting the order information:
 

        req = VtOrderReq()
        req.symbol = contract.symbol
        req.exchange = contract.exchange
        req.vtSymbol = contract.vtSymbol
        req.price = self.roundToPriceTick(contract.priceTick, price)
        req.volume = volume
        
        req.productClass = strategy.productClass
        req.currency = strategy.currency        
        
        # 设计为CTA引擎发出的委托只允许使用限价单
        req.priceType = PRICETYPE_LIMITPRICE  

         The above code is to construct a VtOrder class, we look at how the class is defined:

class VtOrderReq(object):
    """发单时传入的对象类"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.symbol = EMPTY_STRING              # 代码
        self.exchange = EMPTY_STRING            # 交易所
        self.vtSymbol = EMPTY_STRING            # VT合约代码
        self.price = EMPTY_FLOAT                # 价格
        self.volume = EMPTY_INT                 # 数量
    
        self.priceType = EMPTY_STRING           # 价格类型
        self.direction = EMPTY_STRING           # 买卖
        self.offset = EMPTY_STRING              # 开平
        
        # 以下为IB相关
        self.productClass = EMPTY_UNICODE       # 合约类型
        self.currency = EMPTY_STRING            # 合约货币
        self.expiry = EMPTY_STRING              # 到期日
        self.strikePrice = EMPTY_FLOAT          # 行权价
        self.optionType = EMPTY_UNICODE         # 期权类型     
        self.lastTradeDateOrContractMonth = EMPTY_STRING   # 合约月,IB专用
        self.multiplier = EMPTY_STRING                     # 乘数,IB专用
        

         We found that this class is very simple, in fact, contract information. While forcibly order to limit order type. The following code is that some CTA mapping interface,

 # 委托转换
 reqList = self.mainEngine.convertOrderReq(req)

         When sending a formal order, have commissioned conversion process step, vnpy authors write in this process inside the DataEngine code is as follows:

The first is called MainEngine conversion function, and then further calls dataEngine

    def convertOrderReq(self, req):
        """转换委托请求"""
        return self.dataEngine.convertOrderReq(req)

         We found that, dataEngine there is such a result:

    def convertOrderReq(self, req):
        """根据规则转换委托请求"""
        detail = self.detailDict.get(req.vtSymbol, None)
        if not detail:
            return [req]
        else:
            return detail.convertOrderReq(req)

         Really speaking, it is still very strange, why the decision to write so many functions level. We continue to track what covertOrderReq method detail what functions.

         We can only look at the code to, detail inside the store is the position information related to the contract:

detail = PositionDetail(vtSymbol, contract)

         In this case, we look at the definition PositionDetail class:

class PositionDetail(object):
    """本地维护的持仓信息"""
    WORKING_STATUS = [STATUS_UNKNOWN, STATUS_NOTTRADED, STATUS_PARTTRADED]
    
    MODE_NORMAL = 'normal'          # 普通模式
    MODE_SHFE = 'shfe'              # 上期所今昨分别平仓
    MODE_TDPENALTY = 'tdpenalty'    # 平今惩罚

    #----------------------------------------------------------------------
    def __init__(self, vtSymbol, contract=None):
        """Constructor"""
        self.vtSymbol = vtSymbol
        self.symbol = EMPTY_STRING
        self.exchange = EMPTY_STRING
        self.name = EMPTY_UNICODE    
        self.size = 1
        。。。。。。
        。。。。。。
        。。。。。。
        。。。。。。

         nvpy author comments that explain the role of this class is to maintain positions for local information.

         Inside there are several types of methods it:

  1. Update class information, such as updateTrade
  2. Calculation of class, Bill calculatePosition
  3. There is what we mentioned above, tools.
    def convertOrderReq(self, req):
        """转换委托请求"""
        # 普通模式无需转换
        if self.mode is self.MODE_NORMAL:
            return [req]
        
        # 上期所模式拆分今昨,优先平今
        elif self.mode is self.MODE_SHFE:
            # 开仓无需转换
            if req.offset is OFFSET_OPEN:
                return [req]
            
            # 多头
            if req.direction is DIRECTION_LONG:
                posAvailable = self.shortPos - self.shortPosFrozen
                tdAvailable = self.shortTd- self.shortTdFrozen
                ydAvailable = self.shortYd - self.shortYdFrozen            
            # 空头
            else:
                posAvailable = self.longPos - self.longPosFrozen
                tdAvailable = self.longTd - self.longTdFrozen
                ydAvailable = self.longYd - self.longYdFrozen
                
            # 平仓量超过总可用,拒绝,返回空列表
            if req.volume > posAvailable:
                return []
            # 平仓量小于今可用,全部平今
            elif req.volume <= tdAvailable:
                req.offset = OFFSET_CLOSETODAY
                return [req]
            # 平仓量大于今可用,平今再平昨
            else:
                l = []
                
                if tdAvailable > 0:
                    reqTd = copy(req)
                    reqTd.offset = OFFSET_CLOSETODAY
                    reqTd.volume = tdAvailable
                    l.append(reqTd)
                    
                reqYd = copy(req)
                reqYd.offset = OFFSET_CLOSEYESTERDAY
                reqYd.volume = req.volume - tdAvailable
                l.append(reqYd)
                
                return l
            
        # 平今惩罚模式,没有今仓则平昨,否则锁仓
        elif self.mode is self.MODE_TDPENALTY:
            # 多头
            if req.direction is DIRECTION_LONG:
                td = self.shortTd
                ydAvailable = self.shortYd - self.shortYdFrozen
            # 空头
            else:
                td = self.longTd
                ydAvailable = self.longYd - self.longYdFrozen
                
            # 这里针对开仓和平仓委托均使用一套逻辑
            
            # 如果有今仓,则只能开仓(或锁仓)
            if td:
                req.offset = OFFSET_OPEN
                return [req]
            # 如果平仓量小于昨可用,全部平昨
            elif req.volume <= ydAvailable:
                if self.exchange is EXCHANGE_SHFE:
                    req.offset = OFFSET_CLOSEYESTERDAY
                else:
                    req.offset = OFFSET_CLOSE
                return [req]
            # 平仓量大于昨可用,平仓再反向开仓
            else:
                l = []
                
                if ydAvailable > 0:
                    reqClose = copy(req)
                    if self.exchange is EXCHANGE_SHFE:
                        reqClose.offset = OFFSET_CLOSEYESTERDAY
                    else:
                        reqClose.offset = OFFSET_CLOSE
                    reqClose.volume = ydAvailable
                    
                    l.append(reqClose)
                    
                reqOpen = copy(req)
                reqOpen.offset = OFFSET_OPEN
                reqOpen.volume = req.volume - ydAvailable
                l.append(reqOpen)
                
                return l
        
        # 其他情况则直接返回空
        return []

         We found that this conversion is based on trading patterns and change,

MODE_NORMAL = 'normal'          # 普通模式
MODE_SHFE = 'shfe'              # 上期所今昨分别平仓
MODE_TDPENALTY = 'tdpenalty'    # 平今惩罚

        In fact, based on the current position to determine what the next single should be how to adjust, it is still considered the difference Lock position after netting and before the next single.

         Introduced over the function under a single transaction, as well as the rest of the initialization and strategies, tactics stop, start related. We'll continue to look at.

         First, we go back to the beginning of CtaRunTrading files, we found that:

    cta = me.getApp(ctaStrategy.appName)

    cta.loadSetting()
    le.info(u'CTA策略载入成功')

    cta.initAll()
    le.info(u'CTA策略初始化成功')

    cta.startAll()
    le.info(u'CTA策略启动成功')

         The above code to dig us a detailed look at what is behind the calls.

         The first is getApp. This is very simple, it is to return to the policy of the entire module out:

    def getApp(self, appName):
        """获取APP引擎对象"""
        return self.appDict[appName]

         Then we get is actually speaking in front of such a ctaEngine class, and then call loadingSetting method of this class, initAll methods and startAll method, respectively.

         We will look at in this order:

    def loadSetting(self):
        """读取策略配置"""
        with open(self.settingfilePath) as f:
            l = json.load(f)
            
            for setting in l:
                self.loadStrategy(setting)

 The first is to open a configuration file, we can see at the beginning of the class definition about where the path to the file in:

settingFileName = 'CTA_setting.json'
settingfilePath = getJsonPath(settingFileName, __file__)

We look at what is inside this file:

[


    {
        "name": "atr rsi",
        "className": "AtrRsiStrategy",
        "vtSymbol": "rb1901"
    },

    {
        "name": "king keltner",
        "className": "KkStrategy",
        "vtSymbol": "rb1901"
    }
]

         We talked about this at the beginning of the article, the link up here on the. We see, after reading the setup file, it will start calling loadStrategy method, and the setting of each part to pass this way. We look loadStrategy code:

    def loadStrategy(self, setting):
        """载入策略"""
        try:
            name = setting['name']
            className = setting['className']
        except Exception:
            msg = traceback.format_exc()
            self.writeCtaLog(u'载入策略出错:%s' %msg)
            return
        
        # 获取策略类
        strategyClass = STRATEGY_CLASS.get(className, None)
        if not strategyClass:
            self.writeCtaLog(u'找不到策略类:%s' %className)
            return
        
        # 防止策略重名
        if name in self.strategyDict:
            self.writeCtaLog(u'策略实例重名:%s' %name)
        else:
            # 创建策略实例
            strategy = strategyClass(self, setting)  
            self.strategyDict[name] = strategy
            
            # 创建委托号列表
            self.strategyOrderDict[name] = set()
            
            # 保存Tick映射关系
            if strategy.vtSymbol in self.tickStrategyDict:
                l = self.tickStrategyDict[strategy.vtSymbol]
            else:
                l = []
                self.tickStrategyDict[strategy.vtSymbol] = l
            l.append(strategy)

         First, resolve name of the name of the policy and the policy of setting the inside, then to get the name of the policy strategy based on class. STRATEGY_CLASS So what is it?

         We look after the init file you can know, which is run on the variable assignment. Inside the code does not say a word about the folders that traverse the code below. Really, the wording of the author is to be commended. But at this stage we belong to learn the source of the stage, it could rewrite a follow-up personally feel more reasonable version.

         We're back, this is actually saved STRATEGY_CLASS code we write strategy. nvpy comes with source strategy in this strategy the following folder:

         To put it plainly, in fact, according to the policy need, we import dynamic in which init file, stored.

 # 遍历模块下的对象,只有名称中包含'Strategy'的才是策略类
        for k in dir(module):
            if 'Strategy' in k:
                v = module.__getattribute__(k)
                STRATEGY_CLASS[k] = v

         We saw this STRATEGY_CLASS the value is py file we write strategy implementation, follow-up will explain how to write the py file. To put it plainly, is the role of loadingStrategy we write strategy implemented py file to do a load. In fact, I think this is a good design method, can do a very good role in isolation, such a firm offer and backtesting strategies are the same class, but the role does have two.

         Then initAll method.

    def initAll(self):
        """全部初始化"""
        for name in self.strategyDict.keys():
            self.initStrategy(name)    

         In fact, each traversing a written policy class, and then call the initialization method to initialize it.

    def initStrategy(self, name):
        """初始化策略"""
        if name in self.strategyDict:
            strategy = self.strategyDict[name]
            
            if not strategy.inited:
                self.callStrategyFunc(strategy, strategy.onInit)
                strategy.inited = True
                
                self.loadSyncData(strategy)                             # 初始化完成后加载同步数据
                self.subscribeMarketData(strategy)                      # 加载同步数据后再订阅行情
            else:
                self.writeCtaLog(u'请勿重复初始化策略实例:%s' %name)
        else:
            self.writeCtaLog(u'策略实例不存在:%s' %name)        

         We can look at the process, first of all is to get from the dictionary storage policy strategy in the class itself, then check is initialized, and if not, use calltrategyFunc function to initialize. What we look at this function is:

    def callStrategyFunc(self, strategy, func, params=None):
        """调用策略的函数,若触发异常则捕捉"""
        try:
            if params:
                func(params)
            else:
                func()
        except Exception:
            # 停止策略,修改状态为未初始化
            strategy.trading = False
            strategy.inited = False
            
            # 发出日志
            content = '\n'.join([u'策略%s触发异常已停止' %strategy.name,
                                traceback.format_exc()])
            self.writeCtaLog(content)

         In fact, this function is a bit like a decorator, calling essentially onInit () methods and strategies class. As for the method on the class of policy we left behind speak. In short, this looks more complicated, in essence, is the last call to a method fills, but the abnormal catch up. In fact, I think this design is reasonable, or before reason, return policy strategy, the other should be isolated, including anomaly detection and the like, in many cases should not be taken into account in the preparation of the strategy. Although the catch exceptions in the policy inside the class initialization function is also possible, but it seems not so logically decoupled in the.

         Then loadSyncData method, in fact, read from the database belonging to the positions of this strategy; and finally the subscription market strategies. Our strategy is to act on a specific contract, so they need to market strategies class subscription by ctp: subscribeMarketData.

         Here, the basic logic of the main strategies in addition to the class are sort out a bit, a little bit about the possibility of a sudden, but Duokanjibian, painted several figures, probably will be able to understand.

 

 

 

Published 205 original articles · won praise 236 · views 980 000 +

Guess you like

Origin blog.csdn.net/qtlyx/article/details/84924990