news 2026/9/29 18:49:31

AI炒股系统实战:多Agent架构与LGBM双模型拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI炒股系统实战:多Agent架构与LGBM双模型拆解

简介:这是一套面向量化入门者与Python爱好者的轻量级AI炒股系统源码,覆盖选股、风控、择时、复盘四个解耦模块,并采用分类+回归双LGBM模型,既判断涨跌又预测涨幅。所有预测仅基于当日及之前数据,无未来函数,适合在普通笔记本上完成单票深度择时与周度复盘实验。资源包共11个文件,以4个Python脚本(主程序、训练、工具函数)为核心,附带两个训练好的pkl模型、两份csv交易/数据文件、两个pyc缓存及1个requirements.txt依赖说明,整体仅598KB,结构简洁便于二次开发。已有440人学习下载。通过该资源可快速搭建Streamlit交互界面,点按按钮即可跑通选股与复盘流程,是入门AI量化和扩展多Agent策略的良好起点。

1. AI炒股系统不是黑匣子:多Agent架构与LGBM双模型怎么落地

入门量化时我总以为炒股系统是个黑匣子,跑完回测只能看到一条漂亮的资金曲线。直到拆完这套源码才明白,AI在这里被拆成了四个能单独调试的Agent,配合LGBM分类与回归双模型,既管择时,也管周度复盘。它不需要庞大算力,一个普通笔记本就能跑通,核心解决的不是预测明天涨跌的玄学问题,而是把“什么时候该操作、本周交易哪里不对”变成可回溯、可复现的函数。适合有一年Python经验、想验证AI择时思路而不是沉迷指标参数优化的交易研究者。读之前先确认你认识LGBM的train与predict接口,剩下的跟着目录走。

2. 多Agent架构与LGBM双模型:为什么这么拆,而不是写一个单脚本

2.1 四个Agent的职责边界:比单一大模型更可控

我见过很多人做AI炒股系统时,喜欢把数据下载、特征计算、信号生成、报告输出全塞进一个run.py,跑完就完事。这套源码没有这么干,而是用多Agent架构把流程切成了四个独立模块:数据采集Agent负责拉取和清洗行情,特征Agent负责计算指标与形态识别,择时Agent负责调用LGBM分类模型输出信号,复盘Agent负责在周末汇总本周交易并调用回归模型做持仓诊断。每个Agent之间只通过DataFrame和JSON传数据,不传递自然语言,这也意味着任何一环出问题,你可以直接单独调试它,而不是翻一个三千行的脚本。

拿复盘Agent举个例子。它并不直接读取实时行情,而是读取数据采集Agent落盘的历史行情文件、择时Agent写出的信号记录,以及你自己补充的交易日志。三个输入一合并,才生成周度复盘报告。这种解耦方式带来的实际好处是:择时Agent换参数重跑,复盘Agent不需要跟着改;数据源换了,其他三个Agent完全无感。

提示:如果你准备把这套代码改造成自己的策略研究框架,优先保留Agent之间的数据接口协议,而不是保留具体实现。

2.2 LGBM分类+回归双模型:一个定方向,一个定幅度

为什么选LGBM双模型而不是用单个模型输出一个综合分数?这里的逻辑很直接:分类模型擅长回答“明天是涨还是跌”,但它的输出概率不能直接当收益率来用;回归模型擅长回答“未来五天大概能赚几个点”,但对方向反转的敏感度弱。两者各有盲区,组合起来才完整。

具体来说,分类模型用LGBMClassifier,预测标签是明日相对今日的收益是否大于零,输出概率作为择时信号的置信度。回归模型用LGBMRegressor,预测未来五日累计收益,输出数值用于仓位评估和周度复盘。训练特征完全共享同一份特征工程结果,但评估指标不同,分类看AUC,回归看MAE与RMSE。这套源码把两个模型分开训练、分开保存,预测时各自加载模型文件,互不干扰。

选LGBM而不是XGBoost或深度学习模型,理由有三个。第一,LGBM对表格型数据的拟合能力在中小样本上不会输给神经网络,训练速度快到可以频繁重训。第二,特征重要性输出很直观,你能看到MACD、RSI、成交量这些因子到底谁在起作用,而不是面对一个深度学习黑匣子。第三,它对缺失值和异常值有一定容忍度,行情数据里偶尔出现停牌导致的空值,不用专门做太多清洗就能喂进去。

2.3 源码目录与数据流:一张表看清每个文件干什么

刚打开压缩包时目录有点多,我把关键模块整理成了下面这张表,按数据流向排序,训练时照着这个顺序跑不会乱。

路径/文件名职责说明运行时依赖
config.yaml全局配置:数据区间、模型参数、阈值、标的列表无
data_fetcher.py数据采集Agent:下载日线行情并落盘为parquet行情接口
feature_engineer.py特征Agent:计算MACD、RSI、rolling统计与形态data_fetcher输出
trainer_classifier.py训练LGBM分类模型,输出模型文件feature_engineer输出
trainer_regressor.py训练LGBM回归模型,输出模型文件feature_engineer输出
signal_generator.py择时Agent:加载分类模型生成每日信号训练好的classifier
weekly_review.py复盘Agent:加载回归模型生成周度诊断报告训练好的regressor
backtest.py回测脚本:按信号与次日开盘价模拟交易signal_generator输出

整个数据流是一条单向链:行情原始数据先经data_fetcher,再进feature_engineer,之后分叉到两个训练脚本,训练产物再被signal_generator和weekly_review加载。没有循环依赖,这也是它能被拆开调试的前提。运行时建议按顺序执行,首次跑通后再考虑并行加速。

3. 择时分析实战:从MACD双底特征到LGBM分类信号

3.1 特征工程:把MACD双底/双顶变成模型能懂的数值

热搜里“python+源代码+macd双底+高+低”指向的正是这个环节。MACD双底是经典的看涨形态,但模型不认识K线图,你得把形态翻译成数值特征。这套源码的做法不是直接传MACD柱序列,而是提取出双底形态的关键要素:DIF与DEA是否在零轴下方形成二次金叉、两底之间的最低点差值、第二次金叉时柱状体高度与第一次的对比。我把这部分特征构造代码简化如下:

def extract_macd_double_bottom(df, short=12, long=26, signal=9): # 计算标准MACD三要素 ema_short = df['close'].ewm(span=short, adjust=False).mean() ema_long = df['close'].ewm(span=long, adjust=False).mean() dif = ema_short - ema_long dea = dif.ewm(span=signal, adjust=False).mean() macd_hist = (dif - dea) * 2 # 寻找MACD柱状体从负转正的拐点 cross_up = (macd_hist.shift(1) <= 0) & (macd_hist > 0) cross_down = (macd_hist.shift(1) > 0) & (macd_hist <= 0) features = pd.DataFrame(index=df.index) features['macd_dif'] = dif features['macd_dea'] = dea features['macd_hist'] = macd_hist # 近10日内是否出现零轴下方的金叉,作为双底候选 features['golden_cross_below_zero'] = ( (dif > dea) & (dea < 0) & cross_up ).rolling(10, min_periods=1).max() return features

这段代码有个关键处理:用shift(1)判断金叉拐点,确保当前K线的信号只依赖之前的数据,避免未来函数。参数short、long、signal是MACD标准值,12、26、9对应日线级别最常用的配置,如果你做的是周线级别,可以改成26、52、12,但样本量会明显减少,需谨慎。rolling(10)的作用是把“过去10天内是否出现过零轴下方金叉”变成一个持久特征,而不是只保留金叉当天为1,这样模型更容易捕捉到二次金叉的延续性。

除了MACD形态,特征表里还叠加了RSI、布林带位置、过去5日和20日收益率、成交量相对20日均量的比值。这些特征全部用shift(1)对齐,保证特征值在T日收盘后能计算,且预测的是T+1日行情。特征Agent每新增一个因子,都要在最后拼接到主特征表,统一对齐索引。

3.2 分类模型训练:正负样本、早停与类别平衡

分类模型的目标是预测T+1日收益率是否为正。训练前有一个容易被忽视的步骤:划定训练集与测试集时绝对不能随机切分,而是要按时间顺序切分。这套源码默认用前70%的交易日做训练,后30%做测试,并且保留了连续时间。标签构造采用future_return > 0作为正样本,跳过了收益恰好为0的极少情况。

import lightgbm as lgb from sklearn.metrics import roc_auc_score # 按时间顺序切分,不shuffle split_idx = int(len(X) * 0.7) X_train, X_test = X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test = y.iloc[:split_idx], y.iloc[split_idx:] model = lgb.LGBMClassifier( objective='binary', learning_rate=0.05, n_estimators=1000, num_leaves=31, class_weight='balanced', random_state=42 ) model.fit( X_train, y_train, eval_set=[(X_test, y_test)], eval_metric='auc', callbacks=[lgb.early_stopping(stopping_rounds=50, verbose=True)] ) print('Test AUC:', roc_auc_score(y_test, model.predict_proba(X_test)[:, 1]))

class_weight='balanced'在这里很关键,因为A股下跌和横盘的天数通常多于大涨,正样本比例可能只有四成,不平衡会逼着模型永远预测下跌。early_stopping监听测试集AUC,连续50轮不提升就停止,避免过拟合。num_leaves=31默认值可以先用,后面再调;别一上来就开64或128,否则你会在第五章节看到它怎么让你的回测翻车。

训练完成后模型会保存为lgbm_classifier.txt,后续signal_generator直接load。我一般会在训练脚本里加一行日志,输出特征重要性Top 10,方便和二次金叉特征的实际贡献对照。

3.3 信号生成与滞后对齐:提交预测结果给你“后悔药”

择时Agent读取最新的特征表,调用已训练好的分类模型,输出每个交易日的上涨概率。信号不能直接当买卖指令,因为概率值本身没有阈值概念。这套源码默认把prob > 0.55定义为买入信号,prob < 0.45定义为卖出信号,中间区域保持原有仓位不动。这个阈值可以在config.yaml里改,但它影响的是误报率与漏报率的平衡。

# signal_generator.py 核心片段 from datetime import timedelta import pandas as pd import lightgbm as lgb model = lgb.Booster(model_file='models/lgbm_classifier.txt') df = pd.read_parquet('features/feature_table.parquet') # 只保留模型训练时见过的特征列,避免列顺序错乱 feature_cols = [c for c in model.feature_name() if c in df.columns] df = df[feature_cols].dropna() # 预测的是次日信号,日期列右移一天 df['signal_date'] = df.index + timedelta(days=1) df['buy_prob'] = model.predict(df[feature_cols]) df['signal'] = 'hold' df.loc[df['buy_prob'] > 0.55, 'signal'] = 'buy' df.loc[df['buy_prob'] < 0.45, 'signal'] = 'sell' df[['signal_date', 'buy_prob', 'signal']].to_csv('signals/signal_log.csv', index=False)

注意signal_date的处理:模型用的是T日收盘后的特征,对应的是T+1日的交易决策,所以把特征索引加一天,这才是真正对账用的日期。很多自写回测在这里出错,回测成绩好看,实盘完全对不上,核心原因就是没有做这个滞后对齐。signal_log.csv会同时记录日期、概率和信号,这份文件也是随后复盘Agent的输入之一。

4. 周度复盘模块:回归模型、持仓诊断与Agent报告怎么串起来

4.1 回归模型训练:预测五日收益,给仓位一个参考数字

分类模型能告诉你方向,但给不了“大概能赚多少”的参考。周度复盘需要量化每一笔信号的收益质量,所以源码里设计了第二个LGBM模型,直接回归预测未来五日的累计收益率。标签构造与分类模型不同,分类用T+1单日收益的符号,回归用T+1到T+5的累计收益。这里要注意:回归任务对异常值非常敏感,遇到停牌复牌后的一字涨停,收益率会拉到远超正常分布的水平,训练前要做clip处理。

# trainer_regressor.py 核心片段 df = load_feature_table() # 构造五日累计收益标签,clip到[-0.2, 0.2]抑制极端值 df['future_5d_return'] = df['close'].pct_change(periods=5).shift(-5) df = df.dropna(subset=['future_5d_return']) df['future_5d_return'] = df['future_5d_return'].clip(-0.2, 0.2) split_idx = int(len(df) * 0.7) train = df.iloc[:split_idx] test = df.iloc[split_idx:] model = lgb.LGBMRegressor( objective='regression', learning_rate=0.05, n_estimators=1000, num_leaves=31, random_state=42 ) model.fit( train[feature_cols], train['future_5d_return'], eval_set=[(test[feature_cols], test['future_5d_return'])], eval_metric='mae', callbacks=[lgb.early_stopping(stopping_rounds=50, verbose=True)] )

pct_change(periods=5).shift(-5)的含义是:T日那行数据记录的是,从T+1到T+5这五天的总收益率。shift(-5)是把未来信息对齐到当前训练样本的特征行,这在训练阶段是允许的,因为你是在用特征预测未知的未来,但绝对不能在预测阶段对尚未发生的数据做同样操作。clip(-0.2, 0.2)等效于把极端涨停跌停截断,不然模型会被那几天的超常收益带走,损失函数全消耗在拟合极端值上。

4.2 复盘Agent:把日志、建议与违规项拼成周报告

每周五收盘后,复盘Agent要做三件事:读取本周信号记录、读取回归模型的预测收益、读取实际行情收益,然后对比生成报告。它输出的不是一句“本周表现良好”,而是一份包含表格和数字的诊断文档。源码里的报告格式是Markdown文件,方便直接贴到文档或通知工具里。

# weekly_review.py 报告主结构 report = [] week_records = signal_log[(signal_log.index >= monday) & (signal_log.index <= friday)] for _, row in week_records.iterrows(): actual_5d = price_after_5d(row['signal_date']) / row['close'] - 1 pred_5d = reg_model.predict(row[feature_cols])[0] report.append({ '日期': row['signal_date'], '方向': row['signal'], '预测五日收益': round(pred_5d, 4), '实际五日收益': round(actual_5d, 4), '偏差': round(pred_5d - actual_5d, 4), }) violations = detect_violations(signal_log, trade_log) report.append({'违规项': violations})

这份报告的字段设计表如下,你拿到源码后可以直接复用这个结构:

字段来源作用
日期signal_log对齐交易事实
方向signal_log判断做多/做空/观望
预测五日收益regression模型复盘预期
实际五日收益行情价格计算识别偏差
偏差预测-实际衡量模型是否系统性悲观/乐观

detect_violations是源码里比较有意思的函数:它会检查是否在信号为sell时仍然持有、是否在buy信号次日开盘价高于信号价3%时强行追高。这些规则是写死在配置里的,属于交易纪律的自动化检查。我建议你拿到源码后先把这里改成自己的交易规则,因为模型输出了信号,但执行层面的风险控制仍然依赖这些硬规则。

4.3 报告怎么用:从偏差里反推阈值设置

复盘报告不只是用来回看,它最实际的价值是暴露模型偏差。如果连续三周,买入信号的真实五日收益都显著低于预测值,说明分类模型的概率阈值给得太松,0.55买入了太多低质量信号,这时应该上调阈值到0.6试一轮。反过来,如果信号数量太少、空仓时间过长,说明阈值太紧。

另一个常用做法是把偏差按年份聚合。A股不同年份的市场风格差异极大,牛市里信号正确率高,震荡市里又把把止损。源码里提供了一个analyze_bias_by_quarter函数,按季度输出平均偏差和命中率,看哪个季度模型表现滑坡,再回头查那段区间的特征分布。这样做比总盯着总体准确率更接近实战,因为AI炒股系统的信号最终要落到不同市场环境下,宁可知道它会失效,也别让它静默失效。

5. 避坑排查:数据、未来函数与Agent通信的五个教训

5.1 未来函数:回测曲线漂亮,实盘瞬间翻车

现象:回测里年化收益喜人,跑实盘或模拟盘时,信号永远慢半拍,收益曲线大幅回落。

原因:特征构造或信号生成时,用到当日收盘后才知道的数据去预测当日结果,最常见就是shift用反方向,或者在生成T日特征时把T日收盘价也放进去了。

解决:全流程统一规则,所有特征在T日计算时只能使用T日收盘前可得的信息。代码里强制检查每个特征列,把凡是与close同期的列在预测前执行shift(1),再把T+1信号日期加一天。我在signal_generator里加了断言,如果特征表索引与信号日期重合,直接报错退出。

5.2 时间序列随机拆分:训练集偷看了未来

现象:模型测试AUC高达0.85,但换成滚动回测后AUC只有0.55,差距巨大。

原因:训练集与测试集使用了随机切分,同一个月内的样本被拆到了两边,模型其实记住了行情形态的时间上下文,而不是学到了真正的预测能力。

解决:一律按时间顺序切分,且测试集必须晚于训练集。这套源码里split_idx = int(len(X) * 0.7)是按行索引切的,不是train_test_split随机切。如果你想做更严格验证,改成按年份切,比如2019到2022训练,2023测试,这样更能反映真实的前瞻性。

5.3 LGBM过拟合:num_leaves开太大,噪声当信号

现象:训练集AUC接近0.99,测试集AUC只有0.52,特征重要性里排第一的居然是某个无意义的ID列。

原因:num_leaves=128加上min_data_in_leaf=5,模型深度足够把训练集的每一条噪声都背下来,量化特征里的微小波动全被当成规律。

解决:先把num_leaves降到31以下,min_data_in_leaf调到20以上。然后用feature_fraction=0.8和bagging_fraction=0.8做随机采样,逼迫模型找共性规律。我在调参时习惯先用默认参数跑一版,只有测试集AUC超过0.6才考虑放宽复杂度,否则优先怀疑特征是噪声而不是模型容量不够。

5.4 Agent间传数据:索引错位导致报告日期全偏

现象:复盘报告里的实际收益和信号日期对不上,明明周一发的买入信号,却显示周五才买入。

原因:三个Agent各自读取数据时,日期索引的时区或格式不一致。数据采集Agent存下来的日期是2024-01-15 00:00:00,复盘Agent用str.contains('2024-01-15')匹配,而信号Agent输出的是2024/01/15,字符串一拼接就错位。

解决:统一约定Agent间通信只传时间戳,用pd.to_datetime规范化后再比较。我在源码里保留了一个normalize_datetime工具函数,所有Agent入口都调它,确保日期在进入逻辑前已经统一格式。如果你的数据源跨了多个市场或时区,这一步省不了。

5.5 复权方式不一致:除权除息日之后信号全乱

现象:某只股票在6月除权后,模型连续三天给出强烈卖出信号,但实际股价并没有明显下跌。

原因:特征计算用了前复权数据,行情落盘时却用了未复权价格,除权导致的跳空被模型识别成暴跌信号。LGBM的回归模型对收益率的跳变极其敏感,一个除权就能把预测值拉偏好几个点。

解决:全流程统一使用前复权价格,且在数据采集Agent里就把复权处理做完,后续Agent不再碰原始价格。adjust='qfq'是行情接口里最常用的参数,如果你在data_fetcher里改了复权方式,记得把训练好的两个模型全部重跑一遍,旧模型对新的价格序列不生效。

6. 进阶:滚动训练与特征稳定性验证,把系统从“能用”推到“敢用”

6.1 每月滚动重训,让模型跟上市场风格变化

静态模型最怕市场风格切换,2024年有效的因子在2025年可能变成反向指标。常见做法是每个月末用过去12个月数据重训一次模型,并用最近一个月的样本验证。源码里预留了一个retrain_cron.py,参数是训练窗口长度和重训频率,我习惯设成window_days=365, freq='1ME'。重训不是从零开始,会延续上一轮的最优num_leaves和learning_rate,这样超参数稳定,特征重要性变化才可对比。

6.2 特征稳定性检查:只用两行代码确认因子没有失效

每轮重训后我会额外跑一次特征重要性排序,上周模型排名前五的特征,本周如果跌出前十,就要回去查原始数据是否出了问题。下面这段代码可以放在重训脚本尾部:

importance = pd.Series(model.feature_importances_, index=feature_cols) top_features = importance.sort_values(ascending=False).head(5) print('当前Top特征:', top_features.index.tolist())

只有那些在连续三轮重训里都保持高重要性的特征,才值得被写进最终策略白名单。这套源码最合我心意的地方也在这——它把AI炒股从“一键跑出代码”拉回到“每天确认输入、每周检查输出、每月验证特征”的工程节奏里。多Agent架构让每一步都可以单独重放,LGBM双模型让方向判断和幅度判断各有数据支撑。以前我做策略总是回测完就扔进文件夹吃灰,从那以后我每次落地一套新源码,都会强制走一遍先跑数据流、再核对信号日期、最后跑一个滚动重训的三步流程,确认每一步都对得上账,才敢把它放进模拟盘。希望这些拆解能帮你在自己的数据集上少走几趟弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 18:48:32

钢材表面缺陷检测实战:基于YOLOv5的数据集训练与部署

简介&#xff1a;这套基于YOLOv5的钢材表面缺陷检测资源&#xff0c;面向工业视觉质检人员与深度学习开发者&#xff0c;专注于解决钢材表面裂纹、凹坑、氧化皮、划痕等缺陷的自动识别与定位问题&#xff0c;适用于产线质检、材料科学实验等场景。压缩包共包含2000个文件&#…

作者头像 李华
网站建设 2026/9/29 18:48:29

从脚本散落到CLI-Anything:构建可扩展的命令行工具链

先说个场景&#xff1a;你电脑里是不是也躺着一堆脚本&#xff0c;build.sh、deploy.py、cleanup.js&#xff0c;名字各异&#xff0c;位置分散&#xff0c;时间一长自己都忘了哪个是干嘛的&#xff1f;我前几年就处在这种状态里&#xff0c;每次要发个版本&#xff0c;得先翻终…

作者头像 李华
网站建设 2026/9/29 18:47:32

项目输入四要素:从标题到摘要,打造清晰博文创作基础

要生成博文&#xff0c;需要你给我完整的项目输入&#xff0c;仅凭“项目标题: 内景 美术馆(Art Gallery)”这一个字段&#xff0c;我只能猜方向&#xff1a;是写美术馆空间摄影技巧、画廊展览策划复盘&#xff0c;还是室内设计案例分析&#xff0c;全都无从落笔。 所以请把下…

作者头像 李华
网站建设 2026/9/29 18:46:33

数据中心机房建设方案:八大子系统设计与UPS、空调负荷计算实操

简介&#xff1a;这份《数据中心机房建设方案》文档面向数据中心规划、机房工程设计与运维人员&#xff0c;以及需要了解IDC建设流程的IT从业者&#xff0c;系统梳理了从需求分析到技术选型的完整建设思路。内容围绕机房基础设施、IT基础设施、业务系统与数据管理三大板块展开&…

作者头像 李华
网站建设 2026/9/29 18:46:31

2026安阳景区古建牌坊检测排名 TOP5 CMA 资质机构提供牌坊裂缝检测、牌坊倾斜检测、老化检测 联系方式推荐

在安阳这座承载殷商文明与北魏石窟的千年古都&#xff0c;散落于景区、乡村与街巷间的古建牌坊&#xff0c;既是石木镌刻的历史坐标&#xff0c;也是文旅传承的视觉脊梁。然而放眼本地检测市场&#xff0c;机构虽鳞次栉比&#xff0c;服务质量却鱼龙混杂。不少无资质团队出具的…

作者头像 李华
网站建设 2026/9/29 18:45:18

多卡GPU训练扩展效率:为什么双卡跑不满两倍速度?

说实话&#xff0c;第一次把第二张 GPU 插进机器、满怀期待地跑起 LLM 训练&#xff0c;然后看到训练时间只缩短了三分之一的时候&#xff0c;我第一反应是怀疑自己买到了假卡。群里一问&#xff0c;才发现这不是我一个人踩过的坑&#xff0c;几乎每个从单卡切到多卡的人都会经…

作者头像 李华