Robinhood 加密 api 取消限价订单错误

问题描述 投票:0回答:1

下面的代码块是 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

如果我需要提供更多信息,请告诉我。

python curl
1个回答
0
投票

您没有在 POST 请求中传递正文参数,因此当“Expecting a value”时

json=json.loads(body)
会得到一个空字符串。您可以尝试将
body={}
作为默认参数,否则您需要对 POST 请求进行条件检查以不发送空正文。您也不需要
json.dumps()
,因为 requests 在提供字典时会为您做到这一点

此外,根据文档,取消 API 也不会返回 JSON 响应,因此

return response.json()
可能不起作用,您也需要处理该问题

© www.soinside.com 2019 - 2024. All rights reserved.