# %pip install backtrader
import backtrader as bt
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import datetime
# 1. Tushare股票数据
import tushare as ts
import pandas as pd
import numpy as np
def get_tushare_token(file_path='tushare_token.txt'):
"""
从文件读取 Tushare Token
"""
try:
with open(file_path, 'r') as file:
token = file.readline().strip()
return token
except FileNotFoundError:
print(f"Error: Token文件 {file_path} 未找到")
return None
except Exception as e:
print(f"读取Token时发生错误: {e}")
return None
def fetch_stock_data(tickers, start_date, end_date):
"""
从Tushare获取真实股票数据
参数:
tickers: 股票代码列表(使用完整代码如'600036.SH')
start_date: 开始日期
end_date: 结束日期
返回:
字典,键为股票代码,值为DataFrame数据
"""
# 获取 Token
token = get_tushare_token()
if not token:
raise ValueError("无法获取 Tushare Token")
# 初始化 Pro API
pro = ts.pro_api(token)
# 存储股票数据的字典
stock_data = {}
for ticker in tickers:
try:
# 获取日线行情数据(前复权)
df = pro.daily(
ts_code=ticker,
start_date=start_date.replace('-', ''),
end_date=end_date.replace('-', '')
)
# 数据预处理
if df is None or df.empty:
print(f"未获取到 {ticker} 的数据")
continue
# 转换日期
df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')
df = df.sort_values('trade_date').set_index('trade_date')
# 重命名列以完全匹配合成数据格式
df_processed = pd.DataFrame({
'Open': df['open'],
'High': df['high'],
'Low': df['low'],
'Close': df['close'],
'Volume': df['vol']
})
# 添加到股票数据字典
stock_data[ticker] = df_processed
except Exception as e:
print(f"获取 {ticker} 数据时发生错误: {e}")
return stock_data
def preprocess_stock_data(stock_data):
"""
进一步处理股票数据,确保数据质量
参数:
stock_data: 原始股票数据字典
返回:
处理后的股票数据字典
"""
processed_data = {}
for ticker, df in stock_data.items():
# 删除缺失值
df = df.dropna()
# 检查数据长度
if len(df) == 0:
print(f"警告:{ticker} 没有有效数据")
continue
# 异常值处理
Q1 = df['Close'].quantile(0.25)
Q3 = df['Close'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df = df[(df['Close'] >= lower_bound) & (df['Close'] <= upper_bound)]
# 确保列顺序和命名完全一致
df = df[['Open', 'High', 'Low', 'Close', 'Volume']]
processed_data[ticker] = df
return processed_data
# def main():
# # 选择要获取数据的股票
# tickers = ['600036.SH', '000001.SZ', '601318.SH'] # 招商银行、平安银行、中国平安
# start_date = '2020-01-01'
# end_date = '2022-12-31'
# # 获取股票数据
# stock_data = fetch_stock_data(tickers, start_date, end_date)
# # 数据预处理
# processed_stock_data = preprocess_stock_data(stock_data)
# # 数据验证与分析
# for ticker, df in processed_stock_data.items():
# print(f"\n{ticker} 数据统计:")
# print(df.describe())
# # 额外分析
# print("\n价格变化:")
# print(f"起始价格: {df['Close'].iloc[0]:.2f}")
# print(f"结束价格: {df['Close'].iloc[-1]:.2f}")
# print(f"总体变化: {(df['Close'].iloc[-1] / df['Close'].iloc[0] - 1) * 100:.2f}%")
# # 验证列名
# print("\n列名验证:")
# print(df.columns.tolist())
# return processed_stock_data
# if __name__ == "__main__":
# stock_data = main()
# 选择要获取数据的股票
tickers = ['600036.SH', '000001.SZ', '601318.SH'] # 招商银行、平安银行、中国平安
start_date = '2020-01-01'
end_date = '2022-12-31'
# end_date = '2020-12-31'
# 获取股票数据
stock_data = fetch_stock_data(tickers, start_date, end_date)
# 数据预处理
processed_stock_data = preprocess_stock_data(stock_data)
# 数据验证与分析
for ticker, df in processed_stock_data.items():
print(f"\n{ticker} 数据统计:")
print(df.describe())
# 额外分析
print("\n价格变化:")
print(f"起始价格: {df['Close'].iloc[0]:.2f}")
print(f"结束价格: {df['Close'].iloc[-1]:.2f}")
print(f"总体变化: {(df['Close'].iloc[-1] / df['Close'].iloc[0] - 1) * 100:.2f}%")
# 验证列名
print("\n列名验证:")
print(df.columns.tolist())
stock_data = processed_stock_data
600036.SH 数据统计: Open High Low Close Volume count 728.000000 728.000000 728.000000 728.000000 7.280000e+02 mean 42.750920 43.345838 42.164135 42.752459 6.789903e+05 std 8.066488 8.205544 7.898203 8.051716 3.417884e+05 min 26.900000 27.130000 26.300000 26.820000 2.156602e+05 25% 35.395000 35.837500 35.045000 35.397500 4.539281e+05 50% 40.935000 41.690000 40.415000 41.055000 5.852150e+05 75% 50.877500 51.435000 50.042500 50.900000 8.042732e+05 max 58.210000 58.920000 57.610000 58.500000 2.504770e+06 价格变化: 起始价格: 38.88 结束价格: 37.26 总体变化: -4.17% 列名验证: ['Open', 'High', 'Low', 'Close', 'Volume'] 000001.SZ 数据统计: Open High Low Close Volume count 728.000000 728.000000 728.000000 728.000000 7.280000e+02 mean 16.570577 16.832555 16.314890 16.581470 1.089831e+06 std 3.570154 3.660223 3.471243 3.576886 5.388167e+05 min 10.330000 10.450000 10.220000 10.340000 3.439356e+05 25% 13.640000 13.745000 13.417500 13.585000 7.339923e+05 50% 15.800000 16.050000 15.625000 15.820000 9.635799e+05 75% 19.000000 19.322500 18.620000 19.022500 1.298082e+06 max 24.910000 25.310000 24.520000 25.010000 4.749276e+06 价格变化: 起始价格: 16.87 结束价格: 13.16 总体变化: -21.99% 列名验证: ['Open', 'High', 'Low', 'Close', 'Volume'] 601318.SH 数据统计: Open High Low Close Volume count 728.000000 728.000000 728.000000 728.000000 7.280000e+02 mean 62.862514 63.559904 62.192418 62.832857 6.668724e+05 std 16.305443 16.463620 16.085748 16.260773 3.261314e+05 min 36.390000 36.670000 35.900000 36.150000 2.251350e+05 25% 47.485000 48.027500 46.730000 47.462500 4.369186e+05 50% 62.555000 63.275000 61.970000 62.335000 5.840238e+05 75% 78.445000 79.282500 77.632500 78.197500 8.167294e+05 max 93.380000 94.620000 92.090000 93.380000 2.806695e+06 价格变化: 起始价格: 86.12 结束价格: 47.00 总体变化: -45.42% 列名验证: ['Open', 'High', 'Low', 'Close', 'Volume']
# 2. 数据可视化
def plot_stock_prices(stock_data, window=20):
"""
绘制股票价格走势图和移动平均线
参数:
stock_data: 字典,键为股票代码,值为DataFrame数据
window: 移动平均窗口大小
"""
plt.figure(figsize=(15, 10))
for i, (ticker, data) in enumerate(stock_data.items()):
plt.subplot(len(stock_data), 1, i+1)
# 计算移动平均线
ma = data['Close'].rolling(window=window).mean()
# 绘制收盘价和移动平均线
plt.plot(data.index, data['Close'], label=f'{ticker} 收盘价')
plt.plot(data.index, ma, label=f'{window}日移动平均线', linestyle='--')
plt.title(f'{ticker} 股价走势')
plt.ylabel('价格')
plt.legend()
# 仅在最后一个子图上显示x轴标签
if i == len(stock_data) - 1:
plt.xlabel('日期')
plt.tight_layout()
plt.show()
# 绘制股票价格走势图
plot_stock_prices(stock_data)
def calculate_returns(stock_data):
"""
计算收益率
参数:
stock_data: 字典,键为股票代码,值为DataFrame数据
返回:
dict: 字典,键为股票代码,值为包含收益率的DataFrame
"""
returns_data = {}
for ticker, data in stock_data.items():
# 复制数据
df = data.copy()
# 计算日收益率
df['Daily_Return'] = df['Close'].pct_change()
# 计算累积收益率
df['Cumulative_Return'] = (1 + df['Daily_Return']).cumprod() - 1
returns_data[ticker] = df
return returns_data
# 计算收益率
returns_data = calculate_returns(stock_data)
def plot_cumulative_returns(returns_data):
"""
绘制累积收益率对比图
参数:
returns_data: 字典,键为股票代码,值为包含收益率的DataFrame
"""
plt.figure(figsize=(15, 6))
for ticker, data in returns_data.items():
plt.plot(data.index, data['Cumulative_Return'] * 100, label=ticker)
plt.title('各股票累积收益率对比')
plt.xlabel('日期')
plt.ylabel('累积收益率 (%)')
plt.legend()
plt.grid(True)
plt.show()
# 绘制累积收益率对比图
plot_cumulative_returns(returns_data)
def calculate_statistics(returns_data):
"""
计算统计指标
参数:
returns_data: 字典,键为股票代码,值为包含收益率的DataFrame
返回:
DataFrame: 包含各股票统计指标的DataFrame
"""
stats = []
for ticker, data in returns_data.items():
# 提取日收益率
daily_returns = data['Daily_Return'].dropna()
# 计算统计指标
avg_return = daily_returns.mean() * 252 * 100 # 年化收益率
volatility = daily_returns.std() * np.sqrt(252) * 100 # 年化波动率
sharpe = avg_return / volatility if volatility != 0 else 0 # 夏普比率
max_drawdown = (data['Close'] / data['Close'].cummax() - 1).min() * 100 # 最大回撤
stats.append({
'Ticker': ticker,
'Avg Return (%)': avg_return,
'Volatility (%)': volatility,
'Sharpe Ratio': sharpe,
'Max Drawdown (%)': max_drawdown
})
# 转换为DataFrame
stats_df = pd.DataFrame(stats).set_index('Ticker')
return stats_df
# 计算并显示统计指标
stats_df = calculate_statistics(returns_data)
print(stats_df)
Avg Return (%) Volatility (%) Sharpe Ratio Max Drawdown (%) Ticker 600036.SH 4.116795 33.533192 0.122768 -54.153846 000001.SZ -2.252198 35.772030 -0.062960 -58.656537 601318.SH -17.057392 28.119877 -0.606596 -61.287214
# 3. 准备Backtrader数据
def prepare_backtrader_data(stock_data):
"""
将股票数据转换为Backtrader格式
参数:
stock_data: 字典,键为股票代码,值为DataFrame数据
返回:
dict: 字典,键为股票代码,值为Backtrader的数据源
"""
bt_data = {}
for ticker, df in stock_data.items():
# 确保列名与Backtrader期望的一致
df_bt = df.copy()
df_bt.columns = [col.lower() for col in df_bt.columns] # 将列名转为小写
# 创建Backtrader数据源
data = bt.feeds.PandasData(
dataname=df_bt,
# 指定日期列是索引
datetime=None,
# 指定OHLCV列
open=0,
high=1,
low=2,
close=3,
volume=4,
openinterest=-1 # 不使用
)
bt_data[ticker] = data
return bt_data
# 准备Backtrader数据
bt_data = prepare_backtrader_data(stock_data)
# 4. 定义交易策略
# 1. 移动平均交叉策略
class MACrossStrategy(bt.Strategy):
"""
移动平均交叉策略
当短期移动平均线上穿长期移动平均线时买入
当短期移动平均线下穿长期移动平均线时卖出
"""
params = (
('short_period', 20), # 短期移动平均线周期
('long_period', 50), # 长期移动平均线周期
('printlog', False), # 是否打印日志
)
def __init__(self):
# 初始化移动平均线指标
self.short_ma = bt.indicators.SimpleMovingAverage(
self.data.close, period=self.params.short_period)
self.long_ma = bt.indicators.SimpleMovingAverage(
self.data.close, period=self.params.long_period)
# 交叉信号
self.crossover = bt.indicators.CrossOver(self.short_ma, self.long_ma)
# 跟踪订单,持仓和资产
self.order = None
self.buyprice = None
self.buycomm = None
# 添加移动平均线到图表
self.short_ma.plotinfo.plotname = f'SMA({self.params.short_period})'
self.long_ma.plotinfo.plotname = f'SMA({self.params.long_period})'
def log(self, txt, dt=None, doprint=False):
""" 记录策略信息 """
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print(f'{dt.isoformat()}, {txt}')
def notify_order(self, order):
""" 订单状态变化通知 """
# 如果订单已提交或接受,不做任何事
if order.status in [order.Submitted, order.Accepted]:
return
# 检查订单是否已经完成
if order.status in [order.Completed]:
if order.isbuy():
self.log(
f'买入执行, 价格: {order.executed.price:.2f}, '
f'成本: {order.executed.value:.2f}, '
f'手续费: {order.executed.comm:.2f}'
)
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # 卖出
self.log(
f'卖出执行, 价格: {order.executed.price:.2f}, '
f'成本: {order.executed.value:.2f}, '
f'手续费: {order.executed.comm:.2f}'
)
# 记录当前持仓规模
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('订单被取消/拒绝')
# 重置订单
self.order = None
def notify_trade(self, trade):
""" 交易结果通知 """
if not trade.isclosed:
return
self.log(f'交易利润, 毛利润: {trade.pnl:.2f}, 净利润: {trade.pnlcomm:.2f}')
def next(self):
""" 策略逻辑 """
# 记录收盘价
self.log(f'收盘价, {self.data.close[0]:.2f}')
# 检查是否有未完成的订单
if self.order:
return
# 检查是否已持仓
if not self.position:
# 如果短期移动平均线上穿长期移动平均线,则买入
if self.crossover > 0:
self.log(f'买入信号, {self.data.close[0]:.2f}')
# 记录订单以避免重复买入
self.order = self.buy()
else:
# 如果持仓,且短期移动平均线下穿长期移动平均线,则卖出
if self.crossover < 0:
self.log(f'卖出信号, {self.data.close[0]:.2f}')
# 记录订单以避免重复卖出
self.order = self.sell()
def stop(self):
""" 策略结束时调用 """
self.log('MA交叉策略结束,短期={} 长期={}'.format(
self.params.short_period, self.params.long_period), doprint=True)
# 5. 执行回测
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
def run_backtest(data, strategy, cash=100000.0, commission=0.001, **kwargs):
"""
运行回测
参数:
data: Backtrader数据源
strategy: 策略类
cash: 初始资金
commission: 手续费率
**kwargs: 策略参数
返回:
cerebro: Backtrader引擎
"""
# 创建Backtrader引擎
cerebro = bt.Cerebro()
# 添加数据
cerebro.adddata(data)
# 设置初始资金
cerebro.broker.setcash(cash)
# 设置手续费
cerebro.broker.setcommission(commission=commission)
# 添加策略,传入参数
cerebro.addstrategy(strategy, **kwargs)
# 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.01, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
# 设置标的数量计算方式
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)
# 运行回测
print(f'初始投资组合价值: {cerebro.broker.getvalue():.2f}')
results = cerebro.run()
print(f'最终投资组合价值: {cerebro.broker.getvalue():.2f}')
# 打印分析结果
strat = results[0]
print('\n分析结果:')
print(f'夏普比率: {strat.analyzers.sharpe.get_analysis()["sharperatio"]:.3f}')
print(f'最大回撤: {strat.analyzers.drawdown.get_analysis()["max"]["drawdown"]:.2f}%')
print(f'年化收益率: {strat.analyzers.returns.get_analysis()["ravg"] * 252 * 100:.2f}%')
trade_analysis = strat.analyzers.trades.get_analysis()
if trade_analysis['total']['total'] > 0:
print(f'总交易次数: {trade_analysis["total"]["total"]}')
print(f'获利交易次数: {trade_analysis["won"]["total"]}')
print(f'亏损交易次数: {trade_analysis["lost"]["total"]}')
if trade_analysis['won']['total'] > 0:
print(f'平均获利交易收益: {trade_analysis["won"]["pnl"]["average"]:.2f}')
if trade_analysis['lost']['total'] > 0:
print(f'平均亏损交易损失: {trade_analysis["lost"]["pnl"]["average"]:.2f}')
# Jupyter 绘图方案
plt.figure(figsize=(16, 10))
figs = cerebro.plot(
style='bar', # K线样式
barup='green', # 上涨K线颜色
bardown='red', # 下跌K线颜色
volume=True, # 显示成交量
trades=True, # 显示交易
figsize=(16, 10) # 图表尺寸
)
# plt.tight_layout()
plt.show()
return cerebro
# 运行移动平均交叉策略回测
ticker = tickers[0] # 选择第一支股票
cerebro_ma = run_backtest(
data=bt_data[ticker],
strategy=MACrossStrategy,
cash=100000.0,
commission=0.001,
short_period=20,
long_period=50,
printlog=False
)
初始投资组合价值: 100000.00 2022-12-30, MA交叉策略结束,短期=20 长期=50 最终投资组合价值: 88897.98 分析结果: 夏普比率: -0.114 最大回撤: 39.91% 年化收益率: -4.07% 总交易次数: 8 获利交易次数: 2 亏损交易次数: 5 平均获利交易收益: 12870.65 平均亏损交易损失: -7986.17
# 6. 策略参数优化
def optimize_strategy(data, strategy, cash=100000.0, commission=0.001, **kwargs):
"""
优化策略参数
参数:
data: Backtrader数据源
strategy: 策略类
cash: 初始资金
commission: 手续费率
**kwargs: 策略参数范围
返回:
results: 优化结果
"""
# 创建Backtrader引擎
cerebro = bt.Cerebro(maxcpus=1) # 使用单核处理以避免并行问题
# 添加数据
cerebro.adddata(data)
# 设置初始资金
cerebro.broker.setcash(cash)
# 设置手续费
cerebro.broker.setcommission(commission=commission)
# 添加策略,使用参数范围
cerebro.optstrategy(strategy, **kwargs)
# 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.01, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
# 设置标的数量计算方式
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)
# 运行优化
print('开始优化...')
results = cerebro.run()
print('优化完成!')
# 收集结果
final_results = []
for run in results:
for strategy in run:
sharpe = strategy.analyzers.sharpe.get_analysis()['sharperatio']
drawdown = strategy.analyzers.drawdown.get_analysis()['max']['drawdown']
annual_return = strategy.analyzers.returns.get_analysis()['ravg'] * 252 * 100
# 获取策略参数
params = {}
for param_name, param_value in strategy.params._getitems():
# 排除私有属性和不需要的属性
if not param_name.startswith('_') and param_name != 'printlog':
params[param_name] = param_value
final_results.append({
'params': params,
'sharpe': sharpe,
'drawdown': drawdown,
'annual_return': annual_return
})
# 按夏普比率排序
#final_results.sort(key=lambda x: x['sharpe'], reverse=True)
final_results.sort(key=lambda x: float('-inf') if x['sharpe'] is None else x['sharpe'], reverse=True)
return final_results
# 优化MA交叉策略
ticker = tickers[0] # 使用第一支股票
# 定义参数范围
params = {
'short_period': range(5, 31, 5), # 短期MA周期:5, 10, 15, 20, 25, 30
'long_period': range(30, 101, 10), # 长期MA周期:30, 40, 50, 60, 70, 80, 90, 100
'printlog': [False] # 禁用日志输出
}
# 运行优化
ma_results = optimize_strategy(
data=bt_data[ticker],
strategy=MACrossStrategy,
cash=100000.0,
commission=0.001,
**params
)
# 显示前10个最佳结果
print('\nMA交叉策略优化结果 (按夏普比率排序):')
print('\n前10个最佳参数组合:')
for i, result in enumerate(ma_results[:10]):
params_str = ', '.join([f'{k}={v}' for k, v in result['params'].items()])
print(f'{i+1}. {params_str} - 夏普比率: {result["sharpe"]:.3f}, '
f'最大回撤: {result["drawdown"]:.2f}%, 年化收益率: {result["annual_return"]:.2f}%')
开始优化... 2022-12-30, MA交叉策略结束,短期=5 长期=30 2022-12-30, MA交叉策略结束,短期=5 长期=40 2022-12-30, MA交叉策略结束,短期=5 长期=50 2022-12-30, MA交叉策略结束,短期=5 长期=60 2022-12-30, MA交叉策略结束,短期=5 长期=70 2022-12-30, MA交叉策略结束,短期=5 长期=80 2022-12-30, MA交叉策略结束,短期=5 长期=90 2022-12-30, MA交叉策略结束,短期=5 长期=100 2022-12-30, MA交叉策略结束,短期=10 长期=30 2022-12-30, MA交叉策略结束,短期=10 长期=40 2022-12-30, MA交叉策略结束,短期=10 长期=50 2022-12-30, MA交叉策略结束,短期=10 长期=60 2022-12-30, MA交叉策略结束,短期=10 长期=70 2022-12-30, MA交叉策略结束,短期=10 长期=80 2022-12-30, MA交叉策略结束,短期=10 长期=90 2022-12-30, MA交叉策略结束,短期=10 长期=100 2022-12-30, MA交叉策略结束,短期=15 长期=30 2022-12-30, MA交叉策略结束,短期=15 长期=40 2022-12-30, MA交叉策略结束,短期=15 长期=50 2022-12-30, MA交叉策略结束,短期=15 长期=60 2022-12-30, MA交叉策略结束,短期=15 长期=70 2022-12-30, MA交叉策略结束,短期=15 长期=80 2022-12-30, MA交叉策略结束,短期=15 长期=90 2022-12-30, MA交叉策略结束,短期=15 长期=100 2022-12-30, MA交叉策略结束,短期=20 长期=30 2022-12-30, MA交叉策略结束,短期=20 长期=40 2022-12-30, MA交叉策略结束,短期=20 长期=50 2022-12-30, MA交叉策略结束,短期=20 长期=60 2022-12-30, MA交叉策略结束,短期=20 长期=70 2022-12-30, MA交叉策略结束,短期=20 长期=80 2022-12-30, MA交叉策略结束,短期=20 长期=90 2022-12-30, MA交叉策略结束,短期=20 长期=100 2022-12-30, MA交叉策略结束,短期=25 长期=30 2022-12-30, MA交叉策略结束,短期=25 长期=40 2022-12-30, MA交叉策略结束,短期=25 长期=50 2022-12-30, MA交叉策略结束,短期=25 长期=60 2022-12-30, MA交叉策略结束,短期=25 长期=70 2022-12-30, MA交叉策略结束,短期=25 长期=80 2022-12-30, MA交叉策略结束,短期=25 长期=90 2022-12-30, MA交叉策略结束,短期=25 长期=100 2022-12-30, MA交叉策略结束,短期=30 长期=30 2022-12-30, MA交叉策略结束,短期=30 长期=40 2022-12-30, MA交叉策略结束,短期=30 长期=50 2022-12-30, MA交叉策略结束,短期=30 长期=60 2022-12-30, MA交叉策略结束,短期=30 长期=70 2022-12-30, MA交叉策略结束,短期=30 长期=80 2022-12-30, MA交叉策略结束,短期=30 长期=90 2022-12-30, MA交叉策略结束,短期=30 长期=100 优化完成! MA交叉策略优化结果 (按夏普比率排序): 前10个最佳参数组合: 1. short_period=20, long_period=50 - 夏普比率: 1.867, 最大回撤: 24.28%, 年化收益率: 7.98% 2. short_period=20, long_period=50 - 夏普比率: 1.222, 最大回撤: 18.87%, 年化收益率: 11.03% 3. short_period=20, long_period=50 - 夏普比率: 1.188, 最大回撤: 20.71%, 年化收益率: 10.25% 4. short_period=20, long_period=50 - 夏普比率: 1.178, 最大回撤: 22.68%, 年化收益率: 10.43% 5. short_period=20, long_period=50 - 夏普比率: 1.054, 最大回撤: 21.74%, 年化收益率: 10.08% 6. short_period=20, long_period=50 - 夏普比率: 1.001, 最大回撤: 23.64%, 年化收益率: 9.35% 7. short_period=20, long_period=50 - 夏普比率: 0.948, 最大回撤: 29.21%, 年化收益率: 10.57% 8. short_period=20, long_period=50 - 夏普比率: 0.922, 最大回撤: 22.51%, 年化收益率: 9.37% 9. short_period=20, long_period=50 - 夏普比率: 0.915, 最大回撤: 23.03%, 年化收益率: 10.72% 10. short_period=20, long_period=50 - 夏普比率: 0.884, 最大回撤: 19.20%, 年化收益率: 10.92%
# 7. 使用最佳参数运行策略
# 获取最佳参数
best_params = ma_results[0]['params']
print(f'最佳参数组合: {best_params}')
# 使用最佳参数运行策略
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
cerebro_best = run_backtest(
data=bt_data[ticker],
strategy=MACrossStrategy,
cash=100000.0,
commission=0.001,
**best_params,
printlog=False
)
最佳参数组合: {'short_period': 20, 'long_period': 50} 初始投资组合价值: 100000.00 2022-12-30, MA交叉策略结束,短期=20 长期=50 最终投资组合价值: 88897.98 分析结果: 夏普比率: -0.114 最大回撤: 39.91% 年化收益率: -4.07% 总交易次数: 8 获利交易次数: 2 亏损交易次数: 5 平均获利交易收益: 12870.65 平均亏损交易损失: -7986.17