Access to Binance, KuCoin and Huobi APIs in C#

It is convenient to receive data through the RestAPI of the exchange directly from the client code, but for any reason, there are situations when it is still better or even necessary to pre-process the data on your server, save it in your database, and only after that provide access to this processed data to the client application through, again, your web service, through your Rest full API.

Let’s consider one of these situations when it is not convenient to receive data on a client directly from the exchange, in our case it is getting a list of exchange trading pairs sorted by such indicators as, for example, liquidity and volatility. In the example below, we use the JavaScript client code to access the list of exchange products to our Rest full API Web Service using the link

https://cryptoalert.mizerov.com/api/Products/” + ex

where ex is the exchange code.

<!-- Получение отсортированного по ликвидности списка пар
     с любой из 3-х бирж по параметру из своей БД через свой Web API -->
<script>
    function getSymbols(ex) {
        var url = "https://cryptoalert.mizerov.com/api/Products/" + ex;

        //Обращение за данными на свой сервер
        $.getJSON(url, function (data) {

            //Парсинг - преобразование полученных данных в объект
            var paras = JSON.parse(JSON.stringify(data));

            //Очистить список
            $(".symbols").html("");

            //Заполнение списка символов в листбокс
            $.each(paras, function (i, symInfo) {
                var sym = symInfo.symbol.replace("-", "");
                var sli = "<li id=id" + i + " onclick=\"sel(" + i + ", '" + sym + "')\">" + sym + "</li>";
                $(".symbols").append(sli);
            });
        });
    }
</script>

That is, once again, let’s set the task this way, we would like to see a list of trading pairs, in which the top lines will contain, so to speak, the most interesting instruments for a trader. Well, you know, of course, that up to 1500 pairs are traded on the Binance, KuCoun and Huobi crypto exchanges each, and among them there are quite a few illiquid assets, small volumes or zero at all, or the price twitches somehow artificially, not in a market way, why do we need such , even if they are on the list, but somewhere below, where to wind for a long time, and on top of the beauties, as in the example below.

The source code for this example can be found here https://github.com/amizerov/Vidos.BinanceAPI/blob/master/l1/93.htm.

Here I will not consider the implementation of my Rest full API Web service cryptoalert.mizerov.com, we will immediately move on to the C# code, which will receive a list of pairs, parse and store in the Database.

Open Visual Studio and select project type Windows Forms Application (Microsoft)it is this type of project that will allow us to use the chips of the latest version of the C# language.

Let’s add a TabControl with 3 tabs and a Start button to the main form, which will start the service. We will put multi-line TextBoxes on each tab, in which we will display messages about the progress of the service for each exchange separately.

To work with the database, we will use EntityFrameworkso let’s add packages

and make a class for connecting to the database

public class CaDb: DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=***;Database=CryptoAlert;UID=ca;PWD=***");
    }
    public DbSet< Product >? Products { get; set; }
}

in which we write the connection string and specify only one table Products. In this project, in this our microservice, we limit ourselves to solving one task, so we will work only with this one table, so we don’t need anything else here, and we will associate it with the model class product

public class Product
{
    public int Id { get; set; }
    public string symbol { get; set; }
    public int exchange { get; set; }
    public string baseasset { get; set; }
    public string quoteasset { get; set; }
    public double volatility { get; set; }
    public double liquidity { get; set; }
}

Class product this is a model, so it simply repeats all the fields that are in the corresponding table in the database, and then we will add to it a method for calculating candlestick statistics and a method for saving these statistics in the database.

Next, let’s create an abstract exchange class Exchangein which we will stake out the need to implement public properties in the heirs (classes for specific exchanges) ID and Name (number and name), as well as three protected methods ToProduct, Process Products and GetKlinesin them we will contact the exchange API for a list of products, and for candles for each pair by which we will calculate volatility and liquidity this pair, of course, we will save all this later in the database. In addition, in our abstract class Exchange we will implement a public method that will run a long process of receiving data and calculating statistics in a separate thread, so as not to hang up the main program window. class heirs Exchange we plan to have 3 classes Binance, Kucoin and Huobi.

using CryptoExchange.Net.CommonObjects;

namespace caSever01
{
    public abstract class Exchange
    {
        public abstract int ID { get; }
        public abstract string Name { get; }
        public void UpdateProductsStat()
        {
            Task.Run(() => {
                ProcessProducts();
            });
        }
        protected abstract Product ToProduct(Object p);
        protected abstract void ProcessProducts();
        protected abstract List<Kline> GetKlines(string symbol,
                                int IntervarInMinutes, int PeriodInDays);
    }
}

Well, here the moment has come when we finally have to specifically get our hands dirty and start writing code in C # for a specific exchange, of course we will start with Binance. After studying the issue, I came to the conclusion that in order to work with the API Binance it is better to use the so-called wrapper, a wrapper in the form of a set of classes in C # to call API methods, rather than pull them directly via http. As it turned out, the library from JKorf, Binance.Net, is the most popular, it is free, as they say open source, our favorite, and here is the github linkI suggest everyone to use and support a wonderful project.

For our task, we just need to connect this library to the project through NuGet package manager and you can start implementing the abstract class Exchange as a concrete class Binance:

using Binance.Net.Clients;
using Binance.Net.Objects.Models.Spot;
using CryptoExchange.Net.CommonObjects;

namespace caSever01
{
    public class Binance : Exchange
    {
        public override int ID => 1;
        public override string Name => "Binance";

        BinanceClient client = new();

        protected override Product ToProduct(object p)
        {
            BinanceProduct binaProd = (BinanceProduct)p;
            Product product = new();
            product.symbol = binaProd.Symbol;
            product.exchange = ID;
            product.baseasset = binaProd.BaseAsset;
            product.quoteasset = binaProd.QuoteAsset;

            return product;
        }
... ... ...

Specify the number and name of the exchange, then create a client object of type BinanceClienthere even without authorization, authorization is not required to receive exchange market data.

Method ToProduct converts a Binance-specific object of type BinanceProduct in our model product and that’s it, now let’s move on to the main thing:

protected override void ProcessProducts()
{
    var r = client.SpotApi.ExchangeData.GetProductsAsync().Result;
    if (r.Success)
    {
        foreach (var p in r.Data)
        {
            if (p.Status == "TRADING")
            {
                Product product = ToProduct(p);
                List<Kline> klines = GetKlines(p.Symbol, 1, 5);
                if (klines.Count > 0)
                {
                    product.CalcStat(klines);
                    product.SaveStat();
                }
                Thread.Sleep(1000);
            }
        }
    }
}

In method Process Products First of all, we get a list of trading pairs with the following call:
var r = client.SpotApi.ExchangeData.GetProductsAsync().Result;
and then for each pair we get candles in the method GetKlineswe calculate the statistics of the product, passing these candles to it in the method CalcStat and save in database savestat.

At each iteration of the cycle for products, we do at the end
Thread.Sleep(1000);
this is an attempt to avoid the error: Too Many Requests, but for some reason it still periodically occurs on KuCoin, although according to their description of the API, there should not be an excess of the number of requests.

The last method left to implement for Binance this is GetKlines.

protected override List<Kline> GetKlines(string symbol, int IntervarInMinutes, int PeriodInDays)
{
    List<Kline> klines = new List<Kline>();

    int m = IntervarInMinutes % 60;
    int h = (IntervarInMinutes - m) / 60;
    TimeSpan klInterval = new TimeSpan(h, m, 0);

    var r = client.SpotApi.CommonSpotClient
        .GetKlinesAsync(symbol, klInterval,
            DateTime.Now.AddDays(-1 * PeriodInDays), DateTime.Now).Result;
    if (r.Success)
    {
        klines = r.Data.ToList();

        if (klines.Count == 0)
        {
            Msg.Send(1, $"GetProductStat({symbol})", "i==0");
        }
    }
    else
    {
        Msg.Send(1, $"GetProductStat({symbol})", r.Error!.Message);
    }
    return klines;
}

To get the pair candles on Binance, use the call

var r = client.SpotApi.CommonSpotClient
        .GetKlinesAsync(symbol, klInterval,
                DateTime.Now.AddDays(-1 * PeriodInDays), DateTime.Now).Result;

This, as you can see, spot candles in syntax, everything is absolutely transparent. This, of course, is the main advantage of the wrapper, in comparison with direct calls to the exchange API methods via http.

Classes Kucoin and Huobi are absolutely the same, we built our code here in such a way that to add a new exchange, you need to add only one class (one file), everything else does not change. Other classes are said to be loosely coupled to Exchange descendants and in order to implement Kucoin you can just copy Binancechange there, of course, ID, Name, well, something else on the little things, like this:

using Kucoin.Net.Clients;
using Kucoin.Net.Objects.Models.Spot;
using CryptoExchange.Net.CommonObjects;

namespace caSever01
{
    public class Kucoin : Exchange
    {
        public override int ID => 2;
        public override string Name => "Kucoin";

        KucoinClient client = new();

        protected override Product ToProduct(object p)
        {
            KucoinSymbol binaProd = (KucoinSymbol)p;
            Product product = new();
            product.symbol = binaProd.Symbol;
            product.exchange = ID;
            product.baseasset = binaProd.BaseAsset;
            product.quoteasset = binaProd.QuoteAsset;

            return product;
        }
        protected override void ProcessProducts()
        {
            var r = client.SpotApi.ExchangeData.GetSymbolsAsync().Result;
            if (r.Success)
            {
                foreach (var p in r.Data)
                {
                    if (p.EnableTrading)
                    {
                        Product product = ToProduct(p);
                        List<Kline> klines = GetKlines(p.Symbol, 1, 5);
                        if (klines.Count > 0)
                        {
                            product.CalcStat(klines);
                            product.SaveStat();
                        }
                        Thread.Sleep(1000);
                    }
                }
            }
        }
        protected override List<Kline> GetKlines(string symbol,
                                int IntervarInMinutes, int PeriodInDays)
        {
            List<Kline> klines = new List<Kline>();

            int m = IntervarInMinutes % 60;
            int h = (IntervarInMinutes - m) / 60;
            TimeSpan klInterval = new TimeSpan(h, m, 0);

            var r = client.SpotApi.CommonSpotClient
                .GetKlinesAsync(symbol, klInterval, DateTime.Now.AddDays(-1 * PeriodInDays), DateTime.Now).Result;

            if (r.Success)
            {
                klines = r.Data.ToList();

                if (klines.Count == 0)
                {
                    Msg.Send(2, $"GetProductStat({symbol})", "i==0");
                }
            }
            else
            {
                Msg.Send(2, $"GetProductStat({symbol})", r.Error!.Message);
            }
            return klines;
        }
    }
}

The main difference in each of these classes, the client field will be of its own type BinancrClient, KucoinClient and HuobiClient respectively.

That is, wrapper developers Binance.NET, Kucoin.NET and Huobi.NET they are trying to unify the object models of these libraries so that the implementations of calls to the API methods of all these exchanges do not differ much, but so far they have not succeeded everywhere.

For example, I noticed that Huobi call to get candles doesn’t want to accept DateTimeFrom, DateTimeTo parameters.

var r = client.SpotApi.CommonSpotClient
    .GetKlinesAsync(symbol, klInterval).Result;
// !!! не хочет принимать параметры фром ту как в других биржах
//, DateTime.Now.AddDays(-1 * PeriodInDays), DateTime.Now).Result;

By using a fancy thing called Polymorphismon the main form we can combine all 3 classes into one array of elements of the type Exchange:

public partial class FrmMain : Form
{
    List<Exchange> _exchangeList = new() { new Binance(), new Kucoin(), new Huobi() };

And by pressing a button like btnStart, we will update the statistics of trading pairs in a cycle:

private void btnStart_Click(object sender, EventArgs e)
{
    foreach(Exchange exchange in _exchangeList)
    {
        exchange.UpdateProductsStat();
    }
}

In this case, the processes for each exchange will be launched in a separate thread.

Finally, I summarize and remind you that to work with exchanges and the database, we use the following set of additional, absolutely free libraries:

That’s all for now. I don’t say goodbye for a long time. In the next post, we will consider more complex things, namely, receiving data through a socket, that is, not at the request of our side, but as events occur on the exchange.

You can find the project code here

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *