diff --git a/coinbase/jwt_generator.py b/coinbase/jwt_generator.py index 67fb9c4..ff98ef2 100644 --- a/coinbase/jwt_generator.py +++ b/coinbase/jwt_generator.py @@ -1,3 +1,4 @@ +import secrets import time import jwt @@ -25,7 +26,7 @@ def build_jwt(key_var, secret_var, service, uri=None): jwt_data, private_key, algorithm="ES256", - headers={"kid": key_var, "nonce": str(int(time.time()))}, + headers={"kid": key_var, "nonce": secrets.token_hex()}, ) return jwt_token diff --git a/coinbase/rest/__init__.py b/coinbase/rest/__init__.py index 8fb39ff..842ad9e 100755 --- a/coinbase/rest/__init__.py +++ b/coinbase/rest/__init__.py @@ -6,6 +6,14 @@ class RESTClient(RESTBase): from .common import get_unix_time from .convert import commit_convert_trade, create_convert_quote, get_convert_trade from .fees import get_transaction_summary + from .futures import ( + cancel_pending_futures_sweep, + get_futures_balance_summary, + get_futures_position, + list_futures_positions, + list_futures_sweeps, + schedule_futures_sweep, + ) from .market_data import get_candles, get_market_trades from .orders import ( cancel_orders, diff --git a/coinbase/rest/futures.py b/coinbase/rest/futures.py new file mode 100644 index 0000000..926e417 --- /dev/null +++ b/coinbase/rest/futures.py @@ -0,0 +1,69 @@ +from coinbase.constants import API_PREFIX + + +def get_futures_balance_summary(self, **kwargs): + """ + Get information on your balances related to Coinbase Financial Markets (CFM) futures trading. + + https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmbalancesummary + """ + endpoint = f"{API_PREFIX}/cfm/balance_summary" + + return self.get(endpoint, **kwargs) + + +def list_futures_positions(self, **kwargs): + """ + Get a list of all open positions in CFM futures products. + + https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmpositions + """ + endpoint = f"{API_PREFIX}/cfm/positions" + + return self.get(endpoint, **kwargs) + + +def get_futures_position(self, product_id: str, **kwargs): + """ + Get the position of a specific CFM futures product. + + https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmposition + """ + endpoint = f"{API_PREFIX}/cfm/positions/{product_id}" + + return self.get(endpoint, **kwargs) + + +def schedule_futures_sweep(self, usd_amount: str, **kwargs): + """ + Schedule a sweep of funds from your CFTC-regulated futures account to your Coinbase Inc. USD Spot wallet. + + https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_schedulefcmsweep + """ + endpoint = f"{API_PREFIX}/cfm/sweeps/schedule" + + data = {"usd_amount": usd_amount} + + return self.post(endpoint, data=data, **kwargs) + + +def list_futures_sweeps(self, **kwargs): + """ + Get information on your pending and/or processing requests to sweep funds from your CFTC-regulated futures account to your Coinbase Inc. USD Spot wallet. + + https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmsweeps + """ + endpoint = f"{API_PREFIX}/cfm/sweeps" + + return self.get(endpoint, **kwargs) + + +def cancel_pending_futures_sweep(self, **kwargs): + """ + Cancel your pending sweep of funds from your CFTC-regulated futures account to your Coinbase Inc. USD Spot wallet. + + https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_cancelfcmsweep + """ + endpoint = f"{API_PREFIX}/cfm/sweeps" + + return self.delete(endpoint, **kwargs) diff --git a/tests/rest/test_futures.py b/tests/rest/test_futures.py new file mode 100644 index 0000000..f1a0fa7 --- /dev/null +++ b/tests/rest/test_futures.py @@ -0,0 +1,118 @@ +import unittest + +from requests_mock import Mocker + +from coinbase.rest import RESTClient +from tests.constants import TEST_API_KEY, TEST_API_SECRET + + +class FuturesTest(unittest.TestCase): + def test_get_futures_balance_summary(self): + client = RESTClient(TEST_API_KEY, TEST_API_SECRET) + + expected_response = {"key_1": "value_1", "key_2": "value_2"} + + with Mocker() as m: + m.request( + "GET", + "https://api.coinbase.com/api/v3/brokerage/cfm/balance_summary", + json=expected_response, + ) + balance_summary = client.get_futures_balance_summary() + + captured_request = m.request_history[0] + + self.assertEqual(captured_request.query, "") + self.assertEqual(balance_summary, expected_response) + + def test_list_futures_positions(self): + client = RESTClient(TEST_API_KEY, TEST_API_SECRET) + + expected_response = {"key_1": "value_1", "key_2": "value_2"} + + with Mocker() as m: + m.request( + "GET", + "https://api.coinbase.com/api/v3/brokerage/cfm/positions", + json=expected_response, + ) + positions = client.list_futures_positions() + + captured_request = m.request_history[0] + + self.assertEqual(captured_request.query, "") + self.assertEqual(positions, expected_response) + + def test_get_futures_position(self): + client = RESTClient(TEST_API_KEY, TEST_API_SECRET) + + expected_response = {"key_1": "value_1", "key_2": "value_2"} + + with Mocker() as m: + m.request( + "GET", + "https://api.coinbase.com/api/v3/brokerage/cfm/positions/PRODUCT_ID_1", + json=expected_response, + ) + position = client.get_futures_position("PRODUCT_ID_1") + + captured_request = m.request_history[0] + + self.assertEqual(captured_request.query, "") + self.assertEqual(position, expected_response) + + def test_schedule_futures_sweep(self): + client = RESTClient(TEST_API_KEY, TEST_API_SECRET) + + expected_response = {"key_1": "value_1", "key_2": "value_2"} + + with Mocker() as m: + m.request( + "POST", + "https://api.coinbase.com/api/v3/brokerage/cfm/sweeps/schedule", + json=expected_response, + ) + response = client.schedule_futures_sweep("5") + + captured_request = m.request_history[0] + captured_json = captured_request.json() + + self.assertEqual(captured_request.query, "") + self.assertEqual(captured_json, {"usd_amount": "5"}) + self.assertEqual(response, expected_response) + + def test_list_futures_sweeps(self): + client = RESTClient(TEST_API_KEY, TEST_API_SECRET) + + expected_response = {"key_1": "value_1", "key_2": "value_2"} + + with Mocker() as m: + m.request( + "GET", + "https://api.coinbase.com/api/v3/brokerage/cfm/sweeps", + json=expected_response, + ) + sweeps = client.list_futures_sweeps() + + captured_request = m.request_history[0] + + self.assertEqual(captured_request.query, "") + self.assertEqual(sweeps, expected_response) + + def test_cancel_pending_futures_sweep(self): + client = RESTClient(TEST_API_KEY, TEST_API_SECRET) + + expected_response = {"key_1": "value_1", "key_2": "value_2"} + + with Mocker() as m: + m.request( + "DELETE", + "https://api.coinbase.com/api/v3/brokerage/cfm/sweeps", + json=expected_response, + ) + delete = client.cancel_pending_futures_sweep() + + captured_request = m.request_history[0] + + self.assertEqual(captured_request.query, "") + self.assertEqual(delete, expected_response)