''' 以下是代码的详细说明: #公众号:松鼠Quant #主页:www.quant789.com #本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!! #版权归松鼠Quant所有,禁止转发、转卖源码违者必究。 1. 导入必要的模块和库: backtrader 用于回测功能 datetime 用于处理日期和时间 GenericCSVData 用于从CSV文件加载数据 numpy 用于数值操作 time 用于时间相关操作 matplotlib.pyplot 用于绘图 2. 定义自定义手续费模板MyCommission 继承自bt.CommInfoBase 3. 定义自定义数据源类 GenericCSV_SIG: 继承自 GenericCSVData,并添加了两个额外的行:'sig'和'delta' 定义了参数 'sig'和'delta' 4. 定义 MyStrategy_固定止损_跟踪止盈 类: 继承自 bt.Strategy(backtrader的基础策略类) 定义了两个参数:trailing_stop_percent 和 fixed_stop_loss_percent 初始化策略并设置各种变量和指标 实现了 next 方法,该方法在数据源的每个新的K线出现时被调用 根据当前K线数据更新跟踪止盈价格 实现了跟踪止盈出场和固定止损出场 根据信号处理多头和空头仓位 在策略执行过程中打印调试信息 5. if __name__ == "__main__": 代码块: 使用 Cerebro 实例设置回测环境 使用 GenericCSV_SIG 数据源从CSV文件加载数据 将数据源和策略添加到 Cerebro 实例中 添加观察者和分析器以评估性能 设置初始资金和经纪人参数 运行回测并获取结果 打印回测报告,包括收益率、回撤、胜率和交易统计数据 使用 matplotlib 绘制回测结果 使用说明:使用前需要调整的相关参数如下 1.确定python到csv文件夹下运行,修改csv文件为需要运行的csv 2.MyStrategy_固定止损_跟踪止盈:可以修改跟踪百分比和移动周期均线。 3.__init__函数中:可以修改lost手数,, 4.next函数:一、修改清仓时间参数,每个品种不一致;二、window_size和window_size_delta的周期暂为10;三、修改“开多组合”和“开空组合” 5.__main__函数:一、修改回测时间段fromdate和todate;二、根据交易平中设置初始资金、手续费,单手保证金,合约倍数; ''' # 需要进一步了解windows_size的计算规则,日线? import backtrader as bt from datetime import datetime from datetime import time as s_time from backtrader.feeds import GenericCSVData import numpy as np import pandas as pd import time import matplotlib.pyplot as plt import os # 导入表头解决图标中中文显示问题 from pylab import mpl 手续费汇总=0 class GenericCSV_SIG(GenericCSVData): # 从基类继承,添加一个 'sig',‘delta’行 lines = ('sig','delta') # 添加参数为从基类继承的参数 params = (('sig',6),('delta', 8)) class MyStrategy_固定止损_跟踪止盈(bt.Strategy): params = ( ('trailing_stop_percent', 0.02), # 跟踪止盈百分比 ('fixed_stop_loss_percent', 0.01), # 固定止损百分比 # ('sma1_period', 60), # 移动平均线周期 # ('sma2_period',120), ) def __init__(self): self.Lots=1 #下单手数 self.signal = self.datas[0].sig # 使用sig字段作为策略的信号字段 self.delta= self.datas[0].delta # 获取数据序列别名列表 line_aliases = self.datas[0].getlinealiases() self.pos=0 print(line_aliases) self.high=self.datas[0].high self.low=self.datas[0].low self.closes=self.datas[0].close self.open=self.datas[0].open self.trailing_stop_percent = self.params.trailing_stop_percent self.short_trailing_stop_price = 0 self.long_trailing_stop_price = 0 self.fixed_stop_loss_percent = self.params.fixed_stop_loss_percent self.sl_long_price=0 self.sl_shor_price=0 #240884432 self.out_long=0 self.out_short=0 self.rinei_ma=[] self.rinei_mean=0 self.datetime_list= [] self.high_list = [] self.low_list = [] self.close_list = [] self.opens_list = [] self.deltas_list = [] self.delta_cumsum=[] self.barN = 0 # self.sma1 = bt.indicators.SimpleMovingAverage( # self.data, period=self.params.sma1_period # ) # self.sma2 = bt.indicators.SimpleMovingAverage( # self.data, period=self.params.sma2_period # ) self.df = pd.DataFrame(columns=['datetime', 'high', 'low', 'close', 'open', 'delta', 'delta_cumsum']) self.trader_df=pd.DataFrame(columns=['open', 'high', 'low', 'close', 'volume', 'openInterest','delta']) def log(self, txt, dt=None): '''可选,构建策略打印日志的函数:可用于打印订单记录或交易记录等''' dt = dt or self.datas[0].datetime.date(0) print('%s, %s' % (dt.isoformat(), txt)) def notify_order(self, order): # 未被处理的订单 if order.status in [order.Submitted, order.Accepted]: return # 已经处理的订单 if order.status in [order.Completed, order.Canceled, order.Margin]: global 手续费汇总 if order.isbuy(): 手续费汇总 +=order.executed.comm self.log( 'BUY EXECUTED, 订单编号:%.0f,成交价格: %.2f, 手续费滑点:%.2f, 成交量: %.2f, 品种: %s,手续费汇总:%.2f' % (order.ref, # 订单编号 order.executed.price, # 成交价 order.executed.comm, # 佣金 order.executed.size, # 成交量 order.data._name,# 品种名称 手续费汇总)) else: # Sell 手续费汇总 +=order.executed.comm self.log('SELL EXECUTED, 订单编号:%.0f,成交价格: %.2f, 手续费滑点:%.2f, 成交量: %.2f, 品种: %s,手续费汇总:%.2f' % (order.ref, order.executed.price, order.executed.comm, order.executed.size, order.data._name, 手续费汇总)) def next(self): #公众号:松鼠Quant #主页:www.quant789.com #本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!! #版权归松鼠Quant所有,禁止转发、转卖源码违者必究。 #bar线计数初始化 self.barN += 1 position = self.getposition(self.datas[0]).size #时间轴 dt = bt.num2date(self.data.datetime[0]) #更新跟踪止损价格 def 每日重置数据(): # 获取当前时间 current_time = dt.time() #print(current_time) # 设置清仓操作的时间范围1:14:55到15:00 clearing_time1_start = s_time(14, 55) clearing_time1_end = s_time(15, 0) # 设置清仓操作的时间范围2:00:55到01:00 clearing_time2_start = s_time(22, 55) clearing_time2_end = s_time(23, 0) # 创建一个标志变量 clearing_executed = False if clearing_time1_start <= current_time <= clearing_time1_end and not clearing_executed : clearing_executed = True # 设置标志变量为已执行 self.rinei_ma=[] self.delta_cumsum=[] self.deltas_list=[] elif clearing_time2_start <= current_time <= clearing_time2_end and not clearing_executed : clearing_executed = True # 设置标志变量为已执行 self.rinei_ma=[] self.delta_cumsum=[] self.deltas_list=[] # 如果不在任何时间范围内,可以执行其他操作 else: self.rinei_ma.append(self.closes[0]) self.rinei_mean = np.mean(self.rinei_ma) #self.delta_cumsum=[] #self.deltas_list=[] #print('rinei_ma',self.rinei_ma) clearing_executed = False pass return clearing_executed run_kg=每日重置数据() #过滤成交量为0或小于0 if self.data.volume[0] <= 0 : return #print(f'volume,{self.data.volume[0]}') if self.long_trailing_stop_price >0 and self.pos>0: #print('datetime+sig: ',dt,'旧多头出线',self.long_trailing_stop_price,'low',self.low[0]) self.long_trailing_stop_price = self.low[0] if self.long_trailing_stop_price0 and self.pos<0: #print('datetime+sig: ',dt,'旧空头出线',self.short_trailing_stop_price,'high',self.high[0]) self.short_trailing_stop_price = self.high[0] if self.high[0] 0: if self.low[0] < self.out_long and self.pos>0 and self.sl_long_price>0 and self.low[0]>self.sl_long_price: print('--多头止盈出场datetime+sig: ',dt,'Trailing stop triggered: Closing position','TR',self.out_long,'low', self.low[0]) self.close(data=self.data, price=self.data.close[0],size=self.Lots, exectype=bt.Order.Market) self.long_trailing_stop_price = 0 self.sl_long_price=0 self.out_long=0 self.pos = 0 if self.out_short>0: if self.high[0] > self.out_short and self.pos<0 and self.sl_shor_price>0 and self.high[0]0 and self.fixed_stop_loss_L>0 and self.pos > 0 and self.closes[0] < self.fixed_stop_loss_L: print('--多头止损datetime+sig: ', dt, 'Fixed stop loss triggered: Closing position', 'SL', self.fixed_stop_loss_L, 'close', self.closes[0]) self.close(data=self.data, price=self.data.close[0],size=self.Lots, exectype=bt.Order.Market) self.long_trailing_stop_price = 0 self.sl_long_price=0 self.out_long = 0 self.pos = 0 self.fixed_stop_loss_S = self.sl_shor_price * (1 + self.fixed_stop_loss_percent) if self.sl_shor_price>0 and self.fixed_stop_loss_S>0 and self.pos < 0 and self.closes[0] > self.fixed_stop_loss_S: print('--空头止损datetime+sig: ', dt, 'Fixed stop loss triggered: Closing position', 'SL', self.fixed_stop_loss_S, 'close', self.closes[0]) self.close(data=self.data, price=self.data.close[0], size=self.Lots,exectype=bt.Order.Market) self.short_trailing_stop_price = 0 self.sl_shor_price=0 self.out_short = 0 self.pos = 0 # 更新最高价和最低价的列表 self.datetime_list.append(dt) self.high_list.append(self.data.high[0]) self.low_list.append(self.data.low[0]) self.close_list.append(self.data.close[0]) self.opens_list.append(self.data.open[0]) self.deltas_list.append(self.data.delta[0]) # 计算delta累计 self.delta_cumsum.append(sum(self.deltas_list)) # 将当前行数据添加到 DataFrame # new_row = { # 'datetime': dt, # 'high': self.data.high[0], # 'low': self.data.low[0], # 'close': self.data.close[0], # 'open': self.data.open[0], # 'delta': self.data.delta[0], # 'delta_cumsum': sum(self.deltas_list) # } # # 使用pandas.concat代替append # self.df = pd.concat([self.df, pd.DataFrame([new_row])], ignore_index=True) # # 检查文件是否存在 # csv_file_path = f"output.csv" # if os.path.exists(csv_file_path): # # 仅保存最后一行数据 # self.df.tail(1).to_csv(csv_file_path, mode='a', header=False, index=False) # else: # # 创建新文件并保存整个DataFrame # self.df.to_csv(csv_file_path, index=False) # if run_kg==False : # # # 构建delta的正数和负数 # positive_nums = [x for x in self.data.delta if x > 0] # negative_nums = [x for x in self.data.delta if x < 0] # positive_sums = [x for x in self.delta_cumsum if x > 0] # negative_sums = [x for x in self.delta_cumsum if x < 0] # # # # 开多组合= self.rinei_mean>0 and self.closes[0]>self.rinei_mean and self.signal[0] >1 and self.data.delta[0]>1000 and self.delta_cumsum[-1]>1500 # # 开空组合= self.rinei_mean>0 and self.closes[0]0 and self.closes[0]>self.rinei_mean and self.signal[0] > 1 and self.data.delta[0]>max(self.data.delta[-60:-1]) #and self.delta_cumsum[-1] > np.max(self.delta_cumsum[-61:-2]) #np.mean(self.data.delta_cumsum[-61:-2]) # 开空组合= self.rinei_mean>0 and self.closes[0]0 and self.closes[0]>self.rinei_mean and self.signal[0] >1 and self.data.delta[0]>1500 and self.delta_cumsum[-1]>2000 开空组合= self.rinei_mean>0 and self.closes[0]1 平空条件=self.pos>0 and self.signal[0] <-1 if self.pos !=1 : # if 平多条件: #print('datetime+sig: ', dt, 'Fixed stop loss triggered: Closing position', 'SL', self.fixed_stop_loss_S, 'close', self.closes[0]) self.close(data=self.data, price=self.data.close[0], exectype=bt.Order.Market) self.short_trailing_stop_price = 0 self.sl_shor_price=0 self.out_short = 0 self.pos = 0 if 开多组合 : # self.buy(data=self.data, price=self.data.close[0], size=1, exectype=bt.Order.Market) self.pos=1 self.long_trailing_stop_price=self.low[0] self.sl_long_price=self.data.open[0] #print('datetime+sig: ',dt,' sig: ',self.signal[0],'保存多头价格: ',self.long_trailing_stop_price) if self.pos !=-1 : # if 平空条件: #print('datetime+sig: ', dt, 'Fixed stop loss triggered: Closing position', 'SL', self.fixed_stop_loss_L, 'close', self.closes[0]) self.close(data=self.data, price=self.data.close[0], exectype=bt.Order.Market) self.long_trailing_stop_price = 0 self.sl_long_price=0 self.out_long = 0 self.pos = 0 if 开空组合: # self.sell(data=self.data, price=self.data.close[0], size=1, exectype=bt.Order.Market) self.pos=-1 self.short_trailing_stop_price=self.high[0] self.sl_shor_price=self.data.open[0] #print('datetime+sig: ',dt,' sig: ',self.signal[0],'保存空头价格: ',self.short_trailing_stop_price) if __name__ == "__main__": #公众号:松鼠Quant #主页:www.quant789.com #本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!! #版权归松鼠Quant所有,禁止转发、转卖源码违者必究。 # 创建Cerebro实例 cerebro = bt.Cerebro() #数据 csv_file='./tick生成的OF数据-own/back_ofdata_dj.csv' # # 从CSV文件加载数据 data = GenericCSV_SIG( dataname=csv_file, fromdate=datetime(2023,1,1), todate=datetime(2023,12,29), timeframe=bt.TimeFrame.Minutes, nullvalue=0.0, dtformat='%Y-%m-%d %H:%M:%S', datetime=0, high=3, low=4, open=2, close=1, volume=5, openinterest=None, sig=6, delta=8 ) # 添加数据到Cerebro实例 cerebro.adddata(data) # 添加策略到Cerebro实例 cerebro.addstrategy(MyStrategy_固定止损_跟踪止盈) # 添加观察者和分析器到Cerebro实例 #cerebro.addobserver(bt.observers.BuySell) cerebro.addobserver(bt.observers.Value) cerebro.addanalyzer(bt.analyzers.Returns, _name='returns') cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown') cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades') 初始资金=10000 cerebro.broker.setcash(初始资金) # 设置初始资金 #手续费,单手保证金,合约倍数 cerebro.broker.setcommission(commission=14, margin=5000.0,mult=10)#回测参数 # 运行回测 result = cerebro.run() # 获取策略分析器中的结果 analyzer = result[0].analyzers total_trades = analyzer.trades.get_analysis()['total']['total'] winning_trades = analyzer.trades.get_analysis()['won']['total'] # 获取TradeAnalyzer分析器的结果 trade_analyzer_result = analyzer.trades.get_analysis() # 获取总收益额 total_profit = trade_analyzer_result.pnl.net.total if total_trades > 0: win_rate = winning_trades / total_trades else: win_rate = 0.0 # 打印回测报告 print('回测报告:') print('期初权益', 初始资金) print('期末权益', 初始资金+round(total_profit)) print('盈亏额', round(total_profit)) print('最大回撤率,', round(analyzer.drawdown.get_analysis()['drawdown'],2),'%') print('胜率,', round(win_rate*100,2),'%') print("交易次数,", total_trades) print("盈利次数,", winning_trades) print("亏损次数,", total_trades - winning_trades) print('总手续费+滑点,', 手续费汇总) 手续费汇总=0 # 设置中文显示 mpl.rcParams["font.sans-serif"] = ["SimHei"] mpl.rcParams["axes.unicode_minus"] = False # 保存回测图像文件 plot = cerebro.plot()[0][0] plot_filename = os.path.splitext(os.path.basename(csv_file))[0] +'ss'+ '_plot.png' plot_path = os.path.join('部分回测报告', plot_filename) plot.savefig(plot_path) #公众号:松鼠Quant #主页:www.quant789.com #本策略仅作学习交流使用,实盘交易盈亏投资者个人负责!!!