936 lines
37 KiB
C#
936 lines
37 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reactive.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.MGSHMomentum;
|
|
using MarketData.Generator.Model;
|
|
using MarketData.MarketDataModel;
|
|
using MarketData.Utils;
|
|
using PortfolioManager.DataSeriesViewModels;
|
|
using PortfolioManager.Dialogs;
|
|
using PortfolioManager.Extensions;
|
|
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 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()
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,$"Dispose MGSHMomentumViewModel");
|
|
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 = "Bollinger Bands...", Command = BollingerBandsCommand, StaysOpenOnClick = false });
|
|
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(CanExecuteBollingerBands))]
|
|
public async Task BollingerBands()
|
|
{
|
|
await ExecuteBollingerBands();
|
|
}
|
|
|
|
[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 CanExecuteBollingerBands()
|
|
{
|
|
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;
|
|
}
|
|
|
|
[RelayCommand]
|
|
public void ToggleReturnOrPercent()
|
|
{
|
|
HandleToggleReturnOrPercent();
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task LoadFile()
|
|
{
|
|
await LoadTradeFile();
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task Reload()
|
|
{
|
|
await ReloadTradeFile();
|
|
}
|
|
|
|
public async Task ExecuteBollingerBands()
|
|
{
|
|
MarketData.MarketDataModel.StopLimits stopLimits = GetHistoricalStopLimitsMarketDataModel();
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
SaveParameters saveParams = null;
|
|
sb = new StringBuilder();
|
|
sb.Append("Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,");
|
|
sb.Append(selectedPosition.Symbol).Append(",");
|
|
sb.Append("SelectedWatchList,{All},SelectedDayCount,");
|
|
sb.Append(GetDayCountSelectionForBollingerBands(selectedPosition, true));
|
|
saveParams = SaveParameters.Parse(sb.ToString());
|
|
SaveParameters stopLimitParams = StopLimitsExtensions.FromStopLimits(stopLimits);
|
|
saveParams.AddRange(stopLimitParams);
|
|
saveParams.Referer=this;
|
|
WorkspaceInstantiator.Invoke(saveParams);
|
|
await Task.FromResult(true);
|
|
}
|
|
|
|
// This getter returns non-model (MarketData.MarketDataModel) specific stop limits to pass along to the bollinger bands
|
|
private MarketData.MarketDataModel.StopLimits GetHistoricalStopLimitsMarketDataModel()
|
|
{
|
|
if (null == sessionParams || null == selectedPosition) return null;
|
|
DateGenerator dateGenerator = new DateGenerator();
|
|
|
|
MarketData.MarketDataModel.StopLimits marketDataModelStopLimits = new MarketData.MarketDataModel.StopLimits();
|
|
|
|
MarketData.Generator.Model.StopLimits stopLimits =
|
|
new MarketData.Generator.Model.StopLimits((from MarketData.Generator.Model.StopLimit stopLimit in sessionParams.StopLimits
|
|
where stopLimit.StopLimitId.Equals(selectedPosition.Symbol + Utility.DateTimeToStringYYYYMMDDMMSSTT(selectedPosition.PurchaseDate)) select stopLimit).
|
|
OrderByDescending(x => x.AnalysisDate).ToList());
|
|
|
|
MarketData.MarketDataModel.StopLimit initialStopLimit = new MarketData.MarketDataModel.StopLimit();
|
|
initialStopLimit.Symbol = selectedPosition.Symbol;
|
|
initialStopLimit.Shares = 0;
|
|
initialStopLimit.StopPrice = selectedPosition.InitialStopLimit;
|
|
initialStopLimit.StopType = StopLimitConstants.STOP_QUOTE;
|
|
initialStopLimit.EffectiveDate = selectedPosition.PurchaseDate;
|
|
initialStopLimit.Active = 1;
|
|
marketDataModelStopLimits.Add(initialStopLimit);
|
|
|
|
foreach (MarketData.Generator.Model.StopLimit stopLimit in stopLimits)
|
|
{
|
|
MarketData.MarketDataModel.StopLimit marketDataModelStopLimit = new MarketData.MarketDataModel.StopLimit();
|
|
marketDataModelStopLimit.Symbol = stopLimit.Symbol;
|
|
marketDataModelStopLimit.Shares = 0;
|
|
marketDataModelStopLimit.StopPrice = stopLimit.NewStop;
|
|
marketDataModelStopLimit.StopType = StopLimitConstants.STOP_QUOTE;
|
|
marketDataModelStopLimit.EffectiveDate = stopLimit.AnalysisDate;
|
|
marketDataModelStopLimit.Active = 1;
|
|
marketDataModelStopLimits.Add(marketDataModelStopLimit);
|
|
}
|
|
|
|
return marketDataModelStopLimits;
|
|
}
|
|
|
|
private int GetDayCountSelectionForBollingerBands(MGSHPositionModel selectedPosition,bool ignoreActivePosition=false)
|
|
{
|
|
DateGenerator dateGenerator=new DateGenerator();
|
|
DateTime maxDate = DateTime.Today;
|
|
|
|
if(!ignoreActivePosition)
|
|
{
|
|
maxDate = PricingDA.GetLatestDate(selectedPosition.Symbol);
|
|
if(!selectedPosition.IsActivePosition)
|
|
{
|
|
maxDate = selectedPosition.SellDate;
|
|
}
|
|
}
|
|
int daysBetween=dateGenerator.DaysBetween(selectedPosition.PurchaseDate, maxDate);
|
|
if(daysBetween<90)return 90;
|
|
if(daysBetween<180)return 180;
|
|
if(daysBetween<360)return 360;
|
|
if(daysBetween<720)return 720;
|
|
if(daysBetween<1440)return 1440;
|
|
return 3600;
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
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;
|
|
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("ReloadEnabled");
|
|
base.OnPropertyChanged("LastTradeDate");
|
|
base.OnPropertyChanged("NextTradeDate");
|
|
UpdateTooltipProperties();
|
|
});
|
|
return true;
|
|
}
|
|
|
|
public void UpdateTooltipProperties()
|
|
{
|
|
base.OnPropertyChanged("ExpectationDescription");
|
|
base.OnPropertyChanged("CompanyDescriptionSelectedPosition");
|
|
base.OnPropertyChanged("ToolTipSold");
|
|
base.OnPropertyChanged("ToolTipExposure");
|
|
base.OnPropertyChanged("ToolTipInitialStop");
|
|
base.OnPropertyChanged("ToolTipTrailingStop");
|
|
base.OnPropertyChanged("ToolTipR");
|
|
base.OnPropertyChanged("ToolTipTotalRiskExposure");
|
|
base.OnPropertyChanged("ToolTipRMultiple");
|
|
}
|
|
|
|
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 ToolTipExposure
|
|
{
|
|
get
|
|
{
|
|
if (null == selectedPosition) return "No row selected.";
|
|
StringBuilder sb = new StringBuilder();
|
|
if (selectedPosition.IsActivePosition)
|
|
{
|
|
sb.Append("Exposure = PurchasePrice * Shares\n");
|
|
sb.Append($"{Utility.FormatCurrency(selectedPosition.Exposure)} = {Utility.FormatCurrency(selectedPosition.PurchasePrice)} * {Utility.FormatNumber(selectedPosition.Shares, 3)}");
|
|
}
|
|
else
|
|
{
|
|
sb.Append("Original Exposure = PurchasePrice * Shares\n");
|
|
sb.Append($"{Utility.FormatCurrency(selectedPosition.PurchasePrice * selectedPosition.Shares)} = {Utility.FormatCurrency(selectedPosition.PurchasePrice)} * {Utility.FormatNumber(selectedPosition.Shares, 3)}");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
public String ToolTipSold
|
|
{
|
|
get
|
|
{
|
|
if (null == selectedPosition) return "No row selected.";
|
|
StringBuilder sb = new StringBuilder();
|
|
if (selectedPosition.IsActivePosition)
|
|
{
|
|
sb.Append($"{selectedPosition.Symbol} is currently active.");
|
|
}
|
|
else
|
|
{
|
|
sb.Append($"{selectedPosition.Symbol} {selectedPosition.Comment}.");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
public String CompanyDescriptionSelectedPosition
|
|
{
|
|
get
|
|
{
|
|
if (null == selectedPosition || null == selectedPosition.Symbol) return "No row selected.";
|
|
CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(selectedPosition.Symbol);
|
|
if (null == companyProfile || null == companyProfile.Description || "".Equals(companyProfile.Description)) return "No description found.";
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.Append(companyProfile.Symbol).Append(" - ").Append(companyProfile.CompanyName).Append("\n");
|
|
sb.Append(companyProfile.Sector).Append("/").Append(companyProfile.Industry).Append("\n").Append(companyProfile.Description);
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
public String ToolTipInitialStop
|
|
{
|
|
get
|
|
{
|
|
if (null == selectedPosition) return "No row selected.";
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.Append("[InitialStop]=([InitialStopLimit])").Append("\n");
|
|
sb.Append(Utility.FormatCurrency(selectedPosition.InitialStopLimit)).Append("=").Append(Utility.FormatCurrency(selectedPosition.InitialStopLimit));
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
public String ToolTipTrailingStop
|
|
{
|
|
get
|
|
{
|
|
if (null == selectedPosition) return "No row selected.";
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.Append("[TrailingStop]=([TrailingStopLimit])").Append("\n");
|
|
sb.Append(Utility.FormatCurrency(selectedPosition.TrailingStopLimit)).Append("=").Append(Utility.FormatCurrency(selectedPosition.TrailingStopLimit)).Append("\n");
|
|
List<MarketData.Generator.Model.StopLimit> stopLimits = GetHistoricalStopLimits();
|
|
if (selectedPosition.TrailingStopLimit > selectedPosition.PurchasePrice)
|
|
{
|
|
sb.Append("Riskless Gain").Append(" = ");
|
|
sb.Append(String.Format("(Trailing Stop - Purchase Price) * Shares"));
|
|
sb.Append("\n");
|
|
sb.Append(String.Format("{0}", Utility.FormatCurrency((selectedPosition.TrailingStopLimit - selectedPosition.PurchasePrice) * selectedPosition.Shares)));
|
|
sb.Append(String.Format("=({0}-{1})*{2}", Utility.FormatCurrency(selectedPosition.TrailingStopLimit), Utility.FormatCurrency(selectedPosition.PurchasePrice), Utility.FormatNumber(selectedPosition.Shares, 3)));
|
|
sb.Append("\n");
|
|
}
|
|
if (null != stopLimits && 0 != stopLimits.Count)
|
|
{
|
|
sb.Append("Stop History").Append("\n");
|
|
foreach (MarketData.Generator.Model.StopLimit stopLimit in stopLimits)
|
|
{
|
|
sb.Append(String.Format("Analysis Date:{0} New Stop:{1} Previous Stop:{2}", Utility.DateTimeToStringMMSDDSYYYY(stopLimit.AnalysisDate), Utility.FormatCurrency(stopLimit.NewStop), Utility.FormatCurrency(stopLimit.PreviousStop)));
|
|
sb.Append("\n");
|
|
}
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
public String ToolTipR
|
|
{
|
|
get
|
|
{
|
|
if(null==selectedPosition)return "No row selected.";
|
|
StringBuilder sb=new StringBuilder();
|
|
sb.Append("Current R/Share. See RMultiple for initial risk assumptions.").Append("\n");
|
|
sb.Append("R/Share=TrailingStop>PurchasePrice?0.00:PurchasePrice-TrailingStop").Append("\n");
|
|
if(selectedPosition.TrailingStopLimit>selectedPosition.PurchasePrice)
|
|
{
|
|
sb.Append(Utility.FormatCurrency(0)).Append("=").Append(Utility.FormatCurrency(0));
|
|
}
|
|
else
|
|
{
|
|
sb.Append(Utility.FormatCurrency(selectedPosition.TrailingStopLimit>selectedPosition.PurchasePrice?0.00:selectedPosition.PurchasePrice-selectedPosition.TrailingStopLimit));
|
|
sb.Append("=").Append(Utility.FormatCurrency(selectedPosition.PurchasePrice)).Append("-").Append(Utility.FormatCurrency(selectedPosition.TrailingStopLimit));
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
public String ToolTipTotalRiskExposure
|
|
{
|
|
get
|
|
{
|
|
if(null==selectedPosition)return "No row selected.";
|
|
StringBuilder sb=new StringBuilder();
|
|
sb.Append("Current Risk. See RMultiple for initial risk assumptions.").Append("\n");
|
|
sb.Append("[TotalRiskExposure]=([R/Share]*[Shares])").Append("\n");
|
|
if(null==selectedPosition) return sb.ToString();
|
|
sb.Append(Utility.FormatCurrency(selectedPosition.TotalRiskExposure)).Append("=(").Append(Utility.FormatCurrency(selectedPosition.R)).Append("*").Append(Utility.FormatNumber(selectedPosition.Shares,0)).Append(")");
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
public String ToolTipRMultiple
|
|
{
|
|
get
|
|
{
|
|
if(null==selectedPosition)return "Please select a position by clicking on a row.";
|
|
StringBuilder sb=new StringBuilder();
|
|
sb.Append("RMultiple is based on original position setup.").Append("\n");
|
|
sb.Append("Original Exposure=").Append(Utility.FormatCurrency(selectedPosition.PurchasePrice*selectedPosition.Shares)).Append("\n");
|
|
sb.Append("Original R/Share=PurchasePrice-InitialStop").Append("\n");
|
|
sb.Append(Utility.FormatCurrency(selectedPosition.PurchasePrice-selectedPosition.InitialStopLimit)).Append("=").Append(Utility.FormatCurrency(selectedPosition.PurchasePrice)).Append("-").Append(Utility.FormatCurrency(selectedPosition.InitialStopLimit)).Append("\n");
|
|
sb.Append("Original Risk=Original R/Share*Shares").Append("\n");
|
|
sb.Append(Utility.FormatCurrency((selectedPosition.PurchasePrice-selectedPosition.InitialStopLimit)*selectedPosition.Shares)).Append("=").Append(Utility.FormatCurrency(selectedPosition.PurchasePrice-selectedPosition.InitialStopLimit)).Append("*").Append(selectedPosition.Shares).Append("\n");
|
|
sb.Append("RMultiple=(CurrentPrice-PurchasePrice)/(PurchasePrice-InitialStop)").Append("\n");
|
|
sb.Append(selectedPosition.RMultipleAsString).Append("=").Append("(").Append(Utility.FormatCurrency(selectedPosition.CurrentPrice)).Append("-").Append(Utility.FormatCurrency(selectedPosition.PurchasePrice)).Append(")");
|
|
sb.Append("/").Append("(").Append(Utility.FormatCurrency(selectedPosition.PurchasePrice)).Append("-").Append(Utility.FormatCurrency(selectedPosition.InitialStopLimit)).Append(")");
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
// ************************************************************** T O O L T I P H E L P E R S **************************************************************
|
|
public MarketData.Generator.Model.StopLimits GetHistoricalStopLimits()
|
|
{
|
|
if (null == sessionParams || null == selectedPosition) return null;
|
|
MarketData.Generator.Model.StopLimits stopLimits = new MarketData.Generator.Model.StopLimits(
|
|
(from MarketData.Generator.Model.StopLimit stopLimit in sessionParams.StopLimits where stopLimit.StopLimitId.Equals(selectedPosition.Symbol + Utility.DateTimeToStringYYYYMMDDMMSSTT(selectedPosition.PurchaseDate)) select stopLimit)
|
|
.OrderByDescending(x => x.AnalysisDate).ToList());
|
|
return stopLimits;
|
|
}
|
|
}
|
|
}
|