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