Документация об использовании протокола
Доступ к любой из этих служб
APIСделки на биржах, без ограничений на конкретные API-протоколы, либоrest、websocket、fix... доступны для всех.
Пример общего протокола 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/DigitalAsset。DigitalAssetЭто имя, которое мы сами себе придумываем, и это лишь пример.На странице биржи для добавления количества в Inventor выглядит так:
Обычно для конфигурации счета на бирже требуется только конфигурацияaccess keyиsecret keyОднако некоторые API-интерфейсы бирж требуют передачи транзакционного пароля (например, для некоторых интерфейсов заказа), и в этом случае, поскольку на странице общих протоколов нет лишних элементов, чтобы написать эту информацию, мы можем написать лишнюю конфигурационную информацию, которая должна быть передана, когда мы сталкиваемся с API-интерфейсами таких бирж.secret keyЕсли информация не является конфиденциальной, можно написать:access key), а затем выполняет строку в плагин-программеsplitОперация, чтобы отделить эти данные, как показано в примере на рисункеЗатем обрабатываем встроенное, и получаем
XXX_PassWord。
Например, в полном примере в конце этого поста, функция newBitgo:mylangfunc 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Описание:mylangfunc 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, полученная в соответствии с плагинами GPL
requestВMethodМы можем использоватьswitchAPI для классификации количественного обработки различных изобретателей, то есть выявление политики, которая работает на хостинге, которая вызвана количественным изобретателемAPIВызов:GoПримеры языков:mylangswitch 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:
mylangdefer 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
В основном, это две категории:
- Публичные интерфейсы, которые не требуют подписи, такие как:
GetTicker()GetDepth()GetTrades()GetRecords(period)
...- пользовательский интерфейс, требующий подписи, например:
Buy、SellGetOrder(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Прямо сейчас1440、request.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Тип, обозначающий время <unk>。 2. Администратор автоматически задает время <unk>。 умноженное на 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.id、request.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):
mylangfmt.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"}
-
-
mylang
/*
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)
}
- 这段通用协议插件的代码,是不是就是实现的post、get和解析,同时对接到botvs的api?
是的。 - http://127.0.0.1:6666/DigitalAsset
这个地址是 代表 交易所对象, 托管者 的请求都会 发送到这个地址。 插件服务程序会 监听这个地址请求, 并作出响应,代替托管者去访问交易所,并返回 托管者 要求 的数据(格式),实现 ,策略机器人 兼容某个 BotVS 还没有接入的交易所。
bitmex交易所已经接入平台,但不管用 _C(exchange.GetTicker).Buy 还是 _C(exchange.GetDepth).Bids[0] 都无法取得买一价,而且还会报错。不知道是为什么
这上面的代码,就是通用协议插件是吧?这个插件不是类似于策略模版那种我直接可以调用?我会用py post或者get 发送和获得数据,并解析。这段通用协议插件的代码,是不是就是实现的post、get和解析,同时对接到botvs的api?编写好的 “通用协议插件” 的监听地址和端口设置这个 http://127.0.0.1:6666/DigitalAsset,实际上是不是类似于 https://www.bitmex.com/ 这种地址,机器人像这个地址发信息?
- 1






