public static Portfolio GenerateDefaultPortfolio() { Security goog = new Security {Symbol = "goog"}; Security msft = new Security {Symbol = "msft"}; Security aapl = new Security {Symbol = "aapl"}; Portfolio portfolio = new Portfolio { Name = "po' boy" }; Account mandingo = new Account { Name = "mandingo", Portfolio = portfolio }; AddPosition(mandingo, goog, 100M, 12.34M); AddPosition(mandingo, aapl, 200M, 23.45M); portfolio.Accounts.Add(mandingo); Account took = new Account { Name = "took", Portfolio = portfolio }; AddPosition(took, msft, 100M, 34.56M); portfolio.Accounts.Add(took); return portfolio; }
public IEnumerable<CategoryWeight> GetWeights(Category category, Security security) { if (!_categoryCache.ContainsKey(category.Name)) throw new CategoryNotFoundException(string.Format("Category '{0}' not found.", category.Name)); if (!_securityCache.ContainsKey(security)) throw new SecurityNotFoundException(string.Format("Security '{0}' not found.", security.Symbol)); return _securityCache[security].Where(cw => category.Values.Contains(cw.Value)); }
public Security GetBySymbol(string symbol) { if (!_securityCache.ContainsKey(symbol)) _securityCache[symbol] = new Security { Symbol = symbol }; return _securityCache[symbol]; }
public void AddWeights(Category category, Security security, IEnumerable<CategoryWeight> weights) { if (!_categoryCache.ContainsKey(category.Name)) throw new CategoryNotFoundException(string.Format("Category '{0}' not found.", category.Name)); if (!_securityCache.ContainsKey(security)) { _securityCache.Add(security, new List<CategoryWeight>()); } _securityCache[security].AddRange(weights.Except(_securityCache[security])); }
public void When_Updating_Account_Then_Update_Is_Atomic() { var portfolio = TestDataGenerator.GenerateDefaultPortfolio(); var mandingo = portfolio.Accounts.Single(a => a.Name.Equals("mandingo", StringComparison.InvariantCultureIgnoreCase)); Security yvr = new Security { Symbol = "yvr" }; Security goog = new Security { Symbol = "goog" }; var transactions = new List<Transaction>(2) { new Transaction { Account = mandingo, Date = DateTime.UtcNow, Price = 10M, Security = yvr, Shares = 100M, Type = TransactionType.Buy }, new Transaction { Account = mandingo, Date = DateTime.UtcNow, Price = 10M, Security = goog, Shares = 10M, Type = TransactionType.Sell }, new Transaction { Account = mandingo, Date = DateTime.UtcNow, Price = 10M, Security = new Security { Symbol = "not_owned" }, Shares = 100M, Type = TransactionType.Sell } }; var portfolioService = new PortfolioService(portfolio); Assert.Throws<Exception>(() => portfolioService.UpdateWith(transactions)); Assert.That(mandingo.Positions.Count, Is.EqualTo(2)); var googPosition = mandingo.Positions.Single(p => p.Security.Symbol.Equals(goog.Symbol, StringComparison.InvariantCultureIgnoreCase)); Assert.That(googPosition.Shares, Is.EqualTo(100M)); var aaplPosition = mandingo.Positions.Single(p => p.Security.Symbol.Equals("aapl", StringComparison.InvariantCultureIgnoreCase)); Assert.That(aaplPosition.Shares, Is.EqualTo(200M)); var took = portfolio.Accounts.Single(a => a.Name.Equals("took", StringComparison.InvariantCultureIgnoreCase)); var msftPosition = took.Positions.Single(p => p.Security.Symbol.Equals("msft", StringComparison.InvariantCultureIgnoreCase)); Assert.That(msftPosition.Shares, Is.EqualTo(100M)); }
public void When_Updating_Account_With_Buy_Transaction_And_No_Position_Then_Account_Gains_Position() { // setup Security goog = new Security { Symbol = "goog" }; var portfolio = TestDataGenerator.GenerateEmptyPortfolio(); var mandingo = portfolio.Accounts.Single(a => a.Name.Equals("mandingo", StringComparison.InvariantCultureIgnoreCase)); var portfolioService = new PortfolioService(portfolio); var transaction = new Transaction { Account = mandingo, Date = DateTime.UtcNow, Price = 0M, Security = goog, Shares = 10M, Type = TransactionType.Buy }; // execute portfolioService.UpdateWith(new List<Transaction> { transaction }); // verify Assert.That(mandingo.Positions.Single().Shares, Is.EqualTo(10M)); }
public void GetCategoriesAndWeights(out IEnumerable<Category> categories, out IEnumerable<CategoryWeight> categoryWeights) { var region = new Category { Name = RegionName }; var assetClass = new Category { Name = AssetClassName }; var currency = new Category { Name = CurrencyName }; var weights = new List<CategoryWeight>(); while (_csvReader.ReadNextRecord()) { Security security; string currencyText, regionText, assetClassText; if (_csvReader.HasHeaders) { security = new Security { Symbol = _csvReader["Symbol"] }; currencyText = _csvReader["Currency"]; regionText = _csvReader["Region"]; assetClassText = _csvReader["AssetClass"]; } else { security = new Security { Symbol = _csvReader[0] }; currencyText = _csvReader[1]; regionText = _csvReader[2]; assetClassText = _csvReader[3]; } if (!region.Values.Any(v => v.Name.Equals(regionText, StringComparison.InvariantCultureIgnoreCase))) region.Values.Add(new CategoryValue { Category = region, Name = regionText }); if (!assetClass.Values.Any(v => v.Name.Equals(assetClassText, StringComparison.InvariantCultureIgnoreCase))) assetClass.Values.Add(new CategoryValue { Category = assetClass, Name = assetClassText }); if (!currency.Values.Any(v => v.Name.Equals(currencyText, StringComparison.InvariantCultureIgnoreCase))) currency.Values.Add(new CategoryValue { Category = currency, Name = currencyText }); var regionValue = region.Values.Single(v => v.Name.Equals(regionText, StringComparison.InvariantCultureIgnoreCase)); var assetClassValue = assetClass.Values.Single(v => v.Name.Equals(assetClassText, StringComparison.InvariantCultureIgnoreCase)); var currencyValue = currency.Values.Single(v => v.Name.Equals(currencyText, StringComparison.InvariantCultureIgnoreCase)); weights.Add(new CategoryWeight { Security = security, Value = regionValue, Weight = 100M }); weights.Add(new CategoryWeight { Security = security, Value = assetClassValue, Weight = 100M }); weights.Add(new CategoryWeight { Security = security, Value = currencyValue, Weight = 100M }); } categories = new List<Category> { region, assetClass, currency }; categoryWeights = weights; }
public static Portfolio GenerateFundbotPortfolio() { Security xfn = new Security { Symbol = "XFN.TO" }; Security agg = new Security { Symbol = "AGG" }; Security xiu = new Security { Symbol = "XIU.TO" }; Security cpd = new Security { Symbol = "CPD.TO" }; Security efa = new Security { Symbol = "EFA" }; Portfolio portfolio = new Portfolio { Name = Guid.NewGuid().ToString() }; Account account = new Account { Name = portfolio.Name, Portfolio = portfolio }; portfolio.Accounts.Add(account); AddPosition(account, xfn, 100M, 29.97M); AddPosition(account, agg, 27M, 1000.69M); AddPosition(account, xiu, 100M, 21.1M); AddPosition(account, cpd, 500M, 16.55M); AddPosition(account, efa, 100M, 68.24M); return portfolio; }
private static void AddPosition(Account account, Security security, decimal shares, decimal price) { account.Positions.Add(new Position { Account = account, Shares = shares, Security = security }); account.Transactions.Add(new Transaction { Account = account, Date = DateTime.UtcNow, Price = price, Security = security, Shares = shares, Type = TransactionType.Buy }); }
public static IEnumerable<CategoryWeight> GenerateFundbotWeights(IEnumerable<Category> fundbotCategories) { Security xfn = new Security { Symbol = "XFN.TO" }; Security agg = new Security { Symbol = "AGG" }; Security xiu = new Security { Symbol = "XIU.TO" }; Security cpd = new Security { Symbol = "CPD.TO" }; Security efa = new Security { Symbol = "EFA" }; var categories = fundbotCategories.ToList(); var regionCad = categories.Single(c => c.Name.Equals("Region")).Values.Single(v => v.Name.Equals("CAD")); var regionUsd = categories.Single(c => c.Name.Equals("Region")).Values.Single(v => v.Name.Equals("USD")); var regionIntl = categories.Single(c => c.Name.Equals("Region")).Values.Single(v => v.Name.Equals("INTL")); var currencyCad = categories.Single(c => c.Name.Equals("Currency")).Values.Single(v => v.Name.Equals("CAD")); var currencyUsd = categories.Single(c => c.Name.Equals("Currency")).Values.Single(v => v.Name.Equals("USD")); var assetClassEquity = categories.Single(c => c.Name.Equals("Asset Class")).Values.Single(v => v.Name.Equals("Equity")); var assetClassBonds = categories.Single(c => c.Name.Equals("Asset Class")).Values.Single(v => v.Name.Equals("Bonds")); var assetClassPreferred = categories.Single(c => c.Name.Equals("Asset Class")).Values.Single(v => v.Name.Equals("Preferred")); var weights = new List<CategoryWeight> { new CategoryWeight { Security = xfn, Value = currencyCad, Weight = 100M }, new CategoryWeight { Security = xfn, Value = regionCad, Weight = 100M }, new CategoryWeight { Security = xfn, Value = assetClassEquity, Weight = 100M }, new CategoryWeight { Security = agg, Value = currencyUsd, Weight = 100M }, new CategoryWeight { Security = agg, Value = regionUsd, Weight = 100M }, new CategoryWeight { Security = agg, Value = assetClassBonds, Weight = 100M }, new CategoryWeight { Security = xiu, Value = currencyCad, Weight = 100M }, new CategoryWeight { Security = xiu, Value = regionCad, Weight = 100M }, new CategoryWeight { Security = xiu, Value = assetClassEquity, Weight = 100M }, new CategoryWeight { Security = cpd, Value = currencyCad, Weight = 100M }, new CategoryWeight { Security = cpd, Value = regionCad, Weight = 100M }, new CategoryWeight { Security = cpd, Value = assetClassPreferred, Weight = 100M }, new CategoryWeight { Security = efa, Value = currencyUsd, Weight = 100M }, new CategoryWeight { Security = efa, Value = regionIntl, Weight = 100M }, new CategoryWeight { Security = efa, Value = assetClassEquity, Weight = 100M } }; return weights; }
public void When_Updating_Portfolio_With_Invalid_Transaction_Then_Exception_Is_Thrown() { // setup var portfolio = TestDataGenerator.GenerateEmptyPortfolio(); var portfolioService = new PortfolioService(portfolio); Security goog = new Security { Symbol = "goog" }; var transaction = new Transaction { Account = null, Date = DateTime.UtcNow, Price = 0M, Security = goog, Shares = 10M, Type = TransactionType.Buy }; // verify try { portfolioService.UpdateWith(new List<Transaction> { transaction }); } catch (Exception ex) { Assert.That(ex.Message, Is.EqualTo("One or more transactions are invalid.")); } }
public void When_Updating_Empty_Portfolio_With_Transactions_Then_Portfolio_Is_Updated_To_Correct_State() { // setup var portfolio = TestDataGenerator.GenerateEmptyPortfolio(); var portfolioService = new PortfolioService(portfolio); var mandingo = portfolio.Accounts.Single(a => a.Name.Equals("mandingo", StringComparison.InvariantCultureIgnoreCase)); var took = portfolio.Accounts.Single(a => a.Name.Equals("took", StringComparison.InvariantCultureIgnoreCase)); Security goog = new Security { Symbol = "goog" }; Security msft = new Security { Symbol = "msft" }; var transactionDate = DateTime.UtcNow; var transactions = new List<Transaction>(); transactions.Add(new Transaction { Account = mandingo, Date = transactionDate, Price = 15.25M, Security = goog, Shares = 100M, Type = TransactionType.Buy }); transactions.Add(new Transaction() { Account = took, Date = transactionDate.AddMinutes(14), Price = 21.54M, Security = msft, Shares = 50M, Type = TransactionType.Sell }); transactions.Add(new Transaction() { Account = took, Date = transactionDate, Price = 21.34M, Security = msft, Shares = 200M, Type = TransactionType.Buy }); // execute portfolioService.UpdateWith(transactions); // verify Assert.That(mandingo.Transactions.Count, Is.EqualTo(1)); Assert.That(mandingo.Positions.Count, Is.EqualTo(1)); var position = mandingo.Positions.Single(); Assert.That(position.Security.Symbol, Is.EqualTo(goog.Symbol)); Assert.That(position.Account, Is.EqualTo(mandingo)); Assert.That(position.Shares, Is.EqualTo(100M)); Assert.That(took.Transactions.Count, Is.EqualTo(2)); Assert.That(took.Positions.Count, Is.EqualTo(1)); position = took.Positions.Single(); Assert.That(position.Security.Symbol, Is.EqualTo(msft.Symbol)); Assert.That(position.Account, Is.EqualTo(took)); Assert.That(position.Shares, Is.EqualTo(150M)); }
public void When_Updating_Account_With_Transaction_That_Already_Happened_Then_New_Transaction_Is_Ignored() { var portfolio = TestDataGenerator.GenerateDefaultPortfolio(); var mandingo = portfolio.Accounts.Single(a => a.Name.Equals("mandingo", StringComparison.InvariantCultureIgnoreCase)); Security yvr = new Security { Symbol = "yvr" }; var transactions = new List<Transaction>(); transactions.AddRange(mandingo.Transactions); transactions.Add(new Transaction { Account = mandingo, Date = DateTime.UtcNow, Price = 10M, Security = yvr, Shares = 50M, Type = TransactionType.Buy }); var portfolioService = new PortfolioService(portfolio); portfolioService.UpdateWith(transactions); Assert.That(mandingo.Transactions.Count, Is.EqualTo(3)); var googPosition = mandingo.Positions.Single(p => p.Security.Symbol.Equals("goog", StringComparison.InvariantCultureIgnoreCase)); Assert.That(googPosition.Shares, Is.EqualTo(100M)); var aaplPosition = mandingo.Positions.Single(p => p.Security.Symbol.Equals("aapl", StringComparison.InvariantCultureIgnoreCase)); Assert.That(aaplPosition.Shares, Is.EqualTo(200M)); var yvrPosition = mandingo.Positions.Single(p => p.Security.Symbol.Equals("yvr", StringComparison.InvariantCultureIgnoreCase)); Assert.That(yvrPosition.Shares, Is.EqualTo(50M)); }
public void When_Updating_Account_With_Sell_Transaction_With_More_Shares_Than_Position_Then_Exception_Is_Thrown() { // setup Security goog = new Security { Symbol = "goog" }; var portfolio = TestDataGenerator.GenerateEmptyPortfolio(); var mandingo = portfolio.Accounts.Single(a => a.Name.Equals("mandingo", StringComparison.InvariantCultureIgnoreCase)); mandingo.Positions.Add(new Position { Account = mandingo, Shares = 1M, Security = goog }); var portfolioService = new PortfolioService(portfolio); var transaction = new Transaction { Account = mandingo, Date = DateTime.UtcNow, Price = 0M, Security = goog, Shares = 10M, Type = TransactionType.Sell }; // verify try { portfolioService.UpdateWith(new List<Transaction> { transaction }); } catch (Exception ex) { Assert.That(ex.Message, Is.EqualTo("Tried to sell more shares than the account has.")); } }
private IList<CategoryWeight> GetWeightDataFromQuestrade(Category category, Security security) { var symbolData = GetSymbols(new[] { security }).Single(); var values = _categoryRepository.GetValues(category); var list = new List<CategoryWeight>(); if (category.Name.Equals("security", StringComparison.InvariantCultureIgnoreCase)) { string categoryValueName; switch (symbolData.m_securityType) { case SecurityType.Stock: categoryValueName = "Stock"; break; case SecurityType.Bond: categoryValueName = "Bond"; break; default: throw new Exception(string.Format("Questrade security type not supported: {0}", symbolData.m_securityType)); } var value = values.SingleOrDefault(v => v.Name.Equals(categoryValueName)); if (value == null) { value = new CategoryValue { Category = category, Name = categoryValueName }; _categoryRepository.AddValues(category, new[] { value }); } list.Add(new CategoryWeight { Security = security, Weight = 100M, Value = value }); } else if (category.Name.Equals("currency", StringComparison.InvariantCultureIgnoreCase)) { var value = values.SingleOrDefault(v => v.Name.Equals(symbolData.m_currency)); if (value == null) { value = new CategoryValue { Category = category, Name = symbolData.m_currency }; _categoryRepository.AddValues(category, new[] { value }); } list.Add(new CategoryWeight { Security = security, Weight = 100M, Value = value }); } else { throw new Exception(string.Format("Category not supported: {0}", category.Name)); } if (!list.Any()) return new List<CategoryWeight>(); _categoryRepository.AddWeights(category, security, list); return list; }
// this is going to get nuts... public IList<CategoryWeight> GetWeights(Category category, Security security) { // this exception block sucks. TODO - Replace with something sane. try { var weights = _categoryRepository.GetWeights(category, security).ToList(); if (!weights.Any(w => w.Value.Category == category)) return GetWeightDataFromQuestrade(category, security); return weights.Where(w => w.Value.Category == category).ToList(); } catch (SecurityNotFoundException) { return GetWeightDataFromQuestrade(category, security); } }
public void When_Updating_Account_With_Sell_Transaction_Equal_To_Position_Then_Account_Has_Position_Removed() { // setup Security goog = new Security { Symbol = "goog" }; var portfolio = TestDataGenerator.GenerateEmptyPortfolio(); var mandingo = portfolio.Accounts.Single(a => a.Name.Equals("mandingo", StringComparison.InvariantCultureIgnoreCase)); mandingo.Positions.Add(new Position { Account = mandingo, Shares = 100M, Security = goog }); var portfolioService = new PortfolioService(portfolio); var transaction = new Transaction { Account = mandingo, Date = DateTime.UtcNow, Price = 0M, Security = goog, Shares = 100M, Type = TransactionType.Sell }; // verify portfolioService.UpdateWith(new List<Transaction> { transaction }); Assert.That(mandingo.Positions.Count, Is.EqualTo(0)); }