diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aea22fc --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/hb.iml b/.idea/hb.iml new file mode 100644 index 0000000..6711606 --- /dev/null +++ b/.idea/hb.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a5f3f98 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/api/HuobiServices.py b/api/HuobiServices.py new file mode 100644 index 0000000..5839141 --- /dev/null +++ b/api/HuobiServices.py @@ -0,0 +1,425 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Date : 2017-12-20 15:40:03 +# @Author : KlausQiu +# @QQ : 375235513 +# @github : https://github.com/KlausQIU + +from api.Utils import * + +''' +Market data API +''' + + +# 获取KLine +def get_kline(symbol, period, size=150): + """ + :param symbol + :param period: 可选值:{1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year } + :param size: 可选值: [1,2000] + :return: + """ + params = {'symbol': symbol, + 'period': period, + 'size': size} + + url = MARKET_URL + '/market/history/kline' + return http_get_request(url, params) + + +# 获取marketdepth +def get_depth(symbol, type): + """ + :param symbol + :param type: 可选值:{ percent10, step0, step1, step2, step3, step4, step5 } + :return: + """ + params = {'symbol': symbol, + 'type': type} + + url = MARKET_URL + '/market/depth' + return http_get_request(url, params) + + +# 获取tradedetail +def get_trade(symbol): + """ + :param symbol + :return: + """ + params = {'symbol': symbol} + + url = MARKET_URL + '/market/trade' + return http_get_request(url, params) + + +# 获取merge ticker +def get_ticker(symbol): + """ + :param symbol: + :return: + """ + params = {'symbol': symbol} + + url = MARKET_URL + '/market/detail/merged' + return http_get_request(url, params) + + +# 获取 Market Detail 24小时成交量数据 +def get_detail(symbol): + """ + :param symbol + :return: + """ + params = {'symbol': symbol} + + url = MARKET_URL + '/market/detail' + return http_get_request(url, params) + +# 获取 支持的交易对 +def get_symbols(long_polling=None): + """ + + """ + params = {} + if long_polling: + params['long-polling'] = long_polling + path = '/v1/common/symbols' + return api_key_get(params, path) + +''' +Trade/Account API +''' + + +def get_accounts(): + """ + :return: + """ + path = "/v1/account/accounts" + params = {} + return api_key_get(params, path) + + +# 获取当前账户资产 +def get_balance(acct_id=None): + """ + :param acct_id + :return: + """ + + if not acct_id: + accounts = get_accounts() + acct_id = accounts['data'][0]['id']; + + url = "/v1/account/accounts/{0}/balance".format(acct_id) + params = {"account-id": acct_id} + return api_key_get(params, url) + + +# 下单 + +# 创建并执行订单 +def send_order(amount, source, symbol, _type, price=0): + """ + :param amount: + :param source: 如果使用借贷资产交易,请在下单接口,请求参数source中填写'margin-api' + :param symbol: + :param _type: 可选值 {buy-market:市价买, sell-market:市价卖, buy-limit:限价买, sell-limit:限价卖} + :param price: + :return: + """ + try: + accounts = get_accounts() + acct_id = accounts['data'][0]['id'] + except BaseException as e: + print ('get acct_id error.%s' % e) + acct_id = ACCOUNT_ID + + params = {"account-id": acct_id, + "amount": amount, + "symbol": symbol, + "type": _type, + "source": source} + if price: + params["price"] = price + + url = '/v1/order/orders/place' + return api_key_post(params, url) + + +# 撤销订单 +def cancel_order(order_id): + """ + + :param order_id: + :return: + """ + params = {} + url = "/v1/order/orders/{0}/submitcancel".format(order_id) + return api_key_post(params, url) + + +# 查询某个订单 +def order_info(order_id): + """ + + :param order_id: + :return: + """ + params = {} + url = "/v1/order/orders/{0}".format(order_id) + return api_key_get(params, url) + + +# 查询某个订单的成交明细 +def order_matchresults(order_id): + """ + + :param order_id: + :return: + """ + params = {} + url = "/v1/order/orders/{0}/matchresults".format(order_id) + return api_key_get(params, url) + + +# 查询当前委托、历史委托 +def orders_list(symbol, states, types=None, start_date=None, end_date=None, _from=None, direct=None, size=None): + """ + + :param symbol: + :param states: 可选值 {pre-submitted 准备提交, submitted 已提交, partial-filled 部分成交, partial-canceled 部分成交撤销, filled 完全成交, canceled 已撤销} + :param types: 可选值 {buy-market:市价买, sell-market:市价卖, buy-limit:限价买, sell-limit:限价卖} + :param start_date: + :param end_date: + :param _from: + :param direct: 可选值{prev 向前,next 向后} + :param size: + :return: + """ + params = {'symbol': symbol, + 'states': states} + + if types: + params[types] = types + if start_date: + params['start-date'] = start_date + if end_date: + params['end-date'] = end_date + if _from: + params['from'] = _from + if direct: + params['direct'] = direct + if size: + params['size'] = size + url = '/v1/order/orders' + return api_key_get(params, url) + + +# 查询当前成交、历史成交 +def orders_matchresults(symbol, types=None, start_date=None, end_date=None, _from=None, direct=None, size=None): + """ + + :param symbol: + :param types: 可选值 {buy-market:市价买, sell-market:市价卖, buy-limit:限价买, sell-limit:限价卖} + :param start_date: + :param end_date: + :param _from: + :param direct: 可选值{prev 向前,next 向后} + :param size: + :return: + """ + params = {'symbol': symbol} + + if types: + params[types] = types + if start_date: + params['start-date'] = start_date + if end_date: + params['end-date'] = end_date + if _from: + params['from'] = _from + if direct: + params['direct'] = direct + if size: + params['size'] = size + url = '/v1/order/matchresults' + return api_key_get(params, url) + + + +# 申请提现虚拟币 +def withdraw(address, amount, currency, fee=0, addr_tag=""): + """ + + :param address_id: + :param amount: + :param currency:btc, ltc, bcc, eth, etc ...(火币Pro支持的币种) + :param fee: + :param addr-tag: + :return: { + "status": "ok", + "data": 700 + } + """ + params = {'address': address, + 'amount': amount, + "currency": currency, + "fee": fee, + "addr-tag": addr_tag} + url = '/v1/dw/withdraw/api/create' + + return api_key_post(params, url) + +# 申请取消提现虚拟币 +def cancel_withdraw(address_id): + """ + + :param address_id: + :return: { + "status": "ok", + "data": 700 + } + """ + params = {} + url = '/v1/dw/withdraw-virtual/{0}/cancel'.format(address_id) + + return api_key_post(params, url) + + +''' +借贷API +''' + +# 创建并执行借贷订单 + + +def send_margin_order(amount, source, symbol, _type, price=0): + """ + :param amount: + :param source: 'margin-api' + :param symbol: + :param _type: 可选值 {buy-market:市价买, sell-market:市价卖, buy-limit:限价买, sell-limit:限价卖} + :param price: + :return: + """ + try: + accounts = get_accounts() + acct_id = accounts['data'][0]['id'] + except BaseException as e: + print ('get acct_id error.%s' % e) + acct_id = ACCOUNT_ID + + params = {"account-id": acct_id, + "amount": amount, + "symbol": symbol, + "type": _type, + "source": 'margin-api'} + if price: + params["price"] = price + + url = '/v1/order/orders/place' + return api_key_post(params, url) + +# 现货账户划入至借贷账户 + + +def exchange_to_margin(symbol, currency, amount): + """ + :param amount: + :param currency: + :param symbol: + :return: + """ + params = {"symbol": symbol, + "currency": currency, + "amount": amount} + + url = "/v1/dw/transfer-in/margin" + return api_key_post(params, url) + +# 借贷账户划出至现货账户 + + +def margin_to_exchange(symbol, currency, amount): + """ + :param amount: + :param currency: + :param symbol: + :return: + """ + params = {"symbol": symbol, + "currency": currency, + "amount": amount} + + url = "/v1/dw/transfer-out/margin" + return api_key_post(params, url) + +# 申请借贷 +def get_margin(symbol, currency, amount): + """ + :param amount: + :param currency: + :param symbol: + :return: + """ + params = {"symbol": symbol, + "currency": currency, + "amount": amount} + url = "/v1/margin/orders" + return api_key_post(params, url) + +# 归还借贷 +def repay_margin(order_id, amount): + """ + :param order_id: + :param amount: + :return: + """ + params = {"order-id": order_id, + "amount": amount} + url = "/v1/margin/orders/{0}/repay".format(order_id) + return api_key_post(params, url) + +# 借贷订单 +def loan_orders(symbol, currency, start_date="", end_date="", start="", direct="", size=""): + """ + :param symbol: + :param currency: + :param direct: prev 向前,next 向后 + :return: + """ + params = {"symbol": symbol, + "currency": currency} + if start_date: + params["start-date"] = start_date + if end_date: + params["end-date"] = end_date + if start: + params["from"] = start + if direct and direct in ["prev", "next"]: + params["direct"] = direct + if size: + params["size"] = size + url = "/v1/margin/loan-orders" + return api_key_get(params, url) + + +# 借贷账户详情,支持查询单个币种 +def margin_balance(symbol): + """ + :param symbol: + :return: + """ + params = {} + url = "/v1/margin/accounts/balance" + if symbol: + params['symbol'] = symbol + + return api_key_get(params, url) + + +if __name__ == '__main__': + print (get_symbols()) diff --git a/api/Utils.py b/api/Utils.py new file mode 100644 index 0000000..91cfb88 --- /dev/null +++ b/api/Utils.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Date : 2017-12-20 15:40:03 +# @Author : KlausQiu +# @QQ : 375235513 +# @github : https://github.com/KlausQIU + +import base64 +import datetime +import hashlib +import hmac +import json +import urllib +import urllib.parse +import urllib.request +import requests + +# 此处填写APIKEY + +ACCESS_KEY = "2c862239-1f861cf9-9ce73af3-6c7ed" +SECRET_KEY = "66ead5b3-261721f8-cc099e40-e97e4" + + + +# API 请求地址 +MARKET_URL = "https://api.huobi.pro" +TRADE_URL = "https://api.huobi.pro" + +# 首次运行可通过get_accounts()获取acct_id,然后直接赋值,减少重复获取。 +ACCOUNT_ID = None + +#'Timestamp': '2017-06-02T06:13:49' + +def http_get_request(url, params, add_to_headers=None): + headers = { + "Content-type": "application/x-www-form-urlencoded", + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36', + } + if add_to_headers: + headers.update(add_to_headers) + postdata = urllib.parse.urlencode(params) + response = requests.get(url, postdata, headers=headers, timeout=5) + try: + + if response.status_code == 200: + return response.json() + else: + return + except BaseException as e: + print("httpGet failed, detail is:%s,%s" %(response.text,e)) + return + + +def http_post_request(url, params, add_to_headers=None): + headers = { + "Accept": "application/json", + 'Content-Type': 'application/json' + } + if add_to_headers: + headers.update(add_to_headers) + postdata = json.dumps(params) + response = requests.post(url, postdata, headers=headers, timeout=10) + try: + + if response.status_code == 200: + return response.json() + else: + return + except BaseException as e: + print("httpPost failed, detail is:%s,%s" %(response.text,e)) + return + + +def api_key_get(params, request_path): + method = 'GET' + timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + params.update({'AccessKeyId': ACCESS_KEY, + 'SignatureMethod': 'HmacSHA256', + 'SignatureVersion': '2', + 'Timestamp': timestamp}) + + host_url = TRADE_URL + host_name = urllib.parse.urlparse(host_url).hostname + host_name = host_name.lower() + params['Signature'] = createSign(params, method, host_name, request_path, SECRET_KEY) + + url = host_url + request_path + return http_get_request(url, params) + + +def api_key_post(params, request_path): + method = 'POST' + timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + params_to_sign = {'AccessKeyId': ACCESS_KEY, + 'SignatureMethod': 'HmacSHA256', + 'SignatureVersion': '2', + 'Timestamp': timestamp} + + host_url = TRADE_URL + host_name = urllib.parse.urlparse(host_url).hostname + host_name = host_name.lower() + params_to_sign['Signature'] = createSign(params_to_sign, method, host_name, request_path, SECRET_KEY) + url = host_url + request_path + '?' + urllib.parse.urlencode(params_to_sign) + return http_post_request(url, params) + + +def createSign(pParams, method, host_url, request_path, secret_key): + sorted_params = sorted(pParams.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 = secret_key.encode(encoding='UTF8') + + digest = hmac.new(secret_key, payload, digestmod=hashlib.sha256).digest() + signature = base64.b64encode(digest) + signature = signature.decode() + return signature + diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..143f486 --- /dev/null +++ b/api/__init__.py @@ -0,0 +1 @@ +# __init__.py diff --git a/application.py b/application.py new file mode 100644 index 0000000..556b3bf --- /dev/null +++ b/application.py @@ -0,0 +1,12 @@ +import time +from task import * +from api.HuobiServices import * + +kline = get_kline('eoseth', '60min', 2000) + +order_list = load_pending_order_list() +print(order_list) + +s = datetime.datetime(2018, 2, 22, 13) +t = time.mktime(s.timetuple()) +print(t) \ No newline at end of file diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..e264ac6 --- /dev/null +++ b/log.txt @@ -0,0 +1,10 @@ +2018-02-22 Thursday 16:46:53 run.py : INFO ʼ ... +2018-02-22 Thursday 16:46:53 run.py : INFO д1ʱ - 1519289213.9993107 +2018-02-22 Thursday 16:46:54 connectionpool.py : DEBUG Starting new HTTPS connection (1): api.huobi.pro +2018-02-22 Thursday 16:46:54 connectionpool.py : DEBUG https://api.huobi.pro:443 "GET /v1/account/accounts?AccessKeyId=2c862239-1f861cf9-9ce73af3-6c7ed&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2018-02-22T08%3A46%3A53&Signature=AsWEfJec2gQ4Ix2tbpvGWn1x0KEdPzl5Fl3kEQXpKc0%3D HTTP/1.1" 200 None +2018-02-22 Thursday 16:46:54 connectionpool.py : DEBUG Starting new HTTPS connection (1): api.huobi.pro +2018-02-22 Thursday 16:46:55 connectionpool.py : DEBUG https://api.huobi.pro:443 "POST /v1/order/orders/place?AccessKeyId=2c862239-1f861cf9-9ce73af3-6c7ed&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2018-02-22T08%3A46%3A54&Signature=aY4vnmVrDdc3EIWo%2FSncFbcYjQMxO32bUhc%2B5VoWGLE%3D HTTP/1.1" 200 None +2018-02-22 Thursday 16:47:05 run.py : INFO д2ʱ - 1519289225.1821854 +2018-02-22 Thursday 16:47:15 run.py : INFO д3ʱ - 1519289235.1897564 +2018-02-22 Thursday 16:47:25 run.py : INFO д4ʱ - 1519289245.1974354 +2018-02-22 Thursday 16:47:35 run.py : INFO д5ʱ - 1519289255.2036781 diff --git a/run.py b/run.py new file mode 100644 index 0000000..bdad70e --- /dev/null +++ b/run.py @@ -0,0 +1,63 @@ +import sched +import logging +from task import * +from api.HuobiServices import * + + +def intitial_logging(): + ''''' Output log to file and console ''' + # Define a Handler and set a format which output to file + logging.basicConfig( + level=logging.DEBUG, # 定义输出到文件的log级别,大于此级别的都被输出 + format='%(asctime)s %(filename)s : %(levelname)s %(message)s', # 定义输出log的格式 + datefmt='%Y-%m-%d %A %H:%M:%S', # 时间 + filename='log.txt', # log文件名 + filemode='w') # 写入模式“w”或“a” + # Define a Handler and set a format which output to console + console = logging.StreamHandler() # 定义console handler + console.setLevel(logging.INFO) # 定义该handler级别 + formatter = logging.Formatter('%(asctime)s %(filename)s : %(levelname)s %(message)s') # 定义该handler格式 + console.setFormatter(formatter) + # Create an instance + logging.getLogger().addHandler(console) # 实例化添加handler + + +if __name__ == "__main__": + intitial_logging() + +s = sched.scheduler(time.time, time.sleep) +# 计数器,一个循环任务,总共让自己执行3次 +index = 0 + + +# 第二个工作函数,自调任务,自己开启定时并启动。 +def run_task(): + # 只要没有让自己调用到第3次,那么继续重头开始执行本任务 + # 这里的delay 可以重新指定 + global index + index = index + 1 + msg = '运行次数({0})运行时间 - {1}'.format(index, time.time()) + logging.info(msg) + + order_list = load_pending_order_list() + for order in order_list: + # 未执行已经并且已经到达执行时间 + if order.status == OrderStatus.pending.value and order.time < time.time(): + # 买单 + if order.type == OrderType.buy.value : + # 下单 + result = send_order(order.amount, '', order.symbol, 'buy-limit', order.price) + order.status = OrderStatus.done.value + order.id = result["data"] + + save_order_list(order_list) + + s.enter(10, 1, run_task) + s.run() + +logging.info('开始运行 ...') + +# 开启自调任务 +run_task() + + diff --git a/task/__init__.py b/task/__init__.py new file mode 100644 index 0000000..72b612c --- /dev/null +++ b/task/__init__.py @@ -0,0 +1,91 @@ +# __init__.py +import json +import datetime +import time +from enum import Enum + + +class OrderType(Enum): + buy = 1 + sell = 2 + + +class OrderStatus(Enum): + pending = 1 + done = 2 + + +class Order(): + def __init__(self): + self.symbol = None + self.amount = 0 + self.time = None + self.id = "" + self.status = OrderStatus.pending # 1 pending, 2 done + self.type = OrderType.buy + self.price = 0 + + +def load_pending_order_list(): + # 读取下单Order任务 + file = open("task/pending.json") + s = file.read() + file.close() + + item_list = json.loads(s) + + # 转换成 Order List 对象 + order_list = [] + for item in item_list: + order = Order() + order.symbol = item["symbol"] + order.amount = item["amount"] + order.time = item["time"] + order.status = int(item["status"]) + order.type = int(item["type"]) + order.price = float(item["price"]) + order_list.append(order) + + return order_list + +def save_order_list(order_list): + pending_list = [] + + # 读取完成 的订单 + now = datetime.datetime.now() + file = open("task/d{0}-{1}-{2}.json".format(now.year, now.month, now.day)) + s = file.read() + file.close() + + done_list = [] + if s !="": + done_list = json.loads(s) + + + for order in order_list: + item = {}; + item["id"] = order.id + item["symbol"] = order.symbol + item["amount"] = order.amount + item["time"] = order.time + item["status"] = order.status + item["type"] = order.type + item["price"] = order.price + if order.status == OrderStatus.pending.value: + pending_list.append(item) + + if order.status == OrderStatus.done.value: + done_list.append(item) + + # 保存 未完成 的订单 + s = json.dumps(pending_list) + file = open("task/pending.json", 'w') + file.write(s) + file.close() + + # 保存 完成 的订单 + s = json.dumps(done_list) + now = datetime.datetime.now() + file = open("task/d{0}-{1}-{2}.json".format(now.year, now.month, now.day), 'w') + file.write(s) + file.close() \ No newline at end of file diff --git a/task/d2018-2-22.json b/task/d2018-2-22.json new file mode 100644 index 0000000..d7db1e4 --- /dev/null +++ b/task/d2018-2-22.json @@ -0,0 +1 @@ +[{"id": "1720868327", "symbol": "eoseth", "amount": 1, "time": 1519275600, "status": 2, "type": 1, "price": 0.0001}] \ No newline at end of file diff --git a/task/pending.json b/task/pending.json new file mode 100644 index 0000000..0aecd34 --- /dev/null +++ b/task/pending.json @@ -0,0 +1 @@ +[{"id": "", "symbol": "eoseth", "amount": 1, "time": 1519308000, "status": 1, "type": 1, "price": 0.0001}] \ No newline at end of file