avatar of 发明者量化-小小梦 发明者量化-小小梦
Suivre Messages privés
4
Suivre
1271
Abonnés

Exemple d'accès au contrat d'accord général d'inventeur

Créé le: 2021-09-14 15:00:01, Mis à jour le: 2024-12-04 21:17:44
comments   2
hits   1802

Exemple d’accès au contrat d’accord général d’inventeur

Exemple d’accès au contrat d’accord général d’inventeur

Récemment, certains utilisateurs ont parlé deAOFEXCet échange, considérant qu’il n’y a aucun exemple sur FMZ sur la façon de se connecter à l’interface REST du contrat Exchange. Cet article se concentrera sur l’accèsAOFEXPrenez ceci comme exemple pour expliquer comment accéder au contrat Exchange.

Il existe une distinction entre les bourses au comptant et les bourses à terme sur FMZ. Par exemple, les trois principales bourses que nous connaissons proposent à la fois des opérations au comptant et des opérations sur contrats. Ces différentes interfaces API de marché sont également différentes, et certaines ont même des systèmes API complètement séparés et indépendants. Par conséquent, lors du regroupement de ces échanges sur FMZ, une distinction est faite entre le spot et les contrats à terme.

Il existe déjà de nombreux exemples d’utilisation du protocole général de FMZ pour encapsuler des échanges spot dans la communauté et les documents de la plateforme FMZ, mais il n’existe pas d’exemple complet d’encapsulation d’un échange de contrat. Cependant, le programme de plug-in de protocole général de la version à terme packagée est fondamentalement le même que la version spot, sauf qu’il dispose de quelques interfaces supplémentaires.

Document du protocole général de la FMZ : https://www.fmz.com/bbs-topic/1052 Le document est accompagné d’un accès au protocole général d’échange spot DEMO

Les interfaces qui repèrent les objets d’échange et les objets d’échange à terme doivent encapsuler

L’interface principale de l’objet d’échange spot

  • exchange.GetTicker() Obtenez des données sur le marché des ticks pour les marchés au comptant et à terme.

  • exchange.GetDepth() Obtenez les données du carnet de commandes pour les marchés au comptant et à terme.

  • exchange.GetTrades() Obtenir des données sur les flux d’ordres (enregistrements des transactions sur le marché), tant pour les marchés au comptant que pour les marchés à terme.

  • exchange.GetRecords() Pour obtenir des données K-line, des données au comptant et à terme sont nécessaires.

  • exchange.GetAccount() Obtenez des données sur les actifs du compte pour les marchés au comptant et à terme.

  • exchange.Buy() Pour passer un ordre d’achat, des ordres au comptant et à terme sont nécessaires.

  • exchange.Sell() Pour passer un ordre de vente, un ordre au comptant et à terme sont nécessaires.

  • exchange.GetOrder() Obtenez les données de commande de l’ID spécifié, au comptant et à terme.

  • exchange.GetOrders() Obtenez les commandes en attente actuellement actives, au comptant et à terme.

  • exchange.CancelOrder() Annulez la commande avec l’ID spécifié, à la fois pour le spot et les contrats à terme.

En plus d’encapsuler ces interfaces d’objets d’échange au comptant, les objets d’échange à terme doivent également encapsuler les fonctions d’interface utilisées par les contrats à terme.

  • exchange.SetMarginLevel() Définissez la valeur de l’effet de levier pour le symbole actuel.

  • exchange.SetDirection() Définissez la direction de négociation du produit actuel, c’est-à-dire ouvrir une position longue/ouvrir une position courte/fermer une position longue/fermer une position courte.

  • exchange.SetContractType() Définissez le code du contrat actuel. Parce que les contrats à terme ont des contrats perpétuels (swap)、Contrat de livraison(quarterTrimestriellement, etc., chacun a son propre code de contrat défini sur FMZ. Pour plus de détails, veuillez vous référer au document API FMZ. Ces paramètres doivent également être respectés lors de l’encapsulation, sinon les anciennes stratégies existantes risquent de ne pas fonctionner correctement.

  • exchange.GetPosition() Obtenez les données de position actuelles. Ici, nous pouvons voir qu’il n’existe pas de notion de position dans le trading au comptant. La position logique ne peut être calculée qu’en comparant les variations de compte. Mais les contrats à terme ont des positions.

Les données du corps de la requête envoyée par le dépositaire au plug-in du protocole universel lors de l’appel de l’interface spécifique aux contrats à terme dans la stratégie

En prenant l’exemple de l’échange AOFEX, si vous configurez l’objet d’échange du protocole général sur FMZ, la CLÉ API renseignée est :

  • accessKey : 212f54a1-1c88-1bf5-54a1-f7bf52b3256c
  • secretKey : 7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs

Exemple d’accès au contrat d’accord général d’inventeur

Sur la page de configuration générale du protocole FMZ, il y aaccessKeyetsecretKeyZone de saisie, après avoir configuré l’objet d’échange de protocole universel, les données au format JSON du corps de la requête reçue lorsque le plug-in de protocole universel est en cours d’exécutionaccessKeyLa valeur du champ est212f54a1-1c88-1bf5-54a1-f7bf52b3256csecret_keyLa valeur du champ est7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs

Lors de l’appel de l’interface suivante, l’hôte émettra une requête RPC au plug-in de protocole général comme suit :

  • Lorsque la politique est appeléeexchange.SetMarginLevel(10)Lorsque les données dans le corps de la requête sont :
  {
      "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
      "method":"io",
      "nonce":1631858961289247000,
      "params":{"args":[10],"code":0},   
      "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
  }

Appelez exchange.SetMarginLevel(10) avec 10 comme paramètre.

  • Lorsque la politique est appeléeexchange.SetDirection("buy")Lorsque les données dans le corps de la requête sont :
  {
      "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
      "method":"io",
      "nonce":1631860438946922000,
      "params":{"args":["buy"],"code":1},  
      "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
  }

Appelez exchange.SetDirection(“buy”) et transmettez “buy” comme paramètre.

  • Lorsque la politique est appeléeexchange.SetContractType("swap")Lorsque les données dans le corps de la requête sont :
  {
      "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
      "method":"io",
      "nonce":1631860847525039000,
      "params":{"args":["swap"],"code":2},   
      "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
  }

Appelez exchange.SetContractType(“swap”) et transmettez « swap » comme paramètre.

  • Lorsque la politique est appeléeexchange.GetPosition()Lorsque les données dans le corps de la requête sont :
  {
      "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
      "method":"io",
      "nonce":1631860996119505000,
      "params":{"args":[],"code":3},  
      "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
  }

Lors de l’appel de exchange.GetPosition(), aucun paramètre n’est passé.

Observez les données JSON dans le corps de la requête ci-dessus et constatez que :

  • Lorsqu’une fonction spécifique à un objet d’échange à terme est appelée, le corps de la requêtemethodLes valeurs du champ sontio
  • Distinguer ces fonctions nécessiteparamsDans le champcodeLe jugement, c’est-à-dire :
    • codepour0OuiSetMarginLevel
    • codepour1OuiSetDirection
    • codepour2OuiSetContractType
    • codepour3OuiGetPosition
  • Lorsque ces fonctions spécifiques à l’objet d’échange de contrats à terme sont appelées, les paramètres transmis se trouvent dans le corps de la requête.paramsChampargsmilieu.

Par rapport à l’exemple de plug-in de langage Go existant, vous devezOnPostFaire une extension dans la fonction : Voir le code suivant pour “Fonctions nécessaires pour étendre les objets d’échange à terme“L’emplacement du commentaire.

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" {              // 扩展期货交易所对象需要的函数
            code := toString(request.Params["code"])
            if code == "0" {            
                // 处理SetMarginLevel
                // ...
            } else if code == "1" {
                // 处理SetDirection
                // ...
            } else if code == "2" {     
                // 处理SetContractType
                // ...
            } else if code == "3" {     
                // 处理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
}

SetMarginLevel/SetDirection/SetContractTypeD’après le sens littéral, nous pouvons voir que les trois fonctions sont utilisées pour définir la configuration pertinente du produit commercial actuel. dansSetDirection/SetContractTypeD’un point de vue de conception, une variable locale est définie pour enregistrer la direction de la commande actuelle (c’est-à-dire que lors de la passation d’une commande, ce paramètre doit être lu pour savoir dans quelle direction la commande doit être passée. La raison en est qu’il existe deux directions pour achat à terme : ouverture à long terme et clôture à court terme. Il est donc nécessaire de faire la distinction entre le code du contrat en cours et le code du contrat en cours (quel contrat est interrogé lors de l’obtention d’informations sur le marché, de la passation d’ordres, etc.).

SetMarginLevelIl doit être conçu spécifiquement en fonction du mécanisme de levier de la bourse (1. Le paramètre de levier est transmis en tant que paramètre dans l’interface de commande. 2. La bourse dispose d’une interface pour définir l’effet de levier).

GetPositionIl s’agit d’une fonction permettant d’obtenir la position actuelle du produit. Lorsque le plug-in de protocole général obtient les données renvoyées par l’interface de position d’échange, il construit directement la fonction de position sur FMZ.positionLa même structure de données est suffisante.

Exemple complet de plug-in du protocole général des contrats à terme AOFEX :

Aller

/*
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)
}