10分鐘打造WonderTrader上的期貨日內交易策略

WonderTrader2.png

今天咱們來用WonderTraderpython子框架wtpy來實際編寫一個期貨日內交易的策略。而後咱們會先設定一組參數進行第一輪測試,再根據第一輪測試的結果,調整好參數之後,再進行第二輪測試。藉此來演示一下wtpy中策略如何編寫以及回測。python

準備工做


  • 安裝wtpy。在安裝了python3.6以上的計算機上執行一下命令。git

    $ pip install wtpy

    或者直接下載whl文件到本地進行安裝
    阿里雲鏡像地址:https://mirrors.aliyun.com/py...
    pipy地址:https://pypi.org/project/wtpy...github

  • github複製demo
    咱們選用期貨回測demo來做爲基準的demo進行修改。
    期貨回測demo下載地址:https://github.com/wondertrad...
  • 準備歷史回測數據
    咱們直接使用demo中自帶的股指期貨主力合約5分鐘數據進行回測,文件名爲CFFEX.IF.HOT_m5.csv。爲了提升測試效率,咱們只選取最後近兩個月時間的數據進行回測,具體爲2019年9月10日到2019年10月31日。
    股指期貨主力合約2019年9月10日-2019年10月31日走勢
    IF主力走勢
    2019年9月10日的開盤價爲3976.2,2019年10月31日收盤價爲3879.0,區間漲幅爲-2.44%。

肯定策略算法


  • 選擇策略算法
    咱們選擇比較經典的DualThrust做爲咱們策略的算法。一方面DualThrust流傳了好久了,曾經有不少大機構都用這個模型獲取到了足夠多的收益;另外一方面,DualThrust的算法複雜度比較低,比較適合咱們做爲演示策略來使用。
  • DualThrust的算法邏輯以下
    DualThrust.jpg
    MAX(HH-LC,HC-LL),做爲計算上下邊界的基準值,用今日開盤價做爲基準價,而後用上邊界係數下邊界係數,分別計算出上邊界的價格和下邊界的價格,當最新價突破上邊界或者下邊界的時候,就是咱們發出信號的時候。
    可是在策略的實現中,咱們還須要考慮到已有持倉的時候如何處理,因此最終的策略邏輯以下:算法

    當持倉爲0的時候,價格突破上邊界時,開多進場,價格突破下邊界時,開空進場
    當持倉爲多的時候,價格突破上邊界時,保持倉位,價格突破下邊界時,多反空
    當持倉爲空的時候,價格突破上邊界時,空反多,價格突破下邊界時,保持倉位

策略實現


  • 參數說明
    肯定了策略的算法之後,咱們須要肯定策略模塊的參數。參數的設置,要綜合考慮策略自己的參數,以及模塊使用的參數。最終咱們肯定了以下的參數:json

    name        策略實例名稱
    code        回測使用的合約代碼
    barCnt      要拉取的K線條數
    period      要使用的K線週期,採用週期類型+週期倍數的形式,如m5表示5分鐘線,d3表示3日線
    days        策略算法參數,算法引用的歷史數據條數
    k1          策略算法參數,上邊界係數
    k2          策略算法參數,下邊界係數
    isForStk    DualThrust策略用於控制交易品種的代碼

    咱們還能夠將基本手數做爲參數傳遞給策略模型,這樣的話通用性更強。不過咱們這裏就再也不增設參數了,默認手數都是1手。api

  • 最終策略源碼以下框架

    from wtpy import BaseStrategy
    from wtpy import Context
    
    class StraDualThrust(BaseStrategy):
    
        def __init__(self, name:str, code:str, barCnt:int, period:str, days:int, k1:float, k2:float, isForStk:bool = False):
            BaseStrategy.__init__(self, name)
    
            self.__days__ = days
            self.__k1__ = k1
            self.__k2__ = k2
    
            self.__period__ = period
            self.__bar_cnt__ = barCnt
            self.__code__ = code
    
            self.__is_stk__ = isForStk
    
        def on_init(self, context:Context):
            code = self.__code__    #品種代碼
            if self.__is_stk__:
                code = code + "Q"
    
            context.stra_get_bars(code, self.__period__, self.__bar_cnt__, isMain = True)
            context.stra_log_text("DualThrust inited")
    
        def on_calculate(self, context:Context):
            '''
            策略主調函數,全部的計算邏輯都在這裏完成
            '''
            code = self.__code__    #品種代碼
            
            # 交易單位,主要用於股票的適配
            trdUnit = 1
            if self.__is_stk__:
                trdUnit = 100
    
            #讀取最近50條1分鐘線(dataframe對象)
            theCode = code
            if self.__is_stk__:
                theCode = theCode + "Q"
            df_bars = context.stra_get_bars(theCode, self.__period__, self.__bar_cnt__, isMain = True)
    
            #把策略參數讀進來,做爲臨時變量,方便引用
            days = self.__days__
            k1 = self.__k1__
            k2 = self.__k2__
    
            #平倉價序列、最高價序列、最低價序列
            closes = df_bars["close"]
            highs = df_bars["high"]
            lows = df_bars["low"]
    
            #讀取days天以前到上一個交易日位置的數據
            hh = highs[-days:-1].max()
            hc = closes[-days:-1].max()
            ll = lows[-days:-1].min()
            lc = closes[-days:-1].min()
    
            #讀取今天的開盤價、最高價和最低價
            lastBar = df_bars.iloc[-1]
            openpx = lastBar["open"]
            highpx = lastBar["high"]
            lowpx = lastBar["low"]
    
            '''
            !!!!!這裏是重點
            一、首先根據最後一條K線的時間,計算當前的日期
            二、根據當前的日期,對日線進行切片,並截取所需條數
            三、最後在最終切片內計算所需數據
            '''
    
            #肯定上軌和下軌
            upper_bound = openpx + k1* max(hh-lc,hc-ll)
            lower_bound = openpx - k2* max(hh-lc,hc-ll)
    
            #讀取當前倉位
            curPos = context.stra_get_position(code)/trdUnit
    
            if curPos == 0:
                if highpx >= upper_bound:
                    context.stra_enter_long(code, 1*trdUnit, 'enterlong')
                    context.stra_log_text("向上突破%.2f>=%.2f,多倉進場" % (highpx, upper_bound))
                    #修改並保存
                    self.xxx = 1
                    context.user_save_data('xxx', self.xxx)
                    return
    
                if lowpx <= lower_bound and not self.__is_stk__:
                    context.stra_enter_short(code, 1*trdUnit, 'entershort')
                    context.stra_log_text("向下突破%.2f<=%.2f,空倉進場" % (lowpx, lower_bound))
                    return
            elif curPos > 0:
                if lowpx <= lower_bound:
                    context.stra_exit_long(code, 1*trdUnit, 'exitlong')
                    context.stra_log_text("向下突破%.2f<=%.2f,多倉出場" % (lowpx, lower_bound))
                    #raise Exception("except on purpose")
                    return
            else:
                if highpx >= upper_bound and not self.__is_stk__:
                    context.stra_exit_short(code, 1*trdUnit, 'exitshort')
                    context.stra_log_text("向上突破%.2f>=%.2f,空倉出場" % (highpx, upper_bound))
                    return
    
        def on_tick(self, context:Context, stdCode:str, newTick:dict):
            return

第一輪迴測


  • 肯定參數
    咱們採用股指期貨主力合約5分鐘K線進行回測,每次讀取50條歷史K線,用最近30條K線計算上下突破的邊界,上邊界係數初步定爲0.1,下邊界係數也初步定爲0.1
  • 修改runBT.py中策略的參數,而後運行runBT.py函數

    from wtpy import WtBtEngine
    from wtpy.backtest import WtBtAnalyst
    
    from Strategies.DualThrust import StraDualThrust
    
    if __name__ == "__main__":
        #建立一個運行環境,並加入策略
        engine = WtBtEngine()
        engine.init('.\\Common\\', "configbt.json")
        engine.configBacktest(201909100930,201910311500)
        engine.configBTStorage(mode="csv", path=".\\storage\\")
        engine.commitBTConfig() #代碼裏的配置項,會覆蓋配置文件configbt.json裏的配置項
    
        '''
        建立DualThrust策略的一個實例
        name    策略實例名稱
        code    回測使用的合約代碼
        barCnt  要拉取的K線條數
        period  要使用的K線週期,m表示分鐘線
        days    策略算法參數,算法引用的歷史數據條數
        k1      策略算法參數,上邊界係數
        k2      策略算法參數,下邊界係數
        isForStk    DualThrust策略用於控制交易品種的代碼
        '''
        straInfo = StraDualThrust(name='pydt_IF', code="CFFEX.IF.HOT", barCnt=50, period="m5", days=30, k1=0.1, k2=0.1, isForStk=False)
        engine.set_strategy(straInfo)
    
        #開始運行回測
        engine.run_backtest()
    
        #建立績效分析模塊
        analyst = WtBtAnalyst()
        #將回測的輸出數據目錄傳遞給績效分析模塊
        #init_capital爲初始資金規模
        #rf爲無風險收益率
        #annual_trading_days爲每一年的交易日天數,用於計算年化收益
        analyst.add_strategy("pydt_IF", folder="./outputs_bt/pydt_IF/", init_capital=500000, rf=0.02, annual_trading_days=240)
        #運行績效模塊
        analyst.run()
    
        kw = input('press any key to exit\n')
        engine.release_backtest()
  • 回測執行結束之後,打開自從生成的績效分析報告文件,查看績效分析結果
    回測績效概覽
    回測績效概覽
    而後打開成交日誌文件,查當作交明細
    成交明細1
    ……
    成交明細2
  • 回測結果分析
    從上面的績效報告能夠看出,在這組參數下,時間從2019年9月10日到2019年10月31日下午收盤截止,總共產生了370筆交易,即完整的開平370個回合,換算成成交的話,就是740次成交。
    雖然交易次數不少,可是收益卻很不理想,2個月不到的時間,50w的資金,總共虧損了近10w,約20%。從上面的截圖,咱們能夠看到,在最後一筆出場的時候,總盈虧是4,980.00,也就是說策略的邏輯到最後是盈利的,可是盈利的金額很小。讓咱們再看一下天天結算的資金狀況。
    每日資金結算
    從上圖咱們能夠看到,2個月的時間,佣金一共花費了104,775.82,因此帳戶總盈虧是-99,795.82。這樣咱們就能夠大體得出一個結論:由於上下邊界不夠寬,因此有不少噪音信號,也觸發了買賣的邏輯,從而致使買賣頻繁,佣金太高,最終致使虧損

第二輪迴測


  • 從新調整參數
    根據上一輪的結果分析,咱們須要把上邊界和下邊界拓寬,從而過濾掉一些噪聲波動,減小信號個數。因此咱們第二輪迴測,將上邊界係數改爲0.5,將下邊界係數改爲0.3
  • 修改runBT.py,而後運行runBT.py進行回測測試

    straInfo = StraDualThrust(name='pydt_IF', code="CFFEX.IF.HOT", barCnt=50, period="m5", days=30, k1=0.5, k2=0.3, isForStk=False)
  • 再查看績效報告
    回測績效概覽
    回測績效概覽
    成交明細
    成交明細
    每日資金結算
    image.png
  • 再分析結果
    從第二輪的績效報告能夠看出,當邊界拓寬之後,交易信號減小到了14個回合,可是交易收益卻達到了24,060.00,約爲第一輪收益的5倍,而佣金卻只有1,453.02元,比第一輪的佣金低了兩個量級。最終累計收益也由第一輪的-19.96%提高到盈利4.31%,相對於-2.44%的基準收益率,相對收益率達到6.75%。(這個算法對不對?:orz:)

結束語


上面演示了在WonderTrader上構建一個期貨日內交易策略的基本過程。回測穩定之後,策略就能夠不做任何修改的直接放到實盤裏去運行了。但願可以對你們有所啓發。
最後再打一波廣告:
WonderTradergithub地址:https://github.com/wondertrad...
WonderTrader官網地址:https://wondertrader.github.io阿里雲

相關文章
相關標籤/搜索