4 Commits

Author SHA1 Message Date
7fef9b1050 Improve BollingerBandRendering 2026-03-14 09:39:09 -04:00
01c2516eaa Fix the symbol cache and image cache 2026-02-22 14:04:44 -05:00
9a7b09ef7c Fix symbol cache 2026-02-22 13:36:28 -05:00
7e0c5f22b6 Work on StopLimits 2026-02-18 22:18:36 -05:00
8 changed files with 217 additions and 148 deletions

View File

@@ -57,6 +57,7 @@ namespace PortfolioManager.Cache
{ {
lock (thisLock) lock (thisLock)
{ {
DisposeBitmaps();
imageCache = new Dictionary<ImageType, Bitmap>(); imageCache = new Dictionary<ImageType, Bitmap>();
} }
} }
@@ -66,21 +67,29 @@ namespace PortfolioManager.Cache
lock (thisLock) lock (thisLock)
{ {
if (null == imageCacheInstance) return; if (null == imageCacheInstance) return;
List<Bitmap> bitmaps = imageCache.Values.ToList(); DisposeBitmaps();
foreach (Bitmap bitmap in bitmaps)
{
bitmap.Dispose();
}
imageCache = null; imageCache = null;
imageCacheInstance = null; imageCacheInstance = null;
} }
} }
public IImage GetImage(ImageCache.ImageType imageType) private void DisposeBitmaps()
{ {
lock(this) if(null == imageCache)return;
List<Bitmap> bitmaps = imageCache.Values.ToList();
foreach (Bitmap bitmap in bitmaps)
{ {
return imageCache[imageType]; bitmap.Dispose();
}
}
public IImage GetImage(ImageType imageType)
{
lock(thisLock)
{
if (imageCache == null)throw new ObjectDisposedException(nameof(ImageCache));
if (!imageCache.TryGetValue(imageType, out var bitmap))throw new KeyNotFoundException($"Image {imageType} not found in cache.");
return bitmap;
} }
} }
} }

View File

@@ -10,7 +10,8 @@ namespace PortfolioManager.Cache
public class SymbolCache : IDisposable public class SymbolCache : IDisposable
{ {
private List<String> symbolCache=new List<String>(); private List<String> symbolCache=new List<String>();
private Object thisLock=new Object(); private readonly Object thisLock=new Object();
private readonly Object fetchLock = new Object();
private Thread cacheMonitorThread=null; private Thread cacheMonitorThread=null;
private volatile bool threadRun=true; private volatile bool threadRun=true;
private int cacheRefreshAfter=60000; // Invalidate cache after private int cacheRefreshAfter=60000; // Invalidate cache after
@@ -36,12 +37,15 @@ namespace PortfolioManager.Cache
symbolCache=new List<string>(); symbolCache=new List<string>();
} }
} }
public void Dispose() public void Dispose()
{ {
lock(thisLock) lock(thisLock)
{ {
if(null==symbolCacheInstance || false==threadRun)return; if(null==symbolCacheInstance || false==threadRun)return;
threadRun=false; threadRun=false;
symbolCacheInstance=null;
}
if(null!=cacheMonitorThread) if(null!=cacheMonitorThread)
{ {
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[SymbolCache:Dispose]Thread state is {0}. Joining main thread...",Utility.ThreadStateToString(cacheMonitorThread))); MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[SymbolCache:Dispose]Thread state is {0}. Joining main thread...",Utility.ThreadStateToString(cacheMonitorThread)));
@@ -49,18 +53,26 @@ namespace PortfolioManager.Cache
cacheMonitorThread=null; cacheMonitorThread=null;
MDTrace.WriteLine(LogLevel.DEBUG,"[SymbolCache:Dispose] End."); MDTrace.WriteLine(LogLevel.DEBUG,"[SymbolCache:Dispose] End.");
} }
symbolCacheInstance=null;
}
} }
public List<String> GetSymbols() public List<String> GetSymbols()
{ {
lock(this) lock(thisLock)
{ {
if(0==symbolCache.Count)symbolCache=PricingDA.GetSymbols(); if(symbolCache.Count>0)return new List<string>(symbolCache);
return symbolCache; }
lock(fetchLock)
{
List<String> symbols = PricingDA.GetSymbols();
lock(thisLock)
{
if(symbolCache.Count>0)return new List<string>(symbolCache);
symbolCache=new List<String>(symbols);
return new List<string>(symbols);
} }
} }
}
private void ThreadProc() private void ThreadProc()
{ {
int quantums=0; int quantums=0;
@@ -68,13 +80,15 @@ namespace PortfolioManager.Cache
while(threadRun) while(threadRun)
{ {
Thread.Sleep(quantumInterval); Thread.Sleep(quantumInterval);
if(!threadRun)break;
quantums+=quantumInterval; quantums+=quantumInterval;
if(quantums>cacheRefreshAfter) if(quantums>cacheRefreshAfter)
{ {
quantums=0; quantums=0;
List<String> symbols = PricingDA.GetSymbols();
lock(thisLock) lock(thisLock)
{ {
symbolCache=PricingDA.GetSymbols(); symbolCache=new List<string>(symbols);
} }
} }
} }

View File

@@ -270,7 +270,7 @@ namespace PortfolioManager.Models
{ {
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue); if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
if(!Utility.IsEpoch(position.LastStopAdjustment)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); // if we have a trailing stop then we are no longer using the initial stop if(!Utility.IsEpoch(position.LastStopAdjustment)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); // if we have a trailing stop then we are no longer using the initial stop
StopLimit stopLimit=StopLimitDA.GetStopLimit(position.Symbol); StopLimit stopLimit=StopLimitDA.GetStopLimit(position.Symbol,position.Shares);
if(null==stopLimit||!stopLimit.StopPrice.Equals(Math.Round(position.InitialStopLimit,2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple); if(null==stopLimit||!stopLimit.StopPrice.Equals(Math.Round(position.InitialStopLimit,2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple);
if(currentPriceLow<=position.InitialStopLimit) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red); if(currentPriceLow<=position.InitialStopLimit) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
@@ -295,7 +295,7 @@ namespace PortfolioManager.Models
{ {
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red); return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
} }
StopLimit stopLimit=StopLimitDA.GetStopLimit(position.Symbol); StopLimit stopLimit=StopLimitDA.GetStopLimit(position.Symbol,position.Shares);
if(null==stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.TrailingStopLimit,2))) if(null==stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.TrailingStopLimit,2)))
{ {
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple); return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple);

View File

@@ -36,17 +36,17 @@ namespace PortfolioManager.Models
return compositeDataSource; return compositeDataSource;
} }
public static CompositeDataSource CreateCompositeDataSource(DateTime xSource, double ySource) // public static CompositeDataSource CreateCompositeDataSource(DateTime xSource, double ySource)
{ // {
if (Utility.IsEpoch(xSource)) return Empty(); // if (Utility.IsEpoch(xSource)) return Empty();
SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter(); // SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter();
sortedDateTimeDataAdapter.Add(xSource, ySource); // sortedDateTimeDataAdapter.Add(xSource, ySource);
CompositeDataSource compositeDataSource = new CompositeDataSource() // CompositeDataSource compositeDataSource = new CompositeDataSource()
{ // {
DataAdapter = sortedDateTimeDataAdapter // DataAdapter = sortedDateTimeDataAdapter
}; // };
return compositeDataSource; // return compositeDataSource;
} // }
// This is the active gain/loss as number or percent. // This is the active gain/loss as number or percent.
public static CompositeDataSource GainLoss(ModelPerformanceSeries gainLossList, bool useGainLoss) public static CompositeDataSource GainLoss(ModelPerformanceSeries gainLossList, bool useGainLoss)

View File

@@ -542,7 +542,7 @@ namespace PortfolioManager.Models
{ {
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue); if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
if (!Utility.IsEpoch(position.LastStopAdjustment)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); // if we have a trailing stop then we are no longer using the initial stop if (!Utility.IsEpoch(position.LastStopAdjustment)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); // if we have a trailing stop then we are no longer using the initial stop
StopLimit stopLimit = StopLimitDA.GetStopLimit(position.Symbol); StopLimit stopLimit = StopLimitDA.GetStopLimit(position.Symbol,position.Shares);
if (null == stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.InitialStopLimit, 2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple); if (null == stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.InitialStopLimit, 2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple);
if (currentPriceLow <= position.InitialStopLimit) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red); if (currentPriceLow <= position.InitialStopLimit) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
@@ -569,7 +569,7 @@ namespace PortfolioManager.Models
{ {
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red); return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
} }
StopLimit stopLimit = StopLimitDA.GetStopLimit(position.Symbol); StopLimit stopLimit = StopLimitDA.GetStopLimit(position.Symbol,position.Shares);
if (null == stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.TrailingStopLimit, 2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple); if (null == stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.TrailingStopLimit, 2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple);
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Eremex.AvaloniaUI.Charts; using Eremex.AvaloniaUI.Charts;
using MarketData.MarketDataModel; using MarketData.MarketDataModel;
using MarketData.Utils;
using PortfolioManager.DataSeriesViewModels; using PortfolioManager.DataSeriesViewModels;
namespace PortfolioManager.Models namespace PortfolioManager.Models
@@ -37,5 +38,25 @@ namespace PortfolioManager.Models
}; };
return compositeDataSource; return compositeDataSource;
} }
public static CompositeDataSource CreateCompositeDataSource(DateTime xSource,StopLimits stopLimits)
{
if (null == stopLimits || 0 == stopLimits.Count) return Empty();
foreach(StopLimit stopLimit in stopLimits)
{
stopLimit.EffectiveDate = xSource;
}
SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter();
List<StopLimit> sortedStopLimits = stopLimits.OrderBy(x => x.EffectiveDate).ToList();
foreach (StopLimit stopLimit in sortedStopLimits)
{
sortedDateTimeDataAdapter.Add(stopLimit.EffectiveDate, stopLimit.StopPrice);
}
CompositeDataSource compositeDataSource = new CompositeDataSource()
{
DataAdapter = sortedDateTimeDataAdapter
};
return compositeDataSource;
}
} }
} }

View File

@@ -40,7 +40,7 @@ namespace PortfolioManager.Renderers
private bool showInsiderTransactions = true; private bool showInsiderTransactions = true;
private bool showLeastSquares = true; private bool showLeastSquares = true;
private bool syncTradeToBand = true; private bool syncTradeToBand = true;
private StopLimit stopLimit = default; // StopLimits that the renderer has located using StopLimitDA private StopLimits internalStopLimits = default; // StopLimits that the renderer has located using StopLimitDA
private StopLimits externalStopLimits = default; // StopLimits that are provided to the renderer externally by a model private StopLimits externalStopLimits = default; // StopLimits that are provided to the renderer externally by a model
private PortfolioTrades portfolioTrades = default; private PortfolioTrades portfolioTrades = default;
private PortfolioTrades portfolioTradesLots = default; private PortfolioTrades portfolioTradesLots = default;
@@ -56,6 +56,13 @@ namespace PortfolioManager.Renderers
PropertyChanged += OnBollingerBandRendererPropertyChanged; PropertyChanged += OnBollingerBandRendererPropertyChanged;
} }
private DateTime EarliestBollingerBandDate {get;set;} // This gets set when the bollinger band is generated
private bool IsVisible(DateTime date)
{
return syncTradeToBand || date >= EarliestBollingerBandDate;
}
private void OnBollingerBandRendererPropertyChanged(Object sender, PropertyChangedEventArgs eventArgs) private void OnBollingerBandRendererPropertyChanged(Object sender, PropertyChangedEventArgs eventArgs)
{ {
if (eventArgs.PropertyName.Equals("ShowLegend")) if (eventArgs.PropertyName.Equals("ShowLegend"))
@@ -94,10 +101,12 @@ namespace PortfolioManager.Renderers
{ {
lock(Plotter.Plot.Sync) lock(Plotter.Plot.Sync)
{ {
int bollingerBandMovingAverageDays = 20;
MDTrace.WriteLine(LogLevel.DEBUG,$"[SetData] ENTER"); MDTrace.WriteLine(LogLevel.DEBUG,$"[SetData] ENTER");
this.selectedSymbol = selectedSymbol; this.selectedSymbol = selectedSymbol;
this.selectedDayCount = selectedDayCount; this.selectedDayCount = selectedDayCount;
stopLimit = StopLimitDA.GetStopLimit(selectedSymbol);
internalStopLimits = StopLimitDA.GetStopLimits(selectedSymbol);
portfolioTrades = PortfolioDA.GetTradesSymbol(selectedSymbol); portfolioTrades = PortfolioDA.GetTradesSymbol(selectedSymbol);
portfolioTradesLots = LotAggregator.CombineLots(portfolioTrades); portfolioTradesLots = LotAggregator.CombineLots(portfolioTrades);
@@ -105,13 +114,18 @@ namespace PortfolioManager.Renderers
if (null != portfolioTrades && 0 != portfolioTrades.Count) if (null != portfolioTrades && 0 != portfolioTrades.Count)
{ {
DateGenerator dateGenerator = new DateGenerator(); DateGenerator dateGenerator = new DateGenerator();
DateTime earliestTrade = portfolioTrades[0].TradeDate; DateTime earliestTrade = portfolioTrades.First().TradeDate; // portfolio trades are ordered with earliest trade in the lowest index
earliestTrade = earliestTrade.AddDays(-30); earliestTrade = dateGenerator.GenerateHistoricalDate(earliestTrade, bollingerBandMovingAverageDays); // cover the moving average lag in the band
int daysBetween = dateGenerator.DaysBetween(earliestTrade, DateTime.Now); int daysBetween = dateGenerator.DaysBetween(earliestTrade, DateTime.Now);
if (daysBetween < selectedDayCount || !syncTradeToBand) prices = PricingDA.GetPrices(selectedSymbol, selectedDayCount); if (daysBetween < selectedDayCount || !syncTradeToBand)
else prices = PricingDA.GetPrices(selectedSymbol, earliestTrade); {
prices = PricingDA.GetPrices(selectedSymbol, selectedDayCount + bollingerBandMovingAverageDays);
DateTime earliestInsiderTransactionDate = dateGenerator.GenerateFutureBusinessDate(prices[prices.Count - 1].Date, 30); }
else
{
prices = PricingDA.GetPrices(selectedSymbol, earliestTrade);
}
DateTime earliestInsiderTransactionDate = dateGenerator.GenerateFutureBusinessDate(prices.Last().Date, 30);
insiderTransactionSummaries = InsiderTransactionDA.GetInsiderTransactionSummaries(selectedSymbol, earliestInsiderTransactionDate); insiderTransactionSummaries = InsiderTransactionDA.GetInsiderTransactionSummaries(selectedSymbol, earliestInsiderTransactionDate);
// calculate the break even price on the open trades for this symbol // calculate the break even price on the open trades for this symbol
@@ -122,8 +136,8 @@ namespace PortfolioManager.Renderers
if (!syncTradeToBand) if (!syncTradeToBand)
{ {
DateTime earliestPricingDate = prices[prices.Count - 1].Date; DateTime earliestPricingDate = prices.Last().Date;
earliestPricingDate = earliestPricingDate.AddDays(30); earliestPricingDate = dateGenerator.GenerateHistoricalDate(earliestPricingDate, bollingerBandMovingAverageDays);
IEnumerable<PortfolioTrade> tradesInRange = (from portfolioTrade in portfolioTradesLots where portfolioTrade.TradeDate >= earliestPricingDate select portfolioTrade); IEnumerable<PortfolioTrade> tradesInRange = (from portfolioTrade in portfolioTradesLots where portfolioTrade.TradeDate >= earliestPricingDate select portfolioTrade);
portfolioTrades = new PortfolioTrades(); portfolioTrades = new PortfolioTrades();
foreach (PortfolioTrade portfolioTrade in tradesInRange) portfolioTrades.Add(portfolioTrade); foreach (PortfolioTrade portfolioTrade in tradesInRange) portfolioTrades.Add(portfolioTrade);
@@ -132,15 +146,16 @@ namespace PortfolioManager.Renderers
} }
else else
{ {
prices = PricingDA.GetPrices(selectedSymbol, selectedDayCount); prices = PricingDA.GetPrices(selectedSymbol, selectedDayCount + bollingerBandMovingAverageDays);
if (null != prices && 0 != prices.Count) if (null != prices && 0 != prices.Count)
{ {
DateGenerator dateGenerator = new DateGenerator(); DateGenerator dateGenerator = new DateGenerator();
DateTime earliestInsiderTransactionDate = dateGenerator.GenerateFutureBusinessDate(prices[prices.Count - 1].Date, 30); DateTime earliestInsiderTransactionDate = dateGenerator.GenerateFutureBusinessDate(prices.Last().Date, 30);
insiderTransactionSummaries = InsiderTransactionDA.GetInsiderTransactionSummaries(selectedSymbol, earliestInsiderTransactionDate); insiderTransactionSummaries = InsiderTransactionDA.GetInsiderTransactionSummaries(selectedSymbol, earliestInsiderTransactionDate);
} }
} }
bollingerBands = BollingerBandGenerator.GenerateBollingerBands(prices); bollingerBands = BollingerBandGenerator.GenerateBollingerBands(prices, bollingerBandMovingAverageDays);
EarliestBollingerBandDate = bollingerBands.Min(x=>x.Date);
textMarkerManager.Clear(); textMarkerManager.Clear();
CalculateOffsets(); CalculateOffsets();
GenerateBollingerBands(); GenerateBollingerBands();
@@ -235,77 +250,77 @@ namespace PortfolioManager.Renderers
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image); imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image);
} }
/// <summary>
/// Generate Stop Limits
/// </summary>
private void GenerateStopLimits() private void GenerateStopLimits()
{ {
if (null == externalStopLimits && null == zeroPrice) return; if (externalStopLimits == null && zeroPrice == null) return;
if (null != externalStopLimits)
{ // Create the composite data source
if (externalStopLimits != null)
StopLimits = StopLimitCompositeModel.CreateCompositeDataSource(externalStopLimits); StopLimits = StopLimitCompositeModel.CreateCompositeDataSource(externalStopLimits);
} else if (internalStopLimits != null && zeroPrice != null)
else if (null != stopLimit && null != zeroPrice) StopLimits = StopLimitCompositeModel.CreateCompositeDataSource(zeroPrice.Date, internalStopLimits);
{
StopLimits = GainLossModel.CreateCompositeDataSource(zeroPrice.Date, stopLimit.StopPrice);
}
(DateTime[] dates, double[] values) = StopLimits.ToXYData(); (DateTime[] dates, double[] values) = StopLimits.ToXYData();
// Add the image markers // Add the image markers
Image imageStopLimitMarker = TextMarkerImageGenerator.ToSPImage(ImageCache.GetInstance().GetImage(ImageCache.ImageType.RedTriangleUp)); Image imageStopLimitMarker = TextMarkerImageGenerator.ToSPImage(
for (int index = 0; index < dates.Length; index++) ImageCache.GetInstance().GetImage(ImageCache.ImageType.RedTriangleUp));
for (int i = 0; i < dates.Length; i++)
{ {
DateTime date = dates[index]; DateTime date = dates[i];
double value = values[index];
if (!IsVisible(date))
continue;
double value = values[i];
Coordinates coordinates = new Coordinates(date.ToOADate(), value); Coordinates coordinates = new Coordinates(date.ToOADate(), value);
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, imageStopLimitMarker, SizeFactor.Normal); Plotter.Plot.Add.ImageMarker(coordinates, imageStopLimitMarker, SizeFactor.Normal);
} }
if(!showTradeLabels)return; if (!showTradeLabels) return;
// Add the text marker Price latestPrice = prices[0];
if (null != externalStopLimits)
// Helper to draw text markers
void DrawTextMarker(StopLimit limit, bool isLast)
{ {
for (int index = 0; index < externalStopLimits.Count; index++) if (!IsVisible(limit.EffectiveDate)) return;
{
StopLimit limit = externalStopLimits[index];
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.Append(limit.StopType).Append(" "); sb.Append(limit.StopType).Append(" ");
sb.Append(Utility.FormatCurrency(limit.StopPrice)); sb.Append(Utility.FormatCurrency(limit.StopPrice));
if (index == externalStopLimits.Count - 1)
if (isLast)
{ {
Price latestPrice = prices[0]; double percentOffsetFromLow = (latestPrice.Low - limit.StopPrice) / limit.StopPrice;
double percentOffsetFromLow = ((latestPrice.Low - limit.StopPrice) / limit.StopPrice); sb.Append(" (")
sb.Append(" (").Append(percentOffsetFromLow > 0 ? "+" : "").Append(Utility.FormatPercent(percentOffsetFromLow)).Append(")"); .Append(percentOffsetFromLow > 0 ? "+" : "")
.Append(Utility.FormatPercent(percentOffsetFromLow))
.Append(")");
} }
Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), FontFactor.FontSize); Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), FontFactor.FontSize);
Coordinates coordinates = new Coordinates(limit.EffectiveDate.ToOADate(), Coordinates coordinates = new Coordinates(
limit.EffectiveDate.ToOADate(),
limit.StopPrice - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC)); limit.StopPrice - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
coordinates = textMarkerManager.GetBestMarkerLocation(coordinates.X, coordinates.Y,offsets, offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC)); coordinates = textMarkerManager.GetBestMarkerLocation(
coordinates.X, coordinates.Y, offsets, offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image); Plotter.Plot.Add.ImageMarker(coordinates, image);
} }
}
else // Draw external or internal stop limits
if (externalStopLimits != null)
{ {
if (null == zeroPrice) return; for (int i = 0; i < externalStopLimits.Count; i++)
if (null == stopLimit || null == zeroPrice) return; DrawTextMarker(externalStopLimits[i], i == externalStopLimits.Count - 1);
Price latestPrice = prices[0]; }
double percentOffsetFromLow = ((latestPrice.Low - stopLimit.StopPrice) / stopLimit.StopPrice); else if (internalStopLimits != null && zeroPrice != null)
{
StringBuilder sb = new StringBuilder(); for (int i = 0; i < internalStopLimits.Count; i++)
sb.Append(stopLimit.StopType).Append(" "); DrawTextMarker(internalStopLimits[i], i == internalStopLimits.Count - 1);
sb.Append(Utility.FormatCurrency(stopLimit.StopPrice));
sb.Append(" (").Append(percentOffsetFromLow > 0 ? "+" : "").Append(Utility.FormatPercent(percentOffsetFromLow)).Append(")");
Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), FontFactor.FontSize);
Coordinates coordinates = new Coordinates(latestPrice.Date.ToOADate() - offsets.Offset(OffsetDictionary.OffsetType.HorizontalOffset3PC),
stopLimit.StopPrice - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
coordinates = textMarkerManager.GetBestMarkerLocation(coordinates.X, coordinates.Y,offsets, offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image);
} }
} }
@@ -314,38 +329,54 @@ namespace PortfolioManager.Renderers
/// </summary> /// </summary>
private void GenerateTradePoints() private void GenerateTradePoints()
{ {
if (null == portfolioTradesLots || 0 == portfolioTradesLots.Count) return; if (portfolioTradesLots == null || portfolioTradesLots.Count == 0) return;
// Here we add the image markers // Add the image markers
Image tradePointMarker = TextMarkerImageGenerator.ToSPImage(ImageCache.GetInstance().GetImage(ImageCache.ImageType.YellowTriangleUp)); Image tradePointMarker = TextMarkerImageGenerator.ToSPImage(
for (int index = 0; index < portfolioTradesLots.Count; index++) ImageCache.GetInstance().GetImage(ImageCache.ImageType.YellowTriangleUp));
foreach (var portfolioTrade in portfolioTradesLots)
{ {
PortfolioTrade portfolioTrade = portfolioTradesLots[index]; if (!IsVisible(portfolioTrade.TradeDate))
Coordinates coordinates = new Coordinates(portfolioTrade.TradeDate.ToOADate(), portfolioTrade.Price); continue;
Coordinates coordinates = new Coordinates(
portfolioTrade.TradeDate.ToOADate(),
portfolioTrade.Price);
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, tradePointMarker, SizeFactor.Normal); ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, tradePointMarker, SizeFactor.Normal);
} }
if (showTradeLabels) if (!showTradeLabels) return;
// Add the text markers
foreach (var portfolioTrade in portfolioTradesLots)
{ {
// This adds the text markers if (!IsVisible(portfolioTrade.TradeDate))
for (int index = 0; index < portfolioTradesLots.Count; index++) continue;
{
PortfolioTrade portfolioTrade = portfolioTradesLots[index];
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.Append(portfolioTrade.BuySell.Equals("B") ? "Buy " : "Sell "); sb.Append(portfolioTrade.BuySell.Equals("B") ? "Buy " : "Sell ");
sb.Append(Utility.FormatNumber(portfolioTrade.Shares)); sb.Append(Utility.FormatNumber(portfolioTrade.Shares));
sb.Append("@"); sb.Append("@");
sb.Append(Utility.FormatCurrency(portfolioTrade.Price)); sb.Append(Utility.FormatCurrency(portfolioTrade.Price));
Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), FontFactor.FontSize); Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), FontFactor.FontSize);
Coordinates coordinates = new Coordinates(portfolioTrade.TradeDate.ToOADate(),
Coordinates coordinates = new Coordinates(
portfolioTrade.TradeDate.ToOADate(),
portfolioTrade.Price - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC)); portfolioTrade.Price - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
coordinates = textMarkerManager.GetBestMarkerLocation(coordinates.X, coordinates.Y,offsets, offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC)); coordinates = textMarkerManager.GetBestMarkerLocation(
coordinates.X,
coordinates.Y,
offsets,
offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image); ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image);
} }
} }
}
/// <summary> /// <summary>
/// Generate Insider Transactions /// Generate Insider Transactions
@@ -361,8 +392,8 @@ namespace PortfolioManager.Renderers
// get the maximum date in the bollinger band series // get the maximum date in the bollinger band series
DateTime maxBollingerDate = (from BollingerBandElement bollingerBandElement in bollingerBands select bollingerBandElement.Date).Max(); 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 // 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 disposedSummaries = new InsiderTransactionSummaries((from InsiderTransactionSummary insiderTransactionSummary in insiderTransactionSummaries where insiderTransactionSummary.NumberOfSharesAcquiredDisposed < 0 && insiderTransactionSummary.TransactionDate.Date <= maxBollingerDate && IsVisible(insiderTransactionSummary.TransactionDate.Date) select insiderTransactionSummary).ToList());
InsiderTransactionSummaries acquiredSummaries = 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 && IsVisible(insiderTransactionSummary.TransactionDate.Date) select insiderTransactionSummary).ToList());
BinCollection<InsiderTransactionSummary> disposedSummariesBin = BinHelper<InsiderTransactionSummary>.CreateBins(new BinItems<InsiderTransactionSummary>(disposedSummaries), 3); BinCollection<InsiderTransactionSummary> disposedSummariesBin = BinHelper<InsiderTransactionSummary>.CreateBins(new BinItems<InsiderTransactionSummary>(disposedSummaries), 3);
BinCollection<InsiderTransactionSummary> acquiredSummariesBin = BinHelper<InsiderTransactionSummary>.CreateBins(new BinItems<InsiderTransactionSummary>(acquiredSummaries), 3); BinCollection<InsiderTransactionSummary> acquiredSummariesBin = BinHelper<InsiderTransactionSummary>.CreateBins(new BinItems<InsiderTransactionSummary>(acquiredSummaries), 3);

View File

@@ -1,12 +1,6 @@
Type,PortfolioManager.ViewModels.MGSHMomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\MGSH20250331.TXT Type,PortfolioManager.ViewModels.MGSHMomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\MGSH20250331.TXT
Type,PortfolioManager.ViewModels.MomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\MG20180131.TXT Type,PortfolioManager.ViewModels.MomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\MG20180131.TXT
Type,PortfolioManager.ViewModels.CMMomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\CM20191031.TXT Type,PortfolioManager.ViewModels.CMMomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\CM20191031.TXT
Type,PortfolioManager.ViewModels.CMTrendViewModel,PathFileName,C:\boneyard\marketdata\Sessions\CMT20200817.TXT Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,ALHC,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,True,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True
Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,SPOT,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,False,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True Type,PortfolioManager.ViewModels.CMTrendViewModel,PathFileName,C:\boneyard\marketdata\bin\Debug\saferun\CMT20200817.TXT
Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,GWRE,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,True,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,HWM,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,False,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True,StopHistoryCount,5,StopHistory_0,Symbol=HWM|StopPrice=176.71|Shares=0|StopType=Stop Quote|EffectiveDate=11/17/2025,StopHistory_1,Symbol=HWM|StopPrice=186.778498840332|Shares=0|StopType=Stop Quote|EffectiveDate=11/26/2025,StopHistory_2,Symbol=HWM|StopPrice=193.123001594543|Shares=0|StopType=Stop Quote|EffectiveDate=12/26/2025,StopHistory_3,Symbol=HWM|StopPrice=197.56385799408|Shares=0|StopType=Stop Quote|EffectiveDate=1/26/2026,StopHistory_4,Symbol=HWM|StopPrice=235.233001537323|Shares=0|StopType=Stop Quote|EffectiveDate=2/25/2026
Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,CRS,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,True,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True
Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,T,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,True,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True
Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,NRG,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,False,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True
Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,PSO,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,True,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True
Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,PARR,SelectedWatchList,Valuations,SelectedDayCount,180,SyncTradeToBand,False,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True
Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol,SH,SelectedWatchList,Valuations,SelectedDayCount,90,SyncTradeToBand,False,ShowTradeLabels,True,UseLeastSquaresFit,True,ShowInsiderTransactions,True