Passed
Push — 2.3 ( ddf6c0...85dbcf )
by Andreas
01:41 queued 13s
created
Severity
1
#! /usr/bin/env python
2
"""API Wrapper for Bitcoin.de Trading API."""
3
4
import requests
5
import time
6
import json
7
import hmac
8
import hashlib
9
import logging
10
import codecs
11
import decimal
12
import inspect
13
import urllib
14
15
from future.standard_library import install_aliases
16
install_aliases()
17
18
from urllib.parse import urlencode
19
20
logging.basicConfig()
21
log = logging.getLogger(__name__)
22
requests_log = logging.getLogger("requests.packages.urllib3")
23
requests_log.propagate = True
24
25
__version__ = '2.3'
26
27
# disable unsecure SSL warning
28
requests.packages.urllib3.disable_warnings()
29
30
class ParameterBuilder(object):
31
    '''To verify given parameters for API.'''
32
    def __init__(self, avail_params, given_params, uri):
33
        self.verify_keys_and_values(avail_params, given_params)
34
        self.params = given_params
35
        self.create_url(uri)
36
37
    def verify_keys_and_values(self, avail_params, given_params):
38
        for k, v in given_params.items():
39
            if k not in avail_params:
40
                list_string = ', '.join(avail_params)
41
                raise KeyError("{} is not any of {}".format(k, list_string))
42
            if k == 'trading_pair':
43
                self.error_on_invalid_value(v, self.TRADING_PAIRS)
44
            elif k == 'type':
45
                self.error_on_invalid_value(v, self.ORDER_TYPES)
46
            elif k == 'currency':
47
                self.error_on_invalid_value(v, self.CURRENCIES)
48
            elif k == 'seat_of_bank':
49
                self.error_on_invalid_value(v, self.BANK_SEATS)
50
            elif k in ['min_trust_level', 'trust_level']:
51
                self.error_on_invalid_value(v, self.TRUST_LEVELS)
52
            elif k == 'payment_option':
53
                self.error_on_invalid_value(v, self.PAYMENT_OPTIONS)
54
            elif k == 'state':
55
                caller = inspect.stack()[2][3]
56
                if caller in ["showMyOrders", "showMyOrderDetails"]:
57
                    self.error_on_invalid_value(v, self.ORDER_STATES)
58
                elif caller in ["showMyTrades", "showMyTradesDetails"]:
59
                    self.error_on_invalid_value(v, self.TRADE_STATES)
60
61
    def error_on_invalid_value(self, value, list):
62
        if value not in list:
63
            list_string = ', '.join(str(x) for x in list)
64
            raise ValueError("{} is not any of {}".format(value, list_string))
65
66
    def create_url(self, uri):
67
        if self.params:
68
            self.encoded_string = urlencode(self.params)
69
            self.url = uri + '?' + self.encoded_string
70
        else:
71
            self.encoded_string = ''
72
            self.url = uri
73
74
75
    TRADING_PAIRS = ['btceur', 'bcheur', 'etheur', 'btgeur', 'bsveur']
76
    ORDER_TYPES = ['buy', 'sell']
77
    CURRENCIES = ['btc', 'bch', 'eth', 'btg', 'bsv']
78
    BANK_SEATS = ['AT', 'BE', 'BG', 'CH', 'CY', 'CZ',
79
                  'DE', 'DK', 'EE', 'ES', 'FI', 'FR',
80
                  'GB', 'GR', 'HR', 'HU', 'IE', 'IS',
81
                  'IT', 'LI', 'LT', 'LU', 'LV', 'MT',
82
                  'MQ', 'NL', 'NO', 'PL', 'PT', 'RO',
83
                  'SE', 'SI', 'SK']
84
    TRUST_LEVELS = ['bronze', 'silver', 'gold', 'platin']
85
    TRADE_STATES = [-1, 0, 1]
86
    ORDER_STATES = [-2, -1, 0]
87
    PAYMENT_OPTIONS = [1, 2, 3]
88
    TRADE_TYPES = ['all', 'buy', 'sell', 'inpayment',
89
                   'payout', 'affiliate', 'welcome_btc',
90
                   'buy_yubikey', 'buy_goldshop',
91
                   'buy_diamondshop', 'kickback',
92
                   'outgoing_fee_voluntary']
93
94
def HandleRequestsException(e):
95
    """Handle Exception from request."""
96
    log.warning(e)
97
98
99
def HandleAPIErrors(r):
100
    """To handle Errors from BTCDE API."""
101
    valid_status_codes = [200, 201, 204]
102
    if r.status_code not in valid_status_codes:
103
        content = r.json()
104
        errors = content.get('errors')
105
        log.warning('API Error Code: {}'.format(str(errors[0]['code'])))
106
        log.warning('API Error Message: {}'.format(errors[0]['message']))
107
        log.warning('API Error URL: {}'.format(r.url))
108
        return False
109
    else:
110
        return True
111
112
113
class Connection(object):
114
    """To provide connection credentials to the trading API"""
115
    def __init__(self, api_key, api_secret):
116
        self.api_key = api_key
117
        self.api_secret = api_secret
118
        # set initial self.nonce
119
        self.nonce = int(time.time() * 1000000)
120
        # Bitcoin.de API URI
121
        self.apihost = 'https://api.bitcoin.de'
122
        self.apiversion = 'v2'
123
        self.orderuri = self.apihost + '/' + self.apiversion + '/' + 'orders'
124
        self.tradeuri = self.apihost + '/' + self.apiversion + '/' + 'trades'
125
        self.accounturi = self.apihost + '/' + self.apiversion + '/' + 'account'
126
127
    def build_hmac_sign(self, md5string, method, url):
128
        hmac_data = '{method}#{url}#{key}#{nonce}#{md5}'\
129
                    .format(method=method, url=url,
130
                            key=self.api_key, nonce=str(self.nonce),
131
                            md5=md5string)
132
        hmac_signed = hmac.new(bytearray(self.api_secret.encode()), msg=hmac_data.encode(), digestmod=hashlib.sha256).hexdigest()
133
        return hmac_signed
134
135
    def set_header(self, url, method, encoded_string):
136
        # raise self.nonce before using
137
        self.nonce = int(time.time() * 1000000)
138
        if method == 'POST':
139
            md5_encoded_query_string = hashlib.md5(encoded_string.encode()).hexdigest()
140
        else:
141
            md5_encoded_query_string = hashlib.md5(b'').hexdigest()
142
        hmac_signed = self.build_hmac_sign(md5_encoded_query_string,
143
                                           method, url)
144
        # set header
145
        header = {'content-type':
146
                  'application/x-www-form-urlencoded; charset=utf-8',
147
                  'X-API-KEY': self.api_key,
148
                  'X-API-NONCE': str(self.nonce),
149
                  'X-API-SIGNATURE': hmac_signed }
150
        return header
151
152
    def send_request(self, url, method, header, encoded_string):
153
        if method == 'GET':
154
            r = requests.get(url, headers=(header),
155
                             stream=True, verify=False)
156
        elif method == 'POST':
157
            r = requests.post(url, headers=(header), data=encoded_string,
158
                              stream=True, verify=False)
159
        elif method == 'DELETE':
160
            r = requests.delete(url, headers=(header),
161
                                stream=True, verify=False)
162
        return r
0 ignored issues
show
The variable r does not seem to be defined for all execution paths.
Loading history...
163
164
    def APIConnect(self, method, params):
165
        """Transform Parameters to URL"""
166
        header = self.set_header(params.url, method,
167
                                 params.encoded_string)
168
        log.debug('Set Header: {}'.format(header))
169
        try:
170
            r = self.send_request(params.url, method, header,
171
                                  params.encoded_string)
172
            # Handle API Errors
173
            if HandleAPIErrors(r):
174
                # get results
175
                result = r.json(parse_float=decimal.Decimal)
176
            else:
177
                result = {}
178
        except requests.exceptions.RequestException as e:
179
            HandleRequestsException(e)
180
            result = {}
181
        return result
182
183
    def showOrderbook(self, order_type, trading_pair, **args):
184
        """Search Orderbook for offers."""
185
        params = {'type': order_type,
186
                  'trading_pair': trading_pair}
187
        params.update(args)
188
        avail_params = ['type', 'trading_pair', 'amount', 'price',
189
                        'order_requirements_fullfilled',
190
                        'only_kyc_full', 'only_express_orders',
191
                        'only_same_bankgroup', 'only_same_bic',
192
                        'seat_of_bank']
193
        p = ParameterBuilder(avail_params, params, self.orderuri)
194
        return self.APIConnect('GET', p)
195
196
197
    def createOrder(self, order_type, trading_pair, max_amount, price, **args):
198
        """Create a new Order."""
199
        # Build parameters
200
        params = {'type': order_type,
201
                  'trading_pair': trading_pair,
202
                  'max_amount': max_amount,
203
                  'price': price}
204
        params.update(args)
205
        avail_params = ['type', 'trading_pair', 'max_amount', 'price',
206
                        'min_amount', 'new_order_for_remaining_amount',
207
                        'min_trust_level', 'only_kyc_full', 'payment_option',
208
                        'seat_of_bank']
209
        p = ParameterBuilder(avail_params, params, self.orderuri)
210
        return self.APIConnect('POST', p)
211
212
213
    def deleteOrder(self, order_id, trading_pair):
214
        """Delete an Order."""
215
        # Build parameters
216
        params = {'order_id': order_id,
217
                  'trading_pair': trading_pair}
218
        avail_params = ['order_id', 'trading_pair']
219
        newuri = self.orderuri + "/" + order_id + "/" + trading_pair
220
        p = ParameterBuilder(avail_params, params, newuri)
221
        p.encoded_string = ''
222
        p.url = newuri
223
        return self.APIConnect('DELETE', p)
224
225
226
    def showMyOrders(self, **args):
227
        """Query and Filter own Orders."""
228
        # Build parameters
229
        params = args
230
        avail_params = ['type', 'trading_pair', 'state',
231
                        'date_start', 'date_end', 'page']
232
        newuri = self.orderuri + '/my_own'
233
        p = ParameterBuilder(avail_params, params, newuri)
234
        return self.APIConnect('GET', p)
235
236
237
    def showMyOrderDetails(self, order_id):
238
        """Details to an own Order."""
239
        newuri = self.orderuri + '/' + order_id
240
        p = ParameterBuilder({}, {}, newuri)
241
        return self.APIConnect('GET', p)
242
243
244
    def executeTrade(self, order_id, order_type, trading_pair, amount):
245
        """Buy/Sell on a specific Order."""
246
        newuri = self.tradeuri + '/' + order_id
247
        params = {'order_id': order_id,
248
                  'type': order_type,
249
                  'trading_pair': trading_pair,
250
                  'amount': amount}
251
        avail_params = ['order_id', 'type', 'trading_pair',
252
                        'amount']
253
        p = ParameterBuilder(avail_params, params, newuri)
254
        return self.APIConnect('POST', p)
255
256
257
    def showMyTrades(self, **args):
258
        """Query and Filter on past Trades."""
259
        # Build parameters
260
        params = args
261
        avail_params = ['type', 'trading_pair', 'state',
262
                        'date_start', 'date_end', 'page']
263
        p = ParameterBuilder(avail_params, params, self.tradeuri)
264
        return self.APIConnect('GET', p)
265
266
267
    def showMyTradeDetails(self, trade_id):
268
        """Details to a specific Trade."""
269
        newuri = self.tradeuri + '/' + trade_id
270
        params = {}
271
        p = ParameterBuilder({}, {}, newuri)
272
        return self.APIConnect('GET', p)
273
274
275
    def showAccountInfo(self):
276
        """Query on Account Infos."""
277
        p = ParameterBuilder({}, {}, self.accounturi)
278
        return self.APIConnect('GET', p)
279
280
281
    def showOrderbookCompact(self, trading_pair):
282
        """Bids and Asks in compact format."""
283
        params = {'trading_pair': trading_pair}
284
        # Build parameters
285
        avail_params = ['trading_pair']
286
        p = ParameterBuilder(avail_params, params,
287
                             self.orderuri + '/compact')
288
        return self.APIConnect('GET', p)
289
290
291
    def showPublicTradeHistory(self, trading_pair, **args):
292
        """All successful trades of the las 7 days."""
293
        params = {'trading_pair': trading_pair}
294
        params.update(args)
295
        avail_params = ['trading_pair', 'since_tid']
296
        p = ParameterBuilder(avail_params, params,
297
                             self.tradeuri + '/history')
298
        return self.APIConnect('GET', p)
299
300
301
    def showRates(self, trading_pair):
302
        """Query of the average rate last 3 and 12 hours."""
303
        newuri = self.apihost + '/' + self.apiversion + '/rates'
304
        params = {'trading_pair': trading_pair}
305
        avail_params = ['trading_pair']
306
        p = ParameterBuilder(avail_params, params, newuri)
307
        return self.APIConnect('GET', p)
308
309
310
    def showAccountLedger(self, currency, **args):
311
        """Query on Account statement."""
312
        params = {'currency': currency}
313
        params.update(args)
314
        avail_params = ['currency', 'type',
315
                        'datetime_start', 'datetime_end', 'page']
316
        p = ParameterBuilder(avail_params, params,
317
                             self.accounturi + '/ledger')
318
        return self.APIConnect('GET', p)
319