FMZ Quant Trading Platform benutzerdefiniertes Protokoll Zugriff auf benutzerdefinierte Börsen

Schriftsteller:Lydia., Erstellt: 2023-07-12 15:29:54, Aktualisiert: 2024-01-03 21:01:07

img

FMZ Quant Trading Platform benutzerdefiniertes Protokoll Zugriff auf benutzerdefinierte Börsen

Benutzerdefinierte Dokumentation zur Verwendung des Protokolls

Sie können dieses allgemeine Protokoll verwenden, um auf jeden Austausch zuzugreifen, der API-Handel bietet, das spezifische API-Protokoll ist nicht begrenzt, ob es sich um Rest, Websocket, Fix... Alles kann zugegriffen werden, um zu verwenden. Python benutzerdefiniertes Protokoll Beispiel:https://www.fmz.com/strategy/101399

1. Benutzerdefinierte Protokoll-Plug-in-Bedienung, Port-Einstellungen

Die Einstellung der Abhöradresse und des Ports des Custom Protocol Plugins kann wie folgt sein: Zum Beispiel:http://127.0.0.1:6666/DigitalAssetoderhttp://127.0.0.1:6666/exchange.

Warum müssen wir dieseEigentumsrechtundWege- Was? Das liegt daran, dassHinzufügen des AustauschsSeite in derFMZ Quant Plattform Dashboard, wird bei Auswahl der Option Custom Protocol zusätzlich zurAPI-KEYDiese Dienstadresse informiert den Docker, wo er auf die IP und den Port zugreifen kann (der Docker und das Custom Protocol Plugin-Programm laufen möglicherweise nicht auf demselben Gerät).http://127.0.0.1:6666/DigitalAsset. DigitalAssetist nur ein Beispiel und kann durch einen Namen Ihrer Wahl ersetzt werden.

Auf der Add Exchange-Seite der FMZ Quant-Plattform benötigt die Austauschkonfiguration normalerweise nuraccess keyundsecret key. Allerdings erfordern einige Börsen API-Schnittstellen, dass das Handelspasswort weitergegeben wird (z.B. die Orderplatzierungs-Schnittstelle bestimmter Börsen).secret key(oderaccess keyDann können wir im Custom Protocol Plugin Programm eine Zeichenfolge ausführensplitBetrieb, um diese Daten zu trennen, wie in dem Beispiel abgebildet.

img

Und dann im Plugin, verarbeiten Sie es zu erhaltenXXX_PassWord- Ich weiß. Zum Beispiel im vollständigen Beispiel am Ende dieses Posts, in der newBitgo-Funktion:

func newBitgo(accessKey, secretKey string) *iBitgo {
    s := new(iBitgo)
    s.accessKey = accessKey
    s.secretKey = secretKey
    // Additional configuration information in the secretKey can be separated here and can be written as in the following comment
    /*
    arr := strings.SplitN(secretKey, ",", 2)
    if len(arr) != 2 {
        panic("Configuration error!")
    }
    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
}

Wenn die Parameter in den Anforderungsdaten erneut analysiert werden, sendet der Docker die Anforderungsdaten als:

"secret_key" : "XXX",

Das Plugin empfängt die eingehenden Daten, die diese Art von Informationen enthalten, und trennt das XXX_PassWord auf der Grundlage des Komma-Trenners davon, so dass es die zusätzlichen übermittelten Daten erhält.

Beispiel für ein allgemeines benutzerdefiniertes Protokoll-PluginmainFunktion:GoBeschreibung der Sprache

func main(){
    var addr = flag.String("b", "127.0.0.1:6666", "bing addr")   // Set command line parameters, default value description, port setting 6666
    flag.Parse()                                                 // Parsing the command line
    if *addr == "" {
        flag.Usage()                                             // Display the command line description
        return 
    }
    basePath := "/DigitalAsset"
    log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")   // Print listening port information
    http.HandleFunc(basePath, OnPost)
    http.ListenAndServe(*addr, nil)
}

2. Reaktionsfunktion

Das Custom Protocol Plugin Programm hört kontinuierlich auf einem angegebenen Port für eingehenderequest. Sobald eine Anfrage empfangen wird, ruft sie die Antwortfunktion zur Ausführung auf und analysiert dann die Parameter aus den Anforderungsdaten. Die vom Docker gesendeten Anforderungsdaten sind:

/* JSON structure of request, FMZ Quant call GetTicker, docker sent to the custom protocol plugin case example (the value of params may be different for each API call, here the method is 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",
                   ...
               },                       // The parameters are slightly different for each request. That is, different FMZ Quant APIs are called in the strategy with different parameters, which are described in the following sections for each API.
}
*/

Also, basierend auf derMethodDas Feld imrequestStruktur erhalten durch JSON deserializing die Anfrage Körperdaten erhalten im Universal Protocol Plugin Programm, können wir eineswitchAnweisung, um verschiedene FMZ Quant-APIs zu kategorisieren und zu verarbeiten, die auf dem Docker aufgerufen werden (d. h. identifizieren, welche FMZ QuantAPIdie auf dem Docker ausgeführte Strategie ruft auf):

Beispiel inGoSprache:

switch request.Method {    // M of request.Method is capitalized here, the body of the request received by the custom protocol program for the JSON data, in the Go language, the anti-JSON serialization (Unmarshal) is a structure, the first letter of the field must be capitalized
  case "accounts" :        // When the exchange.GetAccount() function is called in the bot strategy on the docker, the docker sends in a request where the Body carries data with a method attribute value of 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:
      ...

Nach der Ausführung dieser Verzweigungen sollten die zurückgegebenen Daten in die Struktur geschrieben werden, die das Programm Custom Protocol Plugin verwenden wird, um auf die Anfrage des Dockers zu antworten.

Beispiel in Go:

defer func(){                                // Handling of closing work 
      if e := recover(); e != nil {          // The recover() function is used to catch panic, e ! = nil, i.e. an error has occurred
          if ee, ok := e.(error); ok {       // Type derivation, successful derivation assigns ee.Error() to e
              e = ee.Error()                 // Call the Error method to get the returned error string
          }
          ret = map[string]string{"error": fmt.Sprintf("%v", e)}
      }
      b, _ := json.Marshal(ret)              // Encode the result obtained from this call, ret, assign it to b, and write it into the response pointer
      w.Write(b)
      //fmt.Println("after w.Write the b is", string(b))     // test
  }()

3. Arten von API-Aufrufen

Grob unterteilt in zwei Kategorien:

  1. öffentliche Schnittstellen, für die keine Unterschrift erforderlich ist, z. B.:

GetTicker()

GetDepth()

GetTrades()

GetRecords(period)

  1. Benutzeroberflächen, die sich unterschreiben müssen, wie z. B.:

Buy, Sell

GetOrder(id)

GetOrders()

GetAccount()

CancelOrder(id)Ich... Die Unterschriftmethoden können von Börse zu Börse variieren und müssen speziell nach den Bedürfnissen geschrieben werden.

4. Das Datenformat für die Interaktion zwischen dem Custom Protocol Plugin und dem Docker beim Aufruf verschiedener FMZ Quant API-Schnittstellen:

Einige FMZ Quant API-Schnittstellen, wieGetName(), GetLabel(), etc. nicht an dieBenutzerdefiniertes Protokoll-Pluginwenn sie gerufen werden. Wenn Sie anrufenexchange.GetName(), wird der im Universal-Plugin konfigurierte Austausch Exchange zurückgeben.

    1. GetTicker: Wird verwendet, um die aktuellen Tickerdaten zu erhalten.

Diemethodin derrequestvon derAnschlagmaschinefür die Hörantwortfunktion istticker.

Der Docker sendet den Parameter:request.Params.symbol, die vom Docker basierend auf der auf der Seite des Roboters eingestellten Währung gesendet wird.

Das im Anforderungskörper übertragene Datenformat (JSON), wenn der Docker das benutzerdefinierte Protokollplugin anfordert

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "ticker",
    "params" :     {"symbol" : "ETH_BTC"},     // Take the ETH_BTC trading pair for example
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts (d. h. das Format, in dem die Daten an den Docker zurückgegeben werden, nachdem die Austauschoberfläche vom Common Protocol Plug-in angefordert wurde)

JSON-Struktur

{
    "data": {
        "time": 1500793319499,  // Millisecond timestamp, integer
        "buy": 1000,            // floating-point type as follows
        "sell": 1001,
        "last": 1005,
        "high": 1100,
        "low": 980,
        "vol": 523,
    }
}
    1. GetRecords: Wird verwendet, um die vom Austausch bereitgestellten K-Liniendaten abzurufen (basierend auf den vom Docker angeforderten Parametern)

Diemethodin derrequestvom Docker an die Hörantwortfunktion gesendet wirdrecords.

Der Docker sendet die Parameter:request.Params.period, die mit dem ersten Parameter derexchange.GetRecordsDie tatsächlicherequest.Params.periodDie Periode in Minuten ist die Periode in Minuten.60*24, was1440. request.Params.symbolwird vom Docker auf der Grundlage der festgelegten Währung gesendet.

Das Datenformat, das im Anforderungskörper gespeichert wird, wenn der Docker das benutzerdefinierte Protokollplugin anfordert.

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "records",
    "params" :     {"symbol" : "ETH_BTC", "period" : "1440"},     // Example of an ETH_BTC pair with a daily K-period
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

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],
            ...
    ]
}

Daten des Sprachtests:

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}
}

FMZ Quant-PlattformLogAnzeigenrecordsDaten:

[
    {"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 im zweidimensionalen Array ist der Typint2. Der Docker multipliziert den Zeitstempel automatisch mit 1000, wie oben erwähnt.

    1. GetDepth: Holt die Tiefeninformationen (Bestellbuch, ask1, ask2... bid1, bid2...) vom Austausch ab.

Diemethodin derrequestvom Docker an die Hörantwortfunktion gesendet wirddepth.

Der Docker sendet den Parameter:request.Params.symbol, die vom Docker auf der Grundlage der in der Strategie festgelegten Währung gesendet wird.

Das im Anforderungskörper übertragene Datenformat, wenn der Docker das benutzerdefinierte Protokollplugin anfordert

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "depth",
    "params" :     {"symbol" : "ETH_BTC"},     // Take the ETH_BTC trading pair for example
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

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], ... ],
    }
}
    1. GetTrades: Erhalten Sie innerhalb eines bestimmten Zeitraums (ohne eigene Geschäfte) die Handelsprotokolle der gesamten Börse

Diemethodin derrequestvom Docker an die Hörantwortfunktion gesendet wird:trades.

Parameter, die vom Docker gesendet werden: Der Wert vonrequest.Params.symbolist die Handelswährung, beispielsweise:btc, die vom Docker basierend auf den Strategie-Einstellungen gesendet wird.

Das Datenformat, das der Docker beim Anfordern des benutzerdefinierten Protokollplugins trägt, ist wie folgt:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "trades",
    "params" :     {"symbol" : "ETH_BTC"},     // Take the ETH_BTC trading pair for example
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

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",
        },{
            ...
        }
    ]
}
    1. GetAccount: Erhalten Sie Informationen über Kontovermögen.

Diemethodin derrequestvom Docker an die Hörantwortfunktion gesendet wird:accounts.

Parameter, die vom Docker gesendet werden: (Hinweis: Im Allgemeinen soll das gesamte Vermögen des Kontos erhalten werden! Bitte wenden Sie sich an die Austauschoberfläche, um zu sehen, ob es sich um einzelne Vermögenswerte oder die Gesamtanlage handelt)

Das Datenformat, das der Docker beim Anfordern des benutzerdefinierten Protokollplugins trägt, ist wie folgt:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "accounts",
    "params" :     {},                         
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

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" : {...}             // It is possible to write the raw message (response) returned by the exchange when the plugin accesses the exchange
}
    1. Kauf, Verkauf: Stelle eine Order für den Handel (Markt- oder Limit-Order).

Diemethodin derrequestvom Docker an die Hörantwortfunktion gesendet wird:trade.

Parameter, die vom Docker gesendet werden:request.Params.type: vom Docker gesendet, je nachdem, ob er aufruftexchange.Buyoderexchange.Sell, request.Params.price: der erste Parameter derAPIin der Strategie aufgerufene Funktion,request.Params.amount: der zweite Parameter derAPIin der Strategie aufgerufene Funktion,request.Params.symbol: vom Docker auf der Grundlage der festgelegten Handelswährung gesendet.

Das Datenformat, das der Docker beim Anfordern des benutzerdefinierten Protokollplugins trägt, ist wie folgt:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "trade",
    "params" :     {
                       "symbol" : "ETH_BTC", 
                       "type" : "buy", 
                       "price" : "1000", 
                       "amount" : "1"
                   },                          // Example of an ETH_BTC trading pair, "type": "buy" buy request, price 1000, quantity 1
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

JSON-Struktur

{
    "data": {
        "id": 125456,      // Order id returned after placing an order
                           // If the order id is in the form of a string like "asdf346sfasf532"
                           // Here the id can also be a string type
    }
}
    1. GetOrder: Erhalten Sie Informationen zu einer bestimmten Bestellung nach Bestell-ID

Diemethodin derrequestvom Docker an die Hörantwortfunktion gesendet wird:order.

Parameter, die vom Docker gesendet werden:request.Params.id, request.Params.symbol.

Das Datenformat, das der Docker beim Anfordern des benutzerdefinierten Protokollplugins trägt, ist wie folgt:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "order",
    "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // Take the ETH_BTC trading pair and order ID XXX as an example. Please note that some exchanges use numerical order IDs, such as 123456, while others use string order IDs, such as poimd55sdfheqxv. The specific format of the order ID depends on the exchange.
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

JSON-Struktur

{ 
    "data": {
        "id": 2565244,
        "amount": 0.15,
        "price": 1002,
        "status": "open",    // "open": pending, "closed": closed, "canceled": canceled
        "deal_amount": 0,
        "type": "buy",       // "buy"、"sell"
        "avg_price": 0,      // If not provided by the exchange, it can be assigned a value of 0 during processing
    }
}
    1. GetOrders: Erhalten Sie Informationen für alle nicht ausgeführten Bestellungen

Diemethodin derrequestvom Docker an die Hörantwortfunktion gesendet wirdorders.

Parameter, die vom Docker gesendet werden:request.Params.symbol

Das Datenformat, das der Docker beim Anfordern des benutzerdefinierten Protokollplugins trägt, ist wie folgt:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "orders",
    "params" :     {"symbol" : "ETH_BTC"},     // Take the ETH_BTC trading pair for example
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

JSON-Struktur

{
    "data": [{
        "id": 542156,
        "amount": 0.25,
        "price": 1005,
        "deal_amount": 0,
        "type": "buy",      // "buy"、"sell"
        "status": "open",   // "open"
    },{
        ...
    }]
}
    1. Stornieren: Stornieren Sie eine Bestellung mit der angegebenen Bestell-ID

Diemethodin derrequestvom Docker an die Hörantwortfunktion gesendet wirdcancel.

Parameter, die vom Docker gesendet werden:request.Params.id(String-Typ, der erste Parameter der von der Strategie aufgerufenen API-Funktion),request.Params.symbol(z. B. BTC, der vom Docker auf der Grundlage der von der Strategie festgelegten Währung gesendet wird)

Das Datenformat, das der Docker beim Anfordern des benutzerdefinierten Protokollplugins trägt, ist wie folgt:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "cancel",
    "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // Take an ETH_BTC trading pair with an id of "XXX" (same as the GetOrder function's parameter id) for example
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

JSON-Struktur

{
    "data": true,        // true or false
}
    1. Rufen Sie denexchange.IOFunktion der FMZ Quant-Plattform

Diemethodin derrequestvom Docker an die Listening-Reaktionsfunktion gesendet wird, beginnt mit_api_.

Das Datenformat, das der Docker beim Anfordern des benutzerdefinierten Protokollplugins trägt, ist wie folgt:

{
    "access_key" : "access_key",
    "secret_key" : "secret_key",
    "nonce" :      "1500793319499",            // millisecond timestamp
    "method" :     "__api_XXX",                // XXX is the API interface for the specific exchange (base address not included)
    "params" :     {"borrow_id" : "123", "symbol" : "cny"},      // Specifically, the parameters passed into the IO function
}

Struktur des an den Docker gesendeten endgültigen Rückgabewerts:

{
    "data": {...}       // The return value of a specific interface call
}

Als Beispiel wird in der Strategieaufforderung Folgendes genannt:

var io_str = exchange.IO("api", "POST", "cancel_borrow", "symbol=cny&borrow_id=123")

Testcode im Plugin (Geschaltungssprache):

fmt.Println("request.Method:", request.Method, "request.Params:", request.Params)

Kommandozeile des Plugins: 2017/08/31 10:19:59 Laufenhttp://127.0.0.1:6666/DigitalAsset

Plugin-Befehlszeilen-Druck von: request.Method, request.ParamsIn dem vom Docker gesendeten Anforderungskörper sind die in der Anfrage analysierten Daten wie folgt:request.Methodist__api_cancel_borrow request.Paramsist{"borrow_id" : "123", "symbol" : "cny"}

Sie können den Umgang mit diesen anpassenexchange.IOAnrufe, die direkt auf die Börse zugreifenAPI.

# Attention:
# When calling exchange.IO("api", "POST", "/api/v1/getAccount", "symbol=BTC_USDT"), 
# If the second parameter is not POST but: exchange.IO("api", "GET", "/api/v1/getAccount", "symbol=BTC_USDT")
# is the GET method, which is then stored in the header Http-Method in the http request accepted by the custom protocol plugin.
# So you need to refer to the following sample code for the custom protocol handling IO function implementation:
// tapiCall function definition
func (p *iStocksExchange) tapiCall(method string, params map[string]string, httpType string) (js *Json, err error) {
    ...
}

// In the OnPost function
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 den Austausch.GetRawJSON:

Das zugrunde liegende System verarbeitet automatisch die Anrufe anexchange.GetRawJSON, so dass es nicht notwendig ist, es im Plugin zu implementieren.

  • Unterstützung für den Austausch.

Das zugrunde liegende System verarbeitet automatisch die Anrufe anexchange.Go, so dass es nicht notwendig ist, es im Plugin zu handhaben.

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)

img

img

# Note: If you specify a timeout when waiting using exchange. 
#      Always make sure to obtain the final data so that the concurrent threads of the application can be reclaimed.
  • Unterstützung von Futures-Funktionen:

Für Futures-Funktionen müssen Sie spezifische Handhabungen im Plugin-Programm implementieren. Zum Beispiel Hebelwirkung, Vertragskode und Bestellrichtung. Sie können eine lokale Variable festlegen, um diese Informationen zu erfassen. Um Positionen abzurufen, müssen Sie auf die Exchange API zugreifen, um Rohdaten zu erhalten und sie in die in der FMZ-Plattform definierte Positionsstruktur zu verarbeiten und dann zurückzugeben.

Wenn die folgenden Funktionen in der Strategie aufgerufen werden, wird das Format derRpcSie müssen auf das Format des Plugins achten.RpcRequestDer Hauptunterschied besteht darin, dass der Wert von Params eine zusammengesetzte Struktur ist.

SetContractType wird angezeigt Setzen Sie den Vertragskode.

{"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"}

SetDirection Setzt die Richtung für die Platzierung von Futures-Orders fest.

{"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"}

MarginLevel festlegen Setzt den Futures-Leverage.

{"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"}

Position erhalten Holen Sie sich die Futures-Position. Wann?exchange.GetPosition()heißt:

{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":[],"code":3},"secret_key":"123"}

Wann?exchange.GetPosition("swap")heißt:

{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":["swap"],"code":3},"secret_key":"123"}
  • Vollständige Go-Sprache Beispiel für benutzerdefiniertes Protokoll-Plugin (Zugriff auf Bitgo Exchange)
/*
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 // Sort by value
    return ms[i].Key < ms[j].Key // Sort by 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 {        // The fields in a struct must start with an uppercase letter, otherwise they cannot be parsed correctly. Structs can have exported and unexported fields, where fields starting with an uppercase letter are considered exported.
                                // During unmarshaling, the JSON tag of a struct is used to match and find the corresponding field. Therefore, modifiers are required in this case.
    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)
}

Mehr