Navigation

    Backtrader Community

    • Register
    • 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/

    Help with Exchange Rate Data Feed

    General Code/Help
    1
    2
    418
    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.
    • O
      Oliver Kiersnowski last edited by

      Hi, I am trying to build a simple backtest for a moving crossover strategy on some historical FX trading data. Currently, the account currency is in pounds (as would be in reality). I want to trade EUR/USD, for example. I input the first data feed as the traded pair EUR/USD, and then the exchange rate GBP/USD for the second.

      class SmaCross(bt.Strategy):
          params = (
              ('pfast', 50),
              ('pslow', 200),
              ('stake', 1000),  # units of the base currency (base/quote) => 1000 = 1000EUR in EUR/USD trading
              ('exectype', bt.Order.Market),
          )
      
          def log(self, txt, dt=None, time=None):
              """ Logging function for this strategy"""
              dt = dt or self.data0.datetime.date(0)
              time = time or self.data0.datetime.time(0)
              print('%s %s, %s' % (dt.isoformat(), time.isoformat(), txt))
      
          def __init__(self):
              # Keep a reference to the "close" line in the data[0] dataseries
              self.dataclose = self.datas[0].close
      
              sma1 = bt.indicators.SMA(self.data, period=self.p.pfast)
              sma2 = bt.indicators.SMA(self.data, period=self.p.pslow)
              self.crossover = bt.ind.CrossOver(sma1, sma2, plot=False)  # Crossover signal
      
              # To keep track of pending orders
              self.order = None
      
          def notify_order(self, order):
              if order.status in [order.Submitted, order.Accepted]:
                  # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                  return
      
              # 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("BUY EXECUTED, %f" % order.executed.price)
                  elif order.issell():
                      self.log("SELL EXECUTED, %f. BALANCE: %f. MARGIN REMAINING: %f." %
                               (order.executed.price,
                                cerebro.broker.getvalue(),
                                cerebro.broker.get_cash()
                                ))
                      cerebro.signals = []
      
              elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                  self.log('Order Canceled/Margin/Rejected')
      
              # Write down: no pending order
              self.order = None
      
          def notify_trade(self, trade):
              if trade.justopened:
                  print('----TRADE OPENED----')
                  print('Size: %f' % trade.size)
              elif trade.isclosed:
                  print('----TRADE CLOSED----')
                  print('Profit, Gross {}, Net {}'.format(
                      trade.pnl,
                      trade.pnlcomm))
              else:
                  return
      
          def next(self):
              # self.log((self.broker.getvalue(), self.broker.get_cash(), self.dataclose[0]))  # current price of the position
              if self.order:
                  return
      
              if not self.position:
                  if self.crossover > 0:
                      self.order = self.buy(size=self.p.stake,
                                            exectype=self.p.exectype,
                                            price=self.data.close[0])  # enter long
                      cerebro.broker.get_value()
                      self.log('BUYING AT %f. BALANCE: %f. MARGIN REMAINING: %f.' % (self.data.close[0], cerebro.broker.getvalue(),cerebro.broker.get_cash()))
              elif self.crossover < 0:
                  self.order = self.close(exectype=self.p.exectype,
                                          price=self.data.close[0])  # close long position
                  self.log('CLOSING AT %f' % self.data.close[0])
                  cerebro.signals.append('CLOSING')
      

      In my custom commission scheme, I have changed the profitandloss calculations, along with the cash adjustment so that the floating balance of the account represents the profits and losses in real time in the account currency (pounds).

          def profitandloss(self, size, price, newprice):
              backtrader_prof = size * (newprice - price) * self.p.mult
              if backtrader_prof == 0:
                  return backtrader_prof
              else:
                  pips = (newprice - price) * 10000
      
                  if acc_currency == 'Base':
                      pip_value = (size / price) * self.p.multiplier
                  elif acc_currency == "Quote":
                      pip_value = (size * self.p.multiplier)
                  else:
                      if self.p.exchange_format == "XXX/GBP":
                          pip_value = size * self.p.multiplier
                          pip_value = pip_value * self.p.exchange_rate  # pip value in pounds
                      elif self.p.exchange_format == "GBP/XXX":
                          pip_value = size * self.p.multiplier
                          if "CLOSING" in cerebro.signals:
                              pip_value = pip_value * (1 / cerebro.datas[1].open),  # pip value in pounds
                          else:
                              pip_value = pip_value * (1 / cerebro.datas[1])
                  pnl = pips * pip_value
                  return pnl
      
          def cashadjust(self, size, price, newprice):
              """Calculates cash adjustment for a given price difference"""
              return self.profitandloss(size, price, newprice)
      

      The problem, is that the profit and loss is calculated correctly for the trade below £4.34, but the balance in the account after this one trade is incorrect at £2003.98.

      Starting Portfolio Value: £2000.000000
      2016-03-16 21:00:00, BUYING AT 1.121060. BALANCE: 2000.000000. MARGIN REMAINING: 2000.000000.
      2016-03-17 01:00:00, BUY EXECUTED, 1.121100
      ----TRADE OPENED----
      Size: 1000.000000
      2016-05-18 09:00:00, CLOSING AT 1.127440
      2016-05-18 13:00:00, SELL EXECUTED, 1.127410. BALANCE: 2003.984311. MARGIN REMAINING: 2003.984311.
      ----TRADE CLOSED----
      Profit, Gross 4.335577848014318, Net 4.335577848014318
      Final Portfolio Value: £2003.984311
      

      I do not understand how the cash adjustment can be incorrect if it uses the same code to provide the profit and loss, which is correct?

      Any help would be greatly provided. Maybe it's something wrong with the inputting of a second data feed. I don't enumerate through them in the strategy as I just want to carry out the strategy on the first dataset, and then use the second as an exchange rate for my commission scheme.

      Many thanks!

      O 1 Reply Last reply Reply Quote 0
      • O
        Oliver Kiersnowski @Oliver Kiersnowski last edited by

        OK: thought this through and it appears the discrepancy comes from calculating the floating balance at each bar and converting it into pounds using different exchange rates for every bar. The final profit and loss is just calculated using the difference between entry and exit price and the exchange rate at the point of exit - so there’s the discrepancy.

        Any ideas on how to correctly calculate the floating balance in pounds or to make the account value and PnL match up after a trade would be much appreciated.

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