public void Redistribute_TotalWeightLessThanOne_RedistributedWeightIsAprroxOne() { var weights = new StockWeights { { "TLV", 0.1m }, { "FP", 0.2m }, { "EL", 0.3m } }; weights = weights.Redistribute(); Assert.IsTrue(weights.Sum(w => w.Value).IsApproxOne()); }
public void Redistribute_TotalWeightAlreadyOne_WeightsRemainUnchanged() { var weights = new StockWeights { { "TLV", 0.3m }, { "FP", 0.6m }, { "EL", 0.1m } }; weights = weights.Redistribute(); Assert.AreEqual(weights["TLV"], 0.3); Assert.AreEqual(weights["FP"], 0.6); Assert.AreEqual(weights["EL"], 0.1); }
public void AdjustWeights_EmptyPortfolio_ToBuyWeightsCorrectlyCalculated() { var currentWeights = new StockWeights(); var targetWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.3m }, { "EL", 0.5m } }; var strategy = new FollowTargetAdjustmentStrategy(); var toBuyWeights = strategy.AdjustWeights(currentWeights, targetWeights, toBuyInverseRatio: 0); Assert.AreEqual(0.2m, toBuyWeights["TLV"]); Assert.AreEqual(0.3m, toBuyWeights["FP"]); Assert.AreEqual(0.5m, toBuyWeights["EL"]); }
public void AdjustWeights_CurrentWeightHigherThanTargetWeight_WouldNormallyResultInNegativeWeight_ToBuyWeightIsRemoved() { var currentWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.2m }, { "EL", 0.6m } }; var targetWeights = new StockWeights { { "TLV", 0.1m }, { "FP", 0.3m }, { "EL", 0.5m }, { "SNG", 0.1m } }; var strategy = new FollowTargetAdjustmentStrategy(); var toBuyWeights = strategy.AdjustWeights(currentWeights, targetWeights, toBuyInverseRatio: 2); Assert.IsFalse(toBuyWeights.ContainsKey("TLV")); }
public void AdjustWeights_InverseToBuyRatioVeryHigh_ToBuyWeightsSumEqualsOneIsCorrecltyEnforced() { var currentWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.2m }, { "EL", 0.6m } }; var targetWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.3m }, { "EL", 0.5m } }; var strategy = new FollowTargetAdjustmentStrategy(); var toBuyWeights = strategy.AdjustWeights(currentWeights, targetWeights, toBuyInverseRatio: 10); Assert.IsTrue(toBuyWeights.Sum(w => w.Value).IsApproxOne()); }
public void AdjustWeights_WeightsBellowMinimalWeightAreCutOff_CutOffWeightIsRedistributed() { var currentWeights = new StockWeights { { "TLV", 0.08m }, { "SNG", 0.09m }, { "FP", 0.22m }, { "EL", 0.61m } }; var targetWeights = new StockWeights { { "TLV", 0.08m }, { "SNG", 0.09m }, { "FP", 0.22m }, { "EL", 0.61m } }; var strategy = new MinOrderValueCutOffStrategy(new FollowTargetAdjustmentStrategy(), 0.1m); var toBuyWeights = strategy.AdjustWeights(currentWeights, targetWeights, toBuyInverseRatio: 2); Assert.IsFalse(toBuyWeights.ContainsKey("TLV")); Assert.IsTrue(toBuyWeights.Sum(w => w.Value).IsApproxOne()); }
public void BuildPortfolio_AtLeastOneTargetWeightAboveAboveOneHundredPercent_ThrowsArgumentException() { var prices = new StockPrices { { "TLV", 10 }, { "FP", 20 }, { "EL", 30 } }; var targetWeights = new StockWeights { { "TLV", 1.2m }, { "FP", 0.3m }, { "EL", 0.5m } }; var builder = new PortfolioBuilder() .UsePrices(prices) .UseTargetWeights(targetWeights) .UseToBuyAmount(100); Assert.Throws <ArgumentException>(() => builder.Build()); }
public void AdjustWeights_InverseToBuyRatioIsOne_ToBuyWeightsCorrectlyCalculated() // Simulates second buying sessions => inverseToBuyRatio = 1 { var currentWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.2m }, { "EL", 0.6m } }; var targetWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.3m }, { "EL", 0.5m } }; var strategy = new FollowTargetAdjustmentStrategy(); var toBuyWeights = strategy.AdjustWeights(currentWeights, targetWeights, toBuyInverseRatio: 1); Assert.AreEqual(0.2m, toBuyWeights["TLV"]); Assert.AreEqual(0.4m, toBuyWeights["FP"]); Assert.AreEqual(0.4m, toBuyWeights["EL"]); }
public void BuildPortfolio_SumOfTargetWeightApproxEqualWithOneHundredPercent_DoesNotThrow() { var prices = new StockPrices { { "TLV", 10 }, { "FP", 20 }, { "EL", 30 } }; var targetWeights = new StockWeights { { "TLV", 0.21m }, { "FP", 0.3m }, { "EL", 0.5m } }; var builder = new PortfolioBuilder() .UsePrices(prices) .UseTargetWeights(targetWeights) .UseToBuyAmount(100); Assert.DoesNotThrow(() => builder.Build()); }
public void BuildPortfolio_MissingPriceForTargetSymbol_ThrowsArgumentException() { var prices = new StockPrices { { "TLV", 10 }, { "FP", 20 }, { "EL", 30 } }; var targetWeights = new StockWeights { { "TLV", 0.21m }, { "FP", 0.3m } }; var builder = new PortfolioBuilder() .UsePrices(prices) .UseTargetWeights(targetWeights) .UseToBuyAmount(100); Assert.Throws <ArgumentException>(() => builder.Build()); }
public void BuildPortfolio_InitialStocks_EnoughAvailableAmount_NewStocksAreAdded() { var prices = new StockPrices { { "TLV", 10 }, { "FP", 20 }, { "EL", 30 } }; var stocks = new[] { new Stock("TLV") { Count = 2, Price = 10, Weight = 0.2m }, // should add 2 new Stock("FP") { Count = 1, Price = 20, Weight = 0.2m }, // should add 3 new Stock("EL") { Count = 2, Price = 30, Weight = 0.6m } // should add 2 }; var targetWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.3m }, { "EL", 0.5m } }; // target stock 4 * TLV + 4 * FP + 4 * EL = 240 var portfolio = new PortfolioBuilder() .UsePrices(prices) .UseStocks(stocks) .UseWeightAdjustmentStrategy(new FollowTargetAdjustmentStrategy()) .UseTargetWeights(targetWeights) .UseToBuyAmount(140) .Build(); Assert.AreEqual(4, portfolio["EL"].Count); Assert.AreEqual(4, portfolio["FP"].Count); Assert.AreEqual(4, portfolio["TLV"].Count); Assert.AreEqual(240, portfolio.TotalValue); }
public void AdjustWeights_TargetWeightsHasMoreSymbols_ToBuyWeightsCorrectlyCalculated() { var currentWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.2m }, { "EL", 0.6m } }; var targetWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.2m }, { "EL", 0.5m }, { "SNG", 0.1m } }; var strategy = new FollowTargetAdjustmentStrategy(); var toBuyWeights = strategy.AdjustWeights(currentWeights, targetWeights, toBuyInverseRatio: 2); Assert.AreEqual(0.2m, toBuyWeights["TLV"]); Assert.AreEqual(0.2m, toBuyWeights["FP"]); Assert.AreEqual(0.3m, toBuyWeights["EL"]); Assert.AreEqual(0.3m, toBuyWeights["SNG"]); }
public void BuildPortfolio_NotEnoughForAny_PortfolioValueIsZero() { var prices = new StockPrices { { "TLV", 10 }, { "FP", 20 } }; var targetWeights = new StockWeights { { "TLV", 0.5m }, { "FP", 0.5m } }; var portfolio = new PortfolioBuilder() .UsePrices(prices) .UseTargetWeights(targetWeights) .UseToBuyAmount(9) .Build(); Assert.Zero(portfolio.TotalValue); }
public void BuildPortfolio_NotEnoughForAll_PortfolioValueIsCorrect() { var prices = new StockPrices { { "TLV", 10 }, { "FP", 20 } }; var targetWeights = new StockWeights { { "TLV", 0.5m }, { "FP", 0.5m } }; var portfolio = new PortfolioBuilder() .UsePrices(prices) .UseTargetWeights(targetWeights) .UseToBuyAmount(20) .Build(); Assert.AreEqual(10, portfolio.TotalValue); }
public StockWeights AdjustWeights(StockWeights currentWeights, StockWeights targetWeights, decimal toBuyInverseRatio) { // Adjust weights based on initial strategy var adjustedWeights = _innerStrategy.AdjustWeights(currentWeights, targetWeights, toBuyInverseRatio); // Cut off insufficient weights trying to maximize number of resulting weigthts targetWeights = CutOffAndRedistribute(adjustedWeights); return(targetWeights); StockWeights CutOffAndRedistribute(StockWeights weights) { weights = weights.Redistribute(); if (weights.Any(w => w.Value <= _minWeight)) { weights = CutOffAndRedistribute(weights.Take(weights.Count - 1).AsStockWeights()); } return(weights); } }
public void BuildPortfolio_MinOrderValueIsSet_StockTotalValueLessThanMinimal_StockIsNotAddedToPortfolio() { var prices = new StockPrices { { "TLV", 10 }, { "FP", 20 }, { "EL", 30 } }; var targetWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.3m }, { "EL", 0.5m } }; var portfolio = new PortfolioBuilder() .UsePrices(prices) .UseTargetWeights(targetWeights) .UseToBuyAmount(140) .UseMinOrderValue(31) .Build(); Assert.IsFalse(portfolio.Any(s => s.Symbol == "TLV")); }
public StockWeights AdjustWeights(StockWeights currentWeights, StockWeights targetWeights, decimal toBuyInverseRatio) { // Starting from: cn * C + bn * B = tn * T // , where cn - weight for stock 'n', C - total current stock value // , bn - to buy weight for stock 'n', B - to buy available amount // , tn - target weight for stock 'n', T - total final stock value // We get: bn = tn * (C / B + 1) - cn * (C / B), where C / B is the to buy inverse ratio // With first constraint that bn >= 0 which results in: cn <= tn * (1 + B / C) // With second constraint that b1 + b2 + ... + bn = 1 // Apply the first constraint and remove the target weights that don't fulfill it var allowedWeights = targetWeights .Where(w => toBuyInverseRatio == 0 || // 0 only on the first buy SafeGetCurrentWeight(w.Key) <= w.Value * (1 + 1 / toBuyInverseRatio)) .OrderByDescending(w => w.Value) .AsStockWeights(); // Redistribute the removed weights to the other weights allowedWeights = allowedWeights.Redistribute(); var toBuyWeights = allowedWeights .Select(w => (w.Key, w.Value * (toBuyInverseRatio + 1) - SafeGetCurrentWeight(w.Key) * toBuyInverseRatio)) .AsStockWeights(); // Enforce the second constraint return(toBuyWeights.Redistribute()); decimal SafeGetCurrentWeight(string symbol) { if (currentWeights.ContainsKey(symbol)) { return(currentWeights[symbol]); } return(0); } }
public void BuildPortfolio_AlmostEnoughForAll_SmallestEligibleStockIsDecreased() { var prices = new StockPrices { { "TLV", 10 }, { "FP", 20 }, { "EL", 30 } }; var targetWeights = new StockWeights { { "TLV", 0.2m }, { "FP", 0.3m }, { "EL", 0.5m } }; var portfolio = new PortfolioBuilder() .UsePrices(prices) .UseTargetWeights(targetWeights) .UseToBuyAmount(119) .Build(); Assert.AreEqual(2, portfolio["EL"].Count); Assert.AreEqual(2, portfolio["FP"].Count); Assert.AreEqual(1, portfolio["TLV"].Count); Assert.AreEqual(110, portfolio.TotalValue); }
public StockWeights AdjustWeights(StockWeights currentWeights, StockWeights targetWeights, decimal toBuyInverseRatio) { return(targetWeights); }
/// <summary> /// Equaly redistributes weights such that their sum is 1. /// </summary> /// <remarks> /// This applies for both cases, weights over 1 or less than 1. /// </remarks> public static StockWeights Redistribute(this StockWeights weights) { var totalWeight = weights.Sum(w => w.Value); // this needs to become 1 after redistribution return(weights.Select(w => (w.Key, w.Value / totalWeight)).AsStockWeights()); }