器學(xué)習(xí)的多因子研究框架:從XGBoost到貝葉斯優(yōu)化實(shí)戰(zhàn))
簡(jiǎn)介一套基于機(jī)器學(xué)習(xí)的多因子研究框架配套源代碼與文檔說(shuō)明面向量化研究、金融數(shù)據(jù)分析人員及數(shù)據(jù)科學(xué)家覆蓋從因子篩選、收益預(yù)測(cè)、風(fēng)險(xiǎn)量化到組合配置的完整流程??蚣軆?nèi)部整合XGBoost、LightGBM兩種梯度提升算法用于收益建模并借助貝葉斯優(yōu)化實(shí)現(xiàn)超參數(shù)自動(dòng)尋優(yōu)同時(shí)納入了波動(dòng)率、VaR等風(fēng)險(xiǎn)度量與均值-方差、Black-Litterman等組合優(yōu)化方案便于直接復(fù)現(xiàn)和二次開(kāi)發(fā)。壓縮包共三百零六個(gè)文件以表格數(shù)據(jù)、交互式分析筆記、程序腳本及數(shù)據(jù)庫(kù)文件為主另含有C/C、Java等輔助代碼整體大小約三十八點(diǎn)九八兆字節(jié)。包內(nèi)目錄結(jié)構(gòu)清晰包含模型日志、因子數(shù)據(jù)、腳本實(shí)現(xiàn)與說(shuō)明文檔便于按模塊進(jìn)行復(fù)現(xiàn)與擴(kuò)展。該資源已吸引349人學(xué)習(xí)對(duì)希望系統(tǒng)掌握機(jī)器學(xué)習(xí)多因子建模流程、或正在構(gòu)建量化投研框架的讀者具有較高參考價(jià)值。1. 從調(diào)參翻車現(xiàn)場(chǎng)說(shuō)起基于機(jī)器學(xué)習(xí)的多因子研究框架能解決什么做多因子研究的人遲早會(huì)碰上一個(gè)尷尬時(shí)刻因子庫(kù)攢了幾百個(gè)手工回測(cè)一個(gè)個(gè)看效率低不說(shuō)還容易漏掉因子之間的交互關(guān)系。基于機(jī)器學(xué)習(xí)的多因子研究框架解決的就是這件事——把因子選擇、收益預(yù)測(cè)模型、超參數(shù)尋優(yōu)、風(fēng)險(xiǎn)約束和組合優(yōu)化放進(jìn)同一條流水線讓研究從“手工試錯(cuò)”變成“機(jī)器迭代”。這份資源里包含多個(gè) XGBoost 訓(xùn)練日志、一份貝葉斯優(yōu)化調(diào)參日志以及 svmtrain.c 源碼覆蓋從收益模型到風(fēng)險(xiǎn)模型的完整鏈路。適合量化分析入門(mén)開(kāi)發(fā)者、因子研究相關(guān)從業(yè)者以及想了解機(jī)器學(xué)習(xí)怎么在投資研究里落地的數(shù)據(jù)工程師。2. 框架整體拆解從日志文件到三層研究流水線2.1 先看目錄文件命名和日志時(shí)間線還原研究節(jié)奏拿到資源先把文件列表鋪開(kāi)看一遍這就是研究者的“操作軌跡”。這份資源里的文件按命名規(guī)則可以分成三組m1_xgboost.log.2019-11-21 至 m1_xgboost.log.2019-11-25基礎(chǔ) XGBoost 模型訓(xùn)練日志間隔幾天跑一次BayesOptimizationXGBoost.log.2019-12-16貝葉斯優(yōu)化調(diào)參日志與 m1_xgboost.log.2019-12-16 同一天出現(xiàn)svmtrain.cSVM 訓(xùn)練源碼文件大概率承擔(dān)基線模型或?qū)φ諏?shí)驗(yàn)的角色。這里有個(gè)很關(guān)鍵的觀察點(diǎn)12 月 16 日之前日志里只有 m1_xgboost 系列說(shuō)明這個(gè)階段還在用默認(rèn)參數(shù)或者人工給定的參數(shù)直接訓(xùn)練12 月 16 日出現(xiàn) BayesOptimizationXGBoost.log說(shuō)明研究者把調(diào)參環(huán)節(jié)正式納入了研究流程。日志命名里的日期就是迭代輪次的時(shí)間戳這也是實(shí)踐中的一個(gè)好習(xí)慣——訓(xùn)練腳本輸出的日志一定要帶日期。后續(xù)你想回溯某一次調(diào)參到底改了哪些參數(shù)看日志比翻聊天記錄靠譜得多。從實(shí)現(xiàn)角度看框架更像一條流水線而不是單個(gè)模型。收益預(yù)測(cè)模型負(fù)責(zé)從因子矩陣算出預(yù)期收益風(fēng)險(xiǎn)模型負(fù)責(zé)量化波動(dòng)和極端虧損組合優(yōu)化則把前兩者的輸出揉成最終持倉(cāng)權(quán)重。三個(gè)環(huán)節(jié)各有各的文件和參數(shù)通過(guò)數(shù)據(jù)接口串起來(lái)。2.2 三層架構(gòu)收益模型、風(fēng)險(xiǎn)模型與組合優(yōu)化如何配合整個(gè)研究框架可以理解為三層結(jié)構(gòu)第一層是收益模型層。輸入是預(yù)處理后的因子面板數(shù)據(jù)輸出是每個(gè)標(biāo)的在未來(lái)一段時(shí)間內(nèi)的預(yù)期收益。這一層使用 XGBoost 和 LightGBM 這類梯度提升樹(shù)模型因?yàn)樗鼈兡懿蹲揭蜃又g的非線性關(guān)系和交互效應(yīng)這是線性回歸做不到的。比如“低換手率 高動(dòng)量”這種組合對(duì)收益的影響樹(shù)模型可以通過(guò)分裂路徑自動(dòng)學(xué)到。第二層是風(fēng)險(xiǎn)模型層。這一層計(jì)算組合的波動(dòng)率、VaRValue at Risk和 CVaRConditional VaR用來(lái)量化極端行情下的潛在虧損。VaR 回答“在當(dāng)前置信水平下最大可能虧損是多少”CVaR 更進(jìn)一步衡量“一旦虧損超過(guò) VaR 閾值平均會(huì)虧多少”。這兩個(gè)指標(biāo)決定了后面組合優(yōu)化的風(fēng)險(xiǎn)約束邊界。第三層是組合優(yōu)化層。在收益預(yù)測(cè)和風(fēng)險(xiǎn)度量都有了之后用均值-方差優(yōu)化Markowitz尋找有效前沿上的最佳權(quán)重組合。還可以用 Black-Litterman 模型把研究者自己的觀點(diǎn)揉進(jìn)先驗(yàn)收益里讓權(quán)重結(jié)果更貼近實(shí)際判斷。三層之間有明確的數(shù)據(jù)流因子矩陣 → 收益預(yù)測(cè) → 風(fēng)險(xiǎn)估計(jì) → 權(quán)重生成。這個(gè)框架的本質(zhì)就是把“選什么、配多少、怎么防回撤”三個(gè)問(wèn)題統(tǒng)一到一個(gè)系統(tǒng)里解決。2.3 為什么多因子研究選樹(shù)模型而不是傳統(tǒng)線性回歸選 XGBoost / LightGBM 而不是傳統(tǒng)線性回歸核心原因是因子和收益之間的關(guān)系根本不是線性的。比如流動(dòng)性因子的影響在極低流動(dòng)性區(qū)間收益表現(xiàn)可能反轉(zhuǎn)這種倒 U 型關(guān)系在線性模型里只能靠手動(dòng)構(gòu)造多項(xiàng)式項(xiàng)去逼近而樹(shù)模型通過(guò)閾值分裂天然就能擬合分段非線性。另一個(gè)原因在于特征交互。單個(gè)因子可能預(yù)測(cè)能力很弱但“小市值 高波動(dòng) 低動(dòng)量”這種組合的預(yù)測(cè)能力會(huì)明顯增強(qiáng)樹(shù)模型在分裂過(guò)程中會(huì)隱式地構(gòu)造這種交互特征線性模型要顯式建模就麻煩得多。在選型上XGBoost 和 LightGBM 各有側(cè)重。XGBoost 對(duì)缺失值有內(nèi)置處理策略自帶正則化項(xiàng)在中小規(guī)模因子上表現(xiàn)穩(wěn)定LightGBM 采用單邊梯度采樣和互斥特征捆綁訓(xùn)練速度更快適合樣本量大的場(chǎng)景。svmtrain.c 的存在則補(bǔ)足了對(duì)照實(shí)驗(yàn)——SVM 在低維小樣本因子上往往有不錯(cuò)表現(xiàn)用它在同一套數(shù)據(jù)上訓(xùn)練可以衡量樹(shù)模型的增益到底有多大。常見(jiàn)做法是把 SVM 作為基線跑完一套指標(biāo)之后再上 XGBoost這樣能清楚看到樹(shù)模型帶來(lái)了多少提升。3. 收益模型實(shí)戰(zhàn)XGBoost 訓(xùn)練流程與參數(shù)體系3.1 從因子面板到訓(xùn)練樣本數(shù)據(jù)切分和標(biāo)簽構(gòu)造的完整流程XGBoost 收益模型的第一步是整理因子面板數(shù)據(jù)。通常一張因子面板包含日期、標(biāo)的代碼、若干因子列和價(jià)格數(shù)據(jù)。在構(gòu)造標(biāo)簽時(shí)要格外小心用未來(lái)五天收益作為預(yù)測(cè)目標(biāo)意味著用 t 日截面因子預(yù)測(cè) t1 到 t5 的累計(jì)收益。import pandas as pd import numpy as np import xgboost as xgb # 讀取因子面板按日期和標(biāo)的全排序 df pd.read_csv(factor_panel.csv, parse_dates[date]) df df.sort_values([date, symbol]).reset_index(dropTrue) # 構(gòu)造未來(lái)5日收益作為標(biāo)簽注意按每個(gè)標(biāo)的單獨(dú)shift df[label] df.groupby(symbol)[close].transform( lambda s: s.shift(-5) / s - 1.0 ) # 因子列篩選假設(shè)數(shù)據(jù)集中因子列都以 factor_ 開(kāi)頭 feature_cols [c for c in df.columns if c.startswith(factor_)] # 嚴(yán)格按時(shí)間切分防止未來(lái)數(shù)據(jù)泄露進(jìn)訓(xùn)練集 train df[df[date] 2019-10-01].dropna(subset[label]) test df[df[date] 2019-10-01].dropna(subset[label]) dtrain xgb.DMatrix(train[feature_cols], labeltrain[label]) dtest xgb.DMatrix(test[feature_cols], labeltest[label]) # 基礎(chǔ)參數(shù)配置 params { objective: reg:squarederror, learning_rate: 0.05, max_depth: 4, min_child_weight: 5, subsample: 0.8, colsample_bytree: 0.6, lambda: 1.0, alpha: 0.5, eval_metric: rmse, seed: 42 } model xgb.train( params, dtrain, num_boost_round500, evals[(dtrain, train), (dtest, test)], early_stopping_rounds50, verbose_eval10 )這段代碼里最容易被忽略的是標(biāo)簽構(gòu)造那一行。df.groupby(symbol)[close].shift(-5) 是按每個(gè)標(biāo)的單獨(dú)前移價(jià)格而不是對(duì)整個(gè)面板做 shift。如果寫(xiě)成 df[close].shift(-5)跨界面的數(shù)據(jù)就會(huì)把前一個(gè)標(biāo)的的收盤(pán)價(jià)挪到當(dāng)前標(biāo)的最后一行上構(gòu)造出來(lái)的標(biāo)簽全是錯(cuò)位的模型訓(xùn)練出來(lái)預(yù)測(cè)能力幾乎為零。時(shí)間切分也需要注意train/test 必須按日期硬切不能用隨機(jī)切分。金融時(shí)間序列有強(qiáng)烈的自相關(guān)隨機(jī)打亂會(huì)讓模型“偷看”到未來(lái)信息回測(cè)時(shí)拿到虛高的收益實(shí)盤(pán)基本翻車。3.2 關(guān)鍵參數(shù)在因子場(chǎng)景下的取值邏輯XGBoost 參數(shù)不少真正在研究場(chǎng)景里高頻調(diào)整的其實(shí)就七八個(gè)。下表是我在這個(gè)框架中常用的取值區(qū)間和調(diào)整邏輯參數(shù)常用取值調(diào)整邏輯learning_rate0.01~0.1越小越穩(wěn)但需要更多迭代輪數(shù)因子噪聲大0.05 左右起步比較合理max_depth3~6因子數(shù)據(jù)量通常不大深度超過(guò) 6 容易過(guò)擬合單因子噪聲min_child_weight3~10控制葉子節(jié)點(diǎn)最小樣本權(quán)重值越大越保守能抑制極端樣本影響subsample0.6~0.9行采樣比例下降能減少過(guò)擬合但太低會(huì)讓模型欠擬合colsample_bytree0.5~0.8列采樣比例直接控制每棵樹(shù)用多少因子減小因子間共線性影響lambda / alpha0.1~2.0L2/L1 正則化因子多的時(shí)候把這兩個(gè)參數(shù)調(diào)大可以讓權(quán)重更平滑early_stopping_rounds30~80建議設(shè)置否則模型迭代到最后必定過(guò)擬合有一個(gè)參數(shù)組合要特別警惕learning_rate 很低比如 0.01但同時(shí) num_boost_round 固定不改會(huì)導(dǎo)致模型欠擬合。在因子場(chǎng)景里我一般會(huì)把學(xué)習(xí)率固定在 0.05然后靠 early_stopping_rounds 自動(dòng)決定迭代次數(shù)而不是拍腦袋定一個(gè)固定輪數(shù)。此外 min_child_weight 這個(gè)參數(shù)在金融數(shù)據(jù)里比圖像分類場(chǎng)景重要得多——金融標(biāo)簽噪聲大偶爾一個(gè)極端收益樣本就能帶偏葉子節(jié)點(diǎn)把它從默認(rèn)的 1 調(diào)到 5 以上能顯著提升回測(cè)穩(wěn)定性。3.3 從 svmtrain.c 看基線模型的價(jià)值svmtrain.c 是這份資源里唯一的 C 源碼文件。這類文件通常實(shí)現(xiàn)的是 SVM 訓(xùn)練核心流程包括核函數(shù)計(jì)算、SMO 求解或梯度下降等步驟。在多因子框架中它的定位一般是基線模型和 XGBoost 做對(duì)比實(shí)驗(yàn)。/* svmtrain.c 核心邏輯示意加載特征矩陣設(shè)置SVM參數(shù)并進(jìn)行訓(xùn)練 */ double *labels load_labels(train_label.csv); double *features load_features(train_feature.csv); svm_parameter param; param.kernel_type RBF_KERNEL; /* 高斯核適合非線性因子 */ param.gamma 0.01; /* 核寬度越大越容易過(guò)擬合 */ param.C 1.0; /* 懲罰系數(shù)權(quán)衡誤分類與間隔 */ svm_model *model svm_train(features, labels, param, n_samples, n_features);在因子研究里SVM 和 XGBoost 的對(duì)比邏輯很清晰。SVM 對(duì)特征尺度敏感必須先做標(biāo)準(zhǔn)化XGBoost 基于樹(shù)分裂尺度不影響預(yù)測(cè)結(jié)果。如果標(biāo)準(zhǔn)化之前的因子數(shù)據(jù)直接丟進(jìn) SVM訓(xùn)練出來(lái)的模型會(huì)偏向量綱大的特征這個(gè)對(duì)比就顯得不公平。我一般先對(duì)因子做截面標(biāo)準(zhǔn)化再分別跑 SVM 和 XGBoost記錄 RMSE 和 IC信息系數(shù)看樹(shù)模型相對(duì)基線提升了多少。這個(gè)對(duì)比在論文和實(shí)戰(zhàn)報(bào)告里都是一個(gè)必要的支撐材料。4. 貝葉斯優(yōu)化調(diào)參把“看運(yùn)氣”變成可復(fù)現(xiàn)的搜索過(guò)程4.1 為什么貝葉斯優(yōu)化比網(wǎng)格搜索更適合因子模型網(wǎng)格搜索在多因子場(chǎng)景里幾乎不可用。假設(shè)要調(diào)節(jié) 7 個(gè)參數(shù)每個(gè)參數(shù)給 5 個(gè)候選值組合數(shù)就是 5 的 7 次方約 7.8 萬(wàn)次訓(xùn)練。每次訓(xùn)練跑 500 棵樹(shù)這個(gè)計(jì)算量在因子數(shù)據(jù)量小的時(shí)候還能忍數(shù)據(jù)量一上來(lái)直接撐不住。隨機(jī)搜索雖然減少了試驗(yàn)次數(shù)但完全是盲猜不利用已經(jīng)跑過(guò)的結(jié)果信息。貝葉斯優(yōu)化的思路是建立一個(gè)代理模型通常用高斯過(guò)程或 TPE用歷史試驗(yàn)結(jié)果預(yù)測(cè)哪些參數(shù)組合更有可能帶來(lái)低損失。每次迭代先由代理模型給出候選點(diǎn)再在真實(shí)數(shù)據(jù)上跑一次訓(xùn)練把新結(jié)果更新進(jìn)代理模型如此反復(fù)。這個(gè)機(jī)制的效率來(lái)自兩點(diǎn)一是代理模型能估計(jì)參數(shù)空間里每一塊區(qū)域的潛在收益二是搜索過(guò)程會(huì)自動(dòng)避開(kāi)已經(jīng)驗(yàn)證效果差的區(qū)域。對(duì)比來(lái)看網(wǎng)格搜索的缺陷在于“均勻撒網(wǎng)”不考慮反饋貝葉斯優(yōu)化則是“邊試邊學(xué)”。在因子模型調(diào)參這種每輪訓(xùn)練成本不低的場(chǎng)景下貝葉斯優(yōu)化是當(dāng)前實(shí)踐中最合理的默認(rèn)選擇。4.2 搜索空間設(shè)計(jì)與迭代實(shí)現(xiàn)用 Hyperopt 庫(kù)實(shí)現(xiàn)貝葉斯優(yōu)化是常見(jiàn)做法。核心是先定義參數(shù)搜索空間再定義一個(gè)目標(biāo)函數(shù)讓優(yōu)化器最小化這個(gè)函數(shù)的返回值。import numpy as np import xgboost as xgb from hyperopt import fmin, tpe, hp, Trials, STATUS_OK def objective(params): # 注意Hyperopt 搜索出來(lái)的某些參數(shù)需要轉(zhuǎn)成整數(shù) params { learning_rate: params[learning_rate], max_depth: int(params[max_depth]), min_child_weight: int(params[min_child_weight]), subsample: params[subsample], colsample_bytree: params[colsample_bytree], reg_lambda: params[reg_lambda], reg_alpha: params[reg_alpha], seed: 42 } # 自定義時(shí)序交叉驗(yàn)證5折walk-forward fold_dates np.percentile(train[date].astype(np.int64), [20, 40, 60, 80]) scores [] for i in range(4): tr train[train[date] fold_dates[i]] va train[(train[date] fold_dates[i]) (train[date] fold_dates[i1])] dtr xgb.DMatrix(tr[feature_cols], labeltr[label]) dva xgb.DMatrix(va[feature_cols], labelva[label]) model xgb.train( params, dtr, num_boost_round300, evals[(dva, valid)], early_stopping_rounds30, verbose_evalFalse ) pred model.predict(dva) ic np.corrcoef(pred, va[label])[0, 1] scores.append(ic) # 取平均IC的負(fù)值作為損失IC越高損失越低 return {loss: -np.mean(scores), status: STATUS_OK} space { learning_rate: hp.uniform(learning_rate, 0.01, 0.2), max_depth: hp.quniform(max_depth, 3, 8, 1), min_child_weight: hp.quniform(min_child_weight, 1, 10, 1), subsample: hp.uniform(subsample, 0.5, 1.0), colsample_bytree: hp.uniform(colsample_bytree, 0.3, 0.9), reg_lambda: hp.loguniform(reg_lambda, np.log(0.1), np.log(5.0)), reg_alpha: hp.loguniform(reg_alpha, np.log(0.1), np.log(5.0)) } trials Trials() best fmin( fnobjective, spacespace, algotpe.suggest, max_evals50, trialstrials, rstatenp.random.RandomState(42) ) print(Best params:, best)這里最關(guān)鍵的設(shè)計(jì)決策是目標(biāo)函數(shù)的選擇。我選擇 5 折時(shí)序交叉驗(yàn)證的平均 IC 作為優(yōu)化目標(biāo)而不是直接把 RMSE 最小化。原因是因子研究最終關(guān)心的是排序能力——預(yù)測(cè)值與實(shí)際收益之間的秩相關(guān)性而 IC 正好度量這個(gè)排序能力。如果用 RMSE 做目標(biāo)優(yōu)化出來(lái)的模型可能整體偏差小但排序能力弱這在選股場(chǎng)景里沒(méi)有意義。此外時(shí)序交叉驗(yàn)證的切分點(diǎn)用的是 train 數(shù)據(jù)里的日期分位數(shù)保證每一折的訓(xùn)練集都嚴(yán)格在驗(yàn)證集之前。搜索空間里 max_depth 和 min_child_weight 用了 quniform 加 int 轉(zhuǎn)換因?yàn)闃?shù)的深度必須取整數(shù)。learning_rate 用 uniform 在 0.01 到 0.2 之間連續(xù)采樣這個(gè)區(qū)間覆蓋了從保守到激進(jìn)的常見(jiàn)范圍。reg_lambda 和 reg_alpha 用 loguniform原因是正則化系數(shù)的有效范圍跨越多個(gè)數(shù)量級(jí)對(duì)數(shù)均勻采樣能讓小值區(qū)間和大值區(qū)間有相同的被探索概率。4.3 從貝葉斯優(yōu)化日志中讀取有效信息BayesOptimizationXGBoost.log 這類日志記錄了每一輪迭代的參數(shù)組合、損失值和耗時(shí)。你在看日志的時(shí)候不要只盯著最后一行“best result”而是要關(guān)注中間過(guò)程前 10 輪探索階段損失值波動(dòng)較大說(shuō)明代理模型還在學(xué)習(xí)參數(shù)空間的“地形圖”10-20 輪左右損失開(kāi)始收斂出現(xiàn)的參數(shù)組合逐步集中在某一區(qū)域20 輪之后如果損失不再明顯下降說(shuō)明搜索已經(jīng)接近當(dāng)前空間下的最優(yōu)點(diǎn)。有個(gè)經(jīng)驗(yàn)值得留意如果前幾輪就出現(xiàn)一個(gè)異常高的 IC比如超過(guò) 0.15先別高興這大概率是偶然結(jié)果。我會(huì)在貝葉斯優(yōu)化結(jié)束后拿最優(yōu)參數(shù)在不同隨機(jī)種子下跑 3-5 次完整訓(xùn)練看 IC 的均值和方差。均值穩(wěn)定在 0.06 以上、方差不超過(guò) 0.01才說(shuō)明參數(shù)組合真正有效而不是碰上了一個(gè)運(yùn)氣好的數(shù)據(jù)切片。5. 避坑與排查多因子框架落地過(guò)程中記錄的四條實(shí)戰(zhàn)教訓(xùn)5.1 現(xiàn)象訓(xùn)練集 IC 很高回測(cè)卻穩(wěn)定虧損這是多因子研究里最典型的翻車場(chǎng)景。模型在訓(xùn)練集上 IC 達(dá)到 0.12回測(cè)一跑直接月度負(fù)收益怎么看都不對(duì)。原因通常是兩大類。一是時(shí)間序列泄露預(yù)處理階段用全樣本數(shù)據(jù)計(jì)算了標(biāo)準(zhǔn)化參數(shù)或因子正交化矩陣這部分信息偷偷進(jìn)入了訓(xùn)練集。二是標(biāo)簽構(gòu)造錯(cuò)位剛才提到過(guò)的 groupby shift 問(wèn)題或者復(fù)權(quán)價(jià)格使用不當(dāng)導(dǎo)致未來(lái)價(jià)格被改寫(xiě)。解決方法是把數(shù)據(jù)預(yù)處理的 fit 和 transform 放進(jìn)時(shí)間切分循環(huán)內(nèi)部全樣本只做一次通用清洗所有涉及統(tǒng)計(jì)量的計(jì)算必須在訓(xùn)練折疊里重新擬合。5.2 現(xiàn)象貝葉斯優(yōu)化得到的最優(yōu)參數(shù)換一段樣本就失效貝葉斯優(yōu)化跑完拿最優(yōu)參數(shù)在另一段歷史數(shù)據(jù)上驗(yàn)證IC 掉了一半。這不是優(yōu)化器出了問(wèn)題而是過(guò)擬合了優(yōu)化目標(biāo)。每次貝葉斯迭代都是用同一個(gè)驗(yàn)證集評(píng)估損失迭代次數(shù)多了參數(shù)會(huì)慢慢記住驗(yàn)證集上的噪聲分布。解決思路有兩個(gè)。第一個(gè)是增加交叉驗(yàn)證折數(shù)把單驗(yàn)證集改成多段折疊取平均削弱單一段樣本的噪聲影響。第二個(gè)是限定搜索輪數(shù)max_evals 不要開(kāi)太大50 輪左右足夠后面每多一輪都在增加對(duì)驗(yàn)證集的過(guò)擬合。5.3 現(xiàn)象XGBoost 和 LightGBM 在同一份數(shù)據(jù)上指標(biāo)互相矛盾比如 XGBoost 跑出來(lái)因子 A 重要性最高LightGBM 跑出來(lái)因子 B 重要性最高。這個(gè)現(xiàn)象在因子研究里經(jīng)常碰見(jiàn)原因在于兩個(gè)框架的特征分裂策略和樣本采樣方式不同。XGBoost 按特征預(yù)排序后做精確貪心分裂LightGBM 用直方圖算法做近似分裂這兩個(gè)機(jī)制對(duì)噪聲因子和離散因子的敏感度不一樣。加上 LightGBM 默認(rèn)的 subsample 策略是隨機(jī)采樣隨機(jī)種子不同也會(huì)造成重要性的順序漂移。解決方法是不要拿一次訓(xùn)練結(jié)果下結(jié)論固定隨機(jī)種子后做多次重復(fù)訓(xùn)練比較因子重要性的穩(wěn)定排名。同時(shí)把兩個(gè)模型的評(píng)估指標(biāo)統(tǒng)一成同一種手動(dòng)檢查參數(shù)語(yǔ)義是否一致比如 min_child_weight 在 XGBoost 里是樣本權(quán)重和LightGBM 里對(duì)應(yīng) min_data_in_leaf 的語(yǔ)義完全不同。5.4 現(xiàn)象日志文件不連續(xù)中間隔了好幾天沒(méi)記錄資源里 m1_xgboost.log 在 11 月 25 日之后直接跳到 12 月 16 日中間隔了二十天。這個(gè)現(xiàn)象在真實(shí)研究里非常常見(jiàn)中間這段時(shí)間往往是在做因子數(shù)據(jù)清洗或者特征工程沒(méi)有跑模型。排查時(shí)不要默認(rèn)漏了日志先看前后兩天的參數(shù)配置是否有變化。比如 11 月 22 日的日志里 max_depth 是 511 月 25 日變成了 4說(shuō)明中間大概率做了一次特征篩選把高相關(guān)因子去掉了所以降低樹(shù)的深度來(lái)減少過(guò)擬合。提示在日志文件的參數(shù)配置里加上 commit 號(hào)或備注字段比單獨(dú)維護(hù) changelog 更直接這樣斷檔期間改了什么一眼就能看到。這一條解決方式看起來(lái)簡(jiǎn)單實(shí)際執(zhí)行起來(lái)能省掉大量回溯時(shí)間。從那以后我每次訓(xùn)練腳本都會(huì)在日志文件名末尾追加一個(gè)短備注比如 m1_xgboost.log.2019-11-25.depth4_feat30二十天后回來(lái)看也清楚當(dāng)時(shí)發(fā)生了什么。6. 風(fēng)險(xiǎn)模型與組合優(yōu)化把收益預(yù)測(cè)落成真正的持倉(cāng)權(quán)重有了收益預(yù)測(cè)和參數(shù)穩(wěn)定的模型還差最后一步怎么分配倉(cāng)位。風(fēng)險(xiǎn)模型和組合優(yōu)化就是把預(yù)測(cè)結(jié)果變成可執(zhí)行權(quán)重的環(huán)節(jié)。在均值-方差優(yōu)化的框架下目標(biāo)函數(shù)是最小化組合波動(dòng)率同時(shí)滿足預(yù)期收益約束也可以用夏普比率最大化作為目標(biāo)import numpy as np from scipy.optimize import minimize # 假設(shè)有12個(gè)標(biāo)的mu為模型預(yù)測(cè)日度收益cov為協(xié)方差矩陣 mu np.array([0.0012, 0.0008, 0.0021, 0.0005, 0.0016, 0.0009, 0.0013, 0.0011, 0.0007, 0.0014, 0.0010, 0.0006]) cov np.random.rand(12, 12) * 0.01 cov (cov cov.T) / 2 np.eye(12) * 0.02 def portfolio_vol(w, cov): return np.sqrt(w cov w) def neg_sharpe(w, mu, cov, rf0.0002): ret w mu - rf vol portfolio_vol(w, cov) return -(ret / vol) # 約束滿倉(cāng)、不做空、單標(biāo)的上限不超過(guò)20% constraints [ {type: eq, fun: lambda w: np.sum(w) - 1.0} ] bounds [(0.0, 0.2) for _ in range(len(mu))] w0 np.ones(len(mu)) / len(mu) result minimize( neg_sharpe, w0, args(mu, cov), methodSLSQP, boundsbounds, constraintsconstraints ) weights result.x print(Optimal weights:, np.round(weights, 4))這個(gè)優(yōu)化器的輸出就是最終持倉(cāng)權(quán)重。實(shí)際落地時(shí)有幾點(diǎn)要注意協(xié)方差矩陣建議用歷史日度收益數(shù)據(jù)計(jì)算也可以用 EWMA 給近端數(shù)據(jù)更高的權(quán)重單標(biāo)的權(quán)重上限 20% 是常見(jiàn)控制集中度風(fēng)險(xiǎn)的約束也可以換成由最大回撤預(yù)算推導(dǎo)出的值。Black-Litterman 模型則是在這個(gè)基礎(chǔ)之上把研究者的主觀觀點(diǎn)作為額外輸入調(diào)整先驗(yàn)收益后重新優(yōu)化讓權(quán)重結(jié)果更貼近實(shí)際判斷。風(fēng)險(xiǎn)模型的落地也不復(fù)雜。VaR 用歷史模擬法直接在收益序列上取 95% 分位數(shù)CVaR 則取尾部超出部分的均值。這兩個(gè)指標(biāo)加進(jìn)組合優(yōu)化里就把“會(huì)虧多少”這個(gè)約束從定性變成了定量。執(zhí)行策略上我建議每周重算一次協(xié)方差矩陣模型預(yù)測(cè)更新時(shí)同步觸發(fā)權(quán)重再平衡避免因子信號(hào)變化了持倉(cāng)還停留在舊權(quán)重上。組合優(yōu)化看起來(lái)數(shù)學(xué)含量高本質(zhì)上就是一套帶約束的數(shù)學(xué)規(guī)劃邊界和約束設(shè)對(duì)了結(jié)果就能直接進(jìn)交易系統(tǒng)。從那以后我每次跑完因子模型的交叉驗(yàn)證都會(huì)強(qiáng)制走一遍“收益預(yù)測(cè) → 風(fēng)險(xiǎn)計(jì)算 → 權(quán)重生成”的閉環(huán)而不是看完 IC 就收工。模型預(yù)測(cè)再好落不成一組滿足風(fēng)險(xiǎn)約束的權(quán)重前面所有工作都還是停在紙上。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取