diff --git a/README.md b/README.md index 9fc02b0..3af59ce 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,15 @@ to run on Windows 7 machines. ## Installation 1. Clone the repository and go to its folder. -2. Compile the code using Visual Studio, MSBuild or via this handy script file: +2. Clone mdvx/CryptoExchange.Net and mdvx/Binance.Net from + * https://github.com/mdvx/CryptoExchange.Net + * https://github.com/mdvx/Binance.Net +3. Build the Release target for these two Projects, the forks include Strong name signing for Excel. - `build.cmd` +4. Compile the code using Visual Studio + Open project in Visual Studio and fix up the references to Binance.net and CryptoExchange.Net -3. Register the COM server by running the following script in admin command prompt: +5. Register the COM server by running the following script in admin command prompt: `register.cmd` @@ -20,11 +24,15 @@ Once the RTD server has been installed, you can use it from Excel via the RTD ma This is the syntax: * `=RTD("crypto",,"GDAX", instrument, field)` + +* `=RTD("crypto",,"CLOCK")` // A high fidelity clock update every few milliseconds + * `=RTD("crypto",,"BINANCE", instrument, field)` * `=RTD("crypto",,"BINANCE_DEPTH",instrument, field,depth)` // depth is 0-9 * `=RTD("crypto",,"BINANCE_TRADE",instrument, field)` * `=RTD("crypto",,"BINANCE_CANDLE",instrument, interval, field)` // interval is 0-11 -* `=RTD("crypto",,"BINANCE_HISTORY",instrument)` // not yet working +* `=RTD("crypto",,"BINANCE_HISTORY",instrument)` // Returns and string Array +* `=RTD("crypto",,"BINANCE", "DRIFT")` // drift between Binance Server and RTD Server *All* currency pairs traded on GDAX are supported, including the main ones: * BTC-USD diff --git a/doc/crypto.xlsx b/doc/crypto.xlsx index 20f26e5..68d8757 100644 Binary files a/doc/crypto.xlsx and b/doc/crypto.xlsx differ diff --git a/src/CryptoRtd/BinanceAdapter.cs b/src/CryptoRtd/BinanceAdapter.cs index 3c8b399..86fd1f0 100644 --- a/src/CryptoRtd/BinanceAdapter.cs +++ b/src/CryptoRtd/BinanceAdapter.cs @@ -3,6 +3,7 @@ using CryptoExchange.Net; using Newtonsoft.Json; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; @@ -21,6 +22,8 @@ class BinanceAdapter public const string BINANCE_HISTORY = "BINANCE_HISTORY"; private SubscriptionManager _subMgr; + private BinanceExchangeInfo _exchangeInfo; + private int _drift; BinanceSocketClient socketClient; private Dictionary SubscribedTick = new Dictionary(); @@ -58,13 +61,24 @@ public BinanceAdapter(SubscriptionManager subMgr) }); socketClient = new BinanceSocketClient(); + + _exchangeInfo = QueryExchangeInfo(); + _drift = QueryDrift(); } internal void UnsubscribeAllStreams() { socketClient.UnsubscribeAllStreams(); } + private object PreCacheResult(string origin, string instrument, string field, object value) + { + lock (_subMgr) + { + _subMgr.PreSet(SubscriptionManager.FormatPath(origin, string.Empty, instrument, field), value); + } + return value; + } private object CacheResult(string origin, string instrument, string field, object value) { lock (_subMgr) @@ -84,19 +98,41 @@ private object CacheResult(string origin, string instrument, string field, int d return value; } - public object Subscribe(string origin, string instrument, string field, int num) + public object Subscribe(int topicId, string origin, string instrument, string field, int num) { + + lock (_subMgr) + { + if (num < 0) + _subMgr.Subscribe(topicId, origin, String.Empty, instrument, field); + else + _subMgr.Subscribe(topicId, origin, String.Empty, instrument, field, num); + } + switch (origin) { case BINANCE: - if (field.Equals(RtdFields.DRIFT)) - return SubscribeDrift(); - else - return SubscribeTick(instrument, field); + SubscribeTick(instrument, field); + + switch (field) + { + case RtdFields.BASE_ASSET: + case RtdFields.BASE_ASSET_PRECISION: + //case RtdFields.FILTERS: + case RtdFields.ICEBERG_ALLOWED: + case RtdFields.NAME: + case RtdFields.ORDER_TYPES: + case RtdFields.QUOTE_ASSET: + case RtdFields.QUOTE_ASSET_PRECISION: + case RtdFields.STATUS: + case RtdFields.EXCHANGE_SYMBOLS: + return _subMgr.GetValue(topicId); + } + break; case BINANCE_24H: Get24HPriceAsync(instrument, field); - return SubscriptionManager.UninitializedValue; + break; case BINANCE_CANDLE: return SubscribeCandle(instrument, field, num); @@ -114,6 +150,7 @@ public object Subscribe(string origin, string instrument, string field, int num) default: return "Unsupported origin: " + origin; } + return _subMgr.GetValue(topicId); } [Obsolete] @@ -145,18 +182,71 @@ private async void GetPriceAsync(string instrument, string field) CacheResult(BINANCE, instrument, field, result.Error.Message); } } + public object QueryInfo(string stat) + { + switch(stat) + { + case RtdFields.DRIFT: return _drift; + case RtdFields.EXCHANGE_TIME: return _exchangeInfo.ServerTime.ToLocalTime(); + case RtdFields.EXCHANGE_TIMEZONE: return _exchangeInfo.TimeZone; + case RtdFields.EXCHANGE_SYMBOLS: return _exchangeInfo.Symbols; + //case RtdFields.EXCHANGE_RATE_LIMITS: return _exchangeInfo.RateLimits; + //case RtdFields.EXCHANGE_FILTERS: return _exchangeInfo.ExchangeFilters; + } + return SubscriptionManager.UnsupportedField; + } - private TimeSpan SubscribeDrift() + public int QueryDrift() { using (var client = new BinanceClient()) { - DateTime server = client.GetServerTime().Data.ToLocalTime(); - DateTime after = DateTime.Now.ToLocalTime(); + DateTime server = client.GetServerTime().Data; + DateTime after = DateTime.Now; TimeSpan drift = after.Subtract(server); - CacheResult(BINANCE, null, RtdFields.DRIFT, drift); + CacheResult(BINANCE, null, RtdFields.DRIFT, drift.Milliseconds); - return drift; + return drift.Milliseconds; + } + } + private BinanceExchangeInfo QueryExchangeInfo() + { + using (var client = new BinanceClient()) + { + CallResult info = client.GetExchangeInfo(); + if (info.Success) + { + _exchangeInfo = info.Data; + + var symbols = new List(_exchangeInfo.Symbols.Length); + foreach (BinanceSymbol symbol in _exchangeInfo.Symbols) + { + symbols.Add(symbol.Name); + + PreCacheResult(BINANCE, symbol.Name, RtdFields.BASE_ASSET, symbol.BaseAsset); + PreCacheResult(BINANCE, symbol.Name, RtdFields.BASE_ASSET_PRECISION, symbol.BaseAssetPrecision); + //PreCacheResult(BINANCE, symbol.Name, RtdFields.FILTERS, MakeStringArray(symbol.Filters)); + PreCacheResult(BINANCE, symbol.Name, RtdFields.ICEBERG_ALLOWED, symbol.IceBergAllowed); + PreCacheResult(BINANCE, symbol.Name, RtdFields.NAME, symbol.Name); + PreCacheResult(BINANCE, symbol.Name, RtdFields.ORDER_TYPES, MakeStringArray(symbol.OrderTypes)); + PreCacheResult(BINANCE, symbol.Name, RtdFields.QUOTE_ASSET, symbol.QuoteAsset); + PreCacheResult(BINANCE, symbol.Name, RtdFields.QUOTE_ASSET_PRECISION, symbol.QuoteAssetPrecision); + PreCacheResult(BINANCE, symbol.Name, RtdFields.STATUS, symbol.Status); + } + PreCacheResult(BINANCE, String.Empty, RtdFields.EXCHANGE_SYMBOLS, MakeStringArray(symbols)); + + return _exchangeInfo; + } + else + { + Console.Out.WriteLine(info.Error); + return null; + } } + + } + private string MakeStringArray(IEnumerable array) + { + return JsonConvert.SerializeObject(array); } private async void Get24HPriceAsync(string instrument, string field) @@ -207,25 +297,32 @@ private async void Get24HPriceAsync(string instrument, string field) private void CacheTick(BinanceStreamTick data) { var instrument = data.Symbol; - CacheResult( BINANCE, instrument, RtdFields.FIRST_ID, data.FirstTradeId); - CacheResult( BINANCE, instrument, RtdFields.LAST_ID, data.LastTradeId); - CacheResult( BINANCE, instrument, RtdFields.QUOTE_VOL, data.TotalTradedQuoteAssetVolume); - CacheResult( BINANCE, instrument, RtdFields.VOL, data.TotalTradedBaseAssetVolume); + lock (_subMgr) + { + CacheResult(BINANCE, instrument, RtdFields.FIRST_ID, data.FirstTradeId); + CacheResult(BINANCE, instrument, RtdFields.LAST_ID, data.LastTradeId); + CacheResult(BINANCE, instrument, RtdFields.QUOTE_VOL, data.TotalTradedQuoteAssetVolume); + CacheResult(BINANCE, instrument, RtdFields.VOL, data.TotalTradedBaseAssetVolume); + + CacheResult(BINANCE, instrument, RtdFields.ASK, data.BestAskPrice); + CacheResult(BINANCE, instrument, RtdFields.ASK_SIZE, data.BestAskQuantity); + CacheResult(BINANCE, instrument, RtdFields.BID, data.BestBidPrice); + CacheResult(BINANCE, instrument, RtdFields.BID_SIZE, data.BestBidQuantity); - CacheResult( BINANCE, instrument, RtdFields.ASK, data.BestAskPrice); - CacheResult( BINANCE, instrument, RtdFields.ASK_SIZE, data.BestAskQuantity); - CacheResult( BINANCE, instrument, RtdFields.BID, data.BestBidPrice); - CacheResult( BINANCE, instrument, RtdFields.BID_SIZE, data.BestBidQuantity); + CacheResult(BINANCE, instrument, RtdFields.LOW, data.LowPrice); + CacheResult(BINANCE, instrument, RtdFields.HIGH, data.HighPrice); - CacheResult( BINANCE, instrument, RtdFields.LOW, data.LowPrice); - CacheResult( BINANCE, instrument, RtdFields.HIGH, data.HighPrice); + CacheResult(BINANCE, instrument, RtdFields.VWAP, data.WeightedAverage); + CacheResult(BINANCE, instrument, RtdFields.PRICE_PCT, data.PriceChangePercentage / 100); + CacheResult(BINANCE, instrument, RtdFields.PRICE_CHG, data.PriceChange); + CacheResult(BINANCE, instrument, RtdFields.TRADES, data.TotalTrades); - CacheResult( BINANCE, instrument, RtdFields.VWAP, data.WeightedAverage); - CacheResult( BINANCE, instrument, RtdFields.PRICE_PCT, data.PriceChangePercentage / 100); - CacheResult( BINANCE, instrument, RtdFields.PRICE_CHG, data.PriceChange); - CacheResult( BINANCE, instrument, RtdFields.TRADES, data.TotalTrades); + CacheResult(BINANCE, instrument, RtdFields.OPEN_TIME, data.StatisticsOpenTime.ToLocalTime()); + CacheResult(BINANCE, instrument, RtdFields.CLOSE_TIME, data.StatisticsCloseTime.ToLocalTime()); - CacheResult( BINANCE, instrument, RtdFields.SPREAD, data.BestAskPrice - data.BestBidPrice); + // A calculated field + CacheResult(BINANCE, instrument, RtdFields.SPREAD, data.BestAskPrice - data.BestBidPrice); + } } private object DecodeTick(BinanceStreamTick data, string field) @@ -272,43 +369,36 @@ private object SubscribeTick(string instrument, string field) else { SubscribedTick.Add(instrument, true); - var successSymbol = socketClient.SubscribeToSymbolTicker(instrument, (BinanceStreamTick data) => - { - TickCache[key] = data; - CacheTick(data); - }); + Task.Run(() => + socketClient.SubscribeToSymbolTicker(instrument, (BinanceStreamTick data) => + { + TickCache[key] = data; + CacheTick(data); + }) + ); return SubscriptionManager.UninitializedValue; } } // Order Book private void CacheOrderBook(BinanceStreamOrderBook stream) { - var instrument = stream.Symbol; - var bidCount = stream.Bids.Count; - var askCount = stream.Asks.Count; - - - for(int depth = 0; depth < bidCount; depth++) + lock (_subMgr) { - CacheResult(BINANCE_DEPTH, instrument, RtdFields.BID_DEPTH, depth, stream.Bids[depth].Price); - CacheResult(BINANCE_DEPTH, instrument, RtdFields.BID_DEPTH_SIZE, depth, stream.Bids[depth].Quantity); - } - //for (int depth = bidCount; depth < 10; depth++) - //{ - // CacheResult(BINANCE, instrument, RtdFields.BID_DEPTH, depth, SubscriptionManager.UninitializedValue); - // CacheResult(BINANCE, instrument, RtdFields.BID_DEPTH_SIZE, depth, SubscriptionManager.UninitializedValue); - //} + CacheResult(BINANCE_DEPTH, stream.Symbol, RtdFields.SYMBOL, stream.Symbol); + CacheResult(BINANCE_DEPTH, stream.Symbol, RtdFields.LAST_UPDATE_ID, stream.LastUpdateId); - for (int depth = 0; depth < askCount; depth++) - { - CacheResult(BINANCE_DEPTH, instrument, RtdFields.ASK_DEPTH, depth, stream.Asks[depth].Price); - CacheResult(BINANCE_DEPTH, instrument, RtdFields.ASK_DEPTH_SIZE, depth, stream.Asks[depth].Quantity); + for (int depth = 0; depth < stream.Bids.Count; depth++) + { + CacheResult(BINANCE_DEPTH, stream.Symbol, RtdFields.BID_DEPTH, depth, stream.Bids[depth].Price); + CacheResult(BINANCE_DEPTH, stream.Symbol, RtdFields.BID_DEPTH_SIZE, depth, stream.Bids[depth].Quantity); + } + + for (int depth = 0; depth < stream.Asks.Count; depth++) + { + CacheResult(BINANCE_DEPTH, stream.Symbol, RtdFields.ASK_DEPTH, depth, stream.Asks[depth].Price); + CacheResult(BINANCE_DEPTH, stream.Symbol, RtdFields.ASK_DEPTH_SIZE, depth, stream.Asks[depth].Quantity); + } } - //for (int depth = askCount; depth < 10; depth++) - //{ - // CacheResult(BINANCE, instrument, RtdFields.ASK_DEPTH, depth, SubscriptionManager.UninitializedValue); - // CacheResult(BINANCE, instrument, RtdFields.ASK_DEPTH_SIZE, depth, SubscriptionManager.UninitializedValue); - //} } private object DecodeOrderBook(BinanceStreamOrderBook stream, string field, int depth) { @@ -317,6 +407,12 @@ private object DecodeOrderBook(BinanceStreamOrderBook stream, string field, int switch (field) { + case RtdFields.SYMBOL: + return stream.Symbol; + + case RtdFields.LAST_UPDATE_ID: + return stream.LastUpdateId; + case RtdFields.ASK_DEPTH: if (depth >= askCount) return SubscriptionManager.UninitializedValue; @@ -371,17 +467,20 @@ private object SubscribeOrderBook(string instrument, string field, int depth) private void CacheTrade(BinanceStreamAggregatedTrade stream) { var instrument = stream.Symbol; - //CacheResult(BINANCE_TRADE, instrument, RtdFields.TRADE_ID, stream.TradeId); - CacheResult(BINANCE_TRADE, instrument, RtdFields.TRADE_ID, stream.AggregatedTradeId); - CacheResult(BINANCE_TRADE, instrument, RtdFields.PRICE, stream.Price); - CacheResult(BINANCE_TRADE, instrument, RtdFields.QUANTITY, stream.Quantity); - //CacheResult(BINANCE_TRADE, instrument, RtdFields.BUYER_ORDER_ID, stream.BuyerOrderId); - //CacheResult(BINANCE_TRADE, instrument, RtdFields.SELLER_ORDER_ID, stream.SellerOrderId); - CacheResult(BINANCE_TRADE, instrument, RtdFields.FIRST_ID, stream.FirstTradeId); - CacheResult(BINANCE_TRADE, instrument, RtdFields.LAST_ID, stream.LastTradeId); - CacheResult(BINANCE_TRADE, instrument, RtdFields.TRADE_TIME, stream.TradeTime.ToLocalTime()); - CacheResult(BINANCE_TRADE, instrument, RtdFields.BUYER_IS_MAKER, stream.BuyerIsMaker); - CacheResult(BINANCE_TRADE, instrument, RtdFields.IGNORE, stream.Ignore); + lock (_subMgr) + { + //CacheResult(BINANCE_TRADE, instrument, RtdFields.TRADE_ID, stream.TradeId); + CacheResult(BINANCE_TRADE, instrument, RtdFields.TRADE_ID, stream.AggregatedTradeId); + CacheResult(BINANCE_TRADE, instrument, RtdFields.PRICE, stream.Price); + CacheResult(BINANCE_TRADE, instrument, RtdFields.QUANTITY, stream.Quantity); + //CacheResult(BINANCE_TRADE, instrument, RtdFields.BUYER_ORDER_ID, stream.BuyerOrderId); + //CacheResult(BINANCE_TRADE, instrument, RtdFields.SELLER_ORDER_ID, stream.SellerOrderId); + CacheResult(BINANCE_TRADE, instrument, RtdFields.FIRST_ID, stream.FirstTradeId); + CacheResult(BINANCE_TRADE, instrument, RtdFields.LAST_ID, stream.LastTradeId); + CacheResult(BINANCE_TRADE, instrument, RtdFields.TRADE_TIME, stream.TradeTime.ToLocalTime()); + CacheResult(BINANCE_TRADE, instrument, RtdFields.BUYER_IS_MAKER, stream.BuyerIsMaker); + CacheResult(BINANCE_TRADE, instrument, RtdFields.IGNORE, stream.Ignore); + } } private object DecodeTrade(BinanceStreamAggregatedTrade stream, string field) { @@ -432,26 +531,30 @@ private void CacheCandle(BinanceStreamKlineData stream, int interval) { var instrument = stream.Symbol; var data = stream.Data; - CacheResult(BINANCE_CANDLE, instrument, RtdFields.EVENT, interval,stream.Event); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.EVENT_TIME, interval, stream.EventTime.ToLocalTime()); - - CacheResult(BINANCE_CANDLE, instrument, RtdFields.FIRST_ID, interval, data.FirstTrade); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.LAST_ID, interval, data.LastTrade); - - CacheResult(BINANCE_CANDLE, instrument, RtdFields.HIGH, interval, data.High); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.LOW, interval, data.Low); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.OPEN_TIME, interval, data.OpenTime.ToLocalTime()); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.OPEN, interval, data.Open); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.CLOSE_TIME, interval, data.CloseTime.ToLocalTime()); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.CLOSE, interval, data.Close); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.FINAL, interval, data.Final); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.INTERVAL, interval, data.Interval.ToString()); - - CacheResult(BINANCE_CANDLE, instrument, RtdFields.TRADES, interval, data.TradeCount); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.QUOTE_VOL, interval, data.QuoteAssetVolume); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.VOL, interval, data.Volume); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.TAKE_BUY_VOL, interval, data.TakerBuyBaseAssetVolume); - CacheResult(BINANCE_CANDLE, instrument, RtdFields.TAKE_BUY_QUOTE_VOL, interval, data.TakerBuyQuoteAssetVolume); + lock (_subMgr) + { + + CacheResult(BINANCE_CANDLE, instrument, RtdFields.EVENT, interval, stream.Event); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.EVENT_TIME, interval, stream.EventTime.ToLocalTime()); + + CacheResult(BINANCE_CANDLE, instrument, RtdFields.FIRST_ID, interval, data.FirstTrade); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.LAST_ID, interval, data.LastTrade); + + CacheResult(BINANCE_CANDLE, instrument, RtdFields.HIGH, interval, data.High); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.LOW, interval, data.Low); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.OPEN_TIME, interval, data.OpenTime.ToLocalTime()); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.OPEN, interval, data.Open); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.CLOSE_TIME, interval, data.CloseTime.ToLocalTime()); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.CLOSE, interval, data.Close); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.FINAL, interval, data.Final); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.INTERVAL, interval, data.Interval.ToString()); + + CacheResult(BINANCE_CANDLE, instrument, RtdFields.TRADES, interval, data.TradeCount); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.QUOTE_VOL, interval, data.QuoteAssetVolume); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.VOL, interval, data.Volume); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.TAKE_BUY_VOL, interval, data.TakerBuyBaseAssetVolume); + CacheResult(BINANCE_CANDLE, instrument, RtdFields.TAKE_BUY_QUOTE_VOL, interval, data.TakerBuyQuoteAssetVolume); + } } private object DecodeCandle(BinanceStreamKlineData stream, string field) { diff --git a/src/CryptoRtd/CryptoRtd.csproj b/src/CryptoRtd/CryptoRtd.csproj index 635faa8..9045400 100644 --- a/src/CryptoRtd/CryptoRtd.csproj +++ b/src/CryptoRtd/CryptoRtd.csproj @@ -4,7 +4,7 @@ Debug AnyCPU - {95F50618-AA6C-4551-8CD7-49E04C0D5CE9} + {5B968A88-46DB-429C-A10E-AE86018FBAEB} Library Properties CryptoRtd diff --git a/src/CryptoRtd/CryptoRtdServer.cs b/src/CryptoRtd/CryptoRtdServer.cs index 3912498..7758024 100644 --- a/src/CryptoRtd/CryptoRtdServer.cs +++ b/src/CryptoRtd/CryptoRtdServer.cs @@ -108,11 +108,21 @@ object IRtdServer.ConnectData (int topicId, { case CLOCK: _subMgr.Subscribe(topicId, CLOCK); - break; + return DateTime.Now.ToLocalTime(); + } + return "Unsupported origin: " + origin; + } + else if (strings.Length == 2) + { + string origin = strings.GetValue(0).ToString().ToUpperInvariant(); + string stat = strings.GetValue(1).ToString().ToUpperInvariant(); - default: - return "Unsupported origin: " + origin; + switch(origin) + { + case BinanceAdapter.BINANCE: + return _binanceAdapter.QueryInfo(stat); } + return "Unsupported origin: " + origin; } else if (strings.Length >= 3) { @@ -130,10 +140,6 @@ object IRtdServer.ConnectData (int topicId, switch (origin) { - case CLOCK: - _subMgr.Subscribe(topicId, CLOCK); - break; - case GDAX: lock (_subMgr) { @@ -141,7 +147,7 @@ object IRtdServer.ConnectData (int topicId, _subMgr.Subscribe(topicId,origin,String.Empty,instrument,field); } Task.Run(() => SubscribeGdaxWebSocketToTicker(topicId,instrument)); // dont block excel - break; + return SubscriptionManager.UninitializedValue; case BinanceAdapter.BINANCE: case BinanceAdapter.BINANCE_24H: @@ -153,18 +159,11 @@ object IRtdServer.ConnectData (int topicId, if (strings.Length > 3) Int32.TryParse(strings.GetValue(3).ToString(), out depth); - lock (_subMgr) - { - if (depth < 0) - _subMgr.Subscribe(topicId,origin,String.Empty,instrument,field); - else - _subMgr.Subscribe(topicId,origin,String.Empty,instrument,field,depth); - } - return _binanceAdapter.Subscribe(origin, instrument, field, depth); + return _binanceAdapter.Subscribe(topicId, origin, instrument, field, depth); default: return "ERROR: Unsupported origin: " + origin; } - return SubscriptionManager.UninitializedValue; + //return SubscriptionManager.UninitializedValue; } return "ERROR: Expected: origin, vendor, instrument, field, [depth]"; } @@ -257,15 +256,15 @@ private void OnWebSocketMessageReceived (object sender, WebSocketMessageEventArg lock (_subMgr) { - _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "BID"),jobj.best_bid); - _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "ASK"),jobj.best_ask); - _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "LAST_SIZE"),jobj.last_size); + _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, RtdFields.BID),jobj.best_bid); + _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, RtdFields.ASK),jobj.best_ask); + _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, RtdFields.LAST_SIZE),jobj.last_size); _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "LAST_PRICE"),jobj.price); _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "LAST_SIDE"),jobj.side); - _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "high_24h"), jobj.high_24h); - _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "low_24h"), jobj.low_24h); - _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "open_24h"), jobj.open_24h); - _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "volume_24h"), jobj.volume_24h); + _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "HIGH_24H"), jobj.high_24h); + _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "LOW_24H"), jobj.low_24h); + _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "OPEN_24H"), jobj.open_24h); + _subMgr.Set(SubscriptionManager.FormatPath(origin, String.Empty, prod, "VOLUME_24H"), jobj.volume_24h); } } } @@ -328,14 +327,21 @@ class SubscriptionManager readonly Dictionary _subByPath; readonly Dictionary _subByTopicId; + readonly Dictionary _dirtyMap; public SubscriptionManager () { _subByPath = new Dictionary(); _subByTopicId = new Dictionary(); + _dirtyMap = new Dictionary(); } - public bool IsDirty { get; private set; } + public bool IsDirty { + get + { + return _dirtyMap.Count > 0; + } + } public void Subscribe(int topicId, string origin) { var subInfo = new SubInfo(topicId, origin); @@ -345,24 +351,26 @@ public void Subscribe(int topicId, string origin) } public void Subscribe(int topicId, string origin, string vendor, string instrument, string field) { - var subInfo = new SubInfo( - topicId, - FormatPath(origin, vendor, instrument, field)); + var path = FormatPath(origin, vendor, instrument, field); + if (_subByPath.TryGetValue(path, out SubInfo subInfo)) + subInfo.TopicId = topicId; + else + subInfo = new SubInfo(topicId, path); _subByTopicId[topicId] = subInfo; _subByPath[subInfo.Path] = subInfo; } public void Subscribe(int topicId, string origin, string vendor, string instrument, string field, int depth) { - var subInfo = new SubInfo( - topicId, - FormatPath(origin, vendor, instrument, field, depth)); + var path = FormatPath(origin, vendor, instrument, field, depth); + if (!_subByTopicId.TryGetValue(topicId, out SubInfo subInfo)) + subInfo = new SubInfo(topicId, path); _subByTopicId[topicId] = subInfo; _subByPath[subInfo.Path] = subInfo; } - public void Unsubscribe (int topicId) + public void Unsubscribe(int topicId) { SubInfo subInfo; if (_subByTopicId.TryGetValue(topicId, out subInfo)) @@ -376,18 +384,15 @@ public List GetUpdatedValues () { var updated = new List(_subByTopicId.Count); - // For simplicity, let's just do a linear scan - foreach (var subInfo in _subByTopicId.Values) + lock (_dirtyMap) { - if (subInfo.IsDirty) + foreach (var subInfo in _dirtyMap.Values) { updated.Add(new UpdatedValue(subInfo.TopicId, subInfo.Value)); - subInfo.IsDirty = false; } + _dirtyMap.Clear(); } - IsDirty = false; - return updated; } @@ -399,7 +404,8 @@ public void Set(string path, object value) if (value != subInfo.Value) { subInfo.Value = value; - IsDirty = true; + lock (_dirtyMap) + _dirtyMap[subInfo.TopicId] = subInfo; } } } @@ -411,53 +417,75 @@ public void Set(int topicId, object value) if (value != subInfo.Value) { subInfo.Value = value; - IsDirty = true; + lock(_dirtyMap) + _dirtyMap[subInfo.TopicId] = subInfo; } } } - + public void PreSet(string path, object value) + { + SubInfo subInfo; + if (_subByPath.TryGetValue(path, out subInfo)) + { + if (value != subInfo.Value) + { + subInfo.Value = value; + lock (_dirtyMap) + _dirtyMap[subInfo.TopicId] = subInfo; + } + } + else + { + _subByPath[path] = new SubInfo(path, value); + } + } public static string FormatPath(string origin, string vendor, string instrument, string field) { return string.Format("{0}/{1}/{2}/{3}", origin.ToUpperInvariant(), - vendor.ToUpperInvariant(), - instrument.ToUpperInvariant(), - field.ToUpperInvariant()); + vendor?.ToUpperInvariant(), + instrument?.ToUpperInvariant(), + field?.ToUpperInvariant()); } public static string FormatPath(string origin, string vendor, string instrument, string field, int num) { return string.Format("{0}/{1}/{2}/{3}/{4}", origin.ToUpperInvariant(), - vendor.ToUpperInvariant(), - instrument.ToUpperInvariant(), - field.ToUpperInvariant(), + vendor?.ToUpperInvariant(), + instrument?.ToUpperInvariant(), + field?.ToUpperInvariant(), num); // can be depth or limit } - class SubInfo - { - public int TopicId { get; private set; } - public string Path { get; private set; } - private object _value; + internal object GetValue(string origin, string vendor, string instrument, string field) + { + return _subByPath[FormatPath(origin, vendor, instrument, field)].Value; + } + public object GetValue(int topicId) + { + if (_subByTopicId.TryGetValue(topicId, out SubInfo sub)) + return sub.Value; - public object Value - { - get { return _value; } - set - { - _value = value; - IsDirty = true; - } - } + return UninitializedValue; + } - public bool IsDirty { get; set; } + class SubInfo + { + public int TopicId { get; set; } + public string Path { get; private set; } + public object Value { get; set; } public SubInfo (int topicId, string path) { TopicId = topicId; Path = path; Value = UninitializedValue; - IsDirty = false; + } + public SubInfo(string path, object value) + { + TopicId = -1; // this a PreSet or PreCache + Path = path; + Value = value; } } } diff --git a/src/CryptoRtd/RtdFields.cs b/src/CryptoRtd/RtdFields.cs index a35dcef..8cc7164 100644 --- a/src/CryptoRtd/RtdFields.cs +++ b/src/CryptoRtd/RtdFields.cs @@ -10,10 +10,28 @@ public class RtdFields { public const string BINANCE = "BINANCE"; + + //Info + public const string DRIFT = "DRIFT"; + public const string EXCHANGE_TIME = "EXCHANGE_TIME"; + public const string EXCHANGE_TIMEZONE = "EXCHANGE_TIMEZONE"; + public const string EXCHANGE_SYMBOLS = "EXCHANGE_SYMBOLS"; + //public const string EXCHANGE_RATE_LIMITS = "EXCHANGE_RATE_LIMITS"; + //public const string EXCHANGE_FILTERS = "EXCHANGE_FILTERS"; + + public const string BASE_ASSET = "BASE_ASSET"; + public const string BASE_ASSET_PRECISION = "BASE_ASSET_PRECISION"; + //public const string FILTERS = "FILTERS"; + public const string ICEBERG_ALLOWED = "ICEBERG_ALLOWED"; + public const string NAME = "NAME"; + public const string ORDER_TYPES = "ORDER_TYPES"; + public const string QUOTE_ASSET ="QUOTE_ASSET"; + public const string QUOTE_ASSET_PRECISION = "QUOTE_ASSET_PRECISION"; + public const string STATUS = "STATUS"; + // Price public const string PRICE = "PRICE"; public const string SYMBOL = "SYMBOL"; - public const string DRIFT = "DRIFT"; public static readonly string[] PRICE_FIELDS = { PRICE, SYMBOL, DRIFT }; // 24Price @@ -44,11 +62,12 @@ public class RtdFields OPEN, OPEN_TIME, CLOSE, CLOSE_TIME,VWAP, PRICE_PCT, PRICE_CHG, TRADES, SPREAD }; // Depth + public const string LAST_UPDATE_ID = "LAST_UPDATE_ID"; public const string ASK_DEPTH = "ASK_DEPTH"; public const string ASK_DEPTH_SIZE = "ASK_DEPTH_SIZE"; public const string BID_DEPTH = "BID_DEPTH"; public const string BID_DEPTH_SIZE = "BID_DEPTH_SIZE"; - public static readonly string[] DEPTH = { SYMBOL, ASK_DEPTH, ASK_DEPTH_SIZE, BID_DEPTH, BID_DEPTH_SIZE }; + public static readonly string[] DEPTH = { SYMBOL, LAST_UPDATE_ID, ASK_DEPTH, ASK_DEPTH_SIZE, BID_DEPTH, BID_DEPTH_SIZE }; // Trade diff --git a/src/crypto-rtd.sln b/src/crypto-rtd.sln index 3a8acca..48bdea4 100644 --- a/src/crypto-rtd.sln +++ b/src/crypto-rtd.sln @@ -12,7 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\build.cmd = ..\build.cmd ..\doc\crypto-rtd-excel.png = ..\doc\crypto-rtd-excel.png ..\doc\crypto.xlsx = ..\doc\crypto.xlsx - ..\doc\crypto2.xlsx = ..\doc\crypto2.xlsx ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md ..\register.cmd = ..\register.cmd