Initial Commit

This commit is contained in:
2025-06-10 19:03:43 -04:00
commit 93ab70180a
62 changed files with 49312 additions and 0 deletions

View 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();
}
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}

View 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; }
}
}

View 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)");
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace PortfolioManager.ViewModels
{
public interface IPersistentViewModel
{
SaveParameters GetSaveParameters();
void SetSaveParameters(SaveParameters saveParameters);
bool CanPersist();
}
}

View 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();
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}
}

View 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();
}
}
}
}

View 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
}
}

View 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);
}
}
}

View File

@@ -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;
}
}
}