Skip to content

Commit 53b09b7

Browse files
authored
Trackers (#121)
Updated examples Updated CryptoExchange.Net to v8.1.0 Moved FormatSymbol to HTXExchange class Added support Side setting on SharedTrade model Added HTXTrackerFactory Added overload to Create method on HTXOrderBookFactory support SharedSymbol parameter Fixed rate limiting incorrectly applied to websocket market data connections
1 parent cfed792 commit 53b09b7

24 files changed

+490
-31
lines changed

Examples/Examples.sln

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTX.Examples.Api", "HTX.Exa
77
EndProject
88
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTX.Examples.Console", "HTX.Examples.Console\HTX.Examples.Console.csproj", "{FD4F95C8-D9B7-4F81-9245-4CE667DFD421}"
99
EndProject
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTX.Examples.OrderBook", "HTX.Examples.OrderBook\HTX.Examples.OrderBook.csproj", "{B73A14C6-BED9-4C51-A2C4-6E5FA43741B2}"
11+
EndProject
12+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTX.Examples.Tracker", "HTX.Examples.Tracker\HTX.Examples.Tracker.csproj", "{4A6E7966-5E0C-4FFF-A941-987CF91577CC}"
13+
EndProject
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTX.Net", "..\HTX.Net\HTX.Net.csproj", "{1761E80A-9D75-4D7F-8A88-31E4499D6DB9}"
15+
EndProject
1016
Global
1117
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1218
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +27,18 @@ Global
2127
{FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Debug|Any CPU.Build.0 = Debug|Any CPU
2228
{FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Release|Any CPU.ActiveCfg = Release|Any CPU
2329
{FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Release|Any CPU.Build.0 = Release|Any CPU
30+
{B73A14C6-BED9-4C51-A2C4-6E5FA43741B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{B73A14C6-BED9-4C51-A2C4-6E5FA43741B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{B73A14C6-BED9-4C51-A2C4-6E5FA43741B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{B73A14C6-BED9-4C51-A2C4-6E5FA43741B2}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{4A6E7966-5E0C-4FFF-A941-987CF91577CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35+
{4A6E7966-5E0C-4FFF-A941-987CF91577CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
36+
{4A6E7966-5E0C-4FFF-A941-987CF91577CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
37+
{4A6E7966-5E0C-4FFF-A941-987CF91577CC}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{1761E80A-9D75-4D7F-8A88-31E4499D6DB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{1761E80A-9D75-4D7F-8A88-31E4499D6DB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{1761E80A-9D75-4D7F-8A88-31E4499D6DB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{1761E80A-9D75-4D7F-8A88-31E4499D6DB9}.Release|Any CPU.Build.0 = Release|Any CPU
2442
EndGlobalSection
2543
GlobalSection(SolutionProperties) = preSolution
2644
HideSolutionNode = FALSE
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net7.0</TargetFramework>
4+
<TargetFramework>net8.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<InvariantGlobalization>true</InvariantGlobalization>
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="JKorf.HTX.Net" Version="6.0.0" />
1211
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.0" />
1312
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
1413
</ItemGroup>
1514

15+
<ItemGroup>
16+
<ProjectReference Include="..\..\HTX.Net\HTX.Net.csproj" />
17+
</ItemGroup>
18+
1619
</Project>

Examples/HTX.Examples.Console/HTX.Examples.Console.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net7.0</TargetFramework>
5+
<TargetFramework>net8.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="JKorf.HTX.Net" Version="6.0.0" />
11+
<ProjectReference Include="..\..\HTX.Net\HTX.Net.csproj" />
1212
</ItemGroup>
1313

1414
</Project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Spectre.Console.Cli" Version="0.49.1" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\HTX.Net\HTX.Net.csproj" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using HTX.Net.Interfaces;
2+
using CryptoExchange.Net;
3+
using CryptoExchange.Net.SharedApis;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Spectre.Console;
6+
7+
var collection = new ServiceCollection();
8+
collection.AddHTX();
9+
var provider = collection.BuildServiceProvider();
10+
11+
var trackerFactory = provider.GetRequiredService<IHTXOrderBookFactory>();
12+
13+
// Creat and start the order book
14+
var book = trackerFactory.Create(new SharedSymbol(TradingMode.Spot, "ETH", "USDT"));
15+
var result = await book.StartAsync();
16+
if (!result.Success)
17+
{
18+
Console.WriteLine(result);
19+
return;
20+
}
21+
22+
// Create Spectre table
23+
var table = new Table();
24+
table.ShowRowSeparators = true;
25+
table.AddColumn("Bid Quantity", x => { x.RightAligned(); })
26+
.AddColumn("Bid Price", x => { x.RightAligned(); })
27+
.AddColumn("Ask Price", x => { x.LeftAligned(); })
28+
.AddColumn("Ask Quantity", x => { x.LeftAligned(); });
29+
30+
for(var i = 0; i < 10; i++)
31+
table.AddEmptyRow();
32+
33+
await AnsiConsole.Live(table)
34+
.StartAsync(async ctx =>
35+
{
36+
while (true)
37+
{
38+
var snapshot = book.Book;
39+
for (var i = 0; i < 10; i++)
40+
{
41+
var bid = snapshot.bids.ElementAt(i);
42+
var ask = snapshot.asks.ElementAt(i);
43+
table.UpdateCell(i, 0, ExchangeHelpers.Normalize(bid.Quantity).ToString());
44+
table.UpdateCell(i, 1, ExchangeHelpers.Normalize(bid.Price).ToString());
45+
table.UpdateCell(i, 2, ExchangeHelpers.Normalize(ask.Price).ToString());
46+
table.UpdateCell(i, 3, ExchangeHelpers.Normalize(ask.Quantity).ToString());
47+
}
48+
49+
ctx.Refresh();
50+
await Task.Delay(500);
51+
}
52+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Spectre.Console.Cli" Version="0.49.1" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\HTX.Net\HTX.Net.csproj" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using HTX.Net.Interfaces;
2+
using CryptoExchange.Net.SharedApis;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Spectre.Console;
5+
using System.Globalization;
6+
7+
var collection = new ServiceCollection();
8+
collection.AddHTX();
9+
var provider = collection.BuildServiceProvider();
10+
11+
var trackerFactory = provider.GetRequiredService<IHTXTrackerFactory>();
12+
13+
// Creat and start the tracker, keep track of the last 10 minutes
14+
var tracker = trackerFactory.CreateTradeTracker(new SharedSymbol(TradingMode.Spot, "ETH", "USDT"), period: TimeSpan.FromMinutes(10));
15+
var result = await tracker.StartAsync();
16+
if (!result.Success)
17+
{
18+
Console.WriteLine(result);
19+
return;
20+
}
21+
22+
// Create Spectre table
23+
var table = new Table();
24+
table.ShowRowSeparators = true;
25+
table.AddColumn("5 Min Data").AddColumn("-5 Min", x => { x.RightAligned(); })
26+
.AddColumn("Now", x => { x.RightAligned(); })
27+
.AddColumn("Dif", x => { x.RightAligned(); });
28+
29+
table.AddRow("Count", "", "", "");
30+
table.AddRow("Average price", "", "", "");
31+
table.AddRow("Average weighted price", "", "", "");
32+
table.AddRow("Buy/Sell Ratio", "", "", "");
33+
table.AddRow("Volume", "", "", "");
34+
table.AddRow("Value", "", "", "");
35+
table.AddRow("Complete", "", "", "");
36+
table.AddRow("", "", "", "");
37+
table.AddRow("Status", "", "", "");
38+
table.AddRow("Synced From", "", "", "");
39+
40+
// Set default culture for currency display
41+
CultureInfo ci = new CultureInfo("en-US");
42+
Thread.CurrentThread.CurrentCulture = ci;
43+
Thread.CurrentThread.CurrentUICulture = ci;
44+
45+
await AnsiConsole.Live(table)
46+
.StartAsync(async ctx =>
47+
{
48+
while (true)
49+
{
50+
// Get the stats from 10 minutes until 5 minutes ago
51+
var secondLastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-10), DateTime.UtcNow.AddMinutes(-5));
52+
53+
// Get the stats from 5 minutes ago until now
54+
var lastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-5));
55+
56+
// Get the differences between them
57+
var compare = secondLastMinute.CompareTo(lastMinute);
58+
59+
// Update the columns
60+
UpdateDec(0, 1, secondLastMinute.TradeCount);
61+
UpdateDec(0, 2, lastMinute.TradeCount);
62+
UpdateStr(0, 3, $"[{(compare.TradeCountDif.Difference < 0 ? "red" : "green")}]{compare.TradeCountDif.Difference} / {compare.TradeCountDif.PercentageDifference}%[/]");
63+
64+
UpdateStr(1, 1, secondLastMinute.AveragePrice?.ToString("C"));
65+
UpdateStr(1, 2, lastMinute.AveragePrice?.ToString("C"));
66+
UpdateStr(1, 3, $"[{(compare.AveragePriceDif?.Difference < 0 ? "red" : "green")}]{compare.AveragePriceDif?.Difference?.ToString("C")} / {compare.AveragePriceDif?.PercentageDifference}%[/]");
67+
68+
UpdateStr(2, 1, secondLastMinute.VolumeWeightedAveragePrice?.ToString("C"));
69+
UpdateStr(2, 2, lastMinute.VolumeWeightedAveragePrice?.ToString("C"));
70+
UpdateStr(2, 3, $"[{(compare.VolumeWeightedAveragePriceDif?.Difference < 0 ? "red" : "green")}]{compare.VolumeWeightedAveragePriceDif?.Difference?.ToString("C")} / {compare.VolumeWeightedAveragePriceDif?.PercentageDifference}%[/]");
71+
72+
UpdateDec(3, 1, secondLastMinute.BuySellRatio);
73+
UpdateDec(3, 2, lastMinute.BuySellRatio);
74+
UpdateStr(3, 3, $"[{(compare.BuySellRatioDif?.Difference < 0 ? "red" : "green")}]{compare.BuySellRatioDif?.Difference} / {compare.BuySellRatioDif?.PercentageDifference}%[/]");
75+
76+
UpdateDec(4, 1, secondLastMinute.Volume);
77+
UpdateDec(4, 2, lastMinute.Volume);
78+
UpdateStr(4, 3, $"[{(compare.VolumeDif.Difference < 0 ? "red" : "green")}]{compare.VolumeDif.Difference} / {compare.VolumeDif.PercentageDifference}%[/]");
79+
80+
UpdateStr(5, 1, secondLastMinute.QuoteVolume.ToString("C"));
81+
UpdateStr(5, 2, lastMinute.QuoteVolume.ToString("C"));
82+
UpdateStr(5, 3, $"[{(compare.QuoteVolumeDif.Difference < 0 ? "red" : "green")}]{compare.QuoteVolumeDif.Difference?.ToString("C")} / {compare.QuoteVolumeDif.PercentageDifference}%[/]");
83+
84+
UpdateStr(6, 1, secondLastMinute.Complete.ToString());
85+
UpdateStr(6, 2, lastMinute.Complete.ToString());
86+
87+
UpdateStr(8, 1, tracker.Status.ToString());
88+
UpdateStr(9, 1, tracker.SyncedFrom?.ToString());
89+
90+
ctx.Refresh();
91+
await Task.Delay(500);
92+
}
93+
});
94+
95+
96+
void UpdateDec(int row, int col, decimal? val)
97+
{
98+
table.UpdateCell(row, col, val?.ToString() ?? string.Empty);
99+
}
100+
101+
void UpdateStr(int row, int col, string? val)
102+
{
103+
table.UpdateCell(row, col, val ?? string.Empty);
104+
}

Examples/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@
44
A minimal API showing how to integrate HTX.Net in a web API project
55

66
### HTX.Examples.Console
7-
A simple console client demonstrating basic usage
7+
A simple console client demonstrating basic usage
8+
9+
### HTX.Examples.OrderBook
10+
Example of using the client side order book implementation
11+
12+
### HTX.Examples.Tracker
13+
Example of using the trade tracker

HTX.Net/Clients/SpotApi/HTXRestClientSpotApi.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,9 @@ internal HTXRestClientSpotApi(ILogger logger, HttpClient? httpClient, HTXRestOpt
6565

6666
protected override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer();
6767

68-
6968
/// <inheritdoc />
7069
public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null)
71-
{
72-
return $"{baseAsset.ToLowerInvariant()}{quoteAsset.ToLowerInvariant()}";
73-
}
70+
=> HTXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime);
7471

7572
/// <inheritdoc />
7673
protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)

HTX.Net/Clients/SpotApi/HTXRestClientSpotApiShared.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ async Task<ExchangeWebResult<IEnumerable<SharedTrade>>> IRecentTradeRestClient.G
148148
if (!result)
149149
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, null, default);
150150

151-
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.SelectMany(x => x.Details.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp))).ToArray());
151+
return result.AsExchangeResult<IEnumerable<SharedTrade>>(Exchange, request.Symbol.TradingMode, result.Data.SelectMany(x => x.Details.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)
152+
{
153+
Side = x.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell
154+
})).ToArray());
152155
}
153156

154157
#endregion

0 commit comments

Comments
 (0)