commit 52707d9acb5fd29450d4a018bb51399352211a91 Author: dustoair <107600816+dustoair@users.noreply.github.com> Date: Mon Jul 25 19:22:24 2022 +0800 open src diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..6327199 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,10 @@ +pipeline: + ssh: + image: 'me/sre/drone-ssh:20210519' + host: 172.16.0.22 + username: root + password: 123456 + port: 22 + #username: deploy+ volumes:+ -/root/drone_rsa:/root/ssh/drone_rsa key_path:/root/ssh/drone_rsa + script: + - echo"test ssh1" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2885d26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,118 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +*.idea/* +*.idea +*.iml +# C extensions +*.so +.DS_Store +config.json +venv +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +nohub.out +myquant.txt + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.idea +.idea/* + +# log file +grid_trader_log.txt +*.log + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..337e179 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +#mmmmmmmmmmmm/binance_grid_trader:67 +FROM python:3.9 +#RUN pip install -r requirements.txt +WORKDIR /app +ADD . /app +RUN python -m pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Dockerfile-requirements b/Dockerfile-requirements new file mode 100644 index 0000000..20ea662 --- /dev/null +++ b/Dockerfile-requirements @@ -0,0 +1,5 @@ +#mmmmmmmmmmmmmmmmmmmmmmmmm/binance_grid_trader_requirements:5 +FROM python:3.9 + +RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ +RUN pip install -r requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..c0e8b21 --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +![](https://s3.sre.ink/img/2021/01/21/a22526c00c30927e7836b77aef927729.jpg) +# 配置 文件`config.json`说明 + + + +| 配置 | 说明 | +| ----------- | ----------- | +| sleep_time | 循环间隔 | +| cancel_time | 取消过期订单间隔 | +| platform | 交易类型 现货交易 **binance_spot** 或者 合约交易**binance_future** | +|symbol | 交易对: BTCUSDT, BNBUSDT等 | +|api_key | 从交易所获取 | +|api_secret | 交易所获取 | +|profit_scroll | 滚动止盈比例,比如 1.05 表示触发滚动止盈后将卖单价格提到1.05被再挂上| +|profit_intend | 滚动触发比例,比如0.95 表示当前价格达到卖单价格的0.95后,触发滚动 | +|deficit_scroll | 滚动止损比例,比如0.9 表示在触发滚动止盈时候,同时将止损价设置为当前价格的0.9 | +|gap_percent | 期望利润率,网格交易的价格间隙
buy_price = round_to(float(check_order.get("price")) * (1 - float(config.gap_percent)),
sell_price = round_to(float(check_order.get("price")) * (1 + float(config.gap_percent)), float(config.min_price))
越大单次利润率越高,越小越容易成交 最好不超过1% | +|quantity | 每次下单的数量 比如50个XRP | +|min_price | 价格波动的最小单位, 用来计算价格精度: 如
XRPUSDT 0.00001
BTCUSDT 是0.01
BNBUSDT是0.0001
ETHUSDT 是0.01
这个价格要从交易所查看,每个交易对不一样。| +|price_stop_to_buy | 抛仓价,高于这个就不要买了 | +|price_stop_to_sell | 清仓价,低于这个就不要卖了 | +|min_qty | 最小的下单量
现货要求最小下单是10USDT等值的币
XRPUSDT的话 10USDT大概40个XRP
而对于合约来说, BTCUSDT要求是0.001个BTC | +|max_orders | 最多允许的挂买单数量
超过该值时候 删除最高价的一个买单| +|max_sell_orders|卖单的上限,达到上限之后不再创建买单;但是不影响创建卖单| +|proxy_host | 如果需要用代理的话,请填写你的代理IP | +|proxy_port | 代理端口号 | +|dingding_robot_url | 钉钉机器人的地址 | +|dingding_user_phone | 自己在钉钉群的手机号 | +|user_email | 邮件接收地址 | +|SMTP_HOST | 邮件服务器 | +|SMTP_PORT | 邮件服务器端口 | +|SMTP_USER | 邮件服务器发信邮箱 | +|SMTP_PASSWORD | 邮件服务器发信邮箱密码 | + + + + +例如: + +```json +{ + "platform": "binance_spot", + "symbol": "LINKUSDT", + "api_key": "11111111111111111111111111111", + "api_secret": "2222222222222222222222222", + "gap_percent": 0.005, + "quantity": 5, + "min_price": 0.0001, + "price_stop_to_buy": 22, + "price_stop_to_sell": 14, + "min_qty": 5, + "max_orders": 10, + "max_sell_orders": 10, + "proxy_host": "", + "proxy_port": 0, + "dingding_robot_url": "https://oapi.dingtalk.com/robot/send?access_token=33333333333333333333333333333d29c", + "dingding_user_phone": "33333333333333333", + "user_email": "3333333333333333333333", + "SMTP_HOST": "33333333333333333333", + "SMTP_PORT": 80, + "SMTP_USER": "33333333333333333333333", + "SMTP_PASSWORD": "33333333333333333333333333" +} + +``` + + + +### start.sh +```bash +nohup python -u main.py > grid_nohup.out 2>&1 & +``` + + + +## 网格交易策略使用行情 +- 震荡行情 +- 适合币圈的高波动率的品种 +- 适合现货, 如果交易合约,需要注意防止极端行情爆仓。 + +# 51bitquant网格交易策略 +https://github.com/51bitquant/binance_grid_trader + + +网格交易的原理视频讲解链接: +[https://www.bilibili.com/video/BV1Jg4y1v7vr/](https://www.bilibili.com/video/BV1Jg4y1v7vr/) + + +视频讲解如下: +[https://www.bilibili.com/video/BV1eK4y147HT/](https://www.bilibili.com/video/BV1eK4y147HT/) + +https://github.com/hengxuZ/binance-quantization +# 币圈低风险套利赚钱-年化15%以上 +如何在币圈进行低风险的套利。 + + +## 买卖出入金的方式 +通过场外OTC,使用人民购买USDT,付给对方钱后,点击已付款,OTC商家会释放相应USDT给你。 在把法币的资产划转到币币账户进行交易相应的数字资产。 + +相应的如果你想把数字资产换成法币,那么你把你的资产划转到法币,然后出售资产,在收到对方的钱,且确认是对方的账户转给你,数目也是对的情况下,点击已收款,然后释放对应的数字资产给商家。 + + +## 存币赚取利息 + 通过存币,交易所会帮你把资产借贷给相应的人,然后对方需要支付相应的利息给你。 + +## 挖矿赚利息 +类似DEFI,挖矿,或者CEFI挖矿的方式。为相应的交易对提供流动性,赚取对应的手续费 + +## 永续资金费率套利 +在多头市场情况下,永续资金费率有的高达0.75%,一般情况下,有的资金费率在万1到千2之间,也有可能是负是资金费率。正的资金费率表示都头出资金费率,负的资金费率表示空方付对应的资金费率。 + +## 远近合约跨期套利 +一般情况下,远期合约的价格币现货价格、永续合约或者近期的合约的价格要高。如果你买入现货,去对应的远期合约做空对应的资产,那么就相当于你把资产卖出一个较高的价格,等到他们价格相等或者接近的时候,再平仓。不过这个一般是要用程序去执行比较好。这样可以随时间监控他们之间的价差,捕捉更多的套利机会。 + +## 持有平台币参与交易所的一些活动 +持有BNB或者OKB,参加他们平台的一些活动,比如最近的BNB要用来挖矿,类似之前的IEO活动。 + + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bakreq.txt b/bakreq.txt new file mode 100644 index 0000000..a3e92b7 --- /dev/null +++ b/bakreq.txt @@ -0,0 +1,6 @@ +requests==2.22.0 +dingtalkchatbot +pytz +APScheduler==3.6.3 +aiohttp==3.6.2 +websocket_client==0.57.0 \ No newline at end of file diff --git a/binance/__init__.py b/binance/__init__.py new file mode 100644 index 0000000..5b75099 --- /dev/null +++ b/binance/__init__.py @@ -0,0 +1,3 @@ +from .binance_constant import OrderType, OrderStatus, OrderSide, RequestMethod,Interval +from .binance_spot import BinanceSpotHttp +from .binance_future import BinanceFutureHttp \ No newline at end of file diff --git a/binance/binance_constant.py b/binance/binance_constant.py new file mode 100644 index 0000000..ea757d3 --- /dev/null +++ b/binance/binance_constant.py @@ -0,0 +1,55 @@ +from enum import Enum + +class RequestMethod(Enum): + """ + 请求的方法. + """ + GET = 'GET' + POST = 'POST' + PUT = 'PUT' + DELETE = 'DELETE' + + + +class OrderStatus(Enum): + NEW = "NEW" + PARTIALLY_FILLED = "PARTIALLY_FILLED" + FILLED = "FILLED" + CANCELED = "CANCELED" + PENDING_CANCEL = "PENDING_CANCEL" + REJECTED = "REJECTED" + EXPIRED = "EXPIRED" + + +class OrderType(Enum): + LIMIT = "LIMIT" + MARKET = "MARKET" + STOP = "STOP" + + + +class OrderSide(Enum): + BUY = "BUY" + SELL = "SELL" + + +class Interval(Enum): + """ + 请求的K线数据.. + """ + MINUTE_1 = '1m' + MINUTE_3 = '3m' + MINUTE_5 = '5m' + MINUTE_15 = '15m' + MINUTE_30 = '30m' + HOUR_1 = '1h' + HOUR_2 = '2h' + HOUR_4 = '4h' + HOUR_6 = '6h' + HOUR_8 = '8h' + HOUR_12 = '12h' + DAY_1 = '1d' + DAY_3 = '3d' + WEEK_1 = '1w' + MONTH_1 = '1M' + diff --git a/binance/binance_future.py b/binance/binance_future.py new file mode 100644 index 0000000..0cb448b --- /dev/null +++ b/binance/binance_future.py @@ -0,0 +1,345 @@ +from binance import OrderType, OrderStatus, OrderSide, RequestMethod,Interval +import requests +import time +import hmac +import hashlib +from threading import Thread, Lock +from datetime import datetime + +class BinanceFutureHttp(object): + + def __init__(self, api_key=None, secret=None, host=None, proxy_host="", proxy_port=0, timeout=5, try_counts=5): + self.key = api_key + self.secret = secret + self.host = host if host else "https://fapi.binance.com" + self.recv_window = 5000 + self.timeout = timeout + self.order_count_lock = Lock() + self.order_count = 1_000_000 + self.try_counts = try_counts # 失败尝试的次数. + self.proxy_host = proxy_host + self.proxy_port = proxy_port + + @property + def proxies(self): + if self.proxy_host and self.proxy_port: + proxy = f"http://{self.proxy_host}:{self.proxy_port}" + return {"http": proxy, "https": proxy} + return {} + + def build_parameters(self, params: dict): + keys = list(params.keys()) + keys.sort() + return '&'.join([f"{key}={params[key]}" for key in params.keys()]) + + def request(self, req_method: RequestMethod, path: str, requery_dict=None, verify=False): + url = self.host + path + + if verify: + query_str = self._sign(requery_dict) + url += '?' + query_str + elif requery_dict: + url += '?' + self.build_parameters(requery_dict) + headers = {"X-MBX-APIKEY": self.key} + + for i in range(0, self.try_counts): + try: + response = requests.request(req_method.value, url=url, headers=headers, timeout=self.timeout, proxies=self.proxies) + if response.status_code == 200: + return response.json() + else: + print(f"请求没有成功: {response.status_code}, 继续尝试请求") + except Exception as error: + print(f"请求:{path}, 发生了错误: {error}, 时间: {datetime.now()}") + time.sleep(3) + + def server_time(self): + path = '/fapi/v1/time' + return self.request(req_method=RequestMethod.GET, path=path) + + def exchangeInfo(self): + + """ + {'timezone': 'UTC', 'serverTime': 1570802268092, 'rateLimits': + [{'rateLimitType': 'REQUEST_WEIGHT', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}, + {'rateLimitType': 'ORDERS', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}], + 'exchangeFilters': [], 'symbols': + [{'symbol': 'BTCUSDT', 'status': 'TRADING', 'maintMarginPercent': '2.5000', 'requiredMarginPercent': '5.0000', + 'baseAsset': 'BTC', 'quoteAsset': 'USDT', 'pricePrecision': 2, 'quantityPrecision': 3, 'baseAssetPrecision': 8, + 'quotePrecision': 8, + 'filters': [{'minPrice': '0.01', 'maxPrice': '100000', 'filterType': 'PRICE_FILTER', 'tickSize': '0.01'}, + {'stepSize': '0.001', 'filterType': 'LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, + {'stepSize': '0.001', 'filterType': 'MARKET_LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, + {'limit': 200, 'filterType': 'MAX_NUM_ORDERS'}, + {'multiplierDown': '0.8500', 'multiplierUp': '1.1500', 'multiplierDecimal': '4', 'filterType': 'PERCENT_PRICE'}], + 'orderTypes': ['LIMIT', 'MARKET', 'STOP'], 'timeInForce': ['GTC', 'IOC', 'FOK', 'GTX']}]} + + :return: + """ + + path = '/fapi/v1/exchangeInfo' + return self.request(req_method=RequestMethod.GET, path=path) + + def order_book(self, symbol, limit=5): + limits = [5, 10, 20, 50, 100, 500, 1000] + if limit not in limits: + limit = 5 + + path = "/fapi/v1/depth" + query_dict = {"symbol": symbol, + "limit": limit + } + + return self.request(RequestMethod.GET, path, query_dict) + + def get_kline(self, symbol, interval: Interval, start_time=None, end_time=None, limit=500, max_try_time=10): + """ + + :param symbol: + :param interval: + :param start_time: + :param end_time: + :param limit: + :return: + [ + 1499040000000, // 开盘时间 + "0.01634790", // 开盘价 + "0.80000000", // 最高价 + "0.01575800", // 最低价 + "0.01577100", // 收盘价(当前K线未结束的即为最新价) + "148976.11427815", // 成交量 + 1499644799999, // 收盘时间 + "2434.19055334", // 成交额 + 308, // 成交笔数 + "1756.87402397", // 主动买入成交量 + "28.46694368", // 主动买入成交额 + "17928899.62484339" // 请忽略该参数 + ] + """ + path = "/fapi/v1/klines" + + query_dict = { + "symbol": symbol, + "interval": interval.value, + "limit": limit + } + + if start_time: + query_dict['startTime'] = start_time + + if end_time: + query_dict['endTime'] = end_time + + for i in range(max_try_time): + data = self.request(RequestMethod.GET, path, query_dict) + if isinstance(data, list) and len(data): + return data + + def get_latest_price(self, symbol): + path = "/fapi/v1/ticker/price" + query_dict = {"symbol": symbol} + return self.request(RequestMethod.GET, path, query_dict) + + def get_ticker(self, symbol): + path = "/fapi/v1/ticker/bookTicker" + query_dict = {"symbol": symbol} + return self.request(RequestMethod.GET, path, query_dict) + + ########################### the following request is for private data ######################## + + def _timestamp(self): + return int(time.time() * 1000) + + def _sign(self, params): + + requery_string = self.build_parameters(params) + hexdigest = hmac.new(self.secret.encode('utf8'), requery_string.encode("utf-8"), hashlib.sha256).hexdigest() + return requery_string + '&signature=' + str(hexdigest) + + def get_client_order_id(self): + + """ + generate the client_order_id for user. + :return: new client order id + """ + with self.order_count_lock: + self.order_count += 1 + return "x-cLbi5uMH" + str(self._timestamp()) + str(self.order_count) + + def place_order(self, symbol: str, order_side: OrderSide, order_type: OrderType, quantity, price, + time_inforce="GTC", client_order_id=None, recvWindow=5000, stop_price=0): + + """ + 下单.. + :param symbol: BTCUSDT + :param side: BUY or SELL + :param type: LIMIT MARKET STOP + :param quantity: 数量. + :param price: 价格 + :param stop_price: 停止单的价格. + :param time_inforce: + :param params: 其他参数 + + LIMIT : timeInForce, quantity, price + MARKET : quantity + STOP: quantity, price, stopPrice + :return: + + """ + + path = '/fapi/v1/order' + + if client_order_id is None: + client_order_id = self.get_client_order_id() + + params = { + "symbol": symbol, + "side": order_side.value, + "type": order_type.value, + "quantity": quantity, + "price": price, + "recvWindow": recvWindow, + "timeInForce": "GTC", + "timestamp": self._timestamp(), + "newClientOrderId": client_order_id + } + + if order_type == OrderType.LIMIT: + params['timeInForce'] = time_inforce + + if order_type == OrderType.MARKET: + if params.get('price'): + del params['price'] + + if order_type == OrderType.STOP: + if stop_price > 0: + params["stopPrice"] = stop_price + else: + raise ValueError("stopPrice must greater than 0") + # print(params) + return self.request(RequestMethod.POST, path=path, requery_dict=params, verify=True) + + def get_order(self, symbol, client_order_id=None): + path = "/fapi/v1/order" + query_dict = {"symbol": symbol, "timestamp": self._timestamp()} + if client_order_id: + query_dict["origClientOrderId"] = client_order_id + + return self.request(RequestMethod.GET, path, query_dict, verify=True) + + def cancel_order(self, symbol, client_order_id=None): + path = "/fapi/v1/order" + params = {"symbol": symbol, "timestamp": self._timestamp()} + if client_order_id: + params["origClientOrderId"] = client_order_id + + return self.request(RequestMethod.DELETE, path, params, verify=True) + + def get_open_orders(self, symbol=None): + path = "/fapi/v1/openOrders" + + params = {"timestamp": self._timestamp()} + if symbol: + params["symbol"] = symbol + + return self.request(RequestMethod.GET, path, params, verify=True) + + def cancel_open_orders(self, symbol): + """ + 撤销某个交易对的所有挂单 + :param symbol: symbol + :return: return a list of orders. + """ + path = "/fapi/v1/allOpenOrders" + + params = {"timestamp": self._timestamp(), + "recvWindow": self.recv_window, + "symbol": symbol + } + + return self.request(RequestMethod.DELETE, path, params, verify=True) + + def get_balance(self): + """ + [{'accountId': 18396, 'asset': 'USDT', 'balance': '530.21334791', 'withdrawAvailable': '530.21334791', 'updateTime': 1570330854015}] + :return: + """ + path = "/fapi/v1/balance" + params = {"timestamp": self._timestamp()} + + return self.request(RequestMethod.GET, path=path, requery_dict=params, verify=True) + + def get_account_info(self): + """ + {'feeTier': 2, 'canTrade': True, 'canDeposit': True, 'canWithdraw': True, 'updateTime': 0, 'totalInitialMargin': '0.00000000', + 'totalMaintMargin': '0.00000000', 'totalWalletBalance': '530.21334791', 'totalUnrealizedProfit': '0.00000000', + 'totalMarginBalance': '530.21334791', 'totalPositionInitialMargin': '0.00000000', 'totalOpenOrderInitialMargin': '0.00000000', + 'maxWithdrawAmount': '530.2133479100000', 'assets': + [{'asset': 'USDT', 'walletBalance': '530.21334791', 'unrealizedProfit': '0.00000000', 'marginBalance': '530.21334791', + 'maintMargin': '0.00000000', 'initialMargin': '0.00000000', 'positionInitialMargin': '0.00000000', 'openOrderInitialMargin': '0.00000000', + 'maxWithdrawAmount': '530.2133479100000'}]} + :return: + """ + path = "/fapi/v1/account" + params = {"timestamp": self._timestamp()} + return self.request(RequestMethod.GET, path, params, verify=True) + + def get_position_info(self): + """ + [{'symbol': 'BTCUSDT', 'positionAmt': '0.000', 'entryPrice': '0.00000', 'markPrice': '8326.40833498', 'unRealizedProfit': '0.00000000', 'liquidationPrice': '0'}] + :return: + """ + path = "/fapi/v1/positionRisk" + params = {"timestamp": self._timestamp()} + return self.request(RequestMethod.GET, path, params, verify=True) + + +if __name__ == '__main__': + # import pandas as pd + + key = "xxxx" + secret = 'xxxx' + binance = BinanceFutureHttp(api_key=key, secret=secret) + + # import datetime + # print(datetime.datetime.now()) + + data = binance.get_kline('BTCUSDT', Interval.HOUR_1, limit=100) + print(data) + print(isinstance(data, list)) + + exit() + # info = binance.exchangeInfo() + # print(info) + # exit() + # print(binance.order_book(symbol='BTCUSDT', limit=5)) + # exit() + + # print(binance.latest_price('BTCUSDT')) + # kline = binance.kline('BTCUSDT', interval='1m') + # print(pd.DataFrame(kline)) + + # print(binance.ticker("BTCUSDT")) + + # print(binance.get_ticker('BTCUSDT')) + # {'symbol': 'BTCUSDT', 'side': 'SELL', 'type': 'LIMIT', 'quantity': 0.001, 'price': 8360.82, 'recvWindow': 5000, 'timestamp': 1570969995974, 'timeInForce': 'GTC'} + + # data = binance.place_order(symbol="BTCUSDT", side=OrderSide.BUY, order_type=OrderType.MARKET, quantity=0.001, price=8250.82) + # print(data) + + # cancel_order = binance.cancel_order("BTCUSDT", order_id="30714952") + # print(cancel_order) + + # balance = binance.get_balance() + # print(balance) + + # account_info = binance.get_account_info() + # print(account_info) + + # account_info = binance.get_position_info() + # print(account_info) + + """ + {'orderId': 30714952, 'symbol': 'BTCUSDT', 'accountId': 18396, 'status': 'NEW', 'clientOrderId': 'ZC3qSbzbODl0GId9idK9hM', 'price': '7900', 'origQty': '0.010', 'executedQty': '0', 'cumQty': '0', 'cumQuote': '0', 'timeInForce': 'GTC', 'type': 'LIMIT', 'reduceOnly': False, 'side': 'BUY', 'stopPrice': '0', 'updateTime': 1569658787083} + {'orderId': 30714952, 'symbol': 'BTCUSDT', 'accountId': 18396, 'status': 'NEW', 'clientOrderId': 'ZC3qSbzbODl0GId9idK9hM', 'price': '7900', 'origQty': '0.010', 'executedQty': '0', 'cumQty': '0', 'cumQuote': '0', 'timeInForce': 'GTC', 'type': 'LIMIT', 'reduceOnly': False, 'side': 'BUY', 'stopPrice': '0', 'updateTime': 1569658787083} + """ diff --git a/binance/binance_future_trader.py b/binance/binance_future_trader.py new file mode 100644 index 0000000..f50f002 --- /dev/null +++ b/binance/binance_future_trader.py @@ -0,0 +1,226 @@ +from binance import BinanceFutureHttp, OrderStatus, OrderType, OrderSide +from utils import config +from utils import round_to +import logging +from datetime import datetime + +class BinanceFutureTrader(object): + + def __init__(self): + self.http_client = BinanceFutureHttp(api_key=config.api_key, secret=config.api_secret, proxy_host=config.proxy_host, proxy_port=config.proxy_port) + + self.buy_orders = [] # 买单. buy orders + self.sell_orders = [] # 卖单. sell orders + + + def get_bid_ask_price(self): + + ticker = self.http_client.get_ticker(config.symbol) + + bid_price = 0 + ask_price = 0 + if ticker: + bid_price = float(ticker.get('bidPrice', 0)) + ask_price = float(ticker.get('askPrice', 0)) + + return bid_price, ask_price + + def get_latest_price(self): + #获取最新价格 + ticker = self.http_client.get_latest_price(config.symbol) + price = 0 + if ticker: + price = float(ticker.get('price', 0)) + return price + + def grid_trader(self): + """ + 执行核心逻辑,网格交易的逻辑. + + the grid trading logic + :return: + """ + + bid_price, ask_price = self.get_bid_ask_price() + print(f"bid_price: {bid_price}, ask_price: {ask_price}, time: {datetime.now()}") + + quantity = round_to(float(config.quantity), float(config.min_qty)) + + self.buy_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. + self.sell_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. + + buy_delete_orders = [] # 需要删除买单 + sell_delete_orders = [] # 需要删除的卖单 + + + # 买单逻辑,检查成交的情况. + for buy_order in self.buy_orders: + + check_order = self.http_client.get_order(buy_order.get('symbol', config.symbol),client_order_id=buy_order.get('clientOrderId')) + + if check_order: + if check_order.get('status') == OrderStatus.CANCELED.value: + buy_delete_orders.append(buy_order) + print(f"buy order status was canceled: {check_order.get('status')}, time: {datetime.now()}") + elif check_order.get('status') == OrderStatus.FILLED.value: + # 买单成交,挂卖单. + print(f"买单成交了, 时间: {datetime.now()}") + logging.info(f"买单成交时间: {datetime.now()}, 价格: {check_order.get('price')}, 数量: {check_order.get('origQty')}") + + sell_price = round_to(float(check_order.get("price")) * (1 + float(config.gap_percent)), float(config.min_price)) + + if 0 < sell_price < ask_price: + # 防止价格 + sell_price = round_to(ask_price, float(config.min_price)) + + new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, order_type=OrderType.LIMIT, quantity=quantity, price=sell_price) + if new_sell_order: + print(f"买单成交,下了对应价格的卖单: {new_sell_order}, 时间: {datetime.now()}") + buy_delete_orders.append(buy_order) + self.sell_orders.append(new_sell_order) + + buy_price = round_to(float(check_order.get("price")) * (1 - float(config.gap_percent)), + config.min_price) + + if buy_price > bid_price > 0: + buy_price = round_to(buy_price, float(config.min_price)) + + new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=quantity, price=buy_price) + if new_buy_order: + print(f"买单成交,下了更低价的买单: {new_buy_order}, 时间: {datetime.now()}") + self.buy_orders.append(new_buy_order) + + + elif check_order.get('status') == OrderStatus.NEW.value: + print(f"buy order status is: New , 时间: {datetime.now()}") + else: + print(f"buy order status is not above options: {check_order.get('status')}, 时间: {datetime.now()}") + + # 过期或者拒绝的订单删除掉. + for delete_order in buy_delete_orders: + self.buy_orders.remove(delete_order) + + # 卖单逻辑, 检查卖单成交情况. + for sell_order in self.sell_orders: + + check_order = self.http_client.get_order(sell_order.get('symbol', config.symbol), + client_order_id=sell_order.get('clientOrderId')) + if check_order: + if check_order.get('status') == OrderStatus.CANCELED.value: + sell_delete_orders.append(sell_order) + + print(f"sell order status was canceled: {check_order.get('status')}, 时间: {datetime.now()}") + elif check_order.get('status') == OrderStatus.FILLED.value: + print(f"卖单成交了, 时间: {datetime.now()}") + logging.info( + f"卖单成交时间: {datetime.now()}, 价格: {check_order.get('price')}, 数量: {check_order.get('origQty')}") + # 卖单成交,先下买单. + buy_price = round_to(float(check_order.get("price")) * (1 - float(config.gap_percent)), float(config.min_price)) + if buy_price > bid_price > 0: + buy_price = round_to(buy_price, float(config.min_price)) + + new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, + order_type=OrderType.LIMIT, quantity=quantity, price=buy_price) + if new_buy_order: + print(f"卖单成交,下了对应价格的买单: {new_buy_order}, 时间: {datetime.now()}") + sell_delete_orders.append(sell_order) + self.buy_orders.append(new_buy_order) + + sell_price = round_to(float(check_order.get("price")) * (1 + float(config.gap_percent)), float(config.min_price)) + + if 0 < sell_price < ask_price: + # 防止价格 + sell_price = round_to(ask_price, float(config.min_price)) + + new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, + order_type=OrderType.LIMIT, quantity=quantity, + price=sell_price) + if new_sell_order: + print(f"卖单成交,下了更高价的卖单: {new_sell_order}, 时间: {datetime.now()}") + self.sell_orders.append(new_sell_order) + + elif check_order.get('status') == OrderStatus.NEW.value: + print(f"sell order status is: New, 时间: {datetime.now()}") + else: + print(f"sell order status is not in above options: {check_order.get('status')}, 时间: {datetime.now()}") + + # 过期或者拒绝的订单删除掉. + for delete_order in sell_delete_orders: + self.sell_orders.remove(delete_order) + + # 没有买单的时候. + if len(self.buy_orders) <= 0: + if bid_price > 0: + price = round_to(bid_price * (1 - float(config.gap_percent)), float(config.min_price)) + + buy_order = self.http_client.place_order(symbol=config.symbol,order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=quantity,price=price) + print(f'没有买单,根据盘口下买单: {buy_order}, 时间: {datetime.now()}') + if buy_order: + self.buy_orders.append(buy_order) + else: + self.buy_orders.sort(key=lambda x: float(x['price']), reverse=False) # 最低价到最高价 + delete_orders = [] + for i in range(len(self.buy_orders) -1): + order = self.buy_orders[i] + next_order = self.buy_orders[i+1] + + if float(next_order['price'])/float(order['price']) - 1 < 0.001: + print(f"买单之间价差太小,撤销订单:{next_order}, 时间: {datetime.now()}") + cancel_order = self.http_client.cancel_order(next_order.get('symbol'), + client_order_id=next_order.get('clientOrderId')) + if cancel_order: + delete_orders.append(next_order) + + for order in delete_orders: + self.buy_orders.remove(order) + + + if len(self.buy_orders) > int(config.max_orders): # 最多允许的挂单数量. + # 订单数量比较多的时候. + self.buy_orders.sort(key=lambda x: float(x['price']), reverse=False) # 最低价到最高价 + + delete_order = self.buy_orders[0] + print(f"订单太多了,撤销最低价的买单:{delete_order}, 时间: {datetime.now()}") + order = self.http_client.cancel_order(delete_order.get('symbol'), client_order_id=delete_order.get('clientOrderId')) + if order: + self.buy_orders.remove(delete_order) + + # 没有卖单的时候. + if len(self.sell_orders) <= 0: + if ask_price > 0: + price = round_to(ask_price * (1 + float(config.gap_percent)), float(config.min_price)) + sell_order = self.http_client.place_order(symbol=config.symbol,order_side=OrderSide.SELL, order_type=OrderType.LIMIT, quantity=quantity,price=price) + print(f'没有卖单,根据盘口下卖单:{sell_order} , 时间: {datetime.now()}') + if sell_order: + self.sell_orders.append(sell_order) + + else: + self.sell_orders.sort(key=lambda x: float(x['price']), reverse=False) # 最低价到最高价 + delete_orders = [] + for i in range(len(self.sell_orders) - 1): + order = self.sell_orders[i] + next_order = self.sell_orders[i + 1] + + if float(next_order['price']) / float(order['price']) - 1 < 0.001: + print(f"卖单之间价差太小,撤销订单:{next_order}, 时间: {datetime.now()}") + cancel_order = self.http_client.cancel_order(next_order.get('symbol'), + client_order_id=next_order.get('clientOrderId')) + if cancel_order: + delete_orders.append(next_order) + + for order in delete_orders: + self.sell_orders.remove(order) + + if len(self.sell_orders) > int(config.max_orders): # 最多允许的挂单数量. + # 订单数量比较多的时候. + self.sell_orders.sort(key=lambda x: x['price'], reverse=True) # 最高价到最低价 + + delete_order = self.sell_orders[0] + print(f"订单太多了,撤销最高价的卖单:{delete_order}, 时间:{datetime.now()}") + order = self.http_client.cancel_order(delete_order.get('symbol'), + client_order_id=delete_order.get('clientOrderId')) + if order: + self.sell_orders.remove(delete_order) + + + diff --git a/binance/binance_spot.py b/binance/binance_spot.py new file mode 100644 index 0000000..88febc9 --- /dev/null +++ b/binance/binance_spot.py @@ -0,0 +1,456 @@ +from binance import OrderType, OrderStatus, OrderSide, RequestMethod,Interval +import requests +import time +import hmac +import hashlib +from threading import Lock +from utils import config +from utils import utility + + +class BinanceSpotHttp(object): + ''' + # https://binance-docs.github.io/apidocs/spot/cn/ # 152d3db758 + # 如果上面的baseURL访问有性能问题,请访问下面的API集群: + # https://api1.binance.com + # https://api2.binance.com + # https://api3.binance.com + # https://api4.binance.com + # ORDERS rateLimits 100次/10s 200000次/1day + ''' + + def __init__(self, api_key=None, secret=None, host=None, proxy_host=None, proxy_port=0, timeout=5, try_counts=5): + self.api_key = api_key + self.secret = secret + self.host = host if host else "https://api.binance.com" + self.recv_window = 10000 + self.timeout = timeout + self.order_count_lock = Lock() + self.order_count = 1_000_000 + self.try_counts = try_counts # 失败尝试的次数. + self.proxy_host = proxy_host + self.proxy_port = proxy_port + + @property + def proxies(self): + if self.proxy_host and self.proxy_port: + proxy = f"http://{self.proxy_host}:{self.proxy_port}" + return {"http": proxy, "https": proxy} + + return None + + def build_parameters(self, params: dict): + keys = list(params.keys()) + keys.sort() + return '&'.join([f"{key}={params[key]}" for key in params.keys()]) + + def request(self, req_method: RequestMethod, path: str, requery_dict=None, verify=False): + url = self.host + path + + if verify: + query_str = self._sign(requery_dict) + url += '?' + query_str + elif requery_dict: + url += '?' + self.build_parameters(requery_dict) + headers = {"X-MBX-APIKEY": self.api_key} + + for i in range(0, self.try_counts): + try: + response = requests.request(req_method.value, url=url, headers=headers, timeout=self.timeout, proxies=self.proxies) + http_code = response.status_code + + if http_code == 200: + return response.json() + elif http_code == 400 and "Account has insufficient balance for requested action." in str(response.json()): + print(f"交易对: {config.symbol} 余额不足") + break + elif http_code == 400 and "Filter failure: MIN_NOTIONAL" in str(response.json()): + print(f"交易对: {config.symbol} 最小下单数量不够,多买点") + break + elif http_code == 400 and "Unknown order sent." in str(response.json()): + print(f"交易对: {config.symbol} 订单号错误") + print(f"请求结果{str(response.json())}") + break + elif http_code == 400 and "Illegal characters found in parameter 'symbol'" in str(response.json()): + print(f"交易对: {config.symbol} 交易对配置错误,无法识别") + break + elif http_code == 400 and "This action disabled is on this account" in str(response.json()): + print(f"交易对: {config.symbol} 账号无权此操作") + break + else: + print("未知请求错误:") + print(response.json(), http_code) + + except Exception as error: + print(f"请求:{path}, 发生了错误: {error}") + time.sleep(3) + + def get_ping(self): + #测试服务器连通性 + #测试能否联通 Rest API。 + path = '/api/v3/ping' + return self.request(req_method=RequestMethod.GET, path=path) + + def get_server_time(self): + #获取服务器时间 + #测试能否联通 Rest API 并 获取服务器时间。 + path = '/api/v3/time' + return self.request(req_method=RequestMethod.GET, path=path) + + + + + def get_exchange_info(self): + #交易规范信息 + #获取交易规则和交易对信息。 + + """ + return: + the exchange info in json format: + {'timezone': 'UTC', 'serverTime': 1570802268092, 'rateLimits': + [{'rateLimitType': 'REQUEST_WEIGHT', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}, + {'rateLimitType': 'ORDERS', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}], + 'exchangeFilters': [], 'symbols': + [{'symbol': 'BTCUSDT', 'status': 'TRADING', 'maintMarginPercent': '2.5000', 'requiredMarginPercent': '5.0000', + 'baseAsset': 'BTC', 'quoteAsset': 'USDT', 'pricePrecision': 2, 'quantityPrecision': 3, 'baseAssetPrecision': 8, + 'quotePrecision': 8, + 'filters': [{'minPrice': '0.01', 'maxPrice': '100000', 'filterType': 'PRICE_FILTER', 'tickSize': '0.01'}, + {'stepSize': '0.001', 'filterType': 'LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, + {'stepSize': '0.001', 'filterType': 'MARKET_LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, + {'limit': 200, 'filterType': 'MAX_NUM_ORDERS'}, + {'multiplierDown': '0.8500', 'multiplierUp': '1.1500', 'multiplierDecimal': '4', 'filterType': 'PERCENT_PRICE'}], + 'orderTypes': ['LIMIT', 'MARKET', 'STOP'], 'timeInForce': ['GTC', 'IOC', 'FOK', 'GTX']}]} + + """ + + path = '/api/v3/exchangeInfo' + return self.request(req_method=RequestMethod.GET, path=path) + + + + def get_order_book(self, symbol, limit=5): + """ + 深度信息 + https://binance-docs.github.io/apidocs/spot/cn/#e7746f7d60 + :param symbol: BTCUSDT, BNBUSDT ect, 交易对. + :param limit: market depth. 默认 100; 最大 5000. 可选值:[5, 10, 20, 50, 100, 500, 1000, 5000] + :return: return order_book in json 返回订单簿,json数据格式. + """ + limits = [5, 10, 20, 50, 100, 500, 1000] + if limit not in limits: + limit = 5 + + path = "/api/v3/depth" + query_dict = {"symbol": symbol, + "limit": limit + } + + return self.request(RequestMethod.GET, path, query_dict) + + def get_recent_trades(self, symbol, limit=5): + """ + 近期成交列表 + https://binance-docs.github.io/apidocs/spot/cn/#38a975b802 + :param symbol: BTCUSDT, BNBUSDT ect, 交易对. + :param limit: 默认 500; 最大值 1000. + :return: return order_book in json 返回订单簿,json数据格式. + """ + + if limit >= 1000: + limit = 1000 + + path = "/api/v3/trades" + query_dict = {"symbol": symbol, + "limit": limit + } + + return self.request(RequestMethod.GET, path, query_dict) + + def get_kline(self, symbol, interval: Interval, start_time=None, end_time=None, limit=500, max_try_time=10): + """ + 获取K线数据. + 每根K线代表一个交易对。 + 每根K线的开盘时间可视为唯一ID + https://binance-docs.github.io/apidocs/spot/cn/#c59e471e81 + :param symbol: + :param interval: 间隔 + :param start_time: + :param end_time: + :param limit: 默认 500; 最大 1000 + :param max_try_time: + :return: + [ + [ + 1499040000000, // 开盘时间 + "0.01634790", // 开盘价 + "0.80000000", // 最高价 + "0.01575800", // 最低价 + "0.01577100", // 收盘价(当前K线未结束的即为最新价) + "148976.11427815", // 成交量 + 1499644799999, // 收盘时间 + "2434.19055334", // 成交额 + 308, // 成交笔数 + "1756.87402397", // 主动买入成交量 + "28.46694368", // 主动买入成交额 + "17928899.62484339" // 请忽略该参数 + ] +] + """ + path = "/api/v3/klines" + query_dict = { + "symbol": symbol, + "interval": interval, + "limit": limit + } + + # query_dict = { + # "symbol": symbol, + # "interval": interval.value, + # "limit": limit + # } + + if start_time: + query_dict['startTime'] = start_time + + if end_time: + query_dict['endTime'] = end_time + + for i in range(max_try_time): + data = self.request(RequestMethod.GET, path, query_dict) + if isinstance(data, list) and len(data): + return data + + def get_latest_price(self, symbol): + """ + 获取交易对最新价格 + 不发送交易对参数,则会返回所有交易对信息 + 权重: + 1 单一交易对; + 2 交易对参数缺失 + :param symbol: 获取最新的价格. + :return: {'symbol': 'BTCUSDT', 'price': '9168.90000000'} + + + """ + path = "/api/v3/ticker/price" + query_dict = {"symbol": symbol} + return self.request(RequestMethod.GET, path, query_dict) + + def get_avg_price_5min(self, symbol): + """ + 当前平均价格 in last 5min + 权重: 1 + :param symbol: + :return: + { + "mins": 5, + "price": "9.35751834" + } + + """ + path = "/api/v3/avgPrice" + query_dict = {"symbol": symbol} + return self.request(RequestMethod.GET, path, query_dict) + + def get_price_info_24h(self, symbol): + """ + 24hr 价格变动情况 + 24 小时滚动窗口价格变动数据。 请注意,不携带symbol参数会返回全部交易对数据,不仅数据庞大,而且权重极高 + 权重: 1 单一交易对; + 40 交易对参数缺失; + 请注意,不携带symbol参数会返回全部交易对数据 + + :param symbol: + :return: +{ + "symbol": "BNBBTC", + "priceChange": "-94.99999800", + "priceChangePercent": "-95.960", + "weightedAvgPrice": "0.29628482", + "prevClosePrice": "0.10002000", + "lastPrice": "4.00000200", + "lastQty": "200.00000000", + "bidPrice": "4.00000000", + "askPrice": "4.00000200", + "openPrice": "99.00000000", + "highPrice": "100.00000000", + "lowPrice": "0.10000000", + "volume": "8913.30000000", + "quoteVolume": "15.30000000", + "openTime": 1499783499040, + "closeTime": 1499869899040, + "firstId": 28385, // 首笔成交id + "lastId": 28460, // 末笔成交id + "count": 76 // 成交笔数 +} + + """ + path = "/api/v3/ticker/24hr" + query_dict = {"symbol": symbol} + return self.request(RequestMethod.GET, path, query_dict) + + def get_ticker(self, symbol): + """ + 当前最优挂单 + 返回当前最优的挂单(最高买单,最低卖单) + bid price的本意是出价 最高买价 + ask price的本意是要价 最低卖价 + ask是“要”的意思,就卖家喊出来的价 + bid是“投标”的意思,是买家愿意出的最高价。 + :param symbol: 交易对 + :return: 返回的数据如下: + { + 'symbol': 'BTCUSDT', 'bidPrice': '9168.50000000', 'bidQty': '1.27689900', + 'askPrice': '9168.51000000', 'askQty': '0.93307800' + } + """ + path = "/api/v3/ticker/bookTicker" + query_dict = {"symbol": symbol} + return self.request(RequestMethod.GET, path, query_dict) + + def get_client_order_id(self): + """ + generate the client_order_id for user. + :return: + """ + with self.order_count_lock: + self.order_count += 1 + return config.symbol + str(utility.get_current_timestamp()) + str(self.order_count) + + def _sign(self, params): + """ + 签名的方法, signature for the private request. + :param params: request parameters + :return: + """ + + query_string = self.build_parameters(params) + hex_digest = hmac.new(self.secret.encode('utf8'), query_string.encode("utf-8"), hashlib.sha256).hexdigest() + return query_string + '&signature=' + str(hex_digest) + + def place_order(self, symbol: str, order_side: OrderSide, order_type: OrderType, quantity: float, price: float, + client_order_id: str = None, time_inforce="GTC", stop_price=0): + """ + + :param symbol: 交易对名称 + :param order_side: 买或者卖, BUY or SELL + :param order_type: 订单类型 LIMIT or other order type. + :param quantity: 数量 + :param price: 价格. + :param client_order_id: 用户的订单ID + :param time_inforce: + :param stop_price: + :return: + """ + + path = '/api/v3/order' + + if client_order_id is None: + client_order_id = self.get_client_order_id() + + + + params = { + "symbol": symbol, + "side": order_side.value, + "type": order_type.value, + "quantity": quantity, + "price": price, + "recvWindow": self.recv_window, + "timestamp": utility.get_current_timestamp(), + "newClientOrderId": client_order_id + } + + + if order_type == OrderType.LIMIT: + params['timeInForce'] = time_inforce + + if order_type == OrderType.MARKET: + if params.get('price'): + del params['price'] + + if order_type == OrderType.STOP: + if stop_price > 0: + params["stopPrice"] = stop_price + else: + raise ValueError("stopPrice must greater than 0") + + return self.request(RequestMethod.POST, path=path, requery_dict=params, verify=True) + + def get_order(self, symbol: str, client_order_id: str): + """ + 获取订单状态. + :param symbol: + :param client_order_id: + :return: + """ + path = "/api/v3/order" + prams = {"symbol": symbol, "timestamp": utility.get_current_timestamp(), "origClientOrderId": client_order_id} + + return self.request(RequestMethod.GET, path, prams, verify=True) + + def cancel_order(self, symbol, client_order_id): + """ + 撤销订单. + :param symbol: + :param client_order_id: + :return: + """ + path = "/api/v3/order" + params = {"symbol": symbol, "timestamp": utility.get_current_timestamp(), + "origClientOrderId": client_order_id + } + + for i in range(0, 3): + try: + order = self.request(RequestMethod.DELETE, path, params, verify=True) + return order + except Exception as error: + print(f'取消订单失败:{error}') + return + + def get_open_orders(self, symbol=None): + """ + 获取所有的订单. + :param symbol: BNBUSDT, or BTCUSDT etc. + :return: + """ + path = "/api/v3/openOrders" + + params = {"timestamp": utility.get_current_timestamp()} + if symbol: + params["symbol"] = symbol + + return self.request(RequestMethod.GET, path, params, verify=True) + + def cancel_open_orders(self, symbol): + """ + 撤销某个交易对的所有挂单 + :param symbol: symbol + :return: return a list of orders. + """ + path = "/api/v3/openOrders" + + params = {"timestamp": utility.get_current_timestamp(), + "recvWindow": self.recv_window, + "symbol": symbol + } + + return self.request(RequestMethod.DELETE, path, params, verify=True) + + def get_account_info(self): + """ + {'feeTier': 2, 'canTrade': True, 'canDeposit': True, 'canWithdraw': True, 'updateTime': 0, 'totalInitialMargin': '0.00000000', + 'totalMaintMargin': '0.00000000', 'totalWalletBalance': '530.21334791', 'totalUnrealizedProfit': '0.00000000', + 'totalMarginBalance': '530.21334791', 'totalPositionInitialMargin': '0.00000000', 'totalOpenOrderInitialMargin': '0.00000000', + 'maxWithdrawAmount': '530.2133479100000', 'assets': + [{'asset': 'USDT', 'walletBalance': '530.21334791', 'unrealizedProfit': '0.00000000', 'marginBalance': '530.21334791', + 'maintMargin': '0.00000000', 'initialMargin': '0.00000000', 'positionInitialMargin': '0.00000000', 'openOrderInitialMargin': '0.00000000', + 'maxWithdrawAmount': '530.2133479100000'}]} + :return: + """ + path = "/api/v3/account" + params = {"timestamp": utility.get_current_timestamp(), + "recvWindow": self.recv_window + } + return self.request(RequestMethod.GET, path, params, verify=True) diff --git a/binance/binance_trader.py b/binance/binance_trader.py new file mode 100644 index 0000000..009ed8f --- /dev/null +++ b/binance/binance_trader.py @@ -0,0 +1,434 @@ +from binance import BinanceSpotHttp, OrderStatus, OrderType, OrderSide +from utils import config +from utils import round_to +from utils import utility + + +class BinanceTrader(object): + + def __init__(self): + """ + :param api_key: + :param secret: + :param trade_type: 交易的类型, only support future and spot. + """ + self.loop_counts = 0 + self.config_error_counts = 0 #止损价配置不当,提示次数 + self.sleep_time = config.sleep_time #循环间隔秒数 + self.cancel_time = config.cancel_time #取消订单周期 + self.http_client = BinanceSpotHttp(api_key=config.api_key, secret=config.api_secret, proxy_host=config.proxy_host, proxy_port=config.proxy_port) + + self.buy_orders = [] # 本地买单列表. + self.sell_orders = [] # 本地卖单列表. + self.deficit_scroll_map = {} # 滚动止盈的清仓价 + + + def get_avg_price_5min(self): + #获取5分钟内的平均价 + ticker = self.http_client.get_avg_price_5min(config.symbol) + price = 0 + if ticker: + price = float(ticker.get('price', 0)) + return price + + def get_latest_price(self): + #获取最新价格 + ticker = self.http_client.get_latest_price(config.symbol) + price = 0 + if ticker: + price = float(ticker.get('price', 0)) + return price + + def get_price_change_percent_24h(self): + #获取24h内的价格变化百分比数据 + ticker = self.http_client.get_price_info_24h(config.symbol) + priceChangePercent = 0 + if ticker: + priceChangePercent = float(ticker.get('priceChangePercent', 0)) + return priceChangePercent + + def get_bid_ask_price(self): + #最高买价 最低卖价 + ticker = self.http_client.get_ticker(config.symbol) + + bid_price = 0 + ask_price = 0 + if ticker: + bid_price = float(ticker.get('bidPrice', 0)) + ask_price = float(ticker.get('askPrice', 0)) + + return bid_price, ask_price + + def get_depth(self,limit): + ticker = self.http_client.get_order_book(config.symbol,limit=limit) + if ticker: + #high_price = float(ticker[0][2]) + print(ticker) + return 123 + + + def get_kline(self,interval): + ticker = self.http_client.get_kline(config.symbol,interval=interval,limit=1) + if ticker: + high_price = float(ticker[0][2]) + low_price = float(ticker[0][3]) + return low_price, high_price + + # 获取倒数第二个k线数据 + def get_kline_pre4(self,interval): + ticker = self.http_client.get_kline(config.symbol,interval=interval,limit=4) + if ticker: + high_price = float(ticker[3][2]) + low_price = float(ticker[3][3]) + return low_price, high_price + + def create_buy_order(self,quantity,buy_price): + new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, + order_type=OrderType.LIMIT, quantity=quantity, + price=buy_price) + if new_buy_order: + # 买单维护到买单列表待处理 + self.buy_orders.append(new_buy_order) + print(f"买单创建 价格: {buy_price}, 数量: {quantity} ") + else: + print(f"买单创建失败 价格: {buy_price}, 数量: {quantity} ") + + def create_sell_order(self,quantity,sell_price): + new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, + order_type=OrderType.LIMIT, quantity=quantity, price=sell_price) + if new_sell_order: + # 放到卖单列表待处理 + self.sell_orders.append(new_sell_order) + print(f"卖单创建 价格: {sell_price}, 数量: {quantity} ") + else: + print(f"卖单创建失败 价格: {sell_price}, 数量: {quantity} ") + + def create_sell_order_by_client_id(self,quantity,sell_price,client_order_id): + new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, + order_type=OrderType.LIMIT, quantity=quantity, price=sell_price,client_order_id=client_order_id) + if new_sell_order is not None: + # 放到卖单列表待处理 + self.sell_orders.append(new_sell_order) + print(f"卖单创建 价格: {sell_price}, 数量: {quantity} ") + return True + else: + print(f"卖单创建失败 价格: {sell_price}, 数量: {quantity} ") + return False + + def create_sell_order_market(self,quantity): + new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, + order_type=OrderType.MARKET, quantity=quantity) + if new_sell_order: + # 市价订单必定成功 + self.sell_orders.append(new_sell_order) + print(f"交易对:{config.symbol} 市价卖出数量: {quantity} ") + return True + else: + print(f"交易对:{config.symbol} 卖单创建失败 市价卖出数量: {quantity} ") + return False + + def cancel_single_order(self,cancel_order): + order = self.http_client.cancel_order(config.symbol,cancel_order.get('clientOrderId')) + if order is None: + utility.alert(f"订单撤销失败 symbol: {config.symbol}, clientOrderId: {cancel_order.get('clientOrderId')} ") + return False + else: + print(f"订单撤销成功 symbol: {config.symbol}, clientOrderId: {cancel_order.get('clientOrderId')} ") + return True + + + def cancel_open_orders(self): + open_orders_list = self.http_client.get_open_orders(config.symbol) + for open_order in open_orders_list: + if open_order.get('status') == OrderStatus.NEW.value and open_order.get('side') == OrderSide.BUY.value: + #只取消本客户端创建的买单 + if open_order.get('clientOrderId') and config.symbol in open_order.get('clientOrderId'): + self.cancel_single_order(open_order) + print(f"买单{open_order.get('clientOrderId')}已取消取消") + + def sync_open_orders(self): + open_orders_list = self.http_client.get_open_orders(config.symbol) + for open_order in open_orders_list: + #只同步本客户端创建的订单 + if open_order.get('clientOrderId') and config.symbol in open_order.get('clientOrderId'): + if open_order.get('status') == OrderStatus.NEW.value and open_order.get('side') == OrderSide.BUY.value: + self.buy_orders.append(open_order) + print(f"买单{open_order.get('clientOrderId')}同步到本地") + if open_order.get('status') == OrderStatus.NEW.value and open_order.get('side') == OrderSide.SELL.value: + self.sell_orders.append(open_order) + print(f"卖单{open_order.get('clientOrderId')}同步到本地") + + #网格交易核心逻辑. + def grid_trader(self): + """ + #网格交易核心逻辑. + """ + + quantity = round_to(float(config.quantity), float(config.min_qty)) ## quantity是config.quantity到config.min_qty的精度转换 + profit_scroll = float(config.profit_scroll) ## 每次刷新止盈价的比例 + deficit_scroll = float(config.deficit_scroll) ## 每次刷新止损价的比例 + profit_intend = float(config.profit_intend) ## 每次准备刷新止盈价的比例,到了这个比例直接刷 + gap_percent= float(config.gap_percent) ## 卖单提价比例 + + self.loop_counts+=1 + print(f"{utility.cn_time()} 第{self.loop_counts}轮循环开始") + + bid_price, ask_price = self.get_bid_ask_price() + print(f"{config.symbol}:最高买价bid_price: {bid_price}, 最低卖价ask_price: {ask_price}") + avg_price_5min = float(self.get_avg_price_5min()) + #latest_price = self.get_latest_price() + + #5分钟k线 15分钟k线 + low_price_in_15m, high_price_in_15m = self.get_kline("15m") + print(f"{config.symbol}:15分钟最低价: {low_price_in_15m}, 15分钟最高价: {high_price_in_15m}") + + low_price_in_4h, high_price_in_4h = self.get_kline("4h") + low_price_pre_in_4h, high_price_pre_in_4h = self.get_kline_pre4("2h") + print(f"low_price_in_4h: {low_price_in_4h}") + print(f"high_price_in_4h: {high_price_in_4h}") + print(f"low_price_pre_in_4h: {low_price_pre_in_4h}") + print(f"high_price_pre_in_4h: {high_price_pre_in_4h}") + # 取波峰波谷的中间值 + peak_price = high_price_in_4h if high_price_in_4h <= high_price_pre_in_4h else high_price_pre_in_4h + bottom_price = low_price_in_4h if low_price_in_4h >= low_price_pre_in_4h else low_price_pre_in_4h + delta_price_in_4h = float(peak_price)-float(bottom_price) + price_stop_to_sell = float(bottom_price) + delta_price_in_4h * 0.1 + price_stop_to_buy = float(peak_price) - delta_price_in_4h * 0.8 + print(f"high_price_in_4h_4h: {peak_price}") + print(f"low_price_in_4h_4h: {bottom_price}") + print(f"价格上限: {price_stop_to_buy}") + print(f"价格下限: {price_stop_to_sell}") + # price_stop_to_sell = float(config.price_stop_to_sell) + # price_stop_to_buy = float(config.price_stop_to_buy) + + + + self.buy_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. + self.sell_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. + + + buy_delete_orders = [] # 需要删除的买单 + sell_delete_orders = [] # 需要删除的卖单 + + + + # 买单逻辑,检查买单列表成交的情况.一旦成功立即加价卖 + print("-------------买单逻辑-----------------") + for buy_order in self.buy_orders: + check_order = self.http_client.get_order(buy_order.get('symbol', config.symbol),client_order_id=buy_order.get('clientOrderId')) + if check_order: + order_status = check_order.get('status') + order_price = check_order.get('price') + order_orig_qty = check_order.get('origQty') + order_client_id = buy_order.get('clientOrderId') + + #取消状态的买单 不管 + if order_status == OrderStatus.CANCELED.value: + buy_delete_orders.append(buy_order) + print(f"买单{order_client_id}状态为取消: {order_status}") + + #成交状态买单,需要处理 + elif order_status == OrderStatus.FILLED.value: + print(f"成交状态的买单: {order_client_id} 成交时间: {utility.cn_time()} 价格: {order_price} 数量: {order_orig_qty}") + # 买单成交后,提高价格,挂盈利卖单 + sell_price = round_to(float(order_price) * (1 + float(config.gap_percent)), float(config.min_price)) + if 0 < sell_price < ask_price: + # 防止价格小于最低卖价ask_price + sell_price = round_to(ask_price, float(config.min_price)) + #如果卖价小于设定的持仓价格price_stop_to_sell,将卖价提高至持仓价,才交易 + if 0 < sell_price < price_stop_to_sell: + print(f"卖价{sell_price} 小于持仓价{price_stop_to_sell} ,已调整为持仓价交易") + sell_price = price_stop_to_sell + #盈利卖单挂 + if self.create_sell_order(quantity, sell_price): + print(f"{utility.cn_time()} :交易对:{config.symbol}, 买单 {order_client_id} 成交, 价格: {order_price}, 数量: {order_orig_qty}") + # 盈利卖单挂成功的话,删掉本买单 + buy_delete_orders.append(buy_order) + if buy_order in self.buy_orders: + self.buy_orders.remove(buy_order) + + + + #未成交的买单不处理 + elif order_status == OrderStatus.NEW.value: + print(f"未成交的买单{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + + # 部分成交的买单 提示下就行了 + elif order_status == OrderStatus.PARTIALLY_FILLED.value: + print(f"部分成交的买单{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + + #异常状态买单 + else: + utility.alert(f"交易对:{config.symbol}, 时间: {utility.cn_time()}, 买单{order_client_id}失败{order_status}") + + # 过期或者拒绝的订单删除掉. + for delete_order in buy_delete_orders: + if delete_order in self.buy_orders: + self.buy_orders.remove(delete_order) + + + # 卖单逻辑, 检查卖单成交情况 + print("--------------卖单逻辑----------------") + for sell_order in self.sell_orders: + check_order = self.http_client.get_order(sell_order.get('symbol', config.symbol),client_order_id=sell_order.get('clientOrderId')) + if check_order: + order_status = check_order.get('status') + order_price = check_order.get('price') + order_orig_qty = check_order.get('origQty') + order_client_id = sell_order.get('clientOrderId') + #卖单状态为取消 不管 + if order_status == OrderStatus.CANCELED.value: + sell_delete_orders.append(sell_order) + print(f"卖单{order_client_id}状态为取消: {order_status}") + + #卖单状态为成交 + elif order_status == OrderStatus.FILLED.value: + print(f"卖单{order_client_id}成交时间: {utility.cn_time()}, 价格: {order_price}, 数量: {order_orig_qty}") + #第一遍才发通知 + if sell_order not in sell_delete_orders: + utility.alert(f"卖单成交: {utility.cn_time()} {order_client_id}, 交易对:{config.symbol}, 价格: {order_price}, 数量: {order_orig_qty}") + # 卖单成交,不再维护该卖单状态 + sell_delete_orders.append(sell_order) + if sell_order in self.sell_orders: + self.sell_orders.remove(sell_order) + + + # 新挂的卖单 创建移动止盈策略 + elif order_status == OrderStatus.NEW.value: + print(f"还没成交的卖单:{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + print(f"当前最低卖价: {ask_price}") + orig_buy_price = float(order_price) / (float(gap_percent) + 1) + print(f"预估本单的买入价格: {orig_buy_price}") + triger_price = float(order_price) * float(profit_intend) + print(f"移动止盈触发价格: {triger_price}") + if ask_price >= triger_price: + print("最低卖价大于移动止盈触发价格,先删除本卖单,再创建一个更高价的卖单") + if self.cancel_single_order(check_order): + # 删除成功 再创建一个更高价的卖单 + new_order_price_profit = float(order_price) * float(profit_scroll) + new_order_price_deficit = float(ask_price) * float(deficit_scroll) + new_order_price_profit = round_to(new_order_price_profit, float(config.min_price)) + new_order_price_deficit = round_to(new_order_price_deficit, float(config.min_price)) + print(f"new_order_price_profit: {new_order_price_profit}") + print(f"new_order_price_deficit: {new_order_price_deficit}") + profit_client_order_id = self.http_client.get_client_order_id() + profit_sell_order = self.create_sell_order_by_client_id(order_orig_qty, + new_order_price_profit, + profit_client_order_id) + if profit_sell_order: + print("----------------移动止盈触发成功-------yyyyyyyyyyyyyyyyyyyyyyyyyyyy------") + print( + f"{utility.cn_time()} :卖单 {order_client_id}滚动止盈触发新卖单{profit_client_order_id},交易对:{config.symbol},新期望价格: {new_order_price_profit}, 数量: {order_orig_qty}") + # 移动止盈卖单挂成功的话,本地不再维护原卖单 + sell_delete_orders.append(sell_order) + if sell_order in self.sell_orders: + self.sell_orders.remove(sell_order) + # 本地记录新卖单的止损价格 + self.deficit_scroll_map[profit_client_order_id] = new_order_price_deficit + else: + print("----------------移动止盈触发失败------eeeeeeeeeeeeeeeeeeeeeeeeeeee-------") + print( + f"{utility.cn_time()} 移动止盈卖单创建失败 交易对:{config.symbol} 价格: {new_order_price_profit}, 数量: {order_orig_qty} ") + utility.alert( + f"{utility.cn_time()} 移动止盈卖单创建失败 交易对:{config.symbol} 价格: {new_order_price_profit}, 数量: {order_orig_qty} ") + else: + print("原买单删除失败") + + # 当前卖单存在止损价,且最低卖价不大于止损价时候,止损处理掉当前卖单 + # 其实所存的止损价是经过滚动止盈产生的,所以还是在盈利 + # 当前卖价要大于买单价格才可以 + elif order_client_id in self.deficit_scroll_map and ask_price <= self.deficit_scroll_map[order_client_id] and ask_price > orig_buy_price: + # 先删除本卖单,挂太高了,卖不出去 + if self.cancel_open_orders(check_order): + # 市价卖出本订单待卖的币 + self.create_sell_order_market(order_orig_qty) + print("----------移动止损成功--------------") + else: + print(f"止损卖单取消失败:{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + + + + # 部分成交的卖单 提示下就行了 + elif order_status == OrderStatus.PARTIALLY_FILLED.value: + print(f"部分成交的卖单{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + + else: + utility.alert(f"挂卖单失败 交易对:{config.symbol}, 时间: {utility.cn_time()}, {order_status}") + + #处理过期或者拒绝的订单,不在维护到本地 + for delete_order in sell_delete_orders: + if delete_order in self.sell_orders: + self.sell_orders.remove(delete_order) + + # 买单数量未满 并且卖单也没满时候 usdt没有积压 可以新建买单 + if len(self.buy_orders) < int(config.max_orders) and len(self.sell_orders) < int(config.max_sell_orders): + print("买单数量未满,卖单也没积压,开始创建一个新买单:") + print("------------------------------") + if bid_price > 0: + buy_price = round_to(bid_price * (1 - float(config.gap_percent)), float(config.min_price)) + ##如果买价大于设定的抛仓价格price_stop_to_buy,将买价降低至抛仓价,才交易 + if buy_price > price_stop_to_buy > 0: + buy_price = price_stop_to_buy + #动态价格保护,如果买价不高于15分钟k线最高值,说明价格在跌,适合买 + if buy_price <= high_price_in_15m : + # 如果价格不大于5分钟内的平均价 说明价格还可以,能继续买入 + if buy_price <= avg_price_5min: + buy_price = round_to(buy_price,float(config.min_price)) + print(f"开始创建买单:价格{buy_price} 数量 {quantity} ") + self.create_buy_order(quantity,buy_price) + else: + print(f"买价 {buy_price}大于平均价 {avg_price_5min} 再等等") + else: + print(f"买价 {buy_price}大于15分钟最高价 {high_price_in_15m} 高位勿买") + + # 买单数量过多,cancel_time 取消一次价格最离谱的买单 + elif len(self.buy_orders) >= int(config.max_orders): + # cancel_time更新一次 主循环cancel_time/gcd_time次 + cancel_period=self.cancel_time/self.sleep_time + print(f"当前买单数量达到{config.max_orders},将在{(cancel_period - self.loop_counts % cancel_period)*self.sleep_time}秒后删除一个价格最离谱的买单") + if self.loop_counts % cancel_period == 0: + print(f"开始删除价格最离谱的买单:") + print("------------------------------") + # 最离谱到最接近 + self.buy_orders.sort(key=lambda x: abs(float(x['price'])-float(avg_price_5min)), reverse=True) + impossible_order=self.buy_orders[0] + if self.cancel_single_order(impossible_order): + if impossible_order in self.buy_orders: + self.buy_orders.remove(impossible_order) + buy_delete_orders.append(impossible_order) + print(f"买单列表已满 价格离谱的买单{impossible_order.get('clientOrderId')}删除 价格: {impossible_order.get('price')}, 数量: {impossible_order.get('origQty')} ") + else: + print(f"买单列表已满 价格离谱的买单{impossible_order.get('clientOrderId')}删除失败 价格: {impossible_order.get('price')}, 数量: {impossible_order.get('origQty')} ") + for fade_order in self.buy_orders: + if float(fade_order.get('price')) > high_price_in_15m or float(fade_order.get('price')) < low_price_in_15m: + if fade_order in self.buy_orders: + self.buy_orders.remove(fade_order) + buy_delete_orders.append(fade_order) + print(f"取消价格未在15m k线的买单{fade_order.get('clientOrderId')} 价格: {impossible_order.get('price')}, 数量: {impossible_order.get('origQty')} ") + + + + # 没有卖单的时候 暂时先不创建卖单,防止利润计算错误 + if len(self.sell_orders) <= 0: + print(f"当前没有卖单,请注意查看{config.symbol}余额") + print("------------------------------") + + + + #卖单过多,暂不处理 + elif len(self.sell_orders) > int(config.max_orders): + print("卖单过多:") + print("------------------------------") + print(f"交易对{config.symbol}卖单较多,大于个{config.max_orders} 请处理") + + #集中处理 buy_delete_orders sell_delete_orders + for buy_order in buy_delete_orders: + if self.cancel_single_order(buy_order): + if buy_order in buy_delete_orders: + buy_delete_orders.remove(buy_order) + for sell_order in sell_delete_orders: + if self.cancel_single_order(sell_order): + if sell_order in sell_delete_orders: + sell_delete_orders.remove(sell_order) + + print(f"{utility.cn_time()} 第{self.loop_counts}轮循环结束 休息{self.sleep_time}s") \ No newline at end of file diff --git a/binance/trade.txt b/binance/trade.txt new file mode 100644 index 0000000..de30dda --- /dev/null +++ b/binance/trade.txt @@ -0,0 +1,12 @@ + + #循环提醒配置不当 + ##循环配置不当的逻辑先去掉,直接从4h kline自适应 + # if avg_price_5min > price_stop_to_buy or avg_price_5min < price_stop_to_sell: + # print(f"交易对{config.symbol}:止损范围配置不当,恐难以成交,五分钟内平均价: {avg_price_5min}") + # self.config_error_counts+=1 + # #15次循环提醒一次配置不当 + # if self.config_error_counts%15 == 0: + # self.config_error_counts = 0 + # utility.alert(f"交易对{config.symbol}:止损范围配置不当,恐难以成交,五分钟内平均价: {avg_price_5min}") + # else: + # self.config_error_counts = 0 diff --git a/binance/trader_bak b/binance/trader_bak new file mode 100644 index 0000000..22e5613 --- /dev/null +++ b/binance/trader_bak @@ -0,0 +1,404 @@ +from binance import BinanceSpotHttp, OrderStatus, OrderType, OrderSide +from utils import config +from utils import round_to +from utils import utility + + +class BinanceTrader(object): + + def __init__(self): + """ + :param api_key: + :param secret: + :param trade_type: 交易的类型, only support future and spot. + """ + self.loop_counts = 0 + self.config_error_counts = 0 #止损价配置不当,提示次数 + self.http_client = BinanceSpotHttp(api_key=config.api_key, secret=config.api_secret, proxy_host=config.proxy_host, proxy_port=config.proxy_port) + + self.buy_orders = [] # 本地买单列表. + self.sell_orders = [] # 本地卖单列表. + + + + + def get_avg_price_5min(self): + #获取5分钟内的平均价 + ticker = self.http_client.get_avg_price_5min(config.symbol) + price = 0 + if ticker: + price = float(ticker.get('price', 0)) + return price + + def get_latest_price(self): + #获取最新价格 + ticker = self.http_client.get_latest_price(config.symbol) + price = 0 + if ticker: + price = float(ticker.get('price', 0)) + return price + + def get_price_change_percent_24h(self): + #获取24h内的价格变化百分比数据 + ticker = self.http_client.get_price_info_24h(config.symbol) + priceChangePercent = 0 + if ticker: + priceChangePercent = float(ticker.get('priceChangePercent', 0)) + return priceChangePercent + + def get_bid_ask_price(self): + #最高买价 最低卖价 + ticker = self.http_client.get_ticker(config.symbol) + + bid_price = 0 + ask_price = 0 + if ticker: + bid_price = float(ticker.get('bidPrice', 0)) + ask_price = float(ticker.get('askPrice', 0)) + + return bid_price, ask_price + + def get_depth(self,limit): + ticker = self.http_client.get_order_book(config.symbol,limit=limit) + if ticker: + #high_price = float(ticker[0][2]) + print(ticker) + return 123 + + def get_kline_15m(self): + ticker = self.http_client.get_kline(config.symbol,interval="15m",limit=1) + if ticker: + high_price = float(ticker[0][2]) + low_price = float(ticker[0][3]) + return low_price, high_price + + def get_kline_5m(self): + ticker = self.http_client.get_kline(config.symbol,interval="5m",limit=1) + if ticker: + high_price = float(ticker[0][2]) + low_price = float(ticker[0][3]) + return low_price, high_price + + def create_buy_order(self,quantity,buy_price): + new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, + order_type=OrderType.LIMIT, quantity=quantity, + price=buy_price) + if new_buy_order: + # 买单维护到买单列表待处理 + self.buy_orders.append(new_buy_order) + print(f"买单创建 价格: {buy_price}, 数量: {quantity} ") + else: + print(f"买单创建失败 价格: {buy_price}, 数量: {quantity} ") + + def create_buy_order_by_client_id(self,quantity,buy_price,client_order_id): + new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, + order_type=OrderType.LIMIT, quantity=quantity, + price=buy_price,client_order_id=client_order_id) + if new_buy_order: + # 买单维护到买单列表待处理 + self.buy_orders.append(new_buy_order) + print(f"买单创建 价格: {buy_price}, 数量: {quantity} ") + else: + print(f"买单创建失败 价格: {buy_price}, 数量: {quantity} ") + + def create_sell_order(self,quantity,sell_price): + new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, + order_type=OrderType.LIMIT, quantity=quantity, price=sell_price) + if new_sell_order: + # 放到卖单列表待处理 + self.sell_orders.append(new_sell_order) + print(f"卖单创建 价格: {sell_price}, 数量: {quantity} ") + else: + print(f"卖单创建失败 价格: {sell_price}, 数量: {quantity} ") + + def create_sell_order_by_client_id(self,quantity,sell_price,client_order_id): + new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, + order_type=OrderType.LIMIT, quantity=quantity, price=sell_price,client_order_id=client_order_id) + if new_sell_order: + # 放到卖单列表待处理 + self.sell_orders.append(new_sell_order) + print(f"卖单创建 价格: {sell_price}, 数量: {quantity} ") + else: + print(f"卖单创建失败 价格: {sell_price}, 数量: {quantity} ") + + def create_sell_order_market(self,quantity): + new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, + order_type=OrderType.MARKET, quantity=quantity) + if new_sell_order: + # 市价订单必定成功 + self.sell_orders.append(new_sell_order) + print(f"交易对:{config.symbol} 市价卖出数量: {quantity} ") + else: + print(f"交易对:{config.symbol} 卖单创建失败 市价卖出数量: {quantity} ") + + def cancel_single_order(self,cancel_order): + order = self.http_client.cancel_order(config.symbol,cancel_order.get('clientOrderId')) + if order: + print(f"订单撤销 symbol: {config.symbol}, clientOrderId: {cancel_order.get('clientOrderId')} ") + else: + print(f"订单撤销失败 symbol: {config.symbol}, clientOrderId: {cancel_order.get('clientOrderId')} ") + + def cancel_open_orders(self): + open_orders_list = self.http_client.get_open_orders(config.symbol) + for open_order in open_orders_list: + if open_order.get('status') == OrderStatus.NEW.value and open_order.get('side') == OrderSide.BUY.value: + #只取消本客户端创建的买单 + if open_order.get('clientOrderId') and config.symbol in open_order.get('clientOrderId'): + self.cancel_single_order(open_order) + print(f"买单{open_order.get('clientOrderId')}已取消取消") + + def sync_open_orders(self): + open_orders_list = self.http_client.get_open_orders(config.symbol) + for open_order in open_orders_list: + #只同步本客户端创建的订单 + if open_order.get('clientOrderId') and config.symbol in open_order.get('clientOrderId'): + if open_order.get('status') == OrderStatus.NEW.value and open_order.get('side') == OrderSide.BUY.value: + self.buy_orders.append(open_order) + print(f"买单{open_order.get('clientOrderId')}同步到本地") + if open_order.get('status') == OrderStatus.NEW.value and open_order.get('side') == OrderSide.SELL.value: + self.sell_orders.append(open_order) + print(f"卖单{open_order.get('clientOrderId')}同步到本地") + + #网格交易核心逻辑. + def grid_trader(self): + """ + #网格交易核心逻辑. + """ + price_stop_to_sell = float(config.price_stop_to_sell) + price_stop_to_buy = float(config.price_stop_to_buy) + quantity = round_to(float(config.quantity), float(config.min_qty)) ## quantity是config.quantity到config.min_qty的四舍五入值 + profit_scroll = float(config.profit_scroll) ## 每次刷新止盈价的比例 + deficit_scroll = float(config.deficit_scroll) ## 每次刷新止损价的比例 + profit_intend = float(config.profit_intend) ## 每次准备刷新止盈价的比例,到了这个比例直接刷 + + self.loop_counts+=1 + print(f"{utility.cn_time()} 第{self.loop_counts}轮循环开始") + + bid_price, ask_price = self.get_bid_ask_price() + print(f"{config.symbol}:最高买价bid_price: {bid_price}, 最低卖价ask_price: {ask_price}") + avg_price_5min = float(self.get_avg_price_5min()) + #latest_price = self.get_latest_price() + + #5分钟k线 15分钟k线 + low_price_in_15m, high_price_in_15m = self.get_kline_15m() + print(f"{config.symbol}:15分钟最低价: {low_price_in_15m}, 15分钟最高价: {high_price_in_15m}") + + # low_price_in_5m, high_price_in_5m = self.get_kline_5m() + # print(f"{config.symbol}:5分钟最低价: {low_price_in_5m}, 5分钟最高价: {high_price_in_5m}") + + #循环提醒配置不当 + if avg_price_5min > price_stop_to_buy or avg_price_5min < price_stop_to_sell: + print(f"交易对{config.symbol}:止损范围配置不当,恐难以成交,五分钟内平均价: {avg_price_5min}") + self.config_error_counts+=1 + #15次循环提醒一次配置不当 + if self.config_error_counts%15 == 0: + self.config_error_counts = 0 + utility.alert(f"交易对{config.symbol}:止损范围配置不当,恐难以成交,五分钟内平均价: {avg_price_5min}") + else: + self.config_error_counts = 0 + + + self.buy_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. + self.sell_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. + + + buy_delete_orders = [] # 需要删除的买单 + sell_delete_orders = [] # 需要删除的卖单 + deficit_scroll_map = {} # 滚动止盈的清仓价 + buy_order_price_map = {} # 买单的价格 + buy_order_sell_map = {} # 买卖单对应关系 + + + # 买单逻辑,检查买单列表成交的情况.一旦成功立即加价卖 + # print("当前委托买单:") + # print(self.buy_orders) + print("-------------买单逻辑-----------------") + for buy_order in self.buy_orders: + check_order = self.http_client.get_order(buy_order.get('symbol', config.symbol),client_order_id=buy_order.get('clientOrderId')) + if check_order: + order_status = check_order.get('status') + order_price = check_order.get('price') + order_orig_qty = check_order.get('origQty') + order_client_id = buy_order.get('clientOrderId') + + #取消状态的买单 不管 + if order_status == OrderStatus.CANCELED.value: + buy_delete_orders.append(buy_order) + print(f"买单{order_client_id}状态为取消: {order_status}") + + #成交状态买单,需要处理 + elif order_status == OrderStatus.FILLED.value: + print(f"买单{order_client_id}成交时间: {utility.cn_time()}, 价格: {order_price}, 数量: {order_orig_qty}") + # 买单成交后,提高价格,挂盈利卖单 + sell_price = round_to(float(order_price) * (1 + float(config.gap_percent)), float(config.min_price)) + if 0 < sell_price < ask_price: + # 防止价格小于最低卖价ask_price + sell_price = round_to(ask_price, float(config.min_price)) + #如果卖价小于设定的持仓价格price_stop_to_sell,将卖价提高至持仓价,才交易 + if 0 < sell_price < price_stop_to_sell: + print(f"卖价{sell_price} 小于持仓价{price_stop_to_sell} ,已调整为持仓价交易") + sell_price = price_stop_to_sell + #盈利卖单挂 + profit_client_order_id = BinanceSpotHttp.get_client_order_id() + if self.create_sell_order_by_client_id(quantity, sell_price,profit_client_order_id): + print(f"{utility.cn_time()} :交易对:{config.symbol}, 买单 {order_client_id} 成交, 价格: {order_price}, 数量: {order_orig_qty}") + # 盈利卖单挂成功的话,删掉本买单 + buy_delete_orders.append(buy_order) + self.buy_orders.remove(buy_order) + # 维护买卖单关系 + buy_order_sell_map[order_client_id] = profit_client_order_id + + + + #未成交的买单不处理 + elif order_status == OrderStatus.NEW.value: + print(f"未成交的买单{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + + # 部分成交的买单 提示下就行了 + elif order_status == OrderStatus.PARTIALLY_FILLED.value: + print(f"部分成交的买单{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + + #异常状态买单 + else: + utility.alert(f"交易对:{config.symbol}, 时间: {utility.cn_time()}, 买单{order_client_id}失败{order_status}") + + # 过期或者拒绝的订单删除掉. + for delete_order in buy_delete_orders: + self.buy_orders.remove(delete_order) + + + # 卖单逻辑, 检查卖单成交情况.不做措施 + # print("当前委托卖单:") + # print(self.sell_orders) + print("--------------卖单逻辑----------------") + for sell_order in self.sell_orders: + check_order = self.http_client.get_order(sell_order.get('symbol', config.symbol),client_order_id=sell_order.get('clientOrderId')) + if check_order: + order_status = check_order.get('status') + order_price = check_order.get('price') + order_orig_qty = check_order.get('origQty') + order_client_id = sell_order.get('clientOrderId') + #卖单状态为取消 不管 + if order_status == OrderStatus.CANCELED.value: + sell_delete_orders.append(sell_order) + print(f"卖单{order_client_id}状态为取消: {order_status}") + + #卖单状态为成交 + elif order_status == OrderStatus.FILLED.value: + print(f"卖单{order_client_id}成交时间: {utility.cn_time()}, 价格: {order_price}, 数量: {order_orig_qty}") + #第一遍才发通知 + if sell_order not in sell_delete_orders: + utility.alert(f"卖单成交: {utility.cn_time()} {order_client_id}, 交易对:{config.symbol}, 价格: {order_price}, 数量: {order_orig_qty}") + # 卖单成交,不再维护该卖单状态 + sell_delete_orders.append(sell_order) + self.sell_orders.remove(sell_order) + + # 新挂的卖单 创建移动止盈策略 + elif order_status == OrderStatus.NEW.value: + print(f"还没成交的卖单:{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + print(f"当前最低卖价: {ask_price}") + #预估 + # 最低卖价大于原卖单定价的止盈比例时候,先删除本卖单,再创建一个更高价的卖单 + if ask_price >= order_price * profit_intend: + # 先删除本卖单 + if self.cancel_open_orders(check_order): + # 再创建一个更高价的卖单 + new_order_price_profit = order_price * profit_scroll + new_order_price_deficit = ask_price * deficit_scroll + profit_client_order_id = BinanceSpotHttp.get_client_order_id() + profit_sell_order = self.self.create_sell_order_by_client_id(order_orig_qty,new_order_price_profit,profit_client_order_id) + if profit_sell_order: + print( + f"{utility.cn_time()} :卖单 {order_client_id}滚动止盈触发新卖单{profit_client_order_id},交易对:{config.symbol},新期望价格: {new_order_price_profit}, 数量: {order_orig_qty}") + # 移动止盈卖单挂成功的话,本地不再维护原卖单 + sell_delete_orders.append(sell_order) + self.sell_orders.remove(sell_order) + # 本地记录新卖单的止损价格 + deficit_scroll_map[profit_client_order_id] = new_order_price_deficit + else: + print(f"{utility.cn_time()} 移动止盈卖单创建失败 交易对:{config.symbol} 价格: {new_order_price_profit}, 数量: {order_orig_qty} ") + utility.alert(f"{utility.cn_time()} 移动止盈卖单创建失败 交易对:{config.symbol} 价格: {new_order_price_profit}, 数量: {order_orig_qty} ") + else: + print(f"止盈卖单取消失败:{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + # 当前卖单存在止损价,且最低卖价不大于止损价时候,止损处理掉当前卖单 + # 其实所存的止损价是经过滚动止盈产生的,所以还是在盈利 + elif deficit_scroll_map.has_key(order_client_id) and ask_price <= deficit_scroll_map[order_client_id]: + # 先删除本卖单,挂太高了,卖不出去 + if self.cancel_open_orders(check_order): + # 市价卖出本订单待卖的币 + self.create_sell_order_market(order_orig_qty) + else: + print(f"止损卖单取消失败:{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + + + + # 部分成交的卖单 提示下就行了 + elif order_status == OrderStatus.PARTIALLY_FILLED.value: + print(f"部分成交的卖单{order_client_id} 价格: {order_price}, 数量: {order_orig_qty}") + + else: + utility.alert(f"挂卖单失败 交易对:{config.symbol}, 时间: {utility.cn_time()}, {order_status}") + + # 过期或者拒绝的订单删除掉. + for delete_order in sell_delete_orders: + self.sell_orders.remove(delete_order) + + # 买单数量未满 并且卖单也没满时候 usdt没有积压 可以新建买单 + if len(self.buy_orders) < int(config.max_orders) and len(self.sell_orders) < int(config.max_sell_orders): + print("买单数量未满,卖单也没积压,开始创建一个新买单:") + print("------------------------------") + if bid_price > 0: + buy_price = round_to(bid_price * (1 - float(config.gap_percent)), float(config.min_price)) + ##如果买价大于设定的抛仓价格price_stop_to_buy,将买价降低至抛仓价,才交易 + if buy_price > price_stop_to_buy > 0: + buy_price = price_stop_to_buy + #动态价格保护,如果买价不高于15分钟k线最高值,说明价格在跌,适合买 + if buy_price <= high_price_in_15m : + # 如果价格不大于5分钟内的平均价 说明价格还可以,能继续买入 + if buy_price <= avg_price_5min: + print(f"开始创建买单:价格{buy_price} 数量 {quantity} ") + buy_client_order_id = BinanceSpotHttp.get_client_order_id() + if self.create_buy_order_by_client_id(quantity,buy_price,buy_client_order_id): + # 买入成功后记录买单的原价 + buy_order_price_map[buy_client_order_id] = buy_price + else: + print(f"买单{buy_client_order_id}创建失败 数量 {quantity} 价格{buy_price}") + else: + print(f"买价 {buy_price}大于平均价 {avg_price_5min} 再等等") + else: + print(f"买价 {buy_price}大于15分钟最高价 {high_price_in_15m} 高位勿买") + + # 买单数量过多,2min取消一次价格最离谱的买单 + elif len(self.buy_orders) >= int(config.max_orders): + # 主循环6次 2min更新一次 + print(f"当前买单数量达到{config.max_orders},将在{(6 - self.loop_counts % 6)*20}秒后删除一个价格最离谱的买单") + if self.loop_counts % 6 == 0: + print(f"开始删除价格最离谱的买单:") + print("------------------------------") + # 最离谱到最接近 + self.buy_orders.sort(key=lambda x: abs(float(x['price'])-float(avg_price_5min)), reverse=True) + impossible_order=self.buy_orders[0] + if self.cancel_single_order(impossible_order): + self.buy_orders.remove(impossible_order) + print(f"买单列表已满 价格离谱的买单{impossible_order.get('clientOrderId')}删除 价格: {impossible_order.get('price')}, 数量: {impossible_order.get('origQty')} ") + for fade_order in self.buy_orders: + if float(fade_order.get('price')) > high_price_in_15m or float(fade_order.get('price')) < low_price_in_15m: + self.buy_orders.remove(fade_order) + print(f"取消价格未在15m k线的买单{fade_order.get('clientOrderId')} 价格: {impossible_order.get('price')}, 数量: {impossible_order.get('origQty')} ") + + + + # 没有卖单的时候 暂时先不创建卖单,防止利润计算错误 + if len(self.sell_orders) <= 0: + print(f"当前没有卖单,请注意查看{config.symbol}余额") + print("------------------------------") + + + + #卖单过多,暂不处理 + elif len(self.sell_orders) > int(config.max_orders): + print("卖单过多:") + print("------------------------------") + print(f"交易对{config.symbol}卖单较多,大于个{config.max_orders} 请处理") + + print(f"{utility.cn_time()} 第{self.loop_counts}轮循环结束 休息20s") \ No newline at end of file diff --git a/conf/config-example.json b/conf/config-example.json new file mode 100644 index 0000000..c9fa5a3 --- /dev/null +++ b/conf/config-example.json @@ -0,0 +1,28 @@ + { + "sleep_time": 10, + "cancel_time": 120, + "platform": "binance_spot", + "symbol": "SHIBUSDT", + "api_key": "44444444444444444444444444", + "api_secret": "4444444444444444444444444444444444444444444444444444", + "profit_scroll": 1.005, + "profit_intend": 0.996, + "deficit_scroll": 0.985, + "gap_percent": 0.01, + "quantity": 500000, + "min_price": 0.00000001, + "price_stop_to_buy": 0.7, + "price_stop_to_sell": 0.5, + "min_qty": 100000, + "max_orders": 2, + "max_sell_orders": 2, + "proxy_host": "privoxy", + "proxy_port": 8118, + "dingding_robot_url": "https://oapi.dingtalk.com/robot/send?access_token=666666666666666666666666666666666d29c", + "dingding_user_phone": "1111111111", + "user_email": "me@me.me", + "SMTP_HOST": "smtp.me.me", + "SMTP_PORT": 80, + "SMTP_USER": "me2@me.me", + "SMTP_PASSWORD": "77777777777777777777777777777777777" + } diff --git a/conf_huobi/config-example.json b/conf_huobi/config-example.json new file mode 100644 index 0000000..402d747 --- /dev/null +++ b/conf_huobi/config-example.json @@ -0,0 +1,11 @@ +{ + "account_id": 44444, + "api_key": "e7f555555555555555555b3", + "api_secret": "f065555555555555555555555555555aec", + "symbol_spot": ["btcusdt","btc3lusdt","eth3susdt"], + "symbol_margin": ["linkusdt","xmrusdt","bsvusdt","ethusdt"], + "symbol_c2c": ["linkusdt","xmrusdt","bsvusdt","ethusdt"], + "proxy_host": "172.16.0.137", + "proxy_port": 58591 +} + diff --git a/docker_build.md b/docker_build.md new file mode 100644 index 0000000..bccc852 --- /dev/null +++ b/docker_build.md @@ -0,0 +1,26 @@ +# prod 机器上 + +## docker build push ,k8s update img + +```bash + +CURRENT_VERSION=41 +CURRENT_IMG="me/grid_trader" +cd /root +rm -rf grid_trader +git clone https://mmmm/grid_trader.git +cd grid_trader +docker build -t $CURRENT_IMG:$CURRENT_VERSION . +docker push $CURRENT_IMG:$CURRENT_VERSION +clear +echo $CURRENT_VERSION + + +DEPLOYMENTS=`kubectl -n sre get deployment |grep binance-trader-qy |awk '{print $1}' |xargs` +for deployment in $DEPLOYMENTS;do + kubectl set image deployments $deployment *=$CURRENT_IMG:$CURRENT_VERSION -n sre +done + +``` + + diff --git a/example/__init__.py b/example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/account/get_account_asset_valuation.py b/example/account/get_account_asset_valuation.py new file mode 100644 index 0000000..56e5484 --- /dev/null +++ b/example/account/get_account_asset_valuation.py @@ -0,0 +1,24 @@ +from huobi.client.account import AccountClient +from example.api_key import * + +account_client = AccountClient(api_key=g_api_key,secret_key=g_secret_key) + +''' +https://huobiapi.github.io/docs/spot/v1/cn/#bd9157656f +spot:现货账户, +margin:逐仓杠杆账户, +otc:OTC 账户, +point:点卡账户, +super-margin:全仓杠杆账户, +investment: C2C杠杆借出账户, +borrow: C2C杠杆借入账户, +矿池账户: minepool, +ETF账户: etf, +抵押借贷账户: crypto-loans + +逐仓/全仓/C2C杠杆账户(margin/super-margin/borrow)会在第一次划转资产时创建,如果未划转过资产则不会有杠杆账户。 +''' + +account_type = "etf" #币币账户余额 +asset_valuation = account_client.get_account_asset_valuation(account_type=account_type, valuation_currency="usd") +asset_valuation.print_object() diff --git a/example/account/get_account_balance.py b/example/account/get_account_balance.py new file mode 100644 index 0000000..92b730b --- /dev/null +++ b/example/account/get_account_balance.py @@ -0,0 +1,3522 @@ +from huobi.client.account import AccountClient +from huobi.utils import LogInfo +from example.api_key import * + + +# 获取账户总余额-明细 + +''' +ssh://root@40.83.77.152:22/usr/local/bin/python3 -u /opt/binance_grid_trader/example/account/get_account_balance.py +====== (SDK encapsulated api) not recommend for low performance and frequence limitation ====== +Account ID : 18788142 +Account Type : margin +Account State : working +Subtype : btcusdt + + Currency : btc + Balance Type : trade + Balance : 0.000000593380222235 + + Currency : btc + Balance Type : frozen + Balance : 0 + + Currency : btc + Balance Type : loan + Balance : 0 + + Currency : btc + Balance Type : interest + Balance : 0 + + Currency : usdt + Balance Type : trade + Balance : 0.000000000000011349 + + Currency : usdt + Balance Type : frozen + Balance : 0.000000000000000001 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + +Account ID : 18766866 +Account Type : margin +Account State : working +Subtype : bchusdt + + Currency : bch + Balance Type : trade + Balance : 0.000066417266187046 + + Currency : bch + Balance Type : frozen + Balance : 0 + + Currency : bch + Balance Type : loan + Balance : 0 + + Currency : bch + Balance Type : interest + Balance : 0 + + Currency : usdt + Balance Type : trade + Balance : 27.726710382000002202 + + Currency : usdt + Balance Type : frozen + Balance : 0.000000000000000001 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + +Account ID : 18688813 +Account Type : margin +Account State : working +Subtype : linkusdt + + Currency : link + Balance Type : trade + Balance : 0.000872717632331562 + + Currency : link + Balance Type : frozen + Balance : 0 + + Currency : link + Balance Type : loan + Balance : -926.06600784 + + Currency : link + Balance Type : interest + Balance : -1.41368408 + + Currency : usdt + Balance Type : trade + Balance : 12255.3867979586747305 + + Currency : usdt + Balance Type : frozen + Balance : 18600.000000000000000004 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + +Account ID : 18716338 +Account Type : margin +Account State : working +Subtype : omgusdt + + Currency : omg + Balance Type : trade + Balance : 1.95822488 + + Currency : omg + Balance Type : frozen + Balance : 0 + + Currency : omg + Balance Type : loan + Balance : 0 + + Currency : omg + Balance Type : interest + Balance : 0 + + Currency : usdt + Balance Type : trade + Balance : 0.00000000748 + + Currency : usdt + Balance Type : frozen + Balance : 0 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + +Account ID : 9736507 +Account Type : spot +Account State : working +Subtype : + + Currency : lun + Balance Type : trade + Balance : 0 + + Currency : lun + Balance Type : frozen + Balance : 0 + + Currency : ksm + Balance Type : trade + Balance : 0 + + Currency : ksm + Balance Type : frozen + Balance : 0 + + Currency : ring + Balance Type : trade + Balance : 0 + + Currency : ring + Balance Type : frozen + Balance : 0 + + Currency : bsv3l + Balance Type : trade + Balance : 0 + + Currency : bsv3l + Balance Type : frozen + Balance : 0 + + Currency : fil + Balance Type : trade + Balance : 0 + + Currency : fil + Balance Type : frozen + Balance : 0 + + Currency : bag + Balance Type : trade + Balance : 0 + + Currency : bag + Balance Type : frozen + Balance : 0 + + Currency : kcash + Balance Type : trade + Balance : 0 + + Currency : kcash + Balance Type : frozen + Balance : 0 + + Currency : bal + Balance Type : trade + Balance : 0 + + Currency : bal + Balance Type : frozen + Balance : 0 + + Currency : theta + Balance Type : trade + Balance : 0 + + Currency : theta + Balance Type : frozen + Balance : 0 + + Currency : npxs + Balance Type : trade + Balance : 0 + + Currency : npxs + Balance Type : frozen + Balance : 0 + + Currency : ssp + Balance Type : trade + Balance : 0 + + Currency : ssp + Balance Type : frozen + Balance : 0 + + Currency : fis + Balance Type : trade + Balance : 0 + + Currency : fis + Balance Type : frozen + Balance : 0 + + Currency : trio + Balance Type : trade + Balance : 0 + + Currency : trio + Balance Type : frozen + Balance : 0 + + Currency : egt + Balance Type : trade + Balance : 0 + + Currency : egt + Balance Type : frozen + Balance : 0 + + Currency : stpt + Balance Type : trade + Balance : 0 + + Currency : stpt + Balance Type : frozen + Balance : 0 + + Currency : bat + Balance Type : trade + Balance : 0 + + Currency : bat + Balance Type : frozen + Balance : 0 + + Currency : mgo + Balance Type : trade + Balance : 0 + + Currency : mgo + Balance Type : frozen + Balance : 0 + + Currency : mxc + Balance Type : trade + Balance : 0 + + Currency : mxc + Balance Type : frozen + Balance : 0 + + Currency : let + Balance Type : trade + Balance : 0 + + Currency : let + Balance Type : frozen + Balance : 0 + + Currency : band + Balance Type : trade + Balance : 0 + + Currency : band + Balance Type : frozen + Balance : 0 + + Currency : near + Balance Type : trade + Balance : 0 + + Currency : near + Balance Type : frozen + Balance : 0 + + Currency : xmr + Balance Type : trade + Balance : 0 + + Currency : xmr + Balance Type : frozen + Balance : 0 + + Currency : dock + Balance Type : trade + Balance : 0 + + Currency : dock + Balance Type : frozen + Balance : 0 + + Currency : bt1 + Balance Type : trade + Balance : 0 + + Currency : bt1 + Balance Type : frozen + Balance : 0 + + Currency : pearl + Balance Type : trade + Balance : 0 + + Currency : pearl + Balance Type : frozen + Balance : 0 + + Currency : waves + Balance Type : trade + Balance : 0 + + Currency : waves + Balance Type : frozen + Balance : 0 + + Currency : bt2 + Balance Type : trade + Balance : 0 + + Currency : bt2 + Balance Type : frozen + Balance : 0 + + Currency : reef + Balance Type : trade + Balance : 0 + + Currency : reef + Balance Type : frozen + Balance : 0 + + Currency : iota + Balance Type : trade + Balance : 0 + + Currency : iota + Balance Type : frozen + Balance : 0 + + Currency : xmx + Balance Type : trade + Balance : 0 + + Currency : xmx + Balance Type : frozen + Balance : 0 + + Currency : iris + Balance Type : trade + Balance : 0 + + Currency : iris + Balance Type : frozen + Balance : 0 + + Currency : cdc + Balance Type : trade + Balance : 0 + + Currency : cdc + Balance Type : frozen + Balance : 0 + + Currency : ae + Balance Type : trade + Balance : 0 + + Currency : ae + Balance Type : frozen + Balance : 0 + + Currency : stk + Balance Type : trade + Balance : 0 + + Currency : stk + Balance Type : frozen + Balance : 0 + + Currency : husd + Balance Type : trade + Balance : 0 + + Currency : husd + Balance Type : frozen + Balance : 0 + + Currency : node + Balance Type : trade + Balance : 0 + + Currency : node + Balance Type : frozen + Balance : 0 + + Currency : ar + Balance Type : trade + Balance : 0 + + Currency : ar + Balance Type : frozen + Balance : 0 + + Currency : musk + Balance Type : trade + Balance : 0 + + Currency : musk + Balance Type : frozen + Balance : 0 + + Currency : iost + Balance Type : trade + Balance : 0 + + Currency : iost + Balance Type : frozen + Balance : 0 + + Currency : dot2l + Balance Type : trade + Balance : 0 + + Currency : dot2l + Balance Type : frozen + Balance : 0 + + Currency : pnt + Balance Type : trade + Balance : 0 + + Currency : pnt + Balance Type : frozen + Balance : 0 + + Currency : eth3l + Balance Type : trade + Balance : 0 + + Currency : eth3l + Balance Type : frozen + Balance : 0 + + Currency : cova + Balance Type : trade + Balance : 0 + + Currency : cova + Balance Type : frozen + Balance : 0 + + Currency : wnxm + Balance Type : trade + Balance : 0 + + Currency : wnxm + Balance Type : frozen + Balance : 0 + + Currency : eth3s + Balance Type : trade + Balance : 0.0000342 + + Currency : eth3s + Balance Type : frozen + Balance : 8483 + + Currency : wing + Balance Type : trade + Balance : 0 + + Currency : wing + Balance Type : frozen + Balance : 0 + + Currency : dgb + Balance Type : trade + Balance : 0 + + Currency : dgb + Balance Type : frozen + Balance : 0 + + Currency : bsv3s + Balance Type : trade + Balance : 0 + + Currency : bsv3s + Balance Type : frozen + Balance : 0 + + Currency : dot2s + Balance Type : trade + Balance : 0 + + Currency : dot2s + Balance Type : frozen + Balance : 0 + + Currency : dgd + Balance Type : trade + Balance : 0 + + Currency : dgd + Balance Type : frozen + Balance : 0 + + Currency : zrx + Balance Type : trade + Balance : 0 + + Currency : zrx + Balance Type : frozen + Balance : 0 + + Currency : bcd + Balance Type : trade + Balance : 0 + + Currency : bcd + Balance Type : frozen + Balance : 0 + + Currency : aac + Balance Type : trade + Balance : 0 + + Currency : aac + Balance Type : frozen + Balance : 0 + + Currency : bch + Balance Type : trade + Balance : 0 + + Currency : bch + Balance Type : frozen + Balance : 0 + + Currency : bsv + Balance Type : trade + Balance : 0 + + Currency : bsv + Balance Type : frozen + Balance : 0 + + Currency : omg + Balance Type : trade + Balance : 0 + + Currency : omg + Balance Type : frozen + Balance : 0 + + Currency : jst + Balance Type : trade + Balance : 0 + + Currency : jst + Balance Type : frozen + Balance : 0 + + Currency : hot + Balance Type : trade + Balance : 0 + + Currency : hot + Balance Type : frozen + Balance : 0 + + Currency : sun + Balance Type : trade + Balance : 0 + + Currency : sun + Balance Type : frozen + Balance : 0 + + Currency : wbtc + Balance Type : trade + Balance : 0 + + Currency : wbtc + Balance Type : frozen + Balance : 0 + + Currency : appc + Balance Type : trade + Balance : 0 + + Currency : appc + Balance Type : frozen + Balance : 0 + + Currency : btc + Balance Type : trade + Balance : 0.000000526 + + Currency : btc + Balance Type : frozen + Balance : 0.026683 + + Currency : sc + Balance Type : trade + Balance : 0 + + Currency : sc + Balance Type : frozen + Balance : 0 + + Currency : rsr + Balance Type : trade + Balance : 0 + + Currency : rsr + Balance Type : frozen + Balance : 0 + + Currency : nkn + Balance Type : trade + Balance : 0 + + Currency : nkn + Balance Type : frozen + Balance : 0 + + Currency : qash + Balance Type : trade + Balance : 0 + + Currency : qash + Balance Type : frozen + Balance : 0 + + Currency : cvc + Balance Type : trade + Balance : 0 + + Currency : cvc + Balance Type : frozen + Balance : 0 + + Currency : ardr + Balance Type : trade + Balance : 0 + + Currency : ardr + Balance Type : frozen + Balance : 0 + + Currency : btg + Balance Type : trade + Balance : 0 + + Currency : btg + Balance Type : frozen + Balance : 0 + + Currency : bcv + Balance Type : trade + Balance : 0 + + Currency : bcv + Balance Type : frozen + Balance : 0 + + Currency : link3l + Balance Type : trade + Balance : 0 + + Currency : link3l + Balance Type : frozen + Balance : 0 + + Currency : bcx + Balance Type : trade + Balance : 0 + + Currency : bcx + Balance Type : frozen + Balance : 0 + + Currency : matic + Balance Type : trade + Balance : 0 + + Currency : matic + Balance Type : frozen + Balance : 0 + + Currency : btm + Balance Type : trade + Balance : 0 + + Currency : btm + Balance Type : frozen + Balance : 0 + + Currency : cvcoin + Balance Type : trade + Balance : 0 + + Currency : cvcoin + Balance Type : frozen + Balance : 0 + + Currency : rcn + Balance Type : trade + Balance : 0 + + Currency : rcn + Balance Type : frozen + Balance : 0 + + Currency : cvp + Balance Type : trade + Balance : 0 + + Currency : cvp + Balance Type : frozen + Balance : 0 + + Currency : link3s + Balance Type : trade + Balance : 0 + + Currency : link3s + Balance Type : frozen + Balance : 0 + + Currency : uip + Balance Type : trade + Balance : 0 + + Currency : uip + Balance Type : frozen + Balance : 0 + + Currency : mzk + Balance Type : trade + Balance : 0 + + Currency : mzk + Balance Type : frozen + Balance : 0 + + Currency : bts + Balance Type : trade + Balance : 0 + + Currency : bts + Balance Type : frozen + Balance : 0 + + Currency : rte + Balance Type : trade + Balance : 0 + + Currency : rte + Balance Type : frozen + Balance : 0 + + Currency : salt + Balance Type : trade + Balance : 0 + + Currency : salt + Balance Type : frozen + Balance : 0 + + Currency : btt + Balance Type : trade + Balance : 0 + + Currency : btt + Balance Type : frozen + Balance : 0 + + Currency : rccc + Balance Type : trade + Balance : 0 + + Currency : rccc + Balance Type : frozen + Balance : 0 + + Currency : one + Balance Type : trade + Balance : 0 + + Currency : one + Balance Type : frozen + Balance : 0 + + Currency : ong + Balance Type : trade + Balance : 0 + + Currency : ong + Balance Type : frozen + Balance : 0 + + Currency : lxt + Balance Type : trade + Balance : 0 + + Currency : lxt + Balance Type : frozen + Balance : 0 + + Currency : nhbtc + Balance Type : trade + Balance : 0 + + Currency : nhbtc + Balance Type : frozen + Balance : 0 + + Currency : abl + Balance Type : trade + Balance : 0 + + Currency : abl + Balance Type : frozen + Balance : 0 + + Currency : nest + Balance Type : trade + Balance : 0 + + Currency : nest + Balance Type : frozen + Balance : 0 + + Currency : glm + Balance Type : trade + Balance : 0 + + Currency : glm + Balance Type : frozen + Balance : 0 + + Currency : storj + Balance Type : trade + Balance : 0 + + Currency : storj + Balance Type : frozen + Balance : 0 + + Currency : hpt + Balance Type : trade + Balance : 0 + + Currency : hpt + Balance Type : frozen + Balance : 0 + + Currency : gnx + Balance Type : trade + Balance : 0 + + Currency : gnx + Balance Type : frozen + Balance : 0 + + Currency : dht + Balance Type : trade + Balance : 0 + + Currency : dht + Balance Type : frozen + Balance : 0 + + Currency : abt + Balance Type : trade + Balance : 0 + + Currency : abt + Balance Type : frozen + Balance : 0 + + Currency : ugas + Balance Type : trade + Balance : 0 + + Currency : ugas + Balance Type : frozen + Balance : 0 + + Currency : mana + Balance Type : trade + Balance : 0 + + Currency : mana + Balance Type : frozen + Balance : 0 + + Currency : ont + Balance Type : trade + Balance : 0 + + Currency : ont + Balance Type : frozen + Balance : 0 + + Currency : bkbt + Balance Type : trade + Balance : 0 + + Currency : bkbt + Balance Type : frozen + Balance : 0 + + Currency : ruff + Balance Type : trade + Balance : 0 + + Currency : ruff + Balance Type : frozen + Balance : 0 + + Currency : onx + Balance Type : trade + Balance : 0 + + Currency : onx + Balance Type : frozen + Balance : 0 + + Currency : ankr + Balance Type : trade + Balance : 0 + + Currency : ankr + Balance Type : frozen + Balance : 0 + + Currency : rdn + Balance Type : trade + Balance : 0 + + Currency : rdn + Balance Type : frozen + Balance : 0 + + Currency : algo + Balance Type : trade + Balance : 0 + + Currency : algo + Balance Type : frozen + Balance : 0 + + Currency : eth1s + Balance Type : trade + Balance : 0 + + Currency : eth1s + Balance Type : frozen + Balance : 0 + + Currency : rub + Balance Type : trade + Balance : 0 + + Currency : rub + Balance Type : frozen + Balance : 0 + + Currency : gof + Balance Type : trade + Balance : 0 + + Currency : gof + Balance Type : frozen + Balance : 0 + + Currency : but + Balance Type : trade + Balance : 0 + + Currency : but + Balance Type : frozen + Balance : 0 + + Currency : tt + Balance Type : trade + Balance : 0 + + Currency : tt + Balance Type : frozen + Balance : 0 + + Currency : lym + Balance Type : trade + Balance : 0 + + Currency : lym + Balance Type : frozen + Balance : 0 + + Currency : zjlt + Balance Type : trade + Balance : 0 + + Currency : zjlt + Balance Type : frozen + Balance : 0 + + Currency : df + Balance Type : trade + Balance : 0 + + Currency : df + Balance Type : frozen + Balance : 0 + + Currency : ast + Balance Type : trade + Balance : 0 + + Currency : ast + Balance Type : frozen + Balance : 0 + + Currency : wicc + Balance Type : trade + Balance : 0 + + Currency : wicc + Balance Type : frozen + Balance : 0 + + Currency : doge + Balance Type : trade + Balance : 0 + + Currency : doge + Balance Type : frozen + Balance : 0 + + Currency : nano + Balance Type : trade + Balance : 0 + + Currency : nano + Balance Type : frozen + Balance : 0 + + Currency : ach + Balance Type : trade + Balance : 0 + + Currency : ach + Balance Type : frozen + Balance : 0 + + Currency : eko + Balance Type : trade + Balance : 0 + + Currency : eko + Balance Type : frozen + Balance : 0 + + Currency : sexc + Balance Type : trade + Balance : 0 + + Currency : sexc + Balance Type : frozen + Balance : 0 + + Currency : bel + Balance Type : trade + Balance : 0 + + Currency : bel + Balance Type : frozen + Balance : 0 + + Currency : uc + Balance Type : trade + Balance : 0 + + Currency : uc + Balance Type : frozen + Balance : 0 + + Currency : qsp + Balance Type : trade + Balance : 0 + + Currency : qsp + Balance Type : frozen + Balance : 0 + + Currency : ekt + Balance Type : trade + Balance : 0 + + Currency : ekt + Balance Type : frozen + Balance : 0 + + Currency : act + Balance Type : trade + Balance : 0 + + Currency : act + Balance Type : frozen + Balance : 0 + + Currency : loom + Balance Type : trade + Balance : 0 + + Currency : loom + Balance Type : frozen + Balance : 0 + + Currency : mkr + Balance Type : trade + Balance : 0 + + Currency : mkr + Balance Type : frozen + Balance : 0 + + Currency : woo + Balance Type : trade + Balance : 0 + + Currency : woo + Balance Type : frozen + Balance : 0 + + Currency : ycc + Balance Type : trade + Balance : 0 + + Currency : ycc + Balance Type : frozen + Balance : 0 + + Currency : zec + Balance Type : trade + Balance : 0 + + Currency : zec + Balance Type : frozen + Balance : 0 + + Currency : itc + Balance Type : trade + Balance : 0 + + Currency : itc + Balance Type : frozen + Balance : 0 + + Currency : pai + Balance Type : trade + Balance : 0 + + Currency : pai + Balance Type : frozen + Balance : 0 + + Currency : meetone + Balance Type : trade + Balance : 0 + + Currency : meetone + Balance Type : frozen + Balance : 0 + + Currency : ren + Balance Type : trade + Balance : 0 + + Currency : ren + Balance Type : frozen + Balance : 0 + + Currency : ela + Balance Type : trade + Balance : 0 + + Currency : ela + Balance Type : frozen + Balance : 0 + + Currency : hbar + Balance Type : trade + Balance : 0 + + Currency : hbar + Balance Type : frozen + Balance : 0 + + Currency : atp + Balance Type : trade + Balance : 0 + + Currency : atp + Balance Type : frozen + Balance : 0 + + Currency : ncash + Balance Type : trade + Balance : 0 + + Currency : ncash + Balance Type : frozen + Balance : 0 + + Currency : elf + Balance Type : trade + Balance : 0 + + Currency : elf + Balance Type : frozen + Balance : 0 + + Currency : ada + Balance Type : trade + Balance : 0 + + Currency : ada + Balance Type : frozen + Balance : 0 + + Currency : icx + Balance Type : trade + Balance : 0 + + Currency : icx + Balance Type : frozen + Balance : 0 + + Currency : req + Balance Type : trade + Balance : 0 + + Currency : req + Balance Type : frozen + Balance : 0 + + Currency : add + Balance Type : trade + Balance : 0 + + Currency : add + Balance Type : frozen + Balance : 0 + + Currency : vsys + Balance Type : trade + Balance : 0 + + Currency : vsys + Balance Type : frozen + Balance : 0 + + Currency : usd01 + Balance Type : trade + Balance : 0 + + Currency : usd01 + Balance Type : frozen + Balance : 0 + + Currency : eos3l + Balance Type : trade + Balance : 0 + + Currency : eos3l + Balance Type : frozen + Balance : 0 + + Currency : zen + Balance Type : trade + Balance : 0 + + Currency : zen + Balance Type : frozen + Balance : 0 + + Currency : tusd + Balance Type : trade + Balance : 0 + + Currency : tusd + Balance Type : frozen + Balance : 0 + + Currency : eoss + Balance Type : trade + Balance : 0 + + Currency : eoss + Balance Type : frozen + Balance : 0 + + Currency : pax + Balance Type : trade + Balance : 0 + + Currency : pax + Balance Type : frozen + Balance : 0 + + Currency : em + Balance Type : trade + Balance : 0 + + Currency : em + Balance Type : frozen + Balance : 0 + + Currency : hb10 + Balance Type : trade + Balance : 0 + + Currency : hb10 + Balance Type : frozen + Balance : 0 + + Currency : pay + Balance Type : trade + Balance : 0 + + Currency : pay + Balance Type : frozen + Balance : 0 + + Currency : rbtc + Balance Type : trade + Balance : 0 + + Currency : rbtc + Balance Type : frozen + Balance : 0 + + Currency : eos3s + Balance Type : trade + Balance : 0 + + Currency : eos3s + Balance Type : frozen + Balance : 0 + + Currency : rvn + Balance Type : trade + Balance : 0 + + Currency : rvn + Balance Type : frozen + Balance : 0 + + Currency : propy + Balance Type : trade + Balance : 0 + + Currency : propy + Balance Type : frozen + Balance : 0 + + Currency : chr + Balance Type : trade + Balance : 0 + + Currency : chr + Balance Type : frozen + Balance : 0 + + Currency : egcc + Balance Type : trade + Balance : 0 + + Currency : egcc + Balance Type : frozen + Balance : 0 + + Currency : mln + Balance Type : trade + Balance : 0 + + Currency : mln + Balance Type : frozen + Balance : 0 + + Currency : adt + Balance Type : trade + Balance : 0 + + Currency : adt + Balance Type : frozen + Balance : 0 + + Currency : bft + Balance Type : trade + Balance : 0 + + Currency : bft + Balance Type : frozen + Balance : 0 + + Currency : she + Balance Type : trade + Balance : 0 + + Currency : she + Balance Type : frozen + Balance : 0 + + Currency : sand + Balance Type : trade + Balance : 0 + + Currency : sand + Balance Type : frozen + Balance : 0 + + Currency : bch3l + Balance Type : trade + Balance : 0 + + Currency : bch3l + Balance Type : frozen + Balance : 0 + + Currency : kava + Balance Type : trade + Balance : 0 + + Currency : kava + Balance Type : frozen + Balance : 0 + + Currency : adx + Balance Type : trade + Balance : 0 + + Currency : adx + Balance Type : frozen + Balance : 0 + + Currency : bch3s + Balance Type : trade + Balance : 0 + + Currency : bch3s + Balance Type : frozen + Balance : 0 + + Currency : chz + Balance Type : trade + Balance : 0 + + Currency : chz + Balance Type : frozen + Balance : 0 + + Currency : xrp + Balance Type : trade + Balance : 0.00562304308335422 + + Currency : xrp + Balance Type : frozen + Balance : 0 + + Currency : wpr + Balance Type : trade + Balance : 0 + + Currency : wpr + Balance Type : frozen + Balance : 0 + + Currency : idt + Balance Type : trade + Balance : 0 + + Currency : idt + Balance Type : frozen + Balance : 0 + + Currency : tfuel + Balance Type : trade + Balance : 0 + + Currency : tfuel + Balance Type : frozen + Balance : 0 + + Currency : dka + Balance Type : trade + Balance : 0 + + Currency : dka + Balance Type : frozen + Balance : 0 + + Currency : xrt + Balance Type : trade + Balance : 0 + + Currency : xrt + Balance Type : frozen + Balance : 0 + + Currency : vidy + Balance Type : trade + Balance : 0 + + Currency : vidy + Balance Type : frozen + Balance : 0 + + Currency : hc + Balance Type : trade + Balance : 0 + + Currency : hc + Balance Type : frozen + Balance : 0 + + Currency : perp + Balance Type : trade + Balance : 0 + + Currency : perp + Balance Type : frozen + Balance : 0 + + Currency : mass + Balance Type : trade + Balance : 0 + + Currency : mass + Balance Type : frozen + Balance : 0 + + Currency : for + Balance Type : trade + Balance : 0 + + Currency : for + Balance Type : frozen + Balance : 0 + + Currency : uma + Balance Type : trade + Balance : 0 + + Currency : uma + Balance Type : frozen + Balance : 0 + + Currency : qun + Balance Type : trade + Balance : 0 + + Currency : qun + Balance Type : frozen + Balance : 0 + + Currency : sbtc + Balance Type : trade + Balance : 0 + + Currency : sbtc + Balance Type : frozen + Balance : 0 + + Currency : luna + Balance Type : trade + Balance : 0 + + Currency : luna + Balance Type : frozen + Balance : 0 + + Currency : pizza + Balance Type : trade + Balance : 0 + + Currency : pizza + Balance Type : frozen + Balance : 0 + + Currency : bcha + Balance Type : trade + Balance : 0 + + Currency : bcha + Balance Type : frozen + Balance : 0 + + Currency : yfii + Balance Type : trade + Balance : 0 + + Currency : yfii + Balance Type : frozen + Balance : 0 + + Currency : gas + Balance Type : trade + Balance : 0 + + Currency : gas + Balance Type : frozen + Balance : 0 + + Currency : yee + Balance Type : trade + Balance : 0 + + Currency : yee + Balance Type : frozen + Balance : 0 + + Currency : nexo + Balance Type : trade + Balance : 0 + + Currency : nexo + Balance Type : frozen + Balance : 0 + + Currency : lend + Balance Type : trade + Balance : 0 + + Currency : lend + Balance Type : frozen + Balance : 0 + + Currency : portal + Balance Type : trade + Balance : 0 + + Currency : portal + Balance Type : frozen + Balance : 0 + + Currency : eng + Balance Type : trade + Balance : 0 + + Currency : eng + Balance Type : frozen + Balance : 0 + + Currency : bhd + Balance Type : trade + Balance : 0 + + Currency : bhd + Balance Type : frozen + Balance : 0 + + Currency : aave + Balance Type : trade + Balance : 0 + + Currency : aave + Balance Type : frozen + Balance : 0 + + Currency : wan + Balance Type : trade + Balance : 0 + + Currency : wan + Balance Type : frozen + Balance : 0 + + Currency : grt + Balance Type : trade + Balance : 0 + + Currency : grt + Balance Type : frozen + Balance : 0 + + Currency : grs + Balance Type : trade + Balance : 0 + + Currency : grs + Balance Type : frozen + Balance : 0 + + Currency : gt + Balance Type : trade + Balance : 0 + + Currency : gt + Balance Type : frozen + Balance : 0 + + Currency : waxp + Balance Type : trade + Balance : 0 + + Currency : waxp + Balance Type : frozen + Balance : 0 + + Currency : hbc + Balance Type : trade + Balance : 0 + + Currency : hbc + Balance Type : frozen + Balance : 0 + + Currency : uni + Balance Type : trade + Balance : 0 + + Currency : uni + Balance Type : frozen + Balance : 0 + + Currency : gsc + Balance Type : trade + Balance : 0 + + Currency : gsc + Balance Type : frozen + Balance : 0 + + Currency : ckb + Balance Type : trade + Balance : 0 + + Currency : ckb + Balance Type : frozen + Balance : 0 + + Currency : swftc + Balance Type : trade + Balance : 0 + + Currency : swftc + Balance Type : frozen + Balance : 0 + + Currency : yfi + Balance Type : trade + Balance : 0 + + Currency : yfi + Balance Type : frozen + Balance : 0 + + Currency : xtz + Balance Type : trade + Balance : 0 + + Currency : xtz + Balance Type : frozen + Balance : 0 + + Currency : avax + Balance Type : trade + Balance : 0 + + Currency : avax + Balance Type : frozen + Balance : 0 + + Currency : eon + Balance Type : trade + Balance : 0 + + Currency : eon + Balance Type : frozen + Balance : 0 + + Currency : xrp3s + Balance Type : trade + Balance : 0 + + Currency : xrp3s + Balance Type : frozen + Balance : 0 + + Currency : eop + Balance Type : trade + Balance : 0 + + Currency : eop + Balance Type : frozen + Balance : 0 + + Currency : eos + Balance Type : trade + Balance : 0 + + Currency : eos + Balance Type : frozen + Balance : 0 + + Currency : value + Balance Type : trade + Balance : 0 + + Currency : value + Balance Type : frozen + Balance : 0 + + Currency : fair + Balance Type : trade + Balance : 0 + + Currency : fair + Balance Type : frozen + Balance : 0 + + Currency : ht + Balance Type : trade + Balance : 0.0024 + + Currency : ht + Balance Type : frozen + Balance : 0 + + Currency : bix + Balance Type : trade + Balance : 0 + + Currency : bix + Balance Type : frozen + Balance : 0 + + Currency : ost + Balance Type : trade + Balance : 0 + + Currency : ost + Balance Type : frozen + Balance : 0 + + Currency : skm + Balance Type : trade + Balance : 0 + + Currency : skm + Balance Type : frozen + Balance : 0 + + Currency : skl + Balance Type : trade + Balance : 0 + + Currency : skl + Balance Type : frozen + Balance : 0 + + Currency : gtc + Balance Type : trade + Balance : 0 + + Currency : gtc + Balance Type : frozen + Balance : 0 + + Currency : 1inch + Balance Type : trade + Balance : 0 + + Currency : 1inch + Balance Type : frozen + Balance : 0 + + Currency : ocn + Balance Type : trade + Balance : 0 + + Currency : ocn + Balance Type : frozen + Balance : 0 + + Currency : zil + Balance Type : trade + Balance : 0 + + Currency : zil + Balance Type : frozen + Balance : 0 + + Currency : xrp3l + Balance Type : trade + Balance : 0.0000534 + + Currency : xrp3l + Balance Type : frozen + Balance : 0 + + Currency : xem + Balance Type : trade + Balance : 0 + + Currency : xem + Balance Type : frozen + Balance : 0 + + Currency : nas + Balance Type : trade + Balance : 0 + + Currency : nas + Balance Type : frozen + Balance : 0 + + Currency : comp + Balance Type : trade + Balance : 0 + + Currency : comp + Balance Type : frozen + Balance : 0 + + Currency : wtc + Balance Type : trade + Balance : 0 + + Currency : wtc + Balance Type : frozen + Balance : 0 + + Currency : hvt + Balance Type : trade + Balance : 0 + + Currency : hvt + Balance Type : frozen + Balance : 0 + + Currency : xvg + Balance Type : trade + Balance : 0 + + Currency : xvg + Balance Type : frozen + Balance : 0 + + Currency : iq + Balance Type : trade + Balance : 0 + + Currency : iq + Balance Type : frozen + Balance : 0 + + Currency : tnb + Balance Type : trade + Balance : 0 + + Currency : tnb + Balance Type : frozen + Balance : 0 + + Currency : btc3s + Balance Type : trade + Balance : 0.0000704 + + Currency : btc3s + Balance Type : frozen + Balance : 0 + + Currency : 18c + Balance Type : trade + Balance : 0 + + Currency : 18c + Balance Type : frozen + Balance : 0 + + Currency : powr + Balance Type : trade + Balance : 0 + + Currency : powr + Balance Type : frozen + Balance : 0 + + Currency : pvt + Balance Type : trade + Balance : 0 + + Currency : pvt + Balance Type : frozen + Balance : 0 + + Currency : btc3l + Balance Type : trade + Balance : 0 + + Currency : btc3l + Balance Type : frozen + Balance : 1.996 + + Currency : ctxc + Balance Type : trade + Balance : 0 + + Currency : ctxc + Balance Type : frozen + Balance : 0 + + Currency : tnt + Balance Type : trade + Balance : 0 + + Currency : tnt + Balance Type : frozen + Balance : 0 + + Currency : nbs + Balance Type : trade + Balance : 0 + + Currency : nbs + Balance Type : frozen + Balance : 0 + + Currency : fsn + Balance Type : trade + Balance : 0 + + Currency : fsn + Balance Type : frozen + Balance : 0 + + Currency : hive + Balance Type : trade + Balance : 0 + + Currency : hive + Balance Type : frozen + Balance : 0 + + Currency : iic + Balance Type : trade + Balance : 0 + + Currency : iic + Balance Type : frozen + Balance : 0 + + Currency : kmd + Balance Type : trade + Balance : 0 + + Currency : kmd + Balance Type : frozen + Balance : 0 + + Currency : bags + Balance Type : trade + Balance : 0 + + Currency : bags + Balance Type : frozen + Balance : 0 + + Currency : steem + Balance Type : trade + Balance : 0 + + Currency : steem + Balance Type : frozen + Balance : 0 + + Currency : lol + Balance Type : trade + Balance : 0 + + Currency : lol + Balance Type : frozen + Balance : 0 + + Currency : dot + Balance Type : trade + Balance : 0 + + Currency : dot + Balance Type : frozen + Balance : 0 + + Currency : cmt + Balance Type : trade + Balance : 0 + + Currency : cmt + Balance Type : frozen + Balance : 0 + + Currency : ncc + Balance Type : trade + Balance : 0 + + Currency : ncc + Balance Type : frozen + Balance : 0 + + Currency : top + Balance Type : trade + Balance : 0 + + Currency : top + Balance Type : frozen + Balance : 0 + + Currency : get + Balance Type : trade + Balance : 0 + + Currency : get + Balance Type : frozen + Balance : 0 + + Currency : gve + Balance Type : trade + Balance : 0 + + Currency : gve + Balance Type : frozen + Balance : 0 + + Currency : cvnt + Balance Type : trade + Balance : 0 + + Currency : cvnt + Balance Type : frozen + Balance : 0 + + Currency : tos + Balance Type : trade + Balance : 0 + + Currency : tos + Balance Type : frozen + Balance : 0 + + Currency : fti + Balance Type : trade + Balance : 0 + + Currency : fti + Balance Type : frozen + Balance : 0 + + Currency : smt + Balance Type : trade + Balance : 0 + + Currency : smt + Balance Type : frozen + Balance : 0 + + Currency : man + Balance Type : trade + Balance : 0 + + Currency : man + Balance Type : frozen + Balance : 0 + + Currency : cnns + Balance Type : trade + Balance : 0 + + Currency : cnns + Balance Type : frozen + Balance : 0 + + Currency : knc + Balance Type : trade + Balance : 0 + + Currency : knc + Balance Type : frozen + Balance : 0 + + Currency : cnn + Balance Type : trade + Balance : 0 + + Currency : cnn + Balance Type : frozen + Balance : 0 + + Currency : bifi + Balance Type : trade + Balance : 0 + + Currency : bifi + Balance Type : frozen + Balance : 0 + + Currency : ftt + Balance Type : trade + Balance : 0 + + Currency : ftt + Balance Type : frozen + Balance : 0 + + Currency : pha + Balance Type : trade + Balance : 0 + + Currency : pha + Balance Type : frozen + Balance : 0 + + Currency : snc + Balance Type : trade + Balance : 0 + + Currency : snc + Balance Type : frozen + Balance : 0 + + Currency : zla + Balance Type : trade + Balance : 0 + + Currency : zla + Balance Type : frozen + Balance : 0 + + Currency : blz + Balance Type : trade + Balance : 0 + + Currency : blz + Balance Type : frozen + Balance : 0 + + Currency : api3 + Balance Type : trade + Balance : 0 + + Currency : api3 + Balance Type : frozen + Balance : 0 + + Currency : chat + Balance Type : trade + Balance : 0 + + Currency : chat + Balance Type : frozen + Balance : 0 + + Currency : eosdac + Balance Type : trade + Balance : 0 + + Currency : eosdac + Balance Type : frozen + Balance : 0 + + Currency : yamv2 + Balance Type : trade + Balance : 0 + + Currency : yamv2 + Balance Type : frozen + Balance : 0 + + Currency : snt + Balance Type : trade + Balance : 0 + + Currency : snt + Balance Type : frozen + Balance : 0 + + Currency : atom + Balance Type : trade + Balance : 0 + + Currency : atom + Balance Type : frozen + Balance : 0 + + Currency : snx + Balance Type : trade + Balance : 0 + + Currency : snx + Balance Type : frozen + Balance : 0 + + Currency : soc + Balance Type : trade + Balance : 0 + + Currency : soc + Balance Type : frozen + Balance : 0 + + Currency : dac + Balance Type : trade + Balance : 0 + + Currency : dac + Balance Type : frozen + Balance : 0 + + Currency : seele + Balance Type : trade + Balance : 0 + + Currency : seele + Balance Type : frozen + Balance : 0 + + Currency : dai + Balance Type : trade + Balance : 0 + + Currency : dai + Balance Type : frozen + Balance : 0 + + Currency : gusd + Balance Type : trade + Balance : 0 + + Currency : gusd + Balance Type : frozen + Balance : 0 + + Currency : sol + Balance Type : trade + Balance : 0 + + Currency : sol + Balance Type : frozen + Balance : 0 + + Currency : hit + Balance Type : trade + Balance : 0 + + Currency : hit + Balance Type : frozen + Balance : 0 + + Currency : ltc3s + Balance Type : trade + Balance : 0 + + Currency : ltc3s + Balance Type : frozen + Balance : 0 + + Currency : etc + Balance Type : trade + Balance : 0 + + Currency : etc + Balance Type : frozen + Balance : 0 + + Currency : dat + Balance Type : trade + Balance : 0 + + Currency : dat + Balance Type : frozen + Balance : 0 + + Currency : etf + Balance Type : trade + Balance : 0 + + Currency : etf + Balance Type : frozen + Balance : 0 + + Currency : ogo + Balance Type : trade + Balance : 0 + + Currency : ogo + Balance Type : frozen + Balance : 0 + + Currency : ogn + Balance Type : trade + Balance : 0 + + Currency : ogn + Balance Type : frozen + Balance : 0 + + Currency : usdt + Balance Type : trade + Balance : 3528.75950756236086578 + + Currency : usdt + Balance Type : frozen + Balance : 0.000000000000000002 + + Currency : eth + Balance Type : trade + Balance : 0 + + Currency : eth + Balance Type : frozen + Balance : 0 + + Currency : mco + Balance Type : trade + Balance : 0 + + Currency : mco + Balance Type : frozen + Balance : 0 + + Currency : topc + Balance Type : trade + Balance : 0 + + Currency : topc + Balance Type : frozen + Balance : 0 + + Currency : aidoc + Balance Type : trade + Balance : 0 + + Currency : aidoc + Balance Type : frozen + Balance : 0 + + Currency : titan + Balance Type : trade + Balance : 0 + + Currency : titan + Balance Type : frozen + Balance : 0 + + Currency : neo + Balance Type : trade + Balance : 0 + + Currency : neo + Balance Type : frozen + Balance : 0 + + Currency : sushi + Balance Type : trade + Balance : 0 + + Currency : sushi + Balance Type : frozen + Balance : 0 + + Currency : mta + Balance Type : trade + Balance : 0 + + Currency : mta + Balance Type : frozen + Balance : 0 + + Currency : lrc + Balance Type : trade + Balance : 0 + + Currency : lrc + Balance Type : frozen + Balance : 0 + + Currency : ltc3l + Balance Type : trade + Balance : 0 + + Currency : ltc3l + Balance Type : frozen + Balance : 0 + + Currency : renbtc + Balance Type : trade + Balance : 0 + + Currency : renbtc + Balance Type : frozen + Balance : 0 + + Currency : ven + Balance Type : trade + Balance : 0 + + Currency : ven + Balance Type : frozen + Balance : 0 + + Currency : etn + Balance Type : trade + Balance : 0 + + Currency : etn + Balance Type : frozen + Balance : 0 + + Currency : swrv + Balance Type : trade + Balance : 0 + + Currency : swrv + Balance Type : frozen + Balance : 0 + + Currency : firo + Balance Type : trade + Balance : 0 + + Currency : firo + Balance Type : frozen + Balance : 0 + + Currency : wgp + Balance Type : trade + Balance : 0 + + Currency : wgp + Balance Type : frozen + Balance : 0 + + Currency : new + Balance Type : trade + Balance : 0 + + Currency : new + Balance Type : frozen + Balance : 0 + + Currency : gxc + Balance Type : trade + Balance : 0 + + Currency : gxc + Balance Type : frozen + Balance : 0 + + Currency : mtl + Balance Type : trade + Balance : 0 + + Currency : mtl + Balance Type : frozen + Balance : 0 + + Currency : dbc + Balance Type : trade + Balance : 0 + + Currency : dbc + Balance Type : frozen + Balance : 0 + + Currency : vet + Balance Type : trade + Balance : 0 + + Currency : vet + Balance Type : frozen + Balance : 0 + + Currency : trb + Balance Type : trade + Balance : 0 + + Currency : trb + Balance Type : frozen + Balance : 0 + + Currency : mtn + Balance Type : trade + Balance : 0 + + Currency : mtn + Balance Type : frozen + Balance : 0 + + Currency : mt + Balance Type : trade + Balance : 0 + + Currency : mt + Balance Type : frozen + Balance : 0 + + Currency : fil3s + Balance Type : trade + Balance : 0 + + Currency : fil3s + Balance Type : frozen + Balance : 0 + + Currency : bnt + Balance Type : trade + Balance : 0 + + Currency : bnt + Balance Type : frozen + Balance : 0 + + Currency : lba + Balance Type : trade + Balance : 0 + + Currency : lba + Balance Type : frozen + Balance : 0 + + Currency : oxt + Balance Type : trade + Balance : 0 + + Currency : oxt + Balance Type : frozen + Balance : 0 + + Currency : usdc + Balance Type : trade + Balance : 0 + + Currency : usdc + Balance Type : frozen + Balance : 0 + + Currency : mx + Balance Type : trade + Balance : 0 + + Currency : mx + Balance Type : frozen + Balance : 0 + + Currency : fil3l + Balance Type : trade + Balance : 0 + + Currency : fil3l + Balance Type : frozen + Balance : 0 + + Currency : utk + Balance Type : trade + Balance : 0 + + Currency : utk + Balance Type : frozen + Balance : 0 + + Currency : nvt + Balance Type : trade + Balance : 0 + + Currency : nvt + Balance Type : frozen + Balance : 0 + + Currency : mtx + Balance Type : trade + Balance : 0 + + Currency : mtx + Balance Type : frozen + Balance : 0 + + Currency : wxt + Balance Type : trade + Balance : 0 + + Currency : wxt + Balance Type : frozen + Balance : 0 + + Currency : edu + Balance Type : trade + Balance : 0 + + Currency : edu + Balance Type : frozen + Balance : 0 + + Currency : trx + Balance Type : trade + Balance : 0.002358 + + Currency : trx + Balance Type : frozen + Balance : 69069.31 + + Currency : dash + Balance Type : trade + Balance : 0 + + Currency : dash + Balance Type : frozen + Balance : 0 + + Currency : mds + Balance Type : trade + Balance : 0 + + Currency : mds + Balance Type : frozen + Balance : 0 + + Currency : nuls + Balance Type : trade + Balance : 0 + + Currency : nuls + Balance Type : frozen + Balance : 0 + + Currency : mdx + Balance Type : trade + Balance : 0 + + Currency : mdx + Balance Type : frozen + Balance : 0 + + Currency : pond + Balance Type : trade + Balance : 0 + + Currency : pond + Balance Type : frozen + Balance : 0 + + Currency : lina + Balance Type : trade + Balance : 0 + + Currency : lina + Balance Type : frozen + Balance : 0 + + Currency : bor + Balance Type : trade + Balance : 0 + + Currency : bor + Balance Type : frozen + Balance : 0 + + Currency : lsk + Balance Type : trade + Balance : 0 + + Currency : lsk + Balance Type : frozen + Balance : 0 + + Currency : bot + Balance Type : trade + Balance : 0 + + Currency : bot + Balance Type : frozen + Balance : 0 + + Currency : nsure + Balance Type : trade + Balance : 0 + + Currency : nsure + Balance Type : frozen + Balance : 0 + + Currency : link + Balance Type : trade + Balance : 0.0099 + + Currency : link + Balance Type : frozen + Balance : 0 + + Currency : box + Balance Type : trade + Balance : 0 + + Currency : box + Balance Type : frozen + Balance : 0 + + Currency : akro + Balance Type : trade + Balance : 0 + + Currency : akro + Balance Type : frozen + Balance : 0 + + Currency : meet + Balance Type : trade + Balance : 0 + + Currency : meet + Balance Type : frozen + Balance : 0 + + Currency : qtum + Balance Type : trade + Balance : 0 + + Currency : qtum + Balance Type : frozen + Balance : 0 + + Currency : uni2l + Balance Type : trade + Balance : 0 + + Currency : uni2l + Balance Type : frozen + Balance : 0 + + Currency : arpa + Balance Type : trade + Balance : 0 + + Currency : arpa + Balance Type : frozen + Balance : 0 + + Currency : dta + Balance Type : trade + Balance : 0 + + Currency : dta + Balance Type : frozen + Balance : 0 + + Currency : dcr + Balance Type : trade + Balance : 0 + + Currency : dcr + Balance Type : frozen + Balance : 0 + + Currency : uuu + Balance Type : trade + Balance : 0 + + Currency : uuu + Balance Type : frozen + Balance : 0 + + Currency : uni2s + Balance Type : trade + Balance : 0 + + Currency : uni2s + Balance Type : frozen + Balance : 0 + + Currency : cre + Balance Type : trade + Balance : 0 + + Currency : cre + Balance Type : frozen + Balance : 0 + + Currency : zec3l + Balance Type : trade + Balance : 0 + + Currency : zec3l + Balance Type : frozen + Balance : 0 + + Currency : kan + Balance Type : trade + Balance : 0 + + Currency : kan + Balance Type : frozen + Balance : 0 + + Currency : beth + Balance Type : trade + Balance : 0 + + Currency : beth + Balance Type : frozen + Balance : 0 + + Currency : ltc + Balance Type : trade + Balance : 0 + + Currency : ltc + Balance Type : frozen + Balance : 0 + + Currency : zec3s + Balance Type : trade + Balance : 0 + + Currency : zec3s + Balance Type : frozen + Balance : 0 + + Currency : inc + Balance Type : trade + Balance : 0 + + Currency : inc + Balance Type : frozen + Balance : 0 + + Currency : mex + Balance Type : trade + Balance : 0 + + Currency : mex + Balance Type : frozen + Balance : 0 + + Currency : cro + Balance Type : trade + Balance : 0 + + Currency : cro + Balance Type : frozen + Balance : 0 + + Currency : mvl + Balance Type : trade + Balance : 0 + + Currency : mvl + Balance Type : frozen + Balance : 0 + + Currency : inj + Balance Type : trade + Balance : 0 + + Currency : inj + Balance Type : frozen + Balance : 0 + + Currency : evx + Balance Type : trade + Balance : 0 + + Currency : evx + Balance Type : frozen + Balance : 0 + + Currency : ant + Balance Type : trade + Balance : 0 + + Currency : ant + Balance Type : frozen + Balance : 0 + + Currency : crv + Balance Type : trade + Balance : 0 + + Currency : crv + Balance Type : frozen + Balance : 0 + + Currency : cru + Balance Type : trade + Balance : 0 + + Currency : cru + Balance Type : frozen + Balance : 0 + + Currency : btc1s + Balance Type : trade + Balance : 0 + + Currency : btc1s + Balance Type : frozen + Balance : 0 + + Currency : srn + Balance Type : trade + Balance : 0 + + Currency : srn + Balance Type : frozen + Balance : 0 + + Currency : datx + Balance Type : trade + Balance : 0 + + Currency : datx + Balance Type : frozen + Balance : 0 + + Currency : badger + Balance Type : trade + Balance : 0.91950210527285626 + + Currency : badger + Balance Type : frozen + Balance : 0 + + Currency : pols + Balance Type : trade + Balance : 0 + + Currency : pols + Balance Type : frozen + Balance : 0 + + Currency : pc + Balance Type : trade + Balance : 0 + + Currency : pc + Balance Type : frozen + Balance : 0 + + Currency : poly + Balance Type : trade + Balance : 0 + + Currency : poly + Balance Type : frozen + Balance : 0 + + Currency : xlm + Balance Type : trade + Balance : 0 + + Currency : xlm + Balance Type : frozen + Balance : 0 + + Currency : lamb + Balance Type : trade + Balance : 0 + + Currency : lamb + Balance Type : frozen + Balance : 0 + + Currency : phx + Balance Type : trade + Balance : 0 + + Currency : phx + Balance Type : frozen + Balance : 0 + + Currency : front + Balance Type : trade + Balance : 0 + + Currency : front + Balance Type : frozen + Balance : 0 + + +Account ID : 18781342 +Account Type : margin +Account State : working +Subtype : bsvusdt + + Currency : bsv + Balance Type : trade + Balance : 0.000059073599142826 + + Currency : bsv + Balance Type : frozen + Balance : 50.8113 + + Currency : bsv + Balance Type : loan + Balance : 0 + + Currency : bsv + Balance Type : interest + Balance : 0 + + Currency : usdt + Balance Type : trade + Balance : 0.000900000000003071 + + Currency : usdt + Balance Type : frozen + Balance : 0.000000000000000003 + + Currency : usdt + Balance Type : loan + Balance : -6688.376 + + Currency : usdt + Balance Type : interest + Balance : -21.41935655 + + +Account ID : 18837978 +Account Type : margin +Account State : working +Subtype : htusdt + + Currency : usdt + Balance Type : trade + Balance : 0.00000000800000194 + + Currency : usdt + Balance Type : frozen + Balance : 0 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + Currency : ht + Balance Type : trade + Balance : 0.00947382462977359 + + Currency : ht + Balance Type : frozen + Balance : 0 + + Currency : ht + Balance Type : loan + Balance : 0 + + Currency : ht + Balance Type : interest + Balance : 0 + + +Account ID : 18739313 +Account Type : margin +Account State : working +Subtype : ethusdt + + Currency : eth + Balance Type : trade + Balance : 0.000990739293469514 + + Currency : eth + Balance Type : frozen + Balance : 0 + + Currency : eth + Balance Type : loan + Balance : -0.00158068 + + Currency : eth + Balance Type : interest + Balance : 0 + + Currency : usdt + Balance Type : trade + Balance : 1053.900375716000003242 + + Currency : usdt + Balance Type : frozen + Balance : 0.000000000000000001 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + +Account ID : 18692354 +Account Type : margin +Account State : working +Subtype : yfiusdt + + Currency : usdt + Balance Type : trade + Balance : 0 + + Currency : usdt + Balance Type : frozen + Balance : 0 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + Currency : yfi + Balance Type : trade + Balance : 0.000981342 + + Currency : yfi + Balance Type : frozen + Balance : 0 + + Currency : yfi + Balance Type : loan + Balance : 0 + + Currency : yfi + Balance Type : interest + Balance : 0 + + +Account ID : 18821595 +Account Type : margin +Account State : working +Subtype : xrpusdt + + Currency : xrp + Balance Type : trade + Balance : 0 + + Currency : xrp + Balance Type : frozen + Balance : 0 + + Currency : xrp + Balance Type : loan + Balance : 0 + + Currency : xrp + Balance Type : interest + Balance : 0 + + Currency : usdt + Balance Type : trade + Balance : 0 + + Currency : usdt + Balance Type : frozen + Balance : 0 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + +Account ID : 18792019 +Account Type : margin +Account State : working +Subtype : vetusdt + + Currency : vet + Balance Type : trade + Balance : 299.04182932 + + Currency : vet + Balance Type : frozen + Balance : 0 + + Currency : vet + Balance Type : loan + Balance : 0 + + Currency : vet + Balance Type : interest + Balance : 0 + + Currency : usdt + Balance Type : trade + Balance : 0.0000000096 + + Currency : usdt + Balance Type : frozen + Balance : 0 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + +Account ID : 18736093 +Account Type : margin +Account State : working +Subtype : trxusdt + + Currency : usdt + Balance Type : trade + Balance : 5.843408887800000024 + + Currency : usdt + Balance Type : frozen + Balance : 0.000000000000000001 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + Currency : trx + Balance Type : trade + Balance : 0.684836479013854132 + + Currency : trx + Balance Type : frozen + Balance : 0 + + Currency : trx + Balance Type : loan + Balance : 0 + + Currency : trx + Balance Type : interest + Balance : 0 + + +Account ID : 18767134 +Account Type : margin +Account State : working +Subtype : renusdt + + Currency : usdt + Balance Type : trade + Balance : 0.00000000702 + + Currency : usdt + Balance Type : frozen + Balance : 0 + + Currency : usdt + Balance Type : loan + Balance : 0 + + Currency : usdt + Balance Type : interest + Balance : 0 + + Currency : ren + Balance Type : trade + Balance : 8.30309806 + + Currency : ren + Balance Type : frozen + Balance : 0 + + Currency : ren + Balance Type : loan + Balance : 0 + + Currency : ren + Balance Type : interest + Balance : 0 + + +Account ID : 17863569 +Account Type : otc +Account State : working +Subtype : + + Currency : usdt + Balance Type : trade + Balance : 0 + + Currency : usdt + Balance Type : frozen + Balance : 0 + + +Account ID : 18788774 +Account Type : margin +Account State : working +Subtype : xmrusdt + + Currency : usdt + Balance Type : trade + Balance : 0.00000000800000496 + + Currency : usdt + Balance Type : frozen + Balance : 0.000000000000000001 + + Currency : usdt + Balance Type : loan + Balance : -7994.332 + + Currency : usdt + Balance Type : interest + Balance : -31.33522464 + + Currency : xmr + Balance Type : trade + Balance : 0.000074003175294476 + + Currency : xmr + Balance Type : frozen + Balance : 81.4258 + + Currency : xmr + Balance Type : loan + Balance : 0 + + Currency : xmr + Balance Type : interest + Balance : 0 + + + +Process finished with exit code 0 + +''' + + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) +LogInfo.output("====== (SDK encapsulated api) not recommend for low performance and frequence limitation ======") +account_balance_list = account_client.get_account_balance() +if account_balance_list and len(account_balance_list): + for account_obj in account_balance_list: + account_obj.print_object() + print() \ No newline at end of file diff --git a/example/account/get_account_balance_by_subuid.py b/example/account/get_account_balance_by_subuid.py new file mode 100644 index 0000000..8d9014c --- /dev/null +++ b/example/account/get_account_balance_by_subuid.py @@ -0,0 +1,12 @@ +from huobi.client.account import AccountClient +from huobi.constant import * + +# get accounts +from huobi.utils import * +from example.api_key import * +# 子账户财产 +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) +list_obj = account_client.get_account_balance_by_subuid(sub_uid=g_sub_uid) +LogInfo.output_list(list_obj) + diff --git a/example/account/get_account_balance_sufficient.py b/example/account/get_account_balance_sufficient.py new file mode 100644 index 0000000..fb46941 --- /dev/null +++ b/example/account/get_account_balance_sufficient.py @@ -0,0 +1,33 @@ +from huobi.client.account import AccountClient + +# get accounts +from huobi.utils import * +from example.api_key import * + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) +# list_obj = account_client.get_accounts() +# if list_obj and len(list_obj): +# for account_obj in list_obj: +# list_obj = account_client.get_balance(account_id=account_obj.id) +# LogInfo.output("===== {account_id} , {account_type} =====".format(account_id=account_obj.id, account_type=account_obj.type)) +# if len(list_obj): +# for obj in list_obj: +# if float(obj.balance) > 0.1: # only show account with balance +# obj.print_object() +# print() + +LogInfo.output("====== (SDK encapsulated api) not recommend for low performance and frequence limitation ======") +account_balance_list = account_client.get_account_balance() +if account_balance_list and len(account_balance_list): + for account_balance_obj in account_balance_list: + if account_balance_obj and len(account_balance_obj.list): + PrintBasic.print_basic(account_balance_obj.id, "ID") + PrintBasic.print_basic(account_balance_obj.type, "Account Type") + PrintBasic.print_basic(account_balance_obj.state, "Account State") + PrintBasic.print_basic(account_balance_obj.subtype, "Subtype") + for balance_obj in account_balance_obj.list: + if float(balance_obj.balance) > 0.1: # only show account with balance + balance_obj.print_object("\t") + print() + print() \ No newline at end of file diff --git a/example/account/get_account_by_type_symbol.py b/example/account/get_account_by_type_symbol.py new file mode 100644 index 0000000..f79e083 --- /dev/null +++ b/example/account/get_account_by_type_symbol.py @@ -0,0 +1,34 @@ +from huobi.client.account import AccountClient +from huobi.client.margin import MarginClient +from huobi.constant import * + +# get accounts +from huobi.utils import * +from example.api_key import * + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) + +LogInfo.output("========= case 1 get spot (SDK encapsulated api) =========") +account_obj = account_client.get_account_by_type_and_symbol(account_type=AccountType.SPOT, symbol=None) +account_obj.print_object() + +LogInfo.output("========= case 2 get margin-linkusdt (SDK encapsulated api) =========") +account_obj = account_client.get_account_by_type_and_symbol(account_type=AccountType.MARGIN, symbol="linkusdt") +if account_obj: + account_obj.print_object() + +LogInfo.output("========= case 3 get margin-linkusdt (SDK encapsulated api) =========") +account_obj = account_client.get_account_by_type_and_symbol(account_type=AccountType.SUPER_MARGIN, symbol=None) +if account_obj: + account_obj.print_object() + +LogInfo.output("========= case 4 get margin-linkusdt (original api, supported) =========") +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = margin_client.get_margin_account_balance(symbol="linkusdt") +LogInfo.output_list(list_obj) + +LogInfo.output("========= case 5 get cross-margin (original api, supported) =========") +print(margin_client) +account_balance = margin_client.get_cross_margin_account_balance() +account_balance.print_object() \ No newline at end of file diff --git a/example/account/get_account_history.py b/example/account/get_account_history.py new file mode 100644 index 0000000..e53e16e --- /dev/null +++ b/example/account/get_account_history.py @@ -0,0 +1,11 @@ +from huobi.client.account import AccountClient + +# get accounts +from huobi.utils import * +from example.api_key import * +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) + +account_history = account_client.get_account_history(account_id=g_account_id, start_time=1600827872000, size=4) +LogInfo.output_list(account_history["data"]) +LogInfo.output('Next Id: %s' % (account_history["next_id"])) diff --git a/example/account/get_account_ledger.py b/example/account/get_account_ledger.py new file mode 100644 index 0000000..a967a37 --- /dev/null +++ b/example/account/get_account_ledger.py @@ -0,0 +1,8 @@ +from huobi.client.account import AccountClient +from huobi.utils import * +from example.api_key import * +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) + +list_obj = account_client.get_account_ledger(account_id=g_account_id) +LogInfo.output_list(list_obj) diff --git a/example/account/get_account_point.py b/example/account/get_account_point.py new file mode 100644 index 0000000..6da04ef --- /dev/null +++ b/example/account/get_account_point.py @@ -0,0 +1,8 @@ +from huobi.client.account import AccountClient +from example.api_key import * + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) + +account_point_result = account_client.get_account_point() +account_point_result.print_object() diff --git a/example/account/get_accounts.py b/example/account/get_accounts.py new file mode 100644 index 0000000..ecaf373 --- /dev/null +++ b/example/account/get_accounts.py @@ -0,0 +1,12 @@ +from huobi.client.account import AccountClient + +# get accounts +from huobi.utils import * +from example.api_key import * + + +account_client = AccountClient(api_key=g_api_key,secret_key=g_secret_key) + +list_obj = account_client.get_accounts() +LogInfo.output_list(list_obj) + diff --git a/example/account/get_aggregated_subuser_balance.py b/example/account/get_aggregated_subuser_balance.py new file mode 100644 index 0000000..5854f68 --- /dev/null +++ b/example/account/get_aggregated_subuser_balance.py @@ -0,0 +1,10 @@ +from huobi.client.account import AccountClient + +# get accounts +from huobi.utils import * +from example.api_key import * + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) +list_obj = account_client.get_aggregated_subuser_balance() +LogInfo.output_list(list_obj) diff --git a/example/account/get_balance.py b/example/account/get_balance.py new file mode 100644 index 0000000..c050d7f --- /dev/null +++ b/example/account/get_balance.py @@ -0,0 +1,9 @@ +from huobi.client.account import AccountClient + +# get accounts +from huobi.utils import * +from example.api_key import * +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) +list_obj = account_client.get_balance(account_id=g_account_id) +LogInfo.output_list(list_obj) \ No newline at end of file diff --git a/example/account/post_account_transfer.py b/example/account/post_account_transfer.py new file mode 100644 index 0000000..fecf2a8 --- /dev/null +++ b/example/account/post_account_transfer.py @@ -0,0 +1,12 @@ +from huobi.client.account import AccountClient +from huobi.constant import * + +account_client = AccountClient(api_key=g_api_key, secret_key=g_secret_key) +# transfer_result = account_client.post_account_transfer(111859319, 'spot', 10354000, 147509118, 'spot', 13956126, 'trx', 3600) +# transfer_result.print_object() + +# transfer_result = account_client.post_account_transfer(111859319, 'spot', 10354000, 147991959, 'spot', 14026125, 'usdt', 3600) +# transfer_result.print_object() + +transfer_result = account_client.post_account_transfer(111859319, 'spot', 10354000, 141403659, 'spot', 13082796, 'usdt', 32.21) +transfer_result.print_object() diff --git a/example/account/post_point_transfer.py b/example/account/post_point_transfer.py new file mode 100644 index 0000000..f4db684 --- /dev/null +++ b/example/account/post_point_transfer.py @@ -0,0 +1,8 @@ +from huobi.client.account import AccountClient +from huobi.constant import * + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) + +point_transfer_result = account_client.post_point_transfer(from_uid=111859319, to_uid=124409916, group_id=0, amount=10) +point_transfer_result.print_object() diff --git a/example/account/post_sub_user_management.py b/example/account/post_sub_user_management.py new file mode 100644 index 0000000..679b338 --- /dev/null +++ b/example/account/post_sub_user_management.py @@ -0,0 +1,13 @@ + +from huobi.client.account import AccountClient +from huobi.constant import * + +# get accounts +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) + +ret = account_client.post_sub_uid_management(sub_uid=g_sub_uid, action=SubUidAction.LOCK) +ret.print_object() + +ret = account_client.post_sub_uid_management(sub_uid=g_sub_uid, action=SubUidAction.UNLOCK) +ret.print_object() \ No newline at end of file diff --git a/example/account/post_transfer_between_futures_and_pro.py b/example/account/post_transfer_between_futures_and_pro.py new file mode 100644 index 0000000..33ab7cc --- /dev/null +++ b/example/account/post_transfer_between_futures_and_pro.py @@ -0,0 +1,19 @@ + +from huobi.client.account import AccountClient +from huobi.constant import * +from huobi.utils import * +import time + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) +transfer_order_id = account_client.transfer_between_futures_and_pro(currency="trx", amount=200, transfer_type=TransferFuturesPro.TO_FUTURES) +LogInfo.output("transfer from pro to future Order Id : {id}".format(id=transfer_order_id)) + +# need wait a minute +time.sleep(2) + +transfer_order_id = account_client.transfer_between_futures_and_pro(currency="trx", amount=200, transfer_type=TransferFuturesPro.TO_PRO) +LogInfo.output("transfer from future to pro Order Id : {id}".format(id=transfer_order_id)) + + + diff --git a/example/account/post_transfer_between_parent_and_subuser.py b/example/account/post_transfer_between_parent_and_subuser.py new file mode 100644 index 0000000..a0a959f --- /dev/null +++ b/example/account/post_transfer_between_parent_and_subuser.py @@ -0,0 +1,15 @@ + +from huobi.client.account import AccountClient +from huobi.constant import * +from huobi.utils import * + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key) +transfer_order_id = account_client.transfer_between_parent_and_subuser(sub_uid=g_sub_uid, currency="usdt", amount=10, transfer_type=TransferMasterType.OUT) +LogInfo.output("transfer from master to subuser Order Id : {id}".format(id=transfer_order_id)) + +transfer_order_id = account_client.transfer_between_parent_and_subuser(sub_uid=g_sub_uid, currency="usdt", amount=10, transfer_type=TransferMasterType.IN) +LogInfo.output("transfer from subuser to master Order Id : {id}".format(id=transfer_order_id)) + + + diff --git a/example/account/req_account_balance_list.py b/example/account/req_account_balance_list.py new file mode 100644 index 0000000..5423ae5 --- /dev/null +++ b/example/account/req_account_balance_list.py @@ -0,0 +1,10 @@ +from huobi.client.account import AccountClient +from huobi.constant import * + + +def callback(account_balance_req: 'AccountBalanceReq'): + account_balance_req.print_object() + + +account_client = AccountClient(api_key=g_api_key, secret_key=g_secret_key) +account_client.req_account_balance(callback=callback, client_req_id=None) diff --git a/example/account/sub_account_update.py b/example/account/sub_account_update.py new file mode 100644 index 0000000..fdd9a2f --- /dev/null +++ b/example/account/sub_account_update.py @@ -0,0 +1,15 @@ +from huobi.client.account import AccountClient +from huobi.constant import * + + +def callback(account_change_event: 'AccountChangeEvent'): + account_change_event.print_object() + print() + + +account_client = AccountClient(api_key=g_api_key, + secret_key=g_secret_key, + init_log=True) +# account_client.sub_account_update(AccountBalanceMode.TOTAL, callback) +account_client.sub_account_update(AccountBalanceMode.BALANCE, callback) + diff --git a/example/algo/get_open_orders.py b/example/algo/get_open_orders.py new file mode 100644 index 0000000..17161c1 --- /dev/null +++ b/example/algo/get_open_orders.py @@ -0,0 +1,10 @@ +from huobi.client.algo import AlgoClient +from huobi.constant import * +from huobi.utils import * + +symbol_test = "adausdt" +account_id = g_account_id + +algo_client = AlgoClient(api_key=g_api_key, secret_key=g_secret_key) +result = algo_client.get_open_orders() +LogInfo.output_list(result) diff --git a/example/algo/get_order.py b/example/algo/get_order.py new file mode 100644 index 0000000..d606d92 --- /dev/null +++ b/example/algo/get_order.py @@ -0,0 +1,10 @@ +from huobi.client.algo import AlgoClient +from huobi.constant import * + +account_id = g_account_id +client_order_id = "test002" + +# get specific order by clientOrderId +algo_client = AlgoClient(api_key=g_api_key, secret_key=g_secret_key) +result = algo_client.get_order(client_order_id) +result.print_object() diff --git a/example/algo/get_order_history.py b/example/algo/get_order_history.py new file mode 100644 index 0000000..896d5f9 --- /dev/null +++ b/example/algo/get_order_history.py @@ -0,0 +1,10 @@ +from huobi.client.algo import AlgoClient +from huobi.constant import * +from huobi.utils import * + +symbol_test = "adausdt" +account_id = g_account_id + +algo_client = AlgoClient(api_key=g_api_key, secret_key=g_secret_key) +result = algo_client.get_order_history(symbol_test, AlgoOrderStatus.TRIGGERED) +LogInfo.output_list(result) diff --git a/example/algo/post_cancel_algo_orders.py b/example/algo/post_cancel_algo_orders.py new file mode 100644 index 0000000..272ba81 --- /dev/null +++ b/example/algo/post_cancel_algo_orders.py @@ -0,0 +1,17 @@ +from huobi.client.algo import AlgoClient +from huobi.constant import * +from huobi.utils import * + +symbol_test = "adausdt" +account_id = g_account_id + +orders_to_cancel = ["test003", "test001"] +algo_client = AlgoClient(api_key=g_api_key, secret_key=g_secret_key) +result = algo_client.cancel_orders(orders_to_cancel) +result.print_object() + +# order_id = algo_client.create_order(symbol=symbol_test, account_id=account_id, order_type=OrderType.BUY_MARKET, source=OrderSource.API, amount=5.0, price=1.292) +# LogInfo.output("created order id : {id}".format(id=order_id)) +# +# order_id = algo_client.create_order(symbol=symbol_test, account_id=account_id, order_type=OrderType.SELL_MARKET, source=OrderSource.API, amount=1.77, price=None) +# LogInfo.output("created order id : {id}".format(id=order_id)) diff --git a/example/algo/post_create_algo_order.py b/example/algo/post_create_algo_order.py new file mode 100644 index 0000000..c14670a --- /dev/null +++ b/example/algo/post_create_algo_order.py @@ -0,0 +1,12 @@ +from huobi.client.algo import AlgoClient +from huobi.constant import * +from huobi.utils import * + +symbol_test = "adausdt" +account_id = g_account_id + +algo_client = AlgoClient(api_key=g_api_key, secret_key=g_secret_key) +order_id = algo_client.create_order(symbol=symbol_test, account_id=account_id, order_side=OrderSide.BUY, + order_type=AlgoOrderType.LIMIT, order_size=65, order_price=0.08, stop_price=0.085, + client_order_id="test004") +LogInfo.output("created order id : {id}".format(id=order_id)) diff --git a/example/api_key.py b/example/api_key.py new file mode 100644 index 0000000..7ca78bb --- /dev/null +++ b/example/api_key.py @@ -0,0 +1,3 @@ +g_api_key = "e7f941dd-4167913c-mjlpdje3ld-f97b3" +g_secret_key = "f06af3d0-eaf3e4ff-9f0e931f-43aec" +g_account_id = 18788142 \ No newline at end of file diff --git a/example/etf/get_etf_swap_config.py b/example/etf/get_etf_swap_config.py new file mode 100644 index 0000000..4d88659 --- /dev/null +++ b/example/etf/get_etf_swap_config.py @@ -0,0 +1,16 @@ +from huobi.client.etf import EtfClient + + +etf_client = EtfClient() +etf_config = etf_client.get_etf_swap_config("hb10") +etf_config.print_object() + + + + + + + + + + diff --git a/example/etf/get_etf_swap_list.py b/example/etf/get_etf_swap_list.py new file mode 100644 index 0000000..2e355d7 --- /dev/null +++ b/example/etf/get_etf_swap_list.py @@ -0,0 +1,24 @@ +from huobi.client.etf import EtfClient +from huobi.constant import * +from huobi.utils import * + + +etf_client = EtfClient(api_key=g_api_key, secret_key=g_secret_key) +etf_list = etf_client.get_etf_swap_list(etf_name="hb10", offset=0, size=20) +LogInfo.output_list(etf_list) + + + + + + + + + + + + + + + + diff --git a/example/etf/post_etf_swap_in.py b/example/etf/post_etf_swap_in.py new file mode 100644 index 0000000..a1df864 --- /dev/null +++ b/example/etf/post_etf_swap_in.py @@ -0,0 +1,23 @@ +from huobi.client.etf import EtfClient +from huobi.constant import * + +etf_client = EtfClient(api_key=g_api_key, secret_key=g_secret_key) +etf_swap_ret = etf_client.post_etf_swap_in("hb10", 1000) +etf_swap_ret.print_object() + + + + + + + + + + + + + + + + + diff --git a/example/etf/post_etf_swap_out.py b/example/etf/post_etf_swap_out.py new file mode 100644 index 0000000..7ebe90b --- /dev/null +++ b/example/etf/post_etf_swap_out.py @@ -0,0 +1,22 @@ +from huobi.client.etf import EtfClient +from huobi.constant import * + +etf_client = EtfClient(api_key=g_api_key, secret_key=g_secret_key) +etf_swap_ret = etf_client.post_etf_swap_out("hb10", 1000) +etf_swap_ret.print_object() + + + + + + + + + + + + + + + + diff --git a/example/generic/get_exchange_currencies.py b/example/generic/get_exchange_currencies.py new file mode 100644 index 0000000..9f055bc --- /dev/null +++ b/example/generic/get_exchange_currencies.py @@ -0,0 +1,9 @@ +from huobi.client.generic import GenericClient +from huobi.utils import * + + +generic_client = GenericClient() +list_obj = generic_client.get_exchange_currencies() +LogInfo.output("---- Supported currency ----") +for currency in list_obj: + LogInfo.output(currency) \ No newline at end of file diff --git a/example/generic/get_exchange_info.py b/example/generic/get_exchange_info.py new file mode 100644 index 0000000..4897240 --- /dev/null +++ b/example/generic/get_exchange_info.py @@ -0,0 +1,13 @@ +from huobi.client.generic import GenericClient +from huobi.utils import * + + +generic_client = GenericClient() +list_obj = generic_client.get_exchange_info() +LogInfo.output("---- Supported symbols ----") +for symbol in list_obj.symbol_list: + LogInfo.output(symbol.symbol) + +LogInfo.output("---- Supported currencies ----"); +for currency in list_obj.currencies: + LogInfo.output(currency) diff --git a/example/generic/get_exchange_symbols.py b/example/generic/get_exchange_symbols.py new file mode 100644 index 0000000..7c996c4 --- /dev/null +++ b/example/generic/get_exchange_symbols.py @@ -0,0 +1,13 @@ +from huobi.client.generic import GenericClient +from huobi.utils import * + + +generic_client = GenericClient() +list_obj = generic_client.get_exchange_symbols() +if len(list_obj): + for idx, row in enumerate(list_obj): + LogInfo.output("------- number " + str(idx) + " -------") + row.print_object() + + + diff --git a/example/generic/get_exchange_timestamp.py b/example/generic/get_exchange_timestamp.py new file mode 100644 index 0000000..6ffad7c --- /dev/null +++ b/example/generic/get_exchange_timestamp.py @@ -0,0 +1,8 @@ +from huobi.client.generic import GenericClient +from huobi.utils import * + + +generic_client = GenericClient() +ts = generic_client.get_exchange_timestamp() +LogInfo.output(ts) + diff --git a/example/generic/get_market_status.py b/example/generic/get_market_status.py new file mode 100644 index 0000000..2b8c2ff --- /dev/null +++ b/example/generic/get_market_status.py @@ -0,0 +1,8 @@ +from huobi.client.generic import GenericClient + +generic_client = GenericClient() +market_status = generic_client.get_market_status() +print(market_status) + + + diff --git a/example/generic/get_reference_currencies.py b/example/generic/get_reference_currencies.py new file mode 100644 index 0000000..1d0379a --- /dev/null +++ b/example/generic/get_reference_currencies.py @@ -0,0 +1,13 @@ +from huobi.client.generic import GenericClient +from huobi.utils import * + + +generic_client = GenericClient() + +list_obj = generic_client.get_reference_currencies() +LogInfo.output_list(list_obj) + + +list_obj = generic_client.get_reference_currencies(currency="usdt") +LogInfo.output_list(list_obj) + diff --git a/example/generic/get_system_status.py b/example/generic/get_system_status.py new file mode 100644 index 0000000..7f315db --- /dev/null +++ b/example/generic/get_system_status.py @@ -0,0 +1,12 @@ +from huobi.client.generic import GenericClient + + +""" +GET https://status.huobigroup.com/api/v2/summary.json +""" +generic_client = GenericClient() +system_status = generic_client.get_system_status() +print(system_status) + + + diff --git a/example/margin/get_cross_margin_account_balance.py b/example/margin/get_cross_margin_account_balance.py new file mode 100644 index 0000000..8e6ccc1 --- /dev/null +++ b/example/margin/get_cross_margin_account_balance.py @@ -0,0 +1,9 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * + + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +account_balance = margin_client.get_cross_margin_account_balance() +account_balance.print_object() + + diff --git a/example/margin/get_cross_margin_loan_info.py b/example/margin/get_cross_margin_loan_info.py new file mode 100644 index 0000000..0b626f3 --- /dev/null +++ b/example/margin/get_cross_margin_loan_info.py @@ -0,0 +1,8 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = margin_client.get_cross_margin_loan_info() +LogInfo.output_list(list_obj) + diff --git a/example/margin/get_cross_margin_loan_orders.py b/example/margin/get_cross_margin_loan_orders.py new file mode 100644 index 0000000..8b9ced7 --- /dev/null +++ b/example/margin/get_cross_margin_loan_orders.py @@ -0,0 +1,16 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) + +# no filter +list_obj = margin_client.get_cross_margin_loan_orders() +LogInfo.output_list(list_obj) + +# filter by state +list_obj = margin_client.get_cross_margin_loan_orders(state=LoanOrderState.ACCRUAL) +LogInfo.output_list(list_obj) + + diff --git a/example/margin/get_general_repayment_loan_records.py b/example/margin/get_general_repayment_loan_records.py new file mode 100644 index 0000000..d37c0c2 --- /dev/null +++ b/example/margin/get_general_repayment_loan_records.py @@ -0,0 +1,12 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import LogInfo + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) + +list_result = margin_client.get_general_repayment_loan_records(limit=10) +LogInfo.output_list(list_result) + + + + diff --git a/example/margin/get_margin_account_balance.py b/example/margin/get_margin_account_balance.py new file mode 100644 index 0000000..5bf1a7f --- /dev/null +++ b/example/margin/get_margin_account_balance.py @@ -0,0 +1,8 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = margin_client.get_margin_account_balance(symbol="eosusdt") +LogInfo.output_list(list_obj) + diff --git a/example/margin/get_margin_loan_info.py b/example/margin/get_margin_loan_info.py new file mode 100644 index 0000000..ff519f9 --- /dev/null +++ b/example/margin/get_margin_loan_info.py @@ -0,0 +1,8 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = margin_client.get_margin_loan_info(symbols="btcusdt,ethusdt,eosusdt") +LogInfo.output_list(list_obj) + diff --git a/example/margin/get_margin_loan_orders.py b/example/margin/get_margin_loan_orders.py new file mode 100644 index 0000000..0ae699f --- /dev/null +++ b/example/margin/get_margin_loan_orders.py @@ -0,0 +1,8 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = margin_client.get_margin_loan_orders(symbol="eosusdt") +LogInfo.output_list(list_obj) + diff --git a/example/margin/post_cross_margin_create_loan_orders.py b/example/margin/post_cross_margin_create_loan_orders.py new file mode 100644 index 0000000..741409c --- /dev/null +++ b/example/margin/post_cross_margin_create_loan_orders.py @@ -0,0 +1,29 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + + +amount = 100 +currency = "usdt" + + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +# order_id = margin_client.post_cross_margin_create_loan_orders(currency=currency, amount=amount) +# LogInfo.output("step 1: create loan order {id}".format(id=order_id)) + +# to check not clearing loan orders +list_obj = margin_client.get_cross_margin_loan_orders(currency=currency, state=LoanOrderState.ACCRUAL) +LogInfo.output("step 2: loaning order information ") +LogInfo.output_list(list_obj) + +if list_obj and len(list_obj): + for loan_order in list_obj: + # pay attention to Scientific Notation + repay_amount = float(loan_order.loan_balance) + float(loan_order.interest_balance) + LogInfo.output("repay loan order {id}, repay amount : {amount}".format(id=loan_order.id, amount=repay_amount)) + result = margin_client.post_cross_margin_loan_order_repay(order_id=loan_order.id, amount=repay_amount) + LogInfo.output("step 3: repay loan order {id}, status : {status}, repay amount : {amount}".format(id=loan_order.id, status=result, amount=repay_amount)) + +list_obj = margin_client.get_cross_margin_loan_orders(currency=currency, state=LoanOrderState.ACCRUAL) +LogInfo.output("step 4: loaning order history ") +LogInfo.output_list(list_obj) diff --git a/example/margin/post_cross_margin_loan_order_repay.py b/example/margin/post_cross_margin_loan_order_repay.py new file mode 100644 index 0000000..5f0a1d3 --- /dev/null +++ b/example/margin/post_cross_margin_loan_order_repay.py @@ -0,0 +1,19 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) + +# to check not clearing loan orders +list_obj = margin_client.get_cross_margin_loan_orders(currency="usdt", state=LoanOrderState.ACCRUAL) +LogInfo.output_list(list_obj) + +if list_obj and len(list_obj): + for loan_order in list_obj: + repay_amount = float(loan_order.loan_balance) + float(loan_order.interest_balance) + LogInfo.output("repay loan order {id} and amount {amount}".format(id=loan_order.id, amount=repay_amount)) + order_id = margin_client.post_cross_margin_loan_order_repay(order_id=loan_order.id, amount=repay_amount) + +# to check not clearing loan orders +list_obj = margin_client.get_cross_margin_loan_orders(currency="usdt", state=LoanOrderState.ACCRUAL) +LogInfo.output_list(list_obj) \ No newline at end of file diff --git a/example/margin/post_cross_margin_transfer.py b/example/margin/post_cross_margin_transfer.py new file mode 100644 index 0000000..44ebdc4 --- /dev/null +++ b/example/margin/post_cross_margin_transfer.py @@ -0,0 +1,12 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +transfer_id = margin_client.post_cross_margin_transfer_in(currency="eos", amount=5) +LogInfo.output(transfer_id) + +transfer_id = margin_client.post_cross_margin_transfer_out(currency="eos", amount=5) +LogInfo.output(transfer_id) + + diff --git a/example/margin/post_general_repay_loan.py b/example/margin/post_general_repay_loan.py new file mode 100644 index 0000000..cf2f918 --- /dev/null +++ b/example/margin/post_general_repay_loan.py @@ -0,0 +1,8 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import LogInfo + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) + +list_result = margin_client.post_general_repay_loan(account_id=g_account_id, currency="usdt", amount=1) +LogInfo.output_list(list_result) diff --git a/example/margin/post_margin_create_order.py b/example/margin/post_margin_create_order.py new file mode 100644 index 0000000..69c0efe --- /dev/null +++ b/example/margin/post_margin_create_order.py @@ -0,0 +1,25 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * +import time + +loan_amount = 100 +interest_amount = 0.004083 + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +loan_id = margin_client.post_create_margin_order(symbol="eosusdt", currency="usdt", amount=loan_amount) +LogInfo.output("step 1: loan id : {id}".format(id=loan_id)) + +time.sleep(2) + +repay_id = margin_client.post_repay_margin_order(loan_id=loan_id, amount=loan_amount + interest_amount) +LogInfo.output("step 2: repay id : {id}".format(id=repay_id)) + +list_obj = margin_client.get_margin_loan_orders(symbol="eosusdt", states=LoanOrderState.ACCRUAL) +LogInfo.output("step 3: loaning order information") +LogInfo.output_list(list_obj) + + +list_obj = margin_client.get_margin_loan_orders(symbol="eosusdt") +LogInfo.output("step 4: loan order history") +LogInfo.output_list(list_obj) \ No newline at end of file diff --git a/example/margin/post_margin_repay_order.py b/example/margin/post_margin_repay_order.py new file mode 100644 index 0000000..386f29a --- /dev/null +++ b/example/margin/post_margin_repay_order.py @@ -0,0 +1,8 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +transfer_id = margin_client.post_repay_margin_order(loan_id=7440184, amount=100.004083) +LogInfo.output("transfer id : {id}".format(id=transfer_id)) + diff --git a/example/margin/post_margin_transfer_in.py b/example/margin/post_margin_transfer_in.py new file mode 100644 index 0000000..5b8f787 --- /dev/null +++ b/example/margin/post_margin_transfer_in.py @@ -0,0 +1,8 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +transfer_id = margin_client.post_transfer_in_margin(symbol="eosusdt", currency="usdt", amount=20) +LogInfo.output("transfer id : {id}".format(id=transfer_id)) + diff --git a/example/margin/post_margin_transfer_out.py b/example/margin/post_margin_transfer_out.py new file mode 100644 index 0000000..6d23f81 --- /dev/null +++ b/example/margin/post_margin_transfer_out.py @@ -0,0 +1,7 @@ +from huobi.client.margin import MarginClient +from huobi.constant import * +from huobi.utils import * + +margin_client = MarginClient(api_key=g_api_key, secret_key=g_secret_key) +transfer_id = margin_client.post_transfer_out_margin(symbol="eosusdt", currency="usdt", amount=20) +LogInfo.output("transfer id : {id}".format(id=transfer_id)) diff --git a/example/market/get_candlestick.py b/example/market/get_candlestick.py new file mode 100644 index 0000000..9b22f16 --- /dev/null +++ b/example/market/get_candlestick.py @@ -0,0 +1,26 @@ +from huobi.client.market import MarketClient +from huobi.constant import * +from huobi.utils import * + +market_client = MarketClient(init_log=True) +interval = CandlestickInterval.MIN5 +symbol = "ethusdt" +list_obj = market_client.get_candlestick(symbol, interval, 10) +LogInfo.output("---- {interval} candlestick for {symbol} ----".format(interval=interval, symbol=symbol)) +LogInfo.output_list(list_obj) + + + + + + + + + + + + + + + + diff --git a/example/market/get_history_trade.py b/example/market/get_history_trade.py new file mode 100644 index 0000000..e039c34 --- /dev/null +++ b/example/market/get_history_trade.py @@ -0,0 +1,5 @@ +from huobi.client.market import MarketClient +from huobi.utils import * +market_client = MarketClient() +list_obj = market_client.get_history_trade("btcusdt", 6) +LogInfo.output_list(list_obj) diff --git a/example/market/get_market_detail.py b/example/market/get_market_detail.py new file mode 100644 index 0000000..b7f1307 --- /dev/null +++ b/example/market/get_market_detail.py @@ -0,0 +1,8 @@ +from huobi.client.market import MarketClient + +market_client = MarketClient() +obj = market_client.get_market_detail("btcusdt") +obj.print_object() + + + diff --git a/example/market/get_market_detail_merged.py b/example/market/get_market_detail_merged.py new file mode 100644 index 0000000..a247be0 --- /dev/null +++ b/example/market/get_market_detail_merged.py @@ -0,0 +1,9 @@ +from huobi.client.market import MarketClient +from huobi.constant import * + +market_client = MarketClient() +obj = market_client.get_market_detail_merged("btcusdt") +obj.print_object() + + + diff --git a/example/market/get_market_tickers.py b/example/market/get_market_tickers.py new file mode 100644 index 0000000..4270436 --- /dev/null +++ b/example/market/get_market_tickers.py @@ -0,0 +1,23 @@ +from huobi.client.market import MarketClient +from huobi.constant import * +from huobi.utils import * + +market_client = MarketClient(init_log=True) +list_obj = market_client.get_market_tickers() +LogInfo.output_list(list_obj) + + + + + + + + + + + + + + + + diff --git a/example/market/get_market_trade.py b/example/market/get_market_trade.py new file mode 100644 index 0000000..6f4d89d --- /dev/null +++ b/example/market/get_market_trade.py @@ -0,0 +1,18 @@ +from huobi.client.market import MarketClient +from huobi.utils import * + +market_client = MarketClient() +list_obj = market_client.get_market_trade(symbol="eosusdt") +LogInfo.output_list(list_obj) + + + + + + + + + + + + diff --git a/example/market/get_pricedepth.py b/example/market/get_pricedepth.py new file mode 100644 index 0000000..a15468c --- /dev/null +++ b/example/market/get_pricedepth.py @@ -0,0 +1,21 @@ + +from huobi.client.market import MarketClient +from huobi.utils import * + + +market_client = MarketClient() +symbol = "btcusdt" +depth_size = 6 +depth = market_client.get_pricedepth(symbol, DepthStep.STEP0, depth_size) +LogInfo.output("---- Top {size} bids ----".format(size=len(depth.bids))) +i = 0 +for entry in depth.bids: + i = i + 1 + LogInfo.output(str(i) + ": price: " + str(entry.price) + ", amount: " + str(entry.amount)) + +LogInfo.output("---- Top {size} asks ----".format(size=len(depth.asks))) + +i = 0 +for entry in depth.asks: + i = i + 1 + LogInfo.output(str(i) + ": price: " + str(entry.price) + ", amount: " + str(entry.amount)) \ No newline at end of file diff --git a/example/market/req_candlestick.py b/example/market/req_candlestick.py new file mode 100644 index 0000000..bee2823 --- /dev/null +++ b/example/market/req_candlestick.py @@ -0,0 +1,16 @@ +from huobi.client.market import MarketClient +from huobi.constant import * +from huobi.exception.huobi_api_exception import HuobiApiException + +def callback(candlestick_req: 'CandlestickReq'): + candlestick_req.print_object() + +def error(e: 'HuobiApiException'): + print(e.error_code + e.error_message) + +sub_client = MarketClient(init_log=True) +#sub_client.request_candlestick_event("btcusdt", CandlestickInterval.MIN1, callback, from_ts_second=None, end_ts_second=None, error_handler=None) +#sub_client.request_candlestick_event("btcusdt", CandlestickInterval.MIN1, callback, from_ts_second=1571124360, end_ts_second=1571129820) +#sub_client.request_candlestick_event("btcusdt", CandlestickInterval.MIN1, callback, from_ts_second=1569361140, end_ts_second=0) +#sub_client.request_candlestick_event("btcusdt", CandlestickInterval.MIN1, callback, from_ts_second=1569379980) +sub_client.req_candlestick("btcusdt", CandlestickInterval.MIN1, callback) diff --git a/example/market/req_market_detail.py b/example/market/req_market_detail.py new file mode 100644 index 0000000..bc63b99 --- /dev/null +++ b/example/market/req_market_detail.py @@ -0,0 +1,12 @@ +from huobi.client.market import MarketClient +from huobi.model.market import * + + + +def callback(obj_event: 'MarketDetailReq'): + obj_event.print_object() + print() + + +sub_client = MarketClient() +sub_client.req_market_detail("btcusdt", callback) diff --git a/example/market/req_mbp.py b/example/market/req_mbp.py new file mode 100644 index 0000000..b2c3232 --- /dev/null +++ b/example/market/req_mbp.py @@ -0,0 +1,14 @@ +from huobi.client.market import MarketClient +from huobi.constant import * + + +def callback(mbp_event: 'MbpIncreaseEvent'): + mbp_event.print_object() + + +def error(e: 'HuobiApiException'): + print(e.error_code + e.error_message) + + +market_client = MarketClient(init_log=True) +market_client.req_mbp("btcusdt", MbpLevel.MBP5, callback, error) diff --git a/example/market/req_pricedepth.py b/example/market/req_pricedepth.py new file mode 100644 index 0000000..3cc938a --- /dev/null +++ b/example/market/req_pricedepth.py @@ -0,0 +1,15 @@ + +from huobi.client.market import MarketClient +from huobi.constant import DepthStep + + +def callback(price_depth_req: 'PriceDepthReq'): + price_depth_req.print_object() + + +def error(e: 'HuobiApiException'): + print(e.error_code + e.error_message) + + +sub_client = MarketClient() +sub_client.req_pricedepth("btcusdt", DepthStep.STEP0, callback, error) diff --git a/example/market/req_trade_detail.py b/example/market/req_trade_detail.py new file mode 100644 index 0000000..cc67fe9 --- /dev/null +++ b/example/market/req_trade_detail.py @@ -0,0 +1,15 @@ +from huobi.client.market import MarketClient + + + +def callback(trade_req: 'TradeDetailReq'): + print("---- trade_event: ----") + trade_req.print_object() + print() + + + +market_client = MarketClient() +market_client.req_trade_detail("btcusdt,eosusdt", callback) + + diff --git a/example/market/sub_candlestick.py b/example/market/sub_candlestick.py new file mode 100644 index 0000000..732b44c --- /dev/null +++ b/example/market/sub_candlestick.py @@ -0,0 +1,18 @@ + +from huobi.client.market import MarketClient +from huobi.constant import * +from huobi.exception.huobi_api_exception import HuobiApiException +from huobi.model.market.candlestick_event import CandlestickEvent + + +def callback(candlestick_event: 'CandlestickEvent'): + candlestick_event.print_object() + print("\n") + + +def error(e: 'HuobiApiException'): + print(e.error_code + e.error_message) + +market_client = MarketClient() +market_client.sub_candlestick("btcusdt,ethusdt", CandlestickInterval.MIN1, callback, error) + diff --git a/example/market/sub_market_detail.py b/example/market/sub_market_detail.py new file mode 100644 index 0000000..a567fba --- /dev/null +++ b/example/market/sub_market_detail.py @@ -0,0 +1,11 @@ +from huobi.client.market import MarketClient +from huobi.model.market import * + + +def callback(obj_event: 'MarketDetailEvent'): + obj_event.print_object() + print() + + +market_client = MarketClient() +market_client.sub_market_detail("btcusdt", callback) \ No newline at end of file diff --git a/example/market/sub_mbp_full.py b/example/market/sub_mbp_full.py new file mode 100644 index 0000000..e1cd3cb --- /dev/null +++ b/example/market/sub_mbp_full.py @@ -0,0 +1,14 @@ +from huobi.client.market import MarketClient +from huobi.constant import * + + +def callback(mbp_event: 'MbpFullEvent'): + mbp_event.print_object() + + +def error(e: 'HuobiApiException'): + print(e.error_code + e.error_message) + + +market_client = MarketClient(init_log=True) +market_client.sub_mbp_full("btcusdt,eosusdt", MbpLevel.MBP5, callback, error) diff --git a/example/market/sub_mbp_increase.py b/example/market/sub_mbp_increase.py new file mode 100644 index 0000000..044781e --- /dev/null +++ b/example/market/sub_mbp_increase.py @@ -0,0 +1,14 @@ +from huobi.client.market import MarketClient +from huobi.constant import * + + +def callback(mbp_event: 'MbpIncreaseEvent'): + mbp_event.print_object() + + +def error(e: 'HuobiApiException'): + print(e.error_code + e.error_message) + + +market_client = MarketClient(init_log=True) +market_client.sub_mbp_increase("btcusdt,eosusdt", MbpLevel.MBP5, callback, error) diff --git a/example/market/sub_pricedepth.py b/example/market/sub_pricedepth.py new file mode 100644 index 0000000..bca45d9 --- /dev/null +++ b/example/market/sub_pricedepth.py @@ -0,0 +1,18 @@ + +from huobi.client.market import MarketClient +from huobi.constant import * + + + +def callback(price_depth_event: 'PriceDepthEvent'): + price_depth_event.print_object() + + +def error(e: 'HuobiApiException'): + print(e.error_code + e.error_message) + + +market_client = MarketClient() +market_client.sub_pricedepth("btcusdt", DepthStep.STEP0, callback, error) +market_client.sub_pricedepth("eosusdt", DepthStep.STEP0, callback, error) +market_client.sub_pricedepth("ethusdt", DepthStep.STEP0, callback, error) diff --git a/example/market/sub_pricedepth_bbo.py b/example/market/sub_pricedepth_bbo.py new file mode 100644 index 0000000..f1a9704 --- /dev/null +++ b/example/market/sub_pricedepth_bbo.py @@ -0,0 +1,15 @@ + +from huobi.client.market import MarketClient + + +def callback(price_depth_event: 'PriceDepthBboEvent'): + price_depth_event.print_object() + print() + + + +def error(e: 'HuobiApiException'): + print(e.error_code + e.error_message) + +market_client = MarketClient() +market_client.sub_pricedepth_bbo("btcusdt", callback, error) diff --git a/example/market/sub_trade_detail.py b/example/market/sub_trade_detail.py new file mode 100644 index 0000000..a80e3a9 --- /dev/null +++ b/example/market/sub_trade_detail.py @@ -0,0 +1,13 @@ +from huobi.client.market import MarketClient + + +def callback(trade_event: 'TradeDetailEvent'): + print("---- trade_event: ----") + trade_event.print_object() + print() + + + +market_client = MarketClient(init_log=True) +market_client.sub_trade_detail("btcusdt,eosusdt", callback) + diff --git a/example/subuser/get_uid.py b/example/subuser/get_uid.py new file mode 100644 index 0000000..9bea859 --- /dev/null +++ b/example/subuser/get_uid.py @@ -0,0 +1,10 @@ +from huobi.client.subuser import SubuserClient +from huobi.constant import * +from huobi.utils import * + +from example.api_key import * + + +subuser_client = SubuserClient(api_key=g_api_key, secret_key=g_secret_key) +uid = subuser_client.get_uid() +LogInfo.output(uid) diff --git a/example/subuser/get_user_apikey_info.py b/example/subuser/get_user_apikey_info.py new file mode 100644 index 0000000..870dd67 --- /dev/null +++ b/example/subuser/get_user_apikey_info.py @@ -0,0 +1,7 @@ +from huobi.client.subuser import SubuserClient +from huobi.constant import * +from huobi.utils import * + +subuser_client = SubuserClient(api_key=g_api_key, secret_key=g_secret_key) +apikey_info = subuser_client.get_user_apikey_info(122946475) +LogInfo.output_list(apikey_info) diff --git a/example/subuser/post_create_subuser.py b/example/subuser/post_create_subuser.py new file mode 100644 index 0000000..49b3909 --- /dev/null +++ b/example/subuser/post_create_subuser.py @@ -0,0 +1,17 @@ +from huobi.client.subuser import SubuserClient +from huobi.constant import * +from huobi.utils import * +import string +import random + +subuser_client = SubuserClient(api_key=g_api_key, secret_key=g_secret_key) +userName = ''.join(random.choices(string.ascii_uppercase + string.digits, k=7)) +params = {"userList": [ + { + "userName": userName, + "note": "huobi" + } +]} + +userList = subuser_client.post_create_subuser(params) +LogInfo.output_list(userList) diff --git a/example/subuser/post_set_subuser_transferability.py b/example/subuser/post_set_subuser_transferability.py new file mode 100644 index 0000000..39f6f7b --- /dev/null +++ b/example/subuser/post_set_subuser_transferability.py @@ -0,0 +1,9 @@ +from huobi.client.subuser import SubuserClient +from huobi.constant import * +from huobi.utils import * + +subuser_client = SubuserClient(api_key=g_api_key, secret_key=g_secret_key) +sub_uids = '122946475' + +transferability_result = subuser_client.post_set_subuser_transferability(sub_uids, False) +LogInfo.output_list(transferability_result) diff --git a/example/subuser/post_subuser_apikey_deletion.py b/example/subuser/post_subuser_apikey_deletion.py new file mode 100644 index 0000000..b493619 --- /dev/null +++ b/example/subuser/post_subuser_apikey_deletion.py @@ -0,0 +1,10 @@ +from huobi.client.subuser import SubuserClient +from huobi.constant import * +from huobi.utils import * + +subuser_client = SubuserClient(api_key=g_api_key, secret_key=g_secret_key) +sub_uid = '122946475' +access_key = '7ab679d7-b9fee8ed-9cd4cd8a-bgbfh5tv3f' + +result = subuser_client.post_subuser_apikey_deletion(sub_uid, access_key) +LogInfo.output(result) diff --git a/example/subuser/post_subuser_apikey_generation.py b/example/subuser/post_subuser_apikey_generation.py new file mode 100644 index 0000000..f2d65b2 --- /dev/null +++ b/example/subuser/post_subuser_apikey_generation.py @@ -0,0 +1,13 @@ +from huobi.client.subuser import SubuserClient +from huobi.constant import * + +subuser_client = SubuserClient(api_key=g_api_key, secret_key=g_secret_key) + +otp_token = '746316' +sub_uid = 122946475 +note = "huobi_subuser" +permission = 'readOnly' +# ip_addresses = '' + +result = subuser_client.post_subuser_apikey_generate(otp_token, sub_uid, note, permission) +result.print_object() diff --git a/example/subuser/post_subuser_apikey_modification.py b/example/subuser/post_subuser_apikey_modification.py new file mode 100644 index 0000000..63a1c51 --- /dev/null +++ b/example/subuser/post_subuser_apikey_modification.py @@ -0,0 +1,12 @@ +from huobi.client.subuser import SubuserClient +from huobi.constant import * + +subuser_client = SubuserClient(api_key=g_api_key, secret_key=g_secret_key) + +subUid = 122946475 +access_key = "abc" +note = "test" +permission = 'readOnly,trade' + +result = subuser_client.post_subuser_apikey_modification(subUid, access_key, permission=permission, note=note) +result.print_object() diff --git a/example/subuser/post_trade_market.py b/example/subuser/post_trade_market.py new file mode 100644 index 0000000..dd0df19 --- /dev/null +++ b/example/subuser/post_trade_market.py @@ -0,0 +1,12 @@ +from huobi.client.subuser import SubuserClient +from huobi.constant import * +from huobi.utils import * + +subuser_client = SubuserClient(api_key=g_api_key, secret_key=g_secret_key) + +subUids = '159284259' +accountType = SubuserTradePrivilegeType.MARGIN +activation = SubUserTradeStatus.DEACTIVATED + +subUserList = subuser_client.post_set_tradable_market(subUids, accountType, activation) +LogInfo.output_list(subUserList) diff --git a/example/trade/get_feerate.py b/example/trade/get_feerate.py new file mode 100644 index 0000000..d605e6e --- /dev/null +++ b/example/trade/get_feerate.py @@ -0,0 +1,10 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + +from example.api_key import * + + +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = trade_client.get_feerate(symbols="htusdt,btcusdt,eosusdt") +LogInfo.output_list(list_obj) \ No newline at end of file diff --git a/example/trade/get_history_orders_in_48hours.py b/example/trade/get_history_orders_in_48hours.py new file mode 100644 index 0000000..248e65a --- /dev/null +++ b/example/trade/get_history_orders_in_48hours.py @@ -0,0 +1,26 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + +from example.api_key import * + +symbol_test_list = ["linkusdt"] +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +for symbol_test in symbol_test_list: + list_obj = trade_client.get_history_orders(symbol=symbol_test, start_time=None, end_time=None, size=20, direct=None) + LogInfo.output_list(list_obj) + + + + + + + + + + + + + + + diff --git a/example/trade/get_matchresult.py b/example/trade/get_matchresult.py new file mode 100644 index 0000000..ece70cf --- /dev/null +++ b/example/trade/get_matchresult.py @@ -0,0 +1,11 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + + +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = trade_client.get_match_result(symbol="trxusdt", size=5, direct=QueryDirection.NEXT) +LogInfo.output_list(list_obj) + +list_obj = trade_client.get_match_result(symbol="eosusdt", size=5) +LogInfo.output_list(list_obj) \ No newline at end of file diff --git a/example/trade/get_matchresult_by_order_id.py b/example/trade/get_matchresult_by_order_id.py new file mode 100644 index 0000000..843680f --- /dev/null +++ b/example/trade/get_matchresult_by_order_id.py @@ -0,0 +1,11 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + + + +order_id_test = 87939085540 + +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = trade_client.get_match_results_by_order_id(order_id=order_id_test) +LogInfo.output_list(list_obj) \ No newline at end of file diff --git a/example/trade/get_open_orders.py b/example/trade/get_open_orders.py new file mode 100644 index 0000000..246dd97 --- /dev/null +++ b/example/trade/get_open_orders.py @@ -0,0 +1,25 @@ +from huobi.client.account import AccountClient +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + +from example.api_key import * + + +symbol = "btcusdt" + +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +account_client = AccountClient(api_key=g_api_key,secret_key=g_secret_key) + +account_spot = account_client.get_account_by_type_and_symbol(account_type=AccountType.SPOT, symbol=None) +account_id_test = account_spot.id + +direct_tmp = QueryDirection.NEXT +LogInfo.output("==============test case 1 for {direct}===============".format(direct=direct_tmp)) +list_obj = trade_client.get_open_orders(symbol=symbol, account_id=account_id_test, direct=direct_tmp) +LogInfo.output_list(list_obj) + +direct_tmp = QueryDirection.PREV +LogInfo.output("==============test case 2 for {direct}===============".format(direct=direct_tmp)) +list_obj = trade_client.get_open_orders(symbol=symbol, account_id=account_id_test, direct=direct_tmp) +LogInfo.output_list(list_obj) diff --git a/example/trade/get_order.py b/example/trade/get_order.py new file mode 100644 index 0000000..fedfe56 --- /dev/null +++ b/example/trade/get_order.py @@ -0,0 +1,10 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + +order_id = 25943298415466 +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +orderObj = trade_client.get_order(order_id=order_id) +LogInfo.output("======= get order by order id : {order_id} =======".format(order_id=order_id)) +orderObj.print_object() + diff --git a/example/trade/get_order_by_client_order_id.py b/example/trade/get_order_by_client_order_id.py new file mode 100644 index 0000000..9c5bdda --- /dev/null +++ b/example/trade/get_order_by_client_order_id.py @@ -0,0 +1,12 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + +client_order_id_test = "xxxxxx" # unique id in 24hours + +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +orderObj = trade_client.get_order_by_client_order_id(client_order_id=client_order_id_test) +LogInfo.output("======= get order by client order id : {client_id} =======".format(client_id=client_order_id_test)) +orderObj.print_object() + + diff --git a/example/trade/get_orders.py b/example/trade/get_orders.py new file mode 100644 index 0000000..7ddd666 --- /dev/null +++ b/example/trade/get_orders.py @@ -0,0 +1,27 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + +from example.api_key import * + +symbol = "linkusdt" +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = trade_client.get_orders(symbol=symbol, order_state=OrderState.FILLED, + order_type=OrderType.BUY_LIMIT, start_date=None, end_date=None, + start_id=None, size=None, direct=QueryDirection.PREV) + +LogInfo.output("===== step 1 ==== {symbol} {count} orders found".format(symbol=symbol, count=len(list_obj))) +LogInfo.output_list(list_obj) + +symbol = "linkusdt" +list_obj = trade_client.get_orders(symbol=symbol, order_state=OrderState.CANCELED, + order_type=OrderType.BUY_LIMIT, start_date="2021-01-21", end_date=None, + start_id=None, size=None, direct=QueryDirection.PREV) +LogInfo.output("===== step 2 ==== {symbol} {count} canceled buy limit orders found".format(symbol=symbol, count=len(list_obj))) +LogInfo.output_list(list_obj) + +list_obj = trade_client.get_orders(symbol=symbol, order_state=OrderState.FILLED, + order_type=None, start_date=None, end_date=None, + start_id=None, size=None, direct=QueryDirection.PREV) +LogInfo.output("===== step 3 ==== {symbol} {count} filled orders found".format(symbol=symbol, count=len(list_obj))) +LogInfo.output_list(list_obj) diff --git a/example/trade/get_transact_feerate.py b/example/trade/get_transact_feerate.py new file mode 100644 index 0000000..6563a7e --- /dev/null +++ b/example/trade/get_transact_feerate.py @@ -0,0 +1,9 @@ + +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + + +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +list_obj = trade_client.get_transact_feerate(symbols="htusdt,btcusdt,eosusdt") +LogInfo.output_list(list_obj) \ No newline at end of file diff --git a/example/trade/post_batch_cancel_open_order.py b/example/trade/post_batch_cancel_open_order.py new file mode 100644 index 0000000..464a703 --- /dev/null +++ b/example/trade/post_batch_cancel_open_order.py @@ -0,0 +1,8 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * + + +# cancel all the open orders under account +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +result = trade_client.cancel_open_orders(account_id=g_account_id) +result.print_object() diff --git a/example/trade/post_batch_cancel_order.py b/example/trade/post_batch_cancel_order.py new file mode 100644 index 0000000..ea2d982 --- /dev/null +++ b/example/trade/post_batch_cancel_order.py @@ -0,0 +1,21 @@ +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + + +trade_client = TradeClient(api_key=g_api_key, secret_key=g_secret_key) +symbol_test = "eosusdt" + +i = 0 +n = 3 +order_id_list = [] +while(i list: + from huobi.service.account.get_balance import GetBalanceService + """ + Get the balance of a all accounts. + + :return: The information of all account balance. + """ + server_url = get_default_server_url(self.__kwargs.get("url")) + tasks = [] + account_obj_map = {} + accounts = self.get_accounts() + account_balance_list = [] + account_balance_json_map = {} + for account_item in accounts: + account_obj_map[account_item.id] = account_item + balance_params = {"account-id": account_item.id} + balance_request = GetBalanceService(balance_params).get_request(**self.__kwargs) + balance_url = server_url + balance_request.url + tasks.append(asyncio.ensure_future( + self.async_get_account_balance(balance_url, account_item.id, account_balance_json_map))) + + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(asyncio.wait(tasks)) + except Exception as ee: + print(ee) + finally: + # loop.close() #for thread safe, the event loop can't be closed + pass + + for account_id, account_balance_json in account_balance_json_map.items(): + account_balance = AccountBalance.json_parse(account_balance_json.get("data", {})) + account_obj_tmp = account_obj_map.get(account_id, None) + account_balance.subtype = None if account_obj_tmp is None else account_obj_tmp.subtype + account_balance_list.append(account_balance) + + del account_balance_json_map + del tasks + return account_balance_list + + def get_account_balance_by_subuid(self, sub_uid): + """ + Get account balance of a sub-account. + + :param sub_uid: the specified sub account id to get balance for. + :return: the balance of a sub-account specified by sub-account uid. + """ + check_should_not_none(sub_uid, "sub-uid") + params = { + "sub-uid": sub_uid + } + from huobi.service.account.get_account_balance_by_subuid import GetAccountBalanceBySubUidService + return GetAccountBalanceBySubUidService(params).request(**self.__kwargs) + + def get_aggregated_subuser_balance(self): + """ + Get the aggregated balance of all sub-accounts of the current user. + + :return: The balance of all the sub-account aggregated. + """ + params = {} + from huobi.service.account.get_aggregate_subuser_balance import GetAggregateSubUserBalanceService + return GetAggregateSubUserBalanceService(params).request(**self.__kwargs) + + def transfer_between_parent_and_subuser(self, sub_uid: 'int', currency: 'str', amount: 'float', + transfer_type: 'TransferMasterType'): + """ + Transfer Asset between Parent and Sub Account. + + :param sub_uid: The target sub account uid to transfer to or from. (mandatory) + :param currency: The crypto currency to transfer. (mandatory) + :param amount: The amount of asset to transfer. (mandatory) + :param transfer_type: The type of transfer, see {@link TransferMasterType} (mandatory) + :return: The order id. + """ + check_currency(currency) + check_should_not_none(sub_uid, "sub-uid") + check_should_not_none(amount, "amount") + check_should_not_none(transfer_type, "type") + + params = { + "sub-uid": sub_uid, + "currency": currency, + "amount": amount, + "type": transfer_type + } + from huobi.service.account.post_subaccount_transfer import PostSubaccountTransferService + return PostSubaccountTransferService(params).request(**self.__kwargs) + + def sub_account_update(self, mode: 'AccountBalanceMode', callback, error_handler=None): + """ + Subscribe accounts update + + :param mode: subscribe mode + "0" : for balance + "1" : for available and balance + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + + check_should_not_none(callback, "callback") + if str(mode) == AccountBalanceMode.TOTAL: + mode = AccountBalanceMode.TOTAL + else: + mode = AccountBalanceMode.BALANCE + + params = { + "mode": mode, + } + + from huobi.service.account.sub_account_update_v2 import SubAccountUpdateV2Service + SubAccountUpdateV2Service(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_account_balance(self, callback, client_req_id=None, error_handler=None): + """ + Subscribe account changing event. If the balance is updated, server will send the data to client and onReceive in callback will be called. + + :param client_req_id: client request ID + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(account_event: 'AccountEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + + check_should_not_none(callback, "callback") + params = { + "client_req_id": client_req_id + } + + from huobi.service.account.req_account_balance import ReqAccountBalanceService + ReqAccountBalanceService(params).subscribe(callback, error_handler, **self.__kwargs) + + def transfer_between_futures_and_pro(self, currency: 'str', amount: 'float', + transfer_type: 'TransferFuturesPro') -> int: + """ + Transfer Asset between Futures and Contract. + + :param currency: The crypto currency to transfer. (mandatory) + :param amount: The amount of asset to transfer. (mandatory) + :param transfer_type: The type of transfer, need be "futures-to-pro" or "pro-to-futures" (mandatory) + :return: The order id. + """ + + check_currency(currency) + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + check_should_not_none(transfer_type, "transfer_type") + params = { + "currency": currency, + "amount": amount, + "type": transfer_type + } + + from huobi.service.account.post_futures_and_pro_transfer import PostTransferBetweenFuturesAndProService + return PostTransferBetweenFuturesAndProService(params).request(**self.__kwargs) + + def get_account_balance_by_subuid(self, sub_uid): + """ + Get account balance of a sub-account. + + :param sub_uid: the specified sub account id to get balance for. + :return: the balance of a sub-account specified by sub-account uid. + """ + check_should_not_none(sub_uid, "sub-uid") + params = { + "sub-uid": sub_uid + } + from huobi.service.account.get_account_balance_by_subuid import GetAccountBalanceBySubUidService + return GetAccountBalanceBySubUidService(params).request(**self.__kwargs) + + def get_account_history(self, account_id: 'int', currency: 'str' = None, + transact_types: 'str' = None, start_time: 'int' = None, end_time: 'int' = None, + sort: 'str' = None, size: 'int' = None): + """ + get account change record + :param account_id: account id (mandatory) + :param currency: currency as "btc,eth" (optional) + :param transact_types: see AccountTransactType, the value can be "trade" (交易),"etf"(ETF申购), "transact-fee"(交易手续费), "deduction"(手续费抵扣), "transfer"(划转), "credit"(借币), "liquidation"(清仓), "interest"(币息), "deposit"(充币),"withdraw"(提币), "withdraw-fee"(提币手续费), "exchange"(兑换), "other-types"(其他) (optional) + :param start_time&end_time: for time range to search (optional) + :param sort: see SortDesc, "asc" or "desc" (optional) + :param size: page size (optional) + + :return: account change record list. + """ + check_should_not_none(account_id, "account-id") + params = { + "account-id": account_id, + "currency": currency, + "transact-types": transact_types, + "start-time": start_time, + "end-time": end_time, + "sort": sort, + "size": size + } + from huobi.service.account.get_account_history import GetAccountHistoryService + return GetAccountHistoryService(params).request(**self.__kwargs) + + def post_sub_uid_management(self, sub_uid: 'int', action: 'str'): + """ + use to freeze or unfreeze the sub uid + + :return: user and status. + """ + + check_should_not_none(sub_uid, "subUid") + check_should_not_none(action, "action") + + params = { + "subUid": sub_uid, + "action": action + } + from huobi.service.account.post_sub_uid_management import PostSubUidManagementService + return PostSubUidManagementService(params).request(**self.__kwargs) + + def get_account_ledger(self, account_id: 'int', currency: 'str' = None, transact_types: 'str' = None, + start_time: 'int' = None, end_time: 'int' = None, sort: 'str' = None, limit: 'int' = None, + from_id: 'int' = None) -> list: + """ + get account ledger + :param account_id: account id (mandatory) + :param currency: currency as "btc,eth" (optional) + :param transact_types: see AccountTransactType, the value can be "trade" (交易),"etf"(ETF申购), "transact-fee"(交易手续费), "deduction"(手续费抵扣), "transfer"(划转), "credit"(借币), "liquidation"(清仓), "interest"(币息), "deposit"(充币),"withdraw"(提币), "withdraw-fee"(提币手续费), "exchange"(兑换), "other-types"(其他) (optional) + :param start_time&end_time: for time range to search (optional) + :param sort: see SortDesc, "asc" or "desc" (optional) + :param size: page size (optional) + :return: account ledger list. + """ + + check_should_not_none(account_id, "accountId") + + params = { + "accountId": account_id, + "currency": currency, + "transactTypes": transact_types, + "startTime": start_time, + "endTime": end_time, + "sort": sort, + "limit": limit, + "fromId": from_id + } + from huobi.service.account.get_account_ledger import GetAccountLedgerService + return GetAccountLedgerService(params).request(**self.__kwargs) + + def post_account_transfer(self, from_user: 'int', from_account_type: 'str', from_account: 'int', to_user: 'int', + to_account_type: 'str', to_account: 'int', currency: 'str', amount: 'str'): + check_should_not_none(from_user, "from-user") + check_should_not_none(from_account_type, "from-account-type") + check_should_not_none(from_account, "from_account") + check_should_not_none(to_user, "to-user") + check_should_not_none(to_account, "to-account") + check_should_not_none(to_account_type, "to-account") + check_should_not_none(currency, "currency") + + check_in_list(from_account_type, [AccountType.SPOT], "from_account_type") + check_in_list(to_account_type, [AccountType.SPOT], "to_account_type") + + params = { + "from-user": from_user, + "from-account-type": from_account_type, + "from-account": from_account, + "to-user": to_user, + "to-account-type": to_account_type, + "to-account": to_account, + "currency": currency, + "amount": amount + } + from huobi.service.account.post_account_transfer import PostAccountTransferService + return PostAccountTransferService(params).request(**self.__kwargs) + + def get_account_asset_valuation(self, account_type, valuation_currency: 'str' = None, sub_uid: 'str' = None): + check_should_not_none(account_type, "account-type") + + params = { + "accountType": account_type, + "valuationCurrency": valuation_currency.upper(), + "subUid": sub_uid + } + from huobi.service.account.get_account_asset_valuation import GetAccountAssetValuationService + return GetAccountAssetValuationService(params).request(**self.__kwargs) + + def get_account_point(self, sub_uid: 'str' = None): + params = { + "subUid": sub_uid + } + + from huobi.service.account.get_account_point import GetAccountPointService + return GetAccountPointService(params).request(**self.__kwargs) + + def post_point_transfer(self, from_uid: 'str', to_uid: 'str', group_id: 'str', amount: 'str'): + + params = { + "fromUid": from_uid, + "toUid": to_uid, + "groupId": group_id, + "amount": amount + } + + from huobi.service.account.post_point_transfer import PostPointTransferService + return PostPointTransferService(params).request(**self.__kwargs) diff --git a/huobi/client/algo.py b/huobi/client/algo.py new file mode 100644 index 0000000..b684096 --- /dev/null +++ b/huobi/client/algo.py @@ -0,0 +1,127 @@ +from huobi.model.algo import * +from huobi.utils.input_checker import * + + +class AlgoClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: to init logger + """ + self.__kwargs = kwargs + + def create_order(self, account_id: 'int', symbol: 'str', order_side: 'OrderSide', order_type: 'OrderType', + client_order_id: 'str', stop_price: 'str', order_price: 'str' = None, order_size: 'str' = None, + order_value: 'str' = None, time_in_force: 'str' = None, trailing_rate: 'str' = None) -> int: + """ + Make an algo order in huobi. + :param account_id: Account id. (mandatory) + :param symbol: The symbol, like "btcusdt". (mandatory) + :param order_side: the Order side, possible values: buy,sell. (mandatory) + :param order_type: The order type, possible values: limit, market. (mandatory) + :param stop_price: The stop price. (mandatory) + :param order_price: The limit price of limit order, only needed for limit order. (mandatory for buy-limit, sell-limit, buy-limit-maker and sell-limit-maker) + :param order_size: The amount of market order only + :param order_value: for market buy order only + :param stop_price: Price for auto sell to get the max benefit + :param time_in_force: gtc(invalid for orderType=market), boc(invalid orderType=market),ioc,fok(invalid for orderType=market) + :param trailing_rate: for trailing orders only + :param client_order_id: unique Id which is user defined and must be unique in recent 24 hours + """ + + params = self.create_order_param_check(symbol, account_id, order_side, order_type, stop_price, order_price, + order_size, order_value, time_in_force, trailing_rate, client_order_id) + from huobi.service.algo.post_create_order import PostCreateOrderService + return PostCreateOrderService(params).request(**self.__kwargs) + + def cancel_orders(self, client_order_ids) -> CancelOrderResult: + check_should_not_none(client_order_ids, "clientOrderIds") + + params = { + "clientOrderIds": client_order_ids + } + from huobi.service.algo.post_cancel_orders import PostCancelOrderService + return PostCancelOrderService(params).request(**self.__kwargs) + + def get_open_orders(self, account_id: 'str' = None, symbol: 'str' = None, order_side: 'OrderSide' = None, + order_type: 'AlgoOrderType' = None, sort: 'SortDesc' = None, limit: 'int' = 100, + from_id: 'int' = None): + + params = { + "accountId": account_id, + "symbol": symbol, + "orderSide": order_side, + "orderType": order_type, + "sort": sort, + "limit": limit, + "fromId": from_id + } + from huobi.service.algo.get_open_orders import GetOpenOrdersService + return GetOpenOrdersService(params).request(**self.__kwargs) + + def get_order_history(self, symbol: 'str', order_status: 'AlgoOrderStatus', account_id: 'str' = None, + order_side: 'OrderSide' = None, order_type: 'AlgoOrderType' = None, start_time: 'int' = None, + end_time: 'int' = None, sort: 'SortDesc' = SortDesc.DESC, limit: 'int' = 100, + from_id: 'int' = None): + + params = { + "symbol": symbol, + "accountId": account_id, + "orderSide": order_side, + "orderType": order_type, + "orderStatus": order_status, + "startTime": start_time, + "endTime": end_time, + "sort": sort, + "limit": limit, + "fromId": from_id + } + from huobi.service.algo.get_order_history import GetOrderHistoryService + return GetOrderHistoryService(params).request(**self.__kwargs) + + def get_order(self, client_order_id: 'str'): + params = { + "clientOrderId": client_order_id + } + from huobi.service.algo.get_order_by_cid import GetOrderByClientOrderIdService + return GetOrderByClientOrderIdService(params).request(**self.__kwargs) + + def create_order_param_check(self, symbol, account_id, order_side, order_type, stop_price, order_price, + order_size, order_value, time_in_force, trailing_rate, client_order_id): + check_symbol(symbol) + check_should_not_none(account_id, "accountId") + check_should_not_none(order_type, "orderType") + check_should_not_none(order_side, "orderSide") + + if order_type == OrderType.SELL_LIMIT \ + or order_type == OrderType.BUY_LIMIT \ + or order_type == OrderType.BUY_LIMIT_MAKER \ + or order_type == OrderType.SELL_LIMIT_MAKER: + check_should_not_none(order_price, "orderPrice") + + if time_in_force is not None: + check_time_in_force(time_in_force) + + if order_type in [OrderType.SELL_MARKET, OrderType.BUY_MARKET]: + order_price = None + + params = { + "accountId": account_id, + "symbol": symbol, + "orderPrice": order_price, + "orderSide": order_side, + "orderSize": order_size, + "orderValue": order_value, + "timeInForce": time_in_force, + "orderType": order_type, + "clientOrderId": client_order_id, + "stopPrice": stop_price, + "trailingRate": trailing_rate + } + + return params diff --git a/huobi/client/etf.py b/huobi/client/etf.py new file mode 100644 index 0000000..3f1668c --- /dev/null +++ b/huobi/client/etf.py @@ -0,0 +1,93 @@ +from huobi.constant import * +from huobi.model.etf import * +from huobi.utils import * + + +class EtfClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: to init logger + """ + self.__kwargs = kwargs + + def get_etf_swap_config(self, etf_name: 'str') -> EtfSwapConfig: + """ + Get the basic information of ETF creation and redemption, as well as ETF constituents, + including max amount of creation, min amount of creation, max amount of redemption, min amount + of redemption, creation fee rate, redemption fee rate, eft create/redeem status. + + :param etf_symbol: The symbol, currently only support hb10. (mandatory) + :return: The etf configuration information. + """ + check_symbol(etf_name) + params={ + "etf_name":etf_name + } + + from huobi.service.etf.get_etf_swap_config import GetEtfSwapConfigService + return GetEtfSwapConfigService(params).request(**self.__kwargs) + + def get_etf_swap_list(self, etf_name: 'str', offset: 'int', size: 'int') -> list: + """ + Get past creation and redemption.(up to 100 records) + + :param etf_symbol: The symbol, currently only support hb10. (mandatory) + :param offset: The offset of the records, set to 0 for the latest records. (mandatory) + :param size: The number of records to return, the range is [1, 100]. (mandatory) + :return: The swap history. + """ + check_symbol(etf_name) + params={ + "etf_name":etf_name, + "offset" : offset, + "limit" : size + } + + from huobi.service.etf.get_etf_swap_list import GetEtfSwapListService + return GetEtfSwapListService(params).request(**self.__kwargs) + + def post_etf_swap_in(self, etf_name: 'str', amount: 'int') -> None: + """ + Order creation or redemption of ETF. + + :param etf_name: The symbol, currently only support hb10. (mandatory) + :param amount: The amount to create or redemption. (mandatory) + :return: No return + """ + check_symbol(etf_name) + check_should_not_none(amount, "amount") + + params = { + "etf_name" : etf_name, + "amount" : amount + } + + from huobi.service.etf.post_etf_swap_in import PostEftSwapInService + return PostEftSwapInService(params).request(**self.__kwargs) + + def post_etf_swap_out(self, etf_name: 'str', amount: 'int') -> None: + """ + Order creation or redemption of ETF. + + :param etf_name: The symbol, currently only support hb10. (mandatory) + :param amount: The amount to create or redemption. (mandatory) + :return: No return + """ + + check_symbol(etf_name) + check_should_not_none(amount, "amount") + + params = { + "etf_name": etf_name, + "amount": amount + } + + from huobi.service.etf.post_etf_swap_out import PostEtfSwapOutService + return PostEtfSwapOutService(params).request(**self.__kwargs) + diff --git a/huobi/client/generic.py b/huobi/client/generic.py new file mode 100644 index 0000000..ddc0ce1 --- /dev/null +++ b/huobi/client/generic.py @@ -0,0 +1,102 @@ +from huobi.connection.impl.restapi_request import RestApiRequest +from huobi.constant import * +from huobi.model.generic import * + + +class GenericClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: to init logger + """ + self.__kwargs = kwargs + + def get_exchange_timestamp(self) -> int: + """ + Get the timestamp from Huobi server. The timestamp is the Unix timestamp in millisecond. + The count shows how many milliseconds passed from Jan 1st 1970, 00:00:00.000 at UTC. + e.g. 1546300800000 is Thu, 1st Jan 2019 00:00:00.000 UTC. + + :return: The timestamp in UTC + """ + + params = {} + + from huobi.service.generic.get_exchange_timestamp import GetExchangeTimestampService + return GetExchangeTimestampService(params).request(**self.__kwargs) + + def get_exchange_currencies(self) -> list(): + """ + Get all the trading assets and currencies supported in huobi. + The information of trading instrument, including base currency, quote precision, etc. + + :return: The information of trading currencies. + """ + + params = {} + + from huobi.service.generic.get_exchange_currencies import GetExchangeCurrenciesService + return GetExchangeCurrenciesService(params).request(**self.__kwargs) + + def get_exchange_symbols(self) -> list(): + """ + Get all the trading assets and currencies supported in huobi. + The information of trading instrument etc. + + :return: The information of trading instrument. + """ + + params = {} + + from huobi.service.generic.get_exchange_symbols import GetExchangeSymbolsService + return GetExchangeSymbolsService(params).request(**self.__kwargs) + + def get_exchange_info(self) -> ExchangeInfo: + """ + Get all the trading assets and currencies supported in huobi. + The information of trading instrument, including base currency, quote precision, etc. + + :return: The information of trading instrument and currencies. + """ + + ret = ExchangeInfo() + ret.symbol_list = self.get_exchange_symbols() + ret.currencies = self.get_exchange_currencies() + return ret + + def get_reference_currencies(self, currency: 'str' = None, is_authorized_user: 'bool' = None) -> list: + """ + Get all the trading assets and currencies supported in huobi. + The information of trading instrument, including base currency, quote precision, etc. + + :param currency: btc, ltc, bch, eth, etc ...(available currencies in Huobi Global) + :param is_authorized_user: is Authorized user? True or False + :return: The information of trading instrument and currencies. + """ + + params = { + "currency": currency, + "authorizedUser": is_authorized_user + } + + from huobi.service.generic.get_reference_currencies import GetReferenceCurrenciesService + return GetReferenceCurrenciesService(params).request(**self.__kwargs) + + def get_system_status(self) -> str: + """ + get system status + + :return: system status. + """ + + from huobi.service.generic.get_system_status import GetSystemStatusService + return GetSystemStatusService({}).request(**self.__kwargs) + + def get_market_status(self): + from huobi.service.generic.get_market_status import GetMarketStatusService + return GetMarketStatusService({}).request(**self.__kwargs) diff --git a/huobi/client/linear_swap.py b/huobi/client/linear_swap.py new file mode 100644 index 0000000..5e586c1 --- /dev/null +++ b/huobi/client/linear_swap.py @@ -0,0 +1,540 @@ +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils.input_checker import * + + +class TradeClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: to init logger + """ + self.__kwargs = kwargs + + def get_feerate(self, symbols: 'str') -> list: + """ + Get the candlestick/kline for the specified symbol. The data number is 150 as default. + + :param symbol: The symbol, like "btcusdt". To query hb10, put "hb10" at here. (mandatory) + :param interval: The candlestick/kline interval, MIN1, MIN5, DAY1 etc. (mandatory) + :param size: The start time of of requested candlestick/kline data. (optional) + :param start_time: The start time of of requested candlestick/kline data. (optional) + :param end_time: The end time of of requested candlestick/kline data. (optional) + :return: The list of candlestick/kline data. + """ + check_symbol(symbols) + + params = { + "symbols": symbols + } + + from huobi.service.trade.get_feerate import GetFeeRateService + return GetFeeRateService(params).request(**self.__kwargs) + + def get_transact_feerate(self, symbols: 'str') -> list: + """ + The request of get transact fee rate list. + + :param symbols: The symbol, like "btcusdt,htusdt". (mandatory) + :return: The transact fee rate list. + """ + check_symbol(symbols) + + params = { + "symbols": symbols + } + + from huobi.service.trade.get_transact_feerate import GetTransactFeeRateService + return GetTransactFeeRateService(params).request(**self.__kwargs) + + def sub_order_update(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe order changing event. If a order is created, canceled etc, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(order_update_event: 'OrderUpdateEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(callback, "callback") + + params = { + "symbol_list" : symbol_list, + } + + from huobi.service.trade.sub_order_update_v2 import SubOrderUpdateV2Service + SubOrderUpdateV2Service(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_order_list(self, symbol: 'str', account_id: int, callback, order_states:'str', + order_types:'str'=None, start_date:'str'=None, end_date:'str'=None, from_id=None, + direct=None, size=None, client_req_id:'str'=None, error_handler=None): + """ + request order list. + + :param symbol: The symbol, like "btcusdt". + :param order_states: order status, can be one state or many state sepearted by comma, such as "submitted,partial-filled,partial-canceled,filled,canceled,created" + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(candlestick_event: 'CandlestickEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + check_should_not_none(symbol, "symbol") + check_should_not_none(order_states, "states") + check_should_not_none(account_id, "account-d") + check_should_not_none(callback, "callback") + params = { + "symbol": symbol, + "account-id" : account_id, + "states" : order_states, + "types" : order_types, + "start-date" : start_date, + "end-date" : end_date, + "from": from_id, + "direct" : direct, + "size" : size, + "client-req-id" : client_req_id + } + + from huobi.service.trade.req_order_list import ReqOrderListService + ReqOrderListService(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_order_detail(self, order_id: 'str', callback, + client_req_id:'str'=None, error_handler=None): + """ + Subscribe candlestick/kline event. If the candlestick/kline is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param interval: The candlestick/kline interval, MIN1, MIN5, DAY1 etc. + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(candlestick_event: 'CandlestickEvent'): + pass + :param client_req_id: client request ID + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + check_should_not_none(order_id, "order_id") + check_should_not_none(callback, "callback") + params = { + "order-id": order_id, + "cid": client_req_id, + } + + from huobi.service.trade.req_order_detail import ReqOrderDetailService + ReqOrderDetailService(params).subscribe(callback, error_handler, **self.__kwargs) + + def get_order(self, order_id: 'int') -> Order: + """ + Get the details of an order. + + :param order_id: The order id. (mandatory) + :return: The information of order. + """ + check_should_not_none(order_id, "order_id") + + params = { + "order_id": order_id, + } + + from huobi.service.trade.get_order_by_id import GetOrderByIdService + return GetOrderByIdService(params).request(**self.__kwargs) + + def get_order_by_client_order_id(self, client_order_id): + check_should_not_none(client_order_id, "clientOrderId") + + params = { + "clientOrderId": client_order_id, + } + + from huobi.service.trade.get_order_by_client_order_id import GetOrderByClientOrderIdService + return GetOrderByClientOrderIdService(params).request(**self.__kwargs) + + def get_orders(self, symbol: 'str', order_state: 'OrderState', order_type: 'OrderType' = None, + start_date: 'str' = None, end_date: 'str' = None, start_id: 'int' = None, + size: 'int' = None, direct=None) -> list: + check_symbol(symbol) + check_should_not_none(order_state, "order_state") + start_date = format_date(start_date, "start_date") + end_date = format_date(end_date, "end_date") + + params = { + "symbol" : symbol, + "types" : order_type, + "start-date" : start_date, + "end-date" : end_date, + "from" : start_id, + "states" : order_state, + "size" : size, + "direct" : direct + } + + from huobi.service.trade.get_orders import GetOrdersService + return GetOrdersService(params).request(**self.__kwargs) + + def get_open_orders(self, symbol: 'str', account_id: 'int', side: 'OrderSide' = None, + size: 'int' = None, from_id=None, direct=None) -> list: + """ + The request of get open orders. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param account_id: account id (mandatory) + :param side: The order side, buy or sell. If no side defined, will return all open orders of the account. (optional) + :param size: The number of orders to return. Range is [1, 500]. (optional) + :param direct: 1:prev order by ID asc from from_id, 2:next order by ID desc from from_id + :param from_id: start ID for search + :return: The orders information. + """ + check_symbol(symbol) + check_range(size, 1, 500, "size") + check_should_not_none(account_id, "account_id") + params = { + "symbol" : symbol, + "account-id" : account_id, + "side" : side, + "size" : size, + "from" : from_id, + "direct" : direct + } + + from huobi.service.trade.get_open_orders import GetOpenOrdersService + return GetOpenOrdersService(params).request(**self.__kwargs) + + def get_history_orders(self, symbol=None, start_time=None, end_time=None, size=None, direct=None)-> list: + """ + Transfer Asset between Futures and Contract. + + :param direct: + :param symbol: The target sub account uid to transfer to or from. (optional) + :param start_time: The crypto currency to transfer. (optional) + :param end_time: The amount of asset to transfer. (optional) + :param size: The type of transfer, need be "futures-to-pro" or "pro-to-futures" (optional) + :return: The Order list. + """ + params = { + "symbol" :symbol, + "start-time" : start_time, + "end-time" : end_time, + "size" : size, + "direct" : direct + } + + from huobi.service.trade.get_history_orders import GetHistoryOrdersService + return GetHistoryOrdersService(params).request(**self.__kwargs) + + def get_match_result(self, symbol: 'str', order_type: 'OrderSide' = None, start_date: 'str' = None, + end_date: 'str' = None, + size: 'int' = None, + from_id: 'int' = None, + direct:'str'=None): + """ + Search for the trade records of an account. + + :param symbol: The symbol, like "btcusdt" (mandatory). + :param order_type: The types of order to include in the search (optional). + :param start_date: Search starts date in format yyyy-mm-dd. (optional). + :param end_date: Search ends date in format yyyy-mm-dd. (optional). + :param size: The number of orders to return, range [1-100]. (optional). + :param from_id: Search order id to begin with. (optional). + :return: + """ + + check_symbol(symbol) + start_date = format_date(start_date, "start_date") + end_date = format_date(end_date, "end_date") + check_range(size, 1, 100, "size") + + params = { + "symbol" : symbol, + "start-date" : start_date, + "end-date" : end_date, + "types" : order_type, + "size" : size, + "from" : from_id, + "direct" : direct + } + + from huobi.service.trade.get_match_results import GetMatchResultsService + return GetMatchResultsService(params).request(**self.__kwargs) + + def get_match_results_by_order_id(self, order_id: 'int') -> list: + """ + Get detail match results of an order. + + :param order_id: The order id. (mandatory) + :return: The list of match result. + """ + check_should_not_none(order_id, "order_id") + + params = { + "order_id": order_id + } + + from huobi.service.trade.get_match_results_by_order_id import GetMatchResultsByOrderIdService + return GetMatchResultsByOrderIdService(params).request(**self.__kwargs) + + def order_source_desc(self, account_type): + default_source = "api" + if account_type: + if account_type == AccountType.MARGIN: + return "margin-api" + return default_source + + def create_order_param_check(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', source:'str', client_order_id=None, stop_price=None, operator=None): + check_symbol(symbol) + check_should_not_none(account_id, "account_id") + check_should_not_none(order_type, "order_type") + check_should_not_none(amount, "amount") + check_should_not_none(source, "source") + + if order_type == OrderType.SELL_LIMIT \ + or order_type == OrderType.BUY_LIMIT \ + or order_type == OrderType.BUY_LIMIT_MAKER \ + or order_type == OrderType.SELL_LIMIT_MAKER: + check_should_not_none(price, "price") + if order_type in [OrderType.SELL_MARKET, OrderType.BUY_MARKET]: + price = None + + params = { + "account-id" : account_id, + "amount" : amount, + "price": price, + "symbol": symbol, + "type": order_type, + "source": source, + "client-order-id": client_order_id, + "stop-price": stop_price, + "operator": operator + } + + return params + + def create_order(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', source:'str', client_order_id=None, stop_price=None, operator=None) -> int: + """ + Make an order in huobi. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param account_id: Account id. (mandatory) + :param order_type: The order type. (mandatory) + :param source: The order source. (mandatory) + for spot, it's "api", see OrderSource.API + for margin, it's "margin-api", see OrderSource.MARGIN_API + for super margin, it's "super-margin-api", see OrderSource.SUPER_MARGIN_API + :param amount: The amount to buy (quote currency) or to sell (base currency). (mandatory) + :param price: The limit price of limit order, only needed for limit order. (mandatory for buy-limit, sell-limit, buy-limit-maker and sell-limit-maker) + :param client_order_id: unique Id which is user defined and must be unique in recent 24 hours + :param stop_price: Price for auto sell to get the max benefit + :param operator: the condition for stop_price, value can be "gte" or "lte", gte – greater than and equal (>=), lte – less than and equal (<=) + :return: The order id. + """ + + params = self.create_order_param_check(symbol, account_id, order_type, amount, + price, source, client_order_id, stop_price, operator) + from huobi.service.trade.post_create_order import PostCreateOrderService + return PostCreateOrderService(params).request(**self.__kwargs) + + def create_spot_order(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', client_order_id=None, stop_price=None, + operator=None) -> int: + order_source = OrderSource.API + return self.create_order(symbol=symbol, account_id=account_id, order_type=order_type, amount=amount, + price=price, source=order_source, client_order_id=client_order_id, stop_price=stop_price, + operator=operator) + + def create_margin_order(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', client_order_id=None, stop_price=None, + operator=None) -> int: + order_source = OrderSource.MARGIN_API + return self.create_order(symbol=symbol, account_id=account_id, order_type=order_type, amount=amount, + price=price, source=order_source, client_order_id=client_order_id, + stop_price=stop_price, + operator=operator) + + def create_super_margin_order(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', client_order_id=None, stop_price=None, + operator=None) -> int: + order_source = OrderSource.SUPER_MARGIN_API + return self.create_order(symbol=symbol, account_id=account_id, order_type=order_type, amount=amount, + price=price, source=order_source, client_order_id=client_order_id, + stop_price=stop_price, + operator=operator) + + def cancel_order(self, symbol, order_id): + check_symbol(symbol) + check_should_not_none(order_id, "order_id") + + params = { + "order_id" : order_id + } + + from huobi.service.trade.post_cancel_order import PostCancelOrderService + return PostCancelOrderService(params).request(**self.__kwargs) + + def cancel_orders(self, symbol, order_id_list)->BatchCancelResult: + """ + Submit cancel request for cancelling multiple orders. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param order_id_list: The list of order id. the max size is 50. (mandatory) + :return: No return + """ + check_symbol(symbol) + check_should_not_none(order_id_list, "order_id_list") + check_list(order_id_list, 1, 50, "order_id_list") + + string_list = list() + for order_id in order_id_list: + string_list.append(str(order_id)) + + params = { + "order-ids" : string_list + } + + from huobi.service.trade.post_batch_cancel_order import PostBatchCancelOrderService + return PostBatchCancelOrderService(params).request(**self.__kwargs) + + def cancel_open_orders(self, account_id, symbols: 'str'=None , side=None, size=None)->BatchCancelCount: + """ + Request to cancel open orders. + + :param symbols: The symbol, like "btcusdt". + :param account_type: Account type. (mandatory) + :param side: The order side, buy or sell. If no side defined, will cancel all open orders of the account. (optional) + :param size: The number of orders to cancel. Range is [1, 100]. (optional) + :return: Status of batch cancel result. + """ + check_should_not_none(account_id, "account_id") + + params = { + "account-id": account_id, + "symbol" : symbols, + "side" : side, + "size" : size + } + + from huobi.service.trade.post_batch_cancel_open_order import PostBatchCancelOpenOrderService + return PostBatchCancelOpenOrderService(params).request(**self.__kwargs) + + def cancel_client_order(self, client_order_id)->int: + """ + Request to cancel open orders. + + :param client_order_id: user defined unique order id + """ + check_should_not_none(client_order_id, "client-order-id") + + params = { + "client-order-id" : client_order_id + } + + from huobi.service.trade.post_cancel_client_order import PostCancelClientOrderService + return PostCancelClientOrderService(params).request(**self.__kwargs) + + def transfer_between_futures_and_pro(self, currency: 'str', amount: 'float', + transfer_type: 'TransferFuturesPro')-> int: + """ + Transfer Asset between Futures and Contract. + + :param sub_uid: The target sub account uid to transfer to or from. (mandatory) + :param currency: The crypto currency to transfer. (mandatory) + :param amount: The amount of asset to transfer. (mandatory) + :param transfer_type: The type of transfer, need be "futures-to-pro" or "pro-to-futures" (mandatory) + :return: The order id. + """ + check_currency(currency) + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + check_should_not_none(transfer_type, "transfer_type") + params = { + "currency" : currency, + "amount" : amount, + "type" : transfer_type + + } + + from huobi.service.trade.post_transfer_futures_pro import PostTransferFuturesProService + return PostTransferFuturesProService(params).request(**self.__kwargs) + + def batch_create_order(self, order_config_list) -> int: + """ + Make an order in huobi. + :param order_config_list: order config list, it can batch create orders, and each order config check as below + : items as below + :param symbol: The symbol, like "btcusdt". (mandatory) + :param account_type: Account type. (mandatory) + :param order_type: The order type. (mandatory) + :param amount: The amount to buy (quote currency) or to sell (base currency). (mandatory) + :param price: The limit price of limit order, only needed for limit order. (mandatory for buy-limit, sell-limit, buy-limit-maker and sell-limit-maker) + :param client_order_id: unique Id which is user defined and must be unique in recent 24 hours + :param stop_price: Price for auto sell to get the max benefit + :param operator: the condition for stop_price, value can be "gte" or "lte", gte – greater than and equal (>=), lte – less than and equal (<=) + :return: The order id. + """ + + check_should_not_none(order_config_list, "order_config_list") + check_list(order_config_list, 1, 10, "create order config list") + + new_config_list = list() + for item in order_config_list: + new_item = self.create_order_param_check( + item.get("symbol", None), + item.get("account_id", None), + item.get("order_type", None), + item.get("amount", None), + item.get("price", None), + item.get("source", None), + item.get("client_order_id", None), + item.get("stop-price", None), + item.get("operator", None)) + + new_config_list.append(new_item) + + from huobi.service.trade.post_batch_create_order import PostBatchCreateOrderService + return PostBatchCreateOrderService(new_config_list).request(**self.__kwargs) + + def sub_trade_clearing(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe trade clearing by symbol + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + "*" for all symbols + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + check_should_not_none(symbols, "symbols") + symbol_list = symbols.split(",") + if ("*" in symbol_list): + symbol_list = ["*"] + else: + check_symbol_list(symbol_list) + + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + } + + from huobi.service.trade.sub_trade_clearing_v2 import SubTradeClearingV2Service + SubTradeClearingV2Service(params).subscribe(callback, error_handler, **self.__kwargs) diff --git a/huobi/client/margin.py b/huobi/client/margin.py new file mode 100644 index 0000000..0fe3596 --- /dev/null +++ b/huobi/client/margin.py @@ -0,0 +1,363 @@ +from huobi.utils.input_checker import * + + +class MarginClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: to init logger + """ + self.__kwargs = kwargs + + def post_transfer_in_margin(self, symbol: 'str', currency: 'str', amount: 'float') -> int: + """ + Transfer asset from spot account to margin account. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param currency: The currency of transfer. (mandatory) + :param amount: The amount of transfer. (mandatory) + :return: + """ + check_symbol(symbol) + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + + params = { + "symbol": symbol, + "currency": currency, + "amount": amount + } + + from huobi.service.margin.post_transfer_in_margin import PostTransferInMarginService + return PostTransferInMarginService(params).request(**self.__kwargs) + + def post_transfer_out_margin(self, symbol: 'str', currency: 'str', amount: 'float') -> int: + """ + Transfer asset from margin account to spot account. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param currency: The currency of transfer. (mandatory) + :param amount: The amount of transfer. (mandatory) + :return: + """ + check_symbol(symbol) + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + + params = { + "symbol": symbol, + "currency": currency, + "amount": amount + } + + from huobi.service.margin.post_transfer_out_margin import PostTransferOutMarginService + return PostTransferOutMarginService(params).request(**self.__kwargs) + + def get_margin_account_balance(self, symbol: 'str') -> list: + """ + Get the Balance of the Margin Loan Account. + + :param symbol: The currency, like "btc". (mandatory) + :return: The margin loan account detail list. + """ + check_symbol(symbol) + + params = { + "symbol": symbol + } + + from huobi.service.margin.get_margin_account_balance import GetMarginAccountBalanceService + return GetMarginAccountBalanceService(params).request(**self.__kwargs) + + def post_create_margin_order(self, symbol: 'str', currency: 'str', amount: 'float') -> int: + """ + Submit a request to borrow with margin account. + + :param symbol: The trading symbol to borrow margin, e.g. "btcusdt", "bccbtc". (mandatory) + :param currency: The currency to borrow,like "btc". (mandatory) + :param amount: The amount of currency to borrow. (mandatory) + :return: The margin order id. + """ + check_symbol(symbol) + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + + params = { + "symbol": symbol, + "currency": currency, + "amount": amount + } + + from huobi.service.margin.post_create_margin_order import PostCreateMarginOrderService + return PostCreateMarginOrderService(params).request(**self.__kwargs) + + def post_repay_margin_order(self, loan_id: 'int', amount: 'float') -> int: + """ + Get the margin loan records. + + :param load_id: The previously returned order id when loan order was created. (mandatory) + :param amount: The amount of currency to repay. (mandatory) + :return: The margin order id. + """ + check_should_not_none(loan_id, "loan_id") + check_should_not_none(amount, "amount") + + params = { + "loan_id": loan_id, + "amount": amount + } + + from huobi.service.margin.post_repay_margin_order import PostRepayMarginOrderService + return PostRepayMarginOrderService(params).request(**self.__kwargs) + + def get_margin_loan_orders(self, symbol: 'str', start_date: 'str' = None, end_date: 'str' = None, + states: 'LoanOrderState' = None, from_id: 'int' = None, + size: 'int' = None, direction: 'QueryDirection' = None) -> list: + """ + Get the margin loan records. + + :param symbol: The symbol, like "btcusdt" (mandatory). + :param start_date: The search starts date in format yyyy-mm-dd. (optional). + :param end_date: The search end date in format yyyy-mm-dd.(optional, can be null). + :param states: The loan order states, it could be created, accrual, cleared or invalid. (optional) + :param from_id: Search order id to begin with. (optional) + :param size: The number of orders to return.. (optional) + :param direction: The query direction, prev or next. (optional) + :return: The list of the margin loan records. + """ + + check_symbol(symbol) + start_date = format_date(start_date, "start_date") + end_date = format_date(end_date, "end_date") + + params = { + "symbol": symbol, + "start-date": start_date, + "end-date": end_date, + "states": states, + "from": from_id, + "size": size, + "direct": direction + } + + from huobi.service.margin.get_margin_loan_orders import GetMarginLoanOrdersService + return GetMarginLoanOrdersService(params).request(**self.__kwargs) + + def get_margin_loan_info(self, symbols: 'str' = None) -> list: + """ + The request of get margin loan info, can return currency loan info list. + + :param symbols: The symbol, like "btcusdt,htusdt". (optional) + :return: The cross margin loan info. + """ + + check_symbol(symbols) + params = { + "symbols": symbols + } + + from huobi.service.margin.get_margin_loan_info import GetMarginLoanInfoService + return GetMarginLoanInfoService(params).request(**self.__kwargs) + + def get_cross_margin_loan_info(self) -> list: + """ + The request of currency loan info list. + + :return: The cross margin loan info list. + """ + params = {} + + from huobi.service.margin.get_cross_margin_loan_info import GetCrossMarginLoanInfoService + return GetCrossMarginLoanInfoService(params).request(**self.__kwargs) + + def post_cross_margin_transfer_in(self, currency: 'str', amount: 'float') -> int: + """ + transfer currency to cross account. + + :param currency: currency name (mandatory) + :param amount: transfer amount (mandatory) + :return: return transfer id. + """ + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + + params = { + "amount": amount, + "currency": currency + } + + from huobi.service.margin.post_cross_margin_transfer_in import PostCrossMarginTransferInService + return PostCrossMarginTransferInService(params).request(**self.__kwargs) + + def post_cross_margin_transfer_out(self, currency: 'str', amount: 'float') -> int: + """ + transfer currency to cross account. + + :param currency: currency name (mandatory) + :param amount: transfer amount (mandatory) + :return: return transfer id. + """ + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + + params = { + "amount": amount, + "currency": currency + } + + from huobi.service.margin.post_cross_margin_transfer_out import PostCrossMarginTransferOutService + return PostCrossMarginTransferOutService(params).request(**self.__kwargs) + + def post_cross_margin_create_loan_orders(self, currency: 'str', amount: 'float') -> int: + """ + create cross margin loan orders + + :param currency: currency name (mandatory) + :param amount: transfer amount (mandatory) + :return: return order id. + """ + + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + + params = { + "amount": amount, + "currency": currency + } + + from huobi.service.margin.post_cross_margin_create_loan_orders import PostCrossMarginCreateLoanOrdersService + return PostCrossMarginCreateLoanOrdersService(params).request(**self.__kwargs) + + def post_cross_margin_loan_order_repay(self, order_id: 'str', amount: 'float'): + """ + repay cross margin loan orders + + :param order_id: order_id for loan (mandatory) + :param amount: transfer amount (mandatory) + :return: return order id. + """ + + check_should_not_none(order_id, "order-id") + check_should_not_none(amount, "amount") + + params = { + "amount": amount, + "order-id": order_id + } + + from huobi.service.margin.post_cross_margin_loan_order_repay import PostCrossMarginLoanOrderRepayService + return PostCrossMarginLoanOrderRepayService(params).request(**self.__kwargs) + + def get_cross_margin_loan_orders(self, currency: 'str' = None, state: 'str' = None, + start_date: 'str' = None, end_date: 'str' = None, + from_id: 'int' = None, size: 'int' = None, direct: 'str' = None, + sub_uid: 'int' = None) -> list: + """ + get cross margin loan orders + + :return: return list. + """ + params = { + "currency": currency, + "state": state, + "start-date": start_date, + "end-date": end_date, + "from": from_id, + "size": size, + "direct": direct, + "sub-uid": sub_uid + } + + from huobi.service.margin.get_cross_margin_loan_orders import GetCrossMarginLoanOrdersService + return GetCrossMarginLoanOrdersService(params).request(**self.__kwargs) + + def get_cross_margin_account_balance(self, sub_uid: 'int' = None): + """ + get cross margin account balance + + :return: cross-margin account. + """ + params = { + "sub-uid": sub_uid + } + + from huobi.service.margin.get_cross_margin_account_balance import GetCrossMarginAccountBalanceService + return GetCrossMarginAccountBalanceService(params).request(**self.__kwargs) + + def post_general_repay_loan(self, account_id: 'str', currency: 'str', amount: 'float', + transact_id: 'str' = None) -> list: + """ + Repay Margin Loan(Cross). + + :param account_id: repayment account ID .(mandatory) + :param currency: repayment currency. (mandatory). + :param amount: repayment amount.(mandatory). + :param transact_id: loan transaction ID. (optional) + """ + check_should_not_none(account_id, "account_id") + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + + params = { + "accountId": account_id, + "currency": currency, + "amount": amount, + "transact_id": transact_id + } + + from huobi.service.margin.post_general_repay_loan import PostGeneralRepayLoanService + return PostGeneralRepayLoanService(params).request(**self.__kwargs) + + def get_general_repayment_loan_records(self, repay_id: 'str' = None, account_id: 'str' = None, + currency: 'str' = None, start_time: 'int' = None, end_time: 'int' = None, + sort: 'str' = None, limit: 'int' = None, from_id: 'int' = None) -> list: + + """ + Get Repayment Record Reference(Cross). + + :param repay_id: repayment transaction ID. (optional) + :param account_id: account ID (default value: all accounts) (optional). + :param currency: borrowing/lending currency (default value: all currencies). (optional) + :param start_time: start time (unix time in millisecond; range: [(endTime – x D), endTime]; default value: (endTime – x D) (optional) + :param end_time: end time (unix time in millisecond;range: [(present time – y D), present time]; default value: present time) (optional) + :param sort: sort direction (virtual value: asc, desc; default value: desc) (optional) + :param limit: max return items per page (range: [1,100]; default value: 50) (optional) + :param from_id: search original ID (only available when searching for the next page) (optional) + """ + + params = { + + } + if repay_id is not None: + params['repayId'] = repay_id + + if account_id is not None: + params['accountId'] = account_id + + if account_id is not None: + params['currency'] = currency + + if start_time is not None: + params['startTime'] = start_time + + if end_time is not None: + params['endTime'] = end_time + + if sort is not None: + params['sort'] = sort + + if limit is not None: + params['limit'] = limit + + if from_id is not None: + params['fromId'] = from_id + + from huobi.service.margin.get_general_repayment_loan_records import GetGeneralRepaymentLoanRecordsService + return GetGeneralRepaymentLoanRecordsService(params).request(**self.__kwargs) + + return diff --git a/huobi/client/market.py b/huobi/client/market.py new file mode 100644 index 0000000..7d281be --- /dev/null +++ b/huobi/client/market.py @@ -0,0 +1,496 @@ +from huobi.constant import * +from huobi.model.market import * +from huobi.utils import * +from huobi.utils.input_checker import check_in_list + + +class MarketClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: to init logger + """ + self.__kwargs = kwargs + + def get_candlestick(self, symbol, period, size=200): + """ + Get the candlestick/kline for the specified symbol. The data number is 150 as default. + + :param symbol: The symbol, like "btcusdt". To query hb10, put "hb10" at here. (mandatory) + :param period: The candlestick/kline interval, MIN1, MIN5, DAY1 etc. (mandatory) + :param size: The start time of of requested candlestick/kline data. (optional) + :return: The list of candlestick/kline data. + """ + check_symbol(symbol) + check_should_not_none(period, "period") + check_range(size, 1, 2000, "size") + + params = { + "symbol": symbol, + "period": period, + "size": size + } + from huobi.service.market.get_candlestick import GetCandleStickService + return GetCandleStickService(params).request(**self.__kwargs) + + def sub_candlestick(self, symbols: 'str', interval: 'CandlestickInterval', callback, error_handler): + + """ + Subscribe candlestick/kline event. If the candlestick/kline is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param interval: The candlestick/kline interval, MIN1, MIN5, DAY1 etc. + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(candlestick_event: 'CandlestickEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(interval, "interval") + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + "interval": interval, + } + + from huobi.service.market.sub_candlestick import SubCandleStickService + SubCandleStickService(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_candlestick(self, symbols: 'str', interval: 'CandlestickInterval', callback, + from_ts_second=None, end_ts_second=None, error_handler=None): + """ + Subscribe candlestick/kline event. If the candlestick/kline is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param interval: The candlestick/kline interval, MIN1, MIN5, DAY1 etc. + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(candlestick_event: 'CandlestickEvent'): + pass + :param from_ts_second : data from timestamp [it's second] + :param end_ts_second : data util timestamp [it's second] + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(interval, "interval") + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + "interval": interval, + "from_ts_second": from_ts_second, + "end_ts_second": end_ts_second + } + + from huobi.service.market.req_candlestick import ReqCandleStickService + ReqCandleStickService(params).subscribe(callback, error_handler, **self.__kwargs) + + def get_pricedepth(self, symbol: 'str', depth_type: 'str', depth_size: 'int' = None) -> PriceDepth: + """ + Get the Market Depth of a symbol. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param depth_type: The tpye, like "step0" to "step5". (mandatory) + :param depth_size(optional): The maximum number of Market Depth step0 requested. range [1 - 150], default is 150 + The maximum number of Market Depth step1,step2,step3,step4,step5 requested. size is in [5, 10, 20], default is 20. + :return: Market Depth data. + """ + + check_symbol(symbol) + check_in_list(depth_type, [DepthStep.STEP0, DepthStep.STEP1, DepthStep.STEP2, DepthStep.STEP3, DepthStep.STEP4, + DepthStep.STEP5], "depth_type") + params = { + "symbol": symbol, + "type": depth_type, + # "depth": depth_size + } + + from huobi.service.market.get_pricedepth import GetPriceDepthService + ret_data = GetPriceDepthService(params).request(**self.__kwargs) + + if depth_size is not None: + if (ret_data.bids is not None) and (len(ret_data.bids) > depth_size): + ret_data.bids = ret_data.bids[0:depth_size] + + if (ret_data.asks is not None) and (len(ret_data.asks) > depth_size): + ret_data.asks = ret_data.asks[0:depth_size] + + return ret_data + + @staticmethod + def get_depth_step_list(): + return [DepthStep.STEP0, + DepthStep.STEP1, + DepthStep.STEP2, + DepthStep.STEP3, + DepthStep.STEP4, + DepthStep.STEP5] + + @staticmethod + def get_valid_depth_step(value, defalut_value): + step_list = MarketClient.get_depth_step_list() + if value in step_list: + return value + else: + return defalut_value + + def sub_pricedepth(self, symbols: 'str', depth_step: 'str', callback, error_handler=None): + """ + Subscribe price depth event. If the price depth is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param depth_step: The depth precision, string from step0 to step5. + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + new_step = MarketClient.get_valid_depth_step(value=depth_step, defalut_value=DepthStep.STEP0) + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + "step": new_step, + } + + from huobi.service.market.sub_pricedepth import SubPriceDepthService + SubPriceDepthService(params).subscribe(callback, error_handler, **self.__kwargs) + + def sub_pricedepth_bbo(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe price depth event. If the price depth is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + } + + from huobi.service.market.sub_pricedepth_bbo import SubPriceDepthBboService + SubPriceDepthBboService(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_pricedepth(self, symbols: 'str', depth_step: 'str', callback, error_handler=None): + """ + Subscribe price depth event. If the price depth is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param depth_step: The depth precision, string from step0 to step5. + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + new_step = MarketClient.get_valid_depth_step(value=depth_step, defalut_value=DepthStep.STEP0) + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + "step": new_step, + } + + from huobi.service.market.req_pricedepth import ReqPriceDepthService + ReqPriceDepthService(params).subscribe(callback, error_handler, **self.__kwargs) + + def get_market_detail(self, symbol: 'str') -> MarketDetail: + """ + Get trade statistics in 24 hours. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :return: Trade statistics. + """ + + check_symbol(symbol) + + params = { + "symbol": symbol, + } + + from huobi.service.market.get_market_detail import GetMarketDetailService + return GetMarketDetailService(params).request(**self.__kwargs) + + def sub_market_detail(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe 24 hours trade statistics event. If statistics is generated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(trade_statistics_event: 'TradeStatisticsEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + } + + from huobi.service.market.sub_market_detail import SubMarketDetailService + SubMarketDetailService(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_market_detail(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe 24 hours trade statistics event. If statistics is generated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(trade_statistics_event: 'TradeStatisticsEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + } + + from huobi.service.market.req_market_detail import ReqMarketDetailService + ReqMarketDetailService(params).subscribe(callback, error_handler, **self.__kwargs) + + def get_market_trade(self, symbol: 'str') -> list: + """ + Get the most recent trades with their price, volume and direction. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :return: The list of trade. + """ + + check_symbol(symbol) + + params = { + "symbol": symbol, + } + + from huobi.service.market.get_market_trade import GetMarketTradeService + return GetMarketTradeService(params).request(**self.__kwargs) + + def get_history_trade(self, symbol: 'str', size: 'int' = None) -> list: + """ + Get the most recent trades with their price, volume and direction. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param size: The number of historical trade requested, range [1 - 2000] (optional) + :return: The list of trade. + """ + + check_symbol(symbol) + check_range(size, 1, 2000, "size") + + params = { + "symbol": symbol, + "size": size + } + + from huobi.service.market.get_history_trade import GetHistoryTradeService + return GetHistoryTradeService(params).request(**self.__kwargs) + + def sub_trade_detail(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe price depth event. If the price depth is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(trade_event: 'TradeEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + } + + from huobi.service.market.sub_trade_detail import SubTradeDetailService + SubTradeDetailService(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_trade_detail(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe price depth event. If the price depth is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(trade_event: 'TradeEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + } + + from huobi.service.market.req_trade_detail import ReqTradeDetailService + ReqTradeDetailService(params).subscribe(callback, error_handler, **self.__kwargs) + + def get_market_detail_merged(self, symbol): + check_symbol(symbol) + params = { + "symbol": symbol + } + + from huobi.service.market.get_market_detail_merged import GetMarketDetailMergedService + return GetMarketDetailMergedService(params).request(**self.__kwargs) + + def get_market_tickers(self) -> list: + """ + get market tickers + + :return: market ticker list. + """ + + params = {} + from huobi.service.market.get_market_tickers import GetMarketTickersService + return GetMarketTickersService(params).request(**self.__kwargs) + + """ + increase mbp(market by price) + """ + + def sub_mbp_increase(self, symbols: 'str', levels: 'int', callback, error_handler=None): + """ + Subscribe mbp event. If the mbp is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param levels: level, 5,10,20,150. current only support 150 + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + check_should_not_none(symbols, "symbol") + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(levels, "levels") + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + "levels": levels + } + + from huobi.service.market.sub_mbp_increase import SubMbpIncreaseService + SubMbpIncreaseService(params).subscribe(callback, error_handler, **self.__kwargs) + + """ + subscribe full mbp(market by price) + """ + + def sub_mbp_full(self, symbols: 'str', levels: 'int', callback, error_handler=None): + """ + Subscribe full mbp event. If the mbp is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param levels: level, 5,10,20 + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + check_should_not_none(symbols, "symbol") + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(levels, "levels") + check_in_list(levels, [MbpLevel.MBP5, MbpLevel.MBP10, MbpLevel.MBP20], "levels") + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + "levels": levels + } + + from huobi.service.market.sub_mbp_full import SubMbpFullService + SubMbpFullService(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_mbp(self, symbols: 'str', levels: 'int', callback, auto_close=True, error_handler=None): + """ + Subscribe mbp event. If the mbp is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param levels: level, 5,10,20,150. current only support 150 + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param auto_close : close websocket connection after get data + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + check_should_not_none(symbols, "symbol") + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(levels, "levels") + check_should_not_none(callback, "callback") + params = { + "symbol_list": symbol_list, + "levels": levels + } + from huobi.service.market.req_mbp import ReqMbpService + ReqMbpService(params).subscribe(callback, error_handler, **self.__kwargs) diff --git a/huobi/client/subuser.py b/huobi/client/subuser.py new file mode 100644 index 0000000..bda5809 --- /dev/null +++ b/huobi/client/subuser.py @@ -0,0 +1,115 @@ +from huobi.utils import * +from huobi.constant import * +from huobi.utils.input_checker import check_in_list + + +class SubuserClient(object): + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: Init logger, default is False, True will init logger handler + """ + self.__kwargs = kwargs + + def post_create_subuser(self, user_list): + check_should_not_none(user_list, 'userList') + + params = user_list + from huobi.service.subuser.post_create_subuser import PostSubuserCreationService + return PostSubuserCreationService(params).request(**self.__kwargs) + + def post_set_tradable_market(self, sub_uids, account_type: 'SubuserTradePrivilegeType', + activation: 'SubUserTradeStatus'): + check_should_not_none(sub_uids, 'subUids') + check_should_not_none(account_type, 'accountType') + check_should_not_none(activation, 'activation') + + check_in_list(account_type, + [SubuserTradePrivilegeType.MARGIN, SubuserTradePrivilegeType.SUPER_MARGIN], "accountType") + check_in_list(activation, [SubUserTradeStatus.ACTIVATED, SubUserTradeStatus.DEACTIVATED], "activation") + + params = { + 'subUids': sub_uids, + 'accountType': account_type, + 'activation': activation + } + from huobi.service.subuser.post_tradable_market import PostTradableMarketService + return PostTradableMarketService(params).request(**self.__kwargs) + + def post_set_subuser_transferability(self, sub_uids: 'str', transferrable: 'bool', + account_type: 'AccountType' = AccountType.SPOT): + check_should_not_none(sub_uids, 'subUids') + check_should_not_none(transferrable, 'transferrable') + check_in_list(account_type, [AccountType.SPOT], 'accountType') + + params = { + "subUids": sub_uids, + "accountType": account_type, + "transferrable": transferrable + } + from huobi.service.subuser.post_set_transferability import PostSetSubuserTransferability + return PostSetSubuserTransferability(params).request(**self.__kwargs) + + def post_subuser_apikey_generate(self, otp_token: 'str', sub_uid: 'int', note: 'str', permission: 'bool', + ip_addresses: 'str' = None): + check_should_not_none(otp_token, 'otpToken') + check_should_not_none(sub_uid, 'subUid') + check_should_not_none(note, 'note') + check_should_not_none(permission, 'permission') + # check_in_list(permission, [AccountType.SPOT], 'accountType') + + params = { + "otpToken": otp_token, + "subUid": sub_uid, + "note": note, + "permission": permission, + "ipAddresses": ip_addresses + } + from huobi.service.subuser.post_subuser_apikey_generation import PostSubuserApikeyGenerationService + return PostSubuserApikeyGenerationService(params).request(**self.__kwargs) + + def get_user_apikey_info(self, uid: 'str', access_key: 'str' = None): + check_should_not_none(uid, 'uid') + + params = { + "uid": uid, + "accessKey": access_key + } + from huobi.service.subuser.get_user_apikey_info import GetUserApikeyInfoService + return GetUserApikeyInfoService(params).request(**self.__kwargs) + + def post_subuser_apikey_modification(self, sub_uid: 'str', access_key: 'str', note: 'str' = None, + permission: 'str' = None, ip_addresses: 'str' = None): + check_should_not_none(sub_uid, 'subUid') + check_should_not_none(access_key, 'accessKey') + + params = { + "subUid": sub_uid, + "accessKey": access_key, + "note": note, + "permission": permission, + "ipAddresses": ip_addresses + } + from huobi.service.subuser.post_subuser_apikey_modification import PostSubuserApikeyModificationService + return PostSubuserApikeyModificationService(params).request(**self.__kwargs) + + def post_subuser_apikey_deletion(self, sub_uid: 'str', access_key: 'str'): + check_should_not_none(sub_uid, 'subUid') + check_should_not_none(access_key, 'accessKey') + + params = { + "subUid": sub_uid, + "accessKey": access_key + } + from huobi.service.subuser.post_subuser_apikey_deletion import PostSubuserApikeyDeletionService + return PostSubuserApikeyDeletionService(params).request(**self.__kwargs) + + def get_uid(self): + params = { + } + from huobi.service.subuser.get_uid import GetUidService + return GetUidService(params).request(**self.__kwargs) diff --git a/huobi/client/trade.py b/huobi/client/trade.py new file mode 100644 index 0000000..5e586c1 --- /dev/null +++ b/huobi/client/trade.py @@ -0,0 +1,540 @@ +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils.input_checker import * + + +class TradeClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: to init logger + """ + self.__kwargs = kwargs + + def get_feerate(self, symbols: 'str') -> list: + """ + Get the candlestick/kline for the specified symbol. The data number is 150 as default. + + :param symbol: The symbol, like "btcusdt". To query hb10, put "hb10" at here. (mandatory) + :param interval: The candlestick/kline interval, MIN1, MIN5, DAY1 etc. (mandatory) + :param size: The start time of of requested candlestick/kline data. (optional) + :param start_time: The start time of of requested candlestick/kline data. (optional) + :param end_time: The end time of of requested candlestick/kline data. (optional) + :return: The list of candlestick/kline data. + """ + check_symbol(symbols) + + params = { + "symbols": symbols + } + + from huobi.service.trade.get_feerate import GetFeeRateService + return GetFeeRateService(params).request(**self.__kwargs) + + def get_transact_feerate(self, symbols: 'str') -> list: + """ + The request of get transact fee rate list. + + :param symbols: The symbol, like "btcusdt,htusdt". (mandatory) + :return: The transact fee rate list. + """ + check_symbol(symbols) + + params = { + "symbols": symbols + } + + from huobi.service.trade.get_transact_feerate import GetTransactFeeRateService + return GetTransactFeeRateService(params).request(**self.__kwargs) + + def sub_order_update(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe order changing event. If a order is created, canceled etc, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(order_update_event: 'OrderUpdateEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + symbol_list = symbols.split(",") + check_symbol_list(symbol_list) + check_should_not_none(callback, "callback") + + params = { + "symbol_list" : symbol_list, + } + + from huobi.service.trade.sub_order_update_v2 import SubOrderUpdateV2Service + SubOrderUpdateV2Service(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_order_list(self, symbol: 'str', account_id: int, callback, order_states:'str', + order_types:'str'=None, start_date:'str'=None, end_date:'str'=None, from_id=None, + direct=None, size=None, client_req_id:'str'=None, error_handler=None): + """ + request order list. + + :param symbol: The symbol, like "btcusdt". + :param order_states: order status, can be one state or many state sepearted by comma, such as "submitted,partial-filled,partial-canceled,filled,canceled,created" + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(candlestick_event: 'CandlestickEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + check_should_not_none(symbol, "symbol") + check_should_not_none(order_states, "states") + check_should_not_none(account_id, "account-d") + check_should_not_none(callback, "callback") + params = { + "symbol": symbol, + "account-id" : account_id, + "states" : order_states, + "types" : order_types, + "start-date" : start_date, + "end-date" : end_date, + "from": from_id, + "direct" : direct, + "size" : size, + "client-req-id" : client_req_id + } + + from huobi.service.trade.req_order_list import ReqOrderListService + ReqOrderListService(params).subscribe(callback, error_handler, **self.__kwargs) + + def req_order_detail(self, order_id: 'str', callback, + client_req_id:'str'=None, error_handler=None): + """ + Subscribe candlestick/kline event. If the candlestick/kline is updated, server will send the data to client and onReceive in callback will be called. + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + :param interval: The candlestick/kline interval, MIN1, MIN5, DAY1 etc. + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(candlestick_event: 'CandlestickEvent'): + pass + :param client_req_id: client request ID + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + :return: No return + """ + check_should_not_none(order_id, "order_id") + check_should_not_none(callback, "callback") + params = { + "order-id": order_id, + "cid": client_req_id, + } + + from huobi.service.trade.req_order_detail import ReqOrderDetailService + ReqOrderDetailService(params).subscribe(callback, error_handler, **self.__kwargs) + + def get_order(self, order_id: 'int') -> Order: + """ + Get the details of an order. + + :param order_id: The order id. (mandatory) + :return: The information of order. + """ + check_should_not_none(order_id, "order_id") + + params = { + "order_id": order_id, + } + + from huobi.service.trade.get_order_by_id import GetOrderByIdService + return GetOrderByIdService(params).request(**self.__kwargs) + + def get_order_by_client_order_id(self, client_order_id): + check_should_not_none(client_order_id, "clientOrderId") + + params = { + "clientOrderId": client_order_id, + } + + from huobi.service.trade.get_order_by_client_order_id import GetOrderByClientOrderIdService + return GetOrderByClientOrderIdService(params).request(**self.__kwargs) + + def get_orders(self, symbol: 'str', order_state: 'OrderState', order_type: 'OrderType' = None, + start_date: 'str' = None, end_date: 'str' = None, start_id: 'int' = None, + size: 'int' = None, direct=None) -> list: + check_symbol(symbol) + check_should_not_none(order_state, "order_state") + start_date = format_date(start_date, "start_date") + end_date = format_date(end_date, "end_date") + + params = { + "symbol" : symbol, + "types" : order_type, + "start-date" : start_date, + "end-date" : end_date, + "from" : start_id, + "states" : order_state, + "size" : size, + "direct" : direct + } + + from huobi.service.trade.get_orders import GetOrdersService + return GetOrdersService(params).request(**self.__kwargs) + + def get_open_orders(self, symbol: 'str', account_id: 'int', side: 'OrderSide' = None, + size: 'int' = None, from_id=None, direct=None) -> list: + """ + The request of get open orders. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param account_id: account id (mandatory) + :param side: The order side, buy or sell. If no side defined, will return all open orders of the account. (optional) + :param size: The number of orders to return. Range is [1, 500]. (optional) + :param direct: 1:prev order by ID asc from from_id, 2:next order by ID desc from from_id + :param from_id: start ID for search + :return: The orders information. + """ + check_symbol(symbol) + check_range(size, 1, 500, "size") + check_should_not_none(account_id, "account_id") + params = { + "symbol" : symbol, + "account-id" : account_id, + "side" : side, + "size" : size, + "from" : from_id, + "direct" : direct + } + + from huobi.service.trade.get_open_orders import GetOpenOrdersService + return GetOpenOrdersService(params).request(**self.__kwargs) + + def get_history_orders(self, symbol=None, start_time=None, end_time=None, size=None, direct=None)-> list: + """ + Transfer Asset between Futures and Contract. + + :param direct: + :param symbol: The target sub account uid to transfer to or from. (optional) + :param start_time: The crypto currency to transfer. (optional) + :param end_time: The amount of asset to transfer. (optional) + :param size: The type of transfer, need be "futures-to-pro" or "pro-to-futures" (optional) + :return: The Order list. + """ + params = { + "symbol" :symbol, + "start-time" : start_time, + "end-time" : end_time, + "size" : size, + "direct" : direct + } + + from huobi.service.trade.get_history_orders import GetHistoryOrdersService + return GetHistoryOrdersService(params).request(**self.__kwargs) + + def get_match_result(self, symbol: 'str', order_type: 'OrderSide' = None, start_date: 'str' = None, + end_date: 'str' = None, + size: 'int' = None, + from_id: 'int' = None, + direct:'str'=None): + """ + Search for the trade records of an account. + + :param symbol: The symbol, like "btcusdt" (mandatory). + :param order_type: The types of order to include in the search (optional). + :param start_date: Search starts date in format yyyy-mm-dd. (optional). + :param end_date: Search ends date in format yyyy-mm-dd. (optional). + :param size: The number of orders to return, range [1-100]. (optional). + :param from_id: Search order id to begin with. (optional). + :return: + """ + + check_symbol(symbol) + start_date = format_date(start_date, "start_date") + end_date = format_date(end_date, "end_date") + check_range(size, 1, 100, "size") + + params = { + "symbol" : symbol, + "start-date" : start_date, + "end-date" : end_date, + "types" : order_type, + "size" : size, + "from" : from_id, + "direct" : direct + } + + from huobi.service.trade.get_match_results import GetMatchResultsService + return GetMatchResultsService(params).request(**self.__kwargs) + + def get_match_results_by_order_id(self, order_id: 'int') -> list: + """ + Get detail match results of an order. + + :param order_id: The order id. (mandatory) + :return: The list of match result. + """ + check_should_not_none(order_id, "order_id") + + params = { + "order_id": order_id + } + + from huobi.service.trade.get_match_results_by_order_id import GetMatchResultsByOrderIdService + return GetMatchResultsByOrderIdService(params).request(**self.__kwargs) + + def order_source_desc(self, account_type): + default_source = "api" + if account_type: + if account_type == AccountType.MARGIN: + return "margin-api" + return default_source + + def create_order_param_check(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', source:'str', client_order_id=None, stop_price=None, operator=None): + check_symbol(symbol) + check_should_not_none(account_id, "account_id") + check_should_not_none(order_type, "order_type") + check_should_not_none(amount, "amount") + check_should_not_none(source, "source") + + if order_type == OrderType.SELL_LIMIT \ + or order_type == OrderType.BUY_LIMIT \ + or order_type == OrderType.BUY_LIMIT_MAKER \ + or order_type == OrderType.SELL_LIMIT_MAKER: + check_should_not_none(price, "price") + if order_type in [OrderType.SELL_MARKET, OrderType.BUY_MARKET]: + price = None + + params = { + "account-id" : account_id, + "amount" : amount, + "price": price, + "symbol": symbol, + "type": order_type, + "source": source, + "client-order-id": client_order_id, + "stop-price": stop_price, + "operator": operator + } + + return params + + def create_order(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', source:'str', client_order_id=None, stop_price=None, operator=None) -> int: + """ + Make an order in huobi. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param account_id: Account id. (mandatory) + :param order_type: The order type. (mandatory) + :param source: The order source. (mandatory) + for spot, it's "api", see OrderSource.API + for margin, it's "margin-api", see OrderSource.MARGIN_API + for super margin, it's "super-margin-api", see OrderSource.SUPER_MARGIN_API + :param amount: The amount to buy (quote currency) or to sell (base currency). (mandatory) + :param price: The limit price of limit order, only needed for limit order. (mandatory for buy-limit, sell-limit, buy-limit-maker and sell-limit-maker) + :param client_order_id: unique Id which is user defined and must be unique in recent 24 hours + :param stop_price: Price for auto sell to get the max benefit + :param operator: the condition for stop_price, value can be "gte" or "lte", gte – greater than and equal (>=), lte – less than and equal (<=) + :return: The order id. + """ + + params = self.create_order_param_check(symbol, account_id, order_type, amount, + price, source, client_order_id, stop_price, operator) + from huobi.service.trade.post_create_order import PostCreateOrderService + return PostCreateOrderService(params).request(**self.__kwargs) + + def create_spot_order(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', client_order_id=None, stop_price=None, + operator=None) -> int: + order_source = OrderSource.API + return self.create_order(symbol=symbol, account_id=account_id, order_type=order_type, amount=amount, + price=price, source=order_source, client_order_id=client_order_id, stop_price=stop_price, + operator=operator) + + def create_margin_order(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', client_order_id=None, stop_price=None, + operator=None) -> int: + order_source = OrderSource.MARGIN_API + return self.create_order(symbol=symbol, account_id=account_id, order_type=order_type, amount=amount, + price=price, source=order_source, client_order_id=client_order_id, + stop_price=stop_price, + operator=operator) + + def create_super_margin_order(self, symbol: 'str', account_id: 'int', order_type: 'OrderType', amount: 'float', + price: 'float', client_order_id=None, stop_price=None, + operator=None) -> int: + order_source = OrderSource.SUPER_MARGIN_API + return self.create_order(symbol=symbol, account_id=account_id, order_type=order_type, amount=amount, + price=price, source=order_source, client_order_id=client_order_id, + stop_price=stop_price, + operator=operator) + + def cancel_order(self, symbol, order_id): + check_symbol(symbol) + check_should_not_none(order_id, "order_id") + + params = { + "order_id" : order_id + } + + from huobi.service.trade.post_cancel_order import PostCancelOrderService + return PostCancelOrderService(params).request(**self.__kwargs) + + def cancel_orders(self, symbol, order_id_list)->BatchCancelResult: + """ + Submit cancel request for cancelling multiple orders. + + :param symbol: The symbol, like "btcusdt". (mandatory) + :param order_id_list: The list of order id. the max size is 50. (mandatory) + :return: No return + """ + check_symbol(symbol) + check_should_not_none(order_id_list, "order_id_list") + check_list(order_id_list, 1, 50, "order_id_list") + + string_list = list() + for order_id in order_id_list: + string_list.append(str(order_id)) + + params = { + "order-ids" : string_list + } + + from huobi.service.trade.post_batch_cancel_order import PostBatchCancelOrderService + return PostBatchCancelOrderService(params).request(**self.__kwargs) + + def cancel_open_orders(self, account_id, symbols: 'str'=None , side=None, size=None)->BatchCancelCount: + """ + Request to cancel open orders. + + :param symbols: The symbol, like "btcusdt". + :param account_type: Account type. (mandatory) + :param side: The order side, buy or sell. If no side defined, will cancel all open orders of the account. (optional) + :param size: The number of orders to cancel. Range is [1, 100]. (optional) + :return: Status of batch cancel result. + """ + check_should_not_none(account_id, "account_id") + + params = { + "account-id": account_id, + "symbol" : symbols, + "side" : side, + "size" : size + } + + from huobi.service.trade.post_batch_cancel_open_order import PostBatchCancelOpenOrderService + return PostBatchCancelOpenOrderService(params).request(**self.__kwargs) + + def cancel_client_order(self, client_order_id)->int: + """ + Request to cancel open orders. + + :param client_order_id: user defined unique order id + """ + check_should_not_none(client_order_id, "client-order-id") + + params = { + "client-order-id" : client_order_id + } + + from huobi.service.trade.post_cancel_client_order import PostCancelClientOrderService + return PostCancelClientOrderService(params).request(**self.__kwargs) + + def transfer_between_futures_and_pro(self, currency: 'str', amount: 'float', + transfer_type: 'TransferFuturesPro')-> int: + """ + Transfer Asset between Futures and Contract. + + :param sub_uid: The target sub account uid to transfer to or from. (mandatory) + :param currency: The crypto currency to transfer. (mandatory) + :param amount: The amount of asset to transfer. (mandatory) + :param transfer_type: The type of transfer, need be "futures-to-pro" or "pro-to-futures" (mandatory) + :return: The order id. + """ + check_currency(currency) + check_should_not_none(currency, "currency") + check_should_not_none(amount, "amount") + check_should_not_none(transfer_type, "transfer_type") + params = { + "currency" : currency, + "amount" : amount, + "type" : transfer_type + + } + + from huobi.service.trade.post_transfer_futures_pro import PostTransferFuturesProService + return PostTransferFuturesProService(params).request(**self.__kwargs) + + def batch_create_order(self, order_config_list) -> int: + """ + Make an order in huobi. + :param order_config_list: order config list, it can batch create orders, and each order config check as below + : items as below + :param symbol: The symbol, like "btcusdt". (mandatory) + :param account_type: Account type. (mandatory) + :param order_type: The order type. (mandatory) + :param amount: The amount to buy (quote currency) or to sell (base currency). (mandatory) + :param price: The limit price of limit order, only needed for limit order. (mandatory for buy-limit, sell-limit, buy-limit-maker and sell-limit-maker) + :param client_order_id: unique Id which is user defined and must be unique in recent 24 hours + :param stop_price: Price for auto sell to get the max benefit + :param operator: the condition for stop_price, value can be "gte" or "lte", gte – greater than and equal (>=), lte – less than and equal (<=) + :return: The order id. + """ + + check_should_not_none(order_config_list, "order_config_list") + check_list(order_config_list, 1, 10, "create order config list") + + new_config_list = list() + for item in order_config_list: + new_item = self.create_order_param_check( + item.get("symbol", None), + item.get("account_id", None), + item.get("order_type", None), + item.get("amount", None), + item.get("price", None), + item.get("source", None), + item.get("client_order_id", None), + item.get("stop-price", None), + item.get("operator", None)) + + new_config_list.append(new_item) + + from huobi.service.trade.post_batch_create_order import PostBatchCreateOrderService + return PostBatchCreateOrderService(new_config_list).request(**self.__kwargs) + + def sub_trade_clearing(self, symbols: 'str', callback, error_handler=None): + """ + Subscribe trade clearing by symbol + + :param symbols: The symbols, like "btcusdt". Use comma to separate multi symbols, like "btcusdt,ethusdt". + "*" for all symbols + :param callback: The implementation is required. onReceive will be called if receive server's update. + example: def callback(price_depth_event: 'PriceDepthEvent'): + pass + :param error_handler: The error handler will be called if subscription failed or error happen between client and Huobi server + example: def error_handler(exception: 'HuobiApiException') + pass + + :return: No return + """ + check_should_not_none(symbols, "symbols") + symbol_list = symbols.split(",") + if ("*" in symbol_list): + symbol_list = ["*"] + else: + check_symbol_list(symbol_list) + + check_should_not_none(callback, "callback") + + params = { + "symbol_list": symbol_list, + } + + from huobi.service.trade.sub_trade_clearing_v2 import SubTradeClearingV2Service + SubTradeClearingV2Service(params).subscribe(callback, error_handler, **self.__kwargs) diff --git a/huobi/client/wallet.py b/huobi/client/wallet.py new file mode 100644 index 0000000..519b822 --- /dev/null +++ b/huobi/client/wallet.py @@ -0,0 +1,179 @@ +from huobi.utils.input_checker import * +from huobi.model.wallet import * + + +class WalletClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + init_log: to init logger + """ + self.__kwargs = kwargs + + def get_deposit_withdraw(self, op_type: 'str', currency: 'str' = None, from_id: 'int' = None, size: 'int' = None, + direct: 'str' = None) -> list: + """ + Get the withdraw records of an account. + + :param currency: The currency, like "btc". (optional) + :param from_id: The beginning withdraw record id. (optional) + :param op_type: deposit or withdraw, see defination DepositWithdraw (mandatory) + :param size: The size of record. (optional) + :param direct: "prev" is order by asc, "next" is order by desc, default as "prev"(optional) + :return: The list of withdraw records. + """ + check_should_not_none(op_type, "operate type") + + params = { + "currency": currency, + "type": op_type, + "from": from_id, + "direct": direct, + "size": size + } + + from huobi.service.wallet.get_deposit_withdraw import GetDepositWithdrawService + return GetDepositWithdrawService(params).request(**self.__kwargs) + + def post_create_withdraw(self, address: 'str', amount: 'float', currency: 'str', fee: 'float', + chain: 'str' = None, address_tag: 'str' = None) -> int: + """ + Submit a request to withdraw some asset from an account. + + :param address: The destination address of this withdraw. (mandatory) + :param amount: The amount of currency to withdraw. (mandatory) + :param currency: The crypto currency to withdraw. (mandatory) + :param fee: The fee to pay with this withdraw. (mandatory) + :param address_tag: A tag specified for this address. (optional) + :param chain: set as "usdt" to withdraw USDT to OMNI, set as "trc20usdt" to withdraw USDT to TRX. (optional) + :return: Withdraw id + """ + check_symbol(currency) + check_should_not_none(address, "address") + check_should_not_none(amount, "amount") + check_should_not_none(fee, "fee") + + params = { + "currency": currency, + "address": address, + "amount": amount, + "fee": fee, + "chain": chain, + "addr-tag": address_tag + } + + from huobi.service.wallet.post_create_withdraw import PostCreateWithdrawService + return PostCreateWithdrawService(params).request(**self.__kwargs) + + def post_cancel_withdraw(self, withdraw_id: 'int') -> int: + """ + Cancel an withdraw request. + + :param withdraw_id: withdraw id (mandatory) + :return: No return. + """ + params = { + "withdraw-id": withdraw_id + } + + from huobi.service.wallet.post_cancel_withdraw import PostCancelWithdrawService + return PostCancelWithdrawService(params).request(**self.__kwargs) + + def get_account_deposit_address(self, currency: 'str') -> list: + """ + Get deposit address of corresponding chain, for a specific crypto currency (except IOTA) + + :param currency: The currency, like "btc". (optional) + :return: + """ + check_should_not_none(currency, "currency") + + params = { + "currency": currency + } + + from huobi.service.wallet.get_account_deposit_address import GetAccountDepositAddressService + return GetAccountDepositAddressService(params).request(**self.__kwargs) + + def get_account_withdraw_quota(self, currency: 'str') -> list: + """ + Get the withdraw quota for currencies + + :param currency: The currency, like "btc". (mandatory) + :return: + """ + check_should_not_none(currency, "currency") + + params = { + "currency": currency, + } + + from huobi.service.wallet.get_account_withdraw_quota import GetAccountWithdrawQuotaService + return GetAccountWithdrawQuotaService(params).request(**self.__kwargs) + + def get_sub_user_deposit_history(self, sub_uid: 'int', currency: 'str' = None, + start_time: 'int' = None, end_time: 'int' = None, + sort: 'str' = None, limit: 'int' = None, from_id: 'int' = None) -> DepositHistory: + """ + Parent get sub user depoist history. + + :param sub_uid: Sub user id. (mandatory) + :param currency: Cryptocurrency. + :param start_time: Farthest time + :param end_time: Nearest time + :param sort: Sorting order + :param limit: Maximum number of items in one page + :param from_id: First record Id in this query + """ + check_should_not_none(sub_uid, "sub_uid") + + params = { + "subUid": sub_uid, + "currency": currency, + "startTime": start_time, + "endTime": end_time, + "sort": sort, + "limit": limit, + "fromId": from_id + } + + from huobi.service.wallet.get_sub_user_deposit_history import GetSubUserDepositHistoryService + return GetSubUserDepositHistoryService(params).request(**self.__kwargs) + + def get_sub_user_deposit_address(self, sub_uid: 'int', currency: 'str') -> list: + """ + Parent get sub user deposit address + + :param sub_uid: Sub user id + :param currency: Cryptocurrency, like "btc". (mandatory) + :return: + """ + + check_should_not_none(sub_uid, "subUid") + check_should_not_none(currency, "currency") + params = { + "subUid": sub_uid, + "currency": currency + } + + from huobi.service.wallet.get_sub_user_deposit_address import GetSubUserDepositAddressService + return GetSubUserDepositAddressService(params).request(**self.__kwargs) + + def get_account_withdraw_address(self, currency: 'str', chain: 'str'=None, note: 'str'=None, limit: 'int' = 100, + fromid: 'int' = None): + check_should_not_none(currency, "currency") + params = { + "currency": currency, + "chain": chain, + "note": note, + "limit": limit, + "fromid": fromid + } + from huobi.service.wallet.get_account_withdraw_address import GetAccountWithdrawAddressService + return GetAccountWithdrawAddressService(params).request(**self.__kwargs) + diff --git a/huobi/connection/__init__.py b/huobi/connection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/connection/impl/__init__.py b/huobi/connection/impl/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/huobi/connection/impl/__init__.py @@ -0,0 +1 @@ + diff --git a/huobi/connection/impl/private_def.py b/huobi/connection/impl/private_def.py new file mode 100644 index 0000000..a64c805 --- /dev/null +++ b/huobi/connection/impl/private_def.py @@ -0,0 +1,11 @@ + + +class ConnectionState: + IDLE = 0 + CONNECTED = 1 + WAIT_RECONNECT = 2 + CLOSED_ON_ERROR = 3 + CLOSED = 4 + +CONNECT_HEART_BEAT_LIMIT_MS = 60000 # max interval between two package +RECONNECT_AFTER_TIME_MS = 63000 # if not need connect immediately, it will enter delay connect status and will connect after setting times \ No newline at end of file diff --git a/huobi/connection/impl/restapi_invoker.py b/huobi/connection/impl/restapi_invoker.py new file mode 100644 index 0000000..6c23d5f --- /dev/null +++ b/huobi/connection/impl/restapi_invoker.py @@ -0,0 +1,94 @@ +import requests +from huobi.exception.huobi_api_exception import HuobiApiException +from huobi.utils.etf_result import etf_result_check +from huobi.utils import * +import time + +from huobi.utils.print_mix_object import TypeCheck + +session = requests.Session() + +def check_response(dict_data): + status = dict_data.get("status", None) + code = dict_data.get("code", None) + success = dict_data.get("success", None) + if status and len(status): + if TypeCheck.is_basic(status): # for normal case + if status == "error": + err_code = dict_data.get("err-code", 0) + err_msg = dict_data.get("err-msg", "") + raise HuobiApiException(HuobiApiException.EXEC_ERROR, + "[Executing] " + str(err_code) + ": " + err_msg) + elif status != "ok": + raise HuobiApiException(HuobiApiException.RUNTIME_ERROR, + "[Invoking] Response is not expected: " + status) + elif TypeCheck.is_dict(status): # for https://status.huobigroup.com/api/v2/summary.json in example example/generic/get_system_status.py + if dict_data.get("page") and dict_data.get("components"): + pass + else: + raise HuobiApiException(HuobiApiException.EXEC_ERROR, + "[Executing] System is in maintenances") + elif code: + code_int = int(code) + if code_int != 200: + err_code = dict_data.get("code", 0) + err_msg = dict_data.get("message", "") + raise HuobiApiException(HuobiApiException.EXEC_ERROR, + "[Executing] " + str(err_code) + ": " + err_msg) + elif success is not None: + if bool(success) is False: + err_code = etf_result_check(dict_data.get("code")) + err_msg = dict_data.get("message", "") + if err_code == "": + raise HuobiApiException(HuobiApiException.EXEC_ERROR, "[Executing] " + err_msg) + else: + raise HuobiApiException(HuobiApiException.EXEC_ERROR, "[Executing] " + str(err_code) + ": " + err_msg) + else: + raise HuobiApiException(HuobiApiException.RUNTIME_ERROR, "[Invoking] Status cannot be found in response.") + + +def call_sync(request, is_checked=False): + if request.method == "GET": + # print("call_sync url : " , request.host + request.url) + response = session.get(request.host + request.url, headers=request.header) + if is_checked is True: + return response.text + #dict_data = json.loads(response.text, encoding="utf-8") + dict_data = json.loads(response.text) + # print("call_sync === recv data : ", dict_data) + check_response(dict_data) + return request.json_parser(dict_data) + + elif request.method == "POST": + response = session.post(request.host + request.url, data=json.dumps(request.post_body), headers=request.header) + dict_data = json.loads(response.text, encoding="utf-8") + # print("call_sync === recv data : ", dict_data) + check_response(dict_data) + return request.json_parser(dict_data) + +def call_sync_perforence_test(request, is_checked=False): + if request.method == "GET": + inner_start_time = time.time() + # print("call_sync_perforence_test url : ", request.host + request.url) + response = session.get(request.host + request.url, headers=request.header) + #print("call_sync_perforence_test data :", response.text) + inner_end_time = time.time() + cost_manual = round(inner_end_time - inner_start_time, 6) + req_cost = response.elapsed.total_seconds() + if is_checked is True: + return response.text + dict_data = json.loads(response.text, encoding="utf-8") + # print("call_sync === recv data : ", dict_data) + check_response(dict_data) + return request.json_parser(dict_data), req_cost, cost_manual + + elif request.method == "POST": + inner_start_time = time.time() + response = session.post(request.host + request.url, data=json.dumps(request.post_body), headers=request.header) + inner_end_time = time.time() + cost_manual = round(inner_end_time - inner_start_time, 6) + req_cost = response.elapsed.total_seconds() + dict_data = json.loads(response.text, encoding="utf-8") + # print("call_sync === recv data : ", dict_data) + check_response(dict_data) + return request.json_parser(dict_data), req_cost, cost_manual diff --git a/huobi/connection/impl/restapi_request.py b/huobi/connection/impl/restapi_request.py new file mode 100644 index 0000000..d6e029f --- /dev/null +++ b/huobi/connection/impl/restapi_request.py @@ -0,0 +1,11 @@ + +class RestApiRequest(object): + + def __init__(self): + self.method = "" + self.url = "" + self.host = "" + self.post_body = "" + self.header = dict() + self.json_parser = None + diff --git a/huobi/connection/impl/websocket_manage.py b/huobi/connection/impl/websocket_manage.py new file mode 100644 index 0000000..72bc10c --- /dev/null +++ b/huobi/connection/impl/websocket_manage.py @@ -0,0 +1,281 @@ +import threading +import websocket +import gzip +import ssl +import logging +import urllib.parse + +from huobi.constant import * +from huobi.utils import * +from huobi.exception.huobi_api_exception import HuobiApiException +from huobi.connection.impl.private_def import ConnectionState + +# Key: original_connection, Value: connection +websocket_connection_handler = dict() + + +def on_message(original_connection, message): + websocket_connection = websocket_connection_handler[original_connection] + websocket_connection.on_message(message) + return + + +def on_error(original_connection, error): + websocket_connection = websocket_connection_handler[original_connection] + websocket_connection.on_failure(error) + + +def on_close(original_connection): + websocket_connection = websocket_connection_handler[original_connection] + websocket_connection.on_close() + + +def on_open(original_connection): + websocket_connection = websocket_connection_handler[original_connection] + websocket_connection.on_open(original_connection) + + +connection_id = 0 + + +def websocket_func(*args): + try: + websocket_manage = args[0] + websocket_manage.original_connection = websocket.WebSocketApp(websocket_manage.url, + on_message=on_message, + on_error=on_error, + on_close=on_close) + global websocket_connection_handler + websocket_connection_handler[websocket_manage.original_connection] = websocket_manage + websocket_manage.logger.info("[Sub][" + str(websocket_manage.id) + "] Connecting...") + websocket_manage.original_connection.on_open = on_open + websocket_manage.original_connection.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) + websocket_manage.logger.info("[Sub][" + str(websocket_manage.id) + "] Connection event loop down") + if websocket_manage.state == ConnectionState.CONNECTED: + websocket_manage.state = ConnectionState.IDLE + except Exception as ex: + print(ex) + +class WebsocketManage: + + def __init__(self, api_key, secret_key, uri, request): + self.__thread = None + self.__market_url = HUOBI_WEBSOCKET_URI_PRO + "/ws" + self.__trading_url = HUOBI_WEBSOCKET_URI_PRO + "/ws/" + request.api_version + self.__mbp_feed_url = HUOBI_WEBSOCKET_URI_PRO + "/feed" + self.__api_key = api_key + self.__secret_key = secret_key + self.request = request + self.reconnect_at = 0 + self.original_connection = None + self.last_receive_time = 0 + self.logger = logging.getLogger("huobi-client") + self.state = ConnectionState.IDLE + global connection_id + connection_id += 1 + self.id = connection_id + host = urllib.parse.urlparse(uri).hostname + if host.find("api") == 0: + self.__market_url = "wss://" + host + "/ws" + self.__mbp_feed_url = "wss://" + host + "/feed" + self.__trading_url = "wss://" + host + "/ws/" + request.api_version + else: + self.__market_url = "wss://" + host + "/api/ws" + self.__mbp_feed_url = "wss://" + host + "/feed" + self.__trading_url = "wss://" + host + "/ws/" + request.api_version + + if request.is_trading: + self.url = self.__trading_url + elif request.is_mbp_feed: + self.url = self.__mbp_feed_url + else: + self.url = self.__market_url + + def close_and_wait_reconnect(self, delay_in_ms): + if self.original_connection is not None: + self.original_connection.close() + self.original_connection = None + self.state = ConnectionState.WAIT_RECONNECT + self.reconnect_at = + delay_in_ms + self.logger.warning("[Sub][%d] Lost connectiong for %d ms, will try reconnecting " % (self.id, self.reconnect_at)) + + def re_connect(self): + if get_current_timestamp() > self.reconnect_at: + self.logger.info("[Sub][%d] Reconnecting ... " % self.id) + self.connect() + + def connect(self): + if self.state == ConnectionState.CONNECTED: + self.logger.info("[Sub][" + str(self.id) + "] Already connected") + else: + self.__thread = threading.Thread(target=websocket_func, args=[self]) + self.__thread.start() + + def send(self, data): + # print("Send Data : " + data) + self.original_connection.send(data) + + def close(self): + self.original_connection.close() + del websocket_connection_handler[self.original_connection] + self.state = ConnectionState.CLOSED + self.logger.info("[Sub][" + str(self.id) + "] Closing normally") + + def on_open(self, original_connection): + self.logger.info("[Sub][" + str(self.id) + "] Connected to server") + self.original_connection = original_connection + self.last_receive_time = get_current_timestamp() + self.state = ConnectionState.CONNECTED + if self.request.is_trading: + try: + if self.request.api_version == ApiVersion.VERSION_V1: + builder = UrlParamsBuilder() + create_signature(self.__api_key, self.__secret_key, + "GET", self.url, builder) + builder.put_url("op", "auth") + self.send(builder.build_url_to_json()) + elif self.request.api_version == ApiVersion.VERSION_V2: + builder = UrlParamsBuilder() + create_signature_v2(self.__api_key, self.__secret_key, + "GET", self.url, builder) + self.send(builder.build_url_to_json()) + else: + self.on_error("api version for create the signature fill failed") + + except Exception as e: + self.on_error("Unexpected error when create the signature: " + str(e)) + else: + if self.request.subscription_handler is not None: + self.request.subscription_handler(self) + return + + def on_error(self, error_message): + if self.request.error_handler is not None: + exception = HuobiApiException(HuobiApiException.SUBSCRIPTION_ERROR, error_message) + self.request.error_handler(exception) + self.logger.error("[Sub][" + str(self.id) + "] " + str(error_message)) + + def on_failure(self, error): + self.on_error("Unexpected error: " + str(error)) + self.close_on_error() + + def on_message(self, message): + self.last_receive_time = get_current_timestamp() + if isinstance(message, (str)): # V2 + # print("RX string : ", message) + dict_data = json.loads(message) + elif isinstance(message, (bytes)): # V1 + # print("RX bytes: " + gzip.decompress(message).decode("utf-8")) + dict_data = json.loads(gzip.decompress(message).decode("utf-8")) + else: + print("RX unknow type : ", type(message)) + return + + status_outer = dict_data.get("status", "") + err_code_outer = dict_data.get("err-code", 0) + op_outer = dict_data.get("op", "") + action_outer = dict_data.get("action", "") + ch_outer = dict_data.get("ch", "") + rep_outer = dict_data.get("rep", "") + ping_market_outer = int(dict_data.get("ping", 0)) + if status_outer and len(status_outer) and status_outer != "ok": + error_code = dict_data.get("err-code", "Unknown error") + error_msg = dict_data.get("err-msg", "Unknown error") + self.on_error(error_code + ": " + error_msg) + elif err_code_outer and int(err_code_outer) != 0: + error_code = dict_data.get("err-code", "Unknown error") + error_msg = dict_data.get("err-msg", "Unknown error") + self.on_error(error_code + ": " + error_msg) + elif op_outer and len(op_outer): # for V1 + if op_outer == "notify": + self.__on_receive(dict_data) + elif op_outer == "ping": + #print("******** receive trade ping pong ********", dict_data) + ping_ts = dict_data.get("ts", 0) + self.__process_ping_on_trading_line(ping_ts) + elif op_outer == "auth": + if self.request.subscription_handler is not None: + self.request.subscription_handler(self) + elif op_outer == "req": + self.__on_receive(dict_data) + elif action_outer and len(action_outer): # for V2 + if action_outer == "ping": + action_data = dict_data.get("data") + ping_ts = action_data.get("ts") + self.__process_ping_on_v2_trade(ping_ts) + elif action_outer == "sub": + action_code = dict_data.get("code", -1) + if action_code == 200: + logging.info("subscribe ACK received") + else: + logging.error("receive error data : " + message) + elif action_outer == "req": # + action_code = dict_data.get("code", -1) + if action_code == 200: + logging.info("signature ACK received") + if self.request.subscription_handler is not None: + self.request.subscription_handler(self) + else: + logging.error("receive error data : " + message) + elif action_outer == "push": + action_data = dict_data.get("data") + if action_data: + self.__on_receive(dict_data) + else: + logging.error("receive error push data : " + message) + + elif ch_outer and len(ch_outer): + self.__on_receive(dict_data) + elif rep_outer and len(rep_outer): + self.__on_receive(dict_data) + elif ping_market_outer: + #print("******** receive market ping pong ********", dict_data) + ping_ts = ping_market_outer + self.__process_ping_on_market_line(ping_ts) + else: + #print("unknown data process, RX: ", gzip.decompress(message).decode("utf-8")) + pass + + def __on_receive(self, dict_data): + res = None + try: + if self.request.json_parser is not None: + res = self.request.json_parser(dict_data) + except Exception as e: + self.on_error("Failed to parse server's response: " + str(e)) + + try: + if self.request.update_callback is not None: + self.request.update_callback(res) + except Exception as e: + self.on_error("Process error: " + str(e) + + " You should capture the exception in your error handler") + + # websocket request will close the connection after receive + if self.request.auto_close: + self.close() + + def __process_ping_on_trading_line(self, ping_ts): + #print("### __process_ping_on_trading_line ###") + #self.send("{\"op\":\"pong\",\"ts\":" + str(get_current_timestamp()) + "}") + PrintBasic.print_basic(ping_ts, "response time") + self.send("{\"op\":\"pong\",\"ts\":" + str(ping_ts) + "}") + return + + def __process_ping_on_market_line(self, ping_ts): + #print("### __process_ping_on_market_line ###") + #self.send("{\"pong\":" + str(get_current_timestamp()) + "}") + PrintBasic.print_basic(ping_ts, "response time") + self.send("{\"pong\":" + str(ping_ts) + "}") + return + + def __process_ping_on_v2_trade(self, ping_ts): + # PrintDate.timestamp_to_date(ping_ts) + self.send("{\"action\": \"pong\",\"data\": {\"ts\": " + str(ping_ts) +"}}") + return + + def close_on_error(self): + if self.original_connection is not None: + self.original_connection.close() + self.state = ConnectionState.CLOSED_ON_ERROR + self.logger.error("[Sub][" + str(self.id) + "] Connection is closing due to error") diff --git a/huobi/connection/impl/websocket_request.py b/huobi/connection/impl/websocket_request.py new file mode 100644 index 0000000..9efd1de --- /dev/null +++ b/huobi/connection/impl/websocket_request.py @@ -0,0 +1,14 @@ +from huobi.constant import ApiVersion + + +class WebsocketRequest(object): + + def __init__(self): + self.subscription_handler = None + self.auto_close = False # close connection after receive data, for subscribe set False, for request set True + self.is_trading = False + self.is_mbp_feed = False + self.error_handler = None + self.json_parser = None + self.update_callback = None + self.api_version = ApiVersion.VERSION_V1 # v1 as default diff --git a/huobi/connection/impl/websocket_watchdog.py b/huobi/connection/impl/websocket_watchdog.py new file mode 100644 index 0000000..f755def --- /dev/null +++ b/huobi/connection/impl/websocket_watchdog.py @@ -0,0 +1,67 @@ +import threading +import logging +from apscheduler.schedulers.blocking import BlockingScheduler + +from huobi.connection.impl.private_def import * +from huobi.utils.time_service import get_current_timestamp + + +def watch_dog_job(*args): + watch_dog_obj = args[0] + + for idx, websocket_manage in enumerate(watch_dog_obj.websocket_manage_list): + if websocket_manage.request.auto_close: # setting auto close no need reconnect + pass + elif websocket_manage.state == ConnectionState.CONNECTED: + if watch_dog_obj.is_auto_connect: + ts = get_current_timestamp() - websocket_manage.last_receive_time + if ts > watch_dog_obj.heart_beat_limit_ms: + watch_dog_obj.logger.warning("[Sub][" + str(websocket_manage.id) + "] No response from server") + websocket_manage.close_and_wait_reconnect(watch_dog_obj.wait_reconnect_millisecond()) + elif websocket_manage.state == ConnectionState.WAIT_RECONNECT: + watch_dog_obj.logger.warning("[Sub] call re_connect") + websocket_manage.re_connect() + pass + elif websocket_manage.state == ConnectionState.CLOSED_ON_ERROR: + if watch_dog_obj.is_auto_connect: + websocket_manage.close_and_wait_reconnect(watch_dog_obj.reconnect_after_ms) + pass + + +class WebSocketWatchDog(threading.Thread): + mutex = threading.Lock() + websocket_manage_list = list() + + def __init__(self, is_auto_connect=True, heart_beat_limit_ms=CONNECT_HEART_BEAT_LIMIT_MS, reconnect_after_ms=RECONNECT_AFTER_TIME_MS): + threading.Thread.__init__(self) + self.is_auto_connect = is_auto_connect + self.heart_beat_limit_ms = heart_beat_limit_ms + self.reconnect_after_ms = reconnect_after_ms if reconnect_after_ms > heart_beat_limit_ms else heart_beat_limit_ms + self.logger = logging.getLogger("huobi-client") + self.scheduler = BlockingScheduler() + self.scheduler.add_job(watch_dog_job, "interval", max_instances=10, seconds=1, args=[self]) + self.start() + + def run(self): + self.scheduler.start() + + def on_connection_created(self, websocket_manage): + self.mutex.acquire() + self.websocket_manage_list.append(websocket_manage) + self.mutex.release() + + def on_connection_closed(self, websocket_manage): + self.mutex.acquire() + self.websocket_manage_list.remove(websocket_manage) + self.mutex.release() + + # calculate next reconnect time + def wait_reconnect_millisecond(self): + wait_millisecond = int(self.reconnect_after_ms - self.heart_beat_limit_ms) + now_ms = get_current_timestamp() + wait_millisecond = wait_millisecond if wait_millisecond else 1000 + # job loop after 1 second + return (wait_millisecond + now_ms) + + + diff --git a/huobi/connection/restapi_sync_client.py b/huobi/connection/restapi_sync_client.py new file mode 100644 index 0000000..22e2207 --- /dev/null +++ b/huobi/connection/restapi_sync_client.py @@ -0,0 +1,154 @@ +import logging + +from huobi.connection.impl.restapi_invoker import call_sync, call_sync_perforence_test +from huobi.connection.impl.restapi_request import RestApiRequest +from huobi.constant import * +from huobi.utils import * + +from huobi.exception.huobi_api_exception import HuobiApiException + + + +class RestApiSyncClient(object): + + def __init__(self, **kwargs): + """ + Create the request client instance. + :param kwargs: The option of request connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: The URL name like "https://api.huobi.pro". + performance_test: for performance test + init_log: to init logger + """ + self.__api_key = kwargs.get("api_key", None) + self.__secret_key = kwargs.get("secret_key", None) + self.__server_url = kwargs.get("url", get_default_server_url(None)) + self.__init_log = kwargs.get("init_log", None) + self.__performance_test = kwargs.get("performance_test", None) + if self.__init_log and self.__init_log: + logger = logging.getLogger("huobi-client") + logger.setLevel(level=logging.INFO) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) + logger.addHandler(handler) + + def __create_request_by_get(self, url, builder): + request = RestApiRequest() + request.method = "GET" + request.host = self.__server_url + request.header.update({'Content-Type': 'application/json'}) + request.url = url + builder.build_url() + return request + + def __create_request_by_post_with_signature(self, url, builder): + request = RestApiRequest() + request.method = "POST" + request.host = self.__server_url + create_signature(self.__api_key, self.__secret_key, request.method, request.host + url, builder) + request.header.update({'Content-Type': 'application/json'}) + if (len(builder.post_list)): # specify for case : /v1/order/batch-orders + request.post_body = builder.post_list + else: + request.post_body = builder.post_map + request.url = url + builder.build_url() + return request + + def __create_request_by_get_with_signature(self, url, builder): + request = RestApiRequest() + request.method = "GET" + request.host = self.__server_url + create_signature(self.__api_key, self.__secret_key, request.method, request.host + url, builder) + request.header.update({"Content-Type": "application/x-www-form-urlencoded"}) + request.url = url + builder.build_url() + return request + + def create_request(self, method, url, params, parse): + builder = UrlParamsBuilder() + if params and len(params): + if method in [HttpMethod.GET, HttpMethod.GET_SIGN]: + for key, value in params.items(): + builder.put_url(key, value) + elif method in [HttpMethod.POST, HttpMethod.POST_SIGN]: + for key, value in params.items(): + builder.put_post(key, value) + else: + raise HuobiApiException(HuobiApiException.EXEC_ERROR, + "[error] undefined HTTP method") + + if method == HttpMethod.GET: + request = self.__create_request_by_get(url, builder) + elif method == HttpMethod.GET_SIGN: + request = self.__create_request_by_get_with_signature(url, builder) + elif method == HttpMethod.POST_SIGN: + request = self.__create_request_by_post_with_signature(url, builder) + elif method == HttpMethod.POST: + request = self.__create_request_by_post_with_signature(url, builder) + else: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] " + method + " is invalid http method") + + request.json_parser = parse + + return request + + """ + for post batch operation, such as batch create orders[ /v1/order/batch-orders ] + """ + def create_request_post_batch(self, method, url, params, parse): + builder = UrlParamsBuilder() + if params and len(params): + if method in [HttpMethod.POST, HttpMethod.POST_SIGN]: + if isinstance(params, list): + builder.post_list = params + else: + raise HuobiApiException(HuobiApiException.EXEC_ERROR, + "[error] undefined HTTP method") + + request = self.__create_request_by_post_with_signature(url, builder) + request.json_parser = parse + + return request + + def request_process(self, method, url, params, parse): + if self.__performance_test is not None and self.__performance_test is True: + return self.request_process_performance(method, url, params, parse) + else: + return self.request_process_product(method, url, params, parse) + + def request_process_product(self, method, url, params, parse): + request = self.create_request(method, url, params, parse) + if request: + return call_sync(request) + + return None + + def request_process_performance(self, method, url, params, parse): + request = self.create_request(method, url, params, parse) + if request: + return call_sync_perforence_test(request) + + return None, 0, 0 + + """ + for post batch operation, such as batch create orders[ /v1/order/batch-orders ] + """ + def request_process_post_batch(self, method, url, params, parse): + if self.__performance_test is not None and self.__performance_test is True: + return self.request_process_post_batch_performance(method, url, params, parse) + else: + return self.request_process_post_batch_product(method, url, params, parse) + + def request_process_post_batch_product(self, method, url, params, parse): + request = self.create_request_post_batch(method, url, params, parse) + if request: + return call_sync(request) + + return None + + def request_process_post_batch_performance(self, method, url, params, parse): + request = self.create_request_post_batch(method, url, params, parse) + if request: + return call_sync_perforence_test(request) + + return None, 0, 0 + diff --git a/huobi/connection/subscribe_client.py b/huobi/connection/subscribe_client.py new file mode 100644 index 0000000..11a6d16 --- /dev/null +++ b/huobi/connection/subscribe_client.py @@ -0,0 +1,82 @@ +import logging + +from huobi.connection.impl.websocket_watchdog import WebSocketWatchDog +from huobi.connection.impl.websocket_manage import WebsocketManage +from huobi.connection.impl.websocket_request import WebsocketRequest +from huobi.constant.system import WebSocketDefine, ApiVersion + + +class SubscribeClient(object): + # static property + subscribe_watch_dog = WebSocketWatchDog() + + def __init__(self, **kwargs): + """ + Create the subscription client to subscribe the update from server. + + :param kwargs: The option of subscription connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: Set the URI for subscription. + init_log: to init logger + """ + self.__api_key = kwargs.get("api_key", None) + self.__secret_key = kwargs.get("secret_key", None) + self.__uri = kwargs.get("url", WebSocketDefine.Uri) + self.__init_log = kwargs.get("init_log", None) + if self.__init_log and self.__init_log: + logger = logging.getLogger("huobi-client") + logger.setLevel(level=logging.INFO) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) + logger.addHandler(handler) + + self.__websocket_manage_list = list() + + def __create_websocket_manage(self, request): + manager = WebsocketManage(self.__api_key, self.__secret_key, self.__uri, request) + self.__websocket_manage_list.append(manager) + manager.connect() + SubscribeClient.subscribe_watch_dog.on_connection_created(manager) + + def create_request(self, subscription_handler, parse, callback, error_handler, is_trade, is_mbp_feed=False): + request = WebsocketRequest() + request.subscription_handler = subscription_handler + request.is_trading = is_trade + request.is_mbp_feed = is_mbp_feed + request.auto_close = False # subscribe need connection. websocket request need close request. + request.json_parser = parse + request.update_callback = callback + request.error_handler = error_handler + return request + + def create_request_v1(self, subscription_handler, parse, callback, error_handler, is_trade=False): + request = self.create_request(subscription_handler=subscription_handler, parse=parse, callback=callback, + error_handler=error_handler, is_trade=is_trade) + request.api_version = ApiVersion.VERSION_V1 + return request + + def create_request_v2(self, subscription_handler, parse, callback, error_handler, is_trade=False): + request = self.create_request(subscription_handler=subscription_handler, parse=parse, callback=callback, + error_handler=error_handler, is_trade=is_trade) + request.api_version = ApiVersion.VERSION_V2 + return request + + def execute_subscribe_v1(self, subscription_handler, parse, callback, error_handler, is_trade=False): + request = self.create_request_v1(subscription_handler, parse, callback, error_handler, is_trade) + self.__create_websocket_manage(request) + + def execute_subscribe_v2(self, subscription_handler, parse, callback, error_handler, is_trade=False): + request = self.create_request_v2(subscription_handler, parse, callback, error_handler, is_trade) + self.__create_websocket_manage(request) + + def execute_subscribe_mbp(self, subscription_handler, parse, callback, error_handler, is_trade=False, + is_mbp_feed=True): + request = self.create_request(subscription_handler, parse, callback, error_handler, is_trade, is_mbp_feed) + self.__create_websocket_manage(request) + + def unsubscribe_all(self): + for websocket_manage in self.__websocket_manage_list: + SubscribeClient.subscribe_watch_dog.on_connection_closed(websocket_manage) + websocket_manage.close() + self.__websocket_manage_list.clear() diff --git a/huobi/connection/websocket_req_client.py b/huobi/connection/websocket_req_client.py new file mode 100644 index 0000000..5e0c07a --- /dev/null +++ b/huobi/connection/websocket_req_client.py @@ -0,0 +1,54 @@ +import logging + +from huobi.connection.impl.websocket_manage import WebsocketManage +from huobi.connection.impl.websocket_request import WebsocketRequest +from huobi.constant.system import WebSocketDefine + + +class WebSocketReqClient(object): + + def __init__(self, **kwargs): + """ + Create the subscription client to subscribe the update from server. + + :param kwargs: The option of subscription connection. + api_key: The public key applied from Huobi. + secret_key: The private key applied from Huobi. + url: Set the URI for subscription. + init_log: to init logger + """ + + self.__api_key = kwargs.get("api_key", None) + self.__secret_key = kwargs.get("secret_key", None) + self.__uri = kwargs.get("url", WebSocketDefine.Uri) + self.__init_log = kwargs.get("init_log", None) + if self.__init_log and self.__init_log: + logger = logging.getLogger("huobi-client") + logger.setLevel(level=logging.INFO) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) + logger.addHandler(handler) + + def __create_websocket_manage(self, request): + manager = WebsocketManage(self.__api_key, self.__secret_key, self.__uri, request) + manager.connect() + + def create_request(self, subscription_handler, parse, callback, error_handler, is_trade=False, is_mbp_feed=False): + request = WebsocketRequest() + request.subscription_handler = subscription_handler + request.is_trading = is_trade + request.is_mbp_feed = is_mbp_feed + request.auto_close = True # for websocket request, auto close the connection after request. + request.json_parser = parse + request.update_callback = callback + request.error_handler = error_handler + return request + + def execute_subscribe_v1(self, subscription_handler, parse, callback, error_handler, is_trade=False): + request = self.create_request(subscription_handler, parse, callback, error_handler, is_trade) + self.__create_websocket_manage(request) + + def execute_subscribe_mbp(self, subscription_handler, parse, callback, error_handler, is_trade=False, + is_mbp_feed=True): + request = self.create_request(subscription_handler, parse, callback, error_handler, is_trade, is_mbp_feed) + self.__create_websocket_manage(request) diff --git a/huobi/constant/__init__.py b/huobi/constant/__init__.py new file mode 100644 index 0000000..905e73b --- /dev/null +++ b/huobi/constant/__init__.py @@ -0,0 +1,7 @@ +from huobi.constant.definition import * +from huobi.constant.result import * +from huobi.constant.system import * +from huobi.constant.test import * + + + diff --git a/huobi/constant/definition.py b/huobi/constant/definition.py new file mode 100644 index 0000000..3fdafaa --- /dev/null +++ b/huobi/constant/definition.py @@ -0,0 +1,366 @@ +class CandlestickInterval: + MIN1 = "1min" + MIN5 = "5min" + MIN15 = "15min" + MIN30 = "30min" + MIN60 = "60min" + HOUR4 = "4hour" + DAY1 = "1day" + MON1 = "1mon" + WEEK1 = "1week" + YEAR1 = "1year" + INVALID = None + + +class OrderSide: + BUY = "buy" + SELL = "sell" + INVALID = None + + +class TradeDirection: + BUY = "buy" + SELL = "sell" + INVALID = None + + +class OrderType: + SELL_LIMIT = "sell-limit" + BUY_LIMIT = "buy-limit" + BUY_MARKET = "buy-market" + SELL_MARKET = "sell-market" + BUY_IOC = "buy-ioc" + SELL_IOC = "sell-ioc" + BUY_LIMIT_MAKER = "buy-limit-maker" + SELL_LIMIT_MAKER = "sell-limit-maker" + BUY_STOP_LIMIT = "buy-stop-limit" + SELL_STOP_LIMIT = "sell-stop-limit" + BUY_LIMIT_FOK = "buy-limit-fok" + SELL_LIMIT_FOK = "sell-limit-fok" + BUY_STOP_LIMIT_FOK = "buy-stop-limit-fok" + SELL_STOP_LIMIT_FOK = "sell-stop-limit-fok" + INVALID = None + + +class AlgoOrderType: + LIMIT = "limit" + MARKET = "market" + + +class AlgoOrderStatus: + CANCELED = "canceled" + REJECTED = "rejected" + TRIGGERED = "triggered" + + +class AccountType: + SPOT = "spot" + MARGIN = "margin" + OTC = "otc" + POINT = "point" + MINEPOLL = "minepool" + ETF = "etf" + AGENCY = "agency" + SUPER_MARGIN = "super-margin" + INVALID = None + + +class AccountState: + WORKING = "working" + LOCK = "lock" + INVALID = None + + +class AccountPointState: + WORKING = "working" + LOCK = "lock" + INVALID = None + FL_SYS = "fl-sys" + FL_MGT = "fl-mgt" + FL_END = "fl-end" + FL_NEGATIVE = "fl-negative" + + +class AccountBalanceUpdateType: + TRADE = "trade" + FROZEN = "frozen" + LOAN = "loan" + INTEREST = "interest" + LOAN_AVAILABLE = "loan-available" + TRANSFER_OUT_AVAILABLE = "transfer-out-available" + INVALID = None + + +class WithdrawState: + SUBMITTED = "submitted" + REEXAMINE = "reexamine" + CANCELED = "canceled" + PASS = "pass" + REJECT = "reject" + PRETRANSFER = "pre-transfer" + WALLETTRANSFER = "wallet-transfer" + WALEETREJECT = "wallet-reject" + CONFIRMED = "confirmed" + CONFIRMERROR = "confirm-error" + REPEALED = "repealed" + VERIFYING = "verifying" + FAILED = "failed" + INVALID = None + + +class DepositWithdraw: + DEPOSIT = "deposit" + WITHDRAW = "withdraw" + + +class DepositState: + CONFIRMING = "confirming" + SAFE = "safe" + CONFIRMED = "confirmed" + ORPHAN = "orphan" + INVALID = None + + +class LoanOrderState: + CREATED = "created" + ACCRUAL = "accrual" + CLEARED = "cleared" + FAILED = "failed" + INVALID = None + + +class OrderSource: + SYS = "sys" + WEB = "web" + API = "api" + APP = "app" + FL_SYS = "fl-sys" + FL_MGT = "fl-mgt" + SPOT_WEB = "spot-web" + SPOT_API = "spot-api" + SPOT_APP = "spot-app" + MARGIN_API = "margin-api" + MARGIN_WEB = "margin-web" + MARGIN_APP = "margin-app" + SUPER_MARGIN_API = "super-margin-api" + SUPER_MARGIN_WEB = "super-margin-web" + SUPER_MARGIN_APP = "super-margin-app" + SUPER_MARGIN_FL_SYS = "super-margin-fl-sys" + SUPER_MARGIN_FL_MGT = "super-margin-fl-mgt" + INVALID = None + + +class OrderState: + CREATED = "created" # for stop loss order + PRE_SUBMITTED = "pre-submitted" + SUBMITTING = "submitting" + SUBMITTED = "submitted" + PARTIAL_FILLED = "partial-filled" + CANCELLING = "cancelling" + PARTIAL_CANCELED = "partial-canceled" + FILLED = "filled" + CANCELED = "canceled" + FAILED = "failed" + PLACE_TIMEOUT = "place_timeout" + INVALID = None + + +class TimeInForceType: + IOC = "ioc" + FOK = "fok" + BOC = "boc" + GTC = "gtc" + + +class TransferMasterType: + IN = "master-transfer-in" + OUT = "master-transfer-out" + POINT_IN = "master-point-transfer-in" + POINT_OUT = "master-point-transfer-out" + INVALID = None + + +class EtfStatus: + NORMAL = "1" + REBALANCING_START = "2" + CREATION_AND_REDEMPTION_SUSPEND = "3" + CREATION_SUSPEND = "4" + REDEMPTION_SUSPEND = "5" + INVALID = None + + +class EtfSwapType: + IN = "1" + OUT = "2" + INVALID = None + + +class AccountChangeType: + NEWORDER = "order.place" + TRADE = "order.match" + REFUND = "order.refund" + CANCELORDER = "order.cancel" + FEE = "order.fee-refund" + TRANSFER = "margin.transfer" + LOAN = "margin.loan" + INTEREST = "margin.interest" + REPAY = "margin.repay" + OTHER = "other" + INVALID = None + + +class BalanceMode: + AVAILABLE = "0" + TOTAL = "1" + INVALID = None + + +class AccountBalanceMode: + BALANCE = "0" + TOTAL = "1" + INVALID = None + + +class OperateMode: + PING = "ping" + PONG = "pong" + INVALID = None + + +class QueryDirection: + PREV = "prev" + NEXT = "next" + INVALID = None + + +class TransferFuturesPro: + TO_PRO = "futures-to-pro" + TO_FUTURES = "pro-to-futures" + + +class MatchRole: + MAKER = "maker" + TAKER = "taker" + + +class DepthStep: + STEP0 = "step0" + STEP1 = "step1" + STEP2 = "step2" + STEP3 = "step3" + STEP4 = "step4" + STEP5 = "step5" + + +class DepthSize: + SIZE5 = 5 + SIZE10 = 10 + SIZE20 = 20 + + +class MbpLevel: + MBP5 = 5 + MBP10 = 10 + MBP20 = 20 + MBP150 = 150 + + +class ChainDepositStatus: + ALLOWED = "allowed" + PROHIBITED = "prohibited" + INVALID = None + + +class ChainWithdrawStatus: + ALLOWED = "allowed" + PROHIBITED = "prohibited" + INVALID = None + + +class InstrumentStatus: + NORMAL = "normal" + DELISTED = "delisted" + INVALID = None + + +class AccountChangeType: + ORDER_PLACE = "order-place" + ORDER_MATCH = "order-match" + ORDER_REFUND = "order-refund" + ORDER_CANCEL = "order-cancel" + ORDER_FEE_REFUND = "order-fee-refund" + MARGIN_TRANSFER = "margin-transfer" + MARGIN_LOAN = "margin-loan" + MARGIN_INTEREST = "margin-interest" + MARGIN_REPAY = "margin-repay" + OTHER = "other" + DEPOSIT = "deposit" + WITHDRAW = "withdraw" + INVALID = None + + +class FeeDeductType: + DEDUCT_BY_HT = "ht" + DEDUCT_BY_POINT = "point" + INVALID = None + + +class SubUidAction: + UNLOCK = "unlock" + LOCK = "lock" + INVALID = None + + +class SubUidState: + NORMAL = "normal" + LOCK = "lock" + INVALID = None + + +class OrderUpdateEventType: + CREATION = "creation" + TRADE = "trade" + CANCELLATION = "cancellation" + INVALID = None + + +class AccountTransactType: + TRADE = "trade" + ETF = "etf" + TRANSACT_FEE = "transact-fee" + DEDUCTION = "deduction" + TRANSFER = "transfer" + CREDIT = "credit" + LIQUIDATION = "liquidation" + INTEREST = "interest" + DEPOSIT = "deposit" + WITHDRAW = "withdraw" + WITHDRAW_FEE = "withdraw-fee" + EXCHANGE = "exchange" + OTHER = "other-types" + + +class SortDesc: + ASC = "asc" + DESC = "desc" + + +class SubuserTradePrivilegeType: + MARGIN = "isolated-margin" + SUPER_MARGIN = "cross-margin" + + +class SubUserTradeStatus: + ACTIVATED = "activated" + DEACTIVATED = "deactivated" + + +class MarketStatus: + NORMAL = 1 + HALTED = 2 + CANCEL_ONLY = 3 + + +class HaltReason: + EMERGENCY_MAINTENANCE = 2 + SCHEDULED_MAINTENANCE = 3 diff --git a/huobi/constant/result.py b/huobi/constant/result.py new file mode 100644 index 0000000..0026777 --- /dev/null +++ b/huobi/constant/result.py @@ -0,0 +1,11 @@ + +class OutputKey: + KeyData = "data" + KeyTick = "tick" + KeyChannelCh = "ch" + KeyChannelRep = "rep" + + + + + diff --git a/huobi/constant/system.py b/huobi/constant/system.py new file mode 100644 index 0000000..f459404 --- /dev/null +++ b/huobi/constant/system.py @@ -0,0 +1,32 @@ + +HUOBI_URL_PRO = "https://api.huobi.pro" +HUOBI_URL_VN = "https://api.huobi.vn" +HUOBI_URL_SO = "https://api.huobi.so" + + +HUOBI_WEBSOCKET_URI_PRO = "wss://api.huobi.pro" +HUOBI_WEBSOCKET_URI_VN = "wss://api.huobi.vn" +HUOBI_WEBSOCKET_URI_SO = "wss://api.huobi.so" + +class WebSocketDefine: + Uri = HUOBI_WEBSOCKET_URI_PRO + +class RestApiDefine: + Url = HUOBI_URL_PRO + +class HttpMethod: + GET = "GET" + GET_SIGN = "GET_SIGN" + POST = "POST" + POST_SIGN = "POST_SIGN" + + +class ApiVersion: + VERSION_V1 = "v1" + VERSION_V2 = "v2" + +def get_default_server_url(user_configed_url): + if user_configed_url and len(user_configed_url): + return user_configed_url + else: + return RestApiDefine.Url diff --git a/huobi/constant/test.py b/huobi/constant/test.py new file mode 100644 index 0000000..4dad710 --- /dev/null +++ b/huobi/constant/test.py @@ -0,0 +1,10 @@ +g_api_key="xxxxxx" +g_secret_key="xxxxxx" + + + + +g_sub_uid = 123456 + +g_account_id = 123456 + diff --git a/huobi/exception/__init__.py b/huobi/exception/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/exception/huobi_api_exception.py b/huobi/exception/huobi_api_exception.py new file mode 100644 index 0000000..5f18e28 --- /dev/null +++ b/huobi/exception/huobi_api_exception.py @@ -0,0 +1,14 @@ + +class HuobiApiException(Exception): + + RUNTIME_ERROR = "RuntimeError" + INPUT_ERROR = "InputError" + KEY_MISSING = "KeyMissing" + SYS_ERROR = "SysError" + SUBSCRIPTION_ERROR = "SubscriptionError" + ENV_ERROR = "EnvironmentError" + EXEC_ERROR = "ExecuteError" + + def __init__(self, error_code, error_message): + self.error_code = error_code + self.error_message = error_message diff --git a/huobi/model/__init__.py b/huobi/model/__init__.py new file mode 100644 index 0000000..fd40910 --- /dev/null +++ b/huobi/model/__init__.py @@ -0,0 +1,4 @@ + + + + diff --git a/huobi/model/account/__init__.py b/huobi/model/account/__init__.py new file mode 100644 index 0000000..78d8613 --- /dev/null +++ b/huobi/model/account/__init__.py @@ -0,0 +1,14 @@ +from huobi.model.account.account import Account +from huobi.model.account.account_balance import AccountBalance +from huobi.model.account.account_balance_req import AccountBalanceReq +from huobi.model.account.account_update import AccountUpdate +from huobi.model.account.account_update_event import AccountUpdateEvent +from huobi.model.account.balance import Balance +from huobi.model.account.complete_subaccount import CompleteSubAccount +from huobi.model.account.margin_balance_detail import MarginBalanceDetail +from huobi.model.account.account_history import AccountHistory +from huobi.model.account.sub_uid_management import SubUidManagement +from huobi.model.account.account_ledger import AccountLedger +from huobi.model.account.account_transfer_result import AccountTransferResult +from huobi.model.account.account_point_result import AccountPointResult +from huobi.model.account.account_point_transfer_result import AccountPointTransferResult diff --git a/huobi/model/account/account.py b/huobi/model/account/account.py new file mode 100644 index 0000000..d1e7535 --- /dev/null +++ b/huobi/model/account/account.py @@ -0,0 +1,27 @@ +from huobi.constant import * + + +class Account: + """ + The account information for spot account, margin account etc. + + :member + id: The unique account id. + account_type: The type of this account, possible value: spot, margin, otc, point. + account_state: The account state, possible value: working, lock. + balances: The balance list of the specified currency. The content is Balance class + + """ + + def __init__(self): + self.id = 0 + self.type = AccountType.INVALID + self.state = AccountState.INVALID + self.subtype = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.type, format_data + "Account Type") + PrintBasic.print_basic(self.state, format_data + "Account State") + PrintBasic.print_basic(self.subtype, format_data + "Subtype") diff --git a/huobi/model/account/account_asset_valuation.py b/huobi/model/account/account_asset_valuation.py new file mode 100644 index 0000000..c0b78ee --- /dev/null +++ b/huobi/model/account/account_asset_valuation.py @@ -0,0 +1,18 @@ +class AccountAssetValuationResult: + """ + The account information for spot account, margin account etc. + + :member + balance: balance valuation bases on given valuation currency. + timestamp: unix timestamp from server. + + """ + + def __init__(self): + self.balance = "" + self.timestamp = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.balance, format_data + "balance") + PrintBasic.print_basic(self.timestamp, format_data + "timestamp") diff --git a/huobi/model/account/account_balance.py b/huobi/model/account/account_balance.py new file mode 100644 index 0000000..66cbc48 --- /dev/null +++ b/huobi/model/account/account_balance.py @@ -0,0 +1,58 @@ +from huobi.constant import * +from huobi.model.account.balance import Balance +from huobi.utils import default_parse, default_parse_list_dict + + +class AccountBalance: + """ + The account information for spot account, margin account etc. + + :member + id: The unique account id. + account_type: The type of this account, possible value: spot, margin, otc, point. + account_state: The account state, possible value: working, lock. + list: The balance list of the specified currency. The content is Balance class + + """ + + def __init__(self): + self.id = 0 + self.type = AccountType.INVALID + self.state = AccountState.INVALID + self.subtype = "" + self.list = list() + + @staticmethod + def json_parse(data_dict): + if data_dict and len(data_dict): + balance_list = data_dict.get("list") + data_dict.pop("list") + account_balance_obj = default_parse(data_dict, AccountBalance, Balance) + account_balance_obj.subtype = data_dict.get("subtype", data_dict.get("symbol")) + account_balance_obj.list = default_parse_list_dict(balance_list, Balance, []) + return account_balance_obj + + return None + + @staticmethod + def json_parse_list(data_list): + account_balance_list = [] + if data_list and len(data_list): + for item in data_list: + item_obj = AccountBalance.json_parse(item) + if item_obj: + account_balance_list.append(item_obj) + return account_balance_list + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "Account ID") + PrintBasic.print_basic(self.type, format_data + "Account Type") + PrintBasic.print_basic(self.state, format_data + "Account State") + PrintBasic.print_basic(self.subtype, format_data + "Subtype") + + print() + if len(self.list): + for row in self.list: + row.print_object(format_data+"\t") + print() diff --git a/huobi/model/account/account_balance_req.py b/huobi/model/account/account_balance_req.py new file mode 100644 index 0000000..9539db2 --- /dev/null +++ b/huobi/model/account/account_balance_req.py @@ -0,0 +1,30 @@ + +class AccountBalanceReq: + """ + The account change information received by subscription of account. + + :member + ts: The UNIX formatted timestamp generated by server in UTC. + cid: client request ID + topic: request Channel or Topic + data: The list of account and balance + + """ + + def __init__(self): + self.ts = 0 + self.cid = "" + self.topic = "" + self.data = list() + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ts, format_data + "Timestamp") + PrintBasic.print_basic(self.cid, format_data + "Client Order ID") + PrintBasic.print_basic(self.topic, format_data + "Topic") + print() + if len(self.data): + for account_balance in self.data: + account_balance.print_object() + print() \ No newline at end of file diff --git a/huobi/model/account/account_history.py b/huobi/model/account/account_history.py new file mode 100644 index 0000000..3719483 --- /dev/null +++ b/huobi/model/account/account_history.py @@ -0,0 +1,36 @@ +class AccountHistory: + """ + The account information for spot account, margin account etc. + + :member + account_id: Account Id. + currency: Currency name + transact_amt: Amount change (positive value if income, negative value if outcome) + transact-type: Amount change type + avail_balance: Available balance + acct_balance: Account balance + transact_time: Transaction time (database time) + record_id: Unique record ID in the database + + """ + + def __init__(self): + self.account_id = 0 + self.currency = "" + self.transact_amt = "" + self.transact_type = "" + self.avail_balance = "" + self.acct_balance = "" + self.transact_time = 0 + self.record_id = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.account_id, format_data + "Account Id") + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.transact_amt, format_data + "Transact Amount") + PrintBasic.print_basic(self.transact_type, format_data + "Transact Type") + PrintBasic.print_basic(self.avail_balance, format_data + "Avail Balance") + PrintBasic.print_basic(self.acct_balance, format_data + "Account Balance") + PrintBasic.print_basic(self.transact_time, format_data + "Transact Time") + PrintBasic.print_basic(self.record_id, format_data + "Record Id") diff --git a/huobi/model/account/account_ledger.py b/huobi/model/account/account_ledger.py new file mode 100644 index 0000000..bf08d4a --- /dev/null +++ b/huobi/model/account/account_ledger.py @@ -0,0 +1,38 @@ +class AccountLedger: + """ + The account ledger information. + + :member + accountId: Account ID. + currency: Cryptocurrency. + transactAmt: Transaction amount (income positive, expenditure negative). + transactType: Transaction type. + transferType: Transfer type (only valid for transactType=transfer). + transactId: Transaction ID. + transactTime: Transaction time. + transferer: Transferer’s account ID. + transferee: Transferee’s account ID. + """ + + def __init__(self): + self.accountId = 0 + self.currency = "" + self.transactAmt = 0.0 + self.transactType = "" + self.transferType = "" + self.transactId = 0 + self.transactTime = 0 + self.transferer = 0 + self.transferee = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.accountId, format_data + "Account ID") + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.transactAmt, format_data + "Transaction Amount") + PrintBasic.print_basic(self.transactType, format_data + "Transaction Type") + PrintBasic.print_basic(self.transferType, format_data + "Transfer Type") + PrintBasic.print_basic(self.transactId, format_data + "Transaction ID") + PrintBasic.print_basic(self.transactTime, format_data + "Transaction Time") + PrintBasic.print_basic(self.transferer, format_data + "Transferer’s Account ID") + PrintBasic.print_basic(self.transferee, format_data + "Transferee’s Account ID") \ No newline at end of file diff --git a/huobi/model/account/account_point_group.py b/huobi/model/account/account_point_group.py new file mode 100644 index 0000000..2a5223f --- /dev/null +++ b/huobi/model/account/account_point_group.py @@ -0,0 +1,25 @@ +from huobi.constant import * + + +class AccountPointGroup: + """ + The account information for spot account, margin account etc. + + :member + id: The unique account id. + account_type: The type of this account, possible value: spot, margin, otc, point. + account_state: The account state, possible value: working, lock. + list: The balance list of the specified currency. The content is Balance class + + """ + + def __init__(self): + self.groupId = "" + self.expiryDate = "" + self.remainAmt = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.groupId, format_data + "Group Id") + PrintBasic.print_basic(self.expiryDate, format_data + "Expiration date") + PrintBasic.print_basic(self.remainAmt, format_data + "Remain Amount") diff --git a/huobi/model/account/account_point_result.py b/huobi/model/account/account_point_result.py new file mode 100644 index 0000000..4a28b1a --- /dev/null +++ b/huobi/model/account/account_point_result.py @@ -0,0 +1,46 @@ +from huobi.constant import * +from huobi.model.account.account_point_group import AccountPointGroup +from huobi.utils import default_parse, default_parse_list_dict + + +class AccountPointResult: + """ + The account information for spot account, margin account etc. + + :member + id: The unique account id. + account_type: The type of this account, possible value: spot, margin, otc, point. + account_state: The account state, possible value: working, lock. + list: The balance list of the specified currency. The content is Balance class + + """ + + def __init__(self): + self.accountId = "" + self.accountStatus = AccountPointState.INVALID + self.groupIds = list() + self.acctBalance = "" + + @staticmethod + def json_parse(data_dict): + if data_dict and len(data_dict): + group_ids = data_dict.get("groupIds") + data_dict.pop("groupIds") + account_point_obj = default_parse(data_dict, AccountPointResult, AccountPointGroup) + account_point_obj.subtype = data_dict.get("subtype", data_dict.get("symbol")) + account_point_obj.list = default_parse_list_dict(group_ids, AccountPointGroup, []) + return account_point_obj + + return None + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.accountId, format_data + "Account ID") + PrintBasic.print_basic(self.accountStatus, format_data + "Account Status") + PrintBasic.print_basic(self.acctBalance, format_data + "Account Balance") + + print() + if len(self.groupIds): + for row in self.groupIds: + row.print_object(format_data + "\t") + print() diff --git a/huobi/model/account/account_point_transfer_result.py b/huobi/model/account/account_point_transfer_result.py new file mode 100644 index 0000000..bc9c72f --- /dev/null +++ b/huobi/model/account/account_point_transfer_result.py @@ -0,0 +1,20 @@ +class AccountPointTransferResult: + """ + The account change information received by subscription of account. + + :member + timestamp: The UNIX formatted timestamp generated by server in UTC. + change_type: The event that asset change notification related. + account_change_list: The list of account change, the content is AccountChange class + + """ + + def __init__(self): + self.transactId = "" + self.transactTime = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.transactId, format_data + "transactId") + PrintBasic.print_basic(self.transactTime, format_data + "transactTime") + self.data.print_object() diff --git a/huobi/model/account/account_transfer_result.py b/huobi/model/account/account_transfer_result.py new file mode 100644 index 0000000..9c5ae59 --- /dev/null +++ b/huobi/model/account/account_transfer_result.py @@ -0,0 +1,20 @@ +class AccountTransferResult: + """ + The account information for spot account, margin account etc. + + :member + id: The unique account id. + account_type: The type of this account, possible value: spot, margin, otc, point. + account_state: The account state, possible value: working, lock. + balances: The balance list of the specified currency. The content is Balance class + + """ + + def __init__(self): + self.transact_id = 0 + self.transact_time = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.transact_id, format_data + "TransactionId") + PrintBasic.print_basic(self.transact_time, format_data + "TransactionTime") diff --git a/huobi/model/account/account_update.py b/huobi/model/account/account_update.py new file mode 100644 index 0000000..ca3ad1d --- /dev/null +++ b/huobi/model/account/account_update.py @@ -0,0 +1,35 @@ +from huobi.constant import * + + +class AccountUpdate: + """ + The account change information received by subscription of account. + + :member + currency: The currency of the change. + accountId: The account id. + balance: Account balance (only exists when account balance changed) + available: Available balance (only exists when available balance changed) + changeType: Change type see AccountChangeType, valid value: order-place,order-match,order-refund,order-cancel,order-fee-refund,margin-transfer,margin-loan,margin-interest,margin-repay,other, + accountType: Account type see AccountBalanceUpdateType, valid value: trade, frozen, loan, interest + changeTime: Change time, unix time in millisecond + """ + + def __init__(self): + self.currency = "" + self.accountId = 0 + self.balance = "" + self.available = "" + self.changeType = AccountChangeType.INVALID + self.accountType = AccountBalanceUpdateType.INVALID + self.changeTime = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.accountId, format_data + "Account ID") + PrintBasic.print_basic(self.balance, format_data + "Balance") + PrintBasic.print_basic(self.available, format_data + "Available") + PrintBasic.print_basic(self.changeType, format_data + "Account Change Type") + PrintBasic.print_basic(self.accountType, format_data + "Account Balance Change Type") + PrintBasic.print_basic(self.changeTime, format_data + "Account Timestamp") diff --git a/huobi/model/account/account_update_event.py b/huobi/model/account/account_update_event.py new file mode 100644 index 0000000..4400983 --- /dev/null +++ b/huobi/model/account/account_update_event.py @@ -0,0 +1,23 @@ +from huobi.constant import * +from huobi.model.account import AccountUpdate + + +class AccountUpdateEvent: + """ + The account change information received by subscription of account. + + :member + timestamp: The UNIX formatted timestamp generated by server in UTC. + change_type: The event that asset change notification related. + account_change_list: The list of account change, the content is AccountChange class + + """ + + def __init__(self): + self.ch = 0 + self.data = AccountUpdate() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ch, format_data + "Topic") + self.data.print_object() \ No newline at end of file diff --git a/huobi/model/account/balance.py b/huobi/model/account/balance.py new file mode 100644 index 0000000..01621e7 --- /dev/null +++ b/huobi/model/account/balance.py @@ -0,0 +1,25 @@ +from huobi.constant import * +from huobi.utils import default_parse_list_dict + + +class Balance: + """ + The balance of specified account. + + :member + currency: The currency of this balance. + balance_type: The balance type, trade or frozen. + balance: The balance in the main currency unit. + + """ + + def __init__(self): + self.currency = "" + self.type = AccountBalanceUpdateType.INVALID + self.balance = 0.0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.type, format_data + "Balance Type") + PrintBasic.print_basic(self.balance, format_data + "Balance") \ No newline at end of file diff --git a/huobi/model/account/complete_subaccount.py b/huobi/model/account/complete_subaccount.py new file mode 100644 index 0000000..a49a163 --- /dev/null +++ b/huobi/model/account/complete_subaccount.py @@ -0,0 +1,27 @@ + +from huobi.constant import * + + +class CompleteSubAccount: + """ + Sub-account completed info + + :member + id: The sub-id. + account_type: The sub account type. + balances: The balance list, the content is Balance class. + """ + + def __init__(self): + self.id = 0 + self.account_type = AccountType.INVALID + self.balances = list() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.account_type, format_data + "Account Type") + if len(self.balances): + for row in self.balances: + row.print_object() + print() \ No newline at end of file diff --git a/huobi/model/account/margin_balance_detail.py b/huobi/model/account/margin_balance_detail.py new file mode 100644 index 0000000..6e7da7a --- /dev/null +++ b/huobi/model/account/margin_balance_detail.py @@ -0,0 +1,24 @@ + +from huobi.constant import * + + +class MarginBalanceDetail: + def __init__(self): + self.id = 0 + self.symbol = 0 + self.state = AccountState.INVALID + self.type = AccountType.INVALID + self.risk_rate = 0.0 + self.fl_price = 0.0 + self.fl_type = "" + self.sub_account_balance_list = list() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.type, format_data + "Account Type") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.state, format_data + "Account State") + PrintBasic.print_basic(self.fl_price, format_data + "Burst Price") + PrintBasic.print_basic(self.fl_type, format_data + "Burst Type") + PrintBasic.print_basic(self.risk_rate, format_data + "Risk Rate") \ No newline at end of file diff --git a/huobi/model/account/sub_uid_management.py b/huobi/model/account/sub_uid_management.py new file mode 100644 index 0000000..9d3266e --- /dev/null +++ b/huobi/model/account/sub_uid_management.py @@ -0,0 +1,20 @@ +from huobi.constant import SubUidState + + +class SubUidManagement: + """ + The trade information with price and amount etc. + + :member + subUid: sub user ID. + userState: sub user account state, states see SubUidState. + """ + + def __init__(self): + self.subUid = 0 + self.userState = SubUidState.INVALID + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.subUid, format_data + "subUid") + PrintBasic.print_basic(self.userState, format_data + "userState") diff --git a/huobi/model/algo/__init__.py b/huobi/model/algo/__init__.py new file mode 100644 index 0000000..a7279a2 --- /dev/null +++ b/huobi/model/algo/__init__.py @@ -0,0 +1,3 @@ +from huobi.model.algo.cancel_order_result import CancelOrderResult +from huobi.model.algo.order_list_item import OrderListItem +from huobi.model.algo.order_history_item import OrderHistoryItem diff --git a/huobi/model/algo/cancel_order_result.py b/huobi/model/algo/cancel_order_result.py new file mode 100644 index 0000000..1ab3faa --- /dev/null +++ b/huobi/model/algo/cancel_order_result.py @@ -0,0 +1,17 @@ +class CancelOrderResult: + """ + The result of batch cancel operation. + + :member + accepted: The clientOrderIds accepted. + rejected: The clientOrderIds rejected . + + """ + + def __init__(self): + self.accepted = [] + self.rejected = [] + + def print_object(self, format_data=""): + print("Success Order Counts", len(self.accepted), " accepted Order Ids : ", self.accepted) + print("Fail Order Counts", len(self.rejected), " Rejected Order Ids : ", self.rejected) \ No newline at end of file diff --git a/huobi/model/algo/order_history_item.py b/huobi/model/algo/order_history_item.py new file mode 100644 index 0000000..f9b9020 --- /dev/null +++ b/huobi/model/algo/order_history_item.py @@ -0,0 +1,51 @@ +class OrderHistoryItem: + """ + The result of batch cancel operation. + + :member + orderOrigTime + lastActTime + symbol + source + orderSide + orderType + timeInForce + clientOrderId + accountId + orderPrice + orderSize + stopPrice + orderStatus + + """ + + def __init__(self): + self.orderOrigTime = "" + self.lastActTime = "" + self.symbol = "" + self.source = "" + self.orderSide = "" + self.orderType = "" + self.timeInForce = "" + self.clientOrderId = "" + self.accountId = "" + self.orderPrice = "" + self.orderSize = "" + self.stopPrice = "" + self.orderStatus = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.orderOrigTime, format_data + "") + PrintBasic.print_basic(self.lastActTime, format_data + "") + PrintBasic.print_basic(self.symbol, format_data + "") + PrintBasic.print_basic(self.source, format_data + "") + PrintBasic.print_basic(self.orderSide, format_data + "") + PrintBasic.print_basic(self.orderType, format_data + "") + PrintBasic.print_basic(self.timeInForce, format_data + "") + PrintBasic.print_basic(self.clientOrderId, format_data + "") + PrintBasic.print_basic(self.accountId, format_data + "") + PrintBasic.print_basic(self.orderPrice, format_data + "") + PrintBasic.print_basic(self.orderSize, format_data + "") + PrintBasic.print_basic(self.stopPrice, format_data + "") + PrintBasic.print_basic(self.orderStatus, format_data + "") diff --git a/huobi/model/algo/order_list_item.py b/huobi/model/algo/order_list_item.py new file mode 100644 index 0000000..ee1e5b7 --- /dev/null +++ b/huobi/model/algo/order_list_item.py @@ -0,0 +1,73 @@ +class OrderListItem: + """ + The result of batch cancel operation. + + :member + event_type: + symbol: + order_id: + trade_price: + trade_volume: + order_side: + aggressor: + trade_id: + trade_time: + transact_fee: + fee_deduct: + fee_deduct_type: + fee_currency: + account_id: + source: + order_price: + order_size: + client_order_id: + order_create_time: + order_status: + """ + + def __init__(self): + self.eventType = "" + self.symbol = "" + self.orderId = "" + self.tradePrice = "" + self.tradeVolume = "" + self.orderSide = "" + self.aggressor = "" + self.tradeId = "" + self.tradeTime = "" + self.transactFee = "" + self.feeDeduct = "" + self.feeDeductType = "" + self.feeCurrency = "" + self.accountId = "" + self.source = "" + self.orderPrice = "" + self.orderSize = "" + self.clientOrderId = "" + self.orderCreateTime = "" + self.orderStatus = "" + self.trailingRate = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.eventType, format_data + "Event Type") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.orderId, format_data + "OrderId") + PrintBasic.print_basic(self.tradePrice, format_data + "Trade Price") + PrintBasic.print_basic(self.tradeVolume, format_data + "Trade Volume") + PrintBasic.print_basic(self.orderSide, format_data + "Order Side") + PrintBasic.print_basic(self.aggressor, format_data + "Aggressor") + PrintBasic.print_basic(self.tradeId, format_data + "TradeId") + PrintBasic.print_basic(self.tradeTime, format_data + "Trade Time") + PrintBasic.print_basic(self.transactFee, format_data + "Transact Fee") + PrintBasic.print_basic(self.feeDeduct, format_data + "Fee Deduct") + PrintBasic.print_basic(self.feeDeductType, format_data + "Fee Deduct Type") + PrintBasic.print_basic(self.feeCurrency, format_data + "Fee Currency") + PrintBasic.print_basic(self.accountId, format_data + "Account Id") + PrintBasic.print_basic(self.source, format_data + "Source") + PrintBasic.print_basic(self.orderPrice, format_data + "Order Price") + PrintBasic.print_basic(self.orderSize, format_data + "Order Size") + PrintBasic.print_basic(self.clientOrderId, format_data + "Client Order Id") + PrintBasic.print_basic(self.orderCreateTime, format_data + "Order Create Time") + PrintBasic.print_basic(self.orderStatus, format_data + "Order Status") + PrintBasic.print_basic(self.trailingRate, format_data + "Trailing Rate (Trailing Order Only)") diff --git a/huobi/model/etf/__init__.py b/huobi/model/etf/__init__.py new file mode 100644 index 0000000..2ee6750 --- /dev/null +++ b/huobi/model/etf/__init__.py @@ -0,0 +1,4 @@ +from huobi.model.etf.etf_swap_config import EtfSwapConfig +from huobi.model.etf.etf_swap_list import EtfSwapList +from huobi.model.etf.etf_swap_in_out import EtfSwapInOut +from huobi.model.etf.unitprice import UnitPrice \ No newline at end of file diff --git a/huobi/model/etf/etf_swap_config.py b/huobi/model/etf/etf_swap_config.py new file mode 100644 index 0000000..aa8ea8e --- /dev/null +++ b/huobi/model/etf/etf_swap_config.py @@ -0,0 +1,48 @@ +from huobi.constant import * + + +class EtfSwapConfig: + """ + The basic information of ETF creation and redemption, as well as ETF constituents, including max + amount of creation, min amount of creation, max amount of redemption, min amount of redemption, + creation fee rate, redemption fee rate, eft create/redeem status. + + :member + purchase_max_amount: The max creation amounts per request. + purchase_min_amount: The minimum creation amounts per request. + redemption_max_amount: The max redemption amounts per request. + redemption_min_amount: The minimum redemption amounts per request. + purchase_fee_rate: The creation fee rate. + redemption_fee_rate: The redemption fee rate. + status: The status of the ETF. + unit_price_list: ETF constitution in format of amount and currency. + + """ + def __init__(self): + self.etf_name = "" + self.etf_status = EtfStatus.INVALID + self.purchase_fee_rate = 0.0 + self.purchase_max_amount = 0 + self.purchase_min_amount = 0 + self.redemption_fee_rate = 0.0 + self.redemption_max_amount = 0 + self.redemption_min_amount = 0 + self.unit_price = list() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.etf_name, format_data + "ETF Name") + PrintBasic.print_basic(self.etf_status, format_data + "ETF Status") + + PrintBasic.print_basic(self.purchase_fee_rate, format_data + "Purchase Fee Rate") + PrintBasic.print_basic(self.purchase_max_amount, format_data + "Purchase Max Amount") + PrintBasic.print_basic(self.purchase_min_amount, format_data + "Purchase Min Amount") + + PrintBasic.print_basic(self.redemption_fee_rate, format_data + "Redemption Fee Rate") + PrintBasic.print_basic(self.redemption_max_amount, format_data + "Redemption Max Amount") + PrintBasic.print_basic(self.redemption_min_amount, format_data + "Redemption Min Amount") + print() + if len(self.unit_price): + for row in self.unit_price: + row.print_object(format_data) + print() diff --git a/huobi/model/etf/etf_swap_in_out.py b/huobi/model/etf/etf_swap_in_out.py new file mode 100644 index 0000000..0641c19 --- /dev/null +++ b/huobi/model/etf/etf_swap_in_out.py @@ -0,0 +1,19 @@ + + +class EtfSwapInOut: + """ + :member + """ + + def __init__(self): + self.code = 0 + self.data = None + self.message = "" + self.success = False + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.code, format_data + "Return Code") + PrintBasic.print_basic_bool(self.data, format_data + "Data") + PrintBasic.print_basic(self.message, format_data + "Message") + PrintBasic.print_basic_bool(self.success, format_data + "Success") diff --git a/huobi/model/etf/etf_swap_list.py b/huobi/model/etf/etf_swap_list.py new file mode 100644 index 0000000..be879ec --- /dev/null +++ b/huobi/model/etf/etf_swap_list.py @@ -0,0 +1,88 @@ +from huobi.constant import * +from huobi.model.etf.unitprice import UnitPrice +from huobi.utils import default_parse_list_dict + + +class EtfSwapList: + """ + The past creation and redemption. + + :member + id: the operation Id. + gmt_created: The UNIX formatted timestamp in UTC of the operation. + currency: The ETF name. + amount: Creation or redemption amount. + type: The swap type. Creation or redemption. + status: The operation result + rate: The fee rate. + fee: The actual fee amount + + point_card_amount: Discount from point card. + used_currency_list: For creation this is the list and amount of underlying assets used for ETF creation. + For redemption this is the amount of ETF used for redemption. The content is UnitPrice class. + obtain_currency_list: For creation this is the amount for ETF created. + For redemption this is the list and amount of underlying assets obtained. The content is UnitPrice class + """ + + def __init__(self): + self.id = 0 + self.gmt_created = 0 + self.currency = "" + self.amount = 0.0 + self.type = EtfSwapType.INVALID + self.status = 0 + self.rate = 0.0 + self.fee = 0.0 + self.point_card_amount = 0.0 + self.used_currency_list = list() + self.obtain_currency_list = list() + + @staticmethod + def json_parse(dict_data): + if dict_data and len(dict_data): + detail = dict_data.get("detail", {}) + dict_data.pop("detail") + etf_swap_obj = default_parse_list_dict(dict_data, EtfSwapList) + if detail and len(detail): + etf_swap_obj.rate = detail.get("rate", 0) + etf_swap_obj.fee = detail.get("fee", 0) + etf_swap_obj.point_card_amount = detail.get("point_card_amount", 0) + etf_swap_obj.used_currency_list = default_parse_list_dict(detail.get("used_currency_list"), UnitPrice, []) + etf_swap_obj.obtain_currency_list = default_parse_list_dict(detail.get("obtain_currency_list"), UnitPrice, []) + return etf_swap_obj + + return None + + @staticmethod + def json_parse_list(dict_data): + ret_list = list() + for item in dict_data: + item_obj = EtfSwapList.json_parse(item) + if item_obj is not None: + ret_list.append(item_obj) + + return ret_list + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "Operater Id") + PrintBasic.print_basic(self.gmt_created, format_data + "GMT Create Time") + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.type, format_data + "Type") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.rate, format_data + "Rate") + PrintBasic.print_basic(self.fee, format_data + "Fee") + PrintBasic.print_basic(self.status, format_data + "Status") + PrintBasic.print_basic(self.point_card_amount, format_data + "Point Card Amount") + + if len(self.used_currency_list): + PrintBasic.print_basic("used_currency_list as below:") + for row in self.used_currency_list: + row.print_object(format_data + "\t") + + if len(self.obtain_currency_list): + PrintBasic.print_basic("obtain_currency_list as below:") + for row in self.obtain_currency_list: + row.print_object(format_data + "\t") + diff --git a/huobi/model/etf/unitprice.py b/huobi/model/etf/unitprice.py new file mode 100644 index 0000000..b9c3340 --- /dev/null +++ b/huobi/model/etf/unitprice.py @@ -0,0 +1,10 @@ +class UnitPrice: + + def __init__(self): + self.currency = "" + self.amount = 0.0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.amount, format_data + "Amount") \ No newline at end of file diff --git a/huobi/model/generic/__init__.py b/huobi/model/generic/__init__.py new file mode 100644 index 0000000..08d45b4 --- /dev/null +++ b/huobi/model/generic/__init__.py @@ -0,0 +1,5 @@ +from huobi.model.generic.symbol import Symbol +from huobi.model.generic.exchange_info import ExchangeInfo +from huobi.model.generic.chain import Chain +from huobi.model.generic.reference_currency import ReferenceCurrency +from huobi.model.generic.market_status import MarketStatus diff --git a/huobi/model/generic/chain.py b/huobi/model/generic/chain.py new file mode 100644 index 0000000..1ae896d --- /dev/null +++ b/huobi/model/generic/chain.py @@ -0,0 +1,69 @@ +from huobi.constant import * + +class Chain: + """ + The Huobi Chain. + + :member + chain: Chain name + numOfConfirmations: Number of confirmations required for deposit success (trading & withdrawal allowed once reached) + numOfFastConfirmations: Number of confirmations required for quick success (trading allowed but withdrawal disallowed once reached) + minDepositAmt: Minimal deposit amount in each request + depositStatus: Deposit status allowed,prohibited + minWithdrawAmt: Minimal withdraw amount in each request. + maxWithdrawAmt : Maximum withdraw amount in each request + withdrawQuotaPerDay : Maximum withdraw amount in a day + withdrawQuotaPerYear : Maximum withdraw amount in a year + withdrawQuotaTotal : Maximum withdraw amount in total + withdrawPrecision : Withdraw amount precision + withdrawFeeType : Type of withdraw fee (only one type can be applied to each currency) + + transactFeeWithdraw : Withdraw fee in each request (only applicable to withdrawFeeType = fixed) + minTransactFeeWithdraw : Minimal withdraw fee in each request (only applicable to withdrawFeeType = circulated) + maxTransactFeeWithdraw : Maximum withdraw fee in each request (only applicable to withdrawFeeType = circulated or ratio) + transactFeeRateWithdraw : Withdraw fee in each request (only applicable to withdrawFeeType = ratio) + withdrawStatus : Withdraw status + """ + + def __init__(self): + self.chain = "" + self.baseChain = "" + self.baseChainProtocol = "" + self.numOfConfirmations = 0 + self.numOfFastConfirmations = 0 + self.depositStatus = ChainDepositStatus.INVALID + self.minDepositAmt = 0 + self.withdrawStatus = ChainWithdrawStatus.INVALID + self.minWithdrawAmt = 0 + self.withdrawPrecision = 0 + self.maxWithdrawAmt = 0.0 + self.withdrawQuotaPerDay = 0.0 + self.withdrawQuotaPerYear = 0.0 + self.withdrawQuotaTotal = 0.0 + self.withdrawFeeType = "" + self.transactFeeWithdraw = 0.0 + self.minTransactFeeWithdraw = 0.0 + self.maxTransactFeeWithdraw = 0.0 + self.transactFeeRateWithdraw = 0.0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.chain, format_data + "Chain") + PrintBasic.print_basic(self.baseChain, format_data + "Base Chain") + PrintBasic.print_basic(self.baseChainProtocol, format_data + "Base Chain Protocol") + PrintBasic.print_basic(self.numOfConfirmations, format_data + "numOfConfirmations") + PrintBasic.print_basic(self.numOfFastConfirmations, format_data + "numOfFastConfirmations") + PrintBasic.print_basic(self.depositStatus, format_data + "depositStatus") + PrintBasic.print_basic(self.minDepositAmt, format_data + "minDepositAmount") + PrintBasic.print_basic(self.withdrawStatus, format_data + "withdrawStatus") + PrintBasic.print_basic(self.minWithdrawAmt, format_data + "minWithdrawAmount") + PrintBasic.print_basic(self.withdrawPrecision, format_data + "withdrawPrecision") + PrintBasic.print_basic(self.maxWithdrawAmt, format_data + "maxWithdrawAmount") + PrintBasic.print_basic(self.withdrawQuotaPerDay, format_data + "withdrawQuotaPerDay") + PrintBasic.print_basic(self.withdrawQuotaPerYear, format_data + "withdrawQuotaPerYear") + PrintBasic.print_basic(self.withdrawQuotaTotal, format_data + "withdrawQuotaTotal") + PrintBasic.print_basic(self.withdrawFeeType, format_data + "withdrawFeeType") + PrintBasic.print_basic(self.transactFeeWithdraw, format_data + "transactFeeWithdraw") + PrintBasic.print_basic(self.minTransactFeeWithdraw, format_data + "minTransactFeeWithdraw") + PrintBasic.print_basic(self.maxTransactFeeWithdraw, format_data + "maxTransactFeeWithdraw") + PrintBasic.print_basic(self.transactFeeRateWithdraw, format_data + "transactFeeRateWithdraw") diff --git a/huobi/model/generic/exchange_info.py b/huobi/model/generic/exchange_info.py new file mode 100644 index 0000000..d0e6871 --- /dev/null +++ b/huobi/model/generic/exchange_info.py @@ -0,0 +1,12 @@ +class ExchangeInfo: + """ + The Huobi supported the symbols and currencies. + + :member + symbol_list: The symbol list. The content is Symbol class. + currencies: The currency list. The content is string value. + """ + + def __init__(self): + self.symbol_list = list() + self.currencies = list() diff --git a/huobi/model/generic/market_status.py b/huobi/model/generic/market_status.py new file mode 100644 index 0000000..4a5ac5c --- /dev/null +++ b/huobi/model/generic/market_status.py @@ -0,0 +1,20 @@ +from huobi.constant import * + + +class MarketStatus: + """ + The Huobi market status info. + + :member + marketStatus: . + haltStartTime: . + haltEndTime: . + haltReason: + affectedSymbols: + """ + + def __init__(self): + self.marketStatus = MarketStatus.NORMAL + self.haltStartTime = -1 + self.haltEndTime = -1 + self.affectedSymbols = "" diff --git a/huobi/model/generic/reference_currency.py b/huobi/model/generic/reference_currency.py new file mode 100644 index 0000000..9e52751 --- /dev/null +++ b/huobi/model/generic/reference_currency.py @@ -0,0 +1,26 @@ +from huobi.constant import * + +class ReferenceCurrency: + """ + The Huobi supported static reference information for each currency. + + :member + currency: currency + instStatus: Instrument status + chains: chain list + """ + + def __init__(self): + self.currency = "" + self.instStatus = InstrumentStatus.INVALID + self.chains = [] + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.instStatus, format_data + "Instrument Status") + if self.chains and len(self.chains): + for chain_obj in self.chains: + chain_obj.print_object("\t") + print() diff --git a/huobi/model/generic/symbol.py b/huobi/model/generic/symbol.py new file mode 100644 index 0000000..e498c2a --- /dev/null +++ b/huobi/model/generic/symbol.py @@ -0,0 +1,66 @@ +class Symbol: + """ + The Huobi supported symbols. + + :member + base_currency: The base currency in a trading symbol. + quote_currency: The quote currency in a trading symbol. + price_precision: The quote currency precision when quote price (decimal places). + amount_precision: The base currency precision when quote amount (decimal places). + symbol_partition: The trading section, possible values: [main,innovation,bifurcation]. + symbol: The symbol, like "btcusdt". + state : trade status, maybe one in [online,offline,suspend] + value_precision : value precision + min_order_amt : minimum volume limit only used in limit-order and sell-market order + max_order_amt : Maximum volume + min_order_value : Minimum order amount + leverage_ratio : Leverage ratio for symbol + limit_order_min_order_amt: Minimum order amount of limit order in base currency (NEW) + limit_order_max_order_amt: Max order amount of limit order in base currency (NEW) + sell_market_min_order_amt: Minimum order amount of sell-market order in base currency (NEW) + sell_market_max_order_amt: Max order amount of sell-market order in base currency (NEW) + buy_market_max_order_amt: Max order value of buy-market order in quote currency (NEW) + max_order_value: Max order value of limit order and buy-market order in usdt (NEW) + + """ + + def __init__(self): + self.base_currency = "" + self.quote_currency = "" + self.price_precision = 0 + self.amount_precision = 0 + self.symbol_partition = "" + self.symbol = "" + self.state = "" + self.value_precision = 0 + self.min_order_amt = "" + self.max_order_amt = "" + self.min_order_value = "" + self.leverage_ratio = 0 + self.limit_order_min_order_amt = 0 + self.limit_order_max_order_amt = 0 + self.sell_market_min_order_amt = 0 + self.sell_market_max_order_amt = 0 + self.buy_market_max_order_value = 0 + self.max_order_value = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.base_currency, format_data + "Base Currency") + PrintBasic.print_basic(self.quote_currency, format_data + "Quote Currency") + PrintBasic.print_basic(self.price_precision, format_data + "Price Precision") + PrintBasic.print_basic(self.amount_precision, format_data + "Amount Precision") + PrintBasic.print_basic(self.symbol_partition, format_data + "Symbol Partition") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.state, format_data + "State") + PrintBasic.print_basic(self.value_precision, format_data + "Value Precision") + PrintBasic.print_basic(self.min_order_amt, format_data + "Min Order Amount") + PrintBasic.print_basic(self.max_order_amt, format_data + "Max Order Amount") + PrintBasic.print_basic(self.min_order_value, format_data + "Min Order Value") + PrintBasic.print_basic(self.leverage_ratio, format_data + "Leverage Ratio") + PrintBasic.print_basic(self.limit_order_min_order_amt, format_data + "Minimum order amount (Limit Order)") + PrintBasic.print_basic(self.limit_order_max_order_amt, format_data + "Max order amount (Limit Order)") + PrintBasic.print_basic(self.sell_market_min_order_amt, format_data + "Min order amount (Sell Market Order)") + PrintBasic.print_basic(self.sell_market_max_order_amt, format_data + "Max order amount (Sell Market Order)") + PrintBasic.print_basic(self.buy_market_max_order_value, format_data + "Max order value (Buy Market Order)") + PrintBasic.print_basic(self.max_order_value, format_data + "Max order value (In USDT)") diff --git a/huobi/model/margin/__init__.py b/huobi/model/margin/__init__.py new file mode 100644 index 0000000..9e38399 --- /dev/null +++ b/huobi/model/margin/__init__.py @@ -0,0 +1,8 @@ +from huobi.model.margin.loan_order import LoanOrder +from huobi.model.margin.margin_account_balance import MarginAccountBalance +from huobi.model.margin.loan_ino import LoanInfo +from huobi.model.margin.margin_loan_ino import MarginLoanInfo +from huobi.model.margin.cross_margin_loan_ino import CrossMarginLoanInfo +from huobi.model.margin.cross_margin_account_balance import CrossMarginAccountBalance + + diff --git a/huobi/model/margin/cross_margin_account_balance.py b/huobi/model/margin/cross_margin_account_balance.py new file mode 100644 index 0000000..d62a89d --- /dev/null +++ b/huobi/model/margin/cross_margin_account_balance.py @@ -0,0 +1,49 @@ +from huobi.constant import * +from huobi.model.account import Balance +from huobi.utils import * + + +class CrossMarginAccountBalance: + + """ + The account information for spot account, margin account etc. + + :member + id: The unique account id. + type: The type of this account, possible value: spot, margin, otc, point. + state: The account state, possible value: working, lock. + list: The balance list of the specified currency. The content is Balance class + """ + + + def __init__(self): + self.id = 0 + self.type = AccountType.INVALID + self.state = AccountState.INVALID + self.risk_rate = 0 + self.acct_balance_sum = 0.0 + self.debt_balance_sum = 0.0 + self.list = list() + + @staticmethod + def json_parse(data_json): + balance_list_json = data_json.get("list", []) + data_json.pop("list") + + account_balance = default_parse_list_dict(data_json, CrossMarginAccountBalance) + account_balance.list = default_parse_list_dict(balance_list_json, Balance, []) + + return account_balance + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "Account ID") + PrintBasic.print_basic(self.type, format_data + "Account Type") + PrintBasic.print_basic(self.state, format_data + "Account State") + PrintBasic.print_basic(self.risk_rate, format_data + "Risk Rate") + PrintBasic.print_basic(self.acct_balance_sum, format_data + "Total Balance") + PrintBasic.print_basic(self.debt_balance_sum, format_data + "Debt Balance") + if self.list and len(self.list): + for balance in self.list: + balance.print_object("\t") + print() diff --git a/huobi/model/margin/cross_margin_loan_ino.py b/huobi/model/margin/cross_margin_loan_ino.py new file mode 100644 index 0000000..e9b6986 --- /dev/null +++ b/huobi/model/margin/cross_margin_loan_ino.py @@ -0,0 +1,9 @@ + +from huobi.model.margin.loan_ino import LoanInfo + +class CrossMarginLoanInfo(LoanInfo): + def __init__(self): + LoanInfo.__init__(self) + + def print_object(self, format_data=""): + LoanInfo.print_object(self) diff --git a/huobi/model/margin/general_repay_loan_record.py b/huobi/model/margin/general_repay_loan_record.py new file mode 100644 index 0000000..d0e0760 --- /dev/null +++ b/huobi/model/margin/general_repay_loan_record.py @@ -0,0 +1,59 @@ +from huobi.constant import * + + +class GeneralRepayLoanRecord: + """ + The general repay loan record information. + + :member + repayId: repayment transaction ID. + repayTime: repayment transaction time (unix time in millisecond). + accountId: repayment account ID. + currency: repayment currency, like "usdt". + repaidAmount: repaid amount. + transactIds: ID list of original loan transactions (arranged by order of repayment time). + nextId: search the start ID in the next page (return only when there is data in the next page). + """ + + def __init__(self): + self.repayId = None + self.repayTime = None + self.accountId = None + self.currency = None + self.repaidAmount = None + self.transactIds = Transact() + self.nextId = None + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.repayId, format_data + "repayId") + PrintBasic.print_basic(self.repayTime, format_data + "repayTime") + PrintBasic.print_basic(self.accountId, format_data + "accountId") + PrintBasic.print_basic(self.currency, format_data + "currency") + PrintBasic.print_basic(self.repaidAmount, format_data + "repaidAmount") + PrintBasic.print_basic(self.transactIds, format_data + "transactIds") + PrintBasic.print_basic(self.nextId, format_data + "nextId") + + print() + + +class Transact: + + """ + The general repay loan record information. + + :member + transactId: original loan transaction ID. + repaidPrincipal: principal repaid. + repaidInterest: interest repaid. + paidHt: HT paid. + paidPoint: point paid. + """ + + def __init__(self): + self.transactId = None + self.repaidPrincipal = None + self.repaidInterest = None + self.paidHt = None + self.paidPoint = None + diff --git a/huobi/model/margin/general_repay_loan_result.py b/huobi/model/margin/general_repay_loan_result.py new file mode 100644 index 0000000..1b65954 --- /dev/null +++ b/huobi/model/margin/general_repay_loan_result.py @@ -0,0 +1,31 @@ +from huobi.constant import * + + +class GeneralRepayLoanResult: + """ + The margin order information. + + :member + id: Inner id. + type: The account type. + state: The account state. + symbol: The symbol, like "btcusdt". + fl_price: The trigger price. + fl_type: The trigger type. + risk_rate: The risk rate. + list:Balance Object list + """ + + def __init__(self): + self.repayId = 0 + self.repayTime = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.repayId, format_data + "repayId") + PrintBasic.print_basic(self.repayTime, format_data + "repayTime") + + print() + + + diff --git a/huobi/model/margin/loan_ino.py b/huobi/model/margin/loan_ino.py new file mode 100644 index 0000000..27bdba9 --- /dev/null +++ b/huobi/model/margin/loan_ino.py @@ -0,0 +1,30 @@ + +class LoanInfo: + """ + The margin rate define. + + :member + currency: The currency name. + interest_rate: all interest rate + min_loan_amt: min loan amount. + max_loan_amt: max loan amount. + loanable_amt: loanable amount. + actual_rate: rate after deduction. + """ + + def __init__(self): + self.currency = "" + self.interest_rate = "" + self.min_loan_amt = "" + self.max_loan_amt = "" + self.loanable_amt = "" + self.actual_rate = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.interest_rate, format_data + "Interest Rate") + PrintBasic.print_basic(self.min_loan_amt, format_data + "Min Loan Amount") + PrintBasic.print_basic(self.max_loan_amt, format_data + "Max Loan Amount") + PrintBasic.print_basic(self.loanable_amt, format_data + "Loanable Amount") + PrintBasic.print_basic(self.actual_rate, format_data + "Actual Rate") diff --git a/huobi/model/margin/loan_order.py b/huobi/model/margin/loan_order.py new file mode 100644 index 0000000..8d92fe2 --- /dev/null +++ b/huobi/model/margin/loan_order.py @@ -0,0 +1,62 @@ +from huobi.constant import * + + +class LoanOrder: + """ + The margin order information. + + :member + id: The order id. + user_id: The user id. + account_type: The account type which created the loan order. + currency: The currency name. + loan_amount: The amount of the origin loan. + loan_balance: The amount of the loan left. + interest_rate: The loan interest rate. + interest_amount: The accumulated loan interest. + interest_balance: The amount of loan interest left. + state: The loan stats, possible values: created, accrual, cleared, invalid. + created_at: The UNIX formatted timestamp in UTC when the order was created. + accrued_at: The UNIX formatted timestamp in UTC when the last accrue happened. + """ + + def __init__(self): + self.currency = "" + self.deduct_rate = 0 + self.paid_point = 0.0 + self.deduct_currency = "" + self.user_id = 0 + self.created_at = 0 + self.account_id = 0 + self.paid_coin = 0.0 + self.loan_amount = 0.0 + self.interest_amount = 0.0 + self.deduct_amount = 0.0 + self.loan_balance = 0.0 + self.interest_balance = 0.0 + self.updated_at = 0 + self.accrued_at = 0 + self.interest_rate = 0.0 + self.id = 0 + self.state = LoanOrderState.INVALID + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.deduct_rate, format_data + "Deduct Rate") + PrintBasic.print_basic(self.paid_point, format_data + "Paid Point") + PrintBasic.print_basic(self.deduct_currency, format_data + "Deduct Currency") + PrintBasic.print_basic(self.user_id, format_data + "User Id") + PrintBasic.print_basic(self.created_at, format_data + "Create Time") + PrintBasic.print_basic(self.account_id, format_data + "Account Id") + PrintBasic.print_basic(self.paid_coin, format_data + "Paid Coin") + PrintBasic.print_basic(self.loan_amount, format_data + "Load Amount") + PrintBasic.print_basic(self.interest_amount, format_data + "Interest Amount") + PrintBasic.print_basic(self.deduct_amount, format_data + "Deduct Amount") + PrintBasic.print_basic(self.loan_balance, format_data + "Loan Balance") + PrintBasic.print_basic(self.interest_balance, format_data + "Interest Balance") + PrintBasic.print_basic(self.updated_at, format_data + "Update Time") + PrintBasic.print_basic(self.accrued_at, format_data + "Accrued Time") + PrintBasic.print_basic(self.interest_rate, format_data + "Interest Rate") + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.state, format_data + "Loan Order State") diff --git a/huobi/model/margin/margin_account_balance.py b/huobi/model/margin/margin_account_balance.py new file mode 100644 index 0000000..f5aff02 --- /dev/null +++ b/huobi/model/margin/margin_account_balance.py @@ -0,0 +1,41 @@ +from huobi.constant import * + + +class MarginAccountBalance: + """ + The margin order information. + + :member + id: Inner id. + type: The account type. + state: The account state. + symbol: The symbol, like "btcusdt". + fl_price: The trigger price. + fl_type: The trigger type. + risk_rate: The risk rate. + list:Balance Object list + """ + + def __init__(self): + self.id = 0 + self.type = AccountType.INVALID + self.state = AccountState.INVALID + self.symbol = "" + self.fl_price = 0.0 + self.fl_type = 0.0 + self.risk_rate = 0.0 + self.list = [] + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.type, format_data + "Account Type") + PrintBasic.print_basic(self.state, format_data + "Account State") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.fl_price, format_data + "Trigger Price") + PrintBasic.print_basic(self.fl_type, format_data + "Trigger Type") + PrintBasic.print_basic(self.risk_rate, format_data + "Risk Rate") + if self.list and len(self.list): + for balance_obj in self.list: + balance_obj.print_object("\t") + print() \ No newline at end of file diff --git a/huobi/model/margin/margin_loan_ino.py b/huobi/model/margin/margin_loan_ino.py new file mode 100644 index 0000000..e7f3e48 --- /dev/null +++ b/huobi/model/margin/margin_loan_ino.py @@ -0,0 +1,40 @@ +from huobi.model.margin.loan_ino import LoanInfo +from huobi.utils import default_parse_list_dict + + +class MarginLoanInfo: + """ + The margin loan info. + + :member + symbol: symbol like "btcusdt" + currencies: loan info for currency in symbol + """ + + def __init__(self): + self.symbol = "" + self.currencies = list() + + @staticmethod + def json_parse(json_data): + retList = [] + for idx, item in enumerate(json_data): + margin_loan_obj = MarginLoanInfo() + margin_loan_obj.symbol = item.get("symbol", "") + + currencies_json = item.get("currencies") + result_list = default_parse_list_dict(currencies_json, LoanInfo, []) + + margin_loan_obj.currencies = result_list + + retList.append(margin_loan_obj) + + return retList + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + if self.currencies and len(self.currencies): + for currency_item in self.currencies: + currency_item.print_object("\t") + print() diff --git a/huobi/model/market/__init__.py b/huobi/model/market/__init__.py new file mode 100644 index 0000000..ffb0366 --- /dev/null +++ b/huobi/model/market/__init__.py @@ -0,0 +1,26 @@ +from huobi.model.market.candlestick import Candlestick +from huobi.model.market.candlestick_event import CandlestickEvent +from huobi.model.market.candlestick_req import CandlestickReq +from huobi.model.market.last_trade_bestquote import LastTradeAndBestQuote +from huobi.model.market.pricedepth import PriceDepth +from huobi.model.market.pricedepth_event import PriceDepthEvent +from huobi.model.market.pricedepth_req import PriceDepthReq +from huobi.model.market.pricedepth_bbo import PriceDepthBbo +from huobi.model.market.pricedepth_bbo_event import PriceDepthBboEvent +from huobi.model.market.market_detail_merged import MarketDetailMerged +from huobi.model.market.market_detail import MarketDetail +from huobi.model.market.market_detail_event import MarketDetailEvent +from huobi.model.market.market_detail_req import MarketDetailReq +from huobi.model.market.trade import Trade +from huobi.model.market.trade_detail import TradeDetail +from huobi.model.market.trade_detail_event import TradeDetailEvent +from huobi.model.market.trade_detail_req import TradeDetailReq +from huobi.model.market.market_ticker import MarketTicker +from huobi.model.market.depth_entry import DepthEntry +from huobi.model.market.mbp import Mbp +from huobi.model.market.mbp_increase_event import MbpIncreaseEvent +from huobi.model.market.mbp_full_event import MbpFullEvent +from huobi.model.market.mbp_req import MbpReq + + + diff --git a/huobi/model/market/candlestick.py b/huobi/model/market/candlestick.py new file mode 100644 index 0000000..2bfdc61 --- /dev/null +++ b/huobi/model/market/candlestick.py @@ -0,0 +1,41 @@ + +class Candlestick: + """ + The candlestick/kline data. + + :member + id : keep the original timestamp + timestamp: The UNIX formatted timestamp in UTC. + high: The high price. + low: The low price. + open: The opening price. + close: The closing price. + amount: The aggregated trading volume in USDT. + count: The number of completed trades. it returns 0 when get ETF candlestick + vol: The trading volume in base currency. + + """ + + def __init__(self): + self.id = 0 + #self.timestamp = 0 + self.high = 0.0 + self.low = 0.0 + self.open = 0.0 + self.close = 0.0 + self.amount = 0.0 + self.count = 0 + self.vol = 0.0 #self.volume = 0.0 + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "Id") + #PrintBasic.print_basic(self.timestamp, format_data + "Unix Time") + PrintBasic.print_basic(self.high, format_data + "High") + PrintBasic.print_basic(self.low, format_data + "Low") + PrintBasic.print_basic(self.open, format_data + "Open") + PrintBasic.print_basic(self.close, format_data + "Close") + PrintBasic.print_basic(self.count, format_data + "Count") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.vol, format_data + "Volume") \ No newline at end of file diff --git a/huobi/model/market/candlestick_event.py b/huobi/model/market/candlestick_event.py new file mode 100644 index 0000000..47784bb --- /dev/null +++ b/huobi/model/market/candlestick_event.py @@ -0,0 +1,26 @@ +from huobi.constant import * +from huobi.model.market import * + + +class CandlestickEvent: + """ + The candlestick/kline data received by subscription of candlestick/kline. + + :member + ch: the topic you subscribed + ts: the UNIX formatted timestamp generated by server in UTC. + tick: the data of candlestick/kline. + """ + + def __init__(self): + self.ch = "" + self.ts = 0 + self.tick = Candlestick() + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ts, format_data + "Unix Time") + PrintBasic.print_basic(self.ch, format_data + "Channel") + if self.tick: + self.tick.print_object() diff --git a/huobi/model/market/candlestick_req.py b/huobi/model/market/candlestick_req.py new file mode 100644 index 0000000..db4d625 --- /dev/null +++ b/huobi/model/market/candlestick_req.py @@ -0,0 +1,27 @@ +from huobi.constant import * + +class CandlestickReq: + """ + The candlestick/kline data received by subscription of candlestick/kline. + + :member + rep: the Channel or topic you subscribed. + id: the UNIX formatted timestamp generated by server in UTC. + data: the data of candlestick/kline. + + """ + + def __init__(self): + self.rep = "" + self.id = 0 + self.data = list() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.rep, format_data + "Channel") + PrintBasic.print_basic(self.id, format_data + "Unix Time") + print() + if len(self.data): + for row in self.data: + row.print_object() + print() \ No newline at end of file diff --git a/huobi/model/market/depth_entry.py b/huobi/model/market/depth_entry.py new file mode 100644 index 0000000..e043789 --- /dev/null +++ b/huobi/model/market/depth_entry.py @@ -0,0 +1,25 @@ + +class DepthEntry: + """ + An depth entry consisting of price and amount. + + :member + price: The price of the depth. + amount: The amount of the depth. + """ + + def __init__(self): + self.price = 0.0 + self.amount = 0.0 + + @staticmethod + def json_parse(data_array): + entry = DepthEntry() + entry.price = data_array[0] + entry.amount = data_array[1] + return entry + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.price, format_data + "Price") + PrintBasic.print_basic(self.amount, format_data + "Amount") \ No newline at end of file diff --git a/huobi/model/market/last_trade_bestquote.py b/huobi/model/market/last_trade_bestquote.py new file mode 100644 index 0000000..88977fe --- /dev/null +++ b/huobi/model/market/last_trade_bestquote.py @@ -0,0 +1,22 @@ +class LastTradeAndBestQuote: + """ + The last trade and best bid/ask. + + :member + last_trade_price: The last trade price. + last_trade_amount: The last trade amount. + ask_price: The best ask price. + ask_amount: The best ask amount. + bid_price: The best bid price. + bid_amount: The best bid amount. + + """ + + def __init__(self): + self.last_trade_price = 0.0 + self.last_trade_amount = 0.0 + self.ask_price = 0.0 + self.ask_amount = 0.0 + self.bid_price = 0.0 + self.bid_amount = 0.0 + diff --git a/huobi/model/market/market_detail.py b/huobi/model/market/market_detail.py new file mode 100644 index 0000000..094b3de --- /dev/null +++ b/huobi/model/market/market_detail.py @@ -0,0 +1,38 @@ +class MarketDetail: + """ + The summary of trading in the market for the last 24 hours + + :member + id: response ID + open: The opening price of last 24 hours. + close: The last price of last 24 hours. + amount: The aggregated trading volume in USDT. + high: The high price of last 24 hours. + low: The low price of last 24 hours. + count: The number of completed trades. + volume: The trading volume in base currency of last 24 hours. + version: inner data + """ + + def __init__(self): + self.id = 0 + self.open = 0.0 + self.close = 0.0 + self.amount = 0.0 + self.high = 0.0 + self.low = 0.0 + self.count = 0 + self.vol = 0.0 + self.version = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.open, format_data + "Open") + PrintBasic.print_basic(self.close, format_data + "Close") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.high, format_data + "High") + PrintBasic.print_basic(self.low, format_data + "Low") + PrintBasic.print_basic(self.count, format_data + "Count") + PrintBasic.print_basic(self.vol, format_data + "Volume") + # PrintBasic.print_basic(self.version, format_data + "Version") \ No newline at end of file diff --git a/huobi/model/market/market_detail_event.py b/huobi/model/market/market_detail_event.py new file mode 100644 index 0000000..3f70ca4 --- /dev/null +++ b/huobi/model/market/market_detail_event.py @@ -0,0 +1,22 @@ +from huobi.model.market.market_detail import MarketDetail + +class MarketDetailEvent: + """ + The 24H trade statistics received by subscription of trade statistics. + + :member + Channel: topic you subscribe, include symbol. + timestamp: The UNIX formatted timestamp generated by server in UTC. + trade_statistics: The trade statistics. + """ + + def __init__(self): + self.ch = "" + self.ts = 0 + self.tick = MarketDetail() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ch, format_data + "Channel") + PrintBasic.print_basic(self.ts, format_data + "Timestamp") + self.tick.print_object() \ No newline at end of file diff --git a/huobi/model/market/market_detail_merged.py b/huobi/model/market/market_detail_merged.py new file mode 100644 index 0000000..5fe8ab4 --- /dev/null +++ b/huobi/model/market/market_detail_merged.py @@ -0,0 +1,41 @@ + +class MarketDetailMerged: + """ + The best bid/ask consisting of price and amount. + + :member + timestamp: The Unix formatted timestamp in UTC. + bid_price: The best bid price. + bid_amount: The best bid amount. + ask_price: The best ask price. + ask_amount: The best ask amount. + + """ + + def __init__(self): + self.amount = 0 + self.open = 0.0 + self.close = 0.0 + self.high = 0.0 + self.id = 0 + self.count = 0.0 + self.low = 0.0 + self.version = 0 + self.ask = [] + self.vol = 0.0 + self.bid = [] + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + # PrintBasic.print_basic(self.version, format_data + "Version") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.count, format_data + "Count") + PrintBasic.print_basic(self.vol, format_data + "Volume") + + PrintBasic.print_basic(self.open, format_data + "Open") + PrintBasic.print_basic(self.close, format_data + "Close") + PrintBasic.print_basic(self.high, format_data + "High") + PrintBasic.print_basic(self.low, format_data + "Low") + + print("Ask", self.ask) + print("Bid", self.bid) \ No newline at end of file diff --git a/huobi/model/market/market_detail_req.py b/huobi/model/market/market_detail_req.py new file mode 100644 index 0000000..1b73809 --- /dev/null +++ b/huobi/model/market/market_detail_req.py @@ -0,0 +1,22 @@ +from huobi.model.market.market_detail import MarketDetail + +class MarketDetailReq: + """ + The 24H trade statistics received by request of trade statistics only once. + + :member + rep: The topic you subscribed. + ts: The UNIX formatted timestamp generated by server in UTC. + trade_statistics: The trade statistics. + """ + + def __init__(self): + self.rep = 0 + self.ts = 0 + self.data = MarketDetail() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ts, format_data + "Timestamp") + PrintBasic.print_basic(self.rep, format_data + "Channel") + self.data.print_object() \ No newline at end of file diff --git a/huobi/model/market/market_ticker.py b/huobi/model/market/market_ticker.py new file mode 100644 index 0000000..d9662da --- /dev/null +++ b/huobi/model/market/market_ticker.py @@ -0,0 +1,47 @@ +class MarketTicker: + """ + The ticker information. + + :member + amount: The aggregated trading volume in last 24 hours (rotating 24h). + count: The number of completed trades of last 24 hours (rotating 24h). + open: The opening price of a nature day (Singapore time). + close: The last price of a nature day (Singapore time). + low: The low price of a nature day (Singapore time). + high: The high price of a nature day (Singapore time). + vol: The aggregated trading value in last 24 hours (rotating 24h). + symbol: The trading symbol of this object, e.g. btcusdt, bccbtc. + bid: Best bid price. + bidSize: Best bid size. + ask: Best ask price. + askSize: Best ask size. + """ + + def __init__(self): + self.amount = 0.0 + self.count = 0 + self.open = 0.0 + self.close = 0.0 + self.low = 0.0 + self.high = 0.0 + self.vol = 0.0 + self.symbol = "" + self.bid = 0.0 + self.bidSize = 0.0 + self.ask = 0.0 + self.askSize = 0.0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.count, format_data + "Count") + PrintBasic.print_basic(self.open, format_data + "Opening Price") + PrintBasic.print_basic(self.close, format_data + "Last Price") + PrintBasic.print_basic(self.low, format_data + "Low Price") + PrintBasic.print_basic(self.high, format_data + "High Price") + PrintBasic.print_basic(self.vol, format_data + "Vol") + PrintBasic.print_basic(self.symbol, format_data + "Trading Symbol") + PrintBasic.print_basic(self.bid, format_data + "Best Bid Price") + PrintBasic.print_basic(self.bidSize, format_data + "Best Bid Size") + PrintBasic.print_basic(self.ask, format_data + "Best Ask Price") + PrintBasic.print_basic(self.askSize, format_data + "Best Ask Size") \ No newline at end of file diff --git a/huobi/model/market/mbp.py b/huobi/model/market/mbp.py new file mode 100644 index 0000000..fb962cc --- /dev/null +++ b/huobi/model/market/mbp.py @@ -0,0 +1,54 @@ + + +from huobi.model.market.depth_entry import DepthEntry + +class Mbp: + """ + Increasement of price depth information. + + :member + seqNum: current seqNum. + prevSeqNum: previous seqNum. + bids: The list of the bid depth. The content is DepthEntry class. + asks: The list of the ask depth. The content is DepthEntry class. + + """ + def __init__(self): + self.seqNum = 0 + self.prevSeqNum = 0 + self.bids = list() + self.asks = list() + + @staticmethod + def json_parse(json_data): + mbp = Mbp() + bid_list = list() + mbp.seqNum = json_data.get("seqNum", 0) + mbp.prevSeqNum = json_data.get("prevSeqNum", 0) # prevSeqNum only for increased subscribe, request doesn't have this value + for item in json_data.get("bids", []): + depth_entry = DepthEntry() + depth_entry.price = item[0] + depth_entry.amount = item[1] + bid_list.append(depth_entry) + ask_list = list() + for item in json_data.get("asks", []): + depth_entry = DepthEntry() + depth_entry.price = item[0] + depth_entry.amount = item[1] + ask_list.append(depth_entry) + mbp.bids = bid_list + mbp.asks = ask_list + + return mbp + + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.seqNum, format_data + "seqNum") + PrintBasic.print_basic(self.prevSeqNum, format_data + "prevSeqNum") + for entry in self.bids: + PrintBasic.print_basic(str(entry.price) + ", amount: " + str(entry.amount), format_data + "Bids price: ") + + for entry in self.asks: + PrintBasic.print_basic(str(entry.price) + ", amount: " + str(entry.amount), format_data + "Asks price: ") diff --git a/huobi/model/market/mbp_full_event.py b/huobi/model/market/mbp_full_event.py new file mode 100644 index 0000000..e70112e --- /dev/null +++ b/huobi/model/market/mbp_full_event.py @@ -0,0 +1,33 @@ +from huobi.model.market import Mbp + + +class MbpFullEvent: + """ + full price depth. + + :member + ch: Topic of subscribed. + timestamp: The UNIX formatted timestamp generated by server in UTC. + data: The price depth. + + """ + + def __init__(self): + self.ch = "" + self.ts = 0 + self.data = Mbp() + + @staticmethod + def json_parse(json_data): + mbp_event = MbpFullEvent() + mbp_event.ts = json_data.get("ts") + mbp_event.ch = json_data.get("ch") + mbp = Mbp.json_parse(json_data.get("tick", {})) + mbp_event.data = mbp + return mbp_event + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ch, format_data + "Topic") + PrintBasic.print_basic(self.ts, format_data + "Timestamp") + self.data.print_object(format_data + "\t") \ No newline at end of file diff --git a/huobi/model/market/mbp_increase_event.py b/huobi/model/market/mbp_increase_event.py new file mode 100644 index 0000000..c60da0b --- /dev/null +++ b/huobi/model/market/mbp_increase_event.py @@ -0,0 +1,34 @@ +from huobi.model.market import Mbp + + +class MbpIncreaseEvent: + """ + increasement of price depth. + + :member + ch: Topic of subscribed. + timestamp: The UNIX formatted timestamp generated by server in UTC. + data: The price depth. + + """ + + def __init__(self): + self.ch = "" + self.ts = 0 + self.data = Mbp() + + @staticmethod + def json_parse(json_data): + mbp_event = MbpIncreaseEvent() + mbp_event.ts = json_data.get("ts") + mbp_event.ch = json_data.get("ch") + mbp = Mbp.json_parse(json_data.get("tick", {})) + mbp_event.data = mbp + return mbp_event + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ch, format_data + "Topic") + PrintBasic.print_basic(self.ts, format_data + "Timestamp") + + self.data.print_object(format_data + "\t") diff --git a/huobi/model/market/mbp_req.py b/huobi/model/market/mbp_req.py new file mode 100644 index 0000000..31f4fd9 --- /dev/null +++ b/huobi/model/market/mbp_req.py @@ -0,0 +1,36 @@ +from huobi.model.market import Mbp, PriceDepth + + +class MbpReq: + """ + The market price depth. + + :member + rep: request Topic + id: The UNIX formatted timestamp generated by server in UTC. + data: The price depth. + """ + + def __init__(self): + self.rep = "" + self.id = "" + + self.data = Mbp() + + + @staticmethod + def json_parse(data_json): + mbp_event = MbpReq() + mbp_event.id = data_json.get("id") + mbp_event.rep = data_json.get("rep") + data = data_json.get("data", {}) + mbp = Mbp.json_parse(data) + mbp_event.data = mbp + return mbp_event + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.rep, format_data + "Topic") + PrintBasic.print_basic(self.id, format_data + "Timestamp") + + self.data.print_object(format_data + "\t") diff --git a/huobi/model/market/pricedepth.py b/huobi/model/market/pricedepth.py new file mode 100644 index 0000000..814a120 --- /dev/null +++ b/huobi/model/market/pricedepth.py @@ -0,0 +1,59 @@ +from huobi.model.market.depth_entry import DepthEntry + + +class PriceDepth: + """ + The price depth information. + + :member + ts: The UNIX formatted timestamp in UTC. + version: + bids: The list of the bid depth. The content is DepthEntry class. + asks: The list of the ask depth. The content is DepthEntry class. + + """ + def __init__(self): + self.ts = 0 + self.version = 0 + self.bids = list() + self.asks = list() + + @staticmethod + def json_parse(dict_data): + price_depth_obj = PriceDepth() + price_depth_obj.ts = dict_data.get("ts") + price_depth_obj.version = dict_data.get("version") + bid_list = list() + bids_array = dict_data.get("bids", []) + for item in bids_array: + depth_entry = DepthEntry.json_parse(item) + bid_list.append(depth_entry) + ask_list = list() + asks_array = dict_data.get("asks", []) + for item in asks_array: + depth_entry = DepthEntry.json_parse(item) + ask_list.append(depth_entry) + price_depth_obj.bids = bid_list + price_depth_obj.asks = ask_list + + return price_depth_obj + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ts, format_data + "UTC Time") + PrintBasic.print_basic(self.version, format_data + "Version") + if len(self.bids): + i = 0 + print(format_data, "---- Top " + str(len(self.bids)) + " bids ----") + for entry in self.bids: + i = i + 1 + print(format_data, str(i) + ": price: " + str(entry.price) + ", amount: " + str(entry.amount)) + #print() + + if len(self.asks): + i = 0 + print(format_data, "---- Top " + str(len(self.asks)) + " asks ----") + for entry in self.asks: + i = i + 1 + print(format_data, str(i) + ": price: " + str(entry.price) + ", amount: " + str(entry.amount)) + #print() \ No newline at end of file diff --git a/huobi/model/market/pricedepth_bbo.py b/huobi/model/market/pricedepth_bbo.py new file mode 100644 index 0000000..001a1b7 --- /dev/null +++ b/huobi/model/market/pricedepth_bbo.py @@ -0,0 +1,34 @@ + +class PriceDepthBbo: + """ + The price depth information. + + :member + timestamp: The UNIX formatted timestamp in UTC. + bid: the first bid near trade value. + bidSize: the bid size. + ask: The first ask near trade value. + askSize: the ask size. + quoteTime : quote time + symbol : trade symbol + + + """ + def __init__(self): + self.seqId = 0 + self.ask = 0.0 + self.askSize = 0.0 + self.bid = 0.0 + self.bidSize = 0.0 + self.quoteTime = 0 + self.symbol = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.seqId, format_data + "Seq ID") + PrintBasic.print_basic(self.ask, format_data + "Ask") + PrintBasic.print_basic(self.askSize, format_data + "Ask Size") + PrintBasic.print_basic(self.bid, format_data + "Bid") + PrintBasic.print_basic(self.bidSize, format_data + "Bid Size") + PrintBasic.print_basic(self.quoteTime, format_data + "Quote Time") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") \ No newline at end of file diff --git a/huobi/model/market/pricedepth_bbo_event.py b/huobi/model/market/pricedepth_bbo_event.py new file mode 100644 index 0000000..f65d560 --- /dev/null +++ b/huobi/model/market/pricedepth_bbo_event.py @@ -0,0 +1,25 @@ +from huobi.model.market import PriceDepthBbo + + +class PriceDepthBboEvent: + """ + The price depth received by subscription of price depth. + + :member + symbol: The symbol you subscribed. + timestamp: The UNIX formatted timestamp generated by server in UTC. + data: The price depth. + + """ + + def __init__(self): + self.ts = 0 + self.ch = "" + self.tick = PriceDepthBbo() + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ts, format_data + "Time") + PrintBasic.print_basic(self.ch, format_data + "Channel") + self.tick.print_object(format_data) \ No newline at end of file diff --git a/huobi/model/market/pricedepth_event.py b/huobi/model/market/pricedepth_event.py new file mode 100644 index 0000000..aa960be --- /dev/null +++ b/huobi/model/market/pricedepth_event.py @@ -0,0 +1,23 @@ + +from huobi.model.market.pricedepth import PriceDepth +class PriceDepthEvent: + """ + The price depth information. + + :member + ts: The UNIX formatted timestamp in UTC. + version: + bids: The list of the bid depth. The content is DepthEntry class. + asks: The list of the ask depth. The content is DepthEntry class. + + """ + def __init__(self): + self.ch = "" + self.tick = PriceDepth() + + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ch, format_data + "Channel") + self.tick.print_object("\t") \ No newline at end of file diff --git a/huobi/model/market/pricedepth_req.py b/huobi/model/market/pricedepth_req.py new file mode 100644 index 0000000..f04f993 --- /dev/null +++ b/huobi/model/market/pricedepth_req.py @@ -0,0 +1,22 @@ + +from huobi.model.market.pricedepth import PriceDepth + +class PriceDepthReq: + """ + The price depth information. + + :member + ts: The UNIX formatted timestamp in UTC. + version: + bids: The list of the bid depth. The content is DepthEntry class. + asks: The list of the ask depth. The content is DepthEntry class. + + """ + def __init__(self): + self.rep = "" + self.data = PriceDepth() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.rep, format_data + "Channel") + self.data.print_object("\t") \ No newline at end of file diff --git a/huobi/model/market/trade.py b/huobi/model/market/trade.py new file mode 100644 index 0000000..e621ed0 --- /dev/null +++ b/huobi/model/market/trade.py @@ -0,0 +1,29 @@ + +class Trade: + """ + The trade information with price and amount etc. + + :member + price: The trading price in quote currency. + amount: The trading volume in base currency. + trade_id: The unique trade id of this trade. + timestamp: The UNIX formatted timestamp generated by server in UTC. + direction: The direction of the taker trade: 'buy' or 'sell'. + """ + + def __init__(self): + self.price = 0.0 + self.amount = 0.0 + self.trade_id = 0 + self.ts = 0 + self.direction = "" + + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.trade_id, format_data + "Trade Id") + PrintBasic.print_basic(self.ts, format_data + "Trade Time") + PrintBasic.print_basic(self.price, format_data + "Price") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.direction, format_data + "Direction") \ No newline at end of file diff --git a/huobi/model/market/trade_detail.py b/huobi/model/market/trade_detail.py new file mode 100644 index 0000000..629db66 --- /dev/null +++ b/huobi/model/market/trade_detail.py @@ -0,0 +1,29 @@ + +class TradeDetail: + """ + The trade information with price and amount etc. + + :member + price: The trading price in quote currency. + amount: The trading volume in base currency. + tradeId: The unique trade id of this trade. + timestamp: The UNIX formatted timestamp generated by server in UTC. + direction: The direction of the taker trade: 'buy' or 'sell'. + """ + + def __init__(self): + self.price = 0.0 + self.amount = 0.0 + self.tradeId = 0 + self.ts = 0 + self.direction = "" + + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.tradeId, format_data + "Trade Id") + PrintBasic.print_basic(self.ts, format_data + "Trade Time") + PrintBasic.print_basic(self.price, format_data + "Price") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.direction, format_data + "Direction") \ No newline at end of file diff --git a/huobi/model/market/trade_detail_event.py b/huobi/model/market/trade_detail_event.py new file mode 100644 index 0000000..ef16073 --- /dev/null +++ b/huobi/model/market/trade_detail_event.py @@ -0,0 +1,28 @@ + + +class TradeDetailEvent: + """ + The trade received by subscription of trade. + + :member + symbol: The symbol you subscribed. + timestamp: The UNIX formatted timestamp generated by server in UTC. + trade_list: The trade list. The content is Trade class. + """ + + def __init__(self): + self.ch = "" + self.id = 0 + self.ts = 0 + self.data = list() + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ch, format_data + "Channel") + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.ts, format_data + "Unix Time") + if len(self.data): + for trade_detail in self.data: + trade_detail.print_object() + print() \ No newline at end of file diff --git a/huobi/model/market/trade_detail_req.py b/huobi/model/market/trade_detail_req.py new file mode 100644 index 0000000..8aa1e29 --- /dev/null +++ b/huobi/model/market/trade_detail_req.py @@ -0,0 +1,23 @@ + + +class TradeDetailReq: + """ + The trade received by subscription of trade. + + :member + rep: The Channel you subscribed. + trade_list: The trade list. The content is Trade class. + """ + + def __init__(self): + self.rep = "" + self.data = list() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.rep, format_data + "Channel") + print() + if len(self.data): + for trade_detail in self.data: + trade_detail.print_object() + print() \ No newline at end of file diff --git a/huobi/model/subuser/__init__.py b/huobi/model/subuser/__init__.py new file mode 100644 index 0000000..41bd908 --- /dev/null +++ b/huobi/model/subuser/__init__.py @@ -0,0 +1,5 @@ +from huobi.model.subuser.subuser_creation import SubuserCreation +from huobi.model.subuser.subuser_transferability import SubuserTransferability +from huobi.model.subuser.subuser_apikey_generation import SubuserApikeyGeneration +from huobi.model.subuser.user_apikey_info import UserApikeyInfo +from huobi.model.subuser.subuser_apikey_modification import SubuserApikeyModification diff --git a/huobi/model/subuser/subuser_apikey_generation.py b/huobi/model/subuser/subuser_apikey_generation.py new file mode 100644 index 0000000..aad98a3 --- /dev/null +++ b/huobi/model/subuser/subuser_apikey_generation.py @@ -0,0 +1,26 @@ +class SubuserApikeyGeneration: + """ + The trade information with price and amount etc. + + :member + accessKey: + secretKey: + note: + permission: "trade,readOnly", + ipAddresses": + """ + + def __init__(self): + self.accessKey = "" + self.secretKey = "" + self.note = "" + self.permission = "" + self.ipAddresses = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.accessKey, format_data + "accessKey") + PrintBasic.print_basic(self.secretKey, format_data + "secretKey") + PrintBasic.print_basic(self.note, format_data + "note") + PrintBasic.print_basic(self.permission, format_data + "permission") + PrintBasic.print_basic(self.ipAddresses, format_data + "ipAddresses") diff --git a/huobi/model/subuser/subuser_apikey_modification.py b/huobi/model/subuser/subuser_apikey_modification.py new file mode 100644 index 0000000..6265e7d --- /dev/null +++ b/huobi/model/subuser/subuser_apikey_modification.py @@ -0,0 +1,20 @@ +class SubuserApikeyModification: + """ + The trade information with price and amount etc. + + :member + note: + permission: "trade,readOnly", + ipAddresses": + """ + + def __init__(self): + self.note = "" + self.permission = "" + self.ipAddresses = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.note, format_data + "note") + PrintBasic.print_basic(self.permission, format_data + "permission") + PrintBasic.print_basic(self.ipAddresses, format_data + "ipAddresses") diff --git a/huobi/model/subuser/subuser_creation.py b/huobi/model/subuser/subuser_creation.py new file mode 100644 index 0000000..b8f6b88 --- /dev/null +++ b/huobi/model/subuser/subuser_creation.py @@ -0,0 +1,27 @@ + +class SubuserCreation: + """ + The trade information with price and amount etc. + + :member + subUid: sub user ID. + userState: sub user account state, states see SubUidState. + """ + + def __init__(self): + self.user_name = "" + self.note = "" + self.uid = 0 + self.err_code = 0 + self.err_message = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.user_name, format_data + "userName") + PrintBasic.print_basic(self.note, format_data + "note") + PrintBasic.print_basic(self.uid, format_data + "uid") + PrintBasic.print_basic(self.err_code, format_data + "errCode") + PrintBasic.print_basic(self.err_message, format_data + "errMessage") + + + diff --git a/huobi/model/subuser/subuser_transferability.py b/huobi/model/subuser/subuser_transferability.py new file mode 100644 index 0000000..a327390 --- /dev/null +++ b/huobi/model/subuser/subuser_transferability.py @@ -0,0 +1,20 @@ + +class SubuserTransferability: + """ + The trade information with price and amount etc. + + :member + subUid: sub user ID. + userState: sub user account state, states see SubUidState. + """ + + def __init__(self): + self.transferrable = "" + self.accountType = "" + self.subUid = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.subUid, format_data + "subUid") + PrintBasic.print_basic(self.accountType, format_data + "accountType") + PrintBasic.print_basic(self.transferrable, format_data + "transferrable") diff --git a/huobi/model/subuser/trade_market.py b/huobi/model/subuser/trade_market.py new file mode 100644 index 0000000..74d22b7 --- /dev/null +++ b/huobi/model/subuser/trade_market.py @@ -0,0 +1,21 @@ + +class TradeMarket: + """ + The trade information with price and amount etc. + + :member + subUid: sub user ID. + accountType: + activation: sub user account state for given accountType. + """ + + def __init__(self): + self.sub_uid = "" + self.account_type = "" + self.activation = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.sub_uid, format_data + "subUid") + PrintBasic.print_basic(self.account_type, format_data + "accountType") + PrintBasic.print_basic(self.activation, format_data + "activation") diff --git a/huobi/model/subuser/user_apikey_info.py b/huobi/model/subuser/user_apikey_info.py new file mode 100644 index 0000000..a55c126 --- /dev/null +++ b/huobi/model/subuser/user_apikey_info.py @@ -0,0 +1,37 @@ +class UserApikeyInfo: + """ + The trade information with price and amount etc. + + :member + accessKey: . + createTime: + ipAddresses: . + note: + permission: + status: + updateTime: + validDays: + + """ + + def __init__(self): + self.accessKey = "" + self.createTime = 0 + self.ipAddresses = "" + self.note = "" + self.permission = "" + self.status = "" + self.updateTime = 0 + self.validDays = -1 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + + PrintBasic.print_basic(self.accessKey, format_data + "accessKey") + PrintBasic.print_basic(self.createTime, format_data + "createTime") + PrintBasic.print_basic(self.ipAddresses, format_data + "ipAddresses") + PrintBasic.print_basic(self.note, format_data + "note") + PrintBasic.print_basic(self.permission, format_data + "permission") + PrintBasic.print_basic(self.status, format_data + "status") + PrintBasic.print_basic(self.updateTime, format_data + "updateTime") + PrintBasic.print_basic(self.validDays, format_data + "validDays") diff --git a/huobi/model/trade/__init__.py b/huobi/model/trade/__init__.py new file mode 100644 index 0000000..3a83d41 --- /dev/null +++ b/huobi/model/trade/__init__.py @@ -0,0 +1,17 @@ +from huobi.model.trade.batch_cancel_result import BatchCancelResult +from huobi.model.trade.batch_cancel_count import BatchCancelCount +from huobi.model.trade.feerate import FeeRate +from huobi.model.trade.matchresult import MatchResult +from huobi.model.trade.order import Order +from huobi.model.trade.order_detail_req import OrderDetailReq +from huobi.model.trade.order_list_item import OrderListItem +from huobi.model.trade.order_list_req import OrderListReq +from huobi.model.trade.order_update_event import OrderUpdateEvent +from huobi.model.trade.order_update import OrderUpdate +from huobi.model.trade.batch_create_order import BatchCreateOrder +from huobi.model.trade.transact_feerate import TransactFeeRate +from huobi.model.trade.trade_clearing import TradeClearing +from huobi.model.trade.trade_clearing_event import TradeClearingEvent + + + diff --git a/huobi/model/trade/batch_cancel_count.py b/huobi/model/trade/batch_cancel_count.py new file mode 100644 index 0000000..bbec6d0 --- /dev/null +++ b/huobi/model/trade/batch_cancel_count.py @@ -0,0 +1,23 @@ + + + +class BatchCancelCount: + """ + The result of batch cancel operation. + + :member + success_count: The number of cancel request sent successfully. + failed_count: The number of cancel request failed. + next_id:next open order id + """ + + def __init__(self): + self.success_count = 0 + self.failed_count = 0 + self.next_id = -1 + + def print_object(self, format_data=""): + from huobi.utils import PrintBasic + PrintBasic.print_basic(self.success_count, format_data + "Success Count") + PrintBasic.print_basic(self.failed_count, format_data + "Failed Count") + PrintBasic.print_basic(self.next_id, format_data + "Next Open Order ID") \ No newline at end of file diff --git a/huobi/model/trade/batch_cancel_result.py b/huobi/model/trade/batch_cancel_result.py new file mode 100644 index 0000000..aae02b5 --- /dev/null +++ b/huobi/model/trade/batch_cancel_result.py @@ -0,0 +1,17 @@ +class BatchCancelResult: + """ + The result of batch cancel operation. + + :member + success_count: The number of cancel request sent successfully. + failed_count: The number of cancel request failed. + + """ + + def __init__(self): + self.success = [] + self.failed = [] + + def print_object(self, format_data=""): + print("Success Order Counts", len(self.success), " Success Order Ids : ", self.success) + print("Fail Order Counts", len(self.failed), " Fail Order Ids : ", self.failed) \ No newline at end of file diff --git a/huobi/model/trade/batch_create_order.py b/huobi/model/trade/batch_create_order.py new file mode 100644 index 0000000..dc36274 --- /dev/null +++ b/huobi/model/trade/batch_create_order.py @@ -0,0 +1,23 @@ +class BatchCreateOrder: + """ + batch create order result + + :member + order_id: The transfer id. + client_order_id: The crypto currency to deposit. + err_code: The on-chain transaction hash. + err_msg: The number of crypto asset transferred in its minimum unit. + + """ + def __init__(self): + self.order_id = 0 + self.client_order_id = "" + self.err_code = "" + self.err_msg = "" + + def print_object(self, format_data=""): + from huobi.utils import PrintBasic + PrintBasic.print_basic(self.order_id, format_data + "Order Id") + PrintBasic.print_basic(self.client_order_id, format_data + "Client Order Id") + PrintBasic.print_basic(self.err_code, format_data + "Error Code") + PrintBasic.print_basic(self.err_msg, format_data + "Error Message") \ No newline at end of file diff --git a/huobi/model/trade/feerate.py b/huobi/model/trade/feerate.py new file mode 100644 index 0000000..62d0db9 --- /dev/null +++ b/huobi/model/trade/feerate.py @@ -0,0 +1,22 @@ + +class FeeRate: + """ + The account information for spot account, margin account etc. + + :member + symbol: The symbol, like "btcusdt". + maker_fee: maker fee rate + taker_fee: taker fee rate + + """ + + def __init__(self): + self.symbol = "" + self.maker_fee = "" + self.taker_fee = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.maker_fee, format_data + "Maker Fee") + PrintBasic.print_basic(self.taker_fee, format_data + "Taker Fee") \ No newline at end of file diff --git a/huobi/model/trade/matchresult.py b/huobi/model/trade/matchresult.py new file mode 100644 index 0000000..26f42a6 --- /dev/null +++ b/huobi/model/trade/matchresult.py @@ -0,0 +1,58 @@ +from huobi.constant import * + + +class MatchResult: + """ + The match result information. + + :member + created_timestamp: The UNIX formatted timestamp in UTC when the match and fill is done. + filled_amount: The amount which has been filled. + filled_fees: The transaction fee paid so far. + id: The internal id. + match_id: The match id of this match. + order_id: The order id of this order. + price: The limit price of limit order. + source: The source where the order was triggered, possible values: sys, web, api, app. + symbol: The symbol, like "btcusdt". + type: The order type, possible values are: buy-market, sell-market, buy-limit, sell-limit, + buy-ioc, sell-ioc, buy-limit-maker, sell-limit-maker, buy-limit-fok, sell-limit-fok, buy-stop-limit-fok, sell-stop-limit-fok. + filled_points: deduct points + fee_deduct_currency: deduct type, it means deduct from HT/ HT points / or other currency + fee_currency: + """ + + def __init__(self): + self.created_at = 0 + self.filled_amount = 0.0 + self.filled_fees = 0.0 + self.id = 0 + self.match_id = 0 + self.order_id = 0 + self.price = 0.0 + self.source = OrderSource.INVALID + self.symbol = "" + self.type = OrderType.INVALID + self.role = "" + self.filled_points = "" + self.fee_deduct_currency = "" + self.fee_currency = "" + self.fee_deduct_state = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.created_at, format_data + "Create Time") + PrintBasic.print_basic(self.filled_amount, format_data + "Fill Amount") + PrintBasic.print_basic(self.filled_fees, format_data + "Fill Fee") + PrintBasic.print_basic(self.filled_points, format_data + "Fill Points") + PrintBasic.print_basic(self.match_id, format_data + "Match ID") + PrintBasic.print_basic(self.order_id, format_data + "Order ID") + PrintBasic.print_basic(self.price, format_data + "Price") + PrintBasic.print_basic(self.source, format_data + "Source") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.type, format_data + "Order Type") + PrintBasic.print_basic(self.role, format_data + "Role") + PrintBasic.print_basic(self.fee_deduct_currency, format_data + "Fee Deduct Currency") + PrintBasic.print_basic(self.fee_currency, format_data + "Fee Currency") + PrintBasic.print_basic(self.fee_deduct_state, format_data + "Fee Deduct State") diff --git a/huobi/model/trade/order.py b/huobi/model/trade/order.py new file mode 100644 index 0000000..00df78b --- /dev/null +++ b/huobi/model/trade/order.py @@ -0,0 +1,86 @@ +from huobi.constant import * +from huobi.utils.json_parser import fill_obj + + +class Order: + """ + The detail order information. + + :member + amount: The amount of base currency in this order. + price: The limit price of limit order. + created_timestamp: The UNIX formatted timestamp in UTC when the order was created. + canceled_timestamp: The UNIX formatted timestamp in UTC when the order was canceled, if not canceled then has value of 0. + finished_timestamp: The UNIX formatted timestamp in UTC when the order was changed to a final state. This is not the time the order is matched. + order_id: The order id. + symbol: The symbol, like "btcusdt". + order_type: The order type, possible values are: buy-market, sell-market, buy-limit, sell-limit, buy-ioc, sell-ioc, buy-limit-maker, sell-limit-maker, buy-limit-fok, sell-limit-fok, buy-stop-limit-fok, sell-stop-limit-fok. + filled_amount: The amount which has been filled. + filled_cash_amount: The filled total in quote currency. + filled_fees: The transaction fee paid so far. + source: The source where the order was triggered, possible values: sys, web, api, app. + state: The order state: submitted, partial-filled, cancelling, filled, canceled. + stop_price : stop price used for buy-stop-limit,sell-stop-limit + operator : only [gte] and [lte] to trigger buy-stop-limit,sell-stop-limit + """ + + def __init__(self): + self.id = 0 + self.symbol = "" + self.account_id = 0 + self.amount = 0.0 + self.price = 0.0 + self.created_at = 0 + self.canceled_at = 0 + self.finished_at = 0 + self.type = OrderType.INVALID + self.filled_amount = 0.0 + self.filled_cash_amount = 0.0 + self.filled_fees = 0.0 + self.source = OrderSource.INVALID + self.state = OrderState.INVALID + self.client_order_id = "" + self.stop_price = "" + self.next_time = 0 + self.operator="" + + @staticmethod + def json_parse(json_data): + order = fill_obj(json_data, Order) + order.filled_amount = json_data.get("filled-amount", json_data.get("field-amount", 0)) + order.filled_cash_amount = json_data.get("filled-cash-amount", json_data.get("field-cash-amount", 0)) + order.filled_fees = json_data.get("filled-fees", json_data.get("field-fees", 0)) + return order + + @staticmethod + def json_parse_list(json_data): + if json_data and len(json_data): + order_list = list() + for idx, row in enumerate(json_data): + order_item = Order.json_parse(row) + order_list.append(order_item) + return order_list + + return list() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "Order Id") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.price, format_data + "Price") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.created_at, format_data + "Create Time") + PrintBasic.print_basic(self.canceled_at, format_data + "Cancel Time") + PrintBasic.print_basic(self.finished_at, format_data + "Finish Time") + PrintBasic.print_basic(self.type, format_data + "Order Type") + PrintBasic.print_basic(self.filled_amount, format_data + "Filled Amount") + PrintBasic.print_basic(self.filled_cash_amount, format_data + "Filled Cash Amount") + PrintBasic.print_basic(self.filled_fees, format_data + "Filled Fees") + #PrintBasic.print_basic(self.account_type, format_data + "Account Type") + PrintBasic.print_basic(self.source, format_data + "Order Source") + PrintBasic.print_basic(self.state, format_data + "Order State") + PrintBasic.print_basic(self.client_order_id, format_data + "Client Order Id") + PrintBasic.print_basic(self.stop_price, format_data + "Stop Price") + PrintBasic.print_basic(self.operator, format_data + "Operator") + PrintBasic.print_basic(self.next_time, format_data + "Next Time") + diff --git a/huobi/model/trade/order_detail_req.py b/huobi/model/trade/order_detail_req.py new file mode 100644 index 0000000..4b4ac09 --- /dev/null +++ b/huobi/model/trade/order_detail_req.py @@ -0,0 +1,29 @@ +from huobi.model.trade.order_list_item import OrderListItem + + +class OrderDetailReq: + """ + The order update received by subscription of order update. + + :member + symbol: The symbol you subscribed. + timestamp: The UNIX formatted timestamp generated by server in UTC. + topic : request topic + client_req_id : client request id + data: The order detail. + + """ + + def __init__(self): + self.ts = 0 + self.topic = "" + self.cid = "" + self.data = OrderListItem() + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ts, format_data + "Timestamp") + PrintBasic.print_basic(self.cid, format_data + "Client Req ID") + PrintBasic.print_basic(self.topic, format_data + "Topic") + self.data.print_object("\t") \ No newline at end of file diff --git a/huobi/model/trade/order_list_item.py b/huobi/model/trade/order_list_item.py new file mode 100644 index 0000000..c790d0a --- /dev/null +++ b/huobi/model/trade/order_list_item.py @@ -0,0 +1,55 @@ + +from huobi.constant import * + +class OrderListItem: + """ + The order update received by request of order list. + + :member + symbol: The symbol you subscribed. + timestamp: The UNIX formatted timestamp generated by server in UTC. + topic: request topic + client_req_id: client request ID + order_list : order list + + """ + + def __init__(self): + self.id = 0 + self.symbol = "" + self.account_id = 0 + self.amount = 0.0 + self.price = 0.0 + self.created_at = 0 + self.type = OrderType.INVALID + self.finished_at = 0 + self.source = OrderSource.INVALID + self.state = OrderState.INVALID + self.canceled_at = 0 + self.filled_amount = 0.0 + self.filled_cash_amount = 0.0 + self.filled_fees = 0.0 + self.stop_price = 0.0 + self.operator = "" + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.account_id, format_data + "Account Id") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.price, format_data + "Price") + PrintBasic.print_basic(self.created_at, format_data + "Create Time") + PrintBasic.print_basic(self.type, format_data + "Order Type") + PrintBasic.print_basic(self.finished_at, format_data + "Finish Time") + PrintBasic.print_basic(self.source, format_data + "Order Source") + PrintBasic.print_basic(self.state, format_data + "Order State") + PrintBasic.print_basic(self.canceled_at, format_data + "Cancel Time") + PrintBasic.print_basic(self.filled_amount, format_data + "Filled Amount") + PrintBasic.print_basic(self.filled_cash_amount, format_data + "Filled Cash Amount") + PrintBasic.print_basic(self.filled_fees, format_data + "Filled Fees") + PrintBasic.print_basic(self.stop_price, format_data + "Stop Price") + PrintBasic.print_basic(self.operator, format_data + "Operator") + + diff --git a/huobi/model/trade/order_list_req.py b/huobi/model/trade/order_list_req.py new file mode 100644 index 0000000..dddf0b0 --- /dev/null +++ b/huobi/model/trade/order_list_req.py @@ -0,0 +1,29 @@ + +class OrderListReq: + """ + The order update received by request of order list. + + :member + symbol: The symbol you subscribed. + timestamp: The UNIX formatted timestamp generated by server in UTC. + topic: request topic + data : OrderListItem + + """ + + def __init__(self): + self.ts = 0 + self.topic = "" + self.data = list() + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ts, format_data + "Timestamp") + PrintBasic.print_basic(self.topic, format_data + "Channel") + print("Order List as below : count " + str(len(self.data))) + if len(self.data): + for orderlistitem_obj in self.data: + orderlistitem_obj.print_object("\t ") + print() + diff --git a/huobi/model/trade/order_update.py b/huobi/model/trade/order_update.py new file mode 100644 index 0000000..fdbf747 --- /dev/null +++ b/huobi/model/trade/order_update.py @@ -0,0 +1,56 @@ +from huobi.constant import * + + +class OrderUpdate: + """ + The detail order information. + + :member + orderId: The order id. + tradePrice: trade price + tradeVolume: trade volume + tradeId: Id record for trade + tradeTime: trade timestamp (ms) + aggressor: true (taker), false (maker) + remainAmt: Remaining amount (for buy-market order it's remaining value) + orderStatus: Order status, valid value: partial-filled, filled + clientOrderId: Client order ID (if any) + eventType: Event type, valid value: trade + symbol: The symbol, like "btcusdt". + type: The order type, possible values are: buy-market, sell-market, buy-limit, sell-limit, buy-ioc, sell-ioc, buy-limit-maker, sell-limit-maker, buy-limit-fok, sell-limit-fok. + """ + + def __init__(self): + self.orderId = 0 + self.tradePrice = "" + self.tradeVolume = "" + self.tradeId = 0 + self.tradeTime = 0 + self.aggressor = False + self.remainAmt = "" + self.orderStatus = OrderState.INVALID + self.clientOrderId = "" + self.eventType = "" + self.symbol = "" + self.type = OrderType.INVALID + self.accountId = 0 + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.orderId, format_data + "Order Id") + PrintBasic.print_basic(self.tradePrice, format_data + "Trade Price") + PrintBasic.print_basic(self.tradeVolume, format_data + "Trade Volume") + PrintBasic.print_basic(self.tradeId, format_data + "Trade Id") + PrintBasic.print_basic(self.tradeTime, format_data + "Trade Timestamp") + PrintBasic.print_basic(self.aggressor, format_data + "is Taker") + PrintBasic.print_basic(self.orderStatus, format_data + "Order State") + PrintBasic.print_basic(self.clientOrderId, format_data + "Client Order Id") + PrintBasic.print_basic(self.eventType, format_data + "Event Type") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.type, format_data + "Order Type") + PrintBasic.print_basic(self.accountId, format_data + "Account Id") + + + + diff --git a/huobi/model/trade/order_update_event.py b/huobi/model/trade/order_update_event.py new file mode 100644 index 0000000..8399ec3 --- /dev/null +++ b/huobi/model/trade/order_update_event.py @@ -0,0 +1,23 @@ +from huobi.model.trade.order_update import OrderUpdate + + +class OrderUpdateEvent: + """ + The order update received by subscription of order update. + + :member + ch: The symbol you subscribed. + data: The order detail. + + """ + + def __init__(self): + self.ch = "" + self.data = OrderUpdate() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ch, format_data + "Topic") + + orderupdate = self.data + orderupdate.print_object() \ No newline at end of file diff --git a/huobi/model/trade/trade_clearing.py b/huobi/model/trade/trade_clearing.py new file mode 100644 index 0000000..d1f1726 --- /dev/null +++ b/huobi/model/trade/trade_clearing.py @@ -0,0 +1,60 @@ +from huobi.constant import * +from huobi.utils.json_parser import fill_obj, default_parse_list_dict + + +class TradeClearing: + """ + The detail order information. + + :member + order_id: The order id. + symbol: The symbol, like "btcusdt". + tradePrice: trade price. + tradeVolume: trade Volume. + orderSide: order Side, more to see OrderSide + orderType: order type, more to see OrderType + aggressor: is aggressor, only true or false + tradeId: trade ID. + tradeTime: trade Time. + transactFee: transact Fee. + feeDeduct: Deduct Fee. + feeDeductType: fee Deduct Type, current only support ht and point + """ + + def __init__(self): + self.symbol = "" + self.orderId = 0 + self.tradePrice = "" + self.tradeVolume = "" + self.orderSide = OrderSide.INVALID + self.orderType = OrderType.INVALID + self.aggressor = False + self.tradeId = 0 + self.tradeTime = 0 + self.transactFee = "" + self.feeDeduct = "" + self.feeDeductType = FeeDeductType.INVALID + + @staticmethod + def json_parse(json_data): + if json_data.get("orderId", None): + return default_parse_list_dict(json_data, TradeClearing) + else: + return TradeClearing() + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.orderId, format_data + "Order Id") + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.tradePrice, format_data + "Trade Price") + PrintBasic.print_basic(self.tradeVolume, format_data + "Trade Volume") + PrintBasic.print_basic(self.orderSide, format_data + "Order Side") + PrintBasic.print_basic(self.orderType, format_data + "Order Type") + PrintBasic.print_basic(self.aggressor, format_data + "is Taker") + PrintBasic.print_basic(self.tradeId, format_data + "Trade Id") + PrintBasic.print_basic(self.tradeTime, format_data + "Trade Time") + PrintBasic.print_basic(self.transactFee, format_data + "Transact Fee") + PrintBasic.print_basic(self.feeDeduct, format_data + "Fee Deduct") + PrintBasic.print_basic(self.feeDeductType, format_data + "Fee Deduct Type") + + diff --git a/huobi/model/trade/trade_clearing_event.py b/huobi/model/trade/trade_clearing_event.py new file mode 100644 index 0000000..adb64b6 --- /dev/null +++ b/huobi/model/trade/trade_clearing_event.py @@ -0,0 +1,32 @@ +from huobi.model.trade import TradeClearing + + +class TradeClearingEvent: + """ + subscribe trading clearing information + + :member + action: current is "sub" for subscribe + ch: subscribe topic. + data: data detail in TradeClearing. + """ + + def __init__(self): + self.action = "" + self.ch = "" + self.seq = 0 + self.data = TradeClearing() + + @staticmethod + def json_parse(data_json): + event_obj = TradeClearingEvent() + event_obj.action = data_json.get("action") + event_obj.ch = data_json.get("ch") + event_obj.seq = data_json.get("seq", 0) + event_obj.data = TradeClearing.json_parse(data_json.get("data", {})) + return event_obj + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.ch, format_data + "Channel") + self.data.print_object() diff --git a/huobi/model/trade/transact_feerate.py b/huobi/model/trade/transact_feerate.py new file mode 100644 index 0000000..67c0be5 --- /dev/null +++ b/huobi/model/trade/transact_feerate.py @@ -0,0 +1,27 @@ + +class TransactFeeRate: + """ + The transact fee rate. + + :member + symbol: symbol like "btcusdt" + makerFeeRate: maker fee rate + takerFeeRate: taker fee rate + actualMakerRate: actual maker fee rate + actualTakerRate: actual taker fee rate + """ + + def __init__(self): + self.symbol = "" + self.makerFeeRate = "" + self.takerFeeRate = "" + self.actualMakerRate = "" + self.actualTakerRate = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.symbol, format_data + "Symbol") + PrintBasic.print_basic(self.makerFeeRate, format_data + "makerFeeRate") + PrintBasic.print_basic(self.takerFeeRate, format_data + "takerFeeRate") + PrintBasic.print_basic(self.actualMakerRate, format_data + "actualMakerRate") + PrintBasic.print_basic(self.actualTakerRate, format_data + "actualTakerRate") \ No newline at end of file diff --git a/huobi/model/wallet/__init__.py b/huobi/model/wallet/__init__.py new file mode 100644 index 0000000..f351a02 --- /dev/null +++ b/huobi/model/wallet/__init__.py @@ -0,0 +1,7 @@ +from huobi.model.wallet.deposit import Deposit +from huobi.model.wallet.withdraw import Withdraw +from huobi.model.wallet.chain_deposit_address import ChainDepositAddress +from huobi.model.wallet.chain_withdraw_address import ChainWithdrawAddress +from huobi.model.wallet.withdraw_quota import WithdrawQuota +from huobi.model.wallet.deposit_history import DepositHistory +from huobi.model.wallet.deposit_history_item import DepositHistoryItem diff --git a/huobi/model/wallet/chain_deposit_address.py b/huobi/model/wallet/chain_deposit_address.py new file mode 100644 index 0000000..b2f6ce0 --- /dev/null +++ b/huobi/model/wallet/chain_deposit_address.py @@ -0,0 +1,25 @@ + +class ChainDepositAddress: + """ + The deposit address. + + :member + currency: The crypto currency to deposit. + address: Deposit address + addressTag: Deposit address tag. + chain: Block chain name. + """ + def __init__(self): + self.currency = "" + self.address = "" + self.addressTag = "" + self.chain = "" + + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.address, format_data + "Address") + PrintBasic.print_basic(self.addressTag, format_data + "addressTag") + PrintBasic.print_basic(self.chain, format_data + "Chain") diff --git a/huobi/model/wallet/chain_withdraw_address.py b/huobi/model/wallet/chain_withdraw_address.py new file mode 100644 index 0000000..ff16357 --- /dev/null +++ b/huobi/model/wallet/chain_withdraw_address.py @@ -0,0 +1,25 @@ +class ChainWithdrawAddress: + """ + The deposit address. + + :member + currency: The crypto currency to deposit. + address: Deposit address + addressTag: Deposit address tag. + chain: Block chain name. + """ + + def __init__(self): + self.currency = "" + self.address = "" + self.addressTag = "" + self.chain = "" + self.note = "" + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.address, format_data + "Address") + PrintBasic.print_basic(self.addressTag, format_data + "addressTag") + PrintBasic.print_basic(self.chain, format_data + "Chain") + PrintBasic.print_basic(self.note, format_data + "Note") diff --git a/huobi/model/wallet/deposit.py b/huobi/model/wallet/deposit.py new file mode 100644 index 0000000..fc6471a --- /dev/null +++ b/huobi/model/wallet/deposit.py @@ -0,0 +1,48 @@ +from huobi.constant import * + + +class Deposit: + """ + The latest status for deposits + + :member + id: The transfer id. + currency: The crypto currency to deposit. + tx_hash: The on-chain transaction hash. + amount: The number of crypto asset transferred in its minimum unit. + address: The deposit source address. + address_tag: The user defined address tag. + fee: The amount of fee taken by Huobi in this crypto's minimum unit. + created_at: The UNIX formatted timestamp in UTC for the transfer creation. + updated_at: The UNIX formatted timestamp in UTC for the transfer's latest update. + state: The deposit state of this transfer. + """ + + def __init__(self): + self.id = 0 + self.type = DepositWithdraw.DEPOSIT + self.currency = "" + self.tx_hash = "" + self.amount = 0.0 + self.chain = "" + self.address = "" + self.address_tag = "" + self.fee = 0.0 + self.created_at = 0 + self.updated_at = 0 + self.state = DepositState.INVALID + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.type, format_data + "Operate Type") + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.chain, format_data + "Chain") + PrintBasic.print_basic(self.tx_hash, format_data + "Trade Hash") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.address, format_data + "Address") + PrintBasic.print_basic(self.address_tag, format_data + "Address Tag") + PrintBasic.print_basic(self.fee, format_data + "Fee") + PrintBasic.print_basic(self.state, format_data + "Deposit State") + PrintBasic.print_basic(self.created_at, format_data + "Create Time") + PrintBasic.print_basic(self.updated_at, format_data + "Update Time") diff --git a/huobi/model/wallet/deposit_history.py b/huobi/model/wallet/deposit_history.py new file mode 100644 index 0000000..7ef4ac8 --- /dev/null +++ b/huobi/model/wallet/deposit_history.py @@ -0,0 +1,21 @@ +from huobi.model.wallet.deposit_history_item import DepositHistoryItem +class DepositHistory: + """ + The deposit history + + :member + nextId: next id. + data: history list. + """ + + def __init__(self): + self.data = list() + self.nextId = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.nextId, format_data + "NextId") + if self.data and len(self.data): + for item in self.data: + item.print_object() + PrintBasic.print_basic("", format_data + "") diff --git a/huobi/model/wallet/deposit_history_item.py b/huobi/model/wallet/deposit_history_item.py new file mode 100644 index 0000000..9215c9d --- /dev/null +++ b/huobi/model/wallet/deposit_history_item.py @@ -0,0 +1,42 @@ +from huobi.constant import * + + +class DepositHistoryItem: + """ + The deposit history + + :member + id: The transfer id. + currency: The crypto currency to deposit. + txHash: The on-chain transaction hash. + amount: The number of crypto asset transferred in its minimum unit. + address: The deposit source address. + addressTag: The user defined address tag. + deposit_state: The deposit state of this transfer. + created_timestamp: The UNIX formatted timestamp in UTC for the transfer creation. + updated_timestamp: The UNIX formatted timestamp in UTC for the transfer's latest update. + """ + def __init__(self): + self.id = 0 + self.currency = "" + self.txHash = "" + self.chain = "" + self.amount = 0.0 + self.address = "" + self.addressTag = "" + self.deposit_state = WithdrawState.INVALID + self.created_timestamp = 0 + self.updated_timestamp = 0 + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.chain, format_data + "Chain") + PrintBasic.print_basic(self.txHash, format_data + "Trade Hash") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.address, format_data + "Address") + PrintBasic.print_basic(self.addressTag, format_data + "Address Tag") + PrintBasic.print_basic(self.deposit_state, format_data + "Deposit State") + PrintBasic.print_basic(self.created_timestamp, format_data + "Create Time") + PrintBasic.print_basic(self.updated_timestamp, format_data + "Update Time") diff --git a/huobi/model/wallet/withdraw.py b/huobi/model/wallet/withdraw.py new file mode 100644 index 0000000..24a69d9 --- /dev/null +++ b/huobi/model/wallet/withdraw.py @@ -0,0 +1,48 @@ +from huobi.constant import * + + +class Withdraw: + """ + The latest status for withdraws. + + :member + id: The transfer id. + currency: The crypto currency to deposit. + tx_hash: The on-chain transaction hash. + amount: The number of crypto asset transferred in its minimum unit. + address: The deposit source address. + address_tag: The user defined address tag. + fee: The amount of fee taken by Huobi in this crypto's minimum unit. + created_at: The UNIX formatted timestamp in UTC for the transfer creation. + updated_at: The UNIX formatted timestamp in UTC for the transfer's latest update. + state: The withdraw state of this transfer. + """ + def __init__(self): + self.id = 0 + self.type = DepositWithdraw.WITHDRAW + self.currency = "" + self.chain = "" + self.tx_hash = "" + self.amount = 0.0 + self.address = "" + self.address_tag = "" + self.fee = 0.0 + self.created_at = 0 + self.updated_at = 0 + self.state = WithdrawState.INVALID + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.id, format_data + "ID") + PrintBasic.print_basic(self.currency, format_data + "Currency") + PrintBasic.print_basic(self.type, format_data + "Operator Type") + PrintBasic.print_basic(self.chain, format_data + "Chain") + PrintBasic.print_basic(self.tx_hash, format_data + "Trade Hash") + PrintBasic.print_basic(self.amount, format_data + "Amount") + PrintBasic.print_basic(self.address, format_data + "Address") + PrintBasic.print_basic(self.address_tag, format_data + "Address Tag") + PrintBasic.print_basic(self.fee, format_data + "Fee") + PrintBasic.print_basic(self.state, format_data + "Withdraw State") + PrintBasic.print_basic(self.created_at, format_data + "Create Time") + PrintBasic.print_basic(self.updated_at, format_data + "Update Time") \ No newline at end of file diff --git a/huobi/model/wallet/withdraw_quota.py b/huobi/model/wallet/withdraw_quota.py new file mode 100644 index 0000000..376cc47 --- /dev/null +++ b/huobi/model/wallet/withdraw_quota.py @@ -0,0 +1,37 @@ + +class WithdrawQuota: + """ + Withdraw Quota info. + + :member + chain: Block chain name. + maxWithdrawAmt: Maximum withdraw amount in each request. + withdrawQuotaPerDay: Maximum withdraw amount in a day + remainWithdrawQuotaPerDay: Remaining withdraw quota in the day + withdrawQuotaPerYear: Maximum withdraw amount in a year + remainWithdrawQuotaPerYear: Remaining withdraw quota in the year + withdrawQuotaTotal: Maximum withdraw amount in total + remainWithdrawQuotaTotal: Remaining withdraw quota in total + """ + def __init__(self): + self.chain = "" + self.maxWithdrawAmt = "" + self.withdrawQuotaPerDay = "" + self.remainWithdrawQuotaPerDay = "" + self.withdrawQuotaPerYear = "" + self.remainWithdrawQuotaPerYear = "" + self.withdrawQuotaTotal = "" + self.remainWithdrawQuotaTotal = "" + + + + def print_object(self, format_data=""): + from huobi.utils.print_mix_object import PrintBasic + PrintBasic.print_basic(self.chain, format_data + "Chain") + PrintBasic.print_basic(self.maxWithdrawAmt, format_data + "maxWithdrawAmt") + PrintBasic.print_basic(self.withdrawQuotaPerDay, format_data + "withdrawQuotaPerDay") + PrintBasic.print_basic(self.remainWithdrawQuotaPerDay, format_data + "remainWithdrawQuotaPerDay") + PrintBasic.print_basic(self.withdrawQuotaPerYear, format_data + "withdrawQuotaPerYear") + PrintBasic.print_basic(self.remainWithdrawQuotaPerYear, format_data + "remainWithdrawQuotaPerYear") + PrintBasic.print_basic(self.withdrawQuotaTotal, format_data + "withdrawQuotaTotal") + PrintBasic.print_basic(self.remainWithdrawQuotaTotal, format_data + "remainWithdrawQuotaTotal") diff --git a/huobi/service/__init__.py b/huobi/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/account/__init__.py b/huobi/service/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/account/error_code.md b/huobi/service/account/error_code.md new file mode 100644 index 0000000..99e4095 --- /dev/null +++ b/huobi/service/account/error_code.md @@ -0,0 +1,13 @@ +常见错误码 +以下是账户相关接口返回的错误码、错误消息以及说明。 + +错误码 错误消息 说明 +500 system error 调用内部服务异常 +1002 forbidden 禁止操作,如用户入参中accountId与UID不一致 +2002 "invalid field value in currency" currency不符合正则规则^[a-z0-9]{2,10}$ +2002 "invalid field value in transactTypes" 变动类型transactTypes不是“transfer” +2002 "invalid field value in sort" 分页请求参数不是合法的"asc或desc" +2002 "value in fromId is not found in record” 未找到fromId +2002 "invalid field value in accountId" 查询参数中accountId为空 +2002 "value in startTime exceeded valid range" 入参查询时间大于当前时间,或者距离当前时间超过180天 +2002 "value in endTime exceeded valid range") 查询结束时间小于开始时间,或者查询时间跨度大于10天 \ No newline at end of file diff --git a/huobi/service/account/get_account_asset_valuation.py b/huobi/service/account/get_account_asset_valuation.py new file mode 100644 index 0000000..6162c27 --- /dev/null +++ b/huobi/service/account/get_account_asset_valuation.py @@ -0,0 +1,38 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account.account_asset_valuation import AccountAssetValuationResult +from huobi.utils import * +''' +获取账户资产估值 +API Key 权限:读取 + +限频值(NEW):100次/2s + +按照BTC或法币计价单位,获取指定账户的总资产估值。 +请求参数: +accountType 账户类型 spot:现货账户, margin:逐仓杠杆账户,otc:OTC 账户,super-margin:全仓杠杆账户 +valuationCurrency 资产估值法币,即资产按哪个法币为单位进行估值。 可选法币有:BTC、CNY、USD、JPY、KRW、GBP、TRY、EUR、RUB、VND、HKD、TWD、MYR、SGD、AED、SAR (大小写敏感) +subUid 子用户的 UID,若不填,则返回API key所属用户的账户资产估值 +{ + "code": 200, + "data": { + "balance": "34.75", 按照某一个法币为单位的总资产估值 + "timestamp": 1594901254363 数据返回时间,为unix time in millisecond + }, + "ok": true +} +''' + +class GetAccountAssetValuationService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/account/asset-valuation" + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse(data, AccountAssetValuationResult, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/account/get_account_balance_by_subuid.py b/huobi/service/account/get_account_balance_by_subuid.py new file mode 100644 index 0000000..2953207 --- /dev/null +++ b/huobi/service/account/get_account_balance_by_subuid.py @@ -0,0 +1,22 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * + + +class GetAccountBalanceBySubUidService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + sub_uid = self.params["sub-uid"] + + def get_channel(): + path = "/v1/account/accounts/{}" + return path.format(sub_uid) + + def parse(dict_data): + data_list = dict_data.get("data", []) + return AccountBalance.json_parse_list(data_list) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, get_channel(), self.params, parse) diff --git a/huobi/service/account/get_account_history.py b/huobi/service/account/get_account_history.py new file mode 100644 index 0000000..213c39e --- /dev/null +++ b/huobi/service/account/get_account_history.py @@ -0,0 +1,30 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#0d3c2e7382 +账户流水 +API Key 权限:读取 +限频值(NEW):5次/2s + +该节点基于用户账户ID返回账户流水。 + +''' + +class GetAccountHistoryService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/account/history" + + def parse(dict_data): + response = dict() + data_list = dict_data.get("data", []) + response['data'] = default_parse_list_dict(data_list, AccountHistory, []) + response['next_id'] = dict_data.get("next-id", 0) + return response + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/account/get_account_ledger.py b/huobi/service/account/get_account_ledger.py new file mode 100644 index 0000000..2802131 --- /dev/null +++ b/huobi/service/account/get_account_ledger.py @@ -0,0 +1,36 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#84f1b5486d +财务流水 +API Key 权限:读取 + +该节点基于用户账户ID返回财务流水。 +一期上线暂时仅支持划转流水的查询(“transactType” = “transfer”)。 +通过“startTime”/“endTime”框定的查询窗口最大为10天,意即,通过单次查询可检索的范围最大为10天。 +该查询窗口可在最近180天范围内平移,意即,通过多次平移窗口查询,最多可检索到过往180天的记录。 + +''' + + +class GetAccountLedgerService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/account/ledger" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, AccountLedger, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/account/get_account_point.py b/huobi/service/account/get_account_point.py new file mode 100644 index 0000000..b2d03ad --- /dev/null +++ b/huobi/service/account/get_account_point.py @@ -0,0 +1,19 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * +from huobi.utils import * + + +class GetAccountPointService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/point/account" + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse(data, AccountPointResult, {}) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/account/get_accounts.py b/huobi/service/account/get_accounts.py new file mode 100644 index 0000000..51572a2 --- /dev/null +++ b/huobi/service/account/get_accounts.py @@ -0,0 +1,50 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#555911a809 +账户信息 +API Key 权限:读取 +限频值(NEW):100次/2s +响应数据 + + +{ + "data": [ + { + "id": 100001, account-id + "type": "spot", 账户类型 spot:现货账户, margin:逐仓杠杆账户, otc:OTC 账户, point:点卡账户, super-margin:全仓杠杆账户, investment: C2C杠杆借出账户, borrow: C2C杠杆借入账户,矿池账户: minepool, ETF账户: etf, 抵押借贷账户: crypto-loans + "subtype": "", 子账户类型(仅对逐仓杠杆账户有效) 逐仓杠杆交易标的,例如btcusdt + "state": "working" 账户状态 working:正常, lock:账户被锁定 + } + { + "id": 100002, + "type": "margin", + "subtype": "btcusdt", + "state": "working" + }, + { + "id": 100003, + "type": "otc", + "subtype": "", + "state": "working" + } + ] +} +逐仓/全仓/C2C杠杆账户(margin/super-margin/borrow)会在第一次划转资产时创建,如果未划转过资产则不会有杠杆账户。 +''' + +class GetAccountsService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/account/accounts" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, Account, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/account/get_aggregate_subuser_balance.py b/huobi/service/account/get_aggregate_subuser_balance.py new file mode 100644 index 0000000..c8610e2 --- /dev/null +++ b/huobi/service/account/get_aggregate_subuser_balance.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * +from huobi.utils import * + + + +class GetAggregateSubUserBalanceService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/subuser/aggregate-balance" + + def parse(dict_data): + data = dict_data.get("data", []) + return default_parse_list_dict(data, Balance) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/account/get_balance.py b/huobi/service/account/get_balance.py new file mode 100644 index 0000000..f4f2678 --- /dev/null +++ b/huobi/service/account/get_balance.py @@ -0,0 +1,66 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#bd9157656f +账户余额 +API Key 权限:读取 +限频值(NEW):100次/2s +查询指定账户的余额,支持以下账户: + +spot:现货账户, margin:逐仓杠杆账户,otc:OTC 账户,point:点卡账户,super-margin:全仓杠杆账户, investment: C2C杠杆借出账户, borrow: C2C杠杆借入账户 +响应数据 +{ + "data": { + "id": 100009, 账户 ID + "type": "spot", 账户类型 pot:现货账户, margin:逐仓杠杆账户, otc:OTC 账户, point:点卡账户, super-margin:全仓杠杆账户, investment: C2C杠杆借出账户, borrow: C2C杠杆借入账户,矿池账户: minepool, ETF账户: etf, 抵押借贷账户: crypto-loans + "state": "working", 账户状态 working:正常 lock:账户被锁定 + "list": [ + { + "currency": "usdt", 币种 + "type": "trade", 类型 trade: 交易余额,frozen: 冻结余额, loan: 待还借贷本金, interest: 待还借贷利息, lock: 锁仓, bank: 储蓄 + "balance": "5007.4362872650" 余额 + }, + { + "currency": "usdt", + "type": "frozen", + "balance": "348.1199920000" + } + ] + } +} +''' + +class GetBalanceService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + account_id = self.params["account-id"] + + def get_channel(): + path = "/v1/account/accounts/{}/balance" + return path.format(account_id) + + def parse(dict_data): + data = dict_data.get("data", {}) + balance_list = data.get("list", []) + return default_parse_list_dict(balance_list, Balance, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, get_channel(), self.params, parse) + + def get_request(self, **kwargs): + account_id = self.params["account-id"] + + def get_channel(): + path = "/v1/account/accounts/{}/balance" + return path.format(account_id) + + def parse(dict_data): + data = dict_data.get("data", {}) + balance_list = data.get("list", []) + return default_parse_list_dict(balance_list, Balance, []) + + return RestApiSyncClient(**kwargs).create_request(HttpMethod.GET_SIGN, get_channel(), self.params, parse) diff --git a/huobi/service/account/post_account_transfer.py b/huobi/service/account/post_account_transfer.py new file mode 100644 index 0000000..b5d31f7 --- /dev/null +++ b/huobi/service/account/post_account_transfer.py @@ -0,0 +1,41 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#c5034eb6d0 +资产划转 +API Key 权限:交易 + +该节点为母用户和子用户进行资产划转的通用接口。 + +母用户和子用户均支持的功能包括: +1、币币账户与逐仓杠杠账户之间的划转; +2、逐仓杠杠不同账户间相同币种的直接划转,如逐仓杠杠BTC/USDT账户和ETH/USDT账户,相同币种USDT可直接划转; + +仅母用户支持的功能包括: +1、母用户币币账户与子用户币币账户间的划转; +2、不同子用户币币账户间划转; + +仅子用户支持的功能包括: +1、子用户币币账户向母用户下的其他子用户币币账户划转,此权限默认关闭,需母用户授权。授权接口为 POST /v2/sub-user/transferability; +2、子用户币币账户向母用户币币账户划转; + +其他划转功能将逐步上线,敬请期待。 + + +''' + +class PostAccountTransferService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/account/transfer" + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse(data, AccountTransferResult, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/account/post_futures_and_pro_transfer.py b/huobi/service/account/post_futures_and_pro_transfer.py new file mode 100644 index 0000000..802af81 --- /dev/null +++ b/huobi/service/account/post_futures_and_pro_transfer.py @@ -0,0 +1,23 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostTransferBetweenFuturesAndProService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/futures/transfer" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/account/post_point_transfer.py b/huobi/service/account/post_point_transfer.py new file mode 100644 index 0000000..b771c3c --- /dev/null +++ b/huobi/service/account/post_point_transfer.py @@ -0,0 +1,19 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import * +from huobi.utils import * + + +class PostPointTransferService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/point/transfer" + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse(data, AccountPointTransferResult, {}) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/account/post_sub_uid_management.py b/huobi/service/account/post_sub_uid_management.py new file mode 100644 index 0000000..226f300 --- /dev/null +++ b/huobi/service/account/post_sub_uid_management.py @@ -0,0 +1,24 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.account import * + + +class PostSubUidManagementService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/management" + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", {}), SubUidManagement) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/account/post_subaccount_transfer.py b/huobi/service/account/post_subaccount_transfer.py new file mode 100644 index 0000000..832b844 --- /dev/null +++ b/huobi/service/account/post_subaccount_transfer.py @@ -0,0 +1,24 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostSubaccountTransferService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/subuser/transfer" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/account/req_account_balance.py b/huobi/service/account/req_account_balance.py new file mode 100644 index 0000000..7226b36 --- /dev/null +++ b/huobi/service/account/req_account_balance.py @@ -0,0 +1,34 @@ + +from huobi.utils import * + +from huobi.connection.websocket_req_client import * +from huobi.model.account import * + + + +class ReqAccountBalanceService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + client_req_id = self.params["client_req_id"] + def subscription(connection): + connection.send(request_account_list_channel(client_req_id)) + + def parse(dict_data): + req_obj = AccountBalanceReq() + req_obj.ts = dict_data.get("ts", 0) + req_obj.topic = dict_data.get("topic", 0) + req_obj.cid = dict_data.get("cid", 0) + data_list = dict_data.get("data", []) + req_obj.data = AccountBalance.json_parse_list(data_list) + return req_obj + + WebSocketReqClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler, + is_trade=True) + + + diff --git a/huobi/service/account/sub_account_update_v2.py b/huobi/service/account/sub_account_update_v2.py new file mode 100644 index 0000000..a1e1b3f --- /dev/null +++ b/huobi/service/account/sub_account_update_v2.py @@ -0,0 +1,30 @@ +from huobi.utils import * + +from huobi.connection.subscribe_client import SubscribeClient +from huobi.model.account import * + + +class SubAccountUpdateV2Service: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + mode = self.params["mode"] + + def subscription(connection): + connection.send(accounts_update_channel(mode)) + + def parse(dict_data): + account_change_event = AccountUpdateEvent() + account_change_event.ch = dict_data.get("ch") + data = dict_data.get("data", {}) + if data and len(data): + account_change_event.data = default_parse_list_dict(data, AccountUpdate) + + return account_change_event + + SubscribeClient(**kwargs).execute_subscribe_v2(subscription, + parse, + callback, + error_handler, + is_trade=True) diff --git a/huobi/service/algo/__init__.py b/huobi/service/algo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/algo/get_open_orders.py b/huobi/service/algo/get_open_orders.py new file mode 100644 index 0000000..c390037 --- /dev/null +++ b/huobi/service/algo/get_open_orders.py @@ -0,0 +1,41 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import * +from huobi.model.algo import * + + +class GetOpenOrdersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/algo-orders/opening" + + # { + # "code": 200, + # "data": [ + # { + # "accountId": 3684354, + # "clientOrderId": "test004", + # "lastActTime": 1600141535221, + # "orderOrigTime": 1600141535137, + # "orderPrice": "0.08", + # "orderSide": "buy", + # "orderSize": "65", + # "orderStatus": "created", + # "orderType": "limit", + # "source": "api", + # "stopPrice": "0.085", + # "symbol": "adausdt", + # "trailingRate": 0.001, + # "timeInForce": "gtc" + # } + # ] + # } + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse_list_dict(data, OrderListItem) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/algo/get_order_by_cid.py b/huobi/service/algo/get_order_by_cid.py new file mode 100644 index 0000000..c33c285 --- /dev/null +++ b/huobi/service/algo/get_order_by_cid.py @@ -0,0 +1,19 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import * +from huobi.model.algo import * + + +class GetOrderByClientOrderIdService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/algo-orders/specific" + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse(data, OrderHistoryItem) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/algo/get_order_history.py b/huobi/service/algo/get_order_history.py new file mode 100644 index 0000000..05aee58 --- /dev/null +++ b/huobi/service/algo/get_order_history.py @@ -0,0 +1,19 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import * +from huobi.model.algo import * + + +class GetOrderHistoryService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/algo-orders/history" + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse_list_dict(data, OrderHistoryItem) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/algo/post_cancel_orders.py b/huobi/service/algo/post_cancel_orders.py new file mode 100644 index 0000000..96caaff --- /dev/null +++ b/huobi/service/algo/post_cancel_orders.py @@ -0,0 +1,20 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import default_parse_fill_directly +from huobi.model.algo import * + + +class PostCancelOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/algo-orders/cancellation" + + # {'code': 200, 'data': {'accepted': [], 'rejected': ['test001', 'test002']}} + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse_fill_directly(data, CancelOrderResult) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/algo/post_create_order.py b/huobi/service/algo/post_create_order.py new file mode 100644 index 0000000..9f3ab86 --- /dev/null +++ b/huobi/service/algo/post_create_order.py @@ -0,0 +1,18 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * + + +class PostCreateOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/algo-orders" + + # {'code': 200, 'data': {'clientOrderId': 'test001'}} + def parse(dict_data): + data = dict_data.get('data') + return data.get('clientOrderId') + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/etf/__init__.py b/huobi/service/etf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/etf/get_etf_swap_config.py b/huobi/service/etf/get_etf_swap_config.py new file mode 100644 index 0000000..9712b9d --- /dev/null +++ b/huobi/service/etf/get_etf_swap_config.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.etf import * +from huobi.utils import * + + + +class GetEtfSwapConfigService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/etf/swap/config" + + def parse(dict_data): + data_info = dict_data.get("data", {}) + return default_parse(data_info, EtfSwapConfig, UnitPrice) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/etf/get_etf_swap_list.py b/huobi/service/etf/get_etf_swap_list.py new file mode 100644 index 0000000..26d01dd --- /dev/null +++ b/huobi/service/etf/get_etf_swap_list.py @@ -0,0 +1,23 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.etf import * + +class GetEtfSwapListService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/etf/swap/list" + + def parse(dict_data): + return EtfSwapList.json_parse_list(dict_data.get("data", [])) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + + diff --git a/huobi/service/etf/post_etf_swap_in.py b/huobi/service/etf/post_etf_swap_in.py new file mode 100644 index 0000000..f7b28eb --- /dev/null +++ b/huobi/service/etf/post_etf_swap_in.py @@ -0,0 +1,28 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.etf import * + + + +class PostEftSwapInService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/etf/swap/in" + + def parse(dict_data): + return default_parse_fill_directly(dict_data, EtfSwapInOut) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + + + + diff --git a/huobi/service/etf/post_etf_swap_out.py b/huobi/service/etf/post_etf_swap_out.py new file mode 100644 index 0000000..9eca017 --- /dev/null +++ b/huobi/service/etf/post_etf_swap_out.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.etf import * + + +class PostEtfSwapOutService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/etf/swap/out" + + def parse(dict_data): + return default_parse_fill_directly(dict_data, EtfSwapInOut) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + + diff --git a/huobi/service/generic/__init__.py b/huobi/service/generic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/generic/get_exchange_currencies.py b/huobi/service/generic/get_exchange_currencies.py new file mode 100644 index 0000000..d403c34 --- /dev/null +++ b/huobi/service/generic/get_exchange_currencies.py @@ -0,0 +1,23 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod + + +class GetExchangeCurrenciesService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/common/currencys" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return data_list if len(data_list) else [] + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/generic/get_exchange_symbols.py b/huobi/service/generic/get_exchange_symbols.py new file mode 100644 index 0000000..b92a56e --- /dev/null +++ b/huobi/service/generic/get_exchange_symbols.py @@ -0,0 +1,19 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.generic import * +from huobi.utils import * + + +class GetExchangeSymbolsService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/common/symbols" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, Symbol, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) diff --git a/huobi/service/generic/get_exchange_timestamp.py b/huobi/service/generic/get_exchange_timestamp.py new file mode 100644 index 0000000..f95d669 --- /dev/null +++ b/huobi/service/generic/get_exchange_timestamp.py @@ -0,0 +1,24 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.utils.json_parser import default_parse_data_as_long + + +class GetExchangeTimestampService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/common/timestamp" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/generic/get_market_status.py b/huobi/service/generic/get_market_status.py new file mode 100644 index 0000000..788e55c --- /dev/null +++ b/huobi/service/generic/get_market_status.py @@ -0,0 +1,18 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.generic import * +from huobi.utils import * + + +class GetMarketStatusService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/market-status" + + def parse(dict_data): + return default_parse(dict_data.get("data", {}), MarketStatus) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) diff --git a/huobi/service/generic/get_reference_currencies.py b/huobi/service/generic/get_reference_currencies.py new file mode 100644 index 0000000..cda6039 --- /dev/null +++ b/huobi/service/generic/get_reference_currencies.py @@ -0,0 +1,31 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.generic import * +from huobi.utils import * + + + +class GetReferenceCurrenciesService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/reference/currencies" + + def parse(dict_data): + ret_list = [] + data_list = dict_data.get("data", []) + if data_list and len(data_list): + for reference_currency in data_list: + reference_currency_obj = default_parse(reference_currency, ReferenceCurrency, Chain) + ret_list.append(reference_currency_obj) + return ret_list + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/generic/get_system_status.py b/huobi/service/generic/get_system_status.py new file mode 100644 index 0000000..ebeffa5 --- /dev/null +++ b/huobi/service/generic/get_system_status.py @@ -0,0 +1,23 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod + + +class GetSystemStatusService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/api/v2/summary.json" + kwargs["url"] = "https://status.huobigroup.com" + + def parse(dict_data): + return dict_data + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/linear-swap/__init__.py b/huobi/service/linear-swap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/linear-swap/post_swap_openorders.py b/huobi/service/linear-swap/post_swap_openorders.py new file mode 100644 index 0000000..b8249fe --- /dev/null +++ b/huobi/service/linear-swap/post_swap_openorders.py @@ -0,0 +1,69 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils.json_parser import default_parse_data_as_long + + + +''' +https://huobiapi.github.io/docs/usdt_swap/v1/cn/#136259e73a +该接口仅支持逐仓模式。 +请求参数 +参数名称 是否必须 类型 描述 取值范围 +contract_code true string 合约代码 "BTC-USDT" ... +page_index false int 页码,不填默认第1页 +page_size false int 页长,不填默认20,不得多于50 + +返回参数 +参数名称 是否必须 类型 描述 取值范围 +status true string 请求处理结果 + + +symbol true string 品种代码 +contract_code true string 合约代码 "BTC-USDT" ... +volume true decimal 委托数量 +price true decimal 委托价格 +order_price_type true string 订单报价类型 "limit":限价,"opponent":对手价,"post_only":只做maker单,post only下单只受用户持仓数量限制,"lightning":闪电平仓,"optimal_5":最优5档,"optimal_10":最优10档,"optimal_20":最优20档,"fok":FOK订单,"ioc":IOC订单, "opponent_ioc": 对手价-IOC下单,"lightning_ioc": 闪电平仓-IOC下单,"optimal_5_ioc": 最优5档-IOC下单,"optimal_10_ioc": 最优10档-IOC下单,"optimal_20_ioc":最优20档-IOC下单,"opponent_fok": 对手价-FOK下单,"lightning_fok":闪电平仓-FOK下单,"optimal_5_fok":最优5档-FOK下单,"optimal_10_fok":最优10档-FOK下单,"optimal_20_fok":最优20档-FOK下单 +order_type true int 订单类型 1:报单 、 2:撤单 、 3:强平、4:交割 +direction true string "buy":买 "sell":卖 +offset true string "open":开 "close":平 +lever_rate true int 杠杆倍数 +order_id true long 订单ID +order_id_str true string 订单ID,字符串类型 +client_order_id true long 客户订单ID +created_at true long 订单创建时间 +trade_volume true decimal 成交数量 +trade_turnover true decimal 成交总金额 +fee true decimal 手续费 +fee_asset true string 手续费币种 "BTC","ETH"... +trade_avg_price true decimal 成交均价 +margin_frozen true decimal 冻结保证金 +margin_asset true string 保证金币种(计价币种) +profit true decimal 收益 +status true int 订单状态 (3未成交 4部分成交 5部分成交已撤单 6全部成交 7已撤单) +order_source true string 订单来源 (system:系统、web:用户网页、api:用户API、m:用户M站、risk:风控系统、settlement:交割结算、ios:ios客户端、android:安卓客户端、windows:windows客户端、mac:mac客户端、trigger:计划委托触发、tpsl:止盈止损触发) +liquidation_type true string 强平类型 +canceled_at true long 撤单时间 +margin_mode true string 保证金模式 isolated:逐仓模式 +margin_account true string 保证金账户 比如“BTC-USDT” +is_tpsl true int 是否设置止盈止损 1:是;0:否 + +total_page true int 总页数 +current_page true int 当前页 +total_size true int 总条数 + +ts true long 时间戳 +''' + + +class PostSwapOpenOrdersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/linear-swap-api/v1/swap_openorders" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/margin/__init__.py b/huobi/service/margin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/margin/get_cross_margin_account_balance.py b/huobi/service/margin/get_cross_margin_account_balance.py new file mode 100644 index 0000000..b534211 --- /dev/null +++ b/huobi/service/margin/get_cross_margin_account_balance.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.margin import * +from huobi.utils import * + + + +class GetCrossMarginAccountBalanceService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/cross-margin/accounts/balance" + + def parse(dict_data): + return CrossMarginAccountBalance.json_parse(dict_data.get("data", {})) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/get_cross_margin_loan_info.py b/huobi/service/margin/get_cross_margin_loan_info.py new file mode 100644 index 0000000..30040d2 --- /dev/null +++ b/huobi/service/margin/get_cross_margin_loan_info.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.margin import * +from huobi.utils import * + + + +class GetCrossMarginLoanInfoService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/cross-margin/loan-info" + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", []), CrossMarginLoanInfo, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/get_cross_margin_loan_orders.py b/huobi/service/margin/get_cross_margin_loan_orders.py new file mode 100644 index 0000000..36c8878 --- /dev/null +++ b/huobi/service/margin/get_cross_margin_loan_orders.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.margin import * +from huobi.utils import * + + + +class GetCrossMarginLoanOrdersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/cross-margin/loan-orders" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, LoanOrder, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/get_general_repayment_loan_records.py b/huobi/service/margin/get_general_repayment_loan_records.py new file mode 100644 index 0000000..59df420 --- /dev/null +++ b/huobi/service/margin/get_general_repayment_loan_records.py @@ -0,0 +1,22 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.margin.general_repay_loan_record import GeneralRepayLoanRecord +from huobi.model.margin.general_repay_loan_result import GeneralRepayLoanResult +from huobi.utils.json_parser import default_parse_list_dict + + +class GetGeneralRepaymentLoanRecordsService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + + def get_channel(): + path = "/v2/account/repayment" + return path + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", {}), GeneralRepayLoanRecord) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, get_channel(), self.params, parse) \ No newline at end of file diff --git a/huobi/service/margin/get_margin_account_balance.py b/huobi/service/margin/get_margin_account_balance.py new file mode 100644 index 0000000..14531a6 --- /dev/null +++ b/huobi/service/margin/get_margin_account_balance.py @@ -0,0 +1,32 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.account import Balance +from huobi.model.margin import * +from huobi.utils import * + + + +class GetMarginAccountBalanceService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/margin/accounts/balance" + + def parse(dict_data): + data_list = dict_data.get("data", []) + account_balance_list = [] + if data_list and len(data_list): + for row in data_list: + account_balance = default_parse(row, MarginAccountBalance, Balance) + account_balance_list.append(account_balance) + return account_balance_list + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/get_margin_loan_info.py b/huobi/service/margin/get_margin_loan_info.py new file mode 100644 index 0000000..82b9a04 --- /dev/null +++ b/huobi/service/margin/get_margin_loan_info.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.margin import * +from huobi.utils import * + + + +class GetMarginLoanInfoService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/margin/loan-info" + + def parse(dict_data): + return MarginLoanInfo.json_parse(dict_data.get("data", [])) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/get_margin_loan_orders.py b/huobi/service/margin/get_margin_loan_orders.py new file mode 100644 index 0000000..509d210 --- /dev/null +++ b/huobi/service/margin/get_margin_loan_orders.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.margin import * +from huobi.utils import * + + + +class GetMarginLoanOrdersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/margin/loan-orders" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, LoanOrder, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/post_create_margin_order.py b/huobi/service/margin/post_create_margin_order.py new file mode 100644 index 0000000..1ffc452 --- /dev/null +++ b/huobi/service/margin/post_create_margin_order.py @@ -0,0 +1,23 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostCreateMarginOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/margin/orders" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/post_cross_margin_create_loan_orders.py b/huobi/service/margin/post_cross_margin_create_loan_orders.py new file mode 100644 index 0000000..f99fa3e --- /dev/null +++ b/huobi/service/margin/post_cross_margin_create_loan_orders.py @@ -0,0 +1,23 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostCrossMarginCreateLoanOrdersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/cross-margin/orders" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/post_cross_margin_loan_order_repay.py b/huobi/service/margin/post_cross_margin_loan_order_repay.py new file mode 100644 index 0000000..0fc53eb --- /dev/null +++ b/huobi/service/margin/post_cross_margin_loan_order_repay.py @@ -0,0 +1,22 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod + + +class PostCrossMarginLoanOrderRepayService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/cross-margin/orders/{order_id}/repay".format(order_id=self.params.get("order-id")) + + def parse(dict_data): + return dict_data.get("status", None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/post_cross_margin_transfer_in.py b/huobi/service/margin/post_cross_margin_transfer_in.py new file mode 100644 index 0000000..dada255 --- /dev/null +++ b/huobi/service/margin/post_cross_margin_transfer_in.py @@ -0,0 +1,24 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostCrossMarginTransferInService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/cross-margin/transfer-in" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/post_cross_margin_transfer_out.py b/huobi/service/margin/post_cross_margin_transfer_out.py new file mode 100644 index 0000000..91f7019 --- /dev/null +++ b/huobi/service/margin/post_cross_margin_transfer_out.py @@ -0,0 +1,24 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostCrossMarginTransferOutService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/cross-margin/transfer-out" + + def parse(dict_data): + transfer_id = default_parse_data_as_long(dict_data, None) + return transfer_id + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/post_general_repay_loan.py b/huobi/service/margin/post_general_repay_loan.py new file mode 100644 index 0000000..95d26dc --- /dev/null +++ b/huobi/service/margin/post_general_repay_loan.py @@ -0,0 +1,21 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.margin.general_repay_loan_result import GeneralRepayLoanResult +from huobi.utils.json_parser import default_parse_list_dict + + +class PostGeneralRepayLoanService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + + def get_channel(): + path = "/v2/account/repayment" + return path + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", {}), GeneralRepayLoanResult) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, get_channel(), self.params, parse) \ No newline at end of file diff --git a/huobi/service/margin/post_repay_margin_order.py b/huobi/service/margin/post_repay_margin_order.py new file mode 100644 index 0000000..d1f7a53 --- /dev/null +++ b/huobi/service/margin/post_repay_margin_order.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostRepayMarginOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + loan_id = self.params["loan_id"] + def get_channel(): + path = "/v1/margin/orders/{}/repay" + return path.format(loan_id) + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, get_channel(), self.params, parse) + + + + + + diff --git a/huobi/service/margin/post_transfer_in_margin.py b/huobi/service/margin/post_transfer_in_margin.py new file mode 100644 index 0000000..3415432 --- /dev/null +++ b/huobi/service/margin/post_transfer_in_margin.py @@ -0,0 +1,23 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostTransferInMarginService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/dw/transfer-in/margin" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/margin/post_transfer_out_margin.py b/huobi/service/margin/post_transfer_out_margin.py new file mode 100644 index 0000000..57e4876 --- /dev/null +++ b/huobi/service/margin/post_transfer_out_margin.py @@ -0,0 +1,23 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostTransferOutMarginService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/dw/transfer-out/margin" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/market/__init__.py b/huobi/service/market/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/huobi/service/market/__init__.py @@ -0,0 +1 @@ + diff --git a/huobi/service/market/get_candlestick.py b/huobi/service/market/get_candlestick.py new file mode 100644 index 0000000..6fa76ee --- /dev/null +++ b/huobi/service/market/get_candlestick.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.market import * +from huobi.utils import * + + + +class GetCandleStickService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/market/history/kline" + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", []), Candlestick) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/market/get_history_trade.py b/huobi/service/market/get_history_trade.py new file mode 100644 index 0000000..8e9015d --- /dev/null +++ b/huobi/service/market/get_history_trade.py @@ -0,0 +1,35 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.market import * +from huobi.utils import * + + +class GetHistoryTradeService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/market/history/trade" + + def parse(dict_data): + trade_list_ret = [] # two level list, list item is list too + data_list_outer = dict_data.get("data", []) + if len(data_list_outer): + for row in data_list_outer: + data_list_inner = row.get("data", []) + if len(data_list_inner): + for trade_info in data_list_inner: + trade_obj = default_parse_list_dict(trade_info, Trade, None) # return a list + if trade_obj: + trade_list_ret.append(trade_obj) + + return trade_list_ret + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/market/get_market_detail.py b/huobi/service/market/get_market_detail.py new file mode 100644 index 0000000..8b72e20 --- /dev/null +++ b/huobi/service/market/get_market_detail.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.market import * +from huobi.utils import * + + + +class GetMarketDetailService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/market/detail" + + def parse(dict_data): + tick = dict_data.get("tick", {}) + return default_parse(tick, MarketDetail) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/market/get_market_detail_merged.py b/huobi/service/market/get_market_detail_merged.py new file mode 100644 index 0000000..94977c0 --- /dev/null +++ b/huobi/service/market/get_market_detail_merged.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.market import * +from huobi.utils import * + + + +class GetMarketDetailMergedService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/market/detail/merged" + + def parse(dict_data): + tick = dict_data.get("tick", {}) + return default_parse_fill_directly(tick, MarketDetailMerged) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/market/get_market_tickers.py b/huobi/service/market/get_market_tickers.py new file mode 100644 index 0000000..285c343 --- /dev/null +++ b/huobi/service/market/get_market_tickers.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.market import * +from huobi.utils import * + + + +class GetMarketTickersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/market/tickers" + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", []), MarketTicker) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/market/get_market_trade.py b/huobi/service/market/get_market_trade.py new file mode 100644 index 0000000..9d14e06 --- /dev/null +++ b/huobi/service/market/get_market_trade.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.market import * +from huobi.utils import * + + +class GetMarketTradeService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/market/trade" + + def parse(dict_data): + tick = dict_data.get("tick", {}) + data = tick.get("data", []) + return default_parse_list_dict(data, Trade, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/market/get_pricedepth.py b/huobi/service/market/get_pricedepth.py new file mode 100644 index 0000000..a245382 --- /dev/null +++ b/huobi/service/market/get_pricedepth.py @@ -0,0 +1,24 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.market import PriceDepth + + +class GetPriceDepthService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/market/depth" + + def parse(dict_data): + tick = dict_data.get("tick", {}) + return PriceDepth.json_parse(tick) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET, channel, self.params, parse) + + + + + + diff --git a/huobi/service/market/req_candlestick.py b/huobi/service/market/req_candlestick.py new file mode 100644 index 0000000..695a039 --- /dev/null +++ b/huobi/service/market/req_candlestick.py @@ -0,0 +1,34 @@ +import time + +from huobi.utils import * + +from huobi.connection.websocket_req_client import * +from huobi.model.market import * +from huobi.utils.channels_request import * + + +class ReqCandleStickService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + interval = self.params["interval"] + from_ts_second = self.params.get("from_ts_second", None) + end_ts_second = self.params.get("end_ts_second", None) + + def subscription(connection): + for symbol in symbol_list: + connection.send(request_kline_channel(symbol, interval, from_ts_second, end_ts_second)) + time.sleep(0.01) + + def parse(dict_data): + return default_parse(dict_data, CandlestickReq, Candlestick) + + WebSocketReqClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/req_market_detail.py b/huobi/service/market/req_market_detail.py new file mode 100644 index 0000000..043b204 --- /dev/null +++ b/huobi/service/market/req_market_detail.py @@ -0,0 +1,31 @@ +import time + +from huobi.utils import * + +from huobi.connection.websocket_req_client import * +from huobi.model.market import * +from huobi.utils.channels_request import * + + +class ReqMarketDetailService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(request_market_detail_channel(symbol)) + time.sleep(0.01) + + def parse(dict_data): + return default_parse(dict_data, MarketDetailReq, MarketDetail) + + WebSocketReqClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/req_mbp.py b/huobi/service/market/req_mbp.py new file mode 100644 index 0000000..51aa791 --- /dev/null +++ b/huobi/service/market/req_mbp.py @@ -0,0 +1,27 @@ +import time + +from huobi.model.market import * +from huobi.utils import * +from huobi.connection.websocket_req_client import WebSocketReqClient + + +class ReqMbpService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + level = self.params["levels"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(request_mbp_channel(symbol, level)) + time.sleep(0.01) + + def parse(dict_data): + return MbpReq.json_parse(dict_data) + + WebSocketReqClient(**kwargs).execute_subscribe_mbp(subscription, + parse, + callback, + error_handler) diff --git a/huobi/service/market/req_pricedepth.py b/huobi/service/market/req_pricedepth.py new file mode 100644 index 0000000..1a5cd59 --- /dev/null +++ b/huobi/service/market/req_pricedepth.py @@ -0,0 +1,36 @@ +import time + +from huobi.connection.websocket_req_client import * +from huobi.utils.channels_request import * +from huobi.model.market import * + + +class ReqPriceDepthService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + step = self.params["step"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(request_price_depth_channel(symbol, step)) + time.sleep(0.01) + + def parse(dict_data): + price_depth_event = PriceDepthReq() + price_depth_event.id = dict_data.get("id") + price_depth_event.rep = dict_data.get("rep") + data = dict_data.get("data", {}) + price_depth_obj = PriceDepth.json_parse(data) + price_depth_event.data = price_depth_obj + return price_depth_event + + WebSocketReqClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/req_trade_detail.py b/huobi/service/market/req_trade_detail.py new file mode 100644 index 0000000..44b28d4 --- /dev/null +++ b/huobi/service/market/req_trade_detail.py @@ -0,0 +1,29 @@ +import time + +from huobi.model.market import * +from huobi.utils import * +from huobi.connection.websocket_req_client import * + + +class ReqTradeDetailService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(request_trade_detail_channel(symbol)) + time.sleep(0.01) + + def parse(dict_data): + return default_parse(dict_data, TradeDetailReq, TradeDetail) + + WebSocketReqClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/sub_candlestick.py b/huobi/service/market/sub_candlestick.py new file mode 100644 index 0000000..794f87a --- /dev/null +++ b/huobi/service/market/sub_candlestick.py @@ -0,0 +1,33 @@ +import time + +from huobi.utils import * + +from huobi.connection.subscribe_client import SubscribeClient +from huobi.model.market import * + + + +class SubCandleStickService: + def __init__(self, params): + + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + interval = self.params["interval"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(kline_channel(symbol, interval)) + time.sleep(0.01) + + def parse(dict_data): + return default_parse(dict_data, CandlestickEvent, Candlestick) + + SubscribeClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/sub_market_detail.py b/huobi/service/market/sub_market_detail.py new file mode 100644 index 0000000..13c4655 --- /dev/null +++ b/huobi/service/market/sub_market_detail.py @@ -0,0 +1,31 @@ +import time + +from huobi.utils import * + +from huobi.connection.subscribe_client import SubscribeClient +from huobi.model.market import * + + + +class SubMarketDetailService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(market_detail_channel(symbol)) + time.sleep(0.01) + + def parse(dict_data): + return default_parse(dict_data, MarketDetailEvent, MarketDetail) + + SubscribeClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/sub_mbp_full.py b/huobi/service/market/sub_mbp_full.py new file mode 100644 index 0000000..0e99660 --- /dev/null +++ b/huobi/service/market/sub_mbp_full.py @@ -0,0 +1,27 @@ +import time + +from huobi.model.market import * +from huobi.utils import * +from huobi.connection.subscribe_client import SubscribeClient + + +class SubMbpFullService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + level = self.params["levels"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(mbp_full_channel(symbol, level)) + time.sleep(0.01) + + def parse(dict_data): + return MbpFullEvent.json_parse(dict_data) + + SubscribeClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) diff --git a/huobi/service/market/sub_mbp_increase.py b/huobi/service/market/sub_mbp_increase.py new file mode 100644 index 0000000..a185769 --- /dev/null +++ b/huobi/service/market/sub_mbp_increase.py @@ -0,0 +1,30 @@ +import time + +from huobi.model.market import * +from huobi.utils import * +from huobi.connection.subscribe_client import SubscribeClient + + +class SubMbpIncreaseService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + level = self.params["levels"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(mbp_increase_channel(symbol, level)) + time.sleep(0.01) + + def parse(dict_data): + return MbpIncreaseEvent.json_parse(dict_data) + + SubscribeClient(**kwargs).execute_subscribe_mbp(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/sub_pricedepth.py b/huobi/service/market/sub_pricedepth.py new file mode 100644 index 0000000..6210365 --- /dev/null +++ b/huobi/service/market/sub_pricedepth.py @@ -0,0 +1,34 @@ +import time + +from huobi.model.market import * +from huobi.utils import * +from huobi.connection.subscribe_client import SubscribeClient + + +class SubPriceDepthService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + step = self.params["step"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(price_depth_channel(symbol, step)) + time.sleep(0.01) + + def parse(dict_data): + price_depth_event_obj = PriceDepthEvent() + price_depth_event_obj.ch = dict_data.get("ch", "") + tick = dict_data.get("tick", "") + price_depth_event_obj.tick = PriceDepth.json_parse(tick) + return price_depth_event_obj + + SubscribeClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/sub_pricedepth_bbo.py b/huobi/service/market/sub_pricedepth_bbo.py new file mode 100644 index 0000000..b44597b --- /dev/null +++ b/huobi/service/market/sub_pricedepth_bbo.py @@ -0,0 +1,29 @@ +import time + +from huobi.utils import * +from huobi.model.market import * +from huobi.connection.subscribe_client import SubscribeClient + + +class SubPriceDepthBboService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(price_depth_bbo_channel(symbol)) + time.sleep(0.01) + + def parse(dict_data): + return default_parse(dict_data, PriceDepthBboEvent, PriceDepthBbo) + + SubscribeClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/market/sub_trade_detail.py b/huobi/service/market/sub_trade_detail.py new file mode 100644 index 0000000..1bd539b --- /dev/null +++ b/huobi/service/market/sub_trade_detail.py @@ -0,0 +1,32 @@ +import time + +from huobi.model.market import * +from huobi.utils import * +from huobi.connection.subscribe_client import SubscribeClient + + +class SubTradeDetailService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(trade_detail_channel(symbol)) + time.sleep(0.01) + + def parse(dict_data): + tick = dict_data.get("tick", {}) + trade_detail_event = default_parse(tick, TradeDetailEvent, TradeDetail) + trade_detail_event.ch = dict_data.get("ch", "") + return trade_detail_event + + SubscribeClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler) + + + diff --git a/huobi/service/subuser/__init__.py b/huobi/service/subuser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/subuser/get_uid.py b/huobi/service/subuser/get_uid.py new file mode 100644 index 0000000..1e16c7c --- /dev/null +++ b/huobi/service/subuser/get_uid.py @@ -0,0 +1,20 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.subuser import * +from huobi.utils.json_parser import default_parse_data_as_long + + + +class GetUidService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/user/uid" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/subuser/get_user_apikey_info.py b/huobi/service/subuser/get_user_apikey_info.py new file mode 100644 index 0000000..a37c392 --- /dev/null +++ b/huobi/service/subuser/get_user_apikey_info.py @@ -0,0 +1,18 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.subuser import * + + +class GetUserApikeyInfoService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/user/api-key" + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", {}), UserApikeyInfo) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) diff --git a/huobi/service/subuser/post_create_subuser.py b/huobi/service/subuser/post_create_subuser.py new file mode 100644 index 0000000..45dd123 --- /dev/null +++ b/huobi/service/subuser/post_create_subuser.py @@ -0,0 +1,18 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.subuser import * + + +class PostSubuserCreationService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/creation" + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", {}), SubuserCreation) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/subuser/post_set_transferability.py b/huobi/service/subuser/post_set_transferability.py new file mode 100644 index 0000000..47fe674 --- /dev/null +++ b/huobi/service/subuser/post_set_transferability.py @@ -0,0 +1,18 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.subuser import * + + +class PostSetSubuserTransferability: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/transferability" + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", {}), SubuserTransferability) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/subuser/post_subuser_apikey_deletion.py b/huobi/service/subuser/post_subuser_apikey_deletion.py new file mode 100644 index 0000000..821fbfa --- /dev/null +++ b/huobi/service/subuser/post_subuser_apikey_deletion.py @@ -0,0 +1,17 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod + + +class PostSubuserApikeyDeletionService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/api-key-deletion" + + # {'code': 200, 'data': None, 'ok': True} + def parse(dict_data): + return dict_data + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/subuser/post_subuser_apikey_generation.py b/huobi/service/subuser/post_subuser_apikey_generation.py new file mode 100644 index 0000000..76243a5 --- /dev/null +++ b/huobi/service/subuser/post_subuser_apikey_generation.py @@ -0,0 +1,18 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.subuser import * + + +class PostSubuserApikeyGenerationService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/api-key-generation" + + def parse(dict_data): + return default_parse(dict_data.get("data", {}), SubuserApikeyGeneration) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/subuser/post_subuser_apikey_modification.py b/huobi/service/subuser/post_subuser_apikey_modification.py new file mode 100644 index 0000000..6671abe --- /dev/null +++ b/huobi/service/subuser/post_subuser_apikey_modification.py @@ -0,0 +1,18 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.utils import * +from huobi.model.subuser import * + + +class PostSubuserApikeyModificationService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/api-key-modification" + + def parse(dict_data): + return default_parse(dict_data.get("data", {}), SubuserApikeyModification) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/subuser/post_tradable_market.py b/huobi/service/subuser/post_tradable_market.py new file mode 100644 index 0000000..4e644eb --- /dev/null +++ b/huobi/service/subuser/post_tradable_market.py @@ -0,0 +1,17 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.subuser.trade_market import TradeMarket +from huobi.utils import * + + +class PostTradableMarketService: + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/tradable-market" + + def parse(dict_data): + return default_parse_list_dict(dict_data.get("data", {}), TradeMarket) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) diff --git a/huobi/service/trade/__init__.py b/huobi/service/trade/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/trade/get_feerate.py b/huobi/service/trade/get_feerate.py new file mode 100644 index 0000000..f3c7358 --- /dev/null +++ b/huobi/service/trade/get_feerate.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.trade import * +from huobi.utils import * + + +class GetFeeRateService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/fee/fee-rate/get" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, FeeRate, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/get_history_orders.py b/huobi/service/trade/get_history_orders.py new file mode 100644 index 0000000..eeafe6c --- /dev/null +++ b/huobi/service/trade/get_history_orders.py @@ -0,0 +1,24 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.trade import * + + +class GetHistoryOrdersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/history" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return Order.json_parse_list(data_list) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/get_match_results.py b/huobi/service/trade/get_match_results.py new file mode 100644 index 0000000..579bde1 --- /dev/null +++ b/huobi/service/trade/get_match_results.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils import * + + +class GetMatchResultsService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/matchresults" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, MatchResult, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/get_match_results_by_order_id.py b/huobi/service/trade/get_match_results_by_order_id.py new file mode 100644 index 0000000..84094d5 --- /dev/null +++ b/huobi/service/trade/get_match_results_by_order_id.py @@ -0,0 +1,60 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#client-order-id-2 +成交明细 +API Key 权限:读取 +限频值(NEW):50次/2s + +此接口返回指定订单的成交明细。 + +返回的主数据对象为一个对象数组,其中每一个元件代表一个交易结果。: +{ + "data": [ + { + "id": 29553, + "order-id": 59378, + "match-id": 59335, + "trade-id": 100282808529, + "symbol": "ethusdt", + "type": "buy-limit", + "source": "api", + "price": "100.1000000000", + "filled-amount": "9.1155000000", 成交数量 + "filled-fees": "0.0182310000", 交易手续费(正值)或交易返佣金(负值) + "created-at": 1494901400435, 该成交记录创建的时间戳(略晚于成交时间) + "role": "maker", + "filled-points": "0.0", + “fee-deduct-state”:"done", + "fee-deduct-currency": "" 如果为空,代表扣除的手续费是原币;如果为"ht",代表抵扣手续费的是HT;如果为"hbpoint",代表抵扣手续费的是点卡 + } + ... + ] +} + +''' + +class GetMatchResultsByOrderIdService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + order_id = self.params["order_id"] + def get_channel(): + path = "/v1/order/orders/{}/matchresults" + return path.format(order_id) + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, MatchResult, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, get_channel(), self.params, parse) + + + + + + diff --git a/huobi/service/trade/get_open_orders.py b/huobi/service/trade/get_open_orders.py new file mode 100644 index 0000000..c26ce81 --- /dev/null +++ b/huobi/service/trade/get_open_orders.py @@ -0,0 +1,41 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.trade import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#75a79bcd48 +查询当前未成交订单 +API Key 权限:读取 +限频值(NEW):50次/2s +查询已提交但是仍未完全成交或未被撤销的订单。 + +请求参数: +account-id 账户 ID,取值参考 GET /v1/account/accounts。现货交易使用‘spot’账户的 account-id;逐仓杠杆交易,请使用 ‘margin’ 账户的 account-id;全仓杠杆交易,请使用 ‘super-margin’ 账户的 account-id;c2c杠杆交易,请使用borrow账户的account-id +symbol 交易对,即btcusdt, ethbtc...(取值参考GET /v1/common/symbols) +side 指定只返回某一个方向的订单,可能的值有: buy, sell. 默认两个方向都返回。 +from 查询起始 ID,如果是向后查询,则赋值为上一次查询结果中得到的最后一条id ;如果是向前查询,则赋值为上一次查询结果中得到的第一条id +direct 查询方向,prev 向前;next 向后 如字段'from'已设定,此字段'direct'为必填 +size 返回订单的数量,最大值500。 + + + +''' + +class GetOpenOrdersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/openOrders" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return Order.json_parse_list(data_list) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/get_order_by_client_order_id.py b/huobi/service/trade/get_order_by_client_order_id.py new file mode 100644 index 0000000..a076e72 --- /dev/null +++ b/huobi/service/trade/get_order_by_client_order_id.py @@ -0,0 +1,35 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#92d59b6aad +查询订单详情(基于client order ID) +API Key 权限:读取 +限频值(NEW):50次/2s + +此接口返回指定用户自编订单号(24小时内)的订单最新状态和详情。通过API创建的订单,撤销超过2小时后无法查询。建议通过GET /v1/order/orders/{order-id}来撤单,比使用clientOrderId更快更稳定 + + + + +''' + +class GetOrderByClientOrderIdService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/orders/getClientOrder" + + def parse(dict_data): + data_dict = dict_data.get("data", {}) + return Order.json_parse(data_dict) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/get_order_by_id.py b/huobi/service/trade/get_order_by_id.py new file mode 100644 index 0000000..fbdd269 --- /dev/null +++ b/huobi/service/trade/get_order_by_id.py @@ -0,0 +1,59 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#5f8b337a4c +查询订单详情 +API Key 权限:读取 +限频值(NEW):50次/2s + +此接口返回指定订单的最新状态和详情。通过API创建的订单,撤销超过2小时后无法查询。 +请求参数: +order-id 订单ID,填在path中 + +响应数据: +{ + "data": + { + "id": 59378, 订单ID + "symbol": "ethusdt", 交易对 + "account-id": 100009, 账户 ID + "amount": "10.1000000000", 订单数量 + "price": "100.1000000000", 订单价格 + "created-at": 1494901162595, 订单创建时间 + "type": "buy-limit", 订单类型 + "field-amount": "10.1000000000", 已成交数量 + "field-cash-amount": "1011.0100000000", 已成交总金额 + "field-fees": "0.0202000000", 已成交手续费(准确数值请参考matchresults接口) + "finished-at": 1494901400468, 订单变为终结态的时间,不是成交时间,包含“已撤单”状态 + "user-id": 1000, + "source": "api", 订单来源 + "state": "filled", 订单状态 + "canceled-at": 0 订单撤销时间 + } +} +''' + +class GetOrderByIdService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + order_id = self.params["order_id"] + def get_channel(): + path = "/v1/order/orders/{}" + return path.format(order_id) + + def parse(dict_data): + data_dict = dict_data.get("data") + return Order.json_parse(data_dict) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, get_channel(), self.params, parse) + + + + + + diff --git a/huobi/service/trade/get_orders.py b/huobi/service/trade/get_orders.py new file mode 100644 index 0000000..958b0e6 --- /dev/null +++ b/huobi/service/trade/get_orders.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils import * + + +class GetOrdersService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/orders" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return Order.json_parse_list(data_list) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/get_transact_feerate.py b/huobi/service/trade/get_transact_feerate.py new file mode 100644 index 0000000..9113dee --- /dev/null +++ b/huobi/service/trade/get_transact_feerate.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant.system import HttpMethod +from huobi.model.trade import * +from huobi.utils import * + + +class GetTransactFeeRateService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/reference/transact-fee-rate" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, TransactFeeRate, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/post_batch_cancel_open_order.py b/huobi/service/trade/post_batch_cancel_open_order.py new file mode 100644 index 0000000..d8c7656 --- /dev/null +++ b/huobi/service/trade/post_batch_cancel_open_order.py @@ -0,0 +1,56 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#95f2078356 +批量撤销所有订单 +API Key 权限:交易 +限频值(NEW):50次/2s + +此接口发送批量撤销所有(单次最大100个)订单的请求。 +此接口只提交取消请求,实际取消结果需要通过订单状态,撮合状态等接口来确认。 +请求参数: +account-id 账户ID,取值参考 GET /v1/account/accounts +symbol 交易代码列表(最多10 个symbols,多个交易代码间以逗号分隔),btcusdt, ethbtc...(取值参考/v1/common/symbols) +types 订单类型组合,使用逗号分割 +side “buy”或“sell”,缺省将返回所有符合条件尚未成交订单 +size 撤销订单的数量 [0,100] 默认值100 + + + + + + + + + + + + + + + + + +''' + +class PostBatchCancelOpenOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/orders/batchCancelOpenOrders" + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse(data, BatchCancelCount) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/post_batch_cancel_order.py b/huobi/service/trade/post_batch_cancel_order.py new file mode 100644 index 0000000..ab599af --- /dev/null +++ b/huobi/service/trade/post_batch_cancel_order.py @@ -0,0 +1,38 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils import * +from huobi.model.trade import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#b9af010185 +批量撤销指定订单 +API Key 权限:交易 +限频值(NEW):50次/2s +此接口同时为多个订单(基于id)发送取消请求,建议通过order-ids来撤单,比client-order-ids更快更稳定。 +请求参数: +order-ids 订单编号列表(order-ids和client-order-ids必须且只能选一个填写,不超过50张订单),建议通过order-ids来撤单,比client-order-ids更快更稳定 +client-order-ids 用户自编订单号列表(order-ids和client-order-ids必须且只能选一个填写,不超过50张订单),必须已有该订单存在,否则下次下单时不允许用此值 + + + +''' + + +class PostBatchCancelOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/orders/batchcancel" + + def parse(dict_data): + data = dict_data.get("data", {}) + return default_parse_fill_directly(data, BatchCancelResult) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/post_batch_create_order.py b/huobi/service/trade/post_batch_create_order.py new file mode 100644 index 0000000..a56c6e4 --- /dev/null +++ b/huobi/service/trade/post_batch_create_order.py @@ -0,0 +1,32 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade.batch_create_order import BatchCreateOrder +from huobi.utils import * +''' +https://huobiapi.github.io/docs/spot/v1/cn/#fd6ce2a756 +批量下单 +API Key 权限:交易 +限频值(NEW):50次/2s + +一个批量最多10张订单 + +''' +class PostBatchCreateOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/batch-orders" + + def parse(dict_data): + data = dict_data.get("data", []) + return default_parse_list_dict(data, BatchCreateOrder, []) + + return RestApiSyncClient(**kwargs).request_process_post_batch(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/post_cancel_client_order.py b/huobi/service/trade/post_cancel_client_order.py new file mode 100644 index 0000000..7542f94 --- /dev/null +++ b/huobi/service/trade/post_cancel_client_order.py @@ -0,0 +1,33 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import default_parse_data_as_long +''' +https://huobiapi.github.io/docs/spot/v1/cn/#4e53c0fccd +撤销订单(基于client order ID) +API Key 权限:交易 +限频值(NEW):100次/2s + +此接口基于client-order-id(24小时内有效)发送一个撤销订单的请求。 +撤单个订单建议通过接口/v1/order/orders/{order-id}/submitcancel,会更快更稳定 +请求参数: +client-order-id 用户自编订单号,必须24小时内已有该订单存在,否则下次下单时不允许用此值 +''' + +class PostCancelClientOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/orders/submitCancelClientOrder" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/post_cancel_order.py b/huobi/service/trade/post_cancel_order.py new file mode 100644 index 0000000..5873d35 --- /dev/null +++ b/huobi/service/trade/post_cancel_order.py @@ -0,0 +1,49 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import default_parse_data_as_long +''' +https://huobiapi.github.io/docs/spot/v1/cn/#de93fae07b +API Key 权限:交易 +限频值(NEW):100次/2s + +此接口发送一个撤销订单的请求。 +此接口只提交取消请求,实际取消结果需要通过订单状态,撮合状态等接口来确认。 + +请求参数: +order-id 订单ID,填在path中 + +Success response: +{ + "data": "59378" 返回的主数据对象是一个对应下单单号的字符串。 +} + +Failure response: +{ + "status": "error", + "err-code": "order-orderstate-error", + "err-msg": "订单状态错误", + "order-state":-1 // 当前订单状态 +} +''' + +class PostCancelOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + order_id = self.params["order_id"] + + def get_channel(): + path = "/v1/order/orders/{}/submitcancel" + return path.format(order_id) + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, get_channel(), self.params, parse) + + + + + + diff --git a/huobi/service/trade/post_create_order.py b/huobi/service/trade/post_create_order.py new file mode 100644 index 0000000..158d740 --- /dev/null +++ b/huobi/service/trade/post_create_order.py @@ -0,0 +1,57 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.utils.json_parser import default_parse_data_as_long +''' +https://huobiapi.github.io/docs/spot/v1/cn/#5ea2e0cde2-7 +下单 +API Key 权限:交易 限频值;100次/2s +发送一个新订单到火币以进行撮合。 +请求参数: +account-id 账户 ID,取值参考 GET /v1/account/accounts。现货交易使用 ‘spot’ 账户的 account-id;逐仓杠杆交易,请使用 ‘margin’ 账户的 account-id;全仓杠杆交易,请使用 ‘super-margin’ 账户的 account-id +symbol 交易对,即btcusdt, ethbtc...(取值参考GET /v1/common/symbols) +type 订单类型,包括buy-market, sell-market, buy-limit, sell-limit, buy-ioc, sell-ioc, buy-limit-maker, sell-limit-maker(说明见下文), buy-stop-limit, sell-stop-limit, buy-limit-fok, sell-limit-fok, buy-stop-limit-fok, sell-stop-limit-fok +amount 订单交易量(市价买单为订单交易额) +price 订单价格(对市价单无效) +source 现货交易填写“spot-api”,逐仓杠杆交易填写“margin-api”,全仓杠杆交易填写“super-margin-api”, C2C杠杆交易填写"c2c-margin-api" +client-order-id 用户自编订单号(最大长度64个字符,须在24小时内保持唯一性) +stop-price 止盈止损订单触发价格 +operator 止盈止损订单触发价运算符 gte – greater than and equal (>=), lte – less than and equal (<=) + +buy-limit-maker +当“下单价格”>=“市场最低卖出价”,订单提交后,系统将拒绝接受此订单; +当“下单价格”<“市场最低卖出价”,提交成功后,此订单将被系统接受。 + +sell-limit-maker +当“下单价格”<=“市场最高买入价”,订单提交后,系统将拒绝接受此订单; +当“下单价格”>“市场最高买入价”,提交成功后,此订单将被系统接受。 + +响应数据: +{ + "account-id": "100009", + "amount": "10.1", + "price": "100.1", + "source": "api", + "symbol": "ethusdt", + "type": "buy-limit", + "client-order-id": "a0001" 如client order ID(在24小时内)被复用,节点将返回错误消息invalid.client.order.id。 +} +''' + +class PostCreateOrderService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/order/orders/place" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/post_transfer_futures_pro.py b/huobi/service/trade/post_transfer_futures_pro.py new file mode 100644 index 0000000..0723221 --- /dev/null +++ b/huobi/service/trade/post_transfer_futures_pro.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils import * +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostTransferFuturesProService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/futures/transfer" + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/trade/req_order_detail.py b/huobi/service/trade/req_order_detail.py new file mode 100644 index 0000000..9f2df0c --- /dev/null +++ b/huobi/service/trade/req_order_detail.py @@ -0,0 +1,35 @@ + + +from huobi.connection.websocket_req_client import * +from huobi.model.trade import * +from huobi.utils import * + + +class ReqOrderDetailService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + order_id = self.params["order-id"] + client_req_id = self.params["cid"] + + def subscription(connection): + connection.send(request_order_detail_channel(order_id, client_req_id)) + + def parse(dict_data): + order_update_event = default_parse(dict_data, OrderDetailReq, OrderListItem) + + return order_update_event + + WebSocketReqClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler, + is_trade=True) + + + + + + + diff --git a/huobi/service/trade/req_order_list.py b/huobi/service/trade/req_order_list.py new file mode 100644 index 0000000..51a63c0 --- /dev/null +++ b/huobi/service/trade/req_order_list.py @@ -0,0 +1,38 @@ +import time + + +from huobi.connection.websocket_req_client import * +from huobi.model.trade import * +from huobi.utils import * + + +class ReqOrderListService: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol = self.params["symbol"] + account_id = self.params["account-id"] + order_states = self.params["states"] + client_req_id = self.params["client-req-id"] + + def subscription(connection): + connection.send(request_order_list_channel(symbol=symbol, account_id=account_id, states_str=order_states, client_req_id=client_req_id, more_key=self.params)) + + def parse(dict_data): + order_update_event = default_parse(dict_data, OrderListReq, OrderListItem) + + return order_update_event + + WebSocketReqClient(**kwargs).execute_subscribe_v1(subscription, + parse, + callback, + error_handler, + is_trade=True) + + + + + + + diff --git a/huobi/service/trade/sub_order_update_v2.py b/huobi/service/trade/sub_order_update_v2.py new file mode 100644 index 0000000..406e3fe --- /dev/null +++ b/huobi/service/trade/sub_order_update_v2.py @@ -0,0 +1,35 @@ +import time + + +from huobi.connection.subscribe_client import SubscribeClient +from huobi.model.trade import * +from huobi.utils import * + + +class SubOrderUpdateV2Service: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + + def subscription(connection): + for val in symbol_list: + connection.send(orders_update_channel(val)) + time.sleep(0.01) + + def parse(dict_data): + return default_parse(dict_data, OrderUpdateEvent, OrderUpdate) + + SubscribeClient(**kwargs).execute_subscribe_v2(subscription, + parse, + callback, + error_handler, + is_trade=True) + + + + + + + diff --git a/huobi/service/trade/sub_trade_clearing_v2.py b/huobi/service/trade/sub_trade_clearing_v2.py new file mode 100644 index 0000000..520fd54 --- /dev/null +++ b/huobi/service/trade/sub_trade_clearing_v2.py @@ -0,0 +1,35 @@ +import time + + +from huobi.connection.subscribe_client import SubscribeClient +from huobi.model.trade import * +from huobi.utils import * + + +class SubTradeClearingV2Service: + def __init__(self, params): + self.params = params + + def subscribe(self, callback, error_handler, **kwargs): + symbol_list = self.params["symbol_list"] + + def subscription(connection): + for symbol in symbol_list: + connection.send(trade_clearing_channel(symbol)) + time.sleep(0.01) + + def parse(dict_data): + return TradeClearingEvent.json_parse(dict_data) + + SubscribeClient(**kwargs).execute_subscribe_v2(subscription, + parse, + callback, + error_handler, + is_trade=True) + + + + + + + diff --git a/huobi/service/wallet/__init__.py b/huobi/service/wallet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/huobi/service/wallet/get_account_deposit_address.py b/huobi/service/wallet/get_account_deposit_address.py new file mode 100644 index 0000000..33b2bb8 --- /dev/null +++ b/huobi/service/wallet/get_account_deposit_address.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.wallet import * +from huobi.utils import * + + +class GetAccountDepositAddressService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/account/deposit/address" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, ChainDepositAddress) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/wallet/get_account_withdraw_address.py b/huobi/service/wallet/get_account_withdraw_address.py new file mode 100644 index 0000000..ade6e3a --- /dev/null +++ b/huobi/service/wallet/get_account_withdraw_address.py @@ -0,0 +1,25 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.wallet import * +from huobi.utils import * + + +class GetAccountWithdrawAddressService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/account/withdraw/address" + + def parse(dict_data): + data_list = dict_data.get("data", []) + return default_parse_list_dict(data_list, ChainWithdrawAddress) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/wallet/get_account_withdraw_quota.py b/huobi/service/wallet/get_account_withdraw_quota.py new file mode 100644 index 0000000..18d0b20 --- /dev/null +++ b/huobi/service/wallet/get_account_withdraw_quota.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.wallet import * +from huobi.utils import * + + +class GetAccountWithdrawQuotaService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/account/withdraw/quota" + + def parse(dict_data): + data = dict_data.get("data", {}) + chains = data.get("chains", []) + return default_parse_list_dict(chains, WithdrawQuota) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/wallet/get_deposit_withdraw.py b/huobi/service/wallet/get_deposit_withdraw.py new file mode 100644 index 0000000..a011af8 --- /dev/null +++ b/huobi/service/wallet/get_deposit_withdraw.py @@ -0,0 +1,30 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.wallet import * +from huobi.utils import * + + +class GetDepositWithdrawService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/query/deposit-withdraw" + + def parse(dict_data): + op_type = self.params["type"] + data_list = dict_data.get("data", []) + if op_type == DepositWithdraw.DEPOSIT: + return default_parse_list_dict(data_list, Deposit) + elif op_type == DepositWithdraw.WITHDRAW: + return default_parse_list_dict(data_list, Withdraw) + return [] + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/service/wallet/get_sub_user_deposit_address.py b/huobi/service/wallet/get_sub_user_deposit_address.py new file mode 100644 index 0000000..374b729 --- /dev/null +++ b/huobi/service/wallet/get_sub_user_deposit_address.py @@ -0,0 +1,22 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.wallet import * +from huobi.utils import * + + +class GetSubUserDepositAddressService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/deposit-address" + + def parse(dict_data): + json_data_list = dict_data.get("data", []) + return default_parse_list_dict(json_data_list, ChainDepositAddress, []) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + diff --git a/huobi/service/wallet/get_sub_user_deposit_history.py b/huobi/service/wallet/get_sub_user_deposit_history.py new file mode 100644 index 0000000..401160d --- /dev/null +++ b/huobi/service/wallet/get_sub_user_deposit_history.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.wallet import * +from huobi.utils import * + + +class GetSubUserDepositHistoryService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v2/sub-user/query-deposit" + + def parse(dict_data): + deposit_history = DepositHistory() + deposit_history.nextId = dict_data.get("nextId", 0) + json_data_list = dict_data.get("data", []) + deposit_history_item_list = default_parse_list_dict(json_data_list, DepositHistoryItem) + deposit_history.data = deposit_history_item_list + return deposit_history + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.GET_SIGN, channel, self.params, parse) + + + diff --git a/huobi/service/wallet/post_cancel_withdraw.py b/huobi/service/wallet/post_cancel_withdraw.py new file mode 100644 index 0000000..1141aca --- /dev/null +++ b/huobi/service/wallet/post_cancel_withdraw.py @@ -0,0 +1,28 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils import * +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostCancelWithdrawService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + withdraw_id_params = self.params["withdraw-id"] + def get_channel(): + path = "/v1/dw/withdraw-virtual/{}/cancel" + return path.format(withdraw_id_params) + + def parse(dict_data): + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, get_channel(), self.params, parse) + + + + + + diff --git a/huobi/service/wallet/post_create_withdraw.py b/huobi/service/wallet/post_create_withdraw.py new file mode 100644 index 0000000..7fb66cb --- /dev/null +++ b/huobi/service/wallet/post_create_withdraw.py @@ -0,0 +1,26 @@ +from huobi.connection.restapi_sync_client import RestApiSyncClient +from huobi.constant import * +from huobi.model.trade import * +from huobi.utils import * +from huobi.utils.json_parser import default_parse_data_as_long + + +class PostCreateWithdrawService: + + def __init__(self, params): + self.params = params + + def request(self, **kwargs): + channel = "/v1/dw/withdraw/api/create" + + def parse(dict_data): + # return dict_data.get("data", 0) + return default_parse_data_as_long(dict_data, None) + + return RestApiSyncClient(**kwargs).request_process(HttpMethod.POST_SIGN, channel, self.params, parse) + + + + + + diff --git a/huobi/utils/__init__.py b/huobi/utils/__init__.py new file mode 100644 index 0000000..7bf0054 --- /dev/null +++ b/huobi/utils/__init__.py @@ -0,0 +1,11 @@ +from huobi.utils.print_mix_object import PrintMix, PrintBasic, PrintList +from huobi.utils.channels import * +from huobi.utils.channels_request import * +from huobi.utils.json_parser import default_parse, default_parse_list_dict, default_parse_fill_directly +from huobi.utils.api_signature import create_signature, utc_now +from huobi.utils.api_signature_v2 import create_signature_v2, utc_now +from huobi.utils.url_params_builder import UrlParamsBuilder +from huobi.utils.time_service import get_current_timestamp, convert_cst_in_millisecond_to_utc, convert_cst_in_second_to_utc +from huobi.utils.input_checker import check_symbol, check_symbol_list, check_currency, check_range, check_should_none, \ + check_should_not_none, check_list, greater_or_equal, format_date +from huobi.utils.log_info import LogLevel, LogInfo diff --git a/huobi/utils/api_signature.py b/huobi/utils/api_signature.py new file mode 100644 index 0000000..bb3e22c --- /dev/null +++ b/huobi/utils/api_signature.py @@ -0,0 +1,36 @@ +import base64 +import hashlib +import hmac +import datetime +from urllib import parse +import urllib.parse +from huobi.exception.huobi_api_exception import HuobiApiException + + +def create_signature(api_key, secret_key, method, url, builder): + if api_key is None or secret_key is None or api_key == "" or secret_key == "": + raise HuobiApiException(HuobiApiException.KEY_MISSING, "API key and secret key are required") + + timestamp = utc_now() + builder.put_url("AccessKeyId", api_key) + builder.put_url("SignatureVersion", "2") + builder.put_url("SignatureMethod", "HmacSHA256") + builder.put_url("Timestamp", timestamp) + + host = urllib.parse.urlparse(url).hostname + path = urllib.parse.urlparse(url).path + + # 对参数进行排序: + keys = sorted(builder.param_map.keys()) + # 加入& + qs0 = '&'.join(['%s=%s' % (key, parse.quote(builder.param_map[key], safe='')) for key in keys]) + # 请求方法,域名,路径,参数 后加入`\n` + payload0 = '%s\n%s\n%s\n%s' % (method, host, path, qs0) + dig = hmac.new(secret_key.encode('utf-8'), msg=payload0.encode('utf-8'), digestmod=hashlib.sha256).digest() + # 进行base64编码 + s = base64.b64encode(dig).decode() + builder.put_url("Signature", s) + + +def utc_now(): + return datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') diff --git a/huobi/utils/api_signature_v2.py b/huobi/utils/api_signature_v2.py new file mode 100644 index 0000000..c5e1bbd --- /dev/null +++ b/huobi/utils/api_signature_v2.py @@ -0,0 +1,60 @@ +import base64 +import hashlib +import hmac +import datetime +from urllib import parse +import urllib.parse +from huobi.exception.huobi_api_exception import HuobiApiException + + +def create_signature_v2(api_key, secret_key, method, url, builder): + if api_key is None or secret_key is None or api_key == "" or secret_key == "": + raise HuobiApiException(HuobiApiException.KEY_MISSING, "API key and secret key are required") + + timestamp = utc_now() + builder.put_url("accessKey", api_key) + builder.put_url("signatureVersion", "2.1") + builder.put_url("signatureMethod", "HmacSHA256") + builder.put_url("timestamp", timestamp) + + host = urllib.parse.urlparse(url).hostname + path = urllib.parse.urlparse(url).path + + # 对参数进行排序: + keys = sorted(builder.param_map.keys()) + # 加入& + qs0 = '&'.join(['%s=%s' % (key, parse.quote(builder.param_map[key], safe='')) for key in keys]) + # 请求方法,域名,路径,参数 后加入`\n` + payload0 = '%s\n%s\n%s\n%s' % (method, host, path, qs0) + dig = hmac.new(secret_key.encode('utf-8'), msg=payload0.encode('utf-8'), digestmod=hashlib.sha256).digest() + # 进行base64编码 + s = base64.b64encode(dig).decode() + builder.put_url("signature", s) + builder.put_url("authType", "api") + + params = { + "accessKey": api_key, + "signatureVersion": "2.1", + "signatureMethod": "HmacSHA256", + "timestamp": timestamp, + "signature":s, + "authType":"api" + } + + builder.put_url("action", "req") + builder.put_url("ch", "auth") + builder.put_url("params", params) + + """ + # for test + ret_maps = { + "action": "req", + "ch": "auth", + "params" : params + } + + return json.dumps(ret_maps) + """ + +def utc_now(): + return datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') diff --git a/huobi/utils/channel_parser.py b/huobi/utils/channel_parser.py new file mode 100644 index 0000000..3e29e70 --- /dev/null +++ b/huobi/utils/channel_parser.py @@ -0,0 +1,5 @@ +class ChannelParser: + def __init__(self, input): + fields = input.split(".") + if len(fields) >= 2: + self.symbol = fields[1] diff --git a/huobi/utils/channels.py b/huobi/utils/channels.py new file mode 100644 index 0000000..96dfac1 --- /dev/null +++ b/huobi/utils/channels.py @@ -0,0 +1,83 @@ +import json +from huobi.utils.time_service import get_current_timestamp +from huobi.constant import DepthStep + + +def kline_channel(symbol, interval): + channel = dict() + channel["sub"] = "market." + symbol + ".kline." + interval + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def trade_detail_channel(symbol): + channel = dict() + channel["sub"] = "market." + symbol + ".trade.detail" + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def price_depth_channel(symbol, step_type=DepthStep.STEP0): + channel = dict() + channel["sub"] = "market." + symbol + ".depth." + step_type + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def price_depth_bbo_channel(symbol): + channel = dict() + channel["sub"] = "market." + symbol + ".bbo" + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def orders_update_channel(symbol): + channel = dict() + channel["action"] = "sub" + channel["ch"] = "orders#{symbol}".format(symbol=symbol) + return json.dumps(channel) + + +def market_detail_channel(symbol): + channel = dict() + channel["sub"] = "market." + symbol + ".detail" + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def accounts_update_channel(mode=0): + channel = dict() + channel["action"] = "sub" + if mode is None: + channel["ch"] = "accounts.update" + else: + channel["ch"] = "accounts.update#{mode}".format(mode=mode) + return json.dumps(channel) + + +def mbp_increase_channel(symbol, levels): + channel = dict() + channel["sub"] = "market.{symbol}.mbp.{levels}".format(symbol=symbol, levels=levels) + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def mbp_full_channel(symbol, levels): + channel = dict() + channel["sub"] = "market.{symbol}.mbp.refresh.{levels}".format(symbol=symbol, levels=levels) + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def request_mbp_channel(symbol, levels): + channel = dict() + channel["req"] = "market.{symbol}.mbp.{levels}".format(symbol=symbol, levels=levels) + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def trade_clearing_channel(symbol="*"): + channel = dict() + channel["action"] = "sub" + channel["ch"] = "trade.clearing#" + symbol + return json.dumps(channel) diff --git a/huobi/utils/channels_request.py b/huobi/utils/channels_request.py new file mode 100644 index 0000000..a9b14b2 --- /dev/null +++ b/huobi/utils/channels_request.py @@ -0,0 +1,79 @@ +import json +from huobi.utils.time_service import get_current_timestamp + +def dict_add_new(old_dict, new_dict): + if old_dict is None: + old_dict = {} + + if new_dict and len(new_dict): + for key_val, val in new_dict.items(): + if val: + exist_val = old_dict.get(key_val, None) + if exist_val and len(str(exist_val)): + pass + else: + old_dict[key_val] = str(val) + + return old_dict + +def request_kline_channel(symbol, interval, from_ts_second = None, to_ts_second = None): + channel = dict() + channel["req"] = "market." + symbol + ".kline." + interval + channel["id"] = str(get_current_timestamp()) + if from_ts_second: + channel["from"] = int(from_ts_second) + if to_ts_second: + channel["to"] = int(to_ts_second) + return json.dumps(channel) + + +def request_trade_detail_channel(symbol): + channel = dict() + channel["req"] = "market." + symbol + ".trade.detail" + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + + +def request_price_depth_channel(symbol, step_type = "step0"): + channel = dict() + channel["req"] = "market." + symbol + ".depth." + step_type + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + +def request_market_detail_channel(symbol): + channel = dict() + channel["req"] = "market." + symbol + ".detail" + channel["id"] = str(get_current_timestamp()) + return json.dumps(channel) + +def request_account_list_channel(client_req_id = None): + channel = dict() + channel["op"] = "req" + channel["topic"] = "accounts.list" + channel["cid"] = str(client_req_id) if client_req_id else str(get_current_timestamp()) + return json.dumps(channel) + +def request_order_list_channel(symbol, account_id, states_str= None, client_req_id = None, more_key={}): + channel = dict() + try: + channel["op"] = "req" + channel["account-id"] = account_id + channel["topic"] = "orders.list" + channel["symbol"] = symbol + if states_str and len(states_str): + channel["states"] = str(states_str) + channel["cid"] = str(client_req_id) if client_req_id else str(get_current_timestamp()) + channel = dict_add_new(channel, more_key) + + except Exception as e: + print(e) + return json.dumps(channel) + +def request_order_detail_channel(order_id, client_req_id = None): + channel = dict() + channel["op"] = "req" + channel["topic"] = "orders.detail" + channel["order-id"] = str(order_id) + channel["cid"] = str(client_req_id) if client_req_id else str(get_current_timestamp()) + return json.dumps(channel) + diff --git a/huobi/utils/etf_result.py b/huobi/utils/etf_result.py new file mode 100644 index 0000000..fdf693c --- /dev/null +++ b/huobi/utils/etf_result.py @@ -0,0 +1,29 @@ +def etf_result_check(code): + code = int(code) + if code == 200: + return "" + elif code == 10400: + return "Invalid ETF name" + elif code == 13403: + return "Insufficient asset to create ETF" + elif code == 13404: + return "Create and redemption disabled due to system setup" + elif code == 13405: + return "Create and redemption disabled due to configuration issue" + elif code == 13406: + return "Invalid API call" + elif code == 13410: + return "API authentication fails" + elif code == 13500: + return "System error" + elif code == 13601: + return "Create and redemption disabled during rebalance" + elif code == 13603: + return "Create and redemption disabled due to other reason" + elif code == 13604: + return "Create suspended" + elif code == 13605: + return "Redemption suspended" + elif code == 13606: + return "Amount incorrect. For the cases when creation amount or redemption amount is not in the range of min/max amount" + return "" diff --git a/huobi/utils/input_checker.py b/huobi/utils/input_checker.py new file mode 100644 index 0000000..2de456c --- /dev/null +++ b/huobi/utils/input_checker.py @@ -0,0 +1,90 @@ +import re +import time +from huobi.exception.huobi_api_exception import HuobiApiException +from huobi.constant.definition import * + +reg_ex = "[ _`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]|\n|\t" + + +def check_symbol(symbol): + if not isinstance(symbol, str): + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] symbol must be string") + if re.match(reg_ex, symbol): + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] " + symbol + " is invalid symbol") + + +def check_time_in_force(time_in_force, order_type): + if time_in_force is None: + return + + if order_type in [OrderType.BUY_MARKET, OrderType.SELL_MARKET] \ + and time_in_force in [TimeInForceType.GTC, TimeInForceType.BOC, TimeInForceType.FOK]: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] timeInForce not supported for market order") + + +def check_symbol_list(symbols): + if not isinstance(symbols, list): + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] symbols in subscription is not a list") + for symbol in symbols: + check_symbol(symbol) + + +def check_currency(currency): + if not isinstance(currency, str): + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] currency must be string") + if re.match(reg_ex, currency) is not None: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] " + currency + " is invalid currency") + + +def check_range(value, min_value, max_value, name): + if value is None: + return + if min_value > value or value > max_value: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, + "[Input] " + name + " is out of bound. " + str(value) + " is not in [" + str( + min_value) + "," + str(max_value) + "]") + + +def check_should_not_none(value, name): + if value is None: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] " + name + " should not be null") + + +def check_should_none(value, name): + if value is not None: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] " + name + " should be null") + + +def check_in_list(value, list_configed, name): + if (value is not None) and (value not in list_configed): + raise HuobiApiException(HuobiApiException.INPUT_ERROR, + "[Input] " + name + " should be one in " + (",".join(list_configed))) + + +def check_list(list_value, min_value, max_value, name): + if list_value is None: + return + if len(list_value) > max_value: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, + "[Input] " + name + " is out of bound, the max size is " + str(max_value)) + if len(list_value) < min_value: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, + "[Input] " + name + " should contain " + str(min_value) + " item(s) at least") + + +def greater_or_equal(value, base, name): + if value is not None and value < base: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, + "[Input] " + name + " should be greater than " + base) + + +def format_date(value, name): + if value is None: + return None + if not isinstance(value, str): + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] " + name + " must be string") + try: + new_time = time.strptime(value, "%Y-%m-%d") + return time.strftime("%Y-%m-%d", new_time) + except: + raise HuobiApiException(HuobiApiException.INPUT_ERROR, "[Input] " + name + " is not invalid date format") diff --git a/huobi/utils/json_parser.py b/huobi/utils/json_parser.py new file mode 100644 index 0000000..61c38fb --- /dev/null +++ b/huobi/utils/json_parser.py @@ -0,0 +1,122 @@ +from huobi.utils.print_mix_object import * + + +def key_trans(key_origin): + if key_origin and len(key_origin) > 1: + return key_origin.replace("-", "_") + else: + return "" + + +def fill_obj(dict_data, class_name=object): + obj = class_name() + for ks, vs in dict_data.items(): + obj_key = key_trans(ks) + # print("===== fill_obj =====", ks, obj_key, str(vs)) + if hasattr(obj, obj_key): + setattr(obj, obj_key, vs) + continue + return obj + + +def fill_obj_list(list_data, class_name): + if (TypeCheck.is_list(list_data)): + inner_obj_list = list() + for idx, row in enumerate(list_data): + inner_obj = fill_obj(row, class_name) + inner_obj_list.append(inner_obj) + return inner_obj_list + + return list() + + +def default_parse(dict_data, outer_class_name=object, inner_class_name=object): + from huobi.utils.print_mix_object import TypeCheck + rsp_obj = outer_class_name() + + for outer_key, outer_value in dict_data.items(): + obj_key = key_trans(outer_key) + # print("===", outer_key, obj_key, str(outer_value)) + # PrintBasic.print_basic_bool(hasattr(rsp_obj, obj_key)) + if hasattr(rsp_obj, obj_key): + new_value = outer_value + # print("==========", type(outer_value), outer_value) + if (TypeCheck.is_list(outer_value)): + new_value = fill_obj_list(outer_value, inner_class_name) + elif (TypeCheck.is_dict(outer_value)): + new_value = fill_obj(outer_value, inner_class_name) + + setattr(rsp_obj, obj_key, new_value) + continue + + return rsp_obj + + +def default_parse_data_as_long(ret_original_json, key_name=None, default_value=0): + if ret_original_json: + # from data get value by key_name + if key_name and len(key_name): + data_json = ret_original_json.get("data", {}) + ret_val = data_json.get(key_name, default_value) + else: + # get data value + ret_val = ret_original_json.get("data", 0) + return None if ret_val is None else int(ret_val) + else: + return default_value + + +def default_parse_list_dict(inner_data, inner_class_name=object, default_value=None): + from huobi.utils.print_mix_object import TypeCheck + + new_value = default_value + if inner_data and len(inner_data): + if (TypeCheck.is_list(inner_data)): + new_value = fill_obj_list(inner_data, inner_class_name) + elif (TypeCheck.is_dict(inner_data)): + new_value = fill_obj(inner_data, inner_class_name) + else: + new_value = default_value + + return new_value + + +def default_parse_fill_directly(dict_data, outer_class_name=object): + rsp_obj = outer_class_name() + + for outer_key, outer_value in dict_data.items(): + obj_key = key_trans(outer_key) + if hasattr(rsp_obj, obj_key): + new_value = outer_value + setattr(rsp_obj, obj_key, new_value) + continue + + return rsp_obj + + +if __name__ == "__main__": + # json_str = """{"id":1571037900,"open":8306.850000000000000000,"close":8307.990000000000000000,"low":8305.800000000000000000,"high":8308.000000000000000000,"amount":4.314954363225502510,"vol":35845.853207199999993966820000000000000000,"count":45}""" + # obj = parse(json_str, outer_class_name=Candlestick, inner_class_name=None) + + # obj.print_object() + + # json_str = """{"status":"ok","ch":"market.btcusdt.kline.1min","ts":1571038189274,"data":[{"id":1571038140,"open":8304.130000000000000000,"close":8305.000000000000000000,"low":8300.010000000000000000,"high":8305.000000000000000000,"amount":41.791380418639796061,"vol":347038.873910589999990613890000000000000000,"count":165},{"id":1571038080,"open":8306.060000000000000000,"close":8304.130000000000000000,"low":8304.130000000000000000,"high":8306.060000000000000000,"amount":3.440305012281757977,"vol":28571.704804969999998985000000000000000000,"count":70},{"id":1571038020,"open":8303.890000000000000000,"close":8305.990000000000000000,"low":8303.890000000000000000,"high":8307.000000000000000000,"amount":6.164746957847080072,"vol":51200.094613737306439860000000000000000000,"count":89},{"id":1571037960,"open":8308.000000000000000000,"close":8303.660000000000000000,"low":8303.610000000000000000,"high":8308.430000000000000000,"amount":9.449641840557003640,"vol":78488.394246429999983963200000000000000000,"count":128},{"id":1571037900,"open":8306.850000000000000000,"close":8307.990000000000000000,"low":8305.800000000000000000,"high":8308.000000000000000000,"amount":4.314954363225502510,"vol":35845.853207199999993966820000000000000000,"count":45},{"id":1571037840,"open":8305.290000000000000000,"close":8306.820000000000000000,"low":8305.040000000000000000,"high":8307.300000000000000000,"amount":3.661411574656800047,"vol":30412.286333819999984471300000000000000000,"count":68},{"id":1571037780,"open":8306.500000000000000000,"close":8305.490000000000000000,"low":8305.100000000000000000,"high":8307.690000000000000000,"amount":2.958112866447550895,"vol":24569.573928609999984901150000000000000000,"count":59},{"id":1571037720,"open":8306.300000000000000000,"close":8307.560000000000000000,"low":8305.440000000000000000,"high":8309.000000000000000000,"amount":8.506926000000000000,"vol":70665.848347860000000000000000000000000000,"count":90},{"id":1571037660,"open":8306.920000000000000000,"close":8306.300000000000000000,"low":8306.200000000000000000,"high":8307.000000000000000000,"amount":5.084311000000000000,"vol":42233.572201310000000000000000000000000000,"count":57},{"id":1571037600,"open":8307.200000000000000000,"close":8306.920000000000000000,"low":8306.920000000000000000,"high":8309.160000000000000000,"amount":4.540090141728746275,"vol":37715.585646199999989163880000000000000000,"count":54}]}""" + # obj_event = parse(json_str, outer_class_name=CandlestickRsp, inner_class_name=Candlestick) + # obj_event.print_object() + + # json_str = """{"status":"ok","ch":"market.btcusdt.kline.1min","ts":1571038189274,"data":{"id":1571038140,"open":8304.130000000000000000,"close":8305.000000000000000000,"low":8300.010000000000000000,"high":8305.000000000000000000,"amount":41.791380418639796061,"vol":347038.873910589999990613890000000000000000,"count":165}}""" + # obj_event = parse(json_str, outer_class_name=CandlestickEvent, inner_class_name=Candlestick) + # obj_event.print_object() + + # json_str = """{"status":"ok","ch":"market.btcusdt.kline.1min","ts":1571038189274,"data":{"id":1571038140,"open":8304.130000000000000000,"close":8305.000000000000000000,"low":8300.010000000000000000,"high":8305.000000000000000000,"amount":41.791380418639796061,"vol":347038.873910589999990613890000000000000000,"count":165}}""" + # ret = default_parse_restful(json_str, inner_class_name=Candlestick, default_value=None) + # ret.print_object() + + # json_str = """{"status":"ok","ch":"market.btcusdt.kline.1min","ts":1571038189274,"data":[{"id":1571038140,"open":8304.130000000000000000,"close":8305.000000000000000000,"low":8300.010000000000000000,"high":8305.000000000000000000,"amount":41.791380418639796061,"vol":347038.873910589999990613890000000000000000,"count":165},{"id":1571038080,"open":8306.060000000000000000,"close":8304.130000000000000000,"low":8304.130000000000000000,"high":8306.060000000000000000,"amount":3.440305012281757977,"vol":28571.704804969999998985000000000000000000,"count":70},{"id":1571038020,"open":8303.890000000000000000,"close":8305.990000000000000000,"low":8303.890000000000000000,"high":8307.000000000000000000,"amount":6.164746957847080072,"vol":51200.094613737306439860000000000000000000,"count":89},{"id":1571037960,"open":8308.000000000000000000,"close":8303.660000000000000000,"low":8303.610000000000000000,"high":8308.430000000000000000,"amount":9.449641840557003640,"vol":78488.394246429999983963200000000000000000,"count":128},{"id":1571037900,"open":8306.850000000000000000,"close":8307.990000000000000000,"low":8305.800000000000000000,"high":8308.000000000000000000,"amount":4.314954363225502510,"vol":35845.853207199999993966820000000000000000,"count":45},{"id":1571037840,"open":8305.290000000000000000,"close":8306.820000000000000000,"low":8305.040000000000000000,"high":8307.300000000000000000,"amount":3.661411574656800047,"vol":30412.286333819999984471300000000000000000,"count":68},{"id":1571037780,"open":8306.500000000000000000,"close":8305.490000000000000000,"low":8305.100000000000000000,"high":8307.690000000000000000,"amount":2.958112866447550895,"vol":24569.573928609999984901150000000000000000,"count":59},{"id":1571037720,"open":8306.300000000000000000,"close":8307.560000000000000000,"low":8305.440000000000000000,"high":8309.000000000000000000,"amount":8.506926000000000000,"vol":70665.848347860000000000000000000000000000,"count":90},{"id":1571037660,"open":8306.920000000000000000,"close":8306.300000000000000000,"low":8306.200000000000000000,"high":8307.000000000000000000,"amount":5.084311000000000000,"vol":42233.572201310000000000000000000000000000,"count":57},{"id":1571037600,"open":8307.200000000000000000,"close":8306.920000000000000000,"low":8306.920000000000000000,"high":8309.160000000000000000,"amount":4.540090141728746275,"vol":37715.585646199999989163880000000000000000,"count":54}]}""" + # ret = default_parse_restful(json_str, inner_class_name=Candlestick, default_value=None) + # if ret and len(ret): + # for row in ret: + # row.print_object() + # print("========") + + pass diff --git a/huobi/utils/log_info.py b/huobi/utils/log_info.py new file mode 100644 index 0000000..41da0d5 --- /dev/null +++ b/huobi/utils/log_info.py @@ -0,0 +1,31 @@ +class LogLevel: + DEBUG = "debug" + INFO = "info" + WARNINGS = "warning" + ERROR = "error" + FATAL = "fatal" + + +class LogInfo: + @staticmethod + def output(message, log_level=LogLevel.DEBUG): + # if (message and len(message)): + # if log_level == LogLevel.DEBUG: + # logging.debug(message) + # elif log_level == LogLevel.INFO: + # logging.info(message) + # elif log_level == LogLevel.WARNINGS: + # logging.warnings(message) + # elif log_level == LogLevel.ERROR: + # logging.error(message) + # elif log_level == LogLevel.FATAL: + # logging.fatal(message) + + print(message) + + @staticmethod + def output_list(data_list, log_level=LogLevel.DEBUG): + if data_list and len(data_list): + for obj in data_list: + obj.print_object() + print() diff --git a/huobi/utils/print_mix_object.py b/huobi/utils/print_mix_object.py new file mode 100644 index 0000000..894dc43 --- /dev/null +++ b/huobi/utils/print_mix_object.py @@ -0,0 +1,232 @@ +import sys +import time + +BASIC_DATA_TYPE = (int, str, float) +BASIC_DATA_TYPE_BOOL = (bool) + +TYPE_BASIC = "type_basic" +TYPE_BOOL = "type_bool" +TYPE_OBJECT = "type_object" +TYPE_LIST = "type_list" +TYPE_DICT = "type_dict" +TYPE_UNDEFINED = "type_undefined" + + +class TypeCheck: + @staticmethod + def is_list(obj): + return type(obj) == list and isinstance(obj, list) + + @staticmethod + def is_dict(obj): + return type(obj) == dict and isinstance(obj, dict) + + @staticmethod + def is_object(obj): + return isinstance(obj, object) + + @staticmethod + def is_basic(obj): + return isinstance(obj, BASIC_DATA_TYPE) + + @staticmethod + def is_bool(obj): + return isinstance(obj, bool) + + @staticmethod + def get_obj_type(obj): + if TypeCheck.is_basic(obj): + return TYPE_BASIC + elif TypeCheck.is_bool(obj): + return TYPE_BOOL + elif TypeCheck.is_list(obj): + return TYPE_LIST + elif TypeCheck.is_dict(obj): + return TYPE_DICT + elif TypeCheck.is_object(obj): + return TYPE_OBJECT + else: + return TYPE_UNDEFINED + + +class PrintBasic: + @staticmethod + def print_basic(data, name=None): + if name and len(name): + print(str(name) + " : " + str(data)) + else: + print(str(data)) + + @staticmethod + def print_basic_bool(data, name=None): + bool_desc = "True" + if not data: + bool_desc = "False" + + if name and len(name): + print(str(name) + " : " + str(bool_desc)) + else: + print(str(bool_desc)) + + @staticmethod + def print_obj(obj): + if not obj: + return -1 + + members = [attr for attr in dir(obj) if not callable(attr) and not attr.startswith("__")] + for member_def in members: + val_str = str(getattr(obj, member_def)) + print(member_def + ":" + val_str) + return 0 + + +class PrintList: + @staticmethod + def print_list_data(obj): + if not obj: + print("object is None") + return -1 + + if TypeCheck.get_obj_type(obj) == TYPE_LIST: + for idx, row in enumerate(obj): + PrintBasic.print_basic(row) + else: + return -2 + + return 0 + + @staticmethod + def print_origin_object(obj): + if not obj: + print("object is None") + return -1 + obj_type = TypeCheck.get_obj_type(obj) + + if obj_type == TYPE_BASIC: + PrintBasic.print_basic(obj) + elif obj_type == TYPE_BOOL: + PrintBasic.print_basic_bool(obj) + elif obj_type == TYPE_OBJECT: + PrintBasic.print_obj(obj) + else: + return 1 + + return 0 + + @staticmethod + def print_object_list(obj_list): + if not obj_list: + return -1 + + obj_type = TypeCheck.get_obj_type(obj_list) + if obj_type != TYPE_LIST: + return -2 + + print ("data count : ", (len(obj_list))) + print ("\n") + for idx, row in enumerate(obj_list): + print("data number " + (str(idx)) + " :") + PrintList.print_origin_object(row) + print("\n") + print("\n\n") + + return 0 + + @staticmethod + def print_object_dict(obj_dict): + if not obj_dict: + return -1 + + obj_type = TypeCheck.get_obj_type(obj_dict) + if obj_type != TYPE_DICT: + return -2 + + print ("data count : ", (len(obj_dict))) + print ("\n") + for key, row in obj_dict.items(): + PrintBasic.print_basic(str(key) + " :") + PrintList.print_origin_object(row) + print("\n") + print("\n\n") + + return 0 + + +class PrintMix: + @staticmethod + def print_data(data): + if not data: + print (sys._getframe().f_code.co_name + " none data") + return -1 + + obj_type = TypeCheck.get_obj_type(data) + + if obj_type == TYPE_BASIC: + PrintBasic.print_basic(data) + elif obj_type == TYPE_BOOL: + PrintBasic.print_basic_bool(data) + elif obj_type == TYPE_LIST: + PrintList.print_object_list(data) + elif obj_type == TYPE_DICT: + PrintList.print_object_dict(data) + elif obj_type == TYPE_OBJECT: + PrintList.print_origin_object(data) + else: + print (sys._getframe().f_code.co_name + " enter unknown") + return -2 + + return 0 + + + +class PrintDate: + @staticmethod + def timestamp_to_date(ts_minsecond): + try: + ts_minsecond = int(ts_minsecond) + time_local = time.localtime(int(ts_minsecond / 1000)) + dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local) + print("ping " + str(ts_minsecond) + ":" + dt) + except Exception as e: + print(e) + +""" +if __name__ == "__main__": + ping_ts = 1569319465421 + PrintDate.timestamp_to_date(ping_ts) + PrintDate.timestamp_to_date(int(ping_ts), ("ping " + str(ping_ts))) +""" + + +if __name__ == "__main__": + """ + from huobi.model.symbol import Symbol + + symbol_1 = Symbol() + symbol_1.amount_precision = 10009 + symbol_1.symbol = "btcusdt" + + symbol_2 = Symbol() + symbol_2.amount_precision = 28 + symbol_2.symbol = "htusdt" + + symbol_3 = Symbol() + symbol_3.amount_precision = 26 + symbol_3.symbol = "eosusdt" + + symbol_list = [symbol_1, symbol_2, symbol_3] + symbol_dict = {"one": symbol_1, "two": symbol_2, "three": symbol_3} + PrintMix.print_data(symbol_list) + PrintMix.print_data(symbol_dict) + + print(type(symbol_list) == list) + print(type(symbol_dict) == dict) + print(type(symbol_list) == object) + print(isinstance(symbol_list, list)) + print(isinstance(symbol_list, object)) + print(isinstance(symbol_dict, dict)) + print(isinstance(symbol_dict, object)) + """ + + a=['s', 'h', 'i'] + PrintList.print_list_data(a) diff --git a/huobi/utils/time_service.py b/huobi/utils/time_service.py new file mode 100644 index 0000000..d2af8e9 --- /dev/null +++ b/huobi/utils/time_service.py @@ -0,0 +1,22 @@ +import time + + +def get_current_timestamp(): + return int(round(time.time() * 1000)) + + +def convert_cst_in_second_to_utc(time_in_second): + time_in_second = int(time_in_second) + if time_in_second > 946656000 and time_in_second < 946656000000: + return (time_in_second - 8 * 60 * 60) * 1000 + else: + return 0 + + +def convert_cst_in_millisecond_to_utc(time_in_ms): + time_in_ms = int(time_in_ms) + if time_in_ms > 946656000000: + return time_in_ms - 8 * 60 * 60 * 1000 + else: + return 0 + diff --git a/huobi/utils/url_params_builder.py b/huobi/utils/url_params_builder.py new file mode 100644 index 0000000..fd689cc --- /dev/null +++ b/huobi/utils/url_params_builder.py @@ -0,0 +1,33 @@ +import json +import urllib.parse + + +class UrlParamsBuilder(object): + + def __init__(self): + self.param_map = dict() + self.post_map = dict() + self.post_list = list() + + def put_url(self, name, value): + if value is not None: + if isinstance(value, (list, dict)): + self.param_map[name] = value + else: + self.param_map[name] = str(value) + + def put_post(self, name, value): + if value is not None: + if isinstance(value, (list, dict)): + self.post_map[name] = value + else: + self.post_map[name] = str(value) + + def build_url(self): + if len(self.param_map) == 0: + return "" + encoded_param = urllib.parse.urlencode(self.param_map) + return "?" + encoded_param + + def build_url_to_json(self): + return json.dumps(self.param_map) diff --git a/huobi_alert.py b/huobi_alert.py new file mode 100644 index 0000000..08e9982 --- /dev/null +++ b/huobi_alert.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +from utils import utility +from utils import config + +from huobi.client.account import AccountClient +from huobi.client.trade import TradeClient +from huobi.constant import * +from huobi.utils import * + + + +if __name__ == '__main__': + + config.loads('./conf_huobi/config.json') + order_list_submitted = [] + + trade_client = TradeClient(api_key=config.api_key, secret_key=config.api_secret) + account_client = AccountClient(api_key=config.api_key, secret_key=config.api_secret) + + account_spot = account_client.get_account_by_type_and_symbol(account_type=AccountType.SPOT, symbol=None) + account_id_test = account_spot.id + direct_tmp = QueryDirection.NEXT + LogInfo.output("==============test case 1 for {direct}===============".format(direct=direct_tmp)) + for symbol in config.symbol_spot: + print(symbol) + list_obj = trade_client.get_open_orders(symbol=symbol, account_id=account_id_test, direct=direct_tmp) + for order in list_obj: + if order.state == 'submitted': + order_list_submitted.append(str(order.id)) + print(order.id) + print(order) + #LogInfo.output_list(list_obj) + + + # direct_tmp = QueryDirection.PREV + # LogInfo.output("==============test case 2 for {direct}===============".format(direct=direct_tmp)) + # list_obj = trade_client.get_open_orders(symbol=symbol, account_id=account_id_test, direct=direct_tmp) + # LogInfo.output_list(list_obj) + + + + + # #启动新实例 发个通知 + # utility.alert(f"{utility.cn_time()} 火币订单监控启动") + + # while True: + # try: + # time.sleep(20) + # + # except Exception as error: + # utility.alert(error) + # time.sleep(5) + diff --git a/k8s.md b/k8s.md new file mode 100644 index 0000000..f6ecb4d --- /dev/null +++ b/k8s.md @@ -0,0 +1,57 @@ +![](https://s3.sre.ink/img/2021/01/16/ef53751ba6d1886b15a0c06bcb9a9ca2.png) +# configmap +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: binance-config-qy-xrp + namespace: sre +data: + config.json: | + { + "platform": "binance_spot", + "symbol": "XRPUSDT", + "api_key": "your api_ky 1111111111111111111111111111111111111", + "api_secret": "your api_secret 222222222222222222222222222222", + "gap_percent": 0.005, + "quantity": 40, + "min_price": 0.00001, + "min_qty": 40, + "max_orders": 5, + "proxy_host": "", + "proxy_port": 0 + } +``` + +# k8s Deployment api-wechat + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: binance-trader-xrp + namespace: sre +spec: + replicas: 1 + selector: + matchLabels: + binance: trader + template: + metadata: + labels: + binance: trader + spec: + imagePullSecrets: + - name: wzaqqqaliyunsecret + containers: + - name: binance-trader + image: mmmmmmmmmmmmmmmm/binance_grid_trader:6 + imagePullPolicy: IfNotPresent + volumeMounts: + - name: binance-config + mountPath: /app/conf/ + volumes: + - name: binance-config + configMap: + name: binance-config-qy-xrp +``` diff --git a/main.dev.py b/main.dev.py new file mode 100644 index 0000000..fe6658f --- /dev/null +++ b/main.dev.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +import time +import logging +from binance.binance_trader import BinanceTrader +from binance.utils import config + +format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +logging.basicConfig(level=logging.INFO, format=format, filename='grid_trader_log.txt') +logger = logging.getLogger('binance') + + +if __name__ == '__main__': + + + config.loads('./conf/config.json') + trader = BinanceTrader() + + + + while True: + try: + print(trader.get_avg_price_5min()) + print(trader.get_price_change_percent_24h()) + #trader.grid_trader() + time.sleep(200) + + except Exception as error: + print(f"BA catch error: {error}") + time.sleep(5) + diff --git a/main.py b/main.py new file mode 100644 index 0000000..ff2ddd2 --- /dev/null +++ b/main.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +import time +import logging +from binance.binance_trader import BinanceTrader +from binance.binance_future_trader import BinanceFutureTrader +from utils import utility +from utils import config +format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +logging.basicConfig(level=logging.INFO, format=format, filename='grid_trader_log.txt') +logger = logging.getLogger('binance') + + +if __name__ == '__main__': + + + config.loads('./conf/config.json') + sleep_time = 5 + if config.sleep_time: + sleep_time = config.sleep_time + + if config.platform == 'binance_spot': + trader = BinanceTrader() + else: + trader = BinanceFutureTrader() + + #启动新实例, 取消客户端创建的订单 + print("启动新实例, 同步本客户端创建的订单:") + trader.sync_open_orders() + + #启动新实例 发个通知 + utility.alert(f"{utility.cn_time()} 交易对{config.symbol}启动") + + + + + + while True: + try: + trader.grid_trader() + time.sleep(sleep_time) + + except Exception as error: + utility.alert(f"交易{config.symbol}: {error}") + time.sleep(sleep_time) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..48f6028 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +requests==2.22.0 +dingtalkchatbot +pytz +APScheduler==3.6.3 +aiohttp +websocket_client==0.57.0 \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..0bae355 --- /dev/null +++ b/test.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +import time +from binance.binance_trader import BinanceTrader +from binance.binance_future_trader import BinanceFutureTrader +from utils import config + + + +if __name__ == '__main__': + + + config.loads('./conf/config.json') + + trader_spot = BinanceTrader() + trader_future = BinanceFutureTrader() + + + + + while True: + try: + price_spot = trader_spot.get_latest_price() + price_future = trader_future.get_latest_price() + print("diff:") + print(f"price_spot:{price_spot} price_future:{price_future}") + print(f"spot/future: {price_spot/price_future}") + time.sleep(5) + + except Exception as error: + print(f"交易{config.symbol}: {error}") + time.sleep(5) + diff --git a/test1.py b/test1.py new file mode 100644 index 0000000..d2eed2a --- /dev/null +++ b/test1.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +#coding: utf-8 +import smtplib +from email.mime.text import MIMEText +sender = '44444444444444444444g' +receiver = '44444444444444444rg' +subject = 'python email test' +smtpserver = '4444444444444444444rg' +username = '4444444444444444g' +password = '44444444444444444444444' +msg = MIMEText('

ja2磁盘使用超过80%

','html','utf-8') +msg['Subject'] = subject +smtp = smtplib.SMTP() +smtp.connect(smtpserver) +smtp.login(username, password) +smtp.sendmail(sender, receiver, msg.as_string()) +smtp.quit() diff --git a/test2.py b/test2.py new file mode 100644 index 0000000..8d16f6f --- /dev/null +++ b/test2.py @@ -0,0 +1,7 @@ +def as_num(x): + y='{:.32f}'.format(x) # 5f表示保留5位小数点的float型 + return(y) + + + +print(as_num(1.2e-4)) diff --git a/test_depth.py b/test_depth.py new file mode 100644 index 0000000..294b8fc --- /dev/null +++ b/test_depth.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +import time +import logging +from binance.binance_trader import BinanceTrader +from binance.binance_future_trader import BinanceFutureTrader +from utils import utility +from utils import config +format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +logging.basicConfig(level=logging.INFO, format=format, filename='grid_trader_log.txt') +logger = logging.getLogger('binance') + + +if __name__ == '__main__': + + + config.loads('./conf/config.json') + + if config.platform == 'binance_spot': + trader = BinanceTrader() + else: + trader = BinanceFutureTrader() + + + + while True: + try: + trader.get_depth(5) + time.sleep(20) + + except Exception as error: + utility.alert(f"交易{config.symbol}: {error}") + time.sleep(5) + diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..9430428 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,4 @@ +from .config import config +from .utility import * +from .smtp import send_email +from .dingding import * \ No newline at end of file diff --git a/utils/config.py b/utils/config.py new file mode 100644 index 0000000..1c4f0e6 --- /dev/null +++ b/utils/config.py @@ -0,0 +1,54 @@ +# -*- coding:utf-8 -*- +import json + + +class Config: + + def __init__(self): + + self.platform: str = "binance_spot" # 交易的平台 + self.symbol:str = "XRPUSDT" # 交易对. + self.gap_percent: float = 0.005 # 网格变化交易的单位. + self.api_key: str = None + self.api_secret: str = None + self.pass_phrase = None + self.quantity:float = 1 + self.min_price = 0.0001 + self.min_qty = 0.01 + self.max_orders = 1 + self.proxy_host = "" # proxy host + self.proxy_port = 0 # proxy port + + + def loads(self, config_file=None): + """ Load config file. + + Args: + config_file: config json file. + """ + configures = {} + if config_file: + try: + with open(config_file) as f: + data = f.read() + configures = json.loads(data) + except Exception as e: + print(e) + exit(0) + if not configures: + print("config json file error!") + exit(0) + self._update(configures) + + def _update(self, update_fields): + """ + 更新update fields. + :param update_fields: + :return: None + + """ + + for k, v in update_fields.items(): + setattr(self, k, v) + +config = Config() \ No newline at end of file diff --git a/utils/dingding.py b/utils/dingding.py new file mode 100644 index 0000000..814fe52 --- /dev/null +++ b/utils/dingding.py @@ -0,0 +1,34 @@ +from dingtalkchatbot.chatbot import DingtalkChatbot +from utils.config import config + +def dingding_at_me(msg): + # WebHook地址 + webhook = config.dingding_robot_url + #dingding_user = config.dingding_user_phone + secret = 'SEC11b9...这里填写自己的加密设置密钥' # 可选:创建机器人勾选“加签”选项时使用 + # 初始化机器人小丁 + xiaoding = DingtalkChatbot(webhook) # 方式一:通常初始化方式 + #xiaoding = DingtalkChatbot(webhook, secret=secret) # 方式二:勾选“加签”选项时使用(v1.5以上新功能) + #xiaoding = DingtalkChatbot(webhook, pc_slide=True) # 方式三:设置消息链接在PC端侧边栏打开(v1.5以上新功能) + # Text消息@所有人 + #xiaoding.send_text(msg='我就是小丁,小丁就是我!', is_at_all=True) + dingding_user = [config.dingding_user_phone] + #xiaoding.send_text(msg=msg, at_mobiles=dingding_user) + xiaoding.send_text(msg=msg, at_mobiles=dingding_user) + +def dingding_at_all(msg): + webhook = config.dingding_robot_url + xiaoding = DingtalkChatbot(webhook) # 方式一:通常初始化方式 + xiaoding.send_text(msg=msg, is_at_all=True) + +def dingding_say(msg): + webhook = config.dingding_robot_url + xiaoding = DingtalkChatbot(webhook) # 方式一:通常初始化方式 + xiaoding.send_text(msg=msg) + +if __name__ == '__main__': + config.loads('../conf/config.json') + dingding_say("test") + print("dingding utils") + + diff --git a/utils/queue.py b/utils/queue.py new file mode 100644 index 0000000..b5686f3 --- /dev/null +++ b/utils/queue.py @@ -0,0 +1,52 @@ +# 先进先出 +class Queue(): + + def __init__(self,size): + self.size=size + self.front=-1 #队列前面的指针 + self.rear=-1 #队列后面的指针 + #类中设置两个属性分别为front和rear来模拟队列的头尾指针,通过它们值的关系可以判定队列是空还是满 + self.queue=[] + + ## 入队操作 + def enqueue(self,ele): + if self.isfull(): + print("queue is full") + else: + self.queue.append(ele) + self.rear=self.rear+1 + + ## 出队操作 + def dequeue(self): + if self.isempty(): + print("queue is empty") + else: + self.queue.pop(0) + self.front=self.front+1 + + def isfull(self): + if self.rear - self.front + 1 == self.size : + return True + else: + return False + def isempty(self): + if self.front == self.rear: + return True + else: + return False + def showQueue(self): + print(self.queue) + +if __name__ == '__main__': + q = Queue(10) + for i in range(6): + #print(i) + q.enqueue(i) + q.showQueue() + for i in range(3): + q.dequeue() + q.showQueue() + print(q.isempty()) + + + diff --git a/utils/smtp.py b/utils/smtp.py new file mode 100644 index 0000000..75f05ab --- /dev/null +++ b/utils/smtp.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from utils.config import config +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.header import Header + +#自适应邮件标题 +def get_subject(text): + default_subject='交易通知' + if "买单成交" in text: + return "买单成交" + elif "卖单成交" in text: + return "卖单成交" + elif "启动" in text: + return "机器人启动" + elif "配置不当" in text: + return "配置不当" + elif "单创建失败" in text: + return "订单创建失败" + elif "撤销失败" in text: + return "订单撤销失败" + else: + return default_subject + + +#发出邮件 +def send_email(text): + #连接邮箱服务器 + #con = smtplib.SMTP_SSL('mail.yangqiao.org', 465) + con = smtplib.SMTP(config.SMTP_HOST, config.SMTP_PORT) + #登录邮箱 + con.login(config.SMTP_USER, config.SMTP_PASSWORD) + + # 准备数据 创建邮件对象 + msg = MIMEMultipart() + + # 设置邮件主题 设置邮件发送者 设置邮件接受者 + subject_and_sender=get_subject(text) + subject = Header(subject_and_sender, 'utf-8').encode() + msg['Subject'] = subject + msg['From'] = (f"{subject_and_sender} <{config.SMTP_USER}>") + msg['To'] = config.user_email + + # 添加文字内容 + text = MIMEText(text, 'plain', 'utf-8') + msg.attach(text) + + # 发送邮件 + con.sendmail(config.SMTP_USER,config.user_email, msg.as_string()) + con.quit() + + + + + +if __name__ == '__main__': + config.loads('../conf/config.json') + send_email("卖单成交") + + diff --git a/utils/utility.py b/utils/utility.py new file mode 100644 index 0000000..9c5fbc7 --- /dev/null +++ b/utils/utility.py @@ -0,0 +1,110 @@ +import json +from pathlib import Path +from decimal import Decimal +from utils import dingding, smtp +import datetime +import time +import pytz + +def as_num(x): + # 32f表示保留32位小数点的float型 + y = '{:.32f}'.format(x) + return(y) + + +def get_current_timestamp(): + """ + 获取系统的时间. + :return: + """ + return int(time.time() * 1000) + +def alert(msg): + print(msg) + dingding.dingding_at_me(msg) + smtp.send_email(msg) + +def cn_time(): + """ + chinese time + https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + """ + return datetime.datetime.now(pytz.timezone('Asia/Shanghai')) + + +def _get_trader_dir(temp_name: str): + """ + Get path where trader is running in. + """ + cwd = Path.cwd() + temp_path = cwd.joinpath(temp_name) + + if temp_path.exists(): + return cwd, temp_path + + if not temp_path.exists(): + temp_path.mkdir() + + return cwd, temp_path + + +TRADER_DIR, TEMP_DIR = _get_trader_dir("trader") + + +def get_file_path(filename: str): + """ + Get path for temp file with filename. + """ + return TEMP_DIR.joinpath(filename) + + +def get_folder_path(folder_name: str): + """ + Get path for temp folder with folder name. + """ + folder_path = TEMP_DIR.joinpath(folder_name) + if not folder_path.exists(): + folder_path.mkdir() + return folder_path + +def load_json(filename: str): + """ + Load data from json file in temp path. + """ + filepath = get_file_path(filename) + + if filepath.exists(): + with open(filepath, mode="r", encoding="UTF-8") as f: + data = json.load(f) + return data + else: + save_json(filename, {}) + return {} + + +def save_json(filename: str, data: dict): + """ + Save data into json file in temp path. + """ + filepath = get_file_path(filename) + with open(filepath, mode="w+", encoding="UTF-8") as f: + json.dump( + data, + f, + indent=4, + ensure_ascii=False + ) + + +def round_to(value: float, target: float) -> float: + """ + Round price to price tick value. + """ + value = Decimal(str(value)) + target = Decimal(str(target)) + #round() 方法返回浮点数x的四舍五入值。 + ##eg: + ## quantity = round_to(float(config.quantity), float(config.min_qty)) + rounded = float(int(round(value / target)) * target) + return rounded +