├── README.md ├── config.ini ├── okex_trading.py ├── okex_trading_guide.md ├── okex_trading_ui_guide.md └── signal_trading_tutorial.md /README.md: -------------------------------------------------------------------------------- 1 | # ouyi_trading_bot_for_tradingview 2 | **[重大更新]最新网页版无需自行购买服务器安装啦** 3 | 4 | TVCBot网页版交易机器人上线了,官网:https://www.tvcbot.com/aff.php?aff=3 *【任何在电报联系让你付款的都是骗子】* 5 | 专业的事留给专业的人做,鉴于许多朋友觉得部署服务器难度太大,区块普拉斯开发了网页版的机器人,大家只管做交易就行,如果大家不想自己去买服务器部署,省去服务器部署和安装软件的环节,可以尝试网页版的交易机器人,直接就能用起来了,不用看区块普拉斯前面的软件部署视频了 6 | 7 | **加入我们的交流群** 8 | 建议加入TG群组防止失联保持联系:https://t.me/tvcbot8 9 | 10 | **项目简介** 11 | 你是否想将TradingView交易策略做实盘运行但是苦于不会代码?这个程序就是让你即使不会写代码也能将TradingView策略实盘交易实现 12 | 欧易交易所TradingView交易机器人接口服务,通过HTTP接口来对接TradingView交易策略和交易指标,实现TradingView策略自动交易 13 | 14 | 15 | **视频教程** 16 | Youtube教程:https://www.youtube.com/channel/UCp7irV9eIH1BAKSAAQOJcXw 17 | Bilibili教程:https://space.bilibili.com/1970832679 18 | 19 | **有疑问如何与我联系** 20 | 我的微信ID是:blockplus 21 | 22 | 23 | 24 | 25 | **-------------------以上是新版说明-------------------** 26 | **-------------------以下是旧版说明-------------------** 27 | 28 | 29 | **它是如何工作的** 30 | 31 | ![image](https://user-images.githubusercontent.com/94948670/143181162-54d46868-d4cd-4f1f-bbc4-a5836f0c1f5d.png) 32 | 要实现自动交易,我们需要做的工作就是: 33 | 1.稍微小改一下TradingView的交易策略代码,这一部分很简单,人人都能学会,我的Youtube频道和B站频道会教大家如何修改 34 | 2.我们需要一台海外服务器来运行这个软件,不建议使用个人电脑,因为不稳定,而且一般没有外网IP。具体如何部署请看我的另一个视频和文章 如何在服务器上运行这个软件 35 | 36 | **写在前面** 37 | 1.只支持欧易交易所,目前不支持币安binance,因为欧易可以继续为国内用户服务,没注册过的可以通过这里注册:https://okx.com/join/github 享受20%手续费优惠,别小看这20%的优惠(之前口误了),在量化交易中累积起来是很恐怖的 38 | 2.目前建议使用合约交易,使用一倍杠杆就和现货一样了 39 | 3.此脚本仅作学习使用,抛砖引玉,希望大家从这里得到启发,写出更好的东西 40 | 41 | **使用方法** 42 | 方法一(推荐!适合新手小白不会写程序的):直接使用我这里写好的UI界面程序,在本页面找到“Release”下载Configer程序即可。[前往查看使用说明>>](https://github.com/blockplusim/crypto_trading_service_for_tradingview/blob/main/okex_trading_ui_guide.md) 43 | 方法二(适合有Python开发经验的):在Python环境中直接运行本`Okex_trading.py`程序。[前往查看使用说明>>](https://github.com/blockplusim/crypto_trading_service_for_tradingview/blob/main/okex_trading_guide.md) 44 | 45 | 46 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [account] 2 | api_key = b4303333-2222-4fc5-1111-102ae6cf0000 3 | secret = 3A6427asd77425555A4CEBF64FB7684 4 | password = 123456789 5 | enable_proxies = True 6 | proxies = http://127.0.0.1:10800 7 | 8 | [trading] 9 | symbol = BTC-USDT-SWAP 10 | amount = 1 11 | price = 69999 12 | td_mode = isolated 13 | lever = 1 14 | 15 | [service] 16 | api_sec = 5BFasdasdtNg7hwkgCzYQMJSDHDSHD2qOzmtldJasdc 17 | listen_host = 0.0.0.0 18 | listen_Port = 5002 19 | debug_mode = True 20 | ip_white_list = 52.89.214.238,34.212.75.30,54.218.53.128,52.32.178.7,127.0.0.1 -------------------------------------------------------------------------------- /okex_trading.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import configparser 3 | import ccxt 4 | import logging 5 | from flask import Flask 6 | from flask import request, abort 7 | import json 8 | import urllib.request 9 | import requests 10 | import os 11 | import _thread 12 | import time 13 | 14 | 15 | # 读取配置文件,优先读取json格式,如果没有就读取ini格式 16 | config = {} 17 | if os.path.exists('./config.json'): 18 | config = json.load(open('./config.json',encoding="UTF-8")) 19 | elif os.path.exists('./config.ini'): 20 | conf = configparser.ConfigParser() 21 | conf.read("./config.ini", encoding="UTF-8") 22 | for i in dict(conf._sections): 23 | config[i] = {} 24 | for j in dict(conf._sections[i]): 25 | config[i][j] = conf.get(i, j) 26 | config['account']['enable_proxies'] = config['account']['enable_proxies'].lower() == "true" 27 | config['trading']['enable_stop_loss'] = config['trading']['enable_stop_loss'].lower() == "true" 28 | config['trading']['enable_stop_gain'] = config['trading']['enable_stop_gain'].lower() == "true" 29 | else: 30 | logging.info("配置文件 config.json 不存在,程序即将退出") 31 | exit() 32 | 33 | # 服务配置 34 | apiSec = config['service']['api_sec'] 35 | listenHost = config['service']['listen_host'] 36 | listenPort = config['service']['listen_port'] 37 | debugMode = config['service']['debug_mode'] 38 | ipWhiteList = config['service']['ip_white_list'].split(",") 39 | 40 | # 交易对 41 | symbol = config['trading']['symbol'] 42 | amount = config['trading']['amount'] 43 | tdMode = config['trading']['td_mode'] 44 | lever = config['trading']['lever'] 45 | 46 | # 交易所API账户配置 47 | accountConfig = { 48 | 'apiKey': config['account']['api_key'], 49 | 'secret': config['account']['secret'], 50 | 'password': config['account']['password'], 51 | 'enable_proxies': config['account']['enable_proxies'], 52 | 'proxies': { 53 | 'http': config['account']['proxies'], # these proxies won't work for you, they are here for example 54 | 'https': config['account']['proxies'], 55 | } 56 | } 57 | 58 | # 格式化日志 59 | LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" 60 | DATE_FORMAT = "%Y/%m/%d/ %H:%M:%S %p" 61 | logging.basicConfig(filename='okex_trade.log', level=logging.INFO, format=LOG_FORMAT, datefmt=DATE_FORMAT) 62 | # logging.FileHandler(filename='okex_trade.log', encoding=) 63 | 64 | # CCXT初始化 65 | exchange = ccxt.okex5(config={ 66 | 'enableRateLimit': True, 67 | 'apiKey': accountConfig['apiKey'], 68 | 'secret': accountConfig['secret'], 69 | # okex requires this: https://github.com/ccxt/ccxt/wiki/Manual#authentication 70 | 'password': accountConfig['password'], 71 | 'verbose': False, # for debug output 72 | }) 73 | if accountConfig['enable_proxies'] is True: 74 | exchange.proxies = accountConfig['proxies'] 75 | if 'ouyihostname' in config['account']: 76 | exchange.hostname = config['account']['ouyi_hostname'] 77 | 78 | # lastOrdId 79 | lastOrdId = 0 80 | lastOrdType = None 81 | lastAlgoOrdId = 0 82 | 83 | # 挂止盈止损单 84 | def sltpThread(oid, side, symbol, sz, tdMode, config): 85 | global lastOrdType,lastAlgoOrdId 86 | privatePostTradeOrderAlgoParams = { 87 | "instId": symbol, 88 | "tdMode": tdMode, 89 | "side": "sell" if side.lower() == "buy" else "buy", 90 | "ordType": "oco", 91 | "sz": sz 92 | } 93 | if config['trading']['enable_stop_loss']: 94 | privatePostTradeOrderAlgoParams['slTriggerPx'] = config['trading']['stop_loss_trigger_price'] 95 | privatePostTradeOrderAlgoParams['slOrdPx'] = config['trading']['stop_loss_order_price'] 96 | if config['trading']['enable_stop_gain']: 97 | privatePostTradeOrderAlgoParams['tpTriggerPx'] = config['trading']['stop_gain_trigger_price'] 98 | privatePostTradeOrderAlgoParams['tpOrdPx'] = config['trading']['stop_gain_order_price'] 99 | while True: 100 | try: 101 | privateGetTradeOrderRes = exchange.privateGetTradeOrder(params={"ordId": oid,"instId": symbol}) 102 | print(privateGetTradeOrderRes) 103 | if privateGetTradeOrderRes['data'][0]['state'] == "filled": 104 | avgPx = float(privateGetTradeOrderRes['data'][0]['avgPx']) 105 | direction = -1 if side.lower() == "buy" else 1 106 | slTriggerPx = (1 + direction * float(config['trading']['stop_loss_trigger_price'])*0.01) * avgPx 107 | tpOrdPx = (1 + direction * float(config['trading']['stop_gain_order_price'])*0.01) * avgPx 108 | tpTriggerPx = (1 - direction * float(config['trading']['stop_gain_trigger_price'])*0.01) * avgPx 109 | slOrdPx = (1 - direction * float(config['trading']['stop_loss_order_price'])*0.01) * avgPx 110 | privatePostTradeOrderAlgoParams['slTriggerPx'] = '%.12f' % slTriggerPx 111 | privatePostTradeOrderAlgoParams['slOrdPx'] = '%.12f' % slOrdPx 112 | privatePostTradeOrderAlgoParams['tpTriggerPx'] = '%.12f' % tpTriggerPx 113 | privatePostTradeOrderAlgoParams['tpOrdPx'] = '%.12f' % tpOrdPx 114 | print("订单{oid}设置止盈止损...".format(oid=oid)) 115 | privatePostTradeOrderAlgoRes = exchange.privatePostTradeOrderAlgo(params=privatePostTradeOrderAlgoParams) 116 | if 'code' in privatePostTradeOrderAlgoRes and privatePostTradeOrderAlgoRes['code'] == '0': 117 | lastAlgoOrdId = privatePostTradeOrderAlgoRes['data'][0]['algoId'] 118 | break 119 | else: 120 | continue 121 | elif privateGetTradeOrderRes['data'][0]['state'] == "canceled": 122 | lastOrdType = None 123 | break 124 | except Exception as e: 125 | print(e) 126 | time.sleep(1) 127 | print("订单{oid}止盈止损单挂单结束".format(oid=oid)) 128 | 129 | 130 | 131 | # 设置杠杆 132 | def setLever(_symbol, _tdMode, _lever): 133 | try: 134 | privatePostAccountSetLeverageRes = exchange.privatePostAccountSetLeverage( 135 | params={"instId": _symbol, "mgnMode": _tdMode, "lever": _lever}) 136 | # logging.info(json.dumps(privatePostAccountSetLeverageRes)) 137 | return True 138 | except Exception as e: 139 | # logging.error("privatePostTradeCancelBatchOrders " + str(e)) 140 | return False 141 | 142 | # 取消止盈止损订单 143 | 144 | 145 | # 市价全平 146 | def cancelLastOrder(_symbol, _lastOrdId): 147 | try: 148 | res = exchange.privatePostTradeCancelOrder(params={"instId": _symbol, "ordId": _lastOrdId}) 149 | # logging.info("privatePostTradeCancelBatchOrders " + json.dumps(res)) 150 | return True 151 | except Exception as e: 152 | # logging.error("privatePostTradeCancelBatchOrders " + str(e)) 153 | return False 154 | 155 | 156 | # 平掉所有仓位 157 | def closeAllPosition(_symbol, _tdMode): 158 | try: 159 | res = exchange.privatePostTradeClosePosition(params={"instId": _symbol, "mgnMode": _tdMode}) 160 | # logging.info("privatePostTradeClosePosition " + json.dumps(res)) 161 | return True 162 | except Exception as e: 163 | logging.error("privatePostTradeClosePosition " + str(e)) 164 | return False 165 | 166 | # 开仓 167 | def createOrder(_symbol, _amount, _price, _side, _ordType, _tdMode, enable_stop_loss=False, stop_loss_trigger_price=0, stop_loss_order_price=0, enable_stop_gain=False, stop_gain_trigger_price=0, stop_gain_order_price=0): 168 | try: 169 | # 挂单 170 | res = exchange.privatePostTradeOrder( 171 | params={"instId": _symbol, "sz": _amount, "px": _price, "side": _side, "ordType": _ordType, 172 | "tdMode": _tdMode}) 173 | global lastOrdId,config 174 | lastOrdId = res['data'][0]['ordId'] 175 | # 如果止盈止损 176 | if config['trading']['enable_stop_loss'] or config['trading']['enable_stop_gain']: 177 | try: 178 | _thread.start_new_thread(sltpThread, (lastOrdId, _side, _symbol, _amount, _tdMode, config)) 179 | except: 180 | logging.error("Error: unable to run sltpThread") 181 | return True, "create order successfully" 182 | except Exception as e: 183 | logging.error("createOrder " + str(e)) 184 | return False, str(e) 185 | 186 | 187 | # 获取公共数据,包含合约面值等信息 188 | def initInstruments(): 189 | c = 0 190 | try: 191 | # 获取永续合约基础信息 192 | swapInstrumentsRes = exchange.publicGetPublicInstruments(params={"instType": "SWAP"}) 193 | if swapInstrumentsRes['code'] == '0': 194 | global swapInstruments 195 | swapInstruments = swapInstrumentsRes['data'] 196 | c = c + 1 197 | except Exception as e: 198 | logging.error("publicGetPublicInstruments " + str(e)) 199 | try: 200 | # 获取交割合约基础信息 201 | futureInstrumentsRes = exchange.publicGetPublicInstruments(params={"instType": "FUTURES"}) 202 | if futureInstrumentsRes['code'] == '0': 203 | global futureInstruments 204 | futureInstruments = futureInstrumentsRes['data'] 205 | c = c + 1 206 | except Exception as e: 207 | logging.error("publicGetPublicInstruments " + str(e)) 208 | return c >= 2 209 | 210 | # 将 amount 币数转换为合约张数 211 | # 币的数量与张数之间的转换公式 212 | # 单位是保证金币种(币本位的币数单位为币,U本位的币数单位为U) 213 | # 1、币本位合约:币数=张数*面值*合约乘数/标记价格 214 | # 2、U本位合约:币数=张数*面值*合约乘数*标记价格 215 | # 交割合约和永续合约合约乘数都是1 216 | def amountConvertToSZ(_symbol, _amount, _price, _ordType): 217 | _symbol = _symbol.upper() 218 | _symbolSplit = _symbol.split("-") 219 | isSwap = _symbol.endswith("SWAP") 220 | # 获取合约面值 221 | def getFaceValue(_symbol): 222 | instruments = swapInstruments if isSwap else futureInstruments 223 | for i in instruments: 224 | if i['instId'].upper() == _symbol: 225 | return float(i['ctVal']) 226 | return False 227 | faceValue = getFaceValue(_symbol) 228 | if faceValue is False: 229 | raise Exception("getFaceValue error.") 230 | # 币本位合约:张数 = 币数 / 面值 / 合约乘数 * 标记价格 231 | # U本位合约:张数 = 币数 / 面值 / 合约乘数 232 | sz = float(_amount) / faceValue / 1 233 | if _symbolSplit[1] == "USD": 234 | # 如果是市价单,获取一下最新标记价格 235 | if _ordType.upper() == "MARKET": 236 | _price = exchange.publicGetPublicMarkPrice(params={"instId": _symbol,"instType":("SWAP" if isSwap else "FUTURES")})['data'][0]['markPx'] 237 | sz = sz * float(_price) 238 | return int(sz) 239 | 240 | 241 | # 初始化杠杆倍数 242 | setLever(symbol, tdMode, lever) 243 | 244 | app = Flask(__name__) 245 | 246 | @app.before_request 247 | def before_req(): 248 | if request.json is None: 249 | abort(400) 250 | if request.remote_addr not in ipWhiteList: 251 | abort(403) 252 | if "apiSec" not in request.json or request.json["apiSec"] != apiSec: 253 | abort(401) 254 | 255 | 256 | @app.route('/order', methods=['POST']) 257 | def order(): 258 | ret = { 259 | "cancelLastOrder": False, 260 | "closedPosition": False, 261 | "createOrderRes": False, 262 | "msg": "" 263 | } 264 | # 获取参数 或 填充默认参数 265 | _params = request.json 266 | if "apiSec" not in _params or _params["apiSec"] != apiSec: 267 | ret['msg'] = "Permission Denied." 268 | return ret 269 | if "symbol" not in _params: 270 | _params["symbol"] = symbol 271 | if "amount" not in _params: 272 | _params["amount"] = amount 273 | if "tdMode" not in _params: 274 | _params["tdMode"] = tdMode 275 | if "side" not in _params: 276 | ret['msg'] = "Please specify side parameter" 277 | return ret 278 | # 如果修改杠杆倍数,那么需要请重新请求一下 279 | if "lever" in _params and _params['lever'] != lever: 280 | setLever(_params['symbol'], _params['lever'], _params['lever']) 281 | 282 | # 注意:开单的时候会先把原来的仓位平掉,然后再把你的多单挂上 283 | global lastOrdType 284 | if _params['side'].lower() in ["buy", "sell"]: 285 | # 不允许本次开单和上次开单是一样的方向 286 | if lastOrdType is not None: 287 | if lastOrdType == "sell" and _params['side'] == "sell": 288 | ret['msg'] = "sell side duplacated" 289 | return ret 290 | if lastOrdType == "buy" and _params['side'] == "buy": 291 | ret['msg'] = "buy side duplacated" 292 | return ret 293 | # 先取消未成交的挂单 然后平仓 294 | ret["cancelLastOrder"] = cancelLastOrder(_params['symbol'], lastOrdId) 295 | ret["closedPosition"] = closeAllPosition(_params['symbol'], _params['tdMode']) 296 | # 开仓 297 | sz = amountConvertToSZ(_params['symbol'], _params['amount'], _params['price'], _params['ordType']) 298 | if sz < 1: 299 | ret['msg'] = 'Amount is too small. Please increase amount.' 300 | else: 301 | ret["createOrderRes"], ret['msg'] = createOrder(_params['symbol'], sz, _params['price'], _params['side'], 302 | _params['ordType'], _params['tdMode']) 303 | lastOrdType = _params['side'] 304 | # 平仓 305 | elif _params['side'].lower() in ["close"]: 306 | lastOrdType = None 307 | ret["closedPosition"] = closeAllPosition(_params['symbol'], _params['tdMode']) 308 | 309 | # 取消挂单 310 | elif _params['side'].lower() in ["cancel"]: 311 | lastOrdType = None 312 | ret["cancelLastOrder"] = cancelLastOrder(_params['symbol'], lastOrdId) 313 | else: 314 | pass 315 | 316 | return ret 317 | 318 | 319 | if __name__ == '__main__': 320 | try: 321 | ip = json.load(urllib.request.urlopen('http://httpbin.org/ip'))['origin'] 322 | logging.info("*区块普拉斯(Youtube/Bilibili)自动交易服务端\n") 323 | logging.info( 324 | "①.此程序仅支持OKEX欧易交易所(https://www.okx.com/join/tradingview 此链接注册的账号交易手续费优惠二折)".format( 325 | listenPort=listenPort, listenHost=listenHost, ip=ip)) 326 | logging.info( 327 | "②.建议运行再在有独立IP的服务器上,若在个人电脑运行,需要FRP内网穿透,而且影响软件效率".format( 328 | listenPort=listenPort, listenHost=listenHost, ip=ip)) 329 | logging.info( 330 | "③.请务必修改 config.ini 中的apiSec,随便修改为复杂的密钥".format( 331 | listenPort=listenPort, listenHost=listenHost, ip=ip)) 332 | logging.info( 333 | "系统接口服务即将启动!服务监听地址{listenHost}:{listenPort}".format( 334 | listenPort=listenPort, listenHost=listenHost, ip=ip)) 335 | logging.info( 336 | "接口外网访问地址:http://{ip}:{listenPort}/order".format( 337 | listenPort=listenPort, listenHost=listenHost, ip=ip)) 338 | logging.info("请不要关闭这个黑色窗口!否则交易服务将自动停止,接口无法使用!") 339 | 340 | # 初始化交易币对基础信息 341 | if initInstruments() is False: 342 | msg = "初始化货币基础信息失败,请重试" 343 | raise Exception(msg) 344 | # 启动服务 345 | app.run(debug=debugMode, port=listenPort, host=listenHost) 346 | except Exception as e: 347 | logging.error(e) 348 | pass 349 | -------------------------------------------------------------------------------- /okex_trading_guide.md: -------------------------------------------------------------------------------- 1 | ## okex_trading.py 使用指南 2 | 适用于有python使用经验的朋友,因为只是一个python脚本 3 | 4 | **使用流程** 5 | 1.购买海外服务器,注意必须是中国大陆以外的,因为许多交易所目前大陆IP无法连接和交易 6 | 2.安装 Python3.6 环境,安装必备的包:ccxt flask 等 7 | 3.我们需要修改 ***config.ini*** 文件。该文件包含以下内容 8 | 9 | ```ini 10 | [account] 11 | apiKey = 12 | secret = 13 | password = 14 | enable_proxies = False 15 | proxies = 16 | 17 | [trading] 18 | symbol = TRX-USDT-SWAP 19 | amount = 1 20 | tdMode = isolated 21 | lever = 5 22 | 23 | [service] 24 | apiSec = 5BFJYtNg7hwkgCzYQMwwQNctqan9CmqOzmtldJc 25 | listenHost = 0.0.0.0 26 | listenPort = 80 27 | debugMode = True 28 | ipWhiteList = 52.89.214.238,34.212.75.30,54.218.53.128,52.32.178.7,127.0.0.1 29 | ``` 30 | 31 | **[account]** 节点主要配置欧易交易所的API信息 32 | apiKey、secret和password可以在欧易个人中心的API中创建(创建API记得给予交易和读取权限,不要给提现的权限),enable_proxies是是否启用代理的意思,如果你的服务器是国内服务器,一般需要启用,启用填写True,proxies填写具体的代理地址,如:http://127.0.0.1:1080 所以建议使用海外服务器来运行这个程序 33 | 34 | **[trading]** 交易API的默认配置 35 | **symbol**:交易对,比特币BTC/USDT永续合约就是 BTC-USDT-SWAP 其他币也类似,不知道这个怎么设置的话,可以打开电脑版欧易,打开 交易 -> 基础交易 -> 切换你要交易的币对 -> 浏览器地址栏最后面就有交易对信息 36 | **amount**:交易的数量,币的个数。注意这里统一使用币来计价,比如你开BTC,那么这里就是开BTC的数量 37 | **lever**:默认杠杆倍数,如果 /order 请求中带有这个参数,系统会根据lever重新修改杠杆倍数 38 | **tdMode**:保证金模式,isolated:逐仓 ;cross:全仓 39 | 40 | **[service]** 服务配置 41 | **apiSec**:通信密钥,这个一定要修改,随便改长一点复杂一点就行了 42 | **listenHost**: 服务监听地址,一般默认即可 43 | **listenPort**:服务监听端口,一般80即可,注意 TradingView 只支持80和443 44 | **debugMode**:True或者False,开启debug模式的话日志输出会更详细 45 | **ipWhiteList**:授权使用本服务接口的IP地址集合,每个IP地址用英文逗号 **,** 隔开,这里默认前面四个地址是 TradingView 的官方地址 46 | 47 | **运行环境** 48 | Python 3.6.10 49 | ccxt 1.56.41 50 | flask 1.1.2 51 | 52 | **如何运行** 53 | 修改玩config.ini中的配置后,直接使用 `python okex_trading.py` 启动即可 54 | -------------------------------------------------------------------------------- /okex_trading_ui_guide.md: -------------------------------------------------------------------------------- 1 | ## okex_trading_ui 使用说明 2 | 预编译好且带有操作界面的程序,操作比较简单,适合新手小白 3 | 4 | **使用前准备** 5 | 1.需要准备一个欧易OKEX交易所账号,如果你还没有注册,请先[注册一个账号](https://www.ouyicn.group/join/GITHUB),然后到账号API申请V5API,备注名称随意,权限需要选择只读和交易,不要勾选提现!然后确认添加即可 6 | 2.你需要购买一台海外的服务器用于24小时自动运行这个自动交易程序,可以选择腾讯云轻量应用服务器香港等东亚地区的服务器(注意必须买海外的,推荐香港服务器,因为OKX服务在香港,这样速度快点),可查看[腾讯轻量服务器教程](https://www.bilibili.com/video/BV1VP4y1H71e?spm_id_from=333.999.0.0) 7 | 8 | **需要注意的东西** 9 | 1.开始跑策略对接这个软件之前,务必清空你再这个币种的现有仓位,以免导致管理混乱 10 | 2.支持同时跑多个策略和多个币种的,但是一个币种只能同时跑一个策略,不要同时一个币跑多个币种,必然会管理混乱 11 | 3.如果在软件运行过程中,你手动去管理你的这个策略仓位,可能导致数据不一致,这时候你应该在软件界面停止服务再启动服务才可以 12 | 4.请注意,如果启动后发现没有下单成功,可以登录OKEX电脑版,交易界面,右边有个设置,把 `下单模式` 改为 `买卖模式` 13 | 14 | **使用步骤** 15 | 1.在服务器上下载程序,从 `release` 中下载 `okex_trading_ui` 程序并解压到服务器上。[服务器上打开这个链接下载](https://github.com/blockplusim/crypto_trading_service_for_tradingview/releases) 16 | 2.下载完成以后解压,直接双击打开 `configurator.exe` 界面如下: 17 | 18 | 19 | 20 | 在上面的界面中,我们看到需要用到三个参数 apiKey / secret / password 。这三个参数我们可以在欧易OKEX个人中心中API中申请V5API中获取,然后把三个参数复制过来。如果你用的是香港或者其他海外服务器,那么启用代理这个不需要勾选 21 | 22 | 23 | 24 | 下一步我们配置交易相关信息,这里的防止重复下单意思是不允许重复下单,也就是不允许金字塔加仓模式的,如果允许加仓,就不要勾选防止重复下单 25 | 26 | 27 | 28 | 下一步在服务配置这边,一般需要修改 `通信密钥` 和 `外网IP`, 这里通信密钥就是一个简单的密码,用于验证身份;外网IP就是你运行这个软件的服务器的IP地址。我们在TradingView调用的时候需要用到 29 | 30 | 31 | 32 | 配置完成后,点击 `保存配置`,然后再点击 `启动服务` 即可,然后点击 `查看日志` 可以看到运行的情况 33 | 34 | 35 | 36 | 这个启动完成后,我们就可以对接 `TradingView` 来实现自动交易了 37 | 38 | 39 | 40 | 在香港服务器上运行成功界面如上图所示 41 | 42 | 当我们运行成功后,下一步需要和TradingView进行对接 43 | 44 | 点击 `警报生成` 选项来生成TradingView对接信息 45 | 46 | 47 | 48 | 这里有四个选项,分别是 `做多`、`做空`、`市价平仓`和`取消挂单`,我们可以根据TradingView的代码需求进行对接,点击 `生成TradingView告警配置` 生成如下信息 49 | 50 | 51 | 52 | 将对应的信息粘贴到TradingView配置项即可 53 | 54 | 55 | -------------------------------------------------------------------------------- /signal_trading_tutorial.md: -------------------------------------------------------------------------------- 1 | **策略改为自动化交易的信号指标流程** 2 | 3 | **步骤一:选好交易币种/交易对,选好K线时间段** 4 | 5 | 如下图,首先点击这里选币 6 | 7 | ![image](https://user-images.githubusercontent.com/94948670/146497787-fef88716-4083-464b-b0bc-0a92fc522d7e.png) 8 | 9 | 比如我们要用BTCUSDT然后选OKEX的是数据源,注意我们软件用OKEX,所以数据源一定要选OKEX,如果用合约,数据源最好也选对应的合约数据源,根据下图选取即可 10 | 11 | ![image](https://user-images.githubusercontent.com/94948670/146497914-8540a4ef-f6c2-4505-a26c-0356f70fa99e.png) 12 | 13 | 选择K线周期,以4小时为例 14 | 15 | ![image](https://user-images.githubusercontent.com/94948670/146497979-22a8b2e0-0a66-4d9f-b64f-b2f6ea09ff1e.png) 16 | 17 | 18 | **步骤二:修改策略为指标** 19 | 20 | 选定我们需要修改的策略,我们演示用MACD策略,记住这个名字 `MACD Strategy`,点击代码,查看策略代码 21 | 22 | ![image](https://user-images.githubusercontent.com/94948670/146494270-a225e2c1-580f-43da-9d6a-25293bc7166f.png) 23 | 24 | 出现这个代码窗口,点击解锁,然后可以改名,也可以直接点保存 25 | 26 | ![image](https://user-images.githubusercontent.com/94948670/146494374-5fdc540e-f9a3-40f9-b732-54df4b821185.png) 27 | 28 | 修改这一行代码,把strategy改为indicator。其他参数可不改 29 | 30 | ![image](https://user-images.githubusercontent.com/94948670/146494482-3d5104f4-a362-479f-912a-dea3bd00f8a4.png) 31 | 32 | 复制以下代码模板粘贴到代码窗口代码最下面 33 | 34 | ``` 35 | condition1 = false 36 | condition2 = false 37 | condition3 = false 38 | 39 | //开多代码信号 40 | plotshape(condition1, text='▲', style=shape.labeldown, textcolor=color.white, color = color.green, location = location.abovebar, title = "开多") 41 | alertcondition(condition1 , title="Buy", message="Buy") 42 | //开空代码信号 43 | plotshape(condition2, text='▼', style=shape.labeldown, textcolor=color.white, color = color.red, location = location.abovebar, title = "开空") 44 | alertcondition(condition2 , title="Sell", message="Sell") 45 | //平仓代码信号 46 | alertcondition(condition3 , title="Cancel", message="Cancel") 47 | ``` 48 | 49 | 以上的 `condition1 = false` `condition2 = false` `condition3 = false` 就是需要操作判断的代码。也是我们需要修改的片段,根据视频教程修改即可。粘贴后的代码如下 50 | 51 | ![image](https://user-images.githubusercontent.com/94948670/146494888-7a899348-178e-43bb-985e-06fca1454eee.png) 52 | 53 | 接下来我们需要修改触发条件 54 | 55 | ![image](https://user-images.githubusercontent.com/94948670/146495044-b86d9e2c-236a-4b07-9eeb-ed75715bf073.png) 56 | 57 | 其他的不需要修改,接着我们把TradingView策略的代码用“//”注释掉,选中图中代码注释按CTRL+/键即可(注释的意思就是让所注释的代码失效,不执行功能) 58 | 59 | ![image](https://user-images.githubusercontent.com/94948670/146495403-7dab9953-20e5-4848-929d-a60b51b43133.png) 60 | 61 | 接下来就是保存代码并且添加到图表 62 | 63 | ![image](https://user-images.githubusercontent.com/94948670/146495638-b0c816a2-59cc-479a-bd3b-673306fb135a.png) 64 | 65 | 下一步我们就可以在图表中看到做多或者做空信号了,如下图所示 66 | 67 | ![image](https://user-images.githubusercontent.com/94948670/146495782-07a4cc63-a29c-49fe-baa3-dd8f3a261f90.png) 68 | 69 | **步骤三:添加交易通知** 70 | 71 | 接下来我们根据做多和做空信号通知提醒我们的服务器来实现策略自动下单,点击右上角这边的时钟图标 72 | 73 | ![image](https://user-images.githubusercontent.com/94948670/146495922-68ea6800-a758-4b3f-8efd-a9272a43dfa9.png) 74 | 75 | 接下来选择要设置交易提醒的条件,选择我们的策略 `MACD Strategy`,展开第二行的选项,有 Buy、 Sell 和 Cancel三个选项,这就是买入、卖出和取消信号。我们先选择一个 Buy 信号吧,Options选择Once Per Bar Close 76 | 77 | ![image](https://user-images.githubusercontent.com/94948670/146496279-06c88f6f-ad15-44c3-b1a4-ef2226875d0c.png) 78 | 79 | 接下来添加通知服务器的地址,勾选 `WebHook URL`,可以参考我这个地址,把IP地址换位你的腾讯云服务器IP地址即可 80 | 81 | ![image](https://user-images.githubusercontent.com/94948670/146496959-6ac18d6d-0e99-4d8a-ae24-064eee449cb0.png) 82 | 83 | 84 | 复制下面的 `TradingView通知参数模板` 到消息框(message)。 85 | 86 | ``` 87 | { 88 | "symbol": "SHIB-USDT-SWAP", 89 | "price": {{close}}, 90 | "amount": "1000000", 91 | "side": "buy", 92 | "ordType": "limit", 93 | "apiSec": "5BFJYtNg7hwkgCzYQMwwQNctqan9CmqOzmtldJc" 94 | } 95 | ``` 96 | 以上内容可能需要改动,怎么改可以看视频 97 | symbol是交易对,可以从OKEX的官网地址栏或者,详见视频说明。 98 | price是挂单的价格,一般用{{close}}就是收盘价,还有 open(开盘价) / high(最高价) /low(最低价) 等 99 | amount就是挂单的数量,统一用币的单位计算,比如0.1个比特币就填0.1,1000000个shib就用1000000 100 | side就是挂单方向,有四种取值。sell -> 开空(如果有仓位会先平掉);buy -> 开多(如果有仓位会先平掉);close -> 平仓 ;cancel -> 取消所有未成交的挂单 101 | ordType挂单类型,可以是limit限价单,或者是market市价单,使用市价market的话price会失效 102 | apiSec是我们再软件服务配置页面中设置的通信密钥参数。 103 | 104 | 复制以上代价粘贴进message 105 | 106 | ![image](https://user-images.githubusercontent.com/94948670/146497227-d64b399d-e7cc-4828-9d71-7eb271e5a03e.png) 107 | 108 | 继续点击create(创建)即可。然后重复第三步,但是这次要设置卖出信号源,因为买入的已经设置好了。 109 | 设置完成以后即可自动通知交易 110 | 更多信息看我的视频(油管/bilibili:区块普拉斯) 111 | 112 | --------------------------------------------------------------------------------