#!meta {"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}} #!csharp #r "..\src\obj\Debug\QuanTAlib.dll" #r "nuget:Skender.Stock.Indicators" using Skender.Stock.Indicators; using QuanTAlib; #!csharp public class Htit : AbstractBase { private readonly int _period; private readonly CircularBuffer _pr, _sp, _dt, _pd, _q1, _i1, _q2, _i2, _re, _im, _sd, _it; public Htit(int period = 50) : base() { _period = period; _pr = new CircularBuffer(period); _sp = new CircularBuffer(period); _dt = new CircularBuffer(period); _pd = new CircularBuffer(period); _q1 = new CircularBuffer(period); _i1 = new CircularBuffer(period); _q2 = new CircularBuffer(period); _i2 = new CircularBuffer(period); _re = new CircularBuffer(period); _im = new CircularBuffer(period); _sd = new CircularBuffer(period); _it = new CircularBuffer(period); Name = "Htit"; WarmupPeriod = 12; // Minimum required data points Init(); } public Htit(object source, int period = 50) : this(period) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } public override void Init() { base.Init(); _pr.Clear(); _sp.Clear(); _dt.Clear(); _pd.Clear(); _q1.Clear(); _i1.Clear(); _q2.Clear(); _i2.Clear(); _re.Clear(); _im.Clear(); _sd.Clear(); _it.Clear(); } protected override void ManageState(bool isNew) { if (isNew) { _index++; } } protected override double GetLastValid() { return _it[^1]; } protected override double Calculation() { ManageState(Input.IsNew); _pr.Add(Input.Value, Input.IsNew); if (_index > 6) { double adj = (0.075 * _pd[^2]) + 0.54; // smooth and detrender _sp.Add(((4 * _pr[^1]) + (3 * _pr[^2]) + (2 * _pr[^3]) + _pr[^4]) / 10, Input.IsNew); _dt.Add(((0.0962 * _sp[^1]) + (0.5769 * _sp[^3]) - (0.5769 * _sp[^5]) - (0.0962 * _sp[^7])) * adj, Input.IsNew); // in-phase and quadrature _q1.Add(((0.0962 * _dt[^1]) + (0.5769 * _dt[^3]) - (0.5769 * _dt[^5]) - (0.0962 * _dt[^7])) * adj, Input.IsNew); _i1.Add(_dt[^4], Input.IsNew); // advance the phases by 90 degrees double jI = ((0.0962 * _i1[^1]) + (0.5769 * _i1[^3]) - (0.5769 * _i1[^5]) - (0.0962 * _i1[^7])) * adj; double jQ = ((0.0962 * _q1[^1]) + (0.5769 * _q1[^3]) - (0.5769 * _q1[^5]) - (0.0962 * _q1[^7])) * adj; // phasor addition for 3-bar averaging _i2.Add(_i1[^1] - jQ, Input.IsNew); _q2.Add(_q1[^1] + jI, Input.IsNew); _i2[^1] = (0.2 * _i2[^1]) + (0.8 * _i2[^2]); // smoothing it _q2[^1] = (0.2 * _q2[^1]) + (0.8 * _q2[^2]); // homodyne discriminator _re.Add((_i2[^1] * _i2[^2]) + (_q2[^1] * _q2[^2]), Input.IsNew); _im.Add((_i2[^1] * _q2[^2]) - (_q2[^1] * _i2[^2]), Input.IsNew); _re[^1] = (0.2 * _re[^1]) + (0.8 * _re[^2]); // smoothing it _im[^1] = (0.2 * _im[^1]) + (0.8 * _im[^2]); // calculate period _pd.Add(_im[^1] != 0 && _re[^1] != 0 ? 2 * Math.PI / Math.Atan(_im[^1] / _re[^1]) : 0, Input.IsNew); // adjust period to thresholds _pd[^1] = (_pd[^1] > 1.5 * _pd[^2]) ? 1.5 * _pd[^2] : _pd[^1]; _pd[^1] = (_pd[^1] < 0.67 * _pd[^2]) ? 0.67 * _pd[^2] : _pd[^1]; _pd[^1] = (_pd[^1] < 6.0) ? 6.0 : _pd[^1]; _pd[^1] = (_pd[^1] > 50.0) ? 50.0 : _pd[^1]; // smooth the period _pd[^1] = (0.2 * _pd[^1]) + (0.8 * _pd[^2]); _sd.Add((0.33 * _pd[^1]) + (0.67 * _sd[^2]), Input.IsNew); //check this loop // smooth dominant cycle period int dcPeriods = (int)(_sd[^1] + 0.5); double sumPr = 0; for (int d = 1; d < dcPeriods+1; d++) //0 -> 5 { sumPr += _pr[^d]; } _it.Add(dcPeriods > 0 ? sumPr / dcPeriods : _pr[^1], Input.IsNew); Console.WriteLine($"{_index}\t {_it[^1]:F2}"); // final indicators double Trendline, SmoothPrice; Trendline = _index >= 12 // 12th bar ? ((4 * _it[^1]) + (3 * _it[^2]) + (2 * _it[^3]) + _it[^4]) / 10.0 : _pr[^1]; SmoothPrice = ((4 * _pr[^1]) + (3 * _pr[^2]) + (2 * _pr[^3]) + _pr[^4]) / 10.0; Value = Trendline; } else { Value = _pr[^1]; _pd.Add(0, Input.IsNew); _sp.Add(0, Input.IsNew); _dt.Add(0, Input.IsNew); _i1.Add(0, Input.IsNew); _q1.Add(0, Input.IsNew); _i2.Add(0, Input.IsNew); _q2.Add(0, Input.IsNew); _re.Add(0, Input.IsNew); _im.Add(0, Input.IsNew); _sd.Add(0, Input.IsNew); _it.Add(_pr[^1], Input.IsNew); } IsHot = _index >= WarmupPeriod; return Value; } } #!csharp Random rnd = new((int)DateTime.Now.Ticks); GbmFeed feed = new(sigma: 0.5, mu: 0.0); TBarSeries bars = new(feed); feed.Add(15); IEnumerable quotes = feed.Select(q => new Quote { Date = q.Time, Open = (decimal)q.Open, High = (decimal)q.High, Low = (decimal)q.Low, Close = (decimal)q.Close, Volume = (decimal)q.Volume }); Htit ma = new(); TSeries QL = new(); foreach (TBar item in feed) { QL.Add(ma.Calc(new TValue(item.Time, item.Close))); } var SK = quotes.Select(q => (q.Date, (double)q.Close)).GetHtTrendline().Select(i => i.Trendline.Null2NaN()!); Console.WriteLine($"Data\tSkend\tQuanTAlib"); for (int i = 8; i < feed.Length; i++) { Console.WriteLine($"{i}\t{feed[i].Close,6:F2}\t{SK.ElementAt(i),6:F2}\t{QL[i].Value,6:F2} {Math.Truncate(SK.ElementAt(i)*100)==Math.Truncate(QL[i].Value*100)}"); }