Changing tickets orders leads to different results

Hello,
I'm making a simple strategy: every day, the program checks the performance of all tickers, buys the one with the best performance (the greatest increase in the last 60 working days), while selling the rest.
The results should be the same regardless of the order of the tickers. But it's not so. The performance is VERY different. Can you help me spot the problem?
This is the method that selects the best stock:
def buyBestGrowing(self): best = self.datas[0] bestgrowth = best.close[0]/best.close[60] for i,stock in enumerate(self.datas): print("i =",i,"; stock =",stock) stockgrowth = stock.close[0]/stock.close[60] if stockgrowth > bestgrowth: self.close(best) best = stock buy(self,best)
The buy method:
def buy(self,dataX,size=None): #buys the wished size of the specified stock data. If no size is mentioned, buys as much as possible with the current capital based on tomorrow's open price try: self.buy(dataX,size or self.cerebro.broker.get_cash()/dataX.open[1]) except: pass
Thank you!

@marsario Your code is working too hard. Backtrader has built in indicators for this.
You can use the momentum indicator in the init section of your Strategy class, and go through all of your datas to create a momentum indicator for each, and then store them in the dictionary using the
data
object as key.Then in
next
at each bar, you can go through each momentum indictor and check for the max.Try the following:
import datetime import backtrader as bt class Strategy(bt.Strategy): 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 __init__(self): self.mo = dict() for d in self.datas: self.mo[d] = bt.ind.Momentum(d.close, period=10) def next(self): best_mo_val = None best_mo_data = None for dmo, mom in self.mo.items(): if not best_mo_val or mom[0] > best_mo_val: best_mo_val = mom[0] best_mo_data = dmo self.log(f"{best_mo_data._name}, {best_mo_val:5.2f}") if __name__ == "__main__": cerebro = bt.Cerebro() """ NEW DATA PARAMETERS """ for ticker in [ "AAPL", "AMZN", "BAC", "BUD", "FB", "GOOG", "JNJ", "MSFT", "PG", "T", "TSLA", "XOM", "v", ]: data = bt.feeds.YahooFinanceData( dataname=ticker, timeframe=bt.TimeFrame.Days, fromdate=datetime.date(2020, 1, 1), todate=datetime.date(2020, 12, 31), reverse=False, ) cerebro.adddata(data) cerebro.addstrategy(Strategy) # Execute cerebro.run()

Amazing, thanks @runout !
Can you tell me if I understand correctly?
self.mo[d] = bt.ind.Momentum(d.close, period=10)
The
period
is the number of days I want to consider, so if I want to check the increase in the last three months, I should write:period = 60
. Correct?Also, if I want to sell all the positions I had in my portfolio when there is a change of best stock, and if I want to buy the new best stock, I should change this part:
if not best_mo_val or mom[0] > best_mo_val: self.close(best_mo_data) best_mo_val = mom[0] best_mo_data = dmo self.log(f"{best_mo_data._name}, {best_mo_val:5.2f}") try: self.buy(dataX,self.cerebro.broker.get_cash()/dataX.open[1]) except: pass
Thank you so much for taking the time to reply and helping with my code!!

@marsario said in Changing tickets orders leads to different results:
The period is the number of days I want to consider, so if I want to check the increase in the last three months, I should write: period = 60. Correct?
Period is the number of bars.

@runout Hi again!
I tried to run this code
import datetime import backtrader as bt class Strategy(bt.Strategy): 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 __init__(self): self.mo = dict() for d in self.datas: self.mo[d] = bt.ind.Momentum(d.close, period=10) def next(self): best_mo_val = None best_mo_data = None for dmo, mom in self.mo.items(): if not best_mo_val or mom[0] > best_mo_val: self.close(best_mo_data) best_mo_val = mom[0] best_mo_data = dmo self.log(f"{best_mo_data._name}, {best_mo_val:5.2f}") try: self.buy(best_mo_data,self.cerebro.broker.get_cash()/best_mo_data.open[1]) except: pass if __name__ == "__main__": cerebro = bt.Cerebro() """ NEW DATA PARAMETERS """ for ticker in [ "BUD", "FB", "GOOG", "JNJ", "MSFT", "PG", "T", "TSLA", "XOM", "v", "AAPL", "AMZN", "BAC" ]: data = bt.feeds.YahooFinanceData( dataname=ticker, timeframe=bt.TimeFrame.Days, fromdate=datetime.date(2020, 1, 1), todate=datetime.date(2020, 12, 31), reverse=False, ) cerebro.adddata(data) cerebro.addstrategy(Strategy) # Execute print('Initial Portfolio Value: %.2f' % cerebro.broker.getvalue()) cerebro.run() print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
This is the result:
Initial Portfolio Value: 10000.00 20200116, GOOG, 84.33 20200117, GOOG, 119.73 20200121, GOOG, 90.19 20200122, GOOG, 92.61 20200123, GOOG, 82.33 20200124, GOOG, 46.88 20200127, TSLA, 15.97 20200128, GOOG, 13.33 ... 20201222, AMZN, 29.23 20201223, AMZN, 81.07 20201224, AMZN, 71.20 20201228, AMZN, 167.54 20201229, AMZN, 165.03 20201230, AMZN, 120.73 Final Portfolio Value: 16231.82
If I change the tickers' order to:
for ticker in [ "TSLA", "XOM", "v", "AAPL", "AMZN", "BAC", "BUD", "FB", "GOOG", "JNJ", "MSFT", "PG", "T" ]:
the log is the same but the final portfolio value is
11890.82
How is it possible?

@marsario Can you share with us the steps you took to debug your code including logs? Thanks.

These are some trials.
I tried to print the value of the stocks in my portfolio:
import datetime import backtrader as bt class Strategy(bt.Strategy): 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 __init__(self): self.mo = dict() for d in self.datas: self.mo[d] = bt.ind.Momentum(d.close, period=10) def next(self): best_mo_val = None best_mo_data = None for dmo, mom in self.mo.items(): print("The portfolio contains",self.broker.getposition(dmo).size*dmo.close[0],"of",dmo._name) if not best_mo_val or mom[0] > best_mo_val: self.close(best_mo_data) best_mo_val = mom[0] best_mo_data = dmo self.log(f"{best_mo_data._name}, {best_mo_val:5.2f}") try: self.buy(best_mo_data,self.cerebro.broker.get_cash()/best_mo_data.open[1]) except: pass if __name__ == "__main__": cerebro = bt.Cerebro() """ NEW DATA PARAMETERS """ for ticker in [ "TSLA", "XOM", "v", "AAPL", "AMZN", "BAC", "BUD", "FB", "GOOG", "JNJ", "MSFT", "PG", "T" ]: data = bt.feeds.YahooFinanceData( dataname=ticker, timeframe=bt.TimeFrame.Days, fromdate=datetime.date(2020, 1, 1), todate=datetime.date(2020, 12, 31), reverse=False, ) cerebro.adddata(data) cerebro.addstrategy(Strategy) # Execute print('Initial Portfolio Value: %.2f' % cerebro.broker.getvalue()) cerebro.run() print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
The result shows that it buys Google at first (which is the best), and then it keeps it till the end.
Initial Portfolio Value: 10000.00 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 0.0 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20200116, GOOG, 84.33 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 10119.487870067194 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20200117, GOOG, 119.73 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 10146.898989001375 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20200121, GOOG, 90.19 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 10157.494309287653 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20200122, GOOG, 92.61 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 10162.279292642746 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20200123, GOOG, 82.33 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 10025.97562392765 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20200124, GOOG, 46.88 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 9801.696618383905 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20200127, TSLA, 15.97 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 9929.250603249686 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T ... The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 12022.065608957488 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20201229, AMZN, 165.03 The portfolio contains 0.0 of TSLA The portfolio contains 0.0 of XOM The portfolio contains 0.0 of v The portfolio contains 0.0 of AAPL The portfolio contains 0.0 of AMZN The portfolio contains 0.0 of BAC The portfolio contains 0.0 of BUD The portfolio contains 0.0 of FB The portfolio contains 11890.820351217777 of GOOG The portfolio contains 0.0 of JNJ The portfolio contains 0.0 of MSFT The portfolio contains 0.0 of PG The portfolio contains 0.0 of T 20201230, AMZN, 120.73 Final Portfolio Value: 11890.82
After that, I tried to put the close orders at different places and change the close order with a sell order. But none of the results made any sense..

@marsario I think you should spend some time working through the examples in the documentation so that you can get a firm foundation on how backtrader works.
In your example above you have not sold Google, that's why it stay there. And then when the system tries to buy, you will get a margin error, which you should be seeing in logs, and you would know how to after working through the docs. This is a really basic error and is why I'm suggesting you spend some time in the docs.
We are lucky that backtrader has really good documentation, try to work through some of it for a while then we can help you as you go along. Good luck!

I appreciate that you are taking the time to reply each time, but I feel you are underestimating how much effort I'm putting in to try to make this code run. It's been a week that I have been going through the documentation and if I wrote to this forum it's because I have no idea of how to make it run. Yes, I understand it doesn't close orders. And how to make them close? I've been trying both with sell and close orders, changing the piece of code, nothing helps.

@marsario I know that backtrader can be frustrating when you first start. Trust me I know. However, I would argue, after looking some more at your code, that you must first learn to walk before you run. And you are already trying to run.
You need to take a step back and work through some of the examples.
But if you insist on tackling a multi data/multi indicator dictionary strategy to start, here are a few more tips.
Add in these loggers they will give you more information:
def notify_order(self, order): self.log( "Order ref: {} / Type {} / Status {}".format( order.ref, "Buy" * order.isbuy() or "Sell", order.getstatusname() ) ) if order.status == order.Completed: self.holdstart = len(self) # if not order.alive() and order.ref in self.orefs: # self.orefs.remove(order.ref) def notify_trade(self, trade): """Provides notification of closed trades.""" dt = self.data.datetime.datetime() if trade.isclosed: print( "{} {} Closed: PnL Gross {}, Net {},".format( dt, trade.data._name, round(trade.pnl, 2), round(trade.pnlcomm, 1), ) )
You don't need the
try/except
clause.When you add the loggers above, you will see you are getting
margin
notice. This means there's not enough cash. This is because you are trying to buy 100% of the cash value of the account and TSLA is rising. You need to use a percentage of the account. You could use Cheat On Open, but again, that's a bit advanced.The following makes no sense in backtrader.
best_mo_data.open[1]
The numbering goes 0, 1, 2, etc.
IMHO, you need to read the docs and work on simpler examples.
Good luck.