protected static async Task TestRequest(IWebSocketClientConnection connection) { // Simple keep-alive mechanism using TestRequest/Heartbeat long nextExpectedCounter = 0; bool disconnect = false; do { await Task.Delay(_testRequestDelay); if (!connection.IsConnected) { break; } if (!connection.EnableTestRequest) { continue; } if (nextExpectedCounter > connection.receivedMessageCounter) { if (!disconnect) { connection.SendTestRequest(); disconnect = true; } else { // second chance before disconnecting await Task.Delay(_testRequestDelay); if (nextExpectedCounter > connection.receivedMessageCounter) { connection.OnLogEvent(LogStatusType.ERROR, "Websocket connection not responding"); connection.Shutdown(); break; } disconnect = false; } } else { disconnect = false; } // update expectation for next iteration nextExpectedCounter = connection.receivedMessageCounter + 1; } while (connection.IsConnected); }
private void OnBrokerNotification(object sender, SystemEventArgs evt) { IWebSocketClientConnection webSocketConnection = (IWebSocketClientConnection)sender; try { switch (evt.evtType) { case SystemEventType.LOGIN_OK: LogStatus(LogStatusType.INFO, "Processing after succesful LOGON"); this._myUserID = evt.json["UserID"].Value <ulong>(); // disable test request to avoid disconnection during the "slow" market data processing webSocketConnection.EnableTestRequest = false; StartInitialRequestsAfterLogon(webSocketConnection); break; case SystemEventType.MARKET_DATA_REQUEST_REJECT: LogStatus(LogStatusType.ERROR, "Unexpected Marketdata Request Reject"); webSocketConnection.Shutdown(); break; case SystemEventType.MARKET_DATA_FULL_REFRESH: { string symbol = evt.json["Symbol"].Value <string>(); // dump the order book LogStatus(LogStatusType.WARN, _allOrderBooks[symbol].ToString()); // bring back the testrequest keep-alive mechanism after processing the book webSocketConnection.EnableTestRequest = true; // run the trading strategy to buy and sell orders based on the top of the book _tradingStrategy.runStrategy(webSocketConnection, symbol); // TODO: remove the temp dump bellow this._vwapForTradingSym.PrintTradesAndTheVWAP(); // example how to notify the application to start //this._tradingStrategy.OnStart(webSocketConnection); } break; // --- Order Book Management Events --- case SystemEventType.ORDER_BOOK_CLEAR: { string symbol = evt.json["Symbol"].Value <string>(); OrderBook orderBook = null; if (_allOrderBooks.TryGetValue(symbol, out orderBook)) { orderBook.Clear(); } else { orderBook = new OrderBook(symbol); _allOrderBooks.Add(symbol, orderBook); } } break; case SystemEventType.ORDER_BOOK_NEW_ORDER: { string symbol = evt.json["Symbol"].Value <string>(); OrderBook orderBook = null; if (_allOrderBooks.TryGetValue(symbol, out orderBook)) { orderBook.AddOrder(evt.json); } else { LogStatus(LogStatusType.ERROR, "Order Book not found for Symbol " + symbol + " @ " + evt.evtType.ToString()); } } break; case SystemEventType.ORDER_BOOK_DELETE_ORDERS_THRU: { string symbol = evt.json["Symbol"].Value <string>(); OrderBook orderBook = null; if (_allOrderBooks.TryGetValue(symbol, out orderBook)) { orderBook.DeleteOrdersThru(evt.json); } else { LogStatus(LogStatusType.ERROR, "Order Book not found for Symbol " + symbol + " @ " + evt.evtType.ToString() ); } } break; case SystemEventType.ORDER_BOOK_DELETE_ORDER: { string symbol = evt.json["Symbol"].Value <string>(); OrderBook orderBook = null; if (_allOrderBooks.TryGetValue(symbol, out orderBook)) { orderBook.DeleteOrder(evt.json); } else { LogStatus(LogStatusType.ERROR, "Order Book not found for Symbol " + symbol + " @ " + evt.evtType.ToString() ); } } break; case SystemEventType.ORDER_BOOK_UPDATE_ORDER: { string symbol = evt.json["Symbol"].Value <string>(); OrderBook orderBook = null; if (_allOrderBooks.TryGetValue(symbol, out orderBook)) { orderBook.UpdateOrder(evt.json); } else { LogStatus(LogStatusType.ERROR, "Order Book not found for Symbol " + symbol + " @ " + evt.evtType.ToString() ); } } break; // ------------------------------------ case SystemEventType.TRADE_CLEAR: LogStatus(LogStatusType.WARN, "Receieved Market Data Event " + evt.evtType.ToString()); break; case SystemEventType.SECURITY_STATUS: { LogStatus(LogStatusType.WARN, "Receieved Market Data Event " + evt.evtType.ToString() + " " + (evt.json != null ? evt.json.ToString() : ".") ); SecurityStatus securityStatus = new SecurityStatus(); securityStatus.Market = evt.json["Market"].Value <string>(); securityStatus.Symbol = evt.json["Symbol"].Value <string>(); securityStatus.LastPx = evt.json["LastPx"].Value <ulong>(); securityStatus.HighPx = evt.json["HighPx"].Value <ulong>(); if (evt.json["BestBid"].Type != JTokenType.Null) { securityStatus.BestBid = evt.json["BestBid"].Value <ulong>(); } else { securityStatus.BestBid = 0; } if (evt.json["BestAsk"].Type != JTokenType.Null) { securityStatus.BestAsk = evt.json["BestAsk"].Value <ulong>(); } else { securityStatus.BestAsk = 0; } if (evt.json["LowPx"].Type != JTokenType.Null) { securityStatus.LowPx = evt.json["LowPx"].Value <ulong>(); } else { securityStatus.LowPx = 0; } securityStatus.SellVolume = evt.json["SellVolume"].Value <ulong>(); securityStatus.BuyVolume = evt.json["BuyVolume"].Value <ulong>(); // update the security status information string securityKey = securityStatus.Market + ":" + securityStatus.Symbol; _securityStatusEntries[securityKey] = securityStatus; // update the strategy when a new market information arrives _tradingStrategy.runStrategy(webSocketConnection, _tradingSymbol); } break; case SystemEventType.TRADE: { JObject msg = evt.json; LogStatus(LogStatusType.WARN, "Receieved Market Data Event " + evt.evtType.ToString() + msg); _vwapForTradingSym.pushTrade( new ShortPeriodTickBasedVWAP.Trade( msg["TradeID"].Value <ulong>(), msg["Symbol"].Value <string>(), msg["MDEntryPx"].Value <ulong>(), msg["MDEntrySize"].Value <ulong>(), String.Format("{0} {1}", msg["MDEntryDate"].Value <string>(), msg["MDEntryTime"].Value <string>()) ) ); /* * LogStatus( * LogStatusType.INFO, * String.Format( * "New Trade : VWAP = {0} | LastPx = {1} - {2} | Size = {3}", * _vwapForTradingSym.calculateVWAP(), * _vwapForTradingSym.getLastPx(), * msg["MDEntryPx"].Value<ulong>(), * msg["MDEntrySize"].Value<ulong>() * ) * ); */ } break; case SystemEventType.TRADING_SESSION_STATUS: break; case SystemEventType.MARKET_DATA_INCREMENTAL_REFRESH: LogStatus(LogStatusType.WARN, "Receieved Market Data Incremental Refresh : " + evt.evtType.ToString()); // update the strategy when an incremental message is processed _tradingStrategy.runStrategy(webSocketConnection, _tradingSymbol); break; // --- Order Entry Replies --- case SystemEventType.EXECUTION_REPORT: { LogStatus(LogStatusType.WARN, "Receieved " + evt.evtType.ToString() + "\n" + evt.json.ToString()); MiniOMS.IOrder order = ProcessExecutionReport(evt.json); _tradingStrategy.OnExecutionReport(webSocketConnection, order); } break; case SystemEventType.ORDER_LIST_RESPONSE: { // process the requested list of orders JObject msg = evt.json; LogStatus(LogStatusType.WARN, "Received " + evt.evtType.ToString() + " : " + "Page=" + msg["Page"].Value <string>() ); JArray ordersLst = msg["OrdListGrp"].Value <JArray>(); if (ordersLst != null && ordersLst.Count > 0) { var columns = msg["Columns"].Value <JArray>(); Dictionary <string, int> indexOf = new Dictionary <string, int>(); int index = 0; foreach (JToken col in columns) { indexOf.Add(col.Value <string>(), index++); } foreach (JArray data in ordersLst) { MiniOMS.Order order = new MiniOMS.Order(); order.ClOrdID = data[indexOf["ClOrdID"]].Value <string>(); order.OrderID = data[indexOf["OrderID"]].Value <ulong>(); order.Symbol = data[indexOf["Symbol"]].Value <string>(); order.Side = data[indexOf["Side"]].Value <char>(); order.OrdType = data[indexOf["OrdType"]].Value <char>(); order.OrdStatus = data[indexOf["OrdStatus"]].Value <char>(); order.AvgPx = data[indexOf["AvgPx"]].Value <ulong>(); order.Price = data[indexOf["Price"]].Value <ulong>(); order.OrderQty = data[indexOf["OrderQty"]].Value <ulong>(); order.OrderQty = data[indexOf["LeavesQty"]].Value <ulong>(); order.CumQty = data[indexOf["CumQty"]].Value <ulong>(); order.CxlQty = data[indexOf["CxlQty"]].Value <ulong>(); order.Volume = data[indexOf["Volume"]].Value <ulong>(); order.OrderDate = data[indexOf["OrderDate"]].Value <string>(); order.TimeInForce = data[indexOf["TimeInForce"]].Value <char>(); LogStatus(LogStatusType.WARN, "Adding Order to MiniOMS -> ClOrdID = " + order.ClOrdID.ToString() + " OrdStatus = " + order.OrdStatus ); try { _miniOMS.AddOrder(order); } catch (System.ArgumentException) { } } // check and request the next page if (ordersLst.Count >= msg["PageSize"].Value <int>()) { LogStatus(LogStatusType.INFO, "Requesting Page " + msg["Page"].Value <int>() + 1); SendRequestForOpenOrders(webSocketConnection, msg["Page"].Value <int>() + 1); } else { LogStatus(LogStatusType.INFO, "EOT - no more Order List pages to process."); // notify application that all requestes where replied, // assuming the ORDER_LIST_REQUEST was the last in the StartInitialRequestsAfterLogon //_tradingStrategy.OnStart(webSocketConnection); } } } break; case SystemEventType.BALANCE_RESPONSE: if (evt.json != null) { //JObject receivedBalances = evt.json[_brokerId.ToString()].Value<JObject>(); foreach (var rb in evt.json[_brokerId.ToString()].Value <JObject>()) { try { this._balances[rb.Key] = rb.Value.Value <ulong>(); } catch (System.OverflowException) { // TODO: find a better solution for this kind of conversion problem // {"4": {"BRL_locked": -1, "BTC_locked": 0, "BRL": 48460657965, "BTC": 50544897}, "MsgType": "U3", "ClientID": 90826379, "BalanceReqID": 3} this._balances[rb.Key] = 0; } } // update the strategy when the balance is updated _tradingStrategy.runStrategy(webSocketConnection, _tradingSymbol); } break; case SystemEventType.TRADE_HISTORY_RESPONSE: { JObject msg = evt.json; LogStatus(LogStatusType.WARN, "Received " + evt.evtType.ToString() + " : " + "Page=" + msg["Page"].Value <string>() ); /* * JArray all_trades = msg["TradeHistoryGrp"].Value<JArray>(); * * if (all_trades != null && all_trades.Count > 0) * { * var columns = msg["Columns"].Value<JArray>(); * Dictionary<string, int> indexOf = new Dictionary<string, int>(); * int index = 0; * foreach (JToken col in columns) * { * indexOf.Add(col.Value<string>(), index++); * } * * foreach (JArray trade in all_trades) * { * _vwapForTradingSym.pushTrade( * new ShortPeriodTickBasedVWAP.Trade( * trade[indexOf["TradeID"]].Value<ulong>(), * trade[indexOf["Market"]].Value<string>(), * trade[indexOf["Price"]].Value<ulong>(), * trade[indexOf["Size"]].Value<ulong>(), * trade[indexOf["Created"]].Value<string>() * ) * ); * } * * // check and request the next page * if (all_trades.Count >= msg["PageSize"].Value<int>()) * { * LogStatus(LogStatusType.INFO, "TODO: Requesting Page " + msg["Page"].Value<int>() + 1); * //TODO: create a function to call here and request a new page if requested period in minutes is not satified * } * else * { * LogStatus(LogStatusType.INFO, "EOT - no more Trade History pages to process."); * } * * LogStatus(LogStatusType.INFO, String.Format("VWAP = {0}", _vwapForTradingSym.calculateVWAP())); * } */ } // break; case SystemEventType.CLOSED: // notify the application the connection was broken //_tradingStrategy.OnClose(webSocketConnection); break; // Following events are ignored because inheritted behaviour is sufficient for this prototype case SystemEventType.OPENED: case SystemEventType.ERROR: case SystemEventType.LOGIN_ERROR: case SystemEventType.HEARTBEAT: break; default: LogStatus(LogStatusType.WARN, "Unhandled Broker Notification Event : " + evt.evtType.ToString()); break; } } catch (Exception ex) { LogStatus(LogStatusType.ERROR, " OnBrokerNotification Event Handler Error : " + ex.Message.ToString() + "\n" + ex.StackTrace ); } }
public void OnMessage(string message, IWebSocketClientConnection connection) { JObject msg = JsonConvert.DeserializeObject <JObject>(message); string msgType = msg["MsgType"].Value <string>(); switch (msgType) { case "BF": //Login response: { if (msg.GetValue("UserReqTyp") != null && msg.GetValue("UserReqTyp").Value <int>() == 3) { Debug.Assert(connection.IsLoggedOn); DispatchEvent(SystemEventType.CHANGE_PASSWORD_RESPONSE, connection, msg); break; } if (msg["UserStatus"].Value <int>() == 1) { Debug.Assert(!connection.IsLoggedOn); connection.IsLoggedOn = true; OnLogEvent(LogStatusType.INFO, "Received LOGIN_OK response"); DispatchEvent(SystemEventType.LOGIN_OK, connection, msg); } else { connection.IsLoggedOn = false; OnLogEvent(LogStatusType.WARN, "Received LOGIN_ERROR response : " + msg["UserStatusText"].Value <string>() ); DispatchEvent(SystemEventType.LOGIN_ERROR, connection, msg); connection.Shutdown(); } break; } case "W": Debug.Assert(connection.IsLoggedOn); if (msg["MarketDepth"].Value <int>() != 1) // Has Market Depth { DispatchEvent(SystemEventType.ORDER_BOOK_CLEAR, connection, msg); DispatchEvent(SystemEventType.TRADE_CLEAR, connection, msg); foreach (JObject entry in msg["MDFullGrp"]) { entry["MDReqID"] = msg["MDReqID"]; switch (entry["MDEntryType"].Value <char>()) { case '0': // Bid case '1': // Offer entry["Symbol"] = msg["Symbol"]; DispatchEvent(SystemEventType.ORDER_BOOK_NEW_ORDER, connection, entry); break; case '2': // Trade DispatchEvent(SystemEventType.TRADE, connection, entry); break; case '4': // Trading Session Status DispatchEvent(SystemEventType.TRADING_SESSION_STATUS, connection, entry); break; } } } DispatchEvent(SystemEventType.MARKET_DATA_FULL_REFRESH, connection, msg); break; case "X": if (msg["MDBkTyp"].Value <int>() == 3) // Order Depth { foreach (JObject entry in msg["MDIncGrp"]) { entry["MDReqID"] = msg["MDReqID"]; switch (entry["MDEntryType"].Value <char>()) { case '0': // Bid case '1': // Offer switch (entry["MDUpdateAction"].Value <char>()) { case '0': DispatchEvent(SystemEventType.ORDER_BOOK_NEW_ORDER, connection, entry); break; case '1': DispatchEvent(SystemEventType.ORDER_BOOK_UPDATE_ORDER, connection, entry); break; case '2': DispatchEvent(SystemEventType.ORDER_BOOK_DELETE_ORDER, connection, entry); break; case '3': DispatchEvent(SystemEventType.ORDER_BOOK_DELETE_ORDERS_THRU, connection, entry); break; } break; case '2': // Trade DispatchEvent(SystemEventType.TRADE, connection, entry); break; case '4': // Trading Session Status DispatchEvent(SystemEventType.TRADING_SESSION_STATUS, connection, entry); break; } } } else { // TODO: Top of the book handling. } DispatchEvent(SystemEventType.MARKET_DATA_INCREMENTAL_REFRESH, connection, msg); break; case "Y": DispatchEvent(SystemEventType.MARKET_DATA_REQUEST_REJECT, connection, msg); break; case "f": DispatchEvent(SystemEventType.SECURITY_STATUS, connection, msg); break; case "U3": DispatchEvent(SystemEventType.BALANCE_RESPONSE, connection, msg); break; case "U5": DispatchEvent(SystemEventType.ORDER_LIST_RESPONSE, connection, msg); break; case "8": //Execution Report if (msg.GetValue("Volume") == null || msg.GetValue("Volume").Type == JTokenType.Null) { if (msg.GetValue("AvgPx") != null && msg.GetValue("AvgPx").Type != JTokenType.Null && msg.GetValue("AvgPx").Value <ulong>() > 0) { msg["Volume"] = (ulong)(msg["CumQty"].Value <ulong>() * (float)(msg["AvgPx"].Value <ulong>() / 1e8)); } else { msg["Volume"] = 0; } } DispatchEvent(SystemEventType.EXECUTION_REPORT, connection, msg); break; case "U33": // Trade History Response DispatchEvent(SystemEventType.TRADE_HISTORY_RESPONSE, connection, msg); break; case "0": DispatchEvent(SystemEventType.HEARTBEAT, connection, msg); break; case "ERROR": OnLogEvent(LogStatusType.ERROR, msg.ToString()); DispatchEvent(SystemEventType.ERROR, connection, msg); connection.Shutdown(); break; default: { Debug.Assert(connection.IsLoggedOn); OnLogEvent(LogStatusType.WARN, "Unhandled message type : " + msgType); break; } } }