4
Подписаться
1271
Подписчики

Универсальный протокол количественной торговой платформы изобретателя подключен к пользовательской бирже

Создано: 2017-08-24 16:29:56, Обновлено: 2021-06-10 15:33:02
comments   23
hits   17429

Документация об использовании протокола

Доступ к любой из этих службAPIСделки на биржах, без ограничений на конкретные API-протоколы, либоrestwebsocketfix… доступны для всех. Пример общего протокола Python: https://www.fmz.com/strategy/101399

  • ### 1. Запуск плагинов общего протокола, настройка портов

Написать адрес и настройки портов для прослушивания встроенного модуля GNU General Public License. Пример:http://127.0.0.1:6666/DigitalAssetИли:http://127.0.0.1:6666/exchange

Зачем это нужно?IPПутьШерстяная ткань? Потому что вКоличество изобретателейСтраницыЦентр управленияПеревернутьДобавление биржиСтраница, выбранная в ячейке “Общие протоколы”, отображается кромеAPI-KEY, а также коробка с адресом службы, которая указывает хостеру, к какому IP-адресу и порту он должен обратиться (хостеры и плагины протокола могут работать не на одном устройстве). Пример заполнения адреса службыhttp://127.0.0.1:6666/DigitalAssetDigitalAssetЭто имя, которое мы сами себе придумываем, и это лишь пример.

На странице биржи для добавления количества в Inventor выглядит так: Обычно для конфигурации счета на бирже требуется только конфигурацияaccess keyиsecret keyОднако некоторые API-интерфейсы бирж требуют передачи транзакционного пароля (например, для некоторых интерфейсов заказа), и в этом случае, поскольку на странице общих протоколов нет лишних элементов, чтобы написать эту информацию, мы можем написать лишнюю конфигурационную информацию, которая должна быть передана, когда мы сталкиваемся с API-интерфейсами таких бирж.secret keyЕсли информация не является конфиденциальной, можно написать:access key), а затем выполняет строку в плагин-программеsplitОперация, чтобы отделить эти данные, как показано в примере на рисунке

Универсальный протокол количественной торговой платформы изобретателя подключен к пользовательской бирже

Затем обрабатываем встроенное, и получаемXXX_PassWord。 Например, в полном примере в конце этого поста, функция 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
  }

Универсальный протокол количественной торговой платформы изобретателя подключен к пользовательской бирже

Плагины общего протоколаmainПримеры функций: GoОписание:

  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, ответная функция

Профилактика плагинов протоколов GNU Профилактика программы в постоянном прослушивании указанных портов, есть ли запросы на отправкуrequest❚ После того, как запрос выполняется, вызывается ответная функция, которая анализирует параметры в запросе, а хостинг отправляет следующие данные:

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

Таким образом, структура, полученная после секвенирования запроса body data в JSON, полученная в соответствии с плагинами GPLrequestВMethodМы можем использоватьswitchAPI для классификации количественного обработки различных изобретателей, то есть выявление политики, которая работает на хостинге, которая вызвана количественным изобретателемAPIВызов:

GoПримеры языков:

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

Эти подразделения выполняют выполненные и возвращенные данные в структуру, на которую должна ответить программа-плагин ГО, в ответ на запросы хозяина.

Пример языка 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))     // 测试
    }()
  • #### Типы вызовов API

В основном, это две категории: 1. Публичные интерфейсы, которые не требуют подписи, такие как:

GetTicker()

GetDepth()

GetTrades()

GetRecords(period)

  1. пользовательский интерфейс, требующий подписи, например:

BuySell

GetOrder(id)

GetOrders()

GetAccount()

CancelOrder(id) … Форма подписи может варьироваться в зависимости от биржи.

  • ### 4. Изобретатели количествуют вызовы APIПлагины общего протоколаиХозяинВзаимодействующий формат данных:

Некоторые из изобретателей измерили API-интерфейсы, такие какGetName()GetLabel()Функции, которые не будут вызваныПлагины общего протоколаОтправка запроса. exchange.GetName()Общепринятые плагины возвращают “Exchange” при вызове биржи.

  • 1、GetTicker: Для получения текущих данных.

    ХозяинОтправка в функцию ответа на прослушиваниеrequestВmethodДля:ticker

    Администратор прислал параметры:request.Params.symbol: отправка валюты администратором в зависимости от настроек страницы робота.

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин General Public License (GPL)

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

    Структура возвращаемой стоимости, которая в конечном итоге отправляется хозяину: ((то есть формат, в котором данные, полученные после запроса встроенного протокола обмена, возвращаются хозяину)

    Структура JSON

    {
        "data": {
            "time": 1500793319499,  // 毫秒时间戳,整型
            "buy": 1000,            // 以下浮点型
            "sell": 1001,
            "last": 1005,
            "high": 1100,
            "low": 980,
            "vol": 523,
        }
    }
    
  • 2、GetRecords:Для получения данных K-линий, предоставляемых биржей.

    Администратор отправляет функцию ответа на прослушиваниеrequestВmethodДля:records

    Администратор прислал параметры:request.Params.periodОтносительные значения:exchange.GetRecordsПервый параметр функции, фактическиrequest.Params.periodПериод, выраженный в минутах, например, дневной60*24Прямо сейчас1440request.Params.symbol: Администратор отправляет в соответствии с установленной валютой.

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

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

    Структура возвращаемой стоимости, которая в конечном итоге будет отправлена администратору:

    Структура 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],
                ...
        ]
    }
    

    Результаты тестирования на языке 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}
    }
    

    Количественная платформа для изобретателей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}
    ]
    

    Примечание: Первый элемент в двухмерной массивеintТип, обозначающий время 。 2. Администратор автоматически задает время 。 умноженное на 1000.

  • 3、GetDepth:Поиск информации об биржах в Интернете: “Заказы тонкие, продать один, продать два… купить один, купить два”…

    Администратор отправляет функцию ответа на прослушиваниеrequestВmethodДля:depth

    Администратор прислал параметры:request.Params.symbol: отправка монет администраторами в соответствии с установленной политикой

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

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

    Структура возвращаемой стоимости, которая в конечном итоге отправляется администратору:

    Структура 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:Доступ к отчетам о транзакциях, предоставляемым биржей, за определенное время на всей бирже (кроме собственных отчетов о транзакциях)

    Администратор отправляет функцию ответа на прослушиваниеrequestВmethodДля:trades

    Администратор прислал параметры:request.Params.symbolНапример:btc, в соответствии с политикой, установленной администратором.

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

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

    Структура возвращаемой стоимости, которая в конечном итоге отправляется администратору:

    Структура 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:Доступ к информации об активах счета

    Администратор отправляет функцию ответа на прослушиваниеrequestВmethodДля:accounts

    Параметры, отправленные администратором: ((Внимание! Обычно получают все активы счета!, в зависимости от интерфейса биржи, получают ли они информацию о совокупных активах отдельно или получают информацию о совокупных активах)

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "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" : {...}             // 可以写入插件访问交易所时,交易所返回的原始信息(response)
    }
    
  • 6、Buy、Sell:Отправка ордера, заказ сделки.

    Администратор отправляет функцию ответа на прослушиваниеrequestВmethodДля:trade

    Администратор прислал параметры:request.Params.typeПопечитель по вызову:exchange.BuyИлиexchange.SellОтправкаrequest.Params.priceВызывается в стратегии:APIПервый аргумент функцииrequest.Params.amountВызывается в стратегии:APIВторой параметр функцииrequest.Params.symbol: Администратор отправляет в соответствии с установленной валютой.

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

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

    Структура возвращаемой стоимости, которая в конечном итоге отправляется администратору:

    Структура JSON

    {
        "data": {
            "id": 125456,      // 下单后返回的订单id
                               // 如果订单id是"asdf346sfasf532"这样的字符串形式
                               // 此处id也可以是字符串类型
        }
    }
    
  • 7、GetOrder:Получить информацию о заказе по указанному номеру заказа

    Администратор отправляет функцию ответа на прослушиваниеrequestВmethodДля:order

    Администратор прислал параметры:request.Params.idrequest.Params.symbol

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

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

    Структура возвращаемой стоимости, которая в конечном итоге отправляется администратору:

    Структура 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、GetOrders: Получить информацию о всех незавершенных заказах

    Администратор отправляет функцию ответа на прослушиваниеrequestМетод в этом разделе:orders

    Администратор прислал параметры:request.Params.symbol

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

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

    Структура возвращаемой стоимости, которая в конечном итоге отправляется администратору:

    Структура JSON

    {
        "data": [{
            "id": 542156,
            "amount": 0.25,
            "price": 1005,
            "deal_amount": 0,
            "type": "buy",      // "buy"、"sell"
            "status": "open",   // "open"
        },{
            ...
        }]
    }
    
  • 9、CancelOrder: Отмена заказа с указанным номером заказа

    Администратор отправляет функцию ответа на прослушиваниеrequestВmethodДля:cancel

    Администратор прислал параметры:request.Params.id: тип стринга, первый параметр API-функции для вызова стратегии,request.Params.symbol:btc (пример) отправляется администратором в валюте, установленной в соответствии с политикой

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

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

    Структура возвращаемой стоимости, которая в конечном итоге отправляется администратору:

    Структура JSON

    {
        "data": true,        // true or false
    }
    
  • 10、IO: Функция exchange.IO для вызова платформы количественного измерения изобретателя

    Администратор отправляет функцию ответа на прослушиваниеrequestВmethodДля чего?__api_Название метода, с которого начинается

    Формат данных, который хостинг запрашивает, чтобы хозяин переносил при запросе на плагин протокола

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

    Структура возвращаемой стоимости, которая в конечном итоге отправляется администратору:

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

    Например, призыв к стратегии:

    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 Running http://127.0.0.1:6666/DigitalAsset

    Запрос. Метод, запрос. Параметры, напечатанные в командной строке плагина После анализа данных в корпусе запроса, отправленного администратором:request.MethodДля:__api_cancel_borrow После анализа данных в корпусе запроса, отправленного администратором:request.ParamsДля:{"borrow_id" : "123", "symbol" : "cny"}

    Настраиваемые обработки для прямого доступа к биржамAPIизexchange.IOВызов.

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

    Автоматическая обработка нижнего уровняexchange.GetRawJSONПризыв к использованию не требуется в плагинах.

  • Поддержка Exchange.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)
    

    Универсальный протокол количественной торговой платформы изобретателя подключен к пользовательской бирже

    Универсальный протокол количественной торговой платформы изобретателя подключен к пользовательской бирже

    # 注意:使用exchange.Go在wait的时候如果指定了超时时间, 
    #      一定要确保获取到最终的数据,这样申请的并发线程才能回收。
    
  • Поддержка фьючерсной функции

    Необходимо реализовать конкретную обработку в программе плагинов, например, настройка рычагов, контрактного кода, направления заказа, можно настроить локальную запись переменных, для получения позиции необходимо получить доступ к интерфейсу биржи, получить исходные данные и обработать и вернуть структуру позиции, определенную на платформе FMZ. Функции, которые получает плагин при вызове следующих функций в стратегии:RpcФормат запроса немного отличается от других интерфейсов, обратите внимание на то, что в программе плагинов General Public LicenseRpcRequestФормат, отличающийся главным образом значениями парамов, представляет собой сложную структуру.

    • SetContractType
      Настройка кода контракта.
      {"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"}
    
    • SetDirection Настройка срочных платежей в следующем направлении.
      {"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"}
    
    • SetMarginLevel Настройка фьючерсного лейвера.
      {"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"}
    
    • GetPosition Доступ к фьючерсным позициям. Когда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 с полным встроенным протоколом (доступ к биткойн-бирже)

”`go /* 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©.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©.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"))
    }