From 4b07e7c74177ae7fda9b4a13689379cdfe4386d2 Mon Sep 17 00:00:00 2001 From: Alek Kim Date: Fri, 24 Jul 2026 13:37:34 +0900 Subject: [PATCH] feat: add momentum indicators and expand function coverage Add a full Momentum Indicators category (momentum_indicators.go) with 26 new wrapped functions: ADX, ADXR, APO, AROON, AROONOSC, BOP, CCI, CMO, DX, IMI, MACD, MACDEXT, MACDFIX, MFI, MOM, MINUS_DI, MINUS_DM, PLUS_DI, PLUS_DM, PPO, ROC, ROCP, ROCR, ROCR100, RSI, STOCH, STOCHF, STOCHRSI, TRIX, ULTOSC, WILLR - Register all new native symbols in Load() (loader.go) - Declare corresponding native function variables in functions.go - Add MAType enum (ta_ma_type.go) for MA-type parameters (SMA, EMA, WMA, DEMA, TEMA, TRIMA, KAMA, MAMA, T3) - Add taResult constants (ta_result.go) mirroring TA-Lib return codes - Each wrapper includes a doc comment with formula, interpretation, and known behavioral edge-case notes --- README.md | 9 + functions.go | 372 ++++++++++++++ loader.go | 34 +- momentum_indicators.go | 1100 ++++++++++++++++++++++++++++++++++++++++ ta_ma_type.go | 15 + 5 files changed, 1529 insertions(+), 1 deletion(-) create mode 100644 momentum_indicators.go create mode 100644 ta_ma_type.go diff --git a/README.md b/README.md index fd469a1..6deb962 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,13 @@ This project currently exposes a small subset of TA-Lib functions: - Exponential/Logarithmic: `Exp`, `Ln`, `Log10` - Rounding: `Ceil`, `Floor`, `Sqrt` +**Momentum Indicators** +- Directional Movement: `ADX`, `ADXR`, `DX`, `MINUS_DI`, `MINUS_DM`, `PLUS_DI`, `PLUS_DM` +- Oscillators: `APO`, `AROON`, `AROONOSC`, `BOP`, `CCI`, `CMO`, `IMI`, `MFI`, `MOM`, `PPO`, `RSI`, `TRIX`, `WILLR`, `ULTOSC` +- Rate of Change: `ROC`, `ROCP`, `ROCR`, `ROCR100` +- MACD family: `MACD`, `MACDEXT`, `MACDFIX` +- Stochastic family: `STOCH`, `STOCHF`, `STOCHRSI` + ## Requirements - Go `1.26` @@ -107,6 +114,8 @@ func main() { - 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`. +- Functions accepting a moving-average type use the `MAType` constant (`SMA`, `EMA`, `WMA`, `DEMA`, `TEMA`, `TRIMA`, `KAMA`, `MAMA`, `T3`). +- Functions returning two outputs (e.g. `AROON`, `MACD`, `STOCH`) return multiple slices; all are `nil` on failure. ## Project status diff --git a/functions.go b/functions.go index 1132227..0727b6b 100644 --- a/functions.go +++ b/functions.go @@ -298,4 +298,376 @@ var ( outNBElement *int32, outReal []float64, ) int32 + + adx func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + adxr func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + apo func( + startIdx int32, + endIdx int32, + inReal []float64, + optInFastPeriod int32, + optInSlowPeriod int32, + optInMAType int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + aroon func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outAroonDown []float64, + outAroonUp []float64, + ) int32 + + aroonosc func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + bop func( + startIdx int32, + endIdx int32, + inOpen []float64, + inHigh []float64, + inLow []float64, + inClose []float64, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + cci func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + cmo func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + dx func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + imi func( + startIdx int32, + endIdx int32, + inOpen []float64, + inClose []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + macd func( + startIdx int32, + endIdx int32, + inReal []float64, + optInFastPeriod int32, + optInSlowPeriod int32, + optInSignalPeriod int32, + outBegIdx *int32, + outNBElement *int32, + outMACD []float64, + outMACDSignal []float64, + outMACDHist []float64, + ) int32 + + macdext func( + startIdx int32, + endIdx int32, + inReal []float64, + optInFastPeriod int32, + optInFastMAType int32, + optInSlowPeriod int32, + optInSlowMAType int32, + optInSignalPeriod int32, + optInSignalMAType int32, + outBegIdx *int32, + outNBElement *int32, + outMACD []float64, + outMACDSignal []float64, + outMACDHist []float64, + ) int32 + + macdfix func( + startIdx int32, + endIdx int32, + inReal []float64, + optInSignalPeriod int32, + outBegIdx *int32, + outNBElement *int32, + outMACD []float64, + outMACDSignal []float64, + outMACDHist []float64, + ) int32 + + mfi func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + inVolume []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + mom func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + minus_di func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + minus_dm func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + plus_di func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + plus_dm func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + ppo func( + startIdx int32, + endIdx int32, + inReal []float64, + optInFastPeriod int32, + optInSlowPeriod int32, + optInMAType int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + roc func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + rocp func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + rocr func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + rocr100 func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + rsi func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + stoch func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInFastKPeriod int32, + optInSlowKPeriod int32, + optInSlowKMAType int32, + optInSlowDPeriod int32, + optInSlowDMAType int32, + outBegIdx *int32, + outNBElement *int32, + outSlowK []float64, + outSlowD []float64, + ) int32 + + stochf func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInFastKPeriod int32, + optInFastDPeriod int32, + optInFastDMAType int32, + outBegIdx *int32, + outNBElement *int32, + outFastK []float64, + outFastD []float64, + ) int32 + + stochrsi func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + optInFastKPeriod int32, + optInFastDPeriod int32, + optInFastDMAType int32, + outBegIdx *int32, + outNBElement *int32, + outFastK []float64, + outFastD []float64, + ) int32 + + trix func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + ultosc func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInTimePeriod1 int32, + optInTimePeriod2 int32, + optInTimePeriod3 int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + willr func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + inClose []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 ) diff --git a/loader.go b/loader.go index bb14b33..a672c2f 100644 --- a/loader.go +++ b/loader.go @@ -56,13 +56,45 @@ func Load() (uintptr, error) { purego.RegisterLibFunc(&tanh, ptr, "TA_TANH") purego.RegisterLibFunc(&ceil, ptr, "TA_CEIL") - purego.RegisterLibFunc(&exp, ptr, "TA_EXP") purego.RegisterLibFunc(&floor, ptr, "TA_FLOOR") purego.RegisterLibFunc(&sqrt, ptr, "TA_SQRT") + purego.RegisterLibFunc(&exp, ptr, "TA_EXP") purego.RegisterLibFunc(&ln, ptr, "TA_LN") purego.RegisterLibFunc(&log10, ptr, "TA_LOG10") + purego.RegisterLibFunc(&adx, ptr, "TA_ADX") + purego.RegisterLibFunc(&adxr, ptr, "TA_ADXR") + purego.RegisterLibFunc(&apo, ptr, "TA_APO") + purego.RegisterLibFunc(&aroon, ptr, "TA_AROON") + purego.RegisterLibFunc(&aroonosc, ptr, "TA_AROONOSC") + purego.RegisterLibFunc(&bop, ptr, "TA_BOP") + purego.RegisterLibFunc(&cci, ptr, "TA_CCI") + purego.RegisterLibFunc(&cmo, ptr, "TA_CMO") + purego.RegisterLibFunc(&dx, ptr, "TA_DX") + purego.RegisterLibFunc(&imi, ptr, "TA_IMI") + purego.RegisterLibFunc(&macd, ptr, "TA_MACD") + purego.RegisterLibFunc(&macdext, ptr, "TA_MACDEXT") + purego.RegisterLibFunc(&macdfix, ptr, "TA_MACDFIX") + purego.RegisterLibFunc(&mfi, ptr, "TA_MFI") + purego.RegisterLibFunc(&mom, ptr, "TA_MOM") + purego.RegisterLibFunc(&minus_di, ptr, "TA_MINUS_DI") + purego.RegisterLibFunc(&minus_dm, ptr, "TA_MINUS_DM") + purego.RegisterLibFunc(&plus_di, ptr, "TA_PLUS_DI") + purego.RegisterLibFunc(&plus_dm, ptr, "TA_PLUS_DM") + purego.RegisterLibFunc(&ppo, ptr, "TA_PPO") + purego.RegisterLibFunc(&roc, ptr, "TA_ROC") + purego.RegisterLibFunc(&rocp, ptr, "TA_ROCP") + purego.RegisterLibFunc(&rocr, ptr, "TA_ROCR") + purego.RegisterLibFunc(&rocr100, ptr, "TA_ROCR100") + purego.RegisterLibFunc(&rsi, ptr, "TA_RSI") + purego.RegisterLibFunc(&stoch, ptr, "TA_STOCH") + purego.RegisterLibFunc(&stochf, ptr, "TA_STOCHF") + purego.RegisterLibFunc(&stochrsi, ptr, "TA_STOCHRSI") + purego.RegisterLibFunc(&trix, ptr, "TA_TRIX") + purego.RegisterLibFunc(&ultosc, ptr, "TA_ULTOSC") + purego.RegisterLibFunc(&willr, ptr, "TA_WILLR") + return ptr, nil } diff --git a/momentum_indicators.go b/momentum_indicators.go new file mode 100644 index 0000000..a4ef67f --- /dev/null +++ b/momentum_indicators.go @@ -0,0 +1,1100 @@ +package talib + +import "log/slog" + +// ADX - Wilder's Average Directional Movement Index, a smoothed measure of trend strength derived from the directional indicators (+DI/-DI). +// Quantifies how strongly a market is trending, regardless of direction. +// Higher values indicate a stronger trend (a common convention treats >25 as trending); says nothing about direction. +// +// +DI = 100*(+DM_p/TR_p), -DI = 100*(-DM_p/TR_p); DX = 100*|(-DI)-(+DI)| / ((-DI)+(+DI)); +// first ADX = mean of the first period DX; then ADX = (prevADX*(period-1) + DX)/period. +// +DM_p/-DM_p/TR_p use Wilder smoothing: X = X - X/period + today's one-bar value. +// +// Note: Wilder's original integer rounding is not applied. +func ADX(inHigh, inLow, inClose []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := adx( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("ADX", "result", retCode) + return nil + } + + return outReal +} + +// ADXR - Smoothed variant of ADX: the average of the current ADX value and the ADX value from (period-1) bars earlier. +// Further damps ADX to gauge trend strength. Higher values mean a stronger trend; smoother and more lagging than ADX. +// +// ADXR[i] = (ADX[i] + ADX[i-(period-1)]) / 2 +// Note: Wilder's original integer rounding is not applied (unreliable when values are near 1). +func ADXR(inHigh, inLow, inClose []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := adxr( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("ADXR", "result", retCode) + return nil + } + + return outReal +} + +// APO - Absolute Price Oscillator: the difference between a fast and a slow moving average of the input, in price units. +// Measures short- vs long-term momentum. Positive when fast MA > slow MA (upward momentum); negative otherwise. +// +// APO = fastMA - slowMA +// fastMA = MA(inReal, optInFastPeriod, optInMAType) +// slowMA = MA(inReal, optInSlowPeriod, optInMAType) +// +// The standard form is exponential — APO with EMA and periods 12/26 is the fast-minus-slow EMA construction underlying the MACD (in price units). +// optInMAType therefore defaults to EMA — the moving average Gerald Appel used for the original MACD; +// pass another type (e.g. talib.MATypeSMA) to override. +func APO(inReal []float64, optInFastPeriod, optInSlowPeriod int, optInMAType MAType) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := apo( + startIdx, + endIdx, + inReal, + int32(optInFastPeriod), + int32(optInSlowPeriod), + int32(optInMAType), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("APO", "result", retCode) + return nil + } + + return outReal +} + +// AROON - Aroon reports how recently the highest high and lowest low occurred within a rolling window of length optInTimePeriod, as two 0-100 oscillators. +// Indicates trend strength and direction. Up near 100 = a very recent new high (strong uptrend); +// Down near 100 = a very recent new low. Up/Down crossovers signal trend shifts. +// +// Up = 100*(period-(today-highestIdx))/period +// Down = 100*(period-(today-lowestIdx))/period +// where highestIdx/lowestIdx index the highest high / lowest low over the window [today-period .. today]. +func AROON(inHigh, inLow []float64, optInTimePeriod int) ([]float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inHigh) - 1) + outBegIdx int32 + outNBElement int32 + outAroonUp = make([]float64, len(inHigh)) + outAroonDown = make([]float64, len(inLow)) + ) + + if retCode := aroon( + startIdx, + endIdx, + inHigh, + inLow, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outAroonUp, + outAroonDown, + ); SUCCESS != taResult(retCode) { + slog.Debug("AROON", "result", retCode) + return nil, nil + } + + return outAroonUp, outAroonDown +} + +// AROONOSC - Aroon Oscillator: AroonUp minus AroonDown over a lookback window. +// Measures trend direction and strength on a -100..+100 scale. +// Positive when the high is more recent than the low (up-trend); negative when the low is more recent (down-trend). +// +// factor = 100 / optInTimePeriod +// AroonUp = factor * (period - (today - highestIdx)) +// AroonDown = factor * (period - (today - lowestIdx)) +// AroonOsc = AroonUp - AroonDown = factor * (highestIdx - lowestIdx) +// highestIdx/lowestIdx = bar index of the highest high / lowest low in the last (period+1) bars. +func AROONOSC(inHigh, inLow []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inHigh) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inHigh)) + ) + + if retCode := aroonosc( + startIdx, + endIdx, + inHigh, + inLow, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("AROONOSC", "result", retCode) + return nil + } + + return outReal +} + +// BOP - Balance Of Power compares where the close sits relative to the open, normalized by the bar's high-low range. +// A per-bar oscillator with no smoothing. Positive: close above open (buyers dominated); negative: sellers dominated. +// +// BOP = (Close - Open) / (High - Low) +func BOP(inOpen, inHigh, inLow, inClose []float64) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := bop( + startIdx, + endIdx, + inOpen, + inHigh, + inLow, + inClose, + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("BOP", "result", retCode) + return nil + } + + return outReal +} + +// CCI - Commodity Channel Index: measures the current typical price relative to its simple moving average, scaled by mean absolute deviation. +// Momentum oscillator flagging overbought/oversold extremes. CCI > +100 overbought; CCI < -100 oversold. +// +// TP_i = (High_i + Low_i + Close_i)/3 +// SMA = (1/N) * sum(TP over N bars) +// meanDev = (1/N) * sum(|TP - SMA| over N bars) +// CCI = (TP_last - SMA) / (0.015 * meanDev) +func CCI(inHigh, inLow, inClose []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := cci( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("CCI", "result", retCode) + return nil + } + + return outReal +} + +// CMO - Chande Momentum Oscillator: bounded momentum measure from Wilder-smoothed average up-moves and down-moves. +// Identical to RSI except the numerator uses (gain-loss) instead of gain. +// Bounded in [-100,+100]; positive = net upward momentum, negative = net downward. +// +// d = P[t]-P[t-1]; over the initial period accumulate gain = sum of positive d, loss = sum of -d for negative d. +// Wilder-smooth each: prevGain = (prevGain*(period-1) + gain_today)/period (same for loss). +// CMO = 100 * (prevGain-prevLoss)/(prevGain+prevLoss); 0 when prevGain+prevLoss == 0. +// +// Note: Gains and losses are smoothed with Wilder's method (as in RSI) rather than the simple period sums of Chande's original definition. +func CMO(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := cmo( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("CMO", "result", retCode) + return nil + } + + return outReal +} + +// DX - Wilder's Directional Movement Index: the normalized spread between +DI and -DI. +// Measures the strength of directional (trending) movement, irrespective of direction. +// Higher DX = stronger trend (either direction); low DX = ranging market. +// +// Seed +DM14, -DM14, TR14 as sums of the first (period-1) one-period values, then Wilder-smooth each: X = X - X/period + today. +// +DI = 100*(+DM14/TR14), -DI = 100*(-DM14/TR14). DX = 100 * |(-DI) - (+DI)| / ((-DI) + (+DI)). +// +// Note: Wilder's original integer rounding is not applied (it can be unreliable when values are near 1). +// When +DI and -DI sum to zero the value is undefined; the previous bar's DX is carried forward instead (the first such bar outputs zero). +func DX(inHigh, inLow, inClose []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := dx( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("DX", "result", retCode) + return nil + } + + return outReal +} + +// IMI - Intraday Momentum Index: an RSI-like 0-100 oscillator built from the open-to-close body of each bar. +// Over a rolling window it ratios cumulative up-body moves against total up+down body moves. +// +// upsum = Σ(close-open) for bars with close>open; +// downsum = Σ(open-close) for bars with close<=open, over window [i-lookback, i]; +// IMI = 100 * upsum/(upsum+downsum) +func IMI(inOpen, inClose []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := imi( + startIdx, + endIdx, + inOpen, + inClose, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("IMI", "result", retCode) + return nil + } + + return outReal +} + +// MACD - Moving Average Convergence/Divergence: the difference between a fast and a slow EMA of the input, plus an EMA-smoothed signal line and their histogram. +// MACD crossing its signal line and histogram sign changes flag momentum shifts. +// +// MACD = EMA_fast - EMA_slow; Signal = EMA(MACD, signalPeriod); Hist = MACD - Signal +// +// Note: If the slow period is set smaller than the fast period, the two are swapped so the slow EMA is always the longer one. +// A signal period of 1 disables signal-line smoothing: the signal equals the MACD line and the histogram is zero. +// Before 0.6.5 this parameter value produced misaligned output (issues #48/#59). +func MACD(inReal []float64, optInFastPeriod, optInSlowPeriod, optInSignalPeriod int) ([]float64, []float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outMACD = make([]float64, len(inReal)) + outMACDSignal = make([]float64, len(inReal)) + outMACDHist = make([]float64, len(inReal)) + ) + + if retCode := macd( + startIdx, + endIdx, + inReal, + int32(optInFastPeriod), + int32(optInSlowPeriod), + int32(optInSignalPeriod), + &outBegIdx, + &outNBElement, + outMACD, + outMACDSignal, + outMACDHist, + ); SUCCESS != taResult(retCode) { + slog.Debug("MACD", "result", retCode) + return nil, nil, nil + } + + return outMACD, outMACDSignal, outMACDHist +} + +// MACDEXT - MACD variant where the fast, slow, and signal moving averages each use a user-selectable MA type. +// Outputs the MACD line, its signal line, and their difference (histogram). +// Hist sign change (MACD crossing its signal line) flags momentum shifts. +// +// MACD = MA_fast(inReal) - MA_slow(inReal) +// Signal = MA_signal(MACD) +// Hist = MACD - Signal +// (each MA_* uses its own MA type and period) +// +// Note: If the slow period is set smaller than the fast period, the fast and slow periods and their MA types are swapped so the slow moving average is always the longer one. +// A signal period of 1 disables signal-line smoothing for every signal MAType: the signal equals the MACD line and the histogram is zero. +func MACDEXT( + inReal []float64, + optInFastPeriod int, optInFastMAType MAType, + optInSlowPeriod int, optInSlowMAType MAType, + optInSignalPeriod int, optInSignalMAType MAType, +) ([]float64, []float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outMACD = make([]float64, len(inReal)) + outMACDSignal = make([]float64, len(inReal)) + outMACDHist = make([]float64, len(inReal)) + ) + + if retCode := macdext( + startIdx, + endIdx, + inReal, + int32(optInFastPeriod), + int32(optInFastMAType), + int32(optInSlowPeriod), + int32(optInSlowMAType), + int32(optInSignalPeriod), + int32(optInSignalMAType), + &outBegIdx, + &outNBElement, + outMACD, + outMACDSignal, + outMACDHist, + ); SUCCESS != taResult(retCode) { + slog.Debug("MACDEXT", "result", retCode) + return nil, nil, nil + } + + return outMACD, outMACDSignal, outMACDHist +} + +// MACDFIX - MACD with the fast/slow EMAs fixed to the classic 12/26 periods (with the classic fixed smoothing factors 0.15 and 0.075), exposing only the signal period. +// Signal-line crossovers and histogram sign flag momentum shifts. +// +// MACD = EMA_12 - EMA_26 (fixed k: 0.15 for 12, 0.075 for 26) +// Signal = EMA(MACD, signalPeriod), k = 2/(signalPeriod+1) +// Hist = MACD - Signal +// +// Note: A signal period of 1 disables signal-line smoothing: the signal equals the MACD line and the histogram is zero. +// Before 0.6.5 this parameter value produced misaligned output (issues #48/#59). +func MACDFIX(inReal []float64, optInSignalPeriod int) ([]float64, []float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outMACD = make([]float64, len(inReal)) + outMACDSignal = make([]float64, len(inReal)) + outMACDHist = make([]float64, len(inReal)) + ) + + if retCode := macdfix( + startIdx, + endIdx, + inReal, + int32(optInSignalPeriod), + &outBegIdx, + &outNBElement, + outMACD, + outMACDSignal, + outMACDHist, + ); SUCCESS != taResult(retCode) { + slog.Debug("MACDFIX", "result", retCode) + return nil, nil, nil + } + + return outMACD, outMACDSignal, outMACDHist +} + +// MFI - Money Flow Index: a volume-weighted momentum oscillator (0-100) comparing positive vs negative money flow over a period. +// A volume-based analog of RSI. >80 overbought, <20 oversold. +// +// TP = (High+Low+Close)/3; MF = TP*Volume, classed positive if TP>prevTP, negative if TP0 and (high-prevHigh)<(prevLow-low), else 0. +// Seed -DM/TR = sum of first (period-1) -DM1/TR1, then Wilder-smooth each: X = X - X/period + today. +// -DI = 100 * (-DM / TR); TR from ta_true_range. +// If period<=1: -DI1 = -DM1/TR1 (no ×100). +// +// Note: Wilder's original integer rounding is not applied (it was removed as unreliable when values are near 1). +func MINUS_DI(inHigh, inLow, inClose []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := minus_di( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("MINUS_DI", "result", retCode) + return nil + } + + return outReal +} + +// MINUS_DM - Minus Directional Movement, the downward component of Wilder's directional movement system. +// Measures Wilder-smoothed downward price motion over the period. Higher -DM indicates stronger downward directional movement. +// +// diffP = high - prevHigh; diffM = prevLow - low +// -DM1 = diffM if (diffM > 0 and diffP < diffM) else 0 +// period<=1: output raw -DM1 per bar. +// period>1: seed = sum of first (period-1) -DM1; then Wilder smooth each bar: +// -DM = prevMinusDM - prevMinusDM/period (+ -DM1 when the bar qualifies) +func MINUS_DM(inHigh, inLow []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inLow) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inLow)) + ) + + if retCode := minus_dm( + startIdx, + endIdx, + inHigh, + inLow, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("MINUS_DM", "result", retCode) + return nil + } + + return outReal +} + +// PLUS_DI - Plus Directional Indicator: the Wilder-smoothed positive directional movement expressed as a percentage of the true range. +// Measures the strength of upward price movement. Rising +DI signals strengthening upward direction; compared against MINUS_DI to judge trend direction. +// +// +DM1 = (H-Hprev) if (H-Hprev) > 0 and (H-Hprev) > (Lprev-L), else 0. +// TR1 = true range = max(H-L, |H-Cprev|, |L-Cprev|). +// Seed +DM/TR = sum of first (period-1) one-period values; then Wilder smooth: X = X - X/period + X1. +// +DI = 100 * (+DM / TR); if TR = 0, +DI = 0. +// When period <= 1: +DI = +DM1 / TR1 (no *100). +// +// Note: Wilder's original integer rounding of intermediate values is not applied (it was unreliable when values are near 1). +func PLUS_DI(inHigh, inLow, inClose []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := plus_di( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("PLUS_DI", "result", retCode) + return nil + } + + return outReal +} + +// PLUS_DM - Plus Directional Movement: the Wilder-smoothed accumulation of upward directional movement (+DM1). +// A component of the Directional Movement System used to build +DI/DX/ADX. +// +// +DM1 = (high - prevHigh) if (high-prevHigh) > 0 and > (prevLow-low), else 0. +// period<=1: output = +DM1 per bar. +// period>1: seed = sum of first (period-1) +DM1; then Wilder smoothing: +// +DM = prevPlusDM - prevPlusDM/period + +DM1(today) +func PLUS_DM(inHigh, inLow []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inLow) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inLow)) + ) + + if retCode := plus_dm( + startIdx, + endIdx, + inHigh, + inLow, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("PLUS_DM", "result", retCode) + return nil + } + + return outReal +} + +// PPO - Percentage Price Oscillator: the difference between a fast and slow moving average expressed as a percentage of the slow MA. +// A normalized (scale-invariant) variant of APO. Positive when the fast MA is above the slow MA (upward momentum), negative otherwise; magnitude is the % deviation. +// +// PPO = ((fastMA(inReal) - slowMA(inReal)) / slowMA(inReal)) * 100, both MAs of type optInMAType; output = 0 when slowMA == 0 +// The standard form is exponential with periods 12 and 26 — ((12-day EMA - 26-day EMA) / 26-day EMA) * 100, +// i.e. the MACD oscillator expressed as a percentage. optInMAType therefore defaults to EMA — the moving average Gerald Appel used for the original PPO/MACD; +// pass another type (e.g. talib.SMA) to override. +func PPO(inReal []float64, optInFastPeriod, optInSlowPeriod int, optInMAType MAType) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := ppo( + startIdx, + endIdx, + inReal, + int32(optInFastPeriod), + int32(optInSlowPeriod), + int32(optInMAType), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("PPO", "result", retCode) + return nil + } + + return outReal +} + +// ROC - Rate-of-change momentum oscillator: the percent change of price versus the price optInTimePeriod bars earlier. +// Centered at zero with positive and negative values. Positive when price rose over the period, negative when it fell; magnitude scales the move. +// +// ROC = ((price / prevPrice) - 1) * 100, where prevPrice = inReal[i - optInTimePeriod] +func ROC(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := roc( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("ROC", "result", retCode) + return nil + } + + return outReal +} + +// ROCP - Rate of change expressed as a fraction of the price optInTimePeriod bars ago. +// Normalized and centered at zero (positive or negative). >0 rising vs N bars ago, <0 falling; equals ROC/100. +// +// ROCP = (price - prevPrice) / prevPrice, prevPrice = inReal[i - optInTimePeriod] +func ROCP(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := rocp( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("ROCP", "result", retCode) + return nil + } + + return outReal +} + +// ROCR - Rate of Change Ratio: the ratio of the current price to the price optInTimePeriod bars ago. +// A momentum measure centered at 1. Always positive, centered at 1: >1 rising, <1 falling. +// +// ROCR = price / price[t - optInTimePeriod] +func ROCR(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := rocr( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("ROCR", "result", retCode) + return nil + } + + return outReal +} + +// ROCR100 - Rate-of-change ratio scaled by 100: current price as a percentage of the price optInTimePeriod bars ago. +// Momentum measure centered at 100 and always positive. Above 100 = price rose vs n bars ago; below 100 = price fell. +// +// ROCR100 = (price / price[t - optInTimePeriod]) * 100 +func ROCR100(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := rocr100( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("ROCR100", "result", retCode) + return nil + } + + return outReal +} + +// RSI - Wilder's Relative Strength Index, a momentum oscillator bounded 0-100 from the ratio of average gains to average losses over the period. +// Used to gauge overbought/oversold conditions. >70 overbought, <30 oversold. +// +// Seed avgGain/avgLoss as the sum of the first (period-1) gains/losses, then Wilder-smooth each: X = X - X/period + today. +// RS = avgGain / avgLoss; RSI = 100 - (100 / (1 + RS)); when avgLoss == 0, RSI = 100; when avgGain == 0, RSI = 0. +// +// Note: Wilder's original integer rounding is not applied (it was unreliable when values are near 1). +func RSI(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := rsi( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("RSI", "result", retCode) + return nil + } + + return outReal +} + +// STOCH - Slow Stochastic oscillator: locates the close within the high-low range over a lookback period, then double-smooths it. +// Returns the Slow-%K and Slow-%D lines. SlowK/SlowD > 80 overbought, < 20 oversold; %K crossing %D signals momentum shifts. +// +// FastK = 100*(Close - LL_n)/(HH_n - LL_n), n = FastK_Period (LL/HH = lowest low / highest high over n) +// SlowK = MA(FastK, SlowK_Period, SlowK_MAType) +// SlowD = MA(SlowK, SlowD_Period, SlowD_MAType) +// +// Note: When the high-low range over the window is zero, the raw stochastic is set to 0 instead of being undefined. +func STOCH( + inHigh, inLow, inClose []float64, + optInFastKPeriod int, + optInSlowKPeriod int, + optInSlowKMAType MAType, + optInSlowDPeriod int, + optInSlowDMAType MAType, +) ([]float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outSlowK = make([]float64, len(inClose)) + outSlowD = make([]float64, len(inClose)) + ) + + if retCode := stoch( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInFastKPeriod), + int32(optInSlowKPeriod), + int32(optInSlowKMAType), + int32(optInSlowDPeriod), + int32(optInSlowDMAType), + &outBegIdx, + &outNBElement, + outSlowK, + outSlowD, + ); SUCCESS != taResult(retCode) { + slog.Debug("STOCH", "result", retCode) + return nil, nil + } + + return outSlowK, outSlowD +} + +// STOCHF - Fast Stochastic Oscillator: the raw %K line and its moving-average-smoothed %D line. +// Unlike STOCH (which slows both lines), STOCHF returns the unsmoothed FastK and FastD. Oscillates 0-100; >80 overbought, <20 oversold. +// +// FastK = 100 * (Close - LowestLow) / (HighestHigh - LowestLow), over the last FastK_Period bars (incl. today) +// FastD = MA(FastK, FastD_Period, FastD_MAType) +// +// Note: When the high-low range over the window is zero, %K is set to 0 instead of being undefined. +func STOCHF( + inHigh, inLow, inClose []float64, + optInFastKPeriod int, + optInFastDPeriod int, + optInFastDMAType MAType, +) ([]float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outFastK = make([]float64, len(inClose)) + outFastD = make([]float64, len(inClose)) + ) + + if retCode := stochf( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInFastKPeriod), + int32(optInFastDPeriod), + int32(optInFastDMAType), + &outBegIdx, + &outNBElement, + outFastK, + outFastD, + ); SUCCESS != taResult(retCode) { + slog.Debug("STOCHF", "result", retCode) + return nil, nil + } + + return outFastK, outFastD +} + +// STOCHRSI - Applies the Fast Stochastic (STOCHF) oscillator to an RSI series instead of price, +// measuring where RSI sits within its recent min/max range. +// Oscillates 0-100; high = RSI near its recent top, low = near its recent bottom. +// +// rsi = RSI(inReal, optInTimePeriod) +// FastK = 100 * (rsi_t - min(rsi, FastK_Period)) / (max(rsi, FastK_Period) - min(rsi, FastK_Period)) +// FastD = MA(FastK, FastD_Period, FastD_MAType) +// +// Note: To reproduce the original article's unsmoothed Stochastic RSI, set the RSI period equal to the %K period and read the raw %K output. +// When the RSI's recent range is zero, %K is set to 0 instead of being undefined. +func STOCHRSI( + inReal []float64, + optInTimePeriod int, + optInFastKPeriod int, + optInFastDPeriod int, + optInFastDMAType MAType, +) ([]float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outFastK = make([]float64, len(inReal)) + outFastD = make([]float64, len(inReal)) + ) + + if retCode := stochrsi( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + int32(optInFastKPeriod), + int32(optInFastDPeriod), + int32(optInFastDMAType), + &outBegIdx, + &outNBElement, + outFastK, + outFastD, + ); SUCCESS != taResult(retCode) { + slog.Debug("STOCHRSI", "result", retCode) + return nil, nil + } + + return outFastK, outFastD +} + +// TRIX - 1-day Rate-Of-Change of a triple-smoothed EMA of the input. +// Momentum oscillator that filters out price moves shorter than the chosen period. +// Oscillates around zero; sign, zero-crossings and slope signal momentum direction. +// +// E1 = EMA(inReal, n); E2 = EMA(E1, n); E3 = EMA(E2, n); TRIX = ROC_1(E3) = 100 * (E3_today/E3_yesterday - 1) +// +// Note: The final rate-of-change step yields 0 when the previous smoothed value is exactly zero, rather than being undefined. +func TRIX(inReal []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := trix( + startIdx, + endIdx, + inReal, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("TRIX", "result", retCode) + return nil + } + + return outReal +} + +// ULTOSC - Ultimate Oscillator: momentum indicator combining buying-pressure/true-range ratios over three time periods into one 0-100 weighted average. +// Blends short-, medium-, and long-term momentum to damp single-period noise. Ranges 0-100; conventionally >70 overbought, <30 oversold. +// +// trueLow = min(low, prevClose); BP = close - trueLow +// TR = max(high-low, |prevClose-high|, |prevClose-low|) +// avg_n = (sum BP over n bars) / (sum TR over n bars) +// ULTOSC = 100 * (4avg_short + 2avg_mid + avg_long) / 7 +// +// Note: The three periods are sorted internally, so the 4/2/1 weighting always applies to the shortest, middle, and longest period regardless of the order in which you pass them. +func ULTOSC( + inHigh, inLow, inClose []float64, + optInTimePeriod1, optInTimePeriod2, optInTimePeriod3 int, +) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := ultosc( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInTimePeriod1), + int32(optInTimePeriod2), + int32(optInTimePeriod3), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("ULTOSC", "result", retCode) + return nil + } + + return outReal +} + +// WILLR - Williams' %R momentum oscillator over a rolling period, bounded in [-100, 0]. +// Measures where the current close sits relative to the high-low range of the last N bars. +// Near 0 = close at period high (overbought); near -100 = close at period low (oversold). +// +// %R = -100 * (highestHigh - close) / (highestHigh - lowestLow) over the trailing optInTimePeriod bars; if highestHigh == lowestLow, output 0. +func WILLR(inHigh, inLow, inClose []float64, optInTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inClose) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inClose)) + ) + + if retCode := willr( + startIdx, + endIdx, + inHigh, + inLow, + inClose, + int32(optInTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); SUCCESS != taResult(retCode) { + slog.Debug("WILLR", "result", retCode) + return nil + } + + return outReal +} diff --git a/ta_ma_type.go b/ta_ma_type.go new file mode 100644 index 0000000..5fcfa2f --- /dev/null +++ b/ta_ma_type.go @@ -0,0 +1,15 @@ +package talib + +type MAType int32 + +const ( + SMA MAType = 0 // Simple Moving Average + EMA MAType = 1 // Exponential Moving Average + WMA MAType = 2 // Weighted Moving Average + DEMA MAType = 3 // Double Exponential Moving Average + TEMA MAType = 4 // Triple Exponential Moving Average + TRIMA MAType = 5 // Triangular Moving Average + KAMA MAType = 6 // Kaufman Adaptive Moving Average + MAMA MAType = 7 // MESA Adaptive Moving Average + T3 MAType = 8 // Triple Exponential Moving Average (T3) +)