diff --git a/CryptoExchange.Net.UnitTests/ConverterTests.cs b/CryptoExchange.Net.UnitTests/ConverterTests.cs
index b2022c8..2820b97 100644
--- a/CryptoExchange.Net.UnitTests/ConverterTests.cs
+++ b/CryptoExchange.Net.UnitTests/ConverterTests.cs
@@ -1,4 +1,5 @@
-using CryptoExchange.Net.Converters;
+using CryptoExchange.Net.Attributes;
+using CryptoExchange.Net.Converters;
 using Newtonsoft.Json;
 using NUnit.Framework;
 using System;
@@ -51,6 +52,44 @@ namespace CryptoExchange.Net.UnitTests
             var output = JsonConvert.DeserializeObject<TimeObject>($"{{ \"time\": null }}");
             Assert.AreEqual(output.Time, null);
         }
+
+        // TODO add tests for ToMilliseconds static methods
+
+        [TestCase(TestEnum.One, "1")]
+        [TestCase(TestEnum.Two, "2")]
+        [TestCase(TestEnum.Three, "three")]
+        [TestCase(TestEnum.Four, "Four")]
+        [TestCase(null, null)]
+        public void TestEnumConverterNullableGetStringTests(TestEnum? value, string expected)
+        {
+            var output = EnumConverter.GetString(value);
+            Assert.AreEqual(output, expected);
+        }
+
+        [TestCase(TestEnum.One, "1")]
+        [TestCase(TestEnum.Two, "2")]
+        [TestCase(TestEnum.Three, "three")]
+        [TestCase(TestEnum.Four, "Four")]
+        public void TestEnumConverterGetStringTests(TestEnum value, string expected)
+        {
+            var output = EnumConverter.GetString(value);
+            Assert.AreEqual(output, expected);
+        }
+
+        [TestCase("1", TestEnum.One)]
+        [TestCase("2", TestEnum.Two)]
+        [TestCase("3", TestEnum.Three)]
+        [TestCase("three", TestEnum.Three)]
+        [TestCase("Four", TestEnum.Four)]
+        [TestCase("four", TestEnum.Four)]
+        [TestCase("Four1", null)]
+        [TestCase(null, null)]
+        public void TestEnumConverterNullableDeserializeTests(string? value, TestEnum? expected)
+        {
+            var val = value == null ? "null" : $"\"{value}\"";
+            var output = JsonConvert.DeserializeObject<EnumObject?>($"{{ \"Value\": {val} }}");
+            Assert.AreEqual(output.Value, expected);
+        }
     }
 
     public class TimeObject
@@ -58,4 +97,21 @@ namespace CryptoExchange.Net.UnitTests
         [JsonConverter(typeof(DateTimeConverter))]
         public DateTime? Time { get; set; }
     }
+
+    public class EnumObject
+    {
+        public TestEnum? Value { get; set; }
+    }
+
+    [JsonConverter(typeof(EnumConverter))]
+    public enum TestEnum
+    {
+        [Map("1")]
+        One,
+        [Map("2")]
+        Two,
+        [Map("three", "3")]
+        Three,
+        Four
+    }
 }
diff --git a/CryptoExchange.Net/Attributes/MapAttribute.cs b/CryptoExchange.Net/Attributes/MapAttribute.cs
new file mode 100644
index 0000000..27f553a
--- /dev/null
+++ b/CryptoExchange.Net/Attributes/MapAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CryptoExchange.Net.Attributes
+{
+    public class MapAttribute : Attribute
+    {
+        public string[] Values { get; set; }
+        public MapAttribute(params string[] maps)
+        {
+            Values = maps;
+        }
+    }
+}
diff --git a/CryptoExchange.Net/Converters/EnumConverter.cs b/CryptoExchange.Net/Converters/EnumConverter.cs
new file mode 100644
index 0000000..9a90c55
--- /dev/null
+++ b/CryptoExchange.Net/Converters/EnumConverter.cs
@@ -0,0 +1,101 @@
+using CryptoExchange.Net.Attributes;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace CryptoExchange.Net.Converters
+{
+    public class EnumConverter : JsonConverter
+    {
+        private static ConcurrentDictionary<Type, List<KeyValuePair<object, string>>> _mapping = new ConcurrentDictionary<Type, List<KeyValuePair<object, string>>>();
+
+        public override bool CanConvert(Type objectType)
+        {
+            return objectType.IsEnum;
+        }
+
+        public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+        {
+            objectType = Nullable.GetUnderlyingType(objectType) ?? objectType;
+            if (!_mapping.TryGetValue(objectType, out var mapping))
+                mapping = AddMapping(objectType);
+
+            if (reader.Value == null)
+                return null;
+
+            var stringValue = reader.Value.ToString();
+            if (string.IsNullOrWhiteSpace(stringValue))
+                return null;
+
+            if (!GetValue(objectType, mapping, stringValue, out var result))
+            {
+                Debug.WriteLine($"Cannot map enum. Type: {objectType.Name}, Value: {reader.Value}");
+                return null;
+            }
+
+            return result;
+        }
+
+        private static List<KeyValuePair<object, string>> AddMapping(Type objectType) 
+        {
+            var mapping = new List<KeyValuePair<object, string>>();
+            var enumMembers = objectType.GetMembers();
+            foreach (var member in enumMembers)
+            {
+                var maps = member.GetCustomAttributes(typeof(MapAttribute), false);
+                foreach (MapAttribute attribute in maps)
+                {
+                    foreach (var value in attribute.Values)
+                        mapping.Add(new KeyValuePair<object, string>(Enum.Parse(objectType, member.Name), value));
+                }
+            }
+            _mapping.TryAdd(objectType, mapping);
+            return mapping;
+        }
+
+        private bool GetValue(Type objectType, List<KeyValuePair<object, string>> enumMapping, string value, out object? result)
+        {
+            // Check for exact match first, then if not found fallback to a case insensitive match 
+            var mapping = enumMapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
+            if (mapping.Equals(default(KeyValuePair<object, string>)))
+                mapping = enumMapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
+
+            if (!mapping.Equals(default(KeyValuePair<object, string>)))
+            {
+                result = mapping.Key;
+                return true;
+            }
+
+            try
+            {
+                result = Enum.Parse(objectType, value, true);
+                return true;
+            }
+            catch (Exception)
+            {
+                result = default;
+                return false;
+            }
+        }
+
+        public static string? GetString<T>(T enumValue)
+        {
+            var objectType = typeof(T);
+            objectType = Nullable.GetUnderlyingType(objectType) ?? objectType;
+
+            if (!_mapping.TryGetValue(objectType, out var mapping))
+                mapping = AddMapping(objectType);
+
+            return enumValue == null ? null : (mapping.FirstOrDefault(v => v.Key.Equals(enumValue)).Value ?? enumValue.ToString());            
+        }
+
+        public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+        {
+            var stringValue = GetString(value);
+            writer.WriteRawValue(stringValue);
+        }
+    }
+}