This commit is contained in:
Miha Kralj
2024-09-30 06:46:07 -07:00
parent 148f0ea846
commit bdc01bff4a
28 changed files with 803 additions and 1053 deletions
-89
View File
@@ -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;
}
}
/// <summary>
/// Core AFIRMA calculation
/// </summary>
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;
}
}
-1
View File
@@ -40,7 +40,6 @@ public class Mama : AbstractBase
public override void Init()
{
Fama = new TValue();
base.Init();
}
protected override void ManageState(bool isNew)
+12 -28
View File
@@ -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;
+71
View File
@@ -0,0 +1,71 @@
namespace QuanTAlib;
/// <summary>
/// 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.
/// </summary>
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();
/// <summary>
/// Processes the calculated value, updates the indicator's own state,
/// and publishes the result through an event.
/// </summary>
/// <param name="value">The calculated TValue to process.</param>
/// <returns>The processed TValue.</returns>
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;
}
}
+2 -1
View File
@@ -84,7 +84,8 @@ public class TBarSeries : List<TBar>
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);
+1 -1
View File
@@ -60,7 +60,7 @@ public class TSeries : List<TValue>
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));
}
-1
View File
@@ -23,7 +23,6 @@
<ItemGroup>
<None Include="readme.md" Pack="true" PackagePath=""/>
<None Include="..\.github\QuanTAlib2.png" Pack="true" Visible="false" PackagePath=""/>
<PackageReference Include="Microsoft.DotNet.Interactive.Formatting" Version="1.0.0-beta.24229.4" />
<PackageReference Include="System.Text.Json" Version="9.0.0-rc.1.24431.7" />
</ItemGroup>
+71
View File
@@ -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;
}
}