Beispiel #1
0
    def _api_query(self, command, req={}):
        if command == "returnTicker" or command == "return24Volume":
            ret = self.session.get(self.public_uri + command)
        elif (command == "returnOrderBook"):
            ret = self.session.get(self.public_uri + command +
                                   '&currencyPair=' + str(req['currencyPair']))
        elif (command == "returnMarketTradeHistory"):
            ret = self.session.get(self.public_uri + 'returnTradeHistory' +
                                   '&currencyPair=' + str(req['currencyPair']))
        elif (command == "returnLoanOrders"):
            ret = self.session.get(self.public_uri + 'returnLoanOrders' +
                                   '&currency=' + str(req['currency']))
        else:
            req['command'] = command

            with self.lock:
                # Protect this region with a lock since poloniex will reject
                # non-increasing nonces. So if two greenlets come in here at
                # the same time one of them will fail
                req['nonce'] = int(time.time() * 1000)
                post_data = str.encode(urlencode(req))

                sign = hmac.new(self.secret, post_data,
                                hashlib.sha512).hexdigest()
                self.session.headers.update({'Sign': sign})
                ret = self.session.post('https://poloniex.com/tradingApi', req)

            result = rlk_jsonloads(ret.text)
            return self.post_process(result)

        return rlk_jsonloads(ret.text)
Beispiel #2
0
    def api_query(self, method, options=None):
        """
        Queries Bittrex with given method and options
        """
        if not options:
            options = {}
        nonce = str(int(time.time() * 1000))
        method_type = 'public'

        if method in BITTREX_MARKET_METHODS:
            method_type = 'market'
        elif method in BITTREX_ACCOUNT_METHODS:
            method_type = 'account'

        request_url = self.uri + method_type + '/' + method + '?'

        if method_type != 'public':
            request_url += 'apikey=' + self.api_key.decode(
            ) + "&nonce=" + nonce + '&'

        request_url += urlencode(options)
        signature = hmac.new(self.secret, request_url.encode(),
                             hashlib.sha512).hexdigest()
        self.session.headers.update({'apisign': signature})
        response = self.session.get(request_url)
        json_ret = rlk_jsonloads(response.text)
        if json_ret['success'] is not True:
            raise ValueError(json_ret['message'])
        return json_ret['result']
Beispiel #3
0
    def get_multieth_balance(self, accounts):
        """Returns a dict with keys being accounts and balances in ETH"""
        balances = {}
        if not self.connected:
            # TODO: accounts.length should be less than 20. If more we gotta do
            # multiple calls
            eth_resp = urlopen(
                Request(
                    'https://api.etherscan.io/api?module=account&action=balancemulti&address=%s'
                    % ','.join(accounts)))
            eth_resp = rlk_jsonloads(eth_resp.read())
            if eth_resp['status'] != 1:
                raise ValueError(
                    'Failed to query etherscan for accounts balance')
            eth_accounts = eth_resp['result']
            for account_entry in eth_accounts:
                amount = FVal(account_entry['balance'])
                balances[account_entry['account']] = from_wei(amount)

        else:
            for account in accounts:
                amount = FVal(self.web3.eth.getBalance(account))
                balances[account] = from_wei(amount)

        return balances
Beispiel #4
0
    def find_usd_price(self, asset, asset_btc_price=None):
        if self.kraken and self.kraken.first_connection_made and asset_btc_price is not None:
            return self.query_kraken_for_price(asset, asset_btc_price)

        # Adjust some ETH tokens to how cryptocompare knows them
        if asset == 'RDN':
            asset = 'RDN*'  # temporary
        if asset == 'DATAcoin':
            asset = 'DATA'
        resp = retry_calls(
            5,
            'find_usd_price',
            'urllib2.urlopen',
            urlopen,
            Request(
                u'https://min-api.cryptocompare.com/data/price?fsym={}&tsyms=USD'.format(
                    asset
                ))
        )

        resp = rlk_jsonloads(resp.read())

        # If there is an error in the response skip this token
        if 'USD' not in resp:
            if resp['Response'] == 'Error':
                print('Could not query USD price for {}. Error: "{}"'.format(
                    asset,
                    resp['Message']),
                )
            else:
                print('Could not query USD price for {}'.format(asset))
            return FVal(0)

        return FVal(resp['USD'])
Beispiel #5
0
def test_decoding():
    strdata = """
    {"a": 3.14, "b":5, "c": "foo", "d": "5.42323143", "e": { "u1": "3.221"}, "f": [2.1, "boo", 3, "4.2324"]}"""

    data = rlk_jsonloads(strdata)
    assert isinstance(data['a'], FVal)
    assert data['a'] == FVal('3.14')
    assert isinstance(data['b'], int)
    assert data['b'] == 5
    assert isinstance(data['c'], (str, bytes))
    assert data['c'] == 'foo'
    assert isinstance(data['d'], FVal)
    assert data['d'] == FVal('5.42323143')

    assert isinstance(data['e']['u1'], FVal)
    assert data['e']['u1'] == FVal('3.221')

    assert isinstance(data['f'][0], FVal)
    assert data['f'][0] == FVal('2.1')
    assert isinstance(data['f'][1], (str, bytes))
    assert data['f'][1] == "boo"
    assert isinstance(data['f'][2], int)
    assert data['f'][2] == 3
    assert isinstance(data['f'][3], FVal)
    assert data['f'][3] == FVal('4.2324')
Beispiel #6
0
    def check_and_get_response(self, response, method):
        if response.status_code in (520, 525, 504):
            raise RecoverableRequestError('kraken',
                                          'Usual kraken 5xx shenanigans')
        elif response.status_code != 200:
            raise ValueError(
                'Kraken API request {} for {} failed with HTTP status '
                'code: {}'.format(
                    response.url,
                    method,
                    response.status_code,
                ))

        result = rlk_jsonloads(response.text)
        if result['error']:
            if isinstance(result['error'], list):
                error = result['error'][0]
            else:
                error = result['error']

            if 'Rate limit exceeded' in error:
                raise RecoverableRequestError('kraken',
                                              'Rate limited exceeded')
            else:
                raise ValueError(error)

        return result['result']
Beispiel #7
0
    def __init__(self, data_directory):

        self.data_directory = data_directory

        try:
            with open(os.path.join(self.data_directory, 'settings.json')) as f:
                self.settings = rlk_jsonloads(f.read())
        except JSONDecodeError as e:
            logger.critical('settings.json file could not be decoded and is corrupt: {}'.format(e))
            self.settings = empty_settings
        except FileNotFoundError:
            self.settings = empty_settings

        self.db = None
        self.eth_tokens = []
        dir_path = os.path.dirname(os.path.realpath(__file__))
        with open(os.path.join(dir_path, 'data', 'eth_tokens.json'), 'r') as f:
            self.eth_tokens = rlk_jsonloads(f.read())
Beispiel #8
0
def do_read_manual_margin_positions(data_directory):
    manual_margin_path = os.path.join(data_directory, MANUAL_MARGINS_LOGFILE)
    if os.path.isfile(manual_margin_path):
        with open(manual_margin_path, 'r') as f:
            margin_data = rlk_jsonloads(f.read())
    else:
        margin_data = []
        logger.error('Could not find manual margins log file at {}'.format(
            manual_margin_path))
    return margin_data
Beispiel #9
0
    def api_query(self, method, options=None):
        if not options:
            options = {}

        with self.lock:
            # Protect this region with a lock since binance will reject
            # non-increasing nonces. So if two greenlets come in here at
            # the same time one of them will fail
            if method in V3_ENDPOINTS:
                api_version = 3
                # Recommended recvWindows is 5000 but we get timeouts with it
                options['recvWindow'] = 10000
                options['timestamp'] = str(int(time.time() * 1000))
                signature = hmac.new(
                    self.secret,
                    urlencode(options).encode('utf-8'),
                    hashlib.sha256
                ).hexdigest()
                options['signature'] = signature
            elif method in V1_ENDPOINTS:
                api_version = 1
            else:
                raise ValueError('Unexpected binance api method {}'.format(method))

            request_url = self.uri + 'v' + str(api_version) + '/' + method + '?'
            request_url += urlencode(options)

            response = self.session.get(request_url)

        if response.status_code != 200:
            result = rlk_jsonloads(response.text)
            raise ValueError(
                'Binance API request {} for {} failed with HTTP status '
                'code: {}, error code: {} and error message: {}'.format(
                    response.url,
                    method,
                    response.status_code,
                    result['code'],
                    result['msg'],
                ))

        json_ret = rlk_jsonloads(response.text)
        return json_ret
Beispiel #10
0
 def get_eth_balance(self, account):
     if not self.connected:
         eth_resp = urlopen(
             Request(
                 'https://api.etherscan.io/api?module=account&action=balance&address=%s'
                 % account))
         eth_resp = rlk_jsonloads(eth_resp.read())
         if eth_resp['status'] != 1:
             raise ValueError(
                 'Failed to query etherscan for accounts balance')
         amount = FVal(eth_resp['result'])
         return from_wei(amount)
     else:
         return from_wei(self.web3.eth.getBalance(account))
Beispiel #11
0
    def process_response(self, response):
        result_or_error = ''
        success = False
        if response.status_code not in HANDLABLE_STATUS_CODES:
            result_or_error = (
                'Unexpected status response({}) from rotkehlchen server'.
                format(response.status_code))
        else:
            result_or_error = rlk_jsonloads(response.text)
            if 'error' in result_or_error:
                result_or_error = result_or_error['error']
            else:
                success = True

        return success, result_or_error
Beispiel #12
0
    def check_trades_cache(self, start_ts, end_ts, special_name=None):
        trades_file = self._get_cachefile_name(special_name)
        trades = dict()
        if os.path.isfile(trades_file):
            with open(trades_file, 'r') as f:
                try:
                    trades = rlk_jsonloads(f.read())
                except:
                    pass

                # no need to query again
                if data_up_todate(trades, start_ts, end_ts):
                    return trades['data']

        return None
Beispiel #13
0
 def __init__(self, ethrpc_port):
     self.connected = True
     # Note that you should create only one RPCProvider per
     # process, as it recycles underlying TCP/IP network connections between
     # your process and Ethereum node
     self.web3 = Web3(
         HTTPProvider('http://localhost:{}'.format(ethrpc_port)))
     try:
         self.web3.eth.blockNumber
         dir_path = os.path.dirname(os.path.realpath(__file__))
         with open(os.path.join(dir_path, 'data', 'token_abi.json'),
                   'r') as f:
             self.token_abi = rlk_jsonloads(f.read())
     except ConnectionError:
         logger.warn(
             'Could not connect to a local ethereum node. Will use etherscan only'
         )
         self.connected = False
Beispiel #14
0
def query_txlist(address, internal, from_block=None, to_block=None):
    result = list()
    if internal:
        reqstring = ('https://api.etherscan.io/api?module=account&action='
                     'txlistinternal&address={}'.format(address))
    else:
        reqstring = ('https://api.etherscan.io/api?module=account&action='
                     'txlist&address={}'.format(address))
    if from_block:
        reqstring += '&startblock={}'.format(from_block)
    if to_block:
        reqstring += '&endblock={}'.format(to_block)

    resp = urlopen(Request(reqstring))
    resp = rlk_jsonloads(resp.read())

    if 'status' not in resp or convert_to_int(resp['status']) != 1:
        status = convert_to_int(resp['status'])
        if status == 0 and resp['message'] == 'No transactions found':
            return list()

        # else unknown error
        raise ValueError(
            'Failed to query txlist from etherscan with query: {} . '
            'Response was: {}'.format(reqstring, resp))

    for v in resp['result']:
        # internal tx list contains no gasprice
        gas_price = -1 if internal else FVal(v['gasPrice'])
        result.append(
            EthereumTransaction(
                timestamp=convert_to_int(v['timeStamp']),
                block_number=convert_to_int(v['blockNumber']),
                hash=v['hash'],
                from_address=v['from'],
                to_address=v['to'],
                value=FVal(v['value']),
                gas=FVal(v['gas']),
                gas_price=gas_price,
                gas_used=FVal(v['gasUsed']),
            ))

    return result
Beispiel #15
0
    def get_multitoken_balance(self, token_symbol, token_address,
                               token_decimals, accounts):
        """Return a dictionary with keys being accounts and value balances of token
        Balance value is normalized through the token decimals.
        """
        balances = {}
        if self.connected:
            token_contract = self.web3.eth.contract(address=token_address,
                                                    abi=self.token_abi)
            for account in accounts:
                token_amount = FVal(token_contract.call().balanceOf(account))
                if token_amount != 0:
                    balances[account] = token_amount / (FVal(10)**
                                                        FVal(token_decimals))
        else:
            for account in accounts:
                print('Checking token {} for account {}'.format(
                    token_symbol, account))
                resp = urlopen(
                    Request(
                        'https://api.etherscan.io/api?module=account&action='
                        'tokenbalance&contractaddress={}&address={}'.format(
                            token_address,
                            account,
                        )))
                resp = rlk_jsonloads(resp.read())
                if resp['status'] != 1:
                    raise ValueError(
                        'Failed to query etherscan for {} token balance of {}'.
                        format(
                            token_symbol,
                            account,
                        ))
                token_amount = FVal(resp['result'])
                if token_amount != 0:
                    balances[account] = token_amount / (FVal(10)**
                                                        FVal(token_decimals))

        return balances
Beispiel #16
0
    def __init__(self, data_directory, history_date_start=DEFAULT_START_DATE):
        self.data_directory = data_directory
        # get the start date for historical data
        self.historical_data_start = createTimeStamp(history_date_start,
                                                     formatstr="%d/%m/%Y")

        self.price_history = dict()
        # TODO: Check if historical data is after the requested start date
        # Check the data folder and load any cached history
        prefix = os.path.join(self.data_directory, 'price_history_')
        regex = re.compile(prefix + '(.*)\.json')
        files_list = glob.glob(prefix + '*.json')
        for file_ in files_list:
            match = regex.match(file_)
            assert match
            cache_key = match.group(1)
            with open(file_, 'rb') as f:
                data = rlk_jsonloads(f.read())
                self.price_history[cache_key] = data

        # Get coin list of crypto compare
        invalidate_cache = True
        coinlist_cache_path = os.path.join(self.data_directory,
                                           'cryptocompare_coinlist.json')
        if os.path.isfile(coinlist_cache_path):
            with open(coinlist_cache_path, 'rb') as f:
                try:
                    data = rlk_jsonloads(f.read())
                    now = ts_now()
                    invalidate_cache = False

                    # If we got a cache and its' over a month old then requery cryptocompare
                    if data['time'] < now and now - data['time'] > 2629800:
                        invalidate_cache = True
                        data = data['data']
                except JSONDecodeError:
                    invalidate_cache = True

        if invalidate_cache:
            query_string = 'https://www.cryptocompare.com/api/data/coinlist/'
            resp = urlopen(Request(query_string))
            resp = rlk_jsonloads(resp.read())
            if 'Response' not in resp or resp['Response'] != 'Success':
                error_message = 'Failed to query cryptocompare for: "{}"'.format(
                    query_string)
                if 'Message' in resp:
                    error_message += ". Error: {}".format(resp['Message'])
                raise ValueError(error_message)
            data = resp['Data']

            # Also save the cache
            with open(coinlist_cache_path, 'w') as f:
                write_data = {'time': ts_now(), 'data': data}
                f.write(rlk_jsondumps(write_data))
        else:
            # in any case take the data
            data = data['data']

        self.cryptocompare_coin_list = data
        # For some reason even though price for the following assets is returned
        # it's not in the coinlist so let's add them here.
        self.cryptocompare_coin_list['DAO'] = object()
        self.cryptocompare_coin_list['USDT'] = object()
Beispiel #17
0
    def get_historical_data(self, from_asset, to_asset, timestamp):
        """Get historical price data from cryptocompare"""
        if from_asset not in self.cryptocompare_coin_list:
            raise ValueError('Attempted to query historical price data for '
                             'unknown asset "{}"'.format(from_asset))

        if to_asset not in self.cryptocompare_coin_list and to_asset not in FIAT_CURRENCIES:
            raise ValueError('Attempted to query historical price data for '
                             'unknown asset "{}"'.format(to_asset))

        cache_key = from_asset + '_' + to_asset
        if cache_key in self.price_history and self.price_history[cache_key][
                'end_time'] > timestamp:
            return self.price_history[cache_key]['data']

        now_ts = int(time.time())
        cryptocompare_hourquerylimit = 2000
        calculated_history = list()

        end_date = self.historical_data_start
        while True:
            pr_end_date = end_date
            end_date = end_date + (cryptocompare_hourquerylimit) * 3600
            query_string = ('https://min-api.cryptocompare.com/data/histohour?'
                            'fsym={}&tsym={}&limit={}&toTs={}'.format(
                                from_asset, to_asset,
                                cryptocompare_hourquerylimit, end_date))
            resp = urlopen(Request(query_string))
            resp = rlk_jsonloads(resp.read())
            if 'Response' not in resp or resp['Response'] != 'Success':
                error_message = 'Failed to query cryptocompare for: "{}"'.format(
                    query_string)
                if 'Message' in resp:
                    error_message += ". Error: {}".format(resp['Message'])
                raise ValueError(error_message)

            if pr_end_date != resp['TimeFrom']:
                # If we get more than we needed, since we are close to the now_ts
                # then skip all the already included entries
                diff = pr_end_date - resp['TimeFrom']
                if resp['Data'][diff / 3600]['time'] != pr_end_date:
                    raise ValueError(
                        'Expected to find the previous date timestamp during '
                        'historical data fetching')
                # just add only the part from the previous timestamp and on
                resp['Data'] = resp['Data'][diff / 3600:]

            if end_date < now_ts and resp['TimeTo'] != end_date:
                raise ValueError('End dates no match')

            # If last time slot and first new are the same, skip the first new slot
            last_entry_equal_to_first = (len(calculated_history) != 0
                                         and calculated_history[-1]['time']
                                         == resp['Data'][0]['time'])
            if last_entry_equal_to_first:
                resp['Data'] = resp['Data'][1:]
            calculated_history += resp['Data']
            if end_date >= now_ts:
                break

        # Let's always check for data sanity for the hourly prices.
        assert check_hourly_data_sanity(calculated_history, from_asset,
                                        to_asset)
        self.price_history[cache_key] = {
            'data': calculated_history,
            'start_time': self.historical_data_start,
            'end_time': now_ts
        }
        # and now since we actually queried the data let's also save them locally
        write_history_data_in_file(
            calculated_history,
            os.path.join(self.data_directory,
                         'price_history_' + cache_key + '.json'),
            self.historical_data_start, now_ts)

        return calculated_history
Beispiel #18
0
    def query_historical_price(self, from_asset, to_asset, timestamp):
        """
        Query the historical price on `timestamp` for `from_asset` in `to_asset`.
        So how much `to_asset` does 1 unit of `from_asset` cost.

        Args:
            from_asset (str): The ticker symbol of the asset for which we want to know
                              the price.
            to_asset (str): The ticker symbol of the asset against which we want to
                            know the price.
            timestamp (int): The timestamp at which to query the price
        """
        if from_asset == to_asset:
            return 1

        if from_asset not in self.cryptocompare_coin_list:
            raise PriceQueryUnknownFromAsset(from_asset)

        data = self.get_historical_data(from_asset, to_asset, timestamp)

        # all data are sorted and timestamps are always increasing by 1 hour
        # find the closest entry to the provided timestamp
        # print("loaded {}_{}".format(from_asset, to_asset))
        assert timestamp > data[0]['time']
        index = convert_to_int((timestamp - data[0]['time']) / 3600,
                               accept_only_exact=False)
        # print("timestamp: {} index: {} data_length: {}".format(timestamp, index, len(data)))
        diff = abs(data[index]['time'] - timestamp)
        if index + 1 <= len(data) - 1:
            diff_p1 = abs(data[index + 1]['time'] - timestamp)
            if diff_p1 < diff:
                index = index + 1

        if data[index]['high'] is None or data[index]['low'] is None:
            # If we get some None in the hourly set price to 0 so that we check daily price
            price = FVal(0)
        else:
            price = FVal((data[index]['high'] + data[index]['low'])) / 2

        if price == 0:
            if from_asset != 'BTC' and to_asset != 'BTC':
                # Just get the BTC price
                asset_btc_price = self.query_historical_price(
                    from_asset, 'BTC', timestamp)
                btc_to_asset_price = self.query_historical_price(
                    'BTC', to_asset, timestamp)
                price = asset_btc_price * btc_to_asset_price
            else:
                # attempt to get the daily price by timestamp
                query_string = (
                    'https://min-api.cryptocompare.com/data/pricehistorical?'
                    'fsym={}&tsyms={}&ts={}'.format(from_asset, to_asset,
                                                    timestamp))
                if to_asset == 'BTC':
                    query_string += '&tryConversion=false'
                resp = urlopen(Request(query_string))
                resp = rlk_jsonloads(resp.read())
                print('DAILY PRICE OF ASSET: "{}"'.format(resp))
                if from_asset not in resp:
                    error_message = 'Failed to query cryptocompare for: "{}"'.format(
                        query_string)
                    raise ValueError(error_message)
                price = FVal(resp[from_asset][to_asset])

                if price == 0:
                    raise NoPriceForGivenTimestamp(
                        from_asset, to_asset,
                        tsToDate(timestamp, formatstr='%d/%m/%Y, %H:%M:%S'))

        return price
Beispiel #19
0
    def get_history(self, start_ts, end_ts, end_at_least_ts=None):
        """Gets or creates trades and loans history from start_ts to end_ts or if
        `end_at_least` is given and we have a cache history which satisfies it we
        return the cache
        """
        if end_at_least_ts is None:
            end_at_least_ts = end_ts

        historyfile_path = os.path.join(self.data_directory,
                                        TRADES_HISTORYFILE)
        if os.path.isfile(historyfile_path):
            with open(historyfile_path, 'r') as infile:
                try:
                    history_json_data = rlk_jsonloads(infile.read())
                except:
                    pass

                all_history_okay = data_up_todate(history_json_data, start_ts,
                                                  end_at_least_ts)
                poloniex_history_okay = True
                if self.poloniex is not None:
                    poloniex_history_okay = self.poloniex.check_trades_cache(
                        start_ts, end_at_least_ts) is not None
                kraken_history_okay = True
                if self.kraken is not None:
                    kraken_history_okay = self.kraken.check_trades_cache(
                        start_ts, end_at_least_ts) is not None
                bittrex_history_okay = True
                if self.bittrex is not None:
                    bittrex_history_okay = self.bittrex.check_trades_cache(
                        start_ts, end_at_least_ts) is not None
                binance_history_okay = True
                if self.binance is not None:
                    binance_history_okay = self.binance.check_trades_cache(
                        start_ts, end_at_least_ts) is not None

                if not self.read_manual_margin_positions:
                    marginfile_path = os.path.join(self.data_directory,
                                                   MARGIN_HISTORYFILE)
                    margin_file_contents = get_jsonfile_contents_or_empty_dict(
                        marginfile_path)
                    margin_history_is_okay = data_up_todate(
                        margin_file_contents, start_ts, end_at_least_ts)
                else:
                    margin_history_is_okay = True
                    margin_file_contents = do_read_manual_margin_positions(
                        self.data_directory)

                loansfile_path = os.path.join(self.data_directory,
                                              LOANS_HISTORYFILE)
                loan_file_contents = get_jsonfile_contents_or_empty_dict(
                    loansfile_path)
                loan_history_is_okay = data_up_todate(loan_file_contents,
                                                      start_ts,
                                                      end_at_least_ts)

                assetmovementsfile_path = os.path.join(
                    self.data_directory, ASSETMOVEMENTS_HISTORYFILE)
                asset_movements_contents = get_jsonfile_contents_or_empty_dict(
                    assetmovementsfile_path)
                asset_movements_history_is_okay = data_up_todate(
                    asset_movements_contents, start_ts, end_at_least_ts)

                eth_tx_log_path = os.path.join(self.data_directory,
                                               ETHEREUM_TX_LOGFILE)
                eth_tx_log_contents = get_jsonfile_contents_or_empty_dict(
                    eth_tx_log_path)
                eth_tx_log_history_history_is_okay = data_up_todate(
                    eth_tx_log_contents, start_ts, end_at_least_ts)

                if (all_history_okay and poloniex_history_okay
                        and kraken_history_okay and bittrex_history_okay
                        and binance_history_okay and margin_history_is_okay
                        and loan_history_is_okay
                        and asset_movements_history_is_okay
                        and eth_tx_log_history_history_is_okay):

                    history_trades = trades_from_dictlist(
                        history_json_data['data'], start_ts, end_ts)
                    if not self.read_manual_margin_positions:
                        margin_trades = trades_from_dictlist(
                            margin_file_contents['data'], start_ts, end_ts)
                    else:
                        margin_trades = margin_file_contents

                    eth_transactions = transactions_from_dictlist(
                        eth_tx_log_contents['data'], start_ts, end_ts)
                    asset_movements = asset_movements_from_dictlist(
                        asset_movements_contents['data'], start_ts, end_ts)

                    history_trades = include_external_trades(
                        self.data_directory, start_ts, end_ts, history_trades)

                    # make sure that this is the same as what is returned
                    # from create_history
                    return (history_trades, margin_trades,
                            loan_file_contents['data'], asset_movements,
                            eth_transactions)

        return self.create_history(start_ts, end_ts, end_at_least_ts)