1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-09 00:46:19 +00:00
This commit is contained in:
Eric Garnier 2020-12-19 17:50:50 +01:00
commit 9deae358ac
37 changed files with 1169 additions and 130 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: JKorf

20
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,20 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
What endpoints and subscriptions are called.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Debug logging**
Add debug logging related to the issue. Enable Debug logging in the client options by settings LogVerbosity to Debug.

View File

@ -113,7 +113,7 @@ namespace CryptoExchange.Net.UnitTests
// assert
Assert.IsTrue(client.BaseAddress == "http://test.address.com");
Assert.IsTrue(client.BaseAddress == "http://test.address.com/");
Assert.IsTrue(client.RateLimiters.Count() == 1);
Assert.IsTrue(client.RateLimitBehaviour == RateLimitingBehaviour.Fail);
Assert.IsTrue(client.RequestTimeout == TimeSpan.FromMinutes(1));

View File

@ -25,7 +25,7 @@ namespace CryptoExchange.Net.UnitTests
//assert
Assert.IsTrue(client.BaseAddress == "http://test.address.com");
Assert.IsTrue(client.BaseAddress == "http://test.address.com/");
Assert.IsTrue(client.ReconnectInterval.TotalSeconds == 6);
}

View File

@ -9,11 +9,11 @@ namespace CryptoExchange.Net.UnitTests
{
public class TestBaseClient: BaseClient
{
public TestBaseClient(): base(new RestClientOptions("http://testurl.url"), null)
public TestBaseClient(): base("Test", new RestClientOptions("http://testurl.url"), null)
{
}
public TestBaseClient(RestClientOptions exchangeOptions) : base(exchangeOptions, exchangeOptions.ApiCredentials == null ? null : new TestAuthProvider(exchangeOptions.ApiCredentials))
public TestBaseClient(RestClientOptions exchangeOptions) : base("Test", exchangeOptions, exchangeOptions.ApiCredentials == null ? null : new TestAuthProvider(exchangeOptions.ApiCredentials))
{
}

View File

@ -16,12 +16,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
{
public class TestRestClient: RestClient
{
public TestRestClient() : base(new RestClientOptions("http://testurl.url"), null)
public TestRestClient() : base("Test", new RestClientOptions("http://testurl.url"), null)
{
RequestFactory = new Mock<IRequestFactory>().Object;
}
public TestRestClient(RestClientOptions exchangeOptions) : base(exchangeOptions, exchangeOptions.ApiCredentials == null ? null : new TestAuthProvider(exchangeOptions.ApiCredentials))
public TestRestClient(RestClientOptions exchangeOptions) : base("Test", exchangeOptions, exchangeOptions.ApiCredentials == null ? null : new TestAuthProvider(exchangeOptions.ApiCredentials))
{
RequestFactory = new Mock<IRequestFactory>().Object;
}

View File

@ -15,7 +15,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
{
}
public TestSocketClient(SocketClientOptions exchangeOptions) : base(exchangeOptions, exchangeOptions.ApiCredentials == null ? null : new TestAuthProvider(exchangeOptions.ApiCredentials))
public TestSocketClient(SocketClientOptions exchangeOptions) : base("test", exchangeOptions, exchangeOptions.ApiCredentials == null ? null : new TestAuthProvider(exchangeOptions.ApiCredentials))
{
SocketFactory = new Mock<IWebsocketFactory>().Object;
Mock.Get(SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny<Log>(), It.IsAny<string>())).Returns(new TestSocket());

View File

@ -80,7 +80,7 @@ namespace CryptoExchange.Net.Authentication
/// <param name="identifierSecret">A key to identify the credentials for the API. For example, when set to `binanceSecret` the json data should contain a value for the property `binanceSecret`. Defaults to 'apiSecret'.</param>
public ApiCredentials(Stream inputStream, string? identifierKey = null, string? identifierSecret = null)
{
using var reader = new StreamReader(inputStream, Encoding.ASCII, false, 512, true);
using var reader = new StreamReader(inputStream, Encoding.UTF8, false, 512, true);
var stringData = reader.ReadToEnd();
var jsonData = stringData.ToJToken();
@ -109,7 +109,7 @@ namespace CryptoExchange.Net.Authentication
{
if (data[key] == null)
return null;
return (string) data[key];
return (string) data[key]!;
}
/// <summary>

View File

@ -25,6 +25,10 @@ namespace CryptoExchange.Net
/// </summary>
public string BaseAddress { get; }
/// <summary>
/// The name of the client
/// </summary>
public string ClientName { get; }
/// <summary>
/// The log object
/// </summary>
protected internal Log log;
@ -64,15 +68,17 @@ namespace CryptoExchange.Net
/// <summary>
/// ctor
/// </summary>
/// <param name="clientName"></param>
/// <param name="options"></param>
/// <param name="authenticationProvider"></param>
protected BaseClient(ClientOptions options, AuthenticationProvider? authenticationProvider)
protected BaseClient(string clientName, ClientOptions options, AuthenticationProvider? authenticationProvider)
{
log = new Log();
log = new Log(clientName);
authProvider = authenticationProvider;
log.UpdateWriters(options.LogWriters);
log.Level = options.LogVerbosity;
ClientName = clientName;
BaseAddress = options.BaseAddress;
apiProxy = options.Proxy;
@ -101,7 +107,7 @@ namespace CryptoExchange.Net
{
var info = "Empty data object received";
log.Write(LogVerbosity.Error, info);
return new CallResult<JToken>(null, new DeserializeError(info));
return new CallResult<JToken>(null, new DeserializeError(info, data));
}
try
@ -110,18 +116,18 @@ namespace CryptoExchange.Net
}
catch (JsonReaderException jre)
{
var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Data: {data}";
return new CallResult<JToken>(null, new DeserializeError(info));
var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}";
return new CallResult<JToken>(null, new DeserializeError(info, data));
}
catch (JsonSerializationException jse)
{
var info = $"Deserialize JsonSerializationException: {jse.Message}. Data: {data}";
return new CallResult<JToken>(null, new DeserializeError(info));
var info = $"Deserialize JsonSerializationException: {jse.Message}";
return new CallResult<JToken>(null, new DeserializeError(info, data));
}
catch (Exception ex)
{
var info = $"Deserialize Unknown Exception: {(ex.InnerException?.Message ?? ex.Message)}. Data: {data}";
return new CallResult<JToken>(null, new DeserializeError(info));
var info = $"Deserialize Unknown Exception: {(ex.InnerException?.Message ?? ex.Message)}";
return new CallResult<JToken>(null, new DeserializeError(info, data));
}
}
@ -186,21 +192,21 @@ namespace CryptoExchange.Net
}
catch (JsonReaderException jre)
{
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Received data: {obj}";
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}";
log.Write(LogVerbosity.Error, info);
return new CallResult<T>(default, new DeserializeError(info));
return new CallResult<T>(default, new DeserializeError(info, obj));
}
catch (JsonSerializationException jse)
{
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}. Received data: {obj}";
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}";
log.Write(LogVerbosity.Error, info);
return new CallResult<T>(default, new DeserializeError(info));
return new CallResult<T>(default, new DeserializeError(info, obj));
}
catch (Exception ex)
{
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {(ex.InnerException?.Message ?? ex.Message)}. Received data: {obj}";
var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {(ex.InnerException?.Message ?? ex.Message)}";
log.Write(LogVerbosity.Error, info);
return new CallResult<T>(default, new DeserializeError(info));
return new CallResult<T>(default, new DeserializeError(info, obj));
}
}
@ -211,7 +217,7 @@ namespace CryptoExchange.Net
/// <param name="stream">The stream to deserialize</param>
/// <param name="serializer">A specific serializer to use</param>
/// <param name="requestId">Id of the request</param>
/// <param name="elapsedMilliseconds">Milliseconds reponse time</param>
/// <param name="elapsedMilliseconds">Milliseconds response time</param>
/// <returns></returns>
protected async Task<CallResult<T>> Deserialize<T>(Stream stream, JsonSerializer? serializer = null, int? requestId = null, long? elapsedMilliseconds = null)
{
@ -237,7 +243,7 @@ namespace CryptoExchange.Net
stream.Seek(0, SeekOrigin.Begin);
var data = await ReadStream(stream).ConfigureAwait(false);
log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {data}");
return new CallResult<T>(default, new DeserializeError(data));
return new CallResult<T>(default, new DeserializeError($"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}", data));
}
catch (JsonSerializationException jse)
{
@ -245,7 +251,7 @@ namespace CryptoExchange.Net
stream.Seek(0, SeekOrigin.Begin);
var data = await ReadStream(stream).ConfigureAwait(false);
log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}, data: {data}");
return new CallResult<T>(default, new DeserializeError(data));
return new CallResult<T>(default, new DeserializeError($"Deserialize JsonSerializationException: {jse.Message}", data));
}
catch (Exception ex)
{
@ -253,7 +259,7 @@ namespace CryptoExchange.Net
stream.Seek(0, SeekOrigin.Begin);
var data = await ReadStream(stream).ConfigureAwait(false);
log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {(ex.InnerException?.Message ?? ex.Message)}, data: {data}");
return new CallResult<T>(default, new DeserializeError(data));
return new CallResult<T>(default, new DeserializeError($"Deserialize Unknown Exception: {(ex.InnerException?.Message ?? ex.Message)}", data));
}
}
@ -291,7 +297,8 @@ namespace CryptoExchange.Net
if (ignore != null)
continue;
properties.Add(attr == null ? prop.Name : ((JsonPropertyAttribute)attr).PropertyName);
var propertyName = ((JsonPropertyAttribute?) attr)?.PropertyName;
properties.Add(propertyName ?? prop.Name);
}
foreach (var token in obj)
{
@ -313,12 +320,12 @@ namespace CryptoExchange.Net
properties.Remove(d);
var propType = GetProperty(d, props)?.PropertyType;
if (propType == null)
if (propType == null || token.Value == null)
continue;
if (!IsSimple(propType) && propType != typeof(DateTime))
{
if (propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject)
CheckObject(propType.GetElementType(), (JObject)token.Value[0], requestId);
CheckObject(propType.GetElementType()!, (JObject)token.Value[0]!, requestId);
else if (token.Value is JObject o)
CheckObject(propType, o, requestId);
}

View File

@ -21,7 +21,7 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (objectType == typeof(JToken))
return JToken.Load(reader);
@ -115,8 +115,11 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
return;
writer.WriteStartArray();
var props = value.GetType().GetProperties();
var ordered = props.OrderBy(p => p.GetCustomAttribute<ArrayPropertyAttribute>()?.Index);

View File

@ -28,9 +28,9 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
var stringValue = GetValue((T) value);
var stringValue = value == null? null: GetValue((T) value);
if (quotes)
writer.WriteValue(stringValue);
else
@ -38,7 +38,7 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return null;

View File

@ -15,7 +15,7 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return null;
@ -25,9 +25,12 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).TotalMilliseconds));
if(value == null)
writer.WriteValue((DateTime?)null);
else
writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).TotalMilliseconds));
}
}
}

View File

@ -17,7 +17,7 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return null;
@ -27,9 +27,9 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).Ticks / ticksPerNanosecond));
writer.WriteValue((long)Math.Round(((DateTime)value! - new DateTime(1970, 1, 1)).Ticks / ticksPerNanosecond));
}
}
}

View File

@ -16,7 +16,7 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return null;
@ -29,9 +29,12 @@ namespace CryptoExchange.Net.Converters
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue((long)Math.Round(((DateTime)value - new DateTime(1970, 1, 1)).TotalSeconds));
if (value == null)
writer.WriteValue((DateTime?)null);
else
writer.WriteValue((long)Math.Round(((DateTime)value! - new DateTime(1970, 1, 1)).TotalSeconds));
}
}
}

View File

@ -9,20 +9,20 @@ namespace CryptoExchange.Net.Converters
public class UTCDateTimeConverter: JsonConverter
{
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue(JsonConvert.SerializeObject(value));
}
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return null;
DateTime value;
if (reader.Value is string s)
value = (DateTime)JsonConvert.DeserializeObject(s);
value = (DateTime)JsonConvert.DeserializeObject(s)!;
else
value = (DateTime) reader.Value;

View File

@ -6,12 +6,13 @@
<PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors>
<Description>A base package for implementing cryptocurrency exchange API's</Description>
<PackageVersion>3.0.14</PackageVersion>
<PackageVersion>3.3.0</PackageVersion>
<AssemblyVersion>3.3.0</AssemblyVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
<NeutralLanguage>en</NeutralLanguage>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>3.0.14 - Updated exception message logging</PackageReleaseNotes>
<PackageReleaseNotes>3.3.0 - Added client name, Added common interfaces, Fixed api key plain text storing in RateLimitterApiKey</PackageReleaseNotes>
<Nullable>enable</Nullable>
<LangVersion>8.0</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -20,7 +21,7 @@
<DocumentationFile>CryptoExchange.Net.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="WebSocket4Net" Version="0.15.2" />
</ItemGroup>
</Project>

View File

@ -209,6 +209,11 @@
The address of the client
</summary>
</member>
<member name="P:CryptoExchange.Net.BaseClient.ClientName">
<summary>
The name of the client
</summary>
</member>
<member name="F:CryptoExchange.Net.BaseClient.log">
<summary>
The log object
@ -244,10 +249,11 @@
Last is used
</summary>
</member>
<member name="M:CryptoExchange.Net.BaseClient.#ctor(CryptoExchange.Net.Objects.ClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<member name="M:CryptoExchange.Net.BaseClient.#ctor(System.String,CryptoExchange.Net.Objects.ClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary>
ctor
</summary>
<param name="clientName"></param>
<param name="options"></param>
<param name="authenticationProvider"></param>
</member>
@ -294,7 +300,7 @@
<param name="stream">The stream to deserialize</param>
<param name="serializer">A specific serializer to use</param>
<param name="requestId">Id of the request</param>
<param name="elapsedMilliseconds">Milliseconds reponse time</param>
<param name="elapsedMilliseconds">Milliseconds response time</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.BaseClient.NextId">
@ -435,6 +441,318 @@
<member name="M:CryptoExchange.Net.Converters.UTCDateTimeConverter.CanConvert(System.Type)">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.ICommonTrade">
<summary>
Common trade
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTrade.CommonId">
<summary>
Id of the trade
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTrade.CommonPrice">
<summary>
Price of the trade
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTrade.CommonQuantity">
<summary>
Quantity of the trade
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTrade.CommonFee">
<summary>
Fee paid for the trade
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTrade.CommonFeeAsset">
<summary>
The asset fee was paid in
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient">
<summary>
Shared interface for exchange wrappers based on the CryptoExchange.Net package
</summary>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetSymbolName(System.String,System.String)">
<summary>
Get the symbol name based on a base and quote asset
</summary>
<param name="baseAsset"></param>
<param name="quoteAsset"></param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetSymbolsAsync">
<summary>
Get a list of symbols for the exchange
</summary>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetTickersAsync">
<summary>
Get a list of tickers for the exchange
</summary>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetKlinesAsync(System.String,System.TimeSpan)">
<summary>
Get a list of candles for a given symbol on the exchange
</summary>
<param name="symbol">The symbol to retrieve the candles for</param>
<param name="timespan">The timespan to retrieve the candles for. The supported value are dependent on the exchange</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetOrderBookAsync(System.String)">
<summary>
Get the order book for a symbol
</summary>
<param name="symbol">The symbol to get the book for</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetRecentTradesAsync(System.String)">
<summary>
The recent trades for a symbol
</summary>
<param name="symbol">The symbol to get the trades for</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.PlaceOrderAsync(System.String,CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderSide,CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderType,System.Decimal,System.Nullable{System.Decimal},System.String)">
<summary>
Place an order
</summary>
<param name="symbol">The symbol the order is for</param>
<param name="side">The side of the order</param>
<param name="type">The type of the order</param>
<param name="quantity">The quantity of the order</param>
<param name="price">The price of the order, only for limit orders</param>
<param name="accountId">[Optional] The account id to place the order on, required for some exchanges, ignored otherwise</param>
<returns>The id of the resulting order</returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetOrderAsync(System.String,System.String)">
<summary>
Get an order by id
</summary>
<param name="orderId">The id</param>
<param name="symbol">[Optional] The symbol the order is on, required for some exchanges, ignored otherwise</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetTradesAsync(System.String,System.String)">
<summary>
Get trades for an order by id
</summary>
<param name="orderId">The id</param>
<param name="symbol">[Optional] The symbol the order is on, required for some exchanges, ignored otherwise</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetOpenOrdersAsync(System.String)">
<summary>
Get a list of open orders
</summary>
<param name="symbol">[Optional] The symbol to get open orders for, required for some exchanges, ignored otherwise</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.GetClosedOrdersAsync(System.String)">
<summary>
Get a list of closed orders
</summary>
<param name="symbol">[Optional] The symbol to get closed orders for, required for some exchanges, ignored otherwise</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.CancelOrderAsync(System.String,System.String)">
<summary>
Cancel an order by id
</summary>
<param name="orderId">The id</param>
<param name="symbol">[Optional] The symbol the order is on, required for some exchanges, ignored otherwise</param>
<returns></returns>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderType">
<summary>
Common order id
</summary>
</member>
<member name="F:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderType.Limit">
<summary>
Limit type
</summary>
</member>
<member name="F:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderType.Market">
<summary>
Market type
</summary>
</member>
<member name="F:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderType.Other">
<summary>
Other order type
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderSide">
<summary>
Common order side
</summary>
</member>
<member name="F:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderSide.Buy">
<summary>
Buy order
</summary>
</member>
<member name="F:CryptoExchange.Net.ExchangeInterfaces.IExchangeClient.OrderSide.Sell">
<summary>
Sell order
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.ICommonKline">
<summary>
Common kline
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonKline.CommonHigh">
<summary>
High price for this kline
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonKline.CommonLow">
<summary>
Low price for this kline
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonKline.CommonOpen">
<summary>
Open price for this kline
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonKline.CommonClose">
<summary>
Close price for this kline
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.ICommonOrder">
<summary>
Common order
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrder.CommonSymbol">
<summary>
Symbol of the order
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrder.CommonPrice">
<summary>
Price of the order
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrder.CommonQuantity">
<summary>
Quantity of the order
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrder.CommonStatus">
<summary>
Status of the order
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrder.IsActive">
<summary>
Whether the order is active
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrder.CommonSide">
<summary>
Side of the order
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrder.CommonType">
<summary>
Type of the order
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.ICommonOrderBook">
<summary>
Common order book
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrderBook.CommonBids">
<summary>
Bids
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrderBook.CommonAsks">
<summary>
Asks
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.ICommonOrderId">
<summary>
Common order id
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonOrderId.CommonId">
<summary>
Id of the order
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.ICommonRecentTrade">
<summary>
Recent trade
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonRecentTrade.CommonPrice">
<summary>
Price of the trade
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonRecentTrade.CommonQuantity">
<summary>
Quantity of the trade
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonRecentTrade.CommonTradeTime">
<summary>
Trade time
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.ICommonSymbol">
<summary>
Common symbol
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonSymbol.CommonName">
<summary>
Symbol name
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonSymbol.CommonMinimumTradeSize">
<summary>
Minimum trade size
</summary>
</member>
<member name="T:CryptoExchange.Net.ExchangeInterfaces.ICommonTicker">
<summary>
Common ticker
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTicker.CommonSymbol">
<summary>
Symbol name
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTicker.CommonHigh">
<summary>
High price
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTicker.CommonLow">
<summary>
Low price
</summary>
</member>
<member name="P:CryptoExchange.Net.ExchangeInterfaces.ICommonTicker.CommonVolume">
<summary>
Volume
</summary>
</member>
<member name="T:CryptoExchange.Net.ExtensionMethods">
<summary>
Helper methods
@ -712,6 +1030,11 @@
The base address of the API
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRestClient.ClientName">
<summary>
Client name
</summary>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRestClient.AddRateLimiter(CryptoExchange.Net.Interfaces.IRateLimiter)">
<summary>
Adds a rate limiter to the client. There are 2 choices, the <see cref="T:CryptoExchange.Net.RateLimiter.RateLimiterTotal"/> and the <see cref="T:CryptoExchange.Net.RateLimiter.RateLimiterPerEndpoint"/>.
@ -1065,7 +1388,12 @@
The verbosity of the logging
</summary>
</member>
<member name="M:CryptoExchange.Net.Logging.Log.#ctor">
<member name="P:CryptoExchange.Net.Logging.Log.ClientName">
<summary>
Client name
</summary>
</member>
<member name="M:CryptoExchange.Net.Logging.Log.#ctor(System.String)">
<summary>
ctor
</summary>
@ -1199,6 +1527,40 @@
<param name="y"></param>
<returns></returns>
</member>
<member name="T:CryptoExchange.Net.Objects.CallResult">
<summary>
The result of an operation
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.CallResult.Error">
<summary>
An error if the call didn't succeed
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.CallResult.Success">
<summary>
Whether the call was successful
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.CallResult.#ctor(CryptoExchange.Net.Objects.Error)">
<summary>
ctor
</summary>
<param name="error"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.CallResult.op_Implicit(CryptoExchange.Net.Objects.CallResult)~System.Boolean">
<summary>
Overwrite bool check so we can use if(callResult) instead of if(callResult.Success)
</summary>
<param name="obj"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.CallResult.CreateErrorResult(CryptoExchange.Net.Objects.Error)">
<summary>
Create an error result
</summary>
<param name="error"></param>
<returns></returns>
</member>
<member name="T:CryptoExchange.Net.Objects.CallResult`1">
<summary>
The result of an operation
@ -1210,16 +1572,6 @@
The data returned by the call
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.CallResult`1.Error">
<summary>
An error if the call didn't succeed
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.CallResult`1.Success">
<summary>
Whether the call was successful
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.CallResult`1.#ctor(`0,CryptoExchange.Net.Objects.Error)">
<summary>
ctor
@ -1233,6 +1585,60 @@
</summary>
<param name="obj"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.CallResult`1.GetResultOrError(`0@,CryptoExchange.Net.Objects.Error@)">
<summary>
Whether the call was successful or not. Useful for nullability checking.
</summary>
<param name="data">The data returned by the call.</param>
<param name="error"><see cref="T:CryptoExchange.Net.Objects.Error"/> on failure.</param>
<returns><c>true</c> when <see cref="T:CryptoExchange.Net.Objects.CallResult`1"/> succeeded, <c>false</c> otherwise.</returns>
</member>
<member name="M:CryptoExchange.Net.Objects.CallResult`1.CreateErrorResult(CryptoExchange.Net.Objects.Error)">
<summary>
Create an error result
</summary>
<param name="error"></param>
<returns></returns>
</member>
<member name="T:CryptoExchange.Net.Objects.WebCallResult">
<summary>
The result of a request
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.WebCallResult.ResponseStatusCode">
<summary>
The status code of the response. Note that a OK status does not always indicate success, check the Success parameter for this.
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.WebCallResult.ResponseHeaders">
<summary>
The response headers
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.WebCallResult.#ctor(System.Nullable{System.Net.HttpStatusCode},System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,System.Collections.Generic.IEnumerable{System.String}}},CryptoExchange.Net.Objects.Error)">
<summary>
ctor
</summary>
<param name="code"></param>
<param name="responseHeaders"></param>
<param name="error"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.WebCallResult.CreateErrorResult(System.Nullable{System.Net.HttpStatusCode},System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,System.Collections.Generic.IEnumerable{System.String}}},CryptoExchange.Net.Objects.Error)">
<summary>
Create an error result
</summary>
<param name="code"></param>
<param name="responseHeaders"></param>
<param name="error"></param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Objects.WebCallResult.CreateErrorResult(CryptoExchange.Net.Objects.WebCallResult)">
<summary>
Create an error result
</summary>
<param name="result"></param>
<returns></returns>
</member>
<member name="T:CryptoExchange.Net.Objects.WebCallResult`1">
<summary>
The result of a request
@ -1258,11 +1664,18 @@
<param name="data"></param>
<param name="error"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.WebCallResult`1.CreateErrorResult(CryptoExchange.Net.Objects.Error)">
<member name="M:CryptoExchange.Net.Objects.WebCallResult`1.#ctor(CryptoExchange.Net.Objects.WebCallResult{`0})">
<summary>
Create an error result
Create new based on existing
</summary>
<param name="error"></param>
<param name="callResult"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.WebCallResult`1.CreateFrom``1(CryptoExchange.Net.Objects.WebCallResult{``0})">
<summary>
Create from a call result
</summary>
<typeparam name="Y"></typeparam>
<param name="source"></param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Objects.WebCallResult`1.CreateErrorResult(System.Nullable{System.Net.HttpStatusCode},System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,System.Collections.Generic.IEnumerable{System.String}}},CryptoExchange.Net.Objects.Error)">
@ -1401,20 +1814,20 @@
</member>
<member name="P:CryptoExchange.Net.Objects.Error.Code">
<summary>
The error code
The error code from the server
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.Error.Message">
<summary>
The message for the error that occured
The message for the error that occurred
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.Error.Data">
<summary>
Optional data for the error
The data which caused the error
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.Error.#ctor(System.Int32,System.String,System.Object)">
<member name="M:CryptoExchange.Net.Objects.Error.#ctor(System.Nullable{System.Int32},System.String,System.Object)">
<summary>
ctor
</summary>
@ -1473,10 +1886,19 @@
Web error returned by the server
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.WebError.#ctor(System.Object)">
<member name="M:CryptoExchange.Net.Objects.WebError.#ctor(System.String,System.Object)">
<summary>
ctor
</summary>
<param name="message"></param>
<param name="data"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.WebError.#ctor(System.Int32,System.String,System.Object)">
<summary>
ctor
</summary>
<param name="code"></param>
<param name="message"></param>
<param name="data"></param>
</member>
<member name="T:CryptoExchange.Net.Objects.DeserializeError">
@ -1484,21 +1906,23 @@
Error while deserializing data
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.DeserializeError.#ctor(System.Object)">
<member name="M:CryptoExchange.Net.Objects.DeserializeError.#ctor(System.String,System.Object)">
<summary>
ctor
</summary>
<param name="data">Deserializing data</param>
<param name="message">The error message</param>
<param name="data">The data which caused the error</param>
</member>
<member name="T:CryptoExchange.Net.Objects.UnknownError">
<summary>
Unknown error
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.UnknownError.#ctor(System.Object)">
<member name="M:CryptoExchange.Net.Objects.UnknownError.#ctor(System.String,System.Object)">
<summary>
ctor
</summary>
<param name="message">Error message</param>
<param name="data">Error data</param>
</member>
<member name="T:CryptoExchange.Net.Objects.ArgumentError">
@ -1992,6 +2416,11 @@
<member name="M:CryptoExchange.Net.RateLimiter.RateLimiterAPIKey.LimitRequest(CryptoExchange.Net.RestClient,System.String,CryptoExchange.Net.Objects.RateLimitingBehaviour)">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.RateLimiter.RateLimiterAPIKey.Dispose">
<summary>
Dispose
</summary>
</member>
<member name="T:CryptoExchange.Net.RateLimiter.RateLimiterPerEndpoint">
<summary>
Limits the amount of requests per time period to a certain limit, counts the request per endpoint.
@ -2184,10 +2613,11 @@
Total requests made
</summary>
</member>
<member name="M:CryptoExchange.Net.RestClient.#ctor(CryptoExchange.Net.Objects.RestClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<member name="M:CryptoExchange.Net.RestClient.#ctor(System.String,CryptoExchange.Net.Objects.RestClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary>
ctor
</summary>
<param name="clientName"></param>
<param name="exchangeOptions"></param>
<param name="authenticationProvider"></param>
</member>
@ -2348,10 +2778,11 @@
If false; data which is a response to a query won't get forwarded to subscriptions as well
</summary>
</member>
<member name="M:CryptoExchange.Net.SocketClient.#ctor(CryptoExchange.Net.Objects.SocketClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<member name="M:CryptoExchange.Net.SocketClient.#ctor(System.String,CryptoExchange.Net.Objects.SocketClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary>
Create a socket client
</summary>
<param name="clientName">Client name</param>
<param name="exchangeOptions">Client options</param>
<param name="authenticationProvider">Authentication provider</param>
</member>

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Common trade
/// </summary>
public interface ICommonTrade
{
/// <summary>
/// Id of the trade
/// </summary>
public string CommonId { get; }
/// <summary>
/// Price of the trade
/// </summary>
public decimal CommonPrice { get; }
/// <summary>
/// Quantity of the trade
/// </summary>
public decimal CommonQuantity { get; }
/// <summary>
/// Fee paid for the trade
/// </summary>
public decimal CommonFee { get; }
/// <summary>
/// The asset fee was paid in
/// </summary>
public string? CommonFeeAsset { get; }
}
}

View File

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CryptoExchange.Net.Objects;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Shared interface for exchange wrappers based on the CryptoExchange.Net package
/// </summary>
public interface IExchangeClient
{
/// <summary>
/// Get the symbol name based on a base and quote asset
/// </summary>
/// <param name="baseAsset"></param>
/// <param name="quoteAsset"></param>
/// <returns></returns>
string GetSymbolName(string baseAsset, string quoteAsset);
/// <summary>
/// Get a list of symbols for the exchange
/// </summary>
/// <returns></returns>
Task<WebCallResult<IEnumerable<ICommonSymbol>>> GetSymbolsAsync();
/// <summary>
/// Get a list of tickers for the exchange
/// </summary>
/// <returns></returns>
Task<WebCallResult<IEnumerable<ICommonTicker>>> GetTickersAsync();
/// <summary>
/// Get a list of candles for a given symbol on the exchange
/// </summary>
/// <param name="symbol">The symbol to retrieve the candles for</param>
/// <param name="timespan">The timespan to retrieve the candles for. The supported value are dependent on the exchange</param>
/// <returns></returns>
Task<WebCallResult<IEnumerable<ICommonKline>>> GetKlinesAsync(string symbol, TimeSpan timespan);
/// <summary>
/// Get the order book for a symbol
/// </summary>
/// <param name="symbol">The symbol to get the book for</param>
/// <returns></returns>
Task<WebCallResult<ICommonOrderBook>> GetOrderBookAsync(string symbol);
/// <summary>
/// The recent trades for a symbol
/// </summary>
/// <param name="symbol">The symbol to get the trades for</param>
/// <returns></returns>
Task<WebCallResult<IEnumerable<ICommonRecentTrade>>> GetRecentTradesAsync(string symbol);
/// <summary>
/// Place an order
/// </summary>
/// <param name="symbol">The symbol the order is for</param>
/// <param name="side">The side of the order</param>
/// <param name="type">The type of the order</param>
/// <param name="quantity">The quantity of the order</param>
/// <param name="price">The price of the order, only for limit orders</param>
/// <param name="accountId">[Optional] The account id to place the order on, required for some exchanges, ignored otherwise</param>
/// <returns>The id of the resulting order</returns>
Task<WebCallResult<ICommonOrderId>> PlaceOrderAsync(string symbol, OrderSide side, OrderType type, decimal quantity, decimal? price = null, string? accountId = null);
/// <summary>
/// Get an order by id
/// </summary>
/// <param name="orderId">The id</param>
/// <param name="symbol">[Optional] The symbol the order is on, required for some exchanges, ignored otherwise</param>
/// <returns></returns>
Task<WebCallResult<ICommonOrder>> GetOrderAsync(string orderId, string? symbol = null);
/// <summary>
/// Get trades for an order by id
/// </summary>
/// <param name="orderId">The id</param>
/// <param name="symbol">[Optional] The symbol the order is on, required for some exchanges, ignored otherwise</param>
/// <returns></returns>
Task<WebCallResult<IEnumerable<ICommonTrade>>> GetTradesAsync(string orderId, string? symbol = null);
/// <summary>
/// Get a list of open orders
/// </summary>
/// <param name="symbol">[Optional] The symbol to get open orders for, required for some exchanges, ignored otherwise</param>
/// <returns></returns>
Task<WebCallResult<IEnumerable<ICommonOrder>>> GetOpenOrdersAsync(string? symbol);
/// <summary>
/// Get a list of closed orders
/// </summary>
/// <param name="symbol">[Optional] The symbol to get closed orders for, required for some exchanges, ignored otherwise</param>
/// <returns></returns>
Task<WebCallResult<IEnumerable<ICommonOrder>>> GetClosedOrdersAsync(string? symbol);
/// <summary>
/// Cancel an order by id
/// </summary>
/// <param name="orderId">The id</param>
/// <param name="symbol">[Optional] The symbol the order is on, required for some exchanges, ignored otherwise</param>
/// <returns></returns>
Task<WebCallResult<ICommonOrderId>> CancelOrderAsync(string orderId, string? symbol);
/// <summary>
/// Common order id
/// </summary>
public enum OrderType
{
/// <summary>
/// Limit type
/// </summary>
Limit,
/// <summary>
/// Market type
/// </summary>
Market,
/// <summary>
/// Other order type
/// </summary>
Other
}
/// <summary>
/// Common order side
/// </summary>
public enum OrderSide
{
/// <summary>
/// Buy order
/// </summary>
Buy,
/// <summary>
/// Sell order
/// </summary>
Sell
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Common kline
/// </summary>
public interface ICommonKline
{
/// <summary>
/// High price for this kline
/// </summary>
decimal CommonHigh { get; }
/// <summary>
/// Low price for this kline
/// </summary>
decimal CommonLow { get; }
/// <summary>
/// Open price for this kline
/// </summary>
decimal CommonOpen { get; }
/// <summary>
/// Close price for this kline
/// </summary>
decimal CommonClose { get; }
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Common order
/// </summary>
public interface ICommonOrder: ICommonOrderId
{
/// <summary>
/// Symbol of the order
/// </summary>
public string CommonSymbol { get; }
/// <summary>
/// Price of the order
/// </summary>
public decimal CommonPrice { get; }
/// <summary>
/// Quantity of the order
/// </summary>
public decimal CommonQuantity { get; }
/// <summary>
/// Status of the order
/// </summary>
public string CommonStatus { get; }
/// <summary>
/// Whether the order is active
/// </summary>
public bool IsActive { get; }
/// <summary>
/// Side of the order
/// </summary>
public IExchangeClient.OrderSide CommonSide { get; }
/// <summary>
/// Type of the order
/// </summary>
public IExchangeClient.OrderType CommonType { get; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
using CryptoExchange.Net.Interfaces;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Common order book
/// </summary>
public interface ICommonOrderBook
{
/// <summary>
/// Bids
/// </summary>
IEnumerable<ISymbolOrderBookEntry> CommonBids { get; }
/// <summary>
/// Asks
/// </summary>
IEnumerable<ISymbolOrderBookEntry> CommonAsks { get; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Common order id
/// </summary>
public interface ICommonOrderId
{
/// <summary>
/// Id of the order
/// </summary>
public string CommonId { get; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Recent trade
/// </summary>
public interface ICommonRecentTrade
{
/// <summary>
/// Price of the trade
/// </summary>
decimal CommonPrice { get; }
/// <summary>
/// Quantity of the trade
/// </summary>
decimal CommonQuantity { get; }
/// <summary>
/// Trade time
/// </summary>
DateTime CommonTradeTime { get; }
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Common symbol
/// </summary>
public interface ICommonSymbol
{
/// <summary>
/// Symbol name
/// </summary>
public string CommonName { get; }
/// <summary>
/// Minimum trade size
/// </summary>
public decimal CommonMinimumTradeSize { get; }
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.ExchangeInterfaces
{
/// <summary>
/// Common ticker
/// </summary>
public interface ICommonTicker
{
/// <summary>
/// Symbol name
/// </summary>
public string CommonSymbol { get; }
/// <summary>
/// High price
/// </summary>
public decimal CommonHigh { get; }
/// <summary>
/// Low price
/// </summary>
public decimal CommonLow { get; }
/// <summary>
/// Volume
/// </summary>
public decimal CommonVolume { get; }
}
}

View File

@ -225,7 +225,7 @@ namespace CryptoExchange.Net
{
if (!allowedValues.Contains(value))
throw new ArgumentException(
$"{value} not allowed for parameter {argumentName}, allowed values: {string.Join(", ", allowedValues)}");
$"{value} not allowed for parameter {argumentName}, allowed values: {string.Join(", ", allowedValues)}", argumentName);
}
/// <summary>
@ -239,7 +239,7 @@ namespace CryptoExchange.Net
{
if (value < minValue || value > maxValue)
throw new ArgumentException(
$"{value} not allowed for parameter {argumentName}, min: {minValue}, max: {maxValue}");
$"{value} not allowed for parameter {argumentName}, min: {minValue}, max: {maxValue}", argumentName);
}
/// <summary>
@ -250,7 +250,7 @@ namespace CryptoExchange.Net
public static void ValidateNotNull(this string value, string argumentName)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException($"No value provided for parameter {argumentName}");
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
}
/// <summary>
@ -261,7 +261,7 @@ namespace CryptoExchange.Net
public static void ValidateNotNull(this object value, string argumentName)
{
if (value == null)
throw new ArgumentException($"No value provided for parameter {argumentName}");
throw new ArgumentException($"No value provided for parameter {argumentName}", argumentName);
}
/// <summary>
@ -272,7 +272,7 @@ namespace CryptoExchange.Net
public static void ValidateNotNull<T>(this IEnumerable<T> value, string argumentName)
{
if (value == null || !value.Any())
throw new ArgumentException($"No values provided for parameter {argumentName}");
throw new ArgumentException($"No values provided for parameter {argumentName}", argumentName);
}
}
}

View File

@ -37,6 +37,11 @@ namespace CryptoExchange.Net.Interfaces
/// </summary>
string BaseAddress { get; }
/// <summary>
/// Client name
/// </summary>
string ClientName { get; }
/// <summary>
/// Adds a rate limiter to the client. There are 2 choices, the <see cref="RateLimiterTotal"/> and the <see cref="RateLimiterPerEndpoint"/>.
/// </summary>

View File

@ -17,11 +17,17 @@ namespace CryptoExchange.Net.Logging
/// </summary>
public LogVerbosity Level { get; set; } = LogVerbosity.Info;
/// <summary>
/// Client name
/// </summary>
public string ClientName { get; set; }
/// <summary>
/// ctor
/// </summary>
public Log()
public Log(string clientName)
{
ClientName = clientName;
writers = new List<TextWriter>();
}
@ -44,7 +50,7 @@ namespace CryptoExchange.Net.Logging
if ((int)logType < (int)Level)
return;
var logMessage = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | {logType} | {message}";
var logMessage = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | {ClientName.PadRight(10)} | {logType} | {message}";
foreach (var writer in writers.ToList())
{
try

View File

@ -7,13 +7,8 @@ namespace CryptoExchange.Net.Objects
/// <summary>
/// The result of an operation
/// </summary>
/// <typeparam name="T"></typeparam>
public class CallResult<T>
public class CallResult
{
/// <summary>
/// The data returned by the call
/// </summary>
public T Data { get; internal set; }
/// <summary>
/// An error if the call didn't succeed
/// </summary>
@ -23,15 +18,56 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public bool Success => Error == null;
/// <summary>
/// ctor
/// </summary>
/// <param name="error"></param>
public CallResult(Error? error)
{
Error = error;
}
/// <summary>
/// Overwrite bool check so we can use if(callResult) instead of if(callResult.Success)
/// </summary>
/// <param name="obj"></param>
public static implicit operator bool(CallResult obj)
{
return obj?.Success == true;
}
/// <summary>
/// Create an error result
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public static WebCallResult CreateErrorResult(Error error)
{
return new WebCallResult(null, null, error);
}
}
/// <summary>
/// The result of an operation
/// </summary>
/// <typeparam name="T"></typeparam>
public class CallResult<T>: CallResult
{
/// <summary>
/// The data returned by the call
/// </summary>
public T Data { get; internal set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="data"></param>
/// <param name="error"></param>
public CallResult([AllowNull]T data, Error? error)
public CallResult([AllowNull]T data, Error? error): base(error)
{
#pragma warning disable 8601
Data = data;
Error = error;
#pragma warning disable 8601
}
/// <summary>
@ -42,6 +78,92 @@ namespace CryptoExchange.Net.Objects
{
return obj?.Success == true;
}
/// <summary>
/// Whether the call was successful or not. Useful for nullability checking.
/// </summary>
/// <param name="data">The data returned by the call.</param>
/// <param name="error"><see cref="Error"/> on failure.</param>
/// <returns><c>true</c> when <see cref="CallResult{T}"/> succeeded, <c>false</c> otherwise.</returns>
public bool GetResultOrError([MaybeNullWhen(false)] out T data, [NotNullWhen(false)] out Error? error)
{
if (Success)
{
data = Data!;
error = null;
return true;
}
else
{
data = default;
error = Error!;
return false;
}
}
/// <summary>
/// Create an error result
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public new static WebCallResult<T> CreateErrorResult(Error error)
{
return new WebCallResult<T>(null, null, default, error);
}
}
/// <summary>
/// The result of a request
/// </summary>
public class WebCallResult : CallResult
{
/// <summary>
/// The status code of the response. Note that a OK status does not always indicate success, check the Success parameter for this.
/// </summary>
public HttpStatusCode? ResponseStatusCode { get; set; }
/// <summary>
/// The response headers
/// </summary>
public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? ResponseHeaders { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="code"></param>
/// <param name="responseHeaders"></param>
/// <param name="error"></param>
public WebCallResult(
HttpStatusCode? code,
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, Error? error) : base(error)
{
ResponseHeaders = responseHeaders;
ResponseStatusCode = code;
}
/// <summary>
/// Create an error result
/// </summary>
/// <param name="code"></param>
/// <param name="responseHeaders"></param>
/// <param name="error"></param>
/// <returns></returns>
public static WebCallResult CreateErrorResult(HttpStatusCode? code, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, Error error)
{
return new WebCallResult(code, responseHeaders, error);
}
/// <summary>
/// Create an error result
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
public static WebCallResult CreateErrorResult(WebCallResult result)
{
return new WebCallResult(result.ResponseStatusCode, result.ResponseHeaders, result.Error);
}
}
/// <summary>
@ -71,18 +193,29 @@ namespace CryptoExchange.Net.Objects
HttpStatusCode? code,
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, [AllowNull] T data, Error? error): base(data, error)
{
ResponseHeaders = responseHeaders;
ResponseStatusCode = code;
ResponseHeaders = responseHeaders;
}
/// <summary>
/// Create an error result
/// Create new based on existing
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public static WebCallResult<T> CreateErrorResult(Error error)
/// <param name="callResult"></param>
public WebCallResult(WebCallResult<T> callResult): base(callResult.Data, callResult.Error)
{
return new WebCallResult<T>(null, null, default!, error);
ResponseHeaders = callResult.ResponseHeaders;
ResponseStatusCode = callResult.ResponseStatusCode;
}
/// <summary>
/// Create from a call result
/// </summary>
/// <typeparam name="Y"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static WebCallResult<T> CreateFrom<Y>(WebCallResult<Y> source) where Y : T
{
return new WebCallResult<T>(source.ResponseStatusCode, source.ResponseHeaders, (T)source.Data, source.Error);
}
/// <summary>
@ -94,7 +227,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public static WebCallResult<T> CreateErrorResult(HttpStatusCode? code, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, Error error)
{
return new WebCallResult<T>(code, responseHeaders, default!, error);
return new WebCallResult<T>(code, responseHeaders, default, error);
}
}
}

View File

@ -6,16 +6,17 @@
public abstract class Error
{
/// <summary>
/// The error code
/// The error code from the server
/// </summary>
public int Code { get; set; }
public int? Code { get; set; }
/// <summary>
/// The message for the error that occured
/// The message for the error that occurred
/// </summary>
public string Message { get; set; }
/// <summary>
/// Optional data for the error
/// The data which caused the error
/// </summary>
public object? Data { get; set; }
@ -25,7 +26,7 @@
/// <param name="code"></param>
/// <param name="message"></param>
/// <param name="data"></param>
protected Error(int code, string message, object? data)
protected Error(int? code, string message, object? data)
{
Code = code;
Message = message;
@ -50,7 +51,7 @@
/// <summary>
/// ctor
/// </summary>
public CantConnectError() : base(1, "Can't connect to the server", null) { }
public CantConnectError() : base(null, "Can't connect to the server", null) { }
}
/// <summary>
@ -61,7 +62,7 @@
/// <summary>
/// ctor
/// </summary>
public NoApiCredentialsError() : base(2, "No credentials provided for private endpoint", null) { }
public NoApiCredentialsError() : base(null, "No credentials provided for private endpoint", null) { }
}
/// <summary>
@ -74,7 +75,7 @@
/// </summary>
/// <param name="message"></param>
/// <param name="data"></param>
public ServerError(string message, object? data = null) : base(3, "Server error: " + message, data) { }
public ServerError(string message, object? data = null) : base(null, message, data) { }
/// <summary>
/// ctor
@ -95,8 +96,17 @@
/// <summary>
/// ctor
/// </summary>
/// <param name="message"></param>
/// <param name="data"></param>
public WebError(object? data) : base(4, "Web error", data) { }
public WebError(string message, object? data = null) : base(null, message, data) { }
/// <summary>
/// ctor
/// </summary>
/// <param name="code"></param>
/// <param name="message"></param>
/// <param name="data"></param>
public WebError(int code, string message, object? data = null) : base(code, message, data) { }
}
/// <summary>
@ -107,8 +117,9 @@
/// <summary>
/// ctor
/// </summary>
/// <param name="data">Deserializing data</param>
public DeserializeError(object? data) : base(5, "Error deserializing data", data) { }
/// <param name="message">The error message</param>
/// <param name="data">The data which caused the error</param>
public DeserializeError(string message, object? data) : base(null, message, data) { }
}
/// <summary>
@ -119,8 +130,9 @@
/// <summary>
/// ctor
/// </summary>
/// <param name="message">Error message</param>
/// <param name="data">Error data</param>
public UnknownError(object? data = null) : base(6, "Unknown error occured", data) { }
public UnknownError(string message, object? data = null) : base(null, message, data) { }
}
/// <summary>
@ -132,7 +144,7 @@
/// ctor
/// </summary>
/// <param name="message"></param>
public ArgumentError(string message) : base(7, "Invalid parameter: " + message, null) { }
public ArgumentError(string message) : base(null, "Invalid parameter: " + message, null) { }
}
/// <summary>
@ -144,7 +156,7 @@
/// ctor
/// </summary>
/// <param name="message"></param>
public RateLimitError(string message) : base(8, "Rate limit exceeded: " + message, null) { }
public RateLimitError(string message) : base(null, "Rate limit exceeded: " + message, null) { }
}
/// <summary>
@ -155,6 +167,6 @@
/// <summary>
/// ctor
/// </summary>
public CancellationRequestedError() : base(9, "Cancellation requested", null) { }
public CancellationRequestedError() : base(null, "Cancellation requested", null) { }
}
}

View File

@ -76,10 +76,22 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public class ClientOptions : BaseOptions
{
private string _baseAddress;
/// <summary>
/// The base address of the client
/// </summary>
public string BaseAddress { get; set; }
public string BaseAddress
{
get => _baseAddress;
set
{
var newValue = value;
if (!newValue.EndsWith("/"))
newValue += "/";
_baseAddress = newValue;
}
}
/// <summary>
/// The api credentials
@ -89,7 +101,7 @@ namespace CryptoExchange.Net.Objects
/// <summary>
/// Should check objects for missing properties based on the model and the received JSON
/// </summary>
public bool ShouldCheckObjects { get; set; } = true;
public bool ShouldCheckObjects { get; set; } = false;
/// <summary>
/// Proxy to use
@ -100,7 +112,9 @@ namespace CryptoExchange.Net.Objects
/// ctor
/// </summary>
/// <param name="baseAddress"></param>
#pragma warning disable 8618
public ClientOptions(string baseAddress)
#pragma warning restore 8618
{
BaseAddress = baseAddress;
}

View File

@ -39,8 +39,8 @@ namespace CryptoExchange.Net.OrderBook
private readonly bool strictLevels;
private Task? _processTask;
private AutoResetEvent _queueEvent;
private ConcurrentQueue<object> _processQueue;
private readonly AutoResetEvent _queueEvent;
private readonly ConcurrentQueue<object> _processQueue;
/// <summary>
/// Order book implementation id
@ -208,7 +208,7 @@ namespace CryptoExchange.Net.OrderBook
asks = new SortedList<decimal, ISymbolOrderBookEntry>();
bids = new SortedList<decimal, ISymbolOrderBookEntry>(new DescComparer<decimal>());
log = new Log { Level = options.LogVerbosity };
log = new Log(options.OrderBookName) { Level = options.LogVerbosity };
var writers = options.LogWriters ?? new List<TextWriter> { new DebugTextWriter() };
log.UpdateWriters(writers.ToList());
}
@ -377,7 +377,7 @@ namespace CryptoExchange.Net.OrderBook
FirstUpdateId = item.StartUpdateId,
LastUpdateId = item.EndUpdateId,
});
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update buffered #{item.StartUpdateId}-#{item.EndUpdateId} [{Asks.Count()} asks, {Bids.Count()} bids]");
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update buffered #{item.StartUpdateId}-#{item.EndUpdateId} [{item.Asks.Count()} asks, {item.Bids.Count()} bids]");
}
else
{

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
@ -10,10 +12,11 @@ namespace CryptoExchange.Net.RateLimiter
/// <summary>
/// Limits the amount of requests per time period to a certain limit, counts the request per API key.
/// </summary>
public class RateLimiterAPIKey: IRateLimiter
public class RateLimiterAPIKey: IRateLimiter, IDisposable
{
internal Dictionary<string, RateLimitObject> history = new Dictionary<string, RateLimitObject>();
private readonly SHA256 encryptor;
private readonly int limitPerKey;
private readonly TimeSpan perTimePeriod;
private readonly object historyLock = new object();
@ -26,6 +29,7 @@ namespace CryptoExchange.Net.RateLimiter
public RateLimiterAPIKey(int limitPerApiKey, TimeSpan perTimePeriod)
{
limitPerKey = limitPerApiKey;
encryptor = SHA256.Create();
this.perTimePeriod = perTimePeriod;
}
@ -35,7 +39,14 @@ namespace CryptoExchange.Net.RateLimiter
if(client.authProvider?.Credentials?.Key == null)
return new CallResult<double>(0, null);
var key = client.authProvider.Credentials.Key.GetString();
var keyBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(client.authProvider.Credentials.Key.GetString()));
StringBuilder builder = new StringBuilder();
for (int i = 0; i < keyBytes.Length; i++)
{
builder.Append(keyBytes[i].ToString("x2"));
}
var key = builder.ToString();
int waitTime;
RateLimitObject rlo;
@ -69,5 +80,13 @@ namespace CryptoExchange.Net.RateLimiter
return new CallResult<double>(waitTime, null);
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
encryptor.Dispose();
}
}
}

View File

@ -76,9 +76,10 @@ namespace CryptoExchange.Net
/// <summary>
/// ctor
/// </summary>
/// <param name="clientName"></param>
/// <param name="exchangeOptions"></param>
/// <param name="authenticationProvider"></param>
protected RestClient(RestClientOptions exchangeOptions, AuthenticationProvider? authenticationProvider) : base(exchangeOptions, authenticationProvider)
protected RestClient(string clientName, RestClientOptions exchangeOptions, AuthenticationProvider? authenticationProvider) : base(clientName, exchangeOptions, authenticationProvider)
{
if (exchangeOptions == null)
throw new ArgumentNullException(nameof(exchangeOptions));
@ -232,7 +233,7 @@ namespace CryptoExchange.Net
var parseResult = ValidateJson(data);
if (!parseResult.Success)
return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, new ServerError(data));
return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, parseResult.Error!);
var error = await TryParseError(parseResult.Data);
if (error != null)
return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, error);
@ -257,7 +258,10 @@ namespace CryptoExchange.Net
responseStream.Close();
response.Close();
var parseResult = ValidateJson(data);
return new WebCallResult<T>(statusCode, headers, default, parseResult.Success ? ParseErrorResponse(parseResult.Data) : new ServerError(data));
var error = parseResult.Success ? ParseErrorResponse(parseResult.Data) : parseResult.Error!;
if(error.Code == null || error.Code == 0)
error.Code = (int)response.StatusCode;
return new WebCallResult<T>(statusCode, headers, default, error);
}
}
catch (HttpRequestException requestException)

View File

@ -83,9 +83,10 @@ namespace CryptoExchange.Net
/// <summary>
/// Create a socket client
/// </summary>
/// <param name="clientName">Client name</param>
/// <param name="exchangeOptions">Client options</param>
/// <param name="authenticationProvider">Authentication provider</param>
protected SocketClient(SocketClientOptions exchangeOptions, AuthenticationProvider? authenticationProvider): base(exchangeOptions, authenticationProvider)
protected SocketClient(string clientName, SocketClientOptions exchangeOptions, AuthenticationProvider? authenticationProvider): base(clientName, exchangeOptions, authenticationProvider)
{
if (exchangeOptions == null)
throw new ArgumentNullException(nameof(exchangeOptions));
@ -294,7 +295,7 @@ namespace CryptoExchange.Net
var connectResult = await ConnectSocket(socket).ConfigureAwait(false);
if (!connectResult)
return new CallResult<bool>(false, new CantConnectError());
return new CallResult<bool>(false, connectResult.Error);
if (!authenticated || socket.Authenticated)
return new CallResult<bool>(true, null);
@ -427,7 +428,8 @@ namespace CryptoExchange.Net
/// <returns></returns>
protected virtual SocketConnection GetWebsocket(string address, bool authenticated)
{
var socketResult = sockets.Where(s => s.Value.Socket.Url == address && (s.Value.Authenticated == authenticated || !authenticated) && s.Value.Connected).OrderBy(s => s.Value.HandlerCount).FirstOrDefault();
var socketResult = sockets.Where(s => s.Value.Socket.Url.TrimEnd('/') == address.TrimEnd('/')
&& (s.Value.Authenticated == authenticated || !authenticated) && s.Value.Connected).OrderBy(s => s.Value.HandlerCount).FirstOrDefault();
var result = socketResult.Equals(default(KeyValuePair<int, SocketConnection>)) ? null : socketResult.Value;
if (result != null)
{

View File

@ -61,6 +61,10 @@ Implementations from third parties
<br />
<a href="https://github.com/ridicoulous/Bitmex.Net">Bitmex</a>
</td>
<td><a href="https://github.com/intelligences/HitBTC.Net"><img src="https://github.com/intelligences/HitBTC.Net/blob/master/src/HitBTC.Net/Icon/icon.png?raw=true"></a>
<br />
<a href="https://github.com/intelligences/HitBTC.Net">HitBTC</a>
</td>
</tr>
</table>
@ -199,6 +203,27 @@ The order book will automatically reconnect when the connection is lost and resy
To stop synchronizing an order book use the `Stop` method.
## Release notes
* Version 3.3.0 - 10 dec 2020
* Added client name
* Added common interfaces
* Fixed api key plain text storing in RateLimitterApiKey
* Version 3.2.1 - 19 nov 2020
* Fixed error code parsing
* Version 3.2.0 - 19 nov 2020
* Fix for multiple socket subscriptions re-using the same socket connection
* Updated errors
* Version 3.1.0 - 08 Oct 2020
* Added CallResult without type parameter for calls which don't return data
* Added GetErrorOrResult method on CallResult to support proper nullability checking
* Fix for reading credentials from file
* Fix for setting custom base addresses in clients
* Version 3.0.15 - 06 Oct 2020
* Changed default ShouldCheckObjects to false to prevent spam in logging
* Version 3.0.14 - 24 Aug 2020
* Updated exception message logging