量化交易backtrader实践(二)_基础加强篇(3)_策略类实践进阶

通过前面几节的实践,我们已经对股票数据的获取,pandasData数据的格式处理,到bactrader的DATAS结构以及里面的data的数据结构,以及在init和next函数中如何读取和计算都有了比较清晰的认知。我们通过一个最简的回测系统,验证了添加2个策略,添加多个股票data,以及将1个策略同时应用于多个股票的功能,也封装了运行函数从而实现多策略和多股票的循环回测。

接着,我们需要仔细看一下Strategy这个策略类。

01_策略类(Strategy)

001_从最简回测系统开始

还是先运行一下数据准备的代码,在notebook的workspace里获取5支股票的数据。

# Notebook 数据准备
import akshare as ak
import pandas as pddef get_df_from_stock(code1):stock_df = ak.stock_zh_a_hist(symbol=code1, period="daily", adjust="qfq")stock_df.rename(columns={'日期':'date', '开盘':'open', '收盘':'close','最高':'high', '最低':'low',  '成交量':'volume',},inplace=True)stock_df.index = pd.to_datetime(stock_df.date)stock_df['openinterest'] = 0stock_df = stock_df[['open','high','low','close','volume','openinterest']]return stock_dfmyStockList = ['001287','002179','600860','300233','002774']
df_stock_list = []for x in myStockList:df_tmp = get_df_from_stock(x)df_stock_list.append(df_tmp)

然后是我们的最简回测代码,这个是确认可以运行成功并绘制出图像的,只不过它不产出任何的文本信息,开始和结束的资产打印是run_main_plot函数在cerebro.run()前后通过语句print(cerebro.broker.getvalue())出来的,与SmaCross_s1这个类没有关系。

from datetime import datetime
import backtrader as btclass SmaCross_s1(bt.Strategy):   # 双均线策略def __init__(self):self.fast_ma = bt.indicators.SMA(self.data.close, period=5)self.slow_ma = bt.indicators.SMA(self.data.close, period=20)self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma)def next(self):if not self.position:if self.crossover > 0:self.buy() else:if self.crossover < 0:self.close()run_main_plot_01(SmaCross_s1,df_stock_list[2], myStockList[2])

002_Strategy类进阶

从最简策略看到,只要有__init__()和next()函数,就可以进行策略的回测,就会有交易发生,就可以有图像输出,但Strategy类可不止这么一点东西。

比如说买了什么,买了多少,订单怎么被执行的,一买一卖交易后盈亏多少,回测什么时候开始的,开始前做了哪些预备工作,什么时候结束的,结束时可以做哪些操作等。

点击进入源码,查看outlook,可以看到Strategy下的所有函数:

我们可以先忽略掉下滑线开头的(内部)函数,以及一系列的getxxx函数,然后再看一下源码,发现里面有一些虚函数等着用户去重写,例如

    def start(self):'''Called right before the backtesting is about to be started.'''passdef stop(self):'''Called right before the backtesting is about to be stopped'''passdef notify_cashvalue(self, cash, value):'''Receives the current fund value, value status of the strategy's broker'''passdef notify_fund(self, cash, value, fundvalue, shares):'''Receives the current cash, value, fundvalue and fund shares'''passdef notify_order(self, order):'''Receives an order whenever there has been a change in one'''passdef notify_trade(self, trade):'''Receives a trade whenever there has been a change in one'''passdef notify_store(self, msg, *args, **kwargs):'''Receives a notification from a store provider'''pass

接着,我们再通过AI助手“请提供一个backtrader的典型strategy类,包括一般所需要的函数重写”得到一个比较全面的程序代码,再进行分类:

(1)params:全局参数,可选,用于更改交易策略中变量/参数的值,可用于参数调优。

(2)log:日志,可选,用于记录策略的执行日志。

(3)init:用于初始化交易策略,在其中声明的指标会在next()方法调用之前进行计算。

(4)notify_order,可选,用于跟踪交易订单(order)的状态。

(5)notify_trade,可选,用于跟踪交易的状态,已平仓的交易将报告毛利和净利润。

(6)next,必选,用于制定交易策略的函数,策略模块最核心的部分

(7)stop, 可选,用于回测结束后输出结果

 A_参数params与log

参数params

一般情况而言,这里的参数主要是给指标使用的,比如前面的双均线策略,就有2个参数,可以把它们放到Params里,然后在init中给indicators的参数使用。

class SmaCross_s1(bt.Strategy):params = (('fast', 5),('slow', 10),)def __init__(self):self.fast_ma = bt.indicators.SMA(self.data.close, period=self.params.fast)self.slow_ma = bt.indicators.SMA(self.data.close, period=self.params.slow)

这种方式跟股票软件公式里的参数设置功能类似,比如下面这个KDJ的公式,就放了三个参数N,M1,M2,这样在使用时,用户可以在不更改公式的情况下,直接设置参数更改。

 而在backtrader里,设置了参数后还可以进行参数优化,也就是对多种参数的组合回测。

##### 参数及默认值设置
params = (('myparam', 20),
)#################  参数优化时使用 optstrategy
cerebro.optstrategy(TestOpt1, myparam=range(15,30) )    

一般情况下参数的名称设置要根据指标来,比如上面的双均线,参数名就是“fast”和"slow",又比如MACD指标的参数,就可以设置为period_me1,period_me2,period_signal,对于内置的指标indicators,可以直接从官网的indicators-Reference而去查找到对应的例如MACD下的Params。

除了指标的参数以外,由于params是全局的,我们就可以利用它来记录或计算一些东西,比如说将来我们会有很多的回测策略,也很有可能连续对多个策略多支股票进行循环回测,那么就需要在最后输出列表中显示出每一次的股票名称以及策略名称,这样的情况下,我们可以直接给所有的策略都添加一个strategy_name的参数。

class SmaCross_s1(bt.Strategy):params = (('strategy_name','双均线策略'),   # 用参数记录策略的name('fast', 5),('slow', 10),)
记录log

这里AI提供的示例代码我们可以直接用,都不需要修改。

class MyStrategy(bt.Strategy):params = (('maperiod', 15),('printlog', False),)def log(self, txt, dt=None, doprint=False):''' Logging function for this strategy'''if self.params.printlog or doprint:dt = dt or self.datas[0].datetime.date(0)print(f'{dt.isoformat()}, {txt}')

代码中设置了一个printlog的参数,用于双重控制是否打印,另一重控制在log函数的参数doprint中。对于文本输出,有些时候我们很需要它,比如要看每笔成交的价格,手续费多少,每次交易的收益等,但有些时候用不到它,如果进行了10支股票的5个策略的回测,有50组结果,这个时候我们需要的是一张统计表,而不需要每笔详单。因此,就需要对log进行参数设置,可以打印也可以在不需要的时候不打印。

根据log的代码,可知它其实就是把需要显示的字符串打印出来,在字符串前面加上了发生的日期,也就是datetime.date(),大多数情况下,使用默认的dt=None就可以了,但在两个策略或多个股票data的情况下,注意log的时候要给出dt=xxx,否则可能报错。

B_订单notify_order

Order订单模块和Broker经纪商模块是交易相关的核心模块。

  • Cerebro是Backtrader的关键控制中心,是大脑。
  • Strategy是大脑的神经,基于数据和分析做出最终的决策。
  • Order 订单将Strategy的做出的决策转换为由券商(Broker)执行操作的消息。

在Strategy类中,通过三种方式进行订单Order的操作,前面类的成员方法截图上也已经看到过

  1. 创建订单 - 函数主要有 3 个:买入 buy() 、卖出 sell()、平仓 close()
  2. 取消订单 - 在strategy中的为cancel()
  3. 通知 - Strategy中定义notify_order函数用于跟踪订单的状态信息

关于订单状态有以下这些:

  • Order.Created:订单已被创建;
  • Order.Submitted:订单已被传递给经纪商 Broker;
  • Order.Accepted:订单已被经纪商接收;
  • Order.Partial:订单已被部分成交;
  • Order.Complete:订单已成交;
  • Order.Rejected:订单已被经纪商拒绝;
  • Order.Margin:执行该订单需要追加保证金,并且先前接受的订单已从系统中删除;
  • Order.Cancelled (or Order.Canceled):确认订单已经被撤销;
  • Order.Expired:订单已到期,其已经从系统中删除 。

看到这里,我们是否发现这个与真实的股票/期货交易是一致的?

以股票交易为例,感觉要上涨了(看多)进行买入(买多)操作,先点击下单按钮,再点击买按钮,然后输入价格,输入数量(A股最小买入单位是手=100股),最后点击红色的买入按钮(Submitted),这个订单就开始生成,但如果你买入的时候资金不足什么的,这个订单就创建失败(Rejected),也就是被拒绝了,你需要重新填价格或数量。

接着,订单再次提交没问题就会被券商接受(Accepted),此时就可以在“当日委托”以及“撤单”页面上看到这个订单。在没有成交之前,我们是可以撤单的(Canceled),而能否成交还要看价格到没到,价格到了还要看你排在什么位置,很多时候我们算准了最低价,但这个价位成交量很低是成交不了的。如果成交了(Completed),订单就算完成了。如果没有成交,当天收盘后这些订单会被自动取消(Expired)。其中Margin是期货这类保证金交易特有的状态,股票交易应该用不到。

为此,我们在AI提供的代码上,加了一句self.log('order.status %s'%order.status在每次进入的时候,在submitted/accepted 以及completed里同样加了显示status的语句。

由输出结果可知,每一个成功的buy/sell都要进到notify_order中来3次,第1次是status1-submit,第2次是status2 - Accept,第3次才是status 4- Completed。并且backtrader的默认流程是以当天收盘价下单,但是以第二日的开盘价进行的成交。这个详细的放到交易设置的set_coc里再做介绍。

因此,如果有一个券商接口,也就可以实现自动交易。不过现阶段先不要着急那些东西,先看看在Notify_order中需要打印哪些东西?对于submitted/accepted而言,真实的交易是会收到弹窗提示的,但没有实质性的数据在这里,所以只需要在Completed的状态里进行一些数据的打印。

from datetime import datetime
import backtrader as btclass SmaCross_s3(bt.Strategy):params = (('fast', 5),('slow', 10),('printlog',True),)def log(self, txt, dt=None, doprint=True):''' Logging function for this strategy'''if self.params.printlog or doprint:dt = dt or self.datas[0].datetime.date(0)print(f'{dt.isoformat()}, {txt}')def __init__(self):self.fast_ma = bt.indicators.SMA(self.data.close, period=self.params.fast)self.slow_ma = bt.indicators.SMA(self.data.close, period=self.params.slow)self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma)def stop(self):print(self.getposition())def next(self):if not self.position:if self.crossover > 0:self.buy() else:if self.crossover < 0:self.sell()def notify_order(self, order):    #记录交易执行情况############################################################ 如果order为submitted/accepted,返回空if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed # 指令为buy/sell,报告价格结果# Attention: broker could reject order if not enough cashif order.status in [order.Completed]:             # 正常能够完成的定单if order.isbuy():self.log('BUY , Price: %.2f, Cost: %.2f, Comm :%.2f, number :%s ,size :%.2f, %s' %(order.executed.price,  #成交价order.executed.value,	#成交额order.executed.comm,	#佣金order.ref,				#订单编号order.executed.size, # 成交量order.data._name)) # 股票名称self.buyprice = order.executed.priceself.buycomm = order.executed.commelse:  # Sellself.log('SELL , Price: %.2f, Cost: %.2f, Comm %.2f, number :%s ,size :%.2f, %s' %(order.executed.price,  #成交价order.executed.value,	#成交额order.executed.comm,	#佣金order.ref,				#订单编号order.executed.size, # 成交量order.data._name)) # 股票名称self.bar_executed = len(self)   # 从执行那根stick开始计数elif order.status in [order.Canceled, order.Margin, order.Rejected]:  #钱不够,买/卖超限等会被终止,拒绝等self.log('Order Canceled/Margin/Rejected')self.order = Nonerun_main_plot_01(SmaCross_s3,df_stock_list[2], myStockList[2])-----------------------
Starting Portfolio Value: 100000.00
2024-02-22, BUY , Price: 9.20, Cost: 49891.54, Comm :49.89, number :92 ,size :5422.99, 600860
2024-03-11, SELL , Price: 11.18, Cost: 49891.54, Comm 60.63, number :93 ,size :-5422.99, 600860
2024-03-21, BUY , Price: 11.70, Cost: 54705.66, Comm :54.71, number :94 ,size :4675.70, 600860
2024-03-28, SELL , Price: 10.50, Cost: 54705.66, Comm 49.09, number :95 ,size :-4675.70, 600860
2024-04-10, BUY , Price: 10.53, Cost: 52307.16, Comm :52.31, number :96 ,size :4967.44, 600860
2024-04-11, SELL , Price: 10.06, Cost: 52307.16, Comm 49.97, number :97 ,size :-4967.44, 600860
2024-04-29, BUY , Price: 10.50, Cost: 55982.91, Comm :55.98, number :98 ,size :5331.71, 600860
Final Portfolio Value: 99327.02

在这里我们可以打印价格,费用,佣金,订单号,股票数量,股票名称等。从数据看出,订单号当前都是连号的数字,股票数量还有小数是不符合A股实际情况的,后续需要更改,cost似乎只记录买单的值,而卖单的cost与对应的买单是一样的,或许这里需要使用另外的参数而不是executed.value吧,如果把value翻译为价值,那么同样数量的股票价值相等似乎也说得过去。另外,佣金有点贵了,千分之一啊!

C_交易notify_trade

notify_trade是交易通知,它要比notify_order要简单很多,于是我们还是直接去源码\backtrader\trade.py,把它的代码贴给AI助手进行解析:

这段代码定义了一个名为 Trade 的类,用于跟踪一笔交易的生命周期,包括交易的规模、价格、佣金以及价值等方面的信息。

基本属性

  1. ref:唯一的交易标识符。
  2. status:交易的状态,可以是创建(Created)、打开(Open)、关闭(Closed)。
  3. tradeid:用于标识交易组的 ID,默认为 0。
  4. size:当前交易的规模(正数表示多头,负数表示空头)。
  5. price:当前交易的价格。
  6. value:当前交易的价值。
  7. commission:累计的佣金。
  8. pnl:当前的利润与亏损(毛利润)。
  9. pnlcomm:扣除佣金后的利润与亏损(净收益)。

交易状态标志

  1. isclosed:布尔值,记录最后的更新是否关闭了交易(即规模归零)。
  2. isopen:布尔值,记录任何更新是否打开了交易。
  3. justopened:布尔值,如果交易刚刚打开。

时间相关属性

  1. baropen:交易打开所在的 K 线编号。
  2. dtopen:交易打开时的日期时间(浮点数编码,可以转换为 Python 的 datetime 对象)。
  3. barclose:交易关闭所在的 K 线编号。
  4. dtclose:交易关闭时的日期时间(浮点数编码,可以转换为 Python 的 datetime 对象)。

其他属性

  1. barlen:交易保持打开状态的 K 线数量。
  2. historyon:布尔值,指示是否记录历史。
  3. history:列表,记录每次更新事件的结果状态和参数。列表的第一个条目是打开事件,最后一个条目是关闭事件。

所以对于trade来说,是开仓后需要平仓才算结束isclosed,它会记录每笔交易的price, size, value, 佣金,因此可以得到pnl毛利润和pnlcomm净利润,同时要注意它的状态是open还是close,因为交易的利润需要closed的才能计算,open的计算的是浮盈或者浮亏,是不确定的。

在notify_trade中,我们可以调用trade类的方法或成员查看交易信息,从下面的输出结果看到,开仓并平仓的会记为1次交易trade,price,comm,pnl,pnlcomm等都有记录,且有开仓的日期以及是总数据中的第几天等;而最后一笔买入没有对应的卖出,不输出trade信息(在程序中return了)。

    def notify_trade(self, trade):if not trade.isclosed:returnself.log(f'OPERATION PROFIT, GROSS {trade.pnl:.2f}, NET {trade.pnlcomm:.2f}')print(trade.__str__())----------------------------
2024-02-22, BUY EXECUTED, Price: 9.20, Cost: 49891.54, Comm :49.89, number :115 ,size :5422.99, 600860
2024-03-11, SELL EXECUTED, Price: 11.18, Cost: 49891.54, Comm 60.63, number :116 ,size :-5422.99, 600860
2024-03-11, OPERATION PROFIT, GROSS 10737.53, NET 10627.01   # self.log()的内容ref:67
data:<backtrader.feeds.pandafeed.PandasData object at 0x000002D141D63A50>
tradeid:0
size:0.0
price:9.2                       # price
value:0.0
commission:110.52060737527114   # comm
pnl:10737.527114967465          # pnl
pnlcomm:10627.006507592194      # pnlcomm
justopened:False
isopen:False                    # isopen
isclosed:True                   # isclosed
baropen:23
dtopen:738938.0                 # 开仓日期
barclose:35
dtclose:738956.0                # 平仓日期
barlen:12
historyon:False
history:[]
status:2............
2024-04-29, BUY EXECUTED, Price: 10.50, Cost: 55982.91, Comm :55.98, number :121 ,size :5331.71, 600860
Final Portfolio Value: 99327.02

 如果我们对于not trade.isclosed的情况也进行输出,那么输出的结果是这样的:

    def notify_trade(self, trade):if not trade.isclosed:print(trade.__str__())  # open 交易也打印信息returnself.log(f'OPERATION PROFIT, GROSS {trade.pnl:.2f}, NET {trade.pnlcomm:.2f}')    ---------------------2024-04-29, BUY EXECUTED, Price: 10.50, Cost: 55982.91, Comm :55.98, number :142 ,size :5331.71, 600860
ref:82
data:<backtrader.feeds.pandafeed.PandasData object at 0x000002D1421E43D0>
tradeid:0
size:5331.706119018024
price:10.5
value:55982.91424968925
commission:55.98291424968926
pnl:0.0
pnlcomm:-55.98291424968926
justopened:True
isopen:True                            # isopen = True
isclosed:False
baropen:68
dtopen:739005.0
barclose:0
dtclose:0.0
barlen:0
historyon:False
history:[]
status:1                               # open的trade status为1
Final Portfolio Value: 99327.02

接着,如果在后面的notify_trade中,我们想要在每笔交易完成的时候,打印出参数的配置和收益(其实这些在添加了评价后result里都有,但是参数优化的评价数据类型非常的奇怪,当前的进度还没有到Analyzer评价的部分,因此我们通过参数params,trade以及stop()来进行简单的计算和评价,不论是单个回测还是参数优化都可以得到参数配置和收益。甚至,我们还可以把最后买入但没有卖出的跟当前价格进行比较来计算浮动盈亏,并把浮盈的也暂时计算入成功率。

D_开始与结束start_stop

start()的功能是在回测开始前进行一些工作,一般不使用,stop()是在整个回测结束的时候进行的一些工作。由于在一次回测里可能有很多买入卖出的order和trade,但结束的时候我们希望记录它们的累计收益,成功率,盈亏比等,就需要灵活运用stop()了。

首先,在params里创建几个用于记录交易次数和成功次数的参数

class SmaCross_s4(bt.Strategy):params = (('stra_name','双均线策略s4_打印简单评价'),('fast', 5),('slow', 10),('printlog',False),('tradeCnt',0),('sucessCnt',0),)

其中tradeCnt是交易次数计数,只把closed的交易进行计数,同时在交易closed时计算pnlcomm是否大于0来判断这次的交易是否成功,成功则把sucessCnt计数增加。

    def notify_trade(self, trade):self.mytrade = tradeif not trade.isclosed:returnself.params.tradeCnt += 1if trade.pnlcomm > 0:self.params.sucessCnt +=1self.log(f'OPERATION PROFIT, 毛利润 {trade.pnl:.2f}, 净利润 {trade.pnlcomm:.2f}')

最后到stop()函数中来,通过self.mytrade 把最后notify_trade的数据获取到,通过isopen是否为True先确认到目前为止是否有未平仓的交易,计算浮动盈亏,最后把浮动盈亏也计入交易成功率计算。

    def stop(self):trade = self.mytradesucessPct = self.params.sucessCnt/self.params.tradeCnt * 100.0if trade.isopen == True:     # 有开仓计算浮盈亏winlose_stat = '浮盈' if trade.price<= self.data.close[0] else '浮亏'winlose_val = (self.data.close[0]-trade.price)*trade.sizewinlose_suc_pct = (self.params.sucessCnt+1)/(self.params.tradeCnt+1) if winlose_val >0 \else self.params.sucessCnt/(self.params.tradeCnt+1)self.log("%s,参数(%2d,%2d,%2d), 期末总资金 %.2f  盈利为 %.2f" % (self.params.stra_name,self.params.fast,self.params.slow,0,self.broker.getvalue(),self.broker.getvalue()-100000))self.log("总共交易次数为 %2d ,交易成功率为 %.1f%%," % (self.params.tradeCnt,sucessPct)) self.log("当前开仓状态,%s %.2f,浮动交易成功率为%.1f%%"%(winlose_stat, winlose_val,winlose_suc_pct*100))else:   # 已平仓直接输出sucessPct = self.params.sucessCnt/self.params.tradeCntself.log("%s,参数(%2d,%2d,%2d), 期末总资金 %.2f  盈利为 %.2f" % (self.params.stra_name,self.params.fast,self.params.slow,0,self.broker.getvalue(),self.broker.getvalue()-100000))self.log("总共交易次数为 %2d ,交易成功率为 %.1f%%," % (self.params.tradeCnt,sucessPct)) 

然后运行回测就能得到一个简单的评价结果,例如

run_main_plot_01(SmaCross_s4,df_stock_list[1], myStockList[1])----------------------------
Starting Portfolio Value: 100000.00
2024-01-30, 买入, No.002179, Price: 31.28, Cost: 49337.54, Comm :49.34,size :1577.29
2024-02-02, 卖出, Price: 30.85, Comm 48.66
2024-02-02, OPERATION PROFIT, 毛利润 -678.23, 净利润 -776.232024-02-19, 买入, No.002179, Price: 34.60, Cost: 49958.42, Comm :49.96,size :1443.88
2024-02-29, 卖出, Price: 32.80, Comm 47.36
2024-02-29, OPERATION PROFIT, 毛利润 -2598.99, 净利润 -2696.312024-03-05, 买入, No.002179, Price: 33.51, Cost: 47977.38, Comm :47.98,size :1431.73
2024-03-28, 卖出, Price: 33.17, Comm 47.49
2024-03-28, OPERATION PROFIT, 毛利润 -486.79, 净利润 -582.262024-04-22, 买入, No.002179, Price: 32.30, Cost: 48271.50, Comm :48.27,size :1494.472024-05-10, 双均线策略s4_打印简单评价,参数( 5,10, 0), 期末总资金 100798.80  盈利为 798.80
2024-05-10, 总共交易次数为  3 ,交易成功率为 0.0%,
2024-05-10, 当前开仓状态,浮盈 4901.87,浮动交易成功率为25.0%
Final Portfolio Value: 100798.80

从输出结果上看,一共有3组买入卖出,第4次只有买入,最后打印了策略名称,参数配置和交易次数及成功率等,由于最后1笔只有买入,其trade.isopen为True,于是计算得到买入到当前是浮盈4901块,从而总交易次数为4,成功1次,得到浮动交易成功率为25%。

这里发现了一个问题,也就是如果每个策略用到不同的指标,其参数名会不一样,那么每一个策略的stop()里的最后self.log()中,参数名就会变化,就需要重新写。而我们想偷懒,准备是除了init和next之外全部不会改动,于是基于这个我们把指标参数的设置改为了和股票软件类似的表示方法(N,M1,M2...)这样,指标参数就都定义为p1,p2,p3,p4,一般3个参数足够用了。

003_继承Strategy类与偷懒

前面说到我们想偷懒,准备除了init和next这两个关键函数外,其他的都不改动代码。所以可以简单的用继承和重写来解决。

我们只需要写一个类BaseSt,继承自bt.Strategy,在里面把log, notify_order, notify_trade, stop等其他函数完成重写即可。

然后在新创建策略的时候,让它继承自BaseSt,这样就只需要把聚焦于init和next这些核心就可以了。例如就拿上面的SmaCross_s4稍做一些修改做继承,在mystrategy5的类里,就不需要再写额外的代码,只需要params上稍做修改,init和next里根据指标策略需求制作即可:

class mystrategy5(SmaCross_s4):params = (('stra_name','布林经典策略'),('p1', 20),('p2', 2),('printlog',False),('tradeCnt',0),('sucessCnt',0),)def __init__(self):self.order = NoneBOLL1 = bt.indicators.BBands(self.data,period =self.p.p1, devfactor=self.p.p2)bot = BOLL1.l.bottop = BOLL1.l.topself.crsup = bt.indicators.CrossUp(self.data.close,bot)self.crsdn = bt.indicators.CrossDown(top,self.data.close)def next(self):if not self.position:if self.crsup > 0:self.buy() else:if self.crsdn > 0:self.sell()run_main_plot_01(mystrategy5,df_stock_list[1], myStockList[1])------------------------
Starting Portfolio Value: 100000.00
2022-03-22, 买入, No.002179, Price: 44.52, Cost: 50000.00, Comm :50.00,size :1123.09
2022-06-06, 卖出, Price: 46.42, Comm 52.13
2022-06-06, OPERATION PROFIT, 毛利润 2133.87, 净利润 2031.742022-08-02, 买入, No.002179, Price: 47.08, Cost: 51299.17, Comm :51.30,size :1089.62
2022-09-15, 卖出, Price: 50.28, Comm 54.79
2022-09-15, OPERATION PROFIT, 毛利润 3486.77, 净利润 3380.692022-10-12, 买入, No.002179, Price: 46.18, Cost: 52366.03, Comm :52.37,size :1133.95
2023-06-30, 卖出, Price: 43.75, Comm 49.61
2023-06-30, OPERATION PROFIT, 毛利润 -2755.51, 净利润 -2857.492023-08-02, 买入, No.002179, Price: 42.76, Cost: 51313.47, Comm :51.31,size :1200.03
2023-08-25, 卖出, Price: 43.38, Comm 52.06
2023-08-25, OPERATION PROFIT, 毛利润 744.02, 净利润 640.652023-09-25, 买入, No.002179, Price: 41.90, Cost: 51597.80, Comm :51.60,size :1231.45
2024-02-19, 卖出, Price: 34.60, Comm 42.61
2024-02-19, OPERATION PROFIT, 毛利润 -8989.59, 净利润 -9083.802024-04-16, 买入, No.002179, Price: 32.29, Cost: 47085.06, Comm :47.09,size :1458.19
2024-05-08, 卖出, Price: 34.72, Comm 50.63
2024-05-08, OPERATION PROFIT, 毛利润 3543.41, 净利润 3445.702024-07-10, 买入, No.002179, Price: 36.12, Cost: 48684.40, Comm :48.68,size :1347.852024-09-11, 布林经典策略,参数(20, 2, 0), 期末总资金 100096.68  盈利为 96.68
2024-09-11, 总共交易次数为  6 ,交易成功率为 66.7%,
2024-09-11, 当前开仓状态,浮盈 2587.87,浮动交易成功率为71.4%
Final Portfolio Value: 100096.68

由输出结果可以看到,在这个类里的代码量非常少,但仍然实现了前面的简单评价的功能,这里6组有买入和卖出的交易,1笔只有买入的交易,持仓至今浮盈2587块,浮动交易成功率为71.4%。

如果在参数优化里,由于参数组合比较多而不希望打印出order和trade的详单,则我们可以再做一个baseOptStrategy的类,在其中把notify_order和notify_trade里的log去掉,然后创建策略的时候再继承自这个baseOptStrategy就可以了。看起来我们的偷懒是成功的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/54178.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# 结合 Javascript 测试获取天气信息

目录 测试效果 范例运行环境 关键代码 C#获取网页数据 前端代码 JavaScript 实现 总结 测试效果 获取一些简单的天气信息&#xff0c;可以丰富我们的应用系统&#xff0c;比如开发一个小桌面&#xff0c;小组件&#xff0c;增加一些实用性的系统功能&#xff0c;本文将…

neo4j安装为服务+配置环境变量

目录 neo4j安装为服务 windows services 参照JDK&#xff0c;将neo4j加入到环境变量 neo4j安装为服务 windows services 我的上一篇文章详细写明了如何安装启动neo4j《neo4j安装启动教程对应的jdk配置》&#xff0c;文末的启动neo4j是通过cmd命令行访问bin目录&#xff0c;这…

Git+Jenkins 基本使用(Basic Usage of Git+Jenkins)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

Java企业面试题2

1.语言的分代&#xff1a; 第1代&#xff1a;机器语言 机器语言是最底层的计算机编程语言&#xff0c;它是由二进制数构成的一系列指令&#xff0c;直接与计算机硬件交互。每个二进制位模式代表一条特定的指令或数据地址。因为它是直接在硬件上执行的&#xff0c;所以运行效率…

诚邀见证2024九章云极DataCanvas算力包产品发布会!

算力&#xff0c;是驱动全球智能化升级的关键力量&#xff0c;也是智算经济腾飞的主要燃料。 在智算经济腾起之际&#xff0c;我们洞察未来:算力不仅是生产力还将作为社会性普惠AI资源。我们思考未来:算力产品和模式需要何种创新才能够加速算力普惠的进程?我们定义未来:用单位…

react学习笔记一:react介绍

将view规划成一个个的组件&#xff0c;是一个响应式的声明式的设计。 虚拟dom&#xff0c;减少dom操作。vue的虚拟dom是在react的基础上拓展来的。 单向数据流&#xff1a;是一种数据流动的模式。数据流的方向是有上到下的&#xff0c;在react中主要是从父组件流向子组件。 …

计算机毕业设计 基于SpringBoot框架的网上蛋糕销售系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【微处理器系统原理与应用设计第十三讲】通用同/异步收发器USART轮询模式应用设计

USART提供两设备之间的串行双工通信&#xff0c;并支持中断和DMA工作。采用轮询、中断和DMA三种方式进行数据收发。 一、功能需求 实现远程串行通信数据的回传确认。微处理器系统构成的测控设备通过USART&#xff08;串口&#xff09;与用户设备&#xff08;上位机&#xff0…

【Python篇】Python 函数综合指南——从基础到高阶

文章目录 Python 函数综合指南1. 函数介绍1.1 什么是函数&#xff1f;1.2 定义函数示例&#xff1a;1.3 调用函数1.4 函数参数1.4.1 必需参数1.4.2 默认参数1.4.3 关键字参数1.4.4 可变长度参数 2. Python 内置函数2.1 字符串处理函数示例&#xff1a; 2.2 数学函数示例&#x…

基于Spring Boot的能源管理系统+建筑能耗+建筑能耗监测系统+节能监测系统+能耗监测+建筑能耗监测

介绍 建筑节能监测系统是基于计算机网络、物联网、大数据和数据可视化等多种技术融合形成的一套节能监测系统。 系统实现了对建筑电、水、热&#xff0c;气等能源、资源消耗情况的实时监测和预警、动态分析和评估&#xff0c;为用户建立了科学、系统的节能分析方法&#xff0c…

如何让大模型更好地进行场景落地?

自ChatGPT模型问世后&#xff0c;在全球范围内掀起了AI新浪潮。 有很多企业和高校也随之开源了一些效果优异的大模型&#xff0c;例如&#xff1a;Qwen系列模型、MiniCPM序列模型、Yi系列模型、ChatGLM系列模型、Llama系列模型、Baichuan系列模型、Deepseek系列模型、Moss模型…

简单题28-找出字符传中第一个匹配项的下标(Java and Python)20240918

问题描述&#xff1a; Java代码&#xff1a; class Solution {public int strStr(String haystack, String needle) {int n1 haystack.length();int n2 needle.length();if (n2 0) {return 0; // 如果 needle 为空字符串&#xff0c;直接返回 0}if (n1 < n2) {return -…

LeetCode[中等] 142. 环形链表 II

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整…

LeetCode[中等] 438. 找到字符串中所有字母异位词

给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#xff08;包括相同的字符串&#xff09;。 思路&#xff1a;滑动窗口 s包含p的异位词 ——> 则…

大数据新视界 --大数据大厂之数据科学项目实战:从问题定义到结果呈现的完整流程

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

组合逻辑电路的设计

目录 基本设计步骤 应用实例1&#xff08;三人表决电路&#xff09; 逻辑抽象 列真值表 化简&#xff08;卡诺图&#xff09; 转化为与非表达式 画逻辑图 应用实例2&#xff08;二进制转换为格雷码&#xff09; 逻辑抽象 列真值表 卡诺图化简 画逻辑图 基本设计…

【rust】rust条件编译

在c语言中&#xff0c;条件编译是一个非常好用的功能&#xff0c;那么rust中如何实现条件编译呢? rust的条件编译需要两个部分&#xff0c;一个是fratures&#xff0c;另一个是cfg。Cargo feature是一个非常强大的功能&#xff0c;可以提供条件编译和可选依赖项的高级特性&…

C++ -命名空间-详解

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【C】 欢迎点赞&#x1f44d;收藏⭐关注❤️ C -命名空间-详解 1.C语言缺点之一 -- 命名冲突2.命名空间2.1定义2.2使用访问命名空间中的变量展开命名空间域指定访问命名空间域 2.3其他功能 3.C 标准库中的命名空间指定展开…

云计算实训50——Kubernetes基础命令、常用指令

一、Kubernetes 自动补齐 # 安装自动补齐软件 [rootmaster ~]# yum -y install bash-completion # 临时开启自动补齐功能 [rootmaster ~]# source # 永 久开启自动补齐功能 [rootmaster ~]# echo "source > ~/.bashrc 二、Kubernetes 基础命令 kubectl [command] …

Linux:进程(二)

目录 一、cwd的理解 二、fork的理解 1.代码共享 2.各司其职 3.fork的返回值 三、进程状态 1.进程排队 2.进程状态 运行状态 一、cwd的理解 cwd&#xff08;current working directory&#xff09;。译为当前工作目录。 在C语言中&#xff0c;使用fopen函数打开文件时&…