測股票次日開盤價(jià):從數(shù)據(jù)清洗到Checkpoint加載)
簡介基于TensorFlow構(gòu)建的股票價(jià)格走勢預(yù)測項(xiàng)目面向具備一定Python基礎(chǔ)、希望入門量化選股或序列預(yù)測的開發(fā)者。項(xiàng)目通過tushare模塊獲取股票歷史數(shù)據(jù)利用pandas完成清洗與特征處理再以TensorFlow搭建模型學(xué)習(xí)價(jià)格規(guī)律最終借助matplotlib將真實(shí)走勢與預(yù)測結(jié)果可視化當(dāng)前聚焦預(yù)測第二日開盤價(jià)流程完整、可直接運(yùn)行學(xué)習(xí)。壓縮包共5個(gè)文件包含兩個(gè)Python腳本、一個(gè)checkpoint文件及配套的data與index數(shù)據(jù)文件其中.py文件分別負(fù)責(zé)模型加載與預(yù)測測試模型文件用于保存訓(xùn)練好的權(quán)重整體僅456KB便于快速下載與本地調(diào)試驗(yàn)證。目前已有2142人學(xué)習(xí)使用適合作為股票價(jià)格預(yù)測實(shí)戰(zhàn)的入門參考。通過對照load_model.py與test.py的調(diào)用邏輯讀者可以理解模型持久化、數(shù)據(jù)對齊和單步預(yù)測的基本思路并在此基礎(chǔ)上擴(kuò)展多步預(yù)測或引入更多特征。1. 用TensorFlow做股票價(jià)格走勢預(yù)測解壓后直接加載模型預(yù)測第二天開盤價(jià)拿到這份基于python實(shí)現(xiàn)股票價(jià)格走勢預(yù)測的壓縮包時(shí)我原本沒抱太高期望很多號稱“預(yù)測股票”的項(xiàng)目解壓后不是缺權(quán)重就是少腳本。這個(gè)包反而干凈cp-0002.ckpt.index、cp-0002.ckpt.data-00000-of-00001、checkpoint三個(gè)模型文件配上load_model.py和test.py兩個(gè)腳本沒有花架子。整個(gè)鏈路是TensorFlow搭LSTM模型tushare拉日線行情pandas清洗matplotlib把真實(shí)開盤價(jià)和預(yù)測開盤價(jià)畫在同一張圖上。它能干的事非常具體用最近60個(gè)交易日的開盤價(jià)預(yù)測下一個(gè)交易日的開盤價(jià)。適合已經(jīng)裝好Python環(huán)境、想跑通一個(gè)完整量化預(yù)測流程的人也適合手上有checkpoint卻不知道該怎么恢復(fù)模型做預(yù)測的開發(fā)者。預(yù)測準(zhǔn)確率屬于“趨勢大致對、數(shù)值有偏差”的水平把它當(dāng)參考沒問題當(dāng)提款機(jī)還遠(yuǎn)。2. tushare與pandas數(shù)據(jù)鏈路從拉日線到切出60天窗口模型再玄學(xué)數(shù)據(jù)錯一步后面全白搭。這一章先把數(shù)據(jù)側(cè)講透接口怎么選、字段怎么洗、窗口怎么切這三步?jīng)Q定了模型能不能學(xué)到真實(shí)規(guī)律。2.1 tushare接口選型daily接口的參數(shù)細(xì)節(jié)tushare是A股數(shù)據(jù)里最常用的python數(shù)據(jù)源之一。預(yù)測腳本里拉日線最典型的寫法是調(diào)用pro.daily接口下面這段可以被直接復(fù)用import tushare as ts import pandas as pd ts.set_token(你的token) # 在tushare官網(wǎng)注冊后換成自己的token pro ts.pro_api() df pro.daily( ts_code000001.SZ, # 深市平安銀行滬市用600000.SH start_date20200101, end_date20241231 ) df df.sort_values(trade_date).reset_index(dropTrue) print(df.head())pro.daily返回的trade_date是YYYYMMDD格式的字符串而且默認(rèn)按日期倒序排列。排序這一步不能省不然喂給LSTM的序列是時(shí)間倒流的模型會學(xué)到“明天等于昨天”的錯覺。ts_code的后綴規(guī)則是滬市SH、深市SZ指數(shù)代碼帶指數(shù)后綴這個(gè)參數(shù)寫錯會直接返回空表。但這里有個(gè)隱蔽問題pro.daily返回的是未復(fù)權(quán)價(jià)格。上市公司分紅送轉(zhuǎn)后歷史價(jià)格會出現(xiàn)人為跳空LSTM看到這種斷層會給出離譜預(yù)測。我一般會換成ts.pro_bar接口它直接支持復(fù)權(quán)參數(shù)df ts.pro_bar( ts_code000001.SZ, start_date20200101, end_date20241231, adjqfq # qfq前復(fù)權(quán)hfq后復(fù)權(quán) )對比項(xiàng)ts.pro.dailyts.pro_bar復(fù)權(quán)處理未復(fù)權(quán)需自己算支持qfq/hfq日期字段trade_date字符串datedatetime類型排序默認(rèn)降序需手動排序提示tushare的pro接口有積分門檻daily需要120分以上pro_bar部分參數(shù)需要更高積分。注冊后完善個(gè)人信息能拿到基礎(chǔ)分夠用了。2.2 pandas清洗拿到手先處理這四件事原始數(shù)據(jù)不能直接進(jìn)模型。我習(xí)慣按缺失值、重復(fù)行、列裁剪、類型檢查四步來洗每步都有明確目的print(df.isna().sum()) # 1. 缺失值統(tǒng)計(jì)重點(diǎn)看open列 df df.dropna(subset[open]).copy() df df.drop_duplicates(subset[trade_date]) # 2. 按日期去重 df df[[trade_date, open, high, low, close, vol]] df df.rename(columns{vol: volume}) # 3. 只留需要的列 print(df.dtypes) # 4. 確認(rèn)日期是str、價(jià)格是float缺失值最常見的來源是停牌日停牌當(dāng)天沒有成交記錄tushare會把該行直接跳過而不是填NaN。如果不做處理窗口切分時(shí)會把停牌前后的兩個(gè)交易日拼成連續(xù)序列等于給模型喂了一段“假歷史”。重復(fù)行則多來自分頁拉數(shù)據(jù)時(shí)的邊界重疊用drop_duplicates按日期去重解決。vol字段單位是“手”不是“股”計(jì)算成交額時(shí)需要乘以單價(jià)再乘100但這個(gè)包只做開盤價(jià)預(yù)測vol只用來輔助觀察不參與訓(xùn)練。2.3 為什么只拿開盤價(jià)當(dāng)特征窗口長度和特征維度的選擇這個(gè)包的設(shè)計(jì)思路很有意思只預(yù)測第二天的開盤價(jià)而輸入特征只有開盤價(jià)一根序列。很多初學(xué)者會問為什么不把最高價(jià)、最低價(jià)、成交量全塞進(jìn)去我自己的血淚經(jīng)驗(yàn)是特征越多數(shù)據(jù)泄漏和數(shù)據(jù)漂移的坑越多。成交量在免費(fèi)數(shù)據(jù)里的噪聲很大high和low本身與open存在同期強(qiáng)相關(guān)這些特征加進(jìn)去訓(xùn)練集loss會降低但換一段行情就翻車。LSTM需要一個(gè)固定長度的輸入窗口。這個(gè)包里checkpoint記錄的輸入形狀是三維張量對應(yīng)(批次, 60, 1)也就是60個(gè)交易日、每個(gè)交易日1個(gè)特征。60天約等于一個(gè)季度的交易日數(shù)量是短期信息與中期趨勢的折中。窗口太短比如20天模型只盯著最近一個(gè)月抓不住平臺期窗口太長比如90天樣本數(shù)量變少訓(xùn)練時(shí)間變長收益提升卻很有限。值得提醒的是移動平均MA5、MA10這類衍生特征也別急著加它們本質(zhì)是歷史價(jià)格的線性組合模型學(xué)會“抄均線”后在樣本外會迅速失效。3. TensorFlow時(shí)序建模把60天開盤價(jià)映射成第二天開盤價(jià)數(shù)據(jù)洗好只完成了一半接下來要做的是把“過去60天”和“下一天”的關(guān)系用模型擬合出來。這里用到的不是普通全連接網(wǎng)絡(luò)而是LSTM。3.1 為什么是LSTM序列記憶與梯度消失股票開盤價(jià)不是獨(dú)立隨機(jī)變量今天的價(jià)格往往和過去一段時(shí)間的數(shù)據(jù)存在依賴關(guān)系這就是時(shí)間序列的自相關(guān)性。普通全連接網(wǎng)絡(luò)把60天的價(jià)格當(dāng)成60個(gè)互不相干的特征丟失了交易日之間的先后順序。RNN能保留順序但樸素RNN在長序列上梯度消失嚴(yán)重學(xué)到后面就忘了前面。LSTM用輸入門、遺忘門、輸出門三組門控結(jié)構(gòu)來控制信息保留和丟棄在60天這個(gè)中等長度的窗口上既不會忘掉一個(gè)月前的趨勢又能對最近幾天的突變做出反應(yīng)。這個(gè)包把序列切到60天正好落在LSTM擅長的區(qū)間里。3.2 樣本構(gòu)造與數(shù)據(jù)集劃分索引別寫錯預(yù)測值就是下一天模型訓(xùn)練前要把一維的價(jià)格序列轉(zhuǎn)成監(jiān)督學(xué)習(xí)格式——X是窗口y是窗口外的下一天。這里最常見的一個(gè)錯誤是把y取成窗口內(nèi)最后一個(gè)值那等于讓模型復(fù)讀輸入loss會很低但毫無預(yù)測能力。import numpy as np def make_sequences(open_prices, seq_len60): X, y [], [] for i in range(len(open_prices) - seq_len): X.append(open_prices[i: i seq_len]) # 第 i 天到第 i59 天 y.append(open_prices[i seq_len]) # 第 i60 天即窗口后的第一天 return np.array(X).reshape(-1, seq_len, 1), np.array(y).reshape(-1, 1) X_all, y_all make_sequences(scaled_open, seq_len60) print(X_all.shape, y_all.shape)索引規(guī)則必須對齊當(dāng)X取open_prices[0]到open_prices[59]時(shí)y是open_prices[60]也就是第61個(gè)交易日的開盤價(jià)。如果把y寫成open_prices[i seq_len - 1]模型學(xué)到的就是從60天序列里“摘出最后一個(gè)值”實(shí)盤預(yù)測時(shí)立刻失效。歸一化要在切窗口之前完成。我一般用MinMaxScaler把價(jià)格縮放到0到1之間避免LSTM激活函數(shù)在輸入端飽和from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_open scaler.fit_transform(open_prices.reshape(-1, 1)).flatten()注意這里fit_transform只應(yīng)該對訓(xùn)練集調(diào)用。嚴(yán)謹(jǐn)?shù)淖龇ㄊ窍惹蟹钟?xùn)練集和測試集再用訓(xùn)練集的價(jià)格擬合scaler最后統(tǒng)一transform全量數(shù)據(jù)。預(yù)測階段再單獨(dú)fit一次是后面避坑章里的頭號翻車原因。數(shù)據(jù)集劃分遵循時(shí)間順序而不是隨機(jī)打亂split int(len(X_all) * 0.8) X_train, X_val X_all[:split], X_all[split:] y_train, y_val y_all[:split], y_all[split:]時(shí)間序列一旦隨機(jī)shuffle驗(yàn)證集里會混入未來的樣本驗(yàn)證loss虛低實(shí)盤時(shí)立刻現(xiàn)出原形。前80%做訓(xùn)練、后20%做驗(yàn)證是序列預(yù)測的通用做法不用K折交叉驗(yàn)證因?yàn)楣善毙星椴粷M足樣本獨(dú)立同分布假設(shè)。3.3 模型結(jié)構(gòu)與訓(xùn)練參數(shù)兩層LSTM加Dropout的典型配置這個(gè)包沒有附帶訓(xùn)練腳本但根據(jù)checkpoint的輸入形狀訓(xùn)練側(cè)大概率是一個(gè)兩層LSTM加全連接輸出的結(jié)構(gòu)。這也是我處理單變量序列預(yù)測時(shí)最常用的配置import tensorflow as tf model tf.keras.Sequential([ tf.keras.layers.LSTM(64, return_sequencesTrue, input_shape(60, 1)), tf.keras.layers.Dropout(0.2), tf.keras.layers.LSTM(32), tf.keras.layers.Dense(1) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse ) model.summary()網(wǎng)絡(luò)層參數(shù)作用LSTM 第一層64單元return_sequencesTrue提取短期模式輸出完整的序列給下一層Dropout0.2隨機(jī)丟棄20%的連接抑制過擬合LSTM 第二層32單元把60步信息壓縮成一個(gè)向量Dense1輸出歸一化后的次日開盤價(jià)input_shape(60, 1)里的60是窗口長度1是特征數(shù)量。第一層LSTM必須保留return_sequencesTrue否則輸出到第二層時(shí)只剩最后一個(gè)時(shí)間步的隱狀態(tài)中間59天的信息全被丟棄。loss用mse而不是mae原因是mse對大誤差更敏感股價(jià)序列的極端值更需要被懲罰如果你更關(guān)心誤差的絕對值換成mae也可以但要注意學(xué)習(xí)率可能要相應(yīng)調(diào)低。3.4 Checkpoint保存機(jī)制cp-0002這個(gè)編號是什么訓(xùn)練過程中最怕斷電和顯存溢出。ModelCheckpoint回調(diào)把權(quán)重定期落盤這個(gè)包里出現(xiàn)的cp-0002.ckpt就是訓(xùn)練到第2輪時(shí)保存的一份完整快照cp_callback tf.keras.callbacks.ModelCheckpoint( filepathmodel/cp-{epoch:04d}.ckpt, save_weights_onlyFalse, save_freq5 ) history model.fit( X_train, y_train, batch_size32, epochs50, validation_data(X_val, y_val), callbacks[cp_callback] )save_weights_onlyFalse時(shí)每輪保存會生成三個(gè)文件.ckpt.index記錄變量名和索引.ckpt.data-00000-of-00001存放實(shí)際權(quán)重?cái)?shù)值checkpoint文件記錄“最新一次保存的是哪個(gè)模型”。cp-0002說明這個(gè)快照對應(yīng)第2輪迭代保存時(shí)間點(diǎn)比較早說明訓(xùn)練可能在中途被中斷過也可能作者特意選了個(gè)早期檢查點(diǎn)來避免過擬合。對于這種60天窗口的風(fēng)險(xiǎn)預(yù)測第2輪已經(jīng)能學(xué)到基本的均值回歸結(jié)構(gòu)預(yù)測趨勢不會差太遠(yuǎn)。注意save_freq5表示每5輪保存一次如果訓(xùn)練在中間某輪被手動停止最后一次保存的編號可能不是5的整數(shù)倍cp-0002就是這樣出現(xiàn)的。理解了這個(gè)機(jī)制你就能判斷checkpoint文件是不是完整。4. 加載模型做單步預(yù)測load_model.py與test.py實(shí)戰(zhàn)解讀模型訓(xùn)練完預(yù)測環(huán)節(jié)只剩三件事恢復(fù)checkpoint、構(gòu)造輸入窗口、把預(yù)測值畫出來。這個(gè)包里的load_model.py和test.py核心邏輯就是在解決這三件事。4.1 從checkpoint恢復(fù)模型結(jié)構(gòu)必須與訓(xùn)練時(shí)一致很多人拿到.ckpt文件第一反應(yīng)是用tf.keras.models.load_model直接加載這是必翻車的寫法。Keras的load_model只認(rèn).h5或SavedModel格式認(rèn)不了Saver格式的ckpt。load_model.py里靠譜的恢復(fù)邏輯是這樣的# 讀取checkpoint文件拿到最新的模型路徑 with open(model/checkpoint, r) as f: lines f.readlines() for line in lines: if model_checkpoint_path in line: ckpt_path line.strip().split()[1] print(ckpt_path) import tensorflow.compat.v1 as tf tf.disable_v2_behavior() def build_model(): model tf.keras.Sequential([ tf.keras.layers.LSTM(64, return_sequencesTrue, input_shape(60, 1)), tf.keras.layers.Dropout(0.2), tf.keras.layers.LSTM(32), tf.keras.layers.Dense(1) ]) return model tf_model build_model() saver tf.train.Saver() with tf.Session() as sess: saver.restore(sess, ckpt_path) # 到這里 sess 里已經(jīng)是恢復(fù)好權(quán)重的完整計(jì)算圖這段代碼里有三個(gè)細(xì)節(jié)值得說。第一checkpoint文件里存的是相對路徑如果模型目錄被移動過saver.restore會找不到文件這時(shí)需要把路徑改成絕對路徑。第二模型結(jié)構(gòu)必須和訓(xùn)練時(shí)完全一致LSTM單元數(shù)、層數(shù)、input_shape任何一個(gè)參數(shù)變更restore都會報(bào)變量名不匹配。包里沒帶訓(xùn)練腳本這塊結(jié)構(gòu)定義是唯一需要手工對齊的地方。第三如果用TensorFlow 2.x環(huán)境import tensorflow.compat.v1并關(guān)閉v2行為是為了讓Saver機(jī)制可用如果環(huán)境本來就是TF1.x直接import tensorflow as tf即可。4.2 test.py的預(yù)測流程歸一化、reshape、反歸一化預(yù)測階段最容易出錯的是輸入數(shù)據(jù)的預(yù)處理。訓(xùn)練時(shí)做了MinMax縮放預(yù)測時(shí)必須用同一個(gè)scaler做變換一個(gè)完整流程長這樣import pickle import tushare as ts df ts.pro_bar( ts_code000001.SZ, start_date20250101, end_date20250314, adjqfq ).sort_values(date).reset_index(dropTrue) open_prices df[open].values dates pd.to_datetime(df[date]) with open(scaler.pkl, rb) as f: scaler pickle.load(f) scaled scaler.transform(open_prices.reshape(-1, 1)).flatten() last_window scaled[-60:].reshape(1, 60, 1) with tf.Session() as sess: saver.restore(sess, ckpt_path) pred_scaled sess.run(tf_model.output, feed_dict{tf_model.input: last_window}) pred_open scaler.inverse_transform(pred_scaled.reshape(-1, 1))[0][0] print(f預(yù)測下一交易日開盤價(jià): {pred_open:.2f})這里做了四件事拉數(shù)據(jù)到預(yù)測日的前一天、加載訓(xùn)練時(shí)保存的scaler、把最近60天價(jià)格縮放到0到1并reshape成三維、預(yù)測后反縮放還原成真實(shí)價(jià)格。關(guān)鍵點(diǎn)是scaler只做transform不做fit_transform以及l(fā)ast_window取的是scaled數(shù)組的最后60個(gè)值保證窗口緊貼預(yù)測日。如果你用的是TensorFlow 2.x的Keras接口最后一行的sess.run等價(jià)于model.predict(last_window)數(shù)據(jù)準(zhǔn)備邏輯完全一致。4.3 matplotlib可視化真實(shí)價(jià)與預(yù)測價(jià)的同軸呈現(xiàn)預(yù)測結(jié)果單獨(dú)一個(gè)數(shù)字沒有說服力要畫出來對比。matplotlib的用法不復(fù)雜但有一個(gè)經(jīng)典坑是中文亂碼import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False real_window open_prices[-60:] plt.figure(figsize(10, 5)) plt.plot(dates[-60:], real_window, label真實(shí)開盤價(jià), linewidth1.5) plt.axhline(pred_open, colorred, linestyle--, label預(yù)測下一個(gè)交易日開盤價(jià)) plt.title(股票價(jià)格走勢預(yù)測真實(shí) vs 預(yù)測) plt.legend() plt.grid(alpha0.3) plt.show()SimHei是Windows下的中文字體macOS要換成Arial Unicode MSLinux用Noto Sans CJK SC。如果不設(shè)置中文字體title和label會變成方框初學(xué)者經(jīng)常在這上面卡半小時(shí)。畫真實(shí)價(jià)格時(shí)取最近60天窗口就好別把幾百天的序列全打出來圖會糊成一團(tuán)。預(yù)測值用一條水平虛線標(biāo)注一眼就能看出模型認(rèn)為明天會在哪個(gè)位置開盤。這里要降低預(yù)期第二天開盤價(jià)受隔夜外盤、消息面和集合競價(jià)影響模型基于歷史形態(tài)給出的只是條件期望。它適合做早盤參考不適合單獨(dú)當(dāng)交易信號。5. 避坑指南預(yù)測第二天開盤價(jià)的五個(gè)高頻翻車現(xiàn)場以下五個(gè)問題都是重跑這個(gè)類型項(xiàng)目時(shí)幾乎必然遇到過的我按現(xiàn)象、原因、解決的順序列清楚相當(dāng)于給動手復(fù)現(xiàn)的人一份后悔藥。5.1 維度不匹配與歸一化漂移坑一predict時(shí)忘了batch維度?,F(xiàn)象執(zhí)行model.predict(last_window)時(shí)報(bào)錯“incompatible shapes”或“expected ndim3, found ndim2”。原因訓(xùn)練輸入是三維張量(批次, 60, 1)預(yù)測時(shí)如果直接取scaled[-60:]得到的是二維數(shù)組(60,)或(60, 1)模型無法識別。解決固定用last_window scaled[-60:].reshape(1, 60, 1)把batch維度顯式寫出來。坑二歸一化參數(shù)漂移導(dǎo)致預(yù)測值系統(tǒng)性偏低?,F(xiàn)象預(yù)測值形態(tài)和真實(shí)曲線相似但從某一天起整體低一截差值基本固定。原因預(yù)測腳本里對包含測試數(shù)據(jù)的全量價(jià)格又重新調(diào)用了fit_transformscaler的min和max被新數(shù)據(jù)污染訓(xùn)練時(shí)的分布和預(yù)測時(shí)的分布不一致。解決訓(xùn)練結(jié)束后用pickle把scaler對象存成文件預(yù)測時(shí)只調(diào)transform不碰fit。這一點(diǎn)在4.2節(jié)代碼里已經(jīng)體現(xiàn)但很多人為了省事會在預(yù)測腳本里重寫一遍scaler結(jié)果就是模型預(yù)測價(jià)值直接報(bào)廢。5.2 checkpoint恢復(fù)失敗與tushare數(shù)據(jù)口徑坑三用load_model直接加載ckpt報(bào)錯?,F(xiàn)象tf.keras.models.load_model(model/cp-0002.ckpt)拋出異常提示格式不支持。原因ckpt是TensorFlow Saver的權(quán)重快照格式只包含變量值和索引不包含模型結(jié)構(gòu)Keras的load_model要求完整模型文件。解決按4.1節(jié)方式先build_model重建結(jié)構(gòu)再用saver.restore恢復(fù)權(quán)重。如果你想把ckpt轉(zhuǎn)成h5方便后續(xù)加載可以在restore后用model.save(model.h5)保存一份以后就少一個(gè)坑??铀挠?xùn)練用前復(fù)權(quán)、預(yù)測用未復(fù)權(quán)數(shù)據(jù)預(yù)測值徹底失真?,F(xiàn)象在同一只股票上換時(shí)間段回測預(yù)測價(jià)格和真實(shí)價(jià)格相差10%以上并且訓(xùn)練集loss并不高。原因訓(xùn)練時(shí)用pro_bar(adjqfq)拿到前復(fù)權(quán)數(shù)據(jù)預(yù)測時(shí)換成pro.daily拿到未復(fù)權(quán)價(jià)格。除權(quán)除息造成的價(jià)格跳空在序列里形成一個(gè)人為的斷點(diǎn)LSTM基于前復(fù)權(quán)的歷史形態(tài)推斷出的窗口跟未復(fù)權(quán)序列完全不在一個(gè)口徑上。解決數(shù)據(jù)源全程統(tǒng)一訓(xùn)練和預(yù)測都用ts.pro_bar(adjqfq)并且把這一行寫到腳本頭部注釋里防止過幾天自己忘掉。5.3 準(zhǔn)確率評估誤區(qū)坑五拿分類準(zhǔn)確率要求回歸模型覺得模型“不穩(wěn)定”。現(xiàn)象把預(yù)測值和第二天真實(shí)開盤價(jià)對比誤差有時(shí)1%有時(shí)5%于是得出結(jié)論“模型不靠譜”。原因回歸任務(wù)沒有accuracy的概念“準(zhǔn)確率還可以”指的是平均誤差水平可控不是每次命中。第二天開盤價(jià)里包含大量歷史序列之外的信息LSTM能做的只是從過去60天形態(tài)推斷條件期望而不是預(yù)測確定值。解決改用MAE和MAPE衡量。指標(biāo)公式10元價(jià)位股票的合理范圍MAEmean(|真實(shí)值 - 預(yù)測值|)0.1 ~ 0.3 元MAPEMAE / mean(真實(shí)值) x 100%1% ~ 3%如果你的MAPE能穩(wěn)定在3%以內(nèi)這個(gè)模型就已經(jīng)具備參考價(jià)值了。真實(shí)交易里1%的誤差被手續(xù)費(fèi)和滑點(diǎn)吃掉后剩不了多少這也是為什么這個(gè)包定位是學(xué)習(xí)項(xiàng)目而不是自動交易策略。6. 進(jìn)階用法改成滾動預(yù)測畫出未來五天開盤價(jià)趨勢線單步預(yù)測畫出來是條水平虛線只能看到一個(gè)點(diǎn)。實(shí)際做趨勢觀察時(shí)我更習(xí)慣把單步預(yù)測擴(kuò)展成滾動多步預(yù)測——每預(yù)測出下一天的開盤價(jià)就把它拼到窗口末尾丟掉最早的一天再預(yù)測下一天。def rolling_predict(model, scaler, last_window, days5): current last_window.copy() # 形狀 (1, 60, 1) preds [] for _ in range(days): p model.predict(current)[0, 0] # 歸一化后的次日開盤價(jià) preds.append(p) p np.array([[[p]]]) current np.concatenate((current[:, 1:, :], p), axis1) return scaler.inverse_transform(np.array(preds).reshape(-1, 1)).flatten() future_prices rolling_predict(model, scaler, last_window, days5) print(future_prices)滾動預(yù)測有兩個(gè)使用邊界要記住。一是誤差會沿著窗口累積第一天的預(yù)測最可信第三天以后基本只能看方向別當(dāng)真值用二是它只適合畫趨勢線比如判斷未來一周開盤價(jià)是抬升還是下探不適合直接拿來做機(jī)械交易信號。這類滾動邏輯在量化交易策略代碼里經(jīng)常作為因子研究的基礎(chǔ)工具出現(xiàn)用它驗(yàn)證模型穩(wěn)定性比單點(diǎn)預(yù)測更有說服力。驗(yàn)證方法也很簡單拿最近60天數(shù)據(jù)做回看式驗(yàn)證假設(shè)今天是T用T-59到T的窗口預(yù)測T1逐日往前滾60天最后統(tǒng)計(jì)整段預(yù)測的MAE和MAPE。這個(gè)指標(biāo)庫要單獨(dú)建一個(gè)腳本存起來每次改窗口長度或模型參數(shù)后都跑一遍看MAPE是變好還是變差比盯著某一天的預(yù)測值靠譜得多。從那以后我每次拿到別人的模型包第一件事都是打開checkpoint文件確認(rèn)路徑反推訓(xùn)練時(shí)的模型結(jié)構(gòu)再寫測試腳本這一套流程幫我少翻了很多次車。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取