diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6b7abf9..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/alpha/platforms/huobi_usdt_swap/restapi/rest_algo_order_sync.py b/alpha/platforms/huobi_usdt_swap/restapi/rest_algo_order_sync.py new file mode 100644 index 0000000..454b84c --- /dev/null +++ b/alpha/platforms/huobi_usdt_swap/restapi/rest_algo_order_sync.py @@ -0,0 +1,40 @@ +import json + +from alpha.utils.http_utils import post, get_url_suffix, get + +class RestAlgoOrderUsdtSwap: + + def __init__(self, access_key: str, secret_key: str, host: str = None): + self.access_key = access_key + self.secret_key = secret_key + if host is None: + host = "api.hbdm.com" + self.host = host + + def algo_order(self, data: dict = None) -> dict: + """创建策略订单""" + path = "/v5/algo/order" + return post(self.access_key, self.secret_key, self.host, path, data) + + def cancel_algo_orders(self, data: dict = None) -> dict: + """取消策略订单""" + path = "/v5/algo/cancel-orders" + return post(self.access_key, self.secret_key, self.host, path, data) + + def query_algo_order(self, params: dict = None) -> dict: + """查询策略订单""" + path = "/v5/algo/order" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def query_open_algo_orders(self, params: dict = None) -> dict: + """查询未触发策略订单""" + path = "/v5/algo/order/opens" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def query_algo_order_history(self, params: dict = None) -> dict: + """查询历史策略订单""" + path = "/v5/algo/order/history" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) \ No newline at end of file diff --git a/alpha/platforms/huobi_usdt_swap/restapi/rest_algo_order_usdt_swap.py b/alpha/platforms/huobi_usdt_swap/restapi/rest_algo_order_usdt_swap.py new file mode 100644 index 0000000..a6732c2 --- /dev/null +++ b/alpha/platforms/huobi_usdt_swap/restapi/rest_algo_order_usdt_swap.py @@ -0,0 +1,283 @@ +# -*- coding:utf-8 -*- + +""" +Huobi USDT Swap Api Module. + +Author: QiaoXiaofeng +Date: 2020/09/10 +Email: andyjoe318@gmail.com +""" + +import gzip +import json +import copy +import hmac +import base64 +import urllib +import hashlib +import datetime +import time +from urllib.parse import urljoin +from alpha.utils.request import AsyncHttpRequests +from alpha.const import USER_AGENT + +__all__ = ("HuobiUsdtSwapRestAlgoOrderAPI",) + + +class HuobiUsdtSwapRestAlgoOrderAPI: + """Huobi USDT Swap 策略订单REST API Client""" + + def __init__(self, host, access_key, secret_key): + self._host = host + self._access_key = access_key + self._secret_key = secret_key + + async def algo_order(self, + contract_code: str, + type: str, + position_side: str, + side: str, + margin_mode: str, + algo_client_order_id: str = None, + volume: str = None, + tp_trigger_price: str = None, + tp_order_price: str = None, + tp_type: str = None, + tp_trigger_price_type: str = None, + sl_trigger_price: str = None, + sl_order_price: str = None, + sl_type: str = None, + sl_trigger_price_type: str = None, + price: str = None, + price_match: str = None, + trigger_price: str = None, + trigger_price_type: str = None, + active_price: str = None, + order_price_type: str = None, + callback_rate: str = None, + reduce_only: bool = None): + """创建策略订单""" + uri = "/v5/algo/order" + body = { + "contract_code": contract_code, + "type": type, + "position_side": position_side, + "side": side, + "margin_mode": margin_mode + } + + # 可选字段 + if algo_client_order_id: + body["algo_client_order_id"] = algo_client_order_id + if volume: + body["volume"] = volume + if tp_trigger_price: + body["tp_trigger_price"] = tp_trigger_price + if tp_order_price: + body["tp_order_price"] = tp_order_price + if tp_type: + body["tp_type"] = tp_type + if tp_trigger_price_type: + body["tp_trigger_price_type"] = tp_trigger_price_type + if sl_trigger_price: + body["sl_trigger_price"] = sl_trigger_price + if sl_order_price: + body["sl_order_price"] = sl_order_price + if sl_type: + body["sl_type"] = sl_type + if sl_trigger_price_type: + body["sl_trigger_price_type"] = sl_trigger_price_type + if price: + body["price"] = price + if price_match: + body["price_match"] = price_match + if trigger_price: + body["trigger_price"] = trigger_price + if trigger_price_type: + body["trigger_price_type"] = trigger_price_type + if active_price: + body["active_price"] = active_price + if order_price_type: + body["order_price_type"] = order_price_type + if callback_rate: + body["callback_rate"] = callback_rate + if reduce_only is not None: + body["reduce_only"] = 1 if reduce_only else 0 + + success, error = await self.request("POST", uri, body=body, auth=True) + return success, error + + async def cancel_algo_orders(self, + algo_id: str = None, + algo_client_order_id: str = None, + contract_code: str = None): + """取消策略订单""" + uri = "/v5/algo/cancel-orders" + body = {} + + if algo_id: + body["algo_id"] = algo_id + if algo_client_order_id: + body["algo_client_order_id"] = algo_client_order_id + if contract_code: + body["contract_code"] = contract_code + + success, error = await self.request("POST", uri, body=body, auth=True) + return success, error + + async def query_algo_order(self, + type: str, + algo_id: str = None, + algo_client_order_id: str = None, + contract_code: str = None): + """查询策略订单""" + uri = "/v5/algo/order" + params = { + "type": type + } + + if algo_id: + params["algo_id"] = algo_id + if algo_client_order_id: + params["algo_client_order_id"] = algo_client_order_id + if contract_code: + params["contract_code"] = contract_code + + success, error = await self.request("GET", uri, params=params, auth=True) + return success, error + + async def query_open_algo_orders(self, + type: str, + contract_code: str = None, + algo_id: str = None, + algo_client_order_id: str = None, + from_page: int = None, + limit: int = None, + direct: str = None): + """查询未触发策略订单""" + uri = "/v5/algo/order/opens" + params = { + "type": type + } + + if contract_code: + params["contract_code"] = contract_code + if algo_id: + params["algo_id"] = algo_id + if algo_client_order_id: + params["algo_client_order_id"] = algo_client_order_id + if from_page is not None: + params["from"] = from_page + if limit is not None: + params["limit"] = limit + if direct: + params["direct"] = direct + + success, error = await self.request("GET", uri, params=params, auth=True) + return success, error + + async def query_algo_order_history(self, + type: str, + contract_code: str = None, + margin_mode: str = None, + states: list = None, + start_time: int = None, + end_time: int = None, + from_page: int = None, + limit: int = None, + direct: str = None): + """查询历史策略订单""" + uri = "/v5/algo/order/history" + params = { + "type": type + } + + if contract_code: + params["contract_code"] = contract_code + if margin_mode: + params["margin_mode"] = margin_mode + if states: + params["states"] = ",".join(states) + if start_time: + params["start_time"] = start_time + if end_time: + params["end_time"] = end_time + if from_page is not None: + params["from"] = from_page + if limit is not None: + params["limit"] = limit + if direct: + params["direct"] = direct + + success, error = await self.request("GET", uri, params=params, auth=True) + return success, error + + + async def request(self, method, uri, params=None, body=None, headers=None, auth=False): + """ Do HTTP request. + + Args: + method: HTTP request method. `GET` / `POST` / `DELETE` / `PUT`. + uri: HTTP request uri. + params: HTTP query params. + body: HTTP request body. + headers: HTTP request headers. + auth: If this request requires authentication. + + Returns: + success: Success results, otherwise it's None. + error: Error information, otherwise it's None. + """ + if uri.startswith("http://") or uri.startswith("https://"): + url = uri + else: + url = self._host + uri + + if auth: + timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S") + params = params if params else {} + params.update({"AccessKeyId": self._access_key, + "SignatureMethod": "HmacSHA256", + "SignatureVersion": "2", + "Timestamp": timestamp}) + + params["Signature"] = self.generate_signature(method, params, uri) + + if not headers: + headers = {} + if method == "GET": + headers["Content-type"] = "application/x-www-form-urlencoded" + headers["User-Agent"] = USER_AGENT + _, success, error = await AsyncHttpRequests.fetch("GET", url, params=params, headers=headers, timeout=10) + else: + headers["Accept"] = "application/json" + headers["Content-type"] = "application/json" + headers["User-Agent"] = USER_AGENT + _, success, error = await AsyncHttpRequests.fetch("POST", url, params=params, data=body, headers=headers, + timeout=10) + if error: + return None, error + if not isinstance(success, dict): + result = json.loads(success) + else: + result = success + if result.get("status") != "ok": + return None, result + return result, None + + def generate_signature(self, method, params, request_path): + if request_path.startswith("http://") or request_path.startswith("https://"): + host_url = urllib.parse.urlparse(request_path).hostname.lower() + request_path = '/' + '/'.join(request_path.split('/')[3:]) + else: + host_url = urllib.parse.urlparse(self._host).hostname.lower() + sorted_params = sorted(params.items(), key=lambda d: d[0], reverse=False) + encode_params = urllib.parse.urlencode(sorted_params) + payload = [method, host_url, request_path, encode_params] + payload = "\n".join(payload) + payload = payload.encode(encoding="UTF8") + secret_key = self._secret_key.encode(encoding="utf8") + digest = hmac.new(secret_key, payload, digestmod=hashlib.sha256).digest() + signature = base64.b64encode(digest) + signature = signature.decode() + return signature \ No newline at end of file diff --git a/alpha/platforms/huobi_usdt_swap/restapi/rest_copytrading_trade_sync.py b/alpha/platforms/huobi_usdt_swap/restapi/rest_copytrading_trade_sync.py new file mode 100644 index 0000000..7115071 --- /dev/null +++ b/alpha/platforms/huobi_usdt_swap/restapi/rest_copytrading_trade_sync.py @@ -0,0 +1,63 @@ +import json + +from alpha.utils.http_utils import post, get_url_suffix, get + +class RestCopyTradingTradeSync: + def __init__(self, access_key: str, secret_key: str, host: str = None): + self.access_key = access_key + self.secret_key = secret_key + if host is None: + host = "api.hbdm.com" + self.host = host + + def contract_copytrading_trader_instruments(self, params: dict = None) -> json: + path = "/api/v6/copyTrading/trader/instruments" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def contract_copytrading_trader_statistics(self, params: dict = None) -> json: + path = "/api/v6/copyTrading/trader/statistics" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def contract_copytrading_trader_profit_sharing_history(self, params: dict = None) -> json: + path = "/api/v6/copyTrading/trader/profit-sharing-history" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def contract_copytrading_trader_profit_sharing_history_summary(self, params: dict = None) -> json: + path = "/api/v6/copyTrading/trader/profit-sharing-history-summary" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def contract_copytrading_trader_unrealized_profit_sharing_summary(self, params: dict = None) -> json: + path = "/api/v6/copyTrading/trader/unrealized-profit-sharing-summary" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def contract_copytrading_trader_followers(self, params: dict = None) -> json: + path = "/api/v6/copyTrading/trader/followers" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def contract_copytrading_trader_follower(self, data: dict = None) -> json: + path = "/api/v6/copyTrading/trader/follower" + return post(self.access_key, self.secret_key, self.host, path, data) + + def contract_copytrading_trader_transfer(self, data: dict = None) -> json: + path = "/api/v6/copyTrading/trader/transfer" + return post(self.access_key, self.secret_key, self.host, path, data) + + def contract_copytrading_trader_follower_settings(self, data: dict = None) -> json: + path = "/api/v6/copyTrading/trader/follower-settings" + return post(self.access_key, self.secret_key, self.host, path, data) + + def contract_copytrading_trader_config(self, params: dict = None) -> json: + path = "/api/v6/copyTrading/trader/config" + path = "{}?{}".format(path, get_url_suffix('get', self.access_key, self.secret_key, self.host, path)) + return get(self.host, path, params) + + def contract_copytrading_trader_apikey(self, data: dict = None) -> json: + path = "/api/v6/copyTrading/trader/apikey" + return post(self.access_key, self.secret_key, self.host, path, data) + diff --git a/alpha/platforms/huobi_usdt_swap/restapi/rest_copytrading_trade_usdt_swap.py b/alpha/platforms/huobi_usdt_swap/restapi/rest_copytrading_trade_usdt_swap.py new file mode 100644 index 0000000..b3abcf5 --- /dev/null +++ b/alpha/platforms/huobi_usdt_swap/restapi/rest_copytrading_trade_usdt_swap.py @@ -0,0 +1,186 @@ +# -*- coding:utf-8 -*- + +import gzip +import json +import copy +import hmac +import base64 +import urllib +import hashlib +import datetime +import time +from urllib.parse import urljoin +from alpha.utils.request import AsyncHttpRequests +from alpha.const import USER_AGENT + +__all__ = ("HuobiUsdtSwapRestCopytradingTradeAPI",) + +class HuobiUsdtSwapRestCopytradingTradeAPI: + """ Huobi USDT Swap REST API Client. """ + + def __init__(self, host: str, access_key: str, secret_key: str): + """初始化跟单API客户端 + + Args: + host: API主机地址,如 "https://api.hbdm.com" + access_key: API访问密钥 + secret_key: API秘密密钥 + """ + self._host = host + self._access_key = access_key + self._secret_key = secret_key + self._success_code = "0" + + async def query_trader_instruments(self, inst_type: str) -> tuple: + uri = "/api/v6/copyTrading/trader/instruments" + params = {"instType": inst_type} + return await self._request("GET", uri, params=params, auth=True) + + async def query_trader_statistics(self, inst_type: str) -> tuple: + uri = "/api/v6/copyTrading/trader/statistics" + params = {"instType": inst_type} + return await self._request("GET", uri, params=params, auth=True) + + async def query_trader_profit_sharing_history(self, inst_id: str, inst_type: str, begin: str=None, end: str=None, after: str=None, before: str=None, limit: str=None) -> tuple: + uri = "/api/v6/copyTrading/trader/profit-sharing-history" + params = {"instId": inst_id, "instType": inst_type} + if begin: + params["begin"] = begin + if end: + params["end"] = end + if after: + params["after"] = after + if before: + params["before"] = before + if limit: + params["limit"] = limit + return await self._request("GET", uri, params=params, auth=True) + + async def query_trader_profit_sharing_history_summary(self, inst_type: str) -> tuple: + uri = "/api/v6/copyTrading/trader/profit-sharing-history-summary" + params = {"instType": inst_type} + return await self._request("GET", uri, params=params, auth=True) + + async def query_trader_unrealized_profit_sharing_summary(self, inst_type: str, ccy: str=None) -> tuple: + uri = "/api/v6/copyTrading/trader/unrealized-profit-sharing-summary" + params = {"instType": inst_type} + if ccy: + params["ccy"] = ccy + return await self._request("GET", uri, params=params, auth=True) + + async def query_trader_followers(self, inst_type: str, begin: str=None, end: str=None, after: str=None, before: str=None, limit: str=None) -> tuple: + uri = "/api/v6/copyTrading/trader/followers" + params = {"instType": inst_type} + if begin: + params["begin"] = begin + if end: + params["end"] = end + if after: + params["after"] = after + if before: + params["before"] = before + if limit: + params["limit"] = limit + return await self._request("GET", uri, params=params, auth=True) + + async def delete_trader_follower(self, inst_type: str, follower_uids: str) -> tuple: + uri = "/api/v6/copyTrading/trader/follower" + body = {"instType": inst_type, "followerUids": follower_uids} + return await self.request("POST", uri, body=body, auth=True) + + async def trader_transfer(self, amt: str, from_: str, to: str, ccy: str=None) -> tuple: + uri = "/api/v6/copyTrading/trader/transfer" + body = {"amt": amt, "from": from_, "to": to} + if ccy: + body["ccy"] = ccy + return await self.request("POST", uri, body=body, auth=True) + + async def trader_follower_settings(self, inst_type: str, enable: str=None, profit_sharing_ratio: str=None, max_followers: str=None) -> tuple: + uri = "/api/v6/copyTrading/trader/follower-settings" + body = {"instType": inst_type} + if enable: + body["enable"] = enable + if profit_sharing_ratio: + body["profitSharingRatio"] = profit_sharing_ratio + if max_followers: + body["maxFollowers"] = max_followers + return await self.request("POST", uri, body=body, auth=True) + + async def trader_config(self, inst_type: str) -> tuple: + uri = "/api/v6/copyTrading/trader/config" + params = {"instType": inst_type} + return await self._request("GET", uri, params=params, auth=True) + + async def trader_apikey(self, inst_type: str) -> tuple: + uri = "/api/v6/copyTrading/trader/apikey" + body = {"instType": inst_type} + return await self.request("POST", uri, body=body, auth=True) + + async def request(self, method, uri, params=None, body=None, headers=None, auth=False): + """ Do HTTP request. + + Args: + method: HTTP request method. `GET` / `POST` / `DELETE` / `PUT`. + uri: HTTP request uri. + params: HTTP query params. + body: HTTP request body. + headers: HTTP request headers. + auth: If this request requires authentication. + + Returns: + success: Success results, otherwise it's None. + error: Error information, otherwise it's None. + """ + if uri.startswith("http://") or uri.startswith("https://"): + url = uri + else: + url = self._host + uri + + if auth: + timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S") + params = params if params else {} + params.update({"AccessKeyId": self._access_key, + "SignatureMethod": "HmacSHA256", + "SignatureVersion": "2", + "Timestamp": timestamp}) + + params["Signature"] = self.generate_signature(method, params, uri) + + if not headers: + headers = {} + if method == "GET": + headers["Content-type"] = "application/x-www-form-urlencoded" + headers["User-Agent"] = USER_AGENT + _, success, error = await AsyncHttpRequests.fetch("GET", url, params=params, headers=headers, timeout=10) + else: + headers["Accept"] = "application/json" + headers["Content-type"] = "application/json" + headers["User-Agent"] = USER_AGENT + _, success, error = await AsyncHttpRequests.fetch("POST", url, params=params, data=body, headers=headers, + timeout=10) + if error: + return None, error + if not isinstance(success, dict): + result = json.loads(success) + else: + result = success + if result.get("status") != "ok": + return None, result + return result, None + + def generate_signature(self, method, params, request_path): + if request_path.startswith("http://") or request_path.startswith("https://"): + host_url = urllib.parse.urlparse(request_path).hostname.lower() + request_path = '/' + '/'.join(request_path.split('/')[3:]) + else: + host_url = urllib.parse.urlparse(self._host).hostname.lower() + sorted_params = sorted(params.items(), key=lambda d: d[0], reverse=False) + encode_params = urllib.parse.urlencode(sorted_params) + payload = [method, host_url, request_path, encode_params] + payload = "\n".join(payload) + payload = payload.encode(encoding="UTF8") + secret_key = self._secret_key.encode(encoding="utf8") + digest = hmac.new(secret_key, payload, digestmod=hashlib.sha256).digest() + signature = base64.b64encode(digest) + signature = signature.decode() + return signature \ No newline at end of file diff --git a/setup.py b/setup.py index 875bc08..aa6f44f 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="alphaquant", - version="1.1.7", + version="1.1.8", packages=[ "alpha", "alpha.utils",