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/

    Converting a Tradingview Strategy to a Backtrader Strategy - EMA Crossover

    Indicators/Strategies/Analyzers
    4
    8
    642
    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.
    • hfrog713
      hfrog713 last edited by

      Hi Guys,

      I need help with converting a tradingview strategy over into backtrader. I have been working on this for a couple of months and can't seem to get the numbers to matchup. We are getting the data from the Bybit API and loading into a pandas dataframe. We then use backtrader to load the dataframe into Cerebro.

      Please see below for the BackTrader Strategy and Pandas Data.

      class LongCloseLarpStrat(bt.Strategy):
          params = (('fastMA',22),('slowMA', 23))
          
          def log(self, txt, dt=None):
              ''' Logging function for this strategy'''
              dt = dt or self.datas[0].datetime.datetime(0)
              print('%s, %s' % (dt.isoformat(), txt))
              
              
          def __init__(self):
              self.livetrades = {}
              self.counter = 0
              
              self.fastMA = bt.indicators.ExponentialMovingAverage(period=self.p.fastMA)
              self.slowMA = bt.indicators.ExponentialMovingAverage(period=self.p.slowMA)
              self.crossover = bt.indicators.CrossOver(self.fastMA, self.slowMA) 
                
          def notify_order(self, order):
              if order.status in [order.Submitted, order.Accepted, order.Partial]:# Buy/Sell order submitted/accepted to/by broker/partially filled - Nothing to do
                  return
      
              # Check if an order has been completed
              if order.status in [order.Completed]:
                  if order.isbuy():
                      self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, tradeID: %d' %(order.executed.price, order.executed.value, order.executed.comm, order.tradeid))
                  elif order.issell():
                      self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, tradeID: %d' %(order.executed.price, order.executed.value, order.executed.comm, order.tradeid))
      
              elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                  self.log('Order Canceled/Margin/Rejected')
      
          def notify_trade(self, trade):
              if trade.justopened:
                  self.log('New trade just opened with id %d'%(trade.tradeid))
                  self.livetrades[trade.tradeid] = trade
              elif trade.isclosed:
                  self.log('A trade just closed with id %d'%(trade.tradeid))
                  self.livetrades.pop(trade.tradeid)
              else:
                  self.log('Trade id %d updated'%(trade.tradeid))
                  
          def next(self):
              if self.position.size:
                  if self.crossover < 0:
                      self.sell()
              elif self.crossover > 0:
                  self.buy()
      

      Pandas Data

      class PandasData(bt.feeds.PandasData):
          lines = ('turnover','high_low2', 'ema_slow', 'ema_fast', 'macd_delta')
          params = (
              ('datetime', None),
              ('open','Open'),
              ('high','High'),
              ('low','Low'),
              ('close','Close'),
              ('volume','Volume'),
              ('openinterest', None),
              ('turnover','Turnover'),        
              ('high_low2','high_low2'), 
              ('ema_slow', 'ema_slow'),
              ('ema_fast', 'ema_fast'),
              ('macd_delta', 'macd_delta')
          )
      

      Please see below for the Pinescript Code.

      //@version=3
      strategy(title = "test", overlay=true, initial_capital = 10000, pyramiding = 0, commission_type = strategy.commission.percent, commission_value = 0.0, calc_on_order_fills = false, calc_on_every_tick = false, default_qty_type = strategy.percent_of_equity, default_qty_value = 100, currency = currency.USD)
      
      fastMA = ema(close, 22)
      slowMA = ema(close, 23)
      
      plot(fastMA, color = red, linewidth = 1, title = "Fast MA")
      plot(slowMA, color = blue, linewidth = 1, title = "Slow MA")
      
      buyCondition = crossover(fastMA, slowMA)
      closeCondition = crossunder(fastMA, slowMA)
      
      strategy.entry("MA Cross - Long", strategy.long)
      strategy.close("MA Cross - Long", closeCondition)
      

      I apologize if I did not ask this question in the correct manner.
      Please let me know if there is any additional questions or anything I can add.

      Here is a link to the tradingview ticker.

      https://www.tradingview.com/symbols/BTCUSD/?exchange=BYBIT

      We are working on the 3 hour timeframe.
      Also, here is the code for getting the data from bybit.

      def get_historical_klines_pd(client, symbol, interval, start_str, end_str=None):
          """Get Historical Klines from Bybit 
          See dateparse docs for valid start and end string formats 
          http://dateparser.readthedocs.io/en/latest/
          If using offset strings for dates add "UTC" to date string 
          e.g. "now UTC", "11 hours ago UTC"
          :param symbol: Name of symbol pair -- BTCUSD, ETCUSD, EOSUSD, XRPUSD 
          :type symbol: str
          :param interval: Bybit Kline interval -- 1 3 5 15 30 60 120 240 360 720 "D" "M" "W" "Y"
          :type interval: str
          :param start_str: Start date string in UTC format
          :type start_str: str
          :param end_str: optional - end date string in UTC format
          :type end_str: str
          :return: list of OHLCV values
          """
      
          # set parameters for kline() 
          interval = int(interval)
          timeframe = str(interval)
          limit    = 200
          start_ts = int(date_to_milliseconds(start_str)/1000)
          end_ts = None
          if end_str:
              end_ts = int(date_to_milliseconds(end_str)/1000)
          else: 
              end_ts = int(date_to_milliseconds('now')/1000)
      
      
          # init our list
          output_data = []
      
          # loop counter 
          idx = 0
          # it can be difficult to know when a symbol was listed on Binance so allow start time to be before list date
          symbol_existed = False
          while True:
              # fetch the klines from start_ts up to max 200 entries 
              temp_tuple = client.Kline.Kline_get(symbol=symbol, interval=timeframe, **{'from':start_ts}, limit=limit).result()        
              temp_dict = temp_tuple[0]
              # handle the case where our start date is before the symbol pair listed on Binance
              if not symbol_existed and len(temp_dict):
                  symbol_existed = True
      
              if symbol_existed:
                  # extract data and convert to list 
                  temp_data = [list(i.values()) for i in temp_dict['result']]
                  # append this loops data to our output data
                  output_data += temp_data
      
                  # update our start timestamp using the last value in the array and add the interval timeframe
                  # NOTE: current implementation does not support inteval of D/W/M/Y
                  start_ts = temp_data[len(temp_data) - 1][2] + interval*60
      
              else:
                  # it wasn't listed yet, increment our start date
                  start_ts += timeframe
      
              idx += 1
              # check if we received less than the required limit and exit the loop
              if len(temp_data) < limit:
                  # exit the while loop
                  break
      
              # sleep after every 3rd call to be kind to the API
              if idx % 3 == 0:
                  time.sleep(0.2)
      
          # convert to data frame 
          df = pd.DataFrame(output_data, columns=['Symbol', 'Interval', 'TimeStamp', 'Open', 'High', 'Low', 'Close', 'Volume', 'Turnover'])
          df['Date'] = [datetime.fromtimestamp(i).strftime('%Y-%m-%d %H:%M:%S.%d')[:-3] for i in df['TimeStamp']]
      
          return df
      

      Thanks,
      Hernando

      run-out D 2 Replies Last reply Reply Quote 0
      • run-out
        run-out @hfrog713 last edited by

        @hfrog713 said in Converting a Tradingview Strategy to a Backtrader Strategy - EMA Crossover:

        can't seem to get the numbers to matchup

        Could you describe a bit more what the error is?

        RunBacktest.com

        hfrog713 2 Replies Last reply Reply Quote 1
        • hfrog713
          hfrog713 @run-out last edited by

          @run-out Hi Sorry, i am not getting an error......the problem is the number of trades, trade prices, and times of trades don't match at all with what i am seeing in the tradingview strategy. I have confirmed that the data looks the same in the dataframe as the data i am using in tradingview.

          I can upload samples of the data if required.
          The results i get from backtrader is the following.

          Trade Analysis Results:
          Total Open Total Closed Total Won Total Lost
          0 62 8 54
          Strike Rate Win Streak Losing Streak PnL Net
          12.9 2 11 -96628.15
          Final Portfolio Value: $3371.849999999994

          The results i get from TradingView are attached as a picture.

          Please let me know if you need anything else.
          I believe I am just trying to get a simple EMA crossover to buy and sell on positive and negative crossover. You can see what i am trying to do directly in the PineScript.

          Thanks in advance for your help.

          1 Reply Last reply Reply Quote 0
          • hfrog713
            hfrog713 @run-out last edited by

            @run-out tradingview_trades.png

            run-out 1 Reply Last reply Reply Quote 0
            • run-out
              run-out @hfrog713 last edited by

              @hfrog713 From what I can see of your strategy (it would be better if you just included the whole code) you are running a basic crossover. This is plain vanilla stuff. I cannot see any errors in your Strategy. You have a pandas feed with extra lines. I have no idea why they are there.

              I don't feel I can answer your question very well about Tradingview. I think what you need to do, in my opinion, is to start hunting the anomolies between the two systems. This can be tedious.

              Print out detailed logs of your backtest and make sure Backtrader is doing what you expect. Then try to compare the same data with your Tradingview charts and try to find out where the they diverge.

              When you have something more specific about backtrader giving specific errors let us know. Good luck.

              RunBacktest.com

              1 Reply Last reply Reply Quote 1
              • D
                dasch @hfrog713 last edited by

                @hfrog713 said in Converting a Tradingview Strategy to a Backtrader Strategy - EMA Crossover:

                def next(self):
                    if self.position.size:
                        if self.crossover < 0:
                            self.sell()
                    elif self.crossover > 0:
                        self.buy()
                

                Here is a indention error, the elif self.crossover > 0

                1 Reply Last reply Reply Quote 3
                • hfrog713
                  hfrog713 last edited by

                  I guess the crux of the question is.......

                  I have some logic in tradingview....

                  //@version=3
                  strategy(title = "test", overlay=true, initial_capital = 10000, pyramiding = 0, commission_type = strategy.commission.percent, commission_value = 0.0, calc_on_order_fills = false, calc_on_every_tick = false, default_qty_type = strategy.percent_of_equity, default_qty_value = 100, currency = currency.USD)

                  fastMA = ema(close, 22)
                  slowMA = ema(close, 23)
                  
                  buyCondition = crossover(fastMA, slowMA)
                  closeCondition = crossunder(fastMA, slowMA)
                  
                  if buyCondition
                      strategy.entry("MA Cross - Long", strategy.long)
                  
                  strategy.close("MA Cross - Long", closeCondition)
                  

                  How do i convert that to logic in backtrader.......

                  I thought the following, but i end up with different results.

                  class LongCloseLarpStrat(bt.Strategy):
                  
                      params = (('fastMA',22),('slowMA', 23))
                  
                      
                  
                      def log(self, txt, dt=None):
                  
                          ''' Logging function for this strategy'''
                  
                          dt = dt or self.datas[0].datetime.datetime(0)
                  
                          print('%s, %s' % (dt.isoformat(), txt))
                  
                          
                  
                          
                  
                      def __init__(self):
                  
                          self.livetrades = {}
                  
                          self.counter = 0
                  
                          
                  
                          self.fastMA = bt.indicators.ExponentialMovingAverage(period=self.p.fastMA)
                  
                          self.slowMA = bt.indicators.ExponentialMovingAverage(period=self.p.slowMA)
                  
                          self.crossover = bt.indicators.CrossOver(self.fastMA, self.slowMA) 
                  
                            
                  
                      def notify_order(self, order):
                  
                          if order.status in [order.Submitted, order.Accepted, order.Partial]:# Buy/Sell order submitted/accepted to/by broker/partially filled - Nothing to do
                  
                              return
                  
                  
                  
                          # Check if an order has been completed
                  
                          if order.status in [order.Completed]:
                  
                              if order.isbuy():
                  
                                  self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, tradeID: %d' %(order.executed.price, order.executed.value, order.executed.comm, order.tradeid))
                  
                              elif order.issell():
                  
                                  self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, tradeID: %d' %(order.executed.price, order.executed.value, order.executed.comm, order.tradeid))
                  
                  
                  
                          elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                  
                              self.log('Order Canceled/Margin/Rejected')
                  
                  
                  
                      def notify_trade(self, trade):
                  
                          if trade.justopened:
                  
                              self.log('New trade just opened with id %d'%(trade.tradeid))
                  
                              self.livetrades[trade.tradeid] = trade
                  
                          elif trade.isclosed:
                  
                              self.log('A trade just closed with id %d'%(trade.tradeid))
                  
                              self.livetrades.pop(trade.tradeid)
                  
                          else:
                  
                              self.log('Trade id %d updated'%(trade.tradeid))
                  
                              
                  
                      def next(self):
                  
                          if self.position.size:
                  
                              if self.crossover == -1:
                  
                                  self.sell()
                  
                          elif not self.position.size:
                  
                              if self.crossover == 1:
                  
                                  self.buy()
                  

                  Can someone help me out here?

                  Thanks in advance,
                  Hernando

                  D 1 Reply Last reply Reply Quote 0
                  • D
                    davidavr @hfrog713 last edited by

                    @hfrog713 It's hard to really provide any helpful information without knowing what "different results" means. Are you getting different EMA values? Are you getting different buy/sell signals from the crossover indicator? Are the signals the same but they result in different orders?

                    Additionally, without the full script and the data file, it's going to be hard to try it out locally. I ran your script against some AAPL prices and it just generates one sell signal during the period of test. Is that right or wrong? Is it consistent with what TradeView would show? It's hard for me to know.

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