├── requirements.txt ├── okteto.yml ├── scripts ├── top.sh ├── start.sh ├── orders.sh ├── mailstop.sh ├── stop.sh ├── mail.sh └── setup.sh ├── .gitignore ├── Dockerfile ├── utility ├── get_margin_transfer.py ├── repay_margin_loan.py ├── cancel_all_orders.py ├── rebuy_bnb.py ├── rebuy_coin.py └── resell_coin.py ├── bundle.sh ├── settings_tpl.py ├── README.md ├── binance_orders.py ├── .stignore ├── binance_report.py ├── binance_top.py └── binance_bot.py /requirements.txt: -------------------------------------------------------------------------------- 1 | prettytable==0.7.2 2 | python-binance==0.7.5 3 | TA-Lib 4 | pandas 5 | numpy 6 | -------------------------------------------------------------------------------- /okteto.yml: -------------------------------------------------------------------------------- 1 | name: binance-bot 2 | image: okteto/python:3 3 | command: 4 | - bash 5 | workdir: /usr/src/app 6 | -------------------------------------------------------------------------------- /scripts/top.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "get current profit report" 4 | echo "===========================" 5 | 6 | python3 binance_top.py 7 | 8 | echo "Done!" 9 | -------------------------------------------------------------------------------- /scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "start strading bot" 4 | echo "=====================" 5 | 6 | python3 binance_bot.py > bot.log 2>&1 & 7 | 8 | echo "Done!" -------------------------------------------------------------------------------- /scripts/orders.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Get Current Orders" 4 | echo "======================" 5 | 6 | python3 binance_orders.py 7 | 8 | 9 | echo "Done!" 10 | -------------------------------------------------------------------------------- /scripts/mailstop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "stop mail cronjob" 4 | echo "====================" 5 | 6 | kill -9 $( ps aux|grep binance_report.py|grep -v grep|awk '{print $2}' ) 7 | 8 | 9 | echo "Done!" 10 | -------------------------------------------------------------------------------- /scripts/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "stop trading bot" 4 | echo "====================" 5 | 6 | # 停止机器人 7 | kill -9 $( ps aux|grep binance_bot.py|grep -v grep|awk '{print $2}' ) 8 | 9 | echo "Done!" 10 | -------------------------------------------------------------------------------- /scripts/mail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "start profit report cronjob every 12" 4 | echo "=======================================" 5 | 6 | python3 binance_report.py > mail.log 2>&1 & 7 | 8 | 9 | echo "Done!" 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | release 2 | *.log 3 | mail.sh 4 | mailstop.sh 5 | orders.sh 6 | setup.sh 7 | start.sh 8 | stop.sh 9 | top.sh 10 | !scripts/*.sh 11 | qcat-release-* 12 | settings.py 13 | sample/* 14 | __pycache__/* 15 | encrypt/* 16 | *.pye 17 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Welcome to binance bot manager!" 4 | 5 | echo "First, copy settings_tpl.py to settings.py..." 6 | 7 | cp settings_tpl.py settings.py 8 | 9 | echo "Second, edit settings.py to tuning parameters..." 10 | 11 | echo "./start.sh to bootstrap the bot service..." 12 | 13 | echo "Done!" 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 构建bot镜像 2 | 3 | FROM 9fevrier/python-ta-lib-pandas 4 | 5 | 6 | ENV PYTHON_PANDAS_VERSION 0.25.3 7 | 8 | WORKDIR /data/bot 9 | 10 | COPY . /data/bot 11 | 12 | RUN apk add --no-cache --virtual .build-deps \ 13 | musl-dev \ 14 | gcc \ 15 | g++ \ 16 | make \ 17 | && apk add --update libstdc++ \ 18 | && apk add --update libffi-dev libressl-dev \ 19 | && pip3 install cython \ 20 | pandas==${PYTHON_PANDAS_VERSION} \ 21 | && pip3 install -r requirements.txt \ 22 | && apk del .build-deps \ 23 | && rm -rf /root/.cache \ 24 | /var/cache/apk/* \ 25 | /var/lib/apk/lists/* 26 | 27 | WORKDIR /data/bot 28 | 29 | CMD python3 -------------------------------------------------------------------------------- /utility/get_margin_transfer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from binance.client import Client 4 | from binance.enums import * 5 | from binance.websockets import BinanceSocketManager 6 | import time 7 | from datetime import datetime 8 | 9 | import sys, os 10 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 11 | from settings import BinanceKey1 12 | 13 | api_key = BinanceKey1['api_key'] 14 | api_secret = BinanceKey1['api_secret'] 15 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 16 | 17 | def run(): 18 | print('='*30) 19 | print("获取划转历史 (USER_DATA)") 20 | result = get_margin_transfer() 21 | print(result) 22 | 23 | def get_margin_transfer(): 24 | ''' 25 | not yet implemented 26 | ''' 27 | return "TODO" 28 | 29 | if __name__ == "__main__": 30 | run() -------------------------------------------------------------------------------- /bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | echo Qcat Automate Trading Bot Release Script 5 | echo "==========================================" 6 | 7 | rm -rf release && mkdir release 8 | 9 | echo "copy core bot file to release..." 10 | cp -avr binance_bot.py binance_top.py binance_orders.py binance_report.py \ 11 | kdj_bollinger_bot.py \ 12 | settings_tpl.py requirements.txt ./release/ 13 | 14 | echo "some useful scripts for trading..." 15 | cp -avr ./utility ./release/ 16 | 17 | echo "some bash script to get thing start..." 18 | cp -avr ./scripts/*.sh ./release/ 19 | 20 | echo "Now starting bundle the code and package it..." 21 | 22 | git fetch origin master --tags 23 | 24 | latesttag=$(git describe --tags) 25 | echo checking out ${latesttag} 26 | git checkout ${latesttag} 27 | 28 | zip -q -r -e -o "qcat-release-${latesttag}.zip" ./release 29 | echo "Done!" -------------------------------------------------------------------------------- /utility/repay_margin_loan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from binance.client import Client 4 | from binance.enums import * 5 | from binance.websockets import BinanceSocketManager 6 | import time 7 | from datetime import datetime 8 | 9 | import sys, os 10 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 11 | from settings import BinanceKey1 12 | from settings import MarginAccount 13 | 14 | api_key = BinanceKey1['api_key'] 15 | api_secret = BinanceKey1['api_secret'] 16 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 17 | 18 | # 配置参数 19 | loan_qty = MarginAccount['loan'] 20 | coin_symbol = MarginAccount['coin_symbol'] 21 | 22 | 23 | def run(): 24 | print('='*30) 25 | print("\n归还loan {} {}\n".format(coin_symbol, loan_qty)) 26 | repay_loan(coin_symbol, loan_qty) 27 | print("Done!") 28 | 29 | def repay_loan(coin_symbol, qty): 30 | transaction = client.repay_margin_loan(asset=coin_symbol, 31 | amount=qty) 32 | print(transaction) 33 | 34 | if __name__ == "__main__": 35 | run() -------------------------------------------------------------------------------- /utility/cancel_all_orders.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from binance.client import Client 4 | from binance.enums import * 5 | from binance.websockets import BinanceSocketManager 6 | import time 7 | from datetime import datetime 8 | 9 | import sys, os 10 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 11 | from settings import BinanceKey1 12 | 13 | api_key = BinanceKey1['api_key'] 14 | api_secret = BinanceKey1['api_secret'] 15 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 16 | 17 | # 配置参数 18 | symbol = 'EOSUSDT' 19 | coin_symbol = 'EOS' 20 | usdt_symbol = 'USDT' 21 | bnb_symbol = 'BNB' 22 | 23 | def run(): 24 | print('='*30) 25 | 26 | cancel_all_margin_orders(symbol) 27 | print("Done!") 28 | 29 | def cancel_all_margin_orders(symbol): 30 | orders = client.get_open_margin_orders(symbol=symbol) 31 | print("取消挂单:\n") 32 | 33 | for o in orders: 34 | result = client.cancel_margin_order(symbol=symbol, 35 | orderId=o.get('orderId')) 36 | print("{}".format(result)) 37 | 38 | 39 | if __name__ == "__main__": 40 | run() -------------------------------------------------------------------------------- /settings_tpl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Binance bot all settings 5 | """ 6 | 7 | ZohoMail = {'username': 'support@qcat.io', 8 | 'password':'< your password >', 9 | 'smtpserver': '< smtp server>', 10 | 'serverport': 465, 11 | 'recipient': [' '], 12 | } 13 | 14 | MarginAccount = { 15 | 'pair_symbol': 'EOSUSDT', 16 | 'coin_symbol': 'EOS', 17 | 'usdt_symbol': 'USDT', 18 | 'loan_balance': 0, # <-----借贷币的数量 19 | 'loan_enabled': False, #<------是否借币开关,在币价动荡的时候,可以关闭借币通道 20 | 'coin_balance': 0, #<----- 开盘总持有币数量 21 | 'depth': 1, # <-----对手单深度数 22 | 'bnb_symbol': 'BNB', 23 | 'base_balance': 0, # <---投入本金,一般为USDT 24 | 'base_bnb_balance': 0, # <---投入BNB,当手续费使用,在千1手续费之上打75折 25 | 'fiat_symbol': 'USD', # <---本地法币 26 | 'fiat_price': 1, # 1 USD 兑换 本地法币的价格 27 | 'max_margins': 20, # <--------(depth * 2) * float(0.8), 最大交易对数量 28 | 'free_cash_limit_percentile': 0.1, #<-----最小可用金额百分比, 20% 29 | 'price_accuracy': '%.4f', #<----- 价格精度,交易需要指定。默认 .4f 30 | 'qty_accuracy': '%.2f', #<------ 数量进度, EOS是 2个, BTC是6个 31 | 'trend_limit_tan': [0.1763, -0.1763], #<--------- 趋势判断,kline 用tan函数,默认是tan10, 但是EOS比值太小,换了更小的值 32 | } 33 | 34 | BinanceKey1 = {'api_key': '< your api key>', 35 | 'api_secret':'< your api secret>'} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## QCat Automated Crypto Trading bot 2 | 3 | ### *注意* :对于小于4G内存的小机器,增加Swap 4 | RAM > 1G or add swap, 不然ta-lib安装会失败 5 | 6 | ```bash 7 | # (增加4G) 8 | dd if=/dev/zero of=/tmp/mem.swap bs=1M count=4096 9 | free -m 10 | 11 | mkswap /tmp/mem.swap 12 | swapon /tmp/mem.swap 13 | # 确认是否增加成功: 14 | free -m 15 | ``` 16 | 17 | ### 安装RSI指标计算库依赖 18 | ```bash 19 | apt-get update 20 | apt-get install build-essential 21 | 22 | # download from 23 | wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz 24 | 25 | tar -xvf ta-lib-0.4.0-src.tar.gz 26 | 27 | cd ta-lib 28 | 29 | ./configure --prefix=/usr 30 | 31 | make && make install 32 | 33 | apt-get install python3-pip 34 | ### ta-lib, 需要最少1G的内存,小机器请使用swap扩展 35 | pip3 install numpy -i http://pypi.douban.com/simple --trusted-host pypi.douban.com 36 | pip3 install ta-lib 37 | 38 | pip3 install pandas -i http://pypi.douban.com/simple --trusted-host pypi.douban.com 39 | pip3 install -r requirements.txt 40 | ``` 41 | 42 | ### 安装配置并运行 43 | bash setup.sh #初始化安装 44 | vi settings.py #bot全局配置,运行前务必配置 45 | start.sh 46 | 47 | ### 打包发行安装包 48 | bash bundle.sh 生成安装包并发行zip格式安装包 49 | 50 | * start.sh 启动机器人 51 | * stop.sh 暂停机器人 52 | * top.sh 查询当前账户挂单状态 53 | * mail.sh 发盈利报告邮件,12小时统计发送一次 54 | * mailstop.sh 停止邮件 55 | 56 | ### How do I protect my Python source code? 57 | 58 | 在线混淆工具(选择:Dancing Links模式):http://pyob.oxyry.com/ 59 | 60 | 专业工具(Linux,macos):https://github.com/Hnfull/Intensio-Obfuscator 61 | 62 | -------------------------------------------------------------------------------- /utility/rebuy_bnb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from binance.client import Client 4 | from binance.enums import * 5 | from binance.websockets import BinanceSocketManager 6 | import time 7 | from datetime import datetime 8 | 9 | import sys, os 10 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 11 | from settings import BinanceKey1 12 | 13 | api_key = BinanceKey1['api_key'] 14 | api_secret = BinanceKey1['api_secret'] 15 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 16 | 17 | # 配置参数 18 | symbol = 'BNBUSDT' 19 | coin_symbol = 'BNB' 20 | usdt_symbol = 'USDT' 21 | bnb_symbol = 'BNB' 22 | max_qty = 1 23 | 24 | def run(): 25 | print('='*30) 26 | print("\n购入{},手续费需要 {} {}\n".format(coin_symbol, max_qty, coin_symbol)) 27 | repay_coin(max_qty) 28 | print("Done!") 29 | 30 | def repay_coin(qty): 31 | ticker = client.get_orderbook_ticker(symbol=symbol) 32 | print("Current bid price: {}".format(ticker.get('bidPrice'))) 33 | print("Current ask price: {}".format(ticker.get('askPrice'))) 34 | buy_price = float(ticker.get('bidPrice')) 35 | buy_price = '%.4f' % buy_price 36 | 37 | 38 | buy_order = client.create_margin_order(symbol=symbol, 39 | side=SIDE_BUY, 40 | type=ORDER_TYPE_LIMIT, 41 | quantity=qty, 42 | price=buy_price, 43 | timeInForce=TIME_IN_FORCE_GTC) 44 | 45 | if __name__ == "__main__": 46 | run() -------------------------------------------------------------------------------- /utility/rebuy_coin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from binance.client import Client 4 | from binance.enums import * 5 | from binance.websockets import BinanceSocketManager 6 | import time 7 | from datetime import datetime 8 | 9 | import sys, os 10 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 11 | from settings import BinanceKey1 12 | 13 | api_key = BinanceKey1['api_key'] 14 | api_secret = BinanceKey1['api_secret'] 15 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 16 | 17 | # 配置参数 18 | symbol = 'EOSUSDT' 19 | coin_symbol = 'EOS' 20 | usdt_symbol = 'USDT' 21 | bnb_symbol = 'BNB' 22 | max_qty = 40 23 | 24 | def run(): 25 | print('='*30) 26 | print("\n购入{},恢复Bot的最低风控阈值 >= 60 {}\n".format(coin_symbol, coin_symbol)) 27 | repay_coin(max_qty) 28 | print("Done!") 29 | 30 | def repay_coin(qty): 31 | ticker = client.get_orderbook_ticker(symbol=symbol) 32 | print("Current bid price: {}".format(ticker.get('bidPrice'))) 33 | print("Current ask price: {}".format(ticker.get('askPrice'))) 34 | buy_price = float(ticker.get('bidPrice')) 35 | buy_price = '%.4f' % buy_price 36 | 37 | 38 | buy_order = client.create_margin_order(symbol=symbol, 39 | side=SIDE_BUY, 40 | type=ORDER_TYPE_LIMIT, 41 | quantity=qty, 42 | price=buy_price, 43 | timeInForce=TIME_IN_FORCE_GTC) 44 | 45 | if __name__ == "__main__": 46 | run() -------------------------------------------------------------------------------- /utility/resell_coin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from binance.client import Client 4 | from binance.enums import * 5 | from binance.websockets import BinanceSocketManager 6 | import time 7 | from datetime import datetime 8 | 9 | import sys, os 10 | sys.path.append(os.path.join(os.path.dirname(__file__), '..')) 11 | from settings import BinanceKey1 12 | 13 | api_key = BinanceKey1['api_key'] 14 | api_secret = BinanceKey1['api_secret'] 15 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 16 | 17 | # 配置参数 18 | symbol = 'EOSUSDT' 19 | coin_symbol = 'EOS' 20 | usdt_symbol = 'USDT' 21 | bnb_symbol = 'BNB' 22 | max_qty = 40 23 | 24 | def run(): 25 | print('='*30) 26 | print("\n卖出{},恢复Bot的最低风控阈值 >= 60 {}\n".format(coin_symbol, coin_symbol)) 27 | repay_coin(max_qty) 28 | print("Done!") 29 | 30 | def repay_coin(qty): 31 | ticker = client.get_orderbook_ticker(symbol=symbol) 32 | print("Current bid price: {}".format(ticker.get('bidPrice'))) 33 | print("Current ask price: {}".format(ticker.get('askPrice'))) 34 | 35 | sell_price = float(ticker.get('askPrice')) 36 | sell_price = '%.4f' % sell_price 37 | 38 | sell_order = client.create_margin_order(symbol=symbol, 39 | side=SIDE_SELL, 40 | type=ORDER_TYPE_LIMIT, 41 | quantity=qty, 42 | price=sell_price, 43 | timeInForce=TIME_IN_FORCE_GTC) 44 | 45 | if __name__ == "__main__": 46 | run() -------------------------------------------------------------------------------- /binance_orders.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from binance.client import Client 4 | from binance.enums import * 5 | from binance.websockets import BinanceSocketManager 6 | import time 7 | from datetime import datetime 8 | import prettytable as pt 9 | from settings import MarginAccount 10 | 11 | from settings import BinanceKey1 12 | 13 | api_key = BinanceKey1['api_key'] 14 | api_secret = BinanceKey1['api_secret'] 15 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 16 | 17 | # 配置参数 18 | pair_symbol = MarginAccount['pair_symbol'] 19 | coin_symbol = MarginAccount['coin_symbol'] 20 | usdt_symbol = MarginAccount['usdt_symbol'] 21 | bnb_symbol = MarginAccount['bnb_symbol'] 22 | max_margins = 15 23 | # 投入本金,手工统计 24 | base_balance = MarginAccount['base_balance'] 25 | fiat_symbol = MarginAccount['fiat_symbol'] 26 | fiat_price = MarginAccount['fiat_price'] 27 | 28 | def run(): 29 | print('='*30) 30 | get_all_margin_orders() 31 | 32 | 33 | def get_all_margin_orders(): 34 | orders = client.get_open_margin_orders(symbol=pair_symbol) 35 | 36 | tb = pt.PrettyTable() 37 | tb.field_names = ["orderId", "Qty", "Price", "Side","Symbol", "Time"] 38 | for o in orders: 39 | tb.add_row([ o["orderId"], o["origQty"], o["price"], o["side"], o["symbol"], timestamp2string(o["time"]) ]) 40 | 41 | print(tb) 42 | 43 | 44 | def timestamp2string(timeStamp): 45 | try: 46 | d = datetime.fromtimestamp(int(timeStamp)/1000) 47 | dtstr = d.strftime("%Y-%m-%d %H:%M:%S") 48 | return dtstr 49 | except Exception as e: 50 | print(e) 51 | return '' 52 | 53 | if __name__ == "__main__": 54 | run() -------------------------------------------------------------------------------- /.stignore: -------------------------------------------------------------------------------- 1 | .git 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build 13 | develop-eggs 14 | dist 15 | downloads 16 | eggs 17 | .eggs 18 | lib 19 | lib64 20 | parts 21 | sdist 22 | var 23 | wheels 24 | pip-wheel-metadata 25 | share/python-wheels 26 | *.egg-info 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Translations 42 | *.mo 43 | *.pot 44 | 45 | # Django stuff: 46 | *.log 47 | local_settings.py 48 | db.sqlite3 49 | 50 | # Flask stuff: 51 | instance 52 | .webassets-cache 53 | 54 | # Scrapy stuff: 55 | .scrapy 56 | 57 | # Sphinx documentation 58 | docs/_build 59 | 60 | # PyBuilder 61 | target 62 | 63 | # Jupyter Notebook 64 | .ipynb_checkpoints 65 | 66 | # IPython 67 | profile_default 68 | ipython_config.py 69 | 70 | # pyenv 71 | .python-version 72 | 73 | # celery beat schedule file 74 | celerybeat-schedule 75 | 76 | # SageMath parsed files 77 | *.sage.py 78 | 79 | # Environments 80 | .env 81 | .venv 82 | env 83 | venv 84 | ENV 85 | env.bak 86 | venv.bak 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | .spyproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | 95 | # mypy 96 | .mypy_cache 97 | .dmypy.json 98 | dmypy.json 99 | 100 | # Pyre type checker 101 | .pyre 102 | -------------------------------------------------------------------------------- /binance_report.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import time 5 | from datetime import datetime 6 | import smtplib 7 | from email.mime.text import MIMEText 8 | from email.mime.multipart import MIMEMultipart 9 | from email.header import Header 10 | 11 | from binance.client import Client 12 | from binance.enums import * 13 | from binance.websockets import BinanceSocketManager 14 | from settings import ZohoMail 15 | 16 | from settings import MarginAccount 17 | from settings import BinanceKey1 18 | api_key = BinanceKey1['api_key'] 19 | api_secret = BinanceKey1['api_secret'] 20 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 21 | 22 | # 配置参数 23 | pair_symbol = MarginAccount['pair_symbol'] 24 | coin_symbol = MarginAccount['coin_symbol'] 25 | usdt_symbol = MarginAccount['usdt_symbol'] 26 | bnb_symbol = MarginAccount['bnb_symbol'] 27 | max_margins = 15 28 | # 投入本金,手工统计 29 | base_balance = MarginAccount['base_balance'] 30 | fiat_symbol = MarginAccount['fiat_symbol'] 31 | fiat_price = MarginAccount['fiat_price'] 32 | base_bnb_balance = MarginAccount['base_bnb_balance'] 33 | 34 | def eSend(sender,recipient,username,password,smtpserver,port): 35 | try: 36 | now = datetime.now() 37 | dt_string = now.strftime("%Y-%m-%d") 38 | subject = "QCat Auto Trading Bot Report - {}".format(dt_string) #邮件标题 39 | e_content = '{0:^27}\n{1:^27}\n{2:^25}\n{3:^25}'.format('i','/ \\','(-----)','(--------)') #邮件正文 40 | e_content += "今天行情不错奥,Qcat自动交易机器人帮您统计了一下收益:\n\n" 41 | e_content += get_account_status() 42 | 43 | #邮件头 44 | message = MIMEMultipart() 45 | message['From'] = sender #发送 46 | message['To'] = ",".join(recipient) #收件 47 | message['Subject'] = Header(subject, 'utf-8') 48 | message.attach(MIMEText(e_content, 'plain', 'utf-8')) # 邮件正文 49 | 50 | #执行 51 | server = smtplib.SMTP_SSL(smtpserver, port) 52 | server.login(username, password) 53 | server.sendmail(sender, recipient, message.as_string()) #发送 54 | server.quit() 55 | print("SEND") 56 | except Exception as e: 57 | print(e) 58 | print("SEND FAILED") 59 | 60 | def run(): 61 | while True: 62 | #配置 63 | #__time_____ 64 | ehour=11 #定时小时 65 | emin=49 #定时分钟 66 | esec=50 #定时秒 67 | current_time = time.localtime(time.time()) #当前时间date 68 | cur_time = time.strftime('%D:%H:%M', time.localtime(time.time())) #当前时间str 69 | 70 | #__email_____ 71 | sender = 'support@qcat.io' # 发件人邮箱 72 | recipient = ZohoMail['recipient'] # 收件人邮箱,可以多个(列表形式)群发 73 | username = ZohoMail['username'] # 发件人姓名 74 | password = ZohoMail['password'] # smtp密码,qq是给你分配一串,163是自己设置 75 | smtpserver = ZohoMail['smtpserver'] # 邮箱服务器 76 | serverport = ZohoMail['serverport'] 77 | 78 | #操作 79 | if ((current_time.tm_hour == ehour) and (current_time.tm_min == emin) and (current_time.tm_sec == esec)): 80 | print ("START") 81 | eSend(sender, recipient, username, password, smtpserver, serverport) 82 | print(cur_time) 83 | # sleep 1 second 84 | time.sleep(1) 85 | 86 | 87 | def get_account_status(): 88 | coinusdt_avg_price = client.get_avg_price(symbol=pair_symbol) 89 | bnbusdt_avg_price = client.get_avg_price(symbol='BNBUSDT') 90 | btcusdt_avg_price = client.get_avg_price(symbol='BTCUSDT') 91 | current_coin_price = float(coinusdt_avg_price.get('price')) 92 | current_bnb_price = float(bnbusdt_avg_price.get('price')) 93 | current_btc_price = float(btcusdt_avg_price.get('price')) 94 | message = "QCat Auto Trading bot\n" 95 | message += "-"*30 96 | now = datetime.now() 97 | dt_string = now.strftime("%Y-%m-%d %H:%M:%S") 98 | message += "\n统计时间:%s\n" % dt_string 99 | message += "当前 {} 价格: {:.4f} USDT\n".format(coin_symbol, current_coin_price) 100 | message += "当前 BTC 价格: {:.2f} USDT\n".format(current_btc_price) 101 | message += "当前 BNB 价格: {:.2f} USDT\n\n".format(current_bnb_price) 102 | 103 | # 计算账户信息 start 104 | account = client.get_margin_account() 105 | userAssets = account.get('userAssets') 106 | free_coin = float(0) 107 | free_cash = float(0) 108 | loan_coin = float(0) 109 | net_coin = float(0) 110 | bnb_free_coin = float(0) 111 | for asset in userAssets: 112 | if asset.get('asset') == coin_symbol: 113 | free_coin = asset.get('free') 114 | loan_coin = asset.get('borrowed') 115 | net_coin = asset.get('netAsset') 116 | if asset.get('asset') == bnb_symbol: 117 | bnb_free_coin = asset.get('free') 118 | if asset.get('asset') == usdt_symbol: 119 | free_cash = asset.get('free') 120 | # 计算账号信息 end 121 | 122 | # 总投入成本 123 | total_base_balance = float(base_balance) + float(base_bnb_balance * current_bnb_price) 124 | # 利润 125 | profit_balance = (float(account.get("totalNetAssetOfBtc")) * current_btc_price) - total_base_balance 126 | # 利润率 127 | profit_percent = float(profit_balance / total_base_balance) 128 | message += "*** 投入本金: {} USDT, {} BNB (约为{}: {:.2f}) ***\n".format(base_balance, base_bnb_balance, fiat_symbol, total_base_balance * fiat_price) 129 | message += "*** 日累计盈亏: {0:.2f} USDT(约为{1}: {2:.2f} ),日利润率{3:.3%} ***\n\n".format(profit_balance, fiat_symbol, float(profit_balance * fiat_price), profit_percent) 130 | message += "-"*30 131 | message += "\n杠杆账号资产详情:\n" 132 | message += "\n当前杠杆账号风险率(默认3倍杠杆,小于2表示危险,需要人工处理): %s\n" % account.get("marginLevel") 133 | message += "当前杠杆账号总资产估值: {0} BTC (约为 {1:.2f} USDT )\n".format( account.get("totalAssetOfBtc"), float(account.get("totalAssetOfBtc")) * current_btc_price ) 134 | message += "当前杠杆账号账户权益: {0} BTC (约为 {1:.2f} USDT )\n".format( account.get("totalNetAssetOfBtc"), float(account.get("totalNetAssetOfBtc")) * current_btc_price ) 135 | 136 | message += "当前帐户可用现金{0}是: {1}\n".format(usdt_symbol, float(free_cash) ) 137 | message += "当前账户可用代币{0}是: {1}\n".format(coin_symbol, float(free_coin) ) 138 | message += "当前账户可用{0}是: {1}\n".format(bnb_symbol, float(bnb_free_coin) ) 139 | message += "-"*30 140 | message += "\n当前借入{}资产是{}\n".format(coin_symbol, loan_coin) 141 | message += "当前{}的净资产是{}\n".format(coin_symbol, net_coin) 142 | return message 143 | 144 | if __name__ == "__main__": 145 | run() 146 | -------------------------------------------------------------------------------- /binance_top.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from binance.client import Client 4 | from binance.enums import * 5 | from binance.websockets import BinanceSocketManager 6 | import time 7 | from datetime import datetime 8 | 9 | from settings import MarginAccount 10 | from settings import BinanceKey1 11 | api_key = BinanceKey1['api_key'] 12 | api_secret = BinanceKey1['api_secret'] 13 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 14 | 15 | # 配置参数 16 | pair_symbol = MarginAccount['pair_symbol'] 17 | coin_symbol = MarginAccount['coin_symbol'] 18 | usdt_symbol = MarginAccount['usdt_symbol'] 19 | bnb_symbol = MarginAccount['bnb_symbol'] 20 | max_margins = 15 21 | # 投入本金,手工统计 22 | base_balance = MarginAccount['base_balance'] 23 | fiat_symbol = MarginAccount['fiat_symbol'] 24 | fiat_price = MarginAccount['fiat_price'] 25 | base_bnb_balance = MarginAccount['base_bnb_balance'] 26 | 27 | def run(): 28 | print('='*30) 29 | get_account_status() 30 | 31 | def get_account_status(): 32 | coinusdt_avg_price = client.get_avg_price(symbol=pair_symbol) 33 | bnbusdt_avg_price = client.get_avg_price(symbol='BNBUSDT') 34 | btcusdt_avg_price = client.get_avg_price(symbol='BTCUSDT') 35 | current_coin_price = float(coinusdt_avg_price.get('price')) 36 | current_bnb_price = float(bnbusdt_avg_price.get('price')) 37 | current_btc_price = float(btcusdt_avg_price.get('price')) 38 | message = "QCat Auto Trading bot\n" 39 | message += "-"*30 40 | now = datetime.now() 41 | dt_string = now.strftime("%Y-%m-%d %H:%M:%S") 42 | message += "\n统计时间:%s\n" % dt_string 43 | message += "当前 {} 价格: {:.4f} USDT\n".format(coin_symbol, current_coin_price) 44 | message += "当前 BTC 价格: {:.2f} USDT\n".format(current_btc_price) 45 | message += "当前 BNB 价格: {:.2f} USDT\n\n".format(current_bnb_price) 46 | 47 | # 计算账户信息 start 48 | account = client.get_margin_account() 49 | userAssets = account.get('userAssets') 50 | free_coin = float(0) 51 | free_cash = float(0) 52 | loan_coin = float(0) 53 | net_coin = float(0) 54 | bnb_free_coin = float(0) 55 | for asset in userAssets: 56 | if asset.get('asset') == coin_symbol: 57 | free_coin = asset.get('free') 58 | loan_coin = asset.get('borrowed') 59 | net_coin = asset.get('netAsset') 60 | if asset.get('asset') == bnb_symbol: 61 | bnb_free_coin = asset.get('free') 62 | if asset.get('asset') == usdt_symbol: 63 | free_cash = asset.get('free') 64 | # 计算账号信息 end 65 | 66 | # 总投入成本 67 | total_base_balance = float(base_balance) + float(base_bnb_balance * current_bnb_price) 68 | # 利润,总资产-全部借款,USDT汇率 69 | profit_balance = (float(account.get("totalNetAssetOfBtc")) * current_btc_price) - total_base_balance 70 | # 利润率 71 | profit_percent = float(profit_balance / total_base_balance) 72 | message += "*** 投入本金: {} USDT, {} BNB (约为{}: {:.2f}) ***\n".format(base_balance, base_bnb_balance, fiat_symbol, total_base_balance * fiat_price) 73 | message += "*** 日累计盈亏: {0:.2f} USDT(约为{1}: {2:.2f} ),日利润率{3:.3%} ***\n\n".format(profit_balance, fiat_symbol, float(profit_balance * fiat_price), profit_percent) 74 | message += "-"*30 75 | message += "\n杠杆账号资产详情:\n" 76 | message += "\n当前杠杆账号风险率(默认3倍杠杆,小于2表示危险,需要人工处理): %s\n" % account.get("marginLevel") 77 | message += "当前杠杆账号总资产估值: {0} BTC (约为 {1:.2f} USDT )\n".format( account.get("totalAssetOfBtc"), float(account.get("totalAssetOfBtc")) * current_btc_price ) 78 | message += "当前杠杆账号账户权益: {0} BTC (约为 {1:.2f} USDT )\n".format( account.get("totalNetAssetOfBtc"), float(account.get("totalNetAssetOfBtc")) * current_btc_price ) 79 | 80 | message += "当前帐户可用现金{0}是: {1}\n".format(usdt_symbol, float(free_cash) ) 81 | message += "当前账户可用代币{0}是: {1}\n".format(coin_symbol, float(free_coin) ) 82 | message += "当前账户可用{0}是: {1}\n".format(bnb_symbol, float(bnb_free_coin) ) 83 | print(message) 84 | asset_message = "当前借入{}资产是{}\n".format(coin_symbol, loan_coin) 85 | asset_message += "当前{}的净资产是{}\n".format(coin_symbol, net_coin) 86 | print(asset_message) 87 | 88 | 89 | def get_margin_stream_keepalive(listen_key): 90 | result = client.margin_stream_keepalive(listen_key) 91 | print(result) 92 | 93 | # get listen key for websocket 94 | def get_margin_listen_key(): 95 | key = client.margin_stream_get_listen_key() 96 | print(key) 97 | 98 | def retry_websocket(): 99 | bm = BinanceSocketManager(client) 100 | conn_key = bm.start_margin_socket(process_message) 101 | print("websocket Conn key: " + conn_key) 102 | bm.start() 103 | time.sleep(1) 104 | bm.stop_socket(conn_key) 105 | conn_key = bm.start_margin_socket(process_message) 106 | print("renewer websocket Conn key: " + conn_key) 107 | 108 | def process_message(msg): 109 | print(msg) 110 | 111 | def is_max_margins(max_margins): 112 | orders = client.get_open_margin_orders(symbol=pair_symbol) 113 | if len(orders) > max_margins: 114 | return True 115 | else: 116 | return False 117 | 118 | def loan_asset(coin_symbol, qty): 119 | account = client.get_margin_account() 120 | userAssets = account.get('userAssets') 121 | origin_loan = float(0) 122 | for asset in userAssets: 123 | if asset.get('asset') == 'EOS': 124 | origin_loan = float(asset.get('borrowed')) 125 | qty = qty - origin_loan 126 | if qty <= float(0): 127 | print('don\'t need loan, original loan: {}'.format(origin_loan)) 128 | pass 129 | else: 130 | transaction = client.create_margin_loan(asset=coin_symbol, 131 | amount=qty) 132 | print(transaction) 133 | 134 | def repay_asset(coin_symbol, qty): 135 | transaction = client.repay_margin_loan(asset=coin_symbol, 136 | amount=qty) 137 | print(transaction) 138 | 139 | def get_all_margin_orders(): 140 | orders = client.get_open_margin_orders(symbol=pair_symbol) 141 | print(orders) 142 | 143 | def cacel_all_margin_orders(): 144 | orders = client.get_open_margin_orders(symbol=pair_symbol) 145 | for o in orders: 146 | result = client.cancel_margin_order(symbol=pair_symbol, 147 | orderId=o.get('orderId')) 148 | print(result) 149 | 150 | def margin_account(): 151 | account = client.get_margin_account() 152 | userAssets = account.get('userAssets') 153 | for asset in userAssets: 154 | if asset.get('asset') == coin_symbol: 155 | eos_free = asset.get('free') 156 | if asset.get('asset') == usdt_symbol: 157 | usdt_free = asset.get('free') 158 | 159 | print(eos_free, usdt_free) 160 | 161 | def new_margin_order(): 162 | ticker = client.get_orderbook_ticker(symbol=pair_symbol) 163 | print("Current bid price: {}".format(ticker.get('bidPrice'))) 164 | print("Current ask price: {}".format(ticker.get('askPrice'))) 165 | buy_price = float(ticker.get('bidPrice'))*float(1-0.005) 166 | buy_price = '%.4f' % buy_price 167 | 168 | sell_price = float(ticker.get('askPrice'))*float(1+0.005) 169 | sell_price = '%.4f' % sell_price 170 | 171 | buy_order = client.create_margin_order(symbol=pair_symbol, 172 | side=SIDE_BUY, 173 | type=ORDER_TYPE_LIMIT, 174 | quantity=10.0, 175 | price=buy_price, 176 | timeInForce=TIME_IN_FORCE_GTC) 177 | 178 | sell_order = client.create_margin_order(symbol=pair_symbol, 179 | side=SIDE_SELL, 180 | type=ORDER_TYPE_LIMIT, 181 | quantity=10.0, 182 | price=sell_price, 183 | timeInForce=TIME_IN_FORCE_GTC) 184 | 185 | if __name__ == "__main__": 186 | run() -------------------------------------------------------------------------------- /binance_bot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 币安自动交易bot 4 | KDJ + Bollinger Close Price边界跨过布林线后,通过KDJ的金叉给一个捕鱼信号。每次只出一单。 5 | 只捕捉做多的信号,一次交易,最后是换成USDT,适合没有机器学习的场景 6 | """ 7 | import time 8 | from datetime import datetime,timedelta 9 | import sys 10 | import signal 11 | import threading 12 | 13 | import logging 14 | from logging.handlers import TimedRotatingFileHandler 15 | import re 16 | 17 | import pandas as pd 18 | import talib 19 | from talib import MA_Type 20 | import numpy as np #computing multidimensionla arrays 21 | 22 | from binance.client import Client 23 | from binance.enums import * 24 | from binance.websockets import BinanceSocketManager 25 | from twisted.internet import reactor 26 | 27 | from settings import MarginAccount 28 | from settings import BinanceKey1 29 | api_key = BinanceKey1['api_key'] 30 | api_secret = BinanceKey1['api_secret'] 31 | client = Client(api_key, api_secret, {"verify": True, "timeout": 10000}) 32 | 33 | 34 | # logger初始化 35 | 36 | logger = logging.getLogger("kdj_bollinger_bot") 37 | 38 | log_format = "%(asctime)s [%(threadName)s] [%(name)s] [%(levelname)s] %(filename)s[line:%(lineno)d] %(message)s" 39 | log_level = 10 40 | handler = TimedRotatingFileHandler("kdj_bollinger_bot.log", when="midnight", interval=1) 41 | handler.setLevel(log_level) 42 | formatter = logging.Formatter(log_format) 43 | handler.setFormatter(formatter) 44 | handler.suffix = "%Y%m%d" 45 | handler.extMatch = re.compile(r"^\d{8}$") 46 | logger.addHandler(handler) 47 | logger.setLevel(logging.INFO) 48 | 49 | # step0 初始化参数,请咨询核对调优 50 | pair_symbol = MarginAccount['pair_symbol'] 51 | coin_symbol = MarginAccount['coin_symbol'] 52 | usdt_symbol = MarginAccount['usdt_symbol'] 53 | loan = MarginAccount['loan_balance'] 54 | depth = MarginAccount['depth'] 55 | coin_balance = MarginAccount['coin_balance'] 56 | # 每手交易的币数量。 57 | qty = coin_balance / depth 58 | base_balance = MarginAccount['base_balance'] 59 | # 最大交易对 60 | max_margins = MarginAccount['max_margins'] 61 | # 账户余额必须大于30%才能交易 62 | free_cash_limit_percentile = MarginAccount['free_cash_limit_percentile'] 63 | # 价格精度 64 | price_accuracy = MarginAccount['price_accuracy'] 65 | # 数量进度 66 | qty_accuracy = MarginAccount['qty_accuracy'] 67 | # 趋势判断值 68 | trend_limit_tan = MarginAccount['trend_limit_tan'] 69 | #是否借币开关 70 | loan_enabled = MarginAccount['loan_enabled'] 71 | 72 | # BinanceSocketManager 全局变量初始化 73 | bm = None 74 | conn_key = None 75 | indicator = None 76 | #memory order list 77 | long_order = [] 78 | short_order = [] 79 | order_dt_started = datetime.utcnow() 80 | close_price_list = [] #当前 close价格 81 | 82 | def run(): 83 | initialize_arb() 84 | 85 | def initialize_arb(): 86 | welcome_message = "\n\n---------------------------------------------------------\n\n" 87 | welcome_message+= "Binance auto trading bot\n" 88 | bot_start_time = str(datetime.now()) 89 | welcome_message+= "\nBot Start Time: {}\n\n\n".format(bot_start_time) 90 | logger.info(welcome_message) 91 | time.sleep(1) 92 | status = client.get_system_status() 93 | logger.info("\nExchange Status: {}" % status) 94 | 95 | # step1 第一入口 96 | # 1.2 借出币 97 | if loan_enabled: 98 | loan_asset(coin_symbol, loan) 99 | 100 | # step2 监听杠杆交易 101 | global bm, conn_key 102 | bm = BinanceSocketManager(client) 103 | conn_key = bm.start_margin_socket(process_message) 104 | logger.info("websocket Conn key: {}".format(conn_key) ) 105 | bm.setDaemon(True) 106 | bm.start() 107 | 108 | # 根据KDJ线 + Bolling线 定期执行, 主进程 109 | t = threading.Thread(target=kdj_signal_loop, args=(pair_symbol,)) 110 | t.start() 111 | 112 | # 30分钟ping user websocket,key可以存活1个小时 113 | l = threading.Thread(target=get_margin_stream_keepalive, args=(conn_key,)) 114 | l.setDaemon(True) 115 | l.start() 116 | 117 | # 30分钟检查一下过期的订单,主动取消。保证可以挂新订单。 118 | t = threading.Thread(target=outdated_order_clear, args=(pair_symbol,)) 119 | t.setDaemon(True) 120 | t.start() 121 | 122 | 123 | """ 124 | 超过区间日期(这里默认是3天)的订单自动取消 125 | """ 126 | def outdated_order_clear(symbol): 127 | while True: 128 | orders = client.get_open_margin_orders(symbol=symbol) 129 | today = datetime.now() 130 | offset = timedelta(days=-3) 131 | re_date = (today + offset) 132 | re_date_unix = time.mktime(re_date.timetuple()) 133 | for o in orders: 134 | d = float(o["time"])/1000 135 | #挂单时间小于等于3天前 136 | if d <= re_date_unix: 137 | result = client.cancel_margin_order(symbol=symbol, 138 | orderId=o["orderId"]) 139 | logger.info("过期3天的订单自动取消,取消订单交易ID: {}".format(result)) 140 | # 每个小时检查一次 141 | time.sleep(60*60*1) 142 | 143 | """ 144 | KDJ strategy loop 145 | """ 146 | def kdj_signal_loop(symbol): 147 | while True: 148 | kdj_signal_trading(symbol) 149 | time.sleep(5) 150 | 151 | """ 152 | KDJ + bollinger_signal 自动交易策略 153 | bollinger_signal 21,2 154 | 155 | SHORT: 做空单, 1% 156 | LONG:做多单, 1% 157 | MLONG: 中间位置下跌空单, 0.5% 158 | MSHORT: 中间位置上涨, 0.5% 159 | """ 160 | def kdj_signal_trading(symbol): 161 | fastk_period = 9 162 | slowk_period = 3 163 | slowd_period = 3 164 | 165 | try: 166 | candles = client.get_klines(symbol=symbol, interval=Client.KLINE_INTERVAL_5MINUTE) 167 | df = pd.DataFrame(candles) 168 | df.columns=['timestart','open','high','low','close','?','timeend','?','?','?','?','?'] 169 | df.timestart = [datetime.fromtimestamp(i/1000) for i in df.timestart.values] 170 | df.timeend = [datetime.fromtimestamp(i/1000) for i in df.timeend.values] 171 | except Exception as e: 172 | logger.error("Connection aborted, {}".format(e)) 173 | return 174 | 175 | low_list = df['low'].rolling(9, min_periods=9).min() 176 | low_list.fillna(value = df['low'].expanding().min(), inplace = True) 177 | high_list = df['high'].rolling(9, min_periods=9).max() 178 | high_list.fillna(value = df['high'].expanding().max(), inplace = True) 179 | 180 | # Close price 181 | close_data = [float(x) for x in df.close.values] 182 | np_close_data = np.array(close_data) 183 | rsv = (np_close_data - low_list) / (high_list - low_list) * 100 184 | 185 | #high price 186 | high_data = [float(x) for x in df.high.values] 187 | np_high_data = np.array(high_data) 188 | 189 | #low price 190 | low_data = [float(x) for x in df.low.values] 191 | np_low_data = np.array(low_data) 192 | 193 | #Bollinger range 194 | up,mid,down=talib.BBANDS(np_close_data,21,nbdevup=2,nbdevdn=2) 195 | cur_up = up[-1] 196 | cur_md = mid[-1] 197 | cur_dn = down[-1] 198 | 199 | logger.info("===================START=============================") 200 | logger.info("Bollinger: up: {:.2f}, mid: {:.2f}, down: {:.2f}".format(up[-1],mid[-1],down[-1])) 201 | 202 | df['K'] = pd.DataFrame(rsv).ewm(com=2).mean() 203 | df['D'] = df['K'].ewm(com=2).mean() 204 | df['J'] = 3 * df['K'] - 2 * df['D'] 205 | np_k = np.array(df['K']) 206 | np_d = np.array(df['D']) 207 | np_j = np.array(df['J']) 208 | 209 | global indicator, long_order, short_order, order_dt_started, close_price_list 210 | logger.info("KDJ: k: {:.2f}, d: {:.2f}, j: {:.2f}".format(np_k[-1],np_d[-1],np_j[-1])) 211 | logger.info("K - D: {}".format(float(np_k[-1]) - float(np_d[-1]))) 212 | logger.info("high_data: {},low_data: {}, close_data: {} => DN:{}, UP:{}".format(np_high_data[-1],np_low_data[-1],np_close_data[-1], cur_dn, cur_up)) 213 | logger.info("LONG indicator: {}, long_order:{}".format(float(np_low_data[-1]) <= float(cur_dn), len(long_order))) 214 | logger.info("SHORT indicator: {}, short_order:{}".format(float(np_high_data[-1]) >= float(cur_up),len(short_order))) 215 | 216 | order_dt_ended = datetime.utcnow() 217 | logger.info("挂单时间间隔:{} > {}".format((order_dt_ended - order_dt_started).total_seconds(), 60*5)) 218 | logger.info("===================END=============================") 219 | 220 | 221 | # 交易策略,吃多单 222 | if check_range(float(np_k[-1]) - float(np_d[-1]), -2.0, 2.0) and \ 223 | float(np_k[-1]) < float(30) and float(np_j[-1]) < float(30) and \ 224 | float(np_low_data[-1]) <= float(cur_dn) and \ 225 | (order_dt_ended - order_dt_started).total_seconds() > 60*5: 226 | 227 | indicator = "LONG" # 做多 228 | close_price_list.append(float(np_close_data[-1])) #加入当前最新价格 229 | order_dt_started = datetime.utcnow() # 5分钟只能下一单 230 | elif check_range(float(np_k[-1]) - float(np_d[-1]), -2.0, 2.0) and \ 231 | float(np_k[-1]) > float(70) and float(np_j[-1]) > float(70) and \ 232 | float(np_high_data[-1]) >= float(cur_up) and \ 233 | (order_dt_ended - order_dt_started).total_seconds() > 60*5: 234 | 235 | indicator = "SHORT" # 做空 236 | close_price_list.append(float(np_close_data[-1])) #加入当前最新价格 237 | order_dt_started = datetime.utcnow() # 5分钟只能下一单 238 | elif check_range(float(np_k[-1]) - float(np_d[-1]), -2.0, 2.0) and \ 239 | float(np_low_data[-1]) > float(cur_dn) and \ 240 | float(np_high_data[-1]) < float(cur_up) and \ 241 | (order_dt_ended - order_dt_started).total_seconds() > 60*5: 242 | 243 | indicator = "GRID" # 做网格 244 | close_price_list.append(float(np_close_data[-1])) #加入当前最新价格 245 | order_dt_started = datetime.utcnow() # 5分钟只能下一单 246 | 247 | # 这里加上延时判断,当获得信号后,判断60/5 = 12 次 信号中,close报价list是按照涨的趋势还是跌的趋势,这样可以果断修正交易策略 248 | if indicator in ["SHORT", "LONG", "GRID"]: 249 | close_price_list.append(float(np_close_data[-1])) #加入当前最新价格 250 | logger.info("最新价格信号列表{}".format(close_price_list)) 251 | # 开始计算close次数 252 | if len(close_price_list) >= 36: 253 | #判断list的数字趋势是涨还是跌 254 | indicator = check_indicator(close_price_list, indicator) 255 | logger.info("此次下单信号为: {}".format(indicator)) 256 | new_margin_order(symbol,qty,indicator) # 下单 257 | #重置条件,close_price_list.clear and indicator=NONE 258 | close_price_list.clear() 259 | indicator = None 260 | 261 | """ 262 | 检查当前价格趋势 263 | * k > 0.1763 表示上升 264 | * k < -0.1763 表示下降 265 | * 其他,则表示平稳或震荡 266 | """ 267 | def check_indicator(close_price_list, indicator): 268 | index=[*range(1,len(close_price_list)+1)] 269 | k=trendline(index, close_price_list, 3) #采用三次多项式拟合曲线 270 | logger.info("涨跌k趋势指标: {}, k > {} 表示上升, k < {} 表示下降".format(k, trend_limit_tan[0], trend_limit_tan[1])) 271 | if float(k) > float(trend_limit_tan[0]): #上升 应该做多 272 | if indicator == "SHORT": 273 | indicator = "LONG" 274 | elif float(k) < float(trend_limit_tan[1]): #下降 应该做空 275 | if indicator == "LONG": 276 | indicator = "SHORT" 277 | #返回信号值 278 | return indicator 279 | 280 | """ 281 | 最小二乘法将序列拟合成直线。后根据直线的斜率k去取得序列的走势 282 | * k > 0.1763 表示上升 283 | * k < -0.1763 表示下降 284 | * 其他,则表示平稳或震荡 285 | """ 286 | def trendline(index,data, order=1): 287 | coeffs = np.polyfit(index, list(data), order) 288 | slope = coeffs[-2] 289 | return float(slope) 290 | 291 | """ 292 | 检查K,D信号是否越界 293 | """ 294 | def check_range(value,left_tick,right_tick): 295 | if left_tick <= value <= right_tick: 296 | return True 297 | return False 298 | 299 | """ 300 | 下单函数,做空,做多 301 | """ 302 | def new_margin_order(symbol,qty,indicator): 303 | # 当前报价口的买卖价格 304 | ticker = client.get_orderbook_ticker(symbol=symbol) 305 | logger.info("Current bid price: {}".format(ticker.get('bidPrice'))) 306 | logger.info("Current ask price: {}".format(ticker.get('askPrice'))) 307 | buy_price = float(ticker.get('bidPrice')) 308 | buy_price = price_accuracy % buy_price 309 | sell_price = float(ticker.get('askPrice')) 310 | sell_price = price_accuracy % sell_price 311 | 312 | # 挂单总数量给予限制 313 | if is_max_margins(max_margins) == True: 314 | logger.warning("Come across max margins limits....return, don't do order anymore.") 315 | return 316 | 317 | #计算当前账号的币的余额够不够,账户币余额必须大于free_cash_limit_percentile才能交易 318 | account = client.get_margin_account() 319 | userAssets = account.get('userAssets') 320 | free_coin = float(0) 321 | free_cash = float(0) 322 | for asset in userAssets: 323 | if asset.get('asset') == coin_symbol: 324 | free_coin = float(asset.get('free')) 325 | if asset.get('asset') == usdt_symbol: 326 | free_cash = float(asset.get('free')) 327 | # 规则:账户余额必须大于 base_balance * free_cash_limit_percentile 才能交易 328 | if free_coin < qty: 329 | logger.warning("Current Account coin balance < {} {}. don't do order anymore.".format(qty, coin_symbol)) 330 | buy_coin_qty = float(qty_accuracy % float(loan * 0.5)) 331 | if free_cash > (base_balance * free_cash_limit_percentile) and (buy_coin_qty * float(buy_price)) < free_cash: 332 | repay_asset(pair_symbol, coin_symbol, buy_coin_qty, "BUY") 333 | return 334 | if free_cash < (base_balance * free_cash_limit_percentile): 335 | logger.warning("Current Account cash balance < {}%. don't do order anymore.".format(free_cash_limit_percentile * 100)) 336 | sell_coin_qty = float(qty_accuracy % float(free_coin - loan)) 337 | if free_coin > loan and (sell_coin_qty * float(sell_price)) > 10: # 币安要求最小交易金额必须大于10 338 | repay_asset(pair_symbol, coin_symbol, sell_coin_qty, SIDE_SELL) 339 | return 340 | 341 | # LONG or SHORT or GRID 342 | if indicator == "LONG": 343 | buy_price = float(ticker.get('bidPrice'))*float(1) 344 | buy_price = price_accuracy % buy_price 345 | 346 | sell_price = float(ticker.get('askPrice'))*float(1+0.008) 347 | sell_price = price_accuracy % sell_price 348 | 349 | buy_order = client.create_margin_order(symbol=symbol, 350 | side=SIDE_BUY, 351 | type=ORDER_TYPE_LIMIT, 352 | quantity=qty, 353 | price=buy_price, 354 | sideEffectType='AUTO_REPAY', 355 | timeInForce=TIME_IN_FORCE_GTC) 356 | 357 | sell_order = client.create_margin_order(symbol=symbol, 358 | side=SIDE_SELL, 359 | type=ORDER_TYPE_LIMIT, 360 | quantity=qty, 361 | price=sell_price, 362 | sideEffectType='MARGIN_BUY', 363 | timeInForce=TIME_IN_FORCE_GTC) 364 | 365 | logger.info("做多:买单ID:{}, 价格:{}, 数量:{}".format(buy_order, buy_price, qty)) 366 | logger.info("做多:卖单ID:{}, 价格:{}, 数量:{}".format(sell_order, sell_price, qty)) 367 | long_order.append( buy_order.get("orderId") ) 368 | long_order.append( sell_order.get("orderId") ) 369 | 370 | elif indicator == "GRID": 371 | buy_price = float(ticker.get('bidPrice'))*float(1-0.002) 372 | buy_price = price_accuracy % buy_price 373 | 374 | sell_price = float(ticker.get('askPrice'))*float(1+0.002) 375 | sell_price = price_accuracy % sell_price 376 | 377 | buy_order = client.create_margin_order(symbol=symbol, 378 | side=SIDE_BUY, 379 | type=ORDER_TYPE_LIMIT, 380 | quantity=qty, 381 | price=buy_price, 382 | sideEffectType='AUTO_REPAY', 383 | timeInForce=TIME_IN_FORCE_GTC) 384 | 385 | sell_order = client.create_margin_order(symbol=symbol, 386 | side=SIDE_SELL, 387 | type=ORDER_TYPE_LIMIT, 388 | quantity=qty, 389 | price=sell_price, 390 | sideEffectType='MARGIN_BUY', 391 | timeInForce=TIME_IN_FORCE_GTC) 392 | 393 | logger.info("做网格:买单ID:{}, 价格:{}, 数量:{}".format(buy_order, buy_price, qty)) 394 | logger.info("做网格:卖单ID:{}, 价格:{}, 数量:{}".format(sell_order, sell_price, qty)) 395 | short_order.append( buy_order.get("orderId") ) 396 | short_order.append( sell_order.get("orderId") ) 397 | 398 | elif indicator == "SHORT": 399 | buy_price = float(ticker.get('bidPrice'))*float(1-0.003) 400 | buy_price = price_accuracy % buy_price 401 | 402 | sell_price = float(ticker.get('askPrice'))*float(1) 403 | sell_price = price_accuracy % sell_price 404 | 405 | buy_order = client.create_margin_order(symbol=symbol, 406 | side=SIDE_BUY, 407 | type=ORDER_TYPE_LIMIT, 408 | quantity=qty, 409 | price=buy_price, 410 | sideEffectType='AUTO_REPAY', 411 | timeInForce=TIME_IN_FORCE_GTC) 412 | 413 | sell_order = client.create_margin_order(symbol=symbol, 414 | side=SIDE_SELL, 415 | type=ORDER_TYPE_LIMIT, 416 | quantity=qty, 417 | price=sell_price, 418 | sideEffectType='MARGIN_BUY', 419 | timeInForce=TIME_IN_FORCE_GTC) 420 | 421 | logger.info("做空:买单ID:{}, 价格:{}, 数量:{}".format(buy_order, buy_price, qty)) 422 | logger.info("做空:卖单ID:{}, 价格:{}, 数量:{}".format(sell_order, sell_price, qty)) 423 | short_order.append( buy_order.get("orderId") ) 424 | short_order.append( sell_order.get("orderId") ) 425 | else: 426 | logger.info("NO CHANCE: indicator:{}".format(indicator)) 427 | 428 | ''' 429 | purpose: coin补仓, 提供50%的币的数量 430 | ''' 431 | def repay_asset(pair_symbol, coin_symbol, qty, type): 432 | ticker = client.get_orderbook_ticker(symbol=pair_symbol) 433 | print("Current bid price: {}".format(ticker.get('bidPrice'))) 434 | print("Current ask price: {}".format(ticker.get('askPrice'))) 435 | 436 | if type == SIDE_BUY: 437 | buy_price = float(ticker.get('bidPrice')) 438 | buy_price = price_accuracy % buy_price 439 | 440 | buy_order = client.create_margin_order(symbol=pair_symbol, 441 | side=SIDE_BUY, 442 | type=ORDER_TYPE_LIMIT, 443 | quantity=qty, 444 | price=buy_price, 445 | timeInForce=TIME_IN_FORCE_GTC) 446 | logger.info("自动补仓代币 {}: {}, 补仓单价:{}, order: {}".format(coin_symbol, qty, buy_price, buy_order)) 447 | 448 | elif type == SIDE_SELL: 449 | sell_price = float(ticker.get('askPrice')) 450 | sell_price = price_accuracy % sell_price 451 | 452 | sell_order = client.create_margin_order(symbol=pair_symbol, 453 | side=SIDE_SELL, 454 | type=ORDER_TYPE_LIMIT, 455 | quantity=qty, 456 | price=sell_price, 457 | timeInForce=TIME_IN_FORCE_GTC) 458 | logger.info("自动兑换代币 {}: {}, 兑换单价:{}, order: {}".format(coin_symbol, qty, sell_price, sell_order)) 459 | 460 | ''' 461 | purpose: 杠杆交易怕平仓,所以通过最简化的交易单数可以判断出是否超出仓位 462 | 463 | ''' 464 | def is_max_margins(max_margins): 465 | orders = client.get_open_margin_orders(symbol=pair_symbol) 466 | if len(orders) > max_margins: 467 | return True 468 | else: 469 | return False 470 | 471 | ''' 472 | purpose: 自动借币 473 | 474 | if account.loan 有币: 475 | pass 476 | ''' 477 | def loan_asset(coin_symbol, qty): 478 | account = client.get_margin_account() 479 | userAssets = account.get('userAssets') 480 | origin_loan = float(0) 481 | for asset in userAssets: 482 | if asset.get('asset') == coin_symbol: 483 | origin_loan = float(asset.get('borrowed')) 484 | qty = qty - origin_loan 485 | if qty <= float(0): 486 | logger.info('don\'t need loan, original loan: {}'.format(origin_loan)) 487 | pass 488 | else: 489 | transaction = client.create_margin_loan(asset=coin_symbol, 490 | amount=qty) 491 | logger.info(transaction) 492 | 493 | def process_message(msg): 494 | if msg['e'] == 'error': 495 | error_msg = "\n\n---------------------------------------------------------\n\n" 496 | error_msg += "websocket error:\n" 497 | error_msg += msg.get('m') 498 | logger.warning(error_msg) 499 | # close and restart the socket 500 | global conn_key 501 | bm.stop_socket(conn_key) 502 | conn_key = bm.start_margin_socket(process_message) 503 | logger.info("renewer websocket Conn key: {}".format(conn_key)) 504 | else: 505 | # 处理event executionReport 506 | if msg.get('e') == 'executionReport' and msg.get('s') == pair_symbol: 507 | logger.info(msg) 508 | # 当有交易成功的挂单,更新交易对字典 509 | # "i": 4293153, // orderId 510 | if msg.get('e') == 'executionReport' and msg.get('s') == pair_symbol and msg.get('X') == ORDER_STATUS_FILLED: 511 | global short_order, long_order 512 | if msg.get('i') in short_order: 513 | short_order.remove( msg.get('i') ) 514 | logger.info("做空单交易完成,交易ID: {}".format(msg.get('i'))) 515 | elif msg.get('i') in long_order: 516 | long_order.remove( msg.get('i') ) 517 | logger.info("做多单交易完成,交易ID: {}".format(msg.get('i'))) 518 | 519 | ''' 520 | Purpose: Keepalive a user data stream to prevent a time out. 521 | User data streams will close after 60 minutes. 522 | It's recommended to send a ping about every 30 minutes. 523 | ''' 524 | def get_margin_stream_keepalive(listen_key): 525 | while True: 526 | result = client.margin_stream_keepalive(listen_key) 527 | time.sleep(60 * 30) 528 | 529 | def term_sig_handler(signum, frame): 530 | print('catched singal: %d' % signum) 531 | reactor.stop() 532 | sys.exit() 533 | 534 | if __name__ == "__main__": 535 | signal.signal(signal.SIGTERM, term_sig_handler) 536 | signal.signal(signal.SIGINT, term_sig_handler) 537 | signal.signal(signal.SIGHUP, term_sig_handler) 538 | run() --------------------------------------------------------------------------------