Skip to content

Commit

Permalink
Reduce duplicated code by reusing common functions
Browse files Browse the repository at this point in the history
  • Loading branch information
hmG3 committed Jun 16, 2024
1 parent 2f56cff commit dfdb9d2
Show file tree
Hide file tree
Showing 31 changed files with 672 additions and 993 deletions.
8 changes: 4 additions & 4 deletions src/TALib.NETCore/Core/IndicatorFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal IndicatorFunction(

public Core.RetCode Run<T>(T[][] inputs, T[] options, T[][] outputs) where T : IFloatingPointIeee754<T>

Check warning on line 57 in src/TALib.NETCore/Core/IndicatorFunction.cs

View workflow job for this annotation

GitHub Actions / analyze (ubuntu-latest)

Make this method private or simplify its parameters to not use multidimensional/jagged arrays. (https://rules.sonarsource.com/csharp/RSPEC-2368)
{
var functionMethod = ReflectMethods<T>(publicOnly: false)
var functionMethod = ReflectMethods(publicOnly: false)
.FirstOrDefault(mi => !mi.Name.EndsWith(LookbackSuffix) && FunctionMethodSelector(mi)) ??
throw new MissingMethodException(null, $"{Name}<{typeof(T).Name}>");

Expand All @@ -78,9 +78,9 @@ public Core.RetCode Run<T>(T[][] inputs, T[] options, T[][] outputs) where T : I
return retCode;
}

public int Lookback<T>(params int[] options) where T : IFloatingPointIeee754<T>
public int Lookback(params int[] options)
{
var lookbackMethod = ReflectMethods<T>(publicOnly: true)
var lookbackMethod = ReflectMethods(publicOnly: true)
.FirstOrDefault(mi => mi.Name.EndsWith(LookbackSuffix) && LookbackMethodSelector(mi))
?? throw new MissingMethodException(null, LookbackMethodName);

Expand Down Expand Up @@ -124,7 +124,7 @@ public void SetUnstablePeriod(int period)

public override string ToString() => Name;

private static IEnumerable<MethodInfo> ReflectMethods<T>(bool publicOnly) where T : IFloatingPointIeee754<T> =>
private static IEnumerable<MethodInfo> ReflectMethods(bool publicOnly) =>
typeof(Functions).GetMethods(BindingFlags.Static | (publicOnly ? BindingFlags.Public : BindingFlags.NonPublic))
.Concat(typeof(Candles).GetMethods(BindingFlags.Static | (publicOnly ? BindingFlags.Public : BindingFlags.NonPublic)));

Expand Down
202 changes: 176 additions & 26 deletions src/TALib.NETCore/Functions/FunctionUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,17 +203,20 @@ private static Core.RetCode CalcPriceOscillator<T>(
{
outBegIdx = outNbElement = 0;

// Make sure slow is really slower than the fast period! if not, swap...
if (optInSlowPeriod < optInFastPeriod)
{
(optInSlowPeriod, optInFastPeriod) = (optInFastPeriod, optInSlowPeriod);
}

// Calculate the fast MA into the tempBuffer.
var retCode = Ma(inReal, startIdx, endIdx, tempBuffer, out var outBegIdx2, out _, optInFastPeriod, optInMethod);
if (retCode != Core.RetCode.Success)
{
return retCode;
}

// Calculate the slow MA into the output.
retCode = Ma(inReal, startIdx, endIdx, outReal, out var outBegIdx1, out var outNbElement1, optInSlowPeriod, optInMethod);
if (retCode != Core.RetCode.Success)
{
Expand All @@ -224,11 +227,13 @@ private static Core.RetCode CalcPriceOscillator<T>(
{
if (doPercentageOutput)
{
// Calculate ((fast MA)-(slow MA))/(slow MA) in the output.
T tempReal = outReal[i];
outReal[i] = !T.IsZero(tempReal) ? (tempBuffer[j] - tempReal) / tempReal * Hundred<T>() : T.Zero;
}
else
{
// Calculate (fast MA)-(slow MA) in the output.
outReal[i] = tempBuffer[j] - outReal[i];
}
}
Expand Down Expand Up @@ -386,6 +391,151 @@ private static Core.RetCode CalcVariance<T>(
return Core.RetCode.Success;
}

private static T CalcAccumulationDistribution<T>(
ReadOnlySpan<T> high,
ReadOnlySpan<T> low,
ReadOnlySpan<T> close,
ReadOnlySpan<T> volume,
ref int today,
T ad) where T : IFloatingPointIeee754<T>
{
T h = high[today];
T l = low[today];
T tmp = h - l;
T c = close[today];
if (tmp > T.Zero)
{
ad += (c - l - (h - c)) / tmp * volume[today];
}

today++;

return ad;
}

private static (int, T) CalcLowest<T>(
ReadOnlySpan<T> input,
int trailingIdx,
int today,
int lowestIdx,
T lowest)
where T : IFloatingPointIeee754<T>
{
T tmp = input[today];
if (lowestIdx < trailingIdx)
{
lowestIdx = trailingIdx;
lowest = input[lowestIdx];
var i = lowestIdx;
while (++i <= today)
{
tmp = input[i];
if (tmp > lowest)
{
continue;
}

lowestIdx = i;
lowest = tmp;
}
}
else if (tmp <= lowest)
{
lowestIdx = today;
lowest = tmp;
}

return (lowestIdx, lowest);
}

private static (int, T) CalcHighest<T>(
ReadOnlySpan<T> input,
int trailingIdx,
int today,
int highestIdx,
T highest)
where T : IFloatingPointIeee754<T>
{
T tmp = input[today];
if (highestIdx < trailingIdx)
{
highestIdx = trailingIdx;
highest = input[highestIdx];
var i = highestIdx;
while (++i <= today)
{
tmp = input[i];
if (tmp < highest)
{
continue;
}

highestIdx = i;
highest = tmp;
}
}
else if (tmp >= highest)
{
highestIdx = today;
highest = tmp;
}

return (highestIdx, highest);
}

private static void UpdateDMAndTR<T>(
ReadOnlySpan<T> high,
ReadOnlySpan<T> low,
ReadOnlySpan<T> close,
ref int today,
ref T prevHigh,
ref T prevLow,
ref T prevClose,
ref T prevPlusDM,
ref T prevMinusDM,
ref T prevTR,
T timePeriod,
bool applySmoothing = true)
where T : IFloatingPointIeee754<T>
{
var diffP = high[today] - prevHigh;
var diffM = prevLow - low[today];
prevHigh = high[today];
prevLow = low[today];

if (applySmoothing)
{
prevPlusDM -= prevPlusDM / timePeriod;
prevMinusDM -= prevMinusDM / timePeriod;
}

if (diffM > T.Zero && diffP < diffM)
{
prevMinusDM += diffM;
}
else if (diffP > T.Zero && diffP > diffM)
{
prevPlusDM += diffP;
}

if (close.IsEmpty)
{
return;
}

var trueRange = TrueRange(prevHigh, prevLow, prevClose);
prevTR = applySmoothing ? prevTR - prevTR / timePeriod + trueRange : prevTR + trueRange;
prevClose = close[today];
}

private static (T minusDI, T plusDI) CalculateDI<T>(T prevMinusDM, T prevPlusDM, T prevTR) where T : IFloatingPointIeee754<T>
{
var minusDI = Hundred<T>() * (prevMinusDM / prevTR);
var plusDI = Hundred<T>() * (prevPlusDM / prevTR);

return (minusDI, plusDI);
}

private static T TrueRange<T>(T th, T tl, T yc) where T : IFloatingPointIeee754<T>
{
T range = th - tl;
Expand Down Expand Up @@ -422,10 +572,26 @@ public enum HilbertKeys
JQ = 39
}

public static T[] HilbertVariablesFactory<T>() where T : IFloatingPointIeee754<T> => new T[4 * 11];
public static T[] HilbertBufferFactory<T>() where T : IFloatingPointIeee754<T> => new T[4 * 11];

public static void DoHilbertOdd<T>(
Span<T> buffer,
HilbertKeys baseKey,
T input,
int hilbertIdx,
T adjustedPrevPeriod) where T : IFloatingPointIeee754<T> =>
DoHilbertTransform(buffer, baseKey, input, true, hilbertIdx, adjustedPrevPeriod);

public static void DoHilbertEven<T>(
Span<T> buffer,
HilbertKeys baseKey,
T input,
int hilbertIdx,
T adjustedPrevPeriod) where T : IFloatingPointIeee754<T> =>
DoHilbertTransform(buffer, baseKey, input, false, hilbertIdx, adjustedPrevPeriod);

private static void DoHilbertTransform<T>(
Span<T> variables,
Span<T> buffer,
HilbertKeys baseKey,
T input,
bool isOdd,
Expand All @@ -442,31 +608,15 @@ private static void DoHilbertTransform<T>(
var prevIndex = baseIndex + (isOdd ? 1 : 2);
var prevInputIndex = baseIndex + (isOdd ? 3 : 4);

variables[baseIndex] = -variables[hilbertIndex];
variables[hilbertIndex] = hilbertTempT;
variables[baseIndex] += hilbertTempT;
variables[baseIndex] -= variables[prevIndex];
variables[prevIndex] = b * variables[prevInputIndex];
variables[baseIndex] += variables[prevIndex];
variables[prevInputIndex] = input;
variables[baseIndex] *= adjustedPrevPeriod;
buffer[baseIndex] = -buffer[hilbertIndex];
buffer[hilbertIndex] = hilbertTempT;
buffer[baseIndex] += hilbertTempT;
buffer[baseIndex] -= buffer[prevIndex];
buffer[prevIndex] = b * buffer[prevInputIndex];
buffer[baseIndex] += buffer[prevIndex];
buffer[prevInputIndex] = input;
buffer[baseIndex] *= adjustedPrevPeriod;
}

public static void DoHilbertOdd<T>(
Span<T> variables,
HilbertKeys baseKey,
T input,
int hilbertIdx,
T adjustedPrevPeriod) where T : IFloatingPointIeee754<T> =>
DoHilbertTransform(variables, baseKey, input, true, hilbertIdx, adjustedPrevPeriod);

public static void DoHilbertEven<T>(
Span<T> variables,
HilbertKeys baseKey,
T input,
int hilbertIdx,
T adjustedPrevPeriod) where T : IFloatingPointIeee754<T> =>
DoHilbertTransform(variables, baseKey, input, false, hilbertIdx, adjustedPrevPeriod);
}

private static T Two<T>() where T : IFloatingPointIeee754<T> => T.CreateChecked(2);
Expand Down
6 changes: 6 additions & 0 deletions src/TALib.NETCore/Functions/TA_Accbands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ public static Core.RetCode Accbands<T>(
return Core.RetCode.Success;
}

// Buffer will contain also the lookback required for SMA to satisfy the caller requested startIdx/endIdx.
var outputSize = endIdx - startIdx + 1;
var bufferSize = outputSize + lookbackTotal;
Span<T> tempBuffer1 = new T[bufferSize];
Span<T> tempBuffer2 = new T[bufferSize];

// Calculate the upper/lower band at the same time (no SMA yet).
// Must start calculation back enough to cover the lookback required later for the SMA.
for (int j = 0, i = startIdx - lookbackTotal; i <= endIdx; i++, j++)
{
T tempReal = inHigh[i] + inLow[i];
Expand All @@ -80,18 +83,21 @@ public static Core.RetCode Accbands<T>(
}
}

// Calculate the middle band, which is a moving average of the close.
var retCode = Sma(inClose, startIdx, endIdx, outRealMiddleBand, out _, out var outNbElementDummy, optInTimePeriod);
if (retCode != Core.RetCode.Success || outNbElementDummy != outputSize)
{
return retCode;
}

// Take the SMA for the upper band.
retCode = Sma(tempBuffer1, 0, bufferSize - 1, outRealUpperBand, out _, out outNbElementDummy, optInTimePeriod);
if (retCode != Core.RetCode.Success || outNbElementDummy != outputSize)
{
return retCode;
}

// Take the SMA for the lower band.
retCode = Sma(tempBuffer2, 0, bufferSize - 1, outRealLowerBand, out _, out outNbElementDummy, optInTimePeriod);
if (retCode != Core.RetCode.Success || outNbElementDummy != outputSize)
{
Expand Down
20 changes: 8 additions & 12 deletions src/TALib.NETCore/Functions/TA_Ad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ public static Core.RetCode Ad<T>(
return Core.RetCode.OutOfRangeStartIndex;
}

/* Note:
* Results from this function might vary slightly when using float instead of double
* and this cause a different floating-point precision to be used.
* For most function, this is not an apparent difference but for function using large cumulative values
* (like this AD function), minor imprecision adds up and becomes significant.
* For better precision, use double in calculations.
*/
var nbBar = endIdx - startIdx + 1;
outBegIdx = startIdx;
outNbElement = nbBar;
Expand All @@ -50,19 +57,8 @@ public static Core.RetCode Ad<T>(

while (nbBar != 0)
{
T high = inHigh[currentBar];
T low = inLow[currentBar];
T tmp = high - low;
T close = inClose[currentBar];

if (tmp > T.Zero)
{
ad += (close - low - (high - close)) / tmp * inVolume[currentBar];
}

ad = CalcAccumulationDistribution(inHigh, inLow, inClose, inVolume, ref currentBar, ad);
outReal[outIdx++] = ad;

currentBar++;
nbBar--;
}

Expand Down
Loading

0 comments on commit dfdb9d2

Please sign in to comment.