/// <summary> /// Processes a new fill, eventually creating new trades /// </summary> /// <param name="fill">The new fill order event</param> /// <param name="conversionRate">The current security market conversion rate into the account currency</param> /// <param name="feeInAccountCurrency">The current order fee in the account currency</param> /// <param name="multiplier">The contract multiplier</param> public void ProcessFill(OrderEvent fill, decimal conversionRate, decimal feeInAccountCurrency, decimal multiplier = 1.0m) { // If we have multiple fills per order, we assign the order fee only to its first fill // to avoid counting the same order fee multiple times. var orderFee = 0m; if (!_ordersWithFeesAssigned.Contains(fill.OrderId)) { orderFee = feeInAccountCurrency; _ordersWithFeesAssigned.Add(fill.OrderId); } switch (_groupingMethod) { case FillGroupingMethod.FillToFill: ProcessFillUsingFillToFill(fill.Clone(), orderFee, conversionRate, multiplier); break; case FillGroupingMethod.FlatToFlat: ProcessFillUsingFlatToFlat(fill.Clone(), orderFee, conversionRate, multiplier); break; case FillGroupingMethod.FlatToReduced: ProcessFillUsingFlatToReduced(fill.Clone(), orderFee, conversionRate, multiplier); break; } }
/// <summary> /// Places a new order and assigns a new broker ID to the order /// </summary> /// <param name="order">The order to be placed</param> /// <returns>True if the request for a new order has been placed, false otherwise</returns> public override bool PlaceOrder(Order order) { Log.Trace("TradeStation.PlaceOrder(): " + order); if (_cancelledQcOrderIDs.Contains(order.Id)) { Log.Trace("TradeStation.PlaceOrder(): Cancelled Order: " + order.Id + " - " + order); return(false); } var tsOrder = new OrderRequestDefinition(); tsOrder.Symbol = _symbolMapper.GetBrokerageSymbol(order.Symbol); if (order.Symbol.SecurityType == SecurityType.Option) { tsOrder.AssetType = OrderRequestDefinitionAssetType.OP; } else { tsOrder.AssetType = OrderRequestDefinitionAssetType.EQ; } tsOrder.AccountKey = _accountKeys[0]; tsOrder.Quantity = order.AbsoluteQuantity.ToString(); if (order.Direction == OrderDirection.Buy) { tsOrder.TradeAction = OrderRequestDefinitionTradeAction.BUY; } else { tsOrder.TradeAction = OrderRequestDefinitionTradeAction.SELL; } tsOrder.OrderConfirmId = Guid.NewGuid().ToString(); if (order.TimeInForce == TimeInForce.Day) { tsOrder.Duration = OrderRequestDefinitionDuration.DAY; } else { tsOrder.Duration = OrderRequestDefinitionDuration.GTC; } switch (order.Type) { case Orders.OrderType.Limit: tsOrder.LimitPrice = ((LimitOrder)order).LimitPrice.ToString(); tsOrder.OrderType = OrderRequestDefinitionOrderType.Limit; //TODO: support more order types tsOrder.TradeAction = tsOrder.TradeAction == OrderRequestDefinitionTradeAction.BUY ? OrderRequestDefinitionTradeAction.BUYTOOPEN : OrderRequestDefinitionTradeAction.SELLTOCLOSE; break; case Orders.OrderType.Market: tsOrder.OrderType = OrderRequestDefinitionOrderType.Market; break; default: throw new Exception("Order type not supported!"); } var response = _tradeStationClient.PostOrderAsync(_accessToken, tsOrder).Result; Log.Trace("TradeStation.PlaceOrder(): " + response.Message); return(response.OrderStatus == OrderResponseDefinitionOrderStatus.Ok); /* * * // before doing anything, verify only one outstanding order per symbol * var cachedOpenOrder = _cachedOpenOrdersByTradierOrderID.FirstOrDefault(x => x.Value.Order.Symbol == order.Symbol.Value).Value; * if (cachedOpenOrder != null) * { * var qcOrder = _orderProvider.GetOrderByBrokerageId(cachedOpenOrder.Order.Id); * if (qcOrder == null) * { * // clean up our mess, this should never be encountered. * TradierCachedOpenOrder tradierOrder; * Log.Error("TradierBrokerage.PlaceOrder(): Unable to locate existing QC Order when verifying single outstanding order per symbol."); * _cachedOpenOrdersByTradierOrderID.TryRemove(cachedOpenOrder.Order.Id, out tradierOrder); * } * // if the qc order is still listed as open, then we have an issue, attempt to cancel it before placing this new order * else if (qcOrder.Status.IsOpen()) * { * // let the world know what we're doing * OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "OneOrderPerSymbol", * "Tradier Brokerage currently only supports one outstanding order per symbol. Canceled old order: " + qcOrder.Id) * ); * * // cancel the open order and clear out any contingents * ContingentOrderQueue contingent; * _contingentOrdersByQCOrderID.TryRemove(qcOrder.Id, out contingent); * // don't worry about the response here, if it couldn't be canceled it was * // more than likely already filled, either way we'll trust we're clean to proceed * // with this new order * CancelOrder(qcOrder); * } * } * * var holdingQuantity = _securityProvider.GetHoldingsQuantity(order.Symbol); * * var orderRequest = new TradierPlaceOrderRequest(order, TradierOrderClass.Equity, holdingQuantity); * * // do we need to split the order into two pieces? * bool crossesZero = OrderCrossesZero(order); * if (crossesZero) * { * // first we need an order to close out the current position * var firstOrderQuantity = -holdingQuantity; * var secondOrderQuantity = order.Quantity - firstOrderQuantity; * * orderRequest.Quantity = Math.Abs(firstOrderQuantity); * * // we actually can't place this order until the closingOrder is filled * // create another order for the rest, but we'll convert the order type to not be a stop * // but a market or a limit order * var restOfOrder = new TradierPlaceOrderRequest(order, TradierOrderClass.Equity, 0) { Quantity = Math.Abs(secondOrderQuantity) }; * restOfOrder.ConvertStopOrderTypes(); * * _contingentOrdersByQCOrderID.AddOrUpdate(order.Id, new ContingentOrderQueue(order, restOfOrder)); * * // issue the first order to close the position * var response = TradierPlaceOrder(orderRequest); * bool success = response.Errors.Errors.IsNullOrEmpty(); * if (!success) * { * // remove the contingent order if we weren't succesful in placing the first * ContingentOrderQueue contingent; * _contingentOrdersByQCOrderID.TryRemove(order.Id, out contingent); * return false; * } * * var closingOrderID = response.Order.Id; * order.BrokerId.Add(closingOrderID.ToString()); * return true; * } * else * { * var response = TradierPlaceOrder(orderRequest); * if (!response.Errors.Errors.IsNullOrEmpty()) * { * return false; * } * order.BrokerId.Add(response.Order.Id.ToString()); * return true; * } */ }