Continuing work on BollingerBandViewModel

This commit is contained in:
2025-06-13 18:59:01 -04:00
parent 9d7a18df54
commit b7ef1c6cb3
4 changed files with 493 additions and 25 deletions

View File

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Threading;
using MarketData;
using MarketData.Utils;
using MarketData.DataAccess;
namespace PortfolioManager.Cache
{
public class SymbolCache : IDisposable
{
private List<String> symbolCache=new List<String>();
private Object thisLock=new Object();
private Thread cacheMonitorThread=null;
private volatile bool threadRun=true;
private int cacheRefreshAfter=60000; // Invalidate cache after
private static SymbolCache symbolCacheInstance=null;
private SymbolCache()
{
cacheMonitorThread=new Thread(new ThreadStart(ThreadProc));
cacheMonitorThread.Start();
}
public static SymbolCache GetInstance()
{
lock(typeof(SymbolCache))
{
if(null==symbolCacheInstance) symbolCacheInstance=new SymbolCache();
return symbolCacheInstance;
}
}
public void Clear()
{
lock(thisLock)
{
symbolCache=new List<string>();
}
}
public void Dispose()
{
lock(thisLock)
{
if(null==symbolCacheInstance || false==threadRun)return;
threadRun=false;
if(null!=cacheMonitorThread)
{
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[SymbolCache:Dispose]Thread state is {0}. Joining main thread...",Utility.ThreadStateToString(cacheMonitorThread)));
cacheMonitorThread.Join(5000);
cacheMonitorThread=null;
MDTrace.WriteLine(LogLevel.DEBUG,"[SymbolCache:Dispose] End.");
}
symbolCacheInstance=null;
}
}
public List<String> GetSymbols()
{
lock(this)
{
if(0==symbolCache.Count)symbolCache=PricingDA.GetSymbols();
return symbolCache;
}
}
private void ThreadProc()
{
int quantums=0;
int quantumInterval=1000;
while(threadRun)
{
Thread.Sleep(quantumInterval);
quantums+=quantumInterval;
if(quantums>cacheRefreshAfter)
{
quantums=0;
lock(thisLock)
{
symbolCache=PricingDA.GetSymbols();
}
}
}
}
}
}

View File

@@ -1,11 +1,10 @@
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using Eremex.AvaloniaUI.Charts;
namespace PortfolioManager.DataSeriesViewModels
{
public partial class CompositeDataSource : ObservableObject
{
[ObservableProperty] ISeriesDataAdapter dataAdapter;
}
public partial class CompositeDataSource : ObservableObject
{
[ObservableProperty] ISeriesDataAdapter dataAdapter;
}
}

View File

@@ -2,38 +2,89 @@ 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 DynamicData;
using Eremex.AvaloniaUI.Charts;
using MarketData.DataAccess;
using MarketData.Generator;
using MarketData.Generator.CMMomentum;
using MarketData.Generator.Interface;
using MarketData.Generator.Model;
using MarketData.MarketDataModel;
using MarketData.Numerical;
using MarketData.Utils;
using PortfolioManager.Cache;
using PortfolioManager.DataSeriesViewModels;
using PortfolioManager.Dialogs;
using PortfolioManager.Models;
using PortfolioManager.UIUtils;
using Position = MarketData.Generator.CMMomentum.Position;
namespace PortfolioManager.ViewModels
{
public partial class BollingerBandViewModel : WorkspaceViewModel
{
private bool isBusy = false;
private List<String> watchListNames;
private String selectedWatchList;
private List<Int32> dayCounts = new int[] { 60, 90, 180, 360, 720, 1440, 3600 }.ToList<int>();
private int selectedDayCount = 90;
private ObservableCollection<String> symbols = new ObservableCollection<String>();
private String selectedSymbol = default;
private InsiderTransactionSummaries insiderTransactionSummaries = null;
private Price zeroPrice = null;
private PortfolioTrades portfolioTrades;
private PortfolioTrades portfolioTradesLots;
private StopLimit stopLimit; // This is the stop limit that is looked up in the database and displayed (if there is one)
private Prices prices = null;
private bool syncTradeToBand = true;
private String companyName = default;
private CompositeDataSource compositeDataSourceZeroPoint = null;
private CompositeDataSource compositeDataSourceStopLimit = null;
private CompositeDataSource compositeDataSourceInsiderTransactionPointDisposedSmall = null;
private CompositeDataSource compositeDataSourceInsiderTransactionPointDisposedMedium = null;
private CompositeDataSource compositeDataSourceInsiderTransactionPointDisposedLarge = null;
private CompositeDataSource compositeDataSourceInsiderTransactionPointAcquiredSmall = null;
private CompositeDataSource compositeDataSourceInsiderTransactionPointAcquiredMedium = null;
private CompositeDataSource compositeDataSourceInsiderTransactionPointAcquiredLarge = null;
private CompositeDataSource compositeDataSourceK = null;
private CompositeDataSource compositeDataSourceKL1 = null;
private CompositeDataSource compositeDataSourceL = null;
private CompositeDataSource compositeDataSourceLP1 = null;
private CompositeDataSource compositeDataSourceHigh = null;
private CompositeDataSource compositeDataSourceLow = null;
private CompositeDataSource compositeDataSourceClose = null;
private CompositeDataSource compositeDataSourceSMAN = null;
private CompositeDataSource compositeDataSourceVolume = null;
private CompositeDataSource compositeDataSourceLeastSquares = null;
private CompositeDataSource compositeDataSourceTradePoints = null;
private BollingerBands bollingerBands;
public BollingerBandViewModel()
{
PropertyChanged += OnViewModelPropertyChanged;
DisplayName = "BollingerBand View";
Initialize();
}
private void Initialize()
{
Task workerTask = Task.Factory.StartNew(() =>
{
watchListNames = WatchListDA.GetWatchLists();
watchListNames.Insert(0, UIConstants.CONST_ALL);
selectedWatchList = watchListNames.Find(x => x.Equals("Valuations"));
symbols.AddRange(WatchListDA.GetWatchList(selectedWatchList));
});
workerTask.ContinueWith((continuation) =>
{
base.OnPropertyChanged("Symbols");
base.OnPropertyChanged("WatchListNames");
base.OnPropertyChanged("SelectedWatchList");
});
}
public bool IsBusy
@@ -49,7 +100,92 @@ namespace PortfolioManager.ViewModels
}
}
// ******************************************************************* P E R S I S T E N C E ***************************************************
public override String Title
{
get
{
return DisplayName;
}
}
public override String DisplayName
{
get
{
return "BollingerBand Model";
// if (null == pathFileName) return "MGSHMomentum Model";
// String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
// return "MGSHMomentum Model (" + pureFileName + ")";
}
}
public ObservableCollection<String> Symbols
{
get
{
return symbols;
}
}
public String SelectedSymbol
{
get
{
return selectedSymbol;
}
set
{
selectedSymbol = value;
base.OnPropertyChanged("SelectedSymbol");
}
}
public int SelectedDayCount
{
get
{
return selectedDayCount;
}
set
{
selectedDayCount = value;
base.OnPropertyChanged("SelectedDayCount");
}
}
public List<int> DayCounts
{
get
{
return dayCounts;
}
}
// ****************************************************** P R O P E R T I E S ************************************************
public List<String> WatchListNames
{
get
{
return watchListNames;
}
}
public String SelectedWatchList
{
get
{
return selectedWatchList;
}
set
{
selectedWatchList = value;
base.OnPropertyChanged("SelectedWatchList");
}
}
// ******************************************************************* P E R S I S T E N C E ***************************************************
public override bool CanPersist()
{
@@ -58,12 +194,234 @@ namespace PortfolioManager.ViewModels
public override SaveParameters GetSaveParameters()
{
return null;
return null;
}
public override void SetSaveParameters(SaveParameters saveParameters)
{
return;
return;
}
// *************************************************************************************************************************************
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName.Equals("SelectedSymbol"))
{
InitializeDataSources();
insiderTransactionSummaries = null;
zeroPrice = null;
prices = null;
portfolioTrades = null;
portfolioTradesLots = null;
stopLimit = null;
}
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") && null!=selectedSymbol))
{
IsBusy=true;
Task workerTask=Task.Factory.StartNew(()=>
{
base.DisplayName="Bollinger("+selectedSymbol+")";
base.OnPropertyChanged("DisplayName");
// DEBUG
stopLimit=PortfolioDA.GetStopLimit(selectedSymbol);
portfolioTrades = PortfolioDA.GetTradesSymbol(selectedSymbol);
portfolioTradesLots=LotAggregator.CombineLots(portfolioTrades);
if (null != portfolioTrades && 0 != portfolioTrades.Count)
{
DateGenerator dateGenerator = new DateGenerator();
DateTime earliestTrade = portfolioTrades[0].TradeDate;
earliestTrade = earliestTrade.AddDays(-30);
int daysBetween = dateGenerator.DaysBetween(earliestTrade, DateTime.Now);
if (daysBetween < selectedDayCount || !syncTradeToBand) prices = PricingDA.GetPrices(selectedSymbol, selectedDayCount);
else prices = PricingDA.GetPrices(selectedSymbol, earliestTrade);
DateTime earliestInsiderTransactionDate=dateGenerator.GenerateFutureBusinessDate(prices[prices.Count-1].Date,30);
insiderTransactionSummaries=InsiderTransactionDA.GetInsiderTransactionSummaries(selectedSymbol,earliestInsiderTransactionDate);
// calculate the break even price on the open trades for this symbol
PortfolioTrades openTrades=portfolioTrades.GetOpenTrades();
DateTime latestPricingDate = PricingDA.GetLatestDate(selectedSymbol);
Price latestPrice = PricingDA.GetPrice(selectedSymbol, latestPricingDate);
zeroPrice=ParityGenerator.GenerateGainLossValue(openTrades,latestPrice);
if (!syncTradeToBand)
{
DateTime earliestPricingDate = prices[prices.Count - 1].Date;
earliestPricingDate = earliestPricingDate.AddDays(30);
IEnumerable<PortfolioTrade> tradesInRange = (from portfolioTrade in portfolioTradesLots where portfolioTrade.TradeDate >= earliestPricingDate select portfolioTrade);
portfolioTrades = new PortfolioTrades();
foreach (PortfolioTrade portfolioTrade in tradesInRange) portfolioTrades.Add(portfolioTrade);
portfolioTradesLots = portfolioTrades;
}
}
else
{
prices = PricingDA.GetPrices(selectedSymbol, selectedDayCount);
if (null != prices && 0 != prices.Count)
{
DateGenerator dateGenerator = new DateGenerator();
DateTime earliestInsiderTransactionDate = dateGenerator.GenerateFutureBusinessDate(prices[prices.Count - 1].Date, 30);
insiderTransactionSummaries = InsiderTransactionDA.GetInsiderTransactionSummaries(selectedSymbol, earliestInsiderTransactionDate);
}
}
companyName = PricingDA.GetNameForSymbol(selectedSymbol);
bollingerBands = BollingerBandGenerator.GenerateBollingerBands(prices);
CreateCompositeDataSources();
});
workerTask.ContinueWith((continuation)=>
{
IsBusy = false;
base.OnPropertyChanged("K");
base.OnPropertyChanged("KL1");
base.OnPropertyChanged("L");
base.OnPropertyChanged("LP1");
base.OnPropertyChanged("High");
base.OnPropertyChanged("Low");
base.OnPropertyChanged("Close");
base.OnPropertyChanged("SMAN");
base.OnPropertyChanged("Volume");
base.OnPropertyChanged("LeastSquares");
base.OnPropertyChanged("Title");
base.OnPropertyChanged("TradePoints");
base.OnPropertyChanged("Markers");
base.OnPropertyChanged("ZeroPoint");
base.OnPropertyChanged("ZeroPointMarkers");
base.OnPropertyChanged("StopLimit");
base.OnPropertyChanged("StopLimitMarkers");
base.OnPropertyChanged("RiskFreeRatePoint");
base.OnPropertyChanged("RiskFreeRatePointMarkers");
base.OnPropertyChanged("InsiderTransactionPointDisposedSmall");
base.OnPropertyChanged("InsiderTransactionPointMarkersDisposedSmall");
base.OnPropertyChanged("InsiderTransactionPointDisposedMedium");
base.OnPropertyChanged("InsiderTransactionPointMarkersDisposedMedium");
base.OnPropertyChanged("InsiderTransactionPointDisposedLarge");
base.OnPropertyChanged("InsiderTransactionPointMarkersDisposedLarge");
base.OnPropertyChanged("InsiderTransactionPointAcquiredSmall");
base.OnPropertyChanged("InsiderTransactionPointMarkersAcquiredSmall");
base.OnPropertyChanged("InsiderTransactionPointAcquiredMedium");
base.OnPropertyChanged("InsiderTransactionPointMarkersAcquiredMedium");
base.OnPropertyChanged("InsiderTransactionPointAcquiredLarge");
base.OnPropertyChanged("InsiderTransactionPointMarkersAcquiredLarge");
});
}
else if (eventArgs.PropertyName.Equals("SelectedWatchList"))
{
IsBusy = true;
Task workerTask = Task.Factory.StartNew(() =>
{
if (selectedWatchList.Equals(Constants.CONST_ALL))
{
symbols.Clear();
symbols.AddRange(SymbolCache.GetInstance().GetSymbols());
}
else
{
symbols.Clear();
symbols.AddRange(WatchListDA.GetWatchList(selectedWatchList));
}
});
workerTask.ContinueWith((continuation) =>
{
IsBusy = false;
base.OnPropertyChanged("Symbols");
});
}
}
public void CreateCompositeDataSources()
{
if(null==prices || 0==prices.Count)return;
double minClose=(from Price price in prices select price.Close).Min();
// get the maximum date in the bollinger band series
DateTime maxBollingerDate=(from BollingerBandElement bollingerBandElement in bollingerBands select bollingerBandElement.Date).Max();
// ensure that the insider transactions are clipped to the bollingerband max date. There are some items in insider transaction summaries (options dated in the future) that will throw the graphic out of proportion
InsiderTransactionSummaries disposedSummaries=new InsiderTransactionSummaries((from InsiderTransactionSummary insiderTransactionSummary in insiderTransactionSummaries where insiderTransactionSummary.NumberOfSharesAcquiredDisposed<0 && insiderTransactionSummary.TransactionDate.Date<=maxBollingerDate select insiderTransactionSummary).ToList());
InsiderTransactionSummaries acquiredSummaries=new InsiderTransactionSummaries((from InsiderTransactionSummary insiderTransactionSummary in insiderTransactionSummaries where insiderTransactionSummary.NumberOfSharesAcquiredDisposed>0 && insiderTransactionSummary.TransactionDate.Date<=maxBollingerDate select insiderTransactionSummary).ToList());
BinCollection<InsiderTransactionSummary> disposedSummariesBin=BinHelper<InsiderTransactionSummary>.CreateBins(new BinItems<InsiderTransactionSummary>(disposedSummaries),3);
BinCollection<InsiderTransactionSummary> acquiredSummariesBin=BinHelper<InsiderTransactionSummary>.CreateBins(new BinItems<InsiderTransactionSummary>(acquiredSummaries),3);
// compositeDataSourceZeroPoint = GainLossModel.Price(zeroPrice);
/*
if(null!=stopLimits)
{
compositeDataSourceStopLimit=StopLimitCompositeModel.CreateCompositeDataSource(stopLimits);
}
else if(null!=stopLimit && null!=zeroPrice)
{
compositeDataSourceStopLimit=GainLossModel.CreateCompositeDataSource(zeroPrice.Date,stopLimit.StopPrice);
}
compositeDataSourceInsiderTransactionPointDisposedSmall=InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(disposedSummariesBin[2]),minClose);
compositeDataSourceInsiderTransactionPointDisposedMedium=InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(disposedSummariesBin[1]),minClose);
compositeDataSourceInsiderTransactionPointDisposedLarge=InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(disposedSummariesBin[0]),minClose);
compositeDataSourceInsiderTransactionPointAcquiredSmall=InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(acquiredSummariesBin[0]),minClose);
compositeDataSourceInsiderTransactionPointAcquiredMedium=InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(acquiredSummariesBin[1]),minClose);
compositeDataSourceInsiderTransactionPointAcquiredLarge=InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(acquiredSummariesBin[2]),minClose);
compositeDataSourceK =BollingerBandModel.K(bollingerBands);
compositeDataSourceKL1 =BollingerBandModel.KL1(bollingerBands);
compositeDataSourceL =BollingerBandModel.L(bollingerBands);
compositeDataSourceLP1 =BollingerBandModel.LP1(bollingerBands);
compositeDataSourceHigh =BollingerBandModel.High(bollingerBands);
compositeDataSourceLow =BollingerBandModel.Low(bollingerBands);
compositeDataSourceClose =BollingerBandModel.Close(bollingerBands);
compositeDataSourceSMAN =BollingerBandModel.SMAN(bollingerBands);
compositeDataSourceVolume =BollingerBandModel.Volume(bollingerBands);
compositeDataSourceLeastSquares = BollingerBandModel.LeastSquares(bollingerBands);
compositeDataSourceTradePoints = PortfolioTradeModel.PortfolioTrades(portfolioTradesLots);
*/
}
private void InitializeDataSources()
{
compositeDataSourceStopLimit = Empty();
compositeDataSourceZeroPoint = Empty();
compositeDataSourceInsiderTransactionPointDisposedSmall = Empty();
compositeDataSourceInsiderTransactionPointDisposedMedium = Empty();
compositeDataSourceInsiderTransactionPointDisposedLarge = Empty();
compositeDataSourceInsiderTransactionPointAcquiredSmall = Empty();
compositeDataSourceInsiderTransactionPointAcquiredMedium = Empty();
compositeDataSourceInsiderTransactionPointAcquiredLarge = Empty();
compositeDataSourceK = Empty();
compositeDataSourceKL1 = Empty();
compositeDataSourceL = Empty();
compositeDataSourceLP1 = Empty();
compositeDataSourceHigh = Empty();
compositeDataSourceLow = Empty();
compositeDataSourceClose = Empty();
compositeDataSourceSMAN = Empty();
compositeDataSourceVolume = Empty();
compositeDataSourceLeastSquares = Empty();
compositeDataSourceTradePoints = Empty();
}
private static CompositeDataSource Empty()
{
return new CompositeDataSource()
{
DataAdapter = new SortedDateTimeDataAdapter()
};
}
}
}
}

View File

@@ -44,7 +44,29 @@
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Vertical">
<!-- Fields go here -->
<!-- <Label Content="Watch List" HorizontalAlignment="Left"></Label>
<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}"/>
<!--
<Label Content="Watch List" HorizontalAlignment="Left"></Label>
<ComboBox ItemsSource="{Binding Path=WatchListNames, Mode=OneTime}" SelectedItem="{Binding Path=SelectedWatchList}" ></ComboBox>
<Label Content="Symbol" HorizontalAlignment="Left"></Label>
<ComboBox Name="cbSymbols" ItemsSource="{Binding Path=Symbols, Mode=OneWay}" SelectedItem="{Binding Path=SelectedSymbol}" UseLayoutRounding="False" >
@@ -70,7 +92,13 @@
<RowDefinition Height="3*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<mxc:CartesianChart Grid.Row="0">
<!-- <mxc:CartesianChart.Series>
<mxc:CartesianSeries Name="PerformanceSeries" DataAdapter="{Binding Data.DataAdapter}" >
<mxc:CartesianLineSeriesView Color="MidnightBlue" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
</mxc:CartesianSeries>
</mxc:CartesianChart.Series> -->
</mxc:CartesianChart>
<!-- The Chart goes here at grid.row =0 -->
</Grid>