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/

    SQLite example

    General Discussion
    3
    14
    1928
    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.
    • L
      lampalork last edited by

      Dear Community,

      Does anyone has a SQLite example (database schema + class extending AbstractDatabase) that she is willing to share?
      For now, I'm just looking at setting of simple database with one table that will host daily bars (symbol, date, open, high, low, close, volume).

      Thanks a lot in advance
      Lamp'

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

        I have examples but could you be more clear what you want to have/do?

        RunBacktest.com

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

          Hi @run-out, essentially, I want to store data in sqlite, create data feeds for each symbol from SQLite (instead of genericCsv of Panda feed) and run a backtest. Down the road, I want to snap latest bars from broker and append these prices to sqlite table.
          Regards
          Lamp'

          1 Reply Last reply Reply Quote 1
          • vladisld
            vladisld last edited by

            Please take a look at the PSQL data feed implementation suggested in the following PR:

            https://github.com/mementum/backtrader/pull/393

            it could be relatively easily converted to use SQLite API. However, you need an additional code to store new data since the above PR is only for data feed.

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

              Did @vladisld answer your question or are you still looking for a straight up sqlite3 solution?

              RunBacktest.com

              1 Reply Last reply Reply Quote 0
              • L
                lampalork last edited by

                It is a partial match; I was hoping someone had already ready-to-run code. But if nobody has anything better; I'll find some time to look at this PR and adapt it. Thanks for checking @run-out

                1 Reply Last reply Reply Quote 0
                • vladisld
                  vladisld last edited by

                  Just out of curiosity, why SQLite and not some time series database like Influxdb?

                  1 Reply Last reply Reply Quote 0
                  • L
                    lampalork last edited by

                    In short, I'm designing an Expert Advisor based on backtrader. I worked with daily bars. SQLite will be clearly fast enough for my needs (in fact, I'm already ok with CSV perf). I will be snapping latest market data from a broker (for this; it will be easier/cleaner to add data to the database than CSV), compute signals and list down order required for rebalancing the portfolio.

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

                      @lampalork In my case I simply connect to sqlite3 using pandas, create a dataframe, and then input this into backtrader using pandas feeds.

                      import sqlite3
                      
                      pdict = {}
                      #
                      # Create database engine
                      engine = sqlite3.connect("filepath", detect_types=sqlite3.PARSE_DECLTYPES)
                      table = 'tablename'
                      sql_start = pdict["from_date"]
                      sql_end = pdict["to_date"]
                      
                      sql = (
                              "SELECT * FROM "
                              + table
                              + " WHERE datetime BETWEEN '"
                              + sql_start
                              + "' AND '"
                              + sql_end
                              + "'"
                      )
                      df_ohlcv = pd.read_sql(sql, con=engine)
                      df_ohlcv = df_ohlcv.set_index("datetime", drop=True)
                      df_ohlcv.index = pd.to_datetime(df_ohlcv.index)
                      

                      From here you can input into backtrader as a pandas dataframe.

                      If you have multiple tickers, then you just grab the tickers from the dataframe

                      tickers_list = df['tickers'].unique()
                      

                      Then cycle through the tickers to filter your dataframe and add in the datas one at a time.

                      RunBacktest.com

                      L 1 Reply Last reply Reply Quote 1
                      • L
                        lampalork @run-out last edited by

                        @run-out thanks a lot. I m building a dedicated feed using sqlalchemy (i.e. not using pandas). I ll publish as soon as I get a stable version

                        1 Reply Last reply Reply Quote 1
                        • L
                          lampalork last edited by

                          Hi All,
                          Long overdue. In case it can be useful to her folks.

                          import datetime as dt
                          from backtrader import TimeFrame
                          from backtrader.feed import DataBase
                          from backtrader import date2num
                          from sqlalchemy import create_engine
                          
                          class SQLiteData(DataBase):
                              params = (
                                  ('database', None),
                                  ('symbol', 'XBTUSD'),
                                  ('tick_value', 0.01),
                                  ('timeframe ', TimeFrame.Minutes),
                                  ('compression', 1),
                                  ('fromdate', dt.datetime(1900,1,1)),
                                  ('todate', dt.datetime.max),
                                  )
                          
                              def __init__(self):
                                  try:
                                      self.engine = create_engine('sqlite:///{0}'.format(self.p.database), echo=False)
                                      self._timeframe = self.p.timeframe
                                      self._compression = self.p.compression
                                      self._dataname = '{0}'.format(self.p.symbol)
                                  except Exception as ex:
                                      print('Unable to open database {0} - {1}'.format(self.p.database, ex))
                          
                              def start(self):
                                  try:
                                      self.conn = self.engine.connect()
                                      sql_query = "SELECT `date`,`open`,`high`,`low`,`close`,`volume` FROM `historical_data` WHERE `symbol` = '" + self.p.symbol + "' AND `date` between '"+ self.p.fromdate.strftime("%Y-%m-%d %H:%M:%S") +"' and '" + self.p.todate.strftime("%Y-%m-%d %H:%M:%S") + "' ORDER BY `date` ASC"
                                      #print(sql_query)
                                      self.result = self.conn.execute(sql_query)
                                  except Exception as ex:
                                      print('Unable to read table historical_data from database {0} - {1}'.format(self.p.database, ex))
                                      return
                          
                              def stop(self):
                                  self.engine.dispose()
                          
                              def _load(self):
                                  one_row = self.result.fetchone()
                                  if one_row is None:
                                      return False
                                  self.lines.datetime[0] = date2num(dt.datetime.strptime(str(one_row[0]),'%Y-%m-%d %H:%M:%S')) # date parsing
                                  self.lines.open[0] = float(one_row[1])
                                  self.lines.high[0] = float(one_row[2])
                                  self.lines.low[0] = float(one_row[3])
                                  self.lines.close[0] = float(one_row[4])
                                  self.lines.volume[0] = int(one_row[5])
                                  self.lines.openinterest[0] = -1
                                  return True
                          
                          
                          1 Reply Last reply Reply Quote 2
                          • vladisld
                            vladisld last edited by

                            Thanks for sharing.

                            As a side note:

                            suggest to take a look at the discussion in the PR I was posting above (https://github.com/mementum/backtrader/pull/393).

                            There are several issues in this PR that were reviewed/fixed that your code may benefit from as well.

                            1 Reply Last reply Reply Quote 1
                            • L
                              lampalork last edited by

                              Thanks a lot. My previous stuff was working but it was breaking while running optimization with multiple cpu because of the way it was designed. This psql example has shown me what to do.

                              Here is the new version for the benefits of others. Happy Friday!

                              Lamp'

                              import datetime as dt
                              from backtrader import TimeFrame
                              from backtrader.feed import DataBase
                              from backtrader import date2num
                              
                              class SQLiteData(DataBase):
                                 
                                  params = (
                                      ('database', None),
                                      ('symbol', 'XBTUSD'),
                                      ('tick_value', 0.01),
                                      ('timeframe ', TimeFrame.Minutes),
                                      ('compression', 1),
                                      ('fromdate', dt.datetime(1900, 1, 1)),
                                      ('todate', dt.datetime.max),
                              
                                      # parameterized column indices for ease of overwriting/re-implementing
                                      ('datetime', 0),
                                      ('open', 1),
                                      ('high', 2),
                                      ('low', 3),
                                      ('close', 4),
                                      ('volume', 5),
                                      ('openinterest', -1),
                                  )
                              
                                  def __init__(self):
                                      self._timeframe = self.p.timeframe
                                      self._compression = self.p.compression
                                      self._dataname = '{0}-{1:.2f}'.format(self.p.symbol, self.p.tick_value)
                              
                                  def start(self):
                                      engine = self._connect_db()
                              
                                      sql_query = "SELECT `date`,`open`,`high`,`low`,`close`,`volume` FROM `historical_data` WHERE `symbol` = '" + self.p.symbol + "' AND `date` between '" + self.p.fromdate.strftime(
                                          "%Y-%m-%d %H:%M:%S") + "' and '" + self.p.todate.strftime("%Y-%m-%d %H:%M:%S") + "' ORDER BY `date` ASC"
                              
                                      result = engine.execute(sql_query)
                                      self.price_rows = result.fetchall()
                                      result.close()
                              
                                      self.price_i = 0
                                      super(SQLiteData, self).start()
                              
                                  def _load(self):
                                      if self.price_i >= len(self.price_rows):
                                          return False
                                      row = self.price_rows[self.price_i]
                                      self.price_i += 1
                              
                                      for datafield in self.getlinealiases():
                                          if datafield == 'datetime':
                                              self.lines.datetime[0] = date2num(dt.datetime.strptime(row[self.p.datetime],'%Y-%m-%d %H:%M:%S'))
                                          elif datafield == 'volume':
                                              self.lines.volume[0] = row[self.p.volume]
                              
                                          else:
                                              # get the column index
                                              colidx = getattr(self.params, datafield)
                                              if colidx < 0:
                                                  # column not present -- skip
                                                  continue
                                              # get the line to be set
                                              line = getattr(self.lines, datafield)
                                              line[0] = float(row[colidx])
                              
                                      return True
                              
                              
                                  def _connect_db(self):
                                      from sqlalchemy import create_engine
                                      url = 'sqlite:///{0}'.format(self.p.database)
                                      engine = create_engine(url, echo=False)
                                      return engine
                              
                              
                                  def preload(self):
                                      super(SQLiteData, self).preload()
                                      # preloaded - no need to keep the price_rows as data is stored in the cerebro's Lines
                                      self.price_rows = None
                              
                              
                              1 Reply Last reply Reply Quote 0
                              • L
                                lampalork last edited by

                                Latest version that resolves the memory consumption error described here:
                                https://community.backtrader.com/topic/2397/high-memory-consumption-while-optimizing-using-influxdb-data-feed

                                import datetime as dt
                                from backtrader import TimeFrame
                                from backtrader.feed import DataBase
                                from backtrader import date2num
                                
                                
                                class SQLiteData(DataBase):
                                    '''
                                    Fetches data from SQLite, and wraps it into a Feed consumable by cerebro
                                    takes url connection string in form of :
                                        sqlite://{database}
                                
                                    this implementation assumes a single table (historical_data) with all prices,
                                    conforming to a schema similar to the following:
                                
                                            symbol        TEXT,
                                            date          TEXT (YYYY-mm-dd HH:mm),
                                            open          REAL,
                                            high          REAL,
                                            low           REAL,
                                            close         REAL,
                                            volume        INTEGER,
                                            unique (symbol, date)
                                
                                    if your databases are set up differently, you can override the
                                    start() method.
                                    '''
                                
                                    params = (
                                        ('database', None),
                                        ('symbol', 'XBTUSD'),
                                        ('tick_value', 0.01),
                                        ('timeframe ', TimeFrame.Minutes),
                                        ('compression', 1),
                                        ('fromdate', dt.datetime(1900, 1, 1)),
                                        ('todate', dt.datetime.max),
                                
                                        # parameterized column indices for ease of overwriting/re-implementing
                                        ('datetime', 0),
                                        ('open', 1),
                                        ('high', 2),
                                        ('low', 3),
                                        ('close', 4),
                                        ('volume', 5),
                                        ('openinterest', -1),
                                    )
                                
                                    def __init__(self):
                                        self._timeframe = self.p.timeframe
                                        self._compression = self.p.compression
                                        self._dataname = '{0}-{1:.2f}'.format(self.p.symbol, self.p.tick_value)
                                
                                    def start(self):
                                        super(SQLiteData, self).start()
                                        self.biter = None
                                        self.preloaded = False
                                
                                    def _preload(self):
                                        engine = self._connect_db()
                                
                                        sql_query = "SELECT `date`,`open`,`high`,`low`,`close`,`volume` FROM `historical_data` WHERE `symbol` = '" + self.p.symbol + "' AND `date` between '" + self.p.fromdate.strftime(
                                            "%Y-%m-%d %H:%M:%S") + "' and '" + self.p.todate.strftime("%Y-%m-%d %H:%M:%S") + "' ORDER BY `date` ASC"
                                
                                        result = engine.execute(sql_query)
                                        dbars = result.fetchall()
                                        result.close()
                                
                                        self.biter = iter(dbars)
                                
                                    def preload(self):
                                        if not self.biter:
                                            self._preload()
                                
                                        while self.load():
                                            pass
                                
                                        self._last()
                                        self.home()
                                
                                        self.biter = None
                                        self.preloaded = True
                                
                                    def _load(self):
                                        if self.preloaded:
                                            return False
                                
                                        if not self.biter:
                                            self._preload()
                                
                                        try:
                                            bar = next(self.biter)
                                        except StopIteration:
                                            return False
                                
                                        for field in self.getlinealiases():
                                            if field == 'datetime':
                                                self.lines.datetime[0] = date2num(dt.datetime.strptime(bar[self.p.datetime], '%Y-%m-%d %H:%M:%S'))
                                            elif field == 'volume':
                                                self.lines.volume[0] = bar[self.p.volume]
                                            else:
                                                # get the column index
                                                colidx = getattr(self.params, field)
                                                if colidx < 0:
                                                # column not present -- skip
                                                    continue
                                
                                                # get the line to be set
                                                line = getattr(self.lines, field)
                                                line[0] = float(bar[colidx])
                                
                                        return True
                                
                                
                                    def _connect_db(self):
                                        from sqlalchemy import create_engine
                                        url = 'sqlite:///{0}'.format(self.p.database)
                                        engine = create_engine(url, echo=False)
                                        return engine
                                
                                
                                1 Reply Last reply Reply Quote 1
                                • 1 / 1
                                • First post
                                  Last post
                                Copyright © 2016, 2017, 2018, 2019, 2020, 2021 NodeBB Forums | Contributors