feat: add 8 new indicators with full integration

New indicators:
- HWC (Holt-Winters Channel) — channels, 27 tests
- VWMACD (Volume-Weighted MACD) — momentum, 38 tests
- Squeeze Pro — oscillators, 69 tests
- BW_MFI (Bill Williams MFI) — oscillators
- DSTOCH (Double Stochastic) — oscillators
- ATRSTOP (ATR Trailing Stop) — reversals
- VSTOP (Volatility Stop) — reversals
- Convexity (Beta Convexity) — statistics, 23 tests

Integration:
- Python bridge: Exports.cs, _bridge.py, wrapper modules
- Documentation: _sidebar.md, _index.md pages, SPEC.md
- All analyzer warnings fixed (MA0074, xUnit2013, S2699)

Build: 0 warnings, 0 errors | Tests: 15,933 passed, 0 failed
This commit is contained in:
Miha Kralj
2026-03-17 08:35:29 -07:00
parent 6f0a339c9b
commit 15f4bb90f3
71 changed files with 10194 additions and 44 deletions
+79
View File
@@ -308,6 +308,20 @@ public static unsafe partial class Exports
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_atrstop")]
public static int QtlAtrstop(double* high, double* low, double* close, double* output, int n, int period, double multiplier)
{
if (high == null || low == null || close == null || output == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
if (period <= 0) return StatusCodes.QTL_ERR_INVALID_PARAM;
try
{
Atrstop.Batch(Src(high, n), Src(low, n), Src(close, n), Dst(output, n), period, multiplier);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_atr")]
public static int QtlAtr(double* sourceOpen, double* sourceHigh, double* sourceLow, double* sourceClose, double* sourceVolume, int period, int n, double* dst)
{
@@ -372,6 +386,44 @@ public static unsafe partial class Exports
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_bwmfi")]
public static int QtlBwMfi(double* high, double* low, double* volume, double* mfiOutput, double* zoneOutput, int n)
{
if (high == null || low == null || volume == null || mfiOutput == null || zoneOutput == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
try
{
var hi = Src(high, n);
var lo = Src(low, n);
var vol = Src(volume, n);
var mfiTmp = new double[n];
var zoneTmp = new int[n];
BwMfi.Batch(hi, lo, vol, mfiTmp.AsSpan(), zoneTmp.AsSpan());
mfiTmp.AsSpan().CopyTo(Dst(mfiOutput, n));
var zoneDst = Dst(zoneOutput, n);
for (int i = 0; i < n; i++)
{
zoneDst[i] = zoneTmp[i];
}
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_dstoch")]
public static int QtlDstoch(double* high, double* low, double* close, double* output, int n, int period)
{
if (high == null || low == null || close == null || output == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
if (period <= 0) return StatusCodes.QTL_ERR_INVALID_PARAM;
try
{
Dstoch.Batch(Src(high, n), Src(low, n), Src(close, n), Dst(output, n), period);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_cci")]
public static int QtlCci(double* sourceOpen, double* sourceHigh, double* sourceLow, double* sourceClose, double* sourceVolume, int period, int n, double* dst)
{
@@ -2848,6 +2900,19 @@ public static unsafe partial class Exports
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_squeeze_pro")]
public static int QtlSqueezePro(double* high, double* low, double* close, double* momOut, double* sqOut, int n, int period, double bbMult, double kcMultWide, double kcMultNormal, double kcMultNarrow, int momLength, int momSmooth, int useSma)
{
if (high == null || low == null || close == null || momOut == null || sqOut == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
try
{
SqueezePro.Batch(Src(high, n), Src(low, n), Src(close, n), Dst(momOut, n), Dst(sqOut, n), period, bbMult, kcMultWide, kcMultNormal, kcMultNarrow, momLength, momSmooth, useSma != 0);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_ssf2")]
public static int QtlSsf2(double* source, double* output, int n, int period)
{
@@ -3385,6 +3450,20 @@ public static unsafe partial class Exports
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_vstop")]
public static int QtlVstop(double* high, double* low, double* close, double* output, int n, int period, double multiplier)
{
if (high == null || low == null || close == null || output == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
if (period <= 0) return StatusCodes.QTL_ERR_INVALID_PARAM;
try
{
Vstop.Batch(Src(high, n), Src(low, n), Src(close, n), Dst(output, n), period, multiplier);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
[UnmanagedCallersOnly(EntryPoint = "qtl_vov")]
public static int QtlVov(double* source, double* output, int n, int volatilityPeriod, int vovPeriod)
{
+63
View File
@@ -242,6 +242,27 @@ public static unsafe partial class Exports
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
// Vwmacd: multi-output (close, volume → vwmacd, signal, histogram)
[UnmanagedCallersOnly(EntryPoint = "qtl_vwmacd")]
public static int QtlVwmacd(double* close, double* volume, int n,
double* dstVwmacd, double* dstSignal, double* dstHist,
int fastPeriod, int slowPeriod, int signalPeriod)
{
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
if (close == null || volume == null ||
dstVwmacd == null || dstSignal == null || dstHist == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (fastPeriod < 1 || slowPeriod < 1 || signalPeriod < 1) return StatusCodes.QTL_ERR_INVALID_PARAM;
try
{
Vwmacd.Batch(
Src(close, n), Src(volume, n),
Dst(dstVwmacd, n), Dst(dstSignal, n), Dst(dstHist, n),
fastPeriod, slowPeriod, signalPeriod);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
// ═══════════════════════════════════════════════════════════════════════
// §8.3 Oscillators
// ═══════════════════════════════════════════════════════════════════════
@@ -841,6 +862,26 @@ public static unsafe partial class Exports
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
// Hwc: multi-output (src → upper, middle, lower; int period, double multiplier)
[UnmanagedCallersOnly(EntryPoint = "qtl_hwc")]
public static int QtlHwc(double* src, int n,
double* dstUpper, double* dstMiddle, double* dstLower,
int period, double multiplier)
{
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
if (src == null || dstUpper == null || dstMiddle == null || dstLower == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (period < 1) return StatusCodes.QTL_ERR_INVALID_PARAM;
try
{
Hwc.Batch(
Src(src, n),
Dst(dstUpper, n), Dst(dstMiddle, n), Dst(dstLower, n),
period, multiplier);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
// ═══════════════════════════════════════════════════════════════════════
// §8.7 Volatility
// ═══════════════════════════════════════════════════════════════════════
@@ -1179,6 +1220,28 @@ public static unsafe partial class Exports
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
// Convexity: multi-output (asset, market → betaStd, betaUp, betaDown, ratio, convexity)
[UnmanagedCallersOnly(EntryPoint = "qtl_convexity")]
public static int QtlConvexity(double* asset, double* market, int n,
double* dstBetaStd, double* dstBetaUp, double* dstBetaDown,
double* dstRatio, double* dstConvexity, int period)
{
if (n <= 0) return StatusCodes.QTL_ERR_INVALID_LENGTH;
if (asset == null || market == null ||
dstBetaStd == null || dstBetaUp == null || dstBetaDown == null ||
dstRatio == null || dstConvexity == null) return StatusCodes.QTL_ERR_NULL_PTR;
if (period < 1) return StatusCodes.QTL_ERR_INVALID_PARAM;
try
{
Convexity.Batch(
Src(asset, n), Src(market, n),
Dst(dstBetaStd, n), Dst(dstBetaUp, n), Dst(dstBetaDown, n),
Dst(dstRatio, n), Dst(dstConvexity, n), period);
return StatusCodes.QTL_OK;
}
catch { return StatusCodes.QTL_ERR_INTERNAL; }
}
// ═══════════════════════════════════════════════════════════════════════
// §8.10 Error Metrics
// ═══════════════════════════════════════════════════════════════════════