機器學習與比特幣示例

第12節 機器學習與比特幣示例

作者: 阿布

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

阿布量化微信公眾號: abu_quant

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

本節ipython notebook

備註:不熟悉編程的用戶可參考界面操作實現本示例

在《量化交易之路》中我曾經編寫過豬老三的世界中使用機器學習對股價和漲跌進行預測的幻想示例。

在豬老三的世界中實現了:機器學習.fit(x, y) = (股價預測,漲跌預測) =發財

在豬老三的童話中我設定的可以影響股價走勢的參數是有限個的,特別是影響漲跌的因素很容易構造特徵。在真實的市場中可以影響股價走勢的因素是無限多的,而且這些因素之間的也可以是相關的。就像你求解一個方程組,這個方程組不是有一個兩個解,它是有無限個解的系統,並且每個解都與其他任意個解相關,但又並非簡單線性相關。市場是一個二級混沌系統,認為任何想通過技術對股價進行預測或者漲跌預測都是不可能的,不倫你自己認為你使用的技術本身有多高深,無異於管中窺豹。

本系列教程中講到的ump裁判系統是abupy中通過機器學習技術對回測結果進行學習,反向指導決策新的交易是否攔截的實際應用,本節講解機器學習在量化領域很有作用的另一個方向:閥值的估計,因為無論是編寫選股策略,擇時策略還是任何涉及決策的代碼模型中都離不開閥值,比如最常用的止盈止損策略,在代碼的編寫中就一定會涉及閥值,比如之前的章節一直使用的abupy中內置止盈止損策略AbuFactorCloseAtrNStop。

之所以一定會涉及閥值的確定是因為就像剛剛說的類似你求解一個方程組,如果所有的參數都是未知數,那麼你怎麼解出你需要的答案,所以一定要把一些變數變成常數值,然後通過這些常數值來確定更多的變數,最終解出你所關心的解。

對於閥值的確定傳統的做法是根據經驗來設定,實際上所謂的經驗是個體對問題的統計模型, 在機器學習技術的幫助下,可以實現更客觀,全面,適應範圍更廣的閥值設定。

不論是個體經驗對閥值進行常量定估還是通過機器學習技術通過數據模型對閥值進行定估,都達不到絕對準確預測結果的目標,量化交易的概率優勢並不具有絕對的優勢,即達不到預測的程度,量化交易中大多策略是基於對歷史規律的總結,在規律的基礎上發現概率優勢,它的最大理論依據是人性的相似性以及人性很難改變的事實,如果每一個瞬間的股票價格都是全體交易者對價值所達成的一種瞬間共識,那麼歷史的規律在今後的交易中同樣具有指導意義。

按照上面解方程的說法就是,定估的常量只要能滿足大多數時候解出合理的解,甚至很多時候定估的常量只要能滿足有時候能解出合理的解就可以,對於有時候能解出合理的情況,可以在上層通過非均衡技術對決策進行二次邏輯過濾,我反覆提及過的非均衡技術思想是量化中很很重要的一種設計思路,因為我們量化的目標結果就是非均衡,我們想要贏的錢比輸的多

1. 比特幣特徵的提取

下面通過對比特幣的短線交易決策為例,示例上述論點以及abupy中機器學習模塊的使用,以及數據獲取:

from abupy import abu, ml, nd, tl, pd_resample, AbuML, AbuMLPd, AbuMetricsBase from abupy import ABuSymbolPd, ABuScalerUtil, get_price, ABuMarketDrawing, ABuKLUtil# btc是比特幣symbol代號btc = ABuSymbolPd.make_kl_df("btc", start="2013-09-01", end="2017-07-26")

之前在比特幣, 萊特幣的回測那節使用ABuKLUtil.date_week_wave對走勢的日震蕩做過統計如下:

ABuKLUtil.date_week_wave(btc)date_week周一 5.0108周二 5.5610周三 5.4437周四 5.7275周五 5.3008周六 4.7875周日 4.6528Name: wave, dtype: float64

從上面可以看出大概0.055的日震蕩幅度可以成做大波動的交易對比特幣來說,下面對數據添加新列big_wave,可以看到結果大波動的佔總交易日的1/3:

btc["big_wave"] = (btc.high - btc.low) / btc.pre_close > 0.055btc["big_wave"] = btc["big_wave"].astype(int)btc["big_wave"].value_counts()0 10051 420Name: big_wave, dtype: int64

任何大的決策其實都是由很多看極起來極不起眼的小事組成的,如果我們是做比特幣日內的交易者,首先你需要判斷今天適不適合做交易,做出這個判斷的依據里有一條即是今天的波動需要足夠大,下面通過機器學習技術演示如何決策今天的波動是否足夠大。

備註:由於abupy中內置沙盒數據沒有分時的數據,所以本示例使用日線數據做為分析對象,實際策略中應該使用的是分鐘數據

首先切割訓練集和測試集,保留最後60天走勢數據做為測試集數據:

btc_train_raw = btc[:-60]btc_test_raw = btc[-60:]

下面為訓練集和測試集數據都加上5,10,21,60日均線特徵:

def calc_ma(tc, ma): ma_key = "ma{}".format(ma) tc[ma_key] = nd.ma.calc_ma_from_prices(tc.close, ma, min_periods=1)for ma in [5, 10, 21, 60]: calc_ma(btc_train_raw, ma) calc_ma(btc_test_raw, ma)btc_train_raw.tail(1)

編寫特徵抽取組合函數btc_siblings_df:

  • 它首先將所有交易日以3個為一組,切割成多個子df,即每一個子df中有3個交易日的交易數據
  • 使用數據標準化將連續3天交易日中的連續數值特徵進行標準化操作
  • 抽取第一天,第二天的大多數特徵分別改名字以one,two為特徵前綴,如:one_open,one_close,two_ma5,two_high.....,
  • 第三天的特徵只使用"open", "low", "pre_close", "date_week",該名前綴today,如today_open,today_date_week
  • 第三天的抽取了"big_wave",其將在之後做為y
  • 將抽取改名字後的特徵連接起來組合成為一條新數據,即3天的交易數據特徵->1條新的數據

代碼如下所示:

def btc_siblings_df(btc_raw): # 將所有交易日以3個為一組,切割成多個子df,即每一個子df中有3個交易日的交易數據 btc_siblings = [btc_raw.ix[sib_ind * 3:(sib_ind + 1) * 3, :] for sib_ind in np.arange(0, int(btc_raw.shape[0] / 3))] btc_df = pd.DataFrame() for sib_btc in btc_siblings: # 使用數據標準化將連續3天交易日中的連續數值特徵進行標準化操作 sib_btc_scale = ABuScalerUtil.scaler_std( sib_btc.filter(["open", "close", "high", "low", "volume", "pre_close", "ma5", "ma10", "ma21", "ma60", "atr21", "atr14"])) # 把標準化後的和big_wave,date_week連接起來 sib_btc_scale = pd.concat([sib_btc["big_wave"], sib_btc_scale, sib_btc["date_week"]], axis=1) # 抽取第一天,第二天的大多數特徵分別改名字以one,two為特徵前綴,如:one_open,one_close,two_ma5,two_high..... a0 = sib_btc_scale.iloc[0].filter(["open", "close", "high", "low", "volume", "pre_close", "ma5", "ma10", "ma21", "ma60", "atr21", "atr14", "date_week"]) a0.rename(index={"open": "one_open", "close": "one_close", "high": "one_high", "low": "one_low", "volume": "one_volume", "pre_close": "one_pre_close", "ma5": "one_ma5", "ma10": "one_ma10", "ma21": "one_ma21", "ma60": "one_ma60", "atr21": "one_atr21", "atr14": "one_atr14", "date_week": "one_date_week"}, inplace=True) a1 = sib_btc_scale.iloc[1].filter(["open", "close", "high", "low", "volume", "pre_close", "ma5", "ma10", "ma21", "ma60", "atr21", "atr14", "date_week"]) a1.rename(index={"open": "two_open", "close": "two_close", "high": "two_high", "low": "two_low", "volume": "two_volume", "pre_close": "two_pre_close", "ma5": "two_ma5", "ma10": "two_ma10", "ma21": "two_ma21", "ma60": "two_ma60", "atr21": "two_atr21", "atr14": "two_atr14", "date_week": "two_date_week"}, inplace=True) # 第三天的特徵只使用"open", "low", "pre_close", "date_week",該名前綴today,如today_open,today_date_week a2 = sib_btc_scale.iloc[2].filter(["big_wave", "open", "low", "pre_close", "date_week"]) a2.rename(index={"open": "today_open", "low": "today_low", "pre_close": "today_pre_close", "date_week": "today_date_week"}, inplace=True) # 將抽取改名字後的特徵連接起來組合成為一條新數據,即3天的交易數據特徵->1條新的數據 btc_df = btc_df.append(pd.concat([a0, a1, a2], axis=0), ignore_index=True) return btc_df

第三天的特徵避免使用high是因為訓練的目標y是big_wave,之所以可以使用當天的low是因為比特幣市場的特點為24小時交易,即沒有明確的一天的概念,也即沒有明確一天中的最低,實盤使用即人工對當前最低根據24小時最低進行猜測,或直接使用24小時最低,或者使用當前的最新價格都可,組成數據後使用模型進行決策,決策的結果即為未來數小時內是否會有大的波動,實際上最終大波動的決策成立,需要其它很多模型共同生效,比如外盤的走勢決策等等。

下面使用訓練集數據btc_train_raw做為參數抽取組合特徵,重新組合好的特徵如tail所示:

btc_train0 = btc_siblings_df(btc_train_raw)btc_train0.tail()

如上所示這樣我們只能得到454條訓練集數據,但由於每3條連續交易日數據組合成一個特徵,只要向前跳一條數據進行特徵組合抽取即可以得到另一組新特徵,下面分別跳過第一個,第二個數據,抽取btc_train1,btc_train2:

btc_train1 = btc_siblings_df(btc_train_raw[1:])btc_train2 = btc_siblings_df(btc_train_raw[2:])

下面把上面的3組特徵連起來,然後把周幾這個特徵使用pd.get_dummies進行離散化處理,使得所有特徵值的範圍都在0-1之間,最終的特徵如下btc_train所示:

btc_train = pd.concat([btc_train0, btc_train1, btc_train2])btc_train.index = np.arange(0, btc_train.shape[0])dummies_one_week = pd.get_dummies(btc_train["one_date_week"], prefix="one_date_week")dummies_two_week = pd.get_dummies(btc_train["two_date_week"], prefix="two_date_week")dummies_today_week = pd.get_dummies(btc_train["today_date_week"], prefix="today_date_week")btc_train.drop(["one_date_week", "two_date_week", "today_date_week"], inplace=True, axis=1)btc_train = pd.concat([btc_train, dummies_one_week, dummies_two_week, dummies_today_week], axis=1)pd.options.display.max_rows=10btc_train

2. abu中內置機器學習模塊的使用

下面使用abupy中內置機器學習工具AbuML對特徵數據進行封裝,代碼如下所示,下面的y值即是big_wave列:

train_matrix = btc_train.as_matrix()y = train_matrix[:, 0]x = train_matrix[:, 1:]btc_ml = AbuML(x, y, btc_train)

AbuML會根據數據特點自動內部選擇使用分類器或者回歸器,下面進行特指最優分類器操作,比如特指使用隨機森林做為分類器,執行random_forest_classifier_best會在內部對n_estimators參數和訓練集數據進行grid search擬合,尋找最合適的參數最終做為內部分類器:

btc_ml.random_forest_classifier_best()start grid search please wait...

RandomForestClassifier(bootstrap=True, class_weight=None, criterion="gini", max_depth=None, max_features="auto", max_leaf_nodes=None, min_impurity_split=1e-07, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=260, n_jobs=1, oob_score=False, random_state=None, verbose=0, warm_start=False)

所有best函數中尋找最優參數內部只根據學習器的特點尋找最少量的參數設置,如果需要自定義參數範圍可使用如下所示:

param_grid = {"max_features": ["sqrt", "log2"], "n_estimators": np.arange(50, 500, 50)}btc_ml.random_forest_classifier_best(param_grid=param_grid)start grid search please wait...

下面使用btc_ml對訓練集進行交叉準確率評分:

btc_ml.cross_val_accuracy_score()RandomForestClassifier score mean: 0.8151620867325032array([ 0.781 , 0.8102, 0.7883, 0.8382, 0.8162, 0.8162, 0.8235, 0.8456, 0.7794, 0.8529])

下面使用btc_ml對訓練集進行交叉roc_auc評分:

btc_ml.cross_val_roc_auc_score()RandomForestClassifier score mean: 0.8399573797130188array([ 0.815 , 0.8785, 0.8166, 0.8018, 0.8707, 0.8484, 0.8148, 0.8551, 0.8005, 0.8981])

AbuML對外的函數都支持關鍵子參數fiter_type,可以指定使用的學習器類型如回歸,聚類等,每個函數都通過內部裝飾器聲明自己支持的學習器類型對不支持的類型輸出不支持,如下想通過指定使用回歸器進行roc_auc評分:

btc_ml.cross_val_roc_auc_score(fiter_type=ml.EMLFitType.E_FIT_REG)cross_val_roc_auc_score not support reg!

上述輸出顯示函數不支持回歸器,因為內部實現中通過entry_wrapper裝飾器聲明了只支持E_FIT_CLF,即分類器:

@entry_wrapper(support=(EMLFitType.E_FIT_CLF,))def cross_val_roc_auc_score(self, cv=10, **kwargs): """ 被裝飾器entry_wrapper(support=(EMLFitType.E_FIT_CLF,))裝飾, 即支持有監督學習分類,使用cross_val_score對數據進行roc_auc度量,如果數據的y的 label標籤 > 2,通過label_binarize將label標籤進行二值化處理, 依次計算二值化的列的roc_auc,結果返回score最好的數據度量 :param cv: 透傳cross_val_score的參數,默認10 :param kwargs: 外部可以傳遞x, y, 通過 x = kwargs.pop("x", self.x) y = kwargs.pop("y", self.y) 確定傳遞self._do_cross_val_score中參數x,y, 以及裝飾器使用的fiter_type,eg: ttn_abu.cross_val_roc_auc_score(fiter_type=ml.EMLFitType.E_FIT_REG) :return: cross_val_score返回的score序列, eg: array([ 1. , 0.9 , 1. , 0.9 , 1. , 0.9 , 1. , 0.9 , 0.95, 1. ]) """ x = kwargs.pop("x", self.x) y = kwargs.pop("y", self.y) return self._do_cross_val_score(x, y, cv, _EMLScoreType.E_SCORE_ROC_AUC.value)

更多詳情實現請閱讀源代碼AbuML

下面使用train_test_split_xy查看訓練集輸出的precision_score,recall_score,混淆矩陣,以及f1等度量結果:

btc_ml.train_test_split_xy()x-y:(1363, 48)-(1363,)train_x-train_y:(1226, 48)-(1226,)test_x-test_y:(137, 48)-(137,)accuracy = 0.77precision_score = 0.62recall_score = 0.39 Predicted | 0 | 1 | |-----|-----| 0 | 90 | 9 |Actual |-----|-----| 1 | 23 | 15 | |-----|-----| precision recall f1-score support 0.0 0.80 0.91 0.85 99 1.0 0.62 0.39 0.48 38avg / total 0.75 0.77 0.75 137

下面通過plot_roc_estimator繪製roc曲線:

btc_ml.plot_roc_estimator()

下面通過plot_confusion_matrices繪製混淆矩陣:

btc_ml.plot_confusion_matrices()[[915 65] [183 200]]

下面通過plot_decision_function繪製決策邊界,由於繪製2d圖,內部已經使用pca將數據特徵降維後再繪製:

btc_ml.plot_decision_function()

下面通過plot_graphviz_tree繪製邏輯決策圖:

btc_ml.plot_graphviz_tree()RandomForestClassifier not hasattr tree_, use decision tree replace

下面通過feature_selection對特徵的支持度進行評級:

pd.options.display.max_rows = 48btc_ml.feature_selection(show=False).sort_values(by="ranking")

下面通過importances_coef_pd對特徵的重要程度進行量化:

btc_ml.importances_coef_pd().sort_values(by="importance")[::-1]

3. 測試集的驗證與非均衡技術

下面將前面保留切割的60條測試數據進行特徵抽取組合,方式和抽取訓練集時一樣,代碼如下所示:

btc_test0 = btc_siblings_df(btc_test_raw)btc_test1 = btc_siblings_df(btc_test_raw[1:])btc_test2 = btc_siblings_df(btc_test_raw[2:])btc_test = pd.concat([btc_test0, btc_test1, btc_test2])btc_test.index = np.arange(0, btc_test.shape[0])dummies_one_week = pd.get_dummies(btc_test["one_date_week"], prefix="one_date_week")dummies_two_week = pd.get_dummies(btc_test["two_date_week"], prefix="two_date_week")dummies_today_week = pd.get_dummies(btc_test["today_date_week"], prefix="today_date_week")btc_test.drop(["one_date_week", "two_date_week", "today_date_week"], inplace=True, axis=1)btc_test = pd.concat([btc_test, dummies_one_week, dummies_two_week, dummies_today_week], axis=1)matrix_test = btc_test.as_matrix()y_test = matrix_test[:, 0]x_test = matrix_test[:, 1:]

對測試集數據進行準確率評估,代碼如下所示:

from sklearn import metricsy_predict = [btc_ml.predict(x_test[test_ind])[0] for test_ind in np.arange(0, matrix_test.shape[0])]print("測試集正確率{:3f}".format(metrics.accuracy_score(y_test, y_predict)))測試集正確率0.620690

如上所示上面的準確率結果為60%以上正確,下面使用predict_proba看看概率結果:

y_prob = [btc_ml.predict_proba(x_test[test_ind])[0] for test_ind in np.arange(0, matrix_test.shape[0])]y_prob[-10:][array([ 0.495, 0.505]), array([ 0.9075, 0.0925]), array([ 0.7875, 0.2125]), array([ 0.83, 0.17]), array([ 0.8375, 0.1625]), array([ 0.96, 0.04]), array([ 0.58, 0.42]), array([ 0.495, 0.505]), array([ 0.6575, 0.3425]), array([ 0.565, 0.435])]

本節開始的時候說過很多時候定估的決策只要能滿足有時候能解出合理的解就可以,通過非均衡技術對決策進行二次邏輯過濾即可,下面解釋這句話的意思,通過上面y_predict輸出你可以看到準確率結果為60%以上,如果你只按照這個決策認定比特幣今天是否有大行情還是有很多錯誤的可能,上面的y_prob輸出了每一個決策的概率。

那麼我們如果希望做比特幣交易只希望在決策模型有很大把握的情況下才做,否則寧可少賺點錢也不做應該怎麼做呢?

上面predict的決策在某一個值在0.5以上即進行了判斷,如果我們希望把握更大一些就需要調整這個值,比如0.55以上才能認定決策,那首要的問題就是如何選定這個閥值,下面示例使用search_match_pos_threshold進行閥值的確定:

btc_ml.search_match_pos_threshold(accuracy_match=0.85)0.580 satisfy require, accuracy:0.854, effect_rate:0.879

結果如上所示,search_match_pos_threshold函數的作用是對訓練集數據進行非均衡結果度量,從0.5至0.99的範圍內開始不斷向上調整決策閥值:

比如測試閥值為0.55,那麼如果決策的概率為array([ 0.5378, 0.4622]),那麼通過閥值二分化後結果為(0, 0),這個結果將不計入正確率統計中,即認為未達成有效的決策,但如果決策的概率為 array([ 0.9711, 0.0289]),那麼通過閥值二分化後結果為(1, 0),認為達成有效的決策,當有效的決策正確率達到參數accuracy_match傳遞的值,本例中使用0.85即85%的正確率時,停止搜索,本例返回的結果為0.580,即使用閥值0.580可以達成非均衡決策的85%正確率。

備註:與之對應search_match_neg_threshold進行閥值搜索,從0.5至0.01的範圍內開始不斷向下調整決策閥值,更多詳情請閱讀源代碼。

下面使用0.580做為閥值,通過predict_proba_threshold函數進行決策,代碼如下:

y_prob_threshold = [btc_ml.predict_proba_threshold(x_test[test_ind], 0.580, 0) for test_ind in np.arange(0, matrix_test.shape[0])]

上面predict_proba_threshold傳遞的第三個參數0為未達成有效決策的情況下返回的決策結果,即閥值二分化後結果為(0, 0)後這裡返回0,因為在比特幣這個示例中如果交易者想要保守的方式決策今天是否適合做日內交易,那麼就希望只在有很大概率的情況下返回1,即適合交易,其它情況下沒有太大把握下全部返回0即可,下面使用precision_score計算查准率:

metrics.precision_score(y_test, y_prob_threshold)1.0

如上所示查准率100%正確,但召回率評分非常低,即比特幣在很多有大行情的情況下為了保守,放棄了日交易,只在把握大的時候行動:

metrics.recall_score(y_test, y_prob_threshold)0.20000000000000001

與上面的情況相反也有些激進的交易者想要的決策結果是只要今天不是很大把握說沒有行情,那就進行交易。

下面使用0.90做為閥值,通過predict_proba_threshold函數進行決策,第三個參數1即在為未達成有效決策的情況下返回的決策結果為1,可以看到結果的決策中大多數都被決策為1,代碼如下:

y_prob_threshold = [btc_ml.predict_proba_threshold(x_test[test_ind], 0.90, 1) for test_ind in np.arange(0, matrix_test.shape[0])]pd.Series(y_prob_threshold).value_counts()1 490 9dtype: int64

4. 繼承AbuMLPd對數據處理進行封裝

在abupy中不建議直接使用AbuML類進行構造,推薦使用繼承AbuMLPd後實現make_xy方法,在make_xy中將數據訓練集和測試集進行組裝完成,AbuMLPd基類通過代理方法對AbuML中的所有方法進行代理,即可以和使用AbuML中的方法一樣的介面操作,本節使用的示例內置在abupy項目中BtcBigWaveClf類,具體實現請直接閱讀BtcBigWaveClf。

小結:由於abupy中內置沙盒數據沒有分時的數據,所以本示例使用日線數據做為分析對象,實際策略中應該使用的是分鐘數據,本節示例主要為配合abupy中機器學習模塊的使用示例所寫,與真實的日內交易決策還有很大差別,在後面的章節涉及到實盤交易章節會具體詳細的示例完整的一個日內交易策略,請關注公眾號的更新提醒。

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

推薦閱讀:

一個 MXNet 實現的 Mask R-CNN.
域名關聯模型:讓惡意軟體自我暴露
新手學機器學習一個月後,一些第一手乾貨分享
李宏毅機器學習2016 第十五講 無監督學習 生成模型之 VAE

TAG:机器学习 | 比特币Bitcoin | 量化交易 |