量化交易:大盤擬合穩定突破策略

阿布量化發表於2019-02-28

作者: 阿布
阿布量化版權所有 未經允許 禁止轉載

abu量化系統github地址(歡迎+star)

本節ipython notebook

上一節講解的是A股市場的回測,本節講解港股市場的回測示例。

買入因子,賣出因子等依然使用相同的設定,如下所示:

from abupy import AbuFactorAtrNStop, AbuFactorPreAtrNStop, AbuFactorCloseAtrNStop, AbuFactorBuyBreak, ABuProgress
from abupy import abu, tl, get_price, ABuSymbolPd, EMarketTargetType, AbuMetricsBase, AbuHkUnit, six

# 設定初始資金數
read_cash = 1000000

# 買入因子依然延用向上突破因子
buy_factors = [{`xd`: 60, `class`: AbuFactorBuyBreak},
               {`xd`: 42, `class`: AbuFactorBuyBreak}]

# 賣出因子繼續使用上一節使用的因子
sell_factors = [
    {`stop_loss_n`: 1.0, `stop_win_n`: 3.0,
     `class`: AbuFactorAtrNStop},
    {`class`: AbuFactorPreAtrNStop, `pre_atr_n`: 1.5},
    {`class`: AbuFactorCloseAtrNStop, `close_atr_n`: 1.5}
]複製程式碼

1. 港股市場的回測示例

擇時股票池使用沙盒快取資料中的如下股票:

港股市場:

  • 中國恆大(hk03333)
  • 騰訊控股(hk00700)
  • 長城汽車(hk02333)
  • 中國信達(hk01359)
  • 復星國際(hk00656)
  • 金山軟體(hk03888)
  • 中國平安(hk02318)
  • 恆生指數(hkHSI)

程式碼如下所示:

# 擇時股票池
choice_symbols = [`hk03333`, `hk00700`, `hk02333`, `hk01359`, `hk00656`, `hk03888`, `hk02318`]複製程式碼

與A股市場類似首先將abupy量化環境設定為港股,程式碼如下所示:

# 設定市場型別為港股
abupy.env.g_market_target = EMarketTargetType.E_MARKET_TARGET_HK複製程式碼
abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash,
                                                   buy_factors,
                                                   sell_factors,
                                                   n_folds=6,
                                                   choice_symbols=choice_symbols)
AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)複製程式碼
買入後賣出的交易數量:202
買入後尚未賣出的交易數量:3
勝率:50.4950%
平均獲利期望:12.9899%
平均虧損期望:-5.9130%
盈虧比:2.4770
策略收益: 85.8217%
基準收益: 18.5928%
策略年化收益: 14.6923%
基準年化收益: 3.1830%
策略買入成交比例:90.7317%
策略資金利用率比例:32.5890%
策略共執行1472個交易日複製程式碼

從上收益曲線可以看到在大概在2015年上旬的有一次收益猛烈的拉昇出現,下面輸出top10筆收益,程式碼如下所示:

orders_pd = abu_result_tuple.orders_pd
top_10 = orders_pd.sort_values(by=`profit`)[::-1].dropna(subset=[`sell_price`])[:10]
top_10.filter([`symbol`, `buy_date`, `buy_price`, `sell_date`, `sell_price`, `profit`])複製程式碼

從輸出可以清晰的看出,top3的高收益交易都發生在2015年3月份買入,4月份賣出,3筆收益都非常高。

結合HSI基準在3-4月之間也有一個拉昇,這裡的收益曲線猛烈拉昇也可以理解了。

在《量化交易之路》中寫過:很多時候我們編寫出一個策略後發現預期收益非常高,遇到這樣的情況,如果你是認真的,那就應該考慮怎麼降低收益,降低的不僅是收益也是風險,對應的提高的將是系統的穩定性。

有很多朋友問怎樣才能降低風險,提高穩定性,比如上面的回測,是不是要把3-4月份這次收益猛烈的拉昇濾除才叫降低風險呢?

當然不是,上面的3-4月份拉昇收益是順勢的收益,如果要把這段過濾將是逆勢行為。

有些朋友問降低風險是不是需要通過買對衝來實現?

其實也不建議個人投資者買對衝,因為對衝的產品一般都是和時間相關的,比如期權,期貨,增加一個時間維度會將整個交易複雜度提升幾個數量級,並不建議一般交易者進行這種對衝,特別是以股票為主要投資物件的一般交易者(但是可以在策略中做相同品種的相關性對衝,在之後的章節會有完成相關性策略示例)。

我認為最適合一般投資者的的提高穩定性的方法就是降低交易頻率,比如ump裁判攔截,主要就是通過學習策略中的失敗交易,從不同角度進行學習交易特點,指導決策攔截交易,攔截了大量的交易可以節省佣金,更重大的意義在於邊裁避免了重大的風險。

本節講解在不考慮使用ump系統的情況下,示例如何通過硬編碼策略來降低交易頻率,提高系統的穩定性。

2. 優化策略,提高系統的穩定性

下面的示例將在突破策略AbuFactorBuyBreak基礎上進行降低交易頻率,提高系統的穩定性處理。

首先看下面使用AbuTLine.show_least_valid_poly示例,通過get_price獲取港股大盤HSI一個月收盤價格資料,然後使用AbuTLine.show_least_valid_poly進行poly判斷,它返回的結果是檢測至少poly次擬合曲線可以代表原始曲線y的走勢,如下所示顯示1次
曲線即可以代表20160301-20160401這一個月的擬合走勢,如下所示:

hsi = get_price(`hkHSI`, start_date=`20160301`, end_date=`20160401`)
tl.AbuTLine(hsi.price, `HSI`).show_least_valid_poly()複製程式碼
1複製程式碼

驗證poly(預設=1)次多項式擬合迴歸的趨勢曲線是否能代表原始曲線y的走勢主要程式碼,請閱讀原始碼AbuTLine

如上20160301-20160401使用一次擬合曲線即可以代表原始曲線,下面向前推一個月20160128-20160229:

hsi = get_price(`hkHSI`, start_date=`20160128`, end_date=`20160229`)
tl.AbuTLine(hsi.price, `HSI`).show_least_valid_poly()複製程式碼
4複製程式碼

如上結果顯示至少需要最少4次以上的擬合才可以代表原始曲線,實際上poly的次數越大,越代表此段週期內波動比較大,從上視覺化圖也可清晰看出,上面需要4次擬合才能代表的走勢明顯處在震盪走勢中。

下面根據上面這個判斷依據編寫策略基本,策略和之前一直使用的AbuFactorBuyBreak基本一樣,不同的是實現fit_month方法(fit_month即在回測策略中每一個月執行一次的方法), 在fit_month中使用show_least_valid_poly計算這個月大盤價格走勢的least poly值,如果大於一個閥值比如2,就說明最近大盤走勢震盪,那麼就封鎖交易,直到在fit_month中計算出的least poly值小於閥值,解鎖交易,程式碼實現如下所示:

from abupy import AbuFactorBuyBase, BuyCallMixin

class AbuSDBreak(AbuFactorBuyBase, BuyCallMixin):
    def _init_self(self, **kwargs):        
        # 外部可以設定poly閥值,self.poly在fit_month中和每一個月大盤計算的poly比較,若是大盤的poly大於poly認為走勢震盪
        self.poly = kwargs.pop(`poly`, 2)
        # 是否封鎖買入策略進行擇時交易
        self.lock = False

        # 下面的程式碼和AbuFactorBuyBase的實現一摸一樣
        self.xd = kwargs[`xd`]
        self.skip_days = 0
        self.factor_name = `{}:{}`.format(self.__class__.__name__, self.xd)

    def fit_month(self, today):
        # fit_month即在回測策略中每一個月執行一次的方法
        # 策略中擁有self.benchmark,即交易基準物件,AbuBenchmark例項物件,benchmark.kl_pd即對應的市場大盤走勢
        benchmark_df = self.benchmark.kl_pd
        # 拿出大盤的今天
        benchmark_today = benchmark_df[benchmark_df.date == today.date]
        if benchmark_today.empty:
            return 0
        # 要拿大盤最近一個月的走勢,準備切片的start,end
        end_key = int(benchmark_today.ix[0].key)
        start_key = end_key - 20
        if start_key < 0:
            return 0

        # 使用切片切出從今天開始向前20天的資料
        benchmark_month = benchmark_df[start_key:end_key+1]
        # 通過大盤最近一個月的收盤價格做為引數構造AbuTLine物件
        benchmark_month_line = tl.AbuTLine(benchmark_month.close, `benchmark month line`)
        # 計算這個月最少需要幾次擬合才能代表走勢曲線
        least = benchmark_month_line.show_least_valid_poly(show=False)

        if least >= self.poly:
            # 如果最少的擬合次數大於閥值self.poly,說明走勢成立,大盤非震盪走勢,解鎖交易
            self.lock=False
        else:
            # 如果最少的擬合次數小於閥值self.poly,說明大盤處於震盪走勢,封鎖策略進行交易
            self.lock=True

    def fit_day(self, today):
        if self.lock:
            # 如果封鎖策略進行交易的情況下,策略不進行擇時
            return None

        # 下面的程式碼和AbuFactorBuyBase的實現一摸一樣
        day_ind = int(today.key)
        if day_ind < self.xd - 1 or day_ind >= self.kl_pd.shape[0] - 1:
            return None
        if self.skip_days > 0:
            self.skip_days -= 1
            return None
        if today.close == self.kl_pd.close[day_ind - self.xd + 1:day_ind + 1].max():
            self.skip_days = self.xd
            return self.make_buy_order(day_ind)
        return None複製程式碼

下面重新組成買入因子字典,使用剛剛編寫的買入策略AbuSDBreak,突破週期還是60,42:

buy_factors = [{`xd`: 60, `class`: AbuSDBreak}, 
               {`xd`: 42, `class`: AbuSDBreak}]複製程式碼
# 使用run_loop_back執行策略
abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash,
                                                   buy_factors,
                                                   sell_factors,
                                                   choice_symbols=choice_symbols,
                                                   n_folds=6)
AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)複製程式碼
買入後賣出的交易數量:122
買入後尚未賣出的交易數量:3
勝率:61.4754%
平均獲利期望:11.5062%
平均虧損期望:-4.7367%
盈虧比:3.9707
策略收益: 91.2822%
基準收益: 18.5928%
策略年化收益: 15.6271%
基準年化收益: 3.1830%
策略買入成交比例:98.4000%
策略資金利用率比例:22.8984%
策略共執行1472個交易日複製程式碼

直觀上的收益曲線變的平緩了些,交易數量減少了接近40%。

使用AbuSDBreak,降低交易頻率,提高系統的穩定性處理的回測結果:

  • 買入後賣出的交易數量:123
  • 勝率:60.9756%
  • 平均獲利期望:12.3589%
  • 平均虧損期望:-4.6619%
  • 盈虧比:4.2144
  • 策略收益: 100.1270%
  • 基準收益: 18.5928%
  • 策略年化收益: 17.1413%
  • 基準年化收益: 3.1830%
  • 策略買入成交比例:98.4127%
  • 策略資金利用率比例:22.3742%
  • 策略共執行1472個交易日

本節時使用AbuFactorBuyBreak的回測結果:

  • 買入後賣出的交易數量:201
  • 勝率:50.7463%
  • 平均獲利期望:13.2619%
  • 平均虧損期望:-5.9121%
  • 盈虧比:2.6158
  • 策略收益: 93.7512%
  • 基準收益: 18.5928%
  • 策略年化收益: 16.0498%
  • 基準年化收益: 3.1830%
  • 策略買入成交比例:91.1765%
  • 策略資金利用率比例:32.3410%
  • 策略共執行1472個交易日

上面這種提高系統穩定性的方法需要在策略新增一些交易規則,閥值進行交易的篩選過濾,後面的章節會示例使用abupy中ump裁判攔截系統,其優點是:

  1. 不需要在具體策略中硬編碼
  2. 不需要人工設定閥值,即且使得程式碼邏輯清晰
  3. 分離基礎策略和策略優化監督模組,提高靈活度和適配性
  4. 發現策略中隱藏的交易策略問題
  5. 可以通過不斷的學習新的交易資料(給每一個裁判看更多的比賽錄影,提高比賽錄影水準),提高攔截水平

讀者可自行使用上面的AbuSDBreak做為上一節A股回測的策略,重新進行回測,從結果你可以發現與本節港股的回測類似,交易頻率降低,交易成功概率提高。

3 將優化策略的`策略`做為類裝飾器進行封裝

在上面說過使用ump模組的優點有個是:分離基礎策略和策略優化監督模組,提高靈活度和適配性,

實際上上面使用的AbuSDBreak中的優化策略可以封裝在一個裝飾器類中,之後編寫的所有策略都可以選擇是否通過這個類裝飾器進行策略優化,程式碼如下所示:

class AbuLeastPolyWrap(object):
    """
        做為買入因子策略裝飾器封裝show_least_valid_poly對大盤震盪大的情況下封鎖交易
    """

    def __call__(self, cls):
        """只做為買入因子策略類的裝飾器"""

        if isinstance(cls, six.class_types):
            # 只做為類裝飾器使用

            init_self = cls._init_self
            org_fit_day = cls.fit_day

            # fit_month不是必須實現的
            org_fit_month = getattr(cls, `fit_month`, None)
            def init_self_wrapped(*args, **kwargs):
                # 拿出被裝飾的self物件
                warp_self = args[0]
                # 外部可以設定poly閥值,self.poly在fit_month中和每一個月大盤計算的poly比較,
                # 若是大盤的poly大於poly認為走勢震盪
                warp_self.poly = kwargs.pop(`poly`, 2)
                # 是否封鎖買入策略進行擇時交易
                warp_self.lock = False
                # 呼叫原始的_init_self
                init_self(*args, **kwargs)

            def fit_day_wrapped(*args, **kwargs):
                # 拿出被裝飾的self物件
                warp_self = args[0]
                if warp_self.lock:
                    # 如果封鎖策略進行交易的情況下,策略不進行擇時
                    return None
                return org_fit_day(*args, **kwargs)

            def fit_month_wrapped(*args, **kwargs):
                warp_self = args[0]
                today = args[1]
                # fit_month即在回測策略中每一個月執行一次的方法
                # 策略中擁有self.benchmark,即交易基準物件,AbuBenchmark例項物件,benchmark.kl_pd即對應的市場大盤走勢
                benchmark_df = warp_self.benchmark.kl_pd
                # 拿出大盤的今天
                benchmark_today = benchmark_df[benchmark_df.date == today.date]
                if benchmark_today.empty:
                    return 0
                # 要拿大盤最近一個月的走勢,準備切片的start,end
                end_key = int(benchmark_today.ix[0].key)
                start_key = end_key - 20
                if start_key < 0:
                    return 0

                # 使用切片切出從今天開始向前20天的資料
                benchmark_month = benchmark_df[start_key:end_key+1]
                # 通過大盤最近一個月的收盤價格做為引數構造AbuTLine物件
                benchmark_month_line = tl.AbuTLine(benchmark_month.close, `benchmark month line`)
                # 計算這個月最少需要幾次擬合才能代表走勢曲線
                least = benchmark_month_line.show_least_valid_poly(show=False)

                if least >= warp_self.poly:
                    # 如果最少的擬合次數大於閥值self.poly,說明走勢成立,大盤非震盪走勢,解鎖交易
                    warp_self.lock=False
                else:
                    # 如果最少的擬合次數小於閥值self.poly,說明大盤處於震盪走勢,封鎖策略進行交易
                    warp_self.lock=True

                if org_fit_month is not None:
                    return org_fit_month(*args, **kwargs)

            cls._init_self = init_self_wrapped
            init_self_wrapped.__name__ = `_init_self`

            cls.fit_day = fit_day_wrapped
            fit_day_wrapped.__name__ = `fit_day`

            cls.fit_month = fit_month_wrapped
            fit_month_wrapped.__name__ = `fit_month`

            return cls
        else:
            raise TypeError(`AbuLeastPolyWrap just for class warp`)複製程式碼

下面編寫一個很簡單的策略,連續漲兩天就買入股票,示例AbuLeastPolyWrap的使用:

from abupy import AbuFactorBuyBase, BuyCallMixin

@AbuLeastPolyWrap()
class AbuTwoDayBuy(AbuFactorBuyBase, BuyCallMixin):
    """示例AbuLeastPolyWrap,混入BuyCallMixin,即向上突破觸發買入event"""

    def _init_self(self, **kwargs):
        """簡單示例什麼都不編寫了"""
        pass

    def fit_day(self, today):
        """
        針對每一個交易日擬合買入交易策略,今天漲,昨天漲就買
        :param today: 當前驅動的交易日金融時間序列資料
        :return:
        """
        # key是金融時間序列索引
        day_ind = int(today.key)
        # 忽略不符合買入的天(統計週期內前第1天及最後一天)
        if day_ind == 0 or day_ind >= self.kl_pd.shape[0] - 1:
            return None

        # 今天的漲幅
        td_change = today.p_change
        # 昨天的漲幅
        yd_change = self.kl_pd.ix[day_ind - 1].p_change

        if td_change > 0 and  yd_change > 0 and td_change > yd_change:
            # 連續漲兩天, 且今天的漲幅比昨天還高 ->買入
            return self.make_buy_order(day_ind)
        return None複製程式碼

如下通過字典裝載買入因子,只使用AbuTwoDayBuy一個買入因子,字典中設定poly=2, 即多於2次擬合的趨勢都認為大盤是處於震盪走勢,封鎖交易行為,因為基礎策略AbuTwoDayBuy中的實現屬於頻繁買入策略,所以AbuLeastPolyWrap裝飾器裝飾這個策略的目的就是控制降低交易頻率,提過穩定性。

備註: 讀者可嘗試切換poly值1-4來進行回測,檢視回測效果

buy_factors = [{`class`: AbuTwoDayBuy, `poly`: 2}]複製程式碼

最後依然使用abu.run_loop_back對交易進行回測:

abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash,
                                                   buy_factors,
                                                   sell_factors,
                                                   choice_symbols=choice_symbols,
                                                   n_folds=6)
AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)複製程式碼
買入後賣出的交易數量:598
買入後尚未賣出的交易數量:13
勝率:47.6589%
平均獲利期望:12.2090%
平均虧損期望:-7.2464%
盈虧比:1.7095
策略收益: 108.1026%
基準收益: 18.5928%
策略年化收益: 18.5067%
基準年化收益: 3.1830%
策略買入成交比例:55.3191%
策略資金利用率比例:49.3860%
策略共執行1472個交易日複製程式碼

讀者可以看到AbuTwoDayBuy這個策略有多麼簡單甚至簡陋,但是配合AbuLeastPolyWrap進行回測的效果也還可以。

有很多做量化的人聲稱自己的策略多麼複雜,多麼nb,裡面有多少高科技,但是就是不能對外公佈,公佈了就沒有效果了,還請你原諒它。

在我看來簡單的基礎策略是最好的,簡單策略和複雜策略輸出的都只是什麼時候買,什麼時候賣。

基礎策略追求的就應該是簡單(可以一句話說明你的基礎策略),不要刻意追求複雜的策略,對量化策略失敗結果的人工分析,通過外層輔助優化策略,指導策略,讓策略自我進行學習,自我調整才是應該追求複雜的地方。

在《量化交易之路》中反覆強調:投資的目標需要有一個比較準確的預測結果,投機的目標是獲取交易概率優勢,量化交易更傾向於投機範疇,預測肯定了確定性,概率優勢不需要肯定確定性,對確定性認識的差異導致交易者最終的交易行為產生根本區別,複雜的策略和簡單的策略都對應著概率優勢,但是對於策略本身複雜的策略並不一定比簡單的策略有效。

對於港股交易還有一個相對其它市場特殊的地方,即每手的數量,A股市場每手的數量是定值100,美股一股也可以買賣,港股是每一個交易品種都有自己對應的每手數量,下面使用AbuHkUnit對每手數量進行查詢:

unit = AbuHkUnit()
{symbol: unit.query_unit(symbol) for symbol in choice_symbols}複製程式碼
{`hk00656`: 500,
 `hk00700`: 100,
 `hk01359`: 1000,
 `hk02318`: 500,
 `hk02333`: 500,
 `hk03333`: 1000,
 `hk03888`: 1000}複製程式碼

可以看到每一個股票的unit都是不同的,在abupy內部會根據資金管理計算出來的買入數量和對應股票的unit進行對比,不滿足一手的不能成交,其它市場的實現也類似。

abu量化文件目錄章節

  1. 擇時策略的開發
  2. 擇時策略的優化
  3. 滑點策略與交易手續費
  4. 多支股票擇時回測與倉位管理
  5. 選股策略的開發
  6. 回測結果的度量
  7. 尋找策略最優引數和評分
  8. A股市場的回測
  9. 港股市場的回測
  10. 比特幣,萊特幣的回測
  11. 期貨市場的回測
  12. 機器學習與比特幣示例
  13. 量化技術分析應用
  14. 量化相關性分析應用
  15. 量化交易和搜尋引擎
  16. UMP主裁交易決策
  17. UMP邊裁交易決策
  18. 自定義裁判決策交易
  19. 資料來源
  20. A股全市場回測
  21. A股UMP決策
  22. 美股全市場回測
  23. 美股UMP決策

abu量化系統文件教程持續更新中,請關注公眾號中的更新提醒。

更多關於量化交易相關請閱讀《量化交易之路》

更多關於量化交易與機器學習相關請閱讀《機器學習之路》

更多關於abu量化系統請關注微信公眾號: abu_quant

相關文章