利用Python檢驗你的策略參數是否過擬合(轉)


過擬合現象

一般來說,量化研究員在優化其交易策略參數時難免會面臨這樣一個問題:優化過后的策略在樣本內表現一般來說均會超過其在樣本外的表現,即參數過擬合。
對於參數優化來說,由於優化時存在噪音,過擬合是不可避免的現象。然而為了追求策略的穩定性,我們應當盡可能地使過擬合風險最小化。     為了檢測在一個策略的參數優化過程中的過擬合風險,David H. Bailey等人在2015年發表了一篇名為《THE PROBABILITY OF BACKTEST OVERFITTING》的文章,
給出了一種估計參數過擬合概率的方法,本文將對此方法作較為詳細的闡述。

主要思路

David H. Bailey等人的主要思路是這樣的:對於一個策略的參數優化過程來說,我們能夠獲得一系列的樣本內以及樣本外的策略收益率序列,
並能夠使用它們計算出策略的評價指標,如夏普比率,收益回撤比等等。是故,他們定義了這樣一個概率空間(T,F,P) ,其中T為樣本內指標與樣本外指標的組合組成的集合,
如(樣本內夏普比,相應的樣本外夏普比)。在這樣一個概率空間內,我們可以認為當我們選擇最優的樣本內參數時,我們得到的樣本外策略表現劣於平均樣本外策略表現的概率,即為參數過擬合的概率。

 

主要框架

    為了計量過擬合的概率,David H. Bailey等人使用了一種類似交叉驗證的方法來對一個經過參數優化的策略回報矩陣重采樣。
第一步

    我們在其參數空間上隨機采樣,得到N組不同的參數以及它們所對應的收益率序列,將其拼接成一個大矩陣M,其形狀為TxN,T即是策略的回測時長。
第二步

    我們將矩陣M以一定的步長L按行分割為S個不同的區塊,如從第1行到第L行為第一個區塊,第L+1行到第2L行為第二個區塊等。
第三步

    我們對這些不同的小矩陣隨機分為兩組,一組代表隨機抽樣產生的樣本內組,另一組便是樣本外組。我們將這些分塊矩陣重新按照時間順序拼接起來,得到了抽樣的樣本內收益率序列以及相應的抽樣的樣本外收益率序列。


第四步

    我們計算重新抽樣后N個不同參數的策略的樣本內收益率序列評價指標(如夏普比率),以及相應的N個樣本外收益率序列評價指標。計算完成后,我們需要觀察樣本內最優策略對應的樣本外策略的表現。在這里,我們使用Rank以及Relative Rank = Rank / (N + 1)來評價樣本外策略的表現。此外,我們還使用Logit = Log(Relative Rank / (1 - Relative Rank))作為樣本外策略表現的評價指標,注意到它是關於Relative Rank的增函數,是故樣本外策略表現越好,其評價值便越大。此外,我們也能夠獲得樣本內的最優夏普比率以及樣本外相應的夏普比率。


第五步

    我們需要估計第四步中Logit統計量的分布,是故我們需要對第三步~第四步進行重復操作,操作的次數由我們自己決定,次數越多,其估計所得分布越穩健。

代碼實現

    本文使用Python3 + WinPython初步實現了該方法,其所用到的包主要為numpy。此外,對於結果的可視化展示以及進一步分析還使用了pandas,matplotlib,seaborn以及sklearn。

    首先,實現該方法的代碼如下:


# Overfitting Test 
def PBOTest(rets,SampleTimes,block_len): 

''' 
Probability of Overfitting Test Based On Return Matrix and Sharpe Ratio Inputs: 
rets: Return Matrix with shape of TxN indicating N backtesting result using different params 
SampleTimes: Overfitting Indicator Sample 
Size block_len: Bootstrap Block Length Outputs: 
PBO: Probability of Overffiting 
POS: Probability of Getting Loss OutOfSample 
rd: Overfitting Indicator Logit 
ratios: Sharpe Ratios of Optimal InSample and of respective OutOfSample 
''' 

# Compute Through the Framework 
    N = rets.shape[1] 
    S = len(rets)//block_len 
    M = rets[:(block_len*S)] 
    Ms = M.reshape((S,block_len,N)) 
    training_part_block = S//2 + S%2 
    rd = np.zeros(SampleTimes) 
    ratios = np.zeros((SampleTimes,2)) 
    for i in range(SampleTimes): 
        rnd_num = np.random.rand(S).argsort() 
        TPartOrder = np.sort(rnd_num[:training_part_block]) 
        VPartOrder = np.sort(rnd_num[training_part_block:]) 
        TJ = Ms[TPartOrder].reshape((training_part_block*block_len,N)) 
        VJ = Ms[VPartOrder].reshape(((S-training_part_block)*block_len,N)) 
        TSharpe = np.mean(TJ,axis = 0)/np.std(TJ,axis = 0) 
        VSharpe = np.mean(VJ,axis = 0)/np.std(VJ,axis = 0) 
        Vrelative_rank = (1 + VSharpe.argsort().argsort())/(N + 1) 
        # Compute rd 
        T_n_best = np.argmax(TSharpe) 
        VT_n_best_rank = Vrelative_rank[T_n_best] 
        logit = np.log(VT_n_best_rank/(1-VT_n_best_rank)) 
        rd[i] = logit 
        # Compute Ratio 
        ratios[i,0] = TSharpe[T_n_best] 
        ratios[i,1] = VSharpe[T_n_best] 
    PBO = np.sum(rd<=0) / SampleTimes 
    POS = np.sum(ratios[:,1] <= 0) / SampleTimes 
return PBO,POS,rd,ratios
View Code

我們來進一步分析一下這段代碼:

    N = rets.shape[1] 
    S = len(rets)//block_len 
    M = rets[:(block_len*S)] 
    Ms = M.reshape((S,block_len,N)) 
    training_part_block = S//2 + S%2 
    rd = np.zeros(SampleTimes) 
    ratios = np.zeros((SampleTimes,2)) 

這一段代碼定義了回測中我們從參數空間抽取參數進行回測的次數N,以及區塊的長度block_len和區塊的個數S,並且如果rets的行不能整除S,我們便舍去除不盡的部分,最后得到的矩陣為M。Ms為一個三維矩陣,是經過分塊的矩陣的組合。training_part_block是我們在第三步中需要抽取的小矩陣的個數。rd,ratios分別是最終結果統計量Logit以及夏普比率的預分配空間。

        rnd_num = np.random.rand(S).argsort() 
        TPartOrder = np.sort(rnd_num[:training_part_block]) 
        VPartOrder = np.sort(rnd_num[training_part_block:]) 
        TJ = Ms[TPartOrder].reshape((training_part_block*block_len,N)) 
        VJ = Ms[VPartOrder].reshape(((S-training_part_block)*block_len,N))     

 

這一段代碼主要實現了框架中的第三步以及第四步,如重新采樣等。首先我們生成一串長度為S的隨機數,利用argsort函數得到其下標的排序rnd_num。其次,我們利用前training_part_block個下標作為組成樣本內矩陣的下標TPartOrder,並且重新拼接形成TJ。VPartOrder以及VJ同理。

        TSharpe = np.mean(TJ,axis = 0)/np.std(TJ,axis = 0) 
        VSharpe = np.mean(VJ,axis = 0)/np.std(VJ,axis = 0) 
        Vrelative_rank = (1 + VSharpe.argsort().argsort())/(N + 1)     

然后,我們計算樣本內所有策略的夏普比率TSharpe以及樣本外所有策略的夏普比率VSharpe,並且計算得到代表樣本外策略表現的Relative Rank的Vrelative_rank。

        # Compute rd 
        T_n_best = np.argmax(TSharpe) 
        VT_n_best_rank = Vrelative_rank[T_n_best] 
        logit = np.log(VT_n_best_rank/(1-VT_n_best_rank)) 
        rd[i] = logit 
        # Compute Ratio 
        ratios[i,0] = TSharpe[T_n_best] 
        ratios[i,1] = VSharpe[T_n_best]     

接下來,我們能夠計算得到最優樣本內策略對應的樣本外Realative Rank以及Logit統計量,並且將其賦值給預先分配好的空間。

    PBO = np.sum(rd<=0) / SampleTimes 
    POS = np.sum(ratios[:,1] <= 0) / SampleTimes 

最后,我們計算出過擬合的概率PBO,即為Logit統計量小於0的概率。POS為樣本外策略夏普比率為負的概率,即優化出虧損策略的概率。

框架測試

    那最后讓我們來嘗試一下這一個框架吧。我們首先下載得到上證指數從2010年1月1日到2017年5月16日之間的日行情,利用pandas進行讀入。我們使用簡單的均線策略進行測試:如快線上穿慢線即做多,快線下穿慢線做空。其參數為快線的計算長度以及慢線的計算長度。暫時不設止損線。策略的代碼如下:



def MA(close,num = 5): 
    res = [close[0]*1] 
    for i in range(1,len(close)): 
        if i >= num: 
            res.append((res[-1]*num - close[i-num] + close[i])/num) 
        else: 
            res.append((res[-1]*i + close[i])/(i+1)) 
        res = np.array(res) 
        return res
        
        
def backtest(data,params = [],num = 100,money = 1e6): 
    Fastlen,Slowlen,thdAB,stoploss = params 
    high = data['high'].values 
    low = data['low'].values 
    close = data['close'].values 
    hlen = len(close) 
    profit = np.zeros(hlen) 
    Ama = MA(close, Fastlen) 
    Bma = MA(close, Slowlen) 
    opened = False 
    long = False 
    opened_price = 0 
    for i in range(hlen): 
        if not opened: 
            if Ama[i]/Bma[i] - 1 > thdAB: 
                opened = True 
                long = True 
                opened_price = close[i] 
                profit[i] = -close[i]*0.0002*num 
            elif Ama[i]/Bma[i] - 1 < -thdAB: 
                opened = True 
                long = False 
                opened_price = close[i] 
                profit[i] = -close[i]*0.0012*num 
        else: 
            if long: 
                profit[i] = (close[i] - close[i-1])*num 
                if Ama[i] < Bma[i] or low[i]/opened_price - 1 < -stoploss: 
                    opened = False 
                    profit[i] = profit[i] - close[i]*num*0.0012 
            else: 
                profit[i] = (close[i-1] - close[i])*num 
                if Ama[i] > Bma[i] or high[i]/opened_price - 1 > stoploss: 
                    opened = False 
                    profit[i] = profit[i] - close[i]*num*0.0002 
    rets = profit / money 
    equity = np.cumprod(1 + rets) 
    return rets,equity
View Code

利用回測函數,我們能夠得到相應的收益率序列以及凈值曲線。

 

 

 我們對該策略進行參數的重采樣,有:

    X = np.arange(5,120)
    rets = np.array([backtest(data,[x,2*x,0,1],100)[0] for x in X]).T

便能夠得到我們需要的收益率序列矩陣,對其進行策略參數過擬合測試:

np.random.seed(0)
PBO,POS,rd,ratios = PBOTest(rets,SampleTimes = 1000,block_len = 50)

我們得到了其過擬合的概率以及虧損的概率。在該例子中,他們分別為70.2%以及 56.3%。此外,我們可以對結果進行進一步的分析:

    np.random.seed(0)
    PBO,POS,rd,ratios = PBOTest(rets,SampleTimes = 1000,block_len = 50)
    
    
    # Histgram of Dist of logit 
    plt.figure(figsize = (10,6)) 
    plt.title('Distribution of Logit') 
    plt.hist(rd,bins = 20) 
    
    # Performance Degration 
    model = sl.LinearRegression() 
    model.fit(ratios[:,0].reshape((-1,1)),ratios[:,1]) 
    plt.figure(figsize = (10,8)) 
    plt.title('Performance Degration') 
    plt.plot(ratios[:,0],ratios[:,1],'.') 
    plt.plot(ratios[:,0],model.decision_function(ratios[:,0].reshape((-1,1))),'-') 
    plt.xlabel('IS Sharpe Ratio') 
    plt.ylabel('OOS Sharpe Ratio')

 

 該圖顯示了Logit統計量的分布。

 

 

該圖形象地顯示出了隨着樣本內夏普比率的上升,樣本外的夏普比率的變化趨勢。

總結

    本文實現了David H. Bailey等人的方法對策略過擬合的概率進行了測試。該方法有一個特點,那便是我們僅僅需要回測收益率序列便能夠對其是否過擬合進行測試,其測試的本質實質上是看樣本內以及樣本外策略的盈利能力的延續性,以及優化過程中是否所有的時間段的策略表現都會隨着參數的變化而同時被優化。利用該方法,我們通過估計過擬合的概率,能夠在一定程度上評估策略的穩健性。



作者:時風_瞬
鏈接:http://www.jianshu.com/p/3bd86cca96bd

THE PROBABILITY OF BACKTEST OVERFITTING    https://files.cnblogs.com/files/hdu-2010/THE_PROBABILITY_OF_BACKTEST_OVERFITTING.pdf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM