Type/to search
8
Follow
1364
Followers
发明者通用协议合约接入示例
Discussions
Created 2021-09-14 15:00:01  Updated 2024-12-04 21:17:44
 2
 2176

img

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

最近有用户总聊起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

img

在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语言

mylang
/* 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) }
Related Recommendations
Comment
All comments (2)

    麻了,这也太难了

    4 years ago

    找个程序员,2天就帮你搞定了。

    4 years ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)