import os
import sys

root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
sys.path.append(root)

# ----------------------------------------------------------------------------

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

# ----------------------------------------------------------------------------
# -*- coding: utf-8 -*-

from ccxt.base.decimal_to_precision import DECIMAL_PLACES  # noqa E402
from ccxt.base.decimal_to_precision import TICK_SIZE  # noqa E402
import numbers  # noqa E402
import json  # noqa E402
from ccxt.base.precise import Precise  # noqa E402
from ccxt.base.errors import OnMaintenance  # noqa E402
from ccxt.base.errors import OperationFailed  # noqa E402

def log_template(exchange, method, entry):
    # there are cases when exchange is undefined (eg. base tests)
    id = exchange.id if (exchange is not None) else 'undefined'
    method_string = method if (method is not None) else 'undefined'
    entry_string = exchange.json(entry) if (exchange is not None) else ''
    return ' <<< ' + id + ' ' + method_string + ' ::: ' + entry_string + ' >>> '


def is_temporary_failure(e):
    return (isinstance(e, OperationFailed)) and (not (isinstance(e, OnMaintenance)))


def string_value(value):
    string_val = None
    if isinstance(value, str):
        string_val = value
    elif value is None:
        string_val = 'undefined'
    else:
        string_val = str(value)
    return string_val


def assert_type(exchange, skipped_properties, entry, key, format):
    if key in skipped_properties:
        return None
    # because "typeof" string is not transpilable without === 'name', we list them manually at this moment
    entry_key_val = exchange.safe_value(entry, key)
    format_key_val = exchange.safe_value(format, key)
    same_string = (isinstance(entry_key_val, str)) and (isinstance(format_key_val, str))
    same_numeric = (isinstance(entry_key_val, numbers.Real)) and (isinstance(format_key_val, numbers.Real))
    same_boolean = ((entry_key_val) or (entry_key_val is False)) and ((format_key_val) or (format_key_val is False))
    same_array = isinstance(entry_key_val, list) and isinstance(format_key_val, list)
    same_object = (isinstance(entry_key_val, dict)) and (isinstance(format_key_val, dict))
    result = (entry_key_val is None) or same_string or same_numeric or same_boolean or same_array or same_object
    return result


def assert_structure(exchange, skipped_properties, method, entry, format, empty_allowed_for=None, deep=False):
    log_text = log_template(exchange, method, entry)
    assert entry is not None, 'item is null/undefined' + log_text
    # get all expected & predefined keys for this specific item and ensure thos ekeys exist in parsed structure
    allow_empty_skips = exchange.safe_list(skipped_properties, 'allowNull', [])
    if empty_allowed_for is not None:
        empty_allowed_for = concat(empty_allowed_for, allow_empty_skips)
    if isinstance(format, list):
        assert isinstance(entry, list), 'entry is not an array' + log_text
        real_length = len(entry)
        expected_length = len(format)
        assert real_length == expected_length, 'entry length is not equal to expected length of ' + str(expected_length) + log_text
        for i in range(0, len(format)):
            empty_allowed_for_this_key = (empty_allowed_for is None) or exchange.in_array(i, empty_allowed_for)
            value = entry[i]
            if i in skipped_properties:
                continue
            # check when:
            # - it's not inside "allowe empty values" list
            # - it's not undefined
            if empty_allowed_for_this_key and (value is None):
                continue
            assert value is not None, str(i) + ' index is expected to have a value' + log_text
            # because of other langs, this is needed for arrays
            type_assertion = assert_type(exchange, skipped_properties, entry, i, format)
            assert type_assertion, str(i) + ' index does not have an expected type ' + log_text
    else:
        assert isinstance(entry, dict), 'entry is not an object' + log_text
        keys = list(format.keys())
        for i in range(0, len(keys)):
            key = keys[i]
            if key in skipped_properties:
                continue
            assert key in entry, '"' + string_value(key) + '" key is missing from structure' + log_text
            if key in skipped_properties:
                continue
            empty_allowed_for_this_key = (empty_allowed_for is None) or exchange.in_array(key, empty_allowed_for)
            value = entry[key]
            # check when:
            # - it's not inside "allowe empty values" list
            # - it's not undefined
            if empty_allowed_for_this_key and (value is None):
                continue
            # if it was in needed keys, then it should have value.
            assert value is not None, '"' + string_value(key) + '" key is expected to have a value' + log_text
            # add exclusion for info key, as it can be any type
            if key != 'info':
                type_assertion = assert_type(exchange, skipped_properties, entry, key, format)
                assert type_assertion, '"' + string_value(key) + '" key is neither undefined, neither of expected type' + log_text
                if deep:
                    if isinstance(value, dict):
                        assert_structure(exchange, skipped_properties, method, value, format[key], empty_allowed_for, deep)


def assert_timestamp(exchange, skipped_properties, method, entry, now_to_check=None, key_name_or_index='timestamp', allow_null=True):
    log_text = log_template(exchange, method, entry)
    skip_value = exchange.safe_value(skipped_properties, key_name_or_index)
    if skip_value is not None:
        return   # skipped
    is_date_time_object = isinstance(key_name_or_index, str)
    if is_date_time_object:
        assert (key_name_or_index in entry), 'timestamp key "' + key_name_or_index + '" is missing from structure' + log_text
    else:
        # if index was provided (mostly from fetchOHLCV) then we check if it exists, as mandatory
        assert not (entry[key_name_or_index] is None), 'timestamp index ' + string_value(key_name_or_index) + ' is undefined' + log_text
    ts = entry[key_name_or_index]
    assert ts is not None or allow_null, 'timestamp is null' + log_text
    if ts is not None:
        assert isinstance(ts, numbers.Real), 'timestamp is not numeric' + log_text
        assert isinstance(ts, int), 'timestamp should be an integer' + log_text
        min_ts = 1230940800000  # 03 Jan 2009 - first block
        max_ts = 2147483648000  # 19 Jan 2038 - max int
        assert ts > min_ts, 'timestamp is impossible to be before ' + str(min_ts) + ' (03.01.2009)' + log_text  # 03 Jan 2009 - first block
        assert ts < max_ts, 'timestamp more than ' + str(max_ts) + ' (19.01.2038)' + log_text  # 19 Jan 2038 - int32 overflows # 7258118400000  -> Jan 1 2200
        if now_to_check is not None:
            max_ms_offset = 60000  # 1 min
            assert ts < now_to_check + max_ms_offset, 'returned item timestamp (' + exchange.iso8601(ts) + ') is ahead of the current time (' + exchange.iso8601(now_to_check) + ')' + log_text


def assert_timestamp_and_datetime(exchange, skipped_properties, method, entry, now_to_check=None, key_name_or_index='timestamp', allow_null=True):
    log_text = log_template(exchange, method, entry)
    skip_value = exchange.safe_value(skipped_properties, key_name_or_index)
    if skip_value is not None:
        return
    assert_timestamp(exchange, skipped_properties, method, entry, now_to_check, key_name_or_index)
    is_date_time_object = isinstance(key_name_or_index, str)
    # only in case if the entry is a dictionary, thus it must have 'timestamp' & 'datetime' string keys
    if is_date_time_object:
        # we also test 'datetime' here because it's certain sibling of 'timestamp'
        assert ('datetime' in entry), '"datetime" key is missing from structure' + log_text
        dt = entry['datetime']
        assert dt is not None or allow_null, 'timestamp is null' + log_text
        if dt is not None:
            assert isinstance(dt, str), '"datetime" key does not have a string value' + log_text
            # there are exceptional cases, like getting microsecond-targeted string '2022-08-08T22:03:19.014680Z', so parsed unified timestamp, which carries only 13 digits (millisecond precision) can not be stringified back to microsecond accuracy, causing the bellow assertion to fail
            #    assert (dt === exchange.iso8601 (entry['timestamp']))
            # so, we have to compare with millisecond accururacy
            dt_parsed = exchange.parse8601(dt)
            ts_ms = entry['timestamp']
            diff = abs(dt_parsed - ts_ms)
            if diff >= 500:
                dt_parsed_string = exchange.iso8601(dt_parsed)
                dt_entry_string = exchange.iso8601(ts_ms)
                assert False, 'datetime is not iso8601 of timestamp:' + dt_parsed_string + '(string) != ' + dt_entry_string + '(from ts)' + log_text


def assert_currency_code(exchange, skipped_properties, method, entry, actual_code, expected_code=None, allow_null=True):
    if ('currency' in skipped_properties) or ('currencyIdAndCode' in skipped_properties):
        return
    log_text = log_template(exchange, method, entry)
    assert actual_code is not None or allow_null, 'currency code is null' + log_text
    if actual_code is not None:
        assert isinstance(actual_code, str), 'currency code should be either undefined or a string' + log_text
        assert (actual_code in exchange.currencies), 'currency code ("' + actual_code + '") should be present in exchange.currencies' + log_text
        if expected_code is not None:
            assert actual_code == expected_code, 'currency code in response ("' + string_value(actual_code) + '") should be equal to expected code ("' + string_value(expected_code) + '")' + log_text


def assert_valid_currency_id_and_code(exchange, skipped_properties, method, entry, currency_id, currency_code, allow_null=True):
    # this is exclusive exceptional key name to be used in `skip-tests.json`, to skip check for currency id and code
    if ('currency' in skipped_properties) or ('currencyIdAndCode' in skipped_properties):
        return
    log_text = log_template(exchange, method, entry)
    undefined_values = currency_id is None and currency_code is None
    defined_values = currency_id is not None and currency_code is not None
    assert undefined_values or defined_values, 'currencyId and currencyCode should be either both defined or both undefined' + log_text
    assert defined_values or allow_null, 'currency code and id is not defined' + log_text
    if defined_values:
        # check by code
        currency_by_code = exchange.currency(currency_code)
        assert currency_by_code['id'] == currency_id, 'currencyId "' + string_value(currency_id) + '" does not match currency id from instance: "' + string_value(currency_by_code['id']) + '"' + log_text
        # check by id
        currency_by_id = exchange.safe_currency(currency_id)
        assert currency_by_id['code'] == currency_code, 'currencyCode ' + string_value(currency_code) + ' does not match currency of id: ' + string_value(currency_id) + log_text


def assert_symbol(exchange, skipped_properties, method, entry, key, expected_symbol=None, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    actual_symbol = exchange.safe_string(entry, key)
    if actual_symbol is not None:
        assert isinstance(actual_symbol, str), 'symbol should be either undefined or a string' + log_text
    if expected_symbol is not None:
        assert actual_symbol == expected_symbol, 'symbol in response ("' + string_value(actual_symbol) + '") should be equal to expected symbol ("' + string_value(expected_symbol) + '")' + log_text
    defined_values = actual_symbol is not None and expected_symbol is not None
    assert defined_values or allow_null, 'symbols are not defined' + log_text


def assert_symbol_in_markets(exchange, skipped_properties, method, symbol):
    log_text = log_template(exchange, method, {})
    assert (symbol in exchange.markets), 'symbol should be present in exchange.symbols' + log_text


def assert_greater(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    value = exchange.safe_string(entry, key)
    assert value is not None or allow_null, 'value is null' + log_text
    if value is not None:
        assert Precise.string_gt(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be > ' + string_value(compare_to) + log_text


def assert_greater_or_equal(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    value = exchange.safe_string(entry, key)
    assert value is not None or allow_null, 'value is null' + log_text
    if value is not None and compare_to is not None:
        assert Precise.string_ge(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be >= ' + string_value(compare_to) + log_text


def assert_less(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    value = exchange.safe_string(entry, key)
    assert value is not None or allow_null, 'value is null' + log_text
    if value is not None and compare_to is not None:
        assert Precise.string_lt(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be < ' + string_value(compare_to) + log_text


def assert_less_or_equal(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    value = exchange.safe_string(entry, key)
    assert value is not None or allow_null, 'value is null' + log_text
    if value is not None and compare_to is not None:
        assert Precise.string_le(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be <= ' + string_value(compare_to) + log_text


def assert_equal(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    value = exchange.safe_string(entry, key)
    assert value is not None or allow_null, 'value is null' + log_text
    if value is not None and compare_to is not None:
        assert Precise.string_eq(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be equal to ' + string_value(compare_to) + log_text


def assert_non_equal(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    value = exchange.safe_string(entry, key)
    assert value is not None or allow_null, 'value is null' + log_text
    if value is not None:
        assert not Precise.string_eq(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected not to be equal to ' + string_value(compare_to) + log_text


def assert_in_array(exchange, skipped_properties, method, entry, key, expected_array, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    value = exchange.safe_value(entry, key)
    assert value is not None or allow_null, 'value is null' + log_text
    # todo: remove undefined check
    if value is not None:
        stingified_array_value = exchange.json(expected_array)  # don't use expectedArray.join (','), as it bugs in other languages, if values are bool, undefined or etc..
        assert exchange.in_array(value, expected_array), '"' + string_value(key) + '" key (value "' + string_value(value) + '") is not from the expected list : [' + stingified_array_value + ']' + log_text


def assert_fee_structure(exchange, skipped_properties, method, entry, key, allow_null=True):
    log_text = log_template(exchange, method, entry)
    key_string = string_value(key)
    if isinstance(key, int):
        assert isinstance(entry, list), 'fee container is expected to be an array' + log_text
        assert key < len(entry), 'fee key ' + key_string + ' was expected to be present in entry' + log_text
    else:
        assert isinstance(entry, dict), 'fee container is expected to be an object' + log_text
        assert key in entry, 'fee key "' + key + '" was expected to be present in entry' + log_text
    fee_object = exchange.safe_value(entry, key)
    assert fee_object is not None or allow_null, 'fee object is null' + log_text
    # todo: remove undefined check to make stricter
    if fee_object is not None:
        assert 'cost' in fee_object, key_string + ' fee object should contain "cost" key' + log_text
        if fee_object['cost'] is None:
            return   # todo: remove undefined check to make stricter
        assert isinstance(fee_object['cost'], numbers.Real), key_string + ' "cost" must be numeric type' + log_text
        # assertGreaterOrEqual (exchange, skippedProperties, method, feeObject, 'cost', '0'); # fee might be negative in the case of a rebate or reward
        assert 'currency' in fee_object, '"' + key_string + '" fee object should contain "currency" key' + log_text
        assert_currency_code(exchange, skipped_properties, method, entry, fee_object['currency'])


def assert_timestamp_order(exchange, method, code_or_symbol, items, ascending=True):
    for i in range(0, len(items)):
        if i > 0:
            current_ts = items[i - 1]['timestamp']
            next_ts = items[i]['timestamp']
            if current_ts is not None and next_ts is not None:
                ascending_or_descending = 'ascending' if ascending else 'descending'
                comparison = (current_ts <= next_ts) if ascending else (current_ts >= next_ts)
                assert comparison, exchange.id + ' ' + method + ' ' + string_value(code_or_symbol) + ' must return a ' + ascending_or_descending + ' sorted array of items by timestamp, but ' + str(current_ts) + ' is opposite with its next ' + str(next_ts) + ' ' + exchange.json(items)


def assert_integer(exchange, skipped_properties, method, entry, key, allow_null=True):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    if entry is not None:
        value = exchange.safe_value(entry, key)
        assert value is not None or allow_null, 'value is null' + log_text
        if value is not None:
            is_integer = isinstance(value, int)
            assert is_integer, '"' + string_value(key) + '" key (value "' + string_value(value) + '") is not an integer' + log_text


def check_precision_accuracy(exchange, skipped_properties, method, entry, key):
    if key in skipped_properties:
        return
    if exchange.is_tick_precision():
        # TICK_SIZE should be above zero
        assert_greater(exchange, skipped_properties, method, entry, key, '0')
        # the below array of integers are inexistent tick-sizes (theoretically technically possible, but not in real-world cases), so their existence in our case indicates to incorrectly implemented tick-sizes, which might mistakenly be implemented with DECIMAL_PLACES, so we throw error
        decimal_numbers = ['2', '3', '4', '5', '6', '7', '8', '9', '11', '12', '13', '14', '15', '16']
        for i in range(0, len(decimal_numbers)):
            num = decimal_numbers[i]
            num_str = num
            assert_non_equal(exchange, skipped_properties, method, entry, key, num_str)
    else:
        # todo: significant-digits return doubles from `this.parseNumber`, so for now can't assert against integer atm
        # assertInteger (exchange, skippedProperties, method, entry, key); # should be integer
        assert_less_or_equal(exchange, skipped_properties, method, entry, key, '18')  # should be under 18 decimals
        assert_greater_or_equal(exchange, skipped_properties, method, entry, key, '-8')  # in real-world cases, there would not be less than that


def fetch_best_bid_ask(exchange, method, symbol):
    log_text = log_template(exchange, method, {})
    # find out best bid/ask price
    best_bid = None
    best_ask = None
    used_method = None
    if exchange.has['fetchOrderBook']:
        used_method = 'fetchOrderBook'
        orderbook = exchange.fetch_order_book(symbol)
        bids = exchange.safe_list(orderbook, 'bids')
        asks = exchange.safe_list(orderbook, 'asks')
        best_bid_array = exchange.safe_list(bids, 0)
        best_ask_array = exchange.safe_list(asks, 0)
        best_bid = exchange.safe_number(best_bid_array, 0)
        best_ask = exchange.safe_number(best_ask_array, 0)
    elif exchange.has['fetchBidsAsks']:
        used_method = 'fetchBidsAsks'
        tickers = exchange.fetch_bids_asks([symbol])
        ticker = exchange.safe_dict(tickers, symbol)
        best_bid = exchange.safe_number(ticker, 'bid')
        best_ask = exchange.safe_number(ticker, 'ask')
    elif exchange.has['fetchTicker']:
        used_method = 'fetchTicker'
        ticker = exchange.fetch_ticker(symbol)
        best_bid = exchange.safe_number(ticker, 'bid')
        best_ask = exchange.safe_number(ticker, 'ask')
    elif exchange.has['fetchTickers']:
        used_method = 'fetchTickers'
        tickers = exchange.fetch_tickers([symbol])
        ticker = exchange.safe_dict(tickers, symbol)
        best_bid = exchange.safe_number(ticker, 'bid')
        best_ask = exchange.safe_number(ticker, 'ask')
    #
    assert best_bid is not None and best_ask is not None, log_text + ' ' + exchange.id + ' could not get best bid/ask for ' + symbol + ' using ' + used_method + ' while testing ' + method
    return [best_bid, best_ask]


def fetch_order(exchange, symbol, order_id, skipped_properties):
    fetched_order = None
    original_id = order_id
    # set 'since' to 5 minute ago for optimal results
    since_time = exchange.milliseconds() - 1000 * 60 * 5
    # iterate
    methods_singular = ['fetchOrder', 'fetchOpenOrder', 'fetchClosedOrder', 'fetchCanceledOrder']
    for i in range(0, len(methods_singular)):
        singular_fetch_name = methods_singular[i]
        if exchange.has[singular_fetch_name]:
            current_order = exchange[singular_fetch_name](original_id, symbol)
            # if there is an id inside the order, it means the order was fetched successfully
            if current_order['id'] == original_id:
                fetched_order = current_order
                break
    #
    # search through plural methods
    if fetched_order is None:
        methods_plural = ['fetchOrders', 'fetchOpenOrders', 'fetchClosedOrders', 'fetchCanceledOrders']
        for i in range(0, len(methods_plural)):
            plural_fetch_name = methods_plural[i]
            if exchange.has[plural_fetch_name]:
                orders = exchange[plural_fetch_name](symbol, since_time)
                found = False
                for j in range(0, len(orders)):
                    current_order = orders[j]
                    if current_order['id'] == original_id:
                        fetched_order = current_order
                        found = True
                        break
                if found:
                    break
    return fetched_order


def assert_order_state(exchange, skipped_properties, method, order, asserted_status, strict_check):
    # note, `strictCheck` is `true` only from "fetchOrder" cases
    log_text = log_template(exchange, method, order)
    msg = 'order should be ' + asserted_status + ', but it was not asserted' + log_text
    filled = exchange.safe_string(order, 'filled')
    amount = exchange.safe_string(order, 'amount')
    # shorthand variables
    status_undefined = (order['status'] is None)
    status_open = (order['status'] == 'open')
    status_closed = (order['status'] == 'closed')
    status_clanceled = (order['status'] == 'canceled')
    filled_defined = (filled is not None)
    amount_defined = (amount is not None)
    condition = None
    #
    # ### OPEN STATUS
    #
    # if strict check, then 'status' must be 'open' and filled amount should be less then whole order amount
    strict_open = status_open and (filled_defined and amount_defined and filled < amount)
    # if non-strict check, then accept & ignore undefined values
    nonstrict_open = (status_open or status_undefined) and ((not filled_defined or not amount_defined) or Precise.string_lt(filled, amount))
    # check
    if asserted_status == 'open':
        condition = strict_open if strict_check else nonstrict_open
        assert condition, msg
        return
    #
    # ### CLOSED STATUS
    #
    # if strict check, then 'status' must be 'closed' and filled amount should be equal to the whole order amount
    closed_strict = status_closed and (filled_defined and amount_defined and Precise.string_eq(filled, amount))
    # if non-strict check, then accept & ignore undefined values
    closed_non_strict = (status_closed or status_undefined) and ((not filled_defined or not amount_defined) or Precise.string_eq(filled, amount))
    # check
    if asserted_status == 'closed':
        condition = closed_strict if strict_check else closed_non_strict
        assert condition, msg
        return
    #
    # ### CANCELED STATUS
    #
    # if strict check, then 'status' must be 'canceled' and filled amount should be less then whole order amount
    canceled_strict = status_clanceled and (filled_defined and amount_defined and Precise.string_lt(filled, amount))
    # if non-strict check, then accept & ignore undefined values
    canceled_non_strict = (status_clanceled or status_undefined) and ((not filled_defined or not amount_defined) or Precise.string_lt(filled, amount))
    # check
    if asserted_status == 'canceled':
        condition = canceled_strict if strict_check else canceled_non_strict
        assert condition, msg
        return
    #
    # ### CLOSED_or_CANCELED STATUS
    #
    if asserted_status == 'closed_or_canceled':
        condition = (closed_strict or canceled_strict) if strict_check else (closed_non_strict or canceled_non_strict)
        assert condition, msg
        return


def get_active_markets(exchange, include_unknown=True):
    filtered_active = exchange.filter_by(exchange.markets, 'active', True)
    if include_unknown:
        filtered_undefined = exchange.filter_by(exchange.markets, 'active', None)
        return exchange.array_concat(filtered_active, filtered_undefined)
    return filtered_active


def remove_proxy_options(exchange, skipped_properties):
    proxy_url = exchange.check_proxy_url_settings()
    [http_proxy, https_proxy, socks_proxy] = exchange.check_proxy_settings()
    # because of bug in transpiled, about `.proxyUrl` being transpiled into `.proxy_url`, we have to use this workaround
    exchange.set_property(exchange, 'proxyUrl', None)
    exchange.set_property(exchange, 'proxy_url', None)
    exchange.set_property(exchange, 'httpProxy', None)
    exchange.set_property(exchange, 'http_proxy', None)
    exchange.set_property(exchange, 'httpsProxy', None)
    exchange.set_property(exchange, 'https_proxy', None)
    exchange.set_property(exchange, 'socksProxy', None)
    exchange.set_property(exchange, 'socks_proxy', None)
    return [proxy_url, http_proxy, https_proxy, socks_proxy]


def set_proxy_options(exchange, skipped_properties, proxy_url, http_proxy, https_proxy, socks_proxy):
    exchange.proxy_url = proxy_url
    exchange.http_proxy = http_proxy
    exchange.https_proxy = https_proxy
    exchange.socks_proxy = socks_proxy


def concat(a=None, b=None):
    # we use this method temporarily, because of ast-transpiler issue across langs
    if a is None:
        return b
    elif b is None:
        return a
    else:
        result = []
        for i in range(0, len(a)):
            result.append(a[i])
        for j in range(0, len(b)):
            result.append(b[j])
        return result


def assert_non_emtpy_array(exchange, skipped_properties, method, entry, hint=None):
    log_text = log_template(exchange, method, entry)
    if hint is not None:
        log_text = log_text + ' ' + hint
    assert isinstance(entry, list), 'response is expected to be an array' + log_text
    if not ('emptyResponse' in skipped_properties):
        return
    assert len(entry) > 0, 'response is expected to be a non-empty array' + log_text + ' (add "emptyResponse" in skip-tests.json to skip this check)'


def assert_round_minute_timestamp(exchange, skipped_properties, method, entry, key):
    if key in skipped_properties:
        return
    log_text = log_template(exchange, method, entry)
    ts = exchange.safe_string(entry, key)
    assert Precise.string_mod(ts, '60000') == '0', 'timestamp should be a multiple of 60 seconds (1 minute)' + log_text


def deep_equal(a, b):
    return json.dumps(a) == json.dumps(b)


def assert_deep_equal(exchange, skipped_properties, method, a, b):
    log_text = log_template(exchange, method, {})
    assert deep_equal(a, b), 'two dicts do not match: ' + json.dumps(a) + ' != ' + json.dumps(b) + log_text
