Dokumentation der GPL
Mit diesem Protokoll kann auf jede der
APIHandel mit den Exchanges, die spezifische API-Vereinbarungen unbeschränkt, sei esrest、websocket、fix... können aufgerufen werden.
Beispiel für eine Python-General-Protocol:
-
1. Funktionieren von GPRS-Plugins und Port-Einstellungen
Schreiben Sie die Überwachungsadressen und Port-Einstellungen für die GNU/GPL-Plug-in.
Beispiel:http://127.0.0.1:6666/DigitalAssetOder:http://127.0.0.1:6666/exchange。Warum ist das so?IP、Der WegWolltuch?
Denn inQuantifizierung der ErfinderSeitenKontrollzentrumUmdrehenHinzufügen von BörsenSeite, wählen Sie das Feld GPRS und zeigen Sie außerAPI-KEYEs gibt auch einen Service Address-Feld, der dem Host sagt, welche IP und welchen Port es zu besuchen gilt (Hoster und GPO-Plugins können nicht auf demselben Gerät laufen).http://127.0.0.1:6666/DigitalAsset。DigitalAssetDas ist eine eigene Bezeichnung, hier nur ein Beispiel.Auf der Seite des Inventor Quantify Add Exchange sieht es so aus:
In der Regel benötigt eine Börse Kontoinformationen nur für die Konfigurationaccess keyUndsecret keyWenn wir auf eine API dieser Art von Börsen stoßen, können wir die konfigurierten Informationen, die überflüssig sind und die übertragen werden müssen, in die API schreiben, da auf der GPRS-Seite keine überflüssigen Steuerelemente sind, um diese Informationen zu schreiben.secret keyWenn die Informationen nicht sensibel sind, können sie inaccess keyDas ist die Art, wie wir das in der GPL-Plug-in-String machen.splitDiese Daten werden getrennt, wie in der Abbildung gezeigt.Das ist eine sehr gute Idee, aber es ist nicht einfach.
XXX_PassWord。
Zum Beispiel, in dem vollständigen Beispiel am Ende dieses Beitrags, die newBitgo Funktion:mylangfunc newBitgo(accessKey, secretKey string) *iBitgo { s := new(iBitgo) s.accessKey = accessKey s.secretKey = secretKey // 在此可以分离secretKey中的额外配置信息,可以写成如下注释中的内容 /* arr := strings.SplitN(secretKey, ",", 2) if len(arr) != 2 { panic("配置错误!") } s.secretKey = arr[0] // secret key s.passWord = arr[1] // XXX_PassWord */ s.apiBase = "https://www.bitgo.cn" s.timeout = 20 * time.Second s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60) return s }Allgemeine GNU-Plugins
mainFunktionsbeispiel:
GoDas ist eine sehr schwierige Situation.mylangfunc main(){ var addr = flag.String("b", "127.0.0.1:6666", "bing addr") // 设置命令行参数,默认值描述,端口设置6666 flag.Parse() // 解析命令行 if *addr == "" { flag.Usage() // 显示命令行描述 return } basePath := "/DigitalAsset" log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...") // 打印监听端口信息 http.HandleFunc(basePath, OnPost) http.ListenAndServe(*addr, nil) } -
2. Reaktionsfunktion
GNU-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-in-Plug-Plug-in-Plug-in-Plug-Plug-in-Plug-Plug-in-Plug-in-Plug-Plug-in-Plug-Plug-in-Plug-Plug-in-Plug-Plug-in-Plug-Plug-in-Plug-in-Plug-Plug-in-Plug-in-Plug-in-Plug-Plug-in-Plug-Plug-in-Plug-Plug-in-Plug-Plug-in
requestWenn eine Anfrage gestellt wird, wird die Antwortfunktion aufgerufen, um die Parameter in den Anforderungsdaten zu analysieren. Die Anforderungsdaten, die der Host sendet, sind:/* request的JSON结构,发明者量化调用GetTicker,托管者发送给通用协议插件情况举例(各个API调用时,params的值可能不一样,此处method为ticker): { "access_key" : "XXX", // `json:"access_key"` "secret_key" : "XXX", // `json:"secret_key"` "nonce" : "1502345965295519602", // `json:"nonce"` "method" : "ticker", // `json:"method"` "params" : { // `json:"params"` "symbol" : "btc", ... }, // 各个请求参数略有区别。即在策略中调用不同的 发明者量化 API会有不同的参数, 在下文各个API 有介绍说明。 } */So erhält man die Struktur nach der Rücksequenzierung der RequestBody-Daten nach der JSON-Sequenzierung, die in einem GPO-Plugin empfangen wurde.
requestInMethodWir können sie benutzen.switchKlassifizieren Sie die Quantifizierungs-API, die die Quantifizierung von verschiedenen Erfindern behandelt, um zu identifizieren, welche der Inventoren von der auf dem Host ausgeführten Strategie aufgerufen wirdAPI(Aufruf:GoSprachbeispiele:mylangswitch request.Method { // 此处request.Method的M为大写,通用协议程序接收到的请求主体中为JSON数据,在Go语言中反JSON序列化(Unmarshal)为结构体,字段首字母必须大写 case "accounts" : // 当托管者上的机器人策略中调用了exchange.GetAccount()函数,托管者会发送来请求,其中Body携带的数据中method属性值为accounts data, err = e.GetAccount(symbol) case "ticker" : data, err = e.GetTicker(symbol) case "depth" : data, err = e.GetDepth(symbol) case "trades" : data, err = e.GetTrades(symbol) case "trade" : ... default: ...Diese Abteilungen schreiben die zurückgegebenen Daten in eine Struktur, auf die die GMV-Plug-in-Programme antworten sollen, und antworten auf die Anfragen der Trustees.
Ein Beispiel aus der Sprache Go:
mylangdefer func(){ // 处理收尾工作 if e := recover(); e != nil { // recover()函数用于捕获panic,e != nil即有错误发生 if ee, ok := e.(error); ok { // 类型推导,推导成功把ee.Error()赋值给e e = ee.Error() // 调用Error方法获取返回的错误字符串 } ret = map[string]string{"error": fmt.Sprintf("%v", e)} } b, _ := json.Marshal(ret) // 把本次调用获取的结果ret编码,赋值给b,写入响应指针 w.Write(b) //fmt.Println("after w.Write the b is", string(b)) // 测试 }() -
3. Arten von API-Aufrufen
Es gibt zwei Kategorien:
- Öffentliche Schnittstellen, die keine Unterschrift benötigen, wie:
GetTicker()GetDepth()GetTrades()GetRecords(period)
...- Benutzeroberflächen, die eine Unterschrift erfordern, wie z. B.:
Buy、SellGetOrder(id)GetOrders()GetAccount()CancelOrder(id)
...
Die Unterschrift kann von Börse zu Börse variieren und muss je nach Bedarf spezifisch erstellt werden. -
4. Der Erfinder quantifiziert die Aufrufe der einzelnen API-SchnittstellenGPL-Plug-insUndGastgeberInteraktive Datenformate:
Einige API-Schnittstellen, die von den Erfindern quantifiziert wurden, sind:
GetName(),GetLabel()Funktionen, die nicht anGPL-Plug-insAnfrage eingereicht.
exchange.GetName()Der Name "Exchange" wird bei Anrufen von Börsen, die mit einem allgemeinen Plugin konfiguriert sind, zurückgegeben.-
1、GetTicker: Die Daten werden für die aktuellen Entwicklungen verwendet.
GastgeberDie Funktion, die die Überwachungsantwort sendet, ist
requestInmethodFür:ticker。Der Administrator hat folgende Parameter gesendet:
request.Params.symbol: Die Währung, die vom Administrator nach den Einstellungen auf der Bot-Seite gesendet wird.Das Datenformat, das der Host bei der Anforderung eines GM-Plugins vom Host benötigt (JSON)
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "ticker", "params" : {"symbol" : "ETH_BTC"}, // 以ETH_BTC交易对举例 }Die zurückgegebene Wertstruktur, die schließlich an den Host gesendet wird, ist: ((d.h. das Format, in dem die Daten an den Host zurückgegeben werden, nachdem das GMV-Plug-in die Exchange-Schnittstelle angefordert hat)
JSON-Struktur
{ "data": { "time": 1500793319499, // 毫秒时间戳,整型 "buy": 1000, // 以下浮点型 "sell": 1001, "last": 1005, "high": 1100, "low": 980, "vol": 523, } } -
2、GetRecords:K-Linien-Daten, die von der Börse bereitgestellt werden.
Der Administrator sendet die Überwachungs-
requestInmethodFür:recordsDer Administrator hat folgende Parameter gesendet:
request.Params.periodWertverknüpfungexchange.GetRecordsDer erste Parameter der Funktionrequest.Params.periodDie Periode wird in Minuten angegeben, z. B. die Tagesperiode als60*24Im Augenblick1440、request.Params.symbol: Verwalter senden die Währung, die sie eingestellt haben.Datenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "records", "params" : {"symbol" : "ETH_BTC", "period" : "1440"}, // 以ETH_BTC交易对,K线周期为日线举例 }Die zurückgegebene Werte, die schließlich an den Administrator gesendet werden:
JSON-Struktur
{ "data": [ [1500793319, 1.1, 2.2, 3.3, 4.4, 5.5], // "Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5 [1500793259, 1.01, 2.02, 3.03, 4.04, 5.05], ... ] }Go-Testdaten:
ret_records = []interface{}{ [6]interface{}{1500793319, 1.1, 2.2, 3.3, 4.4, 5.5}, [6]interface{}{1500793259, 1.01, 2.02, 3.03, 4.04, 5.05} }Erfinder-Quantifizierungsplattform
LogzeigenrecordsDaten:[ {"Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5}, {"Time":1500793259000,"Open":1.01,"High":2.02,"Low":3.03,"Close":4.04,"Volume":5.05} ]Anmerkung 1: Das erste Element in der zweidimensionalen Array ist
intDer Typ, der die Zeitspanne angibt, ist <unk>。 2. Der Administrator gibt automatisch die Zeitspanne mit 1000 zu multiplizieren. Siehe oben. -
3、GetDepth:In-depth-Informationen über die Börsen: "Order sind dünn, Verkauf eins, Verkauf zwei, Kauf eins, Kauf zwei"...
Der Administrator sendet die Überwachungs-
requestInmethodFür:depthDer Administrator hat folgende Parameter gesendet:
request.Params.symbol: Senden von Währungen, die von Administratoren gemäß der gesetzten Strategie eingestellt wurdenDatenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "depth", "params" : {"symbol" : "ETH_BTC"}, // 以ETH_BTC交易对举例 }Die zurückgegebene Wertestruktur, die schließlich an den Host gesendet wird:
JSON-Struktur
{ "data" : { "time" : 1500793319499, "asks" : [ [1000, 0.5], [1001, 0.23], [1004, 2.1], ... ], "bids" : [ [999, 0.25], [998, 0.8], [995, 1.4], ... ], } } -
4、GetTrades:Zugriff auf die von der Börse bereitgestellten Transaktionsdaten der gesamten Börse über einen bestimmten Zeitraum (nicht eigene Transaktionsdaten)
Der Administrator sendet die Überwachungs-
requestInmethodFür:tradesDer Administrator hat folgende Parameter gesendet:
request.Params.symbolZum Beispiel:btc, die vom Trustee gemäß der von ihm festgelegten Strategie gesendet werden.Datenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "trades", "params" : {"symbol" : "ETH_BTC"}, // 以ETH_BTC交易对举例 }Die zurückgegebene Wertestruktur, die schließlich an den Host gesendet wird:
JSON-Struktur
{ "data": [ { "id": 12232153, "time" : 1529919412968, "price": 1000, "amount": 0.5, "type": "buy", // "buy"、"sell" },{ "id": 12545664, "time" : 1529919412900, "price": 1001, "amount": 1, "type": "sell", },{ ... } ] } -
5、GetAccount:Zugriff auf Informationen über Konto-Assets
Der Administrator sendet die Überwachungs-
requestInmethodFür:accountsAdministrator sendet Parameter: ((Achtung! Im Allgemeinen werden alle Vermögenswerte des Kontos erfasst!, je nach Interface der Börse, entweder einzeln oder insgesamt)
Datenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "accounts", "params" : {}, }Die zurückgegebene Werte, die schließlich an den Administrator gesendet werden:
JSON-Struktur
{ "data": [ {"currency": "btc", "free": 1.2, "frozen": 0.1}, {"currency": "ltc", "free": 25, "frozen": 2.1}, {"currency": "ltc", "free": 25, "frozen": 2.1}, ... ], "raw" : {...} // 可以写入插件访问交易所时,交易所返回的原始信息(response) } -
6、Buy、Sell:Versenden von Auftrags- und Auftragsgeschäften.
Der Administrator sendet die Überwachungs-
requestInmethodFür:tradeDer Administrator hat folgende Parameter gesendet:
request.Params.typeDer Verwalter ist auf Abruf:exchange.BuyOder auchexchange.SellDas ist eine schlechte Idee.request.Params.priceIn der Strategie:APIDas erste Parameter der Funktionrequest.Params.amountIn der Strategie:APIDas zweite Parameter der Funktionrequest.Params.symbol: Verwalter senden die Währung, die sie eingestellt haben.Datenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "trade", "params" : { "symbol" : "ETH_BTC", "type" : "buy", "price" : "1000", "amount" : "1" }, // 以ETH_BTC交易对,"type":"buy"买请求,价格1000,数量1举例 }Die zurückgegebene Wertestruktur, die schließlich an den Host gesendet wird:
JSON-Struktur
{ "data": { "id": 125456, // 下单后返回的订单id // 如果订单id是"asdf346sfasf532"这样的字符串形式 // 此处id也可以是字符串类型 } } -
7、GetOrder:Erhalten Sie Informationen über die angegebene Bestellnummer
Der Administrator sendet die Überwachungs-
requestInmethodFür:orderDer Administrator hat folgende Parameter gesendet:
request.Params.id、request.Params.symbolDatenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "order", "params" : {"symbol" : "ETH_BTC", "id" : "XXX"}, // 以ETH_BTC交易对,订单id为XXX举例,注意有些交易所的订单号是数字形式的订单ID,如123456,有些交易所的订单号是字符串形式的ID,如poimd55sdfheqxv,具体看交易所的订单ID格式 }Die zurückgegebene Wertestruktur, die schließlich an den Host gesendet wird:
JSON-Struktur
{ "data": { "id": 2565244, "amount": 0.15, "price": 1002, "status": "open", // "open":挂起状态、"closed":完成关闭状态、"cancelled":已取消 "deal_amount": 0, "type": "buy", // "buy"、"sell" "avg_price": 0, // 如果交易所没有提供,在处理时可以赋值为0 } } -
8、GetOrders: Erhalten Sie Informationen zu allen noch nicht abgeschlossenen Bestellungen
Der Administrator sendet die Überwachungs-
requestDie Methode lautet:ordersDer Administrator hat folgende Parameter gesendet:
request.Params.symbolDatenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "orders", "params" : {"symbol" : "ETH_BTC"}, // 以ETH_BTC交易对举例 }Die zurückgegebene Wertestruktur, die schließlich an den Host gesendet wird:
JSON-Struktur
{ "data": [{ "id": 542156, "amount": 0.25, "price": 1005, "deal_amount": 0, "type": "buy", // "buy"、"sell" "status": "open", // "open" },{ ... }] } -
9、CancelOrder: Stornierung eines Auftrags mit einer angegebenen Auftragsnummer
Der Administrator sendet die Überwachungs-
requestInmethodFür:cancelDer Administrator hat folgende Parameter gesendet:
request.Params.idDer String-Typ, der erste Parameter der API-Funktion, die von der Strategie aufgerufen wird.request.Params.symbol:btc (Beispiel) wird von einem Administrator in einer Währung gesendet, die von der Strategie abhängtDatenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "cancel", "params" : {"symbol" : "ETH_BTC", "id" : "XXX"}, // 以ETH_BTC交易对,id为"XXX"(同GetOrder函数的参数id一样),举例 }Die zurückgegebene Wertestruktur, die schließlich an den Host gesendet wird:
JSON-Struktur
{ "data": true, // true or false } -
10、IO: Die Exchange.IO-Funktion, die die Quantifizierungsplattform des Erfinders aufruft
Der Administrator sendet die Überwachungs-
requestInmethodFür__api_Der Name des Methods, mit dem die Methode beginnt.Datenformat, das der Host beim Anfordern eines GMV-Plugins vom Host verlangt
{ "access_key" : "access_key", "secret_key" : "secret_key", "nonce" : "1500793319499", // 毫秒时间戳 "method" : "__api_XXX", // XXX为具体交易所的API接口(不包含基地址) "params" : {"borrow_id" : "123", "symbol" : "cny"}, // 具体是传入IO函数的参数 }Die zurückgegebene Wertestruktur, die schließlich an den Host gesendet wird:
{ "data": {...} // 具体的接口调用的返回值 }Die Strategie-Aufrufe sind beispielsweise:
var io_str = exchange.IO("api", "POST", "cancel_borrow", "symbol=cny&borrow_id=123")Der Testcode im Plugin (in der Sprache go):
mylangfmt.Println("request.Method:", request.Method, "request.Params:", request.Params)Befehlszeile des Plugins:
2017/08/31 10:19:59 Running http://127.0.0.1:6666/DigitalAsset ...Das Plug-in wird in der Kommandozeile request.Method,request.Params gedruckt
Nach der Analyse der Daten im Request-Body, das der Host sendet:request.MethodFür:__api_cancel_borrow
Nach der Analyse der Daten im Request-Body, das der Host sendet:request.ParamsFür:{"borrow_id" : "123", "symbol" : "cny"}Anpassbar für den direkten Zugriff auf diese Börsen
APIvonexchange.IOAufruf# 注意: # 在调用exchange.IO("api", "POST", "/api/v1/getAccount", "symbol=BTC_USDT")时, # 如果第二个参数不是POST,而是:exchange.IO("api", "GET", "/api/v1/getAccount", "symbol=BTC_USDT") # 是GET方法,这时在通用协议插件接受到的http请求中头部Http-Method中储存的才是GET, # 所以在通用协议处理IO函数实现时,需要参考以下范例代码: // tapiCall函数定义 func (p *iStocksExchange) tapiCall(method string, params map[string]string, httpType string) (js *Json, err error) { ... } // 在OnPost函数中 if strings.HasPrefix(request.Method, "__api_") { var js *Json js, err = e.tapiCall(request.Method[6:], request.Params, r.Header.Get("Http-Method")) ... } -
Unterstützung für exchange.GetRawJSON
Automatisierung der Unterlagen
exchange.GetRawJSONDie Anrufung von "Plugin" muss nicht in Plugins implementiert werden. -
Unterstützung für Exchange.Go
Automatisierung der Unterlagen
exchange.GoDie Anrufung von "PDF" und "PDF" muss nicht in einem Plugin behandelt werden.var beginTime = new Date().getTime() var ret = exchange.Go("GetDepth") var endTime = new Date().getTime() Log(endTime - beginTime, "#FF0000") // Sleep(2000) beginTime = new Date().getTime() Log(exchange.GetTicker()) endTime = new Date().getTime() Log(endTime - beginTime, "#FF0000") var depth = ret.wait() Log("depth:", depth)# 注意:使用exchange.Go在wait的时候如果指定了超时时间, # 一定要确保获取到最终的数据,这样申请的并发线程才能回收。 -
Unterstützung für die Futures-Funktion
Spezifische Abläufe, wie zum Beispiel das Setzen von Leverage, Kontraktcode, Auftragseinstellungen, können in der Plugin-Prozedur vorgenommen werden. Ein lokaler Variablen-Record kann eingerichtet werden. Der Zugriff auf die Haltestelle erfordert den Zugriff auf die Börsen-Schnittstelle, um die Rohdaten zu erhalten und die als auf der FMZ-Plattform definierte Haltestelle zu verarbeiten und zurückzugeben.
Wenn die folgende Funktion in der Strategie aufgerufen wird, erhält das Plug-inRpcDas Format der Anfrage unterscheidet sich geringfügig von den anderen Schnittstellen.RpcRequestDas Format von params unterscheidet sich vor allem dadurch, dass es sich um eine komplexe Struktur handelt.-
SetContractType
Einstellung des Vertragscodes{"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"} -
SetDirection
Setzen Sie die Futures in die folgende Richtung.{"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"} -
SetMarginLevel
Setzen Sie Futures-Leverage ein.{"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"} -
GetPosition
Erhalt von Futures-Positionen.
Wennexchange.GetPosition()Anrufzeit:{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":[],"code":3},"secret_key":"123"}Wenn
exchange.GetPosition("swap")Anrufzeit:{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":["swap"],"code":3},"secret_key":"123"}
-
-
-
Ein Beispiel für eine vollständige Go-Sprache mit General-Protocol-Plugins (Beispiel für den Zugriff auf die BIT-Börse)
mylang
/*
GOOS=linux GOARCH=amd64 go build -ldflags '-s -w -extldflags -static' rest_bitgo.go
*/
package main
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
func toFloat(s interface{}) float64 {
var ret float64
switch v := s.(type) {
case float64:
ret = v
case float32:
ret = float64(v)
case int64:
ret = float64(v)
case int:
ret = float64(v)
case int32:
ret = float64(v)
case string:
ret, _ = strconv.ParseFloat(strings.TrimSpace(v), 64)
}
return ret
}
func float2str(i float64) string {
return strconv.FormatFloat(i, 'f', -1, 64)
}
func toInt64(s interface{}) int64 {
var ret int64
switch v := s.(type) {
case int:
ret = int64(v)
case float64:
ret = int64(v)
case bool:
if v {
ret = 1
} else {
ret = 0
}
case int64:
ret = v
case string:
ret, _ = strconv.ParseInt(strings.TrimSpace(v), 10, 64)
}
return ret
}
func toString(s interface{}) string {
var ret string
switch v := s.(type) {
case string:
ret = v
case int64:
ret = strconv.FormatInt(v, 10)
case float64:
ret = strconv.FormatFloat(v, 'f', -1, 64)
case bool:
ret = strconv.FormatBool(v)
default:
ret = fmt.Sprintf("%v", s)
}
return ret
}
type Json struct {
data interface{}
}
func NewJson(body []byte) (*Json, error) {
j := new(Json)
err := j.UnmarshalJSON(body)
if err != nil {
return nil, err
}
return j, nil
}
func (j *Json) UnmarshalJSON(p []byte) error {
return json.Unmarshal(p, &j.data)
}
func (j *Json) Get(key string) *Json {
m, err := j.Map()
if err == nil {
if val, ok := m[key]; ok {
return &Json{val}
}
}
return &Json{nil}
}
func (j *Json) CheckGet(key string) (*Json, bool) {
m, err := j.Map()
if err == nil {
if val, ok := m[key]; ok {
return &Json{val}, true
}
}
return nil, false
}
func (j *Json) Map() (map[string]interface{}, error) {
if m, ok := (j.data).(map[string]interface{}); ok {
return m, nil
}
return nil, errors.New("type assertion to map[string]interface{} failed")
}
func (j *Json) Array() ([]interface{}, error) {
if a, ok := (j.data).([]interface{}); ok {
return a, nil
}
return nil, errors.New("type assertion to []interface{} failed")
}
func (j *Json) Bool() (bool, error) {
if s, ok := (j.data).(bool); ok {
return s, nil
}
return false, errors.New("type assertion to bool failed")
}
func (j *Json) String() (string, error) {
if s, ok := (j.data).(string); ok {
return s, nil
}
return "", errors.New("type assertion to string failed")
}
func (j *Json) Bytes() ([]byte, error) {
if s, ok := (j.data).(string); ok {
return []byte(s), nil
}
return nil, errors.New("type assertion to []byte failed")
}
func (j *Json) Int() (int, error) {
if f, ok := (j.data).(float64); ok {
return int(f), nil
}
return -1, errors.New("type assertion to float64 failed")
}
func (j *Json) MustArray(args ...[]interface{}) []interface{} {
var def []interface{}
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustArray() received too many arguments %d", len(args))
}
a, err := j.Array()
if err == nil {
return a
}
return def
}
func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} {
var def map[string]interface{}
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustMap() received too many arguments %d", len(args))
}
a, err := j.Map()
if err == nil {
return a
}
return def
}
func (j *Json) MustString(args ...string) string {
var def string
switch len(args) {
case 0:
case 1:
def = args[0]
default:
log.Panicf("MustString() received too many arguments %d", len(args))
}
s, err := j.String()
if err == nil {
return s
}
return def
}
func (j *Json) MustInt64() int64 {
var ret int64
var err error
switch v := j.data.(type) {
case int:
ret = int64(v)
case int64:
ret = v
case float64:
ret = int64(v)
case string:
if ret, err = strconv.ParseInt(v, 10, 64); err != nil {
panic(err)
}
default:
ret = 0
//panic("type assertion to int64 failed")
}
return ret
}
func (j *Json) MustFloat64() float64 {
var ret float64
var err error
switch v := j.data.(type) {
case int:
ret = float64(v)
case int64:
ret = float64(v)
case float64:
ret = v
case string:
v = strings.Replace(v, ",", "", -1)
if ret, err = strconv.ParseFloat(v, 64); err != nil {
panic(err)
}
default:
ret = 0
//panic("type assertion to float64 failed")
}
return ret
}
type iBitgo struct {
accessKey string
secretKey string
currency string
opCurrency string
baseCurrency string
secret string
secretExpires int64
apiBase string
step int64
newRate float64
timeout time.Duration
timeLocation *time.Location
}
type MapSorter []Item
type Item struct {
Key string
Val string
}
func NewMapSorter(m map[string]string) MapSorter {
ms := make(MapSorter, 0, len(m))
for k, v := range m {
if strings.HasPrefix(k, "!") {
k = strings.Replace(k, "!", "", -1)
}
ms = append(ms, Item{k, v})
}
return ms
}
func (ms MapSorter) Len() int {
return len(ms)
}
func (ms MapSorter) Less(i, j int) bool {
//return ms[i].Val < ms[j].Val // 按值排序
return ms[i].Key < ms[j].Key // 按键排序
}
func (ms MapSorter) Swap(i, j int) {
ms[i], ms[j] = ms[j], ms[i]
}
func encodeParams(params map[string]string, escape bool) string {
ms := NewMapSorter(params)
sort.Sort(ms)
v := url.Values{}
for _, item := range ms {
v.Add(item.Key, item.Val)
}
if escape {
return v.Encode()
}
var buf bytes.Buffer
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
vs := v[k]
prefix := k + "="
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(prefix)
buf.WriteString(v)
}
}
return buf.String()
}
func newBitgo(accessKey, secretKey string) *iBitgo {
s := new(iBitgo)
s.accessKey = accessKey
s.secretKey = secretKey
s.apiBase = "https://www.bitgo.cn"
s.timeout = 20 * time.Second
s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
return s
}
func (p *iBitgo) apiCall(method string) (*Json, error) {
req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, method), nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return NewJson(b)
}
func (p *iBitgo) GetTicker(symbol string) (ticker interface{}, err error) {
var js *Json
js, err = p.apiCall("action=market&symbol=" + symbol)
if err != nil {
return
}
dic := js.Get("data")
ticker = map[string]interface{}{
"time": js.Get("time").MustInt64(),
"buy": dic.Get("buy").MustFloat64(),
"sell": dic.Get("sell").MustFloat64(),
"last": dic.Get("last").MustFloat64(),
"high": dic.Get("high").MustFloat64(),
"low": dic.Get("low").MustFloat64(),
"vol": dic.Get("vol").MustFloat64(),
}
return
}
func (p *iBitgo) GetDepth(symbol string) (depth interface{}, err error) {
var js *Json
js, err = p.apiCall("action=depth&symbol=" + symbol)
if err != nil {
return
}
dic := js.Get("data")
asks := [][2]float64{}
bids := [][2]float64{}
for _, pair := range dic.Get("asks").MustArray() {
arr := pair.([]interface{})
asks = append(asks, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
}
for _, pair := range dic.Get("bids").MustArray() {
arr := pair.([]interface{})
bids = append(bids, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
}
depth = map[string]interface{}{
"time": js.Get("time").MustInt64(),
"asks": asks,
"bids": bids,
}
return
}
func (p *iBitgo) GetTrades(symbol string) (trades interface{}, err error) {
var js *Json
js, err = p.apiCall("action=trades&symbol=" + symbol)
if err != nil {
return
}
dic := js.Get("data")
items := []map[string]interface{}{}
for _, pair := range dic.MustArray() {
item := map[string]interface{}{}
arr := pair.(map[string]interface{})
item["id"] = toInt64(arr["id"])
item["price"] = toFloat(arr["price"])
item["amount"] = toFloat(arr["amount"])
// trade.Time = toInt64(arr["time"]) * 1000
if toString(arr["en_type"]) == "bid" {
item["type"] = "buy"
} else {
item["type"] = "sell"
}
items = append(items, item)
}
trades = items
return
}
func (p *iBitgo) GetRecords(step int64, symbol string) (records interface{}, err error) {
var js *Json
js, err = p.apiCall(fmt.Sprintf("action=kline&symbol=%s&step=%d", symbol, step*60))
if err != nil {
return
}
items := []interface{}{}
for _, pair := range js.Get("data").MustArray() {
arr := pair.([]interface{})
if len(arr) < 6 {
err = errors.New("response format error")
return
}
item := [6]interface{}{}
item[0] = toInt64(arr[0])
item[1] = toFloat(arr[1])
item[2] = toFloat(arr[2])
item[3] = toFloat(arr[3])
item[4] = toFloat(arr[4])
item[5] = toFloat(arr[5])
items = append(items, item)
}
records = items
return
}
func (p *iBitgo) tapiCall(method string, params map[string]string) (js *Json, err error) {
if params == nil {
params = map[string]string{}
}
params["api_key"] = p.accessKey
h := md5.New()
h.Write([]byte(encodeParams(params, false) + "&secret_key=" + p.secretKey))
params["sign"] = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
params["action"] = method
qs := encodeParams(params, false)
req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, qs), nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
js, err = NewJson(b)
if js != nil {
if code := js.Get("code").MustInt64(); code != 200 {
s := js.Get("msg").MustString()
if s == "" {
s = fmt.Sprintf("%v", toString(js.data))
}
return nil, errors.New(s)
}
}
return js, err
}
func (p *iBitgo) GetAccount(symbol string) (account interface{}, err error) {
var js *Json
js, err = p.tapiCall("userinfo", nil)
if err != nil {
return
}
mp := js.Get("data")
assets := map[string]map[string]interface{}{}
for k := range mp.MustMap() {
dic := mp.Get(k)
if k == "free" {
for c := range dic.MustMap() {
if _, ok := assets[c]; !ok {
assets[c] = map[string]interface{}{}
}
assets[c]["currency"] = c
assets[c]["free"] = dic.Get(c).MustFloat64()
}
} else if k == "frozen" {
for c := range dic.MustMap() {
if _, ok := assets[c]; !ok {
assets[c] = map[string]interface{}{}
}
assets[c]["currency"] = c
assets[c]["frozen"] = dic.Get(c).MustFloat64()
}
}
}
accounts := []map[string]interface{}{}
for _, pair := range assets {
accounts = append(accounts, pair)
}
account = accounts
return
}
func (p *iBitgo) Trade(side string, price, amount float64, symbol string) (orderId interface{}, err error) {
var js *Json
js, err = p.tapiCall("trade", map[string]string{
"symbol": symbol,
"type": side,
"price": float2str(price),
"amount": float2str(amount),
})
if err != nil {
return
}
orderId = map[string]int64{"id": js.Get("orderId").MustInt64()}
return
}
func (p *iBitgo) GetOrders(symbol string) (orders interface{}, err error) {
var js *Json
js, err = p.tapiCall("entrust", map[string]string{"symbol": symbol})
if err != nil {
return
}
items := []map[string]interface{}{}
for _, ele := range js.Get("data").MustArray() {
mp := ele.(map[string]interface{})
item := map[string]interface{}{}
item["id"] = toInt64(mp["id"])
item["amount"] = toFloat(mp["count"])
if _, ok := mp["prize"]; ok {
item["price"] = toFloat(mp["prize"])
} else {
item["price"] = toFloat(mp["price"])
}
item["deal_amount"] = toFloat(mp["success_count"])
if toInt64(mp["type"]) == 0 {
item["type"] = "buy"
} else {
item["type"] = "sell"
}
item["status"] = "open"
items = append(items, item)
}
return items, nil
}
func (p *iBitgo) GetOrder(orderId int64, symbol string) (order interface{}, err error) {
var js *Json
js, err = p.tapiCall("order", map[string]string{"id": toString(orderId)})
if err != nil {
return
}
found := false
item := map[string]interface{}{}
for _, ele := range js.Get("data").MustArray() {
mp := ele.(map[string]interface{})
if toInt64(mp["id"]) != orderId {
continue
}
item["id"] = toInt64(mp["id"])
item["amount"] = toFloat(mp["count"])
if _, ok := mp["prize"]; ok {
item["price"] = toFloat(mp["prize"])
} else {
item["price"] = toFloat(mp["price"])
}
item["deal_amount"] = toFloat(mp["success_count"])
if toInt64(mp["type"]) == 0 {
item["type"] = "buy"
} else {
item["type"] = "sell"
}
switch toInt64(mp["status"]) {
case 1, 2:
item["status"] = "open"
case 3:
item["status"] = "closed"
case 4:
item["status"] = "cancelled"
}
found = true
break
}
if !found {
return nil, errors.New("order not found")
}
return item, nil
}
func (p *iBitgo) CancelOrder(orderId int64, symbol string) (ret bool, err error) {
_, err = p.tapiCall("cancel_entrust", map[string]string{"id": strconv.FormatInt(orderId, 10)})
if err != nil {
return
}
ret = true
return
}
type RpcRequest struct { // 结构体里的字段首字母必须大写,否则无法正常解析,结构体有导出和未导出,大写字母开头为导出。
// 在Unmarshal的时候会 根据 json 匹配查找该结构体的tag, 所以此处需要修饰符
AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"`
Nonce int64 `json:"nonce"`
Method string `json:"method"`
Params map[string]string `json:"params"`
}
func OnPost(w http.ResponseWriter, r *http.Request) {
var ret interface{}
defer func() {
if e := recover(); e != nil {
if ee, ok := e.(error); ok {
e = ee.Error()
}
ret = map[string]string{"error": fmt.Sprintf("%v", e)}
}
b, _ := json.Marshal(ret)
w.Write(b)
}()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
var request RpcRequest
err = json.Unmarshal(b, &request)
if err != nil {
panic(err)
}
e := newBitgo(request.AccessKey, request.SecretKey)
symbol := request.Params["symbol"]
if s := request.Params["access_key"]; len(s) > 0 {
e.accessKey = s
}
if s := request.Params["secret_key"]; len(s) > 0 {
e.secretKey = s
}
if symbolIdx, ok := map[string]int{
"btc": 1,
"ltc": 2,
"etp": 3,
"eth": 4,
"etc": 5,
"doge": 6,
"bec": 7,
}[strings.Replace(strings.ToLower(symbol), "_cny", "", -1)]; ok {
symbol = toString(symbolIdx)
}
var data interface{}
switch request.Method {
case "ticker":
data, err = e.GetTicker(symbol)
case "depth":
data, err = e.GetDepth(symbol)
case "trades":
data, err = e.GetTrades(symbol)
case "records":
data, err = e.GetRecords(toInt64(request.Params["period"]), symbol)
case "accounts":
data, err = e.GetAccount(symbol)
case "trade":
side := request.Params["type"]
if side == "buy" {
side = "0"
} else {
side = "1"
}
price := toFloat(request.Params["price"])
amount := toFloat(request.Params["amount"])
data, err = e.Trade(side, price, amount, symbol)
case "orders":
data, err = e.GetOrders(symbol)
case "order":
data, err = e.GetOrder(toInt64(request.Params["id"]), symbol)
case "cancel":
data, err = e.CancelOrder(toInt64(request.Params["id"]), symbol)
default:
if strings.HasPrefix(request.Method, "__api_") {
data, err = e.tapiCall(request.Method[6:], request.Params)
} else {
panic(errors.New(request.Method + " not support"))
}
}
if err != nil {
panic(err)
}
ret = map[string]interface{}{
"data": data,
}
return
}
func main() {
var addr = flag.String("b", "127.0.0.1:6666", "bind addr")
flag.Parse()
if *addr == "" {
flag.Usage()
return
}
basePath := "/exchange"
log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")
http.HandleFunc(basePath, OnPost)
http.ListenAndServe(*addr, nil)
}
- 这段通用协议插件的代码,是不是就是实现的post、get和解析,同时对接到botvs的api?
是的。 - http://127.0.0.1:6666/DigitalAsset
这个地址是 代表 交易所对象, 托管者 的请求都会 发送到这个地址。 插件服务程序会 监听这个地址请求, 并作出响应,代替托管者去访问交易所,并返回 托管者 要求 的数据(格式),实现 ,策略机器人 兼容某个 BotVS 还没有接入的交易所。
bitmex交易所已经接入平台,但不管用 _C(exchange.GetTicker).Buy 还是 _C(exchange.GetDepth).Bids[0] 都无法取得买一价,而且还会报错。不知道是为什么
这上面的代码,就是通用协议插件是吧?这个插件不是类似于策略模版那种我直接可以调用?我会用py post或者get 发送和获得数据,并解析。这段通用协议插件的代码,是不是就是实现的post、get和解析,同时对接到botvs的api?编写好的 “通用协议插件” 的监听地址和端口设置这个 http://127.0.0.1:6666/DigitalAsset,实际上是不是类似于 https://www.bitmex.com/ 这种地址,机器人像这个地址发信息?
- 1






