Anda dapat menggunakan protokol umum ini untuk mengakses setiap pertukaran yang menyediakan perdagangan API, protokol API tertentu tidak terbatas, apakah itu istirahat, websocket, memperbaiki... Semua dapat diakses untuk digunakan. Contoh protokol kustom Python:https://www.fmz.com/strategy/101399
Pengaturan untuk alamat mendengarkan dan port dari http://127.0.0.1:6666/DigitalAsset
atauhttp://127.0.0.1:6666/exchange
.
Mengapa kita perlu mengatur iniIPdanjalurApa yang kau lakukan?
Ini karena ketikamenambahkan pertukaranhalaman diFMZ Quant Platform Dashboard, dengan memilih opsi API-KEY
. Alamat layanan ini memberi tahu docker di mana untuk mengakses IP dan port (docker dan program plugin Custom Protocol mungkin tidak berjalan pada perangkat yang sama).http://127.0.0.1:6666/DigitalAsset
. DigitalAsset
hanya contoh dan dapat diganti dengan nama pilihan Anda.
Pada halaman access key
dansecret key
. Namun, beberapa pertukaran secret key
(atauaccess key
jika informasi tidak sensitif). Kemudian, dalam program Custom Protocol plugin, kita dapat melakukan stringsplit
operasi untuk memisahkan data ini, seperti yang ditunjukkan dalam gambar contoh.
Dan kemudian di plugin, memprosesnya untuk mendapatkanXXX_PassWord
Aku tidak tahu.
Misalnya, dalam contoh lengkap di akhir posting ini, dalam fungsi newBitgo:
func newBitgo(accessKey, secretKey string) *iBitgo {
s := new(iBitgo)
s.accessKey = accessKey
s.secretKey = secretKey
// Additional configuration information in the secretKey can be separated here and can be written as in the following comment
/*
arr := strings.SplitN(secretKey, ",", 2)
if len(arr) != 2 {
panic("Configuration error!")
}
s.secretKey = arr[0] // secret key
s.passWord = arr[1] // XXX_PassWord
*/
s.apiBase = "https://www.bitgo.cn"
s.timeout = 20 * time.Second
s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
return s
}
Parsing parameter dalam data permintaan lagi, docker mengirim data permintaan sebagai:
"secret_key" : "XXX",
Plugin menerima data masuk yang berisi informasi semacam ini dan memisahkan XXX_PassWord dari itu berdasarkan pemisah koma, sehingga ia mendapatkan data tambahan yang dilewatkan.
Contoh plugin protokol kustom secara keseluruhanmain
Fungsi:Go
Deskripsi bahasa:
func main(){
var addr = flag.String("b", "127.0.0.1:6666", "bing addr") // Set command line parameters, default value description, port setting 6666
flag.Parse() // Parsing the command line
if *addr == "" {
flag.Usage() // Display the command line description
return
}
basePath := "/DigitalAsset"
log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...") // Print listening port information
http.HandleFunc(basePath, OnPost)
http.ListenAndServe(*addr, nil)
}
Program request
Setelah permintaan diterima, ia memanggil fungsi respons untuk dijalankan dan kemudian menganalisis parameter dari data permintaan. Data permintaan yang dikirim oleh docker adalah:
/* JSON structure of request, FMZ Quant call GetTicker, docker sent to the custom protocol plugin case example (the value of params may be different for each API call, here the method is ticker):
{
"access_key" : "XXX", // `json:"access_key"`
"secret_key" : "XXX", // `json:"secret_key"`
"nonce" : "1502345965295519602", // `json:"nonce"`
"method" : "ticker", // `json:"method"`
"params" : { // `json:"params"`
"symbol" : "btc",
...
}, // The parameters are slightly different for each request. That is, different FMZ Quant APIs are called in the strategy with different parameters, which are described in the following sections for each API.
}
*/
Jadi, berdasarkanMethod
bidang direquest
struktur yang diperoleh oleh JSON deserializing permintaan Data tubuh diterima dalam program Universal Protocol Plugin, kita dapat menggunakanswitch
pernyataan untuk mengkategorikan dan menangani berbagai FMZ Quant API yang dipanggil pada docker (yaitu, mengidentifikasi yang FMZ QuantAPI
strategi yang berjalan di docker memanggil):
Contoh diGo
Bahasa:
switch request.Method { // M of request.Method is capitalized here, the body of the request received by the custom protocol program for the JSON data, in the Go language, the anti-JSON serialization (Unmarshal) is a structure, the first letter of the field must be capitalized
case "accounts" : // When the exchange.GetAccount() function is called in the bot strategy on the docker, the docker sends in a request where the Body carries data with a method attribute value of accounts
data, err = e.GetAccount(symbol)
case "ticker" :
data, err = e.GetTicker(symbol)
case "depth" :
data, err = e.GetDepth(symbol)
case "trades" :
data, err = e.GetTrades(symbol)
case "trade" :
...
default:
...
Setelah menjalankan cabang ini, data yang dikembalikan harus ditulis ke dalam struktur yang akan digunakan oleh program Custom Protocol Plugin untuk menanggapi permintaan docker.
Contoh dalam bahasa Go:
defer func(){ // Handling of closing work
if e := recover(); e != nil { // The recover() function is used to catch panic, e ! = nil, i.e. an error has occurred
if ee, ok := e.(error); ok { // Type derivation, successful derivation assigns ee.Error() to e
e = ee.Error() // Call the Error method to get the returned error string
}
ret = map[string]string{"error": fmt.Sprintf("%v", e)}
}
b, _ := json.Marshal(ret) // Encode the result obtained from this call, ret, assign it to b, and write it into the response pointer
w.Write(b)
//fmt.Println("after w.Write the b is", string(b)) // test
}()
Secara kasar dibagi menjadi dua kategori:
GetTicker()
GetDepth()
GetTrades()
GetRecords(period)
…
Buy
, Sell
GetOrder(id)
GetOrders()
GetAccount()
CancelOrder(id)
Aku tidak tahu.
Metode tanda tangan dapat bervariasi dari pertukaran ke pertukaran, dan perlu ditulis secara khusus sesuai dengan kebutuhan.
Beberapa antarmuka FMZ Quant API, seperti
GetName()
,GetLabel()
, dll, jangan mengirim permintaan kePlugin Protokol Khususketika dipanggil. Saat meneleponexchange.GetName()
, pertukaran yang dikonfigurasi dalam plugin universal akan mengembalikanExchange .
Peraturanmethod
dalamrequest
dikirim olehDockeruntuk fungsi respon mendengarkan adalahticker
.
Docker mengirim parameter:request.Params.symbol
, yang dikirim oleh docker berdasarkan mata uang yang ditetapkan pada halaman robot.
Format data (JSON) yang dibawa dalam request body ketika docker meminta Custom Protocol Plugin
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "ticker",
"params" : {"symbol" : "ETH_BTC"}, // Take the ETH_BTC trading pair for example
}
Struktur nilai pengembalian akhir yang dikirim ke docker: (yaitu format di mana data dikembalikan ke docker setelah antarmuka pertukaran diminta oleh plug-in protokol umum)
Struktur JSON
{
"data": {
"time": 1500793319499, // Millisecond timestamp, integer
"buy": 1000, // floating-point type as follows
"sell": 1001,
"last": 1005,
"high": 1100,
"low": 980,
"vol": 523,
}
}
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan adalahrecords
.
Docker mengirimkan parameter:request.Params.period
, yang terkait dengan parameter pertama dariexchange.GetRecords
fungsi.request.Params.period
periode dalam menit. Misalnya periode harian adalah60*24
, yang merupakan1440
. request.Params.symbol
dikirim oleh pelabuhan berdasarkan mata uang yang ditetapkan.
Format data yang dibawa dalam request body ketika docker meminta Custom Protocol Plugin.
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "records",
"params" : {"symbol" : "ETH_BTC", "period" : "1440"}, // Example of an ETH_BTC pair with a daily K-period
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
Struktur 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],
...
]
}
Data tes bahasa 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}
}
Platform Kuantum FMZLog
tampilanrecords
data:
[
{"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}
]
Catatan: 1. elemen pertama dalam array dua dimensi adalah jenisint
Docker akan secara otomatis mengalikan timestamp dengan 1000, seperti yang disebutkan di atas.
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan adalahdepth
.
Docker mengirim parameter:request.Params.symbol
, yang dikirim oleh pelabuhan berdasarkan mata uang yang ditetapkan dalam strategi.
Format data yang dibawa dalam badan permintaan ketika docker meminta Custom Protocol Plugin
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "depth",
"params" : {"symbol" : "ETH_BTC"}, // Take the ETH_BTC trading pair for example
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
Struktur JSON
{
"data" : {
"time" : 1500793319499,
"asks" : [ [1000, 0.5], [1001, 0.23], [1004, 2.1], ... ],
"bids" : [ [999, 0.25], [998, 0.8], [995, 1.4], ... ],
}
}
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan adalah:trades
.
Parameter yang dikirim oleh docker: Nilai darirequest.Params.symbol
adalah mata uang perdagangan, misalnya:btc
, yang dikirim oleh docker berdasarkan pengaturan strategi.
Format data yang dibawa oleh docker ketika meminta plugin protokol kustom adalah sebagai berikut
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "trades",
"params" : {"symbol" : "ETH_BTC"}, // Take the ETH_BTC trading pair for example
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
Struktur 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",
},{
...
}
]
}
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan adalah:accounts
.
Parameter yang dikirim oleh docker: (Catatan: Secara umum, untuk mendapatkan semua aset akun! Silakan lihat antarmuka pertukaran untuk melihat apakah untuk mendapatkan informasi aset individu atau total aset)
Format data yang dibawa oleh docker ketika meminta plugin protokol kustom adalah sebagai berikut
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "accounts",
"params" : {},
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
Struktur JSON
{
"data": [
{"currency": "btc", "free": 1.2, "frozen": 0.1},
{"currency": "ltc", "free": 25, "frozen": 2.1},
{"currency": "ltc", "free": 25, "frozen": 2.1},
...
],
"raw" : {...} // It is possible to write the raw message (response) returned by the exchange when the plugin accesses the exchange
}
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan adalah:trade
.
Parameter yang dikirim oleh docker:request.Params.type
: dikirim oleh docker berdasarkan apakah itu memanggilexchange.Buy
atauexchange.Sell
, request.Params.price
: parameter pertama dariAPI
fungsi yang dipanggil dalam strategi,request.Params.amount
: parameter kedua dariAPI
fungsi yang dipanggil dalam strategi,request.Params.symbol
: dikirim oleh pelabuhan berdasarkan mata uang perdagangan yang ditetapkan.
Format data yang dibawa oleh docker ketika meminta plugin protokol kustom adalah sebagai berikut
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "trade",
"params" : {
"symbol" : "ETH_BTC",
"type" : "buy",
"price" : "1000",
"amount" : "1"
}, // Example of an ETH_BTC trading pair, "type": "buy" buy request, price 1000, quantity 1
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
Struktur JSON
{
"data": {
"id": 125456, // Order id returned after placing an order
// If the order id is in the form of a string like "asdf346sfasf532"
// Here the id can also be a string type
}
}
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan adalah:order
.
Parameter yang dikirim oleh docker:request.Params.id
, request.Params.symbol
.
Format data yang dibawa oleh docker ketika meminta plugin protokol kustom adalah sebagai berikut
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "order",
"params" : {"symbol" : "ETH_BTC", "id" : "XXX"}, // Take the ETH_BTC trading pair and order ID XXX as an example. Please note that some exchanges use numerical order IDs, such as 123456, while others use string order IDs, such as poimd55sdfheqxv. The specific format of the order ID depends on the exchange.
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
Struktur JSON
{
"data": {
"id": 2565244,
"amount": 0.15,
"price": 1002,
"status": "open", // "open": pending, "closed": closed, "canceled": canceled
"deal_amount": 0,
"type": "buy", // "buy"、"sell"
"avg_price": 0, // If not provided by the exchange, it can be assigned a value of 0 during processing
}
}
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan adalahorders
.
Parameter yang dikirim oleh docker:request.Params.symbol
Format data yang dibawa oleh docker ketika meminta plugin protokol kustom adalah sebagai berikut
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "orders",
"params" : {"symbol" : "ETH_BTC"}, // Take the ETH_BTC trading pair for example
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
Struktur JSON
{
"data": [{
"id": 542156,
"amount": 0.25,
"price": 1005,
"deal_amount": 0,
"type": "buy", // "buy"、"sell"
"status": "open", // "open"
},{
...
}]
}
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan adalahcancel
.
Parameter yang dikirim oleh docker:request.Params.id
(jenis string, parameter pertama dari fungsi API yang dipanggil oleh strategi),request.Params.symbol
(misalnya, btc, dikirim oleh pelabuhan berdasarkan mata uang yang ditetapkan oleh strategi)
Format data yang dibawa oleh docker ketika meminta plugin protokol kustom adalah sebagai berikut
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "cancel",
"params" : {"symbol" : "ETH_BTC", "id" : "XXX"}, // Take an ETH_BTC trading pair with an id of "XXX" (same as the GetOrder function's parameter id) for example
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
Struktur JSON
{
"data": true, // true or false
}
Peraturanmethod
dalamrequest
dikirim oleh docker ke fungsi respons mendengarkan dimulai dengan_api_
.
Format data yang dibawa oleh docker ketika meminta plugin protokol kustom adalah sebagai berikut
{
"access_key" : "access_key",
"secret_key" : "secret_key",
"nonce" : "1500793319499", // millisecond timestamp
"method" : "__api_XXX", // XXX is the API interface for the specific exchange (base address not included)
"params" : {"borrow_id" : "123", "symbol" : "cny"}, // Specifically, the parameters passed into the IO function
}
Struktur nilai pengembalian akhir yang dikirim ke docker:
{
"data": {...} // The return value of a specific interface call
}
Sebagai contoh, ajakan strategi:
var io_str = exchange.IO("api", "POST", "cancel_borrow", "symbol=cny&borrow_id=123")
Kode pengujian di plugin (bahasa go):
fmt.Println("request.Method:", request.Method, "request.Params:", request.Params)
Plugin baris perintah: 2017/08/31 10:19:59 Berlarihttp://127.0.0.1:6666/DigitalAsset …
Printer baris perintah plugin dari: request.Method, request.ParamsDalam request body yang dikirim oleh docker, data yang dianalisis dalam request adalah sebagai berikut:request.Method
adalah__api_cancel_borrow
request.Params
adalah{"borrow_id" : "123", "symbol" : "cny"}
Anda dapat menyesuaikan penanganan iniexchange.IO
panggilan yang langsung mengakses pertukaranAPI
.
# Attention:
# When calling exchange.IO("api", "POST", "/api/v1/getAccount", "symbol=BTC_USDT"),
# If the second parameter is not POST but: exchange.IO("api", "GET", "/api/v1/getAccount", "symbol=BTC_USDT")
# is the GET method, which is then stored in the header Http-Method in the http request accepted by the custom protocol plugin.
# So you need to refer to the following sample code for the custom protocol handling IO function implementation:
// tapiCall function definition
func (p *iStocksExchange) tapiCall(method string, params map[string]string, httpType string) (js *Json, err error) {
...
}
// In the OnPost function
if strings.HasPrefix(request.Method, "__api_") {
var js *Json
js, err = e.tapiCall(request.Method[6:], request.Params, r.Header.Get("Http-Method"))
...
}
Sistem yang mendasari secara otomatis menangani panggilan keexchange.GetRawJSON
, jadi tidak perlu diimplementasikan di plugin.
Sistem yang mendasari secara otomatis menangani panggilan keexchange.Go
, jadi tidak perlu menangani itu di plugin.
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)
# Note: If you specify a timeout when waiting using exchange.
# Always make sure to obtain the final data so that the concurrent threads of the application can be reclaimed.
Anda perlu menerapkan penanganan khusus dalam program plugin untuk fungsi futures. Misalnya, mengatur leverage, kode kontrak, dan arah order. Anda dapat mengatur variabel lokal untuk merekam informasi ini. Untuk mengambil posisi, Anda perlu mengakses API pertukaran untuk mendapatkan data mentah dan memprosesnya ke dalam struktur posisi yang didefinisikan dalam platform FMZ, dan kemudian mengembalikannya.
Ketika fungsi berikut dipanggil dalam strategi, format dariRpc
Permintaan yang diterima oleh program plugin sedikit berbeda dari antarmuka lain.RpcRequest
Perbedaan utama adalah bahwa nilai param adalah struktur senyawa.
SetContractType Tetapkan kode kontrak.
{"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"}
SetDirection Menetapkan arah untuk menempatkan pesanan berjangka.
{"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"}
SetMarginLevel Menetapkan leverage futures.
{"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"}
DapatkanPosisi Dapatkan posisi berjangka. Kapan?
exchange.GetPosition()
disebut:
{"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":[],"code":3},"secret_key":"123"}
Kapan?exchange.GetPosition("swap")
disebut:
{"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 // Sort by value
return ms[i].Key < ms[j].Key // Sort by key
}
func (ms MapSorter) Swap(i, j int) {
ms[i], ms[j] = ms[j], ms[i]
}
func encodeParams(params map[string]string, escape bool) string {
ms := NewMapSorter(params)
sort.Sort(ms)
v := url.Values{}
for _, item := range ms {
v.Add(item.Key, item.Val)
}
if escape {
return v.Encode()
}
var buf bytes.Buffer
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
vs := v[k]
prefix := k + "="
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(prefix)
buf.WriteString(v)
}
}
return buf.String()
}
func newBitgo(accessKey, secretKey string) *iBitgo {
s := new(iBitgo)
s.accessKey = accessKey
s.secretKey = secretKey
s.apiBase = "https://www.bitgo.cn"
s.timeout = 20 * time.Second
s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
return s
}
func (p *iBitgo) apiCall(method string) (*Json, error) {
req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, method), nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return NewJson(b)
}
func (p *iBitgo) GetTicker(symbol string) (ticker interface{}, err error) {
var js *Json
js, err = p.apiCall("action=market&symbol=" + symbol)
if err != nil {
return
}
dic := js.Get("data")
ticker = map[string]interface{}{
"time": js.Get("time").MustInt64(),
"buy": dic.Get("buy").MustFloat64(),
"sell": dic.Get("sell").MustFloat64(),
"last": dic.Get("last").MustFloat64(),
"high": dic.Get("high").MustFloat64(),
"low": dic.Get("low").MustFloat64(),
"vol": dic.Get("vol").MustFloat64(),
}
return
}
func (p *iBitgo) GetDepth(symbol string) (depth interface{}, err error) {
var js *Json
js, err = p.apiCall("action=depth&symbol=" + symbol)
if err != nil {
return
}
dic := js.Get("data")
asks := [][2]float64{}
bids := [][2]float64{}
for _, pair := range dic.Get("asks").MustArray() {
arr := pair.([]interface{})
asks = append(asks, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
}
for _, pair := range dic.Get("bids").MustArray() {
arr := pair.([]interface{})
bids = append(bids, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
}
depth = map[string]interface{}{
"time": js.Get("time").MustInt64(),
"asks": asks,
"bids": bids,
}
return
}
func (p *iBitgo) GetTrades(symbol string) (trades interface{}, err error) {
var js *Json
js, err = p.apiCall("action=trades&symbol=" + symbol)
if err != nil {
return
}
dic := js.Get("data")
items := []map[string]interface{}{}
for _, pair := range dic.MustArray() {
item := map[string]interface{}{}
arr := pair.(map[string]interface{})
item["id"] = toInt64(arr["id"])
item["price"] = toFloat(arr["price"])
item["amount"] = toFloat(arr["amount"])
// trade.Time = toInt64(arr["time"]) * 1000
if toString(arr["en_type"]) == "bid" {
item["type"] = "buy"
} else {
item["type"] = "sell"
}
items = append(items, item)
}
trades = items
return
}
func (p *iBitgo) GetRecords(step int64, symbol string) (records interface{}, err error) {
var js *Json
js, err = p.apiCall(fmt.Sprintf("action=kline&symbol=%s&step=%d", symbol, step*60))
if err != nil {
return
}
items := []interface{}{}
for _, pair := range js.Get("data").MustArray() {
arr := pair.([]interface{})
if len(arr) < 6 {
err = errors.New("response format error")
return
}
item := [6]interface{}{}
item[0] = toInt64(arr[0])
item[1] = toFloat(arr[1])
item[2] = toFloat(arr[2])
item[3] = toFloat(arr[3])
item[4] = toFloat(arr[4])
item[5] = toFloat(arr[5])
items = append(items, item)
}
records = items
return
}
func (p *iBitgo) tapiCall(method string, params map[string]string) (js *Json, err error) {
if params == nil {
params = map[string]string{}
}
params["api_key"] = p.accessKey
h := md5.New()
h.Write([]byte(encodeParams(params, false) + "&secret_key=" + p.secretKey))
params["sign"] = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
params["action"] = method
qs := encodeParams(params, false)
req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, qs), nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
js, err = NewJson(b)
if js != nil {
if code := js.Get("code").MustInt64(); code != 200 {
s := js.Get("msg").MustString()
if s == "" {
s = fmt.Sprintf("%v", toString(js.data))
}
return nil, errors.New(s)
}
}
return js, err
}
func (p *iBitgo) GetAccount(symbol string) (account interface{}, err error) {
var js *Json
js, err = p.tapiCall("userinfo", nil)
if err != nil {
return
}
mp := js.Get("data")
assets := map[string]map[string]interface{}{}
for k := range mp.MustMap() {
dic := mp.Get(k)
if k == "free" {
for c := range dic.MustMap() {
if _, ok := assets[c]; !ok {
assets[c] = map[string]interface{}{}
}
assets[c]["currency"] = c
assets[c]["free"] = dic.Get(c).MustFloat64()
}
} else if k == "frozen" {
for c := range dic.MustMap() {
if _, ok := assets[c]; !ok {
assets[c] = map[string]interface{}{}
}
assets[c]["currency"] = c
assets[c]["frozen"] = dic.Get(c).MustFloat64()
}
}
}
accounts := []map[string]interface{}{}
for _, pair := range assets {
accounts = append(accounts, pair)
}
account = accounts
return
}
func (p *iBitgo) Trade(side string, price, amount float64, symbol string) (orderId interface{}, err error) {
var js *Json
js, err = p.tapiCall("trade", map[string]string{
"symbol": symbol,
"type": side,
"price": float2str(price),
"amount": float2str(amount),
})
if err != nil {
return
}
orderId = map[string]int64{"id": js.Get("orderId").MustInt64()}
return
}
func (p *iBitgo) GetOrders(symbol string) (orders interface{}, err error) {
var js *Json
js, err = p.tapiCall("entrust", map[string]string{"symbol": symbol})
if err != nil {
return
}
items := []map[string]interface{}{}
for _, ele := range js.Get("data").MustArray() {
mp := ele.(map[string]interface{})
item := map[string]interface{}{}
item["id"] = toInt64(mp["id"])
item["amount"] = toFloat(mp["count"])
if _, ok := mp["prize"]; ok {
item["price"] = toFloat(mp["prize"])
} else {
item["price"] = toFloat(mp["price"])
}
item["deal_amount"] = toFloat(mp["success_count"])
if toInt64(mp["type"]) == 0 {
item["type"] = "buy"
} else {
item["type"] = "sell"
}
item["status"] = "open"
items = append(items, item)
}
return items, nil
}
func (p *iBitgo) GetOrder(orderId int64, symbol string) (order interface{}, err error) {
var js *Json
js, err = p.tapiCall("order", map[string]string{"id": toString(orderId)})
if err != nil {
return
}
found := false
item := map[string]interface{}{}
for _, ele := range js.Get("data").MustArray() {
mp := ele.(map[string]interface{})
if toInt64(mp["id"]) != orderId {
continue
}
item["id"] = toInt64(mp["id"])
item["amount"] = toFloat(mp["count"])
if _, ok := mp["prize"]; ok {
item["price"] = toFloat(mp["prize"])
} else {
item["price"] = toFloat(mp["price"])
}
item["deal_amount"] = toFloat(mp["success_count"])
if toInt64(mp["type"]) == 0 {
item["type"] = "buy"
} else {
item["type"] = "sell"
}
switch toInt64(mp["status"]) {
case 1, 2:
item["status"] = "open"
case 3:
item["status"] = "closed"
case 4:
item["status"] = "cancelled"
}
found = true
break
}
if !found {
return nil, errors.New("order not found")
}
return item, nil
}
func (p *iBitgo) CancelOrder(orderId int64, symbol string) (ret bool, err error) {
_, err = p.tapiCall("cancel_entrust", map[string]string{"id": strconv.FormatInt(orderId, 10)})
if err != nil {
return
}
ret = true
return
}
type RpcRequest struct { // The fields in a struct must start with an uppercase letter, otherwise they cannot be parsed correctly. Structs can have exported and unexported fields, where fields starting with an uppercase letter are considered exported.
// During unmarshaling, the JSON tag of a struct is used to match and find the corresponding field. Therefore, modifiers are required in this case.
AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"`
Nonce int64 `json:"nonce"`
Method string `json:"method"`
Params map[string]string `json:"params"`
}
func OnPost(w http.ResponseWriter, r *http.Request) {
var ret interface{}
defer func() {
if e := recover(); e != nil {
if ee, ok := e.(error); ok {
e = ee.Error()
}
ret = map[string]string{"error": fmt.Sprintf("%v", e)}
}
b, _ := json.Marshal(ret)
w.Write(b)
}()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
var request RpcRequest
err = json.Unmarshal(b, &request)
if err != nil {
panic(err)
}
e := newBitgo(request.AccessKey, request.SecretKey)
symbol := request.Params["symbol"]
if s := request.Params["access_key"]; len(s) > 0 {
e.accessKey = s
}
if s := request.Params["secret_key"]; len(s) > 0 {
e.secretKey = s
}
if symbolIdx, ok := map[string]int{
"btc": 1,
"ltc": 2,
"etp": 3,
"eth": 4,
"etc": 5,
"doge": 6,
"bec": 7,
}[strings.Replace(strings.ToLower(symbol), "_cny", "", -1)]; ok {
symbol = toString(symbolIdx)
}
var data interface{}
switch request.Method {
case "ticker":
data, err = e.GetTicker(symbol)
case "depth":
data, err = e.GetDepth(symbol)
case "trades":
data, err = e.GetTrades(symbol)
case "records":
data, err = e.GetRecords(toInt64(request.Params["period"]), symbol)
case "accounts":
data, err = e.GetAccount(symbol)
case "trade":
side := request.Params["type"]
if side == "buy" {
side = "0"
} else {
side = "1"
}
price := toFloat(request.Params["price"])
amount := toFloat(request.Params["amount"])
data, err = e.Trade(side, price, amount, symbol)
case "orders":
data, err = e.GetOrders(symbol)
case "order":
data, err = e.GetOrder(toInt64(request.Params["id"]), symbol)
case "cancel":
data, err = e.CancelOrder(toInt64(request.Params["id"]), symbol)
default:
if strings.HasPrefix(request.Method, "__api_") {
data, err = e.tapiCall(request.Method[6:], request.Params)
} else {
panic(errors.New(request.Method + " not support"))
}
}
if err != nil {
panic(err)
}
ret = map[string]interface{}{
"data": data,
}
return
}
func main() {
var addr = flag.String("b", "127.0.0.1:6666", "bind addr")
flag.Parse()
if *addr == "" {
flag.Usage()
return
}
basePath := "/exchange"
log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")
http.HandleFunc(basePath, OnPost)
http.ListenAndServe(*addr, nil)
}