Files
Avalonia/PortfolioManager/ViewModels/GainLossViewModel.cs
2025-06-16 19:59:04 -04:00

850 lines
32 KiB
C#

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<Tasks, Semaphore> semaphorePool = new Dictionary<Tasks, Semaphore>();
private PortfolioTrades portfolioTrades = null;
private ObservableCollection<String> symbols = new ObservableCollection<String>(new String[]{ALL}.ToList());
private ObservableCollection<String> accounts = new ObservableCollection<String>(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<GainLossCompoundModel> gainLossCompoundModelCollection = new ObservableCollection<GainLossCompoundModel>();
private ObservableCollection<GainLossSummaryItem> gainLossSummaryItemCollection = new ObservableCollection<GainLossSummaryItem>();
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<GainLossCompoundModel>(gainLossModelCollection);
}
GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(portfolioTrades, gainLossGenerator, activeGainLossGenerator);
gainLossSummaryItemCollection = new ObservableCollection<GainLossSummaryItem>(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<String>() { 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<GainLossSummaryItem>(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<String> collectionToRemove = accounts.Where(x => !x.Equals(ALL)).ToList();
accounts.Remove(collectionToRemove);
accounts.AddRange(portfolioTrades.Accounts);
}
else
{
List<String> portfolioTradeAccounts = new List<String>(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<String> collectionToRemove = symbols.Where(x => !x.Equals(ALL)).ToList();
symbols.Remove(collectionToRemove);
symbols.AddRange(portfolioTrades.Symbols);
}
else
{
List<String> tradedSymbols = new List<String>(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<String> Accounts
{
get
{
return accounts;
}
}
public ObservableCollection<String> 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<GainLossCompoundModel> 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<GainLossSummaryItem> 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<MenuItem> GainLossMenuItems
{
get
{
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
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)");
}
}
}
}
}