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