이 GPL를 사용하여 모든 서비스에 액세스할 수 있습니다.
API거래 거래소, 특정 API 프로토콜, 또는rest、websocket、fix…에 접속할 수 있습니다. 파이썬 일반 프로토콜 예시: https://www.fmz.com/strategy/101399
잘 작성된 GNU/GPL 플러그인 의 감시 주소와 포트 설정.
예:http://127.0.0.1:6666/DigitalAsset또는:http://127.0.0.1:6666/exchange。
왜 이런 것들을 설치해야 할까요?IP、경로모직물?
왜냐하면발명가 양적페이지제어 센터점프거래소 추가페이지, 선택 GPL , 표시 제외API-KEY[서비스 주소]는 호스트가 어떤 IP와 포트를 사용해야 하는지를 알려줍니다. [서비스 주소]는 호스트와 GPL 플러그인 프로그램이 동일한 장치에서 실행되지 않을 수 있습니다.]http://127.0.0.1:6666/DigitalAsset。DigitalAsset이 글은 본인이 정의한 이름의 예입니다.
발명가의 양적 추가 거래소 페이지에는 다음과 같은 내용이 있습니다.
일반적으로, 거래소에서는 계정 정보를 설정할 때만 설정합니다.access key그리고secret key그러나 일부 거래소의 API 인터페이스는 거래 암호를 전송하도록 요구합니다 (예를 들어, 일부 거래소의 주문 인터페이스는), 이러한 정보를 작성하기 위해 GPRS 페이지에 불필요한 컨트롤이 없기 때문에 이러한 거래소의 API를 만났을 때, 우리는 전달해야 할 불필요한 구성 정보를 작성 할 수 있습니다.secret key이 정보에 민감하지 않은 경우access key), 그리고 GPL 플러그인에서 문자열을 만드는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
}

전체적인 GPL 플러그인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)
}
GPL 플러그인 프로그램은 지정된 포트를 계속 감시하고, 요청에 의해 전송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 有介绍说明。
}
*/
따라서, JSON 배열을 거꾸로 JSON 배열을 거꾸로 JSON 배열을 거꾸로 JSON 배열을 거꾸로 JSON 배열을 거꾸로 JSON 배열을 거꾸로request~ 안에Method우리는 이것을 사용할 수 있습니다switch다른 발명가들의 양적 API를 분류하기 위해, 즉, 어떤 발명가가 양적이라고 호칭되는 호스트에서 실행되는 정책을 식별하기 위해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:
...
이 분할들은 반환된 데이터를 실행한 후, GMCP 플러그인 프로그램에서 응답해야 하는 구조로 작성하고, 관리자의 요청에 응답한다.
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: 관리자가 로봇 페이지에 따라 설정한 통화로 전송한다.
호스트가 GNU의 플러그인을 요청할 때 주체가 가져갈 수 있는 데이터 형식 (JSON)
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // 毫秒时间戳
"method" : "ticker",
"params" : {"symbol" : "ETH_BTC"}, // 以ETH_BTC交易对举例
}
최종적으로 호스트에 전송된 반환값 구조: ((즉, GMCP 플러그인이 거래소 인터페이스를 요청한 후 데이터가 호스트에 반환되는 형식)
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: 관리자가 설정한 통화로 전송한다.
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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}
]
참고: 1, 2차원 배열의 첫 번째 요소는int타입, 시간。 2 ∙ 관리자는 자동으로 시간。 곱하기 1000, 이상으로 볼 수 있습니다。
이 함수에서 입력된 모든 함수들은request~ 안에method다음으로:depth
관리자가 보낸 변수:request.Params.symbol: 관리자가 설정한 정책에 따라 송금됩니다.
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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, 관리자가 설정한 정책에 따라 동전으로 전송한다.
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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
관리자가 보낸 변수: ((주의하십시오! 일반적으로 계좌의 모든 자산을 얻습니다! 거래소 인터페이스를 따라 개별적으로 또는 총 자산 정보를 얻습니다.)
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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: 관리자가 설정한 통화로 전송한다.
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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 (예: 예제) 관리자가 정책 설정에 따라 보낸 화폐
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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_시작되는 메소드 이름:
호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식
{
"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,request.Params
호스트가 보낸 요청의 몸체에서 데이터 해독 후의 요청: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요청 형식은 다른 인터페이스와 약간 다릅니다.RpcRequest이 형식의 주요 차이점은 params의 값이 복합 구조라는 것이다.
{"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"}
/*
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)
}