850 lines
32 KiB
C#
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)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |