蜗牛博客VNPY学习记录:
VN.PY 2.0学习记录一(如何回测)
VN.PY 2.0学习记录二(策略开发)
Vn.py学习记录三(米筐教程)
VN.PY 2.0学习记录四(多线程、多进程)
Vn.py学习记录五–交易时间段及Widgets
Vn.py学习记录六(无界面模拟盘)
Vn.py学习记录七(V2.0.5版本)
Vnpy学习记录八(R-Breaker及pickle)
Vn.py学习记录九(事件驱动引擎)
VN.PY学习记录十(源码概述)
VNPY学习记录11(微信+Vscode)
VNPY学习记录12(父子进程、回调函数)
VNPY学习记录13(部署到云服务器,实现自动交易)
说明:
使用界面回测时,每次修改了策略,要重新登陆vnpy,非常麻烦。
一、通过getdata.py获取回测数据
现在VNPY与米筐合作,可以非常方便地获得相关数据。
1.填写下载账号
这里又有一个坑,首先说明一下,下载了VN.PY的安装文件,解压缩之后,会有一个VNPY的文件夹,然后执行了安装程序之后,会在C:\ProgramData\VNConda\Lib\site-packages\下面也生成一个VNPY的文件夹。
我之前说过,写策略需要在C:\ProgramData\VNConda\Lib\site-packages\vnpy\app\cta_strategy\strategies这个文件夹下面建立,不然根本无法加载。然后今天打算测试回测的时候,因为记得之前看教程说需要在一个文件中输入ricqquant的账号与密码,所以我一个劲地在C:\ProgramData\VNConda\Lib\site-packages\vnpy\这个文件夹下面找文件,找了一通没有找到。后来竟然在F:\vnpy\vnpy-2.0.2\tests\backtesting\这里面找到了,要修改的其实就是getdata.py文件!
打开getdata.py文件,填写账号与密码。

注意:
填写代码时要注意大小写,比如螺纹钢要写“RB88.SHFE”才能下载到数据,用rb88.SHFE就不行。
2.获取数据
执行getdata.py文件,然后“咚”的一声,又掉坑里了。出现如下的错误提示:
rqdatac.share.errors.AuthenticationFailed: authentication failed.
不知是因为现在是五一假期非交易时间段,还是账号的原因,反正就是取不到数。
Updated on Jul.06 2019:
1.这里经后来确认,是由于我输错了账号、密码的原因。米筐的账号、密码是make.bat里面的那个,不是注册网站的那个。
2.这个getdata文件随便放哪儿都可以。
只要填写了正确的RQdata账号与密码,那么就可以取到数据了:

取到的数据是直接写入到了sqlite数据库,比如我设定的是取20190501-20190601的数据,这里就已经取到了。
导入的数据在哪儿呢?
默认生成的databased.db文件在当前运行目录下的.vntrader下,我的是在c盘user目录下的.vntrader里面。可以使用SQLiteSPY查看:

二、自己写代码获取数据
其实这一部分完全是自己最开始使用的时候,无法通过getdata.py文件获取数据折腾出来的,当作自己的笔记放这儿吧,大家如果是通过其他途径获的数据,可以将这个作为参考如何导入数据。
(一)安装jupyter
安装好VN.py2.0.2之后,jupyter是用不了的,需要用下面的命令重新安装。
pip install jupyter
(二)试用ricequant
1.RQDatac
pip install --extra-index-url https://rquser:ricequant99@py.ricequant.com/simple/ rqdatac
2.配置License
直接执行ricequant提供的make.bat文件即可,然后重启CMD窗口。
3.取tick数据
新建quant.py文件,代码如下:
import rqdatac as rq
from rqdatac import *
rq.init()
rq.get_price('000001.XSHE','2018-3-23','2018-3-23','tick')
成果展示:

4.取股票数据
import rqdatac as rq
import pandas as pd
from rqdatac import *
rq.init()
data = rq.get_price('平安银行','2018-3-23','2018-4-23')
df = pd.DataFrame(data)
df.to_csv('pingan.csv')
成果展示:

备注:
默认取一天的数据,如果要取一分钟的数据则用:
data = rq.get_price('平安银行','2018-3-23','2018-3-28','1m')
函数的详细用法看这里:
https://www.ricequant.com/doc/rqdata-institutional#research-API-get_price
5.获取1分钟数据
import rqdatac as rq
import pandas as pd
from rqdatac import *
rq.init()
FIELDS = ["open", "high", "low", "close", "volume"]
data = rq.get_price(
"IF88",
frequency="1m",
fields=FIELDS,
start_date='20190101',
end_date='20190630'
)
df = pd.DataFrame(data)
df.to_csv('cf88new.csv')
(三)载入CSV文件
1.按下面的格式整理一份CSV文件,

2.导入数据:
执行VNPY的“功能————CSV导入”功能。

这里又有一个小坑,VN.PY默认的表头信息是“Open,High,Low,Close”这样第一个字母大写的形式,而从ricequant导出的数据是“open,high,low,close”这样第一个不是大写的,当我第一次运行的时候,明明所有字段都错了,却只提示我“volume”这个字段出错,结果我就在那里找呀找呀,找了半天没找到到底哪里出错了,后来才发现是这个字段首字母大小写的问题。
3.导入成功:

备注:
上面整理数据的那一步其实可以省略,设定好字段后,程序会自动识别。
另外,第二次导入数据之后,会在第一次的数据后面添加数据。
4.查看数据

三、策略回测
(一)回测方法一
使用vnpy的UI界面进行回测,这个简单,就不说了。
(二)自己写代码回测
新建一个q2.py文件用于回测,比如我的就放在C:\Users\Administrator\下面。
备注:回测的策略必须放在:C:\veighna_studio\Lib\site-packages\vnpy_ctastrategy\strategies 下面。
写回测代码:
from vnpy.app.cta_strategy.backtesting import BacktestingEngine
from vnpy.app.cta_strategy.strategies.boll_channel_strategy import BollChannelStrategy
from datetime import datetime
engine = BacktestingEngine()
engine.set_parameters(
vt_symbol = "000001.SZSE",
interval ="d",
start = datetime(2018,3,23),
end = datetime(2018,4,23),
rate = 0,
slippage = 0,
size = 10,
pricetick = 0.2,
capital = 1_000_000,
)
engine.add_strategy(BollChannelStrategy,{})
engine.load_data()
engine.run_backtesting()
df = engine.calculate_result()
engine.calculate_statistics()
engine.show_chart()
执行q2.py成果展示:

Updated on Aug.6.2026
更新了,直接读取csv文件回测。
from datetime import datetime
import pandas as pd
from vnpy_ctastrategy.backtesting import BacktestingEngine
from vnpy_ctastrategy.strategies.double_ma_strategy import DoubleMaStrategy
from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.object import BarData
def load_bars_from_csv(
filepath: str,
symbol: str,
exchange: Exchange
) -> list[BarData]:
"""从CSV文件加载K线数据,转换为BarData列表"""
df: pd.DataFrame = pd.read_csv(filepath) # 修正:使用传入的文件路径
bars: list[BarData] = []
for row in df.itertuples():
bar: BarData = BarData(
symbol=symbol,
exchange=exchange,
datetime=datetime.strptime(row.datetime, "%m/%d/%Y %H:%M"), # 修正时间格式
interval=Interval.MINUTE,
open_price=row.open,
high_price=row.high,
low_price=row.low,
close_price=row.close,
volume=row.volume,
gateway_name="CSV"
)
bars.append(bar)
return bars
engine = BacktestingEngine()
engine.set_parameters(
vt_symbol="RB888.SHFE",
interval=Interval.MINUTE,
start=datetime(2018, 3, 23),
end=datetime(2018, 6, 23),
rate=0,
slippage=0,
size=10,
pricetick=0.2,
capital=1_000_000,
)
engine.add_strategy(DoubleMaStrategy, {})
# engine.load_data()
# 跳过 load_data(),直接注入 CSV 数据
engine.history_data = load_bars_from_csv(
"test.csv", "rb888", Exchange.SHFE
)
engine.run_backtesting()
df = engine.calculate_result()
engine.calculate_statistics()
engine.show_chart()
# 假设 engine 是你已经运行完毕的 BacktestingEngine 实例
trades = engine.get_all_trades() # 返回 List[TradeData]
#保存成交记当。同样的,每日盈亏可以用 engine.get_all_daily_results(),持仓记录用 engine.get_all_positions() 等。
df2 = pd.DataFrame([t.__dict__ for t in trades])
df2.to_csv('trade_log.csv', index=False)
四、一个策略多品种批量回测
效果展示:

from datetime import datetime
import pandas as pd
from vnpy_ctastrategy.backtesting import BacktestingEngine
from vnpy_ctastrategy.strategies.twenty03_strategy import Twenty03Strategy
from vnpy.trader.constant import Interval
# 定义需要回测的品种列表,每个品种包含合约代码、乘数、最小变动价位
symbols = [
{"vt_symbol": "TA999.CZCE", "size": 5, "pricetick": 2}, # PTA:5吨/手,2元/吨
{"vt_symbol": "SA999.CZCE", "size": 20, "pricetick": 1}, # 纯碱:20吨/手,1元/吨
{"vt_symbol": "P999.DCE", "size": 10, "pricetick": 2}, # 棕榈油:10吨/手,2元/吨
{"vt_symbol": "FU999.SHFE", "size": 10, "pricetick": 1}, # 燃料油:10吨/手,1元/吨
{"vt_symbol": "V999.DCE", "size": 5, "pricetick": 1}, # PVC:5吨/手,1元/吨
{"vt_symbol": "RB999.SHFE", "size": 10, "pricetick": 1}, # 螺纹钢:10吨/手,1元/吨
{"vt_symbol": "MA999.CZCE", "size": 10, "pricetick": 1}, # 甲醇:10吨/手,1元/吨
{"vt_symbol": "AL999.SHFE", "size": 5, "pricetick": 5}, # 铝:5吨/手,5元/吨
{"vt_symbol": "SP999.SHFE", "size": 10, "pricetick": 2}, # 纸浆:10吨/手,2元/吨
{"vt_symbol": "FG999.CZCE", "size": 20, "pricetick": 1}, # 玻璃:20吨/手,1元/吨
{"vt_symbol": "AU999.SHFE", "size": 1000, "pricetick": 0.02}, # 沪金:1000克/手,最小价位:0.02
{"vt_symbol": "AG999.SHFE", "size": 15, "pricetick": 1}, # 沪银:15千克/手,最小价位:1
{"vt_symbol": "RU999.SHFE", "size": 10, "pricetick": 5}, # 天然橡胶:10吨/手,最小价位:5
{"vt_symbol": "SR999.CZCE", "size": 10, "pricetick": 1}, # 白糖:10吨/手,最小价位:1
{"vt_symbol": "M999.DCE", "size": 10, "pricetick": 1}, # 豆粕:10吨/手,最小价位:1
{"vt_symbol": "I999.DCE", "size": 100, "pricetick": 0.5}, # 铁矿石:100吨/手,最小价位:0.5
{"vt_symbol": "J999.DCE", "size": 100, "pricetick": 0.5}, # 焦炭:100吨/手,最小价位:0.5
{"vt_symbol": "JM999.DCE", "size": 60, "pricetick": 0.5}, # 焦煤:60吨/手,最小价位:0.5
{"vt_symbol": "SC999.INE", "size": 1000, "pricetick": 0.1}, # 原油:1000桶/手,最小价位:0.1
{"vt_symbol": "CU999.SHFE", "size": 5, "pricetick": 10}, # 沪铜:5吨/手,最小价位:10
{"vt_symbol": "NI999.SHFE", "size": 1, "pricetick": 10}, # 沪镍:1吨/手,最小价位:10
{"vt_symbol": "SN999.SHFE", "size": 1, "pricetick": 10}, # 沪锡:1吨/手,最小价位:10
{"vt_symbol": "RM999.CZCE", "size": 10, "pricetick": 1}, # 菜粕:10吨/手,最小价位:1
{"vt_symbol": "CF999.CZCE", "size": 5, "pricetick": 5}, # 棉花:5吨/手,最小价位:5
]
# 公共回测参数
start = datetime(2016, 1, 1)
end = datetime(2026, 7, 31)
interval = Interval.MINUTE
rate = 0.0001 #万分之一手续费
slippage = 1 #1个最小变动价位滑点
capital = 100_000
# 用于存储每个品种的回测结果摘要
results_summary = []
# 用于存储所有品种每年的收益
yearly_summary = []
for sym in symbols:
# 为每个品种创建独立的回测引擎
engine = BacktestingEngine()
engine.set_parameters(
vt_symbol=sym["vt_symbol"],
interval=interval,
start=start,
end=end,
rate=rate,
slippage=slippage,
size=sym["size"],
pricetick=sym["pricetick"],
capital=capital,
)
# 添加策略(所有品种使用相同策略参数)
engine.add_strategy(Twenty03Strategy, {})
# 加载数据并运行回测
engine.load_data()
engine.run_backtesting()
# 计算回测结果
df = engine.calculate_result()
stats = engine.calculate_statistics()
# 方法1:从 stats 中获取百分比收益率,并正确计算期末资金
total_return = stats.get("total_return", 0) # 已是百分比数值
final_balance = capital * (1 + total_return / 100)
# 方法2(更准确):直接从回测结果 DataFrame 取最后一天的权益
# final_balance = df["balance"].iloc[-1]
annual_return = stats.get("annual_return", 0)
max_drawdown = stats.get("max_drawdown", 0)
results_summary.append({
"vt_symbol": sym["vt_symbol"],
"final_balance": final_balance,
"total_return(%)": total_return, # 建议列名注明百分比
"annual_return(%)": annual_return,
"max_drawdown": max_drawdown,
})
# ========== 新增:计算并记录每年收益 ==========
if df is not None and not df.empty:
# 确保索引为DatetimeIndex
if not isinstance(df.index, pd.DatetimeIndex):
df.index = pd.to_datetime(df.index)
# 按年份分组,取每年最后一个交易日的权益
yearly_balance = df.groupby(df.index.year)['balance'].last()
prev_balance = capital
print(f"\n===== {sym['vt_symbol']} 年度收益率 =====")
for year, end_balance in yearly_balance.items():
ret = (end_balance - prev_balance) / prev_balance * 100
yearly_summary.append({
'vt_symbol': sym['vt_symbol'],
'year': year,
'return(%)': round(ret, 2)
})
print(f"{year}: {ret:.2f}%")
prev_balance = end_balance
# ========== 年度收益计算结束 ==========
# ========== 导出成交记录(原有代码) ==========
trades = engine.get_all_trades()
if trades:
# 将 TradeData 对象列表转换为 DataFrame
df_trades = pd.DataFrame([t.__dict__ for t in trades])
# 生成安全的文件名(替换可能存在的路径分隔符)
safe_symbol = sym["vt_symbol"].replace("/", "_").replace(":", "_")
trade_filename = f"{safe_symbol}_trades.csv"
df_trades.to_csv(trade_filename, index=False)
print(f"已保存 {sym['vt_symbol']} 的成交记录至 {trade_filename},共 {len(trades)} 条")
else:
print(f"{sym['vt_symbol']} 没有成交记录")
# ========== 成交记录导出结束 ==========
# 可选:显示单个品种的图表(若需要可取消注释)
#engine.show_chart()
# 将汇总结果转为 DataFrame 并打印
summary_df = pd.DataFrame(results_summary)
print("\n===== 各品种回测汇总 =====")
print(summary_df)
# 保存汇总结果到 CSV
summary_df.to_csv("backtest_summary.csv", index=False)
# 打印并保存年度收益汇总
if yearly_summary:
yearly_df = pd.DataFrame(yearly_summary)
print("\n===== 所有品种年度收益率汇总 =====")
print(yearly_df)
yearly_df.to_csv("yearly_returns.csv", index=False)
五、解决问题
上面的回测虽然运行成功,可是没有任何成交数据。到底是什么原因呢?代码问题?数据导入的时候的格式问题?还是其他的问题。
后来通过测试发现是数据量的问题,我用来测试的数据量太少了,后来将测试的1分钟数据期间改为2个月,终于好了。
成果展示:

图形展示:

用两个月数据大致测试了一下各个策略的年化收益率:
AtrRsiStrategy 16.88%
BollChannelStrategy 21.75%
DoubleMaStrategy -222.75%
DualThrustStrategy 48.12%
KingKeltnerStrategy 报错
MultiSignalStrategy -279%
MultiTimeframeStrategy 18.27%
TestStrategy 报错
TurtleSignalStrategy 134.98
看一个IF88.CFFEX从2016,1,1到2019,7,1的策略,有的年份很赚, 有的年份还会亏钱:


1