Recentemente, alguns utilizadores têm falado sobre a troca deAOFEX
Considerando que não há nenhum exemplo de como acessar a interface REST do contrato Exchange em FMZ, neste artigo, vamos usarAOFEX
como um exemplo para explicar como aceder à bolsa de contratos.
Há uma distinção entre as plataformas spot e as plataformas de futuros no FMZ. Por exemplo, as três plataformas conhecidas têm negociação spot e negociação de contratos. Nesses diferentes mercados, as interfaces de API também são diferentes, e até mesmo alguns sistemas de API são completamente separados e independentes. Portanto, ao encapsular essas bolsas no FMZ, precisamos distinguir spot e futuros.
Já existem muitos exemplos no fórum da plataforma FMZ e na documentação da API para encapsular trocas spot usando o protocolo geral FMZ, mas ainda não há um exemplo completo de encapsular uma troca de contrato.
Documentação do protocolo geral da FMZ:https://www.fmz.com/bbs-topic/9120A documentação inclui um DEMO de acesso de um protocolo geral de plataforma spot.
Interfaces principais para os objectos de troca spot
troca.GetTicker ((() Para obter a data do tick, que é necessária tanto no spot quanto nos futuros.
troca.GetDepth ((() Obter dados da carteira de pedidos, necessários tanto no momento como nos futuros.
Troca.GetTrades ((() Obter dados de fluxo de pedidos (registro de execução de mercado), necessários tanto no mercado à vista como no mercado futuros.
Troca.GetRecords ((() Para obter dados da linha K, que são necessários tanto no spot como nos futuros.
troca.GetAccount ((() Para obter os dados dos activos da conta, que são necessários tanto no momento como nos futuros.
troca.Comprar() Para colocar ordens de compra, que é necessária tanto no local e futuros.
Troca.Venda. Para colocar ordens de venda, que são necessárias tanto no spot como nos futuros.
troca.GetOrder ((() Para obter os dados da ordem com um ID especificado, que é necessário tanto no spot quanto nos futuros.
troca.GetOrders ((() Para obter as ordens pendentes na ação atual, que é necessária tanto no local e futuros.
troca.Cancelar encomenda ((() Para cancelar a ordem com uma identificação especificada, que é necessária tanto no spot quanto nos futuros.
Os objetos de troca de futuros precisam encapsular funções de interface adicionais utilizadas pelos futuros, exceto as funções de interface mencionadas acima.
troca.SetMarginLevel() Para definir o valor de alavancagem do símbolo atual.
troca.SetDirection (() Para definir a direção de negociação do símbolo atual, nomeadamente: abrir longo/abrir curto/fechar longo/fechar curto.
exchange.SetContractType (()
Para os futuros tem contratos perpétuosswap
), contratos de entrega (quarter
) e outros contratos, eles têm seus próprios códigos de contrato, e você pode pesquisar na documentação FMZ API para detalhes.
troca.GetPosition ((() Para obter os dados de posição do símbolo atual. Aqui você pode notar que não há tal conceito de posições de detenção no local; local só pode calcular as posições de detenção na lógica, comparando as alterações da conta; no entanto, os futuros tem posições de detenção.
Se configurar os objetos de troca do protocolo geral no FMZ, a API KEY deve ser preenchida como:
Há caixas de entrada paraaccessKey
esecretKey
Após o objeto de troca de protocolo geral é configurado, quando o programa de plug-in de protocolo geral, durante a sua operação recebeu o pedido, e o valor do campo de JSON formatadoaccessKey
No corpo do pedido está:212f54a1-1c88-1bf5-54a1-f7bf52b3256c
, e o valor de campo desecret_key
é7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs
.
Ao chamar a seguinte interface, o docker enviará um pedido RPC para o plugin de protocolo geral da seguinte forma:
Quando ligarexchange.SetMarginLevel(10)
Na estratégia, os dados do organismo requerente são:
{
"access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
"method":"io",
"nonce":1631858961289247000,
"params":{"args":[10],"code":0},
"secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
}
Intercâmbio de chamadas.SetMarginLevel(10), e passar 10 como o parâmetro.
Quando ligarexchange.SetDirection("buy")
Na estratégia, os dados do organismo requerente são:
{
"access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
"method":"io",
"nonce":1631860438946922000,
"params":{"args":["buy"],"code":1},
"secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
}
Intercâmbio de chamadas.SetDirection ((
Quando ligarexchange.SetContractType("swap")
Na estratégia, os dados do organismo requerente são:
{
"access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
"method":"io",
"nonce":1631860847525039000,
"params":{"args":["swap"],"code":2},
"secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
}
Intercâmbio de chamadas.SetContractType ((
Quando ligarexchange.GetPosition()
Na estratégia, os dados do organismo requerente são:
{
"access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
"method":"io",
"nonce":1631860996119505000,
"params":{"args":[],"code":3},
"secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
}
Intercâmbio de chamadas.GetPosition(), nenhum parâmetro foi passado.
Observe os dados JSON no corpo do pedido acima, e podemos ver:
Quando uma função específica para os futuros é chamada, o valor do campo demethod
No corpo do pedido está tudoio
.
A distinção entre essas funções deve julgar acode
no campo.params
, nomeadamente:
code
é0
Significa:SetMarginLevel
.code
é1
Significa:SetDirection
.code
é2
Significa:SetContractType
.code
é3
Significa:GetPosition
.quando essas funções específicas para os futuros são chamados, os parâmetros passados são todos noargs
do campo campoparams
no corpo do pedido.
Em comparação com a versão spot do exemplo do programa plugin de linguagem Go, é necessário fazer uma extensão noOnPost
Função:
Veja os lugares onde há uma observação deFunção necessária para estender o objeto de troca de futuros
func OnPost(w http.ResponseWriter, r *http.Request) {
var ret interface{}
defer func() {
if e := recover(); e != nil {
if ee, ok := e.(error); ok {
e = ee.Error()
}
ret = map[string]string{"error": fmt.Sprintf("%v", e)}
}
b, _ := json.Marshal(ret)
w.Write(b)
}()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
var request RpcRequest
err = json.Unmarshal(b, &request)
if err != nil {
panic(err)
}
e := newZG(request.AccessKey, request.SecretKey)
var symbol string
if _, ok := request.Params["symbol"]; ok {
symbol = strings.ToUpper(request.Params["symbol"].(string))
}
var data interface{}
switch request.Method {
case "ticker":
data, err = e.GetTicker(symbol, "GET")
case "depth":
data, err = e.GetDepth(symbol, "GET")
case "trades":
data, err = e.GetTrades(symbol, "GET")
case "records":
data, err = e.GetRecords(toInt64(request.Params["period"]), symbol, "GET")
case "accounts":
data, err = e.GetAccount(symbol, "GET")
case "trade":
side := request.Params["type"].(string)
if side == "buy" {
side = "BUY"
} else {
side = "SELL"
}
price := toFloat(request.Params["price"])
amount := toFloat(request.Params["amount"])
data, err = e.Trade(side, price, amount, symbol, "POST")
case "orders":
data, err = e.GetOrders(symbol, "POST")
case "order":
data, err = e.GetOrder(toString(request.Params["id"]), symbol, "POST")
case "cancel":
data, err = e.CancelOrder(toString(request.Params["id"]), symbol, "POST")
default:
if strings.HasPrefix(request.Method, "__api_") {
params := map[string]interface{}{}
for k, v := range request.Params {
params[k] = toString(v)
}
data, err = e.tapiCall(request.Method[6:], params, "GET")
} else if request.Method == "io" { // extend the function required by the futures exchange object
code := toString(request.Params["code"])
if code == "0" {
// process SetMarginLevel
// ...
} else if code == "1" {
// process SetDirection
// ...
} else if code == "2" {
// process SetContractType
// ...
} else if code == "3" {
// process GetPosition
// ...
} else {
panic(errors.New(request.Method + " not support"))
}
} else {
panic(errors.New(request.Method + " not support"))
}
}
if err != nil {
panic(err)
}
ret = map[string]interface{}{
"data": data,
}
return
}
As três funçõesSetMarginLevel
, SetDirection
, SetContractType
, são literalmente usados para definir a configuração relevante do símbolo de negociação atual.SetDirection
/SetContractType
é projetado para definir uma variável local para registrar a direção da ordem atual (ou seja, você precisa ler essa configuração ao colocar uma ordem para saber em que direção colocar uma ordem. A razão é que existem duas direções para a compra de futuros: abrir longo e fechar curto, por isso precisa ser distinguido) e o código do contrato atual (ao obter dados como cotações de mercado e ordens, você precisa ter certeza de qual contrato é consultado).
SetMarginLevel
A plataforma deve ter uma interface para definir a alavancagem, de acordo com o mecanismo de alavancagem da plataforma.
GetPosition
é a função para obter a posição atual do símbolo; quando o programa de plug-in de protocolo geral obtém os dados retornados pela interface de posição da plataforma, directamente construir a mesma estrutura de dados que oposition
em FMZ.
Vai para a língua
/*
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)
}