简介:这份Python量化代码文档面向金融数据分析初学者与量化策略开发者,以可编辑Word形式呈现,便于非编程人员理解与修改代码。内容围绕NumPy、Pandas、Matplotlib三大核心库展开,涵盖数据读取、清洗、排序、索引设置及可视化,并给出获取股票与指数数据、计算年化收益率、最大回撤、平均涨幅等完整函数示例,同时涉及跨平台路径兼容与future模块的版本适配问题。资源包仅含1个docx文件,大小约13KB,轻量易携带,适合作为量化入门参考或团队协作模板。目前已有65人学习下载。读者可从中获得可直接运行的策略开发骨架、指标计算逻辑与数据处理思路,快速理解量化回测的基本流程与代码组织方式。
1. 从一份“可编辑的 Python 量化代码 Word 文档”说起
你拿到手的可能是一份标题写着“Python量化代码(可编辑修改word版).docx”的文件,打开一看,里面是一堆策略逻辑、指标公式和回测参数,但格式是 Word,不是 .py。很多人第一反应是“这怎么跑”,第二反应是“要不要一行行复制到编辑器里”。我见过太多人卡在这一步:文档里的代码缩进被 Word 自动排版吃掉,中文引号变成弯引号,if和for的层级全靠肉眼对齐,复制到 IDE 直接报IndentationError。这份文档真正的价值不是“Word 版”,而是它把一套量化策略的骨架用人类可读的方式写清楚了——均线怎么算、仓位怎么加、止损怎么设。你要做的是把它还原成能跑、能改、能回测的 Python 工程,而不是把它当成一份只能看的说明书。适合谁?适合手里有策略逻辑但还没形成代码习惯的从业者,也适合想从零搭一个可复现回测框架的新手。下面我按“拆文档 → 还原代码 → 接数据 → 回测 → 避坑 → 进阶”的顺序,把这条路走一遍。
2. 把 Word 里的量化逻辑拆成可执行的 Python 模块
2.1 先分清文档里哪些是“策略描述”,哪些是“代码片段”
一份典型的量化 Word 文档通常混着三类内容:自然语言写的交易规则(比如“收盘价上穿 20 日均线买入”)、伪代码或半成品代码块、以及参数表格。我的习惯是先做一次“三色标记”:用三种底色分别标出规则句、代码块、参数表。规则句要转成布尔条件,代码块要检查缩进和符号,参数表要抽成配置字典。这一步不做,后面一定乱。比如文档里写“当短期均线上穿长期均线且成交量大于前一日 1.2 倍时开仓”,你要拆成两个条件:ma_short > ma_long且volume > volume.shift(1) * 1.2。注意shift(1)在 pandas 里是前一行,不是“前一天”,如果数据是分钟级,含义就变了。这一步的产出是一张“规则-变量-数据源”对照表,后面写代码直接照着填。
2.2 用 Python 脚本把 docx 文本抽出来,避免手动复制
手动复制最大的坑是 Word 的智能引号和软回车。我一般用python-docx把段落和表格一次性读出来,存成纯文本再处理。下面这段代码是我常用的抽取脚本,关键在paragraph.text和table分开处理,并且把弯引号统一替换掉。
from docx import Document import re def extract_docx_text(path): doc = Document(path) lines = [] # 抽取正文段落 for para in doc.paragraphs: text = para.text.strip() if text: lines.append(text) # 抽取表格内容,按行拼接 for table in doc.tables: for row in table.rows: cells = [cell.text.strip() for cell in row.cells] lines.append(" | ".join(cells)) # 统一替换中文弯引号和全角符号 cleaned = [] for line in lines: line = line.replace("“", '"').replace("”", '"') line = line.replace("‘", "'").replace("’", "'") line = line.replace(":", ":").replace(",", ",") cleaned.append(line) return cleaned if __name__ == "__main__": content = extract_docx_text("策略文档.docx") with open("extracted.txt", "w", encoding="utf-8") as f: f.write("\n".join(content))逻辑说明:Document(path)加载 docx,paragraphs拿正文,tables拿表格。表格里每个单元格用|拼接,方便后面用正则切分。替换弯引号是因为 Python 只认直引号,全角冒号和逗号在代码里也会报语法错误。参数说明:path换成你的文件名;如果文档里有嵌套表格,table.rows只取最外层,嵌套的需要递归,但量化文档一般不会那么复杂。跑完你会得到一个extracted.txt,里面代码和规则混在一起,下一步就是人工分拣。
2.3 把策略规则翻译成 pandas 可计算的条件表达式
分拣之后,每一条规则都要落成 pandas 的布尔序列。比如“20 日均线上穿 60 日均线”写成:
df['ma20'] = df['close'].rolling(20).mean() df['ma60'] = df['close'].rolling(60).mean() df['signal'] = (df['ma20'] > df['ma60']) & (df['ma20'].shift(1) <= df['ma60'].shift(1))这里shift(1)用来判断“上穿”而不是“已经在上方”。很多人只写ma20 > ma60,结果回测里每天都在开仓,因为条件持续为真。参数说明:rolling(20)的窗口是 20 根 K 线,不是 20 天,如果你的数据是日线,那就是 20 天;如果是分钟线,要换算。signal是布尔序列,后面用它生成持仓。这一步的产出是一个signals.py,里面每个策略条件一个函数,输入 DataFrame,输出布尔列。这样后面换数据、换参数都不用动主逻辑。
3. 用 Backtrader 把还原后的代码跑通一次完整回测
3.1 为什么选 Backtrader 而不是自己写循环
自己写 for 循环回测不是不行,但滑点、手续费、仓位管理、基准对比这些细节很容易漏。Backtrader 把这些都封装好了,而且社区资料多,遇到问题能搜到。常见做法是继承bt.Strategy,在next()里写交易逻辑。下面是一个最小可跑的回测骨架,把第 2 章还原出来的均线策略接进去。
import backtrader as bt import pandas as pd class MaCrossStrategy(bt.Strategy): params = (('fast', 20), ('slow', 60), ('stake', 100),) def __init__(self): self.ma_fast = bt.indicators.SMA(self.data.close, period=self.p.fast) self.ma_slow = bt.indicators.SMA(self.data.close, period=self.p.slow) self.crossover = bt.indicators.CrossOver(self.ma_fast, self.ma_slow) def next(self): if not self.position: if self.crossover > 0: self.buy(size=self.p.stake) else: if self.crossover < 0: self.close() if __name__ == "__main__": cerebro = bt.Cerebro() cerebro.addstrategy(MaCrossStrategy) df = pd.read_csv("data.csv", parse_dates=['datetime'], index_col='datetime') data = bt.feeds.PandasData(dataname=df) cerebro.adddata(data) cerebro.broker.setcash(100000.0) cerebro.broker.setcommission(commission=0.001) cerebro.addsizer(bt.sizers.FixedSize, stake=100) print(f"初始资金: {cerebro.broker.getvalue():.2f}") cerebro.run() print(f"最终资金: {cerebro.broker.getvalue():.2f}")逻辑说明:CrossOver是 Backtrader 内置的上穿下穿指标,比手写shift更稳。next()里先判断有没有持仓,没有才开仓,有就等反向信号平仓。setcommission(0.001)是万分之一手续费,A 股常见双边万三,这里只是示例。参数说明:fast和slow对应文档里的短期和长期均线周期;stake是每次买入股数,A 股要按 100 的整数倍。PandasData要求 DataFrame 的索引是 datetime,列名默认是 open/high/low/close/volume,如果你的列名不同,要传datetime=None并指定列映射。跑通后你会看到初始资金和最终资金,但别急着高兴,回测收益高不代表实盘能赚,下一章讲怎么验证。
3.2 数据接入:从 CSV 到 Backtrader 的列名映射
Backtrader 对列名有默认约定,但你的 CSV 可能叫日期、开盘、收盘。常见做法是读进来先重命名,再设索引。下面这段处理了中文列名和缺失值。
import pandas as pd df = pd.read_csv("raw_data.csv") df.columns = [c.strip().lower() for c in df.columns] rename_map = { '日期': 'datetime', '时间': 'datetime', '开盘': 'open', '最高': 'high', '最低': 'low', '收盘': 'close', '成交量': 'volume' } df = df.rename(columns=rename_map) df['datetime'] = pd.to_datetime(df['datetime']) df = df.set_index('datetime').sort_index() df = df[['open', 'high', 'low', 'close', 'volume']].dropna() df.to_csv("data.csv", index=True)逻辑说明:先统一列名小写去空格,再按中文映射重命名。pd.to_datetime处理日期格式,sort_index保证时间升序,Backtrader 要求数据按时间正序。dropna去掉缺失行,否则回测中途会报错。参数说明:如果你的数据是分钟级,datetime列要包含时分秒;volume如果是手数,注意和stake的单位一致。这一步跑完,data.csv就是 Backtrader 能直接吃的格式。
3.3 回测结果里必须看的三个指标:最大回撤、夏普、胜率
Backtrader 默认只打印最终资金,但你要自己加分析器。下面这段在cerebro.run()之前加三个分析器,跑完打印。
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='dd') cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.0) cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades') results = cerebro.run() strat = results[0] print(f"最大回撤: {strat.analyzers.dd.get_analysis()['max']['drawdown']:.2f}%") print(f"夏普比率: {strat.analyzers.sharpe.get_analysis()['sharperatio']:.2f}") ta = strat.analyzers.trades.get_analysis() total = ta.total.closed won = ta.won.total print(f"胜率: {won / total * 100:.2f}%")逻辑说明:DrawDown给最大回撤百分比,SharpeRatio给夏普,TradeAnalyzer给交易明细。riskfreerate=0.0表示无风险利率设为零,简化计算。参数说明:夏普大于 1 算及格,大于 2 算不错,但要看回测周期;最大回撤超过 30% 就要检查仓位是不是太重;胜率低于 40% 不一定差,如果盈亏比高也能赚。这三个指标一起看,单看一个容易翻车。
4. 避坑:Word 转 Python 量化代码最常见的 5 个翻车点
4.1 缩进被 Word 吃掉,复制后直接 IndentationError
现象:从 Word 复制代码到 PyCharm 或 VS Code,运行报IndentationError: unexpected indent。原因:Word 用制表符和空格混排,复制时层级丢失。解决:不要直接复制,用第 2 章的python-docx脚本抽文本,然后在编辑器里用Ctrl+Alt+L(PyCharm)或Shift+Alt+F(VS Code)格式化。如果已经复制了,全选后先Ctrl+[左移到底,再手动缩进。
4.2 中文引号和全角符号导致语法错误
现象:SyntaxError: invalid character '“'。原因:Word 自动把直引号转成弯引号,冒号逗号也变全角。解决:用脚本统一替换,或者在编辑器里开Ctrl+H正则替换[“”]为",[‘’]为',:为:,,为,。注意替换前备份,别把字符串里的中文标点也改了。
4.3 回测里用了未来函数,收益虚高
现象:回测年化 80%,实盘一跑就亏。原因:策略里用了shift(-1)或者用当天收盘价算信号又用当天收盘价成交。解决:信号计算用shift(1),成交价用次日开盘或收盘,Backtrader 里self.buy()默认下一根 K 线开盘成交,但如果你在next()里直接读self.data.close[0]做判断,要确认没有用到未来数据。检查方法:把数据截断到某一天,看信号是否变化。
4.4 手续费和滑点设成零,回测结果不可信
现象:回测曲线完美,实盘差很多。原因:setcommission(0)和没设滑点。解决:A 股双边手续费大约万三到万五,滑点按一个最小变动价位设。Backtrader 里cerebro.broker.set_slippage_perc(0.001)设千分之一滑点。别小看这个,高频策略里滑点能吃掉大半利润。
4.5 参数过拟合:在历史上调出最佳参数,换个周期就失效
现象:20/60 均线在 2020 年数据上夏普 3.0,换 2022 年数据夏普 0.3。原因:参数是针对特定历史区间调出来的。解决:把数据分成样本内和样本外,样本内调参,样本外验证。或者用滚动窗口,每半年重新调一次参数。我一般会跑三组参数:保守、中性、激进,看哪组在样本外更稳。
5. 进阶:把 Word 文档变成可维护的量化工程
5.1 用配置文件管理策略参数,不再硬编码
Word 文档里的参数表别扔,转成 YAML 或 JSON。下面是一个config.yaml示例,Backtrader 策略从里面读参数。
strategy: name: ma_cross fast: 20 slow: 60 stake: 100 commission: 0.001 slippage: 0.001 data: path: data.csv start: "2020-01-01" end: "2023-12-31"读取代码:
import yaml with open("config.yaml", "r", encoding="utf-8") as f: cfg = yaml.safe_load(f) fast = cfg['strategy']['fast'] slow = cfg['strategy']['slow']逻辑说明:yaml.safe_load把配置读成字典,后面改参数不用动 Python 文件。参数说明:start和end用来截取回测区间,避免每次手动改 CSV。这样一份 Word 文档就变成了“文档 + 配置 + 代码”三件套,团队里谁都能接手。
5.2 用向量化回测快速验证逻辑,再用事件驱动回测精算
Backtrader 是事件驱动,跑得慢但贴近实盘。如果你只想快速验证信号对不对,可以用 pandas 向量化算一遍。下面这段算均线策略的累计收益,几秒钟出结果。
df['ma_fast'] = df['close'].rolling(20).mean() df['ma_slow'] = df['close'].rolling(60).mean() df['position'] = (df['ma_fast'] > df['ma_slow']).astype(int) df['ret'] = df['close'].pct_change() df['strategy_ret'] = df['position'].shift(1) * df['ret'] df['cum_ret'] = (1 + df['strategy_ret']).cumprod() print(df['cum_ret'].iloc[-1])逻辑说明:position是 0 或 1,shift(1)保证用昨天信号吃今天收益。cumprod算累计净值。参数说明:pct_change()默认算相邻行收益率,如果数据有跳空,要检查。向量化回测适合快速迭代,但不要用它替代事件驱动回测,因为成交价和仓位管理不一样。
5.3 一个我踩过的坑:Word 里的“年化收益”没扣复权
早期我从一份 Word 文档里还原策略,回测年化 45%,实盘一上发现分红除权那天价格跳空,收益全被吃掉。后来才知道文档里的价格是未复权价,而回测应该用前复权或后复权。常见做法是数据源直接取复权价,或者在回测前用adjust处理。如果你拿到的 CSV 没有复权标记,去查数据源的说明,别默认它是对的。这个坑让我养成了一个习惯:任何回测跑完,先看除权日附近的交易记录,确认价格连续。希望帮到你。
本文还有配套的精品资源,点击获取