docs: def aggregate_trade_iter(self, symbol, start_str=None, last_id=None):
          """Iterate over aggregate trade data from (start_time or last_id) to
          the end of the history so far.
  
          If start_time is specified, start with the first trade after
          start_time. Meant to initialise a local cache of trade data.
  
          If last_id is specified, start with the trade after it. This is meant
          for updating a pre-existing local trade data cache.
  
          Only allows start_str or last_id—not both. Not guaranteed to work
          right if you're running more than one of these simultaneously. You
          will probably hit your rate limit.
  
          See dateparser 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: Symbol string e.g. ETHBTC
          :type symbol: str
          :param start_str: Start date string in UTC format or timestamp in milliseconds. The iterator will
          return the first trade occurring later than this time.
          :type start_str: str|int
          :param last_id: aggregate trade ID of the last known aggregate trade.
          Not a regular trade ID. See https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list.
  
          :returns: an iterator of JSON objects, one per trade. The format of
          each object is identical to Client.aggregate_trades().
  
          :type last_id: int
          """
          if start_str is not None and last_id is not None:
              raise ValueError(
                  'start_time and last_id may not be simultaneously specified.')
  
          # If there's no last_id, get one.
          if last_id is None:
              # Without a last_id, we actually need the first trade.  Normally,
              # we'd get rid of it. See the next loop.
              if start_str is None:
                  trades = self.get_aggregate_trades(symbol=symbol, fromId=0)
              else:
                  # The difference between startTime and endTime should be less
                  # or equal than an hour and the result set should contain at
                  # least one trade.
                  if type(start_str) == int:
                      start_ts = start_str
                  else:
                      start_ts = date_to_milliseconds(start_str)
                  # If the resulting set is empty (i.e. no trades in that interval)
                  # then we just move forward hour by hour until we find at least one
                  # trade or reach present moment
                  while True:
                      end_ts = start_ts + (60 * 60 * 1000)
                      trades = self.get_aggregate_trades(
                          symbol=symbol,
                          startTime=start_ts,
                          endTime=end_ts)
                      if len(trades) > 0:
                          break
                      # If we reach present moment and find no trades then there is
                      # nothing to iterate, so we're done
                      if end_ts > int(time.time() * 1000):
                          return
                      start_ts = end_ts
              for t in trades:
                  yield t
              last_id = trades[-1][self.AGG_ID]
  
          while True:
              # There is no need to wait between queries, to avoid hitting the
              # rate limit. We're using blocking IO, and as long as we're the
              # only thread running calls like this, Binance will automatically
              # add the right delay time on their end, forcing us to wait for
              # data. That really simplifies this function's job. Binance is
              # fucking awesome.
              trades = self.get_aggregate_trades(symbol=symbol, fromId=last_id)
              # fromId=n returns a set starting with id n, but we already have
              # that one. So get rid of the first item in the result set.
              trades = trades[1:]
              if len(trades) == 0:
                  return
              for t in trades:
                  yield t
              last_id = trades[-1][self.AGG_ID]