diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4a8d999 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: csharp +mono: none +solution: Kraken.Net.sln +dotnet: 2.0.0 +dist: xenial +script: + - dotnet build Kraken.Net/Kraken.Net.csproj --framework "netstandard2.0" + - dotnet test Kraken.Net.UnitTests/Kraken.Net.UnitTests.csproj \ No newline at end of file diff --git a/Kraken.Net.UnitTests/Kraken.Net.UnitTests.csproj b/Kraken.Net.UnitTests/Kraken.Net.UnitTests.csproj new file mode 100644 index 0000000..87973b4 --- /dev/null +++ b/Kraken.Net.UnitTests/Kraken.Net.UnitTests.csproj @@ -0,0 +1,19 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + diff --git a/Kraken.Net.UnitTests/KrakenClientTests.cs b/Kraken.Net.UnitTests/KrakenClientTests.cs new file mode 100644 index 0000000..882871f --- /dev/null +++ b/Kraken.Net.UnitTests/KrakenClientTests.cs @@ -0,0 +1,83 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Logging; +using CryptoExchange.Net.Objects; +using Kraken.Net.Objects; +using Kraken.Net.UnitTests.TestImplementations; + +namespace Kraken.Net.UnitTests +{ + [TestFixture] + public class KrakenClientTests + { + [TestCase()] + public void TestConversions() + { + var ignoreMethods = new string[] + { + "GetMarkets", + "GetOrderBook", + }; + var defaultParameterValues = new Dictionary + { + { "assets", new [] { "XBTUSD" } }, + {"clientOrderId", null } + }; + + var methods = typeof(KrakenClient).GetMethods(BindingFlags.Public | BindingFlags.Instance); + var callResultMethods = methods.Where(m => m.ReturnType.IsGenericType && m.ReturnType.GetGenericTypeDefinition() == typeof(WebCallResult<>)); + foreach (var method in callResultMethods) + { + if (ignoreMethods.Contains(method.Name)) + continue; + + var expectedType = method.ReturnType.GetGenericArguments()[0]; + var expected = typeof(TestHelpers).GetMethod("CreateObjectWithTestParameters").MakeGenericMethod(expectedType).Invoke(null, null); + var parameters = TestHelpers.CreateParametersForMethod(method, defaultParameterValues); + var client = TestHelpers.CreateResponseClient(SerializeExpected(expected), new KrakenClientOptions(){ ApiCredentials = new ApiCredentials("Test", "Test"), LogVerbosity = LogVerbosity.Debug}); + + // act + var result = method.Invoke(client, parameters); + var callResult = result.GetType().GetProperty("Success").GetValue(result); + var data = result.GetType().GetProperty("Data").GetValue(result); + + // assert + Assert.AreEqual(true, callResult); + Assert.IsTrue(TestHelpers.AreEqual(expected, data), method.Name); + } + } + + + [TestCase()] + public void TestErrorResult_Should_ResultInFailedCall() + { + // arrange + var client = TestHelpers.CreateAuthResponseClient($"{{\"error\": [\"first error\", \"another error\"], \"result\": null}}"); + + // act + var result = client.GetMarkets(); + + // assert + Assert.IsFalse(result.Success); + Assert.IsTrue(result.Error.Message.Contains("first error")); + Assert.IsTrue(result.Error.Message.Contains("another error")); + } + + public string SerializeExpected(T data) + { + var result = new KrakenResult() + { + Result = data, + Error = new string[] {} + }; + + return JsonConvert.SerializeObject(result); + } + } +} diff --git a/Kraken.Net.UnitTests/KrakenSocketClientTests.cs b/Kraken.Net.UnitTests/KrakenSocketClientTests.cs new file mode 100644 index 0000000..680efe9 --- /dev/null +++ b/Kraken.Net.UnitTests/KrakenSocketClientTests.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CryptoExchange.Net; +using CryptoExchange.Net.Objects; +using Kraken.Net.UnitTests.TestImplementations; +using Kucoin.Net.UnitTests.TestImplementations; +using Newtonsoft.Json; +using NUnit.Framework; + +namespace Kraken.Net.UnitTests +{ + [TestFixture] + public class KrakenSocketClientTests + { + [Test] + public void Subscribe_Should_SucceedIfAckResponse() + { + // arrange + var socket = new TestSocket(); + socket.CanConnect = true; + var client = TestHelpers.CreateSocketClient(socket); + + // act + var subTask = client.SubscribeToTickerUpdatesAsync("test", test => { }); + socket.InvokeMessage($"{{\"channelID\": 1, \"status\": \"subscribed\", \"reqid\":\"{BaseClient.LastId}\"}}"); + var subResult = subTask.Result; + + // assert + Assert.IsTrue(subResult.Success); + } + + [Test] + public void Subscribe_Should_FailIfNotAckResponse() + { + // arrange + var socket = new TestSocket(); + socket.CanConnect = true; + var client = TestHelpers.CreateSocketClient(socket); + + // act + var subTask = client.SubscribeToTickerUpdatesAsync("test", test => { }); + socket.InvokeMessage($"{{\"channelID\": 1, \"status\": \"error\", \"errormessage\": \"Failed to sub\", \"reqid\":\"{BaseClient.LastId}\"}}"); + var subResult = subTask.Result; + + // assert + Assert.IsFalse(subResult.Success); + Assert.IsTrue(subResult.Error.Message.Contains("Failed to sub")); + } + } +} diff --git a/Kraken.Net.UnitTests/TestImplementations/TestHelpers.cs b/Kraken.Net.UnitTests/TestImplementations/TestHelpers.cs new file mode 100644 index 0000000..da8c271 --- /dev/null +++ b/Kraken.Net.UnitTests/TestImplementations/TestHelpers.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using CryptoExchange.Net; +using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Logging; +using Kraken.Net.Interfaces; +using Moq; +using Newtonsoft.Json; + +namespace Kraken.Net.UnitTests.TestImplementations +{ + public class TestHelpers + { + [ExcludeFromCodeCoverage] + public static bool AreEqual(object self, object to, params string[] ignore) + { + if (self == null && to == null) + return true; + + if ((self != null && to == null) || (self == null && to != null)) + return false; + + var type = self.GetType(); + if (type.IsArray) + { + var list = (Array) self; + var listOther = (Array)to; + for (int i = 0; i < list.Length; i++) + { + if(!AreEqual(list.GetValue(i), listOther.GetValue(i))) + return false; + } + + return true; + } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var dict = (IDictionary) self; + var other = (IDictionary) to; + var items1 = new List(); + var items2 = new List(); + foreach (DictionaryEntry item in dict) + items1.Add(item); + foreach (DictionaryEntry item in other) + items2.Add(item); + + for (int i = 0; i < items1.Count; i++) + { + if (!AreEqual(items1[i].Key, items2[i].Key)) + return false; + if (!AreEqual(items1[i].Value, items2[i].Value)) + return false; + } + + return true; + } + + + if (type.IsValueType || type == typeof(string)) + return Equals(self, to); + + var ignoreList = new List(ignore); + foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (ignoreList.Contains(pi.Name)) + continue; + + var selfValue = type.GetProperty(pi.Name).GetValue(self, null); + var toValue = type.GetProperty(pi.Name).GetValue(to, null); + + if (pi.PropertyType.IsValueType || pi.PropertyType == typeof(string)) + { + if (!Equals(selfValue, toValue)) + return false; + continue; + } + + if (pi.PropertyType.IsClass) + { + if (!AreEqual(selfValue, toValue, ignore)) + return false; + continue; + } + + if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))) + return false; + } + + return true; + } + + public static KrakenSocketClient CreateSocketClient(IWebsocket socket, KrakenSocketClientOptions options = null) + { + KrakenSocketClient client; + client = options != null ? new KrakenSocketClient(options) : new KrakenSocketClient(new KrakenSocketClientOptions() { LogVerbosity = LogVerbosity.Debug, ApiCredentials = new ApiCredentials("Test", "Test") }); + client.SocketFactory = Mock.Of(); + Mock.Get(client.SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny(), It.IsAny())).Returns(socket); + return client; + } + + public static KrakenSocketClient CreateAuthenticatedSocketClient(IWebsocket socket, KrakenSocketClientOptions options = null) + { + KrakenSocketClient client; + client = options != null ? new KrakenSocketClient(options) : new KrakenSocketClient(new KrakenSocketClientOptions(){ LogVerbosity = LogVerbosity.Debug, ApiCredentials = new ApiCredentials("Test", "Test")}); + client.SocketFactory = Mock.Of(); + Mock.Get(client.SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny(), It.IsAny())).Returns(socket); + return client; + } + + public static IKrakenClient CreateClient(KrakenClientOptions options = null) + { + IKrakenClient client; + client = options != null ? new KrakenClient(options) : new KrakenClient(new KrakenClientOptions(){LogVerbosity = LogVerbosity.Debug}); + client.RequestFactory = Mock.Of(); + return client; + } + + public static IKrakenClient CreateAuthResponseClient(string response) + { + var client = (KrakenClient)CreateClient(new KrakenClientOptions(){ ApiCredentials = new ApiCredentials("Test", "Test")}); + SetResponse(client, response); + return client; + } + + + public static IKrakenClient CreateResponseClient(string response, KrakenClientOptions options = null) + { + var client = (KrakenClient)CreateClient(options); + SetResponse(client, response); + return client; + } + + public static IKrakenClient CreateResponseClient(T response, KrakenClientOptions options = null) + { + var client = (KrakenClient)CreateClient(options); + SetResponse(client, JsonConvert.SerializeObject(response)); + return client; + } + + public static void SetResponse(RestClient client, string responseData) + { + var expectedBytes = Encoding.UTF8.GetBytes(responseData); + var responseStream = new MemoryStream(); + responseStream.Write(expectedBytes, 0, expectedBytes.Length); + responseStream.Seek(0, SeekOrigin.Begin); + + var response = new Mock(); + response.Setup(c => c.GetResponseStream()).Returns(responseStream); + + var request = new Mock(); + request.Setup(c => c.Headers).Returns(new WebHeaderCollection()); + request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com")); + request.Setup(c => c.GetResponse()).Returns(Task.FromResult(response.Object)); + request.Setup(c => c.GetRequestStream()).Returns(Task.FromResult((Stream)new MemoryStream())); + + var factory = Mock.Get(client.RequestFactory); + factory.Setup(c => c.Create(It.IsAny())) + .Returns(request.Object); + } + + public static void SetErrorWithResponse(IKrakenClient client, string responseData, HttpStatusCode code) + { + var expectedBytes = Encoding.UTF8.GetBytes(responseData); + var responseStream = new MemoryStream(); + responseStream.Write(expectedBytes, 0, expectedBytes.Length); + responseStream.Seek(0, SeekOrigin.Begin); + + var r = new Mock(); + r.Setup(x => x.GetResponseStream()).Returns(responseStream); + var we = new WebException("", null, WebExceptionStatus.Success, r.Object); + + var request = new Mock(); + request.Setup(c => c.Headers).Returns(new WebHeaderCollection()); + request.Setup(c => c.GetResponse()).Throws(we); + + var factory = Mock.Get(client.RequestFactory); + factory.Setup(c => c.Create(It.IsAny())) + .Returns(request.Object); + } + + + public static T CreateObjectWithTestParameters() where T: class + { + var type = typeof(T); + if (type.IsArray) + { + var elementType = type.GetElementType(); + var result = Array.CreateInstance(elementType, 2); + result.SetValue(GetTestValue(elementType, 0), 0); + result.SetValue(GetTestValue(elementType, 1), 1); + return (T)Convert.ChangeType(result, type); + } + else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var result = (IDictionary)Activator.CreateInstance(type); + result.Add(GetTestValue(type.GetGenericArguments()[0], 0), GetTestValue(type.GetGenericArguments()[1], 0)); + result.Add(GetTestValue(type.GetGenericArguments()[0], 1), GetTestValue(type.GetGenericArguments()[1], 1)); + return (T)Convert.ChangeType(result, type); + } + else + { + var obj = Activator.CreateInstance(); + return FillWithTestParameters(obj); + } + } + + public static T FillWithTestParameters(T obj) where T : class + { + + var properties = obj.GetType().GetProperties(); + int i = 1; + foreach (var property in properties) + { + var value = GetTestValue(property.PropertyType, i); + Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; + object safeValue = (value == null) ? null : Convert.ChangeType(value, t); + property.SetValue(obj, safeValue, null); + i++; + } + + return obj; + } + + public static object[] CreateParametersForMethod(MethodInfo method, Dictionary defaultValues) + { + var param = method.GetParameters(); + var result = new object[param.Length]; + for(int i = 0; i < param.Length; i++) + { + if (defaultValues.ContainsKey(param[i].Name)) + result[i] = defaultValues[param[i].Name]; + else + result[i] = GetTestValue(param[i].ParameterType, i); + } + + return result; + } + + public static object GetTestValue(Type type, int i) + { + if (type == typeof(bool)) + return true; + + if (type == typeof(bool?)) + return (bool?) true; + + if (type == typeof(decimal)) + return i / 10m; + + if (type == typeof(decimal?)) + return (decimal?) (i / 10m); + + if (type == typeof(int) || type == typeof(long)) + return i; + + if (type == typeof(int?)) + return (int?) i; + + if (type == typeof(long?)) + return (long?) i; + + if (type == typeof(DateTime)) + return new DateTime(2019, 1, Math.Max(i, 1)); + + if(type == typeof(DateTime?)) + return (DateTime?) new DateTime(2019, 1, Math.Max(i, 1)); + + if (type == typeof(string)) + return "string" + i; + + if (type.IsEnum) + { + return Activator.CreateInstance(type); + } + + if (type.IsArray) + { + var elementType = type.GetElementType(); + var result = Array.CreateInstance(elementType, 2); + result.SetValue(GetTestValue(elementType, 0), 0); + result.SetValue(GetTestValue(elementType, 1), 1); + return result; + } + + if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>))) + { + var result = (IList)Activator.CreateInstance(type); + result.Add(GetTestValue(type.GetGenericArguments()[0], 0)); + result.Add(GetTestValue(type.GetGenericArguments()[0], 1)); + return result; + } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var result = (IDictionary)Activator.CreateInstance(type); + result.Add(GetTestValue(type.GetGenericArguments()[0], 0), GetTestValue(type.GetGenericArguments()[1], 0)); + result.Add(GetTestValue(type.GetGenericArguments()[0], 1), GetTestValue(type.GetGenericArguments()[1], 1)); + return Convert.ChangeType(result, type); + } + + if (type.IsClass) + return FillWithTestParameters(Activator.CreateInstance(type)); + + return null; + } + } +} diff --git a/Kraken.Net.UnitTests/TestImplementations/TestSocket.cs b/Kraken.Net.UnitTests/TestImplementations/TestSocket.cs new file mode 100644 index 0000000..4412652 --- /dev/null +++ b/Kraken.Net.UnitTests/TestImplementations/TestSocket.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Security.Authentication; +using System.Text; +using System.Threading.Tasks; +using CryptoExchange.Net.Interfaces; +using Newtonsoft.Json; +using WebSocket4Net; + +namespace Kucoin.Net.UnitTests.TestImplementations +{ + public class TestSocket: IWebsocket + { + public bool CanConnect { get; set; } + public bool Connected { get; set; } + + public event Action OnClose; + public event Action OnMessage; + public event Action OnError; + public event Action OnOpen; + + public int Id { get; } + public bool ShouldReconnect { get; set; } + public Func DataInterpreterString { get; set; } + public Func DataInterpreterBytes { get; set; } + public DateTime? DisconnectTime { get; set; } + public string Url { get; } + public WebSocketState SocketState { get; } + public bool IsClosed => !Connected; + public bool IsOpen => Connected; + public bool PingConnection { get; set; } + public TimeSpan PingInterval { get; set; } + public SslProtocols SSLProtocols { get; set; } + public TimeSpan Timeout { get; set; } + public string Origin { get; set; } + public bool Reconnecting { get; set; } + + public Task Connect() + { + Connected = CanConnect; + return Task.FromResult(CanConnect); + } + + public void Send(string data) + { + if(!Connected) + throw new Exception("Socket not connected"); + } + + public void Reset() + { + + } + + public Task Close() + { + Connected = false; + return Task.FromResult(0); + } + + public void SetProxy(string host, int port) + { + throw new NotImplementedException(); + } + public void Dispose() + { + } + + public void InvokeClose() + { + Connected = false; + OnClose?.Invoke(); + } + + public void InvokeOpen() + { + OnOpen?.Invoke(); + } + + public void InvokeMessage(string data) + { + OnMessage?.Invoke(data); + } + + public void InvokeMessage(T data) + { + OnMessage?.Invoke(JsonConvert.SerializeObject(data)); + } + } +} diff --git a/Kraken.Net.sln b/Kraken.Net.sln new file mode 100644 index 0000000..b16b39a --- /dev/null +++ b/Kraken.Net.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28822.285 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kraken.Net", "Kraken.Net\Kraken.Net.csproj", "{FDB8D284-1B3D-4258-BF89-E2DBCADF3DE8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kraken.Net.UnitTests", "Kraken.Net.UnitTests\Kraken.Net.UnitTests.csproj", "{8BB6B9AF-27E3-4C6B-8B85-1CDA0697497C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FDB8D284-1B3D-4258-BF89-E2DBCADF3DE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDB8D284-1B3D-4258-BF89-E2DBCADF3DE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDB8D284-1B3D-4258-BF89-E2DBCADF3DE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDB8D284-1B3D-4258-BF89-E2DBCADF3DE8}.Release|Any CPU.Build.0 = Release|Any CPU + {8BB6B9AF-27E3-4C6B-8B85-1CDA0697497C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BB6B9AF-27E3-4C6B-8B85-1CDA0697497C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BB6B9AF-27E3-4C6B-8B85-1CDA0697497C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BB6B9AF-27E3-4C6B-8B85-1CDA0697497C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D55A3C45-A9F3-47E3-B382-8748EA6061BD} + EndGlobalSection +EndGlobal diff --git a/Kraken.Net/AssemblyInfo.cs b/Kraken.Net/AssemblyInfo.cs new file mode 100644 index 0000000..b9e00a6 --- /dev/null +++ b/Kraken.Net/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Kraken.Net.UnitTests")] \ No newline at end of file diff --git a/Kraken.Net/Converters/ExtendedDictionaryConverter.cs b/Kraken.Net/Converters/ExtendedDictionaryConverter.cs new file mode 100644 index 0000000..99007eb --- /dev/null +++ b/Kraken.Net/Converters/ExtendedDictionaryConverter.cs @@ -0,0 +1,83 @@ +using System; +using CryptoExchange.Net.Converters; +using Kraken.Net.Objects; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Kraken.Net.Converters +{ + internal class ExtendedDictionaryConverter: JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var data = (KrakenDictionaryResult) value; + writer.WriteStartObject(); + writer.WritePropertyName("data"); + writer.WriteRawValue(JsonConvert.SerializeObject(data.Data)); + writer.WritePropertyName("last"); + writer.WriteValue(JsonConvert.SerializeObject(data.Last, new TimestampSecondsConverter())); + writer.WriteEndObject(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var obj = JObject.Load(reader); + var inner = obj.First; + var data = inner.First.ToObject(); + var result = (KrakenDictionaryResult)Activator.CreateInstance(objectType); + result.Data = data; + var timestamp = (long)obj["last"]; + if(timestamp > 1000000000000000000) + result.Last = obj["last"].ToObject(new JsonSerializer() {Converters = {new TimestampNanoSecondsConverter()}}); + else + result.Last = obj["last"].ToObject(new JsonSerializer() {Converters = {new TimestampSecondsConverter()}}); + return Convert.ChangeType(result, objectType); + } + + public override bool CanConvert(Type objectType) + { + return true; + } + } + + /// + /// Dictionary result + /// + /// Type of the data + public class KrakenDictionaryResult + { + /// + /// The data + /// + public T Data { get; set; } + /// + /// The timestamp of the data + /// + [JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Last { get; set; } + } + + /// + /// Kline result + /// + [JsonConverter(typeof(ExtendedDictionaryConverter))] + public class KrakenKlinesResult : KrakenDictionaryResult + { + } + + /// + /// Trade result + /// + [JsonConverter(typeof(ExtendedDictionaryConverter))] + public class KrakenTradesResult : KrakenDictionaryResult + { + } + + /// + /// Spread result + /// + [JsonConverter(typeof(ExtendedDictionaryConverter))] + public class KrakenSpreadsResult : KrakenDictionaryResult + { + } +} diff --git a/Kraken.Net/Converters/KlineIntervalConverter.cs b/Kraken.Net/Converters/KlineIntervalConverter.cs new file mode 100644 index 0000000..9152d13 --- /dev/null +++ b/Kraken.Net/Converters/KlineIntervalConverter.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using CryptoExchange.Net.Converters; +using Kraken.Net.Objects; + +namespace Kraken.Net.Converters +{ + internal class KlineIntervalConverter: BaseConverter + { + public KlineIntervalConverter() : this(true) { } + public KlineIntervalConverter(bool quotes) : base(quotes) { } + + protected override List> Mapping => new List> + { + new KeyValuePair(KlineInterval.OneMinute, "1"), + new KeyValuePair(KlineInterval.FiveMinutes, "5"), + new KeyValuePair(KlineInterval.FifteenMinutes, "15"), + new KeyValuePair(KlineInterval.ThirtyMinutes, "30"), + new KeyValuePair(KlineInterval.OneHour, "60"), + new KeyValuePair(KlineInterval.FourHour, "240"), + new KeyValuePair(KlineInterval.OneDay, "1440"), + new KeyValuePair(KlineInterval.OneWeek, "10080"), + new KeyValuePair(KlineInterval.FifteenDays, "21600"), + }; + } +} diff --git a/Kraken.Net/Converters/LedgerEntryTypeConverter.cs b/Kraken.Net/Converters/LedgerEntryTypeConverter.cs new file mode 100644 index 0000000..f3e556b --- /dev/null +++ b/Kraken.Net/Converters/LedgerEntryTypeConverter.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using CryptoExchange.Net.Converters; +using Kraken.Net.Objects; + +namespace Kraken.Net.Converters +{ + internal class LedgerEntryTypeConverter: BaseConverter + { + public LedgerEntryTypeConverter() : this(true) { } + public LedgerEntryTypeConverter(bool quotes) : base(quotes) { } + + protected override List> Mapping => new List> + { + new KeyValuePair(LedgerEntryType.Trade, "trade"), + new KeyValuePair(LedgerEntryType.Deposit, "deposit"), + new KeyValuePair(LedgerEntryType.Withdrawal, "withdrawal"), + new KeyValuePair(LedgerEntryType.Margin, "margin"), + }; + } +} diff --git a/Kraken.Net/Converters/OrderSideConverter.cs b/Kraken.Net/Converters/OrderSideConverter.cs new file mode 100644 index 0000000..c02d795 --- /dev/null +++ b/Kraken.Net/Converters/OrderSideConverter.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using CryptoExchange.Net.Converters; +using Kraken.Net.Objects; + +namespace Kraken.Net.Converters +{ + internal class OrderSideConverter: BaseConverter + { + public OrderSideConverter() : this(true) { } + public OrderSideConverter(bool quotes) : base(quotes) { } + + protected override List> Mapping => new List> + { + new KeyValuePair(OrderSide.Buy, "buy"), + new KeyValuePair(OrderSide.Sell, "sell"), + new KeyValuePair(OrderSide.Buy, "b"), + new KeyValuePair(OrderSide.Sell, "s"), + }; + } +} diff --git a/Kraken.Net/Converters/OrderStatusConverter.cs b/Kraken.Net/Converters/OrderStatusConverter.cs new file mode 100644 index 0000000..8fd7d83 --- /dev/null +++ b/Kraken.Net/Converters/OrderStatusConverter.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using CryptoExchange.Net.Converters; +using Kraken.Net.Objects; + +namespace Kraken.Net.Converters +{ + internal class OrderStatusConverter: BaseConverter + { + public OrderStatusConverter() : this(true) { } + public OrderStatusConverter(bool quotes) : base(quotes) { } + + protected override List> Mapping => new List> + { + new KeyValuePair(OrderStatus.Open, "open"), + new KeyValuePair(OrderStatus.Pending, "pending"), + new KeyValuePair(OrderStatus.Closed, "closed"), + new KeyValuePair(OrderStatus.Canceled, "canceled"), + new KeyValuePair(OrderStatus.Expired, "expired"), + }; + } +} diff --git a/Kraken.Net/Converters/OrderTypeConverter.cs b/Kraken.Net/Converters/OrderTypeConverter.cs new file mode 100644 index 0000000..a9ea000 --- /dev/null +++ b/Kraken.Net/Converters/OrderTypeConverter.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using CryptoExchange.Net.Converters; +using Kraken.Net.Objects; + +namespace Kraken.Net.Converters +{ + internal class OrderTypeConverter: BaseConverter + { + public OrderTypeConverter() : this(true) { } + public OrderTypeConverter(bool quotes) : base(quotes) { } + + protected override List> Mapping => new List> + { + new KeyValuePair(OrderType.Limit, "limit"), + new KeyValuePair(OrderType.Market, "market"), + new KeyValuePair(OrderType.StopLoss, "stop-loss"), + new KeyValuePair(OrderType.TakeProfit, "take-profit"), + new KeyValuePair(OrderType.StopLossProfit, "stop-loss-profit"), + new KeyValuePair(OrderType.StopLossProfitLimit, "stop-loss-profit-limit"), + new KeyValuePair(OrderType.StopLossLimit, "stop-loss-limit"), + new KeyValuePair(OrderType.TakeProfitLimit, "take-profit-limit"), + new KeyValuePair(OrderType.TrailingStop, "trailing-stop"), + new KeyValuePair(OrderType.TrailingStopLimit, "trailing-stop-limit"), + new KeyValuePair(OrderType.StopLossAndLimit, "stop-loss-and-limit"), + new KeyValuePair(OrderType.SettlePosition, "settle-position"), + }; + } +} diff --git a/Kraken.Net/Converters/OrderTypeMinimalConverter.cs b/Kraken.Net/Converters/OrderTypeMinimalConverter.cs new file mode 100644 index 0000000..da50ede --- /dev/null +++ b/Kraken.Net/Converters/OrderTypeMinimalConverter.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using CryptoExchange.Net.Converters; +using Kraken.Net.Objects; + +namespace Kraken.Net.Converters +{ + internal class OrderTypeMinimalConverter: BaseConverter + { + public OrderTypeMinimalConverter() : this(true) { } + public OrderTypeMinimalConverter(bool quotes) : base(quotes) { } + + protected override List> Mapping => new List> + { + new KeyValuePair(OrderTypeMinimal.Limit, "l"), + new KeyValuePair(OrderTypeMinimal.Market, "m"), + }; + } +} diff --git a/Kraken.Net/Converters/StreamOrderBookConverter.cs b/Kraken.Net/Converters/StreamOrderBookConverter.cs new file mode 100644 index 0000000..48e16ac --- /dev/null +++ b/Kraken.Net/Converters/StreamOrderBookConverter.cs @@ -0,0 +1,50 @@ +using Kraken.Net.Objects; +using Kraken.Net.Objects.Socket; +using Newtonsoft.Json.Linq; + +namespace Kraken.Net.Converters +{ + internal static class StreamOrderBookConverter + { + public static KrakenSocketEvent Convert(JArray arr) + { + var result = new KrakenSocketEvent(); + result.ChannelId = (int)arr[0]; + + var orderBook = new KrakenStreamOrderBook(); + if (arr.Count == 4) + { + var innerObject = arr[1]; + if (innerObject["as"] != null) + { + // snapshot + orderBook.Asks = innerObject["as"].ToObject(); + orderBook.Bids = innerObject["bs"].ToObject(); + } + else if (innerObject["a"] != null) + { + // Only asks + orderBook.Asks = innerObject["a"].ToObject(); + } + else + { + // Only bids + orderBook.Bids = innerObject["b"].ToObject(); + } + + result.Topic = (string)arr[2]; + result.Market = (string)arr[3]; + } + else + { + orderBook.Asks = arr[1]["a"].ToObject(); + orderBook.Bids = arr[2]["b"].ToObject(); + result.Topic = (string)arr[3]; + result.Market = (string)arr[4]; + } + + result.Data = orderBook; + return result; + } + } +} diff --git a/Kraken.Net/Interfaces/IKrakenClient.cs b/Kraken.Net/Interfaces/IKrakenClient.cs new file mode 100644 index 0000000..5c9048d --- /dev/null +++ b/Kraken.Net/Interfaces/IKrakenClient.cs @@ -0,0 +1,457 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.RateLimiter; +using Kraken.Net.Converters; +using Kraken.Net.Objects; + +namespace Kraken.Net.Interfaces +{ + /// + /// Interface for the Kraken client + /// + public interface IKrakenClient: IRestClient + { + /// + /// Get the server time + /// + /// Server time + CallResult GetServerTime(); + + /// + /// Get the server time + /// + /// Server time + Task> GetServerTimeAsync(); + + /// + /// Get a list of assets and info about them + /// + /// Filter list for specific assets + /// Dictionary of asset info + WebCallResult> GetAssets(params string[] assets); + + /// + /// Get a list of assets and info about them + /// + /// Filter list for specific assets + /// Dictionary of asset info + Task>> GetAssetsAsync(params string[] assets); + + /// + /// Get a list of markets and info about them + /// + /// Filter list for specific markets + /// Dictionary of market info + WebCallResult> GetMarkets(params string[] markets); + + /// + /// Get a list of markets and info about them + /// + /// Filter list for specific markets + /// Dictionary of market info + Task>> GetMarketsAsync(params string[] markets); + + /// + /// Get tickers for markets + /// + /// Markets to get tickers for + /// Dictionary with ticker info + WebCallResult> GetTickers(params string[] markets); + + /// + /// Get tickers for markets + /// + /// Markets to get tickers for + /// Dictionary with ticker info + Task>> GetTickersAsync(params string[] markets); + + /// + /// Gets kline data for a market + /// + /// The market to get data for + /// The interval of the klines + /// Return klines since a secific time + /// Kline data + WebCallResult GetKlines(string market, KlineInterval interval, DateTime? since = null); + + /// + /// Gets kline data for a market + /// + /// The market to get data for + /// The interval of the klines + /// Return klines since a secific time + /// Kline data + Task> GetKlinesAsync(string market, KlineInterval interval, DateTime? since = null); + + /// + /// Get the order book for a market + /// + /// Market to get the book for + /// Limit to book to the best x bids/asks + /// Order book for the market + WebCallResult GetOrderBook(string market, int? limit = null); + + /// + /// Get the order book for a market + /// + /// Market to get the book for + /// Limit to book to the best x bids/asks + /// Order book for the market + Task> GetOrderBookAsync(string market, int? limit = null); + + /// + /// Get a list of recent trades for a market + /// + /// Market to get trades for + /// Return trades since a specific time + /// Recent trades + WebCallResult GetRecentTrades(string market, DateTime? since = null); + + /// + /// Get a list of recent trades for a market + /// + /// Market to get trades for + /// Return trades since a specific time + /// Recent trades + Task> GetRecentTradesAsync(string market, DateTime? since = null); + + /// + /// Get spread data for a market + /// + /// Market to get spread data for + /// Return spread data since a specific time + /// Spread data + WebCallResult GetRecentSpread(string market, DateTime? since = null); + + /// + /// Get spread data for a market + /// + /// Market to get spread data for + /// Return spread data since a specific time + /// Spread data + Task> GetRecentSpreadAsync(string market, DateTime? since = null); + + /// + /// Get balances + /// + /// Dictionary with balances for assets + WebCallResult> GetAccountBalance(); + + /// + /// Get balances + /// + /// Dictionary with balances for assets + Task>> GetAccountBalanceAsync(); + + /// + /// Get trade balance + /// + /// Base asset to get trade balance for + /// Trade balance data + WebCallResult GetTradeBalance(string baseAsset = null); + + /// + /// Get trade balance + /// + /// Base asset to get trade balance for + /// Trade balance data + Task> GetTradeBalanceAsync(string baseAsset = null); + + /// + /// Get a list of open orders + /// + /// Filter by client order id + /// List of open orders + WebCallResult GetOpenOrders(string clientOrderId = null); + + /// + /// Get a list of open orders + /// + /// Filter by client order id + /// List of open orders + Task> GetOpenOrdersAsync(string clientOrderId = null); + + /// + /// Get a list of closed orders + /// + /// Filter by client order id + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Closed orders page + WebCallResult GetClosedOrders(string clientOrderId = null, DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null); + + /// + /// Get a list of closed orders + /// + /// Filter by client order id + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Closed orders page + Task> GetClosedOrdersAsync(string clientOrderId = null, DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null); + + /// + /// Get info on specific orders + /// + /// Get orders by clientOrderId + /// Get orders by their order ids + /// Dictionary with order info + WebCallResult> GetOrders(string clientOrderId = null, params string[] orderIds); + + /// + /// Get info on specific orders + /// + /// Get orders by clientOrderId + /// Get orders by their order ids + /// Dictionary with order info + Task>> GetOrdersAsync(string clientOrderId = null, params string[] orderIds); + + /// + /// Get trade history + /// + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Trade history page + WebCallResult GetTradeHistory(DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null); + + /// + /// Get trade history + /// + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Trade history page + Task> GetTradeHistoryAsync(DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null); + + /// + /// Get info on specific trades + /// + /// The trades to get info on + /// Dictionary with trade info + WebCallResult> GetTrades(params string[] tradeIds); + + /// + /// Get info on specific trades + /// + /// The trades to get info on + /// Dictionary with trade info + Task>> GetTradesAsync(params string[] tradeIds); + + /// + /// Get a list of open positions + /// + /// Filter by transaction ids + /// Dictionary with position info + WebCallResult> GetOpenPositions(params string[] transactionIds); + + /// + /// Get a list of open positions + /// + /// Filter by transaction ids + /// Dictionary with position info + Task>> GetOpenPositionsAsync(params string[] transactionIds); + + /// + /// Get ledger entries info + /// + /// Filter list by asset names + /// Filter list by entry types + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Ledger entries page + WebCallResult GetLedgerInfo(string[] assets = null, LedgerEntryType[] entryTypes = null, DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null); + + /// + /// Get ledger entries info + /// + /// Filter list by asset names + /// Filter list by entry types + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Ledger entries page + Task> GetLedgerInfoAsync(string[] assets = null, LedgerEntryType[] entryTypes = null, DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null); + + /// + /// Get info on specific ledger entries + /// + /// The ids to get info for + /// Dictionary with ledger entry info + WebCallResult> GetLedgersEntry(params string[] ledgerIds); + + /// + /// Get info on specific ledger entries + /// + /// The ids to get info for + /// Dictionary with ledger entry info + Task>> GetLedgersEntryAsync(params string[] ledgerIds); + + /// + /// Get trade volume + /// + /// Markets to get data for + /// Trade fee info + WebCallResult GetTradeVolume(params string[] markets); + + /// + /// Get trade volume + /// + /// Markets to get data for + /// Trade fee info + Task> GetTradeVolumeAsync(params string[] markets); + + /// + /// Get deposit methods + /// + /// Asset to get methods for + /// Array with methods for deposit + WebCallResult GetDepositMethods(string asset); + + /// + /// Get deposit methods + /// + /// Asset to get methods for + /// Array with methods for deposit + Task> GetDepositMethodsAsync(string asset); + + /// + /// Get deposit addresses for an asset + /// + /// The asset to get the deposit address for + /// The method of deposit + /// Whether to generate a new address + /// + WebCallResult GetDepositAddresses(string asset, string depositMethod, bool generateNew = false); + + /// + /// Get deposit addresses for an asset + /// + /// The asset to get the deposit address for + /// The method of deposit + /// Whether to generate a new address + /// + Task> GetDepositAddressesAsync(string asset, string depositMethod, bool generateNew = false); + + /// + /// Get status deposits for an asset + /// + /// Asset to get deposit info for + /// The deposit method + /// Deposit status list + WebCallResult GetDepositStatus(string asset, string depositMethod); + + /// + /// Get status deposits for an asset + /// + /// Asset to get deposit info for + /// The deposit method + /// Deposit status list + Task> GetDepositStatusAsync(string asset, string depositMethod); + + /// + /// Place a new order + /// + /// The market the order is on + /// The side of the order + /// The type of the order + /// The quantity of the order + /// A client id to reference the order by + /// Price of the order: + /// Limit=limit price, + /// StopLoss=stop loss price, + /// TakeProfit=take profit price, + /// StopLossProfit=stop loss price, + /// StopLossProfitLimit=stop loss price, + /// StopLossLimit=stop loss trigger price, + /// TakeProfitLimit=take profit trigger price, + /// TrailingStop=trailing stop offset, + /// TrailingStopLimit=trailing stop offset, + /// StopLossAndLimit=stop loss price, + /// + /// Secondary price of an order: + /// StopLossProfit/StopLossProfitLimit=take profit price, + /// StopLossLimit/TakeProfitLimit=triggered limit price, + /// TrailingStopLimit=triggered limit offset, + /// StopLossAndLimit=limit price + /// Desired leverage + /// Scheduled start time + /// Expiration time + /// Only validate inputs, don't actually place the order + /// Placed order info + WebCallResult PlaceOrder( + string market, + OrderSide side, + OrderType type, + decimal quantity, + uint? clientOrderId = null, + decimal? price = null, + decimal? secondaryPrice = null, + decimal? leverage = null, + DateTime? startTime = null, + DateTime? expireTime = null, + bool? validateOnly = null); + + /// + /// Place a new order + /// + /// The market the order is on + /// The side of the order + /// The type of the order + /// The quantity of the order + /// A client id to reference the order by + /// Price of the order: + /// Limit=limit price, + /// StopLoss=stop loss price, + /// TakeProfit=take profit price, + /// StopLossProfit=stop loss price, + /// StopLossProfitLimit=stop loss price, + /// StopLossLimit=stop loss trigger price, + /// TakeProfitLimit=take profit trigger price, + /// TrailingStop=trailing stop offset, + /// TrailingStopLimit=trailing stop offset, + /// StopLossAndLimit=stop loss price, + /// + /// Secondary price of an order: + /// StopLossProfit/StopLossProfitLimit=take profit price, + /// StopLossLimit/TakeProfitLimit=triggered limit price, + /// TrailingStopLimit=triggered limit offset, + /// StopLossAndLimit=limit price + /// Desired leverage + /// Scheduled start time + /// Expiration time + /// Only validate inputs, don't actually place the order + /// Placed order info + Task> PlaceOrderAsync( + string market, + OrderSide side, + OrderType type, + decimal quantity, + uint? clientOrderId = null, + decimal? price = null, + decimal? secondaryPrice = null, + decimal? leverage = null, + DateTime? startTime = null, + DateTime? expireTime = null, + bool? validateOnly = null); + + /// + /// Cancel an order + /// + /// The id of the order to cancel + /// Cancel result + WebCallResult CancelOrder(string orderId); + + /// + /// Cancel an order + /// + /// The id of the order to cancel + /// Cancel result + Task> CancelOrderAsync(string orderId); + } +} \ No newline at end of file diff --git a/Kraken.Net/Interfaces/IKrakenSocketClient.cs b/Kraken.Net/Interfaces/IKrakenSocketClient.cs new file mode 100644 index 0000000..965d757 --- /dev/null +++ b/Kraken.Net/Interfaces/IKrakenSocketClient.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Sockets; +using Kraken.Net.Objects; +using Kraken.Net.Objects.Socket; + +namespace Kraken.Net.Interfaces +{ + /// + /// Interface for the Kraken socket client + /// + public interface IKrakenSocketClient: ISocketClient + { + /// + /// Subscribe to ticker updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + CallResult SubscribeToTickerUpdates(string market, Action> handler); + + /// + /// Subscribe to ticker updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + Task> SubscribeToTickerUpdatesAsync(string market, Action> handler); + + /// + /// Subscribe to kline updates + /// + /// Market to subscribe to + /// Kline interval + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + CallResult SubscribeToKlineUpdates(string market, KlineInterval interval, Action> handler); + + /// + /// Subscribe to kline updates + /// + /// Market to subscribe to + /// Kline interval + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + Task> SubscribeToKlineUpdatesAsync(string market, KlineInterval interval, Action> handler); + + /// + /// Subscribe to trade updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + CallResult SubscribeToTradeUpdates(string market, Action>> handler); + + /// + /// Subscribe to trade updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + Task> SubscribeToTradeUpdatesAsync(string market, Action>> handler); + + /// + /// Subscribe to spread updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + CallResult SubscribeToSpreadUpdates(string market, Action> handler); + + /// + /// Subscribe to spread updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + Task> SubscribeToSpreadUpdatesAsync(string market, Action> handler); + + /// + /// Subscribe to depth updates + /// + /// Market to subscribe to + /// Depth of the initial order book snapshot + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + CallResult SubscribeToDepthUpdates(string market, int depth, Action> handler); + + /// + /// Subscribe to depth updates + /// + /// Market to subscribe to + /// Depth of the initial order book snapshot + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + Task> SubscribeToDepthUpdatesAsync(string market, int depth, Action> handler); + } +} \ No newline at end of file diff --git a/Kraken.Net/Kraken.Net.csproj b/Kraken.Net/Kraken.Net.csproj new file mode 100644 index 0000000..88ce060 --- /dev/null +++ b/Kraken.Net/Kraken.Net.csproj @@ -0,0 +1,25 @@ + + + netstandard2.0 + + + Kraken.Net + JKorf + 0.0.1 + Kraken.Net is a .Net wrapper for the Kraken API. It includes all features the API provides, REST API and Websocket, using clear and readable objects including but not limited to Reading market info, Placing and managing orders and Reading balances and funds + false + Kraken Kraken.Net C# .Net CryptoCurrency Exchange API wrapper + https://github.com/JKorf/Kraken.Net + https://github.com/JKorf/Kraken.Net/blob/master/LICENSE + https://raw.githubusercontent.com/JKorf/Kraken.Net/master/Resources/icon.png + en + true + 0.0.1 - Initial release + + + Kraken.Net.xml + + + + + \ No newline at end of file diff --git a/Kraken.Net/Kraken.Net.xml b/Kraken.Net/Kraken.Net.xml new file mode 100644 index 0000000..99c7637 --- /dev/null +++ b/Kraken.Net/Kraken.Net.xml @@ -0,0 +1,2574 @@ + + + + Kraken.Net + + + + + Dictionary result + + Type of the data + + + + The data + + + + + The timestamp of the data + + + + + Kline result + + + + + Trade result + + + + + Spread result + + + + + Interface for the Kraken client + + + + + Get the server time + + Server time + + + + Get the server time + + Server time + + + + Get a list of assets and info about them + + Filter list for specific assets + Dictionary of asset info + + + + Get a list of assets and info about them + + Filter list for specific assets + Dictionary of asset info + + + + Get a list of markets and info about them + + Filter list for specific markets + Dictionary of market info + + + + Get a list of markets and info about them + + Filter list for specific markets + Dictionary of market info + + + + Get tickers for markets + + Markets to get tickers for + Dictionary with ticker info + + + + Get tickers for markets + + Markets to get tickers for + Dictionary with ticker info + + + + Gets kline data for a market + + The market to get data for + The interval of the klines + Return klines since a secific time + Kline data + + + + Gets kline data for a market + + The market to get data for + The interval of the klines + Return klines since a secific time + Kline data + + + + Get the order book for a market + + Market to get the book for + Limit to book to the best x bids/asks + Order book for the market + + + + Get the order book for a market + + Market to get the book for + Limit to book to the best x bids/asks + Order book for the market + + + + Get a list of recent trades for a market + + Market to get trades for + Return trades since a specific time + Recent trades + + + + Get a list of recent trades for a market + + Market to get trades for + Return trades since a specific time + Recent trades + + + + Get spread data for a market + + Market to get spread data for + Return spread data since a specific time + Spread data + + + + Get spread data for a market + + Market to get spread data for + Return spread data since a specific time + Spread data + + + + Get balances + + Dictionary with balances for assets + + + + Get balances + + Dictionary with balances for assets + + + + Get trade balance + + Base asset to get trade balance for + Trade balance data + + + + Get trade balance + + Base asset to get trade balance for + Trade balance data + + + + Get a list of open orders + + Filter by client order id + List of open orders + + + + Get a list of open orders + + Filter by client order id + List of open orders + + + + Get a list of closed orders + + Filter by client order id + Return data after this time + Return data before this time + Offset the results by + Closed orders page + + + + Get a list of closed orders + + Filter by client order id + Return data after this time + Return data before this time + Offset the results by + Closed orders page + + + + Get info on specific orders + + Get orders by clientOrderId + Get orders by their order ids + Dictionary with order info + + + + Get info on specific orders + + Get orders by clientOrderId + Get orders by their order ids + Dictionary with order info + + + + Get trade history + + Return data after this time + Return data before this time + Offset the results by + Trade history page + + + + Get trade history + + Return data after this time + Return data before this time + Offset the results by + Trade history page + + + + Get info on specific trades + + The trades to get info on + Dictionary with trade info + + + + Get info on specific trades + + The trades to get info on + Dictionary with trade info + + + + Get a list of open positions + + Filter by transaction ids + Dictionary with position info + + + + Get a list of open positions + + Filter by transaction ids + Dictionary with position info + + + + Get ledger entries info + + Filter list by asset names + Filter list by entry types + Return data after this time + Return data before this time + Offset the results by + Ledger entries page + + + + Get ledger entries info + + Filter list by asset names + Filter list by entry types + Return data after this time + Return data before this time + Offset the results by + Ledger entries page + + + + Get info on specific ledger entries + + The ids to get info for + Dictionary with ledger entry info + + + + Get info on specific ledger entries + + The ids to get info for + Dictionary with ledger entry info + + + + Get trade volume + + Markets to get data for + Trade fee info + + + + Get trade volume + + Markets to get data for + Trade fee info + + + + Get deposit methods + + Asset to get methods for + Array with methods for deposit + + + + Get deposit methods + + Asset to get methods for + Array with methods for deposit + + + + Get deposit addresses for an asset + + The asset to get the deposit address for + The method of deposit + Whether to generate a new address + + + + + Get deposit addresses for an asset + + The asset to get the deposit address for + The method of deposit + Whether to generate a new address + + + + + Get status deposits for an asset + + Asset to get deposit info for + The deposit method + Deposit status list + + + + Get status deposits for an asset + + Asset to get deposit info for + The deposit method + Deposit status list + + + + Place a new order + + The market the order is on + The side of the order + The type of the order + The quantity of the order + A client id to reference the order by + Price of the order: + Limit=limit price, + StopLoss=stop loss price, + TakeProfit=take profit price, + StopLossProfit=stop loss price, + StopLossProfitLimit=stop loss price, + StopLossLimit=stop loss trigger price, + TakeProfitLimit=take profit trigger price, + TrailingStop=trailing stop offset, + TrailingStopLimit=trailing stop offset, + StopLossAndLimit=stop loss price, + + Secondary price of an order: + StopLossProfit/StopLossProfitLimit=take profit price, + StopLossLimit/TakeProfitLimit=triggered limit price, + TrailingStopLimit=triggered limit offset, + StopLossAndLimit=limit price + Desired leverage + Scheduled start time + Expiration time + Only validate inputs, don't actually place the order + Placed order info + + + + Place a new order + + The market the order is on + The side of the order + The type of the order + The quantity of the order + A client id to reference the order by + Price of the order: + Limit=limit price, + StopLoss=stop loss price, + TakeProfit=take profit price, + StopLossProfit=stop loss price, + StopLossProfitLimit=stop loss price, + StopLossLimit=stop loss trigger price, + TakeProfitLimit=take profit trigger price, + TrailingStop=trailing stop offset, + TrailingStopLimit=trailing stop offset, + StopLossAndLimit=stop loss price, + + Secondary price of an order: + StopLossProfit/StopLossProfitLimit=take profit price, + StopLossLimit/TakeProfitLimit=triggered limit price, + TrailingStopLimit=triggered limit offset, + StopLossAndLimit=limit price + Desired leverage + Scheduled start time + Expiration time + Only validate inputs, don't actually place the order + Placed order info + + + + Cancel an order + + The id of the order to cancel + Cancel result + + + + Cancel an order + + The id of the order to cancel + Cancel result + + + + Interface for the Kraken socket client + + + + + Subscribe to ticker updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to ticker updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to kline updates + + Market to subscribe to + Kline interval + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to kline updates + + Market to subscribe to + Kline interval + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to trade updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to trade updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to spread updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to spread updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to depth updates + + Market to subscribe to + Depth of the initial order book snapshot + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to depth updates + + Market to subscribe to + Depth of the initial order book snapshot + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Client for the Kraken Rest API + + + + + Create a new instance of KrakenClient using the default options + + + + + Create a new instance of KrakenClient using provided options + + The options to use for this client + + + + Get the server time + + Server time + + + + Get the server time + + Server time + + + + Get a list of assets and info about them + + Filter list for specific assets + Dictionary of asset info + + + + Get a list of assets and info about them + + Filter list for specific assets + Dictionary of asset info + + + + Get a list of markets and info about them + + Filter list for specific markets + Dictionary of market info + + + + Get a list of markets and info about them + + Filter list for specific markets + Dictionary of market info + + + + Get tickers for markets + + Markets to get tickers for + Dictionary with ticker info + + + + Get tickers for markets + + Markets to get tickers for + Dictionary with ticker info + + + + Gets kline data for a market + + The market to get data for + The interval of the klines + Return klines since a secific time + Kline data + + + + Gets kline data for a market + + The market to get data for + The interval of the klines + Return klines since a secific time + Kline data + + + + Get the order book for a market + + Market to get the book for + Limit to book to the best x bids/asks + Order book for the market + + + + Get the order book for a market + + Market to get the book for + Limit to book to the best x bids/asks + Order book for the market + + + + Get a list of recent trades for a market + + Market to get trades for + Return trades since a specific time + Recent trades + + + + Get a list of recent trades for a market + + Market to get trades for + Return trades since a specific time + Recent trades + + + + Get spread data for a market + + Market to get spread data for + Return spread data since a specific time + Spread data + + + + Get spread data for a market + + Market to get spread data for + Return spread data since a specific time + Spread data + + + + Get balances + + Dictionary with balances for assets + + + + Get balances + + Dictionary with balances for assets + + + + Get trade balance + + Base asset to get trade balance for + Trade balance data + + + + Get trade balance + + Base asset to get trade balance for + Trade balance data + + + + Get a list of open orders + + Filter by client order id + List of open orders + + + + Get a list of open orders + + Filter by client order id + List of open orders + + + + Get a list of closed orders + + Filter by client order id + Return data after this time + Return data before this time + Offset the results by + Closed orders page + + + + Get a list of closed orders + + Filter by client order id + Return data after this time + Return data before this time + Offset the results by + Closed orders page + + + + Get info on specific orders + + Get orders by clientOrderId + Get orders by their order ids + Dictionary with order info + + + + Get info on specific orders + + Get orders by clientOrderId + Get orders by their order ids + Dictionary with order info + + + + Get trade history + + Return data after this time + Return data before this time + Offset the results by + Trade history page + + + + Get trade history + + Return data after this time + Return data before this time + Offset the results by + Trade history page + + + + Get info on specific trades + + The trades to get info on + Dictionary with trade info + + + + Get info on specific trades + + The trades to get info on + Dictionary with trade info + + + + Get a list of open positions + + Filter by transaction ids + Dictionary with position info + + + + Get a list of open positions + + Filter by transaction ids + Dictionary with position info + + + + Get ledger entries info + + Filter list by asset names + Filter list by entry types + Return data after this time + Return data before this time + Offset the results by + Ledger entries page + + + + Get ledger entries info + + Filter list by asset names + Filter list by entry types + Return data after this time + Return data before this time + Offset the results by + Ledger entries page + + + + Get info on specific ledger entries + + The ids to get info for + Dictionary with ledger entry info + + + + Get info on specific ledger entries + + The ids to get info for + Dictionary with ledger entry info + + + + Get trade volume + + Markets to get data for + Trade fee info + + + + Get trade volume + + Markets to get data for + Trade fee info + + + + Get deposit methods + + Asset to get methods for + Array with methods for deposit + + + + Get deposit methods + + Asset to get methods for + Array with methods for deposit + + + + Get deposit addresses for an asset + + The asset to get the deposit address for + The method of deposit + Whether to generate a new address + + + + + Get deposit addresses for an asset + + The asset to get the deposit address for + The method of deposit + Whether to generate a new address + + + + + Get status deposits for an asset + + Asset to get deposit info for + The deposit method + Deposit status list + + + + Get status deposits for an asset + + Asset to get deposit info for + The deposit method + Deposit status list + + + + Place a new order + + The market the order is on + The side of the order + The type of the order + The quantity of the order + A client id to reference the order by + Price of the order: + Limit=limit price, + StopLoss=stop loss price, + TakeProfit=take profit price, + StopLossProfit=stop loss price, + StopLossProfitLimit=stop loss price, + StopLossLimit=stop loss trigger price, + TakeProfitLimit=take profit trigger price, + TrailingStop=trailing stop offset, + TrailingStopLimit=trailing stop offset, + StopLossAndLimit=stop loss price, + + Secondary price of an order: + StopLossProfit/StopLossProfitLimit=take profit price, + StopLossLimit/TakeProfitLimit=triggered limit price, + TrailingStopLimit=triggered limit offset, + StopLossAndLimit=limit price + Desired leverage + Scheduled start time + Expiration time + Only validate inputs, don't actually place the order + Placed order info + + + + Place a new order + + The market the order is on + The side of the order + The type of the order + The quantity of the order + A client id to reference the order by + Price of the order: + Limit=limit price, + StopLoss=stop loss price, + TakeProfit=take profit price, + StopLossProfit=stop loss price, + StopLossProfitLimit=stop loss price, + StopLossLimit=stop loss trigger price, + TakeProfitLimit=take profit trigger price, + TrailingStop=trailing stop offset, + TrailingStopLimit=trailing stop offset, + StopLossAndLimit=stop loss price, + + Secondary price of an order: + StopLossProfit/StopLossProfitLimit=take profit price, + StopLossLimit/TakeProfitLimit=triggered limit price, + TrailingStopLimit=triggered limit offset, + StopLossAndLimit=limit price + Desired leverage + Scheduled start time + Expiration time + Only validate inputs, don't actually place the order + Placed order info + + + + Cancel an order + + The id of the order to cancel + Cancel result + + + + Cancel an order + + The id of the order to cancel + Cancel result + + + + + + + Options for the Kraken client + + + + + ctor + + + + + Options for the Kraken socket client + + + + + ctor + + + + + Options for the Kraken symbol order book + + + + + The client to use for the socket connection. When using the same client for multiple order books the connection can be shared. + + + + + + The client to use for the socket connection. When using the same client for multiple order books the connection can be shared. + + + + Client for the Kraken websocket API + + + + + Create a new instance of KrakenSocketClient using the default options + + + + + Create a new instance of KrakenSocketClient using provided options + + The options to use for this client + + + + Subscribe to ticker updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to ticker updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to kline updates + + Market to subscribe to + Kline interval + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to kline updates + + Market to subscribe to + Kline interval + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to trade updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to trade updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to spread updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to spread updates + + Market to subscribe to + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to depth updates + + Market to subscribe to + Depth of the initial order book snapshot + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + Subscribe to depth updates + + Market to subscribe to + Depth of the initial order book snapshot + Data handler + A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + + + + + + + + + + + + + + + + + + + + + + Live order book implementation + + + + + Create a new order book instance + + The symbol the order book is for + The initial limit of entries in the order book + Options for the order book + + + + + + + + + + + + + Dispose + + + + + The time interval of kline data + + + + + 1m + + + + + 5m + + + + + 15m + + + + + 30m + + + + + 1h + + + + + 4h + + + + + 1d + + + + + 1w + + + + + 15d + + + + + Side of an order + + + + + Buy + + + + + Sell + + + + + Order type, limited to market or limit + + + + + Limit order + + + + + Market order + + + + + Order type + + + + + Limit order + + + + + Market order + + + + + Stop loss order + + + + + Take profit order + + + + + Stop loss profit order + + + + + Stop loss profit limit order + + + + + Stop loss limit order + + + + + Take profit limit order + + + + + Trailing stop order + + + + + Trailing stop limit order + + + + + Stop loss and limit order + + + + + Settle position + + + + + Status of an order + + + + + Pending + + + + + Active, not (fully) filled + + + + + Fully filled + + + + + Canceled + + + + + Expired + + + + + The type of a ledger entry + + + + + Deposit + + + + + Withdrawal + + + + + Trade change + + + + + Margin + + + + + Info on an asset + + + + + Alternative name + + + + + Class of the asset + + + + + Decimal precision of the asset + + + + + Decimals to display + + + + + Result of a cancel request + + + + + Amount of canceled orders + + + + + Pending cancellation orders + + + + + Deposit address + + + + + The actual address + + + + + The expire time of the address + + + + + If the address has been used before + + + + + Info about a deposit method + + + + + Name of the method + + + + + Deposit limit (max) of the method + + + + + The deposit fee for the method + + + + + The fee for setting up an address + + + + + Generate address + + + + + Deposit status info + + + + + The name of the deposit method + + + + + The class of the asset + + + + + The asset name + + + + + Reference id + + + + + Transaction id + + + + + Info about the transaction + + + + + The amount involved in the deposit + + + + + The fee paid for the deposit + + + + + The timestamp + + + + + Status of the transaction + + + + + Fee level details + + + + + The minimal volume for this level + + + + + The fee percentage for this level + + + + + Kline data + + + + + Timestamp of the kline + + + + + The open price for this kline + + + + + The highest price during this kline + + + + + The lowest price during this kline + + + + + The close price of this kline (or price of last trade if kline isn't closed yet) + + + + + The volume weighted average price + + + + + Volume during this kline + + + + + The number of trades during this kline + + + + + Kline data from stream + + + + + Timestamp of the kline + + + + + The end time for the kline + + + + + The open price for this kline + + + + + The highest price during this kline + + + + + The lowest price during this kline + + + + + The close price of this kline (or price of last trade if kline isn't closed yet) + + + + + The volume weighted average price + + + + + Volume during this kline + + + + + The number of trades during this kline + + + + + Ledger entry info + + + + + Reference id + + + + + Timestamp + + + + + The type of entry + + + + + Class of the asset + + + + + Name of the asset + + + + + The quantity of the entry + + + + + Fee paid + + + + + Resulting balance + + + + + Market info + + + + + Alternative name + + + + + Name to use for the socket client subscriptions + + + + + Class of the base asset + + + + + Name of the base asset + + + + + Class of the quote asset + + + + + Name of the quote asset + + + + + Let size + + + + + Decimals of the market + + + + + Lot decimals + + + + + Lot multiplier + + + + + Buy leverage amounts + + + + + Sell leverage amounts + + + + + Fee structure + + + + + Maker fee structure + + + + + The currency the fee is deducted from + + + + + Margin call level + + + + + Stop-out/liquidation margin level + + + + + Order info + + + + + Reference id + + + + + Client reference id + + + + + Status of the order + + + + + Open timestamp + + + + + Start timestamp + + + + + Expire timestamp + + + + + Close timestamp + + + + + Order details + + + + + Quantity of the order + + + + + Filled quantity + + + + + Cost of the order + + + + + Fee + + + + + Average price of the order + + + + + Stop price + + + + + Limit price + + + + + Miscellaneous info + + + + + Order flags + + + + + Reason of failure + + + + + Trade ids + + + + + Order details + + + + + The market of the order + + + + + Side of the order + + + + + Type of the order + + + + + Price of the order + + + + + Secondary price of the order ( for details) + + + + + Amount of leverage + + + + + Order description + + + + + Conditional close order description + + + + + Order book + + + + + Asks in the book + + + + + Bids in the book + + + + + Order book entry + + + + + Price of the entry + + + + + Quantity of the entry + + + + + Timestamp of change + + + + + Stream order book + + + + + Asks + + + + + Bids + + + + + ctor + + + + + Stream order book entry + + + + + Price of the entry + + + + + Quantity of the entry + + + + + Timestamp of the entry + + + + + Type of update + + + + + Base page data + + + + + Total number of records + + + + + Open orders page + + + + + Open orders + + + + + Closed orders page + + + + + Closed orders + + + + + User trades page + + + + + Trades + + + + + Ledger page + + + + + Ledger entries + + + + + Placed order info + + + + + Order ids + + + + + Descriptions + + + + + Order descriptions + + + + + Order description + + + + + Close order description + + + + + Position info + + + + + Order id + + + + + Market + + + + + Timestamp + + + + + Side + + + + + Type + + + + + Cost + + + + + Fee + + + + + Quantity + + + + + Closed quantity + + + + + Margin + + + + + Value + + + + + Net profit/loss + + + + + Misc info + + + + + Flags + + + + + Spread info + + + + + Timestamp of the data + + + + + Best bid price + + + + + Best ask price + + + + + Stream spread data + + + + + Best bid price + + + + + Best ask price + + + + + Timestamp of the data + + + + + Best bid volume + + + + + Best ask volume + + + + + Tick info + + + + + High price info + + + + + Low price info + + + + + Last trade info + + + + + Best ask info + + + + + Best bid info + + + + + Trade count info + + + + + Volume weighted average price info + + + + + Volume info + + + + + Tick info + + + + + Open price + + + + + Tick info + + + + + Open price info + + + + + Tick detail info + + + + + Value for today + + + + + Rolling 24h window value + + + + + Last trade details + + + + + Price of last trade + + + + + Quantity of last trade + + + + + Best entry info + + + + + Price of best entry + + + + + Lot quantity + + + + + Quantity + + + + + Trade info + + + + + Price of the trade + + + + + Quantity of the trade + + + + + Timestamp of trade + + + + + Side + + + + + Order type + + + + + Misc info + + + + + Trade balance info + + + + + Combined balance + + + + + Trade balance + + + + + Margin open positions + + + + + Unrealized net profit in open positions + + + + + Cost basis for open positions + + + + + Open positions valuation + + + + + Equity + + + + + Free margin + + + + + Margin level + + + + + Trade volume info + + + + + Currency + + + + + Volume + + + + + Fees structure + + + + + Maker fees structure + + + + + Fee level info + + + + + Fee + + + + + Minimal fee + + + + + Maximal fee + + + + + Next fee + + + + + Next volume + + + + + Tier volume + + + + + User trade info + + + + + Order id + + + + + Market + + + + + Timestamp of trade + + + + + Side + + + + + Order type + + + + + Price of the trade + + + + + Cost of the trade + + + + + Fee paid for trade + + + + + Quantity of the trade + + + + + Margin + + + + + Misc info + + + + + Position status + + + + + Closed average price + + + + + Closed cost + + + + + Closed fee + + + + + Closed quantity + + + + + Closed margin + + + + + Closed net profit/loss + + + + + Trade ids + + + + + Event received from the socket + + + + + + Id of the channel + + + + + The data + + + + + The topic of the data + + + + + The market the data is for + + + + diff --git a/Kraken.Net/KrakenAuthenticationProvider.cs b/Kraken.Net/KrakenAuthenticationProvider.cs new file mode 100644 index 0000000..0e0de43 --- /dev/null +++ b/Kraken.Net/KrakenAuthenticationProvider.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using CryptoExchange.Net; +using CryptoExchange.Net.Authentication; + +namespace Kraken.Net +{ + internal class KrakenAuthenticationProvider: AuthenticationProvider + { + private static readonly object nonceLock = new object(); + private static long lastNonce; + internal static string Nonce + { + get + { + lock (nonceLock) + { + var nonce = DateTime.UtcNow.Ticks; + if (nonce == lastNonce) + nonce += 1; + + lastNonce = nonce; + return lastNonce.ToString(CultureInfo.InvariantCulture); + } + } + } + + private HMACSHA512 encryptor; + + public KrakenAuthenticationProvider(ApiCredentials credentials) : base(credentials) + { + encryptor = new HMACSHA512(Convert.FromBase64String(credentials.Secret.GetString())); + } + + public override Dictionary AddAuthenticationToParameters(string uri, string method, Dictionary parameters, bool signed) + { + if (!signed) + return parameters; + + parameters.Add("nonce", Nonce); + return parameters; + } + + public override Dictionary AddAuthenticationToHeaders(string uri, string method, Dictionary parameters, bool signed) + { + if(!signed) + return new Dictionary(); + + var nonce = parameters.Single(n => n.Key == "nonce").Value; + var paramList = parameters.OrderBy(o => o.Key != "nonce"); + var pars = string.Join("&", paramList.Select(p => $"{p.Key}={p.Value}")); + + var result = new Dictionary(); + result.Add("API-Key", Credentials.Key.GetString()); + var np = nonce + pars; + byte[] nonceParamsBytes; + using (SHA256 sha = SHA256Managed.Create()) + nonceParamsBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(np)); + var pathBytes = Encoding.UTF8.GetBytes(uri.Split(new[] { ".com" }, StringSplitOptions.None)[1]); + var allBytes = pathBytes.Concat(nonceParamsBytes).ToArray(); + var sign = encryptor.ComputeHash(allBytes); + + result.Add("API-Sign", Convert.ToBase64String(sign)); + return result; + } + } +} diff --git a/Kraken.Net/KrakenClient.cs b/Kraken.Net/KrakenClient.cs new file mode 100644 index 0000000..2299f7a --- /dev/null +++ b/Kraken.Net/KrakenClient.cs @@ -0,0 +1,681 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CryptoExchange.Net; +using CryptoExchange.Net.Converters; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Objects; +using Kraken.Net.Converters; +using Kraken.Net.Interfaces; +using Kraken.Net.Objects; +using Newtonsoft.Json; + +namespace Kraken.Net +{ + /// + /// Client for the Kraken Rest API + /// + public class KrakenClient: RestClient, IKrakenClient + { + #region fields + private static KrakenClientOptions defaultOptions = new KrakenClientOptions(); + private static KrakenClientOptions DefaultOptions => defaultOptions.Copy(); + #endregion + + #region ctor + /// + /// Create a new instance of KrakenClient using the default options + /// + public KrakenClient() : this(DefaultOptions) + { + } + + /// + /// Create a new instance of KrakenClient using provided options + /// + /// The options to use for this client + public KrakenClient(KrakenClientOptions options) : base(options, options.ApiCredentials == null ? null : new KrakenAuthenticationProvider(options.ApiCredentials)) + { + postParametersPosition = PostParameters.InBody; + requestBodyFormat = RequestBodyFormat.FormData; + Configure(options); + } + #endregion + + #region methods + + /// + /// Get the server time + /// + /// Server time + public CallResult GetServerTime() => GetServerTimeAsync().Result; + /// + /// Get the server time + /// + /// Server time + public async Task> GetServerTimeAsync() + { + var result = await Execute(GetUri("/0/public/Time")).ConfigureAwait(false); + if (!result.Success) + return WebCallResult.CreateErrorResult(result.Error); + return new CallResult(result.Data.UnixTime, null); + } + + /// + /// Get a list of assets and info about them + /// + /// Filter list for specific assets + /// Dictionary of asset info + public WebCallResult> GetAssets(params string[] assets) => GetAssetsAsync(assets).Result; + /// + /// Get a list of assets and info about them + /// + /// Filter list for specific assets + /// Dictionary of asset info + public async Task>> GetAssetsAsync(params string[] assets) + { + var parameters = new Dictionary(); + if(assets.Any()) + parameters.AddOptionalParameter("asset", string.Join(",", assets)); + + return await Execute>(GetUri("/0/public/Assets"), parameters:parameters).ConfigureAwait(false); + } + + /// + /// Get a list of markets and info about them + /// + /// Filter list for specific markets + /// Dictionary of market info + public WebCallResult> GetMarkets(params string[] markets) => GetMarketsAsync(markets).Result; + /// + /// Get a list of markets and info about them + /// + /// Filter list for specific markets + /// Dictionary of market info + public async Task>> GetMarketsAsync(params string[] markets) + { + var parameters = new Dictionary(); + if (markets.Any()) + parameters.AddOptionalParameter("pair", string.Join(",", markets)); + + return await Execute>(GetUri("/0/public/AssetPairs"), parameters: parameters).ConfigureAwait(false); + } + + /// + /// Get tickers for markets + /// + /// Markets to get tickers for + /// Dictionary with ticker info + public WebCallResult> GetTickers(params string[] markets) => GetTickersAsync(markets).Result; + /// + /// Get tickers for markets + /// + /// Markets to get tickers for + /// Dictionary with ticker info + public async Task>> GetTickersAsync(params string[] markets) + { + if (!markets.Any()) + return WebCallResult>.CreateErrorResult(new ArgumentError("Specify markets to get tickers for")); + + var parameters = new Dictionary(); + parameters.AddParameter("pair", string.Join(",", markets)); + + return await Execute>(GetUri("/0/public/Ticker"), parameters: parameters).ConfigureAwait(false); + } + + /// + /// Gets kline data for a market + /// + /// The market to get data for + /// The interval of the klines + /// Return klines since a secific time + /// Kline data + public WebCallResult GetKlines(string market, KlineInterval interval, DateTime? since = null) => GetKlinesAsync(market, interval, since).Result; + /// + /// Gets kline data for a market + /// + /// The market to get data for + /// The interval of the klines + /// Return klines since a secific time + /// Kline data + public async Task> GetKlinesAsync(string market, KlineInterval interval, DateTime? since = null) + { + var parameters = new Dictionary() + { + {"pair", market}, + {"interval", JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false))} + }; + parameters.AddOptionalParameter("since", since.HasValue ? JsonConvert.SerializeObject(since, new TimestampSecondsConverter()): null); + return await Execute(GetUri("/0/public/OHLC"), parameters: parameters).ConfigureAwait(false); + } + + /// + /// Get the order book for a market + /// + /// Market to get the book for + /// Limit to book to the best x bids/asks + /// Order book for the market + public WebCallResult GetOrderBook(string market, int? limit = null) => GetOrderBookAsync(market, limit).Result; + /// + /// Get the order book for a market + /// + /// Market to get the book for + /// Limit to book to the best x bids/asks + /// Order book for the market + public async Task> GetOrderBookAsync(string market, int? limit = null) + { + var parameters = new Dictionary() + { + {"pair", market}, + }; + parameters.AddOptionalParameter("count", limit); + var result = await Execute>(GetUri("/0/public/Depth"), parameters: parameters).ConfigureAwait(false); + if(!result.Success) + return new WebCallResult(result.ResponseStatusCode, result.ResponseHeaders, null, result.Error); + return new WebCallResult(result.ResponseStatusCode, result.ResponseHeaders, result.Data.First().Value, result.Error); + } + + /// + /// Get a list of recent trades for a market + /// + /// Market to get trades for + /// Return trades since a specific time + /// Recent trades + public WebCallResult GetRecentTrades(string market, DateTime? since = null) => GetRecentTradesAsync(market, since).Result; + /// + /// Get a list of recent trades for a market + /// + /// Market to get trades for + /// Return trades since a specific time + /// Recent trades + public async Task> GetRecentTradesAsync(string market, DateTime? since = null) + { + var parameters = new Dictionary() + { + {"pair", market}, + }; + parameters.AddOptionalParameter("since", since.HasValue ? JsonConvert.SerializeObject(since, new TimestampSecondsConverter()) : null); + return await Execute(GetUri("/0/public/Trades"), parameters: parameters).ConfigureAwait(false); + } + + /// + /// Get spread data for a market + /// + /// Market to get spread data for + /// Return spread data since a specific time + /// Spread data + public WebCallResult GetRecentSpread(string market, DateTime? since = null) => GetRecentSpreadAsync(market, since).Result; + /// + /// Get spread data for a market + /// + /// Market to get spread data for + /// Return spread data since a specific time + /// Spread data + public async Task> GetRecentSpreadAsync(string market, DateTime? since = null) + { + var parameters = new Dictionary() + { + {"pair", market}, + }; + parameters.AddOptionalParameter("since", since.HasValue ? JsonConvert.SerializeObject(since, new TimestampSecondsConverter()) : null); + return await Execute(GetUri("/0/public/Spread"), parameters: parameters).ConfigureAwait(false); + } + + /// + /// Get balances + /// + /// Dictionary with balances for assets + public WebCallResult> GetAccountBalance() => GetAccountBalanceAsync().Result; + /// + /// Get balances + /// + /// Dictionary with balances for assets + public async Task>> GetAccountBalanceAsync() + { + return await Execute>(GetUri("/0/private/Balance"), Constants.PostMethod, null, true).ConfigureAwait(false); + } + + /// + /// Get trade balance + /// + /// Base asset to get trade balance for + /// Trade balance data + public WebCallResult GetTradeBalance(string baseAsset = null) => GetTradeBalanceAsync(baseAsset).Result; + /// + /// Get trade balance + /// + /// Base asset to get trade balance for + /// Trade balance data + public async Task> GetTradeBalanceAsync(string baseAsset = null) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("aclass", "currency"); + parameters.AddOptionalParameter("asset", baseAsset); + return await Execute(GetUri("/0/private/TradeBalance"), Constants.PostMethod, null, true).ConfigureAwait(false); + } + + /// + /// Get a list of open orders + /// + /// Filter by client order id + /// List of open orders + public WebCallResult GetOpenOrders(string clientOrderId = null) => GetOpenOrdersAsync(clientOrderId).Result; + /// + /// Get a list of open orders + /// + /// Filter by client order id + /// List of open orders + public async Task> GetOpenOrdersAsync(string clientOrderId = null) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("trades", true); + parameters.AddOptionalParameter("userref", clientOrderId); + return await Execute(GetUri("/0/private/OpenOrders"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get a list of closed orders + /// + /// Filter by client order id + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Closed orders page + public WebCallResult GetClosedOrders(string clientOrderId = null, DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null) => GetClosedOrdersAsync(clientOrderId, startTime, endTime, resultOffset).Result; + /// + /// Get a list of closed orders + /// + /// Filter by client order id + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Closed orders page + public async Task> GetClosedOrdersAsync(string clientOrderId = null, DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("trades", true); + parameters.AddOptionalParameter("userref", clientOrderId); + parameters.AddOptionalParameter("start", startTime.HasValue ? JsonConvert.SerializeObject(startTime.Value, new TimestampSecondsConverter()) : null); + parameters.AddOptionalParameter("end", endTime.HasValue ? JsonConvert.SerializeObject(endTime.Value, new TimestampSecondsConverter()) : null); + parameters.AddOptionalParameter("ofs", resultOffset); + return await Execute(GetUri("/0/private/ClosedOrders"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get info on specific orders + /// + /// Get orders by clientOrderId + /// Get orders by their order ids + /// Dictionary with order info + public WebCallResult> GetOrders(string clientOrderId = null, params string[] orderIds) => GetOrdersAsync(clientOrderId, orderIds).Result; + /// + /// Get info on specific orders + /// + /// Get orders by clientOrderId + /// Get orders by their order ids + /// Dictionary with order info + public async Task>> GetOrdersAsync(string clientOrderId = null, params string[] orderIds) + { + if((string.IsNullOrEmpty(clientOrderId) && !orderIds.Any()) || (!string.IsNullOrEmpty(clientOrderId) && orderIds.Any())) + return WebCallResult>.CreateErrorResult(new ArgumentError("Provide either clientOrderId or orderIds")); + + var parameters = new Dictionary(); + parameters.AddOptionalParameter("trades", true); + parameters.AddOptionalParameter("userref", clientOrderId); + parameters.AddOptionalParameter("txid", orderIds.Any() ? string.Join(",", orderIds): null); + return await Execute>(GetUri("/0/private/QueryOrders"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get trade history + /// + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Trade history page + public WebCallResult GetTradeHistory(DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null) => GetTradeHistoryAsync(startTime, endTime, resultOffset).Result; + /// + /// Get trade history + /// + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Trade history page + public async Task> GetTradeHistoryAsync(DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("trades", true); + parameters.AddOptionalParameter("start", startTime.HasValue ? JsonConvert.SerializeObject(startTime.Value, new TimestampSecondsConverter()) : null); + parameters.AddOptionalParameter("end", endTime.HasValue ? JsonConvert.SerializeObject(endTime.Value, new TimestampSecondsConverter()) : null); + parameters.AddOptionalParameter("ofs", resultOffset); + return await Execute(GetUri("/0/private/TradesHistory"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get info on specific trades + /// + /// The trades to get info on + /// Dictionary with trade info + public WebCallResult> GetTrades(params string[] tradeIds) => GetTradesAsync(tradeIds).Result; + /// + /// Get info on specific trades + /// + /// The trades to get info on + /// Dictionary with trade info + public async Task>> GetTradesAsync(params string[] tradeIds) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("trades", true); + parameters.AddOptionalParameter("txid", tradeIds.Any() ? string.Join(",", tradeIds) : null); + return await Execute>(GetUri("/0/private/QueryTrades"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get a list of open positions + /// + /// Filter by transaction ids + /// Dictionary with position info + public WebCallResult> GetOpenPositions(params string[] transactionIds) => GetOpenPositionsAsync(transactionIds).Result; + /// + /// Get a list of open positions + /// + /// Filter by transaction ids + /// Dictionary with position info + public async Task>> GetOpenPositionsAsync(params string[] transactionIds) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("docalcs", true); + parameters.AddOptionalParameter("txid", transactionIds.Any() ? string.Join(",", transactionIds) : null); + return await Execute>(GetUri("/0/private/OpenPositions"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get ledger entries info + /// + /// Filter list by asset names + /// Filter list by entry types + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Ledger entries page + public WebCallResult GetLedgerInfo(string[] assets = null, LedgerEntryType[] entryTypes = null, DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null) => GetLedgerInfoAsync(assets, entryTypes, startTime, endTime, resultOffset).Result; + /// + /// Get ledger entries info + /// + /// Filter list by asset names + /// Filter list by entry types + /// Return data after this time + /// Return data before this time + /// Offset the results by + /// Ledger entries page + public async Task> GetLedgerInfoAsync(string[] assets = null, LedgerEntryType[] entryTypes = null, DateTime? startTime = null, DateTime? endTime = null, int? resultOffset = null) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("asset", assets != null ? string.Join(",", assets) : null); + parameters.AddOptionalParameter("type", entryTypes != null ? string.Join(",", entryTypes.Select(e => JsonConvert.SerializeObject(e, new LedgerEntryTypeConverter()))) : null); + parameters.AddOptionalParameter("start", startTime.HasValue ? JsonConvert.SerializeObject(startTime.Value, new TimestampSecondsConverter()) : null); + parameters.AddOptionalParameter("end", endTime.HasValue ? JsonConvert.SerializeObject(endTime.Value, new TimestampSecondsConverter()) : null); + parameters.AddOptionalParameter("ofs", resultOffset); + return await Execute(GetUri("/0/private/Ledgers"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get info on specific ledger entries + /// + /// The ids to get info for + /// Dictionary with ledger entry info + public WebCallResult> GetLedgersEntry(params string[] ledgerIds) => GetLedgersEntryAsync(ledgerIds).Result; + /// + /// Get info on specific ledger entries + /// + /// The ids to get info for + /// Dictionary with ledger entry info + public async Task>> GetLedgersEntryAsync(params string[] ledgerIds) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("id", ledgerIds.Any() ? string.Join(",", ledgerIds) : null); + return await Execute>(GetUri("/0/private/QueryLedgers"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get trade volume + /// + /// Markets to get data for + /// Trade fee info + public WebCallResult GetTradeVolume(params string[] markets) => GetTradeVolumeAsync(markets).Result; + /// + /// Get trade volume + /// + /// Markets to get data for + /// Trade fee info + public async Task> GetTradeVolumeAsync(params string[] markets) + { + var parameters = new Dictionary(); + parameters.AddOptionalParameter("fee-info", true); + parameters.AddOptionalParameter("pair", markets.Any() ? string.Join(",", markets) : null); + return await Execute(GetUri("/0/private/TradeVolume"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get deposit methods + /// + /// Asset to get methods for + /// Array with methods for deposit + public WebCallResult GetDepositMethods(string asset) => GetDepositMethodsAsync(asset).Result; + /// + /// Get deposit methods + /// + /// Asset to get methods for + /// Array with methods for deposit + public async Task> GetDepositMethodsAsync(string asset) + { + var parameters = new Dictionary() + { + {"asset", asset} + }; + return await Execute(GetUri("/0/private/DepositMethods"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get deposit addresses for an asset + /// + /// The asset to get the deposit address for + /// The method of deposit + /// Whether to generate a new address + /// + public WebCallResult GetDepositAddresses(string asset, string depositMethod, bool generateNew = false) => GetDepositAddressesAsync(asset, depositMethod, generateNew).Result; + /// + /// Get deposit addresses for an asset + /// + /// The asset to get the deposit address for + /// The method of deposit + /// Whether to generate a new address + /// + public async Task> GetDepositAddressesAsync(string asset, string depositMethod, bool generateNew = false) + { + var parameters = new Dictionary() + { + {"asset", asset}, + {"method", depositMethod}, + }; + + if(generateNew) + // If False is send it will still generate new, so only add it when it's true + parameters.Add("new", generateNew); + + return await Execute(GetUri("/0/private/DepositAddresses"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Get status deposits for an asset + /// + /// Asset to get deposit info for + /// The deposit method + /// Deposit status list + public WebCallResult GetDepositStatus(string asset, string depositMethod) => GetDepositStatusAsync(asset, depositMethod).Result; + /// + /// Get status deposits for an asset + /// + /// Asset to get deposit info for + /// The deposit method + /// Deposit status list + public async Task> GetDepositStatusAsync(string asset, string depositMethod) + { + var parameters = new Dictionary() + { + {"asset", asset}, + {"method", depositMethod}, + }; + + return await Execute(GetUri("/0/private/DepositStatus"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Place a new order + /// + /// The market the order is on + /// The side of the order + /// The type of the order + /// The quantity of the order + /// A client id to reference the order by + /// Price of the order: + /// Limit=limit price, + /// StopLoss=stop loss price, + /// TakeProfit=take profit price, + /// StopLossProfit=stop loss price, + /// StopLossProfitLimit=stop loss price, + /// StopLossLimit=stop loss trigger price, + /// TakeProfitLimit=take profit trigger price, + /// TrailingStop=trailing stop offset, + /// TrailingStopLimit=trailing stop offset, + /// StopLossAndLimit=stop loss price, + /// + /// Secondary price of an order: + /// StopLossProfit/StopLossProfitLimit=take profit price, + /// StopLossLimit/TakeProfitLimit=triggered limit price, + /// TrailingStopLimit=triggered limit offset, + /// StopLossAndLimit=limit price + /// Desired leverage + /// Scheduled start time + /// Expiration time + /// Only validate inputs, don't actually place the order + /// Placed order info + public WebCallResult PlaceOrder( + string market, + OrderSide side, + OrderType type, + decimal quantity, + uint? clientOrderId = null, + decimal? price = null, + decimal? secondaryPrice = null, + decimal? leverage = null, + DateTime? startTime = null, + DateTime? expireTime = null, + bool? validateOnly = null) => PlaceOrderAsync(market, side, type, quantity, clientOrderId, price, secondaryPrice, leverage, startTime, expireTime, validateOnly).Result; + /// + /// Place a new order + /// + /// The market the order is on + /// The side of the order + /// The type of the order + /// The quantity of the order + /// A client id to reference the order by + /// Price of the order: + /// Limit=limit price, + /// StopLoss=stop loss price, + /// TakeProfit=take profit price, + /// StopLossProfit=stop loss price, + /// StopLossProfitLimit=stop loss price, + /// StopLossLimit=stop loss trigger price, + /// TakeProfitLimit=take profit trigger price, + /// TrailingStop=trailing stop offset, + /// TrailingStopLimit=trailing stop offset, + /// StopLossAndLimit=stop loss price, + /// + /// Secondary price of an order: + /// StopLossProfit/StopLossProfitLimit=take profit price, + /// StopLossLimit/TakeProfitLimit=triggered limit price, + /// TrailingStopLimit=triggered limit offset, + /// StopLossAndLimit=limit price + /// Desired leverage + /// Scheduled start time + /// Expiration time + /// Only validate inputs, don't actually place the order + /// Placed order info + public async Task> PlaceOrderAsync( + string market, + OrderSide side, + OrderType type, + decimal quantity, + uint? clientOrderId = null, + decimal? price = null, + decimal? secondaryPrice = null, + decimal? leverage = null, + DateTime? startTime = null, + DateTime? expireTime = null, + bool? validateOnly = null) + { + var parameters = new Dictionary() + { + { "pair", market }, + { "type", JsonConvert.SerializeObject(side, new OrderSideConverter(false)) }, + { "ordertype", JsonConvert.SerializeObject(type, new OrderTypeConverter(false)) }, + { "volume", quantity }, + }; + parameters.AddOptionalParameter("price", price); + parameters.AddOptionalParameter("userref", clientOrderId); + parameters.AddOptionalParameter("price2", secondaryPrice); + parameters.AddOptionalParameter("leverage", leverage); + parameters.AddOptionalParameter("starttm", startTime.HasValue ? JsonConvert.SerializeObject(startTime.Value, new TimestampSecondsConverter()) : null); + parameters.AddOptionalParameter("expiretm", expireTime.HasValue ? JsonConvert.SerializeObject(expireTime.Value, new TimestampSecondsConverter()) : null); + parameters.AddOptionalParameter("validate", validateOnly); + return await Execute(GetUri("/0/private/AddOrder"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + /// + /// Cancel an order + /// + /// The id of the order to cancel + /// Cancel result + public WebCallResult CancelOrder(string orderId) => CancelOrderAsync(orderId).Result; + /// + /// Cancel an order + /// + /// The id of the order to cancel + /// Cancel result + public async Task> CancelOrderAsync(string orderId) + { + var parameters = new Dictionary() + { + {"txid", orderId} + }; + return await Execute(GetUri("/0/private/CancelOrder"), Constants.PostMethod, parameters, true).ConfigureAwait(false); + } + + #endregion + /// + protected override void WriteParamBody(IRequest request, Dictionary parameters) + { + var stringData = string.Join("&", parameters.OrderBy(p => p.Key != "nonce").Select(p => $"{p.Key}={p.Value}")); + WriteParamBody(request, stringData); + } + + private Uri GetUri(string endpoint) + { + return new Uri(BaseAddress + endpoint); + } + + private async Task> Execute(Uri url, string method = Constants.GetMethod, Dictionary parameters = null, bool signed = false) + { + var result = await ExecuteRequest>(url, method, parameters, signed).ConfigureAwait(false); + if (!result.Success) + return new WebCallResult(result.ResponseStatusCode, result.ResponseHeaders, default, result.Error); + + if (result.Data.Error.Any()) + return new WebCallResult(result.ResponseStatusCode, result.ResponseHeaders, default, new ServerError(string.Join(", ", result.Data.Error))); + + return new WebCallResult(result.ResponseStatusCode, result.ResponseHeaders, result.Data.Result, null); + } + } +} diff --git a/Kraken.Net/KrakenOptions.cs b/Kraken.Net/KrakenOptions.cs new file mode 100644 index 0000000..9dfb2e4 --- /dev/null +++ b/Kraken.Net/KrakenOptions.cs @@ -0,0 +1,54 @@ +using CryptoExchange.Net.Objects; +using Kraken.Net.Interfaces; + +namespace Kraken.Net +{ + /// + /// Options for the Kraken client + /// + public class KrakenClientOptions : RestClientOptions + { + /// + /// ctor + /// + public KrakenClientOptions() + { + BaseAddress = "https://api.kraken.com"; + } + } + + /// + /// Options for the Kraken socket client + /// + public class KrakenSocketClientOptions : SocketClientOptions + { + /// + /// ctor + /// + public KrakenSocketClientOptions() + { + BaseAddress = "wss://ws.kraken.com"; + SocketSubscriptionsCombineTarget = 10; + } + } + + /// + /// Options for the Kraken symbol order book + /// + public class KrakenOrderBookOptions : OrderBookOptions + { + /// + /// The client to use for the socket connection. When using the same client for multiple order books the connection can be shared. + /// + public IKrakenSocketClient SocketClient { get; } + + /// + /// + /// The client to use for the socket connection. When using the same client for multiple order books the connection can be shared. + public KrakenOrderBookOptions(IKrakenSocketClient client = null) : base("Kraken", false) + { + SocketClient = client; + } + } +} + diff --git a/Kraken.Net/KrakenSocketClient.cs b/Kraken.Net/KrakenSocketClient.cs new file mode 100644 index 0000000..c877bdd --- /dev/null +++ b/Kraken.Net/KrakenSocketClient.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CryptoExchange.Net; +using CryptoExchange.Net.Logging; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Sockets; +using Kraken.Net.Converters; +using Kraken.Net.Interfaces; +using Kraken.Net.Objects; +using Kraken.Net.Objects.Socket; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Kraken.Net +{ + /// + /// Client for the Kraken websocket API + /// + public class KrakenSocketClient: SocketClient, IKrakenSocketClient + { + #region fields + private static KrakenSocketClientOptions defaultOptions = new KrakenSocketClientOptions(); + private static KrakenSocketClientOptions DefaultOptions => defaultOptions.Copy(); + #endregion + + #region ctor + /// + /// Create a new instance of KrakenSocketClient using the default options + /// + public KrakenSocketClient() : this(DefaultOptions) + { + } + + /// + /// Create a new instance of KrakenSocketClient using provided options + /// + /// The options to use for this client + public KrakenSocketClient(KrakenSocketClientOptions options) : base(options, options.ApiCredentials == null ? null : new KrakenAuthenticationProvider(options.ApiCredentials)) + { + Configure(options); + + AddGenericHandler("Connection", (connection, token) => { }); + AddGenericHandler("HeartBeat", (connection, token) => { }); + } + #endregion + + #region methods + /// + /// Subscribe to ticker updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public CallResult SubscribeToTickerUpdates(string market, Action> handler) => SubscribeToTickerUpdatesAsync(market, handler).Result; + /// + /// Subscribe to ticker updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public async Task> SubscribeToTickerUpdatesAsync(string market, Action> handler) + { + return await Subscribe(new KrakenSubscribeRequest("ticker", NextId(), market), null, false, handler).ConfigureAwait(false); + } + + /// + /// Subscribe to kline updates + /// + /// Market to subscribe to + /// Kline interval + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public CallResult SubscribeToKlineUpdates(string market, KlineInterval interval, Action> handler) => SubscribeToKlineUpdatesAsync(market, interval, handler).Result; + /// + /// Subscribe to kline updates + /// + /// Market to subscribe to + /// Kline interval + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public async Task> SubscribeToKlineUpdatesAsync(string market, KlineInterval interval, Action> handler) + { + var intervalMinutes = int.Parse(JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false))); + return await Subscribe(new KrakenSubscribeRequest("ohlc", NextId(), market) { Details = new KrakenOHLCSubscriptionDetails(intervalMinutes) }, null, false, handler).ConfigureAwait(false); + } + + /// + /// Subscribe to trade updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public CallResult SubscribeToTradeUpdates(string market, Action>> handler) => SubscribeToTradeUpdatesAsync(market, handler).Result; + /// + /// Subscribe to trade updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public async Task> SubscribeToTradeUpdatesAsync(string market, Action>> handler) + { + return await Subscribe(new KrakenSubscribeRequest("trade", NextId(), market), null, false, handler).ConfigureAwait(false); + } + + /// + /// Subscribe to spread updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public CallResult SubscribeToSpreadUpdates(string market, Action> handler) => SubscribeToSpreadUpdatesAsync(market, handler).Result; + /// + /// Subscribe to spread updates + /// + /// Market to subscribe to + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public async Task> SubscribeToSpreadUpdatesAsync(string market, Action> handler) + { + return await Subscribe(new KrakenSubscribeRequest("spread", NextId(), market), null, false, handler).ConfigureAwait(false); + } + + /// + /// Subscribe to depth updates + /// + /// Market to subscribe to + /// Depth of the initial order book snapshot + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public CallResult SubscribeToDepthUpdates(string market, int depth, Action> handler) => SubscribeToDepthUpdatesAsync(market, depth, handler).Result; + + /// + /// Subscribe to depth updates + /// + /// Market to subscribe to + /// Depth of the initial order book snapshot + /// Data handler + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + public async Task> SubscribeToDepthUpdatesAsync(string market, int depth, Action> handler) + { + var innerHandler = new Action(data => + { + var token = data.ToJToken(log); + if (token == null || token.Type != JTokenType.Array) + { + log.Write(LogVerbosity.Warning, "Failed to deserialize stream order book"); + return; + } + handler(StreamOrderBookConverter.Convert((JArray) token)); + }); + + return await Subscribe(new KrakenSubscribeRequest("book", NextId(), market) { Details = new KrakenDepthSubscriptionDetails(depth)}, null, false, innerHandler).ConfigureAwait(false); + } + #endregion + + /// + protected override bool HandleQueryResponse(SocketConnection s, object request, JToken data, out CallResult callResult) + { + throw new NotImplementedException(); + } + + /// + protected override bool HandleSubscriptionResponse(SocketConnection s, SocketSubscription subscription, object request, JToken message, out CallResult callResult) + { + callResult = null; + if (message.Type != JTokenType.Object) + return false; + + if (message["reqid"] == null) + return false; + + int requestId = (int) message["reqid"]; + var kRequest = (KrakenSubscribeRequest) request; + if (requestId != kRequest.RequestId) + return false; + + var response = message.ToObject(); + kRequest.ChannelId = response.ChannelId; + callResult = new CallResult(response, response.Status == "subscribed" ? null: new ServerError(response.ErrorMessage)); + return true; + } + + /// + protected override bool MessageMatchesHandler(JToken message, object request) + { + if (message.Type != JTokenType.Array) + return false; + + var kRequest = (KrakenSubscribeRequest) request; + var arr = (JArray) message; + if (!int.TryParse(arr[0].ToString(), out var channelId)) + return false; + + return kRequest.ChannelId == channelId; + } + + /// + protected override bool MessageMatchesHandler(JToken message, string identifier) + { + if (message.Type != JTokenType.Object) + return false; + + if (identifier == "HeartBeat" && message["event"] != null && (string)message["event"] == "heartbeat") + return true; + + if (identifier == "Connection" && message["event"] != null && (string)message["event"] == "systemStatus") + return true; + + return false; + } + + /// + protected override Task> AuthenticateSocket(SocketConnection s) + { + throw new NotImplementedException(); + } + + /// + protected override async Task Unsubscribe(SocketConnection connection, SocketSubscription subscription) + { + var channelId = ((KrakenSubscribeRequest)subscription.Request).ChannelId; + var unsub = new KrakenUnsubscribeRequest(NextId(), channelId); + var result = false; + await connection.SendAndWait(unsub, ResponseTimeout, data => + { + if (data.Type != JTokenType.Object) + return false; + + if (data["reqid"] == null) + return false; + + int requestId = (int)data["reqid"]; + if (requestId != unsub.RequestId) + return false; + + var response = data.ToObject(); + result = response.Status == "unsubscribed"; + return true; + }).ConfigureAwait(false); + return result; + } + } +} diff --git a/Kraken.Net/KrakenSymbolOrderBook.cs b/Kraken.Net/KrakenSymbolOrderBook.cs new file mode 100644 index 0000000..6af5658 --- /dev/null +++ b/Kraken.Net/KrakenSymbolOrderBook.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.OrderBook; +using CryptoExchange.Net.Sockets; +using Kraken.Net.Interfaces; +using Kraken.Net.Objects; +using Kraken.Net.Objects.Socket; + +namespace Kraken.Net +{ + /// + /// Live order book implementation + /// + public class KrakenSymbolOrderBook : SymbolOrderBook + { + private readonly IKrakenSocketClient socketClient; + private bool initialSnapshotDone; + private readonly int limit; + + /// + /// Create a new order book instance + /// + /// The symbol the order book is for + /// The initial limit of entries in the order book + /// Options for the order book + public KrakenSymbolOrderBook(string market, int limit, KrakenOrderBookOptions options = null) : base(market, options ?? new KrakenOrderBookOptions()) + { + socketClient = options?.SocketClient ?? new KrakenSocketClient(); + + this.limit = limit; + } + + /// + protected override async Task> DoStart() + { + var result = await socketClient.SubscribeToDepthUpdatesAsync(Symbol, limit, ProcessUpdate).ConfigureAwait(false); + if (!result.Success) + return result; + + Status = OrderBookStatus.Syncing; + + while (!initialSnapshotDone) + await Task.Delay(10).ConfigureAwait(false); // Wait for first update to fill the order book + + return result; + } + + /// + protected override void DoReset() + { + initialSnapshotDone = false; + } + + private void ProcessUpdate(KrakenSocketEvent data) + { + if (!initialSnapshotDone) + { + SetInitialOrderBook(DateTime.UtcNow.Ticks, data.Data.Asks, data.Data.Bids); + initialSnapshotDone = true; + } + else + { + var processEntries = new List(); + foreach (var entry in data.Data.Asks) + processEntries.Add(new ProcessEntry(OrderBookEntryType.Ask, new OrderBookEntry(entry.Price, entry.Quantity))); + foreach (var entry in data.Data.Bids) + processEntries.Add(new ProcessEntry(OrderBookEntryType.Bid, new OrderBookEntry(entry.Price, entry.Quantity))); + + UpdateOrderBook(DateTime.UtcNow.Ticks, DateTime.UtcNow.Ticks, processEntries); + } + } + + /// + protected override async Task> DoResync() + { + while (!initialSnapshotDone) + await Task.Delay(10).ConfigureAwait(false); // Wait for first update to fill the order book + + return new CallResult(true, null); + } + + /// + /// Dispose + /// + public override void Dispose() + { + processBuffer.Clear(); + asks.Clear(); + bids.Clear(); + + socketClient?.Dispose(); + } + } +} diff --git a/Kraken.Net/Objects/Enums.cs b/Kraken.Net/Objects/Enums.cs new file mode 100644 index 0000000..1a535b6 --- /dev/null +++ b/Kraken.Net/Objects/Enums.cs @@ -0,0 +1,188 @@ +using Kraken.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// The time interval of kline data + /// + public enum KlineInterval + { + /// + /// 1m + /// + OneMinute, + /// + /// 5m + /// + FiveMinutes, + /// + /// 15m + /// + FifteenMinutes, + /// + /// 30m + /// + ThirtyMinutes, + /// + /// 1h + /// + OneHour, + /// + /// 4h + /// + FourHour, + /// + /// 1d + /// + OneDay, + /// + /// 1w + /// + OneWeek, + /// + /// 15d + /// + FifteenDays + } + + /// + /// Side of an order + /// + [JsonConverter(typeof(OrderSideConverter))] + public enum OrderSide + { + /// + /// Buy + /// + Buy, + /// + /// Sell + /// + Sell + } + + /// + /// Order type, limited to market or limit + /// + [JsonConverter(typeof(OrderTypeMinimalConverter))] + public enum OrderTypeMinimal + { + /// + /// Limit order + /// + Limit, + /// + /// Market order + /// + Market + } + + /// + /// Order type + /// + [JsonConverter(typeof(OrderTypeConverter))] + public enum OrderType + { + /// + /// Limit order + /// + Limit, + /// + /// Market order + /// + Market, + /// + /// Stop loss order + /// + StopLoss, + /// + /// Take profit order + /// + TakeProfit, + /// + /// Stop loss profit order + /// + StopLossProfit, + /// + /// Stop loss profit limit order + /// + StopLossProfitLimit, + /// + /// Stop loss limit order + /// + StopLossLimit, + /// + /// Take profit limit order + /// + TakeProfitLimit, + /// + /// Trailing stop order + /// + TrailingStop, + /// + /// Trailing stop limit order + /// + TrailingStopLimit, + /// + /// Stop loss and limit order + /// + StopLossAndLimit, + /// + /// Settle position + /// + SettlePosition + } + + /// + /// Status of an order + /// + [JsonConverter(typeof(OrderStatusConverter))] + public enum OrderStatus + { + /// + /// Pending + /// + Pending, + /// + /// Active, not (fully) filled + /// + Open, + /// + /// Fully filled + /// + Closed, + /// + /// Canceled + /// + Canceled, + /// + /// Expired + /// + Expired + } + + /// + /// The type of a ledger entry + /// + [JsonConverter(typeof(LedgerEntryTypeConverter))] + public enum LedgerEntryType + { + /// + /// Deposit + /// + Deposit, + /// + /// Withdrawal + /// + Withdrawal, + /// + /// Trade change + /// + Trade, + /// + /// Margin + /// + Margin + } +} diff --git a/Kraken.Net/Objects/KrakenAssetInfo.cs b/Kraken.Net/Objects/KrakenAssetInfo.cs new file mode 100644 index 0000000..d4f2ae4 --- /dev/null +++ b/Kraken.Net/Objects/KrakenAssetInfo.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Info on an asset + /// + public class KrakenAssetInfo + { + /// + /// Alternative name + /// + [JsonProperty("altname")] + public string AlternateName { get; set; } + /// + /// Class of the asset + /// + [JsonProperty("aclass")] + public string AssetClass { get; set; } + /// + /// Decimal precision of the asset + /// + public int Decimals { get; set; } + /// + /// Decimals to display + /// + [JsonProperty("display_decimals")] + public int DisplayDecimals { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenCancelResult.cs b/Kraken.Net/Objects/KrakenCancelResult.cs new file mode 100644 index 0000000..2d1c5ca --- /dev/null +++ b/Kraken.Net/Objects/KrakenCancelResult.cs @@ -0,0 +1,20 @@ +using CryptoExchange.Net.Attributes; + +namespace Kraken.Net.Objects +{ + /// + /// Result of a cancel request + /// + public class KrakenCancelResult + { + /// + /// Amount of canceled orders + /// + public int Count { get; set; } + /// + /// Pending cancellation orders + /// + [JsonOptionalProperty] + public long[] Pending { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenDepositAddress.cs b/Kraken.Net/Objects/KrakenDepositAddress.cs new file mode 100644 index 0000000..0ec9177 --- /dev/null +++ b/Kraken.Net/Objects/KrakenDepositAddress.cs @@ -0,0 +1,27 @@ +using System; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Deposit address + /// + public class KrakenDepositAddress + { + /// + /// The actual address + /// + public string Address { get; set; } + /// + /// The expire time of the address + /// + [JsonProperty("expiretm"), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime ExpireTime { get; set; } + /// + /// If the address has been used before + /// + [JsonProperty("new")] + public bool IsNew { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenDepositMethod.cs b/Kraken.Net/Objects/KrakenDepositMethod.cs new file mode 100644 index 0000000..5253351 --- /dev/null +++ b/Kraken.Net/Objects/KrakenDepositMethod.cs @@ -0,0 +1,34 @@ +using CryptoExchange.Net.Attributes; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Info about a deposit method + /// + public class KrakenDepositMethod + { + /// + /// Name of the method + /// + public string Method { get; set; } + /// + /// Deposit limit (max) of the method + /// + public string Limit { get; set; } + /// + /// The deposit fee for the method + /// + public decimal Fee { get; set; } + /// + /// The fee for setting up an address + /// + [JsonProperty("address-setup-fee"), JsonOptionalProperty] + public decimal? AddressSetupFee { get; set; } + /// + /// Generate address + /// + [JsonProperty("gen-address")] + public bool GenerateAddress { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenDepositStatus.cs b/Kraken.Net/Objects/KrakenDepositStatus.cs new file mode 100644 index 0000000..b437c7d --- /dev/null +++ b/Kraken.Net/Objects/KrakenDepositStatus.cs @@ -0,0 +1,60 @@ +using System; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Deposit status info + /// + public class KrakenDepositStatus + { + /// + /// The name of the deposit method + /// + public string Method { get; set; } + /// + /// The class of the asset + /// + [JsonProperty("aclass")] + public string AssetClass { get; set; } + /// + /// The asset name + /// + public string Asset { get; set; } + /// + /// Reference id + /// + [JsonProperty("refid")] + public string ReferenceId { get; set; } + /// + /// Transaction id + /// + [JsonProperty("txid")] + public string TransactionId { get; set; } + /// + /// Info about the transaction + /// + [JsonProperty("info")] + public string TransactionInfo { get; set; } + /// + /// The amount involved in the deposit + /// + public decimal Amount { get; set; } + /// + /// The fee paid for the deposit + /// + public decimal Fee { get; set; } + /// + /// The timestamp + /// + [JsonConverter(typeof(TimestampSecondsConverter))] + [JsonProperty("time")] + public DateTime Timestamp { get; set; } + /// + /// Status of the transaction + /// + public string Status { get; set; } + + } +} diff --git a/Kraken.Net/Objects/KrakenFeeEntry.cs b/Kraken.Net/Objects/KrakenFeeEntry.cs new file mode 100644 index 0000000..f97c73e --- /dev/null +++ b/Kraken.Net/Objects/KrakenFeeEntry.cs @@ -0,0 +1,23 @@ +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Fee level details + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenFeeEntry + { + /// + /// The minimal volume for this level + /// + [ArrayProperty(0)] + public int Volume { get; set; } + /// + /// The fee percentage for this level + /// + [ArrayProperty(1)] + public decimal FeePercentage { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenKline.cs b/Kraken.Net/Objects/KrakenKline.cs new file mode 100644 index 0000000..1430274 --- /dev/null +++ b/Kraken.Net/Objects/KrakenKline.cs @@ -0,0 +1,107 @@ +using System; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Kline data + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenKline + { + /// + /// Timestamp of the kline + /// + [ArrayProperty(0), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// The open price for this kline + /// + [ArrayProperty(1)] + public decimal Open { get; set; } + /// + /// The highest price during this kline + /// + [ArrayProperty(2)] + public decimal High { get; set; } + /// + /// The lowest price during this kline + /// + [ArrayProperty(3)] + public decimal Low { get; set; } + /// + /// The close price of this kline (or price of last trade if kline isn't closed yet) + /// + [ArrayProperty(4)] + public decimal Close { get; set; } + /// + /// The volume weighted average price + /// + [ArrayProperty(5)] + public decimal VolumeWeightedAveragePrice { get; set; } + /// + /// Volume during this kline + /// + [ArrayProperty(6)] + public decimal Volume { get; set; } + /// + /// The number of trades during this kline + /// + [ArrayProperty(7)] + public int TradeCount { get; set; } + } + + /// + /// Kline data from stream + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenStreamKline + { + /// + /// Timestamp of the kline + /// + [ArrayProperty(0), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// The end time for the kline + /// + [ArrayProperty(1), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime EndTimestamp { get; set; } + /// + /// The open price for this kline + /// + [ArrayProperty(2)] + public decimal Open { get; set; } + /// + /// The highest price during this kline + /// + [ArrayProperty(3)] + public decimal High { get; set; } + /// + /// The lowest price during this kline + /// + [ArrayProperty(4)] + public decimal Low { get; set; } + /// + /// The close price of this kline (or price of last trade if kline isn't closed yet) + /// + [ArrayProperty(5)] + public decimal Close { get; set; } + /// + /// The volume weighted average price + /// + [ArrayProperty(6)] + public decimal VolumeWeightedAveragePrice { get; set; } + /// + /// Volume during this kline + /// + [ArrayProperty(7)] + public decimal Volume { get; set; } + /// + /// The number of trades during this kline + /// + [ArrayProperty(8)] + public int TradeCount { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenLedgerEntry.cs b/Kraken.Net/Objects/KrakenLedgerEntry.cs new file mode 100644 index 0000000..e43434d --- /dev/null +++ b/Kraken.Net/Objects/KrakenLedgerEntry.cs @@ -0,0 +1,50 @@ +using System; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Ledger entry info + /// + public class KrakenLedgerEntry + { + /// + /// Reference id + /// + [JsonProperty("refid")] + public string ReferenceId { get; set; } + /// + /// Timestamp + /// + [JsonProperty("time"), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// The type of entry + /// + public LedgerEntryType Type { get; set; } + /// + /// Class of the asset + /// + [JsonProperty("aclass")] + public string AssetClass { get; set; } + /// + /// Name of the asset + /// + public string Asset { get; set; } + /// + /// The quantity of the entry + /// + [JsonProperty("amount")] + public decimal Quantity { get; set; } + /// + /// Fee paid + /// + public decimal Fee { get; set; } + /// + /// Resulting balance + /// + [JsonProperty("balance")] + public decimal BalanceAfter { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenMarket.cs b/Kraken.Net/Objects/KrakenMarket.cs new file mode 100644 index 0000000..f27c20c --- /dev/null +++ b/Kraken.Net/Objects/KrakenMarket.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Market info + /// + public class KrakenMarket + { + /// + /// Alternative name + /// + [JsonProperty("altname")] + public string AlternateName { get; set; } + /// + /// Name to use for the socket client subscriptions + /// + [JsonProperty("wsname")] + public string WebsocketName { get; set; } + /// + /// Class of the base asset + /// + [JsonProperty("aclass_base")] + public string BaseAssetClass { get; set; } + /// + /// Name of the base asset + /// + [JsonProperty("base")] + public string BaseAsset { get; set; } + /// + /// Class of the quote asset + /// + [JsonProperty("aclass_quote")] + public string QuoteAssetClass { get; set; } + /// + /// Name of the quote asset + /// + [JsonProperty("quote")] + public string QuoteAsset { get; set; } + /// + /// Let size + /// + [JsonProperty("lot")] + public string VolumeLotSize { get; set; } + /// + /// Decimals of the market + /// + [JsonProperty("pair_decimals")] + public int Decimals { get; set; } + /// + /// Lot decimals + /// + [JsonProperty("lot_decimals")] + public int LotDecimals { get; set; } + /// + /// Lot multiplier + /// + [JsonProperty("lot_multiplier")] + public decimal LotMultiplier { get; set; } + /// + /// Buy leverage amounts + /// + [JsonProperty("leverage_buy")] + public decimal[] LeverageBuy { get; set; } + /// + /// Sell leverage amounts + /// + [JsonProperty("leverage_sell")] + public decimal[] LeverageSell { get; set; } + /// + /// Fee structure + /// + public List Fees { get; set; } + /// + /// Maker fee structure + /// + [JsonProperty("fees_maker")] + public List FeesMaker { get; set; } + /// + /// The currency the fee is deducted from + /// + [JsonProperty("fee_volume_currency")] + public string FeeCurrency { get; set; } + /// + /// Margin call level + /// + [JsonProperty("margin_call")] + public int MarginCall { get; set; } + /// + /// Stop-out/liquidation margin level + /// + [JsonProperty("margin_stop")] + public int MarginStop { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenOrder.cs b/Kraken.Net/Objects/KrakenOrder.cs new file mode 100644 index 0000000..7723a86 --- /dev/null +++ b/Kraken.Net/Objects/KrakenOrder.cs @@ -0,0 +1,147 @@ +using System; +using CryptoExchange.Net.Attributes; +using CryptoExchange.Net.Converters; +using Kraken.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Order info + /// + public class KrakenOrder + { + /// + /// Reference id + /// + [JsonProperty("refid")] + public string ReferenceId { get; set; } + /// + /// Client reference id + /// + [JsonProperty("userref")] + public string ClientOrderId { get; set; } + /// + /// Status of the order + /// + public OrderStatus Status { get; set; } + /// + /// Open timestamp + /// + [JsonProperty("opentm"), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime OpenTime { get; set; } + /// + /// Start timestamp + /// + [JsonProperty("starttm"), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime StartTime { get; set; } + /// + /// Expire timestamp + /// + [JsonProperty("expiretm"), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime ExpireTime { get; set; } + /// + /// Close timestamp + /// + [JsonProperty("closedtm"), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime? ClosedTime { get; set; } + /// + /// Order details + /// + [JsonProperty("descr")] + public KrakenOrderInfo OrderDetails { get; set; } + /// + /// Quantity of the order + /// + [JsonProperty("vol")] + public decimal Quantity { get; set; } + /// + /// Filled quantity + /// + [JsonProperty("vol_exec")] + public decimal ExecutedQuantity { get; set; } + /// + /// Cost of the order + /// + public decimal Cost { get; set; } + /// + /// Fee + /// + public decimal Fee { get; set; } + /// + /// Average price of the order + /// + [JsonProperty("price")] + public decimal AveragePrice { get; set; } + /// + /// Stop price + /// + public decimal StopPrice { get; set; } + /// + /// Limit price + /// + public decimal LimitPrice { get; set; } + /// + /// Miscellaneous info + /// + public string Misc { get; set; } + /// + /// Order flags + /// + public string Oflags { get; set; } + /// + /// Reason of failure + /// + [JsonOptionalProperty] + public string Reason { get; set; } + /// + /// Trade ids + /// + [JsonProperty("trades")] + [JsonOptionalProperty] + public long[] TradeIds { get; set; } + } + + /// + /// Order details + /// + public class KrakenOrderInfo + { + /// + /// The market of the order + /// + [JsonProperty("pair")] + public string Market { get; set; } + /// + /// Side of the order + /// + [JsonProperty("type"), JsonConverter(typeof(OrderSideConverter))] + public OrderSide Side { get; set; } + /// + /// Type of the order + /// + [JsonProperty("ordertype"), JsonConverter(typeof(OrderTypeConverter))] + public OrderType Type { get; set; } + /// + /// Price of the order + /// + public decimal Price { get; set; } + /// + /// Secondary price of the order ( for details) + /// + [JsonProperty("price2")] + public decimal SecondaryPrice { get; set; } + /// + /// Amount of leverage + /// + public string Leverage { get; set; } + /// + /// Order description + /// + public string Order { get; set; } + /// + /// Conditional close order description + /// + public string Close { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenOrderBook.cs b/Kraken.Net/Objects/KrakenOrderBook.cs new file mode 100644 index 0000000..52df64a --- /dev/null +++ b/Kraken.Net/Objects/KrakenOrderBook.cs @@ -0,0 +1,99 @@ +using System; +using CryptoExchange.Net.Converters; +using CryptoExchange.Net.OrderBook; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Order book + /// + public class KrakenOrderBook + { + /// + /// Asks in the book + /// + public KrakenOrderBookEntry[] Asks { get; set; } + /// + /// Bids in the book + /// + public KrakenOrderBookEntry[] Bids { get; set; } + } + + /// + /// Order book entry + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenOrderBookEntry: ISymbolOrderBookEntry + { + /// + /// Price of the entry + /// + [ArrayProperty(0)] + public decimal Price { get; set; } + /// + /// Quantity of the entry + /// + [ArrayProperty(1)] + public decimal Quantity { get; set; } + /// + /// Timestamp of change + /// + [ArrayProperty(2), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + } + + /// + /// Stream order book + /// + public class KrakenStreamOrderBook + { + /// + /// Asks + /// + [JsonProperty("as")] + public KrakenStreamOrderBookEntry[] Asks { get; set; } + /// + /// Bids + /// + [JsonProperty("bs")] + public KrakenStreamOrderBookEntry[] Bids { get; set; } + + /// + /// ctor + /// + public KrakenStreamOrderBook() + { + Asks = new KrakenStreamOrderBookEntry[0]; + Bids = new KrakenStreamOrderBookEntry[0]; + } + } + + /// + /// Stream order book entry + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenStreamOrderBookEntry : ISymbolOrderBookEntry + { + /// + /// Price of the entry + /// + [ArrayProperty(0)] + public decimal Price { get; set; } + /// + /// Quantity of the entry + /// + [ArrayProperty(1)] + public decimal Quantity { get; set; } + /// + /// Timestamp of the entry + /// + [ArrayProperty(2), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// Type of update + /// + [ArrayProperty(3)] + public string UpdateType { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenPageData.cs b/Kraken.Net/Objects/KrakenPageData.cs new file mode 100644 index 0000000..37e021f --- /dev/null +++ b/Kraken.Net/Objects/KrakenPageData.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using CryptoExchange.Net.Attributes; + +namespace Kraken.Net.Objects +{ + /// + /// Base page data + /// + public class KrakenPageData + { + /// + /// Total number of records + /// + [JsonOptionalProperty] + public int Count { get; set; } + } + + /// + /// Open orders page + /// + public class OpenOrdersPage : KrakenPageData + { + /// + /// Open orders + /// + public Dictionary Open { get; set; } + } + + /// + /// Closed orders page + /// + public class KrakenClosedOrdersPage: KrakenPageData + { + /// + /// Closed orders + /// + public Dictionary Closed { get; set; } + } + + /// + /// User trades page + /// + public class KrakenUserTradesPage : KrakenPageData + { + /// + /// Trades + /// + public Dictionary Trades { get; set; } + } + + /// + /// Ledger page + /// + public class KrakenLedgerPage : KrakenPageData + { + /// + /// Ledger entries + /// + public Dictionary Ledger { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenPlacedOrder.cs b/Kraken.Net/Objects/KrakenPlacedOrder.cs new file mode 100644 index 0000000..840f358 --- /dev/null +++ b/Kraken.Net/Objects/KrakenPlacedOrder.cs @@ -0,0 +1,40 @@ +using CryptoExchange.Net.Attributes; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Placed order info + /// + public class KrakenPlacedOrder + { + /// + /// Order ids + /// + [JsonProperty("txid")] + public string[] OrderIds { get; set; } + /// + /// Descriptions + /// + [JsonProperty("descr")] + public KrakenPlacedOrderDescription Descriptions { get; set; } + } + + /// + /// Order descriptions + /// + public class KrakenPlacedOrderDescription + { + /// + /// Order description + /// + [JsonProperty("order")] + public string OrderDescription { get; set; } + /// + /// Close order description + /// + [JsonProperty("close")] + [JsonOptionalProperty] + public string CloseOrderDescription { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenPosition.cs b/Kraken.Net/Objects/KrakenPosition.cs new file mode 100644 index 0000000..abaf541 --- /dev/null +++ b/Kraken.Net/Objects/KrakenPosition.cs @@ -0,0 +1,77 @@ +using System; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Position info + /// + public class KrakenPosition + { + /// + /// Order id + /// + [JsonProperty("ordertxid")] + public string OrderId { get; set; } + /// + /// Market + /// + [JsonProperty("pair")] + public string Market { get; set; } + /// + /// Timestamp + /// + [JsonProperty("time"), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// Side + /// + [JsonProperty("type")] + public OrderSide Side { get; set; } + /// + /// Type + /// + [JsonProperty("ordertype")] + public OrderType Type { get; set; } + /// + /// Cost + /// + public decimal Cost { get; set; } + /// + /// Fee + /// + public decimal Fee { get; set; } + /// + /// Quantity + /// + [JsonProperty("vol")] + public decimal Quantity { get; set; } + /// + /// Closed quantity + /// + [JsonProperty("vol_closed")] + public decimal QuantityClosed { get; set; } + /// + /// Margin + /// + public decimal Margin { get; set; } + /// + /// Value + /// + public decimal? Value { get; set; } + /// + /// Net profit/loss + /// + [JsonProperty("net")] + public decimal? ProfitLoss { get; set; } + /// + /// Misc info + /// + public string Misc { get; set; } + /// + /// Flags + /// + public string OFlags { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenResult.cs b/Kraken.Net/Objects/KrakenResult.cs new file mode 100644 index 0000000..20a8bca --- /dev/null +++ b/Kraken.Net/Objects/KrakenResult.cs @@ -0,0 +1,8 @@ +namespace Kraken.Net.Objects +{ + internal class KrakenResult + { + public string[] Error { get; set; } + public T Result { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenServerTime.cs b/Kraken.Net/Objects/KrakenServerTime.cs new file mode 100644 index 0000000..37bbcf1 --- /dev/null +++ b/Kraken.Net/Objects/KrakenServerTime.cs @@ -0,0 +1,14 @@ +using System; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + internal class KrakenServerTime + { + [JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime UnixTime { get; set; } + [JsonProperty("rfc1123")] + public string RfcTime { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenSpread.cs b/Kraken.Net/Objects/KrakenSpread.cs new file mode 100644 index 0000000..fd3a934 --- /dev/null +++ b/Kraken.Net/Objects/KrakenSpread.cs @@ -0,0 +1,63 @@ +using System; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Spread info + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenSpread + { + /// + /// Timestamp of the data + /// + [ArrayProperty(0), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// Best bid price + /// + [ArrayProperty(1)] + public decimal Bid { get; set; } + /// + /// Best ask price + /// + [ArrayProperty(2)] + public decimal Ask { get; set; } + } + + /// + /// Stream spread data + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenStreamSpread + { + /// + /// Best bid price + /// + [ArrayProperty(0)] + public decimal Bid { get; set; } + /// + /// Best ask price + /// + [ArrayProperty(1)] + public decimal Ask { get; set; } + /// + /// Timestamp of the data + /// + [ArrayProperty(2), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// Best bid volume + /// + [ArrayProperty(3)] + public decimal BidVolume { get; set; } + /// + /// Best ask volume + /// + [ArrayProperty(4)] + public decimal AskVolume { get; set; } + + } +} diff --git a/Kraken.Net/Objects/KrakenTick.cs b/Kraken.Net/Objects/KrakenTick.cs new file mode 100644 index 0000000..159019a --- /dev/null +++ b/Kraken.Net/Objects/KrakenTick.cs @@ -0,0 +1,136 @@ +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Tick info + /// + public class KrakenTick + { + /// + /// High price info + /// + [JsonProperty("h")] + public KrakenTickInfo High { get; set; } + /// + /// Low price info + /// + [JsonProperty("l")] + public KrakenTickInfo Low { get; set; } + /// + /// Last trade info + /// + [JsonProperty("c")] + public KrakenLastTrade LastTrade { get; set; } + /// + /// Best ask info + /// + [JsonProperty("a")] + public KrakenBestEntry BestAsks { get; set; } + /// + /// Best bid info + /// + [JsonProperty("b")] + public KrakenBestEntry BestBids { get; set; } + /// + /// Trade count info + /// + [JsonProperty("t")] + public KrakenTickInfo Trades { get; set; } + /// + /// Volume weighted average price info + /// + [JsonProperty("p")] + public KrakenTickInfo VolumeWeightedAveragePrice { get; set; } + /// + /// Volume info + /// + [JsonProperty("v")] + public KrakenTickInfo Volume { get; set; } + } + + /// + /// Tick info + /// + public class KrakenRestTick: KrakenTick + { + /// + /// Open price + /// + [JsonProperty("o")] + public decimal Open { get; set; } + } + + /// + /// Tick info + /// + public class KrakenStreamTick : KrakenTick + { + /// + /// Open price info + /// + [JsonProperty("o")] + public KrakenTickInfo Open { get; set; } + + } + + /// + /// Tick detail info + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenTickInfo + { + /// + /// Value for today + /// + [ArrayProperty(0)] + public decimal ValueToday { get; set; } + /// + /// Rolling 24h window value + /// + [ArrayProperty(1)] + public decimal Value24H { get; set; } + } + + /// + /// Last trade details + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenLastTrade + { + /// + /// Price of last trade + /// + [ArrayProperty(0)] + public decimal Price { get; set; } + /// + /// Quantity of last trade + /// + [ArrayProperty(1)] + public decimal Quantity { get; set; } + } + + /// + /// Best entry info + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenBestEntry + { + /// + /// Price of best entry + /// + [ArrayProperty(0)] + public decimal Price { get; set; } + /// + /// Lot quantity + /// + [ArrayProperty(1)] + public decimal LotQuantity { get; set; } + /// + /// Quantity + /// + [ArrayProperty(2)] + public decimal Quantity { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenTrade.cs b/Kraken.Net/Objects/KrakenTrade.cs new file mode 100644 index 0000000..585224d --- /dev/null +++ b/Kraken.Net/Objects/KrakenTrade.cs @@ -0,0 +1,45 @@ +using System; +using CryptoExchange.Net.Converters; +using Kraken.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Trade info + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenTrade + { + /// + /// Price of the trade + /// + [ArrayProperty(0)] + public decimal Price { get; set; } + /// + /// Quantity of the trade + /// + [ArrayProperty(1)] + public decimal Quantity { get; set; } + /// + /// Timestamp of trade + /// + [ArrayProperty(2), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// Side + /// + [ArrayProperty(3), JsonConverter(typeof(OrderSideConverter))] + public OrderSide Side { get; set; } + /// + /// Order type + /// + [ArrayProperty(4), JsonConverter(typeof(OrderTypeMinimalConverter))] + public OrderTypeMinimal Type { get; set; } + /// + /// Misc info + /// + [ArrayProperty(5)] + public string Misc { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenTradeBalance.cs b/Kraken.Net/Objects/KrakenTradeBalance.cs new file mode 100644 index 0000000..a111835 --- /dev/null +++ b/Kraken.Net/Objects/KrakenTradeBalance.cs @@ -0,0 +1,56 @@ +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Trade balance info + /// + public class KrakenTradeBalance + { + /// + /// Combined balance + /// + [JsonProperty("eb")] + public decimal CombinedBalance { get; set; } + /// + /// Trade balance + /// + [JsonProperty("tb")] + public decimal TradeBalance { get; set; } + /// + /// Margin open positions + /// + [JsonProperty("m")] + public decimal MarginOpenPositions { get; set; } + /// + /// Unrealized net profit in open positions + /// + [JsonProperty("n")] + public decimal OpenPositionsUnrealizedNetProfit { get; set; } + /// + /// Cost basis for open positions + /// + [JsonProperty("c")] + public decimal OpenPositionsCostBasis { get; set; } + /// + /// Open positions valuation + /// + [JsonProperty("v")] + public decimal OpenPositionsValuation { get; set; } + /// + /// Equity + /// + [JsonProperty("e")] + public decimal Equity { get; set; } + /// + /// Free margin + /// + [JsonProperty("mf")] + public decimal FreeMargin { get; set; } + /// + /// Margin level + /// + [JsonProperty("ml")] + public decimal MarginLevel { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenTradeVolume.cs b/Kraken.Net/Objects/KrakenTradeVolume.cs new file mode 100644 index 0000000..83ba0a3 --- /dev/null +++ b/Kraken.Net/Objects/KrakenTradeVolume.cs @@ -0,0 +1,61 @@ +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// Trade volume info + /// + public class KrakenTradeVolume + { + /// + /// Currency + /// + public string Currency { get; set; } + /// + /// Volume + /// + public decimal Volume { get; set; } + /// + /// Fees structure + /// + public KrakenFeeStruct[] Fees { get; set; } + /// + /// Maker fees structure + /// + [JsonProperty("fees_maker")] + public KrakenFeeStruct[] MakerFees { get; set; } + } + + /// + /// Fee level info + /// + public class KrakenFeeStruct + { + /// + /// Fee + /// + public decimal Fee { get; set; } + /// + /// Minimal fee + /// + [JsonProperty("minfee")] + public decimal MinimalFee { get; set; } + /// + /// Maximal fee + /// + [JsonProperty("maxfee")] + public decimal MaximumFee { get; set; } + /// + /// Next fee + /// + public decimal NextFee { get; set; } + /// + /// Next volume + /// + public decimal NextVolume { get; set; } + /// + /// Tier volume + /// + public decimal TierVolume { get; set; } + } +} diff --git a/Kraken.Net/Objects/KrakenUserTrade.cs b/Kraken.Net/Objects/KrakenUserTrade.cs new file mode 100644 index 0000000..6c67b4c --- /dev/null +++ b/Kraken.Net/Objects/KrakenUserTrade.cs @@ -0,0 +1,103 @@ +using System; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects +{ + /// + /// User trade info + /// + public class KrakenUserTrade + { + /// + /// Order id + /// + [JsonProperty("ordertxid")] + public string OrderId { get; set; } + /// + /// Market + /// + [JsonProperty("pair")] + public string Market { get; set; } + /// + /// Timestamp of trade + /// + [JsonProperty("time"), JsonConverter(typeof(TimestampSecondsConverter))] + public DateTime Timestamp { get; set; } + /// + /// Side + /// + [JsonProperty("type")] + public OrderSide Side { get; set; } + /// + /// Order type + /// + [JsonProperty("ordertype")] + public OrderType Type { get; set; } + /// + /// Price of the trade + /// + public decimal Price { get; set; } + /// + /// Cost of the trade + /// + public decimal Cost { get; set; } + /// + /// Fee paid for trade + /// + public decimal Fee { get; set; } + /// + /// Quantity of the trade + /// + [JsonProperty("vol")] + public decimal Quantity { get; set; } + /// + /// Margin + /// + public decimal Margin { get; set; } + /// + /// Misc info + /// + public string Misc { get; set; } + + /// + /// Position status + /// + [JsonProperty("posstatus")] + public string PositionStatus { get; set; } + /// + /// Closed average price + /// + [JsonProperty("cprice")] + public decimal? ClosedAveragePrice { get; set; } + /// + /// Closed cost + /// + [JsonProperty("ccost")] + public decimal? ClosedCost { get; set; } + /// + /// Closed fee + /// + [JsonProperty("cfee")] + public decimal? ClosedFee { get; set; } + /// + /// Closed quantity + /// + [JsonProperty("cvol")] + public decimal? ClosedQuantity { get; set; } + /// + /// Closed margin + /// + [JsonProperty("cmargin")] + public decimal? ClosedMargin { get; set; } + /// + /// Closed net profit/loss + /// + [JsonProperty("net")] + public decimal? ClosedProfitLoss { get; set; } + /// + /// Trade ids + /// + public string[] Trades { get; set; } + } +} diff --git a/Kraken.Net/Objects/Socket/KrakenSubscribeRequest.cs b/Kraken.Net/Objects/Socket/KrakenSubscribeRequest.cs new file mode 100644 index 0000000..537cd7e --- /dev/null +++ b/Kraken.Net/Objects/Socket/KrakenSubscribeRequest.cs @@ -0,0 +1,59 @@ +using Newtonsoft.Json; + +namespace Kraken.Net.Objects.Socket +{ + internal class KrakenSubscribeRequest + { + [JsonProperty("event")] + public string Event { get; set; } = "subscribe"; + [JsonProperty("reqid")] + public int RequestId { get; set; } + [JsonProperty("pair")] + public string[] Markets { get; set; } + [JsonProperty("subscription")] + public KrakenSubscriptionDetails Details { get; set; } + + [JsonIgnore] + public int ChannelId { get; set; } + + public KrakenSubscribeRequest(string topic, int requestId, params string[] markets) + { + RequestId = requestId; + Markets = markets; + Details = new KrakenSubscriptionDetails(topic); + } + } + + internal class KrakenSubscriptionDetails + { + [JsonProperty("name")] + public string Topic { get; set; } + + public KrakenSubscriptionDetails(string topic) + { + Topic = topic; + } + } + + internal class KrakenOHLCSubscriptionDetails: KrakenSubscriptionDetails + { + [JsonProperty("interval")] + public int Interval { get; set; } + + public KrakenOHLCSubscriptionDetails(int interval) : base("ohlc") + { + Interval = interval; + } + } + + internal class KrakenDepthSubscriptionDetails : KrakenSubscriptionDetails + { + [JsonProperty("depth")] + public int Depth { get; set; } + + public KrakenDepthSubscriptionDetails(int depth) : base("book") + { + Depth = depth; + } + } +} diff --git a/Kraken.Net/Objects/Socket/KrakenSubscriptionEvent.cs b/Kraken.Net/Objects/Socket/KrakenSubscriptionEvent.cs new file mode 100644 index 0000000..424b978 --- /dev/null +++ b/Kraken.Net/Objects/Socket/KrakenSubscriptionEvent.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Kraken.Net.Objects.Socket +{ + internal class KrakenSubscriptionEvent + { + public int ChannelId { get; set; } + public string ChannelName { get; set; } + public string Event { get; set; } + public string Pair { get; set; } + public string Status { get; set; } + [JsonProperty("reqid")] + public string RequestId { get; set; } + public KrakenSubscriptionDetails Subscription { get; set; } + public string ErrorMessage { get; set; } + } +} diff --git a/Kraken.Net/Objects/Socket/KrakenUnsubscribeRequest.cs b/Kraken.Net/Objects/Socket/KrakenUnsubscribeRequest.cs new file mode 100644 index 0000000..096de55 --- /dev/null +++ b/Kraken.Net/Objects/Socket/KrakenUnsubscribeRequest.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Kraken.Net.Objects.Socket +{ + internal class KrakenUnsubscribeRequest + { + [JsonProperty("event")] + public string Event { get; set; } = "unsubscribe"; + [JsonProperty("reqid")] + public int RequestId { get; set; } + [JsonProperty("channelID")] + public int ChannelId { get; set; } + + public KrakenUnsubscribeRequest(int requestId, int channelId) + { + RequestId = requestId; + ChannelId = channelId; + } + } +} diff --git a/Kraken.Net/Objects/Socket/SocketEvent.cs b/Kraken.Net/Objects/Socket/SocketEvent.cs new file mode 100644 index 0000000..f2960b7 --- /dev/null +++ b/Kraken.Net/Objects/Socket/SocketEvent.cs @@ -0,0 +1,35 @@ +using CryptoExchange.Net.Attributes; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Kraken.Net.Objects.Socket +{ + /// + /// Event received from the socket + /// + /// + [JsonConverter(typeof(ArrayConverter))] + public class KrakenSocketEvent + { + /// + /// Id of the channel + /// + [ArrayProperty(0)] + public int ChannelId { get; set; } + /// + /// The data + /// + [ArrayProperty(1), JsonConversion] + public T Data { get; set; } + /// + /// The topic of the data + /// + [ArrayProperty(2)] + public string Topic { get; set; } + /// + /// The market the data is for + /// + [ArrayProperty(3)] + public string Market { get; set; } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f093fca --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Jan Korf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..07afe63 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# ![Icon](https://github.com/JKorf/Kraken.Net/blob/master/Resources/icon.png?raw=true) Kraken.Net + +![Build status](https://travis-ci.org/JKorf/Kraken.Net.svg?branch=master) + +A .Net wrapper for the Kraken API as described on [Kraken](https://www.kraken.com/features/api), including all features the API provides using clear and readable objects. + +**If you think something is broken, something is missing or have any questions, please open an [Issue](https://github.com/JKorf/Kraken.Net/issues)** + +## CryptoExchange.Net +Implementation is build upon the CryptoExchange.Net library, make sure to also check out the documentation on that: [docs](https://github.com/JKorf/CryptoExchange.Net) + +Other CryptoExchange.Net implementations: + + + + + + + + +
+
+Bittrex +
+
+Bitfinex +
+
+Binance +
+
+CoinEx +
+
+Huobi +
+
+Kucoin +
+ +Implementations from third parties: + + + + + +
+
+Switcheo +
+
+Liquid +
+ + +## Donations +Donations are greatly appreciated and a motivation to keep improving. + +**Btc**: 12KwZk3r2Y3JZ2uMULcjqqBvXmpDwjhhQS +**Eth**: 0x069176ca1a4b1d6e0b7901a6bc0dbf3bb0bf5cc2 +**Nano**: xrb_1ocs3hbp561ef76eoctjwg85w5ugr8wgimkj8mfhoyqbx4s1pbc74zggw7gs + + +## Installation +![Nuget version](https://img.shields.io/nuget/v/Kraken.net.svg) ![Nuget downloads](https://img.shields.io/nuget/dt/Kraken.Net.svg) +Available on [Nuget](https://www.nuget.org/packages/Kraken.Net/). +``` +pm> Install-Package Kraken.Net +``` +To get started with Kraken.Net first you will need to get the library itself. The easiest way to do this is to install the package into your project using [NuGet](https://www.nuget.org/packages/Kraken.Net/). Using Visual Studio this can be done in two ways. + +### Using the package manager +In Visual Studio right click on your solution and select 'Manage NuGet Packages for solution...'. A screen will appear which initially shows the currently installed packages. In the top bit select 'Browse'. This will let you download net package from the NuGet server. In the search box type'Kraken.Net' and hit enter. The Kraken.Net package should come up in the results. After selecting the package you can then on the right hand side select in which projects in your solution the package should install. After you've selected all project you wish to install and use Kraken.Net in hit 'Install' and the package will be downloaded and added to you projects. + +### Using the package manager console +In Visual Studio in the top menu select 'Tools' -> 'NuGet Package Manager' -> 'Package Manager Console'. This should open up a command line interface. On top of the interface there is a dropdown menu where you can select the Default Project. This is the project that Kraken.Net will be installed in. After selecting the correct project type `Install-Package Kraken.Net` in the command line interface. This should install the latest version of the package in your project. + +After doing either of above steps you should now be ready to actually start using Kraken.Net. +## Getting started +After installing it's time to actually use it. To get started you have to add the Kraken.Net namespace: `using Kraken.Net;`. + +Kraken.Net provides two clients to interact with the Kraken API. The `KrakenClient` provides all rest API calls. The `KrakenSocketClient` provides functions to interact with the websocket provided by the Kraken API. Both clients are disposable and as such can be used in a `using` statement. + +## Release notes +* Version 0.0.1 - 29 Aug 2019 + * Initial release diff --git a/Resources/icon.png b/Resources/icon.png new file mode 100644 index 0000000..5a19594 Binary files /dev/null and b/Resources/icon.png differ