Initial Commit
This commit is contained in:
609
PortfolioManager/ViewModels/CMMomentumViewModel.cs
Normal file
609
PortfolioManager/ViewModels/CMMomentumViewModel.cs
Normal file
@@ -0,0 +1,609 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator;
|
||||
using MarketData.Generator.CMMomentum;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.Generator.Model;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Dialogs;
|
||||
using PortfolioManager.Models;
|
||||
using PortfolioManager.UIUtils;
|
||||
using Position = MarketData.Generator.CMMomentum.Position;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class CMMomentumViewModel : WorkspaceViewModel
|
||||
{
|
||||
private bool isBusy = false;
|
||||
private CMSessionParams sessionParams;
|
||||
private CMParams cmParams;
|
||||
private ObservableCollection<String> nvpDictionaryKeys = default;
|
||||
private NVPDictionary nvpDictionary = null;
|
||||
private String selectedParameter = null;
|
||||
private String pathFileName;
|
||||
private String initialPath;
|
||||
private ModelPerformanceSeries modelPerformanceSeries = null;
|
||||
private ModelStatistics modelStatistics = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private CMPositionModelCollection positions = null;
|
||||
private CMPositionModel selectedPosition = null;
|
||||
private bool showMarkers = false;
|
||||
|
||||
public CMMomentumViewModel()
|
||||
{
|
||||
DisplayName = "CMMomentum Model";
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == pathFileName) return "CMMomentum Model";
|
||||
String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
|
||||
return "CMMomentum Model (" + pureFileName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
public String TradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate));
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<String> Parameters
|
||||
{
|
||||
get { return nvpDictionaryKeys; }
|
||||
}
|
||||
|
||||
public String SelectedParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedParameter;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedParameter = value;
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
}
|
||||
}
|
||||
|
||||
public String ParameterValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
|
||||
return nvpDictionary[selectedParameter].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public String CashBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.CashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<MenuItem> PositionsMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
|
||||
collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReloadEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return !String.IsNullOrEmpty(pathFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public CMPositionModelCollection AllPositions
|
||||
{
|
||||
get { return positions; }
|
||||
}
|
||||
|
||||
public String NonTradeableCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.NonTradeableCash);
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
public String ModelExpectation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
return Utility.FormatNumber(modelStatistics.Expectancy, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush ExpectationColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public CMPositionModel SelectedPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedPosition = value;
|
||||
base.OnPropertyChanged("SelectedPosition");
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelPerformanceSeries) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams || null == modelPerformanceSeries) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Positions allPositions = sessionParams.GetCombinedPositions();
|
||||
DateTime minDate = allPositions.Min(x => x.PurchaseDate);
|
||||
DateTime maxDate = PricingDA.GetLatestDate();
|
||||
|
||||
if (modelPerformanceSeries.Count < 2)
|
||||
{
|
||||
sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
|
||||
return sb.ToString();
|
||||
}
|
||||
if (showAsGainLoss)
|
||||
{
|
||||
double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
|
||||
double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
|
||||
sb.Append("$ GainLoss");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatCurrency(latestGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
|
||||
}
|
||||
else
|
||||
{
|
||||
double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
|
||||
double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
|
||||
double change = latestCumGainLoss - prevCumGainLoss;
|
||||
sb.Append("% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatPercent(latestCumGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************** R E L A Y ***************************************************************
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadFile()
|
||||
{
|
||||
await LoadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Reload()
|
||||
{
|
||||
await ReloadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanClosePosition))]
|
||||
public async Task Close()
|
||||
{
|
||||
await OpenCloseDialog();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEdit))]
|
||||
public async Task Edit()
|
||||
{
|
||||
await OpenEditDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleReturnOrPercent()
|
||||
{
|
||||
HandleToggleReturnOrPercent();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAddToWatchList))]
|
||||
public async Task AddToWatchList()
|
||||
{
|
||||
WatchListDA.AddToWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
|
||||
public async Task RemoveFromWatchList()
|
||||
{
|
||||
WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
// ***************************************************************************************************************************************
|
||||
|
||||
public async Task OpenCloseDialog()
|
||||
{
|
||||
IPurePosition clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
ClosePositionDialog dialog = new ClosePositionDialog();
|
||||
ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = closePositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
if (!closePositionViewModel.IsSuccess) return;
|
||||
CMMomentumBacktest momentumModel = new CMMomentumBacktest();
|
||||
if (!momentumModel.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
|
||||
return;
|
||||
}
|
||||
String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}. Saved to {4}. A backup was created.",
|
||||
clonedPosition.Symbol,
|
||||
clonedPosition.PurchaseDate.ToShortDateString(),
|
||||
clonedPosition.SellDate.ToShortDateString(),
|
||||
Utility.FormatCurrency(clonedPosition.CurrentPrice),
|
||||
pathFileName);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task OpenEditDialog()
|
||||
{
|
||||
EditPositionDialogNoStop dialog = new EditPositionDialogNoStop();
|
||||
Position clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
EditPositionDialogNoStopViewModel editPositionViewModel = new EditPositionDialogNoStopViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = editPositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
GetTopLevelWindow().BringIntoView();
|
||||
if (!editPositionViewModel.IsSuccess) return;
|
||||
CMMomentumBacktest momentumBacktest = new CMMomentumBacktest();
|
||||
if (!momentumBacktest.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
|
||||
return;
|
||||
}
|
||||
selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
|
||||
selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
|
||||
String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}. A backup was created.",
|
||||
selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice));
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
private void HandleToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
public bool CanAddToWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
|
||||
}
|
||||
|
||||
public bool CanClosePosition()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanEdit()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanRemoveFromWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
|
||||
if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task ReloadTradeFile()
|
||||
{
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task LoadTradeFile()
|
||||
{
|
||||
TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Open Trade File",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
IStorageFile storageFile = files.FirstOrDefault();
|
||||
if (null == storageFile) return;
|
||||
Uri uri = storageFile.Path;
|
||||
pathFileName = uri.LocalPath;
|
||||
if (null == pathFileName) return;
|
||||
if (!CMSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
pathFileName = null;
|
||||
}
|
||||
else LoadSessionFile();
|
||||
}
|
||||
|
||||
public bool LoadSessionFile()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CMSessionManager.IsValidSessionFile(pathFileName)) return false;
|
||||
initialPath = Path.GetDirectoryName(pathFileName);
|
||||
sessionParams = CMSessionManager.RestoreSession(pathFileName);
|
||||
if (null == sessionParams) { MxMessageBox.Show(GetTopLevelWindow(), String.Format("Unable to open {0}", pathFileName)); pathFileName = null; return false; }
|
||||
modelStatistics = CMMomentumBacktest.GetModelStatistics(sessionParams);
|
||||
modelPerformanceSeries = CMMomentumBacktest.GetModelPerformance(sessionParams);
|
||||
cmParams = sessionParams.CMParams;
|
||||
NVPCollection nvpCollection = sessionParams.CMParams.ToNVPCollection();
|
||||
nvpDictionary = nvpCollection.ToDictionary();
|
||||
List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
|
||||
dictionaryKeys.Sort();
|
||||
nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
|
||||
selectedParameter = nvpDictionaryKeys[0];
|
||||
positions = new CMPositionModelCollection();
|
||||
positions.Add(sessionParams.ActivePositions);
|
||||
positions.Add(sessionParams.AllPositions);
|
||||
UpdatePositionPrices(false);
|
||||
UpdatePositionRSI3(true);
|
||||
RunPerformance();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), String.Format("Exception {0}", exception.ToString()), "Error");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith(continuation =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("Parameters");
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("DisplayName");
|
||||
base.OnPropertyChanged("AllPositions");
|
||||
// base.OnPropertyChanged("CanMonitor");
|
||||
base.OnPropertyChanged("CashBalance");
|
||||
base.OnPropertyChanged("NonTradeableCash");
|
||||
base.OnPropertyChanged("ModelExpectation");
|
||||
base.OnPropertyChanged("ExpectationColor");
|
||||
base.OnPropertyChanged("ExpectationDescription");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdatePositionPrices(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime today = DateTime.Now;
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from CMPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from CMPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (CMPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
Price price = PricingDA.GetPrice(symbol);
|
||||
if (null == price) continue;
|
||||
selectedPosition.CurrentPrice = price.Close;
|
||||
selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePositionRSI3(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from CMPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from CMPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (CMPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
RSICollection rsiCollection = RSIGenerator.GenerateRSI(symbol, 30, 3);
|
||||
if (null == rsiCollection || 0 == rsiCollection.Count) continue;
|
||||
selectedPosition.RSI3 = rsiCollection[rsiCollection.Count - 1].RSI;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void RunPerformance()
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
modelPerformanceSeries = CMMomentumBacktest.GetModelPerformance(sessionParams);
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
// ********************************************************** P E R S I S T E N C E ******************************************************
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
SaveParameters saveParams = new SaveParameters();
|
||||
if (null == pathFileName) return null;
|
||||
saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
|
||||
saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
|
||||
return saveParams;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
|
||||
if (!LoadSessionFile()) pathFileName = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************** T O O L T I P S *************************************************
|
||||
|
||||
public String ExpectationDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if(null==modelStatistics) return "";
|
||||
StringBuilder sb=new StringBuilder();
|
||||
sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
|
||||
sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss,2)).Append("%").Append("\n");
|
||||
sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy,2)).Append("\n");
|
||||
sb.Append("\n");
|
||||
sb.Append("Maintain a positive Expectancy and you're a winner.");
|
||||
sb.Append("\n");
|
||||
sb.Append("The calculations are based on closed positions.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
621
PortfolioManager/ViewModels/CMTrendViewModel.cs
Normal file
621
PortfolioManager/ViewModels/CMTrendViewModel.cs
Normal file
@@ -0,0 +1,621 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator.CMTrend;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.Generator.Model;
|
||||
using MarketData.Generator.ModelGenerators;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Dialogs;
|
||||
using PortfolioManager.Models;
|
||||
using PortfolioManager.UIUtils;
|
||||
using Position=MarketData.Generator.CMTrend.Position;
|
||||
using StopLimit = MarketData.MarketDataModel.StopLimit;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class CMTrendViewModel : WorkspaceViewModel
|
||||
{
|
||||
private bool isBusy = false;
|
||||
private bool showMarkers = false;
|
||||
private ModelPerformanceSeries modelPerformanceSeries = null;
|
||||
private ModelStatistics modelStatistics = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private CMTPositionModelCollection positions = null;
|
||||
private CMTPositionModel selectedPosition = null;
|
||||
private CMTParams configuration = null;
|
||||
private String pathFileName = null;
|
||||
private String initialPath = null;
|
||||
private CMTSessionParams sessionParams;
|
||||
private NVPDictionary nvpDictionary = null;
|
||||
private ObservableCollection<String> nvpDictionaryKeys = null;
|
||||
private String selectedParameter = null;
|
||||
|
||||
public CMTrendViewModel()
|
||||
{
|
||||
DisplayName = "CMTrend Model";
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
protected override void OnDispose()
|
||||
{
|
||||
base.OnDispose();
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelPerformanceSeries) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ObservableCollection<String> Parameters
|
||||
{
|
||||
get { return nvpDictionaryKeys; }
|
||||
}
|
||||
|
||||
public String SelectedParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedParameter;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedParameter = value;
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
}
|
||||
}
|
||||
|
||||
public String ParameterValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
|
||||
return nvpDictionary[selectedParameter].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public String TradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(sessionParams.TradeDate);
|
||||
}
|
||||
}
|
||||
|
||||
public String CashBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.CashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public String NonTradeableCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.NonTradeableCash);
|
||||
}
|
||||
}
|
||||
|
||||
public String ModelExpectation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
return Utility.FormatNumber(modelStatistics.Expectancy, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush ExpectationColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams || null == modelPerformanceSeries) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Positions allPositions = sessionParams.GetCombinedPositions();
|
||||
DateTime minDate = allPositions.Min(x => x.PurchaseDate);
|
||||
DateTime maxDate = PricingDA.GetLatestDate();
|
||||
|
||||
if (modelPerformanceSeries.Count < 2)
|
||||
{
|
||||
sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
|
||||
return sb.ToString();
|
||||
}
|
||||
if (showAsGainLoss)
|
||||
{
|
||||
double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
|
||||
double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
|
||||
sb.Append("$ GainLoss");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatCurrency(latestGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
|
||||
}
|
||||
else
|
||||
{
|
||||
double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
|
||||
double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
|
||||
double change = latestCumGainLoss - prevCumGainLoss;
|
||||
sb.Append("% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatPercent(latestCumGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************************************
|
||||
public ObservableCollection<MenuItem> PositionsMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
|
||||
collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ********************************************************************* R E L A Y S *****************************************************************
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanClosePosition))]
|
||||
public async Task Close()
|
||||
{
|
||||
await OpenCloseDialog();
|
||||
}
|
||||
|
||||
public bool CanClosePosition()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEdit))]
|
||||
public async Task Edit()
|
||||
{
|
||||
await OpenEditDialog();
|
||||
}
|
||||
|
||||
public bool CanEdit()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleReturnOrPercent()
|
||||
{
|
||||
HandleToggleReturnOrPercent();
|
||||
}
|
||||
|
||||
// This is not currently being displayed
|
||||
// [RelayCommand]
|
||||
// public void Run()
|
||||
// {
|
||||
// RunCandidateGenerator();
|
||||
// }
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadFile()
|
||||
{
|
||||
await LoadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Reload()
|
||||
{
|
||||
await ReloadTradeFile();
|
||||
}
|
||||
|
||||
public async Task ReloadTradeFile()
|
||||
{
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAddToWatchList))]
|
||||
public async Task AddToWatchList()
|
||||
{
|
||||
WatchListDA.AddToWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public bool CanAddToWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
|
||||
public async Task RemoveFromWatchList()
|
||||
{
|
||||
WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public bool CanRemoveFromWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
|
||||
if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
public bool ReloadEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return !String.IsNullOrEmpty(pathFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public CMTPositionModelCollection AllPositions
|
||||
{
|
||||
get { return positions; }
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == pathFileName) return "CMTrend Model";
|
||||
String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
|
||||
return "CMTrend Model (" + pureFileName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ***********************************************************************************************************************************
|
||||
#region Operations
|
||||
|
||||
public async Task OpenCloseDialog()
|
||||
{
|
||||
bool deleteStop = false;
|
||||
IPosition clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
// bool hasStopLimit = PortfolioDA.HasStopLimit(clonedPosition.Symbol);
|
||||
|
||||
ClosePositionDialog dialog = new ClosePositionDialog();
|
||||
ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = closePositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
if (!closePositionViewModel.IsSuccess) return;
|
||||
CMTTrendModel mmTrendModel = new CMTTrendModel();
|
||||
if (!mmTrendModel.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
|
||||
return;
|
||||
}
|
||||
// if (deleteStop) PortfolioDA.DeleteStopLimit(clonedPosition.Symbol);
|
||||
String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}, Delete Stop:{4} to {5}. A backup was created.",
|
||||
clonedPosition.Symbol, clonedPosition.PurchaseDate.ToShortDateString(), clonedPosition.SellDate.ToShortDateString(), Utility.FormatCurrency(clonedPosition.CurrentPrice), deleteStop, pathFileName);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
public async Task OpenEditDialog()
|
||||
{
|
||||
EditPositionDialog dialog = new EditPositionDialog();
|
||||
IPosition clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
EditPositionDialogViewModel editPositionViewModel = new EditPositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = editPositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
GetTopLevelWindow().BringIntoView();
|
||||
if (!editPositionViewModel.IsSuccess) return;
|
||||
CMTTrendModel mmTrendModel = new CMTTrendModel();
|
||||
if (!mmTrendModel.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, clonedPosition.InitialStopLimit, clonedPosition.TrailingStopLimit, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
|
||||
return;
|
||||
}
|
||||
if (!selectedPosition.TrailingStopLimit.Equals(clonedPosition.TrailingStopLimit))
|
||||
{
|
||||
selectedPosition.TrailingStopLimit = clonedPosition.TrailingStopLimit;
|
||||
selectedPosition.LastStopAdjustment = DateTime.Now.Date;
|
||||
}
|
||||
selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
|
||||
selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
|
||||
selectedPosition.TrailingStopLimit = clonedPosition.TrailingStopLimit;
|
||||
selectedPosition.InitialStopLimit = clonedPosition.InitialStopLimit;
|
||||
|
||||
String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}, Trailing Stop:{3}. A backup was created.",
|
||||
selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice), Utility.FormatCurrency(selectedPosition.TrailingStopLimit));
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
public async Task LoadTradeFile()
|
||||
{
|
||||
TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Open Trade File",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
IStorageFile storageFile = files.FirstOrDefault();
|
||||
if (null == storageFile) return;
|
||||
Uri uri = storageFile.Path;
|
||||
pathFileName = uri.LocalPath;
|
||||
|
||||
if (null == pathFileName) return;
|
||||
if (!CMTSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
pathFileName = null;
|
||||
}
|
||||
else LoadSessionFile();
|
||||
}
|
||||
|
||||
public bool LoadSessionFile()
|
||||
{
|
||||
IsBusy = true;
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CMTSessionManager.IsValidSessionFile(pathFileName)) return false;
|
||||
initialPath = Path.GetDirectoryName(pathFileName);
|
||||
sessionParams = CMTSessionManager.RestoreSession(pathFileName);
|
||||
if (null == sessionParams)
|
||||
{
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
modelStatistics = CMTTrendModel.GetModelStatistics(sessionParams);
|
||||
modelPerformanceSeries = CMTTrendModel.GetModelPerformance(sessionParams);
|
||||
configuration = sessionParams.CMTParams;
|
||||
NVPCollection nvpCollection = sessionParams.CMTParams.ToNVPCollection();
|
||||
nvpDictionary = nvpCollection.ToDictionary();
|
||||
List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
|
||||
dictionaryKeys.Sort();
|
||||
nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
|
||||
selectedParameter = nvpDictionaryKeys[0];
|
||||
positions = new CMTPositionModelCollection();
|
||||
positions.Add(sessionParams.ActivePositions);
|
||||
positions.Add(sessionParams.AllPositions);
|
||||
UpdatePositionPrices(false);
|
||||
RunPerformance();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
IsBusy = false;
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith(continuation =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("Parameters");
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("DisplayName");
|
||||
base.OnPropertyChanged("AllPositions");
|
||||
base.OnPropertyChanged("CashBalance");
|
||||
base.OnPropertyChanged("NonTradeableCash");
|
||||
base.OnPropertyChanged("ModelExpectation");
|
||||
base.OnPropertyChanged("ExpectationColor");
|
||||
base.OnPropertyChanged("ExpectationDescription");
|
||||
base.OnPropertyChanged("ReloadEnabled");
|
||||
base.OnPropertyChanged("TradeDate");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RunPerformance()
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
modelPerformanceSeries = CMTTrendModel.GetModelPerformance(sessionParams);
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
private void UpdatePositionPrices(bool signalChangeEvent = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime today = DateTime.Now;
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from CMTPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from CMTPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (CMTPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
Price price = PricingDA.GetPrice(symbol);
|
||||
if (null == price) continue;
|
||||
selectedPosition.CurrentPrice = price.Close;
|
||||
selectedPosition.CurrentPriceLow = price.Low;
|
||||
selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
|
||||
selectedPosition.EdgeRatio = EdgeRatioGenerator.CalculateEdgeRatio(symbol, selectedPosition.PurchaseDate, selectedPosition.PurchasePrice, price.Date).EdgeRatio;
|
||||
}
|
||||
}
|
||||
if (signalChangeEvent) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public CMTPositionModel SelectedPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedPosition = value;
|
||||
base.OnPropertyChanged("SelectedPosition");
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************************** P E R S I S T E N C E ********************************************************
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
SaveParameters saveParams = new SaveParameters();
|
||||
if (null == pathFileName) return null;
|
||||
saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
|
||||
saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
|
||||
return saveParams;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
|
||||
if (!LoadSessionFile()) pathFileName = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************** T O O L T I P S *************************************************
|
||||
|
||||
public String ExpectationDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if(null==modelStatistics) return "";
|
||||
StringBuilder sb=new StringBuilder();
|
||||
sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
|
||||
sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss,2)).Append("%").Append("\n");
|
||||
sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy,2)).Append("\n");
|
||||
sb.Append("\n");
|
||||
sb.Append("Maintain a positive Expectancy and you're a winner.");
|
||||
sb.Append("\n");
|
||||
sb.Append("The calculations are based on closed positions.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
PortfolioManager/ViewModels/CommandViewModel.cs
Normal file
37
PortfolioManager/ViewModels/CommandViewModel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Input;
|
||||
using System.Text;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public class CommandViewModel : ViewModelBase
|
||||
{
|
||||
public CommandViewModel(string displayName, ICommand command)
|
||||
{
|
||||
if (command == null) throw new ArgumentNullException("command");
|
||||
base.DisplayName = displayName;
|
||||
this.Command = command;
|
||||
}
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
PortfolioManager/ViewModels/DialogViewModelBase.cs
Normal file
45
PortfolioManager/ViewModels/DialogViewModelBase.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Input;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public interface ICloseableDialog
|
||||
{
|
||||
public Task Close();
|
||||
}
|
||||
|
||||
public abstract class DialogViewModelBase : ModelBase, ICloseableDialog, IDisposable
|
||||
{
|
||||
public DialogViewModelBase(Window dialogWindow)
|
||||
{
|
||||
DialogWindow = dialogWindow;
|
||||
}
|
||||
|
||||
public virtual async Task Close()
|
||||
{
|
||||
Dispose();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (null == DialogWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DialogWindow.Hide(); // !!Important ARM64
|
||||
DialogWindow.Close();
|
||||
DialogWindow = null;
|
||||
}
|
||||
|
||||
public bool IsSuccess { get; set; } = false;
|
||||
|
||||
protected Window DialogWindow { get; private set; }
|
||||
}
|
||||
}
|
||||
822
PortfolioManager/ViewModels/GainLossViewModel.cs
Normal file
822
PortfolioManager/ViewModels/GainLossViewModel.cs
Normal file
@@ -0,0 +1,822 @@
|
||||
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 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;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class GainLossViewModel : WorkspaceViewModel
|
||||
{
|
||||
private const String ALL = "{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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// ****************************************************** 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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
PortfolioManager/ViewModels/IPersistentViewModel.cs
Normal file
9
PortfolioManager/ViewModels/IPersistentViewModel.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public interface IPersistentViewModel
|
||||
{
|
||||
SaveParameters GetSaveParameters();
|
||||
void SetSaveParameters(SaveParameters saveParameters);
|
||||
bool CanPersist();
|
||||
}
|
||||
}
|
||||
712
PortfolioManager/ViewModels/MGSHMomentumViewModel.cs
Normal file
712
PortfolioManager/ViewModels/MGSHMomentumViewModel.cs
Normal file
@@ -0,0 +1,712 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Labs.Input;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator;
|
||||
using MarketData.Generator.MGSHMomentum;
|
||||
using MarketData.Generator.Model;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Dialogs;
|
||||
using PortfolioManager.Models;
|
||||
using PortfolioManager.UIUtils;
|
||||
using StopLimit = MarketData.MarketDataModel.StopLimit;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class MGSHMomentumViewModel : WorkspaceViewModel
|
||||
{
|
||||
private ObservableCollection<MGSHMomentumCandidate> momentumCandidates = new ObservableCollection<MGSHMomentumCandidate>();
|
||||
private MGSHSessionParams sessionParams;
|
||||
private String initialPath = null;
|
||||
private MGSHPositionModelCollection positions = null;
|
||||
private ModelPerformanceSeries modelPerformanceSeries = null;
|
||||
private ModelStatistics modelStatistics = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private String pathFileName = null;
|
||||
private ObservableCollection<String> nvpDictionaryKeys = default;
|
||||
private NVPDictionary nvpDictionary = null;
|
||||
private String selectedParameter = null;
|
||||
private MGSHConfiguration configuration = null;
|
||||
private MGSHPositionModel selectedPosition = null;
|
||||
private bool isBusy = false;
|
||||
private bool showMarkers = false;
|
||||
|
||||
public MGSHMomentumViewModel()
|
||||
{
|
||||
DisplayName = "MGSHMomentum Model";
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
protected override void OnDispose()
|
||||
{
|
||||
base.OnDispose();
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelPerformanceSeries) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************************************
|
||||
public ObservableCollection<MenuItem> PositionsMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
|
||||
collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************************* R E L A Y S *****************************************************************
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanClosePosition))]
|
||||
public async Task Close()
|
||||
{
|
||||
await OpenCloseDialog();
|
||||
}
|
||||
|
||||
public bool CanClosePosition()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEdit))]
|
||||
public async Task Edit()
|
||||
{
|
||||
await OpenEditDialog();
|
||||
}
|
||||
|
||||
public bool CanEdit()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleReturnOrPercent()
|
||||
{
|
||||
HandleToggleReturnOrPercent();
|
||||
}
|
||||
|
||||
// This is not currently being displayed
|
||||
[RelayCommand]
|
||||
public void Run()
|
||||
{
|
||||
RunCandidateGenerator();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadFile()
|
||||
{
|
||||
await LoadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Reload()
|
||||
{
|
||||
await ReloadTradeFile();
|
||||
}
|
||||
|
||||
public async Task ReloadTradeFile()
|
||||
{
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAddToWatchList))]
|
||||
public async Task AddToWatchList()
|
||||
{
|
||||
WatchListDA.AddToWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public bool CanAddToWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
|
||||
public async Task RemoveFromWatchList()
|
||||
{
|
||||
WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public bool CanRemoveFromWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
|
||||
if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ****************************************************************************************************************************************************
|
||||
#region Operations
|
||||
|
||||
public async Task OpenEditDialog()
|
||||
{
|
||||
EditPositionDialog dialog = new EditPositionDialog();
|
||||
MGSHPosition clonedPosition = MGSHPosition.Clone(selectedPosition.Position);
|
||||
EditPositionDialogViewModel editPositionViewModel = new EditPositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = editPositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
GetTopLevelWindow().BringIntoView();
|
||||
if (!editPositionViewModel.IsSuccess) return;
|
||||
MGSHMomentumBacktest mgshMomentumBacktest = new MGSHMomentumBacktest();
|
||||
if (!mgshMomentumBacktest.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, clonedPosition.InitialStopLimit, clonedPosition.TrailingStopLimit, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
|
||||
return;
|
||||
}
|
||||
if (!selectedPosition.TrailingStopLimit.Equals(clonedPosition.TrailingStopLimit))
|
||||
{
|
||||
selectedPosition.TrailingStopLimit = clonedPosition.TrailingStopLimit;
|
||||
selectedPosition.LastStopAdjustment = DateTime.Now.Date;
|
||||
// StopLimit stopLimit = PortfolioDA.GetStopLimit(clonedPosition.Symbol);
|
||||
// if (null == stopLimit)
|
||||
// {
|
||||
// stopLimit = new StopLimit();
|
||||
// stopLimit.Symbol = clonedPosition.Symbol;
|
||||
// stopLimit.StopType = StopLimitConstants.STOP_QUOTE;
|
||||
// stopLimit.Shares = clonedPosition.Shares;
|
||||
// }
|
||||
// stopLimit.StopPrice = clonedPosition.TrailingStopLimit;
|
||||
// PortfolioDA.InsertUpdateStopLimit(stopLimit);
|
||||
}
|
||||
selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
|
||||
selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
|
||||
selectedPosition.TrailingStopLimit = clonedPosition.TrailingStopLimit;
|
||||
selectedPosition.InitialStopLimit = clonedPosition.InitialStopLimit;
|
||||
|
||||
String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}, Trailing Stop:{3}. A backup was created.",
|
||||
selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice), Utility.FormatCurrency(selectedPosition.TrailingStopLimit));
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
public async Task OpenCloseDialog()
|
||||
{
|
||||
bool deleteStop = false;
|
||||
MGSHPosition clonedPosition = MGSHPosition.Clone(selectedPosition.Position);
|
||||
// bool hasStopLimit = PortfolioDA.HasStopLimit(clonedPosition.Symbol);
|
||||
|
||||
ClosePositionDialog dialog = new ClosePositionDialog();
|
||||
ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = closePositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
if (!closePositionViewModel.IsSuccess) return;
|
||||
MGSHMomentumBacktest mgshMomentumBacktest = new MGSHMomentumBacktest();
|
||||
if (!mgshMomentumBacktest.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
|
||||
return;
|
||||
}
|
||||
// if (deleteStop) PortfolioDA.DeleteStopLimit(clonedPosition.Symbol);
|
||||
String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}, Delete Stop:{4} to {5}. A backup was created.",
|
||||
clonedPosition.Symbol, clonedPosition.PurchaseDate.ToShortDateString(), clonedPosition.SellDate.ToShortDateString(), Utility.FormatCurrency(clonedPosition.CurrentPrice), deleteStop, pathFileName);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void HandleToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
// This is not currently being dispplayed
|
||||
private void RunCandidateGenerator()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
DateTime selectedDate = dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate);
|
||||
IsBusy = true;
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
MGSHConfiguration localConfiguration = new MGSHConfiguration();
|
||||
localConfiguration.MaxPositions = int.Parse(nvpDictionary["MaxPositions"].Value);
|
||||
localConfiguration.HoldingPeriod = int.Parse(nvpDictionary["HoldingPeriod"].Value);
|
||||
MGSHMomentumCandidates candidates = MGSHMomentumGenerator.GenerateMomentumWithFallback(selectedDate, configuration == null ? localConfiguration : configuration);
|
||||
momentumCandidates = new ObservableCollection<MGSHMomentumCandidate>();
|
||||
foreach (MGSHMomentumCandidate momentumCandidate in candidates) momentumCandidates.Add(momentumCandidate);
|
||||
});
|
||||
workerTask.ContinueWith((continuation) =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("AllItems");
|
||||
base.OnPropertyChanged("Title");
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadTradeFile()
|
||||
{
|
||||
TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Open Trade File",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
IStorageFile storageFile = files.FirstOrDefault();
|
||||
if (null == storageFile) return;
|
||||
Uri uri = storageFile.Path;
|
||||
pathFileName = uri.LocalPath;
|
||||
|
||||
if (null == pathFileName) return;
|
||||
if (!MGSHSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
pathFileName = null;
|
||||
}
|
||||
else LoadSessionFile();
|
||||
}
|
||||
|
||||
public bool LoadSessionFile()
|
||||
{
|
||||
IsBusy = true;
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!MGSHSessionManager.IsValidSessionFile(pathFileName)) return false;
|
||||
initialPath = Path.GetDirectoryName(pathFileName);
|
||||
sessionParams = MGSHSessionManager.RestoreSession(pathFileName);
|
||||
if (null == sessionParams)
|
||||
{
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
modelStatistics = MGSHMomentumBacktest.GetModelStatistics(sessionParams);
|
||||
modelPerformanceSeries = MGSHMomentumBacktest.GetModelPerformance(sessionParams);
|
||||
configuration = sessionParams.Configuration;
|
||||
NVPCollection nvpCollection = sessionParams.Configuration.ToNVPCollection();
|
||||
nvpDictionary = nvpCollection.ToDictionary();
|
||||
List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
|
||||
dictionaryKeys.Sort();
|
||||
nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
|
||||
selectedParameter = nvpDictionaryKeys[0];
|
||||
positions = new MGSHPositionModelCollection();
|
||||
positions.Add(sessionParams.ActivePositions); // active positions will go into their assigned slot
|
||||
positions.Add(sessionParams.HedgePositions, sessionParams.ActivePositions.GetMaxSlotNumber() + 1); // -1 is a special slot so active hedge positions will always appear in the slot position 1 past the max
|
||||
positions.Add(sessionParams.AllPositions);
|
||||
UpdatePositionPrices(false);
|
||||
UpdatePositionRSI3(true);
|
||||
RunPerformance();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception.ToString());
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith(continuation =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("Parameters");
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("DisplayName");
|
||||
base.OnPropertyChanged("AllPositions");
|
||||
base.OnPropertyChanged("CashBalance");
|
||||
base.OnPropertyChanged("NonTradeableCash");
|
||||
base.OnPropertyChanged("HedgeCash");
|
||||
base.OnPropertyChanged("ModelExpectation");
|
||||
base.OnPropertyChanged("ExpectationColor");
|
||||
base.OnPropertyChanged("ExpectationDescription");
|
||||
base.OnPropertyChanged("ReloadEnabled");
|
||||
base.OnPropertyChanged("LastTradeDate");
|
||||
base.OnPropertyChanged("NextTradeDate");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdatePositionPrices(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime today = DateTime.Now;
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from MGSHPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from MGSHPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (MGSHPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
Price price = PricingDA.GetPrice(symbol);
|
||||
if (null == price) continue;
|
||||
selectedPosition.CurrentPrice = price.Close;
|
||||
selectedPosition.CurrentPriceLow = price.Low;
|
||||
selectedPosition.Volume = price.Volume;
|
||||
selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePositionRSI3(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from MGSHPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from MGSHPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (MGSHPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
RSICollection rsiCollection = RSIGenerator.GenerateRSI(symbol, 30, 3);
|
||||
if (null == rsiCollection || 0 == rsiCollection.Count) continue;
|
||||
selectedPosition.RSI3 = rsiCollection[rsiCollection.Count - 1].RSI;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void RunPerformance()
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
modelPerformanceSeries = MGSHMomentumBacktest.GetModelPerformance(sessionParams);
|
||||
base.OnPropertyChanged("Data"); // The CompositeDataSource for the ChartPlotters
|
||||
base.OnPropertyChanged("GraphTitle"); // The Title for the ChartPlotter
|
||||
}
|
||||
|
||||
// ************************************************************************************************************************
|
||||
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
SaveParameters saveParams = new SaveParameters();
|
||||
if (null == pathFileName) return null;
|
||||
saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
|
||||
saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
|
||||
return saveParams;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
|
||||
if (!LoadSessionFile()) pathFileName = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// **************************************************** P R O P E R T I E S ********************************************************
|
||||
// *****************************************************************************************************************************
|
||||
|
||||
public bool ReloadEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return !String.IsNullOrEmpty(pathFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public MGSHPositionModelCollection AllPositions
|
||||
{
|
||||
get { return positions; }
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == pathFileName) return "MGSHMomentum Model";
|
||||
String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
|
||||
return "MGSHMomentum Model (" + pureFileName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<String> Parameters
|
||||
{
|
||||
get { return nvpDictionaryKeys; }
|
||||
}
|
||||
|
||||
public String SelectedParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedParameter;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedParameter = value;
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
}
|
||||
}
|
||||
|
||||
public String ParameterValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
|
||||
return nvpDictionary[selectedParameter].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public String LastTradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate));
|
||||
}
|
||||
}
|
||||
|
||||
public String NextTradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(sessionParams.TradeDate);
|
||||
}
|
||||
}
|
||||
|
||||
public String CashBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.CashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public String NonTradeableCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.NonTradeableCash);
|
||||
}
|
||||
}
|
||||
|
||||
public String HedgeCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return "";
|
||||
return Utility.FormatCurrency(sessionParams.HedgeCashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams || null == modelPerformanceSeries) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
MGSHPositions allPositions = sessionParams.GetCombinedPositions();
|
||||
DateTime minDate = allPositions.Min(x => x.PurchaseDate);
|
||||
DateTime maxDate = PricingDA.GetLatestDate();
|
||||
|
||||
if (modelPerformanceSeries.Count < 2)
|
||||
{
|
||||
sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
|
||||
return sb.ToString();
|
||||
}
|
||||
if (showAsGainLoss)
|
||||
{
|
||||
double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
|
||||
double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
|
||||
sb.Append("$ GainLoss");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatCurrency(latestGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
|
||||
}
|
||||
else
|
||||
{
|
||||
double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
|
||||
double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
|
||||
double change = latestCumGainLoss - prevCumGainLoss;
|
||||
sb.Append("% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatPercent(latestCumGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public String ModelExpectation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
return Utility.FormatNumber(modelStatistics.Expectancy, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush ExpectationColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public MGSHPositionModel SelectedPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedPosition = value;
|
||||
base.OnPropertyChanged("SelectedPosition");
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************** T O O L T I P S *************************************************
|
||||
|
||||
public String ExpectationDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if(null==modelStatistics) return "";
|
||||
StringBuilder sb=new StringBuilder();
|
||||
sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
|
||||
sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss,2)).Append("%").Append("\n");
|
||||
sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy,2)).Append("\n");
|
||||
sb.Append("\n");
|
||||
sb.Append("Maintain a positive Expectancy and you're a winner.");
|
||||
sb.Append("\n");
|
||||
sb.Append("The calculations are based on closed positions.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
247
PortfolioManager/ViewModels/MainWindowViewModel.cs
Normal file
247
PortfolioManager/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Threading;
|
||||
using MarketData.Cache;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.Command;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public class TabIndexArgs : EventArgs
|
||||
{
|
||||
public int Index { get; set; }
|
||||
}
|
||||
|
||||
public partial class MainWindowViewModel : WorkspaceViewModel
|
||||
{
|
||||
private ReadOnlyCollection<CommandViewModel> commands;
|
||||
private ObservableCollection<WorkspaceViewModel> workspaces;
|
||||
private int selectedIndex = -1;
|
||||
|
||||
public EventHandler<TabIndexArgs> OnIndexChangeEventHandler;
|
||||
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
base.DisplayName = GetTitle();
|
||||
Dispatcher.UIThread.InvokeAsync(() => LoadViewStateThreadProc());
|
||||
}
|
||||
|
||||
private static String GetTitle()
|
||||
{
|
||||
DataSourceEx dataSource = MainDataSource.Instance.LocateDataSource("market_data");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("eNavigator").Append(" ").Append("(").Append(Environment.UserName).Append(")").Append(" ");
|
||||
sb.Append("[").Append(Environment.OSVersion.VersionString).Append("]").Append(" ");
|
||||
sb.Append("[").Append("DataSource->").Append(dataSource.Datasource).Append("]");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void LoadViewStateThreadProc()
|
||||
{
|
||||
WorkspacePersistenceHelper.Load(WorkspacePersistenceHelper.PARAMS_FILE, this.Workspaces, InstantiateWorkspace);
|
||||
}
|
||||
|
||||
protected override void OnDispose()
|
||||
{
|
||||
WorkspacePersistenceHelper.Save(WorkspacePersistenceHelper.PARAMS_FILE, this.Workspaces);
|
||||
|
||||
foreach (WorkspaceViewModel workspaceViewModel in this.Workspaces)
|
||||
{
|
||||
workspaceViewModel.Dispose();
|
||||
}
|
||||
|
||||
try { LocalPriceCache.GetInstance().Dispose(); } catch (Exception) {; }
|
||||
|
||||
try { GBPriceCache.GetInstance().Dispose(); } catch (Exception) {; }
|
||||
|
||||
// try{PriceCache.GetInstance().Dispose();}catch(Exception){;}
|
||||
// try{SymbolCache.GetInstance().Dispose();}catch(Exception){;}
|
||||
base.OnDispose();
|
||||
}
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<CommandViewModel> Commands
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == commands)
|
||||
{
|
||||
List<CommandViewModel> commandList = this.CreateCommands();
|
||||
commands = new ReadOnlyCollection<CommandViewModel>(commandList);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CommandViewModel> CreateCommands()
|
||||
{
|
||||
return new List<CommandViewModel>()
|
||||
{
|
||||
new CommandViewModel("Gain/Loss", new MyRelayCommand(ParamArrayAttribute => this.ViewGainLoss())),
|
||||
new CommandViewModel("Momentum Model", new MyRelayCommand(ParamArrayAttribute => this.ViewMomentum())),
|
||||
new CommandViewModel("MGSHMomentum Model", new MyRelayCommand(ParamArrayAttribute => this.ViewMGSHMomentum())),
|
||||
new CommandViewModel("CMMomentum Model", new MyRelayCommand(ParamArrayAttribute => this.ViewCMMomentum())),
|
||||
new CommandViewModel("CMTrend Model", new MyRelayCommand(ParamArrayAttribute => this.ViewCMTrend()))
|
||||
};
|
||||
}
|
||||
private void ViewCMTrend()
|
||||
{
|
||||
CMTrendViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new CMTrendViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
private void ViewGainLoss()
|
||||
{
|
||||
GainLossViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new GainLossViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
private void ViewMGSHMomentum()
|
||||
{
|
||||
MGSHMomentumViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new MGSHMomentumViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
private void ViewMomentum()
|
||||
{
|
||||
MomentumViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new MomentumViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
private void ViewCMMomentum()
|
||||
{
|
||||
CMMomentumViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new CMMomentumViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
public ObservableCollection<WorkspaceViewModel> Workspaces
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == workspaces)
|
||||
{
|
||||
workspaces = new ObservableCollection<WorkspaceViewModel>();
|
||||
workspaces.CollectionChanged += this.OnWorkspacesChanged;
|
||||
}
|
||||
return workspaces;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.NewItems != null && e.NewItems.Count != 0)
|
||||
{
|
||||
foreach (WorkspaceViewModel workspace in e.NewItems)
|
||||
{
|
||||
workspace.RequestClose += this.OnWorkspaceRequestClose;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.OldItems != null && e.OldItems.Count != 0)
|
||||
{
|
||||
foreach (WorkspaceViewModel workspace in e.OldItems)
|
||||
{
|
||||
workspace.RequestClose -= this.OnWorkspaceRequestClose;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWorkspaceRequestClose(object sender, EventArgs e)
|
||||
{
|
||||
WorkspaceViewModel workspace = sender as WorkspaceViewModel;
|
||||
workspace.Dispose();
|
||||
this.Workspaces.Remove(workspace);
|
||||
RemoveMenuItem(workspace);
|
||||
if (null != workspace.Referer) this.SetActiveWorkspace(workspace.Referer);
|
||||
}
|
||||
|
||||
public void RemoveMenuItem(WorkspaceViewModel viewModel)
|
||||
{
|
||||
// if(!menuCollectionDictionary.ContainsKey(viewModel))return;
|
||||
// menuCollection.Remove(menuCollectionDictionary[viewModel]);
|
||||
// menuCollectionDictionary.Remove(viewModel);
|
||||
}
|
||||
|
||||
private void SetActiveWorkspace(WorkspaceViewModel workspace)
|
||||
{
|
||||
int itemIndex = workspaces.IndexOf(workspace);
|
||||
SelectedIndex = itemIndex;
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedIndex;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedIndex = value;
|
||||
TabIndexArgs args = new TabIndexArgs() { Index = selectedIndex };
|
||||
OnIndexChangeEventHandler.Invoke(this, args);
|
||||
base.OnPropertyChanged("SelectedIndex");
|
||||
}
|
||||
}
|
||||
|
||||
public void InstantiateWorkspace(SaveParameters saveParameters)
|
||||
{
|
||||
WorkspaceViewModel workspaceViewModel = WorkspacePersistenceHelper.Load(saveParameters, workspaces, InstantiateWorkspace);
|
||||
// AddMenuItem(workspaceViewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
PortfolioManager/ViewModels/ModelBase.cs
Normal file
60
PortfolioManager/ViewModels/ModelBase.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Axiom.Utils;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
|
||||
public abstract class ModelBase : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
|
||||
public Window GetTopLevelWindow()
|
||||
{
|
||||
Window currentWindow = default;
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
currentWindow = desktop.MainWindow;
|
||||
}
|
||||
return currentWindow;
|
||||
}
|
||||
|
||||
public virtual String DisplayName
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
protected virtual bool ThrowOnInvalidPropertyName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
protected virtual void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
this.VerifyPropertyName(propertyName);
|
||||
PropertyChangedEventHandler handler = PropertyChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var e = new PropertyChangedEventArgs(propertyName);
|
||||
handler(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void VerifyPropertyName(string propertyName)
|
||||
{
|
||||
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
|
||||
{
|
||||
string message = "Invalid property name: " + propertyName;
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, message);
|
||||
if (this.ThrowOnInvalidPropertyName) throw new Exception(message); else Debug.Fail(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
622
PortfolioManager/ViewModels/MomentumViewModel.cs
Normal file
622
PortfolioManager/ViewModels/MomentumViewModel.cs
Normal file
@@ -0,0 +1,622 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.Generator.Model;
|
||||
using MarketData.Generator.Momentum;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Dialogs;
|
||||
using PortfolioManager.Models;
|
||||
using PortfolioManager.UIUtils;
|
||||
using Position = MarketData.Generator.Momentum.Position;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class MomentumViewModel : WorkspaceViewModel
|
||||
{
|
||||
private bool isBusy = false;
|
||||
private MGSessionParams sessionParams;
|
||||
private ObservableCollection<String> nvpDictionaryKeys = default;
|
||||
private NVPDictionary nvpDictionary = null;
|
||||
private String selectedParameter = null;
|
||||
private String pathFileName;
|
||||
private String initialPath;
|
||||
private ModelPerformanceSeries modelPerformanceSeries = null;
|
||||
private ModelStatistics modelStatistics = null;
|
||||
private MGConfiguration configuration = null;
|
||||
private MGPositionModelCollection positions = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private MGPositionModel selectedPosition = null;
|
||||
private bool showMarkers = false;
|
||||
|
||||
public MomentumViewModel()
|
||||
{
|
||||
DisplayName = "MGMomentum Model";
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == pathFileName) return "MGMomentum Model";
|
||||
String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
|
||||
return "MGMomentum Model (" + pureFileName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
public String TradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate));
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<String> Parameters
|
||||
{
|
||||
get { return nvpDictionaryKeys; }
|
||||
}
|
||||
|
||||
public String SelectedParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedParameter;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedParameter = value;
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
}
|
||||
}
|
||||
|
||||
public String ParameterValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
|
||||
return nvpDictionary[selectedParameter].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public String CashBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.CashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<MenuItem> PositionsMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
|
||||
collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReloadEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return !String.IsNullOrEmpty(pathFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public MGPositionModelCollection AllPositions
|
||||
{
|
||||
get { return positions; }
|
||||
}
|
||||
|
||||
public String NonTradeableCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.NonTradeableCash);
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
public String ModelExpectation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
return Utility.FormatNumber(modelStatistics.Expectancy, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush ExpectationColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public MGPositionModel SelectedPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedPosition = value;
|
||||
base.OnPropertyChanged("SelectedPosition");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelPerformanceSeries) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams || null == modelPerformanceSeries) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Positions allPositions = sessionParams.GetCombinedPositions();
|
||||
DateTime minDate = allPositions.Min(x => x.PurchaseDate);
|
||||
DateTime maxDate = PricingDA.GetLatestDate();
|
||||
|
||||
if (modelPerformanceSeries.Count < 2)
|
||||
{
|
||||
sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
|
||||
return sb.ToString();
|
||||
}
|
||||
if (showAsGainLoss)
|
||||
{
|
||||
double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
|
||||
double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
|
||||
sb.Append("$ GainLoss");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatCurrency(latestGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
|
||||
}
|
||||
else
|
||||
{
|
||||
double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
|
||||
double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
|
||||
double change = latestCumGainLoss - prevCumGainLoss;
|
||||
sb.Append("% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatPercent(latestCumGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************** R E L A Y ***************************************************************
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadFile()
|
||||
{
|
||||
await LoadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Reload()
|
||||
{
|
||||
await ReloadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanClosePosition))]
|
||||
public async Task Close()
|
||||
{
|
||||
await OpenCloseDialog();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEdit))]
|
||||
public async Task Edit()
|
||||
{
|
||||
await OpenEditDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleReturnOrPercent()
|
||||
{
|
||||
HandleToggleReturnOrPercent();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAddToWatchList))]
|
||||
public async Task AddToWatchList()
|
||||
{
|
||||
WatchListDA.AddToWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
|
||||
public async Task RemoveFromWatchList()
|
||||
{
|
||||
WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
// ****************************************************************************************************************************************
|
||||
|
||||
public async Task OpenCloseDialog()
|
||||
{
|
||||
IPurePosition clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
ClosePositionDialog dialog = new ClosePositionDialog();
|
||||
ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = closePositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
if (!closePositionViewModel.IsSuccess) return;
|
||||
MomentumBacktest momentumModel = new MomentumBacktest();
|
||||
if (!momentumModel.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
|
||||
return;
|
||||
}
|
||||
String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}. Saved to {4}. A backup was created.",
|
||||
clonedPosition.Symbol,
|
||||
clonedPosition.PurchaseDate.ToShortDateString(),
|
||||
clonedPosition.SellDate.ToShortDateString(),
|
||||
Utility.FormatCurrency(clonedPosition.CurrentPrice),
|
||||
pathFileName);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task OpenEditDialog()
|
||||
{
|
||||
EditPositionDialogNoStop dialog = new EditPositionDialogNoStop();
|
||||
Position clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
EditPositionDialogNoStopViewModel editPositionViewModel = new EditPositionDialogNoStopViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = editPositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
GetTopLevelWindow().BringIntoView();
|
||||
if (!editPositionViewModel.IsSuccess) return;
|
||||
MomentumBacktest momentumBacktest = new MomentumBacktest();
|
||||
if (!momentumBacktest.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
|
||||
return;
|
||||
}
|
||||
selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
|
||||
selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
|
||||
String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}. A backup was created.",
|
||||
selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice));
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
private void HandleToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
public bool CanAddToWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
|
||||
}
|
||||
|
||||
public bool CanClosePosition()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanEdit()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanRemoveFromWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
|
||||
if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task ReloadTradeFile()
|
||||
{
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task LoadTradeFile()
|
||||
{
|
||||
TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Open Trade File",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
IStorageFile storageFile = files.FirstOrDefault();
|
||||
if (null == storageFile) return;
|
||||
Uri uri = storageFile.Path;
|
||||
pathFileName = uri.LocalPath;
|
||||
if (null == pathFileName) return;
|
||||
if (!MGSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
pathFileName = null;
|
||||
}
|
||||
else LoadSessionFile();
|
||||
}
|
||||
|
||||
public bool LoadSessionFile()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!MGSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), String.Format("'{0}' is not a valid model. IsValidSessionFile returned false.", pathFileName));
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
initialPath = Path.GetDirectoryName(pathFileName);
|
||||
sessionParams = MGSessionManager.RestoreSession(pathFileName);
|
||||
if (null == sessionParams)
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), String.Format("Unable to open '{0}'. Restore session failed.", pathFileName));
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
modelStatistics = MomentumBacktest.GetModelStatistics(sessionParams);
|
||||
modelPerformanceSeries = MomentumBacktest.GetModelPerformance(sessionParams);
|
||||
configuration = sessionParams.Configuration;
|
||||
NVPCollection nvpCollection = sessionParams.Configuration.ToNVPCollection();
|
||||
nvpDictionary = nvpCollection.ToDictionary();
|
||||
List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
|
||||
dictionaryKeys.Sort();
|
||||
nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
|
||||
selectedParameter = nvpDictionaryKeys[0];
|
||||
positions = new MGPositionModelCollection();
|
||||
positions.Add(sessionParams.ActivePositions);
|
||||
positions.Add(sessionParams.AllPositions);
|
||||
UpdatePositionPrices(false);
|
||||
UpdatePositionRSI3(true);
|
||||
RunPerformance();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), String.Format("Exception {0}", exception.ToString()), "Error");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith(continuation =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("Parameters");
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("DisplayName");
|
||||
base.OnPropertyChanged("AllPositions");
|
||||
// base.OnPropertyChanged("CanMonitor");
|
||||
base.OnPropertyChanged("CashBalance");
|
||||
base.OnPropertyChanged("NonTradeableCash");
|
||||
base.OnPropertyChanged("ModelExpectation");
|
||||
base.OnPropertyChanged("ExpectationColor");
|
||||
base.OnPropertyChanged("ReloadEnabled");
|
||||
base.OnPropertyChanged("ExpectationDescription");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdatePositionPrices(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime today = DateTime.Now;
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from MGPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from MGPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (MGPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
Price price = PricingDA.GetPrice(symbol);
|
||||
if (null == price) continue;
|
||||
selectedPosition.CurrentPrice = price.Close;
|
||||
selectedPosition.Volume = price.Volume;
|
||||
selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePositionRSI3(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from MGPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from MGPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (MGPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
RSICollection rsiCollection = RSIGenerator.GenerateRSI(symbol, 30, 3);
|
||||
if (null == rsiCollection || 0 == rsiCollection.Count) continue;
|
||||
selectedPosition.RSI3 = rsiCollection[rsiCollection.Count - 1].RSI;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void RunPerformance()
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
modelPerformanceSeries = MomentumBacktest.GetModelPerformance(sessionParams);
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
// ********************************************************** P E R S I S T E N C E ******************************************************
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
SaveParameters saveParams = new SaveParameters();
|
||||
if (null == pathFileName) return null;
|
||||
saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
|
||||
saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
|
||||
return saveParams;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
|
||||
if (!LoadSessionFile()) pathFileName = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************** T O O L T I P S *************************************************
|
||||
|
||||
public String ExpectationDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
|
||||
sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent, 2)).Append("%").Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent, 2)).Append("%").Append("\n");
|
||||
sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain, 2)).Append("%").Append("\n");
|
||||
sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss, 2)).Append("%").Append("\n");
|
||||
sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy, 2)).Append("\n");
|
||||
sb.Append("\n");
|
||||
sb.Append("Maintain a positive Expectancy and you're a winner.");
|
||||
sb.Append("\n");
|
||||
sb.Append("The calculations are based on closed positions.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
PortfolioManager/ViewModels/ViewModelBase.cs
Normal file
36
PortfolioManager/ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public abstract class ViewModelBase : ModelBase, IDisposable, IPersistentViewModel
|
||||
{
|
||||
protected ViewModelBase()
|
||||
{
|
||||
}
|
||||
public abstract SaveParameters GetSaveParameters();
|
||||
|
||||
public abstract void SetSaveParameters(SaveParameters saveParameters);
|
||||
|
||||
public abstract bool CanPersist();
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
this.OnDispose();
|
||||
}
|
||||
|
||||
protected virtual void OnDispose()
|
||||
{
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
~ViewModelBase()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
110
PortfolioManager/ViewModels/WorkspaceViewModel.cs
Normal file
110
PortfolioManager/ViewModels/WorkspaceViewModel.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public delegate void InstantiateWorkspace(SaveParameters saveParameters);
|
||||
|
||||
public abstract class WorkspaceViewModel : ViewModelBase
|
||||
{
|
||||
// Relay Command
|
||||
private RelayCommand closeCommand;
|
||||
|
||||
// Events
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
private InstantiateWorkspace workspaceInstantiator;
|
||||
private bool canClose = true;
|
||||
private bool isClosed = false;
|
||||
private String title = "WorkspaceViewModel";
|
||||
|
||||
public WorkspaceViewModel Referer { get; set; }
|
||||
|
||||
protected WorkspaceViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public InstantiateWorkspace WorkspaceInstantiator
|
||||
{
|
||||
get
|
||||
{
|
||||
return workspaceInstantiator;
|
||||
}
|
||||
set
|
||||
{
|
||||
workspaceInstantiator = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IRelayCommand CloseCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == closeCommand)
|
||||
{
|
||||
Action closeAction = delegate ()
|
||||
{
|
||||
this.OnRequestClose();
|
||||
};
|
||||
closeCommand = new RelayCommand(delegate () { this.OnRequestClose(); });
|
||||
}
|
||||
return closeCommand;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get { return isClosed; }
|
||||
set
|
||||
{
|
||||
if (isClosed != value)
|
||||
{
|
||||
isClosed = value;
|
||||
base.OnPropertyChanged("IsClosed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanClose
|
||||
{
|
||||
get { return canClose; }
|
||||
set
|
||||
{
|
||||
if (canClose != value)
|
||||
{
|
||||
canClose = value;
|
||||
base.OnPropertyChanged("CanClose");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return title;
|
||||
}
|
||||
set
|
||||
{
|
||||
title = value;
|
||||
base.OnPropertyChanged("Title");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual String Header
|
||||
{
|
||||
get
|
||||
{
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRequestClose()
|
||||
{
|
||||
EventHandler handler = this.RequestClose;
|
||||
if (null != handler) handler(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
//using System.Windows.Data;
|
||||
//using System.Windows.Forms;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
//using MarketData;
|
||||
using System.Reflection;
|
||||
using MarketData;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public class WorkspaceViewModelPersistenceHolder
|
||||
{
|
||||
public WorkspaceViewModel Workspace { get; set; }
|
||||
public SaveParameters WorkspaceParameters { get; set; }
|
||||
}
|
||||
public class WorkspaceViewModelPersistenceCollection : List<WorkspaceViewModelPersistenceHolder>
|
||||
{
|
||||
}
|
||||
public class WorkspacePersistenceHelper
|
||||
{
|
||||
public static String PARAMS_FILE="saveparams.config";
|
||||
|
||||
private WorkspacePersistenceHelper()
|
||||
{
|
||||
}
|
||||
|
||||
public static void Save(String pathFileName, ObservableCollection<WorkspaceViewModel> workspaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
StreamWriter streamWriter = null;
|
||||
FileStream outStream = null;
|
||||
if (null == workspaces || 0 == workspaces.Count) return;
|
||||
// MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Writing {0} to {1}", pathFileName, Directory.GetCurrentDirectory()));
|
||||
if (!File.Exists(pathFileName)) streamWriter = File.CreateText(pathFileName);
|
||||
else
|
||||
{
|
||||
outStream = new FileStream(pathFileName, FileMode.Truncate);
|
||||
streamWriter = new StreamWriter(outStream);
|
||||
}
|
||||
foreach (WorkspaceViewModel workspaceViewModel in workspaces)
|
||||
{
|
||||
if (workspaceViewModel.CanPersist())
|
||||
{
|
||||
SaveParameters workspaceViewModelParams = workspaceViewModel.GetSaveParameters();
|
||||
if (null == workspaceViewModelParams) continue;
|
||||
streamWriter.WriteLine(workspaceViewModelParams.ToString());
|
||||
}
|
||||
}
|
||||
streamWriter.Flush();
|
||||
streamWriter.Close();
|
||||
streamWriter.Dispose();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Write(exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void Load(String pathFileName, ObservableCollection<WorkspaceViewModel> workspaces, InstantiateWorkspace workspaceInstantiator)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<int, SaveParameters> loadedItems = new Dictionary<int, SaveParameters>();
|
||||
WorkspaceViewModelPersistenceCollection workspaceViewModelPersistenceCollection = new WorkspaceViewModelPersistenceCollection();
|
||||
if (!File.Exists(PARAMS_FILE))
|
||||
{
|
||||
// MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Unable to load params file : {0}",PARAMS_FILE));
|
||||
return;
|
||||
}
|
||||
FileStream inStream = new FileStream(PARAMS_FILE, FileMode.Open);
|
||||
StreamReader streamReader = new StreamReader(inStream);
|
||||
String strLine = null;
|
||||
while (null != (strLine = streamReader.ReadLine()))
|
||||
{
|
||||
SaveParameters workspaceViewModelParams = null;
|
||||
try { workspaceViewModelParams = SaveParameters.Parse(strLine); }
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error parsing line from configuration file. The line was : {0}", strLine));
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("The exception was : {0}", exception.ToString()));
|
||||
continue;
|
||||
}
|
||||
if (loadedItems.ContainsKey(workspaceViewModelParams.Key))
|
||||
{
|
||||
// MDTrace.WriteLine(LogLevel.DEBUG,"Item already loaded, details follow...");
|
||||
foreach (KeyValuePair<String, String> kvp in workspaceViewModelParams)
|
||||
{
|
||||
// MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Key:{0} Value:{1}",kvp.Key,kvp.Value));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else loadedItems.Add(workspaceViewModelParams.Key, workspaceViewModelParams);
|
||||
var itemType = from KeyValuePair<String, String> item in workspaceViewModelParams where item.Key.Equals("Type") select item;
|
||||
KeyValuePair<String, String> selectedItem = itemType.FirstOrDefault();
|
||||
WorkspaceViewModel workspaceViewModel = null;
|
||||
Type[] booleanTypes = new Type[1];
|
||||
booleanTypes[0] = typeof(bool);
|
||||
ConstructorInfo constructorInfo = Type.GetType(selectedItem.Value).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, CallingConventions.HasThis, booleanTypes, null);
|
||||
if (null != constructorInfo) workspaceViewModel = (WorkspaceViewModel)Activator.CreateInstance(Type.GetType(selectedItem.Value), new object[] { true });
|
||||
else workspaceViewModel = (WorkspaceViewModel)Activator.CreateInstance(Type.GetType(selectedItem.Value));
|
||||
workspaceViewModel.WorkspaceInstantiator = workspaceInstantiator;
|
||||
WorkspaceViewModelPersistenceHolder workspaceViewModelPersistenceHolder = new WorkspaceViewModelPersistenceHolder();
|
||||
workspaceViewModelPersistenceHolder.Workspace = workspaceViewModel;
|
||||
workspaceViewModelPersistenceHolder.WorkspaceParameters = workspaceViewModelParams;
|
||||
workspaceViewModelPersistenceCollection.Add(workspaceViewModelPersistenceHolder);
|
||||
}
|
||||
streamReader.Close();
|
||||
for (int index = 0; index < workspaceViewModelPersistenceCollection.Count; index++)
|
||||
{
|
||||
WorkspaceViewModelPersistenceHolder workspaceViewModelPersistenceHolder = workspaceViewModelPersistenceCollection[index];
|
||||
workspaces.Add(workspaceViewModelPersistenceHolder.Workspace);
|
||||
workspaceViewModelPersistenceHolder.Workspace.SetSaveParameters(workspaceViewModelPersistenceHolder.WorkspaceParameters);
|
||||
if (index == workspaceViewModelPersistenceCollection.Count - 1)
|
||||
{
|
||||
// ICollectionView collectionView = CollectionViewSource.GetDefaultView(workspaces);
|
||||
// if (null != collectionView) collectionView.MoveCurrentTo(workspaceViewModelPersistenceHolder.Workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static WorkspaceViewModel Load(SaveParameters saveParams, ObservableCollection<WorkspaceViewModel> workspaces, InstantiateWorkspace workspaceInstantiator)
|
||||
{
|
||||
var itemType = from KeyValuePair<String, String> item in saveParams where item.Key.Equals("Type") select item;
|
||||
KeyValuePair<String, String> selectedItem = itemType.FirstOrDefault();
|
||||
WorkspaceViewModel workspaceViewModel = null;
|
||||
Type[] booleanTypes = new Type[1];
|
||||
booleanTypes[0] = typeof(bool);
|
||||
ConstructorInfo constructorInfo = Type.GetType(selectedItem.Value).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, CallingConventions.HasThis, booleanTypes, null);
|
||||
if (null != constructorInfo) workspaceViewModel = (WorkspaceViewModel)Activator.CreateInstance(Type.GetType(selectedItem.Value), new object[] { true });
|
||||
else workspaceViewModel = (WorkspaceViewModel)Activator.CreateInstance(Type.GetType(selectedItem.Value));
|
||||
workspaceViewModel.WorkspaceInstantiator = workspaceInstantiator;
|
||||
WorkspaceViewModelPersistenceHolder workspaceViewModelPersistenceHolder = new WorkspaceViewModelPersistenceHolder();
|
||||
workspaceViewModelPersistenceHolder.Workspace = workspaceViewModel;
|
||||
workspaceViewModelPersistenceHolder.WorkspaceParameters = saveParams;
|
||||
workspaces.Add(workspaceViewModelPersistenceHolder.Workspace);
|
||||
workspaceViewModelPersistenceHolder.Workspace.SetSaveParameters(workspaceViewModelPersistenceHolder.WorkspaceParameters);
|
||||
// ICollectionView collectionView = CollectionViewSource.GetDefaultView(workspaces);
|
||||
// if (null != collectionView) collectionView.MoveCurrentTo(workspaceViewModelPersistenceHolder.Workspace);
|
||||
return workspaceViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveParametersList : List<KeyValuePair<String, String>>
|
||||
{
|
||||
public int Key
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (KeyValuePair<String, String> kvp in this)
|
||||
{
|
||||
sb.Append(kvp.Key).Append(",").Append(kvp.Value);
|
||||
}
|
||||
return sb.ToString().GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveParameters : SaveParametersList
|
||||
{
|
||||
public WorkspaceViewModel Referer { get; set; }
|
||||
|
||||
public bool ContainsKey(String key)
|
||||
{
|
||||
foreach (KeyValuePair<String, String> item in this)
|
||||
{
|
||||
if (item.Key.Equals(key)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int index = 0; index < Count; index++)
|
||||
{
|
||||
KeyValuePair<String, String> item = this[index];
|
||||
sb.Append(item.Key).Append(",").Append(item.Value);
|
||||
if (index < Count - 1) sb.Append(",");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static SaveParameters Parse(String strLine)
|
||||
{
|
||||
if (null == strLine) return null;
|
||||
String[] strArray = strLine.Split(',');
|
||||
if (null == strArray || 0 == strArray.Length) return null;
|
||||
SaveParameters saveParameters = new SaveParameters();
|
||||
for (int index = 0; index < strArray.Length; index += 2)
|
||||
{
|
||||
KeyValuePair<String, String> savedParameter = new KeyValuePair<String, String>(strArray[index], strArray[index + 1]);
|
||||
saveParameters.Add(savedParameter);
|
||||
}
|
||||
return saveParameters;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user