Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Update mexc provider to use v3 api #367

Merged
merged 6 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 70 additions & 51 deletions oracle/provider/mexc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package provider
import (
"context"
"encoding/json"
"fmt"
"math/big"
"net/http"
"net/url"
"strconv"
"strings"
"sync"

Expand All @@ -17,9 +20,9 @@ import (

const (
mexcWSHost = "wbs.mexc.com"
mexcWSPath = "/raw/ws"
mexcRestHost = "https://www.mexc.com"
mexcRestPath = "/open/api/v2/market/ticker"
mexcWSPath = "/ws"
mexcRestHost = "https://api.mexc.com/"
mexcRestPath = "/api/v3/ticker/price"
)

var _ Provider = (*MexcProvider)(nil)
Expand All @@ -28,9 +31,7 @@ type (
// MexcProvider defines an Oracle provider implemented by the Mexc public
// API.
//
// REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#ticker-information
// REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#k-line
// REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#overview
// REF: https://mexcdevelop.github.io/apidocs/spot_v3_en/
MexcProvider struct {
wsc *WebsocketController
logger zerolog.Logger
Expand All @@ -42,41 +43,43 @@ type (

// MexcTickerResponse is the ticker price response object.
MexcTickerResponse struct {
Symbol map[string]MexcTicker `json:"data"` // e.x. ATOM_USDT
Symbol string `json:"s"` // e.x. ATOMUSDT
Metadata MexcTicker `json:"d"` // Metadata for ticker
}
MexcTicker struct {
LastPrice float64 `json:"p"` // Last price ex.: 0.0025
Volume float64 `json:"v"` // Total traded base asset volume ex.: 1000
LastPrice string `json:"b"` // Best bid price ex.: 0.0025
Volume string `json:"B"` // Best bid qty ex.: 1000
}

// MexcCandle is the candle websocket response object.
MexcCandleResponse struct {
Symbol string `json:"symbol"` // Symbol ex.: ATOM_USDT
Metadata MexcCandle `json:"data"` // Metadata for candle
Symbol string `json:"s"` // Symbol ex.: ATOMUSDT
Metadata MexcCandle `json:"d"` // Metadata for candle
}
MexcCandle struct {
Close float64 `json:"c"` // Price at close
TimeStamp int64 `json:"t"` // Close time in unix epoch ex.: 1645756200000
Volume float64 `json:"v"` // Volume during period
Data MexcCandleData `json:"k"`
}
MexcCandleData struct {
Close *big.Float `json:"c"` // Price at close
TimeStamp int64 `json:"T"` // Close time in unix epoch ex.: 1645756200
Volume *big.Float `json:"v"` // Volume during period
}

// MexcCandleSubscription Msg to subscribe all the candle channels.
MexcCandleSubscription struct {
OP string `json:"op"` // kline
Symbol string `json:"symbol"` // streams to subscribe ex.: atom_usdt
Interval string `json:"interval"` // Min1、Min5、Min15、Min30
Method string `json:"method"` // ex.: SUBSCRIPTION
Params []string `json:"params"` // ex.: [[email protected]@<symbol>@<interval>]
}

// MexcTickerSubscription Msg to subscribe all the ticker channels.
MexcTickerSubscription struct {
OP string `json:"op"` // kline
Method string `json:"method"` // ex.: SUBSCRIPTION
Params []string `json:"params"` // ex.: [[email protected]@<symbol>]
}

// MexcPairSummary defines the response structure for a Mexc pair
// summary.
MexcPairSummary struct {
Data []MexcPairData `json:"data"`
}
MexcPairSummary []MexcPairData

// MexcPairData defines the data response structure for an Mexc pair.
MexcPairData struct {
Expand Down Expand Up @@ -144,12 +147,13 @@ func (p *MexcProvider) StartConnections() {
}

func (p *MexcProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} {
subscriptionMsgs := make([]interface{}, 0, len(cps)+1)
subscriptionMsgs := make([]interface{}, 0, len(cps)*2)
mexcPairs := make([]string, 0, len(cps))
for _, cp := range cps {
mexcPair := currencyPairToMexcPair(cp)
subscriptionMsgs = append(subscriptionMsgs, newMexcCandleSubscriptionMsg(mexcPair))
mexcPairs = append(mexcPairs, currencyPairToMexcPair(cp))
}
subscriptionMsgs = append(subscriptionMsgs, newMexcTickerSubscriptionMsg())
subscriptionMsgs = append(subscriptionMsgs, newMexcCandleSubscriptionMsg(mexcPairs))
subscriptionMsgs = append(subscriptionMsgs, newMexcTickerSubscriptionMsg(mexcPairs))
return subscriptionMsgs
}

Expand Down Expand Up @@ -195,20 +199,14 @@ func (p *MexcProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte)
)

tickerErr = json.Unmarshal(bz, &tickerResp)
for _, cp := range p.subscribedPairs {
mexcPair := currencyPairToMexcPair(cp)
if tickerResp.Symbol[mexcPair].LastPrice != 0 {
p.setTickerPair(
tickerResp.Symbol[mexcPair],
mexcPair,
)
telemetryWebsocketMessage(ProviderMexc, MessageTypeTicker)
return
}
if tickerResp.Metadata.LastPrice != "" {
p.setTickerPair(tickerResp.Metadata, tickerResp.Symbol)
telemetryWebsocketMessage(ProviderMexc, MessageTypeTicker)
return
}

candleErr = json.Unmarshal(bz, &candleResp)
if candleResp.Metadata.Close != 0 {
if candleResp.Metadata.Data.Close != nil {
p.setCandlePair(candleResp.Metadata, candleResp.Symbol)
telemetryWebsocketMessage(ProviderMexc, MessageTypeCandle)
return
Expand All @@ -224,11 +222,20 @@ func (p *MexcProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte)
}

func (mt MexcTicker) toTickerPrice() (types.TickerPrice, error) {
price, err := decmath.NewDecFromFloat(mt.LastPrice)
tickerPrice, err := strconv.ParseFloat(mt.LastPrice, 64)
rbajollari marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return types.TickerPrice{}, err
}
volume, err := decmath.NewDecFromFloat(mt.Volume)
price, err := decmath.NewDecFromFloat(tickerPrice)
if err != nil {
return types.TickerPrice{}, err
}

tickerVolume, err := strconv.ParseFloat(mt.Volume, 64)
if err != nil {
return types.TickerPrice{}, err
}
volume, err := decmath.NewDecFromFloat(tickerVolume)
if err != nil {
return types.TickerPrice{}, err
}
Expand All @@ -241,19 +248,23 @@ func (mt MexcTicker) toTickerPrice() (types.TickerPrice, error) {
}

func (mc MexcCandle) toCandlePrice() (types.CandlePrice, error) {
close, err := decmath.NewDecFromFloat(mc.Close)
candleClose, _ := mc.Data.Close.Float64()
close, err := decmath.NewDecFromFloat(candleClose)
if err != nil {
return types.CandlePrice{}, err
}
volume, err := decmath.NewDecFromFloat(mc.Volume)

candleVolume, _ := mc.Data.Volume.Float64()
volume, err := decmath.NewDecFromFloat(candleVolume)
if err != nil {
return types.CandlePrice{}, err
}

candle := types.CandlePrice{
Price: close,
Volume: volume,
// convert seconds -> milli
TimeStamp: SecondsToMilli(mc.TimeStamp),
TimeStamp: SecondsToMilli(mc.Data.TimeStamp),
}
return candle, nil
}
Expand All @@ -279,9 +290,9 @@ func (p *MexcProvider) GetAvailablePairs() (map[string]struct{}, error) {
return nil, err
}

availablePairs := make(map[string]struct{}, len(pairsSummary.Data))
for _, pairName := range pairsSummary.Data {
availablePairs[strings.ToUpper(strings.ReplaceAll(pairName.Symbol, "_", ""))] = struct{}{}
availablePairs := make(map[string]struct{}, len(pairsSummary))
for _, pairName := range pairsSummary {
availablePairs[strings.ToUpper(pairName.Symbol)] = struct{}{}
}

return availablePairs, nil
Expand All @@ -290,21 +301,29 @@ func (p *MexcProvider) GetAvailablePairs() (map[string]struct{}, error) {
// currencyPairToMexcPair receives a currency pair and return mexc
// ticker symbol atomusdt@ticker.
func currencyPairToMexcPair(cp types.CurrencyPair) string {
return strings.ToUpper(cp.Base + "_" + cp.Quote)
return strings.ToUpper(cp.Base + cp.Quote)
}

// newMexcCandleSubscriptionMsg returns a new candle subscription Msg.
func newMexcCandleSubscriptionMsg(param string) MexcCandleSubscription {
func newMexcCandleSubscriptionMsg(symbols []string) MexcCandleSubscription {
params := make([]string, len(symbols))
for i, symbol := range symbols {
params[i] = fmt.Sprintf("[email protected]@%s@Min1", symbol)
}
return MexcCandleSubscription{
OP: "sub.kline",
Symbol: param,
Interval: "Min1",
Method: "SUBSCRIPTION",
Params: params,
}
}

// newMexcTickerSubscriptionMsg returns a new ticker subscription Msg.
func newMexcTickerSubscriptionMsg() MexcTickerSubscription {
func newMexcTickerSubscriptionMsg(symbols []string) MexcTickerSubscription {
params := make([]string, len(symbols))
for i, symbol := range symbols {
params[i] = fmt.Sprintf("[email protected]@%s", symbol)
}
return MexcTickerSubscription{
OP: "sub.overview",
Method: "SUBSCRIPTION",
Params: params,
}
}
12 changes: 6 additions & 6 deletions oracle/provider/mexc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestMexcProvider_GetTickerPrices(t *testing.T) {
volume := sdk.MustNewDecFromStr("2396974.02000000")

tickerMap := map[string]types.TickerPrice{}
tickerMap["ATOM_USDT"] = types.TickerPrice{
tickerMap["ATOMUSDT"] = types.TickerPrice{
Price: lastPrice,
Volume: volume,
}
Expand All @@ -45,12 +45,12 @@ func TestMexcProvider_GetTickerPrices(t *testing.T) {
volume := sdk.MustNewDecFromStr("2396974.02000000")

tickerMap := map[string]types.TickerPrice{}
tickerMap["ATOM_USDT"] = types.TickerPrice{
tickerMap["ATOMUSDT"] = types.TickerPrice{
Price: lastPriceAtom,
Volume: volume,
}

tickerMap["LUNA_USDT"] = types.TickerPrice{
tickerMap["LUNAUSDT"] = types.TickerPrice{
Price: lastPriceLuna,
Volume: volume,
}
Expand All @@ -77,7 +77,7 @@ func TestMexcProvider_GetTickerPrices(t *testing.T) {
func TestMexcCurrencyPairToMexcPair(t *testing.T) {
cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"}
MexcSymbol := currencyPairToMexcPair(cp)
require.Equal(t, MexcSymbol, "ATOM_USDT")
require.Equal(t, MexcSymbol, "ATOMUSDT")
}

func TestMexcProvider_getSubscriptionMsgs(t *testing.T) {
Expand All @@ -88,8 +88,8 @@ func TestMexcProvider_getSubscriptionMsgs(t *testing.T) {
subMsgs := provider.getSubscriptionMsgs(cps...)

msg, _ := json.Marshal(subMsgs[0])
require.Equal(t, "{\"op\":\"sub.kline\",\"symbol\":\"ATOM_USDT\",\"interval\":\"Min1\"}", string(msg))
require.Equal(t, "{\"method\":\"SUBSCRIPTION\",\"params\":[\"[email protected]@ATOMUSDT@Min1\"]}", string(msg))

msg, _ = json.Marshal(subMsgs[1])
require.Equal(t, "{\"op\":\"sub.overview\"}", string(msg))
require.Equal(t, "{\"method\":\"SUBSCRIPTION\",\"params\":[\"[email protected]@ATOMUSDT\"]}", string(msg))
}
Loading