ATR-based Trailing Stop (#18)

This commit is contained in:
Miha Kralj
2023-04-17 09:56:13 -07:00
committed by GitHub
35 changed files with 670 additions and 379 deletions
+8
View File
@@ -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
+53 -50
View File
@@ -10,13 +10,21 @@ on:
jobs: jobs:
build_test: build_test:
runs-on: windows-latest #runs-on: windows-latest
runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
############## Install tools
- name: Create Quantower folder at root
run: |
sudo mkdir -p /Quantower/
sudo chmod -R 777 /Quantower
- name: Install .NET - name: Install .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
@@ -34,31 +42,14 @@ jobs:
uses: gittools/actions/gitversion/execute@v0 uses: gittools/actions/gitversion/execute@v0
with: with:
useConfigFile: true useConfigFile: true
configFilePath: /a/QuanTAlib/QuanTAlib/GitVersion.yml #configFilePath: GitVersion.yml
updateAssemblyInfo: true 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 - name: Install JDK11 for Sonar Scanner
uses: actions/setup-java@v1 uses: actions/setup-java@v3
with: with:
java-version: 1.11 java-version: 11
distribution: 'zulu'
- name: Install JetBrains - name: Install JetBrains
run: dotnet tool install JetBrains.dotCover.GlobalTool --global run: dotnet tool install JetBrains.dotCover.GlobalTool --global
@@ -74,42 +65,52 @@ jobs:
run: dotnet sonarscanner begin /o:"mihakralj" /k:"mihakralj_QuanTAlib" run: dotnet sonarscanner begin /o:"mihakralj" /k:"mihakralj_QuanTAlib"
/d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
/d:sonar.host.url="https://sonarcloud.io" /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 - name: Build Main branch of QuanTAlib DLL
if: ${{ github.ref != 'refs/heads/dev' }} 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 - name: Build dev branch of QuanTAlib DLL
if: ${{ github.ref == 'refs/heads/dev' }} 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 - 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 - 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 - name: DotCover Test
run: dotnet dotcover test ./Tests/Tests.csproj --verbosity minimal --dcReportType=DetailedXML --dcoutput=./coveragereport.xml 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 uses: codecov/codecov-action@v3
with: with:
files: ./coveragereport.xml files: cover*
verbose: true verbose: true
- name: Sonar reporter - name: Upload to Sonar
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: dotnet sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" run: dotnet sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
- name: Codacy coverage reporter ############## Publish dev release
uses: codacy/codacy-coverage-reporter-action@v1
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
coverage-reports: ./coveragereport.xml
- name: Publish dev release assets - name: Publish dev release assets
if: ${{ github.ref == 'refs/heads/dev' }} if: ${{ github.ref == 'refs/heads/dev' }}
@@ -120,18 +121,20 @@ jobs:
prerelease: true prerelease: true
overwrite: true overwrite: true
release_name: ${{ steps.gitversion.outputs.SemVer }} release_name: ${{ steps.gitversion.outputs.SemVer }}
tag_name: ${{ steps.gitversion.outputs.SemVer }} tag_name: prerelease
release_config: | release_config: |
.\Calculations\bin\Release\net6.0\QuanTAlib.dll Calculations/bin/Release/net6.0/QuanTAlib.dll
.\Indicators\bin\Release\QuanTAlib_Indicators.dll Indicators/bin/Release/QuanTAlib_Indicators.dll
.\Strategies\bin\Release\QuanTAlib_Strategies.dll Strategies/bin/Release/QuanTAlib_Strategies.dll
- name: Push package to myget.org - 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 }} --api-key ${{ secrets.MYGET_DEPLOY_KEY_QUANTALIB }}
--source https://www.myget.org/F/quantalib/api/v2/package --source https://www.myget.org/F/quantalib/api/v2/package
--skip-duplicate --skip-duplicate
############## Publish main release
- name: Publish main release assets - name: Publish main release assets
if: ${{ github.ref == 'refs/heads/main' }} if: ${{ github.ref == 'refs/heads/main' }}
uses: SourceSprint/upload-multiple-releases@1.0.7 uses: SourceSprint/upload-multiple-releases@1.0.7
@@ -141,15 +144,15 @@ jobs:
prerelease: false prerelease: false
overwrite: true overwrite: true
release_name: ${{ steps.gitversion.outputs.MajorMinorPatch }} release_name: ${{ steps.gitversion.outputs.MajorMinorPatch }}
tag_name: ${{ steps.gitversion.outputs.MajorMinorPatch }} tag_name: latest
release_config: | release_config: |
.\Calculations\bin\Release\net6.0\QuanTAlib.dll Calculations/bin/Release/net6.0/QuanTAlib.dll
.\Indicators\bin\Release\QuanTAlib_Indicators.dll Indicators/bin/Release/QuanTAlib_Indicators.dll
.\Strategies\bin\Release\QuanTAlib_Strategies.dll Strategies/bin/Release/QuanTAlib_Strategies.dll
- name: Push package to nuget.org - name: Push package to nuget.org
if: ${{ github.ref == 'refs/heads/main' }} 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 }} --api-key ${{ secrets.NUGET_DEPLOY_KEY_QUANTLIB }}
--source https://api.nuget.org/v3/index.json --source https://api.nuget.org/v3/index.json
--skip-duplicate --skip-duplicate
+2 -1
View File
@@ -34,6 +34,7 @@
<AssemblyVersion>0.2.1.0</AssemblyVersion> <AssemblyVersion>0.2.1.0</AssemblyVersion>
<FileVersion>0.2.1.0</FileVersion> <FileVersion>0.2.1.0</FileVersion>
<InformationalVersion>0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d</InformationalVersion> <InformationalVersion>0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d</InformationalVersion>
<SuppressNETSdkWarningProperty>NETSDK1057</SuppressNETSdkWarningProperty>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType> <DebugType>full</DebugType>
@@ -60,7 +61,7 @@
<AdditionalFiles Include="..\.sonarlint\mihakralj_quantalib\CSharp\SonarLint.xml" Link="SonarLint.xml" /> <AdditionalFiles Include="..\.sonarlint\mihakralj_quantalib\CSharp\SonarLint.xml" Link="SonarLint.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\Docs\readme.md"> <None Include="..\docs\readme.md">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath></PackagePath> <PackagePath></PackagePath>
</None> </None>
+34
View File
@@ -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;
}
@@ -1,56 +1,63 @@
namespace QuanTAlib; namespace QuanTAlib;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Linq; using System.Linq;
/* <summary> /* <summary>
TSeries is the cornerstone of all QuanTAlib classess. TSeries is the cornerstone of all QuanTAlib classes.
TSeries is a single List of tuples (time, value) and contains several operators, casts, overloads TSeries is a single List of tuples (time, value) and contains several operators, casts, overloads
and other helpers that simplify usage of library. and other helpers that simplify usage of library.
Think of TSeries as an equivalent of Numpy array. Think of TSeries as an equivalent of Numpy array.
- includes Length property (to mimic array's method) - includes Length property (to mimic array's method)
- includes publishing and subscribing methods that attach to events - includes publishing and subscribing methods that attach to events
</summary> */ </summary> */
public class TSeriesEventArgs : EventArgs{
public bool update { get; set; }
}
public class TSeries : List<(DateTime t, double v)> { public class TSeries : List<(DateTime t, double v)> {
public static implicit operator (DateTime t, double v)(TSeries l) => l[^1]; 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 double(TSeries l) => l[^1].v;
public static implicit operator DateTime(TSeries l) => l[^1].t; public static implicit operator DateTime(TSeries l) => l[^1].t;
public List<DateTime> t => this.Select(item => item.t).ToList(); public List<DateTime> t => this.Select(item => item.t).ToList();
public List<double> v => this.Select(item => item.v).ToList(); public List<double> v => this.Select(item => item.v).ToList();
public int Length => this.Count; public int Length => this.Count;
public TSeries Tail(int count = 10) { public TSeries Tail(int count = 10) {
var tailSeries = new TSeries(); var tailSeries = new TSeries();
tailSeries.AddRange(this.Skip(Math.Max(0, this.Count - count)).Take(count)); tailSeries.AddRange(this.Skip(Math.Max(0, this.Count - count)).Take(count));
return tailSeries; 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; } if (update) { this[^1] = TValue; }
else { base.Add(TValue); } else { base.Add(TValue); }
OnEvent(update); OnEvent(update);
return TValue;
} }
public void Add(DateTime t, double v, bool update = false) => this.Add((t, v), update); 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); public void Add(double v, bool update = false) => this.Add((DateTime.Now, v), update);
protected virtual void OnEvent(bool update = false) { protected virtual void OnEvent(bool update = false) {
Pub?.Invoke(this, new TSeriesEventArgs { update = update }); } Pub?.Invoke(this, new TSeriesEventArgs { update = update });
}
public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); public delegate void NewDataEventHandler(object source, TSeriesEventArgs args);
public event NewDataEventHandler Pub; public event NewDataEventHandler Pub;
public void Sub(object source, TSeriesEventArgs e) { public void Sub(object source, TSeriesEventArgs e) {
TSeries ss = (TSeries)source; TSeries ss = (TSeries)source;
if (ss.Count > 0) { if (ss.Count > 0) {
this.AddRange(ss); this.AddRange(ss);
} else {
Add(ss[^1], e.update);
} }
} else {
} this.Add(ss[^1], e.update);
}
public class TSeriesEventArgs : EventArgs{ }
public bool update { get; set; }
} }
+64 -35
View File
@@ -13,46 +13,75 @@ EQUITY - Generates P&L portfolio based on trades signals and equity prices
//optional: warmup period: warmup //optional: warmup period: warmup
public class EQUITY_Series : Single_TSeries_Indicator { public class EQUITY_Series : Single_TSeries_Indicator {
int trade_state = 0; readonly TSeries inmarket; //for every bar
readonly int _warmup = 0; private readonly TSeries _price;
double eq_value = 0; private double _equity;
readonly TSeries _prices; private readonly double _capital;
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) { readonly int _warmup;
_prices = prices; double _cash;
_long = Long; int _units;
_short = Short; private bool _longbuy, _longsell;
_warmup = Warmup; 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); } if (base._data.Count > 0) { base.Add(base._data); }
} }
public override void Add((System.DateTime t, double v) TValue, bool update) { 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 (this.Count > _warmup) {
if (TValue.v == 1 && this.Count > _warmup) {
//we are not in-market and we can do long trades // harvest the gain-loss from previous day
if (_short) { trade_state = 0; } _investment_value = _units * _price[this.Count - 1].v;
if (_long) { trade_state = 1; } _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}");
} }
inmarket.Add(TValue.t, (double)_inmarket);
//sell signal base.Add((TValue.t, _equity), update, _NaN);
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);
} }
} }
+1 -1
View File
@@ -15,7 +15,7 @@ DECAY:
</summary> */ </summary> */
public class DECAY_Series : Single_TSeries_Indicator { public class DECAY_Series : Single_TSeries_Indicator {
private bool _exp; private readonly bool _exp;
private double _pdecay, _ppdecay; private double _pdecay, _ppdecay;
private readonly double _dfactor; private readonly double _dfactor;
+10 -8
View File
@@ -21,12 +21,14 @@ Sources:
public class LINREG_Series : Single_TSeries_Indicator public class LINREG_Series : Single_TSeries_Indicator
{ {
public readonly TSeries Intercept = new(); private readonly TSeries p_Intercept = new();
public readonly TSeries RSquared = new(); private readonly TSeries p_RSquared = new();
public readonly TSeries StdDev = new(); private readonly TSeries p_StdDev = new();
private readonly System.Collections.Generic.List<double> _buffer = new(); private readonly System.Collections.Generic.List<double> _buffer = new();
public TSeries Intercept => p_Intercept;
public LINREG_Series(TSeries source, int period, bool useNaN = false) public TSeries RSquared => p_RSquared;
public TSeries StdDev => p_StdDev;
public LINREG_Series(TSeries source, int period, bool useNaN = false)
: base(source, period, useNaN) : base(source, period, useNaN)
{ {
if (this._data.Count > 0) { base.Add(this._data); } 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); base.Add(ret, update, _NaN);
ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _intercept); 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); 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); ret = (TValue.t, this.Count < this._p - 1 && this._NaN ? double.NaN : _RSquared);
RSquared.Add(ret, update); p_RSquared.Add(ret, update);
} }
} }
+1 -1
View File
@@ -18,7 +18,7 @@ Remark:
public class SDEV_Series : Single_TSeries_Indicator 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); } if (base._data.Count > 0) { base.Add(base._data); }
} }
+1 -1
View File
@@ -14,7 +14,7 @@ Sources:
https://phemex.com/academy/what-is-arnaud-legoux-moving-averages https://phemex.com/academy/what-is-arnaud-legoux-moving-averages
https://www.prorealcode.com/prorealtime-indicators/alma-arnaud-legoux-moving-average/ 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)
</summary> */ </summary> */
+3 -6
View File
@@ -41,7 +41,7 @@ public class DEMA_Series : Single_TSeries_Indicator
_lastsum = _lastlastsum; _lastsum = _lastlastsum;
_lastema1 = _lastlastema1; _lastema1 = _lastlastema1;
_lastema2 = _lastlastema2; _lastema2 = _lastlastema2;
} }
else { else {
_lastlastsum = _lastsum; _lastlastsum = _lastsum;
_lastlastema1 = _lastema1; _lastlastema1 = _lastema1;
@@ -55,9 +55,6 @@ public class DEMA_Series : Single_TSeries_Indicator
} }
else if (_len <= _period && _useSMA && _period != 0) { else if (_len <= _period && _useSMA && _period != 0) {
_sum += TValue.v; _sum += TValue.v;
if (_period != 0 && _len > _period) {
_sum -= (_data[base.Count - _period - (update ? 1 : 0)].v);
}
_ema1 = _sum / Math.Min(_len, _period); _ema1 = _sum / Math.Min(_len, _period);
_ema2 = _ema1; _ema2 = _ema1;
} }
@@ -67,8 +64,8 @@ public class DEMA_Series : Single_TSeries_Indicator
} }
_dema = 2*_ema1 - _ema2; _dema = 2*_ema1 - _ema2;
_lastema1 = _ema1; _lastema1 = Double.IsNaN(_ema1)?_lastema1:_ema1;
_lastema2 = _ema2; _lastema2 = Double.IsNaN(_ema2)?_lastema2:_ema2;
base.Add((TValue.t, _dema), update, _NaN); base.Add((TValue.t, _dema), update, _NaN);
} }
+5 -7
View File
@@ -9,24 +9,22 @@ DWMA: Double Weighted Moving Average
</summary> */ </summary> */
public class DWMA_Series : Single_TSeries_Indicator { public class DWMA_Series : Single_TSeries_Indicator {
private readonly System.Collections.Generic.List<double> _buffer1 = new();
private readonly System.Collections.Generic.List<double> _weights = new();
public DWMA_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) { public DWMA_Series(TSeries source, int period, bool useNaN = false) : base(source, period, useNaN) {
for (int i = 0; i < this._p; i++) { for (int i = 0; i < this._p; i++) {
double _weight = (i + 1) * (i + 1); double _weight = (i + 1) * (i + 1);
this._weights.Add(_weight); this._weights.Add(_weight);
} }
if (base._data.Count > 0) { base.Add(base._data); } if (base._data.Count > 0) { base.Add(base._data); }
} }
private readonly System.Collections.Generic.List<double> _buffer1 = new();
private readonly System.Collections.Generic.List<double> _weights = new();
public override void Add((System.DateTime t, double v) TValue, bool update) { public override void Add((System.DateTime t, double v) TValue, bool update) {
Add_Replace_Trim(_buffer1, TValue.v, _p, update); Add_Replace_Trim(_buffer1, TValue.v, _p, update);
double _wma1 = 0; double _wma1 = 0, _wsum = 0;
double _wsum = 0;
for (int i = 0; i < _buffer1.Count; i++) { for (int i = 0; i < _buffer1.Count; i++) {
_wma1 += _buffer1[i] * this._weights[i]; _wma1 += _buffer1[i] * _weights[i];
_wsum += this._weights[i]; _wsum += _weights[i];
} }
_wma1 /= _wsum; _wma1 /= _wsum;
+1 -1
View File
@@ -60,7 +60,7 @@ public class EMA_Series : Single_TSeries_Indicator {
else { else {
_ema = _k * (TValue.v - _lastema) + _lastema; _ema = _k * (TValue.v - _lastema) + _lastema;
} }
_lastema = _ema; _lastema = Double.IsNaN(_ema)?_lastema:_ema;
base.Add((TValue.t, _ema), update, _NaN); base.Add((TValue.t, _ema), update, _NaN);
} }
+6 -4
View File
@@ -27,13 +27,14 @@ public class JMA_Series : Single_TSeries_Indicator {
public TSeries mma1 { get; } public TSeries mma1 { get; }
public TSeries mma2 { 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 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 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; 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) { 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; Kv = 0;
pr = (phase * 0.01) + 1.5; 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) { 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 (this.Count == 0) { prev_ma1 = prev_jma = TValue.v; }
if (update) { if (update) {
upperBand = p_upperBand; upperBand = p_upperBand;
@@ -95,8 +97,8 @@ public class JMA_Series : Single_TSeries_Indicator {
/// from avolty to rolty /// from avolty to rolty
double rvolty = (avolty != 0) ? volty / avolty : 0; double rvolty = (avolty != 0) ? volty / avolty : 0;
double len1 = (Math.Log(Math.Sqrt(_p)) / Math.Log(2.0)) + 2; double len1 = (Math.Log(Math.Sqrt(_p)) / Math.Log(2.0)) + 2;
if (len1 < 0) if (len1 < 0) { len1 = 0; }
len1 = 0;
double pow1 = Math.Max(len1 - 2.0, 0.5); 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 > Math.Pow(len1, 1.0 / pow1)) { rvolty = Math.Pow(len1, 1.0 / pow1); }
if (rvolty < 1) { rvolty = 1; } if (rvolty < 1) { rvolty = 1; }
+1 -1
View File
@@ -33,7 +33,7 @@ public class MAMA_Series : Single_TSeries_Indicator
public override void Add((System.DateTime t, double v) TValue, bool update) public override void Add((System.DateTime t, double v) TValue, bool update)
{ {
if (!update) { if (!update) {
// roll forward (oldx = x) // 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; 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;
+1 -11
View File
@@ -13,16 +13,6 @@ Sources:
https://technicalindicators.net/indicators-technical-analysis/150-t3-moving-average https://technicalindicators.net/indicators-technical-analysis/150-t3-moving-average
http://www.binarytribune.com/forex-trading-indicators/t3-moving-average-indicator/ 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
</summary> */ </summary> */
public class T3_Series : Single_TSeries_Indicator { public class T3_Series : Single_TSeries_Indicator {
private readonly double _k, _k1m, _c1, _c2, _c3, _c4; 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 _lastema1, _lastema2, _lastema3, _lastema4, _lastema5, _lastema6;
private double _llastema1, _llastema2, _llastema3, _llastema4, _llastema5, _llastema6; 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) { 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 double _a = vfactor; //0.7; //0.618
+1 -8
View File
@@ -9,13 +9,6 @@ TRIX: Triple Exponential Average
has become a popular technical analysis tool to aid chartists in spotting diversions has become a popular technical analysis tool to aid chartists in spotting diversions
and directional cues in stock trading patterns. and directional cues in stock trading patterns.
Calculation:
Ema1 = Ema (Close);
Ema2 = Ema (Ema1);
Ema3 = Ema (Ema2);
TRIX = (Ema3-Ema3[1]) / Ema3[1]
Sources: Sources:
https://www.investopedia.com/terms/t/trix.asp 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 _lastema1, _lastema2, _lastema3;
private double _llastema1, _llastema2, _llastema3; 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) public TRIX_Series(TSeries source, int period, bool useNaN = false, bool useSMA = true) : base(source, period, useNaN)
{ {
+1 -33
View File
@@ -6,7 +6,7 @@ ADO: Chaikin Accumulation/Distribution Oscillator
ADO measures the momentum of ADL using the difference between slow (10-day) EMA(ADL) ADO measures the momentum of ADL using the difference between slow (10-day) EMA(ADL)
and fast (3-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: Sources:
https://school.stockcharts.com/doku.php?id=technical_indicators:chaikin_oscillator 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);
}
}
*/
+2 -3
View File
@@ -26,7 +26,7 @@ public class CMO_Series : Single_TSeries_Indicator {
public override void Add((DateTime t, double v) TValue, bool update) { public override void Add((DateTime t, double v) TValue, bool update) {
if (this.Count == 0) { _plast_value = _last_value = TValue.v; } 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_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); 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); double _cmo = 100 * (_cmo_up - _cmo_dn) / (_cmo_up + _cmo_dn);
if (_cmo_up + _cmo_dn == 0) if (_cmo_up + _cmo_dn == 0) {_cmo = 0;}
_cmo = 0;
base.Add((TValue.t, _cmo), update, _NaN); base.Add((TValue.t, _cmo), update, _NaN);
} }
} }
-55
View File
@@ -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));
}
}
+43 -1
View File
@@ -3,9 +3,10 @@ using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using TradingPlatform.BusinessLayer; using TradingPlatform.BusinessLayer;
using TradingPlatform.BusinessLayer.Chart;
namespace QuanTAlib; namespace QuanTAlib;
public class JMA_chart : QuanTAlib_Indicator { public class JMA_chart : Indicator {
#region Parameters #region Parameters
[InputParameter("Data source", 0, variants: new object[] [InputParameter("Data source", 0, variants: new object[]
@@ -31,6 +32,12 @@ public class JMA_chart : QuanTAlib_Indicator {
private JMA_Series 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() { public JMA_chart() :base() {
Name = "JMA - Jurik Moving Avg"; Name = "JMA - Jurik Moving Avg";
Description = "Jurik Moving Average description"; Description = "Jurik Moving Average description";
@@ -42,6 +49,21 @@ public class JMA_chart : QuanTAlib_Indicator {
protected override void OnInit() { protected override void OnInit() {
base.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, indicator = new(source: bars.Select(DataSource), period: Period,
phase: Jphase, vshort: Vshort, vlong: Vlong, phase: Jphase, vshort: Vshort, vlong: Vlong,
useNaN: true); useNaN: true);
@@ -49,6 +71,26 @@ public class JMA_chart : QuanTAlib_Indicator {
protected override void OnUpdate(UpdateArgs args) { protected override void OnUpdate(UpdateArgs args) {
base.OnUpdate(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); 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));
}
} }
+89
View File
@@ -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);
}
}
+265 -121
View File
@@ -1,136 +1,280 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Linq;
using TradingPlatform.BusinessLayer; using TradingPlatform.BusinessLayer;
namespace QuanTAlib; namespace QuanTAlib;
public class MovingAverage_chart : Indicator public class MovingAverage_chart : Indicator {
{ #region Parameters
#region Parameters [InputParameter("MA1: Type", 0, variants: new object[]
[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[]
{ "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FMA", 7, "DEMA", 8, "TEMA", 9, { "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})] "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 #endregion Parameters
protected HistoricalData History; 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() public MovingAverage_chart() {
{ this.SeparateWindow = false;
this.SeparateWindow = false; this.Name = "2MA Crossover";
this.Name = "Flexible Moving Average"; this.AddLineSeries("MA1", Color.SeaGreen, 3, LineStyle.Solid);
this.AddLineSeries("MA", Color.Yellow, 3, LineStyle.Solid); this.AddLineSeries("MA2", Color.OrangeRed, 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)})";
} }
protected override void OnUpdate(UpdateArgs args) protected override void OnInit() {
{ this.bars = new();
bool update = !(args.Reason == UpdateReason.NewBar || this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime);
args.Reason == UpdateReason.HistoricalBar); for (int i = this.History.Count - 1; i >= 0; i--) {
this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), var rec = this.History[i, SeekOriginHistory.Begin];
this.GetPrice(PriceType.High), bars.Add(rec.TimeLeft, rec[PriceType.Open],
this.GetPrice(PriceType.Low), rec[PriceType.High], rec[PriceType.Low],
this.GetPrice(PriceType.Close), rec[PriceType.Close], rec[PriceType.Volume]);
this.GetPrice(PriceType.Volume), update); }
this.SetValue(this.indicator[this.indicator.Count - 1].v); 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);
}
}
} }
+1
View File
@@ -16,6 +16,7 @@
<FileVersion>0.2.1.0</FileVersion> <FileVersion>0.2.1.0</FileVersion>
<InformationalVersion>0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d</InformationalVersion> <InformationalVersion>0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d</InformationalVersion>
<Version>0.2.1-dev.2</Version> <Version>0.2.1-dev.2</Version>
<SuppressNETSdkWarningProperty>NETSDK1057</SuppressNETSdkWarningProperty>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>True</Optimize> <Optimize>True</Optimize>
+4 -4
View File
@@ -38,8 +38,8 @@ namespace SimpleMACross {
} }
protected override void OnRun() { protected override void OnRun() {
if (this.CurrentAccount != null && this.CurrentAccount.State == BusinessObjectState.Fake) this.CurrentAccount = Core.Instance.GetAccount(this.CurrentAccount.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.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) { 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); this.Log("Incorrect input parameters... Symbol or Account are not specified or they have different connectionID.", StrategyLoggingLevel.Error);
return; } return; }
@@ -59,12 +59,12 @@ namespace SimpleMACross {
private void OnUpdate() { private void OnUpdate() {
bool update = hdm.Last().TimeLeft - prev_time < this.period.Duration ? true : false; 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], 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); 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}");}
} }
+1
View File
@@ -16,6 +16,7 @@
<FileVersion>0.2.1.0</FileVersion> <FileVersion>0.2.1.0</FileVersion>
<InformationalVersion>0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d</InformationalVersion> <InformationalVersion>0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d</InformationalVersion>
<Version>0.2.1-dev.2</Version> <Version>0.2.1-dev.2</Version>
<SuppressNETSdkWarningProperty>NETSDK1057</SuppressNETSdkWarningProperty>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>True</Optimize> <Optimize>True</Optimize>
+1 -1
View File
@@ -9,7 +9,7 @@ public class TSeries_Test
public void InsertingTuple() public void InsertingTuple()
{ {
TSeries s = new() { (t: DateTime.Today, v: double.Epsilon) }; 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] [Fact]
+2 -1
View File
@@ -8,13 +8,14 @@ public class Update
[Fact] [Fact]
public void Add_Test() 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); ALMA_Series c = new(a, 4);
Assert.Equal(6, c.Count); Assert.Equal(6, c.Count);
a.Add(5); a.Add(5);
Assert.Equal(a.Count, c.Count); Assert.Equal(a.Count, c.Count);
a.Add(10, update: true); a.Add(10, update: true);
Assert.Equal(a.Count, c.Count); Assert.Equal(a.Count, c.Count);
Assert.Equal(0, a[1].v);
} }
[Fact] [Fact]
+32
View File
@@ -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);
}
}
+2 -1
View File
@@ -8,13 +8,14 @@ public class EMA_Test
[Fact] [Fact]
public void Add_Test() 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); EMA_Series c = new(a, 3);
Assert.Equal(6, c.Count); Assert.Equal(6, c.Count);
a.Add(5); a.Add(5);
Assert.Equal(a.Count, c.Count); Assert.Equal(a.Count, c.Count);
a.Add(0, update: true); a.Add(0, update: true);
Assert.Equal(a.Count, c.Count); Assert.Equal(a.Count, c.Count);
Assert.Equal(0, a[1].v);
} }
[Fact] [Fact]
+4
View File
@@ -12,6 +12,10 @@
<Version>0.2.1-dev.2</Version> <Version>0.2.1-dev.2</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Python.Included" Version="3.11.2" /> <PackageReference Include="Python.Included" Version="3.11.2" />
<PackageReference Include="pythonnet" Version="3.1.0-preview2023-03-04" /> <PackageReference Include="pythonnet" Version="3.1.0-preview2023-03-04" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />