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