diff --git a/PortfolioManager/Renderers/BollingerBandRenderer.cs b/PortfolioManager/Renderers/BollingerBandRenderer.cs index 4d0604a..5fcebc6 100644 --- a/PortfolioManager/Renderers/BollingerBandRenderer.cs +++ b/PortfolioManager/Renderers/BollingerBandRenderer.cs @@ -33,12 +33,15 @@ namespace PortfolioManager.Renderers public class BollingerBandRenderer : ModelBase { + private String selectedSymbol = default; + private int selectedDayCount = int.MinValue; private Price latestPrice = default; private Price zeroPrice = default; private bool showLabels = true; private bool showMarkers = true; private bool showLegend = false; private bool showTradeLabels = true; + private bool showInsiderTransactions = true; private bool syncTradeToBand = true; private StopLimit stopLimit = default; private StopLimits stopLimits = default; @@ -48,11 +51,14 @@ namespace PortfolioManager.Renderers private BollingerBands bollingerBands; private InsiderTransactionSummaries insiderTransactionSummaries = null; - double maxBollingerDate = 0.00; - double minBollingerDate = 0.00; - double spread = 0.00; - double percentShift3PC = 0.00; - double percentShift1PC = 0.00; + // double maxBollingerDate = 0.00; + // double minBollingerDate = 0.00; + //double spread = 0.00; + double percentShiftHorz3PC = 0.00; + double percentShiftHorz1PC = 0.00; + double percentShiftVert3PC = 0.00; + double percentShiftVert1PC = 0.00; + double percentShiftVert5PC = 0.00; public BollingerBandRenderer(AvaPlot plotter) { @@ -73,6 +79,15 @@ namespace PortfolioManager.Renderers if (!ShowLegend) Plotter.Plot.HideLegend(); else Plotter.Plot.ShowLegend(); } + else if (eventArgs.PropertyName.Equals("ShowLegend")) + { + if (!ShowLegend) Plotter.Plot.HideLegend(); + else Plotter.Plot.ShowLegend(); + } + else if (eventArgs.PropertyName.Equals("ShowInsiderTransactions")) + { + SetData(selectedSymbol, selectedDayCount); + } } public void Render() @@ -84,6 +99,8 @@ namespace PortfolioManager.Renderers public void SetData(String selectedSymbol, int selectedDayCount) { + this.selectedSymbol = selectedSymbol; + this.selectedDayCount = selectedDayCount; stopLimit = PortfolioDA.GetStopLimit(selectedSymbol); portfolioTrades = PortfolioDA.GetTradesSymbol(selectedSymbol); portfolioTradesLots = LotAggregator.CombineLots(portfolioTrades); @@ -129,11 +146,18 @@ namespace PortfolioManager.Renderers } bollingerBands = BollingerBandGenerator.GenerateBollingerBands(prices); - maxBollingerDate = bollingerBands.Max(x=>x.Date).ToOADate(); - minBollingerDate = bollingerBands.Min(x=>x.Date).ToOADate(); - spread = (maxBollingerDate - minBollingerDate); - percentShift3PC = spread * .03; - percentShift1PC = spread * .01; + double maxBollingerDate = bollingerBands.Max(x=>x.Date).ToOADate(); + double minBollingerDate = bollingerBands.Min(x=>x.Date).ToOADate(); + double maxBollingerValue = bollingerBands.Max(x=>x.K); + double minBollingerValue = bollingerBands.Min(x=>x.L); + + double spreadHorz = (maxBollingerDate - minBollingerDate); + double spreadVert = (maxBollingerValue - minBollingerValue); + percentShiftHorz3PC = spreadHorz * .03; + percentShiftHorz1PC = spreadHorz * .01; + percentShiftVert3PC = spreadVert * .03; + percentShiftVert1PC = spreadVert * .01; + percentShiftVert5PC = spreadVert * .05; GenerateBollingerBands(); GenerateZeroPoint(zeroPrice); @@ -233,7 +257,7 @@ namespace PortfolioManager.Renderers Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), 155, 24, FontFactor.FontSize); // Coordinates coordinates = new Coordinates(latestPrice.Date.ToOADate()/(1+onePercent), stopLimit.StopPrice - 5.00); - Coordinates coordinates = new Coordinates(latestPrice.Date.ToOADate()-percentShift3PC, stopLimit.StopPrice - 5.00); + Coordinates coordinates = new Coordinates(latestPrice.Date.ToOADate()-percentShiftHorz3PC, stopLimit.StopPrice - 5.00); ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image); } } @@ -245,7 +269,6 @@ namespace PortfolioManager.Renderers { if (null == portfolioTradesLots || 0 == portfolioTradesLots.Count || !showTradeLabels) return; // Here we add the image markers - Image tradePointMarker = TextMarkerImageGenerator.ToSPImage(ImageCache.GetInstance().GetImage(ImageCache.ImageType.YellowTriangleUp)); for (int index = 0; index < portfolioTradesLots.Count; index++) { @@ -265,7 +288,7 @@ namespace PortfolioManager.Renderers sb.Append(Utility.FormatCurrency(portfolioTrade.Price)); Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), 150, 24, FontFactor.FontSize); - Coordinates coordinates = new Coordinates(portfolioTrade.TradeDate.ToOADate(), portfolioTrade.Price - 5.00); + Coordinates coordinates = new Coordinates(portfolioTrade.TradeDate.ToOADate(), portfolioTrade.Price - percentShiftVert5PC); ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image); } } @@ -275,7 +298,7 @@ namespace PortfolioManager.Renderers /// private void GenerateInsiderTransactions() { - if (null == prices || 0 == prices.Count || !ShowMarkers) return; + if (null == prices || 0 == prices.Count || !ShowInsiderTransactions) return; ImageMarker imageMarker = default; Coordinates coordinates = default; @@ -512,6 +535,19 @@ namespace PortfolioManager.Renderers } } + public bool ShowInsiderTransactions + { + get + { + return showInsiderTransactions; + } + set + { + showInsiderTransactions = value; + base.OnPropertyChanged("ShowInsiderTransactions"); + } + } + public AvaPlot Plotter { get; private set; } private CompositeDataSource InsiderTransactionPointDisposedSmall { get; set; } = Empty(); diff --git a/PortfolioManager/ViewModels/PlotterWorkspaceViewModel.cs b/PortfolioManager/ViewModels/PlotterWorkspaceViewModel.cs index 0c677b8..d85bb92 100644 --- a/PortfolioManager/ViewModels/PlotterWorkspaceViewModel.cs +++ b/PortfolioManager/ViewModels/PlotterWorkspaceViewModel.cs @@ -10,6 +10,8 @@ namespace PortfolioManager.ViewModels public abstract class PlotterWorkspaceViewModel : WorkspaceViewModel { + public AvaPlot Plotter { get; set; } + public EventHandler OnPlotterLoadedEventHandler; public void OnPlotterLoaded(AvaPlot avaPlot) diff --git a/PortfolioManager/ViewModels/ScottPlotViewModel.cs b/PortfolioManager/ViewModels/ScottPlotViewModel.cs index e2127aa..5cf8643 100644 --- a/PortfolioManager/ViewModels/ScottPlotViewModel.cs +++ b/PortfolioManager/ViewModels/ScottPlotViewModel.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Input; using DynamicData; using MarketData; using MarketData.Cache; @@ -17,245 +18,279 @@ using ScottPlot.Avalonia; namespace PortfolioManager.ViewModels { - public partial class ScottPlotViewModel : PlotterWorkspaceViewModel - { -// private AvaPlot plotter = default; - private List watchListNames; - private String selectedWatchList; - private ObservableCollection symbols = new ObservableCollection(); - private List dayCounts = new int[] { 60, 90, 180, 360, 720, 1440, 3600 }.ToList(); - private int selectedDayCount = 90; - private bool isBusy = false; - private String selectedSymbol = default; - private String companyName = default; - BollingerBandRenderer bollingerBandRenderer = default; - - public ScottPlotViewModel() + public partial class ScottPlotViewModel : PlotterWorkspaceViewModel { - DisplayName = "Bollinger"; - Plotter = new AvaPlot(); - base.OnPropertyChanged("Plotter"); - OnPlotterLoadedEventHandler += PlotterLoadedEvent; - PropertyChanged += OnViewModelPropertyChanged; - Initialize(); - } + // private AvaPlot plotter = default; + private List watchListNames; + private String selectedWatchList; + private ObservableCollection symbols = new ObservableCollection(); + private List dayCounts = new int[] { 60, 90, 180, 360, 720, 1440, 3600 }.ToList(); + private int selectedDayCount = 90; + private bool isBusy = false; + private String selectedSymbol = default; + private String companyName = default; + BollingerBandRenderer bollingerBandRenderer = default; - protected override void OnDispose() - { - MDTrace.WriteLine(LogLevel.DEBUG, $"Dispose BollingerBandViewModel"); - base.OnDispose(); - } - - private void Initialize(bool executePropertyChanged = true) - { - Task workerTask = Task.Factory.StartNew(() => - { - MDTrace.WriteLine(LogLevel.DEBUG, $"BollingerBandViewModel::Initialize()"); - watchListNames = WatchListDA.GetWatchLists(); - watchListNames.Insert(0, UIConstants.CONST_ALL); - selectedWatchList = watchListNames.Find(x => x.Equals("Valuations")); - symbols.AddRange(WatchListDA.GetWatchList(selectedWatchList)); - }); - workerTask.ContinueWith((continuation) => - { - if (executePropertyChanged) + public ScottPlotViewModel() { - base.OnPropertyChanged("Symbols"); - base.OnPropertyChanged("WatchListNames"); - base.OnPropertyChanged("SelectedWatchList"); + DisplayName = "Bollinger"; +// Plotter = new AvaPlot(); +// base.OnPropertyChanged("Plotter"); + OnPlotterLoadedEventHandler += PlotterLoadedEvent; + PropertyChanged += OnViewModelPropertyChanged; + Initialize(); } - }); - } - - // ******************************************************************************************************************************************* - /// - /// This event will be called the first time the view is rendered and each time the view comes back into focus (i.e.) Tab activated - /// - /// - /// - public void PlotterLoadedEvent(object sender, PlotterLoadedEventArgs e) - { - // Plotter = e.AvaPlot; - // Plotter.Plot.Clear(); - // Plotter.Refresh(); - // base.OnPropertyChanged("SelectedSymbol"); - // MDTrace.WriteLine(LogLevel.DEBUG,$"SelectedSymbol:{selectedSymbol}"); - // if (default == Plotter) - // { - // Plotter = e.AvaPlot; - // Plotter.Plot.Clear(); - // } - // else - // { - // Plotter = e.AvaPlot; - // Plotter.Plot.Clear(); - // base.OnPropertyChanged("SelectedSymbol"); - // } - } - - private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs) - { - - if ((eventArgs.PropertyName.Equals("SyncTradeToBand") || - eventArgs.PropertyName.Equals("ShowTradeLabels") || - eventArgs.PropertyName.Equals("SelectedSymbol") || - eventArgs.PropertyName.Equals("ShowRiskFree") || - eventArgs.PropertyName.Equals("LeastSquaresFit") || - eventArgs.PropertyName.Equals("SelectedDayCount")) - && !String.IsNullOrEmpty(selectedSymbol) - && default!=Plotter) - { - IsBusy = true; - Task workerTask = Task.Factory.StartNew(() => + protected override void OnDispose() { - companyName = PricingDA.GetNameForSymbol(selectedSymbol); - bollingerBandRenderer = new BollingerBandRenderer(Plotter); - bollingerBandRenderer.SetData(selectedSymbol, selectedDayCount); - bollingerBandRenderer.Render(); - }); - workerTask.ContinueWith((continuation) => - { - base.OnPropertyChanged("GraphTitle"); - IsBusy = false; - }); - } - } - - // ********************************************** 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) - { - } - - // ****************************************************** P R O P E R T I E S ************************************************ - - public AvaPlot Plotter { get; set; } = default; - - public String GraphTitle - { - get - { - if (null == companyName || null == bollingerBandRenderer || null == bollingerBandRenderer.Prices) return ""; - String displayCompanyName = companyName; - if (displayCompanyName.Length > 40) displayCompanyName = displayCompanyName.Substring(0, 40) + "..."; - StringBuilder sb = new StringBuilder(); - float change = float.NaN; - Prices prices2day = new Prices(bollingerBandRenderer.Prices.Take(2).ToList()); - if (2 == prices2day.Count) change = prices2day.GetReturns()[0]; - sb.Append(displayCompanyName); - sb.Append(" (").Append(selectedSymbol).Append(") "); - sb.Append(Utility.DateTimeToStringMMHDDHYYYY(bollingerBandRenderer.Prices[bollingerBandRenderer.Prices.Count - 1].Date)); - sb.Append(" Thru "); - sb.Append(Utility.DateTimeToStringMMHDDHYYYY(bollingerBandRenderer.Prices[0].Date)); - sb.Append(" (").Append(Utility.FormatCurrency(bollingerBandRenderer.Prices[0].Close)); - sb.Append("/").Append(Utility.FormatCurrency(bollingerBandRenderer.Prices[0].Low)); - if (!float.IsNaN(change)) - { - sb.Append(","); - sb.Append(change >= 0.00 ? "+" : "").Append(Utility.FormatPercent((double)change)); + MDTrace.WriteLine(LogLevel.DEBUG, $"Dispose BollingerBandViewModel"); + base.OnDispose(); } - sb.Append(")"); - return sb.ToString(); - } - } - public List WatchListNames - { - get - { - return watchListNames; - } - } - - public String SelectedWatchList - { - get - { - return selectedWatchList; - } - set - { - selectedWatchList = value; - base.OnPropertyChanged("SelectedWatchList"); - } - } - - public ObservableCollection Symbols - { - get - { - return symbols; - } - } - - public int SelectedDayCount - { - get - { - return selectedDayCount; - } - set - { - selectedDayCount = value; - base.OnPropertyChanged("SelectedDayCount"); - } - } - - public List DayCounts - { - get - { - return dayCounts; - } - } - - public String SelectedSymbol - { - get - { - return selectedSymbol; - } - set - { - if (String.IsNullOrEmpty(value)) + private void Initialize(bool executePropertyChanged = true) { - return; + Task workerTask = Task.Factory.StartNew(() => + { + MDTrace.WriteLine(LogLevel.DEBUG, $"BollingerBandViewModel::Initialize()"); + watchListNames = WatchListDA.GetWatchLists(); + watchListNames.Insert(0, UIConstants.CONST_ALL); + selectedWatchList = watchListNames.Find(x => x.Equals("Valuations")); + symbols.AddRange(WatchListDA.GetWatchList(selectedWatchList)); + }); + workerTask.ContinueWith((continuation) => + { + if (executePropertyChanged) + { + base.OnPropertyChanged("Symbols"); + base.OnPropertyChanged("WatchListNames"); + base.OnPropertyChanged("SelectedWatchList"); + } + }); } - selectedSymbol = value; - base.OnPropertyChanged("SelectedSymbol"); - } - } - public bool IsBusy - { - get - { - return isBusy; - } - set - { - isBusy = value; - base.OnPropertyChanged("IsBusy"); - } - } - public override String Title - { - get - { - if (null == selectedSymbol) return DisplayName; - return "Bollinger " + "(" + selectedSymbol + ")"; + // ******************************************************************************************************************************************* + /// + /// This event will be called the first time the view is rendered and each time the view comes back into focus (i.e.) Tab activated + /// + /// + /// + public void PlotterLoadedEvent(object sender, PlotterLoadedEventArgs e) + { + base.OnPropertyChanged("Plotter"); } - } + + private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs) + { + + if ((eventArgs.PropertyName.Equals("SyncTradeToBand") || + eventArgs.PropertyName.Equals("ShowTradeLabels") || + eventArgs.PropertyName.Equals("SelectedSymbol") || + eventArgs.PropertyName.Equals("ShowRiskFree") || + eventArgs.PropertyName.Equals("LeastSquaresFit") || + eventArgs.PropertyName.Equals("SelectedDayCount")) + && !String.IsNullOrEmpty(selectedSymbol) + && default != Plotter) + { + IsBusy = true; + Task workerTask = Task.Factory.StartNew(() => + { + companyName = PricingDA.GetNameForSymbol(selectedSymbol); + bollingerBandRenderer = new BollingerBandRenderer(Plotter); + bollingerBandRenderer.SetData(selectedSymbol, selectedDayCount); + bollingerBandRenderer.Render(); + }); + workerTask.ContinueWith((continuation) => + { + base.OnPropertyChanged("GraphTitle"); + IsBusy = false; + }); + } + } + + // ********************************************** 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) + { + } + + // ****************************************************** P R O P E R T I E S ************************************************ + + // public AvaPlot Plotter { get; set; } = default; + + public String GraphTitle + { + get + { + if (null == companyName || null == bollingerBandRenderer || null == bollingerBandRenderer.Prices) return ""; + String displayCompanyName = companyName; + if (displayCompanyName.Length > 40) displayCompanyName = displayCompanyName.Substring(0, 40) + "..."; + StringBuilder sb = new StringBuilder(); + float change = float.NaN; + Prices prices2day = new Prices(bollingerBandRenderer.Prices.Take(2).ToList()); + if (2 == prices2day.Count) change = prices2day.GetReturns()[0]; + sb.Append(displayCompanyName); + sb.Append(" (").Append(selectedSymbol).Append(") "); + sb.Append(Utility.DateTimeToStringMMHDDHYYYY(bollingerBandRenderer.Prices[bollingerBandRenderer.Prices.Count - 1].Date)); + sb.Append(" Thru "); + sb.Append(Utility.DateTimeToStringMMHDDHYYYY(bollingerBandRenderer.Prices[0].Date)); + sb.Append(" (").Append(Utility.FormatCurrency(bollingerBandRenderer.Prices[0].Close)); + sb.Append("/").Append(Utility.FormatCurrency(bollingerBandRenderer.Prices[0].Low)); + if (!float.IsNaN(change)) + { + sb.Append(","); + sb.Append(change >= 0.00 ? "+" : "").Append(Utility.FormatPercent((double)change)); + } + sb.Append(")"); + return sb.ToString(); + } + } + + public List WatchListNames + { + get + { + return watchListNames; + } + } + + public String SelectedWatchList + { + get + { + return selectedWatchList; + } + set + { + selectedWatchList = value; + base.OnPropertyChanged("SelectedWatchList"); + } + } + + public ObservableCollection Symbols + { + get + { + return symbols; + } + } + + public int SelectedDayCount + { + get + { + return selectedDayCount; + } + set + { + selectedDayCount = value; + base.OnPropertyChanged("SelectedDayCount"); + } + } + + public List DayCounts + { + get + { + return dayCounts; + } + } + + public String SelectedSymbol + { + get + { + return selectedSymbol; + } + set + { + if (String.IsNullOrEmpty(value)) + { + return; + } + selectedSymbol = value; + base.OnPropertyChanged("SelectedSymbol"); + } + } + + public bool IsBusy + { + get + { + return isBusy; + } + set + { + isBusy = value; + base.OnPropertyChanged("IsBusy"); + } + } + + public override String Title + { + get + { + if (null == selectedSymbol) return DisplayName; + return "Bollinger " + "(" + selectedSymbol + ")"; + } + } + + public bool SyncTradeToBand + { + get + { + if (null == bollingerBandRenderer) return false; + return bollingerBandRenderer.SyncTradeToBand; + } + set + { + bollingerBandRenderer.SyncTradeToBand = value; + + } + } + + public bool ShowTradeLabels + { + get + { + if (null == bollingerBandRenderer) return false; + return bollingerBandRenderer.ShowLabels; + } + set + { + bollingerBandRenderer.ShowLabels = value; + + } + } + + public Boolean CheckBoxShowInsiderTransactions + { + get + { + if (null == bollingerBandRenderer) return false; + return bollingerBandRenderer.ShowInsiderTransactions; + } + set + { + bollingerBandRenderer.ShowInsiderTransactions = value; + } + } + + + [RelayCommand] + public async Task Refresh() + { + base.OnPropertyChanged("SelectedSymbol"); + await Task.FromResult(true); + } } } \ No newline at end of file diff --git a/PortfolioManager/Views/ScottPlotView.axaml b/PortfolioManager/Views/ScottPlotView.axaml index 3d0d065..1c66d3d 100644 --- a/PortfolioManager/Views/ScottPlotView.axaml +++ b/PortfolioManager/Views/ScottPlotView.axaml @@ -61,10 +61,10 @@ - + diff --git a/PortfolioManager/Views/ScottPlotView.axaml.cs b/PortfolioManager/Views/ScottPlotView.axaml.cs index 8381fb4..56bd04d 100644 --- a/PortfolioManager/Views/ScottPlotView.axaml.cs +++ b/PortfolioManager/Views/ScottPlotView.axaml.cs @@ -10,23 +10,23 @@ namespace PortfolioManager.Views; public partial class ScottPlotView : UserControl { - // private AvaPlot avaPlot = default; - public ScottPlotView() - { - InitializeComponent(); - } + public ScottPlotView() + { + this.DataContextChanged += OnDataContextChanged; + InitializeComponent(); + } - private void AvaPlot_Loaded(object sender, RoutedEventArgs e) + private void OnDataContextChanged(object sender, EventArgs e) + { + if (default != (sender as ScottPlotView)) { - // This code will execute when the AvaPlot is loaded - // if (sender is ScottPlot.Avalonia.AvaPlot) - // { - // if (default == avaPlot) - // { - // PlotterWorkspaceViewModel viewModel = (DataContext as PlotterWorkspaceViewModel); - // avaPlot = this.Find("AvaPlot"); - // viewModel.OnPlotterLoaded(avaPlot); - // } - // } + ScottPlotView view = (sender as ScottPlotView); + PlotterWorkspaceViewModel viewModel = (DataContext as PlotterWorkspaceViewModel); + if (default == viewModel.Plotter) + { + viewModel.Plotter = new AvaPlot(); + viewModel.OnPlotterLoaded(viewModel.Plotter); + } } + } } \ No newline at end of file