そのようなAのコードがある場合にMainEngineの話をする前に、次のとおりです。
me.addApp(ctaStrategy)
ここで、我々はこのaddApp MainEngine機能コードの内部を見てみましょう:
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)
私は関係なく、変数の命名やプロセス全体の、それを発見し、その背後にある分析は非常に簡単でなければなりませんので、ゲートウェイは、のようなものです。
同様に、我々はctaStrategyの内容を見てください。同様に、以下ではvnpy /トレーダー/アプリ/ ctaStrategy初期化ファイルフォルダに、これはコードです:
from .ctaEngine import CtaEngine
from .uiCtaWidget import CtaEngineManager
appName = 'CtaStrategy'
appDisplayName = u'CTA策略'
appEngine = CtaEngine
appWidget = CtaEngineManager
appIco = 'cta.ico'
私たちは、最終的にはAppEngineのは、どのようにクラスの一種で、見て。実際CtaEngineには、
私たちは、一般的に、このクラスを達成するための方法です見てください。
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()
私たちは、クラスの初期化機能ctaEngineは、実際に、MainEngineとEventEngineを渡す必要がので、少し厄介があると感じていることが分かりました。。。初期化機能、フロントは比較的簡単で、イベントリスナーを登録するには、最後の関数から始めて、内部で使用するいくつかの変数を初期化することです。私たちは、この方法を見て:
def registerEvent(self):
"""注册事件监听"""
self.eventEngine.register(EVENT_TICK, self.processTickEvent)
self.eventEngine.register(EVENT_ORDER, self.processOrderEvent)
self.eventEngine.register(EVENT_TRADE, self.processTradeEvent)
そこで、我々は今、長い間その上にコールバックメソッドのように、これらのイベントを調べます。
これらのメソッドの具体的な内容は、言うことはありません。コールバックプロセスの開始時にこれらの関数の処理に、このクラスの中で、シングル、引き出しなどのイベントを生成するための他の方法があります。これらの方法には、次のとおりですsendOrder、cancelOrder、sendStopOrder、cancelStopOrder。
我々sendOrderたとえば、
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
まず、すべての契約から得られたデータは、注文情報を送信するためのクラスを構築しMainEngine:
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
上記のコードは、私たちは、クラスが定義されている方法を見て、VtOrderクラスを構築することです。
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专用
私たちは、実際には、このクラスは非常に単純であることを契約情報を発見しました。指値注文タイプに強制的にオーダーしながら。次のコードは、そのいくつかのCTAマッピングインタフェースであります
# 委托转换
reqList = self.mainEngine.convertOrderReq(req)
正式なオーダーを送信する際、変換プロセスのステップを委託している、vnpy著者はDataEngineコード内でこのプロセスに書き込み、次のとおりです。
第MainEngine変換関数と呼ばれ、そしてその後、さらにコールdataEngine
def convertOrderReq(self, req):
"""转换委托请求"""
return self.dataEngine.convertOrderReq(req)
私たちは、このような結果があるdataEngine、ことがわかりました。
def convertOrderReq(self, req):
"""根据规则转换委托请求"""
detail = self.detailDict.get(req.vtSymbol, None)
if not detail:
return [req]
else:
return detail.convertOrderReq(req)
決定は非常に多くの機能レベルを記述するために、なぜ本当に言えば、それは、まだ非常に奇妙です。私たちは、どのような機能をどのようなcovertOrderReq方法の詳細を追跡し続けます。
我々は唯一のコードを見ることができ、店舗内の詳細は、契約に関連した位置情報は以下のとおりです。
detail = PositionDetail(vtSymbol, contract)
この場合、我々は定義PositionDetailクラスを見てみましょう。
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作者のコメントは、ローカル情報のための位置を維持することです。
内部方法のいくつかのタイプは、それがあります。
- このようupdateTradeとして更新クラス情報、
- クラスの計算、ビルcalculatePosition
- 私たちは、上記のツールを言及するものがあります。
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 []
私たちは、この変換は取引パターンや変化に基づいていることがわかりました
MODE_NORMAL = 'normal' # 普通模式
MODE_SHFE = 'shfe' # 上期所今昨分别平仓
MODE_TDPENALTY = 'tdpenalty' # 平今惩罚
実際には、次のシングルを調整する方法がどうあるべきかを決定するために、現在の位置に基づいて、それはまだネッティング後、次のシングルの前に差異ロック位置を考えられています。
単一のトランザクションの下で機能だけでなく、初期化や戦略、戦術停止の残りの部分を上に導入、関連始めます。私たちは、見にいきましょう。
まず、我々はCtaRunTradingファイルの先頭に戻って、我々はそれを見つけました:
cta = me.getApp(ctaStrategy.appName)
cta.loadSetting()
le.info(u'CTA策略载入成功')
cta.initAll()
le.info(u'CTA策略初始化成功')
cta.startAll()
le.info(u'CTA策略启动成功')
私たちの呼び出し背後にあるもので、詳細を見て掘るために上記のコード。
最初はgetAppです。これはアウトモジュール全体の政策に戻ることで、非常に簡単です:
def getApp(self, appName):
"""获取APP引擎对象"""
return self.appDict[appName]
その後、我々は実際に、このようなctaEngineクラスの前で話している取得し、その後、それぞれ、このクラスのloadingSetting方法、initAll方法とstartallをメソッドを呼び出します。
私たちは、この順に見ていきます。
def loadSetting(self):
"""读取策略配置"""
with open(self.settingfilePath) as f:
l = json.load(f)
for setting in l:
self.loadStrategy(setting)
最初は、設定ファイルを開くことで、我々はおよそクラス定義の先頭に見ることができる場所にあるファイルのパス:
settingFileName = 'CTA_setting.json'
settingfilePath = getJsonPath(settingFileName, __file__)
私たちは、このファイル内にあるものを見て:
[
{
"name": "atr rsi",
"className": "AtrRsiStrategy",
"vtSymbol": "rb1901"
},
{
"name": "king keltner",
"className": "KkStrategy",
"vtSymbol": "rb1901"
}
]
私たちは、資料の冒頭にこのことについてのここまでのリンクを話しました。私たちは、それがloadStrategyメソッドの呼び出しを開始し、各部分の設定は、この方法を渡すために、セットアップファイルを読んだ後、参照してください。私たちは、loadStrategyコードを見て:
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)
まず、ポリシーの名前と内部の設定のポリシーの決意名、そのクラスに基づいた政策戦略の名前を取得します。STRATEGY_CLASSだから、それは何ですか?
私たちは、変数割り当てで実行され、あなたが知ることができるinitファイル、世話します。コードの中に以下のコードを通過するフォルダに関する言葉を言っていません。本当に、著者の文言が表彰されます。しかし、我々は、ステージのソースを学ぶために属し、この段階では、それがより合理的なバージョンを感じる個人的にフォローアップを書き換えることができます。
我々は戻って、これは実際には、我々は戦略を書くSTRATEGY_CLASSコードを保存されています。nvpyは、この戦略次のフォルダにソース戦略が付属しています:
端的に言えば、実際には、政策の必要性に応じて、我々はファイルを初期化する動的な、保存をインポートします。
# 遍历模块下的对象,只有名称中包含'Strategy'的才是策略类
for k in dir(module):
if 'Strategy' in k:
v = module.__getattribute__(k)
STRATEGY_CLASS[k] = v
私たちは、フォローアップPYファイルを作成する方法を説明します値は、我々は戦略実行PYファイルの書き込みされ、このSTRATEGY_CLASSを見ました。端的に言えば、我々は負荷を行うための戦略実装PYファイルを書き込むloadingStrategyの役割です。実際に、私はこれは良いデザインの方法だと思います、孤立して非常に良い役割を行うことができ、そのような会社の提供やバックテスト戦略は同じクラスですが、役割は2を持っています。
その後initAll方法。
def initAll(self):
"""全部初始化"""
for name in self.strategyDict.keys():
self.initStrategy(name)
実際には、それぞれが書かれたポリシークラスを横断し、それを初期化する初期化メソッドを呼び出します。
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)
私たちは、チェックが初期化され、クラス自体に辞書ストレージポリシー戦略から取得することですまず第一に、プロセスを見て、そうでない場合は、初期化するためにcalltrategyFunc機能を使用することができます。私たちは、この機能を見ては、次のとおりです。
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)
実際には、この機能は、基本的にのOnInit()メソッドと戦略クラスを呼び出し、ビットデコレータのようなものです。ポリシーのクラスの方法として、私たちは話す残しました。要するに、このルックスは、より複雑な、本質的には、メソッドの塗りつぶしを最後に呼び出しますが、異常なキャッチアップがあります。実際に、私はこのデザインは合理的である、または理由、返品ポリシー戦略の前に、他の多くの例では、異常検出などを含め、隔離されなければならない戦略の準備の際に考慮すべきではないと思います。クラスの初期化関数内のポリシーでキャッチ例外がも可能ですが、それはとても論理的に切り離されていないようです。
その後loadSyncData方法、実際には、この戦略の位置に属するデータベースから読み込まれ、最後に、サブスクリプション・市場戦略。subscribeMarketData:当社の戦略は、彼らが市場戦略CTPにより、クラスのサブスクリプションに必要なので、具体的な契約に基づいて行動することです。
ここでは、クラスに加えて、主要な戦略の基本的なロジックが少し整理され、突然の可能性が、Duokanjibianについて少しは、塗装いくつかの図は、おそらく理解することができるようになります。