From 2918725f2e0a696210684fb5de2c0ea0f7863454 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 20 Aug 2020 20:10:30 -0300 Subject: [PATCH] fix and update setup.py --- build/lib/ftx/__init__.py | 1 + build/lib/ftx/api.py | 254 ++++++++++++++++++++++++++++++ dist/ftx-0.0.1-py3-none-any.whl | Bin 0 -> 6441 bytes dist/ftx-0.0.1.tar.gz | Bin 0 -> 5929 bytes ftx.egg-info/PKG-INFO | 104 ++++++++++++ ftx.egg-info/SOURCES.txt | 9 ++ ftx.egg-info/dependency_links.txt | 1 + ftx.egg-info/requires.txt | 3 + ftx.egg-info/top_level.txt | 1 + setup.py | 6 +- 10 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 build/lib/ftx/__init__.py create mode 100644 build/lib/ftx/api.py create mode 100644 dist/ftx-0.0.1-py3-none-any.whl create mode 100644 dist/ftx-0.0.1.tar.gz create mode 100644 ftx.egg-info/PKG-INFO create mode 100644 ftx.egg-info/SOURCES.txt create mode 100644 ftx.egg-info/dependency_links.txt create mode 100644 ftx.egg-info/requires.txt create mode 100644 ftx.egg-info/top_level.txt diff --git a/build/lib/ftx/__init__.py b/build/lib/ftx/__init__.py new file mode 100644 index 0000000..82fbd51 --- /dev/null +++ b/build/lib/ftx/__init__.py @@ -0,0 +1 @@ +from api import FtxClient \ No newline at end of file diff --git a/build/lib/ftx/api.py b/build/lib/ftx/api.py new file mode 100644 index 0000000..1bf3550 --- /dev/null +++ b/build/lib/ftx/api.py @@ -0,0 +1,254 @@ +import time +import urllib.parse +from typing import Optional, Dict, Any, List + +from requests import Request, Session, Response +import hmac +from ciso8601 import parse_datetime + + +class FtxClient: + + def __init__(self, base_url=None, api_key=None, api_secret=None, subaccount_name=None) -> None: + self._session = Session() + self._base_url = 'https://ftx.com/api/' + self._api_key = api_key + self._api_secret = api_secret + self._subaccount_name = subaccount_name + + def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any: + return self._request('GET', path, params=params) + + def _post(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any: + return self._request('POST', path, json=params) + + def _delete(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any: + return self._request('DELETE', path, json=params) + + def _request(self, method: str, path: str, **kwargs) -> Any: + request = Request(method, self._base_url + path, **kwargs) + if self._api_key: + self._sign_request(request) + response = self._session.send(request.prepare()) + + return self._process_response(response) + + def _sign_request(self, request: Request) -> None: + ts = int(time.time() * 1000) + prepared = request.prepare() + signature_payload = f'{ts}{prepared.method}{prepared.path_url}'.encode() + if prepared.body: + signature_payload += prepared.body + signature = hmac.new(self._api_secret.encode(), signature_payload, 'sha256').hexdigest() + request.headers['FTX-KEY'] = self._api_key + request.headers['FTX-SIGN'] = signature + request.headers['FTX-TS'] = str(ts) + if self._subaccount_name: + request.headers['FTX-SUBACCOUNT'] = urllib.parse.quote(self._subaccount_name) + + def _process_response(self, response: Response) -> Any: + try: + data = response.json() + except ValueError: + response.raise_for_status() + raise + else: + if not data['success']: + raise Exception(data['error']) + return data['result'] + + + # + # Authentication required methods + # + + def authentication_required(fn): + """Annotation for methods that require auth.""" + def wrapped(self, *args, **kwargs): + if not self._api_key: + raise TypeError("You must be authenticated to use this method") + else: + return fn(self, *args, **kwargs) + return wrapped + + @authentication_required + def get_account_info(self) -> dict: + return self._get(f'account') + + @authentication_required + def get_open_orders(self, market: str = None) -> List[dict]: + return self._get(f'orders', {'market': market}) + + @authentication_required + def get_order_history(self, market: str = None, side: str = None, order_type: str = None, start_time: float = None, end_time: float = None) -> List[dict]: + return self._get(f'orders/history', {'market': market, 'side': side, 'orderType': order_type, 'start_time': start_time, 'end_time': end_time}) + + @authentication_required + def get_conditional_order_history(self, market: str = None, side: str = None, type: str = None, order_type: str = None, start_time: float = None, end_time: float = None) -> List[dict]: + return self._get(f'conditional_orders/history', {'market': market, 'side': side, 'type': type, 'orderType': order_type, 'start_time': start_time, 'end_time': end_time}) + + @authentication_required + def modify_order( + self, existing_order_id: Optional[str] = None, + existing_client_order_id: Optional[str] = None, price: Optional[float] = None, + size: Optional[float] = None, client_order_id: Optional[str] = None, + ) -> dict: + assert (existing_order_id is None) ^ (existing_client_order_id is None), \ + 'Must supply exactly one ID for the order to modify' + assert (price is None) or (size is None), 'Must modify price or size of order' + path = f'orders/{existing_order_id}/modify' if existing_order_id is not None else \ + f'orders/by_client_id/{existing_client_order_id}/modify' + return self._post(path, { + **({'size': size} if size is not None else {}), + **({'price': price} if price is not None else {}), + ** ({'clientId': client_order_id} if client_order_id is not None else {}), + }) + + @authentication_required + def get_conditional_orders(self, market: str = None) -> List[dict]: + return self._get(f'conditional_orders', {'market': market}) + + @authentication_required + def place_order(self, market: str, side: str, price: float, size: float, type: str = 'limit', + reduce_only: bool = False, ioc: bool = False, post_only: bool = False, + client_id: str = None) -> dict: + return self._post(f'orders', {'market': market, + 'side': side, + 'price': price, + 'size': size, + 'type': type, + 'reduceOnly': reduce_only, + 'ioc': ioc, + 'postOnly': post_only, + 'clientId': client_id, + }) + @authentication_required + def place_conditional_order( + self, market: str, side: str, size: float, type: str = 'stop', + limit_price: float = None, reduce_only: bool = False, cancel: bool = True, + trigger_price: float = None, trail_value: float = None + ) -> dict: + """ + To send a Stop Market order, set type='stop' and supply a trigger_price + To send a Stop Limit order, also supply a limit_price + To send a Take Profit Market order, set type='trailing_stop' and supply a trigger_price + To send a Trailing Stop order, set type='trailing_stop' and supply a trail_value + """ + assert type in ('stop', 'take_profit', 'trailing_stop') + assert type not in ('stop', 'take_profit') or trigger_price is not None, \ + 'Need trigger prices for stop losses and take profits' + assert type not in ('trailing_stop',) or (trigger_price is None and trail_value is not None), \ + 'Trailing stops need a trail value and cannot take a trigger price' + + return self._post(f'conditional_orders', + {'market': market, 'side': side, 'triggerPrice': trigger_price, + 'size': size, 'reduceOnly': reduce_only, 'type': 'stop', + 'cancelLimitOnTrigger': cancel, 'orderPrice': limit_price}) + + @authentication_required + def cancel_order(self, order_id: str) -> dict: + return self._delete(f'orders/{order_id}') + + @authentication_required + def cancel_orders(self, market_name: str = None, conditional_orders: bool = False, + limit_orders: bool = False) -> dict: + return self._delete(f'orders', {'market': market_name, + 'conditionalOrdersOnly': conditional_orders, + 'limitOrdersOnly': limit_orders, + }) + + @authentication_required + def get_fills(self) -> List[dict]: + return self._get(f'fills') + + @authentication_required + def get_balances(self) -> List[dict]: + return self._get(f'wallet/balances') + + @authentication_required + def get_deposit_address(self, ticker: str) -> dict: + return self._get(f'wallet/deposit_address/{ticker}') + + @authentication_required + def get_positions(self, show_avg_price: bool = False) -> List[dict]: + return self._get(f'positions', {'showAvgPrice': show_avg_price}) + + @authentication_required + def get_position(self, name: str, show_avg_price: bool = False) -> dict: + return next(filter(lambda x: x['future'] == name, self.get_positions(show_avg_price)), None) + + @authentication_required + def set_leverage(self, leverage): + return self._post(f'account/leverage', {'leverage': leverage}) + + @authentication_required + def get_subaccounts(self) -> List[dict]: + return self._get(f'subaccounts') + + @authentication_required + def create_subaccounts(self, nickname) -> List[dict]: + return self._post(f'subaccounts', {'nickname': nickname}) + + @authentication_required + def delete_subaccounts(self, nickname) -> List[dict]: + return self._delete(f'subaccounts', {'nickname': nickname}) + + @authentication_required + def get_subaccounts_balance(self, nickname) -> List[dict]: + return self._get(f'subaccounts/{nickname}/balances', {'nickname': nickname}) + + @authentication_required + def request_quote(self, fromCoin, toCoin , size) -> List[dict]: + return self._post(f'otc/quotes', {'fromCoin': fromCoin, 'toCoin': toCoin, 'size': size}) + + # + # Public methods + # + + def get_futures(self) -> List[dict]: + return self._get(f'futures') + + def get_future(self, future_name: str) -> dict: + return self._get(f'futures/{future_name}') + + def get_markets(self) -> List[dict]: + return self._get(f'markets') + + def get_market(self, market: str) -> dict: + return self._get(f'markets/{market}') + + def get_orderbook(self, market: str, depth: int = None) -> dict: + return self._get(f'markets/{market}/orderbook', {'depth': depth}) + + def get_trades(self, market: str, limit: int = 100, start_time: float = None, end_time: float = None) -> dict: + return self._get(f'markets/{market}/trades', {'limit':limit, 'start_time': start_time, 'end_time': end_time}) + + def get_all_trades(self, market: str, start_time: float = None, end_time: float = None) -> List: + ids = set() + limit = 100 + results = [] + while True: + response = self._get(f'markets/{market}/trades', { + 'end_time': end_time, + 'start_time': start_time, + }) + deduped_trades = [r for r in response if r['id'] not in ids] + results.extend(deduped_trades) + ids |= {r['id'] for r in deduped_trades} + print(f'Adding {len(response)} trades with end time {end_time}') + if len(response) == 0: + break + end_time = min(parse_datetime(t['time']) for t in response).timestamp() + if len(response) < limit: + break + return results + + def get_historical_data(self,market_name: str,resolution: int ,limit: int ,start_time: float ,end_time: float ) -> dict: + return self._get(f'markets/{market_name}/candles', dict(resolution=resolution,limit=limit,start_time=start_time,end_time=end_time)) + + def get_future_stats(self, future_name) -> List[dict]: + return self._get(f'futures/{future_name}/stats', {'future_name' : future_name}) + + def get_funding_rates(self) -> List[dict]: + return self._get(f'funding_rates') + diff --git a/dist/ftx-0.0.1-py3-none-any.whl b/dist/ftx-0.0.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..5d71e3b37cdfb0cd12b63e3958af0a0366e0bb04 GIT binary patch literal 6441 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 M_eo@IM1QRQ4-Olnga7~l literal 0 HcmV?d00001 diff --git a/dist/ftx-0.0.1.tar.gz b/dist/ftx-0.0.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..9fd121ae85848118ed6c3782a4e895939c2d9798 GIT binary patch literal 5929 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 import ftx + >>> client = ftx.FtxClient() + >>> result = client.get_orderbook('BTC/USD', 1) + >>> result + {'asks': [[11861.0, 1.778]], 'bids': [[11860.5, 0.1]]} + + >>> result['asks'] + [[11861.0, 1.778]] + + >>> result['bids'] + [[11860.5, 0.1]] + + ### Market's Instrument data + The API supports fetching full data for one or multiple markets. + + >>> client.get_market('BTC/USD') + {'ask': 11849.0, 'baseCurrency': 'BTC', 'bid': 11848.5, 'change1h': 0.00025325004220834034, 'change24h': 0.008983693106825051, 'changeBod': 0.006925855109411514, 'enabled': True, 'last': 11849.0, 'minProvideSize': 0.0001, 'name': 'BTC/USD', 'postOnly': False, 'price': 11849.0, 'priceIncrement': 0.5, 'quoteCurrency': 'USD', 'quoteVolume24h': 9271567.5201, 'restricted': False, 'sizeIncrement': 0.0001, 'type': 'spot', 'underlying': None, 'volumeUsd24h': 9271567.5201} + + ### Date ranges + Any time-based parameters accept Python `datetime` objects. All timestamps returned from FTX are UTC. + + >>> client = ftx.FtxClient() + >>> client.get_trades('BTC/USD', 1, datetime.datetime(2020,8,20).timestamp()) + [{'id': 88953674, 'liquidation': False, 'price': 11861.0, 'side': 'sell', 'size': 0.0105, 'time': '2020-08-20T17:33:19.115690+00:00'}] + + ### Authenticated endpoints + Private endpoints require authentication. Clients authenticate with an API key. For more information, see:[API keys](https://help.ftx.com/hc/en-us/articles/360044411911-FTX-Features-Overview#h_6a76d63d-e6cd-45db-87ab-5778af4e3b07) + + To get an authenticated client instance: + + >>> client = ftx.FtxClient(api_key=, api_secret=) + + If you try to access a private endpoint with an unauthenticated client, an error is raised. Calls + to private endpoints work the same as regular ones: + + client.get_open_orders('BTC/USD') + + + ## Advanced usage + + ### Placing orders + An order can be placed through the `place_order()` function. See + [the API Documentation](https://docs.ftx.com/#place-order) for required and optional parameters. + + client.place_order('BTC/USD', 'sell', 12345.0, 10) + + ### Modifying orders + Orders can be modified by providing the original order ID. + + >>> client.place_order('BTC/USD', 'sell', 12345.0, 10) + {"createdAt": "2020-08-20T17:33:19.115690+00:00","filledSize": 0,"id": 9596912,"market": "BTC/USD"... + + >>> client.modify_order(9596912, 12500.0, 15).result() + + ### Canceling orders + An order can be canceled given the order ID: + + client.cancel_order(9596912).result() + +Keywords: ftx,bitcoin,crypto-api,api-connector,exchange-api,crypto-exchange,digital-currency,trading +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Financial and Insurance Industry +Classifier: Topic :: Software Development +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Operating System :: OS Independent diff --git a/ftx.egg-info/SOURCES.txt b/ftx.egg-info/SOURCES.txt new file mode 100644 index 0000000..c0132b7 --- /dev/null +++ b/ftx.egg-info/SOURCES.txt @@ -0,0 +1,9 @@ +README.md +setup.py +ftx/__init__.py +ftx/api.py +ftx.egg-info/PKG-INFO +ftx.egg-info/SOURCES.txt +ftx.egg-info/dependency_links.txt +ftx.egg-info/requires.txt +ftx.egg-info/top_level.txt \ No newline at end of file diff --git a/ftx.egg-info/dependency_links.txt b/ftx.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ftx.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/ftx.egg-info/requires.txt b/ftx.egg-info/requires.txt new file mode 100644 index 0000000..b58d75d --- /dev/null +++ b/ftx.egg-info/requires.txt @@ -0,0 +1,3 @@ +requests==2.23.0 +satoshi==0.1.3 +schedule==0.6.0 diff --git a/ftx.egg-info/top_level.txt b/ftx.egg-info/top_level.txt new file mode 100644 index 0000000..d2ac2e2 --- /dev/null +++ b/ftx.egg-info/top_level.txt @@ -0,0 +1 @@ +ftx diff --git a/setup.py b/setup.py index e4a3dd4..c0e1b5e 100644 --- a/setup.py +++ b/setup.py @@ -7,13 +7,13 @@ setup(name='ftx', version='0.0.1', - description="Unofficial python3 FTX exchange API 1.0.", + description="Unofficial python3 FTX exchange API 1.0.1", long_description=open(join(here, 'README.md')).read(), license='MIT', author='thomgabriel', author_email='thomgabriel@protonmail.com', - url='https://github.com/thomgabriel/ftx', - download_url = 'https://github.com/thomgabriel/ftx/dist/ftx-0.0.1.tar.gz', + url='https://github.com/quan-digital/ftx/tree/v1.0', + download_url = 'https://github.com/quan-digital/ftx/tree/v1.0', install_requires=[ 'requests==2.23.0', 'schedule==0.6.0',