diff --git a/CryptoExchange.Net.UnitTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/RestClientTests.cs
index 6152424..aa480d6 100644
--- a/CryptoExchange.Net.UnitTests/RestClientTests.cs
+++ b/CryptoExchange.Net.UnitTests/RestClientTests.cs
@@ -116,7 +116,7 @@ namespace CryptoExchange.Net.UnitTests
 
             // assert
             Assert.IsTrue(client.ClientOptions.BaseAddress == "http://test.address.com/");
-            Assert.IsTrue(client.ClientOptions.RateLimiters.Count() == 1);
+            Assert.IsTrue(client.ClientOptions.RateLimiters.Count == 1);
             Assert.IsTrue(client.ClientOptions.RateLimitingBehaviour == RateLimitingBehaviour.Fail);
             Assert.IsTrue(client.ClientOptions.RequestTimeout == TimeSpan.FromMinutes(1));
         }
diff --git a/CryptoExchange.Net/Attributes/JsonConversionAttribute.cs b/CryptoExchange.Net/Attributes/JsonConversionAttribute.cs
index 67c9021..e3fdb29 100644
--- a/CryptoExchange.Net/Attributes/JsonConversionAttribute.cs
+++ b/CryptoExchange.Net/Attributes/JsonConversionAttribute.cs
@@ -5,6 +5,7 @@ namespace CryptoExchange.Net.Attributes
     /// <summary>
     /// Used for conversion in ArrayConverter
     /// </summary>
+    [AttributeUsage(AttributeTargets.Property)]
     public class JsonConversionAttribute: Attribute
     {
     }
diff --git a/CryptoExchange.Net/Attributes/JsonOptionalPropertyAttribute.cs b/CryptoExchange.Net/Attributes/JsonOptionalPropertyAttribute.cs
deleted file mode 100644
index c50ff7b..0000000
--- a/CryptoExchange.Net/Attributes/JsonOptionalPropertyAttribute.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace CryptoExchange.Net.Attributes
-{
-    /// <summary>
-    /// Marks property as optional
-    /// </summary>
-    public class JsonOptionalPropertyAttribute : Attribute
-    {
-    }
-}
diff --git a/CryptoExchange.Net/Authentication/ApiCredentials.cs b/CryptoExchange.Net/Authentication/ApiCredentials.cs
index 60c6aa6..18355c0 100644
--- a/CryptoExchange.Net/Authentication/ApiCredentials.cs
+++ b/CryptoExchange.Net/Authentication/ApiCredentials.cs
@@ -7,7 +7,7 @@ using Newtonsoft.Json.Linq;
 namespace CryptoExchange.Net.Authentication
 {
     /// <summary>
-    /// Api credentials info
+    /// Api credentials, used to sign requests accessing private endpoints
     /// </summary>
     public class ApiCredentials: IDisposable
     {
diff --git a/CryptoExchange.Net/BaseClient.cs b/CryptoExchange.Net/BaseClient.cs
index aa4a652..3af741d 100644
--- a/CryptoExchange.Net/BaseClient.cs
+++ b/CryptoExchange.Net/BaseClient.cs
@@ -30,7 +30,7 @@ namespace CryptoExchange.Net
         /// </summary>
         protected internal Log log;
         /// <summary>
-        /// The authentication provider
+        /// The authentication provider when api credentials have been provided
         /// </summary>
         protected internal AuthenticationProvider? authProvider;
         /// <summary>
@@ -72,7 +72,6 @@ namespace CryptoExchange.Net
             ClientOptions = options;
 
             ExchangeName = exchangeName;
-            //BaseAddress = options.BaseAddress;
 
             log.Write(LogLevel.Debug, $"Client configuration: {options}, CryptoExchange.Net: v{typeof(BaseClient).Assembly.GetName().Version}, {ExchangeName}.Net: v{GetType().Assembly.GetName().Version}");
         }
@@ -88,7 +87,7 @@ namespace CryptoExchange.Net
         }
 
         /// <summary>
-        /// Tries to parse the json data and returns a JToken, validating the input not being empty and being valid json
+        /// Tries to parse the json data and return a JToken, validating the input not being empty and being valid json
         /// </summary>
         /// <param name="data">The data to parse</param>
         /// <returns></returns>
diff --git a/CryptoExchange.Net/Converters/ArrayConverter.cs b/CryptoExchange.Net/Converters/ArrayConverter.cs
index f46cfb3..b196fd0 100644
--- a/CryptoExchange.Net/Converters/ArrayConverter.cs
+++ b/CryptoExchange.Net/Converters/ArrayConverter.cs
@@ -181,6 +181,7 @@ namespace CryptoExchange.Net.Converters
     /// <summary>
     /// Mark property as an index in the array
     /// </summary>
+    [AttributeUsage(AttributeTargets.Property)]
     public class ArrayPropertyAttribute: Attribute
     {
         /// <summary>
diff --git a/CryptoExchange.Net/Converters/BaseConverter.cs b/CryptoExchange.Net/Converters/BaseConverter.cs
index 75d5948..a507a18 100644
--- a/CryptoExchange.Net/Converters/BaseConverter.cs
+++ b/CryptoExchange.Net/Converters/BaseConverter.cs
@@ -75,7 +75,7 @@ namespace CryptoExchange.Net.Converters
 
         private bool GetValue(string value, out T result)
         {
-            //check for exact match first, then if not found fallback to a case insensitive match 
+            // Check for exact match first, then if not found fallback to a case insensitive match 
             var mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
             if(mapping.Equals(default(KeyValuePair<T, string>)))
                 mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
diff --git a/CryptoExchange.Net/Converters/TimestampConverter.cs b/CryptoExchange.Net/Converters/TimestampConverter.cs
index 7dd9c21..b070771 100644
--- a/CryptoExchange.Net/Converters/TimestampConverter.cs
+++ b/CryptoExchange.Net/Converters/TimestampConverter.cs
@@ -4,7 +4,7 @@ using Newtonsoft.Json;
 namespace CryptoExchange.Net.Converters
 {
     /// <summary>
-    /// converter for milliseconds to datetime
+    /// Converter for milliseconds to datetime
     /// </summary>
     public class TimestampConverter : JsonConverter
     {
diff --git a/CryptoExchange.Net/Interfaces/IRestClient.cs b/CryptoExchange.Net/Interfaces/IRestClient.cs
index 3778958..3c150ed 100644
--- a/CryptoExchange.Net/Interfaces/IRestClient.cs
+++ b/CryptoExchange.Net/Interfaces/IRestClient.cs
@@ -18,7 +18,7 @@ namespace CryptoExchange.Net.Interfaces
         IRequestFactory RequestFactory { get; set; }
 
         /// <summary>
-        /// The total amount of requests made
+        /// The total amount of requests made with this client
         /// </summary>
         int TotalRequestsMade { get; }
 
@@ -34,7 +34,7 @@ namespace CryptoExchange.Net.Interfaces
         void RemoveRateLimiters();
 
         /// <summary>
-        /// Client options
+        /// The options provided for this client
         /// </summary>
         RestClientOptions ClientOptions { get; }
     }
diff --git a/CryptoExchange.Net/Interfaces/ISocketClient.cs b/CryptoExchange.Net/Interfaces/ISocketClient.cs
index 12964de..b18c835 100644
--- a/CryptoExchange.Net/Interfaces/ISocketClient.cs
+++ b/CryptoExchange.Net/Interfaces/ISocketClient.cs
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.Interfaces
     public interface ISocketClient: IDisposable
     {
         /// <summary>
-        /// Client options
+        /// The options provided for this client
         /// </summary>
         SocketClientOptions ClientOptions { get; }
 
@@ -20,6 +20,13 @@ namespace CryptoExchange.Net.Interfaces
         /// </summary>
         public double IncomingKbps { get; }
 
+        /// <summary>
+        /// Unsubscribe from a stream using the subscription id received when starting the subscription
+        /// </summary>
+        /// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
+        /// <returns></returns>
+        Task UnsubscribeAsync(int subscriptionId);
+
         /// <summary>
         /// Unsubscribe from a stream
         /// </summary>
diff --git a/CryptoExchange.Net/Interfaces/IWebsocket.cs b/CryptoExchange.Net/Interfaces/IWebsocket.cs
index 9e05ac6..af84536 100644
--- a/CryptoExchange.Net/Interfaces/IWebsocket.cs
+++ b/CryptoExchange.Net/Interfaces/IWebsocket.cs
@@ -7,41 +7,41 @@ using System.Threading.Tasks;
 namespace CryptoExchange.Net.Interfaces
 {
     /// <summary>
-    /// Interface for websocket interaction
+    /// Webscoket connection interface
     /// </summary>
     public interface IWebsocket: IDisposable
     {
         /// <summary>
-        /// Websocket closed
+        /// Websocket closed event
         /// </summary>
         event Action OnClose;
         /// <summary>
-        /// Websocket message received
+        /// Websocket message received event
         /// </summary>
         event Action<string> OnMessage;
         /// <summary>
-        /// Websocket error
+        /// Websocket error event
         /// </summary>
         event Action<Exception> OnError;
         /// <summary>
-        /// Websocket opened
+        /// Websocket opened event
         /// </summary>
         event Action OnOpen;
 
         /// <summary>
-        /// Id
+        /// Unique id for this socket
         /// </summary>
         int Id { get; }
         /// <summary>
-        /// Origin
+        /// Origin header
         /// </summary>
         string? Origin { get; set; }
         /// <summary>
-        /// Encoding to use
+        /// Encoding to use for sending/receiving string data
         /// </summary>
         Encoding? Encoding { get; set; }
         /// <summary>
-        /// Reconnecting
+        /// Whether socket is in the process of reconnecting
         /// </summary>
         bool Reconnecting { get; set; }
         /// <summary>
@@ -61,15 +61,15 @@ namespace CryptoExchange.Net.Interfaces
         /// </summary>
         Func<string, string>? DataInterpreterString { get; set; }
         /// <summary>
-        /// Socket url
+        /// The url the socket connects to
         /// </summary>
         string Url { get; }
         /// <summary>
-        /// Is closed
+        /// Whether the socket connection is closed
         /// </summary>
         bool IsClosed { get; }
         /// <summary>
-        /// Is open
+        /// Whether the socket connection is open
         /// </summary>
         bool IsOpen { get; }
         /// <summary>
@@ -77,10 +77,15 @@ namespace CryptoExchange.Net.Interfaces
         /// </summary>
         SslProtocols SSLProtocols { get; set; }
         /// <summary>
-        /// Timeout
+        /// The max time for no data being received before the connection is considered lost
         /// </summary>
         TimeSpan Timeout { get; set; }
         /// <summary>
+        /// Set a proxy to use when connecting
+        /// </summary>
+        /// <param name="proxy"></param>
+        void SetProxy(ApiProxy proxy);
+        /// <summary>
         /// Connect the socket
         /// </summary>
         /// <returns></returns>
@@ -91,18 +96,13 @@ namespace CryptoExchange.Net.Interfaces
         /// <param name="data"></param>
         void Send(string data);
         /// <summary>
-        /// Reset socket
+        /// Reset socket when a connection is lost to prepare for a new connection
         /// </summary>
         void Reset();
         /// <summary>
-        /// Close the connecting
+        /// Close the connection
         /// </summary>
         /// <returns></returns>
         Task CloseAsync();
-        /// <summary>
-        /// Set proxy
-        /// </summary>
-        /// <param name="proxy"></param>
-        void SetProxy(ApiProxy proxy);
     }
 }
diff --git a/CryptoExchange.Net/Interfaces/IWebsocketFactory.cs b/CryptoExchange.Net/Interfaces/IWebsocketFactory.cs
index 1b0d74f..809c624 100644
--- a/CryptoExchange.Net/Interfaces/IWebsocketFactory.cs
+++ b/CryptoExchange.Net/Interfaces/IWebsocketFactory.cs
@@ -11,17 +11,17 @@ namespace CryptoExchange.Net.Interfaces
         /// <summary>
         /// Create a websocket for an url
         /// </summary>
-        /// <param name="log"></param>
-        /// <param name="url"></param>
+        /// <param name="log">The logger</param>
+        /// <param name="url">The url the socket is fo</param>
         /// <returns></returns>
         IWebsocket CreateWebsocket(Log log, string url);
         /// <summary>
         /// Create a websocket for an url
         /// </summary>
-        /// <param name="log"></param>
-        /// <param name="url"></param>
-        /// <param name="cookies"></param>
-        /// <param name="headers"></param>
+        /// <param name="log">The logger</param>
+        /// <param name="url">The url the socket is fo</param>
+        /// <param name="cookies">Cookies to be send in the initial request</param>
+        /// <param name="headers">Headers to be send in the initial request</param>
         /// <returns></returns>
         IWebsocket CreateWebsocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers);
     }
diff --git a/CryptoExchange.Net/Logging/ConsoleLogger.cs b/CryptoExchange.Net/Logging/ConsoleLogger.cs
index baabb7d..54d67a4 100644
--- a/CryptoExchange.Net/Logging/ConsoleLogger.cs
+++ b/CryptoExchange.Net/Logging/ConsoleLogger.cs
@@ -4,7 +4,7 @@ using System;
 namespace CryptoExchange.Net.Logging
 {
     /// <summary>
-    /// Log to console
+    /// ILogger implementation for logging to the console
     /// </summary>
     public class ConsoleLogger : ILogger
     {
diff --git a/CryptoExchange.Net/Logging/DebugLogger.cs b/CryptoExchange.Net/Logging/DebugLogger.cs
index 88b8f7d..9b042de 100644
--- a/CryptoExchange.Net/Logging/DebugLogger.cs
+++ b/CryptoExchange.Net/Logging/DebugLogger.cs
@@ -5,7 +5,7 @@ using System.Diagnostics;
 namespace CryptoExchange.Net.Logging
 {
     /// <summary>
-    /// Default log writer, writes to debug
+    /// Default log writer, uses Trace.WriteLine
     /// </summary>
     public class DebugLogger: ILogger
     {
diff --git a/CryptoExchange.Net/Objects/Options.cs b/CryptoExchange.Net/Objects/Options.cs
index 45c22f4..24c5f58 100644
--- a/CryptoExchange.Net/Objects/Options.cs
+++ b/CryptoExchange.Net/Objects/Options.cs
@@ -15,7 +15,7 @@ namespace CryptoExchange.Net.Objects
     public class BaseOptions
     {
         /// <summary>
-        /// The minimum log level to output. Setting it to null will send all messages to the registered ILoggers. 
+        /// The minimum log level to output
         /// </summary>
         public LogLevel LogLevel { get; set; } = LogLevel.Information;
 
@@ -86,12 +86,12 @@ namespace CryptoExchange.Net.Objects
         }
 
         /// <summary>
-        /// The api credentials
+        /// The api credentials used for signing requests
         /// </summary>        
         public ApiCredentials? ApiCredentials { get; set; }
 
         /// <summary>
-        /// Proxy to use
+        /// Proxy to use when connecting
         /// </summary>
         public ApiProxy? Proxy { get; set; }
 
@@ -161,7 +161,7 @@ namespace CryptoExchange.Net.Objects
         /// <inheritdoc />
         public override string ToString()
         {
-            return $"{base.ToString()}, RateLimiters: {RateLimiters.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, RequestTimeout: {RequestTimeout:c}";
+            return $"{base.ToString()}, RateLimiters: {RateLimiters.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, RequestTimeout: {RequestTimeout:c}, HttpClient: {(HttpClient == null ? "-": "set")}";
         }
     }
 
@@ -181,7 +181,7 @@ namespace CryptoExchange.Net.Objects
         public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
 
         /// <summary>
-        /// The maximum number of times to try to reconnect
+        /// The maximum number of times to try to reconnect, default null will retry indefinitely 
         /// </summary>
         public int? MaxReconnectTries { get; set; }
 
@@ -196,11 +196,13 @@ namespace CryptoExchange.Net.Objects
         public int MaxConcurrentResubscriptionsPerSocket { get; set; } = 5;
 
         /// <summary>
-        /// The time to wait for a socket response before giving a timeout
+        /// The max time to wait for a response after sending a request on the socket before giving a timeout
         /// </summary>
         public TimeSpan SocketResponseTimeout { get; set; } = TimeSpan.FromSeconds(10);
+
         /// <summary>
-        /// The time after which the connection is assumed to be dropped. This can only be used for socket connections where a steady flow of data is expected.
+        /// The max time of not receiving any data after which the connection is assumed to be dropped. This can only be used for socket connections where a steady flow of data is expected,
+        /// for example when the server sends intermittent ping requests
         /// </summary>
         public TimeSpan SocketNoDataTimeout { get; set; }
 
@@ -234,7 +236,7 @@ namespace CryptoExchange.Net.Objects
         /// <inheritdoc />
         public override string ToString()
         {
-            return $"{base.ToString()}, AutoReconnect: {AutoReconnect}, ReconnectInterval: {ReconnectInterval}, SocketResponseTimeout: {SocketResponseTimeout:c}, SocketSubscriptionsCombineTarget: {SocketSubscriptionsCombineTarget}";
+            return $"{base.ToString()}, AutoReconnect: {AutoReconnect}, ReconnectInterval: {ReconnectInterval}, MaxReconnectTries: {MaxReconnectTries}, MaxResubscribeTries: {MaxResubscribeTries}, MaxConcurrentResubscriptionsPerSocket: {MaxConcurrentResubscriptionsPerSocket}, SocketResponseTimeout: {SocketResponseTimeout:c}, SocketNoDataTimeout: {SocketNoDataTimeout}, SocketSubscriptionsCombineTarget: {SocketSubscriptionsCombineTarget}";
         }
     }
 }
diff --git a/CryptoExchange.Net/OrderBook/ProcessBufferEntry.cs b/CryptoExchange.Net/OrderBook/ProcessBufferEntry.cs
index 1bf9a60..c907881 100644
--- a/CryptoExchange.Net/OrderBook/ProcessBufferEntry.cs
+++ b/CryptoExchange.Net/OrderBook/ProcessBufferEntry.cs
@@ -10,19 +10,19 @@ namespace CryptoExchange.Net.OrderBook
     public class ProcessBufferRangeSequenceEntry
     {
         /// <summary>
-        /// First update id
+        /// First sequence number in this update
         /// </summary>
         public long FirstUpdateId { get; set; }
         /// <summary>
-        /// Last update id
+        /// Last sequence number in this update
         /// </summary>
         public long LastUpdateId { get; set; }
         /// <summary>
-        /// List of asks
+        /// List of changed/new asks
         /// </summary>
         public IEnumerable<ISymbolOrderBookEntry> Asks { get; set; } = Array.Empty<ISymbolOrderBookEntry>();
         /// <summary>
-        /// List of bids
+        /// List of changed/new bids
         /// </summary>
         public IEnumerable<ISymbolOrderBookEntry> Bids { get; set; } = Array.Empty<ISymbolOrderBookEntry>();
     }
diff --git a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs
index 17d9136..a7f1e29 100644
--- a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs
+++ b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs
@@ -18,52 +18,64 @@ namespace CryptoExchange.Net.OrderBook
     /// </summary>
     public abstract class SymbolOrderBook : ISymbolOrderBook, IDisposable
     {
-        /// <summary>
-        /// The process buffer, used while syncing
-        /// </summary>
-        protected readonly List<ProcessBufferRangeSequenceEntry> processBuffer;
-        /// <summary>
-        /// The ask list
-        /// </summary>
-        protected SortedList<decimal, ISymbolOrderBookEntry> asks;
-        /// <summary>
-        /// The bid list
-        /// </summary>
-        protected SortedList<decimal, ISymbolOrderBookEntry> bids;
-
         private readonly object bookLock = new object();
 
         private OrderBookStatus status;
         private UpdateSubscription? subscription;
         
-        private readonly bool validateChecksum;
-
         private bool _stopProcessing;
         private Task? _processTask;
+
         private readonly AutoResetEvent _queueEvent;
         private readonly ConcurrentQueue<object> _processQueue;
+        private readonly bool validateChecksum;
+
+        private class EmptySymbolOrderBookEntry : ISymbolOrderBookEntry
+        {
+            public decimal Quantity { get { return 0m; } set {; } }
+            public decimal Price { get { return 0m; } set {; } }
+        }
+
+        private static readonly ISymbolOrderBookEntry emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry();
+
 
         /// <summary>
-        /// Order book implementation id
+        /// A buffer to store messages received before the initial book snapshot is processed. These messages
+        /// will be processed after the book snapshot is set. Any messages in this buffer with sequence numbers lower
+        /// than the snapshot sequence number will be discarded
         /// </summary>
-        public string Id { get; }
+        protected readonly List<ProcessBufferRangeSequenceEntry> processBuffer;
+
+        /// <summary>
+        /// The ask list, should only be accessed using the bookLock
+        /// </summary>
+        protected SortedList<decimal, ISymbolOrderBookEntry> asks;
+
+        /// <summary>
+        /// The bid list, should only be accessed using the bookLock
+        /// </summary>
+        protected SortedList<decimal, ISymbolOrderBookEntry> bids;
+
         /// <summary>
         /// The log
         /// </summary>
         protected Log log;
 
         /// <summary>
-        /// Whether update numbers are consecutive
+        /// Whether update numbers are consecutive. If set to true and an update comes in which isn't the previous sequences number + 1
+        /// the book will resynchronize as it is deemed out of sync
         /// </summary>
         protected bool sequencesAreConsecutive;
         
         /// <summary>
-        /// Whether levels should be strictly enforced
+        /// Whether levels should be strictly enforced. For example, when an order book has 25 levels and a new update comes in which pushes
+        /// the current level 25 ask out of the top 25, should the curent the level 26 entry be removed from the book or does the 
+        /// server handle this
         /// </summary>
         protected bool strictLevels;
 
         /// <summary>
-        /// If order book is set
+        /// If the initial snapshot of the book has been set
         /// </summary>
         protected bool bookSet;
 
@@ -72,9 +84,10 @@ namespace CryptoExchange.Net.OrderBook
         /// </summary>
         protected int? Levels { get; set; } = null;
 
-        /// <summary>
-        /// The status of the order book. Order book is up to date when the status is `Synced`
-        /// </summary>
+        /// <inheritdoc/>
+        public string Id { get; }
+
+        /// <inheritdoc/>
         public OrderBookStatus Status 
         {
             get => status;
@@ -90,46 +103,31 @@ namespace CryptoExchange.Net.OrderBook
             }
         }
 
-        /// <summary>
-        /// Last update identifier
-        /// </summary>
+        /// <inheritdoc/>
         public long LastSequenceNumber { get; private set; }
-        /// <summary>
-        /// The symbol of the order book
-        /// </summary>
+
+        /// <inheritdoc/>
         public string Symbol { get; }
 
-        /// <summary>
-        /// Event when the state changes
-        /// </summary>
+        /// <inheritdoc/>
         public event Action<OrderBookStatus, OrderBookStatus>? OnStatusChange;
 
-        /// <summary>
-        /// Event when the BestBid or BestAsk changes ie a Pricing Tick
-        /// </summary>
+        /// <inheritdoc/>
         public event Action<(ISymbolOrderBookEntry BestBid, ISymbolOrderBookEntry BestAsk)>? OnBestOffersChanged;
 
-        /// <summary>
-        /// Event when order book was updated, containing the changed bids and asks. Be careful! It can generate a lot of events at high-liquidity markets 
-        /// </summary>
+        /// <inheritdoc/>
         public event Action<(IEnumerable<ISymbolOrderBookEntry> Bids, IEnumerable<ISymbolOrderBookEntry> Asks)>? OnOrderBookUpdate;
-        /// <summary>
-        /// Timestamp of the last update
-        /// </summary>
+
+        /// <inheritdoc/>
         public DateTime UpdateTime { get; private set; }
 
-        /// <summary>
-        /// The number of asks in the book
-        /// </summary>
+        /// <inheritdoc/>
         public int AskCount { get; private set; }
-        /// <summary>
-        /// The number of bids in the book
-        /// </summary>
+
+        /// <inheritdoc/>
         public int BidCount { get; private set; }
 
-        /// <summary>
-        /// The list of asks
-        /// </summary>
+        /// <inheritdoc/>
         public IEnumerable<ISymbolOrderBookEntry> Asks
         {
             get
@@ -139,9 +137,7 @@ namespace CryptoExchange.Net.OrderBook
             }
         }
 
-        /// <summary>
-        /// The list of bids
-        /// </summary>
+        /// <inheritdoc/>
         public IEnumerable<ISymbolOrderBookEntry> Bids 
         {
             get
@@ -151,9 +147,7 @@ namespace CryptoExchange.Net.OrderBook
             }
         }
 
-        /// <summary>
-        /// Get a snapshot of the book at this moment
-        /// </summary>
+        /// <inheritdoc/>
         public (IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks) Book
         {
             get
@@ -163,17 +157,7 @@ namespace CryptoExchange.Net.OrderBook
             }
         }
 
-        private class EmptySymbolOrderBookEntry : ISymbolOrderBookEntry
-        {
-            public decimal Quantity { get { return 0m; } set {; } }
-            public decimal Price { get { return 0m; } set {; } }
-        }
-
-        private static readonly ISymbolOrderBookEntry emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry();
-
-        /// <summary>
-        /// The best bid currently in the order book
-        /// </summary>
+        /// <inheritdoc/>
         public ISymbolOrderBookEntry BestBid
         {
             get
@@ -183,9 +167,7 @@ namespace CryptoExchange.Net.OrderBook
             }
         }
 
-        /// <summary>
-        /// The best ask currently in the order book
-        /// </summary>
+        /// <inheritdoc/>
         public ISymbolOrderBookEntry BestAsk 
         {
             get
@@ -195,9 +177,7 @@ namespace CryptoExchange.Net.OrderBook
             }
         }
 
-        /// <summary>
-        /// BestBid/BesAsk returned as a pair
-        /// </summary>
+        /// <inheritdoc/>
         public (ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers {
             get {
                 lock (bookLock)
@@ -208,9 +188,9 @@ namespace CryptoExchange.Net.OrderBook
         /// <summary>
         /// ctor
         /// </summary>
-        /// <param name="id"></param>
-        /// <param name="symbol"></param>
-        /// <param name="options"></param>
+        /// <param name="id">The id of the order book. Should be set to {Exchange}[{type}], for example: Kucoin[Spot]</param>
+        /// <param name="symbol">The symbol the order book is for</param>
+        /// <param name="options">The options for the order book</param>
         protected SymbolOrderBook(string id, string symbol, OrderBookOptions options)
         {
             if (symbol == null)
@@ -236,10 +216,7 @@ namespace CryptoExchange.Net.OrderBook
             log.UpdateWriters(writers.ToList());
         }
 
-        /// <summary>
-        /// Start connecting and synchronizing the order book
-        /// </summary>
-        /// <returns></returns>
+        /// <inheritdoc/>
         public async Task<CallResult<bool>> StartAsync()
         {
             if (Status != OrderBookStatus.Disconnected)
@@ -275,13 +252,21 @@ namespace CryptoExchange.Net.OrderBook
             return new CallResult<bool>(true, null);
         }
 
-        /// <summary>
-        /// Get the average price that a market order would fill at at the current order book state. This is no guarentee that an order of that quantity would actually be filled
-        /// at that price since between this calculation and the order placement the book can have changed.
-        /// </summary>
-        /// <param name="quantity">The quantity in base asset to fill</param>
-        /// <param name="type">The type</param>
-        /// <returns>Average fill price</returns>
+        /// <inheritdoc/>
+        public async Task StopAsync()
+        {
+            log.Write(LogLevel.Debug, $"{Id} order book {Symbol} stopping");
+            Status = OrderBookStatus.Disconnected;
+            _queueEvent.Set();
+            if (_processTask != null)
+                await _processTask.ConfigureAwait(false);
+
+            if (subscription != null)
+                await subscription.CloseAsync().ConfigureAwait(false);
+            log.Write(LogLevel.Debug, $"{Id} order book {Symbol} stopped");
+        }
+
+        /// <inheritdoc/>
         public CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type)
         {
             if (Status != OrderBookStatus.Synced)
@@ -312,6 +297,219 @@ namespace CryptoExchange.Net.OrderBook
             return new CallResult<decimal>(Math.Round(totalCost / totalAmount, 8), null);
         }
 
+        /// <summary>
+        /// Implementation for starting the order book. Should typically have logic for subscribing to the update stream and retrieving
+        /// and setting the initial order book
+        /// </summary>
+        /// <returns></returns>
+        protected abstract Task<CallResult<UpdateSubscription>> DoStartAsync();
+
+        /// <summary>
+        /// Reset the order book
+        /// </summary>
+        protected virtual void DoReset() { }
+
+        /// <summary>
+        /// Resync the order book
+        /// </summary>
+        /// <returns></returns>
+        protected abstract Task<CallResult<bool>> DoResyncAsync();
+
+        /// <summary>
+        /// Implementation for validating a checksum value with the current order book. If checksum validation fails (returns false)
+        /// the order book will be resynchronized
+        /// </summary>
+        /// <param name="checksum"></param>
+        /// <returns></returns>
+        protected virtual bool DoChecksum(int checksum) => true;
+                
+        /// <summary>
+        /// Set the initial data for the order book. Typically the snapshot which was requested from the Rest API, or the first snapshot
+        /// received from a socket subcription
+        /// </summary>
+        /// <param name="orderBookSequenceNumber">The last update sequence number until which the snapshot is in sync</param>
+        /// <param name="askList">List of asks</param>
+        /// <param name="bidList">List of bids</param>
+        protected void SetInitialOrderBook(long orderBookSequenceNumber, IEnumerable<ISymbolOrderBookEntry> bidList, IEnumerable<ISymbolOrderBookEntry> askList)
+        {
+            _processQueue.Enqueue(new InitialOrderBookItem { StartUpdateId = orderBookSequenceNumber, EndUpdateId = orderBookSequenceNumber, Asks = askList, Bids = bidList });
+            _queueEvent.Set();
+        }
+
+        /// <summary>
+        /// Add an update to the process queue. Updates the book by providing changed bids and asks, along with an update number which should be higher than the previous update numbers
+        /// </summary>
+        /// <param name="updateId">The sequence number</param>
+        /// <param name="bids">List of updated/new bids</param>
+        /// <param name="asks">List of updated/new asks</param>
+        protected void UpdateOrderBook(long updateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
+        {
+            _processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = updateId, EndUpdateId = updateId, Asks = asks, Bids = bids });
+            _queueEvent.Set();
+        }
+
+        /// <summary>
+        /// Add an update to the process queue. Updates the book by providing changed bids and asks, along with the first and last sequence number in the update
+        /// </summary>
+        /// <param name="firstUpdateId">The sequence number of the first update</param>
+        /// <param name="lastUpdateId">The sequence number of the last update</param>
+        /// <param name="bids">List of updated/new bids</param>
+        /// <param name="asks">List of updated/new asks</param>
+        protected void UpdateOrderBook(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
+        {
+            _processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = firstUpdateId, EndUpdateId = lastUpdateId, Asks = asks, Bids = bids });
+            _queueEvent.Set();
+        }
+
+        /// <summary>
+        /// Add an update to the process queue. Updates the book by providing changed bids and asks, each with its own sequence number
+        /// </summary>
+        /// <param name="bids">List of updated/new bids</param>
+        /// <param name="asks">List of updated/new asks</param>
+        protected void UpdateOrderBook(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
+        {
+            var highest = Math.Max(bids.Any() ? bids.Max(b => b.Sequence) : 0, asks.Any() ? asks.Max(a => a.Sequence) : 0);
+            var lowest = Math.Min(bids.Any() ? bids.Min(b => b.Sequence) : long.MaxValue, asks.Any() ? asks.Min(a => a.Sequence) : long.MaxValue);
+
+            _processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = lowest, EndUpdateId = highest, Asks = asks, Bids = bids });
+            _queueEvent.Set();
+        }
+
+        /// <summary>
+        /// Add a checksum value to the process queue
+        /// </summary>
+        /// <param name="checksum">The checksum value</param>
+        protected void AddChecksum(int checksum)
+        {
+            _processQueue.Enqueue(new ChecksumItem() { Checksum = checksum });
+            _queueEvent.Set();
+        }
+
+        /// <summary>
+        /// Check and empty the process buffer; see what entries to update the book with
+        /// </summary>
+        protected void CheckProcessBuffer()
+        {
+            var pbList = processBuffer.ToList();
+            if (pbList.Count > 0)
+                log.Write(LogLevel.Debug, "Processing buffered updates");
+
+            foreach (var bufferEntry in pbList)
+            {
+                ProcessRangeUpdates(bufferEntry.FirstUpdateId, bufferEntry.LastUpdateId, bufferEntry.Bids, bufferEntry.Asks);
+                processBuffer.Remove(bufferEntry);
+            }
+        }
+
+        /// <summary>
+        /// Update order book with an entry
+        /// </summary>
+        /// <param name="sequence">Sequence number of the update</param>
+        /// <param name="type">Type of entry</param>
+        /// <param name="entry">The entry</param>
+        protected virtual bool ProcessUpdate(long sequence, OrderBookEntryType type, ISymbolOrderBookEntry entry)
+        {
+            if (sequence <= LastSequenceNumber)
+            {
+                log.Write(LogLevel.Debug, $"{Id} order book {Symbol} update skipped #{sequence}");
+                return false;
+            }
+
+            if (sequencesAreConsecutive && sequence > LastSequenceNumber + 1)
+            {
+                // Out of sync
+                log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync (expected { LastSequenceNumber + 1}, was {sequence}), reconnecting");
+                _stopProcessing = true;
+                Resubscribe();
+                return false;
+            }
+
+            UpdateTime = DateTime.UtcNow;
+            var listToChange = type == OrderBookEntryType.Ask ? asks : bids;
+            if (entry.Quantity == 0)
+            {
+                if (!listToChange.ContainsKey(entry.Price))
+                    return true;
+
+                listToChange.Remove(entry.Price);
+                if (type == OrderBookEntryType.Ask) AskCount--;
+                else BidCount--;
+            }
+            else
+            {
+                if (!listToChange.ContainsKey(entry.Price))
+                {
+                    listToChange.Add(entry.Price, entry);
+                    if (type == OrderBookEntryType.Ask) AskCount++;
+                    else BidCount++;
+                }
+                else
+                {
+                    listToChange[entry.Price] = entry;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Wait until the order book snapshot has been set
+        /// </summary>
+        /// <param name="timeout">Max wait time</param>
+        /// <returns></returns>
+        protected async Task<CallResult<bool>> WaitForSetOrderBookAsync(int timeout)
+        {
+            var startWait = DateTime.UtcNow;
+            while (!bookSet && Status == OrderBookStatus.Syncing)
+            {
+                if ((DateTime.UtcNow - startWait).TotalMilliseconds > timeout)
+                    return new CallResult<bool>(false, new ServerError("Timeout while waiting for data"));
+
+                await Task.Delay(10).ConfigureAwait(false);
+            }
+
+            return new CallResult<bool>(true, null);
+        }
+
+        /// <summary>
+        /// Dispose the order book
+        /// </summary>
+        public abstract void Dispose();
+
+        /// <summary>
+        /// String representation of the top 3 entries
+        /// </summary>
+        /// <returns></returns>
+        public override string ToString()
+        {
+            return ToString(3);
+        }
+
+        /// <summary>
+        /// String representation of the top x entries
+        /// </summary>
+        /// <returns></returns>
+        public string ToString(int numberOfEntries)
+        {
+            var result = string.Empty;
+            result += $"Asks ({AskCount}): {Environment.NewLine}";
+            foreach (var entry in Asks.Take(numberOfEntries).Reverse())
+                result += $"  {entry.Price.ToString(CultureInfo.InvariantCulture).PadLeft(8)} | {entry.Quantity.ToString(CultureInfo.InvariantCulture).PadRight(8)}{Environment.NewLine}";
+
+            result += $"Bids ({BidCount}): {Environment.NewLine}";
+            foreach (var entry in Bids.Take(numberOfEntries))
+                result += $"  {entry.Price.ToString(CultureInfo.InvariantCulture).PadLeft(8)} | {entry.Quantity.ToString(CultureInfo.InvariantCulture).PadRight(8)}{Environment.NewLine}";
+            return result;
+        }
+
+        private void CheckBestOffersChanged(ISymbolOrderBookEntry prevBestBid, ISymbolOrderBookEntry prevBestAsk)
+        {
+            var (bestBid, bestAsk) = BestOffers;
+            if (bestBid.Price != prevBestBid.Price || bestBid.Quantity != prevBestBid.Quantity ||
+                   bestAsk.Price != prevBestAsk.Price || bestAsk.Quantity != prevBestAsk.Quantity)
+                OnBestOffersChanged?.Invoke((bestBid, bestAsk));
+        }
+
         private void Reset()
         {
             _queueEvent.Set();
@@ -339,50 +537,9 @@ namespace CryptoExchange.Net.OrderBook
             Status = OrderBookStatus.Synced;
         }
 
-        /// <summary>
-        /// Stop syncing the order book
-        /// </summary>
-        /// <returns></returns>
-        public async Task StopAsync()
-        {
-            log.Write(LogLevel.Debug, $"{Id} order book {Symbol} stopping");
-            Status = OrderBookStatus.Disconnected;
-            _queueEvent.Set();
-            if(_processTask != null)
-                await _processTask.ConfigureAwait(false);
-
-            if(subscription != null)
-                await subscription.CloseAsync().ConfigureAwait(false);
-            log.Write(LogLevel.Debug, $"{Id} order book {Symbol} stopped");
-        }
-
-        /// <summary>
-        /// Start the order book
-        /// </summary>
-        /// <returns></returns>
-        protected abstract Task<CallResult<UpdateSubscription>> DoStartAsync();
-
-        /// <summary>
-        /// Reset the order book
-        /// </summary>
-        protected virtual void DoReset() { }
-
-        /// <summary>
-        /// Resync the order book
-        /// </summary>
-        /// <returns></returns>
-        protected abstract Task<CallResult<bool>> DoResyncAsync();
-
-        /// <summary>
-        /// Validate a checksum with the current order book
-        /// </summary>
-        /// <param name="checksum"></param>
-        /// <returns></returns>
-        protected virtual bool DoChecksum(int checksum) => true;
-
         private void ProcessQueue()
         {
-            while(Status != OrderBookStatus.Disconnected)
+            while (Status != OrderBookStatus.Disconnected)
             {
                 _queueEvent.WaitOne();
 
@@ -462,7 +619,7 @@ namespace CryptoExchange.Net.OrderBook
                         _stopProcessing = true;
                         Resubscribe();
                         return;
-                    }                    
+                    }
 
                     OnOrderBookUpdate?.Invoke((item.Bids, item.Asks));
                     CheckBestOffersChanged(prevBestBid, prevBestAsk);
@@ -476,7 +633,7 @@ namespace CryptoExchange.Net.OrderBook
             {
                 if (!validateChecksum)
                     return;
-                                
+
                 bool checksumResult = false;
                 try
                 {
@@ -490,7 +647,7 @@ namespace CryptoExchange.Net.OrderBook
                         throw;
                 }
 
-                if(!checksumResult)
+                if (!checksumResult)
                 {
                     log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync. Resyncing");
                     _stopProcessing = true;
@@ -520,67 +677,6 @@ namespace CryptoExchange.Net.OrderBook
             });
         }
 
-        /// <summary>
-        /// Set the initial data for the order book
-        /// </summary>
-        /// <param name="orderBookSequenceNumber">The last update sequence number</param>
-        /// <param name="askList">List of asks</param>
-        /// <param name="bidList">List of bids</param>
-        protected void SetInitialOrderBook(long orderBookSequenceNumber, IEnumerable<ISymbolOrderBookEntry> bidList, IEnumerable<ISymbolOrderBookEntry> askList)
-        {
-            _processQueue.Enqueue(new InitialOrderBookItem { StartUpdateId = orderBookSequenceNumber, EndUpdateId = orderBookSequenceNumber, Asks = askList, Bids = bidList });
-            _queueEvent.Set();
-        }
-
-        /// <summary>
-        /// Update the order book using a single id for an update
-        /// </summary>
-        /// <param name="rangeUpdateId"></param>
-        /// <param name="bids"></param>
-        /// <param name="asks"></param>
-        protected void UpdateOrderBook(long rangeUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
-        {
-            _processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = rangeUpdateId, EndUpdateId = rangeUpdateId, Asks = asks, Bids = bids });
-            _queueEvent.Set();
-        }
-
-        /// <summary>
-        /// Add a checksum to the process queue
-        /// </summary>
-        /// <param name="checksum"></param>
-        protected void AddChecksum(int checksum)
-        {
-            _processQueue.Enqueue(new ChecksumItem() { Checksum = checksum });
-            _queueEvent.Set();
-        }
-
-        /// <summary>
-        /// Update the order book using a first/last update id
-        /// </summary>
-        /// <param name="firstUpdateId"></param>
-        /// <param name="lastUpdateId"></param>
-        /// <param name="bids"></param>
-        /// <param name="asks"></param>
-        protected void UpdateOrderBook(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
-        {
-            _processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = firstUpdateId, EndUpdateId = lastUpdateId, Asks = asks, Bids = bids });
-            _queueEvent.Set();
-        }
-
-        /// <summary>
-        /// Update the order book using sequenced entries
-        /// </summary>
-        /// <param name="bids">List of bids</param>
-        /// <param name="asks">List of asks</param>
-        protected void UpdateOrderBook(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
-        {
-            var highest = Math.Max(bids.Any() ? bids.Max(b => b.Sequence) : 0, asks.Any() ? asks.Max(a => a.Sequence) : 0);
-            var lowest = Math.Min(bids.Any() ? bids.Min(b => b.Sequence) : long.MaxValue, asks.Any() ? asks.Min(a => a.Sequence) : long.MaxValue);
-
-            _processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = lowest, EndUpdateId = highest , Asks = asks, Bids = bids });
-            _queueEvent.Set();
-        }
-
         private void ProcessRangeUpdates(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
         {
             if (lastUpdateId <= LastSequenceNumber)
@@ -612,132 +708,7 @@ namespace CryptoExchange.Net.OrderBook
 
             LastSequenceNumber = lastUpdateId;
             log.Write(LogLevel.Debug, $"{Id} order book {Symbol} update processed #{firstUpdateId}-{lastUpdateId}");
-        }
-
-        /// <summary>
-        /// Check and empty the process buffer; see what entries to update the book with
-        /// </summary>
-        protected void CheckProcessBuffer()
-        {
-            var pbList = processBuffer.ToList();
-            if(pbList.Count > 0)
-                log.Write(LogLevel.Debug, "Processing buffered updates");
-
-            foreach (var bufferEntry in pbList)
-            {
-                ProcessRangeUpdates(bufferEntry.FirstUpdateId, bufferEntry.LastUpdateId, bufferEntry.Bids, bufferEntry.Asks);
-                processBuffer.Remove(bufferEntry);
-            }
-        }
-
-        /// <summary>
-        /// Update order book with an entry
-        /// </summary>
-        /// <param name="sequence">Sequence number of the update</param>
-        /// <param name="type">Type of entry</param>
-        /// <param name="entry">The entry</param>
-        protected virtual bool ProcessUpdate(long sequence, OrderBookEntryType type, ISymbolOrderBookEntry entry)
-        {
-            if (sequence <= LastSequenceNumber)
-            {
-                log.Write(LogLevel.Debug, $"{Id} order book {Symbol} update skipped #{sequence}");
-                return false;
-            }
-
-            if (sequencesAreConsecutive && sequence > LastSequenceNumber + 1)
-            {
-                // Out of sync
-                log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync (expected { LastSequenceNumber + 1}, was {sequence}), reconnecting");
-                _stopProcessing = true;
-                Resubscribe();
-                return false;
-            }
-
-            UpdateTime = DateTime.UtcNow;
-            var listToChange = type == OrderBookEntryType.Ask ? asks : bids;
-            if (entry.Quantity == 0)
-            {
-                if (!listToChange.ContainsKey(entry.Price))
-                    return true;
-
-                listToChange.Remove(entry.Price);
-                if (type == OrderBookEntryType.Ask) AskCount--;
-                else BidCount--;
-            }
-            else
-            {
-                if (!listToChange.ContainsKey(entry.Price))
-                {
-                    listToChange.Add(entry.Price, entry);
-                    if (type == OrderBookEntryType.Ask) AskCount++;
-                    else BidCount++;
-                }
-                else
-                {
-                    listToChange[entry.Price] = entry;
-                }
-            }
-
-            return true;
-        }
-
-        /// <summary>
-        /// Wait until the order book has been set
-        /// </summary>
-        /// <param name="timeout">Max wait time</param>
-        /// <returns></returns>
-        protected async Task<CallResult<bool>> WaitForSetOrderBookAsync(int timeout)
-        {
-            var startWait = DateTime.UtcNow;
-            while (!bookSet && Status == OrderBookStatus.Syncing)
-            {
-                if ((DateTime.UtcNow - startWait).TotalMilliseconds > timeout)
-                    return new CallResult<bool>(false, new ServerError("Timeout while waiting for data"));
-
-                await Task.Delay(10).ConfigureAwait(false);
-            }
-
-            return new CallResult<bool>(true, null);
-        }
-
-        private void CheckBestOffersChanged(ISymbolOrderBookEntry prevBestBid, ISymbolOrderBookEntry prevBestAsk)
-        {
-            var (bestBid, bestAsk) = BestOffers;
-            if (bestBid.Price != prevBestBid.Price || bestBid.Quantity != prevBestBid.Quantity ||
-                   bestAsk.Price != prevBestAsk.Price || bestAsk.Quantity != prevBestAsk.Quantity)
-                OnBestOffersChanged?.Invoke((bestBid, bestAsk));
-        }
-
-        /// <summary>
-        /// Dispose the order book
-        /// </summary>
-        public abstract void Dispose();
-
-        /// <summary>
-        /// String representation of the top 3 entries
-        /// </summary>
-        /// <returns></returns>
-        public override string ToString()
-        {
-            return ToString(3);
-        }
-
-        /// <summary>
-        /// String representation of the top x entries
-        /// </summary>
-        /// <returns></returns>
-        public string ToString(int numberOfEntries)
-        {
-            var result = string.Empty;
-            result += $"Asks ({AskCount}): {Environment.NewLine}";
-            foreach (var entry in Asks.Take(numberOfEntries).Reverse())
-                result += $"  {entry.Price.ToString(CultureInfo.InvariantCulture).PadLeft(8)} | {entry.Quantity.ToString(CultureInfo.InvariantCulture).PadRight(8)}{Environment.NewLine}";
-
-            result += $"Bids ({BidCount}): {Environment.NewLine}";
-            foreach (var entry in Bids.Take(numberOfEntries))
-                result += $"  {entry.Price.ToString(CultureInfo.InvariantCulture).PadLeft(8)} | {entry.Quantity.ToString(CultureInfo.InvariantCulture).PadRight(8)}{Environment.NewLine}";
-            return result;
-        }
+        }        
     }
 
     internal class DescComparer<T> : IComparer<T>
diff --git a/CryptoExchange.Net/Requests/Request.cs b/CryptoExchange.Net/Requests/Request.cs
index 58c2d44..b707530 100644
--- a/CryptoExchange.Net/Requests/Request.cs
+++ b/CryptoExchange.Net/Requests/Request.cs
@@ -11,7 +11,7 @@ using CryptoExchange.Net.Interfaces;
 namespace CryptoExchange.Net.Requests
 {
     /// <summary>
-    /// Request object
+    /// Request object, wrapper for HttpRequestMessage
     /// </summary>
     public class Request : IRequest
     {
@@ -49,6 +49,7 @@ namespace CryptoExchange.Net.Requests
 
         /// <inheritdoc />
         public Uri Uri => request.RequestUri;
+
         /// <inheritdoc />
         public int RequestId { get; }
 
diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs
index be68097..3da74c4 100644
--- a/CryptoExchange.Net/Requests/RequestFactory.cs
+++ b/CryptoExchange.Net/Requests/RequestFactory.cs
@@ -7,7 +7,7 @@ using CryptoExchange.Net.Objects;
 namespace CryptoExchange.Net.Requests
 {
     /// <summary>
-    /// WebRequest factory
+    /// Request factory
     /// </summary>
     public class RequestFactory : IRequestFactory
     {
diff --git a/CryptoExchange.Net/Requests/Response.cs b/CryptoExchange.Net/Requests/Response.cs
index 93d2113..e5a07b4 100644
--- a/CryptoExchange.Net/Requests/Response.cs
+++ b/CryptoExchange.Net/Requests/Response.cs
@@ -8,7 +8,7 @@ using CryptoExchange.Net.Interfaces;
 namespace CryptoExchange.Net.Requests
 {
     /// <summary>
-    /// HttpWebResponse response object
+    /// Response object, wrapper for HttpResponseMessage
     /// </summary>
     internal class Response : IResponse
     {
diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs
index 2ded49b..559c0b7 100644
--- a/CryptoExchange.Net/RestClient.cs
+++ b/CryptoExchange.Net/RestClient.cs
@@ -67,9 +67,7 @@ namespace CryptoExchange.Net
         /// </summary>
         protected IEnumerable<IRateLimiter> RateLimiters { get; private set; }
 
-        /// <summary>
-        /// Total requests made by this client
-        /// </summary>
+        /// <inheritdoc />
         public int TotalRequestsMade { get; private set; }
 
         /// <summary>
@@ -133,7 +131,6 @@ namespace CryptoExchange.Net
         /// <param name="cancellationToken">Cancellation token</param>
         /// <param name="parameters">The parameters of the request</param>
         /// <param name="signed">Whether or not the request should be authenticated</param>
-        /// <param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param> 
         /// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
         /// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
         /// <param name="credits">Credits used for the request</param>
@@ -147,7 +144,6 @@ namespace CryptoExchange.Net
             CancellationToken cancellationToken,
             Dictionary<string, object>? parameters = null, 
             bool signed = false, 
-            bool checkResult = true,
             HttpMethodParameterPosition? parameterPosition = null,
             ArrayParametersSerialization? arraySerialization = null, 
             int credits = 1,
diff --git a/CryptoExchange.Net/SocketClient.cs b/CryptoExchange.Net/SocketClient.cs
index 714e385..7022bd3 100644
--- a/CryptoExchange.Net/SocketClient.cs
+++ b/CryptoExchange.Net/SocketClient.cs
@@ -80,9 +80,7 @@ namespace CryptoExchange.Net
         /// </summary>
         protected internal int? RateLimitPerSocketPerSecond { get; set; }
 
-        /// <summary>
-        /// The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
-        /// </summary>
+        /// <inheritdoc />
         public double IncomingKbps
         {
             get
diff --git a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
index d3ee1cc..bc74c74 100644
--- a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
+++ b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
@@ -42,7 +42,7 @@ namespace CryptoExchange.Net.Sockets
         private DateTime _lastReceivedMessagesUpdate;
 
         /// <summary>
-        /// Received messages time -> size
+        /// Received messages, the size and the timstamp
         /// </summary>
         protected readonly List<ReceiveItem> _receivedMessages;
         /// <summary>
@@ -72,17 +72,15 @@ namespace CryptoExchange.Net.Sockets
         /// </summary>
         protected readonly List<Action<string>> messageHandlers = new List<Action<string>>();
 
-        /// <summary>
-        /// The id of this socket
-        /// </summary>
+        /// <inheritdoc />
         public int Id { get; }
 
         /// <inheritdoc />
         public string? Origin { get; set; }
-        /// <summary>
-        /// Whether this socket is currently reconnecting
-        /// </summary>
+
+        /// <inheritdoc />
         public bool Reconnecting { get; set; }
+
         /// <summary>
         /// The timestamp this socket has been active for the last time
         /// </summary>
@@ -92,22 +90,19 @@ namespace CryptoExchange.Net.Sockets
         /// Delegate used for processing byte data received from socket connections before it is processed by handlers
         /// </summary>
         public Func<byte[], string>? DataInterpreterBytes { get; set; }
+
         /// <summary>
         /// Delegate used for processing string data received from socket connections before it is processed by handlers
         /// </summary>
         public Func<string, string>? DataInterpreterString { get; set; }
-        /// <summary>
-        /// Url this socket connects to
-        /// </summary>
+
+        /// <inheritdoc />
         public string Url { get; }
-        /// <summary>
-        /// If the connection is closed
-        /// </summary>
+
+        /// <inheritdoc />
         public bool IsClosed => _socket.State == WebSocketState.Closed;
 
-        /// <summary>
-        /// If the connection is open
-        /// </summary>
+        /// <inheritdoc />
         public bool IsOpen => _socket.State == WebSocketState.Open && !_closing;
 
         /// <summary>
@@ -116,9 +111,7 @@ namespace CryptoExchange.Net.Sockets
         public SslProtocols SSLProtocols { get; set; }
 
         private Encoding _encoding = Encoding.UTF8;
-        /// <summary>
-        /// Encoding used for decoding the received bytes into a string
-        /// </summary>
+        /// <inheritdoc />
         public Encoding? Encoding
         {
             get => _encoding;
@@ -128,19 +121,16 @@ namespace CryptoExchange.Net.Sockets
                     _encoding = value;
             }
         }
+
         /// <summary>
         /// The max amount of outgoing messages per second
         /// </summary>
         public int? RatelimitPerSecond { get; set; }
 
-        /// <summary>
-        /// The timespan no data is received on the socket. If no data is received within this time an error is generated
-        /// </summary>
+        /// <inheritdoc />
         public TimeSpan Timeout { get; set; }
 
-        /// <summary>
-        /// The current kilobytes per second of data being received, averaged over the last 3 seconds
-        /// </summary>
+        /// <inheritdoc />
         public double IncomingKbps
         {
             get
@@ -157,33 +147,28 @@ namespace CryptoExchange.Net.Sockets
             }
         }
 
-        /// <summary>
-        /// Socket closed event
-        /// </summary>
+        /// <inheritdoc />
         public event Action OnClose
         {
             add => closeHandlers.Add(value);
             remove => closeHandlers.Remove(value);
         }
-        /// <summary>
-        /// Socket message received event
-        /// </summary>
+
+        /// <inheritdoc />
         public event Action<string> OnMessage
         {
             add => messageHandlers.Add(value);
             remove => messageHandlers.Remove(value);
         }
-        /// <summary>
-        /// Socket error event
-        /// </summary>
+
+        /// <inheritdoc />
         public event Action<Exception> OnError
         {
             add => errorHandlers.Add(value);
             remove => errorHandlers.Remove(value);
         }
-        /// <summary>
-        /// Socket opened event
-        /// </summary>
+
+        /// <inheritdoc />
         public event Action OnOpen
         {
             add => openHandlers.Add(value);
@@ -224,10 +209,7 @@ namespace CryptoExchange.Net.Sockets
             _socket = CreateSocket();
         }
 
-        /// <summary>
-        /// Set a proxy to use. Should be set before connecting
-        /// </summary>
-        /// <param name="proxy"></param>
+        /// <inheritdoc />
         public virtual void SetProxy(ApiProxy proxy)
         {
             _socket.Options.Proxy = new WebProxy(proxy.Host, proxy.Port);
@@ -235,10 +217,7 @@ namespace CryptoExchange.Net.Sockets
                 _socket.Options.Proxy.Credentials = new NetworkCredential(proxy.Login, proxy.Password);
         }
 
-        /// <summary>
-        /// Connect the websocket
-        /// </summary>
-        /// <returns>True if successfull</returns>
+        /// <inheritdoc />
         public virtual async Task<bool> ConnectAsync()
         {
             log.Write(LogLevel.Debug, $"Socket {Id} connecting");
@@ -270,10 +249,7 @@ namespace CryptoExchange.Net.Sockets
             return true;
         }
 
-        /// <summary>
-        /// Send data over the websocket
-        /// </summary>
-        /// <param name="data">Data to send</param>
+        /// <inheritdoc />
         public virtual void Send(string data)
         {
             if (_closing)
@@ -285,10 +261,7 @@ namespace CryptoExchange.Net.Sockets
             _sendEvent.Set();
         }
 
-        /// <summary>
-        /// Close the websocket
-        /// </summary>
-        /// <returns></returns>
+        /// <inheritdoc />
         public virtual async Task CloseAsync()
         {
             log.Write(LogLevel.Debug, $"Socket {Id} closing");
@@ -344,9 +317,7 @@ namespace CryptoExchange.Net.Sockets
             log.Write(LogLevel.Trace, $"Socket {Id} disposed");
         }
 
-        /// <summary>
-        /// Reset the socket so a new connection can be attempted after it has been connected before
-        /// </summary>
+        /// <inheritdoc />
         public void Reset()
         {
             log.Write(LogLevel.Debug, $"Socket {Id} resetting");
diff --git a/CryptoExchange.Net/Sockets/MessageEvent.cs b/CryptoExchange.Net/Sockets/MessageEvent.cs
index 02a3792..b60c62c 100644
--- a/CryptoExchange.Net/Sockets/MessageEvent.cs
+++ b/CryptoExchange.Net/Sockets/MessageEvent.cs
@@ -26,7 +26,7 @@ namespace CryptoExchange.Net.Sockets
         public DateTime ReceivedTimestamp { get; set; }
 
         /// <summary>
-        /// 
+        /// ctor
         /// </summary>
         /// <param name="connection"></param>
         /// <param name="jsonData"></param>
diff --git a/CryptoExchange.Net/Sockets/SocketConnection.cs b/CryptoExchange.Net/Sockets/SocketConnection.cs
index 1df5dc9..829a2b5 100644
--- a/CryptoExchange.Net/Sockets/SocketConnection.cs
+++ b/CryptoExchange.Net/Sockets/SocketConnection.cs
@@ -14,7 +14,7 @@ using CryptoExchange.Net.Objects;
 namespace CryptoExchange.Net.Sockets
 {
     /// <summary>
-    /// Socket connecting
+    /// A single socket connection to the server
     /// </summary>
     public class SocketConnection
     {
@@ -22,26 +22,32 @@ namespace CryptoExchange.Net.Sockets
         /// Connection lost event
         /// </summary>
         public event Action? ConnectionLost;
+
         /// <summary>
         /// Connection closed and no reconnect is happening
         /// </summary>
         public event Action? ConnectionClosed;
+
         /// <summary>
         /// Connecting restored event
         /// </summary>
         public event Action<TimeSpan>? ConnectionRestored;
+
         /// <summary>
         /// The connection is paused event
         /// </summary>
         public event Action? ActivityPaused;
+
         /// <summary>
         /// The connection is unpaused event
         /// </summary>
         public event Action? ActivityUnpaused;
+
         /// <summary>
         /// Connecting closed event
         /// </summary>
         public event Action? Closed;
+
         /// <summary>
         /// Unhandled message event
         /// </summary>
@@ -57,30 +63,35 @@ namespace CryptoExchange.Net.Sockets
         }
 
         /// <summary>
-        /// If connection is authenticated
+        /// If the connection has been authenticated
         /// </summary>
         public bool Authenticated { get; set; }
+
         /// <summary>
         /// If connection is made
         /// </summary>
         public bool Connected { get; private set; }
 
         /// <summary>
-        /// The underlying socket
+        /// The underlying websocket
         /// </summary>
         public IWebsocket Socket { get; set; }
+
         /// <summary>
         /// If the socket should be reconnected upon closing
         /// </summary>
         public bool ShouldReconnect { get; set; }
+
         /// <summary>
-        /// Current reconnect try
+        /// Current reconnect try, reset when a successful connection is made
         /// </summary>
         public int ReconnectTry { get; set; }
+
         /// <summary>
-        /// Current resubscribe try
+        /// Current resubscribe try, reset when a successful connection is made
         /// </summary>
         public int ResubscribeTry { get; set; }
+
         /// <summary>
         /// Time of disconnecting
         /// </summary>
@@ -138,7 +149,7 @@ namespace CryptoExchange.Net.Sockets
         /// <summary>
         /// Process a message received by the socket
         /// </summary>
-        /// <param name="data"></param>
+        /// <param name="data">The received data</param>
         private void ProcessMessage(string data)
         {
             var timestamp = DateTime.UtcNow;
@@ -193,7 +204,7 @@ namespace CryptoExchange.Net.Sockets
         }
 
         /// <summary>
-        /// Add subscription to this connection
+        /// Add a subscription to this connection
         /// </summary>
         /// <param name="subscription"></param>
         public void AddSubscription(SocketSubscription subscription)
@@ -203,15 +214,20 @@ namespace CryptoExchange.Net.Sockets
         }
 
         /// <summary>
-        /// Get a subscription on this connection
+        /// Get a subscription on this connection by id
         /// </summary>
         /// <param name="id"></param>
-        public SocketSubscription GetSubscription(int id)
+        public SocketSubscription? GetSubscription(int id)
         {
             lock (subscriptionLock)
                 return subscriptions.SingleOrDefault(s => s.Id == id);
         }
 
+        /// <summary>
+        /// Process data
+        /// </summary>
+        /// <param name="messageEvent"></param>
+        /// <returns>True if the data was successfully handled</returns>
         private bool HandleData(MessageEvent messageEvent)
         {
             SocketSubscription? currentSubscription = null;
@@ -249,7 +265,7 @@ namespace CryptoExchange.Net.Sockets
                 
                 sw.Stop();
                 if (sw.ElapsedMilliseconds > 500)
-                    log.Write(LogLevel.Warning, $"Socket {Socket.Id} message processing slow ({sw.ElapsedMilliseconds}ms), consider offloading data handling to another thread. " +
+                    log.Write(LogLevel.Debug, $"Socket {Socket.Id} message processing slow ({sw.ElapsedMilliseconds}ms), consider offloading data handling to another thread. " +
                                                     "Data from this socket may arrive late or not at all if message processing is continuously slow.");
                 else
                     log.Write(LogLevel.Trace, $"Socket {Socket.Id} message processed in {sw.ElapsedMilliseconds}ms");
@@ -269,7 +285,7 @@ namespace CryptoExchange.Net.Sockets
         /// <typeparam name="T">The data type expected in response</typeparam>
         /// <param name="obj">The object to send</param>
         /// <param name="timeout">The timeout for response</param>
-        /// <param name="handler">The response handler</param>
+        /// <param name="handler">The response handler, should return true if the received JToken was the response to the request</param>
         /// <returns></returns>
         public virtual Task SendAndWaitAsync<T>(T obj, TimeSpan timeout, Func<JToken, bool> handler)
         {
@@ -391,6 +407,7 @@ namespace CryptoExchange.Net.Sockets
                         if (!reconnectResult)
                         {
                             ResubscribeTry++;
+                            DisconnectTime = time;
 
                             if (socketClient.ClientOptions.MaxResubscribeTries != null &&
                             ResubscribeTry >= socketClient.ClientOptions.MaxResubscribeTries)
@@ -419,7 +436,7 @@ namespace CryptoExchange.Net.Sockets
                             if (lostTriggered)
                             {
                                 lostTriggered = false;
-                                InvokeConnectionRestored(time);
+                                _ = Task.Run(() => ConnectionRestored?.Invoke(time.HasValue ? DateTime.UtcNow - time.Value : TimeSpan.FromSeconds(0))).ConfigureAwait(false);
                             }
 
                             break;
@@ -443,11 +460,6 @@ namespace CryptoExchange.Net.Sockets
             }
         }
 
-        private async void InvokeConnectionRestored(DateTime? disconnectTime)
-        {
-            await Task.Run(() => ConnectionRestored?.Invoke(disconnectTime.HasValue ? DateTime.UtcNow - disconnectTime.Value : TimeSpan.FromSeconds(0))).ConfigureAwait(false);
-        }
-
         private async Task<bool> ProcessReconnectAsync()
         {
             if (Authenticated)
diff --git a/CryptoExchange.Net/Sockets/SocketSubscription.cs b/CryptoExchange.Net/Sockets/SocketSubscription.cs
index 77058ce..23e6428 100644
--- a/CryptoExchange.Net/Sockets/SocketSubscription.cs
+++ b/CryptoExchange.Net/Sockets/SocketSubscription.cs
@@ -9,9 +9,10 @@ namespace CryptoExchange.Net.Sockets
     public class SocketSubscription
     {
         /// <summary>
-        /// Subscription id
+        /// Unique subscription id
         /// </summary>
         public int Id { get; }
+
         /// <summary>
         /// Exception event
         /// </summary>
@@ -23,25 +24,28 @@ namespace CryptoExchange.Net.Sockets
         public Action<MessageEvent> MessageHandler { get; set; }
 
         /// <summary>
-        /// Request object
+        /// The request object send when subscribing on the server. Either this or the `Identifier` property should be set
         /// </summary>
         public object? Request { get; set; }
+
         /// <summary>
-        /// Subscription identifier
+        /// The subscription identifier, used instead of a `Request` object to identify the subscription
         /// </summary>
         public string? Identifier { get; set; }
+
         /// <summary>
-        /// Is user subscription or generic
+        /// Whether this is a user subscription or an internal listener
         /// </summary>
         public bool UserSubscription { get; set; }
         
         /// <summary>
-        /// If the subscription has been confirmed
+        /// If the subscription has been confirmed to be subscribed by the server
         /// </summary>
         public bool Confirmed { get; set; }
 
         /// <summary>
-        /// Cancellation token registration, should be disposed when subscription is closed
+        /// Cancellation token registration, should be disposed when subscription is closed. Used for closing the subscription with 
+        /// a provided cancelation token
         /// </summary>
         public CancellationTokenRegistration? CancellationTokenRegistration { get; set; }
 
@@ -55,7 +59,7 @@ namespace CryptoExchange.Net.Sockets
         }
 
         /// <summary>
-        /// Create SocketSubscription for a request
+        /// Create SocketSubscription for a subscribe request
         /// </summary>
         /// <param name="id"></param>
         /// <param name="request"></param>
diff --git a/CryptoExchange.Net/Sockets/UpdateSubscription.cs b/CryptoExchange.Net/Sockets/UpdateSubscription.cs
index dd8b029..39fa5d0 100644
--- a/CryptoExchange.Net/Sockets/UpdateSubscription.cs
+++ b/CryptoExchange.Net/Sockets/UpdateSubscription.cs
@@ -23,7 +23,7 @@ namespace CryptoExchange.Net.Sockets
 
         /// <summary>
         /// Event when the connection is closed. This event happens when reconnecting/resubscribing has failed too often based on the <see cref="SocketClientOptions.MaxReconnectTries"/> and <see cref="SocketClientOptions.MaxResubscribeTries"/> options,
-        /// or <see cref="SocketClientOptions.AutoReconnect"/> is false
+        /// or <see cref="SocketClientOptions.AutoReconnect"/> is false. The socket will not be reconnected
         /// </summary>
         public event Action ConnectionClosed
         {
@@ -33,8 +33,8 @@ namespace CryptoExchange.Net.Sockets
 
         /// <summary>
         /// Event when the connection is restored. Timespan parameter indicates the time the socket has been offline for before reconnecting. 
-        /// Note that when the executing code is suspended and resumed at a later period (for example laptop going to sleep) the disconnect time will be incorrect as the diconnect
-        /// will only be detected after resuming. This will lead to an incorrect disconnected timespan.
+        /// Note that when the executing code is suspended and resumed at a later period (for example, a laptop going to sleep) the disconnect time will be incorrect as the diconnect
+        /// will only be detected after resuming the code, so the initial disconnect time is lost. Use the timespan only for informational purposes.
         /// </summary>
         public event Action<TimeSpan> ConnectionRestored
         {