網(wǎng)絡(luò)在股指預(yù)測與量化選股中的完整實(shí)踐路徑)
簡介一份聚焦神經(jīng)網(wǎng)絡(luò)在金融時(shí)序分析中應(yīng)用的科研論文PDF面向量化研究、金融建模及機(jī)器學(xué)習(xí)實(shí)踐者。資料系統(tǒng)梳理了數(shù)據(jù)預(yù)處理環(huán)節(jié)中的異常值檢測與缺失值填補(bǔ)流程重點(diǎn)講解了RBP神經(jīng)網(wǎng)絡(luò)補(bǔ)全缺損數(shù)據(jù)、Markowitz均值-方差組合模型優(yōu)化收益風(fēng)險(xiǎn)以及NARX非線性多步預(yù)測網(wǎng)絡(luò)構(gòu)建股指預(yù)測模型的方法同時(shí)結(jié)合Python繪制時(shí)間序列調(diào)和曲線直觀展示數(shù)據(jù)缺失情況。NARX模型采用三層結(jié)構(gòu)隱含層使用高斯激活函數(shù)依據(jù)鏈?zhǔn)狡⒎址▌t調(diào)整網(wǎng)絡(luò)參數(shù)最終給出平均誤差0.092以內(nèi)的實(shí)證結(jié)果。內(nèi)容還覆蓋了投資組合相關(guān)矩陣與協(xié)方差矩陣的評價(jià)維度以及過擬合與交叉驗(yàn)證等實(shí)操問題從數(shù)據(jù)清洗、組合評估到模型優(yōu)化形成完整鏈路。資源為單份PDF文件共1個(gè)文件壓縮包大小1.83MB已有119人學(xué)習(xí)瀏覽。對于希望快速理解神經(jīng)網(wǎng)絡(luò)選股思路、NARX動態(tài)建模技巧以及風(fēng)險(xiǎn)收益平衡機(jī)制的讀者這份材料能提供較清晰的算法推導(dǎo)與實(shí)驗(yàn)參考價(jià)值適合作為金融科技、數(shù)據(jù)建模方向的入門研讀或項(xiàng)目參考資料。1. 股指預(yù)測不是玄學(xué)但網(wǎng)絡(luò)選錯(cuò)、數(shù)據(jù)切錯(cuò)都會翻車量化圈每隔一段時(shí)間就有人曬出“基于神經(jīng)網(wǎng)絡(luò)的股指預(yù)測與選股”的優(yōu)美曲線點(diǎn)開一看多半是驗(yàn)證集在漲。做過的人心里都有數(shù)這個(gè)方向真正的難點(diǎn)從來不是把模型跑起來而是讓預(yù)測嚴(yán)格落在未來信息之外讓選股信號在回測里真實(shí)可復(fù)現(xiàn)。它要解決兩件事——對指數(shù)未來漲跌給出概率判斷再把同一套方法平移到全市場個(gè)股上排序選出值得持倉的組合。適合已有Python和基礎(chǔ)機(jī)器學(xué)習(xí)底子、想用深度神經(jīng)網(wǎng)絡(luò)替代經(jīng)驗(yàn)規(guī)則的從業(yè)者??炊疚哪隳玫降氖且徽讖臄?shù)據(jù)清洗、特征構(gòu)造、LSTM訓(xùn)練到選股驗(yàn)證的落地路徑而不是一張畫餅的收益曲線。2. 把漲跌裝進(jìn)監(jiān)督學(xué)習(xí)問題建模與網(wǎng)絡(luò)選型2.1 先定義問題為什么預(yù)測方向比預(yù)測價(jià)格靠譜我最初的做法很幼稚把每天收盤價(jià)丟給LSTM讓它輸出明天收盤價(jià)。訓(xùn)練損失降到接近0驗(yàn)證集擬合得像印上去一樣可實(shí)盤一跑就鈍。后來才想明白價(jià)格序列是非平穩(wěn)的十年前3000點(diǎn)和現(xiàn)在3000點(diǎn)的含義完全不同。讓網(wǎng)絡(luò)去擬合絕對價(jià)格等于逼它記下一條長期漂移曲線而對真正要抓的短期相對變化幾乎沒有表達(dá)力。常見做法是把它重構(gòu)成分類問題給定過去N個(gè)交易日的特征序列預(yù)測未來H個(gè)交易日收益率是否大于某個(gè)閾值。數(shù)學(xué)化一點(diǎn)x_t 是過去N天的特征向量序列y 是未來H日收益率的正負(fù)號。為什么是分類而不是回歸因?yàn)榉诸悓υ肼暩敯魸q跌方向的信噪比遠(yuǎn)高于具體收益數(shù)值同時(shí)分類輸出天然能轉(zhuǎn)成概率為后面選股排序提供同一把尺子。閾值通常設(shè)在0.5%到1%之間太大會讓正樣本稀少太小則幾乎全是噪聲標(biāo)簽。另一個(gè)必須想清楚的點(diǎn)是滑窗長度N。對日線數(shù)據(jù)我一般取20個(gè)交易日約一個(gè)月作為基礎(chǔ)窗口。20這個(gè)數(shù)字不是玄學(xué)它對應(yīng)A股月級別趨勢的記憶長度太長會把遠(yuǎn)古行情塞進(jìn)來太短則抓不住均線支撐。窗口定了之后整個(gè)數(shù)據(jù)集的樣本量由總交易日減去窗口和預(yù)測跨度決定一般能拿到兩千到三千條樣本夠一個(gè)中小規(guī)模網(wǎng)絡(luò)吃飽。2.2 網(wǎng)絡(luò)選型前饋神經(jīng)網(wǎng)絡(luò)、LSTM、一維卷積與GNN怎么挑模型選型是新手最容易翻車的地方。一上來就上幾十層Transformer結(jié)果驗(yàn)證集還不如“昨天漲今天就買”的樸素基線。先看前饋神經(jīng)網(wǎng)絡(luò)也就是常說的BP神經(jīng)網(wǎng)絡(luò)它結(jié)構(gòu)最簡單輸入是一個(gè)打平的特征向量經(jīng)過若干隱藏層后接輸出層靠反向傳播更新權(quán)重。它的問題在于完全沒有時(shí)間順序概念——把第1天的數(shù)據(jù)和第19天的數(shù)據(jù)交換位置模型輸出不變這對行情序列是致命的。用它做序列預(yù)測調(diào)參再狠準(zhǔn)確率也基本在52%以下定位只能是基線對照。循環(huán)神經(jīng)網(wǎng)絡(luò)RNN在設(shè)計(jì)上天生為序列建模但經(jīng)典RNN存在梯度消失長序列記憶很差。LSTM神經(jīng)網(wǎng)絡(luò)用輸入門、遺忘門和輸出門把長期信息保存在單元狀態(tài)里是目前RNN最常用的替代。對股指日線這種幾百到幾千個(gè)樣本的序列兩層LSTM把小周期波動和大周期趨勢分開建模是最穩(wěn)的起點(diǎn)方案。卷積神經(jīng)網(wǎng)絡(luò)在序列預(yù)測里的用法比較特別一維卷積神經(jīng)網(wǎng)絡(luò)用一維卷積核掃描時(shí)間軸提取K線形態(tài)的局部模式比如“連續(xù)三根陽線”“長下影線放量”。它訓(xùn)練快、不挑數(shù)據(jù)規(guī)模但單用對全局趨勢捕捉弱常見做法是把1D CNN輸出再接LSTM讓CNN提特征、LSTM記時(shí)序。圖神經(jīng)網(wǎng)絡(luò)GNN是這幾年的熱點(diǎn)把個(gè)股之間的關(guān)聯(lián)、行業(yè)聯(lián)動建模成圖結(jié)構(gòu)理論上對選股很有吸引力。但圖的構(gòu)造方式、鄰接矩陣的時(shí)效性都很難調(diào)數(shù)據(jù)量也不夠支撐入門階段不建議動它等LSTM流水線跑通再研究不遲。我給的選型結(jié)論是三步走BP做基線LSTM作為主力模型先把預(yù)測做穩(wěn)之后用1D CNNLSTM的復(fù)合結(jié)構(gòu)去提特征換取邊際收益。架構(gòu)對比見表。模型時(shí)間順序訓(xùn)練成本過擬合風(fēng)險(xiǎn)在股指預(yù)測中的角色前饋/BP神經(jīng)網(wǎng)絡(luò)不支持低中基線對照RNN/LSTM支持中高主力方案一維卷積神經(jīng)網(wǎng)絡(luò)局部支持低中特征提取器CNNLSTM支持中高高進(jìn)階方案圖神經(jīng)網(wǎng)絡(luò)GNN支持高高選股擴(kuò)展2.3 輸入設(shè)計(jì)只喂收盤價(jià)等于讓模型閉眼猜我見過很多人復(fù)現(xiàn)代碼訓(xùn)練數(shù)據(jù)就是一個(gè)收盤價(jià)序列網(wǎng)絡(luò)怎么調(diào)都上不去。原因很簡單單序列信息量太低成交量、換手率、振幅、量比、均線偏離這些特征直接關(guān)系到參與者情緒和資金行為不加等于少了一整維信息。我常用的特征集按四維組織。第一維度是價(jià)格開高低收以及它們衍生出的漲跌幅和振幅。第二維度是量能成交量、成交額、換手率、量比當(dāng)日成交量與過去5日均量的比值。第三維度是資金指數(shù)層面可以看北向資金凈流入個(gè)股層面如果拿不到就用成交額替代。第四維度是技術(shù)形態(tài)5日和20日均線偏離度、RSI、布林帶位置。特征不是越多越好這是血淚教訓(xùn)。原始序列堆到三四十維網(wǎng)絡(luò)很快開始背特征驗(yàn)證集幻覺式上漲。經(jīng)驗(yàn)上限是20維左右超過的部分該篩就篩。另外所有特征在進(jìn)網(wǎng)絡(luò)前要做標(biāo)準(zhǔn)化量綱不統(tǒng)一會讓LSTM的門控機(jī)制失效——比如成交額以億為單位、RSI在0到100之間直接把原始值拼進(jìn)特征向量梯度會被大數(shù)值特征帶走小數(shù)值特征等于白放。3. 從行情數(shù)據(jù)到訓(xùn)練樣本特征構(gòu)造與數(shù)據(jù)集切分3.1 數(shù)據(jù)獲取與清洗停牌、復(fù)權(quán)和跳空三件事以滬深300指數(shù)日線為例常見做法是用akshare這類免費(fèi)數(shù)據(jù)接口直接拿數(shù)據(jù)代碼很短import akshare as ak import pandas as pd # 拉取滬深300指數(shù)日線接口返回的列名在不同版本略有差異 raw ak.index_zh_a_hist(symbol000300, perioddaily, start_date20100101, end_date20241231) raw.columns [date, code, open, close, high, low, volume, amount, amplitude, pct_change, change, turnover] # 只保留建模要用的列并按時(shí)間升序排列 df raw[[date, open, close, high, low, volume, amount, turnover]].copy() df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) # 有缺失的交易日直接剔除不做填充 df df.dropna().reset_index(dropTrue) print(df.tail())這段代碼的邏輯是先拿全歷史日線再統(tǒng)一列名然后按時(shí)間排序最后剔除缺失值。需要說明的是指數(shù)日線本身不存在復(fù)權(quán)問題但如果把模型平移到個(gè)股上一定要用前復(fù)權(quán)價(jià)格否則除權(quán)日會出現(xiàn)一個(gè)與真實(shí)交易無關(guān)的假跳空模型會當(dāng)成重要信號學(xué)進(jìn)去。停牌日同樣要處理常見做法是刪除該日記錄而不是用前后填充值硬補(bǔ)因?yàn)檠a(bǔ)出來的價(jià)格不真實(shí)。參數(shù)上start_date選2010年而不是更早一是A股早期流動性結(jié)構(gòu)和現(xiàn)在差異大二是數(shù)據(jù)區(qū)間越長行情風(fēng)格漂移越明顯。對日線級預(yù)測10到15年數(shù)據(jù)已經(jīng)能覆蓋兩三輪牛熊再多反而讓模型去擬合早已失效的舊規(guī)則。3.2 特征構(gòu)造與標(biāo)簽生成未來5日方向怎么寫才不泄漏特征構(gòu)造我習(xí)慣用向量化操作一次性算完寫在一個(gè)函數(shù)里清洗和調(diào)試都方便def build_features(df): # 基礎(chǔ)漲跌幅和振幅 df[ret_1d] df[close].pct_change() df[amp] (df[high] - df[low]) / df[close] # 均線偏離度收盤價(jià)相對均線的位置 for win in [5, 10, 20]: ma df[close].rolling(win).mean() df[fma_dev_{win}] df[close] / ma - 1 # 量比當(dāng)日成交量除以5日均量反映放量縮量 df[vol_ratio] df[volume] / df[volume].rolling(5).mean() # RSI(14)常用動量指標(biāo) diff df[close].diff() gain diff.clip(lower0).rolling(14).mean() loss (-diff.clip(upper0)).rolling(14).mean() rs gain / (loss 1e-9) df[rsi_14] 100 - 100 / (1 rs) # 標(biāo)簽未來5日收益率是否超過0.5%作為上漲正樣本 df[future_ret] df[close].shift(-5) / df[close] - 1 df[label] (df[future_ret] 0.005).astype(int) # 刪除因滾動窗口和shift產(chǎn)生的前后缺失行 df df.dropna().reset_index(dropTrue) return df這里有一個(gè)關(guān)鍵細(xì)節(jié)標(biāo)簽的future_ret用的是close.shift(-5)意味著第t行樣本的標(biāo)簽只有到t5日收盤后才能確定。真實(shí)部署時(shí)要格外注意當(dāng)天收盤后只能做出當(dāng)天的特征和預(yù)測不能用未來數(shù)據(jù)驗(yàn)證這是很多回測失效的根源。量比、均線偏離、RSI都是無量綱或有界量進(jìn)入LSTM前不需要再做標(biāo)準(zhǔn)化成交量、成交額這類絕對量則要取對數(shù)或交給StandardScaler。不要把原始成交量直接喂進(jìn)去幾百億和幾百萬的數(shù)值差距會把梯度推向一個(gè)方向小特征直接失效。3.3 按時(shí)間切分與標(biāo)準(zhǔn)化訓(xùn)練集、驗(yàn)證集、測試集順序不能亂切分股票數(shù)據(jù)不能像圖像分類那樣隨機(jī)打亂必須按時(shí)間順序硬切import numpy as np def split_sequences(df, feature_cols, seq_len20): data df[feature_cols].values labels df[label].values X, y [], [] for i in range(len(data) - seq_len): win_x data[i:i seq_len] win_y labels[i seq_len - 1] # 窗口內(nèi)最后一天的標(biāo)簽 X.append(win_x) y.append(win_y) X np.array(X, dtypenp.float32) y np.array(y, dtypenp.int64) # 按時(shí)間順序前70%訓(xùn)練中間15%驗(yàn)證最后15%測試 n len(X) idx_train int(n * 0.70) idx_val int(n * 0.85) X_train, y_train X[:idx_train], y[:idx_train] X_val, y_val X[idx_train:idx_val], y[idx_train:idx_val] X_test, y_test X[idx_val:], y[idx_val:] return (X_train, y_train), (X_val, y_val), (X_test, y_test)這里seq_len20代表用最近20個(gè)交易日的特征序列預(yù)測未來5日方向。特征窗口和標(biāo)簽窗口前后相接不存在重疊所以每個(gè)滑窗樣本的信息只來自窗口內(nèi)的過去20天。標(biāo)準(zhǔn)化必須在切分之后進(jìn)行且只能fit訓(xùn)練集再拿同一組參數(shù)transform驗(yàn)證集和測試集from sklearn.preprocessing import StandardScaler scaler StandardScaler() n_features X_train.shape[2] X_train_flat X_train.reshape(-1, n_features) scaler.fit(X_train_flat) # 只學(xué)訓(xùn)練集的均值和方差 X_train scaler.transform(X_train_flat).reshape(X_train.shape) X_val scaler.transform(X_val.reshape(-1, n_features)).reshape(X_val.shape) X_test scaler.transform(X_test.reshape(-1, n_features)).reshape(X_test.shape)如果把scaler.fit放在切分前或?qū)φ麄€(gè)數(shù)據(jù)集做驗(yàn)證集和測試集的信息就會泄漏進(jìn)訓(xùn)練過程這是時(shí)序預(yù)測里最隱蔽的坑也是很多模型實(shí)驗(yàn)集指標(biāo)高得離譜、實(shí)盤一塌糊涂的原因。注意標(biāo)準(zhǔn)化一定要在切分之后做。先切分、再fit、再transform這個(gè)順序錯(cuò)了后面的所有評估都失去意義。4. LSTM模型訓(xùn)練與調(diào)參先讓預(yù)測穩(wěn)定再談收益4.1 搭建一個(gè)可復(fù)現(xiàn)的兩層LSTM預(yù)測器模型結(jié)構(gòu)我固定成“LSTM層全連接層”代碼短、調(diào)試容易也方便后續(xù)把數(shù)據(jù)規(guī)模擴(kuò)大后換更復(fù)雜的結(jié)構(gòu)import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size, hidden_size, num_layers, dropout0.3): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0.0, ) # 二分類輸出1個(gè)logit配合BCEWithLogitsLoss使用 self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x shape: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # 取最后一個(gè)時(shí)間步的隱狀態(tài)作為序列表示 last out[:, -1, :] logits self.fc(last).squeeze(-1) return logits參數(shù)含義逐一說清楚input_size是特征維度也就是第3章feature_cols的長度hidden_size是LSTM隱狀態(tài)維度64是起步值再小表達(dá)能力不足再大容易過擬合num_layers2分成兩個(gè)堆疊層讓網(wǎng)絡(luò)先學(xué)短周期再學(xué)長周期batch_firstTrue表示輸入張量維度順序是(批次, 序列長度, 特征數(shù))PyTorch默認(rèn)是(序列長度, 批次, 特征數(shù))寫錯(cuò)就會出現(xiàn)維度報(bào)錯(cuò)。forward里out[:, -1, :]取的是序列最后一個(gè)時(shí)間步的輸出。有人會把所有時(shí)間步的輸出做mean pooling也可以但最后一個(gè)時(shí)間步的信息最接近預(yù)測點(diǎn)實(shí)操里效果通常更好。全連接層把隱狀態(tài)壓到1個(gè)logit對應(yīng)二分類如果要換成三分類漲/平/跌把fc輸出維度改成3并把損失函數(shù)換成CrossEntropyLoss即可。4.2 訓(xùn)練配置學(xué)習(xí)率、早停與學(xué)習(xí)率衰減訓(xùn)練循環(huán)用PyTorch標(biāo)配寫法加上梯度裁剪、早停和學(xué)習(xí)率衰減三個(gè)機(jī)制def train_model(model, train_loader, val_loader, epochs60, lr1e-3, patience10): optimizer torch.optim.Adam(model.parameters(), lrlr, weight_decay1e-5) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5) criterion nn.BCEWithLogitsLoss() best_val_loss float(inf) best_state None wait 0 for epoch in range(epochs): # 訓(xùn)練階段 model.train() total_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() logits model(xb) loss criterion(logits, yb.float()) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() * xb.size(0) # 驗(yàn)證階段 model.eval() val_loss 0.0 with torch.no_grad(): for xb, yb in val_loader: logits model(xb) loss criterion(logits, yb.float()) val_loss loss.item() * xb.size(0) scheduler.step(val_loss) # 早停驗(yàn)證損失連續(xù)patience輪不下降就恢復(fù)最佳權(quán)重 if val_loss best_val_loss: best_val_loss val_loss best_state {k: v.clone() for k, v in model.state_dict().items()} wait 0 else: wait 1 if wait patience: model.load_state_dict(best_state) print(f早停于 epoch {epoch}) break else: model.load_state_dict(best_state) return model三個(gè)關(guān)鍵點(diǎn)。第一梯度裁剪clip_grad_norm_是LSTM訓(xùn)練必須的序列建模的梯度范數(shù)容易爆炸不裁剪loss會突然變成nanmax_norm1.0是保守值想放開可以試2.0。第二weight_decay1e-5做小的L2正則對抗過擬合。第三ReduceLROnPlateau在驗(yàn)證損失平穩(wěn)時(shí)把學(xué)習(xí)率減半比固定學(xué)習(xí)率更容易收斂到好的局部極小值。EarlyStopping的patience10輪和scheduler的patience5輪要區(qū)分開scheduler先一步開始降學(xué)習(xí)率給模型機(jī)會跳出平臺期如果降了學(xué)習(xí)率還改善不了再觸發(fā)早停這比直接早停更穩(wěn)。epochs60只是上限實(shí)戰(zhàn)里數(shù)據(jù)量小時(shí)往往20到40輪就早停了。4.3 評估預(yù)測方向準(zhǔn)確率、F1和樸素基線對比訓(xùn)練完不要只看loss要看預(yù)測方向的命中情況。我固定用下面這組指標(biāo)卡模型from sklearn.metrics import accuracy_score, f1_score, confusion_matrix def evaluate(model, X, y): model.eval() with torch.no_grad(): logits model(torch.tensor(X)) prob torch.sigmoid(logits) pred (prob 0.5).int().numpy() acc accuracy_score(y, pred) f1 f1_score(y, pred) cm confusion_matrix(y, pred) return {accuracy: acc, f1: f1, confusion: cm}除了accuracy我還額外看兩個(gè)東西。一是F1分?jǐn)?shù)因?yàn)闈q跌樣本經(jīng)常不平衡單邊行情里會嚴(yán)重傾斜準(zhǔn)確率會被多數(shù)類放大。二是看漲類別的召回率——選股任務(wù)里更關(guān)心“該漲的股票有沒有被選出來”召回率低說明模型把大量真上漲漏掉了排序選股的意義就大打折扣。最重要的對照實(shí)驗(yàn)是樸素基線直接用“今日收盤漲則預(yù)測明天也漲”做規(guī)則準(zhǔn)確率大約51%到53%。神經(jīng)網(wǎng)絡(luò)如果連這個(gè)都超不過別談后面的選股先去查特征和泄漏。當(dāng)模型在測試集上準(zhǔn)確率穩(wěn)定大于55%時(shí)方向統(tǒng)計(jì)意義上可用想沖到60%以上日線級別的指數(shù)預(yù)測很難復(fù)現(xiàn)不如轉(zhuǎn)向選股場景——樣本量從1個(gè)指數(shù)變成幾千只股票模型能學(xué)到的東西才夠多。5. 時(shí)序預(yù)測的五個(gè)經(jīng)典坑數(shù)據(jù)泄漏、過擬合與失效特征5.1 標(biāo)準(zhǔn)化和標(biāo)簽里的未來函數(shù)兩個(gè)最隱蔽的數(shù)據(jù)泄漏現(xiàn)象訓(xùn)練完的模型在驗(yàn)證集上準(zhǔn)確率高達(dá)80%曲線完美得像資產(chǎn)翻倍可實(shí)盤交易幾天就連續(xù)虧損。這是我在這個(gè)方向踩過的最大一個(gè)坑排查到最后發(fā)現(xiàn)泄漏根本不在模型里而在數(shù)據(jù)處理。原因一有的人在切分?jǐn)?shù)據(jù)前對整份DataFrame做StandardScaler把驗(yàn)證集、測試集的均值和方差也學(xué)進(jìn)了轉(zhuǎn)換參數(shù)。模型等于開卷考試測試數(shù)據(jù)的信息已經(jīng)被編碼進(jìn)特征分布里驗(yàn)證集當(dāng)然好看。原因二標(biāo)簽構(gòu)造時(shí)誤用未來信息比如用當(dāng)天的最高最低價(jià)來決定當(dāng)天是否持倉或者用未來復(fù)權(quán)數(shù)據(jù)反推現(xiàn)在的價(jià)格都屬于未來函數(shù)。解決嚴(yán)格遵循“先按時(shí)間切分再只fit訓(xùn)練集”兩步法標(biāo)簽必須由t1日及之后的純未來數(shù)據(jù)計(jì)算且特征窗口和標(biāo)簽窗口之間不能有重疊。我現(xiàn)在的代碼里專門寫了一條斷言測試集樣本的時(shí)間戳必須全部晚于訓(xùn)練集跑數(shù)前先校驗(yàn)寧可報(bào)錯(cuò)也不要帶病訓(xùn)練。5.2 過擬合與隨機(jī)種子為什么兩次訓(xùn)練結(jié)果差很多現(xiàn)象訓(xùn)練集loss一路降到0.1以下驗(yàn)證集loss在某個(gè)epoch后突然反彈或者模型輸出基本只有“漲”一類。另一個(gè)現(xiàn)象是同一份數(shù)據(jù)、同一個(gè)腳本兩次訓(xùn)練的準(zhǔn)確率能差3到5個(gè)百分點(diǎn)這讓回測和調(diào)參都不可信神經(jīng)網(wǎng)絡(luò)在這里表現(xiàn)得像個(gè)黑匣子。原因hidden_size設(shè)到256甚至512LSTM層數(shù)加到3層以上再加上全連接層后參數(shù)量輕松過千萬而日線樣本量只有兩三千條模型直接背題。隨機(jī)種子不固定是另一個(gè)隱形原因PyTorch的DataLoader默認(rèn)shuffleTrue加上沒有固定torch.manual_seed每個(gè)epoch的樣本順序都在變訓(xùn)練結(jié)果自然漂移。解決從LSTM兩層、hidden_size64起步dropout0.3加早停和梯度裁剪在腳本開頭固定torch.manual_seed(42)、np.random.seed(42)DataLoader里設(shè)置generator。如果調(diào)大參數(shù)量要同步增加正則強(qiáng)度并把訓(xùn)練集樣本擴(kuò)到1萬條以上否則就是拿參數(shù)量換幻覺。5.3 類別不平衡與回測滑點(diǎn)選股信號失效的兩個(gè)放大器現(xiàn)象把模型從指數(shù)平移到全市場個(gè)股后Top20組合的回測年化收益很高但實(shí)盤跟單跑不贏有的月份還跑輸指數(shù)。原因一全市場個(gè)股的漲跌分布并非均衡。指數(shù)成分股在震蕩市大約五成交易日上漲但個(gè)股端牛市里上漲占比可到60%以上。模型用默認(rèn)閾值0.5做分類時(shí)會把大量邊緣上漲判成下跌選股排名的區(qū)分度被壓縮。原因二回測只按收盤價(jià)成交沒扣手續(xù)費(fèi)和滑點(diǎn)。A股雙邊費(fèi)率加滑點(diǎn)一般在萬三到萬五5日換倉一次一年約50次換手成本累計(jì)吃掉3到5個(gè)點(diǎn)預(yù)測本身只有55%左右的準(zhǔn)確率這點(diǎn)優(yōu)勢很容易被交易成本抹平。解決對類別做加權(quán)或降低判斷閾值回測里每筆成交強(qiáng)制扣除雙邊費(fèi)用至少扣萬五再算凈收益選股周期不要短于5個(gè)交易日換手越頻繁成本越敏感。我現(xiàn)在的做法是預(yù)測概率只用于排序排名穩(wěn)定后再結(jié)合基本面做人工過濾不盲目相信分?jǐn)?shù)本身。5.4 特征失效與行情漂移模型不是訓(xùn)一次用一年現(xiàn)象模型在測試集上準(zhǔn)確率56%照此跑了一個(gè)月第二個(gè)月開始連續(xù)低于50%Top20組合跑不贏指數(shù)。很多人第一反應(yīng)是“模型壞了”其實(shí)只是市場風(fēng)格變了。原因日線特征均線、RSI、量比在趨勢市有效進(jìn)入震蕩市后特征分布整體偏移模型學(xué)到的是舊風(fēng)格下的規(guī)律。行情風(fēng)格漂移不是異常而是常態(tài)用固定模型去對抗漂移等于拿去年的地圖找今年的路。解決滾動重訓(xùn)每月底用最近12個(gè)月數(shù)據(jù)重新訓(xùn)練一次同時(shí)對特征分布做監(jiān)控比如計(jì)算特征均值、方差的變化幅度一旦超過閾值就觸發(fā)重訓(xùn)。這是我做這個(gè)方向最持續(xù)的維護(hù)成本也是想長期投入必須接受的現(xiàn)實(shí)模型是一個(gè)需要定期保養(yǎng)的工具不是一勞永逸的印鈔機(jī)。6. 從預(yù)測分?jǐn)?shù)到選股組合用RankIC評估選股能力6.1 用預(yù)測概率排序構(gòu)建等權(quán)組合訓(xùn)練好模型后等收盤拿到當(dāng)日全市場股票特征逐只推理出上漲概率按概率從高到低排序取前20只等權(quán)持有5個(gè)交易日scores [] for stock_df in all_stocks: # 每只股票的特征序列 prob predict(model, stock_df) # 取sigmoid后的概率 scores.append(prob) # 按概率降序取前20只等權(quán)分配資金 picks sorted(range(len(scores)), keylambda i: -scores[i])[:20] weight 1 / 20等權(quán)是為了不把賭注押在單只股票上。排序而不是設(shè)閾值選股是因?yàn)槟P托?zhǔn)不一定完美——概率絕對值可能有偏但相對排名在同類股票之間更穩(wěn)定。6.2 RankIC與分組回測看排序能力不看準(zhǔn)確率選股模型的評價(jià)指標(biāo)和預(yù)測模型不一樣準(zhǔn)確率只說明“漲跌命中”選股還看重“概率高的是不是真的漲更多”。我固定用下一期收益和預(yù)測概率的Spearman相關(guān)系數(shù)也就是RankIC來判斷RankIC區(qū)間判斷大于0.05較好的選股能力值得繼續(xù)投入0.03到0.05有一定區(qū)分度可小倉位驗(yàn)證0到0.03統(tǒng)計(jì)不顯著先回去查特征泄漏小于0模型在反向選擇立即停用我自己的習(xí)慣是每天盤中跑完選股后把Top20組合收益和全市場平均收益對比連續(xù)跟蹤4到8周看超額收益是否穩(wěn)定為正。這個(gè)頻率能過濾掉單個(gè)月的行情運(yùn)氣也比訓(xùn)練一次看一個(gè)測試集更接近實(shí)盤真相。這個(gè)方向值得做但它不是一夜暴富的捷徑靠的是把數(shù)據(jù)、特征、回測紀(jì)律一件件磨到位。我自己也栽過“驗(yàn)證集75%、實(shí)盤虧損”的跟頭后來才明白時(shí)序預(yù)測里最大的敵人不是模型容量而是無處不在的未來信息。希望你在這個(gè)方向上少走幾步彎路。本文還有配套的精品資源點(diǎn)擊獲取