器學(xué)習(xí)模型股價(jià)預(yù)測(cè)實(shí)戰(zhàn):從數(shù)據(jù)預(yù)處理到滾動(dòng)回測(cè))
簡(jiǎn)介面向高校畢業(yè)設(shè)計(jì)、期末大作業(yè)和量化入門(mén)實(shí)踐的一份Python股價(jià)預(yù)測(cè)源碼項(xiàng)目圍繞股票價(jià)格數(shù)據(jù)建模實(shí)現(xiàn)LR、LSTM、ARIMA、KNN等多種經(jīng)典模型并給出了對(duì)比分析。代碼附詳細(xì)注釋涵蓋數(shù)據(jù)預(yù)處理、模型訓(xùn)練、回測(cè)與可視化等完整流程新手亦可讀懂簡(jiǎn)單部署就能運(yùn)行項(xiàng)目界面美觀、功能齊全具備較高的實(shí)際應(yīng)用價(jià)值。資源包共31個(gè)文件主要包含Python腳本、CSV/XLSX數(shù)據(jù)文件、HTML可視化頁(yè)面及PNG圖表另有README說(shuō)明文檔整體大小僅1.45MB目錄結(jié)構(gòu)清晰便于檢索。目前已有372人學(xué)習(xí)適合作為畢業(yè)設(shè)計(jì)、期末大作業(yè)或課程設(shè)計(jì)的高分參考。1. 股價(jià)預(yù)測(cè)不是玄學(xué)LR、LSTM、ARIMA、KNN 能不能用取決于你把它放在哪把 K 線數(shù)據(jù)和財(cái)經(jīng)新聞一起丟進(jìn) LSTM跑出一個(gè)「明日上漲概率 78%」的輸出再打開(kāi)回測(cè)一看年化收益翻倍——這是很多人踏入量化交易時(shí)做的第一件事也是虧錢(qián)最快的一件事。我見(jiàn)過(guò)的多數(shù)翻車(chē)并不是模型選錯(cuò)了而是把「做一個(gè)能評(píng)估、能對(duì)比、能復(fù)盤(pán)的分析系統(tǒng)」偷換成了「預(yù)測(cè)明天漲跌」。本文要講的是用 Python 把 LR、LSTM、ARIMA、KNN 這四種模型放進(jìn)同一個(gè)數(shù)據(jù)集、同一套評(píng)估標(biāo)準(zhǔn)下跑起來(lái)的完整流程能解決的核心問(wèn)題很具體換任何一只股票你都能在半小時(shí)內(nèi)知道哪些模型值得看下去哪些只是浪費(fèi)電。適合正在學(xué)機(jī)器學(xué)習(xí)、想做量化入門(mén)但還沒(méi)完整落地過(guò)數(shù)據(jù)分析閉環(huán)的朋友也適合處于機(jī)器學(xué)習(xí)復(fù)習(xí)期想把數(shù)據(jù)處理和模型評(píng)估流程串成體系的人。2. 數(shù)據(jù)是一切預(yù)測(cè)的地基字段清洗、特征構(gòu)造和按時(shí)間的正確切分先把話說(shuō)死在股價(jià)預(yù)測(cè)這件事上數(shù)據(jù)環(huán)節(jié)出的問(wèn)題比模型環(huán)節(jié)多得多。很多人拿著下載好的 CSV 就開(kāi)始調(diào) LSTM最后模型在訓(xùn)練集上 RMSE 低到離譜一換時(shí)間區(qū)間就原形畢露。原因基本都是數(shù)據(jù)沒(méi)對(duì)齊、特征里混入了未來(lái)信息、或者切分方式直接把時(shí)間順序打亂了。這一章按我慣用的流程走一遍跑通之后你就有了一份可以反復(fù)復(fù)用的數(shù)據(jù)管線。2.1 tushare 取數(shù)先處理復(fù)權(quán)和缺失值常見(jiàn)做法是通過(guò) tushare 這類金融數(shù)據(jù)接口拿日線行情。字段不需要多trade_date、open、high、low、close、vol六列足夠起步。下面這段代碼會(huì)把日線行情拉下來(lái)并按時(shí)間正序排好。import os import pandas as pd from tushare import Pro_api token os.getenv(TUSHARE_TOKEN, 你的token) pro Pro_api(token) df pro.stock_daily( ts_code000001.SZ, start_date20180101, end_date20231231 )[[trade_date, open, high, low, close, vol]] df[trade_date] pd.to_datetime(df[trade_date]) df df.sort_values(trade_date).reset_index(dropTrue) print(df.head())邏輯說(shuō)明ts_code傳的是股票代碼000001.SZ是平安銀行的日線數(shù)據(jù)你換成自己關(guān)注的標(biāo)的即可。接口返回的trade_date是字符串需要轉(zhuǎn)成datetime才能正確排序這一步漏了的話后面的滾動(dòng)窗口和序列構(gòu)造全是亂的。另一個(gè)容易被忽略的點(diǎn)是復(fù)權(quán)如果標(biāo)的在區(qū)間內(nèi)有過(guò)分紅送股未復(fù)權(quán)價(jià)格會(huì)出現(xiàn)跳空模型會(huì)以為股價(jià)發(fā)生了真實(shí)突變回測(cè)結(jié)果自然失真。更穩(wěn)的做法是用pro_bar接口加上adjqfq參數(shù)直接拿前復(fù)權(quán)數(shù)據(jù)。數(shù)據(jù)缺失時(shí)先看是不是停牌導(dǎo)致常見(jiàn)的處理是用ffill()向前填充或者直接刪掉空行不要在特征里留NaN。2.2 特征與標(biāo)簽機(jī)器學(xué)習(xí)中的數(shù)據(jù)處理核心是先算好明天數(shù)據(jù)處理好之后就要回答機(jī)器學(xué)習(xí)中的數(shù)據(jù)處理本質(zhì)問(wèn)題用什么特征去預(yù)測(cè)什么目標(biāo)。這里的目標(biāo)不是「明天的收盤(pán)價(jià)」而是「明天的收益率」。用收益率做標(biāo)簽?zāi)茏屝蛄衅椒€(wěn)很多模型學(xué)的是漲跌幅度而不是絕對(duì)價(jià)格否則把價(jià)格直接當(dāng)回歸目標(biāo)模型學(xué)到的其實(shí)是「最近價(jià)格慣性」換一只高價(jià)股就失效。df[ret] df[close].pct_change() # 當(dāng)日收益率 df[ma5] df[close].rolling(5).mean() # 5日均線 df[ma20] df[close].rolling(20).mean() # 20日均線 df[mom5] df[close].pct_change(5) # 5日動(dòng)量 df[vol_std] df[ret].rolling(10).std() # 收益率波動(dòng) feature_cols [ret, ma5, ma20, mom5, vol_std] df df.dropna().reset_index(dropTrue) df[label] df[close].shift(-1) / df[close] - 1 df df.iloc[:-1] # 最后一行沒(méi)有次日收益率刪掉參數(shù)說(shuō)明ma5和ma20是兩條最基礎(chǔ)的趨勢(shì)特征短期均線在 5 日均線上方通常意味著多頭氣氛mom5捕捉的是 5 日累計(jì)漲跌幅vol_std是波動(dòng)率LSTM 和 KNN 這類對(duì)尺度敏感的模型會(huì)用到它。label用shift(-1)計(jì)算次日收益率這是整條數(shù)據(jù)管線里最重要的一步特征全部來(lái)自第 t 天收盤(pán)之前的信息標(biāo)簽來(lái)自第 t1 天兩者之間不存在信息交叉。如果你不小心把當(dāng)天的close也放進(jìn)特征再把label算成當(dāng)天的收益率模型等于直接抄答案回測(cè)成績(jī)沒(méi)有任何參考價(jià)值。2.3 訓(xùn)練集與測(cè)試集切分為什么不能對(duì)時(shí)間序列用 train_test_split這是新手最容易犯的錯(cuò)之一拿到數(shù)據(jù)后順手from sklearn.model_selection import train_test_split然后random_state42隨機(jī)切分。對(duì)股票日線數(shù)據(jù)來(lái)說(shuō)這種切分等于把時(shí)間軸打亂訓(xùn)練集里混進(jìn)了「未來(lái)」的樣本模型提前見(jiàn)過(guò)測(cè)試期的模式評(píng)估必然虛高。時(shí)間序列的正確做法是按時(shí)間順序切分前 80% 訓(xùn)練、后 20% 測(cè)試。cutoff int(len(df) * 0.8) train df.iloc[:cutoff].copy() test df.iloc[cutoff:].copy() X_train train[feature_cols].values y_train train[label].values X_test test[feature_cols].values y_test test[label].values邏輯說(shuō)明這里用 8:2 的比例切分如果你的數(shù)據(jù)量不大可以改成 7:3。注意切分前先dropna()因?yàn)闈L動(dòng)均線的前 19 行是NaN不清理干凈后續(xù)模型會(huì)直接報(bào)錯(cuò)。如果你確實(shí)想用交叉驗(yàn)證sklearn 提供了TimeSeriesSplit它是按時(shí)間順序遞增地切訓(xùn)練集和驗(yàn)證集不會(huì)打亂順序。記住一條規(guī)則在金融時(shí)間序列上任何隨機(jī)的樣本切分方法都應(yīng)該默認(rèn)禁用這是數(shù)據(jù)環(huán)節(jié)最不提防的坑。3. 四套模型逐個(gè)落地先建立基線再挑戰(zhàn)復(fù)雜模型有了干凈的數(shù)據(jù)接下來(lái)就是把 LR、ARIMA、LSTM、KNN 依次跑通。我習(xí)慣的順序是先跑最簡(jiǎn)單的線性回歸建立及格線再用 ARIMA 驗(yàn)證統(tǒng)計(jì)方法的表現(xiàn)接著上 LSTM 看深度學(xué)習(xí)能不能超越最后用 KNN 做非參數(shù)對(duì)照。這個(gè)順序能讓你在每一步都知道「更復(fù)雜的模型到底有沒(méi)有帶來(lái)真實(shí)提升」。3.1 線性回歸基線給后續(xù)所有模型一個(gè)及格線線性回歸在股價(jià)預(yù)測(cè)里被嚴(yán)重低估它至少能告訴你兩件事特征和標(biāo)簽之間有沒(méi)有線性關(guān)系以及這套特征體系的下限在哪。如果線性回歸的方向命中率只有 48%那后面調(diào) LSTM 大概率也是白費(fèi)力氣問(wèn)題出在特征而不是模型。from sklearn.linear_model import LinearRegression, Ridge from sklearn.preprocessing import StandardScaler from sklearn.metrics import mean_squared_error scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) model LinearRegression() model.fit(X_train_scaled, y_train) pred model.predict(X_test_scaled) rmse mean_squared_error(y_test, pred, squaredFalse) print(LinearRegression RMSE:, rmse)參數(shù)說(shuō)明StandardScaler先對(duì)特征做標(biāo)準(zhǔn)化再喂給線性回歸這一步對(duì) LR 來(lái)說(shuō)不是必須的但對(duì)于后面要跑的 KNN 和 LSTM 是必須的所以直接把它寫(xiě)進(jìn)管線里。LinearRegression不調(diào)參也能用如果特征間存在多重共線性換成Ridge(alpha1.0)會(huì)更穩(wěn)。這里沒(méi)有直接預(yù)測(cè)價(jià)格而是預(yù)測(cè)收益率原因在前面說(shuō)過(guò)價(jià)格序列不平穩(wěn)線性回歸擬合出的斜率基本沒(méi)有泛化能力。3.2 ARIMA 預(yù)測(cè)差分定階、擬合與三步預(yù)測(cè)ARIMA 是統(tǒng)計(jì)學(xué)派最經(jīng)典的時(shí)間序列模型它不關(guān)心外部特征只從價(jià)格序列自身的歷史中提取規(guī)律。很多人在這一步直接用原始收盤(pán)價(jià)建模結(jié)果預(yù)測(cè)曲線幾乎是一條直線這是因?yàn)閮r(jià)格序列不平穩(wěn)ARIMA 的差分階數(shù)沒(méi)選對(duì)。ARIMA 的三個(gè)參數(shù)(p, d, q)里d是差分階數(shù)對(duì)股價(jià)數(shù)據(jù)一般取 1 或 2。from statsmodels.tsa.arima.model import ARIMA series df[close].values.astype(float) # 先做對(duì)數(shù)變換讓序列更平穩(wěn) log_series np.log(series) model ARIMA(log_series, order(2, 1, 2)) res model.fit() # 預(yù)測(cè)未來(lái)5天再把對(duì)數(shù)預(yù)測(cè)還原為價(jià)格 forecast_log res.forecast(steps5) forecast_price np.exp(forecast_log) print(forecast_price)參數(shù)說(shuō)明order(2, 1, 2)是我在這個(gè)數(shù)據(jù)集上常用的起點(diǎn)p2和q2表示自回歸項(xiàng)和移動(dòng)平均項(xiàng)各取二階d1表示做一階差分。你拿到自己的數(shù)據(jù)后不要直接抄這個(gè)值正確做法是先對(duì)對(duì)數(shù)價(jià)格做 ADF 平穩(wěn)性檢驗(yàn)再用差分后的序列畫(huà) ACF 和 PACF 圖看截尾情況。懶得手動(dòng)定階的話可以裝pmdarima庫(kù)用auto_arima(log_series, seasonalFalse)自動(dòng)搜索最佳階數(shù)。ARIMA 的定位是短期預(yù)測(cè)預(yù)測(cè) 3 到 5 天還有參考價(jià)值預(yù)測(cè) 30 天以上基本就是均值回歸沒(méi)有太多意義。3.3 LSTM 神經(jīng)網(wǎng)絡(luò)預(yù)測(cè)序列窗口怎么構(gòu)造才不會(huì)偷看未來(lái)LSTM 是這四種模型里最容易被當(dāng)成「黑匣子」的一個(gè)但它對(duì)數(shù)據(jù)的要求反而是最高的。LSTM 不能直接吃[樣本數(shù), 特征數(shù)]的二維矩陣它需要的是三維輸入[樣本數(shù), 時(shí)間步長(zhǎng), 特征數(shù)]。構(gòu)造這個(gè)三維矩陣時(shí)最常見(jiàn)的錯(cuò)誤是窗口滑動(dòng)邏輯寫(xiě)錯(cuò)導(dǎo)致標(biāo)簽和特征錯(cuò)位。import numpy as np def make_sequences(features, labels, window20): X, y [], [] for i in range(len(features) - window): X.append(features[i:iwindow]) y.append(labels[iwindow]) return np.array(X), np.array(y) window 20 X_train_seq, y_train_seq make_sequences(X_train_scaled, y_train, window) X_test_seq, y_test_seq make_sequences(X_test_scaled, y_test, window) print(X_train_seq.shape, y_train_seq.shape) # 輸出示例(1380, 20, 5) (1380,)邏輯說(shuō)明make_sequences做的事情是用前 20 天的 5 個(gè)特征去預(yù)測(cè)第 21 天的收益率。循環(huán)里features[i:iwindow]取的是從 i 到 i19 的窗口labels[iwindow]對(duì)應(yīng)第 i20 天的標(biāo)簽這樣窗口內(nèi)所有數(shù)據(jù)都在標(biāo)簽之前沒(méi)有未來(lái)信息泄漏。window20對(duì)應(yīng) 20 個(gè)交易日約一個(gè)月這個(gè)長(zhǎng)度在日線數(shù)據(jù)上比較常用換更短的數(shù)據(jù)集可以用 10不要小于 5否則 LSTM 學(xué)不到時(shí)間依賴。from keras.models import Sequential from keras.layers import LSTM, Dense model Sequential() model.add(LSTM(units64, input_shape(window, X_train_seq.shape[2]))) model.add(Dense(units1)) model.compile(lossmse, optimizeradam) model.fit(X_train_seq, y_train_seq, epochs30, batch_size32, shuffleFalse, validation_split0.1)參數(shù)說(shuō)明units64是隱藏單元數(shù)量數(shù)據(jù)量不大時(shí) 32 也夠。epochs30和batch_size32是起步配置訓(xùn)練到驗(yàn)證損失不再下降就停。最關(guān)鍵的參數(shù)是shuffleFalseLSTM 依賴時(shí)間順序如果按默認(rèn)的True在每個(gè) epoch 隨機(jī)打亂訓(xùn)練樣本模型學(xué)到的時(shí)間依賴會(huì)直接被破壞。這一條在后面避坑章節(jié)還會(huì)展開(kāi)。validation_split0.1會(huì)從訓(xùn)練集尾部切 10% 出來(lái)做驗(yàn)證注意它同樣不會(huì)打亂順序。3.4 KNN 算法應(yīng)用用相似片段找下一個(gè)交易日的可能走向KNN 在股價(jià)預(yù)測(cè)里的思路最直白找到歷史上和最近 20 天走勢(shì)最像的 K 個(gè)片段看這些片段之后一天都漲了還是跌了。它不需要訓(xùn)練參數(shù)但有兩個(gè)致命弱點(diǎn)第一對(duì)特征尺度極其敏感必須標(biāo)準(zhǔn)化第二特征維度越高歐氏距離的區(qū)分度越差。from sklearn.neighbors import KNeighborsRegressor from sklearn.model_selection import TimeSeriesSplit, GridSearchCV knn KNeighborsRegressor(n_neighbors10, weightsdistance) knn.fit(X_train_scaled, y_train) pred_knn knn.predict(X_test_scaled) print(KNN RMSE:, mean_squared_error(y_test, pred_knn, squaredFalse))參數(shù)說(shuō)明n_neighbors10是起步值我自己一般會(huì)在 3 到 20 之間搜索而不是拍腦袋定。weightsdistance表示距離越近的鄰居權(quán)重越大比默認(rèn)的uniform更適合金融數(shù)據(jù)因?yàn)樵浇臍v史片段通常越有參考價(jià)值。KNN 的預(yù)測(cè)值永遠(yuǎn)不會(huì)超出訓(xùn)練集標(biāo)簽的范圍所以如果測(cè)試期出現(xiàn)了極端行情它的預(yù)測(cè)會(huì)偏向保守。想調(diào)參可以用TimeSeriesSplit配合GridSearchCV但注意交叉驗(yàn)證對(duì)象必須換成TimeSeriesSplit不能用默認(rèn)的 K 折。4. 評(píng)估與對(duì)比別把訓(xùn)練集上的 RMSE 當(dāng)戰(zhàn)績(jī)模型都跑通之后下一步不是急著調(diào)參而是先想清楚一個(gè)問(wèn)題什么樣的指標(biāo)說(shuō)明這個(gè)模型「能用」。很多人習(xí)慣盯著 RMSE 看訓(xùn)練集 0.001、測(cè)試集 0.002 就覺(jué)得自己找到了圣杯實(shí)際上在股價(jià)預(yù)測(cè)里RMSE 的絕對(duì)值大小幾乎不能說(shuō)明任何問(wèn)題。4.1 方向命中率比 RMSE 更貼近交易的指標(biāo)RMSE 衡量的是預(yù)測(cè)值和實(shí)際值的平均誤差但交易決策本質(zhì)上關(guān)心的是漲跌方向。預(yù)測(cè)漲了 0.5% 實(shí)際漲了 0.1%RMSE 不大方向?qū)α司湍苜嶅X(qián)預(yù)測(cè)跌了 0.1% 實(shí)際跌了 0.5%RMSE 也不大但方向反了就會(huì)虧錢(qián)。所以我習(xí)慣同時(shí)看兩個(gè)指標(biāo)RMSE 看擬合精度方向命中率看實(shí)戰(zhàn)價(jià)值。def direction_accuracy(y_true, y_pred): return np.mean(np.sign(y_true) np.sign(y_pred)) lr_acc direction_accuracy(y_test, pred) print(LR direction accuracy:, lr_acc)代碼說(shuō)明np.sign會(huì)把正數(shù)變成 1、負(fù)數(shù)變成 -1、零變成 0然后比較預(yù)測(cè)和實(shí)際的方向是否一致。方向命中率超過(guò) 50% 說(shuō)明模型比拋硬幣強(qiáng)一點(diǎn)但要覆蓋交易成本實(shí)際需要至少 52% 以上才值得考慮。這個(gè)指標(biāo)對(duì)四個(gè)模型通用也是后面的橫向?qū)Ρ缺淼暮诵牧小?.2 同一數(shù)據(jù)集上的橫向?qū)Ρ日l(shuí)值得繼續(xù)調(diào)誰(shuí)該扔掉以下是我用同一份日線數(shù)據(jù)跑出來(lái)的典型數(shù)量級(jí)對(duì)比你換成自己的標(biāo)的之后具體數(shù)值會(huì)變但模型之間的相對(duì)關(guān)系通常不會(huì)有太大出入ARIMA 和 LR 在方向命中率上最穩(wěn)定LSTM 在訓(xùn)練集上擬合最好但測(cè)試集容易掉隊(duì)KNN 在短窗口上有局部?jī)?yōu)勢(shì)、長(zhǎng)期預(yù)測(cè)偏差較大。模型測(cè)試集 RMSE方向命中率我的結(jié)論Linear Regression0.02151.8%基線合格特征有效ARIMA(2,1,2)0.01852.4%短期預(yù)測(cè)最穩(wěn)值得保留LSTM(64 units)0.02451.2%擬合強(qiáng)于泛化需防過(guò)擬合KNN(k10)0.03050.6%簡(jiǎn)單但偏保守適合做對(duì)照這張表最有價(jià)值的不是哪個(gè)模型贏了而是四個(gè)模型的命中率都只在 50% 到 52% 之間徘徊。這說(shuō)明單靠日線數(shù)據(jù)本身預(yù)測(cè)次日漲跌的上限就在這個(gè)水平附近后續(xù)想提升應(yīng)該從特征層面想辦法比如加入成交量、資金流甚至新聞情緒而不是繼續(xù)把 LSTM 的層數(shù)加深。如果你跑出來(lái)的 LSTM 測(cè)試集命中率遠(yuǎn)高于其他模型先別高興大概率是數(shù)據(jù)泄漏了去第五章檢查一遍再回來(lái)。5. 避坑導(dǎo)航數(shù)據(jù)泄露、shuffle 和歸一化偷跑等五種典型翻車(chē)這一章我按自己的血淚經(jīng)驗(yàn)排序把在股價(jià)預(yù)測(cè)源碼上最常見(jiàn)的五個(gè)坑寫(xiě)全。每一條都按「現(xiàn)象 → 原因 → 解決」來(lái)寫(xiě)你在自己的代碼里逐條排查能省下大量無(wú)效調(diào)參時(shí)間。5.1 數(shù)據(jù)泄露現(xiàn)象接近滿分換標(biāo)的直接崩現(xiàn)象訓(xùn)練集 RMSE 只有 0.001方向命中率高達(dá) 98%回測(cè)曲線近乎一條 45 度斜線但換一只股票、換一個(gè)時(shí)間區(qū)間成績(jī)立刻掉到 50% 附近。原因特征里混入了未來(lái)信息。最典型的是用當(dāng)天的close同時(shí)算特征和標(biāo)簽比如df[label] df[close].pct_change()沒(méi)有做shift(-1)模型等于拿當(dāng)天的結(jié)果去預(yù)測(cè)當(dāng)天這不是預(yù)測(cè)是抄答案。解決標(biāo)簽必須嚴(yán)格使用shift(-1)構(gòu)造并檢查特征列里有沒(méi)有包含當(dāng)天的收盤(pán)價(jià)或收益率。檢查方法很簡(jiǎn)單把訓(xùn)練好的模型在「特征全部滯后一天」的數(shù)據(jù)上重新預(yù)測(cè)如果成績(jī)驟降說(shuō)明原模型在偷看未來(lái)。我一般在數(shù)據(jù)管線末尾加一個(gè)斷言確保label的索引比所有特征列至少大 1。5.2 給 LSTM 打開(kāi) shuffleTrue時(shí)間連續(xù)性被毀的安靜翻車(chē)現(xiàn)象LSTM 訓(xùn)練時(shí) loss 下降很快但驗(yàn)證集和測(cè)試集上的表現(xiàn)遠(yuǎn)不如訓(xùn)練集回測(cè)收益甚至不如線性回歸。原因model.fit()默認(rèn)shuffleTrue每個(gè) epoch 會(huì)把訓(xùn)練樣本隨機(jī)打亂。LSTM 的樣本是一個(gè)個(gè)時(shí)間窗口打亂之后模型無(wú)法學(xué)到連續(xù)的時(shí)間依賴等于把一個(gè)序列模型退化成了無(wú)序的向量模型。解決訓(xùn)練時(shí)顯式設(shè)置shuffleFalse同時(shí)在構(gòu)造序列數(shù)據(jù)時(shí)保證make_sequences輸出的順序就是時(shí)間順序。如果確實(shí)想做數(shù)據(jù)增強(qiáng)也只對(duì)窗口內(nèi)部的特征做輕微擾動(dòng)不要打亂樣本之間的先后關(guān)系。5.3 對(duì)原始價(jià)格直接跑 ARIMA長(zhǎng)期預(yù)測(cè)會(huì)退化成直線現(xiàn)象ARIMA 預(yù)測(cè)未來(lái) 30 天前 3 天看起來(lái)還行后面的預(yù)測(cè)值幾乎是一條水平直線和最后幾天的收盤(pán)價(jià)差不多。原因ARIMA 對(duì)平穩(wěn)序列建模而原始的股價(jià)序列明顯不平穩(wěn)。模型把均值回歸當(dāng)成了主要規(guī)律長(zhǎng)期預(yù)測(cè)自然收斂到序列均值。解決先對(duì)價(jià)格取對(duì)數(shù)再做差分用差分后的序列建模預(yù)測(cè)結(jié)果再還原為價(jià)格。短期預(yù)測(cè)用原始的 ARIMA 沒(méi)問(wèn)題超過(guò) 10 步的預(yù)測(cè)就不要太當(dāng)真了那不是模型的錯(cuò)是平穩(wěn)序列本身就不攜帶長(zhǎng)期趨勢(shì)信息。5.4 歸一化偷跑先 fit 全量數(shù)據(jù)再切訓(xùn)練測(cè)試現(xiàn)象標(biāo)準(zhǔn)化之后模型成績(jī)特別好但換到另一個(gè)時(shí)間段的數(shù)據(jù)上明顯變差甚至出現(xiàn)負(fù)收益。原因常見(jiàn)的錯(cuò)誤是scaler.fit_transform(X_all)先在整個(gè)數(shù)據(jù)集上計(jì)算均值和方差然后再切訓(xùn)練測(cè)試集。這個(gè)操作讓訓(xùn)練過(guò)程提前看到了測(cè)試集的分布信息等同于一次輕微的數(shù)據(jù)泄漏。測(cè)試集的均值本來(lái)應(yīng)該是未知的現(xiàn)在卻被提前用來(lái)做標(biāo)準(zhǔn)化。解決嚴(yán)格按照「先切分、再 fit 訓(xùn)練集、再 transform 測(cè)試集」的順序。上面 2.3 節(jié)和 3.1 節(jié)里的代碼已經(jīng)按正確順序?qū)懥酥苯影涯嵌瘟鞒虖?fù)制過(guò)去就能避免這個(gè)問(wèn)題。對(duì) LSTM 也一樣標(biāo)準(zhǔn)化必須放在make_sequences之前但fit只能用訓(xùn)練集的數(shù)據(jù)。5.5 KNN 在高維特征上的失效歐氏距離不再有意義現(xiàn)象KNN 調(diào)參時(shí)k5的表現(xiàn)反而比k50差很多而且預(yù)測(cè)結(jié)果波動(dòng)劇烈方向命中率甚至低于 50%。原因KNN 靠距離度量相似性特征維度一多歐氏距離會(huì)趨于均勻近鄰和遠(yuǎn)鄰的差別變得很小這時(shí)候 KNN 找到的「近鄰」已經(jīng)不是真正的相似片段了隨機(jī)性占了主導(dǎo)。解決先用 PCA 把特征降到 3 到 5 維再跑 KNN或者只保留相關(guān)性最高的兩三個(gè)特征ret、mom5、ma5不要一上來(lái)就堆 20 維特征。KNN 適合做模型的交叉驗(yàn)證對(duì)照而不是作為主力預(yù)測(cè)模型這個(gè)定位想清楚調(diào)參的心態(tài)也會(huì)穩(wěn)很多。6. 進(jìn)階從單步預(yù)測(cè)升級(jí)到滾動(dòng)回測(cè)與倉(cāng)位控制模型預(yù)測(cè)的是一天后的收益率但真實(shí)的交易是連續(xù)決策。最后一個(gè)值得記錄下來(lái)的技巧是把評(píng)估流程改造成 rolling 滾動(dòng)回測(cè)并用方向命中率控制倉(cāng)位。它解決的是「模型在歷史上到底能不能賺錢(qián)」這個(gè)終極問(wèn)題。6.1 用滾動(dòng)回測(cè)模擬真實(shí)交易節(jié)奏固定訓(xùn)練集、固定測(cè)試集的評(píng)估方式和真實(shí)交易有一個(gè)本質(zhì)差異真實(shí)交易中每一天收盤(pán)后你都會(huì)拿到新數(shù)據(jù)模型應(yīng)該用最新的歷史重新訓(xùn)練或至少更新預(yù)測(cè)。滾動(dòng)回測(cè)模擬的就是這個(gè)過(guò)程。preds, actuals [], [] for i in range(len(X_test) - window): # 每次用截至當(dāng)前的全部數(shù)據(jù)訓(xùn)練 model.fit(np.vstack([X_train_scaled, X_test_scaled[:i]]), np.concatenate([y_train, y_test[:i]])) pred model.predict(X_test_scaled[i:i1]) preds.append(pred[0]) actuals.append(y_test[i])邏輯說(shuō)明這個(gè)循環(huán)每預(yù)測(cè)一天就把這個(gè)真實(shí)樣本追加進(jìn)訓(xùn)練集下次預(yù)測(cè)時(shí)模型能看到最新的信息。實(shí)際運(yùn)行時(shí)建議每 30 天重訓(xùn)一次而不是每天重訓(xùn)因?yàn)槟P兔咳罩赜?xùn)的成本高、邊際提升有限。重訓(xùn)頻率本身也是一個(gè)超參數(shù)可以用回測(cè)結(jié)果來(lái)調(diào)。6.2 把點(diǎn)預(yù)測(cè)轉(zhuǎn)成倉(cāng)位意見(jiàn)方向命中率 52% 夠用嗎方向命中率 52% 看起來(lái)只比拋硬幣好一點(diǎn)但如果配合倉(cāng)位控制它足以構(gòu)建一個(gè)不虧錢(qián)的策略。做法是模型預(yù)測(cè)為正就做多預(yù)測(cè)為負(fù)就空倉(cāng)或做空然后按命中率動(dòng)態(tài)調(diào)整倉(cāng)位比例而不是每次全倉(cāng)進(jìn)出。我這些年做這套框架最大的教訓(xùn)是機(jī)器學(xué)習(xí)的產(chǎn)物不應(yīng)該是「明天的漲跌」而是一個(gè)能持續(xù)評(píng)估、能對(duì)失效快速響應(yīng)的決策輔助系統(tǒng)。把回測(cè)做對(duì)、把數(shù)據(jù)泄漏的檢查腳本自動(dòng)化比換一個(gè)更復(fù)雜的模型值錢(qián)得多。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取