mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-12 17:03:10 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0a2288910 | |||
| 89c11afc21 | |||
| da6ed580f1 | |||
| 1c33e297e7 |
@@ -108,5 +108,33 @@ namespace CryptoExchange.Net.UnitTests
|
||||
Assert.AreEqual(1.06666667m, resultBids2.Data);
|
||||
Assert.AreEqual(1.23333333m, resultAsks2.Data);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CalculateTradableAmount()
|
||||
{
|
||||
var orderbook = new TestableSymbolOrderBook();
|
||||
orderbook.SetData(new List<ISymbolOrderBookEntry>
|
||||
{
|
||||
new BookEntry{ Price = 1, Quantity = 1 },
|
||||
new BookEntry{ Price = 1.1m, Quantity = 1 },
|
||||
},
|
||||
new List<ISymbolOrderBookEntry>()
|
||||
{
|
||||
new BookEntry{ Price = 1.2m, Quantity = 1 },
|
||||
new BookEntry{ Price = 1.3m, Quantity = 1 },
|
||||
});
|
||||
|
||||
var resultBids = orderbook.CalculateTradableAmount(2, OrderBookEntryType.Bid);
|
||||
var resultAsks = orderbook.CalculateTradableAmount(2, OrderBookEntryType.Ask);
|
||||
var resultBids2 = orderbook.CalculateTradableAmount(1.5m, OrderBookEntryType.Bid);
|
||||
var resultAsks2 = orderbook.CalculateTradableAmount(1.5m, OrderBookEntryType.Ask);
|
||||
|
||||
Assert.True(resultBids.Success);
|
||||
Assert.True(resultAsks.Success);
|
||||
Assert.AreEqual(1.9m, resultBids.Data);
|
||||
Assert.AreEqual(1.61538462m, resultAsks.Data);
|
||||
Assert.AreEqual(1.4m, resultBids2.Data);
|
||||
Assert.AreEqual(1.23076923m, resultAsks2.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace CryptoExchange.Net
|
||||
{
|
||||
foreach (var limiter in RateLimiters)
|
||||
{
|
||||
var limitResult = await limiter.LimitRequestAsync(_log, uri.AbsolutePath, method, signed, Options.ApiCredentials?.Key, Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false);
|
||||
var limitResult = await limiter.LimitRequestAsync(_log, uri.AbsolutePath, method, signed, Options.ApiCredentials?.Key ?? ClientOptions.ApiCredentials?.Key, Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false);
|
||||
if (!limitResult.Success)
|
||||
return new CallResult<IRequest>(limitResult.Error!);
|
||||
}
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<PackageId>CryptoExchange.Net</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>A base package for implementing cryptocurrency API's</Description>
|
||||
<PackageVersion>5.4.0</PackageVersion>
|
||||
<AssemblyVersion>5.4.0</AssemblyVersion>
|
||||
<FileVersion>5.4.0</FileVersion>
|
||||
<PackageVersion>5.4.1</PackageVersion>
|
||||
<AssemblyVersion>5.4.1</AssemblyVersion>
|
||||
<FileVersion>5.4.1</FileVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageReleaseNotes>5.4.0 - Added unsubscribing when receiving subscribe answer after the request timeout has passed, Fixed socket options copying, Made TimeSync implementation optional, Cleaned up ApiCredentials and added better support for extending ApiCredentials</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>5.4.1 - Added CalculateTradableAmount to SymbolOrderBook, Improved socket reconnect robustness, Fixed api rate limiter not working correctly</PackageReleaseNotes>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
@@ -101,13 +101,22 @@ namespace CryptoExchange.Net.Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Get the average price that a market order would fill at at the current order book state. This is no guarentee that an order of that quantity would actually be filled
|
||||
/// at that price since between this calculation and the order placement the book can have changed.
|
||||
/// at that price since between this calculation and the order placement the book might have changed.
|
||||
/// </summary>
|
||||
/// <param name="quantity">The quantity in base asset to fill</param>
|
||||
/// <param name="type">The type</param>
|
||||
/// <returns>Average fill price</returns>
|
||||
CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type);
|
||||
|
||||
/// <summary>
|
||||
/// Get the amount of base asset which can be traded with the quote quantity when placing a market order at at the current order book state.
|
||||
/// This is no guarentee that an order of that quantity would actually be fill the quantity returned by this since between this calculation and the order placement the book might have changed.
|
||||
/// </summary>
|
||||
/// <param name="quoteQuantity">The quantity in quote asset looking to trade</param>
|
||||
/// <param name="type">The type</param>
|
||||
/// <returns>Amount of base asset tradable with the specified amount of quote asset</returns>
|
||||
CallResult<decimal> CalculateTradableAmount(decimal quoteQuantity, OrderBookEntryType type);
|
||||
|
||||
/// <summary>
|
||||
/// String representation of the top x entries
|
||||
/// </summary>
|
||||
|
||||
@@ -304,14 +304,14 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type)
|
||||
public CallResult<decimal> CalculateAverageFillPrice(decimal baseQuantity, OrderBookEntryType type)
|
||||
{
|
||||
if (Status != OrderBookStatus.Synced)
|
||||
return new CallResult<decimal>(new InvalidOperationError($"{nameof(CalculateAverageFillPrice)} is not available when book is not in Synced state"));
|
||||
|
||||
var totalCost = 0m;
|
||||
var totalAmount = 0m;
|
||||
var amountLeft = quantity;
|
||||
var amountLeft = baseQuantity;
|
||||
lock (_bookLock)
|
||||
{
|
||||
var list = type == OrderBookEntryType.Ask ? asks : bids;
|
||||
@@ -334,6 +334,35 @@ namespace CryptoExchange.Net.OrderBook
|
||||
return new CallResult<decimal>(Math.Round(totalCost / totalAmount, 8));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CallResult<decimal> CalculateTradableAmount(decimal quoteQuantity, OrderBookEntryType type)
|
||||
{
|
||||
if (Status != OrderBookStatus.Synced)
|
||||
return new CallResult<decimal>(new InvalidOperationError($"{nameof(CalculateTradableAmount)} is not available when book is not in Synced state"));
|
||||
|
||||
var quoteQuantityLeft = quoteQuantity;
|
||||
var totalBaseQuantity = 0m;
|
||||
lock (_bookLock)
|
||||
{
|
||||
var list = type == OrderBookEntryType.Ask ? asks : bids;
|
||||
|
||||
var step = 0;
|
||||
while (quoteQuantityLeft > 0)
|
||||
{
|
||||
if (step == list.Count)
|
||||
return new CallResult<decimal>(new InvalidOperationError("Quantity is larger than order in the order book"));
|
||||
|
||||
var element = list.ElementAt(step);
|
||||
var stepAmount = Math.Min(element.Value.Quantity * element.Value.Price, quoteQuantityLeft);
|
||||
quoteQuantityLeft -= stepAmount;
|
||||
totalBaseQuantity += stepAmount / element.Value.Price;
|
||||
step++;
|
||||
}
|
||||
}
|
||||
|
||||
return new CallResult<decimal>(Math.Round(totalBaseQuantity, 8));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation for starting the order book. Should typically have logic for subscribing to the update stream and retrieving
|
||||
/// and setting the initial order book
|
||||
|
||||
@@ -211,8 +211,16 @@ namespace CryptoExchange.Net.Sockets
|
||||
while (_closeTask == null)
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
|
||||
await _closeTask.ConfigureAwait(false);
|
||||
_closeTask = null;
|
||||
await _closeSem.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _closeTask.ConfigureAwait(false);
|
||||
_closeTask = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_closeSem.Release();
|
||||
}
|
||||
|
||||
if (!Parameters.AutoReconnect)
|
||||
{
|
||||
@@ -285,8 +293,19 @@ namespace CryptoExchange.Net.Sockets
|
||||
return;
|
||||
|
||||
_log.Write(LogLevel.Debug, $"Socket {Id} reconnect requested");
|
||||
_closeTask = CloseInternalAsync();
|
||||
await _closeTask.ConfigureAwait(false);
|
||||
await _closeSem.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_processState != ProcessState.Processing)
|
||||
return;
|
||||
|
||||
_closeTask = CloseInternalAsync();
|
||||
await _closeTask.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_closeSem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -430,8 +449,19 @@ namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
// Connection closed unexpectedly, .NET framework
|
||||
OnError?.Invoke(ioe);
|
||||
if (_closeTask?.IsCompleted != false)
|
||||
_closeTask = CloseInternalAsync();
|
||||
await _closeSem.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_processState != ProcessState.Processing)
|
||||
return;
|
||||
|
||||
if (_closeTask?.IsCompleted != false)
|
||||
_closeTask = CloseInternalAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_closeSem.Release();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -488,8 +518,17 @@ namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
// Connection closed unexpectedly
|
||||
OnError?.Invoke(wse);
|
||||
if (_closeTask?.IsCompleted != false)
|
||||
_closeTask = CloseInternalAsync();
|
||||
await _closeSem.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_processState == ProcessState.Processing && _closeTask?.IsCompleted != false)
|
||||
_closeTask = CloseInternalAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_closeSem.Release();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -497,8 +536,17 @@ namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
// Connection closed unexpectedly
|
||||
_log.Write(LogLevel.Debug, $"Socket {Id} received `Close` message");
|
||||
if (_closeTask?.IsCompleted != false)
|
||||
_closeTask = CloseInternalAsync();
|
||||
await _closeSem.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_processState == ProcessState.Processing && _closeTask?.IsCompleted != false)
|
||||
_closeTask = CloseInternalAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_closeSem.Release();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
var reconnectSuccessful = await ProcessReconnectAsync().ConfigureAwait(false);
|
||||
if (!reconnectSuccessful)
|
||||
{
|
||||
_log.Write(LogLevel.Warning, "Failed reconnect processing, reconnecting again");
|
||||
_log.Write(LogLevel.Warning, $"Failed reconnect processing: {reconnectSuccessful.Error}, reconnecting again");
|
||||
await _socket.ReconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -33,6 +33,11 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
|
||||
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
||||
|
||||
## Release notes
|
||||
* Version 5.4.1 - 18 Mar 2023
|
||||
* Added CalculateTradableAmount to SymbolOrderBook
|
||||
* Improved socket reconnect robustness
|
||||
* Fixed api rate limiter not working correctly
|
||||
|
||||
* Version 5.4.0 - 14 Feb 2023
|
||||
* Added unsubscribing when receiving subscribe answer after the request timeout has passed
|
||||
* Fixed socket options copying
|
||||
|
||||
Reference in New Issue
Block a user