From bdc01bff4aabe4d26848e11f663f0704c4d846b5 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Mon, 30 Sep 2024 06:46:07 -0700 Subject: [PATCH] Atr --- .sonarlint/QuanTAlib.json | 4 - .vscode/settings.json | 13 + Directory.Build.props | 1 + QuanTAlib.sln | 81 +- SyntheticVendor/SyntheticVendor.cs | 5 +- Tests/test_consistency.cs | 857 ---------------------- Tests/test_iTBar.cs | 58 ++ Tests/test_iTValue.cs | 97 +++ Tests/test_skender.stock.cs | 20 + docs/Progress.md | 5 + docs/index.html | 34 +- docs/readme.md | 7 +- lib/averages/Ama.cs | 89 --- lib/averages/Mama.cs | 1 - lib/averages/T3.cs | 40 +- lib/core/AbstractBarBase.cs | 71 ++ lib/core/tbar.cs | 3 +- lib/core/tvalue.cs | 2 +- lib/quantalib.csproj | 1 - lib/volatility/Atr.cs | 71 ++ notebooks/Skender.dib | 45 ++ quantower/Averages/AmaIndicator.cs | 25 - quantower/Averages/Averages.csproj | 1 - quantower/Statistics/Statistics.csproj | 1 - quantower/Statistics/_IndicatorBarBase.cs | 136 ++++ quantower/Volatility/AtrIndicator.cs | 23 + quantower/Volatility/Volatility.csproj | 29 + quantower/Volatility/_IndicatorBarBase.cs | 136 ++++ 28 files changed, 803 insertions(+), 1053 deletions(-) delete mode 100644 .sonarlint/QuanTAlib.json create mode 100644 .vscode/settings.json delete mode 100644 Tests/test_consistency.cs create mode 100644 Tests/test_iTBar.cs create mode 100644 Tests/test_iTValue.cs create mode 100644 docs/Progress.md delete mode 100644 lib/averages/Ama.cs create mode 100644 lib/core/AbstractBarBase.cs create mode 100644 lib/volatility/Atr.cs create mode 100644 notebooks/Skender.dib delete mode 100644 quantower/Averages/AmaIndicator.cs create mode 100644 quantower/Statistics/_IndicatorBarBase.cs create mode 100644 quantower/Volatility/AtrIndicator.cs create mode 100644 quantower/Volatility/Volatility.csproj create mode 100644 quantower/Volatility/_IndicatorBarBase.cs diff --git a/.sonarlint/QuanTAlib.json b/.sonarlint/QuanTAlib.json deleted file mode 100644 index 482e31c0..00000000 --- a/.sonarlint/QuanTAlib.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "SonarCloudOrganization": "mihakralj", - "ProjectKey": "mihakralj_QuanTAlib" -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e77d093f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "sonarlint.connectedMode.connections.sonarcloud": [ + { + "organizationKey": "mihakralj", + "token": "6df7cd62a17dc4e1c5532df1da2f49d5a977dd50", + "connectionId": "mihakralj" + } + ], + "sonarlint.connectedMode.project": { + "connectionId": "mihakralj", + "projectKey": "mihakralj_QuanTAlib" + } +} \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 48962b26..9aa0211e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,6 +35,7 @@ all runtime; build; native; contentfiles; analyzers + diff --git a/QuanTAlib.sln b/QuanTAlib.sln index 7b1d3247..8727aad2 100644 --- a/QuanTAlib.sln +++ b/QuanTAlib.sln @@ -1,57 +1,68 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 +VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "quantalib", "lib\quantalib.csproj", "{584E06A9-CEB4-476A-85CC-6A8FF3974AE2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "benchmark", "benchmark\benchmark.csproj", "{DBB674D9-43AA-4383-A4D0-E791847C7145}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{D85FEBB4-B651-466F-85CC-FD902378D4D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "quantalib", "lib\quantalib.csproj", "{D2F742C0-9E2A-4B07-8D9F-0DF0BA3DDA31}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MovingAverages", "quantower\Averages\Averages.csproj", "{32CC09CC-26E3-4FCE-8932-C0513C4AD766}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SyntheticVendor", "SyntheticVendor\SyntheticVendor.csproj", "{1777C7BB-F67A-4F00-99BB-EE48178A7129}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyntheticVendor", "SyntheticVendor\SyntheticVendor.csproj", "{20B1B5F1-8C36-4668-B0AE-951C13AE197B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "tests\Tests.csproj", "{A89D1A5D-9DB9-4E5D-A7D6-E153509430CD}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "quantower", "quantower", "{A8D9AE68-24E3-476C-BB98-244541BB4B43}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "quantower", "quantower", "{1EC1B338-6AD9-4E3D-82C3-6591CC148DD5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Statistics", "quantower\Statistics\Statistics.csproj", "{B6D3EB11-63B6-430F-B526-E1981B3D8214}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Averages", "quantower\Averages\Averages.csproj", "{61AFF1E5-9E2B-47F4-9665-D8358C5BDD6F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "benchmark", "benchmark\benchmark.csproj", "{6629C6D2-FC34-4BDF-AEF4-A5859E05981E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Statistics", "quantower\Statistics\Statistics.csproj", "{7EBB6ECD-15FA-44DD-B231-FED97E0B710D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volatility", "quantower\Volatility\Volatility.csproj", "{9190CFDD-9FD8-4E14-9269-79325B03EC6C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DBB674D9-43AA-4383-A4D0-E791847C7145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBB674D9-43AA-4383-A4D0-E791847C7145}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBB674D9-43AA-4383-A4D0-E791847C7145}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBB674D9-43AA-4383-A4D0-E791847C7145}.Release|Any CPU.Build.0 = Release|Any CPU + {D2F742C0-9E2A-4B07-8D9F-0DF0BA3DDA31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2F742C0-9E2A-4B07-8D9F-0DF0BA3DDA31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2F742C0-9E2A-4B07-8D9F-0DF0BA3DDA31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2F742C0-9E2A-4B07-8D9F-0DF0BA3DDA31}.Release|Any CPU.Build.0 = Release|Any CPU + {1777C7BB-F67A-4F00-99BB-EE48178A7129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1777C7BB-F67A-4F00-99BB-EE48178A7129}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1777C7BB-F67A-4F00-99BB-EE48178A7129}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1777C7BB-F67A-4F00-99BB-EE48178A7129}.Release|Any CPU.Build.0 = Release|Any CPU + {A89D1A5D-9DB9-4E5D-A7D6-E153509430CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A89D1A5D-9DB9-4E5D-A7D6-E153509430CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A89D1A5D-9DB9-4E5D-A7D6-E153509430CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A89D1A5D-9DB9-4E5D-A7D6-E153509430CD}.Release|Any CPU.Build.0 = Release|Any CPU + {61AFF1E5-9E2B-47F4-9665-D8358C5BDD6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61AFF1E5-9E2B-47F4-9665-D8358C5BDD6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61AFF1E5-9E2B-47F4-9665-D8358C5BDD6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61AFF1E5-9E2B-47F4-9665-D8358C5BDD6F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EBB6ECD-15FA-44DD-B231-FED97E0B710D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EBB6ECD-15FA-44DD-B231-FED97E0B710D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EBB6ECD-15FA-44DD-B231-FED97E0B710D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EBB6ECD-15FA-44DD-B231-FED97E0B710D}.Release|Any CPU.Build.0 = Release|Any CPU + {9190CFDD-9FD8-4E14-9269-79325B03EC6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9190CFDD-9FD8-4E14-9269-79325B03EC6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9190CFDD-9FD8-4E14-9269-79325B03EC6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9190CFDD-9FD8-4E14-9269-79325B03EC6C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {584E06A9-CEB4-476A-85CC-6A8FF3974AE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {584E06A9-CEB4-476A-85CC-6A8FF3974AE2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {584E06A9-CEB4-476A-85CC-6A8FF3974AE2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {584E06A9-CEB4-476A-85CC-6A8FF3974AE2}.Release|Any CPU.Build.0 = Release|Any CPU - {D85FEBB4-B651-466F-85CC-FD902378D4D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D85FEBB4-B651-466F-85CC-FD902378D4D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D85FEBB4-B651-466F-85CC-FD902378D4D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D85FEBB4-B651-466F-85CC-FD902378D4D2}.Release|Any CPU.Build.0 = Release|Any CPU - {32CC09CC-26E3-4FCE-8932-C0513C4AD766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32CC09CC-26E3-4FCE-8932-C0513C4AD766}.Debug|Any CPU.Build.0 = Debug|Any CPU - {32CC09CC-26E3-4FCE-8932-C0513C4AD766}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32CC09CC-26E3-4FCE-8932-C0513C4AD766}.Release|Any CPU.Build.0 = Release|Any CPU - {20B1B5F1-8C36-4668-B0AE-951C13AE197B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {20B1B5F1-8C36-4668-B0AE-951C13AE197B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {20B1B5F1-8C36-4668-B0AE-951C13AE197B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {20B1B5F1-8C36-4668-B0AE-951C13AE197B}.Release|Any CPU.Build.0 = Release|Any CPU - {B6D3EB11-63B6-430F-B526-E1981B3D8214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6D3EB11-63B6-430F-B526-E1981B3D8214}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6D3EB11-63B6-430F-B526-E1981B3D8214}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6D3EB11-63B6-430F-B526-E1981B3D8214}.Release|Any CPU.Build.0 = Release|Any CPU - {6629C6D2-FC34-4BDF-AEF4-A5859E05981E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6629C6D2-FC34-4BDF-AEF4-A5859E05981E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6629C6D2-FC34-4BDF-AEF4-A5859E05981E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6629C6D2-FC34-4BDF-AEF4-A5859E05981E}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection GlobalSection(NestedProjects) = preSolution - {B6D3EB11-63B6-430F-B526-E1981B3D8214} = {A8D9AE68-24E3-476C-BB98-244541BB4B43} + {61AFF1E5-9E2B-47F4-9665-D8358C5BDD6F} = {1EC1B338-6AD9-4E3D-82C3-6591CC148DD5} + {7EBB6ECD-15FA-44DD-B231-FED97E0B710D} = {1EC1B338-6AD9-4E3D-82C3-6591CC148DD5} + {9190CFDD-9FD8-4E14-9269-79325B03EC6C} = {1EC1B338-6AD9-4E3D-82C3-6591CC148DD5} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {080D10E4-B741-4A85-B37B-08845A5C78D9} EndGlobalSection EndGlobal diff --git a/SyntheticVendor/SyntheticVendor.cs b/SyntheticVendor/SyntheticVendor.cs index a770055d..03d49548 100644 --- a/SyntheticVendor/SyntheticVendor.cs +++ b/SyntheticVendor/SyntheticVendor.cs @@ -540,7 +540,10 @@ namespace SyntheticVendorNamespace openValue = 0.0001; closeValue = 100; } - else { } + else + { + // No action + } return new HistoryItemBar { diff --git a/Tests/test_consistency.cs b/Tests/test_consistency.cs deleted file mode 100644 index ea6ae40d..00000000 --- a/Tests/test_consistency.cs +++ /dev/null @@ -1,857 +0,0 @@ -using Xunit; -namespace QuanTAlib; - -public class Consistency -{ - Random rnd; - int series_len = 1000; - int corrections = 100; - - public Consistency() - { //constructor - rnd = new((int)DateTime.Now.Ticks); - } - - - [Fact] - public void CanUpdate() - { - - GbmFeed gbm = new(); - TSeries input = new(gbm.Close); - TSeries output = new(input); - - gbm.Add(10000); - - Assert.Equal(input.Count, output.Count); - for (int i = 0; i < input.Count; i++) - { - Assert.Equal(input[i].v, output[i].v); - } - } - - [Fact] - public void Alma_isNew() - { - int p = (int)rnd.Next(2, 100); - double offset = rnd.Next(); - double sigma = rnd.Next(1, 100); - Alma ma1 = new(period: p, offset: offset, sigma: sigma); - Alma ma2 = new(period: p, offset: offset, sigma: sigma); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Convolution_isNew() - { - Convolution ma1 = new(new double[] { 1.0, 2, 3, 2, 1 }); - Convolution ma2 = new(new double[] { 1.0, 2, 3, 2, 1 }); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Dema_isNew() - { - int p = (int)rnd.Next(2, 100); - Dema ma1 = new(p); - Dema ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Dsma_isNew() - { - int p = (int)rnd.Next(2, 100); - Dsma ma1 = new(p); - Dsma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Dwma_isNew() - { - int p = (int)rnd.Next(2, 100); - Dwma ma1 = new(p); - Dwma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void EmaSma_isNew() - { - int p = (int)rnd.Next(2, 100); - Ema ma1 = new(p, useSma: true); - Ema ma2 = new(p, useSma: true); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Ema_isNew() - { - int p = (int)rnd.Next(2, 100); - Ema ma1 = new(p, useSma: false); - Ema ma2 = new(p, useSma: false); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Sma_isNew() - { - int p = (int)rnd.Next(2, 100); - Sma ma1 = new(p); - Sma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Epma_isNew() - { - int p = (int)rnd.Next(2, 100); - Epma ma1 = new(p); - Epma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Frama_isNew() - { - int p = (int)rnd.Next(2, 100); - Frama ma1 = new(p); - Frama ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Fwma_isNew() - { - int p = (int)rnd.Next(2, 100); - Fwma ma1 = new(p); - Fwma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Gma_isNew() - { - int p = (int)rnd.Next(2, 100); - Gma ma1 = new(p); - Gma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Hma_isNew() - { - int p = (int)rnd.Next(2, 100); - Hma ma1 = new(p); - Hma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - - [Fact] - public void Hwma_isNew() - { - int p = (int)rnd.Next(2, 100); - Hwma ma1 = new(p); - Hwma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Kama_isNew() - { - int p = (int)rnd.Next(2, 100); - Kama ma1 = new(p); - Kama ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Ltma_isNew() - { - int p = rnd.Next(0, 1); - Ltma ma1 = new(p); - Ltma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Mama_isNew() - { - int p = rnd.Next(0, 1); - Mama ma1 = new(p, p * 0.1); - Mama ma2 = new(p, p * 0.1); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - - Assert.True(ma1.Value == ma2.Value, $"Assertion failed for p={p}, i={i}. Expected {ma1.Value} but got {ma2.Value}."); - } - } - - [Fact] - public void Mgdi_isNew() - { - int p = (int)rnd.Next(2, 100); - Mgdi ma1 = new(p); - Mgdi ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Mma_isNew() - { - int p = (int)rnd.Next(2, 100); - Mma ma1 = new(p); - Mma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Qema_isNew() - { - Qema ma1 = new(); - Qema ma2 = new(); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Rema_isNew() - { - int p = (int)rnd.Next(2, 100); - Rema ma1 = new(p); - Rema ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Rma_isNew() - { - int p = (int)rnd.Next(2, 100); - Rma ma1 = new(p); - Rma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Sinema_isNew() - { - int p = (int)rnd.Next(2, 100); - Sinema ma1 = new(p); - Sinema ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Smma_isNew() - { - int p = (int)rnd.Next(2, 100); - Smma ma1 = new(p); - Smma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void T3_isNew() - { - int p = (int)rnd.Next(2, 100); - T3 ma1 = new(p); - T3 ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Tema_isNew() - { - int p = (int)rnd.Next(2, 100); - Tema ma1 = new(p); - Tema ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - - [Fact] - public void Trima_isNew() - { - int p = (int)rnd.Next(2, 100); - Trima ma1 = new(p); - Trima ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - - [Fact] - public void Vidya_isNew() - { - int p = (int)rnd.Next(2, 100); - Vidya ma1 = new(p); - Vidya ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Wma_isNew() - { - int p = (int)rnd.Next(2, 100); - Wma ma1 = new(p); - Wma ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - [Fact] - public void Zlema_isNew() - { - int p = (int)rnd.Next(2, 100); - Zlema ma1 = new(p); - Zlema ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Entropy_isNew() - { - int p = (int)rnd.Next(2, 100); - Entropy ma1 = new(p); - Entropy ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Kurtosis_isNew() - { - int p = (int)rnd.Next(5, 100); - Kurtosis ma1 = new(p); - Kurtosis ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Max_isNew() - { - int p = (int)rnd.Next(2, 100); - Max ma1 = new(p, 0.01); - Max ma2 = new(p, 0.01); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Min_isNew() - { - int p = (int)rnd.Next(2, 100); - Min ma1 = new(p, 0.01); - Min ma2 = new(p, 0.01); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Med_isNew() - { - int p = (int)rnd.Next(2, 100); - Median ma1 = new(p); - Median ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Mode_isNew() - { - int p = (int)rnd.Next(2, 100); - Mode ma1 = new(p); - Mode ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Percentile_isNew() - { - int p = (int)rnd.Next(2, 100); - Percentile ma1 = new(p, 50); - Percentile ma2 = new(p, 50); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Skew_isNew() - { - int p = (int)rnd.Next(2, 100); - Skew ma1 = new(p); - Skew ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Stddev_isNew() - { - int p = (int)rnd.Next(2, 100); - Stddev ma1 = new(p); - Stddev ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Variance_isNew() - { - int p = (int)rnd.Next(2, 100); - Variance ma1 = new(p); - Variance ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - - [Fact] - public void Zscore_isNew() - { - int p = (int)rnd.Next(2, 100); - Zscore ma1 = new(p); - Zscore ma2 = new(p); - for (int i = 0; i < series_len; i++) - { - TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - ma1.Calc(item1); - for (int j = 0; j < corrections; j++) - { - item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - ma1.Calc(item1); - } - ma2.Calc(new TValue(item1.Time, item1.Value, IsNew: true)); - Assert.Equal(ma1.Value, ma2.Value); - } - } - -} diff --git a/Tests/test_iTBar.cs b/Tests/test_iTBar.cs new file mode 100644 index 00000000..4f21c69a --- /dev/null +++ b/Tests/test_iTBar.cs @@ -0,0 +1,58 @@ +using Xunit; +using System.Reflection; + +namespace QuanTAlib +{ + public class BarIndicatorTests + { + private Random rnd; + private const int SeriesLen = 1000; + private const int Corrections = 100; + + public BarIndicatorTests() + { + rnd = new Random((int)DateTime.Now.Ticks); + } + + private static readonly iTValue[] indicators = new iTValue[] + { + new Atr(period: 14), + }; + + [Theory] + [MemberData(nameof(GetIndicators))] + public void IndicatorIsNew(iTValue indicator) + { + var indicator1 = indicator; + var indicator2 = indicator; + + MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; + if (calcMethod == null) + { + throw new Exception($"Calc method not found for indicator type: {indicator.GetType().Name}"); + } + + for (int i = 0; i < SeriesLen; i++) + { + TBar item1 = new(Time: DateTime.Now, Open: rnd.Next(-100, 100), High: rnd.Next(-100, 100), Low: rnd.Next(-100, 100), Close: rnd.Next(-100, 100), Volume: rnd.Next(-1000, 1000), IsNew: true); + calcMethod.Invoke(indicator1, new object[] { item1 }); + + for (int j = 0; j < Corrections; j++) + { + item1 = new(Time: DateTime.Now, Open: rnd.Next(-100, 100), High: rnd.Next(-100, 100), Low: rnd.Next(-100, 100), Close: rnd.Next(-100, 100), Volume: rnd.Next(-1000, 1000), IsNew: false); + calcMethod.Invoke(indicator1, new object[] { item1 }); + } + + var item2 = new TBar (item1.Time, item1.Open, item1.High, item1.Low, item1.Close, item1.Volume , IsNew: true); + calcMethod.Invoke(indicator2, new object[] { item2 }); + + Assert.Equal(indicator1.Value, indicator2.Value); + } + } + + public static IEnumerable GetIndicators() + { + return indicators.Select(indicator => new object[] { indicator }); + } + } +} \ No newline at end of file diff --git a/Tests/test_iTValue.cs b/Tests/test_iTValue.cs new file mode 100644 index 00000000..3f68ae4c --- /dev/null +++ b/Tests/test_iTValue.cs @@ -0,0 +1,97 @@ +using Xunit; +using System.Reflection; + +namespace QuanTAlib +{ + public class IndicatorTests + { + private Random rnd; + private const int SeriesLen = 1000; + private const int Corrections = 100; + + public IndicatorTests() + { + rnd = new Random((int)DateTime.Now.Ticks); + } + + private static readonly iTValue[] indicators = + [ + new Ema(period: 10, useSma: true), + new Alma(period: 14, offset: 0.85, sigma: 6), + new Convolution(new double[] { 1.0, 2, 3, 2, 1 }), + new Dema(period: 14), + new Dsma(period: 14), + new Dwma(period: 14), + new Epma(period: 14), + new Frama(period: 14), + new Fwma(period: 14), + new Gma(period: 14), + new Hma(period: 14), + new Hwma(period: 14), + new Kama(period: 14), + new Mama(fastLimit: 0.5, slowLimit: 0.05), + new Mgdi(period: 14), + new Mma(period: 14), + new Qema(), + new Rema(period: 14), + new Rma(period: 14), + new Sinema(period: 14), + new Sma(period: 14), + new Smma(period: 14), + new T3(period: 14), + new Tema(period: 14), + new Trima(period: 14), + new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2), + new Wma(period: 14), + new Zlema(period: 14), + new Entropy(period: 14), + new Kurtosis(period: 14), + new Max(period: 14, decay: 0.01), + new Min(period: 14, decay: 0.01), + new Median(period: 14), + new Mode(period: 14), + new Percentile(period: 14, percent: 50), + new Skew(period: 14), + new Stddev(period: 14), + new Variance(period: 14), + new Zscore(period: 14) + + ]; + + [Theory] + [MemberData(nameof(GetIndicators))] + public void IndicatorIsNew(iTValue indicator) + { + var indicator1 = indicator; + var indicator2 = indicator; + + MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; + if (calcMethod == null) + { + throw new Exception($"Calc method not found for indicator type: {indicator.GetType().Name}"); + } + + for (int i = 0; i < SeriesLen; i++) + { + TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); + calcMethod.Invoke(indicator1, new object[] { item1 }); + + for (int j = 0; j < Corrections; j++) + { + item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); + calcMethod.Invoke(indicator1, new object[] { item1 }); + } + + var item2 = new TValue(item1.Time, item1.Value, IsNew: true); + calcMethod.Invoke(indicator2, new object[] { item2 }); + + Assert.Equal(indicator1.Value, indicator2.Value); + } + } + + public static IEnumerable GetIndicators() + { + return indicators.Select(indicator => new object[] { indicator }); + } + } +} \ No newline at end of file diff --git a/Tests/test_skender.stock.cs b/Tests/test_skender.stock.cs index 9602b2d5..e652c773 100644 --- a/Tests/test_skender.stock.cs +++ b/Tests/test_skender.stock.cs @@ -320,4 +320,24 @@ public class SkenderTests } } + [Fact] + public void ATR() + { + for (int run = 0; run < iterations; run++) + { + period = rnd.Next(50) + 5; + Atr ma = new(period: period); + TSeries QL = new(); + foreach (TBar item in bars) { QL.Add(ma.Calc(item)); } + + var SK = quotes.GetAtr(lookbackPeriods: period).Select(i => i.Atr.Null2NaN()!); + Assert.Equal(QL.Length, QL.Length); + + for (int i = QL.Length - 1; i > period +500; i--) + { + Assert.InRange(SK.ElementAt(i) - QL[i].Value, -range, range); + } + } + } + } \ No newline at end of file diff --git a/docs/Progress.md b/docs/Progress.md new file mode 100644 index 00000000..5847f8dc --- /dev/null +++ b/docs/Progress.md @@ -0,0 +1,5 @@ +# Backlog and done + +|**QT**|**Cht**|Cmnt|Docs|isNew|Valid| +|--|:--:|:--:|:--:|:--:|:--:| +|AFIRMA|✔️||||| \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 5ff7bd52..4c1158b0 100644 --- a/docs/index.html +++ b/docs/index.html @@ -8,6 +8,7 @@ + +
@@ -40,10 +68,10 @@ window.$docsify = { homepage: 'readme.md', loadSidebar: true, - subMaxLevel: 1, + subMaxLevel: 4, // Increased to allow for level 4+ items name: '', repo: '', - sidebarDisplayLevel: 2, + sidebarDisplayLevel: 3, // Changed to keep levels 1, 2, and 3 expanded themeable: { readyTransition: true, responsiveTables: true diff --git a/docs/readme.md b/docs/readme.md index 32247e64..aa548a91 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -16,7 +16,8 @@ **Quan**titative **TA** **lib**rary (QuanTAlib) is a C# library of classess and methods for quantitative technical analysis useful for analyzing quotes with [Quantower](https://www.quantower.com/) and other C#-based trading platforms. -[**Visit documentation pages**](https://mihakralj.github.io/QuanTAlib/#/) +[**Visit documentation pages**](https://mihakralj.github.io/QuanTAlib/#/)
+[**List of indicators - implemented and planned**](indicators/indicators.md) **QuanTAlib** is a C# library written with some specific design criteria in mind. Here is why there is '_yet another C# TA library_': @@ -24,10 +25,6 @@ - **Allow updates/corrections** of the last quote - QuanTAlib is re-calculating the last value as many times as required before continuing to the new bar - **Calculate early data right** - calculated data is as valid as mathematically possible from the first value onwards - no blackout or warming-up periods. All indicators return data from the first bar, alongside with a flag `isHot` - defining if calculation is already stable. -### Coverage - -[List of indicators - implemented and planned](indicators/indicators.md) - ## Installation to Quantower - `` is the directory where Quantower is installed - where `Start.lnk` launcher is diff --git a/lib/averages/Ama.cs b/lib/averages/Ama.cs deleted file mode 100644 index 78beb01e..00000000 --- a/lib/averages/Ama.cs +++ /dev/null @@ -1,89 +0,0 @@ -namespace QuanTAlib; - -public class Ama : AbstractBase -{ - private readonly int Period; - private readonly CircularBuffer _buffer; - private readonly double _alpha; // Adaptive factor - private double _lastAfirma, _p_lastAfirma; - private double _lastError, _p_lastError; - - public Ama(int period, double alpha = 0.1) - { - if (period < 1) - { - throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); - } - if (alpha <= 0 || alpha >= 1) - { - throw new ArgumentOutOfRangeException(nameof(alpha), "Alpha must be between 0 and 1 (exclusive)."); - } - Period = period; - WarmupPeriod = period; - _buffer = new CircularBuffer(period); - _alpha = alpha; - Name = "Afirma"; - WarmupPeriod = period; - Init(); - } - - public Ama(object source, int period, double alpha = 0.1) : this(period: period, alpha: alpha) - { - var pubEvent = source.GetType().GetEvent("Pub"); - pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); - } - - public override void Init() - { - base.Init(); - _lastAfirma = 0; - _lastError = 0; - } - - protected override void ManageState(bool isNew) - { - if (isNew) - { - _lastValidValue = Input.Value; - _index++; - _p_lastAfirma = _lastAfirma; - _p_lastError = _lastError; - } - else - { - _lastAfirma = _p_lastAfirma; - _lastError = _p_lastError; - } - } - - /// - /// Core AFIRMA calculation - /// - protected override double Calculation() - { - double result; - ManageState(IsNew); - _buffer.Add(Input.Value, Input.IsNew); - - if (_index < Period) - { - // Use simple average during warmup period - result = _buffer.Average(); - } - else - { - // AFIRMA calculation - double sma = _buffer.Average(); - double error = Input.Value - _lastAfirma; - double denominator = Math.Abs(error) + Math.Abs(_lastError); - double adaptiveFactor = denominator != 0 ? _alpha * Math.Abs(error) / denominator : _alpha; - result = sma + adaptiveFactor * (Input.Value - sma); - - _lastError = error; - } - - _lastAfirma = result; - IsHot = _index >= WarmupPeriod; - return result; - } -} \ No newline at end of file diff --git a/lib/averages/Mama.cs b/lib/averages/Mama.cs index b090e52d..5372fd35 100644 --- a/lib/averages/Mama.cs +++ b/lib/averages/Mama.cs @@ -40,7 +40,6 @@ public class Mama : AbstractBase public override void Init() { Fama = new TValue(); - base.Init(); } protected override void ManageState(bool isNew) diff --git a/lib/averages/T3.cs b/lib/averages/T3.cs index c2b216d7..1007b943 100644 --- a/lib/averages/T3.cs +++ b/lib/averages/T3.cs @@ -1,7 +1,6 @@ namespace QuanTAlib; -public class T3 : AbstractBase -{ +public class T3 : AbstractBase { private readonly int _period; private readonly bool _useSma; private readonly double _k, _c1, _c2, _c3, _c4; @@ -9,10 +8,8 @@ public class T3 : AbstractBase private double _lastEma1, _lastEma2, _lastEma3, _lastEma4, _lastEma5, _lastEma6; private double _p_lastEma1, _p_lastEma2, _p_lastEma3, _p_lastEma4, _p_lastEma5, _p_lastEma6; - public T3(int period, double vfactor = 0.7, bool useSma = true) - { - if (period < 1) - { + public T3(int period, double vfactor = 0.7, bool useSma = true) { + if (period < 1) { throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period)); } _period = period; @@ -37,15 +34,12 @@ public class T3 : AbstractBase Init(); } - public T3(object source, int period, double vfactor = 0.7, bool useSma = true) : this(period, vfactor, useSma) - { + public T3(object source, int period, double vfactor = 0.7, bool useSma = true) : this(period, vfactor, useSma) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } - public override void Init() - { - base.Init(); + public override void Init() { _lastEma1 = _lastEma2 = _lastEma3 = _lastEma4 = _lastEma5 = _lastEma6 = 0; _buffer1.Clear(); _buffer2.Clear(); @@ -55,10 +49,8 @@ public class T3 : AbstractBase _buffer6.Clear(); } - protected override void ManageState(bool isNew) - { - if (isNew) - { + protected override void ManageState(bool isNew) { + if (isNew) { _lastValidValue = Input.Value; _index++; _p_lastEma1 = _lastEma1; @@ -67,9 +59,7 @@ public class T3 : AbstractBase _p_lastEma4 = _lastEma4; _p_lastEma5 = _lastEma5; _p_lastEma6 = _lastEma6; - } - else - { + } else { _lastEma1 = _p_lastEma1; _lastEma2 = _p_lastEma2; _lastEma3 = _p_lastEma3; @@ -80,18 +70,14 @@ public class T3 : AbstractBase } - protected override double Calculation() - { + protected override double Calculation() { ManageState(Input.IsNew); double ema1, ema2, ema3, ema4, ema5, ema6; - if (_index == 1) - { + if (_index == 1) { ema1 = ema2 = ema3 = ema4 = ema5 = ema6 = Input.Value; - } - else if (_index <= _period && _useSma) - { + } else if (_index <= _period && _useSma) { _buffer1.Add(Input.Value, Input.IsNew); ema1 = _buffer1.Average(); _buffer2.Add(ema1, Input.IsNew); @@ -104,9 +90,7 @@ public class T3 : AbstractBase ema5 = _buffer5.Average(); _buffer6.Add(ema5, Input.IsNew); ema6 = _buffer6.Average(); - } - else - { + } else { ema1 = _k * (Input.Value - _lastEma1) + _lastEma1; ema2 = _k * (ema1 - _lastEma2) + _lastEma2; ema3 = _k * (ema2 - _lastEma3) + _lastEma3; diff --git a/lib/core/AbstractBarBase.cs b/lib/core/AbstractBarBase.cs new file mode 100644 index 00000000..cd643345 --- /dev/null +++ b/lib/core/AbstractBarBase.cs @@ -0,0 +1,71 @@ +namespace QuanTAlib; + +/// +/// Provides a base implementation for financial indicators in the QuanTAlib library. +/// This abstract class implements the iTValue interface and defines common properties +/// and methods used by inheriting indicator types. +/// +public abstract class AbstractBarBase : iTValue +{ + public DateTime Time { get; set; } + public double Value { get; set; } + public bool IsNew { get; set; } + public bool IsHot { get; set; } + + public TBar Input { get; set; } + public String Name { get; set; } = ""; + public int WarmupPeriod { get; set; } + + public TValue Tick => new(Time, Value, IsNew, IsHot); // Stores the current value of indicator + public event ValueSignal Pub = delegate { }; // Publisher of generated values + + protected int _index; //tracking the position of output + protected double _lastValidValue; + // other _internal vars defined here + + protected AbstractBarBase() + { //add parameters into constructor + } + + public void Sub(object source, in TBarEventArgs args) => Calc(args.Bar); + + public virtual void Init() + { + _index = 0; + _lastValidValue = 0; + } + + public virtual TValue Calc(TBar input) + { + Input = input; + if (double.IsNaN(input.Close) || double.IsInfinity(input.Close)) + { + return Process(new TValue(Time: input.Time, Value: GetLastValid(), IsNew: input.IsNew, IsHot: true)); + } + this.Value = Calculation(); + return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot)); + } + + protected virtual double GetLastValid() + { + return this.Value; + } + protected abstract void ManageState(bool isNew); + protected abstract double Calculation(); + + /// + /// Processes the calculated value, updates the indicator's own state, + /// and publishes the result through an event. + /// + /// The calculated TValue to process. + /// The processed TValue. + protected virtual TValue Process(TValue value) + { + this.Time = value.Time; + this.Value = value.Value; + this.IsNew = value.IsNew; + this.IsHot = value.IsHot; + Pub?.Invoke(this, new ValueEventArgs(value)); + return value; + } +} diff --git a/lib/core/tbar.cs b/lib/core/tbar.cs index 0326b043..677db00c 100644 --- a/lib/core/tbar.cs +++ b/lib/core/tbar.cs @@ -84,7 +84,8 @@ public class TBarSeries : List public new virtual void Add(TBar bar) { - if (bar.IsNew) { base.Add(bar); } else { this[^1] = bar; } + if (bar.IsNew || base.Count == 0) { base.Add(bar); } + else { this[^1] = bar; } Pub?.Invoke(this, new TBarEventArgs(bar)); Open.Add(bar.Time, bar.Open, IsNew: bar.IsNew, IsHot: true); diff --git a/lib/core/tvalue.cs b/lib/core/tvalue.cs index fa06f0f5..d72c8cd5 100644 --- a/lib/core/tvalue.cs +++ b/lib/core/tvalue.cs @@ -60,7 +60,7 @@ public class TSeries : List public new virtual void Add(TValue tick) { - if (tick.IsNew) { base.Add(tick); } + if (tick.IsNew || base.Count==0) { base.Add(tick); } else { this[^1] = tick; } Pub?.Invoke(this, new ValueEventArgs(tick)); } diff --git a/lib/quantalib.csproj b/lib/quantalib.csproj index 53b82781..c342f1cd 100644 --- a/lib/quantalib.csproj +++ b/lib/quantalib.csproj @@ -23,7 +23,6 @@ - diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs new file mode 100644 index 00000000..1ab0299a --- /dev/null +++ b/lib/volatility/Atr.cs @@ -0,0 +1,71 @@ +namespace QuanTAlib; + +public class Atr : AbstractBarBase +{ + private readonly int _period; + private readonly Ema _ma; + private double _prevClose, _p_prevClose; + + public Atr(int period) : base() + { + if (period < 1) + { + throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); + } + _period = period; + _ma = new(1.0/period); + WarmupPeriod = _ma.WarmupPeriod; + Name = $"ATR({_period})"; + } + + public Atr(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + public override void Init() + { + base.Init(); + _ma.Init(); + _prevClose = double.NaN; + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_prevClose = _prevClose; + } + else + { + _prevClose = _p_prevClose; + } + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + double trueRange = Math.Max( + Math.Max( + Input.High - Input.Low, + Math.Abs(Input.High - _prevClose) + ), + Math.Abs(Input.Low - _prevClose) + ); + if (_index < 2) + { + trueRange = Input.High - Input.Low; + } + + TValue emaTrueRange = _ma.Calc(new TValue(Input.Time, trueRange, Input.IsNew)); + IsHot = _ma.IsHot; + _prevClose = Input.Close; + + return emaTrueRange.Value; + } + +} + diff --git a/notebooks/Skender.dib b/notebooks/Skender.dib new file mode 100644 index 00000000..52eab105 --- /dev/null +++ b/notebooks/Skender.dib @@ -0,0 +1,45 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}} + +#!csharp + +#r "nuget:Skender.Stock.Indicators" +#r "..\lib\obj\Debug\QuanTAlib.dll" + +#!csharp + +using Skender.Stock.Indicators; +using QuanTAlib; + +GbmFeed gbm = new(); +Atr atr = new(gbm, 5); +TSeries res = new(atr); +gbm.Add(100); + +IEnumerable quotes = gbm.Select(item => new Quote { Date = item.Time, Open = (decimal)item.Open, + High = (decimal)item.High, Low = (decimal)item.Low, Close = (decimal)item.Close, Volume = (decimal)item.Volume }); +var SkResults = quotes.GetAtr(5).Select(i => i.Atr.Null2NaN()!); +for (int i=0; i< gbm.Length; i++) { + Console.WriteLine($"{gbm.High[i].Value,6:F2} {gbm.Low[i].Value,6:F2} {gbm.Close[i].Value,6:F2}\t\t{res[i].Value,10:F4} {SkResults.ElementAt(i),10:F4}"); +} + +#!csharp + +Random rnd = new((int)DateTime.Now.Ticks); +GbmFeed feed = new(sigma: 0.5, mu: 0.0); +TBarSeries bars = new(feed); +feed.Add(20); +IEnumerable quotes; + +int period = rnd.Next(5) + 2; +Atr ma = new(period: period); +TSeries QL = new(); +foreach (TBar item in bars) { + Console.WriteLine($"{ma.Calc(item)}"); + //QL.Add(ma.Calc(item)); +} + +#!csharp + +bars diff --git a/quantower/Averages/AmaIndicator.cs b/quantower/Averages/AmaIndicator.cs deleted file mode 100644 index 417700b5..00000000 --- a/quantower/Averages/AmaIndicator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using TradingPlatform.BusinessLayer; -namespace QuanTAlib; - -public class AmaIndicator : IndicatorBase -{ - [InputParameter("Period", sortIndex: 1, 2, 2000, 1, 0)] - public int Period { get; set; } = 10; - - [InputParameter("Alpha", sortIndex: 2, minimum: -0.01, maximum: 1.0, increment: 0.01, decimalPlaces: 2)] - public double Alpha { get; set; } = 0.1; - private Ama? ma; - protected override AbstractBase QuanTAlib => ma!; - public override string ShortName => $"AMA {Period} : {Alpha} : {SourceName}"; - - - public AmaIndicator() - { - Name = "AMA - Adaptive Moving Average"; - } - - protected override void InitIndicator() - { - ma = new Ama(period: Period, alpha: Alpha); - } -} diff --git a/quantower/Averages/Averages.csproj b/quantower/Averages/Averages.csproj index 3fb41cb2..77c81333 100644 --- a/quantower/Averages/Averages.csproj +++ b/quantower/Averages/Averages.csproj @@ -8,7 +8,6 @@
- lib\%(RecursiveDir)%(Filename)%(Extension) diff --git a/quantower/Statistics/Statistics.csproj b/quantower/Statistics/Statistics.csproj index 36fadba0..262800f0 100644 --- a/quantower/Statistics/Statistics.csproj +++ b/quantower/Statistics/Statistics.csproj @@ -8,7 +8,6 @@ - lib\%(RecursiveDir)%(Filename)%(Extension) diff --git a/quantower/Statistics/_IndicatorBarBase.cs b/quantower/Statistics/_IndicatorBarBase.cs new file mode 100644 index 00000000..a5fae449 --- /dev/null +++ b/quantower/Statistics/_IndicatorBarBase.cs @@ -0,0 +1,136 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; +using TradingPlatform.BusinessLayer.Chart; +using System.Runtime.CompilerServices; +using System.Drawing.Drawing2D; +using System.Collections; +using TradingPlatform.BusinessLayer.TimeSync; + +namespace QuanTAlib; + +#pragma warning disable CA1416 // Validate platform compatibility +public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator +{ + + [InputParameter("Show cold values", sortIndex: 20)] + public bool ShowColdValues { get; set; } = true; + public int MinHistoryDepths { get; set; } + + // LineSeries.LineSeries(string, Color, int, LineStyle)' + + protected LineSeries? Series; + protected abstract AbstractBarBase QuanTAlib { get; } + + int IWatchlistIndicator.MinHistoryDepths => 0; + + protected IndicatorBarBase() + { + OnBackGround = true; + SeparateWindow = false; + Series = new(name: $"{Name}", color: Color.RoyalBlue, width: 2, style: LineStyle.Solid); + + AddLineSeries(Series); + } + + protected abstract void InitIndicator(); + + protected override void OnInit() + { + InitIndicator(); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar bar = new(Time: Time(), + Open: GetPrice(PriceType.Open), + High: GetPrice(PriceType.High), + Low: GetPrice(PriceType.Low), + Close: GetPrice(PriceType.Close), + Volume: GetPrice(PriceType.Volume), + IsNew: args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar); + + TValue result = QuanTAlib.Calc(bar); + Series!.SetValue(result.Value); + Series!.SetMarker(0, Color.Transparent); + + } + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + List allPoints = new List(); + if (CurrentChart == null) { return; } + + Graphics gr = args.Graphics; + + var mainWindow = this.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + + gr.SetClip(clientRect); + DateTime leftTime = new[] { converter.GetTime(clientRect.Left), Time(this.Count - 1) }.Max(); + DateTime rightTime = new[] { converter.GetTime(clientRect.Right), Time(0) }.Min(); + + int leftIndex = (int)HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; + int rightIndex = (int)HistoricalData.GetIndexByTime(rightTime.Ticks); + + for (int i = rightIndex; i < leftIndex; i++) + { + int barX = (int)converter.GetChartX(Time(i)); + int barY = (int)converter.GetChartY(Series![i]); + int halfBarWidth = CurrentChart.BarsWidth / 2; + Point point = new Point(barX + halfBarWidth, barY); + allPoints.Add(point); + } + + if (allPoints.Count > 1) + { + DrawSmoothCombinedCurve(gr, allPoints, this.Count - QuanTAlib.WarmupPeriod - rightIndex); + } + } + + private void DrawSmoothCombinedCurve(Graphics gr, List allPoints, int hotCount) + { + if (allPoints.Count < 2) { return; } + + using (Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }) + using (Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }) + { + // Draw the hot part + if (hotCount > 0) + { + var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray(); + gr.DrawCurve(defaultPen, hotPoints, 0, hotPoints.Length - 1, (float)0.1); + } + + // Draw the cold part + if (ShowColdValues && hotCount < allPoints.Count) + { + var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray(); + gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)0.1); + } + } + } + private static DashStyle ConvertLineStyleToDashStyle(LineStyle lineStyle) + { + return lineStyle switch + { + LineStyle.Solid => DashStyle.Solid, + LineStyle.Dash => DashStyle.Dash, + LineStyle.Dot => DashStyle.Dot, + LineStyle.DashDot => DashStyle.DashDot, + _ => DashStyle.Solid, + }; + } + protected static void DrawText(Graphics gr, string text, Rectangle clientRect) + { + Font font = new Font("Inter", 8); + SizeF textSize = gr.MeasureString(text, font); + RectangleF textRect = new RectangleF(clientRect.Left + 5, + clientRect.Bottom - textSize.Height - 10, + textSize.Width + 10, textSize.Height + 10); + gr.FillRectangle(SystemBrushes.ControlDarkDark, textRect); + gr.DrawString(text, font, Brushes.White, new PointF(textRect.X + 6, textRect.Y + 5)); + } +} \ No newline at end of file diff --git a/quantower/Volatility/AtrIndicator.cs b/quantower/Volatility/AtrIndicator.cs new file mode 100644 index 00000000..fe1b0738 --- /dev/null +++ b/quantower/Volatility/AtrIndicator.cs @@ -0,0 +1,23 @@ +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class AtrIndicator : IndicatorBarBase +{ + [InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)] + public int Period { get; set; } = 20; + + private Atr? atr; + protected override AbstractBarBase QuanTAlib => atr!; + public override string ShortName => $"ATR {Period}"; + public AtrIndicator() + { + Name = "ATR - Average True Range"; + SeparateWindow = true; + } + + protected override void InitIndicator() + { + atr = new(Period); + MinHistoryDepths = atr!.WarmupPeriod; + } +} \ No newline at end of file diff --git a/quantower/Volatility/Volatility.csproj b/quantower/Volatility/Volatility.csproj new file mode 100644 index 00000000..7247b808 --- /dev/null +++ b/quantower/Volatility/Volatility.csproj @@ -0,0 +1,29 @@ + + + Indicator + bin\$(Configuration)\ + true + true + true + + + + + lib\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + ..\..\.github\TradingPlatform.BusinessLayer.dll + + + TradingPlatform.BusinessLayer.xml + + + + \ No newline at end of file diff --git a/quantower/Volatility/_IndicatorBarBase.cs b/quantower/Volatility/_IndicatorBarBase.cs new file mode 100644 index 00000000..a5fae449 --- /dev/null +++ b/quantower/Volatility/_IndicatorBarBase.cs @@ -0,0 +1,136 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; +using TradingPlatform.BusinessLayer.Chart; +using System.Runtime.CompilerServices; +using System.Drawing.Drawing2D; +using System.Collections; +using TradingPlatform.BusinessLayer.TimeSync; + +namespace QuanTAlib; + +#pragma warning disable CA1416 // Validate platform compatibility +public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator +{ + + [InputParameter("Show cold values", sortIndex: 20)] + public bool ShowColdValues { get; set; } = true; + public int MinHistoryDepths { get; set; } + + // LineSeries.LineSeries(string, Color, int, LineStyle)' + + protected LineSeries? Series; + protected abstract AbstractBarBase QuanTAlib { get; } + + int IWatchlistIndicator.MinHistoryDepths => 0; + + protected IndicatorBarBase() + { + OnBackGround = true; + SeparateWindow = false; + Series = new(name: $"{Name}", color: Color.RoyalBlue, width: 2, style: LineStyle.Solid); + + AddLineSeries(Series); + } + + protected abstract void InitIndicator(); + + protected override void OnInit() + { + InitIndicator(); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar bar = new(Time: Time(), + Open: GetPrice(PriceType.Open), + High: GetPrice(PriceType.High), + Low: GetPrice(PriceType.Low), + Close: GetPrice(PriceType.Close), + Volume: GetPrice(PriceType.Volume), + IsNew: args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar); + + TValue result = QuanTAlib.Calc(bar); + Series!.SetValue(result.Value); + Series!.SetMarker(0, Color.Transparent); + + } + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + List allPoints = new List(); + if (CurrentChart == null) { return; } + + Graphics gr = args.Graphics; + + var mainWindow = this.CurrentChart.Windows[args.WindowIndex]; + var converter = mainWindow.CoordinatesConverter; + var clientRect = mainWindow.ClientRectangle; + + gr.SetClip(clientRect); + DateTime leftTime = new[] { converter.GetTime(clientRect.Left), Time(this.Count - 1) }.Max(); + DateTime rightTime = new[] { converter.GetTime(clientRect.Right), Time(0) }.Min(); + + int leftIndex = (int)HistoricalData.GetIndexByTime(leftTime.Ticks) + 1; + int rightIndex = (int)HistoricalData.GetIndexByTime(rightTime.Ticks); + + for (int i = rightIndex; i < leftIndex; i++) + { + int barX = (int)converter.GetChartX(Time(i)); + int barY = (int)converter.GetChartY(Series![i]); + int halfBarWidth = CurrentChart.BarsWidth / 2; + Point point = new Point(barX + halfBarWidth, barY); + allPoints.Add(point); + } + + if (allPoints.Count > 1) + { + DrawSmoothCombinedCurve(gr, allPoints, this.Count - QuanTAlib.WarmupPeriod - rightIndex); + } + } + + private void DrawSmoothCombinedCurve(Graphics gr, List allPoints, int hotCount) + { + if (allPoints.Count < 2) { return; } + + using (Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }) + using (Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }) + { + // Draw the hot part + if (hotCount > 0) + { + var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray(); + gr.DrawCurve(defaultPen, hotPoints, 0, hotPoints.Length - 1, (float)0.1); + } + + // Draw the cold part + if (ShowColdValues && hotCount < allPoints.Count) + { + var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray(); + gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)0.1); + } + } + } + private static DashStyle ConvertLineStyleToDashStyle(LineStyle lineStyle) + { + return lineStyle switch + { + LineStyle.Solid => DashStyle.Solid, + LineStyle.Dash => DashStyle.Dash, + LineStyle.Dot => DashStyle.Dot, + LineStyle.DashDot => DashStyle.DashDot, + _ => DashStyle.Solid, + }; + } + protected static void DrawText(Graphics gr, string text, Rectangle clientRect) + { + Font font = new Font("Inter", 8); + SizeF textSize = gr.MeasureString(text, font); + RectangleF textRect = new RectangleF(clientRect.Left + 5, + clientRect.Bottom - textSize.Height - 10, + textSize.Width + 10, textSize.Height + 10); + gr.FillRectangle(SystemBrushes.ControlDarkDark, textRect); + gr.DrawString(text, font, Brushes.White, new PointF(textRect.X + 6, textRect.Y + 5)); + } +} \ No newline at end of file