public void CostBasisAcrossTransfers() { UiDispatcher.CurrentDispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher; MyMoney m = new MyMoney(); Security s = m.Securities.NewSecurity(); s.Name = "MSFT"; Account a = m.Accounts.AddAccount("Ameritrade"); Account a2 = m.Accounts.AddAccount("Fidelity"); AddInvestment(m, s, a, DateTime.Parse("1/1/2000"), 10, 1, InvestmentType.Buy); m.StockSplits.AddStockSplit(new StockSplit() { Date = DateTime.Parse("6/1/2000"), Numerator = 2, Denominator = 1, Security = s }); AddInvestment(m, s, a, DateTime.Parse("1/1/2001"), 20, 2, InvestmentType.Buy); Transaction transfer = AddInvestment(m, s, a, DateTime.Parse("1/1/2002"), 30, 2, InvestmentType.Remove); m.Transfer(transfer, a2); // now should be able to sell 10 left in this account (after split) AddInvestment(m, s, a, DateTime.Parse("1/1/2003"), 10, 3, InvestmentType.Sell); // and we should have 30 in the other account AddInvestment(m, s, a2, DateTime.Parse("1/1/2004"), 30, 5, InvestmentType.Sell); // Ok, now let's if the cost basis is correct! CostBasisCalculator calc = new CostBasisCalculator(m, DateTime.Now); List <SecurityPurchase> holdings = new List <SecurityPurchase>(calc.GetHolding(a).GetHoldings()); Assert.AreEqual <int>(0, holdings.Count); // should have nothing left. // should have 3 separate cost basis records to cover what we sold. List <SecuritySale> sales = new List <SecuritySale>(calc.GetSales()); Assert.AreEqual <int>(3, sales.Count); SecuritySale s1 = sales[0]; SecuritySale s2 = sales[1]; SecuritySale s3 = sales[2]; // since the sale from Ameritrade account happened first it should be returned first Assert.AreEqual <decimal>(2, s1.CostBasisPerUnit); // $2, no splits Assert.AreEqual(a, s1.Account); // Ameritrade Assert.AreEqual <decimal>(Math.Round(10M, 5), Math.Round(s1.UnitsSold, 5)); // Notice here that the Fidelity account inherited the cost basis records correctly // from the Ameritrade account as a result of the "Transfer" that happened above. Assert.AreEqual <decimal>(Math.Round(1M / 2M, 5), Math.Round(s2.CostBasisPerUnit, 5)); // $1 after 2:1 split Assert.AreEqual(a2, s2.Account); // Fidelity Assert.AreEqual <decimal>(Math.Round(20M, 5), Math.Round(s2.UnitsSold, 5)); Assert.AreEqual <decimal>(2, s3.CostBasisPerUnit); // $2, no splits Assert.AreEqual(a2, s2.Account); // Fidelity Assert.AreEqual <decimal>(Math.Round(10M, 5), Math.Round(s3.UnitsSold, 5)); }
private decimal WriteSecurities(IReportWriter writer, List <PieData> data, string prefix, Predicate <Account> filter, out bool hasNoneType) { hasNoneType = false; decimal balance = 0; Dictionary <SecurityType, decimal> byType = new Dictionary <SecurityType, decimal>(); CostBasisCalculator calc = new CostBasisCalculator(this.myMoney, DateTime.Now); // compute summary foreach (var securityTypeGroup in calc.GetHoldingsBySecurityType(filter)) { SecurityType stype = securityTypeGroup.Key; decimal sb = 0; byType.TryGetValue(stype, out sb); foreach (SecurityPurchase sp in securityTypeGroup.Value) { sb += sp.MarketValue; } byType[stype] = sb; } if (byType.Count > 0) { foreach (SecurityType st in new SecurityType[] { SecurityType.Bond, SecurityType.MutualFund, SecurityType.Equity, SecurityType.MoneyMarket, SecurityType.ETF, SecurityType.Reit, SecurityType.Futures, SecurityType.None }) { decimal sb = 0; if (byType.TryGetValue(st, out sb)) { string caption = prefix + Security.GetSecurityTypeCaption(st); if (sb > 0) { data.Add(new PieData() { Name = caption, Total = sb }); if (st == SecurityType.None) { hasNoneType = true; caption += "*"; } } WriteRow(writer, caption, sb); balance += sb; } } } else { WriteRow(writer, "N/A", 0); } return(balance); }
public void Generate(IReportWriter writer) { flowwriter = writer as FlowDocumentReportWriter; calc = new CostBasisCalculator(this.myMoney, this.reportDate); string heading = "Investment Portfolio Summary"; if (this.account != null) { heading += " for " + account.Name + " (" + account.AccountId + ")"; } writer.WriteHeading(heading); if (reportDate.Date != DateTime.Today) { writer.WriteSubHeading("As of " + reportDate.Date.AddDays(-1).ToLongDateString()); } totalMarketValue = 0; totalGainLoss = 0; // outer table contains 2 columns, left is the summary table, right is the pie chart. writer.StartTable(); writer.StartColumnDefinitions(); writer.WriteColumnDefinition("Auto", 100, double.MaxValue); writer.WriteColumnDefinition("Auto", 100, double.MaxValue); writer.EndColumnDefinitions(); writer.StartRow(); writer.StartCell(); writer.StartTable(); writer.StartColumnDefinitions(); foreach (double minWidth in new double[] { 300, 100, 100 }) { writer.WriteColumnDefinition("Auto", minWidth, double.MaxValue); } writer.EndColumnDefinitions(); writer.StartHeaderRow(); writer.StartCell(); writer.WriteParagraph("Security Type"); writer.EndCell(); writer.StartCell(); writer.WriteNumber("Market Value"); writer.EndCell(); writer.StartCell(); writer.WriteNumber("Gain/Loss"); writer.EndCell(); writer.EndRow(); List <SecurityPieData> data = new List <SecurityPieData>(); decimal cash = this.myMoney.GetInvestmentCashBalance(account); if (cash > 0) { writer.StartRow(); writer.StartCell(); writer.WriteParagraph("Cash"); writer.EndCell(); writer.StartCell(); writer.WriteNumber(cash.ToString("C")); writer.EndCell(); writer.EndRow(); data.Add(new SecurityPieData() { Total = RoundToNearestCent(cash), Name = "Cash" }); } totalMarketValue += cash; if (account == null) { WriteSummary(writer, data, "Tax Deferred ", new Predicate <Account>((a) => { return(a.IsTaxDeferred); })); WriteSummary(writer, data, "", new Predicate <Account>((a) => { return(!a.IsTaxDeferred); })); } else { WriteSummary(writer, data, "", new Predicate <Account>((a) => { return(a == account); })); } writer.StartHeaderRow(); writer.StartCell(); writer.WriteParagraph("Total"); writer.EndCell(); writer.StartCell(); writer.WriteNumber(totalMarketValue.ToString("C")); writer.EndCell(); writer.StartCell(); writer.WriteNumber(totalGainLoss.ToString("C")); writer.EndCell(); writer.EndRow(); writer.EndTable(); writer.EndCell(); // pie chart Chart chart = new Chart(); chart.MinWidth = 400; chart.MinHeight = 300; chart.BorderThickness = new Thickness(0); chart.Padding = new Thickness(0); chart.Margin = new Thickness(0, 00, 0, 0); chart.VerticalAlignment = VerticalAlignment.Top; chart.HorizontalAlignment = HorizontalAlignment.Left; PieSeries series = new PieSeries(); series.IndependentValueBinding = new Binding("Name"); series.DependentValueBinding = new Binding("Total"); chart.Series.Add(series); series.ItemsSource = data; writer.StartCell(); writer.WriteElement(chart); writer.EndCell(); // end the outer table. writer.EndTable(); totalMarketValue = 0; totalGainLoss = 0; List <SecuritySale> errors = new List <SecuritySale>(calc.GetPendingSales(new Predicate <Account>((a) => { return(a == account); }))); if (errors.Count > 0) { writer.WriteSubHeading("Pending Sales"); foreach (var sp in errors) { writer.WriteParagraph(string.Format("Pending sale of {1} units of '{2}' from account '{0}' recorded on {3}", sp.Account.Name, sp.UnitsSold, sp.Security.Name, sp.DateSold.ToShortDateString())); } } if (account == null) { WriteDetails(writer, "Tax Deferred ", new Predicate <Account>((a) => { return(a.IsTaxDeferred); })); WriteDetails(writer, "", new Predicate <Account>((a) => { return(!a.IsTaxDeferred); })); } else { WriteDetails(writer, "", new Predicate <Account>((a) => { return(a == account); })); } }
public void Generate(IReportWriter writer) { flowwriter = writer as FlowDocumentReportWriter; calc = new CostBasisCalculator(this.myMoney, this.reportDate); string heading = "Investment Portfolio Summary"; if (this.selectedGroup != null) { heading = "Investment Portfolio - " + this.selectedGroup.Type; } if (this.account != null) { heading += " for " + account.Name + " (" + account.AccountId + ")"; } writer.WriteHeading(heading); if (reportDate.Date != DateTime.Today) { writer.WriteSubHeading("As of " + reportDate.Date.AddDays(-1).ToLongDateString()); } totalMarketValue = 0; totalGainLoss = 0; // outer table contains 2 columns, left is the summary table, right is the pie chart. writer.StartTable(); writer.StartColumnDefinitions(); writer.WriteColumnDefinition("Auto", 100, double.MaxValue); writer.WriteColumnDefinition("Auto", 100, double.MaxValue); writer.EndColumnDefinitions(); writer.StartRow(); writer.StartCell(); writer.StartTable(); writer.StartColumnDefinitions(); writer.WriteColumnDefinition("30", 30, 30); foreach (double minWidth in new double[] { 300, 100, 100 }) { writer.WriteColumnDefinition("Auto", minWidth, double.MaxValue); } writer.EndColumnDefinitions(); var series = new ChartDataSeries() { Name = "Portfolio" }; IList <ChartDataValue> data = series.Values; if (account == null) { if (this.selectedGroup != null) { WriteSummary(writer, data, TaxStatus.Taxable, null, null, false); } else { WriteSummary(writer, data, TaxStatus.TaxFree, "Tax Free ", new Predicate <Account>((a) => { return(!a.IsClosed && a.IsTaxFree && IsInvestmentAccount(a)); }), true); WriteSummary(writer, data, TaxStatus.TaxDeferred, "Tax Deferred ", new Predicate <Account>((a) => { return(!a.IsClosed && a.IsTaxDeferred && IsInvestmentAccount(a)); }), true); WriteSummary(writer, data, TaxStatus.Taxable, "Taxable ", new Predicate <Account>((a) => { return(!a.IsClosed && !a.IsTaxDeferred && !a.IsTaxFree && IsInvestmentAccount(a)); }), true); } } else { WriteSummary(writer, data, account.TaxStatus, "", new Predicate <Account>((a) => { return(a == account); }), false); } WriteHeaderRow(writer, "Total", totalMarketValue.ToString("C"), totalGainLoss.ToString("C")); writer.EndTable(); writer.EndCell(); // pie chart AnimatingPieChart chart = new AnimatingPieChart(); chart.Width = 400; chart.Height = 300; chart.BorderThickness = new Thickness(0); chart.Padding = new Thickness(0); chart.Margin = new Thickness(0, 00, 0, 0); chart.VerticalAlignment = VerticalAlignment.Top; chart.HorizontalAlignment = HorizontalAlignment.Left; chart.Series = series; chart.ToolTipGenerator = OnGenerateToolTip; chart.PieSliceClicked += OnPieSliceClicked; writer.StartCell(); writer.WriteElement(chart); writer.EndCell(); // end the outer table. writer.EndTable(); totalMarketValue = 0; totalGainLoss = 0; if (this.selectedGroup != null) { WriteDetails(writer, "", this.selectedGroup); } else { List <SecuritySale> errors = new List <SecuritySale>(calc.GetPendingSales(new Predicate <Account>((a) => { return(a == account); }))); if (errors.Count > 0) { writer.WriteSubHeading("Pending Sales"); foreach (var sp in errors) { writer.WriteParagraph(string.Format("Pending sale of {1} units of '{2}' from account '{0}' recorded on {3}", sp.Account.Name, sp.UnitsSold, sp.Security.Name, sp.DateSold.ToShortDateString())); } } if (account == null) { WriteDetails(writer, "Tax Free ", new Predicate <Account>((a) => { return(!a.IsClosed && a.IsTaxFree && IsInvestmentAccount(a)); })); WriteDetails(writer, "Tax Deferred ", new Predicate <Account>((a) => { return(!a.IsClosed && a.IsTaxDeferred && IsInvestmentAccount(a)); })); WriteDetails(writer, "Taxable ", new Predicate <Account>((a) => { return(!a.IsClosed && !a.IsTaxFree && !a.IsTaxDeferred && IsInvestmentAccount(a)); })); } else { WriteDetails(writer, "", new Predicate <Account>((a) => { return(a == account); })); } } }
public void Generate(IReportWriter writer) { flowwriter = writer as FlowDocumentReportWriter; calc = new CostBasisCalculator(this.myMoney, this.reportDate); string heading = "Investment Portfolio Summary"; if (this.account != null) { heading += " for " + account.Name + " (" + account.AccountId + ")"; } writer.WriteHeading(heading); if (reportDate.Date != DateTime.Today) { writer.WriteSubHeading("As of " + reportDate.Date.AddDays(-1).ToLongDateString()); } totalMarketValue = 0; totalGainLoss = 0; // outer table contains 2 columns, left is the summary table, right is the pie chart. writer.StartTable(); writer.StartColumnDefinitions(); writer.WriteColumnDefinition("Auto", 100, double.MaxValue); writer.WriteColumnDefinition("Auto", 100, double.MaxValue); writer.EndColumnDefinitions(); writer.StartRow(); writer.StartCell(); writer.StartTable(); writer.StartColumnDefinitions(); foreach (double minWidth in new double[] { 300, 100, 100 }) { writer.WriteColumnDefinition("Auto", minWidth, double.MaxValue); } writer.EndColumnDefinitions(); List <SecurityPieData> data = new List <SecurityPieData>(); if (account == null) { WriteSummary(writer, data, TaxableIncomeType.None, "Retirement Tax Free ", new Predicate <Account>((a) => { return(!a.IsClosed && !a.IsTaxDeferred && a.Type == AccountType.Retirement); }), true); WriteSummary(writer, data, TaxableIncomeType.All, "Retirement ", new Predicate <Account>((a) => { return(!a.IsClosed && a.IsTaxDeferred && a.Type == AccountType.Retirement); }), true); WriteSummary(writer, data, TaxableIncomeType.All, "Tax Deferred ", new Predicate <Account>((a) => { return(!a.IsClosed && a.IsTaxDeferred && a.Type == AccountType.Brokerage); }), true); WriteSummary(writer, data, TaxableIncomeType.Gains, "", new Predicate <Account>((a) => { return(!a.IsClosed && !a.IsTaxDeferred && a.Type == AccountType.Brokerage); }), true); } else { TaxableIncomeType taxableIncomeType; if (account.IsTaxDeferred) { taxableIncomeType = TaxableIncomeType.All; } else { if (account.Type == AccountType.Retirement) { // Currently treating this combination as tax free taxableIncomeType = TaxableIncomeType.None; } else { taxableIncomeType = TaxableIncomeType.Gains; } } WriteSummary(writer, data, taxableIncomeType, "", new Predicate <Account>((a) => { return(a == account); }), false); } WriteheaderRow(writer, "Total", totalMarketValue.ToString("C"), totalGainLoss.ToString("C")); writer.EndTable(); writer.EndCell(); // pie chart Chart chart = new Chart(); chart.MinWidth = 400; chart.MinHeight = 300; chart.BorderThickness = new Thickness(0); chart.Padding = new Thickness(0); chart.Margin = new Thickness(0, 00, 0, 0); chart.VerticalAlignment = VerticalAlignment.Top; chart.HorizontalAlignment = HorizontalAlignment.Left; PieSeries series = new PieSeries(); series.IndependentValueBinding = new Binding("Name"); series.DependentValueBinding = new Binding("Total"); chart.Series.Add(series); series.ItemsSource = data; writer.StartCell(); writer.WriteElement(chart); writer.EndCell(); // end the outer table. writer.EndTable(); totalMarketValue = 0; totalGainLoss = 0; List <SecuritySale> errors = new List <SecuritySale>(calc.GetPendingSales(new Predicate <Account>((a) => { return(a == account); }))); if (errors.Count > 0) { writer.WriteSubHeading("Pending Sales"); foreach (var sp in errors) { writer.WriteParagraph(string.Format("Pending sale of {1} units of '{2}' from account '{0}' recorded on {3}", sp.Account.Name, sp.UnitsSold, sp.Security.Name, sp.DateSold.ToShortDateString())); } } if (account == null) { WriteDetails(writer, "Retirement Tax Free ", new Predicate <Account>((a) => { return(!a.IsClosed && !a.IsTaxDeferred && a.Type == AccountType.Retirement); })); WriteDetails(writer, "Retirement ", new Predicate <Account>((a) => { return(!a.IsClosed && a.IsTaxDeferred && a.Type == AccountType.Retirement); })); WriteDetails(writer, "Tax Deferred ", new Predicate <Account>((a) => { return(!a.IsClosed && a.IsTaxDeferred && a.Type == AccountType.Brokerage); })); WriteDetails(writer, "", new Predicate <Account>((a) => { return(!a.IsClosed && !a.IsTaxDeferred && a.Type == AccountType.Brokerage); })); } else { WriteDetails(writer, "", new Predicate <Account>((a) => { return(a == account); })); } }
private decimal WriteSecurities(IReportWriter writer, IList <ChartDataValue> data, string prefix, Predicate <Account> filter, out bool hasNoneType) { hasNoneType = false; decimal balance = 0; Dictionary <SecurityType, decimal> byType = new Dictionary <SecurityType, decimal>(); Dictionary <SecurityType, SecurityGroup> groupsByType = new Dictionary <SecurityType, SecurityGroup>(); CostBasisCalculator calc = new CostBasisCalculator(this.myMoney, DateTime.Now); // compute summary foreach (var securityTypeGroup in calc.GetHoldingsBySecurityType(filter)) { SecurityType stype = securityTypeGroup.Type; decimal sb = 0; byType.TryGetValue(stype, out sb); foreach (SecurityPurchase sp in securityTypeGroup.Purchases) { sb += sp.MarketValue; } byType[stype] = sb; groupsByType[stype] = securityTypeGroup; } if (byType.Count > 0) { foreach (SecurityType st in new SecurityType[] { SecurityType.Bond, SecurityType.MutualFund, SecurityType.Equity, SecurityType.MoneyMarket, SecurityType.ETF, SecurityType.Reit, SecurityType.Futures, SecurityType.Private, SecurityType.None }) { decimal sb = 0; if (byType.TryGetValue(st, out sb)) { var color = GetRandomColor(); string caption = Security.GetSecurityTypeCaption(st); if (sb > 0) { string tooltip = caption; if (!string.IsNullOrEmpty(prefix)) { tooltip = prefix + " " + tooltip; } data.Add(new ChartDataValue() { Label = tooltip, Value = (double)sb, Color = color, UserData = groupsByType[st] }); if (st == SecurityType.None) { hasNoneType = true; caption += "*"; } } WriteRow(writer, color, caption, sb); balance += sb; } } } else { WriteRow(writer, GetRandomColor(), "N/A", 0); } return(balance); }