Доступ к любой из этих служб
APIСделки на биржах, без ограничений на конкретные API-протоколы, либоrest、websocket、fix… доступны для всех. Пример общего протокола Python: https://www.fmz.com/strategy/101399
Написать адрес и настройки портов для прослушивания встроенного модуля 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:
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)
}
Профилактика плагинов протоколов 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)) // 测试
}()
В основном, это две категории: 1. Публичные интерфейсы, которые не требуют подписи, такие как:
GetTicker()
GetDepth()
GetTrades()
GetRecords(period)
…
Buy、Sell
GetOrder(id)
GetOrders()
GetAccount()
CancelOrder(id)
…
Форма подписи может варьироваться в зависимости от биржи.
Некоторые из изобретателей измерили API-интерфейсы, такие как
GetName(),GetLabel()Функции, которые не будут вызваныПлагины общего протоколаОтправка запроса.exchange.GetName()Общепринятые плагины возвращают “Exchange” при вызове биржи.
ХозяинОтправка в функцию ответа на прослушивание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,
}
}
Администратор отправляет функцию ответа на прослушивание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Тип, обозначающий время 。 2. Администратор автоматически задает время 。 умноженное на 1000.
Администратор отправляет функцию ответа на прослушивание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], ... ],
}
}
Администратор отправляет функцию ответа на прослушивание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",
},{
...
}
]
}
Администратор отправляет функцию ответа на прослушивание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)
}
Администратор отправляет функцию ответа на прослушивание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也可以是字符串类型
}
}
Администратор отправляет функцию ответа на прослушивание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
}
}
Администратор отправляет функцию ответа на прослушивание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"
},{
...
}]
}
Администратор отправляет функцию ответа на прослушивание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
}
Администратор отправляет функцию ответа на прослушивание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.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Формат, отличающийся главным образом значениями парамов, представляет собой сложную структуру.
{"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 /* 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"))
}