├── .project ├── .pydevproject ├── .vscode └── settings.json ├── README.md ├── __init__.py ├── common ├── __init__.py ├── __pycache__ │ └── __init__.cpython-37.pyc └── email │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── email_handler.cpython-37.pyc │ └── mail_agent.cpython-37.pyc │ ├── email_handler.py │ └── mail_agent.py ├── config ├── __pycache__ │ ├── settings.cpython-36.pyc │ └── settings.cpython-37.pyc ├── db_properties.py └── settings.py ├── dao ├── dao.py └── mysql_connection.py ├── exchangeapi ├── __init__.py ├── __init__.pyc ├── __pycache__ │ ├── __init__.cpython-36.pyc │ └── __init__.cpython-37.pyc └── huobi │ ├── __init__.py │ ├── __init__.pyc │ ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-37.pyc │ ├── rest_api.cpython-36.pyc │ └── rest_api.cpython-37.pyc │ ├── rest_api.py │ └── websocket.py ├── requirements.txt ├── run.py ├── runbts.py ├── strategy ├── __init__.py ├── __init__.pyc ├── __pycache__ │ ├── __init__.cpython-37.pyc │ ├── average_cross.cpython-37.pyc │ ├── k_line_service.cpython-37.pyc │ ├── rest_api_service.cpython-37.pyc │ ├── trend.cpython-37.pyc │ └── websocket_subscribe_service.cpython-37.pyc ├── big_order_service.py ├── k_line_service.py ├── second_kill_service.py └── trend.py ├── test.py ├── test └── fake_run.py ├── test2.py └── util ├── __pycache__ ├── api_key_util.cpython-36.pyc ├── api_key_util.cpython-37.pyc ├── http_request_util.cpython-36.pyc └── http_request_util.cpython-37.pyc ├── api_key_util.py └── http_request_util.py /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | TradeRobot 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /${PROJECT_DIR_NAME} 5 | 6 | python 3.6 7 | Default 8 | 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintEnabled": false 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **TradeBot** 2 | 3 | ## **火币自动化交易平台机器人** 4 | 依赖ta-lib提供指标支持,需要编译 5 | 6 | ### **一.功能设计** 7 | 1. 大单买卖监测(交易量在10+BTC的买卖监测) 8 | 2. 异常波动监测(一分钟内3%+—,三分钟内5%+-,etc...) 9 | 3. 支持涨跌幅度超过限度时邮件通知 10 | 4. 币安与火币的搬砖功能 11 | 5. 上币监测以及限价买入 12 | 6. 通过计算N分钟内M的货币的平均变化趋势,判断火币网大盘的涨跌 13 | 7. 法币交易识别超低订单,低价秒杀 14 | 8. 设置收益点,止损点,到点自动交易 15 | 9. 添加测试脚本模拟买入/卖出,便于根据结果修改参数 16 | 17 | ### 已经实现部分 18 | 1.均线交叉判断趋势 19 | 2.邮件通知 20 | 21 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/__init__.py -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/common/__init__.py -------------------------------------------------------------------------------- /common/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/common/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /common/email/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/common/email/__init__.py -------------------------------------------------------------------------------- /common/email/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/common/email/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /common/email/__pycache__/email_handler.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/common/email/__pycache__/email_handler.cpython-37.pyc -------------------------------------------------------------------------------- /common/email/__pycache__/mail_agent.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/common/email/__pycache__/mail_agent.cpython-37.pyc -------------------------------------------------------------------------------- /common/email/email_handler.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from config import settings 3 | from common.email import mail_agent 4 | from config import settings 5 | 6 | 7 | # 记录上一次邮件发送的时间 8 | last_mail_datetime = None 9 | 10 | def send_mail(title, content): 11 | if not mail_agent: 12 | return 13 | global last_mail_datetime 14 | now = datetime.datetime.now() 15 | # if last_mail_datetime and now - last_mail_datetime < datetime.timedelta( 16 | # minutes=settings.N_MINUTES_STATE): 17 | # return 18 | last_mail_datetime = now 19 | 20 | if settings.MAIL_ACCOUNT and settings.MAIL_AUTH_CODE: 21 | agent = mail_agent.MailAgent(settings.MAIL_ACCOUNT, settings.MAIL_AUTH_CODE) 22 | 23 | with agent.SMTP() as s: 24 | s.send(settings.MAIL_RECEIPIENTS, content, title) -------------------------------------------------------------------------------- /common/email/mail_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import smtplib 5 | import imaplib 6 | from email.encoders import encode_base64 7 | from email.mime.multipart import MIMEMultipart 8 | from email.mime.base import MIMEBase 9 | from email.mime.text import MIMEText 10 | from email.header import decode_header 11 | from email import message_from_bytes 12 | 13 | #找到对应文件的路径,如果不在环境变量则添加到环境变量 14 | # file_path = os.path.dirname(os.path.dirname()) 15 | # if file_path not in sys.path: 16 | # sys.path.insert(0,file_path) 17 | 18 | SERVER_LIB = { 19 | 'sample.com':{ 20 | 'smtp':'smtp.sample.com', 21 | 'imap':'imap.sample.com', 22 | 'smtp_port':0, 23 | 'imap_port':0, 24 | 'use_ssl':True 25 | } 26 | } 27 | 28 | class MailAgent(object): 29 | 30 | def __init__(self,account,auth_code,name='',**config): 31 | 32 | account_name,server_name = account.split('@') 33 | 34 | self.smtp = 'smtp.' + server_name 35 | self.imap = 'imap.' + server_name 36 | self.smtp_port = 0 37 | self.imap_port = 0 38 | self.use_ssl = True 39 | self.name = '%s<%s>' % (name or account_name,account) 40 | self.account = account 41 | self.auth_code = auth_code 42 | 43 | #将SERVER_LIB和config更新入对象属性 44 | self.__dict__.update(SERVER_LIB.get(server_name,{})) 45 | self.__dict__.update(config) 46 | 47 | #初始化邮箱服务 48 | st_SMTP = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP 49 | st_IMAP = imaplib.IMAP4_SSL if self.use_ssl else imaplib.IMAP4 50 | if self.smtp_port: 51 | self.st_SMTP = lambda: st_SMTP(self.smtp,self.smtp_port) 52 | else: 53 | self.st_SMTP = lambda: st_SMTP(self.smtp) 54 | if self.imap_port: 55 | self.st_IMAP = lambda: st_IMAP(self.imap, self.imap_port) 56 | else: 57 | self.st_IMAP = lambda: st_IMAP(self.imap) 58 | self.SMTP = lambda: SMTP(self) 59 | self.IMAP = lambda: IMAP(self) 60 | 61 | class SMTP(object): 62 | 63 | def __init__(self,mail_agent): 64 | self.name,self.account = mail_agent.name,mail_agent.account 65 | self.server = mail_agent.st_SMTP() 66 | try: 67 | self.server.login(mail_agent.account, mail_agent.auth_code) 68 | except: 69 | self.close() 70 | raise 71 | 72 | def close(self): 73 | try: 74 | return self.server.quit() 75 | except: 76 | pass 77 | 78 | def __enter__(self): 79 | return self 80 | 81 | def __exit__(self, exc_type, exc_value, traceback): 82 | self.close() 83 | 84 | def send(self, to_addr, html='', subject='', to_name='', png_content=''): 85 | subject = subject or 'No subject' 86 | to_name = to_name or "; ".join(to_addr) 87 | html = '%s' % html 88 | if html: 89 | html = html.replace('{{png}}', '') 90 | 91 | msg = MIMEMultipart() 92 | msg.attach(MIMEText(html, 'html', 'utf8')) 93 | msg['From'] = self.name 94 | msg['To'] = '%s <%s>' % (to_name, to_addr) 95 | msg['Subject'] = subject 96 | 97 | if png_content: 98 | m = MIMEBase('image', 'png', filename='x.png') 99 | m.add_header('Content-Disposition', 'attachment', filename='x.png') 100 | m.add_header('Content-ID', '<0>') 101 | m.add_header('X-Attachment-Id', '0') 102 | m.set_payload(png_content) 103 | encode_base64(m) 104 | msg.attach(m) 105 | 106 | self.server.sendmail(self.account, to_addr, msg.as_string()) 107 | 108 | class IMAP(object): 109 | 110 | def __init__(self, mail_agent): 111 | 112 | self.name, self.account = mail_agent.name, mail_agent.account 113 | self.conn = mail_agent.st_IMAP() 114 | try: 115 | self.conn.login(mail_agent.account, mail_agent.auth_code) 116 | self.conn.select('INBOX') 117 | except: 118 | self.close() 119 | raise 120 | 121 | def __enter__(self): 122 | return self 123 | 124 | def __exit__(self, exc_type, exc_value, traceback): 125 | self.close() 126 | 127 | def close(self): 128 | try: 129 | return self.conn.close() 130 | except: 131 | pass 132 | 133 | def getSubject(self, i): 134 | conn = self.conn 135 | id_list = conn.search(None, '(UNSEEN)')[1][0].split() 136 | try: 137 | email_id = id_list[i] 138 | except IndexError: 139 | return None, -1 140 | data = conn.fetch(email_id, 'BODY.PEEK[HEADER.FIELDS (SUBJECT)]')[1] 141 | 142 | msg = message_from_bytes(data[0][1]) 143 | s, encoding = decode_header(msg['Subject'])[0] 144 | subject = s if type(s) is str else s.decode(encoding or 'utf-8') 145 | return subject 146 | 147 | if __name__ == '__main__': 148 | from config import settings 149 | 150 | if settings.MAIL_ACCOUNT and settings.MAIL_AUTH_CODE: 151 | mail_agent = MailAgent(settings.MAIL_ACCOUNT, settings.MAIL_AUTH_CODE) 152 | 153 | with mail_agent.SMTP() as s: 154 | s.send(settings.MAIL_RECEIPIENTS, '测试邮件发送', '测试') 155 | print("发送成功") 156 | 157 | time.sleep(5) 158 | 159 | with mail_agent.IMAP() as i: 160 | subject = i.getSubject(-1) 161 | print('最新邮件: ' + subject) 162 | print('接收成功') -------------------------------------------------------------------------------- /config/__pycache__/settings.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/config/__pycache__/settings.cpython-36.pyc -------------------------------------------------------------------------------- /config/__pycache__/settings.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/config/__pycache__/settings.cpython-37.pyc -------------------------------------------------------------------------------- /config/db_properties.py: -------------------------------------------------------------------------------- 1 | DBHOST = "localhost" 2 | DBPORT = 3306 3 | DBUSER = "root" 4 | DBPWD = "" 5 | DBNAME = "chain" 6 | DBCHAR = "utf8" -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import sys 3 | import logging 4 | 5 | # 日志配置文件 6 | from copy import deepcopy 7 | 8 | # 此处填写APIKEY 9 | ACCESS_KEY = " " 10 | SECRET_KEY = " " 11 | 12 | # 用户ID(火币网个人中心可看) 13 | ACCOUNT_ID = None 14 | 15 | _filename = None 16 | _format = "%(asctime)-15s [%(levelname)s] [%(name)s] %(message)s" 17 | _datefmt = "%Y/%m/%d %H:%M:%S" 18 | _level = logging.INFO 19 | 20 | if _filename: 21 | handlers = [logging.StreamHandler(sys.stdout), logging.FileHandler(_filename)] 22 | else: 23 | handlers = [logging.StreamHandler(sys.stdout)] 24 | 25 | logging.basicConfig(format=_format, datefmt=_datefmt, level=_level, handlers=handlers) 26 | 27 | # 目前查询的$symbol均为:<货币>USTD (可选:{ USDT, BTC }) 28 | # 时间间隔均为1min(可选:{ 1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year }) 29 | SYMBOL = "USDT" 30 | PERIOD = "1min" 31 | 32 | # 计算n分钟内的涨跌幅 33 | N_MINUTES_STATE = 10 34 | 35 | # 当达到以下值,发出警报通知,触发下一步动作 36 | PRICE_ALERT_INCREASE_POINT = 1.25 37 | PRICE_ALERT_DECREASE_POINT = -1.25 38 | 39 | # 买入和卖出策略目前写死在代码里 40 | # 卖出策略 - 达到PRICE_ALERT_DECREASE_POINT时 41 | # 以当时的成交价格卖出,如果这种货币为降幅最小两个之一,则等待不卖出 42 | 43 | # 买入策略 - 达到PRICE_ALERT_INCREASE_POINT时 44 | # 以60%和40%的比例分别买涨价最高/次高的 45 | 46 | # 设定参考货币的类型,权重及初始默认拥有的货币数量 47 | COINS = { 48 | "BTC": { 49 | "WEIGHT": 1, 50 | "AMOUNT": 0 51 | }, 52 | "BCH": { 53 | "WEIGHT": 1, 54 | "AMOUNT": 0 55 | }, 56 | "ETH": { 57 | "WEIGHT": 1, 58 | "AMOUNT": 0 59 | }, 60 | "LTC": { 61 | "WEIGHT": 1, 62 | "AMOUNT": 0 63 | }, 64 | "XRP": { 65 | "WEIGHT": 1, 66 | "AMOUNT": 0 67 | }, 68 | "DASH": { 69 | "WEIGHT": 1, 70 | "AMOUNT": 0 71 | }, 72 | "ETC": { 73 | "WEIGHT": 1, 74 | "AMOUNT": 0 75 | }, 76 | "EOS": { 77 | "WEIGHT": 1, 78 | "AMOUNT": 0 79 | }, 80 | "OMG": { 81 | "WEIGHT": 1, 82 | "AMOUNT": 0 83 | } 84 | } 85 | 86 | # 用户当前USDT账户余额 87 | USDT_CURRENCY = 0 88 | 89 | # 备份原始COIN/CURRENCY数据,作对比用 90 | ORIGINAL_USDT_CURRENCY = USDT_CURRENCY 91 | ORIGINAL_COINS = deepcopy(COINS) 92 | ORIGINAL_WEALTH = None 93 | 94 | # 将从火币上获取到的交易信息保存到数据库(mongodb) 95 | DATABASE_RECORD = False 96 | 97 | # 配置以下项目以初始化数据库 98 | DATABASE_SERVER_ADDRESS = None 99 | DATABASE_SERVER_PORT = 27017 100 | DATABASE_NAME = "huobi_exchange" 101 | 102 | # 这里的模拟起止仅用于数据库交易模拟分析 103 | SIMULATE_START = datetime.datetime(2017, 12, 1, 0, 0, 0) 104 | SIMULATE_END = datetime.datetime.now() 105 | 106 | # 如果数据库有用户名/密码,则定义如下 107 | DATABASE_SERVER_USERNAME = None 108 | DATABASE_SERVER_PASSWORD = None 109 | 110 | # 邮件通知,配置SMTP,获取其AuthCode即可发送邮件 111 | MAIL_ACCOUNT = "710777350@qq.com" 112 | MAIL_AUTH_CODE = "famqcgmaidkkbdgc" 113 | MAIL_RECEIPIENTS = ["ahbbhywmd@163.com"] 114 | 115 | # API 请求地址 116 | MARKET_URL = "https://api.huobipro.com" 117 | TRADE_URL = "https://api.huobipro.com" -------------------------------------------------------------------------------- /dao/dao.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | import gzip 3 | import uuid 4 | import json 5 | import time 6 | import StringIO 7 | import threading 8 | from websocket import create_connection 9 | from dao.mysql_connection import Mysql 10 | 11 | 12 | sql = """INSERT INTO {} (id, open, close, low, high, amount, vol, count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE open=%s, close=%s, low=%s, high=%s, amount=%s, vol=%s, count=%s""" 13 | 14 | def save(item, symbol): 15 | conn = Mysql() 16 | print sql.format(symbol) 17 | conn.insertOne(sql.format(symbol), 18 | [item['id'], item['open'], item['close'], item['low'], item['high'], item['amount'],item['vol'], item['count'], 19 | item['open'], item['close'], item['low'], item['high'], item['amount'], item['vol'], item['count']]) 20 | 21 | def createConnection(tradeId, symbol): 22 | print '{}, {} create connection'.format(tradeId, symbol) 23 | try: 24 | ws = create_connection("wss://api.huobipro.com/ws", sslopt={"cert_reqs": ssl.CERT_NONE}) 25 | tradeStr = '{{"id": "{}", "sub": "market.{}.kline.1min"}}' 26 | ws.send(tradeStr.format(tradeId, symbol)) 27 | return ws 28 | except Exception, e: 29 | time.sleep(10) 30 | return createConnection(tradeId, symbol) 31 | 32 | def main(symbol): 33 | tradeId = uuid.uuid1() 34 | ws = createConnection(tradeId, symbol) 35 | while True: 36 | try: 37 | compressData = ws.recv() 38 | compressedStream = StringIO.StringIO(compressData) 39 | gzipFile = gzip.GzipFile(fileobj=compressedStream) 40 | result = gzipFile.read() 41 | if result[:7] == '{"ping"': 42 | ts = result[8:21] 43 | pong = '{"pong":' + ts + '}' 44 | ws.send(pong) 45 | else: 46 | print(result) 47 | item = json.loads(result) 48 | if 'tick' in item: 49 | item = item['tick'] 50 | save(item, symbol) 51 | 52 | except Exception as e: 53 | print('exception: {}'.format(e)) 54 | ws = createConnection(tradeId, symbol) 55 | 56 | def history(symbol): 57 | tradeId = uuid.uuid1() 58 | ws = createConnection() 59 | tradeStr = '{{"id": "{}", "req": "market.{}.kline.1min", "from": {}, "to": {}}}' 60 | start = 1511845180 61 | ws.send(tradeStr.format(tradeId, symbol, 1511845180, 1511845180+60*240)) 62 | index = 1 63 | while True: 64 | try: 65 | compressData = ws.recv() 66 | compressedStream = StringIO.StringIO(compressData) 67 | gzipFile = gzip.GzipFile(fileobj=compressedStream) 68 | result = gzipFile.read() 69 | if result[:7] == '{"ping"': 70 | ts = result[8:21] 71 | pong = '{"pong":' + ts + '}' 72 | ws.send(pong) 73 | # ws.send(tradeStr.format(tradeId, symbol, start+60*240*index+1, start+60*240*(index+1))) 74 | else: 75 | item = json.loads(result) 76 | conn.insertOne(sql.format(symbol), [item['id'], item['open'], item['close'], item['low'], item['high'], item['amount'], item['vol'], item['count'], 77 | item['open'], item['close'], item['low'], item['high'], item['amount'], item['vol'], item['count']]) 78 | # ws.send(tradeStr.format(tradeId, symbol, start+60*240*index+1, start+60*240*(index+1))) 79 | index += 1 80 | time.sleep(.5) 81 | except Exception as e: 82 | print('exception: {}'.format(e)) 83 | ws = createConnection() 84 | 85 | if __name__ == '__main__': 86 | thread_list = [] 87 | symbols = ['btcusdt','bchusdt','ethusdt','etcusdt','ltcusdt','eosusdt','xrpusdt','omgusdt','dashusdt','zecusdt','adausdt','ctxcusdt','actusdt','btmusdt','btsusdt','ontusdt','iostusdt','htusdt','trxusdt','dtausdt','neousdt','qtumusdt','elausdt','venusdt','thetausdt','sntusdt','zilusdt','xemusdt','smtusdt','nasusdt','ruffusdt','hsrusdt','letusdt','mdsusdt','storjusdt','elfusdt','itcusdt','cvcusdt','gntusdt'] 88 | 89 | for symbol in symbols: 90 | thread_list.append(threading.Thread(target=main, args=(symbol,))) 91 | for t in thread_list: 92 | t.start() 93 | for t in thread_list: 94 | t.join() -------------------------------------------------------------------------------- /dao/mysql_connection.py: -------------------------------------------------------------------------------- 1 | """ 2 | @author: Salmon 3 | 1、执行带参数的SQL时,请先用sql语句指定需要输入的条件列表,然后再用tuple/list进行条件批配 4 | 2、在格式SQL中不需要使用引号指定数据类型,系统会根据输入参数自动识别 5 | 3、在输入的值中不需要使用转意函数,系统会自动处理 6 | """ 7 | 8 | import MySQLdb 9 | import sys 10 | sys.path.append('../') 11 | from MySQLdb.cursors import DictCursor 12 | from DBUtils.PooledDB import PooledDB 13 | # from PooledDB import PooledDB 14 | import config.db_properties 15 | 16 | """ 17 | Config是一些数据库的配置文件 18 | """ 19 | 20 | class Mysql(object): 21 | """ 22 | MYSQL数据库对象,负责产生数据库连接 , 此类中的连接采用连接池实现获取连接对象:conn = Mysql.getConn() 23 | 释放连接对象;conn.close()或del conn 24 | """ 25 | # 连接池对象 26 | __pool = None 27 | 28 | def __init__(self): 29 | # 数据库构造函数,从连接池中取出连接,并生成操作游标 30 | self._conn = Mysql.__getConn() 31 | self._cursor = self._conn.cursor() 32 | 33 | @staticmethod 34 | def __getConn(): 35 | """ 36 | @summary: 静态方法,从连接池中取出连接 37 | @return MySQLdb.connection 38 | """ 39 | if Mysql.__pool is None: 40 | Mysql.__pool = PooledDB(creator=MySQLdb, mincached=1, maxcached=20, 41 | host=Config.DBHOST, 42 | port=Config.DBPORT, 43 | user=Config.DBUSER, 44 | passwd=Config.DBPWD, 45 | charset =Config.DBCHAR, 46 | db=Config.DBNAME, 47 | use_unicode=False, 48 | cursorclass=DictCursor, 49 | setsession=['SET AUTOCOMMIT = 1']) 50 | return Mysql.__pool.connection() 51 | 52 | def getAll(self, sql, param=None): 53 | """ 54 | @summary: 执行查询,并取出所有结果集 55 | @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来 56 | @param param: 可选参数,条件列表值(元组/列表) 57 | @return: result list(字典对象)/boolean 查询到的结果集 58 | """ 59 | if param is None: 60 | count = self._cursor.execute(sql) 61 | else: 62 | count = self._cursor.execute(sql, param) 63 | if count > 0: 64 | result = self._cursor.fetchall() 65 | else: 66 | result = False 67 | return result 68 | 69 | def getOne(self, sql, param=None): 70 | """ 71 | @summary: 执行查询,并取出第一条 72 | @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来 73 | @param param: 可选参数,条件列表值(元组/列表) 74 | @return: result list/boolean 查询到的结果集 75 | """ 76 | if param is None: 77 | count = self._cursor.execute(sql) 78 | else: 79 | count = self._cursor.execute(sql, param) 80 | if count > 0: 81 | result = self._cursor.fetchone() 82 | else: 83 | result = False 84 | return result 85 | 86 | def getMany(self, sql, num, param=None): 87 | """ 88 | @summary: 执行查询,并取出num条结果 89 | @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来 90 | @param num:取得的结果条数 91 | @param param: 可选参数,条件列表值(元组/列表) 92 | @return: result list/boolean 查询到的结果集 93 | """ 94 | if param is None: 95 | count = self._cursor.execute(sql) 96 | else: 97 | count = self._cursor.execute(sql, param) 98 | if count > 0: 99 | result = self._cursor.fetchmany(num) 100 | else: 101 | result = False 102 | return result 103 | 104 | def insertOne(self, sql, value): 105 | """ 106 | @summary: 向数据表插入一条记录 107 | @param sql:要插入的SQL格式 108 | @param value:要插入的记录数据tuple/list 109 | @return: insertId 受影响的行数 110 | """ 111 | self._cursor.execute(sql, value) 112 | return self.__getInsertId() 113 | 114 | def insertMany(self, sql, values): 115 | """ 116 | @summary: 向数据表插入多条记录 117 | @param sql:要插入的SQL格式 118 | @param values:要插入的记录数据tuple(tuple)/list[list] 119 | @return: count 受影响的行数 120 | """ 121 | count = self._cursor.executemany(sql, values) 122 | return count 123 | 124 | def __getInsertId(self): 125 | """ 126 | 获取当前连接最后一次插入操作生成的id,如果没有则为0 127 | """ 128 | self._cursor.execute("SELECT @@IDENTITY AS id") 129 | result = self._cursor.fetchall() 130 | return result[0]['id'] 131 | 132 | def __query(self, sql, param=None): 133 | if param is None: 134 | count = self._cursor.execute(sql) 135 | else: 136 | count = self._cursor.execute(sql, param) 137 | return count 138 | 139 | def update(self, sql, param=None): 140 | """ 141 | @summary: 更新数据表记录 142 | @param sql: SQL格式及条件,使用(%s,%s) 143 | @param param: 要更新的 值 tuple/list 144 | @return: count 受影响的行数 145 | """ 146 | return self.__query(sql, param) 147 | 148 | def delete(self, sql, param=None): 149 | """ 150 | @summary: 删除数据表记录 151 | @param sql: SQL格式及条件,使用(%s,%s) 152 | @param param: 要删除的条件 值 tuple/list 153 | @return: count 受影响的行数 154 | """ 155 | return self.__query(sql, param) 156 | 157 | def begin(self): 158 | """ 159 | @summary: 开启事务 160 | """ 161 | self._conn.autocommit(0) 162 | 163 | def end(self, option='commit'): 164 | """ 165 | @summary: 结束事务 166 | """ 167 | if option == 'commit': 168 | self._conn.commit() 169 | else: 170 | self._conn.rollback() 171 | 172 | def dispose(self, isEnd=1): 173 | """ 174 | @summary: 释放连接池资源 175 | """ 176 | if isEnd == 1: 177 | self.end('commit') 178 | else: 179 | self.end('rollback'); 180 | self._cursor.close() 181 | self._conn.close() 182 | 183 | 184 | def create_table(self, symbol): 185 | s = """ 186 | CREATE TABLE `{}` ( 187 | `id` int(11) NOT NULL, 188 | `open` varchar(100) DEFAULT NULL, 189 | `close` varchar(100) DEFAULT NULL, 190 | `low` varchar(100) DEFAULT NULL, 191 | `high` varchar(100) DEFAULT NULL, 192 | `amount` varchar(100) DEFAULT NULL, 193 | `vol` varchar(100) DEFAULT NULL, 194 | `count` varchar(100) DEFAULT NULL, 195 | PRIMARY KEY (`id`) 196 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 197 | """ 198 | self._cursor.execute(s.format(symbol)) 199 | 200 | 201 | if __name__ == "__main__": 202 | symbols = ['btcusdt','bchusdt','ethusdt','etcusdt','ltcusdt','eosusdt','xrpusdt','omgusdt','dashusdt','zecusdt','adausdt','ctxcusdt','actusdt','btmusdt','btsusdt','ontusdt','iostusdt','htusdt','trxusdt','dtausdt','neousdt','qtumusdt','elausdt','venusdt','thetausdt','sntusdt','zilusdt','xemusdt','smtusdt','nasusdt','ruffusdt','hsrusdt','letusdt','mdsusdt','storjusdt','elfusdt','itcusdt','cvcusdt','gntusdt'] 203 | db = Mysql() 204 | for tableName in symbols: 205 | db.create_table(tableName) -------------------------------------------------------------------------------- /exchangeapi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/__init__.py -------------------------------------------------------------------------------- /exchangeapi/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/__init__.pyc -------------------------------------------------------------------------------- /exchangeapi/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /exchangeapi/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /exchangeapi/huobi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/huobi/__init__.py -------------------------------------------------------------------------------- /exchangeapi/huobi/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/huobi/__init__.pyc -------------------------------------------------------------------------------- /exchangeapi/huobi/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/huobi/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /exchangeapi/huobi/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/huobi/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /exchangeapi/huobi/__pycache__/rest_api.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/huobi/__pycache__/rest_api.cpython-36.pyc -------------------------------------------------------------------------------- /exchangeapi/huobi/__pycache__/rest_api.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/exchangeapi/huobi/__pycache__/rest_api.cpython-37.pyc -------------------------------------------------------------------------------- /exchangeapi/huobi/rest_api.py: -------------------------------------------------------------------------------- 1 | from util.api_key_util import * 2 | from util.http_request_util import * 3 | 4 | # 获取KLine 5 | def get_kline(symbol, period, size=150): 6 | """ 7 | :param symbol 8 | :param period: 可选值:{1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year } 9 | :param size: 可选值: [1,2000] 10 | :return: 11 | """ 12 | params = {'symbol': symbol, 13 | 'period': period, 14 | 'size': size} 15 | 16 | url = MARKET_URL + '/market/history/kline' 17 | return http_get_request(url, params) 18 | 19 | 20 | # 获取marketdepth 21 | def get_depth(symbol, type): 22 | """ 23 | :param symbol 24 | :param type: 可选值:{ percent10, step0, step1, step2, step3, step4, step5 } 25 | :return: 26 | """ 27 | params = {'symbol': symbol, 28 | 'type': type} 29 | 30 | url = MARKET_URL + '/market/depth' 31 | return http_get_request(url, params) 32 | 33 | 34 | # 获取tradedetail 35 | def get_trade(symbol): 36 | """ 37 | :param symbol 38 | :return: 39 | """ 40 | params = {'symbol': symbol} 41 | 42 | url = MARKET_URL + '/market/trade' 43 | return http_get_request(url, params) 44 | 45 | 46 | # 获取merge ticker 47 | def get_ticker(symbol): 48 | """ 49 | :param symbol: 50 | :return: 51 | """ 52 | params = {'symbol': symbol} 53 | 54 | url = MARKET_URL + '/market/detail/merged' 55 | return http_get_request(url, params) 56 | 57 | 58 | # 获取 Market Detail 24小时成交量数据 59 | def get_detail(symbol): 60 | """ 61 | :param symbol 62 | :return: 63 | """ 64 | params = {'symbol': symbol} 65 | 66 | url = MARKET_URL + '/market/detail' 67 | return http_get_request(url, params) 68 | 69 | # 获取 支持的交易对 70 | def get_symbols(long_polling=None): 71 | """ 72 | """ 73 | params = {} 74 | if long_polling: 75 | params['long-polling'] = long_polling 76 | path = '/v1/common/symbols' 77 | return api_key_get(params, path) 78 | 79 | ''' 80 | Trade/Account API 81 | ''' 82 | 83 | 84 | def get_accounts(): 85 | """ 86 | :return: 87 | """ 88 | path = "/v1/account/accounts" 89 | params = {} 90 | return api_key_get(params, path) 91 | 92 | ACCOUNT_ID = 0 93 | # 获取当前账户资产 94 | def get_balance(acct_id=None): 95 | """ 96 | :param acct_id 97 | :return: 98 | """ 99 | global ACCOUNT_ID 100 | 101 | if not acct_id: 102 | accounts = get_accounts() 103 | acct_id = accounts['data'][0]['id']; 104 | 105 | url = "/v1/account/accounts/{0}/balance".format(acct_id) 106 | params = {"account-id": acct_id} 107 | return api_key_get(params, url) 108 | 109 | 110 | # 下单 111 | 112 | # 创建并执行订单 113 | def send_order(amount, source, symbol, _type, price=0): 114 | """ 115 | :param amount: 116 | :param source: 如果使用借贷资产交易,请在下单接口,请求参数source中填写'margin-api' 117 | :param symbol: 118 | :param _type: 可选值 {buy-market:市价买, sell-market:市价卖, buy-limit:限价买, sell-limit:限价卖} 119 | :param price: 120 | :return: 121 | """ 122 | try: 123 | accounts = get_accounts() 124 | acct_id = accounts['data'][0]['id'] 125 | except BaseException as e: 126 | print ('get acct_id error.%s' % e) 127 | acct_id = ACCOUNT_ID 128 | 129 | params = {"account-id": acct_id, 130 | "amount": amount, 131 | "symbol": symbol, 132 | "type": _type, 133 | "source": source} 134 | if price: 135 | params["price"] = price 136 | 137 | url = '/v1/order/orders/place' 138 | return api_key_post(params, url) 139 | 140 | 141 | # 撤销订单 142 | def cancel_order(order_id): 143 | """ 144 | 145 | :param order_id: 146 | :return: 147 | """ 148 | params = {} 149 | url = "/v1/order/orders/{0}/submitcancel".format(order_id) 150 | return api_key_post(params, url) 151 | 152 | 153 | # 查询某个订单 154 | def order_info(order_id): 155 | """ 156 | 157 | :param order_id: 158 | :return: 159 | """ 160 | params = {} 161 | url = "/v1/order/orders/{0}".format(order_id) 162 | return api_key_get(params, url) 163 | 164 | 165 | # 查询某个订单的成交明细 166 | def order_matchresults(order_id): 167 | """ 168 | 169 | :param order_id: 170 | :return: 171 | """ 172 | params = {} 173 | url = "/v1/order/orders/{0}/matchresults".format(order_id) 174 | return api_key_get(params, url) 175 | 176 | 177 | # 查询当前委托、历史委托 178 | def orders_list(symbol, states, types=None, start_date=None, end_date=None, _from=None, direct=None, size=None): 179 | """ 180 | 181 | :param symbol: 182 | :param states: 可选值 {pre-submitted 准备提交, submitted 已提交, partial-filled 部分成交, partial-canceled 部分成交撤销, filled 完全成交, canceled 已撤销} 183 | :param types: 可选值 {buy-market:市价买, sell-market:市价卖, buy-limit:限价买, sell-limit:限价卖} 184 | :param start_date: 185 | :param end_date: 186 | :param _from: 187 | :param direct: 可选值{prev 向前,next 向后} 188 | :param size: 189 | :return: 190 | """ 191 | params = {'symbol': symbol, 192 | 'states': states} 193 | 194 | if types: 195 | params[types] = types 196 | if start_date: 197 | params['start-date'] = start_date 198 | if end_date: 199 | params['end-date'] = end_date 200 | if _from: 201 | params['from'] = _from 202 | if direct: 203 | params['direct'] = direct 204 | if size: 205 | params['size'] = size 206 | url = '/v1/order/orders' 207 | return api_key_get(params, url) 208 | 209 | 210 | # 查询当前成交、历史成交 211 | def orders_matchresults(symbol, types=None, start_date=None, end_date=None, _from=None, direct=None, size=None): 212 | """ 213 | 214 | :param symbol: 215 | :param types: 可选值 {buy-market:市价买, sell-market:市价卖, buy-limit:限价买, sell-limit:限价卖} 216 | :param start_date: 217 | :param end_date: 218 | :param _from: 219 | :param direct: 可选值{prev 向前,next 向后} 220 | :param size: 221 | :return: 222 | """ 223 | params = {'symbol': symbol} 224 | 225 | if types: 226 | params[types] = types 227 | if start_date: 228 | params['start-date'] = start_date 229 | if end_date: 230 | params['end-date'] = end_date 231 | if _from: 232 | params['from'] = _from 233 | if direct: 234 | params['direct'] = direct 235 | if size: 236 | params['size'] = size 237 | url = '/v1/order/matchresults' 238 | return api_key_get(params, url) 239 | 240 | 241 | 242 | # 申请提现虚拟币 243 | def withdraw(address, amount, currency, fee=0, addr_tag=""): 244 | """ 245 | :param address_id: 246 | :param amount: 247 | :param currency:btc, ltc, bcc, eth, etc ...(火币Pro支持的币种) 248 | :param fee: 249 | :param addr-tag: 250 | :return: { 251 | "status": "ok", 252 | "data": 700 253 | } 254 | """ 255 | params = {'address': address, 256 | 'amount': amount, 257 | "currency": currency, 258 | "fee": fee, 259 | "addr-tag": addr_tag} 260 | url = '/v1/dw/withdraw/api/create' 261 | 262 | return api_key_post(params, url) 263 | 264 | # 申请取消提现虚拟币 265 | def cancel_withdraw(address_id): 266 | """ 267 | :param address_id: 268 | :return: { 269 | "status": "ok", 270 | "data": 700 271 | } 272 | """ 273 | params = {} 274 | url = '/v1/dw/withdraw-virtual/{0}/cancel'.format(address_id) 275 | 276 | return api_key_post(params, url) 277 | 278 | 279 | ''' 280 | 借贷API 281 | ''' 282 | 283 | # 创建并执行借贷订单 284 | 285 | 286 | def send_margin_order(amount, source, symbol, _type, price=0): 287 | """ 288 | :param amount: 289 | :param source: 'margin-api' 290 | :param symbol: 291 | :param _type: 可选值 {buy-market:市价买, sell-market:市价卖, buy-limit:限价买, sell-limit:限价卖} 292 | :param price: 293 | :return: 294 | """ 295 | try: 296 | accounts = get_accounts() 297 | acct_id = accounts['data'][0]['id'] 298 | except BaseException as e: 299 | print ('get acct_id error.%s' % e) 300 | acct_id = ACCOUNT_ID 301 | 302 | params = {"account-id": acct_id, 303 | "amount": amount, 304 | "symbol": symbol, 305 | "type": _type, 306 | "source": 'margin-api'} 307 | if price: 308 | params["price"] = price 309 | 310 | url = '/v1/order/orders/place' 311 | return api_key_post(params, url) 312 | 313 | # 现货账户划入至借贷账户 314 | 315 | 316 | def exchange_to_margin(symbol, currency, amount): 317 | """ 318 | :param amount: 319 | :param currency: 320 | :param symbol: 321 | :return: 322 | """ 323 | params = {"symbol": symbol, 324 | "currency": currency, 325 | "amount": amount} 326 | 327 | url = "/v1/dw/transfer-in/margin" 328 | return api_key_post(params, url) 329 | 330 | # 借贷账户划出至现货账户 331 | 332 | 333 | def margin_to_exchange(symbol, currency, amount): 334 | """ 335 | :param amount: 336 | :param currency: 337 | :param symbol: 338 | :return: 339 | """ 340 | params = {"symbol": symbol, 341 | "currency": currency, 342 | "amount": amount} 343 | 344 | url = "/v1/dw/transfer-out/margin" 345 | return api_key_post(params, url) 346 | 347 | # 申请借贷 348 | def get_margin(symbol, currency, amount): 349 | """ 350 | :param amount: 351 | :param currency: 352 | :param symbol: 353 | :return: 354 | """ 355 | params = {"symbol": symbol, 356 | "currency": currency, 357 | "amount": amount} 358 | url = "/v1/margin/orders" 359 | return api_key_post(params, url) 360 | 361 | # 归还借贷 362 | def repay_margin(order_id, amount): 363 | """ 364 | :param order_id: 365 | :param amount: 366 | :return: 367 | """ 368 | params = {"order-id": order_id, 369 | "amount": amount} 370 | url = "/v1/margin/orders/{0}/repay".format(order_id) 371 | return api_key_post(params, url) 372 | 373 | # 借贷订单 374 | def loan_orders(symbol, currency, start_date="", end_date="", start="", direct="", size=""): 375 | """ 376 | :param symbol: 377 | :param currency: 378 | :param direct: prev 向前,next 向后 379 | :return: 380 | """ 381 | params = {"symbol": symbol, 382 | "currency": currency} 383 | if start_date: 384 | params["start-date"] = start_date 385 | if end_date: 386 | params["end-date"] = end_date 387 | if start: 388 | params["from"] = start 389 | if direct and direct in ["prev", "next"]: 390 | params["direct"] = direct 391 | if size: 392 | params["size"] = size 393 | url = "/v1/margin/loan-orders" 394 | return api_key_get(params, url) 395 | 396 | 397 | # 借贷账户详情,支持查询单个币种 398 | def margin_balance(symbol): 399 | """ 400 | :param symbol: 401 | :return: 402 | """ 403 | params = {} 404 | url = "/v1/margin/accounts/balance" 405 | if symbol: 406 | params['symbol'] = symbol 407 | 408 | return api_key_get(params, url) -------------------------------------------------------------------------------- /exchangeapi/huobi/websocket.py: -------------------------------------------------------------------------------- 1 | from config import settings 2 | from service import k_line_service 3 | import gzip 4 | import json 5 | import logging 6 | import websocket 7 | 8 | logger = logging.getLogger("service") 9 | 10 | ### 11 | # 通过websocket与火币网实现通信 12 | ### 13 | 14 | def save_data(msg): 15 | if settings.DATABASE_RECORD and mongodb: 16 | try: 17 | collection = mongodb.get_collection(msg['ch'].replace('.', '_')) 18 | collection.insert_one(msg) 19 | except Exception as exp: 20 | logger.error("无法保存到数据库:" + str(exp)) 21 | 22 | 23 | def send_message(ws, msg_dict): 24 | data = json.dumps(msg_dict).encode() 25 | logger.debug("发送消息:" + str(msg_dict)) 26 | ws.send(data) 27 | 28 | 29 | def on_message(ws, message): 30 | unzipped_data = gzip.decompress(message).decode() 31 | msg_dict = json.loads(unzipped_data) 32 | if 'ping' in msg_dict: 33 | data = { 34 | "pong": msg_dict['ping'] 35 | } 36 | logger.debug("收到ping消息: " + str(msg_dict)) 37 | send_message(ws, data) 38 | elif 'subbed' in msg_dict: 39 | logger.debug("收到订阅状态消息:" + str(msg_dict)) 40 | else: 41 | save_data(msg_dict) 42 | logger.debug("收到消息: " + str(msg_dict)) 43 | kline_handler.handle_raw_message(msg_dict) 44 | 45 | 46 | def on_error(ws, error): 47 | error = gzip.decompress(error).decode() 48 | logger.error(str(error)) 49 | 50 | 51 | def on_close(ws): 52 | logger.info("已断开连接") 53 | 54 | 55 | def on_open(ws): 56 | # 遍历settings中的货币对象 57 | for currency in settings.COINS.keys(): 58 | subscribe = "market.{0}{1}.kline.{2}".format(currency, settings.SYMBOL, settings.PERIOD).lower() 59 | data = { 60 | "sub": subscribe, 61 | "id": currency 62 | } 63 | # 订阅K线图 64 | send_message(ws, data) 65 | 66 | 67 | def start(): 68 | ws = websocket.WebSocketApp( 69 | "wss://api.huobipro.com/ws", 70 | on_open=on_open, 71 | on_message=on_message, 72 | on_error=on_error, 73 | on_close=on_close 74 | ) 75 | ws.run_forever() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | panadas 3 | websocket 4 | websocket-client 5 | talib 6 | 7 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import time 2 | import talib as ta 3 | import numpy as np 4 | import pandas as pd 5 | import json 6 | from strategy import trend 7 | from exchangeapi.huobi import rest_api 8 | from common.email import email_handler 9 | 10 | if __name__ == "__main__": 11 | 12 | while(True): 13 | result = rest_api.get_kline('ethusdt',"60min",100) 14 | # print(result.get('data')) 15 | df = pd.DataFrame(result.get('data'), columns=['id', 'open', 'close', 'low', 'high', 'amount', 'vol', 'count']).sort_values(by = 'id',axis = 0,ascending = True) 16 | # print(df) 17 | 18 | ret = trend.MACross(df, 'close', 10, 25); 19 | print(ret) 20 | if ret : 21 | email_handler.send_mail("huobi 十五分钟均线交叉", "火币十五分钟均线交叉") 22 | time.sleep(600) -------------------------------------------------------------------------------- /runbts.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | import talib as ta 4 | import numpy as np 5 | import pandas as pd 6 | import json 7 | from strategy import trend 8 | from exchangeapi.huobi import rest_api 9 | from common.email import email_handler 10 | 11 | class huobiThread (threading.Thread): 12 | def __init__(self, threadID, symbol,period,lines,shortPeriod,longPeriod,delay=600): 13 | threading.Thread.__init__(self) 14 | self.threadID = threadID 15 | self.symbol = symbol 16 | self.period = period 17 | self.lines = lines 18 | self.shortPeriod = shortPeriod 19 | self.longPeriod = longPeriod 20 | self.delay = delay 21 | 22 | def run(self): 23 | print ("开始线程:" + self.threadID) 24 | monitor(self.threadID, self.symbol,self.period,self.lines,self.shortPeriod,self.longPeriod,self.delay) 25 | print ("退出线程:" + self.threadID) 26 | 27 | 28 | def monitor(name,symbol,period,lines,shortPeriod,longPeriod,delay=600): 29 | # 作为多线程的执行方法,对选定的交易对按照一定的周期进行监控,并且邮件通知, 30 | # 因为火币对频繁调用有一定的限制所以不能一直的去读取 31 | flag = 0; # 1 向上穿越 2下穿越,0 初始value 32 | while(True): 33 | # 检测 btsusdt 交易对,用来为比特股账户的操作提供依据 34 | result = rest_api.get_kline(symbol,period,lines) 35 | # print(result.get('data')) 36 | if result is None: 37 | continue 38 | 39 | df = pd.DataFrame(result.get('data'), columns=['id', 'open', 'close', 'low', 'high', 'amount', 'vol', 'count']).sort_values(by = 'id',axis = 0,ascending = True) 40 | 41 | ret = trend.MACross(df, 'close', shortPeriod, longPeriod); 42 | str = "" 43 | if ret == '向上穿越' and flag != 1: 44 | str="火币交易对:"+symbol+", 周期:"+period+" 向上穿越" 45 | flag = 1 46 | 47 | elif ret == '向下穿越' and flag !=2: 48 | str="火币交易对:"+symbol+", 周期:"+period+" 向下穿越" 49 | flag = 2 50 | else: 51 | str="" 52 | print('火币交易对:%s,flag:%d' % (name, flag)) 53 | if not str == "": 54 | email_handler.send_mail(str, str) 55 | time.sleep(delay) 56 | 57 | 58 | if __name__ == "__main__": 59 | # 创建两个线程 60 | thread1 = huobiThread("btsusdt-15min", 'btsusdt', '15min', 200, 10, 30,60) 61 | thread2 = huobiThread("btsusdt-30min", 'btsusdt', '30min', 200, 7, 25,75) 62 | thread3 = huobiThread("btsusdt-60min", 'btsusdt', '60min', 200, 7, 25,85) 63 | thread4 = huobiThread("btsusdt-4h", 'btsusdt', '60min', 100, 7*4, 25*4,85) 64 | 65 | thread1.start() 66 | thread2.start() 67 | thread3.start() 68 | thread4.start() 69 | 70 | 71 | -------------------------------------------------------------------------------- /strategy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/strategy/__init__.py -------------------------------------------------------------------------------- /strategy/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/strategy/__init__.pyc -------------------------------------------------------------------------------- /strategy/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/strategy/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /strategy/__pycache__/average_cross.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/strategy/__pycache__/average_cross.cpython-37.pyc -------------------------------------------------------------------------------- /strategy/__pycache__/k_line_service.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/strategy/__pycache__/k_line_service.cpython-37.pyc -------------------------------------------------------------------------------- /strategy/__pycache__/rest_api_service.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/strategy/__pycache__/rest_api_service.cpython-37.pyc -------------------------------------------------------------------------------- /strategy/__pycache__/trend.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/strategy/__pycache__/trend.cpython-37.pyc -------------------------------------------------------------------------------- /strategy/__pycache__/websocket_subscribe_service.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/strategy/__pycache__/websocket_subscribe_service.cpython-37.pyc -------------------------------------------------------------------------------- /strategy/big_order_service.py: -------------------------------------------------------------------------------- 1 | import time 2 | import smtplib 3 | from contextlib import suppress 4 | from selenium import webdriver 5 | from selenium.webdriver.common.by import By 6 | from selenium.webdriver.support.ui import WebDriverWait 7 | from selenium.webdriver.support import expected_conditions as EC 8 | 9 | driver = webdriver.Chrome('C:\\Users\\themi\\Desktop\\chromedriver_win32\\chromedriver.exe') 10 | site=['https://www.huobipro.com/coin_coin/exchange/#eth_btc','https://www.huobipro.com/bch_btc/exchange/','https://www.huobipro.com/ltc_btc/exchange/'] 11 | toc=['ETH','BCH','LTC'] 12 | large=[] #!!![IMPORTANT] large[] needs to be cleared after one minutes in order to prevent failure of detection 13 | while True: 14 | iterator=0 15 | while iterator<3: 16 | driver.get(site[iterator]) 17 | time.sleep(2) 18 | try: 19 | assert "BTC" in driver.title 20 | except: 21 | pass 22 | #find the element containing valuable info 23 | time.sleep(2) 24 | try: 25 | textamt = WebDriverWait(driver, 25).until(EC.presence_of_element_located((By.XPATH,"//dl[@class='market_trades_amount']"))).text.splitlines() 26 | texttyp = WebDriverWait(driver, 25).until(EC.presence_of_element_located((By.XPATH, "//dl[@class='market_trades_type']"))).text.splitlines() 27 | texttim = WebDriverWait(driver, 25).until(EC.presence_of_element_located((By.XPATH, "//dl[@class='market_trades_time']"))).text.splitlines() 28 | textpri = WebDriverWait(driver, 25).until(EC.presence_of_element_located((By.XPATH, "//dl[@class='market_trades_price']"))).text.splitlines() 29 | except: 30 | print("卧槽,又有bug了") 31 | #moniter large amount of transaction 32 | size=len(textamt) 33 | i=1 34 | while i5: 40 | if placeholder not in large: 41 | large.append(placeholder) 42 | print('【大单交易检测到!】 在'+ placeholder2+' 有价值 ' + placeholder+ ' 比特币的' + toc[iterator] + placeholder1+ ' 现价为 '+ placeholder3) 43 | #content='large amount of transaction detected, the amount is ' + str(placeholder) 44 | #sending(content) 45 | i=i+1 46 | iterator=iterator+1 47 | driver.close() -------------------------------------------------------------------------------- /strategy/k_line_service.py: -------------------------------------------------------------------------------- 1 | import json 2 | import numpy 3 | import logging 4 | import time 5 | import datetime 6 | import http.client 7 | import statistics 8 | from collections import deque 9 | 10 | logger = logging.getLogger("service") 11 | 12 | # 记录每种虚拟货币的每笔交易 13 | transaction_dict = {} 14 | # 记录每种虚拟货币在1分钟内的分析数据,队列长度设置为10,即10分钟内的数据 15 | analyzed_queue_dict = {} 16 | # 记录10分钟内的"价格"变化,这里的"价格"目前直接用close的差计算,以后考虑通过交易量,标准差等计算 17 | price_change_dict = {} 18 | 19 | #获得usdt校验价格 20 | def get_usdt_sell_price(): 21 | conn = http.client.HTTPSConnection("api-otc.huobi.pro") 22 | conn.request("GET", "/v1/otc/trade/list/public?coinId=2&tradeType=0¤tPage=1&payWay=&country=") 23 | res = conn.getresponse() 24 | try: 25 | data = json.loads(res.read().decode("utf-8"))['data'] 26 | return statistics.mean(list(map(lambda x: x['price'], data))) 27 | except Exception as exp: 28 | logger.error("无法获得USDT交易卖出价:" + str(exp)) 29 | return "失败" 30 | 31 | #获得当前财富 32 | def get_current_wealth(): 33 | total = 0 34 | for key, value in settings.COINS.items(): 35 | total += transaction_dict["market.%susdt.kline.1min" % key.lower()][-1]['tick']['close'] * value['AMOUNT'] 36 | total += settings.USDT_CURRENCY 37 | return total 38 | 39 | #监听价格增长 40 | def trigger_price_increase_action(total_price_change): 41 | content = "价格上升:%.4f" % total_price_change 42 | logger.warning(content) 43 | 44 | # 60%购买当前平均10分钟内涨幅 * WEIGHT最高的, 40%购买第2高的 45 | if settings.USDT_CURRENCY > 0: 46 | sorted_prices = sorted(price_change_dict.items(), key=lambda x: x[1] * settings.COINS[x[0].split(".")[1].replace(settings.SYMBOL.lower(), "").upper()]['WEIGHT'], reverse=True) 47 | settings.COINS[sorted_prices[0][0].split(".")[1].replace(settings.SYMBOL.lower(), "").upper()]['AMOUNT'] = 0.998 * 0.6 * settings.USDT_CURRENCY / transaction_dict[sorted_prices[0][0]][-1]['tick']['close'] 48 | settings.COINS[sorted_prices[1][0].split(".")[1].replace(settings.SYMBOL.lower(), "").upper()]['AMOUNT'] = 0.998 * 0.4 * settings.USDT_CURRENCY / transaction_dict[sorted_prices[1][0]][-1]['tick']['close'] 49 | settings.USDT_CURRENCY = 0 50 | logger.info("总财富USDT: %.2f; 不折腾: %.2f; 时间: %s" % (get_current_wealth(), settings.ORIGINAL_WEALTH, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(transaction_dict['market.etcusdt.kline.1min'][-1]['ts'] / 1000)))) 51 | send_mail("火币网价格上升", content) 52 | 53 | #监听价格下降 54 | def trigger_price_decrease_action(total_price_change): 55 | content = "价格下降:%.4f" % total_price_change 56 | logger.warning(content) 57 | 58 | # 查找当前价格涨幅最高的两个,如果我们已购买的货币在这里面,则不卖出 59 | sorted_prices = sorted(price_change_dict.items(), key=lambda x: x[1] / settings.COINS[x[0].split(".")[1].replace(settings.SYMBOL.lower(), "").upper()]['WEIGHT'], reverse=True) 60 | skip_list = [sorted_prices[0][0], sorted_prices[1][0]] 61 | for key, value in price_change_dict.items(): 62 | if key in skip_list: continue 63 | coin = key.split(".")[1].replace(settings.SYMBOL.lower(), "").upper() 64 | # 全部卖掉 65 | if settings.COINS[coin]['AMOUNT'] > 0: 66 | settings.USDT_CURRENCY += 0.998 * settings.COINS[coin]['AMOUNT'] * transaction_dict[key][-1]['tick']['close'] 67 | settings.COINS[coin]['AMOUNT'] = 0 68 | logger.info("总财富USDT: %.2f; 不折腾: %.2f; 时间: %s" % (get_current_wealth(), settings.ORIGINAL_WEALTH, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(transaction_dict['market.etcusdt.kline.1min'][-1]['ts'] / 1000)))) 69 | send_mail("火币网价格下降", content) 70 | 71 | #预测并且监听消息 72 | def predict_and_notify(total_price_change): 73 | if total_price_change >= settings.PRICE_ALERT_INCREASE_POINT: 74 | trigger_price_increase_action(total_price_change) 75 | if total_price_change <= settings.PRICE_ALERT_DECREASE_POINT: 76 | trigger_price_decrease_action(total_price_change) 77 | logger.info("价格变动:%.4f;USDT当前售价:%.2f" % (total_price_change, get_usdt_sell_price())) 78 | 79 | #进行交易计算 80 | def perform_calculation(): 81 | total_price = 0 82 | total_price_change = 0 83 | # 当收集满所有货币的10分钟内交易额后,计算才有意义 84 | if not len(price_change_dict) == len(settings.COINS): 85 | return 86 | # 取出price_change_dict中的所有数据,并根据settings中设置的WEIGHT决定是否购买 87 | for channel, price in price_change_dict.items(): 88 | currency = channel.split(".")[1].replace(settings.SYMBOL.lower(), "").upper() 89 | weight = settings.COINS[currency]["WEIGHT"] 90 | total_price += settings.COINS[currency].get("AMOUNT", 0) * transaction_dict[channel][-1]['tick']['close'] 91 | total_price_change += price * weight 92 | total_price_change /= sum(list(map(lambda x: x["WEIGHT"], settings.COINS.values()))) 93 | predict_and_notify(total_price_change) 94 | 95 | #火币的交易量相关信息每60秒重置一次 96 | def update_data(channel): 97 | # 从transaction_dict[channel]中获取 98 | # 1. 1分钟内的涨跌幅 99 | # 2. 价格变化幅度(标准差) 100 | # 3. 1分钟内的交易额 101 | # 4. 1分钟最后一笔交易的价格 102 | if channel not in analyzed_queue_dict: 103 | analyzed_queue_dict[channel] = deque("", settings.N_MINUTES_STATE) 104 | data = { 105 | 'change': transaction_dict[channel][-1]['tick']['close'] - transaction_dict[channel][0]['tick']['close'], 106 | 'vol': transaction_dict[channel][-1]['tick']['vol'], 107 | 'mean': numpy.std(list(map(lambda x: x['tick']['close'], transaction_dict[channel]))), 108 | 'close': transaction_dict[channel][-1]['tick']['close'], 109 | } 110 | analyzed_queue_dict[channel].append(data) 111 | logger.debug("updated: " + channel) 112 | if len(analyzed_queue_dict[channel]) == settings.N_MINUTES_STATE: 113 | # 10分钟以内的"价格"变化 114 | price_change_dict[channel] = (analyzed_queue_dict[channel][-1]['close'] - analyzed_queue_dict[channel][0][ 115 | 'close']) * 100 / analyzed_queue_dict[channel][0]['close'] 116 | perform_calculation() 117 | 118 | #定期处理信息 119 | def handle_raw_message(msg_dict): 120 | channel = msg_dict['ch'] 121 | if len(transaction_dict) == len(settings.COINS): 122 | total = 0 123 | for key, value in settings.ORIGINAL_COINS.items(): 124 | total += transaction_dict["market.%susdt.kline.1min" % key.lower()][-1]['tick']['close'] * value['AMOUNT'] 125 | settings.ORIGINAL_WEALTH = settings.ORIGINAL_USDT_CURRENCY + total 126 | if channel not in transaction_dict: 127 | transaction_dict[channel] = [msg_dict] 128 | else: 129 | if transaction_dict[channel][-1]['tick']['count'] > msg_dict['tick']['count']: 130 | # 每60秒计算一次已有数据,然后重置该channel 131 | update_data(channel) 132 | transaction_dict[channel] = [msg_dict] 133 | else: 134 | transaction_dict[channel].append(msg_dict) -------------------------------------------------------------------------------- /strategy/second_kill_service.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import urllib2 3 | import json 4 | import time 5 | import sys 6 | import json 7 | import requests 8 | from service.rest_api_service import get_balance, send_order, cancel_order, order_matchresults 9 | 10 | #用户配置信息 11 | url_buy = 'https://api-otc.huobi.pro/v1/otc/trade/list/public?coinId=1&tradeType=1¤tPage=1&payWay=&country=' 12 | url_sell = 'https://api-otc.huobi.pro/v1/otc/trade/list/public?coinId=1&tradeType=0¤tPage=1&payWay=&country=' 13 | url_order = 'https://api-otc.huobi.pro/v1/otc/order/submit' 14 | url_get_order = 'https://api-otc.huobi.pro/v1/otc/order/confirm?order_ticket=' 15 | agent = 'User-Agent,Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7' 16 | avg_data = [] 17 | #---------------------# 18 | #防波动收购差价配置 19 | d_price = 1200 20 | #防做空收购数量境界 21 | max_buy_num = 0.18 22 | #爬虫定时/秒 23 | t = 5 24 | #输入用户cookie和token头的值 25 | cookie = '' 26 | token = '' 27 | #---------------------# 28 | 29 | #获取收售比特币的数据 30 | def get_buy(url,headers): 31 | req = urllib2.Request(url) 32 | req.add_header("User-Agent",headers) 33 | req.add_header("Host","api-otc.huobi.pro") 34 | content = urllib2.urlopen(req) 35 | buy_data = content.read() 36 | content.close() 37 | return buy_data 38 | 39 | 40 | #将json格式化,得出最低价格 41 | def json_data(jsondata): 42 | json_to_python = json.loads(jsondata) 43 | mindata_json = json_to_python['data'][0] 44 | new_min_json = json.dumps(mindata_json,ensure_ascii=False) 45 | new_min_data = json.loads(new_min_json) 46 | min_price = new_min_data['price'] 47 | min_number = new_min_data['tradeCount'] 48 | min_id = new_min_data['id'] 49 | min_tradeNo = new_min_data['tradeNo'] 50 | return min_price,min_number,min_id 51 | 52 | 53 | #取出售商家参考平均值 54 | def avg_sell(jsondata_sell): 55 | json_to_python = json.loads(jsondata_sell) 56 | for i in range(5): 57 | maxdata_json = json_to_python['data'][i] 58 | new_max_json = json.dumps(maxdata_json,ensure_ascii=False) 59 | new_max_data = json.loads(new_max_json) 60 | avg_data.append(new_max_data['price']) 61 | return (avg_data[0]+avg_data[1]+avg_data[2]+avg_data[3]+avg_data[4])//5 62 | 63 | 64 | #get_ticket脚本 65 | def get_ticket(buyNum,minBuy,tradeId): 66 | maxTradeLimit = buyNum * minBuy 67 | data = {'tradeId':tradeId,'amount':maxTradeLimit,'remark':''} 68 | req = urllib2.Request(url_order) 69 | req.add_header("User-Agent",agent) 70 | req.add_header("Host","api-otc.huobi.pro") 71 | req.add_header("token",token) 72 | req.add_header("Referer","https://otc.huobipro.com") 73 | req.add_header("Cookie",cookie) 74 | req.add_data(urllib.urlencode(data)) 75 | content = urllib2.urlopen(req) 76 | order_data = content.read() 77 | content.close() 78 | json_to_python = json.loads(order_data) 79 | ticket_json = json_to_python['data'] 80 | new_ticket_json = json.dumps(ticket_json,ensure_ascii=False) 81 | new_ticket = json.loads(new_ticket_json) 82 | try: 83 | ticket = new_ticket['ticket'] 84 | except TypeError: 85 | ticket = 0 86 | return ticket 87 | 88 | 89 | #下单脚本 90 | def get_order(order_ticket): 91 | data = '' 92 | req = urllib2.Request(url_get_order+order_ticket) 93 | req.add_header("User-Agent",agent) 94 | req.add_header("Host","api-otc.huobi.pro") 95 | req.add_header("token",token) 96 | req.add_header("Referer","https://otc.huobipro.com") 97 | req.add_header("Cookie",cookie) 98 | req.add_data(urllib.urlencode(data)) 99 | content = urllib2.urlopen(req) 100 | result_data = content.read() 101 | content.close() 102 | return result_data 103 | 104 | 105 | #主逻辑与下单脚本 106 | def btc_scan(): 107 | buydata = get_buy(url_buy,agent) 108 | selldata = get_buy(url_sell,agent) 109 | minBuy,buyNum,tradeId = json_data(buydata) 110 | avgSell = avg_sell(selldata) 111 | if avgSell - minBuy > d_price: 112 | if buyNum < max_buy_num: 113 | ticket = get_ticket(buyNum,minBuy,tradeId) 114 | if ticket != 0: 115 | order_result = get_order(ticket) 116 | else: 117 | order_result = 'error order' 118 | return '1','price differences:'+str(avgSell - minBuy),ticket,order_result 119 | else: 120 | return '2','count to much!',buyNum 121 | else: 122 | return 0,'waitting','min buy price:'+str(minBuy),'sell avg price:'+str(avgSell) 123 | 124 | 125 | #定时执行 126 | def timer(n): 127 | while True: 128 | print time.strftime('%X',time.localtime()), 129 | print btc_scan() 130 | time.sleep(n) 131 | timer(t) 132 | 133 | ### 134 | # 等待买入的币种(小写) 135 | COIN1 = 'ela' 136 | # 你想买进的COIN1的数量 137 | COIN1_AMOUNT = 10 138 | # 当COIN1的价格小于这个价位,程序允许买入,单位为COIN2 139 | COIN1_PRICE = 0.00153 140 | # 用来支付的币种,在USDT/BTC/ETH中间选择 141 | COIN2 = 'btc' 142 | 143 | def get_tote(coin): 144 | balance_dict = get_balance() 145 | token = {coin: 0} 146 | token_list = balance_dict['data']['list'] 147 | for item in token_list: 148 | if item['currency'] == coin and item['type'] == 'trade': 149 | token[coin] = item['balance'] 150 | break 151 | return token[coin] 152 | 153 | 154 | def get_price(symbol): 155 | addr = 'https://api.huobi.pro/market/depth?symbol={symbol}&type=step0' 156 | resp_json = requests.get(addr.format(symbol=symbol)).text 157 | if '[]' not in resp_json: 158 | resp_dict = json.loads(resp_json) 159 | sell_price = resp_dict['tick']['asks'][0][0] 160 | return '%f' % sell_price 161 | else: 162 | return '0' 163 | 164 | 165 | def buy_limit(coin1, coin2, coin1_max_price, coin1_amount): 166 | coin1_price = get_price(coin1 + coin2) 167 | if 0 < float(coin1_price) < coin1_max_price: 168 | resp_dict = send_order(amount=str(coin1_amount), 169 | source='', 170 | symbol=coin1 + coin2, 171 | _type='buy-limit', 172 | price=coin1_price) 173 | return resp_dict 174 | else: 175 | return {'status': 'err'} 176 | 177 | 178 | def task(): 179 | print('任务说明>> 我想用 {} 来买 {} '.format(COIN2, COIN1)) 180 | print('钱包状况>> 我有 {} 个 {} '.format(get_tote(COIN2), COIN2)) 181 | while True: 182 | resp_dict = buy_limit(COIN1, COIN2, COIN1_PRICE, COIN1_AMOUNT) 183 | if 'data' in resp_dict: 184 | order_id = resp_dict['data'] 185 | print('当前进度>> 已下买单,订单ID为 {}'.format(order_id)) 186 | if order_matchresults(order_id)['status'] != 'ok': 187 | print('订单追踪>> 未买到,取消订单...') 188 | cancel_order(order_id) 189 | else: 190 | print('订单追踪>> 已经买入,任务完成') 191 | break 192 | else: 193 | print('当前进度>> 暂未上币,或价格不符...') 194 | 195 | 196 | if __name__ == '__main__': 197 | if len(sys.argv) < 2: 198 | task() -------------------------------------------------------------------------------- /strategy/trend.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | import talib as ta 5 | from pandas.io.json import json_normalize 6 | import json 7 | import time 8 | 9 | 10 | # 使用talib库作为基础的指标库,只用获取对应的数据即可 11 | def SMA(df, period=5, type='close'): 12 | '''均线 13 | ''' 14 | 15 | # df = df.sort_values(by = 'id',axis = 0,ascending = True) 16 | df['ma']= ta.SMA(df[type],period) 17 | return df 18 | 19 | def MACross(df, type='close', shortPeriod=5, longPeriod=20): 20 | '''均线交叉 21 | ''' 22 | df['short'] = ta.SMA(df[type],shortPeriod) 23 | df['long'] = ta.SMA(df[type],longPeriod) 24 | shortMA = df['short'] 25 | longMA = df['long'] 26 | #print(df) 27 | if len(df) <= 2 : 28 | return '数据不足' 29 | if shortMA[0] > longMA[0] and shortMA[1] < longMA[1] : 30 | return '向上穿越'; 31 | elif shortMA[0] < longMA[0] and shortMA[1] > longMA[1] : 32 | return '向下穿越' 33 | return '非穿越' 34 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import talib as ta 2 | import numpy as np 3 | 4 | p = np.array([1.0,2.0,3.0,4.0,5.0,6.0]) 5 | s = ta.MA(p, 5) 6 | print(s) -------------------------------------------------------------------------------- /test/fake_run.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from config import settings 3 | from service import k_line_service 4 | from service import mongodb 5 | 6 | logger = Logging.getLogger(__name__) 7 | 8 | # 不通过websocket,直接通过读取到的数据库的值调用kline_handler 9 | if __name__ == "__main__": 10 | index_dict = {} 11 | currency_dict = {} 12 | if mongodb: 13 | for currency in settings.COINS.keys(): 14 | index_dict["market_%susdt_kline_1min" % currency.lower()] = 0 15 | 16 | for collection_name in index_dict.keys(): 17 | collection = mongodb.get_collection(collection_name) 18 | currency_dict[collection_name] = collection.find_one({}, skip=index_dict[collection_name]) 19 | 20 | while True: 21 | # 从currency_dict中找到ts最小的key,更新该key 22 | min_ts_document_key = min(currency_dict.items(), key=lambda x: x[1]['_id'])[0] 23 | collection = mongodb.get_collection(min_ts_document_key) 24 | index_dict[min_ts_document_key] += 1 25 | kline_handler.handle_raw_message(currency_dict[min_ts_document_key]) 26 | currency_dict[min_ts_document_key] = collection.find_one( 27 | {'ts': {'$gt': 1000 * int(settings.SIMULATE_START.timestamp()), 28 | '$lt': 1000 * int(settings.SIMULATE_END.timestamp())}}, skip=index_dict[min_ts_document_key]) 29 | if currency_dict[min_ts_document_key] is None: 30 | break 31 | print("已完成处理") -------------------------------------------------------------------------------- /test2.py: -------------------------------------------------------------------------------- 1 | from exchangeapi.huobi import rest_api 2 | import talib as ta 3 | import numpy as np 4 | import pandas as pd 5 | import json 6 | 7 | if __name__ == "__main__": 8 | #websocket.start() 9 | print(rest_api.get_kline('ethusdt',"1min",10)) 10 | 11 | result = rest_api.get_kline('ethusdt',"1min",50) 12 | 13 | print(result.get('data')) 14 | 15 | df = pd.DataFrame(result.get('data'), columns=['id', 'open', 'close', 'low', 'high', 'amount', 'vol', 'count']) 16 | print(df) 17 | dm = df.sort_values(by = 'id',axis = 0,ascending = True) 18 | 19 | print(dm) 20 | 21 | 22 | #print(ta.EMA(df['close'],5)) 23 | dm['ema10']= ta.EMA(dm['close'],10) 24 | dm['sma10']= ta.SMA(dm['close'],10) 25 | print(dm) -------------------------------------------------------------------------------- /util/__pycache__/api_key_util.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/util/__pycache__/api_key_util.cpython-36.pyc -------------------------------------------------------------------------------- /util/__pycache__/api_key_util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/util/__pycache__/api_key_util.cpython-37.pyc -------------------------------------------------------------------------------- /util/__pycache__/http_request_util.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/util/__pycache__/http_request_util.cpython-36.pyc -------------------------------------------------------------------------------- /util/__pycache__/http_request_util.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Salmon-Bot/TradeRobot/860483cb8febe659bb9596d44136daa9732c2db1/util/__pycache__/http_request_util.cpython-37.pyc -------------------------------------------------------------------------------- /util/api_key_util.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import datetime 3 | import hashlib 4 | import hmac 5 | import json 6 | import urllib 7 | import urllib.parse 8 | import urllib.request 9 | import requests 10 | 11 | from config.settings import ACCESS_KEY 12 | from config.settings import SECRET_KEY 13 | from config.settings import ACCOUNT_ID 14 | from config.settings import MARKET_URL 15 | from config.settings import TRADE_URL 16 | 17 | def api_key_get(params, request_path): 18 | method = 'GET' 19 | timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') 20 | params.update({'AccessKeyId': ACCESS_KEY, 21 | 'SignatureMethod': 'HmacSHA256', 22 | 'SignatureVersion': '2', 23 | 'Timestamp': timestamp}) 24 | 25 | host_url = TRADE_URL 26 | host_name = urllib.parse.urlparse(host_url).hostname 27 | host_name = host_name.lower() 28 | params['Signature'] = createSign(params, method, host_name, request_path, SECRET_KEY) 29 | 30 | url = host_url + request_path 31 | return http_get_request(url, params) 32 | 33 | 34 | def api_key_post(params, request_path): 35 | method = 'POST' 36 | timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') 37 | params_to_sign = {'AccessKeyId': ACCESS_KEY, 38 | 'SignatureMethod': 'HmacSHA256', 39 | 'SignatureVersion': '2', 40 | 'Timestamp': timestamp} 41 | 42 | host_url = TRADE_URL 43 | host_name = urllib.parse.urlparse(host_url).hostname 44 | host_name = host_name.lower() 45 | params_to_sign['Signature'] = createSign(params_to_sign, method, host_name, request_path, SECRET_KEY) 46 | url = host_url + request_path + '?' + urllib.parse.urlencode(params_to_sign) 47 | return http_post_request(url, params) 48 | 49 | 50 | def createSign(pParams, method, host_url, request_path, secret_key): 51 | sorted_params = sorted(pParams.items(), key=lambda d: d[0], reverse=False) 52 | encode_params = urllib.parse.urlencode(sorted_params) 53 | payload = [method, host_url, request_path, encode_params] 54 | payload = '\n'.join(payload) 55 | payload = payload.encode(encoding='UTF8') 56 | secret_key = secret_key.encode(encoding='UTF8') 57 | 58 | digest = hmac.new(secret_key, payload, digestmod=hashlib.sha256).digest() 59 | signature = base64.b64encode(digest) 60 | signature = signature.decode() 61 | return signature -------------------------------------------------------------------------------- /util/http_request_util.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib 3 | import urllib.parse 4 | import urllib.request 5 | import requests 6 | import os 7 | 8 | #找到对应文件的路径,如果不在环境变量则添加到环境变量 9 | # file_path = os.path.dirname(os.path.dirname()) 10 | # if file_path not in sys.path: 11 | # sys.path.insert(0,p) 12 | 13 | def http_get_request(url, params, add_to_headers=None): 14 | headers = { 15 | "Content-type": "application/x-www-form-urlencoded", 16 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36', 17 | } 18 | if add_to_headers: 19 | headers.update(add_to_headers) 20 | postdata = urllib.parse.urlencode(params) 21 | response = requests.get(url, postdata, headers=headers, timeout=5) 22 | try: 23 | if response.status_code == 200: 24 | return response.json() 25 | else: 26 | return 27 | except BaseException as e: 28 | print("httpGet failed, detail is:%s,%s" %(response.text,e)) 29 | return 30 | 31 | def http_post_request(url, params, add_to_headers=None): 32 | headers = { 33 | "Accept": "application/json", 34 | 'Content-Type': 'application/json' 35 | } 36 | if add_to_headers: 37 | headers.update(add_to_headers) 38 | postdata = json.dumps(params) 39 | response = requests.post(url, postdata, headers=headers, timeout=10) 40 | try: 41 | 42 | if response.status_code == 200: 43 | return response.json() 44 | else: 45 | return 46 | except BaseException as e: 47 | print("httpPost failed, detail is:%s,%s" %(response.text,e)) 48 | return 49 | 50 | --------------------------------------------------------------------------------