TradingViewWebHook報警直連FMZ機器人

最近越來越多的TradingView使用者,把TradingView圖表信號連通到發明者量化平臺(FMZ.COM)上根據圖表信號,讓FMZ上的機器人策略執行交易,這樣對於編程技術小白來說省去了大量代碼編寫、設計工作。直接可以讓指標用於程序化、自動化交易,降低了不少程序化、量化交易開發門檻。對於TradingViewWebHook實現實盤自動交易,有好幾種設計方案。

前一篇方案:https://www.fmz.com/digest-topic/5533。

之前的方案,走的是發明者量化交易平臺擴展API接口,給機器人發送指令。今天我們一起來看另一種方案,讓TradingView的報警WebHook請求直接發送給FMZ量化交易平臺機器人,實現直接發送指令,命令機器人交易。

機器人策略源碼

策略使用Python編寫,在使用該策略創建機器人並啓動後,機器人會創建一個線程,該線程會啓動一個服務監聽設置的端口。等待外部請求並處理。我測試的時候是用在服務器上的託管者測試的,託管者所在設備必須能被外部訪問。機器人執行交易時,使用的是市價單接口,當然也可以改造這個策略,實現限價單下單邏輯。爲了簡單易懂、程序精簡,這裏使用了市價單,所以必須交易所支持市價單纔行。

'''
請求格式:http://x.x.x.x:xxxx/data?access_key=xxx&secret_key=yyy&type=buy&amount=0.001
策略機器人蔘數:
- 類型:加密字符串,AccessKey , SecretKey ,可以用FMZ平臺的低權限的API KEY,或者自己生成KEY也可以。
- 類型:字符串,合約ID,ContractType
- 類型:數值,端口號,Port
'''

import _thread
import json
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qs, urlparse

def url2Dict(url):
    query = urlparse(url).query  
    params = parse_qs(query)  
    result = {key: params[key][0] for key in params}  
    return result

class Executor(BaseHTTPRequestHandler):
    def do_GET(self):
        try:
            dictParam = url2Dict(self.path)
            Log("測試", dictParam)
        except Exception as e:
            Log("Provider do_GET error, e:", e)
    def do_POST(self):
        try:
            self.send_response(200)
            self.send_header("Content-type", "application/json")
            self.end_headers()
            dictParam = url2Dict(self.path)
            
            # 校驗
            if len(dictParam) == 4 and dictParam["access_key"] == AccessKey and dictParam["secret_key"] == SecretKey:
                del dictParam["access_key"]
                del dictParam["secret_key"]
                Log("接收到請求", "參數:", dictParam, "#FF0000")
                '''
                map[access_key:xxx amount:0.001 secret_key:yyy type:buy]
                '''
                isSpot = True
                if exchange.GetName().find("Futures") != -1:
                    if ContractType != "":
                        exchange.SetContractType(ContractType)
                        isSpot = False 
                    else :
                        raise "未設置期貨合約"
                
                if isSpot and dictParam["type"] == "buy":
                    exchange.Buy(-1, float(dictParam["amount"]))
                    Log(exchange.GetAccount())
                elif isSpot and dictParam["type"] == "sell":
                    exchange.Sell(-1, float(dictParam["amount"]))
                    Log(exchange.GetAccount())
                elif not isSpot and dictParam["type"] == "long":
                    exchange.SetDirection("buy")
                    exchange.Buy(-1, float(dictParam["amount"]))
                    Log("持倉:", exchange.GetPosition())
                elif not isSpot and dictParam["type"] == "short":
                    exchange.SetDirection("sell")
                    exchange.Sell(-1, float(dictParam["amount"]))
                    Log("持倉:", exchange.GetPosition())
                elif not isSpot and dictParam["type"] == "cover_long":
                    exchange.SetDirection("closebuy")
                    exchange.Sell(-1, float(dictParam["amount"]))
                    Log("持倉:", exchange.GetPosition())
                elif not isSpot and dictParam["type"] == "cover_short":
                    exchange.SetDirection("closesell")
                    exchange.Buy(-1, float(dictParam["amount"]))
                    Log("持倉:", exchange.GetPosition())
            
            # 寫入數據應答
            self.wfile.write(json.dumps({"state": "ok"}).encode())
        except Exception as e:
            Log("Provider do_POST error, e:", e)


def createServer(host):
    try:
        server = ThreadingHTTPServer(host, Executor)
        Log("Starting server, listen at: %s:%s" % host)
        server.serve_forever()
    except Exception as e:
        Log("createServer error, e:", e)
        raise Exception("stop")

def main():
    # 開啓一個線程
    try:
        _thread.start_new_thread(createServer, (("0.0.0.0", Port), ))         # VPS服務器上測試        
    except Exception as e:        
        Log("錯誤信息:", e)
        raise Exception("stop")    
    Log("賬戶資產信息:", _C(exchange.GetAccount))
    while True:
        if exchange.GetName() == "Futures_CTP":
            if exchange.IO("status"):
                LogStatus(_D(), "CTP連接")
            else:
                LogStatus(_D(), "CTP未連接")
        else:
            LogStatus(_D())
        Sleep(2000)

策略參數:

TradingView的WebHook報警請求

報警請求設置爲:

http://xxx.xxx.xxx.xxx:80/data?access_key=e3809e173e23004821a9bfb6a468e308&secret_key=45a811e0009d91ad21154e79d4074bc6&type=sell&amount=0.1

由於Trading View發送的是POST請求,所以監聽服務中要監聽POST請求,並且Trading View對於http協議只允許用80端口。

  • xxx.xxx.xxx.xxx,爲機器人所在託管者的設備IP地址。填寫自己的設備具體IP地址,需要注意必須能被外網訪問纔行。
  • access_keysecret_key可以自己生成,只要WebHook報警請求中的access_keysecret_key填寫與機器人蔘數上配置的一致即可。
  • type,交易方向,買入或者賣出、開倉或者平倉,注意現貨期貨是區分的。如果是期貨,注意機器人蔘數上要設置期貨合約代碼,並且配置的交易所對象需要是期貨交易所。
  • amount,交易數量。

運行測試

使用wexApp模擬盤測試。

END

完整策略地址:https://www.fmz.com/strategy/221850

方案中的access_keysecret_key僅僅爲識別,對於使用http並無安全性。該方案僅僅作爲思路、拋磚引玉,實際應用應當增加安全方面的考慮,使用https通信。

更新

  • 由於HTTPServer本身有些坑,考慮使用ThreadingHTTPServer代替。
    參考:https://docs.python.org/3.7/library/http.server.html
    需要Python3.7版本。

  • 增加了GET請求測試
    可以使用瀏覽器,地址欄輸入請求鏈接。

    http://xxx.xx.xx.xx/data?access_key=123&secret_key=123&type=buy&amount=1
    

    測試:

  • 增加了商品期貨連接狀態的判斷

HTTPServer問題的資料:
https://www.zybuluo.com/JunQiu/note/1350528

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章