问题
I am trying to place a trade on the US version of Binance API without the use of external libraries.
I can successfully get prices and display my account balance using GET requests and urllib
. The first example code works and I can pass my API_KEY
and SECRET_KEY
without issues (those values are private, they aren't displayed here and are located in settings.py
).
Placing a trade requires POST and I'm not sure where I went wrong, my POST requests don't work but GET requests work fine. To my understanding of the docs to make a POST request I'm supposed to encode the parameters using urllib.parse.urlencode()
and pass it into the data
parameter in urllib.request.Request()
.
Doing so doesn't throw an error but when I try to open the request with urllib.request.urlopen()
I get an error:
Traceback (most recent call last):
File "C:\Users\user\PycharmProjects\test\test.py", line 80, in <module> place_trade(symbol='BTCUSD', side='BUY', order_type='MARKET', quantity=1)
File "C:\Users\user\PycharmProjects\test\test.py", line 73, in place_trade response = urllib.request.urlopen(req)
File "C:\Users\user\AppData\Local\Programs\Python\Python39\lib\urllib\request.py", line 214, in urlopen return opener.open(url, data, timeout)
File "C:\Users\user\AppData\Local\Programs\Python\Python39\lib\urllib\request.py", line 523, in open response = meth(req, response)
File "C:\Users\user\AppData\Local\Programs\Python\Python39\lib\urllib\request.py", line 632, in http_response response = self.parent.error(
File "C:\Users\user\AppData\Local\Programs\Python\Python39\lib\urllib\request.py", line 561, in error return self._call_chain(*args)
File "C:\Users\user\AppData\Local\Programs\Python\Python39\lib\urllib\request.py", line 494, in _call_chain result = func(*args)
File "C:\Users\user\AppData\Local\Programs\Python\Python39\lib\urllib\request.py", line 641, in http_error_default raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 500: Internal Server Error
The HTTP Return Codes state:
HTTP 5XX return codes are used for internal errors; the issue is on Binance's side. It is important to NOT treat this as a failure operation; the execution status is UNKNOWN and could have been a success.
However I don't believe that to be the case because I can run the other code's function get_account_balance()
without issues. I'm not sure what I'm doing wrong, both codes are almost identical except for making GET vs. POST requests.
Code to get account balance - works fine:
import json
import time
import hmac
import settings
import hashlib
import urllib.parse
import urllib.request
def get_account_balance():
# Setup header with API_KEY
headers = {'X-MBX-APIKEY': settings.API_KEY}
# Params requires timestamp in MS
params = {'timestamp': int(time.time() * 1000)}
# Encode params into url
url = 'https://api.binance.us/api/v3/account?' + urllib.parse.urlencode(params)
# Create a HMAC SHA256 signature
secret = bytes(settings.SECRET_KEY.encode('utf-8'))
signature = hmac.new(secret, urllib.parse.urlencode(params).encode('utf-8'), hashlib.sha256).hexdigest()
# Add signature to url
url += f'&signature={signature}'
# Make a request
req = urllib.request.Request(url, headers=headers)
# Read and decode response
response = urllib.request.urlopen(req).read().decode('utf-8')
# Convert to json
response_json = json.loads(response)
# Print balances for all coins not at 0
for entry in response_json['balances']:
if entry['free'] == '0.00000000':
continue
print(entry)
get_account_balance()
Code to place trade - does not work:
import json
import time
import hmac
import settings
import hashlib
import urllib.parse
import urllib.request
def place_trade(symbol, side, order_type, quantity):
# Setup header with API_KEY
headers = {'X-MBX-APIKEY': settings.API_KEY}
# Params require symbol, side, type, quantity and timestamp (for market orders)
params = {
'symbol': symbol,
'side': side,
'type': order_type,
'quantity': quantity,
'timestamp': int(time.time() * 1000)
}
# Encode params into url
url = 'https://api.binance.us/api/v3/order/test?' + urllib.parse.urlencode(params)
# Create a HMAC SHA256 signature
secret = bytes(settings.SECRET_KEY.encode('utf-8'))
signature = hmac.new(secret, urllib.parse.urlencode(params).encode('utf-8'), hashlib.sha256).hexdigest()
# Add signature to url
url += f'&signature={signature}'
# Encode params
data = urllib.parse.urlencode(params).encode('ascii')
# Make a POST request
req = urllib.request.Request(url, data, headers)
# Open request and convert to string and then to json
response = urllib.request.urlopen(req) # <- line with error
response_str = response.read().decode('utf-8')
response_json = json.loads(response_str)
print(response_json)
place_trade(symbol='BTCUSD', side='BUY', order_type='MARKET', quantity=1)
References
urlib docs
US Binance Home Page
US Binance API Github
API New Order Endpoint
API New Order Endpoint - For Testing*
This endpoint is used in the example but both endpoints function the same and have the same error
I also looked at the library python-binance
for examples
PyPi
GitHub
Docs
EDIT
I can place a successful order using the requests
library. I went through the library's source code but can't figure out how to properly format a POST request using urllib
Code to place trade using requests
library - works:
import hmac
import time
import hashlib
import requests
import settings
import urllib.parse
session = requests.session()
session.headers.update({'X-MBX-APIKEY': settings.API_KEY})
url = 'https://api.binance.us/api/v3/order/test'
params = {
'symbol': 'BTCUSD',
'side': 'BUY',
'type': 'MARKET',
'quantity': 1,
'timestamp': int(time.time() * 1000)
}
secret = bytes(settings.SECRET_KEY.encode('utf-8'))
signature = hmac.new(secret, urllib.parse.urlencode(params).encode('utf-8'), hashlib.sha256).hexdigest()
params['signature'] = signature
result = session.post(url, params)
print(result)
print(result.text)
回答1:
The problem was with the following lines:
# Encode params into url
url = 'https://api.binance.us/api/v3/order/test?' + urllib.parse.urlencode(params)
# Add signature to url
url += f'&signature={signature}'
Apparently, when using urllib
GET request payloads have to be encoded into the url itself, however POST requests require you to pass them into into the data
parameter:
data = urllib.parse.urlencode(params).encode('ascii')
req = urllib.request.Request(url, data=data, headers=headers)
In my question, I was passing my payload through the url AND the data
parameter. Removing the url payload fixes the issue. Side note for anyone stumbling upon this, putting a question mark ?
after the url is optional for POST requests but not GET requests when using urllib
Working code to post a trade without external libraries:
import json
import time
import hmac
import hashlib
import settings
import urllib.parse
import urllib.request
params = {
'symbol': 'BTCUSD',
'side': 'BUY',
'type': 'MARKET',
'quantity': 1,
'timestamp': int(time.time() * 1000)
}
secret = bytes(settings.SECRET_KEY.encode('utf-8'))
signature = hmac.new(secret, urllib.parse.urlencode(params).encode('utf-8'), hashlib.sha256).hexdigest()
params['signature'] = signature
url = 'https://api.binance.us/api/v3/order/test'
headers = {'X-MBX-APIKEY': settings.API_KEY}
data = urllib.parse.urlencode(params).encode('ascii')
req = urllib.request.Request(url, data=data, headers=headers)
response = urllib.request.urlopen(req)
response_str = response.read().decode('utf-8')
response_json = json.loads(response_str)
print(response_json)
来源:https://stackoverflow.com/questions/66003521/issues-placing-a-trade-with-binance-api-using-python