Skip to content

Commit 7d4cdbc

Browse files
authored
Add MEXC public REST endpoints (#838)
* Add CryptoUtility.SecondsToPeriodInMinutesUpToHourString Convert seconds to a period string, i.e. 1m, 5m, 60m, 4h, 1d, 1W, 1M. Used on MEXC. * Add MEXC public REST endpoints * Create ExchangeMEXCAPITests.cs * Add MEXC to README.md
1 parent 40fac2f commit 7d4cdbc

File tree

4 files changed

+384
-43
lines changed

4 files changed

+384
-43
lines changed

README.md

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,49 +24,50 @@ Feel free to visit the discord channel at <https://discord.gg/58ktxXuTVK> and ch
2424
The following cryptocurrency exchanges are supported:
2525
(Web socket key: T = tickers, R = trades, B = orderbook / delta orderbook, O = private orders, U = user data)
2626

27-
| Exchange Name | Public REST | Private REST | Web Socket | Notes |
28-
| ----------------------- | ----------- | ------------ | ------------- | ------------------------------------------- |
29-
| ApolloX | x | x | T R B O U | |
30-
| Aquanow | wip | x | | |
31-
| Binance | x | x | T R B O U | |
32-
| ~~Binance Jersey~~ | ~~x~~ | ~~x~~ | ~~T R B O U~~ | Ceased operations |
33-
| Binance.US | x | x | T R B O U | |
34-
| Binance DEX | | | R | |
35-
| Bitbank | x | x | | |
36-
| Bitfinex | x | x | T R O | |
37-
| Bitflyer | | | R | |
38-
| Bithumb | x | | R | |
39-
| BitMEX | x | x | R O | |
40-
| Bitstamp | x | x | R | |
41-
| Bittrex | x | x | T R | |
42-
| BL3P | x | x | R B | Trades stream does not send trade's ids. |
43-
| Bleutrade | x | x | | |
44-
| BtcTurk | | | R | |
45-
| BTSE | x | x | | |
46-
| Bybit | x | x | R | Has public method for Websocket Positions |
47-
| Coinbase (Advanced) | x | x | T R O U | |
48-
| Coincheck | | | R | |
49-
| Coinmate | x | x | | |
50-
| Crypto.com | | | R | |
51-
| Digifinex | x | x | R B | |
52-
| Dydx | | | R | |
53-
| FTX | x | x | T R | |
54-
| FTX.us | x | x | T R | |
55-
| gate.io | x | x | R | |
56-
| Gemini | x | x | T R B | |
57-
| HitBTC | x | x | R | |
58-
| Huobi | x | x | R B | |
59-
| Kraken | x | x | R | Dark order symbols not supported |
60-
| KuCoin | x | x | T R | |
61-
| LBank | x | x | R | |
62-
| Livecoin | x | x | | |
63-
| NDAX | x | x | T R | |
64-
| OKCoin | x | x | R B | |
65-
| OKEx | x | x | T R B O | |
66-
| Poloniex | x | x | T R B | |
67-
| UPbit | | | R | |
68-
| YoBit | x | x | | |
69-
| ZB.com | wip | | R | |
27+
| Exchange Name | Public REST | Private REST | Web Socket | Notes |
28+
|---------------------| ----------- |--------------| ------------- | ------------------------------------------- |
29+
| ApolloX | x | x | T R B O U | |
30+
| Aquanow | wip | x | | |
31+
| Binance | x | x | T R B O U | |
32+
| ~~Binance Jersey~~ | ~~x~~ | ~~x~~ | ~~T R B O U~~ | Ceased operations |
33+
| Binance.US | x | x | T R B O U | |
34+
| Binance DEX | | | R | |
35+
| Bitbank | x | x | | |
36+
| Bitfinex | x | x | T R O | |
37+
| Bitflyer | | | R | |
38+
| Bithumb | x | | R | |
39+
| BitMEX | x | x | R O | |
40+
| Bitstamp | x | x | R | |
41+
| Bittrex | x | x | T R | |
42+
| BL3P | x | x | R B | Trades stream does not send trade's ids. |
43+
| Bleutrade | x | x | | |
44+
| BtcTurk | | | R | |
45+
| BTSE | x | x | | |
46+
| Bybit | x | x | R | Has public method for Websocket Positions |
47+
| Coinbase (Advanced) | x | x | T R O U | |
48+
| Coincheck | | | R | |
49+
| Coinmate | x | x | | |
50+
| Crypto.com | | | R | |
51+
| Digifinex | x | x | R B | |
52+
| Dydx | | | R | |
53+
| FTX | x | x | T R | |
54+
| FTX.us | x | x | T R | |
55+
| gate.io | x | x | R | |
56+
| Gemini | x | x | T R B | |
57+
| HitBTC | x | x | R | |
58+
| Huobi | x | x | R B | |
59+
| Kraken | x | x | R | Dark order symbols not supported |
60+
| KuCoin | x | x | T R | |
61+
| LBank | x | x | R | |
62+
| Livecoin | x | x | | |
63+
| MEXC | x | | | |
64+
| NDAX | x | x | T R | |
65+
| OKCoin | x | x | R B | |
66+
| OKEx | x | x | T R B O | |
67+
| Poloniex | x | x | T R B | |
68+
| UPbit | | | R | |
69+
| YoBit | x | x | | |
70+
| ZB.com | wip | | R | |
7071

7172
The following cryptocurrency services are supported:
7273

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Newtonsoft.Json.Linq;
6+
7+
namespace ExchangeSharp
8+
{
9+
public sealed class ExchangeMEXCAPI : ExchangeAPI
10+
{
11+
public override string BaseUrl { get; set; } = "https://api.mexc.com/api/v3";
12+
public override string BaseUrlWebSocket { get; set; } = "wss://wbs.mexc.com/ws";
13+
14+
public override string PeriodSecondsToString(int seconds) =>
15+
CryptoUtility.SecondsToPeriodInMinutesUpToHourString(seconds);
16+
17+
private ExchangeMEXCAPI()
18+
{
19+
NonceStyle = NonceStyle.UnixMilliseconds;
20+
MarketSymbolSeparator = string.Empty;
21+
MarketSymbolIsUppercase = true;
22+
RateLimit = new RateGate(20, TimeSpan.FromSeconds(2));
23+
}
24+
25+
public override Task<string> ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol)
26+
{
27+
var quoteLength = 3;
28+
if (marketSymbol.EndsWith("USDT") ||
29+
marketSymbol.EndsWith("USDC") ||
30+
marketSymbol.EndsWith("TUSD"))
31+
{
32+
quoteLength = 4;
33+
}
34+
35+
var baseSymbol = marketSymbol.Substring(marketSymbol.Length - quoteLength);
36+
37+
return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(
38+
marketSymbol.Replace(baseSymbol, "")
39+
+ GlobalMarketSymbolSeparator
40+
+ baseSymbol);
41+
}
42+
43+
protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
44+
{
45+
return (await OnGetMarketSymbolsMetadataAsync())
46+
.Select(x => x.MarketSymbol);
47+
}
48+
49+
protected internal override async Task<IEnumerable<ExchangeMarket>> OnGetMarketSymbolsMetadataAsync()
50+
{
51+
var symbols = await MakeJsonRequestAsync<JToken>("/exchangeInfo", BaseUrl);
52+
53+
return (symbols["symbols"] ?? throw new ArgumentNullException())
54+
.Select(symbol => new ExchangeMarket()
55+
{
56+
MarketSymbol = symbol["symbol"].ToStringInvariant(),
57+
IsActive = symbol["isSpotTradingAllowed"].ConvertInvariant<bool>(),
58+
MarginEnabled = symbol["isMarginTradingAllowed"].ConvertInvariant<bool>(),
59+
BaseCurrency = symbol["baseAsset"].ToStringInvariant(),
60+
QuoteCurrency = symbol["quoteAsset"].ToStringInvariant(),
61+
QuantityStepSize = symbol["baseSizePrecision"].ConvertInvariant<decimal>(),
62+
// Not 100% sure about this
63+
PriceStepSize =
64+
CryptoUtility.PrecisionToStepSize(symbol["quoteCommissionPrecision"].ConvertInvariant<decimal>()),
65+
MinTradeSizeInQuoteCurrency = symbol["quoteAmountPrecision"].ConvertInvariant<decimal>(),
66+
MaxTradeSizeInQuoteCurrency = symbol["maxQuoteAmount"].ConvertInvariant<decimal>()
67+
});
68+
}
69+
70+
protected override async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>> OnGetTickersAsync()
71+
{
72+
var tickers = new List<KeyValuePair<string, ExchangeTicker>>();
73+
var token = await MakeJsonRequestAsync<JToken>("/ticker/24hr", BaseUrl);
74+
foreach (var t in token)
75+
{
76+
var symbol = (t["symbol"] ?? throw new ArgumentNullException()).ToStringInvariant();
77+
tickers.Add(new KeyValuePair<string, ExchangeTicker>(symbol,
78+
await this.ParseTickerAsync(
79+
t,
80+
symbol,
81+
"askPrice",
82+
"bidPrice",
83+
"lastPrice",
84+
"volume",
85+
timestampType: TimestampType.UnixMilliseconds,
86+
timestampKey: "closeTime")));
87+
}
88+
89+
return tickers;
90+
}
91+
92+
protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol) =>
93+
await this.ParseTickerAsync(
94+
await MakeJsonRequestAsync<JToken>($"/ticker/24hr?symbol={marketSymbol.ToUpperInvariant()}", BaseUrl),
95+
marketSymbol,
96+
"askPrice",
97+
"bidPrice",
98+
"lastPrice",
99+
"volume",
100+
timestampType: TimestampType.UnixMilliseconds,
101+
timestampKey: "closeTime");
102+
103+
protected override async Task<ExchangeOrderBook> OnGetOrderBookAsync(string marketSymbol, int maxCount = 100)
104+
{
105+
const int maxDepth = 5000;
106+
const string sequenceKey = "lastUpdateId";
107+
marketSymbol = marketSymbol.ToUpperInvariant();
108+
if (string.IsNullOrEmpty(marketSymbol))
109+
{
110+
throw new ArgumentOutOfRangeException(nameof(marketSymbol), "Market symbol cannot be empty.");
111+
}
112+
113+
if (maxCount > maxDepth)
114+
{
115+
throw new ArgumentOutOfRangeException(nameof(maxCount), $"Max order book depth is {maxDepth}");
116+
}
117+
118+
var token = await MakeJsonRequestAsync<JToken>($"/depth?symbol={marketSymbol}");
119+
var orderBook = token.ParseOrderBookFromJTokenArrays(sequence: sequenceKey);
120+
orderBook.MarketSymbol = marketSymbol;
121+
orderBook.ExchangeName = Name;
122+
orderBook.LastUpdatedUtc = DateTime.UtcNow;
123+
124+
return orderBook;
125+
}
126+
127+
protected override async Task<IEnumerable<ExchangeTrade>> OnGetRecentTradesAsync(string marketSymbol,
128+
int? limit = null)
129+
{
130+
const int maxLimit = 1000;
131+
const int defaultLimit = 500;
132+
marketSymbol = marketSymbol.ToUpperInvariant();
133+
if (limit == null || limit <= 0)
134+
{
135+
limit = defaultLimit;
136+
}
137+
138+
if (limit > maxLimit)
139+
{
140+
throw new ArgumentOutOfRangeException(nameof(limit), $"Max recent trades limit is {maxLimit}");
141+
}
142+
143+
var token = await MakeJsonRequestAsync<JToken>($"/trades?symbol={marketSymbol}&limit={limit.Value}");
144+
return token
145+
.Select(t => t.ParseTrade(
146+
"qty",
147+
"price",
148+
"isBuyerMaker",
149+
"time",
150+
TimestampType.UnixMilliseconds,
151+
"id",
152+
"true"));
153+
}
154+
155+
protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(
156+
string marketSymbol,
157+
int periodSeconds,
158+
DateTime? startDate = null,
159+
DateTime? endDate = null,
160+
int? limit = null)
161+
{
162+
var period = PeriodSecondsToString(periodSeconds);
163+
const int maxLimit = 1000;
164+
const int defaultLimit = 500;
165+
if (limit == null || limit <= 0)
166+
{
167+
limit = defaultLimit;
168+
}
169+
170+
if (limit > maxLimit)
171+
{
172+
throw new ArgumentOutOfRangeException(nameof(limit), $"Max recent candlesticks limit is {maxLimit}");
173+
}
174+
175+
176+
var url = $"/klines?symbol={marketSymbol}&interval={period}&limit={limit.Value}";
177+
if (startDate != null)
178+
{
179+
url =
180+
$"{url}&startTime={new DateTimeOffset(startDate.Value).ToUnixTimeMilliseconds()}";
181+
}
182+
183+
if (endDate != null)
184+
{
185+
url = $"{url}&endTime={new DateTimeOffset(endDate.Value).ToUnixTimeMilliseconds()}";
186+
}
187+
188+
var candleResponse = await MakeJsonRequestAsync<JToken>(url);
189+
return candleResponse.Select(
190+
cr =>
191+
this.ParseCandle(
192+
cr,
193+
marketSymbol,
194+
periodSeconds,
195+
1,
196+
2,
197+
3,
198+
4,
199+
0,
200+
TimestampType.UnixMilliseconds,
201+
5,
202+
7
203+
));
204+
}
205+
}
206+
207+
public partial class ExchangeName
208+
{
209+
public const string MEXC = "MEXC";
210+
}
211+
}

src/ExchangeSharp/Utility/CryptoUtility.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,42 @@ public static byte[] AesEncryption(byte[] input, byte[] password, byte[] salt)
13091309
}
13101310
}
13111311

1312+
/// <summary>
1313+
/// Convert seconds to a period string, i.e. 1m, 5m, 60m, 4h, 1d, 1W, 1M
1314+
/// </summary>
1315+
/// <param name="seconds">Seconds. Use 60 for minute, 3600 for hour, 3600*24 for day, 3600*24*30 for month.</param>
1316+
/// <returns>Period string</returns>
1317+
public static string SecondsToPeriodInMinutesUpToHourString(int seconds)
1318+
{
1319+
const int minuteThreshold = 60;
1320+
const int hourThreshold = 60 * 60;
1321+
const int dayThreshold = 60 * 60 * 24;
1322+
const int weekThreshold = dayThreshold * 7;
1323+
const int monthThreshold = dayThreshold * 30;
1324+
1325+
if (seconds >= monthThreshold)
1326+
{
1327+
return seconds / monthThreshold + "M";
1328+
}
1329+
1330+
if (seconds >= weekThreshold)
1331+
{
1332+
return seconds / weekThreshold + "W";
1333+
}
1334+
1335+
if (seconds >= dayThreshold)
1336+
{
1337+
return seconds / dayThreshold + "d";
1338+
}
1339+
1340+
if (seconds >= hourThreshold)
1341+
{
1342+
return seconds / 60 + "m";
1343+
}
1344+
1345+
return seconds / minuteThreshold + "m";
1346+
}
1347+
13121348
/// <summary>
13131349
/// Convert seconds to a period string, i.e. 5s, 1m, 2h, 3d, 1w, 1M, etc.
13141350
/// </summary>

0 commit comments

Comments
 (0)