For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See:

Adding multiple data feeds in a for loop gives error

  • When trying to add multiple data feeds I'm getting the following error:

    ❯ python3
    Traceback (most recent call last):
      File "", line 43, in <module>
        optimized_runs =
      File "/home/user/.local/lib/python3.8/site-packages/backtrader/", line 1143, in run
        for r in pool.imap(self, iterstrats):
      File "/usr/lib/python3.8/multiprocessing/", line 868, in next
        raise value
    multiprocessing.pool.MaybeEncodingError: Error sending result: '[<strategies.strategies.EMAcrossover object at 0x7f9dda1051f0>]'. Reason: 'PicklingError("Can't pickle <class 'backtrader.metabase.AutoInfoClass_95a5b7035dfc45a8b0580d4ca9a9ac62_AXJO.csv'>: attribute lookup AutoInfoClass_95a5b7035dfc45a8b0580d4ca9a9ac62_AXJO.csv on backtrader.metabase failed")'

    The code works for a single data feed. This is what I've changed from the code:

        modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
        datapath = os.path.join(modpath, 'data/indexes/')
        for file in os.listdir(datapath):
            path = os.path.join(modpath, f'data/indexes/{file}')
            data = bt.feeds.YahooFinanceCSVData(

    The code for a single data feed:

    import backtrader as bt
    class MaxRiskSizer(bt.Sizer):
        params = (('risk', 0.98),)
        def __init__(self):
            if self.p.risk > 1 or self.p.risk < 0:
                raise ValueError('The risk parameter is a percentage which must be'
                    'entered as a float. e.g. 0.5')
        def _getsizing(self, comminfo, cash, data, isbuy):
            position =
            if not position:
                size = comminfo.getsize(data.close[0], cash * self.p.risk)
                size = position.size
            return size

    import backtrader as bt
    class DegiroCommission(bt.CommInfoBase):
        params = (('per_share', 0.004), ('flat', 0.5),)
        def _getcommission(self, size, price, pseudoexec):
            return self.p.flat + size * self.p.per_share

    from __future__ import (absolute_import, division, print_function,
    import datetime
    import os.path
    import sys
    import math
    import backtrader as bt
    from strategies.strategies import *
    from sizers import MaxRiskSizer
    from commissions import DegiroCommission
    STARTING_CASH = 1700
    if __name__ == '__main__':
        cerebro = bt.Cerebro(optreturn=False)
        cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annual_return')
        cerebro.optstrategy(EMAcrossover, fast=range(5, 7), slow=range(10,12))
        #cerebro.optstrategy(EMAcrossover, fast=range(5, 55, 5), slow=range(60, 300, 10))
        #cerebro.optstrategy(EMAcrossover, fast=range(15, 55), slow=range(40, 60))
        modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
        datapath = os.path.join(modpath, 'data/SPY.csv')
        data = bt.feeds.YahooFinanceCSVData(
        comminfo = DegiroCommission()
        optimized_runs =
        final_results_list = []
        for run in optimized_runs:
            for strategy in run:
                PnL = round( - STARTING_CASH, 2)
                my_dict = strategy.analyzers.annual_return.get_analysis()
                annual_returns = [v for _, v in my_dict.items() if v != 0]
                average_annual_return = sum(annual_returns) / len(annual_returns)
                strategy.params.slow, PnL, round(average_annual_return*100, 2)])
        sort_by_return = sorted(final_results_list, key=lambda x: x[3],
        print('\nSorted by Annualized Return:')
        for line in sort_by_return[:10]:
        sort_by_pnl = sorted(final_results_list, key=lambda x: x[2],
        print('\nSorted by PnL:')
        for line in sort_by_pnl[:10]:

    The indexes have different starting day for the datas. Maybe that affects the optimization somehow.

  • The strategy was missing.

    class EMAcrossover(bt.Strategy):
        params = (('fast', 20), ('slow', 50),)
        def log(self, txt, dt=None):
            dt = dt or self.datas[0]
            #print(f'{dt.isoformat()} {txt}') # Comment this line when running optimization
        def __init__(self):
            self.dataclose = self.datas[0].close
            self.order = None
            fast_ema, slow_ema = bt.ind.EMA(, bt.ind.EMA(period=self.p.slow)
            self.crossover = bt.indicators.CrossOver(fast_ema, slow_ema)
            #self.signal_add(bt.SIGNAL_LONGSHORT, bt.ind.CrossOver(ema1, ema2))
        def notify_trade(self, trade):
            if not trade.isclosed:
            self.log(f'GROSS {trade.pnl:.2f}, NET {trade.pnlcomm:.2f}')
        def notify_order(self, order):
            if order.status in [order.Submitted, order.Accepted]:
                # An active Buy/Sell order has been submitted/accepted - Nothing to do
            # 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(f'BUY EXECUTED, {order.executed.price:.2f}')
                elif order.issell():
                    self.log(f'SELL EXECUTED, {order.executed.price:.2f}')
                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 next(self):
            if self.order:
            if self.crossover > 0:
                self.log(f'BUY CREATE {self.dataclose[0]:.2f}')
                self.order =
            elif self.crossover < 0:
                self.log(f'SELL CREATE {self.dataclose[0]:.2f}')
                self.order = self.sell()

Log in to reply