diff --git a/Examples/Examples.sln b/Examples/Examples.sln index 5f1a7a7..904ae53 100644 --- a/Examples/Examples.sln +++ b/Examples/Examples.sln @@ -7,6 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kraken.Examples.Api", "Krak EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kraken.Examples.Console", "Kraken.Examples.Console\Kraken.Examples.Console.csproj", "{FD4F95C8-D9B7-4F81-9245-4CE667DFD421}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kraken.Net", "..\Kraken.Net\Kraken.Net.csproj", "{00A92FF0-9CA4-4EBE-8642-18AC90974F3A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kraken.Examples.OrderBook", "Kraken.Examples.OrderBook\Kraken.Examples.OrderBook.csproj", "{9838F221-C2AC-4DF8-A1E7-58DD78EF027C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kraken.Examples.Tracker", "Kraken.Examples.Tracker\Kraken.Examples.Tracker.csproj", "{494D13E5-964C-4C0B-A44F-9B22584E78E6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +27,18 @@ Global {FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Debug|Any CPU.Build.0 = Debug|Any CPU {FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Release|Any CPU.ActiveCfg = Release|Any CPU {FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Release|Any CPU.Build.0 = Release|Any CPU + {00A92FF0-9CA4-4EBE-8642-18AC90974F3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00A92FF0-9CA4-4EBE-8642-18AC90974F3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00A92FF0-9CA4-4EBE-8642-18AC90974F3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00A92FF0-9CA4-4EBE-8642-18AC90974F3A}.Release|Any CPU.Build.0 = Release|Any CPU + {9838F221-C2AC-4DF8-A1E7-58DD78EF027C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9838F221-C2AC-4DF8-A1E7-58DD78EF027C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9838F221-C2AC-4DF8-A1E7-58DD78EF027C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9838F221-C2AC-4DF8-A1E7-58DD78EF027C}.Release|Any CPU.Build.0 = Release|Any CPU + {494D13E5-964C-4C0B-A44F-9B22584E78E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {494D13E5-964C-4C0B-A44F-9B22584E78E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {494D13E5-964C-4C0B-A44F-9B22584E78E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {494D13E5-964C-4C0B-A44F-9B22584E78E6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Examples/Kraken.Examples.Api/Kraken.Examples.Api.csproj b/Examples/Kraken.Examples.Api/Kraken.Examples.Api.csproj index 0e836b5..f3b04b5 100644 --- a/Examples/Kraken.Examples.Api/Kraken.Examples.Api.csproj +++ b/Examples/Kraken.Examples.Api/Kraken.Examples.Api.csproj @@ -1,16 +1,19 @@ - net7.0 + net8.0 enable enable true - + + + + diff --git a/Examples/Kraken.Examples.Console/Kraken.Examples.Console.csproj b/Examples/Kraken.Examples.Console/Kraken.Examples.Console.csproj index da25bfc..8852d8e 100644 --- a/Examples/Kraken.Examples.Console/Kraken.Examples.Console.csproj +++ b/Examples/Kraken.Examples.Console/Kraken.Examples.Console.csproj @@ -2,13 +2,13 @@ Exe - net7.0 + net8.0 enable enable - + diff --git a/Examples/Kraken.Examples.Console/Program.cs b/Examples/Kraken.Examples.Console/Program.cs index cd2b83c..419fcd2 100644 --- a/Examples/Kraken.Examples.Console/Program.cs +++ b/Examples/Kraken.Examples.Console/Program.cs @@ -19,7 +19,7 @@ var socketClient = new KrakenSocketClient(logFactory); var subscription = await socketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH/USDT", update => { - Console.WriteLine($"Websocket client ticker price for ETHUSDT: {update.Data.LastTrade.Price}"); + Console.WriteLine($"Websocket client ticker price for ETHUSDT: {update.Data.LastPrice}"); }); Console.ReadLine(); diff --git a/Examples/Kraken.Examples.OrderBook/Kraken.Examples.OrderBook.csproj b/Examples/Kraken.Examples.OrderBook/Kraken.Examples.OrderBook.csproj new file mode 100644 index 0000000..978d3ef --- /dev/null +++ b/Examples/Kraken.Examples.OrderBook/Kraken.Examples.OrderBook.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Examples/Kraken.Examples.OrderBook/Program.cs b/Examples/Kraken.Examples.OrderBook/Program.cs new file mode 100644 index 0000000..10f0dc5 --- /dev/null +++ b/Examples/Kraken.Examples.OrderBook/Program.cs @@ -0,0 +1,52 @@ +using Kraken.Net.Interfaces; +using CryptoExchange.Net; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; + +var collection = new ServiceCollection(); +collection.AddKraken(); +var provider = collection.BuildServiceProvider(); + +var bookFactory = provider.GetRequiredService(); + +// Creat and start the order book +var book = bookFactory.Create(new SharedSymbol(TradingMode.Spot, "ETH", "USD")); +var result = await book.StartAsync(); +if (!result.Success) +{ + Console.WriteLine(result.Error); + return; +} + +// Create Spectre table +var table = new Table(); +table.ShowRowSeparators = true; +table.AddColumn("Bid Quantity", x => { x.RightAligned(); }) + .AddColumn("Bid Price", x => { x.RightAligned(); }) + .AddColumn("Ask Price", x => { x.LeftAligned(); }) + .AddColumn("Ask Quantity", x => { x.LeftAligned(); }); + +for(var i = 0; i < 10; i++) + table.AddEmptyRow(); + +await AnsiConsole.Live(table) + .StartAsync(async ctx => + { + while (true) + { + var snapshot = book.Book; + for (var i = 0; i < 10; i++) + { + var bid = snapshot.bids.ElementAt(i); + var ask = snapshot.asks.ElementAt(i); + table.UpdateCell(i, 0, ExchangeHelpers.Normalize(bid.Quantity).ToString()); + table.UpdateCell(i, 1, ExchangeHelpers.Normalize(bid.Price).ToString()); + table.UpdateCell(i, 2, ExchangeHelpers.Normalize(ask.Price).ToString()); + table.UpdateCell(i, 3, ExchangeHelpers.Normalize(ask.Quantity).ToString()); + } + + ctx.Refresh(); + await Task.Delay(500); + } + }); diff --git a/Examples/Kraken.Examples.Tracker/Kraken.Examples.Tracker.csproj b/Examples/Kraken.Examples.Tracker/Kraken.Examples.Tracker.csproj new file mode 100644 index 0000000..978d3ef --- /dev/null +++ b/Examples/Kraken.Examples.Tracker/Kraken.Examples.Tracker.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Examples/Kraken.Examples.Tracker/Program.cs b/Examples/Kraken.Examples.Tracker/Program.cs new file mode 100644 index 0000000..09707ab --- /dev/null +++ b/Examples/Kraken.Examples.Tracker/Program.cs @@ -0,0 +1,104 @@ +using Kraken.Net.Interfaces; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; +using System.Globalization; + +var collection = new ServiceCollection(); +collection.AddKraken(); +var provider = collection.BuildServiceProvider(); + +var trackerFactory = provider.GetRequiredService(); + +// Creat and start the tracker, keep track of the last 10 minutes +var tracker = trackerFactory.CreateTradeTracker(new SharedSymbol(TradingMode.Spot, "ETH", "USD"), period: TimeSpan.FromMinutes(10)); +var result = await tracker.StartAsync(); +if (!result.Success) +{ + Console.WriteLine(result.Error); + return; +} + +// Create Spectre table +var table = new Table(); +table.ShowRowSeparators = true; +table.AddColumn("5 Min Data").AddColumn("-5 Min", x => { x.RightAligned(); }) + .AddColumn("Now", x => { x.RightAligned(); }) + .AddColumn("Dif", x => { x.RightAligned(); }); + +table.AddRow("Count", "", "", ""); +table.AddRow("Average price", "", "", ""); +table.AddRow("Average weighted price", "", "", ""); +table.AddRow("Buy/Sell Ratio", "", "", ""); +table.AddRow("Volume", "", "", ""); +table.AddRow("Value", "", "", ""); +table.AddRow("Complete", "", "", ""); +table.AddRow("", "", "", ""); +table.AddRow("Status", "", "", ""); +table.AddRow("Synced From", "", "", ""); + +// Set default culture for currency display +CultureInfo ci = new CultureInfo("en-US"); +Thread.CurrentThread.CurrentCulture = ci; +Thread.CurrentThread.CurrentUICulture = ci; + +await AnsiConsole.Live(table) + .StartAsync(async ctx => + { + while (true) + { + // Get the stats from 10 minutes until 5 minutes ago + var secondLastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-10), DateTime.UtcNow.AddMinutes(-5)); + + // Get the stats from 5 minutes ago until now + var lastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-5)); + + // Get the differences between them + var compare = secondLastMinute.CompareTo(lastMinute); + + // Update the columns + UpdateDec(0, 1, secondLastMinute.TradeCount); + UpdateDec(0, 2, lastMinute.TradeCount); + UpdateStr(0, 3, $"[{(compare.TradeCountDif.Difference < 0 ? "red" : "green")}]{compare.TradeCountDif.Difference} / {compare.TradeCountDif.PercentageDifference}%[/]"); + + UpdateStr(1, 1, secondLastMinute.AveragePrice?.ToString("C")); + UpdateStr(1, 2, lastMinute.AveragePrice?.ToString("C")); + UpdateStr(1, 3, $"[{(compare.AveragePriceDif?.Difference < 0 ? "red" : "green")}]{compare.AveragePriceDif?.Difference?.ToString("C")} / {compare.AveragePriceDif?.PercentageDifference}%[/]"); + + UpdateStr(2, 1, secondLastMinute.VolumeWeightedAveragePrice?.ToString("C")); + UpdateStr(2, 2, lastMinute.VolumeWeightedAveragePrice?.ToString("C")); + UpdateStr(2, 3, $"[{(compare.VolumeWeightedAveragePriceDif?.Difference < 0 ? "red" : "green")}]{compare.VolumeWeightedAveragePriceDif?.Difference?.ToString("C")} / {compare.VolumeWeightedAveragePriceDif?.PercentageDifference}%[/]"); + + UpdateDec(3, 1, secondLastMinute.BuySellRatio); + UpdateDec(3, 2, lastMinute.BuySellRatio); + UpdateStr(3, 3, $"[{(compare.BuySellRatioDif?.Difference < 0 ? "red" : "green")}]{compare.BuySellRatioDif?.Difference} / {compare.BuySellRatioDif?.PercentageDifference}%[/]"); + + UpdateDec(4, 1, secondLastMinute.Volume); + UpdateDec(4, 2, lastMinute.Volume); + UpdateStr(4, 3, $"[{(compare.VolumeDif.Difference < 0 ? "red" : "green")}]{compare.VolumeDif.Difference} / {compare.VolumeDif.PercentageDifference}%[/]"); + + UpdateStr(5, 1, secondLastMinute.QuoteVolume.ToString("C")); + UpdateStr(5, 2, lastMinute.QuoteVolume.ToString("C")); + UpdateStr(5, 3, $"[{(compare.QuoteVolumeDif.Difference < 0 ? "red" : "green")}]{compare.QuoteVolumeDif.Difference?.ToString("C")} / {compare.QuoteVolumeDif.PercentageDifference}%[/]"); + + UpdateStr(6, 1, secondLastMinute.Complete.ToString()); + UpdateStr(6, 2, lastMinute.Complete.ToString()); + + UpdateStr(8, 1, tracker.Status.ToString()); + UpdateStr(9, 1, tracker.SyncedFrom?.ToString()); + + ctx.Refresh(); + await Task.Delay(500); + } + }); + + +void UpdateDec(int row, int col, decimal? val) +{ + table.UpdateCell(row, col, val?.ToString() ?? string.Empty); +} + +void UpdateStr(int row, int col, string? val) +{ + table.UpdateCell(row, col, val ?? string.Empty); +} diff --git a/Examples/README.md b/Examples/README.md index be0558c..e32e768 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -4,4 +4,10 @@ A minimal API showing how to integrate Kraken.Net in a web API project ### Kraken.Examples.Console -A simple console client demonstrating basic usage \ No newline at end of file +A simple console client demonstrating basic usage + +### Kraken.Examples.OrderBook +Example of using the client side order book implementation + +### Kraken.Examples.Tracker +Example of using the trade tracker \ No newline at end of file diff --git a/Kraken.Net/Kraken.Net.csproj b/Kraken.Net/Kraken.Net.csproj index 20a8072..0f7f803 100644 --- a/Kraken.Net/Kraken.Net.csproj +++ b/Kraken.Net/Kraken.Net.csproj @@ -51,12 +51,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - \ No newline at end of file diff --git a/Kraken.Net/Kraken.Net.xml b/Kraken.Net/Kraken.Net.xml index a319336..f536d90 100644 --- a/Kraken.Net/Kraken.Net.xml +++ b/Kraken.Net/Kraken.Net.xml @@ -3772,6 +3772,11 @@ + + + ctor + + ctor diff --git a/Kraken.Net/SymbolOrderBooks/KrakenOrderBookFactory.cs b/Kraken.Net/SymbolOrderBooks/KrakenOrderBookFactory.cs index 699219f..14f6874 100644 --- a/Kraken.Net/SymbolOrderBooks/KrakenOrderBookFactory.cs +++ b/Kraken.Net/SymbolOrderBooks/KrakenOrderBookFactory.cs @@ -37,10 +37,10 @@ public KrakenOrderBookFactory(IServiceProvider serviceProvider) /// public ISymbolOrderBook Create(SharedSymbol symbol, Action? options = null) { - var symbolName = KrakenExchange.FormatSymbol(symbol.BaseAsset, symbol.QuoteAsset, symbol.TradingMode, symbol.DeliverTime); if (symbol.TradingMode == TradingMode.Spot) - return CreateSpot(symbolName, options); + return CreateSpot(symbol.BaseAsset + "/" + symbol.QuoteAsset, options); + var symbolName = KrakenExchange.FormatSymbol(symbol.BaseAsset, symbol.QuoteAsset, symbol.TradingMode, symbol.DeliverTime); return CreateFutures(symbolName, options); }