在 Backtrader 中使用 MACD 指标的多头和空头策略

long and short strategy with macd indicator in Backtrader

我刚从 Matlab 切换到 python 甚至更新到 backtrader 库来回测交易策略。我的问题似乎很明显。

我的问题似乎与此类似: https://community.backtrader.com/topic/2857/wanted-exit-long-and-open-short-on-the-same-bar-and-vice-versa

还有这个: https://community.backtrader.com/topic/2797/self-close-does-not-clear-position 下面的代码是一个简单的 MACD 策略。 这是代码:

# -*- coding: utf-8 -*-
"""

"""

import backtrader as bt
import argparse
import backtrader.feeds as btFeeds
import numpy as np
import yfinance as yf
import pandas as pd
import talib



class SimpleMACDStrat(bt.Strategy):
    
    def __init__(self):
        #Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close
        self.order = None

    def log(self, txt, dt=None):
        dt = dt or self.datas[0].datetime.date(0)
        print(f'{dt.isoformat()} {txt}')
        #Print date and close
        
    
    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('LONG EXECUTED, %.2f' % order.executed.price)
                
            elif order.issell():
                self.log('SELL EXECUTED, %.2f' % order.executed.price)

            self.bar_executed = len(self)

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

        # Write down: no pending order
        self.order = None
        

    def next(self):
        self.log("Close: '{0}'" .format(self.data.adj_close[0]))
        print('%f %f %f %f %f %f %f %f %f %f %f %f %f' % (self.data.Indexx[0],self.data.open[0],
                                                 self.data.high[0],self.data.low[0],
                               self.data.close[0],self.data.adj_close[0],
                               self.data.volume[0],self.data.EMA_100[0], 
                               self.data.RSI[0], self.data.CCI[0],
                               self.data.MACD_macd[0],self.data.MACD_sign[0],self.data.MACD_hist[0]))

        if self.order:
            return
        
        if self.data.MACD_hist[0]>0:
            if self.position.size<0 and self.data.MACD_hist[-1]<0  :
                     self.close()
                     self.log('CLOSE SHORT POSITION, %.2f' % self.dataclose[0])
       
            elif self.position.size==0:
                    self.order=self.buy()
                    self.log('OPEN LONG POSITION, %.2f' % self.dataclose[0])
                    
                
        elif self.data.MACD_hist[0]<0:
            if self.position.size>0 and self.data.MACD_hist[-1]>0:
                     self.order=self.close()
                     self.log('CLOSE LONG POSITION, %.2f' % self.dataclose[0])
                            
            elif self.position.size==0:
                    self.order=self.sell()
                    self.log('OPEN SHORT POSITION, %.2f' % self.dataclose[0])
        print('')
        


class BasicIndicatorsFeeded(btFeeds.PandasData):
    lines = ('Indexx', 'adj_close', 'EMA_100', 'RSI', 'CCI', 'MACD_macd', 'MACD_sign', 'MACD_hist',)

    params = ( ('Indexx', 0), ('adj_close', 5), ('volume', 6), 
              ('EMA_100', 7), ('RSI', 8), ('CCI', 9),
              ('MACD_macd', 10), ('MACD_sign', 11), ('MACD_hist', 12),)







if __name__ == '__main__':
    
    cerebro = bt.Cerebro()
    
    #Add data feed to Cerebro
    data1 = yf.download("AAPL",start="2021-08-09", end="2021-12-21",group_by="ticker")
    data1.insert(0,'Indexx',' ')
    data1['Indexx']=range(len(data1))
    data1['EMA_100']=talib.EMA(data1['Adj Close'],100)
    data1['RSI']=talib.RSI(data1['Adj Close'],14)
    data1['CCI']=talib.CCI(data1['High'], data1['Low'], data1['Adj Close'], timeperiod=14)
    data1['MACD_macd']=talib.MACD(data1['Adj Close'], fastperiod=12, slowperiod=26, signalperiod=9)[0]
    data1['MACD_sign']=talib.MACD(data1['Adj Close'], fastperiod=12, slowperiod=26, signalperiod=9)[1]
    data1['MACD_hist']=talib.MACD(data1['Adj Close'], fastperiod=12, slowperiod=26, signalperiod=9)[2]
   # data1['Long_position']
    # Run Cerebro Engine
    cerebro.broker.setcash(8000000000)
    start_portfolio_value = cerebro.broker.getvalue()
    cerebro.addstrategy(SimpleMACDStrat)
    
    data = BasicIndicatorsFeeded(dataname=data1)
    cerebro.adddata(data)
   
    cerebro.run()
    cerebro.plot()
   # print(data1)
    print('-------------------')
    #print('%f' %data)
    # print(data)
    end_portfolio_value = cerebro.broker.getvalue()
    pnl = end_portfolio_value - start_portfolio_value
    print(f'Starting Portfolio Value: {start_portfolio_value:2f}')
    print(f'Final Portfolio Value: {end_portfolio_value:2f}')
    print(f'PnL: {pnl:.2f}')

结果如下:

results

2021 年 11 月 10 日,macd_hist 从积极变为消极。我们预计第二天(2021-11-11):

a) 多头头寸在 and

之后平仓

b)开空仓

1)我们看到 a) 实际上在同一天关闭。下一次不是应该发生吗?

2)第二天还执行了卖出,这是不应该发生的。

对于 1) 和 2) 的任何建议都将受到欢迎。谢谢。

阿贝

编辑:

顺便说一句,我知道这个想法可以这样编码(只有 def next):

    def next(self):
        #print('%f' % (self.datas[0].Indexxx[0])
        self.log("Close: '{0}'" .format(self.data.adj_close[0]))
        print('%f %f %f %f %f %f %f %f %f %f %f %f %f' % (self.data.Indexx[0],self.data.open[0],
                                                 self.data.high[0],self.data.low[0],
                               self.data.close[0],self.data.adj_close[0],
                               self.data.volume[0],self.data.EMA_100[0], 
                               self.data.RSI[0], self.data.CCI[0],
                               self.data.MACD_macd[0],self.data.MACD_sign[0],self.data.MACD_hist[0]))
        if self.order:
            return
 

        print(self.position)
        if self.data.MACD_hist[0]>0 and self.data.MACD_hist[-1]<0:
                     self.order=self.buy()
                     self.log('CLOSE SHORT POSITION and open long, %.2f' % self.dataclose[0])
                
                
        if self.data.MACD_hist[0]<0 and self.data.MACD_hist[-1]>0:

                     self.order=self.sell()
                     self.log('CLOSE LONG POSITION and open short, %.2f' % self.dataclose[0])
                
        print('')

可是我真的很想分开

self.close()

例如

self.buy()

这将使我以后可以使用不同的条件来平仓和开仓。

非常感谢任何输入、想法和评论。

阿贝

在您的代码中显示了以下内容:

if self.data.MACD_hist[0]>0:
            if self.position.size<0 and self.data.MACD_hist[-1]<0  :
                     self.close()
                     self.log('CLOSE SHORT POSITION, %.2f' % self.dataclose[0])
       
            elif self.position.size==0:
                    self.order=self.buy()
                    self.log('OPEN LONG POSITION, %.2f' % self.dataclose[0])
                    
                
        elif self.data.MACD_hist[0]<0:
            if self.position.size>0 and self.data.MACD_hist[-1]>0:
                     self.order=self.close()
                     self.log('CLOSE LONG POSITION, %.2f' % self.dataclose[0])
                            
            elif self.position.size==0:
                    self.order=self.sell()
                    self.log('OPEN SHORT POSITION, %.2f' % self.dataclose[0])

按照逻辑,next只可能满足其中一个条件。

您表示您希望收盘和入场分开。您需要将 elif 更改为 if。此外,如果您使用 self.position.size == 0 标准,则在执行收盘前不会发生这种情况,因此收盘后的柱线,而不是下一个柱线。但如果您希望有其他条件,您可以在另一个 if 语句之后输入。

if self.data.MACD_hist[0]>0:
            if self.position.size<0 and self.data.MACD_hist[-1]<0  :
                     self.close()
                     self.log('CLOSE SHORT POSITION, %.2f' % self.dataclose[0])
            #### change here  ####
            if SOME OTHER CONDITION:
                    self.order=self.buy()
                    self.log('OPEN LONG POSITION, %.2f' % self.dataclose[0])
                    
                ...

一旦平仓,实际上就没有必要检查仓位是否达到 0 个单位。你可以放心地假设它会。