diff --git a/.github/workflows/main_automation.yml b/.github/workflows/main_automation.yml index f8450eee..33010768 100644 --- a/.github/workflows/main_automation.yml +++ b/.github/workflows/main_automation.yml @@ -70,12 +70,13 @@ jobs: -p:PackageVersion=${{ github.ref == 'refs/heads/dev' && steps.gitversion.outputs.FullSemVer || steps.gitversion.outputs.MajorMinorPatch }} dotnet build ./quantower/Averages/Averages.csproj --configuration Release --nologo dotnet build ./quantower/Statistics/Statistics.csproj --configuration Release --nologo + dotnet build ./quantower/Volatility/Volatility.csproj --configuration Release --nologo dotnet build ./SyntheticVendor/SyntheticVendor.csproj --configuration Release --nologo - name: Run tests with coverage if: github.ref == 'refs/heads/dev' run: | - dotnet test --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput="./TestResults/" + dotnet test ./Tests/Tests.csproj --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput="./TestResults/" dotnet-coverage collect "dotnet test" -f xml -o "coverage.xml" - name: Generate and process coverage report @@ -105,7 +106,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: dotnet sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" - name: Publish release assets uses: SourceSprint/upload-multiple-releases@1.0.7 diff --git a/QuanTAlib.sln b/QuanTAlib.sln index 07695eaf..4d6759fe 100644 --- a/QuanTAlib.sln +++ b/QuanTAlib.sln @@ -1,10 +1,10 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "quantalib", "lib\quantalib.csproj", "{A1B2C3D4-E5F6-47G8-H9I0-J1K2L3M4N5O6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "tests\Tests.csproj", "{B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{B2C3D4E5-F6G7-48H9-I0J1-K2L3M4N5O6P7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyntheticVendor", "SyntheticVendor\SyntheticVendor.csproj", "{C3D4E5F6-G7H8-49I0-J1K2-L3M4N5O6P7Q8}" EndProject diff --git a/SyntheticVendor/SyntheticVendor.cs b/SyntheticVendor/SyntheticVendor.cs index 03d49548..70d96fc9 100644 --- a/SyntheticVendor/SyntheticVendor.cs +++ b/SyntheticVendor/SyntheticVendor.cs @@ -751,8 +751,10 @@ namespace SyntheticVendorNamespace }; } - +#pragma warning disable S2245 + // NOSONAR readonly Random random = new Random(); +#pragma warning restore S2245 private double currentAmplitude = 100; private HistoryItemBar GenerateAMSignal(DateTime time, TimeSpan slice) { diff --git a/Tests/test_iTValue.cs b/Tests/test_iTValue.cs index 3f68ae4c..6504695f 100644 --- a/Tests/test_iTValue.cs +++ b/Tests/test_iTValue.cs @@ -18,6 +18,7 @@ namespace QuanTAlib [ new Ema(period: 10, useSma: true), new Alma(period: 14, offset: 0.85, sigma: 6), + new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman), new Convolution(new double[] { 1.0, 2, 3, 2, 1 }), new Dema(period: 14), new Dsma(period: 14), diff --git a/lib/averages/Dwma.cs b/lib/averages/Dwma.cs index ec7fc883..ba09d84c 100644 --- a/lib/averages/Dwma.cs +++ b/lib/averages/Dwma.cs @@ -6,11 +6,6 @@ namespace QuanTAlib; /// The weights are decreasing over the period with p^2 decay, and the most recent data has the heaviest weight. /// /// -/// Smoothness: ★★★★★ (5/5) -/// Sensitivity: ★★★☆☆ (3/5) -/// Overshooting: ★★★★☆ (4/5) -/// Lag: ★★☆☆☆ (2/5) -/// /// The DWMA is calculated by applying two WMAs in sequence: /// 1. An inner WMA is applied to the input data. /// 2. An outer WMA is then applied to the result of the inner WMA. @@ -28,7 +23,6 @@ namespace QuanTAlib; public class Dwma : AbstractBase { - private readonly int _period; private readonly Wma _innerWma; private readonly Wma _outerWma; @@ -38,11 +32,10 @@ public class Dwma : AbstractBase { throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); } - _period = period; _innerWma = new Wma(period); _outerWma = new Wma(period); Name = "Wma"; - WarmupPeriod = 2 * _period - 1; + WarmupPeriod = 2 * period - 1; Init(); } diff --git a/lib/averages/Fwma.cs b/lib/averages/Fwma.cs index 9746036a..f5644d6a 100644 --- a/lib/averages/Fwma.cs +++ b/lib/averages/Fwma.cs @@ -2,7 +2,6 @@ namespace QuanTAlib; public class Fwma : AbstractBase { - private readonly int _period; private readonly Convolution _convolution; public Fwma(int period) @@ -11,8 +10,7 @@ public class Fwma : AbstractBase { throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); } - _period = period; - _convolution = new Convolution(GenerateKernel(_period)); + _convolution = new Convolution(GenerateKernel(period)); Name = "Fwma"; WarmupPeriod = period; Init(); diff --git a/lib/averages/Gma.cs b/lib/averages/Gma.cs index 4a0780fc..4f003f5c 100644 --- a/lib/averages/Gma.cs +++ b/lib/averages/Gma.cs @@ -2,7 +2,6 @@ namespace QuanTAlib; public class Gma : AbstractBase { - private readonly int _period; private readonly Convolution _convolution; public Gma(int period) @@ -11,8 +10,7 @@ public class Gma : AbstractBase { throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); } - _period = period; - _convolution = new Convolution(GenerateKernel(_period)); + _convolution = new Convolution(GenerateKernel(period)); Name = "Gma"; WarmupPeriod = period; Init(); diff --git a/lib/averages/Hma.cs b/lib/averages/Hma.cs index 6486d689..220b419d 100644 --- a/lib/averages/Hma.cs +++ b/lib/averages/Hma.cs @@ -2,7 +2,6 @@ namespace QuanTAlib; public class Hma : AbstractBase { - private readonly int _period, _sqrtPeriod; private readonly Convolution _wmaHalf, _wmaFull, _wmaFinal; public Hma(int period) @@ -11,13 +10,12 @@ public class Hma : AbstractBase { throw new ArgumentException("Period must be greater than or equal to 2.", nameof(period)); } - _period = period; - _sqrtPeriod = (int)Math.Sqrt(period); + int _sqrtPeriod = (int)Math.Sqrt(period); _wmaHalf = new Convolution(GenerateWmaKernel(period / 2)); _wmaFull = new Convolution(GenerateWmaKernel(period)); _wmaFinal = new Convolution(GenerateWmaKernel(_sqrtPeriod)); Name = "Hma"; - WarmupPeriod = _period + _sqrtPeriod - 1; + WarmupPeriod = period + _sqrtPeriod - 1; Init(); } diff --git a/lib/averages/Sinema.cs b/lib/averages/Sinema.cs index ddba6f94..6834dc64 100644 --- a/lib/averages/Sinema.cs +++ b/lib/averages/Sinema.cs @@ -2,7 +2,6 @@ namespace QuanTAlib; public class Sinema : AbstractBase { - private readonly int _period; private readonly Convolution _convolution; public Sinema(int period) @@ -11,8 +10,7 @@ public class Sinema : AbstractBase { throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); } - _period = period; - _convolution = new Convolution(GenerateKernel(_period)); + _convolution = new Convolution(GenerateKernel(period)); Name = "Sinema"; WarmupPeriod = period; Init(); diff --git a/lib/averages/Sma.cs b/lib/averages/Sma.cs index 3d2fb71d..c0294bd0 100644 --- a/lib/averages/Sma.cs +++ b/lib/averages/Sma.cs @@ -4,7 +4,6 @@ public class Sma : AbstractBase { // inherited _index // inherited _value - private readonly int Period; private readonly CircularBuffer _buffer; public Sma(int period) @@ -13,7 +12,6 @@ public class Sma : AbstractBase { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } - Period = period; WarmupPeriod = period; _buffer = new CircularBuffer(period); Name = "Sma"; @@ -28,11 +26,6 @@ public class Sma : AbstractBase } //inhereted public void Sub(object source, in ValueEventArgs args) - public override void Init() - { - base.Init(); - } - protected override void ManageState(bool isNew) { if (isNew) diff --git a/lib/averages/Trima.cs b/lib/averages/Trima.cs index 3abea746..7175d40a 100644 --- a/lib/averages/Trima.cs +++ b/lib/averages/Trima.cs @@ -2,7 +2,6 @@ namespace QuanTAlib; public class Trima : AbstractBase { - private readonly int _period; private readonly Convolution _convolution; public Trima(int period) @@ -11,8 +10,7 @@ public class Trima : AbstractBase { throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); } - _period = period; - _convolution = new Convolution(GenerateKernel(_period)); + _convolution = new Convolution(GenerateKernel(period)); Name = "Trima"; WarmupPeriod = period; Init(); diff --git a/lib/averages/Vidya.cs b/lib/averages/Vidya.cs index cbff1ee5..112d99bf 100644 --- a/lib/averages/Vidya.cs +++ b/lib/averages/Vidya.cs @@ -6,7 +6,6 @@ namespace QuanTAlib; public class Vidya : AbstractBase { - private readonly int _shortPeriod; private readonly int _longPeriod; private readonly double _alpha; private double _lastVIDYA, _p_lastVIDYA; @@ -19,12 +18,11 @@ public class Vidya : AbstractBase { throw new ArgumentException("Short period must be greater than or equal to 1.", nameof(shortPeriod)); } - _shortPeriod = shortPeriod; _longPeriod = (longPeriod == 0) ? shortPeriod * 4 : longPeriod; _alpha = alpha; WarmupPeriod = _longPeriod; - Name = $"Vidya({_shortPeriod},{_longPeriod})"; - _shortBuffer = new CircularBuffer(_shortPeriod); + Name = $"Vidya({shortPeriod},{_longPeriod})"; + _shortBuffer = new CircularBuffer(shortPeriod); _longBuffer = new CircularBuffer(_longPeriod); Init(); } diff --git a/lib/core/tbar.cs b/lib/core/tbar.cs index 677db00c..6f4ee16e 100644 --- a/lib/core/tbar.cs +++ b/lib/core/tbar.cs @@ -30,15 +30,11 @@ public readonly record struct TBar(DateTime Time, double Open, double High, doub public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { } public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { } - - // when TBar casts to double, it returns its Close - public static implicit operator double(TBar bar) => bar.Close; - public static implicit operator DateTime(TBar tv) => tv.Time; - - // castings for sloppy people - a single double injected into a TBar, and a single TValue injected into a TBar public TBar(double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) { } public TBar(TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) { } + public static implicit operator double(TBar bar) => bar.Close; + public static implicit operator DateTime(TBar tv) => tv.Time; public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; } @@ -70,11 +66,8 @@ public class TBarSeries : List public TBarSeries() { this.Name = "Bar"; - Open = new(); - High = new(); - Low = new(); - Close = new(); - Volume = new(); + (Open, High, Low, Close, Volume) = ([], [], [], [], []); + } public TBarSeries(object source) : this() { diff --git a/lib/statistics/Median.cs b/lib/statistics/Median.cs index fe6955a8..1adc8077 100644 --- a/lib/statistics/Median.cs +++ b/lib/statistics/Median.cs @@ -27,11 +27,6 @@ namespace QuanTAlib pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } - public override void Init() - { - base.Init(); - } - protected override void ManageState(bool isNew) { if (isNew) diff --git a/lib/statistics/Mode.cs b/lib/statistics/Mode.cs index 13b48465..6acf96b3 100644 --- a/lib/statistics/Mode.cs +++ b/lib/statistics/Mode.cs @@ -24,11 +24,6 @@ public class Mode : AbstractBase pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } - public override void Init() - { - base.Init(); - } - protected override void ManageState(bool isNew) { if (isNew) diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs index 1ab0299a..79198654 100644 --- a/lib/volatility/Atr.cs +++ b/lib/volatility/Atr.cs @@ -2,7 +2,6 @@ namespace QuanTAlib; public class Atr : AbstractBarBase { - private readonly int _period; private readonly Ema _ma; private double _prevClose, _p_prevClose; @@ -12,10 +11,9 @@ public class Atr : AbstractBarBase { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } - _period = period; _ma = new(1.0/period); WarmupPeriod = _ma.WarmupPeriod; - Name = $"ATR({_period})"; + Name = $"ATR({period})"; } public Atr(object source, int period) : this(period) diff --git a/quantower/Averages/Averages.csproj b/quantower/Averages/Averages.csproj index 77c81333..29752926 100644 --- a/quantower/Averages/Averages.csproj +++ b/quantower/Averages/Averages.csproj @@ -18,6 +18,9 @@ + + %(Filename)%(Extension) + ..\..\.github\TradingPlatform.BusinessLayer.dll diff --git a/quantower/Statistics/Statistics.csproj b/quantower/Statistics/Statistics.csproj index 262800f0..696cb2a3 100644 --- a/quantower/Statistics/Statistics.csproj +++ b/quantower/Statistics/Statistics.csproj @@ -18,6 +18,9 @@ + + %(Filename)%(Extension) + ..\..\.github\TradingPlatform.BusinessLayer.dll diff --git a/quantower/Statistics/_IndicatorBase.cs b/quantower/Statistics/_IndicatorBase.cs deleted file mode 100644 index d4d01fb7..00000000 --- a/quantower/Statistics/_IndicatorBase.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System.Drawing; -using TradingPlatform.BusinessLayer; -using TradingPlatform.BusinessLayer.Chart; -using System.Runtime.CompilerServices; -using System.Drawing.Drawing2D; -using System.Collections; -using TradingPlatform.BusinessLayer.TimeSync; - -namespace QuanTAlib; - -#pragma warning disable CA1416 // Validate platform compatibility -public abstract class IndicatorBase : Indicator, IWatchlistIndicator -{ - - [InputParameter("Data source", sortIndex: 17, variants: [ - "Open", 1, - "High", 2, - "Low", 3, - "Close", 4, - "HL/2 (Median)", 5, - "OC/2 (Midpoint)", 6, - "OHL/3 (Mean)", 7, - "HLC/3 (Typical)", 8, - "OHLC/4 (Average)", 9, - "HLCC/4 (Weighted)", 10 - ])] - public int Source { get; set; } = 4; - - [InputParameter("Show cold values", sortIndex: 20)] - public bool ShowColdValues { get; set; } = true; - public int MinHistoryDepths { get; set; } - - // LineSeries.LineSeries(string, Color, int, LineStyle)' - - protected LineSeries? Series; - protected string SourceName; - protected abstract AbstractBase QuanTAlib { get; } - - int IWatchlistIndicator.MinHistoryDepths => 0; - - protected IndicatorBase() - { - OnBackGround = true; - SeparateWindow = false; - SourceName = GetName(Source); - Series = new(name: $"{Name}", color: Color.RoyalBlue, width: 2, style: LineStyle.Solid); - - AddLineSeries(Series); - } - - protected abstract void InitIndicator(); - - protected override void OnInit() - { - InitIndicator(); - SourceName = GetName(Source); - base.OnInit(); - } - - protected override void OnUpdate(UpdateArgs args) - { - TBar bar = new(Time: Time(), - Open: GetPrice(PriceType.Open), - High: GetPrice(PriceType.High), - Low: GetPrice(PriceType.Low), - Close: GetPrice(PriceType.Close), - Volume: GetPrice(PriceType.Volume), - IsNew: args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar); - - double price = Source switch - { - 1 => bar.Open, - 2 => bar.High, - 3 => bar.Low, - 4 => bar.Close, - 5 => bar.HL2, - 6 => bar.OC2, - 7 => bar.OHL3, - 8 => bar.HLC3, - 9 => bar.OHLC4, - 10 => bar.HLCC4, - _ => bar.Close - }; - - TValue input = new TValue(bar.Time, price, bar.IsNew); - TValue result = QuanTAlib.Calc(input); - Series!.SetValue(result.Value); - Series!.SetMarker(0, Color.Transparent); - - } - - public override void OnPaintChart(PaintChartEventArgs args) - { - base.OnPaintChart(args); - List allPoints = new List(); - if (CurrentChart == null) { return; } - - Graphics gr = args.Graphics; - - var mainWindow = this.CurrentChart.Windows[args.WindowIndex]; - var converter = mainWindow.CoordinatesConverter; - var clientRect = mainWindow.ClientRectangle; - - gr.SetClip(clientRect); - DateTime leftTime = new[] { converter.GetTime(clientRect.Left), Time(this.Count - 1) }.Max(); - DateTime rightTime = new[] { converter.GetTime(clientRect.Right), Time(0) }.Min(); - - int leftIndex = (int)HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; - int rightIndex = (int)HistoricalData.GetIndexByTime(rightTime.Ticks); - - for (int i = rightIndex; i < leftIndex; i++) - { - int barX = (int)converter.GetChartX(Time(i)); - int barY = (int)converter.GetChartY(Series![i]); - int halfBarWidth = CurrentChart.BarsWidth / 2; - Point point = new Point(barX + halfBarWidth, barY); - allPoints.Add(point); - } - - if (allPoints.Count > 1) - { - DrawSmoothCombinedCurve(gr, allPoints, this.Count - QuanTAlib.WarmupPeriod - rightIndex); - } - } - - private void DrawSmoothCombinedCurve(Graphics gr, List allPoints, int hotCount) - { - if (allPoints.Count < 2) { return; } - - using (Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }) - using (Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }) - { - // 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)0.1); - } - - // 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)0.1); - } - } - } - 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, - }; - } - protected static void DrawText(Graphics gr, string text, Rectangle clientRect) - { - 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(SystemBrushes.ControlDarkDark, textRect); - gr.DrawString(text, font, Brushes.White, new PointF(textRect.X + 6, textRect.Y + 5)); - } - protected static string GetName(int pType) - { - return pType switch - { - 1 => "Open", - 2 => "High", - 3 => "Low", - 4 => "Close", - 5 => "Median", - 6 => "Midpoint", - 7 => "Mean", - 8 => "Typical", - 9 => "Average", - 10 => "Weighted", - _ => "N/A" - }; - } - -} \ No newline at end of file diff --git a/quantower/Volatility/Volatility.csproj b/quantower/Volatility/Volatility.csproj index 7247b808..3bf9b4dc 100644 --- a/quantower/Volatility/Volatility.csproj +++ b/quantower/Volatility/Volatility.csproj @@ -18,6 +18,9 @@ + + %(Filename)%(Extension) + ..\..\.github\TradingPlatform.BusinessLayer.dll diff --git a/quantower/Volatility/_IndicatorBarBase.cs b/quantower/Volatility/_IndicatorBarBase.cs deleted file mode 100644 index a5fae449..00000000 --- a/quantower/Volatility/_IndicatorBarBase.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Drawing; -using TradingPlatform.BusinessLayer; -using TradingPlatform.BusinessLayer.Chart; -using System.Runtime.CompilerServices; -using System.Drawing.Drawing2D; -using System.Collections; -using TradingPlatform.BusinessLayer.TimeSync; - -namespace QuanTAlib; - -#pragma warning disable CA1416 // Validate platform compatibility -public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator -{ - - [InputParameter("Show cold values", sortIndex: 20)] - public bool ShowColdValues { get; set; } = true; - public int MinHistoryDepths { get; set; } - - // LineSeries.LineSeries(string, Color, int, LineStyle)' - - protected LineSeries? Series; - protected abstract AbstractBarBase QuanTAlib { get; } - - int IWatchlistIndicator.MinHistoryDepths => 0; - - protected IndicatorBarBase() - { - OnBackGround = true; - SeparateWindow = false; - Series = new(name: $"{Name}", color: Color.RoyalBlue, width: 2, style: LineStyle.Solid); - - AddLineSeries(Series); - } - - protected abstract void InitIndicator(); - - protected override void OnInit() - { - InitIndicator(); - base.OnInit(); - } - - protected override void OnUpdate(UpdateArgs args) - { - TBar bar = new(Time: Time(), - Open: GetPrice(PriceType.Open), - High: GetPrice(PriceType.High), - Low: GetPrice(PriceType.Low), - Close: GetPrice(PriceType.Close), - Volume: GetPrice(PriceType.Volume), - IsNew: args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar); - - TValue result = QuanTAlib.Calc(bar); - Series!.SetValue(result.Value); - Series!.SetMarker(0, Color.Transparent); - - } - - public override void OnPaintChart(PaintChartEventArgs args) - { - base.OnPaintChart(args); - List allPoints = new List(); - if (CurrentChart == null) { return; } - - Graphics gr = args.Graphics; - - var mainWindow = this.CurrentChart.Windows[args.WindowIndex]; - var converter = mainWindow.CoordinatesConverter; - var clientRect = mainWindow.ClientRectangle; - - gr.SetClip(clientRect); - DateTime leftTime = new[] { converter.GetTime(clientRect.Left), Time(this.Count - 1) }.Max(); - DateTime rightTime = new[] { converter.GetTime(clientRect.Right), Time(0) }.Min(); - - int leftIndex = (int)HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; - int rightIndex = (int)HistoricalData.GetIndexByTime(rightTime.Ticks); - - for (int i = rightIndex; i < leftIndex; i++) - { - int barX = (int)converter.GetChartX(Time(i)); - int barY = (int)converter.GetChartY(Series![i]); - int halfBarWidth = CurrentChart.BarsWidth / 2; - Point point = new Point(barX + halfBarWidth, barY); - allPoints.Add(point); - } - - if (allPoints.Count > 1) - { - DrawSmoothCombinedCurve(gr, allPoints, this.Count - QuanTAlib.WarmupPeriod - rightIndex); - } - } - - private void DrawSmoothCombinedCurve(Graphics gr, List allPoints, int hotCount) - { - if (allPoints.Count < 2) { return; } - - using (Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }) - using (Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }) - { - // 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)0.1); - } - - // 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)0.1); - } - } - } - 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, - }; - } - protected static void DrawText(Graphics gr, string text, Rectangle clientRect) - { - 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(SystemBrushes.ControlDarkDark, textRect); - gr.DrawString(text, font, Brushes.White, new PointF(textRect.X + 6, textRect.Y + 5)); - } -} \ No newline at end of file diff --git a/quantower/Statistics/_IndicatorBarBase.cs b/quantower/_IndicatorBarBase.cs similarity index 100% rename from quantower/Statistics/_IndicatorBarBase.cs rename to quantower/_IndicatorBarBase.cs diff --git a/quantower/Averages/_IndicatorBase.cs b/quantower/_IndicatorBase.cs similarity index 100% rename from quantower/Averages/_IndicatorBase.cs rename to quantower/_IndicatorBase.cs