資源の読み込みに... 荷物...

FMZ 量子取引プラットフォーム カスタムプロトコル カスタム交換へのアクセス

作者: リン・ハーンFMZ~リディア作成日:2023-07-12 15:29:54 更新日:2024-01-03 21:01:07

img

FMZ 量子取引プラットフォーム カスタムプロトコル カスタム交換へのアクセス

カスタム プロトコル使用ドキュメント

特定のAPIプロトコルは制限されていません. それは休憩,Webソケット,修正... すべては使用するためにアクセスできます. Python カスタム プロトコル 例:https://www.fmz.com/strategy/101399

1. 独自のプロトコルプラグイン操作,ポート設定

Custom Protocol Pluginのリスニングアドレスとポートの設定は次のとおりである: 例えば:http://127.0.0.1:6666/DigitalAssetまたはhttp://127.0.0.1:6666/exchange.

なぜこれらの設定する必要がありますIPそして道路ほら 原因は交換を加えるページにFMZ 量子プラットフォーム ダッシュボードCustom Protocolを選択すると,API-KEY. このサービス アドレスは,ドーカーがIPとポートにアクセスする場所を通知します (ドーカーとカスタム プロトコルプラグインプログラムは同じデバイスで実行されない場合があります).例えば,サービス アドレスは,http://127.0.0.1:6666/DigitalAsset. DigitalAsset選択した名前で置き換えることができます.

FMZ Quant Platformの Add Exchangeページでは,通常,交換構成には,access keyそしてsecret key. しかし,いくつかの取引所 API インターフェースは,取引パスワードを転送する必要があります (例えば,特定の取引所の注文配置インターフェース). そのような場合,カスタムプロトコルページにはこの情報を入力するための追加のコントロールがないため,追加の必要な構成情報を入力することができますsecret key(またはaccess key文字列を実行します. 文字列の文字列の文字列の文字列の文字列の文字列の文字列の文字列の文字列の文字列をsplitこのデータを分離する操作は,例の図のように

img

プラグインでそれを処理してXXX_PassWordわかった NewBitgo関数で,この記事の終わりの完全な例で,

func newBitgo(accessKey, secretKey string) *iBitgo {
    s := new(iBitgo)
    s.accessKey = accessKey
    s.secretKey = secretKey
    // Additional configuration information in the secretKey can be separated here and can be written as in the following comment
    /*
    arr := strings.SplitN(secretKey, ",", 2)
    if len(arr) != 2 {
        panic("Configuration error!")
    }
    s.secretKey = arr[0]   // secret key 
    s.passWord = arr[1]    // XXX_PassWord
    */
    s.apiBase = "https://www.bitgo.cn"
    s.timeout = 20 * time.Second
    s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)

    return s
}

要求データのパラメータを再び解析すると,ドッカーが要求データを以下のように送信します.

"secret_key" : "XXX",

このプラグインは,このような情報を含む入荷データを受信し,XXX_PassWordをコンマ分離符に基づいて分離し,追加の転送されたデータを取得します.

全体的なカスタムプロトコルプラグインの例main機能:Go言語説明:

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

2. 応答機能

Custom Protocol Pluginプログラムは,入力される受信機を指定されたポートで継続的に聴く.request. 要求が受信されると,実行するために応答関数を呼び出し,要求データからパラメータを解析します. ドッカーが送信する要求データは:

/* JSON structure of request, FMZ Quant call GetTicker, docker sent to the custom protocol plugin case example (the value of params may be different for each API call, here the method is ticker):
{
    "access_key" : "XXX",               // `json:"access_key"`
    "secret_key" : "XXX",               // `json:"secret_key"`
    "nonce" : "1502345965295519602",    // `json:"nonce"`
    "method" : "ticker",                // `json:"method"`
    "params" : {                        // `json:"params"`
                   "symbol" : "btc",
                   ...
               },                       // The parameters are slightly different for each request. That is, different FMZ Quant APIs are called in the strategy with different parameters, which are described in the following sections for each API.
}
*/

統計学的な結果からMethodフィールドでrequestユニバーサルプロトコルプラグインプログラムで受信したボディデータ,我々は使用することができますswitchドッカーで呼び出される異なる FMZ Quant API を分類し処理するための文言 (すなわち,どの FMZ Quant を識別する)APIドッカー上で実行されている戦略は,次のように呼んでいる.

例としてGo言語:

switch request.Method {    // M of request.Method is capitalized here, the body of the request received by the custom protocol program for the JSON data, in the Go language, the anti-JSON serialization (Unmarshal) is a structure, the first letter of the field must be capitalized
  case "accounts" :        // When the exchange.GetAccount() function is called in the bot strategy on the docker, the docker sends in a request where the Body carries data with a method attribute value of accounts
      data, err = e.GetAccount(symbol)
  case "ticker" :
      data, err = e.GetTicker(symbol)
  case "depth" :
      data, err = e.GetDepth(symbol)
  case "trades" :
      data, err = e.GetTrades(symbol)
  case "trade" :
  ...
  default:
      ...

これらのブランチを実行した後,返されたデータは,カスタムプロトコルプラグインプログラムがdockerの要求に応答するために使用する構造に書き込む必要があります.

Go言語の例:

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

3. API 呼び出しの種類

概して2つのカテゴリーに分かれます

  1. 署名を必要としない公開インターフェース,例えば:

GetTicker()

GetDepth()

GetTrades()

GetRecords(period)

  1. 署名が必要なユーザーインターフェース,例えば:

Buy, Sell

GetOrder(id)

GetOrders()

GetAccount()

CancelOrder(id)... ほら サインメソッドは交換によって異なりますが,必要に応じて特に書き込む必要があります.

4. 各種の FMZ Quant API インターフェースを呼び出す際に,カスタムプロトコルプラグインとドッカーとの間の相互作用のためのデータ形式:

FMZ Quant API インターフェースの一部,例えばGetName(), GetLabel()申請を送らないことカスタム プロトコルプラグイン呼ばれたら 呼び出すときexchange.GetName(),ユニバーサルプラグインで設定された交換は,Exchangeを返します.

    1. GetTicker: 現在のティッカーデータを入手するために使用されます.

についてmethodについてrequest派遣されたドッカー聴き回答関数に対してticker.

ドーカーがパラメータを送信します.request.Params.symbolロボットのページに設定された通貨に基づいてドッカーによって送信されます.

ドッカーがカスタムプロトコルプラグインを要求するときに要求ボディに運び込まれたデータ形式 (JSON)

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

ドッカーに送信される最終返信値の構造 (つまり,交換インターフェースが共通プロトコルプラグインによって要求された後に,ドッカーに送信されたデータの形式)

JSON 構造

{
    "data": {
        "time": 1500793319499,  // Millisecond timestamp, integer
        "buy": 1000,            // floating-point type as follows
        "sell": 1001,
        "last": 1005,
        "high": 1100,
        "low": 980,
        "vol": 523,
    }
}
    1. GetRecords: 交換によって提供されるK線データを取得するために使用されます. (ドッカーによって要求されるパラメータに基づいて)

についてmethodについてrequest応答機能に送信されます.records.

ドーカーがパラメータを送信します.request.Params.period, 基準の第1パラメータに関連しています.exchange.GetRecords実際の機能はrequest.Params.period例えば,1日間の周期は,60*24これは1440. request.Params.symbol設定された通貨に基づいてドーカーによって送られます.

ドッカーがカスタム プロトコルプラグインを要求するときに要求ボディに運ばれるデータ形式.

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

ドーカーに送信される最終返信値の構造:

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

言語テストのデータ:

ret_records = []interface{}{
    [6]interface{}{1500793319, 1.1, 2.2, 3.3, 4.4, 5.5}, 
    [6]interface{}{1500793259, 1.01, 2.02, 3.03, 4.04, 5.05}
}

FMZ 量子プラットフォームLogディスプレイrecordsデータ:

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

注: 1. 二次元の配列の最初の要素は,タイプint2 ドーカーは上記のようにタイムスタンプを 1000 で自動的に掛けます.

    1. GetDepth:深度情報 (注文簿, ask1, ask2...bid1, bid2...) を交換から取得する.

についてmethodについてrequest応答機能に送信されます.depth.

ドーカーがパラメータを送信します.request.Params.symbol戦略に設定された通貨に基づいてドーカーによって送信されます.

ドッカーがカスタム プロトコルプラグインを要求するときに,要求ボディに運ばれるデータ形式

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

ドーカーに送信される最終返信値の構造:

JSON 構造

{
    "data" : {
        "time" : 1500793319499,
        "asks" : [ [1000, 0.5], [1001, 0.23], [1004, 2.1], ... ],
        "bids" : [ [999, 0.25], [998, 0.8], [995, 1.4], ... ],
    }
}
    1. GetTrades: 特定の期間中に取引所の取引記録をすべて取得する (自分の取引を除く)

についてmethodについてrequestドーカーがリスニング応答機能に送信するのは:trades.

ドッカーが送信するパラメータ:request.Params.symbol取引通貨は,例えば:btc戦略設定に基づいてドッカーによって送信されます.

カスタムプロトコルプラグインを要求する際にドッカーによって運ばれるデータ形式は以下のとおりです

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

ドーカーに送信される最終返信値の構造:

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",
        },{
            ...
        }
    ]
}
    1. GetAccount:アカウント資産情報を取得する.

についてmethodについてrequestドーカーがリスニング応答機能に送信するのは:accounts.

(注: 一般的には,アカウントのすべての資産を取得するためにです! 交換インターフェースを参照してください,個々の資産または総資産情報を取得するためにです)

カスタムプロトコルプラグインを要求する際にドッカーによって運ばれるデータ形式は以下のとおりです

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

ドーカーに送信される最終返信値の構造:

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" : {...}             // It is possible to write the raw message (response) returned by the exchange when the plugin accesses the exchange
}
    1. 買い,売る:取引のための注文 (市場注文または制限注文) をします.

についてmethodについてrequestドーカーがリスニング応答機能に送信するのは:trade.

ドーカーから送信されたパラメータ:request.Params.type: 呼び出しされているかどうかに基づいてドッカーによって送信exchange.Buyまたはexchange.Sell, request.Params.price: の最初のパラメータAPI戦略で呼び出された関数request.Params.amount: 2番目のパラメータAPI戦略で呼び出された関数request.Params.symbol: 設定された取引通貨に基づいてドーカーによって送信されます.

カスタムプロトコルプラグインを要求する際にドッカーによって運ばれるデータ形式は以下のとおりです

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

ドーカーに送信される最終返信値の構造:

JSON 構造

{
    "data": {
        "id": 125456,      // Order id returned after placing an order
                           // If the order id is in the form of a string like "asdf346sfasf532"
                           // Here the id can also be a string type
    }
}
    1. GetOrder: 注文 ID によって特定の注文の情報を取得する

についてmethodについてrequestドーカーがリスニング応答機能に送信するのは:order.

ドーカーから送信されたパラメータ:request.Params.id, request.Params.symbol.

カスタムプロトコルプラグインを要求する際にドッカーによって運ばれるデータ形式は以下のとおりです

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

ドーカーに送信される最終返信値の構造:

JSON 構造

{ 
    "data": {
        "id": 2565244,
        "amount": 0.15,
        "price": 1002,
        "status": "open",    // "open": pending, "closed": closed, "canceled": canceled
        "deal_amount": 0,
        "type": "buy",       // "buy"、"sell"
        "avg_price": 0,      // If not provided by the exchange, it can be assigned a value of 0 during processing
    }
}
    1. GetOrders: 満たされていないすべての注文の情報を取得する

についてmethodについてrequest応答機能に送信されます.orders.

ドーカーから送信されたパラメータ:request.Params.symbol

カスタムプロトコルプラグインを要求する際にドッカーによって運ばれるデータ形式は以下のとおりです

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

ドーカーに送信される最終返信値の構造:

JSON 構造

{
    "data": [{
        "id": 542156,
        "amount": 0.25,
        "price": 1005,
        "deal_amount": 0,
        "type": "buy",      // "buy"、"sell"
        "status": "open",   // "open"
    },{
        ...
    }]
}
    1. CancelOrder: 指定された注文 ID の注文をキャンセル

についてmethodについてrequest応答機能に送信されます.cancel.

ドーカーから送信されたパラメータ:request.Params.id(string type, 戦略で呼び出される API 関数の最初のパラメータ)request.Params.symbol(例えば,btc,戦略によって設定された通貨に基づいてドーカーによって送信)

カスタムプロトコルプラグインを要求する際にドッカーによって運ばれるデータ形式は以下のとおりです

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

ドーカーに送信される最終返信値の構造:

JSON 構造

{
    "data": true,        // true or false
}
    1. 呼び出すexchange.IOFMZ量子プラットフォームの機能

についてmethodについてrequest応答機能に送信される._api_.

カスタムプロトコルプラグインを要求する際にドッカーによって運ばれるデータ形式は以下のとおりです

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

ドーカーに送信される最終返信値の構造:

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

例えば,戦略の呼びかけは,

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

プラグインのテストコード (go言語):

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

プラグインのコマンドライン: 2017/08/31 10:19:59 走っているhttp://127.0.0.1:6666/DigitalAsset

プラグインのコマンドラインプリント: request.Method, request.Paramsドッカーが送信したリクエストボディでは,リクエスト内の解析されたデータは次のとおりです.request.Method__api_cancel_borrow request.Params{"borrow_id" : "123", "symbol" : "cny"}

操作をカスタマイズできますexchange.IO直接アクセスする呼び出しAPI.

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

// In the OnPost function
if strings.HasPrefix(request.Method, "__api_") {
    var js *Json
    js, err = e.tapiCall(request.Method[6:], request.Params, r.Header.Get("Http-Method"))
    ...
}
  • 交換のサポート.GetRawJSON:

基本システムでは,自動的にexchange.GetRawJSONプラグインで実装する必要はありません.

  • 交換サポート.Go:

基本システムでは,自動的にexchange.Goプラグインで処理する必要はありません.

var beginTime = new Date().getTime()
var ret = exchange.Go("GetDepth")
var endTime = new Date().getTime()
Log(endTime - beginTime, "#FF0000")

// Sleep(2000)
beginTime = new Date().getTime()
Log(exchange.GetTicker())
endTime = new Date().getTime()
Log(endTime - beginTime, "#FF0000")
var depth = ret.wait()
Log("depth:", depth)

img

img

# Note: If you specify a timeout when waiting using exchange. 
#      Always make sure to obtain the final data so that the concurrent threads of the application can be reclaimed.
  • フューチャー機能のサポート:

フューチャー機能のためのプラグインプログラムで特定のハンドリングを実装する必要があります.例えば,レバレッジ,契約コード,オーダー方向を設定します.この情報を記録するためにローカル変数を設定できます.ポジションを取得するには,交換 API にアクセスして生データを取得し,FMZ プラットフォームで定義されたポジション構造に処理し,それを返します.

戦略で次の関数が呼び出される場合,Rpcプラグインプログラムから受信されたリクエストは,他のインターフェースと少し異なります.RpcRequestパラムの値が複合構造であるということです.

SetContractType について 契約コードを設定する

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

設定する フューチャーオーダーの方向性を設定します

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

マージンレベルを設定 フューチャーレバレッジを設定します

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

位置を取得 フューチャーポジションを いつexchange.GetPosition()呼び名:

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

いつexchange.GetPosition("swap")呼び名:

{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":["swap"],"code":3},"secret_key":"123"}
  • コンプリート Go 言語 パーソナル プロトコルプラグインの例 (Bitgo Exchangeへのアクセス)
/*
GOOS=linux GOARCH=amd64 go build -ldflags '-s -w -extldflags -static' rest_bitgo.go
*/
package main

import (
    "bytes"
    "crypto/md5"
    "encoding/hex"
    "encoding/json"
    "errors"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "sort"
    "strconv"
    "strings"
    "time"
)

func toFloat(s interface{}) float64 {
    var ret float64
    switch v := s.(type) {
    case float64:
        ret = v
    case float32:
        ret = float64(v)
    case int64:
        ret = float64(v)
    case int:
        ret = float64(v)
    case int32:
        ret = float64(v)
    case string:
        ret, _ = strconv.ParseFloat(strings.TrimSpace(v), 64)
    }
    return ret
}

func float2str(i float64) string {
    return strconv.FormatFloat(i, 'f', -1, 64)
}

func toInt64(s interface{}) int64 {
    var ret int64
    switch v := s.(type) {
    case int:
        ret = int64(v)
    case float64:
        ret = int64(v)
    case bool:
        if v {
            ret = 1
        } else {
            ret = 0
        }
    case int64:
        ret = v
    case string:
        ret, _ = strconv.ParseInt(strings.TrimSpace(v), 10, 64)
    }
    return ret
}

func toString(s interface{}) string {
    var ret string
    switch v := s.(type) {
    case string:
        ret = v
    case int64:
        ret = strconv.FormatInt(v, 10)
    case float64:
        ret = strconv.FormatFloat(v, 'f', -1, 64)
    case bool:
        ret = strconv.FormatBool(v)
    default:
        ret = fmt.Sprintf("%v", s)
    }
    return ret
}

type Json struct {
    data interface{}
}

func NewJson(body []byte) (*Json, error) {
    j := new(Json)
    err := j.UnmarshalJSON(body)
    if err != nil {
        return nil, err
    }
    return j, nil
}

func (j *Json) UnmarshalJSON(p []byte) error {
    return json.Unmarshal(p, &j.data)
}

func (j *Json) Get(key string) *Json {
    m, err := j.Map()
    if err == nil {
        if val, ok := m[key]; ok {
            return &Json{val}
        }
    }
    return &Json{nil}
}

func (j *Json) CheckGet(key string) (*Json, bool) {
    m, err := j.Map()
    if err == nil {
        if val, ok := m[key]; ok {
            return &Json{val}, true
        }
    }
    return nil, false
}

func (j *Json) Map() (map[string]interface{}, error) {
    if m, ok := (j.data).(map[string]interface{}); ok {
        return m, nil
    }
    return nil, errors.New("type assertion to map[string]interface{} failed")
}

func (j *Json) Array() ([]interface{}, error) {
    if a, ok := (j.data).([]interface{}); ok {
        return a, nil
    }
    return nil, errors.New("type assertion to []interface{} failed")
}

func (j *Json) Bool() (bool, error) {
    if s, ok := (j.data).(bool); ok {
        return s, nil
    }
    return false, errors.New("type assertion to bool failed")
}

func (j *Json) String() (string, error) {
    if s, ok := (j.data).(string); ok {
        return s, nil
    }
    return "", errors.New("type assertion to string failed")
}

func (j *Json) Bytes() ([]byte, error) {
    if s, ok := (j.data).(string); ok {
        return []byte(s), nil
    }
    return nil, errors.New("type assertion to []byte failed")
}

func (j *Json) Int() (int, error) {
    if f, ok := (j.data).(float64); ok {
        return int(f), nil
    }

    return -1, errors.New("type assertion to float64 failed")
}

func (j *Json) MustArray(args ...[]interface{}) []interface{} {
    var def []interface{}

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustArray() received too many arguments %d", len(args))
    }

    a, err := j.Array()
    if err == nil {
        return a
    }

    return def
}

func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} {
    var def map[string]interface{}

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustMap() received too many arguments %d", len(args))
    }

    a, err := j.Map()
    if err == nil {
        return a
    }

    return def
}

func (j *Json) MustString(args ...string) string {
    var def string

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustString() received too many arguments %d", len(args))
    }

    s, err := j.String()
    if err == nil {
        return s
    }

    return def
}

func (j *Json) MustInt64() int64 {
    var ret int64
    var err error
    switch v := j.data.(type) {
    case int:
        ret = int64(v)
    case int64:
        ret = v
    case float64:
        ret = int64(v)
    case string:
        if ret, err = strconv.ParseInt(v, 10, 64); err != nil {
            panic(err)
        }
    default:
        ret = 0
        //panic("type assertion to int64 failed")
    }
    return ret
}

func (j *Json) MustFloat64() float64 {
    var ret float64
    var err error
    switch v := j.data.(type) {
    case int:
        ret = float64(v)
    case int64:
        ret = float64(v)
    case float64:
        ret = v
    case string:
        v = strings.Replace(v, ",", "", -1)
        if ret, err = strconv.ParseFloat(v, 64); err != nil {
            panic(err)
        }
    default:
        ret = 0
        //panic("type assertion to float64 failed")
    }
    return ret
}

type iBitgo struct {
    accessKey     string
    secretKey     string
    currency      string
    opCurrency    string
    baseCurrency  string
    secret        string
    secretExpires int64
    apiBase       string
    step          int64
    newRate       float64
    timeout       time.Duration
    timeLocation  *time.Location
}

type MapSorter []Item

type Item struct {
    Key string
    Val string
}

func NewMapSorter(m map[string]string) MapSorter {
    ms := make(MapSorter, 0, len(m))

    for k, v := range m {
        if strings.HasPrefix(k, "!") {
            k = strings.Replace(k, "!", "", -1)
        }
        ms = append(ms, Item{k, v})
    }

    return ms
}

func (ms MapSorter) Len() int {
    return len(ms)
}

func (ms MapSorter) Less(i, j int) bool {
    //return ms[i].Val < ms[j].Val // Sort by value
    return ms[i].Key < ms[j].Key // Sort by key
}

func (ms MapSorter) Swap(i, j int) {
    ms[i], ms[j] = ms[j], ms[i]
}

func encodeParams(params map[string]string, escape bool) string {
    ms := NewMapSorter(params)
    sort.Sort(ms)

    v := url.Values{}
    for _, item := range ms {
        v.Add(item.Key, item.Val)
    }
    if escape {
        return v.Encode()
    }
    var buf bytes.Buffer
    keys := make([]string, 0, len(v))
    for k := range v {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    for _, k := range keys {
        vs := v[k]
        prefix := k + "="
        for _, v := range vs {
            if buf.Len() > 0 {
                buf.WriteByte('&')
            }
            buf.WriteString(prefix)
            buf.WriteString(v)
        }
    }
    return buf.String()
}

func newBitgo(accessKey, secretKey string) *iBitgo {
    s := new(iBitgo)
    s.accessKey = accessKey
    s.secretKey = secretKey
    s.apiBase = "https://www.bitgo.cn"
    s.timeout = 20 * time.Second
    s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)

    return s
}

func (p *iBitgo) apiCall(method string) (*Json, error) {
    req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, method), nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    return NewJson(b)
}

func (p *iBitgo) GetTicker(symbol string) (ticker interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=market&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")
    ticker = map[string]interface{}{
        "time": js.Get("time").MustInt64(),
        "buy":  dic.Get("buy").MustFloat64(),
        "sell": dic.Get("sell").MustFloat64(),
        "last": dic.Get("last").MustFloat64(),
        "high": dic.Get("high").MustFloat64(),
        "low":  dic.Get("low").MustFloat64(),
        "vol":  dic.Get("vol").MustFloat64(),
    }
    return
}

func (p *iBitgo) GetDepth(symbol string) (depth interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=depth&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")

    asks := [][2]float64{}
    bids := [][2]float64{}

    for _, pair := range dic.Get("asks").MustArray() {
        arr := pair.([]interface{})
        asks = append(asks, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
    }

    for _, pair := range dic.Get("bids").MustArray() {
        arr := pair.([]interface{})
        bids = append(bids, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
    }
    depth = map[string]interface{}{
        "time": js.Get("time").MustInt64(),
        "asks": asks,
        "bids": bids,
    }
    return
}

func (p *iBitgo) GetTrades(symbol string) (trades interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=trades&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")
    items := []map[string]interface{}{}
    for _, pair := range dic.MustArray() {
        item := map[string]interface{}{}
        arr := pair.(map[string]interface{})
        item["id"] = toInt64(arr["id"])
        item["price"] = toFloat(arr["price"])
        item["amount"] = toFloat(arr["amount"])
        // trade.Time = toInt64(arr["time"]) * 1000
        if toString(arr["en_type"]) == "bid" {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        items = append(items, item)
    }
    trades = items
    return
}

func (p *iBitgo) GetRecords(step int64, symbol string) (records interface{}, err error) {
    var js *Json
    js, err = p.apiCall(fmt.Sprintf("action=kline&symbol=%s&step=%d", symbol, step*60))
    if err != nil {
        return
    }
    items := []interface{}{}
    for _, pair := range js.Get("data").MustArray() {
        arr := pair.([]interface{})
        if len(arr) < 6 {
            err = errors.New("response format error")
            return
        }
        item := [6]interface{}{}
        item[0] = toInt64(arr[0])
        item[1] = toFloat(arr[1])
        item[2] = toFloat(arr[2])
        item[3] = toFloat(arr[3])
        item[4] = toFloat(arr[4])
        item[5] = toFloat(arr[5])

        items = append(items, item)
    }
    records = items
    return
}

func (p *iBitgo) tapiCall(method string, params map[string]string) (js *Json, err error) {
    if params == nil {
        params = map[string]string{}
    }
    params["api_key"] = p.accessKey
    h := md5.New()
    h.Write([]byte(encodeParams(params, false) + "&secret_key=" + p.secretKey))
    params["sign"] = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
    params["action"] = method
    qs := encodeParams(params, false)

    req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, qs), nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    js, err = NewJson(b)
    if js != nil {
        if code := js.Get("code").MustInt64(); code != 200 {
            s := js.Get("msg").MustString()
            if s == "" {
                s = fmt.Sprintf("%v", toString(js.data))
            }
            return nil, errors.New(s)
        }
    }
    return js, err
}

func (p *iBitgo) GetAccount(symbol string) (account interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("userinfo", nil)
    if err != nil {
        return
    }
    mp := js.Get("data")
    assets := map[string]map[string]interface{}{}
    for k := range mp.MustMap() {
        dic := mp.Get(k)
        if k == "free" {
            for c := range dic.MustMap() {
                if _, ok := assets[c]; !ok {
                    assets[c] = map[string]interface{}{}
                }
                assets[c]["currency"] = c
                assets[c]["free"] = dic.Get(c).MustFloat64()
            }
        } else if k == "frozen" {
            for c := range dic.MustMap() {
                if _, ok := assets[c]; !ok {
                    assets[c] = map[string]interface{}{}
                }
                assets[c]["currency"] = c
                assets[c]["frozen"] = dic.Get(c).MustFloat64()
            }
        }
    }
    accounts := []map[string]interface{}{}
    for _, pair := range assets {
        accounts = append(accounts, pair)
    }

    account = accounts
    return
}

func (p *iBitgo) Trade(side string, price, amount float64, symbol string) (orderId interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("trade", map[string]string{
        "symbol": symbol,
        "type":   side,
        "price":  float2str(price),
        "amount": float2str(amount),
    })
    if err != nil {
        return
    }
    orderId = map[string]int64{"id": js.Get("orderId").MustInt64()}
    return
}

func (p *iBitgo) GetOrders(symbol string) (orders interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("entrust", map[string]string{"symbol": symbol})
    if err != nil {
        return
    }
    items := []map[string]interface{}{}
    for _, ele := range js.Get("data").MustArray() {
        mp := ele.(map[string]interface{})
        item := map[string]interface{}{}
        item["id"] = toInt64(mp["id"])
        item["amount"] = toFloat(mp["count"])
        if _, ok := mp["prize"]; ok {
            item["price"] = toFloat(mp["prize"])
        } else {
            item["price"] = toFloat(mp["price"])
        }
        item["deal_amount"] = toFloat(mp["success_count"])

        if toInt64(mp["type"]) == 0 {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        item["status"] = "open"
        items = append(items, item)
    }
    return items, nil
}

func (p *iBitgo) GetOrder(orderId int64, symbol string) (order interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("order", map[string]string{"id": toString(orderId)})
    if err != nil {
        return
    }

    found := false
    item := map[string]interface{}{}
    for _, ele := range js.Get("data").MustArray() {
        mp := ele.(map[string]interface{})
        if toInt64(mp["id"]) != orderId {
            continue
        }
        item["id"] = toInt64(mp["id"])
        item["amount"] = toFloat(mp["count"])
        if _, ok := mp["prize"]; ok {
            item["price"] = toFloat(mp["prize"])
        } else {
            item["price"] = toFloat(mp["price"])
        }
        item["deal_amount"] = toFloat(mp["success_count"])

        if toInt64(mp["type"]) == 0 {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        switch toInt64(mp["status"]) {
        case 1, 2:
            item["status"] = "open"
        case 3:
            item["status"] = "closed"
        case 4:
            item["status"] = "cancelled"
        }
        found = true
        break
    }
    if !found {
        return nil, errors.New("order not found")
    }
    return item, nil
}

func (p *iBitgo) CancelOrder(orderId int64, symbol string) (ret bool, err error) {
    _, err = p.tapiCall("cancel_entrust", map[string]string{"id": strconv.FormatInt(orderId, 10)})
    if err != nil {
        return
    }
    ret = true
    return
}

type RpcRequest struct {        // The fields in a struct must start with an uppercase letter, otherwise they cannot be parsed correctly. Structs can have exported and unexported fields, where fields starting with an uppercase letter are considered exported.
                                // During unmarshaling, the JSON tag of a struct is used to match and find the corresponding field. Therefore, modifiers are required in this case.
    AccessKey string            `json:"access_key"`
    SecretKey string            `json:"secret_key"`
    Nonce     int64             `json:"nonce"`
    Method    string            `json:"method"`
    Params    map[string]string `json:"params"`
}

func OnPost(w http.ResponseWriter, r *http.Request) {
    var ret interface{}
    defer func() {
        if e := recover(); e != nil {
            if ee, ok := e.(error); ok {
                e = ee.Error()
            }
            ret = map[string]string{"error": fmt.Sprintf("%v", e)}
        }
        b, _ := json.Marshal(ret)
        w.Write(b)
    }()

    b, err := ioutil.ReadAll(r.Body)
    if err != nil {
        panic(err)
    }
    var request RpcRequest
    err = json.Unmarshal(b, &request)
    if err != nil {
        panic(err)
    }
    e := newBitgo(request.AccessKey, request.SecretKey)
    symbol := request.Params["symbol"]
    if s := request.Params["access_key"]; len(s) > 0 {
        e.accessKey = s
    }
    if s := request.Params["secret_key"]; len(s) > 0 {
        e.secretKey = s
    }
    if symbolIdx, ok := map[string]int{
        "btc":  1,
        "ltc":  2,
        "etp":  3,
        "eth":  4,
        "etc":  5,
        "doge": 6,
        "bec":  7,
    }[strings.Replace(strings.ToLower(symbol), "_cny", "", -1)]; ok {
        symbol = toString(symbolIdx)
    }
    var data interface{}
    switch request.Method {
    case "ticker":
        data, err = e.GetTicker(symbol)
    case "depth":
        data, err = e.GetDepth(symbol)
    case "trades":
        data, err = e.GetTrades(symbol)
    case "records":
        data, err = e.GetRecords(toInt64(request.Params["period"]), symbol)
    case "accounts":
        data, err = e.GetAccount(symbol)
    case "trade":
        side := request.Params["type"]
        if side == "buy" {
            side = "0"
        } else {
            side = "1"
        }
        price := toFloat(request.Params["price"])
        amount := toFloat(request.Params["amount"])
        data, err = e.Trade(side, price, amount, symbol)
    case "orders":
        data, err = e.GetOrders(symbol)
    case "order":
        data, err = e.GetOrder(toInt64(request.Params["id"]), symbol)
    case "cancel":
        data, err = e.CancelOrder(toInt64(request.Params["id"]), symbol)
    default:
        if strings.HasPrefix(request.Method, "__api_") {
            data, err = e.tapiCall(request.Method[6:], request.Params)
        } else {
            panic(errors.New(request.Method + " not support"))
        }
    }
    if err != nil {
        panic(err)
    }
    ret = map[string]interface{}{
        "data": data,
    }

    return
}

func main() {
    var addr = flag.String("b", "127.0.0.1:6666", "bind addr")
    flag.Parse()
    if *addr == "" {
        flag.Usage()
        return
    }
    basePath := "/exchange"
    log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")
    http.HandleFunc(basePath, OnPost)
    http.ListenAndServe(*addr, nil)
}

もっと