#!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<Quote> 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)}");
}
