diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..27ae0e75 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +# Top-most EditorConfig file +root = true + +[*.{cs,vb}] +# Suppress S3776 (Cognitive Complexity) +dotnet_diagnostic.S3776.severity = none +# Suppress CA1416 (Platform Compatibility) +dotnet_diagnostic.CA1416.severity = none \ No newline at end of file diff --git a/.github/workflows/main_automation.yml b/.github/workflows/main_automation.yml index 5f1efe28..0f91fe12 100644 --- a/.github/workflows/main_automation.yml +++ b/.github/workflows/main_automation.yml @@ -10,13 +10,21 @@ on: jobs: build_test: - runs-on: windows-latest + #runs-on: windows-latest + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 +############## Install tools + + - name: Create Quantower folder at root + run: | + sudo mkdir -p /Quantower/ + sudo chmod -R 777 /Quantower + - name: Install .NET uses: actions/setup-dotnet@v3 with: @@ -34,31 +42,14 @@ jobs: uses: gittools/actions/gitversion/execute@v0 with: useConfigFile: true - configFilePath: /a/QuanTAlib/QuanTAlib/GitVersion.yml + #configFilePath: GitVersion.yml updateAssemblyInfo: true - - name: Display GitVersion variables (without prefix) - run: | - echo "Major: ${{ steps.gitversion.outputs.major }}" - echo "Minor: ${{ steps.gitversion.outputs.minor }}" - echo "Patch: ${{ steps.gitversion.outputs.patch }}" - echo "PreReleaseTag: ${{ steps.gitversion.outputs.preReleaseTag }}" - echo "PreReleaseTagWithDash: ${{ steps.gitversion.outputs.preReleaseTagWithDash }}" - echo "PreReleaseLabel: ${{ steps.gitversion.outputs.preReleaseLabel }}" - echo "PreReleaseNumber: ${{ steps.gitversion.outputs.preReleaseNumber }}" - echo "WeightedPreReleaseNumber: ${{ steps.gitversion.outputs.weightedPreReleaseNumber }}" - echo "FullBuildMetaData: ${{ steps.gitversion.outputs.fullBuildMetaData }}" - echo "MajorMinorPatch: ${{ steps.gitversion.outputs.majorMinorPatch }}" - echo "SemVer: ${{ steps.gitversion.outputs.semVer }}" - echo "AssemblySemVer: ${{ steps.gitversion.outputs.assemblySemVer }}" - echo "AssemblySemFileVer: ${{ steps.gitversion.outputs.assemblySemFileVer }}" - echo "FullSemVer: ${{ steps.gitversion.outputs.fullSemVer }}" - echo "InformationalVersion: ${{ steps.gitversion.outputs.informationalVersion }}" - - name: Install JDK11 for Sonar Scanner - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 1.11 + java-version: 11 + distribution: 'zulu' - name: Install JetBrains run: dotnet tool install JetBrains.dotCover.GlobalTool --global @@ -74,42 +65,52 @@ jobs: run: dotnet sonarscanner begin /o:"mihakralj" /k:"mihakralj_QuanTAlib" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" - /d:sonar.cs.dotcover.reportsPaths=./coveragereport.html + /d:sonar.cs.dotcover.reportsPaths=./dotcover.xml + +############# Build and test - name: Build Main branch of QuanTAlib DLL if: ${{ github.ref != 'refs/heads/dev' }} - run: dotnet build ./Calculations/Calculations.csproj --verbosity detailed --configuration Release --nologo -p:PackageVersion=${{ steps.gitversion.outputs.MajorMinorPatch }} - + run: dotnet build ./Calculations/Calculations.csproj --configuration Release --nologo -p:PackageVersion=${{ steps.gitversion.outputs.MajorMinorPatch }} - name: Build dev branch of QuanTAlib DLL if: ${{ github.ref == 'refs/heads/dev' }} - run: dotnet build ./Calculations/Calculations.csproj --verbosity detailed --configuration Release --nologo -p:PackageVersion=${{ steps.gitversion.outputs.FullSemVer }} - + run: dotnet build ./Calculations/Calculations.csproj --configuration Release --nologo -p:PackageVersion=${{ steps.gitversion.outputs.FullSemVer }} - name: Build Indicators DLL - run: dotnet build ./Indicators/Indicators.csproj --verbosity detailed --configuration Release --nologo - + run: dotnet build ./Indicators/Indicators.csproj --configuration Release --nologo - name: Build Strategies DLL - run: dotnet build ./Strategies/Strategies.csproj --verbosity detailed --configuration Release --nologo + run: dotnet build ./Strategies/Strategies.csproj --configuration Release --nologo - - name: DotCover Test XML - run: dotnet dotcover test ./Tests/Tests.csproj --verbosity minimal --dcReportType=DetailedXML --dcoutput=./coveragereport.xml + - name: DotCover Test + run: dotnet dotcover test Tests/Tests.csproj --dcReportType=DetailedXML --dcReportType=HTML --dcoutput=dotcover.xml --dcoutput=dotcover.html + - name: Coverlet Test + run: dotnet test -p:CollectCoverage=true --collect:"XPlat Code Coverage" --results-directory "./" - - name: Upload coverage reports to Codecov +############## Report to Sonar/CodeCov/Codacy + + - name: Move coverage report to project root + run: | + report=$(find . -name '*coverage.cobertura.xml' | head -1) + mv "$report" ./coverage.cobertura.xml + + - name: Upload to Codacy + uses: codacy/codacy-coverage-reporter-action@v1 + with: + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + coverage-reports: "*cover*.xml" + + - name: Upload to Codecov uses: codecov/codecov-action@v3 with: - files: ./coveragereport.xml + files: cover* verbose: true - - name: Sonar reporter + - name: Upload to Sonar env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: dotnet sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" - - name: Codacy coverage reporter - uses: codacy/codacy-coverage-reporter-action@v1 - with: - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - coverage-reports: ./coveragereport.xml +############## Publish dev release - name: Publish dev release assets if: ${{ github.ref == 'refs/heads/dev' }} @@ -120,18 +121,20 @@ jobs: prerelease: true overwrite: true release_name: ${{ steps.gitversion.outputs.SemVer }} - tag_name: ${{ steps.gitversion.outputs.SemVer }} + tag_name: prerelease release_config: | - .\Calculations\bin\Release\net6.0\QuanTAlib.dll - .\Indicators\bin\Release\QuanTAlib_Indicators.dll - .\Strategies\bin\Release\QuanTAlib_Strategies.dll + Calculations/bin/Release/net6.0/QuanTAlib.dll + Indicators/bin/Release/QuanTAlib_Indicators.dll + Strategies/bin/Release/QuanTAlib_Strategies.dll - name: Push package to myget.org - run: dotnet nuget push '.\Calculations\bin\Release\QuanTAlib.*.nupkg' + run: dotnet nuget push 'Calculations/bin/Release/QuanTAlib.*.nupkg' --api-key ${{ secrets.MYGET_DEPLOY_KEY_QUANTALIB }} --source https://www.myget.org/F/quantalib/api/v2/package --skip-duplicate +############## Publish main release + - name: Publish main release assets if: ${{ github.ref == 'refs/heads/main' }} uses: SourceSprint/upload-multiple-releases@1.0.7 @@ -141,15 +144,15 @@ jobs: prerelease: false overwrite: true release_name: ${{ steps.gitversion.outputs.MajorMinorPatch }} - tag_name: ${{ steps.gitversion.outputs.MajorMinorPatch }} + tag_name: latest release_config: | - .\Calculations\bin\Release\net6.0\QuanTAlib.dll - .\Indicators\bin\Release\QuanTAlib_Indicators.dll - .\Strategies\bin\Release\QuanTAlib_Strategies.dll + Calculations/bin/Release/net6.0/QuanTAlib.dll + Indicators/bin/Release/QuanTAlib_Indicators.dll + Strategies/bin/Release/QuanTAlib_Strategies.dll - name: Push package to nuget.org if: ${{ github.ref == 'refs/heads/main' }} - run: dotnet nuget push '.\Calculations\bin\Release\QuanTAlib.*.nupkg' + run: dotnet nuget push 'Calculations/bin/Release/QuanTAlib.*.nupkg' --api-key ${{ secrets.NUGET_DEPLOY_KEY_QUANTLIB }} --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/Calculations/Basics/TSeries.cs b/Calculations/Basics/TSeries.cs index 15f53d9f..9dd6488d 100644 --- a/Calculations/Basics/TSeries.cs +++ b/Calculations/Basics/TSeries.cs @@ -1,6 +1,7 @@ namespace QuanTAlib; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; /* @@ -18,9 +19,9 @@ public class TSeries : List<(DateTime t, double v)> { public static implicit operator (DateTime t, double v)(TSeries l) => l[^1]; public static implicit operator double(TSeries l) => l[^1].v; public static implicit operator DateTime(TSeries l) => l[^1].t; - public List t => this.Select(item => item.t).ToList(); - public List v => this.Select(item => item.v).ToList(); - public int Length => this.Count; + public ReadOnlyCollection t => this.Select(item => item.t).ToList().AsReadOnly(); + public ReadOnlyCollection v => this.Select(item => item.v).ToList().AsReadOnly(); + public int Length => Count; public TSeries Tail(int count = 10) { var tailSeries = new TSeries(); diff --git a/Calculations/Calculations.csproj b/Calculations/Calculations.csproj index 02fbbdf4..4140e36e 100644 --- a/Calculations/Calculations.csproj +++ b/Calculations/Calculations.csproj @@ -34,6 +34,7 @@ 0.2.1.0 0.2.1.0 0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d + NETSDK1057 full @@ -60,7 +61,7 @@ - + True diff --git a/Calculations/Statistics/DECAY_Series.cs b/Calculations/Statistics/DECAY_Series.cs index b89b8d75..2084c95a 100644 --- a/Calculations/Statistics/DECAY_Series.cs +++ b/Calculations/Statistics/DECAY_Series.cs @@ -15,7 +15,7 @@ DECAY: */ public class DECAY_Series : Single_TSeries_Indicator { - private bool _exp; + private readonly bool _exp; private double _pdecay, _ppdecay; private readonly double _dfactor; diff --git a/Calculations/Statistics/LINREG_Series.cs b/Calculations/Statistics/LINREG_Series.cs index 2aff6af2..96458c54 100644 --- a/Calculations/Statistics/LINREG_Series.cs +++ b/Calculations/Statistics/LINREG_Series.cs @@ -21,12 +21,14 @@ Sources: public class LINREG_Series : Single_TSeries_Indicator { - public readonly TSeries Intercept = new(); - public readonly TSeries RSquared = new(); - public readonly TSeries StdDev = new(); + private readonly TSeries p_Intercept = new(); + private readonly TSeries p_RSquared = new(); + private readonly TSeries p_StdDev = new(); private readonly System.Collections.Generic.List _buffer = new(); - - public LINREG_Series(TSeries source, int period, bool useNaN = false) + public TSeries Intercept => p_Intercept; + public TSeries RSquared => p_RSquared; + public TSeries StdDev => p_StdDev; +public LINREG_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) { if (this._data.Count > 0) { base.Add(this._data); } @@ -80,12 +82,12 @@ public class LINREG_Series : Single_TSeries_Indicator base.Add(ret, update, _NaN); ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _intercept); - Intercept.Add(ret, update); + p_Intercept.Add(ret, update); ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _StdDev); - StdDev.Add(ret, update); + p_StdDev.Add(ret, update); ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _RSquared); - RSquared.Add(ret, update); + p_RSquared.Add(ret, update); } } \ No newline at end of file diff --git a/Calculations/Trends/ALMA_Series.cs b/Calculations/Trends/ALMA_Series.cs index ceb70320..30d755f5 100644 --- a/Calculations/Trends/ALMA_Series.cs +++ b/Calculations/Trends/ALMA_Series.cs @@ -14,7 +14,7 @@ Sources: https://phemex.com/academy/what-is-arnaud-legoux-moving-averages https://www.prorealcode.com/prorealtime-indicators/alma-arnaud-legoux-moving-average/ -TODO: Discrepancy with Pandas-TA (but passes the validation with Skender.GetAlma) + Discrepancy with Pandas-TA (but passes the validation with Skender.GetAlma) */ diff --git a/Calculations/Trends/DEMA_Series.cs b/Calculations/Trends/DEMA_Series.cs index 3aaf2560..1a568c85 100644 --- a/Calculations/Trends/DEMA_Series.cs +++ b/Calculations/Trends/DEMA_Series.cs @@ -41,7 +41,7 @@ public class DEMA_Series : Single_TSeries_Indicator _lastsum = _lastlastsum; _lastema1 = _lastlastema1; _lastema2 = _lastlastema2; - } + } else { _lastlastsum = _lastsum; _lastlastema1 = _lastema1; @@ -67,8 +67,8 @@ public class DEMA_Series : Single_TSeries_Indicator } _dema = 2*_ema1 - _ema2; - _lastema1 = _ema1; - _lastema2 = _ema2; + _lastema1 = Double.IsNaN(_ema1)?_lastema1:_ema1; + _lastema2 = Double.IsNaN(_ema2)?_lastema2:_ema2; base.Add((TValue.t, _dema), update, _NaN); } diff --git a/Calculations/Trends/DWMA_Series.cs b/Calculations/Trends/DWMA_Series.cs index 7cdb0954..08d2ecb9 100644 --- a/Calculations/Trends/DWMA_Series.cs +++ b/Calculations/Trends/DWMA_Series.cs @@ -9,24 +9,22 @@ DWMA: Double Weighted Moving Average */ public class DWMA_Series : Single_TSeries_Indicator { + private readonly System.Collections.Generic.List _buffer1 = new(); + private readonly System.Collections.Generic.List _weights = new(); public DWMA_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) { for (int i = 0; i < this._p; i++) { double _weight = (i + 1) * (i + 1); this._weights.Add(_weight); } - if (base._data.Count > 0) { base.Add(base._data); } } - private readonly System.Collections.Generic.List _buffer1 = new(); - private readonly System.Collections.Generic.List _weights = new(); public override void Add((System.DateTime t, double v) TValue, bool update) { Add_Replace_Trim(_buffer1, TValue.v, _p, update); - double _wma1 = 0; - double _wsum = 0; + double _wma1 = 0, _wsum = 0; for (int i = 0; i < _buffer1.Count; i++) { - _wma1 += _buffer1[i] * this._weights[i]; - _wsum += this._weights[i]; + _wma1 += _buffer1[i] * _weights[i]; + _wsum += _weights[i]; } _wma1 /= _wsum; diff --git a/Calculations/Trends/EMA_Series.cs b/Calculations/Trends/EMA_Series.cs index 6fe35b19..6ce1d3a1 100644 --- a/Calculations/Trends/EMA_Series.cs +++ b/Calculations/Trends/EMA_Series.cs @@ -60,7 +60,7 @@ public class EMA_Series : Single_TSeries_Indicator { else { _ema = _k * (TValue.v - _lastema) + _lastema; } - _lastema = _ema; + _lastema = Double.IsNaN(_ema)?_lastema:_ema; base.Add((TValue.t, _ema), update, _NaN); } diff --git a/Calculations/Trends/JMA_Series.cs b/Calculations/Trends/JMA_Series.cs index 09cdeeb3..d9964848 100644 --- a/Calculations/Trends/JMA_Series.cs +++ b/Calculations/Trends/JMA_Series.cs @@ -27,13 +27,14 @@ public class JMA_Series : Single_TSeries_Indicator { public TSeries mma1 { get; } public TSeries mma2 { get; } - private double upperBand, lowerBand, vsum, Kv, del1, del2; + private double upperBand, lowerBand, vsum, Kv; private double prev_ma1, prev_det0, prev_det1, prev_vsum, prev_jma; private double p_upperBand, p_lowerBand, p_Kv, p_prev_ma1, p_prev_det0, p_prev_det1, p_prev_vsum, p_prev_jma; private readonly int _voltyS, _voltyL; public JMA_Series(TSeries source, int period, double phase = 0.0, int vshort = 10, int vlong = 65, bool useNaN = false) : base(source, period, useNaN) { - upperBand = lowerBand = prev_ma1 = prev_det0 = prev_det1 = prev_vsum = prev_jma = Kv = del1 = del2 = 0.0; + upperBand = lowerBand = prev_ma1 = prev_det0 = prev_det1 = prev_vsum = prev_jma = Kv = 0.0; + Kv = 0; pr = (phase * 0.01) + 1.5; @@ -48,6 +49,7 @@ public class JMA_Series : Single_TSeries_Indicator { } public override void Add((System.DateTime t, double v) TValue, bool update) { + double del1 = 0.0, del2 = 0.0; if (this.Count == 0) { prev_ma1 = prev_jma = TValue.v; } if (update) { upperBand = p_upperBand; diff --git a/Calculations/Trends/MAMA_Series.cs b/Calculations/Trends/MAMA_Series.cs index 7c491913..624321a8 100644 --- a/Calculations/Trends/MAMA_Series.cs +++ b/Calculations/Trends/MAMA_Series.cs @@ -33,7 +33,7 @@ public class MAMA_Series : Single_TSeries_Indicator public override void Add((System.DateTime t, double v) TValue, bool update) { - + if (!update) { // roll forward (oldx = x) pr.io = pr.i6; pr.i6 = pr.i5; pr.i5 = pr.i4; pr.i4 = pr.i3; pr.i3 = pr.i2; pr.i2 = pr.i1; pr.i1 = pr.i; diff --git a/Calculations/Trends/T3_Series.cs b/Calculations/Trends/T3_Series.cs index 304fc200..4f598993 100644 --- a/Calculations/Trends/T3_Series.cs +++ b/Calculations/Trends/T3_Series.cs @@ -13,16 +13,6 @@ Sources: https://technicalindicators.net/indicators-technical-analysis/150-t3-moving-average http://www.binarytribune.com/forex-trading-indicators/t3-moving-average-indicator/ -Calculation: - Volume Factor is typically 0.7 (but also 0.618); - Ema1 = Ema (Close); - Ema2 = Ema (Ema1); - Ema3 = Ema (Ema2); - Ema4 = Ema (Ema3); - Ema5 = Ema (Ema4); - Ema6 = Ema (Ema5); - T3 = –(a*a*a) * Ema6 + (3*a*a + 3*a*a*a) * Ema5 + (–6*a*a – 3*a – 3*a*a*a) * Ema4 + (1 + 3*a + a*a*a + 3*a*a) * Ema3 - */ public class T3_Series : Single_TSeries_Indicator { private readonly double _k, _k1m, _c1, _c2, _c3, _c4; @@ -35,7 +25,7 @@ public class T3_Series : Single_TSeries_Indicator { private double _lastema1, _lastema2, _lastema3, _lastema4, _lastema5, _lastema6; private double _llastema1, _llastema2, _llastema3, _llastema4, _llastema5, _llastema6; - private bool _useSMA; + private readonly bool _useSMA; public T3_Series(TSeries source, int period, double vfactor = 0.7, bool useNaN = false, bool useSMA = true) : base(source, period, useNaN) { double _a = vfactor; //0.7; //0.618 diff --git a/Calculations/Trends/TRIX_Series.cs b/Calculations/Trends/TRIX_Series.cs index 36c97fdb..7815048c 100644 --- a/Calculations/Trends/TRIX_Series.cs +++ b/Calculations/Trends/TRIX_Series.cs @@ -9,13 +9,6 @@ TRIX: Triple Exponential Average has become a popular technical analysis tool to aid chartists in spotting diversions and directional cues in stock trading patterns. - -Calculation: - Ema1 = Ema (Close); - Ema2 = Ema (Ema1); - Ema3 = Ema (Ema2); - TRIX = (Ema3-Ema3[1]) / Ema3[1] - Sources: https://www.investopedia.com/terms/t/trix.asp @@ -29,7 +22,7 @@ public class TRIX_Series : Single_TSeries_Indicator private double _lastema1, _lastema2, _lastema3; private double _llastema1, _llastema2, _llastema3; - private bool _useSMA; + private readonly bool _useSMA; public TRIX_Series(TSeries source, int period, bool useNaN = false, bool useSMA = true) : base(source, period, useNaN) { diff --git a/Calculations/Volatility/ADOSC_Series.cs b/Calculations/Volatility/ADOSC_Series.cs index 9fca7e30..797e1f85 100644 --- a/Calculations/Volatility/ADOSC_Series.cs +++ b/Calculations/Volatility/ADOSC_Series.cs @@ -6,7 +6,7 @@ ADO: Chaikin Accumulation/Distribution Oscillator ADO measures the momentum of ADL using the difference between slow (10-day) EMA(ADL) and fast (3-day) EMA(ADL): - Chaikin A/D Oscillator = (3-day EMA of ADL) - (10-day EMA of ADL) + Chaikin A/D Oscillator is defined as 3-day EMA of ADL minus 10-day EMA of ADL Sources: https://school.stockcharts.com/doku.php?id=technical_indicators:chaikin_oscillator @@ -53,35 +53,3 @@ public class ADOSC_Series : Single_TBars_Indicator } } -/* -public class ADOSC_Series : Single_TBars_Indicator -{ - private readonly ADL_Series _TSadl; - - private readonly EMA_Series _TSslow; - private readonly EMA_Series _TSfast; - private readonly SUB_Series _TSado; - - public ADOSC_Series(TBars source, bool useNaN = false) : base(source, period: 0, useNaN) - { - _TSadl = new(source: source, useNaN: false); - _TSslow = new(source: _TSadl, period: 10, useNaN: false); - _TSfast = new(source: _TSadl, period: 3, useNaN: false); - _TSado = new(_TSfast, _TSslow); - - if (source.Count > 0) - { base.Add(_TSado); } - Console.WriteLine(base.Count); - } - - public override void Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update) - { - if (update) - { _TSadl.Add(TBar, true); } - - double _ado = this._TSado[(this.Count < this._TSado.Count) ? this.Count : this._TSado.Count - 1].v; - var result = (TBar.t, _ado); - base.Add(result, update); - } -} -*/ \ No newline at end of file diff --git a/Indicators/Charts/xMA_chart.cs b/Indicators/Charts/xMA_chart.cs index f66b43cf..fbfb9731 100644 --- a/Indicators/Charts/xMA_chart.cs +++ b/Indicators/Charts/xMA_chart.cs @@ -1,136 +1,280 @@ using System; using System.Drawing; +using System.Linq; using TradingPlatform.BusinessLayer; namespace QuanTAlib; -public class MovingAverage_chart : Indicator -{ - #region Parameters - - [InputParameter("Smoothing period", 0, 1, 999, 1, 1)] - private int Period = 10; - - [InputParameter("Data source", 1, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int DataSource = 3; - - [InputParameter("Moving Average Type", 2, variants: new object[] +public class MovingAverage_chart : Indicator { + #region Parameters + [InputParameter("MA1: Type", 0, variants: new object[] { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FMA", 7, "DEMA", 8, "TEMA", 9, "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MAtype = 1; + private int MA1type = 15; + + [InputParameter("MA1: Smoothing period", 1, 1, 999, 1, 1)] + private int MA1Period = 10; + + [InputParameter("MA1: Data source", 2, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA1DataSource = 3; + + [InputParameter("MA2: Type", 3, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA2type = 16; + + [InputParameter("MA2: Smoothing period", 4, 1, 999, 1, 1)] + private int MA2Period = 50; + + [InputParameter("MA2: Data source", 5, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA2DataSource = 8; + + [InputParameter("Long trades", 6)] + private bool LongTrades = true; + + [InputParameter("Short trades", 6)] + private bool ShortTrades = false; + #endregion Parameters protected HistoricalData History; - private TBars bars ; + private TBars bars; - /////// - private TSeries indicator; - /////// + /////// + private TSeries MA1, MA2; + private CROSS_Series trades; + private COMPARE_Series overunder; + private EQUITY_Series equity; + /////// - public MovingAverage_chart() - { - this.SeparateWindow = false; - this.Name = "Flexible Moving Average"; - this.AddLineSeries("MA", Color.Yellow, 3, LineStyle.Solid); - } - - protected override void OnInit() - { - this.bars = new(); - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - for (int i = this.History.Count - 1; i >= 0; i--) { - var rec = this.History[i, SeekOriginHistory.Begin]; - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } - - switch (MAtype) { - case 0: - indicator = new SMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Simple Moving Average - SMA"; - break; - case 1: - indicator = new EMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Exponential Moving Average - EMA"; - break; - case 2: - indicator = new WMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Weighted Moving Average - WMA"; - break; - case 3: - indicator = new T3_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Tillson T3 Moving Average - T3"; - break; - case 4: - indicator = new SMMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Smoothed Moving Average - SMMA"; - break; - case 5: - indicator = new TRIMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Triangular Moving Average - TRIMA"; - break; - case 6: - indicator = new DWMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Double Weighted Moving Average - DWMA"; - break; - case 7: - indicator = new FMA_Series(source: bars.Select(this.DataSource), period: this.Period); - this.Name = $"Fibonacci Moving Average - FMA"; - break; - case 8: - indicator = new DEMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Double Exponential Moving Average - DEMA"; - break; - case 9: - indicator = new TEMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Triple Exponential Moving Average - TEMA"; - break; - case 10: - indicator = new ALMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Arnaud Legoux Moving Average - ALMA"; - break; - case 11: - indicator = new HMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Hull Moving Average - HMA"; - break; - case 12: - indicator = new HEMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Hull-Exponential Moving Average - HEMA"; - break; - case 13: - double factor= 1.015 * Math.Exp(-0.043 * (double)this.Period); - indicator = new MAMA_Series(source: bars.Select(this.DataSource), - fastlimit: factor, slowlimit: factor*0.1, - useNaN: false); - this.Name = $"MESA Adaptive Moving Average - MAMA"; - break; - case 14: - indicator = new KAMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Kaufman's Adaptive Moving Average - KAMA"; - break; - case 15: - indicator = new ZLEMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Zero Lag Exponential Moving Average - ZLEMA"; - break; - default: - indicator = new JMA_Series(source: bars.Select(this.DataSource), period: this.Period, useNaN: false); - this.Name = $"Jurik Moving Average - JMA"; - break; - } - this.Name = this.Name + $" ({Period}:{TBars.SelectStr(this.DataSource)})"; + public MovingAverage_chart() { + this.SeparateWindow = false; + this.Name = "2MA Crossover"; + this.AddLineSeries("MA1", Color.SeaGreen, 3, LineStyle.Solid); + this.AddLineSeries("MA2", Color.OrangeRed, 3, LineStyle.Solid); } - protected override void OnUpdate(UpdateArgs args) - { - bool update = !(args.Reason == UpdateReason.NewBar || - args.Reason == UpdateReason.HistoricalBar); - this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), - this.GetPrice(PriceType.High), - this.GetPrice(PriceType.Low), - this.GetPrice(PriceType.Close), - this.GetPrice(PriceType.Volume), update); - this.SetValue(this.indicator[this.indicator.Count - 1].v); - } + protected override void OnInit() { + this.bars = new(); + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + for (int i = this.History.Count - 1; i >= 0; i--) { + var rec = this.History[i, SeekOriginHistory.Begin]; + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } + this.Name = "Crossover[ "; + switch (MA1type) { + case 0: + MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA1 = new FMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); + this.Name += $"FMA"; + break; + case 8: + MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); + MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"JMA"; + break; + } + + this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; + + switch (MA2type) { + case 0: + MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA2 = new FMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); + this.Name += $"FMA"; + break; + case 8: + MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); + MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"JMA"; + break; + } + this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; + + overunder = new(MA1, MA2); + trades = new(MA1, MA2); + equity = new(trades,prices: bars.Open,Long:LongTrades,Short:ShortTrades,Warmup:MA1Period+MA2Period); + } + + protected override void OnUpdate(UpdateArgs args) { + bool update = !(args.Reason == UpdateReason.NewBar || + args.Reason == UpdateReason.HistoricalBar); + this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), + this.GetPrice(PriceType.High), + this.GetPrice(PriceType.Low), + this.GetPrice(PriceType.Close), + this.GetPrice(PriceType.Volume), update); + this.SetValue(this.MA1[^1].v, lineIndex: 0); + this.SetValue(this.MA2[^1].v, lineIndex: 1); + + if (trades[^1].v == 1) { + this.EndCloud(0, 1, Color.Empty); + if (LongTrades) { + this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.SeaGreen, upperIcon: IndicatorLineMarkerIconType.UpArrow)); + this.BeginCloud(0, 1, Color.FromArgb(127, Color.Green)); + } + if (ShortTrades) { + this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); + } + } + if (trades[^1].v == -1) { + this.EndCloud(0, 1, Color.Empty); + if (ShortTrades) { + this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); + this.BeginCloud(0, 1, Color.FromArgb(127, Color.Red)); + } + if (LongTrades) { + this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.SeaGreen, upperIcon: IndicatorLineMarkerIconType.DownArrow)); + } + } + } + public override void OnPaintChart(PaintChartEventArgs args) { + base.OnPaintChart(args); + if (this.CurrentChart == null) return; + Graphics graphics = args.Graphics; + var mainWindow = this.CurrentChart.MainWindow; + int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); + int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); + int historycount = HistoricalData.Count; + int ymax = mainWindow.ClientRectangle.Height; + int ymin = ymax - (int)(ymax / 4); + double eqmin = equity.v.Min(); + double eqmax = equity.v.Max(); + double proportion = (ymax-ymin) / (eqmax-eqmin); + + for (int i = leftIndex; i <= rightIndex; i++) { + int xi = (int)Math.Round(mainWindow.CoordinatesConverter.GetChartX(Time(Count - 1 - i))); + int width = this.CurrentChart.BarsWidth; + int height = (int)((equity[i+historycount].v) *proportion); + + Brush bb = Brushes.DarkSlateGray; + bb = (overunder[i+historycount].v>0 && LongTrades)? Brushes.Green : bb; + bb = (overunder[i + historycount].v < 0 && ShortTrades) ? Brushes.Red : bb; + + graphics.FillRectangle(bb, xi, ymax - height, width, height); + } + } } diff --git a/Indicators/Indicators.csproj b/Indicators/Indicators.csproj index 3ba70424..8c28acf6 100644 --- a/Indicators/Indicators.csproj +++ b/Indicators/Indicators.csproj @@ -16,6 +16,7 @@ 0.2.1.0 0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d 0.2.1-dev.2 + NETSDK1057 True diff --git a/Strategies/Strategies.csproj b/Strategies/Strategies.csproj index 88daf6b3..aa04c0c4 100644 --- a/Strategies/Strategies.csproj +++ b/Strategies/Strategies.csproj @@ -16,6 +16,7 @@ 0.2.1.0 0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d 0.2.1-dev.2 + NETSDK1057 True diff --git a/Tests/MovingAvg/ALMA_Test.cs b/Tests/MovingAvg/ALMA_Test.cs index 589ab75e..b35e0d4b 100644 --- a/Tests/MovingAvg/ALMA_Test.cs +++ b/Tests/MovingAvg/ALMA_Test.cs @@ -8,13 +8,14 @@ public class Update [Fact] public void Add_Test() { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries a = new() { Double.NaN, 0, 1, 2, 3, 4 }; ALMA_Series c = new(a, 4); Assert.Equal(6, c.Count); a.Add(5); Assert.Equal(a.Count, c.Count); a.Add(10, update: true); Assert.Equal(a.Count, c.Count); + Assert.Equal(0, a[1].v); } [Fact] diff --git a/Tests/MovingAvg/DWMA_Test.cs b/Tests/MovingAvg/DWMA_Test.cs new file mode 100644 index 00000000..471f1713 --- /dev/null +++ b/Tests/MovingAvg/DWMA_Test.cs @@ -0,0 +1,32 @@ +using Xunit; +using System; +using QuanTAlib; + +namespace MovingAvg; +public class DWMA_Test +{ + [Fact] + public void Add_Test() + { + TSeries a = new() { Double.NaN, 0, 1, 2, 3, 4 }; + DWMA_Series c = new(a, 3); + Assert.Equal(6, c.Count); + a.Add(5); + Assert.Equal(a.Count, c.Count); + a.Add(0, update: true); + Assert.Equal(a.Count, c.Count); + Assert.Equal(0, a[1].v); + } + + [Fact] + public void Edge_Test() + { + TSeries a = new() { double.NaN, double.Epsilon, double.PositiveInfinity, double.MaxValue }; + DWMA_Series c = new(a, 3); + Assert.Equal(a.Count, c.Count); + a.Add(double.NaN); + Assert.Equal(a.Count, c.Count); + a.Add(double.PositiveInfinity); + Assert.Equal(a.Count, c.Count); + } +} diff --git a/Tests/MovingAvg/EMA_Test.cs b/Tests/MovingAvg/EMA_Test.cs index dbba9ac8..fddc3279 100644 --- a/Tests/MovingAvg/EMA_Test.cs +++ b/Tests/MovingAvg/EMA_Test.cs @@ -8,13 +8,14 @@ public class EMA_Test [Fact] public void Add_Test() { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries a = new() { Double.NaN, 0, 1, 2, 3, 4 }; EMA_Series c = new(a, 3); Assert.Equal(6, c.Count); a.Add(5); Assert.Equal(a.Count, c.Count); a.Add(0, update: true); Assert.Equal(a.Count, c.Count); + Assert.Equal(0, a[1].v); } [Fact] diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 7751eda6..fb878d36 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -12,6 +12,10 @@ 0.2.1-dev.2 + + runtime; build; native; contentfiles; analyzers; buildtransitive + all +