在 backtrader 上运行回测,我收到了很多不应该取消的买单/保证金/拒绝

问题描述

在回溯测试中,许多买单被取消,我找不到原因。查看 Writefile 日志,似乎购买订单是在第二天收盘时创建的。在大多数情况下,买入价在当天的范围内,但仍未执行。

我尝试了不同的资产、不同大小的数据馈送、不同的策略,但结果都一样。

在 Jupyter 笔记本上运行。我包括代码和日志。

最后,我将 AllInSizerInt() 中的认参数 ('100') 更改为低于 100 并且它起作用了。我真的不明白为什么,我以为sizer会从经纪人那里得到现金头寸并调整订单。

修复方法如下:

''''
python
#Add the sizer.  We plan to go 'all in' every time  
cerebro.addsizer(bt.sizers.AllInSizerInt,percents = 95)  

''''

这是原始代码

''''
python
#### Import databases
from datetime import datetime
import backTrader as bt
import backTrader.indicators as btind

abspath = '/mypath/'
logfile = 'abs_momentum.csv'
# Create a Strategy

class Abs_momentum(bt.Strategy):
    alias = ('Abs_momentum',)
    params = (
    # period for momentum calculation
    ('lookback',252),('range',100))
    

def __init__(self):
    # keep track of close price in the series
    self.data_close = self.datas[0].close

    # keep track of pending orders/buy price/buy commission
    self.order  = None
    self.price  = None
    self.comm   = None

    # add a momentum indicator
    self.mom    = btind.MomentumOsc(self.data_close,\
                                    period = self.p.lookback,\
                                    band = self.p.range)

    self.buysig = self.mom
    
    
def log(self,txt,dt=None):
    ''' Logging function fot this strategy'''
    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]:
        # Buy/Sell order submitted/accepted to/by broker - nothing to do
        return

    # Check if an order has been completed
    # Attention: broker Could reject order if not enough cash
    if order.status in [order.Completed]:
        if order.isbuy():
            self.log(
                'BUY EXECUTED,Price: %.2f,Cost: %.2f,Comm %.2f' %
                (order.executed.price,order.executed.value,order.executed.comm))

            self.buyprice = order.executed.price
            self.buycomm = order.executed.comm
        else:  # Sell
            self.log('SELL EXECUTED,Comm %.2f' %
                     (order.executed.price,order.executed.comm))

        #self.bar_executed = len(self)

    elif order.status in [order.Canceled,order.Margin,order.Rejected]:
        self.log('Order Canceled/Margin/Rejected')

    self.order = None

def notify_Trade(self,Trade):
    if not Trade.isclosed:
        return

    self.log('OPERATION PROFIT,GROSS %.2f,NET %.2f' %
             (Trade.pnl,Trade.pnlcomm))
def next(self):
    
    # do nothing if an order is pending
    if self.order:
        return

    # check if there is already a position
    if not self.position:
        # buy condition
        if self.buysig > 100:
            self.log(f'BUY CREATED --- Price: {self.data_close[0]:.2f}')
            self.order = self.buy(size = None)
    else:
        # sell condition
        if self.buysig < 100:
            self.log(f'SELL CREATED --- Price: {self.data_close[0]:.2f}')
            self.order = self.sell(Size = None)
###
#### Download data from Yahoo Finance
data = bt.Feeds.YahooFinanceData(dataname= 'SPY',\
                             fromdate=datetime(2018,6,15),\
                             todate=datetime(2021,3,17),\
                               reverse = False)
####

# create a Cerebro entity
cerebro = bt.Cerebro(stdstats = False)

### Set up the backtest
# Add the Data Feed to Cerebro
cerebro.adddata(data)

# Set our desired cash start
cerebro.broker.setcash(100000.0)

# Set the commission - 0.1% ... divide by 100 to remove the %
cerebro.broker.setcommission(commission=0.001)

#Add the sizer.  We plan to go 'all in' every time
cerebro.addsizer(bt.sizers.AllInSizerInt)

#Add the strategy
cerebro.addstrategy(Abs_momentum)

#Add the observers to the plot
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)

#Write to a csv file
cerebro.addwriter(bt.WriterFile,out = (abspath + logfile),csv=True,\ data(csv) = False)

# Print out the starting conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything
cerebro.run()

# Print out the final result
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
###

### Plot the results
cerebro.plot(iplot=True,volume=False)
###

''''

这是日志:

>Starting Portfolio Value: 100000.00
>2019-06-18,BUY CREATED --- Price: 282.95
>2019-06-19,Order Canceled/Margin/Rejected
>2019-06-19,BUY CREATED --- Price: 283.58
>2019-06-20,Order Canceled/Margin/Rejected
>2019-06-20,BUY CREATED --- Price: 286.29
>2019-06-21,Order Canceled/Margin/Rejected
>2019-06-21,BUY CREATED --- Price: 285.88
>2019-06-24,BUY EXECUTED,Price: 286.10,Cost: 99848.90,Comm 99.85
>2020-03-12,SELL CREATED --- Price: 243.56
>2020-03-13,SELL EXECUTED,Price: 258.27,Comm 90.14
>2020-03-13,OPERATION PROFIT,GROSS -9712.67,NET -9902.66
>2020-04-17,BUY CREATED --- Price: 283.04
>2020-04-20,Price: 279.06,Cost: 88741.08,Comm 88.74
>2020-04-20,SELL CREATED --- Price: 278.05
>2020-04-21,Price: 273.25,Comm 86.89
>2020-04-21,GROSS -1847.58,NET -2023.21
>2020-04-29,BUY CREATED --- Price: 289.53
>2020-04-30,Order Canceled/Margin/Rejected
>2020-04-30,BUY CREATED --- Price: 286.83
>2020-05-01,Order Canceled/Margin/Rejected
>2020-05-06,BUY CREATED --- Price: 280.68
>2020-05-07,Order Canceled/Margin/Rejected
>2020-05-07,BUY CREATED --- Price: 284.07
>2020-05-08,Order Canceled/Margin/Rejected
>2020-05-08,BUY CREATED --- Price: 288.77
>2020-05-11,Price: 286.69,Cost: 87153.76,Comm 87.15
>Final Portfolio Value: 121189.86

解决方法

您从 100% 规模交易调整到 95% 规模交易纠正了这个问题的原因是,尝试在投资组合上全部投入 100% 总会在此过程中产生一些利润。这可以在您的日志中看到:

>2019-06-19,Order Canceled/Margin/Rejected

问题是您正在计算要交易前收盘价的股票数量。如果下一根柱线的市场价格跳空,您将没有足够的现金购买。有一个名为 cheat-on-open 的选项,它允许在下一个开盘价出现峰值以允许调整股票大小。这有帮助。

但实际上,最好在 100% 以下交易。