python wrapper

This commit is contained in:
Miha Kralj
2026-02-28 14:14:35 -08:00
parent 82e0248eb0
commit 83e9511261
521 changed files with 62395 additions and 15669 deletions
@@ -107,12 +107,12 @@ public class TyppriceIndicatorTests
indicator.Initialize();
var now = DateTime.UtcNow;
// H=110, L=90, C=105 → (110+90+105)/3 = 101.666...
// O=100, H=110, L=90, C=105 → (100+110+90)/3 = 100.0
indicator.HistoricalData.AddBar(now, 100, 110, 90, 105, 1000);
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
double val = indicator.LinesSeries[0].GetValue(0);
Assert.Equal(305.0 * (1.0 / 3.0), val, 10);
Assert.Equal(300.0 * (1.0 / 3.0), val, 10);
}
[Fact]
+1 -1
View File
@@ -24,7 +24,7 @@ public sealed class TyppriceIndicator : Indicator, IWatchlistIndicator
OnBackGround = true;
SeparateWindow = false;
Name = "TYPPRICE - Typical Price";
Description = "Average of High, Low, and Close prices: (H+L+C)/3.";
Description = "Average of Open, High, and Low prices: (O+H+L)/3.";
_series = new LineSeries(name: "TYPPRICE", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid);
AddLineSeries(_series);
+17 -17
View File
@@ -44,23 +44,23 @@ public class TyppriceTests
#region Basic Calculation Tests
[Fact]
public void Update_Bar_ReturnsHLC3()
public void Update_Bar_ReturnsOHL3()
{
var indicator = new Typprice();
var bar = new TBar(DateTime.UtcNow, 100, 110, 90, 105, 1000);
var result = indicator.Update(bar);
// (110 + 90 + 105) * (1/3) = 101.666...
double expected = (110.0 + 90.0 + 105.0) * (1.0 / 3.0);
// (100 + 110 + 90) * (1/3) = 100.0
double expected = (100.0 + 110.0 + 90.0) * (1.0 / 3.0);
Assert.Equal(expected, result.Value, Tolerance);
}
[Fact]
public void Update_Bar_MatchesTBarHLC3()
public void Update_Bar_MatchesTBarOHL3()
{
var indicator = new Typprice();
var bar = new TBar(DateTime.UtcNow, 50, 60, 40, 55, 500);
var result = indicator.Update(bar);
Assert.Equal(bar.HLC3, result.Value, Tolerance);
Assert.Equal(bar.OHL3, result.Value, Tolerance);
}
[Fact]
@@ -94,7 +94,7 @@ public class TyppriceTests
indicator.Update(new TBar(time.AddMinutes(1), 105, 115, 95, 110, 1000), isNew: true);
var corrected = indicator.Update(new TBar(time.AddMinutes(1), 106, 120, 80, 111, 1000), isNew: false);
double expected = (120.0 + 80.0 + 111.0) * (1.0 / 3.0);
double expected = (106.0 + 120.0 + 80.0) * (1.0 / 3.0);
Assert.Equal(expected, corrected.Value, Tolerance);
}
@@ -168,7 +168,7 @@ public class TyppriceTests
// Mode 3: Span batch
double[] spanOutput = new double[bars.Count];
Typprice.Batch(bars.HighValues, bars.LowValues, bars.CloseValues, spanOutput);
Typprice.Batch(bars.OpenValues, bars.HighValues, bars.LowValues, spanOutput);
for (int i = 0; i < bars.Count; i++)
{
@@ -178,7 +178,7 @@ public class TyppriceTests
}
[Fact]
public void AllBars_MatchTBarHLC3()
public void AllBars_MatchTBarOHL3()
{
var bars = GenerateBars(50);
var indicator = new Typprice();
@@ -186,7 +186,7 @@ public class TyppriceTests
for (int i = 0; i < bars.Count; i++)
{
var result = indicator.Update(bars[i], isNew: true);
Assert.Equal(bars[i].HLC3, result.Value, Tolerance);
Assert.Equal(bars[i].OHL3, result.Value, Tolerance);
}
}
@@ -197,24 +197,24 @@ public class TyppriceTests
[Fact]
public void Batch_MismatchedLengths_ThrowsArgumentException()
{
double[] high = new double[10];
double[] low = new double[5]; // mismatched
double[] close = new double[10];
double[] open = new double[10];
double[] high = new double[5]; // mismatched
double[] low = new double[10];
double[] output = new double[10];
var ex = Assert.Throws<ArgumentException>(() => Typprice.Batch(high, low, close, output));
Assert.Equal("low", ex.ParamName);
var ex = Assert.Throws<ArgumentException>(() => Typprice.Batch(open, high, low, output));
Assert.Equal("high", ex.ParamName);
}
[Fact]
public void Batch_OutputTooShort_ThrowsArgumentException()
{
double[] open = new double[10];
double[] high = new double[10];
double[] low = new double[10];
double[] close = new double[10];
double[] output = new double[5]; // too short
var ex = Assert.Throws<ArgumentException>(() => Typprice.Batch(high, low, close, output));
var ex = Assert.Throws<ArgumentException>(() => Typprice.Batch(open, high, low, output));
Assert.Equal("output", ex.ParamName);
}
@@ -231,7 +231,7 @@ public class TyppriceTests
{
var bars = GenerateBars(10_000);
double[] output = new double[bars.Count];
Typprice.Batch(bars.HighValues, bars.LowValues, bars.CloseValues, output);
Typprice.Batch(bars.OpenValues, bars.HighValues, bars.LowValues, output);
Assert.True(double.IsFinite(output[^1]));
}
+150 -39
View File
@@ -1,14 +1,14 @@
using System.Runtime.CompilerServices;
using TALib;
using Skender.Stock.Indicators;
using Xunit;
using Xunit.Abstractions;
namespace QuanTAlib.Tests;
/// <summary>
/// Validation for Typprice (Typical Price) = (H+L+C)/3.
/// Cross-validated against TA-Lib TYPPRICE (exact match expected).
/// Skender, Tulip, and Ooples do not implement TYPPRICE as a standalone function.
/// Validation for Typprice (Typical Price) = (O+H+L)/3.
/// Cross-validates against Skender.Stock.Indicators GetBaseQuote(CandlePart.OHL3),
/// plus formula verification, streaming-vs-batch consistency, and determinism.
/// </summary>
public sealed class TyppriceValidationTests : IDisposable
{
@@ -36,39 +36,116 @@ public sealed class TyppriceValidationTests : IDisposable
}
}
// ── A) Cross-validate with TA-Lib TYPPRICE ────────────────────────────────
// ── A) Skender OHL3 batch validation ──────────────────────────────────────
[Fact]
public void TALib_TypPrice_Batch_Validates()
public void Validate_Against_Skender_OHL3_Batch()
{
double[] high = _data.HighPrices.ToArray();
double[] low = _data.LowPrices.ToArray();
double[] close = _data.ClosePrices.ToArray();
// Skender GetBaseQuote(CandlePart.OHL3) computes (Open+High+Low)/3
var skenderResults = _data.SkenderQuotes
.GetBaseQuote(CandlePart.OHL3)
.ToList();
// TA-Lib TypPrice
var taOut = new double[high.Length];
var retCode = Functions.TypPrice(high.AsSpan(), low.AsSpan(), close.AsSpan(),
0..^0, taOut, out var outRange);
Assert.Equal(TALib.Core.RetCode.Success, retCode);
var (offset, length) = outRange.GetOffsetAndLength(taOut.Length);
var qlResult = Typprice.Batch(_data.Bars);
// QuanTAlib batch span
var qlOut = new double[high.Length];
Typprice.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(), qlOut.AsSpan());
Assert.Equal(qlResult.Count, skenderResults.Count);
int mismatches = 0;
for (int j = 0; j < length; j++)
int count = qlResult.Count;
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
for (int i = start; i < count; i++)
{
int qi = j + offset;
double err = Math.Abs(qlOut[qi] - taOut[j]);
if (err > ValidationHelper.TalibTolerance) { mismatches++; }
double qlVal = qlResult.Values[i];
double skVal = skenderResults[i].Value;
Assert.True(
Math.Abs(qlVal - skVal) <= ValidationHelper.SkenderTolerance,
$"Mismatch at index {i}: QuanTAlib={qlVal:G17}, Skender={skVal:G17}, Diff={Math.Abs(qlVal - skVal):G17}");
}
double mismatchRate = (double)mismatches / length;
_output.WriteLine($"TALib TYPPRICE: {length} compared, {mismatches} mismatches ({mismatchRate:P2})");
Assert.Equal(0, mismatches);
_output.WriteLine($"TYPPRICE vs Skender OHL3 batch: {count} bars, last {count - start} verified within {ValidationHelper.SkenderTolerance}: PASSED");
}
// ── B) Streaming == Batch span ────────────────────────────────────────────
// ── B) Skender OHL3 streaming validation ──────────────────────────────────
[Fact]
public void Validate_Against_Skender_OHL3_Streaming()
{
var skenderResults = _data.SkenderQuotes
.GetBaseQuote(CandlePart.OHL3)
.ToList();
var ind = new Typprice();
int count = _data.Bars.Count;
double[] streamValues = new double[count];
for (int i = 0; i < count; i++)
{
var result = ind.Update(_data.Bars[i], isNew: true);
streamValues[i] = result.Value;
}
// Verify last N bars
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
for (int i = start; i < count; i++)
{
double qlVal = streamValues[i];
double skVal = skenderResults[i].Value;
Assert.True(
Math.Abs(qlVal - skVal) <= ValidationHelper.SkenderTolerance,
$"Mismatch at index {i}: QuanTAlib={qlVal:G17}, Skender={skVal:G17}");
}
_output.WriteLine($"TYPPRICE streaming vs Skender OHL3: {count} bars, last {count - start} verified: PASSED");
}
// ── C) Skender OHL3 span validation ───────────────────────────────────────
[Fact]
[SkipLocalsInit]
public void Validate_Against_Skender_OHL3_Span()
{
var skenderResults = _data.SkenderQuotes
.GetBaseQuote(CandlePart.OHL3)
.ToList();
int count = _data.Bars.Count;
double[] o = new double[count], h = new double[count], l = new double[count];
for (int i = 0; i < count; i++)
{
o[i] = _data.Bars[i].Open;
h[i] = _data.Bars[i].High;
l[i] = _data.Bars[i].Low;
}
var qlOut = new double[count];
Typprice.Batch(o.AsSpan(), h.AsSpan(), l.AsSpan(), qlOut.AsSpan());
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
for (int i = start; i < count; i++)
{
double qlVal = qlOut[i];
double skVal = skenderResults[i].Value;
Assert.True(
Math.Abs(qlVal - skVal) <= ValidationHelper.SkenderTolerance,
$"Span mismatch at index {i}: QuanTAlib={qlVal:G17}, Skender={skVal:G17}");
}
_output.WriteLine($"TYPPRICE span vs Skender OHL3: {count} bars, last {count - start} verified: PASSED");
}
// ── D) Formula verification: (O+H+L)/3 ───────────────────────────────────
[Fact]
public void Validate_Formula_Manual()
{
var bar = new TBar(DateTime.UtcNow, open: 10.0, high: 18.0, low: 6.0, close: 15.0, volume: 1000);
var ind = new Typprice();
var result = ind.Update(bar, isNew: true);
double expected = (10.0 + 18.0 + 6.0) / 3.0; // = 11.333...
Assert.Equal(expected, result.Value, 1e-12);
_output.WriteLine($"TYPPRICE formula: expected={expected}, actual={result.Value}: PASSED");
}
// ── E) Streaming == Batch span ────────────────────────────────────────────
[Fact]
[SkipLocalsInit]
public void Validate_Streaming_Equals_Batch()
@@ -84,28 +161,37 @@ public sealed class TyppriceValidationTests : IDisposable
double streamVal = ind.Last.Value;
// Batch span
double[] h = new double[N], l = new double[N], c = new double[N];
for (int i = 0; i < N; i++) { h[i] = bars[i].High; l[i] = bars[i].Low; c[i] = bars[i].Close; }
double[] o = new double[N], h = new double[N], l = new double[N];
for (int i = 0; i < N; i++) { o[i] = bars[i].Open; h[i] = bars[i].High; l[i] = bars[i].Low; }
var qlOut = new double[N];
Typprice.Batch(h.AsSpan(), l.AsSpan(), c.AsSpan(), qlOut.AsSpan());
Typprice.Batch(o.AsSpan(), h.AsSpan(), l.AsSpan(), qlOut.AsSpan());
_output.WriteLine($"Streaming={streamVal:F10}, Batch={qlOut[N - 1]:F10}");
Assert.Equal(streamVal, qlOut[N - 1], 1e-12);
}
// ── C) Formula verification: (H+L+C)/3 ───────────────────────────────────
// ── F) Matches TBar.OHL3 property ─────────────────────────────────────────
[Fact]
public void Validate_Formula_Manual()
public void Validate_MatchesTBarOHL3()
{
var bar = new TBar(DateTime.UtcNow, open: 10.0, high: 18.0, low: 6.0, close: 15.0, volume: 1000);
const int N = 100;
var gbm = new GBM(100.0, 0.05, 0.2, seed: 2001);
var ind = new Typprice();
var result = ind.Update(bar, isNew: true);
double expected = (18.0 + 6.0 + 15.0) / 3.0; // = 13.0
Assert.Equal(expected, result.Value, 1e-12);
_output.WriteLine($"TYPPRICE formula: expected={expected}, actual={result.Value}: PASSED");
int mismatches = 0;
for (int i = 0; i < N; i++)
{
var bar = gbm.Next(isNew: true);
var result = ind.Update(bar, isNew: true);
double err = Math.Abs(result.Value - bar.OHL3);
if (err > 1e-12) { mismatches++; }
}
_output.WriteLine($"TBar.OHL3 comparison: {N} bars, {mismatches} mismatches");
Assert.Equal(0, mismatches);
}
// ── D) Batch(TBarSeries) == Calculate ─────────────────────────────────────
// ── G) Batch(TBarSeries) == Calculate ─────────────────────────────────────
[Fact]
public void Validate_BatchBarSeries_Equals_Calculate()
{
@@ -119,7 +205,7 @@ public sealed class TyppriceValidationTests : IDisposable
_output.WriteLine("TYPPRICE Batch(TBarSeries) == Calculate: PASSED");
}
// ── E) Determinism ────────────────────────────────────────────────────────
// ── H) Determinism ────────────────────────────────────────────────────────
[Fact]
public void Validate_Deterministic()
{
@@ -128,4 +214,29 @@ public sealed class TyppriceValidationTests : IDisposable
for (int i = 0; i < r1.Count; i++) { Assert.Equal(r1.Values[i], r2.Values[i], 15); }
_output.WriteLine("TYPPRICE determinism: PASSED");
}
// ── I) Skender OC2 structural validation (bonus) ──────────────────────────
[Fact]
public void Validate_Skender_OC2_MatchesTBarOC2()
{
// Verify Skender CandlePart.OC2 = (Open+Close)/2 matches TBar.OC2
var skenderResults = _data.SkenderQuotes
.GetBaseQuote(CandlePart.OC2)
.ToList();
int count = _data.Bars.Count;
int start = Math.Max(0, count - ValidationHelper.DefaultVerificationCount);
for (int i = start; i < count; i++)
{
double skVal = skenderResults[i].Value;
double tbarVal = _data.Bars[i].OC2;
Assert.True(
Math.Abs(skVal - tbarVal) <= ValidationHelper.SkenderTolerance,
$"OC2 mismatch at {i}: Skender={skVal:G17}, TBar={tbarVal:G17}");
}
_output.WriteLine($"Skender OC2 vs TBar.OC2: {count} bars, last {count - start} verified: PASSED");
}
}
+21 -22
View File
@@ -5,21 +5,20 @@ namespace QuanTAlib;
/// <summary>
/// TYPPRICE: Typical Price
/// Calculates the average of High, Low, and Close prices.
/// Equivalent to TBar.HLC3 but as a proper streaming indicator with bar correction.
/// Calculates the average of Open, High, and Low prices.
/// Equivalent to TBar.OHL3 but as a proper streaming indicator with bar correction.
/// </summary>
/// <remarks>
/// <b>Calculation:</b>
/// <list type="number">
/// <item>TypPrice = (High + Low + Close) / 3</item>
/// <item>TypPrice = (Open + High + Low) / 3</item>
/// </list>
///
/// <b>Key characteristics:</b>
/// <list type="bullet">
/// <item>Stateless bar-by-bar calculation (no lookback period)</item>
/// <item>TA-Lib compatible (TYPPRICE function)</item>
/// <item>Always hot after first bar</item>
/// <item>Widely used as the default price input for many indicators (e.g., CCI)</item>
/// <item>Uses Open, High, and Low to represent typical price action</item>
/// </list>
/// </remarks>
[SkipLocalsInit]
@@ -29,9 +28,9 @@ public sealed class Typprice : AbstractBase
[StructLayout(LayoutKind.Auto)]
private record struct State(
double LastValidOpen,
double LastValidHigh,
double LastValidLow,
double LastValidClose,
double LastResult,
int Count
);
@@ -66,17 +65,17 @@ public sealed class Typprice : AbstractBase
public override bool IsHot => _s.Count >= WarmupPeriod;
/// <summary>
/// Computes the typical price from HLC values.
/// Computes the typical price from OHL values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double ComputeTypicalPrice(double high, double low, double close)
private static double ComputeTypicalPrice(double open, double high, double low)
{
return Math.FusedMultiplyAdd(high, OneThird, (low + close) * OneThird);
return Math.FusedMultiplyAdd(open, OneThird, (high + low) * OneThird);
}
/// <summary>
/// Updates the indicator with a TValue input.
/// For TValue input, treats the value as H, L, and C (result = value).
/// For TValue input, treats the value as O, H, and L (result = value).
/// Prefer Update(TBar) for standard OHLC data.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -94,7 +93,7 @@ public sealed class Typprice : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue Update(TBar bar, bool isNew = true)
{
return UpdateCore(bar.Time, bar.High, bar.Low, bar.Close, isNew);
return UpdateCore(bar.Time, bar.Open, bar.High, bar.Low, isNew);
}
/// <summary>
@@ -118,7 +117,7 @@ public sealed class Typprice : AbstractBase
var tSpan = CollectionsMarshal.AsSpan(t);
var vSpan = CollectionsMarshal.AsSpan(v);
Batch(source.HighValues, source.LowValues, source.CloseValues, vSpan);
Batch(source.OpenValues, source.HighValues, source.LowValues, vSpan);
for (int i = 0; i < len; i++)
{
@@ -164,7 +163,7 @@ public sealed class Typprice : AbstractBase
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private TValue UpdateCore(long timeTicks, double high, double low, double close, bool isNew)
private TValue UpdateCore(long timeTicks, double open, double high, double low, bool isNew)
{
if (isNew)
{
@@ -178,11 +177,11 @@ public sealed class Typprice : AbstractBase
var s = _s;
// Handle non-finite values — use last valid values
if (!double.IsFinite(open)) { open = s.LastValidOpen; } else { s.LastValidOpen = open; }
if (!double.IsFinite(high)) { high = s.LastValidHigh; } else { s.LastValidHigh = high; }
if (!double.IsFinite(low)) { low = s.LastValidLow; } else { s.LastValidLow = low; }
if (!double.IsFinite(close)) { close = s.LastValidClose; } else { s.LastValidClose = close; }
double result = ComputeTypicalPrice(high, low, close);
double result = ComputeTypicalPrice(open, high, low);
if (!double.IsFinite(result))
{
@@ -229,18 +228,18 @@ public sealed class Typprice : AbstractBase
}
/// <summary>
/// Batch calculation using spans for HLC data.
/// Batch calculation using spans for OHL data.
/// </summary>
public static void Batch(
ReadOnlySpan<double> open,
ReadOnlySpan<double> high,
ReadOnlySpan<double> low,
ReadOnlySpan<double> close,
Span<double> output)
{
int len = high.Length;
if (low.Length != len || close.Length != len)
int len = open.Length;
if (high.Length != len || low.Length != len)
{
throw new ArgumentException("All input spans must have the same length", nameof(low));
throw new ArgumentException("All input spans must have the same length", nameof(high));
}
if (output.Length < len)
{
@@ -249,7 +248,7 @@ public sealed class Typprice : AbstractBase
for (int i = 0; i < len; i++)
{
output[i] = ComputeTypicalPrice(high[i], low[i], close[i]);
output[i] = ComputeTypicalPrice(open[i], high[i], low[i]);
}
}
@@ -269,7 +268,7 @@ public sealed class Typprice : AbstractBase
return;
}
Batch(source.HighValues, source.LowValues, source.CloseValues, output);
Batch(source.OpenValues, source.HighValues, source.LowValues, output);
}
public static (TSeries Results, Typprice Indicator) Calculate(TBarSeries source)
+21 -25
View File
@@ -11,31 +11,29 @@
### TL;DR
- TYPPRICE computes the equal-weighted average of High, Low, and Close: $(H + L + C) \times \frac{1}{3}$.
- TYPPRICE computes the equal-weighted average of Open, High, and Low: $(O + H + L) \times \frac{1}{3}$.
- No configurable parameters; computation is stateless per bar.
- Output range: Varies (see docs).
- Requires `1` bars of warmup before first valid output (IsHot = true).
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
- Equivalent to `TBar.OHL3` computed property.
TYPPRICE computes the equal-weighted average of High, Low, and Close: $(H + L + C) \times \frac{1}{3}$. This three-component mean is the most widely used "representative price" in technical analysis, serving as the default input for CCI, MFI, and many other indicators. By including Close but excluding Open, Typical Price captures both the range extremes and the settlement point, giving slightly more weight to closing action than AVGPRICE does. The calculation is stateless and costs a single FMA instruction per bar.
TYPPRICE computes the equal-weighted average of Open, High, and Low: $(O + H + L) \times \frac{1}{3}$. This three-component mean captures the opening price and the full intra-bar range without including the settlement (Close). By excluding Close, Typical Price isolates the session's initial positioning and range extremes, making it useful as an input where you want a price representative that is independent of closing action. The calculation is stateless and costs a single FMA instruction per bar.
## Historical Context
Typical Price became the standard price transform through its adoption by Donald Lambert in his 1980 Commodity Channel Index (CCI), which explicitly requires $(H+L+C)/3$ as its input. Gene Quong and Avrum Soudack used it in the Money Flow Index (MFI) in 1989. The TA-Lib function `TA_TYPPRICE` codified it as a standalone operation. TradingView exposes it as the `hlc3` built-in source selector.
The OHL3 variant of Typical Price represents the average of the bar's opening level and its range extremes. Unlike the more common HLC3 formulation (which TA-Lib implements as `TA_TYPPRICE`), OHL3 excludes the closing price entirely. This makes it suitable for analysis where the settlement price should not influence the representative price, for example when studying intra-session price discovery or when the closing price is already used as a separate signal component.
The choice of three components rather than four is not arbitrary. Excluding Open removes the overnight gap component, which reflects news-driven repositioning rather than intra-session supply and demand. For intraday analysis, this makes Typical Price a purer measure of within-session fair value than AVGPRICE. For daily bars on instruments with significant gaps (equities, futures at session boundaries), the distinction matters; for 24-hour markets (forex, crypto), it is negligible.
In QuanTAlib, `TBar.HLC3` provides the same value as a zero-cost computed property. The `Typprice` indicator class wraps this in the streaming `ITValuePublisher` interface with bar correction, NaN safety, and event chaining.
In QuanTAlib, `TBar.OHL3` provides the same value as a zero-cost computed property. The `Typprice` indicator class wraps this in the streaming `ITValuePublisher` interface with bar correction, NaN safety, and event chaining.
## Architecture & Physics
### 1. Core Formula
$$\text{TypPrice}_t = (H_t + L_t + C_t) \times \tfrac{1}{3}$$
$$\text{TypPrice}_t = (O_t + H_t + L_t) \times \tfrac{1}{3}$$
Implemented as FMA with a precomputed reciprocal constant:
$$\text{TypPrice}_t = \text{FMA}\!\left(H_t,\; \tfrac{1}{3},\; (L_t + C_t) \times \tfrac{1}{3}\right)$$
$$\text{TypPrice}_t = \text{FMA}\!\left(O_t,\; \tfrac{1}{3},\; (H_t + L_t) \times \tfrac{1}{3}\right)$$
The constant $\frac{1}{3}$ is stored as `private const double OneThird = 1.0 / 3.0`, evaluated at compile time. No runtime division occurs.
@@ -43,7 +41,7 @@ The constant $\frac{1}{3}$ is stored as `private const double OneThird = 1.0 / 3
Stateless per bar. State exists only for:
- **Last-valid substitution**: Non-finite H, L, or C values are replaced with the last known finite value for that component.
- **Last-valid substitution**: Non-finite O, H, or L values are replaced with the last known finite value for that component.
- **Bar correction**: `isNew=false` rolls back to previous state for same-timestamp rewrites.
### 3. Complexity
@@ -64,18 +62,18 @@ Division by a non-power-of-two constant is 4-5x more expensive than multiplicati
### Pseudo-code
```
```text
function TYPPRICE(bar):
const OneThird ← 1.0 / 3.0 // compile-time constant
h, l, c ← bar.High, bar.Low, bar.Close
o, h, l ← bar.Open, bar.High, bar.Low
// Substitute last-valid for non-finite inputs
if !finite(o): o ← lastValidOpen
if !finite(h): h ← lastValidHigh
if !finite(l): l ← lastValidLow
if !finite(c): c ← lastValidClose
result ← FMA(h, OneThird, (l + c) × OneThird)
result ← FMA(o, OneThird, (h + l) × OneThird)
return result
```
@@ -83,10 +81,10 @@ function TYPPRICE(bar):
| Context | Meaning |
|---------|---------|
| Close > TYPPRICE | Close above session's HLC center (bullish settlement) |
| Close < TYPPRICE | Close below session's HLC center (bearish settlement) |
| TYPPRICE trending up | Both range and settlement are rising |
| TYPPRICE as CCI input | Standard; CCI = (Price - SMA(Price)) / (0.015 × MeanDeviation) |
| Close > TYPPRICE | Close above session's OHL center (bullish settlement relative to range) |
| Close < TYPPRICE | Close below session's OHL center (bearish settlement relative to range) |
| TYPPRICE trending up | Opening levels and range are rising |
| TYPPRICE as input | Useful where Close independence is desired |
## Performance Profile
@@ -94,9 +92,9 @@ function TYPPRICE(bar):
| Operation | Count | Cost (cycles) | Subtotal |
|-----------|:-----:|:-------------:|:--------:|
| ADD (L+C) | 1 | 1 | 1 |
| MUL ((L+C) × OneThird) | 1 | 3 | 3 |
| FMA (H × OneThird + prev) | 1 | 4 | 4 |
| ADD (H+L) | 1 | 1 | 1 |
| MUL ((H+L) × OneThird) | 1 | 3 | 3 |
| FMA (O × OneThird + prev) | 1 | 4 | 4 |
| **Total (hot)** | **3** | | **~8 cycles** |
### Batch Mode (SIMD Analysis)
@@ -104,12 +102,10 @@ function TYPPRICE(bar):
| Aspect | Assessment |
|--------|------------|
| SIMD vectorizable | Yes: element-wise arithmetic, no inter-bar dependency |
| Optimal strategy | `Vector<double>` over H/L/C spans with broadcast OneThird |
| Optimal strategy | `Vector<double>` over O/H/L spans with broadcast OneThird |
| Memory | $O(1)$ streaming; $O(n)$ batch output span |
| Throughput | Near memory-bandwidth bound for large series |
## Resources
- **Lambert, D.R.** "Commodity Channel Index: Tools for Trading Cyclical Trends." *Technical Analysis of Stocks & Commodities*, 1980.
- **Quong, G. & Soudack, A.** "Volume-Weighted RSI: Money Flow." *Technical Analysis of Stocks & Commodities*, 1989.
- **TA-Lib** `TA_TYPPRICE` function reference.
- **QuanTAlib** `TBar.OHL3` computed property reference.