
In letzter Zeit haben einige Benutzer darüber gesprochenAOFEXDieser Exchange, da es auf der FMZ kein Beispiel dafür gibt, wie man sich mit der REST-Schnittstelle des Vertrags-Exchanges verbindet. In diesem Artikel geht es um den ZugangAOFEXNehmen Sie dies als Beispiel, um zu erklären, wie Sie auf die Vertragsbörse zugreifen können.
An der FMZ wird zwischen Spotbörsen und Terminbörsen unterschieden. Beispielsweise bieten die drei uns bekannten großen Börsen sowohl Spot-Handel als auch Kontrakthandel an. Die API-Schnittstellen dieser verschiedenen Märkte sind ebenfalls unterschiedlich und manche verfügen sogar über völlig separate und unabhängige API-Systeme. Daher wird bei der Paketierung dieser Börsen auf der FMZ zwischen Spot und Futures unterschieden.
Es gibt bereits viele Beispiele für die Verwendung des allgemeinen FMZ-Protokolls zum Einkapseln von Spot-Austauschen in der FMZ-Plattform-Community und in den Dokumenten, aber es gibt kein vollständiges Beispiel für die Einkapselung eines Vertragsaustauschs. Das allgemeine Protokoll-Plug-In-Programm der verpackten Futures-Version ist jedoch grundsätzlich dasselbe wie das der Spot-Version, verfügt jedoch über einige zusätzliche Schnittstellen.
Allgemeines Protokolldokument der FMZ: https://www.fmz.com/bbs-topic/1052 Das Dokument enthält eine DEMO zum allgemeinen Protokollzugriff auf den Spot-Austausch
Die Hauptschnittstelle des Spot-Exchange-Objekts
exchange.GetTicker() Erhalten Sie Tick-Marktdaten für Spot und Futures.
exchange.GetDepth() Erhalten Sie Orderbuchdaten für Spot- und Futures-Kontrakte.
exchange.GetTrades() Erhalten Sie Auftragsflussdaten (Aufzeichnungen von Markttransaktionen) sowohl für Spot- als auch für Futures-Kontrakte.
exchange.GetRecords() Um K-Line-Daten zu erhalten, werden sowohl Spot- als auch Futures benötigt.
exchange.GetAccount() Erhalten Sie Kontovermögensdaten für Spot- und Futures-Konten.
exchange.Buy() Zum Platzieren einer Kauforder sind sowohl Spot- als auch Futures-Orders erforderlich.
exchange.Sell() Zum Platzieren einer Verkaufsorder sind sowohl Spot- als auch Futures-Orders erforderlich.
exchange.GetOrder() Holen Sie sich die Auftragsdaten der angegebenen ID, sowohl Spot als auch Futures.
exchange.GetOrders() Holen Sie sich die aktuell aktiven ausstehenden Aufträge (Spot- und Futures-Aufträge).
exchange.CancelOrder() Stornieren Sie die Bestellung mit der angegebenen ID, sowohl für Spot- als auch für Futures.
Zusätzlich zur Kapselung dieser Schnittstellen von Spot-Exchange-Objekten müssen Futures-Exchange-Objekte auch die von Futures verwendeten Schnittstellenfunktionen kapseln.
exchange.SetMarginLevel() Legen Sie den Hebelwert für das aktuelle Symbol fest.
exchange.SetDirection() Legen Sie die Handelsrichtung des aktuellen Produkts fest, d. h. Long-Position öffnen/Short-Position öffnen/Long-Position schließen/Short-Position schließen.
exchange.SetContractType()
Stellen Sie den aktuellen Vertragscode ein. Da Futures unbefristete Verträge sind (swap)、Liefervertrag(quarterVierteljährlich) usw. haben jeweils einen eigenen Vertragscode, der in FMZ definiert ist. Einzelheiten finden Sie im FMZ-API-Dokument. Auch bei der Kapselung müssen diese Einstellungen unbedingt beachtet werden, da sonst die bestehenden alten Strategien nicht mehr richtig funktionieren.
exchange.GetPosition() Holen Sie sich die aktuellen Positionsdaten. Hier sehen wir, dass es im Spothandel kein Positionskonzept gibt. Die logische Position kann nur durch den Vergleich von Kontoveränderungen berechnet werden. Aber Futures haben Positionen.
Am Beispiel des AOFEX-Austauschs: Wenn Sie das Austauschobjekt des allgemeinen Protokolls auf FMZ konfigurieren, lautet der auszufüllende API-Schlüssel:

Auf der FMZ-Seite zur allgemeinen Protokollkonfiguration gibt esaccessKeyUndsecretKeyEingabefeld: Nach der Konfiguration des Universal Protocol Exchange-Objekts werden die JSON-Formatdaten des Bodys in der Anforderung empfangen, wenn das Universal Protocol-Plugin ausgeführt wirdaccessKeyDer Wert des Feldes ist212f54a1-1c88-1bf5-54a1-f7bf52b3256c,secret_keyDer Wert des Feldes ist7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs。
Beim Aufruf der folgenden Schnittstelle sendet der Host wie folgt eine RPC-Anforderung an das allgemeine Protokoll-Plugin:
exchange.SetMarginLevel(10)Wenn die Daten im Anforderungstext sind: {
"access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
"method":"io",
"nonce":1631858961289247000,
"params":{"args":[10],"code":0},
"secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
}
Rufen Sie exchange.SetMarginLevel(10) mit 10 als Parameter auf.
exchange.SetDirection("buy")Wenn die Daten im Anforderungstext sind: {
"access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
"method":"io",
"nonce":1631860438946922000,
"params":{"args":["buy"],"code":1},
"secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
}
Rufen Sie exchange.SetDirection(“buy”) auf und übergeben Sie “buy” als Parameter.
exchange.SetContractType("swap")Wenn die Daten im Anforderungstext sind: {
"access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
"method":"io",
"nonce":1631860847525039000,
"params":{"args":["swap"],"code":2},
"secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
}
Rufen Sie exchange.SetContractType(“swap”) auf und übergeben Sie “swap” als Parameter.
exchange.GetPosition()Wenn die Daten im Anforderungstext sind: {
"access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
"method":"io",
"nonce":1631860996119505000,
"params":{"args":[],"code":3},
"secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
}
Beim Aufruf von exchange.GetPosition() werden keine Parameter übergeben.
Beachten Sie die JSON-Daten im obigen Anforderungstext und stellen Sie Folgendes fest:
methodDie Feldwerte sindio。paramsIm FeldcodeUrteil, das heißt:
codefür0JaSetMarginLevel。codefür1JaSetDirection。codefür2JaSetContractType。codefür3JaGetPosition。paramsFeldargsMitte.Im Vergleich zum vorhandenen Plug-In-Beispiel für die Sprache Go müssen SieOnPostNehmen Sie eine Erweiterung in der Funktion vor:
Siehe den folgenden Code für “Erforderliche Funktionen zur Erweiterung von Terminbörsenobjekten„Der Speicherort des Kommentars.
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/SetContractTypeAus der wörtlichen Bedeutung können wir erkennen, dass die drei Funktionen dazu dienen, die jeweilige Konfiguration des aktuellen Handelsprodukts festzulegen. InSetDirection/SetContractTypeAus Designsicht wird eine lokale Variable gesetzt, um die aktuelle Orderrichtung aufzuzeichnen (das heißt, beim Platzieren einer Order muss diese Einstellung gelesen werden, um zu wissen, in welche Richtung die Order platziert werden soll. Der Grund dafür ist, dass Futures-Käufe zwei Richtungen haben : Eröffnung Long und Schließung Short. Daher ist es notwendig, zwischen dem aktuellen Vertragscode und dem aktuellen Vertragscode zu unterscheiden (welcher Vertrag wird beim Einholen von Marktinformationen, Platzieren von Aufträgen usw. abgefragt).
SetMarginLevelEs muss speziell entsprechend dem Hebelmechanismus der Börse gestaltet werden (1. Der Hebelparameter wird als Parameter in der Auftragsschnittstelle übergeben. 2. Die Börse verfügt über eine Schnittstelle zum Einstellen des Hebels).
GetPositionEs handelt sich um eine Funktion zum Abrufen der aktuellen Position des Produkts. Wenn das allgemeine Protokoll-Plugin die von der Austauschpositionsschnittstelle zurückgegebenen Daten abruft, erstellt es direkt die Positionsfunktion auf FMZ.positionEs reicht die gleiche Datenstruktur aus.
Gehen
/*
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)
}