/// <summary> /// Process submit order /// For crypto currencies: check if we have the correct amount of base currency, if not, create and additional order for getting the correct base currency amount /// </summary> /// <param name="ticket"></param> /// <returns></returns> protected override OrderTicketResponse SubmitOrder(SubmitOrderTicket ticket) { //Check for associated quant fund var quantfund = Portfolio.QuantFunds.FirstOrDefault(x => x.FundId == ticket.FundId); //Check if we can get the pending order var cashposition = (quantfund == null ? CashManager.GetCashPositions() : CashManager.GetCashPositions(quantfund))[ticket.Security.BaseCurrency]; decimal value = Math.Abs(ticket.Security.ConvertValue(ticket.Quantity * ticket.Security.Price, cashposition.BaseCurrency)); if (cashposition.TotalSettledCash < value) { //We need extra cash for this in currency X decimal valueneeded = value - cashposition.TotalSettledCash; //Create and send var security = Portfolio.BrokerAccount.Securities[$"{cashposition.BaseCurrency}.BC"]; var orderticket = SubmitOrderTicket.MarketOrder(ticket.FundId, security, valueneeded, $"Base currency conversion needed to execute order with id {ticket.OrderId}"); var response = SubmitOrder(orderticket); //Wait for this order if (!response.IsError) { WaitForOrder(response.OrderId); } else { _log.Error($"Could not process currency conversion for order with id {ticket.OrderId}, due to conversion order error {response.ErrorCode} : {response.ErrorMessage}"); } } //Use base implementation return(base.SubmitOrder(ticket)); }
/// <summary> /// Calculate order size for signal order /// </summary> /// <param name="ticket"></param> /// <param name="state"></param> /// <param name="weight"></param> /// <returns></returns> public override decimal OrderQuantity(SubmitOrderTicket ticket, SecurityState state, decimal weight) { //Get the needed weight (instead of the supplied weight we get from the universe, we change it to reflect our own weight) if (_adjustedweights.ContainsKey(ticket.Security)) { var oldweight = weight; weight = _adjustedweights[ticket.Security]; Info($"Adjusted weight from {oldweight}, to {weight}"); } else { Warning($"Could not adjust weight, could not find security {ticket.Security}"); } //Get target amount decimal target = TargetWeight(ticket.Security, (state == SecurityState.EntryShort ? -1 : state == SecurityState.EntryLong ? 1 : 0) * weight); //Check if target is valid if (target == 0) { ticket.Cancel(); return(0); } else { return(target); } }
/// <summary> /// Processes the submit ticket. /// </summary> /// <param name="ticket">The ticket.</param> /// <param name="quantfund">Associated Quant Fund</param> /// <returns></returns> private OrderTicket ProcessSubmitTicket(SubmitOrderTicket ticket, IQuantFund quantfund) { if (quantfund != null && quantfund.IsBackfilling) { ticket.SetResponse( OrderTicketResponse.Error(ticket.OrderId, OrderTicketResponseErrorCode.QuantFundBackfilling, $"Quant fund {ticket.FundId} is currently backfilling, cannot process orders.")); } else { _orderTicketQueue.Add(ticket); ticket.SetResponse(OrderTicketResponse.Processed(ticket.OrderId), OrderTicketState.Processing); } return(ticket); }
/// <summary> /// Calculate order size for signal order /// </summary> /// <param name="ticket"></param> /// <param name="state"></param> /// <param name="weight"></param> /// <returns></returns> public override decimal OrderQuantity(SubmitOrderTicket ticket, SecurityState state, decimal weight) { //Get target amount decimal target = TargetWeight(ticket.Security, (state == SecurityState.EntryShort ? -1 : state == SecurityState.EntryLong ? 1 : 0) * weight); //Check if target is valid if (target == 0) { ticket.Cancel(); return(0); } else { return(target); } }
/// <summary> /// Perform risk management checks and retrieve new orders to offset risks /// </summary> /// <param name="orderticket"></param> /// <param name="state"></param> /// <param name="weight"></param> /// <returns></returns> public virtual IEnumerable <OrderTicket> RiskManagement(SubmitOrderTicket orderticket, SecurityState state, decimal weight) => throw new NotImplementedException("RiskManagement method should be implemented for a risk management module to function.");
/// <summary> /// Create a new stop limit order ticket /// </summary> /// <param name="security">The security.</param> /// <param name="quantity">The quantity.</param> /// <param name="stopprice">The stopprice.</param> /// <param name="limitprice">The limitprice.</param> /// <param name="comment">The comment.</param> /// <param name="exchange"></param> /// <returns></returns> public SubmitOrderTicket StopLimitOrder(Security security, decimal quantity, decimal stopprice, decimal limitprice, string comment = "", string exchange = "") => SubmitOrderTicket.StopLimitOrder(QuantFund.FundId, security, quantity, limitprice, stopprice, comment, exchange);
/// <summary> /// Create a new market order ticket /// </summary> /// <param name="security">The security.</param> /// <param name="quantity">The quantity.</param> /// <param name="comment">The comment.</param> /// <param name="exchange"></param> /// <returns></returns> public SubmitOrderTicket MarketOrder(Security security, decimal quantity, string comment = "", string exchange = "") => SubmitOrderTicket.MarketOrder(QuantFund.FundId, security, quantity, comment, exchange);
/// <summary> /// Perform risk management checks and retrieve new orders to offset risks /// </summary> /// <param name="orderticket"></param> /// <param name="state"></param> /// <param name="weight"></param> /// <returns></returns> public override IEnumerable <OrderTicket> RiskManagement(SubmitOrderTicket orderticket, SecurityState state, decimal weight) => Nothing;
/// <summary> /// Process submit order /// </summary> /// <param name="ticket"></param> protected virtual OrderTicketResponse SubmitOrder(SubmitOrderTicket ticket) { //Get order from factory PendingOrder pendingorder = OrderFactory.CreateOrder(ticket); OrderImpl order = pendingorder.Order as OrderImpl; //Round off order quantity for correct amounts RoundLotSize(order); //try and get the order from the order tracker if (!OrderTracker.TryAddOrder(pendingorder)) { _log.Error($"Unable to add new order, order with id {pendingorder.OrderId} was already submitted"); return(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.OrderAlreadyExists, $"Current order with id {pendingorder.OrderId} was already submitted")); } if (!OrderTracker.TryGetOrder(pendingorder.OrderId, out pendingorder)) { _log.Error($"Unable to retrieve newly added order, order with id {pendingorder.OrderId} was cannot be processed properly"); return(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.UnableToFindOrder, $"Current order with id {pendingorder.OrderId} cannot be found")); } //Round of prices RoundOrderPrices(order); //Set our new order pendingorder.UpdateOrder(order); //Check for correct size if (order.Quantity == 0) { order.State = OrderState.Invalid; var ticketresponse = OrderTicketResponse.Error(order.InternalId, OrderTicketResponseErrorCode.OrderQuantityZero); Portfolio.Log(LogLevel.Error, ticketresponse.ErrorMessage); return(ticketresponse); } //Check if we have enough capital for an order bool sufficientcapital = GetSufficientCapitalForOrder(pendingorder); if (!sufficientcapital) { //Not enough capital to execute this order order.State = OrderState.Invalid; var response = OrderTicketResponse.Error(order.InternalId, OrderTicketResponseErrorCode.InsufficientBuyingPower, $"Cannot execute order with id {order.InternalId}, insufficient funds to execute order."); Portfolio.Log(LogLevel.Error, response.ErrorMessage); HandleOrderTicketEvent(OrderTicketEvent.Error(order.InternalId, $"Insufficent capital to execute order")); return(response); } //Check if broker accepts order at this moment try { if (!Portfolio.BrokerModel.CanSubmitOrder(order, out var message)) { //Broker model did not accept this order order.State = OrderState.Invalid; var response = OrderTicketResponse.Error(order.InternalId, OrderTicketResponseErrorCode.BrokerageModelRefusedToSubmitOrder, $"Order with id {order.InternalId}: {message}"); Portfolio.Log(LogLevel.Error, ""); HandleOrderTicketEvent(OrderTicketEvent.Error(order.InternalId, $"Broker model of type {Portfolio.BrokerModel.BrokerType} declared order cannot be submitted, message: {message}")); return(response); } } catch (Exception exc) { _log.Error(exc, $"Could not run CanSubmitOrder on order with id {order.InternalId}, please check the implemented logic"); order.State = OrderState.Invalid; return(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.ProcessingError, $"Current order with id {pendingorder.OrderId} cannot be processed due to error")); } //Try to execute this order to the broker connection attached bool ordersuccess = false; try { ordersuccess = BrokerConnection.SubmitOrder(pendingorder); } catch (Exception exc) { _log.Error(exc); } //Check if placing the order was a success if (!ordersuccess) { order.State = OrderState.Invalid; var submitmessage = "BrokerConnection failed to place order"; var response = OrderTicketResponse.Error(order.InternalId, OrderTicketResponseErrorCode.BrokerageFailedToSubmitOrder, submitmessage); Portfolio.Log(LogLevel.Error, submitmessage); HandleOrderTicketEvent(OrderTicketEvent.Error(order.InternalId, submitmessage)); return(response); } order.State = OrderState.Submitted; return(OrderTicketResponse.Processed(order.InternalId)); }
/// <summary> /// Process any simulated orders /// </summary> /// <param name="datapoints"></param> protected virtual void ProcessSimulatedOrders(IEnumerable <DataPoint> datapoints) { //Check if we need to check this if (OrderCount == 0) { return; } //Only check on tick data and bars var datapointsused = datapoints.Where(x => x.DataType == DataType.QuoteBar || x.DataType == DataType.TradeBar || x.DataType == DataType.Tick); //Triggered orders List <PendingOrder> triggered = new List <PendingOrder>(); //Get all orders that need to be simulated internally foreach (var item in OrderTracker.PendingOrders .Where(x => !x.OrderState.IsDone()) .Where(x => x.IsSimulated) .Join(datapointsused, x => x.Security.Ticker, x => x.Ticker, (po, dp) => new { PendingOrder = po, DataPoint = dp }) .ToArray()) { //Check if already triggered if (triggered.Select(x => x.OrderId).Contains(item.PendingOrder.OrderId)) { continue; } //Get Data var currentprices = item.DataPoint.OrderPrice(item.PendingOrder.Security, item.PendingOrder.Order.Direction); var order = item.PendingOrder.Order; //Create function for converting to a market order SubmitOrderTicket CreateSubmitTicket() => SubmitOrderTicket.MarketOrder(item.PendingOrder.FundId, order.Security, order.Quantity); //logging function void Log(decimal price) => _log.Debug($"Simulated order of type {order.Type} with id {order.InternalId} was triggered at price {price} for processing"); string CreateComment() => $"Sending market order for simulated order {item.PendingOrder.OrderId} and order type {OrderType.Limit}. OldComment: {item.PendingOrder.Comment}"; //Check order type and logic switch (order) { case LimitOrder limitorder: { //Check if limit order price is triggered if (limitorder.IsTriggered(currentprices, out decimal price)) { //create a new order from this order, as it will be converted to a market order Log(price); var norder = CreateSubmitTicket(); norder.Comment = CreateComment(); SubmitOrder(norder); //Add to triggered triggered.Add(item.PendingOrder); } break; } case StopLimitOrder stoplimitorder: { //Check if stop limit order price is triggered if (stoplimitorder.IsTriggered(currentprices, out decimal price)) { //create a new order from this order, as it will be converted to a market order Log(price); var norder = CreateSubmitTicket(); norder.Comment = CreateComment(); SubmitOrder(norder); //Add to triggered triggered.Add(item.PendingOrder); } break; } case StopMarketOrder stoporder: { //Check if stop order price is triggered if (stoporder.IsTriggered(currentprices, out decimal price)) { //create a new order from this order, as it will be converted to a market order Log(price); var norder = CreateSubmitTicket(); norder.Comment = CreateComment(); SubmitOrder(norder); //Add to triggered triggered.Add(item.PendingOrder); } break; } case MarketOnCloseOrder marketOnCloseOrder: { //Check if stop order price is triggered if (marketOnCloseOrder.IsTriggered()) { //create a new order from this order, as it will be converted to a market order var norder = CreateSubmitTicket(); norder.Comment = CreateComment(); SubmitOrder(norder); //Add to triggered triggered.Add(item.PendingOrder); } break; } case MarketOnOpenOrder marketOnOpenOrder: { //Check if stop order price is triggered if (marketOnOpenOrder.IsTriggered()) { //create a new order from this order, as it will be converted to a market order var norder = CreateSubmitTicket(); norder.Comment = CreateComment(); SubmitOrder(norder); //Add to triggered triggered.Add(item.PendingOrder); } break; } default: _log.Error($"Simulated order of type {item.PendingOrder.Order.Type} is not supported, removing order!"); triggered.Add(item.PendingOrder); break; } } //Remove all triggered orders triggered.ForEach(x => OrderTracker.TryRemoveOrder(x.OrderId)); }
/// <summary> /// Calculate order size for signal order /// </summary> /// <param name="ticket"></param> /// <param name="state"></param> /// <param name="weight"></param> /// <returns></returns> public virtual decimal OrderQuantity(SubmitOrderTicket ticket, SecurityState state, decimal weight) => throw new NotImplementedException("OrderQuantity should be implemented for a money management module to function.");