├── .editorconfig ├── .vscode └── launch.json ├── Dockerfile ├── README.md ├── binance_grid_trader ├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── bitcoin_arbitrage.md ├── config.json ├── gateway │ ├── __init__.py │ ├── binance_future.py │ └── binance_spot.py ├── grid_trader_log.txt ├── main.py ├── requirements.txt ├── start.sh ├── test.py ├── trader │ ├── binance_future_trader.py │ └── binance_trader.py └── utils │ ├── __init__.py │ ├── config.py │ └── utility.py ├── docker-compose.yml └── qr.jpeg /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*.yml] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Launch Main", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/grid_trader/main.py", 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | ENV PLATFORM="binance_spot" \ 4 | SYMBOL="BNBUSDT" \ 5 | API_DOMAIN="binance.com" \ 6 | API_KEY="" \ 7 | API_SECRET="" \ 8 | GAP_PERCENT="0.01" \ 9 | QUANTITY="1" \ 10 | MIN_PRICE="0.0001" \ 11 | MIN_QTY="0.01" \ 12 | MAX_ORDERS="1" \ 13 | PROXY_HOST="" \ 14 | PROXY_PORT="0" 15 | 16 | COPY ./binance_grid_trader /root/binance_grid_trader 17 | WORKDIR /root/binance_grid_trader 18 | 19 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \ 20 | apk --no-cache add \ 21 | jq \ 22 | curl \ 23 | moreutils \ 24 | py3-pip && \ 25 | pip install --upgrade pip && \ 26 | pip install -r requirements.txt 27 | 28 | CMD cat config.json | \ 29 | jq ".platform = \"${PLATFORM}\"" | \ 30 | jq ".symbol = \"${SYMBOL}\"" | \ 31 | jq ".api_domain = \"${API_DOMAIN}\"" | \ 32 | jq ".api_key = \"${API_KEY}\"" | \ 33 | jq ".api_secret = \"${API_SECRET}\"" | \ 34 | jq ".gap_percent = \"${GAP_PERCENT}\"" | \ 35 | jq ".quantity = \"${QUANTITY}\"" | \ 36 | jq ".min_price = \"${MIN_PRICE}\"" | \ 37 | jq ".min_qty = \"${MIN_QTY}\"" | \ 38 | jq ".max_orders = \"${MAX_ORDERS}\"" | \ 39 | jq ".proxy_host = \"${PROXY_HOST}\"" | \ 40 | jq ".proxy_port = \"${PROXY_PORT}\"" | sponge config.json && \ 41 | python3 -u main.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 网格交易策略 2 | 3 | ## 为什么要使用此策略 4 | 1. 网格交易策略风险小,回撤率低,收益靠谱 5 | 2. 基本不用人工干预,可在服务器24小时挂机 6 | 3. 非常适合币圈行情 7 | 8 | ### 环境要求 9 | 1. 安装 docker 10 | 2. 安装 docker-compose 11 | 12 | ### Centos7安装参考(推荐): 13 | - [https://www.cnblogs.com/ruanqin/p/11082506.html](https://www.cnblogs.com/ruanqin/p/11082506.html) 14 | 15 | #### Win10安装参考: 16 | - [https://www.cnblogs.com/makalochen/p/14286100.html](https://www.cnblogs.com/makalochen/p/14286100.html) 17 | 18 | #### Ubuntu安装参考: 19 | - [https://www.jianshu.com/p/482d1eb4d9a2](https://www.jianshu.com/p/482d1eb4d9a2) 20 | 21 | ### 第一步 注册币安账号 22 | 23 | - 注册币安账号返佣10% 24 | - [https://accounts.binancezh.io/zh-CN/register?ref=F6P7LQM3](https://accounts.binancezh.io/zh-CN/register?ref=F6P7LQM3) 25 | - ![image](https://github.com/tiansin/docker_grid_trader/blob/master/qr.jpeg) 26 | 27 | - 生成API密钥: 28 | - [https://www.binance.com/zh-CN/my/settings/api-management](https://www.binance.com/zh-CN/my/settings/api-management) 29 | 30 | ### 第二步 创建并修改`docker-compose.yml`配置文件 31 | 32 | docker-compose.yml 文件内容 33 | ```yml 34 | version: '2' 35 | 36 | services: 37 | binance_grid_trader: 38 | image: tiansin/binance_grid_trader:latest 39 | restart: always 40 | environment: 41 | PLATFORM: "binance_spot" 42 | SYMBOL: "BNBUSDT" 43 | API_DOMAIN: "binancezh.co" 44 | API_KEY: "" 45 | API_SECRET: "" 46 | GAP_PERCENT: 0.01 47 | QUANTITY: 1 48 | MIN_PRICE: 0.0001 49 | MIN_QTY: 0.01 50 | MAX_ORDERS: 1 51 | PROXY_HOST: "" 52 | PROXY_PORT: 0 53 | logging: 54 | options: 55 | max-size: 10m 56 | max-file: "3" 57 | ``` 58 | 59 | 修改其中的环境变量 60 | ```yml 61 | PLATFORM: "binance_spot" 62 | SYMBOL: "BNBUSDT" 63 | API_DOMAIN: "binance.com" 64 | API_KEY: "API密钥的 API Key" 65 | API_SECRET: "API密钥的 Secret Key" 66 | GAP_PERCENT: 0.01 67 | QUANTITY: 1 68 | MIN_PRICE: 0.0001 69 | MIN_QTY: 0.01 70 | MAX_ORDERS: 1 71 | PROXY_HOST: "" 72 | PROXY_PORT: 0 73 | ``` 74 | 75 | 1. `PLATFORM` 是交易的平台, 填写 binance_spot (现货) 或者 binance_future (合约) 76 | 2. `SYMBOL` 交易对: BTCUSDT, BNBUSDT等 77 | 3. `API_DOMAIN` API的域名, 由于大陆网络无法访问币安API, 需要`配置代理`或者购买`香港`或海外服务器进行配置, 本地测试或条件有限可以配置成`binancezh.co`(偶尔有延时) 78 | 4. `API_KEY` : 从交易所获取,第一步生成的API密钥 79 | 5. `API_SECRET`: 交易所获取,第一步生成的API密钥 80 | 6. `GAP_PERCENT`: 网格交易的价格间隙 81 | 7. `QUANTITY` : 每次下单的数量 82 | 8. `MIN_PRICE`: 价格波动的最小单位, 用来计算价格精度: 如BTCUSDT 是0.01, 83 | BNBUSDT是0.0001, ETHUSDT 是0.01, 这个价格要从交易所查看,每个交易对不一样。 84 | 85 | 9. `MIN_QTY`: 最小的下单量, 现货B要求最小下单是10USDT等值的币, 而对于合约来说,BTCUSDT要求是0.001个BTC 86 | 10. `MAX_ORDERS`: 单边的挂单量,超过则取消之前的挂单 87 | 11. `PROXY_HOST`: 如果需要用代理的话,请填写你的代理,如: 127.0.0.1 88 | 12. `PROXY_PORT`: 代理端口号,如: 8080 89 | 90 | 91 | ### 第三步 上传配置文件并启动服务 92 | 1. 把修改好的`docker-compose.yml`文件上传到 `/usr/src/` 目录 93 | 2. 执行命令`cd /usr/src && docker-compose up -d` 94 | 3. 使用命令`docker-compose logs -f`可以查看执行日志,检查程序运转是否正常 95 | 96 | ## 提供一些本人在正式环境跑过的一些配置模板 97 | 98 | *需替换密钥,可按照您的资金配比修改下单的数量,有需要可以配置代理* 99 | 100 | - DOGEUSDT 101 | ```yml 102 | PLATFORM: "binance_spot" 103 | SYMBOL: "DOGEUSDT" 104 | API_DOMAIN: "binance.com" 105 | API_KEY: "API密钥的 API Key" 106 | API_SECRET: "API密钥的 Secret Key" 107 | GAP_PERCENT: 0.016 108 | QUANTITY: 100 109 | MIN_PRICE: 0.00001 110 | MIN_QTY: 50 111 | MAX_ORDERS: 1 112 | PROXY_HOST: "" 113 | PROXY_PORT: 0 114 | ``` 115 | 116 | - BNBUSDT 117 | ```yml 118 | PLATFORM: "binance_spot" 119 | SYMBOL: "BNBUSDT" 120 | API_DOMAIN: "binance.com" 121 | API_KEY: "API密钥的 API Key" 122 | API_SECRET: "API密钥的 Secret Key" 123 | GAP_PERCENT: 0.011 124 | QUANTITY: 0.1 125 | MIN_PRICE: 0.01 126 | MIN_QTY: 0.05 127 | MAX_ORDERS: 1 128 | PROXY_HOST: "" 129 | PROXY_PORT: 0 130 | ``` 131 | 132 | - CAKEUSDT 133 | ```yml 134 | PLATFORM: "binance_spot" 135 | SYMBOL: "CAKEUSDT" 136 | API_DOMAIN: "binance.com" 137 | API_KEY: "API密钥的 API Key" 138 | API_SECRET: "API密钥的 Secret Key" 139 | GAP_PERCENT: 0.015 140 | QUANTITY: 1.0 141 | MIN_PRICE: 0.001 142 | MIN_QTY: 0.5 143 | MAX_ORDERS: 1 144 | PROXY_HOST: "" 145 | PROXY_PORT: 0 146 | ``` 147 | 148 | - MATICUSDT 149 | ```yml 150 | PLATFORM: "binance_spot" 151 | SYMBOL: "MATICUSDT" 152 | API_DOMAIN: "binance.com" 153 | API_KEY: "API密钥的 API Key" 154 | API_SECRET: "API密钥的 Secret Key" 155 | GAP_PERCENT: 0.016 156 | QUANTITY: 50 157 | MIN_PRICE: 0.00001 158 | MIN_QTY: 20 159 | MAX_ORDERS: 1 160 | PROXY_HOST: "" 161 | PROXY_PORT: 0 162 | ``` 163 | 164 | ## 网格交易策略适合行情 165 | - 震荡行情 166 | - 适合币圈的高波动率的品种 167 | - 适合现货, 如果交易合约,需要注意防止极端行情爆仓。 168 | 169 | -------------------------------------------------------------------------------- /binance_grid_trader/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.idea/* 6 | *.idea 7 | # C extensions 8 | *.so 9 | .DS_Store 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | nohub.out 61 | myquant.txt 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # SageMath parsed files 86 | *.sage.py 87 | 88 | # Environments 89 | .env 90 | .venv 91 | env/ 92 | venv/ 93 | ENV/ 94 | env.bak/ 95 | venv.bak/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | .spyproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | .idea 110 | .idea/* 111 | 112 | 113 | -------------------------------------------------------------------------------- /binance_grid_trader/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 51bitquant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /binance_grid_trader/README.md: -------------------------------------------------------------------------------- 1 | # 网格交易策略 2 | 3 | ## 使用 how to use 4 | 1. 修改配置文件, config your config file. 5 | 6 | ```json 7 | { 8 | "platform": "binance_spot", 9 | "symbol": "BTCUSDT", 10 | "api_key": "replace your api key here", 11 | "api_secret": "replace your api secret here", 12 | "gap_percent": 0.001, 13 | "quantity": 0.001, 14 | "min_price": 0.01, 15 | "min_qty": 0.001, 16 | "max_orders": 1, 17 | "proxy_host": "127.0.0.1", 18 | "proxy_port": 1087 19 | } 20 | 21 | ``` 22 | 23 | 1. platform 是交易的平台, 填写 binance_spot 或者 binance_future, 24 | 如果交易的是合约的,就填写binance_future. 25 | 2. symbol 交易对: BTCUSDT, BNBUSDT等 26 | 3. api_key : 从交易所获取 27 | 4. api_secret: 交易所获取 28 | 5. gap_percent: 网格交易的价格间隙 29 | 6. quantity : 每次下单的数量 30 | 7. min_price: 价格波动的最小单位, 用来计算价格精度: 如BTCUSDT 是0.01, 31 | BNBUSDT是0.0001, ETHUSDT 是0.01, 这个价格要从交易所查看,每个交易对不一样。 32 | 33 | 8. min_qty: 最小的下单量, 现货B要求最小下单是10USDT等值的币, 而对于合约来说, 34 | BTCUSDT要求是0.001个BTC 35 | 9. max_orders: 单边的下单量 36 | 10. proxy_host: 如果需要用代理的话,请填写你的代理 your proxy host, if you 37 | want proxy 38 | 11. proxy_port: 代理端口号 your proxy port for connecting to binance. 39 | 40 | 41 | 修改完配置文件后,用shell 命令运行下面的shell 命令: 42 | > sh start.sh 43 | 44 | 网格交易的原理视频讲解链接: 45 | [https://www.bilibili.com/video/BV1Jg4y1v7vr/](https://www.bilibili.com/video/BV1Jg4y1v7vr/) 46 | 47 | ## 交易所注册推荐码 48 | 49 | - OKEX 交易所注册推荐码, 手续费返佣20% 50 | - [https://www.okex.me/join/1847111798](https://www.okex.me/join/1847111798) 51 | 52 | - 币安合约推荐码:返佣10% 53 | - [https://www.binancezh.com/cn/futures/ref/51bitquant](https://www.binancezh.com/cn/futures/ref/51bitquant) 54 | 55 | ## 网格交易策略使用行情 56 | - 震荡行情 57 | - 适合币圈的高波动率的品种 58 | - 适合现货, 如果交易合约,需要注意防止极端行情爆仓。 59 | 60 | ## 服务器购买 61 | 推荐ucloud的服务器 62 | - 价格便宜 63 | - 网络速度和性能还不错 64 | - 推荐链接如下:可以通过下面链接够买服务器,可以享受打折优惠: 65 | 66 | [https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing](https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing) 67 | 68 | 视频讲解如下: 69 | [https://www.bilibili.com/video/BV1eK4y147HT/](https://www.bilibili.com/video/BV1eK4y147HT/) 70 | 71 | 72 | ## 部署服务器 73 | 参考我的博客 74 | - [https://www.jianshu.com/p/50fc54ca5ead](https://www.jianshu.com/p/50fc54ca5ead) 75 | - [https://www.jianshu.com/p/61cb2a24a658](https://www.jianshu.com/p/61cb2a24a658) 76 | - [https://www.jianshu.com/p/8c1afcbbe722](https://www.jianshu.com/p/8c1afcbbe722) 77 | 78 | 79 | ## linux 常用命令 80 | 81 | - cd # 是切换工作目录, 具体使用可以通过man 指令 | 指令 --help 82 | - clear 83 | - ls # 列出当前文件夹的文件 84 | - rm 文件名 # 删除文件 85 | - rm -rf 文件夹 # 删除文件 86 | - cp # 拷贝文件 copy 87 | - scp scp binance_grid_trader.zip ubuntu@xxx.xxx.xxx.xxx:/home/ubuntu 88 | - pwd 89 | - mv # 移动或者剪切文件 90 | - ps -ef | grep main.py # 查看进程 91 | - kill 进程id # 杀死当前进程 92 | 93 | ## 部署 94 | 直接把代码上传到服务器, 通过scp命令上传 95 | - 先把代码压缩一下 96 | - 通过一下命令上传到自己的服务器, **xxx.xxx.xxx.xxx**为你的服务器地址, **:/home/ubuntu**表示你上传到服务器的目录 97 | 98 | > scp binance_grid_trader.zip ubuntu@xxx.xxx.xxx.xxx:/home/ubuntu 99 | 100 | 安装软件 sudo apt-get install 软件名称 | 库 101 | > sudo apt-get install unzip # pip install requests 102 | 解压文件 103 | > unzip binance_grid_trader.zip 104 | 105 | 进入该文件夹目录 106 | > cd binance_grid_trader 107 | 108 | 安装依赖包 109 | > pip install -r requirements.txt 110 | 111 | 执行运行脚本 112 | > sh start.sh 113 | 114 | 查看程序运行的id 115 | > ps -ef | grep main.py 116 | 117 | 杀死进程, 关闭程序 118 | > kill <进程ID> 119 | 120 | **linux服务器指令和网格策略实盘部署过程如下** 121 | [https://www.bilibili.com/video/BV1mK411n7JW/](https://www.bilibili.com/video/BV1mK411n7JW/) 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /binance_grid_trader/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansin/docker_grid_trader/96a7a7c1fb6e6115bcd75324437b225ec4bf127b/binance_grid_trader/__init__.py -------------------------------------------------------------------------------- /binance_grid_trader/bitcoin_arbitrage.md: -------------------------------------------------------------------------------- 1 | # 币圈低风险套利赚钱-年化15%以上 2 | 今天我们给大家讲讲如何在币圈进行低风险的套利。 3 | 4 | ## 注册交易所账号 5 | 6 | OKEX 交易所注册推荐码,手续费返佣20%: 7 | > https://www.okex.me/join/1847111798 8 | 9 | 如果无法访问,可以试下一下链接 10 | 11 | > https://www.okex.com/join/1847111798 12 | 13 | 币安合约推荐码: 返佣10% 14 | 15 | > https://www.binancezh.com/cn/futures/ref/51bitquant 16 | 17 | 18 | ## 买卖出入金的方式 19 | 通过场外OTC,使用人民购买USDT,付给对方钱后,点击已付款,OTC商家会释放相应USDT给你。 在把法币的资产划转到币币账户进行交易相应的数字资产。 20 | 21 | 相应的如果你想把数字资产换成法币,那么你把你的资产划转到法币,然后出售资产,在收到对方的钱,且确认是对方的账户转给你,数目也是对的情况下,点击已收款,然后释放对应的数字资产给商家。 22 | 23 | 24 | ## 存币赚取利息 25 | 通过存币,交易所会帮你把资产借贷给相应的人,然后对方需要支付相应的利息给你。 26 | 27 | ## 挖矿赚利息 28 | 类似DEFI,挖矿,或者CEFI挖矿的方式。为相应的交易对提供流动性,赚取对应的手续费 29 | 30 | ## 永续资金费率套利 31 | 在多头市场情况下,永续资金费率有的高达0.75%,一般情况下,有的资金费率在万1到千2之间,也有可能是负是资金费率。正的资金费率表示都头出资金费率,负的资金费率表示空方付对应的资金费率。 32 | 33 | ## 远近合约跨期套利 34 | 一般情况下,远期合约的价格币现货价格、永续合约或者近期的合约的价格要高。如果你买入现货,去对应的远期合约做空对应的资产,那么就相当于你把资产卖出一个较高的价格,等到他们价格相等或者接近的时候,再平仓。不过这个一般是要用程序去执行比较好。这样可以随时间监控他们之间的价差,捕捉更多的套利机会。 35 | 36 | ## 持有平台币参与交易所的一些活动 37 | 持有BNB或者OKB,参加他们平台的一些活动,比如最近的BNB要用来挖矿,类似之前的IEO活动。 38 | 39 | 40 | -------------------------------------------------------------------------------- /binance_grid_trader/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "binance_spot", 3 | "symbol": "BTCUSDT", 4 | "api_domain": "binance.com", 5 | "api_key": "replace your api key here", 6 | "api_secret": "replace your api secret here", 7 | "gap_percent": 0.001, 8 | "quantity": 0.001, 9 | "min_price": 0.01, 10 | "min_qty": 0.001, 11 | "max_orders": 1, 12 | "proxy_host": "", 13 | "proxy_port": 0 14 | } -------------------------------------------------------------------------------- /binance_grid_trader/gateway/__init__.py: -------------------------------------------------------------------------------- 1 | from .binance_future import BinanceFutureHttp 2 | from .binance_spot import BinanceSpotHttp, OrderType, OrderStatus, OrderSide -------------------------------------------------------------------------------- /binance_grid_trader/gateway/binance_future.py: -------------------------------------------------------------------------------- 1 | """ 2 | 1. Binance Future http requests. 3 | 4 | """ 5 | 6 | """ 7 | 币安推荐码: 返佣10% 8 | https://www.binancezh.pro/cn/register?ref=AIR1GC70 9 | 10 | 币安合约推荐码: 返佣10% 11 | https://www.binancezh.com/cn/futures/ref/51bitquant 12 | 13 | if you don't have a binance account, you can use the invitation link to register one: 14 | https://www.binancezh.com/cn/futures/ref/51bitquant 15 | 16 | or use the inviation code: 51bitquant 17 | 18 | 网格交易: 适合币圈的高波动率的品种,适合现货, 如果交易合约,需要注意防止极端行情爆仓。 19 | 20 | 21 | 服务器购买地址: https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing 22 | """ 23 | 24 | import requests 25 | import time 26 | import hmac 27 | import hashlib 28 | from enum import Enum 29 | from threading import Thread, Lock 30 | from datetime import datetime 31 | 32 | 33 | class OrderStatus(object): 34 | NEW = "NEW" 35 | PARTIALLY_FILLED = "PARTIALLY_FILLED" 36 | FILLED = "FILLED" 37 | CANCELED = "CANCELED" 38 | PENDING_CANCEL = "PENDING_CANCEL" 39 | REJECTED = "REJECTED" 40 | EXPIRED = "EXPIRED" 41 | 42 | 43 | class OrderType(Enum): 44 | LIMIT = "LIMIT" 45 | MARKET = "MARKET" 46 | STOP = "STOP" 47 | 48 | 49 | class RequestMethod(Enum): 50 | """ 51 | 请求的方法. 52 | """ 53 | GET = 'GET' 54 | POST = 'POST' 55 | PUT = 'PUT' 56 | DELETE = 'DELETE' 57 | 58 | 59 | class Interval(Enum): 60 | """ 61 | 请求的K线数据.. 62 | """ 63 | MINUTE_1 = '1m' 64 | MINUTE_3 = '3m' 65 | MINUTE_5 = '5m' 66 | MINUTE_15 = '15m' 67 | MINUTE_30 = '30m' 68 | HOUR_1 = '1h' 69 | HOUR_2 = '2h' 70 | HOUR_4 = '4h' 71 | HOUR_6 = '6h' 72 | HOUR_8 = '8h' 73 | HOUR_12 = '12h' 74 | DAY_1 = '1d' 75 | DAY_3 = '3d' 76 | WEEK_1 = '1w' 77 | MONTH_1 = '1M' 78 | 79 | 80 | class OrderSide(Enum): 81 | BUY = "BUY" 82 | SELL = "SELL" 83 | 84 | 85 | class BinanceFutureHttp(object): 86 | 87 | def __init__(self, api_key=None, secret=None, domain=None, proxy_host="", proxy_port=0, timeout=5, try_counts=5): 88 | self.key = api_key 89 | self.secret = secret 90 | self.domain = domain 91 | self.recv_window = 5000 92 | self.timeout = timeout 93 | self.order_count_lock = Lock() 94 | self.order_count = 1_000_000 95 | self.try_counts = try_counts # 失败尝试的次数. 96 | self.proxy_host = proxy_host 97 | self.proxy_port = proxy_port 98 | 99 | @property 100 | def proxies(self): 101 | if self.proxy_host and self.proxy_port: 102 | proxy = f"http://{self.proxy_host}:{self.proxy_port}" 103 | return {"http": proxy, "https": proxy} 104 | return {} 105 | 106 | def build_parameters(self, params: dict): 107 | keys = list(params.keys()) 108 | keys.sort() 109 | return '&'.join([f"{key}={params[key]}" for key in params.keys()]) 110 | 111 | def request(self, req_method: RequestMethod, path: str, requery_dict=None, verify=False): 112 | url = f"https://fapi.{self.domain}{path}" 113 | 114 | if verify: 115 | query_str = self._sign(requery_dict) 116 | url += '?' + query_str 117 | elif requery_dict: 118 | url += '?' + self.build_parameters(requery_dict) 119 | headers = {"X-MBX-APIKEY": self.key} 120 | 121 | for i in range(0, self.try_counts): 122 | try: 123 | response = requests.request(req_method.value, url=url, headers=headers, timeout=self.timeout, proxies=self.proxies) 124 | if response.status_code == 200: 125 | return response.json() 126 | else: 127 | print(f"请求没有成功: {response.status_code}, 继续尝试请求") 128 | except Exception as error: 129 | print(f"请求:{path}, 发生了错误: {error}, 时间: {datetime.now()}") 130 | time.sleep(3) 131 | 132 | def server_time(self): 133 | path = '/fapi/v1/time' 134 | return self.request(req_method=RequestMethod.GET, path=path) 135 | 136 | def exchangeInfo(self): 137 | 138 | """ 139 | {'timezone': 'UTC', 'serverTime': 1570802268092, 'rateLimits': 140 | [{'rateLimitType': 'REQUEST_WEIGHT', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}, 141 | {'rateLimitType': 'ORDERS', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}], 142 | 'exchangeFilters': [], 'symbols': 143 | [{'symbol': 'BTCUSDT', 'status': 'TRADING', 'maintMarginPercent': '2.5000', 'requiredMarginPercent': '5.0000', 144 | 'baseAsset': 'BTC', 'quoteAsset': 'USDT', 'pricePrecision': 2, 'quantityPrecision': 3, 'baseAssetPrecision': 8, 145 | 'quotePrecision': 8, 146 | 'filters': [{'minPrice': '0.01', 'maxPrice': '100000', 'filterType': 'PRICE_FILTER', 'tickSize': '0.01'}, 147 | {'stepSize': '0.001', 'filterType': 'LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, 148 | {'stepSize': '0.001', 'filterType': 'MARKET_LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, 149 | {'limit': 200, 'filterType': 'MAX_NUM_ORDERS'}, 150 | {'multiplierDown': '0.8500', 'multiplierUp': '1.1500', 'multiplierDecimal': '4', 'filterType': 'PERCENT_PRICE'}], 151 | 'orderTypes': ['LIMIT', 'MARKET', 'STOP'], 'timeInForce': ['GTC', 'IOC', 'FOK', 'GTX']}]} 152 | 153 | :return: 154 | """ 155 | 156 | path = '/fapi/v1/exchangeInfo' 157 | return self.request(req_method=RequestMethod.GET, path=path) 158 | 159 | def order_book(self, symbol, limit=5): 160 | limits = [5, 10, 20, 50, 100, 500, 1000] 161 | if limit not in limits: 162 | limit = 5 163 | 164 | path = "/fapi/v1/depth" 165 | query_dict = {"symbol": symbol, 166 | "limit": limit 167 | } 168 | 169 | return self.request(RequestMethod.GET, path, query_dict) 170 | 171 | def get_kline(self, symbol, interval: Interval, start_time=None, end_time=None, limit=500, max_try_time=10): 172 | """ 173 | 174 | :param symbol: 175 | :param interval: 176 | :param start_time: 177 | :param end_time: 178 | :param limit: 179 | :return: 180 | [ 181 | 1499040000000, // 开盘时间 182 | "0.01634790", // 开盘价 183 | "0.80000000", // 最高价 184 | "0.01575800", // 最低价 185 | "0.01577100", // 收盘价(当前K线未结束的即为最新价) 186 | "148976.11427815", // 成交量 187 | 1499644799999, // 收盘时间 188 | "2434.19055334", // 成交额 189 | 308, // 成交笔数 190 | "1756.87402397", // 主动买入成交量 191 | "28.46694368", // 主动买入成交额 192 | "17928899.62484339" // 请忽略该参数 193 | ] 194 | """ 195 | path = "/fapi/v1/klines" 196 | 197 | query_dict = { 198 | "symbol": symbol, 199 | "interval": interval.value, 200 | "limit": limit 201 | } 202 | 203 | if start_time: 204 | query_dict['startTime'] = start_time 205 | 206 | if end_time: 207 | query_dict['endTime'] = end_time 208 | 209 | for i in range(max_try_time): 210 | data = self.request(RequestMethod.GET, path, query_dict) 211 | if isinstance(data, list) and len(data): 212 | return data 213 | 214 | def get_latest_price(self, symbol): 215 | path = "/fapi/v1/ticker/price" 216 | query_dict = {"symbol": symbol} 217 | return self.request(RequestMethod.GET, path, query_dict) 218 | 219 | def get_ticker(self, symbol): 220 | path = "/fapi/v1/ticker/bookTicker" 221 | query_dict = {"symbol": symbol} 222 | return self.request(RequestMethod.GET, path, query_dict) 223 | 224 | ########################### the following request is for private data ######################## 225 | 226 | def _timestamp(self): 227 | return int(time.time() * 1000) 228 | 229 | def _sign(self, params): 230 | 231 | requery_string = self.build_parameters(params) 232 | hexdigest = hmac.new(self.secret.encode('utf8'), requery_string.encode("utf-8"), hashlib.sha256).hexdigest() 233 | return requery_string + '&signature=' + str(hexdigest) 234 | 235 | def get_client_order_id(self): 236 | 237 | """ 238 | generate the client_order_id for user. 239 | :return: new client order id 240 | """ 241 | with self.order_count_lock: 242 | self.order_count += 1 243 | return "x-cLbi5uMH" + str(self._timestamp()) + str(self.order_count) 244 | 245 | def place_order(self, symbol: str, order_side: OrderSide, order_type: OrderType, quantity, price, 246 | time_inforce="GTC", client_order_id=None, recvWindow=5000, stop_price=0): 247 | 248 | """ 249 | 下单.. 250 | :param symbol: BTCUSDT 251 | :param side: BUY or SELL 252 | :param type: LIMIT MARKET STOP 253 | :param quantity: 数量. 254 | :param price: 价格 255 | :param stop_price: 停止单的价格. 256 | :param time_inforce: 257 | :param params: 其他参数 258 | 259 | LIMIT : timeInForce, quantity, price 260 | MARKET : quantity 261 | STOP: quantity, price, stopPrice 262 | :return: 263 | 264 | """ 265 | 266 | path = '/fapi/v1/order' 267 | 268 | if client_order_id is None: 269 | client_order_id = self.get_client_order_id() 270 | 271 | params = { 272 | "symbol": symbol, 273 | "side": order_side.value, 274 | "type": order_type.value, 275 | "quantity": quantity, 276 | "price": price, 277 | "recvWindow": recvWindow, 278 | "timeInForce": "GTC", 279 | "timestamp": self._timestamp(), 280 | "newClientOrderId": client_order_id 281 | } 282 | 283 | if order_type == OrderType.LIMIT: 284 | params['timeInForce'] = time_inforce 285 | 286 | if order_type == OrderType.MARKET: 287 | if params.get('price'): 288 | del params['price'] 289 | 290 | if order_type == OrderType.STOP: 291 | if stop_price > 0: 292 | params["stopPrice"] = stop_price 293 | else: 294 | raise ValueError("stopPrice must greater than 0") 295 | # print(params) 296 | return self.request(RequestMethod.POST, path=path, requery_dict=params, verify=True) 297 | 298 | def get_order(self, symbol, client_order_id=None): 299 | path = "/fapi/v1/order" 300 | query_dict = {"symbol": symbol, "timestamp": self._timestamp()} 301 | if client_order_id: 302 | query_dict["origClientOrderId"] = client_order_id 303 | 304 | return self.request(RequestMethod.GET, path, query_dict, verify=True) 305 | 306 | def cancel_order(self, symbol, client_order_id=None): 307 | path = "/fapi/v1/order" 308 | params = {"symbol": symbol, "timestamp": self._timestamp()} 309 | if client_order_id: 310 | params["origClientOrderId"] = client_order_id 311 | 312 | return self.request(RequestMethod.DELETE, path, params, verify=True) 313 | 314 | def get_open_orders(self, symbol=None): 315 | path = "/fapi/v1/openOrders" 316 | 317 | params = {"timestamp": self._timestamp()} 318 | if symbol: 319 | params["symbol"] = symbol 320 | 321 | return self.request(RequestMethod.GET, path, params, verify=True) 322 | 323 | def cancel_open_orders(self, symbol): 324 | """ 325 | 撤销某个交易对的所有挂单 326 | :param symbol: symbol 327 | :return: return a list of orders. 328 | """ 329 | path = "/fapi/v1/allOpenOrders" 330 | 331 | params = {"timestamp": self._timestamp(), 332 | "recvWindow": self.recv_window, 333 | "symbol": symbol 334 | } 335 | 336 | return self.request(RequestMethod.DELETE, path, params, verify=True) 337 | 338 | def get_balance(self): 339 | """ 340 | [{'accountId': 18396, 'asset': 'USDT', 'balance': '530.21334791', 'withdrawAvailable': '530.21334791', 'updateTime': 1570330854015}] 341 | :return: 342 | """ 343 | path = "/fapi/v1/balance" 344 | params = {"timestamp": self._timestamp()} 345 | 346 | return self.request(RequestMethod.GET, path=path, requery_dict=params, verify=True) 347 | 348 | def get_account_info(self): 349 | """ 350 | {'feeTier': 2, 'canTrade': True, 'canDeposit': True, 'canWithdraw': True, 'updateTime': 0, 'totalInitialMargin': '0.00000000', 351 | 'totalMaintMargin': '0.00000000', 'totalWalletBalance': '530.21334791', 'totalUnrealizedProfit': '0.00000000', 352 | 'totalMarginBalance': '530.21334791', 'totalPositionInitialMargin': '0.00000000', 'totalOpenOrderInitialMargin': '0.00000000', 353 | 'maxWithdrawAmount': '530.2133479100000', 'assets': 354 | [{'asset': 'USDT', 'walletBalance': '530.21334791', 'unrealizedProfit': '0.00000000', 'marginBalance': '530.21334791', 355 | 'maintMargin': '0.00000000', 'initialMargin': '0.00000000', 'positionInitialMargin': '0.00000000', 'openOrderInitialMargin': '0.00000000', 356 | 'maxWithdrawAmount': '530.2133479100000'}]} 357 | :return: 358 | """ 359 | path = "/fapi/v1/account" 360 | params = {"timestamp": self._timestamp()} 361 | return self.request(RequestMethod.GET, path, params, verify=True) 362 | 363 | def get_position_info(self): 364 | """ 365 | [{'symbol': 'BTCUSDT', 'positionAmt': '0.000', 'entryPrice': '0.00000', 'markPrice': '8326.40833498', 'unRealizedProfit': '0.00000000', 'liquidationPrice': '0'}] 366 | :return: 367 | """ 368 | path = "/fapi/v1/positionRisk" 369 | params = {"timestamp": self._timestamp()} 370 | return self.request(RequestMethod.GET, path, params, verify=True) 371 | 372 | 373 | if __name__ == '__main__': 374 | # import pandas as pd 375 | 376 | key = "xxxx" 377 | secret = 'xxxx' 378 | domain = 'binance.com' 379 | binance = BinanceFutureHttp(domain=domain, api_key=key, secret=secret) 380 | 381 | # import datetime 382 | # print(datetime.datetime.now()) 383 | 384 | data = binance.get_kline('BTCUSDT', Interval.HOUR_1, limit=100) 385 | print(data) 386 | print(isinstance(data, list)) 387 | 388 | exit() 389 | # info = binance.exchangeInfo() 390 | # print(info) 391 | # exit() 392 | # print(binance.order_book(symbol='BTCUSDT', limit=5)) 393 | # exit() 394 | 395 | # print(binance.latest_price('BTCUSDT')) 396 | # kline = binance.kline('BTCUSDT', interval='1m') 397 | # print(pd.DataFrame(kline)) 398 | 399 | # print(binance.ticker("BTCUSDT")) 400 | 401 | # print(binance.get_ticker('BTCUSDT')) 402 | # {'symbol': 'BTCUSDT', 'side': 'SELL', 'type': 'LIMIT', 'quantity': 0.001, 'price': 8360.82, 'recvWindow': 5000, 'timestamp': 1570969995974, 'timeInForce': 'GTC'} 403 | 404 | # data = binance.place_order(symbol="BTCUSDT", side=OrderSide.BUY, order_type=OrderType.MARKET, quantity=0.001, price=8250.82) 405 | # print(data) 406 | 407 | # cancel_order = binance.cancel_order("BTCUSDT", order_id="30714952") 408 | # print(cancel_order) 409 | 410 | # balance = binance.get_balance() 411 | # print(balance) 412 | 413 | # account_info = binance.get_account_info() 414 | # print(account_info) 415 | 416 | # account_info = binance.get_position_info() 417 | # print(account_info) 418 | 419 | """ 420 | {'orderId': 30714952, 'symbol': 'BTCUSDT', 'accountId': 18396, 'status': 'NEW', 'clientOrderId': 'ZC3qSbzbODl0GId9idK9hM', 'price': '7900', 'origQty': '0.010', 'executedQty': '0', 'cumQty': '0', 'cumQuote': '0', 'timeInForce': 'GTC', 'type': 'LIMIT', 'reduceOnly': False, 'side': 'BUY', 'stopPrice': '0', 'updateTime': 1569658787083} 421 | {'orderId': 30714952, 'symbol': 'BTCUSDT', 'accountId': 18396, 'status': 'NEW', 'clientOrderId': 'ZC3qSbzbODl0GId9idK9hM', 'price': '7900', 'origQty': '0.010', 'executedQty': '0', 'cumQty': '0', 'cumQuote': '0', 'timeInForce': 'GTC', 'type': 'LIMIT', 'reduceOnly': False, 'side': 'BUY', 'stopPrice': '0', 'updateTime': 1569658787083} 422 | """ 423 | -------------------------------------------------------------------------------- /binance_grid_trader/gateway/binance_spot.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | 4 | 币安推荐码: 返佣10% 5 | https://www.binancezh.pro/cn/register?ref=AIR1GC70 6 | 7 | 币安合约推荐码: 返佣10% 8 | https://www.binancezh.com/cn/futures/ref/51bitquant 9 | 10 | if you don't have a binance account, you can use the invitation link to register one: 11 | https://www.binancezh.com/cn/futures/ref/51bitquant 12 | 13 | or use the inviation code: 51bitquant 14 | 网格交易: 适合币圈的高波动率的品种,适合现货, 如果交易合约,需要注意防止极端行情爆仓。 15 | 16 | 17 | 服务器购买地址: https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing 18 | """ 19 | 20 | 21 | import requests 22 | import time 23 | import hmac 24 | import hashlib 25 | from enum import Enum 26 | from threading import Lock 27 | 28 | 29 | class OrderStatus(Enum): 30 | NEW = "NEW" 31 | PARTIALLY_FILLED = "PARTIALLY_FILLED" 32 | FILLED = "FILLED" 33 | CANCELED = "CANCELED" 34 | PENDING_CANCEL = "PENDING_CANCEL" 35 | REJECTED = "REJECTED" 36 | EXPIRED = "EXPIRED" 37 | 38 | 39 | class OrderType(Enum): 40 | LIMIT = "LIMIT" 41 | MARKET = "MARKET" 42 | STOP = "STOP" 43 | 44 | 45 | class RequestMethod(Enum): 46 | """ 47 | 请求的方法. 48 | """ 49 | GET = 'GET' 50 | POST = 'POST' 51 | PUT = 'PUT' 52 | DELETE = 'DELETE' 53 | 54 | 55 | class Interval(Enum): 56 | """ 57 | 请求的K线数据.. 58 | """ 59 | MINUTE_1 = '1m' 60 | MINUTE_3 = '3m' 61 | MINUTE_5 = '5m' 62 | MINUTE_15 = '15m' 63 | MINUTE_30 = '30m' 64 | HOUR_1 = '1h' 65 | HOUR_2 = '2h' 66 | HOUR_4 = '4h' 67 | HOUR_6 = '6h' 68 | HOUR_8 = '8h' 69 | HOUR_12 = '12h' 70 | DAY_1 = '1d' 71 | DAY_3 = '3d' 72 | WEEK_1 = '1w' 73 | MONTH_1 = '1M' 74 | 75 | 76 | class OrderSide(Enum): 77 | BUY = "BUY" 78 | SELL = "SELL" 79 | 80 | 81 | class BinanceSpotHttp(object): 82 | 83 | def __init__(self, api_key=None, secret=None, domain=None, proxy_host=None, proxy_port=0, timeout=5, try_counts=5): 84 | self.api_key = api_key 85 | self.secret = secret 86 | self.domain = domain 87 | self.recv_window = 10000 88 | self.timeout = timeout 89 | self.order_count_lock = Lock() 90 | self.order_count = 1_000_000 91 | self.try_counts = try_counts # 失败尝试的次数. 92 | self.proxy_host = proxy_host 93 | self.proxy_port = proxy_port 94 | 95 | @property 96 | def proxies(self): 97 | if self.proxy_host and self.proxy_port: 98 | proxy = f"http://{self.proxy_host}:{self.proxy_port}" 99 | return {"http": proxy, "https": proxy} 100 | 101 | return None 102 | 103 | def build_parameters(self, params: dict): 104 | keys = list(params.keys()) 105 | keys.sort() 106 | return '&'.join([f"{key}={params[key]}" for key in params.keys()]) 107 | 108 | def request(self, req_method: RequestMethod, path: str, requery_dict=None, verify=False): 109 | url = f"https://api.{self.domain}{path}" 110 | 111 | if verify: 112 | query_str = self._sign(requery_dict) 113 | url += '?' + query_str 114 | elif requery_dict: 115 | url += '?' + self.build_parameters(requery_dict) 116 | headers = {"X-MBX-APIKEY": self.api_key} 117 | 118 | for i in range(0, self.try_counts): 119 | try: 120 | response = requests.request(req_method.value, url=url, headers=headers, timeout=self.timeout, proxies=self.proxies) 121 | if response.status_code == 200: 122 | return response.json() 123 | else: 124 | print(response.json(), response.status_code) 125 | except Exception as error: 126 | print(f"请求:{path}, 发生了错误: {error}") 127 | time.sleep(3) 128 | 129 | def get_server_time(self): 130 | path = '/api/v3/time' 131 | return self.request(req_method=RequestMethod.GET, path=path) 132 | 133 | def get_exchange_info(self): 134 | 135 | """ 136 | return: 137 | the exchange info in json format: 138 | {'timezone': 'UTC', 'serverTime': 1570802268092, 'rateLimits': 139 | [{'rateLimitType': 'REQUEST_WEIGHT', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}, 140 | {'rateLimitType': 'ORDERS', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 1200}], 141 | 'exchangeFilters': [], 'symbols': 142 | [{'symbol': 'BTCUSDT', 'status': 'TRADING', 'maintMarginPercent': '2.5000', 'requiredMarginPercent': '5.0000', 143 | 'baseAsset': 'BTC', 'quoteAsset': 'USDT', 'pricePrecision': 2, 'quantityPrecision': 3, 'baseAssetPrecision': 8, 144 | 'quotePrecision': 8, 145 | 'filters': [{'minPrice': '0.01', 'maxPrice': '100000', 'filterType': 'PRICE_FILTER', 'tickSize': '0.01'}, 146 | {'stepSize': '0.001', 'filterType': 'LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, 147 | {'stepSize': '0.001', 'filterType': 'MARKET_LOT_SIZE', 'maxQty': '1000', 'minQty': '0.001'}, 148 | {'limit': 200, 'filterType': 'MAX_NUM_ORDERS'}, 149 | {'multiplierDown': '0.8500', 'multiplierUp': '1.1500', 'multiplierDecimal': '4', 'filterType': 'PERCENT_PRICE'}], 150 | 'orderTypes': ['LIMIT', 'MARKET', 'STOP'], 'timeInForce': ['GTC', 'IOC', 'FOK', 'GTX']}]} 151 | 152 | """ 153 | 154 | path = '/api/v3/exchangeInfo' 155 | return self.request(req_method=RequestMethod.GET, path=path) 156 | 157 | def get_order_book(self, symbol, limit=5): 158 | """ 159 | :param symbol: BTCUSDT, BNBUSDT ect, 交易对. 160 | :param limit: market depth. 161 | :return: return order_book in json 返回订单簿,json数据格式. 162 | """ 163 | limits = [5, 10, 20, 50, 100, 500, 1000] 164 | if limit not in limits: 165 | limit = 5 166 | 167 | path = "/api/v3/depth" 168 | query_dict = {"symbol": symbol, 169 | "limit": limit 170 | } 171 | 172 | return self.request(RequestMethod.GET, path, query_dict) 173 | 174 | def get_kline(self, symbol, interval: Interval, start_time=None, end_time=None, limit=500, max_try_time=10): 175 | """ 176 | 获取K线数据. 177 | :param symbol: 178 | :param interval: 179 | :param start_time: 180 | :param end_time: 181 | :param limit: 182 | :param max_try_time: 183 | :return: 184 | """ 185 | path = "/api/v3/klines" 186 | 187 | query_dict = { 188 | "symbol": symbol, 189 | "interval": interval.value, 190 | "limit": limit 191 | } 192 | 193 | if start_time: 194 | query_dict['startTime'] = start_time 195 | 196 | if end_time: 197 | query_dict['endTime'] = end_time 198 | 199 | for i in range(max_try_time): 200 | data = self.request(RequestMethod.GET, path, query_dict) 201 | if isinstance(data, list) and len(data): 202 | return data 203 | 204 | def get_latest_price(self, symbol): 205 | """ 206 | :param symbol: 获取最新的价格. 207 | :return: {'symbol': 'BTCUSDT', 'price': '9168.90000000'} 208 | 209 | """ 210 | path = "/api/v3/ticker/price" 211 | query_dict = {"symbol": symbol} 212 | return self.request(RequestMethod.GET, path, query_dict) 213 | 214 | def get_ticker(self, symbol): 215 | """ 216 | :param symbol: 交易对 217 | :return: 返回的数据如下: 218 | { 219 | 'symbol': 'BTCUSDT', 'bidPrice': '9168.50000000', 'bidQty': '1.27689900', 220 | 'askPrice': '9168.51000000', 'askQty': '0.93307800' 221 | } 222 | """ 223 | path = "/api/v3/ticker/bookTicker" 224 | query_dict = {"symbol": symbol} 225 | return self.request(RequestMethod.GET, path, query_dict) 226 | 227 | def get_client_order_id(self): 228 | """ 229 | generate the client_order_id for user. 230 | :return: 231 | """ 232 | with self.order_count_lock: 233 | self.order_count += 1 234 | return "x-A6SIDXVS" + str(self.get_current_timestamp()) + str(self.order_count) 235 | 236 | def get_current_timestamp(self): 237 | """ 238 | 获取系统的时间. 239 | :return: 240 | """ 241 | return int(time.time() * 1000) 242 | 243 | def _sign(self, params): 244 | """ 245 | 签名的方法, signature for the private request. 246 | :param params: request parameters 247 | :return: 248 | """ 249 | 250 | query_string = self.build_parameters(params) 251 | hex_digest = hmac.new(self.secret.encode('utf8'), query_string.encode("utf-8"), hashlib.sha256).hexdigest() 252 | return query_string + '&signature=' + str(hex_digest) 253 | 254 | def place_order(self, symbol: str, order_side: OrderSide, order_type: OrderType, quantity: float, price: float, 255 | client_order_id: str = None, time_inforce="GTC", stop_price=0): 256 | """ 257 | 258 | :param symbol: 交易对名称 259 | :param order_side: 买或者卖, BUY or SELL 260 | :param order_type: 订单类型 LIMIT or other order type. 261 | :param quantity: 数量 262 | :param price: 价格. 263 | :param client_order_id: 用户的订单ID 264 | :param time_inforce: 265 | :param stop_price: 266 | :return: 267 | """ 268 | 269 | path = '/api/v3/order' 270 | 271 | if client_order_id is None: 272 | client_order_id = self.get_client_order_id() 273 | 274 | params = { 275 | "symbol": symbol, 276 | "side": order_side.value, 277 | "type": order_type.value, 278 | "quantity": quantity, 279 | "price": price, 280 | "recvWindow": self.recv_window, 281 | "timestamp": self.get_current_timestamp(), 282 | "newClientOrderId": client_order_id 283 | } 284 | 285 | if order_type == OrderType.LIMIT: 286 | params['timeInForce'] = time_inforce 287 | 288 | if order_type == OrderType.MARKET: 289 | if params.get('price'): 290 | del params['price'] 291 | 292 | if order_type == OrderType.STOP: 293 | if stop_price > 0: 294 | params["stopPrice"] = stop_price 295 | else: 296 | raise ValueError("stopPrice must greater than 0") 297 | 298 | return self.request(RequestMethod.POST, path=path, requery_dict=params, verify=True) 299 | 300 | def get_order(self, symbol: str, client_order_id: str): 301 | """ 302 | 获取订单状态. 303 | :param symbol: 304 | :param client_order_id: 305 | :return: 306 | """ 307 | path = "/api/v3/order" 308 | prams = {"symbol": symbol, "timestamp": self.get_current_timestamp(), "origClientOrderId": client_order_id} 309 | 310 | return self.request(RequestMethod.GET, path, prams, verify=True) 311 | 312 | def cancel_order(self, symbol, client_order_id): 313 | """ 314 | 撤销订单. 315 | :param symbol: 316 | :param client_order_id: 317 | :return: 318 | """ 319 | path = "/api/v3/order" 320 | params = {"symbol": symbol, "timestamp": self.get_current_timestamp(), 321 | "origClientOrderId": client_order_id 322 | } 323 | 324 | for i in range(0, 3): 325 | try: 326 | order = self.request(RequestMethod.DELETE, path, params, verify=True) 327 | return order 328 | except Exception as error: 329 | print(f'cancel order error:{error}') 330 | return 331 | 332 | def get_open_orders(self, symbol=None): 333 | """ 334 | 获取所有的订单. 335 | :param symbol: BNBUSDT, or BTCUSDT etc. 336 | :return: 337 | """ 338 | path = "/api/v3/openOrders" 339 | 340 | params = {"timestamp": self.get_current_timestamp()} 341 | if symbol: 342 | params["symbol"] = symbol 343 | 344 | return self.request(RequestMethod.GET, path, params, verify=True) 345 | 346 | def cancel_open_orders(self, symbol): 347 | """ 348 | 撤销某个交易对的所有挂单 349 | :param symbol: symbol 350 | :return: return a list of orders. 351 | """ 352 | path = "/api/v3/openOrders" 353 | 354 | params = {"timestamp": self.get_current_timestamp(), 355 | "recvWindow": self.recv_window, 356 | "symbol": symbol 357 | } 358 | 359 | return self.request(RequestMethod.DELETE, path, params, verify=True) 360 | 361 | def get_account_info(self): 362 | """ 363 | {'feeTier': 2, 'canTrade': True, 'canDeposit': True, 'canWithdraw': True, 'updateTime': 0, 'totalInitialMargin': '0.00000000', 364 | 'totalMaintMargin': '0.00000000', 'totalWalletBalance': '530.21334791', 'totalUnrealizedProfit': '0.00000000', 365 | 'totalMarginBalance': '530.21334791', 'totalPositionInitialMargin': '0.00000000', 'totalOpenOrderInitialMargin': '0.00000000', 366 | 'maxWithdrawAmount': '530.2133479100000', 'assets': 367 | [{'asset': 'USDT', 'walletBalance': '530.21334791', 'unrealizedProfit': '0.00000000', 'marginBalance': '530.21334791', 368 | 'maintMargin': '0.00000000', 'initialMargin': '0.00000000', 'positionInitialMargin': '0.00000000', 'openOrderInitialMargin': '0.00000000', 369 | 'maxWithdrawAmount': '530.2133479100000'}]} 370 | :return: 371 | """ 372 | path = "/api/v3/account" 373 | params = {"timestamp": self.get_current_timestamp(), 374 | "recvWindow": self.recv_window 375 | } 376 | return self.request(RequestMethod.GET, path, params, verify=True) 377 | -------------------------------------------------------------------------------- /binance_grid_trader/grid_trader_log.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansin/docker_grid_trader/96a7a7c1fb6e6115bcd75324437b225ec4bf127b/binance_grid_trader/grid_trader_log.txt -------------------------------------------------------------------------------- /binance_grid_trader/main.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | 币安推荐码: 返佣10% 4 | https://www.binancezh.pro/cn/register?ref=AIR1GC70 5 | 6 | 币安合约推荐码: 返佣10% 7 | https://www.binancezh.com/cn/futures/ref/51bitquant 8 | 9 | if you don't have a binance account, you can use the invitation link to register one: 10 | https://www.binancezh.com/cn/futures/ref/51bitquant 11 | 12 | or use the inviation code: 51bitquant 13 | 14 | 风险提示: 网格交易在单边行情的时候,会承受比较大的风险,请你了解整个代码的逻辑,然后再使用。 15 | RISK NOTE: Grid trading will endure great risk at trend market, please check the code before use it. USE AT YOUR OWN RISK. 16 | 17 | 18 | """ 19 | 20 | import time 21 | import logging 22 | from trader.binance_trader import BinanceTrader 23 | from trader.binance_future_trader import BinanceFutureTrader 24 | from utils import config 25 | 26 | format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 27 | logging.basicConfig(level=logging.INFO, format=format, filename='grid_trader_log.txt') 28 | logger = logging.getLogger('binance') 29 | 30 | if __name__ == '__main__': 31 | 32 | config.loads('./config.json') 33 | 34 | if config.platform == 'binance_spot': 35 | trader = BinanceTrader() 36 | else: 37 | trader = BinanceFutureTrader() 38 | orders = trader.http_client.cancel_open_orders(config.symbol) 39 | print(f"cancel orders: {orders}") 40 | 41 | while True: 42 | try: 43 | trader.grid_trader() 44 | time.sleep(20) 45 | 46 | except Exception as error: 47 | print(f"catch error: {error}") 48 | time.sleep(5) 49 | 50 | -------------------------------------------------------------------------------- /binance_grid_trader/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.22.0 2 | -------------------------------------------------------------------------------- /binance_grid_trader/start.sh: -------------------------------------------------------------------------------- 1 | nohup python -u main.py > grid_nohup.out 2>&1 & 2 | -------------------------------------------------------------------------------- /binance_grid_trader/test.py: -------------------------------------------------------------------------------- 1 | # test code 2 | # 代码测试 -------------------------------------------------------------------------------- /binance_grid_trader/trader/binance_future_trader.py: -------------------------------------------------------------------------------- 1 | """ 2 | 币安推荐码: 返佣10% 3 | https://www.binancezh.pro/cn/register?ref=AIR1GC70 4 | 5 | 币安合约推荐码: 返佣10% 6 | https://www.binancezh.com/cn/futures/ref/51bitquant 7 | 8 | if you don't have a binance account, you can use the invitation link to register one: 9 | https://www.binancezh.com/cn/futures/ref/51bitquant 10 | 11 | or use the inviation code: 51bitquant 12 | 13 | 网格交易: 适合币圈的高波动率的品种,适合现货, 如果交易合约,需要注意防止极端行情爆仓。 14 | 15 | 16 | 服务器购买地址: https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing 17 | """ 18 | 19 | 20 | from gateway import BinanceFutureHttp, OrderStatus, OrderType, OrderSide 21 | from utils import config 22 | from utils import utility, round_to 23 | from enum import Enum 24 | import logging 25 | from datetime import datetime 26 | 27 | class BinanceFutureTrader(object): 28 | 29 | def __init__(self): 30 | """ 31 | the binance future trader, 币安合约交易的网格交易, 32 | the grid trading in Future will endure a lot of risk, use it before you understand the risk and grid strategy. 33 | 网格交易在合约上会有很大的风险,请注意风险 34 | """ 35 | self.http_client = BinanceFutureHttp(domain=config.api_domain, api_key=config.api_key, secret=config.api_secret, proxy_host=config.proxy_host, proxy_port=config.proxy_port) 36 | 37 | self.buy_orders = [] # 买单. buy orders 38 | self.sell_orders = [] # 卖单. sell orders 39 | 40 | 41 | def get_bid_ask_price(self): 42 | 43 | ticker = self.http_client.get_ticker(config.symbol) 44 | 45 | bid_price = 0 46 | ask_price = 0 47 | if ticker: 48 | bid_price = float(ticker.get('bidPrice', 0)) 49 | ask_price = float(ticker.get('askPrice', 0)) 50 | 51 | return bid_price, ask_price 52 | 53 | def grid_trader(self): 54 | """ 55 | 执行核心逻辑,网格交易的逻辑. 56 | 57 | the grid trading logic 58 | :return: 59 | """ 60 | 61 | bid_price, ask_price = self.get_bid_ask_price() 62 | print(f"bid_price: {bid_price}, ask_price: {ask_price}, time: {datetime.now()}") 63 | 64 | quantity = round_to(float(config.quantity), float(config.min_qty)) 65 | 66 | self.buy_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. 67 | self.sell_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. 68 | 69 | buy_delete_orders = [] # 需要删除买单 70 | sell_delete_orders = [] # 需要删除的卖单 71 | 72 | 73 | # 买单逻辑,检查成交的情况. 74 | for buy_order in self.buy_orders: 75 | 76 | check_order = self.http_client.get_order(buy_order.get('symbol', config.symbol),client_order_id=buy_order.get('clientOrderId')) 77 | 78 | if check_order: 79 | if check_order.get('status') == OrderStatus.CANCELED.value: 80 | buy_delete_orders.append(buy_order) 81 | print(f"buy order status was canceled: {check_order.get('status')}, time: {datetime.now()}") 82 | elif check_order.get('status') == OrderStatus.FILLED.value: 83 | # 买单成交,挂卖单. 84 | print(f"买单成交了, 时间: {datetime.now()}") 85 | logging.info(f"买单成交时间: {datetime.now()}, 价格: {check_order.get('price')}, 数量: {check_order.get('origQty')}") 86 | 87 | sell_price = round_to(float(check_order.get("price")) * (1 + float(config.gap_percent)), float(config.min_price)) 88 | 89 | if 0 < sell_price < ask_price: 90 | # 防止价格 91 | sell_price = round_to(ask_price, float(config.min_price)) 92 | 93 | new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, order_type=OrderType.LIMIT, quantity=quantity, price=sell_price) 94 | if new_sell_order: 95 | print(f"买单成交,下了对应价格的卖单: {new_sell_order}, 时间: {datetime.now()}") 96 | buy_delete_orders.append(buy_order) 97 | self.sell_orders.append(new_sell_order) 98 | 99 | buy_price = round_to(float(check_order.get("price")) * (1 - float(config.gap_percent)), 100 | config.min_price) 101 | 102 | if buy_price > bid_price > 0: 103 | buy_price = round_to(bid_price, float(config.min_price)) 104 | 105 | new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=quantity, price=buy_price) 106 | if new_buy_order: 107 | print(f"买单成交,下了更低价的买单: {new_buy_order}, 时间: {datetime.now()}") 108 | self.buy_orders.append(new_buy_order) 109 | 110 | 111 | elif check_order.get('status') == OrderStatus.NEW.value: 112 | print(f"buy order status is: New , 时间: {datetime.now()}") 113 | else: 114 | print(f"buy order status is not above options: {check_order.get('status')}, 时间: {datetime.now()}") 115 | 116 | # 过期或者拒绝的订单删除掉. 117 | for delete_order in buy_delete_orders: 118 | self.buy_orders.remove(delete_order) 119 | 120 | # 卖单逻辑, 检查卖单成交情况. 121 | for sell_order in self.sell_orders: 122 | 123 | check_order = self.http_client.get_order(sell_order.get('symbol', config.symbol), 124 | client_order_id=sell_order.get('clientOrderId')) 125 | if check_order: 126 | if check_order.get('status') == OrderStatus.CANCELED.value: 127 | sell_delete_orders.append(sell_order) 128 | 129 | print(f"sell order status was canceled: {check_order.get('status')}, 时间: {datetime.now()}") 130 | elif check_order.get('status') == OrderStatus.FILLED.value: 131 | print(f"卖单成交了, 时间: {datetime.now()}") 132 | logging.info( 133 | f"卖单成交时间: {datetime.now()}, 价格: {check_order.get('price')}, 数量: {check_order.get('origQty')}") 134 | # 卖单成交,先下买单. 135 | buy_price = round_to(float(check_order.get("price")) * (1 - float(config.gap_percent)), float(config.min_price)) 136 | if buy_price > bid_price > 0: 137 | buy_price = round_to(bid_price, float(config.min_price)) 138 | 139 | new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, 140 | order_type=OrderType.LIMIT, quantity=quantity, price=buy_price) 141 | if new_buy_order: 142 | print(f"卖单成交,下了对应价格的买单: {new_buy_order}, 时间: {datetime.now()}") 143 | sell_delete_orders.append(sell_order) 144 | self.buy_orders.append(new_buy_order) 145 | 146 | sell_price = round_to(float(check_order.get("price")) * (1 + float(config.gap_percent)), float(config.min_price)) 147 | 148 | if 0 < sell_price < ask_price: 149 | # 防止价格 150 | sell_price = round_to(ask_price, float(config.min_price)) 151 | 152 | new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, 153 | order_type=OrderType.LIMIT, quantity=quantity, 154 | price=sell_price) 155 | if new_sell_order: 156 | print(f"卖单成交,下了更高价的卖单: {new_sell_order}, 时间: {datetime.now()}") 157 | self.sell_orders.append(new_sell_order) 158 | 159 | elif check_order.get('status') == OrderStatus.NEW.value: 160 | print(f"sell order status is: New, 时间: {datetime.now()}") 161 | else: 162 | print(f"sell order status is not in above options: {check_order.get('status')}, 时间: {datetime.now()}") 163 | 164 | # 过期或者拒绝的订单删除掉. 165 | for delete_order in sell_delete_orders: 166 | self.sell_orders.remove(delete_order) 167 | 168 | # 没有买单的时候. 169 | if len(self.buy_orders) <= 0: 170 | if bid_price > 0: 171 | price = round_to(bid_price * (1 - float(config.gap_percent)), float(config.min_price)) 172 | 173 | buy_order = self.http_client.place_order(symbol=config.symbol,order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=quantity,price=price) 174 | print(f'没有买单,根据盘口下买单: {buy_order}, 时间: {datetime.now()}') 175 | if buy_order: 176 | self.buy_orders.append(buy_order) 177 | else: 178 | self.buy_orders.sort(key=lambda x: float(x['price']), reverse=False) # 最低价到最高价 179 | delete_orders = [] 180 | for i in range(len(self.buy_orders) -1): 181 | order = self.buy_orders[i] 182 | next_order = self.buy_orders[i+1] 183 | 184 | if float(next_order['price'])/float(order['price']) - 1 < 0.001: 185 | print(f"买单之间价差太小,撤销订单:{next_order}, 时间: {datetime.now()}") 186 | cancel_order = self.http_client.cancel_order(next_order.get('symbol'), 187 | client_order_id=next_order.get('clientOrderId')) 188 | if cancel_order: 189 | delete_orders.append(next_order) 190 | 191 | for order in delete_orders: 192 | self.buy_orders.remove(order) 193 | 194 | 195 | if len(self.buy_orders) > int(config.max_orders): # 最多允许的挂单数量. 196 | # 订单数量比较多的时候. 197 | self.buy_orders.sort(key=lambda x: float(x['price']), reverse=False) # 最低价到最高价 198 | 199 | delete_order = self.buy_orders[0] 200 | print(f"订单太多了,撤销最低价的买单:{delete_order}, 时间: {datetime.now()}") 201 | order = self.http_client.cancel_order(delete_order.get('symbol'), client_order_id=delete_order.get('clientOrderId')) 202 | if order: 203 | self.buy_orders.remove(delete_order) 204 | 205 | # 没有卖单的时候. 206 | if len(self.sell_orders) <= 0: 207 | if ask_price > 0: 208 | price = round_to(ask_price * (1 + float(config.gap_percent)), float(config.min_price)) 209 | sell_order = self.http_client.place_order(symbol=config.symbol,order_side=OrderSide.SELL, order_type=OrderType.LIMIT, quantity=quantity,price=price) 210 | print(f'没有卖单,根据盘口下卖单:{sell_order} , 时间: {datetime.now()}') 211 | if sell_order: 212 | self.sell_orders.append(sell_order) 213 | 214 | else: 215 | self.sell_orders.sort(key=lambda x: float(x['price']), reverse=False) # 最低价到最高价 216 | delete_orders = [] 217 | for i in range(len(self.sell_orders) - 1): 218 | order = self.sell_orders[i] 219 | next_order = self.sell_orders[i + 1] 220 | 221 | if float(next_order['price']) / float(order['price']) - 1 < 0.001: 222 | print(f"卖单之间价差太小,撤销订单:{next_order}, 时间: {datetime.now()}") 223 | cancel_order = self.http_client.cancel_order(next_order.get('symbol'), 224 | client_order_id=next_order.get('clientOrderId')) 225 | if cancel_order: 226 | delete_orders.append(next_order) 227 | 228 | for order in delete_orders: 229 | self.sell_orders.remove(order) 230 | 231 | if len(self.sell_orders) > int(config.max_orders): # 最多允许的挂单数量. 232 | # 订单数量比较多的时候. 233 | self.sell_orders.sort(key=lambda x: x['price'], reverse=True) # 最高价到最低价 234 | 235 | delete_order = self.sell_orders[0] 236 | print(f"订单太多了,撤销最高价的卖单:{delete_order}, 时间:{datetime.now()}") 237 | order = self.http_client.cancel_order(delete_order.get('symbol'), 238 | client_order_id=delete_order.get('clientOrderId')) 239 | if order: 240 | self.sell_orders.remove(delete_order) 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /binance_grid_trader/trader/binance_trader.py: -------------------------------------------------------------------------------- 1 | """ 2 | 币安推荐码: 返佣10% 3 | https://www.binancezh.pro/cn/register?ref=AIR1GC70 4 | 5 | 币安合约推荐码: 返佣10% 6 | https://www.binancezh.com/cn/futures/ref/51bitquant 7 | 8 | if you don't have a binance account, you can use the invitation link to register one: 9 | https://www.binancezh.com/cn/futures/ref/51bitquant 10 | 11 | or use the inviation code: 51bitquant 12 | 13 | 网格交易: 适合币圈的高波动率的品种,适合现货, 如果交易合约,需要注意防止极端行情爆仓。 14 | 15 | 16 | 服务器购买地址: https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing 17 | """ 18 | 19 | 20 | from gateway import BinanceSpotHttp, OrderStatus, OrderType, OrderSide 21 | from utils import config 22 | from utils import utility, round_to 23 | from enum import Enum 24 | import logging 25 | from datetime import datetime 26 | 27 | class BinanceTrader(object): 28 | 29 | def __init__(self): 30 | """ 31 | :param api_key: 32 | :param secret: 33 | :param trade_type: 交易的类型, only support future and spot. 34 | """ 35 | self.http_client = BinanceSpotHttp(domain=config.api_domain, api_key=config.api_key, secret=config.api_secret, proxy_host=config.proxy_host, proxy_port=config.proxy_port) 36 | 37 | self.buy_orders = [] # 买单. 38 | self.sell_orders = [] # 卖单. 39 | 40 | 41 | def get_bid_ask_price(self): 42 | 43 | ticker = self.http_client.get_ticker(config.symbol) 44 | 45 | bid_price = 0 46 | ask_price = 0 47 | if ticker: 48 | bid_price = float(ticker.get('bidPrice', 0)) 49 | ask_price = float(ticker.get('askPrice', 0)) 50 | 51 | return bid_price, ask_price 52 | 53 | def grid_trader(self): 54 | """ 55 | 执行核心逻辑,网格交易的逻辑. 56 | :return: 57 | """ 58 | 59 | bid_price, ask_price = self.get_bid_ask_price() 60 | print(f"bid_price: {bid_price}, ask_price: {ask_price}") 61 | 62 | quantity = round_to(float(config.quantity), float(config.min_qty)) 63 | 64 | self.buy_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. 65 | self.sell_orders.sort(key=lambda x: float(x['price']), reverse=True) # 最高价到最低价. 66 | print(f"buy orders: {self.buy_orders}") 67 | print("------------------------------") 68 | print(f"sell orders: {self.sell_orders}") 69 | 70 | buy_delete_orders = [] # 需要删除买单 71 | sell_delete_orders = [] # 需要删除的卖单 72 | 73 | 74 | # 买单逻辑,检查成交的情况. 75 | for buy_order in self.buy_orders: 76 | 77 | check_order = self.http_client.get_order(buy_order.get('symbol', config.symbol),client_order_id=buy_order.get('clientOrderId')) 78 | 79 | if check_order: 80 | if check_order.get('status') == OrderStatus.CANCELED.value: 81 | buy_delete_orders.append(buy_order) 82 | print(f"buy order status was canceled: {check_order.get('status')}") 83 | elif check_order.get('status') == OrderStatus.FILLED.value: 84 | # 买单成交,挂卖单. 85 | logging.info(f"买单成交时间: {datetime.now()}, 价格: {check_order.get('price')}, 数量: {check_order.get('origQty')}") 86 | 87 | 88 | sell_price = round_to(float(check_order.get("price")) * (1 + float(config.gap_percent)), float(config.min_price)) 89 | 90 | if 0 < sell_price < ask_price: 91 | # 防止价格 92 | sell_price = round_to(ask_price, float(config.min_price)) 93 | 94 | new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, order_type=OrderType.LIMIT, quantity=quantity, price=sell_price) 95 | if new_sell_order: 96 | buy_delete_orders.append(buy_order) 97 | self.sell_orders.append(new_sell_order) 98 | 99 | buy_price = round_to(float(check_order.get("price")) * (1 - float(config.gap_percent)), 100 | config.min_price) 101 | if buy_price > bid_price > 0: 102 | buy_price = round_to(bid_price, float(config.min_price)) 103 | 104 | new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=quantity, price=buy_price) 105 | if new_buy_order: 106 | self.buy_orders.append(new_buy_order) 107 | 108 | 109 | elif check_order.get('status') == OrderStatus.NEW.value: 110 | print("buy order status is: New") 111 | else: 112 | print(f"buy order status is not above options: {check_order.get('status')}") 113 | 114 | # 过期或者拒绝的订单删除掉. 115 | for delete_order in buy_delete_orders: 116 | self.buy_orders.remove(delete_order) 117 | 118 | # 卖单逻辑, 检查卖单成交情况. 119 | for sell_order in self.sell_orders: 120 | 121 | check_order = self.http_client.get_order(sell_order.get('symbol', config.symbol), 122 | client_order_id=sell_order.get('clientOrderId')) 123 | if check_order: 124 | if check_order.get('status') == OrderStatus.CANCELED.value: 125 | sell_delete_orders.append(sell_order) 126 | 127 | print(f"sell order status was canceled: {check_order.get('status')}") 128 | elif check_order.get('status') == OrderStatus.FILLED.value: 129 | logging.info( 130 | f"卖单成交时间: {datetime.now()}, 价格: {check_order.get('price')}, 数量: {check_order.get('origQty')}") 131 | # 卖单成交,先下买单. 132 | buy_price = round_to(float(check_order.get("price")) * (1 - float(config.gap_percent)), float(config.min_price)) 133 | if buy_price > bid_price > 0: 134 | buy_price = round_to(bid_price, float(config.min_price)) 135 | 136 | new_buy_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.BUY, 137 | order_type=OrderType.LIMIT, quantity=quantity, price=buy_price) 138 | if new_buy_order: 139 | sell_delete_orders.append(sell_order) 140 | self.buy_orders.append(new_buy_order) 141 | 142 | sell_price = round_to(float(check_order.get("price")) * (1 + float(config.gap_percent)), float(config.min_price)) 143 | 144 | if 0 < sell_price < ask_price: 145 | # 防止价格 146 | sell_price = round_to(ask_price, float(config.min_price)) 147 | 148 | new_sell_order = self.http_client.place_order(symbol=config.symbol, order_side=OrderSide.SELL, 149 | order_type=OrderType.LIMIT, quantity=quantity, 150 | price=sell_price) 151 | if new_sell_order: 152 | self.sell_orders.append(new_sell_order) 153 | 154 | elif check_order.get('status') == OrderStatus.NEW.value: 155 | print("sell order status is: New") 156 | else: 157 | print(f"sell order status is not in above options: {check_order.get('status')}") 158 | 159 | # 过期或者拒绝的订单删除掉. 160 | for delete_order in sell_delete_orders: 161 | self.sell_orders.remove(delete_order) 162 | 163 | # 没有买单的时候. 164 | if len(self.buy_orders) <= 0: 165 | if bid_price > 0: 166 | price = round_to(bid_price * (1 - float(config.gap_percent)), float(config.min_price)) 167 | buy_order = self.http_client.place_order(symbol=config.symbol,order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=quantity,price=price) 168 | if buy_order: 169 | self.buy_orders.append(buy_order) 170 | 171 | elif len(self.buy_orders) > int(config.max_orders): # 最多允许的挂单数量. 172 | # 订单数量比较多的时候. 173 | self.buy_orders.sort(key=lambda x: float(x['price']), reverse=False) # 最低价到最高价 174 | 175 | delete_order = self.buy_orders[0] 176 | order = self.http_client.cancel_order(delete_order.get('symbol'), client_order_id=delete_order.get('clientOrderId')) 177 | if order: 178 | self.buy_orders.remove(delete_order) 179 | 180 | # 没有卖单的时候. 181 | if len(self.sell_orders) <= 0: 182 | if ask_price > 0: 183 | price = round_to(ask_price * (1 + float(config.gap_percent)), float(config.min_price)) 184 | order = self.http_client.place_order(symbol=config.symbol,order_side=OrderSide.SELL, order_type=OrderType.LIMIT, quantity=quantity,price=price) 185 | if order: 186 | self.sell_orders.append(order) 187 | 188 | elif len(self.sell_orders) > int(config.max_orders): # 最多允许的挂单数量. 189 | # 订单数量比较多的时候. 190 | self.sell_orders.sort(key=lambda x: x['price'], reverse=True) # 最高价到最低价 191 | 192 | delete_order = self.sell_orders[0] 193 | order = self.http_client.cancel_order(delete_order.get('symbol'), 194 | client_order_id=delete_order.get('clientOrderId')) 195 | if order: 196 | self.sell_orders.remove(delete_order) 197 | -------------------------------------------------------------------------------- /binance_grid_trader/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import config 2 | from .utility import * 3 | -------------------------------------------------------------------------------- /binance_grid_trader/utils/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | 4 | 币安推荐码: 返佣10% 5 | https://www.binancezh.pro/cn/register?ref=AIR1GC70 6 | 7 | 币安合约推荐码: 返佣10% 8 | https://www.binancezh.com/cn/futures/ref/51bitquant 9 | 10 | if you don't have a binance account, you can use the invitation link to register one: 11 | https://www.binancezh.com/cn/futures/ref/51bitquant 12 | 13 | or use the inviation code: 51bitquant 14 | 15 | 网格交易: 适合币圈的高波动率的品种,适合现货, 如果交易合约,需要注意防止极端行情爆仓。 16 | 17 | 18 | 服务器购买地址: https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing 19 | """ 20 | 21 | 22 | import json 23 | 24 | 25 | class Config: 26 | 27 | def __init__(self): 28 | 29 | self.platform: str = "binance_spot" # 交易的平台 30 | self.symbol:str = "BNBUSDT" # 交易对. 31 | self.gap_percent: float = 0.01 # 网格变化交易的单位. 32 | self.api_domain:str = "binance.com" # API接口网关域名 33 | self.api_key: str = None 34 | self.api_secret: str = None 35 | self.pass_phrase = None 36 | self.quantity:float = 1 37 | self.min_price = 0.0001 38 | self.min_qty = 0.01 39 | self.max_orders = 1 40 | self.proxy_host = "" # proxy host 41 | self.proxy_port = 0 # proxy port 42 | 43 | 44 | def loads(self, config_file=None): 45 | """ Load config file. 46 | 47 | Args: 48 | config_file: config json file. 49 | """ 50 | configures = {} 51 | if config_file: 52 | try: 53 | with open(config_file) as f: 54 | data = f.read() 55 | configures = json.loads(data) 56 | except Exception as e: 57 | print(e) 58 | exit(0) 59 | if not configures: 60 | print("config json file error!") 61 | exit(0) 62 | self._update(configures) 63 | 64 | def _update(self, update_fields): 65 | """ 66 | 更新update fields. 67 | :param update_fields: 68 | :return: None 69 | 70 | """ 71 | 72 | for k, v in update_fields.items(): 73 | setattr(self, k, v) 74 | 75 | config = Config() -------------------------------------------------------------------------------- /binance_grid_trader/utils/utility.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | General utility functions. 4 | 5 | 币安推荐码: 返佣10% 6 | https://www.binancezh.pro/cn/register?ref=AIR1GC70 7 | 8 | 币安合约推荐码: 返佣10% 9 | https://www.binancezh.com/cn/futures/ref/51bitquant 10 | 11 | if you don't have a binance account, you can use the invitation link to register one: 12 | https://www.binancezh.com/cn/futures/ref/51bitquant 13 | 14 | or use the inviation code: 51bitquant 15 | 16 | 网格交易: 适合币圈的高波动率的品种,适合现货, 如果交易合约,需要注意防止极端行情爆仓。 17 | 18 | 19 | 服务器购买地址: https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing 20 | """ 21 | 22 | 23 | import json 24 | from pathlib import Path 25 | from decimal import Decimal 26 | 27 | 28 | def _get_trader_dir(temp_name: str): 29 | """ 30 | Get path where trader is running in. 31 | """ 32 | cwd = Path.cwd() 33 | temp_path = cwd.joinpath(temp_name) 34 | 35 | if temp_path.exists(): 36 | return cwd, temp_path 37 | 38 | if not temp_path.exists(): 39 | temp_path.mkdir() 40 | 41 | return cwd, temp_path 42 | 43 | 44 | TRADER_DIR, TEMP_DIR = _get_trader_dir("trader") 45 | 46 | 47 | def get_file_path(filename: str): 48 | """ 49 | Get path for temp file with filename. 50 | """ 51 | return TEMP_DIR.joinpath(filename) 52 | 53 | 54 | def get_folder_path(folder_name: str): 55 | """ 56 | Get path for temp folder with folder name. 57 | """ 58 | folder_path = TEMP_DIR.joinpath(folder_name) 59 | if not folder_path.exists(): 60 | folder_path.mkdir() 61 | return folder_path 62 | 63 | def load_json(filename: str): 64 | """ 65 | Load data from json file in temp path. 66 | """ 67 | filepath = get_file_path(filename) 68 | 69 | if filepath.exists(): 70 | with open(filepath, mode="r", encoding="UTF-8") as f: 71 | data = json.load(f) 72 | return data 73 | else: 74 | save_json(filename, {}) 75 | return {} 76 | 77 | 78 | def save_json(filename: str, data: dict): 79 | """ 80 | Save data into json file in temp path. 81 | """ 82 | filepath = get_file_path(filename) 83 | with open(filepath, mode="w+", encoding="UTF-8") as f: 84 | json.dump( 85 | data, 86 | f, 87 | indent=4, 88 | ensure_ascii=False 89 | ) 90 | 91 | 92 | def round_to(value: float, target: float) -> float: 93 | """ 94 | Round price to price tick value. 95 | """ 96 | value = Decimal(str(value)) 97 | target = Decimal(str(target)) 98 | rounded = float(int(round(value / target)) * target) 99 | return rounded 100 | 101 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | binance_grid_trader: 5 | image: tiansin/binance_grid_trader:latest 6 | build: 7 | context: ./ 8 | dockerfile: Dockerfile 9 | restart: always 10 | environment: 11 | PLATFORM: "binance_spot" 12 | SYMBOL: "BNBUSDT" 13 | API_DOMAIN: "binancezh.co" 14 | API_KEY: "" 15 | API_SECRET: "" 16 | GAP_PERCENT: 0.01 17 | QUANTITY: 1 18 | MIN_PRICE: 0.0001 19 | MIN_QTY: 0.01 20 | MAX_ORDERS: 1 21 | PROXY_HOST: "" 22 | PROXY_PORT: 0 23 | logging: 24 | options: 25 | max-size: 10m 26 | max-file: "3" 27 | 28 | -------------------------------------------------------------------------------- /qr.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansin/docker_grid_trader/96a7a7c1fb6e6115bcd75324437b225ec4bf127b/qr.jpeg --------------------------------------------------------------------------------