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/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/Basics/Pair_TSeries_Abstract.cs b/Calculations/ClassStructures/Pair_TSeries_Abstract.cs similarity index 100% rename from Calculations/Basics/Pair_TSeries_Abstract.cs rename to Calculations/ClassStructures/Pair_TSeries_Abstract.cs diff --git a/Calculations/Basics/Single_TBars_Abstract.cs b/Calculations/ClassStructures/Single_TBars_Abstract.cs similarity index 100% rename from Calculations/Basics/Single_TBars_Abstract.cs rename to Calculations/ClassStructures/Single_TBars_Abstract.cs diff --git a/Calculations/Basics/Single_TSeries_Abstract.cs b/Calculations/ClassStructures/Single_TSeries_Abstract.cs similarity index 100% rename from Calculations/Basics/Single_TSeries_Abstract.cs rename to Calculations/ClassStructures/Single_TSeries_Abstract.cs diff --git a/Calculations/Basics/TBars.cs b/Calculations/ClassStructures/TBars.cs similarity index 100% rename from Calculations/Basics/TBars.cs rename to Calculations/ClassStructures/TBars.cs diff --git a/Calculations/ClassStructures/TOrders.cs b/Calculations/ClassStructures/TOrders.cs new file mode 100644 index 00000000..bb1a1e71 --- /dev/null +++ b/Calculations/ClassStructures/TOrders.cs @@ -0,0 +1,34 @@ +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Linq; + + +public enum OType { + NIL = 0, // No position + BTO = 1, // Buy to Open + STC = 2, // Sell to Close + STO = 3, // Sell to Open + BTC = 4, // Buy to Close + END = 5, // Exit the trade +} + + +public class TOrders : List<(DateTime t, OType o)> { + + public void Add((DateTime t, OType o) TOrder, bool update = false) + { + if (update) { this[^1] = TOrder; } + else { base.Add(TOrder); } + OnEvent(update); + } + + + protected virtual void OnEvent(bool update = false) { + Pub?.Invoke(this, new TSeriesEventArgs { update = update }); } + public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); + public event NewDataEventHandler Pub; + +} \ No newline at end of file diff --git a/Calculations/Basics/TSeries.cs b/Calculations/ClassStructures/TSeries.cs similarity index 52% rename from Calculations/Basics/TSeries.cs rename to Calculations/ClassStructures/TSeries.cs index 15f53d9f..eafec24d 100644 --- a/Calculations/Basics/TSeries.cs +++ b/Calculations/ClassStructures/TSeries.cs @@ -1,56 +1,63 @@ namespace QuanTAlib; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; using System.Linq; /* -TSeries is the cornerstone of all QuanTAlib classess. - TSeries is a single List of tuples (time, value) and contains several operators, casts, overloads - and other helpers that simplify usage of library. +TSeries is the cornerstone of all QuanTAlib classes. + TSeries is a single List of tuples (time, value) and contains several operators, casts, overloads + and other helpers that simplify usage of library. Think of TSeries as an equivalent of Numpy array. - + - includes Length property (to mimic array's method) - includes publishing and subscribing methods that attach to events */ + + +public class TSeriesEventArgs : EventArgs{ + public bool update { get; set; } +} + 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 List t => this.Select(item => item.t).ToList(); + public List v => this.Select(item => item.v).ToList(); + public int Length => this.Count; public TSeries Tail(int count = 10) { var tailSeries = new TSeries(); tailSeries.AddRange(this.Skip(Math.Max(0, this.Count - count)).Take(count)); return tailSeries; } - public void Add((DateTime t, double v) TValue, bool update = false) { + public (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { if (update) { this[^1] = TValue; } else { base.Add(TValue); } OnEvent(update); + return TValue; } public void Add(DateTime t, double v, bool update = false) => this.Add((t, v), update); - public void Add(double v, bool update = false) => this.Add((DateTime.Now, v), update); - protected virtual void OnEvent(bool update = false) { - Pub?.Invoke(this, new TSeriesEventArgs { update = update }); } + public void Add(double v, bool update = false) => this.Add((DateTime.Now, v), update); + protected virtual void OnEvent(bool update = false) { + Pub?.Invoke(this, new TSeriesEventArgs { update = update }); + } - public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); - public event NewDataEventHandler Pub; + public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); + public event NewDataEventHandler Pub; - public void Sub(object source, TSeriesEventArgs e) { - TSeries ss = (TSeries)source; - if (ss.Count > 0) { - this.AddRange(ss); - } else { - Add(ss[^1], e.update); + public void Sub(object source, TSeriesEventArgs e) { + TSeries ss = (TSeries)source; + if (ss.Count > 0) { + this.AddRange(ss); } - } -} - -public class TSeriesEventArgs : EventArgs{ - public bool update { get; set; } + else { + this.Add(ss[^1], e.update); + } + } } diff --git a/Calculations/Logic/EQUITY_Series.cs b/Calculations/Logic/EQUITY_Series.cs index 63e8eea5..1257352d 100644 --- a/Calculations/Logic/EQUITY_Series.cs +++ b/Calculations/Logic/EQUITY_Series.cs @@ -13,46 +13,75 @@ EQUITY - Generates P&L portfolio based on trades signals and equity prices //optional: warmup period: warmup public class EQUITY_Series : Single_TSeries_Indicator { - int trade_state = 0; - readonly int _warmup = 0; - double eq_value = 0; - readonly TSeries _prices; - readonly bool _long, _short; - public EQUITY_Series(TSeries trades, TSeries prices, bool Long = true, bool Short = false, int Warmup = 0) : base(trades, period: 0, useNaN: false) { - _prices = prices; - _long = Long; - _short = Short; - _warmup = Warmup; + readonly TSeries inmarket; //for every bar + private readonly TSeries _price; + private double _equity; + private readonly double _capital; + + readonly int _warmup; + double _cash; + int _units; + private bool _longbuy, _longsell; + double _long_order, _open_order; + double _investment_value; + short _inmarket; + + public EQUITY_Series(TSeries signal, TSeries price, int warmup = 0, double capital = 1000) : base(signal, period: 0, useNaN: false) { + _capital = capital; + _cash = _capital; + _investment_value = 0; + _warmup = (warmup > 0) ? warmup : 1; + + inmarket = new(); + _longbuy = _longsell = false; + _open_order = 0; + _inmarket = 0; + _units = 0; + _long_order = 0; + + _price = price; //we buy on the Open price of the NEXT bar + _long_order = 0; + if (base._data.Count > 0) { base.Add(base._data); } } public override void Add((System.DateTime t, double v) TValue, bool update) { - if (this.Count != 0) - eq_value = this[this.Count - 1].v; - //buy signal - if (TValue.v == 1 && this.Count > _warmup) { - //we are not in-market and we can do long trades - if (_short) { trade_state = 0; } - if (_long) { trade_state = 1; } + if (this.Count > _warmup) { + + // harvest the gain-loss from previous day + _investment_value = _units * _price[this.Count - 1].v; + _equity = _cash + _investment_value; + + + //execute orders from previous bar + if (_longbuy && _inmarket == 0) { //time to execute the long buy + _units = (int)(_cash / _price[this.Count - 1].v); + _long_order = _units * _price[this.Count - 1].v; + _cash -= _long_order; + _open_order = _long_order; + _equity = _cash + _open_order; + _inmarket = 1; + _longbuy = false; + } + + if (_longsell && _inmarket == 1) { //time to execute the long sell + _long_order = (_units * _price[this.Count - 1].v); + _cash += _long_order; + _units = 0; + + _open_order = 0; + _equity = _cash + _open_order; + _inmarket = 0; + _longsell = false; + } + + if (_inmarket == 0 && TValue.v == 1) { _longbuy = true; } //out of market, enter long + if (_inmarket == 1 && TValue.v == -1) { _longsell = true; } //long market, exit long + + //Console.WriteLine($"{TValue.v,3}\t {(_inmarket)} : {_cash,10:f2} + {_units*_price[this.Count-1].v,7:f2} = {_equity-_capital:f2}"); } - - //sell signal - if (TValue.v == -1 && this.Count > _warmup) { - //we are in-market and we can do long trades - if (_long) { trade_state = 0; } - if (_short) { trade_state = -1; } - } - - if (trade_state == 1) { - eq_value = this[this.Count - 1].v + (_prices[this.Count].v - _prices[this.Count - 1].v); - - } - - if (trade_state == -1) { - eq_value = this[this.Count - 1].v + (_prices[this.Count - 1].v - _prices[this.Count].v); - - } - base.Add((TValue.t, eq_value), update, _NaN); + inmarket.Add(TValue.t, (double)_inmarket); + base.Add((TValue.t, _equity), update, _NaN); } } \ No newline at end of file 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/Statistics/SDEV_Series.cs b/Calculations/Statistics/SDEV_Series.cs index 88f6ed8f..b85c3a16 100644 --- a/Calculations/Statistics/SDEV_Series.cs +++ b/Calculations/Statistics/SDEV_Series.cs @@ -18,7 +18,7 @@ Remark: public class SDEV_Series : Single_TSeries_Indicator { - public SDEV_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) + public SDEV_Series(TSeries source, int period=0, bool useNaN = false) : base(source, period, useNaN) { if (base._data.Count > 0) { base.Add(base._data); } } 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..6dea06e5 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; @@ -55,9 +55,6 @@ public class DEMA_Series : Single_TSeries_Indicator } else if (_len <= _period && _useSMA && _period != 0) { _sum += TValue.v; - if (_period != 0 && _len > _period) { - _sum -= (_data[base.Count - _period - (update ? 1 : 0)].v); - } _ema1 = _sum / Math.Min(_len, _period); _ema2 = _ema1; } @@ -67,8 +64,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..f3d221a3 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; @@ -95,8 +97,8 @@ public class JMA_Series : Single_TSeries_Indicator { /// from avolty to rolty double rvolty = (avolty != 0) ? volty / avolty : 0; double len1 = (Math.Log(Math.Sqrt(_p)) / Math.Log(2.0)) + 2; - if (len1 < 0) - len1 = 0; + if (len1 < 0) { len1 = 0; } + double pow1 = Math.Max(len1 - 2.0, 0.5); if (rvolty > Math.Pow(len1, 1.0 / pow1)) { rvolty = Math.Pow(len1, 1.0 / pow1); } if (rvolty < 1) { rvolty = 1; } 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/Calculations/Volatility/CMO_Series.cs b/Calculations/Volatility/CMO_Series.cs index 9295dfa5..6cabc235 100644 --- a/Calculations/Volatility/CMO_Series.cs +++ b/Calculations/Volatility/CMO_Series.cs @@ -26,7 +26,7 @@ public class CMO_Series : Single_TSeries_Indicator { public override void Add((DateTime t, double v) TValue, bool update) { if (this.Count == 0) { _plast_value = _last_value = TValue.v; } - if (update) _last_value = _plast_value; else _plast_value = _last_value; + if (update) {_last_value = _plast_value;} else {_plast_value = _last_value;} Add_Replace_Trim(_buff_up, (TValue.v > _last_value) ? TValue.v-_last_value : 0, _p, update); Add_Replace_Trim(_buff_dn, (TValue.v < _last_value) ? _last_value-TValue.v : 0, _p, update); @@ -40,8 +40,7 @@ public class CMO_Series : Single_TSeries_Indicator { } double _cmo = 100 * (_cmo_up - _cmo_dn) / (_cmo_up + _cmo_dn); - if (_cmo_up + _cmo_dn == 0) - _cmo = 0; + if (_cmo_up + _cmo_dn == 0) {_cmo = 0;} base.Add((TValue.t, _cmo), update, _NaN); } } \ No newline at end of file diff --git a/Indicators/Basics/QuanTAlib_Indicator.cs b/Indicators/Basics/QuanTAlib_Indicator.cs deleted file mode 100644 index 483ca3c5..00000000 --- a/Indicators/Basics/QuanTAlib_Indicator.cs +++ /dev/null @@ -1,55 +0,0 @@ -using TradingPlatform.BusinessLayer; -using System.Drawing; -using QuanTAlib; -using System; -using TradingPlatform.BusinessLayer.Chart; - -namespace QuanTAlib; - -public abstract class QuanTAlib_Indicator : Indicator { - protected TBars bars; - protected IChartWindow mainWindow; - protected Graphics graphics; - protected int firstOnScreenBarIndex, lastOnScreenBarIndex; - protected HistoricalData History; - protected int HistPeriod; - - protected override void OnInit() { - base.OnInit(); - bars = new(); - var dur1 = this.HistoricalData.FromTime; - var dur = this.HistoricalData.Period.Duration.TotalSeconds * (HistPeriod*4) ; //seconds of two periods - - 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]); - } - } - - protected override void OnUpdate(UpdateArgs args) { - base.OnUpdate(args); - bars.Add(Time(), GetPrice(PriceType.Open), - GetPrice(PriceType.High), - GetPrice(PriceType.Low), - GetPrice(PriceType.Close), - GetPrice(PriceType.Volume), - update: !(args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar)); - } - public override void OnPaintChart(PaintChartEventArgs args) { - base.OnPaintChart(args); - if (this.CurrentChart == null) return; - graphics = args.Graphics; - mainWindow = this.CurrentChart.MainWindow; - - DateTime leftTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left); - DateTime rightTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right); - firstOnScreenBarIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(leftTime); - lastOnScreenBarIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(rightTime)); - } -} \ No newline at end of file diff --git a/Indicators/Charts/JMA_chart.cs b/Indicators/Charts/JMA_chart.cs index e2166ef4..366d759b 100644 --- a/Indicators/Charts/JMA_chart.cs +++ b/Indicators/Charts/JMA_chart.cs @@ -3,9 +3,10 @@ using System.Diagnostics; using System.Drawing; using System.Linq; using TradingPlatform.BusinessLayer; +using TradingPlatform.BusinessLayer.Chart; namespace QuanTAlib; -public class JMA_chart : QuanTAlib_Indicator { +public class JMA_chart : Indicator { #region Parameters [InputParameter("Data source", 0, variants: new object[] @@ -31,6 +32,12 @@ public class JMA_chart : QuanTAlib_Indicator { private JMA_Series indicator; /////// + protected TBars bars; + protected IChartWindow mainWindow; + protected Graphics graphics; + protected int firstOnScreenBarIndex, lastOnScreenBarIndex; + protected HistoricalData History; + protected int HistPeriod; public JMA_chart() :base() { Name = "JMA - Jurik Moving Avg"; Description = "Jurik Moving Average description"; @@ -42,6 +49,21 @@ public class JMA_chart : QuanTAlib_Indicator { protected override void OnInit() { base.OnInit(); + bars = new(); + var dur1 = this.HistoricalData.FromTime; + var dur = this.HistoricalData.Period.Duration.TotalSeconds * (HistPeriod * 4); //seconds of two periods + + 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]); + } + indicator = new(source: bars.Select(DataSource), period: Period, phase: Jphase, vshort: Vshort, vlong: Vlong, useNaN: true); @@ -49,6 +71,26 @@ public class JMA_chart : QuanTAlib_Indicator { protected override void OnUpdate(UpdateArgs args) { base.OnUpdate(args); + bars.Add(Time(), GetPrice(PriceType.Open), + GetPrice(PriceType.High), + GetPrice(PriceType.Low), + GetPrice(PriceType.Close), + GetPrice(PriceType.Volume), + update: !(args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar)); + this.SetValue(indicator[^1].v, lineIndex: 0); } + public override void OnPaintChart(PaintChartEventArgs args) { + base.OnPaintChart(args); + if (this.CurrentChart == null) + return; + graphics = args.Graphics; + mainWindow = this.CurrentChart.MainWindow; + + DateTime leftTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left); + DateTime rightTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right); + firstOnScreenBarIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(leftTime); + lastOnScreenBarIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(rightTime)); + } + } diff --git a/Indicators/Charts/TrailingStop.cs b/Indicators/Charts/TrailingStop.cs new file mode 100644 index 00000000..3df040ca --- /dev/null +++ b/Indicators/Charts/TrailingStop.cs @@ -0,0 +1,89 @@ +using System; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class TrailingStop_chart : Indicator { + #region Parameters + + [InputParameter("Period", 0, 1, 100, 1, 1)] + protected int _period = 30; + + [InputParameter("Factor", 1, 1, 100, 0.1, 1)] + protected double _factor = 10; + + [InputParameter("Long TS", 2)] + private bool _LongTS = true; + + [InputParameter("Short TS", 3)] + private bool _ShortTS = false; + + #endregion Parameters + + /////// + private HistoricalData History; + private TBars bars; + private ATR_Series _atr; + + private double _tslineL, _ratchetL, _tslineS, _ratchetS; + /////// + + public TrailingStop_chart() :base() { + Name = $"ATR Trailing Stop"; + AddLineSeries(lineName: "TrailingATR Long", lineColor: Color.Yellow, lineWidth: 1,lineStyle: LineStyle.Dot); + AddLineSeries(lineName: "Ratchet Long", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); + + AddLineSeries(lineName: "TrailingATR Short", lineColor: Color.Yellow, lineWidth: 1, lineStyle: LineStyle.Dot); + AddLineSeries(lineName: "Ratchet Short", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); + SeparateWindow = false; + } + + + protected override void OnInit() { + this.Name = $"Trailing Stop (ATR:{_period}, Mult:{_factor:f2})"; + 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]); + } + _atr = new(source: bars, _period, useNaN: true); + _ratchetL = Double.NegativeInfinity; + _ratchetS = Double.PositiveInfinity; + + this.LinesSeries[0].Visible = _LongTS; + this.LinesSeries[1].Visible = _LongTS; + this.LinesSeries[2].Visible = _ShortTS; + this.LinesSeries[3].Visible = _ShortTS; + } + + 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); + + _tslineL = bars.High[^1].v - (_factor * _atr[^1].v); + _ratchetL = Math.Max(_tslineL,_ratchetL); + _ratchetL = (_ratchetL > bars.Low[^1].v) ? _tslineL : _ratchetL; + + _tslineS = bars.Low[^1].v + (_factor * _atr[^1].v); + _ratchetS = Math.Min(_tslineS, _ratchetS); + _ratchetS = (_ratchetS < bars.High[^1].v) ? _tslineS : _ratchetS; + + + this.SetValue(_tslineL, lineIndex: 0); + this.SetValue(_ratchetL, lineIndex: 1); + this.SetValue(_tslineS, lineIndex: 2); + this.SetValue(_ratchetS, lineIndex: 3); + } +} + diff --git a/Indicators/Charts/xMA_chart.cs b/Indicators/Charts/xMA_chart.cs index f66b43cf..a6b412b6 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,price: bars.Open,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/SimpleMACross1.cs b/Strategies/SimpleMACross1.cs index 98e14aa2..117af5ca 100644 --- a/Strategies/SimpleMACross1.cs +++ b/Strategies/SimpleMACross1.cs @@ -38,8 +38,8 @@ namespace SimpleMACross { } protected override void OnRun() { - if (this.CurrentAccount != null && this.CurrentAccount.State == BusinessObjectState.Fake) this.CurrentAccount = Core.Instance.GetAccount(this.CurrentAccount.CreateInfo()); - if (this.CurrentSymbol != null && this.CurrentSymbol.State == BusinessObjectState.Fake) this.CurrentSymbol = Core.Instance.GetSymbol(this.CurrentSymbol.CreateInfo()); + if (this.CurrentAccount != null && this.CurrentAccount.State == BusinessObjectState.Fake) {this.CurrentAccount = Core.Instance.GetAccount(this.CurrentAccount.CreateInfo());} + if (this.CurrentSymbol != null && this.CurrentSymbol.State == BusinessObjectState.Fake) {this.CurrentSymbol = Core.Instance.GetSymbol(this.CurrentSymbol.CreateInfo());} if (this.CurrentSymbol == null || this.CurrentAccount == null || this.CurrentSymbol.ConnectionId != this.CurrentAccount.ConnectionId) { this.Log("Incorrect input parameters... Symbol or Account are not specified or they have different connectionID.", StrategyLoggingLevel.Error); return; } @@ -59,12 +59,12 @@ namespace SimpleMACross { private void OnUpdate() { bool update = hdm.Last().TimeLeft - prev_time < this.period.Duration ? true : false; - if (!update) prev_time = hdm.Last().TimeLeft; + if (!update) {prev_time = hdm.Last().TimeLeft;} bars.Add(hdm.Last().TimeLeft, hdm.Last()[PriceType.Open], hdm.Last()[PriceType.High], hdm.Last()[PriceType.Low], hdm.Last()[PriceType.Close], hdm.Last()[PriceType.Volume], update); - if (!update) this.LogInfo($"{bars.Close.Last().t} OHLC4:{(double)bars.OHLC4}"); + if (!update) {this.LogInfo($"{bars.Close.Last().t} OHLC4:{(double)bars.OHLC4}");} } 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/Basics/TSeries_Test.cs b/Tests/Basics/TSeries_Test.cs index 84bf9d42..2344e85c 100644 --- a/Tests/Basics/TSeries_Test.cs +++ b/Tests/Basics/TSeries_Test.cs @@ -9,7 +9,7 @@ public class TSeries_Test public void InsertingTuple() { TSeries s = new() { (t: DateTime.Today, v: double.Epsilon) }; - Assert.Equal((DateTime.Today, double.Epsilon), s); + Assert.Equal((DateTime.Today, double.Epsilon), s[^1]); } [Fact] 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 +