Files
2024-09-22 17:31:24 -07:00

199 lines
6.5 KiB
Plaintext

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