using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Avalonia.Controls; using CommunityToolkit.Mvvm.Input; using DynamicData; using MarketData; using MarketData.Cache; using MarketData.DataAccess; using MarketData.Generator; using MarketData.Generator.GainLoss; using MarketData.Generator.MovingAverage; using MarketData.MarketDataModel; using MarketData.MarketDataModel.GainLoss; using MarketData.Utils; using PortfolioManager.DataSeriesViewModels; using PortfolioManager.Models; using PortfolioManager.UIUtils; namespace PortfolioManager.ViewModels { public partial class GainLossViewModel : WorkspaceViewModel { private const String ALL = UIConstants.CONST_ALL; private enum Tasks { Accounts, SelectedSymbol, SelectedAccounts, LeastSquaresFit, UseDividends }; private Dictionary semaphorePool = new Dictionary(); private PortfolioTrades portfolioTrades = null; private ObservableCollection symbols = new ObservableCollection(new String[]{ALL}.ToList()); private ObservableCollection accounts = new ObservableCollection(new String[]{ALL}.ToList()); private String selectedSymbol = ALL; private String selectedAccount = ALL; private DateTime latestMarketDate; private bool isBusy = false; private bool suspendUpdate = false; private bool includeDividends = false; private String selectedCompanyName = String.Empty; private ITotalGainLossGenerator gainLossGenerator = null; private GainLossCompoundModelCollection gainLossModelCollection = null; private bool showAsGainLoss = true; private bool showActiveGainLoss = false; private ObservableCollection gainLossCompoundModelCollection = new ObservableCollection(); private ObservableCollection gainLossSummaryItemCollection = new ObservableCollection(); private IActiveGainLossGenerator activeGainLossGenerator = null; private TotalGainLossCollection totalGainLoss = null; // total gain/loss private bool useCumulativeGainLoss = true; private bool showMarkers = false; private bool useLeastSquaresFit = true; private GainLossCompoundModel selectedGainLossCompoundItem = null; private GainLossSummaryItem selectedGainLossSummaryItem = null; public GainLossViewModel() { DisplayName = "GainLossView"; semaphorePool.Add(Tasks.SelectedSymbol, new Semaphore(1, 1)); semaphorePool.Add(Tasks.SelectedAccounts, new Semaphore(1, 1)); semaphorePool.Add(Tasks.LeastSquaresFit, new Semaphore(1, 1)); semaphorePool.Add(Tasks.Accounts, new Semaphore(1, 1)); semaphorePool.Add(Tasks.UseDividends, new Semaphore(1, 1)); PropertyChanged += OnViewModelPropertyChanged; Initialize(); } protected override void OnDispose() { MDTrace.WriteLine(LogLevel.DEBUG,$"Dispose GainLossViewModel"); base.OnDispose(); } public override String DisplayName { get { return "GainLossView"; } } public override String Title { get { return DisplayName; } } public String PercentButtonText { get { if (!showAsGainLoss) return "Show $"; else return "Show %"; } } public String ActiveTotalButtonText { get { if (!showActiveGainLoss) return "Show Active G/L"; else return "Show Total G/L"; } } public bool ShowMarkers { get { return showMarkers; } set { showMarkers = value; base.OnPropertyChanged("ShowMarkers"); } } private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs) { if (eventArgs.PropertyName.Equals("SelectedSymbol"))HandleSelectedSymbol(); else if (eventArgs.PropertyName.Equals("Symbols"))HandleSymbols(); else if (eventArgs.PropertyName.Equals("SelectedAccount"))HandleSelectedAccount(); else if (eventArgs.PropertyName.Equals("LeastSquaresFit"))HandleLeastSquaresFit(); else if (eventArgs.PropertyName.Equals("SelectedGainLossCompoundItem"))HandleSelectedGainLossCompoundItem(); else if (eventArgs.PropertyName.Equals("CheckBoxUseCumulativeReturns"))HandleCheckBoxUseCumulativeReturns(); } private void HandleSymbols() { SelectedSymbol = symbols[0]; } private void HandleSelectedSymbol() { try { semaphorePool[Tasks.SelectedSymbol].WaitOne(); gainLossCompoundModelCollection = null; gainLossSummaryItemCollection = null; if (suspendUpdate) return; suspendUpdate = true; MDTrace.WriteLine(LogLevel.DEBUG, $"HandleSelectedSymbol:{selectedSymbol}"); if (String.IsNullOrEmpty(selectedSymbol) || String.IsNullOrEmpty(selectedAccount)) { UpdateProperties(); return; } MDTrace.WriteLine(LogLevel.DEBUG, String.Format("[GainLossViewModel::OnGainLossViewModelPropertyChanged]SelectedSymbol '{0}'", selectedSymbol)); IsBusy = true; Task workerTask = Task.Factory.StartNew(() => { DividendPayments dividendPayments = null; if (ALL.Equals(selectedSymbol)) { portfolioTrades = PortfolioDA.GetTrades(); if (includeDividends) dividendPayments = DividendPaymentDA.GetDividendPayments(); selectedCompanyName = ""; } else { portfolioTrades = PortfolioDA.GetTrades(selectedSymbol); if (includeDividends) dividendPayments = DividendPaymentDA.GetDividendPaymentsForSymbol(selectedSymbol); selectedCompanyName = PricingDA.GetNameForSymbol(selectedSymbol); } if (!ALL.Equals(selectedAccount)) { portfolioTrades = portfolioTrades.FilterAccount(selectedAccount); } if (null != dividendPayments && !ALL.Equals(selectedAccount)) { dividendPayments = dividendPayments.FilterAccounts(selectedAccount); } // check the TotalGainLoss generator and build appropriately InstantiateGenerators(); LocalPriceCache.GetInstance().Refresh(); GainLossCollection gainLoss = null; MDTrace.WriteLine(LogLevel.DEBUG, "GeneratingActiveGainLoss"); // gainLoss will contain the gain/loss from active positions. Never includes dividends .. just positions gainLoss = activeGainLossGenerator.GenerateGainLoss(portfolioTrades); MDTrace.WriteLine(LogLevel.DEBUG, "GeneratingTotalGainLoss)"); // Call the appropriate TotalGainLoss method depending on whether we should include dividends or not. if (includeDividends) totalGainLoss = gainLossGenerator.GenerateTotalGainLossWithDividends(portfolioTrades, dividendPayments); else totalGainLoss = gainLossGenerator.GenerateTotalGainLoss(portfolioTrades); TotalGainLossItem lastItem = totalGainLoss[totalGainLoss.Count - 1]; MDTrace.WriteLine(LogLevel.DEBUG, $"Date:{lastItem.Date.ToShortDateString()} TotalGainLoss:{Utility.FormatCurrency(lastItem.TotalGainLoss, 2)}"); gainLossModelCollection = null; gainLossModelCollection = new GainLossCompoundModelCollection(gainLoss, totalGainLoss); if (null != gainLossModelCollection) { gainLossModelCollection.Sort(new SortGainLossCompoundModelYearDescendingOrder()); gainLossCompoundModelCollection = new ObservableCollection(gainLossModelCollection); } GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(portfolioTrades, gainLossGenerator, activeGainLossGenerator); gainLossSummaryItemCollection = new ObservableCollection(gainLossSummaryItems); }); workerTask.ContinueWith((continuation) => { IsBusy = false; UpdateProperties(); suspendUpdate = false; }); } finally { semaphorePool[Tasks.SelectedSymbol].Release(); } } private void HandleSelectedAccount() { try { semaphorePool[Tasks.SelectedAccounts].WaitOne(); Task workerTask = Task.Factory.StartNew(() => { if (String.IsNullOrEmpty(selectedAccount)) return; if (ALL.Equals(selectedAccount)) { portfolioTrades = PortfolioDA.GetTrades(); } else { portfolioTrades = PortfolioDA.GetTradesForAccounts(new List() { selectedAccount }); } }); workerTask.ContinueWith((continuation) => { SetSymbols(); SelectedSymbol = Symbols[0]; }); } finally { semaphorePool[Tasks.SelectedAccounts].Release(); } } private void HandleLeastSquaresFit() { MDTrace.WriteLine(LogLevel.DEBUG, $"HandleLeastSquaresFit"); } private void HandleSelectedGainLossCompoundItem() { if (null == selectedGainLossCompoundItem) return; IsBusy = true; Task workerTask = Task.Factory.StartNew(() => { DateTime selectedDate = selectedGainLossCompoundItem.Date; PortfolioTrades tradesOnOrBefore = portfolioTrades.GetTradesOnOrBefore(selectedDate); GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(tradesOnOrBefore, gainLossGenerator, activeGainLossGenerator, selectedDate); gainLossSummaryItemCollection = new ObservableCollection(gainLossSummaryItems); }); workerTask.ContinueWith((continuation) => { IsBusy = false; base.OnPropertyChanged("GainLossSummaryItemCollection"); UpdateSummaryProperties(); }); } private void HandleCheckBoxUseCumulativeReturns() { MDTrace.WriteLine(LogLevel.DEBUG, $"HandleCheckBoxUseCumulativeReturns"); } // ******************************************************************************************************************************** public void InstantiateGenerators() { if (null == gainLossGenerator) { if (useCumulativeGainLoss) gainLossGenerator = new GainLossGeneratorCum(); else gainLossGenerator = new GainLossGenerator(); } if (null == activeGainLossGenerator) activeGainLossGenerator = new ActiveGainLossGenerator(); } private void Initialize() { IsBusy = true; suspendUpdate = true; semaphorePool[Tasks.Accounts].WaitOne(); Task workerTask = Task.Factory.StartNew(() => { latestMarketDate = PremarketDA.GetLatestMarketDate(); portfolioTrades = PortfolioDA.GetTrades(); }); workerTask.ContinueWith((continuation) => { SetAccounts(); SetSymbols(); selectedSymbol = symbols[0]; selectedAccount = accounts[0]; IsBusy = false; suspendUpdate = false; base.OnPropertyChanged("SelectedSymbol"); semaphorePool[Tasks.Accounts].Release(); }); } public void SetAccounts() { if (null == portfolioTrades || 0 == portfolioTrades.Count) return; if (default != accounts.Where(x => x.Equals(ALL)).FirstOrDefault()) { selectedAccount = accounts[0]; IEnumerable collectionToRemove = accounts.Where(x => !x.Equals(ALL)).ToList(); accounts.Remove(collectionToRemove); accounts.AddRange(portfolioTrades.Accounts); } else { List portfolioTradeAccounts = new List(portfolioTrades.Accounts); portfolioTradeAccounts.Insert(0, ALL); accounts.AddRange(portfolioTradeAccounts); selectedAccount = accounts[0]; } } public void SetSymbols() { if (null == portfolioTrades || 0 == portfolioTrades.Count) return; if (default != symbols.Where(x => x.Equals(ALL)).FirstOrDefault()) { selectedSymbol = symbols[0]; IEnumerable collectionToRemove = symbols.Where(x => !x.Equals(ALL)).ToList(); symbols.Remove(collectionToRemove); symbols.AddRange(portfolioTrades.Symbols); } else { List tradedSymbols = new List(portfolioTrades.Symbols); tradedSymbols.Insert(0, ALL); symbols.AddRange(tradedSymbols); selectedSymbol = symbols[0]; } } public String SelectedSymbol { get { return selectedSymbol; } set { if (null == value) return; selectedSymbol = value; base.OnPropertyChanged("SelectedSymbol"); } } public String SelectedAccount { get { return selectedAccount; } set { if (null == value) return; selectedAccount = value; base.OnPropertyChanged("SelectedAccount"); } } public ObservableCollection Accounts { get { return accounts; } } public ObservableCollection Symbols { get { return symbols; } } public String TotalGainLoss { get { if (null == totalGainLoss || 0 == totalGainLoss.Count) { return Constants.CONST_DASHES; } return Utility.FormatCurrency(totalGainLoss[totalGainLoss.Count - 1].TotalGainLoss, 2); } } // ************************************************************************************************************************************************ public String SummaryDate { get { if (null == gainLossSummaryItemCollection || 0 == gainLossSummaryItemCollection.Count) { return Constants.CONST_DASHES; } return gainLossSummaryItemCollection[0].Date.ToShortDateString(); } } public String SummaryChange { get { if (null == gainLossSummaryItemCollection) { return Constants.CONST_DASHES; } return Utility.FormatCurrency(gainLossSummaryItemCollection.Sum(x => x.Change), 2); } } public String SummaryGainLoss { get { if (null == gainLossSummaryItemCollection) { return Constants.CONST_DASHES; } return Utility.FormatCurrency(gainLossSummaryItemCollection.Sum(x => x.CurrentGainLoss), 2); } } public String SummaryPositions { get { if (null == gainLossSummaryItemCollection) { return Constants.CONST_DASHES; } return Utility.FormatNumber(gainLossSummaryItemCollection.Count(), 0, true); } } public bool CheckBoxIncludeDividends { get { return includeDividends; } set { includeDividends = value; base.OnPropertyChanged("CheckBoxIncludeDividends"); base.OnPropertyChanged("SelectedSymbol"); } } // ************************************************************************************************************************************************ private void UpdateProperties() { base.OnPropertyChanged("TotalGainLoss"); base.OnPropertyChanged("GraphTitle"); base.OnPropertyChanged("Title"); base.OnPropertyChanged("Data"); base.OnPropertyChanged("MA21"); base.OnPropertyChanged("MA55"); base.OnPropertyChanged("MA90"); base.OnPropertyChanged("LeastSquares"); base.OnPropertyChanged("GainLossCompoundModelCollection"); // The compound model collection is the data behind the views left-hand grid. base.OnPropertyChanged("GainLossSummaryItemCollection"); // The summary item collection is the data behind the views right-hand grid. base.OnPropertyChanged("Parity"); base.OnPropertyChanged("DollarChangePercent"); UpdateSummaryProperties(); } private void UpdateSummaryProperties() { base.OnPropertyChanged("SummaryGainLoss"); base.OnPropertyChanged("SummaryDate"); base.OnPropertyChanged("SummaryChange"); base.OnPropertyChanged("SummaryDate"); base.OnPropertyChanged("SummaryPositions"); } // ***************************************************************************************************************** public bool IsBusy { get { return isBusy; } set { isBusy = value; base.OnPropertyChanged("IsBusy"); } } // ********************************************* P E R S I S T E N C E ********************************************* public override bool CanPersist() { return false; } public override SaveParameters GetSaveParameters() { return null; } public override void SetSaveParameters(SaveParameters saveParameters) { } // ************************************************** C H A R T D A T A ******************************************** public CompositeDataSource Data { get { if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty(); CompositeDataSource compositeDataSource = null; if (showActiveGainLoss) compositeDataSource = GainLossModel.GainLoss(gainLossModelCollection, showAsGainLoss); else compositeDataSource = GainLossModel.TotalGainLoss(gainLossModelCollection, showAsGainLoss); return compositeDataSource; } } public CompositeDataSource MA21 { get { if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty(); CompositeDataSource compositeDataSource = null; DMAValues dmaValues = null; if (showActiveGainLoss) { if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 21); else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 21); compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues); } else { if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 21); else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 21); compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues); } return compositeDataSource; } } public CompositeDataSource MA55 { get { if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty(); CompositeDataSource compositeDataSource = null; DMAValues dmaValues = null; if (showActiveGainLoss) { if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 55); else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 55); compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues); } else { if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 55); else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 55); compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues); } return compositeDataSource; } } public CompositeDataSource MA90 { get { if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty(); CompositeDataSource compositeDataSource = null; DMAValues dmaValues = null; if (showActiveGainLoss) { if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 90); else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 90); compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues); } else { if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 90); else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 90); compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues); } return compositeDataSource; } } public CompositeDataSource LeastSquares { get { if (!useLeastSquaresFit || null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty(); if (showActiveGainLoss) return GainLossModel.LeastSquares(gainLossModelCollection, showAsGainLoss); else return GainLossModel.TotalLeastSquares(gainLossModelCollection, showAsGainLoss); } } // ********************************************* C H A R T T I T L E **************************************** public String GraphTitle { get { if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) { return "GainLossView"; } StringBuilder sb = new StringBuilder(); sb.Append(showActiveGainLoss ? "Active" : "Total").Append(" "); sb.Append("Gain/Loss "); if (showAsGainLoss) sb.Append("($)"); else sb.Append("(%)"); String accounts = selectedAccount.Equals(ALL) ? Utility.ListToString(portfolioTrades.Accounts) : selectedAccount; if (!ALL.Equals(selectedSymbol)) sb.Append(" (").Append(accounts).Append(") ").Append(" - ").Append(selectedCompanyName).Append(" (").Append(selectedSymbol).Append(") "); else sb.Append(" (").Append(accounts).Append(") ").Append(" - ").Append(selectedCompanyName); String part = sb.ToString(); if (part.Length >= 75) { sb = new StringBuilder(); sb.Append(part.Substring(0, 75)); sb.Append("..."); } DateTime fromDate = gainLossModelCollection.Select(x => x.Date).Min(); DateTime toDate = gainLossModelCollection.Select(x => x.Date).Max(); sb.Append(" from ").Append(Utility.DateTimeToStringMMHDDHYYYY(fromDate)); sb.Append(" Thru ").Append(Utility.DateTimeToStringMMHDDHYYYY(toDate)); return sb.ToString(); } } // ************************************************************************************************************************************************************************* // ***************************************************************************** T A B L E D A T A *********************************************************************** // ************************************************************************************************************************************************************************* // The compound model collection is the data behind the views left-hand grid. This view shows the compound gain/loss since inception public ObservableCollection GainLossCompoundModelCollection { get { return gainLossCompoundModelCollection; } } // The summary model collection is the data behind the views right-hand grid. This view shows the gain/loss for the selected date public ObservableCollection GainLossSummaryItemCollection { get { return gainLossSummaryItemCollection; } } // ****************************************************************************** G R I D S E L E C T I O N S *********************************************** // The selected item in the compound model public GainLossCompoundModel SelectedGainLossCompoundItem { get { return selectedGainLossCompoundItem; } set { selectedGainLossCompoundItem = value; base.OnPropertyChanged("SelectedGainLossCompoundItem"); } } // The selected item in the summary model public GainLossSummaryItem SelectedGainLossSummaryItem { get { return selectedGainLossSummaryItem; } set { selectedGainLossSummaryItem = value; base.OnPropertyChanged("SelectedGainLossSummaryItem"); base.OnPropertyChanged("Parity"); base.OnPropertyChanged("DollarChangePercent"); } } // ********************************************* R E L A Y S ********************************************* [RelayCommand] public async Task ToggleReturnOrPercent() { showAsGainLoss = !showAsGainLoss; base.OnPropertyChanged("SelectedSymbol"); base.OnPropertyChanged("PercentButtonText"); await Task.FromResult(true); } [RelayCommand] public async Task ToggleActiveOrTotal() { showActiveGainLoss = !showActiveGainLoss; base.OnPropertyChanged("SelectedSymbol"); base.OnPropertyChanged("ActiveTotalButtonText"); await Task.FromResult(true); } [RelayCommand] public async Task PerformRefresh() { latestMarketDate = PremarketDA.GetLatestMarketDate(); base.OnPropertyChanged("SelectedSymbol"); await Task.FromResult(true); } [RelayCommand] public async Task PerformReset() { SelectedAccount = ALL; await Task.FromResult(true); } // *********************************************************** M E N U I T E M S **************************************************** public ObservableCollection GainLossMenuItems { get { ObservableCollection collection = new ObservableCollection(); collection.Add(new MenuItem() { Header = "Display Bollinger Band...", Command = BollingerBandsCommand, StaysOpenOnClick = false }); return collection; } } [RelayCommand(CanExecute = nameof(CanExecuteBollingerBand))] public async Task BollingerBands() { if (null == selectedGainLossSummaryItem) return; SaveParameters saveParams = SaveParameters.Parse("Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol," + selectedGainLossSummaryItem.Symbol + ",SelectedWatchList,{All},SelectedDayCount,180,SyncTradeToBand,FALSE"); saveParams.Referer = this; WorkspaceInstantiator.Invoke(saveParams); await Task.FromResult(true); } public bool CanExecuteBollingerBand() { return selectedGainLossSummaryItem == default ? false : true; } // ****************************************************** T O O L T I P S ********************************************************* public String DollarChangePercent { get { return "'$ Change(%)' is calculated as a percentage of dollar change in market value from the previous period to the current period.\nThis number may appear skewed if share count has changed day over day."; } } public String Parity { get { Profiler profiler = new Profiler(); try { if (null == selectedGainLossSummaryItem || null == selectedGainLossSummaryItem.Symbol) { return "No row selected."; } StringBuilder sb = new StringBuilder(); PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTradesSymbol(selectedGainLossSummaryItem.Symbol); DateTime currentDate = PricingDA.GetLatestDate(selectedGainLossSummaryItem.Symbol); String companyName = PricingDA.GetNameForSymbol(selectedGainLossSummaryItem.Symbol); if (null != companyName) sb.Append(companyName).Append("\n"); CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(selectedGainLossSummaryItem.Symbol); if (null != companyProfile) { sb.Append(companyProfile.Sector ?? Constants.CONST_QUESTION).Append("/").Append(companyProfile.Industry ?? Constants.CONST_QUESTION).Append("\n"); } if (null != portfolioTrades && 0 != portfolioTrades.Count) { double shares = (from PortfolioTrade portfolioTrade in portfolioTrades select portfolioTrade.Shares).Sum(); double exposure = portfolioTrades.Sum(x => x.Exposure()); // Calculate the gain loss so that we can show the difference between our all time high percentage and where we are right now InstantiateGenerators(); GainLossCollection gainLoss = activeGainLossGenerator.GenerateGainLoss(portfolioTrades); // gainLoss contains the gain/loss from active positions. Never includes dividends .. just positions GainLossItem maxGainLossItem = gainLoss.OrderByDescending(x => x.GainLossPercent).FirstOrDefault(); GainLossItem minGainLossItem = gainLoss.OrderBy(x => x.GainLossPercent).FirstOrDefault(); String accounts = Utility.ListToString(portfolioTrades.Accounts); sb.Append("You own '").Append(selectedGainLossSummaryItem.Symbol).Append("' in ").Append(portfolioTrades.Count).Append(" lot(s) (").Append(Utility.FormatNumber(shares, 0, true)).Append(" shares) ").Append("\n"); sb.Append("Exposure: ").Append(Utility.FormatCurrency(exposure)); sb.Append("\n").Append("Accounts: ").Append(accounts); ParityElement parityElement = ParityGenerator.GenerateBreakEven(selectedGainLossSummaryItem.Symbol); if (null != parityElement) { sb.Append("\n").Append(parityElement.ToString()); sb.Append("\n").Append("All Time Gain/Loss: ").Append(maxGainLossItem.GainLossPercent < 0 ? "" : "+").Append(Utility.FormatPercent(maxGainLossItem.GainLossPercent / 100)); sb.Append(" (").Append(minGainLossItem.GainLossPercent < 0 ? "" : "+").Append(Utility.FormatPercent(minGainLossItem.GainLossPercent / 100)).Append(")"); } } else sb.Append("You don't hold any shares of '").Append(selectedGainLossSummaryItem.Symbol).Append("'"); sb.Append("."); DateGenerator dateGenerator = new DateGenerator(); DateTime priorDate = dateGenerator.FindPrevBusinessDay(currentDate); Price p1 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, currentDate); Price p2 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, priorDate); if (null == p2 && null != p1) { priorDate = dateGenerator.FindPrevBusinessDay(priorDate); p2 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, priorDate); } if (null == p1 || null == p2) return sb.ToString(); sb.Append("\n"); double change = (p1.Close - p2.Close) / p2.Close; sb.Append(String.Format("Latest Price {0} {1} ({2}{3})", Utility.DateTimeToStringMMSDDSYYYY(p1.Date), Utility.FormatCurrency(p1.Close), change < 0 ? "-" : "+", Utility.FormatPercent(Math.Abs(change)))); if (companyProfile.FreezePricing) sb.Append(" ").Append("**Frozen**"); sb.Append("\n").Append(String.Format("Source: {0}", p1.SourceAsString())); return sb.ToString(); } finally { MDTrace.WriteLine(LogLevel.DEBUG, $"[Parity]Done, total took {profiler.End()}(ms)"); } } } } }