diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d2b3332 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,95 @@ +# Contributing to WhiteBit Python SDK + +Thank you for your interest in contributing! + +--- + +## Important: Auto-generated Core + +The core SDK (`src/whitebit/`) is **automatically generated** from the WhiteBit API definition using [Fern](https://buildwithfern.com). This means: + +- **Do not open PRs that modify files inside `src/whitebit/`** — changes will be overwritten on the next generation run. +- To request a new endpoint or fix an incorrect API definition, open an issue instead (see [Reporting Bugs](#reporting-bugs) and [Requesting Features](#requesting-features)). + +--- + +## What you CAN contribute + +| Area | How | +|---|---| +| **Bug reports** | Open an issue with reproduction steps | +| **Feature requests** | Open an issue describing the use case | +| **Examples** | Add or improve files in `examples/` | +| **Tests** | Add test cases in `tests/` | +| **Use cases & guides** | Real-world usage patterns, strategies, bots | +| **Documentation** | Fixes and improvements to `README.md` | + +--- + +## Reporting Bugs + +Before opening an issue, search existing ones to avoid duplicates. + +Include: + +- Python version (`python --version`) +- SDK version (`pip show whitebit-python-sdk`) +- Minimal code snippet that reproduces the problem +- Full error traceback +- Expected vs actual behaviour + +--- + +## Requesting Features + +Open an issue with the label **enhancement** and describe: + +- The use case you are trying to solve +- The API endpoint involved (link to the [WhiteBit API docs](https://docs.whitebit.com/) if applicable) +- Any alternative approaches you considered + +--- + +## Contributing Examples and Tests + +Examples and tests are the best place to contribute code directly. + +**Examples** live in `examples/` — feel free to add: +- New usage patterns for existing clients +- Real-world trading strategies or bots +- Integration patterns (e.g. asyncio, frameworks) + +**Tests** live in `tests/test.py`. All tests use the `responses` library to mock HTTP calls — no real API credentials are needed. + +```bash +# Setup +python -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +pip install -e . + +# Run tests +python -m pytest tests/ -v +``` + +When adding a test: +1. Mock the HTTP call with `@responses.activate` +2. Cover both a success case and an error/missing-credentials case +3. Assert the request URL, method, body, and response + +--- + +## Submitting a Pull Request + +1. Fork the repo and create a branch: `git checkout -b feature/my-feature` +2. Make sure all existing tests pass +3. Keep commits focused — one logical change per commit +4. Reference any related issue in the PR description (e.g. `Closes #42`) + +--- + +## Code Style + +- Follow [PEP 8](https://peps.python.org/pep-0008/) +- Use type hints where possible +- Do not commit API keys, secrets, or any credentials diff --git a/README.md b/README.md index ab98c3d..fdf3943 100644 --- a/README.md +++ b/README.md @@ -1,123 +1,169 @@ -## A Python SDK for [whitebit](https://www.whitebit.com) +# WhiteBit Python SDK + [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![PyPI version](https://badge.fury.io/py/whitebit-python-sdk.svg)](https://badge.fury.io/py/whitebit-python-sdk) +[![Python Version](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/downloads/) -Please read [whitebit API document](https://whitebit-exchange.github.io/api-docs/) before continuing. +An official Python SDK for the [WhiteBit](https://www.whitebit.com) cryptocurrency exchange API. -## API List +> Please read the [WhiteBit API documentation](https://docs.whitebit.com/) before using this SDK. -- [Private API](https://whitebit-exchange.github.io/api-docs/private/http-trade-v4/) -- [Public API](https://whitebit-exchange.github.io/api-docs/public/http-v4/) -- [Private WS](https://whitebit-exchange.github.io/api-docs/private/websocket/) -- [Public WS](https://whitebit-exchange.github.io/api-docs/public/websocket/) +--- -v4 is the preferred one to use +## Installation -## Disclaimer -“You acknowledge that the software is provided “as is”. Author makes no representations or warranties with respect to -the software whether express or implied, including but not limited to, implied warranties of merchantability and fitness -for a particular purpose. author makes no representation or warranty that: (i) the use and distribution of the software -will be uninterrupted or error free, and (ii) any use and distribution of the software is free from infringement of any -third party intellectual property rights. It shall be your sole responsibility to make such determination before the use -of software. Author disclaims any liability in case any such use and distribution infringe any third party’s -intellectual property rights. Author hereby disclaims any warranty and liability whatsoever for any development created -by or for you with respect to your customers. You acknowledge that you have relied on no warranties and that no -warranties are made by author or granted by law whenever it is permitted by law.” +```bash +pip install whitebit-python-sdk +``` -## REST API +Python 3.9 or higher is required. -### Setup +--- -#### Install the Python module: +## Quick Start -```bash -python3 -m pip install python-whitebit-sdk +### Sync client + +```python +from whitebit import WhitebitApi + +client = WhitebitApi(txc_apikey="YOUR_API_KEY", token="YOUR_TOKEN") + +# Market data (public) +print(client.public_api_v4.get_tickers()) + +# Spot trading +print(client.spot_trading.get_trade_account_balance(ticker="BTC")) +print(client.spot_trading.create_limit_order(market="BTC_USDT", side="buy", amount="0.001", price="30000")) + +# Collateral +print(client.collateral_trading.get_open_positions(market="BTC_USDT")) ``` -Init client for API services. Get APIKey/SecretKey from your whitebit account. +### Async client ```python -from whitebit import MainAccountClient +import asyncio +from whitebit import AsyncWhitebitApi + +async def main(): + client = AsyncWhitebitApi(txc_apikey="YOUR_API_KEY", token="YOUR_TOKEN") + + # Market data (public) + tickers = await client.public_api_v4.get_tickers() + print(tickers) + + # Spot trading + balance = await client.spot_trading.get_trade_account_balance(ticker="BTC") + print(balance) + + order = await client.spot_trading.create_limit_order( + market="BTC_USDT", side="buy", amount="0.001", price="30000" + ) + print(order) -account = MainAccountClient(api_key="", api_secret="")) +asyncio.run(main()) ``` -Following are some simple examples. +Get your API key from your [WhiteBit account settings](https://whitebit.com/settings/api). -See the **examples** folder for full references. +--- -#### Create Spot Limit Order +> **Note:** The SDK client (`WhitebitApi` / `AsyncWhitebitApi`) is auto-generated from the WhiteBit API definition using [Fern](https://buildwithfern.com). To report issues or request new endpoints, open an issue — see [Contributing](CONTRIBUTING.md). -```python -# Create order/spot client -order = OrderClient(api_key="", - api_secret="") +## Clients -# Call SDK function put_limit -print(order.put_limit("BTC_USDT", "sell", "0.1", "40000", True)) -``` +The SDK exposes two top-level clients — `WhitebitApi` (sync) and `AsyncWhitebitApi` (async). Both share the same sub-client structure: -## Websocket API +| Sub-client | Auth | Description | +|---|---|---| +| `client.public_api_v4` | No | Tickers, order books, klines, assets, trades | +| `client.spot_trading` | Yes | Place/cancel spot orders, balance, order history | +| `client.collateral_trading` | Yes | Margin/futures orders, positions, OCO | +| `client.main_account` | Yes | Main balance, deposit/withdraw history | +| `client.deposit` | Yes | Deposit addresses, fiat deposit URLs | +| `client.withdraw` | Yes | Withdrawals | +| `client.transfer` | Yes | Transfer between balances | +| `client.codes` | Yes | Transfer codes (create, apply, history) | +| `client.fees` | Yes | Fee information | +| `client.market_fee` | Yes | Market-specific fees | +| `client.jwt` | Yes | WebSocket JWT token | +| `client.authentication` | Yes | OAuth2 token management | +| `client.sub_account` | Yes | Sub-account management | +| `client.sub_account_api_keys` | Yes | Sub-account API key management | +| `client.crypto_lending_fixed` | Yes | Fixed crypto lending | +| `client.crypto_lending_flex` | Yes | Flex crypto lending | +| `client.credit_line` | Yes | Credit line | +| `client.mining_pool` | Yes | Mining pool stats and management | -### Setup +--- -Init bot class and "on_message" method for work with ws responses. Get APIKey/SecretKey from your whitebit account. +## Examples -```python -class Bot(WhitebitWsClient): - def __init__(self): - super().__init__(key="", secret="") +Full working examples are in the [`examples/`](examples/) directory: - async def on_message(self, event) -> None: - logging.info(event) - -``` +### [trade_examples.py](examples/trade_examples.py) -Following are some simple examples. +Covers `TradeMarketClient`, `TradeAccountClient`, and `TradeOrderClient`: +- Get tickers, order books, klines, assets +- Get spot balance, order history, unexecuted orders +- Place limit, market, stop-limit, stop-market orders +- Bulk limit orders, kill switch -See the **examples** folder for full references. +### [main_examples.py](examples/main_examples.py) -#### Subscribe on deals topic +Covers `MainAccountClient`: +- Main account balance and fee info +- Transaction history +- Create and apply transfer codes -```python -class Bot(WhitebitWsClient): - '''Can be used to create a custom trading strategy/bot''' - - def __init__(self): - super().__init__(key="", secret="") - - async def on_message(self, event) -> None: - '''receives the websocket events''' - if 'result' in event: - result = event['result'] - match result: - case 'pong': return - logging.info(event['result']) - return - else: - method = event['method'] - match method: - case WhitebitWsClient.DEALS_UPDATE: - logging.info(event['params']) - return - - -async def main() -> None: - bot = Bot() - await bot.get_deals("BTC_USDT", 0, 100) - await bot.subscribe_deals(["BTC_USDT"]) - while not bot.exception_occur: - await asyncio.sleep(100) - return - - -if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - try: - asyncio.run(main()) - except KeyboardInterrupt: - pass - finally: - loop.close() +### [collateral_examples.py](examples/collateral_examples.py) + +Covers `CollateralMarketClient`, `CollateralAccountClient`, `CollateralOrderClient`: +- Futures/collateral market info +- Margin balance, open positions, leverage +- Place and cancel collateral/OCO orders + +### [ws_example.py](examples/ws_example.py) + +Covers `WhitebitWsClient`: +- Extend the client and implement `on_message` for real-time events +- Subscribe to deals, prices, order book depth, balance, pending orders + +--- + +## WebSocket Topics + +| Method | Description | +|---|---| +| `subscribe_kline` / `get_kline` | Candlestick (OHLCV) data | +| `subscribe_last_price` / `get_last_price` | Last price updates | +| `subscribe_market_depth` / `get_market_depth` | Order book depth | +| `subscribe_market_stat` / `get_market_stat` | 24h market statistics | +| `subscribe_market_trades` / `get_market_trades` | Public trades stream | +| `subscribe_deals` / `get_deals` | Deals stream | +| `subscribe_spot_balance` / `get_spot_balance` | Spot account balance | +| `subscribe_margin_balance` / `get_margin_balance` | Margin account balance | +| `subscribe_pending_orders` / `get_pending_orders` | Open orders updates | +| `subscribe_orders_executed` / `get_orders_executed` | Executed orders | + +--- + +## Running Tests + +```bash +pip install -r requirements.txt +python -m pytest tests/ -v ``` + +--- + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on reporting bugs, requesting features, and submitting pull requests. + +--- + +## Disclaimer + +The software is provided "as is" without warranty of any kind. Use at your own risk. diff --git a/tests/test.py b/tests/test.py index cf05789..4989ce3 100644 --- a/tests/test.py +++ b/tests/test.py @@ -116,13 +116,126 @@ def test_trade_order_positive(self): assert str(responses.calls[0].response.json()) == str(response) -def main(): - MyTestCase.test_trade_private_query_without_api_key() - MyTestCase.test_trade_account_positive() - MyTestCase.test_trade_account_negative() - MyTestCase.test_trade_order_positive() - MyTestCase.test_trade_order_negative() +class MainAccountTestCase(unittest.TestCase): + @responses.activate + def test_main_account_no_api_key(self): + client = MainAccountClient() + with self.assertRaises(ValueError): + client.get_balance("BTC") + + @responses.activate + def test_main_account_balance_positive(self): + expected_response = {"BTC": {"main_balance": "2.5"}} + responses.add( + responses.POST, + 'https://whitebit.com/api/v4/main-account/balance', + json=expected_response, + status=200, + ) + + client = MainAccountClient("key", "secret") + response = client.get_balance("BTC") + + req = json.loads(responses.calls[0].request.body) + assert req["ticker"] == "BTC" + assert req["request"] == "/api/v4/main-account/balance" + assert response == expected_response + + @responses.activate + def test_main_account_balance_negative(self): + responses.add( + responses.POST, + 'https://whitebit.com/api/v4/main-account/balance', + json={'error': 'unauthorized'}, + status=401, + ) + + client = MainAccountClient("bad_key", "bad_secret") + with self.assertRaises(Exception): + client.get_balance("BTC") + + @responses.activate + def test_main_account_get_fee(self): + expected_response = {"BTC": {"deposit": "0", "withdraw": "0.0004"}} + responses.add( + responses.POST, + 'https://whitebit.com/api/v4/main-account/fee', + json=expected_response, + status=200, + ) + + client = MainAccountClient("key", "secret") + response = client.get_fee() + + assert response == expected_response + assert responses.calls[0].request.method == "POST" + + +class CollateralOrderTestCase(unittest.TestCase): + @responses.activate + def test_collateral_no_api_key(self): + client = CollateralOrderClient() + with self.assertRaises(ValueError): + client.cancel_order("BTC_USDT", 1) + + @responses.activate + def test_collateral_limit_order_positive(self): + expected_response = { + "orderId": 99, + "market": "BTC_USDT", + "side": "buy", + "type": "collateral limit", + "amount": "0.001", + "price": "30000", + } + responses.add( + responses.POST, + 'https://whitebit.com/api/v4/order/collateral/limit', + json=expected_response, + status=200, + ) + + client = CollateralOrderClient("key", "secret") + response = client.put_limit("BTC_USDT", "buy", "0.001", "30000") + + req = json.loads(responses.calls[0].request.body) + assert req["market"] == "BTC_USDT" + assert req["side"] == "buy" + assert req["amount"] == "0.001" + assert req["price"] == "30000" + assert response == expected_response + + @responses.activate + def test_collateral_cancel_order_positive(self): + expected_response = {"orderId": 99, "market": "BTC_USDT"} + responses.add( + responses.POST, + 'https://whitebit.com/api/v4/order/cancel', + json=expected_response, + status=200, + ) + + client = CollateralOrderClient("key", "secret") + response = client.cancel_order("BTC_USDT", 99) + + req = json.loads(responses.calls[0].request.body) + assert req["market"] == "BTC_USDT" + assert req["orderId"] == 99 + assert response == expected_response + + @responses.activate + def test_collateral_cancel_order_negative(self): + responses.add( + responses.POST, + 'https://whitebit.com/api/v4/order/cancel', + json={'error': 'order not found'}, + status=404, + ) + + client = CollateralOrderClient("key", "secret") + with self.assertRaises(Exception): + client.cancel_order("BTC_USDT", 0) if __name__ == '__main__': - main() + unittest.main() diff --git a/whitebit/__init__.py b/whitebit/__init__.py deleted file mode 100644 index 5191eaf..0000000 --- a/whitebit/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .trade import * -from .main import * -from .collateral import * -from .stream import * diff --git a/whitebit/__version__.py b/whitebit/__version__.py deleted file mode 100644 index d695222..0000000 --- a/whitebit/__version__.py +++ /dev/null @@ -1,3 +0,0 @@ -'''This is the Whitebit main module version''' -VERSION = (1, 0, 5) -__version__ = '.'.join(map(str, VERSION)) diff --git a/whitebit/client.py b/whitebit/client.py deleted file mode 100644 index 7fb8185..0000000 --- a/whitebit/client.py +++ /dev/null @@ -1,89 +0,0 @@ -import math -import time -import requests -import base64 -import hashlib -import hmac -import json - - -def _create_uri(params) -> str: - data = '' - strl = [] - for key in sorted(params): - strl.append(f'{key}={params[key]}') - data += '&'.join(strl) - return f'?{data}'.replace(' ', '%20') - - -class Whitebit: - def __init__(self, api_key: str = '', api_secret: str = ''): - self.__api_key = api_key - self.__api_secret = api_secret - self.__url = "https://whitebit.com" - self.__session = requests.Session() - self.__session.headers.update({'User-Agent': 'python-whitebit-sdk'}) - - def _request(self, - method: str = "GET", - uri: str = '', - timeout: int = 10, - auth: bool = True, - params: dict = None, - return_raw: bool = False - ) -> dict: - if params is None: - params = {} - - headers = {'Content-Type': 'application/json'} - - if auth: - self.__create_authed_request(params, headers, uri) - return self.__check_response_data( - self.__session.request(method=method, url=self.__url + uri, headers=headers, json=params, - timeout=timeout), - return_raw - ) - - if params: - uri += _create_uri(params) - url = f'{self.__url}{uri}' - - return self.__check_response_data( - self.__session.request(method=method, url=url, headers=headers, timeout=timeout), - return_raw - ) - - def __create_authed_request(self, params, headers, uri): - if not self.__api_key or self.__api_key == '' or not self.__api_secret or self.__api_secret == '': raise ValueError( - 'Missing credentials.') - params['request'] = uri - params['nonce'] = int(time.time() * 1000) - params['nonceWindow'] = True - headers.update({ - 'X-TXC-APIKEY': self.__api_key, - 'X-TXC-SIGNATURE': self.__get_signature(params), - 'X-TXC-PAYLOAD': self.__payload.decode('ascii'), - }) - - def __get_signature(self, data: dict) -> str: - data_json = json.dumps(data, separators=(',', ':')) # use separators param for deleting spaces - self.__payload = base64.b64encode(data_json.encode('ascii')) - return hmac.new(self.__api_secret.encode('ascii'), self.__payload, hashlib.sha512).hexdigest() - - def __check_response_data(self, response_data, return_raw: bool = False) -> dict: - if response_data.status_code in ['200', 200, '201', 201]: - if return_raw: - return response_data - try: - data = response_data.json() - except ValueError as exc: - raise ValueError(response_data.content) from exc - else: - return data - raise Exception(f'{response_data.status_code} - {response_data.text}') - - def _to_str_list(self, value) -> str: - if isinstance(value, str): return value - if isinstance(value, list): return ','.join(value) - raise ValueError('a must be type of str or list of strings') diff --git a/whitebit/collateral/__init__.py b/whitebit/collateral/__init__.py deleted file mode 100644 index c4a09fb..0000000 --- a/whitebit/collateral/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .account import * -from .market import * -from .order import * diff --git a/whitebit/collateral/account/__init__.py b/whitebit/collateral/account/__init__.py deleted file mode 100644 index 96c79ae..0000000 --- a/whitebit/collateral/account/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .account import * diff --git a/whitebit/collateral/account/account.py b/whitebit/collateral/account/account.py deleted file mode 100644 index 3071c34..0000000 --- a/whitebit/collateral/account/account.py +++ /dev/null @@ -1,53 +0,0 @@ -import whitebit -from whitebit.client import Whitebit - - -class CollateralAccountClient(Whitebit): - __BALANCE_URL = "/api/v4/collateral-account/balance" - __LEVERAGE_URL = "/api/v4/collateral-account/leverage" - __POSITION_HISTORY_URL = "/api/v4/collateral-account/positions/history" - __OPEN_POSITIONS_URL = "/api/v4/collateral-account/positions/open" - __SUMMARY_URL = "/api/v4/collateral-account/summary" - __OCO_ORDERS_URL = "/api/v4/oco-orders" - - def get_balance(self, ticker: str = ""): - params = {} - if ticker != "": - params = {'ticker': ticker} - return self._request(method='POST', uri=self.__BALANCE_URL, params=params, auth=True) - - def get_summary(self): - return self._request(method='POST', uri=self.__SUMMARY_URL, auth=True) - - def set_leverage(self, leverage): - params = {'leverage': leverage} - return self._request(method='POST', uri=self.__LEVERAGE_URL, params=params, auth=True) - - def get_open_positions(self, market): - params = {'market': market} - return self._request(method='POST', uri=self.__OPEN_POSITIONS_URL, params=params, auth=True) - - def get_positions_history(self, market: str = None, position_id: int = None, start_date: int = None, - end_date: int = None, limit: str = None, offset: str = None): - params = {} - if market is not None: - params['market'] = market - if position_id is not None: - params['positionId'] = position_id - if start_date is not None: - params['startDate'] = start_date - if end_date is not None: - params['endDate'] = end_date - if limit is not None: - params['limit'] = limit - if offset is not None: - params['offset'] = offset - return self._request(method='POST', uri=self.__OPEN_POSITIONS_URL, params=params, auth=True) - - def get_oco_orders(self, market: str, offset: int = None, limit: int = None): - params = {'market': market} - if offset is not None: - params["offset"] = offset - if limit is not None: - params["limit"] = limit - return self._request(method='POST', uri=self.__OPEN_POSITIONS_URL, params=params, auth=True) diff --git a/whitebit/collateral/market/__init__.py b/whitebit/collateral/market/__init__.py deleted file mode 100644 index 387d7ac..0000000 --- a/whitebit/collateral/market/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .market import * diff --git a/whitebit/collateral/market/market.py b/whitebit/collateral/market/market.py deleted file mode 100644 index 12ccd79..0000000 --- a/whitebit/collateral/market/market.py +++ /dev/null @@ -1,13 +0,0 @@ -import whitebit -from whitebit.client import Whitebit - - -class CollateralMarketClient(Whitebit): - __COLLATERAL_MARKETS_ENDPOINT = "/api/v4/public/collateral/markets" - __FUTURES_MARKETS_ENDPOINT = "/api/v4/public/futures" - - def get_markets_info(self): - return self._request(method='GET', uri=self.__COLLATERAL_MARKETS_ENDPOINT, auth=False) - - def get_futures_markets(self): - return self._request(method='GET', uri=self.__FUTURES_MARKETS_ENDPOINT, auth=False) diff --git a/whitebit/collateral/order/__init__.py b/whitebit/collateral/order/__init__.py deleted file mode 100644 index 4ca2c09..0000000 --- a/whitebit/collateral/order/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .order import * diff --git a/whitebit/collateral/order/order.py b/whitebit/collateral/order/order.py deleted file mode 100644 index 271dc7a..0000000 --- a/whitebit/collateral/order/order.py +++ /dev/null @@ -1,85 +0,0 @@ -import whitebit -from whitebit.client import Whitebit - - -class CollateralOrderClient(Whitebit): - __ORDER_CANCEL_URL = "/api/v4/order/cancel" - __LIMIT_URL = "/api/v4/order/collateral/limit" - __MARKET_URL = "/api/v4/order/collateral/market" - __OCO_URL = "/api/v4/order/collateral/oco" - __CANCEL_OCO_URL = "/api/v4/order/oco-cancel" - __STOP_MARKET_URL = "/api/v4/order/collateral/trigger-market" - __STOP_LIMIT_URL = "/api/v4/order/collateral/stop-limit" - - def put_market(self, market: str, side: str, amount, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__MARKET_URL, params=params, auth=True) - - def put_limit(self, market: str, side: str, amount, price, post_only: bool = False, ioc: bool = False, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - "price": price, - "postOnly": post_only, - "ioc": ioc - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__LIMIT_URL, params=params, auth=True) - - def put_stop_limit(self, market: str, side: str, amount, price, activation_price, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - "price": price, - "activation_price": activation_price - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__STOP_LIMIT_URL, params=params, auth=True) - - def put_stop_market(self, market: str, side: str, amount, activation_price, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - "activation_price": activation_price - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__STOP_MARKET_URL, params=params, auth=True) - - def cancel_order(self, market: str, order_id): - params = { - "market": market, - "orderId": order_id, - } - return self._request(method='POST', uri=self.__ORDER_CANCEL_URL, params=params, auth=True) - - def put_oco(self, market: str, side: str, amount, price, activation_price, stop_limit_price, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - "activation_price": activation_price, - "stop_limit_price": stop_limit_price, - "price": price - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__OCO_URL, params=params, auth=True) - - def cancel_oco(self, market: str, order_id): - params = { - "market": market, - "orderId": order_id, - } - return self._request(method='POST', uri=self.__CANCEL_OCO_URL, params=params, auth=True) \ No newline at end of file diff --git a/whitebit/main/__init__.py b/whitebit/main/__init__.py deleted file mode 100644 index 96c79ae..0000000 --- a/whitebit/main/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .account import * diff --git a/whitebit/main/account/__init__.py b/whitebit/main/account/__init__.py deleted file mode 100644 index d7ecb3e..0000000 --- a/whitebit/main/account/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .account import MainAccountClient diff --git a/whitebit/main/account/account.py b/whitebit/main/account/account.py deleted file mode 100644 index 0765847..0000000 --- a/whitebit/main/account/account.py +++ /dev/null @@ -1,86 +0,0 @@ -import whitebit -from whitebit.client import Whitebit - - -class MainAccountClient(Whitebit): - - __FEE_URL = "/api/v4/main-account/fee" - __BALANCE_URL = "/api/v4/main-account/balance" - __HISTORY_URL = "/api/v4/main-account/history" - __TRANSFER_URL = "/api/v4/main-account/transfer" - - __CODES_URL = "/api/v4/main-account/codes" - __CODES_APPLY_URL = "/api/v4/main-account/codes/apply" - __CODES_MY_URL = "/api/v4/main-account/codes/my" - __CODES_HISTORY_URL = "/api/v4/main-account/codes/history" - - __CUSTOM_FEE_BY_MARKET_URL = "/api/v4/market/fee/single" - __CUSTOM_FEE_URL = "/api/v4/market/fee" - - def transfer(self, limit: int = None, offset: int = None): - params = {} - if limit is not None: - params['limit'] = limit - if offset is not None: - offset['offset'] = offset - return self._request(method='POST', uri=self.__TRANSFER_URL, params=params, auth=True) - - def get_fee(self): - return self._request(method='POST', uri=self.__FEE_URL, auth=True) - - def get_history(self, transaction_method: str = None, ticker: str = None, addresses: [str] = None, - unique_id: str = None, status: [int] = None, offset: int = 0, limit: int = 100): - params = {'offset': offset, 'limit': limit} - if transaction_method is not None: - params['transactionMethod'] = transaction_method - if ticker is not None: - params['ticker'] = ticker - if addresses is not None and len(addresses) != 0: - params['addresses'] = addresses - if unique_id is not None: - params['unique_id'] = unique_id - if status is not None and len(status) != 0: - params['status'] = status - return self._request(method='POST', uri=self.__HISTORY_URL, params=params, auth=True) - - def get_balance(self, ticker: str = ""): - params = {'ticker': ticker} - return self._request(method='POST', uri=self.__BALANCE_URL, params=params, auth=True) - - def create_code(self, ticker: str, amount: str, passw: str = None, description: str = None): - params = {'ticker': ticker, "amount": amount} - if passw is not None: - params['passphrase'] = passw - if description is not None: - params['description'] = description - return self._request(method='POST', uri=self.__CODES_URL, params=params, auth=True) - - def apply_code(self, code: str, passw: str = None): - params = {'code': code} - if passw is not None: - params['passphrase'] = passw - return self._request(method='POST', uri=self.__CODES_APPLY_URL, params=params, auth=True) - - def get_my_codes(self, limit: int = None, offset: int = None): - params = {} - if limit is not None: - params['limit'] = limit - if offset is not None: - offset['offset'] = offset - return self._request(method='POST', uri=self.__CODES_MY_URL, params=params, auth=True) - - def get_codes_history(self, limit: int = None, offset: int = None): - params = {} - if limit is not None: - params['limit'] = limit - if offset is not None: - offset['offset'] = offset - return self._request(method='POST', uri=self.__CODES_HISTORY_URL, params=params, auth=True) - - def get_custom_fee(self): - params = {} - return self._request(method='POST', uri=self.__CODES_HISTORY_URL, params=params, auth=True) - - def get_custom_fee_by_market(self, market: str): - params = {'market': market} - return self._request(method='POST', uri=self.__CODES_HISTORY_URL, params=params, auth=True) diff --git a/whitebit/stream/__init__.py b/whitebit/stream/__init__.py deleted file mode 100644 index f43f43c..0000000 --- a/whitebit/stream/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .ws import ConnectWebsocket diff --git a/whitebit/stream/ws.py b/whitebit/stream/ws.py deleted file mode 100644 index 8d63ce6..0000000 --- a/whitebit/stream/ws.py +++ /dev/null @@ -1,583 +0,0 @@ -import logging -import json -import time -import asyncio -from enum import Enum -from random import random -import traceback -from typing import List -import websockets -from whitebit.trade.account.account import TradeAccountClient - - -class ConnectWebsocket: - __TIME_REQUEST = "ping" - __AUTHORIZE_REQUEST = "authorize" - - MAX_RECONNECT_NUM = 10 - - def __init__(self, client, url: str, callback, token: str = ''): - self.__client = client - self.__ws_url = url - self.__callback = callback - - self.__reconnect_num = 0 - self.__ws_conn_authed = None - - self._token = token - - self.__last_ping = None - self.__socket = None - self.__subscriptions = [] - - asyncio.ensure_future( - self.__run_forever(), - loop=asyncio.get_running_loop() - ) - - @property - def subscriptions(self) -> list: - '''Returns the active subscriptions''' - return self.__subscriptions - - async def __run(self, event: asyncio.Event): - keep_alive = True - self.__last_ping = time.time() - - async with websockets.connect(f'{self.__ws_url}', ping_interval=None) as socket: - logging.info('Websocket connected!') - self.__socket = socket - - if not event.is_set(): - await self.send_ping() - event.set() - self.__reconnect_num = 0 - - while keep_alive: - if time.time() - self.__last_ping > 10: await self.send_ping() - try: - _msg = await asyncio.wait_for(self.__socket.recv(), timeout=15) - except asyncio.TimeoutError: - await self.send_ping() - except asyncio.CancelledError: - logging.exception('asyncio.CancelledError') - keep_alive = False - await self.__callback({'error': 'asyncio.CancelledError'}) - else: - try: - msg = json.loads(_msg) - except ValueError: - logging.warning(_msg) - else: - if 'result' in msg and msg["id"] == 0: - continue - await self.__callback(msg) - - async def __run_forever(self) -> None: - try: - while True: await self.__reconnect() - except Exception("MaxReconnectError"): - await self.__callback({'error': 'MaxReconnectError'}) - except Exception as execption: - self.__callback({'error': f'{execption}: {traceback.format_exc()}'}) - finally: - self.__client.exception_occur = True - - async def __reconnect(self): - logging.info('Websocket start connect/reconnect ' + websockets.version.tag) - - self.__reconnect_num += 1 - if self.__reconnect_num >= self.MAX_RECONNECT_NUM: - raise Exception("MaxReconnectError") - - reconnect_wait = self.__get_reconnect_wait(self.__reconnect_num) - logging.debug( - f'asyncio sleep reconnect_wait={reconnect_wait} s reconnect_num={self.__reconnect_num}' - ) - await asyncio.sleep(reconnect_wait) - logging.debug('asyncio sleep done') - event = asyncio.Event() - - tasks = { - asyncio.ensure_future(self.__recover_subscriptions(event)): self.__recover_subscriptions, - asyncio.ensure_future(self.__run(event)): self.__run - } - - while set(tasks.keys()): - finished, pending = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_EXCEPTION) - exception_occur = False - for task in finished: - if task.exception(): - exception_occur = True - traceback.print_stack() - message = f'{task} got an exception {task.exception()}\n {task.get_stack()}' - logging.warning(message) - for process in pending: - logging.warning(f'pending {process}') - try: - process.cancel() - except asyncio.CancelledError: - logging.exception('asyncio.CancelledError') - logging.warning('Cancel OK') - await self.__callback({'error': message}) - if exception_occur: break - logging.warning('reconnect over') - - async def __recover_subscriptions(self, event): - logging.info( - f'Recover subscriptions {self.__subscriptions} waiting.' - ) - await event.wait() - - await self.auth() - time.sleep(1) - - for sub in self.__subscriptions: - await self.send_message(sub) - logging.info(f'{sub} OK') - - logging.info( - f'Recovering subscriptions {self.__subscriptions} done.' - ) - - async def send_ping(self): - msg = { - 'id': 0, - 'method': self.__TIME_REQUEST, - 'params': [] - } - await self.__socket.send(json.dumps(msg)) - self.__last_ping = time.time() - - async def send_message(self, msg): - while not self.__socket: await asyncio.sleep(.4) - await self.__socket.send(json.dumps(msg)) - - def append_subscription(self, sub_data: dict) -> None: - self.remove_subscription(sub_data) # remove from list, to avoid duplicates - self.__subscriptions.append(sub_data) - - def remove_subscription(self, sub_data: dict) -> None: - self.__subscriptions = [x for x in self.__subscriptions if x["method"] != sub_data["method"]] - - def __get_reconnect_wait(self, attempts: int) -> float: - return round(random() * min(60 * 3, (2 ** attempts) - 1) + 1) - - @property - def token(self): - return self._token - - async def auth(self): - msg = { - 'id': int(time.time() * 1000), - 'method': self.__AUTHORIZE_REQUEST, - 'params': [self.token, "python sdk"] - } - await self.send_message(msg) - - -class WhitebitWsClient(TradeAccountClient): - PROD_ENV_URL = 'wss://api.whitebit.com/ws' - - __AUTHORIZE_REQUEST = "authorize" - __TIME_REQUEST = "time" - __PING_REQUEST = "ping" - __KLINE_REQUEST = "candles_request" - __KLINE_SUBSCRIBE = "candles_subscribe" - KLINE_UPDATE = "candles_update" - __KLINE_UNSUBSCRIBE = "candles_unsubscribe" - - __DEPTH_REQUEST = "depth_request" - __DEPTH_SUBSCRIBE = "depth_subscribe" - DEPTH_UPDATE = "depth_update" - __DEPTH_UNSUBSCRIBE = "depth_unsubscribe" - - __LAST_PRICE_REQUEST = "lastprice_request" - __LAST_PRICE_SUBSCRIBE = "lastprice_subscribe" - LAST_PRICE_UPDATE = "lastprice_update" - __LAST_PRICE_UNSUBSCRIBE = "lastprice_unsubscribe" - - __MARKET_STAT_REQUEST = "market_request" - __MARKET_STAT_SUBSCRIBE = "market_subscribe" - MARKET_STAT_UPDATE = "market_update" - __MARKET_STAT_UNSUBSCRIBE = "market_unsubscribe" - - __MARKET_STAT_TODAY_REQUEST = "marketToday_query" - __MARKET_STAT_TODAY_SUBSCRIBE = "marketToday_subscribe" - MARKET_STAT_TODAY_UPDATE = "marketToday_update" - __MARKET_STAT_TODAY_UNSUBSCRIBE = "marketToday_unsubscribe" - - __TRADES_REQUEST = "trades_request" - __TRADES_SUBSCRIBE = "trades_subscribe" - TRADES_UPDATE = "trades_update" - __TRADES_UNSUBSCRIBE = "trades_unsubscribe" - - __ORDER_PENDING_REQUEST = "ordersPending_request" - __ORDERS_PENDING_SUBSCRIBE = "ordersPending_subscribe" - ORDERS_PENDING_UPDATE = "ordersPending_update" - __ORDERS_PENDING_UNSUBSCRIBE = "ordersPending_unsubscribe" - - __DEALS_REQUEST = "deals_request" - __DEALS_SUBSCRIBE = "deals_subscribe" - DEALS_UPDATE = "deals_update" - __DEALS_UNSUBSCRIBE = "deals_unsubscribe" - - __SPOT_BALANCE_REQUEST = "balanceSpot_request" - __SPOT_BALANCE_SUBSCRIBE = "balanceSpot_subscribe" - SPOT_BALANCE_UPDATE = "balanceSpot_update" - __SPOT_BALANCE_UNSUBSCRIBE = "balanceSpot_unsubscribe" - - __ORDERS_EXECUTED_REQUEST = "ordersExecuted_request" - __ORDERS_EXECUTED_SUBSCRIBE = "ordersExecuted_subscribe" - ORDERS_EXECUTED_UPDATE = "ordersExecuted_update" - __ORDERS_EXECUTED_UNSUBSCRIBE = "ordersExecuted_unsubscribe" - - __MARGIN_BALANCE_REQUEST = "balanceMargin_request" - __MARGIN_BALANCE_SUBSCRIBE = "balanceMargin_subscribe" - MARGIN_BALANCE_UPDATE = "balanceMargin_update" - __MARGIN_BALANCE_UNSUBSCRIBE = "balanceMargin_unsubscribe" - - def __init__(self, key: str = '', secret: str = '', - callback=None): - super().__init__(api_key=key, api_secret=secret) - self.__callback = callback - token = self.get_ws_token() - self.exception_occur = False - self._conn = ConnectWebsocket( - client=self, - url=self.PROD_ENV_URL, - token=token["websocket_token"], - callback=self.on_message - ) - - async def on_message(self, msg: dict): - if self.__callback is not None: - await self.__callback(msg) - else: - logging.warning('Received event but no callback is defined') - logging.info(msg) - - async def __subscribe(self, subscription: dict) -> None: - self._conn.append_subscription(subscription) - await self._conn.send_message(subscription) - - async def __unsubscribe(self, subscription: dict) -> None: - self._conn.remove_subscription(subscription) - await self._conn.send_message(subscription) - - async def get_authorize(self, token: str, com_id=int(time.time() * 1000)): - await self._conn.send_message( - {'id': com_id, - 'method': self.__AUTHORIZE_REQUEST, - 'params': [token, 'python-sdk']}) - - async def get_spot_balance(self, assets: List[str], com_id=int(time.time() * 1000)): - assets_as_interface = [i for i in assets] - await self._conn.send_message( - {'id': com_id, - 'method': self.__SPOT_BALANCE_REQUEST, - 'params': assets_as_interface}) - - async def subscribe_spot_balance(self, assets: List[str], com_id=int(time.time() * 1000)): - assets_as_interface = [i for i in assets] - await self.__subscribe( - {'id': com_id, - 'method': self.__SPOT_BALANCE_SUBSCRIBE, - 'params': assets_as_interface}) - - async def unsubscribe_spot_balance(self, com_id=int(time.time() * 1000)): - await self.__unsubscribe( - {'id': com_id, - 'method': self.__SPOT_BALANCE_UNSUBSCRIBE, - 'params': []}) - - async def get_margin_balance(self, assets: List[str], com_id=int(time.time() * 1000)): - assets_as_interface = [asset for asset in assets] - await self._conn.send_message( - {'id': com_id, - 'method': self.__MARGIN_BALANCE_REQUEST, - 'params': assets_as_interface}) - - async def subscribe_margin_balance(self, assets: List[str], com_id=int(time.time() * 1000)): - assets_as_interface = [asset for asset in assets] - await self.__subscribe( - {'id': com_id, - 'method': self.__MARGIN_BALANCE_SUBSCRIBE, - 'params': assets_as_interface}) - - async def unsubscribe_margin_balance(self, com_id=int(time.time() * 1000)): - await self.__unsubscribe( - {'id': com_id, - 'method': self.__MARGIN_BALANCE_UNSUBSCRIBE, - 'params': []}) - - async def get_pending_orders(self, market: str, offset: int = 0, limit: int = 100, com_id=int(time.time() * 1000)): - await self._conn.send_message({ - 'id': com_id, - 'method': self.__ORDER_PENDING_REQUEST, - 'params': [market, offset, limit]}) - - async def subscribe_pending_orders(self, markets: List[str], com_id=int(time.time() * 1000)): - markets_as_interface = [market for market in markets] - await self.__subscribe( - {'id': com_id, - 'method': self.__ORDERS_PENDING_SUBSCRIBE, - 'params': markets_as_interface}) - - async def unsubscribe_pending_orders(self, com_id=int(time.time() * 1000)): - await self.__unsubscribe( - {'id': com_id, - 'method': self.__ORDERS_PENDING_UNSUBSCRIBE, - 'params': []}) - - class GetOrdersExecFilter(Enum): - LIMIT = 1 - MARKET = 2 - MARKET_STOCK = 202 - STOP_LIMIT = 3 - STOP_MARKET = 4 - MARGIN_LIMIT = 7 - MARGIN_MARKET = 8 - MARGIN_STOP_LIMIT = 9 - MARGIN_TRIGGER_STOP_MARKET = 10 - MARGIN_NORMALIZATION = 14 - - async def get_orders_executed(self, market: str, order_types: List[int], offset: int = 0, limit: int = 100, - com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__ORDERS_EXECUTED_REQUEST, - 'params': [ - { - 'market': market, - 'order_types': order_types - }, - offset, - limit - ] - } - await self._conn.send_message(msg) - - class OrdersSubscribeExecFilter(Enum): - ALL = 0 - LIMIT = 1 - MARKET = 2 - - async def subscribe_orders_executed(self, markets: List[str], order_filter: OrdersSubscribeExecFilter, - com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__ORDERS_EXECUTED_SUBSCRIBE, - 'params': [markets, order_filter] - } - await self.__subscribe(msg) - - async def unsubscribe_orders_executed(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__ORDERS_EXECUTED_UNSUBSCRIBE, - 'params': [] - } - await self.__unsubscribe(msg) - - async def get_deals(self, market: str, offset: int = 0, limit: int = 100, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__DEALS_REQUEST, - 'params': [market, offset, limit], - } - await self._conn.send_message(msg) - - async def subscribe_deals(self, markets: List[str], com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__DEALS_SUBSCRIBE, - 'params': [markets] - } - await self.__subscribe(msg) - - async def unsubscribe_deals(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__DEALS_UNSUBSCRIBE, - 'params': [] - } - await self.__unsubscribe(msg) - - async def send_ping(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__PING_REQUEST, - 'params': [] - } - await self._conn.send_message(msg) - - async def get_time(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__TIME_REQUEST, - 'params': [] - } - await self._conn.send_message(msg) - - async def get_kline(self, market: str, start_time: int, end_time: int, interval: int, - com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__KLINE_REQUEST, - 'params': [market, start_time, end_time, interval] - } - await self._conn.send_message(msg) - - async def subscribe_kline(self, market: str, interval: int, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__KLINE_SUBSCRIBE, - 'params': [market, interval] - } - await self.__subscribe(msg) - - async def unsubscribe_kline(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__KLINE_UNSUBSCRIBE, - 'params': [] - } - await self.__unsubscribe(msg) - - async def get_last_price(self, market: str, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__LAST_PRICE_REQUEST, - 'params': [market] - } - await self._conn.send_message(msg) - - async def subscribe_last_price(self, market: List[str], com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__LAST_PRICE_SUBSCRIBE, - 'params': market - } - await self.__subscribe(msg) - - async def unsubscribe_last_price(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__LAST_PRICE_UNSUBSCRIBE, - 'params': [] - } - await self.__unsubscribe(msg) - - async def get_market_stat(self, market: str, period: int, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__MARKET_STAT_REQUEST, - 'params': [market, period] - } - await self._conn.send_message(msg) - - async def subscribe_market_stat(self, market: List[str], com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__MARKET_STAT_SUBSCRIBE, - 'params': market - } - await self.__subscribe(msg) - - async def unsubscribe_market_stat(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__MARKET_STAT_UNSUBSCRIBE, - 'params': [] - } - await self.__unsubscribe(msg) - - async def get_market_stat_today(self, market: str, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__MARKET_STAT_TODAY_REQUEST, - 'params': [market] - } - await self._conn.send_message(msg) - - async def subscribe_market_stat_today(self, market: List[str], com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__MARKET_STAT_TODAY_SUBSCRIBE, - 'params': market - } - await self.__subscribe(msg) - - async def unsubscribe_market_stat_today(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__MARKET_STAT_TODAY_UNSUBSCRIBE, - 'params': [] - } - await self.__unsubscribe(msg) - - async def get_market_trades(self, market: str, limit: int = 0, start_trade_id: int = 0, - com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__TRADES_REQUEST, - 'params': [market, limit, start_trade_id] - } - await self._conn.send_message(msg) - - async def subscribe_market_trades(self, market: List[str], com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__TRADES_SUBSCRIBE, - 'params': market - } - await self.__subscribe(msg) - - async def unsubscribe_market_trades(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__TRADES_UNSUBSCRIBE, - 'params': [] - } - await self.__unsubscribe(msg) - - async def get_market_depth(self, market: str, price_interval: str = "0", limit: int = 100, - com_id=int(time.time() * 1000)): - """Support price intervals ["0.00000001", "0.0000001", "0.000001", "0.00001", "0.0001", "0.001", "0.01", - "0.1", "0"] """ - market_depth_filter_variants = ["0.00000001", "0.0000001", "0.000001", "0.00001", "0.0001", "0.001", "0.01", - "0.1", "0"] - if price_interval not in market_depth_filter_variants: - logging.warning("You should use only this interval variants = " + str(market_depth_filter_variants)) - return - msg = { - 'id': com_id, - 'method': self.__DEPTH_REQUEST, - 'params': [market, limit, price_interval] - } - await self._conn.send_message(msg) - - async def subscribe_market_depth(self, market: str, price_interval: str = "0", limit: int = 100, - multiple_sub: bool = False, com_id=int(time.time() * 1000)): - """Support price intervals ["0.00000001", "0.0000001", "0.000001", "0.00001", "0.0001", "0.001", "0.01", - "0.1", "0"] """ - market_depth_filter_variants = ["0.00000001", "0.0000001", "0.000001", "0.00001", "0.0001", "0.001", "0.01", - "0.1", "0"] - if price_interval not in market_depth_filter_variants: - logging.warning("You should use only this interval variants = " + str(market_depth_filter_variants)) - return - msg = { - 'id': com_id, - 'method': self.__DEPTH_SUBSCRIBE, - 'params': [market, limit, price_interval, multiple_sub] - } - await self.__subscribe(msg) - - async def unsubscribe_market_depth(self, com_id=int(time.time() * 1000)): - msg = { - 'id': com_id, - 'method': self.__DEPTH_UNSUBSCRIBE, - 'params': [] - } - await self.__unsubscribe(msg) diff --git a/whitebit/trade/__init__.py b/whitebit/trade/__init__.py deleted file mode 100644 index 3df7cbf..0000000 --- a/whitebit/trade/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from whitebit.trade.market.market import TradeMarketClient -from whitebit.trade.account.account import TradeAccountClient -from whitebit.trade.order.order import TradeOrderClient diff --git a/whitebit/trade/account/__init__.py b/whitebit/trade/account/__init__.py deleted file mode 100644 index 0ca409e..0000000 --- a/whitebit/trade/account/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .account import TradeAccountClient diff --git a/whitebit/trade/account/account.py b/whitebit/trade/account/account.py deleted file mode 100644 index ed60bd1..0000000 --- a/whitebit/trade/account/account.py +++ /dev/null @@ -1,79 +0,0 @@ -import whitebit -from whitebit.client import Whitebit - - -class TradeAccountClient(Whitebit): - __BALANCE_URL = "/api/v4/trade-account/balance" - __ORDER_URL = "/api/v4/trade-account/order" - __ORDER_HISTORY_URL = "/api/v4/trade-account/order/history" - __ORDER_EXECUTED_HISTORY_URL = "/api/v4/trade-account/executed-history" - __ORDERS_URL = "/api/v4/orders" - __TIME_URL = "/api/v4/public/time" - __PING_URL = "/api/v4/public/ping" - __WS_TOKEN_URL = "/api/v4/profile/websocket_token" - - def get_time(self): - return self._request(method='GET', uri=self.__TIME_URL, auth=False) - - def get_ping(self): - return self._request(method='GET', uri=self.__PING_URL, auth=False) - - def get_ws_token(self): - return self._request(method='POST', uri=self.__WS_TOKEN_URL, auth=True) - - def get_balance(self, ticker: str = ""): - params = {} - if ticker != "": - params = {'ticker': ticker} - return self._request(method='POST', uri=self.__BALANCE_URL, params=params, auth=True) - - def get_order_deals(self, order_id: int, offset: int = None, limit: int = None): - params = {"orderId": order_id} - if offset is not None: - params["offset"] = offset - if limit is not None: - params["limit"] = limit - return self._request(method='POST', uri=self.__ORDER_URL, params=params, auth=True) - - def get_executed_history(self, market: str = None, client_order_id: str = None, - offset: int = None, limit: int = None): - params = {} - if market is not None: - params["market"] = market - if client_order_id is not None: - params["clientOrderId"] = client_order_id - if offset is not None: - params["offset"] = offset - if limit is not None: - params["limit"] = limit - return self._request(method='POST', uri=self.__ORDER_EXECUTED_HISTORY_URL, params=params, auth=True) - - def get_history(self, market: str = None, order_id: str = None, client_order_id: str = None, - offset: int = None, limit: int = None): - params = {} - if market is not None: - params["market"] = market - if order_id is not None: - params["orderId"] = order_id - if client_order_id is not None: - params["clientOrderId"] = client_order_id - if limit is not None: - params["limit"] = limit - if offset is not None: - params["offset"] = offset - return self._request(method='POST', uri=self.__ORDER_HISTORY_URL, params=params, auth=True) - - def get_unexecuted_orders(self, market: str = None, order_id: str = None, client_order_id: str = None, - offset: int = None, limit: int = None): - params = {} - if market is not None: - params["market"] = market - if order_id is not None: - params["orderId"] = order_id - if client_order_id is not None: - params["clientOrderId"] = client_order_id - if limit is not None: - params["limit"] = limit - if offset is not None: - params["offset"] = offset - return self._request(method='POST', uri=self.__ORDERS_URL, params=params, auth=True) diff --git a/whitebit/trade/market/__init__.py b/whitebit/trade/market/__init__.py deleted file mode 100644 index 8d498dd..0000000 --- a/whitebit/trade/market/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .market import TradeMarketClient \ No newline at end of file diff --git a/whitebit/trade/market/market.py b/whitebit/trade/market/market.py deleted file mode 100644 index 0e75d0a..0000000 --- a/whitebit/trade/market/market.py +++ /dev/null @@ -1,83 +0,0 @@ -import whitebit -from whitebit.client import Whitebit - - -class TradeMarketClient(Whitebit): - __MARKETS_URL = "/api/v2/public/markets" - __MARKET_ACTIVITY_URL = "/api/v2/public/ticker" - __SINGLE_MARKET_ACTIVITY_URL = "/api/v1/public/ticker" - __TICKER_URL = "/api/v4/public/ticker" - __TICKERS_URL = "/api/v1/public/tickers" - __SYMBOLS_URL = "/api/v1/public/symbols" - __KLINE_URL = "/api/v1/public/kline" - __TRADING_FEE_URL = "/api/v2/public/fee" - __FEE_LIST_URL = "/api/v4/public/fee" - __DEPTH_URL = "/api/v2/public/depth/" - __ORDERBOOK_URL = "/api/v4/public/orderbook/" - __DEALS_URL = "/api/v4/public/trades/" - __TRADE_HISTORY_URL = "/api/v1/public/history" - __ASSETS_URL = "/api/v4/public/assets" - - def __init__(self, api_key: str = '', api_secret: str = ''): - super().__init__(api_key, api_secret) - - def get_markets_info(self): - return self._request(method='GET', uri=self.__MARKETS_URL, auth=False) - - def get_tickers(self): - return self._request(method='GET', uri=self.__TICKERS_URL, auth=False) - - def get_available_tickers(self): - return self._request(method='GET', uri=self.__TICKER_URL, auth=False) - - def get_market_activity(self): - return self._request(method='GET', uri=self.__MARKET_ACTIVITY_URL, auth=False) - - def get_single_market_activity(self, market): - params = {'market': market} - return self._request(method='GET', uri=self.__SINGLE_MARKET_ACTIVITY_URL, params=params, auth=False) - - def get_symbols(self): - return self._request(method='GET', uri=self.__SYMBOLS_URL, auth=False) - - def get_kline(self, market: str, start: str = None, end: str = None, interval: str = None, limit: str = None): - params = {'market': market} - if start is not None: - params['start'] = start - if end is not None: - params['end'] = end - if interval is not None: - params['interval'] = interval - if limit is not None: - params['limit'] = limit - - return self._request(method='GET', uri=self.__KLINE_URL, params=params, auth=False) - - def get_trading_fee(self): - return self._request(method='GET', uri=self.__TRADING_FEE_URL, auth=False) - - def get_fee_list(self): - return self._request(method='GET', uri=self.__FEE_LIST_URL, auth=False) - - def get_order_book(self, market, limit: str = None, level: str = None): - params = {} - if limit is not None: - params['limit'] = limit - if level is not None: - params['level'] = level - return self._request(method='GET', uri=self.__ORDERBOOK_URL + market, params=params, auth=False) - - def get_depth(self, market): - return self._request(method='GET', uri=self.__DEPTH_URL + market, auth=False) - - def get_trade_history(self, market, last_id, limit: str = None): - params = {'market': market, 'lastId': last_id} - if limit is not None: - params['limit'] = limit - return self._request(method='GET', uri=self.__TRADE_HISTORY_URL, params=params, auth=False) - - def get_deals(self, market): - return self._request(method='GET', uri=self.__DEALS_URL + market, auth=False) - - def get_assets(self): - return self._request(method='GET', uri=self.__ASSETS_URL, auth=False) diff --git a/whitebit/trade/order/__init__.py b/whitebit/trade/order/__init__.py deleted file mode 100644 index cee96eb..0000000 --- a/whitebit/trade/order/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .order import TradeOrderClient diff --git a/whitebit/trade/order/order.py b/whitebit/trade/order/order.py deleted file mode 100644 index 5476538..0000000 --- a/whitebit/trade/order/order.py +++ /dev/null @@ -1,108 +0,0 @@ -import whitebit -from whitebit.client import Whitebit - - -class TradeOrderClient(Whitebit): - __ORDER_CANCEL_URL = "/api/v4/order/cancel" - __LIMIT_URL = "/api/v4/order/new" - __MARKET_URL = "/api/v4/order/market" - __MARKET_STOCK_URL = "/api/v4/order/stock_market" - __STOP_MARKET_URL = "/api/v4/order/stop_market" - __STOP_LIMIT_URL = "/api/v4/order/stop_limit" - __BULK_URL = "/api/v4/order/bulk" - __KILL_SWITCH_TIMER = "/api/v4/order/kill-switch" - __KILL_SWITCH_STATUS = "/api/v4/order/kill-switch/status" - - ORDER_TYPE_SPOT = "spot" - ORDER_TYPE_MARGIN = "margin" - ORDER_TYPE_FUTURES = "futures" - - def put_market(self, market: str, side: str, amount, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__MARKET_URL, params=params, auth=True) - - def put_market_stock(self, market: str, side: str, amount, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__MARKET_STOCK_URL, params=params, auth=True) - - def put_limit(self, market: str, side: str, amount, price, post_only: bool = False, ioc: bool = False, - client_order_id: str = ""): - params = self.build_limit_order(market, side, amount, price, post_only, ioc, client_order_id) - return self._request(method='POST', uri=self.__LIMIT_URL, params=params, auth=True) - - def put_stop_limit(self, market: str, side: str, amount, price, activation_price, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - "price": price, - "activation_price": activation_price, - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__STOP_LIMIT_URL, params=params, auth=True) - - def put_stop_market(self, market: str, side: str, amount, activation_price, client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - "activation_price": activation_price, - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return self._request(method='POST', uri=self.__STOP_MARKET_URL, params=params, auth=True) - - def cancel_order(self, market: str, order_id): - params = { - "market": market, - "orderId": order_id, - } - return self._request(method='POST', uri=self.__ORDER_CANCEL_URL, params=params, auth=True) - - def limit_bulk(self, orders: list): - params = { - "orders": orders - } - return self._request(method='POST', uri=self.__BULK_URL, params=params, auth=True) - - def build_limit_order(self, market: str, side: str, amount, price, post_only: bool = False, ioc: bool = False, - client_order_id: str = ""): - params = { - "market": market, - "side": side, - "amount": amount, - "price": price, - "postOnly": post_only, - "ioc": ioc - } - if client_order_id != "": - params['clientOrderId'] = client_order_id - return params - - def put_kill_switch(self, market: str, timeout: str, types: list = None): - params = { - "market": market, - "timeout": timeout, - } - if types: - params['types'] = types - return self._request(method='POST', uri=self.__KILL_SWITCH_TIMER, params=params, auth=True) - - def get_kill_switch_status(self, market: str = ""): - params = {} - if market != "": - params['market'] = market - return self._request(method='POST', uri=self.__KILL_SWITCH_STATUS, params=params, auth=True)