恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python双均线回测与DeepSeek大模型信号过滤实战
首页
资讯中心
/
Python双均线回测与DeepSeek大模型信号过滤实战
Python双均线回测与DeepSeek大模型信号过滤实战
发布时间:2026/9/26 3:56:46
简介这是一份基于DeepSeek自然语言驱动与Python生态的股票趋势预测量化分析方案核心价值在于用AI生成数据分析代码的方式降低金融量化入门门槛。内容以宁德时代300750.SZ为实战案例覆盖从AKShare、yfinance获取历史行情到借助pandas-ta一键计算MACD、RSI、布林带等技术指标再到用matplotlib绘制包含K线、成交量和指标子图的复合图表最后通过backtesting库完成双均线策略回测评估夏普比率与回撤等关键绩效。整体形成一条可复制的从数据到决策的自动化分析链路适合具备一定Python基础、对量化交易感兴趣的投资者、金融从业者或数据科学初学者阅读实践也适合财经研究人员快速生成分析代码并提升效率。包体为1份docx文档约19KB内容组织紧凑含完整代码示例与逐步讲解并提醒注意数据质量、过拟合风险与AI模型局限。目前已有266人学习下载可作为股票技术分析与量化策略回测的入门参考。1. 双均线人人都懂为什么还要拉一个大模型进来做股票趋势预测最容易翻车的不是模型不够强而是你根本说不清“这个信号为什么有效”。传统量化里双均线金叉死叉、MACD背离、RSI超买超卖每一个指标单独拎出来都有道理但叠在一起经常互相打架。这个标题给了一条明确的路用Python把技术指标算扎实把双均线策略回测跑通再用DeepSeek这个有推理能力的模型去解释信号、过滤噪声、生成可执行的操作建议。适合两类人一类是刚学会Python、想正经做量化回测的开发者另一类是已经能跑通策略、但卡在“信号太多不知道信谁”的投资者。核心思路不是让AI替你做决策而是把AI当作一个会读图表的副驾让趋势预测从“看指标”升级成“看懂指标背后的逻辑”。下面按落地顺序从数据、指标、策略、接入一直讲到回测避坑。2. 从数据到技术指标先把地基打牢2.1 数据源怎么选免费接口够用但接口坑要提前知道做A股日线级别的趋势预测日K数据是最低要求最好再带成交量。常见做法是用akshare、tushare这类免费Python库拉数据它们封装了公开数据源不用自己去啃网页接口。选型标准就三条数据字段是否包含前复权收盘价、接口是否稳定、限频能不能接受。tushare的积分制对高频拉取有限制akshare胜在免费无门槛但字段命名偶尔会变。我一般本地建一个SQLite文件做缓存第一次全量拉之后每天增量更新避免反复打接口。import akshare as ak import sqlite3 import pandas aspd from datetime import datetime, timedelta def fetch_daily_data(symbol: str, days: int 500) - pd.DataFrame: 拉取日K数据并写入SQLite缓存。 symbol: sh600000 或 sz000001 这类带前缀代码 end datetime.now().strftime(%Y%m%d) start (datetime.now() - timedelta(daysdays)).strftime(%Y%m%d) df ak.stock_zh_a_hist(symbolsymbol, perioddaily, start_datestart, end_dateend, adjustqfq) # 统一列名方便后续计算 df.rename(columns{日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume}, inplaceTrue) df[date] pd.to_datetime(df[date]) df df[[date, open, close, high, low, volume]] conn sqlite3.connect(kline.db) df.to_sql(symbol, conn, if_existsreplace, indexFalse) conn.close() return df这段代码做了三件事用adjustqfq拿前复权价格避免分红除权造成的价格断层把中文字段映射成英文后续pandas处理不用来回切落SQLite后面回测反复读数据不会触发接口限频。参数days500对应A股约两年交易日跑双均线策略足够如果你要看更长的牛熊周期改成days1500但注意akshare对单次请求的数据量有限制太长的区间要分段拉。这里有个容易被忽略的细节前复权和后复权在均线策略里的差别。前复权以最新价格为基准往回调整适合看“当前视角下的历史走势”后复权保持历史价格不变适合算真实收益率。双均线策略关心的是金叉死叉的位置不是绝对价格所以前复权就够了但如果你要算累计收益建议用后复权数据再跑一遍对比避免除权日出现假的均线交叉。2.2 技术指标计算不要直接调库自己实现才能懂边界技术指标这一层市面上有TA-Lib、pandas-ta这些现成库但我不建议直接上。原因是这些库封装得太黑匣子MACD的EMA周期参数、RSI的平滑方式各库实现都有细微差异策略回测差0.5个百分点都可能是指标算法不同造成的。自己用pandas实现一遍每个指标的逻辑就彻底清晰了。这里给出双均线和RSI的极简实现import pandas as pd def add_technical_indicators(df: pd.DataFrame, fast: int 5, slow: int 20, rsi_period: int 14) - pd.DataFrame: 计算双均线和RSI返回带指标列的DataFrame。 均线用SMARSI用Wilder平滑。 # 双均线SMA_fast 和 SMA_slow df[ma_fast] df[close].rolling(windowfast).mean() df[ma_slow] df[close].rolling(windowslow).mean() # 金叉死叉信号ma_fast 上穿 ma_slow 为1下穿为-1 df[cross] 0 df.loc[df[ma_fast] df[ma_slow], cross] 1 df.loc[df[ma_fast] df[ma_slow], cross] -1 df[signal] df[cross].diff() # signal 为 2 表示金叉-1 - 1-2 表示死叉1 - -1 # RSI 用Wilder平滑先算涨跌幅再递推EMA delta df[close].diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.ewm(alpha1/rsi_period, adjustFalse).mean() avg_loss loss.ewm(alpha1/rsi_period, adjustFalse).mean() rs avg_gain / avg_loss df[rsi] 100 - (100 / (1 rs)) return df参数说明fast5和slow20是双均线最常用的组合5日线代表短期动能20日线近似月线代表中期趋势。signal列的2和-2是核心信号后面策略逻辑直接基于它触发买卖。RSI的Wilder平滑和普通EMA的区别在于alpha1/rsi_period而不是2/(period1)Wilder方式更平滑对突变价格的反应更迟钝判断超买超卖时更稳。自己做指标有一个额外收获你会发现rolling窗口在数据开头会有NaNdiff()会产生第一个缺失值这两处如果不在回测里处理就会在策略开头出现假信号。常见做法是直接丢弃前slow1行的信号宁可少算几天不要带着脏数据入场。2.3 信号对齐把所有指标统一到一张表里指标算完之后下一步是把信号整理成“每天该做什么”的指令表。很多新手在这里翻车是让每个指标各自输出一份信号然后到买入时再临时判断逻辑全乱。正确做法是提前把所有指标列合并成一张宽表每一行代表一个交易日列是价格、均线、RSI、信号标记后续策略和回测都只认这张表。def build_signal_table(df: pd.DataFrame) - pd.DataFrame: 整合指标到一张表输出带交易指令的DataFrame。 指令规则金叉且RSI70 - buy死叉或RSI80 - sell其他 - hold table df.dropna().copy() table[action] hold # 金叉条件上穿且未超买 table.loc[(table[signal] 2) (table[rsi] 70), action] buy # 死叉条件下穿或严重超买 table.loc[(table[signal] -2) | (table[rsi] 80), action] sell return table这条规则只用了金叉与RSI的组合就有两个明显的意图金叉时RSI已经超过70说明短期涨幅过快追进去容易接盘过滤掉死叉和超卖同理。参数70和80在震荡市可以放宽到75和85在趋势市建议收窄到65和75。信号表是后面所有逻辑的入口DeepSeek的介入也是以这张表的输出为上下文表结构设计得越干净后面接大模型就越省事。3. 双均线策略基线逻辑与参数选择的门道3.1 金叉死叉的边界为什么同样是均线有人赚有人亏双均线策略的原理一句话就能说清短期均线上穿长期均线买入下穿卖出。但同样一套逻辑有人回测年化30%有人亏到怀疑人生差别几乎都出在边界条件的处理上。第一是“交叉确认”的时机盘中实时判断容易反复横跳收盘后确认信号更稳第二是死叉卖出后是否立刻反向做空A股普通账户没有融券做空条件不具备时死叉应该清仓观望而不是反手空第三是均线用SMA还是EMASMA对价格反应迟钝信号少但假信号少EMA反应快但震荡市里容易被反复打脸。def generate_trades(signal_table: pd.DataFrame, initial_cash: float 100000, position_pct: float 0.95) - list: 根据信号表生成交易记录。 只在收盘价确认信号后次日开盘执行避免用当天收盘价成交的幻觉。 trades [] cash initial_cash shares 0 for i in range(1, len(signal_table)): today signal_table.iloc[i] prev_close signal_table.iloc[i-1][close] action today[action] # 次日开盘执行这里用下一根K线开盘价近似 exec_price today[open] if action buy and shares 0: buy_amount cash * position_pct shares int(buy_amount / exec_price / 100) * 100 # A股按手买入 cost shares * exec_price cash - cost trades.append({date: today[date], type: buy, price: exec_price, shares: shares, cash_after: cash}) elif action sell and shares 0: cash shares * exec_price trades.append({date: today[date], type: sell, price: exec_price, shares: shares, cash_after: cash}) shares 0 trades.append({date: signal_table.iloc[-1][date], type: final, price: signal_table.iloc[-1][close], shares: shares, cash_after: cash shares * signal_table.iloc[-1][close]}) return trades关键逻辑在成交时机的选择用today[open]价格成交。因为信号是昨天收盘后确认的真正能买最早是今天开盘这是回测里最容易造假也最容易被忽略的一环。很多新手直接用prev_close作为买入价等于提前知道了信号还按昨天的低价成交回测收益虚高。position_pct0.95留5%现金应对手续费和滑点不把子弹打光。3.2 参数组合怎么定网格搜索之外还要看参数平原双均线的核心参数是快线窗口fast和慢线窗口slow。(5,20)是经典短线组合(10,60)是中线(20,120)是长线。很多人一上来就做网格搜索遍历所有组合找收益最高的但这恰恰是过拟合的起点。我一般先把参数扫描的结果画成热力图看高收益区域是“孤峰”还是“高原”。孤峰意味着参数稍微偏一点收益就崩换样本外数据基本废掉高原说明这段参数区间对噪声不敏感接近“免费的午餐”。import itertools import numpy as np def grid_search(signal_table_builder, df, fast_range, slow_range): 简单参数扫描对每个fast/slow组合计算最终资产值 results {} base df.copy() for fast, slow in itertools.product(fast_range, slow_range): if fast slow: continue table signal_table_builder(base, fast, slow) trades generate_trades(table) final_value trades[-1][cash_after] results[(fast, slow)] final_value return results扫描结果一般会呈现一个规律fast在3到8之间、slow在15到30之间时结果差异不大这就是参数平原超出这个范围要么信号太密手续费吃光利润要么信号太疏错过主要行情。不要只盯着最高收益那组参数而是在平原里挑一组“语义最清晰”的——比如(5,20)因为它对应一周均线和一月均线解释成本低后续接DeepSeek时你更容易讲清楚策略为什么这么走。3.3 双均线策略和DeepSeek的协作边界双均线本身是规则引擎它最大的短板不是赚不到钱而是不会解释自己。金叉买入后股价连续下跌规则引擎只会继续持有它不知道是宏观环境变了还是单纯被套。DeepSeek的定位就在这里把规则引擎输出的信号切片最近N天的价格走势、金叉死叉位置、RSI超买超卖状态、成交量变化拼成一段文本让DeepSeek给出“这个信号在当前市场环境下可信度如何、是否需要推迟/加速执行”的判断。但这里有一条红线DeepSeek的建议不能直接触发交易它只能修正规则引擎的参数或标记可疑信号最终的执行必须回到双均线规则本身。后面第4章会讲怎么把这种协作落到API调用上。4. DeepSeek接入让大模型读图表、给结论4.1 最小可用API调用上下文怎么组装才不浪费token把DeepSeek接入量化流程最常见的误区是直接把K线数据全量喂给模型。日线数据500条每条含OHLCV五个字段哪怕压缩成文本也要几千token成本高不说模型还抓不住重点。正确的做法是只喂信号窗口内的摘要。以金叉信号为例取出信号前后各10个交易日的收盘价、5日均线、20日均线、RSI值再附上信号类型和触发日期组装成一段结构化文本让模型做归因分析。这样每次调用的token控制在500以内成本几乎可以忽略。import json from openai import OpenAI client OpenAI( api_keyyour-deepseek-api-key, base_urlhttps://api.deepseek.com/v1 ) def build_prompt(signal_table: pd.DataFrame, symbol: str, idx: int) - str: 截取信号点前后窗口生成模型的输入上下文 window signal_table.iloc[max(0, idx-10): idx10] lines [] for _, row in window.iterrows(): lines.append(f{row[date].strftime(%m-%d)} fclose{row[close]:.2f} ma5{row[ma_fast]:.2f} fma20{row[ma_slow]:.2f} rsi{row[rsi]:.1f}) return (你是一个A股技术分析助手。以下是{symbol}最近20个交易日的 收盘价、5日均线、20日均线和RSI值。\n 请判断1) 当前趋势方向 2) 最近一次均线金叉/死叉的可信度 3) 未来5个交易日走势倾向。\n 只输出JSON不要多余文字。\n).format(symbolsymbol) \n.join(lines) def analyze_signal(signal_table: pd.DataFrame, symbol: str, idx: int) - dict: prompt build_prompt(signal_table, symbol, idx) resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: prompt}], temperature0.3, response_format{type: json_object} ) return json.loads(resp.choices[0].message.content)这里的两个关键参数temperature0.3是反复试出来的平衡点低于0.2会输出千篇一律的套话高于0.5就开始编造价格走势response_format强制JSON输出后面接自动决策流程时不用解析自然语言减少一个翻车点。base_url指向DeepSeek官方兼容OpenAI的接口如果你用的是本地部署的蒸馏版本把地址换成http://localhost:11434/v1代码几乎不用改。4.2 输出结构化的两种方式强约束与校验兜底只要接过大模型做决策辅助就会遇上一个玄学问题模型偶尔会自作主张在JSON里多加字段、改字段名甚至干脆输出一段废话。GPT时代大家靠提示词约束但更靠谱的是双保险——提示词里给死结构代码里再做一层schema校验。上面代码已经用response_format做了第一层强约束第二层是在解析JSON后检查键是否齐全。如果模型输出不符合预期最稳妥的兜底是丢弃这次分析结果回到纯双均线规则不因为模型抽风而影响已落地的策略。4.3 成本与频率控制不能让AI比手续费还要贵DeepSeek的推理价格虽然不贵但如果你对每根K线的每个潜在信号都做分析一天几百次调用月成本照样轻松上千。控制频率的常用做法有两个一是只在signal列出现2或-2时才触发调用二是对同一信号做缓存——同样的日期、同样的均线参数、同样的RSI值结果一定一样没必要重复算第二遍。缓存可以简单地用{symbol}:{idx}:{fast}:{slow}做键存SQLite命中直接读取没命中才发起请求。# 建议把DeepSeek分析结果单独存一张表后续复盘可以直接查 sqlite3 kline.db CREATE TABLE IF NOT EXISTS ai_analysis ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT NOT NULL, signal_idx INTEGER NOT NULL, signal_type TEXT NOT NULL, result_json TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(symbol, signal_idx) );这张表有三个实际的用途回测时对比“纯双均线”和“双均线AI过滤”两套策略的差异复盘时查看当时模型的判断逻辑是否合理调参时用来评估不同temperature值对结果稳定性的影响。表结构里加UNIQUE(symbol, signal_idx)省去了代码里手动去重的逻辑。5. 回测实现与四大避坑点别让历史数据骗了你5.1 自研还是用现成引擎backtrader与轻量自研的取舍回测引擎的选择取决于你要做什么。反向因子验证、多标的组合、复杂撮合逻辑如部分成交、涨跌停无法成交直接用backtrader这类现成框架它把撮合、滑点、手续费、多标的管理都封装好了如果你只需要对单标的跑双均线策略、验证波段逻辑自研更可控。因为自研代码全在你自己手里出了怪异结果能顺着代码一行行排查backtrader的黑匣子排错成本反而更高。import pandas as pd import numpy as np def run_backtest(signal_table: pd.DataFrame, initial_cash: float 100000, fee_rate: float 0.0003, stamp_tax: float 0.0005, slippage: float 0.002): 简易回测支持手续费、印花税、滑点。 信号次日开盘成交买卖分开计费。 cash initial_cash shares 0 records [] for i in range(1, len(signal_table)): row signal_table.iloc[i] action row[action] exec_price row[open] * (1 slippage) if action buy \ else row[open] * (1 - slippage) if action buy and shares 0: shares int(cash * 0.95 / exec_price / 100) * 100 cost shares * exec_price fee cost * fee_rate if cost fee cash: shares int((cash - fee) / exec_price / 100) * 100 cost shares * exec_price fee cost * fee_rate cash - (cost fee) elif action sell and shares 0: revenue shares * exec_price fee revenue * (fee_rate stamp_tax) cash revenue - fee shares 0 records.append({date: row[date], action: action, price: exec_price, cash: cash, shares: shares}) final_value cash shares * signal_table.iloc[-1][close] return final_value, records这段自研回测比第3章的版本多了三个细节slippage0.002模拟买卖各0.2%的冲击成本A股小盘股这个值不算夸张印花税只在卖出时收取所以stamp_tax放在sell分支买入时如果计算出的费用超过可用现金自动缩减股数而不是报错。5.2 撮合细节的三个默认值不能乱调滑点、手续费、印花税是回测的三大默认值它们的合理区间直接影响结果真实性。A股佣金现在普遍万2.5到万3即0.00025到0.0003印花税卖出收0.0005滑点取决于你的下单方式——普通市价单0.002是保守估计做市价单频繁交易还要更高。这三个参数优先用小仓位实盘验证在模拟盘连续交易一周把真实成交均价和信号触发时的理论价格做对比得到的差值就是你自己的滑点模型。5.3 避坑回测里最容易骗人的4个地方坑1未来函数导致的收益虚高。现象是回测年化收益高得离谱但实盘怎么都跑不出来。原因是代码里用当天的收盘价信号又用当天的收盘价成交等于提前知道结果。解决信号确认统一用收盘后数据成交价统一用次日开盘价第3章的generate_trades已经这样实现检查你自己的代码有没有漏网之鱼。坑2涨跌停日成交不了的“幽灵交易”。现象是回测记录里出现了涨停价买入、跌停价卖出但A股实际情况是涨停买不进、跌停卖不掉。原因是最简回测没有判断当日是否一字板。解决在回测循环里加入if row[close] row[open] and abs(row[close] - prev_close) / prev_close 0.095这类判断识别疑似一字板跳过当日信号。坑3手续费只算买入不算卖出。现象是频繁交易策略回测看着盈利实盘连手续费都覆盖不了。原因是卖出分支漏了印花税。解决购买卖出双向计费卖出时叠加印花税代码已在sell分支中用fee_rate stamp_tax处理。坑4样本内过拟合。现象是在某一只股票上参数调得飞起换一只股票立刻失效。原因是用同一段数据既做参数寻优又做效果验证。解决时间序列要严格按前70%调参、后30%验证的方式切分验证段样本在调参时绝不能碰。DeepSeek接入后更要警惕模型看到过验证段数据分析结果本身就不算样本外了。6. 往前再走一步把AI过滤后的信号做成可对比的报表最后这一步是把整套系统从“能跑通”变成“能说服自己”。做法是跑两条回测线A线是纯双均线策略B线是叠加DeepSeek过滤后的策略模型判定“可信度低”的信号就跳过。对比两边的最终资产、最大回撤和交易次数你就能直观看到AI到底加了正分还是负分。def filter_by_ai(base_table: pd.DataFrame, ai_results: dict) - pd.DataFrame: 根据DeepSeek判断将低可信度信号置为hold filtered base_table.copy() for idx, row in filtered.iterrows(): if row[action] hold: continue ref_idx row.name # 依赖index对齐 result ai_results.get(ref_idx) if result and result.get(confidence, high) low: filtered.at[idx, action] hold return filtered对比报告里重点看三个数字年化收益率的差值判断AI过滤是否真的提升了盈利最大回撤的收窄幅度看AI是否在下跌行情里提前躲开了风险交易次数过滤后的次数明显变少说明AI至少帮你省了手续费。我自己跑过的组合里DeepSeek的信号过滤通常能减少30%到50%的无效交易回测收益不一定提升但回撤普遍更小持仓体验更接近“拿得住”。一个亲身的教训别指望AI能预测第二天的涨跌。哪怕DeepSeek在100次信号判断里对80次那20次错误也足够吃掉全部收益。正确用法是让它当“风险过滤器”而不是“上涨预言机”。接DeepSeek的第一个月我试过让它直接输出买卖评级结果回测曲线剧烈抖动改成只过滤低置信信号后策略稳定多了。这套系统做完后建议按这个顺序验证先只跑双均线再叠加AI过滤最后拉长周期看样本外表现——每一步都有明确的对比指标你才知道搭建的这套体系里哪一环真正创造了价值。希望这个从数据到指标、从策略到AI接入的完整流程帮到你也欢迎用更长时间的K线数据来检验这套框架的边界。本文还有配套的精品资源点击获取