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