public List<Deal> Split(Order order) { if (order == null) throw new ArgumentNullException("order"); CheckOrderAmount(order); // This is the simplest-stupid split logic for 2 brokers // Traverse all possible deal combinations and select the one with minimum total cost decimal broker1Amount; decimal broker2Amount; var deals = new List<Deal>(); // Start from putting the maximum possible amount to broker1 if (order.Amount > _broker1.MaxAmount) { broker1Amount = _broker1.MaxAmount; broker2Amount = order.Amount - _broker1.MaxAmount; deals.Add(_broker1.Estimate(broker1Amount)); deals.Add(_broker2.Estimate(broker2Amount)); } else { broker1Amount = order.Amount; broker2Amount = 0; deals.Add(_broker1.Estimate(broker1Amount)); } var minCost = deals.Sum(q => q.TotalCost); // Now, while broker1 has what to substract, and broker2 has capacity for addition, check all possible combinations while (broker1Amount > 0 && broker2Amount < _broker2.MaxAmount) { broker1Amount -= AmountDelta; broker2Amount += AmountDelta; var currentDeals = new List<Deal>(); if (broker1Amount > 0) currentDeals.Add(_broker1.Estimate(broker1Amount)); if (broker2Amount > 0) currentDeals.Add(_broker2.Estimate(broker2Amount)); var totalCost = currentDeals.Sum(d => d.TotalCost); if (totalCost < minCost) { minCost = totalCost; deals = currentDeals; } } // Initialize Order property in all deals foreach (var deal in deals) { deal.Order = order; } return deals; }
public void Insert(Order order) { if (order == null) throw new ArgumentNullException("order"); //NOTE: In real application Orders and Deals are saved into different tables (with 1:N relationship). // Here, for simplicity, we just store Order object in memory. _orders.Add(order); }
public void Split_OptimalIsAllAmountToBroker2_ReturnsBroker2Deal() { var order = new Order(Build_Client(), OrderType.Buy, 100); // Broker2 always returns optimal deal var broker2OptimalDeal = new Deal(_broker2Mock.Object, 500, 1, 0.01m); Setup_Broker1Mock_Estimate(new Deal(_broker1Mock.Object, 1000, 1, 0.01m)); Setup_Broker2Mock_Estimate(broker2OptimalDeal); var expectedDeals = new List<Deal> { broker2OptimalDeal }; var actualDeals = _orderSplitService.Split(order); actualDeals.ShouldAllBeEquivalentTo(expectedDeals); }
/// <summary> /// Executes the specified order. /// This is a main enty point into application. /// In real application we can expose it via REST API. But please note: /// - This is a Domain Service, and it expects already prepared Order domain object. /// - In case of REST API, we would need to create an Application Service that gets Order Data Transfer Object (DTO), wich then would be transformed into our domain Order object. /// - This, for example, would include validating whether Client specified in order exists in our system. /// </summary> /// <param name="order">The order.</param> /// <returns></returns> public decimal Execute(Order order) { if (order == null) throw new ArgumentNullException("order"); // Split order on deals and then execute them order.Deals = _orderSplitService.Split(order); // NOTE: In real application, deals execution and inserting order must be executed in scope of one transaction foreach (var deal in order.Deals) { deal.Execute(); } // Keep audit trail of order _orderRepository.Insert(order); var totalCost = order.Deals.Sum(q => q.TotalCost); return totalCost; }
public void Split_OptimalIsToSplitOrder_ReturnsBroker2Deal() { var order = new Order(Build_Client(), OrderType.Buy, 100); // Only one combination of Broker1 and Broker2 deals is optimal var broker1OptimalDeal = new Deal(_broker1Mock.Object, 60, 1, 0.01m); var broker2OptimalDeal = new Deal(_broker2Mock.Object, 40, 1, 0.01m); Setup_Broker1Mock_Estimate(new Deal(_broker1Mock.Object, 1000, 1, 0.01m)); Setup_Broker1Mock_Estimate(60, broker1OptimalDeal); Setup_Broker2Mock_Estimate(new Deal(_broker2Mock.Object, 1000, 1, 0.01m)); Setup_Broker2Mock_Estimate(40, broker2OptimalDeal); var expectedDeals = new List<Deal> { broker1OptimalDeal, broker2OptimalDeal }; var actualDeals = _orderSplitService.Split(order); actualDeals.ShouldAllBeEquivalentTo(expectedDeals); }
private void ExecuteOrderTestCase(Client client, OrderType orderType, decimal amount, decimal expectedTotalCost) { var order = new Order(client, orderType, amount); var actualTotalCost = _orderExecutionService.Execute(order); Assert.AreEqual(expectedTotalCost, actualTotalCost); }
private void CheckOrderAmount(Order order) { var maxAmount = _broker1.MaxAmount + _broker2.MaxAmount; if (order.Amount > maxAmount) throw new InvalidOperationException(string.Format("Order amount ({0}) is greater than maximum amount possible for execution by all brokers ({1}).", order.Amount, maxAmount)); }
public void Split_OrderAmountIsToLarge_Exception() { var order = new Order(Build_Client(), OrderType.Buy, 210); Action act = () => _orderSplitService.Split(order); act.ShouldThrow<InvalidOperationException>().WithMessage("Order amount (210) is greater than maximum amount possible for execution by all brokers (200)."); }