private void sendOrder(IWebSocketClientConnection webSocketConnection, string symbol, char side, ulong qty, ulong price) { Debug.Assert(side == OrderSide.BUY || side == OrderSide.SELL); if (side != OrderSide.BUY && side != OrderSide.SELL) { throw new ArgumentException(); } if (qty >= _minTradeSize) { // send order (Participate don't initiate - aka book or cancel) and keep track of the ClOrdId string clorid = _tradeclient.SendOrder( webSocketConnection, symbol, qty, price, side, _tradeclient.BrokerId, MakeClOrdId(), OrdType.LIMIT, ExecInst.PARTICIPATE_DONT_INITIATE ); if (side == OrderSide.BUY) { _strategyBuyOrderClorid = clorid; } else { _strategySellOrderClorid = clorid; } } }
public void SendRequestToCancelAllOrders(IWebSocketClientConnection connection) { JObject order_cancel_request = new JObject(); order_cancel_request["MsgType"] = "F"; order_cancel_request["FingerPrint"] = connection.Device.FingerPrint; order_cancel_request["STUNTIP"] = connection.Device.Stuntip; connection.SendMessage(order_cancel_request.ToString()); }
// public string SendOrder(IWebSocketClientConnection connection, string symbol, ulong qty, ulong price, char side, int broker_id, string client_order_id, char order_type = '2', char execInst = default(char)) { // add pending new order to the OMS MiniOMS.Order orderToSend = new MiniOMS.Order(); orderToSend.Symbol = symbol; orderToSend.OrderQty = qty; orderToSend.Price = price; orderToSend.Side = side; orderToSend.ClOrdID = client_order_id; orderToSend.OrdType = order_type; orderToSend.OrdStatus = 'A'; // PENDING_NEW according to FIX std try { _miniOMS.AddOrder(orderToSend); } catch (Exception ex) { LogStatus(LogStatusType.ERROR, "The MiniOMS Rejected the Order : " + orderToSend.ClOrdID + ";" + orderToSend.OrderQty + ";" + orderToSend.Price.ToString() + ";\n" + ex.Message.ToString() + "\n" + ex.StackTrace ); return(null); } // send the order to the broker JObject new_order_single = new JObject(); new_order_single["MsgType"] = "D"; new_order_single["ClOrdID"] = orderToSend.ClOrdID; new_order_single["Symbol"] = orderToSend.Symbol; new_order_single["Side"] = orderToSend.Side.ToString(); new_order_single["OrdType"] = orderToSend.OrdType.ToString(); new_order_single["Price"] = orderToSend.Price; new_order_single["OrderQty"] = orderToSend.OrderQty; new_order_single["BrokerID"] = broker_id; if (execInst != default(char)) { new_order_single["ExecInst"] = execInst.ToString(); } new_order_single["FingerPrint"] = connection.Device.FingerPrint; new_order_single["STUNTIP"] = connection.Device.Stuntip; connection.SendMessage(new_order_single.ToString()); return(orderToSend.ClOrdID); }
private void SendRequestForOpenOrders(IWebSocketClientConnection connection, int page = 0) { JObject orders_list_request = new JObject(); orders_list_request["MsgType"] = "U4"; orders_list_request["OrdersReqID"] = connection.NextOutgoingSeqNum(); orders_list_request["Page"] = page; orders_list_request["PageSize"] = 20; orders_list_request["Filter"] = new JArray("has_leaves_qty eq 1" /*"has_cum_qty eq 1"*/); connection.SendMessage(orders_list_request.ToString()); }
private void replaceOrder(IWebSocketClientConnection webSocketConnection, string symbol, char side, ulong price, ulong qty = 0) { Debug.Assert(side == OrderSide.BUY || side == OrderSide.SELL); string existingClorId = side == OrderSide.BUY ? this._strategyBuyOrderClorid : this._strategySellOrderClorid; var orderToReplace = _tradeclient.miniOMS.GetOrderByClOrdID(existingClorId); if (qty == 0) { qty = calculateOrderQty(symbol, side, price); } if (qty < _minOrderSize) { return; // order is too small to send } // cancel the previous sent order since it is not possible to modify the order if (orderToReplace != null) { switch (orderToReplace.OrdStatus) { case OrdStatus.PENDING_NEW: // client control - in the case no response was received case OrdStatus.PENDING_CANCEL: LogStatus( LogStatusType.WARN, String.Format( "WAITING ORDER STATE CHANGE : {0} CLORDID {1} SIDE {2}", orderToReplace.OrdStatus.ToString(), orderToReplace.ClOrdID, side) ); return; // wait the confirmation of the NEW or CANCEL (TODO: create a timeout to avoid ad-eternum wait) case OrdStatus.NEW: case OrdStatus.PARTIALLY_FILLED: // cancel the order to replace it if (orderToReplace.Price == price && orderToReplace.LeavesQty == qty) { return; // order is essencially the same and should not be replaced } _tradeclient.CancelOrderByClOrdID(webSocketConnection, orderToReplace.ClOrdID); break; //return; default: break; } } // send a new order sendOrder(webSocketConnection, symbol, side, qty, price); }
public void SendTestRequest(IWebSocketClientConnection connection) { JObject test_request = new JObject(); test_request["MsgType"] = "1"; test_request["FingerPrint"] = connection.Device.FingerPrint; test_request["STUNTIP"] = connection.Device.Stuntip; test_request["TestReqID"] = connection.NextOutgoingSeqNum().ToString(); test_request["SendTime"] = Util.ConvertToUnixTimestamp(DateTime.Now).ToString(); string test_request_msg = test_request.ToString(); connection.SendMessage(test_request_msg); }
public void OnClose(IWebSocketClientConnection webSocketConn) { Debug.Assert(!webSocketConn.IsConnected); webSocketConn.IsLoggedOn = false; OnLogEvent(LogStatusType.ERROR, "WebSocket closed."); DispatchEvent(SystemEventType.CLOSED, webSocketConn); bool bRetVal = _connections.Remove(webSocketConn); if (bRetVal) { OnLogEvent(LogStatusType.INFO, "Removed connection : " + webSocketConn.ToString()); } }
protected virtual void DispatchEvent( SystemEventType evtType, IWebSocketClientConnection connection, JObject json = null) { if (SystemEvent != null) { SystemEventArgs args = new SystemEventArgs(); args.evtType = evtType; args.json = json; SystemEvent(connection, args); } }
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); }
public void OnExecutionReport(IWebSocketClientConnection webSocketConnection, MiniOMS.IOrder order) { if (_priceType == PriceType.PEGGED && _strategySide == OrderSide.SELL) { if (order.OrdStatus == OrdStatus.FILLED || order.OrdStatus == OrdStatus.PARTIALLY_FILLED) { ulong theSoldAmount = _tradeclient.GetSoldAmount(); if (theSoldAmount >= _maxAmountToSell) { LogStatus(LogStatusType.WARN, String.Format("[OnExecutionReport] Cannot exceed the allowed max amount to sell : {0} {1}", theSoldAmount, _maxAmountToSell)); _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategySellOrderClorid); } } } }
private void replaceOrder(IWebSocketClientConnection webSocketConnection, string symbol, char side, ulong price, ulong qty = 0) { Debug.Assert(side == OrderSide.BUY || side == OrderSide.SELL); string existingClorId = side == OrderSide.BUY ? this._strategyBuyOrderClorid : this._strategySellOrderClorid; var orderToReplace = _tradeclient.miniOMS.GetOrderByClOrdID(existingClorId); // cancel the previous sent order since it is not possible to modify the order if (orderToReplace != null) { switch (orderToReplace.OrdStatus) { case OrdStatus.PENDING_NEW: // client control - in the case no response was received case OrdStatus.PENDING_CANCEL: LogStatus( LogStatusType.WARN, String.Format( "WAITING ORDER STATE CHANGE : {0} CLORDID {1} SIDE {2}", orderToReplace.OrdStatus.ToString(), orderToReplace.ClOrdID, side) ); return; // wait the confirmation case OrdStatus.NEW: case OrdStatus.PARTIALLY_FILLED: // cancel the order to replace it _tradeclient.CancelOrderByClOrdID(webSocketConnection, orderToReplace.ClOrdID); break; default: break; } } if (qty == 0) { qty = calculateOrderQty(symbol, side, price); } // send a new buy order sendOrder(webSocketConnection, symbol, side, qty, price); }
public bool CancelOrderByClOrdID(IWebSocketClientConnection connection, string clOrdID) { MiniOMS.Order orderToCancel = _miniOMS.GetOrderByClOrdID(clOrdID); if (orderToCancel != null) { if (orderToCancel.OrdStatus == OrdStatus.NEW || orderToCancel.OrdStatus == OrdStatus.PARTIALLY_FILLED) { orderToCancel.OrdStatus = OrdStatus.PENDING_CANCEL; JObject order_cancel_request = new JObject(); order_cancel_request["MsgType"] = "F"; order_cancel_request["ClOrdID"] = clOrdID; order_cancel_request["FingerPrint"] = connection.Device.FingerPrint; order_cancel_request["STUNTIP"] = connection.Device.Stuntip; connection.SendMessage(order_cancel_request.ToString()); return(true); } } return(false); }
public void OnOpen(IWebSocketClientConnection connection) { Debug.Assert(connection.IsConnected); Debug.Assert(!connection.IsLoggedOn); OnLogEvent(LogStatusType.INFO, "Connection Succeeded"); _connections.Add(connection); // dispatch the connection opened DispatchEvent(SystemEventType.OPENED, connection); // build the json Login Request Message JObject login_request = new JObject(); login_request["MsgType"] = "BE"; login_request["UserReqID"] = connection.NextOutgoingSeqNum(); login_request["UserReqTyp"] = "1"; login_request["Username"] = connection.UserAccount.Username; login_request["Password"] = connection.UserAccount.Password; login_request["BrokerID"] = connection.UserAccount.BrokerId; login_request["CancelOnDisconnect"] = "1"; // enabled so that all session orders are automatically cancelled upon a disconnection (should work in next backend version) if (connection.UserAccount.SecondFactor != null && connection.UserAccount.SecondFactor != string.Empty) { login_request["SecondFactor"] = connection.UserAccount.SecondFactor; } login_request["UserAgent"] = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36"; login_request["UserAgentLanguage"] = "en-US"; login_request["UserAgentTimezoneOffset"] = ":180,"; login_request["UserAgentPlatform"] = "Linux x86_64"; login_request["FingerPrint"] = connection.Device.FingerPrint; login_request["STUNTIP"] = connection.Device.Stuntip; // send the login request Message on wire connection.SendMessage(login_request.ToString()); }
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; } } }
public void OnError(string ErrorMessage, IWebSocketClientConnection webSocketConn) { OnLogEvent(LogStatusType.ERROR, ErrorMessage); DispatchEvent(SystemEventType.ERROR, webSocketConn); }
/*abstract*/ public void runStrategy(IWebSocketClientConnection webSocketConnection, string symbol) { // Run the strategy to try to have an order on at least one side of the book according to fixed price range // but never executing as a taker if (!_enabled) // strategy cannot run when disabled { LogStatus(LogStatusType.WARN, "Strategy is disabled and will not run"); return; } // get the BTC Price in dollar SecurityStatus btcusd_quote = _tradeclient.GetSecurityStatus("BITSTAMP", "BTCUSD"); if (btcusd_quote == null || btcusd_quote.LastPx == 0) { if (_priceType == PriceType.TRAILING_STOP || _priceType == PriceType.PEGGED) { LogStatus(LogStatusType.WARN, "BITSTAMP:BTCUSD not available"); return; } } // get the dollar price SecurityStatus usd_official_quote = _tradeclient.GetSecurityStatus("UOL", "USDBRL"); // use USDBRT for the turism quote if (usd_official_quote == null || usd_official_quote.BestAsk == 0 || usd_official_quote.BestBid == 0) { if (_priceType == PriceType.TRAILING_STOP || _priceType == PriceType.PEGGED) { LogStatus(LogStatusType.WARN, "UOL:USDBRL not available"); return; } } OrderBook orderBook = _tradeclient.GetOrderBook(symbol); if (orderBook == null) { LogStatus(LogStatusType.ERROR, "Order Book not available for " + symbol); return; } if (_priceType == PriceType.TRAILING_STOP) { if (_strategySide == OrderSide.SELL && _stop_price > 0) { if (btcusd_quote.LastPx <= _stop_price) { // trigger the stop when the price goes down ulong stop_price_floor = (ulong)(Math.Round(_stop_price / 1e8 * usd_official_quote.BestAsk / 1e8 * _stop_price_adjustment_factor, 2) * 1e8); ulong best_bid_price = orderBook.BestBid != null ? orderBook.BestBid.Price : 0; ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL, stop_price_floor, ulong.MaxValue); Console.WriteLine("DEBUG ** Triggered Trailing Stop and should execute ASAP ** [{0}],[{1}],[{2}],[{3}]", btcusd_quote.LastPx, best_bid_price, stop_price_floor, availableQty); if (best_bid_price >= stop_price_floor) { // force a minimal execution as maker to get e-mail notification when the trailing stop is triggered availableQty = availableQty > _minOrderSize ? availableQty - _minOrderSize : availableQty; sendOrder(webSocketConnection, symbol, OrderSide.SELL, availableQty, stop_price_floor, OrdType.LIMIT, 0, ExecInst.DEFAULT, TimeInForce.IMMEDIATE_OR_CANCEL); } // change the strategy so that the bot might negociate the leaves qty as a maker applying another limit factor as a sell floor _priceType = PriceType.PEGGED; _sell_floor_price = stop_price_floor; Console.WriteLine("DEBUG Changed Strategy to FLOAT with SELL_FLOOR_PRICE=[{0}]", _sell_floor_price); } else { // when the market goes up - adjust the stop in case of a new high price in BTCUSD if (btcusd_quote.LastPx > _trailing_stop_high_price) { _stop_price = (ulong)(Math.Round(btcusd_quote.LastPx / 1e8 * (1 - _stoppx_offset_percentage / 100), 3) * 1e8); _trailing_stop_high_price = btcusd_quote.LastPx; Console.WriteLine("DEBUG Trailing STOP [StopPx={0}] [HighPx=[{1}] [EntryPx={2}] [CapPx={3}]", _stop_price, _trailing_stop_high_price, _trailing_stop_entry_price, _trailing_stop_cap_price); } // and check we should make profit if (_trailing_stop_cap_price > 0 && btcusd_quote.LastPx > _trailing_stop_cap_price) { ulong best_bid_price = orderBook.BestBid != null ? orderBook.BestBid.Price : 0; double local_exchange_spread = (best_bid_price / 1e8) / (double)(btcusd_quote.LastPx / 1e8 * usd_official_quote.BestBid / 1e8); if (local_exchange_spread > 1) { _sell_floor_price = (ulong)(Math.Round(btcusd_quote.LastPx / 1e8 * usd_official_quote.BestBid / 1e8 * _stop_price_adjustment_factor, 2) * 1e8); ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL, _sell_floor_price, ulong.MaxValue); Console.WriteLine("DEBUG ** Reached Trailing Stop CAP and should execute ASAP ** [{0}],[{1}],[{2}],[{3}]", btcusd_quote.LastPx, best_bid_price, _sell_floor_price, availableQty); if (best_bid_price >= _sell_floor_price) { availableQty = availableQty > _minOrderSize ? availableQty - _minOrderSize : availableQty; // force minimal execution as maker sendOrder(webSocketConnection, symbol, OrderSide.SELL, availableQty, _sell_floor_price, OrdType.LIMIT, 0, ExecInst.DEFAULT, TimeInForce.IMMEDIATE_OR_CANCEL); } // change the strategy so that the bot might negociate the leaves qty as a maker _priceType = PriceType.PEGGED; Console.WriteLine("DEBUG Changed Strategy to FLOAT with SELL_FLOOR_PRICE=[{0}]", _sell_floor_price); } else { Console.WriteLine("DEBUG ** Reached Trailing Stop CAP [but without local exchange spread] ** [{0}],[{1}],[{2}],[{3}]", btcusd_quote.LastPx, best_bid_price, usd_official_quote.BestBid, local_exchange_spread); } } } } return; } // ** temporary workaround to support market pegged sell order strategy without plugins** if (_priceType == PriceType.PEGGED && _strategySide == OrderSide.SELL) { // make the price float according to the MID Price // gather the data to calculate the midprice // instead of bestAsk let's use the Price reached if one decides to buy X BTC ulong maxPriceToBuyXBTC = orderBook.MaxPriceForAmountWithoutSelfOrders( OrderBook.OrdSide.SELL, (ulong)(1 * 1e8), // TODO: make it a parameter _tradeclient.UserId); // let's use the VWAP as the market price (short period tick based i.e last 30 min.) ulong marketPrice = _tradeclient.CalculateVWAP(); // calculate the mid price ulong midprice = (ulong)((orderBook.BestBid.Price + maxPriceToBuyXBTC + marketPrice) / 3); _sellTargetPrice = midprice + _pegOffsetValue; // check the selling FLOOR if (_sellTargetPrice < _sell_floor_price) { _sellTargetPrice = _sell_floor_price; } } // another workaround for sending a single stop order and disable the strategy if (_priceType == PriceType.STOP) { if (_strategySide == OrderSide.BUY) { char ordType = (_buyTargetPrice == 0 ? OrdType.STOP_MARKET : OrdType.STOP_LIMIT); ulong ref_price = (_buyTargetPrice > _stop_price ? _buyTargetPrice : _stop_price); ulong qty = calculateOrderQty(symbol, _strategySide, ref_price); sendOrder(webSocketConnection, symbol, OrderSide.BUY, qty, _buyTargetPrice, ordType, _stop_price, default(char)); } if (_strategySide == OrderSide.SELL) { char ordType = (_sellTargetPrice == 0 ? OrdType.STOP_MARKET : OrdType.STOP_LIMIT); ulong qty = calculateOrderQty(symbol, OrderSide.SELL); sendOrder(webSocketConnection, symbol, OrderSide.SELL, qty, _sellTargetPrice, ordType, _stop_price, default(char)); } // disable strategy after sending the stop order... this._enabled = false; return; } // run the strategy that change orders in the book if (_maxOrderSize > 0) { webSocketConnection.EnableTestRequest = false; if (_strategySide == OrderSide.BUY || _strategySide == default(char)) // buy or both { runBuyStrategy(webSocketConnection, symbol); } if (_strategySide == OrderSide.SELL || _strategySide == default(char)) // sell or both { runSellStrategy(webSocketConnection, symbol); } webSocketConnection.EnableTestRequest = true; } }
private void runSellStrategy(IWebSocketClientConnection webSocketConnection, string symbol) { OrderBook.IOrder bestOffer = _tradeclient.GetOrderBook(symbol).BestOffer; if (bestOffer != null) { if (bestOffer.UserId != _tradeclient.UserId) { // sell @ 1 cent bellow the best price (TODO: parameter for price increment) ulong sellPrice = bestOffer.Price - (ulong)(0.01 * 1e8); if (sellPrice >= _sellTargetPrice) { replaceOrder(webSocketConnection, symbol, OrderSide.SELL, sellPrice); } else { // cannot fight for the first position thus try to find a visible position in the book OrderBook orderBook = _tradeclient.GetOrderBook(symbol); List <OrderBook.Order> sellside = orderBook.GetOfferOrders(); int i = sellside.BinarySearch( new OrderBook.Order(OrderBook.OrdSide.SELL, _sellTargetPrice + (ulong)(0.01 * 1e8)), new OrderBook.OrderPriceComparer() ); int position = (i < 0 ? ~i : i); Debug.Assert(position > 0); // verificar se a profundidade vale a pena: (TODO: parameters for max_pos_depth and max_amount_depth) if (position > 5 + 1 && orderBook.DoesAmountExceedsLimit( OrderBook.OrdSide.SELL, position - 1, (ulong)(10 * 1e8))) { _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategySellOrderClorid); return; } var pivotOrder = sellside[position]; if (pivotOrder.UserId == _tradeclient.UserId) { // ordem ja e minha : pega + recursos disponiveis e cola no preco no vizinho se já nao estiver ulong price_delta = sellside[position + 1].Price - pivotOrder.Price; ulong newSellPrice = (price_delta > (ulong)(0.01 * 1e8) ? pivotOrder.Price + price_delta - (ulong)(0.01 * 1e8) : pivotOrder.Price); ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL); if (newSellPrice > pivotOrder.Price || availableQty > pivotOrder.Qty) { replaceOrder(webSocketConnection, symbol, OrderSide.SELL, newSellPrice, availableQty); } } else { // estabelece preco de venda 1 centavo menor do que nesta posicao ulong newSellPrice = pivotOrder.Price - (ulong)(0.01 * 1e8); replaceOrder(webSocketConnection, symbol, OrderSide.SELL, newSellPrice); } } } else { // check and replace the order to get closer to the order in the second position and gather more available funds List <OrderBook.Order> sellside = _tradeclient.GetOrderBook(symbol).GetOfferOrders(); ulong price_delta = sellside.Count > 1 ? sellside[1].Price - sellside[0].Price : 0; ulong newSellPrice = (price_delta > (ulong)(0.01 * 1e8) ? bestOffer.Price + price_delta - (ulong)(0.01 * 1e8) : bestOffer.Price); ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL); if (newSellPrice > bestOffer.Price || availableQty > bestOffer.Qty) { replaceOrder(webSocketConnection, symbol, OrderSide.SELL, newSellPrice, availableQty); } } } else { // TODO: empty book scenario } }
private void runSellStrategy(IWebSocketClientConnection webSocketConnection, string symbol) { OrderBook.IOrder bestBid = _tradeclient.GetOrderBook(symbol).BestBid; OrderBook.IOrder bestOffer = _tradeclient.GetOrderBook(symbol).BestOffer; if (_priceType == PriceType.MARKET_AS_MAKER) { ulong sellPrice = 0; if (bestBid != null) { sellPrice = bestBid.Price + (ulong)(0.01 * 1e8); } else if (bestOffer != null) { sellPrice = bestOffer.Price; } if (sellPrice > 0 || _sell_floor_price > 0) { if (sellPrice >= _sell_floor_price) { replaceOrder(webSocketConnection, symbol, OrderSide.SELL, sellPrice); return; } _sellTargetPrice = _sell_floor_price; // find the best position as maker for the sell floor price } else { // empty book scenario without a user defined sell floor price _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategySellOrderClorid); return; } } // available funds with a target price should execute ASAP even as liquidity takers whenever possible if (_sellTargetPrice > 0 && bestBid != null && bestBid.Price >= _sellTargetPrice && bestBid.UserId != _tradeclient.UserId) { ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL, bestBid.Price, ulong.MaxValue); if (availableQty > _minOrderSize) { ulong sell_qty = Math.Min(availableQty, bestBid.Qty); // execute the order as taker and emulate IOC instruction sendOrder(webSocketConnection, symbol, OrderSide.SELL, sell_qty, bestBid.Price, OrdType.LIMIT, 0, ExecInst.DEFAULT, TimeInForce.IMMEDIATE_OR_CANCEL); return; } else { // cancel current order to free balance to be used as taker MiniOMS.IOrder own_sell_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategySellOrderClorid); if (own_sell_order != null && (own_sell_order.OrdStatus == OrdStatus.NEW || own_sell_order.OrdStatus == OrdStatus.PARTIALLY_FILLED)) { if (own_sell_order.LeavesQty > _minOrderSize) { ulong sell_qty = Math.Min(own_sell_order.LeavesQty, bestBid.Qty); _tradeclient.CancelOrderByClOrdID(webSocketConnection, own_sell_order.ClOrdID); sendOrder(webSocketConnection, symbol, OrderSide.SELL, sell_qty, bestBid.Price, OrdType.LIMIT, 0, ExecInst.DEFAULT, TimeInForce.IMMEDIATE_OR_CANCEL); return; } } } } // post the order in the order book if (bestOffer != null) { if (bestOffer.UserId != _tradeclient.UserId) { // sell @ 1 cent bellow the best price (TODO: parameter for price increment) ulong sellPrice = bestOffer.Price - (ulong)(0.01 * 1e8); if (sellPrice >= _sellTargetPrice) { // TODO: Become a Taker when the spread is "small" (i.e for stop trailing converted to pegged or for any pegged) if (sellPrice > bestBid.Price) { replaceOrder(webSocketConnection, symbol, OrderSide.SELL, sellPrice); } else { // avoid being a taker or receiving a reject when using ExecInst=6 but stay in the book with max price ulong max_sell_price = bestBid.Price + (ulong)(0.01 * 1e8); var own_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategySellOrderClorid); ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL); if (own_order == null || own_order.Price != max_sell_price || availableQty > own_order.OrderQty) { replaceOrder(webSocketConnection, symbol, OrderSide.SELL, max_sell_price); } } } else { // cannot fight for the first position thus try to find a visible position in the book OrderBook orderBook = _tradeclient.GetOrderBook(symbol); List <OrderBook.Order> sellside = orderBook.GetOfferOrders(); int i = sellside.BinarySearch( new OrderBook.Order(OrderBook.OrdSide.SELL, _sellTargetPrice + (ulong)(0.01 * 1e8)), new OrderBook.OrderPriceComparer() ); int position = (i < 0 ? ~i : i); Debug.Assert(position > 0); // verificar se a profundidade vale a pena: (TODO: parameters for max_pos_depth and max_amount_depth) if (position > 15 + 1 && orderBook.DoesAmountExceedsLimit( OrderBook.OrdSide.SELL, position - 1, (ulong)(20 * 1e8))) { _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategySellOrderClorid); return; } var pivotOrder = sellside[position]; if (pivotOrder.UserId == _tradeclient.UserId) { // make sure the order is the same or from another client instance MiniOMS.IOrder own_sell_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategySellOrderClorid); if (sellside[position].OrderId == own_sell_order.OrderID) { // ordem ja e minha : pega + recursos disponiveis e cola no preco do vizinho se já nao estiver ulong price_delta = sellside.Count > position + 1 ? sellside[position + 1].Price - pivotOrder.Price : 0; ulong newSellPrice = (price_delta > (ulong)(0.01 * 1e8) ? pivotOrder.Price + price_delta - (ulong)(0.01 * 1e8) : pivotOrder.Price); ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL); if (newSellPrice > pivotOrder.Price || availableQty > pivotOrder.Qty) { replaceOrder(webSocketConnection, symbol, OrderSide.SELL, newSellPrice, availableQty); } } } else { // estabelece preco de venda 1 centavo menor do que nesta posicao ulong newSellPrice = pivotOrder.Price - (ulong)(0.01 * 1e8); replaceOrder(webSocketConnection, symbol, OrderSide.SELL, newSellPrice); } } } else { // check and replace the order on the top to get closer to the order in the second position and gather more available funds MiniOMS.IOrder own_sell_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategySellOrderClorid); List <OrderBook.Order> sellside = _tradeclient.GetOrderBook(symbol).GetOfferOrders(); if (sellside[0].OrderId == own_sell_order.OrderID) { ulong price_delta = sellside.Count > 1 ? sellside[1].Price - sellside[0].Price : 0; ulong newSellPrice = (price_delta > (ulong)(0.01 * 1e8) ? bestOffer.Price + price_delta - (ulong)(0.01 * 1e8) : bestOffer.Price); ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL); if (newSellPrice > bestOffer.Price || availableQty > bestOffer.Qty) { replaceOrder(webSocketConnection, symbol, OrderSide.SELL, newSellPrice, availableQty); } } } } else { // empty book scenario ulong availableQty = calculateOrderQty(symbol, OrderSide.SELL); Debug.Assert(_sellTargetPrice > 0); ulong sell_price = Math.Max(_sellTargetPrice, bestBid != null ? bestBid.Price + (ulong)(0.01 * 1e8) : 0); sendOrder(webSocketConnection, symbol, OrderSide.SELL, availableQty, sell_price); } }
private void runBuyStrategy(IWebSocketClientConnection webSocketConnection, string symbol) { OrderBook.IOrder bestBid = _tradeclient.GetOrderBook(symbol).BestBid; OrderBook.IOrder bestOffer = _tradeclient.GetOrderBook(symbol).BestOffer; if (_priceType == PriceType.MARKET_AS_MAKER) { ulong buyPrice = 0; if (bestOffer != null) { buyPrice = bestOffer.Price - (ulong)(0.01 * 1e8); } else if (bestBid != null) { buyPrice = bestBid.Price; } Debug.Assert(_buy_cap_price > 0); if (_buy_cap_price == 0) { _buy_cap_price = ulong.MaxValue; } if (buyPrice > 0 && buyPrice <= _buy_cap_price) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, buyPrice); return; } if (_buy_cap_price == ulong.MaxValue) { _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategyBuyOrderClorid); return; } _buyTargetPrice = _buy_cap_price; // find the best position as maker at the buy cap price } if (_priceType == PriceType.EXPLORE_BOOK_DEPTH) { // set the price based on the depth (this is a lot inefficent but I don't care) OrderBook orderBook = _tradeclient.GetOrderBook(symbol); ulong max_price = orderBook.MaxPriceForAmountWithoutSelfOrders(OrderBook.OrdSide.BUY, _minBookDepth, _tradeclient.UserId); ulong min_price = orderBook.MaxPriceForAmountWithoutSelfOrders(OrderBook.OrdSide.BUY, _maxBookDepth, _tradeclient.UserId); max_price = max_price < ulong.MaxValue ? max_price : min_price; var myOrder = _tradeclient.miniOMS.GetOrderByClOrdID(this._strategyBuyOrderClorid); if (max_price < ulong.MaxValue) { min_price = min_price < ulong.MaxValue ? min_price : max_price; if (myOrder == null || myOrder.Price > max_price || myOrder.Price < min_price) { //LogStatus (LogStatusType.WARN, String.Format ("[DT] must change order price not in expected position {0} {1} {2}", myOrder != null ? myOrder.Price : 0, max_price, min_price)); if (min_price < max_price) { _buyTargetPrice = min_price + (ulong)(0.01 * 1e8); // 1 pip better than min_price } else { _buyTargetPrice = min_price - (ulong)(0.01 * 1e8); // 1 pip worse than min_price } } else { return; // don't change the order because it is still in an acceptable position } } else { // no reference found in the book SecurityStatus usd_official_quote = _tradeclient.GetSecurityStatus("UOL", "USDBRL"); // use USDBRT for the turism quote SecurityStatus btcusd_quote = _tradeclient.GetSecurityStatus("BITSTAMP", "BTCUSD"); if (usd_official_quote != null && usd_official_quote.BestBid > 0 && btcusd_quote != null && btcusd_quote.BestBid > 0) { ulong market_price = _tradeclient.CalculateVWAP(); ulong lastPrice = _tradeclient.GetLastPrice(); ulong off_sale_price = (ulong)(usd_official_quote.BestBid / 1e8 * btcusd_quote.BestBid / 1e8 * 0.5 * 1e8); _buyTargetPrice = Math.Min(Math.Min(market_price, lastPrice), off_sale_price); } else { return; } } } if (bestBid != null) { if (bestBid.UserId != _tradeclient.UserId) { // buy @ 1 cent above the best price (TODO: parameter for price increment) ulong buyPrice = bestBid.Price + (ulong)(0.01 * 1e8); if (buyPrice <= this._buyTargetPrice) { if (buyPrice < bestOffer.Price) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, buyPrice); } else { // avoid being a taker or receiving a reject when using ExecInst=6 but stay in the book with max price ulong max_buy_price = bestOffer.Price - (ulong)(0.01 * 1e8); var own_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategyBuyOrderClorid); ulong availableQty = calculateOrderQty(symbol, OrderSide.BUY, max_buy_price); if (own_order == null || own_order.Price != max_buy_price || availableQty > own_order.OrderQty) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, max_buy_price); } } } else { // cannot fight for the first position thus try to find a visible position in the book OrderBook orderBook = _tradeclient.GetOrderBook(symbol); List <OrderBook.Order> buyside = orderBook.GetBidOrders(); int i = buyside.BinarySearch( new OrderBook.Order(OrderBook.OrdSide.BUY, _buyTargetPrice - (ulong)(0.01 * 1e8)), new OrderBook.ReverseOrderPriceComparer() ); int position = (i < 0 ? ~i : i); Debug.Assert(position > 0); if (this._priceType != PriceType.EXPLORE_BOOK_DEPTH) { // verificar se a profundidade vale a pena: (TODO: parameters for max_pos_depth and max_amount_depth) if (position > 15 + 1 && orderBook.DoesAmountExceedsLimit( OrderBook.OrdSide.BUY, position - 1, (ulong)(20 * 1e8))) { _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategyBuyOrderClorid); return; } } var pivotOrder = buyside[position]; if (pivotOrder.UserId == _tradeclient.UserId) { // make sure the order is the same or from another client instance MiniOMS.IOrder own_buy_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategyBuyOrderClorid); if (buyside[position].OrderId == own_buy_order.OrderID) { // ordem ja e minha : pega + recursos disponiveis e cola no preco no vizinho se já nao estiver ulong price_delta = buyside.Count > position + 1 ? pivotOrder.Price - buyside[position + 1].Price : 0; ulong newBuyPrice = (price_delta > (ulong)(0.01 * 1e8) ? pivotOrder.Price - price_delta + (ulong)(0.01 * 1e8) : pivotOrder.Price); ulong availableQty = calculateOrderQty(symbol, OrderSide.BUY, newBuyPrice); if (newBuyPrice < pivotOrder.Price || availableQty > pivotOrder.Qty) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, newBuyPrice, availableQty); } } } else { // estabelece preco de venda 1 centavo maior do que nesta posicao ulong newbuyPrice = pivotOrder.Price + (ulong)(0.01 * 1e8); replaceOrder(webSocketConnection, symbol, OrderSide.BUY, newbuyPrice); } } } else { // make sure the order is the same or from another client instance // check and replace order to get closer to the order in the second position and gather more avaible funds List <OrderBook.Order> buyside = _tradeclient.GetOrderBook(symbol).GetBidOrders(); MiniOMS.IOrder own_buy_order = _tradeclient.miniOMS.GetOrderByClOrdID(_strategyBuyOrderClorid); if (buyside[0].OrderId == own_buy_order.OrderID) { ulong price_delta = buyside.Count > 1 ? buyside[0].Price - buyside[1].Price : 0; ulong newBuyPrice = (price_delta > (ulong)(0.01 * 1e8) ? bestBid.Price - price_delta + (ulong)(0.01 * 1e8) : bestBid.Price); ulong availableQty = calculateOrderQty(symbol, OrderSide.BUY, newBuyPrice); if (newBuyPrice < bestBid.Price || availableQty > bestBid.Qty) { replaceOrder(webSocketConnection, symbol, OrderSide.BUY, newBuyPrice, availableQty); } } } } else { // empty book scenario ulong availableQty = calculateOrderQty(symbol, OrderSide.BUY, _buyTargetPrice); Debug.Assert(_buyTargetPrice > 0); ulong buy_price = Math.Min(_buyTargetPrice, bestOffer != null ? bestOffer.Price - (ulong)(0.01 * 1e8) : ulong.MaxValue); sendOrder(webSocketConnection, symbol, OrderSide.BUY, availableQty, buy_price); } }
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 ); } }
private void StartInitialRequestsAfterLogon(IWebSocketClientConnection connection) { // 1. cancel all user orders SendRequestToCancelAllOrders(connection); // not necessary if cancel on disconnect is active // 2. send the balance request JObject balance_request = new JObject(); balance_request["MsgType"] = "U2"; balance_request["BalanceReqID"] = connection.NextOutgoingSeqNum(); balance_request["FingerPrint"] = connection.Device.FingerPrint; balance_request["STUNTIP"] = connection.Device.Stuntip; connection.SendMessage(balance_request.ToString()); // 3. send market data request JObject marketdata_request = new JObject(); marketdata_request["MsgType"] = "V"; marketdata_request["MDReqID"] = connection.NextOutgoingSeqNum(); marketdata_request["SubscriptionRequestType"] = "1"; marketdata_request["MarketDepth"] = 0; marketdata_request["MDUpdateType"] = "1"; marketdata_request["MDEntryTypes"] = new JArray("0", "1", "2"); // bid, offer, trade marketdata_request["Instruments"] = new JArray(_tradingSymbol); marketdata_request["FingerPrint"] = connection.Device.FingerPrint; marketdata_request["STUNTIP"] = connection.Device.Stuntip; connection.SendMessage(marketdata_request.ToString()); // 4. send security status request JObject securitystatus_request = new JObject(); securitystatus_request["MsgType"] = "e"; securitystatus_request["SecurityStatusReqID"] = connection.NextOutgoingSeqNum(); securitystatus_request["SubscriptionRequestType"] = "1"; JArray instruments = new JArray(); instruments.Add("BLINK:BTCBRL"); instruments.Add("BLINK:BTCUSD"); instruments.Add("BLINK:BTCVND"); instruments.Add("BLINK:BTCVEF"); instruments.Add("BLINK:BTCPKR"); instruments.Add("BLINK:BTCCLP"); instruments.Add("BITSTAMP:BTCUSD"); instruments.Add("ITBIT:BTCUSD"); instruments.Add("BITFINEX:BTCUSD"); instruments.Add("BTRADE:BTCUSD"); instruments.Add("MBT:BTCBRL"); instruments.Add("KRAKEN:BTCEUR"); instruments.Add("COINFLOOR:BTCGBP"); instruments.Add("UOL:USDBRL"); instruments.Add("UOL:USDBRT"); instruments.Add("OKCOIN:BTCCNY"); securitystatus_request["Instruments"] = instruments; securitystatus_request["FingerPrint"] = connection.Device.FingerPrint; securitystatus_request["STUNTIP"] = connection.Device.Stuntip; connection.SendMessage(securitystatus_request.ToString()); // 5. send the trade history request JObject trades_request = new JObject(); trades_request["MsgType"] = "U32"; trades_request["TradeHistoryReqID"] = connection.NextOutgoingSeqNum(); //trades_request["Filter"] = new JArray("Symbol eq 'BTCBRL'"); // not working //trades_request["SymbolList"] = new JArray("BTCBRL"); // not working trades_request["FingerPrint"] = connection.Device.FingerPrint; trades_request["STUNTIP"] = connection.Device.Stuntip; connection.SendMessage(trades_request.ToString()); // 6. send request for all "open" orders SendRequestForOpenOrders(connection); }
/*abstract*/ public void runStrategy(IWebSocketClientConnection webSocketConnection, string symbol) { // Run the strategy to try to have an order on at least one side of the book according to fixed price range // but never executing as a taker if (!_enabled) // strategy cannot run when disabled { LogStatus(LogStatusType.WARN, "Strategy is disabled and will not run"); return; } // ** temporary workaround to support market pegged sell order strategy without plugins** if (_priceType == PriceType.PEGGED && _strategySide == OrderSide.SELL) { // make the price float according to the MID Price /* * // requires the Security List for the trading symbol * SecurityStatus status = _tradeclient.GetSecurityStatus ("BLINK", symbol); * if (status == null) * { * LogStatus( * LogStatusType.WARN, * String.Format( * "Waiting Security Status BLINK:{0} to run Pegged strategy", * symbol) * ); * return; * } */ // check the remaining qty that can still be sold ulong theSoldAmount = _tradeclient.GetSoldAmount(); if (theSoldAmount < _maxAmountToSell) { ulong uAllowedAmountToSell = _maxAmountToSell - theSoldAmount; _maxTradeSize = _maxTradeSize < uAllowedAmountToSell ? _maxTradeSize : uAllowedAmountToSell; _maxTradeSize = _maxTradeSize > _minTradeSize ? _maxTradeSize : _minTradeSize; } else { LogStatus(LogStatusType.WARN, String.Format("[runStrategy] Cannot exceed the allowed max amount to sell : {0} {1}", theSoldAmount, _maxAmountToSell)); _tradeclient.CancelOrderByClOrdID(webSocketConnection, _strategySellOrderClorid); return; } // gather the data to calculate the midprice OrderBook orderBook = _tradeclient.GetOrderBook(symbol); // instead of bestAsk let's use the Price reached if one decides to buy 1 BTC ulong maxPriceToBuy1BTC = orderBook.MaxPriceForAmountWithoutSelfOrders( OrderBook.OrdSide.SELL, (ulong)(1 * 1e8), // TODO: make it a parameter _tradeclient.UserId); // gather the magic element of the midprice (i.e. price to buy 10 BTC) ulong maxPriceToBuyXBTC = orderBook.MaxPriceForAmountWithoutSelfOrders( OrderBook.OrdSide.SELL, (ulong)(10 * 1e8), // TODO: make it a parameter _tradeclient.UserId); // instead of the last price let's use the VWAP (short period tick based i.e last 30 min.) ulong vwap = _tradeclient.CalculateVWAP(); ulong lastPx = _tradeclient.GetLastPrice(); ulong marketPrice = vwap > lastPx ? vwap : lastPx; // calculate the mid price //ulong midprice = (ulong)((status.BestAsk + status.BestBid + status.LastPx + maxPriceToBuyXBTC) / 4); ulong midprice = (ulong)((orderBook.BestBid.Price + maxPriceToBuy1BTC + maxPriceToBuyXBTC + marketPrice) / 4); Debug.Assert(_pegOffsetValue > 0); _sellTargetPrice = midprice + _pegOffsetValue; // get the dollar price SecurityStatus usd_official_quote = _tradeclient.GetSecurityStatus("UOL", "USDBRL"); // use USDBRT for the turism quote if (usd_official_quote == null || usd_official_quote.BestAsk == 0) { LogStatus(LogStatusType.WARN, "UOL:USDBRL not available"); } // get the BTC Price in dollar SecurityStatus bitfinex_btcusd_quote = _tradeclient.GetSecurityStatus("BITSTAMP", "BTCUSD"); if (bitfinex_btcusd_quote == null || bitfinex_btcusd_quote.BestAsk == 0) { LogStatus(LogStatusType.WARN, "BITSTAMP:BTCUSD not available"); } // calculate the selling floor must be at least the price of the BTC in USD //ulong floor = (ulong)(1.01 * bitfinex_btcusd_quote.BestAsk * (float)(usd_official_quote.BestAsk / 1e8)); //if (floor == 0) { ulong floor = (ulong)(8900 * 1e8); // TODO: make it an optional parameter or pegged to the dolar bitcoin //} //floor = (ulong)(5400 * 1e8); // check the selling FLOOR if (_sellTargetPrice < floor) { _sellTargetPrice = floor; } } // run the strategy if (_maxTradeSize > 0) { webSocketConnection.EnableTestRequest = false; if (_strategySide == OrderSide.BUY || _strategySide == default(char)) // buy or both { runBuyStrategy(webSocketConnection, symbol); } if (_strategySide == OrderSide.SELL || _strategySide == default(char)) // sell or both { runSellStrategy(webSocketConnection, symbol); } webSocketConnection.EnableTestRequest = true; } }