Inventeur de plate-forme de négociation quantitative de protocole général d'accès à l'échange personnalisé

Auteur:Le petit rêve, Créé: 2017-08-24 16:29:56, mis à jour: 2021-06-10 15:33:02

Documents d'utilisation du protocole général

Vous pouvez accéder à n'importe quel service fourni par ce protocole.APILes échanges, les protocoles d'API spécifiques sont illimités, que ce soitrestwebsocketfixLe site Web de l'entreprise est accessible et utilisé par tous. L'exemple du protocole général Python:https://www.fmz.com/strategy/101399

  • 1, le plug-in du protocole général est en cours d'exécution, les ports sont configurés

    L'adresse de surveillance et les paramètres de port du plug-in GNU / Linux sont bien écrits. Par exemple:http://127.0.0.1:6666/DigitalAssetVous pouvez aussi:http://127.0.0.1:6666/exchange

    Pourquoi mettre ces choses là?Intégration intellectuelleLe parcoursJe ne sais pas. Parce que dansInventeur de la quantificationPage suivanteCentre de contrôleLe sautAjouter des échangesSur la page, sélectionnez la barre des protocoles généraux pour afficher saufAPI-KEYIl y a aussi une barre d'adresse de service, qui indique à l'hôte quelle adresse IP et quel port il faut accéder (l'hôte et le plugin du protocole général ne peuvent pas fonctionner sur le même appareil).http://127.0.0.1:6666/DigitalAssetDigitalAssetC'est un nom auto-défini, c'est juste un exemple.

    La page de l'inventeur quantité d'ajouter à l'échange est la suivante: Normalement, les informations de configuration des comptes des bourses sont configuréesaccess keyetsecret keyCependant, certaines interfaces API des échanges demandent de transmettre des mots de passe de transaction (par exemple, les interfaces de sous-titrage de certaines échanges), alors, comme il n'y a pas de contrôles supplémentaires sur la page du protocole général pour écrire ces informations, lorsque nous rencontrons l'API de ces échanges, nous pouvons écrire les informations de configuration supplémentaires nécessaires pour transmettre ces informations dans la page de configuration.secret keySi les informations ne sont pas sensibles, elles peuvent être écrites dansaccess keyVous pouvez créer un fichier en utilisant un plug-in de protocole général.splitL'opération consiste à séparer ces données, comme le montre l'exemple ci-dessous.

    img

    Il y a un autre problème, c'est qu'il n'y a pas d'appareil qui fonctionne.XXX_PassWordJe ne sais pas. Par exemple, dans le dernier exemple complet de cet article, la fonction newBitgo:

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

    img

    Plugin de protocole généralmainExemple de fonction:GoDes mots qui décrivent:

    func 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, fonction de réponse

    Plugin de protocole universel pour le piratage des programmes de surveillance en continu sur le port spécifié, si une demande est envoyéerequestLorsqu'une requête est lancée, la fonction de réponse est appelée à exécuter la requête, puis à analyser les paramètres dans les données de la requête.

    /* 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 有介绍说明。
    }
    */
    

    Donc, selon les demandes reçues dans le plugin du protocole général, les structures obtenues après la séquençage des données de corps contre JSONrequestDansMethodNous pouvons utiliserswitchClassifier les API pour traiter les quantifications de différents inventeurs (c'est-à-dire identifier les appels à la stratégie en cours d'exécution sur les hôtes qui sont quantifiés par l'inventeur)APIJe vous en prie.

    GoDes exemples de langues:

    switch 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:
          ...
    

    Ces branches, après avoir exécuté les données retournées, sont écrites dans une structure à laquelle les plugins du protocole général doivent répondre, répondant aux demandes des administrateurs.

    L'exemple du langage Go:

    defer 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))     // 测试
      }()
    
  • Type d'appel de l'API

    Il y a deux catégories: Les utilisateurs peuvent choisir une interface publique qui ne nécessite pas de signature, comme:

    GetTicker()

    GetDepth()

    GetTrades()

    GetRecords(period)

    2° Les interfaces utilisateur qui nécessitent une signature, telles que:

    BuySell

    GetOrder(id)

    GetOrders()

    GetAccount()

    CancelOrder(id)Je ne sais pas. Les signatures peuvent varier d'un échange à l'autre et doivent être écrites en fonction des besoins.

  • 4, les inventeurs quantifient les appels vers les interfaces APIPlugin de protocole généraletLe gérantLes formats de données interactives:

    Il existe des interfaces API quantifiées par des inventeurs comme:GetName()GetLabel()Une fonction équivalente qui n'est pas appeléePlugin de protocole généralEnvoyer une demande.exchange.GetName()L'interface utilisant le plug-in générique renvoie "Exchange" lors d'un appel.

    • 1, GetTicker: Je suis désolé.Les données sont utilisées pour obtenir les données actuelles sur le marché.

      Le gérantLa réponse est envoyée à la fonction de surveillancerequestDansmethodPour:ticker

      L'administrateur envoie les paramètres:request.Params.symbol: envoyé par l'administrateur en fonction de la devise définie par la page du robot.

      Le format de données porté par le sujet (JSON) est demandé par l'administrateur lors d'une demande à un plugin du protocole général

      {
          "access_key" : "access_key",
          "secret_key" : "secret_key",
          "nonce" :      "1500793319499",            // 毫秒时间戳
          "method" :     "ticker",
          "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
      }
      

      La structure de la valeur de retour finalement envoyée au dépositaire: (c'est-à-dire le format dans lequel les données sont retournées au dépositaire après que le plug-in du protocole général a demandé l'interface de l'échange)

      Structure du JSON

      {
          "data": {
              "time": 1500793319499,  // 毫秒时间戳,整型
              "buy": 1000,            // 以下浮点型
              "sell": 1001,
              "last": 1005,
              "high": 1100,
              "low": 980,
              "vol": 523,
          }
      }
      
    • 2, GetRecords:Utilisé pour obtenir les données de ligne K fournies par l'échange.

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestDansmethodPour:records

      L'administrateur envoie les paramètres:request.Params.periodLes valeurs associéesexchange.GetRecordsLe premier paramètre de la fonction, en fait.request.Params.periodLes cycles représentés par le nombre de minutes, par exemple les cycles de jour, sont:60*24Je veux dire,1440request.Params.symbolLes utilisateurs peuvent choisir la devise de leur choix.

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "access_key" : "access_key",
          "secret_key" : "secret_key",
          "nonce" :      "1500793319499",            // 毫秒时间戳
          "method" :     "records",
          "params" :     {"symbol" : "ETH_BTC", "period" : "1440"},     // 以ETH_BTC交易对,K线周期为日线举例
      }
      

      La structure de valeurs retournées est la suivante:

      Structure du JSON

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

      Les données du test linguistique Go:

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

      Plateforme de quantification des inventeursLogAffichagerecordsLes données:

      [
          {"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}
      ]
      

      Remarque: le premier élément d'une matrice en 2D estintLe type, qui représente le chronomètre. 2 Les administrateurs donnent automatiquement le chronomètre multiplié par 1000, et plus encore.

    • 3 ̊ Profondeur:Pour obtenir des informations plus détaillées sur les bourses (commandes minces, vente 1, vente 2, achat 1, achat 2)

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestDansmethodPour:depth

      L'administrateur envoie les paramètres:request.Params.symbol: envoyé par l'administrateur en fonction de la devise définie par la stratégie

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "access_key" : "access_key",
          "secret_key" : "secret_key",
          "nonce" :      "1500793319499",            // 毫秒时间戳
          "method" :     "depth",
          "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
      }
      

      La structure de valeurs de retour finalement envoyée à l'administrateur:

      Structure du JSON

      {
          "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:Obtenir des enregistrements de transactions sur l'ensemble de l'échange fournis par l'échange (autres que les propres enregistrements de transactions)

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestDansmethodPour:trades

      L'administrateur envoie les paramètres:request.Params.symbolLa valeur de la monnaie à échanger est par exemple:btcLes messages sont envoyés par l'administrateur en fonction de la monnaie définie par la stratégie.

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "access_key" : "access_key",
          "secret_key" : "secret_key",
          "nonce" :      "1500793319499",            // 毫秒时间戳
          "method" :     "trades",
          "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
      }
      

      La structure de valeurs de retour finalement envoyée à l'administrateur:

      Structure du JSON

      { 
          "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 ̊ Obtenir un compte:Obtenir des informations sur les actifs du compte

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestDansmethodPour:accounts

      Paramètres envoyés par le dépositaire: ((attention! généralement, tous les actifs du compte sont obtenus!, selon l'interface de l'échange, soit individuellement, soit pour obtenir des informations sur les actifs totaux)

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "access_key" : "access_key",
          "secret_key" : "secret_key",
          "nonce" :      "1500793319499",            // 毫秒时间戳
          "method" :     "accounts",
          "params" :     {},                         
      }
      

      La structure de valeurs retournées est la suivante:

      Structure du JSON

      {
          "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, Acheter, Vendre:Envoyer une commande, passer une commande.

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestDansmethodPour:trade

      L'administrateur envoie les paramètres:request.Params.typeLes gestionnaires: sur appelexchange.BuyOu alorsexchange.SellLe message a été envoyé.request.Params.priceLes appels à la stratégie:APILe premier paramètre d'une fonction,request.Params.amountLes appels à la stratégie:APILe deuxième paramètre de la fonction est:request.Params.symbolLes utilisateurs peuvent choisir la devise de leur choix.

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "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举例
      }
      

      La structure de valeurs de retour finalement envoyée à l'administrateur:

      Structure du JSON

      {
          "data": {
              "id": 125456,      // 下单后返回的订单id
                                 // 如果订单id是"asdf346sfasf532"这样的字符串形式
                                 // 此处id也可以是字符串类型
          }
      }
      
    • 7, Obtenir ordre:Obtenir des informations sur les commandes avec le numéro de commande spécifié

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestDansmethodPour:order

      L'administrateur envoie les paramètres:request.Params.idrequest.Params.symbol

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "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格式
      }
      

      La structure de valeurs de retour finalement envoyée à l'administrateur:

      Structure du JSON

      { 
          "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, Obtenir des ordres:Retrouvez toutes les commandes en attente

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestLa méthode est la suivante:orders

      L'administrateur envoie les paramètres:request.Params.symbol

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "access_key" : "access_key",
          "secret_key" : "secret_key",
          "nonce" :      "1500793319499",            // 毫秒时间戳
          "method" :     "orders",
          "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
      }
      

      La structure de valeurs de retour finalement envoyée à l'administrateur:

      Structure du JSON

      {
          "data": [{
              "id": 542156,
              "amount": 0.25,
              "price": 1005,
              "deal_amount": 0,
              "type": "buy",      // "buy"、"sell"
              "status": "open",   // "open"
          },{
              ...
          }]
      }
      
    • 9, annuler la commande:Annuler la commande avec le numéro de commande spécifié

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestDansmethodPour:cancel

      L'administrateur envoie les paramètres:request.Params.idLe type de chaîne, le premier paramètre de l'API appelée par la stratégie.request.Params.symbol:btc ((exemple) envoyé par l'hôte selon la devise définie par la politique

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "access_key" : "access_key",
          "secret_key" : "secret_key",
          "nonce" :      "1500793319499",            // 毫秒时间戳
          "method" :     "cancel",
          "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // 以ETH_BTC交易对,id为"XXX"(同GetOrder函数的参数id一样),举例
      }
      

      La structure de valeurs de retour finalement envoyée à l'administrateur:

      Structure du JSON

      {
          "data": true,        // true or false
      }
      
    • 10 IO:调用发明者量化平台的exchange.IO函数

      Le serveur de télécommunication est un service de télécommunication qui permet aux utilisateurs de télécharger des informations sur les réseaux sociaux.requestDansmethodPour__api_Le nom de la méthode commence par ʽ.

      Format de données que l'administrateur demande aux plugins du protocole général

      {
          "access_key" : "access_key",
          "secret_key" : "secret_key",
          "nonce" :      "1500793319499",            // 毫秒时间戳
          "method" :     "__api_XXX",                // XXX为具体交易所的API接口(不包含基地址)
          "params" :     {"borrow_id" : "123", "symbol" : "cny"},      // 具体是传入IO函数的参数
      }
      

      La structure de valeurs de retour finalement envoyée à l'administrateur:

      {
          "data": {...}       // 具体的接口调用的返回值
      }
      

      Par exemple, les appels stratégiques:

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

      Le code de test dans le plugin (langue go):

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

      La ligne de commande du plugin: 2017/08/31 10:19:59 en cours d'exécutionhttp://127.0.0.1:6666/DigitalAsset

      Request.Method,request.Params imprimé dans la ligne de commande du pluginDans la requête envoyée par l'administrateur après avoir analysé les données dans le corps:request.MethodPour:__api_cancel_borrowDans la requête envoyée par l'administrateur après avoir analysé les données dans le corps:request.ParamsPour:{"borrow_id" : "123", "symbol" : "cny"}

      Ces échanges sont personnalisables pour accéder directementAPIJe ne sais pasexchange.IOJe vous appelle.

      # 注意:
      # 在调用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"))
          ...
      }
      
    • 对于exchange.GetRawJSON的支持

      Traitement automatique au fondexchange.GetRawJSONLes utilisateurs peuvent télécharger des applications qui n'ont pas besoin d'être implémentées dans un plugin.

    • 对于exchange.Go的支持

      Traitement automatique au fondexchange.GoLes utilisateurs peuvent télécharger des appels à l'aide d'un plug-in qui n'a pas besoin d'être traité.

      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

      # 注意:使用exchange.Go在wait的时候如果指定了超时时间, 
      #      一定要确保获取到最终的数据,这样申请的并发线程才能回收。
      
    • Prise en charge des fonctions futures

      Des traitements spécifiques doivent être réalisés dans le plugin, tels que la mise en place de l'effet de levier, du code de contrat, de la direction de la commande, la mise en place d'un enregistrement de variables locales, l'accès au portefeuille, l'accès à l'interface de l'échange, l'accès aux données brutes et le traitement de la structure de portefeuille définie sur la plate-forme FMZ. Lorsque la fonction suivante est appelée dans la stratégie, le plugin reçoitRpcLe format de la requête est légèrement différent de celui des autres interfaces.RpcRequestLe format, principalement différencié par la valeur params, est une structure complexe.

      • Type de contrat défini
        Le code du contrat a été défini.

        {"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"}
        
      • La direction définie Les futures sont placées dans une seule direction.

        {"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"}
        
      • Le niveau de marge Les investisseurs ont besoin d'un peu de temps pour se mettre en place.

        {"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"}
        
      • Pour obtenir la position Il a également publié un article intitulé "L'acquisition d'un portefeuille futur". Quandexchange.GetPosition()Les appels:

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

        Quandexchange.GetPosition("swap")Les appels:

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

  • Exemple de langage Go complet avec le plug-in du protocole général (accès à l'échange de bitcoins)

/*
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)
}


Plus de

Le foinExemple d'échange personnalisé avec le protocole Python Access https://www.fmz.com/strategy/101399

Le foinExemple d'échange personnalisé avec le protocole Python FMZ http://www.fmz.com/strategy/101399

- Je ne sais pas.L'échange bitmex est déjà connecté à la plateforme, mais il n'est pas possible d'obtenir un prix d'achat avec _C (exchange.GetTicker).Buy ou _C (exchange.GetDepth).

Une tendresse quotidienneC'est un peu plus facile d'écrire un plug-in de protocole général qui peut être entièrement lié à l'API de l'échange, plutôt que d'utiliser directement son propre programme.

Zhchy751Y a-t-il des exemples d'accès à un protocole général prêt à l'emploi, des robots complets / stratégies / configurations d'échanges, etc.?

Je vous en prie.Le code ci-dessus, c'est le plugin du protocole général, n'est-ce pas? Ce plugin n'est pas comme le modèle de stratégie que je peux appeler directement? J'envoie et reçois des données et je les analyse à l'aide de py post ou get. Le code du plugin du protocole général, c'est le post, get décomposé et il est en train de recevoir l'API de botvs?

chalieC'est très bien avec Python.

éclaboussureVous ne comprenez pas encore complètement... Est-ce que l'utilisation d'un plug-in GPL signifie que vous devez écrire beaucoup de fonctions vous-même, par exemple GetAccout)))?

AlanyaobotVS n'est-il pas un exemple de plus de j? Pourquoi pas de j? Pourquoi pas de GO?

Nxtplayer6 a décollé, mais n'a rien fait.

Les fiancées aussi.Il est agressif.

Le petit rêveBITMEX est un échange de contrats à terme. Vous devez configurer les contrats que vous souhaitez exploiter ou accéder aux transactions, voir la documentation de l'API exchange.SetContractType

Une tendresse quotidienneJe ne suis pas d'accord.

Le petit rêveMais si la stratégie est multi-plateforme? On ne peut pas abandonner une forêt pour un arbre! ^^

Le petit rêveIl s'agit d'une compilation en un fichier exécutable qui peut être exécuté avec un hôte. Il n'a pas d'importance quel catalogue. Il n'y a pas encore d'exemple de python, mais je vais en écrire un.

Je vous en prie.Comment le plugin le fait-il fonctionner, par exemple en format py, directement dans le répertoire exe de l'hôte?

Le petit rêvePour l'API BotVS, les objets de l'échange sont les mêmes (que le BotVS soit déjà connecté ou que vous ayez écrit votre propre plugin de protocole général supporté) et aucune modification n'est nécessaire. Voir ci-dessus la documentation: 1, le plugin du protocole général est en cours d'exécution, les ports sont configurés Le contenu est clair.

Je vous en prie.Est-ce que cela ressemble à un modèle de stratégie, où les échanges qui appellent le plugin correspondant appellent la fonction ici, et les autres utilisent l'API botvs existante?

Le petit rêve- Le code de ce plugin de protocole général, est-ce que c'est le code post, get, décompressé, qui est mis en œuvre et qui est lié à l'API de botvs? Je ne sais pas. - Le projet de loi sur les droits de l'homme est en cours de révision. L'adresse est celle qui représente l'objet de l'échange, à laquelle toutes les requêtes du gestionnaire sont envoyées. Le programme de service de plug-in écoute la demande de l'adresse et réagit, en remplacement du gestionnaire, pour accéder à l'échange et retourner les données demandées par le gestionnaire.

Le petit rêveC'est la même chose avec Python, la même interface, le même format. Les différences ne sont que dans la langue utilisée.

Le petit rêveIl s'agit d'un exemple de plug-in de type externe à l'hôte, dont l'objectif est d'écrire du code interactif pour accéder à la plate-forme BotVS, ce qui équivaut à connecter l'échange à BotVS. Vous pouvez écrire ce type de plug-in en utilisant des langages tels que PHP, node.js, python, Go, etc.

Le petit rêveOui, pour écrire une mise en œuvre concrète de ces fonctions d'interface telles que BotVS API GetAccount, il suffit d'écrire le code de l'échange qui est connecté à BotVS.

Le petit rêveIl est possible d'utiliser Python, Node.js, PHP et Golang, par exemple.