Skip to content

Commit

Permalink
Atr, FlowIndicator and fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
mihakralj committed Oct 21, 2024
1 parent fbe4046 commit e3d7cd9
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 181 deletions.
1 change: 1 addition & 0 deletions lib/averages/Ema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public Ema(double alpha)
_k = alpha;
_useSma = false;
_sma = new(1);
Name = "Ema";
_period = 1;
WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - _k)); //95th percentile
Init();
Expand Down
21 changes: 12 additions & 9 deletions lib/averages/Jma.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace QuanTAlib;

public class Jma : AbstractBase
{
private readonly int _period;
private readonly double _period;
private readonly double _phase;
private readonly CircularBuffer _vsumBuff;
private readonly CircularBuffer _avoltyBuff;
Expand All @@ -22,6 +22,7 @@ public class Jma : AbstractBase
public double UpperBand { get; set; }
public double LowerBand { get; set; }
public double Volty { get; set; }
public double Factor { get; set; }

/// <summary>
/// Initializes a new instance of the Jma class with the specified parameters.
Expand All @@ -31,18 +32,19 @@ public class Jma : AbstractBase
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when period is less than 1.
/// </exception>
public Jma(int period, int phase = 0)
public Jma(int period, int phase = 0, double factor = 0.45)
{
if (period < 1)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
Factor = factor;
_period = period;
_phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5);

_vsumBuff = new CircularBuffer(10);
_avoltyBuff = new CircularBuffer(65);
_beta = 0.45 * (period - 1) / (0.45 * (period - 1) + 2);
_beta = factor * (_period - 1) / (factor * (_period - 1) + 2);

WarmupPeriod = period * 2;
Name = $"JMA({period})";
Expand Down Expand Up @@ -114,17 +116,18 @@ protected override double Calculation()
ManageState(Input.IsNew);

double price = Input.Value;
if (_index == 1)
if (_index <= 1)
{
_upperBand = _lowerBand = price;
_prevMa1 = _prevJma = price;
}

double del1 = price - _upperBand;
double del2 = price - _lowerBand;
double volty = Math.Max(Math.Abs(del1), Math.Abs(del2));

_vsumBuff.Add(volty, Input.IsNew);
_vSum += (_vsumBuff[^1] - _vsumBuff[0]) / 10;
_vSum += (_vsumBuff[^1] - _vsumBuff[0]) / _vsumBuff.Count;
_avoltyBuff.Add(_vSum, Input.IsNew);
double avgvolty = _avoltyBuff.Average();

Expand All @@ -137,15 +140,15 @@ protected override double Calculation()
_upperBand = (del1 >= 0) ? price : price - (Kv * del1);
_lowerBand = (del2 <= 0) ? price : price - (Kv * del2);

double alpha = Math.Pow(_beta, pow2);
double ma1 = (1 - alpha) * Input.Value + alpha * _prevMa1;
double _alpha = Math.Pow(_beta, pow2);
double ma1 = Input.Value + _alpha * (_prevMa1 - Input.Value); //original: (1 - _alpha) * Input.Value + _alpha * _prevMa1;
_prevMa1 = ma1;

double det0 = (price - ma1) * (1 - _beta) + _beta * _prevDet0;
double det0 = price + _beta * (_prevDet0 - price + ma1) - ma1; //original: (price - ma1) * (1 - _beta) + _beta * _prevDet0;
_prevDet0 = det0;
double ma2 = ma1 + _phase * det0;

double det1 = ((ma2 - _prevJma) * (1 - alpha) * (1 - alpha) ) + (alpha * alpha * _prevDet1);
double det1 = ((ma2 - _prevJma) * (1 - _alpha) * (1 - _alpha) ) + (_alpha * _alpha * _prevDet1);
_prevDet1 = det1;
double jma = _prevJma + det1;
_prevJma = jma;
Expand Down
132 changes: 100 additions & 32 deletions lib/averages/Rma.cs
Original file line number Diff line number Diff line change
@@ -1,94 +1,162 @@
using System;

namespace QuanTAlib;

/// <summary>
/// RMA: Relative Moving Average (also known as Wilder's Moving Average)
/// RMA is similar to EMA but uses a different smoothing factor.
/// </summary>
/// <remarks>
/// RMA is similar to EMA but uses a different smoothing factor.
///
/// Key characteristics:
/// - Uses no buffer, relying only on the previous RMA value.
/// - The weight of new data points (alpha) is calculated as 1 / period.
/// - Provides a smoother curve compared to SMA and EMA, reacting more slowly to price changes.
///
/// Calculation method:
/// RMA = (Previous RMA * (period - 1) + New Data) / period
/// This implementation can use SMA for the first Period bars as a seeding value for RMA when useSma is true.
///
/// Sources:
/// - https://www.tradingview.com/pine-script-reference/v5/#fun_ta{dot}rma
/// - https://www.investopedia.com/terms/w/wilders-smoothing.asp
/// </remarks>
public class Rma : AbstractBase
{
// inherited _index
// inherited _value

/// <summary>
/// The period for the RMA calculation.
/// </summary>
private readonly int _period;
private double _lastRma;
private readonly double _alpha;
private double _savedLastRma;

public Rma(int period)
/// <summary>
/// Circular buffer for SMA calculation.
/// </summary>
private CircularBuffer _sma;

/// <summary>
/// The last calculated RMA value.
/// </summary>
private double _lastRma, _p_lastRma;

/// <summary>
/// Compensator for early RMA values.
/// </summary>
private double _e, _p_e;

/// <summary>
/// The smoothing factor for RMA calculation.
/// </summary>
private readonly double _k;

/// <summary>
/// Flags to track initialization status.
/// </summary>
private bool _isInit, _p_isInit;

/// <summary>
/// Flag to determine whether to use SMA for initial values.
/// </summary>
private readonly bool _useSma;

/// <summary>
/// Initializes a new instance of the Rma class with a specified period.
/// </summary>
/// <param name="period">The period for RMA calculation.</param>
/// <param name="useSma">Whether to use SMA for initial values. Default is true.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
public Rma(int period, bool useSma = true)
{
if (period < 1)
{
throw new ArgumentException("Period must be greater than or equal to 1.", nameof(period));
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
_period = period;
WarmupPeriod = period * 2;
_alpha = 1.0 / _period; // Wilder's smoothing factor
Name = $"Rma({_period})";
_k = 1.0 / _period; // Wilder's smoothing factor
_useSma = useSma;
_sma = new(period);
Name = "Rma";
WarmupPeriod = _period * 2; // RMA typically needs more warmup periods
Init();
}

public Rma(object source, int period) : this(period)
/// <summary>
/// Initializes a new instance of the Rma class with a specified source and period.
/// </summary>
/// <param name="source">The source object for event subscription.</param>
/// <param name="period">The period for RMA calculation.</param>
/// <param name="useSma">Whether to use SMA for initial values. Default is true.</param>
public Rma(object source, int period, bool useSma = true) : this(period, useSma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}

/// <summary>
/// Initializes the Rma instance.
/// </summary>
public override void Init()
{
base.Init();
_e = 1.0;
_lastRma = 0;
_savedLastRma = 0;
_isInit = false;
_p_isInit = false;
_sma = new(_period);
}

/// <summary>
/// Manages the state of the Rma instance.
/// </summary>
/// <param name="isNew">Indicates whether the input is new.</param>
protected override void ManageState(bool isNew)
{
if (isNew)
{
_savedLastRma = _lastRma;
_lastValidValue = Input.Value;
_p_lastRma = _lastRma;
_p_isInit = _isInit;
_p_e = _e;
_index++;
}
else
{
_lastRma = _savedLastRma;
_lastRma = _p_lastRma;
_isInit = _p_isInit;
_e = _p_e;
}
}

/// <summary>
/// Performs the RMA calculation.
/// </summary>
/// <returns>The calculated RMA value.</returns>
protected override double Calculation()
{
double result, _rma;
ManageState(Input.IsNew);

double rma;

if (_index == 1)
{
rma = Input.Value;
}
else if (_index <= _period)
// when _UseSma == true, use SMA calculation until we have enough data points
if (!_isInit && _useSma)
{
// Simple average during initial period
rma = (_lastRma * (_index - 1) + Input.Value) / _index;
_sma.Add(Input.Value, Input.IsNew);
_rma = _sma.Average();
result = _rma;
if (_index >= _period)
{
_isInit = true;
}
}
else
{
// Wilder's smoothing method
rma = _alpha * (_lastRma - Input.Value) + _lastRma;
}
// compensator for early rma values
_e = (_e > 1e-10) ? (1 - _k) * _e : 0;

_lastRma = rma;
IsHot = _index >= WarmupPeriod;
_rma = _k * Input.Value + (1 - _k) * _lastRma;

return rma;
// _useSma decides if we use compensator or not
result = (_useSma || _e <= double.Epsilon) ? _rma : _rma / (1 - _e);
}
_lastRma = _rma;
IsHot = _index >= WarmupPeriod;
return result;
}
}
6 changes: 6 additions & 0 deletions lib/core/abstractBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ public virtual TValue Calc(TValue input)
Input2 = new(Time: Input.Time, Value: double.NaN, IsNew: Input.IsNew, IsHot: Input.IsHot);
return Process(input.Value, input.Time, input.IsNew);
}
public virtual TValue Calc(double value, bool IsNew)
{
Input = new(this.Time, Value: value, IsNew: IsNew, IsHot: false);
Input2 = new(this.Time, double.NaN, false, false);
return Process(Input.Value, Input.Time, Input.IsNew);
}

public virtual TValue Calc(TBar barInput)
{
Expand Down
2 changes: 2 additions & 0 deletions lib/core/tbar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { }
public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { }
public TBar(double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) { }
public TBar(TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) { }
public TBar(TBar v) : this(Time: v.Time, Open: v.Open, High: v.High, Low: v.Low, Close: v.Close, Volume: v.Volume, IsNew: true) { }


public static implicit operator double(TBar bar) => bar.Close;
public static implicit operator DateTime(TBar tv) => tv.Time;
Expand Down
4 changes: 2 additions & 2 deletions lib/core/tvalue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ public TSeries(object source) : this()
var pubEvent = source.GetType().GetEvent("Pub");
if (pubEvent != null)
{

/*
var nameProperty = source.GetType().GetProperty("Name");
if (nameProperty != null)
{
Name = nameProperty.GetValue(nameProperty)?.ToString()!;
}

*/
pubEvent.AddEventHandler(source, new ValueSignal(Sub));
}
}
Expand Down
38 changes: 19 additions & 19 deletions lib/feeds/GbmFeed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ public class GbmFeed : TBarSeries
{
private readonly double _mu, _sigma;
private readonly RandomNumberGenerator _rng;
private double _lastClose, _lastHigh, _lastLow;
private double _lastClose;

public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2)
{
_lastClose = _lastHigh = _lastLow = initialPrice;
_lastClose = initialPrice;
_mu = mu;
_sigma = sigma;
_rng = RandomNumberGenerator.Create();
Expand All @@ -24,9 +24,7 @@ public void Add(int count)
DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count);
for (int i = 0; i < count; i++)
{
Add(startTime, true);
Add(startTime, false);
Add(startTime, false);
Add(startTime, isNew: true);
startTime = startTime.AddHours(1);
}
}
Expand All @@ -36,27 +34,29 @@ public TBar Generate(DateTime time, bool isNew = true)
double dt = 1.0 / 252;
double drift = (_mu - 0.5 * _sigma * _sigma) * dt;
double diffusion = _sigma * Math.Sqrt(dt) * GenerateNormalRandom();
double newClose = _lastClose * Math.Exp(drift + diffusion);

double open = _lastClose;
double high = Math.Max(_lastHigh, Math.Max(open, newClose) * (1 + GenerateRandomDouble() * 0.01));
double low = Math.Min(_lastLow, Math.Min(open, newClose) * (1 - GenerateRandomDouble() * 0.01));
double close = open * Math.Exp(drift + diffusion);

// Generate intra-bar price movements
double maxMove = Math.Abs(close - open) * 1.5; // Allow for some extra movement within the bar
double high = Math.Max(open, close) + maxMove * GenerateRandomDouble();
double low = Math.Min(open, close) - maxMove * GenerateRandomDouble();

// Ensure high is always greater than or equal to both open and close
high = Math.Max(high, Math.Max(open, close));

// Ensure low is always less than or equal to both open and close
low = Math.Min(low, Math.Min(open, close));

double volume = 1000 + GenerateRandomDouble() * 1000;

if (isNew)
{
_lastClose = newClose;
}
else
{
high = Math.Max(_lastHigh, high);
low = Math.Min(_lastLow, low);
_lastClose = close;
}
_lastHigh = high;
_lastLow = low;

TBar bar = new(time, open, high, low, newClose, volume, isNew);
return bar;
return new TBar(time, open, high, low, close, volume, isNew);
}

private double GenerateNormalRandom()
Expand All @@ -73,4 +73,4 @@ private double GenerateRandomDouble()
_rng.GetBytes(bytes);
return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue;
}
}
}
Loading

0 comments on commit e3d7cd9

Please sign in to comment.