Ошибка при попытке доступа к API binance: объект Binance не имеет атрибута binance_keys - PullRequest
0 голосов
/ 14 июля 2020

Помогите, я кодирую бота, работающего с Binance, и получаю сообщение об ошибке, когда пытаюсь получить доступ к API для совершения покупки. ключ API хранится в текстовом файле:

строка 332, в signRequest signature = hma c .new (self.binance_keys ['secret_key']. encode ('utf-8'), query_string. encode ('utf-8'), hashlib.sha256) AttributeError: объект 'Binance' не имеет атрибута 'binance_keys'

Кодовая строка 332:

import requests 
import json
import decimal
import hmac
import time
import pandas as pd
import hashlib
from decimal import Decimal

request_delay = 1000

class Binance:

    ORDER_STATUS_NEW = 'NEW'
    ORDER_STATUS_PARTIALLY_FILLED = 'PARTIALLY_FILLED'
    ORDER_STATUS_FILLED = 'FILLED'
    ORDER_STATUS_CANCELED = 'CANCELED'
    ORDER_STATUS_PENDING_CANCEL = 'PENDING_CANCEL'
    ORDER_STATUS_REJECTED = 'REJECTED'
    ORDER_STATUS_EXPIRED = 'EXPIRED'

    SIDE_BUY = 'BUY'
    SIDE_SELL = 'SELL'

    ORDER_TYPE_LIMIT = 'LIMIT'
    ORDER_TYPE_MARKET = 'MARKET'
    ORDER_TYPE_STOP_LOSS = 'STOP_LOSS'
    ORDER_TYPE_STOP_LOSS_LIMIT = 'STOP_LOSS_LIMIT'
    ORDER_TYPE_TAKE_PROFIT = 'TAKE_PROFIT'
    ORDER_TYPE_TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT'
    ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER'

    KLINE_INTERVALS = ['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w', '1M']

    def __init__(self, filename=None):

        self.base = 'https://api.binance.com'

        self.endpoints = {
            "order": '/api/v3/order',
            "testOrder": '/api/v3/order/test',
            "allOrders": '/api/v3/allOrders',
            "klines": '/api/v3/klines',
            "exchangeInfo": '/api/v3/exchangeInfo',
            "24hrTicker" : '/api/v3/ticker/24hr',
            "averagePrice" : '/api/v3/avgPrice',
            "orderBook" : '/api/v3/depth',
            "account" : '/api/v3/account'
        }
        self.account_access = False

        if filename == None:
            return
    
        f = open(filename, "r")
        contents = []
        if f.mode == 'r':
            contents = f.read().split('\n')

        self.binance_keys = dict(api_key = contents[0], secret_key=contents[1])

        self.headers = {"X-MBX-APIKEY": self.binance_keys['api_key']}

        self.account_access = True
    
    def _get(self, url, params=None, headers=None) -> dict:
        """ Makes a Get Request """
        try: 
            response = requests.get(url, params=params, headers=headers)
            data = json.loads(response.text)
            data['url'] = url
        except Exception as e:
            print("Exception occured when trying to access "+url)
            print(e)
            data = {'code': '-1', 'url':url, 'msg': e}
        return data

    def _post(self, url, params=None, headers=None) -> dict:
        """ Makes a Post Request """
        try: 
            response = requests.post(url, params=params, headers=headers)
            data = json.loads(response.text)
            data['url'] = url
        except Exception as e:
            print("Exception occured when trying to access "+url)
            print(e)
            data = {'code': '-1', 'url':url, 'msg': e}
        return data

    def GetTradingSymbols(self, quoteAssets:list=None):
        ''' Gets All symbols which are tradable (currently) '''
        url = self.base + self.endpoints["exchangeInfo"]
        data = self._get(url)
        if data.__contains__('code'):
            return []

        symbols_list = []
        for pair in data['symbols']:
            if pair['status'] == 'TRADING':
                if quoteAssets != None and pair['quoteAsset'] in quoteAssets:
                    symbols_list.append(pair['symbol'])

        return symbols_list

    def GetSymbolDataOfSymbols(self, symbols:list=None):
        ''' Gets All symbols which are tradable (currently) '''
        url = self.base + self.endpoints["exchangeInfo"]
        data = self._get(url)
        if data.__contains__('code'):
            return []

        symbols_list = []

        for pair in data['symbols']:
            if pair['status'] == 'TRADING':
                if symbols != None and pair['symbol'] in symbols:
                    symbols_list.append(pair)

        return symbols_list

    def GetSymbolKlinesExtra(self, symbol:str, interval:str, limit:int=1000, end_time=False):
        # Basicall, we will be calling the GetSymbolKlines as many times as we need 
        # in order to get all the historical data required (based on the limit parameter)
        # and we'll be merging the results into one long dataframe.

        repeat_rounds = 0
        if limit > 1000:
            repeat_rounds = int(limit/1000)
        initial_limit = limit % 1000
        if initial_limit == 0:
            initial_limit = 1000
        # First, we get the last initial_limit candles, starting at end_time and going
        # backwards (or starting in the present moment, if end_time is False)
        df = self.GetSymbolKlines(symbol, interval, limit=initial_limit, end_time=end_time)
        while repeat_rounds > 0:
            # Then, for every other 1000 candles, we get them, but starting at the beginning
            # of the previously received candles.
            df2 = self.GetSymbolKlines(symbol, interval, limit=1000, end_time=df['time'][0])
            df = df2.append(df, ignore_index = True)
            repeat_rounds = repeat_rounds - 1
        
        return df

    def GetAccountData(self) -> dict:
        """ Gets Balances & Account Data """

        url = self.base + self.endpoints["account"]
        
        params = {
        'recvWindow': 6000,
        'timestamp': int(round(time.time()*1000)) + request_delay
        }
        self.signRequest(params)
        
        return self._get(url, params, self.headers)

    def Get24hrTicker(self, symbol:str):
        url = self.base + self.endpoints['24hrTicker'] + "?symbol="+symbol
        return self._get(url)

    def GetSymbolKlines(self, symbol:str, interval:str, limit:int=1000, end_time=False):
        ''' 
        Gets trading data for one symbol 
        
        Parameters
        --
            symbol str:        The symbol for which to get the trading data

            interval str:      The interval on which to get the trading data
                minutes      '1m' '3m' '5m' '15m' '30m'
                hours        '1h' '2h' '4h' '6h' '8h' '12h'
                days         '1d' '3d'
                weeks        '1w'
                months       '1M;
        '''

        if limit > 1000:
            return self.GetSymbolKlinesExtra(symbol, interval, limit, end_time)
        
        params = '?&symbol='+symbol+'&interval='+interval+'&limit='+str(limit)
        if end_time:
            params = params + '&endTime=' + str(int(end_time))

        url = self.base + self.endpoints['klines'] + params

        # download data
        data = requests.get(url)
        dictionary = json.loads(data.text)

        # put in dataframe and clean-up
        df = pd.DataFrame.from_dict(dictionary)
        df = df.drop(range(6, 12), axis=1)

        # rename columns
        col_names = ['time', 'open', 'high', 'low', 'close', 'volume']
        df.columns = col_names

        # transform values from strings to floats
        for col in col_names:
            df[col] = df[col].astype(float)

        df['date'] = pd.to_datetime(df['time'] * 1000000, infer_datetime_format=True)

        return df
    
    def PlaceOrderFromDict(self, params, test:bool=False):
        """ Places order from params dict """

        params['recvWindow'] = 5000
        params['timestamp'] = int(round(time.time()*1000)) + request_delay
        
        self.signRequest(params)
        url = ''
        if test: 
            url = self.base + self.endpoints['testOrder']
        else:
            url = self.base + self.endpoints['order']
        return self._post(url, params, self.headers)

    def PlaceOrder(self, symbol:str, side:str, type:str, quantity:float=0, price:float=0, test:bool=True):
        '''
        Places an order on Binance
        Parameters
        --
            symbol str:        The symbol for which to get the trading data
            side str:          The side of the order 'BUY' or 'SELL'
            type str:          The type, 'LIMIT', 'MARKET', 'STOP_LOSS'
            quantity float:    .....

        '''

        params = {
            'symbol': symbol,
            'side': side,           # BUY or SELL
            'type': type,               # MARKET, LIMIT, STOP LOSS etc
            'quoteOrderQty': quantity,
            'recvWindow': 5000,
            'timestamp': int(round(time.time()*1000)) + request_delay
        }

        if type != 'MARKET':
            params['timeInForce'] = 'GTC'
            params['price'] = Binance.floatToString(price)

        self.signRequest(params)

        url = ''
        if test: 
            url = self.base + self.endpoints['testOrder']
        else:
            url = self.base + self.endpoints['order']

        return self._post(url, params=params, headers=self.headers)

    def CancelOrder(self, symbol:str, orderId:str):
        '''
            Cancels the order on a symbol based on orderId
        '''

        params = {
            'symbol': symbol,
            'orderId' : orderId,
            'recvWindow': 5000,
            'timestamp': int(round(time.time()*1000)) + request_delay
        }

        self.signRequest(params)

        url = self.base + self.endpoints['order']

        try: 
            response = requests.delete(url, params=params, headers=self.headers)
            data = response.text
        except Exception as e:
            print("Exception occured when trying to cancel order on "+url)
            print(e)
            data = {'code': '-1', 'msg':e}
        
        return json.loads(data)

    def GetOrderInfo(self, symbol:str, orderId:str):
        '''
            Gets info about an order on a symbol based on orderId
        '''

        params = {
            'symbol': symbol,
            'origClientOrderId' : orderId,
            'recvWindow': 5000,
            'timestamp': int(round(time.time()*1000)) + request_delay
        }

        self.signRequest(params)

        url = self.base + self.endpoints['order']

        return self._get(url, params=params, headers=self.headers)

    def GetAllOrderInfo(self, symbol:str):
        '''
            Gets info about all order on a symbol
        '''

        params = {
            'symbol': symbol,
            'timestamp': int(round(time.time()*1000)) + request_delay
        }

        self.signRequest(params)

        url = self.base + self.endpoints['allOrders']

        try: 
            response = requests.get(url, params=params, headers=self.headers)
            data = response.text
        except Exception as e:
            print("Exception occured when trying to get info on all orders on "+url)
            print(e)
            data = {'code': '-1', 'msg':e}

        return json.loads(data)

    def signRequest(self, params:dict):
        ''' Signs the request to the Binance API '''

        query_string = '&'.join(["{}={}".format(d, params[d]) for d in params])
        signature = hmac.new(self.binance_keys['secret_key'].encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256)
        params['signature'] = signature.hexdigest()
    
    @classmethod
    def floatToString(cls, f:float):
        ''' Converts the given float to a string,
        without resorting to the scientific notation '''

        ctx = decimal.Context()
        ctx.prec = 12
        d1 = ctx.create_decimal(repr(f))
        return format(d1, 'f')

    @classmethod
    def get10Factor(cls, num):
        """ Returns the number of 0s before the first non-0 digit of a number 
        (if |num| is < than 1) or negative the number of digits between the first 
        integer digit and the last, (if |num| >= 1) 
        
        get10Factor(0.00000164763) = 6
        get10Factor(1600623.3) = -6
        """
        p = 0
        for i in range(-20, 20):
            if num == num % 10**i:
                p = -(i - 1)
                break
        return p

    @classmethod
    def RoundToValidPrice(cls, symbol_data, desired_price, round_up:bool=False) -> Decimal:
        """ Returns the minimum quantity of a symbol we can buy,
        closest to desiredPrice """
        
        pr_filter = {}
        
        for fil in symbol_data["filters"]:
            if fil["filterType"] == "PRICE_FILTER":
                pr_filter = fil
                break
        
        if not pr_filter.keys().__contains__("tickSize"):
            raise Exception("Couldn't find tickSize or PRICE_FILTER in symbol_data.")
            return

        round_off_number = int(cls.get10Factor((float(pr_filter["tickSize"]))))

        number = round(Decimal(desired_price), round_off_number)
        if round_up:
            number = number + Decimal(pr_filter["tickSize"])

        return number

    @classmethod
    def RoundToValidQuantity(cls, symbol_data, desired_quantity, round_up:bool=False) -> Decimal:
        """ Returns the minimum quantity of a symbol we can buy,
        closest to desiredPrice """
        
        lot_filter = {}
        
        for fil in symbol_data["filters"]:
            if fil["filterType"] == "LOT_SIZE":
                lot_filter = fil
                break
        
        if not lot_filter.keys().__contains__("stepSize"):
            raise Exception("Couldn't find stepSize or PRICE_FILTER in symbol_data.")
            return

        round_off_number = int(cls.get10Factor((float(lot_filter["stepSize"]))))

        number = round(Decimal(desired_quantity), round_off_number)
        if round_up:
            number = number + Decimal(lot_filter["stepSize"])

        return number


def Main():

    symbol = 'NEOBTC'
    client_id = '73a40bae-61c7-11ea-8e67-f40f241d61b4'
    exchange = Binance('credentials.txt')

    d = exchange.GetOrderInfo(symbol, client_id)
    print(d)


if __name__ == '__main__':
    Main()
...