Navigation

    Backtrader Community

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    1. Home
    2. Tony Shacklock
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/
    • Profile
    • Following 1
    • Followers 0
    • Topics 3
    • Posts 13
    • Best 4
    • Groups 0

    Tony Shacklock

    @Tony Shacklock

    4
    Reputation
    3
    Profile views
    13
    Posts
    0
    Followers
    1
    Following
    Joined Last Online
    Location New Zealand

    Tony Shacklock Unfollow Follow

    Best posts made by Tony Shacklock

    • RE: How to associate each strategy with it's data feed.

      Hi all

      Just after I posted that question, I found a better way by using getdatabyname(). See below.
      However, I still want to clarify my understanding that the Strategy class needs to identify which datas[] to use. Right?

      class MyStrategy(bt.Strategy):
          params=(('data_name', ''),)
      
          def __init__(self):
              pass
      
          def next(self):
      
              # Find the data feed we need.
              inst = self.params.data_name
              inst_data = self.getdatabyname(inst)
      
              # Print what was selected.
              dt = inst_data.datetime.date(0)
              dn = inst_data._name
              print(f'{dt.isoformat():s}: instrument is {inst:s}')
              print(f'            inst_data is {dn:s}')
      

      Tony

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • RE: Simply access some values like trade.pnl

      @run-out
      Thanks. That's much simpler.

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • RE: Understand long revers short logic

      @jas2210
      The value will be 0 when there is no cross.
      From the docs:
      "Formula:

      • diff = data - data1
      • upcross = last_non_zero_diff < 0 and data0(0) > data1(0)
      • downcross = last_non_zero_diff > 0 and data0(0) < data1(0)
      • crossover = upcross - downcross"

      So when upcross = False (0) and downcross = False (0), then crossover = 0 - 0.

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • RE: #ValueError #Invalid #literal for #int() while running #cereber.run()

      @nsananguly
      Have you tried setting maxcpus=1?
      cerebro.run(maxcpus=1)
      I think this is needed when optimising, but I don't know if it's related to your error.

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock

    Latest posts made by Tony Shacklock

    • RE: #ValueError #Invalid #literal for #int() while running #cereber.run()

      @nsananguly
      Have you tried setting maxcpus=1?
      cerebro.run(maxcpus=1)
      I think this is needed when optimising, but I don't know if it's related to your error.

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • Bar Times

      I'm sure I read somewhere about the details of period time and interval (open & closed ended, etc.), but I can't find it now. I would like to confirm my understanding on one point and I have a follow-on question.
      For intra-day periods, we must use
      end-of-bar time. Right? So, if our data has start-of-bar timestamps we must calculate the end-of-bar time. Is my understanding correct?

      If that is correct, then my question is about the end-of bar time. Should it be just the bar period later than the bar start time, or should we subtract some rounding as is done with daily timestamps. For example, for a 1-hour timeframe a 13:00 bar start-time becomes 14:00, or 13:59.999989?

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • RE: How to get trade close prices?

      @vladisld Perfect, thanks!

      posted in Indicators/Strategies/Analyzers
      Tony Shacklock
      Tony Shacklock
    • RE: How to get trade close prices?

      After digging deeper, I see that the Trade.price is the average entry price for the trade. So my question becomes, what is the best way to get trade closing prices?
      T

      posted in Indicators/Strategies/Analyzers
      Tony Shacklock
      Tony Shacklock
    • How to get trade close prices?

      Hi
      I'm trying to modify the Analyzer described here to record trade open and close times & prices, but it seems that the price is always the open price.
      I think the price should be the current price of the trade, so I expected to see the price when open different from when closed. However, when I print the values the price doesn't change.
      I must be missing something. Any tips?

      Here is the Analyzer:

      class ClosedTradeAnalyzer(bt.analyzers.Analyzer):
          '''
          An Analyzer returning closed trade information.
          '''
      
          def start(self):
              super(ClosedTradeAnalyzer, self).start()
      
      
          def create_analysis(self):
              self.rets = {}
              self.vals = tuple()
      
      
          def notify_trade(self, trade):
              status = trade.status_names[trade.status]
              print(f"Status: {status:6s},"
                    f" size {trade.size:.2f},"
                    f" price {trade.price:.2f},"
                    f" justopened {trade.justopened},"
                    f" isopen {trade.isopen},"
                    f" isclosed {trade.isclosed}"
                    )
      
              if trade.isclosed:
                  self.vals = (
                      self.strategy.datetime.datetime(),
                      trade.data._name,
                      trade.open_datetime(),
                      trade.price,  # Open price?
                      trade.close_datetime(),
                      round(trade.pnl, 2),
                      round(trade.pnlcomm, 2),
                      trade.commission,
                      (trade.dtclose - trade.dtopen),
                  )
                  self.rets[trade.ref] = self.vals
      
      
          def get_analysis(self):
              return self.rets
      

      Here is a section of what it produces:
      Status: Open , size 1.60, price 62.64, justopened True, isopen True, isclosed False
      Status: Closed, size 0.00, price 62.64, justopened False, isopen False, isclosed True
      Status: Open , size 1.61, price 62.02, justopened True, isopen True, isclosed False
      Status: Closed, size 0.00, price 62.02, justopened False, isopen False, isclosed True
      Status: Open , size 1.61, price 61.37, justopened True, isopen True, isclosed False
      Status: Closed, size 0.00, price 61.37, justopened False, isopen False, isclosed True
      Status: Open , size 1.73, price 57.72, justopened True, isopen True, isclosed False
      Status: Closed, size 0.00, price 57.72, justopened False, isopen False, isclosed True

      posted in Indicators/Strategies/Analyzers
      Tony Shacklock
      Tony Shacklock
    • RE: Understand long revers short logic

      @jas2210
      The value will be 0 when there is no cross.
      From the docs:
      "Formula:

      • diff = data - data1
      • upcross = last_non_zero_diff < 0 and data0(0) > data1(0)
      • downcross = last_non_zero_diff > 0 and data0(0) < data1(0)
      • crossover = upcross - downcross"

      So when upcross = False (0) and downcross = False (0), then crossover = 0 - 0.

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • RE: Simply access some values like trade.pnl

      @run-out
      Thanks. That's much simpler.

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • RE: Simply access some values like trade.pnl

      @jas2210
      I'm learning Backtrader too, and noticed the Writer class, which may help you with this. (It is described in an article written after the documentation.) Write it Down

      It writes a csv file, which includes time series data. I'm using it to help indicator development and plotting, but the file also contains trades and p&l, etc.

      This is the Quickstart example from the docs with the Writer added (and a function to extract time series data from the csv file).

      '''
      from future import absolute_import, division, print_function, unicode_literals

      from datetime import datetime
      import pandas as pd
      import matplotlib.pyplot as plt
      import backtrader as bt

      class TestStrategy(bt.Strategy):
      params = (('maperiod', 15),
      ('printlog', False),
      ('tradelog', {})
      )

      def log(self, txt, dt=None, doprint=False):
      
          if self.params.printlog or doprint:
              dt = dt or self.datas[0].datetime.date(0)
              print(f'{dt.isoformat():s}: {txt}')
      
      
      def __init__(self):
      
          # Keep a reference to the 'close' line in the data[0] dataseries.
          self.data_close = self.datas[0].close
      
          # Track pending orders.
          self.order = None
          self.buyprice = None
          self.buycomm = None
      
          # SMA indicator.
          self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
          self.sma.csv = True
      
          # Inidcators to demostrate plotting.
          # bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
          # bt.indicators.WeightedMovingAverage(self.datas[0], period=25, subplot=True)
          # bt.indicators.StochasticSlow(self.datas[0])
          # bt.indicators.MACDHisto(self.datas[0])
          # rsi = bt.indicators.RSI(self.datas[0])
          # bt.indicators.SmoothedMovingAverage(rsi, period=10)
          # bt.indicators.ATR(self.datas[0], plot=False)
      
          self.n_trades = 0
      
      
      def notify_order(self, order):
      
          if order.status in [order.Submitted, order.Accepted]:
              # The order has been submitted to or accepted by the broker. Do nothing.
              return
      
          # Check if the order has been completed.
          if order.status in [order.Completed]:
              if order.isbuy():
                  self.log(f'BUY: Fill price {order.executed.price:.2f}'
                           + f', Cost {order.executed.value:.2f}'
                           + f', Comm {order.executed.comm:.2f}')
                  self.buyprice = order.executed.price
                  self.buycomm = order.executed.comm
              elif order.issell():
                  self.log(f'SELL: Fill price {order.executed.price:.2f}'
                           + f', Cost {order.executed.value:.2f}'
                           + f', Comm {order.executed.comm:.2f}')
                  self.buyprice = order.executed.price
                  self.buycomm = 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  # No pending order.
      
      
      def notify_trade(self, trade):
          if not trade.isclosed:
              return
      
          self.log(f'Operation Profit: Gross {trade.pnl:.2f}, Net {trade.pnlcomm:.2f}',
                   doprint=True)
      
          self.params.tradelog[self.n_trades] = (trade.pnl,trade.pnlcomm )
          self.n_trades += 1
      
      
      def next(self):
          # Simply log the closing price of the series from the reference.
          self.log(f'Close {self.data_close[0]:.2f}')
      
          # If there is a pending order...
          if self.order:
              return  # Do nothing.
      
          # If no existing position...
          if not self.position:
              # Check for a buy signal.
              if self.data_close[0] > self.sma[0]:
                  self.log(f'Buy at {self.data_close[0]:.2f}')
                  self.order = self.buy()
          # Else there is an existing position.
          else:
              # Check for a sell condition.
              if self.data_close[0] < self.sma[0]:
                  self.log(f'Sell at {self.data_close[0]:.2f}')
                  self.order = self.sell()
      
      
      def stop(self):
          self.log(f'(MA Period {self.params.maperiod:2d}) Ending value {self.broker.getvalue():.2f}',
                   doprint=True)
      

      def get_timeseries_data(writer_filepath):

      # From the Backtrader documentation: the time series block is preceded and
      #   followed by a row of '====='.
      
      raw_df = pd.read_csv(writer_filepath, skiprows=1)
      
      # Keep the time series rows.
      first_drop_row = (raw_df.loc[raw_df['Id'].str.find("===") > -1]
                                                            .index.values[0])
      data_df = raw_df.iloc[0 : first_drop_row]
      
      # Make datetime column the index.
      data_df.set_index('datetime',inplace=True)
      data_df.index = data_df.index.astype('datetime64[ns]')
      
      return data_df
      

      if name == 'main':

      # Create the back test engine.
      cerebro = bt.Cerebro()
      
      # Dictionary to store trades.
      trade_log = {}
      
      # Add the strategy.
      cerebro.addstrategy(TestStrategy, maperiod=20, printlog=False,
                          tradelog=trade_log)
      
      # Alternatively, optimise.
      # strats = cerebro.optstrategy(TestStrategy, maperiod=range(10, 31))
      
      # Create a data feed.
      data_path = ('C:/Users/tony/Documents/SwingTree/Trading/Automation/Python/'
                                              + 'BackTrader Sample Data/datas/orcl-1995-2014.txt')
      data = bt.feeds.YahooFinanceCSVData(dataname=data_path,
                                          fromdate=datetime(2000, 1, 1),
                                          todate=datetime(2000, 12, 31),
                                          reverse=False)
      
      # Add the data feed to Cerebro.
      cerebro.adddata(data)
      
      
      cerebro.broker.setcash(1000.0)
      cerebro.broker.setcommission(commission=0.001)
      cerebro.addsizer(bt.sizers.FixedSize, stake=10)
      
      # Add Analyzers.
      cerebro.addanalyzer(bt.analyzers.Transactions, _name='Transactions')
      cerebro.addanalyzer(bt.analyzers.GrossLeverage, _name='GrossLeverage')
      
      # Add the Writer.
      cerebro.addwriter(bt.WriterFile, csv=True, out='Test.csv')
      
      # Run the backtest.
      print(f'Starting porfolio value: {cerebro.broker.getvalue():.2f}')
      
      strat_results = cerebro.run()
      # cerebro.run(maxcpus=1)  # For optimisation.
      
      print(f'Final porfolio value: {cerebro.broker.getvalue():.2f}')
      
      # Plot the results.
      data_df = get_timeseries_data('Test.csv')
      plt.plot(data_df[['close', 'sma']])
      

      '''

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • RE: No error shown but no results, simply double crossover

      I just spotted a couple of error in my response:
      The parameter to add to the strategy should be ('printlog', True), not printLog.
      The call to self.log(txt, doprint=True).
      T

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock
    • RE: No error shown but no results, simply double crossover

      I'm new to Backtrader, too, but I may be able to help with this. (It helps my learning, too.)

      I managed to get your code to run by doing these things:
      Firstly, to get logging to work, add (printLog, True) to your strategy parameters, or call self.log(txt, True).
      I reverted to non-optimisation to start with:

      • Change your strategy parameter default values to integers (not ranges).
      • Change cerebro.optstrategy() to cerebro.addstrategy().
      • Then if you uadd cerebro.plot() at the end you should see some output.

      For optimisation:

      • Add the maxcpus=1 to your Cerebro instance, i.e. back=cerebro.run(maxcpus=1). At least my system needs it.

      I hope that helps.

      posted in General Code/Help
      Tony Shacklock
      Tony Shacklock