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).");
        }