From e0715fb3e3e1d0370dd3e17d9855f817959d745c Mon Sep 17 00:00:00 2001 From: Alek Kim Date: Wed, 22 Jul 2026 17:18:22 +0900 Subject: [PATCH] feat: add math operator wrappers and align TA-Lib binding types Add Go wrappers for additional TA-Lib math operators: Div, Max, MaxIndex, Min, MinIndex, MinMax, MinMaxIndex, Mult, Sub, and Sum. Refactor the TA-Lib function signatures to use int32 consistently, including taResult and Hilbert Transform bindings, so the Go layer matches the native library more closely. Fix HT_TRENDMODE to return []int32 and update related comments and naming for better API consistency. --- README.md | 18 ++- cycle_indicators.go | 49 ++++---- functions.go | 164 +++++++++++++++++++----- loader.go | 10 ++ math_operators.go | 300 ++++++++++++++++++++++++++++++++++++++++++-- ta_result.go | 2 +- 6 files changed, 477 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 7d38a9c..852861d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,17 @@ This project currently exposes a small subset of TA-Lib functions: - `HT_PHASOR` - `HT_SINE` - `HT_TRENDMODE` -- `ADD` +- `Add` +- `Div` +- `Max` +- `MaxIndex` +- `Min` +- `MinIndex` +- `MinMax` +- `MinMaxIndex` +- `Mult` +- `Sub` +- `Sum` ## Requirements @@ -83,14 +93,18 @@ func main() { period := talib.HT_DCPERIOD(series) fmt.Println(period) - sum := talib.ADD(series, series) + sum := talib.Add(series, series) fmt.Println(sum) + + rollingMax := talib.Max(series, 14) + fmt.Println(rollingMax) } ``` ## API notes - Indicator functions return `nil` when the underlying TA-Lib call fails. +- Native TA-Lib functions are exposed with Go-style exported names such as `Add`, `Div`, `Max`, and `Sum`. ## Project status diff --git a/cycle_indicators.go b/cycle_indicators.go index c43aecb..5fcf807 100644 --- a/cycle_indicators.go +++ b/cycle_indicators.go @@ -9,10 +9,10 @@ import ( // Output is the estimated dominant cycle length in bars (clamped to 6-50). func HT_DCPERIOD(inReal []float64) []float64 { var ( - startIdx = 0 - endIdx = len(inReal) - 1 - outBegIdx int - outNBElement int + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 outReal = make([]float64, len(inReal)) ) @@ -29,10 +29,10 @@ func HT_DCPERIOD(inReal []float64) []float64 { // One real output per bar. Output is degrees, wrapped so it never exceeds 315 (can go negative). func HT_DCPHASE(inReal []float64) []float64 { var ( - startIdx = 0 - endIdx = len(inReal) - 1 - outBegIdx int - outNBElement int + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 outReal = make([]float64, len(inReal)) ) @@ -62,10 +62,10 @@ func HT_DCPHASE(inReal []float64) []float64 { // @return outQuadrature Quadrature component (Q1 of the Hilbert Transform) func HT_PHASOR(inReal []float64) ([]float64, []float64) { var ( - startIdx = 0 - endIdx = len(inReal) - 1 - outBegIdx int - outNBElement int + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 outInPhase = make([]float64, len(inReal)) outQuadrature = make([]float64, len(inReal)) ) @@ -94,10 +94,10 @@ func HT_PHASOR(inReal []float64) ([]float64, []float64) { // @return outLeadSine Sine of the phase advanced 45 degrees (lead) func HT_SINE(inReal []float64) ([]float64, []float64) { var ( - startIdx = 0 - endIdx = len(inReal) - 1 - outBegIdx int - outNBElement int + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 outSine = make([]float64, len(inReal)) outLeadSine = make([]float64, len(inReal)) ) @@ -118,16 +118,17 @@ func HT_SINE(inReal []float64) ([]float64, []float64) { return outSine, outLeadSine } -// HT_TRENDMODE - Hilbert Transform classifier that labels each bar as trending (1) or cycling (0). Reuses the MAMA dominant-cycle/phase DSP plus a SineWave/trendline test to decide the market mode. 1 = trending market (favor trend-following); 0 = cycle/mean-reverting mode. +// HT_TRENDMODE - Hilbert Transform classifier that labels each bar as trending (1) or cycling (0). +// Reuses the MAMA dominant-cycle/phase DSP plus a SineWave/trendline test to decide the market mode. // @param inReal Input price series -// @return outInteger 1 = trending market; 0 = cycle/mean-reverting market; 4294967297 (Fermat number) = error (e.g. insufficient data) -func HT_TRENDMODE(inReal []float64) []int { +// @return outInteger 1 = trending market; 0 = cycle/mean-reverting market; +func HT_TRENDMODE(inReal []float64) []int32 { var ( - startIdx = 0 - endIdx = len(inReal) - 1 - outBegIdx int - outNBElement int - outInteger = make([]int, len(inReal)) + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outInteger = make([]int32, len(inReal)) ) if retCode := ht_trendmode( diff --git a/functions.go b/functions.go index 70b67a4..0155e1f 100644 --- a/functions.go +++ b/functions.go @@ -3,62 +3,164 @@ package talib // Cycle Indicators (HT) - Hilbert Transform var ( ht_dcperiod func( - startIdx int, - endIdx int, + startIdx int32, + endIdx int32, inReal []float64, - outBegIdx *int, - outNBElement *int, + outBegIdx *int32, + outNBElement *int32, outReal []float64, - ) int + ) int32 ht_dcphase func( - startIdx int, - endIdx int, + startIdx int32, + endIdx int32, inReal []float64, - outBegIdx *int, - outNBElement *int, + outBegIdx *int32, + outNBElement *int32, outReal []float64, - ) int + ) int32 ht_phasor func( - startIdx int, - endIdx int, + startIdx int32, + endIdx int32, inReal []float64, - outBegIdx *int, - outNBElement *int, + outBegIdx *int32, + outNBElement *int32, outInPhase []float64, outQuadrature []float64, - ) int + ) int32 ht_sine func( - startIdx int, - endIdx int, + startIdx int32, + endIdx int32, inReal []float64, - outBegIdx *int, - outNBElement *int, + outBegIdx *int32, + outNBElement *int32, outSine []float64, outLeadSine []float64, - ) int + ) int32 ht_trendmode func( - startIdx int, - endIdx int, + startIdx int32, + endIdx int32, inReal []float64, - outBegIdx *int, - outNBElement *int, - outInteger []int, - ) int + outBegIdx *int32, + outNBElement *int32, + outInteger []int32, + ) int32 ) // Math Operators var ( add func( - startIdx int, - endIdx int, + startIdx int32, + endIdx int32, inReal0 []float64, inReal1 []float64, - outBegIdx *int, - outNBElement *int, + outBegIdx *int32, + outNBElement *int32, outReal []float64, - ) int + ) int32 + + div func( + startIdx int32, + endIdx int32, + inReal0 []float64, + inReal1 []float64, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + max func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + maxIndex func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outInteger []int32, + ) int32 + + min func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + minIndex func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outInteger []int32, + ) int32 + + minMax func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outMin []float64, + outMax []float64, + ) int32 + + minMaxIndex func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outMinIndex []int32, + outMaxIndex []int32, + ) int32 + + mult func( + startIdx int32, + endIdx int32, + inReal0 []float64, + inReal1 []float64, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + sub func( + startIdx int32, + endIdx int32, + inReal0 []float64, + inReal1 []float64, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + sum func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 ) diff --git a/loader.go b/loader.go index ca3c58f..cd6258b 100644 --- a/loader.go +++ b/loader.go @@ -33,6 +33,16 @@ func Load() (uintptr, error) { purego.RegisterLibFunc(&ht_sine, ptr, "TA_HT_SINE") purego.RegisterLibFunc(&ht_trendmode, ptr, "TA_HT_TRENDMODE") purego.RegisterLibFunc(&add, ptr, "TA_ADD") + purego.RegisterLibFunc(&div, ptr, "TA_DIV") + purego.RegisterLibFunc(&max, ptr, "TA_MAX") + purego.RegisterLibFunc(&maxIndex, ptr, "TA_MAXINDEX") + purego.RegisterLibFunc(&min, ptr, "TA_MIN") + purego.RegisterLibFunc(&minIndex, ptr, "TA_MININDEX") + purego.RegisterLibFunc(&minMax, ptr, "TA_MINMAX") + purego.RegisterLibFunc(&minMaxIndex, ptr, "TA_MINMAXINDEX") + purego.RegisterLibFunc(&mult, ptr, "TA_MULT") + purego.RegisterLibFunc(&sub, ptr, "TA_SUB") + purego.RegisterLibFunc(&sum, ptr, "TA_SUM") return ptr, nil } diff --git a/math_operators.go b/math_operators.go index 4ca6f35..d8e12ae 100644 --- a/math_operators.go +++ b/math_operators.go @@ -2,15 +2,15 @@ package talib import "log/slog" -// ADD - Vector arithmetic addition. Outputs the element-wise sum of two input series. +// Add - Vector arithmetic addition. Outputs the element-wise sum of two input series. // // outReal[i] = inReal0[i] + inReal1[i] -func ADD(inReal0, inReal1 []float64) []float64 { +func Add(inReal0, inReal1 []float64) []float64 { var ( - startIdx = 0 - endIdx = len(inReal0) - 1 - outBegIdx int - outNBElement int + startIdx int32 + endIdx = int32(len(inReal0) - 1) + outBegIdx int32 + outNBElement int32 outReal = make([]float64, len(inReal0)) ) @@ -23,9 +23,293 @@ func ADD(inReal0, inReal1 []float64) []float64 { &outNBElement, outReal, ); SUCCESS != taResult(retCode) { - slog.Debug("ADD", "result", retCode) + slog.Debug("Add", "result", retCode) return nil } - return outReal[:outNBElement] + return outReal +} + +// Div - Element-wise division of two input series. Computes the quotient of corresponding values from two real inputs. +// +// outReal[i] = inReal0[i] / inReal1[i] +func Div(inReal0, inReal1 []float64) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal0) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal0)) + ) + + if retCode := div( + startIdx, + endIdx, + inReal0, + inReal1, + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("Div", "result", retCode) + return nil + } + + return outReal +} + +// Max - Highest input value over a rolling window of the last optInTimePeriod bars. A moving-window maximum. +func Max(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := max( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("Max", "result", retCode) + return nil + } + + return outReal +} + +// MaxIndex - Returns the index of the highest input value within a rolling window of optInTimePeriod bars. +// Same as Max but outputs the location instead of the value. +// +// outInteger[i] = argmax_{j in [i-optInTimePeriod+1, i]} inReal[j] +func MaxIndex(inReal []float64, optInTimePeriod int) []int32 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outInteger = make([]int32, len(inReal)) + ) + + if retCode := maxIndex( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outInteger, + ); SUCCESS != taResult(retCode) { + slog.Debug("MaxIndex", "result", retCode) + return nil + } + + return outInteger +} + +// Min - Rolling minimum: the lowest input value over the trailing period. +// +// outReal[i] = min(inReal[i-optInTimePeriod+1 .. i]) +func Min(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := min( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("Min", "result", retCode) + return nil + } + + return outReal +} + +// MinIndex - Returns the absolute index of the lowest value within a rolling window of the given period. +// Same scan as Min but outputs the position of the minimum rather than its value. +// +// outInteger[t] = argmin_{t-period+1 <= i <= t} inReal[i] (absolute index into inReal) +func MinIndex(inReal []float64, optInTimePeriod int) []int32 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outInteger = make([]int32, len(inReal)) + ) + + if retCode := minIndex( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outInteger, + ); SUCCESS != taResult(retCode) { + slog.Debug("MinIndex", "result", retCode) + return nil + } + + return outInteger +} + +// MinMax - Returns both the lowest and highest values of the input over a rolling window of the last optInTimePeriod bars. +// An overlap-study companion to Min and Max that computes both extrema in one pass. +// @param inReal The input data series. +// @param optInTimePeriod The number of bars (or periods) to look back for the lowest and highest values. +// @return Two slices: the first contains the rolling minimum values, and the second contains the rolling maximum values. +func MinMax(inReal []float64, optInTimePeriod int) ([]float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outMin = make([]float64, len(inReal)) + outMax = make([]float64, len(inReal)) + ) + + if retCode := minMax( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outMin, + outMax, + ); SUCCESS != taResult(retCode) { + slog.Debug("MinMax", "result", retCode) + return nil, nil + } + + return outMin, outMax +} + +// MinMaxIndex - Returns the absolute input indices of the lowest and highest values within each rolling window of optInTimePeriod bars. Index variant of MinMax. +// +// For each t: outMaxIdx[t] = argmax_{i in [t-N+1, t]} inReal[i]; outMinIdx[t] = argmin over the same window (N = optInTimePeriod). +func MinMaxIndex(inReal []float64, optInTimePeriod int) ([]int32, []int32) { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outMinIndex = make([]int32, len(inReal)) + outMaxIndex = make([]int32, len(inReal)) + ) + + if retCode := minMaxIndex( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outMinIndex, + outMaxIndex, + ); SUCCESS != taResult(retCode) { + slog.Debug("MinMaxIndex", "result", retCode) + return nil, nil + } + + return outMinIndex, outMaxIndex +} + +// Mult - Element-wise multiplication of two input series. +// Produces outReal[i] = inReal0[i] * inReal1[i]. +func Mult(inReal0, inReal1 []float64) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal0) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal0)) + ) + + if retCode := mult( + startIdx, + endIdx, + inReal0, + inReal1, + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("Mult", "result", retCode) + return nil + } + + return outReal +} + +// Sub - Element-wise vector subtraction of two input series. Outputs inReal0 minus inReal1 at each index. +// +// outReal[i] = inReal0[i] - inReal1[i] +func Sub(inReal0, inReal1 []float64) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal0) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal0)) + ) + + if retCode := sub( + startIdx, + endIdx, + inReal0, + inReal1, + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("Sub", "result", retCode) + return nil + } + + return outReal +} + +// Sum - Rolling sum of the input over a fixed period. +// Each output is the sum of the most recent optInTimePeriod input values. +func Sum(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := sum( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("Sum", "result", retCode) + return nil + } + + return outReal } diff --git a/ta_result.go b/ta_result.go index 9193d36..3274708 100644 --- a/ta_result.go +++ b/ta_result.go @@ -1,6 +1,6 @@ package talib -type taResult int +type taResult int32 const ( SUCCESS taResult = 0 // No error