Exemple #1
0
        public ActionResult <RebalanceResult> Rebalance(string id)
        {
            {
                Portfolio portfolio = _portfolioDataAccess.GetPortfolio(id).GetAwaiter().GetResult();

                var symbols = portfolio.AllStocks.Select(s => s.Symbol).ToList();
                var quotes  = _alphaClientLogic.GetQuotes(symbols).GetAwaiter().GetResult();

                var dataSet = new RebalanceDataSet
                {
                    Portfolio = portfolio,
                    Quotes    = quotes
                };

                return(_rebalanceLogic.Rebalance(dataSet, false));
            }
        }
        public async Task Rebalance()
        {
            var portfolio = await _portfolioDataAccess.GetPortfolio("5d80d0587d2d4657d8e1fe8f");

            var symbols = portfolio.AllStocks.Select(s => s.Symbol).ToList();
            var quotes  = await _alphaClient.GetQuotes(symbols);

            portfolio.CashOnHand += 500;

            RebalanceDataSet dataSet = new RebalanceDataSet
            {
                Portfolio = portfolio,
                Quotes    = quotes
            };

            var result = _rebalanceLogic.Rebalance(dataSet, false);

            foreach (var action in result.Actions)
            {
                Console.WriteLine($"{action.ActionType} {action.Amount} shares of {action.Symbol}");
            }

            Console.WriteLine($"${result.RemainingCashOnHand} remaining");
        }
        public async Task <List <ScenarioResult> > RunScenario(RunScenarioDataSet dataSet)
        {
            if (dataSet.CashInfluxCadence != CadenceTypeEnum.Weekly)
            {
                throw new Exception($"{dataSet.CashInfluxCadence} cadence not yet supported.");
            }

            ConcurrentQueue <ScenarioResult> results = new ConcurrentQueue <ScenarioResult>();

            await Task.WhenAll(dataSet.Portfolios.Select(p => Task.Run(() =>
            {
                var portfolio = p.Copy();

                var symbols = portfolio.AllStocks.Select(a => a.Symbol).ToList();

                var currentAllocations = symbols.Select(s => new StockAllocation
                {
                    Symbol            = s,
                    DesiredAmount     = 0,
                    DesiredAmountType = AllocationTypeEnum.StockAmount
                }).ToList();

                decimal cashOnHand = dataSet.InitialInvestment;
                foreach (var period in dataSet.History)
                {
                    var quotes = period.Stocks.Select(s => new Quote
                    {
                        Symbol    = s.Symbol,
                        Price     = s.PeriodData.AdjustedClose,
                        QuoteDate = period.ClosingDate
                    }).ToList();

                    portfolio.CashOnHand = cashOnHand;
                    foreach (var allocation in portfolio.AllStocks)
                    {
                        var currentAllocation    = currentAllocations.Find(s => s.Symbol == allocation.Symbol);
                        allocation.CurrentShares = currentAllocation.CurrentShares;
                    }

                    var rebalanceDataSet = new RebalanceDataSet
                    {
                        Portfolio = portfolio,
                        Quotes    = quotes
                    };

                    var rebalanceResult = Rebalance(rebalanceDataSet);

                    cashOnHand = rebalanceResult.RemainingCashOnHand + dataSet.CashInfluxAmount;

                    foreach (var action in rebalanceResult.Actions)
                    {
                        var actualAllocation = currentAllocations.Find(a => a.Symbol == action.Symbol);
                        if (action.ActionType == RebalanceActionTypeEnum.Buy)
                        {
                            actualAllocation.CurrentShares += action.Amount;
                        }
                        else
                        {
                            actualAllocation.CurrentShares -= action.Amount;
                        }
                    }
                }

                decimal finalPortfolioValue = cashOnHand - dataSet.CashInfluxAmount;
                var lastPeriod = dataSet.History.Last();

                foreach (var allocation in currentAllocations)
                {
                    finalPortfolioValue += allocation.CurrentShares *lastPeriod.Stocks.Find(s => s.Symbol == allocation.Symbol).PeriodData.AdjustedClose;
                }

                var totalCashInvested = (dataSet.InitialInvestment + dataSet.CashInfluxAmount * (dataSet.History.Count - 1));
                var result            = new ScenarioResult
                {
                    PercentIncrease     = (finalPortfolioValue / totalCashInvested) - 1,
                    StartDate           = dataSet.History.First().ClosingDate,
                    EndDate             = lastPeriod.ClosingDate,
                    FinalPortfolioValue = finalPortfolioValue,
                    TotalCashInvested   = totalCashInvested
                };

                results.Enqueue(result);
            })));

            return(results.ToList());
        }
        public RebalanceResult Rebalance(RebalanceDataSet dataSet, bool quickRebalance = true)
        {
            var stocks = dataSet.Portfolio.AllStocks.Select(a => new RebalanceItem
            {
                Symbol            = a.Symbol,
                CurrentShares     = a.CurrentShares,
                DesiredAmountType = a.DesiredAmountType,
                DesiredAmount     = a.DesiredAmount,
                Price             = dataSet.Quotes.First(q => string.Equals(q.Symbol, a.Symbol, StringComparison.InvariantCultureIgnoreCase)).Price
            }).ToList();

            var portfolioValue = dataSet.Portfolio.CashOnHand + stocks.Sum(s => s.CurrentShares * s.Price);

            var amountStocks = stocks.Where(s => s.DesiredAmountType == AllocationTypeEnum.StockAmount).ToList();
            var cashStocks   = stocks.Where(s => s.DesiredAmountType == AllocationTypeEnum.CashAmount).ToList();

            List <RebalanceAction> actions = new List <RebalanceAction>();

            foreach (var stock in amountStocks)
            {
                if (stock.CurrentShares != stock.DesiredAmount)
                {
                    var dif    = (int)stock.DesiredAmount - stock.CurrentShares;
                    var action = dif > 0 ? RebalanceActionTypeEnum.Buy : RebalanceActionTypeEnum.Sell;

                    actions.Add(new RebalanceAction
                    {
                        Symbol     = stock.Symbol,
                        ActionType = action,
                        Amount     = Math.Abs(dif)
                    });
                }

                portfolioValue -= stock.DesiredAmount * stock.Price;
            }

            foreach (var stock in cashStocks)
            {
                var actualValue = stock.CurrentShares * stock.Price;
                var dif         = stock.DesiredAmount - actualValue;   // (-100)

                var difAmount = (int)Math.Truncate(dif / stock.Price); // 2 (-45)
                if (difAmount > 0)
                {
                    difAmount -= 1; //Stay below max amount
                }
                portfolioValue -= (stock.CurrentShares + dif) * stock.Price;

                if (difAmount != 0)
                {
                    var action = difAmount > 0 ? RebalanceActionTypeEnum.Buy : RebalanceActionTypeEnum.Sell;

                    actions.Add(new RebalanceAction
                    {
                        Symbol     = stock.Symbol,
                        ActionType = action,
                        Amount     = Math.Abs(difAmount)
                    });
                }
            }

            var percentageStocks = stocks.Where(s => s.DesiredAmountType == AllocationTypeEnum.Percentage).ToList();
            var idealTotalFactor = percentageStocks.Sum(s => s.DesiredAmount);

            var factors = percentageStocks.Select(s => new
            {
                s.Symbol,
                s.CurrentShares,
                s.DesiredAmount,
                s.DesiredAmountType,
                s.Price,
                DesiredFactor = s.DesiredAmount / idealTotalFactor,
                EndAmount     = (int)Math.Truncate((((s.DesiredAmount / idealTotalFactor) * portfolioValue) / s.Price))
            }).Select(s => new RebalancePercentageItem
            {
                Symbol            = s.Symbol,
                CurrentShares     = s.CurrentShares,
                DesiredAmount     = s.DesiredAmount,
                DesiredAmountType = s.DesiredAmountType,
                Price             = s.Price,
                DesiredFactor     = s.DesiredFactor,
                CurrentFactor     = (s.EndAmount * s.Price) / portfolioValue,
                EndAmount         = s.EndAmount
            }).ToList();

            var usedUpAmount = factors.Sum(f => f.Price * f.EndAmount);

            while (true)
            {
                var amountRemaining = portfolioValue - usedUpAmount;

                var availableFactors = factors.Where(f => f.Price < amountRemaining).ToList();

                if (!availableFactors.Any())
                {
                    break;
                }

                var factorToUse = quickRebalance
                    ? availableFactors.Where(f => f.Price < amountRemaining).OrderByDescending(f => f.FactorDifference).FirstOrDefault()
                    : availableFactors.Aggregate((f1, f2) =>
                {
                    var f1Adjusted = ((f1.EndAmount + 1) * f1.Price) / portfolioValue;
                    var f2Adjusted = ((f2.EndAmount + 1) * f2.Price) / portfolioValue;
                    var f1Dif      = Math.Abs(f1.DesiredFactor - f1Adjusted);
                    var f2Dif      = Math.Abs(f2.DesiredFactor - f2Adjusted);
                    return(f1Dif < f2Dif ? f1 : f2);
                });

                if (factorToUse == null)
                {
                    break;
                }

                var adjustedFactor = ((factorToUse.EndAmount + 1) * factorToUse.Price) / portfolioValue;

                factorToUse.EndAmount    += 1;
                factorToUse.CurrentFactor = adjustedFactor;
                usedUpAmount += factorToUse.Price;

                //var factorsInPlay = factors.Where(f => f.Price < amountRemaining).OrderByDescending(f => f.FactorDifference).ToList();
                //if (!factorsInPlay.Any())
                //{
                //    break;
                //}
                //
                //bool filled = false;
                //foreach (var factor in factorsInPlay)
                //{
                //    var adjustedFactor = ((factor.EndAmount + 1) * factor.Price) / portfolioValue;
                //    var newDifference = Math.Abs(factor.DesiredFactor - adjustedFactor);
                //    if (factorsInPlay.Count == 1 || factorsInPlay.Any(f => f.FactorDifference > newDifference))
                //    {
                //        factor.EndAmount += 1;
                //        factor.CurrentFactor = adjustedFactor;
                //        filled = true;
                //        break;
                //    }
                //}
                //
                //if (!filled)
                //    break;
            }

            actions.AddRange(factors.Where(f => f.EndAmount != f.CurrentShares).Select(f => new RebalanceAction
            {
                Symbol     = f.Symbol,
                ActionType = f.EndAmount > f.CurrentShares ? RebalanceActionTypeEnum.Buy : RebalanceActionTypeEnum.Sell,
                Amount     = Math.Abs(f.EndAmount - f.CurrentShares)
            }));

            portfolioValue -= factors.Sum(f => f.Price * f.EndAmount);

            if (portfolioValue < 0)
            {
                throw new Exception("Dataset resulted in a negative balance");
            }

            return(new RebalanceResult
            {
                RemainingCashOnHand = portfolioValue,
                Actions = actions
            });
        }