實(shí)戰(zhàn):機(jī)器學(xué)習(xí)策略demo從數(shù)據(jù)到回測(cè)全流程)
簡(jiǎn)介這是一份面向具備一定Python基礎(chǔ)、對(duì)炒股與量化投資尚不熟悉的初學(xué)者打造的入門(mén)級(jí)demo源碼圍繞機(jī)器學(xué)習(xí)在A股量化策略中的應(yīng)用展開(kāi)幫助讀者快速理解數(shù)據(jù)收集、特征工程、模型構(gòu)建與回測(cè)評(píng)估的完整流程。資源包共14個(gè)文件以5個(gè)py腳本、6張png圖表、2個(gè)txt說(shuō)明及1個(gè)gitignore為主Python文件分別承擔(dān)回測(cè)、特征處理、模型訓(xùn)練與數(shù)據(jù)讀取等模塊職責(zé)圖片則直觀展示決策樹(shù)預(yù)測(cè)股價(jià)漲幅、最大回撤、K線(xiàn)走勢(shì)等關(guān)鍵結(jié)果壓縮包整體約727KB結(jié)構(gòu)清晰便于按模塊學(xué)習(xí)。目前已有156人學(xué)習(xí)下載。通過(guò)這份源碼讀者可對(duì)照運(yùn)行從數(shù)據(jù)處理到策略回測(cè)的全流程觀察不同機(jī)器學(xué)習(xí)算法在價(jià)格預(yù)測(cè)中的表現(xiàn)并借助回測(cè)結(jié)果理解止損、倉(cāng)位控制等風(fēng)險(xiǎn)管理思路為后續(xù)獨(dú)立編寫(xiě)量化策略打下實(shí)踐基礎(chǔ)。1. 從一份能跑通的 A 股量化 demo 說(shuō)起它到底解決了什么很多人第一次接觸量化投資卡住的地方不是不會(huì)寫(xiě) Python而是不知道一個(gè)完整策略從數(shù)據(jù)到回測(cè)到底長(zhǎng)什么樣。網(wǎng)上搜「機(jī)器學(xué)習(xí) 量化投資策略」出來(lái)的要么是純理論推導(dǎo)要么是殘缺的代碼片段跑不起來(lái)也驗(yàn)證不了。這份ai_qua2323weweer.zip就是沖著這個(gè)痛點(diǎn)來(lái)的——它把數(shù)據(jù)獲取、特征工程、模型訓(xùn)練、回測(cè)評(píng)估串成了一條能跑通的鏈路用 A 股市場(chǎng)做樣本適合有 Python 基礎(chǔ)但對(duì)炒股和量化一無(wú)所知的人拿來(lái)拆解。壓縮包里除了main.py、backtest.py、feature.py、model.py、data.py這幾個(gè)核心腳本還附了柱狀圖、最大回撤、K 線(xiàn)圖、決策樹(shù)預(yù)測(cè)股價(jià)漲幅等運(yùn)行截圖以及tushare_token.png提示你需要自己配數(shù)據(jù)源憑證。說(shuō)白了它不是一個(gè)能直接拿去實(shí)盤(pán)的策略而是一個(gè)讓你看清「量化策略長(zhǎng)什么樣」的腳手架。2. 拆開(kāi)壓縮包五個(gè) Python 文件各自扛什么活2.1 模塊分工與調(diào)用關(guān)系拿到源碼先別急著pip install花五分鐘把文件職責(zé)理清楚后面調(diào)參和排錯(cuò)會(huì)省很多事。這個(gè) demo 的結(jié)構(gòu)是典型的流水線(xiàn)式設(shè)計(jì)main.py是總調(diào)度其余四個(gè)模塊各管一段。文件職責(zé)關(guān)鍵輸入關(guān)鍵輸出data.py拉取 A 股歷史行情、清洗缺失值tushare token、股票代碼列表規(guī)整的 DataFramefeature.py從原始行情構(gòu)造技術(shù)指標(biāo)特征行情 DataFrame特征矩陣 Xmodel.py構(gòu)建并訓(xùn)練機(jī)器學(xué)習(xí)模型特征矩陣、標(biāo)簽 y訓(xùn)練好的模型對(duì)象backtest.py用歷史數(shù)據(jù)模擬買(mǎi)賣(mài)、算收益模型預(yù)測(cè)信號(hào)、價(jià)格序列收益曲線(xiàn)、最大回撤main.py串聯(lián)全流程以上全部回測(cè)報(bào)告與圖表stock_list.txt里放的是股票池requirements.txt鎖定了依賴(lài)版本.gitignore說(shuō)明作者是按工程習(xí)慣組織的。img目錄下的截圖不是裝飾是你跑完后用來(lái)對(duì)照結(jié)果是否正常的參照物——比如最大回撤.png能幫你判斷回測(cè)邏輯有沒(méi)有寫(xiě)反。2.2 環(huán)境準(zhǔn)備與依賴(lài)安裝先確認(rèn) Python 版本這類(lèi) demo 一般跑在 3.8 到 3.10 之間比較穩(wěn)。建虛擬環(huán)境是基本操作別嫌麻煩血淚經(jīng)驗(yàn)是直接在系統(tǒng)環(huán)境里裝依賴(lài)后面版本沖突能讓你重裝系統(tǒng)。# 創(chuàng)建并激活虛擬環(huán)境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安裝依賴(lài)requirements.txt 里通常包含這些 pip install -r requirements.txtrequirements.txt里大概率會(huì)有pandas、numpy、scikit-learn、tushare、matplotlib這幾個(gè)。如果安裝tushare時(shí)報(bào)錯(cuò)常見(jiàn)原因是網(wǎng)絡(luò)問(wèn)題或版本過(guò)舊可以指定版本重試。裝完后用pip list核對(duì)一下重點(diǎn)看scikit-learn的版本因?yàn)椴煌姹纠顳ecisionTreeClassifier的默認(rèn)參數(shù)有差異會(huì)影響你復(fù)現(xiàn)截圖里的決策樹(shù)結(jié)果。2.3 數(shù)據(jù)源配置token 是繞不過(guò)去的門(mén)檻tushare_token.png這張截圖就是在提醒你數(shù)據(jù)獲取需要憑證。tushare 的免費(fèi)額度對(duì) demo 級(jí)別的回測(cè)夠用但需要注冊(cè)后拿到 token。# data.py 中通常會(huì)有類(lèi)似這樣的初始化代碼 import tushare as ts # 把這里的 token 替換成你自己的 ts.set_token(你的tushare_token) pro ts.pro_api() # 拉取單只股票日線(xiàn)數(shù)據(jù) df pro.daily(ts_code000001.SZ, start_date20200101, end_date20231231)邏輯說(shuō)明set_token只需調(diào)用一次之后pro_api()返回的接口對(duì)象可以復(fù)用。daily接口返回的是未復(fù)權(quán)數(shù)據(jù)做量化回測(cè)時(shí)要注意復(fù)權(quán)問(wèn)題否則除權(quán)除息日會(huì)出現(xiàn)價(jià)格跳空導(dǎo)致特征失真。參數(shù)上ts_code的格式是「代碼.交易所」000001.SZ是深交所600000.SH是上交所。如果你拉數(shù)據(jù)返回空 DataFrame先檢查 token 是否過(guò)期再確認(rèn)股票代碼格式和日期范圍。3. 特征工程與模型訓(xùn)練把行情變成模型能吃的數(shù)字3.1 從價(jià)格序列構(gòu)造有效特征原始行情只有開(kāi)高低收和成交量直接丟給模型效果很差。feature.py的核心任務(wù)就是把這些序列轉(zhuǎn)成有預(yù)測(cè)意義的特征。常見(jiàn)做法是構(gòu)造動(dòng)量類(lèi)、波動(dòng)率類(lèi)、成交量類(lèi)指標(biāo)。# feature.py 中特征構(gòu)造的典型寫(xiě)法 import pandas as pd import numpy as np def build_features(df): df df.sort_values(trade_date).reset_index(dropTrue) # 動(dòng)量特征過(guò)去 5 日、10 日收益率 df[ret_5] df[close].pct_change(5) df[ret_10] df[close].pct_change(10) # 波動(dòng)率特征過(guò)去 10 日收益率標(biāo)準(zhǔn)差 df[vol_10] df[close].pct_change().rolling(10).std() # 成交量特征當(dāng)日成交量與 5 日均量之比 df[vol_ratio] df[vol] / df[vol].rolling(5).mean() # 標(biāo)簽未來(lái) 5 日漲跌方向漲為 1 跌為 0 df[label] (df[close].shift(-5) df[close]).astype(int) df df.dropna() return df邏輯說(shuō)明pct_change(5)算的是 5 日累計(jì)收益率反映中期動(dòng)量。rolling(10).std()衡量波動(dòng)率波動(dòng)率放大往往伴隨趨勢(shì)轉(zhuǎn)折。vol_ratio捕捉放量行為放量突破在 A 股里是比較有效的信號(hào)。標(biāo)簽用shift(-5)構(gòu)造未來(lái) 5 日方向注意這會(huì)造成最后 5 行標(biāo)簽為 NaN必須dropna掉否則訓(xùn)練時(shí)會(huì)報(bào)錯(cuò)。參數(shù)上窗口期不是固定的5 日和 10 日只是 demo 默認(rèn)值你可以改成 3 日或 20 日觀察模型表現(xiàn)變化。3.2 決策樹(shù)模型的訓(xùn)練與調(diào)參model.py里用的是決策樹(shù)截圖決策樹(shù)預(yù)測(cè)股價(jià)漲幅.png就是它的輸出。選決策樹(shù)而不是神經(jīng)網(wǎng)絡(luò)是因?yàn)樗山忉屝詮?qiáng)能直接看到哪些特征重要對(duì)入門(mén)者友好。# model.py 中模型訓(xùn)練的核心代碼 from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score def train_model(df): feature_cols [ret_5, ret_10, vol_10, vol_ratio] X df[feature_cols] y df[label] # 按時(shí)間順序切分不能隨機(jī)打亂 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] clf DecisionTreeClassifier( max_depth4, # 控制樹(shù)深防止過(guò)擬合 min_samples_leaf50, # 葉子節(jié)點(diǎn)最少樣本數(shù) random_state42 ) clf.fit(X_train, y_train) pred clf.predict(X_test) print(測(cè)試集準(zhǔn)確率:, accuracy_score(y_test, pred)) return clf邏輯說(shuō)明時(shí)間序列數(shù)據(jù)絕對(duì)不能隨機(jī)切分train_test_split默認(rèn)的 shuffle 會(huì)把未來(lái)信息泄露到訓(xùn)練集導(dǎo)致回測(cè)虛高這是新手最容易翻車(chē)的地方。max_depth4是刻意壓低的決策樹(shù)不加限制可以無(wú)限生長(zhǎng)訓(xùn)練集準(zhǔn)確率能到 99% 但測(cè)試集一塌糊涂。min_samples_leaf50保證每個(gè)葉子有足夠樣本支撐避免模型記住噪聲。如果你把max_depth調(diào)到 10 以上大概率會(huì)看到訓(xùn)練準(zhǔn)確率飆升但回測(cè)收益反而下降這就是過(guò)擬合的典型表現(xiàn)。3.3 回測(cè)邏輯別讓未來(lái)函數(shù)毀掉結(jié)果backtest.py是整個(gè) demo 最需要盯緊的部分?;販y(cè)的核心是模擬「如果當(dāng)時(shí)按信號(hào)操作收益會(huì)怎樣」但很容易寫(xiě)出用到未來(lái)數(shù)據(jù)的代碼。# backtest.py 中簡(jiǎn)化的回測(cè)循環(huán) def run_backtest(df, model): df df.copy() df[signal] model.predict(df[[ret_5, ret_10, vol_10, vol_ratio]]) # 信號(hào)滯后一期避免用當(dāng)日收盤(pán)信號(hào)做當(dāng)日交易 df[position] df[signal].shift(1) # 當(dāng)日收益率 df[daily_ret] df[close].pct_change() # 策略收益 持倉(cāng)信號(hào) × 次日收益 df[strategy_ret] df[position] * df[daily_ret] # 累計(jì)凈值 df[cum_ret] (1 df[strategy_ret]).cumprod() # 最大回撤 df[peak] df[cum_ret].cummax() df[drawdown] (df[cum_ret] - df[peak]) / df[peak] max_dd df[drawdown].min() print(累計(jì)收益:, df[cum_ret].iloc[-1] - 1) print(最大回撤:, max_dd) return df邏輯說(shuō)明shift(1)是關(guān)鍵一步它保證你今天看到的信號(hào)明天才執(zhí)行模擬真實(shí)交易中「收盤(pán)后算信號(hào)、次日開(kāi)盤(pán)操作」的節(jié)奏。如果不加這個(gè)滯后等于用當(dāng)天收盤(pán)價(jià)預(yù)測(cè)當(dāng)天漲跌再當(dāng)天買(mǎi)賣(mài)回測(cè)收益會(huì)好得不真實(shí)。cumprod()算累計(jì)凈值cummax()算歷史高點(diǎn)兩者相除得到回撤序列。最大回撤是負(fù)數(shù)越接近 0 越好。跑完后對(duì)照最大回撤.png如果你的回撤超過(guò) -50%要么是模型有問(wèn)題要么是回測(cè)邏輯寫(xiě)錯(cuò)了。4. 避坑與排查跑不通、結(jié)果離譜時(shí)先看這幾條4.1 現(xiàn)象tushare 返回空數(shù)據(jù)或報(bào)權(quán)限錯(cuò)誤原因通常有三個(gè)token 沒(méi)設(shè)置、token 過(guò)期、或者調(diào)用的接口超出免費(fèi)額度。解決方法是先打印ts.get_token()確認(rèn) token 已寫(xiě)入再去 tushare 官網(wǎng)看接口權(quán)限說(shuō)明。免費(fèi)賬戶(hù)對(duì)daily接口有頻率限制短時(shí)間內(nèi)大量拉取會(huì)被臨時(shí)封禁建議在循環(huán)里加time.sleep(0.5)。4.2 現(xiàn)象回測(cè)收益高得離譜年化幾百個(gè)百分點(diǎn)這是典型的未來(lái)函數(shù)問(wèn)題。檢查backtest.py里信號(hào)有沒(méi)有做shift檢查特征構(gòu)造時(shí)有沒(méi)有用到當(dāng)日收盤(pán)之后才知道的數(shù)據(jù)。另一個(gè)隱蔽的坑是標(biāo)簽構(gòu)造用了shift(-5)但特征里不小心混入了未來(lái)信息。解決方法是把回測(cè)信號(hào)整體滯后一期并且用樣本外數(shù)據(jù)驗(yàn)證。4.3 現(xiàn)象決策樹(shù)準(zhǔn)確率 99% 但實(shí)盤(pán)邏輯不成立原因幾乎一定是過(guò)擬合。決策樹(shù)不加max_depth和min_samples_leaf限制可以無(wú)限分裂直到每個(gè)葉子只有一個(gè)樣本訓(xùn)練集完美擬合但毫無(wú)泛化能力。解決方法是在model.py里把樹(shù)深壓到 3 到 5 層葉子最小樣本數(shù)設(shè)到 30 以上然后觀察測(cè)試集準(zhǔn)確率是否在 50% 到 60% 之間——這個(gè)區(qū)間對(duì)股票方向預(yù)測(cè)才是正常的。4.4 現(xiàn)象matplotlib 畫(huà)圖中文顯示成方塊原因是默認(rèn)字體不支持中文。在main.py或畫(huà)圖腳本開(kāi)頭加兩行配置即可。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 用黑體 plt.rcParams[axes.unicode_minus] False # 正常顯示負(fù)號(hào)Mac 系統(tǒng)把SimHei換成Arial Unicode MS。改完如果還不行清一下 matplotlib 的字體緩存再重跑。4.5 現(xiàn)象requirements.txt 安裝時(shí)報(bào)版本沖突常見(jiàn)于pandas和numpy版本不匹配。解決方法是先裝numpy再裝pandas或者直接用pip install -r requirements.txt --no-deps跳過(guò)依賴(lài)解析手動(dòng)補(bǔ)齊缺失的包。如果scikit-learn版本和 demo 作者不一致導(dǎo)致 API 報(bào)錯(cuò)查一下你裝的版本里DecisionTreeClassifier的參數(shù)名有沒(méi)有變多數(shù)情況向下兼容。5. 進(jìn)階玩法把 demo 改成你自己的策略實(shí)驗(yàn)臺(tái)跑通默認(rèn)流程只是起點(diǎn)這個(gè) demo 真正的價(jià)值在于它給你留了改的空間。我一般會(huì)從三個(gè)方向動(dòng)手換模型、換特征、換股票池。換模型最直接。model.py里把DecisionTreeClassifier換成RandomForestClassifier或GradientBoostingClassifier接口幾乎不用改但要注意隨機(jī)森林的n_estimators別設(shè)太大100 到 200 棵足夠再多訓(xùn)練時(shí)間線(xiàn)性增長(zhǎng)而收益提升有限。換成LogisticRegression的話(huà)記得先對(duì)特征做標(biāo)準(zhǔn)化否則量綱差異會(huì)讓系數(shù)失去意義。換特征需要回到feature.py。demo 里只用了動(dòng)量和波動(dòng)率你可以加均線(xiàn)交叉、RSI、MACD 柱狀圖斜率這些經(jīng)典指標(biāo)。但每加一個(gè)特征都要問(wèn)自己這個(gè)特征在交易時(shí)點(diǎn)是否可得如果用了未來(lái)數(shù)據(jù)回測(cè)再好看也是自欺欺人。我習(xí)慣每加三個(gè)特征就跑一次回測(cè)對(duì)比最大回撤和收益曲線(xiàn)避免一次性堆太多導(dǎo)致無(wú)法歸因。換股票池是最容易被低估的一步。stock_list.txt里如果只有幾只股票結(jié)論沒(méi)有統(tǒng)計(jì)意義。常見(jiàn)做法是拉滬深 300 成分股對(duì)每只股票單獨(dú)訓(xùn)練模型再匯總信號(hào)或者把所有股票數(shù)據(jù)拼在一起訓(xùn)練一個(gè)通用模型。前者更穩(wěn)健但計(jì)算量大后者訓(xùn)練快但容易忽略個(gè)股差異。我一般先用 20 只股票做快速驗(yàn)證確認(rèn)流程沒(méi)問(wèn)題再擴(kuò)到全市場(chǎng)。驗(yàn)證方法上除了看累計(jì)收益和最大回撤建議加一個(gè)基準(zhǔn)對(duì)比——比如買(mǎi)入持有滬深 300 ETF 的收益曲線(xiàn)。如果你的策略跑不贏基準(zhǔn)那模型再?gòu)?fù)雜也沒(méi)有意義。這個(gè)對(duì)比圖我每次都會(huì)畫(huà)它比任何單一指標(biāo)都更能說(shuō)明問(wèn)題。從那以后我每次拿到新的量化 demo都強(qiáng)制先跑一遍默認(rèn)參數(shù)、記下基準(zhǔn)結(jié)果再動(dòng)任何一行代碼。這樣后面改出問(wèn)題時(shí)至少知道是改壞了還是本來(lái)就這樣。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取