下面的代码块是 Robinhood 提供的与其加密 api 交互的基本功能。我已经能够成功使用大部分函数,例如“get_orders”、“place_order”和“get_trading_pair”函数。
class CryptoAPITrading:
def __init__(self):
self.api_key = API_KEY
private_bytes = base64.b64decode(BASE64_PRIVATE_KEY)
# Note that the cryptography library used here only accepts a 32 byte ed25519 private key
self.private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_bytes[:32])
self.base_url = "https://trading.robinhood.com"
@staticmethod
def _get_current_timestamp() -> int:
return int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
@staticmethod
def get_query_params(key: str, *args: Optional[str]) -> str:
if not args:
return ""
params = []
for arg in args:
params.append(f"{key}={arg}")
return "?" + "&".join(params)
def make_api_request(self, method: str, path: str, body: str = "") -> Any:
timestamp = self._get_current_timestamp()
headers = self.get_authorization_header(method, path, body, timestamp)
url = self.base_url + path
try:
response = None
if method == "GET":
response = requests.get(url, headers=headers, timeout=10)
elif method == "POST":
response = requests.post(url, headers=headers, json=json.loads(body), timeout=10)
# Print debugging information
print(f"URL: {url}")
print(f"Headers: {headers}")
print(f"Response Status Code: {response.status_code}")
print(f"Response Text: {response.text}")
# Check if the response text is empty
if not response.text:
print("Received an empty response.")
return None
return response.json()
except requests.RequestException as e:
print(f"Error making API request: {e}")
return None
def get_authorization_header(
self, method: str, path: str, body: str, timestamp: int
) -> Dict[str, str]:
message_to_sign = f"{self.api_key}{timestamp}{path}{method}{body}"
signature = self.private_key.sign(message_to_sign.encode("utf-8"))
return {
"x-api-key": self.api_key,
"x-signature": base64.b64encode(signature).decode("utf-8"),
"x-timestamp": str(timestamp),
}
def get_account(self) -> Any:
path = "/api/v1/crypto/trading/accounts/"
return self.make_api_request("GET", path)
# The symbols argument must be formatted in trading pairs, e.g "BTC-USD", "ETH-USD". If no symbols are provided,
# all supported symbols will be returned
def get_trading_pairs(self, *symbols: Optional[str]) -> Any:
query_params = self.get_query_params("symbol", *symbols)
path = f"/api/v1/crypto/trading/trading_pairs/{query_params}"
return self.make_api_request("GET", path)
# The asset_codes argument must be formatted as the short form name for a crypto, e.g "BTC", "ETH". If no asset
# codes are provided, all crypto holdings will be returned
def get_holdings(self, *asset_codes: Optional[str]) -> Any:
query_params = self.get_query_params("asset_code", *asset_codes)
path = f"/api/v1/crypto/trading/holdings/{query_params}"
return self.make_api_request("GET", path)
# The symbols argument must be formatted in trading pairs, e.g "BTC-USD", "ETH-USD". If no symbols are provided,
# the best bid and ask for all supported symbols will be returned
def get_best_bid_ask(self, *symbols: Optional[str]) -> Any:
query_params = self.get_query_params("symbol", *symbols)
path = f"/api/v1/crypto/marketdata/best_bid_ask/{query_params}"
return self.make_api_request("GET", path)
# The symbol argument must be formatted in a trading pair, e.g "BTC-USD", "ETH-USD"
# The side argument must be "bid", "ask", or "both".
# Multiple quantities can be specified in the quantity argument, e.g. "0.1,1,1.999".
def get_estimated_price(self, symbol: str, side: str, quantity: str) -> Any:
path = f"/api/v1/crypto/marketdata/estimated_price/?symbol={symbol}&side={side}&quantity={quantity}"
return self.make_api_request("GET", path)
def place_order(
self,
client_order_id: str,
side: str,
order_type: str,
symbol: str,
order_config: Dict[str, str],
) -> Any:
body = {
"client_order_id": client_order_id,
"side": side,
"type": order_type,
"symbol": symbol,
f"{order_type}_order_config": order_config,
}
path = "/api/v1/crypto/trading/orders/"
return self.make_api_request("POST", path, json.dumps(body))
def cancel_order(self, order_id: str) -> Any:
path = f"/api/v1/crypto/trading/orders/{order_id}/cancel/"
response = self.make_api_request("POST", path)
# Additional debugging
print(f"Cancel Order Response: {response}")
return response
def get_order(self, order_id: str) -> Any:
path = f"/api/v1/crypto/trading/orders/{order_id}/"
return self.make_api_request("GET", path)
def get_orders(self) -> Any:
path = "/api/v1/crypto/trading/orders/"
return self.make_api_request("GET", path)
def main():
"""
BUILD YOUR TRADING STRATEGY HERE
order = api_trading_client.place_order(
str(uuid.uuid4()),
"buy",
"market",
"BTC-USD",
{"asset_quantity": "0.0001"}
)
"""
我现在正在尝试使用“cancel_order”功能。我创建了自己的函数,尝试调用“cancel_order”函数:
def cancel_limit_order():
most_recent_bot_order = get_most_recent_bot_order()
if most_recent_bot_order['type'] != 'limit':
print('The most recent order is not a limit order. Something is wrong')
return False
most_recent_bot_order_id = most_recent_bot_order['id']
try:
response = api_trading_client.cancel_order(most_recent_bot_order_id)
print(f'Cancel order response: {response}')
# Additional debug info
if response is None:
print('No response received. Check API request and server status.')
elif 'error' in response:
print(f"API Error: {response['error']}")
return True
except Exception as e:
print(f'Error with cancelling order: {e}')
response = api_trading_client.cancel_order(most_recent_bot_order_id)
print(f'Cancel order response: {response}')
if response is None:
print('No response received. Check API request and server status.')
elif 'error' in response:
print(f"API Error: {response['error']}")
return False
当我尝试使用“cancel_order”函数时,出现错误。这是我得到的输出:
URL: https://trading.robinhood.com/api/v1/crypto/trading/orders/
Headers: {'x-api-key': '118e4ecf-32e8-4ba2-b8d7-14ac86d59a1a', 'x-signature': 'ecsYkKycTbv1z9C32UuIarzNRGZbOWa/w0V1z7jQkVi4I6xEAaQqTIhX/F6ir4BX0gcAjp05W70n0IPZxOYXAQ==', 'x-timestamp': '1722149992'}
Response Status Code: 200
Response Text: {"next":null,"previous":null,"results":[{"symbol":"BTC-USD","client_order_id":"11111111-c2a8-4c4f-bb6b-ef4c36ee1837","side":"sell","type":"limit","id":"66a5d2fb-9e79-4a3d-8357-09dd7654c6ef","account_number":"311253745830","state":"open","filled_asset_quantity":"0.000000000000000000","executions":[],"average_price":null,"created_at":"2024-07-28T01:11:23.580309-04:00","updated_at":"2024-07-28T01:11:23.963197-04:00","limit_order_config":{"asset_quantity":"0.010000000000000000","limit_price":"100000.000000000000000000"}},{"symbol":"BTC-USD","client_order_id":"11111111-4e75-48c7-a1c0-f3c5fef4c0b6","side":"sell","type":"limit","id":"66a46f8a-3e5e-4c41-adba-b9de5f2e1595","account_number":"311253745830","state":"canceled","filled_asset_quantity":"0.000000000000000000","executions":[],"average_price":null,"created_at":"2024-07-26T23:54:50.627968-04:00","updated_at":"2024-07-28T01:10:32.159892-04:00","limit_order_config":{"asset_quantity":"0.010000000000000000","limit_price":"100000.000000000000000000"}},{"symbol":"AVAX-USD","client_order_id":"11111111-36ea-4143-9723-65f5055ff048","side":"sell","type":"market","id":"66a44751-b5ce-48ae-84a3-97f7e3848d07","account_number":"311253745830","state":"filled","filled_asset_quantity":"0.500000000000000000","executions":[{"effective_price":"28.496000000000000000","quantity":"0.500000000000000000","timestamp":"2024-07-26T21:03:13.812000-04:00"}],"average_price":"28.496000000000000000","created_at":"2024-07-26T21:03:13.614703-04:00","updated_at":"2024-07-26T21:03:14.548993-04:00","market_order_config":{"asset_quantity":"0.500000000000000000"}},{"symbol":"AVAX-USD","client_order_id":"11111111-f191-4cc8-9e7d-9367e07b716b","side":"sell","type":"limit","id":"66a315c2-09de-4ee9-b76f-d50b993693ec","account_number":"311253745830","state":"open","filled_asset_quantity":"0.000000000000000000","executions":[],"average_price":null,"created_at":"2024-07-25T23:19:30.693918-04:00","updated_at":"2024-07-25T23:19:31.181516-04:00","limit_order_config":{"asset_quantity":"21.000000000000000000","limit_price":"40.000000000000000000"}},{"symbol":"BTC-USD","client_order_id":"11111111-0a27-4e10-b1d0-6dbf05ef4d68","side":"buy","type":"market","id":"66a0c0a6-80a5-482a-92bf-8507d89cc1ff","account_number":"311253745830","state":"filled","filled_asset_quantity":"0.000075300000000000","executions":[{"effective_price":"66693.747699810000000000","quantity":"0.000075300000000000","timestamp":"2024-07-24T04:51:50.468000-04:00"}],"average_price":"66693.747699810000000000","created_at":"2024-07-24T04:51:50.139858-04:00","updated_at":"2024-07-24T04:51:51.580646-04:00","market_order_config":{"asset_quantity":"0.000075300000000000"}},{"symbol":"BTC-USD","client_order_id":"2cf90ee7-00b8-4a93-9950-90239bf8bd24","side":"buy","type":"market","id":"669f173f-c734-4895-ba50-b4cc15f991e6","account_number":"311253745830","state":"filled","filled_asset_quantity":"0.000073990000000000","executions":[{"effective_price":"67873.780158900000000000","quantity":"0.000073990000000000","timestamp":"2024-07-22T22:36:47.852000-04:00"}],"average_price":"67873.780158900000000000","created_at":"2024-07-22T22:36:47.378672-04:00","updated_at":"2024-07-22T22:36:48.739653-04:00","market_order_config":{"asset_quantity":"0.000073990000000000"}},{"symbol":"BTC-USD","client_order_id":"554ea01b-642f-4ec2-89bf-c344323be1ac","side":"buy","type":"market","id":"669f0729-a760-45c0-a5a0-3ba6439439f8","account_number":"311253745830","state":"filled","filled_asset_quantity":"0.015963890000000000","executions":[{"effective_price":"67654.319320530000000000","quantity":"0.015963890000000000","timestamp":"2024-07-22T21:28:10.064000-04:00"}],"average_price":"67654.319320530000000000","created_at":"2024-07-22T21:28:09.576433-04:00","updated_at":"2024-07-22T21:28:10.591231-04:00","market_order_config":{"quote_amount":"1080.00"}},{"symbol":"BTC-USD","client_order_id":"4815f9ba-e546-4eb3-b5d5-3b159cbbd0e8","side":"buy","type":"market","id":"669dc87b-f18d-44c1-969c-5116c5a65f00","account_number":"311253745830","state":"filled","filled_asset_quantity":"0.000073290000000000","executions":[{"effective_price":"68520.540000000000000000","quantity":"0.000073290000000000","timestamp":"2024-07-21T22:48:27.694000-04:00"}],"average_price":"68520.540000000000000000","created_at":"2024-07-21T22:48:27.324783-04:00","updated_at":"2024-07-21T22:48:28.105601-04:00","market_order_config":{"asset_quantity":"0.000073290000000000"}},{"symbol":"BTC-USD","client_order_id":"f8273cdc-da2d-4bb8-a547-cedcc246a591","side":"buy","type":"limit","id":"669d9c3b-520f-4063-b15e-042d9ed7eb19","account_number":"311253745830","state":"canceled","filled_asset_quantity":"0.000000000000000000","executions":[],"average_price":null,"created_at":"2024-07-21T19:39:39.085759-04:00","updated_at":"2024-07-21T19:59:22.323724-04:00","limit_order_config":{"quote_amount":"5.00","limit_price":"67990.000000000000000000"}},{"symbol":"AVAX-USD","client_order_id":"3a7bcebc-d381-44f7-89d4-c3a628b5c4dc","side":"sell","type":"market","id":"669b448c-5ca3-4b21-9d90-02fbeef48483","account_number":"311253745830","state":"filled","filled_asset_quantity":"0.705700000000000000","executions":[{"effective_price":"28.352910000000000000","quantity":"0.705700000000000000","timestamp":"2024-07-20T01:01:00.767000-04:00"}],"average_price":"28.352910000000000000","created_at":"2024-07-20T01:01:00.605289-04:00","updated_at":"2024-07-20T01:01:01.115093-04:00","market_order_config":{"quote_amount":"20.00"}},{"symbol":"AVAX-USD","client_order_id":"7fd2f86d-1efe-4f63-b348-309da07b7170","side":"buy","type":"market","id":"668eba89-d97c-4253-be57-99f928c20eda","account_number":"311253745830","state":"filled","filled_asset_quantity":"3.500000000000000000","executions":[{"effective_price":"26.508849370000000000","quantity":"3.500000000000000000","timestamp":"2024-07-10T12:44:58.149000-04:00"}],"average_price":"26.508849370000000000","created_at":"2024-07-10T12:44:57.826274-04:00","updated_at":"2024-07-10T12:44:59.080134-04:00","market_order_config":{"asset_quantity":"3.500000000000000000"}},{"symbol":"AVAX-USD","client_order_id":"0a55733d-af78-4122-bd3f-b6a65e3e8bb0","side":"sell","type":"limit","id":"6678cdd2-3b6c-44f1-854d-be7389b678fb","account_number":"311253745830","state":"canceled","filled_asset_quantity":"0.000000000000000000","executions":[],"average_price":null,"created_at":"2024-06-23T21:37:22.439164-04:00","updated_at":"2024-07-25T23:15:23.210202-04:00","limit_order_config":{"asset_quantity":"18.982500000000000000","limit_price":"60.000000000000000000"}},{"symbol":"AVAX-USD","client_order_id":"d2be1fba-a0cc-4029-9b19-0d14de366182","side":"buy","type":"market","id":"66779c82-f714-4628-8e20-a2d3f0d03421","account_number":"311253745830","state":"filled","filled_asset_quantity":"18.982500000000000000","executions":[{"effective_price":"26.083000000000000000","quantity":"18.982500000000000000","timestamp":"2024-06-22T23:54:43.117000-04:00"}],"average_price":"26.083000000000000000","created_at":"2024-06-22T23:54:42.804401-04:00","updated_at":"2024-06-22T23:54:43.612939-04:00","market_order_config":{"quote_amount":"500.00"}}]}
Latest order: {'symbol': 'BTC-USD', 'client_order_id': '11111111-c2a8-4c4f-bb6b-ef4c36ee1837', 'side': 'sell', 'type': 'limit', 'id': '66a5d2fb-9e79-4a3d-8357-09dd7654c6ef', 'account_number': '311253745830', 'state': 'open', 'filled_asset_quantity': '0.000000000000000000', 'executions': [], 'average_price': None, 'created_at': '2024-07-28T01:11:23.580309-04:00', 'updated_at': '2024-07-28T01:11:23.963197-04:00', 'limit_order_config': {'asset_quantity': '0.010000000000000000', 'limit_price': '100000.000000000000000000'}}
Error with cancelling order: Expecting value: line 1 column 1 (char 0)
Traceback (most recent call last):
File "/Users/andrestendahl/Desktop/mlProjects/Finance/Crypto_MK3_Robinhood/helper_functions.py", line 384, in <module>
cancel_limit_order()
File "/Users/andrestendahl/Desktop/mlProjects/Finance/Crypto_MK3_Robinhood/helper_functions.py", line 190, in cancel_limit_order
response = api_trading_client.cancel_order(most_recent_bot_order_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/andrestendahl/Desktop/mlProjects/Finance/Crypto_MK3_Robinhood/robinhood_api_trading.py", line 121, in cancel_order
response = self.make_api_request("POST", path)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/andrestendahl/Desktop/mlProjects/Finance/Crypto_MK3_Robinhood/robinhood_api_trading.py", line 45, in make_api_request
response = requests.post(url, headers=headers, json=json.loads(body), timeout=10)
^^^^^^^^^^^^^^^^
File "/Users/andrestendahl/anaconda3/lib/python3.11/json/__init__.py", line 346, in loads
return _default_decoder.decode(s)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/andrestendahl/anaconda3/lib/python3.11/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/andrestendahl/anaconda3/lib/python3.11/json/decoder.py", line 355, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
“200”响应似乎意味着“GET”方法成功,但“POST”方法出了问题。我一直在尝试使用各种打印语句进一步调试,但没有取得任何进展。我也不确定错误消息“期望值:第 1 行第 1 列(字符 0)”的实际含义。
以下是我一直在使用的文档: https://docs.robinhood.com/crypto/trading/#tag/Trading/operation/api_v1_post_crypto_trading_cancel_order
如果我需要提供更多信息,请告诉我。
您没有在 POST 请求中传递正文参数,因此当“Expecting a value”时
json=json.loads(body)
会得到一个空字符串。您可以尝试将 body={}
作为默认参数,否则您需要对 POST 请求进行条件检查以不发送空正文。您也不需要 json.dumps()
,因为 requests 在提供字典时会为您做到这一点
此外,根据文档,取消 API 也不会返回 JSON 响应,因此
return response.json()
可能不起作用,您也需要处理该问题