diff --git a/lib/averages/Ema.cs b/lib/averages/Ema.cs
index 6e7c7d9..3e38f74 100644
--- a/lib/averages/Ema.cs
+++ b/lib/averages/Ema.cs
@@ -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();
diff --git a/lib/averages/Jma.cs b/lib/averages/Jma.cs
index 142a47d..c8e8eba 100644
--- a/lib/averages/Jma.cs
+++ b/lib/averages/Jma.cs
@@ -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;
@@ -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; }
///
/// Initializes a new instance of the Jma class with the specified parameters.
@@ -31,18 +32,19 @@ public class Jma : AbstractBase
///
/// Thrown when period is less than 1.
///
- 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})";
@@ -114,9 +116,10 @@ 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;
@@ -124,7 +127,7 @@ protected override double Calculation()
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();
@@ -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;
diff --git a/lib/averages/Rma.cs b/lib/averages/Rma.cs
index f8077fa..628d831 100644
--- a/lib/averages/Rma.cs
+++ b/lib/averages/Rma.cs
@@ -1,18 +1,18 @@
-using System;
-
namespace QuanTAlib;
+
///
/// RMA: Relative Moving Average (also known as Wilder's Moving Average)
-/// RMA is similar to EMA but uses a different smoothing factor.
///
///
+/// 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
@@ -20,75 +20,143 @@ namespace QuanTAlib;
///
public class Rma : AbstractBase
{
+ // inherited _index
+ // inherited _value
+
+ ///
+ /// The period for the RMA calculation.
+ ///
private readonly int _period;
- private double _lastRma;
- private readonly double _alpha;
- private double _savedLastRma;
- public Rma(int period)
+ ///
+ /// Circular buffer for SMA calculation.
+ ///
+ private CircularBuffer _sma;
+
+ ///
+ /// The last calculated RMA value.
+ ///
+ private double _lastRma, _p_lastRma;
+
+ ///
+ /// Compensator for early RMA values.
+ ///
+ private double _e, _p_e;
+
+ ///
+ /// The smoothing factor for RMA calculation.
+ ///
+ private readonly double _k;
+
+ ///
+ /// Flags to track initialization status.
+ ///
+ private bool _isInit, _p_isInit;
+
+ ///
+ /// Flag to determine whether to use SMA for initial values.
+ ///
+ private readonly bool _useSma;
+
+ ///
+ /// Initializes a new instance of the Rma class with a specified period.
+ ///
+ /// The period for RMA calculation.
+ /// Whether to use SMA for initial values. Default is true.
+ /// Thrown when period is less than 1.
+ 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)
+ ///
+ /// Initializes a new instance of the Rma class with a specified source and period.
+ ///
+ /// The source object for event subscription.
+ /// The period for RMA calculation.
+ /// Whether to use SMA for initial values. Default is true.
+ public Rma(object source, int period, bool useSma = true) : this(period, useSma)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
+ ///
+ /// Initializes the Rma instance.
+ ///
public override void Init()
{
base.Init();
+ _e = 1.0;
_lastRma = 0;
- _savedLastRma = 0;
+ _isInit = false;
+ _p_isInit = false;
+ _sma = new(_period);
}
+ ///
+ /// Manages the state of the Rma instance.
+ ///
+ /// Indicates whether the input is new.
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;
}
}
+ ///
+ /// Performs the RMA calculation.
+ ///
+ /// The calculated RMA value.
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;
}
}
diff --git a/lib/core/abstractBase.cs b/lib/core/abstractBase.cs
index 3859473..3ac3d97 100644
--- a/lib/core/abstractBase.cs
+++ b/lib/core/abstractBase.cs
@@ -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)
{
diff --git a/lib/core/tbar.cs b/lib/core/tbar.cs
index 441994e..9b72bc8 100644
--- a/lib/core/tbar.cs
+++ b/lib/core/tbar.cs
@@ -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;
diff --git a/lib/core/tvalue.cs b/lib/core/tvalue.cs
index f7c83cf..843768d 100644
--- a/lib/core/tvalue.cs
+++ b/lib/core/tvalue.cs
@@ -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));
}
}
diff --git a/lib/feeds/GbmFeed.cs b/lib/feeds/GbmFeed.cs
index 5b2edd2..566ee8d 100644
--- a/lib/feeds/GbmFeed.cs
+++ b/lib/feeds/GbmFeed.cs
@@ -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();
@@ -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);
}
}
@@ -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()
@@ -73,4 +73,4 @@ private double GenerateRandomDouble()
_rng.GetBytes(bytes);
return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue;
}
-}
\ No newline at end of file
+}
diff --git a/lib/quantalib.csproj b/lib/quantalib.csproj
index 716dc1a..3f706bc 100644
--- a/lib/quantalib.csproj
+++ b/lib/quantalib.csproj
@@ -31,6 +31,7 @@
https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png
True
false
+ $(NoWarn);NU1903;NU5104
diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs
index e6c2084..3ea3e5f 100644
--- a/lib/volatility/Atr.cs
+++ b/lib/volatility/Atr.cs
@@ -4,13 +4,14 @@ namespace QuanTAlib;
/// Represents an Average True Range (ATR) calculator, a measure of market volatility.
///
///
-/// The ATR class calculates the average true range using an Exponential Moving Average (EMA)
+/// The ATR class calculates the average true range using a Relative Moving Average (RMA)
/// of the true range. The true range is the greatest of: current high - current low,
/// absolute value of current high - previous close, or absolute value of current low - previous close.
///
public class Atr : AbstractBase
{
- private readonly Ema _ma;
+ public double Tr { get; private set; }
+ private readonly Rma _ma;
private double _prevClose, _p_prevClose;
///
@@ -26,7 +27,7 @@ public Atr(int period)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
- _ma = new(1.0 / period);
+ _ma = new(period, useSma: true);
WarmupPeriod = _ma.WarmupPeriod;
Name = $"ATR({period})";
}
@@ -50,6 +51,7 @@ public override void Init()
base.Init();
_ma.Init();
_prevClose = double.NaN;
+ Tr = 0;
}
///
@@ -76,7 +78,7 @@ protected override void ManageState(bool isNew)
/// The calculated ATR value for the current bar.
///
///
- /// This method calculates the true range for the current bar and then uses an EMA
+ /// This method calculates the true range for the current bar and then uses an RMA
/// to smooth the true range values. For the first bar, it uses the high-low range
/// as the true range.
///
@@ -84,22 +86,25 @@ protected override double Calculation()
{
ManageState(BarInput.IsNew);
- double trueRange = Math.Max(
- Math.Max(
- BarInput.High - BarInput.Low,
- Math.Abs(BarInput.High - _prevClose)
- ),
- Math.Abs(BarInput.Low - _prevClose)
- );
- if (_index < 2)
+ if (_index == 1)
+ {
+ Tr = BarInput.High - BarInput.Low;
+ _prevClose = BarInput.Close;
+ }
+ else
{
- trueRange = BarInput.High - BarInput.Low;
+ Tr = Math.Max(
+ BarInput.High - BarInput.Low,
+ Math.Max(
+ Math.Abs(BarInput.High - _prevClose),
+ Math.Abs(BarInput.Low - _prevClose)
+ )
+ );
}
+ _ma.Calc(new TValue(Input.Time, Tr, BarInput.IsNew));
- TValue emaTrueRange = _ma.Calc(new TValue(Input.Time, trueRange, Input.IsNew));
IsHot = _ma.IsHot;
_prevClose = BarInput.Close;
-
- return emaTrueRange.Value;
+ return _ma.Value;
}
}
diff --git a/notebooks/core.dib b/notebooks/core.dib
index 2f997b4..1fa0200 100644
--- a/notebooks/core.dib
+++ b/notebooks/core.dib
@@ -4,7 +4,7 @@
#!csharp
-#r "..\src\obj\Debug\QuanTAlib.dll"
+#r "..\lib\obj\Debug\QuanTAlib.dll"
#r "nuget:Skender.Stock.Indicators"
using Skender.Stock.Indicators;
@@ -13,113 +13,56 @@ QuanTAlib.Formatters.Initialize();
#!csharp
+Atr ma = new(10);
GbmFeed gbm = new();
-EmaCalc ema1 = new(gbm.Close, 10, useSma: false);
-EmaCalc ema2 = new(gbm.Close, 10, useSma: true);
-TValSeries res1 = new(ema1);
-TValSeries res2 = new(ema2);
-gbm.Add(50);
-List mse1 = new();
-List mse2 = new();
-
-
+gbm.Add(30);
+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(10).Select(i => i.Atr.Null2NaN()!);
for (int i=0; i< gbm.Length; i++) {
- double v= gbm.Close[i].Value;
- double e1 = res1[i].Value;
- mse1.Add((e1-v)*(e1-v));
- double e2 = res2[i].Value;
- mse2.Add((e2-v)*(e2-v));
-
- //Console.WriteLine($"{i,3} {mse1.Average(),10:F4} {mse2.Average(),10:F4}");
+ ma.Calc(gbm[i]);
+ Console.WriteLine($"{i,3} {ma.Value,10:F3} \t {SkResults.ElementAt(i):F3}");
}
- Console.WriteLine($"{mse2.Average()-mse1.Average(),10:F8}");
-
-#!csharp
-
-display(res1);
-
#!csharp
+Atr ma = new(10);
GbmFeed gbm = new();
-EmaCalc ema1 = new(gbm.Close, 10, useSma: false);
-EmaCalc ema2 = new(gbm.Close, 10, useSma: true);
-TValSeries res1 = new(ema1);
-TValSeries res2 = new(ema2);
gbm.Add(30);
-IEnumerable quotes = gbm.Close.Select(item => new Quote { Date = item.Time, Close = (decimal)item.Value });
-var SkResults = quotes.GetEma(10).Select(i => i.Ema.Null2NaN()!);
+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.GetTr().Select(i => i.Tr.Null2NaN()!);
for (int i=0; i< gbm.Length; i++) {
- Console.WriteLine($"{i,3} {gbm.Close[i].Value,6:F2} {res1[i].Value,10:F4} {res2[i].Value,10:F4} {SkResults.ElementAt(i),10:F4}");
-}
+ ma.Calc(new TBar(gbm[i]));
-#!csharp
-
-TValSeries test = new();
-
-EmaCalc ma1 = new(test, 7, true);
-TValSeries res1 = new(ma1);
-
-EmaCalc ma2 = new(test, 7, false);
-TValSeries res2 = new(ma2);
-
-test.Add(new[]{1.0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0});
-
-for (int i=0; i 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(10).Select(i => i.Atr.Null2NaN()!);
+for (int i=0; i< gbm.Length; i++) {
+ double delta = Math.Round(res1[i].Value, 10) - Math.Round(SkResults.ElementAt(i), 10);
+ //Console.WriteLine($"{i,3} {gbm.High[i].Value,6:F2} {gbm.Low[i].Value,6:F2} {gbm.Close[i].Value,6:F2} {res1[i].Value,10:F4} {SkResults.ElementAt(i),10:F4}\t{delta}");
+ Console.WriteLine($"{i,3} h:{gbm.High[i].Value,6:F2} l:{gbm.Low[i].Value,6:F2} c:{gbm.Close[i].Value,6:F2} {res1[i].Atr,10:F4} {SkResults.ElementAt(i),10:F4}\t{delta}");
}
-display(result);
-
-#!csharp
-
-TValSeries test = new();
-SmaCalc ma = new(test,7);
-TValSeries result = new(ma);
-test.Add(new[]{81.59, 81.06, 82.87, 83.00, 83.61, 83.15, 82.84, 83.99, 84.55, 84.36, 85.53, 86.54, 86.89, 87.77, 87.29});
-//test.Add(new[]{1.0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0});
-
-display(result);
#!csharp
-TValue test = new(DateTime.Today, 100, IsHot: false);
-TValSeries pub = new();
-TValSeries sub = new(pub);
-pub.Add(test);
-pub.Add(test);
-pub.Add(2, true);
-pub.Add(DateTime.Today, 123.1234214234, IsHot: true);
-
-
-display(sub);
-display(test);
-
-#!csharp
-
-TBar test = new(DateTime.Now, double.NaN,1,2,3,400.1234);
-
-TBarSeries source = new();
-TValSeries target = new(source.Close);
-
-source.Add(new TBar(DateTime.Now,1,2,3,4,125, true));
-source.Add(new TBar(DateTime.Now,2,1,5,2,1312, true));
-source.Add(test);
-source.Name = "MSFT";
-display(source);
-display(test)
-
-#!csharp
-
-#r "nuget:Skender.Stock.Indicators"
-using Skender.Stock.Indicators;
+//EMA test
+GbmFeed gbm = new();
+Ema ema1 = new(gbm.Close, 10, useSma: true);
+TSeries res1 = new(ema1);
+gbm.Add(30);
+IEnumerable quotes = gbm.Close.Select(item => new Quote { Date = item.Time, Close = (decimal)item.Value });
+var SkResults = quotes.GetEma(10).Select(i => i.Ema.Null2NaN()!);
+for (int i=0; i< gbm.Length; i++) {
+ double delta = Math.Round(res1[i].Value, 10) - Math.Round(SkResults.ElementAt(i), 10);
+ Console.WriteLine($"{i,3} {gbm.Close[i].Value,6:F2} {res1[i].Value,10:F4} {SkResults.ElementAt(i),10:F4}\t{delta}");
+}
diff --git a/quantower/Averages/JmaIndicator.cs b/quantower/Averages/JmaIndicator.cs
index e661c9d..5050d3d 100644
--- a/quantower/Averages/JmaIndicator.cs
+++ b/quantower/Averages/JmaIndicator.cs
@@ -11,6 +11,9 @@ public class JmaIndicator : Indicator, IWatchlistIndicator
[InputParameter("Phase", sortIndex: 2, -100, 100, 1, 0)]
public int Phase { get; set; } = 0;
+ [InputParameter("Beta factor", sortIndex: 3, minimum: 0, maximum:5 , increment: 0.01, decimalPlaces: 2)]
+ public double Factor { get; set; } = 0.45;
+
[InputParameter("Data source", sortIndex: 4, variants: [
"Open", SourceType.Open,
"High", SourceType.High,
@@ -34,7 +37,7 @@ public class JmaIndicator : Indicator, IWatchlistIndicator
public int MinHistoryDepths => Math.Max(65,Periods * 2);
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
- public override string ShortName => $"JMA {Periods}:{Phase}:{SourceName}";
+ public override string ShortName => $"JMA {Periods}:{Phase}:{Factor:F2}:{SourceName}";
public JmaIndicator()
{
@@ -49,7 +52,7 @@ public JmaIndicator()
protected override void OnInit()
{
- ma = new Jma(Periods, Phase);
+ ma = new Jma(period: Periods, phase: Phase, factor: Factor);
SourceName = Source.ToString();
base.OnInit();
}
diff --git a/quantower/IndicatorExtensions.cs b/quantower/IndicatorExtensions.cs
index 522c4a3..90d37e1 100644
--- a/quantower/IndicatorExtensions.cs
+++ b/quantower/IndicatorExtensions.cs
@@ -59,14 +59,35 @@ public static TBar GetInputBar(this Indicator indicator, UpdateArgs args)
#pragma warning disable CA1416 // Validate platform compatibility
+ public static void PaintHLine(this Indicator indicator, PaintChartEventArgs args, double value, Pen pen)
+ {
+ if (indicator.CurrentChart == null)
+ return;
+
+ Graphics gr = args.Graphics;
+ var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
+ var converter = mainWindow.CoordinatesConverter;
+ var clientRect = mainWindow.ClientRectangle;
+ gr.SetClip(clientRect);
+ int leftX = clientRect.Left;
+ int rightX = clientRect.Right;
+ int Y = (int)converter.GetChartY(value);
+ using (pen)
+ {
+ gr.DrawLine(pen, new Point(leftX, Y), new Point(rightX, Y));
+ }
+ }
+
public static void PaintSmoothCurve(this Indicator indicator, PaintChartEventArgs args, LineSeries series, int warmupPeriod, bool showColdValues = true, double tension = 0.2)
{
if (!series.Visible || indicator.CurrentChart == null)
return;
Graphics gr = args.Graphics;
- var mainWindow = indicator.CurrentChart.MainWindow;
+ gr.SmoothingMode = SmoothingMode.AntiAlias;
+ var mainWindow = indicator.CurrentChart.Windows[args.WindowIndex];
var converter = mainWindow.CoordinatesConverter;
+
var clientRect = mainWindow.ClientRectangle;
gr.SetClip(clientRect);
diff --git a/quantower/Volatility/AtrIndicator.cs b/quantower/Volatility/AtrIndicator.cs
index de28856..a2f03aa 100644
--- a/quantower/Volatility/AtrIndicator.cs
+++ b/quantower/Volatility/AtrIndicator.cs
@@ -8,9 +8,12 @@ public class AtrIndicator : Indicator, IWatchlistIndicator
[InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)]
public int Periods { get; set; } = 20;
+ [InputParameter("Show cold values", sortIndex: 21)]
+ public bool ShowColdValues { get; set; } = true;
+
private Atr? atr;
protected LineSeries? AtrSeries;
- public static int MinHistoryDepths => 2;
+ public int MinHistoryDepths => Math.Max(5, Periods * 2);
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
public AtrIndicator()
@@ -19,7 +22,7 @@ public AtrIndicator()
Description = "Measures market volatility by calculating the average range between high and low prices.";
SeparateWindow = true;
- AtrSeries = new("ATR", Color.Blue, 2, LineStyle.Solid);
+ AtrSeries = new($"ATR {Periods}", Color.Blue, 2, LineStyle.Solid);
AddLineSeries(AtrSeries);
}
@@ -35,7 +38,16 @@ protected override void OnUpdate(UpdateArgs args)
TValue result = atr!.Calc(input);
AtrSeries!.SetValue(result.Value);
+ AtrSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here
+
}
public override string ShortName => $"ATR ({Periods})";
+
+ public override void OnPaintChart(PaintChartEventArgs args)
+ {
+ base.OnPaintChart(args);
+ this.PaintHLine(args, 0.05, new Pen(Color.DarkRed, width: 2));
+ this.PaintSmoothCurve(args, AtrSeries!, atr!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2);
+ }
}
diff --git a/quantower/Volatility/FlowIndicator.cs b/quantower/Volatility/FlowIndicator.cs
new file mode 100644
index 0000000..d769549
--- /dev/null
+++ b/quantower/Volatility/FlowIndicator.cs
@@ -0,0 +1,78 @@
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using TradingPlatform.BusinessLayer;
+
+namespace QuanTAlib;
+
+public class FlowIndicator : Indicator, IWatchlistIndicator
+{
+ protected string? SourceName;
+ public static int MinHistoryDepths => 2;
+ int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
+
+ public FlowIndicator()
+ {
+ Name = "Flow Visualization";
+ SeparateWindow = false;
+ }
+
+ protected override void OnInit()
+ {
+ // placeholder
+ }
+
+ protected override void OnUpdate(UpdateArgs args)
+ {
+ // placeholder
+ }
+
+#pragma warning disable CA1416 // Validate platform compatibility
+
+ public override void OnPaintChart(PaintChartEventArgs args)
+ {
+ base.OnPaintChart(args);
+ Graphics gr = args.Graphics;
+ gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
+ 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), this.HistoricalData.Time(this!.Count - 1) }.Max();
+ DateTime rightTime = new[] { converter.GetTime(clientRect.Right), this.HistoricalData.Time(0) }.Min();
+
+ int leftIndex = (int)this.HistoricalData.GetIndexByTime(leftTime.Ticks) + 1;
+ int rightIndex = (int)this.HistoricalData.GetIndexByTime(rightTime.Ticks);
+ int width = this.CurrentChart.BarsWidth;
+
+ for (int i = rightIndex; i < leftIndex; i++)
+ {
+ int barX1 = (int)converter.GetChartX(this.HistoricalData.Time(i));
+ int barY1 = (int)converter.GetChartY(this.HistoricalData.Open(i));
+ int barYHigh = (int)converter.GetChartY(this.HistoricalData.High(i));
+ int barYLow = (int)converter.GetChartY(this.HistoricalData.Low(i));
+ int barX2 = barX1 + width;
+ int barY2 = (int)converter.GetChartY(this.HistoricalData.Close(i));
+ using (Brush transparentBrush = new SolidBrush(Color.FromArgb(250, 70, 70, 70)))
+ gr.FillRectangle(transparentBrush, barX1, barYHigh - 1, CurrentChart.BarsWidth, Math.Abs(barYLow - barYHigh) + 2);
+ using (Pen defaultPen = new(Color.Yellow, 3))
+ {
+ defaultPen.StartCap = LineCap.Round;
+ defaultPen.EndCap = LineCap.Round;
+ gr.DrawLine(defaultPen, barX1, barY1, barX2, barY2);
+ }
+ if (i > 0)
+ {
+ int barX0 = (int)converter.GetChartX(this.HistoricalData.Time(i - 1));
+ int barY0 = (int)converter.GetChartY(this.HistoricalData.Open(i - 1));
+ using (Pen dottedPen = new(Color.Yellow, 1))
+ {
+ dottedPen.DashStyle = DashStyle.Dot;
+ gr.DrawLine(dottedPen, barX2, barY2, barX0, barY0);
+ }
+
+ }
+
+ }
+ }
+
+}
diff --git a/quantower/Volatility/JbandsIndicator.cs b/quantower/Volatility/JbandsIndicator.cs
index 608f93c..a77312c 100644
--- a/quantower/Volatility/JbandsIndicator.cs
+++ b/quantower/Volatility/JbandsIndicator.cs
@@ -25,7 +25,8 @@ public class JbandsIndicator : Indicator, IWatchlistIndicator
[InputParameter("vShort", sortIndex: 6, -100, 100, 1, 0)]
public int Phase { get; set; } = 10;
- private Jma? jma;
+ private Jma? jmaUp;
+ private Jma? jmaLo;
protected LineSeries? UbSeries;
protected LineSeries? LbSeries;
protected string? SourceName;
@@ -46,18 +47,20 @@ public JbandsIndicator()
protected override void OnInit()
{
- jma = new(Periods, phase: Phase);
+ jmaUp = new(Periods, phase: Phase);
+ jmaLo = new(Periods, phase: Phase);
SourceName = Source.ToString();
base.OnInit();
}
protected override void OnUpdate(UpdateArgs args)
{
- TValue input = this.GetInputValue(args, Source);
- jma!.Calc(input);
+ TBar input = IndicatorExtensions.GetInputBar(this, args);
+ jmaUp!.Calc(input.High);
+ jmaLo!.Calc(input.Low);
- UbSeries!.SetValue(jma.UpperBand);
- LbSeries!.SetValue(jma.LowerBand);
+ UbSeries!.SetValue(jmaUp.UpperBand);
+ LbSeries!.SetValue(jmaLo.LowerBand);
}
public override string ShortName => $"JBands ({Periods}:{Phase})";