Contoh Perjanjian Akses Protokol Umum FMZ

Penulis:Ninabadass, Dicipta: 2022-04-12 11:36:00, Dikemas kini: 2022-04-12 11:44:03

Contoh Perjanjian Akses Protokol Umum FMZ

Baru-baru ini, sesetengah pengguna telah bercakap tentang PertukaranAOFEX. Memandangkan bahawa tidak ada contoh bagaimana untuk mengakses antara muka REST pertukaran kontrak di FMZ, dalam artikel ini, kita akan menggunakanAOFEXsebagai contoh untuk menerangkan cara mengakses Bursa Kontrak.

Terdapat perbezaan antara platform spot dan platform niaga hadapan di FMZ. Sebagai contoh, tiga platform terkenal mempunyai perdagangan spot dan perdagangan kontrak. Di pasaran yang berbeza ini, antara muka API juga berbeza, dan bahkan beberapa sistem API sama sekali berasingan dan bebas. Oleh itu, ketika merangkumi Bursa ini di FMZ, kita perlu membezakan spot dan niaga hadapan.

Terdapat banyak contoh di forum platform FMZ dan dokumentasi API untuk merangkumi Bursa spot dengan menggunakan protokol umum FMZ, tetapi belum ada contoh lengkap merangkumi Bursa kontrak.

Dokumen protokol umum FMZ:https://www.fmz.com/bbs-topic/9120DEMO akses protokol umum platform spot dilampirkan dalam dokumentasi.

Antara muka objek pertukaran spot dan niaga hadapan yang akan disempurnakan

Antara muka utama untuk objek pertukaran spot

  • pertukaran.GetTicker() Untuk mendapatkan tarikh tik, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran.GetDepth() Untuk mendapatkan data buku pesanan, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran.GetTrades() Untuk mendapatkan data aliran pesanan (rekod pelaksanaan pasaran), yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran.GetRecords() Untuk mendapatkan data K-line, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran.GetAccount ((() Untuk mendapatkan data aset akaun, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran.Beli ((() Untuk meletakkan pesanan beli, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran.Menjual() Untuk meletakkan pesanan jual, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran.GetOrder() Untuk mendapatkan data pesanan dengan ID tertentu, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran.GetOrders() Untuk mendapatkan pesanan yang menunggu dalam tindakan semasa, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

  • pertukaran. Batalkan Perintah ((() Untuk membatalkan pesanan dengan ID tertentu, yang diperlukan dalam kedua-dua spot dan niaga hadapan.

Objek pertukaran niaga hadapan perlu merangkumi fungsi antara muka tambahan yang digunakan oleh niaga hadapan, kecuali fungsi antara muka yang disebutkan di atas.

  • pertukaran.SetMarginLevel() Untuk menetapkan nilai leverage simbol semasa.

  • pertukaran.SetDirection() Untuk menetapkan arah perdagangan simbol semasa, iaitu: buka panjang/ buka pendek/ tutup panjang/ tutup pendek.

  • pertukaran.SetContractType() Untuk menetapkan kod kontrak semasa.swap), kontrak penghantaran (quarter) dan kontrak lain, mereka mempunyai kod kontrak mereka sendiri, dan anda boleh mencari dalam dokumentasi FMZ API untuk butiran.

  • pertukaran.GetPosition ((() Untuk mendapatkan data kedudukan simbol semasa. Di sini anda dapat melihat bahawa tidak ada konsep memegang kedudukan di tempat; tempat hanya boleh mengira kedudukan memegang dalam logik dengan membandingkan perubahan akaun; Walau bagaimanapun, niaga hadapan mempunyai kedudukan memegang.

Data badan dalam permintaan yang dihantar oleh Docker ke Program Plugin Protokol Am apabila memanggil antara muka khusus Masa Depan

Ambil AOFEX sebagai contoh. Jika mengkonfigurasi objek pertukaran protokol umum di FMZ, KEY API harus diisi sebagai:

  • aksesKey: 212f54a1-1c88-1bf5-54a1-f7bf52b3256c
  • Kunci rahsia: 7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs

img

Terdapat kotak input untukaccessKeydansecretKeyPada halaman konfigurasi protokol umum FMZ. Selepas objek pertukaran protokol umum dikonfigurasikan, apabila program pemalam protokol umum, semasa pengoperasiannya telah menerima permintaan, dan nilai medan JSON diformataccessKeydalam badan permintaan adalah212f54a1-1c88-1bf5-54a1-f7bf52b3256c, dan nilai medansecret_keyadalah7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs.

Apabila memanggil antara muka berikut, docker akan menghantar permintaan RPC ke pemalam protokol umum seperti berikut:

  • Apabila panggilanexchange.SetMarginLevel(10)Dalam strategi, data dalam badan yang meminta adalah:

    {
        "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
        "method":"io",
        "nonce":1631858961289247000,
        "params":{"args":[10],"code":0},   
        "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
    }
    

    Panggilan pertukaran.SetMarginLevel(10), dan lulus 10 sebagai parameter.

  • Apabila panggilanexchange.SetDirection("buy")Dalam strategi, data dalam badan yang meminta adalah:

    {
        "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
        "method":"io",
        "nonce":1631860438946922000,
        "params":{"args":["buy"],"code":1},  
        "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
    }
    

    Panggilan pertukaran.SetDirection ((buy), masukkan parameter buy.

  • Apabila panggilanexchange.SetContractType("swap")Dalam strategi, data dalam badan yang meminta adalah:

    {
        "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
        "method":"io",
        "nonce":1631860847525039000,
        "params":{"args":["swap"],"code":2},   
        "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
    }
    

    Panggilan pertukaran.SetContractType ((swap), masukkan parameter swap.

  • Apabila panggilanexchange.GetPosition()Dalam strategi, data dalam badan yang meminta adalah:

    {
        "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
        "method":"io",
        "nonce":1631860996119505000,
        "params":{"args":[],"code":3},  
        "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
    }
    

    Panggilan pertukaran.GetPosition ((), tiada parameter yang dihantar.

    Perhatikan data JSON dalam badan permintaan di atas, dan kita boleh melihat:

  • apabila fungsi tertentu untuk niaga hadapan dipanggil, nilai medanmethoddalam badan permintaan adalah semuaio.

  • Membezakan fungsi-fungsi ini perlu menilaicodedi lapanganparams, iaitu:

    • bila?codeadalah0, ia bermaksudSetMarginLevel.
    • bila?codeadalah1, ia bermaksudSetDirection.
    • bila?codeadalah2, ia bermaksudSetContractType.
    • bila?codeadalah3, ia bermaksudGetPosition.
  • apabila fungsi-fungsi tertentu untuk niaga hadapan dipanggil, parameter yang diteruskan adalah semua dalamargsdari medan medanparamsdalam badan permintaan.

Berbanding dengan versi spot contoh program plugin bahasa Go, ia adalah perlu untuk membuat sambungan dalamOnPostfungsi: Periksa tempat di mana terdapat komen fungsi yang diperlukan untuk memperluaskan objek pertukaran niaga hadapan dalam kod berikut.

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 := newZG(request.AccessKey, request.SecretKey)

    var symbol string 
    if _, ok := request.Params["symbol"]; ok {
        symbol = strings.ToUpper(request.Params["symbol"].(string))
    }

    var data interface{}
    switch request.Method {
    case "ticker":
        data, err = e.GetTicker(symbol, "GET")                                        
    case "depth":
        data, err = e.GetDepth(symbol, "GET")
    case "trades":
        data, err = e.GetTrades(symbol, "GET")
    case "records":
        data, err = e.GetRecords(toInt64(request.Params["period"]), symbol, "GET")    
    case "accounts":
        data, err = e.GetAccount(symbol, "GET")
    case "trade":
        side := request.Params["type"].(string)
        if side == "buy" {
            side = "BUY"
        } else {
            side = "SELL"
        }
        price := toFloat(request.Params["price"])
        amount := toFloat(request.Params["amount"])
        data, err = e.Trade(side, price, amount, symbol, "POST")
    case "orders":
        data, err = e.GetOrders(symbol, "POST")
    case "order":
        data, err = e.GetOrder(toString(request.Params["id"]), symbol, "POST")
    case "cancel":
        data, err = e.CancelOrder(toString(request.Params["id"]), symbol, "POST")
    default:
        if strings.HasPrefix(request.Method, "__api_") {
            params := map[string]interface{}{}
            for k, v := range request.Params {
                params[k] = toString(v)
            }
            data, err = e.tapiCall(request.Method[6:], params, "GET")
        } else if request.Method == "io" {              // extend the function required by the futures exchange object 
            code := toString(request.Params["code"])
            if code == "0" {            
                // process SetMarginLevel
                // ...
            } else if code == "1" {
                // process SetDirection
                // ...
            } else if code == "2" {     
                // process SetContractType
                // ...
            } else if code == "3" {     
                // process GetPosition
                // ...
            } else {
                panic(errors.New(request.Method + " not support"))    
            }
        } else {
            panic(errors.New(request.Method + " not support"))
        }
    }
    if err != nil {
        panic(err)
    }
    ret = map[string]interface{}{
        "data": data,
    }
    return
}

Ketiga fungsi, iaituSetMarginLevel, SetDirection, SetContractType, secara harfiah digunakan untuk menetapkan konfigurasi yang berkaitan dengan simbol dagangan semasa.SetDirection/SetContractTypeadalah direka untuk menetapkan pembolehubah tempatan untuk merakam arah pesanan semasa (iaitu, anda perlu membaca tetapan ini apabila meletakkan pesanan untuk mengetahui arah untuk meletakkan pesanan. alasannya adalah bahawa terdapat dua arah untuk membeli niaga hadapan: buka panjang dan menutup pendek, jadi ia perlu dibezakan) dan kod kontrak semasa (apabila mendapatkan data seperti sebut harga pasaran dan pesanan, anda perlu memastikan kontrak mana yang ditanyakan).

SetMarginLevelperlu direka khusus yang (1. parameter leverage boleh dihantar sebagai parameter dalam antara muka pesanan; 2. platform mempunyai antara muka untuk menetapkan leverage), mengikut mekanisme leverage platform.

GetPositionadalah fungsi untuk mendapatkan kedudukan simbol semasa; apabila program plugin protokol umum memperoleh data yang dikembalikan oleh antara muka kedudukan platform, terus membina struktur data yang sama denganpositiondi FMZ.

Lengkapkan AOFEX Futures Program Plugin Protokol Umum Contoh:

Pergi bahasa

/*
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build xxx.go
*/

package main

import (
    "bytes"
    "encoding/hex"
    "crypto/sha1"
    "encoding/json"
    "errors"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "sort"
    "strconv"
    "strings"
    "time"
    // proxy
    "golang.org/x/net/proxy"
    "crypto/tls"
    "math/rand"
)

var isUsedProxy bool = false    
var ct string = ""              
var direction string = "buy"    
var marginLevel float64 = 10    
var currSymbol string = ""      


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
    }
    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
    }
    return ret
}

type headerTuple struct {
    name  string
    value string
}

type Request struct {
    headers     []headerTuple
    Proxy       string
    Method      string
    Uri         string
    Body        interface{}
    QueryString interface{}
    Timeout     time.Duration
    ContentType string
    Accept      string
    Host        string
    UserAgent   string
}

func (r *Request) AddHeader(name string, value string) {
    if r.headers == nil {
        r.headers = []headerTuple{}
    }
    r.headers = append(r.headers, headerTuple{name: name, value: value})
}

type iAOFEX struct {
    accessKey     string
    secretKey     string
    clientID      string 
    currency      string
    opCurrency    string
    baseCurrency  string
    quoteCurrency string 
    apiBase       string
    timeout       time.Duration
    timeLocation  *time.Location

    // ext
    contractTypes []string
}

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].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 newiAOFEX(accessKey, secretKey string) *iAOFEX {
    s := new(iAOFEX)
    s.accessKey = accessKey
    s.secretKey = secretKey
    s.apiBase = "https://openapi-contract.aofex.info"
    s.timeout = 20 * time.Second
    s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
    s.contractTypes = []string{"swap"}
    return s
}

func (p *iAOFEX) isValidContractType(contractType string) bool {
    for _, t := range p.contractTypes {
        if contractType == t {
            return true
        }
    }
    return false
}

func (p *iAOFEX) calcContractTypeMap(currency string) (contractTypeMap map[string]string, err error) {
    var baseCurrency, quoteCurrency string 
    contractTypeMap = map[string]string{}
    if arr := strings.SplitN(currency, "_", 2); len(arr) == 2 {
        baseCurrency = arr[0]
        quoteCurrency = arr[1]
    } else {
        err = errors.New("symbol error!")
        return 
    }
    contractTypeMap["swap"] = fmt.Sprintf("%s-%s", strings.ToUpper(baseCurrency), strings.ToUpper(quoteCurrency))
    return
}

func (p *iAOFEX) apiCall(method string) (*Json, error) {
    req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", p.apiBase, method), nil)
    if err != nil {
        return nil, err
    }

    fmt.Printf("\n %c[1;44;32m%s%c[0m\n", 0x1B, "apiCall GET create req:" + fmt.Sprintf("%s%s", p.apiBase, method), 0x1B)
    fmt.Println("req:", req)                                                        
    req.Header.Set("Content-Type", "application/json;utf-8")

    // proxy
    strProxy := ""
    client := http.DefaultClient
    if isUsedProxy {
        var auth *proxy.Auth
        proxyAddr := strings.Split(strProxy, "//")[1]
        if strings.Contains(proxyAddr, "@") {
            arr := strings.SplitN(proxyAddr, "@", 2)
            arrAuth := strings.SplitN(arr[0], ":", 2)
            proxyAddr = arr[1]
            auth = &proxy.Auth{}
            auth.User = arrAuth[0]
            if len(arrAuth) == 2 {
                auth.Password = arrAuth[1]
            }
        }
        var dialer proxy.Dialer
        if dialer, err = proxy.SOCKS5("tcp", proxyAddr, auth, proxy.Direct); err == nil {
            client = &http.Client{
                Transport: &http.Transport{
                    Dial:                  dialer.Dial,
                    MaxIdleConnsPerHost:   5,
                    TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
                    ResponseHeaderTimeout: 20 * time.Second,
                },
                Timeout: 20 * time.Second,
            }
        } else {
            return nil, err
        }
    }


    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    var js *Json
    js, err = NewJson(b)
    if err != nil {
        return nil, err
    }

    // fault tolerant 
    if _, ok := js.data.(map[string]interface{}); ok {
        if code, ok := js.MustMap()["code"]; ok {
            if toString(code) != "0" {
                err = errors.New(fmt.Sprintf("%v", js.data))
            }
        }
    }    

    return js, err 
}

func (p *iAOFEX) GetTicker(symbol string) (ticker interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.apiCall(fmt.Sprintf("/openApi/contract/market?symbol=%s", realCt))
    if err != nil {
        return
    }

    depth, errDepth := p.GetDepth(symbol)
    if errDepth != nil {
        err = errDepth
        return 
    }

    ask1 := depth.(map[string]interface{})["asks"].([][2]float64)[0][0]
    bid1 := depth.(map[string]interface{})["bids"].([][2]float64)[0][0]
    mp := js.Get("result").MustMap()
    ticker = map[string]interface{}{
        "time": time.Now().UnixNano() / 1e6,
        "buy":  toFloat(bid1),
        "sell": toFloat(ask1),
        "last": toFloat(mp["close"]),
        "high": toFloat(mp["high"]),
        "low":  toFloat(mp["low"]),
        "vol":  toFloat(mp["vol"]),
    }
    return
}

func (p *iAOFEX) GetDepth(symbol string) (depth interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.apiCall(fmt.Sprintf("/openApi/contract/depth?symbol=%s", realCt))
    if err != nil {
        return
    }

    asks := [][2]float64{}
    bids := [][2]float64{}
    for _, pair := range js.Get("result").Get("asks").MustArray() {                            
        arr := pair.([]interface{})
        asks = append(asks, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
    }
    for _, pair := range js.Get("result").Get("bids").MustArray() {                            
        arr := pair.([]interface{})
        bids = append(bids, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
    }
    depth = map[string]interface{}{
        "time": js.Get("result").Get("ts").MustInt64(),
        "asks": asks,
        "bids": bids,
    }
    return
}

func (p *iAOFEX) GetTrades(symbol string) (trades interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.apiCall(fmt.Sprintf("/openApi/contract/trade?symbol=%s", realCt))
    if err != nil {
        return
    }

    items := []map[string]interface{}{}
    for _, pair := range js.Get("result").Get("data").MustArray() {
        item := map[string]interface{}{}
        mp := pair.(map[string]interface{})
        item["id"] = toString(mp["id"])
        item["price"] = toFloat(mp["price"])
        item["amount"] = toFloat(mp["amount"])
        item["time"] = toInt64(mp["ts"])
        if toString(mp["direction"]) == "buy" {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        items = append(items, item)
    }
    
    for i := 0; i < len(items); i++ {
        for j := 0; j < len(items)-i-1; j++ {
            if toInt64(items[j]["time"]) > toInt64(items[j+1]["time"]) {
                items[j], items[j+1] = items[j+1], items[j]
            }
        }
    }

    trades = items
    return
}

func (p *iAOFEX) GetRecords(step int64, symbol string) (records interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }
    
    var periodDict map[int64]string = map[int64]string{
        1 : "1min",
        5 : "5min",
        15 : "15min",
        30 : "30min",
        60 : "1hour",
        1440 : "1day",
    }
    period, okPeriod := periodDict[step]
    if !okPeriod {
        err = errors.New("period not support")
        return 
    }

    var js *Json
    js, err = p.apiCall(fmt.Sprintf("/openApi/contract/kline?symbol=%s&period=%s&size=500", realCt, period))
    if err != nil {
        return
    }

    items := []interface{}{}
    recordsData := js.Get("result").Get("data").MustArray()
    for i := len(recordsData) - 1 ; i >= 0 ; i-- {
        mp := recordsData[i].(map[string]interface{})
        item := [6]interface{}{}
        item[0] = toInt64(mp["id"])          // time
        item[1] = toFloat(mp["open"])        // open
        item[2] = toFloat(mp["high"])        // high
        item[3] = toFloat(mp["low"])         // low
        item[4] = toFloat(mp["close"])       // close
        item[5] = toFloat(mp["vol"])         // vol
        items = append(items, item)
    }
    records = items
    return
}

func JSON_Encode(d interface{}) string {
    buffer := &bytes.Buffer{}
    encoder := json.NewEncoder(buffer)
    encoder.SetEscapeHTML(false)
    encoder.Encode(d)
    return buffer.String()
}

func (p *iAOFEX) tapiCall(httpMethod string, method string, params map[string]string) (js *Json, err error) {
    if params == nil {
        params = map[string]string{}
    }

    nonce := toString(time.Now().UnixNano() / 1e9)
    strLetter := "124567890abcdefghijklmnopqrstuvwxyz"
    for i := 0 ; i < 5 ; i++ {
        rand.Seed(time.Now().UnixNano())
        nonce += string(strLetter[rand.Intn(len(strLetter))])
    }

    arrParams := []string{}
    arrParams = append(arrParams, p.accessKey)
    arrParams = append(arrParams, p.secretKey)
    arrParams = append(arrParams, nonce)

    for k, v := range params {
        arrParams = append(arrParams, fmt.Sprintf("%s=%s", k, v))
    }

    sort.Strings(arrParams)
    strSign := ""
    for _, ele := range arrParams {
        strSign += toString(ele)
    }
    h := sha1.New()
    h.Write([]byte(strSign))
    signature := hex.EncodeToString(h.Sum(nil))

    strUrl := fmt.Sprintf("%s%s", p.apiBase, method)
    if len(params) > 0 {
        strUrl = fmt.Sprintf("%s%s?%s", p.apiBase, method, encodeParams(params, false))
    }

    req, err := http.NewRequest(httpMethod, strUrl, nil)
    if err != nil {
        return nil, err
    }
    req.Header.Add("Nonce", nonce)
    req.Header.Add("Token", p.accessKey)
    req.Header.Add("Signature", signature)
    
    strProxy := ""
    client := http.DefaultClient
    if isUsedProxy {
        var auth *proxy.Auth
        proxyAddr := strings.Split(strProxy, "//")[1]
        if strings.Contains(proxyAddr, "@") {
            arr := strings.SplitN(proxyAddr, "@", 2)
            arrAuth := strings.SplitN(arr[0], ":", 2)
            proxyAddr = arr[1]
            auth = &proxy.Auth{}
            auth.User = arrAuth[0]
            if len(arrAuth) == 2 {
                auth.Password = arrAuth[1]
            }
        }
        var dialer proxy.Dialer
        if dialer, err = proxy.SOCKS5("tcp", proxyAddr, auth, proxy.Direct); err == nil {
            client = &http.Client{
                Transport: &http.Transport{
                    Dial:                  dialer.Dial,
                    MaxIdleConnsPerHost:   5,
                    TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
                    ResponseHeaderTimeout: 20 * time.Second,
                },
                Timeout: 20 * time.Second,
            }
        } else {
            return nil, err
        }
    }

    fmt.Printf("\n %c[1;44;32m%s%c[0m\n", 0x1B, "apiCall GET create req:" + fmt.Sprintf("%s%s", p.apiBase, method), 0x1B)
    fmt.Println("tapiCall req:", req)  

    resp, err := client.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 err != nil {
        return nil, err
    }

    // fault tolerant 
    if mp, ok := js.data.(map[string]interface{}); ok {
        if errno, ok := mp["errno"]; !ok || toString(errno) != "0" {
            err = errors.New(fmt.Sprintf("%v", js.data))    
        }
    } else {
        err = errors.New(fmt.Sprintf("%v", js.data))
    }

    return js, err
}

func (p *iAOFEX) GetAccount(symbol string) (account interface{}, err error) { 
    symbol = currSymbol
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.tapiCall("GET", "/openApi/contract/walletList", nil)
    if err != nil {
        return
    }

    assets := map[string]map[string]interface{}{}
    for _, ele := range js.Get("result").MustArray() {
        dic := ele.(map[string]interface{})
        if realCt != toString(dic["symbol"]) {
            continue
        }
        arr := strings.SplitN(toString(dic["symbol"]), "-", 2)
        if arr[1] == "USDT" {
            if _, ok := assets[arr[1]]; !ok {
                assets[arr[1]] = map[string]interface{}{}
            }
            assets[arr[1]]["currency"] = arr[1]
            assets[arr[1]]["Info"] = dic
            assets[arr[1]]["free"] = toFloat(dic["avail"])
            assets[arr[1]]["frozen"] = toFloat(dic["frozen"])
        }
    }

    accounts := []map[string]interface{}{}
    for _, pair := range assets {
        accounts = append(accounts, pair)
    }

    account = accounts
    return
}

func (p *iAOFEX) Trade(side string, price, amount float64, symbol string) (orderId interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    params := map[string]string{}
    
    if direction == "buy" {
        params["contract_type"] = "open"
    } else if direction == "sell" {
        params["contract_type"] = "open"
    } else if direction == "closebuy" {
        params["contract_type"] = "close" 
    } else if direction == "closesell" { 
        params["contract_type"] = "close"
    } else {
        err = errors.New("invalid direction!")
        return 
    }

    if (side == "buy-limit" || side == "buy-market") && (direction == "sell" || direction == "closebuy") {
        err = errors.New("invalid direction!")
        return 
    } else if (side == "sell-limit" || side == "sell-market") && (direction == "buy" || direction == "closesell") {
        err = errors.New("invalid direction!")
        return 
    }

    params["type"] = side
    params["lever_rate"] = toString(marginLevel)
    params["amount"] = toString(amount)
    params["symbol"] = realCt
    if price > 0 {
        params["price"] = toString(price)
    }

    var js *Json
    js, err = p.tapiCall("POST", "/openApi/contract/add", params)
    if err != nil {
        return
    }

    orderId = map[string]string{"id": toString(js.MustMap()["result"])}
    return
}

func (p *iAOFEX) GetOrders(symbol string) (orders interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    from := ""
    limit := 100
    items := []map[string]interface{}{}
    for {
        params := map[string]string{
            "symbol" : realCt, 
            "limit" : toString(limit),             
        }
        if from != "" {
            params["from"] = toString(from)
        }

        var js *Json
        js, err = p.tapiCall("GET", "/openApi/contract/currentList", params)
        if err != nil {
            return
        }

        arr := js.Get("result").MustArray()
        for _, ele := range arr {
            mp := ele.(map[string]interface{})
            item := map[string]interface{}{}
            item["id"] = toString(mp["order_id"])
            item["amount"] = toFloat(mp["amount"])
            item["price"] = toFloat(mp["price"])
            item["deal_amount"] = toFloat(mp["deal_amount"])
            item["avg_price"] = toFloat(mp["price_avg"])        
            if toString(mp["type"]) == "buy-limit" || toString(mp["type"]) == "buy-market" || toString(mp["type"]) == "buy-tactics" || toString(mp["type"]) == "buy-market-tactic" || toString(mp["type"]) == "buy-plan" || toString(mp["type"]) == "buy-market-plan" {
                item["type"] = "buy"
            } else {
                item["type"] = "sell"
            }
            item["status"] = "open"
            item["contract_type"] = ct
            items = append(items, item)
            from = toString(mp["order_id"])
        }

        if len(arr) < limit {
            break
        }
    }
    return items, nil
}

func (p *iAOFEX) GetOrder(orderId string, symbol string) (order interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.tapiCall("GET", "/openApi/contract/historyList", map[string]string{
        "from" : toString(orderId),
        "symbol" : realCt, 
        "limit" : "1",
    })
    if err != nil {
        return
    }

    item := map[string]interface{}{}
    for _, ele := range js.Get("result").MustArray() {
        mp := ele.(map[string]interface{})
        if realCt != toString(mp["symbol"]) || toString(mp["order_id"]) != toString(orderId) {
            continue
        }
        item["id"] = toString(mp["order_id"])
        item["amount"] = toFloat(mp["amount"])
        item["price"] = toFloat(mp["price"])
        item["deal_amount"] = toFloat(mp["deal_amount"])
        item["avg_price"] = toFloat(mp["price_avg"])
        item["contract_type"] = ct
        if toString(mp["type"]) == "buy-limit" || toString(mp["type"]) == "buy-market" || toString(mp["type"]) == "buy-tactics" || toString(mp["type"]) == "buy-market-tactic" || toString(mp["type"]) == "buy-plan" || toString(mp["type"]) == "buy-market-plan" {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }    

        switch toString(mp["status"]) {
        case "1", "2":
            item["status"] = "open"
        case "3":
            item["status"] = "closed"
        case "4", "5", "6":
            item["status"] = "cancelled"
        }
        return item, nil
    }

    err = errors.New("order not found")
    return 
}

func (p *iAOFEX) CancelOrder(orderId string, symbol string) (ret bool, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    _, err = p.tapiCall("POST", "/openApi/contract/cancel", map[string]string{
        "order_ids" : toString(orderId), 
        "symbol" : realCt, 
    })
    if err != nil {
        return
    }

    ret = true
    return
}

type RpcRequest struct {                                        
    AccessKey string            `json:"access_key"`
    SecretKey string            `json:"secret_key"`
    Nonce     int64             `json:"nonce"`
    Method    string            `json:"method"`
    Params    map[string]interface{} `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 := newiAOFEX(request.AccessKey, request.SecretKey)
    symbol := strings.ToUpper(toString(request.Params["symbol"]))
    if _, ok := request.Params["symbol"]; ok {        
        currSymbol = symbol        
    }

    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 := toString(request.Params["type"])
        if side == "buy" {
            side = "buy-limit"
            if toFloat(request.Params["price"]) <= 0 {
                side = "buy-market"
            }
        } else {
            side = "sell-limit"
            if toFloat(request.Params["price"]) <= 0 {
                side = "sell-market"
            }
        }
        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(toString(request.Params["id"]), symbol)
    case "cancel":
        data, err = e.CancelOrder(toString(request.Params["id"]), symbol)
    default:
        if strings.HasPrefix(request.Method, "__api_") {  
            params := map[string]string{}
            for k, v := range request.Params {
                params[k] = toString(v)
            }
            data, err = e.tapiCall("GET", request.Method[6:], params)
        } else if request.Method == "io" {
            code := toString(request.Params["code"])
            if code == "0" {            
                if args, ok := request.Params["args"].([]interface{}); ok && len(args) == 1 {
                    marginLevel = toFloat(args[0])
                } else {
                    err = errors.New(fmt.Sprintf("%v", request.Params))
                }
            } else if code == "1" {
                if args, ok := request.Params["args"].([]interface{}); ok && len(args) == 1 {
                    orderDirection := toString(args[0])
                    if orderDirection == "buy" || orderDirection == "sell" || orderDirection == "closebuy" || orderDirection == "closesell" {
                        direction = orderDirection
                        data = orderDirection
                    } else {
                        err = errors.New(fmt.Sprintf("not support orderDirection: %s", orderDirection))
                    }
                } else {
                    err = errors.New(fmt.Sprintf("%v", request.Params))
                }
            } else if code == "2" {     
                if args, ok := request.Params["args"].([]interface{}); ok && len(args) == 1 {
                    contractType := toString(args[0])
                    if e.isValidContractType(contractType) {
                        ct = contractType
                        data = contractType
                    } else {
                        err = errors.New(fmt.Sprintf("not support contractType: %s", contractType))
                    }
                } else {
                    err = errors.New(fmt.Sprintf("%v", request.Params))
                }
            } else if code == "3" {     
                symbol := currSymbol
                mpCt, mpCtErr := e.calcContractTypeMap(symbol)
                if mpCtErr != nil {
                    panic(mpCtErr)
                }
                realCt, ok := mpCt[ct]
                if !ok {
                    err = errors.New("invalid contractType!")
                    panic(err)
                }
                var js *Json
                js, err = e.tapiCall("GET", "/openApi/contract/position", map[string]string{
                    "symbol" : realCt, 
                })
                if err != nil {
                    panic(err)
                }

                items := []map[string]interface{}{}
                for _, ele := range js.Get("result").MustArray() {
                    mp := ele.(map[string]interface{})
                    item := map[string]interface{}{}
                    item["MarginLevel"] = toFloat(mp["lever_rate"])
                    item["Amount"] = toFloat(mp["amount"])
                    item["FrozenAmount"] = toFloat(mp["contract_frozen"])
                    item["Price"] = toFloat(mp["open_price_avg"])
                    item["Profit"] = toFloat(mp["un_profit"])
                    if toString(mp["type"]) == "1" {
                        item["Type"] = 0
                    } else {
                        item["Type"] = 1
                    }
                    item["ContractType"] = ct
                    item["Margin"] = toFloat(mp["bood"])
                    items = append(items, item)
                }
                data = items
            } else {
                panic(errors.New(request.Method + " not support"))    
            }
        } 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:6617", "bind addr")   
    flag.Parse()
    if *addr == "" {
        flag.Usage()
        return
    }
    basePath := "/AOFEX"
    log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")
    http.HandleFunc(basePath, OnPost)
    http.ListenAndServe(*addr, nil)
}

Lebih lanjut