mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-16 17:48:05 +00:00
refactoring
This commit is contained in:
@@ -242,6 +242,9 @@ public class IndicatorExtensionsTests
|
||||
|
||||
// Test without cold values
|
||||
IndicatorExtensions.PaintSmoothCurve(indicator, args, series, warmupPeriod: 5, showColdValues: false);
|
||||
|
||||
// Test PaintLine
|
||||
IndicatorExtensions.PaintLine(indicator, args, series, warmupPeriod: 5, showColdValues: true);
|
||||
}
|
||||
|
||||
// Test PaintHistogram with Positive and Negative values
|
||||
|
||||
+446
-287
@@ -1,287 +1,446 @@
|
||||
using TradingPlatform.BusinessLayer;
|
||||
using TradingPlatform.BusinessLayer.Chart;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
public enum SourceType
|
||||
{
|
||||
Open, High, Low, Close, HL2, OC2, OHL3, HLC3, OHLC4, HLCC4
|
||||
}
|
||||
|
||||
public enum MaType
|
||||
{
|
||||
Alma, Dema, Dsma, Dwma, Ema, Epma, Frama, Fwma, Gma, Hma, Hwma, Jma, Kama, Maaf, Mgdi, MMa, Pwma, Rema, Rma, Sinema, Sma, Smma, T3, Tema, Trima, Vidya, Wma, Zlema
|
||||
}
|
||||
|
||||
public static class IndicatorExtensions
|
||||
{
|
||||
public static readonly Color Averages = Color.FromArgb(255, 255, 128); // #FFFF80 - Yellow
|
||||
public static readonly Color Volume = Color.FromArgb(128, 255, 128); // #80FF80 - Green
|
||||
public static readonly Color Volatility = Color.FromArgb(255, 128, 128); // #FF8080 - Red
|
||||
public static readonly Color Statistics = Color.FromArgb(128, 128, 255); // #8080FF - Blue
|
||||
public static readonly Color Oscillators = Color.FromArgb(255, 128, 255); // #FF80FF - Magenta
|
||||
public static readonly Color Momentum = Color.FromArgb(128, 255, 255); // #80FFFF - Cyan
|
||||
public static readonly Color Experiments = Color.FromArgb(255, 165, 0); // #FFA500 - Orange
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class DataSourceInputAttribute : InputParameterAttribute
|
||||
{
|
||||
public DataSourceInputAttribute(string label = "Data source", int sortIndex = 20)
|
||||
: base(label, sortIndex, variants: new object[]
|
||||
{
|
||||
"Open", SourceType.Open,
|
||||
"High", SourceType.High,
|
||||
"Low", SourceType.Low,
|
||||
"Close", SourceType.Close,
|
||||
"HL/2 (Median)", SourceType.HL2,
|
||||
"OC/2 (Midpoint)", SourceType.OC2,
|
||||
"OHL/3 (Mean)", SourceType.OHL3,
|
||||
"HLC/3 (Typical)", SourceType.HLC3,
|
||||
"OHLC/4 (Average)", SourceType.OHLC4,
|
||||
"HLCC/4 (Weighted)", SourceType.HLCC4
|
||||
})
|
||||
{ }
|
||||
}
|
||||
|
||||
public static TValue GetInputValue(this Indicator indicator, UpdateArgs args, SourceType source)
|
||||
{
|
||||
var historicalData = indicator.HistoricalData;
|
||||
TBar bar = new TBar(
|
||||
time: historicalData.Time(),
|
||||
open: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Open],
|
||||
high: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.High],
|
||||
low: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Low],
|
||||
close: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Close],
|
||||
volume: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Volume]
|
||||
);
|
||||
|
||||
double price = source switch
|
||||
{
|
||||
SourceType.Open => bar.Open,
|
||||
SourceType.High => bar.High,
|
||||
SourceType.Low => bar.Low,
|
||||
SourceType.Close => bar.Close,
|
||||
SourceType.HL2 => bar.HL2,
|
||||
SourceType.OC2 => bar.OC2,
|
||||
SourceType.OHL3 => bar.OHL3,
|
||||
SourceType.HLC3 => bar.HLC3,
|
||||
SourceType.OHLC4 => bar.OHLC4,
|
||||
SourceType.HLCC4 => bar.HLCC4,
|
||||
_ => bar.Close
|
||||
};
|
||||
|
||||
return new TValue(bar.Time, price);
|
||||
}
|
||||
|
||||
public static TBar GetInputBar(this Indicator indicator, UpdateArgs args)
|
||||
{
|
||||
var historicalData = indicator.HistoricalData;
|
||||
return new TBar(
|
||||
time: historicalData.Time(),
|
||||
open: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Open],
|
||||
high: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.High],
|
||||
low: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Low],
|
||||
close: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Close],
|
||||
volume: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Volume]
|
||||
);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
public static int GetHLineY(IChartWindowCoordinatesConverter converter, double value)
|
||||
{
|
||||
return (int)converter.GetChartY(value);
|
||||
}
|
||||
|
||||
public static void PaintHLine(this Indicator indicator, PaintChartEventArgs args, double value, Pen pen)
|
||||
{
|
||||
if (indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
|
||||
var converter = mainWindow.CoordinatesConverter;
|
||||
var clientRect = mainWindow.ClientRectangle;
|
||||
|
||||
gr.SetClip(clientRect);
|
||||
int leftX = clientRect.Left;
|
||||
int rightX = clientRect.Right;
|
||||
int Y = GetHLineY(converter, value);
|
||||
|
||||
using (pen)
|
||||
{
|
||||
gr.DrawLine(pen, new Point(leftX, Y), new Point(rightX, Y));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Point> GetSmoothCurvePoints(Indicator indicator, IChartWindowCoordinatesConverter converter, Rectangle clientRect, LineSeries series)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(indicator);
|
||||
ArgumentNullException.ThrowIfNull(converter);
|
||||
var data = indicator.HistoricalData;
|
||||
if (data == null) return new List<Point>();
|
||||
|
||||
var lastTime = data.Time(data.Count - 1);
|
||||
var firstTime = data.Time(0);
|
||||
|
||||
IChartWindowCoordinatesConverter safeConverter = converter!;
|
||||
DateTime tLeft = safeConverter.GetTime(clientRect.Left);
|
||||
DateTime leftTime = tLeft > lastTime ? tLeft : lastTime;
|
||||
|
||||
DateTime tRight = safeConverter.GetTime(clientRect.Right);
|
||||
DateTime rightTime = tRight < firstTime ? tRight : firstTime;
|
||||
|
||||
int leftIndex = (int)data.GetIndexByTime(leftTime.Ticks) + 1;
|
||||
int rightIndex = (int)data.GetIndexByTime(rightTime.Ticks);
|
||||
|
||||
List<Point> allPoints = new List<Point>();
|
||||
|
||||
for (int i = rightIndex; i < leftIndex; i++)
|
||||
{
|
||||
int barX = (int)converter.GetChartX(data.Time(i));
|
||||
int barY = (int)converter.GetChartY(series[i]);
|
||||
int halfBarWidth = indicator.CurrentChart.BarsWidth / 2;
|
||||
Point point = new Point(barX + halfBarWidth, barY);
|
||||
allPoints.Add(point);
|
||||
}
|
||||
return allPoints;
|
||||
}
|
||||
|
||||
public static void PaintSmoothCurve(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true, double tension = 0.2)
|
||||
{
|
||||
if (!series.Visible || indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
gr.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
|
||||
var converter = mainWindow.CoordinatesConverter;
|
||||
var clientRect = mainWindow.ClientRectangle;
|
||||
|
||||
gr.SetClip(clientRect);
|
||||
|
||||
List<Point> allPoints = GetSmoothCurvePoints(indicator, converter, clientRect, series);
|
||||
|
||||
if (allPoints.Count > 1)
|
||||
{
|
||||
DateTime rightTime = new[] { converter.GetTime(clientRect.Right), indicator.HistoricalData.Time(0) }.Min();
|
||||
int rightIndex = (int)indicator.HistoricalData.GetIndexByTime(rightTime.Ticks);
|
||||
|
||||
using Pen defaultPen = new(series.Color, series.Width) { DashStyle = ConvertLineStyleToDashStyle(series.Style) };
|
||||
using Pen coldPen = new(series.Color, series.Width) { DashStyle = DashStyle.Dot };
|
||||
|
||||
int hotCount = (warmupPeriod >= 0) ? (indicator.Count - warmupPeriod - rightIndex) : 0;
|
||||
|
||||
// Draw the hot part
|
||||
if (hotCount > 0)
|
||||
{
|
||||
var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray();
|
||||
gr.DrawCurve(defaultPen, hotPoints, 0, hotPoints.Length - 1, (float)tension);
|
||||
}
|
||||
|
||||
// Draw the cold part
|
||||
if (showColdValues && hotCount < allPoints.Count)
|
||||
{
|
||||
var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray();
|
||||
gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)tension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<(Rectangle Rect, Color Color)> GetHistogramRectangles(Indicator indicator, IChartWindowCoordinatesConverter converter, Rectangle clientRect, LineSeries series)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(indicator);
|
||||
ArgumentNullException.ThrowIfNull(converter);
|
||||
var data = indicator.HistoricalData;
|
||||
if (data == null) return new List<(Rectangle, Color)>();
|
||||
|
||||
var lastTime = data.Time(data.Count - 1);
|
||||
var firstTime = data.Time(0);
|
||||
|
||||
IChartWindowCoordinatesConverter safeConverter = converter!;
|
||||
DateTime tLeft = safeConverter.GetTime(clientRect.Left);
|
||||
DateTime leftTime = tLeft > lastTime ? tLeft : lastTime;
|
||||
|
||||
DateTime tRight = safeConverter.GetTime(clientRect.Right);
|
||||
DateTime rightTime = tRight < firstTime ? tRight : firstTime;
|
||||
|
||||
int leftIndex = (int)data.GetIndexByTime(leftTime.Ticks) + 1;
|
||||
int rightIndex = (int)data.GetIndexByTime(rightTime.Ticks);
|
||||
|
||||
var result = new List<(Rectangle, Color)>();
|
||||
|
||||
for (int i = rightIndex; i < leftIndex; i++)
|
||||
{
|
||||
int barX = (int)converter.GetChartX(data.Time(i));
|
||||
int barY = (int)converter.GetChartY(series[i]);
|
||||
int barY0 = (int)converter.GetChartY(0);
|
||||
int HistBarWidth = indicator.CurrentChart.BarsWidth - 2;
|
||||
|
||||
if (series[i] > 0)
|
||||
{
|
||||
result.Add((new Rectangle(barX, barY, HistBarWidth, Math.Abs(barY - barY0)), Color.FromArgb(150, 0, 255, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add((new Rectangle(barX, barY0, HistBarWidth, Math.Abs(barY0 - barY)), Color.FromArgb(150, 255, 0, 0)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void PaintHistogram(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true)
|
||||
{
|
||||
if (!series.Visible || indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
gr.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
|
||||
var converter = mainWindow.CoordinatesConverter;
|
||||
var clientRect = mainWindow.ClientRectangle;
|
||||
|
||||
gr.SetClip(clientRect);
|
||||
|
||||
var rects = GetHistogramRectangles(indicator, converter, clientRect, series);
|
||||
|
||||
foreach (var (rect, color) in rects)
|
||||
{
|
||||
using (Brush hist = new SolidBrush(color))
|
||||
{
|
||||
gr.FillRectangle(hist, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawText(this Indicator indicator, PaintChartEventArgs args, string text)
|
||||
{
|
||||
if (indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
var clientRect = indicator.CurrentChart.MainWindow.ClientRectangle;
|
||||
Font font = new Font("Inter", 8);
|
||||
SizeF textSize = gr.MeasureString(text, font);
|
||||
RectangleF textRect = new RectangleF(clientRect.Left + 5,
|
||||
clientRect.Bottom - textSize.Height - 10,
|
||||
textSize.Width + 10, textSize.Height + 10);
|
||||
|
||||
gr.FillRectangle(Brushes.DarkBlue, textRect);
|
||||
gr.DrawString(text, font, Brushes.White, new PointF(textRect.X + 6, textRect.Y + 5));
|
||||
}
|
||||
|
||||
private static DashStyle ConvertLineStyleToDashStyle(LineStyle lineStyle)
|
||||
{
|
||||
return lineStyle switch
|
||||
{
|
||||
LineStyle.Solid => DashStyle.Solid,
|
||||
LineStyle.Dash => DashStyle.Dash,
|
||||
LineStyle.Dot => DashStyle.Dot,
|
||||
LineStyle.DashDot => DashStyle.DashDot,
|
||||
_ => DashStyle.Solid,
|
||||
};
|
||||
}
|
||||
}
|
||||
using TradingPlatform.BusinessLayer;
|
||||
using TradingPlatform.BusinessLayer.Chart;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
public enum SourceType
|
||||
{
|
||||
Open, High, Low, Close, HL2, OC2, OHL3, HLC3, OHLC4, HLCC4
|
||||
}
|
||||
|
||||
public enum MaType
|
||||
{
|
||||
Alma, Dema, Dsma, Dwma, Ema, Epma, Frama, Fwma, Gma, Hma, Hwma, Jma, Kama, Maaf, Mgdi, MMa, Pwma, Rema, Rma, Sinema, Sma, Smma, T3, Tema, Trima, Vidya, Wma, Zlema
|
||||
}
|
||||
|
||||
public static class IndicatorExtensions
|
||||
{
|
||||
public static readonly Color Averages = Color.FromArgb(255, 255, 128); // #FFFF80 - Yellow
|
||||
public static readonly Color Volume = Color.FromArgb(128, 255, 128); // #80FF80 - Green
|
||||
public static readonly Color Volatility = Color.FromArgb(255, 128, 128); // #FF8080 - Red
|
||||
public static readonly Color Statistics = Color.FromArgb(128, 128, 255); // #8080FF - Blue
|
||||
public static readonly Color Oscillators = Color.FromArgb(255, 128, 255); // #FF80FF - Magenta
|
||||
public static readonly Color Momentum = Color.FromArgb(128, 255, 255); // #80FFFF - Cyan
|
||||
public static readonly Color Experiments = Color.FromArgb(255, 165, 0); // #FFA500 - Orange
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class DataSourceInputAttribute : InputParameterAttribute
|
||||
{
|
||||
public DataSourceInputAttribute(string label = "Data source", int sortIndex = 20)
|
||||
: base(label, sortIndex, variants: new object[]
|
||||
{
|
||||
"Open", SourceType.Open,
|
||||
"High", SourceType.High,
|
||||
"Low", SourceType.Low,
|
||||
"Close", SourceType.Close,
|
||||
"HL/2 (Median)", SourceType.HL2,
|
||||
"OC/2 (Midpoint)", SourceType.OC2,
|
||||
"OHL/3 (Mean)", SourceType.OHL3,
|
||||
"HLC/3 (Typical)", SourceType.HLC3,
|
||||
"OHLC/4 (Average)", SourceType.OHLC4,
|
||||
"HLCC/4 (Weighted)", SourceType.HLCC4
|
||||
})
|
||||
{ }
|
||||
}
|
||||
|
||||
public static TValue GetInputValue(this Indicator indicator, UpdateArgs args, SourceType source)
|
||||
{
|
||||
var historicalData = indicator.HistoricalData;
|
||||
var item = historicalData[indicator.Count - 1, SeekOriginHistory.Begin];
|
||||
double price = item.GetPrice(source);
|
||||
return new TValue(item.TimeLeft.Ticks, price);
|
||||
}
|
||||
|
||||
public static TBar GetInputBar(this Indicator indicator, UpdateArgs args)
|
||||
{
|
||||
var historicalData = indicator.HistoricalData;
|
||||
return new TBar(
|
||||
time: historicalData.Time(),
|
||||
open: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Open],
|
||||
high: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.High],
|
||||
low: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Low],
|
||||
close: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Close],
|
||||
volume: historicalData[indicator.Count - 1, SeekOriginHistory.Begin][PriceType.Volume]
|
||||
);
|
||||
}
|
||||
|
||||
public static double GetPrice(this IHistoryItem item, SourceType source)
|
||||
{
|
||||
return source switch
|
||||
{
|
||||
SourceType.Open => item[PriceType.Open],
|
||||
SourceType.High => item[PriceType.High],
|
||||
SourceType.Low => item[PriceType.Low],
|
||||
SourceType.Close => item[PriceType.Close],
|
||||
SourceType.HL2 => (item[PriceType.High] + item[PriceType.Low]) * 0.5,
|
||||
SourceType.OC2 => (item[PriceType.Open] + item[PriceType.Close]) * 0.5,
|
||||
SourceType.OHL3 => (item[PriceType.Open] + item[PriceType.High] + item[PriceType.Low]) * 0.333333333333333333,
|
||||
SourceType.HLC3 => (item[PriceType.High] + item[PriceType.Low] + item[PriceType.Close]) * 0.333333333333333333,
|
||||
SourceType.OHLC4 => (item[PriceType.Open] + item[PriceType.High] + item[PriceType.Low] + item[PriceType.Close]) * 0.25,
|
||||
SourceType.HLCC4 => (item[PriceType.High] + item[PriceType.Low] + item[PriceType.Close] + item[PriceType.Close]) * 0.25,
|
||||
_ => item[PriceType.Close]
|
||||
};
|
||||
}
|
||||
|
||||
public static void FillValues(this HistoricalData history, Span<double> destination, SourceType source)
|
||||
{
|
||||
int count = Math.Min(history.Count, destination.Length);
|
||||
|
||||
// Hoist switch to avoid per-iteration branching
|
||||
switch (source)
|
||||
{
|
||||
case SourceType.Open:
|
||||
for (int i = 0; i < count; i++) destination[i] = history[i, SeekOriginHistory.Begin][PriceType.Open];
|
||||
break;
|
||||
case SourceType.High:
|
||||
for (int i = 0; i < count; i++) destination[i] = history[i, SeekOriginHistory.Begin][PriceType.High];
|
||||
break;
|
||||
case SourceType.Low:
|
||||
for (int i = 0; i < count; i++) destination[i] = history[i, SeekOriginHistory.Begin][PriceType.Low];
|
||||
break;
|
||||
case SourceType.Close:
|
||||
for (int i = 0; i < count; i++) destination[i] = history[i, SeekOriginHistory.Begin][PriceType.Close];
|
||||
break;
|
||||
case SourceType.HL2:
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var item = history[i, SeekOriginHistory.Begin];
|
||||
destination[i] = (item[PriceType.High] + item[PriceType.Low]) * 0.5;
|
||||
}
|
||||
break;
|
||||
case SourceType.OC2:
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var item = history[i, SeekOriginHistory.Begin];
|
||||
destination[i] = (item[PriceType.Open] + item[PriceType.Close]) * 0.5;
|
||||
}
|
||||
break;
|
||||
case SourceType.OHL3:
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var item = history[i, SeekOriginHistory.Begin];
|
||||
destination[i] = (item[PriceType.Open] + item[PriceType.High] + item[PriceType.Low]) * 0.333333333333333333;
|
||||
}
|
||||
break;
|
||||
case SourceType.HLC3:
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var item = history[i, SeekOriginHistory.Begin];
|
||||
destination[i] = (item[PriceType.High] + item[PriceType.Low] + item[PriceType.Close]) * 0.333333333333333333;
|
||||
}
|
||||
break;
|
||||
case SourceType.OHLC4:
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var item = history[i, SeekOriginHistory.Begin];
|
||||
destination[i] = (item[PriceType.Open] + item[PriceType.High] + item[PriceType.Low] + item[PriceType.Close]) * 0.25;
|
||||
}
|
||||
break;
|
||||
case SourceType.HLCC4:
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var item = history[i, SeekOriginHistory.Begin];
|
||||
destination[i] = (item[PriceType.High] + item[PriceType.Low] + item[PriceType.Close] + item[PriceType.Close]) * 0.25;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (int i = 0; i < count; i++) destination[i] = history[i, SeekOriginHistory.Begin][PriceType.Close];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValues(this LineSeries series, ReadOnlySpan<double> values)
|
||||
{
|
||||
int count = values.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
series.SetValue(values[i], i, SeekOriginHistory.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
public static int GetHLineY(IChartWindowCoordinatesConverter converter, double value)
|
||||
{
|
||||
return (int)converter.GetChartY(value);
|
||||
}
|
||||
|
||||
public static void PaintHLine(this Indicator indicator, PaintChartEventArgs args, double value, Pen pen)
|
||||
{
|
||||
if (indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
|
||||
var converter = mainWindow.CoordinatesConverter;
|
||||
var clientRect = mainWindow.ClientRectangle;
|
||||
|
||||
gr.SetClip(clientRect);
|
||||
int leftX = clientRect.Left;
|
||||
int rightX = clientRect.Right;
|
||||
int Y = GetHLineY(converter, value);
|
||||
|
||||
using (pen)
|
||||
{
|
||||
gr.DrawLine(pen, new Point(leftX, Y), new Point(rightX, Y));
|
||||
}
|
||||
}
|
||||
|
||||
public static Point[] GetSmoothCurvePoints(Indicator indicator, IChartWindowCoordinatesConverter converter, Rectangle clientRect, LineSeries series)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(indicator);
|
||||
ArgumentNullException.ThrowIfNull(converter);
|
||||
var data = indicator.HistoricalData;
|
||||
if (data == null) return Array.Empty<Point>();
|
||||
|
||||
var lastTime = data.Time(data.Count - 1);
|
||||
var firstTime = data.Time(0);
|
||||
|
||||
IChartWindowCoordinatesConverter safeConverter = converter!;
|
||||
DateTime tLeft = safeConverter.GetTime(clientRect.Left);
|
||||
DateTime leftTime = tLeft > lastTime ? tLeft : lastTime;
|
||||
|
||||
DateTime tRight = safeConverter.GetTime(clientRect.Right);
|
||||
DateTime rightTime = tRight < firstTime ? tRight : firstTime;
|
||||
|
||||
int leftIndex = (int)data.GetIndexByTime(leftTime.Ticks) + 1;
|
||||
int rightIndex = (int)data.GetIndexByTime(rightTime.Ticks);
|
||||
|
||||
int count = leftIndex - rightIndex;
|
||||
if (count <= 0) return Array.Empty<Point>();
|
||||
|
||||
Point[] allPoints = new Point[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int dataIndex = rightIndex + i;
|
||||
int barX = (int)converter.GetChartX(data.Time(dataIndex));
|
||||
int barY = (int)converter.GetChartY(series[dataIndex]);
|
||||
int halfBarWidth = indicator.CurrentChart.BarsWidth / 2;
|
||||
allPoints[i] = new Point(barX + halfBarWidth, barY);
|
||||
}
|
||||
return allPoints;
|
||||
}
|
||||
|
||||
public static void PaintSmoothCurve(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true, double tension = 0.2)
|
||||
{
|
||||
if (!series.Visible || indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
gr.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
|
||||
var converter = mainWindow.CoordinatesConverter;
|
||||
var clientRect = mainWindow.ClientRectangle;
|
||||
|
||||
gr.SetClip(clientRect);
|
||||
|
||||
Point[] allPoints = GetSmoothCurvePoints(indicator, converter, clientRect, series);
|
||||
|
||||
if (allPoints.Length > 1)
|
||||
{
|
||||
DateTime tRight = converter.GetTime(clientRect.Right);
|
||||
DateTime tZero = indicator.HistoricalData.Time(0);
|
||||
DateTime rightTime = tRight < tZero ? tRight : tZero;
|
||||
|
||||
int rightIndex = (int)indicator.HistoricalData.GetIndexByTime(rightTime.Ticks);
|
||||
|
||||
using Pen defaultPen = new(series.Color, series.Width) { DashStyle = ConvertLineStyleToDashStyle(series.Style) };
|
||||
using Pen coldPen = new(series.Color, series.Width) { DashStyle = DashStyle.Dot };
|
||||
|
||||
int hotCount = (warmupPeriod >= 0) ? (indicator.Count - warmupPeriod - rightIndex) : 0;
|
||||
|
||||
// Draw the hot part
|
||||
int hotSegments = Math.Min(hotCount, allPoints.Length - 1);
|
||||
if (hotSegments > 0)
|
||||
{
|
||||
gr.DrawCurve(defaultPen, allPoints, 0, hotSegments, (float)tension);
|
||||
}
|
||||
|
||||
// Draw the cold part
|
||||
if (showColdValues)
|
||||
{
|
||||
int coldStart = Math.Max(0, hotCount);
|
||||
int coldSegments = (allPoints.Length - 1) - coldStart;
|
||||
|
||||
if (coldSegments > 0)
|
||||
{
|
||||
gr.DrawCurve(coldPen, allPoints, coldStart, coldSegments, (float)tension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void PaintLine(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true)
|
||||
{
|
||||
if (!series.Visible || indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
gr.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
|
||||
var converter = mainWindow.CoordinatesConverter;
|
||||
var clientRect = mainWindow.ClientRectangle;
|
||||
|
||||
gr.SetClip(clientRect);
|
||||
|
||||
var data = indicator.HistoricalData;
|
||||
if (data == null) return;
|
||||
|
||||
var lastTime = data.Time(data.Count - 1);
|
||||
var firstTime = data.Time(0);
|
||||
|
||||
IChartWindowCoordinatesConverter safeConverter = converter!;
|
||||
DateTime tLeft = safeConverter.GetTime(clientRect.Left);
|
||||
DateTime leftTime = tLeft > lastTime ? tLeft : lastTime;
|
||||
|
||||
DateTime tRight = safeConverter.GetTime(clientRect.Right);
|
||||
DateTime rightTime = tRight < firstTime ? tRight : firstTime;
|
||||
|
||||
int leftIndex = (int)data.GetIndexByTime(leftTime.Ticks) + 1;
|
||||
int rightIndex = (int)data.GetIndexByTime(rightTime.Ticks);
|
||||
|
||||
int count = leftIndex - rightIndex;
|
||||
if (count <= 0) return;
|
||||
|
||||
// Use ArrayPool to avoid allocations
|
||||
Point[] allPoints = System.Buffers.ArrayPool<Point>.Shared.Rent(count);
|
||||
try
|
||||
{
|
||||
int halfBarWidth = indicator.CurrentChart.BarsWidth / 2;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int dataIndex = rightIndex + i;
|
||||
int barX = (int)converter.GetChartX(data.Time(dataIndex));
|
||||
int barY = (int)converter.GetChartY(series[dataIndex]);
|
||||
allPoints[i] = new Point(barX + halfBarWidth, barY);
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
using Pen defaultPen = new(series.Color, series.Width) { DashStyle = ConvertLineStyleToDashStyle(series.Style) };
|
||||
using Pen coldPen = new(series.Color, series.Width) { DashStyle = DashStyle.Dot };
|
||||
|
||||
int hotCount = (warmupPeriod >= 0) ? (indicator.Count - warmupPeriod - rightIndex) : 0;
|
||||
|
||||
// Draw the hot part
|
||||
int hotSegments = Math.Min(hotCount, count - 1);
|
||||
if (hotSegments > 0)
|
||||
{
|
||||
gr.DrawCurve(defaultPen, allPoints, 0, hotSegments, tension: 0);
|
||||
}
|
||||
|
||||
// Draw the cold part
|
||||
if (showColdValues)
|
||||
{
|
||||
int coldStart = Math.Max(0, hotCount);
|
||||
int coldSegments = (count - 1) - coldStart;
|
||||
|
||||
if (coldSegments > 0)
|
||||
{
|
||||
gr.DrawCurve(coldPen, allPoints, coldStart, coldSegments, tension: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Buffers.ArrayPool<Point>.Shared.Return(allPoints);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<(Rectangle Rect, Color Color)> GetHistogramRectangles(Indicator indicator, IChartWindowCoordinatesConverter converter, Rectangle clientRect, LineSeries series)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(indicator);
|
||||
ArgumentNullException.ThrowIfNull(converter);
|
||||
var data = indicator.HistoricalData;
|
||||
if (data == null) return new List<(Rectangle, Color)>();
|
||||
|
||||
var lastTime = data.Time(data.Count - 1);
|
||||
var firstTime = data.Time(0);
|
||||
|
||||
IChartWindowCoordinatesConverter safeConverter = converter!;
|
||||
DateTime tLeft = safeConverter.GetTime(clientRect.Left);
|
||||
DateTime leftTime = tLeft > lastTime ? tLeft : lastTime;
|
||||
|
||||
DateTime tRight = safeConverter.GetTime(clientRect.Right);
|
||||
DateTime rightTime = tRight < firstTime ? tRight : firstTime;
|
||||
|
||||
int leftIndex = (int)data.GetIndexByTime(leftTime.Ticks) + 1;
|
||||
int rightIndex = (int)data.GetIndexByTime(rightTime.Ticks);
|
||||
|
||||
var result = new List<(Rectangle, Color)>();
|
||||
|
||||
for (int i = rightIndex; i < leftIndex; i++)
|
||||
{
|
||||
int barX = (int)converter.GetChartX(data.Time(i));
|
||||
int barY = (int)converter.GetChartY(series[i]);
|
||||
int barY0 = (int)converter.GetChartY(0);
|
||||
int HistBarWidth = indicator.CurrentChart.BarsWidth - 2;
|
||||
|
||||
if (series[i] > 0)
|
||||
{
|
||||
result.Add((new Rectangle(barX, barY, HistBarWidth, Math.Abs(barY - barY0)), Color.FromArgb(150, 0, 255, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add((new Rectangle(barX, barY0, HistBarWidth, Math.Abs(barY0 - barY)), Color.FromArgb(150, 255, 0, 0)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void PaintHistogram(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true)
|
||||
{
|
||||
if (!series.Visible || indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
gr.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
|
||||
var converter = mainWindow.CoordinatesConverter;
|
||||
var clientRect = mainWindow.ClientRectangle;
|
||||
|
||||
gr.SetClip(clientRect);
|
||||
|
||||
var rects = GetHistogramRectangles(indicator, converter, clientRect, series);
|
||||
|
||||
foreach (var (rect, color) in rects)
|
||||
{
|
||||
using Brush hist = new SolidBrush(color);
|
||||
gr.FillRectangle(hist, rect);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawText(this Indicator indicator, PaintChartEventArgs args, string text)
|
||||
{
|
||||
if (indicator.CurrentChart == null)
|
||||
return;
|
||||
|
||||
Graphics gr = args.Graphics;
|
||||
var clientRect = indicator.CurrentChart.MainWindow.ClientRectangle;
|
||||
var font = new Font("Inter", 8);
|
||||
SizeF textSize = gr.MeasureString(text, font);
|
||||
var textRect = new RectangleF(clientRect.Left + 5,
|
||||
clientRect.Bottom - textSize.Height - 10,
|
||||
textSize.Width + 10, textSize.Height + 10);
|
||||
|
||||
gr.FillRectangle(Brushes.DarkBlue, textRect);
|
||||
gr.DrawString(text, font, Brushes.White, new PointF(textRect.X + 6, textRect.Y + 5));
|
||||
}
|
||||
|
||||
private static DashStyle ConvertLineStyleToDashStyle(LineStyle lineStyle)
|
||||
{
|
||||
return lineStyle switch
|
||||
{
|
||||
LineStyle.Solid => DashStyle.Solid,
|
||||
LineStyle.Dash => DashStyle.Dash,
|
||||
LineStyle.Dot => DashStyle.Dot,
|
||||
LineStyle.DashDot => DashStyle.DashDot,
|
||||
_ => DashStyle.Solid,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,379 +2,371 @@
|
||||
// These are minimal implementations for unit testing purposes only
|
||||
|
||||
using System.Drawing;
|
||||
using TradingPlatform.BusinessLayer.Chart;
|
||||
|
||||
namespace TradingPlatform.BusinessLayer
|
||||
{
|
||||
using TradingPlatform.BusinessLayer.Chart;
|
||||
namespace Chart
|
||||
{
|
||||
/// <summary>
|
||||
/// Coordinates converter interface
|
||||
/// </summary>
|
||||
public interface IChartWindowCoordinatesConverter
|
||||
{
|
||||
DateTime GetTime(int x);
|
||||
double GetChartX(DateTime time);
|
||||
double GetChartY(double value);
|
||||
}
|
||||
}
|
||||
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the style of indicator line.
|
||||
/// </summary>
|
||||
public enum LineStyle
|
||||
{
|
||||
Solid,
|
||||
Dash,
|
||||
Dot,
|
||||
DashDot,
|
||||
Histogramm,
|
||||
Points,
|
||||
Columns,
|
||||
StepLine
|
||||
}
|
||||
/// <summary>
|
||||
/// Specifies the style of indicator line.
|
||||
/// </summary>
|
||||
public enum LineStyle
|
||||
{
|
||||
Solid,
|
||||
Dash,
|
||||
Dot,
|
||||
DashDot,
|
||||
Histogramm,
|
||||
Points,
|
||||
Columns,
|
||||
StepLine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Price data types
|
||||
/// </summary>
|
||||
public enum PriceType
|
||||
{
|
||||
Open,
|
||||
High,
|
||||
Low,
|
||||
Close,
|
||||
Median,
|
||||
Typical,
|
||||
Weighted,
|
||||
Bid,
|
||||
BidSize,
|
||||
Ask,
|
||||
AskSize,
|
||||
Last,
|
||||
Volume,
|
||||
Ticks,
|
||||
AggressorFlag,
|
||||
TickDirection,
|
||||
BidTickDirection,
|
||||
AskTickDirection,
|
||||
OpenInterest,
|
||||
Mark,
|
||||
FundingRate,
|
||||
QuoteAssetVolume
|
||||
}
|
||||
/// <summary>
|
||||
/// Price data types
|
||||
/// </summary>
|
||||
public enum PriceType
|
||||
{
|
||||
Open,
|
||||
High,
|
||||
Low,
|
||||
Close,
|
||||
Median,
|
||||
Typical,
|
||||
Weighted,
|
||||
Bid,
|
||||
BidSize,
|
||||
Ask,
|
||||
AskSize,
|
||||
Last,
|
||||
Volume,
|
||||
Ticks,
|
||||
AggressorFlag,
|
||||
TickDirection,
|
||||
BidTickDirection,
|
||||
AskTickDirection,
|
||||
OpenInterest,
|
||||
Mark,
|
||||
FundingRate,
|
||||
QuoteAssetVolume
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seek origin for historical data
|
||||
/// </summary>
|
||||
public enum SeekOriginHistory
|
||||
{
|
||||
Begin,
|
||||
End
|
||||
}
|
||||
/// <summary>
|
||||
/// Seek origin for historical data
|
||||
/// </summary>
|
||||
public enum SeekOriginHistory
|
||||
{
|
||||
Begin,
|
||||
End
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update reason for indicator
|
||||
/// </summary>
|
||||
public enum UpdateReason
|
||||
{
|
||||
Unknown,
|
||||
HistoricalBar,
|
||||
NewTick,
|
||||
NewBar
|
||||
}
|
||||
/// <summary>
|
||||
/// Update reason for indicator
|
||||
/// </summary>
|
||||
public enum UpdateReason
|
||||
{
|
||||
Unknown,
|
||||
HistoricalBar,
|
||||
NewTick,
|
||||
NewBar
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Indicator line marker icon type
|
||||
/// </summary>
|
||||
public enum IndicatorLineMarkerIconType
|
||||
{
|
||||
None,
|
||||
Point,
|
||||
Circle,
|
||||
Square,
|
||||
Diamond,
|
||||
Triangle,
|
||||
TriangleDown,
|
||||
Cross,
|
||||
Plus,
|
||||
Star,
|
||||
Flag,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ArrowRight
|
||||
}
|
||||
|
||||
#region Attributes
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for input parameters
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class InputParameterAttribute : Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
public int SortIndex { get; }
|
||||
public double Minimum { get; }
|
||||
public double Maximum { get; }
|
||||
public double Increment { get; }
|
||||
public int DecimalPlaces { get; }
|
||||
public IComparable[]? Variants { get; }
|
||||
#region Attributes
|
||||
|
||||
public InputParameterAttribute(
|
||||
/// <summary>
|
||||
/// Attribute for input parameters
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class InputParameterAttribute(
|
||||
string name = "",
|
||||
int sortIndex = 0,
|
||||
double minimum = int.MinValue,
|
||||
double maximum = int.MaxValue,
|
||||
double increment = 0.01,
|
||||
int decimalPlaces = 2,
|
||||
object[]? variants = null)
|
||||
object[]? variants = null) : Attribute
|
||||
{
|
||||
Name = name;
|
||||
SortIndex = sortIndex;
|
||||
Minimum = minimum;
|
||||
Maximum = maximum;
|
||||
Increment = increment;
|
||||
DecimalPlaces = decimalPlaces;
|
||||
Variants = variants?.Cast<IComparable>().ToArray();
|
||||
public string Name { get; } = name;
|
||||
public int SortIndex { get; } = sortIndex;
|
||||
public double Minimum { get; } = minimum;
|
||||
public double Maximum { get; } = maximum;
|
||||
public double Increment { get; } = increment;
|
||||
public int DecimalPlaces { get; } = decimalPlaces;
|
||||
public IComparable[]? Variants { get; } = variants?.Cast<IComparable>().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region History Item
|
||||
#region History Item
|
||||
|
||||
/// <summary>
|
||||
/// History item interface
|
||||
/// </summary>
|
||||
public interface IHistoryItem
|
||||
{
|
||||
DateTime TimeLeft { get; }
|
||||
long TicksLeft { get; set; }
|
||||
long TicksRight { get; set; }
|
||||
double this[PriceType priceType] { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock history item for testing
|
||||
/// </summary>
|
||||
public class MockHistoryItem : IHistoryItem
|
||||
{
|
||||
public DateTime TimeLeft { get; set; }
|
||||
public long TicksLeft { get; set; }
|
||||
public long TicksRight { get; set; }
|
||||
public double Open { get; set; }
|
||||
public double High { get; set; }
|
||||
public double Low { get; set; }
|
||||
public double Close { get; set; }
|
||||
public double Volume { get; set; }
|
||||
|
||||
public double this[PriceType priceType] => priceType switch
|
||||
/// <summary>
|
||||
/// History item interface
|
||||
/// </summary>
|
||||
public interface IHistoryItem
|
||||
{
|
||||
PriceType.Open => Open,
|
||||
PriceType.High => High,
|
||||
PriceType.Low => Low,
|
||||
PriceType.Close => Close,
|
||||
PriceType.Volume => Volume,
|
||||
PriceType.Median => (High + Low) / 2,
|
||||
PriceType.Typical => (High + Low + Close) / 3,
|
||||
PriceType.Weighted => (High + Low + Close + Close) / 4,
|
||||
_ => Close
|
||||
};
|
||||
}
|
||||
DateTime TimeLeft { get; }
|
||||
long TicksLeft { get; set; }
|
||||
long TicksRight { get; set; }
|
||||
double this[PriceType priceType] { get; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Historical Data
|
||||
|
||||
/// <summary>
|
||||
/// Mock historical data for testing
|
||||
/// </summary>
|
||||
public class HistoricalData
|
||||
{
|
||||
private readonly List<IHistoryItem> _items = new();
|
||||
|
||||
public int Count => _items.Count;
|
||||
|
||||
public IHistoryItem this[int offset, SeekOriginHistory origin = SeekOriginHistory.End]
|
||||
/// <summary>
|
||||
/// Mock history item for testing
|
||||
/// </summary>
|
||||
public class MockHistoryItem : IHistoryItem
|
||||
{
|
||||
get
|
||||
public DateTime TimeLeft { get; set; }
|
||||
public long TicksLeft { get; set; }
|
||||
public long TicksRight { get; set; }
|
||||
public double Open { get; set; }
|
||||
public double High { get; set; }
|
||||
public double Low { get; set; }
|
||||
public double Close { get; set; }
|
||||
public double Volume { get; set; }
|
||||
|
||||
public double this[PriceType priceType] => priceType switch
|
||||
{
|
||||
PriceType.Open => Open,
|
||||
PriceType.High => High,
|
||||
PriceType.Low => Low,
|
||||
PriceType.Close => Close,
|
||||
PriceType.Volume => Volume,
|
||||
PriceType.Median => (High + Low) / 2,
|
||||
PriceType.Typical => (High + Low + Close) / 3,
|
||||
PriceType.Weighted => (High + Low + Close + Close) / 4,
|
||||
_ => Close
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Historical Data
|
||||
|
||||
/// <summary>
|
||||
/// Mock historical data for testing
|
||||
/// </summary>
|
||||
public class HistoricalData
|
||||
{
|
||||
private readonly List<IHistoryItem> _items = [];
|
||||
|
||||
public int Count => _items.Count;
|
||||
|
||||
public IHistoryItem this[int offset, SeekOriginHistory origin = SeekOriginHistory.End]
|
||||
{
|
||||
get
|
||||
{
|
||||
int index = origin == SeekOriginHistory.End
|
||||
? Count - 1 - offset
|
||||
: offset;
|
||||
return _items[index];
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime Time(int offset = 0, SeekOriginHistory origin = SeekOriginHistory.End)
|
||||
{
|
||||
return this[offset, origin].TimeLeft;
|
||||
}
|
||||
|
||||
public long GetIndexByTime(long ticks)
|
||||
{
|
||||
for (int i = 0; i < _items.Count; i++)
|
||||
{
|
||||
if (_items[i].TicksLeft == ticks)
|
||||
return Count - 1 - i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Add(IHistoryItem item)
|
||||
{
|
||||
_items.Add(item);
|
||||
}
|
||||
|
||||
public void AddBar(DateTime time, double open, double high, double low, double close, double volume = 0)
|
||||
{
|
||||
_items.Add(new MockHistoryItem
|
||||
{
|
||||
TimeLeft = time,
|
||||
TicksLeft = time.Ticks,
|
||||
TicksRight = time.Ticks,
|
||||
Open = open,
|
||||
High = high,
|
||||
Low = low,
|
||||
Close = close,
|
||||
Volume = volume
|
||||
});
|
||||
}
|
||||
|
||||
public void Clear() => _items.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update Args
|
||||
|
||||
/// <summary>
|
||||
/// Update arguments for indicator
|
||||
/// </summary>
|
||||
public class UpdateArgs(UpdateReason reason)
|
||||
{
|
||||
public UpdateReason Reason { get; } = reason;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Line Series
|
||||
|
||||
/// <summary>
|
||||
/// Base class for lines
|
||||
/// </summary>
|
||||
public class IndicatorLineMarker(Color color, IndicatorLineMarkerIconType icon = IndicatorLineMarkerIconType.None)
|
||||
{
|
||||
public Color Color { get; set; } = color;
|
||||
public IndicatorLineMarkerIconType Icon { get; set; } = icon;
|
||||
}
|
||||
|
||||
public class Line(string name, Color color, int width, LineStyle style)
|
||||
{
|
||||
public string Name { get; set; } = name;
|
||||
public Color Color { get; set; } = color;
|
||||
public int Width { get; set; } = width;
|
||||
public LineStyle Style { get; set; } = style;
|
||||
public bool Visible { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Line series for indicator output
|
||||
/// </summary>
|
||||
public class LineSeries(string name, Color color, int width, LineStyle style)
|
||||
: Line(name, color, width, style)
|
||||
{
|
||||
private readonly List<double> _values = [];
|
||||
private readonly List<Color> _markers = [];
|
||||
|
||||
public int TimeShift { get; set; }
|
||||
public int DrawBegin { get; set; }
|
||||
public bool ShowLineMarker { get; set; } = true;
|
||||
|
||||
public double this[int offset = 0, SeekOriginHistory origin = SeekOriginHistory.End]
|
||||
{
|
||||
get => GetValue(offset, origin);
|
||||
set => SetValue(value, offset, origin);
|
||||
}
|
||||
|
||||
public double GetValue(int offset = 0, SeekOriginHistory origin = SeekOriginHistory.End)
|
||||
{
|
||||
if (_values.Count == 0)
|
||||
return double.NaN;
|
||||
|
||||
int index = origin == SeekOriginHistory.End
|
||||
? Count - 1 - offset
|
||||
? _values.Count - 1 - offset
|
||||
: offset;
|
||||
return _items[index];
|
||||
|
||||
if (index < 0 || index >= _values.Count)
|
||||
return double.NaN;
|
||||
|
||||
return _values[index];
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime Time(int offset = 0, SeekOriginHistory origin = SeekOriginHistory.End)
|
||||
{
|
||||
return this[offset, origin].TimeLeft;
|
||||
}
|
||||
|
||||
public long GetIndexByTime(long ticks)
|
||||
{
|
||||
for (int i = 0; i < _items.Count; i++)
|
||||
public void SetValue(double value, int offset = 0, SeekOriginHistory origin = SeekOriginHistory.End)
|
||||
{
|
||||
if (_items[i].TicksLeft == ticks)
|
||||
return Count - 1 - i;
|
||||
EnsureCapacity(offset + 1);
|
||||
int index = origin == SeekOriginHistory.End
|
||||
? _values.Count - 1 - offset
|
||||
: offset;
|
||||
_values[index] = value;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Add(IHistoryItem item)
|
||||
{
|
||||
_items.Add(item);
|
||||
}
|
||||
|
||||
public void AddBar(DateTime time, double open, double high, double low, double close, double volume = 0)
|
||||
{
|
||||
_items.Add(new MockHistoryItem
|
||||
public void SetMarker(int offset, Color color)
|
||||
{
|
||||
TimeLeft = time,
|
||||
TicksLeft = time.Ticks,
|
||||
TicksRight = time.Ticks,
|
||||
Open = open,
|
||||
High = high,
|
||||
Low = low,
|
||||
Close = close,
|
||||
Volume = volume
|
||||
});
|
||||
}
|
||||
EnsureMarkerCapacity(offset + 1);
|
||||
int index = _markers.Count - 1 - offset;
|
||||
if (index >= 0 && index < _markers.Count)
|
||||
_markers[index] = color;
|
||||
}
|
||||
|
||||
public void Clear() => _items.Clear();
|
||||
}
|
||||
public void SetMarker(int offset, IndicatorLineMarker marker)
|
||||
{
|
||||
SetMarker(offset, marker.Color);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update Args
|
||||
|
||||
/// <summary>
|
||||
/// Update arguments for indicator
|
||||
/// </summary>
|
||||
public class UpdateArgs
|
||||
{
|
||||
public UpdateReason Reason { get; }
|
||||
|
||||
public UpdateArgs(UpdateReason reason)
|
||||
{
|
||||
Reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Line Series
|
||||
|
||||
/// <summary>
|
||||
/// Base class for lines
|
||||
/// </summary>
|
||||
public abstract class Line
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Color Color { get; set; }
|
||||
public int Width { get; set; }
|
||||
public LineStyle Style { get; set; }
|
||||
public bool Visible { get; set; } = true;
|
||||
|
||||
protected Line(string name, Color color, int width, LineStyle style)
|
||||
{
|
||||
Name = name;
|
||||
Color = color;
|
||||
Width = width;
|
||||
Style = style;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Line series for indicator output
|
||||
/// </summary>
|
||||
public class LineSeries : Line
|
||||
{
|
||||
private readonly List<double> _values = new();
|
||||
private readonly List<Color> _markers = new();
|
||||
|
||||
public int TimeShift { get; set; }
|
||||
public int DrawBegin { get; set; }
|
||||
public bool ShowLineMarker { get; set; } = true;
|
||||
|
||||
public LineSeries(string name, Color color, int width, LineStyle style)
|
||||
: base(name, color, width, style)
|
||||
{
|
||||
}
|
||||
|
||||
public double this[int offset = 0, SeekOriginHistory origin = SeekOriginHistory.End]
|
||||
{
|
||||
get => GetValue(offset, origin);
|
||||
set => SetValue(value, offset, origin);
|
||||
}
|
||||
|
||||
public double GetValue(int offset = 0, SeekOriginHistory origin = SeekOriginHistory.End)
|
||||
{
|
||||
if (_values.Count == 0)
|
||||
return double.NaN;
|
||||
|
||||
int index = origin == SeekOriginHistory.End
|
||||
? _values.Count - 1 - offset
|
||||
: offset;
|
||||
|
||||
if (index < 0 || index >= _values.Count)
|
||||
return double.NaN;
|
||||
|
||||
return _values[index];
|
||||
}
|
||||
|
||||
public void SetValue(double value, int offset = 0, SeekOriginHistory origin = SeekOriginHistory.End)
|
||||
{
|
||||
EnsureCapacity(offset + 1);
|
||||
int index = origin == SeekOriginHistory.End
|
||||
? _values.Count - 1 - offset
|
||||
: offset;
|
||||
_values[index] = value;
|
||||
}
|
||||
|
||||
public void SetMarker(int offset, Color color)
|
||||
{
|
||||
EnsureMarkerCapacity(offset + 1);
|
||||
int index = _markers.Count - 1 - offset;
|
||||
if (index >= 0 && index < _markers.Count)
|
||||
_markers[index] = color;
|
||||
}
|
||||
|
||||
internal void AddValue()
|
||||
{
|
||||
_values.Add(double.NaN);
|
||||
_markers.Add(Color.Transparent);
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int count)
|
||||
{
|
||||
while (_values.Count < count)
|
||||
internal void AddValue()
|
||||
{
|
||||
_values.Add(double.NaN);
|
||||
}
|
||||
|
||||
private void EnsureMarkerCapacity(int count)
|
||||
{
|
||||
while (_markers.Count < count)
|
||||
_markers.Add(Color.Transparent);
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int count)
|
||||
{
|
||||
while (_values.Count < count)
|
||||
_values.Add(double.NaN);
|
||||
}
|
||||
|
||||
private void EnsureMarkerCapacity(int count)
|
||||
{
|
||||
while (_markers.Count < count)
|
||||
_markers.Add(Color.Transparent);
|
||||
}
|
||||
|
||||
public int Count => _values.Count;
|
||||
public IReadOnlyList<double> Values => _values;
|
||||
}
|
||||
|
||||
public int Count => _values.Count;
|
||||
public IReadOnlyList<double> Values => _values;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region Paint Chart Event Args
|
||||
|
||||
#region Paint Chart Event Args
|
||||
|
||||
/// <summary>
|
||||
/// Paint chart event arguments
|
||||
/// </summary>
|
||||
public class PaintChartEventArgs : EventArgs
|
||||
{
|
||||
public Graphics Graphics { get; }
|
||||
public Rectangle ClipRectangle { get; }
|
||||
public int WindowIndex { get; }
|
||||
|
||||
public PaintChartEventArgs(Graphics graphics, Rectangle clipRectangle, int windowIndex = 0)
|
||||
/// <summary>
|
||||
/// Paint chart event arguments
|
||||
/// </summary>
|
||||
public class PaintChartEventArgs(Graphics graphics, Rectangle clipRectangle, int windowIndex = 0) : EventArgs
|
||||
{
|
||||
Graphics = graphics;
|
||||
ClipRectangle = clipRectangle;
|
||||
WindowIndex = windowIndex;
|
||||
public Graphics Graphics { get; } = graphics;
|
||||
public Rectangle ClipRectangle { get; } = clipRectangle;
|
||||
public int WindowIndex { get; } = windowIndex;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Chart
|
||||
}
|
||||
|
||||
namespace TradingPlatform.BusinessLayer.Chart
|
||||
{
|
||||
/// <summary>
|
||||
/// Coordinates converter interface
|
||||
/// </summary>
|
||||
public interface IChartWindowCoordinatesConverter
|
||||
{
|
||||
DateTime GetTime(int x);
|
||||
double GetChartX(DateTime time);
|
||||
double GetChartY(double value);
|
||||
}
|
||||
}
|
||||
|
||||
namespace TradingPlatform.BusinessLayer
|
||||
{
|
||||
using TradingPlatform.BusinessLayer.Chart;
|
||||
|
||||
/// <summary>
|
||||
/// Chart interface
|
||||
@@ -382,7 +374,7 @@ namespace TradingPlatform.BusinessLayer
|
||||
public interface IChart
|
||||
{
|
||||
ChartWindow MainWindow { get; }
|
||||
ChartWindow[] Windows { get; }
|
||||
IList<ChartWindow> Windows { get; }
|
||||
int BarsWidth { get; }
|
||||
}
|
||||
|
||||
@@ -411,7 +403,7 @@ namespace TradingPlatform.BusinessLayer
|
||||
public class MockChart : IChart
|
||||
{
|
||||
public ChartWindow MainWindow { get; } = new();
|
||||
public ChartWindow[] Windows { get; } = new[] { new ChartWindow() };
|
||||
public IList<ChartWindow> Windows { get; } = [new ChartWindow()];
|
||||
public int BarsWidth { get; set; } = 10;
|
||||
}
|
||||
|
||||
@@ -419,83 +411,86 @@ namespace TradingPlatform.BusinessLayer
|
||||
|
||||
#region Indicator Base
|
||||
|
||||
/// <summary>
|
||||
/// Watchlist indicator interface
|
||||
/// </summary>
|
||||
public interface IWatchlistIndicator
|
||||
{
|
||||
int MinHistoryDepths { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for indicators
|
||||
/// </summary>
|
||||
public abstract class Indicator
|
||||
{
|
||||
private readonly List<LineSeries> _lineSeries = new();
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public virtual string ShortName => Name;
|
||||
public virtual string SourceCodeLink => string.Empty;
|
||||
|
||||
public bool SeparateWindow { get; set; }
|
||||
public bool OnBackGround { get; set; }
|
||||
|
||||
public HistoricalData HistoricalData { get; set; } = new();
|
||||
public IChart? CurrentChart { get; set; }
|
||||
|
||||
public int Count => HistoricalData.Count;
|
||||
|
||||
public LineSeries[] LinesSeries => _lineSeries.ToArray();
|
||||
|
||||
protected void AddLineSeries(LineSeries series)
|
||||
/// <summary>
|
||||
/// Watchlist indicator interface
|
||||
/// </summary>
|
||||
public interface IWatchlistIndicator
|
||||
{
|
||||
_lineSeries.Add(series);
|
||||
int MinHistoryDepths { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when indicator is initialized
|
||||
/// Base class for indicators
|
||||
/// </summary>
|
||||
protected virtual void OnInit()
|
||||
public abstract class Indicator
|
||||
{
|
||||
}
|
||||
private readonly List<LineSeries> _lineSeries = [];
|
||||
|
||||
/// <summary>
|
||||
/// Called on each update
|
||||
/// </summary>
|
||||
protected virtual void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
}
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public virtual string ShortName => Name;
|
||||
public virtual string SourceCodeLink => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Called for chart painting
|
||||
/// </summary>
|
||||
public virtual void OnPaintChart(PaintChartEventArgs args)
|
||||
{
|
||||
}
|
||||
public bool SeparateWindow { get; set; }
|
||||
public bool OnBackGround { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the indicator (for testing)
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
OnInit();
|
||||
}
|
||||
public HistoricalData HistoricalData { get; set; } = new();
|
||||
public IChart? CurrentChart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Process an update (for testing)
|
||||
/// </summary>
|
||||
public void ProcessUpdate(UpdateArgs args)
|
||||
{
|
||||
// Ensure line series have capacity for new data
|
||||
foreach (var series in _lineSeries)
|
||||
public int Count => HistoricalData.Count;
|
||||
|
||||
public IList<LineSeries> LinesSeries => _lineSeries.ToArray();
|
||||
|
||||
protected void AddLineSeries(LineSeries series)
|
||||
{
|
||||
series.AddValue();
|
||||
_lineSeries.Add(series);
|
||||
}
|
||||
OnUpdate(args);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Called when indicator is initialized
|
||||
/// </summary>
|
||||
protected virtual void OnInit()
|
||||
{
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on each update
|
||||
/// </summary>
|
||||
protected virtual void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called for chart painting
|
||||
/// </summary>
|
||||
public virtual void OnPaintChart(PaintChartEventArgs args)
|
||||
{
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the indicator (for testing)
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
OnInit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an update (for testing)
|
||||
/// </summary>
|
||||
public void ProcessUpdate(UpdateArgs args)
|
||||
{
|
||||
// Ensure line series have capacity for new data
|
||||
foreach (var series in _lineSeries)
|
||||
{
|
||||
series.AddValue();
|
||||
}
|
||||
OnUpdate(args);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
<Compile Include="**\*.Tests.cs" />
|
||||
<Compile Include="..\lib\**\*.Quantower.Tests.cs" />
|
||||
<!-- Include core library types -->
|
||||
<Compile Include="..\lib\core\**\*.cs" Exclude="..\lib\core\**\*.Tests.cs" />
|
||||
<Compile Include="..\lib\core\**\*.cs" Exclude="..\lib\core\**\*.Tests.cs;..\lib\core\**\obj\**;..\lib\core\**\bin\**" />
|
||||
<!-- Include trends implementations -->
|
||||
<Compile Include="..\lib\trends\**\*.cs" Exclude="..\lib\trends\**\*.Tests.cs;..\lib\trends\**\*.Validation.Tests.cs" />
|
||||
<Compile Include="..\lib\trends\**\*.cs" Exclude="..\lib\trends\**\*.Tests.cs;..\lib\trends\**\*.Validation.Tests.cs;..\lib\trends\**\obj\**;..\lib\trends\**\bin\**" />
|
||||
<!-- Include momentum implementations -->
|
||||
<Compile Include="..\lib\momentum\**\*.cs" Exclude="..\lib\momentum\**\*.Tests.cs;..\lib\momentum\**\*.Validation.Tests.cs" />
|
||||
<Compile Include="..\lib\momentum\**\*.cs" Exclude="..\lib\momentum\**\*.Tests.cs;..\lib\momentum\**\*.Validation.Tests.cs;..\lib\momentum\**\obj\**;..\lib\momentum\**\bin\**" />
|
||||
<!-- Include IndicatorExtensions -->
|
||||
<Compile Include="IndicatorExtensions.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="IndicatorExtensions.cs" />
|
||||
<Compile Include="..\lib\core\**\*.cs" Exclude="..\lib\core\**\*.Tests.cs" />
|
||||
<Compile Include="..\lib\trends\**\*.cs" Exclude="..\lib\trends\**\*.Tests.cs" />
|
||||
<Compile Include="..\lib\core\**\*.cs" Exclude="..\lib\core\**\*.Tests.cs;..\lib\core\**\obj\**;..\lib\core\**\bin\**" />
|
||||
<Compile Include="..\lib\trends\**\*.cs" Exclude="..\lib\trends\**\*.Tests.cs;..\lib\trends\**\obj\**;..\lib\trends\**\bin\**" />
|
||||
<Reference Include="TradingPlatform.BusinessLayer">
|
||||
<HintPath>..\.github\TradingPlatform.BusinessLayer.dll</HintPath>
|
||||
</Reference>
|
||||
|
||||
Reference in New Issue
Block a user