Backtrader Community

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

    getposition(data).price & self.broker.getcash() return unexpected values

    General Code/Help
    1
    3
    1323
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • R
      Rapha last edited by

      Hi there,

      There are two things I cannot get my mind around, probably because of wrong assumptions or understanding on my end. The code I am using is shown at end, which is run with data drawn from AlphaVantage.

      1. I do not understand how the cash balance returned by self.broker.getcash() is computed.
        My expectation:
        Cash_t = Cash_t-1 + Value of positions_t-1 closed valued at open price of day t - Value of positions_t opened valued at open price of day t
        My underlying assumptions:
        -Orders are executed daily at market opening at the open prices --> previous day's positions closed & current day's positions opened
        -No commissions are paid as no commission info are delivered through cerebro.broker.addcommissioninfo.

      2. Why does self.getposition(data=ticker).price does not always return the open price.
        I checked for some date references, and there are many occurences where the price returned by getposition(data).price does not correspond to the open price provided in my input (neither the close price). Below is an example for a given date:

      My log:
      9ef7f30e-187e-444a-9995-d4cc5b29dedc-image.png

      My input data:
      2d7a5b18-3d50-4ddb-b757-92bf6b189cc5-image.png

      Additional remark: the price at which the orders have been executed correspond to the open price, according to my log.

      The code:

      from pathlib import Path
      import csv
      from time import time
      import datetime
      import numpy as np
      import pandas as pd
      import pandas_datareader.data as web
      import matplotlib.pyplot as plt
      import seaborn as sns
      
      #import pandas_market_calendars as mcal
      
      import backtrader as bt
      from backtrader.feeds import PandasData
      
      import quantstats as qs
      
      
      pd.set_option('display.expand_frame_repr', False)
      np.random.seed(42)
      sns.set_style('darkgrid')
      
      def format_time(t):
          m_, s = divmod(t, 60)
          h, m = divmod(m_, 60)
          return f'{h:>02.0f}:{m:>02.0f}:{s:>02.0f}'
         
      #----------------- DATAFRAME LOADER -----------------
      OHLCV = ['open', 'high', 'low', 'close', 'volume']
      class SignalData(PandasData):
          """
          Define pandas DataFrame structure
          """
          cols = OHLCV + ['predicted']
      
          # create lines
          lines = tuple(cols)
      
          # define parameters
          params = {c: -1 for c in cols}
          params.update({'datetime': None})
          params = tuple(params.items())
          
      #----------------- STRATEGY -----------------
      #Includes an option to only trade on certain weekdays in lines 39/40.
      class MLStrategy(bt.Strategy):
          params = (('n_positions', 20),
                    ('min_positions', 10),
                    ('verbose', False),
                    ('log_file', 'backtest.csv'))
      
          def log(self, txt, dt=None):
              """ Logger for the strategy"""
              dt = dt or self.datas[0].datetime.datetime(0)
              with Path(self.p.log_file).open('a') as f:
                  log_writer = csv.writer(f)
                  log_writer.writerow([dt.isoformat()] + txt.split(','))
      
          def notify_order(self, order):
              if order.status in [order.Submitted, order.Accepted]:
                  if order.status in [order.Submitted]:
                      self.log(f'{order.data._name},SUBMITTED')
                  if order.status in [order.Accepted]:
                      self.log(f'{order.data._name},ACCEPTED')
                  return
      
              # Check if an order has been completed
              # broker could reject order if not enough cash
              if self.p.verbose:
                  if order.status in [order.Completed]:
                      p = order.executed.price
                      if order.isbuy():
                          self.log(f'{order.data._name},BUY executed,{p:.2f}')
                      elif order.issell():
                          self.log(f'{order.data._name},SELL executed,{p:.2f}')
      
                  elif order.status in [order.Canceled]:
                      self.log(f'{order.data._name},Order Canceled')
                  elif order.status in [order.Margin]:
                      self.log(f'{order.data._name},Order Margin')
                  elif order.status in [order.Rejected]:
                      self.log(f'{order.data._name},Order Rejected')
      
          def prenext(self):
              self.next()
              self.log('prenext')
      
          def next(self):
              self.log('next')
              today = self.datas[0].datetime.date()
      
              positions = [d._name for d, pos in self.getpositions().items() if pos]
              posdata = [d for d, pos in self.getpositions().items() if pos]    
      
              up, down = {}, {}
              missing = not_missing = 0
              for data in self.datas:
                  if data.datetime.date() == today:
                      if data.predicted[0] > 0:
                          up[data._name] = data.predicted[0]
                      elif data.predicted[0] < 0:
                          down[data._name] = data.predicted[0]
      
              self.log(f'{self.broker.getvalue()},PORTFOLIO VALUE')
              self.log(f'{self.broker.getcash()},CASH VALUE')
              
              for ticker in posdata:
                  self.log(f'{ticker._name,self.getposition(data=ticker).size,self.getposition(data=ticker).price,self.getposition(data=ticker).size*self.getposition(data=ticker).price},POSITION')
                             
              shorts = sorted(down, key=down.get)[:self.p.n_positions]
              longs = sorted(up, key=up.get, reverse=True)[:self.p.n_positions]
              n_shorts, n_longs = len(shorts), len(longs)
      
              if n_shorts < self.p.min_positions+1 or n_longs < self.p.min_positions+1:
                  longs, shorts = [], []
              else:
                  short_target = -1 / n_shorts
                  long_target = 1 / n_longs
                  
              for ticker in positions:
                  if ticker not in longs + shorts:
                      self.order_target_percent(data=ticker, target=0)
                      self.log(f'{ticker},CLOSING ORDER CREATED')
                      
              for ticker in longs:
                  self.order_target_percent(data=ticker, target=long_target)
                  self.log(f'{ticker,long_target},LONG ORDER CREATED')
              for ticker in shorts:
                  self.order_target_percent(data=ticker, target=short_target)
                  self.log(f'{ticker,short_target},SHORT ORDER CREATED')
                  
      cerebro = bt.Cerebro()  # create a "Cerebro" instance
      cash = 1000000
      
      cerebro.broker.setcash(cash)
      
      #------------------------------ ADD INPUT DATA --------------------------------
      idx = pd.IndexSlice
      data = pd.read_hdf('data.h5', 'backtest_data').sort_index()
      
      tickers = data.index.get_level_values(0).unique()
      
      for ticker in tickers:
          df = data.loc[idx[ticker, :], :].droplevel('ticker', axis=0)
          df.index.name = 'datetime'
          bt_data = SignalData(dataname=df)
          cerebro.adddata(bt_data, name=ticker)
          
      #---------------------------- RUN STRATEGY BACKTEST ---------------------------
      
      cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
      cerebro.addstrategy(MLStrategy, n_positions=20, min_positions=10,
                          verbose=True, log_file='backtesting_backtrader_log.csv')
      start = time()
      results = cerebro.run()
      ending_value = cerebro.broker.getvalue()
      duration = time() - start
      
      print(f'Final Portfolio Value: {ending_value:,.2f}')
      print(f'Duration: {format_time(duration)}')```
      R 1 Reply Last reply Reply Quote 0
      • R
        Rapha @Rapha last edited by

        I found the explanation to my second question, so I'll post it there :
        getposition(data).price returns the cost basis. For instance:

        • If all the positions on a given share where bought the same day, it returns the open price of that day.
        • If all the positions on a given share where bought the previous day, it returns the previous day open price.
        • If the positions have been accumulated over multiple periods, it returns the average price.

        Hope it might help some else.

        R 1 Reply Last reply Reply Quote 0
        • R
          Rapha @Rapha last edited by

          With my previous remark in mind, I doubled checked the cash formula above under point 1, and get the expected results using the opening prices out of my input (and not getposition...).

          So problem solved.

          1 Reply Last reply Reply Quote 1
          • 1 / 1
          • First post
            Last post
          Copyright © 2016, 2017, 2018, 2019, 2020, 2021 NodeBB Forums | Contributors