mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-17 01:58:06 +00:00
- Updated event handler signatures to use TValueEventArgs for consistency in Mama, Mgdi, Pwma, Rma, Sma, Ssf, Super, T3, Tema, Trima, Usf, Vidya, Wma, and Atr classes. - Enhanced argument validation by specifying parameter names in exceptions for clarity. - Adjusted tests to align with new event handler signatures. - Improved code readability and maintainability by using structured records and lambda expressions.
184 lines
6.0 KiB
C#
184 lines
6.0 KiB
C#
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Numerics;
|
|
|
|
namespace QuanTAlib;
|
|
|
|
/// <summary>
|
|
/// BOP: Balance of Power
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// BOP measures the strength of buyers vs sellers by comparing the close price to the open price,
|
|
/// relative to the high-low range.
|
|
///
|
|
/// Formula:
|
|
/// BOP = (Close - Open) / (High - Low)
|
|
///
|
|
/// Key characteristics:
|
|
/// - Oscillates between -1 and 1
|
|
/// - 1 indicates buyers dominated (Close = High, Open = Low)
|
|
/// - -1 indicates sellers dominated (Close = Low, Open = High)
|
|
/// - 0 indicates balance (Close = Open)
|
|
/// - Often smoothed with an SMA (though this implementation provides the raw value)
|
|
///
|
|
/// Sources:
|
|
/// https://www.investopedia.com/terms/b/bop.asp
|
|
/// </remarks>
|
|
[SkipLocalsInit]
|
|
public sealed class Bop : ITValuePublisher
|
|
{
|
|
/// <summary>
|
|
/// Display name for the indicator.
|
|
/// </summary>
|
|
public static string Name => "Bop";
|
|
|
|
public event TValuePublishedHandler? Pub;
|
|
|
|
/// <summary>
|
|
/// Current BOP value.
|
|
/// </summary>
|
|
public TValue Last { get; private set; }
|
|
|
|
/// <summary>
|
|
/// True if the indicator has a valid value (always true for BOP as it has no warmup).
|
|
/// </summary>
|
|
public static bool IsHot => true;
|
|
|
|
/// <summary>
|
|
/// The number of bars required for the indicator to warm up.
|
|
/// </summary>
|
|
public static int WarmupPeriod => 0;
|
|
|
|
/// <summary>
|
|
/// Resets the indicator state.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Reset()
|
|
{
|
|
Last = default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the indicator with a new bar.
|
|
/// </summary>
|
|
/// <param name="input">The input bar.</param>
|
|
/// <param name="isNew">Whether this is a new bar or an update to the current one.</param>
|
|
/// <returns>The updated BOP value.</returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public TValue Update(TBar input, bool isNew = true)
|
|
{
|
|
double range = input.High - input.Low;
|
|
double bop = 0;
|
|
|
|
if (range > double.Epsilon)
|
|
{
|
|
bop = (input.Close - input.Open) / range;
|
|
}
|
|
|
|
Last = new TValue(input.Time, bop);
|
|
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
|
return Last;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the indicator with a new value (not supported for BOP as it requires OHLC).
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public TValue Update(TValue input, bool isNew = true)
|
|
{
|
|
// BOP requires OHLC, so we can't calculate it from a single value.
|
|
// We'll treat the input value as Close, and assume Open=Close, High=Close, Low=Close,
|
|
// which results in 0/0 -> 0.
|
|
// Or we could throw NotSupportedException.
|
|
// Given the interface contract, returning 0 is safer than crashing.
|
|
Last = new TValue(input.Time, 0);
|
|
Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = true });
|
|
return Last;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the indicator with a series of bars.
|
|
/// </summary>
|
|
public static TSeries Update(TBarSeries source)
|
|
{
|
|
return Batch(source);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates BOP for a series of bars.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void Calculate(ReadOnlySpan<double> open, ReadOnlySpan<double> high, ReadOnlySpan<double> low, ReadOnlySpan<double> close, Span<double> destination)
|
|
{
|
|
int len = Math.Min(open.Length, Math.Min(high.Length, Math.Min(low.Length, close.Length)));
|
|
if (destination.Length < len)
|
|
len = destination.Length;
|
|
|
|
int i = 0;
|
|
if (Vector.IsHardwareAccelerated && len >= Vector<double>.Count)
|
|
{
|
|
var epsilon = new Vector<double>(double.Epsilon);
|
|
ref var oRef = ref MemoryMarshal.GetReference(open);
|
|
ref var hRef = ref MemoryMarshal.GetReference(high);
|
|
ref var lRef = ref MemoryMarshal.GetReference(low);
|
|
ref var cRef = ref MemoryMarshal.GetReference(close);
|
|
ref var dRef = ref MemoryMarshal.GetReference(destination);
|
|
|
|
while (i <= len - Vector<double>.Count)
|
|
{
|
|
var o = Vector.LoadUnsafe(ref oRef, (nuint)i);
|
|
var h = Vector.LoadUnsafe(ref hRef, (nuint)i);
|
|
var l = Vector.LoadUnsafe(ref lRef, (nuint)i);
|
|
var c = Vector.LoadUnsafe(ref cRef, (nuint)i);
|
|
|
|
var range = h - l;
|
|
var body = c - o;
|
|
|
|
// Create a mask where range > Epsilon
|
|
var mask = Vector.GreaterThan(range, epsilon);
|
|
|
|
// Perform division (results in NaN/Inf if range is 0, but we'll mask it out)
|
|
var div = body / range;
|
|
|
|
// Select div where mask is true, otherwise 0
|
|
var result = Vector.ConditionalSelect(mask, div, Vector<double>.Zero);
|
|
|
|
result.StoreUnsafe(ref dRef, (nuint)i);
|
|
|
|
i += Vector<double>.Count;
|
|
}
|
|
}
|
|
|
|
for (; i < len; i++)
|
|
{
|
|
double range = high[i] - low[i];
|
|
destination[i] = range > double.Epsilon ? (close[i] - open[i]) / range : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates BOP for a TBarSeries.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static TSeries Batch(TBarSeries source)
|
|
{
|
|
if (source.Count == 0) return new TSeries([], []);
|
|
|
|
var len = source.Count;
|
|
|
|
var t = new List<long>(len);
|
|
var v = new List<double>(len);
|
|
CollectionsMarshal.SetCount(t, len);
|
|
CollectionsMarshal.SetCount(v, len);
|
|
|
|
var tSpan = CollectionsMarshal.AsSpan(t);
|
|
var vSpan = CollectionsMarshal.AsSpan(v);
|
|
|
|
source.Open.Times.CopyTo(tSpan);
|
|
Calculate(source.Open.Values, source.High.Values, source.Low.Values, source.Close.Values, vSpan);
|
|
|
|
return new TSeries(t, v);
|
|
}
|
|
}
|
|
|