mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-11 16:32:57 +00:00
Updated examples
This commit is contained in:
@@ -5,33 +5,33 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="12.11.3" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="10.10.2" />
|
||||
<PackageReference Include="BitMart.Net" Version="3.10.0" />
|
||||
<PackageReference Include="BloFin.Net" Version="2.10.2" />
|
||||
<PackageReference Include="Bybit.Net" Version="6.11.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="10.9.2" />
|
||||
<PackageReference Include="CoinW.Net" Version="2.9.2" />
|
||||
<PackageReference Include="CryptoCom.Net" Version="3.10.0" />
|
||||
<PackageReference Include="DeepCoin.Net" Version="3.9.2" />
|
||||
<PackageReference Include="GateIo.Net" Version="3.10.2" />
|
||||
<PackageReference Include="HyperLiquid.Net" Version="4.3.0" />
|
||||
<PackageReference Include="JK.BingX.Net" Version="3.10.0" />
|
||||
<PackageReference Include="JK.Bitget.Net" Version="3.10.0" />
|
||||
<PackageReference Include="JK.Mexc.Net" Version="5.0.1" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="4.12.0" />
|
||||
<PackageReference Include="Jkorf.Aster.Net" Version="3.1.0" />
|
||||
<PackageReference Include="JKorf.BitMEX.Net" Version="3.9.2" />
|
||||
<PackageReference Include="JKorf.Coinbase.Net" Version="3.9.2" />
|
||||
<PackageReference Include="JKorf.HTX.Net" Version="8.9.1" />
|
||||
<PackageReference Include="JKorf.Upbit.Net" Version="2.9.2" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="7.9.1" />
|
||||
<PackageReference Include="Kucoin.Net" Version="8.11.0" />
|
||||
<PackageReference Include="Binance.Net" Version="12.13.0" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="10.12.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="3.11.2" />
|
||||
<PackageReference Include="BloFin.Net" Version="2.11.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="6.13.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="10.11.0" />
|
||||
<PackageReference Include="CoinW.Net" Version="2.10.0" />
|
||||
<PackageReference Include="CryptoCom.Net" Version="3.11.0" />
|
||||
<PackageReference Include="DeepCoin.Net" Version="3.10.0" />
|
||||
<PackageReference Include="GateIo.Net" Version="3.11.0" />
|
||||
<PackageReference Include="HyperLiquid.Net" Version="4.7.1" />
|
||||
<PackageReference Include="JK.BingX.Net" Version="3.11.1" />
|
||||
<PackageReference Include="JK.Bitget.Net" Version="3.12.1" />
|
||||
<PackageReference Include="JK.Mexc.Net" Version="5.2.0" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="4.14.0" />
|
||||
<PackageReference Include="Jkorf.Aster.Net" Version="3.3.0" />
|
||||
<PackageReference Include="JKorf.BitMEX.Net" Version="3.10.0" />
|
||||
<PackageReference Include="JKorf.Coinbase.Net" Version="3.10.1" />
|
||||
<PackageReference Include="JKorf.HTX.Net" Version="8.10.1" />
|
||||
<PackageReference Include="JKorf.Upbit.Net" Version="2.10.1" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="7.11.0" />
|
||||
<PackageReference Include="Kucoin.Net" Version="8.13.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Toobit.Net" Version="3.9.2" />
|
||||
<PackageReference Include="Weex.Net" Version="1.0.0" />
|
||||
<PackageReference Include="WhiteBit.Net" Version="3.9.2" />
|
||||
<PackageReference Include="XT.Net" Version="3.9.2" />
|
||||
<PackageReference Include="Toobit.Net" Version="3.10.0" />
|
||||
<PackageReference Include="Weex.Net" Version="1.1.1" />
|
||||
<PackageReference Include="WhiteBit.Net" Version="3.11.1" />
|
||||
<PackageReference Include="XT.Net" Version="3.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -16,13 +16,26 @@
|
||||
{
|
||||
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "BTC");
|
||||
var tasks = restClients.Select(x => x.GetSpotTickerAsync(new GetTickerRequest(symbol)));
|
||||
await Task.WhenAll(tasks);
|
||||
foreach (var ticker in tasks.Select(x => x.Result))
|
||||
await foreach (var ticker in ParallelEnumerateAsync(tasks))
|
||||
{
|
||||
if (ticker.Success)
|
||||
_prices.Add(ticker.Exchange, ticker.Data.LastPrice);
|
||||
else
|
||||
Debug.WriteLine($"{ticker.Exchange} failed: {ticker.Error}");
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<T> ParallelEnumerateAsync<T>(IEnumerable<Task<T>> tasks)
|
||||
{
|
||||
var remaining = new List<Task<T>>(tasks);
|
||||
|
||||
while (remaining.Count != 0)
|
||||
{
|
||||
var task = await Task.WhenAny(remaining).ConfigureAwait(false);
|
||||
remaining.Remove(task);
|
||||
yield return task.Result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,23 +3,13 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="12.11.0" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="10.10.1" />
|
||||
<PackageReference Include="BitMart.Net" Version="3.9.1" />
|
||||
<PackageReference Include="Bybit.Net" Version="6.10.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="10.9.1" />
|
||||
<PackageReference Include="CryptoCom.Net" Version="3.9.1" />
|
||||
<PackageReference Include="GateIo.Net" Version="3.10.1" />
|
||||
<PackageReference Include="JK.Bitget.Net" Version="3.9.0" />
|
||||
<PackageReference Include="JK.Mexc.Net" Version="4.9.0" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="4.10.1" />
|
||||
<PackageReference Include="JKorf.Coinbase.Net" Version="3.9.1" />
|
||||
<PackageReference Include="JKorf.HTX.Net" Version="8.9.0" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="7.9.0" />
|
||||
<PackageReference Include="Kucoin.Net" Version="8.10.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="12.13.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="6.13.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Binance.Net.Clients;
|
||||
using Binance.Net.Interfaces.Clients;
|
||||
using ConsoleClient.Models;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using CryptoExchange.Net.Sockets;
|
||||
|
||||
namespace ConsoleClient.Exchanges
|
||||
{
|
||||
internal class BinanceExchange : IExchange
|
||||
{
|
||||
private IBinanceSocketClient _socketClient = new BinanceSocketClient();
|
||||
|
||||
public async Task<WebCallResult> CancelOrder(string symbol, string id)
|
||||
{
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.Trading.CancelOrderAsync(symbol, long.Parse(id));
|
||||
return result.AsDataless();
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, decimal>> GetBalances()
|
||||
{
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.Account.GetAccountInfoAsync();
|
||||
return result.Data.Balances.ToDictionary(b => b.Asset, b => b.Total);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OpenOrder>> GetOpenOrders()
|
||||
{
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.Trading.GetOpenOrdersAsync();
|
||||
// Should check result success status here
|
||||
return result.Data.Select(o => new OpenOrder
|
||||
{
|
||||
Symbol = o.Symbol,
|
||||
OrderSide = o.Side.ToString(),
|
||||
OrderStatus = o.Status.ToString(),
|
||||
OrderTime = o.CreateTime,
|
||||
OrderType = o.Type.ToString(),
|
||||
Price = o.Price,
|
||||
Quantity = o.Quantity,
|
||||
QuantityFilled = o.QuantityFilled
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<decimal> GetPrice(string symbol)
|
||||
{
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.ExchangeData.GetPriceAsync(symbol);
|
||||
// Should check result success status here
|
||||
return result.Data.Price;
|
||||
}
|
||||
|
||||
public async Task<WebCallResult<string>> PlaceOrder(string symbol, string side, string type, decimal quantity, decimal? price)
|
||||
{
|
||||
using var client = new BinanceRestClient();
|
||||
var result = await client.SpotApi.Trading.PlaceOrderAsync(
|
||||
symbol,
|
||||
side.ToLower() == "buy" ? Binance.Net.Enums.OrderSide.Buy: Binance.Net.Enums.OrderSide.Sell,
|
||||
type == "market" ? Binance.Net.Enums.SpotOrderType.Market : Binance.Net.Enums.SpotOrderType.Limit,
|
||||
quantity,
|
||||
price: price,
|
||||
timeInForce: type == "market" ? null: Binance.Net.Enums.TimeInForce.GoodTillCanceled);
|
||||
return result.As(result.Data?.Id.ToString());
|
||||
}
|
||||
|
||||
public async Task<UpdateSubscription> SubscribePrice(string symbol, Action<decimal> handler)
|
||||
{
|
||||
var sub = await _socketClient.SpotApi.ExchangeData.SubscribeToMiniTickerUpdatesAsync(symbol, data => handler(data.Data.LastPrice));
|
||||
return sub.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using Bybit.Net.Clients;
|
||||
using Bybit.Net.Interfaces.Clients;
|
||||
using ConsoleClient.Models;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using CryptoExchange.Net.Sockets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ConsoleClient.Exchanges
|
||||
{
|
||||
internal class BybitExchange : IExchange
|
||||
{
|
||||
private IBybitSocketClient _socketClient = new BybitSocketClient();
|
||||
|
||||
public async Task<WebCallResult> CancelOrder(string symbol, string id)
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var result = await client.V5Api.Trading.CancelOrderAsync(Bybit.Net.Enums.Category.Spot, symbol, id);
|
||||
return result.AsDataless();
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, decimal>> GetBalances()
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var result = await client.V5Api.Account.GetBalancesAsync(Bybit.Net.Enums.AccountType.Spot);
|
||||
return result.Data.List.First().Assets.ToDictionary(d => d.Asset, d => d.WalletBalance ?? 0);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OpenOrder>> GetOpenOrders()
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var order = await client.V5Api.Trading.GetOrdersAsync(Bybit.Net.Enums.Category.Spot);
|
||||
return order.Data.List.Select(o => new OpenOrder
|
||||
{
|
||||
Symbol = o.Symbol,
|
||||
OrderSide = o.Side.ToString(),
|
||||
OrderStatus = o.Status.ToString(),
|
||||
OrderTime = o.CreateTime,
|
||||
OrderType = o.OrderType.ToString(),
|
||||
Price = o.Price ?? 0,
|
||||
Quantity = o.Quantity,
|
||||
QuantityFilled = o.QuantityFilled ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<decimal> GetPrice(string symbol)
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var result = await client.V5Api.ExchangeData.GetSpotTickersAsync(symbol);
|
||||
return result.Data.List.First().LastPrice;
|
||||
}
|
||||
|
||||
public async Task<WebCallResult<string>> PlaceOrder(string symbol, string side, string type, decimal quantity, decimal? price)
|
||||
{
|
||||
using var client = new BybitRestClient();
|
||||
var result = await client.V5Api.Trading.PlaceOrderAsync(
|
||||
Bybit.Net.Enums.Category.Spot,
|
||||
symbol,
|
||||
side.ToLower() == "buy" ? Bybit.Net.Enums.OrderSide.Buy : Bybit.Net.Enums.OrderSide.Sell,
|
||||
type == "market" ? Bybit.Net.Enums.NewOrderType.Market : Bybit.Net.Enums.NewOrderType.Limit,
|
||||
quantity,
|
||||
price: price);
|
||||
return result.As(result.Data?.OrderId.ToString());
|
||||
}
|
||||
|
||||
public async Task<UpdateSubscription> SubscribePrice(string symbol, Action<decimal> handler)
|
||||
{
|
||||
var sub = await _socketClient.V5SpotApi.SubscribeToTickerUpdatesAsync(symbol, data => handler(data.Data.LastPrice));
|
||||
return sub.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using ConsoleClient.Models;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using CryptoExchange.Net.Sockets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ConsoleClient.Exchanges
|
||||
{
|
||||
public interface IExchange
|
||||
{
|
||||
Task<decimal> GetPrice(string symbol);
|
||||
Task<IEnumerable<OpenOrder>> GetOpenOrders();
|
||||
Task<Dictionary<string, decimal>> GetBalances();
|
||||
Task<WebCallResult> CancelOrder(string symbol, string id);
|
||||
Task<WebCallResult<string>> PlaceOrder(string symbol, string side, string type, decimal quantity, decimal? price );
|
||||
Task<UpdateSubscription> SubscribePrice(string symbol, Action<decimal> handler);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ConsoleClient.Models
|
||||
{
|
||||
public class OpenOrder
|
||||
{
|
||||
public string Symbol { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal Quantity { get; set; }
|
||||
public decimal QuantityFilled { get; set; }
|
||||
public string OrderType { get; set; }
|
||||
public string OrderStatus { get; set; }
|
||||
public string OrderSide { get; set; }
|
||||
public DateTime OrderTime { get; set; }
|
||||
}
|
||||
}
|
||||
+292
-153
@@ -1,164 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Binance.Net;
|
||||
using Binance.Net.Clients;
|
||||
using Bybit.Net;
|
||||
using Bybit.Net.Clients;
|
||||
using ConsoleClient.Exchanges;
|
||||
using CryptoExchange.Net.Authentication;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
|
||||
namespace ConsoleClient
|
||||
var binanceRest = new BinanceRestClient(options =>
|
||||
{
|
||||
internal class Program
|
||||
options.ApiCredentials = new BinanceCredentials("KEY", "SECRET");
|
||||
});
|
||||
var bybitRest = new BybitRestClient(options =>
|
||||
{
|
||||
options.ApiCredentials = new BybitCredentials("KEY", "SECRET");
|
||||
});
|
||||
var binanceSocket = new BinanceSocketClient(options =>
|
||||
{
|
||||
options.ApiCredentials = new BinanceCredentials("KEY", "SECRET");
|
||||
});
|
||||
var bybitSocket = new BybitSocketClient(options =>
|
||||
{
|
||||
options.ApiCredentials = new BybitCredentials("KEY", "SECRET");
|
||||
});
|
||||
|
||||
var exchanges = new Dictionary<string, ExchangeClients>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["binance"] = new ExchangeClients(
|
||||
"Binance",
|
||||
binanceRest.SpotApi.SharedClient,
|
||||
binanceRest.SpotApi.SharedClient,
|
||||
binanceRest.SpotApi.SharedClient,
|
||||
binanceRest.SpotApi.SharedClient,
|
||||
binanceSocket.SpotApi.SharedClient),
|
||||
["bybit"] = new ExchangeClients(
|
||||
"Bybit",
|
||||
bybitRest.V5Api.SharedClient,
|
||||
bybitRest.V5Api.SharedClient,
|
||||
bybitRest.V5Api.SharedClient,
|
||||
bybitRest.V5Api.SharedClient,
|
||||
bybitSocket.V5SpotApi.SharedClient)
|
||||
};
|
||||
|
||||
PrintHelp();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.Write("> ");
|
||||
var input = Console.ReadLine();
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
continue;
|
||||
|
||||
var commandArgs = input.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var command = commandArgs[0].ToLowerInvariant();
|
||||
|
||||
try
|
||||
{
|
||||
static Dictionary<string, IExchange> _exchanges = new Dictionary<string, IExchange>
|
||||
switch (command)
|
||||
{
|
||||
{ "Binance", new Exchanges.BinanceExchange() },
|
||||
{ "Bybit", new Exchanges.BybitExchange() }
|
||||
};
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
BinanceRestClient.SetDefaultOptions(options =>
|
||||
{
|
||||
options.ApiCredentials = new BinanceCredentials("APIKEY", "APISECRET");
|
||||
});
|
||||
BybitRestClient.SetDefaultOptions(options =>
|
||||
{
|
||||
options.ApiCredentials = new BybitCredentials("APIKEY", "APISECRET");
|
||||
});
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("> Available commands: PlaceOrder, GetBalances, GetOpenOrders, CancelOrder, GetPrice, SubscribePrice");
|
||||
var input = Console.ReadLine();
|
||||
|
||||
switch (input)
|
||||
{
|
||||
case "PlaceOrder":
|
||||
await ProcessPlaceOrder();
|
||||
break;
|
||||
case "GetBalances":
|
||||
await ProcessGetBalances();
|
||||
break;
|
||||
case "GetOpenOrders":
|
||||
await ProcessGetOpenOrders();
|
||||
break;
|
||||
case "CancelOrder":
|
||||
await ProcessCancelOrder();
|
||||
break;
|
||||
case "GetPrice":
|
||||
await ProcessGetPrice();
|
||||
break;
|
||||
case "SubscribePrice":
|
||||
var sub = await ProcessSubscribePrice();
|
||||
Console.ReadLine();
|
||||
await sub.CloseAsync();
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("> Unknown command");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async Task ProcessPlaceOrder()
|
||||
{
|
||||
var exchange = GetInput("Exchange?");
|
||||
var symbol = GetInput("Symbol?");
|
||||
var side = GetInput("Buy/Sell?");
|
||||
var type = GetInput("Limit/Market?");
|
||||
var quantity = GetDecimalInput("Quantity?");
|
||||
decimal? price = null;
|
||||
if (type != "Market")
|
||||
price = GetDecimalInput("Price?");
|
||||
var result = await _exchanges[exchange].PlaceOrder(symbol, side, type, quantity, price);
|
||||
if (result.Success)
|
||||
Console.WriteLine("Order placed, ID: " + result.Data);
|
||||
else
|
||||
Console.WriteLine("Failed to place order: " + result.Error);
|
||||
}
|
||||
|
||||
static async Task ProcessGetBalances()
|
||||
{
|
||||
var exchange = GetInput("Exchange?");
|
||||
Console.WriteLine("Requesting balances...");
|
||||
var balances = await _exchanges[exchange].GetBalances();
|
||||
foreach (var balance in balances.Where(b => b.Value != 0))
|
||||
Console.WriteLine($"> {balance.Key}: {balance.Value}");
|
||||
}
|
||||
|
||||
static async Task ProcessGetOpenOrders()
|
||||
{
|
||||
var exchange = GetInput("Exchange?");
|
||||
Console.WriteLine("Requesting open orders...");
|
||||
var orders = await _exchanges[exchange].GetOpenOrders();
|
||||
if (!orders.Any())
|
||||
Console.WriteLine("No open orders found");
|
||||
else
|
||||
{
|
||||
foreach(var openOrder in orders)
|
||||
Console.WriteLine($"> {openOrder.OrderTime} - {openOrder.Symbol} {openOrder.OrderType} {openOrder.OrderSide} {openOrder.QuantityFilled}/{openOrder.Quantity} @ {openOrder.Price}");
|
||||
}
|
||||
}
|
||||
|
||||
static async Task ProcessCancelOrder()
|
||||
{
|
||||
var exchange = GetInput("Exchange?");
|
||||
var symbol = GetInput("Symbol?");
|
||||
var id = GetInput("Order id?");
|
||||
var result = await _exchanges[exchange].CancelOrder(symbol, id);
|
||||
if (result)
|
||||
Console.WriteLine("Order canceled");
|
||||
else
|
||||
Console.WriteLine("Order cancel failed: " + result.Error);
|
||||
}
|
||||
|
||||
static async Task ProcessGetPrice()
|
||||
{
|
||||
var exchange = GetInput("Exchange?");
|
||||
var symbol = GetInput("Symbol?");
|
||||
Console.WriteLine("Requesting price...");
|
||||
var price = await _exchanges[exchange].GetPrice(symbol);
|
||||
Console.WriteLine($"> {exchange} price for {symbol}: {price}");
|
||||
}
|
||||
|
||||
static async Task<UpdateSubscription> ProcessSubscribePrice()
|
||||
{
|
||||
var exchange = GetInput("Exchange?");
|
||||
var symbol = GetInput("Symbol?");
|
||||
Console.WriteLine("Subscribing price...");
|
||||
return await _exchanges[exchange].SubscribePrice(symbol, price => HandlePriceUpdate(exchange,symbol, price));
|
||||
}
|
||||
|
||||
static void HandlePriceUpdate(string exchange, string symbol, decimal price)
|
||||
{
|
||||
Console.Clear();
|
||||
Console.WriteLine($"> {exchange} price for {symbol}: {price}");
|
||||
Console.WriteLine("Press enter to stop live price updates");
|
||||
}
|
||||
|
||||
static decimal GetDecimalInput(string question)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("> " + question);
|
||||
var response = Console.ReadLine();
|
||||
if (decimal.TryParse(response, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var answer))
|
||||
return answer;
|
||||
else
|
||||
Console.WriteLine("Invalid decimal value");
|
||||
}
|
||||
}
|
||||
|
||||
static string GetInput(string question)
|
||||
{
|
||||
Console.WriteLine("> " + question);
|
||||
return Console.ReadLine();
|
||||
case "help":
|
||||
PrintHelp();
|
||||
break;
|
||||
case "exit":
|
||||
case "quit":
|
||||
return;
|
||||
case "prices":
|
||||
await PrintPricesAsync(commandArgs);
|
||||
break;
|
||||
case "stream":
|
||||
await StreamTickerAsync(commandArgs);
|
||||
break;
|
||||
case "orderbook":
|
||||
await PrintOrderBookAsync(commandArgs);
|
||||
break;
|
||||
case "balances":
|
||||
await PrintBalancesAsync(commandArgs);
|
||||
break;
|
||||
case "open-orders":
|
||||
await PrintOpenOrdersAsync(commandArgs);
|
||||
break;
|
||||
case "place-limit":
|
||||
await PlaceLimitOrderAsync(commandArgs);
|
||||
break;
|
||||
case "cancel-order":
|
||||
await CancelOrderAsync(commandArgs);
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("Unknown command. Type `help` for examples.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Command failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
async Task PrintPricesAsync(string[] args)
|
||||
{
|
||||
var symbol = GetSymbol(args, 1);
|
||||
var tasks = exchanges.Values.Select(async exchange =>
|
||||
{
|
||||
var result = await exchange.TickerRest.GetSpotTickerAsync(new GetTickerRequest(symbol));
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"{exchange.Name,-8} error: {result.Error}");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"{exchange.Name,-8} {result.Data.Symbol,-12} last={result.Data.LastPrice}");
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
async Task StreamTickerAsync(string[] args)
|
||||
{
|
||||
var exchange = GetExchange(args, 1);
|
||||
var symbol = GetSymbol(args, 2);
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
var result = await exchange.TickerSocket.SubscribeToTickerUpdatesAsync(
|
||||
new SubscribeTickerRequest(symbol),
|
||||
update => Console.WriteLine($"{exchange.Name,-8} {update.Data.Symbol,-12} last={update.Data.LastPrice}"),
|
||||
cts.Token);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"Subscription failed: {result.Error}");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Streaming ticker updates. Press Enter to stop.");
|
||||
Console.ReadLine();
|
||||
cts.Cancel();
|
||||
await result.Data.CloseAsync();
|
||||
}
|
||||
|
||||
async Task PrintOrderBookAsync(string[] args)
|
||||
{
|
||||
var exchange = GetExchange(args, 1);
|
||||
var symbol = GetSymbol(args, 2);
|
||||
|
||||
var result = await exchange.OrderBookRest.GetOrderBookAsync(new GetOrderBookRequest(symbol, limit: 5));
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"Order book request failed: {result.Error}");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"{exchange.Name} {symbol.BaseAsset}/{symbol.QuoteAsset} top of book");
|
||||
for (var i = 0; i < Math.Min(5, Math.Min(result.Data.Bids.Length, result.Data.Asks.Length)); i++)
|
||||
Console.WriteLine($"bid {result.Data.Bids[i].Price,15} | ask {result.Data.Asks[i].Price,15}");
|
||||
}
|
||||
|
||||
async Task PrintBalancesAsync(string[] args)
|
||||
{
|
||||
var exchange = GetExchange(args, 1);
|
||||
var result = await exchange.BalanceRest.GetBalancesAsync(new GetBalancesRequest(TradingMode.Spot));
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"Balances request failed: {result.Error}");
|
||||
Console.WriteLine("Set API credentials in environment variables before calling private endpoints.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var balance in result.Data.Where(x => x.Total != 0).OrderBy(x => x.Asset))
|
||||
Console.WriteLine($"{balance.Asset,-8} available={balance.Available} total={balance.Total}");
|
||||
}
|
||||
|
||||
async Task PrintOpenOrdersAsync(string[] args)
|
||||
{
|
||||
var exchange = GetExchange(args, 1);
|
||||
var symbol = GetSymbol(args, 2);
|
||||
|
||||
var result = await exchange.OrderRest.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol));
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"Open orders request failed: {result.Error}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Data.Length == 0)
|
||||
{
|
||||
Console.WriteLine("No open orders.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var order in result.Data)
|
||||
Console.WriteLine($"{order.OrderId,-24} {order.Side,-4} {order.OrderType,-6} {order.Status,-12} {order.OrderQuantity?.QuantityInBaseAsset} @ {order.OrderPrice}");
|
||||
}
|
||||
|
||||
async Task PlaceLimitOrderAsync(string[] args)
|
||||
{
|
||||
var exchange = GetExchange(args, 1);
|
||||
var symbol = GetSymbol(args, 2);
|
||||
var side = ParseSide(args.ElementAtOrDefault(4));
|
||||
var quantity = ParseDecimal(args.ElementAtOrDefault(5), "quantity");
|
||||
var price = ParseDecimal(args.ElementAtOrDefault(6), "price");
|
||||
|
||||
var request = new PlaceSpotOrderRequest(
|
||||
symbol,
|
||||
side,
|
||||
SharedOrderType.Limit,
|
||||
SharedQuantity.Base(quantity),
|
||||
price,
|
||||
SharedTimeInForce.GoodTillCanceled,
|
||||
exchange.OrderRest.GenerateClientOrderId());
|
||||
|
||||
var validationError = exchange.OrderRest.PlaceSpotOrderOptions.ValidateRequest(
|
||||
exchange.Name,
|
||||
request,
|
||||
TradingMode.Spot,
|
||||
exchange.OrderRest.SupportedTradingModes,
|
||||
exchange.OrderRest.SpotSupportedOrderTypes,
|
||||
exchange.OrderRest.SpotSupportedTimeInForce,
|
||||
exchange.OrderRest.SpotSupportedOrderQuantity);
|
||||
if (validationError != null)
|
||||
{
|
||||
Console.WriteLine($"Order request is not valid for {exchange.Name}: {validationError}");
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await exchange.OrderRest.PlaceSpotOrderAsync(request);
|
||||
Console.WriteLine(result.Success
|
||||
? $"Order placed. Id: {result.Data.Id}"
|
||||
: $"Order failed: {result.Error}");
|
||||
}
|
||||
|
||||
async Task CancelOrderAsync(string[] args)
|
||||
{
|
||||
var exchange = GetExchange(args, 1);
|
||||
var symbol = GetSymbol(args, 2);
|
||||
var orderId = args.ElementAtOrDefault(4);
|
||||
if (string.IsNullOrWhiteSpace(orderId))
|
||||
throw new ArgumentException("Missing order id. Example: cancel-order binance BTC USDT 123456");
|
||||
|
||||
var result = await exchange.OrderRest.CancelSpotOrderAsync(new CancelOrderRequest(symbol, orderId));
|
||||
Console.WriteLine(result.Success
|
||||
? $"Order canceled. Id: {result.Data.Id}"
|
||||
: $"Cancel failed: {result.Error}");
|
||||
}
|
||||
|
||||
ExchangeClients GetExchange(string[] args, int index)
|
||||
{
|
||||
var name = args.ElementAtOrDefault(index);
|
||||
if (name == null || !exchanges.TryGetValue(name, out var exchange))
|
||||
throw new ArgumentException($"Unknown exchange `{name}`. Use one of: {string.Join(", ", exchanges.Keys)}");
|
||||
|
||||
return exchange;
|
||||
}
|
||||
|
||||
SharedSymbol GetSymbol(string[] args, int index)
|
||||
{
|
||||
var baseAsset = args.ElementAtOrDefault(index) ?? "BTC";
|
||||
var quoteAsset = args.ElementAtOrDefault(index + 1) ?? "USDT";
|
||||
return new SharedSymbol(TradingMode.Spot, baseAsset.ToUpperInvariant(), quoteAsset.ToUpperInvariant());
|
||||
}
|
||||
|
||||
static decimal ParseDecimal(string? value, string name)
|
||||
{
|
||||
if (!decimal.TryParse(value, out var result))
|
||||
throw new ArgumentException($"Invalid {name} value `{value}`.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static SharedOrderSide ParseSide(string? value)
|
||||
{
|
||||
return value?.ToLowerInvariant() switch
|
||||
{
|
||||
"buy" => SharedOrderSide.Buy,
|
||||
"sell" => SharedOrderSide.Sell,
|
||||
_ => throw new ArgumentException("Side should be `buy` or `sell`.")
|
||||
};
|
||||
}
|
||||
|
||||
static void PrintHelp()
|
||||
{
|
||||
Console.WriteLine("CryptoExchange.Net SharedApis console example");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Public market data:");
|
||||
Console.WriteLine(" prices [base] [quote] e.g. prices BTC USDT");
|
||||
Console.WriteLine(" stream [exchange] [base] [quote] e.g. stream binance ETH USDT");
|
||||
Console.WriteLine(" orderbook [exchange] [base] [quote] e.g. orderbook bybit BTC USDT");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Private endpoints, credentials required:");
|
||||
Console.WriteLine(" balances [exchange]");
|
||||
Console.WriteLine(" open-orders [exchange] [base] [quote]");
|
||||
Console.WriteLine(" place-limit [exchange] [base] [quote] [buy|sell] [quantity] [price] --live");
|
||||
Console.WriteLine(" cancel-order [exchange] [base] [quote] [order-id]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Other:");
|
||||
Console.WriteLine(" help");
|
||||
Console.WriteLine(" exit");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
internal record ExchangeClients(
|
||||
string Name,
|
||||
ISpotTickerRestClient TickerRest,
|
||||
IOrderBookRestClient OrderBookRest,
|
||||
IBalanceRestClient BalanceRest,
|
||||
ISpotOrderRestClient OrderRest,
|
||||
ITickerSocketClient TickerSocket);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# CryptoExchange.Net Examples
|
||||
|
||||
| Path | Topic |
|
||||
| --- | --- | --- |
|
||||
| `SharedClients` | Small runnable exchange agnostic quickstart for REST tickers and websocket ticker updates |
|
||||
| `ConsoleClient` | Runnable CLI for some common operations |
|
||||
| `ai-friendly` | Compact snippets that are easy to paste into small apps or coding-assistant prompts |
|
||||
| `BlazorClient` | Server-side Blazor demo showing DI, live data, order books, and trackers |
|
||||
@@ -1,5 +1,6 @@
|
||||
using Binance.Net.Clients;
|
||||
using Binance.Net.Clients;
|
||||
using BitMart.Net.Clients;
|
||||
using CryptoExchange.Net.Objects.Sockets;
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
using OKX.Net.Clients;
|
||||
|
||||
@@ -20,11 +21,15 @@ Console.WriteLine();
|
||||
Console.WriteLine("Press enter to start websocket");
|
||||
Console.ReadLine();
|
||||
|
||||
await SubscribeTickerUpdatesAsync(binanceSpotSocketClient, symbol);
|
||||
await SubscribeTickerUpdatesAsync(okxSpotSocketClient, symbol);
|
||||
await SubscribeTickerUpdatesAsync(bitmartSpotSocketClient, symbol);
|
||||
var subscriptions = new List<UpdateSubscription>();
|
||||
await SubscribeTickerUpdatesAsync(binanceSpotSocketClient, symbol, subscriptions);
|
||||
await SubscribeTickerUpdatesAsync(okxSpotSocketClient, symbol, subscriptions);
|
||||
await SubscribeTickerUpdatesAsync(bitmartSpotSocketClient, symbol, subscriptions);
|
||||
|
||||
Console.WriteLine("Press enter to stop websocket updates");
|
||||
Console.ReadLine();
|
||||
foreach (var subscription in subscriptions)
|
||||
await subscription.CloseAsync();
|
||||
|
||||
async Task GetLastTradePriceAsync(ISpotTickerRestClient client, SharedSymbol symbol)
|
||||
{
|
||||
@@ -38,7 +43,7 @@ async Task GetLastTradePriceAsync(ISpotTickerRestClient client, SharedSymbol sym
|
||||
Console.WriteLine($"{client.Exchange} {result.Data.Symbol}: {result.Data.LastPrice}");
|
||||
}
|
||||
|
||||
async Task SubscribeTickerUpdatesAsync(ITickerSocketClient client, SharedSymbol symbol)
|
||||
async Task SubscribeTickerUpdatesAsync(ITickerSocketClient client, SharedSymbol symbol, ICollection<UpdateSubscription> subscriptions)
|
||||
{
|
||||
var result = await client.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequest(symbol), update =>
|
||||
{
|
||||
@@ -46,5 +51,10 @@ async Task SubscribeTickerUpdatesAsync(ITickerSocketClient client, SharedSymbol
|
||||
});
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
Console.WriteLine($"Failed to subscribe ticker: {result.Error}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
subscriptions.Add(result.Data);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="12.11.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="3.9.1" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="4.10.1" />
|
||||
<PackageReference Include="Binance.Net" Version="12.13.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="3.11.2" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="4.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user