ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python股票预测源码拆解:量化策略、LSTM与回测避坑指南

Python股票预测源码拆解:量化策略、LSTM与回测避坑指南 简介基于Python的股票预测系统设计源码是一套完整的量化分析学习资料面向金融数据分析、量化投资入门与进阶开发者聚焦通过统计建模和机器学习方法预测股票走势、辅助投资决策。压缩包共77个文件约89.28MB核心为43个Python源文件覆盖数据抓取、预处理、策略生成、回测与结果输出等模块21个CSV数据文件提供历史K线、多币种交易明细等原始数据7个txt文件用于记录策略参数与运行日志另有2个Excel工作簿和2张PNG图表辅助数据整理与可视化分析。项目已按策略场景拆分如加密货币双重策略、银行股及特定个股策略、随机策略训练等便于对照学习不同建模思路。目前已有367人学习下载适合希望通过完整源码理解股票预测系统设计、策略实现与数据处理全流程的读者。1. 这套 Python 股票预测源码先看清楚它到底能干什么如果你搜过「python 量化交易策略代码」或「股票预测系统源码」大概率见过一类开头装上机器学习库、读入 K 线、训练一个 LSTM然后画出一条拟合得完美的预测曲线。我拆这套源码时的第一感受是它和那种「demo 味」很重的项目不一样里面没有刻意造出来的炫技模块而是一套真正跑过数据、按日期切分好行情文件、带多套策略脚本的量化研究框架。核心原料是 OKX 的 ETH/USDT、BTC/USDT 历史行情时间粒度覆盖 5 分钟、15 分钟和 1 小时搭配 A 股单票策略做对照实验。它能解决的实际问题有三层一是给你一套可直接读取、已验证的行情数据管线省掉自己去交易所拉数、对齐时间戳的重复劳动二是给出十几套可运行的策略模板从双均线到网格到随机策略改参数就能复现回测三是带自动/手动两种训练导出流程把「切窗口、归一化、训练、出图」串成一条完整链路。适合的人群是已经会 Python 基础、想认真研究量化策略但不想从零搭数据管线的从业者也适合拿它当课程设计的本科生——它的文件组织方式就是一个现成的系统设计参考。2. 先摸清数据家底CSV 的命名规律、字段结构与排序逻辑2.1 从文件名反推数据获取节奏先把压缩包里和行情数据相关的文件拉出来看你会发现一个很明显的规律eth_usdt_5m_okxdata_0604_sorted.csv、eth_usdt_5m_okxdata_0520_sorted.csv、eth_usdt_5m_okxdata_0610_sorted.csv 这类文件命名结构是「交易对 时间粒度 数据源 日期 是否排序」。0604、0520、0524、0610、0606、0627 这些日期后缀说明数据是分天导出的而带 sorted 的是已经按时间戳排好序的版本没带 sorted 的是原始导出。这个命名习惯很实在。真实从交易所接口拉数据返回的 K 线不保证严格按时间递增偶尔会出现乱序或重复 bar。所以这个项目里专门有 CsvSort.py它的作用就是把原始 CSV 按时间戳排序、去重重新落盘成 sorted 版本。我拆项目时最先跑的就是这个脚本因为后面所有策略和训练脚本读的都是 sorted 文件数据不干净策略全白搭。import pandas as pd from glob import glob def sort_csv(raw_path, out_path, time_colts): df pd.read_csv(raw_path) # 有些导出文件里 ts 列是毫秒时间戳有些是秒这里统一转成 datetime if df[time_col].max() 10**12: df[dt] pd.to_datetime(df[time_col], unitms) else: df[dt] pd.to_datetime(df[time_col], units) # 按时间去重重复的 K 线保留最后一条 df df.drop_duplicates(subsetdt, keeplast) df df.sort_values(dt).reset_index(dropTrue) # 落盘时把 dt 转回毫秒时间戳保持和原文件列一致 df[ts] df[dt].astype(int64) // 10**6 df.drop(columns[dt]).to_csv(out_path, indexFalse) print(f{raw_path} - {out_path}, bars: {len(df)}) # 实际使用时把路径换成你解压后的目录 for f in glob(eth_usdt_5m_okxdata_*.csv): if sorted not in f: sort_csv(f, f.replace(.csv, _sorted.csv))逻辑说明先判断时间戳单位最大时间戳超过 10 的 12 次方基本可以认定是毫秒因为当前真实世界时间戳的秒级是 10 的 9 次方量级然后按时间列去重保留最后一条再排序。排序对后续所有策略都很关键因为 5 分钟 K 线策略本质上是逐 bar 扫描数据一旦乱序计算出的均线、RSI 全是错的而且很难排查。参数说明time_col 默认是 ts如果你的 CSV 里时间列叫 timestamp 或 open_time需要改这个参数unit 的判断阈值 10^12 是我习惯用的经验值如果你的数据是 2023 年之前的秒级时间戳最大也就 10^9毫秒级是 10^12这个阈值足够区分。2.2 K 线字段和质量检查别急着跑策略先读一个 sorted 文件的头部确认字段名。常见 OKX 导出的 5m K 线字段是 ts、open、high、low、close、volume有的文件还会带 turnover。这个项目的所有 CSV 基本都遵循这套列名但 eth_usdt_hourly_prices.csv、btc_usdt_hourly_prices.csv 这种小时线文件的列可能被精简过只有 ts 和 close。我建议在跑任何策略之前先做一轮质量检查。crypto 数据最常见的三个问题某个 bar 的 high 小于 low、成交量出现明显尖峰可能是交易所插针或数据源合并错误、以及相邻 bar 的时间间隔不是严格的 5 分钟中间缺 bar。import pandas as pd df pd.read_csv(eth_usdt_5m_okxdata_0604_sorted.csv) print(df.head()) print(ftotal bars: {len(df)}) # 检查 high/low 是否倒挂 bad_bar df[df[high] df[low]] print(fhigh low bars: {len(bad_bar)}) # 检查时间间隔5m 数据相邻 ts 差应该是 300000 毫秒 df[ts_diff] df[ts].diff() print(df[ts_diff].value_counts().head(10)) # 成交量尖峰检查超过 20 倍中位数的 bar 大概率是异常 med_vol df[volume].median() spike df[df[volume] med_vol * 20] print(fvolume spike bars: {len(spike)})逻辑说明ts_diff 的 value_counts 能直接告诉你数据里有多少个bar的时间间隔是异常的。正常 5 分钟 K 线的 ts 差是 300000 毫秒如果出现大量 600000 或 150000说明数据源有断点策略里算连续 N 根 bar 的指标时这些断点附近的值没有参考意义。参数说明volume 中位数的 20 倍阈值是我拍的一个经验值。crypto 市场出现插针时成交量往往会放大几十倍用均值会被少数极端值拉偏用中位数更稳。如果你要更严格的检查可以把阈值降到 10 倍但要注意 5m 数据在重大行情启动时成交量放大 20 倍也是正常现象这里只能抓「离谱」的抓不了「可疑」的。2.3 CSV 与 Excel 的转换桥convertExcel.py 和 xlsx 的关系压缩包里还有 002235.xlsx 和 301130.xlsx 两个 Excel 文件配合 convertExcel.py 一起看就明白了这不是策略要用 Excel而是把 Excel 里人工整理的股票清单或日线数据转成 CSV 供策略读取。002235 是闽发铝业301130 是骏鼎达从文件名看应该是 A 股个股的数据或自选股清单。这种做法在实盘研究里很常见你从行情软件导出一批股票到 Excel手工筛选或标记后再用脚本转成统一格式的 CSV进入策略选股流程。convertExcel.py 做的事就是把 xlsx 读进来按指定 sheet 和列导出成 txt 或 csv。import pandas as pd def excel_to_csv(xlsx_path, sheet_name, out_path, cols): df pd.read_excel(xlsx_path, sheet_namesheet_name) # cols 可以是列名列表比如 [code, name, close] df_selected df[cols] df_selected.to_csv(out_path, indexFalse, encodingutf-8-sig) print(fexported {len(df_selected)} rows to {out_path}) excel_to_csv(002235.xlsx, Sheet1, 002235.csv, [date, open, high, low, close, volume])逻辑说明注意 to_csv 的 encoding 我用了 utf-8-sig不是默认的 utf-8。Windows 下 Excel 打开 utf-8 无 BOM 的 CSV 会中文乱码加 sig 就是让 Excel 正确识别。这是 A 股数据处理最容易翻车的小问题。参数说明sheet_name 默认为 Sheet1实际文件里可能是别的名字建议先打印 sheet 名再传入cols 列表按需裁剪如果后续策略只需要 close就别把 entire 列全导出来减少内存占用。3. 策略脚本族双均线、双策略与网格的写法差异3.1 ethStrategy5m.py 的单策略骨架逐 bar 扫描与信号生成这个项目里策略脚本非常多ethStrategy5m.py、ethStrategy5mduo.py、ethStrategy15m.py、btcStrategy.py、pullbackStockStrategy.py、wanggeGenerator.py还有 BankOfChinaStrategy.py、ZiguangStrategy.py、ShenhuaOfChinaStrategy.py 这种按单票命名的策略。它们的共同点是从 CSV 读 K 线按固定逻辑生成买卖信号最后把信号落盘或画图。先看最基础的 ethStrategy5m.py。它的思路是经典的双均线快线上穿慢线做多下穿做空。但 crypto 5m 数据和 A 股日线有个显著差异5m 的噪声大单纯双均线会被来回打脸。所以这个策略里一般会加一个过滤条件比如只在大周期趋势方向一致时开仓。import pandas as pd def ema(series, period): return series.ewm(spanperiod, adjustFalse).mean() def generate_signal(df, fast12, slow26, filter_ema200): df df.copy() df[ema_fast] ema(df[close], fast) df[ema_slow] ema(df[close], slow) df[ema_filter] ema(df[close], filter_ema) # 信号: 1 做多, -1 做空, 0 持仓不动 df[signal] 0 long_cond (df[ema_fast] df[ema_slow]) (df[close] df[ema_filter]) short_cond (df[ema_fast] df[ema_slow]) (df[close] df[ema_filter]) # 用 diff 捕捉交叉点只在交叉瞬间发信号 df.loc[long_cond (long_cond.shift(1) False), signal] 1 df.loc[short_cond (short_cond.shift(1) False), signal] -1 return df df pd.read_csv(eth_usdt_5m_okxdata_0604_sorted.csv) df_with_signal generate_signal(df) print(df_with_signal[df_with_signal[signal] ! 0].head(20))逻辑说明这里用 shift(1) 判断条件是否刚从 False 变 True这样只在金叉/死叉的当前 bar 发出信号而不是在整个趋势持续期间天天出信号——如果你直接在条件成立时全量标记为 1回测里同一个趋势会重复开仓资金曲线会失真。ema_filter 是 200 周期的趋势过滤价格在 200 均线上方只做多下方只做空用来过滤震荡行情里的假交叉。参数说明fast、slow、filter_ema 这三个参数是整个策略的命门。5m 数据上 12/26/200 是偏保守的组合信号少但质量略高如果你用 5/13/55信号数量会多很多但回测胜率会明显下降而且手续费占比会吃掉利润。这个策略翻车最狠的地方是趋势过滤方向不对——如果你在熊市里用 12/26/200 做多200 均线压着价格信号基本不会触发不是策略坏了是市场没给你机会。3.2 ethStrategy5mduo.py 的双策略组合信号合流与仓位分配ethStrategy5mduo.py 和 ethStrategy5mduoF.py、ethStrategy5mduoC.py 是同一族的变体从命名看 duo 是 double 的意思本质是把两套策略的信号合流。常见的做法是策略 A 是均线趋势策略 B 是 RSI 或布林带均值回归两者同时给同向信号时才开仓。这种设计思路在实盘里叫「信号共振」目的是降低单策略在特定行情下的失效概率。趋势策略和均值回归策略天然互补趋势策略在单边行情里赚钱在震荡里反复止损均值回归策略在震荡里高抛低吸在单边里却容易逆势扛单。合流后两套策略都认可的方向才是有效方向可以过滤掉大量低质量信号。import pandas as pd import numpy as np def rsi(series, period14): delta series.diff() gain delta.clip(lower0).ewm(alpha1/period, adjustFalse).mean() loss (-delta.clip(upper0)).ewm(alpha1/period, adjustFalse).mean() return 100 - 100 / (1 gain / loss) def dual_strategy(df, fast12, slow26, rsi_buy30, rsi_sell70): df df.copy() df[ema_fast] df[close].ewm(spanfast, adjustFalse).mean() df[ema_slow] df[close].ewm(spanslow, adjustFalse).mean() df[rsi] rsi(df[close]) # 趋势信号 df[trend] np.where(df[ema_fast] df[ema_slow], 1, -1) # 均值回归信号: RSI 超卖时偏向做多, 超买时偏向做空 df[revert] 0 df.loc[df[rsi] rsi_buy, revert] 1 df.loc[df[rsi] rsi_sell, revert] -1 # 合流: 两者同向才出手 df[signal] 0 df.loc[(df[trend] 1) (df[revert] 1), signal] 1 df.loc[(df[trend] -1) (df[revert] -1), signal] -1 return df逻辑说明rsi 的计算用了 ewm 而不是简单滑动平均这是 Wilder 原始 RSI 的算法比 SMA 版本对价格突变更平滑信号少但假信号也少。趋势信号是一个持续状态1 或 -1但合流后的 signal 可以在 RSI 回到中性区时自动变为 0代表退出持仓。参数说明rsi_buy30、rsi_sell70 是经典阈值。这里有个坑5m 数据的 RSI 波动比日线剧烈得多30/70 的阈值一天可能触发十几次手续费直接拖垮收益。我见过有人把阈值改成 20/80信号频率立刻下降一个量级回测结果反而更好。duo 策略的另一个参数是两套策略的权重——有的版本不是简单合流而是各给 0.5 仓位这时候你需要在代码里加一个 position_sizing 逻辑duoC 和 duoF 的差异大概率就在这里一个管仓位计算一个管止损过滤。3.3 wanggeGenerator.py 的网格策略区间、格数与仓位计算wanggeGenerator.py 是网格策略生成器。网格策略在 crypto 市场特别流行因为它不需要预测方向只需要价格在区间内波动就能反复收割。这个脚本的输入一般是价格区间上下限、网格格数、每格下单量输出是每个网格档位的挂单价格和数量。def generate_grid(price_low, price_high, grids, base_amount): # 等差网格: 每格价差固定 step (price_high - price_low) / grids grid_prices [price_low i * step for i in range(grids 1)] # 每格下单量: 按 base_amount 等额分配 amount_per_grid base_amount / grids orders [] for i, p in enumerate(grid_prices): orders.append({ grid_index: i, price: round(p, 2), amount: round(amount_per_grid, 4) }) return orders orders generate_grid(price_low2800, price_high3200, grids10, base_amount1.0) for o in orders[:5]: print(o)逻辑说明等差数列网格是最简单的版本每格价差相同。还有等比网格也就是每格价差的百分比相同适合价格波动大的标的——低价位格距小、高价位格距大但代码逻辑要复杂些。网格策略的关键参数是区间上下限如果价格跌破下限或者涨破上限网格就失效了。所以好的网格脚本必须带「破网」处理逻辑。参数说明price_low 和 price_high 的设定是网格策略里最玄学的地方通常参考过去 30 天的价格分位数。grids 越大每格间距越小成交越频繁但单格利润越薄手续费占比越高base_amount 是总投入内部按网格数等分。我建议 grids 不要超过 20因为 5m 数据的买卖价差加手续费格距太小的话每次网格利润只剩一点点资金效率很低。4. 训练与导出链路从 K 线到 LSTM 模型的完整流程4.1 窗口切分与归一化autoExportBatchTrain.py 的数据准备逻辑autoExportBatchTrain.py 和 handExportTrain.py 这两个脚本是本项目的「训练中枢」。autoExportBatchTrain 是批量导出训练数据handExportTrain 是手动指定数据集导出。它们做的事情本质一样把 K 线序列切成 (样本数, 时间步长, 特征数) 的三维张量供 LSTM 或全连接网络训练。数据切分是时序预测最容易犯错的环节。需要滑动窗口比如用过去 60 根 5 分钟 K 线预测下一根 K 线的收盘价方向。窗口重叠是允许的——训练样本之间可以重叠但验证集和测试集必须按时间顺序切不能用随机划分否则未来数据会泄进训练集测试结果会虚高。import numpy as np from sklearn.preprocessing import MinMaxScaler def create_sequences(data, seq_len60, pred_len1): xs, ys [], [] for i in range(len(data) - seq_len - pred_len 1): xs.append(data[i:iseq_len]) ys.append(data[iseq_len:iseq_lenpred_len]) return np.array(xs), np.array(ys) # 假设 df 是 sorted CSV, 取 close 和 volume 两列 df pd.read_csv(eth_usdt_5m_okxdata_0604_sorted.csv) features df[[close, volume]].values scaler MinMaxScaler(feature_range(0, 1)) features_scaled scaler.fit_transform(features) X, y create_sequences(features_scaled, seq_len60, pred_len1) print(fX shape: {X.shape}, y shape: {y.shape}) # 按时间顺序切分: 前 80% 训练, 后 20% 验证 split int(len(X) * 0.8) X_train, X_val X[:split], X[split:] y_train, y_val y[:split], y[split:]逻辑说明create_sequences 用双循环生成滑动窗口样本seq_len60 表示每根样本包含过去 60 根 K 线。这种方式会产生大量重叠样本但对 LSTM 来说这是正常输入形式。切分点用 int(len(X) * 0.8) 是按比例切实际使用时我建议直接用固定条数切比如「最后 5000 根做验证」避免因为数据集长度变化导致验证集数量不稳定。参数说明seq_len 的选择取决于策略周期。5m 数据上 60 根等于 5 个小时能覆盖一个完整的日内波段如果做小时线seq_len 可能取 24 或 48。MinMaxScaler 的 fit 只能用在训练集上验证集要调用 transform不能用 fit_transform——否则验证集的极值会影响归一化参数等于变相泄露信息。4.2 模型训练骨架与导出trainTodayCreate.py 的当日训练逻辑trainTodayCreate.py 从名字看是「训练今日模型」它的作用是每天收盘后用当天及之前的数据重新训练一次模型更新权重。这种「每日重训」在量化里叫滚动训练目的是让模型适应最近的市场状态。但注意crypto 市场 24 小时交易没有 A 股那种「收盘」概念所以训练触发时机一般是每天固定 UTC 时间点。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential([ LSTM(64, return_sequencesTrue, input_shape(X_train.shape[1], X_train.shape[2])), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(16, activationrelu), Dense(y_train.shape[1], activationlinear) ]) model.compile(optimizeradam, lossmse, metrics[mae]) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs30, batch_size64, verbose1 ) model.save(eth_model_0604.h5)逻辑说明这里用双层 LSTM 加 Dropout。return_sequencesTrue 让第一层 LSTM 输出完整序列给第二层第二层 return_sequencesFalse 只输出最后一个时间步的特征再接全连接层输出预测值。损失函数用 MSE 是因为预测目标是连续价格如果你改成分类预测涨/跌损失函数要换成 binary_crossentropy最后一层激活函数改成 sigmoid。参数说明epochs30、batch_size64 是保守配置。5m 数据量通常很大一天有 288 根 K 线如果累积多天数据样本量轻松破万30 个 epoch 在 GPU 上几分钟跑完。如果 loss 不降先调学习率——用 Adam 默认学习率 0.001如果训练 loss 震荡明显可以降到 0.0001。Dropout 0.2 是常规值太高会欠拟合太低会过拟合。4.3 预测结果可视化output.png 和 1.png 是怎么生成的压缩包里有两张 PNG 图output.png 是模型预测结果的可视化1.png 是某张 K 线图或回测曲线。生成这类图一般用 matplotlib 画三根线真实 K 线收盘价、模型预测值、买卖信号标记点。import matplotlib.pyplot as plt # 假设 y_pred 是模型对验证集的预测, y_val_real 是真实值 plt.figure(figsize(12, 6)) plt.plot(y_val_real, labelreal close, alpha0.7) plt.plot(y_pred, labelpred close, alpha0.7) plt.axvline(xlen(y_train), colorgray, linestyle--, labeltrain/val split) plt.legend() plt.title(ETH/USDT 5m LSTM Prediction) plt.savefig(output.png, dpi150, bbox_inchestight)逻辑说明注意这里画的是验证集上的预测对比。预测曲线和真实曲线贴得越近不代表策略越赚钱——你要看的是方向准确率也就是预测值和真实值同涨同跌的比例。如果模型永远滞后一根 K 线预测值等于上一根真实值图上曲线贴得很近但实际没有任何预测能力。这个「滞后陷阱」是 LSTM 预测 K 线最常见的翻车点判断方法是把预测序列整体平移一根再看相关性是否明显下降。参数说明dpi150 是出图清晰度的基本门槛300 的话文件体积会偏大但作为报告插图更合适。bbox_inchestight 保证图例不被裁掉。实际项目里 output.png 还可能是训练 loss 曲线图如果看到 loss 曲线是锯齿状上下乱跳说明学习率太高或 batch_size 太小。5. 避坑与排查跑这套源码最常见的五个翻车点5.1 KeyError: ts 或 close——列名不统一现象脚本一跑就报 KeyError提示找不到 ts 或 close 列。原因eth_usdt_hourly_prices.csv 这类精简文件可能只有 price 或 timestamp 列名和 eth_usdt_5m_okxdata_sorted.csv 的标准字段不一样。你直接套用标准脚本读它列名对不上就报错。解决跑任何脚本前先 df.columns 打印确认。如果是脚本里写死了列名直接改脚本或者在被读的 CSV 里重命名列。我一般会在读取后加一行 df.rename(columns{timestamp: ts, price: close}, inplaceTrue)把不同来源的列名统一掉。5.2 时间戳单位混用导致 K 线错位现象画出来的 K 线图时间轴混乱明明应该间隔 5 分钟的 bar显示出来却是隔几小时一跳。原因部分 CSV 的 ts 是毫秒部分是秒。策略脚本里算时间间隔或对齐不同数据源时毫秒和秒混在一起差了一千倍K 线对齐全乱。解决在数据读取阶段做统一判断用 2.1 里那个 max 10**12 的阈值做自动识别统一转成毫秒。不要信任任何一个 CSV 的默认单位AI 生成的脚本尤其容易在这上面翻车。5.3 策略信号频繁触发回测收益被手续费吃光现象某套参数在回测里信号非常多胜率看着还行但算上手续费后收益大幅缩水甚至亏损。原因5m 和 15m 这种短周期数据单笔利润本来就薄。OKX 的 taker 手续费大约是 0.05% 到 0.1%一次完整的开平仓要扣两次手续费。如果策略平均持仓只有几根 K 线每次赚 0.2% 就要交 0.1% 手续费收益一半都没了。解决在回测里强制加手续费和滑点模拟参考第 6 章的代码。然后观察信号频率——如果 1000 根 K 线产生超过 30 次交易大概率是参数过拟合市场噪声。把均线周期调大比如 fast 从 12 调到 20或者把 RSI 阈值从 30/70 调到 20/80信号量立刻降下来。5.4 LSTM 预测曲线滞后一根 K 线现象训练好的模型在验证集上画图预测曲线和真实曲线几乎重合但整体右移了一根 K 线方向准确率其实只有 50%。原因用 t 时刻的数据预测 t1模型学到的最优策略是记住上一根 K 线的值当作预测值因为价格有惯性上一根的值和下一根的值很接近MSE 损失最小。这不是模型坏了是任务定义本身就容易诱导模型「抄作业」。解决第一预测目标从「下一根收盘价」改成「未来 N 根的累计收益率」第二在评估时用方向准确率别用 MSE第三把预测序列移位对比确认没有滞后。这个坑我栽过不止一次现在每次训练完第一件事就是看滞后。5.5 批量训练导出时内存不足或训练中断现象跑 autoExportBatchTrain.py数据读多了直接 OOM或者训练到一半进程被杀。原因create_sequences 用双循环生成样本如果 CSV 有几万根 K 线seq_len60生成的是几万个 60x2 的浮点数组内存占用轻松上 GB。解决用 numpy 的 stride_tricks 或 tf.data.Dataset 的 window 方法做流式切窗不要一次性把全部序列载入内存。另外先只读 close 单列跑通流程再加 volume 或其他特征逐步验证内存占用。6. 回测验证的进阶做法手续费、滑点和样本外测试如果只是把策略信号跑出来然后看图那不算回测只能算「给数据画了点标记」。真正的回测至少要回答三个问题手续费和滑点吃掉多少收益、策略在没见过的数据上是否还成立、参数是不是过拟合了某个特定时间段。我建议在项目现有脚本基础上加一个独立的回测评估层把信号转成仓位再算出带成本的资金曲线。def backtest_with_cost(df, signal_colsignal, initial_capital10000, fee_rate0.0006, slippage0.0002): df df.copy() df[position] df[signal_col].shift(1) # 用上一根 bar 的信号, 防止未来函数 df[ret] df[close].pct_change() # 交易发生点: 仓位变化的位置 df[trade] df[position].diff().abs() # 成本: 手续费 滑点, 只在交易时扣除 df[cost] df[trade] * (fee_rate slippage) df[strategy_ret] df[position] * df[ret] - df[cost] df[equity] initial_capital * (1 df[strategy_ret]).cumprod() return df df pd.read_csv(eth_usdt_5m_okxdata_0604_sorted.csv) df generate_signal(df) result backtest_with_cost(df) print(ffinal equity: {result[equity].iloc[-1]:.2f})逻辑说明position 用 shift(1) 是因为信号是在当前 bar 收盘后算出来的最早只能在下根 bar 开盘成交如果不 shift 就等于用了未来数据回测收益会被严重夸大这是回测里最常见的「未来函数」问题。cost 的计算方式是仓位变化时才收一次成本比每次信号都收更接近真实——因为仓位没变就没交易。slippage 是保守估计crypto 5m 级别的滑点一般在 0.01% 到 0.05% 之间。从第一次做完这个回测框架之后我养成了两个习惯第一任何策略必须带手续费和滑点跑完再说话第二同一套参数必须拿去跑下一周的数据验证样本外效果而不是反复调参数直到历史回测好看为止。很多时候历史回测漂亮不是策略厉害是参数过拟合了。把这两步养成肌肉记忆能少走很多弯路。希望这套源码的拆解过程也能帮到你——数据、策略、训练、回测四层都有人走过一遍你踩的坑不会比别人更新鲜重点是尽快发现它。本文还有配套的精品资源点击获取
返回列表