FMZ量化交易通用协议合约接入示例
admin
2023-07-31 14:31:54
0

目前不清退的交易所推荐:

1、全球第二大交易所OKX欧意

国区邀请链接: https://www.myts3cards.com/cn/join/1837888   币种多,交易量大!

国际邀请链接:https://www.okx.com/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!

2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183

全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031  币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!

买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3  CoinMarketCap前五的交易所,注册友好操简单快捷!

FMZ量化交易平台邀请链接:https://www.fmz.com/

发明者通用协议合约接入示例

最近有用户总聊起AOFEX这个Exchange,考虑到FMZ上还没有一篇关于如何对接合约Exchange的REST接口的例子。本篇就以接入AOFEX为例子,讲解如何接入合约Exchange。

在FMZ上对于现货交易所、期货交易所是有区分的。例如我们熟知的三大所,即有现货交易也有合约交易。这些不同的市场API接口也是不同的,甚至有些连API系统都是完全分离各自独立的。所以在FMZ上封装这些Exchange的时候是区分现货、期货的。

对于使用FMZ的通用协议封装现货Exchange在FMZ平台社区、文档已经有不少例子可以参考,但是封装一个合约Exchange还没有一个完整的例子。不过封装期货版的通用协议插件程序基本和现货的一样,仅仅是多了几个接口。

FMZ通用协议文档:https://www.fmz.com/bbs-topic/1052
文档中附带一个现货交易所通用协议接入DEMO

现货交易所对象和期货交易所对象需要封装的接口

现货交易所对象的主要接口

  • exchange.GetTicker()
    获取tick行情数据,现货期货都要有。
  • exchange.GetDepth()
    获取订单薄数据,现货期货都要有。
  • exchange.GetTrades()
    获取订单流数据(市场成交记录),现货期货都要有。
  • exchange.GetRecords()
    获取K线数据,现货期货都要有。
  • exchange.GetAccount()
    获取账户资产数据,现货期货都要有。
  • exchange.Buy()
    下买单,现货期货都要有。
  • exchange.Sell()
    下卖单,现货期货都要有。
  • exchange.GetOrder()
    获取指定ID的订单数据,现货期货都要有。
  • exchange.GetOrders()
    获取当前活动中的挂单,现货期货都要有。
  • exchange.CancelOrder()
    撤销指定ID的订单,现货期货都要有。

期货交易所对象除了需要封装现货交易所对象的这些接口,还需要额外封装期货用到的接口函数。

  • exchange.SetMarginLevel()
    设置当前品种的杠杆值。
  • exchange.SetDirection()
    设置当前品种的交易方向即:开多仓/开空仓/平多仓/平空仓。
  • exchange.SetContractType()
    设置当前合约代码。因为期货有永续合约(swap)、交割合约(quarter季度)等,在FMZ上定义的有各自的合约代码,具体可以查询FMZ API文档。封装的时候也需要遵循这些设置,否则已有的旧策略可能就无法正常运行了。
  • exchange.GetPosition()
    获取当前品种的持仓数据。这里可以看到现货是没有持仓这个概念的,现货只能通过对比账户变动来计算出逻辑上的持仓。但是期货是有持仓的。

策略中调用期货特有接口时托管者发送给通用协议插件程序的请求中的Body数据

以AOFEX交易所为例,假如在FMZ上配置通用协议的交易所对象时,填写的API KEY为:

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

16a8b566e63ab272473716a8b566e63ab2724737

在FMZ上通用协议配置页面有accessKeysecretKey输入框,配置了通用协议交易所对象之后,通用协议插件程序运行时收到的请求中Body的JSON格式数据的accessKey字段的值就是212f54a1-1c88-1bf5-54a1-f7bf52b3256csecret_key字段的值就是7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs

当调用以下接口时托管者会发出RPC请求给通用协议插件程序如下:

  • 当策略中调用exchange.SetMarginLevel(10)时,请求的Body中的数据为:
    {
        \"access_key\":\"212f54a1-1c88-1bf5-54a1-f7bf52b3256c\",
        \"method\":\"io\",
        \"nonce\":1631858961289247000,
        \"params\":{\"args\":[10],\"code\":0},   
        \"secret_key\":\"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs\"
    }
    

    调用exchange.SetMarginLevel(10),参数传入了10。

  • 当策略中调用exchange.SetDirection(\"buy\")时,请求的Body中的数据为:
    {    \"access_key\":\"212f54a1-1c88-1bf5-54a1-f7bf52b3256c\",    \"method\":\"io\",    \"nonce\":1631860438946922000,    \"params\":{\"args\":[\"buy\"],\"code\":1},      \"secret_key\":\"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs\"}

    调用exchange.SetDirection(\”buy\”),参数传入了\”buy\”。

  • 当策略中调用exchange.SetContractType(\"swap\")时,请求的Body中的数据为:
    {    \"access_key\":\"212f54a1-1c88-1bf5-54a1-f7bf52b3256c\",    \"method\":\"io\",    \"nonce\":1631860847525039000,    \"params\":{\"args\":[\"swap\"],\"code\":2},       \"secret_key\":\"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs\"}

    调用exchange.SetContractType(\”swap\”),参数传入了\”swap\”。

  • 当策略中调用exchange.GetPosition()时,请求的Body中的数据为:
    {    \"access_key\":\"212f54a1-1c88-1bf5-54a1-f7bf52b3256c\",    \"method\":\"io\",    \"nonce\":1631860996119505000,    \"params\":{\"args\":[],\"code\":3},      \"secret_key\":\"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs\"}

    调用exchange.GetPosition()时,没有传参数。

观察以上请求Body里的JSON数据发现:

  • 期货交易所对象特有的函数被调用时,请求的Body里的method字段值都是io
  • 区分这些函数需要从params字段中的code判断,即:
    • code0SetMarginLevel
    • code1SetDirection
    • code2SetContractType
    • code3GetPosition
  • 这些期货交易所对象特有的函数在被调用时,传入的参数都在请求Body中的params字段的args中。

相对于现货版的Go语言插件程序例子中,就需要在OnPost函数中做一下扩展:
参看以下代码中有「扩展期货交易所对象需要的函数」注释的位置。

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/SetContractType三个函数从字面意思可以看到是用来设置当前交易品种的相关配置的。其中SetDirection/SetContractType从设计上考虑,是设置本地变量记录当前下单方向(即下单的时候要读取这个设置,知道是要下什么方向的订单,原因就是期货买入有两个方向:开多和平空,所以需要区别)和当前合约代码(获取行情、订单等操作时明确查询的是哪个合约)。

SetMarginLevel需要根据交易所的杠杆机制具体设计(1、杠杆参数在下单接口中作为参数传递。2、交易所有设置杠杆接口)。

GetPosition是获取当前品种的持仓函数,当通用协议插件程序获取到交易所持仓接口返回的数据后,直接构造和FMZ上position一样的数据结构即可。

完整的AOFEX 期货通用协议插件程序例子:

Go语言

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

FMZ量化交易平台邀请链接:https://www.fmz.com/

全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031  币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!

买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3  CoinMarketCap前五的交易所,注册友好操简单快捷!

目前不清退的交易所推荐:

1、全球第二大交易所OKX欧意,邀请链接:https://www.myts3cards.com/cn/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!。

2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183

买好币上币库:https://www.kucoin.com/r/1f7w3

火必所有用户现在可用了,但是要重新注册账号火币:https://www.huobi.com

全球最大交易所币安,

国区邀请链接:https://accounts.suitechsui.mobi/zh-CN/register?ref=16003031 支持86手机号码,网页直接注册。

相关内容

热门资讯

Windows 11 和 10... Windows 11/10 文件夹属性中缺少共享选项卡 – 已修复 1.检查共享选项卡是否可用 右键...
事件 ID 7034:如何通过... 点击进入:ChatGPT工具插件导航大全 服务控制管理器 (SCM) 负责管理系统上运行的服务的活动...
Hive OS LOLMine... 目前不清退的交易所推荐: 1、全球第二大交易所OKX欧意 国区邀请链接: https://www.m...
Radmin VPN Wind... Radmin VPN 是一款免费且用户友好的软件,旨在牢固地连接计算机以创建一个有凝聚力的虚拟专用网...
如何修复 Steam 内容文件... Steam 内容文件锁定是当您的 Steam 文件无法自行更新时出现的错误。解决此问题的最有效方法之...
Hive OS 部署 PXE ... 目前不清退的交易所推荐: 1、全球第二大交易所OKX欧意 国区邀请链接: https://www.m...
如何在Instagram上扫描... 如何在Instagram上扫描名称标签/ QR? 总而言之,您可以通过大约四种不同的方法来扫描这些I...
在 Windows 11 中打... 什么是链路状态电源管理? 您可以在系统控制面板的电源选项中看到链接状态电源管理。它是 PCI Exp...
farols1.1.501.0... faro ls 1.1.501.0(64bit)可以卸载,是一款无需连接外部PC机或笔记本计算机即可...
Hive OS 新建飞行表的方... 目前不清退的交易所推荐: 1、全球第二大交易所OKX欧意 国区邀请链接: https://www.m...