mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 20:48:04 +00:00
refinement
This commit is contained in:
@@ -3,10 +3,10 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '*'
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- '*'
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_test:
|
build_test:
|
||||||
|
|||||||
+18
-68
@@ -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
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.2.32210.308
|
VisualStudioVersion = 17.2.32210.308
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculations", "Calculations\Calculations.csproj", "{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculations", "v2\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
|
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{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}.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.ActiveCfg = Release|Any CPU
|
||||||
{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
{283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
{283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
HideSolutionNode = FALSE
|
||||||
{283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Release|Any CPU.Build.0 = Release|Any CPU
|
EndGlobalSection
|
||||||
{43AD2D78-024C-4D96-A70B-915CF519965A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
{43AD2D78-024C-4D96-A70B-915CF519965A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
SolutionGuid = {E5592DC2-0542-45B2-A0CF-C6B1EDC72B87}
|
||||||
{43AD2D78-024C-4D96-A70B-915CF519965A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
EndGlobalSection
|
||||||
{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
|
EndGlobal
|
||||||
+58
-32
@@ -1,45 +1,71 @@
|
|||||||
public class EMA
|
public class EMA
|
||||||
{
|
{
|
||||||
private readonly int period;
|
private readonly int _period;
|
||||||
private int index;
|
private int _index, _hotIndex;
|
||||||
public TValue Value { get; private set; }
|
public TValue Value { get; private set; }
|
||||||
public bool IsHot => index > period;
|
public bool IsHot => _index > _period;
|
||||||
public int Period => Math.Min(index, period);
|
public int Period => Math.Min(_index, _period);
|
||||||
private double k;
|
private double _k;
|
||||||
private double lastEMA, lastEMAcandidate;
|
private double _lastEMA, _lastEMACandidate;
|
||||||
|
|
||||||
public EMA(int Period) {
|
public EMA(int period) {
|
||||||
this.period = Period;
|
_period = period;
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Init() {
|
public EMA(object source, int period) : this(period) {
|
||||||
this.Value = default;
|
var sourceType = source.GetType();
|
||||||
this.index = 0;
|
var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) });
|
||||||
this.k = 2.0 / (period + 1);
|
if (updateMethod != null) {
|
||||||
this.lastEMA = 0;
|
var pubEvent = sourceType.GetEvent("Pub");
|
||||||
this.lastEMAcandidate = 0;
|
if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) {
|
||||||
}
|
pubEvent.AddEventHandler(source, new Signal(Sub));
|
||||||
|
} else {
|
||||||
public TValue Update(TValue Input, bool IsNew = true) {
|
throw new ArgumentException("Source object must have a Pub event of type NewValue.");
|
||||||
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++;
|
|
||||||
} else {
|
} else {
|
||||||
if (index<=1) { lastEMAcandidate = Input.Value; }
|
throw new ArgumentException("Source object must have an Update(TValue) method.");
|
||||||
lastEMA = lastEMAcandidate;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 kk = (_index <= _period) ? (2.0 / (_index + 1)) : _k;
|
||||||
ma = (Input.Value - lastEMA) * kk + lastEMA;
|
|
||||||
lastEMA = ma;
|
|
||||||
|
|
||||||
this.Value = new TValue(Input.Time, ma, IsNew, index > period);
|
double ema = (input.Value - _lastEMA) * kk + _lastEMA;
|
||||||
return this.Value;
|
_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;
|
||||||
}
|
}
|
||||||
+66
-41
@@ -1,55 +1,80 @@
|
|||||||
|
|
||||||
public class SMA
|
public class SMA
|
||||||
{
|
{
|
||||||
public CircularBuffer buffer = null!;
|
private readonly int _period;
|
||||||
private readonly int period;
|
private int _index, _hotIndex;
|
||||||
public double sum;
|
|
||||||
public TValue Value { get; private set; }
|
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)
|
public SMA(int period) {
|
||||||
{
|
_period = period;
|
||||||
this.period = period;
|
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Init()
|
public SMA(object source, int period) : this(period) {
|
||||||
{
|
var sourceType = source.GetType();
|
||||||
this.buffer = new CircularBuffer(period);
|
var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) });
|
||||||
this.sum = 0;
|
|
||||||
this.IsHot = false;
|
if (updateMethod != null) {
|
||||||
this.Value = default;
|
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)
|
public void Init() {
|
||||||
{
|
_buffer = new CircularBuffer(_period);
|
||||||
if (buffer.Count == 0)
|
_sum = 0;
|
||||||
{
|
_lastValidSMA = 0;
|
||||||
// If buffer is empty, always add the value regardless of isNew
|
Value = default;
|
||||||
buffer.Add(input.Value, true);
|
_index = _hotIndex = 0;
|
||||||
sum = input.Value;
|
_lastAddedValue = 0;
|
||||||
}
|
}
|
||||||
else if (isNew && buffer.Count == buffer.Capacity)
|
|
||||||
{
|
public TValue Update(TValue input) {
|
||||||
// If buffer is full and it's a new value, remove oldest
|
if (!input.IsHot && input.IsNew) { _hotIndex++; }
|
||||||
sum -= buffer[0];
|
|
||||||
buffer.Add(input.Value, true);
|
if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) {
|
||||||
sum += input.Value;
|
Value = new TValue(input.Time, _lastValidSMA, input.IsNew, _index > _period);
|
||||||
}
|
Pub?.Invoke(this, Value);
|
||||||
else
|
return Value;
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double sma = sum / buffer.Count;
|
if (input.IsNew) {
|
||||||
IsHot = buffer.Count >= period;
|
if (_buffer.Count == _buffer.Capacity) {
|
||||||
Value = new TValue(input.Time, sma, isNew, IsHot);
|
_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;
|
return Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Sub(object source, TValue arg) {
|
||||||
|
Update(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Signal Pub;
|
||||||
}
|
}
|
||||||
@@ -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 override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: {Value:F2}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public delegate void Signal(object source, TValue args);
|
||||||
+235
-34
@@ -15,66 +15,267 @@ using System.Runtime.CompilerServices;
|
|||||||
#load "./basics/CircularBuffer.cs"
|
#load "./basics/CircularBuffer.cs"
|
||||||
#load "./basics/TValue.cs"
|
#load "./basics/TValue.cs"
|
||||||
#load "./basics/TBar.cs"
|
#load "./basics/TBar.cs"
|
||||||
#load "./Indicators/SMA.cs"
|
//#load "./Indicators/SMA.cs"
|
||||||
#load "./Indicators/EMA.cs"
|
//#load "./Indicators/EMA.cs"
|
||||||
#load "./GBM_Feed.cs"
|
#load "./GBM_Feed.cs"
|
||||||
|
|
||||||
#!csharp
|
#!csharp
|
||||||
|
|
||||||
|
public delegate void Signal(object source, TValue args);
|
||||||
|
|
||||||
|
#!csharp
|
||||||
|
|
||||||
public class Template
|
public class Template
|
||||||
{
|
{
|
||||||
private CircularBuffer buffer = null!;
|
private CircularBuffer _buffer = null!;
|
||||||
private readonly int period;
|
private readonly int _period;
|
||||||
private int index;
|
private int _index, _hotIndex;
|
||||||
public TValue Value { get; private set; }
|
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) {
|
public Template(int period) {
|
||||||
this.period = Period;
|
_period = period;
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Init() {
|
public Template(object source, int period) : this(period) {
|
||||||
this.buffer = new CircularBuffer(period);
|
var sourceType = source.GetType();
|
||||||
this.IsHot = false;
|
var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue), typeof(bool) });
|
||||||
this.Value = default;
|
|
||||||
this.index = 0;
|
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) {
|
public void Init() {
|
||||||
this.buffer.Add(Input,IsNew);
|
_buffer = new CircularBuffer(_period);
|
||||||
//first value
|
_sum = 0;
|
||||||
if (this.index == 0) {
|
_lastValidValue = 0;
|
||||||
if (IsNew) { this.index++; }
|
Value = default;
|
||||||
this.Value = new TValue(Input.Time, Input.Value, IsNew, true);
|
_index = _hotIndex = 0;
|
||||||
return this.Value;
|
_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) {
|
if (isNew) {
|
||||||
// starting a new bar, fresh calc
|
if (_buffer.Count == _buffer.Capacity) {
|
||||||
index++;
|
_sum -= _buffer[0];
|
||||||
} else {
|
}
|
||||||
// updating existing bar, recalc
|
_buffer.Add(input.Value, true);
|
||||||
}
|
_sum += input.Value;
|
||||||
double ma = 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);
|
double result = _sum / _buffer.Count;
|
||||||
return this.Value;
|
_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
|
#!csharp
|
||||||
|
|
||||||
EMA ma = new(3);
|
EMA ma = new(3);
|
||||||
|
SMA ma1 = new(ma, 3);
|
||||||
Console.WriteLine($"{"Close",5} {"MA()",10}");
|
Console.WriteLine($"{"Close",5} {"MA()",10}");
|
||||||
for (int i = 1; i < 10; i=i+1)
|
for (int i = 1; i < 10; i=i+1)
|
||||||
{
|
{
|
||||||
//ma.Update(100,true);
|
TValue tv = new(DateTime.Now, 1000, IsNew: true);
|
||||||
ma.Update(10,true);
|
ma.Update(tv);
|
||||||
ma.Update(i,false);
|
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}");
|
||||||
Console.WriteLine($"{i} {(double)ma.Value,10:F2} {ma.Value.IsNew}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#!csharp
|
#!csharp
|
||||||
|
|||||||
Reference in New Issue
Block a user