Commit Latest

This commit is contained in:
2025-06-24 19:34:26 -04:00
parent d1a8d73ead
commit 5c5ed13061
5 changed files with 345 additions and 43 deletions

View File

@@ -88,6 +88,7 @@ namespace PortfolioManager.Renderers
portfolioTrades = PortfolioDA.GetTradesSymbol(selectedSymbol); portfolioTrades = PortfolioDA.GetTradesSymbol(selectedSymbol);
portfolioTradesLots = LotAggregator.CombineLots(portfolioTrades); portfolioTradesLots = LotAggregator.CombineLots(portfolioTrades);
Plotter.Plot.Clear();
if (null != portfolioTrades && 0 != portfolioTrades.Count) if (null != portfolioTrades && 0 != portfolioTrades.Count)
{ {
DateGenerator dateGenerator = new DateGenerator(); DateGenerator dateGenerator = new DateGenerator();
@@ -451,6 +452,14 @@ namespace PortfolioManager.Renderers
// *********************************************************** P R O P E R T I E S ***************************************************** // *********************************************************** P R O P E R T I E S *****************************************************
public Prices Prices
{
get
{
return prices;
}
}
public bool SyncTradeToBand public bool SyncTradeToBand
{ {
get get

View File

@@ -1,42 +1,261 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DynamicData;
using MarketData;
using MarketData.Cache;
using MarketData.DataAccess;
using MarketData.MarketDataModel;
using MarketData.Utils;
using PortfolioManager.Renderers; using PortfolioManager.Renderers;
using PortfolioManager.UIUtils;
using ScottPlot.Avalonia; using ScottPlot.Avalonia;
namespace PortfolioManager.ViewModels namespace PortfolioManager.ViewModels
{ {
public partial class ScottPlotViewModel : PlotterWorkspaceViewModel
public partial class ScottPlotViewModel : PlotterWorkspaceViewModel {
// private AvaPlot plotter = default;
private List<String> watchListNames;
private String selectedWatchList;
private ObservableCollection<String> symbols = new ObservableCollection<String>();
private List<Int32> dayCounts = new int[] { 60, 90, 180, 360, 720, 1440, 3600 }.ToList<int>();
private int selectedDayCount = 90;
private bool isBusy = false;
private String selectedSymbol = default;
private String companyName = default;
BollingerBandRenderer bollingerBandRenderer = default;
public ScottPlotViewModel()
{ {
private AvaPlot plotter = default; DisplayName = "Bollinger";
public ScottPlotViewModel() Plotter = new AvaPlot();
{ base.OnPropertyChanged("Plotter");
OnPlotterLoadedEventHandler += PlotterLoadedEvent; OnPlotterLoadedEventHandler += PlotterLoadedEvent;
} PropertyChanged += OnViewModelPropertyChanged;
Initialize();
public void PlotterLoadedEvent(object sender, PlotterLoadedEventArgs e)
{
// String selectedSymbol = "VST";
String selectedSymbol = "CRS";
int selectedDayCount = 180;
plotter = e.AvaPlot;
BollingerBandRenderer bollingerBandRenderer = new BollingerBandRenderer(plotter);
bollingerBandRenderer.SetData(selectedSymbol, selectedDayCount);
bollingerBandRenderer.Render();
}
// ********************************************** 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)
{
}
} }
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)
{
base.OnPropertyChanged("Symbols");
base.OnPropertyChanged("WatchListNames");
base.OnPropertyChanged("SelectedWatchList");
}
});
}
// *******************************************************************************************************************************************
/// <summary>
/// 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
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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(() =>
{
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<String> WatchListNames
{
get
{
return watchListNames;
}
}
public String SelectedWatchList
{
get
{
return selectedWatchList;
}
set
{
selectedWatchList = value;
base.OnPropertyChanged("SelectedWatchList");
}
}
public ObservableCollection<String> Symbols
{
get
{
return symbols;
}
}
public int SelectedDayCount
{
get
{
return selectedDayCount;
}
set
{
selectedDayCount = value;
base.OnPropertyChanged("SelectedDayCount");
}
}
public List<int> 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 + ")";
}
}
}
} }

View File

@@ -22,7 +22,7 @@
<local:OSValueConverter x:Key="OSValueConverter"/> <local:OSValueConverter x:Key="OSValueConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Grid Background="LightGray"> <Grid Background="LightGray">
<li:LoadingIndicator ZIndex="1" IsActive="{Binding IsBusy}" Mode="Arcs" SpeedRatio="1.2" Width="200" Height="200"/> <li:LoadingIndicator ZIndex="1" IsActive="{Binding IsBusy}" Mode="Arcs" SpeedRatio="1.2" Width="200" Height="200"/>
<DockPanel> <DockPanel>
<Grid Margin="4"> <Grid Margin="4">

View File

@@ -11,5 +11,78 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="vm:ScottPlotViewModel" x:DataType="vm:ScottPlotViewModel"
x:Class="PortfolioManager.Views.ScottPlotView"> x:Class="PortfolioManager.Views.ScottPlotView">
<ScottPlot:AvaPlot Name="AvaPlot" Loaded="AvaPlot_Loaded"/> <UserControl.Resources>
<local:CurrencyValueConverter x:Key="CurrencyFormat"/>
<local:DoubleValueConverter x:Key="DoubleFormat"/>
<local:IntValueConverter x:Key="IntFormat"/>
<local:DateValueConverter x:Key="DateFormat"/>
<local:RMultipleValueConverter x:Key="RMultipleFormat"/>
<local:BoolValueConverter x:Key="BoolFormat"/>
<local:OSValueConverter x:Key="OSValueConverter"/>
</UserControl.Resources>
<Grid Background="LightGray">
<li:LoadingIndicator ZIndex="1" IsActive="{Binding IsBusy}" Mode="Arcs" SpeedRatio="1.2" Width="200" Height="200"/>
<DockPanel>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="6" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="3" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="89*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Vertical">
<Label Content="Watch List" HorizontalAlignment="Left" ></Label>
<ComboBox ItemsSource="{Binding Path=WatchListNames, Mode=OneWay}" SelectedItem="{Binding Path=SelectedWatchList}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
<Label Content="Symbols" HorizontalAlignment="Left" ></Label>
<ComboBox ItemsSource="{Binding Path=Symbols, Mode=OneWay}" SelectedItem="{Binding Path=SelectedSymbol}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
<Label Content="Day Count" HorizontalAlignment="Left" ></Label>
<ComboBox ItemsSource="{Binding Path=DayCounts, Mode=OneWay}" SelectedItem="{Binding Path=SelectedDayCount}"/>
<!-- <Button Content="Refresh" HorizontalAlignment="Stretch" Command="{Binding Path=Refresh}"></Button>
<CheckBox Content="Sync Trade To Band" IsChecked="{Binding Mode=TwoWay,Path=SyncTradeToBand}" HorizontalAlignment="Stretch" />
<CheckBox Content="Show Trade Labels" IsChecked="{Binding Mode=TwoWay,Path=ShowTradeLabels}" HorizontalAlignment="Stretch" />
<CheckBox Content="Show Insider Transactions" IsChecked="{Binding Mode=TwoWay,Path=CheckBoxShowInsiderTransactions}" HorizontalAlignment="Stretch" /> -->
</StackPanel>
</Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="24" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="16" Text="{Binding Path=GraphTitle}" HorizontalAlignment="Center"></TextBlock>
<!-- <ScottPlot:AvaPlot Grid.Row="1" Name="AvaPlot" Loaded="AvaPlot_Loaded"/> -->
<ContentControl Grid.Row="1" Content="{Binding Path=Plotter}"/>
</Grid>
</Grid>
</DockPanel>
</Grid>
</UserControl> </UserControl>

View File

@@ -19,13 +19,14 @@ public partial class ScottPlotView : UserControl
private void AvaPlot_Loaded(object sender, RoutedEventArgs e) private void AvaPlot_Loaded(object sender, RoutedEventArgs e)
{ {
// This code will execute when the AvaPlot is loaded // This code will execute when the AvaPlot is loaded
if (sender is ScottPlot.Avalonia.AvaPlot) // if (sender is ScottPlot.Avalonia.AvaPlot)
{ // {
if (default == avaPlot) // if (default == avaPlot)
{ // {
PlotterWorkspaceViewModel viewModel = (DataContext as PlotterWorkspaceViewModel); // PlotterWorkspaceViewModel viewModel = (DataContext as PlotterWorkspaceViewModel);
viewModel.OnPlotterLoaded(this.Find<AvaPlot>("AvaPlot")); // avaPlot = this.Find<AvaPlot>("AvaPlot");
} // viewModel.OnPlotterLoaded(avaPlot);
} // }
// }
} }
} }