/// <summary> /// Converts the tradier order quantity into a qc quantity /// </summary> /// <remarks> /// Tradier quantities are always positive and use the direction to denote +/-, where as qc /// order quantities determine the direction /// </remarks> protected int ConvertQuantity(Anonymous8 order) { switch (order.Type) { case Type2.Buy: return((int)order.Quantity); case Type2.Sell: return(-(int)order.Quantity); default: throw new ArgumentOutOfRangeException(); } }
/* * private bool IsUnknownOrderID(KeyValuePair<long, TradierOrder> x) * { * // we don't have it in our local cache * return !_cachedOpenOrdersByTradierOrderID.ContainsKey(x.Key) * // the transaction happened after we initialized, make sure they're in the same time zone * && x.Value.TransactionDate.ToUniversalTime() > _initializationDateTime.ToUniversalTime() * // we don't have a record of it in our last 10k filled orders * && !_filledTradierOrderIDs.Contains(x.Key); * } * * private void ProcessPotentiallyUpdatedOrder(TradierCachedOpenOrder cachedOrder, TradierOrder updatedOrder) * { * // check for fills or status changes, for either fire a fill event * if (updatedOrder.RemainingQuantity != cachedOrder.Order.RemainingQuantity || ConvertStatus(updatedOrder.Status) != ConvertStatus(cachedOrder.Order.Status)) || { || var qcOrder = _orderProvider.GetOrderByBrokerageId(updatedOrder.Id); || qcOrder.PriceCurrency = "USD"; || const int orderFee = 0; || var fill = new OrderEvent(qcOrder, DateTime.UtcNow, orderFee, "Tradier Fill Event") || { || Status = ConvertStatus(updatedOrder.Status), || // this is guaranteed to be wrong in the event we have multiple fills within our polling interval, || // we're able to partially cope with the fill quantity by diffing the previous info vs current info || // but the fill price will always be the most recent fill, so if we have two fills with 1/10 of a second || // we'll get the latter fill price, so for large orders this can lead to inconsistent state || FillPrice = updatedOrder.LastFillPrice, || FillQuantity = (int)(updatedOrder.QuantityExecuted - cachedOrder.Order.QuantityExecuted) || }; || || // flip the quantity on sell actions || if (IsShort(updatedOrder.Direction)) || { || fill.FillQuantity *= -1; || } || || if (!cachedOrder.EmittedOrderFee) || { || cachedOrder.EmittedOrderFee = true; || var security = _securityProvider.GetSecurity(qcOrder.Symbol); || fill.OrderFee = security.FeeModel.GetOrderFee(security, qcOrder); || } || || // if we filled the order and have another contingent order waiting, submit it || ContingentOrderQueue contingent; || if (fill.Status == OrderStatus.Filled && _contingentOrdersByQCOrderID.TryGetValue(qcOrder.Id, out contingent)) || { || // prevent submitting the contingent order multiple times || if (_contingentReentranceGuardByQCOrderID.Add(qcOrder.Id)) || { || var order = contingent.Next(); || if (order == null || contingent.Contingents.Count == 0) || { || // we've finished with this contingent order || _contingentOrdersByQCOrderID.TryRemove(qcOrder.Id, out contingent); || } || // fire this off in a task so we don't block this thread || if (order != null) || { || // if we have a contingent that needs to be submitted then we can't respect the 'Filled' state from the order || // because the QC order hasn't been technically filled yet, so mark it as 'PartiallyFilled' || fill.Status = OrderStatus.PartiallyFilled; || || Task.Run(() => || { || try || { || Log.Trace("TradierBrokerage.SubmitContingentOrder(): Submitting contingent order for QC id: " + qcOrder.Id); || || var response = TradierPlaceOrder(order); || if (response.Errors.Errors.IsNullOrEmpty()) || { || // add the new brokerage id for retrieval later || qcOrder.BrokerId.Add(response.Order.Id.ToString()); || } || else || { || // if we failed to place this order I don't know what to do, we've filled the first part || // and failed to place the second... strange. Should we invalidate the rest of the order?? || Log.Error("TradierBrokerage.SubmitContingentOrder(): Failed to submit contingent order."); || var message = string.Format("{0} Failed submitting contingent order for QC id: {1} Filled Tradier Order id: {2}", qcOrder.Symbol, qcOrder.Id, updatedOrder.Id); || OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "ContingentOrderFailed", message)); || OnOrderEvent(new OrderEvent(qcOrder, DateTime.UtcNow, orderFee) { Status = OrderStatus.Canceled }); || } || } || catch (Exception err) || { || Log.Error(err); || OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "ContingentOrderError", "An error ocurred while trying to submit an Tradier contingent order: " + err)); || OnOrderEvent(new OrderEvent(qcOrder, DateTime.UtcNow, orderFee) { Status = OrderStatus.Canceled }); || } || finally || { || _contingentReentranceGuardByQCOrderID.Remove(qcOrder.Id); || } || }); || } || } || } || || OnOrderEvent(fill); || } || || // remove from open orders since it's now closed || if (OrderIsClosed(updatedOrder)) || { || _filledTradierOrderIDs.Add(updatedOrder.Id); || _cachedOpenOrdersByTradierOrderID.TryRemove(updatedOrder.Id, out cachedOrder); || } ||} || ||private void UpdateCachedOpenOrder(long key, TradierOrder updatedOrder) ||{ || TradierCachedOpenOrder cachedOpenOrder; || if (_cachedOpenOrdersByTradierOrderID.TryGetValue(key, out cachedOpenOrder)) || { || cachedOpenOrder.Order = updatedOrder; || } || else || { || _cachedOpenOrdersByTradierOrderID[key] = new TradierCachedOpenOrder(updatedOrder); || } ||} */ #endregion #region Conversion routines /// <summary> /// Converts the specified tradier order into a qc order. /// The 'task' will have a value if we needed to issue a rest call for the stop price, otherwise it will be null /// </summary> protected Order ConvertOrder(Anonymous8 order) { Order qcOrder; qcOrder = new LimitOrder() { LimitPrice = (decimal)order.LimitPrice }; //TODO /* * switch (order.OrderType) * { * case Type2 * qcOrder = new LimitOrder { LimitPrice = order.Price }; * break; * case TradierOrderType.Market: * qcOrder = new MarketOrder(); * break; * case TradierOrderType.StopMarket: * qcOrder = new StopMarketOrder { StopPrice = GetOrder(order.Id).StopPrice }; * break; * case TradierOrderType.StopLimit: * qcOrder = new StopLimitOrder { LimitPrice = order.Price, StopPrice = GetOrder(order.Id).StopPrice }; * break; * * //case TradierOrderType.Credit: * //case TradierOrderType.Debit: * //case TradierOrderType.Even: * default: * throw new NotImplementedException("The Tradier order type " + order.Type + " is not implemented."); * } */ qcOrder.Symbol = _symbolMapper.GetLeanSymbol(order.Symbol, order.AssetType);// Symbol.Create(order.Symbol, SecurityType.Equity, Market.USA); qcOrder.Quantity = ConvertQuantity(order); qcOrder.Status = ConvertStatus(order.Status); qcOrder.Id = Int32.Parse(order.OrderID.ToString()); //qcOrder.BrokerId.Add(order.OrderID.ToString()); //qcOrder.ContingentId = qcOrder.Properties.TimeInForce = ConvertDuration(order.Duration); /*var orderByBrokerageId = _orderProvider.GetOrderByBrokerageId(order.OrderID.ToString()); * if (orderByBrokerageId != null) * { * qcOrder.Id = orderByBrokerageId.Id; * } */ qcOrder.Time = DateTime.Parse(order.TimeStamp); //TransactionDate; return(qcOrder); }