最近有用户总聊起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
现货交易所对象的主要接口
期货交易所对象除了需要封装现货交易所对象的这些接口,还需要额外封装期货用到的接口函数。
swap)、交割合约(quarter季度)等,在FMZ上定义的有各自的合约代码,具体可以查询FMZ API文档。封装的时候也需要遵循这些设置,否则已有的旧策略可能就无法正常运行了。以AOFEX交易所为例,假如在FMZ上配置通用协议的交易所对象时,填写的API KEY为:


在FMZ上通用协议配置页面有accessKey和secretKey输入框,配置了通用协议交易所对象之后,通用协议插件程序运行时收到的请求中Body的JSON格式数据的accessKey字段的值就是212f54a1-1c88-1bf5-54a1-f7bf52b3256c,secret_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数据发现:
method字段值都是io。params字段中的code判断,即:
code为0是SetMarginLevel。code为1是SetDirection。code为2是SetContractType。code为3是GetPosition。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一样的数据结构即可。
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)
}
买好币上币库:https://www.kucoin.com/r/1f7w3