Files
Avalonia/PortfolioManager/Renderers/BollingerBandRenderer.cs
2025-07-16 20:43:53 -04:00

819 lines
37 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Linq;
using Eremex.AvaloniaUI.Charts;
using MarketData.DataAccess;
using MarketData.Generator;
using MarketData.MarketDataModel;
using MarketData.Utils;
using PortfolioManager.Cache;
using PortfolioManager.DataSeriesViewModels;
using PortfolioManager.Models;
using PortfolioManager.ViewModels;
using ScottPlot;
using ScottPlot.Avalonia;
using ScottPlot.Plottables;
using SkiaSharp;
using MarketData.Numerical;
using MarketData;
using Avalonia.Controls.Platform;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace PortfolioManager.Renderers
{
// ********************************************************************************************************************************
// public class TextPlot
// {
// public TextPlot()
// {
// }
// public TextPlot(String markerText,SKRect boundingRect,Pixel screenCoordinates)
// {
// BoundingRect = boundingRect;
// ScreenCoordinates = screenCoordinates;
// MarkerText = markerText;
// }
// public Pixel ScreenCoordinates { get; set; }
// public SKRect BoundingRect { get; set; }
// public String MarkerText { get; set; }
// }
// public class TextPlots : List<TextPlot>
// {
// public TextPlots()
// {
// }
// /// <summary>
// /// The coordinate system is based on (0,0) in the upper left
// /// </summary>
// /// <param name="coordinates"></param>
// /// <returns></returns>
// public bool PointInRects(String markerText,Pixel screenCoordinates,ref Pixel adjustedScreenCoordinates,float factor)
// {
// MDTrace.WriteLine(LogLevel.DEBUG,$"Searching for rectangles in X:{screenCoordinates.X}Y:{screenCoordinates.Y}");
// foreach(TextPlot textPlot in this)
// {
// if(Math.Round(screenCoordinates.X,2) >= Math.Round(textPlot.BoundingRect.Left,2) &&
// Math.Round(screenCoordinates.X,2) <= Math.Round(textPlot.BoundingRect.Right,2) &&
// Math.Round(screenCoordinates.Y,2) >= Math.Round(textPlot.BoundingRect.Top,2) &&
// Math.Round(screenCoordinates.Y,2) <= Math.Round(textPlot.BoundingRect.Bottom,2))
// {
// MDTrace.WriteLine(LogLevel.DEBUG,$"The text {markerText} at Point:X:{screenCoordinates.X}:Y:{screenCoordinates.Y} is within Rect:L({textPlot.BoundingRect.Left}),T({textPlot.BoundingRect.Top}),R({textPlot.BoundingRect.Right}),B({textPlot.BoundingRect.Bottom}) with text {textPlot.MarkerText} which is plotted at Coordinates:X:{textPlot.ScreenCoordinates.X} Y:{textPlot.ScreenCoordinates.Y}");
// SKRect lowestRect = FindLowestAdjacentRect(textPlot.BoundingRect);
// adjustedScreenCoordinates.Y = textPlot.ScreenCoordinates.Y - factor;
// adjustedScreenCoordinates.X = screenCoordinates.X;
// return true;
// }
// }
// return false;
// }
// public void Summary()
// {
// MDTrace.WriteLine(LogLevel.DEBUG,$"TextPlots:{Count}");
// foreach(TextPlot textPlot in this)
// {
// MDTrace.WriteLine(LogLevel.DEBUG,$"Rect:L({textPlot.BoundingRect.Left}),T({textPlot.BoundingRect.Top}),R({textPlot.BoundingRect.Right}),B({textPlot.BoundingRect.Bottom}) with text {textPlot.MarkerText} is plotted at Coordinates:X:{textPlot.ScreenCoordinates.X} Y:{textPlot.ScreenCoordinates.Y}");
// }
// }
// /// <summary>
// /// Find the Rectangle with the highest Bottom that lies within the X-plane
// /// </summary>
// /// <returns></returns>
// private SKRect FindLowestAdjacentRect(SKRect rect)
// {
// List<SKRect> adjacentRects = this.Where(x=> rect.Left >= x.BoundingRect.Left && rect.Right <= x.BoundingRect.Right).Select(x=>x.BoundingRect).ToList();
// if(null == adjacentRects || 0==adjacentRects.Count)return new SKRect(){Left=0,Top=0,Right=0,Bottom=0};
// adjacentRects = adjacentRects.OrderByDescending(x=>x.Bottom).ToList();
// return adjacentRects[0];
// }
// }
public class MarkerItem
{
public MarkerItem(double markerDate, double markerPrice)
{
MarkerDate = markerDate;
MarkerPrice = markerPrice;
}
public double MarkerDate { get; set; }
public double MarkerPrice { get; set; }
}
public class TextMarkerManager : List<MarkerItem>
{
private DateGenerator dateGenerator = new DateGenerator();
private const double DATE_SPREAD_PCNT = 10.0; // PERCENT
private const double PRICE_SPREAD_PCNT = 10.0; // PERCENT
public Coordinates GetBestMarkerLocation(double markerDate, double markerPrice,OffsetDictionary offsetDictionary,double verticalAdjustmentFactor)
{
if(!IsOverlapped(markerDate,markerPrice,offsetDictionary))
{
Add(new MarkerItem(markerDate, markerPrice));
return new Coordinates(){X=markerDate,Y=markerPrice};
}
Add(new MarkerItem(markerDate, markerPrice-verticalAdjustmentFactor));
return new Coordinates(){X=markerDate,Y=markerPrice-verticalAdjustmentFactor};
}
private bool IsOverlapped(double markerDate, double markerPrice,OffsetDictionary offsetDictionary)
{
foreach(MarkerItem markerItem in this)
{
double markerItemDate = markerItem.MarkerDate;
double markerItemPrice = markerItem.MarkerPrice;
// double minDate = markerItemDate/(1.00+DATE_SPREAD_PCNT/100.0);
// double maxDate = markerItemDate*(1.00+DATE_SPREAD_PCNT/100.0);
// double minPrice = markerItemPrice/(1.00+PRICE_SPREAD_PCNT/100.0);
// double maxPrice = markerItemPrice*(1.00+PRICE_SPREAD_PCNT/100.0);
double minDate = markerItemDate - offsetDictionary.HorizontalSpread*(DATE_SPREAD_PCNT/100.0);
double maxDate = markerItemDate + offsetDictionary.HorizontalSpread*(DATE_SPREAD_PCNT/100.0);
double minPrice = markerItemPrice - offsetDictionary.VerticalSpread*(PRICE_SPREAD_PCNT/100.0);
double maxPrice = markerItemPrice + offsetDictionary.VerticalSpread*(PRICE_SPREAD_PCNT/100.0);
if(markerDate>=minDate && markerDate<=maxDate && markerPrice >=minPrice && markerPrice<=maxPrice)
{
return true;
}
}
return false;
}
}
// *********************************************************************************************************************************************
public class BollingerBandRenderer : ModelBase
{
private String selectedSymbol = default;
private int selectedDayCount = int.MinValue;
private Price latestPrice = default;
private Price zeroPrice = default;
private bool showMarkers = true;
private bool showLegend = false;
private bool showTradeLabels = true;
private bool showInsiderTransactions = true;
private bool showLeastSquares = true;
private bool syncTradeToBand = true;
private StopLimit stopLimit = default;
private StopLimits stopLimits = default;
private PortfolioTrades portfolioTrades = default;
private PortfolioTrades portfolioTradesLots = default;
private Prices prices = default;
private BollingerBands bollingerBands;
private InsiderTransactionSummaries insiderTransactionSummaries = null;
// private TextPlots textPlots = new TextPlots();
private TextMarkerManager textMarkerManager = new TextMarkerManager();
private OffsetDictionary offsets = new OffsetDictionary();
public BollingerBandRenderer(AvaPlot plotter)
{
Plotter = plotter ?? throw new ArgumentNullException(nameof(plotter));
PropertyChanged += OnBollingerBandRendererPropertyChanged;
}
private void OnBollingerBandRendererPropertyChanged(Object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName.Equals("ShowLegend"))
{
if (!ShowLegend) Plotter.Plot.HideLegend();
else Plotter.Plot.ShowLegend();
}
else if (eventArgs.PropertyName.Equals("ShowLegend"))
{
if (!ShowLegend) Plotter.Plot.HideLegend();
else Plotter.Plot.ShowLegend();
}
}
public void Render()
{
lock(Plotter.Plot.Sync)
{
MDTrace.WriteLine(LogLevel.DEBUG,$"[Render] ENTER");
Plotter.Plot.Axes.Left.TickGenerator = new ScottPlot.TickGenerators.NumericAutomatic()
{
LabelFormatter = (double value) => value.ToString("C") // "C" format specifier formats as currency
};
Plotter.Plot.Axes.DateTimeTicksBottom();
Plotter.Plot.Axes.AutoScale();
Plotter.Plot.XLabel("Date");
Plotter.Plot.YLabel("Price");
if (!ShowLegend) Plotter.Plot.HideLegend();
else Plotter.Plot.ShowLegend();
Plotter.Refresh();
MDTrace.WriteLine(LogLevel.DEBUG,$"[Render] LEAVE");
}
}
public void SetData(String selectedSymbol, int selectedDayCount)
{
lock(Plotter.Plot.Sync)
{
MDTrace.WriteLine(LogLevel.DEBUG,$"[SetData] ENTER");
this.selectedSymbol = selectedSymbol;
this.selectedDayCount = selectedDayCount;
stopLimit = PortfolioDA.GetStopLimit(selectedSymbol);
portfolioTrades = PortfolioDA.GetTradesSymbol(selectedSymbol);
portfolioTradesLots = LotAggregator.CombineLots(portfolioTrades);
Plotter.Plot.Clear();
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);
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);
}
}
bollingerBands = BollingerBandGenerator.GenerateBollingerBands(prices);
// textPlots.Clear();
textMarkerManager.Clear();
CalculateOffsets();
GenerateBollingerBands();
GenerateLeastSquares();
GenerateInsiderTransactions();
GenerateStopLimits();
GenerateTradePoints();
GenerateZeroPoint(zeroPrice);
// textPlots.Summary();
MDTrace.WriteLine(LogLevel.DEBUG,$"[SetData] LEAVE");
}
}
/// <summary>
/// These offsets are used to place markers relative to the area in which the graph occupies.
/// </summary>
private void CalculateOffsets()
{
offsets.MaxBollingerDate = bollingerBands.Max(x => x.Date).ToOADate();
offsets.MinBollingerDate = bollingerBands.Min(x => x.Date).ToOADate();
offsets.MaxBollingerValue = bollingerBands.Max(x => x.K);
offsets.MinBollingerValue = bollingerBands.Min(x => x.L);
offsets.Add(OffsetDictionary.OffsetType.MaxBollingerDate,offsets.MaxBollingerDate);
offsets.Add(OffsetDictionary.OffsetType.MinBollingerDate,offsets.MinBollingerDate);
offsets.Add(OffsetDictionary.OffsetType.MaxBollingerValue,offsets.MaxBollingerValue);
offsets.Add(OffsetDictionary.OffsetType.MinBollingerValue,offsets.MinBollingerValue);
offsets.HorizontalSpread = (offsets.MaxBollingerDate - offsets.MinBollingerDate);
offsets.VerticalSpread = (offsets.MaxBollingerValue - offsets.MinBollingerValue);
offsets.Add(OffsetDictionary.OffsetType.HorizontalOffset1PC,offsets.HorizontalSpread * .01);
offsets.Add(OffsetDictionary.OffsetType.HorizontalOffset3PC,offsets.HorizontalSpread * .03);
offsets.Add(OffsetDictionary.OffsetType.HorizontalOffset5PC,offsets.HorizontalSpread * .05);
offsets.Add(OffsetDictionary.OffsetType.VerticalOffset1PC,offsets.VerticalSpread * .01);
offsets.Add(OffsetDictionary.OffsetType.VerticalOffset3PC,offsets.VerticalSpread *.03);
offsets.Add(OffsetDictionary.OffsetType.VerticalOffset5PC,offsets.VerticalSpread * .05);
offsets.Add(OffsetDictionary.OffsetType.VerticalOffset6PC,offsets.VerticalSpread * .06);
offsets.Add(OffsetDictionary.OffsetType.VerticalOffset6P5PC,offsets.VerticalSpread * .065);
offsets.Add(OffsetDictionary.OffsetType.VerticalOffset7PC,offsets.VerticalSpread * .07);
offsets.Add(OffsetDictionary.OffsetType.VerticalOffset10PC,offsets.VerticalSpread * .10);
offsets.Add(OffsetDictionary.OffsetType.VerticalOffset15PC,offsets.VerticalSpread * .15);
}
/// <summary>
/// Generate LeastSquares line
/// </summary>
private void GenerateLeastSquares()
{
if (null == bollingerBands || !showLeastSquares) return;
LeastSquares = BollingerBandModel.LeastSquares(bollingerBands);
Scatter scatter = default;
{
(DateTime[] dates, double[] values) = LeastSquares.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Orange));
scatter.LegendText = "LeastSquares";
scatter.LineWidth = 3;
}
}
/// <summary>
/// Generate the ZeroPoint marker and text
/// </summary>
/// <param name="zeroPrice"></param>
private void GenerateZeroPoint(Price zeroPrice)
{
if (!ShowMarkers || null == zeroPrice) return;
ImageMarker imageMarker = default;
Coordinates coordinates = default;
Image image = default;
ZeroPoint = GainLossModel.Price(zeroPrice);
(DateTime[] dates, double[] values) = ZeroPoint.ToXYData(); // There is only a single value in this collection
// Place the triangle marker
image = TextMarkerImageGenerator.ToSPImage(ImageCache.GetInstance().GetImage(ImageCache.ImageType.BlueTriangleUp));
coordinates = new Coordinates(dates[0].ToOADate(), values[0]);
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image, SizeFactor.Normal);
if(!showTradeLabels)return;
// Place the text marker
StringBuilder sb = new StringBuilder();
sb.Append("Even ");
sb.Append(Utility.FormatCurrency(zeroPrice.Close));
double parityOffsetPercent = (latestPrice.Close - zeroPrice.Close) / zeroPrice.Close;
sb.Append("(").Append(parityOffsetPercent < 0 ? "" : "+").Append(Utility.FormatPercent(parityOffsetPercent)).Append(")");
image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), FontFactor.FontSize);
coordinates = new Coordinates(dates[0].ToOADate() - offsets.Offset(OffsetDictionary.OffsetType.HorizontalOffset3PC),
values[0] - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
coordinates = textMarkerManager.GetBestMarkerLocation(coordinates.X, coordinates.Y, offsets, offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
// Pixel screenCoordinates = Plotter.Plot.GetPixel(coordinates);
// Pixel adjScreenCoordinates = Plotter.Plot.GetPixel(coordinates);
// if(textPlots.PointInRects(sb.ToString(),screenCoordinates,ref adjScreenCoordinates,(float)offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC)))
// {
// coordinates.Y-=offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC);
// }
// screenCoordinates = Plotter.Plot.GetPixel(coordinates);
// SKRect markerRect = new SKRect(){Left=(float)screenCoordinates.X,Top=(float)screenCoordinates.Y,Right=(float)screenCoordinates.X+image.Width,Bottom=(float)screenCoordinates.Y+image.Height};
// textPlots.Add(new TextPlot(sb.ToString(),markerRect,screenCoordinates));
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image);
}
/// <summary>
/// Generate Stop Limits
/// </summary>
private void GenerateStopLimits()
{
if (null == stopLimits && null == zeroPrice) return;
if (null != stopLimits)
{
StopLimits = StopLimitCompositeModel.CreateCompositeDataSource(stopLimits);
}
else if (null != stopLimit && null != zeroPrice)
{
StopLimits = GainLossModel.CreateCompositeDataSource(zeroPrice.Date, stopLimit.StopPrice);
}
(DateTime[] dates, double[] values) = StopLimits.ToXYData();
// Add the image markers
Image imageStopLimitMarker = TextMarkerImageGenerator.ToSPImage(ImageCache.GetInstance().GetImage(ImageCache.ImageType.RedTriangleUp));
for (int index = 0; index < dates.Length; index++)
{
DateTime date = dates[index];
double value = values[index];
Coordinates coordinates = new Coordinates(date.ToOADate(), value);
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, imageStopLimitMarker, SizeFactor.Normal);
}
if(!showTradeLabels)return;
// Add the text marker
if (null != stopLimits)
{
for (int index = 0; index < stopLimits.Count; index++)
{
StopLimit limit = stopLimits[index];
StringBuilder sb = new StringBuilder();
sb.Append(limit.StopType).Append(" ");
sb.Append(Utility.FormatCurrency(limit.StopPrice));
if (index == stopLimits.Count - 1)
{
Price latestPrice = prices[0];
double percentOffsetFromLow = ((latestPrice.Low - limit.StopPrice) / limit.StopPrice);
sb.Append(" (").Append(percentOffsetFromLow > 0 ? "+" : "").Append(Utility.FormatPercent(percentOffsetFromLow)).Append(")");
}
Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), FontFactor.FontSize);
Coordinates coordinates = new Coordinates(limit.EffectiveDate.ToOADate(),
limit.StopPrice - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
coordinates = textMarkerManager.GetBestMarkerLocation(coordinates.X, coordinates.Y,offsets, offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
// Pixel screenCoordinates = Plotter.Plot.GetPixel(coordinates);
// Pixel adjScreenCoordinates = Plotter.Plot.GetPixel(coordinates);
// if(textPlots.PointInRects(sb.ToString(),screenCoordinates,ref adjScreenCoordinates,(float)offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC)))
// {
// coordinates.Y-=offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC);
// }
// screenCoordinates = Plotter.Plot.GetPixel(coordinates);
// SKRect markerRect = new SKRect(){Left=(float)screenCoordinates.X,Top=(float)screenCoordinates.Y,Right=(float)screenCoordinates.X+image.Width,Bottom=(float)screenCoordinates.Y+image.Height};
// textPlots.Add(new TextPlot(sb.ToString(),markerRect,screenCoordinates));
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image);
}
}
else
{
if (null == zeroPrice) return;
if (null == stopLimit || null == zeroPrice) return;
Price latestPrice = prices[0];
double percentOffsetFromLow = ((latestPrice.Low - stopLimit.StopPrice) / stopLimit.StopPrice);
StringBuilder sb = new StringBuilder();
sb.Append(stopLimit.StopType).Append(" ");
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));
// Pixel screenCoordinates = Plotter.Plot.GetPixel(coordinates);
// Pixel adjScreenCoordinates = Plotter.Plot.GetPixel(coordinates);
// if(textPlots.PointInRects(sb.ToString(),screenCoordinates,ref adjScreenCoordinates,(float)offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC)))
// {
// coordinates.Y-=offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC);
// }
// screenCoordinates = Plotter.Plot.GetPixel(coordinates);
// SKRect markerRect = new SKRect(){Left=(float)screenCoordinates.X,Top=(float)screenCoordinates.Y,Right=(float)screenCoordinates.X+image.Width,Bottom=(float)screenCoordinates.Y+image.Height};
// textPlots.Add(new TextPlot(sb.ToString(),markerRect,screenCoordinates));
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image);
}
}
/// <summary>
/// Generate Trade Points
/// </summary>
private void GenerateTradePoints()
{
if (null == portfolioTradesLots || 0 == portfolioTradesLots.Count) 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++)
{
PortfolioTrade portfolioTrade = portfolioTradesLots[index];
Coordinates coordinates = new Coordinates(portfolioTrade.TradeDate.ToOADate(), portfolioTrade.Price);
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, tradePointMarker, SizeFactor.Normal);
}
if (showTradeLabels)
{
// This adds the text markers
for (int index = 0; index < portfolioTradesLots.Count; index++)
{
PortfolioTrade portfolioTrade = portfolioTradesLots[index];
StringBuilder sb = new StringBuilder();
sb.Append(portfolioTrade.BuySell.Equals("B") ? "Buy " : "Sell ");
sb.Append(Utility.FormatNumber(portfolioTrade.Shares));
sb.Append("@");
sb.Append(Utility.FormatCurrency(portfolioTrade.Price));
Image image = TextMarkerImageGenerator.GenerateImage(sb.ToString(), FontFactor.FontSize);
Coordinates coordinates = new Coordinates(portfolioTrade.TradeDate.ToOADate(),
portfolioTrade.Price - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
coordinates = textMarkerManager.GetBestMarkerLocation(coordinates.X, coordinates.Y,offsets, offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC));
// Pixel screenCoordinates = Plotter.Plot.GetPixel(coordinates);
// Pixel adjScreenCoordinates = Plotter.Plot.GetPixel(coordinates);
// if(textPlots.PointInRects(sb.ToString(),screenCoordinates,ref adjScreenCoordinates,(float)offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC)))
// {
// coordinates.Y-=offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset6P5PC);
// }
// screenCoordinates = Plotter.Plot.GetPixel(coordinates);
// SKRect markerRect = new SKRect(){Left=(float)screenCoordinates.X,Top=(float)screenCoordinates.Y,Right=(float)screenCoordinates.X+image.Width,Bottom=(float)screenCoordinates.Y+image.Height};
// textPlots.Add(new TextPlot(sb.ToString(),markerRect,screenCoordinates));
ImageMarker imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, image);
}
}
}
/// <summary>
/// Generate Insider Transactions
/// </summary>
private void GenerateInsiderTransactions()
{
if (null == prices || 0 == prices.Count || !ShowInsiderTransactions) return;
ImageMarker imageMarker = default;
Coordinates coordinates = default;
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);
InsiderTransactionPointDisposedSmall = InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(disposedSummariesBin[2]), minClose);
InsiderTransactionPointDisposedMedium = InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(disposedSummariesBin[1]), minClose);
InsiderTransactionPointDisposedLarge = InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(disposedSummariesBin[0]), minClose);
InsiderTransactionPointAcquiredSmall = InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(acquiredSummariesBin[0]), minClose);
InsiderTransactionPointAcquiredMedium = InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(acquiredSummariesBin[1]), minClose);
InsiderTransactionPointAcquiredLarge = InsiderTransactionModel.InsiderTransactionSummaries(new InsiderTransactionSummaries(acquiredSummariesBin[2]), minClose);
Image imageDisposed = TextMarkerImageGenerator.ToSPImage(ImageCache.GetInstance().GetImage(ImageCache.ImageType.RedTriangleDown));
Image imageAcquired = TextMarkerImageGenerator.ToSPImage(ImageCache.GetInstance().GetImage(ImageCache.ImageType.GreenTriangleUp));
// Disposed
{
(DateTime[] dates, double[] values) = InsiderTransactionPointDisposedSmall.ToXYData();
for (int index = 0; index < dates.Length; index++)
{
DateTime date = dates[index];
double value = values[index];
coordinates = new Coordinates(date.ToOADate(),offsets.Offset(OffsetDictionary.OffsetType.MinBollingerValue) - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset15PC));
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, imageDisposed, SizeFactor.Small);
}
}
{
(DateTime[] dates, double[] values) = InsiderTransactionPointDisposedMedium.ToXYData();
for (int index = 0; index < dates.Length; index++)
{
DateTime date = dates[index];
double value = values[index];
coordinates = new Coordinates(date.ToOADate(),offsets.Offset(OffsetDictionary.OffsetType.MinBollingerValue) - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset15PC));
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, imageDisposed, SizeFactor.Normal);
}
}
{
(DateTime[] dates, double[] values) = InsiderTransactionPointDisposedLarge.ToXYData();
for (int index = 0; index < dates.Length; index++)
{
DateTime date = dates[index];
double value = values[index];
coordinates = new Coordinates(date.ToOADate(),offsets.Offset(OffsetDictionary.OffsetType.MinBollingerValue) - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset15PC));
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, imageDisposed, SizeFactor.Large);
}
}
// Acquired
{
(DateTime[] dates, double[] values) = InsiderTransactionPointAcquiredSmall.ToXYData();
for (int index = 0; index < dates.Length; index++)
{
DateTime date = dates[index];
double value = values[index];
coordinates = new Coordinates(date.ToOADate(),offsets.Offset(OffsetDictionary.OffsetType.MinBollingerValue) - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset15PC));
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, imageAcquired, SizeFactor.Small);
}
}
{
(DateTime[] dates, double[] values) = InsiderTransactionPointAcquiredMedium.ToXYData();
for (int index = 0; index < dates.Length; index++)
{
DateTime date = dates[index];
double value = values[index];
coordinates = new Coordinates(date.ToOADate(),offsets.Offset(OffsetDictionary.OffsetType.MinBollingerValue) - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset15PC));
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, imageAcquired, SizeFactor.Normal);
}
}
{
(DateTime[] dates, double[] values) = InsiderTransactionPointAcquiredLarge.ToXYData();
for (int index = 0; index < dates.Length; index++)
{
DateTime date = dates[index];
double value = values[index];
coordinates = new Coordinates(date.ToOADate(),offsets.Offset(OffsetDictionary.OffsetType.MinBollingerValue) - offsets.Offset(OffsetDictionary.OffsetType.VerticalOffset15PC));
imageMarker = Plotter.Plot.Add.ImageMarker(coordinates, imageAcquired, SizeFactor.Large);
}
}
}
/// <summary>
/// Generate Bollinger Bands
/// </summary>
private void GenerateBollingerBands()
{
K = BollingerBandModel.K(bollingerBands);
KL1 = BollingerBandModel.KL1(bollingerBands);
L = BollingerBandModel.L(bollingerBands);
LP1 = BollingerBandModel.LP1(bollingerBands);
High = BollingerBandModel.High(bollingerBands);
Low = BollingerBandModel.Low(bollingerBands);
Close = BollingerBandModel.Close(bollingerBands);
SMAN = BollingerBandModel.SMAN(bollingerBands);
// Volume = BollingerBandModel.Volume(bollingerBands);
Scatter scatter = default;
{
(DateTime[] dates, double[] values) = K.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Green));
scatter.LegendText = "K";
scatter.LineWidth = 3;
}
{
(DateTime[] dates, double[] values) = KL1.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Green));
scatter.LegendText = "KL1";
scatter.LineWidth = 2;
}
{
(DateTime[] dates, double[] values) = L.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Green));
scatter.LegendText = "L";
scatter.LineWidth = 3;
}
{
(DateTime[] dates, double[] values) = LP1.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Green));
scatter.LegendText = "LP1";
scatter.LineWidth = 2;
}
{
(DateTime[] dates, double[] values) = High.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Blue));
scatter.LegendText = "High";
scatter.LineWidth = 2;
}
{
(DateTime[] dates, double[] values) = Low.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Red));
scatter.LegendText = "Low";
scatter.LineWidth = 2;
}
{
(DateTime[] dates, double[] values) = Close.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Black));
scatter.LegendText = "Close";
scatter.LineWidth = 2;
}
{
(DateTime[] dates, double[] values) = SMAN.ToXYData();
scatter = Plotter.Plot.Add.ScatterLine(dates, values, ScottPlot.Color.FromSKColor(SKColors.Purple));
scatter.LegendText = "SMAN";
scatter.LineWidth = 2;
}
}
// *********************************************************** P R O P E R T I E S *****************************************************
public StopLimits ExternalStopLimits
{
get
{
return stopLimits;
}
set
{
stopLimits = value;
base.OnPropertyChanged("ExternalStopLimits");
}
}
public Prices Prices
{
get
{
return prices;
}
}
public bool SyncTradeToBand
{
get
{
return syncTradeToBand;
}
set
{
syncTradeToBand = value;
base.OnPropertyChanged("SyncTradeToBand");
}
}
public bool ShowTradeLabels
{
get
{
return showTradeLabels;
}
set
{
showTradeLabels = value;
base.OnPropertyChanged("ShowTradeLabels");
}
}
public bool ShowMarkers
{
get
{
return showMarkers;
}
set
{
showMarkers = value;
base.OnPropertyChanged("ShowMarkers");
}
}
public bool ShowLegend
{
get
{
return showLegend;
}
set
{
showLegend = value;
base.OnPropertyChanged("ShowLegend");
}
}
public bool ShowInsiderTransactions
{
get
{
return showInsiderTransactions;
}
set
{
showInsiderTransactions = value;
base.OnPropertyChanged("ShowInsiderTransactions");
}
}
public bool ShowLeastSquares
{
get
{
return showLeastSquares;
}
set
{
showLeastSquares = value;
base.OnPropertyChanged("ShowLeastSquares");
}
}
public AvaPlot Plotter { get; private set; }
private CompositeDataSource InsiderTransactionPointDisposedSmall { get; set; } = Empty();
private CompositeDataSource InsiderTransactionPointDisposedMedium { get; set; } = Empty();
private CompositeDataSource InsiderTransactionPointDisposedLarge { get; set; } = Empty();
private CompositeDataSource InsiderTransactionPointAcquiredSmall { get; set; } = Empty();
private CompositeDataSource InsiderTransactionPointAcquiredMedium { get; set; } = Empty();
private CompositeDataSource InsiderTransactionPointAcquiredLarge { get; set; } = Empty();
private CompositeDataSource K { get; set; } = Empty();
private CompositeDataSource KL1 { get; set; } = Empty();
private CompositeDataSource L { get; set; } = Empty();
private CompositeDataSource LP1 { get; set; } = Empty();
private CompositeDataSource High { get; set; } = Empty();
private CompositeDataSource Low { get; set; } = Empty();
private CompositeDataSource Close { get; set; } = Empty();
private CompositeDataSource SMAN { get; set; } = Empty();
private CompositeDataSource Volume { get; set; } = Empty();
private CompositeDataSource LeastSquares { get; set; } = Empty();
private CompositeDataSource ZeroPoint { get; set; } = Empty();
private CompositeDataSource StopLimits { get; set; } = Empty();
private static CompositeDataSource Empty()
{
return new CompositeDataSource()
{
DataAdapter = new SortedDateTimeDataAdapter()
};
}
}
}