From f1378e82ba729d40f7c2af2ff8d9c684d4ca23c5 Mon Sep 17 00:00:00 2001 From: Miha Date: Wed, 31 Jul 2024 17:57:52 -0700 Subject: [PATCH] refinement --- .github/workflows/main_automation.yml | 4 +- QuanTAlib.sln | 88 ++------- v2/Indicators/EMA.cs | 90 ++++++--- v2/Indicators/SMA.cs | 107 ++++++---- v2/basics/TValue.cs | 1 + v2/test.dib | 271 ++++++++++++++++++++++---- 6 files changed, 382 insertions(+), 179 deletions(-) diff --git a/.github/workflows/main_automation.yml b/.github/workflows/main_automation.yml index 2130d485..12fac48c 100644 --- a/.github/workflows/main_automation.yml +++ b/.github/workflows/main_automation.yml @@ -3,10 +3,10 @@ on: workflow_dispatch: push: branches: - - '*' + - main pull_request: branches: - - '*' + - main jobs: build_test: diff --git a/QuanTAlib.sln b/QuanTAlib.sln index d0b6ce59..ab71a1e8 100644 --- a/QuanTAlib.sln +++ b/QuanTAlib.sln @@ -1,74 +1,24 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32210.308 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculations", "Calculations\Calculations.csproj", "{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Indicators", "Indicators\Indicators.csproj", "{43AD2D78-024C-4D96-A70B-915CF519965A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strategies", "Strategies\Strategies.csproj", "{FA526AF6-95BC-4AC0-8B46-A304FD06689D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{47B6ACDB-F535-4FEB-9A0A-C427CAE8C28E}" - ProjectSection(SolutionItems) = preProject - docs\.nojekyll = docs\.nojekyll - docs\ALMA.md = docs\ALMA.md - docs\DEMA.md = docs\DEMA.md - docs\DWMA.md = docs\DWMA.md - docs\EMA.md = docs\EMA.md - docs\FMA.md = docs\FMA.md - docs\getting_started.ipynb = docs\getting_started.ipynb - docs\HEMA.md = docs\HEMA.md - docs\HMA.md = docs\HMA.md - docs\HWMA.md = docs\HWMA.md - docs\index.html = docs\index.html - docs\indicators.md = docs\indicators.md - docs\JMA.md = docs\JMA.md - docs\KAMA.md = docs\KAMA.md - docs\LICENSE = docs\LICENSE - docs\MAMA.md = docs\MAMA.md - docs\QA.md = docs\QA.md - docs\readme.md = docs\readme.md - docs\RMA.md = docs\RMA.md - docs\SMA.md = docs\SMA.md - docs\SMMA.md = docs\SMMA.md - docs\T3.md = docs\T3.md - docs\TEMA.md = docs\TEMA.md - docs\TRIMA.md = docs\TRIMA.md - docs\WMA.md = docs\WMA.md - docs\ZLEMA.md = docs\ZLEMA.md - docs\_sidebar.md = docs\_sidebar.md - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculations", "v2\calculations.csproj", "{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}" EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.Build.0 = Release|Any CPU - {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Release|Any CPU.Build.0 = Release|Any CPU - {43AD2D78-024C-4D96-A70B-915CF519965A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43AD2D78-024C-4D96-A70B-915CF519965A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43AD2D78-024C-4D96-A70B-915CF519965A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43AD2D78-024C-4D96-A70B-915CF519965A}.Release|Any CPU.Build.0 = Release|Any CPU - {FA526AF6-95BC-4AC0-8B46-A304FD06689D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA526AF6-95BC-4AC0-8B46-A304FD06689D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA526AF6-95BC-4AC0-8B46-A304FD06689D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA526AF6-95BC-4AC0-8B46-A304FD06689D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E5592DC2-0542-45B2-A0CF-C6B1EDC72B87} - EndGlobalSection -EndGlobal + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E5592DC2-0542-45B2-A0CF-C6B1EDC72B87} + EndGlobalSection +EndGlobal \ No newline at end of file diff --git a/v2/Indicators/EMA.cs b/v2/Indicators/EMA.cs index 536baa35..da9699fc 100644 --- a/v2/Indicators/EMA.cs +++ b/v2/Indicators/EMA.cs @@ -1,45 +1,71 @@ public class EMA { - private readonly int period; - private int index; + private readonly int _period; + private int _index, _hotIndex; public TValue Value { get; private set; } - public bool IsHot => index > period; - public int Period => Math.Min(index, period); - private double k; - private double lastEMA, lastEMAcandidate; + public bool IsHot => _index > _period; + public int Period => Math.Min(_index, _period); + private double _k; + private double _lastEMA, _lastEMACandidate; - public EMA(int Period) { - this.period = Period; + public EMA(int period) { + _period = period; Init(); } - public void Init() { - this.Value = default; - this.index = 0; - this.k = 2.0 / (period + 1); - this.lastEMA = 0; - this.lastEMAcandidate = 0; - } - - public TValue Update(TValue Input, bool IsNew = true) { - double ma; - if (double.IsNaN(Input.Value) || double.IsInfinity(Input.Value)) { - return new TValue(Input.Time, lastEMA, IsNew, index > period); - } - if (IsNew) { - if (index<1) { lastEMA = Input.Value; } - lastEMAcandidate = lastEMA; - index++; + public EMA(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) }); + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } } else { - if (index<=1) { lastEMAcandidate = Input.Value; } - lastEMA = lastEMAcandidate; + throw new ArgumentException("Source object must have an Update(TValue) method."); + } + } + + public void Init() { + Value = default; + _index = _hotIndex = 0; + _k = 2.0 / (_period + 1); + _lastEMA = 0; + _lastEMACandidate = 0; + } + + public TValue Update(TValue input) { + if (!input.IsHot && input.IsNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastEMA, input.IsNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; + } + if (input.IsNew) { + if (_index < 1) { _lastEMA = input.Value; } + _lastEMACandidate = _lastEMA; + _index++; + } else { + if (_index <= 1) { _lastEMACandidate = input.Value; } + _lastEMA = _lastEMACandidate; } - double kk = (index <= period) ? (2.0 / (index+1)) : k; - ma = (Input.Value - lastEMA) * kk + lastEMA; - lastEMA = ma; + double kk = (_index <= _period) ? (2.0 / (_index + 1)) : _k; - this.Value = new TValue(Input.Time, ma, IsNew, index > period); - return this.Value; + double ema = (input.Value - _lastEMA) * kk + _lastEMA; + _lastEMA = ema; + + Value = new TValue(input.Time, ema, input.IsNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); + return Value; } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; } \ No newline at end of file diff --git a/v2/Indicators/SMA.cs b/v2/Indicators/SMA.cs index 490753cb..a4da6232 100644 --- a/v2/Indicators/SMA.cs +++ b/v2/Indicators/SMA.cs @@ -1,55 +1,80 @@ + public class SMA { - public CircularBuffer buffer = null!; - private readonly int period; - public double sum; + private readonly int _period; + private int _index, _hotIndex; public TValue Value { get; private set; } - public bool IsHot { get; private set; } + public bool IsHot => _index >= _period; + public int Period => Math.Min(_index, _period); + private double _sum; + private double _lastValidSMA; + private CircularBuffer _buffer; + private double _lastAddedValue; - public SMA(int period) - { - this.period = period; + public SMA(int period) { + _period = period; Init(); } - public void Init() - { - this.buffer = new CircularBuffer(period); - this.sum = 0; - this.IsHot = false; - this.Value = default; + public SMA(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) }); + + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } + } else { + throw new ArgumentException("Source object must have an Update(TValue) method."); + } } - public TValue Update(TValue input, bool isNew = true) - { - if (buffer.Count == 0) - { - // If buffer is empty, always add the value regardless of isNew - buffer.Add(input.Value, true); - sum = input.Value; - } - else if (isNew && buffer.Count == buffer.Capacity) - { - // If buffer is full and it's a new value, remove oldest - sum -= buffer[0]; - buffer.Add(input.Value, true); - sum += input.Value; - } - else - { - // If it's not new, or if buffer isn't full yet - if (!isNew) - { - // Remove the last value if we're updating - sum -= buffer[buffer.Count - 1]; - } - buffer.Add(input.Value, isNew); - sum += input.Value; + public void Init() { + _buffer = new CircularBuffer(_period); + _sum = 0; + _lastValidSMA = 0; + Value = default; + _index = _hotIndex = 0; + _lastAddedValue = 0; + } + + public TValue Update(TValue input) { + if (!input.IsHot && input.IsNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastValidSMA, input.IsNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; } - double sma = sum / buffer.Count; - IsHot = buffer.Count >= period; - Value = new TValue(input.Time, sma, isNew, IsHot); + if (input.IsNew) { + if (_buffer.Count == _buffer.Capacity) { + _sum -= _buffer[0]; + } + _buffer.Add(input.Value, true); + _sum += input.Value; + _lastAddedValue = input.Value; + _index++; + } else { + _sum = _sum - _lastAddedValue + input.Value; + _buffer[_buffer.Count - 1] = input.Value; + _lastAddedValue = input.Value; + } + + double sma = _sum / _buffer.Count; + _lastValidSMA = sma; + + Value = new TValue(input.Time, sma, input.IsNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); return Value; } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; } \ No newline at end of file diff --git a/v2/basics/TValue.cs b/v2/basics/TValue.cs index 49c2a6ed..d92d2878 100644 --- a/v2/basics/TValue.cs +++ b/v2/basics/TValue.cs @@ -18,3 +18,4 @@ public readonly record struct TValue(DateTime Time, double Value, bool IsNew = t public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: {Value:F2}]"; } +public delegate void Signal(object source, TValue args); \ No newline at end of file diff --git a/v2/test.dib b/v2/test.dib index bde0d059..c70f179d 100644 --- a/v2/test.dib +++ b/v2/test.dib @@ -15,66 +15,267 @@ using System.Runtime.CompilerServices; #load "./basics/CircularBuffer.cs" #load "./basics/TValue.cs" #load "./basics/TBar.cs" -#load "./Indicators/SMA.cs" -#load "./Indicators/EMA.cs" +//#load "./Indicators/SMA.cs" +//#load "./Indicators/EMA.cs" #load "./GBM_Feed.cs" #!csharp +public delegate void Signal(object source, TValue args); + +#!csharp + public class Template { - private CircularBuffer buffer = null!; - private readonly int period; - private int index; + private CircularBuffer _buffer = null!; + private readonly int _period; + private int _index, _hotIndex; public TValue Value { get; private set; } - public bool IsHot { get; private set; } + public bool IsHot => _index >= _period; + public int Period => Math.Min(_index, _period); + private double _sum; + private double _lastValidValue; + private double _lastAddedValue; - public Template(int Period) { - this.period = Period; + public Template(int period) { + _period = period; Init(); } - public void Init() { - this.buffer = new CircularBuffer(period); - this.IsHot = false; - this.Value = default; - this.index = 0; + public Template(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue), typeof(bool) }); + + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } + } else { + throw new ArgumentException("Source object must have an Update(TValue, bool) method."); + } } - public TValue Update(TValue Input, bool IsNew = true) { - this.buffer.Add(Input,IsNew); - //first value - if (this.index == 0) { - if (IsNew) { this.index++; } - this.Value = new TValue(Input.Time, Input.Value, IsNew, true); - return this.Value; + public void Init() { + _buffer = new CircularBuffer(_period); + _sum = 0; + _lastValidValue = 0; + Value = default; + _index = _hotIndex = 0; + _lastAddedValue = 0; + } + + public TValue Update(TValue input, bool isNew = true) { + if (!input.IsHot && isNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastValidValue, isNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; } - if (IsNew) { - // starting a new bar, fresh calc - index++; - } else { - // updating existing bar, recalc - } - double ma = Input.Value; + if (isNew) { + if (_buffer.Count == _buffer.Capacity) { + _sum -= _buffer[0]; + } + _buffer.Add(input.Value, true); + _sum += input.Value; + _lastAddedValue = input.Value; + _index++; + } else { + _sum = _sum - _lastAddedValue + input.Value; + _buffer[_buffer.Count - 1] = input.Value; + _lastAddedValue = input.Value; + } - this.Value = new TValue(Input.Time, ma, IsNew, index > period); - return this.Value; + double result = _sum / _buffer.Count; + _lastValidValue = result; + + Value = new TValue(input.Time, result, isNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); + return Value; } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; +} + +#!csharp + +//public delegate void Signal(object source, TValue args); + +public class SMA +{ + private readonly int _period; + private int _index, _hotIndex; + public TValue Value { get; private set; } + public bool IsHot => _index >= _period; + public int Period => Math.Min(_index, _period); + private double _sum; + private double _lastValidSMA; + private CircularBuffer _buffer; + private double _lastAddedValue; + + public SMA(int period) { + _period = period; + Init(); + } + + public SMA(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) }); + + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } + } else { + throw new ArgumentException("Source object must have an Update(TValue) method."); + } + } + + public void Init() { + _buffer = new CircularBuffer(_period); + _sum = 0; + _lastValidSMA = 0; + Value = default; + _index = _hotIndex = 0; + _lastAddedValue = 0; + } + + public TValue Update(TValue input) { + if (!input.IsHot && input.IsNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastValidSMA, input.IsNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; + } + + if (input.IsNew) { + if (_buffer.Count == _buffer.Capacity) { + _sum -= _buffer[0]; + } + _buffer.Add(input.Value, true); + _sum += input.Value; + _lastAddedValue = input.Value; + _index++; + } else { + _sum = _sum - _lastAddedValue + input.Value; + _buffer[_buffer.Count - 1] = input.Value; + _lastAddedValue = input.Value; + } + + double sma = _sum / _buffer.Count; + _lastValidSMA = sma; + + Value = new TValue(input.Time, sma, input.IsNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); + return Value; + } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; +} + +#!csharp + +public class EMA +{ + private readonly int _period; + private int _index, _hotIndex; + public TValue Value { get; private set; } + public bool IsHot => _index > _period; + public int Period => Math.Min(_index, _period); + private double _k; + private double _lastEMA, _lastEMACandidate; + + public EMA(int period) { + _period = period; + Init(); + } + + public EMA(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) }); + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } + } else { + throw new ArgumentException("Source object must have an Update(TValue) method."); + } + } + + public void Init() { + Value = default; + _index = _hotIndex = 0; + _k = 2.0 / (_period + 1); + _lastEMA = 0; + _lastEMACandidate = 0; + } + + public TValue Update(TValue input) { + if (!input.IsHot && input.IsNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastEMA, input.IsNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; + } + if (input.IsNew) { + if (_index < 1) { _lastEMA = input.Value; } + _lastEMACandidate = _lastEMA; + _index++; + } else { + if (_index <= 1) { _lastEMACandidate = input.Value; } + _lastEMA = _lastEMACandidate; + } + + double kk = (_index <= _period) ? (2.0 / (_index + 1)) : _k; + + double ema = (input.Value - _lastEMA) * kk + _lastEMA; + _lastEMA = ema; + + Value = new TValue(input.Time, ema, input.IsNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); + return Value; + } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; } #!csharp EMA ma = new(3); +SMA ma1 = new(ma, 3); Console.WriteLine($"{"Close",5} {"MA()",10}"); for (int i = 1; i < 10; i=i+1) { - //ma.Update(100,true); - ma.Update(10,true); - ma.Update(i,false); - - - Console.WriteLine($"{i} {(double)ma.Value,10:F2} {ma.Value.IsNew}"); + TValue tv = new(DateTime.Now, 1000, IsNew: true); + ma.Update(tv); + tv = new(DateTime.Now, i, IsNew: false); + ma.Update(tv); + + Console.WriteLine($"{i} {(double)ma.Value,10:F2} {(double)ma1.Value,10:F2} {ma1.Value.IsHot}"); } #!csharp