8. 拼搭出一個比較複雜的策略

qteasy是一個完全本地化部署和運行的量化交易分析工具包,具備以下功能:

  • 金融數據的獲取、清洗、存儲以及處理、可視化、使用

  • 量化交易策略的創建,並提供大量內置基本交易策略

  • 向量化的高速交易策略回測及交易結果評價

  • 交易策略參數的優化以及評價

  • 交易策略的部署、實盤運行

通過本系列教程,您將會通過一系列的實際示例,充分了解qteasy的主要功能以及使用方法。

8.1. 開始前的準備工作

在開始本節教程前,請先確保您已經掌握了下面的內容:

  • 安裝、配置qteasy —— 詳情請參閱QTEASY教程1

  • 設置了一個本地數據源,並已經將足夠的歷史數據下載到本地(包括交易日曆、股票/基金/指數基本資訊、股票/基金/指數的價格數據以及財務指標或其他財務數據——詳情請參閱QTEASY教程2

  • 學會創建交易員對象,使用一個內置交易策略並回測其歷史表現,檢查回測日誌、明白如何調整策略的運行參數或可調參數,改進策略的表現——QTEASY教程3

QTEASY文檔中,還能找到更多關於如何創建交易員對象運行策略,使用歷史數據回測策略,檢查回測交易記錄,修改策略等等相關內容。對qteasy的基本使用方法還不熟悉的同學,可以移步那裏查看更多詳細說明。

8.2. 本節的目標

在上一節教程中,我們創建了一個Operator交易員對象,並且使該交易員運行了第一個選股交易策略。不過,在qteasy中,交易員的能耐可遠不止運行一個交易策略這麼簡單。實際上,Operator對象類似於真正的交易員,它可以同時控制並運行任意多個交易策略,這些交易策略既可以在不同的時刻以不同的頻率分別運行,也可以集中大量同時運行,而且,同時運行的策略還可以以任意指定的方式“混合”成需要的樣子。

您可以把交易員想像成一名畫家,手裏的調色板上有着不同顏色的顏料,他既可以使用不同顏色的顏料勾勒出顏色鮮明的線條,也可以用幾種不同顏色調配處柔和的過渡色;這樣,即使畫家手裏的顏料只有寥寥數種,但是畫筆下卻可以調配出大千世界的億萬種色彩。

這正是qteasy的重要設計理念之一:Operator交易員對象通過下面兩種工具,可以將非常簡單的交易策略組合調配成非常複雜的交易策略,從而實現複雜的功能,這兩種工具包括:

  • Strategy Group 策略組:交易員可以將多個策略分組運行,每一組交易策略共享同樣的運行頻率(例如每日運行,每小時運行或每分鐘運行)以及運行時機(例如每日收盤時運行或每天10:30運行),這樣可以靈活地控制策略的運行時間。

  • Blender 策略混合器:在同一組內同時運行的交易策略必然是同時運行的,此時各個策略會根據各自的運行邏輯生成一組交易信號,但是這一組交易信號必須被以某種方式“合併”成一組,這種合併的方式是通過混合器blender執行的,混合器專門負責將多個策略生成的交易信號融合成一組,而融合的邏輯完全由用戶定義的blender string組合字符串確定,用戶有完全控制權。

在本節中,我們將瞭解如果使用qteasy中更多的內置策略,如何使交易員同時運行多個交易策略,如何使用策略混合器blender來使用交易策略生成不同的組合策略,

目前qteasy支持超過70種內置交易策略,全部都是開箱即用,完整的內置交易策略清單請參見參考文檔

通過下面的API可以獲取qteasy中提供的所有內置交易策略的清單:

import qteasy as qt
qt.built_ins()

上面的API將返回qteasy所有內置交易策略:

{'crossline': qteasy.built_in.CROSSLINE,
 'macd': qteasy.built_in.MACD,
 'dma': qteasy.built_in.DMA,
 'trix': qteasy.built_in.TRIX,
 'cdl': qteasy.built_in.CDL,
 'bband': qteasy.built_in.BBand,
 's-bband': qteasy.built_in.SoftBBand,
 'sarext': qteasy.built_in.SAREXT,
 'ssma': qteasy.built_in.SCRSSMA,
 'sdema': qteasy.built_in.SCRSDEMA,
 'sema': qteasy.built_in.SCRSEMA,
 'sht': qteasy.built_in.SCRSHT,
 'skama': qteasy.built_in.SCRSKAMA,
 'smama': qteasy.built_in.SCRSMAMA,
 ...}

下面清單羅列了部分qteasy內置開箱即用的交易策略,完整的清單請參見參考文檔

ID

策略名稱

說明

crossline

TimingCrossline

crossline擇時策略類,利用長短均線的交叉確定多空狀態
1,當短均線位於長均線上方,且距離大於l*m%時,設置倉位目標爲1
2,當短均線位於長均線下方,且距離大於l*m%時,設置倉位目標爲-1
3,當長短均線之間的距離不大於l*m%時,設置倉位目標爲0

macd

TimingMACD

MACD擇時策略類,運用MACD均線策略,生成目標倉位百分比:
1,當MACD值大於0時,設置倉位目標爲1
2,當MACD值小於0時,設置倉位目標爲0

dma

TimingDMA

DMA擇時策略
1, DMA在AMA上方時,多頭區間,即DMA線自下而上穿越AMA線後,輸出爲1
2, DMA在AMA下方時,空頭區間,即DMA線自上而下穿越AMA線後,輸出爲0

trix

TimingTRIX

TRIX擇時策略,使用股票價格的三重平滑指數移動平均價格進行多空判斷:
計算價格的三重平滑指數移動平均價TRIX,再計算M日TRIX的移動平均:
1, TRIX位於MATRIX上方時,設置倉位目標爲1
2, TRIX位於MATRIX下方時,設置倉位目標位-1

cdl

TimingCDL

CDL擇時策略,在K線圖中找到符合要求的cdldoji模式
搜索歷史數據窗口內出現的cdldoji模式(匹配度0~100之間),加總後/100,計算 等效cdldoji匹配數量,以匹配數量爲交易信號。

bband

TimingBBand

布林帶線交易策略,根據股價與布林帶上軌和布林帶下軌之間的關係確定多空,在價格上穿或下穿布林帶線上下軌時產生交易信號。布林帶線的均線類型不可選
1,當價格上穿上軌時,產生全倉買入信號
2,當價格下穿下軌時,產生全倉賣出信號

s-bband

SoftBBand

布林帶線漸進交易策略,根據股價與布林帶上軌和布林帶下軌之間的關係確定多空,交易信號不是一次性產生的,而是逐步漸進買入和賣出。計算BBAND,檢查價格是否超過BBAND的上軌或下軌:
1,當價格大於上軌後,每天產生10%的比例買入交易信號
2,當價格低於下軌後,每天產生33%的比例賣出交易信號

sarext

TimingSAREXT

擴展拋物線SAR策略,當指標大於0時發出買入信號,當指標小於0時發出賣出信號

ssma

SCRSSMA

單均線交叉策略——SMA均線(簡單移動平均線):根據股價與SMA均線的相對位置設定持倉比例

sdema

SCRSDEMA

單均線交叉策略——DEMA均線(雙重指數平滑移動平均線):根據股價與DEMA均線的相對位置設定持倉比例

sema

SCRSEMA

單均線交叉策略——EMA均線(指數平滑移動均線):根據股價與EMA均線的相對位置設定持倉比例

完整的內置策略清單請見參考文檔

每一個內置交易策略都有自己的ID(例如上表中的crosslinemacddma等等),用戶可以通過這個ID來獲取內置交易策略的實例,並將其加入到交易員對象中使用。例如:

stg = qt.get_built_in_strategy('dma')
stg.info()

得到輸出:

================================ Strategy: DMA =================================
Strategy RULE-ITER(DMA)
Parameters: ['slow', 'long', 'diff'] = (12, 26, 9)                  
Date Types: close_ANY_d x 270                                       
----------------------------- Iteration Properties -----------------------------
Allow multi pars        True
Multi-parameter not set                      

如果需要查看每一個內置交易策略的詳細解釋,例如策略參數的含義、信號生成規則,可以查看每一個交易策略的doc-string

例如:

qt.built_in_doc('Crossline', print_out=True)

可以看到

Init signature: qt.built_in.TimingCrossline(pars:tuple=(35, 120, 0.02))
Docstring:     
crossline择时策略类,利用长短均线的交叉确定多空状态

策略参数:
    s: int, 短均线计算日期;
    l: int, 长均线计算日期;
    m: float, 均线边界宽度(百分比);
信号类型:
    PT型:目标仓位百分比
信号规则:
    1,当短均线位于长均线上方,且距离大于l*m%时,设置仓位目标为1
    2,当短均线位于长均线下方,且距离大于l*mM时,设置仓位目标为-1
    3,当长短均线之间的距离不大于l*m%时,设置仓位目标为0

策略属性缺省值:
默认参数:(35, 120, 0.02)
数据类型:close 收盘价,单数据输入
采样频率:天
窗口长度:270
参数范围:[(10, 250), (10, 250), (0, 1)]
策略不支持参考数据,不支持交易数据
File:           ~/Library/CloudStorage/OneDrive-Personal/Projects/PycharmProjects/qteasy/qteasy/built_in.py
Type:           type
Subclasses:     

ipython等交互式python環境中,也可以使用?來顯示內置交易策略的詳細資訊,例如:

>>> qt.built_in.SelectingNDayRateChange?

可以看到:

Init signature: qt.built_in.SelectingNDayRateChange(pars=(14,))
Docstring:     
基础选股策略:根据股票以前n天的股价变动比例作为选股因子

策略参数:
    n: int, 股票历史数据的选择期
信号类型:
    PT型:百分比持仓比例信号
信号规则:
    在每个选股周期使用以前n天的股价变动比例作为选股因子进行选股
    通过以下策略属性控制选股方法:
    *max_sel_count:     float,  选股限额,表示最多选出的股票的数量,默认值:0.5,表示选中50%的股票
    *condition:         str ,   确定股票的筛选条件,默认值'any'
                                'any'        :默认值,选择所有可用股票
                                'greater'    :筛选出因子大于ubound的股票
                                'less'       :筛选出因子小于lbound的股票
                                'between'    :筛选出因子介于lbound与ubound之间的股票
                                'not_between':筛选出因子不在lbound与ubound之间的股票
    *lbound:            float,  执行条件筛选时的指标下界, 默认值np.-inf
    *ubound:            float,  执行条件筛选时的指标上界, 默认值np.inf
    *sort_ascending:    bool,   排序方法,默认值: False,
                                True: 优先选择因子最小的股票,
                                False, 优先选择因子最大的股票
    *weighting:         str ,   确定如何分配选中股票的权重
                                默认值: 'even'
                                'even'       :所有被选中的股票都获得同样的权重
                                'linear'     :权重根据因子排序线性分配
                                'distance'   :股票的权重与他们的指标与最低之间的差值(距离)成比例
                                'proportion' :权重与股票的因子分值成正比

策略属性缺省值:
默认参数:(14,)
数据类型:close 收盘价,单数据输入
采样频率:月
窗口长度:150
参数范围:[(2, 150)]
策略不支持参考数据,不支持交易数据
File:           ~/Library/CloudStorage/OneDrive-Personal/Projects/PycharmProjects/qteasy/qteasy/built_in.py
Type:           type
Subclasses:    

8.3. 多重策略以及策略組合

qteasy中,一個Operator交易員對象可以同時分組運行任意多個交易策略。這些交易策略在運行的時候,都會分別提取各自所需的歷史數據,獨立生成不同的交易信號,這些交易信號會被組合成一組交易信號,統一執行。

利用這種特性,用戶可以在一個交易員對象中同時運行多個各有側重的交易策略,例如,一個交易策略監控個股的股價,根據股價產生擇信號,第二個交易策略專門負責監控大盤走勢,通過大盤走勢決定整體倉位。第三個交易策略專門負責止盈止損,在特定時刻止損。最終的交易信號以第一個交易策略爲主,但受到第二個策略的節制,必要時會被第三個策略完全控制。

或者,用戶也可以很容易地制定出一個“委員會”策略,在一個綜合性策略中由多個策略獨立地做出交易決策,最終的交易信號由所有子策略組成的”委員會“投票決定,投票的方式可以是簡單多數、絕對多數、加權投票結果等等。

上述交易策略組合中,每一個獨立的交易策略都很簡單,很容易定義,而將他們組合起來,又能發揮更大的作用。同時每一個子策略都是獨立的,可以自由組合出複雜的綜合性交易策略。這樣可以避免不斷地重複開發策略,只需要對子策略重新排列組合,重新定義組合方式,就可以快速地搭建一系列的複雜綜合性交易策略。這樣能夠極大地提高交易策略的搭建效率,縮短週期。時間就是金錢。

交易信號的混合即交易信號的各種運算或函數,從簡單的邏輯運算、加減運算一直到複雜的自定義函數,只要能夠應用於一個ndarray的函數,都可以用於混合交易信號,只要最終輸出的交易信號有意義即可。

定義策略組合方式blender

在一個Operator交易員對象中,所有的交易策略都必須被分配到某一個策略組中,如果策略組中的策略數量多於1個,就必須定義一個blender。如果沒有明確定義blender,而策略的數量超過1個時,qteasy會在運行Operator的時候創建一個默認的blender,但是爲了讓多重策略正確運行,用戶需要自行定義blender

blender_str是用戶自行定義的一個組合表達式,用戶使用這個表達式確定不同交易策略的組合方式。這個組合表達式使用四則運算符、邏輯運算符、函數等符號規定策略信號是如何組合的。blender表達式中可以包括以下元素:

blender表達式中支持的符號如下:

元素

示例

說明

策略序號

s0, s1...

以s開頭,數字結尾的字符串,數字爲Group中的策略的序號,代表這個策略生成的交易信號

數字

-1.35

任何合法的數字,參與表達式運算的數字

運算符

+

包括+ - * / ^等數學運算符

邏輯運算符

and

支持&/~以及and/or/not等邏輯運算符

函數

sum()

表達式支持多種函數,支持的函數參見後表

括號

()

組合運算

blender示例

當一個Operator對象中有三個交易策略時(其序號分別爲s0/s1/s2),按照以下方式定義的blender都是合法可用的,同時使用Operator.set_blender()來設置blender

使用四則運算符定義blender表達式

's0 + s1 + s2'

此時三個交易策略生成的交易信號會被加起來,成爲最終的交易信號,如果策略0的結果爲買入10%,策略1結果爲買入10%,策略2結果爲買入30%,則最終的結果爲買入50%

使用邏輯運算符定義blender表達式:

's0 and s1 and s2'

表示只有當交易策略1、2、3都出現交易信號的時候,纔會最終形成交易信號。如策略1的結果爲買入,策略2結果爲買入,而策略3沒有交易信號,則最終的結果爲沒有交易信號。

blender表達式中還可以包含括號和一些函數:

'max(s0, s1) + s2'

表示策略1、2的結果中最大值與策略3的結果相加,成爲最終交易信號。如果策略1的結果爲買入10%,策略2結果爲買入20%,策略3結果爲買入30%,最終的結果爲買入50%

blender 表達式中每個策略可以出現不止一次,也可以出現純數字:

'(0.5 * s0 + 1.0 * s1 + 1.5 * s2) / 3 * min(s0, s1, s2)'

上面的blender表達式表示:首先計算三個策略信號的加權平均(權重分別爲0.5、1.0、1.5),然後再乘以三個信號的最小值

blender 表達式中函數的操作參數在函數名中定義:

'clip_-0.5_0.5(s0 + s1 + s2) + pos_2_0.2(s0, s1, s2)'

上面的blender表達式定義了兩種不同的函數操作,分別得到結果後相加得到最終結果。第一個函數是範圍剪切,將三組策略信號相加後,剪切掉小於-0.5的信號值以及大於0.5的信號值,得到計算結果;第二個函數是倉位判斷函數,統計三組信號中持倉大於0.2的時間段,將其定義爲“多頭”,然後再統計每一個時間段三個策略中持多頭建議的數量,如果超過兩個策略持多頭建議,則輸出滿倉多頭,否則輸出空倉。

blender表達式中支持的函數如下:

函數

表達式示例

說明

abs

abs(s0)

絕對值函數
計算所有交易信號的絕對值
只能輸入一個交易信號

avg

avg(s0, s1, ...)

平均值函數
計算所有交易信號的平均值
輸入信號的數量不限

avgpos

avgpos_N_T(s0, s1, ...)

平均值累計函數
當交易信號爲持倉目標信號時,統計同一時間產生非空倉信號(輸出信號絕對值>T)的個數,當空頭/多頭信號的數量大於N時,輸出所有空頭/多頭信號的平均值,否則輸出0.
輸入信號的數量不限

ceil

ceil(s0)

向上取整函數
交易信號向上取整
只能輸入一個交易信號

clip

clip_U_L(s0)

範圍剪切函數
剪切超過範圍的信號值,剪切上下範圍在函數名中定義
只能輸入一個交易信號

combo

combo(s0, s1, ...)

組合值函數
輸出所有交易信號加總的值
輸入信號的數量不限

committee

cmt_N_T(s0, s1, ...)

委員會函數(等同於累計持倉函數pos))
當交易信號爲持倉目標信號時,統計同一時間產生非空倉信號(輸出信號絕對值>T)的個數數,當多頭/空頭信號的數量大於N時,輸出-1/1,否則輸出0.
輸入信號的數量不限

exp

exp(s0)

exp函數
計算e的信號次冪
只能輸入一個交易信號

floor

floor(s0)

向下取整函數
交易信號向下取整
只能輸入一個交易信號

log

log(s0)

對數函數
計算以e爲底的對數值
只能輸入一個交易信號

log10

log10(s0)

以10爲底的對數函數
計算以10爲底的對數值
只能輸入一個交易信號

max

max(s0, s1, ...)

最大值函數
計算所有交易信號的最大值
輸入信號的數量不限

min

min(s0, s1, ...)

最小值函數
計算所有交易信號的最小值
輸入信號的數量不限

pos

pos_N_T(s0, s1, ...)

累計持倉函數
當交易信號爲持倉目標信號時,統計同一時間產生非空倉信號(輸出信號絕對值>T)的個數數,當多頭/空頭信號的數量大於N時,輸出-1/1,否則輸出0.
輸入信號的數量不限

position

position_N_T(s0, s1, ...)

累計持倉函數
當交易信號爲持倉目標信號時,統計同一時間產生非空倉信號(輸出信號絕對值>T)的個數數,當多頭/空頭信號的數量大於N時,輸出-1/1,否則輸出0.
輸入信號的數量不限

pow

pow(s0, s1)

冪函數
計算第一個交易信號的第二個信號次冪即sig0^sig1
輸入信號的數量只能爲兩個

power

power(s0, s1)

冪函數
計算第一個交易信號的第二個信號次冪即sig0^sig1
輸入信號的數量只能爲兩個

sqrt

sqrt(s0)

平方根函數
交易信號的平方根
只能輸入一個交易信號

str

str_T(s0, s1, ...)

強度累計函數
將所有交易信號加總,當信號強度超過T時,輸出1,否則輸出0
輸入信號的數量不限

strength

strength_T(s0, s1, ...)

強度累計函數
將所有交易信號加總,當信號強度超過T時,輸出1,否則輸出0
輸入信號的數量不限

sum

sum(s0, s1, ...)

組合值函數
輸出所有交易信號加總的值
輸入信號的數量不限

unify

unify(s0)

均一化函數
均一化交易信號,等比縮放同一行的交易信號使每一行的總和爲1
只能輸入一個交易信號

vote

vote_N_T(s0, s1, ...)

委員會投票函數(等同於累計持倉函數)
當交易信號爲持倉目標信號時,統計同一時間產生非空倉信號(輸出信號絕對值>T)的個數數,當多頭/空頭信號的數量大於N時,輸出-1/1,否則輸出0.
輸入信號的數量不限

以下方法可以被用來設置或獲取策略的blender

operator.set_blender(blender_str=None, group_id=None)

設置blender,直接傳入一個表達式blender_str,這個表達式會被自動解析後用於組合交易策略。 在使用set_blender()的時候,可以使用group_id參數來指定這個blender是用於哪個策略組的,如果不指定group_id,則默認這個blender是用於所有策略組的。

operator.view_blender()

查看blender, 注意此時爲了便於人類閱讀,混合器表達式中的策略代碼s0,s1,s2會被自動替換爲具體的策略ID, 如下面例子所示:

>>> op = qt.Operator('dma, macd, trix')
>>> op.set_blender('(0.5 * s0 + 1.0 * s1 + 1.5 * s2) / 3 * min(s0, s1, s2)', group_id='Group_1')
>>> op.view_blender()

operator.view_blender()輸出一個字典,鍵爲策略組ID,值爲混合器表達式,其中混合器表達式中的s0,s1,s2已經被替換爲具體的策略ID:

輸出如下:

{'Group_1': '(0.5 * dma + 1.0 * macd + 1.5 * trix) / 3 * min(dma, macd, trix)'}

如果在調用view_blender()的時候指定了group_id參數,則只會輸出這個策略組的混合器表達式:

>>> op.view_blender(group_id='Group_1')

輸出如下:

'(0.5 * dma + 1.0 * macd+ 1.5 * trix) / 3 * min(dma, macd, trix)'

上面例子中的s0,s1,s2分別被dmamacdtrix代替,但如果Operator中包含多個相同的策略,它們會被自動分配不同的策略ID,以示區別:

>>> op = qt.Operator('dma, dma, dma')
>>> op.set_blender('(0.5 * s0 + 1.0 * s1 + 1.5 * s2) / 3 * min(s0, s1, s2)')
>>> op.view_blender('Group_1')

輸出如下:

'(0.5 * dma_0 + 1.0 * dma_1 + 1.5 * dma_2) / 3 * min(dma_0, dma_1, dma_2)'

blender使用示例

下面使用一個例子來演示blender的工作方式:

我們生成一個交易員對象,同時運行五個DMA交易策略,但五個交易策略分別有不同的可調參數,這時,我們可以理解爲這個交易員同時運行五個同樣的交易邏輯,但這五個交易邏輯被配置了不同的參數,因此在同樣的輸入條件下,產生不同的交易信號,意味着五個交易策略各有側重,有的擅長於抓取長期變量,有的善於追蹤短期趨勢。

下面,同樣是這五個交易策略,但我們會用三個不同的例子,來展示三種不同的混合方式,向您展示,即使完全相同的交易策略和交易參數,在同一段歷史區間,不同的混合方式同樣能影響最終的交易結果。

第一種混合方式:加權平均混合

第一種混合方式將五個交易策略的結果進行加權平均,每個交易策略的權重如下:

  • s0: 權重0.8

  • s1: 權重1.2

  • s2: 權重2.0

  • s3: 權重0.5

  • s4: 權重1.5

爲了實現上面的加權平均混合,我們可以定義如下的blender表達式,其含義是:首先將五個交易策略的結果分別乘以他們的權重,然後再除以權重之和(即5),得到加權平均的結果:

(0.8*s0+1.2*s1+2*s2+0.5*s3+1.5*s4)/5

接下來,我們將這個blender表達式設置到交易員對象中,運行策略,得到回測結果:

# 创建一个交易员对象,同时运行五个相同的dma交易策略,这些交易策略运行方式相同,但是设置不同的参数后,会产生不同的交易信号。我们通过不同的策略组合方式,得到不同的回测结果
op = qt.Operator('dma, dma, dma, dma, dma')
# 分别给五个不同的交易策略设置不同的策略参数,使他们产生不同的交易信号
op.set_parameter(stg_id=0, par_values=(132, 200, 24))
op.set_parameter(stg_id=1, par_values=(124, 187, 51))
op.set_parameter(stg_id=2, par_values=(103, 81, 16))
op.set_parameter(stg_id=3, par_values=(48, 111, 148))
op.set_parameter(stg_id=4, par_values=(104, 127, 58))

op.set_blender('(0.8*s0+1.2*s1+2*s2+0.5*s3+1.5*s4)/5')

# 运行策略
res = qt.run(op, mode=1, invest_start='20160405', invest_end='20210201')

得到回測結果報告如下:年化收益5.51%,夏普率0.44。關於回測結果報告的解讀,請參見教程第三節的相關內容。

我們在這裏並不關注如何改進交易策略的表現,而是關注同樣的交易策略在不同的混合方式下,表現出不同的回測結果,這些回測結果的差異完全來自於混合方式的不同。

====================================
|                                  |
|         BACKTEST REPORT          |
|                                  |
====================================
qteasy running mode: 1 - History back testing
time consumption for operate signal creation: 171.1 ms
time consumption for operation back testing:  3.2 ms
investment starts on      2016-04-05 15:00:00
ends on                   2021-01-29 15:00:00
Total looped periods:     4.8 years.
-------------operation summary:------------
Only non-empty shares are displayed, call 
"loop_result["oper_count"]" for complete operation summary
          Sell Cnt Buy Cnt Total Long pct Short pct Empty pct
000300.SH   370      354    724   88.1%     -0.0%     11.9%  

Total operation fee:     ¥    3,847.81
total investment amount: ¥  100,000.00
final value:              ¥  129,535.45
Total return:                    29.54% 
Avg Yearly return:                5.51%
Skewness:                         -0.90
Kurtosis:                         12.22
Benchmark return:                63.94% 
Benchmark Yearly return:         10.80%

------strategy loop_results indicators------ 
alpha:                           -0.060
Beta:                             1.311
Sharp ratio:                      0.440
Info ratio:                      -0.041
250 day volatility:               0.125
Max drawdown:                    18.49% 
    peak / valley:        2018-01-26 / 2019-01-03
    recovered on:         2019-03-05


==================END OF REPORT===================

png

如果仔細觀察上面生成的收益曲線圖,可以注意到圖示的背景上繪製了深淺不同的綠色條紋,這些條紋代表該段時間段裏的持倉比例,白色代表空倉持幣,即完全不持有任何股票,而最深的綠色代表100%持股,介於中間的綠色代表0~100%之間的持股比例,持股比例越高,條紋的顏色越深。

由於五個交易策略的輸出結果是以加權平均的形式輸出的,因此只要任意一個交易策略輸出發生改變,就會導致最終的交易信號發生改變,這也導致整個交易歷史區間的買賣次數非常頻繁(共有370次賣出操作和354次買入操作),持倉比例也在0%~100%之間頻繁波動。

從圖中可以看到,整個交易歷史區間存在着深淺不一的綠色,如果您非常仔細地檢查每一個持股區間,會發現這些區間的持股比例正好對應着當時五個交易策略的混合結果:當全部交易策略都」一致決定」全倉買入時,它們的加權平均結果就是100%買入,但只要有一個或多個策略決定持有空倉,最終加權平均的結果就是持有一定百分比的股票,這個百分比等於五個策略信號的加權平均結果。最終的結果就是持倉比例在0%~100%之間波動,完全空倉和完全滿倉的時間都不長。

這也意味着我們不能通過空倉來完全避免單邊下跌行情,不過,始終保持一定倉位卻能更好地抓住上升通道。

同時,您也應該可以觀察到,由於倉位是靈活調整的,在單邊下跌行情中的倉位(綠色的深淺幅度)明顯更低,上漲行情倉位更高,這正是我們所期望的。

接下來,讓我們來看下一種完全不同的混合方式:

第二種混合方式:委員會投票

這種方式讓同樣五個交易策略組成一個」委員會」,通過平等投票來決定倉位,且倉位必須是」非黑即白」的兩種結果之一:要麼滿倉,要麼空倉。其表達式如下:

這時我們需要用到blender_str支持的特殊函數“委員會函數”,cmt_N_T(s0, s1, ...),這個函數的含義是:當同一時間產生非空倉信號(輸出信號絕對值>T)的策略數量大於N時,輸出滿倉多頭(1)或滿倉空頭(-1),否則輸出空倉(0)。因此當N=3,T=0時,表達式如下:

cmt_3_0(s0, s1, s2, s3, s4)

這就相當於我們創建了一個由五個交易策略組成的委員會,只有當同一時間至少有三個策略輸出多頭滿倉建議時,委員會纔會輸出多頭滿倉建議,否則輸出空倉建議。

因此最終的結果就是持倉比例要麼是100%(當三個或以上的策略輸出滿倉建議時),要麼是0%(當三個或以上的策略沒有輸出滿倉建議時)。在單邊下跌行情中,持倉比例要麼是100%,要麼是0%,沒有任何中間狀態。交易員Operator相當於執行了一個由五人投資委員會的投票結果來決定持倉的交易策略。委員會的成員各有自己的想法(因爲他們的參數不同),但最終的決策是由多數人決定的。

op.set_blender('cmt_3_0(s0, s1, s2, s3, s4)')  # 将委员会混合函数设置为blender表达式
# 运行策略
res = qt.run(op, mode=1)

得到結果如下:年化收益6.43,夏普率0.487

====================================
|                                  |
|         BACKTEST REPORT          |
|                                  |
====================================
qteasy running mode: 1 - History back testing
time consumption for operate signal creation: 179.3 ms
time consumption for operation back testing:  3.2 ms
investment starts on      2016-04-05 15:00:00
ends on                   2021-01-29 15:00:00
Total looped periods:     4.8 years.
-------------operation summary:------------
Only non-empty shares are displayed, call 
"loop_result["oper_count"]" for complete operation summary
          Sell Cnt Buy Cnt Total Long pct Short pct Empty pct
000300.SH    10       9      19   57.2%     -0.0%     42.8%  

Total operation fee:     ¥      452.82
total investment amount: ¥  100,000.00
final value:              ¥  135,022.14
Total return:                    35.02% 
Avg Yearly return:                6.43%
Skewness:                         -0.74
Kurtosis:                         11.60
Benchmark return:                63.94% 
Benchmark Yearly return:         10.80%

------strategy loop_results indicators------ 
alpha:                           -0.034
Beta:                             1.001
Sharp ratio:                      0.487
Info ratio:                      -0.026
250 day volatility:               0.138
Max drawdown:                    22.60% 
    peak / valley:        2019-04-19 / 2020-07-24
    recovered on:         Not recovered!


==================END OF REPORT===================

png

您一定看到了,這一次的輸出結果跟前一次有了很大區別:上一次回測時持倉比例是漸變的(投資收益圖背景綠色條帶的顏色深淺代表持倉的比例),而這一次是非黑即白的,要麼是滿倉,要麼是空倉,而且交易的次數也大大減少,一共只有九次買進和十次賣出。您如果仔細分析交易日誌,會發現只有當三個交易策略舉手贊成滿倉時,纔會滿倉,其餘時間空倉。因此在單邊下跌行情中的收益曲線是一條直線。但滿倉時如果股票下跌,也沒有辦法通過適當減倉來降低損失。

接下來,我們仍然使用這個委員會,但是現在只要有兩票投滿倉時,最終就會滿倉:

第三種混合方式:委員會投票

第三種組合方式:同樣是委員會策略,但輸出滿倉多頭的投票門檻變爲2票,即只要有兩個策略認爲輸出多頭即可

op.set_blender('pos_2_0(s0, s1, s2, s3, s4)')
# 运行策略
res = qt.run(op, mode=1)

得到結果如下:年化收益8.46%,夏普率0.612

====================================
|                                  |
|         BACKTEST REPORT          |
|                                  |
====================================
qteasy running mode: 1 - History back testing
time consumption for operate signal creation: 180.0 ms
time consumption for operation back testing:  3.5 ms
investment starts on      2016-04-05 15:00:00
ends on                   2021-01-29 15:00:00
Total looped periods:     4.8 years.
-------------operation summary:------------
Only non-empty shares are displayed, call 
"loop_result["oper_count"]" for complete operation summary
          Sell Cnt Buy Cnt Total Long pct Short pct Empty pct
000300.SH    12       10     22   71.5%     -0.0%     28.5%  

Total operation fee:     ¥      548.27
total investment amount: ¥  100,000.00
final value:              ¥  147,914.95
Total return:                    47.91% 
Avg Yearly return:                8.46%
Skewness:                         -0.64
Kurtosis:                          8.61
Benchmark return:                63.94% 
Benchmark Yearly return:         10.80%

------strategy loop_results indicators------ 
alpha:                           -0.018
Beta:                             1.001
Sharp ratio:                      0.612
Info ratio:                      -0.018
250 day volatility:               0.154
Max drawdown:                    22.34% 
    peak / valley:        2019-04-19 / 2020-07-24
    recovered on:         Not recovered!


==================END OF REPORT===================

png

對比第二張圖表和第三張圖表,您可以發現,滿倉的區間明顯變長了,這是因爲原來需要三張贊成票才能滿倉的策略,現在只要兩張贊成票就可以了,因此更容易出現滿倉的結果

8.4. 本篇回顧

好了,相信到了這裏,您應該會對交易策略的混合有了一個初步的理解了。我們的教程還會繼續,qteasy還有更多的方式實現您希望的交易策略,實際上,儘管qteasy的內核被設計爲一個有利於高速回測和高速執行的向量化的策略內核,但仍然考慮到了足夠的靈活性,理論上您可以實現您所設想的任何類型的交易策略。

同時,qteasy的回測框架也做了相當多的特殊設計,可以完全避免您無意中在交易策略中導入」未來函數」,確保您的交易策略在回測時完全基於過去的數據,同時也使用了很多預處理技術以及JIT技術對內核關鍵函數進行了編譯,以實現不亞於C語言的運行速度。

不過,爲了實現理論上無限可能的交易策略,僅僅使用內置交易策略以及策略混合就不一定夠用了,一些特定的交易策略,或者一些特別複雜的交易策略是無法通過內置策略混合而成的,這就需要我們使用qteasy提供的Strategy基類,基於一定的規則創建一個自定義交易策略了。

qteasy教程的下一節,我們將用一個例子來介紹如何創建一個自定義交易策略,如何定義策略的基本參數,如何定義策略所需的數據類型,如何設置交易信號的生成邏輯。。。