functionloopsclassbacktrader

backtrader loop functions with class


There is a pickle file that has many (say 10) stocks names and also have a folder that has all stock data I am trying to run the code to do the MACD analysis and hope can write down the result buy and sell time, price, position, cash on hand. when I have gone this far and trying to run MACD on all stocks. but after two loops, the data load is wrong. and repeats the results.

'''
import backtrader as bt
import pandas as pd
import pickle
import math


def run_test_macd():
    with open("sp500tickers.pickle", "rb") as f:
        tickers = pickle.load(f)
    cerebro.addstrategy(GoldenCross.gold_cross_class)
    for ticker in tickers:
        macd_stock_test2(ticker)

def macd_stock_test2(ticker):
    # print("stock test")
    # print("ticker")
    cerebro.broker.set_cash(1000000)
    ticker_prices = pd.read_csv('stock_dfs/{}.csv'.format(ticker), index_col='Date', parse_dates=True)

    # print(ticker_prices)
    # ticker prices
    feed = bt.feeds.PandasData(dataname=ticker_prices)
    print(feed)
    print(ticker)
    cerebro.adddata(feed)

    # cerebro.addsizer(bt.sizers.FixedSize, stake=1000)
    # cerebro.addanalyzer(btanalyzers.DrawDown, _name='drawdown')
    # cerebro.addanalyzer(btanalyzers.DrawDown, _name='returns')

    print('starting protfolio value: %.2f' % cerebro.broker.getvalue())
    cerebro.run()

    print('final protfolio value: %.2f' % cerebro.broker.getvalue())
    # cerebro.addanalyzer(SQN)
    #
    # cerebro.addwriter(bt.WriterFile, csv=args.writercsv, rounding=2)
    # cerebro.plot(style='candle')

class gold_cross_class(bt.Strategy):

    #set parameters to define fast and slow
    params = (('fast',40),('slow',150),('order_percentage',0.99),('ticker', "stock"))

    #define constractors
    def __init__(self):
        print("position size:",self.position.size)

        self.fast_moving_average=bt.indicators.EMA(
            self.data.close, period=self.params.fast, plotname='40 day moving average'
        )

        self.slow_moving_average = bt.indicators.EMA(
            self.data.close,  period=self.params.slow, plotname='150 day moving average'
        )

        self.crossover = bt.indicators.CrossOver(self.fast_moving_average, self.slow_moving_average)

    def next(self):
        if self.position.size == 0:
            if self.crossover >0:
                amount_to_invest = (self.params.order_percentage *self.broker.cash)
                self.size=math.floor(amount_to_invest/self.data.close)

                print("Buy {} shares of {} at {} on {}".format(self.size,self.params.ticker, self.data.close[0],self.data.close[0]))
                self.buy(size=self.size)

        if self.position.size > 0:
            if self.crossover<0:
                print("Sell {} shares of {} at {}".format(self.size,self.params.ticker, self.data.close[0]))
                self.sell(size=self.size)
'''

buy and sell price issue

buy order issue when 100% cash is used

updated buy & sell price issues


Solution

  • See my comments in the docstring at the head of the code. I have commented out some of your data loading only so I can load my own data, since I don't have your data.

    import datetime
    import backtrader as bt
    import pandas as pd
    import pickle
    import math
    
    """
    You have a number of issues. 
    1. Establish cerebro in `macd_stock_test2` 
    2. Call the `run_test_macd` from if __name__ == "__main__":
    3. Add in print log
    4. Add in order and trade notify.
    5. Don't pass in the strategy, create it in `macd_stock_test2`.
    6. Change name of GoldCross class to python standard.
    
    """
    
    
    def run_test_macd():
        # with open("sp500tickers.pickle", "rb") as f:
        #     tickers = pickle.load(f)
        tickers = ["TSLA", "FB"]
        for ticker in tickers:
            macd_stock_test2(ticker)
    
    
    def macd_stock_test2(ticker):
        # print("stock test")
        # print("ticker")
        cerebro = bt.Cerebro()
        cerebro.addstrategy(GoldCross)
    
        cerebro.broker.set_cash(1000000)
        # ticker_prices = pd.read_csv(
        #     "stock_dfs/{}.csv".format(ticker), index_col="Date", parse_dates=True
        # )
    
        # print(ticker_prices)
        # ticker prices
        # feed = bt.feeds.PandasData(dataname=ticker_prices)
        # print(feed)
        # print(ticker)
        feed = bt.feeds.YahooFinanceData(
            dataname=ticker,
            timeframe=bt.TimeFrame.Days,
            fromdate=datetime.datetime(2019, 1, 1),
            todate=datetime.datetime(2020, 12, 31),
            reverse=False,
        )
        cerebro.adddata(feed)
    
        # cerebro.addsizer(bt.sizers.FixedSize, stake=1000)
        # cerebro.addanalyzer(btanalyzers.DrawDown, _name='drawdown')
        # cerebro.addanalyzer(btanalyzers.DrawDown, _name='returns')
    
        print("starting portfolio value: %.2f" % cerebro.broker.getvalue())
        cerebro.run()
    
        print("final portfolio value: %.2f" % cerebro.broker.getvalue())
        # cerebro.addanalyzer(SQN)
        #
        # cerebro.addwriter(bt.WriterFile, csv=args.writercsv, rounding=2)
        # cerebro.plot(style='candle')
    
    
    class GoldCross(bt.Strategy):
    
        # set parameters to define fast and slow
        params = (
            ("fast", 40),
            ("slow", 150),
            ("order_percentage", 0.99),
            ("ticker", "stock"),
        )
    
        # define constractors
        def __init__(self):
            print("position size:", self.position.size)
    
            self.fast_moving_average = bt.indicators.EMA(
                self.data.close, period=self.params.fast, plotname="40 day moving average"
            )
    
            self.slow_moving_average = bt.indicators.EMA(
                self.data.close, period=self.params.slow, plotname="150 day moving average"
            )
    
            self.crossover = bt.indicators.CrossOver(
                self.fast_moving_average, self.slow_moving_average
            )
    
        def log(self, txt, dt=None):
            """ Logging function fot this strategy"""
            dt = dt or self.data.datetime[0]
            if isinstance(dt, float):
                dt = bt.num2date(dt)
            print("%s, %s" % (dt.date(), txt))
    
        def notify_order(self, order):
            """ Triggered upon changes to orders. """
    
            # Suppress notification if it is just a submitted order.
            if order.status == order.Submitted:
                return
    
            # Print out the date, security name, order number and status.
            dt, dn = self.datetime.date(), order.data._name
            type = "Buy" if order.isbuy() else "Sell"
            self.log(
                f"{order.data._name:<6} Order: {order.ref:3d}\tType: {type:<5}\tStatus"
                f" {order.getstatusname():<8} \t"
                f"Size: {order.created.size:9.4f} Price: {order.created.price:9.4f} "
                f"Position: {self.getposition(order.data).size}"
            )
            if order.status == order.Margin:
                return
    
            # Check if an order has been completed
            if order.status in [order.Completed]:
                self.log(
                    f"{order.data._name:<6} {('BUY' if order.isbuy() else 'SELL'):<5} "
                    # f"EXECUTED for: {dn} "
                    f"Price: {order.executed.price:6.2f} "
                    f"Cost: {order.executed.value:6.2f} "
                    f"Comm: {order.executed.comm:4.2f} "
                    f"Size: {order.created.size:9.4f} "
                )
    
        def notify_trade(self, trade):
            """Provides notification of closed trades."""
            if trade.isclosed:
                self.log(
                    "{} Closed: PnL Gross {}, Net {},".format(
                        trade.data._name,
                        round(trade.pnl, 2),
                        round(trade.pnlcomm, 1),
                    )
                )
    
        def next(self):
            if self.position.size == 0:
                if self.crossover > 0:
                    amount_to_invest = self.params.order_percentage * self.broker.cash
                    self.size = math.floor(amount_to_invest / self.data.close)
    
                    self.log(
                        "Buy {} shares of {} at {}".format(
                            self.size,
                            self.params.ticker,
                            self.data.close[0],
                        )
                    )
                    self.buy(size=self.size)
    
            if self.position.size > 0:
                if self.crossover < 0:
                    self.log(
                        "Sell {} shares of {} at {}".format(
                            self.size, self.params.ticker, self.data.close[0],
                        )
                    )
                    self.sell(size=self.size)
    
    
    if __name__ == "__main__":
        run_test_macd()
    
    

    With a print out as follows:

    starting protfolio value: 1000000.00
    position size: 0
    2019-10-28, Buy 15105 shares of stock at 65.54
    2019-10-29, TSLA   Order:   1   Type: Buy   Status Accepted     Size: 15105.0000 Price:   65.5400 Position: 15105
    2019-10-29, TSLA   Order:   1   Type: Buy   Status Completed    Size: 15105.0000 Price:   65.5400 Position: 15105
    2019-10-29, TSLA   BUY   Price:  64.00 Cost: 966720.00 Comm: 0.00 Size: 15105.0000 
    final protfolio value: 10527931.90
    starting protfolio value: 1000000.00
    position size: 0
    2020-05-12, Buy 4712 shares of stock at 210.1
    2020-05-13, FB     Order:   2   Type: Buy   Status Accepted     Size: 4712.0000 Price:  210.1000 Position: 4712
    2020-05-13, FB     Order:   2   Type: Buy   Status Completed    Size: 4712.0000 Price:  210.1000 Position: 4712
    2020-05-13, FB     BUY   Price: 209.43 Cost: 986834.16 Comm: 0.00 Size: 4712.0000 
    final protfolio value: 1294217.28
    
    Process finished with exit code 0