diff --git a/CryptoExchange.Net.UnitTests/RateLimitTests.cs b/CryptoExchange.Net.UnitTests/RateLimitTests.cs index a1c8a09c..f27fbaa4 100644 --- a/CryptoExchange.Net.UnitTests/RateLimitTests.cs +++ b/CryptoExchange.Net.UnitTests/RateLimitTests.cs @@ -3,6 +3,7 @@ using CryptoExchange.Net.RateLimiting; using CryptoExchange.Net.RateLimiting.Filters; using CryptoExchange.Net.RateLimiting.Guards; using CryptoExchange.Net.RateLimiting.Interfaces; +using CryptoExchange.Net.RateLimiting.Trackers; using CryptoExchange.Net.UnitTests.Implementations; using NUnit.Framework; using System; @@ -33,12 +34,12 @@ namespace CryptoExchange.Net.UnitTests for (var i = 0; i < requests + 1; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(i == requests ? triggered : !triggered); } triggered = false; await Task.Delay((int)Math.Round(perSeconds * 1000) + 10); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(!triggered); } @@ -58,7 +59,7 @@ namespace CryptoExchange.Net.UnitTests rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); bool expected = i == 1 ? expectLimiting ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; Assert.That(expected); } @@ -79,9 +80,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(expectLimiting ? evnt != null : evnt == null); } @@ -100,12 +101,12 @@ namespace CryptoExchange.Net.UnitTests for (var i = 0; i < requests + 1; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(i == requests ? triggered : !triggered); } triggered = false; await Task.Delay((int)Math.Round(perSeconds * 1000) + 10); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(!triggered); } @@ -123,7 +124,7 @@ namespace CryptoExchange.Net.UnitTests rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); bool expected = i == 1 ? expectLimited ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; Assert.That(expected); } @@ -143,7 +144,7 @@ namespace CryptoExchange.Net.UnitTests rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); bool expected = i == 1 ? expectLimited ? evnt?.DelayTime > TimeSpan.Zero : evnt == null : evnt == null; Assert.That(expected); } @@ -166,9 +167,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, key1, 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, key1, 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, key2, 1, RateLimitingBehaviour.Wait, null, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, key2, 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(expectLimited ? evnt != null : evnt == null); } @@ -185,9 +186,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, null, 1, RateLimitingBehaviour.Wait, null, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, null, 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(expectLimited ? evnt != null : evnt == null); } @@ -205,9 +206,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "123", 1, RateLimitingBehaviour.Wait, null, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(expectLimited ? evnt != null : evnt == null); } @@ -222,9 +223,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent? evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition(host1, "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition(host1, "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition(host2, "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition(host2, "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, 1, default); Assert.That(expectLimited ? evnt != null : evnt == null); } @@ -238,8 +239,8 @@ namespace CryptoExchange.Net.UnitTests rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2)); - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("https://test.com", "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, ct.Token); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("https://test.com", "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, ct.Token); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("https://test.com", "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, 1, ct.Token); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("https://test.com", "1", HttpMethod.Get), "123", 1, RateLimitingBehaviour.Wait, null, 1, ct.Token); Assert.That(result2.Error, Is.TypeOf()); } @@ -257,9 +258,9 @@ namespace CryptoExchange.Net.UnitTests var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2)); // act - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition, null, 1, RateLimitingBehaviour.Fail, null, ct.Token); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition, null, 1, RateLimitingBehaviour.Fail, null, 1, ct.Token); await rateLimiter.ResetAsync(RateLimitItemType.Request, definition, null, null, null, default); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition, null, 1, RateLimitingBehaviour.Fail, null, ct.Token); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition, null, 1, RateLimitingBehaviour.Fail, null, 1, ct.Token); // assert Assert.That(evnt, Is.Null); @@ -279,10 +280,10 @@ namespace CryptoExchange.Net.UnitTests rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; // act - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition1, null, 1, RateLimitingBehaviour.Fail, null, default); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition2, null, 1, RateLimitingBehaviour.Fail, null, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition1, null, 1, RateLimitingBehaviour.Fail, null, 1, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition2, null, 1, RateLimitingBehaviour.Fail, null, 1, default); await rateLimiter.ResetAsync(RateLimitItemType.Request, definition1, null, null, null, default); - var result3 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition2, null, 1, RateLimitingBehaviour.Fail, null, default); + var result3 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, definition2, null, 1, RateLimitingBehaviour.Fail, null, 1, default); // assert Assert.That(evnt, Is.Not.Null); @@ -321,5 +322,73 @@ namespace CryptoExchange.Net.UnitTests // assert Assert.That(evnt != null, Is.EqualTo(expectLimited)); } + + [TestCase(RateLimitWindowType.Fixed)] + [TestCase(RateLimitWindowType.FixedAfterFirst)] + [TestCase(RateLimitWindowType.Sliding)] + [TestCase(RateLimitWindowType.Decay)] + public async Task LowerThreshold_ReservesCapacityForFullThreshold(RateLimitWindowType windowType) + { + var gate = new RateLimitGate("Test"); + gate.AddGuard(new RateLimitGuard( + RateLimitGuard.PerHost, + new LimitItemTypeFilter(RateLimitItemType.Request), + 10, + TimeSpan.FromHours(1), + windowType, + decayPerTimeSpan: 1)); + + var definition = new RequestDefinition( + "https://test.com", "/ticker", HttpMethod.Get); + var logger = new TraceLogger(); + + for (var i = 0; i < 8; i++) + { + var result = await gate.ProcessAsync( + logger, i, RateLimitItemType.Request, definition, null, 1, + RateLimitingBehaviour.Fail, null, 0.8, default); + Assert.That(result.Success, Is.True); + } + + var marketData = await gate.ProcessAsync( + logger, 9, RateLimitItemType.Request, definition, null, 1, + RateLimitingBehaviour.Fail, null, 0.8, default); + Assert.That(marketData.Error, Is.TypeOf()); + + var order = await gate.ProcessAsync( + logger, 10, RateLimitItemType.Request, definition, null, 2, + RateLimitingBehaviour.Fail, null, 1.0, default); + Assert.That(order.Success, Is.True); + + var overHardLimit = await gate.ProcessAsync( + logger, 11, RateLimitItemType.Request, definition, null, 1, + RateLimitingBehaviour.Fail, null, 1.0, default); + Assert.That(overHardLimit.Error, Is.TypeOf()); + } + + [TestCase(0.1, 10)] + [TestCase(1, 50)] + [TestCase(5, 250)] + [TestCase(60, 250)] + public void RateLimitSafetyMargin_DefaultIsProportionalAndCapped(double periodSeconds, int expectedMarginMilliseconds) + { + var margin = WindowTrackerHelpers.GetDefaultSafetyMargin(TimeSpan.FromSeconds(periodSeconds)); + + Assert.That(margin, Is.EqualTo(TimeSpan.FromMilliseconds(expectedMarginMilliseconds))); + } + + [Test] + public void RateLimitGuard_ExplicitSafetyMarginIsUsed() + { + var guard = new RateLimitGuard( + RateLimitGuard.PerHost, + new LimitItemTypeFilter(RateLimitItemType.Request), + 1, + TimeSpan.FromSeconds(1), + RateLimitWindowType.Sliding, + safetyMargin: TimeSpan.Zero); + + Assert.That(guard.SafetyMargin, Is.EqualTo(TimeSpan.Zero)); + } } } diff --git a/CryptoExchange.Net.UnitTests/RequestCoalescerTests.cs b/CryptoExchange.Net.UnitTests/RequestCoalescerTests.cs new file mode 100644 index 00000000..6d6cfc7e --- /dev/null +++ b/CryptoExchange.Net.UnitTests/RequestCoalescerTests.cs @@ -0,0 +1,281 @@ +using CryptoExchange.Net.Objects; +using Microsoft.Extensions.Logging.Abstractions; +using NUnit.Framework; +using System; +using System.Collections; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.UnitTests +{ + [TestFixture] + public class RequestCoalescerTests + { + private static readonly TimeSpan _testTimeout = TimeSpan.FromSeconds(5); + + [Test] + public async Task ExecuteAsync_WithIdenticalRequests_ShouldExecuteRequestOnce() + { + var coalescer = CreateCoalescer(); + var key = CreateKey(); + var requestCompletion = CreateCompletionSource(); + var requestStarted = CreateCompletionSource(); + var requestCount = 0; + + async Task ExecuteRequest(CancellationToken cancellationToken) + { + Interlocked.Increment(ref requestCount); + requestStarted.TrySetResult(true); + return await requestCompletion.Task; + } + + var first = coalescer.ExecuteAsync(key, ExecuteRequest, () => "canceled", CancellationToken.None); + await requestStarted.Task.WaitAsync(_testTimeout); + var second = coalescer.ExecuteAsync(key, ExecuteRequest, () => "canceled", CancellationToken.None); + + Assert.That(requestCount, Is.EqualTo(1)); + + requestCompletion.SetResult("result"); + + Assert.That(await first.WaitAsync(_testTimeout), Is.EqualTo("result")); + Assert.That(await second.WaitAsync(_testTimeout), Is.EqualTo("result")); + Assert.That(requestCount, Is.EqualTo(1)); + } + + [Test] + public async Task ExecuteAsync_WithDifferentKeys_ShouldExecuteEachRequest() + { + var coalescer = CreateCoalescer(); + var firstCompletion = CreateCompletionSource(); + var secondCompletion = CreateCompletionSource(); + var firstStarted = CreateCompletionSource(); + var secondStarted = CreateCompletionSource(); + var requestCount = 0; + + var first = coalescer.ExecuteAsync( + CreateKey(parameters: "symbol=ETH"), + async cancellationToken => + { + Interlocked.Increment(ref requestCount); + firstStarted.TrySetResult(true); + return await firstCompletion.Task; + }, + () => "canceled", + CancellationToken.None); + + var second = coalescer.ExecuteAsync( + CreateKey(parameters: "symbol=BTC"), + async cancellationToken => + { + Interlocked.Increment(ref requestCount); + secondStarted.TrySetResult(true); + return await secondCompletion.Task; + }, + () => "canceled", + CancellationToken.None); + + await Task.WhenAll(firstStarted.Task, secondStarted.Task).WaitAsync(_testTimeout); + Assert.That(requestCount, Is.EqualTo(2)); + + firstCompletion.SetResult("first"); + secondCompletion.SetResult("second"); + + Assert.That(await first.WaitAsync(_testTimeout), Is.EqualTo("first")); + Assert.That(await second.WaitAsync(_testTimeout), Is.EqualTo("second")); + } + + [Test] + public async Task ExecuteAsync_WhenOneListenerCancels_ShouldKeepSharedRequestRunning() + { + var coalescer = CreateCoalescer(); + var requestCompletion = CreateCompletionSource(); + var requestStarted = CreateCompletionSource(); + var requestCount = 0; + using var firstCancellationSource = new CancellationTokenSource(); + + async Task ExecuteRequest(CancellationToken cancellationToken) + { + Interlocked.Increment(ref requestCount); + requestStarted.TrySetResult(cancellationToken); + return await requestCompletion.Task; + } + + var first = coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "canceled", firstCancellationSource.Token); + var requestCancellationToken = await requestStarted.Task.WaitAsync(_testTimeout); + var second = coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "canceled", CancellationToken.None); + + firstCancellationSource.Cancel(); + + Assert.That(await first.WaitAsync(_testTimeout), Is.EqualTo("canceled")); + Assert.That(requestCancellationToken.IsCancellationRequested, Is.False); + Assert.That(requestCount, Is.EqualTo(1)); + + requestCompletion.SetResult("result"); + + Assert.That(await second.WaitAsync(_testTimeout), Is.EqualTo("result")); + } + + [Test] + public async Task ExecuteAsync_WhenAllListenersCancel_ShouldCancelSharedRequest() + { + var coalescer = CreateCoalescer(); + var requestStarted = CreateCompletionSource(); + var requestCanceled = CreateCompletionSource(); + using var firstCancellationSource = new CancellationTokenSource(); + using var secondCancellationSource = new CancellationTokenSource(); + + async Task ExecuteRequest(CancellationToken cancellationToken) + { + using (cancellationToken.Register(() => requestCanceled.TrySetResult(true))) + { + requestStarted.TrySetResult(true); + await requestCanceled.Task; + return "request canceled"; + } + } + + var first = coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "listener canceled", firstCancellationSource.Token); + await requestStarted.Task.WaitAsync(_testTimeout); + var second = coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "listener canceled", secondCancellationSource.Token); + + firstCancellationSource.Cancel(); + Assert.That(await first.WaitAsync(_testTimeout), Is.EqualTo("listener canceled")); + Assert.That(requestCanceled.Task.IsCompleted, Is.False); + + secondCancellationSource.Cancel(); + + Assert.That(await second.WaitAsync(_testTimeout), Is.EqualTo("listener canceled")); + Assert.That(await requestCanceled.Task.WaitAsync(_testTimeout), Is.True); + } + + [Test] + public async Task ExecuteAsync_WithAlreadyCanceledListener_ShouldNotStartRequest() + { + var coalescer = CreateCoalescer(); + var requestCount = 0; + using var cancellationSource = new CancellationTokenSource(); + cancellationSource.Cancel(); + + var result = await coalescer.ExecuteAsync( + CreateKey(), + cancellationToken => + { + Interlocked.Increment(ref requestCount); + return Task.FromResult("result"); + }, + () => "canceled", + cancellationSource.Token); + + Assert.That(result, Is.EqualTo("canceled")); + Assert.That(requestCount, Is.Zero); + } + + [Test] + public async Task ExecuteAsync_AfterRequestCompletes_ShouldStartNewRequest() + { + var coalescer = CreateCoalescer(); + var requestCount = 0; + + Task ExecuteRequest(CancellationToken cancellationToken) + => Task.FromResult(Interlocked.Increment(ref requestCount)); + + var first = await coalescer.ExecuteAsync(CreateKey(typeof(int)), ExecuteRequest, () => -1, CancellationToken.None); + var second = await coalescer.ExecuteAsync(CreateKey(typeof(int)), ExecuteRequest, () => -1, CancellationToken.None); + + Assert.That(first, Is.EqualTo(1)); + Assert.That(second, Is.EqualTo(2)); + Assert.That(requestCount, Is.EqualTo(2)); + } + + [Test] + public void ExecuteAsync_AfterRequestFails_ShouldStartNewRequest() + { + var coalescer = CreateCoalescer(); + var requestCount = 0; + + Task ExecuteRequest(CancellationToken cancellationToken) + { + if (Interlocked.Increment(ref requestCount) == 1) + return Task.FromException(new InvalidOperationException("Request failed")); + + return Task.FromResult("result"); + } + + Assert.ThrowsAsync(async () => + await coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "canceled", CancellationToken.None)); + + Assert.That( + coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "canceled", CancellationToken.None).GetAwaiter().GetResult(), + Is.EqualTo("result")); + Assert.That(requestCount, Is.EqualTo(2)); + } + + [Test] + public async Task ExecuteAsync_WhenAbandonedRequestCompletes_ShouldNotRemoveReplacementRequest() + { + var coalescer = CreateCoalescer(); + var key = CreateKey(); + var oldRequestCompletion = CreateCompletionSource(); + var oldRequestStarted = CreateCompletionSource(); + var replacementCompletion = CreateCompletionSource(); + var replacementStarted = CreateCompletionSource(); + var requestCount = 0; + using var cancellationSource = new CancellationTokenSource(); + + async Task ExecuteOldRequest(CancellationToken cancellationToken) + { + Interlocked.Increment(ref requestCount); + oldRequestStarted.TrySetResult(true); + return await oldRequestCompletion.Task; + } + + async Task ExecuteReplacementRequest(CancellationToken cancellationToken) + { + Interlocked.Increment(ref requestCount); + replacementStarted.TrySetResult(true); + return await replacementCompletion.Task; + } + + var abandonedListener = coalescer.ExecuteAsync(key, ExecuteOldRequest, () => "canceled", cancellationSource.Token); + await oldRequestStarted.Task.WaitAsync(_testTimeout); + var oldPhysicalRequest = GetPhysicalRequest(coalescer, key); + + cancellationSource.Cancel(); + Assert.That(await abandonedListener.WaitAsync(_testTimeout), Is.EqualTo("canceled")); + + var replacementListener = coalescer.ExecuteAsync(key, ExecuteReplacementRequest, () => "canceled", CancellationToken.None); + await replacementStarted.Task.WaitAsync(_testTimeout); + + oldRequestCompletion.SetResult("old result"); + await oldPhysicalRequest.WaitAsync(_testTimeout); + + var joinedReplacementListener = coalescer.ExecuteAsync(key, ExecuteReplacementRequest, () => "canceled", CancellationToken.None); + Assert.That(requestCount, Is.EqualTo(2)); + + replacementCompletion.SetResult("replacement result"); + + Assert.That(await replacementListener.WaitAsync(_testTimeout), Is.EqualTo("replacement result")); + Assert.That(await joinedReplacementListener.WaitAsync(_testTimeout), Is.EqualTo("replacement result")); + Assert.That(requestCount, Is.EqualTo(2)); + } + + private static RequestCoalescer CreateCoalescer() + => new RequestCoalescer(NullLogger.Instance); + + private static RequestCoalescingKey CreateKey(Type? responseType = null, string? parameters = null) + => new RequestCoalescingKey("GET", "https://localhost/test", parameters, responseType ?? typeof(string)); + + private static TaskCompletionSource CreateCompletionSource() + => new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + private static Task GetPhysicalRequest(RequestCoalescer coalescer, RequestCoalescingKey key) + { + var requestsField = typeof(RequestCoalescer).GetField("_requests", BindingFlags.Instance | BindingFlags.NonPublic)!; + var requests = (IDictionary)requestsField.GetValue(coalescer)!; + var entry = requests[key]!; + var requestProperty = entry.GetType().GetProperty("Request")!; + return ((Lazy>)requestProperty.GetValue(entry)!).Value; + } + } +} diff --git a/CryptoExchange.Net.UnitTests/SharedApiClientTests.cs b/CryptoExchange.Net.UnitTests/SharedApiClientTests.cs new file mode 100644 index 00000000..40878c02 --- /dev/null +++ b/CryptoExchange.Net.UnitTests/SharedApiClientTests.cs @@ -0,0 +1,355 @@ +using CryptoExchange.Net.Interfaces.Clients; +using CryptoExchange.Net.RateLimiting; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.UnitTests +{ + [TestFixture] + internal class SharedApiClientTests + { + [Test] + public void GetCapability_ShouldUseCapabilityTradingModes() + { + var sharedApi = new TestSharedApi( + [TradingMode.Spot, TradingMode.PerpetualLinear], + [TradingMode.Spot]); + var client = new TestSharedApiClient(sharedApi); + + Assert.Multiple(() => + { + Assert.That(client.GetCapability(TradingMode.Spot)?.Capability, Is.SameAs(sharedApi)); + Assert.That(client.GetCapability(TradingMode.PerpetualLinear), Is.Null); + Assert.That(client.GetCapabilities(TradingMode.Spot), Has.Count.EqualTo(1)); + Assert.That(client.GetCapabilities(TradingMode.PerpetualLinear), Is.Empty); + }); + } + + [Test] + public void CapabilityTradingModes_ShouldDefaultToSharedApiTradingModes() + { + var sharedApi = new TestSharedApi( + [TradingMode.Spot, TradingMode.PerpetualLinear], + null); + var client = new TestSharedApiClient(sharedApi); + + Assert.Multiple(() => + { + Assert.That(client.GetCapability(TradingMode.Spot)?.Capability, Is.SameAs(sharedApi)); + Assert.That(client.GetCapability(TradingMode.PerpetualLinear)?.Capability, Is.SameAs(sharedApi)); + }); + } + + [Test] + public void GetCapabilities_WithReference_ShouldReturnMatchingCapabilities() + { + var sharedApi = new TestSharedApi( + [TradingMode.Spot], + [TradingMode.Spot]); + var client = new TestSharedApiClient(sharedApi); + + var result = client.GetCapabilities( + new SharedCapabilityReference(), + TradingMode.Spot); + + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0].Capability, Is.SameAs(sharedApi)); + Assert.That(result[0].Options, Is.SameAs(sharedApi.CapabilityOptions)); + } + + [Test] + public void Discover_ShouldReturnAllSharedApiInformation() + { + var restApi = new TestRestSharedApi(); + var socketApi = new TestSocketSharedApi(); + var client = new TestDiSharedApiClient( + restApi, + socketApi); + + var result = client.Discover(); + + Assert.Multiple(() => + { + Assert.That(result.PreferredTransport, Is.EqualTo(SharedTransport.Socket)); + Assert.That(result.SharedApis, Has.Length.EqualTo(2)); + Assert.That(result.SharedApis[0].Transport, Is.EqualTo(SharedTransport.Rest)); + Assert.That(result.SharedApis[1].Transport, Is.EqualTo(SharedTransport.Socket)); + Assert.That(result.SharedApis[0].SupportedTradingModes, Is.EqualTo(new[] { TradingMode.Spot })); + Assert.That(result.SharedApis[0].Capabilities, Has.Length.EqualTo(1)); + Assert.That(result.SharedApis[0].Authenticated, Is.False); + }); + } + + [Test] + public void RegisterSharedApiClient_ShouldResolvePreferredTransport() + { + var restApi = new TestRestSharedApi(); + var socketApi = new TestSocketSharedApi(); + var services = new ServiceCollection(); + + services.AddSingleton(restApi); + services.AddSingleton(socketApi); + + services.RegisterSharedApiClient< + ITestSharedApiClient, + TestDiSharedApiClient>(sharedApis => sharedApis + .Add(client => client.Rest) + .Add(client => client.Socket)); + + using var provider = services.BuildServiceProvider(); + + Assert.Multiple(() => + { + Assert.That( + provider.GetRequiredService(), + Is.Not.Null); + + Assert.That( + provider.GetRequiredService(), + Is.InstanceOf()); + + Assert.That( + provider.GetRequiredService(), + Is.SameAs(socketApi)); + + Assert.That( + provider.GetServices().ToArray(), + Is.EqualTo(new[] { socketApi })); + + Assert.That( + provider.GetRequiredService(), + Is.SameAs(restApi)); + + Assert.That( + provider.GetRequiredService(), + Is.SameAs(socketApi)); + }); + } + + [Test] + public void CapabilityTradingModes_ShouldIntersectApiApplicableAndOverrideModes() + { + var sharedApi = new TestSharedApi( + [TradingMode.Spot, TradingMode.PerpetualLinear, TradingMode.DeliveryLinear], + [TradingMode.Spot, TradingMode.PerpetualLinear], + [TradingMode.PerpetualLinear, TradingMode.DeliveryLinear]); + + Assert.That( + sharedApi.CapabilityOptions.SupportedTradingModes, + Is.EqualTo(new[] { TradingMode.PerpetualLinear })); + } + + [Test] + public void CapabilityOptions_ShouldApplyIntrinsicTradingModes() + { + var apiModes = new[] + { + TradingMode.Spot, + TradingMode.PerpetualLinear, + TradingMode.DeliveryLinear, + TradingMode.PerpetualInverse, + TradingMode.DeliveryInverse + }; + var spotOptions = new PlaceSpotOrderOptions("TestExchange"); + var futuresOptions = new GetPositionsOptions("TestExchange", false); + var perpetualOptions = new GetFundingRateHistoryOptions( + "TestExchange", + true, + true, + true, + 100, + false); + var unrestrictedOptions = new GetTickerOptions("TestExchange"); + + _ = new TestOptionsHost(apiModes, spotOptions); + _ = new TestOptionsHost(apiModes, futuresOptions); + _ = new TestOptionsHost(apiModes, perpetualOptions); + _ = new TestOptionsHost(apiModes, unrestrictedOptions); + + Assert.Multiple(() => + { + Assert.That(spotOptions.SupportedTradingModes, Is.EqualTo(new[] { TradingMode.Spot })); + Assert.That(futuresOptions.SupportedTradingModes, Is.EqualTo(new[] + { + TradingMode.PerpetualLinear, + TradingMode.DeliveryLinear, + TradingMode.PerpetualInverse, + TradingMode.DeliveryInverse + })); + Assert.That(perpetualOptions.SupportedTradingModes, Is.EqualTo(new[] + { + TradingMode.PerpetualLinear, + TradingMode.PerpetualInverse + })); + Assert.That(unrestrictedOptions.SupportedTradingModes, Is.EqualTo(apiModes)); + }); + } + + private interface ITestCapability : ISharedApiCapability + { + } + + private sealed class TestApiClient : IBaseApiClient + { + public string Exchange => "TestExchange"; + public string BaseAddress => "https://test.invalid"; + + public string FormatSymbol( + string baseAsset, + string quoteAsset, + TradingMode tradingMode, + DateTime? deliverDate = null) + => $"{baseAsset}{quoteAsset}"; + + public Task WithRateLimitAdmissionAsync( + RateLimitAdmission admission, + Func> operation) + => operation(); + } + + private interface ITestRestCapability : ITestCapability, ISharedRest + { + } + + private interface ITestSocketCapability : ITestCapability, ISharedSocket + { + } + + private interface ITestRestSharedApi : ITestRestCapability + { + } + + private interface ITestSocketSharedApi : ITestSocketCapability + { + } + + private interface ITestSharedApiClient : ISharedApiClientBase + { + ITestRestSharedApi Rest { get; } + ITestSocketSharedApi Socket { get; } + } + + private sealed class TestCapabilityOptions : CapabilityOptions + { + public override string Description => "Test capability"; + + public TestCapabilityOptions(TradingMode[]? applicableTradingModes) + : base("TestExchange", false, "TestOperation", [], applicableTradingModes) + { + } + } + + private sealed class TestSharedApi : SharedApiBase, ITestCapability + { + public TestCapabilityOptions CapabilityOptions { get; } + + public TestSharedApi(TradingMode[] apiTradingModes, TradingMode[]? capabilityTradingModes) + : this(apiTradingModes, capabilityTradingModes, null) + { + } + + public TestSharedApi( + TradingMode[] apiTradingModes, + TradingMode[]? capabilityTradingModes, + TradingMode[]? applicableTradingModes) + : base( + SharedTransport.Rest, + new TestApiClient(), + apiTradingModes, + () => false, + (baseAsset, quoteAsset, tradingMode, deliverDate) => $"{baseAsset}{quoteAsset}") + { + var options = new TestCapabilityOptions(applicableTradingModes) + { + SupportedTradingModeOverrides = capabilityTradingModes + }; + + SetCapabilities(options); + CapabilityOptions = options; + } + + public override SharedClientInfo Discover() => new(); + } + + private sealed class TestOptionsHost : SharedApiBase + { + public TestOptionsHost(TradingMode[] apiTradingModes, CapabilityOptions capabilityOptions) + : base( + SharedTransport.Rest, + new TestApiClient(), + apiTradingModes, + () => false, + (baseAsset, quoteAsset, tradingMode, deliverDate) => $"{baseAsset}{quoteAsset}") + { + SetCapabilities(capabilityOptions); + } + + public override SharedClientInfo Discover() => new(); + } + + private abstract class TestTransportSharedApi : SharedApiBase + { + protected TestTransportSharedApi(SharedTransport transport) + : base( + transport, + new TestApiClient(), + [TradingMode.Spot], + () => false, + (baseAsset, quoteAsset, tradingMode, deliverDate) => $"{baseAsset}{quoteAsset}") + { + SetCapabilities(new TestCapabilityOptions([TradingMode.Spot])); + } + + public override SharedClientInfo Discover() => new() + { + Exchange = this.Exchange, + TypeName = GetType().Name, + SupportedTradingModes = this.SupportedTradingModes, + Transport = this.Transport, + Authenticated = this.Authenticated, + Capabilities = ((ISharedApi)this).Capabilities.ToArray() + }; + } + + private sealed class TestRestSharedApi : TestTransportSharedApi, ITestRestSharedApi + { + public TestRestSharedApi() : base(SharedTransport.Rest) + { + } + } + + private sealed class TestSocketSharedApi : TestTransportSharedApi, ITestSocketSharedApi + { + public TestSocketSharedApi() : base(SharedTransport.Socket) + { + } + } + + private sealed class TestDiSharedApiClient : SharedApiClientBase, ITestSharedApiClient + { + public ITestRestSharedApi Rest { get; } + public ITestSocketSharedApi Socket { get; } + + public TestDiSharedApiClient( + TestRestSharedApi rest, + TestSocketSharedApi socket) + : base(SharedTransport.Socket, rest, socket) + { + Rest = rest; + Socket = socket; + } + } + + private sealed class TestSharedApiClient : SharedApiClientBase + { + public TestSharedApiClient(params ISharedApiCapability[] sharedApis) + : base(SharedTransport.Rest, sharedApis) + { + } + } + } +} diff --git a/CryptoExchange.Net.UnitTests/SharedCapabilitiesTests.cs b/CryptoExchange.Net.UnitTests/SharedCapabilitiesTests.cs new file mode 100644 index 00000000..c7205419 --- /dev/null +++ b/CryptoExchange.Net.UnitTests/SharedCapabilitiesTests.cs @@ -0,0 +1,89 @@ +using CryptoExchange.Net.SharedApis; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace CryptoExchange.Net.UnitTests +{ + [TestFixture] + internal class SharedCapabilitiesTests + { + [Test] + public void AllSharedApiCapabilities_ShouldBeListedInSharedCapabilities() + { + var markerTypes = new[] + { + typeof(ISharedApiCapability), + typeof(ISharedRest), + typeof(ISharedSocket), + typeof(ISharedSubscription) + }; + + var expectedCapabilities = typeof(ISharedApiCapability).Assembly + .GetTypes() + .Where(x => x.IsInterface + && typeof(ISharedApiCapability).IsAssignableFrom(x) + && !markerTypes.Contains(x)) + .OrderBy(x => x.FullName) + .ToArray(); + + var listedCapabilities = GetTypeAndNestedTypes(typeof(SharedCapabilities)) + .SelectMany(x => x.GetProperties( + BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)) + .SelectMany(x => x.PropertyType.GetGenericArguments()) + .Where(x => x.IsInterface + && typeof(ISharedApiCapability).IsAssignableFrom(x)) + .ToArray(); + + var missingCapabilities = expectedCapabilities + .Except(listedCapabilities) + .Select(x => x.Name) + .ToArray(); + + var unknownCapabilities = listedCapabilities + .Except(expectedCapabilities) + .Select(x => x.Name) + .ToArray(); + + var duplicateCapabilities = listedCapabilities + .GroupBy(x => x) + .Where(x => x.Count() > 1) + .Select(x => x.Key.Name) + .ToArray(); + + Assert.Multiple(() => + { + Assert.That( + missingCapabilities, + Is.Empty, + $"Capabilities missing from SharedCapabilities: " + + string.Join(", ", missingCapabilities)); + + Assert.That( + unknownCapabilities, + Is.Empty, + $"Unknown capabilities listed in SharedCapabilities: " + + string.Join(", ", unknownCapabilities)); + + Assert.That( + duplicateCapabilities, + Is.Empty, + $"Capabilities listed multiple times in SharedCapabilities: " + + string.Join(", ", duplicateCapabilities)); + }); + } + + private static IEnumerable GetTypeAndNestedTypes(Type type) + { + yield return type; + + foreach (var nestedType in type.GetNestedTypes(BindingFlags.Public)) + { + foreach (var result in GetTypeAndNestedTypes(nestedType)) + yield return result; + } + } + } +} diff --git a/CryptoExchange.Net.UnitTests/SharedParametersTests.cs b/CryptoExchange.Net.UnitTests/SharedParametersTests.cs new file mode 100644 index 00000000..4890857e --- /dev/null +++ b/CryptoExchange.Net.UnitTests/SharedParametersTests.cs @@ -0,0 +1,180 @@ +using NUnit.Framework; +using CryptoExchange.Net.SharedApis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace CryptoExchange.Net.UnitTests +{ + [TestFixture] + internal class SharedParametersTests + { + [Test] + public void AllSharedAPIOptions_ShouldListAllDefaultParametersInRequestParameterRules() + { + var failures = new List(); + + var optionsTypes = typeof(CapabilityOptions).Assembly + .GetTypes() + .Where(x => x.IsClass + && !x.IsAbstract + && typeof(CapabilityOptions).IsAssignableFrom(x)) + .OrderBy(x => x.FullName) + .ToArray(); + + foreach (var optionsType in optionsTypes) + { + var genericOptionsType = GetGenericOptionsType(optionsType); + if (genericOptionsType == null) + { + if (optionsType == typeof(ClosePositionOptions)) + continue; + + failures.Add($"{optionsType.Name}: unable to determine request type"); + continue; + } + + var requestType = genericOptionsType.GetGenericArguments()[0]; + var defaultRulesField = GetDefaultParameterRulesField(optionsType); + if (defaultRulesField == null) + { + failures.Add($"{optionsType.Name}: no _defaultParameterRules field found"); + continue; + } + + var rules = (RequestParameterDescription[]?)defaultRulesField.GetValue(null); + if (rules == null) + { + failures.Add($"{optionsType.Name}: _defaultParameterRules is null"); + continue; + } + + var constructorParameterNames = requestType + .GetConstructors(BindingFlags.Instance | BindingFlags.Public) + .SelectMany(x => x.GetParameters()) + .Where(x => !string.Equals( + x.Name, + "exchangeParameters", + StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Name!) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + var parameterProperties = requestType + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(x => x.DeclaringType == requestType + || constructorParameterNames.Contains(x.Name)) + .Where(x => x.Name != nameof(SharedRequest.ExchangeParameters)) + .Select(x => x.Name) + .OrderBy(x => x) + .ToArray(); + + var ruleNames = rules + .Select(x => x.Name) + .ToHashSet(StringComparer.Ordinal); + + foreach (var parameterProperty in parameterProperties) + { + if (!ruleNames.Contains(parameterProperty)) + { + failures.Add( + $"{optionsType.Name}: request property " + + $"{requestType.Name}.{parameterProperty} has no default request parameter rule"); + } + } + } + + Assert.That( + failures, + Is.Empty, + $"Missing default request parameter rules:{Environment.NewLine}" + + string.Join(Environment.NewLine, failures)); + } + + [Test] + public void RequiredExchangeParameter_ShouldAcceptNameOrAnyAlias() + { + const string exchange = "TestExchange"; + var options = new GetTickerOptions(exchange) + { + ExchangeParameterRules = + [ + ExchangeParameterRule.Required( + "Parameter", + "Test parameter", + 1, + "Alias1", + "Alias2") + ] + }; + + var missingResult = options.ValidateRequest(null, null, [TradingMode.Spot]); + var nameResult = options.ValidateRequest( + new ExchangeParameters(new ExchangeParameter(exchange, "Parameter", 1)), + null, + [TradingMode.Spot]); + var firstAliasResult = options.ValidateRequest( + new ExchangeParameters(new ExchangeParameter(exchange, "Alias1", 1)), + null, + [TradingMode.Spot]); + var secondAliasResult = options.ValidateRequest( + new ExchangeParameters(new ExchangeParameter(exchange, "Alias2", 1)), + null, + [TradingMode.Spot]); + + Assert.Multiple(() => + { + Assert.That(missingResult, Is.Not.Null); + Assert.That(nameResult, Is.Null); + Assert.That(firstAliasResult, Is.Null); + Assert.That(secondAliasResult, Is.Null); + }); + } + + [Test] + public void RequiredExchangeParameterWithoutAliases_ShouldBeRequired() + { + const string exchange = "TestExchange"; + var options = new GetTickerOptions(exchange) + { + ExchangeParameterRules = + [ + ExchangeParameterRule.Required( + "Parameter", + "Test parameter", + 1) + ] + }; + + var result = options.ValidateRequest(null, null, [TradingMode.Spot]); + + Assert.That(result, Is.Not.Null); + } + + private static Type? GetGenericOptionsType(Type optionsType) + { + for (var type = optionsType; type != null; type = type.BaseType) + { + if (type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(CapabilityOptions<,>)) + return type; + } + + return null; + } + + private static FieldInfo? GetDefaultParameterRulesField(Type optionsType) + { + for (var type = optionsType; type != null; type = type.BaseType) + { + var field = type.GetField( + "_defaultParameterRules", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); + if (field != null) + return field; + } + + return null; + } + } +} diff --git a/CryptoExchange.Net/Clients/BaseApiClient.cs b/CryptoExchange.Net/Clients/BaseApiClient.cs index 0d6e6b52..1793f7f6 100644 --- a/CryptoExchange.Net/Clients/BaseApiClient.cs +++ b/CryptoExchange.Net/Clients/BaseApiClient.cs @@ -1,10 +1,13 @@ -using System; using CryptoExchange.Net.Interfaces.Clients; using CryptoExchange.Net.Objects.Errors; using CryptoExchange.Net.Objects.Options; +using CryptoExchange.Net.RateLimiting; using CryptoExchange.Net.SharedApis; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Threading; +using System.Threading.Tasks; namespace CryptoExchange.Net.Clients { @@ -78,6 +81,11 @@ namespace CryptoExchange.Net.Clients /// protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]); + /// + /// Current rate limit admission override + /// + protected readonly AsyncLocal AdmissionOverride = new(); + /// /// ctor /// @@ -122,6 +130,29 @@ namespace CryptoExchange.Net.Clients /// public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message); + /// + public async Task WithRateLimitAdmissionAsync( + RateLimitAdmission admission, + Func> operation) + { + if (admission == null) + throw new ArgumentNullException(nameof(admission)); + if (operation == null) + throw new ArgumentNullException(nameof(operation)); + + var previous = AdmissionOverride.Value; + AdmissionOverride.Value = admission; + + try + { + return await operation().ConfigureAwait(false); + } + finally + { + AdmissionOverride.Value = previous; + } + } + /// /// Dispose /// diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 98c8c985..b6973641 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -98,6 +98,10 @@ namespace CryptoExchange.Net.Clients /// Configured environment name /// public abstract string EnvironmentName { get; } + /// + /// Request coalescer + /// + protected RequestCoalescer RequestCoalescer { get; } /// /// ctor @@ -123,6 +127,7 @@ namespace CryptoExchange.Net.Clients { TimeOffsetManager.RegisterRestApi(ClientName); + RequestCoalescer = new RequestCoalescer(_logger); RequestFactory.Configure(options, httpClient); } @@ -177,8 +182,7 @@ namespace CryptoExchange.Net.Clients /// Override the request weight for this request definition, for example when the weight depends on the parameters /// Specify the weight to apply to the individual rate limit guard for this request /// An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters. - /// - protected virtual async Task> SendAsync( + protected virtual Task> SendAsync( RequestDefinition definition, Parameters? uriParameters, Parameters? bodyParameters, @@ -187,6 +191,61 @@ namespace CryptoExchange.Net.Clients int? weight = null, int? weightSingleLimiter = null, string? rateLimitKeySuffix = null) + { + if (!ShouldCoalesce(definition, additionalHeaders)) + { + return SendCoreAsync( + definition, + uriParameters, + bodyParameters, + cancellationToken, + additionalHeaders, + weight, + weightSingleLimiter, + rateLimitKeySuffix); + } + + var key = GetRequestCoalescingKey( + definition, + uriParameters); + + return RequestCoalescer.ExecuteAsync( + key, + requestCancellationToken => SendCoreAsync( + definition, + uriParameters, + bodyParameters, + requestCancellationToken, + additionalHeaders, + weight, + weightSingleLimiter, + rateLimitKeySuffix), + () => HttpResult.Fail( + Exchange, + new CancellationRequestedError()), + cancellationToken); + } + + /// + /// Send a request to the base address based on the request definition + /// + /// Response type + /// Request definition + /// Request query parameters + /// Request body parameters + /// Cancellation token + /// Additional headers for this request + /// Override the request weight for this request definition, for example when the weight depends on the parameters + /// Specify the weight to apply to the individual rate limit guard for this request + /// An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters. + protected virtual async Task> SendCoreAsync(RequestDefinition definition, + Parameters? uriParameters, + Parameters? bodyParameters, + CancellationToken cancellationToken, + Dictionary? additionalHeaders = null, + int? weight = null, + int? weightSingleLimiter = null, + string? rateLimitKeySuffix = null) { var requestId = ExchangeHelpers.NextId(); if (definition.Authenticated && GetAuthenticationProvider() == null) @@ -291,6 +350,9 @@ namespace CryptoExchange.Net.Clients if (ClientOptions.RateLimiterEnabled) { + var admissionOverride = AdmissionOverride.Value ?? ClientOptions.RateLimitAdmission?.Invoke(definition, requestWeight); + var rateRatio = admissionOverride?.MaxUtilizationRatio ?? 1.0; + var limitResult = await definition.RateLimitGate.ProcessAsync( _logger, requestId, @@ -300,6 +362,7 @@ namespace CryptoExchange.Net.Clients requestWeight, ClientOptions.RateLimitingBehaviour, rateLimitKeySuffix + ClientOptions.RateLimitGroup, + rateRatio, cancellationToken).ConfigureAwait(false); if (!limitResult.Success) return limitResult.Error!; @@ -315,6 +378,9 @@ namespace CryptoExchange.Net.Clients if (ClientOptions.RateLimiterEnabled) { var singleRequestWeight = weightSingleLimiter ?? 1; + var admissionOverride = AdmissionOverride.Value ?? ClientOptions.RateLimitAdmission?.Invoke(definition, singleRequestWeight); + var rateRatio = admissionOverride?.MaxUtilizationRatio ?? 1.0; + var limitResult = await definition.RateLimitGate.ProcessSingleAsync( _logger, requestId, @@ -325,6 +391,7 @@ namespace CryptoExchange.Net.Clients singleRequestWeight, ClientOptions.RateLimitingBehaviour, rateLimitKeySuffix, + rateRatio, cancellationToken).ConfigureAwait(false); if (!limitResult.Success) return limitResult.Error!; @@ -688,7 +755,10 @@ namespace CryptoExchange.Net.Clients stringData = stringSerializer.Serialize(parameters.BodyValue); } else + { stringData = stringSerializer.Serialize(parameters); + } + request.SetContent(stringData, RequestBodyContentEncoding, contentType); } else if (contentType == Constants.FormContentHeader) @@ -808,6 +878,34 @@ namespace CryptoExchange.Net.Clients return definition.FullUrl + definition + parameters?.ToFormData(); } + /// + /// Whether a request can be coalesced or not + /// + protected virtual bool ShouldCoalesce( + RequestDefinition definition, + Dictionary? additionalHeaders) + { + return ClientOptions.RequestCoalescingEnabled // Enabled in client options + && !definition.PreventRequestCoalescing // Enabled in request definition + && definition.Method == HttpMethod.Get // Is a GET request + && !definition.Authenticated // Not an authenticated request + && (additionalHeaders == null || additionalHeaders.Count == 0); // No additional headers that might change the response + } + + /// + /// Get a unique key for coalescing requests based on the request definition, parameters, and the type of the expected response. + /// + protected virtual RequestCoalescingKey GetRequestCoalescingKey( + RequestDefinition definition, + Parameters? uriParameters) + { + return new RequestCoalescingKey( + definition.Method.Method, + definition.FullUrl, + uriParameters?.ToFormData(), + typeof(T)); + } + /// public virtual void SetOptions(UpdateOptions options) { diff --git a/CryptoExchange.Net/Clients/SocketApiClient.cs b/CryptoExchange.Net/Clients/SocketApiClient.cs index e9c533b2..70f862b8 100644 --- a/CryptoExchange.Net/Clients/SocketApiClient.cs +++ b/CryptoExchange.Net/Clients/SocketApiClient.cs @@ -858,6 +858,8 @@ namespace CryptoExchange.Net.Clients ReconnectInterval = ClientOptions.ReconnectInterval, RateLimiter = ClientOptions.RateLimiterEnabled ? RateLimiter : null, RateLimitingBehavior = ClientOptions.RateLimitingBehaviour, + RateLimitAdmissionCallbackOptions = (def, weight) => ClientOptions.RateLimitAdmission?.Invoke(def, weight), + RateLimitAdmissionCallbackRequest = () => AdmissionOverride.Value, Proxy = ClientOptions.Proxy, Timeout = ApiOptions.SocketNoDataTimeout ?? ClientOptions.SocketNoDataTimeout, ReceiveBufferSize = ClientOptions.ReceiveBufferSize, diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs index a3aa296d..eb437d81 100644 --- a/CryptoExchange.Net/ExtensionMethods.cs +++ b/CryptoExchange.Net/ExtensionMethods.cs @@ -1,15 +1,20 @@ -using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Clients; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Interfaces.Clients; using CryptoExchange.Net.Objects; +using CryptoExchange.Net.RateLimiting; using CryptoExchange.Net.SharedApis; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Threading.Tasks; using System.Web; namespace CryptoExchange.Net @@ -145,25 +150,42 @@ namespace CryptoExchange.Net /// public static string ToFormData(this IDictionary parameters) { - var formData = HttpUtility.ParseQueryString(string.Empty); + var result = new StringBuilder(); + var first = true; + + void Append(string key, object value) + { + if (!first) + result.Append('&'); + first = false; + + if (!string.IsNullOrEmpty(key)) + { + result.Append(HttpUtility.UrlEncode(key, Encoding.UTF8)); + result.Append('='); + } + + var text = string.Format(CultureInfo.InvariantCulture, "{0}", value); + result.Append(HttpUtility.UrlEncode(text, Encoding.UTF8)); + } + foreach (var kvp in parameters) { if (kvp.Value is null) continue; - if (kvp.Value.GetType().IsArray) + if (kvp.Value is Array array) { - var array = (Array)kvp.Value; foreach (var value in array) - formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", value)); + Append(kvp.Key, value); } else { - formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", kvp.Value)); + Append(kvp.Key, kvp.Value); } } - return formData.ToString()!; + return result.ToString(); } /// @@ -395,7 +417,6 @@ namespace CryptoExchange.Net services.AddTransient(x => (IBookTickerRestClient)client(x)!); if (typeof(ITransferRestClient).IsAssignableFrom(typeof(T))) services.AddTransient(x => (ITransferRestClient)client(x)!); - if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T))) services.AddTransient(x => (ISpotOrderRestClient)client(x)!); if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T))) @@ -493,6 +514,41 @@ namespace CryptoExchange.Net return bytes; } + + /// + /// Execute an operation with a specific rate limit admission rule + /// + /// Client + /// Rate limit admission rule + /// Operation to execute + public static Task WithRateLimitAdmissionAsync( + this TClient client, + RateLimitAdmission admission, + Func> operation) + where TClient : IRateLimitAdmissionClient + { + return client.WithRateLimitAdmissionAsync( + admission, + () => operation(client)); + } + + + /// + /// Return the task results in the form of an IAsyncEnumerable, returning the first completed task first + /// + /// Type of task result + /// Task list + public static async IAsyncEnumerable ParallelEnumerateAsync(this IEnumerable> tasks) + { + var remaining = new List>(tasks); + + while (remaining.Count != 0) + { + var task = await Task.WhenAny(remaining).ConfigureAwait(false); + remaining.Remove(task); + yield return await task.ConfigureAwait(false); + } + } } } diff --git a/CryptoExchange.Net/Interfaces/Clients/IBaseApiClient.cs b/CryptoExchange.Net/Interfaces/Clients/IBaseApiClient.cs index de5f88e9..ed9c80b6 100644 --- a/CryptoExchange.Net/Interfaces/Clients/IBaseApiClient.cs +++ b/CryptoExchange.Net/Interfaces/Clients/IBaseApiClient.cs @@ -1,6 +1,4 @@ -using CryptoExchange.Net.Authentication; -using CryptoExchange.Net.Objects.Options; -using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.SharedApis; using System; namespace CryptoExchange.Net.Interfaces.Clients @@ -8,7 +6,7 @@ namespace CryptoExchange.Net.Interfaces.Clients /// /// Base api client /// - public interface IBaseApiClient + public interface IBaseApiClient : IRateLimitAdmissionClient { /// /// Exchange name diff --git a/CryptoExchange.Net/Interfaces/IRateLimitAdmissionClient.cs b/CryptoExchange.Net/Interfaces/IRateLimitAdmissionClient.cs new file mode 100644 index 00000000..81eacae0 --- /dev/null +++ b/CryptoExchange.Net/Interfaces/IRateLimitAdmissionClient.cs @@ -0,0 +1,22 @@ +using CryptoExchange.Net.RateLimiting; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.Interfaces +{ + /// + /// A client supporting rate limit admission rules + /// + public interface IRateLimitAdmissionClient + { + /// + /// Execute an operation with a specific rate limit admission rule + /// + Task WithRateLimitAdmissionAsync( + RateLimitAdmission admission, + Func> operation); + } + +} diff --git a/CryptoExchange.Net/Logging/Extensions/RestApiClientLoggingExtensions.cs b/CryptoExchange.Net/Logging/Extensions/RestApiClientLoggingExtensions.cs index 901d935c..546b5cab 100644 --- a/CryptoExchange.Net/Logging/Extensions/RestApiClientLoggingExtensions.cs +++ b/CryptoExchange.Net/Logging/Extensions/RestApiClientLoggingExtensions.cs @@ -22,6 +22,7 @@ namespace CryptoExchange.Net.Logging.Extensions private static readonly Action _restApiCacheHit; private static readonly Action _restApiCacheNotHit; private static readonly Action _restApiCancellationRequested; + private static readonly Action _restApiRequestsJoined; static RestApiClientLoggingExtensions() { @@ -89,7 +90,11 @@ namespace CryptoExchange.Net.Logging.Extensions LogLevel.Debug, new EventId(4012, "RestApiCancellationRequested"), "[Req {RequestId}] request cancelled by user"); - + + _restApiRequestsJoined = LoggerMessage.Define( + LogLevel.Debug, + new EventId(4013, "RestApiRequestsJoined"), + "Request for {Method} {Path} joined with in flight request"); } public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error, string? originalData, Exception? exception) @@ -151,9 +156,15 @@ namespace CryptoExchange.Net.Logging.Extensions { _restApiCacheNotHit(logger, key, null); } + public static void RestApiCancellationRequested(this ILogger logger, int? requestId) { _restApiCancellationRequested(logger, requestId, null); } + + public static void RestApiRequestsJoined(this ILogger logger, string method, string path) + { + _restApiRequestsJoined(logger, method, path, null); + } } } diff --git a/CryptoExchange.Net/Objects/Options/ExchangeOptions.cs b/CryptoExchange.Net/Objects/Options/ExchangeOptions.cs index 5c062bdb..99e20d0b 100644 --- a/CryptoExchange.Net/Objects/Options/ExchangeOptions.cs +++ b/CryptoExchange.Net/Objects/Options/ExchangeOptions.cs @@ -1,4 +1,5 @@ using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.RateLimiting; using System; namespace CryptoExchange.Net.Objects.Options @@ -41,6 +42,12 @@ namespace CryptoExchange.Net.Objects.Options /// public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait; + /// + /// A delegate receiving the request definition and the request weight that can be used to determine whether a request should be admitted or rejected based + /// on the request definition and the current rate limit usage ratio. This allows for custom rate limiting logic to be implemented. + /// + public Func? RateLimitAdmission { get; set; } + /// public override string ToString() { diff --git a/CryptoExchange.Net/Objects/Options/RestExchangeOptions.cs b/CryptoExchange.Net/Objects/Options/RestExchangeOptions.cs index dfbb87c1..e6f32eab 100644 --- a/CryptoExchange.Net/Objects/Options/RestExchangeOptions.cs +++ b/CryptoExchange.Net/Objects/Options/RestExchangeOptions.cs @@ -34,6 +34,11 @@ namespace CryptoExchange.Net.Objects.Options = new Version(1, 1); #endif + /// + /// Whether concurrent identical REST requests may share the same in-flight request when allowed by the request definition. + /// + public bool RequestCoalescingEnabled { get; set; } = true; + /// /// Http client keep alive interval for keeping connections open. Only applied when using dotnet8.0 or higher and dependency injection /// @@ -70,9 +75,11 @@ namespace CryptoExchange.Net.Objects.Options item.RateLimiterEnabled = RateLimiterEnabled; item.RateLimitingBehaviour = RateLimitingBehaviour; item.RateLimitGroup = RateLimitGroup; + item.RateLimitAdmission = RateLimitAdmission; item.CachingEnabled = CachingEnabled; item.CachingMaxAge = CachingMaxAge; item.HttpVersion = HttpVersion; + item.RequestCoalescingEnabled = RequestCoalescingEnabled; item.HttpKeepAliveInterval = HttpKeepAliveInterval; #if NET5_0_OR_GREATER item.HttpMaxConnectionsPerServer = HttpMaxConnectionsPerServer; diff --git a/CryptoExchange.Net/Objects/Options/SocketExchangeOptions.cs b/CryptoExchange.Net/Objects/Options/SocketExchangeOptions.cs index c01bad9c..db5e1aeb 100644 --- a/CryptoExchange.Net/Objects/Options/SocketExchangeOptions.cs +++ b/CryptoExchange.Net/Objects/Options/SocketExchangeOptions.cs @@ -105,6 +105,7 @@ namespace CryptoExchange.Net.Objects.Options item.RateLimitingBehaviour = RateLimitingBehaviour; item.RateLimiterEnabled = RateLimiterEnabled; item.RateLimitGroup = RateLimitGroup; + item.RateLimitAdmission = RateLimitAdmission; item.ReceiveBufferSize = ReceiveBufferSize; return item; } diff --git a/CryptoExchange.Net/Objects/RequestCoalescer.cs b/CryptoExchange.Net/Objects/RequestCoalescer.cs new file mode 100644 index 00000000..46dc6f2d --- /dev/null +++ b/CryptoExchange.Net/Objects/RequestCoalescer.cs @@ -0,0 +1,277 @@ +using CryptoExchange.Net.Logging.Extensions; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.Objects +{ + /// + /// Request coalescer, used to detect multiple identical requests and wait for and return only a single actual request result + /// + public class RequestCoalescer + { + private sealed class RequestEntry + { + public RequestCoalescingKey Key { get; } + + public CancellationTokenSource CancellationSource { get; } = new(); + + public Lazy> Request { get; set; } = null!; + + public int ListenerCount { get; set; } + + public bool Completed { get; set; } + + public bool CancellationInProgress { get; set; } + + public bool Disposed { get; set; } + + public RequestEntry(RequestCoalescingKey key) + { + Key = key; + } + } + + private readonly object _sync = new(); + private readonly ILogger _logger; + + private readonly Dictionary _requests = new(); + + /// + /// ctor + /// + public RequestCoalescer(ILogger logger) + { + _logger = logger; + } + + /// + /// Execute a request, coalescing identical requests into a single request + /// + public Task ExecuteAsync( + RequestCoalescingKey key, + Func> requestFactory, + Func cancellationResultFactory, + CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromResult(cancellationResultFactory()); + + RequestEntry entry; + bool joined; + + lock (_sync) + { + joined = _requests.TryGetValue(key, out entry!); + + if (!joined) + { + entry = CreateEntry(key, requestFactory); + _requests.Add(key, entry); + } + + entry.ListenerCount++; + } + + if (joined) + _logger.RestApiRequestsJoined(key.Method, key.Url); + + return WaitAsync( + entry, + cancellationResultFactory, + cancellationToken); + } + + private RequestEntry CreateEntry( + RequestCoalescingKey key, + Func> requestFactory) + { + var entry = new RequestEntry(key); + + entry.Request = new Lazy>( + () => ExecuteCoreAsync( + entry, + async requestCancellationToken => + (await requestFactory(requestCancellationToken).ConfigureAwait(false))!), + LazyThreadSafetyMode.ExecutionAndPublication); + + return entry; + } + + private async Task ExecuteCoreAsync( + RequestEntry entry, + Func> requestFactory) + { + try + { + return await requestFactory(entry.CancellationSource.Token).ConfigureAwait(false); + } + finally + { + CompleteRequest(entry); + } + } + + private async Task WaitAsync( + RequestEntry entry, + Func cancellationResultFactory, + CancellationToken cancellationToken) + { + try + { + var requestTask = entry.Request.Value; + + if (!cancellationToken.CanBeCanceled) + return (TResult)await requestTask.ConfigureAwait(false); + + var cancellationCompletion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + using (cancellationToken.Register( + state => ((TaskCompletionSource)state!).TrySetResult(true), + cancellationCompletion)) + { + var completedTask = await Task.WhenAny(requestTask, cancellationCompletion.Task).ConfigureAwait(false); + if (completedTask != requestTask) + return cancellationResultFactory(); + } + + return (TResult)await requestTask.ConfigureAwait(false); + } + finally + { + ReleaseListener(entry); + } + } + + private void ReleaseListener(RequestEntry entry) + { + var cancelRequest = false; + var disposeCancellationSource = false; + + lock (_sync) + { + entry.ListenerCount--; + + if (entry.ListenerCount == 0) + { + if (!entry.Completed) + { + // Remove the entry before canceling it. A caller arriving + // after this point must create a new physical request + // instead of joining one that is being canceled. + RemoveEntry(entry); + + entry.CancellationInProgress = true; + cancelRequest = true; + } + else if (!entry.Disposed) + { + entry.Disposed = true; + disposeCancellationSource = true; + } + } + } + + if (cancelRequest) + { + try + { + entry.CancellationSource.Cancel(); + } + catch (Exception exception) + { + // Cancellation callbacks are external to the coalescer and + // must not cause an exception to escape the library. + _logger.LogWarning( + exception, + "Error canceling in-flight request {Method} {Url}", + entry.Key.Method, + entry.Key.Url); + } + finally + { + CompleteCancellation(entry); + } + } + else if (disposeCancellationSource) + { + entry.CancellationSource.Dispose(); + } + } + + private void CompleteCancellation(RequestEntry entry) + { + var disposeCancellationSource = false; + + lock (_sync) + { + entry.CancellationInProgress = false; + + if (entry.Completed + && entry.ListenerCount == 0 + && !entry.Disposed) + { + entry.Disposed = true; + disposeCancellationSource = true; + } + } + + if (disposeCancellationSource) + entry.CancellationSource.Dispose(); + } + + private void CompleteRequest(RequestEntry entry) + { + var disposeCancellationSource = false; + + lock (_sync) + { + entry.Completed = true; + + // Only remove this specific entry. If all listeners canceled, + // a replacement request may already have been created for the + // same key. + RemoveEntry(entry); + + if (entry.ListenerCount == 0 + && !entry.CancellationInProgress + && !entry.Disposed) + { + entry.Disposed = true; + disposeCancellationSource = true; + } + } + + if (disposeCancellationSource) + entry.CancellationSource.Dispose(); + } + + /// + /// Remove an entry when it is still the current entry for its key. + /// Must only be called while holding . + /// + private void RemoveEntry(RequestEntry entry) + { + if (_requests.TryGetValue(entry.Key, out var current) + && ReferenceEquals(current, entry)) + { + _requests.Remove(entry.Key); + } + } + } + + /// + /// Key used to identify identical requests for coalescing + /// + /// Request method + /// Request URL + /// Request parameter string + /// Response type + public sealed record RequestCoalescingKey( + string Method, + string Url, + string? Parameters, + Type ResponseType); +} \ No newline at end of file diff --git a/CryptoExchange.Net/Objects/RequestDefinition.cs b/CryptoExchange.Net/Objects/RequestDefinition.cs index 3a782274..dbf02107 100644 --- a/CryptoExchange.Net/Objects/RequestDefinition.cs +++ b/CryptoExchange.Net/Objects/RequestDefinition.cs @@ -101,6 +101,11 @@ namespace CryptoExchange.Net.Objects } } + /// + /// Whether concurrent identical requests should be prevented from sharing the same request/response + /// + public bool PreventRequestCoalescing { get; set; } + /// /// ctor /// diff --git a/CryptoExchange.Net/Objects/RequestDefinitionCache.cs b/CryptoExchange.Net/Objects/RequestDefinitionCache.cs index 0a6477ad..c33a72db 100644 --- a/CryptoExchange.Net/Objects/RequestDefinitionCache.cs +++ b/CryptoExchange.Net/Objects/RequestDefinitionCache.cs @@ -53,6 +53,7 @@ namespace CryptoExchange.Net.Objects /// Try parse the response even when status is not success /// Force trailing `/` /// Optional request identifier override + /// Whether concurrent identical requests should be prevented from sharing the same request/response /// public RequestDefinition GetOrCreate( HttpMethod method, @@ -68,7 +69,8 @@ namespace CryptoExchange.Net.Objects bool? preventCaching = null, bool? tryParseOnNonSuccess = null, bool? forcePathEndWithSlash = null, - string? identifier = null) + string? identifier = null, + bool? preventRequestCoalescing = null) { var identifierToUse = identifier ?? $"{path}{method.Method}{baseAddress}"; if (!_definitions.TryGetValue(identifierToUse, out var def)) @@ -85,6 +87,7 @@ namespace CryptoExchange.Net.Objects PreventCaching = preventCaching ?? false, TryParseOnNonSuccess = tryParseOnNonSuccess ?? false, ForcePathEndWithSlash = forcePathEndWithSlash ?? false, + PreventRequestCoalescing = preventRequestCoalescing ?? false, }; _definitions.TryAdd(identifierToUse, def); } diff --git a/CryptoExchange.Net/Objects/Results/CallResult.cs b/CryptoExchange.Net/Objects/Results/CallResult.cs index b281a7cb..812db18f 100644 --- a/CryptoExchange.Net/Objects/Results/CallResult.cs +++ b/CryptoExchange.Net/Objects/Results/CallResult.cs @@ -97,7 +97,7 @@ public record CallResult : CallResult, ICallResult /// Call result for an exchange /// /// Data type -public record ExchangeCallResult : CallResult +public record ExchangeCallResult : CallResult, IExchangeCallResult { /// /// Exchange name diff --git a/CryptoExchange.Net/Objects/Results/ICallResult.cs b/CryptoExchange.Net/Objects/Results/ICallResult.cs index 8df43ec6..1f83d4a3 100644 --- a/CryptoExchange.Net/Objects/Results/ICallResult.cs +++ b/CryptoExchange.Net/Objects/Results/ICallResult.cs @@ -40,4 +40,23 @@ public interface ICallResult : ICallResult /// The result data, only available when Success = true /// T? Data { get; } +} + +/// +/// Exchange call result +/// +public interface IExchangeCallResult : ICallResult +{ + /// + /// Exchange name + /// + public string Exchange { get; } +} + +/// +/// Exchange call result +/// +/// Result data type +public interface IExchangeCallResult : IExchangeCallResult, ICallResult +{ } \ No newline at end of file diff --git a/CryptoExchange.Net/Objects/Results/IHttpResult.cs b/CryptoExchange.Net/Objects/Results/IHttpResult.cs index f462521a..cdb83451 100644 --- a/CryptoExchange.Net/Objects/Results/IHttpResult.cs +++ b/CryptoExchange.Net/Objects/Results/IHttpResult.cs @@ -10,12 +10,8 @@ namespace CryptoExchange.Net.Objects /// /// HTTP call result /// - public interface IHttpResult : ICallResult + public interface IHttpResult : ICallResult, IExchangeCallResult { - /// - /// Exchange name - /// - string Exchange { get; init; } /// /// The original data returned by the call, only available when `OutputOriginalData` is set to `true` in the client options /// @@ -79,7 +75,7 @@ namespace CryptoExchange.Net.Objects /// HTTP call result /// /// Result data type - public interface IHttpResult : IHttpResult, ICallResult + public interface IHttpResult : IHttpResult, IExchangeCallResult { } } diff --git a/CryptoExchange.Net/Objects/Results/IWebSocketResult.cs b/CryptoExchange.Net/Objects/Results/IWebSocketResult.cs index bdd8d900..b72cce34 100644 --- a/CryptoExchange.Net/Objects/Results/IWebSocketResult.cs +++ b/CryptoExchange.Net/Objects/Results/IWebSocketResult.cs @@ -10,13 +10,8 @@ namespace CryptoExchange.Net.Objects /// /// WebSocket call result /// - public interface IWebSocketResult : ICallResult + public interface IWebSocketResult : ICallResult, IExchangeCallResult { - /// - /// Exchange name - /// - string Exchange { get; init; } - /// /// The request id /// @@ -42,7 +37,7 @@ namespace CryptoExchange.Net.Objects /// WebSocket call result /// /// Data result type - public interface IWebSocketResult : IWebSocketResult, ICallResult + public interface IWebSocketResult : IWebSocketResult, IExchangeCallResult { } diff --git a/CryptoExchange.Net/Objects/Sockets/DataEvent.cs b/CryptoExchange.Net/Objects/Sockets/DataEvent.cs index 15caa7a7..7393e61c 100644 --- a/CryptoExchange.Net/Objects/Sockets/DataEvent.cs +++ b/CryptoExchange.Net/Objects/Sockets/DataEvent.cs @@ -165,7 +165,10 @@ namespace CryptoExchange.Net.Objects.Sockets { StreamId = StreamId, UpdateType = UpdateType, - Symbol = Symbol + Symbol = Symbol, + DataTime = DataTime, + DataTimeLocal = DataTimeLocal, + SequenceNumber = SequenceNumber }; } diff --git a/CryptoExchange.Net/Objects/Sockets/WebSocketParameters.cs b/CryptoExchange.Net/Objects/Sockets/WebSocketParameters.cs index 5ce4c1ab..bce85e6d 100644 --- a/CryptoExchange.Net/Objects/Sockets/WebSocketParameters.cs +++ b/CryptoExchange.Net/Objects/Sockets/WebSocketParameters.cs @@ -1,4 +1,5 @@ -using CryptoExchange.Net.RateLimiting.Interfaces; +using CryptoExchange.Net.RateLimiting; +using CryptoExchange.Net.RateLimiting.Interfaces; using System; using System.Collections.Generic; using System.Text; @@ -64,6 +65,17 @@ namespace CryptoExchange.Net.Objects.Sockets /// public RateLimitingBehaviour RateLimitingBehavior { get; set; } + /// + /// A delegate receiving the request definition and the request weight that can be used to determine whether a request should be admitted or rejected based + /// on the request definition and the current rate limit usage ratio. This allows for custom rate limiting logic to be implemented. + /// + public Func? RateLimitAdmissionCallbackOptions { get; set; } + + /// + /// A delegate for retrieving the current rate limit admission decision for a specific request. This allows for custom rate limiting logic to be implemented. + /// + public Func? RateLimitAdmissionCallbackRequest { get; set; } + /// /// Encoding for sending/receiving data /// diff --git a/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs b/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs index 4ef0ca09..b515f008 100644 --- a/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs @@ -36,6 +36,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards private readonly Dictionary _trackers; private readonly RateLimitWindowType _windowType; private readonly double? _decayRate; + private readonly TimeSpan _safetyMargin; private readonly int? _connectionWeight; private readonly Func _keySelector; private readonly SemaphoreSlim? _sharedGuardSemaphore; @@ -54,6 +55,10 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// The time period for the limit /// public TimeSpan TimeSpan { get; } + /// + /// Additional time to wait after a rate limit window expires to account for latency and timing differences + /// + public TimeSpan SafetyMargin => _safetyMargin; /// /// Whether this guard is shared between multiple gates @@ -68,11 +73,12 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// Limit per period /// Timespan for the period /// Type of rate limit window + /// Additional time to wait after a window expires, or null to use the default margin /// The decay per timespan if windowType is DecayWindowTracker /// The weight of a new connection /// Whether this guard is shared between multiple gates - public RateLimitGuard(Func keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false) - : this(keySelector, new[] { filter }, limit, timeSpan, windowType, decayPerTimeSpan, connectionWeight, shared) + public RateLimitGuard(Func keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false, TimeSpan? safetyMargin = null) + : this(keySelector, new[] { filter }, limit, timeSpan, windowType, decayPerTimeSpan, connectionWeight, shared, safetyMargin) { } @@ -84,10 +90,11 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// Limit per period /// Timespan for the period /// Type of rate limit window + /// Additional time to wait after a window expires, or null to use the default margin /// The decay per timespan if windowType is DecayWindowTracker /// The weight of a new connection /// Whether this guard is shared between multiple gates - public RateLimitGuard(Func keySelector, IEnumerable filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false) + public RateLimitGuard(Func keySelector, IEnumerable filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false, TimeSpan? safetyMargin = null) { _filters = filters; _trackers = new Dictionary(); @@ -97,6 +104,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards SharedGuard = shared; _keySelector = keySelector; _decayRate = decayPerTimeSpan; + _safetyMargin = safetyMargin ?? WindowTrackerHelpers.GetDefaultSafetyMargin(timeSpan); ; _connectionWeight = connectionWeight; if (SharedGuard) @@ -104,7 +112,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards } /// - public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, string? keySuffix) + public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, string? keySuffix, double allowedRateRatio) { foreach (var filter in _filters) { @@ -127,7 +135,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards _trackers.Add(key, tracker); } - var delay = tracker.GetWaitTime(requestWeight); + var delay = tracker.GetWaitTime(requestWeight, allowedRateRatio); if (delay == default) return LimitCheck.NotNeeded(Limit, TimeSpan, tracker.Current); @@ -205,9 +213,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// protected IWindowTracker CreateTracker() { - return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(Limit, TimeSpan) - : _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(Limit, TimeSpan) - : _windowType == RateLimitWindowType.FixedAfterFirst ? new FixedAfterStartWindowTracker(Limit, TimeSpan) : + return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(Limit, TimeSpan, _safetyMargin) + : _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(Limit, TimeSpan, _safetyMargin) + : _windowType == RateLimitWindowType.FixedAfterFirst ? new FixedAfterStartWindowTracker(Limit, TimeSpan, _safetyMargin) : new DecayWindowTracker(Limit, TimeSpan, _decayRate ?? throw new InvalidOperationException("Decay rate not provided")); } } diff --git a/CryptoExchange.Net/RateLimiting/Guards/RetryAfterGuard.cs b/CryptoExchange.Net/RateLimiting/Guards/RetryAfterGuard.cs index c6116813..3160fc07 100644 --- a/CryptoExchange.Net/RateLimiting/Guards/RetryAfterGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Guards/RetryAfterGuard.cs @@ -42,7 +42,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards } /// - public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, string? keySuffix) + public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, string? keySuffix, double allowedRateRatio) { if (type != Type) return LimitCheck.NotApplicable; diff --git a/CryptoExchange.Net/RateLimiting/Guards/SingleLimitGuard.cs b/CryptoExchange.Net/RateLimiting/Guards/SingleLimitGuard.cs index adb60fc3..44ab4d96 100644 --- a/CryptoExchange.Net/RateLimiting/Guards/SingleLimitGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Guards/SingleLimitGuard.cs @@ -24,6 +24,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards private readonly Dictionary _trackers; private readonly RateLimitWindowType _windowType; private readonly double? _decayRate; + private readonly TimeSpan _safetyMargin; private readonly int _limit; private readonly TimeSpan _period; private readonly Func _keySelector; @@ -32,7 +33,12 @@ namespace CryptoExchange.Net.RateLimiting.Guards public string Name => "EndpointLimitGuard"; /// - public string Description => $"Limit requests to endpoint"; + public string Description => _windowType == RateLimitWindowType.Decay ? $"Endpoint limit of {_limit} with a decay rate of {_decayRate}" : $"Limit of {_limit} per {_period}"; + + /// + /// Additional time to wait after a rate limit window expires to account for latency and timing differences + /// + public TimeSpan SafetyMargin => _safetyMargin; /// /// ctor @@ -41,19 +47,21 @@ namespace CryptoExchange.Net.RateLimiting.Guards int limit, TimeSpan period, RateLimitWindowType windowType, - double? decayRate = null, - Func? keySelector = null) + TimeSpan? safetyMargin = null, + Func? keySelector = null, + double? decayRate = null) { _limit = limit; _period = period; _windowType = windowType; _decayRate = decayRate; + _safetyMargin = safetyMargin ?? WindowTrackerHelpers.GetDefaultSafetyMargin(period); _keySelector = keySelector ?? Default; _trackers = new Dictionary(); } /// - public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, string? keySuffix) + public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, string? keySuffix, double allowedRateRatio) { var key = _keySelector(definition, apiKey) + keySuffix; if (!_trackers.TryGetValue(key, out var tracker)) @@ -62,7 +70,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards _trackers.Add(key, tracker); } - var delay = tracker.GetWaitTime(requestWeight); + var delay = tracker.GetWaitTime(requestWeight, allowedRateRatio); if (delay == default) return LimitCheck.NotNeeded(_limit, _period, tracker.Current); @@ -84,8 +92,8 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// protected IWindowTracker CreateTracker() { - return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(_limit, _period) - : _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(_limit, _period) : + return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(_limit, _period, _safetyMargin) + : _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(_limit, _period, _safetyMargin) : new DecayWindowTracker(_limit, _period, _decayRate ?? throw new InvalidOperationException("Decay rate not provided")); } diff --git a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs index e0feafde..5c6155af 100644 --- a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs +++ b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs @@ -53,9 +53,20 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// Request weight /// Behaviour when rate limit is hit /// An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters. - /// Cancelation token + /// The allowed rate ratio + /// Cancellation token /// Error if RateLimitingBehaviour is Fail and rate limit is hit - ValueTask ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, string? keySuffix, CancellationToken ct); + ValueTask ProcessAsync( + ILogger logger, + int itemId, + RateLimitItemType type, + RequestDefinition definition, + string? apiKey, + int requestWeight, + RateLimitingBehaviour behaviour, + string? keySuffix, + double allowedRateRatio, + CancellationToken ct); /// /// Enforces the rate limit as defined in the request definition. When a rate limit is hit will wait for the rate limit to pass if RateLimitingBehaviour is Wait, or return an error if it is set to Fail @@ -69,9 +80,21 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// Behaviour when rate limit is hit /// The weight to apply to the limit guard /// An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters. + /// The allowed rate ratio /// Cancelation token /// Error if RateLimitingBehaviour is Fail and rate limit is hit - ValueTask ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, string? keySuffix, CancellationToken ct); + ValueTask ProcessSingleAsync( + ILogger logger, + int itemId, + IRateLimitGuard guard, + RateLimitItemType type, + RequestDefinition definition, + string? apiKey, + int requestWeight, + RateLimitingBehaviour behaviour, + string? keySuffix, + double allowedRateRatio, + CancellationToken ct); /// /// Reset the limit for the specified parameters diff --git a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGuard.cs b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGuard.cs index ccfec758..b4e8118c 100644 --- a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGuard.cs @@ -25,8 +25,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// The API key /// The request weight /// An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters. + /// The allowed rate ratio /// - LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, string? keySuffix); + LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, string? keySuffix, double allowedRateRatio); /// /// Apply the request to this guard with the specified weight diff --git a/CryptoExchange.Net/RateLimiting/Interfaces/IWindowTracker.cs b/CryptoExchange.Net/RateLimiting/Interfaces/IWindowTracker.cs index a61e6971..b5f01507 100644 --- a/CryptoExchange.Net/RateLimiting/Interfaces/IWindowTracker.cs +++ b/CryptoExchange.Net/RateLimiting/Interfaces/IWindowTracker.cs @@ -22,9 +22,7 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// /// Get the time to wait to fit the weight /// - /// - /// - TimeSpan GetWaitTime(int weight); + TimeSpan GetWaitTime(int weight, double allowedRateRatio); /// /// Register the weight in this window /// diff --git a/CryptoExchange.Net/RateLimiting/RateLimitAdmission.cs b/CryptoExchange.Net/RateLimiting/RateLimitAdmission.cs new file mode 100644 index 00000000..272308d0 --- /dev/null +++ b/CryptoExchange.Net/RateLimiting/RateLimitAdmission.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.RateLimiting +{ + /// + /// Rate limit admission decision result + /// + public record RateLimitAdmission + { + /// + /// Value ratio between 0 and 1 + /// + public double MaxUtilizationRatio { get; } + + private RateLimitAdmission(double maxUtilizationValue) { + if (maxUtilizationValue <= 0 || maxUtilizationValue > 1) + throw new ArgumentOutOfRangeException(nameof(maxUtilizationValue), "Max utilization value must be bigger than 0 and less than or equal to 1"); + + MaxUtilizationRatio = maxUtilizationValue; + } + + /// + /// Only allow the request when below a certain ratio of the rate limit, for example 0.5 means it can use a max of 50% of the rate limit, + /// 1 means it's allowed to use the full rate limit. + /// + /// 0.5 means a max use 50% of the rate limit, 1 means the request is allowed to use the full rate limit + public static RateLimitAdmission WithMaxUtilizationRatio(double value) + => new RateLimitAdmission(value); + } +} diff --git a/CryptoExchange.Net/RateLimiting/RateLimitGate.cs b/CryptoExchange.Net/RateLimiting/RateLimitGate.cs index f7b7ca7f..aa9b99e0 100644 --- a/CryptoExchange.Net/RateLimiting/RateLimitGate.cs +++ b/CryptoExchange.Net/RateLimiting/RateLimitGate.cs @@ -37,14 +37,24 @@ namespace CryptoExchange.Net.RateLimiting } /// - public async ValueTask ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, CancellationToken ct) + public async ValueTask ProcessAsync( + ILogger logger, + int itemId, + RateLimitItemType type, + RequestDefinition definition, + string? apiKey, + int requestWeight, + RateLimitingBehaviour rateLimitingBehaviour, + string? keySuffix, + double allowedRateRatio, + CancellationToken ct) { await _semaphore.WaitAsync(ct).ConfigureAwait(false); bool release = true; _waitingCount++; try { - return await CheckGuardsAsync(_guards, logger, itemId, type, definition, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false); + return await CheckGuardsAsync(_guards, logger, itemId, type, definition, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, allowedRateRatio, ct).ConfigureAwait(false); } catch (TaskCanceledException tce) { @@ -71,6 +81,7 @@ namespace CryptoExchange.Net.RateLimiting int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, + double allowedRateRatio, CancellationToken ct) { await _semaphore.WaitAsync(ct).ConfigureAwait(false); @@ -78,7 +89,7 @@ namespace CryptoExchange.Net.RateLimiting _waitingCount++; try { - return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false); + return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, allowedRateRatio, ct).ConfigureAwait(false); } catch (TaskCanceledException tce) { @@ -94,12 +105,23 @@ namespace CryptoExchange.Net.RateLimiting } } - private async ValueTask CheckGuardsAsync(IEnumerable guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, CancellationToken ct) + private async ValueTask CheckGuardsAsync( + IEnumerable guards, + ILogger logger, + int itemId, + RateLimitItemType type, + RequestDefinition definition, + string? apiKey, + int requestWeight, + RateLimitingBehaviour rateLimitingBehaviour, + string? keySuffix, + double allowedRateRatio, + CancellationToken ct) { foreach (var guard in guards) { // Check if a wait is needed for this guard - var result = guard.Check(type, definition, apiKey, requestWeight, keySuffix); + var result = guard.Check(type, definition, apiKey, requestWeight, keySuffix, allowedRateRatio); if (result.Delay != TimeSpan.Zero && rateLimitingBehaviour == RateLimitingBehaviour.Fail) { // Delay is needed and limit behaviour is to fail the request @@ -109,7 +131,10 @@ namespace CryptoExchange.Net.RateLimiting logger.RateLimitRequestFailed(itemId, definition.Path, guard.Name, guard.Description); RateLimitTriggered?.Invoke(new RateLimitEvent(itemId, _name, guard.Description, definition, result.Current, requestWeight, result.Limit, result.Period, result.Delay, rateLimitingBehaviour)); - return CallResult.Fail(new ClientRateLimitError($"Rate limit check failed on guard {guard.Name}; {guard.Description}")); + var errorMsg = $"Rate limit check failed on guard {guard.Name}; {guard.Description}"; + if (allowedRateRatio < 1) + errorMsg += $" with allowed ratio {allowedRateRatio}"; + return CallResult.Fail(new ClientRateLimitError(errorMsg)); } if (result.Delay != TimeSpan.Zero) @@ -117,7 +142,14 @@ namespace CryptoExchange.Net.RateLimiting // Delay is needed and limit behaviour is to wait for the request to be under the limit _semaphore.Release(); - var description = result.Limit == null ? guard.Description : $"{guard.Description}, Request weight: {requestWeight}, Current: {result.Current}, Limit: {result.Limit}, requests now being limited: {_waitingCount}"; + var description = + result.Limit == null + ? guard.Description + : $"{guard.Description}, Request weight: {requestWeight}," + + $" Current: {result.Current}" + + $", Limit: {result.Limit}" + + (allowedRateRatio < 1 ? $", Allowed ratio: {allowedRateRatio}" : "") + + $", requests now being limited: {_waitingCount}"; if (type == RateLimitItemType.Connection) logger.RateLimitDelayingConnection(itemId, result.Delay, guard.Name, description); else @@ -126,7 +158,7 @@ namespace CryptoExchange.Net.RateLimiting RateLimitTriggered?.Invoke(new RateLimitEvent(itemId, _name, guard.Description, definition, result.Current, requestWeight, result.Limit, result.Period, result.Delay, rateLimitingBehaviour)); await Task.Delay((int)result.Delay.TotalMilliseconds + 1, ct).ConfigureAwait(false); await _semaphore.WaitAsync(ct).ConfigureAwait(false); - return await CheckGuardsAsync(guards, logger, itemId, type, definition, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false); + return await CheckGuardsAsync(guards, logger, itemId, type, definition, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, allowedRateRatio, ct).ConfigureAwait(false); } } diff --git a/CryptoExchange.Net/RateLimiting/Trackers/DecayWindowTracker.cs b/CryptoExchange.Net/RateLimiting/Trackers/DecayWindowTracker.cs index 09218ae4..938d00b5 100644 --- a/CryptoExchange.Net/RateLimiting/Trackers/DecayWindowTracker.cs +++ b/CryptoExchange.Net/RateLimiting/Trackers/DecayWindowTracker.cs @@ -18,9 +18,18 @@ namespace CryptoExchange.Net.RateLimiting.Trackers private int _currentWeight = 0; private DateTime _lastDecrease = DateTime.UtcNow; + private double _decayProgress; - public DecayWindowTracker(int limit, TimeSpan period, double decayRate) + public DecayWindowTracker( + int limit, + TimeSpan period, + double decayRate) { + if (period <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException(nameof(period)); + + if (decayRate <= 0) + throw new ArgumentOutOfRangeException(nameof(decayRate)); Limit = limit; TimePeriod = period; DecreaseRate = decayRate; @@ -31,73 +40,118 @@ namespace CryptoExchange.Net.RateLimiting.Trackers { if (amount == null) { - _lastDecrease = DateTime.UtcNow; - _currentWeight = 0; - } - else - { - _currentWeight = Math.Max(0, _currentWeight - amount.Value); + ResetState(); + return; } + + _currentWeight = Math.Max(0, _currentWeight - amount.Value); + if (_currentWeight == 0) + ResetState(); } /// - public TimeSpan GetWaitTime(int weight) + public TimeSpan GetWaitTime(int weight, double allowedRateRatio) { - // Decrease the counter based on the last update time and decay rate - DecreaseCounter(DateTime.UtcNow); + var now = DateTime.UtcNow; + DecreaseCounter(now); - if (Current + weight > Limit) + if ((Current + weight) / (double)Limit <= allowedRateRatio) + return TimeSpan.Zero; + + if (Current == 0) { - // The weight would cause the rate limit to be passed - if (Current == 0) + if (allowedRateRatio < 1) { - throw new Exception("Request limit reached without any prior request. " + - $"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}"); + throw new Exception( + "Request limit reached max utilization. " + + "This request can never execute with the current rate limiter configuration. " + + $"Request weight: {weight}, RateLimit: {Limit}, " + + $"Request ratio: {(Current + weight) / (double)Limit}, " + + $"AllowedRateRatio: {allowedRateRatio}"); } - // Determine the time to wait before this weight can be applied without going over the rate limit - return DetermineWaitTime(weight); + throw new Exception( + "Request limit reached without any prior request. " + + "This request can never execute with the current rate limiter. " + + $"Request weight: {weight}, RateLimit: {Limit}"); } - // Weight can fit without going over limit - return TimeSpan.Zero; + return DetermineWaitTime( + weight, + allowedRateRatio); } - /// public void ApplyWeight(int weight) { if (_currentWeight == 0) + { _lastDecrease = DateTime.UtcNow; + _decayProgress = 0; + } + _currentWeight += weight; } - /// - /// Decrease the counter based on time passed since last update and the decay rate - /// - /// - protected void DecreaseCounter(DateTime time) + private void DecreaseCounter(DateTime now) { - var dif = (time - _lastDecrease).TotalMilliseconds / TimePeriod.TotalMilliseconds * DecreaseRate; - var decrease = (int)Math.Floor(dif); - if (decrease >= 1) + if (_currentWeight == 0) { - _currentWeight = Math.Max(0, _currentWeight - (int)Math.Floor(dif)); - _lastDecrease = time; + _lastDecrease = now; + _decayProgress = 0; + return; + } + + var elapsed = now - _lastDecrease; + if (elapsed <= TimeSpan.Zero) + return; + + var elapsedDecay = elapsed.Ticks / (double)TimePeriod.Ticks * DecreaseRate; + + var totalDecay = _decayProgress + elapsedDecay; + var completedDecay = (int)Math.Floor(totalDecay); + + _lastDecrease = now; + + if (completedDecay == 0) + { + _decayProgress = totalDecay; + return; + } + + _currentWeight = Math.Max(0, _currentWeight - completedDecay); + if (_currentWeight == 0) + { + // Decay cannot accumulate as credit while the counter is empty. + _decayProgress = 0; + } + else + { + _decayProgress = totalDecay - completedDecay; } } - /// - /// Determine the time to wait before the weight would fit - /// - /// - /// - private TimeSpan DetermineWaitTime(int requestWeight) + private TimeSpan DetermineWaitTime( + int requestWeight, + double allowedRateRatio) { - var weightToRemove = Math.Max(Current - (Limit - requestWeight), 0); - var result = TimeSpan.FromMilliseconds(Math.Ceiling(weightToRemove / DecreaseRate) * TimePeriod.TotalMilliseconds); - if (result < TimeSpan.Zero) - return TimeSpan.Zero; - return result; + var weightToRemove = Current + requestWeight - Limit * allowedRateRatio; + + // The counter is integer-valued, so enough whole weight units + // must decay before the request can be admitted. + var requiredDecay = Math.Ceiling(weightToRemove); + var remainingDecay = Math.Max(0, requiredDecay - _decayProgress); + + var waitTicks = Math.Ceiling(remainingDecay / DecreaseRate * TimePeriod.Ticks); + return waitTicks <= 0 + ? TimeSpan.Zero + : TimeSpan.FromTicks((long)waitTicks); + } + + private void ResetState() + { + _currentWeight = 0; + _decayProgress = 0; + _lastDecrease = DateTime.UtcNow; } } } diff --git a/CryptoExchange.Net/RateLimiting/Trackers/FixedAfterStartWindowTracker.cs b/CryptoExchange.Net/RateLimiting/Trackers/FixedAfterStartWindowTracker.cs index 02e5d74c..524cc23e 100644 --- a/CryptoExchange.Net/RateLimiting/Trackers/FixedAfterStartWindowTracker.cs +++ b/CryptoExchange.Net/RateLimiting/Trackers/FixedAfterStartWindowTracker.cs @@ -17,15 +17,18 @@ namespace CryptoExchange.Net.RateLimiting.Trackers private int _currentWeight = 0; private DateTime? _nextReset; - /// - /// Additional wait time to apply to account for time offset between server and client - /// - private static TimeSpan _fixedWindowBuffer = TimeSpan.FromMilliseconds(1000); + private readonly TimeSpan _safetyMargin; public FixedAfterStartWindowTracker(int limit, TimeSpan period) + : this(limit, period, WindowTrackerHelpers.GetDefaultSafetyMargin(period)) + { + } + + public FixedAfterStartWindowTracker(int limit, TimeSpan period, TimeSpan safetyMargin) { Limit = limit; TimePeriod = period; + _safetyMargin = safetyMargin; _entries = new Queue(); } @@ -53,23 +56,32 @@ namespace CryptoExchange.Net.RateLimiting.Trackers } } - public TimeSpan GetWaitTime(int weight) + public TimeSpan GetWaitTime(int weight, double allowedRateRatio) { // Remove requests no longer in time period from the history var checkTime = DateTime.UtcNow; - if (_nextReset != null && checkTime > _nextReset) + if (_nextReset != null && checkTime >= _nextReset + _safetyMargin) RemoveBefore(_nextReset.Value); if (Current == 0) _nextReset = null; - if (Current + weight > Limit) + if ((Current + weight) / (double)Limit > allowedRateRatio) { // The weight would cause the rate limit to be passed if (Current == 0) { - throw new Exception("Request limit reached without any prior request. " + - $"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}"); + if (allowedRateRatio < 1) + { + throw new Exception("Request limit reached max utilization. " + + $"This request can never execute with the current rate limiter configuration. Request weight: {weight}, RateLimit: {Limit}, " + + $"Request ratio: {(Current + weight) / (double)Limit}, AllowedRateRatio: {allowedRateRatio}"); + } + else + { + throw new Exception("Request limit reached without any prior request. " + + $"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}"); + } } // Determine the time to wait before this weight can be applied without going over the rate limit @@ -121,7 +133,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers private TimeSpan DetermineWaitTime() { var checkTime = DateTime.UtcNow; - var result = (_nextReset!.Value - checkTime) + _fixedWindowBuffer; + var result = _nextReset!.Value + _safetyMargin - checkTime; if (result < TimeSpan.Zero) return TimeSpan.Zero; return result; diff --git a/CryptoExchange.Net/RateLimiting/Trackers/FixedWindowTracker.cs b/CryptoExchange.Net/RateLimiting/Trackers/FixedWindowTracker.cs index a45c68c2..e91caa4e 100644 --- a/CryptoExchange.Net/RateLimiting/Trackers/FixedWindowTracker.cs +++ b/CryptoExchange.Net/RateLimiting/Trackers/FixedWindowTracker.cs @@ -16,15 +16,18 @@ namespace CryptoExchange.Net.RateLimiting.Trackers private readonly Queue _entries; private int _currentWeight = 0; - /// - /// Additional wait time to apply to account for time offset between server and client - /// - private static readonly TimeSpan _fixedWindowBuffer = TimeSpan.FromMilliseconds(1000); + private readonly TimeSpan _safetyMargin; public FixedWindowTracker(int limit, TimeSpan period) + : this(limit, period, WindowTrackerHelpers.GetDefaultSafetyMargin(period)) + { + } + + public FixedWindowTracker(int limit, TimeSpan period, TimeSpan safetyMargin) { Limit = limit; TimePeriod = period; + _safetyMargin = safetyMargin; _entries = new Queue(); } @@ -52,19 +55,32 @@ namespace CryptoExchange.Net.RateLimiting.Trackers } /// - public TimeSpan GetWaitTime(int weight) + public TimeSpan GetWaitTime(int weight, double allowedRateRatio) { // Remove requests no longer in time period from the history var checkTime = DateTime.UtcNow; - RemoveBefore(checkTime.AddTicks(-(checkTime.Ticks % TimePeriod.Ticks))); + var startCurrentWindow = checkTime.AddTicks(-(checkTime.Ticks % TimePeriod.Ticks)); + var resetBoundary = checkTime >= startCurrentWindow + _safetyMargin + ? startCurrentWindow + : startCurrentWindow - TimePeriod; + RemoveBefore(resetBoundary); - if (Current + weight > Limit) + if ((Current + weight) / (double)Limit > allowedRateRatio) { // The weight would cause the rate limit to be passed if (Current == 0) { - throw new Exception("Request limit reached without any prior request. " + - $"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}"); + if (allowedRateRatio < 1) + { + throw new Exception("Request limit reached max utilization. " + + $"This request can never execute with the current rate limiter configuration. Request weight: {weight}, RateLimit: {Limit}, " + + $"Request ratio: {(Current + weight) / (double)Limit}, AllowedRateRatio: {allowedRateRatio}"); + } + else + { + throw new Exception("Request limit reached without any prior request. " + + $"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}"); + } } // Determine the time to wait before this weight can be applied without going over the rate limit @@ -115,8 +131,9 @@ namespace CryptoExchange.Net.RateLimiting.Trackers { var checkTime = DateTime.UtcNow; var startCurrentWindow = checkTime.AddTicks(-(checkTime.Ticks % TimePeriod.Ticks)); - var wait = startCurrentWindow.Add(TimePeriod) - checkTime; - var result = wait.Add(_fixedWindowBuffer); + var result = checkTime < startCurrentWindow + _safetyMargin + ? startCurrentWindow + _safetyMargin - checkTime + : startCurrentWindow + TimePeriod + _safetyMargin - checkTime; if (result < TimeSpan.Zero) return TimeSpan.Zero; return result; diff --git a/CryptoExchange.Net/RateLimiting/Trackers/SlidingWindowTracker.cs b/CryptoExchange.Net/RateLimiting/Trackers/SlidingWindowTracker.cs index c37ac23e..d9605753 100644 --- a/CryptoExchange.Net/RateLimiting/Trackers/SlidingWindowTracker.cs +++ b/CryptoExchange.Net/RateLimiting/Trackers/SlidingWindowTracker.cs @@ -16,15 +16,18 @@ namespace CryptoExchange.Net.RateLimiting.Trackers private readonly List _entries; private int _currentWeight = 0; - /// - /// Additional wait time to apply to account for fluctuating request times - /// - private static readonly TimeSpan _slidingWindowBuffer = TimeSpan.FromMilliseconds(1000); + private readonly TimeSpan _safetyMargin; public SlidingWindowTracker(int limit, TimeSpan period) + : this(limit, period, WindowTrackerHelpers.GetDefaultSafetyMargin(period)) + { + } + + public SlidingWindowTracker(int limit, TimeSpan period, TimeSpan safetyMargin) { Limit = limit; TimePeriod = period; + _safetyMargin = safetyMargin; _entries = new List(); } @@ -53,22 +56,31 @@ namespace CryptoExchange.Net.RateLimiting.Trackers } /// - public TimeSpan GetWaitTime(int weight) + public TimeSpan GetWaitTime(int weight, double allowedRateRatio) { // Remove requests no longer in time period from the history - RemoveBefore(DateTime.UtcNow - TimePeriod); + RemoveBefore(DateTime.UtcNow - TimePeriod - _safetyMargin); - if (Current + weight > Limit) + if ((Current + weight) / (double)Limit > allowedRateRatio) { // The weight would cause the rate limit to be passed if (Current == 0) { - throw new Exception("Request limit reached without any prior request. " + - $"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}"); + if (allowedRateRatio < 1) + { + throw new Exception("Request limit reached max utilization. " + + $"This request can never execute with the current rate limiter configuration. Request weight: {weight}, RateLimit: {Limit}, " + + $"Request ratio: {(Current + weight) / (double)Limit}, AllowedRateRatio: {allowedRateRatio}"); + } + else + { + throw new Exception("Request limit reached without any prior request. " + + $"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}"); + } } // Determine the time to wait before this weight can be applied without going over the rate limit - return DetermineWaitTime(weight); + return DetermineWaitTime(weight, allowedRateRatio); } // Weight can fit without going over limit @@ -90,7 +102,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers { for (var i = 0; i < _entries.Count; i++) { - if (_entries[i].Timestamp < time) + if (_entries[i].Timestamp <= time) { var entry = _entries[i]; _entries.Remove(entry); @@ -108,9 +120,9 @@ namespace CryptoExchange.Net.RateLimiting.Trackers /// Determine the time to wait before the weight would fit /// /// - private TimeSpan DetermineWaitTime(int requestWeight) + private TimeSpan DetermineWaitTime(int requestWeight, double allowedRateRatio) { - var weightToRemove = Math.Max(Current - (Limit - requestWeight), 0); + var weightToRemove = Math.Max(Current + requestWeight - Limit * allowedRateRatio, 0); var removedWeight = 0; for (var i = 0; i < _entries.Count; i++) { @@ -118,7 +130,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers removedWeight += entry.Weight; if (removedWeight >= weightToRemove) { - var result = entry.Timestamp + TimePeriod + _slidingWindowBuffer - DateTime.UtcNow; + var result = entry.Timestamp + TimePeriod + _safetyMargin - DateTime.UtcNow; if (result < TimeSpan.Zero) return TimeSpan.Zero; return result; diff --git a/CryptoExchange.Net/RateLimiting/Trackers/WindowTrackerHelpers.cs b/CryptoExchange.Net/RateLimiting/Trackers/WindowTrackerHelpers.cs new file mode 100644 index 00000000..1c0da20d --- /dev/null +++ b/CryptoExchange.Net/RateLimiting/Trackers/WindowTrackerHelpers.cs @@ -0,0 +1,20 @@ +using System; + +namespace CryptoExchange.Net.RateLimiting.Trackers +{ + internal static class WindowTrackerHelpers + { + private static readonly TimeSpan _minimumSafetyMargin = TimeSpan.FromMilliseconds(10); + private static readonly TimeSpan _maximumSafetyMargin = TimeSpan.FromMilliseconds(250); + + public static TimeSpan GetDefaultSafetyMargin(TimeSpan period) + { + var margin = TimeSpan.FromTicks(period.Ticks / 20); + if (margin < _minimumSafetyMargin) + return _minimumSafetyMargin; + if (margin > _maximumSafetyMargin) + return _maximumSafetyMargin; + return margin; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Assets.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Assets.cs new file mode 100644 index 00000000..b59695f3 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Assets.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Catalog of Shared API capability references. + /// + public static partial class SharedCapabilities + { + /// + /// Asset capabilities. + /// + public static class Assets + { + /// + /// Get asset capability. + /// + public static SharedRestCapability GetAsset { get; } = new(); + /// + /// Get all assets capability. + /// + public static SharedRestCapability GetAllAssets { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Balances.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Balances.cs new file mode 100644 index 00000000..402bfb31 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Balances.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Balance capabilities. + /// + public static class Balances + { + /// + /// Get balances capability. + /// + public static SharedRestCapability GetBalances { get; } = new(); + /// + /// Subscribe to balance updates capability. + /// + public static SharedCapabilityReference SubscribeBalances { get; } = new(); + } + + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Deposits.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Deposits.cs new file mode 100644 index 00000000..24d0a4c3 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Deposits.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Deposit capabilities. + /// + public static class Deposits + { + /// + /// Get deposit addresses capability. + /// + public static SharedRestCapability GetDepositAddresses { get; } = new(); + /// + /// Get deposit history capability. + /// + public static SharedRestCapability GetDepositHistory { get; } = new(); + } + + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Fees.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Fees.cs new file mode 100644 index 00000000..86d3a6f7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Fees.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Fee capabilities. + /// + public static class Fees + { + /// + /// Get fees capability. + /// + public static SharedRestCapability GetFees { get; } = new(); + } + + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Funding.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Funding.cs new file mode 100644 index 00000000..e429c62a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Funding.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Funding capabilities. + /// + public static class Funding + { + /// + /// Get current funding information capability. + /// + public static SharedRestCapability GetFundingInfo { get; } = new(); + /// + /// Get funding rate history capability. + /// + public static SharedRestCapability GetFundingRateHistory { get; } = new(); + /// + /// Get user funding history capability. + /// + public static SharedRestCapability GetUserFundingHistory { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.IndexPrices.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.IndexPrices.cs new file mode 100644 index 00000000..92a8d07a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.IndexPrices.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Index price capabilities. + /// + public static class IndexPrices + { + /// + /// Get an index price capability. + /// + public static SharedRestCapability GetIndexPrice { get; } = new(); + /// + /// Get all index prices capability. + /// + public static SharedRestCapability GetAllIndexPrices { get; } = new(); + /// + /// Subscribe to index price updates capability. + /// + public static SharedCapabilityReference SubscribeIndexPrice { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Klines.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Klines.cs new file mode 100644 index 00000000..a1b010d7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Klines.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Kline capabilities. + /// + public static class Klines + { + /// + /// Get klines capability. + /// + public static SharedRestCapability GetKlines { get; } = new(); + + /// + /// Get mark price klines capability. + /// + public static SharedRestCapability GetMarkPriceKlines { get; } = new(); + + /// + /// Get index price klines capability. + /// + public static SharedRestCapability GetIndexPriceKlines { get; } = new(); + + /// + /// Subscribe to kline updates capability. + /// + public static SharedCapabilityReference SubscribeKlines { get; } = new(); + } + + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Ledger.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Ledger.cs new file mode 100644 index 00000000..63714451 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Ledger.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Ledger capabilities. + /// + public static class Ledger + { + /// + /// Get ledger entries capability. + /// + public static SharedRestCapability GetLedger { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Leverage.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Leverage.cs new file mode 100644 index 00000000..c0d1e0b4 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Leverage.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Leverage capabilities. + /// + public static class Leverage + { + /// + /// Get leverage capability. + /// + public static SharedRestCapability GetLeverage { get; } = new(); + + /// + /// Set leverage capability. + /// + public static SharedRestCapability SetLeverage { get; } = new(); + + /// + /// Get leverage tiers capability. + /// + public static SharedRestCapability GetLeverageTiers { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.MarkPrices.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.MarkPrices.cs new file mode 100644 index 00000000..9afd554a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.MarkPrices.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Mark price capabilities. + /// + public static class MarkPrices + { + /// + /// Get a mark price capability. + /// + public static SharedRestCapability GetMarkPrice { get; } = new(); + + /// + /// Get all mark prices capability. + /// + public static SharedRestCapability GetAllMarkPrices { get; } = new(); + + /// + /// Subscribe to mark price updates capability. + /// + public static SharedCapabilityReference SubscribeMarkPrice { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.OpenInterest.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.OpenInterest.cs new file mode 100644 index 00000000..1f0c0e5a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.OpenInterest.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Open interest capabilities. + /// + public static class OpenInterest + { + /// + /// Get open interest capability. + /// + public static SharedRestCapability GetOpenInterest { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.OrderBooks.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.OrderBooks.cs new file mode 100644 index 00000000..16c3b5d9 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.OrderBooks.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Order book capabilities. + /// + public static class OrderBooks + { + /// + /// Get an order book capability. + /// + public static SharedRestCapability GetOrderBook { get; } = new(); + + /// + /// Get a book ticker capability. + /// + public static SharedRestCapability GetBookTicker { get; } = new(); + + /// + /// Subscribe to order book updates capability. + /// + public static SharedCapabilityReference SubscribeOrderBook { get; } = new(); + + /// + /// Subscribe to incremental order book updates capability. + /// + public static SharedCapabilityReference SubscribeIncrementalOrderBook { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Orders.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Orders.cs new file mode 100644 index 00000000..311196ef --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Orders.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Order capabilities. + /// + public static class Orders + { + /// + /// Spot order capabilities. + /// + public static class Spot + { + /// + /// Place a spot order capability. + /// + public static SharedRestSocketCapability PlaceOrder { get; } = new(); + + /// + /// Place multiple spot orders capability. + /// + public static SharedRestSocketCapability PlaceMultipleOrders { get; } = new(); + + /// + /// Edit a spot order capability. + /// + public static SharedRestSocketCapability EditOrder { get; } = new(); + + /// + /// Edit a spot order by client order id capability. + /// + public static SharedRestSocketCapability EditOrderByClientOrderId { get; } = new(); + + /// + /// Cancel a spot order capability. + /// + public static SharedRestSocketCapability CancelOrder { get; } = new(); + + /// + /// Cancel a spot order by client order id capability. + /// + public static SharedRestSocketCapability CancelOrderByClientOrderId { get; } = new(); + + /// + /// Cancel all spot orders capability. + /// + public static SharedRestSocketCapability CancelAllOrders { get; } = new(); + + /// + /// Cancel all spot orders for a symbol capability. + /// + public static SharedRestSocketCapability CancelAllSymbolOrders { get; } = new(); + + /// + /// Get a spot order capability. + /// + public static SharedRestCapability GetOrder { get; } = new(); + + /// + /// Get a spot order by client order id capability. + /// + public static SharedRestCapability GetOrderByClientOrderId { get; } = new(); + + /// + /// Get open spot orders capability. + /// + public static SharedRestCapability GetOpenOrders { get; } = new(); + + /// + /// Get closed spot orders capability. + /// + public static SharedRestCapability GetClosedOrders { get; } = new(); + + /// + /// Subscribe to spot order updates capability. + /// + public static SharedCapabilityReference SubscribeOrders { get; } = new(); + } + + /// + /// Futures order capabilities. + /// + public static class Futures + { + /// + /// Place a futures order capability. + /// + public static SharedRestSocketCapability PlaceOrder { get; } = new(); + + /// + /// Place multiple futures orders capability. + /// + public static SharedRestSocketCapability PlaceMultipleOrders { get; } = new(); + + /// + /// Edit a futures order capability. + /// + public static SharedRestSocketCapability EditOrder { get; } = new(); + + /// + /// Edit a futures order by client order id capability. + /// + public static SharedRestSocketCapability EditOrderByClientOrderId { get; } = new(); + + /// + /// Cancel a futures order capability. + /// + public static SharedRestSocketCapability CancelOrder { get; } = new(); + + /// + /// Cancel a futures order by client order id capability. + /// + public static SharedRestSocketCapability CancelOrderByClientOrderId { get; } = new(); + + /// + /// Cancel all futures orders capability. + /// + public static SharedRestSocketCapability CancelAllOrders { get; } = new(); + + /// + /// Cancel all futures orders for a symbol capability. + /// + public static SharedRestSocketCapability CancelAllSymbolOrders { get; } = new(); + + /// + /// Get a futures order capability. + /// + public static SharedRestCapability GetOrder { get; } = new(); + + /// + /// Get a futures order by client order id capability. + /// + public static SharedRestCapability GetOrderByClientOrderId { get; } = new(); + + /// + /// Get open futures orders capability. + /// + public static SharedRestCapability GetOpenOrders { get; } = new(); + + /// + /// Get closed futures orders capability. + /// + public static SharedRestCapability GetClosedOrders { get; } = new(); + + /// + /// Subscribe to futures order updates capability. + /// + public static SharedCapabilityReference SubscribeOrders { get; } = new(); + } + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.PositionMode.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.PositionMode.cs new file mode 100644 index 00000000..00c34f9a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.PositionMode.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Position mode capabilities. + /// + public static class PositionMode + { + /// + /// Get position mode capability. + /// + public static SharedRestCapability GetPositionMode { get; } = new(); + + /// + /// Set position mode capability. + /// + public static SharedRestCapability SetPositionMode { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Positions.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Positions.cs new file mode 100644 index 00000000..f6b09579 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Positions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Position capabilities. + /// + public static class Positions + { + /// + /// Get positions capability. + /// + public static SharedRestCapability GetPositions { get; } = new(); + + /// + /// Get position history capability. + /// + public static SharedRestCapability GetPositionHistory { get; } = new(); + + /// + /// Close a full position capability. + /// + public static SharedRestCapability CloseFullPosition { get; } = new(); + + /// + /// Subscribe to position updates capability. + /// + public static SharedCapabilityReference SubscribePositions { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Symbols.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Symbols.cs new file mode 100644 index 00000000..fface02c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Symbols.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Symbol capabilities. + /// + public static class Symbols + { + /// + /// Get spot symbols capability. + /// + public static SharedRestCapability GetSpotSymbols { get; } = new(); + + /// + /// Get futures symbols capability. + /// + public static SharedRestCapability GetFuturesSymbols { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Tickers.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Tickers.cs new file mode 100644 index 00000000..a421d9ed --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Tickers.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Ticker capabilities. + /// + public static class Tickers + { + /// + /// Get a ticker capability. + /// + public static SharedRestCapability GetTicker { get; } = new(); + + /// + /// Get all tickers capability. + /// + public static SharedRestCapability GetAllTickers { get; } = new(); + + /// + /// Subscribe to ticker updates capability. + /// + public static SharedCapabilityReference SubscribeTicker { get; } = new(); + + /// + /// Subscribe to all ticker updates capability. + /// + public static SharedCapabilityReference SubscribeAllTickers { get; } = new(); + + /// + /// Subscribe to book ticker updates capability. + /// + public static SharedCapabilityReference SubscribeBookTicker { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.TpSl.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.TpSl.cs new file mode 100644 index 00000000..60a7542c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.TpSl.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Take profit and stop loss capabilities. + /// + public static class TpSl + { + /// + /// Set futures take profit or stop loss capability. + /// + public static SharedRestCapability SetFuturesTpSl { get; } = new(); + + /// + /// Cancel futures take profit or stop loss capability. + /// + public static SharedRestCapability CancelFuturesTpSl { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Trades.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Trades.cs new file mode 100644 index 00000000..378a58e2 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Trades.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Public trade capabilities. + /// + public static class Trades + { + /// + /// Get recent trades capability. + /// + public static SharedRestCapability GetRecentTrades { get; } = new(); + + /// + /// Get trade history capability. + /// + public static SharedRestCapability GetTradeHistory { get; } = new(); + + /// + /// Subscribe to trade updates capability. + /// + public static SharedCapabilityReference SubscribeTrades { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Transfers.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Transfers.cs new file mode 100644 index 00000000..ac81888b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Transfers.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Transfer capabilities. + /// + public static class Transfers + { + /// + /// Transfer assets capability. + /// + public static SharedRestCapability Transfer { get; } = new(); + + /// + /// Get transfer history capability. + /// + public static SharedRestCapability GetTransferHistory { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.TriggerOrders.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.TriggerOrders.cs new file mode 100644 index 00000000..664c5ba0 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.TriggerOrders.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Trigger order capabilities. + /// + public static class TriggerOrders + { + /// + /// Spot trigger order capabilities. + /// + public static class Spot + { + /// + /// Place a spot trigger order capability. + /// + public static SharedRestCapability PlaceOrder { get; } = new(); + + /// + /// Get a spot trigger order capability. + /// + public static SharedRestCapability GetOrder { get; } = new(); + + /// + /// Cancel a spot trigger order capability. + /// + public static SharedRestCapability CancelOrder { get; } = new(); + } + + /// + /// Futures trigger order capabilities. + /// + public static class Futures + { + /// + /// Place a futures trigger order capability. + /// + public static SharedRestCapability PlaceOrder { get; } = new(); + + /// + /// Get a futures trigger order capability. + /// + public static SharedRestCapability GetOrder { get; } = new(); + + /// + /// Cancel a futures trigger order capability. + /// + public static SharedRestCapability CancelOrder { get; } = new(); + } + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.UserTrades.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.UserTrades.cs new file mode 100644 index 00000000..0e8db5b2 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.UserTrades.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// User trade capabilities. + /// + public static class UserTrades + { + /// + /// Get trades for a spot order capability. + /// + public static SharedRestCapability GetSpotOrderTrades { get; } = new(); + + /// + /// Get spot user trade history capability. + /// + public static SharedRestCapability GetSpotTradeHistory { get; } = new(); + + /// + /// Get trades for a futures order capability. + /// + public static SharedRestCapability GetFuturesOrderTrades { get; } = new(); + + /// + /// Get futures user trade history capability. + /// + public static SharedRestCapability GetFuturesTradeHistory { get; } = new(); + + /// + /// Subscribe to user trade updates capability. + /// + public static SharedCapabilityReference SubscribeUserTrades { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Withdrawals.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Withdrawals.cs new file mode 100644 index 00000000..fbc71882 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilities.Withdrawals.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + public static partial class SharedCapabilities + { + /// + /// Withdrawal capabilities. + /// + public static class Withdrawals + { + /// + /// Withdraw assets capability. + /// + public static SharedRestCapability Withdraw { get; } = new(); + + /// + /// Get withdrawal history capability. + /// + public static SharedRestCapability GetWithdrawalHistory { get; } = new(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilityReference.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilityReference.cs new file mode 100644 index 00000000..e8724dc6 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilityReference.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Strongly typed reference to a Shared API capability. + /// + public class SharedCapabilityReference + where T : ISharedApiCapability + { + /// + /// Capability interface type. + /// + public Type CapabilityType => typeof(T); + + internal SharedCapabilityReference() + { + } + + /// + public override string ToString() => typeof(T).Name; + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilityResolution.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilityResolution.cs new file mode 100644 index 00000000..c51ca506 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedCapabilityResolution.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Capability lookup result + /// + /// Capability type + public class SharedCapabilityResolution + where T : ISharedApiCapability + { + /// + /// The exchange name + /// + public string Exchange => Capability.Exchange; + + /// + /// The transport type + /// + public SharedTransport Transport => Capability.Transport; + + /// + /// The capability + /// + public T Capability { get; } + /// + /// The capability options + /// + public CapabilityOptions Options { get; } + + internal SharedCapabilityResolution( + T capability, + CapabilityOptions options) + { + Capability = capability; + Options = options; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedRestCapability.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedRestCapability.cs new file mode 100644 index 00000000..6a269d04 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedRestCapability.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// References for a capability available through REST. + /// + public class SharedRestCapability + : SharedCapabilityReference + where TCapability : ISharedApiCapability + where TRest : TCapability, ISharedRest + { + /// + /// Transport-agnostic capability. + /// + public SharedCapabilityReference Any { get; } = new(); + + /// + /// REST capability. + /// + public SharedCapabilityReference Rest { get; } = new(); + + internal SharedRestCapability() + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedRestSocketCapability.cs b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedRestSocketCapability.cs new file mode 100644 index 00000000..c50101fe --- /dev/null +++ b/CryptoExchange.Net/SharedApis/CapabilityReferences/SharedRestSocketCapability.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// References for a capability available through REST and WebSocket. + /// + public class SharedRestSocketCapability + : SharedCapabilityReference + where TCapability : ISharedApiCapability + where TRest : TCapability, ISharedRest + where TSocket : TCapability, ISharedSocket + { + /// + /// REST capability. + /// + public SharedCapabilityReference Rest { get; } = new(); + + /// + /// WebSocket capability. + /// + public SharedCapabilityReference Socket { get; } = new(); + + internal SharedRestSocketCapability() + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Enums/SharedLedgerEntryType.cs b/CryptoExchange.Net/SharedApis/Enums/SharedLedgerEntryType.cs new file mode 100644 index 00000000..e97106fe --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Enums/SharedLedgerEntryType.cs @@ -0,0 +1,42 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Ledger entry type + /// + public enum SharedLedgerEntryType + { + /// + /// Trade execution + /// + Trade, + /// + /// Withdrawal + /// + Withdrawal, + /// + /// Deposit + /// + Deposit, + /// + /// Transfer + /// + Transfer, + /// + /// Fee payment + /// + Fee, + /// + /// Funding fee payment + /// + FundingFee, + /// + /// Rebate + /// + Rebate, + + /// + /// Other or unknown + /// + Unknown + } +} diff --git a/CryptoExchange.Net/SharedApis/Enums/SharedOrderBookSubscriptionType.cs b/CryptoExchange.Net/SharedApis/Enums/SharedOrderBookSubscriptionType.cs new file mode 100644 index 00000000..b4c8b4be --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Enums/SharedOrderBookSubscriptionType.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// The type of updates the incremental order book subscription produces + /// + public enum SharedOrderBookSubscriptionType + { + /// + /// Subscription produces an initial snapshot update, followed by incremental change updates + /// + SnapshotThenIncremental, + /// + /// Subscription produces only incremental change updates + /// + OnlyIncremental + } +} diff --git a/CryptoExchange.Net/SharedApis/Enums/SharedTransport.cs b/CryptoExchange.Net/SharedApis/Enums/SharedTransport.cs new file mode 100644 index 00000000..869c58a7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Enums/SharedTransport.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// The type of transport used for the shared API + /// + public enum SharedTransport + { + /// + /// REST transport + /// + Rest, + /// + /// WebSocket transport + /// + Socket + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/ClosePositionRequest.cs b/CryptoExchange.Net/SharedApis/Legacy/ClosePositionRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/ClosePositionRequest.cs rename to CryptoExchange.Net/SharedApis/Legacy/ClosePositionRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Legacy/ISharedClient.cs b/CryptoExchange.Net/SharedApis/Legacy/ISharedClient.cs new file mode 100644 index 00000000..0ad1c902 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Legacy/ISharedClient.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// A shared/common client interface + /// + public interface ISharedClient : ISharedApi + { + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderSocketOptions.cs b/CryptoExchange.Net/SharedApis/Legacy/Options/CancelFuturesOrderSocketOptions.cs similarity index 66% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderSocketOptions.cs rename to CryptoExchange.Net/SharedApis/Legacy/Options/CancelFuturesOrderSocketOptions.cs index 8289f432..5a28f3d0 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderSocketOptions.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Options/CancelFuturesOrderSocketOptions.cs @@ -7,7 +7,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for canceling a futures order /// - public class CancelFuturesOrderSocketOptions : EndpointOptions + public class CancelFuturesOrderSocketOptions : CancelFuturesOrderOptions { /// public override string Description => "Cancel a futures order over a socket connection"; @@ -15,7 +15,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public CancelFuturesOrderSocketOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderManagementSocketClient.CancelFuturesOrderAsync)) + public CancelFuturesOrderSocketOptions(string exchange, bool authenticated) : base(exchange, authenticated) { } } diff --git a/CryptoExchange.Net/SharedApis/Legacy/Options/CancelSpotOrderSocketOptions.cs b/CryptoExchange.Net/SharedApis/Legacy/Options/CancelSpotOrderSocketOptions.cs new file mode 100644 index 00000000..65a39145 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Legacy/Options/CancelSpotOrderSocketOptions.cs @@ -0,0 +1,23 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling a spot order + /// + public class CancelSpotOrderSocketOptions : CancelSpotOrderOptions + { + /// + public override string Description => "Cancel a spot order over a socket connection"; + + /// + /// ctor + /// + public CancelSpotOrderSocketOptions(string exchange, bool authenticated) : base(exchange, authenticated) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Legacy/Options/ClosePositionOptions.cs b/CryptoExchange.Net/SharedApis/Legacy/Options/ClosePositionOptions.cs new file mode 100644 index 00000000..c7ce6864 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Legacy/Options/ClosePositionOptions.cs @@ -0,0 +1,82 @@ +using CryptoExchange.Net.Objects; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for closing position + /// +#pragma warning disable CS0618 // Type or member is obsolete + public class ClosePositionOptions : EndpointOptions +#pragma warning restore CS0618 // Type or member is obsolete + { + private static PropertyInfo[] _requestProperties = typeof(ClosePositionRequest).GetProperties(); + + /// + public override string Description => "Close an open futures position"; + + /// + public override Type CapabilityType => typeof(IFuturesOrderRestClient); + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the position to close", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.PositionMode, "The current position mode of the account", SharedPositionMode.OneWay), + RequestParameterRule.Optional(x => x.PositionSide, "The side of the position to close", SharedPositionSide.Long), + RequestParameterRule.Optional(x => x.MarginMode, "The margin mode of the position", SharedMarginMode.Cross), + RequestParameterRule.Optional(x => x.Quantity, "The quantity of the position to close", 1m), + }; + + /// + /// ctor + /// + public ClosePositionOptions(string exchange, bool authenticated) : base(exchange, nameof(IFuturesOrderRestClient.ClosePositionAsync), true, _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + + /// + /// Validate request + /// + public Error? ValidateRequest(ClosePositionRequest request, IFuturesOrderRestClient client) + { + if (NeedsAuthentication && !client.Authenticated) + return new NoApiCredentialsError(); + + foreach (var param in RequestParameterRules) + { + var property = _requestProperties.Single(x => x.Name == param.Name); + var value = property.GetValue(request); + + if (param.Support == RequestParameterSupport.Required) + { + if (value == null) + { + return ArgumentError.Invalid( + param.Name, + $"Request parameter `{param.Name}` for exchange `{Exchange}` is required and should be provided. Example: {param.ExampleValue}"); + } + } + } + + if (request is SharedSymbolRequest symbolsRequest) + { + if (symbolsRequest.Symbols != null) + { + if (!SupportsMultipleSymbols) + return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Only a single symbol parameter is allowed, multiple symbols are not supported"); + + if (symbolsRequest.Symbols.Length > MaxSymbolCount) + return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Max number of symbols is {MaxSymbolCount} but {symbolsRequest.Symbols.Length} were passed"); + } + + } + + return ValidateRequest(request.ExchangeParameters, request.TradingMode, client.SupportedTradingModes); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Legacy/Options/PlaceFuturesOrderSocketOptions.cs b/CryptoExchange.Net/SharedApis/Legacy/Options/PlaceFuturesOrderSocketOptions.cs new file mode 100644 index 00000000..d9ae66ed --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Legacy/Options/PlaceFuturesOrderSocketOptions.cs @@ -0,0 +1,23 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for placing a new futures order + /// + public class PlaceFuturesOrderSocketOptions : PlaceFuturesOrderOptions + { + /// + public override string Description => "Place a new futures order over a socket connection"; + + /// + /// ctor + /// + public PlaceFuturesOrderSocketOptions(string exchange, bool supportsTpSl) : base(exchange, supportsTpSl) + { + } + + } +} diff --git a/CryptoExchange.Net/SharedApis/Legacy/Options/PlaceSpotOrderSocketOptions.cs b/CryptoExchange.Net/SharedApis/Legacy/Options/PlaceSpotOrderSocketOptions.cs new file mode 100644 index 00000000..e0ce3e7b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Legacy/Options/PlaceSpotOrderSocketOptions.cs @@ -0,0 +1,23 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for placing a new spot order + /// + public class PlaceSpotOrderSocketOptions : PlaceSpotOrderOptions + { + /// + public override string Description => "Place a new spot order over a socket connection"; + + + /// + /// ctor + /// + public PlaceSpotOrderSocketOptions(string exchange) : base(exchange) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFundingRateRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFundingRateRestClient.cs similarity index 87% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFundingRateRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFundingRateRestClient.cs index 7e28a571..55ee06d2 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFundingRateRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFundingRateRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Funding rate request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetFundingRateHistoryOptions GetFundingRateHistoryOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderClientIdRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesOrderClientIdRestClient.cs similarity index 82% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderClientIdRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesOrderClientIdRestClient.cs index 80f0531b..4d32997e 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderClientIdRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesOrderClientIdRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Futures get order by client order id request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetFuturesOrderByClientOrderIdOptions GetFuturesOrderByClientOrderIdOptions { get; } @@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures cancel order by client order id request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelFuturesOrderByClientOrderIdOptions CancelFuturesOrderByClientOrderIdOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesOrderRestClient.cs similarity index 81% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesOrderRestClient.cs index 446cd665..d99d1caf 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesOrderRestClient.cs @@ -38,7 +38,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures place order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
PlaceFuturesOrderOptions PlaceFuturesOrderOptions { get; } @@ -52,7 +52,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures get order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetFuturesOrderOptions GetFuturesOrderOptions { get; } @@ -65,7 +65,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures get open orders request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetOpenFuturesOrdersOptions GetOpenFuturesOrdersOptions { get; } @@ -78,7 +78,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot get closed orders request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetFuturesClosedOrdersOptions GetClosedFuturesOrdersOptions { get; } @@ -93,7 +93,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures get order trades request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetFuturesOrderTradesOptions GetFuturesOrderTradesOptions { get; } @@ -106,10 +106,10 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures user trades request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetFuturesUserTradesOptions GetFuturesUserTradesOptions { get; } + GetFuturesUserTradeHistoryOptions GetFuturesUserTradesOptions { get; } /// /// Get futures user trade records, see for request options and exchange specific required/optional parameters.
/// The result is paginated, if there are more results to be retrieved, the `NextPageRequest` property of the result will contain the pagination request to be used for the next request to continue pagination. @@ -121,7 +121,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures cancel order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelFuturesOrderOptions CancelFuturesOrderOptions { get; } @@ -134,7 +134,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Positions request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetPositionsOptions GetPositionsOptions { get; } @@ -147,7 +147,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Close position order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
ClosePositionOptions ClosePositionOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesSymbolRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesSymbolRestClient.cs similarity index 91% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesSymbolRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesSymbolRestClient.cs index 7329596d..410ab0a9 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesSymbolRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesSymbolRestClient.cs @@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures symbol request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetFuturesSymbolsOptions GetFuturesSymbolsOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTickerRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTickerRestClient.cs similarity index 75% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTickerRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTickerRestClient.cs index 3ea6048f..8e8d2ce8 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTickerRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTickerRestClient.cs @@ -11,10 +11,10 @@ namespace CryptoExchange.Net.SharedApis { /// /// Futures get ticker request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetFuturesTickerOptions GetFuturesTickerOptions { get; } + GetTickerOptions GetFuturesTickerOptions { get; } /// /// Get ticker info for a specific futures symbol, see for request options and exchange specific required/optional parameters.
///
@@ -24,10 +24,10 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures get tickers request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetFuturesTickersOptions GetFuturesTickersOptions { get; } + GetAllTickersOptions GetFuturesTickersOptions { get; } /// /// Get ticker info for all futures symbols, see for request options and exchange specific required/optional parameters.
///
diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTpSlRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTpSlRestClient.cs similarity index 81% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTpSlRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTpSlRestClient.cs index 2069f80b..1e50d680 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTpSlRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTpSlRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Set take profit and/or stop loss options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
SetFuturesTpSlOptions SetFuturesTpSlOptions { get; } @@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Cancel a take profit and/or stop loss options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelFuturesTpSlOptions CancelFuturesTpSlOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTriggerOrderRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTriggerOrderRestClient.cs similarity index 80% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTriggerOrderRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTriggerOrderRestClient.cs index 67c396cc..0d6377f7 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesTriggerOrderRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IFuturesTriggerOrderRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Place spot trigger order options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
PlaceFuturesTriggerOrderOptions PlaceFuturesTriggerOrderOptions { get; } @@ -26,7 +26,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Get trigger order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetFuturesTriggerOrderOptions GetFuturesTriggerOrderOptions { get; } @@ -39,7 +39,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Cancel trigger order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelFuturesTriggerOrderOptions CancelFuturesTriggerOrderOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IIndexPriceKlineRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IIndexPriceKlineRestClient.cs similarity index 87% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IIndexPriceKlineRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IIndexPriceKlineRestClient.cs index bd6c79e2..99a17712 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IIndexPriceKlineRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IIndexPriceKlineRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Index price klines request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetIndexPriceKlinesOptions GetIndexPriceKlinesOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/ILeverageRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/ILeverageRestClient.cs similarity index 82% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/ILeverageRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/ILeverageRestClient.cs index 148ce59c..a8454d04 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/ILeverageRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/ILeverageRestClient.cs @@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Leverage request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetLeverageOptions GetLeverageOptions { get; } @@ -29,7 +29,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Leverage set request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
SetLeverageOptions SetLeverageOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IMarkPriceKlineRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IMarkPriceKlineRestClient.cs similarity index 87% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IMarkPriceKlineRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IMarkPriceKlineRestClient.cs index 761bb167..b3692995 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IMarkPriceKlineRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IMarkPriceKlineRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Mark price klines request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetMarkPriceKlinesOptions GetMarkPriceKlinesOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IOpenInterestRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IOpenInterestRestClient.cs similarity index 83% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IOpenInterestRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IOpenInterestRestClient.cs index 3e9fca24..b60807e9 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IOpenInterestRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IOpenInterestRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Open interest request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetOpenInterestOptions GetOpenInterestOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionHistoryRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IPositionHistoryRestClient.cs similarity index 87% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionHistoryRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IPositionHistoryRestClient.cs index d446ba94..16dfcb54 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionHistoryRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IPositionHistoryRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Position history request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetPositionHistoryOptions GetPositionHistoryOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionModeRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IPositionModeRestClient.cs similarity index 82% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionModeRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IPositionModeRestClient.cs index 2c00cc1e..b03adc8c 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionModeRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Futures/IPositionModeRestClient.cs @@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Position mode request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetPositionModeOptions GetPositionModeOptions { get; } @@ -29,7 +29,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Position mode set request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
SetPositionModeOptions SetPositionModeOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IAssetsRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IAssetsRestClient.cs similarity index 77% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IAssetsRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IAssetsRestClient.cs index 78e6b5ad..9cc93295 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IAssetsRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IAssetsRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Asset request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetAssetOptions GetAssetOptions { get; } @@ -25,10 +25,10 @@ namespace CryptoExchange.Net.SharedApis /// /// Assets request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetAssetsOptions GetAssetsOptions { get; } + GetAllAssetsOptions GetAssetsOptions { get; } /// /// Get info on all assets the exchange supports, see for request options and exchange specific required/optional parameters.
diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IBalanceRestClient.cs similarity index 83% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IBalanceRestClient.cs index 91ca2236..67102a95 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IBalanceRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Balances request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetBalancesOptions GetBalancesOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBookTickerRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IBookTickerRestClient.cs similarity index 83% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IBookTickerRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IBookTickerRestClient.cs index 76bcc5cd..faa38e34 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBookTickerRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IBookTickerRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Book ticker request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetBookTickerOptions GetBookTickerOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IDepositRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IDepositRestClient.cs similarity index 80% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IDepositRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IDepositRestClient.cs index eb2e25d5..d9708451 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IDepositRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IDepositRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Deposit addresses request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetDepositAddressesOptions GetDepositAddressesOptions { get; } @@ -26,10 +26,10 @@ namespace CryptoExchange.Net.SharedApis /// /// Deposits request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetDepositsOptions GetDepositsOptions { get; } + GetDepositHistoryOptions GetDepositsOptions { get; } /// /// Get deposit records, see for request options and exchange specific required/optional parameters.
diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IFeeRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IFeeRestClient.cs similarity index 82% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IFeeRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IFeeRestClient.cs index 2d60ec54..e14d79cf 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IFeeRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IFeeRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Fee request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetFeeOptions GetFeeOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IKlineRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IKlineRestClient.cs similarity index 87% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IKlineRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IKlineRestClient.cs index 5be5c945..2c68df7f 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IKlineRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IKlineRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Kline request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetKlinesOptions GetKlinesOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IOrderBookRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IOrderBookRestClient.cs similarity index 83% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IOrderBookRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IOrderBookRestClient.cs index d4088824..e9b3654b 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IOrderBookRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IOrderBookRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Order book request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetOrderBookOptions GetOrderBookOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IRecentTradeRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IRecentTradeRestClient.cs similarity index 83% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IRecentTradeRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IRecentTradeRestClient.cs index 85cd9c6b..f0da3d9a 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IRecentTradeRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IRecentTradeRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Recent trades request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetRecentTradesOptions GetRecentTradesOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITradeHistoryRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/ITradeHistoryRestClient.cs similarity index 87% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/ITradeHistoryRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/ITradeHistoryRestClient.cs index 2d5ac119..431513a8 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITradeHistoryRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/ITradeHistoryRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Trade history request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetTradeHistoryOptions GetTradeHistoryOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/ITransferRestClient.cs similarity index 82% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/ITransferRestClient.cs index dc9aac6a..89fdeb6b 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/ITransferRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Transfer request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
TransferOptions TransferOptions { get; } @@ -23,4 +23,4 @@ namespace CryptoExchange.Net.SharedApis /// Cancellation token Task> TransferAsync(TransferRequest request, CancellationToken ct = default); } -} +} \ No newline at end of file diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IWithdrawRestClient.cs similarity index 75% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IWithdrawRestClient.cs index f315ef45..ab09059c 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IWithdrawRestClient.cs @@ -1,17 +1,20 @@ using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; using System.Threading; using System.Threading.Tasks; namespace CryptoExchange.Net.SharedApis { /// - /// Client for requesting to withdraw funds from the exchange + /// Client for withdrawing funds /// public interface IWithdrawRestClient : ISharedClient { /// /// Withdraw request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
WithdrawOptions WithdrawOptions { get; } @@ -24,4 +27,4 @@ namespace CryptoExchange.Net.SharedApis /// Task> WithdrawAsync(WithdrawRequest request, CancellationToken ct = default); } -} +} \ No newline at end of file diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawalRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/IWithdrawalRestClient.cs similarity index 83% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawalRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/IWithdrawalRestClient.cs index 7a041156..7c1d26da 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawalRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/IWithdrawalRestClient.cs @@ -11,10 +11,10 @@ namespace CryptoExchange.Net.SharedApis { /// /// Withdrawal records request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetWithdrawalsOptions GetWithdrawalsOptions { get; } + GetWithdrawalHistoryOptions GetWithdrawalsOptions { get; } /// /// Get withdrawal records, see for request options and exchange specific required/optional parameters.
diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderClientIdRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotOrderClientIdRestClient.cs similarity index 82% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderClientIdRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotOrderClientIdRestClient.cs index 89e0be33..702b74d3 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderClientIdRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotOrderClientIdRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Spot get order by client order id request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetSpotOrderByClientOrderIdOptions GetSpotOrderByClientOrderIdOptions { get; } @@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot cancel order by client order id request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelSpotOrderByClientOrderIdOptions CancelSpotOrderByClientOrderIdOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotOrderRestClient.cs similarity index 82% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotOrderRestClient.cs index 47af549b..18fd837b 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotOrderRestClient.cs @@ -38,7 +38,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot place order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
PlaceSpotOrderOptions PlaceSpotOrderOptions { get; } @@ -51,7 +51,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot get order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetSpotOrderOptions GetSpotOrderOptions { get; } @@ -64,7 +64,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot get open orders request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetOpenSpotOrdersOptions GetOpenSpotOrdersOptions { get; } @@ -77,7 +77,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot get closed orders request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetSpotClosedOrdersOptions GetClosedSpotOrdersOptions { get; } @@ -92,7 +92,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot get order trades request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetSpotOrderTradesOptions GetSpotOrderTradesOptions { get; } @@ -106,10 +106,10 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot user trades request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetSpotUserTradesOptions GetSpotUserTradesOptions { get; } + GetSpotUserTradeHistoryOptions GetSpotUserTradesOptions { get; } /// /// Get spot user trade records, see for request options and exchange specific required/optional parameters.
/// The result is paginated, if there are more results to be retrieved, the `NextPageRequest` property of the result will contain the pagination request to be used for the next request to continue pagination. @@ -121,7 +121,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot cancel order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelSpotOrderOptions CancelSpotOrderOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotSymbolRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotSymbolRestClient.cs similarity index 90% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotSymbolRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotSymbolRestClient.cs index 64e16d02..1b8327e8 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotSymbolRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotSymbolRestClient.cs @@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot symbols request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetSpotSymbolsOptions GetSpotSymbolsOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotTickerRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotTickerRestClient.cs similarity index 75% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotTickerRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotTickerRestClient.cs index 8cb30830..f5c9a436 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotTickerRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotTickerRestClient.cs @@ -11,10 +11,10 @@ namespace CryptoExchange.Net.SharedApis { /// /// Spot ticker request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetSpotTickerOptions GetSpotTickerOptions { get; } + GetTickerOptions GetSpotTickerOptions { get; } /// /// Get ticker for a specific spot symbol, see for request options and exchange specific required/optional parameters.
///
@@ -23,10 +23,10 @@ namespace CryptoExchange.Net.SharedApis Task> GetSpotTickerAsync(GetTickerRequest request, CancellationToken ct = default); /// /// Spot tickers request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
- GetSpotTickersOptions GetSpotTickersOptions { get; } + GetAllTickersOptions GetSpotTickersOptions { get; } /// /// Get tickers for all spot symbols, see for request options and exchange specific required/optional parameters.
///
diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotTriggerOrderRestClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotTriggerOrderRestClient.cs similarity index 80% rename from CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotTriggerOrderRestClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotTriggerOrderRestClient.cs index 4d37d796..9bef6bf1 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotTriggerOrderRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Rest/Spot/ISpotTriggerOrderRestClient.cs @@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis { /// /// Place spot trigger order options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
PlaceSpotTriggerOrderOptions PlaceSpotTriggerOrderOptions { get; } @@ -26,7 +26,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Get trigger order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
GetSpotTriggerOrderOptions GetSpotTriggerOrderOptions { get; } @@ -39,7 +39,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Cancel trigger order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelSpotTriggerOrderOptions CancelSpotTriggerOrderOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/Futures/IFuturesOrderManagementSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/Futures/IFuturesOrderManagementSocketClient.cs similarity index 87% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/Futures/IFuturesOrderManagementSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/Futures/IFuturesOrderManagementSocketClient.cs index 50212b79..61df11ea 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Socket/Futures/IFuturesOrderManagementSocketClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Socket/Futures/IFuturesOrderManagementSocketClient.cs @@ -38,7 +38,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures place order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
PlaceFuturesOrderSocketOptions PlaceFuturesOrderOptions { get; } @@ -52,7 +52,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures cancel order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelFuturesOrderSocketOptions CancelFuturesOrderOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/Futures/IFuturesOrderSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/Futures/IFuturesOrderSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/Futures/IFuturesOrderSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/Futures/IFuturesOrderSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/Futures/IPositionSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/Futures/IPositionSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/Futures/IPositionSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/Futures/IPositionSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/IBalanceSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/IBalanceSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/IBalanceSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/IBalanceSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/IBookTickerSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/IBookTickerSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/IBookTickerSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/IBookTickerSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/IKlineSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/IKlineSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/IKlineSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/IKlineSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/IOrderBookSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/IOrderBookSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/IOrderBookSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/IOrderBookSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/ITickerSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/ITickerSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/ITickerSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/ITickerSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/ITickersSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/ITickersSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/ITickersSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/ITickersSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/ITradeSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/ITradeSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/ITradeSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/ITradeSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/IUserTradeSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/IUserTradeSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/IUserTradeSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/IUserTradeSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/Spot/ISpotOrderManagementSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/Spot/ISpotOrderManagementSocketClient.cs similarity index 86% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/Spot/ISpotOrderManagementSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/Spot/ISpotOrderManagementSocketClient.cs index cfa34e94..b205d732 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Socket/Spot/ISpotOrderManagementSocketClient.cs +++ b/CryptoExchange.Net/SharedApis/Legacy/Socket/Spot/ISpotOrderManagementSocketClient.cs @@ -38,7 +38,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot place order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
PlaceSpotOrderSocketOptions PlaceSpotOrderOptions { get; } @@ -51,7 +51,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot cancel order request options.
- /// Use and to check for required and optional parameters for the request.
+ /// Use and to check for required and optional parameters for the request.
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. ///
CancelSpotOrderSocketOptions CancelSpotOrderOptions { get; } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Socket/Spot/ISpotOrderSocketClient.cs b/CryptoExchange.Net/SharedApis/Legacy/Socket/Spot/ISpotOrderSocketClient.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Interfaces/Socket/Spot/ISpotOrderSocketClient.cs rename to CryptoExchange.Net/SharedApis/Legacy/Socket/Spot/ISpotOrderSocketClient.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderByClientOrderIdOptions.cs deleted file mode 100644 index a82a5888..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderByClientOrderIdOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for canceling a futures order by client order id - /// - public class CancelFuturesOrderByClientOrderIdOptions : EndpointOptions - { - /// - public override string Description => "Cancel a futures order by its client order id"; - - /// - /// ctor - /// - public CancelFuturesOrderByClientOrderIdOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderClientIdRestClient.CancelFuturesOrderByClientOrderIdAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderOptions.cs deleted file mode 100644 index 31a06e2e..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesOrderOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for canceling a futures order - /// - public class CancelFuturesOrderOptions : EndpointOptions - { - /// - public override string Description => "Cancel a futures order"; - - /// - /// ctor - /// - public CancelFuturesOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderRestClient.CancelFuturesOrderAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesTpSlOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesTpSlOptions.cs deleted file mode 100644 index 1319e970..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesTpSlOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for canceling a TP/SL - /// - public class CancelFuturesTpSlOptions : EndpointOptions - { - /// - public override string Description => "Cancel a take profit or stop loss order for a futures position"; - - /// - /// ctor - /// - public CancelFuturesTpSlOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesTpSlRestClient.CancelFuturesTpSlAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesTriggerOrderOptions.cs deleted file mode 100644 index b364e5eb..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelFuturesTriggerOrderOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for canceling spot trigger order - /// - public class CancelFuturesTriggerOrderOptions : EndpointOptions - { - /// - public override string Description => "Cancel a futures trigger order"; - - /// - /// ctor - /// - public CancelFuturesTriggerOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesTriggerOrderRestClient.CancelFuturesTriggerOrderAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderByClientOrderIdOptions.cs deleted file mode 100644 index 619dc4c1..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderByClientOrderIdOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for canceling a spot order - /// - public class CancelSpotOrderByClientOrderIdOptions : EndpointOptions - { - /// - public override string Description => "Cancel a spot order by its client order id"; - - /// - /// ctor - /// - public CancelSpotOrderByClientOrderIdOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotOrderClientIdRestClient.CancelSpotOrderByClientOrderIdAsync)) - { - } - - /// - public override Error? ValidateRequest(CancelOrderRequest request, ISpotOrderClientIdRestClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderOptions.cs deleted file mode 100644 index b27747e6..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for canceling a spot order - /// - public class CancelSpotOrderOptions : EndpointOptions - { - /// - public override string Description => "Cancel a spot order"; - - /// - /// ctor - /// - public CancelSpotOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotOrderRestClient.CancelSpotOrderAsync)) - { - } - - /// - public override Error? ValidateRequest(CancelOrderRequest request, ISpotOrderRestClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderSocketOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderSocketOptions.cs deleted file mode 100644 index 4e0a4274..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotOrderSocketOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for canceling a spot order - /// - public class CancelSpotOrderSocketOptions : EndpointOptions - { - /// - public override string Description => "Cancel a spot order over a socket connection"; - - /// - /// ctor - /// - public CancelSpotOrderSocketOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotOrderManagementSocketClient.CancelSpotOrderAsync)) - { - } - - /// - public override Error? ValidateRequest(CancelOrderRequest request, ISpotOrderManagementSocketClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotTriggerOrderOptions.cs deleted file mode 100644 index 3b994556..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/CancelSpotTriggerOrderOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting spot trigger order - /// - public class CancelSpotTriggerOrderOptions : EndpointOptions - { - /// - public override string Description => "Cancel a spot trigger order"; - - /// - /// ctor - /// - public CancelSpotTriggerOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotTriggerOrderRestClient.CancelSpotTriggerOrderAsync)) - { - } - - /// - public override Error? ValidateRequest(CancelOrderRequest request, ISpotTriggerOrderRestClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/ClosePositionOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/ClosePositionOptions.cs deleted file mode 100644 index 1ccb7739..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/ClosePositionOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for closing position - /// - public class ClosePositionOptions : EndpointOptions - { - /// - public override string Description => "Close an open futures position"; - - /// - /// ctor - /// - public ClosePositionOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderRestClient.ClosePositionAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/EndpointOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/EndpointOptions.cs deleted file mode 100644 index 5bb9ef65..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/EndpointOptions.cs +++ /dev/null @@ -1,226 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for an exchange endpoint - /// - public abstract class EndpointOptions - { - /// - /// Required exchange-specific parameters. These can be provided via the `exchangeParameters` property of the request object. - /// - public List RequiredExchangeParameters { get; set; } = new List(); - /// - /// Optional exchange-specific parameters. These can be provided via the `exchangeParameters` property of the request object. - /// - public List OptionalExchangeParameters { get; set; } = new List(); - /// - /// Exchange - /// - public string Exchange { get; set; } - /// - /// Endpoint name - /// - public string EndpointName { get; set; } - /// - /// Exchange specific additional info - /// - public string? RequestNotes { get; set; } - /// - /// Whether the call requires authentication - /// - public bool NeedsAuthentication { get; set; } - /// - /// Whether the call is supported. If false the exchange API does not support this operation. - /// - public bool Supported { get; set; } = true; - /// - /// Description of the endpoint - /// - public abstract string Description { get; } - - /// - /// ctor - /// - public EndpointOptions(string exchange, string endpointName, bool needAuthentication) - { - Exchange = exchange; - EndpointName = endpointName; - NeedsAuthentication = needAuthentication; - } - - /// - /// Validate a request - /// - /// Provided exchange parameters - /// Request trading mode - /// Supported trading modes - /// - public virtual Error? ValidateRequest(ExchangeParameters? exchangeParameters, TradingMode? tradingMode, TradingMode[] supportedTradingModes) - { - if (!Supported) - return ArgumentError.Invalid("Endpoint", $"Endpoint {Exchange} {EndpointName} is not supported by the API"); - - if (tradingMode != null && !supportedTradingModes.Contains(tradingMode.Value)) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{tradingMode} is not supported, supported types: {string.Join(", ", supportedTradingModes)}"); - - foreach (var param in RequiredExchangeParameters) - { - if (param.Names!.All(x => ExchangeParameters.HasValue(exchangeParameters, Exchange, x, param.ValueType) != true)) - { - if (param.Names.Length == 1) - return ArgumentError.Invalid(string.Join("/", param.Names!), $"Exchange parameter `{param.Names[0]}` for exchange `{Exchange}` should be provided. Example: {param.ExampleValue}"); - else - return ArgumentError.Invalid(string.Join("/", param.Names!), $"One of exchange parameters `{string.Join(", ", param.Names!)}` for exchange `{Exchange}` should be provided. Example: {param.ExampleValue}"); - } - } - - return null; - } - } - - /// - /// Options for an exchange endpoint - /// - /// Type of data -#if NET5_0_OR_GREATER - public abstract class EndpointOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TRequest> : EndpointOptions - where TRequest : SharedRequest -#else - public abstract class EndpointOptions : EndpointOptions - where TRequest : SharedRequest -#endif - { - /// - /// ctor - /// - public EndpointOptions(string exchange, bool needsAuthentication, string requestName) : base(exchange, requestName, needsAuthentication) - { - } - } - - /// - /// Options for an exchange endpoint - /// - /// Type of data - /// Type of the client -#if NET5_0_OR_GREATER - public abstract class EndpointOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TRequest, TClient> : EndpointOptions - where TRequest : SharedRequest - where TClient : ISharedClient -#else - public abstract class EndpointOptions : EndpointOptions - where TRequest : SharedRequest - where TClient : ISharedClient -#endif - { - private static PropertyInfo[] _requestProperties = typeof(TRequest).GetProperties(); - - /// - /// Required optional parameters in the request. These can be provided via the `exchangeParameters` property of the request object. - /// - public List RequiredOptionalParameters { get; set; } = new List(); - - /// - /// Whether this accepts multiple symbols (Only applicable to request requiring symbol parameters) - /// - public bool SupportsMultipleSymbols { get; set; } = false; - /// - /// The max number of symbols which can be passed in a call (Only applicable to request requiring symbol parameters) - /// - public int? MaxSymbolCount { get; set; } - - /// - /// ctor - /// - public EndpointOptions(string exchange, bool needsAuthentication, string requestName) : base(exchange, needsAuthentication, requestName) - { - } - - /// - /// Validate a request - /// - /// The request - /// Containing client - /// - public virtual Error? ValidateRequest(TRequest request, TClient client) - { - if (NeedsAuthentication && !client.Authenticated) - return new NoApiCredentialsError(); - - foreach (var param in RequiredOptionalParameters) - { - if (param.Names!.All(x => _requestProperties.Single(p => p.Name == x).GetValue(request, null) == null)) - { - if (param.Names.Length == 1) - return ArgumentError.Invalid(string.Join("/", param.Names!), $"Optional parameter `{param.Names[0]}` for exchange `{Exchange}` should be provided. Example: {param.ExampleValue}"); - else - return ArgumentError.Invalid(string.Join("/", param.Names!), $"One of optional parameters `{string.Join(", ", param.Names!)}` for exchange `{Exchange}` should be provided. Example: {param.ExampleValue}"); - } - } - - if (request is SharedSymbolRequest symbolsRequest) - { - if (symbolsRequest.Symbols != null) - { - if (!SupportsMultipleSymbols) - return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Only a single symbol parameter is allowed, multiple symbols are not supported"); - - if (symbolsRequest.Symbols.Length > MaxSymbolCount) - return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Max number of symbols is {MaxSymbolCount} but {symbolsRequest.Symbols.Length} were passed"); - } - - } - - return ValidateRequest(request.ExchangeParameters, request.TradingMode, client.SupportedTradingModes); - } - - /// - public override string ToString() - { - var sb = new StringBuilder(); - sb.AppendLine($"{EndpointName}"); - if (!Supported) - { - sb.AppendLine(" NOT SUPPORTED"); - return sb.ToString(); - } - - sb.AppendLine($"{Description}"); - if (!string.IsNullOrEmpty(RequestNotes)) - { - sb.Append(" Notes: "); - sb.AppendLine(RequestNotes); - } - if (RequiredOptionalParameters.Any()) - { - sb.AppendLine($" Required optional parameters:"); - foreach(var param in RequiredOptionalParameters) - sb.AppendLine($" {param}"); - } - if (RequiredExchangeParameters.Any()) - { - sb.AppendLine($" Required exchange specific parameters:"); - foreach (var param in RequiredExchangeParameters) - sb.AppendLine($" {param}"); - } - if (OptionalExchangeParameters.Any()) - { - sb.AppendLine($" Optional exchange specific parameters:"); - foreach (var param in OptionalExchangeParameters) - sb.AppendLine($" {param}"); - } - sb.Append(" Needs authentication: "); - sb.AppendLine($"{NeedsAuthentication}"); - return sb.ToString(); - } - } - -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBookTickerOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBookTickerOptions.cs deleted file mode 100644 index 6bec8d04..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBookTickerOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting book ticker - /// - public class GetBookTickerOptions : EndpointOptions - { - /// - public override string Description => "Retrieve the best bid and ask price for a symbol"; - - /// - /// ctor - /// - public GetBookTickerOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IBookTickerRestClient.GetBookTickerAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositAddressesOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositAddressesOptions.cs deleted file mode 100644 index d86393c9..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositAddressesOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting deposit address - /// - public class GetDepositAddressesOptions : EndpointOptions - { - /// - public override string Description => "Retrieve deposit addresses for an asset"; - - /// - /// ctor - /// - public GetDepositAddressesOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IDepositRestClient.GetDepositAddressesAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderByClientOrderIdOptions.cs deleted file mode 100644 index b60930ff..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderByClientOrderIdOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for getting a spot order by client order id - /// - public class GetFuturesOrderByClientOrderIdOptions : EndpointOptions - { - /// - public override string Description => "Retrieve a futures order by its client order id"; - - /// - /// ctor - /// - public GetFuturesOrderByClientOrderIdOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderClientIdRestClient.GetFuturesOrderByClientOrderIdAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderOptions.cs deleted file mode 100644 index c86e310e..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for getting a futures order by id endpoint - /// - public class GetFuturesOrderOptions : EndpointOptions - { - /// - public override string Description => "Retrieve a futures order"; - - /// - /// ctor - /// - public GetFuturesOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderRestClient.GetFuturesOrderAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderTradesOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderTradesOptions.cs deleted file mode 100644 index 4d46e302..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesOrderTradesOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for getting a trades for an order - /// - public class GetFuturesOrderTradesOptions : EndpointOptions - { - /// - public override string Description => "Retrieve trades for a futures order"; - - /// - /// ctor - /// - public GetFuturesOrderTradesOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderRestClient.GetFuturesOrderTradesAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTriggerOrderOptions.cs deleted file mode 100644 index a6cd84bd..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTriggerOrderOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting futures trigger order - /// - public class GetFuturesTriggerOrderOptions : EndpointOptions - { - /// - public override string Description => "Retrieve a futures trigger order"; - - /// - /// ctor - /// - public GetFuturesTriggerOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesTriggerOrderRestClient.GetFuturesTriggerOrderAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetLeverageOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetLeverageOptions.cs deleted file mode 100644 index 9cb92072..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetLeverageOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting asset info - /// - public class GetLeverageOptions : EndpointOptions - { - /// - public override string Description => "Retrieve the current leverage for a futures symbol"; - - /// - /// ctor - /// - public GetLeverageOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ILeverageRestClient.GetLeverageAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenFuturesOrdersOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenFuturesOrdersOptions.cs deleted file mode 100644 index fe138b3c..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenFuturesOrdersOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for getting a futures order by id endpoint - /// - public class GetOpenFuturesOrdersOptions : EndpointOptions - { - /// - public override string Description => "Retrieve open futures orders"; - - /// - /// ctor - /// - public GetOpenFuturesOrdersOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderRestClient.GetOpenFuturesOrdersAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenInterestOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenInterestOptions.cs deleted file mode 100644 index 8d83a884..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenInterestOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting open interest - /// - public class GetOpenInterestOptions : EndpointOptions - { - /// - public override string Description => "Retrieve open interest for a futures symbol"; - - /// - /// ctor - /// - public GetOpenInterestOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IOpenInterestRestClient.GetOpenInterestAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenSpotOrdersOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenSpotOrdersOptions.cs deleted file mode 100644 index ec026cd8..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOpenSpotOrdersOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for getting a spot order by id endpoint - /// - public class GetOpenSpotOrdersOptions : EndpointOptions - { - /// - public override string Description => "Retrieve open spot orders"; - - /// - /// ctor - /// - public GetOpenSpotOrdersOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotOrderRestClient.GetOpenSpotOrdersAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionModeOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionModeOptions.cs deleted file mode 100644 index ae5ae3a8..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionModeOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting current position mode - /// - public class GetPositionModeOptions : EndpointOptions - { - /// - public override string Description => "Retrieve the current futures position mode"; - - /// - /// ctor - /// - public GetPositionModeOptions(string exchange) : base(exchange, true, nameof(IPositionModeRestClient.GetPositionModeAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionsOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionsOptions.cs deleted file mode 100644 index 3a6a31c7..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionsOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting open positions - /// - public class GetPositionsOptions : EndpointOptions - { - /// - public override string Description => "Retrieve open futures positions"; - - /// - /// ctor - /// - public GetPositionsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesOrderRestClient.GetPositionsAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderByClientOrderIdOptions.cs deleted file mode 100644 index 771026c5..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderByClientOrderIdOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for getting a spot order by client order id endpoint - /// - public class GetSpotOrderByClientOrderIdOptions : EndpointOptions - { - /// - public override string Description => "Retrieve a spot order by its client order id"; - - /// - /// ctor - /// - public GetSpotOrderByClientOrderIdOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotOrderClientIdRestClient.GetSpotOrderByClientOrderIdAsync)) - { - } - - /// - public override Error? ValidateRequest(GetOrderRequest request, ISpotOrderClientIdRestClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderOptions.cs deleted file mode 100644 index 0c6b0038..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for getting a spot order by id endpoint - /// - public class GetSpotOrderOptions : EndpointOptions - { - /// - public override string Description => "Retrieve a spot order"; - - /// - /// ctor - /// - public GetSpotOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotOrderRestClient.GetSpotOrderAsync)) - { - } - - /// - public override Error? ValidateRequest(GetOrderRequest request, ISpotOrderRestClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderTradesOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderTradesOptions.cs deleted file mode 100644 index 20cd6a56..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotOrderTradesOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for getting trades for a spot order - /// - public class GetSpotOrderTradesOptions : EndpointOptions - { - /// - public override string Description => "Retrieve trades for a spot order"; - - /// - /// ctor - /// - public GetSpotOrderTradesOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotOrderRestClient.GetSpotOrderTradesAsync)) - { - } - - /// - public override Error? ValidateRequest(GetOrderTradesRequest request, ISpotOrderRestClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTickerOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTickerOptions.cs deleted file mode 100644 index cf0554d2..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTickerOptions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting ticker - /// - public class GetSpotTickerOptions : EndpointOptions - { - /// - public override string Description => "Retrieve price ticker information for a spot symbol"; - - /// - /// Type of ticker calculation - /// - public SharedTickerType TickerType { get; set; } = SharedTickerType.Day24H; - - /// - /// ctor - /// - public GetSpotTickerOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(ISpotTickerRestClient.GetSpotTickerAsync)) - { - TickerType = tickerCalcType ?? SharedTickerType.Day24H; - } - - /// - public override string ToString() - { - var sb = new StringBuilder(base.ToString()); - sb.AppendLine($" Ticker data calculation type: {TickerType}"); - return sb.ToString(); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTickersOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTickersOptions.cs deleted file mode 100644 index 2f291696..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTickersOptions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting tickers - /// - public class GetSpotTickersOptions : EndpointOptions - { - /// - public override string Description => "Retrieve price ticker information for all spot symbols"; - - /// - /// Type of ticker calculation - /// - public SharedTickerType TickerType { get; set; } = SharedTickerType.Day24H; - - /// - /// ctor - /// - public GetSpotTickersOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(ISpotTickerRestClient.GetSpotTickersAsync)) - { - TickerType = tickerCalcType ?? SharedTickerType.Day24H; - } - - /// - public override string ToString() - { - var sb = new StringBuilder(base.ToString()); - sb.AppendLine($" Ticker data calculation type: {TickerType}"); - return sb.ToString(); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTriggerOrderOptions.cs deleted file mode 100644 index 1feb724a..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotTriggerOrderOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting spot trigger order - /// - public class GetSpotTriggerOrderOptions : EndpointOptions - { - /// - public override string Description => "Retrieve a spot trigger order"; - - /// - /// ctor - /// - public GetSpotTriggerOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotTriggerOrderRestClient.GetSpotTriggerOrderAsync)) - { - } - - /// - public override Error? ValidateRequest(GetOrderRequest request, ISpotTriggerOrderRestClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesOrderOptions.cs deleted file mode 100644 index 75451572..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesOrderOptions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Linq; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for placing a new futures order - /// - public class PlaceFuturesOrderOptions : EndpointOptions - { - /// - public override string Description => "Place a new futures order"; - - /// - /// Whether or not the API supports setting take profit / stop loss with the order - /// - public bool SupportsTpSl { get; set; } - - /// - /// ctor - /// - public PlaceFuturesOrderOptions(string exchange, bool supportsTpSl) : base(exchange, true, nameof(IFuturesOrderRestClient.PlaceFuturesOrderAsync)) - { - SupportsTpSl = supportsTpSl; - } - - /// - /// Validate a request - /// - public override Error? ValidateRequest( - PlaceFuturesOrderRequest request, - IFuturesOrderRestClient client - ) - { - if (!SupportsTpSl && (request.StopLossPrice != null || request.TakeProfitPrice != null)) - return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.StopLossPrice) + " / " + nameof(PlaceFuturesOrderRequest.TakeProfitPrice), "Tp/Sl parameters not supported"); - - if (request.OrderType == SharedOrderType.Other) - throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType)); - - if (!client.FuturesSupportedOrderTypes.Contains(request.OrderType)) - return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.OrderType), "Order type not supported"); - - if (request.TimeInForce != null && !client.FuturesSupportedTimeInForce.Contains(request.TimeInForce.Value)) - return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.TimeInForce), "Order time in force not supported"); - - var quantityError = client.FuturesSupportedOrderQuantity.Validate(request.Side, request.OrderType, request.Quantity); - if (quantityError != null) - return quantityError; - - return base.ValidateRequest(request, client); - } - - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesOrderSocketOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesOrderSocketOptions.cs deleted file mode 100644 index 0f249138..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesOrderSocketOptions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Linq; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for placing a new futures order - /// - public class PlaceFuturesOrderSocketOptions : EndpointOptions - { - /// - public override string Description => "Place a new futures order over a socket connection"; - - /// - /// Whether or not the API supports setting take profit / stop loss with the order - /// - public bool SupportsTpSl { get; set; } - - /// - /// ctor - /// - public PlaceFuturesOrderSocketOptions(string exchange, bool supportsTpSl) : base(exchange, true, nameof(IFuturesOrderManagementSocketClient.PlaceFuturesOrderAsync)) - { - SupportsTpSl = supportsTpSl; - } - - /// - /// Validate a request - /// - public override Error? ValidateRequest( - PlaceFuturesOrderRequest request, - IFuturesOrderManagementSocketClient client - ) - { - if (!SupportsTpSl && (request.StopLossPrice != null || request.TakeProfitPrice != null)) - return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.StopLossPrice) + " / " + nameof(PlaceFuturesOrderRequest.TakeProfitPrice), "Tp/Sl parameters not supported"); - - if (request.OrderType == SharedOrderType.Other) - throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType)); - - if (!client.FuturesSupportedOrderTypes.Contains(request.OrderType)) - return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.OrderType), "Order type not supported"); - - if (request.TimeInForce != null && !client.FuturesSupportedTimeInForce.Contains(request.TimeInForce.Value)) - return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.TimeInForce), "Order time in force not supported"); - - var quantityError = client.FuturesSupportedOrderQuantity.Validate(request.Side, request.OrderType, request.Quantity); - if (quantityError != null) - return quantityError; - - return base.ValidateRequest(request, client); - } - - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesTriggerOrderOptions.cs deleted file mode 100644 index 6f5f8bc2..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceFuturesTriggerOrderOptions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using CryptoExchange.Net.Objects; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for placing a new spot trigger order - /// - public class PlaceFuturesTriggerOrderOptions : EndpointOptions - { - /// - public override string Description => "Place a new futures trigger order"; - - /// - /// When true the API holds the funds until the order is triggered or canceled. When false the funds will only be required when the order is triggered and will fail if the funds are not available at that time. - /// - public bool HoldsFunds { get; set; } - - /// - /// ctor - /// - public PlaceFuturesTriggerOrderOptions(string exchange, bool holdsFunds) : base(exchange, true, nameof(IFuturesTriggerOrderRestClient.PlaceFuturesTriggerOrderAsync)) - { - HoldsFunds = holdsFunds; - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotOrderSocketOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotOrderSocketOptions.cs deleted file mode 100644 index 341482b5..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotOrderSocketOptions.cs +++ /dev/null @@ -1,49 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Linq; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for placing a new spot order - /// - public class PlaceSpotOrderSocketOptions : EndpointOptions - { - /// - public override string Description => "Place a new spot order over a socket connection"; - - - /// - /// ctor - /// - public PlaceSpotOrderSocketOptions(string exchange) : base(exchange, true, nameof(ISpotOrderManagementSocketClient.PlaceSpotOrderAsync)) - { - } - - /// - /// Validate a request - /// - public override Error? ValidateRequest( - PlaceSpotOrderRequest request, - ISpotOrderManagementSocketClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - if (request.OrderType == SharedOrderType.Other) - throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType)); - - if (!client.SpotSupportedOrderTypes.Contains(request.OrderType)) - return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.OrderType), "Order type not supported"); - - if (request.TimeInForce != null && !client.SpotSupportedTimeInForce.Contains(request.TimeInForce.Value)) - return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.TimeInForce), "Order time in force not supported"); - - var quantityError = client.SpotSupportedOrderQuantity.Validate(request.Side, request.OrderType, request.Quantity); - if (quantityError != null) - return quantityError; - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotTriggerOrderOptions.cs deleted file mode 100644 index abd48192..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotTriggerOrderOptions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using CryptoExchange.Net.Objects; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for placing a new spot trigger order - /// - public class PlaceSpotTriggerOrderOptions : EndpointOptions - { - /// - public override string Description => "Place a new spot trigger order"; - - /// - /// When true the API holds the funds until the order is triggered or canceled. When false the funds will only be required when the order is triggered and will fail if the funds are not available at that time. - /// - public bool HoldsFunds { get; set; } - - /// - /// ctor - /// - public PlaceSpotTriggerOrderOptions(string exchange, bool holdsFunds) : base(exchange, true, nameof(ISpotTriggerOrderRestClient.PlaceSpotTriggerOrderAsync)) - { - HoldsFunds = holdsFunds; - } - - /// - /// Validate a request - /// - public override Error? ValidateRequest( - PlaceSpotTriggerOrderRequest request, - ISpotOrderRestClient client) - { - if (request.Symbol!.TradingMode != TradingMode.Spot) - return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); - - var quantityError = client.SpotSupportedOrderQuantity.Validate(request.OrderSide, request.OrderPrice == null ? SharedOrderType.Market : SharedOrderType.Limit, request.Quantity); - if (quantityError != null) - return quantityError; - - return base.ValidateRequest(request, client); - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetFuturesTpSlOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetFuturesTpSlOptions.cs deleted file mode 100644 index c6e0eda2..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetFuturesTpSlOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for setting a TP/SL - /// - public class SetFuturesTpSlOptions : EndpointOptions - { - /// - public override string Description => "Set take profit or stop loss values for a futures position"; - - /// - /// ctor - /// - public SetFuturesTpSlOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesTpSlRestClient.SetFuturesTpSlAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetLeverageOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetLeverageOptions.cs deleted file mode 100644 index dd9d6bc8..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetLeverageOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for setting leverage - /// - public class SetLeverageOptions : EndpointOptions - { - /// - public override string Description => "Set the leverage for a futures symbol"; - - /// - /// ctor - /// - public SetLeverageOptions(string exchange) : base(exchange, true, nameof(ILeverageRestClient.SetLeverageAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetPositionModeOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetPositionModeOptions.cs deleted file mode 100644 index 05a064a3..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/SetPositionModeOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for setting position mode - /// - public class SetPositionModeOptions : EndpointOptions - { - /// - public override string Description => "Set the futures position mode"; - - /// - /// ctor - /// - public SetPositionModeOptions(string exchange) : base(exchange, true, nameof(IPositionModeRestClient.SetPositionModeOptions)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/WithdrawOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/WithdrawOptions.cs deleted file mode 100644 index c8a3f403..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/WithdrawOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for requesting a withdrawal - /// - public class WithdrawOptions : EndpointOptions - { - /// - public override string Description => "Withdraw an asset"; - - /// - /// ctor - /// - public WithdrawOptions(string exchange) : base(exchange, true, nameof(IWithdrawRestClient.WithdrawAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/ParameterDescription.cs b/CryptoExchange.Net/SharedApis/Models/Options/ParameterDescription.cs deleted file mode 100644 index 317ff86b..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/ParameterDescription.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Linq; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Parameter description - /// - public class ParameterDescription - { - /// - /// Possible names for the parameter. One of these names can be used to provide the parameter in the ExchangeParameters of the request. - /// - public string[] Names { get; set; } - /// - /// Type of the value - /// - public Type ValueType { get; set; } - /// - /// Parameter description - /// - public string Description { get; set; } - /// - /// Example value - /// - public object ExampleValue { get; set; } - - /// - /// ctor - /// - public ParameterDescription(string parameterName, Type valueType, string description, object exampleValue) - { - Names = [parameterName]; - ValueType = valueType; - Description = description; - ExampleValue = exampleValue; - } - - /// - /// ctor - /// - public ParameterDescription(string[] parameterNames, Type valueType, string description, object exampleValue) - { - Names = parameterNames; - ValueType = valueType; - Description = description; - ExampleValue = exampleValue; - } - - /// - public override string ToString() - { - return $"[{ValueType.Name}] {string.Join(" / ", Names.Select(x => $"\"{x}\""))}: {Description}"; - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeBookTickerOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeBookTickerOptions.cs deleted file mode 100644 index fab66bd8..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeBookTickerOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Linq; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for subscribing to book ticker updates - /// - public class SubscribeBookTickerOptions : EndpointOptions - { - /// - public override string Description => "Subscribe to best bid and ask price updates"; - - /// - /// ctor - /// - public SubscribeBookTickerOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(IBookTickerSocketClient.SubscribeToBookTickerUpdatesAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeFuturesOrderOptions.cs deleted file mode 100644 index 309680a1..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeFuturesOrderOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Linq; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for subscribing to order updates - /// - public class SubscribeFuturesOrderOptions : EndpointOptions - { - /// - public override string Description => "Subscribe to futures order updates"; - - /// - /// ctor - /// - public SubscribeFuturesOrderOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(IFuturesOrderSocketClient.SubscribeToFuturesOrderUpdatesAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTradeOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTradeOptions.cs deleted file mode 100644 index 82a8ea39..00000000 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTradeOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CryptoExchange.Net.Objects; -using System; -using System.Linq; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Options for subscribing to trade updates - /// - public class SubscribeTradeOptions : EndpointOptions - { - /// - public override string Description => "Subscribe to public trade updates for a symbol"; - - /// - /// ctor - /// - public SubscribeTradeOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ITradeSocketClient.SubscribeToTradeUpdatesAsync)) - { - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Models/SharedApiClientInfo.cs b/CryptoExchange.Net/SharedApis/Models/SharedApiClientInfo.cs new file mode 100644 index 00000000..ddbbcf08 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedApiClientInfo.cs @@ -0,0 +1,49 @@ +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Discovery information for a Shared API client. + /// + public class SharedApiClientInfo + { + /// + /// Preferred transport when multiple implementations of a capability are available. + /// + public SharedTransport PreferredTransport { get; } + + /// + /// Shared APIs available on the client. + /// + public SharedClientInfo[] SharedApis { get; } + + internal SharedApiClientInfo( + SharedTransport preferredTransport, + SharedClientInfo[] sharedApis) + { + PreferredTransport = preferredTransport; + SharedApis = sharedApis; + } + + /// + public override string ToString() => ToString(false); + + /// + /// Create a string representation of the available Shared APIs. + /// + /// Whether to include detailed capability information. + public string ToString(bool detailed) + { + var builder = new StringBuilder(); + builder.AppendLine($"Preferred transport: {PreferredTransport}"); + + foreach (var sharedApi in SharedApis) + { + builder.AppendLine(); + builder.Append(sharedApi.ToString(detailed)); + } + + return builder.ToString(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedAsset.cs b/CryptoExchange.Net/SharedApis/Models/SharedAsset.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedAsset.cs rename to CryptoExchange.Net/SharedApis/Models/SharedAsset.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs b/CryptoExchange.Net/SharedApis/Models/SharedBalance.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs rename to CryptoExchange.Net/SharedApis/Models/SharedBalance.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedBookTicker.cs b/CryptoExchange.Net/SharedApis/Models/SharedBookTicker.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedBookTicker.cs rename to CryptoExchange.Net/SharedApis/Models/SharedBookTicker.cs diff --git a/CryptoExchange.Net/SharedApis/SharedClientInfo.cs b/CryptoExchange.Net/SharedApis/Models/SharedClientInfo.cs similarity index 62% rename from CryptoExchange.Net/SharedApis/SharedClientInfo.cs rename to CryptoExchange.Net/SharedApis/Models/SharedClientInfo.cs index a172f345..a00dd347 100644 --- a/CryptoExchange.Net/SharedApis/SharedClientInfo.cs +++ b/CryptoExchange.Net/SharedApis/Models/SharedClientInfo.cs @@ -12,6 +12,8 @@ namespace CryptoExchange.Net.SharedApis ///
public class SharedClientInfo { + private CapabilityOptions[] _capabilities = []; + /// /// Exchange name /// @@ -33,9 +35,35 @@ namespace CryptoExchange.Net.SharedApis ///
public CentralizationType CentralizationType { get; set; } /// - /// Endpoint/subscription info + /// The underlying transport type used by this client /// - public EndpointOptions[] Features { get; init; } = []; + public SharedTransport Transport { get; init; } + /// + /// Whether API credentials have been configured for this client. Does not check whether the credentials are valid. + /// + public bool Authenticated { get; init; } + /// + /// Use Capabilities instead + /// +#pragma warning disable CS0618 // Type or member is obsolete + public EndpointOptions[] Features + { + get => _capabilities + .OfType() + .ToArray(); + + init => _capabilities = value; + } +#pragma warning restore CS0618 // Type or member is obsolete + + /// + /// Client capabilities + /// + public CapabilityOptions[] Capabilities + { + get => _capabilities; + init => _capabilities = value; + } /// /// Create a string representation for this client @@ -54,18 +82,20 @@ namespace CryptoExchange.Net.SharedApis sb.AppendLine($"Supported environments: {string.Join(", ", SupportedEnvironments)}"); sb.AppendLine($"Supported trading modes: {string.Join(", ", SupportedTradingModes)}"); sb.AppendLine($"Centralization type: {CentralizationType}"); - sb.AppendLine($"Features:"); - foreach (var feature in Features) + sb.AppendLine($"Transport: {Transport}"); + sb.AppendLine($"Authenticated: {Authenticated}"); + sb.AppendLine($"Capabilities:"); + foreach (var capability in Capabilities) { if (detailed) { - var stringRep = feature.ToString(); + var stringRep = capability.ToString(); foreach(var line in stringRep!.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) sb.AppendLine($" {line}"); } else { - sb.AppendLine($" {feature.EndpointName}"); + sb.AppendLine($" {capability.OperationName}"); } sb.AppendLine(); } diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedDeposit.cs b/CryptoExchange.Net/SharedApis/Models/SharedDeposit.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedDeposit.cs rename to CryptoExchange.Net/SharedApis/Models/SharedDeposit.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedDepositAddress.cs b/CryptoExchange.Net/SharedApis/Models/SharedDepositAddress.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedDepositAddress.cs rename to CryptoExchange.Net/SharedApis/Models/SharedDepositAddress.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFee.cs b/CryptoExchange.Net/SharedApis/Models/SharedFee.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedFee.cs rename to CryptoExchange.Net/SharedApis/Models/SharedFee.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedFundingFee.cs b/CryptoExchange.Net/SharedApis/Models/SharedFundingFee.cs new file mode 100644 index 00000000..d963b7e2 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedFundingFee.cs @@ -0,0 +1,51 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Funding fee payment + /// + [DebuggerDisplay("[{Timestamp}] {Symbol,nq} {Fee} {Asset,nq}")] + public record SharedFundingFee + { + /// + /// Id + /// + public string? Id { get; set; } + + /// + /// The symbol + /// + public string Symbol { get; set; } + + /// + /// The asset the funding fee was paid in + /// + public string? Asset { get; set; } + + /// + /// The funding fee paid + /// + public decimal Fee { get; set; } + /// + /// Timestamp + /// + public DateTime Timestamp { get; set; } + + /// + /// The side of the position + /// + public SharedPositionSide? Side { get; set; } + + /// + /// ctor + /// + public SharedFundingFee(string symbol, decimal fee, DateTime timestamp) + { + Symbol = symbol; + Fee = fee; + Timestamp = timestamp; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/SharedFundingInfo.cs b/CryptoExchange.Net/SharedApis/Models/SharedFundingInfo.cs new file mode 100644 index 00000000..9798fe1d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedFundingInfo.cs @@ -0,0 +1,34 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Funding info + /// + public record SharedFundingInfo + { + /// + /// The current funding rate + /// + public decimal FundingRate { get; set; } + /// + /// Next funding timestamp + /// + public DateTime? NextFundingTime { get; set; } + /// + /// Funding interval in hours + /// + public int? FundingInterval { get; set; } + + /// + /// ctor + /// + public SharedFundingInfo(decimal fundingRate, DateTime? nextFundingTime, int? fundingInterval) + { + FundingRate = fundingRate; + NextFundingTime = nextFundingTime; + FundingInterval = fundingInterval; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFundingRate.cs b/CryptoExchange.Net/SharedApis/Models/SharedFundingRate.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedFundingRate.cs rename to CryptoExchange.Net/SharedApis/Models/SharedFundingRate.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesKline.cs b/CryptoExchange.Net/SharedApis/Models/SharedFuturesKline.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesKline.cs rename to CryptoExchange.Net/SharedApis/Models/SharedFuturesKline.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesOrder.cs b/CryptoExchange.Net/SharedApis/Models/SharedFuturesOrder.cs similarity index 89% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesOrder.cs rename to CryptoExchange.Net/SharedApis/Models/SharedFuturesOrder.cs index 49daa0e5..84bd8e23 100644 --- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesOrder.cs +++ b/CryptoExchange.Net/SharedApis/Models/SharedFuturesOrder.cs @@ -73,10 +73,12 @@ namespace CryptoExchange.Net.SharedApis /// /// Asset the fee is in /// + [Obsolete("FeeAsset on order level is deprecated and will be removed in a futures version, use FeeAsset on trade level instead")] public string? FeeAsset { get; set; } /// /// Fee paid /// + [Obsolete("Fee on order level is deprecated and will be removed in a futures version, use Fee on trade level instead")] public decimal? Fee { get; set; } /// /// Leverage @@ -94,6 +96,8 @@ namespace CryptoExchange.Net.SharedApis /// /// Last trade info, only available for websocket order updates if the API provides this data in the update /// + // When V1 Shared API's is deprecated, this property should be marked obsolete as this model will only be used for non-websocket updates + //[Obsolete("Use SharedFuturesOrderUpdate.LastTrade instead, LastTrade is never filled on non websocket updates")] public SharedUserTrade? LastTrade { get; set; } /// diff --git a/CryptoExchange.Net/SharedApis/Models/SharedFuturesOrderUpdate.cs b/CryptoExchange.Net/SharedApis/Models/SharedFuturesOrderUpdate.cs new file mode 100644 index 00000000..05527ce7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedFuturesOrderUpdate.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Futures order info + /// + [DebuggerDisplay("{DebugView,nq}")] + public record SharedFuturesOrderUpdate : SharedFuturesOrder + { + /// + /// The info on the executed trade for this update + /// + public new SharedUserTrade? LastTrade + { +#pragma warning disable CS0618 // Type or member is obsolete + get => base.LastTrade; + set => base.LastTrade = value; +#pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// ctor + /// + public SharedFuturesOrderUpdate( + SharedSymbol? sharedSymbol, + string symbol, + string orderId, + SharedOrderType orderType, + SharedOrderSide orderSide, + SharedOrderStatus orderStatus, + DateTime? createTime) + : base(sharedSymbol, symbol, orderId, orderType, orderSide, orderStatus, createTime) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesSymbol.cs b/CryptoExchange.Net/SharedApis/Models/SharedFuturesSymbol.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesSymbol.cs rename to CryptoExchange.Net/SharedApis/Models/SharedFuturesSymbol.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedFuturesTicker.cs b/CryptoExchange.Net/SharedApis/Models/SharedFuturesTicker.cs new file mode 100644 index 00000000..c7bacbf8 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedFuturesTicker.cs @@ -0,0 +1,44 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Futures ticker info + /// + [DebuggerDisplay("{Symbol,nq} High: {HighPrice}, Low: {LowPrice}, Last: {LastPrice}, Change: {ChangePercentage}%")] + public record SharedFuturesTicker: SharedTicker + { + /// + /// Current mark price + /// + public decimal? MarkPrice { get; set; } + /// + /// Current index price + /// + public decimal? IndexPrice { get; set; } + /// + /// Current funding rate + /// + public decimal? FundingRate { get; set; } + /// + /// Next funding time + /// + public DateTime? NextFundingTime { get; set; } + + /// + /// ctor + /// + public SharedFuturesTicker( + SharedSymbol? sharedSymbol, + string symbol, + decimal? lastPrice, + decimal? highPrice, + decimal? lowPrice, + SharedOrderQuantity volumes, + decimal? changePercentage) + :base(sharedSymbol, symbol, lastPrice, highPrice, lowPrice, volumes, changePercentage) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesTriggerOrder.cs b/CryptoExchange.Net/SharedApis/Models/SharedFuturesTriggerOrder.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesTriggerOrder.cs rename to CryptoExchange.Net/SharedApis/Models/SharedFuturesTriggerOrder.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedId.cs b/CryptoExchange.Net/SharedApis/Models/SharedId.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedId.cs rename to CryptoExchange.Net/SharedApis/Models/SharedId.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedIndexPrice.cs b/CryptoExchange.Net/SharedApis/Models/SharedIndexPrice.cs new file mode 100644 index 00000000..157b5415 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedIndexPrice.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Index price + /// + [DebuggerDisplay("{Symbol,nq}: {IndexPrice}")] + public record SharedIndexPrice : SharedSymbolModel + { + /// + /// Current index price + /// + public decimal IndexPrice { get; set; } + + /// + /// ctor + /// + public SharedIndexPrice(SharedSymbol? sharedSymbol, string symbol, decimal indexPrice) + : base(sharedSymbol, symbol) + { + IndexPrice = indexPrice; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedKline.cs b/CryptoExchange.Net/SharedApis/Models/SharedKline.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedKline.cs rename to CryptoExchange.Net/SharedApis/Models/SharedKline.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedLedgerEntry.cs b/CryptoExchange.Net/SharedApis/Models/SharedLedgerEntry.cs new file mode 100644 index 00000000..5fabd44e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedLedgerEntry.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Ledger entry + /// + [DebuggerDisplay("[{Timestamp}] {DeltaQuantity} {Asset,nq}")] + public record SharedLedgerEntry + { + /// + /// The id of the entry + /// + public string? Id { get; set; } + /// + /// The id of the relation that changed the balance, for example a trade id or transfer id + /// + public string? RelationId { get; set; } + /// + /// The asset + /// + public string Asset { get; set; } + /// + /// The adjustment quantity, positive means the balance increased, negative means the balance decreased + /// + public decimal DeltaQuantity { get; set; } + /// + /// Timestamp of the action + /// + public DateTime Timestamp { get; set; } + /// + /// The type of the entry + /// + public SharedLedgerEntryType Type { get; set; } + /// + /// The type of the entry as string, can be used for entry types not recognized by the Shared API + /// + public string TypeString { get; set; } + + /// + /// ctor + /// + public SharedLedgerEntry(string asset, decimal deltaQuantity, SharedLedgerEntryType type, string typeString, DateTime timestamp) + { + Asset = asset; + DeltaQuantity = deltaQuantity; + Type = type; + TypeString = typeString; + Timestamp = timestamp; + } + } +} \ No newline at end of file diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedLeverage.cs b/CryptoExchange.Net/SharedApis/Models/SharedLeverage.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedLeverage.cs rename to CryptoExchange.Net/SharedApis/Models/SharedLeverage.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedLeverageTier.cs b/CryptoExchange.Net/SharedApis/Models/SharedLeverageTier.cs new file mode 100644 index 00000000..50ef325a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedLeverageTier.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Leverage tier + /// + [DebuggerDisplay("[{Index}] {MinNotional} - {MaxNotional}")] + public record SharedLeverageTier : SharedSymbolModel + { + /// + /// Tier index + /// + public int Index { get; set; } + /// + /// The asset the notional value is in + /// + public string? Asset { get; set; } + /// + /// Min notional value for activation of this tier + /// + public decimal MinNotional { get; set; } + /// + /// Max notional value for this tier, null if no limit + /// + public decimal? MaxNotional { get; set; } + /// + /// Maintenance margin rate for this tier + /// + public decimal? MaintenanceMarginRate { get; set; } + /// + /// Max leverage for this tier + /// + public decimal? MaxLeverage { get; set; } + + /// + /// ctor + /// + public SharedLeverageTier(SharedSymbol? sharedSymbol, string symbol, int index, string? asset, decimal minNotional, decimal? maxNotional, decimal? maintenanceMarginRate, decimal? maxLeverage) + : base(sharedSymbol, symbol) + { + Index = index; + Asset = asset; + MinNotional = minNotional; + MaxNotional = maxNotional; + MaintenanceMarginRate = maintenanceMarginRate; + MaxLeverage = maxLeverage; + } + + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/SharedMarkPrice.cs b/CryptoExchange.Net/SharedApis/Models/SharedMarkPrice.cs new file mode 100644 index 00000000..999f7bf8 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedMarkPrice.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Mark price + /// + [DebuggerDisplay("{Symbol,nq}: {MarkPrice}")] + public record SharedMarkPrice : SharedSymbolModel + { + /// + /// Current mark price + /// + public decimal MarkPrice { get; set; } + + /// + /// ctor + /// + public SharedMarkPrice(SharedSymbol? sharedSymbol, string symbol, decimal markPrice) + : base(sharedSymbol, symbol) + { + MarkPrice = markPrice; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedOpenInterest.cs b/CryptoExchange.Net/SharedApis/Models/SharedOpenInterest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedOpenInterest.cs rename to CryptoExchange.Net/SharedApis/Models/SharedOpenInterest.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedOrderBook.cs b/CryptoExchange.Net/SharedApis/Models/SharedOrderBook.cs similarity index 75% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedOrderBook.cs rename to CryptoExchange.Net/SharedApis/Models/SharedOrderBook.cs index 37e517b2..a631aa32 100644 --- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedOrderBook.cs +++ b/CryptoExchange.Net/SharedApis/Models/SharedOrderBook.cs @@ -12,6 +12,10 @@ namespace CryptoExchange.Net.SharedApis /// public SharedQuantityType QuantityType { get; set; } /// + /// The sequence number this snapshot or update is at + /// + public long? SequenceNumber { get; set; } + /// /// Asks list /// public ISymbolOrderBookEntry[] Asks { get; set; } @@ -23,9 +27,10 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public SharedOrderBook(SharedQuantityType quantityType, ISymbolOrderBookEntry[] asks, ISymbolOrderBookEntry[] bids) + public SharedOrderBook(SharedQuantityType quantityType, long? sequenceNumber, ISymbolOrderBookEntry[] asks, ISymbolOrderBookEntry[] bids) { QuantityType = quantityType; + SequenceNumber = sequenceNumber; Asks = asks; Bids = bids; } diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedPosition.cs b/CryptoExchange.Net/SharedApis/Models/SharedPosition.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedPosition.cs rename to CryptoExchange.Net/SharedApis/Models/SharedPosition.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedPositionHistory.cs b/CryptoExchange.Net/SharedApis/Models/SharedPositionHistory.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedPositionHistory.cs rename to CryptoExchange.Net/SharedApis/Models/SharedPositionHistory.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedPositionModeResult.cs b/CryptoExchange.Net/SharedApis/Models/SharedPositionModeResult.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedPositionModeResult.cs rename to CryptoExchange.Net/SharedApis/Models/SharedPositionModeResult.cs diff --git a/CryptoExchange.Net/SharedApis/SharedQuantity.cs b/CryptoExchange.Net/SharedApis/Models/SharedQuantity.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/SharedQuantity.cs rename to CryptoExchange.Net/SharedApis/Models/SharedQuantity.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotOrder.cs b/CryptoExchange.Net/SharedApis/Models/SharedSpotOrder.cs similarity index 85% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotOrder.cs rename to CryptoExchange.Net/SharedApis/Models/SharedSpotOrder.cs index 0535922f..8fb8c1cc 100644 --- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotOrder.cs +++ b/CryptoExchange.Net/SharedApis/Models/SharedSpotOrder.cs @@ -65,10 +65,12 @@ namespace CryptoExchange.Net.SharedApis /// /// Asset the fee is in /// + [Obsolete("FeeAsset on order level is deprecated and will be removed in a futures version, use FeeAsset on trade level instead")] public string? FeeAsset { get; set; } /// /// Fee paid for the order /// + [Obsolete("Fee on order level is deprecated and will be removed in a futures version, use Fee on trade level instead")] public decimal? Fee { get; set; } /// /// Timestamp the order was created @@ -79,8 +81,10 @@ namespace CryptoExchange.Net.SharedApis /// public DateTime? UpdateTime { get; set; } /// - /// Last trade info, only available for websocket order updates if the API provides this data in the update + /// Only available for order updates, contains the info on the executed trade /// + // When V1 Shared API's is deprecated, this property should be marked obsolete as this model will only be used for non-websocket updates + //[Obsolete("Use SharedSpotOrderUpdate.LastTrade instead, LastTrade is never filled on non websocket updates")] public SharedUserTrade? LastTrade { get; set; } /// diff --git a/CryptoExchange.Net/SharedApis/Models/SharedSpotOrderUpdate.cs b/CryptoExchange.Net/SharedApis/Models/SharedSpotOrderUpdate.cs new file mode 100644 index 00000000..022313ca --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedSpotOrderUpdate.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Spot order info + /// + [DebuggerDisplay("{DebugView,nq}")] + public record SharedSpotOrderUpdate : SharedSpotOrder + { + /// + /// The info on the executed trade for this update + /// + public new SharedUserTrade? LastTrade + { +#pragma warning disable CS0618 // Type or member is obsolete + get => base.LastTrade; + set => base.LastTrade = value; +#pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// ctor + /// + public SharedSpotOrderUpdate( + SharedSymbol? sharedSymbol, + string symbol, + string orderId, + SharedOrderType orderType, + SharedOrderSide orderSide, + SharedOrderStatus orderStatus, + DateTime? createTime) + : base(sharedSymbol, symbol, orderId, orderType, orderSide, orderStatus, createTime) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotSymbol.cs b/CryptoExchange.Net/SharedApis/Models/SharedSpotSymbol.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotSymbol.cs rename to CryptoExchange.Net/SharedApis/Models/SharedSpotSymbol.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedSpotTicker.cs b/CryptoExchange.Net/SharedApis/Models/SharedSpotTicker.cs new file mode 100644 index 00000000..5df02285 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedSpotTicker.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Ticker info + /// + [DebuggerDisplay("{Symbol,nq} High: {HighPrice}, Low: {LowPrice}, Last: {LastPrice}, Change: {ChangePercentage}%")] + public record SharedSpotTicker : SharedTicker + { + /// + /// ctor + /// + public SharedSpotTicker( + SharedSymbol? sharedSymbol, + string symbol, + decimal? lastPrice, + decimal? highPrice, + decimal? lowPrice, + SharedOrderQuantity volumes, + decimal? changePercentage) + : base(sharedSymbol, symbol, lastPrice, highPrice, lowPrice, volumes, changePercentage) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotTriggerOrder.cs b/CryptoExchange.Net/SharedApis/Models/SharedSpotTriggerOrder.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotTriggerOrder.cs rename to CryptoExchange.Net/SharedApis/Models/SharedSpotTriggerOrder.cs diff --git a/CryptoExchange.Net/SharedApis/SharedSymbol.cs b/CryptoExchange.Net/SharedApis/Models/SharedSymbol.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/SharedSymbol.cs rename to CryptoExchange.Net/SharedApis/Models/SharedSymbol.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSymbolModel.cs b/CryptoExchange.Net/SharedApis/Models/SharedSymbolModel.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedSymbolModel.cs rename to CryptoExchange.Net/SharedApis/Models/SharedSymbolModel.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotTicker.cs b/CryptoExchange.Net/SharedApis/Models/SharedTicker.cs similarity index 96% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotTicker.cs rename to CryptoExchange.Net/SharedApis/Models/SharedTicker.cs index 51fda8c0..ff03207d 100644 --- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotTicker.cs +++ b/CryptoExchange.Net/SharedApis/Models/SharedTicker.cs @@ -7,7 +7,7 @@ namespace CryptoExchange.Net.SharedApis /// Ticker info /// [DebuggerDisplay("{Symbol,nq} High: {HighPrice}, Low: {LowPrice}, Last: {LastPrice}, Change: {ChangePercentage}%")] - public record SharedSpotTicker: SharedSymbolModel + public record SharedTicker : SharedSymbolModel { /// /// Last trade price @@ -55,7 +55,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public SharedSpotTicker( + public SharedTicker( SharedSymbol? sharedSymbol, string symbol, decimal? lastPrice, diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedTrade.cs b/CryptoExchange.Net/SharedApis/Models/SharedTrade.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedTrade.cs rename to CryptoExchange.Net/SharedApis/Models/SharedTrade.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedTransfer.cs b/CryptoExchange.Net/SharedApis/Models/SharedTransfer.cs new file mode 100644 index 00000000..1b881fdd --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedTransfer.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Transfer info + /// + [DebuggerDisplay("[{Timestamp}] {Quantity} {Asset,nq} {FromAccountType} -> {ToAccountType}")] + public record SharedTransfer + { + /// + /// The id of the Transfer + /// + public string? Id { get; set; } + /// + /// The asset of the Transfer + /// + public string Asset { get; set; } + /// + /// The quantity that was Transfered + /// + public decimal Quantity { get; set; } + /// + /// Timestamp of the Transfer + /// + public DateTime Timestamp { get; set; } + /// + /// Source account type + /// + public SharedAccountType FromAccountType { get; set; } + /// + /// Target account type + /// + public SharedAccountType ToAccountType { get; set; } + + /// + /// ctor + /// + public SharedTransfer(string asset, decimal quantity, SharedAccountType fromAccountType, SharedAccountType toAccountType, DateTime timestamp) + { + Asset = asset; + Quantity = quantity; + Timestamp = timestamp; + FromAccountType = fromAccountType; + ToAccountType = toAccountType; + } + } + +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedUserTrade.cs b/CryptoExchange.Net/SharedApis/Models/SharedUserTrade.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedUserTrade.cs rename to CryptoExchange.Net/SharedApis/Models/SharedUserTrade.cs diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedWithdrawal.cs b/CryptoExchange.Net/SharedApis/Models/SharedWithdrawal.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/ResponseModels/SharedWithdrawal.cs rename to CryptoExchange.Net/SharedApis/Models/SharedWithdrawal.cs diff --git a/CryptoExchange.Net/SharedApis/Options/CapabilityOptions.cs b/CryptoExchange.Net/SharedApis/Options/CapabilityOptions.cs new file mode 100644 index 00000000..13053ebd --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Options/CapabilityOptions.cs @@ -0,0 +1,342 @@ +using CryptoExchange.Net.Objects; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options and description of a Shared API capability. + /// + public abstract class CapabilityOptions + { + /// + /// Request parameter rules for the capability. The rules differ per exchange API and can be used to + /// dynamically discover and adjust the request parameters for a capability. + /// + public RequestParameterDescription[] RequestParameterRules { get; private set; } = []; + /// + /// Exchange parameter rules for the capability. These are parameters specific to the exchange API and + /// must be provided in the request via the ExchangeParameters property of the request object. + /// + public ExchangeParameterDescription[] ExchangeParameterRules { get; set; } = []; + + private readonly RequestParameterDescription[] _defaultParameterRules; + private readonly TradingMode[]? _applicableTradingModes; + + + /// + /// Deprectaed, use RequestParameterRules instead. + /// + public List RequiredOptionalParameters + { + get => RequestParameterRules.Where(x => x.DefaultSupport == RequestParameterSupport.Optional + && x.Support == RequestParameterSupport.Required) + .Cast() + .ToList(); + } + + /// + /// Deprectaed, use ExchangeParameterRules instead. + /// + public List RequiredExchangeParameters + { + get => ExchangeParameterRules.Where(x => x.Requirement == ExchangeParameterRequirement.Required).Cast().ToList(); + } + /// + /// Deprectaed, use ExchangeParameterRules instead. + /// + public List OptionalExchangeParameters + { + get => ExchangeParameterRules.Where(x => x.Requirement == ExchangeParameterRequirement.Optional).Cast().ToList(); + } + + /// + /// Whether this accepts multiple symbols (Only applicable to request requiring symbol parameters) + /// + public bool SupportsMultipleSymbols { get; set; } = false; + /// + /// The max number of symbols which can be passed in a call (Only applicable to request requiring symbol parameters) + /// + public int? MaxSymbolCount { get; set; } + /// + /// Exchange + /// + public string Exchange { get; set; } + /// + /// Operation name + /// + public string OperationName { get; set; } + /// + /// Exchange specific additional info + /// + public string? RequestNotes { get; set; } + /// + /// Whether the call requires authentication + /// + public bool NeedsAuthentication { get; set; } + /// + /// Whether the call is supported. If false the exchange API does not support this operation. + /// + public bool Supported { get; set; } = true; + /// + /// Trading modes supported by this capability on the containing Shared API. + /// + public TradingMode[] SupportedTradingModes { get; private set; } = []; + /// + /// Implementation-specific overrides for the default request parameter rules. + /// + public RequestParameterRuleOverride[] ParameterRuleOverrides + { + init + { + RequestParameterRules = CombineParameters(_defaultParameterRules, value); + } + } + /// + /// Optional implementation-specific restriction of the trading modes supported by this capability. + /// The final supported modes are the intersection of this value, the modes applicable to the capability, + /// and the modes supported by the containing Shared API. + /// + public TradingMode[]? SupportedTradingModeOverrides { private get; init; } + /// + /// Description of the endpoint + /// + public abstract string Description { get; } + /// + /// The type of the capability + /// + public abstract Type CapabilityType { get; } + + /// + /// ctor + /// + public CapabilityOptions( + string exchange, + string operationName, + bool needAuthentication, + IEnumerable defaultParameterRules, + IEnumerable? applicableTradingModes = null) + { + Exchange = exchange; + OperationName = operationName; + NeedsAuthentication = needAuthentication; + + _defaultParameterRules = defaultParameterRules + .Select(x => x.WithSupport(x.DefaultSupport)) + .ToArray(); + + RequestParameterRules = CombineParameters( + _defaultParameterRules, + Array.Empty()); + + _applicableTradingModes = applicableTradingModes?.Distinct().ToArray(); + } + + internal void InitializeSupportedTradingModes(TradingMode[] apiSupportedTradingModes) + { + IEnumerable supportedModes = apiSupportedTradingModes; + if (_applicableTradingModes != null) + supportedModes = supportedModes.Intersect(_applicableTradingModes); + if (SupportedTradingModeOverrides != null) + supportedModes = supportedModes.Intersect(SupportedTradingModeOverrides); + + SupportedTradingModes = supportedModes.Distinct().ToArray(); + } + + private RequestParameterDescription[] CombineParameters( + RequestParameterDescription[] defaultParameters, + IEnumerable overrideParameters + ) + { + var result = defaultParameters + .Select(x => x.WithSupport(x.DefaultSupport)) + .ToList(); + + foreach (var item in overrideParameters) + { + var index = result.FindIndex( + x => x.Name == item.ParameterName); + + if (index == -1) + { + throw new ArgumentException( + $"No default request parameter rule exists for " + + $"`{item.ParameterName}`"); + } + + result[index] = result[index].WithOverride(item); + } + + return result.ToArray(); + } + + /// + /// Validate a request + /// + /// Provided exchange parameters + /// Request trading mode + /// Supported trading modes + /// + public virtual Error? ValidateRequest(ExchangeParameters? exchangeParameters, TradingMode? tradingMode, TradingMode[] supportedTradingModes) + { + if (!Supported) + return ArgumentError.Invalid("Operation", $"Operation {Exchange} {OperationName} is not supported by the API"); + + if (tradingMode != null && !supportedTradingModes.Contains(tradingMode.Value)) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{tradingMode} is not supported, supported types: {string.Join(", ", supportedTradingModes)}"); + + foreach (var param in RequiredExchangeParameters) + { + var hasValue = ExchangeParameters.HasValue(exchangeParameters, Exchange, param.Name, param.ValueType) + || param.Aliases.Any(x => ExchangeParameters.HasValue(exchangeParameters, Exchange, x, param.ValueType)); + if (!hasValue) + { + if (param.Aliases.Length == 0) + return ArgumentError.Invalid(param.Name, $"Exchange parameter `{param.Name}` for exchange `{Exchange}` should be provided. Example: {param.ExampleValue}"); + else + return ArgumentError.Invalid(string.Join("/", [param.Name, .. param.Aliases]), $"One of exchange parameters `{string.Join(", ", [param.Name, .. param.Aliases])}` for exchange `{Exchange}` should be provided. Example: {param.ExampleValue}"); + } + } + + return null; + } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendLine($"{OperationName}"); + if (!Supported) + { + sb.AppendLine(" NOT SUPPORTED"); + return sb.ToString(); + } + + sb.AppendLine($"{Description}"); + if (!string.IsNullOrEmpty(RequestNotes)) + { + sb.Append(" Notes: "); + sb.AppendLine(RequestNotes); + } + if (RequestParameterRules.Any()) + { + sb.AppendLine($" Request parameters:"); + foreach (var param in RequestParameterRules) + sb.AppendLine($" {param}"); + } + if (ExchangeParameterRules.Any()) + { + sb.AppendLine($" Exchange specific parameters:"); + foreach (var param in ExchangeParameterRules) + sb.AppendLine($" {param}"); + } + sb.Append(" Needs authentication: "); + sb.AppendLine($"{NeedsAuthentication}"); + return sb.ToString(); + } + } + + /// + /// Options and description of a Shared API capability. + /// + public abstract class CapabilityOptions< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] +#endif +#pragma warning disable CS0618 // Type or member is obsolete + TRequest, TCapability> : EndpointOptions // Derives from endpoint options for compatibility, but can derive from CapabilityOptions in a later release +#pragma warning restore CS0618 // Type or member is obsolete + where TRequest : SharedRequest + where TCapability : ISharedApiCapability + { + private static PropertyInfo[] _requestProperties = typeof(TRequest).GetProperties(); + private static readonly ConcurrentDictionary<(string Exchange, string Operation, string Parameter), byte> _loggedUnsupportedParameters = new(); + + /// + public override Type CapabilityType => typeof(TCapability); + + /// + /// ctor + /// + public CapabilityOptions( + string exchange, + bool needsAuthentication, + string requestName, + IEnumerable defaultParameterRules, + IEnumerable? applicableTradingModes = null) + : base(exchange, requestName, needsAuthentication, defaultParameterRules, applicableTradingModes) + { + } + + /// + /// Validate a request + /// + /// The request + /// Containing client + /// + public virtual Error? ValidateRequest(TRequest request, TCapability client) + { + if (NeedsAuthentication && !client.Authenticated) + return new NoApiCredentialsError(); + + foreach (var param in RequestParameterRules) + { + var property = _requestProperties.Single(x => x.Name == param.Name); + var value = property.GetValue(request); + + if (param.Support == RequestParameterSupport.Required) + { + if (value == null) + { + return ArgumentError.Invalid( + param.Name, + $"Request parameter `{param.Name}` for exchange `{Exchange}` is required and should be provided. Example: {param.ExampleValue}"); + } + } + else if (param.Support == RequestParameterSupport.NotSupported) + { + if (value != null) + { + // For backwards compatibility don't return an error for provided unsupported parameters, only log a warning. + // In a future release this will be changed to return an error. + //return ArgumentError.Invalid( + // param.Name, + // $"Request parameter `{param.Name}` is not supported by exchange `{Exchange}`"); + + var warningKey = (Exchange, OperationName, param.Name); + if (_loggedUnsupportedParameters.TryAdd(warningKey, 0)) + { + LibraryHelpers.StaticLogger?.LogWarning("The parameter {ParamName} has a supplied value for {Exchange}.{CapabilityName}, but is not supported. " + + "In a future major release this will return an error. To check supported/required parameters use the option RequestParameterRules.", + param.Name, Exchange, OperationName); + } + } + } + } + + if (request is SharedSymbolRequest symbolsRequest) + { + if (symbolsRequest.Symbols != null) + { + if (!SupportsMultipleSymbols) + return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Only a single symbol parameter is allowed, multiple symbols are not supported"); + + if (symbolsRequest.Symbols.Length > MaxSymbolCount) + return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Max number of symbols is {MaxSymbolCount} but {symbolsRequest.Symbols.Length} were passed"); + } + + } + + return ValidateRequest( + request.ExchangeParameters, + request.TradingMode, + SupportedTradingModes); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Options/EndpointOptions.cs b/CryptoExchange.Net/SharedApis/Options/EndpointOptions.cs new file mode 100644 index 00000000..bf5897ce --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Options/EndpointOptions.cs @@ -0,0 +1,40 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Endpoint options + /// + [Obsolete("Use CapabilityOptions instead")] + public abstract class EndpointOptions : CapabilityOptions + { + /// + /// Endpoint name + /// + [Obsolete("Use OperationName instead")] + public string EndpointName + { + get => OperationName; + set => OperationName = value; + } + + /// + /// ctor + /// + protected EndpointOptions( + string exchange, + string operationName, + bool needsAuthentication, + IEnumerable defaultParameterRules, + IEnumerable? applicableTradingModes = null) + : base(exchange, operationName, needsAuthentication, defaultParameterRules, applicableTradingModes) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PaginatedEndpointOptions.cs b/CryptoExchange.Net/SharedApis/Options/PaginatedCapabilityOptions.cs similarity index 70% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PaginatedEndpointOptions.cs rename to CryptoExchange.Net/SharedApis/Options/PaginatedCapabilityOptions.cs index 02d955e4..9c8d4596 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PaginatedEndpointOptions.cs +++ b/CryptoExchange.Net/SharedApis/Options/PaginatedCapabilityOptions.cs @@ -1,28 +1,28 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; namespace CryptoExchange.Net.SharedApis { /// - /// Options for paginated endpoints + /// Options and description of a Shared API capability which returns paginated data. /// + public abstract class PaginatedCapabilityOptions< #if NET5_0_OR_GREATER - public abstract class PaginatedEndpointOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TRequest, TClient> : EndpointOptions - where TRequest : SharedRequest - where TClient : ISharedClient -#else - public abstract class PaginatedEndpointOptions : EndpointOptions - where TRequest : SharedRequest - where TClient : ISharedClient + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] #endif + TRequest, TCapability> : CapabilityOptions + where TRequest : SharedRequest + where TCapability : ISharedApiCapability { /// /// Whether ascending data retrieval and pagination is available /// public bool SupportsAscending { get; set; } /// - /// Whether ascending data retrieval and pagination is available + /// Whether descending data retrieval and pagination is available /// public bool SupportsDescending { get; set; } @@ -44,14 +44,16 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public PaginatedEndpointOptions( + public PaginatedCapabilityOptions( string exchange, bool supportsAscending, bool supportsDescending, bool timePeriodSupport, int maxLimit, bool needsAuthentication, - string requestName) : base(exchange, needsAuthentication, requestName) + string requestName, + IEnumerable defaultParameterRules, + IEnumerable? applicableTradingModes = null) : base(exchange, needsAuthentication, requestName, defaultParameterRules, applicableTradingModes) { SupportsAscending = supportsAscending; SupportsDescending = supportsDescending; diff --git a/CryptoExchange.Net/SharedApis/Options/SharedApiOptions.cs b/CryptoExchange.Net/SharedApis/Options/SharedApiOptions.cs new file mode 100644 index 00000000..00855a0b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Options/SharedApiOptions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for Shared API client instances + /// + public class SharedApiOptions + { + /// + /// The preferred transport type when requesting transport agnostic capabilities + /// + public SharedTransport PreferredTransport { get; set; } + = SharedTransport.Rest; + } +} diff --git a/CryptoExchange.Net/SharedApis/Options/SharedTradingModeSets.cs b/CryptoExchange.Net/SharedApis/Options/SharedTradingModeSets.cs new file mode 100644 index 00000000..4447bbc7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Options/SharedTradingModeSets.cs @@ -0,0 +1,24 @@ +namespace CryptoExchange.Net.SharedApis +{ + internal static class SharedTradingModeSets + { + internal static TradingMode[] Spot { get; } = + [ + TradingMode.Spot + ]; + + internal static TradingMode[] Futures { get; } = + [ + TradingMode.PerpetualLinear, + TradingMode.DeliveryLinear, + TradingMode.PerpetualInverse, + TradingMode.DeliveryInverse + ]; + + internal static TradingMode[] Perpetual { get; } = + [ + TradingMode.PerpetualLinear, + TradingMode.PerpetualInverse + ]; + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Pagination/DataDirection.cs b/CryptoExchange.Net/SharedApis/Pagination/DataDirection.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Pagination/DataDirection.cs rename to CryptoExchange.Net/SharedApis/Pagination/DataDirection.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Pagination/PageRequest.cs b/CryptoExchange.Net/SharedApis/Pagination/PageRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Pagination/PageRequest.cs rename to CryptoExchange.Net/SharedApis/Pagination/PageRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Pagination/Pagination.cs b/CryptoExchange.Net/SharedApis/Pagination/Pagination.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Pagination/Pagination.cs rename to CryptoExchange.Net/SharedApis/Pagination/Pagination.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Pagination/PaginationParameters.cs b/CryptoExchange.Net/SharedApis/Pagination/PaginationParameters.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Pagination/PaginationParameters.cs rename to CryptoExchange.Net/SharedApis/Pagination/PaginationParameters.cs diff --git a/CryptoExchange.Net/SharedApis/Models/ExchangeParameter.cs b/CryptoExchange.Net/SharedApis/Parameters/ExchangeParameter.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/ExchangeParameter.cs rename to CryptoExchange.Net/SharedApis/Parameters/ExchangeParameter.cs diff --git a/CryptoExchange.Net/SharedApis/Parameters/ExchangeParameterRequirement.cs b/CryptoExchange.Net/SharedApis/Parameters/ExchangeParameterRequirement.cs new file mode 100644 index 00000000..6e4a0a0e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Parameters/ExchangeParameterRequirement.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Exchange parameter requirements + /// + public enum ExchangeParameterRequirement + { + /// + /// Required parameter + /// + Required, + /// + /// Optional parameter + /// + Optional + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/ExchangeParameters.cs b/CryptoExchange.Net/SharedApis/Parameters/ExchangeParameters.cs similarity index 83% rename from CryptoExchange.Net/SharedApis/Models/ExchangeParameters.cs rename to CryptoExchange.Net/SharedApis/Parameters/ExchangeParameters.cs index 8be680ef..63cb650c 100644 --- a/CryptoExchange.Net/SharedApis/Models/ExchangeParameters.cs +++ b/CryptoExchange.Net/SharedApis/Parameters/ExchangeParameters.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Data.Common; +using System.Globalization; using System.Linq; namespace CryptoExchange.Net.SharedApis @@ -69,16 +70,7 @@ namespace CryptoExchange.Net.SharedApis if (val == null) return false; - try - { - Type t = Nullable.GetUnderlyingType(type) ?? type; - Convert.ChangeType(val, t); - return true; - } - catch - { - return false; - } + return TryConvertValue(val, type, out _); } /// @@ -99,16 +91,7 @@ namespace CryptoExchange.Net.SharedApis if (val == null) return false; - try - { - Type t = Nullable.GetUnderlyingType(type) ?? type; - Convert.ChangeType(val, t); - return true; - } - catch - { - return false; - } + return TryConvertValue(val, type, out _); } /// @@ -124,18 +107,14 @@ namespace CryptoExchange.Net.SharedApis if (val == null) return default; - if (val is T typeVal) - return typeVal; + if (!TryConvertValue(val, typeof(T), out var convertedValue)) + { + throw new ArgumentException( + $"Incorrect type for parameter, expected {typeof(T).Name}", + name); + } - try - { - Type t = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); - return (T)Convert.ChangeType(val, t); - } - catch - { - throw new ArgumentException("Incorrect type for parameter, expected " + typeof(T).Name, name); - } + return (T)convertedValue!; } /// @@ -158,16 +137,52 @@ namespace CryptoExchange.Net.SharedApis if (val == null) return default; + if (!TryConvertValue(val, typeof(T), out var convertedValue)) + { + throw new ArgumentException( + $"Incorrect type for parameter, expected {typeof(T).Name}", + name); + } + + return (T)convertedValue!; + } + + private static bool TryConvertValue( + object value, + Type expectedType, + out object? convertedValue) + { + var targetType = Nullable.GetUnderlyingType(expectedType) ?? expectedType; + + if (targetType.IsInstanceOfType(value)) + { + convertedValue = value; + return true; + } + + if (targetType.IsEnum) + { + convertedValue = null; + return false; + } + try { - var type = typeof(T); - Type t = Nullable.GetUnderlyingType(type) ?? type; - var result = Convert.ChangeType(val, t); - return (T)result; + convertedValue = Convert.ChangeType( + value, + targetType, + CultureInfo.InvariantCulture); + + return true; } - catch + catch (Exception ex) when ( + ex is FormatException || + ex is InvalidCastException || + ex is OverflowException || + ex is ArgumentException) { - throw new ArgumentException("Incorrect type for parameter, expected " + typeof(T).Name, name); + convertedValue = null; + return false; } } diff --git a/CryptoExchange.Net/SharedApis/Parameters/ParameterDescription.cs b/CryptoExchange.Net/SharedApis/Parameters/ParameterDescription.cs new file mode 100644 index 00000000..3dafef1b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Parameters/ParameterDescription.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Parameter description + /// + public class ParameterDescription + { + /// + /// Name of the parameter + /// + public string Name { get; set; } + /// + /// Alternative names for the parameter. + /// + public string[] Aliases { get; set; } = []; + /// + /// Type of the value + /// + public Type ValueType { get; set; } + /// + /// Parameter description + /// + public string Description { get; set; } + /// + /// Example value + /// + public object ExampleValue { get; set; } + + /// + /// Names of the parameter + /// + [Obsolete("Use Name and Aliases instead")] + public string[] Names + { + get => [Name, .. Aliases]; + } + + + /// + /// ctor + /// + public ParameterDescription(string parameterName, Type valueType, string description, object exampleValue) + { + Name = parameterName; + ValueType = valueType; + Description = description; + ExampleValue = exampleValue; + } + + /// + /// ctor + /// + public ParameterDescription(string parameterName, string[] aliases, Type valueType, string description, object exampleValue) + { + Name = parameterName; + Aliases = aliases; + ValueType = valueType; + Description = description; + ExampleValue = exampleValue; + } + + /// + public override string ToString() + { + return $"[{(Nullable.GetUnderlyingType(ValueType) ?? ValueType).Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}"; + } + } + + /// + /// Request parameter description + /// + public class RequestParameterDescription : ParameterDescription + { + /// + /// Status defined by the shared request contract. + /// + public RequestParameterSupport DefaultSupport { get; } + + /// + /// Status after applying exchange overrides. + /// + public RequestParameterSupport Support { get; } + + /// + /// ctor + /// + public RequestParameterDescription( + RequestParameterSupport support, + string parameterName, + Type valueType, + string description, + object exampleValue) + : base(parameterName, valueType, description, exampleValue) + { + Support = support; + DefaultSupport = support; + } + + private RequestParameterDescription( + RequestParameterSupport defaultSupport, + RequestParameterSupport support, + string parameterName, + Type valueType, + string description, + object exampleValue) + : base(parameterName, valueType, description, exampleValue) + { + DefaultSupport = defaultSupport; + Support = support; + } + + internal RequestParameterDescription WithSupport( + RequestParameterSupport support) + => new( + DefaultSupport, + support, + Name, + ValueType, + Description, + ExampleValue); + + internal RequestParameterDescription WithOverride( + RequestParameterRuleOverride parameterOverride) + => new( + DefaultSupport, + parameterOverride.Support, + Name, + ValueType, + parameterOverride.Description ?? Description, + ExampleValue); + + /// + public override string ToString() + { + return $"[{Support}] [{(Nullable.GetUnderlyingType(ValueType) ?? ValueType).Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}"; + } + } + + /// + /// Exchange-specific parameter description + /// + public class ExchangeParameterDescription : ParameterDescription + { + /// + /// Requirement for the parameter + /// + public ExchangeParameterRequirement Requirement { get; init; } + + /// + /// ctor + /// + public ExchangeParameterDescription( + ExchangeParameterRequirement requirement, + string parameterName, + string[] aliases, + Type valueType, + string description, + object exampleValue) + : base(parameterName, aliases, valueType, description, exampleValue) + { + Requirement = requirement; + } + + /// + public override string ToString() + { + return $"[{Requirement}] [{(Nullable.GetUnderlyingType(ValueType) ?? ValueType).Name}] \"{Name}\" {string.Join(" / ", Aliases.Select(x => $"\"{x}\""))}: {Description}"; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Parameters/RequestParameterRule.cs b/CryptoExchange.Net/SharedApis/Parameters/RequestParameterRule.cs new file mode 100644 index 00000000..10092553 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Parameters/RequestParameterRule.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Creates descriptions for properties on a shared request. + /// + public static class RequestParameterRule + where TRequest : SharedRequest + { + /// + /// Describe a request property which is required by an implementation. + /// + public static RequestParameterDescription Required( + Expression> selector, + string description, + TValue exampleValue) + { + Expression body = selector.Body; + + if (body is UnaryExpression unary + && unary.NodeType == ExpressionType.Convert) + { + body = unary.Operand; + } + + if (body is not MemberExpression member + || member.Member is not PropertyInfo property + || member.Expression != selector.Parameters[0]) + { + throw new ArgumentException( + "Selector must select a direct request property", + nameof(selector)); + } + + return new RequestParameterDescription( + RequestParameterSupport.Required, + property.Name, + property.PropertyType, + description, + exampleValue! + ); + } + + /// + /// Describe a request property which is optional by an implementation. + /// + public static RequestParameterDescription Optional( + Expression> selector, + string description, + TValue exampleValue) + { + Expression body = selector.Body; + + if (body is UnaryExpression unary + && unary.NodeType == ExpressionType.Convert) + { + body = unary.Operand; + } + + if (body is not MemberExpression member + || member.Member is not PropertyInfo property + || member.Expression != selector.Parameters[0]) + { + throw new ArgumentException( + "Selector must select a direct request property", + nameof(selector)); + } + + return new RequestParameterDescription( + RequestParameterSupport.Optional, + property.Name, + property.PropertyType, + description, + exampleValue! + ); + } + } + + /// + /// Creates descriptions for exchange-specific parameters. + /// + public static class ExchangeParameterRule + { + /// + /// Describe a required exchange-specific parameter. + /// + public static ExchangeParameterDescription Required( + string name, + string description, + TValue exampleValue, + params string[] aliases) + => Create(ExchangeParameterRequirement.Required, name, description, exampleValue, aliases); + + /// + /// Describe an optional exchange-specific parameter. + /// + public static ExchangeParameterDescription Optional( + string name, + string description, + TValue exampleValue, + params string[] aliases) + => Create(ExchangeParameterRequirement.Optional, name, description, exampleValue, aliases); + + private static ExchangeParameterDescription Create( + ExchangeParameterRequirement requirement, + string name, + string description, + TValue exampleValue, + string[] aliases) + { + return new ExchangeParameterDescription( + requirement, + name, + aliases, + typeof(TValue), + description, + exampleValue!); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Parameters/RequestParameterRuleOverride.cs b/CryptoExchange.Net/SharedApis/Parameters/RequestParameterRuleOverride.cs new file mode 100644 index 00000000..b00f9c9c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Parameters/RequestParameterRuleOverride.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request parameter rule override + /// + /// The name of the parameter + /// The support level of the parameter + /// A description of the parameter + public record RequestParameterRuleOverride( + string ParameterName, + RequestParameterSupport Support, + string? Description = null); + + /// + /// Request parameter rule override factory + /// + /// The type of the request + public static class RequestParameterRuleOverride + where TRequest : SharedRequest + { + /// + /// Required parameter rule override + /// + public static RequestParameterRuleOverride Required( + Expression> selector, + string? description = null) + => Create(selector, RequestParameterSupport.Required, description); + + /// + /// Optional parameter rule override + /// + public static RequestParameterRuleOverride Optional( + Expression> selector, + string? description = null) + => Create(selector, RequestParameterSupport.Optional, description); + + /// + /// Not supported parameter rule override + /// + public static RequestParameterRuleOverride NotSupported( + Expression> selector, + string? description = null) + => Create(selector, RequestParameterSupport.NotSupported, description); + + private static RequestParameterRuleOverride Create( + Expression> selector, + RequestParameterSupport support, + string? description) + { + var body = selector.Body is UnaryExpression unary + && unary.NodeType == ExpressionType.Convert + ? unary.Operand + : selector.Body; + + if (body is not MemberExpression member + || member.Member is not PropertyInfo property + || member.Expression != selector.Parameters[0]) + { + throw new ArgumentException( + "Selector must select a direct request property", + nameof(selector)); + } + + return new RequestParameterRuleOverride( + property.Name, + support, + description); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Parameters/RequestParameterSupport.cs b/CryptoExchange.Net/SharedApis/Parameters/RequestParameterSupport.cs new file mode 100644 index 00000000..0960c747 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Parameters/RequestParameterSupport.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request parameter support + /// + public enum RequestParameterSupport + { + /// + /// Required parameter + /// + Required, + /// + /// Optional parameter + /// + Optional, + /// + /// Not supported parameter + /// + NotSupported + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesTicker.cs b/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesTicker.cs deleted file mode 100644 index 4d13eeb9..00000000 --- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesTicker.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Diagnostics; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// Futures ticker info - /// - [DebuggerDisplay("{Symbol,nq} High: {HighPrice}, Low: {LowPrice}, Last: {LastPrice}, Change: {ChangePercentage}%")] - public record SharedFuturesTicker: SharedSymbolModel - { - /// - /// Last trade price - /// - public decimal? LastPrice { get; set; } - /// - /// High price in the last 24h - /// - public decimal? HighPrice { get; set; } - /// - /// Low price in the last 24h - /// - public decimal? LowPrice { get; set; } - /// - /// The volume in the last 24h - /// - public SharedOrderQuantity Volumes { get; set; } - - private decimal? _volume; - /// - /// The volume in the last 24h - /// - [Obsolete("Use `Volumes` instead")] - public decimal Volume - { - get - { - if (_volume.HasValue) - return _volume.Value; - - return Volumes.QuantityInBaseAsset ?? Volumes.QuantityInContracts ?? 0; - } - set => _volume = value; - } - /// - /// Change percentage in the last 24h - /// - public decimal? ChangePercentage { get; set; } - /// - /// Current mark price - /// - public decimal? MarkPrice { get; set; } - /// - /// Current index price - /// - public decimal? IndexPrice { get; set; } - /// - /// Current funding rate - /// - public decimal? FundingRate { get; set; } - /// - /// Next funding time - /// - public DateTime? NextFundingTime { get; set; } - - /// - /// ctor - /// - public SharedFuturesTicker( - SharedSymbol? sharedSymbol, - string symbol, - decimal? lastPrice, - decimal? highPrice, - decimal? lowPrice, - SharedOrderQuantity volumes, - decimal? changePercentage) - :base(sharedSymbol, symbol) - { - LastPrice = lastPrice; - HighPrice = highPrice; - LowPrice = lowPrice; - Volumes = volumes; - ChangePercentage = changePercentage; - } - } -} diff --git a/CryptoExchange.Net/SharedApis/SharedApiBase.cs b/CryptoExchange.Net/SharedApis/SharedApiBase.cs new file mode 100644 index 00000000..42192d2e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/SharedApiBase.cs @@ -0,0 +1,80 @@ +using CryptoExchange.Net.Clients; +using CryptoExchange.Net.Interfaces.Clients; +using CryptoExchange.Net.RateLimiting; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Shared API base client + /// + public abstract class SharedApiBase : ISharedApi + { + private readonly Func _symbolFormatter; + private readonly Func _authDelegate; + private readonly IBaseApiClient _apiClient; + private IReadOnlyCollection _capabilities = Array.Empty(); + IReadOnlyCollection ISharedApi.Capabilities => _capabilities; + + /// + public string Exchange { get; } + + /// + public SharedTransport Transport { get; } + + /// + public TradingMode[] SupportedTradingModes { get; } + + /// + public bool Authenticated => _authDelegate(); + + /// + /// Shared API base client + /// + public SharedApiBase( + SharedTransport transport, + IBaseApiClient apiClient, + TradingMode[] supportedTradingModes, + Func authenticated, + Func formatSymbol) + { + _apiClient = apiClient; + _authDelegate = authenticated; + _symbolFormatter = formatSymbol; + Transport = transport; + Exchange = apiClient.Exchange; + SupportedTradingModes = supportedTradingModes; + } + + /// + protected void SetCapabilities(params CapabilityOptions[] capabilities) + { + foreach (var capability in capabilities) + capability.InitializeSupportedTradingModes(SupportedTradingModes); + + _capabilities = Array.AsReadOnly(capabilities); + } + + /// + public string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null) + => _symbolFormatter(baseAsset, quoteAsset, tradingMode, deliverDate); + + /// + public void SetDefaultExchangeParameter(string name, object value) => ExchangeParameters.SetStaticParameter(Exchange, name, value); + + /// + public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticExchangeParameters(Exchange); + + /// + public Task WithRateLimitAdmissionAsync( + RateLimitAdmission admission, + Func> operation) + => _apiClient.WithRateLimitAdmissionAsync(admission, operation); + + /// + public abstract SharedClientInfo Discover(); + } +} diff --git a/CryptoExchange.Net/SharedApis/SharedApiClientBase.cs b/CryptoExchange.Net/SharedApis/SharedApiClientBase.cs new file mode 100644 index 00000000..09f83740 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/SharedApiClientBase.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + internal interface ISharedApiClientResolver + { + object? GetCapability(Type capabilityType); + } + + /// + /// Base interface for Shared API clients + /// + public interface ISharedApiClientBase + { + /// + /// Shared API's available on this client + /// + IReadOnlyList SharedApis { get; } + /// + /// Preferred transport selection when multiple transport options are available + /// + SharedTransport PreferredTransport { get; } + + /// + /// Get discovery information for the Shared APIs available on this client. + /// + SharedApiClientInfo Discover(); + /// + /// The exchange name of this client + /// + string Exchange { get; } + + /// + /// Get all capabilities matching the provided capability type. + /// Example: + /// client.GetCapability<IPlaceSpotOrder>() + /// + /// The capability type + /// Filter by supported trading mode + /// Filter by transport method + IReadOnlyList> GetCapabilities(TradingMode? tradingMode = null, SharedTransport? transport = null) + where T : ISharedApiCapability; + + /// + /// Get all capabilities matching the provided capability reference. + /// + /// The capability type. + /// The capability reference. + /// Filter by supported trading mode. + IReadOnlyList> GetCapabilities( + SharedCapabilityReference capability, + TradingMode? tradingMode = null) + where T : ISharedApiCapability; + + /// + /// Get the requested capability type. If more than one implementation is available the first one is returned. + /// In this case prefer using to select a specific trading mode. + /// Example: + /// client.GetCapability<IPlaceSpotOrder>(SharedTransport.Socket) + /// + /// The capability type + /// The transport method + /// The requested capability type or null if not found + SharedCapabilityResolution? GetCapability(SharedTransport? transport = null) + where T : ISharedApiCapability; + + /// + /// Get the requested capability type for a specific trading mode. + /// Example: + /// client.GetCapability<IPlaceFuturesOrder>(TradingMode.PerpetualLinear, SharedTransport.Rest) + /// + /// The capability type + /// The trading mode + /// The transport method + /// The requested capability type or null if not found + SharedCapabilityResolution? GetCapability(TradingMode tradingMode, SharedTransport? transport = null) + where T : ISharedApiCapability; + + /// + /// Get the capability matching the provided capability reference. Get a capability reference using . + /// If more than one implementation is available the first one is returned.
+ /// Example: + /// client.GetCapability(SharedCapabilities.Orders.Spot.PlaceOrder.Rest) + ///
+ /// The capability type + /// The capability reference + /// The requested capability type or null if not found + SharedCapabilityResolution? GetCapability(SharedCapabilityReference capability) + where T : ISharedApiCapability; + + /// + /// Get the capability matching the provided capability reference for a specific trading mode. Get a capability reference using . + /// If more than one implementation is available the first one is returned.
+ /// Example: + /// client.GetCapability(SharedCapabilities.Orders.Futures.PlaceOrder.Rest, TradingMode.PerpetualLinear) + ///
+ /// The capability type + /// The capability reference + /// The trading mode + /// The requested capability type or null if not found + SharedCapabilityResolution? GetCapability(SharedCapabilityReference capability, TradingMode tradingMode) + where T : ISharedApiCapability; + + } + + /// + /// Base client for Shared API clients + /// + public abstract class SharedApiClientBase : ISharedApiClientBase, ISharedApiClientResolver + { + private readonly ISharedApi[] _sharedApis; + private readonly SharedTransport _preferredTransport; + + /// + public IReadOnlyList SharedApis => _sharedApis; + /// + public SharedTransport PreferredTransport => _preferredTransport; + /// + public string Exchange => _sharedApis[0].Exchange; + + /// + /// ctor + /// + public SharedApiClientBase( + SharedTransport transportPreference, + params ISharedApi[] sharedApis) + { + _preferredTransport = transportPreference; + _sharedApis = sharedApis.Distinct().ToArray(); + } + + /// + public SharedApiClientInfo Discover() + { + return new SharedApiClientInfo( + PreferredTransport, + _sharedApis.Select(x => x.Discover()).ToArray()); + } + + /// + public SharedCapabilityResolution? GetCapability(SharedTransport? transport = null) + where T : ISharedApiCapability + { + return GetCapabilityCore(null, transport); + } + + /// + public SharedCapabilityResolution? GetCapability(TradingMode tradingMode, SharedTransport? transport = null) + where T : ISharedApiCapability + { + return GetCapabilityCore(tradingMode, transport); + } + + /// + public SharedCapabilityResolution? GetCapability(SharedCapabilityReference capability) + where T : ISharedApiCapability + { + return GetCapabilityCore(null, null); + } + + /// + public SharedCapabilityResolution? GetCapability(SharedCapabilityReference capability, TradingMode tradingMode) + where T : ISharedApiCapability + { + return GetCapabilityCore(tradingMode, null); + } + + + /// + public IReadOnlyList> GetCapabilities( + TradingMode? tradingMode = null, + SharedTransport? transport = null) + where T : ISharedApiCapability + { + var result = new List>(); + + foreach (var sharedApi in _sharedApis) + { + if (sharedApi is not T capability) + continue; + + if (transport != null && sharedApi.Transport != transport) + continue; + + var options = GetMatchingOptions(sharedApi, tradingMode); + if (options == null) + continue; + + result.Add(new SharedCapabilityResolution( + capability, + options)); + } + + // Preserve Shared API registration order within each transport. + return result + .OrderBy(x => GetTransportPriority(x.Capability.Transport)) + .ToArray(); + } + + /// + public IReadOnlyList> GetCapabilities( + SharedCapabilityReference capability, + TradingMode? tradingMode = null) + where T : ISharedApiCapability + { + return GetCapabilities(tradingMode); + } + + object? ISharedApiClientResolver.GetCapability(Type capabilityType) + { + return _sharedApis + .Where(sharedApi => + capabilityType.IsInstanceOfType(sharedApi) + && GetMatchingOptions( + sharedApi, + capabilityType, + null) != null) + .OrderBy(sharedApi => + GetTransportPriority(sharedApi.Transport)) + .FirstOrDefault(); + } + + private SharedCapabilityResolution? GetCapabilityCore( + TradingMode? tradingMode, + SharedTransport? selectedTransport = null) + where T : ISharedApiCapability + { + return GetCapabilities(tradingMode, selectedTransport) + .FirstOrDefault(); + } + + private static CapabilityOptions? GetMatchingOptions( + ISharedApi sharedApi, + TradingMode? tradingMode) + where T : ISharedApiCapability + { + return GetMatchingOptions( + sharedApi, + typeof(T), + tradingMode); + } + + private static CapabilityOptions? GetMatchingOptions( + ISharedApi sharedApi, + Type requestedType, + TradingMode? tradingMode) + { + return sharedApi.Capabilities + .Where(options => + options.Supported + && IsCapabilityTypeMatch( + requestedType, + options.CapabilityType) + && (tradingMode == null + || options.SupportedTradingModes.Contains( + tradingMode.Value))) + .OrderByDescending(options => + options.CapabilityType == requestedType) + .FirstOrDefault(); + } + + private static bool IsCapabilityTypeMatch( + Type requestedType, + Type optionsCapabilityType) + { + return optionsCapabilityType.IsAssignableFrom(requestedType) + || requestedType.IsAssignableFrom(optionsCapabilityType); + } + + private int GetTransportPriority(SharedTransport transport) + { + return transport == _preferredTransport ? 0 : 1; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/SharedApiClientRegistrationBuilder.cs b/CryptoExchange.Net/SharedApis/SharedApiClientRegistrationBuilder.cs new file mode 100644 index 00000000..5d312d2e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/SharedApiClientRegistrationBuilder.cs @@ -0,0 +1,110 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Shared API dependency injection registration builder + /// + /// + public class SharedApiClientRegistrationBuilder + where TSharedApiClient : class, ISharedApiClientBase + { + private readonly IServiceCollection _services; + private readonly HashSet _baseCapabilityTypes = new(); + + internal SharedApiClientRegistrationBuilder( + IServiceCollection services) + { + _services = services; + } + + /// + /// Adds a shared API + /// + public SharedApiClientRegistrationBuilder Add< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.Interfaces)] +#endif + TSharedApi>( + Func selector) + where TSharedApi : class, ISharedApi + { + if (selector is null) + throw new ArgumentNullException(nameof(selector)); + + _services.AddTransient( + serviceProvider => + { + var client = serviceProvider + .GetRequiredService(); + + return selector(client); + }); + + var interfaces = typeof(TSharedApi).GetInterfaces(); + + var transportCapabilities = interfaces + .Where(type => + typeof(ISharedApiCapability).IsAssignableFrom(type) + && (typeof(ISharedRest).IsAssignableFrom(type) + || typeof(ISharedSocket).IsAssignableFrom(type)) + && type != typeof(ISharedRest) + && type != typeof(ISharedSocket) + && type != typeof(ISharedSubscription)) + .Distinct(); + + foreach (var capabilityType in transportCapabilities) + { + _services.AddTransient( + capabilityType, + serviceProvider => + { + var client = serviceProvider + .GetRequiredService(); + + return selector(client); + }); + } + + var baseCapabilities = interfaces + .Where(type => + type != typeof(ISharedApiCapability) + && typeof(ISharedApiCapability).IsAssignableFrom(type) + && !typeof(ISharedRest).IsAssignableFrom(type) + && !typeof(ISharedSocket).IsAssignableFrom(type)); + + foreach (var capabilityType in baseCapabilities) + _baseCapabilityTypes.Add(capabilityType); + + return this; + } + + internal void RegisterTransportAgnosticCapabilities() + { + foreach (var capabilityType in _baseCapabilityTypes) + { + _services.AddTransient( + capabilityType, + serviceProvider => + { + var client = serviceProvider + .GetRequiredService(); + + var resolver = (ISharedApiClientResolver)client; + + return resolver.GetCapability(capabilityType) + ?? throw new InvalidOperationException( + $"No implementation of " + + $"{capabilityType.Name} is available on " + + $"{typeof(TSharedApiClient).Name}"); + }); + } + } + } +} diff --git a/CryptoExchange.Net/SharedApis/SharedUtils.cs b/CryptoExchange.Net/SharedApis/SharedUtils.cs index f716767c..a4f9d757 100644 --- a/CryptoExchange.Net/SharedApis/SharedUtils.cs +++ b/CryptoExchange.Net/SharedApis/SharedUtils.cs @@ -1,6 +1,10 @@ using CryptoExchange.Net.Objects; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; namespace CryptoExchange.Net.SharedApis { @@ -12,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Get client information including supported features /// - public static SharedClientInfo GetClientInfo(PlatformInfo platformInfo, ISharedClient client) + public static SharedClientInfo GetClientInfo(PlatformInfo platformInfo, ISharedApi client) { return new SharedClientInfo { @@ -21,173 +25,12 @@ namespace CryptoExchange.Net.SharedApis SupportedEnvironments = platformInfo.SupportedEnvironments, SupportedTradingModes = client.SupportedTradingModes, CentralizationType = platformInfo.CentralizationType, - Features = GetAllEndpointOptions(client) + Transport = client.Transport, + Authenticated = client.Authenticated, + Capabilities = client.Capabilities.Where(x => x.Supported).ToArray() }; } - /// - /// Get all supported endpoints for a client - /// - /// - /// - public static EndpointOptions[] GetAllEndpointOptions(ISharedClient client) - { - var clientType = client.GetType(); - var result = new List(); - if (client is IAssetsRestClient assetClient) - { - result.Add(assetClient.GetAssetOptions); - result.Add(assetClient.GetAssetsOptions); - } - if (client is IBalanceRestClient balanceClient) - result.Add(balanceClient.GetBalancesOptions); - if (client is IDepositRestClient depositClient) - { - result.Add(depositClient.GetDepositAddressesOptions); - result.Add(depositClient.GetDepositsOptions); - } - if (client is IKlineRestClient klineClient) - result.Add(klineClient.GetKlinesOptions); - if (client is IOrderBookRestClient orderBookClient) - result.Add(orderBookClient.GetOrderBookOptions); - if (client is IRecentTradeRestClient recentTradeClient) - result.Add(recentTradeClient.GetRecentTradesOptions); - if (client is ITradeHistoryRestClient tradeHistoryClient) - result.Add(tradeHistoryClient.GetTradeHistoryOptions); - if (client is IWithdrawalRestClient withdrawalClient) - result.Add(withdrawalClient.GetWithdrawalsOptions); - if (client is IWithdrawRestClient withdrawClient) - result.Add(withdrawClient.WithdrawOptions); - if (client is IFeeRestClient feeClient) - result.Add(feeClient.GetFeeOptions); - if (client is IBookTickerRestClient bookTickerClient) - result.Add(bookTickerClient.GetBookTickerOptions); - if (client is ITransferRestClient transferClient) - result.Add(transferClient.TransferOptions); - - if (client is ISpotOrderRestClient spotOrderClient) - { - result.Add(spotOrderClient.PlaceSpotOrderOptions); - result.Add(spotOrderClient.CancelSpotOrderOptions); - result.Add(spotOrderClient.GetClosedSpotOrdersOptions); - result.Add(spotOrderClient.GetOpenSpotOrdersOptions); - result.Add(spotOrderClient.GetSpotOrderOptions); - result.Add(spotOrderClient.GetSpotOrderTradesOptions); - result.Add(spotOrderClient.GetSpotUserTradesOptions); - } - if (client is ISpotSymbolRestClient spotSymbolClient) - result.Add(spotSymbolClient.GetSpotSymbolsOptions); - if (client is ISpotTickerRestClient spotTickerClient) - { - result.Add(spotTickerClient.GetSpotTickerOptions); - result.Add(spotTickerClient.GetSpotTickersOptions); - } - if (client is ISpotTriggerOrderRestClient spotTriggerOrderClient) - { - result.Add(spotTriggerOrderClient.CancelSpotTriggerOrderOptions); - result.Add(spotTriggerOrderClient.GetSpotTriggerOrderOptions); - result.Add(spotTriggerOrderClient.PlaceSpotTriggerOrderOptions); - } - if (client is ISpotOrderClientIdRestClient spotOrderClientIdClient) - { - result.Add(spotOrderClientIdClient.CancelSpotOrderByClientOrderIdOptions); - result.Add(spotOrderClientIdClient.GetSpotOrderByClientOrderIdOptions); - } - - if (client is IFundingRateRestClient fundingRateClient) - result.Add(fundingRateClient.GetFundingRateHistoryOptions); - if (client is IFuturesOrderRestClient futuresOrderClient) - { - result.Add(futuresOrderClient.CancelFuturesOrderOptions); - result.Add(futuresOrderClient.ClosePositionOptions); - result.Add(futuresOrderClient.GetClosedFuturesOrdersOptions); - result.Add(futuresOrderClient.GetFuturesOrderOptions); - result.Add(futuresOrderClient.GetFuturesOrderTradesOptions); - result.Add(futuresOrderClient.GetFuturesUserTradesOptions); - result.Add(futuresOrderClient.GetOpenFuturesOrdersOptions); - result.Add(futuresOrderClient.GetPositionsOptions); - result.Add(futuresOrderClient.PlaceFuturesOrderOptions); - } - if (client is IFuturesSymbolRestClient futuresSymbolClient) - result.Add(futuresSymbolClient.GetFuturesSymbolsOptions); - if (client is IFuturesTickerRestClient futuresTickerClient) - { - result.Add(futuresTickerClient.GetFuturesTickerOptions); - result.Add(futuresTickerClient.GetFuturesTickersOptions); - } - if (client is IIndexPriceKlineRestClient indexPriceKlineClient) - result.Add(indexPriceKlineClient.GetIndexPriceKlinesOptions); - if (client is ILeverageRestClient leverageClient) - { - result.Add(leverageClient.GetLeverageOptions); - result.Add(leverageClient.SetLeverageOptions); - } - if (client is IMarkPriceKlineRestClient markPriceKlineClient) - result.Add(markPriceKlineClient.GetMarkPriceKlinesOptions); - if (client is IOpenInterestRestClient openInterestClient) - result.Add(openInterestClient.GetOpenInterestOptions); - if (client is IPositionHistoryRestClient positionHistoryClient) - result.Add(positionHistoryClient.GetPositionHistoryOptions); - if (client is IPositionModeRestClient positionModeClient) - { - result.Add(positionModeClient.SetPositionModeOptions); - result.Add(positionModeClient.GetPositionModeOptions); - } - if (client is IFuturesTpSlRestClient futuresTpSlClient) - { - result.Add(futuresTpSlClient.SetFuturesTpSlOptions); - result.Add(futuresTpSlClient.CancelFuturesTpSlOptions); - } - if (client is IFuturesTriggerOrderRestClient futuresTriggerOrderClient) - { - result.Add(futuresTriggerOrderClient.CancelFuturesTriggerOrderOptions); - result.Add(futuresTriggerOrderClient.GetFuturesTriggerOrderOptions); - result.Add(futuresTriggerOrderClient.PlaceFuturesTriggerOrderOptions); - } - if (client is IFuturesOrderClientIdRestClient futuresOrderClientIdClient) - { - result.Add(futuresOrderClientIdClient.GetFuturesOrderByClientOrderIdOptions); - result.Add(futuresOrderClientIdClient.CancelFuturesOrderByClientOrderIdOptions); - } - - if (client is IBalanceSocketClient balanceSocketClient) - result.Add(balanceSocketClient.SubscribeBalanceOptions); - if (client is IBookTickerSocketClient bookTickerSocketClient) - result.Add(bookTickerSocketClient.SubscribeBookTickerOptions); - if (client is IKlineSocketClient klineSocketClient) - result.Add(klineSocketClient.SubscribeKlineOptions); - if (client is IOrderBookSocketClient orderBookSocketClient) - result.Add(orderBookSocketClient.SubscribeOrderBookOptions); - if (client is ITickerSocketClient tickerSocketClient) - result.Add(tickerSocketClient.SubscribeTickerOptions); - if (client is ITickersSocketClient tickersSocketClient) - result.Add(tickersSocketClient.SubscribeAllTickersOptions); - if (client is ITradeSocketClient tradeSocketClient) - result.Add(tradeSocketClient.SubscribeTradeOptions); - if (client is IUserTradeSocketClient userTradeSocketClient) - result.Add(userTradeSocketClient.SubscribeUserTradeOptions); - - if (client is ISpotOrderSocketClient spotOrderSocketClient) - result.Add(spotOrderSocketClient.SubscribeSpotOrderOptions); - if (client is ISpotOrderManagementSocketClient spotOrderManagementSocketClient) - { - result.Add(spotOrderManagementSocketClient.PlaceSpotOrderOptions); - result.Add(spotOrderManagementSocketClient.CancelSpotOrderOptions); - } - - if (client is IFuturesOrderSocketClient futuresOrderSocketClient) - result.Add(futuresOrderSocketClient.SubscribeFuturesOrderOptions); - if (client is IPositionSocketClient positionSocketClient) - result.Add(positionSocketClient.SubscribePositionOptions); - if (client is IFuturesOrderManagementSocketClient futuresOrderManagementSocketClient) - { - result.Add(futuresOrderManagementSocketClient.PlaceFuturesOrderOptions); - result.Add(futuresOrderManagementSocketClient.CancelFuturesOrderOptions); - } - - return result.Where(x => x.Supported).ToArray(); - } - /// /// Apply symbols request filter for asset type and trading mode /// @@ -206,5 +49,580 @@ namespace CryptoExchange.Net.SharedApis resultData = resultData.Where(x => x.QuoteAssetSubType == request.QuoteAssetSubType); return resultData.ToArray(); } + + /// + /// Register Shared API client in DI container + /// + public static IServiceCollection RegisterSharedApiClient< + TSharedApiClient, +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicConstructors)] +#endif + TImplementation + >(this IServiceCollection services, Action> configure) + where TImplementation : class, TSharedApiClient + where TSharedApiClient : class, ISharedApiClientBase + { + services.AddTransient(); + services.AddTransient( + serviceProvider => + serviceProvider.GetRequiredService()); + + var builder = + new SharedApiClientRegistrationBuilder(services); + + configure(builder); + builder.RegisterTransportAgnosticCapabilities(); + + return services; + } + + /// + /// Execute GetAssetAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetAssetRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAssetAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAssetAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetAssetRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAssetAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAllAssetsAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetAssetsRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAllAssetsAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAllAssetsAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetAssetsRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAllAssetsAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetBalancesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetBalancesRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetBalancesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetBalancesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetBalancesRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetBalancesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetFeesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetFeeRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetFeesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetFeesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetFeeRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetFeesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetFundingInfoAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetFundingInfoRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetFundingInfoAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetFundingInfoAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetFundingInfoRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetFundingInfoAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetIndexPriceAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetIndexPriceRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetIndexPriceAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetIndexPriceAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetIndexPriceRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetIndexPriceAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetMarkPriceAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetMarkPriceRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetMarkPriceAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetMarkPriceAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetMarkPriceRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetMarkPriceAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAllMarkPricesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetAllMarkPricesRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAllMarkPricesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAllMarkPricesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetAllMarkPricesRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAllMarkPricesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAllIndexPricesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetAllIndexPricesRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAllIndexPricesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAllIndexPricesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetAllIndexPricesRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAllIndexPricesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetLeverageAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetLeverageRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetLeverageAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetLeverageAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetLeverageRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetLeverageAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetLeverageTiersAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetLeverageTiersRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetLeverageTiersAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetLeverageTiersAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetLeverageTiersRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetLeverageTiersAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetOpenInterestAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetOpenInterestRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetOpenInterestAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetOpenInterestAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetOpenInterestRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetOpenInterestAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetBookTickerAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetBookTickerRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetBookTickerAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetBookTickerAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetBookTickerRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetBookTickerAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetOrderBookAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetOrderBookRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetOrderBookAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetOrderBookAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetOrderBookRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetOrderBookAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetPositionModeAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetPositionModeRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetPositionModeAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetPositionModeAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetPositionModeRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetPositionModeAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetPositionsAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetPositionsRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetPositionsAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetPositionsAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetPositionsRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetPositionsAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetSpotSymbolsAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetSymbolsRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetSpotSymbolsAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetSpotSymbolsAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetSymbolsRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetSpotSymbolsAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetFuturesSymbolsAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetSymbolsRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetFuturesSymbolsAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetFuturesSymbolsAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetSymbolsRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetFuturesSymbolsAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetTickerAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetTickerRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetTickerAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetTickerAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetTickerRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetTickerAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAllTickersAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetTickersRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAllTickersAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetAllTickersAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetTickersRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetAllTickersAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetRecentTradesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetRecentTradesRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetRecentTradesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + + /// + /// Execute GetRecentTradesAsync for all capabilities in parallel and return results as they arrive + /// + public static IAsyncEnumerable> ExecuteAllAsync( + this IEnumerable> capabilities, + GetRecentTradesRequest request, + CancellationToken ct = default) + { + return capabilities + .Select(x => x.Capability.GetRecentTradesAsync(request, ct)) + .ParallelEnumerateAsync(); + } + } } diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetAssetsOptions.cs b/CryptoExchange.Net/SharedApis/V2/Assets/GetAllAssets/GetAllAssetsOptions.cs similarity index 56% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetAssetsOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Assets/GetAllAssets/GetAllAssetsOptions.cs index c14123c5..bd2a3c03 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetAssetsOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Assets/GetAllAssets/GetAllAssetsOptions.cs @@ -7,15 +7,17 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting assets info /// - public class GetAssetsOptions : EndpointOptions + public class GetAllAssetsOptions : CapabilityOptions { /// public override string Description => "Retrieve basic info for all assets and the networks they support for withdrawals/deposits"; + private static readonly RequestParameterDescription[] _defaultParameterRules = []; + /// /// ctor /// - public GetAssetsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IAssetsRestClient.GetAssetsAsync)) + public GetAllAssetsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetAllAssetsRest.GetAllAssetsAsync), _defaultParameterRules) { } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetAssetsRequest.cs b/CryptoExchange.Net/SharedApis/V2/Assets/GetAllAssets/GetAssetsRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetAssetsRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Assets/GetAllAssets/GetAssetsRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Assets/GetAllAssets/IGetAllAssets.cs b/CryptoExchange.Net/SharedApis/V2/Assets/GetAllAssets/IGetAllAssets.cs new file mode 100644 index 00000000..9d1213bc --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Assets/GetAllAssets/IGetAllAssets.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving all assets supported on an exchange. + /// + public interface IGetAllAssets : ISharedApiCapability + { + /// + /// Assets request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetAllAssetsOptions GetAllAssetsOptions { get; } + + /// + /// Get info on all assets the exchange supports, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetAllAssetsAsync(GetAssetsRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving all assets supported on an exchange via the REST API. + /// + public interface IGetAllAssetsRest : IGetAllAssets, ISharedRest + { + /// + new Task> GetAllAssetsAsync(GetAssetsRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetAssetOptions.cs b/CryptoExchange.Net/SharedApis/V2/Assets/GetAsset/GetAssetOptions.cs similarity index 55% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetAssetOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Assets/GetAsset/GetAssetOptions.cs index 47f411f6..666917cf 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetAssetOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Assets/GetAsset/GetAssetOptions.cs @@ -7,15 +7,20 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting asset info /// - public class GetAssetOptions : EndpointOptions + public class GetAssetOptions : CapabilityOptions { /// public override string Description => "Retrieve basic info for a single asset and networks it supports for withdrawals/deposits"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Asset, "The asset to retrieve information for", "ETH"), + }; + /// /// ctor /// - public GetAssetOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IAssetsRestClient.GetAssetAsync)) + public GetAssetOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetAssetRest.GetAssetAsync), _defaultParameterRules) { } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetAssetRequest.cs b/CryptoExchange.Net/SharedApis/V2/Assets/GetAsset/GetAssetRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetAssetRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Assets/GetAsset/GetAssetRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Assets/GetAsset/IGetAsset.cs b/CryptoExchange.Net/SharedApis/V2/Assets/GetAsset/IGetAsset.cs new file mode 100644 index 00000000..718d0160 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Assets/GetAsset/IGetAsset.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving asset info from an exchange. + /// + public interface IGetAsset : ISharedApiCapability + { + /// + /// Asset request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetAssetOptions GetAssetOptions { get; } + + /// + /// Get info on a specific asset, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetAssetAsync(GetAssetRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving asset info from an exchange via the REST API. + /// + public interface IGetAssetRest : IGetAsset, ISharedRest + { + /// + new Task> GetAssetAsync(GetAssetRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs b/CryptoExchange.Net/SharedApis/V2/Balances/GetBalances/GetBalancesOptions.cs similarity index 77% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Balances/GetBalances/GetBalancesOptions.cs index 1d53b379..6b868438 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Balances/GetBalances/GetBalancesOptions.cs @@ -6,11 +6,17 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting a transfer /// - public class GetBalancesOptions : EndpointOptions + public class GetBalancesOptions : CapabilityOptions { /// public override string Description => "Retrieve account balances"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the balances by trading mode", TradingMode.Spot), + RequestParameterRule.Optional(x => x.AccountType, "The account type to retrieve balances for", SharedAccountType.Spot), + }; + /// /// Supported account types /// @@ -19,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public GetBalancesOptions(string exchange, params AccountTypeFilter[] accountTypes) : base(exchange, true, nameof(IBalanceRestClient.GetBalancesAsync)) + public GetBalancesOptions(string exchange, params AccountTypeFilter[] accountTypes) : base(exchange, true, nameof(IGetBalancesRest.GetBalancesAsync), _defaultParameterRules) { SupportedAccountTypes = accountTypes; } @@ -29,12 +35,16 @@ namespace CryptoExchange.Net.SharedApis ///
public override Error? ValidateRequest( GetBalancesRequest request, - IBalanceRestClient client) + IGetBalances client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (request.AccountType != null && !IsValid(request.AccountType.Value)) return ArgumentError.Invalid(nameof(request.AccountType), "Invalid AccountType"); - return base.ValidateRequest(request, client); + return error; } /// diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs b/CryptoExchange.Net/SharedApis/V2/Balances/GetBalances/GetBalancesRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Balances/GetBalances/GetBalancesRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Balances/GetBalances/IGetBalances.cs b/CryptoExchange.Net/SharedApis/V2/Balances/GetBalances/IGetBalances.cs new file mode 100644 index 00000000..ebfde46b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Balances/GetBalances/IGetBalances.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving user asset balances from an exchange. + /// + public interface IGetBalances : ISharedApiCapability + { + /// + /// Balances request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetBalancesOptions GetBalancesOptions { get; } + + /// + /// Get balances for the user, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> GetBalancesAsync(GetBalancesRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving user asset balances from an exchange via the REST API. + /// + public interface IGetBalancesRest : IGetBalances, ISharedRest + { + /// + new Task> GetBalancesAsync(GetBalancesRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Balances/SubscribeBalances/ISubscribeBalances.cs b/CryptoExchange.Net/SharedApis/V2/Balances/SubscribeBalances/ISubscribeBalances.cs new file mode 100644 index 00000000..fc86c5f7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Balances/SubscribeBalances/ISubscribeBalances.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to user balance updates + /// + public interface ISubscribeBalancesSocket : ISharedSubscription + { + /// + /// Balance subscription options + /// + SubscribeBalanceOptions SubscribeBalanceOptions { get; } + + /// + /// Subscribe to user balance updates + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToBalanceUpdatesAsync(SubscribeBalancesRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeBalanceOptions.cs b/CryptoExchange.Net/SharedApis/V2/Balances/SubscribeBalances/SubscribeBalanceOptions.cs similarity index 53% rename from CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeBalanceOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Balances/SubscribeBalances/SubscribeBalanceOptions.cs index 44685e4b..6cd84170 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeBalanceOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Balances/SubscribeBalances/SubscribeBalanceOptions.cs @@ -7,15 +7,20 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for subscribing to balance updates /// - public class SubscribeBalanceOptions : EndpointOptions + public class SubscribeBalanceOptions : CapabilityOptions { /// public override string Description => "Subscribe to balance updates"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter balance updates by trading mode", TradingMode.Spot), + }; + /// /// ctor /// - public SubscribeBalanceOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(IBalanceSocketClient.SubscribeToBalanceUpdatesAsync)) + public SubscribeBalanceOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ISubscribeBalancesSocket.SubscribeToBalanceUpdatesAsync), _defaultParameterRules) { } } diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeBalancesRequest.cs b/CryptoExchange.Net/SharedApis/V2/Balances/SubscribeBalances/SubscribeBalancesRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeBalancesRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Balances/SubscribeBalances/SubscribeBalancesRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositAddresses/GetDepositAddressesOptions.cs b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositAddresses/GetDepositAddressesOptions.cs new file mode 100644 index 00000000..cfa9551b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositAddresses/GetDepositAddressesOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting deposit address + /// + public class GetDepositAddressesOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve deposit addresses for an asset"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Asset, "The asset to retrieve deposit addresses for", "ETH"), + RequestParameterRule.Optional(x => x.Network, "The network to retrieve a deposit address for", "ERC20"), + }; + + /// + /// ctor + /// + public GetDepositAddressesOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetDepositAddressesRest.GetDepositAddressesAsync), _defaultParameterRules) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetDepositAddressesRequest.cs b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositAddresses/GetDepositAddressesRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetDepositAddressesRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositAddresses/GetDepositAddressesRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositAddresses/IGetDepositAddresses.cs b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositAddresses/IGetDepositAddresses.cs new file mode 100644 index 00000000..2a4ce693 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositAddresses/IGetDepositAddresses.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving user deposit addresses for an exchange. + /// + public interface IGetDepositAddresses : ISharedApiCapability + { + /// + /// Deposit addresses request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetDepositAddressesOptions GetDepositAddressesOptions { get; } + + /// + /// Get deposit addresses for an asset, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> GetDepositAddressesAsync(GetDepositAddressesRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving user deposit addresses for an exchange via the REST API. + /// + public interface IGetDepositAddressesRest : IGetDepositAddresses, ISharedRest + { + /// + new Task> GetDepositAddressesAsync(GetDepositAddressesRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositsOptions.cs b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositHistory/GetDepositHistoryOptions.cs similarity index 56% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositsOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositHistory/GetDepositHistoryOptions.cs index 11d5f20f..969d998a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositsOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositHistory/GetDepositHistoryOptions.cs @@ -7,22 +7,35 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting deposits /// - public class GetDepositsOptions : PaginatedEndpointOptions + public class GetDepositHistoryOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve deposit history"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Asset, "Filter the result set by asset", "ETH"), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// - public GetDepositsOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IDepositRestClient.GetDepositsAsync)) + public GetDepositHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetDepositHistoryRest.GetDepositHistoryAsync), _defaultParameterRules) { } /// - public override Error? ValidateRequest(GetDepositsRequest request, IDepositRestClient client) + public override Error? ValidateRequest(GetDepositsRequest request, IGetDepositHistoryRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!SupportsAscending && request.Direction == DataDirection.Ascending) return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); @@ -43,7 +56,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetDepositsRequest.cs b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositHistory/GetDepositsRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetDepositsRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositHistory/GetDepositsRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositHistory/IGetDepositHistory.cs b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositHistory/IGetDepositHistory.cs new file mode 100644 index 00000000..c78e1f7b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Deposits/GetDepositHistory/IGetDepositHistory.cs @@ -0,0 +1,41 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving user deposit history from an exchange. + /// + public interface IGetDepositHistory : ISharedApiCapability + { + /// + /// Deposits request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetDepositHistoryOptions GetDepositHistoryOptions { get; } + + /// + /// Get deposit records, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + /// + Task> GetDepositHistoryAsync(GetDepositsRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving user deposit history from an exchange via the REST API. + /// + public interface IGetDepositHistoryRest : IGetDepositHistory, ISharedRest + { + /// + new Task> GetDepositHistoryAsync(GetDepositsRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFeeOptions.cs b/CryptoExchange.Net/SharedApis/V2/Fees/GetFees/GetFeeOptions.cs similarity index 50% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFeeOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Fees/GetFees/GetFeeOptions.cs index b312b1db..a6ea7157 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFeeOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Fees/GetFees/GetFeeOptions.cs @@ -7,15 +7,20 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting trading fee info /// - public class GetFeeOptions : EndpointOptions + public class GetFeeOptions : CapabilityOptions { /// public override string Description => "Retrieve trading fee information"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve trading fees for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + }; + /// /// ctor /// - public GetFeeOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFeeRestClient.GetFeesAsync)) + public GetFeeOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetFeesRest.GetFeesAsync), _defaultParameterRules) { } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetFeeRequest.cs b/CryptoExchange.Net/SharedApis/V2/Fees/GetFees/GetFeeRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetFeeRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Fees/GetFees/GetFeeRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Fees/GetFees/IGetFees.cs b/CryptoExchange.Net/SharedApis/V2/Fees/GetFees/IGetFees.cs new file mode 100644 index 00000000..e213c1cd --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Fees/GetFees/IGetFees.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving user trading fees from an exchange. + /// + public interface IGetFees : ISharedApiCapability + { + /// + /// Fee request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFeeOptions GetFeeOptions { get; } + + /// + /// Get trading fees for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetFeesAsync(GetFeeRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving user trading fees from an exchange via the REST API. + /// + public interface IGetFeesRest : IGetFees, ISharedRest + { + /// + new Task> GetFeesAsync(GetFeeRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/GetFundingInfoOptions.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/GetFundingInfoOptions.cs new file mode 100644 index 00000000..9aa826bb --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/GetFundingInfoOptions.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting funding info + /// + public class GetFundingInfoOptions : PaginatedCapabilityOptions + { + /// + public override string Description => "Retrieve the current funding info for a symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetFundingInfoOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetFundingInfoRest.GetFundingInfoAsync), _defaultParameterRules, SharedTradingModeSets.Perpetual) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/GetFundingInfoRequest.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/GetFundingInfoRequest.cs new file mode 100644 index 00000000..fb8f7811 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/GetFundingInfoRequest.cs @@ -0,0 +1,22 @@ +using System; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve the current funding info for a symbol + /// + public record GetFundingInfoRequest : SharedSymbolRequest + { + /// + /// ctor + /// + /// Symbol to request funding info for + /// Exchange specific parameters + public GetFundingInfoRequest( + SharedSymbol symbol, + ExchangeParameters? exchangeParameters = null) + : base(symbol, exchangeParameters) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/IGetFundingInfo.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/IGetFundingInfo.cs new file mode 100644 index 00000000..8cd82b17 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingInfo/IGetFundingInfo.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving the current funding info for a symbol on an exchange. + /// + public interface IGetFundingInfo : ISharedApiCapability + { + /// + /// Funding info request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFundingInfoOptions GetFundingInfoOptions { get; } + /// + /// Get current funding info for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetFundingInfoAsync(GetFundingInfoRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving the current funding info for a symbol on an exchange via the REST API. + /// + public interface IGetFundingInfoRest : IGetFundingInfo, ISharedRest + { + /// + new Task> GetFundingInfoAsync(GetFundingInfoRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFundingRateHistoryOptions.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/GetFundingRateHistoryOptions.cs similarity index 59% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFundingRateHistoryOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/GetFundingRateHistoryOptions.cs index 5e8f17f1..83b67964 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFundingRateHistoryOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/GetFundingRateHistoryOptions.cs @@ -7,22 +7,35 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting funding rate history /// - public class GetFundingRateHistoryOptions : PaginatedEndpointOptions + public class GetFundingRateHistoryOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve historical funding rates for a futures symbol"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve funding rate history for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// public GetFundingRateHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IFundingRateRestClient.GetFundingRateHistoryAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetFundingRateHistoryRest.GetFundingRateHistoryAsync), _defaultParameterRules, SharedTradingModeSets.Perpetual) { } /// - public override Error? ValidateRequest(GetFundingRateHistoryRequest request, IFundingRateRestClient client) + public override Error? ValidateRequest(GetFundingRateHistoryRequest request, IGetFundingRateHistoryRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!SupportsAscending && request.Direction == DataDirection.Ascending) return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); @@ -43,7 +56,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetFundingRateHistoryRequest.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/GetFundingRateHistoryRequest.cs similarity index 94% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetFundingRateHistoryRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/GetFundingRateHistoryRequest.cs index 6f9ad50f..c8b6c69f 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetFundingRateHistoryRequest.cs +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/GetFundingRateHistoryRequest.cs @@ -27,7 +27,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - /// Symbol to request klines for + /// Symbol to request funding rate history for /// Filter by start time /// Filter by end time /// Max number of results diff --git a/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/IGetFundingRateHistory.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/IGetFundingRateHistory.cs new file mode 100644 index 00000000..8e357ea9 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetFundingRateHistory/IGetFundingRateHistory.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving funding rate history for a symbol on an exchange. + /// + public interface IGetFundingRateHistory : ISharedApiCapability + { + /// + /// Funding rate request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFundingRateHistoryOptions GetFundingRateHistoryOptions { get; } + /// + /// Get funding rate records, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetFundingRateHistoryAsync(GetFundingRateHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving funding rate history for a symbol on an exchange via the REST API. + /// + public interface IGetFundingRateHistoryRest : IGetFundingRateHistory, ISharedRest + { + /// + new Task> GetFundingRateHistoryAsync(GetFundingRateHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/GetUserFundingHistoryOptions.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/GetUserFundingHistoryOptions.cs new file mode 100644 index 00000000..685fbfce --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/GetUserFundingHistoryOptions.cs @@ -0,0 +1,62 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting funding rate history + /// + public class GetUserFundingHistoryOptions : PaginatedCapabilityOptions + { + /// + public override string Description => "Retrieve funding fee payments for the user"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "Filter by symbol", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + + /// + /// ctor + /// + public GetUserFundingHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetUserFundingHistoryRest.GetUserFundingHistoryAsync), _defaultParameterRules, SharedTradingModeSets.Perpetual) + { + } + + /// + public override Error? ValidateRequest(GetUserFundingHistoryRequest request, IGetUserFundingHistoryRest client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetUserFundingHistoryRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetUserFundingHistoryRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetUserFundingHistoryRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetUserFundingHistoryRequest.StartTime), $"Time filter is not supported"); + } + } + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/GetUserFundingHistoryRequest.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/GetUserFundingHistoryRequest.cs new file mode 100644 index 00000000..2deb55f1 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/GetUserFundingHistoryRequest.cs @@ -0,0 +1,56 @@ +using System; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve user funding fee payment history + /// + public record GetUserFundingHistoryRequest : SharedRequest + { + /// + /// Filter be symbol + /// + public SharedSymbol? Symbol { get; set; } + /// + /// Filter by start time + /// + public DateTime? StartTime { get; set; } + /// + /// Filter by end time + /// + public DateTime? EndTime { get; set; } + /// + /// Max number of results + /// + public int? Limit { get; set; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } + + /// + /// ctor + /// + /// Symbol to request funding history + /// Filter by start time + /// Filter by end time + /// Max number of results + /// Data direction + /// Exchange specific parameters + public GetUserFundingHistoryRequest( + SharedSymbol? symbol = null, + DateTime? startTime = null, + DateTime? endTime = null, + int? limit = null, + DataDirection? direction = null, + ExchangeParameters? exchangeParameters = null) + : base(null, exchangeParameters) + { + Symbol = symbol; + StartTime = startTime; + EndTime = endTime; + Limit = limit; + Direction = direction; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/IGetUserFundingHistory.cs b/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/IGetUserFundingHistory.cs new file mode 100644 index 00000000..4dbc059c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Funding/GetUserFundingHistory/IGetUserFundingHistory.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving funding history for a user on an exchange. + /// + public interface IGetUserFundingHistory : ISharedApiCapability + { + /// + /// Funding history request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetUserFundingHistoryOptions GetUserFundingHistoryOptions { get; } + /// + /// Get funding history records, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetUserFundingHistoryAsync(GetUserFundingHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving funding history for a user on an exchange via the REST API. + /// + public interface IGetUserFundingHistoryRest : IGetUserFundingHistory, ISharedRest + { + /// + new Task> GetUserFundingHistoryAsync(GetUserFundingHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Interfaces/ISharedClient.cs b/CryptoExchange.Net/SharedApis/V2/ISharedApi.cs similarity index 77% rename from CryptoExchange.Net/SharedApis/Interfaces/ISharedClient.cs rename to CryptoExchange.Net/SharedApis/V2/ISharedApi.cs index 67bc3c4c..d32c7de9 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/ISharedClient.cs +++ b/CryptoExchange.Net/SharedApis/V2/ISharedApi.cs @@ -1,11 +1,15 @@ -using System; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.RateLimiting; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; namespace CryptoExchange.Net.SharedApis { /// - /// A shared/common client interface + /// Shared API /// - public interface ISharedClient + public interface ISharedApi : IRateLimitAdmissionClient { /// /// Name of the exchange @@ -22,6 +26,16 @@ namespace CryptoExchange.Net.SharedApis /// bool Authenticated { get; } + /// + /// The underlying transport type used by this client + /// + SharedTransport Transport { get; } + + /// + /// Capabilities and operations exposed by this API + /// + IReadOnlyCollection Capabilities { get; } + /// /// Get info on the client and supported features /// diff --git a/CryptoExchange.Net/SharedApis/V2/ISharedApiCapability.cs b/CryptoExchange.Net/SharedApis/V2/ISharedApiCapability.cs new file mode 100644 index 00000000..81f02562 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/ISharedApiCapability.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Shared API capability + /// + public interface ISharedApiCapability : ISharedApi + { + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/ISharedRest.cs b/CryptoExchange.Net/SharedApis/V2/ISharedRest.cs new file mode 100644 index 00000000..d440c7f4 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/ISharedRest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Shared REST API capabitlity + /// + public interface ISharedRest : ISharedApiCapability + { + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/ISharedSocket.cs b/CryptoExchange.Net/SharedApis/V2/ISharedSocket.cs new file mode 100644 index 00000000..48e50272 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/ISharedSocket.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Shared WebSocket API capabitlity + /// + public interface ISharedSocket : ISharedApiCapability + { + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/ISharedSubscription.cs b/CryptoExchange.Net/SharedApis/V2/ISharedSubscription.cs new file mode 100644 index 00000000..1d6cd387 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/ISharedSubscription.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Shared WebSocket API subscription capabitlity + /// + public interface ISharedSubscription : ISharedSocket + { + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/GetAllIndexPricesOptions.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/GetAllIndexPricesOptions.cs new file mode 100644 index 00000000..0dd75a36 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/GetAllIndexPricesOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting the index price + /// + public class GetAllIndexPricesOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve the index prices for all futures symbols"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the index prices by trading mode", TradingMode.PerpetualLinear), + }; + + /// + /// ctor + /// + public GetAllIndexPricesOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetAllIndexPricesRest.GetAllIndexPricesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/GetAllIndexPricesRequest.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/GetAllIndexPricesRequest.cs new file mode 100644 index 00000000..b05c17f0 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/GetAllIndexPricesRequest.cs @@ -0,0 +1,17 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve the current index prices for all symbols + /// + public record GetAllIndexPricesRequest : SharedRequest + { + /// + /// ctor + /// + /// Trading mode + /// Exchange specific parameters + public GetAllIndexPricesRequest(TradingMode? tradingMode = null, ExchangeParameters? exchangeParameters = null) : base(tradingMode, exchangeParameters) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/IGetAllIndexPrices.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/IGetAllIndexPrices.cs new file mode 100644 index 00000000..64054eac --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetAllIndexPrices/IGetAllIndexPrices.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving the index price for all symbols from an exchange. + /// + public interface IGetAllIndexPrices : ISharedApiCapability + { + /// + /// Index price request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetAllIndexPricesOptions GetAllIndexPricesOptions { get; } + /// + /// Get the index price for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetAllIndexPricesAsync(GetAllIndexPricesRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving the index price for all symbols from an exchange via the REST API. + /// + public interface IGetAllIndexPricesRest : IGetAllIndexPrices, ISharedRest + { + /// + new Task> GetAllIndexPricesAsync(GetAllIndexPricesRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/GetIndexPriceOptions.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/GetIndexPriceOptions.cs new file mode 100644 index 00000000..f56546be --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/GetIndexPriceOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting the index price + /// + public class GetIndexPriceOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve the index price for a futures symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve the index price for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetIndexPriceOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetIndexPriceRest.GetIndexPriceAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/GetIndexPriceRequest.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/GetIndexPriceRequest.cs new file mode 100644 index 00000000..075e5725 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/GetIndexPriceRequest.cs @@ -0,0 +1,17 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve the current index price for a symbol + /// + public record GetIndexPriceRequest : SharedSymbolRequest + { + /// + /// ctor + /// + /// Symbol to retrieve the index price for + /// Exchange specific parameters + public GetIndexPriceRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/IGetIndexPrice.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/IGetIndexPrice.cs new file mode 100644 index 00000000..392940aa --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/GetIndexPrice/IGetIndexPrice.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving the index price for a symbol from an exchange. + /// + public interface IGetIndexPrice : ISharedApiCapability + { + /// + /// Index price request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetIndexPriceOptions GetIndexPriceOptions { get; } + /// + /// Get the index price for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetIndexPriceAsync(GetIndexPriceRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving the index price for a symbol from an exchange via the REST API. + /// + public interface IGetIndexPriceRest : IGetIndexPrice, ISharedRest + { + /// + new Task> GetIndexPriceAsync(GetIndexPriceRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/ISubscribeIndexPriceSocket.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/ISubscribeIndexPriceSocket.cs new file mode 100644 index 00000000..a0c8bd8c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/ISubscribeIndexPriceSocket.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to user Index price updates + /// + public interface ISubscribeIndexPriceSocket : ISharedSubscription + { + /// + /// Index price subscription options + /// + SubscribeIndexPriceOptions SubscribeIndexPriceOptions { get; } + + /// + /// Subscribe to user Index price updates + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToIndexPriceUpdatesAsync(SubscribeIndexPriceRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/SubscribeIndexPriceOptions.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/SubscribeIndexPriceOptions.cs new file mode 100644 index 00000000..f3c28940 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/SubscribeIndexPriceOptions.cs @@ -0,0 +1,29 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for subscribing to Index price updates + /// + public class SubscribeIndexPriceOptions : CapabilityOptions + { + /// + public override string Description => "Subscribe to Index price updates for a symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT") }), + }; + + /// + /// ctor + /// + public SubscribeIndexPriceOptions(string exchange, bool needsAuthentication) + : base(exchange, needsAuthentication, nameof(ISubscribeIndexPriceSocket.SubscribeToIndexPriceUpdatesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/SubscribeIndexPriceRequest.cs b/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/SubscribeIndexPriceRequest.cs new file mode 100644 index 00000000..c1698622 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/IndexPrice/SubscribeIndexPrice/SubscribeIndexPriceRequest.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to subscribe to Index price updates for a symbol + /// + public record SubscribeIndexPriceRequest : SharedSymbolRequest + { + /// + /// ctor + /// + /// The symbol to subscribe to + /// Exchange specific parameters + public SubscribeIndexPriceRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) + : base(symbol, exchangeParameters) + { + } + + /// + /// ctor + /// + /// The symbols to subscribe to + /// Exchange specific parameters + public SubscribeIndexPriceRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) + : base(symbols, exchangeParameters) + { + } + + /// + /// ctor + /// + /// The symbols to subscribe to + [Obsolete("Use SubscribeIndexPriceRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) instead")] + public SubscribeIndexPriceRequest(params SharedSymbol[] symbols) : base(symbols, null) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetIndexPriceKlinesOptions.cs b/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetIndexPriceKlines/GetIndexPriceKlinesOptions.cs similarity index 77% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetIndexPriceKlinesOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetIndexPriceKlines/GetIndexPriceKlinesOptions.cs index f7df897b..558466bd 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetIndexPriceKlinesOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetIndexPriceKlines/GetIndexPriceKlinesOptions.cs @@ -8,11 +8,21 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting kline/candlestick data /// - public class GetIndexPriceKlinesOptions : PaginatedEndpointOptions + public class GetIndexPriceKlinesOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve index price candlestick data"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve klines for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.Interval, "The kline interval", SharedKlineInterval.OneMinute), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// The supported kline intervals /// @@ -26,7 +36,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetIndexPriceKlinesOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IIndexPriceKlineRestClient.GetIndexPriceKlinesAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetIndexPriceKlinesRest.GetIndexPriceKlinesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { SupportIntervals = new[] { @@ -51,7 +61,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetIndexPriceKlinesOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication, params SharedKlineInterval[] intervals) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IIndexPriceKlineRestClient.GetIndexPriceKlinesAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IIndexPriceKlineRestClient.GetIndexPriceKlinesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { SupportIntervals = intervals; } @@ -64,8 +74,12 @@ namespace CryptoExchange.Net.SharedApis public bool IsSupported(SharedKlineInterval interval) => SupportIntervals.Contains(interval); /// - public override Error? ValidateRequest(GetKlinesRequest request, IIndexPriceKlineRestClient client) + public override Error? ValidateRequest(GetKlinesRequest request, IGetIndexPriceKlinesRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!IsSupported(request.Interval)) return ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), $"Interval {request.Interval} not supported"); @@ -104,7 +118,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } /// diff --git a/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetIndexPriceKlines/IGetIndexPriceKlines.cs b/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetIndexPriceKlines/IGetIndexPriceKlines.cs new file mode 100644 index 00000000..78ad683f --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetIndexPriceKlines/IGetIndexPriceKlines.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving index price kline/candlestick data from an exchange + /// + public interface IGetIndexPriceKlines : ISharedApiCapability + { + /// + /// Index price klines request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetIndexPriceKlinesOptions GetIndexPriceKlinesOptions { get; } + /// + /// Get index price kline/candlestick data, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetIndexPriceKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving index price kline/candlestick data from an exchange via the REST API. + /// + public interface IGetIndexPriceKlinesRest : IGetIndexPriceKlines, ISharedRest + { + /// + new Task> GetIndexPriceKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetMarkPriceKlinesOptions.cs b/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetMarkPriceKlines/GetMarkPriceKlinesOptions.cs similarity index 77% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetMarkPriceKlinesOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetMarkPriceKlines/GetMarkPriceKlinesOptions.cs index 148ebc95..3cb46f7a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetMarkPriceKlinesOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetMarkPriceKlines/GetMarkPriceKlinesOptions.cs @@ -8,11 +8,21 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting kline/candlestick data /// - public class GetMarkPriceKlinesOptions : PaginatedEndpointOptions + public class GetMarkPriceKlinesOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve mark price candlestick data"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve klines for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.Interval, "The kline interval", SharedKlineInterval.OneMinute), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// The supported kline intervals /// @@ -26,7 +36,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetMarkPriceKlinesOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IMarkPriceKlineRestClient.GetMarkPriceKlinesAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetMarkPriceKlinesRest.GetMarkPriceKlinesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { SupportIntervals = new[] { @@ -51,7 +61,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetMarkPriceKlinesOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication, params SharedKlineInterval[] intervals) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IMarkPriceKlineRestClient.GetMarkPriceKlinesAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetMarkPriceKlinesRest.GetMarkPriceKlinesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { SupportIntervals = intervals; } @@ -64,8 +74,12 @@ namespace CryptoExchange.Net.SharedApis public bool IsSupported(SharedKlineInterval interval) => SupportIntervals.Contains(interval); /// - public override Error? ValidateRequest(GetKlinesRequest request, IMarkPriceKlineRestClient client) + public override Error? ValidateRequest(GetKlinesRequest request, IGetMarkPriceKlinesRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!IsSupported(request.Interval)) return ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), $"Interval {request.Interval} not supported"); @@ -104,7 +118,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } /// diff --git a/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetMarkPriceKlines/IGetMarkPriceKlines.cs b/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetMarkPriceKlines/IGetMarkPriceKlines.cs new file mode 100644 index 00000000..4e7e703b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Klines/Futures/GetMarkPriceKlines/IGetMarkPriceKlines.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving mark price kline/candlestick data from an exchange. + /// + public interface IGetMarkPriceKlines : ISharedApiCapability + { + /// + /// Mark price klines request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetMarkPriceKlinesOptions GetMarkPriceKlinesOptions { get; } + /// + /// Get mark price kline/candlestick data, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetMarkPriceKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving mark price kline/candlestick data from an exchange via the REST API. + /// + public interface IGetMarkPriceKlinesRest : IGetMarkPriceKlines, ISharedRest + { + /// + new Task> GetMarkPriceKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetKlinesOptions.cs b/CryptoExchange.Net/SharedApis/V2/Klines/GetKlines/GetKlinesOptions.cs similarity index 79% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetKlinesOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Klines/GetKlines/GetKlinesOptions.cs index 0f17350f..00e615c5 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetKlinesOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Klines/GetKlines/GetKlinesOptions.cs @@ -8,11 +8,21 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting kline/candlestick data /// - public class GetKlinesOptions : PaginatedEndpointOptions + public class GetKlinesOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve candlestick data for a symbol"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve klines for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.Interval, "The kline interval", SharedKlineInterval.OneMinute), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// The supported kline intervals /// @@ -26,7 +36,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetKlinesOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IKlineRestClient.GetKlinesAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetKlinesRest.GetKlinesAsync), _defaultParameterRules) { SupportIntervals = new[] { @@ -51,7 +61,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetKlinesOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication, params SharedKlineInterval[] intervals) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IKlineRestClient.GetKlinesAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetKlinesRest.GetKlinesAsync), _defaultParameterRules) { SupportIntervals = intervals; } @@ -64,8 +74,12 @@ namespace CryptoExchange.Net.SharedApis public bool IsSupported(SharedKlineInterval interval) => SupportIntervals.Contains(interval); /// - public override Error? ValidateRequest(GetKlinesRequest request, IKlineRestClient client) + public override Error? ValidateRequest(GetKlinesRequest request, IGetKlinesRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!IsSupported(request.Interval)) return ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), $"Interval {request.Interval} not supported"); @@ -104,7 +118,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } /// diff --git a/CryptoExchange.Net/SharedApis/V2/Klines/GetKlines/IGetKlines.cs b/CryptoExchange.Net/SharedApis/V2/Klines/GetKlines/IGetKlines.cs new file mode 100644 index 00000000..7e5bcf60 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Klines/GetKlines/IGetKlines.cs @@ -0,0 +1,41 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for getting kline/candlestick data from an exchange. + /// + public interface IGetKlines : ISharedApiCapability + { + /// + /// Kline request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetKlinesOptions GetKlinesOptions { get; } + + /// + /// Get kline/candlestick data, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + /// + Task> GetKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for getting kline/candlestick data from an exchange via the REST API. + /// + public interface IGetKlinesRest : IGetKlines, ISharedRest + { + /// + new Task> GetKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetKlinesRequest.cs b/CryptoExchange.Net/SharedApis/V2/Klines/GetKlinesRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetKlinesRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Klines/GetKlinesRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/ISubscribeKlines.cs b/CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/ISubscribeKlines.cs new file mode 100644 index 00000000..819db4f8 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/ISubscribeKlines.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using System.Threading; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Objects; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to kline/candlestick updates for a symbol + /// + public interface ISubscribeKlinesSocket : ISharedSubscription + { + /// + /// Kline subscription options + /// + SubscribeKlineOptions SubscribeKlineOptions { get; } + + /// + /// Subscribe to kline/candlestick updates for a symbol + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToKlineUpdatesAsync(SubscribeKlineRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeKlineOptions.cs b/CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/SubscribeKlineOptions.cs similarity index 65% rename from CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeKlineOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/SubscribeKlineOptions.cs index 812b50d9..35b486cf 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeKlineOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/SubscribeKlineOptions.cs @@ -7,11 +7,18 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for subscribing to kline/candlestick updates /// - public class SubscribeKlineOptions : EndpointOptions + public class SubscribeKlineOptions : CapabilityOptions { /// public override string Description => "Subscribe to candlestick updates for a symbol"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.Spot, "ETH", "USDT") }), + RequestParameterRule.Required(x => x.Interval, "The kline interval", SharedKlineInterval.OneMinute), + }; + /// /// Kline intervals supported for updates /// @@ -20,7 +27,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public SubscribeKlineOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(IKlineSocketClient.SubscribeToKlineUpdatesAsync)) + public SubscribeKlineOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ISubscribeKlinesSocket.SubscribeToKlineUpdatesAsync), _defaultParameterRules) { SupportIntervals = new[] { @@ -45,7 +52,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public SubscribeKlineOptions(string exchange, bool needsAuthentication, params SharedKlineInterval[] intervals) - : base(exchange, needsAuthentication, nameof(IKlineSocketClient.SubscribeToKlineUpdatesAsync)) + : base(exchange, needsAuthentication, nameof(ISubscribeKlinesSocket.SubscribeToKlineUpdatesAsync), _defaultParameterRules) { SupportIntervals = intervals; } @@ -60,12 +67,16 @@ namespace CryptoExchange.Net.SharedApis /// /// Validate a request /// - public override Error? ValidateRequest(SubscribeKlineRequest request, IKlineSocketClient client) + public override Error? ValidateRequest(SubscribeKlineRequest request, ISubscribeKlinesSocket client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!IsSupported(request.Interval)) return ArgumentError.Invalid(nameof(SubscribeKlineRequest.Interval), "Interval not supported"); - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeKlineRequest.cs b/CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/SubscribeKlineRequest.cs similarity index 90% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeKlineRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/SubscribeKlineRequest.cs index ed207e7e..ff086d8f 100644 --- a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeKlineRequest.cs +++ b/CryptoExchange.Net/SharedApis/V2/Klines/SubscribeKlines/SubscribeKlineRequest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis { @@ -41,6 +42,7 @@ namespace CryptoExchange.Net.SharedApis ///
/// Kline interval /// The symbols to subscribe to + [Obsolete("Use SubscribeKlineRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) instead")] public SubscribeKlineRequest(SharedKlineInterval interval, params SharedSymbol[] symbols) : base(symbols, null) { Interval = interval; diff --git a/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/GetLedgerOptions.cs b/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/GetLedgerOptions.cs new file mode 100644 index 00000000..16b3db52 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/GetLedgerOptions.cs @@ -0,0 +1,62 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting account ledger + /// + public class GetLedgerOptions : PaginatedCapabilityOptions + { + /// + public override string Description => "Retrieve the account ledger"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Asset, "Filter the result set by asset", "ETH"), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + + /// + /// ctor + /// + public GetLedgerOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetLedgerRest.GetLedgerAsync), _defaultParameterRules) + { + } + + /// + public override Error? ValidateRequest(GetLedgerRequest request, IGetLedgerRest client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetLedgerRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetLedgerRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetLedgerRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetLedgerRequest.StartTime), $"Time filter is not supported"); + } + } + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/GetLedgerRequest.cs b/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/GetLedgerRequest.cs new file mode 100644 index 00000000..3734092f --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/GetLedgerRequest.cs @@ -0,0 +1,55 @@ +using System; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve the account ledger of the user + /// + public record GetLedgerRequest : SharedRequest + { + /// + /// Filter by asset + /// + public string? Asset { get; set; } + /// + /// Filter by start time + /// + public DateTime? StartTime { get; } + /// + /// Filter by end time + /// + public DateTime? EndTime { get; } + /// + /// Max number of results + /// + public int? Limit { get; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } + + /// + /// ctor + /// + /// Filter by asset + /// Filter by start time + /// Filter by end time + /// Max number of results + /// Data direction + /// Exchange specific parameters + public GetLedgerRequest( + string? asset = null, + DateTime? startTime = null, + DateTime? endTime = null, + int? limit = null, + DataDirection? direction = null, + ExchangeParameters? exchangeParameters = null) : base(null, exchangeParameters) + { + Asset = asset; + StartTime = startTime; + EndTime = endTime; + Limit = limit; + Direction = direction; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/IGetLedger.cs b/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/IGetLedger.cs new file mode 100644 index 00000000..15358ff0 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Ledger/GetLedger/IGetLedger.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving the account ledger from an exchange. + /// + public interface IGetLedger : ISharedApiCapability + { + /// + /// Ledger request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetLedgerOptions GetLedgerOptions { get; } + /// + /// Get ledger entries, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetLedgerAsync(GetLedgerRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving the account ledger from an exchange via the REST API. + /// + public interface IGetLedgerRest : IGetLedger, ISharedRest + { + /// + new Task> GetLedgerAsync(GetLedgerRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverage/GetLeverageOptions.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverage/GetLeverageOptions.cs new file mode 100644 index 00000000..fd7b5a0d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverage/GetLeverageOptions.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting leverage info + /// + public class GetLeverageOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve the current leverage for a futures symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve leverage for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.PositionSide, "The position side to retrieve leverage for", SharedPositionSide.Long), + RequestParameterRule.Optional(x => x.MarginMode, "The margin mode to retrieve leverage for", SharedMarginMode.Cross), + }; + + /// + /// ctor + /// + public GetLeverageOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetLeverageRest.GetLeverageAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetLeverageRequest.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverage/GetLeverageRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetLeverageRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverage/GetLeverageRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverage/IGetLeverage.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverage/IGetLeverage.cs new file mode 100644 index 00000000..802e643c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverage/IGetLeverage.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving leverage information for a symbol on an exchange. + /// + public interface IGetLeverage : ISharedApiCapability + { + /// + /// Leverage request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetLeverageOptions GetLeverageOptions { get; } + /// + /// Get the current leverage setting for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetLeverageAsync(GetLeverageRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving leverage information for a symbol on an exchange via the REST API. + /// + public interface IGetLeverageRest : IGetLeverage, ISharedRest + { + /// + new Task> GetLeverageAsync(GetLeverageRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/GetLeverageTiersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/GetLeverageTiersOptions.cs new file mode 100644 index 00000000..908be90b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/GetLeverageTiersOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting leverage tier info + /// + public class GetLeverageTiersOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve the current leverage tier information for a futures symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve leverage tiers for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetLeverageTiersOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetLeverageTiersRest.GetLeverageTiersAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/GetLeverageTiersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/GetLeverageTiersRequest.cs new file mode 100644 index 00000000..854688c2 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/GetLeverageTiersRequest.cs @@ -0,0 +1,17 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve the leverage tier information for a symbol + /// + public record GetLeverageTiersRequest : SharedSymbolRequest + { + /// + /// ctor + /// + /// Symbol to request leverage for + /// Exchange specific parameters + public GetLeverageTiersRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/IGetLeverageTiers.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/IGetLeverageTiers.cs new file mode 100644 index 00000000..068848dd --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Leverage/GetLeverageTiers/IGetLeverageTiers.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving leverage tier information for a symbol on an exchange. + /// + public interface IGetLeverageTiers : ISharedApiCapability + { + /// + /// Leverage tier request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetLeverageTiersOptions GetLeverageTiersOptions { get; } + /// + /// Get the leverage tiers for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetLeverageTiersAsync(GetLeverageTiersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving leverage tier information for a symbol on an exchange via the REST API. + /// + public interface IGetLeverageTiersRest : IGetLeverageTiers, ISharedRest + { + /// + new Task> GetLeverageTiersAsync(GetLeverageTiersRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Leverage/SetLeverage/ISetLeverage.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/SetLeverage/ISetLeverage.cs new file mode 100644 index 00000000..0aa83df6 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Leverage/SetLeverage/ISetLeverage.cs @@ -0,0 +1,42 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for setting leverage on an exchange. + /// + public interface ISetLeverage : ISharedApiCapability + { + /// + /// How the leverage setting is configured on the exchange + /// + SharedLeverageSettingMode LeverageSettingType { get; } + + /// + /// Leverage set request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ SetLeverageOptions SetLeverageOptions { get; } + /// + /// Set the leverage for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> SetLeverageAsync(SetLeverageRequest request, CancellationToken ct = default); + } + + /// + /// Operation for setting leverage on an exchange via the REST API. + /// + public interface ISetLeverageRest : ISetLeverage, ISharedRest + { + /// + new Task> SetLeverageAsync(SetLeverageRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Leverage/SetLeverage/SetLeverageOptions.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/SetLeverage/SetLeverageOptions.cs new file mode 100644 index 00000000..1560cfa6 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Leverage/SetLeverage/SetLeverageOptions.cs @@ -0,0 +1,26 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for setting leverage + /// + public class SetLeverageOptions : CapabilityOptions + { + /// + public override string Description => "Set the leverage for a futures symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to set leverage for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.Leverage, "The leverage to set", 10m), + RequestParameterRule.Optional(x => x.Side, "The position side to set leverage for", SharedPositionSide.Long), + RequestParameterRule.Optional(x => x.MarginMode, "The margin mode to set leverage for", SharedMarginMode.Cross), + }; + + /// + /// ctor + /// + public SetLeverageOptions(string exchange) : base(exchange, true, nameof(ISetLeverageRest.SetLeverageAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/SetLeverageRequest.cs b/CryptoExchange.Net/SharedApis/V2/Leverage/SetLeverage/SetLeverageRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/SetLeverageRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Leverage/SetLeverage/SetLeverageRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/GetAllMarkPricesOptions.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/GetAllMarkPricesOptions.cs new file mode 100644 index 00000000..0bb588ec --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/GetAllMarkPricesOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting the mark price + /// + public class GetAllMarkPricesOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve the mark prices for all futures symbols"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the mark prices by trading mode", TradingMode.PerpetualLinear), + }; + + /// + /// ctor + /// + public GetAllMarkPricesOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetAllMarkPricesRest.GetAllMarkPricesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/GetAllMarkPricesRequest.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/GetAllMarkPricesRequest.cs new file mode 100644 index 00000000..90aa3809 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/GetAllMarkPricesRequest.cs @@ -0,0 +1,17 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve the current mark prices for all symbols + /// + public record GetAllMarkPricesRequest : SharedRequest + { + /// + /// ctor + /// + /// Trading mode + /// Exchange specific parameters + public GetAllMarkPricesRequest(TradingMode? tradingMode = null, ExchangeParameters? exchangeParameters = null) : base(tradingMode, exchangeParameters) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/IGetAllMarkPrices.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/IGetAllMarkPrices.cs new file mode 100644 index 00000000..65276b8b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetAllMarkPrices/IGetAllMarkPrices.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving the mark price for all symbols from an exchange. + /// + public interface IGetAllMarkPrices : ISharedApiCapability + { + /// + /// Mark price request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetAllMarkPricesOptions GetAllMarkPricesOptions { get; } + /// + /// Get the mark price for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetAllMarkPricesAsync(GetAllMarkPricesRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving the mark price for all symbols from an exchange via the REST API. + /// + public interface IGetAllMarkPricesRest : IGetAllMarkPrices, ISharedRest + { + /// + new Task> GetAllMarkPricesAsync(GetAllMarkPricesRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/GetMarkPriceOptions.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/GetMarkPriceOptions.cs new file mode 100644 index 00000000..3929131c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/GetMarkPriceOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting the mark price + /// + public class GetMarkPriceOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve the mark price for a futures symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve the mark price for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetMarkPriceOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetMarkPriceRest.GetMarkPriceAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/GetMarkPriceRequest.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/GetMarkPriceRequest.cs new file mode 100644 index 00000000..32fe19c4 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/GetMarkPriceRequest.cs @@ -0,0 +1,17 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve the current mark price for a symbol + /// + public record GetMarkPriceRequest : SharedSymbolRequest + { + /// + /// ctor + /// + /// Symbol to retrieve the mark price for + /// Exchange specific parameters + public GetMarkPriceRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/IGetMarkPrice.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/IGetMarkPrice.cs new file mode 100644 index 00000000..955cd641 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/GetMarkPrice/IGetMarkPrice.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving the mark price for a symbol from an exchange. + /// + public interface IGetMarkPrice : ISharedApiCapability + { + /// + /// Mark price request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetMarkPriceOptions GetMarkPriceOptions { get; } + /// + /// Get the mark price for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetMarkPriceAsync(GetMarkPriceRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving the mark price for a symbol from an exchange via the REST API. + /// + public interface IGetMarkPriceRest : IGetMarkPrice, ISharedRest + { + /// + new Task> GetMarkPriceAsync(GetMarkPriceRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/ISubscribeMarkPriceSocket.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/ISubscribeMarkPriceSocket.cs new file mode 100644 index 00000000..6d69bd66 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/ISubscribeMarkPriceSocket.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to user mark price updates + /// + public interface ISubscribeMarkPriceSocket : ISharedSubscription + { + /// + /// Mark price subscription options + /// + SubscribeMarkPriceOptions SubscribeMarkPriceOptions { get; } + + /// + /// Subscribe to user mark price updates + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToMarkPriceUpdatesAsync(SubscribeMarkPriceRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/SubscribeMarkPriceOptions.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/SubscribeMarkPriceOptions.cs new file mode 100644 index 00000000..fe6cd5dc --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/SubscribeMarkPriceOptions.cs @@ -0,0 +1,29 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for subscribing to mark price updates + /// + public class SubscribeMarkPriceOptions : CapabilityOptions + { + /// + public override string Description => "Subscribe to mark price updates for a symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT") }), + }; + + /// + /// ctor + /// + public SubscribeMarkPriceOptions(string exchange, bool needsAuthentication) + : base(exchange, needsAuthentication, nameof(ISubscribeMarkPriceSocket.SubscribeToMarkPriceUpdatesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/SubscribeMarkPriceRequest.cs b/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/SubscribeMarkPriceRequest.cs new file mode 100644 index 00000000..80f5b5b1 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/MarkPrice/SubscribeMarkPrice/SubscribeMarkPriceRequest.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to subscribe to mark price updates for a symbol + /// + public record SubscribeMarkPriceRequest : SharedSymbolRequest + { + /// + /// ctor + /// + /// The symbol to subscribe to + /// Exchange specific parameters + public SubscribeMarkPriceRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) + : base(symbol, exchangeParameters) + { + } + + /// + /// ctor + /// + /// The symbols to subscribe to + /// Exchange specific parameters + public SubscribeMarkPriceRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) + : base(symbols, exchangeParameters) + { + } + + /// + /// ctor + /// + /// The symbols to subscribe to + [Obsolete("Use SubscribeMarkPriceRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) instead")] + public SubscribeMarkPriceRequest(params SharedSymbol[] symbols) : base(symbols, null) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/OpenInterest/GetOpenInterestOptions.cs b/CryptoExchange.Net/SharedApis/V2/OpenInterest/GetOpenInterestOptions.cs new file mode 100644 index 00000000..a46a9bf9 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/OpenInterest/GetOpenInterestOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting open interest + /// + public class GetOpenInterestOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve open interest for a futures symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve open interest for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetOpenInterestOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetOpenInterestRest.GetOpenInterestAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetOpenInterestRequest.cs b/CryptoExchange.Net/SharedApis/V2/OpenInterest/GetOpenInterestRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetOpenInterestRequest.cs rename to CryptoExchange.Net/SharedApis/V2/OpenInterest/GetOpenInterestRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/OpenInterest/IGetOpenInterest.cs b/CryptoExchange.Net/SharedApis/V2/OpenInterest/IGetOpenInterest.cs new file mode 100644 index 00000000..ddbdf089 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/OpenInterest/IGetOpenInterest.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving open interest data from an exchange. + /// + public interface IGetOpenInterest : ISharedApiCapability + { + /// + /// Open interest request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetOpenInterestOptions GetOpenInterestOptions { get; } + /// + /// Get the open interest for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetOpenInterestAsync(GetOpenInterestRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving open interest data from an exchange via the REST API. + /// + public interface IGetOpenInterestRest : IGetOpenInterest, ISharedRest + { + /// + new Task> GetOpenInterestAsync(GetOpenInterestRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetBookTicker/GetBookTickerOptions.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetBookTicker/GetBookTickerOptions.cs new file mode 100644 index 00000000..97982597 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetBookTicker/GetBookTickerOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting book ticker + /// + public class GetBookTickerOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve the best bid and ask price for a symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve the book ticker for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetBookTickerOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetBookTickerRest.GetBookTickerAsync), _defaultParameterRules) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetBookTickerRequest.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetBookTicker/GetBookTickerRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetBookTickerRequest.cs rename to CryptoExchange.Net/SharedApis/V2/OrderBooks/GetBookTicker/GetBookTickerRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetBookTicker/IGetBookTicker.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetBookTicker/IGetBookTicker.cs new file mode 100644 index 00000000..330d4219 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetBookTicker/IGetBookTicker.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving the best bid/ask prices for a symbol on an exchange. + /// + public interface IGetBookTicker : ISharedApiCapability + { + /// + /// Book ticker request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetBookTickerOptions GetBookTickerOptions { get; } + + /// + /// Get the best ask/bid info for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> GetBookTickerAsync(GetBookTickerRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving the best bid/ask prices for a symbol on an exchange via the REST API. + /// + public interface IGetBookTickerRest : IGetBookTicker, ISharedRest + { + /// + new Task> GetBookTickerAsync(GetBookTickerRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOrderBookOptions.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetOrderBook/GetOrderBookOptions.cs similarity index 71% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOrderBookOptions.cs rename to CryptoExchange.Net/SharedApis/V2/OrderBooks/GetOrderBook/GetOrderBookOptions.cs index b720a445..730b3219 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetOrderBookOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetOrderBook/GetOrderBookOptions.cs @@ -8,11 +8,17 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting order book /// - public class GetOrderBookOptions : EndpointOptions + public class GetOrderBookOptions : CapabilityOptions { /// public override string Description => "Retrieve the current order book for a symbol"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve the order book for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Limit, "The maximum order book depth to retrieve", 100), + }; + /// /// Supported order book depths /// @@ -31,7 +37,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetOrderBookOptions(string exchange, int minLimit, int maxLimit, bool authenticated) - : base(exchange, authenticated, nameof(IOrderBookRestClient.GetOrderBookAsync)) + : base(exchange, authenticated, nameof(IGetOrderBookRest.GetOrderBookAsync), _defaultParameterRules) { MinLimit = minLimit; MaxLimit = maxLimit; @@ -41,14 +47,18 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetOrderBookOptions(string exchange, int[] supportedLimits, bool authenticated) - : base(exchange, authenticated, nameof(IOrderBookRestClient.GetOrderBookAsync)) + : base(exchange, authenticated, nameof(IGetOrderBookRest.GetOrderBookAsync), _defaultParameterRules) { SupportedLimits = supportedLimits; } /// - public override Error? ValidateRequest(GetOrderBookRequest request, IOrderBookRestClient client) + public override Error? ValidateRequest(GetOrderBookRequest request, IGetOrderBook client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (request.Limit == null) return base.ValidateRequest(request, client); @@ -61,7 +71,7 @@ namespace CryptoExchange.Net.SharedApis if (SupportedLimits != null && !SupportedLimits.Contains(request.Limit.Value)) return ArgumentError.Invalid(nameof(GetOrderBookRequest.Limit), $"Limit should be one of " + string.Join(", ", SupportedLimits)); - return base.ValidateRequest(request, client); + return null; } /// diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetOrderBookRequest.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetOrderBook/GetOrderBookRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetOrderBookRequest.cs rename to CryptoExchange.Net/SharedApis/V2/OrderBooks/GetOrderBook/GetOrderBookRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetOrderBook/IGetOrderBook.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetOrderBook/IGetOrderBook.cs new file mode 100644 index 00000000..1e3e9bfc --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/GetOrderBook/IGetOrderBook.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving an order book snapshot for a symbol on an exchange. + /// + public interface IGetOrderBook : ISharedApiCapability + { + /// + /// Order book request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetOrderBookOptions GetOrderBookOptions { get; } + + /// + /// Get the order book for a symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> GetOrderBookAsync(GetOrderBookRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving an order book snapshot for a symbol on an exchange via the REST API. + /// + public interface IGetOrderBookRest : IGetOrderBook, ISharedRest + { + /// + new Task> GetOrderBookAsync(GetOrderBookRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeIncrementalOrderBook/ISubscribeIncrementalOrderBook.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeIncrementalOrderBook/ISubscribeIncrementalOrderBook.cs new file mode 100644 index 00000000..d99b2776 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeIncrementalOrderBook/ISubscribeIncrementalOrderBook.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to incremental order book updates for a symbol + /// + public interface ISubscribeIncrementalOrderBookSocket : ISharedSubscription + { + /// + /// Order book subscription options + /// + SubscribeOrderBookOptions SubscribeOrderBookOptions { get; } + + /// + /// Subscribe to incremental order book updates for a symbol + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeIncrementalOrderBook/SubscribeIncrementalOrderBookOptions.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeIncrementalOrderBook/SubscribeIncrementalOrderBookOptions.cs new file mode 100644 index 00000000..579680f0 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeIncrementalOrderBook/SubscribeIncrementalOrderBookOptions.cs @@ -0,0 +1,57 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for subscribing to order book snapshot updates + /// + public class SubscribeIncrementalOrderBookOptions : CapabilityOptions + { + /// + public override string Description => "Subscribe to incremental order book updates for a symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.Spot, "ETH", "USDT") }), + RequestParameterRule.Optional(x => x.Limit, "The order book depth", 100), + }; + + /// + /// Order book depths supported for updates + /// + public int[] SupportedLimits { get; } + + /// + /// The type of updates the subscription produces + /// + public SharedOrderBookSubscriptionType UpdateType { get; } + + /// + /// ctor + /// + public SubscribeIncrementalOrderBookOptions(string exchange, bool needsAuthentication, int[] limits, SharedOrderBookSubscriptionType updateType) + : base(exchange, needsAuthentication, nameof(ISubscribeIncrementalOrderBookSocket.SubscribeToOrderBookUpdatesAsync), _defaultParameterRules) + { + SupportedLimits = limits; + UpdateType = updateType; + } + + /// + /// Validate a request + /// + public override Error? ValidateRequest(SubscribeOrderBookRequest request, ISubscribeIncrementalOrderBookSocket client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Limit != null && !SupportedLimits.Contains(request.Limit.Value)) + return ArgumentError.Invalid(nameof(SubscribeOrderBookRequest.Limit), "Limit not supported"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/ISubscribeOrderBook.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/ISubscribeOrderBook.cs new file mode 100644 index 00000000..f5126935 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/ISubscribeOrderBook.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to order book snapshot updates for a symbol + /// + public interface ISubscribeOrderBookSocket : ISharedSubscription + { + /// + /// Order book subscription options + /// + SubscribeOrderBookOptions SubscribeOrderBookOptions { get; } + + /// + /// Subscribe to order book snapshot updates for a symbol + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeOrderBookOptions.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/SubscribeOrderBookOptions.cs similarity index 52% rename from CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeOrderBookOptions.cs rename to CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/SubscribeOrderBookOptions.cs index af0c9dd0..f600f1df 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeOrderBookOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/SubscribeOrderBookOptions.cs @@ -7,11 +7,18 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for subscribing to order book snapshot updates /// - public class SubscribeOrderBookOptions : EndpointOptions + public class SubscribeOrderBookOptions : CapabilityOptions { /// public override string Description => "Subscribe to order book updates for a symbol"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.Spot, "ETH", "USDT") }), + RequestParameterRule.Optional(x => x.Limit, "The order book depth", 100), + }; + /// /// Order book depths supported for updates /// @@ -20,7 +27,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public SubscribeOrderBookOptions(string exchange, bool needsAuthentication, int[] limits) : base(exchange, needsAuthentication, nameof(IOrderBookSocketClient.SubscribeToOrderBookUpdatesAsync)) + public SubscribeOrderBookOptions(string exchange, bool needsAuthentication, int[] limits) : base(exchange, needsAuthentication, nameof(ISubscribeOrderBookSocket.SubscribeToOrderBookUpdatesAsync), _defaultParameterRules) { SupportedLimits = limits; } @@ -28,12 +35,16 @@ namespace CryptoExchange.Net.SharedApis /// /// Validate a request /// - public override Error? ValidateRequest(SubscribeOrderBookRequest request, IOrderBookSocketClient client) + public override Error? ValidateRequest(SubscribeOrderBookRequest request, ISubscribeOrderBookSocket client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (request.Limit != null && !SupportedLimits.Contains(request.Limit.Value)) return ArgumentError.Invalid(nameof(SubscribeOrderBookRequest.Limit), "Limit not supported"); - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeOrderBookRequest.cs b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/SubscribeOrderBookRequest.cs similarity index 88% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeOrderBookRequest.cs rename to CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/SubscribeOrderBookRequest.cs index fd5eee92..e3dbb6c8 100644 --- a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeOrderBookRequest.cs +++ b/CryptoExchange.Net/SharedApis/V2/OrderBooks/SubscribeOrderBook/SubscribeOrderBookRequest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis { @@ -38,6 +39,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
/// The symbols to subscribe to + [Obsolete("Use SubscribeOrderBookRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) instead")] public SubscribeOrderBookRequest(params SharedSymbol[] symbols) : base(symbols, null) { } diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/CancelAllOrdersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/CancelAllOrdersRequest.cs new file mode 100644 index 00000000..15431d5d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/CancelAllOrdersRequest.cs @@ -0,0 +1,16 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to cancel all currently open orders + /// + public record CancelAllOrdersRequest : SharedRequest + { + /// + /// ctor + /// + /// Exchange specific parameters + public CancelAllOrdersRequest(ExchangeParameters? exchangeParameters = null) : base(null, exchangeParameters) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/CancelAllSymbolOrdersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/CancelAllSymbolOrdersRequest.cs new file mode 100644 index 00000000..46d04ee4 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/CancelAllSymbolOrdersRequest.cs @@ -0,0 +1,18 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to cancel all currently open orders + /// + public record CancelAllSymbolOrdersRequest : SharedSymbolRequest + { + /// + /// ctor + /// + /// Symbol the order is on + /// Exchange specific parameters + public CancelAllSymbolOrdersRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) + : base(symbol, exchangeParameters) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/CancelOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/CancelOrderRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/CancelOrderRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/CancelOrderRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/EditOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/EditOrderRequest.cs new file mode 100644 index 00000000..11187936 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/EditOrderRequest.cs @@ -0,0 +1,41 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to edit an order + /// + public record EditOrderRequest : SharedSymbolRequest + { + /// + /// Order id + /// + public string OrderId { get; set; } + /// + /// Quantity of the order + /// + public SharedQuantity? Quantity { get; set; } + /// + /// Price of the order + /// + public decimal? Price { get; set; } + + /// + /// ctor + /// + /// Symbol to place the order on + /// The id of the order to edit + /// New quantity of the order + /// New price of the order + /// Exchange specific parameters + public EditOrderRequest( + SharedSymbol symbol, + string orderId, + SharedQuantity? quantity = null, + decimal? price = null, + ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + { + OrderId = orderId; + Quantity = quantity; + Price = price; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesOrders/CancelAllFuturesOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesOrders/CancelAllFuturesOrdersOptions.cs new file mode 100644 index 00000000..c176c955 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesOrders/CancelAllFuturesOrdersOptions.cs @@ -0,0 +1,25 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling all open Futures orders + /// + public class CancelAllFuturesOrdersOptions : CapabilityOptions + { + /// + public override string Description => "Cancel all Futures orders"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = []; + + /// + /// ctor + /// + public CancelAllFuturesOrdersOptions(string exchange) : base(exchange, true, nameof(ICancelAllFuturesOrders.CancelAllFuturesOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesOrders/ICancelAllFuturesOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesOrders/ICancelAllFuturesOrder.cs new file mode 100644 index 00000000..635217e0 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesOrders/ICancelAllFuturesOrder.cs @@ -0,0 +1,47 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling all Futures orders on an exchange. + /// + public interface ICancelAllFuturesOrders : ISharedApiCapability + { + /// + /// Futures cancel all orders request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelAllFuturesOrdersOptions CancelAllFuturesOrdersOptions { get; } + /// + /// Cancel all Futures orders, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task CancelAllFuturesOrdersAsync(CancelAllOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling all Futures orders on an exchange via the REST API. + /// + public interface ICancelAllFuturesOrdersRest : ICancelAllFuturesOrders, ISharedRest + { + /// + new Task CancelAllFuturesOrdersAsync(CancelAllOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling all Futures orders on an exchange via the WebSocket API. + /// + public interface ICancelAllFuturesOrdersSocket : ICancelAllFuturesOrders, ISharedSocket + { + /// + new Task CancelAllFuturesOrdersAsync(CancelAllOrdersRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesSymbolOrders/CancelAllFuturesSymbolOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesSymbolOrders/CancelAllFuturesSymbolOrdersOptions.cs new file mode 100644 index 00000000..73655d5e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesSymbolOrders/CancelAllFuturesSymbolOrdersOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling all open futures orders for a specific symbol + /// + public class CancelAllFuturesSymbolOrdersOptions : CapabilityOptions + { + /// + public override string Description => "Cancel all open futures orders for a specific symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the orders to cancel", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public CancelAllFuturesSymbolOrdersOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ICancelAllFuturesSymbolOrders.CancelAllFuturesSymbolOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesSymbolOrders/ICancelAllFuturesSymbolOrders.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesSymbolOrders/ICancelAllFuturesSymbolOrders.cs new file mode 100644 index 00000000..ebf080c6 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelAllFuturesSymbolOrders/ICancelAllFuturesSymbolOrders.cs @@ -0,0 +1,48 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling all open futures orders for a specific symbol on an exchange. + /// + public interface ICancelAllFuturesSymbolOrders : ISharedApiCapability + { + /// + /// Futures cancel order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelAllFuturesSymbolOrdersOptions CancelAllFuturesSymbolOrdersOptions { get; } + /// + /// Cancel all open futures orders for a specific symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task CancelAllFuturesSymbolOrdersAsync(CancelAllSymbolOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling all open futures orders for a specific symbol on an exchange via the REST API. + /// + public interface ICancelAllFuturesSymbolOrdersRest : ICancelAllFuturesSymbolOrders, ISharedRest + { + /// + new Task CancelAllFuturesSymbolOrdersAsync(CancelAllSymbolOrdersRequest request, CancellationToken ct = default); + + } + + /// + /// Operation for canceling all open futures orders for a specific symbol on an exchange via the WebSocket API. + /// + public interface ICancelAllFuturesSymbolOrdersSocket : ICancelAllFuturesSymbolOrders, ISharedSocket + { + /// + new Task> CancelAllFuturesSymbolOrdersAsync(CancelAllSymbolOrdersRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrder/CancelFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrder/CancelFuturesOrderOptions.cs new file mode 100644 index 00000000..d6c616c0 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrder/CancelFuturesOrderOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling a futures order + /// + public class CancelFuturesOrderOptions : CapabilityOptions + { + /// + public override string Description => "Cancel a futures order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to cancel", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to cancel", "123"), + }; + + /// + /// ctor + /// + public CancelFuturesOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ICancelFuturesOrder.CancelFuturesOrderAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrder/ICancelFuturesOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrder/ICancelFuturesOrder.cs new file mode 100644 index 00000000..33fd1d18 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrder/ICancelFuturesOrder.cs @@ -0,0 +1,48 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling an open futures order on an exchange. + /// + public interface ICancelFuturesOrder : ISharedApiCapability + { + /// + /// Futures cancel order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelFuturesOrderOptions CancelFuturesOrderOptions { get; } + /// + /// Cancel a futures order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> CancelFuturesOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling an open futures order on an exchange via the REST API. + /// + public interface ICancelFuturesOrderRest : ICancelFuturesOrder, ISharedRest + { + /// + new Task> CancelFuturesOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + + } + + /// + /// Operation for canceling an open futures order on an exchange via the WebSocket API. + /// + public interface ICancelFuturesOrderSocket : ICancelFuturesOrder, ISharedSocket + { + /// + new Task> CancelFuturesOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrderByClientOrderId/CancelFuturesOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrderByClientOrderId/CancelFuturesOrderByClientOrderIdOptions.cs new file mode 100644 index 00000000..f8d81628 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrderByClientOrderId/CancelFuturesOrderByClientOrderIdOptions.cs @@ -0,0 +1,24 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling a futures order by client order id + /// + public class CancelFuturesOrderByClientOrderIdOptions : CapabilityOptions + { + /// + public override string Description => "Cancel a futures order by its client order id"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to cancel", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to cancel", "123"), + }; + + /// + /// ctor + /// + public CancelFuturesOrderByClientOrderIdOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ICancelFuturesOrderByClientOrderId.CancelFuturesOrderByClientOrderIdAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrderByClientOrderId/ICancelFuturesOrderByClientOrderId.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrderByClientOrderId/ICancelFuturesOrderByClientOrderId.cs new file mode 100644 index 00000000..ffc9392a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/CancelFuturesOrderByClientOrderId/ICancelFuturesOrderByClientOrderId.cs @@ -0,0 +1,46 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling an open futures order by its client order id on an exchange. + /// + public interface ICancelFuturesOrderByClientOrderId : ISharedApiCapability + { + /// + /// Futures cancel order by client order id request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelFuturesOrderByClientOrderIdOptions CancelFuturesOrderByClientOrderIdOptions { get; } + /// + /// Cancel a futures order using client order id, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> CancelFuturesOrderByClientOrderIdAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling an open futures order by its client order id on an exchange via the REST API. + /// + public interface ICancelFuturesOrderByClientOrderIdRest : ICancelFuturesOrderByClientOrderId, ISharedRest + { + /// + new Task> CancelFuturesOrderByClientOrderIdAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling an open futures order by its client order id on an exchange via the WebSocket API. + /// + public interface ICancelFuturesOrderByClientOrderIdSocket : ICancelFuturesOrderByClientOrderId, ISharedSocket + { + /// + new Task> CancelFuturesOrderByClientOrderIdAsync(CancelOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrder/EditFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrder/EditFuturesOrderOptions.cs new file mode 100644 index 00000000..9b1290ec --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrder/EditFuturesOrderOptions.cs @@ -0,0 +1,31 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for editing an open Futures order + /// + public class EditFuturesOrderOptions : CapabilityOptions + { + /// + public override string Description => "Edit an existing Futures order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to edit", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to edit", "123"), + RequestParameterRule.Optional(x => x.Quantity, "The new order quantity", SharedQuantity.Base(1)), + RequestParameterRule.Optional(x => x.Price, "The new order price", 0.1m), + }; + + + /// + /// ctor + /// + public EditFuturesOrderOptions(string exchange) : base(exchange, true, nameof(IEditFuturesOrder.EditFuturesOrderAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrder/IEditFuturesOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrder/IEditFuturesOrder.cs new file mode 100644 index 00000000..2cf59a49 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrder/IEditFuturesOrder.cs @@ -0,0 +1,49 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for editing an open Futures order on an exchange. + /// + public interface IEditFuturesOrder : ISharedApiCapability + { + /// + /// Futures edit order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ EditFuturesOrderOptions EditFuturesOrderOptions { get; } + + /// + /// Edit an existing Futures order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> EditFuturesOrderAsync( + EditOrderRequest request, + CancellationToken ct = default); + } + + /// + /// Operation for editing a Futures order on an exchange via the REST API. + /// + public interface IEditFuturesOrderRest : IEditFuturesOrder, ISharedRest + { + /// + new Task> EditFuturesOrderAsync(EditOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for editing a Futures order on an exchange via the WebSocket API. + /// + public interface IEditFuturesOrderSocket : IEditFuturesOrder, ISharedSocket + { + /// + new Task> EditFuturesOrderAsync(EditOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrderByClientOrderId/EditFuturesOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrderByClientOrderId/EditFuturesOrderByClientOrderIdOptions.cs new file mode 100644 index 00000000..10e1c14a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrderByClientOrderId/EditFuturesOrderByClientOrderIdOptions.cs @@ -0,0 +1,26 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for editing a futures order by client order id + /// + public class EditFuturesOrderByClientOrderIdOptions : CapabilityOptions + { + /// + public override string Description => "Edit a futures order by its client order id"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to edit", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The client order id of the order to edit", "123"), + RequestParameterRule.Optional(x => x.Quantity, "The new order quantity", SharedQuantity.Base(1)), + RequestParameterRule.Optional(x => x.Price, "The new order price", 0.1m), + }; + + /// + /// ctor + /// + public EditFuturesOrderByClientOrderIdOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IEditFuturesOrderByClientOrderId.EditFuturesOrderByClientOrderIdAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrderByClientOrderId/IEditFuturesOrderByClientOrderId.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrderByClientOrderId/IEditFuturesOrderByClientOrderId.cs new file mode 100644 index 00000000..62b1c090 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/EditFuturesOrderByClientOrderId/IEditFuturesOrderByClientOrderId.cs @@ -0,0 +1,46 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for editing an open futures order by its client order id on an exchange. + /// + public interface IEditFuturesOrderByClientOrderId : ISharedApiCapability + { + /// + /// Futures edit order by client order id request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ EditFuturesOrderByClientOrderIdOptions EditFuturesOrderByClientOrderIdOptions { get; } + /// + /// Edit a futures order using client order id, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> EditFuturesOrderByClientOrderIdAsync(EditOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for editing an open futures order by its client order id on an exchange via the REST API. + /// + public interface IEditFuturesOrderByClientOrderIdRest : IEditFuturesOrderByClientOrderId, ISharedRest + { + /// + new Task> EditFuturesOrderByClientOrderIdAsync(EditOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for editing an open futures order by its client order id on an exchange via the WebSocket API. + /// + public interface IEditFuturesOrderByClientOrderIdSocket : IEditFuturesOrderByClientOrderId, ISharedSocket + { + /// + new Task> EditFuturesOrderByClientOrderIdAsync(EditOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesClosedOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetClosedFuturesOrders/GetFuturesClosedOrdersOptions.cs similarity index 58% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesClosedOrdersOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetClosedFuturesOrders/GetFuturesClosedOrdersOptions.cs index cd51b2d8..16342ec3 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesClosedOrdersOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetClosedFuturesOrders/GetFuturesClosedOrdersOptions.cs @@ -7,22 +7,35 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting closed orders /// - public class GetFuturesClosedOrdersOptions : PaginatedEndpointOptions + public class GetFuturesClosedOrdersOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve closed futures orders"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve closed orders for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// public GetFuturesClosedOrdersOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IFuturesOrderRestClient.GetClosedFuturesOrdersAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetClosedFuturesOrdersRest.GetClosedFuturesOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { } /// - public override Error? ValidateRequest(GetClosedOrdersRequest request, IFuturesOrderRestClient client) + public override Error? ValidateRequest(GetClosedOrdersRequest request, IGetClosedFuturesOrdersRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!SupportsAscending && request.Direction == DataDirection.Ascending) return ArgumentError.Invalid(nameof(GetClosedOrdersRequest.Direction), $"Ascending direction is not supported"); @@ -43,7 +56,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } } diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetClosedFuturesOrders/IGetClosedFuturesOrders.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetClosedFuturesOrders/IGetClosedFuturesOrders.cs new file mode 100644 index 00000000..dacb9e44 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetClosedFuturesOrders/IGetClosedFuturesOrders.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving closed futures orders on an exchange. + /// + public interface IGetClosedFuturesOrders : ISharedApiCapability + { + /// + /// Futures get closed orders request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFuturesClosedOrdersOptions GetClosedFuturesOrdersOptions { get; } + /// + /// Get info on closed futures orders, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetClosedFuturesOrdersAsync(GetClosedOrdersRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving closed futures orders on an exchange via the REST API. + /// + public interface IGetClosedFuturesOrdersRest : IGetClosedFuturesOrders, ISharedRest + { + /// + new Task> GetClosedFuturesOrdersAsync(GetClosedOrdersRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrder/GetFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrder/GetFuturesOrderOptions.cs new file mode 100644 index 00000000..30f151bb --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrder/GetFuturesOrderOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for getting a futures order by id endpoint + /// + public class GetFuturesOrderOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve a futures order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to retrieve", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to retrieve", "123"), + }; + + /// + /// ctor + /// + public GetFuturesOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetFuturesOrder.GetFuturesOrderAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrder/IGetFuturesOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrder/IGetFuturesOrder.cs new file mode 100644 index 00000000..53581124 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrder/IGetFuturesOrder.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving a futures order from an exchange. + /// + public interface IGetFuturesOrder : ISharedApiCapability + { + /// + /// Futures get order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFuturesOrderOptions GetFuturesOrderOptions { get; } + /// + /// Get info on a specific futures order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetFuturesOrderAsync(GetOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving a futures order from an exchange via the REST API. + /// + public interface IGetFuturesOrderRest : IGetFuturesOrder, ISharedRest + { + /// + new Task> GetFuturesOrderAsync(GetOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrderByClientOrderId/GetFuturesOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrderByClientOrderId/GetFuturesOrderByClientOrderIdOptions.cs new file mode 100644 index 00000000..b2bbbce9 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrderByClientOrderId/GetFuturesOrderByClientOrderIdOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for getting a spot order by client order id + /// + public class GetFuturesOrderByClientOrderIdOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve a futures order by its client order id"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to retrieve", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to retrieve", "123"), + }; + + /// + /// ctor + /// + public GetFuturesOrderByClientOrderIdOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetFuturesOrderByClientOrderId.GetFuturesOrderByClientOrderIdAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrderByClientOrderId/IGetFuturesOrderByClientOrderId.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrderByClientOrderId/IGetFuturesOrderByClientOrderId.cs new file mode 100644 index 00000000..c9ce4663 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetFuturesOrderByClientOrderId/IGetFuturesOrderByClientOrderId.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Endpoint definition for retrieving a futures order by client order id on an exchange. + /// + public interface IGetFuturesOrderByClientOrderId : ISharedApiCapability + { + /// + /// Futures get order by client order id request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFuturesOrderByClientOrderIdOptions GetFuturesOrderByClientOrderIdOptions { get; } + + /// + /// Get info on a specific futures order using a client order id, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetFuturesOrderByClientOrderIdAsync(GetOrderRequest request, CancellationToken ct = default); + } + + /// + /// Endpoint definition for retrieving a futures order by client order id on an exchange. + /// + public interface IGetFuturesOrderByClientOrderIdRest : IGetFuturesOrderByClientOrderId, ISharedRest + { + /// + new Task> GetFuturesOrderByClientOrderIdAsync(GetOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetOpenFuturesOrders/GetOpenFuturesOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetOpenFuturesOrders/GetOpenFuturesOrdersOptions.cs new file mode 100644 index 00000000..7b6e5f4d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetOpenFuturesOrders/GetOpenFuturesOrdersOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for getting a futures order by id endpoint + /// + public class GetOpenFuturesOrdersOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve open futures orders"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the open orders by trading mode", TradingMode.PerpetualLinear), + RequestParameterRule.Optional(x => x.Symbol, "The symbol to retrieve open orders for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetOpenFuturesOrdersOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetOpenFuturesOrders.GetOpenFuturesOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetOpenFuturesOrders/IGetOpenFuturesOrders.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetOpenFuturesOrders/IGetOpenFuturesOrders.cs new file mode 100644 index 00000000..2451aa7d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/GetOpenFuturesOrders/IGetOpenFuturesOrders.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving open futures orders from an exchange. + /// + public interface IGetOpenFuturesOrders : ISharedApiCapability + { + /// + /// Futures get open orders request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetOpenFuturesOrdersOptions GetOpenFuturesOrdersOptions { get; } + /// + /// Get info on a open futures orders, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetOpenFuturesOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving open futures orders from an exchange via the REST API. + /// + public interface IGetOpenFuturesOrdersRest : IGetOpenFuturesOrders, ISharedRest + { + /// + new Task> GetOpenFuturesOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceFuturesOrder/IPlaceFuturesOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceFuturesOrder/IPlaceFuturesOrder.cs new file mode 100644 index 00000000..41b1f21b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceFuturesOrder/IPlaceFuturesOrder.cs @@ -0,0 +1,74 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for placing a futures order on an exchange. + /// + public interface IPlaceFuturesOrder : ISharedApiCapability + { + /// + /// How the trading fee is deducted + /// + SharedFeeDeductionType FuturesFeeDeductionType { get; } + /// + /// How the asset is determined in which the trading fee is paid + /// + SharedFeeAssetType FuturesFeeAssetType { get; } + /// + /// Supported order types for futures orders + /// + SharedOrderType[] FuturesSupportedOrderTypes { get; } + /// + /// Supported time in force types for futures orders + /// + SharedTimeInForce[] FuturesSupportedTimeInForce { get; } + /// + /// Supported quantity types for futures orders + /// + SharedQuantitySupport FuturesSupportedOrderQuantity { get; } + + /// + /// Generate a new random client order id + /// + /// + string GenerateClientOrderId(); + + /// + /// Futures place order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ PlaceFuturesOrderOptions PlaceFuturesOrderOptions { get; } + + /// + /// Place a new futures order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> PlaceFuturesOrderAsync(PlaceFuturesOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for placing a futures order on an exchange via the REST API. + /// + public interface IPlaceFuturesOrderRest : IPlaceFuturesOrder, ISharedRest + { + /// + new Task> PlaceFuturesOrderAsync(PlaceFuturesOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for placing a futures order on an exchange via the WebSocket API. + /// + public interface IPlaceFuturesOrderSocket : IPlaceFuturesOrder, ISharedSocket + { + /// + new Task> PlaceFuturesOrderAsync(PlaceFuturesOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceFuturesOrder/PlaceFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceFuturesOrder/PlaceFuturesOrderOptions.cs new file mode 100644 index 00000000..1a5b7979 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceFuturesOrder/PlaceFuturesOrderOptions.cs @@ -0,0 +1,77 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for placing a new futures order + /// + public class PlaceFuturesOrderOptions : CapabilityOptions + { + /// + public override string Description => "Place a new futures order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to place the order on", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.Side, "The order side", SharedOrderSide.Buy), + RequestParameterRule.Required(x => x.OrderType, "The order type", SharedOrderType.Limit), + RequestParameterRule.Optional(x => x.TimeInForce, "The order time in force", SharedTimeInForce.GoodTillCanceled), + RequestParameterRule.Optional(x => x.Quantity, "The order quantity", SharedQuantity.Base(0.1m)), + RequestParameterRule.Optional(x => x.Price, "The order price", 1m), + RequestParameterRule.Optional(x => x.ClientOrderId, "The client order id", "123"), + RequestParameterRule.Optional(x => x.PositionSide, "The position side of the order", SharedPositionSide.Long), + RequestParameterRule.Optional(x => x.MarginMode, "The margin mode of the order", SharedMarginMode.Cross), + RequestParameterRule.Optional(x => x.ReduceOnly, "Whether the order should only reduce a position", false), + RequestParameterRule.Optional(x => x.Leverage, "The leverage for the position", 10m), + RequestParameterRule.Optional(x => x.TakeProfitPrice, "The take profit price", 1.1m), + RequestParameterRule.Optional(x => x.StopLossPrice, "The stop loss price", 0.9m), + }; + + /// + /// Whether or not the API supports setting take profit / stop loss with the order + /// + public bool SupportsTpSl { get; set; } + + /// + /// ctor + /// + public PlaceFuturesOrderOptions(string exchange, bool supportsTpSl) : base(exchange, true, nameof(IPlaceFuturesOrder.PlaceFuturesOrderAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + SupportsTpSl = supportsTpSl; + } + + /// + /// Validate a request + /// + public override Error? ValidateRequest( + PlaceFuturesOrderRequest request, + IPlaceFuturesOrder client + ) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (!SupportsTpSl && (request.StopLossPrice != null || request.TakeProfitPrice != null)) + return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.StopLossPrice) + " / " + nameof(PlaceFuturesOrderRequest.TakeProfitPrice), "Tp/Sl parameters not supported"); + + if (request.OrderType == SharedOrderType.Other) + throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType)); + + if (!client.FuturesSupportedOrderTypes.Contains(request.OrderType)) + return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.OrderType), "Order type not supported"); + + if (request.TimeInForce != null && !client.FuturesSupportedTimeInForce.Contains(request.TimeInForce.Value)) + return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.TimeInForce), "Order time in force not supported"); + + var quantityError = client.FuturesSupportedOrderQuantity.Validate(request.Side, request.OrderType, request.Quantity); + if (quantityError != null) + return quantityError; + + return null; + } + + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/PlaceFuturesOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceFuturesOrder/PlaceFuturesOrderRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/PlaceFuturesOrderRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceFuturesOrder/PlaceFuturesOrderRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/IPlaceMultipleFuturesOrders.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/IPlaceMultipleFuturesOrders.cs new file mode 100644 index 00000000..b301c85b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/IPlaceMultipleFuturesOrders.cs @@ -0,0 +1,84 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for placing multiple futures order on an exchange in single call. + /// + public interface IPlaceMultipleFuturesOrders : ISharedApiCapability + { + /// + /// How the trading fee is deducted + /// + SharedFeeDeductionType FuturesFeeDeductionType { get; } + /// + /// How the asset is determined in which the trading fee is paid + /// + SharedFeeAssetType FuturesFeeAssetType { get; } + /// + /// Supported order types for futures orders + /// + SharedOrderType[] FuturesSupportedOrderTypes { get; } + /// + /// Supported time in force types for placing futures orders + /// + SharedTimeInForce[] FuturesSupportedTimeInForce { get; } + /// + /// Supported quantity types for placing futures orders + /// + SharedQuantitySupport FuturesSupportedOrderQuantity { get; } + /// + /// Max number of orders per request + /// + public int? MaxFuturesOrdersPerRequest { get; } + /// + /// Whether orders for different symbols are allowed in a single place order request + /// + public bool PlaceMultipleFuturesOrdersAllowsMultipleSymbols { get; } + + /// + /// Generate a new random client order id in a format that is accepted by the exchange. + /// + /// + string GenerateClientOrderId(); + + /// + /// Futures place order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ PlaceMultipleFuturesOrdersOptions PlaceMultipleFuturesOrdersOptions { get; } + + /// + /// Place multiple new futures orders, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task[]>> PlaceMultipleFuturesOrdersAsync( + PlaceMultipleFuturesOrdersRequest request, + CancellationToken ct = default); + } + + /// + /// Operation for placing multiple futures orders on an exchange via the REST API. + /// + public interface IPlaceMultipleFuturesOrdersRest : IPlaceMultipleFuturesOrders, ISharedRest + { + /// + new Task[]>> PlaceMultipleFuturesOrdersAsync(PlaceMultipleFuturesOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for placing multiple futures orders on an exchange via the WebSocket API. + /// + public interface IPlaceMultipleFuturesOrdersSocket : IPlaceMultipleFuturesOrders, ISharedSocket + { + /// + new Task[]>> PlaceMultipleFuturesOrdersAsync(PlaceMultipleFuturesOrdersRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/PlaceMultipleFuturesOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/PlaceMultipleFuturesOrdersOptions.cs new file mode 100644 index 00000000..9db92c5c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/PlaceMultipleFuturesOrdersOptions.cs @@ -0,0 +1,80 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for placing multiple new futures orders + /// + public class PlaceMultipleFuturesOrdersOptions : CapabilityOptions + { + /// + public override string Description => "Place multiple new futures orders"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Orders, "The orders to place", []), + }; + + + /// + /// ctor + /// + public PlaceMultipleFuturesOrdersOptions(string exchange) + : base(exchange, true, nameof(IPlaceMultipleFuturesOrders.PlaceMultipleFuturesOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + + /// + /// Validate a request + /// + public override Error? ValidateRequest( + PlaceMultipleFuturesOrdersRequest request, + IPlaceMultipleFuturesOrders client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Orders.Length > client.MaxFuturesOrdersPerRequest) + return ArgumentError.Invalid(nameof(PlaceMultipleFuturesOrdersRequest.Orders), $"Too many orders, max {client.MaxFuturesOrdersPerRequest}"); + + if (request.Orders.Length == 0) + return ArgumentError.Invalid(nameof(PlaceMultipleFuturesOrdersRequest.Orders), "No orders provided"); + + if (!client.PlaceMultipleFuturesOrdersAllowsMultipleSymbols) + { + var firstSymbol = request.Orders[0].Symbol; + if (request.Orders.Any(order => order.Symbol!.BaseAsset != firstSymbol!.BaseAsset + || order.Symbol.QuoteAsset != firstSymbol.QuoteAsset + || order.Symbol.TradingMode != firstSymbol.TradingMode)) + { + return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.Symbol), "All orders in the request should have matching symbols"); + } + } + + if (!request.Orders.All(x => x.TradingMode == request.TradingMode)) + return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.TradingMode), "All orders in the request should have matching trading modes"); + + foreach (var order in request.Orders) + { + if (order.OrderType == SharedOrderType.Other) + throw new ArgumentException("OrderType can't be `Other`", nameof(order.OrderType)); + + if (!client.FuturesSupportedOrderTypes.Contains(order.OrderType)) + return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.OrderType), "Order type not supported"); + + if (order.TimeInForce != null && !client.FuturesSupportedTimeInForce.Contains(order.TimeInForce.Value)) + return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.TimeInForce), "Order time in force not supported"); + + var quantityError = client.FuturesSupportedOrderQuantity.Validate(order.Side, order.OrderType, order.Quantity); + if (quantityError != null) + return quantityError; + } + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/PlaceMultipleFuturesOrdersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/PlaceMultipleFuturesOrdersRequest.cs new file mode 100644 index 00000000..77e5e5ea --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/PlaceMultipleFuturesOrders/PlaceMultipleFuturesOrdersRequest.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to place multiple new futures orders + /// + public record PlaceMultipleFuturesOrdersRequest : SharedRequest + { + /// + /// Orders to place + /// + public PlaceFuturesOrderRequest[] Orders { get; set; } + + /// + /// ctor + /// + /// Orders to place + /// Exchange specific parameters + public PlaceMultipleFuturesOrdersRequest( + IEnumerable orders, + ExchangeParameters? exchangeParameters = null) : base(null, exchangeParameters) + { + Orders = orders.ToArray(); + TradingMode = Orders.FirstOrDefault()?.TradingMode; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/SubscribeFuturesOrder/ISubscribeFuturesOrdersSocket.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/SubscribeFuturesOrder/ISubscribeFuturesOrdersSocket.cs new file mode 100644 index 00000000..d84748ff --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/SubscribeFuturesOrder/ISubscribeFuturesOrdersSocket.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to user futures order updates + /// + public interface ISubscribeFuturesOrdersSocket : ISharedSubscription + { + /// + /// Futures orders subscription options + /// + SubscribeFuturesOrderOptions SubscribeFuturesOrderOptions { get; } + + /// + /// Subscribe to user futures order updates + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToFuturesOrderUpdatesAsync(SubscribeFuturesOrderRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Futures/SubscribeFuturesOrder/SubscribeFuturesOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/SubscribeFuturesOrder/SubscribeFuturesOrderOptions.cs new file mode 100644 index 00000000..c51c0b88 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/SubscribeFuturesOrder/SubscribeFuturesOrderOptions.cs @@ -0,0 +1,27 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for subscribing to order updates + /// + public class SubscribeFuturesOrderOptions : CapabilityOptions + { + /// + public override string Description => "Subscribe to futures order updates"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter futures order updates by trading mode", TradingMode.PerpetualLinear), + }; + + /// + /// ctor + /// + public SubscribeFuturesOrderOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ISubscribeFuturesOrdersSocket.SubscribeToFuturesOrderUpdatesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeFuturesOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Futures/SubscribeFuturesOrder/SubscribeFuturesOrderRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeFuturesOrderRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/Futures/SubscribeFuturesOrder/SubscribeFuturesOrderRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetClosedOrdersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/GetClosedOrdersRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetClosedOrdersRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/GetClosedOrdersRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetOpenOrdersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/GetOpenOrdersRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetOpenOrdersRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/GetOpenOrdersRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/GetOrderRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetOrderRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/GetOrderRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotOrders/CancelAllSpotOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotOrders/CancelAllSpotOrdersOptions.cs new file mode 100644 index 00000000..16f32643 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotOrders/CancelAllSpotOrdersOptions.cs @@ -0,0 +1,25 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling all open spot orders + /// + public class CancelAllSpotOrdersOptions : CapabilityOptions + { + /// + public override string Description => "Cancel all spot orders"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = []; + + /// + /// ctor + /// + public CancelAllSpotOrdersOptions(string exchange) : base(exchange, true, nameof(ICancelAllSpotOrders.CancelAllSpotOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotOrders/ICancelAllSpotOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotOrders/ICancelAllSpotOrder.cs new file mode 100644 index 00000000..8cc479ab --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotOrders/ICancelAllSpotOrder.cs @@ -0,0 +1,47 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling all spot orders on an exchange. + /// + public interface ICancelAllSpotOrders : ISharedApiCapability + { + /// + /// Spot cancel all orders request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelAllSpotOrdersOptions CancelAllSpotOrdersOptions { get; } + /// + /// Cancel all spot orders, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task CancelAllSpotOrdersAsync(CancelAllOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling all spot orders on an exchange via the REST API. + /// + public interface ICancelAllSpotOrdersRest : ICancelAllSpotOrders, ISharedRest + { + /// + new Task CancelAllSpotOrdersAsync(CancelAllOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling all spot orders on an exchange via the WebSocket API. + /// + public interface ICancelAllSpotOrdersSocket : ICancelAllSpotOrders, ISharedSocket + { + /// + new Task CancelAllSpotOrdersAsync(CancelAllOrdersRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotSymbolOrders/CancelAllSpotSymbolOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotSymbolOrders/CancelAllSpotSymbolOrdersOptions.cs new file mode 100644 index 00000000..6c0a3389 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotSymbolOrders/CancelAllSpotSymbolOrdersOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling all open Spot orders for a specific symbol + /// + public class CancelAllSpotSymbolOrdersOptions : CapabilityOptions + { + /// + public override string Description => "Cancel all open Spot orders for a specific symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the orders to cancel", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public CancelAllSpotSymbolOrdersOptions(string exchange, bool authenticated) + : base(exchange, authenticated, nameof(ICancelAllSpotSymbolOrders.CancelAllSpotSymbolOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotSymbolOrders/ICancelAllSpotSymbolOrders.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotSymbolOrders/ICancelAllSpotSymbolOrders.cs new file mode 100644 index 00000000..272e1aa4 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelAllSpotSymbolOrders/ICancelAllSpotSymbolOrders.cs @@ -0,0 +1,48 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling all open Spot orders for a specific symbol on an exchange. + /// + public interface ICancelAllSpotSymbolOrders : ISharedApiCapability + { + /// + /// Spot cancel order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelAllSpotSymbolOrdersOptions CancelAllSpotSymbolOrdersOptions { get; } + /// + /// Cancel all open Spot orders for a specific symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task CancelAllSpotSymbolOrdersAsync(CancelAllSymbolOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling all open Spot orders for a specific symbol on an exchange via the REST API. + /// + public interface ICancelAllSpotSymbolOrdersRest : ICancelAllSpotSymbolOrders, ISharedRest + { + /// + new Task CancelAllSpotSymbolOrdersAsync(CancelAllSymbolOrdersRequest request, CancellationToken ct = default); + + } + + /// + /// Operation for canceling all open Spot orders for a specific symbol on an exchange via the WebSocket API. + /// + public interface ICancelAllSpotSymbolOrdersSocket : ICancelAllSpotSymbolOrders, ISharedSocket + { + /// + new Task> CancelAllSpotSymbolOrdersAsync(CancelAllSymbolOrdersRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrder/CancelSpotOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrder/CancelSpotOrderOptions.cs new file mode 100644 index 00000000..ee1ff849 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrder/CancelSpotOrderOptions.cs @@ -0,0 +1,43 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling a spot order + /// + public class CancelSpotOrderOptions : CapabilityOptions + { + /// + public override string Description => "Cancel a spot order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to cancel", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The order id of the order to cancel", "123") + }; + + /// + /// ctor + /// + public CancelSpotOrderOptions(string exchange, bool authenticated) + : base(exchange, authenticated, nameof(ICancelSpotOrder.CancelSpotOrderAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + public override Error? ValidateRequest(CancelOrderRequest request, ICancelSpotOrder client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrder/ICancelSpotOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrder/ICancelSpotOrder.cs new file mode 100644 index 00000000..1e4f5d06 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrder/ICancelSpotOrder.cs @@ -0,0 +1,47 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling a spot order on an exchange. + /// + public interface ICancelSpotOrder : ISharedApiCapability + { + /// + /// Spot cancel order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelSpotOrderOptions CancelSpotOrderOptions { get; } + /// + /// Cancel a spot order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> CancelSpotOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling a spot order on an exchange via the REST API. + /// + public interface ICancelSpotOrderRest : ICancelSpotOrder, ISharedRest + { + /// + new Task> CancelSpotOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling a spot order on an exchange via the WebSocket API. + /// + public interface ICancelSpotOrderSocket : ICancelSpotOrder, ISharedSocket + { + /// + new Task> CancelSpotOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrderByClientOrderId/CancelSpotOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrderByClientOrderId/CancelSpotOrderByClientOrderIdOptions.cs new file mode 100644 index 00000000..1bd3e93e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrderByClientOrderId/CancelSpotOrderByClientOrderIdOptions.cs @@ -0,0 +1,43 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling a spot order + /// + public class CancelSpotOrderByClientOrderIdOptions : CapabilityOptions + { + /// + public override string Description => "Cancel a spot order by its client order id"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to cancel", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The client order id of the order to cancel", "123") + }; + + /// + /// ctor + /// + public CancelSpotOrderByClientOrderIdOptions(string exchange, bool authenticated) + : base(exchange, authenticated, nameof(ICancelSpotOrderByClientOrderIdRest.CancelSpotOrderByClientOrderIdAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + public override Error? ValidateRequest(CancelOrderRequest request, ICancelSpotOrderByClientOrderId client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrderByClientOrderId/ICancelSpotOrderByClientOrderId.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrderByClientOrderId/ICancelSpotOrderByClientOrderId.cs new file mode 100644 index 00000000..dbb373bb --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/CancelSpotOrderByClientOrderId/ICancelSpotOrderByClientOrderId.cs @@ -0,0 +1,46 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling a spot order by client order id on an exchange. + /// + public interface ICancelSpotOrderByClientOrderId: ISharedApiCapability + { + /// + /// Spot cancel order by client order id request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelSpotOrderByClientOrderIdOptions CancelSpotOrderByClientOrderIdOptions { get; } + /// + /// Cancel a spot order using client order id, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> CancelSpotOrderByClientOrderIdAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling a spot order by client order id on an exchange via the REST API. + /// + public interface ICancelSpotOrderByClientOrderIdRest : ICancelSpotOrderByClientOrderId, ISharedRest + { + /// + new Task> CancelSpotOrderByClientOrderIdAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling a spot order by client order id on an exchange via the WebSocket API. + /// + public interface ICancelSpotOrderByClientOrderIdSocket : ICancelSpotOrderByClientOrderId, ISharedSocket + { + /// + new Task> CancelSpotOrderByClientOrderIdAsync(CancelOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrder/EditSpotOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrder/EditSpotOrderOptions.cs new file mode 100644 index 00000000..b66492cb --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrder/EditSpotOrderOptions.cs @@ -0,0 +1,48 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for editing an open spot order + /// + public class EditSpotOrderOptions : CapabilityOptions + { + /// + public override string Description => "Edit an existing spot order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to edit", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The order id of the order to edit", "123"), + RequestParameterRule.Optional(x => x.Quantity, "The new order quantity", SharedQuantity.Base(1)), + RequestParameterRule.Optional(x => x.Price, "The new order price", 0.1m), + }; + + /// + /// ctor + /// + public EditSpotOrderOptions(string exchange) + : base(exchange, true, nameof(IEditSpotOrder.EditSpotOrderAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + /// Validate a request + /// + public override Error? ValidateRequest( + EditOrderRequest request, + IEditSpotOrder client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrder/IEditSpotOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrder/IEditSpotOrder.cs new file mode 100644 index 00000000..a94f341d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrder/IEditSpotOrder.cs @@ -0,0 +1,49 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for editing an open spot order on an exchange. + /// + public interface IEditSpotOrder : ISharedApiCapability + { + /// + /// Spot edit order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ EditSpotOrderOptions EditSpotOrderOptions { get; } + + /// + /// Edit an existing spot order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> EditSpotOrderAsync( + EditOrderRequest request, + CancellationToken ct = default); + } + + /// + /// Operation for editing a spot order on an exchange via the REST API. + /// + public interface IEditSpotOrderRest : IEditSpotOrder, ISharedRest + { + /// + new Task> EditSpotOrderAsync(EditOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for editing a spot order on an exchange via the WebSocket API. + /// + public interface IEditSpotOrderSocket : IEditSpotOrder, ISharedSocket + { + /// + new Task> EditSpotOrderAsync(EditOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrderByClientOrderId/EditSpotOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrderByClientOrderId/EditSpotOrderByClientOrderIdOptions.cs new file mode 100644 index 00000000..59f3ed98 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrderByClientOrderId/EditSpotOrderByClientOrderIdOptions.cs @@ -0,0 +1,26 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for editing a spot order by client order id + /// + public class EditSpotOrderByClientOrderIdOptions : CapabilityOptions + { + /// + public override string Description => "Edit a spot order by its client order id"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to edit", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The client order id of the order to edit", "123"), + RequestParameterRule.Optional(x => x.Quantity, "The new order quantity", SharedQuantity.Base(1)), + RequestParameterRule.Optional(x => x.Price, "The new order price", 0.1m), + }; + + /// + /// ctor + /// + public EditSpotOrderByClientOrderIdOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IEditSpotOrderByClientOrderId.EditSpotOrderByClientOrderIdAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrderByClientOrderId/IEditSpotOrderByClientOrderId.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrderByClientOrderId/IEditSpotOrderByClientOrderId.cs new file mode 100644 index 00000000..c6eb1c11 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/EditSpotOrderByClientOrderId/IEditSpotOrderByClientOrderId.cs @@ -0,0 +1,46 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for editing an open spot order by its client order id on an exchange. + /// + public interface IEditSpotOrderByClientOrderId : ISharedApiCapability + { + /// + /// Spot edit order by client order id request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ EditSpotOrderByClientOrderIdOptions EditSpotOrderByClientOrderIdOptions { get; } + /// + /// Edit a spot order using client order id, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> EditSpotOrderByClientOrderIdAsync(EditOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for editing an open spot order by its client order id on an exchange via the REST API. + /// + public interface IEditSpotOrderByClientOrderIdRest : IEditSpotOrderByClientOrderId, ISharedRest + { + /// + new Task> EditSpotOrderByClientOrderIdAsync(EditOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for editing an open Spot order by its client order id on an exchange via the WebSocket API. + /// + public interface IEditSpotOrderByClientOrderIdSocket : IEditSpotOrderByClientOrderId, ISharedSocket + { + /// + new Task> EditSpotOrderByClientOrderIdAsync(EditOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotClosedOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetClosedSpotOrders/GetSpotClosedOrdersOptions.cs similarity index 61% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotClosedOrdersOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetClosedSpotOrders/GetSpotClosedOrdersOptions.cs index 4ab33960..ee3fdd13 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotClosedOrdersOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetClosedSpotOrders/GetSpotClosedOrdersOptions.cs @@ -7,22 +7,35 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting user trades /// - public class GetSpotClosedOrdersOptions : PaginatedEndpointOptions + public class GetSpotClosedOrdersOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve closed spot orders"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve closed spot orders for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// public GetSpotClosedOrdersOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(ISpotOrderRestClient.GetClosedSpotOrdersAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetClosedSpotOrdersRest.GetClosedSpotOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Spot) { } /// - public override Error? ValidateRequest(GetClosedOrdersRequest request, ISpotOrderRestClient client) + public override Error? ValidateRequest(GetClosedOrdersRequest request, IGetClosedSpotOrdersRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (request.Symbol!.TradingMode != TradingMode.Spot) return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); @@ -46,7 +59,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetClosedSpotOrders/IGetClosedSpotOrders.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetClosedSpotOrders/IGetClosedSpotOrders.cs new file mode 100644 index 00000000..2a569570 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetClosedSpotOrders/IGetClosedSpotOrders.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving closed spot orders from an exchange. + /// + public interface IGetClosedSpotOrders : ISharedApiCapability + { + /// + /// Spot get closed orders request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetSpotClosedOrdersOptions GetClosedSpotOrdersOptions { get; } + /// + /// Get info on closed spot orders, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetClosedSpotOrdersAsync(GetClosedOrdersRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving closed spot orders from an exchange via the REST API. + /// + public interface IGetClosedSpotOrdersRest : IGetClosedSpotOrders, ISharedRest + { + /// + new Task> GetClosedSpotOrdersAsync(GetClosedOrdersRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetOpenSpotOrders/GetOpenSpotOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetOpenSpotOrders/GetOpenSpotOrdersOptions.cs new file mode 100644 index 00000000..0e8952ae --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetOpenSpotOrders/GetOpenSpotOrdersOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for getting a spot order by id endpoint + /// + public class GetOpenSpotOrdersOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve open spot orders"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the open orders by trading mode", TradingMode.Spot), + RequestParameterRule.Optional(x => x.Symbol, "The symbol to retrieve open spot orders for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetOpenSpotOrdersOptions(string exchange, bool authenticated) + : base(exchange, authenticated, nameof(IGetOpenSpotOrders.GetOpenSpotOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetOpenSpotOrders/IGetOpenSpotOrders.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetOpenSpotOrders/IGetOpenSpotOrders.cs new file mode 100644 index 00000000..8cf1957f --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetOpenSpotOrders/IGetOpenSpotOrders.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving open spot orders from an exchange. + /// + public interface IGetOpenSpotOrders : ISharedApiCapability + { + /// + /// Spot get open orders request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetOpenSpotOrdersOptions GetOpenSpotOrdersOptions { get; } + /// + /// Get info on a open spot orders, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetOpenSpotOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving open spot orders from an exchange via the REST API. + /// + public interface IGetOpenSpotOrdersRest : IGetOpenSpotOrders, ISharedRest + { + /// + new Task> GetOpenSpotOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrder/GetSpotOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrder/GetSpotOrderOptions.cs new file mode 100644 index 00000000..c1f851b8 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrder/GetSpotOrderOptions.cs @@ -0,0 +1,43 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for getting a spot order by id endpoint + /// + public class GetSpotOrderOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve a spot order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to retrieve", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The order id of the order to retrieve", "123") + }; + + /// + /// ctor + /// + public GetSpotOrderOptions(string exchange, bool authenticated) + : base(exchange, authenticated, nameof(IGetSpotOrder.GetSpotOrderAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + public override Error? ValidateRequest(GetOrderRequest request, IGetSpotOrder client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrder/IGetSpotOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrder/IGetSpotOrder.cs new file mode 100644 index 00000000..46dd120d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrder/IGetSpotOrder.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving a spot order from an exchange. + /// + public interface IGetSpotOrder : ISharedApiCapability + { + /// + /// Spot get order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetSpotOrderOptions GetSpotOrderOptions { get; } + /// + /// Get info on a specific spot order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetSpotOrderAsync(GetOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving a spot order from an exchange via the REST API. + /// + public interface IGetSpotOrderRest : IGetSpotOrder, ISharedRest + { + /// + new Task> GetSpotOrderAsync(GetOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrderByClientOrderId/GetSpotOrderByClientOrderIdOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrderByClientOrderId/GetSpotOrderByClientOrderIdOptions.cs new file mode 100644 index 00000000..7f482701 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrderByClientOrderId/GetSpotOrderByClientOrderIdOptions.cs @@ -0,0 +1,43 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for getting a spot order by client order id endpoint + /// + public class GetSpotOrderByClientOrderIdOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve a spot order by its client order id"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to retrieve", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The client order id of the order to retrieve", "123") + }; + + /// + /// ctor + /// + public GetSpotOrderByClientOrderIdOptions(string exchange, bool authenticated) + : base(exchange, authenticated, nameof(IGetSpotOrderByClientOrderIdRest.GetSpotOrderByClientOrderIdAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + public override Error? ValidateRequest(GetOrderRequest request, IGetSpotOrderByClientOrderIdRest client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrderByClientOrderId/IGetSpotOrderByClientOrderId.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrderByClientOrderId/IGetSpotOrderByClientOrderId.cs new file mode 100644 index 00000000..bbff051b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/GetSpotOrderByClientOrderId/IGetSpotOrderByClientOrderId.cs @@ -0,0 +1,40 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving a spot order by client order id from an exchange. + /// + public interface IGetSpotOrderByClientOrderId : ISharedApiCapability + { + /// + /// Spot get order by client order id request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetSpotOrderByClientOrderIdOptions GetSpotOrderByClientOrderIdOptions { get; } + + /// + /// Get info on a specific spot order using a client order id, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetSpotOrderByClientOrderIdAsync(GetOrderRequest request, CancellationToken ct = default); + + } + + /// + /// Operation for retrieving a spot order by client order id from an exchange via the REST API. + /// + public interface IGetSpotOrderByClientOrderIdRest : IGetSpotOrderByClientOrderId, ISharedRest + { + /// + new Task> GetSpotOrderByClientOrderIdAsync(GetOrderRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/IPlaceMultipleSpotOrders.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/IPlaceMultipleSpotOrders.cs new file mode 100644 index 00000000..10aa26b3 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/IPlaceMultipleSpotOrders.cs @@ -0,0 +1,84 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for placing multiple spot order on an exchange in single call. + /// + public interface IPlaceMultipleSpotOrders : ISharedApiCapability + { + /// + /// How the trading fee is deducted + /// + SharedFeeDeductionType SpotFeeDeductionType { get; } + /// + /// How the asset is determined in which the trading fee is paid + /// + SharedFeeAssetType SpotFeeAssetType { get; } + /// + /// Supported order types for spot orders + /// + SharedOrderType[] SpotSupportedOrderTypes { get; } + /// + /// Supported time in force types for placing spot orders + /// + SharedTimeInForce[] SpotSupportedTimeInForce { get; } + /// + /// Supported quantity types for placing spot orders + /// + SharedQuantitySupport SpotSupportedOrderQuantity { get; } + /// + /// Max number of orders per request + /// + public int? MaxSpotOrdersPerRequest { get; } + /// + /// Whether orders for different symbols are allowed in a single place order request + /// + public bool PlaceMultipleSpotOrdersAllowsMultipleSymbols { get; } + + /// + /// Generate a new random client order id in a format that is accepted by the exchange. + /// + /// + string GenerateClientOrderId(); + + /// + /// Spot place order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ PlaceMultipleSpotOrdersOptions PlaceMultipleSpotOrdersOptions { get; } + + /// + /// Place multiple new spot orders, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task[]>> PlaceMultipleSpotOrdersAsync( + PlaceMultipleSpotOrdersRequest request, + CancellationToken ct = default); + } + + /// + /// Operation for placing multiple spot orders on an exchange via the REST API. + /// s + public interface IPlaceMultipleSpotOrdersRest : IPlaceMultipleSpotOrders, ISharedRest + { + /// + new Task[]>> PlaceMultipleSpotOrdersAsync(PlaceMultipleSpotOrdersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for placing multiple spot orders on an exchange via the WebSocket API. + /// + public interface IPlaceMultipleSpotOrdersSocket : IPlaceMultipleSpotOrders, ISharedSocket + { + /// + new Task[]>> PlaceMultipleSpotOrdersAsync(PlaceMultipleSpotOrdersRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/PlaceMultipleSpotOrdersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/PlaceMultipleSpotOrdersOptions.cs new file mode 100644 index 00000000..3eae71bb --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/PlaceMultipleSpotOrdersOptions.cs @@ -0,0 +1,82 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for placing multiple new spot orders + /// + public class PlaceMultipleSpotOrdersOptions : CapabilityOptions + { + /// + public override string Description => "Place multiple new spot orders"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Orders, "The orders to place", []), + }; + + + /// + /// ctor + /// + public PlaceMultipleSpotOrdersOptions(string exchange) + : base(exchange, true, nameof(IPlaceMultipleSpotOrders.PlaceMultipleSpotOrdersAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + /// Validate a request + /// + public override Error? ValidateRequest( + PlaceMultipleSpotOrdersRequest request, + IPlaceMultipleSpotOrders client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Orders.Length > client.MaxSpotOrdersPerRequest) + return ArgumentError.Invalid(nameof(PlaceMultipleSpotOrdersRequest.Orders), $"Too many orders, max {client.MaxSpotOrdersPerRequest}"); + + if (request.Orders.Length == 0) + return ArgumentError.Invalid(nameof(PlaceMultipleSpotOrdersRequest.Orders), "No orders provided"); + + if (request.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.TradingMode} is not supported, should be Spot"); + + if (!client.PlaceMultipleSpotOrdersAllowsMultipleSymbols) + { + var firstSymbol = request.Orders[0].Symbol; + if (request.Orders.Any(order => order.Symbol!.BaseAsset != firstSymbol!.BaseAsset + || order.Symbol.QuoteAsset != firstSymbol.QuoteAsset + || order.Symbol.TradingMode != firstSymbol.TradingMode)) + { + return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.Symbol), "All orders in the request should have matching symbols"); + } + } + + foreach (var order in request.Orders) + { + if (order.OrderType == SharedOrderType.Other) + throw new ArgumentException("OrderType can't be `Other`", nameof(order.OrderType)); + + if (!client.SpotSupportedOrderTypes.Contains(order.OrderType)) + return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.OrderType), "Order type not supported"); + + if (order.TimeInForce != null && !client.SpotSupportedTimeInForce.Contains(order.TimeInForce.Value)) + return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.TimeInForce), "Order time in force not supported"); + + var quantityError = client.SpotSupportedOrderQuantity.Validate(order.Side, order.OrderType, order.Quantity); + if (quantityError != null) + return quantityError; + } + + + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/PlaceMultipleSpotOrdersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/PlaceMultipleSpotOrdersRequest.cs new file mode 100644 index 00000000..696b554e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceMultipleSpotOrders/PlaceMultipleSpotOrdersRequest.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to place multiple new spot orders + /// + public record PlaceMultipleSpotOrdersRequest : SharedRequest + { + /// + /// Orders to place + /// + public PlaceSpotOrderRequest[] Orders { get; set; } + + /// + /// ctor + /// + /// Orders to place + /// Exchange specific parameters + public PlaceMultipleSpotOrdersRequest( + IEnumerable orders, + ExchangeParameters? exchangeParameters = null) : base(null, exchangeParameters) + { + Orders = orders.ToArray(); + TradingMode = Orders.FirstOrDefault()?.TradingMode; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceSpotOrder/IPlaceSpotOrder.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceSpotOrder/IPlaceSpotOrder.cs new file mode 100644 index 00000000..0e5f3fce --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceSpotOrder/IPlaceSpotOrder.cs @@ -0,0 +1,76 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for placing a spot order on an exchange. + /// + public interface IPlaceSpotOrder : ISharedApiCapability + { + /// + /// How the trading fee is deducted + /// + SharedFeeDeductionType SpotFeeDeductionType { get; } + /// + /// How the asset is determined in which the trading fee is paid + /// + SharedFeeAssetType SpotFeeAssetType { get; } + /// + /// Supported order types for spot orders + /// + SharedOrderType[] SpotSupportedOrderTypes { get; } + /// + /// Supported time in force types for placing spot orders + /// + SharedTimeInForce[] SpotSupportedTimeInForce { get; } + /// + /// Supported quantity types for placing spot orders + /// + SharedQuantitySupport SpotSupportedOrderQuantity { get; } + + /// + /// Generate a new random client order id in a format that is accepted by the exchange. + /// + /// + string GenerateClientOrderId(); + + /// + /// Spot place order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ PlaceSpotOrderOptions PlaceSpotOrderOptions { get; } + + /// + /// Place a new spot order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> PlaceSpotOrderAsync( + PlaceSpotOrderRequest request, + CancellationToken ct = default); + } + + /// + /// Operation for placing a spot order on an exchange via the REST API. + /// + public interface IPlaceSpotOrderRest : IPlaceSpotOrder, ISharedRest + { + /// + new Task> PlaceSpotOrderAsync(PlaceSpotOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for placing a spot order on an exchange via the WebSocket API. + /// + public interface IPlaceSpotOrderSocket : IPlaceSpotOrder, ISharedSocket + { + /// + new Task> PlaceSpotOrderAsync(PlaceSpotOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceSpotOrder/PlaceSpotOrderOptions.cs similarity index 51% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotOrderOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceSpotOrder/PlaceSpotOrderOptions.cs index e12ce6ea..4a1c9421 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PlaceSpotOrderOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceSpotOrder/PlaceSpotOrderOptions.cs @@ -1,5 +1,6 @@ using CryptoExchange.Net.Objects; using System; +using System.Collections; using System.Linq; namespace CryptoExchange.Net.SharedApis @@ -7,16 +8,28 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for placing a new spot order /// - public class PlaceSpotOrderOptions : EndpointOptions + public class PlaceSpotOrderOptions : CapabilityOptions { /// public override string Description => "Place a new spot order"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "Symbol", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderType, "Order type", SharedOrderType.Limit), + RequestParameterRule.Required(x => x.Side, "Order side", SharedOrderSide.Buy), + RequestParameterRule.Optional(x => x.TimeInForce, "Time in force", SharedTimeInForce.GoodTillCanceled), + RequestParameterRule.Optional(x => x.Quantity, "Order quantity", SharedQuantity.Base(0.1m)), + RequestParameterRule.Optional(x => x.Price, "Order price", 1m), + RequestParameterRule.Optional(x => x.ClientOrderId, "Client order id", "123") + }; + /// /// ctor /// - public PlaceSpotOrderOptions(string exchange) : base(exchange, true, nameof(ISpotOrderRestClient.PlaceSpotOrderAsync)) + public PlaceSpotOrderOptions(string exchange) + : base(exchange, true, nameof(IPlaceSpotOrder.PlaceSpotOrderAsync), _defaultParameterRules, SharedTradingModeSets.Spot) { } @@ -25,8 +38,12 @@ namespace CryptoExchange.Net.SharedApis ///
public override Error? ValidateRequest( PlaceSpotOrderRequest request, - ISpotOrderRestClient client) + IPlaceSpotOrder client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (request.Symbol!.TradingMode != TradingMode.Spot) return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); @@ -43,7 +60,7 @@ namespace CryptoExchange.Net.SharedApis if (quantityError != null) return quantityError; - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/PlaceSpotOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceSpotOrder/PlaceSpotOrderRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/PlaceSpotOrderRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/Spot/PlaceSpotOrder/PlaceSpotOrderRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Orders/Spot/SubscribeSpotOrder/ISubscribeSpotOrdersSocket.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/SubscribeSpotOrder/ISubscribeSpotOrdersSocket.cs new file mode 100644 index 00000000..fe3c721b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/SubscribeSpotOrder/ISubscribeSpotOrdersSocket.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to user spot order updates + /// + public interface ISubscribeSpotOrdersSocket : ISharedSubscription + { + /// + /// Spot orders subscription options + /// + SubscribeSpotOrderOptions SubscribeSpotOrderOptions { get; } + + /// + /// Subscribe to user spot order updates + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToSpotOrderUpdatesAsync(SubscribeSpotOrderRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeSpotOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/SubscribeSpotOrder/SubscribeSpotOrderOptions.cs similarity index 53% rename from CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeSpotOrderOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/Spot/SubscribeSpotOrder/SubscribeSpotOrderOptions.cs index 14326a20..1ce6e0d8 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeSpotOrderOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/SubscribeSpotOrder/SubscribeSpotOrderOptions.cs @@ -7,15 +7,18 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for subscribing to order updates /// - public class SubscribeSpotOrderOptions : EndpointOptions + public class SubscribeSpotOrderOptions : CapabilityOptions { /// public override string Description => "Subscribe to spot order updates"; + private static readonly RequestParameterDescription[] _defaultParameterRules = []; + /// /// ctor /// - public SubscribeSpotOrderOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ISpotOrderSocketClient.SubscribeToSpotOrderUpdatesAsync)) + public SubscribeSpotOrderOptions(string exchange, bool needsAuthentication) + : base(exchange, needsAuthentication, nameof(ISubscribeSpotOrdersSocket.SubscribeToSpotOrderUpdatesAsync), _defaultParameterRules, SharedTradingModeSets.Spot) { } } diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeSpotOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/Orders/Spot/SubscribeSpotOrder/SubscribeSpotOrderRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeSpotOrderRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Orders/Spot/SubscribeSpotOrder/SubscribeSpotOrderRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/PositionMode/GetPositionMode/GetPositionModeOptions.cs b/CryptoExchange.Net/SharedApis/V2/PositionMode/GetPositionMode/GetPositionModeOptions.cs new file mode 100644 index 00000000..3fc1c103 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/PositionMode/GetPositionMode/GetPositionModeOptions.cs @@ -0,0 +1,24 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting current position mode + /// + public class GetPositionModeOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve the current futures position mode"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "The trading mode to retrieve the position mode for", TradingMode.PerpetualLinear), + RequestParameterRule.Optional(x => x.Symbol, "The symbol to retrieve the position mode for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetPositionModeOptions(string exchange) : base(exchange, true, nameof(IGetPositionMode.GetPositionModeAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetPositionModeRequest.cs b/CryptoExchange.Net/SharedApis/V2/PositionMode/GetPositionMode/GetPositionModeRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetPositionModeRequest.cs rename to CryptoExchange.Net/SharedApis/V2/PositionMode/GetPositionMode/GetPositionModeRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/PositionMode/GetPositionMode/IGetPositionMode.cs b/CryptoExchange.Net/SharedApis/V2/PositionMode/GetPositionMode/IGetPositionMode.cs new file mode 100644 index 00000000..dcc176d7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/PositionMode/GetPositionMode/IGetPositionMode.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for getting the current user position mode setting from an exchange. + /// + public interface IGetPositionMode : ISharedApiCapability + { + /// + /// Position mode request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetPositionModeOptions GetPositionModeOptions { get; } + /// + /// Get the current position mode setting, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetPositionModeAsync(GetPositionModeRequest request, CancellationToken ct = default); + } + + /// + /// Operation for getting the current user position mode setting from an exchange via the REST API. + /// + public interface IGetPositionModeRest : IGetPositionMode, ISharedRest + { + /// + new Task> GetPositionModeAsync(GetPositionModeRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/PositionMode/SetPositionMode/ISetPositionMode.cs b/CryptoExchange.Net/SharedApis/V2/PositionMode/SetPositionMode/ISetPositionMode.cs new file mode 100644 index 00000000..ad12dee4 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/PositionMode/SetPositionMode/ISetPositionMode.cs @@ -0,0 +1,42 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for setting the user position mode on an exchange. + /// + public interface ISetPositionMode : ISharedApiCapability + { + /// + /// How the exchange handles setting the position mode + /// + SharedPositionModeSelection PositionModeSettingType { get; } + + /// + /// Position mode set request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ SetPositionModeOptions SetPositionModeOptions { get; } + /// + /// Set the position mode to a new value, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> SetPositionModeAsync(SetPositionModeRequest request, CancellationToken ct = default); + } + + /// + /// Operation for setting the user position mode on an exchange via the REST API. + /// + public interface ISetPositionModeRest : ISetPositionMode, ISharedRest + { + /// + new Task> SetPositionModeAsync(SetPositionModeRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/PositionMode/SetPositionMode/SetPositionModeOptions.cs b/CryptoExchange.Net/SharedApis/V2/PositionMode/SetPositionMode/SetPositionModeOptions.cs new file mode 100644 index 00000000..4c2899be --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/PositionMode/SetPositionMode/SetPositionModeOptions.cs @@ -0,0 +1,25 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for setting position mode + /// + public class SetPositionModeOptions : CapabilityOptions + { + /// + public override string Description => "Set the futures position mode"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "The trading mode to set the position mode for", TradingMode.PerpetualLinear), + RequestParameterRule.Optional(x => x.Symbol, "The symbol to set the position mode for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.PositionMode, "The position mode to set", SharedPositionMode.OneWay), + }; + + /// + /// ctor + /// + public SetPositionModeOptions(string exchange) : base(exchange, true, nameof(ISetPositionMode.SetPositionModeAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/SetPositionModeRequest.cs b/CryptoExchange.Net/SharedApis/V2/PositionMode/SetPositionMode/SetPositionModeRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/SetPositionModeRequest.cs rename to CryptoExchange.Net/SharedApis/V2/PositionMode/SetPositionMode/SetPositionModeRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/CloseFullPositionOptions.cs b/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/CloseFullPositionOptions.cs new file mode 100644 index 00000000..8c2428f3 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/CloseFullPositionOptions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for fully closing a position + /// + public class CloseFullPositionOptions : CapabilityOptions + { + /// + public override string Description => "Fully close an open futures position"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the position to close", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.PositionMode, "The current position mode of the account", SharedPositionMode.OneWay), + RequestParameterRule.Optional(x => x.PositionSide, "The side of the position to close", SharedPositionSide.Long), + RequestParameterRule.Optional(x => x.MarginMode, "The margin mode of the position", SharedMarginMode.Cross), + RequestParameterRule.Optional(x => x.PositionId, "The id of the position to close", "12345"), + }; + + /// + /// ctor + /// + public CloseFullPositionOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ICloseFullPosition.CloseFullPositionAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/CloseFullPositionRequest.cs b/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/CloseFullPositionRequest.cs new file mode 100644 index 00000000..3634ec51 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/CloseFullPositionRequest.cs @@ -0,0 +1,49 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to fully close a currently open position + /// + public record CloseFullPositionRequest : SharedSymbolRequest + { + /// + /// The id of the position to close + /// + public string? PositionId { get; set; } + + /// + /// The current position mode of the account for the symbol + /// + public SharedPositionMode? PositionMode { get; set; } + /// + /// The position side to close. Required when in hedge mode + /// + public SharedPositionSide? PositionSide { get; set; } + /// + /// Margin mode + /// + public SharedMarginMode? MarginMode { get; set; } + + /// + /// ctor + /// + /// Symbol to close the position on + /// The current position mode of the account for the symbol + /// The position side to close. Required when in hedge mode + /// Margin mode + /// Id of the position to close + /// Exchange specific parameters + public CloseFullPositionRequest( + SharedSymbol symbol, + SharedPositionMode? mode = null, + SharedPositionSide? positionSide = null, + SharedMarginMode? marginMode = null, + string? positionId = null, + ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + { + PositionId = positionId; + PositionMode = mode; + PositionSide = positionSide; + MarginMode = marginMode; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/ICloseFullPosition.cs b/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/ICloseFullPosition.cs new file mode 100644 index 00000000..75f232c6 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Positions/ClosePosition/ICloseFullPosition.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for fully closing an open position on an exchange. + /// + public interface ICloseFullPosition : ISharedApiCapability + { + /// + /// Close position order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CloseFullPositionOptions CloseFullPositionOptions { get; } + /// + /// Fully close a currently open position, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> CloseFullPositionAsync(CloseFullPositionRequest request, CancellationToken ct = default); + } + + /// + /// Operation for fully closing an open position on an exchange via the REST API. + /// + public interface ICloseFullPositionRest : ICloseFullPosition, ISharedRest + { + /// + new Task> CloseFullPositionAsync(CloseFullPositionRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionHistoryOptions.cs b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositionHistory/GetPositionHistoryOptions.cs similarity index 55% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionHistoryOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Positions/GetPositionHistory/GetPositionHistoryOptions.cs index e362b8ee..afd50285 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionHistoryOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositionHistory/GetPositionHistoryOptions.cs @@ -7,22 +7,36 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting position history /// - public class GetPositionHistoryOptions : PaginatedEndpointOptions + public class GetPositionHistoryOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve historical futures positions"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the result set by trading mode", TradingMode.PerpetualLinear), + RequestParameterRule.Optional(x => x.Symbol, "Filter the result set by symbol", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// public GetPositionHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IPositionHistoryRestClient.GetPositionHistoryAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetPositionHistoryRest.GetPositionHistoryAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { } /// - public override Error? ValidateRequest(GetPositionHistoryRequest request, IPositionHistoryRestClient client) + public override Error? ValidateRequest(GetPositionHistoryRequest request, IGetPositionHistoryRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!SupportsAscending && request.Direction == DataDirection.Ascending) return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); @@ -43,7 +57,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetPositionHistoryRequest.cs b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositionHistory/GetPositionHistoryRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetPositionHistoryRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Positions/GetPositionHistory/GetPositionHistoryRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Positions/GetPositionHistory/IGetPositionHistory.cs b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositionHistory/IGetPositionHistory.cs new file mode 100644 index 00000000..dbbf9d45 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositionHistory/IGetPositionHistory.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving position history from an exchange. + /// + public interface IGetPositionHistory : ISharedApiCapability + { + /// + /// Position history request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetPositionHistoryOptions GetPositionHistoryOptions { get; } + /// + /// Get position history, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetPositionHistoryAsync(GetPositionHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving position history from an exchange via the REST API. + /// + public interface IGetPositionHistoryRest : IGetPositionHistory, ISharedRest + { + /// + new Task> GetPositionHistoryAsync(GetPositionHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Positions/GetPositions/GetPositionsOptions.cs b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositions/GetPositionsOptions.cs new file mode 100644 index 00000000..165f94a1 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositions/GetPositionsOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting open positions + /// + public class GetPositionsOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve open futures positions"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the result set by trading mode", TradingMode.PerpetualLinear), + RequestParameterRule.Optional(x => x.Symbol, "Filter the result set by symbol", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + }; + + /// + /// ctor + /// + public GetPositionsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetPositions.GetPositionsAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetPositionsRequest.cs b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositions/GetPositionsRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetPositionsRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Positions/GetPositions/GetPositionsRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Positions/GetPositions/IGetPositions.cs b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositions/IGetPositions.cs new file mode 100644 index 00000000..66520db0 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Positions/GetPositions/IGetPositions.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving open positions from an exchange. + /// + public interface IGetPositions : ISharedApiCapability + { + /// + /// Positions request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetPositionsOptions GetPositionsOptions { get; } + /// + /// Get open position info, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetPositionsAsync(GetPositionsRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving open positions from an exchange via the REST API. + /// + public interface IGetPositionsRest : IGetPositions, ISharedRest + { + /// + new Task> GetPositionsAsync(GetPositionsRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Positions/SubscribePositions/ISubscribePositions.cs b/CryptoExchange.Net/SharedApis/V2/Positions/SubscribePositions/ISubscribePositions.cs new file mode 100644 index 00000000..d6dfc581 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Positions/SubscribePositions/ISubscribePositions.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using System.Threading; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Objects; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to position updates + /// + public interface ISubscribePositionsSocket : ISharedSubscription + { + /// + /// Position subscription options + /// + SubscribePositionOptions SubscribePositionOptions { get; } + + /// + /// Subscribe to user position updates + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToPositionUpdatesAsync(SubscribePositionRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribePositionOptions.cs b/CryptoExchange.Net/SharedApis/V2/Positions/SubscribePositions/SubscribePositionOptions.cs similarity index 51% rename from CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribePositionOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Positions/SubscribePositions/SubscribePositionOptions.cs index f313f4d5..cac8061f 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribePositionOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Positions/SubscribePositions/SubscribePositionOptions.cs @@ -7,15 +7,20 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for subscribing to position updates /// - public class SubscribePositionOptions : EndpointOptions + public class SubscribePositionOptions : CapabilityOptions { /// public override string Description => "Subscribe to futures position updates"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter position updates by trading mode", TradingMode.PerpetualLinear), + }; + /// /// ctor /// - public SubscribePositionOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(IPositionSocketClient.SubscribeToPositionUpdatesAsync)) + public SubscribePositionOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ISubscribePositionsSocket.SubscribeToPositionUpdatesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { } } diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribePositionRequest.cs b/CryptoExchange.Net/SharedApis/V2/Positions/SubscribePositions/SubscribePositionRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribePositionRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Positions/SubscribePositions/SubscribePositionRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedRequest.cs b/CryptoExchange.Net/SharedApis/V2/SharedRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/SharedRequest.cs rename to CryptoExchange.Net/SharedApis/V2/SharedRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/SharedSymbolRequest.cs b/CryptoExchange.Net/SharedApis/V2/SharedSymbolRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/SharedSymbolRequest.cs rename to CryptoExchange.Net/SharedApis/V2/SharedSymbolRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesSymbolsOptions.cs b/CryptoExchange.Net/SharedApis/V2/Symbols/GetFuturesSymbols/GetFuturesSymbolsOptions.cs similarity index 56% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesSymbolsOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Symbols/GetFuturesSymbols/GetFuturesSymbolsOptions.cs index 137bd68c..91a39a55 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesSymbolsOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Symbols/GetFuturesSymbols/GetFuturesSymbolsOptions.cs @@ -8,36 +8,49 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting symbol info /// - public class GetFuturesSymbolsOptions : EndpointOptions + public class GetFuturesSymbolsOptions : CapabilityOptions { /// public override string Description => "Retrieve supported futures symbols and their trading rules"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the symbols by trading mode", TradingMode.PerpetualLinear), + RequestParameterRule.Optional(x => x.BaseAssetType, "Filter by base asset type", SharedAssetType.Crypto), + RequestParameterRule.Optional(x => x.BaseAssetSubType, "Filter by base asset subtype", SharedAssetSubType.StableCoin), + RequestParameterRule.Optional(x => x.QuoteAssetType, "Filter by quote asset type", SharedAssetType.Crypto), + RequestParameterRule.Optional(x => x.QuoteAssetSubType, "Filter by quote asset subtype", SharedAssetSubType.StableCoin), + }; + /// /// ctor /// - public GetFuturesSymbolsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesSymbolRestClient.GetFuturesSymbolsAsync)) + public GetFuturesSymbolsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetFuturesSymbols.GetFuturesSymbolsAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { } /// - public override Error? ValidateRequest(GetSymbolsRequest request, IFuturesSymbolRestClient client) + public override Error? ValidateRequest(GetSymbolsRequest request, IGetFuturesSymbols client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (request.BaseAssetType != null && request.BaseAssetSubType != null) { - var error = ValidateAssetTypeCombination(request.BaseAssetType.Value, request.BaseAssetSubType.Value); + error = ValidateAssetTypeCombination(request.BaseAssetType.Value, request.BaseAssetSubType.Value); if (error != null) return error; } if (request.QuoteAssetType != null && request.QuoteAssetSubType != null) { - var error = ValidateAssetTypeCombination(request.QuoteAssetType.Value, request.QuoteAssetSubType.Value); + error = ValidateAssetTypeCombination(request.QuoteAssetType.Value, request.QuoteAssetSubType.Value); if (error != null) return error; } - return base.ValidateRequest(request, client); + return null; } private Error? ValidateAssetTypeCombination(SharedAssetType type, SharedAssetSubType subType) diff --git a/CryptoExchange.Net/SharedApis/V2/Symbols/GetFuturesSymbols/IGetFuturesSymbols.cs b/CryptoExchange.Net/SharedApis/V2/Symbols/GetFuturesSymbols/IGetFuturesSymbols.cs new file mode 100644 index 00000000..9c698fa3 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Symbols/GetFuturesSymbols/IGetFuturesSymbols.cs @@ -0,0 +1,61 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for getting supported futures symbols from an exchange. + /// + public interface IGetFuturesSymbols : ISharedApiCapability + { + /// + /// Get the futures symbol catalog. Only available if has been called previously. + /// + SharedSymbolCatalog? FuturesSymbolCatalog { get; } + + /// + /// Futures symbol request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFuturesSymbolsOptions GetFuturesSymbolsOptions { get; } + + /// + /// Get all futures symbols for a specific base asset + /// + /// Asset, for example `ETH` + Task> GetFuturesSymbolsForBaseAssetAsync(string baseAsset); + + /// + /// Gets whether the client supports a futures symbol + /// + /// The symbol + Task> SupportsFuturesSymbolAsync(SharedSymbol symbol); + + /// + /// Gets whether the client supports a futures symbol + /// + /// The symbol name + Task> SupportsFuturesSymbolAsync(string symbolName); + + /// + /// Get info on all futures symbols supported on the exchange, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetFuturesSymbolsAsync(GetSymbolsRequest request, CancellationToken ct = default); + } + + /// + /// Operation for getting supported futures symbols from an exchange via the REST API. + /// + public interface IGetFuturesSymbolsRest : IGetFuturesSymbols, ISharedRest + { + /// + new Task> GetFuturesSymbolsAsync(GetSymbolsRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotSymbolsOptions.cs b/CryptoExchange.Net/SharedApis/V2/Symbols/GetSpotSymbols/GetSpotSymbolsOptions.cs similarity index 56% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotSymbolsOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Symbols/GetSpotSymbols/GetSpotSymbolsOptions.cs index bf3bf192..0273a780 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotSymbolsOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Symbols/GetSpotSymbols/GetSpotSymbolsOptions.cs @@ -8,37 +8,50 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting symbol info /// - public class GetSpotSymbolsOptions : EndpointOptions + public class GetSpotSymbolsOptions : CapabilityOptions { /// public override string Description => "Retrieve supported spot symbols and their trading rules"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter the symbols by trading mode", TradingMode.Spot), + RequestParameterRule.Optional(x => x.BaseAssetType, "Filter by base asset type", SharedAssetType.Crypto), + RequestParameterRule.Optional(x => x.BaseAssetSubType, "Filter by base asset subtype", SharedAssetSubType.StableCoin), + RequestParameterRule.Optional(x => x.QuoteAssetType, "Filter by quote asset type", SharedAssetType.Crypto), + RequestParameterRule.Optional(x => x.QuoteAssetSubType, "Filter by quote asset subtype", SharedAssetSubType.StableCoin), + }; + /// /// ctor /// - public GetSpotSymbolsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotSymbolRestClient.GetSpotSymbolsAsync)) + public GetSpotSymbolsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetSpotSymbols.GetSpotSymbolsAsync), _defaultParameterRules, SharedTradingModeSets.Spot) { } /// - public override Error? ValidateRequest(GetSymbolsRequest request, ISpotSymbolRestClient client) + public override Error? ValidateRequest(GetSymbolsRequest request, IGetSpotSymbols client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (request.BaseAssetType != null && request.BaseAssetSubType != null) { - var error = ValidateAssetTypeCombination(request.BaseAssetType.Value, request.BaseAssetSubType.Value); + error = ValidateAssetTypeCombination(request.BaseAssetType.Value, request.BaseAssetSubType.Value); if (error != null) return error; } if (request.QuoteAssetType != null && request.QuoteAssetSubType != null) { - var error = ValidateAssetTypeCombination(request.QuoteAssetType.Value, request.QuoteAssetSubType.Value); + error = ValidateAssetTypeCombination(request.QuoteAssetType.Value, request.QuoteAssetSubType.Value); if (error != null) return error; } - return base.ValidateRequest(request, client); + return null; } private Error? ValidateAssetTypeCombination(SharedAssetType type, SharedAssetSubType subType) diff --git a/CryptoExchange.Net/SharedApis/V2/Symbols/GetSpotSymbols/IGetSpotSymbols.cs b/CryptoExchange.Net/SharedApis/V2/Symbols/GetSpotSymbols/IGetSpotSymbols.cs new file mode 100644 index 00000000..28f59927 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Symbols/GetSpotSymbols/IGetSpotSymbols.cs @@ -0,0 +1,61 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for getting supported spot symbols from an exchange. + /// + public interface IGetSpotSymbols: ISharedApiCapability + { + /// + /// Get the spot symbol catalog. Only available if has been called previously. + /// + SharedSymbolCatalog? SpotSymbolCatalog { get; } + + /// + /// Get all spot symbols for a specific base asset + /// + /// Asset, for example `ETH` + Task> GetSpotSymbolsForBaseAssetAsync(string baseAsset); + + /// + /// Gets whether the client supports a spot symbol + /// + /// The symbol + Task> SupportsSpotSymbolAsync(SharedSymbol symbol); + + /// + /// Gets whether the client supports a spot symbol + /// + /// The symbol name + Task> SupportsSpotSymbolAsync(string symbolName); + + /// + /// Spot symbols request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetSpotSymbolsOptions GetSpotSymbolsOptions { get; } + + /// + /// Get info on all available spot symbols on the exchange, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetSpotSymbolsAsync(GetSymbolsRequest request, CancellationToken ct = default); + } + + /// + /// Operation for getting supported futures symbols from an exchange via the REST API. + /// + public interface IGetSpotSymbolsRest : IGetSpotSymbols, ISharedRest + { + /// + new Task> GetSpotSymbolsAsync(GetSymbolsRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetSymbolsRequest.cs b/CryptoExchange.Net/SharedApis/V2/Symbols/GetSymbolsRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetSymbolsRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Symbols/GetSymbolsRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTickersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/GetAllTickers/GetAllTickersOptions.cs similarity index 58% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTickersOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/GetAllTickers/GetAllTickersOptions.cs index 196ee6c6..12c3d317 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTickersOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/GetAllTickers/GetAllTickersOptions.cs @@ -5,10 +5,15 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting tickers /// - public class GetFuturesTickersOptions : EndpointOptions + public class GetAllTickersOptions : CapabilityOptions { /// - public override string Description => "Retrieve price ticker information for all futures symbols"; + public override string Description => "Retrieve price ticker information for all symbols in a trading mode"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "The trading mode to retrieve tickers for", TradingMode.Spot), + }; /// /// Type of ticker calculation @@ -18,7 +23,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public GetFuturesTickersOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(IFuturesTickerRestClient.GetFuturesTickersAsync)) + public GetAllTickersOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(IGetAllTickers.GetAllTickersAsync), _defaultParameterRules) { TickerType = tickerCalcType ?? SharedTickerType.Day24H; } diff --git a/CryptoExchange.Net/SharedApis/V2/Tickers/GetAllTickers/IGetAllTickers.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/GetAllTickers/IGetAllTickers.cs new file mode 100644 index 00000000..745bc8ca --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/GetAllTickers/IGetAllTickers.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving all tickers from an exchange. + /// + public interface IGetAllTickers : ISharedApiCapability + { + /// + /// Tickers request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetAllTickersOptions GetAllTickersOptions { get; } + /// + /// Get tickers for all symbols, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetAllTickersAsync(GetTickersRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving all tickers from an exchange via the REST API. + /// + public interface IGetAllTickersRest : IGetAllTickers, ISharedRest + { + /// + new Task> GetAllTickersAsync(GetTickersRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTickerOptions.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/GetTicker/GetTickerOptions.cs similarity index 58% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTickerOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/GetTicker/GetTickerOptions.cs index 4d25a887..1d434065 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesTickerOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/GetTicker/GetTickerOptions.cs @@ -5,10 +5,15 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting ticker /// - public class GetFuturesTickerOptions : EndpointOptions + public class GetTickerOptions : CapabilityOptions { /// - public override string Description => "Retrieve price ticker information for a futures symbol"; + public override string Description => "Retrieve price ticker information for a symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve ticker information for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + }; /// /// Type of ticker calculation @@ -18,7 +23,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public GetFuturesTickerOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(IFuturesTickerRestClient.GetFuturesTickerAsync)) + public GetTickerOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(IGetTicker.GetTickerAsync), _defaultParameterRules) { TickerType = tickerCalcType ?? SharedTickerType.Day24H; } diff --git a/CryptoExchange.Net/SharedApis/V2/Tickers/GetTicker/IGetTicker.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/GetTicker/IGetTicker.cs new file mode 100644 index 00000000..0a867188 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/GetTicker/IGetTicker.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving a single ticker from an exchange. + /// + public interface IGetTicker : ISharedApiCapability + { + /// + /// Ticker request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetTickerOptions GetTickerOptions { get; } + /// + /// Get ticker for a specific symbol, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetTickerAsync(GetTickerRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving a single ticker from an exchange via the REST API + /// + public interface IGetTickerRest : IGetTicker, ISharedRest + { + /// + new Task> GetTickerAsync(GetTickerRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetTickerRequest.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/GetTickerRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetTickerRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/GetTickerRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetTickersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/GetTickersRequest.cs similarity index 72% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetTickersRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/GetTickersRequest.cs index af56d2ab..ce8b9bd0 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetTickersRequest.cs +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/GetTickersRequest.cs @@ -1,4 +1,4 @@ -namespace CryptoExchange.Net.SharedApis +namespace CryptoExchange.Net.SharedApis { /// /// Request to retrieve all symbol tickers @@ -8,7 +8,7 @@ /// /// ctor /// - /// Trading mode + /// The trading mode to retrieve tickers for. Required when using . /// Exchange specific parameters public GetTickersRequest(TradingMode? tradingMode = null, ExchangeParameters? exchangeParameters = null) : base(tradingMode, exchangeParameters) { diff --git a/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeAllTickers/ISubscribeAllTickers.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeAllTickers/ISubscribeAllTickers.cs new file mode 100644 index 00000000..2817522f --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeAllTickers/ISubscribeAllTickers.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to ticker updates for all symbols + /// + public interface ISubscribeAllTickersSocket : ISharedSubscription + { + /// + /// Tickers subscription options + /// + SubscribeTickersOptions SubscribeAllTickersOptions { get; } + + /// + /// Subscribe to tickers updates for all symbols + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToAllTickersUpdatesAsync(SubscribeAllTickersRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeAllTickersRequest.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeAllTickers/SubscribeAllTickersRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeAllTickersRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeAllTickers/SubscribeAllTickersRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTickersOptions.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeAllTickers/SubscribeTickersOptions.cs similarity index 59% rename from CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTickersOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeAllTickers/SubscribeTickersOptions.cs index 1e8c6e5a..6fd661b1 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTickersOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeAllTickers/SubscribeTickersOptions.cs @@ -3,11 +3,16 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for subscribing to ticker updates /// - public class SubscribeTickersOptions : EndpointOptions + public class SubscribeTickersOptions : CapabilityOptions { /// public override string Description => "Subscribe to price ticker updates for all symbols"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter ticker updates by trading mode", TradingMode.Spot), + }; + /// /// Type of ticker calculation /// @@ -16,7 +21,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public SubscribeTickersOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(ITickersSocketClient.SubscribeToAllTickersUpdatesAsync)) + public SubscribeTickersOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(ISubscribeAllTickersSocket.SubscribeToAllTickersUpdatesAsync), _defaultParameterRules) { TickerType = tickerCalcType ?? SharedTickerType.Day24H; } diff --git a/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/ISubscribeBookTicker.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/ISubscribeBookTicker.cs new file mode 100644 index 00000000..07b17510 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/ISubscribeBookTicker.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to book ticker updates for a symbol + /// + public interface ISubscribeBookTickerSocket : ISharedSubscription + { + /// + /// Book ticker subscription options + /// + SubscribeBookTickerOptions SubscribeBookTickerOptions { get; } + + /// + /// Subscribe to book ticker (best ask/bid) updates for a symbol + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToBookTickerUpdatesAsync(SubscribeBookTickerRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/SubscribeBookTickerOptions.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/SubscribeBookTickerOptions.cs new file mode 100644 index 00000000..90f19452 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/SubscribeBookTickerOptions.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for subscribing to book ticker updates + /// + public class SubscribeBookTickerOptions : CapabilityOptions + { + /// + public override string Description => "Subscribe to best bid and ask price updates"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.Spot, "ETH", "USDT") }), + }; + + /// + /// ctor + /// + public SubscribeBookTickerOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ISubscribeBookTickerSocket.SubscribeToBookTickerUpdatesAsync), _defaultParameterRules) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeBookTickerRequest.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/SubscribeBookTickerRequest.cs similarity index 86% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeBookTickerRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/SubscribeBookTickerRequest.cs index 6cabc03a..c3cc4dc1 100644 --- a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeBookTickerRequest.cs +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeBookTicker/SubscribeBookTickerRequest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis { @@ -29,6 +30,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor /// /// The symbols to subscribe to + [Obsolete("Use SubscribeBookTickerRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) instead")] public SubscribeBookTickerRequest(params SharedSymbol[] symbols) : base(symbols, null) { } diff --git a/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/ISubscribeTicker.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/ISubscribeTicker.cs new file mode 100644 index 00000000..4dacde2d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/ISubscribeTicker.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to ticker updates for a symbol + /// + public interface ISubscribeTickerSocket : ISharedSubscription + { + /// + /// Ticker subscription options + /// + SubscribeTickerOptions SubscribeTickerOptions { get; } + + /// + /// Subscribe to ticker updates for a symbol + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToTickerUpdatesAsync(SubscribeTickerRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTickerOptions.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/SubscribeTickerOptions.cs similarity index 51% rename from CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTickerOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/SubscribeTickerOptions.cs index 93246dbf..06e5a97a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeTickerOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/SubscribeTickerOptions.cs @@ -3,11 +3,17 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for subscribing to ticker updates /// - public class SubscribeTickerOptions : EndpointOptions + public class SubscribeTickerOptions : CapabilityOptions { /// public override string Description => "Subscribe to price ticker updates for a symbol"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.Spot, "ETH", "USDT") }), + }; + /// /// Type of ticker calculation /// @@ -16,7 +22,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public SubscribeTickerOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(ITickerSocketClient.SubscribeToTickerUpdatesAsync)) + public SubscribeTickerOptions(string exchange, SharedTickerType? tickerCalcType = null) : base(exchange, false, nameof(ISubscribeTickerSocket.SubscribeToTickerUpdatesAsync), _defaultParameterRules) { TickerType = tickerCalcType ?? SharedTickerType.Day24H; } diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeTickerRequest.cs b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/SubscribeTickerRequest.cs similarity index 86% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeTickerRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/SubscribeTickerRequest.cs index fb15db08..a0771ac6 100644 --- a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeTickerRequest.cs +++ b/CryptoExchange.Net/SharedApis/V2/Tickers/SubscribeTicker/SubscribeTickerRequest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis { @@ -31,6 +32,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
/// The symbols to subscribe to + [Obsolete("Use SubscribeTickerRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) instead")] public SubscribeTickerRequest(params SharedSymbol[] symbols) : base(symbols, null) { } diff --git a/CryptoExchange.Net/SharedApis/V2/TpSl/CancelFuturesTpSl/CancelFuturesTpSlOptions.cs b/CryptoExchange.Net/SharedApis/V2/TpSl/CancelFuturesTpSl/CancelFuturesTpSlOptions.cs new file mode 100644 index 00000000..b1d4bc29 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TpSl/CancelFuturesTpSl/CancelFuturesTpSlOptions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling a TP/SL + /// + public class CancelFuturesTpSlOptions : CapabilityOptions + { + /// + public override string Description => "Cancel a take profit or stop loss order for a futures position"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the take profit or stop loss order to cancel", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.OrderId, "The id of the take profit or stop loss order to cancel", "123"), + RequestParameterRule.Optional(x => x.PositionMode, "The position mode of the account", SharedPositionMode.OneWay), + RequestParameterRule.Optional(x => x.PositionSide, "The side of the position", SharedPositionSide.Long), + RequestParameterRule.Optional(x => x.TpSlSide, "The take profit or stop loss side to cancel", SharedTpSlSide.TakeProfit), + RequestParameterRule.Optional(x => x.MarginMode, "The margin mode of the position", SharedMarginMode.Cross), + }; + + /// + /// ctor + /// + public CancelFuturesTpSlOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ICancelFuturesTpSl.CancelFuturesTpSlAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/CancelTpSlRequest.cs b/CryptoExchange.Net/SharedApis/V2/TpSl/CancelFuturesTpSl/CancelTpSlRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/CancelTpSlRequest.cs rename to CryptoExchange.Net/SharedApis/V2/TpSl/CancelFuturesTpSl/CancelTpSlRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/TpSl/CancelFuturesTpSl/ICancelFuturesTpSl.cs b/CryptoExchange.Net/SharedApis/V2/TpSl/CancelFuturesTpSl/ICancelFuturesTpSl.cs new file mode 100644 index 00000000..ea84e6dc --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TpSl/CancelFuturesTpSl/ICancelFuturesTpSl.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling a take profit and/or stop loss for an open position on an exchange. + /// + public interface ICancelFuturesTpSl : ISharedApiCapability + { + /// + /// Cancel a take profit and/or stop loss options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelFuturesTpSlOptions CancelFuturesTpSlOptions { get; } + /// + /// Cancel an active take profit and/or stop loss for an open position, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> CancelFuturesTpSlAsync(CancelTpSlRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling a take profit and/or stop loss for an open position on an exchange via the REST API. + /// + public interface ICancelFuturesTpSlRest : ICancelFuturesTpSl, ISharedRest + { + /// + new Task> CancelFuturesTpSlAsync(CancelTpSlRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TpSl/SetFuturesTpSl/ISetFuturesTpSl.cs b/CryptoExchange.Net/SharedApis/V2/TpSl/SetFuturesTpSl/ISetFuturesTpSl.cs new file mode 100644 index 00000000..5e8ce4cd --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TpSl/SetFuturesTpSl/ISetFuturesTpSl.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for setting a take profit and/or stop loss for an open position on an exchange. + /// + public interface ISetFuturesTpSl : ISharedApiCapability + { + /// + /// Set take profit and/or stop loss options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ SetFuturesTpSlOptions SetFuturesTpSlOptions { get; } + /// + /// Set a take profit and/or stop loss for an open position, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> SetFuturesTpSlAsync(SetTpSlRequest request, CancellationToken ct = default); + } + + /// + /// Operation for setting a take profit and/or stop loss for an open position on an exchange via the REST API. + /// + public interface ISetFuturesTpSlRest : ISetFuturesTpSl, ISharedRest + { + /// + new Task> SetFuturesTpSlAsync(SetTpSlRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TpSl/SetFuturesTpSl/SetFuturesTpSlOptions.cs b/CryptoExchange.Net/SharedApis/V2/TpSl/SetFuturesTpSl/SetFuturesTpSlOptions.cs new file mode 100644 index 00000000..0bb3991d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TpSl/SetFuturesTpSl/SetFuturesTpSlOptions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for setting a TP/SL + /// + public class SetFuturesTpSlOptions : CapabilityOptions + { + /// + public override string Description => "Set take profit or stop loss values for a futures position"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to set take profit or stop loss for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.PositionSide, "The side of the position", SharedPositionSide.Long), + RequestParameterRule.Required(x => x.TpSlSide, "Whether to set take profit or stop loss", SharedTpSlSide.TakeProfit), + RequestParameterRule.Required(x => x.TriggerPrice, "The trigger price", 1m), + RequestParameterRule.Optional(x => x.PositionMode, "The position mode of the account", SharedPositionMode.OneWay), + RequestParameterRule.Optional(x => x.MarginMode, "The margin mode of the position", SharedMarginMode.Cross), + RequestParameterRule.Optional(x => x.Quantity, "The quantity to close", 1m), + }; + + /// + /// ctor + /// + public SetFuturesTpSlOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISetFuturesTpSl.SetFuturesTpSlAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/SetTpSlRequest.cs b/CryptoExchange.Net/SharedApis/V2/TpSl/SetFuturesTpSl/SetTpSlRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/SetTpSlRequest.cs rename to CryptoExchange.Net/SharedApis/V2/TpSl/SetFuturesTpSl/SetTpSlRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetRecentTradesOptions.cs b/CryptoExchange.Net/SharedApis/V2/Trades/GetRecentTrades/GetRecentTradesOptions.cs similarity index 66% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetRecentTradesOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Trades/GetRecentTrades/GetRecentTradesOptions.cs index 620ba54c..372e88e2 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetRecentTradesOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Trades/GetRecentTrades/GetRecentTradesOptions.cs @@ -6,11 +6,17 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting recent trades /// - public class GetRecentTradesOptions : EndpointOptions + public class GetRecentTradesOptions : CapabilityOptions { /// public override string Description => "Retrieve recent public trades for a symbol"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve recent trades for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + }; + /// /// The max number of trades that can be requested /// @@ -20,13 +26,13 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
public GetRecentTradesOptions(string exchange, int limit, bool authenticated) - : base(exchange, authenticated, nameof(IRecentTradeRestClient.GetRecentTradesAsync)) + : base(exchange, authenticated, nameof(IGetRecentTrades.GetRecentTradesAsync), _defaultParameterRules) { MaxLimit = limit; } /// - public override Error? ValidateRequest(GetRecentTradesRequest request, IRecentTradeRestClient client) + public override Error? ValidateRequest(GetRecentTradesRequest request, IGetRecentTrades client) { var baseError = base.ValidateRequest(request, client); if (baseError != null) diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetRecentTradesRequest.cs b/CryptoExchange.Net/SharedApis/V2/Trades/GetRecentTrades/GetRecentTradesRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetRecentTradesRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Trades/GetRecentTrades/GetRecentTradesRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Trades/GetRecentTrades/IGetRecentTrades.cs b/CryptoExchange.Net/SharedApis/V2/Trades/GetRecentTrades/IGetRecentTrades.cs new file mode 100644 index 00000000..f5412373 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Trades/GetRecentTrades/IGetRecentTrades.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving the most recent trades for a symbol on an exchange. + /// + public interface IGetRecentTrades : ISharedApiCapability + { + /// + /// Recent trades request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetRecentTradesOptions GetRecentTradesOptions { get; } + + /// + /// Get the most recent public trades, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> GetRecentTradesAsync(GetRecentTradesRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving the most recent trades for a symbol on an exchange via the REST API. + /// + public interface IGetRecentTradesRest : IGetRecentTrades, ISharedRest + { + /// + new Task> GetRecentTradesAsync(GetRecentTradesRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetTradeHistoryOptions.cs b/CryptoExchange.Net/SharedApis/V2/Trades/GetTradeHistory/GetTradeHistoryOptions.cs similarity index 53% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetTradeHistoryOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Trades/GetTradeHistory/GetTradeHistoryOptions.cs index c534c834..3aa01de0 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetTradeHistoryOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Trades/GetTradeHistory/GetTradeHistoryOptions.cs @@ -7,22 +7,35 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting trade history /// - public class GetTradeHistoryOptions : PaginatedEndpointOptions + public class GetTradeHistoryOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve public trade history for a symbol"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve trade history for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// public GetTradeHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(ITradeHistoryRestClient.GetTradeHistoryAsync)) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication, nameof(IGetTradeHistoryRest.GetTradeHistoryAsync), _defaultParameterRules) { } /// - public override Error? ValidateRequest(GetTradeHistoryRequest request, ITradeHistoryRestClient client) + public override Error? ValidateRequest(GetTradeHistoryRequest request, IGetTradeHistoryRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!SupportsAscending && request.Direction == DataDirection.Ascending) return ArgumentError.Invalid(nameof(GetTradeHistoryRequest.Direction), $"Ascending direction is not supported"); @@ -32,7 +45,7 @@ namespace CryptoExchange.Net.SharedApis if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) return ArgumentError.Invalid(nameof(GetTradeHistoryRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetTradeHistoryRequest.cs b/CryptoExchange.Net/SharedApis/V2/Trades/GetTradeHistory/GetTradeHistoryRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetTradeHistoryRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Trades/GetTradeHistory/GetTradeHistoryRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Trades/GetTradeHistory/IGetTradeHistory.cs b/CryptoExchange.Net/SharedApis/V2/Trades/GetTradeHistory/IGetTradeHistory.cs new file mode 100644 index 00000000..2887b401 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Trades/GetTradeHistory/IGetTradeHistory.cs @@ -0,0 +1,41 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving trade history from an exchange. + /// + public interface IGetTradeHistory : ISharedApiCapability + { + /// + /// Trade history request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetTradeHistoryOptions GetTradeHistoryOptions { get; } + + /// + /// Get public trade history, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + /// + Task> GetTradeHistoryAsync(GetTradeHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving trade history from an exchange via the REST API. + /// + public interface IGetTradeHistoryRest : IGetTradeHistory, ISharedRest + { + /// + new Task> GetTradeHistoryAsync(GetTradeHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/ISubscribeTrades.cs b/CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/ISubscribeTrades.cs new file mode 100644 index 00000000..bc19911d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/ISubscribeTrades.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Client for subscribing to public trade updates for a symbol + /// + public interface ISubscribeTradesSocket : ISharedSubscription + { + /// + /// Trade subscription options + /// + SubscribeTradeOptions SubscribeTradeOptions { get; } + + /// + /// Subscribe to public trade updates for a symbol + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToTradeUpdatesAsync(SubscribeTradeRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/SubscribeTradeOptions.cs b/CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/SubscribeTradeOptions.cs new file mode 100644 index 00000000..2c81f7ae --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/SubscribeTradeOptions.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for subscribing to trade updates + /// + public class SubscribeTradeOptions : CapabilityOptions + { + /// + public override string Description => "Subscribe to public trade updates for a symbol"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Symbol, "The symbol to subscribe to", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.Symbols, "The symbols to subscribe to", new[] { new SharedSymbol(TradingMode.Spot, "ETH", "USDT") }), + }; + + /// + /// ctor + /// + public SubscribeTradeOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ISubscribeTradesSocket.SubscribeToTradeUpdatesAsync), _defaultParameterRules) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeTradeRequest.cs b/CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/SubscribeTradeRequest.cs similarity index 86% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeTradeRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/SubscribeTradeRequest.cs index d533cd6a..6410b09d 100644 --- a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeTradeRequest.cs +++ b/CryptoExchange.Net/SharedApis/V2/Trades/SubscribeTrades/SubscribeTradeRequest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis { @@ -31,6 +32,7 @@ namespace CryptoExchange.Net.SharedApis /// ctor ///
/// The symbols to subscribe to + [Obsolete("Use SubscribeTradeRequest(IEnumerable symbols, ExchangeParameters? exchangeParameters = null) instead")] public SubscribeTradeRequest(params SharedSymbol[] symbols) : base(symbols, null) { } diff --git a/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/GetTransferHistoryOptions.cs b/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/GetTransferHistoryOptions.cs new file mode 100644 index 00000000..70af0bc5 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/GetTransferHistoryOptions.cs @@ -0,0 +1,62 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting transfer history + /// + public class GetTransferHistoryOptions : PaginatedCapabilityOptions + { + /// + public override string Description => "Retrieve spot user trade history"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Asset, "Filter the result set by asset", "ETH"), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + + /// + /// ctor + /// + public GetTransferHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetTransferHistoryRest.GetTransferHistoryAsync), _defaultParameterRules) + { + } + + /// + public override Error? ValidateRequest(GetTransferHistoryRequest request, IGetTransferHistoryRest client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetTransferHistoryRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetTransferHistoryRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetTransferHistoryRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetTransferHistoryRequest.StartTime), $"Time filter is not supported"); + } + } + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/GetTransferHistoryRequest.cs b/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/GetTransferHistoryRequest.cs new file mode 100644 index 00000000..26eff425 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/GetTransferHistoryRequest.cs @@ -0,0 +1,55 @@ +using System; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to retrieve the transfer history of the user + /// + public record GetTransferHistoryRequest : SharedRequest + { + /// + /// Filter by asset + /// + public string? Asset { get; set; } + /// + /// Filter by start time + /// + public DateTime? StartTime { get; } + /// + /// Filter by end time + /// + public DateTime? EndTime { get; } + /// + /// Max number of results + /// + public int? Limit { get; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } + + /// + /// ctor + /// + /// Filter by asset + /// Filter by start time + /// Filter by end time + /// Max number of results + /// Data direction + /// Exchange specific parameters + public GetTransferHistoryRequest( + string? asset = null, + DateTime? startTime = null, + DateTime? endTime = null, + int? limit = null, + DataDirection? direction = null, + ExchangeParameters? exchangeParameters = null) : base(null, exchangeParameters) + { + Asset = asset; + StartTime = startTime; + EndTime = endTime; + Limit = limit; + Direction = direction; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/IGetTransferHistory.cs b/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/IGetTransferHistory.cs new file mode 100644 index 00000000..6199d78e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Transfers/GetTransferHistory/IGetTransferHistory.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving transfer history from an exchange. + /// + public interface IGetTransferHistory : ISharedApiCapability + { + /// + /// Transfer history request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetTransferHistoryOptions GetTransferHistoryOptions { get; } + /// + /// Get transfer history, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetTransferHistoryAsync(GetTransferHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving transfer history from an exchange via the REST API. + /// + public interface IGetTransferHistoryRest : IGetTransferHistory, ISharedRest + { + /// + new Task> GetTransferHistoryAsync(GetTransferHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Transfers/ITransfer.cs b/CryptoExchange.Net/SharedApis/V2/Transfers/ITransfer.cs new file mode 100644 index 00000000..20e34e06 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Transfers/ITransfer.cs @@ -0,0 +1,38 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for transferring funds between account types on an exchange. + /// + public interface ITransfer : ISharedApiCapability + { + /// + /// Transfer request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ TransferOptions TransferOptions { get; } + + /// + /// Transfer funds between account types, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> TransferAsync(TransferRequest request, CancellationToken ct = default); + } + + /// + /// Operation for transferring funds between account types on an exchange via the REST API. + /// + public interface ITransferRest : ITransfer, ISharedRest + { + /// + new Task> TransferAsync(TransferRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs b/CryptoExchange.Net/SharedApis/V2/Transfers/Transfer/TransferOptions.cs similarity index 55% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Transfers/Transfer/TransferOptions.cs index bd5f6b07..6d85df5a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Transfers/Transfer/TransferOptions.cs @@ -7,11 +7,21 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting a transfer /// - public class TransferOptions : EndpointOptions + public class TransferOptions : CapabilityOptions { /// public override string Description => "Transfer funds between account types"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Asset, "The asset to transfer", "ETH"), + RequestParameterRule.Required(x => x.Quantity, "The quantity to transfer", 1m), + RequestParameterRule.Optional(x => x.FromSymbol, "The symbol of the source account", "ETH-USDT"), + RequestParameterRule.Optional(x => x.ToSymbol, "The symbol of the destination account", "ETH-USDT"), + RequestParameterRule.Required(x => x.FromAccountType, "The source account type", SharedAccountType.Spot), + RequestParameterRule.Required(x => x.ToAccountType, "The destination account type", SharedAccountType.Funding), + }; + /// /// Supported account types /// @@ -20,7 +30,7 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public TransferOptions(string exchange, SharedAccountType[] accountTypes) : base(exchange, true, nameof(ITransferRestClient.TransferAsync)) + public TransferOptions(string exchange, SharedAccountType[] accountTypes) : base(exchange, true, nameof(ITransfer.TransferAsync), _defaultParameterRules) { SupportedAccountTypes = accountTypes; } @@ -30,15 +40,19 @@ namespace CryptoExchange.Net.SharedApis /// public override Error? ValidateRequest( TransferRequest request, - ITransferRestClient client) + ITransfer client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!SupportedAccountTypes.Contains(request.FromAccountType)) return ArgumentError.Invalid(nameof(request.FromAccountType), "Invalid FromAccountType"); if (!SupportedAccountTypes.Contains(request.ToAccountType)) return ArgumentError.Invalid(nameof(request.FromAccountType), "Invalid ToAccountType"); - return base.ValidateRequest(request, client); + return null; } /// diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs b/CryptoExchange.Net/SharedApis/V2/Transfers/Transfer/TransferRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Transfers/Transfer/TransferRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/CancelFuturesTriggerOrder/CancelFuturesTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/CancelFuturesTriggerOrder/CancelFuturesTriggerOrderOptions.cs new file mode 100644 index 00000000..5becc0a9 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/CancelFuturesTriggerOrder/CancelFuturesTriggerOrderOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for canceling spot trigger order + /// + public class CancelFuturesTriggerOrderOptions : CapabilityOptions + { + /// + public override string Description => "Cancel a futures trigger order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to cancel", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to cancel", "123"), + }; + + /// + /// ctor + /// + public CancelFuturesTriggerOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ICancelFuturesTriggerOrder.CancelFuturesTriggerOrderAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/CancelFuturesTriggerOrder/ICancelFuturesTriggerOrder.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/CancelFuturesTriggerOrder/ICancelFuturesTriggerOrder.cs new file mode 100644 index 00000000..88183bd8 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/CancelFuturesTriggerOrder/ICancelFuturesTriggerOrder.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling an active futures trigger order on an exchange. + /// + public interface ICancelFuturesTriggerOrder : ISharedApiCapability + { + /// + /// Cancel trigger order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelFuturesTriggerOrderOptions CancelFuturesTriggerOrderOptions { get; } + /// + /// Cancel a trigger order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> CancelFuturesTriggerOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling an active futures trigger order on an exchange via the REST API. + /// + public interface ICancelFuturesTriggerOrderRest : ICancelFuturesTriggerOrder, ISharedRest + { + /// + new Task> CancelFuturesTriggerOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/GetFuturesTriggerOrder/GetFuturesTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/GetFuturesTriggerOrder/GetFuturesTriggerOrderOptions.cs new file mode 100644 index 00000000..72be664a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/GetFuturesTriggerOrder/GetFuturesTriggerOrderOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting futures trigger order + /// + public class GetFuturesTriggerOrderOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve a futures trigger order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to retrieve", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to retrieve", "123"), + }; + + /// + /// ctor + /// + public GetFuturesTriggerOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetFuturesTriggerOrder.GetFuturesTriggerOrderAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/GetFuturesTriggerOrder/IGetFuturesTriggerOrder.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/GetFuturesTriggerOrder/IGetFuturesTriggerOrder.cs new file mode 100644 index 00000000..bfd66b8b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/GetFuturesTriggerOrder/IGetFuturesTriggerOrder.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for getting a specific futures trigger order from the exchange. + /// + public interface IGetFuturesTriggerOrder : ISharedApiCapability + { + /// + /// Get trigger order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFuturesTriggerOrderOptions GetFuturesTriggerOrderOptions { get; } + /// + /// Get info on a specific trigger order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetFuturesTriggerOrderAsync(GetOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for getting a specific futures trigger order from the exchange via the REST API. + /// + public interface IGetFuturesTriggerOrderRest : IGetFuturesTriggerOrder, ISharedRest + { + /// + new Task> GetFuturesTriggerOrderAsync(GetOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/IPlaceFuturesTriggerOrder.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/IPlaceFuturesTriggerOrder.cs new file mode 100644 index 00000000..72fc64f1 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/IPlaceFuturesTriggerOrder.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for placing a futures trigger order on an exchange. + /// + public interface IPlaceFuturesTriggerOrder : ISharedApiCapability + { + /// + /// Place futures trigger order options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ PlaceFuturesTriggerOrderOptions PlaceFuturesTriggerOrderOptions { get; } + + /// + /// Place a new trigger order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> PlaceFuturesTriggerOrderAsync(PlaceFuturesTriggerOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for placing a futures trigger order on an exchange via the REST API. + /// + public interface IPlaceFuturesTriggerOrderRest : IPlaceFuturesTriggerOrder, ISharedRest + { + /// + new Task> PlaceFuturesTriggerOrderAsync(PlaceFuturesTriggerOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/PlaceFuturesTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/PlaceFuturesTriggerOrderOptions.cs new file mode 100644 index 00000000..e6677cc1 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/PlaceFuturesTriggerOrderOptions.cs @@ -0,0 +1,57 @@ +using CryptoExchange.Net.Objects; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for placing a new spot trigger order + /// + public class PlaceFuturesTriggerOrderOptions : CapabilityOptions + { + /// + public override string Description => "Place a new futures trigger order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to place the trigger order on", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.ClientOrderId, "The client order id", "123"), + RequestParameterRule.Required(x => x.OrderDirection, "The direction of the order when triggered", SharedTriggerOrderDirection.Enter), + RequestParameterRule.Required(x => x.PriceDirection, "The price direction which activates the order", SharedTriggerPriceDirection.PriceAbove), + RequestParameterRule.Required(x => x.Quantity, "The order quantity", SharedQuantity.Base(0.1m)), + RequestParameterRule.Optional(x => x.OrderPrice, "The limit price of the order", 1m), + RequestParameterRule.Required(x => x.TriggerPrice, "The price at which the order activates", 1m), + RequestParameterRule.Optional(x => x.TimeInForce, "The order time in force", SharedTimeInForce.GoodTillCanceled), + RequestParameterRule.Optional(x => x.PositionMode, "The position mode of the account", SharedPositionMode.OneWay), + RequestParameterRule.Required(x => x.PositionSide, "The position side of the order", SharedPositionSide.Long), + RequestParameterRule.Optional(x => x.MarginMode, "The margin mode of the order", SharedMarginMode.Cross), + RequestParameterRule.Optional(x => x.Leverage, "The leverage for the position", 10m), + RequestParameterRule.Optional(x => x.TriggerPriceType, "The price type used to trigger the order", SharedTriggerPriceType.LastPrice), + RequestParameterRule.Optional(x => x.ReduceOnly, "Whether the order is reduce only", true), + }; + + /// + /// When true the API holds the funds until the order is triggered or canceled. When false the funds will only be required when the order is triggered and will fail if the funds are not available at that time. + /// + public bool HoldsFunds { get; set; } + + /// + /// ctor + /// + public PlaceFuturesTriggerOrderOptions(string exchange, bool holdsFunds) : base(exchange, true, nameof(IPlaceFuturesTriggerOrder.PlaceFuturesTriggerOrderAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + HoldsFunds = holdsFunds; + } + + /// + public override Error? ValidateRequest(PlaceFuturesTriggerOrderRequest request, IPlaceFuturesTriggerOrder client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.ReduceOnly == true && request.OrderDirection != SharedTriggerOrderDirection.Exit) + return ArgumentError.Invalid(nameof(request.ReduceOnly), "ReduceOnly can only be enabled for an exit trigger order"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/PlaceFuturesTriggerOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/PlaceFuturesTriggerOrderRequest.cs similarity index 96% rename from CryptoExchange.Net/SharedApis/Models/Rest/PlaceFuturesTriggerOrderRequest.cs rename to CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/PlaceFuturesTriggerOrderRequest.cs index 83559b13..806a4e7a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/PlaceFuturesTriggerOrderRequest.cs +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Futures/PlaceFuturesTriggerOrder/PlaceFuturesTriggerOrderRequest.cs @@ -53,6 +53,10 @@ /// Trigger price type /// public SharedTriggerPriceType? TriggerPriceType { get; set; } + /// + /// Reduce only flag + /// + public bool? ReduceOnly { get; set; } /// /// ctor diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/CancelSpotTriggerOrder/CancelSpotTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/CancelSpotTriggerOrder/CancelSpotTriggerOrderOptions.cs new file mode 100644 index 00000000..5df888b4 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/CancelSpotTriggerOrder/CancelSpotTriggerOrderOptions.cs @@ -0,0 +1,42 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting spot trigger order + /// + public class CancelSpotTriggerOrderOptions : CapabilityOptions + { + /// + public override string Description => "Cancel a spot trigger order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to cancel", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to cancel", "123"), + }; + + /// + /// ctor + /// + public CancelSpotTriggerOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ICancelSpotTriggerOrder.CancelSpotTriggerOrderAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + public override Error? ValidateRequest(CancelOrderRequest request, ICancelSpotTriggerOrder client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/CancelSpotTriggerOrder/ICancelSpotTriggerOrder.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/CancelSpotTriggerOrder/ICancelSpotTriggerOrder.cs new file mode 100644 index 00000000..397cbfbf --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/CancelSpotTriggerOrder/ICancelSpotTriggerOrder.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for canceling a spot trigger order on an exchange. + /// + public interface ICancelSpotTriggerOrder : ISharedApiCapability + { + /// + /// Cancel trigger order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ CancelSpotTriggerOrderOptions CancelSpotTriggerOrderOptions { get; } + /// + /// Cancel a trigger order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> CancelSpotTriggerOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for canceling a spot trigger order on an exchange via the REST API. + /// + public interface ICancelSpotTriggerOrderRest : ICancelSpotTriggerOrder, ISharedRest + { + /// + new Task> CancelSpotTriggerOrderAsync(CancelOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/GetSpotTriggerOrder/GetSpotTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/GetSpotTriggerOrder/GetSpotTriggerOrderOptions.cs new file mode 100644 index 00000000..4501a1eb --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/GetSpotTriggerOrder/GetSpotTriggerOrderOptions.cs @@ -0,0 +1,42 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting spot trigger order + /// + public class GetSpotTriggerOrderOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve a spot trigger order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to retrieve", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to retrieve", "123"), + }; + + /// + /// ctor + /// + public GetSpotTriggerOrderOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetSpotTriggerOrder.GetSpotTriggerOrderAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + public override Error? ValidateRequest(GetOrderRequest request, IGetSpotTriggerOrder client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/GetSpotTriggerOrder/IGetSpotTriggerOrder.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/GetSpotTriggerOrder/IGetSpotTriggerOrder.cs new file mode 100644 index 00000000..b094d3d2 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/GetSpotTriggerOrder/IGetSpotTriggerOrder.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving a specific spot trigger order from an exchange. + /// + public interface IGetSpotTriggerOrder : ISharedApiCapability + { + /// + /// Get trigger order request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetSpotTriggerOrderOptions GetSpotTriggerOrderOptions { get; } + /// + /// Get info on a specific trigger order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetSpotTriggerOrderAsync(GetOrderRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving a specific spot trigger order from an exchange via the REST API. + /// + public interface IGetSpotTriggerOrderRest : IGetSpotTriggerOrder, ISharedRest + { + /// + new Task> GetSpotTriggerOrderAsync(GetOrderRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/PlaceSpotTriggerOrder/IPlaceSpotTriggerOrder.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/PlaceSpotTriggerOrder/IPlaceSpotTriggerOrder.cs new file mode 100644 index 00000000..4885efd9 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/PlaceSpotTriggerOrder/IPlaceSpotTriggerOrder.cs @@ -0,0 +1,41 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for placing a spot trigger order on an exchange. + /// + public interface IPlaceSpotTriggerOrder : ISharedApiCapability + { + /// + /// Place spot trigger order options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ PlaceSpotTriggerOrderOptions PlaceSpotTriggerOrderOptions { get; } + + /// + /// Place a new trigger order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> PlaceSpotTriggerOrderAsync(PlaceSpotTriggerOrderRequest request, CancellationToken ct = default); + + } + + /// + /// Operation for placing a spot trigger order on an exchange via the REST API. + /// + public interface IPlaceSpotTriggerOrderRest : IPlaceSpotTriggerOrder, ISharedRest + { + /// + new Task> PlaceSpotTriggerOrderAsync(PlaceSpotTriggerOrderRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/PlaceSpotTriggerOrder/PlaceSpotTriggerOrderOptions.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/PlaceSpotTriggerOrder/PlaceSpotTriggerOrderOptions.cs new file mode 100644 index 00000000..e1b09f82 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/PlaceSpotTriggerOrder/PlaceSpotTriggerOrderOptions.cs @@ -0,0 +1,55 @@ +using CryptoExchange.Net.Objects; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for placing a new spot trigger order + /// + public class PlaceSpotTriggerOrderOptions : CapabilityOptions + { + /// + public override string Description => "Place a new spot trigger order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to place the trigger order on", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.ClientOrderId, "The client order id", "123"), + RequestParameterRule.Required(x => x.OrderSide, "The order side", SharedOrderSide.Buy), + RequestParameterRule.Required(x => x.PriceDirection, "The price direction which activates the order", SharedTriggerPriceDirection.PriceAbove), + RequestParameterRule.Optional(x => x.TimeInForce, "The order time in force", SharedTimeInForce.GoodTillCanceled), + RequestParameterRule.Required(x => x.Quantity, "The order quantity", SharedQuantity.Base(0.1m)), + RequestParameterRule.Optional(x => x.OrderPrice, "The limit price of the order", 1m), + RequestParameterRule.Required(x => x.TriggerPrice, "The price at which the order activates", 1m), + }; + + /// + /// When true the API holds the funds until the order is triggered or canceled. When false the funds will only be required when the order is triggered and will fail if the funds are not available at that time. + /// + public bool HoldsFunds { get; set; } + + /// + /// ctor + /// + public PlaceSpotTriggerOrderOptions(string exchange, bool holdsFunds) : base(exchange, true, nameof(IPlaceSpotTriggerOrder.PlaceSpotTriggerOrderAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + HoldsFunds = holdsFunds; + } + + /// + /// Validate a request + /// + public override Error? ValidateRequest( + PlaceSpotTriggerOrderRequest request, + IPlaceSpotTriggerOrder client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/PlaceSpotTriggerOrderRequest.cs b/CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/PlaceSpotTriggerOrder/PlaceSpotTriggerOrderRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/PlaceSpotTriggerOrderRequest.cs rename to CryptoExchange.Net/SharedApis/V2/TriggerOrders/Spot/PlaceSpotTriggerOrder/PlaceSpotTriggerOrderRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesOrderTrades/GetFuturesOrderTradesOptions.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesOrderTrades/GetFuturesOrderTradesOptions.cs new file mode 100644 index 00000000..a25db47b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesOrderTrades/GetFuturesOrderTradesOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for getting a trades for an order + /// + public class GetFuturesOrderTradesOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve trades for a futures order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to retrieve trades for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to retrieve trades for", "123"), + }; + + /// + /// ctor + /// + public GetFuturesOrderTradesOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetFuturesOrderTrades.GetFuturesOrderTradesAsync), _defaultParameterRules, SharedTradingModeSets.Futures) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesOrderTrades/IGetFuturesOrderTrades.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesOrderTrades/IGetFuturesOrderTrades.cs new file mode 100644 index 00000000..760224cb --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesOrderTrades/IGetFuturesOrderTrades.cs @@ -0,0 +1,37 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving trades for a specific futures order on an exchange. + /// + public interface IGetFuturesOrderTrades : ISharedApiCapability + { + /// + /// Futures get order trades request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFuturesOrderTradesOptions GetFuturesOrderTradesOptions { get; } + /// + /// Get trades for a specific futures order, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + Task> GetFuturesOrderTradesAsync(GetOrderTradesRequest request, CancellationToken ct = default); + } + + /// + /// Operation for retrieving trades for a specific futures order on an exchange via the REST API. + /// + public interface IGetFuturesOrderTradesRest : IGetFuturesOrderTrades, ISharedRest + { + /// + new Task> GetFuturesOrderTradesAsync(GetOrderTradesRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesUserTradesOptions.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesUserTradeHistory/GetFuturesUserTradeHistoryOptions.cs similarity index 53% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesUserTradesOptions.cs rename to CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesUserTradeHistory/GetFuturesUserTradeHistoryOptions.cs index fdc0d93a..b9191f3a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesUserTradesOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesUserTradeHistory/GetFuturesUserTradeHistoryOptions.cs @@ -7,22 +7,35 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting user trades /// - public class GetFuturesUserTradesOptions : PaginatedEndpointOptions + public class GetFuturesUserTradeHistoryOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve futures user trade history"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve user trades for", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// - public GetFuturesUserTradesOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IFuturesOrderRestClient.GetFuturesUserTradesAsync)) + public GetFuturesUserTradeHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetFuturesUserTradeHistoryRest.GetFuturesUserTradeHistoryAsync), _defaultParameterRules, SharedTradingModeSets.Futures) { } /// - public override Error? ValidateRequest(GetUserTradesRequest request, IFuturesOrderRestClient client) + public override Error? ValidateRequest(GetUserTradesRequest request, IGetFuturesUserTradeHistoryRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!SupportsAscending && request.Direction == DataDirection.Ascending) return ArgumentError.Invalid(nameof(GetUserTradesRequest.Direction), $"Ascending direction is not supported"); @@ -43,7 +56,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesUserTradeHistory/IGetFuturesUserTradeHistory.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesUserTradeHistory/IGetFuturesUserTradeHistory.cs new file mode 100644 index 00000000..fc98ed3c --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/Futures/GetFuturesUserTradeHistory/IGetFuturesUserTradeHistory.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving futures user trade history from an exchange. + /// + public interface IGetFuturesUserTradeHistory : ISharedApiCapability + { + /// + /// Futures user trades request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetFuturesUserTradeHistoryOptions GetFuturesUserTradeHistoryOptions { get; } + /// + /// Get futures user trade records, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetFuturesUserTradeHistoryAsync(GetUserTradesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving futures user trade history from an exchange via the REST API. + /// + public interface IGetFuturesUserTradeHistoryRest : IGetFuturesUserTradeHistory, ISharedRest + { + /// + new Task> GetFuturesUserTradeHistoryAsync(GetUserTradesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetOrderTradesRequest.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/GetOrderTradesRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetOrderTradesRequest.cs rename to CryptoExchange.Net/SharedApis/V2/UserTrades/GetOrderTradesRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetUserTradesRequest.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/GetUserTradesRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetUserTradesRequest.cs rename to CryptoExchange.Net/SharedApis/V2/UserTrades/GetUserTradesRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotOrderTrades/GetSpotOrderTradesOptions.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotOrderTrades/GetSpotOrderTradesOptions.cs new file mode 100644 index 00000000..09a017df --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotOrderTrades/GetSpotOrderTradesOptions.cs @@ -0,0 +1,42 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for getting trades for a spot order + /// + public class GetSpotOrderTradesOptions : CapabilityOptions + { + /// + public override string Description => "Retrieve trades for a spot order"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol of the order to retrieve trades for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Required(x => x.OrderId, "The id of the order to retrieve trades for", "123"), + }; + + /// + /// ctor + /// + public GetSpotOrderTradesOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IGetSpotOrderTrades.GetSpotOrderTradesAsync), _defaultParameterRules, SharedTradingModeSets.Spot) + { + } + + /// + public override Error? ValidateRequest(GetOrderTradesRequest request, IGetSpotOrderTrades client) + { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + + if (request.Symbol!.TradingMode != TradingMode.Spot) + return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); + + return null; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotOrderTrades/IGetSpotOrderTrades.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotOrderTrades/IGetSpotOrderTrades.cs new file mode 100644 index 00000000..c17219c6 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotOrderTrades/IGetSpotOrderTrades.cs @@ -0,0 +1,40 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for getting trades for a specific spot order on an exchange. + /// + public interface IGetSpotOrderTrades : ISharedApiCapability + { + /// + /// Spot get order trades request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetSpotOrderTradesOptions GetSpotOrderTradesOptions { get; } + /// + /// Get trades for a specific spot order, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the `NextPageRequest` property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// Cancellation token + Task> GetSpotOrderTradesAsync(GetOrderTradesRequest request, CancellationToken ct = default); + + } + + /// + /// Operation for getting trades for a specific spot order on an exchange via the REST API. + /// + public interface IGetSpotOrderTradesRest : IGetSpotOrderTrades, ISharedRest + { + /// + new Task> GetSpotOrderTradesAsync(GetOrderTradesRequest request, CancellationToken ct = default); + + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotUserTradesOptions.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotUserTradeHistory/GetSpotUserTradeHistoryOptions.cs similarity index 56% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotUserTradesOptions.cs rename to CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotUserTradeHistory/GetSpotUserTradeHistoryOptions.cs index 7f06a727..3c39a6ae 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotUserTradesOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotUserTradeHistory/GetSpotUserTradeHistoryOptions.cs @@ -7,22 +7,35 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting user trades /// - public class GetSpotUserTradesOptions : PaginatedEndpointOptions + public class GetSpotUserTradeHistoryOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve spot user trade history"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Symbol, "The symbol to retrieve user trades for", new SharedSymbol(TradingMode.Spot, "ETH", "USDT")), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// - public GetSpotUserTradesOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(ISpotOrderRestClient.GetSpotUserTradesAsync)) + public GetSpotUserTradeHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetSpotUserTradeHistoryRest.GetSpotUserTradeHistoryAsync), _defaultParameterRules, SharedTradingModeSets.Spot) { } /// - public override Error? ValidateRequest(GetUserTradesRequest request, ISpotOrderRestClient client) + public override Error? ValidateRequest(GetUserTradesRequest request, IGetSpotUserTradeHistoryRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (request.Symbol!.TradingMode != TradingMode.Spot) return ArgumentError.Invalid("TradingMode", $"TradingMode.{request.Symbol!.TradingMode} is not supported, should be Spot"); @@ -46,7 +59,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotUserTradeHistory/IGetSpotUserTradeHistory.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotUserTradeHistory/IGetSpotUserTradeHistory.cs new file mode 100644 index 00000000..6a56e53a --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/Spot/GetSpotUserTradeHistory/IGetSpotUserTradeHistory.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving spot user trade history from an exchange. + /// + public interface IGetSpotUserTradeHistory : ISharedApiCapability + { + /// + /// Spot user trades request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetSpotUserTradeHistoryOptions GetSpotUserTradeHistoryOptions { get; } + /// + /// Get spot user trade records, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + Task> GetSpotUserTradeHistoryAsync(GetUserTradesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving spot user trade history from an exchange via the REST API. + /// + public interface IGetSpotUserTradeHistoryRest : IGetSpotUserTradeHistory, ISharedRest + { + /// + new Task> GetSpotUserTradeHistoryAsync(GetUserTradesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/UserTrades/SubscribeUserTrades/ISubscribeUserTrades.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/SubscribeUserTrades/ISubscribeUserTrades.cs new file mode 100644 index 00000000..93f16ce7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/SubscribeUserTrades/ISubscribeUserTrades.cs @@ -0,0 +1,28 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for subscribing to user trade updates + /// + public interface ISubscribeUserTradesSocket : ISharedSubscription + { + /// + /// User trade subscription options + /// + SubscribeUserTradeOptions SubscribeUserTradeOptions { get; } + + /// + /// Subscribe to user trade updates + /// + /// Request info + /// Update handler + /// Cancellation token, can be used to stop the updates + /// + Task> SubscribeToUserTradeUpdatesAsync(SubscribeUserTradeRequest request, Action> handler, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeUserTradeOptions.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/SubscribeUserTrades/SubscribeUserTradeOptions.cs similarity index 53% rename from CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeUserTradeOptions.cs rename to CryptoExchange.Net/SharedApis/V2/UserTrades/SubscribeUserTrades/SubscribeUserTradeOptions.cs index 2a3505bc..c499c5d5 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Subscriptions/SubscribeUserTradeOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/UserTrades/SubscribeUserTrades/SubscribeUserTradeOptions.cs @@ -7,15 +7,20 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for subscribing to user trade updates /// - public class SubscribeUserTradeOptions : EndpointOptions + public class SubscribeUserTradeOptions : CapabilityOptions { /// public override string Description => "Subscribe to user trade updates"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.TradingMode, "Filter user trade updates by trading mode", TradingMode.Spot), + }; + /// /// ctor /// - public SubscribeUserTradeOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(IUserTradeSocketClient.SubscribeToUserTradeUpdatesAsync)) + public SubscribeUserTradeOptions(string exchange, bool needsAuthentication) : base(exchange, needsAuthentication, nameof(ISubscribeUserTradesSocket.SubscribeToUserTradeUpdatesAsync), _defaultParameterRules) { } } diff --git a/CryptoExchange.Net/SharedApis/Models/Socket/SubscribeUserTradeRequest.cs b/CryptoExchange.Net/SharedApis/V2/UserTrades/SubscribeUserTrades/SubscribeUserTradeRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Socket/SubscribeUserTradeRequest.cs rename to CryptoExchange.Net/SharedApis/V2/UserTrades/SubscribeUserTrades/SubscribeUserTradeRequest.cs diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetWithdrawalsOptions.cs b/CryptoExchange.Net/SharedApis/V2/Withdrawals/GetWithdrawalHistory/GetWithdrawalHistoryOptions.cs similarity index 55% rename from CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetWithdrawalsOptions.cs rename to CryptoExchange.Net/SharedApis/V2/Withdrawals/GetWithdrawalHistory/GetWithdrawalHistoryOptions.cs index 26693c6f..0305e80a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetWithdrawalsOptions.cs +++ b/CryptoExchange.Net/SharedApis/V2/Withdrawals/GetWithdrawalHistory/GetWithdrawalHistoryOptions.cs @@ -7,22 +7,35 @@ namespace CryptoExchange.Net.SharedApis /// /// Options for requesting withdrawals /// - public class GetWithdrawalsOptions : PaginatedEndpointOptions + public class GetWithdrawalHistoryOptions : PaginatedCapabilityOptions { /// public override string Description => "Retrieve withdrawal history"; + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Optional(x => x.Asset, "Filter the result set by asset", "ETH"), + RequestParameterRule.Optional(x => x.StartTime, "Filter the result set by start time", DateTime.UtcNow.AddDays(-1)), + RequestParameterRule.Optional(x => x.EndTime, "Filter the result set by end time", DateTime.UtcNow.AddHours(-1)), + RequestParameterRule.Optional(x => x.Limit, "Limit the result set to a maximum number of items", 100), + RequestParameterRule.Optional(x => x.Direction, "The direction in which to retrieve the results", DataDirection.Descending), + }; + /// /// ctor /// - public GetWithdrawalsOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) - : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IWithdrawalRestClient.GetWithdrawalsAsync)) + public GetWithdrawalHistoryOptions(string exchange, bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(exchange, supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true, nameof(IGetWithdrawalHistoryRest.GetWithdrawalHistoryAsync), _defaultParameterRules) { } /// - public override Error? ValidateRequest(GetWithdrawalsRequest request, IWithdrawalRestClient client) + public override Error? ValidateRequest(GetWithdrawalsRequest request, IGetWithdrawalHistoryRest client) { + var error = base.ValidateRequest(request, client); + if (error != null) + return error; + if (!SupportsAscending && request.Direction == DataDirection.Ascending) return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); @@ -43,7 +56,7 @@ namespace CryptoExchange.Net.SharedApis } } - return base.ValidateRequest(request, client); + return null; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetWithdrawalsRequest.cs b/CryptoExchange.Net/SharedApis/V2/Withdrawals/GetWithdrawalHistory/GetWithdrawalsRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/GetWithdrawalsRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Withdrawals/GetWithdrawalHistory/GetWithdrawalsRequest.cs diff --git a/CryptoExchange.Net/SharedApis/V2/Withdrawals/GetWithdrawalHistory/IGetWithdrawalHistory.cs b/CryptoExchange.Net/SharedApis/V2/Withdrawals/GetWithdrawalHistory/IGetWithdrawalHistory.cs new file mode 100644 index 00000000..711c9b8f --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Withdrawals/GetWithdrawalHistory/IGetWithdrawalHistory.cs @@ -0,0 +1,41 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for retrieving withdrawal history from an exchange. + /// + public interface IGetWithdrawalHistory : ISharedApiCapability + { + /// + /// Withdrawal records request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ GetWithdrawalHistoryOptions GetWithdrawalHistoryOptions { get; } + + /// + /// Get withdrawal records, see for request options and exchange specific required/optional parameters.
+ /// The result is paginated, if there are more results to be retrieved, the property of the result will contain the pagination request to be used for the next request to continue pagination. + ///
+ /// Request info + /// The pagination request from the previous request result property to continue pagination + /// Cancellation token + /// + Task> GetWithdrawalHistoryAsync(GetWithdrawalsRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } + + /// + /// Operation for retrieving withdrawal history from an exchange via the REST API. + /// + public interface IGetWithdrawalHistoryRest : IGetWithdrawalHistory, ISharedRest + { + /// + new Task> GetWithdrawalHistoryAsync(GetWithdrawalsRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Withdrawals/Withdraw/IWithdraw.cs b/CryptoExchange.Net/SharedApis/V2/Withdrawals/Withdraw/IWithdraw.cs new file mode 100644 index 00000000..9b65dc71 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Withdrawals/Withdraw/IWithdraw.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Operation for withdrawing a specific asset from an exchange. + /// + public interface IWithdraw : ISharedApiCapability + { + /// + /// Withdraw request options.
+ /// Use and to check for required and optional parameters for the request.
+ /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. + ///
+ WithdrawOptions WithdrawOptions { get; } + + /// + /// Request a withdrawal, see for request options and exchange specific required/optional parameters.
+ ///
+ /// Request info + /// Cancellation token + /// + Task> WithdrawAsync(WithdrawRequest request, CancellationToken ct = default); + } + + /// + /// Operation for withdrawing a specific asset from an exchange via the REST API. + /// + public interface IWithdrawRest : IWithdraw, ISharedRest + { + /// + new Task> WithdrawAsync(WithdrawRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/V2/Withdrawals/Withdraw/WithdrawOptions.cs b/CryptoExchange.Net/SharedApis/V2/Withdrawals/Withdraw/WithdrawOptions.cs new file mode 100644 index 00000000..586c82e0 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/V2/Withdrawals/Withdraw/WithdrawOptions.cs @@ -0,0 +1,27 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting a withdrawal + /// + public class WithdrawOptions : CapabilityOptions + { + /// + public override string Description => "Withdraw an asset"; + + private static readonly RequestParameterDescription[] _defaultParameterRules = new[] + { + RequestParameterRule.Required(x => x.Asset, "The asset to withdraw", "ETH"), + RequestParameterRule.Required(x => x.Address, "The address to withdraw to", "0x123"), + RequestParameterRule.Required(x => x.Quantity, "The quantity to withdraw", 1m), + RequestParameterRule.Optional(x => x.AddressTag, "The address tag or memo", "123"), + RequestParameterRule.Optional(x => x.Network, "The network to use for the withdrawal", "ERC20"), + }; + + /// + /// ctor + /// + public WithdrawOptions(string exchange) : base(exchange, true, nameof(IWithdraw.WithdrawAsync), _defaultParameterRules) + { + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/WithdrawRequest.cs b/CryptoExchange.Net/SharedApis/V2/Withdrawals/Withdraw/WithdrawRequest.cs similarity index 100% rename from CryptoExchange.Net/SharedApis/Models/Rest/WithdrawRequest.cs rename to CryptoExchange.Net/SharedApis/V2/Withdrawals/Withdraw/WithdrawRequest.cs diff --git a/CryptoExchange.Net/Sockets/Default/CryptoExchangeWebSocketClient.cs b/CryptoExchange.Net/Sockets/Default/CryptoExchangeWebSocketClient.cs index cb4399a5..ce119f62 100644 --- a/CryptoExchange.Net/Sockets/Default/CryptoExchangeWebSocketClient.cs +++ b/CryptoExchange.Net/Sockets/Default/CryptoExchangeWebSocketClient.cs @@ -208,7 +208,17 @@ namespace CryptoExchange.Net.Sockets.Default { if (Parameters.RateLimiter != null) { - var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, Id, RateLimitItemType.Connection, _requestDefinition, null, 1, Parameters.RateLimitingBehavior, null, _ctsSource.Token).ConfigureAwait(false); + var limitResult = await Parameters.RateLimiter.ProcessAsync( + _logger, + Id, + RateLimitItemType.Connection, + _requestDefinition, + null, + 1, + Parameters.RateLimitingBehavior, + null, + 1.0, + _ctsSource.Token).ConfigureAwait(false); if (!limitResult.Success) return CallResult.Fail(new ClientRateLimitError("Connection limit reached")); } @@ -382,7 +392,15 @@ namespace CryptoExchange.Net.Sockets.Default var bytes = Parameters.Encoding.GetBytes(data); _logger.SocketAddingBytesToSendBuffer(Id, id, bytes); - _sendBuffer.Enqueue(new SendItem { Id = id, Type = WebSocketMessageType.Text, Weight = weight, Bytes = bytes }); + _sendBuffer.Enqueue( + new SendItem + { + Id = id, + Type = WebSocketMessageType.Text, + Weight = weight, + Bytes = bytes, + RateLimitAdmission = Parameters.RateLimitAdmissionCallbackRequest?.Invoke() + }); _sendEvent.Set(); return true; } @@ -394,7 +412,14 @@ namespace CryptoExchange.Net.Sockets.Default return false; _logger.SocketAddingBytesToSendBuffer(Id, id, data); - _sendBuffer.Enqueue(new SendItem { Id = id, Type = WebSocketMessageType.Binary, Weight = weight, Bytes = data }); + _sendBuffer.Enqueue( + new SendItem { + Id = id, + Type = WebSocketMessageType.Binary, + Weight = weight, + Bytes = data, + RateLimitAdmission = Parameters.RateLimitAdmissionCallbackRequest?.Invoke() + }); _sendEvent.Set(); return true; } @@ -530,9 +555,13 @@ namespace CryptoExchange.Net.Sockets.Default { if (Parameters.RateLimiter != null) { + var admission = data.RateLimitAdmission ?? Parameters.RateLimitAdmissionCallbackOptions?.Invoke(_requestDefinition, data.Weight); + var rateRatio = admission?.MaxUtilizationRatio ?? 1.0; + try { - var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, data.Id, RateLimitItemType.Request, _requestDefinition, null, data.Weight, Parameters.RateLimitingBehavior, null, _ctsSource.Token).ConfigureAwait(false); + var limitResult = await Parameters.RateLimiter.ProcessAsync( + _logger, data.Id, RateLimitItemType.Request, _requestDefinition, null, data.Weight, Parameters.RateLimitingBehavior, null, rateRatio, _ctsSource.Token).ConfigureAwait(false); if (!limitResult.Success) { await (OnRequestRateLimited?.Invoke(data.Id) ?? Task.CompletedTask).ConfigureAwait(false); @@ -1026,6 +1055,11 @@ namespace CryptoExchange.Net.Sockets.Default ///
public WebSocketMessageType Type { get; set; } + /// + /// Rate limit admission override + /// + public RateLimitAdmission? RateLimitAdmission { get; set; } + /// /// The bytes to send /// diff --git a/CryptoExchange.Net/Testing/SharedRestRequestValidator.cs b/CryptoExchange.Net/Testing/SharedRestRequestValidator.cs index 2a41b009..5afa6b2d 100644 --- a/CryptoExchange.Net/Testing/SharedRestRequestValidator.cs +++ b/CryptoExchange.Net/Testing/SharedRestRequestValidator.cs @@ -54,7 +54,7 @@ namespace CryptoExchange.Net.Testing public Task ValidateAsync( Func>> methodInvoke, string name, - EndpointOptions endpointOptions, + CapabilityOptions endpointOptions, params Func[] validation) => ValidateAsync(methodInvoke, name, endpointOptions, validation); @@ -72,7 +72,7 @@ namespace CryptoExchange.Net.Testing public async Task ValidateAsync( Func>> methodInvoke, string name, - EndpointOptions endpointOptions, + CapabilityOptions endpointOptions, params Func[] validation) where TActualResponse : TResponse { var listener = new EnumValueTraceListener(); diff --git a/CryptoExchange.Net/Testing/TestHelpers.cs b/CryptoExchange.Net/Testing/TestHelpers.cs index 1eba0e58..2f3f4e18 100644 --- a/CryptoExchange.Net/Testing/TestHelpers.cs +++ b/CryptoExchange.Net/Testing/TestHelpers.cs @@ -1,4 +1,11 @@ -using System; +using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Clients; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Testing.Implementations; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -9,12 +16,6 @@ using System.Net.Http; using System.Reflection; using System.Text; using System.Threading.Tasks; -using CryptoExchange.Net.Authentication; -using CryptoExchange.Net.Clients; -using CryptoExchange.Net.Interfaces; -using CryptoExchange.Net.Objects; -using CryptoExchange.Net.Objects.Sockets; -using CryptoExchange.Net.Testing.Implementations; #pragma warning disable IL2026 #pragma warning disable IL2070 @@ -164,6 +165,7 @@ namespace CryptoExchange.Net.Testing var clientInterfaces = assembly!.GetTypes() .Where(t => t.Name.StartsWith("I" + clientType.Name) && !t.Name.EndsWith("Shared") + && !t.Name.EndsWith("SharedApi") && (excludeInterfaces?.Contains(t.Name) != true)); foreach (var clientInterface in clientInterfaces) @@ -172,7 +174,9 @@ namespace CryptoExchange.Net.Testing foreach (var implementation in implementations) { int methods = 0; - foreach (var method in implementation.GetMethods().Where(m => implementationTypes.IsAssignableFrom(m.ReturnType))) + foreach (var method in implementation.GetMethods() + .Where(m => m.DeclaringType?.Assembly == assembly) + .Where(m => implementationTypes.IsAssignableFrom(m.ReturnType))) { var interfaceMethod = clientInterface.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray()) @@ -185,5 +189,71 @@ namespace CryptoExchange.Net.Testing } } } + + /// + /// Validates that the shared API implementation has all the capabilities it declares and that it doesn't implement any capabilities it doesn't declare + /// + public static (Type[] missingOptions, Type[] missingInterfaces) ValidateSharedApi(T sharedApi) + where T : ISharedApi + { + var allCapabilities = sharedApi + .GetType() + .GetInterfaces() + .Where(x => + x != typeof(T) + && x != typeof(ISharedApiCapability) + && x != typeof(ISharedRest) + && x != typeof(ISharedSocket) + && x != typeof(ISharedSubscription) + && typeof(ISharedApiCapability).IsAssignableFrom(x)) + .ToArray(); + + var implementedCapabilities = allCapabilities + .Where(candidate => !allCapabilities.Any(other => + candidate != other && + candidate.IsAssignableFrom(other))) + .ToArray(); + + var declaredCapabilities = sharedApi.Capabilities + .Select(x => x.CapabilityType) + .ToArray(); + + var missingOptions = implementedCapabilities + .Where(implemented => + !declaredCapabilities.Any(declared => + declared.IsAssignableFrom(implemented))); + + var missingInterfaces = declaredCapabilities + .Where(declared => + !implementedCapabilities.Any(implemented => + declared.IsAssignableFrom(implemented))); + + return (missingOptions.ToArray(), missingInterfaces.ToArray()); + } + + /// + /// Validates that the shared API implementation doesn't have any unsupported capabilities declared + /// + public static CapabilityOptions[] ValidateUnsupportedCapabilities(T sharedApi) + where T : ISharedApi + { + var implementedCapabilities = sharedApi + .GetType() + .GetInterfaces() + .Where(x => + x != typeof(T) + && x != typeof(ISharedApiCapability) + && x != typeof(ISharedRest) + && x != typeof(ISharedSocket) + && x != typeof(ISharedSubscription) + && typeof(ISharedApiCapability).IsAssignableFrom(x)) + .ToArray(); + + var declaredCapabilities = sharedApi.Capabilities + .Where(x => !x.Supported) + .ToArray(); + + return declaredCapabilities; + } } } diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/BalanceTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/BalanceTracker.cs index d544e31a..43242231 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/BalanceTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/BalanceTracker.cs @@ -12,8 +12,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public class BalanceTracker : UserDataItemTracker { - private readonly IBalanceRestClient _restClient; - private readonly IBalanceSocketClient? _socketClient; + private readonly IGetBalancesRest _restClient; + private readonly ISubscribeBalancesSocket? _socketClient; private readonly ExchangeParameters? _exchangeParameters; private readonly SharedAccountType _accountType; @@ -23,8 +23,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers public BalanceTracker( ILogger logger, UserDataSymbolTracker symbolTracker, - IBalanceRestClient restClient, - IBalanceSocketClient? socketClient, + IGetBalancesRest restClient, + ISubscribeBalancesSocket? socketClient, SharedAccountType accountType, TrackerItemConfig config, ExchangeParameters? exchangeParameters = null diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs index b814b267..201f540e 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs @@ -15,8 +15,9 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public class FuturesOrderTracker : UserDataItemTracker { - private readonly IFuturesOrderRestClient _restClient; - private readonly IFuturesOrderSocketClient? _socketClient; + private readonly IGetOpenFuturesOrdersRest _openOrdersClient; + private readonly IGetClosedFuturesOrdersRest _closedOrdersClient; + private readonly ISubscribeFuturesOrdersSocket? _socketClient; private readonly ExchangeParameters? _exchangeParameters; private readonly bool _requiresSymbolParameterOpenOrders; private readonly bool _timeFilterSupportedClosedOrders; @@ -31,23 +32,26 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers public FuturesOrderTracker( ILogger logger, UserDataSymbolTracker symbolTracker, - IFuturesOrderRestClient restClient, - IFuturesOrderSocketClient? socketClient, + IGetOpenFuturesOrdersRest openRestClient, + IGetClosedFuturesOrdersRest closedRestClient, + ISubscribeFuturesOrdersSocket? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, ExchangeParameters? exchangeParameters = null - ) : base(logger, symbolTracker, UserDataType.Orders, restClient.Exchange, config) + ) : base(logger, symbolTracker, UserDataType.Orders, openRestClient.Exchange, config) { if (_socketClient == null) config = config with { PollIntervalConnected = config.PollIntervalDisconnected }; - _restClient = restClient; + _openOrdersClient = openRestClient; + _closedOrdersClient = closedRestClient; _socketClient = socketClient; _exchangeParameters = exchangeParameters; - _requiresSymbolParameterOpenOrders = restClient.GetOpenFuturesOrdersOptions.RequiredOptionalParameters.Any(x => x.Names.Contains("Symbol")); - _timeFilterSupportedClosedOrders = restClient.GetClosedFuturesOrdersOptions.TimePeriodFilterSupport; + _requiresSymbolParameterOpenOrders = openRestClient.GetOpenFuturesOrdersOptions.RequestParameterRules + .Any(x => x.Name == nameof(GetOpenOrdersRequest.Symbol) && x.Support == RequestParameterSupport.Required); + _timeFilterSupportedClosedOrders = closedRestClient.GetClosedFuturesOrdersOptions.TimePeriodFilterSupport; } internal void ClearDataForSymbol(SharedSymbol symbol) @@ -80,18 +84,6 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers changed = true; } - if (updateItem.Fee != null && updateItem.Fee != existingItem.Fee) - { - existingItem.Fee = updateItem.Fee; - changed = true; - } - - if (updateItem.FeeAsset != null && updateItem.FeeAsset != existingItem.FeeAsset) - { - existingItem.FeeAsset = updateItem.FeeAsset; - changed = true; - } - if (updateItem.OrderQuantity != null && updateItem.OrderQuantity != existingItem.OrderQuantity) { existingItem.OrderQuantity = updateItem.OrderQuantity; @@ -193,16 +185,6 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers } } - if (existingItem.Fee != null && updateItem.Fee != null) - { - // Higher fee means later processing - if (existingItem.Fee < updateItem.Fee) - return true; - - if (existingItem.Fee > updateItem.Fee) - return false; - } - return null; } @@ -211,7 +193,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers { await base.HandleUpdateAsync(source, @event).ConfigureAwait(false); - var trades = @event.Where(x => x.LastTrade != null).Select(x => x.LastTrade!).ToArray(); + var trades = @event.OfType().Where(x => x.LastTrade != null).Select(x => x.LastTrade!).ToArray(); if (trades.Length != 0 && OnTradeUpdate != null) await OnTradeUpdate.Invoke(source, trades).ConfigureAwait(false); } @@ -222,7 +204,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers if (_socketClient == null) return Task.FromResult(new WebSocketResult(_exchange, default!, default)); - return ExchangeHelpers.ProcessQueuedAsync( + return ExchangeHelpers.ProcessQueuedAsync( async handler => await _socketClient.SubscribeToFuturesOrderUpdatesAsync(new SubscribeFuturesOrderRequest(exchangeParameters: _exchangeParameters), handler, ct: _cts!.Token).ConfigureAwait(false), x => HandleUpdateAsync(UpdateSource.Push, x.Data))!; } @@ -235,7 +217,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers if (!_requiresSymbolParameterOpenOrders) { - var openOrdersResult = await _restClient.GetOpenFuturesOrdersAsync(new GetOpenOrdersRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false); + var openOrdersResult = await _openOrdersClient.GetOpenFuturesOrdersAsync(new GetOpenOrdersRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!openOrdersResult.Success) { anyError = true; @@ -254,7 +236,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers { foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { - var openOrdersResult = await _restClient.GetOpenFuturesOrdersAsync(new GetOpenOrdersRequest(symbol, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); + var openOrdersResult = await _openOrdersClient.GetOpenFuturesOrdersAsync(new GetOpenOrdersRequest(symbol, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!openOrdersResult.Success) { anyError = true; @@ -298,7 +280,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers { // Can filter by start time, so we can just request data from that point - var closedOrdersResult = await _restClient.GetClosedFuturesOrdersAsync(new GetClosedOrdersRequest(symbol, startTime: fromTimeOrders, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); + var closedOrdersResult = await _closedOrdersClient.GetClosedFuturesOrdersAsync(new GetClosedOrdersRequest(symbol, startTime: fromTimeOrders, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!closedOrdersResult.Success) { symbolError = true; @@ -321,7 +303,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers PageRequest? nextPageRequest = null; while (lastMinReturn > fromTimeOrders) { - var closedOrdersResult = await _restClient.GetClosedFuturesOrdersAsync(new GetClosedOrdersRequest(symbol, direction: DataDirection.Descending, exchangeParameters: _exchangeParameters), nextPageRequest).ConfigureAwait(false); + var closedOrdersResult = await _closedOrdersClient.GetClosedFuturesOrdersAsync(new GetClosedOrdersRequest(symbol, direction: DataDirection.Descending, exchangeParameters: _exchangeParameters), nextPageRequest).ConfigureAwait(false); if (!closedOrdersResult.Success) { symbolError = true; diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs index a29d7910..e657ee42 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs @@ -15,8 +15,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public class FuturesUserTradeTracker : UserDataItemTracker { - private readonly IFuturesOrderRestClient _restClient; - private readonly IUserTradeSocketClient? _socketClient; + private readonly IGetFuturesUserTradeHistoryRest _restClient; + private readonly ISubscribeUserTradesSocket? _socketClient; private readonly ExchangeParameters? _exchangeParameters; private readonly TimeSpan _pollOverlapPeriod = TimeSpan.FromSeconds(3); @@ -28,8 +28,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers public FuturesUserTradeTracker( ILogger logger, UserDataSymbolTracker symbolTracker, - IFuturesOrderRestClient restClient, - IUserTradeSocketClient? socketClient, + IGetFuturesUserTradeHistoryRest restClient, + ISubscribeUserTradesSocket? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, @@ -75,7 +75,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers var updatedPollTime = DateTime.UtcNow; foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { - var tradesResult = await _restClient.GetFuturesUserTradesAsync(new GetUserTradesRequest(symbol, startTime: fromTimeTrades, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); + var tradesResult = await _restClient.GetFuturesUserTradeHistoryAsync(new GetUserTradesRequest(symbol, startTime: fromTimeTrades, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!tradesResult.Success) { anyError = true; diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs index 626b59f3..5bcac4d0 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs @@ -15,8 +15,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public class PositionTracker : UserDataItemTracker { - private readonly IFuturesOrderRestClient _restClient; - private readonly IPositionSocketClient? _socketClient; + private readonly IGetPositionsRest _restClient; + private readonly ISubscribePositionsSocket? _socketClient; private readonly ExchangeParameters? _exchangeParameters; /// @@ -30,8 +30,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers public PositionTracker( ILogger logger, UserDataSymbolTracker symbolTracker, - IFuturesOrderRestClient restClient, - IPositionSocketClient? socketClient, + IGetPositionsRest restClient, + ISubscribePositionsSocket? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs index 7916b527..76eb7888 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs @@ -15,8 +15,9 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public class SpotOrderTracker : UserDataItemTracker { - private readonly ISpotOrderRestClient _restClient; - private readonly ISpotOrderSocketClient? _socketClient; + private readonly IGetOpenSpotOrdersRest _openOrderClient; + private readonly IGetClosedSpotOrdersRest _closedOrderClient; + private readonly ISubscribeSpotOrdersSocket? _socketClient; private readonly ExchangeParameters? _exchangeParameters; private readonly bool _requiresSymbolParameterOpenOrders; private readonly bool _timeFilterSupportedClosedOrders; @@ -31,23 +32,26 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers public SpotOrderTracker( ILogger logger, UserDataSymbolTracker symbolTracker, - ISpotOrderRestClient restClient, - ISpotOrderSocketClient? socketClient, + IGetOpenSpotOrdersRest openOrderClient, + IGetClosedSpotOrdersRest closedOrderClient, + ISubscribeSpotOrdersSocket? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, ExchangeParameters? exchangeParameters = null - ) : base(logger, symbolTracker, UserDataType.Orders, restClient.Exchange, config) + ) : base(logger, symbolTracker, UserDataType.Orders, openOrderClient.Exchange, config) { if (_socketClient == null) config = config with { PollIntervalConnected = config.PollIntervalDisconnected }; - _restClient = restClient; + _openOrderClient = openOrderClient; + _closedOrderClient = closedOrderClient; _socketClient = socketClient; _exchangeParameters = exchangeParameters; - _requiresSymbolParameterOpenOrders = restClient.GetOpenSpotOrdersOptions.RequiredOptionalParameters.Any(x => x.Names.Contains("Symbol")); - _timeFilterSupportedClosedOrders = restClient.GetClosedSpotOrdersOptions.TimePeriodFilterSupport; + _requiresSymbolParameterOpenOrders = openOrderClient.GetOpenSpotOrdersOptions.RequestParameterRules + .Any(x => x.Name == nameof(GetOpenOrdersRequest.Symbol) && x.Support == RequestParameterSupport.Required); + _timeFilterSupportedClosedOrders = closedOrderClient.GetClosedSpotOrdersOptions.TimePeriodFilterSupport; } internal void ClearDataForSymbol(SharedSymbol symbol) @@ -79,18 +83,6 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers changed = true; } - if (updateItem.Fee != null && updateItem.Fee != existingItem.Fee) - { - existingItem.Fee = updateItem.Fee; - changed = true; - } - - if (updateItem.FeeAsset != null && updateItem.FeeAsset != existingItem.FeeAsset) - { - existingItem.FeeAsset = updateItem.FeeAsset; - changed = true; - } - if (updateItem.OrderQuantity != null && updateItem.OrderQuantity != existingItem.OrderQuantity) { existingItem.OrderQuantity ??= new SharedOrderQuantity(); @@ -203,16 +195,6 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers } } - if (existingItem.Fee != null && updateItem.Fee != null) - { - // Higher fee means later processing - if (existingItem.Fee < updateItem.Fee) - return true; - - if (existingItem.Fee > updateItem.Fee) - return false; - } - return null; } @@ -221,7 +203,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers { await base.HandleUpdateAsync(source, @event).ConfigureAwait(false); - var trades = @event.Where(x => x.LastTrade != null).Select(x => x.LastTrade!).ToArray(); + var trades = @event.OfType().Where(x => x.LastTrade != null).Select(x => x.LastTrade!).ToArray(); if (trades.Length != 0 && OnTradeUpdate != null) await OnTradeUpdate(source, trades).ConfigureAwait(false); } @@ -232,7 +214,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers if (_socketClient == null) return Task.FromResult(new WebSocketResult(_exchange!, default!, default)); - return ExchangeHelpers.ProcessQueuedAsync( + return ExchangeHelpers.ProcessQueuedAsync( async handler => await _socketClient.SubscribeToSpotOrderUpdatesAsync(new SubscribeSpotOrderRequest(exchangeParameters: _exchangeParameters), handler, ct: _cts!.Token).ConfigureAwait(false), x => HandleUpdateAsync(UpdateSource.Push, x.Data))!; } @@ -245,7 +227,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers if (!_requiresSymbolParameterOpenOrders) { - var openOrdersResult = await _restClient.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false); + var openOrdersResult = await _openOrderClient.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!openOrdersResult.Success) { anyError = true; @@ -264,7 +246,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers { foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { - var openOrdersResult = await _restClient.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); + var openOrdersResult = await _openOrderClient.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!openOrdersResult.Success) { anyError = true; @@ -308,7 +290,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers { // Can filter by start time, so we can just request data from that point - var closedOrdersResult = await _restClient.GetClosedSpotOrdersAsync(new GetClosedOrdersRequest(symbol, startTime: fromTimeOrders, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); + var closedOrdersResult = await _closedOrderClient.GetClosedSpotOrdersAsync(new GetClosedOrdersRequest(symbol, startTime: fromTimeOrders, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!closedOrdersResult.Success) { symbolError = true; @@ -331,7 +313,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers PageRequest? nextPageRequest = null; while (lastMinReturn > fromTimeOrders) { - var closedOrdersResult = await _restClient.GetClosedSpotOrdersAsync(new GetClosedOrdersRequest(symbol, direction: DataDirection.Descending, exchangeParameters: _exchangeParameters), nextPageRequest).ConfigureAwait(false); + var closedOrdersResult = await _closedOrderClient.GetClosedSpotOrdersAsync(new GetClosedOrdersRequest(symbol, direction: DataDirection.Descending, exchangeParameters: _exchangeParameters), nextPageRequest).ConfigureAwait(false); if (!closedOrdersResult.Success) { symbolError = true; diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs index 15be314f..b830aa60 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs @@ -15,8 +15,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public class SpotUserTradeTracker : UserDataItemTracker { - private readonly ISpotOrderRestClient _restClient; - private readonly IUserTradeSocketClient? _socketClient; + private readonly IGetSpotUserTradeHistoryRest _restClient; + private readonly ISubscribeUserTradesSocket? _socketClient; private readonly ExchangeParameters? _exchangeParameters; private readonly TimeSpan _pollOverlapPeriod = TimeSpan.FromSeconds(3); @@ -28,8 +28,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers public SpotUserTradeTracker( ILogger logger, UserDataSymbolTracker symbolTracker, - ISpotOrderRestClient restClient, - IUserTradeSocketClient? socketClient, + IGetSpotUserTradeHistoryRest restClient, + ISubscribeUserTradesSocket? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, @@ -74,7 +74,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers var updatedPollTime = DateTime.UtcNow; foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { - var tradesResult = await _restClient.GetSpotUserTradesAsync(new GetUserTradesRequest(symbol, startTime: fromTimeTrades, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); + var tradesResult = await _restClient.GetSpotUserTradeHistoryAsync(new GetUserTradesRequest(symbol, startTime: fromTimeTrades, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!tradesResult.Success) { anyError = true; diff --git a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs index f83f01eb..614e0f7e 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs @@ -18,7 +18,7 @@ namespace CryptoExchange.Net.Trackers.UserData /// public abstract class UserFuturesDataTracker : UserDataTracker, IUserFuturesDataTracker { - private readonly IFuturesSymbolRestClient _symbolClient; + private readonly IGetFuturesSymbolsRest _symbolClient; private readonly ExchangeParameters? _exchangeParameters; private readonly TradingMode _tradingMode; @@ -51,13 +51,21 @@ namespace CryptoExchange.Net.Trackers.UserData /// public UserFuturesDataTracker( ILogger logger, - IFuturesSymbolRestClient symbolRestClient, - IBalanceRestClient balanceRestClient, - IBalanceSocketClient? balanceSocketClient, - IFuturesOrderRestClient futuresOrderRestClient, - IFuturesOrderSocketClient? futuresOrderSocketClient, - IUserTradeSocketClient? userTradeSocketClient, - IPositionSocketClient? positionSocketClient, + IGetFuturesSymbolsRest symbolRestClient, + + IGetBalancesRest balanceRestClient, + ISubscribeBalancesSocket? balanceSocketClient, + + IGetOpenFuturesOrdersRest openOrderRestClient, + IGetClosedFuturesOrdersRest closedOrderRestClient, + ISubscribeFuturesOrdersSocket? subscribeFuturesOrdersOperation, + + IGetFuturesUserTradeHistoryRest getFuturesUserTradeHistoryRestClient, + ISubscribeUserTradesSocket? userTradeSocketClient, + + IGetPositionsRest positionRestClient, + ISubscribePositionsSocket? positionSocketClient, + string? userIdentifier, FuturesUserDataTrackerConfig config, SharedAccountType? accountType = null, @@ -78,17 +86,17 @@ namespace CryptoExchange.Net.Trackers.UserData Balances = balanceTracker; trackers.Add(balanceTracker); - var orderTracker = new FuturesOrderTracker(logger, SymbolTracker, futuresOrderRestClient, futuresOrderSocketClient, config.OrdersConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); + var orderTracker = new FuturesOrderTracker(logger, SymbolTracker, openOrderRestClient, closedOrderRestClient, subscribeFuturesOrdersOperation, config.OrdersConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); Orders = orderTracker; trackers.Add(orderTracker); - var positionTracker = new PositionTracker(logger, SymbolTracker, futuresOrderRestClient, positionSocketClient, config.PositionConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, WebsocketPositionUpdatesAreFullSnapshots, exchangeParameters); + var positionTracker = new PositionTracker(logger, SymbolTracker, positionRestClient, positionSocketClient, config.PositionConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, WebsocketPositionUpdatesAreFullSnapshots, exchangeParameters); Positions = positionTracker; trackers.Add(positionTracker); if (config.TrackTrades) { - var tradeTracker = new FuturesUserTradeTracker(logger, SymbolTracker, futuresOrderRestClient, userTradeSocketClient, config.UserTradesConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); + var tradeTracker = new FuturesUserTradeTracker(logger, SymbolTracker, getFuturesUserTradeHistoryRestClient, userTradeSocketClient, config.UserTradesConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); Trades = tradeTracker; trackers.Add(tradeTracker); diff --git a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs index 8180c990..4cd9fc87 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs @@ -17,7 +17,7 @@ namespace CryptoExchange.Net.Trackers.UserData /// public class UserSpotDataTracker : UserDataTracker, IUserSpotDataTracker { - private readonly ISpotSymbolRestClient _symbolClient; + private readonly IGetSpotSymbolsRest _symbolClient; private readonly ExchangeParameters? _exchangeParameters; /// @@ -34,12 +34,18 @@ namespace CryptoExchange.Net.Trackers.UserData /// public UserSpotDataTracker( ILogger logger, - ISpotSymbolRestClient symbolRestClient, - IBalanceRestClient balanceRestClient, - IBalanceSocketClient? balanceSocketClient, - ISpotOrderRestClient spotOrderRestClient, - ISpotOrderSocketClient? spotOrderSocketClient, - IUserTradeSocketClient? userTradeSocketClient, + IGetSpotSymbolsRest symbolRestClient, + + IGetBalancesRest balanceRestClient, + ISubscribeBalancesSocket? balanceSocketClient, + + IGetOpenSpotOrdersRest openOrderRestClient, + IGetClosedSpotOrdersRest closedOrderRestClient, + ISubscribeSpotOrdersSocket? subscribeSpotOrdersOperation, + + IGetSpotUserTradeHistoryRest? getSpotUserTradeHistoryRestClient, + ISubscribeUserTradesSocket? userTradeSocketClient, + string? userIdentifier, SpotUserDataTrackerConfig config, ExchangeParameters? exchangeParameters = null) : base(logger, symbolRestClient.Exchange, config, userIdentifier) @@ -54,13 +60,16 @@ namespace CryptoExchange.Net.Trackers.UserData Balances = balanceTracker; trackers.Add(balanceTracker); - var orderTracker = new SpotOrderTracker(logger, SymbolTracker, spotOrderRestClient, spotOrderSocketClient, config.OrdersConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); + var orderTracker = new SpotOrderTracker(logger, SymbolTracker, openOrderRestClient, closedOrderRestClient, subscribeSpotOrdersOperation, config.OrdersConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); Orders = orderTracker; trackers.Add(orderTracker); if (config.TrackTrades) { - var tradeTracker = new SpotUserTradeTracker(logger, SymbolTracker, spotOrderRestClient, userTradeSocketClient, config.UserTradesConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); + if (getSpotUserTradeHistoryRestClient == null) + throw new ArgumentException("Trade tracking is enabled, but no user trade API client is available"); + + var tradeTracker = new SpotUserTradeTracker(logger, SymbolTracker, getSpotUserTradeHistoryRestClient, userTradeSocketClient, config.UserTradesConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); Trades = tradeTracker; trackers.Add(tradeTracker); diff --git a/README.md b/README.md index a968e7ae..24e780e5 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ For single-exchange code, see also the AI files in each exchange's repository (B See [cryptoexchange-skills-hub](https://github.com/JKorf/cryptoexchange-skills-hub) for installable skills. **Quick prompt to verify your assistant is using these:** -> "Show me how to fetch BTC/USDT spot tickers from Binance and OKX concurrently in C# using the SharedApis pattern." +> "Show me how to fetch BTC/USDT spot tickers from Binance and OKX concurrently in C# using the SharedApis V2 pattern." -The expected output should use `.SharedClient` properties, `SharedSymbol`, `ISpotTickerRestClient`, and `Task.WhenAll`. +The expected output should use `.SharedApi` properties, `SharedSymbol`, `IGetTickerRest`, and `Task.WhenAll`. ### CryptoExchange.Net Ecosystem Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider using a referral link to support development, as well as potentially get some trading fee discount! @@ -38,7 +38,6 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us |![Bitfinex](https://raw.githubusercontent.com/JKorf/Bitfinex.Net/refs/heads/master/Bitfinex.Net/Icon/icon.png)|Bitfinex|CEX|[JKorf/Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitfinex.net.svg?style=flat-square)](https://www.nuget.org/packages/Bitfinex.Net)|-|-| |![Bitget](https://raw.githubusercontent.com/JKorf/Bitget.Net/refs/heads/main/Bitget.Net/Icon/icon.png)|Bitget|CEX|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Bitget.net.svg?style=flat-square)](https://www.nuget.org/packages/JK.Bitget.Net)|[Link](https://partner.bitget.com/bg/1qlf6pj1)|20%| |![BitMart](https://raw.githubusercontent.com/JKorf/BitMart.Net/refs/heads/main/BitMart.Net/Icon/icon.png)|BitMart|CEX|[JKorf/BitMart.Net](https://github.com/JKorf/BitMart.Net)|[![Nuget version](https://img.shields.io/nuget/v/BitMart.net.svg?style=flat-square)](https://www.nuget.org/packages/BitMart.Net)|[Link](https://www.bitmart.com/invite/JKorfAPI/en-US)|30%| -|![BitMEX](https://raw.githubusercontent.com/JKorf/BitMEX.Net/refs/heads/main/BitMEX.Net/Icon/icon.png)|BitMEX|CEX|[JKorf/BitMEX.Net](https://github.com/JKorf/BitMEX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JKorf.BitMEX.net.svg?style=flat-square)](https://www.nuget.org/packages/JKorf.BitMEX.Net)|[Link](https://www.bitmex.com/app/register/94f98e)|30%| |![Bitstamp](https://raw.githubusercontent.com/JKorf/Bitstamp.Net/refs/heads/main/Bitstamp.Net/Icon/icon.png)|Bitstamp|CEX|[JKorf/Bitstamp.Net](https://github.com/JKorf/Bitstamp.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitstamp.net.svg?style=flat-square)](https://www.nuget.org/packages/Bitstamp.Net)|-|-| |![BloFin](https://raw.githubusercontent.com/JKorf/BloFin.Net/refs/heads/main/BloFin.Net/Icon/icon.png)|BloFin|CEX|[JKorf/BloFin.Net](https://github.com/JKorf/BloFin.Net)|[![Nuget version](https://img.shields.io/nuget/v/BloFin.net.svg?style=flat-square)](https://www.nuget.org/packages/BloFin.Net)|-|-| |![Bybit](https://raw.githubusercontent.com/JKorf/Bybit.Net/refs/heads/main/ByBit.Net/Icon/icon.png)|Bybit|CEX|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg?style=flat-square)](https://www.nuget.org/packages/Bybit.Net)|[Link](https://partner.bybit.com/b/jkorf)|-| diff --git a/docs/SHARED_API_V2_MIGRATION.md b/docs/SHARED_API_V2_MIGRATION.md new file mode 100644 index 00000000..91e57f68 --- /dev/null +++ b/docs/SHARED_API_V2_MIGRATION.md @@ -0,0 +1,367 @@ +# Migrating from Shared API V1 to V2 + +Shared API V2 introduces fine-grained capability interfaces. You can migrate one operation at a time: exchange REST and socket clients still expose their V1 `SharedClient` facades, while their `SharedApi` properties expose V2 capabilities. You do not need to replace the exchange clients or migrate every call in one release. + +This guide describes the current source API. Rebuild your application after updating CryptoExchange.Net and the exchange libraries together; retaining V1 usage does not guarantee binary compatibility with older assemblies. + +The snippets are illustrative. Configure credentials and the appropriate environment before trying a trading call. In the order examples, `request` means your populated `PlaceFuturesOrderRequest`. + +## The V2 pieces used in this guide + +V2 has two ways to reach a capability: + +- **Known API surface:** `restClient.SpotApi.SharedApi` is the V2 aggregate for that specific REST API. Its type lists the capabilities that API implements, so use its methods or cast it to a capability interface when the exchange and API surface are already known. The adjacent `SharedClient` property is the V1 facade and remains available during migration. +- **Exchange-wide selection:** `I[Exchange]SharedApiClient` combines an exchange's full Shared API REST and socket V2 surface. `GetCapability` and `GetCapabilities` are methods on this exchange-wide client used for dynamically resolving capabilities. + +For the exchange-wide examples below, `sharedClient` is obtained through DI like this (taking Binance as an example): + +```csharp +using Binance.Net.Interfaces.Clients; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; + +var services = new ServiceCollection(); +services.AddBinance(); // Configure credentials and environment in the options for real requests. +using var provider = services.BuildServiceProvider(); +IBinanceSharedApiClient sharedClient = provider.GetRequiredService(); +``` + +`SharedCapabilities` is a catalog of **strongly typed lookup references**, not a list of endpoints guaranteed to exist on Binance. For example, `SharedCapabilities.Orders.Futures.PlaceOrder` identifies the transport-agnostic `IPlaceFuturesOrder` capability; its `.Rest` and `.Socket` references select `IPlaceFuturesOrderRest` and `IPlaceFuturesOrderSocket`. The reference tells `GetCapability` which interface to look for. Lookup checks this client's implemented and supported capabilities, and returns `null` if none match; it does not send an exchange request. + +```csharp +var placementCapabilityResolution = sharedClient.GetCapability( + SharedCapabilities.Orders.Futures.PlaceOrder.Rest, + TradingMode.PerpetualLinear); + +if (placementCapabilityResolution is not null) +{ + IPlaceFuturesOrderRest api = placement.Capability; + // placement.Options describes parameter and trading-mode support for this implementation. +} +``` + +Use a typed `...Rest`/`...Socket` property or direct capability injection when you already know the exchange and API surface. Use `GetCapability` when that choice is made at runtime or support may vary; supply a trading mode when more than one futures API may match. It returns one resolution containing `Capability` and `Options`. Use `GetCapabilities` to inspect every matching implementation. For transport-agnostic lookup, one result is selected according to `PreferredTransport` (`BinanceOptions.SharedApi.PreferredTransport`, which defaults to REST); a `.Rest` or `.Socket` reference removes that transport ambiguity but may still match more than one API surface. + +## 1. Keep existing calls running, then choose an operation + +For example, the V1 `ISpotOrderRestClient` combines placement, cancellation, retrieval, and user trades. In V2, request the specific operation you use: + +| V1 usage | V2 capability | +| --- | --- | +| `ISpotOrderRestClient.PlaceSpotOrderAsync` | `IPlaceSpotOrderRest.PlaceSpotOrderAsync` | +| `ISpotOrderRestClient.CancelSpotOrderAsync` | `ICancelSpotOrderRest.CancelSpotOrderAsync` | +| `ISpotTickerRestClient.GetSpotTickerAsync` | `IGetTickerRest.GetTickerAsync` | +| `ISpotTickerRestClient.GetSpotTickersAsync` | `IGetAllTickersRest.GetAllTickersAsync` | +| `IFuturesOrderRestClient.PlaceFuturesOrderAsync` | `IPlaceFuturesOrderRest.PlaceFuturesOrderAsync` | +| `IFuturesOrderRestClient.CancelFuturesOrderAsync` | `ICancelFuturesOrderRest.CancelFuturesOrderAsync` | +| `IFuturesOrderRestClient.GetPositionsAsync` | `IGetPositionsRest.GetPositionsAsync` | + +All of these types are in `CryptoExchange.Net.SharedApis`. The exact V2 interfaces exposed by an exchange are listed on its typed `...SharedApi` interface. An exchange can implement some capabilities without implementing every operation from the former V1 aggregate. + +For a known exchange and API surface, the typed property gives compile-time discovery: + +```csharp +using Binance.Net.Clients; +using CryptoExchange.Net.SharedApis; + +var restClient = new BinanceRestClient(); +IPlaceSpotOrderRest placement = restClient.SpotApi.SharedApi; + +var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT"); +var request = new PlaceSpotOrderRequest( + symbol, + SharedOrderSide.Buy, + SharedOrderType.Market, + SharedQuantity.Base(0.01m)); + +var result = await placement.PlaceSpotOrderAsync(request); +if (!result.Success) + Console.WriteLine(result.Error); +``` + +Keep using `restClient.SpotApi.SharedClient` for V1 calls while migrating other operations. `SharedClient` and `SharedApi` are different views of the same exchange API, not a requirement to run two separate exchange clients. + +### Migrating a service using V1 Shared APIs + +Previously, a service could inject `IFuturesOrderRestClient` for both futures order placement and cancellation. For example, with Kraken registered in DI: + +```csharp +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using System.Threading.Tasks; + +var services = new ServiceCollection(); +services.AddKraken(); // Configure credentials and environment in the options for real requests. + +public class FuturesOrderServiceV1 +{ + private readonly IFuturesOrderRestClient _orders; + + public FuturesOrderServiceV1(IFuturesOrderRestClient orders) + { + _orders = orders; + } + + public Task> PlaceAsync(PlaceFuturesOrderRequest request, CancellationToken ct) + => _orders.PlaceFuturesOrderAsync(request, ct); + + public Task> CancelAsync(CancelOrderRequest request, CancellationToken ct) + => _orders.CancelFuturesOrderAsync(request, ct); +} +``` + +In V2, inject the two REST capabilities separately. The request types, method names, and REST result type remain the same for these two calls, so the service's callers need not change (using the same imports as above): + +```csharp +public sealed class FuturesOrderServiceV2 +{ + private readonly IPlaceFuturesOrderRest _placement; + private readonly ICancelFuturesOrderRest _cancellation; + + public FuturesOrderServiceV2( + IPlaceFuturesOrderRest placement, + ICancelFuturesOrderRest cancellation) + { + _placement = placement; + _cancellation = cancellation; + } + + public Task> PlaceAsync(PlaceFuturesOrderRequest request, CancellationToken ct) + => _placement.PlaceFuturesOrderAsync(request, ct); + + public Task> CancelAsync(CancelOrderRequest request, CancellationToken ct) + => _cancellation.CancelFuturesOrderAsync(request, ct); +} +``` + +`services.AddKraken()` registers these V2 capability interfaces as well as the V1 interface; no additional registration is needed. + +This direct injection pattern is clearest when the DI container has one intended implementation for each capability. An exchange with multiple API surfaces for the same operation (for example, Binance USD- and coin-margined futures order management), or an application registering multiple exchanges, may register several different interface implementations. Plain single-service resolution does not express which surface you want and could pair different surfaces. In that case, inject the exchange-specific shared aggregate. For example, if a service receives an `IBinanceSharedApiClient client`, both operations can be selected from its USD-futures REST API: + +```csharp +IPlaceFuturesOrderRest placement = client.UsdFuturesRest; +ICancelFuturesOrderRest cancellation = client.UsdFuturesRest; +``` + +If the surface is selected dynamically instead, use `GetCapability` as shown below and handle its `null` result. Injecting `IEnumerable` is another option when you want to examine every DI registration yourself. + +Moving ticker requesting from V1 to V2: + +```csharp +var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT"); + +// V1 call, still available. +var oldTicker = await restClient.SpotApi.SharedClient.GetSpotTickerAsync( + new GetTickerRequest(symbol)); + +// V2 call on the same REST client. +var newTicker = await restClient.SpotApi.SharedApi.GetTickerAsync( + new GetTickerRequest(symbol)); +``` + + +## 2. Select a transport when it matters + +Some actions have both a transport-agnostic interface and transport-specific interfaces. For example, `IPlaceSpotOrder` returns `IExchangeCallResult`; `IPlaceSpotOrderRest` returns `HttpResult` and `IPlaceSpotOrderSocket` returns `QueryResult`. Select a transport-specific interface if your code needs transport-specific result data or require a certain transport. + +When dynamically resolving a transport-agnostic capability, the exchange shared client prefers its configured `PreferredTransport`. Specify a transport-specific reference or interface if that preference should not determine your call. + +The same operation can be requested with or without a fixed transport: + +```csharp +// One preferred transport. The capability method returns IExchangeCallResult. +var preferred = sharedClient.GetCapability( + SharedCapabilities.Orders.Spot.PlaceOrder); + +// REST only. The capability method returns HttpResult. +var rest = sharedClient.GetCapability( + SharedCapabilities.Orders.Spot.PlaceOrder.Rest); + +// Socket only. The capability method returns QueryResult. +var socket = sharedClient.GetCapability( + SharedCapabilities.Orders.Spot.PlaceOrder.Socket); +``` + +## 3. Replace broad `Supported` checks with capability lookup + +For runtime discovery, use an exchange's shared client and the `SharedCapabilities` catalog. A lookup returns `null` when the requested operation is not available for that API or trading mode. + +```csharp +var resolution = sharedClient.GetCapability( + SharedCapabilities.Orders.Futures.PlaceOrder.Rest, + TradingMode.PerpetualLinear); + +if (resolution is not null) +{ + var result = await resolution.Capability.PlaceFuturesOrderAsync(request); + // Inspect result.Success and result.Error before using result.Data. +} +``` + +`GetCapability` selects one preferred implementation. `GetCapabilities` on an exchange shared client returns all matching implementations, which may include multiple API surfaces or transports. Passing a `TradingMode` is particularly important for exchanges with separate linear and inverse futures APIs. + +For example, the Binance shared client can expose both USD- and coin-margined futures. Specify the mode when selecting one; omit it only when you intentionally want to inspect every match: + +```csharp +var inverse = sharedClient.GetCapability( + SharedCapabilities.Orders.Futures.PlaceOrder.Rest, + TradingMode.PerpetualInverse); + +var allOrderPlacements = sharedClient.GetCapabilities(); +foreach (var match in allOrderPlacements) +{ + Console.WriteLine($"{match.Transport}: " + + string.Join(", ", match.Options.SupportedTradingModes)); +} +``` + +## 4. Inspect options for parameter support + +The presence of a capability does not imply support for every field in its request model. Inspect the resolved `Options` or the capability's typed options property: + +- `RequestParameterRules`: each shared request field's `Required`, `Optional`, or `NotSupported` status for this implementation. +- `ExchangeParameterRules`: required or optional exchange-specific parameters supplied through `ExchangeParameters`. Parameters absent from these rules are not advertised as supported. +- `SupportedTradingModes`: the modes supported by this capability, which can be narrower than those supported by its containing API. + +For instance, check whether `Leverage` can be supplied when placing a Binance futures order. `NotSupported` here means you must use a separate leverage operation if the exchange provides one: + +```csharp +var placement = sharedClient.GetCapability( + SharedCapabilities.Orders.Futures.PlaceOrder.Rest, + TradingMode.PerpetualLinear); + +var leverageRule = placement?.Options.RequestParameterRules + .FirstOrDefault(rule => rule.Name == nameof(PlaceFuturesOrderRequest.Leverage)); + +if (leverageRule?.Support == RequestParameterSupport.NotSupported) + Console.WriteLine("Set leverage separately before placing the order."); + +if (placement is not null) +{ + foreach (var parameter in placement.Options.ExchangeParameterRules) + Console.WriteLine($"{parameter.Name}: {parameter.Requirement}"); +} +``` + +## 5. Review changed request and result semantics + +- The V2 ticker operations are `GetTickerAsync` and `GetAllTickersAsync`, returning `SharedTicker` rather than separate spot/futures ticker models. The V1 spot/futures ticker methods remain on their legacy facades. For bulk ticker calls, provide a trading mode where the capability requires one; consult its `RequestParameterRules` rather than assuming all exchanges use the same requirement. +- V2 socket order updates use `SharedSpotOrderUpdate` or `SharedFuturesOrderUpdate`. Code using update-only fields such as `LastTrade` should use the update model; ordinary order retrieval remains on `SharedSpotOrder` or `SharedFuturesOrder`. +- A transport-agnostic operation generally returns `IExchangeCallResult`, which includes the exchange name. The spot and futures symbol catalog operations still return `ICallResult`. Switch to a REST or socket interface if existing code expects `HttpResult` or `QueryResult`. +- V2 `ICloseFullPosition` closes the entire position. It is not a drop-in replacement for V1 `ClosePositionAsync` with `ClosePositionRequest.Quantity`; use an order operation for partial closes when supported by the exchange. +The full-close distinction is visible in the request shape: unlike V1 `ClosePositionRequest`, `CloseFullPositionRequest` has no quantity parameter. Check for the capability before using it because an exchange need not provide a full-close operation: +```csharp +var close = sharedClient.GetCapability( + SharedCapabilities.Positions.CloseFullPosition.Rest, + TradingMode.PerpetualLinear); + +if (close is not null) +{ + var symbol = new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDT"); + var result = await close.Capability.CloseFullPositionAsync( + new CloseFullPositionRequest(symbol)); +} +``` + +## 6. If using CryptoClients.Net + +The existing `IExchangeRestClient` and `IExchangeSocketClient` remain available. CryptoClients.Net also registers `IExchangeSharedApiClient` for V2 cross-exchange capability lookup: + +```csharp +var resolution = sharedApis.GetCapability( + Exchange.Binance, + SharedCapabilities.Orders.Futures.PlaceOrder.Rest, + TradingMode.PerpetualLinear); +``` + +Use an exchange-specific property such as `sharedApis.Binance` when you want its typed `SpotRest`, `SpotSocket`, or futures API properties. For cross-exchange queries, `GetCapabilities(...)` returns **one preferred match per exchange**. Use `GetImplementations(...)` when you need every matching transport and API surface for each exchange. + +With DI, the lookup can be used without replacing existing REST/socket registrations: + +```csharp +using CryptoClients.Net.Enums; +using CryptoClients.Net.Interfaces; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; + +services.AddCryptoClients(); + +// Later, in a service receiving IExchangeSharedApiClient through DI: +var preferredPerExchange = sharedApis.GetCapabilities( + SharedCapabilities.Orders.Futures.PlaceOrder, + TradingMode.PerpetualLinear, + exchanges: [Exchange.Binance]); + +var everyBinanceImplementation = sharedApis.GetImplementations( + SharedCapabilities.Orders.Futures.PlaceOrder, + exchanges: [Exchange.Binance]); +``` + +`preferredPerExchange` contains at most one matching result for Binance; `everyBinanceImplementation` can contain both REST and socket implementations for USD and coin futures. If you know which API you need, the typed property remains the clearest choice: + +```csharp +IPlaceFuturesOrderRest usdFutures = sharedApis.Binance.UsdFuturesRest; +IPlaceFuturesOrderSocket coinFuturesSocket = sharedApis.Binance.CoinFuturesSocket; +``` + +### Migrating cross-exchange async enumeration + +V1 `IExchangeRestClient` offers operation-specific `...AsyncEnumerable` methods. For example, a service receiving that interface could stream spot ticker results from selected exchanges as their requests finish: + +```csharp +using CryptoClients.Net.Enums; +using CryptoClients.Net.Interfaces; +using CryptoExchange.Net.SharedApis; +using System; +using System.Threading; + +async Task PrintTickersV1Async(IExchangeRestClient client, SharedSymbol symbol, CancellationToken ct) +{ + await foreach (var result in client.GetSpotTickerAsyncEnumerable( + new GetTickerRequest(symbol), + [Exchange.Binance, Exchange.Bybit], + ct)) + { + Console.WriteLine($"{result.Exchange}: {result.Data?.LastPrice}"); + } +} +``` + +In V2, inject `IExchangeSharedApiClient` instead. Select one REST ticker capability per exchange, start the calls, then use `ParallelEnumerateAsync` to yield results in completion order: + +```csharp +using CryptoClients.Net; // ParallelEnumerateAsync +using CryptoClients.Net.Enums; +using CryptoClients.Net.Interfaces; +using CryptoExchange.Net.SharedApis; +using System; +using System.Linq; +using System.Threading; + +async Task PrintTickersV2Async(IExchangeSharedApiClient sharedApis, SharedSymbol symbol, CancellationToken ct) +{ + var capabilities = sharedApis.GetCapabilities( + SharedCapabilities.Tickers.GetTicker.Rest, + TradingMode.Spot, + exchanges: [Exchange.Binance, Exchange.Bybit]); + + var tasks = capabilities.Select(match => + match.Capability.GetTickerAsync(new GetTickerRequest(symbol), ct)); + + await foreach (var result in tasks.ParallelEnumerateAsync()) + { + Console.WriteLine($"{result.Exchange}: {result.Data?.LastPrice}"); + } +} +``` + +The V2 results contain `SharedTicker`, rather than V1's `SharedSpotTicker`. `GetCapabilities` omits exchanges without a matching supported capability and returns one preferred match per exchange; use `GetImplementations` if you need every matching API surface. `ParallelEnumerateAsync` is a CryptoClients.Net extension on `IEnumerable>`, not a method on `IExchangeSharedApiClient`. Passing `ct` to each call cancels its underlying request; the enumeration helper itself has no cancellation-token parameter. + +## Compatibility checklist + +- Existing V1 `SharedClient` methods can be migrated incrementally, but some legacy option property types and concrete option names have changed. Code that explicitly names those types may require edits even when it keeps calling V1 methods. +- `EndpointOptions` and `EndpointName` are retained as obsolete compatibility members. Move discovery code to `CapabilityOptions` and `OperationName` when practical. +- Review code that assumes a broad interface implies every operation is supported. +- Test each exchange/trading-mode combination used by your application, especially bulk market-data requests, futures order placement, and socket order updates.