Sumber daya yang dimuat... Pemuatan...

Inventor Quantitative Trading Platform General Protocol Akses Ke Bursa Customized

Penulis:Penemu Kuantitas - Mimpi Kecil, Dibuat: 2017-08-24 16:29:56, Diperbarui: 2021-06-10 15:33:02

Dokumen Penggunaan Protokol Umum

Anda dapat mengakses layanan apa pun yang tersedia dengan menggunakan protokol ini.APIPertukaran yang diperdagangkan, tidak terbatas pada protokol API tertentu, baikrestwebsocketfix... dapat diakses dan digunakan. Contoh protokol umum Python:https://www.fmz.com/strategy/101399

  • 1, Memulai plug-in protokol umum, pengaturan port

    Perangkat lunak ini memiliki beberapa fitur yang dapat Anda gunakan. Contoh:http://127.0.0.1:6666/DigitalAssetAtau:http://127.0.0.1:6666/exchange

    Mengapa kita harus mengatur ini?IPJalurApa itu? Karena diPenemu KuantitasHalamanPusat KontrolMelompatMenambahkan BursaPada halaman, pilih jendela protokol umum, dan tampilkan kecualiAPI-KEYAda juga batang alamat layanan, yang memberi tahu host IP dan port mana yang harus diakses (host dan plugin protokol umum tidak dapat berjalan di perangkat yang sama).http://127.0.0.1:6666/DigitalAssetDigitalAssetIni adalah nama yang didefinisikan sendiri, hanya contoh.

    Di halaman inventor kuantitatif add-on, seperti ini: Biasanya, informasi konfigurasi akun hanya perlu dikonfigurasi.access keydansecret keyNamun, beberapa antarmuka API dari beberapa bursa memerlukan penyerahan kata sandi transaksi (misalnya antarmuka bawah untuk beberapa bursa), maka karena halaman protokol umum tidak memiliki kontrol yang berlebihan untuk menulis informasi ini, ketika kita menemukan API dari jenis bursa ini, kita dapat menulis informasi konfigurasi yang perlu disalurkan yang berlebihan disecret keyJika informasi tidak sensitif bisa ditulis diaccess keyAnda dapat menggunakan kode ini untuk membuat sebuah string di plugin protokol umum.splitOperasi, memisahkan data ini, seperti yang ditunjukkan dalam contoh di atas.

    img

    Kemudian Anda melakukan pengolahan di plugin, dan Anda mendapatkanXXX_PassWordSaya tidak tahu. Sebagai contoh, dalam contoh lengkap di akhir posting ini, fungsi 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 protokol umum secara keseluruhanmainContoh fungsi:GoBahasa yang digunakan:

    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, fungsi responsif

    Plug-in protokol umum yang menyala pada pelabuhan yang ditunjuk, apakah ada yang dikirim untuk memintarequestSetelah permintaan dilakukan, fungsi respon akan dipanggil untuk melakukan respon, kemudian menganalisis parameter dalam data permintaan, dan data permintaan yang dikirim oleh host adalah:

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

    Jadi menurut permintaan yang diterima di plugin protokol umum, struktur yang diperoleh setelah data bergantian disusun oleh JSON.requestDi dalamMethodKita bisa menggunakannya.switchUntuk mengkategorikan API yang menangani kuantifikasi dari pencipta yang berbeda (misalnya, untuk mengidentifikasi panggilan kebijakan yang berjalan pada host adalah pencipta mana yang kuantifikasi)API(Sunting)

    GoContoh bahasa:

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

    Data yang dikembalikan dari cabang-cabang ini ditulis dalam struktur yang harus dijawab oleh plugin protokol umum.

    Contoh bahasa 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))     // 测试
      }()
    
  • 3. Jenis panggilan API

    Mereka dibagi menjadi dua kelompok: 1. antarmuka publik yang tidak memerlukan tanda tangan, seperti:

    GetTicker()

    GetDepth()

    GetTrades()

    GetRecords(period)

    2. User Interface yang membutuhkan tanda tangan, seperti:

    BuySell

    GetOrder(id)

    GetOrders()

    GetAccount()

    CancelOrder(id)"Saya tidak tahu. Cara penandatanganan mungkin berbeda di setiap bursa dan perlu disusun sesuai kebutuhan.

  • 4. Penemu mengkuantifikasi setiap panggilan APIPlugin Protokol UmumdanPengelolaFormat data interaktif:

    Beberapa penemu mengkualifikasi API seperti:GetName()GetLabel()Fungsi sama, tidak akan disebut denganPlugin Protokol UmumPermintaan dikirim.exchange.GetName()Pertukaran dengan konfigurasi plugin universal akan kembali "Exchange" saat dipanggil.

    • 1, GetTicker:Untuk mendapatkan data pasar saat ini.

      PengelolaAnda dapat mengirim pesan ke fungsi respons.requestDi dalammethodUntuk:ticker

      Pengelola mengirim parameter:request.Params.symbol: dikirim oleh administrator berdasarkan mata uang yang ditetapkan pada halaman bot.

      Format data yang dibawa oleh subjek (JSON) ketika administrator meminta plugin protokol umum

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

      Struktur nilai kembali yang akhirnya dikirimkan ke administrator: (yaitu format data yang dikembalikan ke administrator setelah plugin protokol umum meminta antarmuka pertukaran)

      Struktur JSON

      {
          "data": {
              "time": 1500793319499,  // 毫秒时间戳,整型
              "buy": 1000,            // 以下浮点型
              "sell": 1001,
              "last": 1005,
              "high": 1100,
              "low": 980,
              "vol": 523,
          }
      }
      
    • 2、GetRecords:Untuk mendapatkan data K-line yang disediakan oleh bursa.

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestDi dalammethodUntuk:records

      Pengelola mengirim parameter:request.Params.periodHubungan Nilai:exchange.GetRecordsJadi jika kita menggunakan fungsi ini, kita bisa menggunakan fungsi ini.request.Params.periodPeriode yang diartikan sebagai jumlah menit, misalnya, siklus hari adalah60*24Ini adalah1440request.Params.symbol: Pengelola mengirim sesuai dengan mata uang yang ditetapkan.

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan yang akhirnya dikirim ke administrator:

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

      Data uji bahasa 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}
      }
      

      Platform Kuantitas PenemuLogTampilkanrecordsData:

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

      Perhatikan: 1 adalah elemen pertama dari array kedua dimensi.intJenis, yang mewakili waktu templat.2. Pengelola akan secara otomatis memberikan waktu templat kali 1000, atau lebih.

    • 3, Dapatkan kedalaman:Untuk mendapatkan informasi lebih dalam tentang bursa (order rendah, jual 1, jual 2, beli 1, beli 2...)

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestDi dalammethodUntuk:depth

      Pengelola mengirim parameter:request.Params.symbol: dikirim oleh administrator sesuai dengan mata uang yang ditetapkan oleh kebijakan

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan akhirnya dikirim ke administrator:

      Struktur 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:Mendapatkan catatan transaksi dari seluruh bursa yang disediakan oleh bursa dalam jangka waktu tertentu (tidak termasuk catatan transaksi sendiri)

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestDi dalammethodUntuk:trades

      Pengelola mengirim parameter:request.Params.symbolNilai yang diperdagangkan dalam mata uang seperti:btc, dikirim oleh administrator sesuai dengan mata uang yang ditetapkan oleh kebijakan.

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan akhirnya dikirim ke administrator:

      Struktur 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、GetAccount:Mengakses informasi aset akun

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestDi dalammethodUntuk:accounts

      Pengelola mengirim parameter: ((Perhatikan! umumnya untuk mendapatkan semua aset akun!, khusus untuk melihat antarmuka bursa, untuk mendapatkan informasi aset secara terpisah atau total)

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan yang akhirnya dikirim ke administrator:

      Struktur 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、Beli、Jual:Mengirim pesanan, melakukan transaksi,.. (daftar harga, daftar harga terbatas)

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestDi dalammethodUntuk:trade

      Pengelola mengirim parameter:request.Params.typePengelola: berdasarkan panggilanexchange.BuyAtauexchange.Sell"Kami tidak bisa mengatakannya", katanya.request.Params.pricePermintaan dalam kebijakan:APIIni adalah parameter pertama dari fungsi.request.Params.amountPermintaan dalam kebijakan:APIJika Anda tidak memiliki parameter yang sama, maka Anda tidak akan memiliki parameter yang sama.request.Params.symbol: Pengelola mengirim sesuai dengan mata uang yang ditetapkan.

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan akhirnya dikirim ke administrator:

      Struktur JSON

      {
          "data": {
              "id": 125456,      // 下单后返回的订单id
                                 // 如果订单id是"asdf346sfasf532"这样的字符串形式
                                 // 此处id也可以是字符串类型
          }
      }
      
    • 7, Dapatkan Perintah:Mendapatkan informasi pesanan dengan nomor pesanan tertentu

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestDi dalammethodUntuk:order

      Pengelola mengirim parameter:request.Params.idrequest.Params.symbol

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan akhirnya dikirim ke administrator:

      Struktur 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, Dapatkan Perintah:Mendapatkan informasi tentang semua pesanan yang belum selesai

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestMetode yang digunakan adalah:orders

      Pengelola mengirim parameter:request.Params.symbol

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan akhirnya dikirim ke administrator:

      Struktur JSON

      {
          "data": [{
              "id": 542156,
              "amount": 0.25,
              "price": 1005,
              "deal_amount": 0,
              "type": "buy",      // "buy"、"sell"
              "status": "open",   // "open"
          },{
              ...
          }]
      }
      
    • 9, Batalkan pesanan:Batalkan penugasan pesanan dengan nomor pesanan tertentu

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestDi dalammethodUntuk:cancel

      Pengelola mengirim parameter:request.Params.id: tipe string, parameter pertama dari fungsi API yang dipanggil oleh kebijakan,request.Params.symbol:btc ((contoh) dikirim oleh custodian berdasarkan mata uang yang ditetapkan oleh kebijakan

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan akhirnya dikirim ke administrator:

      Struktur JSON

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

      Anda dapat mengirim pesan yang dikirimkan oleh administrator ke fungsi respons pemantauan.requestDi dalammethodUntuk__api_Nama metode yang dimulai dengan.

      Format data yang dibawa oleh subjek ketika administrator meminta plugin protokol umum

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

      Struktur nilai yang dikembalikan akhirnya dikirim ke administrator:

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

      Misalnya, panggilan strategi:

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

      Kode pengujian dalam plugin (bahasa go):

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

      Perintah dari plugin: 2017/08/31 10:19:59 Berjalanhttp://127.0.0.1:6666/DigitalAsset

      Request.Method,request.Params yang dicetak di baris perintah pluginPermintaan yang dikirim oleh administrator pada request setelah data di Body di parsing:request.MethodUntuk:__api_cancel_borrowPermintaan yang dikirim oleh administrator pada request setelah data di Body di parsing:request.ParamsUntuk:{"borrow_id" : "123", "symbol" : "cny"}

      Anda dapat menyesuaikan proses akses langsung ke bursa iniAPIPerbedaanexchange.IOSaya tidak tahu apa yang terjadi.

      # 注意:
      # 在调用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的支持

      Pengolahan otomatis di bawahexchange.GetRawJSONJika Anda tidak ingin menggunakan plugin ini, Anda harus mengklik tombol yang disebut sebagai "Call" dan tidak perlu mengimplementasikannya di plugin.

    • 对于exchange.Go的支持

      Pengolahan otomatis di bawahexchange.GoPerangkat lunak ini juga dapat digunakan untuk mengaktifkan aplikasi lain seperti Google Play, Google Play Store, dan Google Play Store.

      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的时候如果指定了超时时间, 
      #      一定要确保获取到最终的数据,这样申请的并发线程才能回收。
      
    • Dukungan untuk fungsi futures

      Pengolahan spesifik yang perlu dilakukan di dalam plugin, seperti pengaturan leverage, kode kontrak, arah order, dapat mengatur catatan variabel lokal, untuk mendapatkan pegangan, perlu mengakses antarmuka pertukaran untuk mendapatkan data asli dan memproses struktur pegangan yang didefinisikan di platform FMZ dan kembali. Ketika Anda memanggil fungsi di bawah ini dalam kebijakan, plugin menerimaRpcFormat permintaan sedikit berbeda dengan antarmuka lainnya, perlu diperhatikan dalam plugin protokol umumRpcRequestFormat, yang terutama dibedakan dengan nilai param, adalah struktur kompleks.

      • SetContractType
        Peraturan kode kontrak.

        {"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"}
        
      • SetDirection Setel opsi futures ke arah bawah.

        {"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"}
        
      • Set MarginLevel Setel leverage futures.

        {"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"}
        
      • Dapatkan Posisi "Saya tidak tahu apa yang akan terjadi", katanya. Ketikaexchange.GetPosition()Saat menelepon:

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

        Ketikaexchange.GetPosition("swap")Saat menelepon:

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

  • Plugin protokol umum contoh bahasa Go lengkap ((akses bursa Bitcoin)

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


Lebih banyak

RumputPython Access Protocol Contoh Bursa Custom https://www.fmz.com/strategy/101399

RumputPython Access Protocol Custom Exchange contoh https://www.fmz.com/strategy/101399

Liuwei9090Bursa bitmex telah mengakses platform, tetapi tidak peduli dengan _C (exchange.GetTicker).Buy atau _C (exchange.GetDepth).Bids[0] tidak dapat mendapatkan harga beli, dan juga akan memberikan kesalahan.

Setiap hari yang lembutHahah, menulis plugin protokol umum bisa sepenuhnya menghubungkan API pertukaran sendiri, atau lebih baik menggunakan program sendiri secara langsung, bukankah itu sedikit lebih dari itu?

zhchy751Apakah ada contoh akses protokol umum yang sudah ada, robot / strategi / konfigurasi bursa lengkap, dll?

DyahhuApakah kode di atas adalah plugin protokol umum? Apakah plugin ini tidak mirip dengan template kebijakan yang bisa saya unduh secara langsung? Saya akan menggunakan py post atau get untuk mengirim dan menerima data, dan menguraikannya. Apakah kode plugin protokol umum, yaitu post yang diimplementasikan, get yang diuraikan, dan pada saat yang sama menerima botvs api?

chalieJika Anda memiliki Python, itu akan baik-baik saja.

JadingApakah menggunakan plugin universal berarti Anda harus menulis banyak fungsi sendiri, seperti GetAccout (?).

AlanyaoApakah botVS tidak memiliki banyak paradigma js? Mengapa tidak ada js?

nxtplayer6 sampai terbang, namun tidak berhasil.

Perempuan juga.Kekerasan

Penemu Kuantitas - Mimpi KecilBITMEX adalah bursa futures. Anda harus mengatur kontrak yang ingin Anda gunakan atau akses, lihat dokumen API untuk fungsi exchange.SetContractType.

Setiap hari yang lembutDan juga.

Penemu Kuantitas - Mimpi KecilTapi bagaimana jika strategi itu multi-platform? Anda tidak bisa menyerahkan hutan demi satu pohon!

Penemu Kuantitas - Mimpi KecilJadi, jika Anda ingin mengkompilasi sebuah file yang dapat dieksekusi, dan Anda dapat mengeksekusi file tersebut dengan administrator, Anda dapat mengkompilasi file tersebut dengan menggunakan sistem operasi. Tidak masalah katalog mana. Saya tidak punya contoh Python untuk saat ini, tapi saya akan menulis satu di luar waktu.

DyahhuBagaimana plugin membuatnya berjalan, misalnya ditulis dalam format py, ditempatkan langsung di direktori exe host?

Penemu Kuantitas - Mimpi KecilKebijakan ini tidak memerlukan pengolahan khusus, untuk antarmuka api BotVS, objek-objek bursa ini sama (apakah BotVS sudah diakses atau Anda sendiri menulis protokol umum plugin yang didukung), kebijakan ini tidak perlu melakukan penyesuaian apa pun. Plugin ini berjalan bersama-sama dengan host. Lihat di atas Dokumentasi: 1, Memulai plugin protokol umum, pengaturan port "Saya tidak tahu apa yang terjadi", katanya.

DyahhuApakah ini mirip dengan template strategi yang ditulis sendiri, dimana bursa yang menghubungkan plugin akan memanggil fungsi di sini, dan bursa lain akan menggunakan API botvs yang ada?

Penemu Kuantitas - Mimpi Kecil- Apakah kode dari plugin protokol ini adalah implementasi post, get dan decompile, dan juga menghubungkan ke API botvs? Saya tidak tahu. - http://127.0.0.1:6666/DigitalAsset Alamat ini mewakili objek bursa, setiap permintaan dari administrator akan dikirim ke alamat ini. Plugin layanan akan mendengarkan permintaan alamat ini, dan menanggapi, menggantikan administrator untuk mengunjungi bursa, dan mengembalikan data yang diminta oleh administrator (format), implementasi, dan strategi bot yang kompatibel dengan suatu BotVS yang belum diakses oleh bursa.

Penemu Kuantitas - Mimpi KecilIni adalah implementasi yang sama dengan Python, dan ini adalah interface yang sama. Hanya saja bahasa yang digunakan berbeda.

Penemu Kuantitas - Mimpi KecilIni adalah contoh dari bentuk plugin eksternal host, yang bertujuan untuk menulis beberapa kode interaktif bursa untuk mengakses platform BotVS, yang sama dengan menghubungkan bursa ke BotVS. Anda dapat menulis plugin seperti ini menggunakan bahasa seperti PHP, node.js, python, Go, dan lain-lain.

Penemu Kuantitas - Mimpi KecilYa, untuk menulis implementasi spesifik dari fungsi antarmuka seperti BotVS API GetAccount, adalah sama dengan menulis sendiri kode dari bursa yang terhubung.

Penemu Kuantitas - Mimpi KecilSebagai contoh, Python, Node.js, PHP, dan Golang dapat digunakan.