繁簡切換您正在訪問的是FX168財經網,本網站所提供的內容及信息均遵守中華人民共和國香港特別行政區當地法律法規。

FX168財經網>人物頻道>帖子

【量化課堂】多回測運行和參數分析框架

作者/我是一個土豆 2019-05-10 16:34 0 來源: FX168財經網人物頻道

導語:本文提供了在研究中自動根據指定參數進行回測,並將一組回測結果可視化的框架。


引言

在因子分析系列中我們遇到了一個比較明顯的需求,就是把全部股票按照各個分位進行回測,並且分析每個分位的收益情況。但其實有這個需求的不僅限於因子的分析,有時我們在策略中還會有一些其他的參數需要進行調整並分析調整後的結果。本篇文章對於這個需求設立了一個參數分析框架,集合運行回測和各種分析結果的功能於一體。也希望讀者多提意見,我們會對其進行改進。

整個框架寫在了一個parameter_analysis 類里面,附在文章結尾的研究模塊。下面講功能和使用方法。

初始化回測

如果想進行分析的回測還沒有被運行過,我們需要運行回測並保存回測結果。首先,

parameter_analysis(algorithm_id=None)

將這個類初始化。輸入中的 algorithm_id 是想要回測使用的策略編碼,獲取方式是策略編輯頁 url 的尾部,如下

id.png

parameter_analysis.run_backtest() 函數可以運行回測,並且 parameter_analysis.organize_backtest_results() 函數可以以回測結果分析一些指標和數據。但這兩個函數都已經打包在 parameter_analysis.get_backtest_data() 中,所以我們只講最後的這個函數。調用這個函數的輸入較多,有

parameter_analysis.get_backtest_data(algorithm_id=None,  benchmark_id_None,  file_name='results.pkl',  running_max=10,  start_date='2006-01-01',  end_date='2016-11-30', frequency='day', initial_cash='1000000', param_names=[],  param_values=[])

我們一個一個來講。

  • algorithm_id 正如其名是策略的 id 編碼,和之前提到的一樣。如果在創建 parameter_analysis 時已經提供了的話這里就不用再提供了。

  • benchmark_id 是自定義基準的回測編碼,注意不是策略而是回測。函數會以這個回測的收益率曲線作為基準曲線,要求這個回測的起始和結束時間和參數中的 start_date 與 end_date 分別吻合。如果不提供 benchmark_id,則策略會自動用 algorithm_id 對應的策略中指定的基準作為基準。

  • file_name 是儲存數據的文件名,因為是以 pickle 進行存儲,文件名結尾必須是 .pkl。回測運行完畢後的 pickle 文件將被存儲於和研究的 .ipyny 相同的文件夾,默認文件名為‘results.pkl’。

  • running_max 是同時可運行的回測的上限。由於在平台上同時運行的回測數量上限是 10 個,固 running_max 默認設為 10。但是,如果需要在這個框架運行的時候留出一些空位來運行其他回測,可以將 running_max 設為 9 或者更小的數字。當這個函數被要求運行超過 running_max 數量的回測時,會自動將它們進行排隊,在之前的完成後再運行後面的。

  • start_date 和 end_date 顧名思義就是回測起始和結束的日期,默認數值如上所述。

  • frequency 是回測頻率,按需求使用 ‘day’ 或者 ‘minute’,默認為‘day’。

  • initial_cash 是回測的起始資金,默認一百萬。

  • 值得細講的是 param_names 和 param_values 兩個輸入。這里 param_names 是一個 list 的 strings,它對應着策略中所有需要調整的全局變量的名字。比如說,我們需要將策略中的 g.abc 和 g.x_y_z 兩個參數更改數值進行多次回測,那麼 param_names 應該設為 [‘abc’, ‘x_y_z’]。而param_values 是一個 list 的 list,要求 len(param_values)==len(param_names)。其中 param_values[i] 是 param_names[i] 的名字所對應的調參值。舉例來說,我們需要把 g.abc 和 g.x_y_z 分別賦予 [‘a’,’b’, ‘c’] 和 [1,2] 的值進行回測,那麼就應該輸入 param_values=[[‘a’,’b’,’c’], [1,2]]。函數會自動列舉所有這些參數選項的組合進行回測,在上面的例子中就會產生六個回測,分別對應參數值 (g.abc, g.x_y_z) 等於 (‘a’,1)、(‘a’,2)、(‘b’,1)、(‘b’,2)、(‘c’,1)、(‘c’,2)。

在所有參數對應的回測運行完成之後,函數會整理數據並存儲 pickle 文件。

數據內容和讀取文件

當我們運行完 parameter_analysis.get_backtest_data() 之後,除了保存在 pickle 文件,還會直接保存在這個類下面。我們逐一介紹這些項目:

  • parameter_analysis.algorithm_id 顧名思義就是我們輸入的策略編碼。

  • parameter_analysis.params_df 是包含了所有回測使用的參數的 DataFrame。其中,橫行 index 是回測的編號,代表它是第幾個被運行的回測,從 0 開始計數。豎列 columns 是之前輸入的 param_names 的名字。df 中的內容是回測相對應的參數數值。舉例來說,param_names = [‘abc’, ‘x_y_z’],param_values=[[‘a’,’b’,’c’], [1,2]] 時,生成的 Data Frame 如下:.

  • params_df.PNG

  • parameter_analysis.evaluations 是一個 dict 的 dict。如果 i 是一個回測的編號(對應params_df 里的 index),那麼 parameter_analysis.evaluations[i] 就是這個回測的各項指標,比如收益率、夏普比率、最大回撤。

  • evaluations.PNG

  • 當然,為了可以更直觀地查看數據,還有 DataFrame 版的 parameter_analysis.evaluations_df。它是 evaluations 的 df 版,並且還帶有 params_df 中的參數數據。

  • evaluation_df.PNG

  • parameter_analysis.backtest_ids 是一個 dict 的 strings,keys 依舊是回測的編號,對應的 內容是該回測的 url 編碼。

  • parameter_analysis.benchmark_id 是自定義基準回測的編碼,如果沒提供則是空。

  • parameter_analysis.dates 是一個 list 的 strings,是按序排列的回測中的每一個交易日。

  • parameter_analysis.benchmark_returns 是一 list 的 float,是基準的收益率數據,對應着 dates 中相同位置的日期。

  • parameter_analysis.returns 和 excess_returns 和 log_returns 和 log_excess_returns 都是一 dict 的 list,keys 是各個回測的編號,而相對應的 list 分別是該策略的收益率和超額收益率和對數收益率和對數超額收益率。其中,超額收益率是用回測淨值除以基準淨值計算而出,對數超額收益率是超額收益率的對數。和 benchmark_returns 一樣,list 中的數據對應着 dates 中相同位置的日期。

  • 最後兩個特別計算的指標是 self.excess_max_drawdown 和 self.excess_annual_return。這兩個都是 dict,keys 是回測的編號,內容分別是策略超額收益曲線的最大回撤和策略超額收益曲線的年化收益率。這兩個指標的意義在於,有時我們想看策略相對於基準的強弱,所以在排除掉基準的影響之後可以進行一些有意義的分析。

  • 如果我們通過 parameter_analysis.get_backtest_data() 運行了回測並且獲取了各種數據,但是關閉了研究模塊,那麼在再次打開研究繼續分析時不需要再重新把回測都運行一遍,可以使用讀取 pickle 文件的函數把已保存的數據讀取。

  • 使用 parameter_analysis.read_backtest_data(file_name=’results.pkl’) 即可讀取數據。這里 file_name 是被讀取的文件名,如果不是默認的 results.pkl 的話則需要另行輸入。在讀取之後,parameter_analysis 中的每個項目會變成上面所講的內容,可以進行調取或者使用接下來的介紹的功能。

一些可視化功能

在獲取數據之後,我們可以把一些指標或者曲線畫出來,便於進行觀察和分析。

首先是 parameter_analysis.get_eval4_bar(sort_by=[]) 函數,這個函數會以 bar 圖的形式畫出回測的收益率、最大回撤、夏普比率和波動率四個圖表。每個圖表上從左到右是每一個回測,默認是按照回測編號進行排列,但是也可以輸入 sort_by 進行自定義排列。sort_by 是一個 list 的 strings,它是參數名稱 param_names 的一個子集,意義為按照這些變量進行排序。舉例來說,如果 sort_by=[‘abc’],那就是按照 ‘abc’ 參數從小到大排列回測,然後劃出四張圖表;如果 sort_by=[‘abc’, ‘x_y_z’],那就是先按照 ‘abc’ 進行排列,然後再按照 ‘x_y_z’ 進行排列(以 ‘x_y_z’ 排序後,組內按照 ‘abc’ 排序),排好之後畫圖。下圖是按照 BP 指標大小排列出來的, get_eval4_bar() 函數使用默認值既可。

get_eval4_bar.PNG

接下來是 parameter_analysis.get_eval(sort_by=[]) 和 parameter_analysis.get_excess_eval(sort_by=[])。第一個函數會畫出各個回測年化收益率和最大回撤,而第二個函數會畫出超額年化收益率和超額最大回撤,sort_by 的功能和之前解釋的一樣。這兩個函數的效果還是直接舉例畫出來最直接。下圖是以 BP 單因子策略十分位結果的 paramter_analysis.get_eval() 得出的圖。

get_eval.PNG

最後是畫出回測的收益曲線、超額收益曲線、對數收益曲線以及超額對數收益曲線的函數,分別是 parameter_analysis.plot_returns() 、plot_excess_returns()、plot_log_returns() 和 plot_log_excess_returns()。這些函數沒有輸入,直接用就行。圖是以 BP 單因子策略十分位結果的 paramter_analysis.plot_returns() 得出的圖。

plot_returns.PNG

深入研究舉例

作為示例,這里我們以 BP 因子為例將策略分為 (0,10),(10,20),… ,(90,100) 十個分位區間進行回測並分析。

在回測之前我們需要制定一個自定義基準。在三篇因子研究分析中,我們發現很多因子不論是最大 5% 還是最小 5% 的分位都很輕松地跑贏了上證指數。經過一些分析,可以發現原因在於上證指數的指數構成偏大盤股,並且按照市值進行加權,所以該指數的小市值暴露度很低;然而我們回測的策略是把所有分位內的股票進行等權分配,小市值暴露度比上證指數要大,所以從這點來看可比性不高。為了剔除市值影響造成的策略和基準之間的差異,我們構建一個“等權全指”作為自定義基準,就是每月初將資金等權分配於所有二十一個交易日沒停牌的股票之間,的到回測如下,下圖基準是上證指數。

一個月沒有停牌的股票等權持倉回測.PNG

對應我們調用的指令如下,初始化 parameter_analysis 類並且啟動【量化課堂】因子研究系列之一 估值和資本結構因子中的 BP 因子回測,按 10 個百分點進行分位(共十個回測),並使用上述的等權全指作為基準:

# 初始化 parameter_analysis 類,設定回測策略 id pa = parameter_analysis('bce2e5c55b3b631f91985c9bf113414f')[這個怎麼表述?可以定義簡化名稱調用class?]# 運行回測pa.get_backtest_data(file_name = 'results.pkl',
                          running_max = 10,
                          benchmark_id = 'ae0684d86e9e7128b1ab9c7d77893029',
                          start_date = '2006-02-01',
                          end_date = '2016-11-01',
                          frequency = 'day',
                          initial_cash = '2000000',
                          param_names = ['factor', 'quantile'],
                          param_values = [['BP'], tuple(zip(range(0,100,10), range(10,101,10)))]
                          )

我們先進行回測,然後畫出收益圖。首先是收益曲線 parameter_analysis.plot_returns(),這里回測編號從 0 到 9 是 BP 值從小到大的回測。可以看出這個因子是具有單調性質的,也就是說 BP 值更大的股票表現一般比 BP 小的股票表現更好。但是,BP 指標的單調性並不是絕對的,比如 (10,20) 分位的股票的收益率是最高的 。

plot_returns.PNG

再用 parameter_analysis.get_eval4_bar() 畫出策略的年化收益、最大回撤、夏普率和波動率,可以看出 BP 策略各分位表現出了收益不單調,對應的其他指標也並不單調。能夠簡單總結 BP 較小還是體現公司的股票回報率會好於 BP 較高的股票,伴隨的是收益較高反而最大回撤較小。夏普率的變動與回報率較為一致,但是波動率變動在不同分位間差距不明顯。

get_eval4_bar.PNG

我們使用正的年化收益與負的最大回撤構建柱狀圖,parameter_analysis.get_eval():

get_eval.PNG

加入新的基準後可以計算對於新基準回報率的超額收益率。進一步分析,我們使用 parameter_analysis.plot_excess_returns() 畫出幾個回測的超額收益曲線。這個圖給我們一些有用的信息:BP 值最小的 10% 的股票的波動率極大,並且收益並沒有超出基準太多,說明選這個區間的股票進行投資也許並不是很好。第二現象是,幾個分位的回測在 06 年和 10 之間的超額收益有相互拉開,但在 10 年之後不同分位並沒有明顯的超額收益區別,說明這個因子在 10 年之後基本已經失效。

plot_excess_returns.PNG

如【更新說明】新超額收益 和 對數軸的思路,我們也生成對數軸回報率,使用函數 parameter_analysis.plot_log_returns() 獲取。如對數軸說明文章中介紹的,這樣的圖形在收益膨脹了現值後仍然可以明晰波動的相對大小。

plot_log_returns.PNG

結合超額收益與對數軸,可以通過 parameter_analysis.plot_log_excess_returns() 獲得超額收益的對數軸圖。如果是超額收益始終持續放大的過程,超額收益的對數軸圖形將會是斜線上升的,然而下圖中收益持平說明在相當長的一段時間中沒有變動。

plot_log_excess_returns.PNG

最後,可以通過 parameter_analysis.get_excess_eval() 獲得超額回報和超額回報最大回撤的柱狀圖。可以看出,超額回報要明顯小於策略原始回報,因為剔除了大盤整體的收益。同時,最大回撤也拉開了距離;按照策略淨值算的最大回撤由於經曆過同樣的股災,所以最大回撤都是百分之七十多,但在剔除大盤影響之後就能比較清晰地對比超額收益的回撤。整體來說,按 BP 選股的各個分為超額收益都不高,並且相對於基準的超額回撤都不小。

get_excess_eval.PNG

但這並不說明 BP 因子是一個無用的指標,只是說我們不能單單使用 BP 作為衡量標準並希望能得到超額收益,也許分行業分別計算 BP 或者結合其他的一些方法依然可以獲得較好的收益。

小結

本文提供了一個使用研究模塊調用回測結果進行調參並且研究分析的代碼框架,其具有較好的兼容性和拓展性,細節之處還需要各位測試了解。在接下來的文章里,我們將使用次框架對因子系列文章 (【量化課堂】因子研究系列之一 估值和資本結構因子、【量化課堂】因子研究系列之二 成長因子和【量化課堂】因子研究系列之三 技術因子)中的因子按分位排列進行回測並深入地分析這些因子的收益效果,敬請期待。

本文參考了多篇社區優秀帖子,這里一並感謝:
研究調用回測:【重磅更新】研究模塊調用回測功能
回測排隊運行:多個回測同時優化,改改列表和算法ID就行 by zhao
結果數據保存:JQ平台如何把DataFrame對象pickle或者json出來?

本文由JoinQuant量化課堂推出,版權歸JoinQuant所有,商業轉載請聯系我們獲得授權,非商業轉載請注明出處。v1.1,2017-05-04,修改錯別字,感謝 czdleaf 指出v1.0,2017-01-03,文章上線
#1 先導入所需要的程序包import datetimeimport numpy as npimport pandas as pdimport timefrom jqdata import *from pandas import Series, DataFrameimport matplotlib.pyplot as pltimport seaborn as snsimport itertoolsimport copyimport pickle# 定義類'參數分析'class parameter_analysis(object):# 定義函數中不同的變量def __init__(self, algorithm_id=None):self.algorithm_id = algorithm_id            # 回測idself.params_df = pd.DataFrame()             # 回測中所有調參備選值的內容,列名字為對應修改面兩名稱,對應回測中的 g.XXXXself.results = {}                           # 回測結果的回報率,key 為 params_df 的行序號,value 為self.evaluations = {}                       # 回測結果的各項指標,key 為 params_df 的行序號,value 為一個 dataframeself.backtest_ids = {}                      # 回測結果的 id# 新加入的基準的回測結果 id,可以默認為空 '',則使用回測中設定的基準self.benchmark_id = 'f16629492d6b6f4040b2546262782c78'                      
        self.benchmark_returns = []                 # 新加入的基準的回測回報率self.returns = {}                           # 記錄所有回報率self.excess_returns = {}                    # 記錄超額收益率self.log_returns = {}                       # 記錄收益率的 log 值self.log_excess_returns = {}                # 記錄超額收益的 log 值self.dates = []                             # 回測對應的所有日期self.excess_max_drawdown = {}               # 計算超額收益的最大回撤self.excess_annual_return = {}              # 計算超額收益率的年化指標self.evaluations_df = pd.DataFrame()        # 記錄各項回測指標,除日回報率外# 定義排隊運行多參數回測函數def run_backtest(self,                          # algorithm_id=None,             # 回測策略id running_max=10,                # 回測中同時巡行最大回測數量 start_date='2006-01-01',       # 回測的起始日期 end_date='2016-11-30',         # 回測的結束日期 frequency='day',               # 回測的運行頻率 initial_cash='1000000',        # 回測的初始持倉金額 param_names=[],                # 回測中調整參數涉及的變量 param_values=[]                # 回測中每個變量的備選參數值 ):# 當此處回測策略的 id 沒有給出時,調用類輸入的策略 idif algorithm_id == None: algorithm_id=self.algorithm_id# 生成所有參數組合並加載到 df 中# 包含了不同參數具體備選值的排列組合中一組參數的 tuple 的 listparam_combinations = list(itertools.product(*param_values))# 生成一個 dataframe, 對應的列為每個調參的變量,每個值為調參對應的備選值to_run_df = pd.DataFrame(param_combinations)# 修改列名稱為調參變量的名字to_run_df.columns = param_names# 設定運行起始時間和保存格式start = time.time()# 記錄結束的運行回測finished_backtests = {}# 記錄運行中的回測running_backtests = {}# 計數器pointer = 0# 總運行回測數目,等於排列組合中的元素個數total_backtest_num = len(param_combinations)# 記錄回測結果的回報率all_results = {}# 記錄回測結果的各項指標all_evaluations = {}# 在運行開始時顯示print '【已完成|運行中|待運行】:', # 當運行回測開始後,如果沒有全部運行完全的話:while len(finished_backtests)<total_backtest_num:# 顯示運行、完成和待運行的回測個數print('[%s|%s|%s].' % (len(finished_backtests), 
                                   len(running_backtests), 
                                   (total_backtest_num-len(finished_backtests)-len(running_backtests)) )),# 記錄當前運行中的空位數量to_run = min(running_max-len(running_backtests), total_backtest_num-len(running_backtests)-len(finished_backtests))# 把可用的空位進行跑回測for i in range(pointer, pointer+to_run):# 備選的參數排列組合的 df 中第 i 行變成 dict,每個 key 為列名字,value 為 df 中對應的值params = to_run_df.ix[i].to_dict()# 記錄策略回測結果的 id,調整參數 extras 使用 params 的內容backtest = create_backtest(algorithm_id = algorithm_id,   start_date = start_date, 
                                           end_date = end_date, 
                                           frequency = frequency, 
                                           initial_cash = initial_cash, 
                                           extras = params, 
                                           # 再回測中把改參數的結果起一個名字,包含了所有涉及的變量參數值   name = str(params)   )# 記錄運行中 i 回測的回測 idrunning_backtests[i] = backtest# 計數器計數運行完的數量    pointer = pointer+to_run# 獲取回測結果failed = []finished = []# 對於運行中的回測,key 為 to_run_df 中所有排列組合中的序數for key in running_backtests.keys():# 研究調用回測的結果,running_backtests[key] 為運行中保存的結果 idbt = get_backtest(running_backtests[key])# 獲得運行回測結果的狀態,成功和失敗都需要運行結束後返回,如果沒有返回則運行沒有結束status = bt.get_status()# 當運行回測失敗if status == 'failed':# 失敗 list 中記錄對應的回測結果 idfailed.append(key)# 當運行回測成功時elif status == 'done':# 成功 list 記錄對應的回測結果 id,finish 僅記錄運行成功的finished.append(key)# 回測回報率記錄對應回測的回報率 dict, key to_run_df 中所有排列組合中的序數, value 為回報率的 dict# 每個 value 一個 list 每個對象為一個包含時間、日回報率和基準回報率的 dictall_results[key] = bt.get_results()# 回測回報率記錄對應回測結果指標 dict, key to_run_df 中所有排列組合中的序數, value 為回測結果指標的 dataframeall_evaluations[key] = bt.get_risk()# 記錄運行中回測結果 id 的 list 中刪除失敗的運行for key in failed:running_backtests.pop(key)# 在結束回測結果 dict 中記錄運行成功的回測結果 id,同時在運行中的記錄中刪除該回測for key in finished:finished_backtests[key] = running_backtests.pop(key)# 當一組同時運行的回測結束時報告時間if len(finished_backtests) != 0 and len(finished_backtests) % running_max == 0 and to_run !=0:# 記錄當時時間middle = time.time()# 計算剩餘時間,假設沒工作量時間相等的話remain_time = (middle - start) * (total_backtest_num - len(finished_backtests)) / len(finished_backtests)# print 當前運行時間print('[已用%s時,尚餘%s時,請不要關閉瀏覽器].' % (str(round((middle - start) / 60.0 / 60.0,3)), 
                                          str(round(remain_time / 60.0 / 60.0,3)))),# 5秒鍾後再跑一下time.sleep(5) # 記錄結束時間end = time.time() print ''print('【回測完成】總用時:%s秒(即%s小時)。' % (str(int(end-start)), 
                                           str(round((end-start)/60.0/60.0,2)))),# 對應修改類內部對應self.params_df = to_run_dfself.results = all_resultsself.evaluations = all_evaluationsself.backtest_ids = finished_backtests#7 最大回撤計算方法def find_max_drawdown(self, returns):# 定義最大回撤的變量result = 0# 記錄最高的回報率點historical_return = 0# 遍曆所有日期for i in range(len(returns)):# 最高回報率記錄historical_return = max(historical_return, returns[i])# 最大回撤記錄drawdown = 1-(returns[i] + 1) / (historical_return + 1)# 記錄最大回撤result = max(drawdown, result)# 返回最大回撤值return result# log 收益、新基準下超額收益和相對與新基準的最大回撤def organize_backtest_results(self, benchmark_id=None):# 若新基準的回測結果 id 沒給出if benchmark_id==None:# 使用默認的基準回報率,默認的基準在回測策略中設定self.benchmark_returns = [x['benchmark_returns'] for x in self.results[0]]# 當新基準指標給出後    else:# 基準使用新加入的基準回測結果self.benchmark_returns = [x['returns'] for x in get_backtest(benchmark_id).get_results()]# 回測日期為結果中記錄的第一項對應的日期self.dates = [x['time'] for x in self.results[0]]# 對應每個回測在所有備選回測中的順序 (key),生成新數據# 由 {key:{u'benchmark_returns': 0.022480100091729405,#           u'returns': 0.03184566700000002,#           u'time': u'2006-02-14'}} 格式轉化為:# {key: []} 格式,其中 list 為對應 date 的一個回報率 listfor key in self.results.keys():self.returns[key] = [x['returns'] for x in self.results[key]]# 生成對於基準(或新基準)的超額收益率for key in self.results.keys():self.excess_returns[key] = [(x+1)/(y+1)-1 for (x,y) in zip(self.returns[key], self.benchmark_returns)]# 生成 log 形式的收益率for key in self.results.keys():self.log_returns[key] = [log(x+1) for x in self.returns[key]]# 生成超額收益率的 log 形式for key in self.results.keys():self.log_excess_returns[key] = [log(x+1) for x in self.excess_returns[key]]# 生成超額收益率的最大回撤for key in self.results.keys():self.excess_max_drawdown[key] = self.find_max_drawdown(self.excess_returns[key])# 生成年化超額收益率for key in self.results.keys():self.excess_annual_return[key] = (self.excess_returns[key][-1]+1)**(252./float(len(self.dates)))-1# 把調參數據中的參數組合 df 與對應結果的 df 進行合並self.evaluations_df = pd.concat([self.params_df, pd.DataFrame(self.evaluations).T], axis=1)#         self.evaluations_df = # 獲取最總分析數據,調用排隊回測函數和數據整理的函數    def get_backtest_data(self,  algorithm_id=None,                         # 回測策略id  benchmark_id=None,                         # 新基準回測結果id  file_name='results.pkl',                   # 保存結果的 pickle 文件名字  running_max=10,                            # 最大同時運行回測數量  start_date='2006-01-01',                   # 回測開始時間  end_date='2016-11-30',                     # 回測結束日期  frequency='day',                           # 回測的運行頻率  initial_cash='1000000',                    # 回測初始持倉資金  param_names=[],                            # 回測需要測試的變量  param_values=[]                            # 對應每個變量的備選參數  ):# 調運排隊回測函數,傳遞對應參數self.run_backtest(algorithm_id=algorithm_id,  running_max=running_max,  start_date=start_date,  end_date=end_date,  frequency=frequency,  initial_cash=initial_cash,  param_names=param_names,  param_values=param_values  )# 回測結果指標中加入 log 收益率和超額收益率等指標self.organize_backtest_results(benchmark_id)# 生成 dict 保存所有結果。results = {'returns':self.returns,   'excess_returns':self.excess_returns,   'log_returns':self.log_returns,   'log_excess_returns':self.log_excess_returns,   'dates':self.dates,   'benchmark_returns':self.benchmark_returns,   'evaluations':self.evaluations,   'params_df':self.params_df,   'backtest_ids':self.backtest_ids,   'excess_max_drawdown':self.excess_max_drawdown,   'excess_annual_return':self.excess_annual_return,   'evaluations_df':self.evaluations_df}# 保存 pickle 文件pickle_file = open(file_name, 'wb')pickle.dump(results, pickle_file)pickle_file.close()# 讀取保存的 pickle 文件,賦予類中的對象名對應的保存內容    def read_backtest_data(self, file_name='results.pkl'):pickle_file = open(file_name, 'rb')results = pickle.load(pickle_file)self.returns = results['returns']self.excess_returns = results['excess_returns']self.log_returns = results['log_returns']self.log_excess_returns = results['log_excess_returns']self.dates = results['dates']self.benchmark_returns = results['benchmark_returns']self.evaluations = results['evaluations']self.params_df = results['params_df']self.backtest_ids = results['backtest_ids']self.excess_max_drawdown = results['excess_max_drawdown']self.excess_annual_return = results['excess_annual_return']self.evaluations_df = results['evaluations_df']# 回報率折線圖    def plot_returns(self):# 通過figsize參數可以指定繪圖對象的寬度和高度,單位為英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(111)# 作圖for key in self.returns.keys():ax.plot(range(len(self.returns[key])), self.returns[key], label=key)# 設定benchmark曲線並標記ax.plot(range(len(self.benchmark_returns)), self.benchmark_returns, label='benchmark', c='k', linestyle='') ticks = [int(x) for x in np.linspace(0, len(self.dates)-1, 11)]plt.xticks(ticks, [self.dates[i] for i in ticks])# 設置圖例樣式ax.legend(loc = 2, fontsize = 10)# 設置y標簽樣式ax.set_ylabel('returns',fontsize=20)# 設置x標簽樣式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 設置圖片標題樣式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(self.returns[0]))# 超額收益率圖    def plot_excess_returns(self):# 通過figsize參數可以指定繪圖對象的寬度和高度,單位為英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(111)# 作圖for key in self.returns.keys():ax.plot(range(len(self.excess_returns[key])), self.excess_returns[key], label=key)# 設定benchmark曲線並標記ax.plot(range(len(self.benchmark_returns)), [0]*len(self.benchmark_returns), label='benchmark', c='k', linestyle='')ticks = [int(x) for x in np.linspace(0, len(self.dates)-1, 11)]plt.xticks(ticks, [self.dates[i] for i in ticks])# 設置圖例樣式ax.legend(loc = 2, fontsize = 10)# 設置y標簽樣式ax.set_ylabel('excess returns',fontsize=20)# 設置x標簽樣式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 設置圖片標題樣式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(self.excess_returns[0]))# log回報率圖    def plot_log_returns(self):# 通過figsize參數可以指定繪圖對象的寬度和高度,單位為英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(111)# 作圖for key in self.returns.keys():ax.plot(range(len(self.log_returns[key])), self.log_returns[key], label=key)# 設定benchmark曲線並標記ax.plot(range(len(self.benchmark_returns)), [log(x+1) for x in self.benchmark_returns], label='benchmark', c='k', linestyle='')ticks = [int(x) for x in np.linspace(0, len(self.dates)-1, 11)]plt.xticks(ticks, [self.dates[i] for i in ticks])# 設置圖例樣式ax.legend(loc = 2, fontsize = 10)# 設置y標簽樣式ax.set_ylabel('log returns',fontsize=20)# 設置圖片標題樣式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(self.log_returns[0]))# 超額收益率的 log 圖def plot_log_excess_returns(self):# 通過figsize參數可以指定繪圖對象的寬度和高度,單位為英寸;fig = plt.figure(figsize=(20,8))ax = fig.add_subplot(111)# 作圖for key in self.returns.keys():ax.plot(range(len(self.log_excess_returns[key])), self.log_excess_returns[key], label=key)# 設定benchmark曲線並標記ax.plot(range(len(self.benchmark_returns)), [0]*len(self.benchmark_returns), label='benchmark', c='k', linestyle='')ticks = [int(x) for x in np.linspace(0, len(self.dates)-1, 11)]plt.xticks(ticks, [self.dates[i] for i in ticks])# 設置圖例樣式ax.legend(loc = 2, fontsize = 10)# 設置y標簽樣式ax.set_ylabel('log excess returns',fontsize=20)# 設置圖片標題樣式ax.set_title("Strategy's performances with different parameters", fontsize=21)plt.xlim(0, len(self.log_excess_returns[0]))# 回測的4個主要指標,包括總回報率、最大回撤夏普率和波動def get_eval4_bar(self, sort_by=[]): 
        sorted_params = self.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.indexfig = plt.figure(figsize=(20,7))# 定義位置ax1 = fig.add_subplot(221)# 設定橫軸為對應分位,縱軸為對應指標ax1.bar(range(len(indices)), [self.evaluations[x]['algorithm_return'] for x in indices], 0.6, label = 'Algorithm_return')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 設置圖例樣式ax1.legend(loc='best',fontsize=15)# 設置y標簽樣式ax1.set_ylabel('Algorithm_return', fontsize=15)# 設置y標簽樣式ax1.set_yticklabels([str(x*100)+'% 'for x in ax1.get_yticks()])# 設置圖片標題樣式ax1.set_title("Strategy's of Algorithm_return performances of different quantile", fontsize=15)# x軸範圍plt.xlim(0, len(indices))# 定義位置ax2 = fig.add_subplot(224)# 設定橫軸為對應分位,縱軸為對應指標ax2.bar(range(len(indices)), [self.evaluations[x]['max_drawdown'] for x in indices], 0.6, label = 'Max_drawdown')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 設置圖例樣式ax2.legend(loc='best',fontsize=15)# 設置y標簽樣式ax2.set_ylabel('Max_drawdown', fontsize=15)# 設置x標簽樣式ax2.set_yticklabels([str(x*100)+'% 'for x in ax2.get_yticks()])# 設置圖片標題樣式ax2.set_title("Strategy's of Max_drawdown performances of different quantile", fontsize=15)# x軸範圍plt.xlim(0, len(indices))# 定義位置ax3 = fig.add_subplot(223)# 設定橫軸為對應分位,縱軸為對應指標ax3.bar(range(len(indices)),[self.evaluations[x]['sharpe'] for x in indices], 0.6, label = 'Sharpe')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 設置圖例樣式ax3.legend(loc='best',fontsize=15)# 設置y標簽樣式ax3.set_ylabel('Sharpe', fontsize=15)# 設置x標簽樣式ax3.set_yticklabels([str(x*100)+'% 'for x in ax3.get_yticks()])# 設置圖片標題樣式ax3.set_title("Strategy's of Sharpe performances of different quantile", fontsize=15)# x軸範圍plt.xlim(0, len(indices))# 定義位置ax4 = fig.add_subplot(222)# 設定橫軸為對應分位,縱軸為對應指標ax4.bar(range(len(indices)), [self.evaluations[x]['algorithm_volatility'] for x in indices], 0.6, label = 'Algorithm_volatility')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 設置圖例樣式ax4.legend(loc='best',fontsize=15)# 設置y標簽樣式ax4.set_ylabel('Algorithm_volatility', fontsize=15)# 設置x標簽樣式ax4.set_yticklabels([str(x*100)+'% 'for x in ax4.get_yticks()])# 設置圖片標題樣式ax4.set_title("Strategy's of Algorithm_volatility performances of different quantile", fontsize=15)# x軸範圍plt.xlim(0, len(indices))#14 年化回報和最大回撤,正負雙色表示def get_eval(self, sort_by=[]):sorted_params = self.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.index# 大小fig = plt.figure(figsize = (20, 8))# 圖1位置ax = fig.add_subplot(111)# 生成圖超額收益率的最大回撤ax.bar([x+0.3 for x in range(len(indices))],   [-self.evaluations[x]['max_drawdown'] for x in indices], color = '#32CD32',  
                     width = 0.6, label = 'Max_drawdown', zorder=10)# 圖年化超額收益ax.bar([x for x in range(len(indices))],   [self.evaluations[x]['annual_algo_return'] for x in indices], color = 'r', 
                     width = 0.6, label = 'Annual_return')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 設置圖例樣式ax.legend(loc='best',fontsize=15)# 基準線plt.plot([0, len(indices)], [0, 0], c='k', 
                 linestyle='', label='zero')# 設置圖例樣式ax.legend(loc='best',fontsize=15)# 設置y標簽樣式ax.set_ylabel('Max_drawdown', fontsize=15)# 設置x標簽樣式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 設置圖片標題樣式ax.set_title("Strategy's performances of different quantile", fontsize=15)#   設定x軸長度plt.xlim(0, len(indices))#14 超額收益的年化回報和最大回撤# 加入新的benchmark後超額收益和def get_excess_eval(self, sort_by=[]):sorted_params = self.params_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.index# 大小fig = plt.figure(figsize = (20, 8))# 圖1位置ax = fig.add_subplot(111)# 生成圖超額收益率的最大回撤ax.bar([x+0.3 for x in range(len(indices))],   [-self.excess_max_drawdown[x] for x in indices], color = '#32CD32',  
                     width = 0.6, label = 'Excess_max_drawdown')# 圖年化超額收益ax.bar([x for x in range(len(indices))],   [self.excess_annual_return[x] for x in indices], color = 'r', 
                     width = 0.6, label = 'Excess_annual_return')plt.xticks([x+0.3 for x in range(len(indices))], indices)# 設置圖例樣式ax.legend(loc='best',fontsize=15)# 基準線plt.plot([0, len(indices)], [0, 0], c='k', 
                 linestyle='', label='zero')# 設置圖例樣式ax.legend(loc='best',fontsize=15)# 設置y標簽樣式ax.set_ylabel('Max_drawdown', fontsize=15)# 設置x標簽樣式ax.set_yticklabels([str(x*100)+'% 'for x in ax.get_yticks()])# 設置圖片標題樣式ax.set_title("Strategy's performances of different quantile", fontsize=15)#   設定x軸長度plt.xlim(0, len(indices))
#2 設定回測策略 id pa = parameter_analysis('f16629492d6b6f4040b2546262782c78')
#3 運行回測pa.get_backtest_data(file_name = 'results.pkl',  running_max = 10,  benchmark_id = None,  start_date = '2016-02-01',  end_date = '2016-11-01',  frequency = 'day',  initial_cash = '2000000',  param_names = ['factor', 'quantile'],  param_values = [['BP'], tuple(zip(range(0,100,10), range(10,101,10)))]  )
【已完成|運行中|待運行】: [0|0|10]. [0|10|0]. [0|10|0]. [0|10|0]. [0|10|0]. [3|7|0]. [3|7|0]. [8|2|0]. [8|2|0]. [9|1|0]. [9|1|0]. 
【回測完成】總用時:88秒(即0.02小時)。
#4 數據讀取pa.read_backtest_data('results.pkl')
#5 回測參數的 Dataframepa.params_df

factorquantile
0BP(0, 10)
1BP(10, 20)
2BP(20, 30)
3BP(30, 40)
4BP(40, 50)
5BP(50, 60)
6BP(60, 70)
7BP(70, 80)
8BP(80, 90)
9BP(90, 100)
#6 查看回測結果指標pa.evaluations_df

factorquantile__versionalgorithm_returnalgorithm_volatilityalphaannual_algo_returnannual_bm_return*g_position_days*g_trade_return...max_drawdown_periodmax_leverageperiod_labelprofit_loss_ratiosharpesortinotrading_daystreasury_returnwin_countwin_ratio
0BP(0, 10)1010.24166540.20938480.13524870.34846780.199223120.38190.1213416...[2016-04-14, 2016-05-19]02016-1117.143331.473211.8872741810.03002742500.9157509
1BP(10, 20)1010.27732840.24091540.16244910.40225420.19922385.852220.1033062...[2016-04-14, 2016-05-19]02016-116.2658641.5036571.8965251810.03002742820.7811634
2BP(20, 30)1010.29161170.25678220.17528470.42395810.19922376.169570.08521891...[2016-02-24, 2016-02-29]02016-115.2041871.4952671.8070541810.03002742840.7533156
3BP(30, 40)1010.23275150.26576230.080942390.33511510.19922365.676580.07313098...[2016-04-14, 2016-05-18]02016-113.3026431.1104481.3039371810.03002742660.685567
4BP(40, 50)1010.28836330.26820340.16410430.41901390.19922362.820790.08202844...[2016-04-15, 2016-05-18]02016-113.5841161.4131581.6513281810.03002742770.7120823
5BP(50, 60)1010.30568290.27276460.18828790.44542920.19922361.351920.08770805...[2016-02-22, 2016-02-29]02016-114.3028371.486371.785471810.03002743040.745098
6BP(60, 70)1010.27227930.26355880.14733780.3946040.19922363.548390.0880978...[2016-02-24, 2016-02-29]02016-113.6680621.3454451.6375251810.03002742540.6920981
7BP(70, 80)1010.33656060.27678850.23796310.49285460.19922371.132530.1105355...[2016-02-24, 2016-02-29]02016-113.6211751.6361031.9301421810.03002742420.7245509
8BP(80, 90)1010.24685350.27923390.10228280.35625630.19922369.417320.09610442...[2016-04-15, 2016-05-18]02016-112.3180671.1325861.3363721810.03002742110.6698413
9BP(90, 100)1010.2605140.2861940.11980270.37682260.19922381.895450.1605048...[2016-02-24, 2016-02-29]02016-112.7047741.1769031.3952921810.03002742260.7151899

10 rows × 28 columns

 #7 回報率折線圖    pa.plot_returns()
#8 超額收益率圖    pa.plot_excess_returns()
#9 log回報率圖    pa.plot_log_returns()
#10 超額收益率的 log 圖pa.plot_log_excess_returns()
#11 回測的4個主要指標,包括總回報率、最大回撤夏普率和波動# get_eval4_bar(self, sort_by=[])pa.get_eval4_bar()
#12 年化回報和最大回撤,正負雙色顯示# get_eval(self, sort_by=[])pa.get_eval()
#13 超額收益的年化回報和最大回撤# 加入新的benchmark後超額收益和# get_excess_eval(self, sort_by=[])pa.get_excess_eval()
# test 測試最後bar圖中的sort_by對應內容param_names=['abc','x_y_z']param_values=[['a','b','c'], [1,2]]param_combinations = list(itertools.product(*param_values))to_run_df = pd.DataFrame(param_combinations)to_run_df.columns = param_names# to_run_df.ix[1].to_dict()to_run_df# sort_by = ['abc']sort_by = ['abc', 'x_y_z']# sort_by = ['x_y_z']# sort_by = ['x_y_z','abc']sorted_params = to_run_dffor by in sort_by:sorted_params = sorted_params.sort(by)indices = sorted_params.indexsorted_params

abcx_y_z
0a1
2b1
4c1
1a2
3b2
5c2
 
分享到:
舉報財經168客戶端下載

全部回複

0/140

投稿 您想發表你的觀點和看法?

更多人氣分析師

  • 張亦巧

    人氣2208文章4145粉絲45

    暫無個人簡介信息

  • 張迎妤

    人氣1912文章3305粉絲34

    個人專注於行情技術分析,消息面解讀剖析,給予您第一時間方向...

  • 指導老師

    人氣1864文章4423粉絲52

    暫無個人簡介信息

  • 李冉晴

    人氣2320文章3821粉絲34

    李冉晴,專業現貸實盤分析師。

  • 梁孟梵

    人氣2184文章3177粉絲39

    qq:2294906466 了解群指導添加微信mfmacd

  • 王啟蒙現貨黃金

    人氣328文章3550粉絲8

    本人做分析師以來,並專注於貴金屬投資市場,尤其是在現貨黃金...

  • 金泰鉻J

    人氣2328文章3925粉絲51

    投資問答解咨詢金泰鉻V/信tgtg67即可獲取每日的實時資訊、行情...

  • 金算盤

    人氣2696文章7761粉絲125

    高級分析師,混過名校,廝殺於股市和期貨、證券市場多年,專注...

  • 金帝財神

    人氣4760文章8329粉絲119

    本文由資深分析師金帝財神微信:934295330,指導黃金,白銀,...

FX168財經

FX168財經學院

FX168財經

FX168北美