diff --git a/README.md b/README.md index 27fc21c..d9668fb 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This project currently exposes a focused subset of TA-Lib functions: - Stochastic family: `Stoch`, `StochF`, `StochRSI` **Overlap Studies** -- `AccBands`, `BBands`, `DEMA`, `EMA`, `HT_TRENDLINE`, `KAMA` +- `AccBands`, `BBands`, `DEMA`, `EMA`, `HT_TRENDLINE`, `KAMA`, `MA`, `MAMA`, `MAVP`, `MidPoint`, `MidPrice`, `SAR`, `SARExt`, `SMA`, `T3`, `TEMA`, `TRIMA`, `WMA` ## Requirements diff --git a/functions.go b/functions.go index deabb0e..1fd3700 100644 --- a/functions.go +++ b/functions.go @@ -738,4 +738,142 @@ var ( outNBElement *int32, outReal []float64, ) int32 + + ma func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + optInMAType int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + mama func( + startIdx int32, + endIdx int32, + inReal []float64, + optInFastLimit float64, + optInSlowLimit float64, + outBegIdx *int32, + outNBElement *int32, + outMAMA []float64, + outFAMA []float64, + ) int32 + + mavp func( + startIdx int32, + endIdx int32, + inReal []float64, + inPeriods []float64, + optInMinPeriod int32, + optInMaxPeriod int32, + optInMAType int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + midpoint func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + midprice func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + sar func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + optInAcceleration float64, + optInMaximum float64, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + sarext func( + startIdx int32, + endIdx int32, + inHigh []float64, + inLow []float64, + optInStartValue float64, + optInOffsetOnReverse float64, + optInAccelerationInitLong float64, + optInAccelerationLong float64, + optInAccelerationMaxLong float64, + optInAccelerationInitShort float64, + optInAccelerationShort float64, + optInAccelerationMaxShort float64, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + sma func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + t3 func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + optInVFactor float64, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + tema func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + trima func( + startIdx int32, + endIdx int32, + inReal []float64, + optInTimePeriod int32, + outBegIdx *int32, + outNBElement *int32, + outReal []float64, + ) int32 + + wma 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 de40102..0e16223 100644 --- a/loader.go +++ b/loader.go @@ -101,6 +101,18 @@ func Load() (uintptr, error) { purego.RegisterLibFunc(&ema, ptr, "TA_EMA") purego.RegisterLibFunc(&ht_trendline, ptr, "TA_HT_TRENDLINE") purego.RegisterLibFunc(&kama, ptr, "TA_KAMA") + purego.RegisterLibFunc(&ma, ptr, "TA_MA") + purego.RegisterLibFunc(&mama, ptr, "TA_MAMA") + purego.RegisterLibFunc(&mavp, ptr, "TA_MAVP") + purego.RegisterLibFunc(&midpoint, ptr, "TA_MIDPOINT") + purego.RegisterLibFunc(&midprice, ptr, "TA_MIDPRICE") + purego.RegisterLibFunc(&sar, ptr, "TA_SAR") + purego.RegisterLibFunc(&sarext, ptr, "TA_SAREXT") + purego.RegisterLibFunc(&sma, ptr, "TA_SMA") + purego.RegisterLibFunc(&t3, ptr, "TA_T3") + purego.RegisterLibFunc(&tema, ptr, "TA_TEMA") + purego.RegisterLibFunc(&trima, ptr, "TA_TRIMA") + purego.RegisterLibFunc(&wma, ptr, "TA_WMA") return ptr, nil } diff --git a/overlap_studies.go b/overlap_studies.go index 31355bb..39f4a92 100644 --- a/overlap_studies.go +++ b/overlap_studies.go @@ -204,3 +204,438 @@ func KAMA(inReal []float64, inTimePeriod int) []float64 { return outReal } + +// MA - Generic moving-average dispatcher that forwards the job to a concrete MA implementation selected by optInMAType. +// Single uniform interface over all TA-Lib moving averages. +// +// outReal = MA_of_type(optInMAType)(inReal, optInTimePeriod); default type = SMA +// +// Note: A period of 1 performs no smoothing for every MAType: the output is a copy of the input. +// `TA_MAType_DISABLED` bypasses smoothing explicitly, for any period: the output is a copy of the input with a lookback of 0. +// Every function that takes an MAType parameter accepts it. +func MA(inReal []float64, inTimePeriod int, inMAType MAType) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := ma( + startIdx, + endIdx, + inReal, + int32(inTimePeriod), + int32(inMAType), + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("MA", "result", retCode) + return nil + } + + return outReal +} + +// MAMA - MESA Adaptive Moving Average: an adaptive EMA whose smoothing factor is driven by the dominant-cycle phase rate measured with a Hilbert transform. +// Emits two lines, MAMA and its slower follower FAMA. +// MAMA crossing above FAMA is bullish; crossing below is bearish. +// +// phase = atan(Q1/I1) in degrees; deltaPhase = max(1, prevPhase - phase) +// alpha = max(fastLimit/deltaPhase, slowLimit) if deltaPhase>1 else fastLimit +// MAMA = alpha*price + (1-alpha)*MAMA_prev +// FAMA = (alpha/2)*MAMA + (1-alpha/2)*FAMA_prev +// @param inReal Input data series +// @param inFastLimit Upper bound on the adaptive smoothing factor; default 0.5 (0.01 to 0.99) +// @param inSlowLimit Lower bound on the adaptive smoothing factor; default 0.05 (0.01 to 0.99) +func MAMA(inReal []float64, inFastLimit, inSlowLimit float64) ([]float64, []float64) { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outMAMA = make([]float64, len(inReal)) + outFAMA = make([]float64, len(inReal)) + ) + + if retCode := mama( + startIdx, + endIdx, + inReal, + inFastLimit, + inSlowLimit, + &outBegIdx, + &outNBElement, + outMAMA, + outFAMA, + ); retCode != 0 { + slog.Debug("MAMA", "result", retCode) + return nil, nil + } + + return outMAMA, outFAMA +} + +// MAVP - Moving average whose period varies per bar, driven by a companion period series. +// For each bar it computes an MA of the selected type over the (clamped) period given by inPeriods. +// +// p_i = clamp((int)inPeriods[startIdx+i], optInMinPeriod, optInMaxPeriod); outReal[i] = MA(inReal, p_i, optInMAType) at bar startIdx+i +// +// Note: Fractional per-bar periods are truncated to whole numbers before being clamped to the minimum and maximum period. +// Period values of 1 perform no smoothing (the bar's output equals its input); the minimum allowed period is 1 since 0.6.5. +// @param inReal Input data series +// @param inPeriods per-bar desired MA period +// @param inMinPeriod Lower clamp for the per-bar period; default 2 (1-100000) +// @param inMaxPeriod Upper clamp for the per-bar period; default 30 (1-100000) +// @param inMAType Type of moving average to compute; default talib.MA_SMA +func MAVP(inReal, inPeriods []float64, inMinPeriod, inMaxPeriod int, inMAType MAType) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := mavp( + startIdx, + endIdx, + inReal, + inPeriods, + int32(inMinPeriod), + int32(inMaxPeriod), + int32(inMAType), + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("MAVP", "result", retCode) + return nil + } + + return outReal +} + +// MidPoint - Midpoint over a period: the average of the highest and lowest input values within the lookback window. +// A single-series overlap smoother (use MIDPRICE for separate high/low price bars). +// +// MIDPOINT = (Highest(inReal, period) + Lowest(inReal, period)) / 2 +// @param inReal Input data series +// @param inTimePeriod Lookback window length; default 14 (2-100000) +func MidPoint(inReal []float64, inTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := midpoint( + startIdx, + endIdx, + inReal, + int32(inTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("MidPoint", "result", retCode) + return nil + } + + return outReal +} + +// MidPrice - Midpoint of the price range over a rolling window: the average of the highest high and lowest low across the last optInTimePeriod bars. +// An overlap-study line plotted on price. +// +// MIDPRICE = (Highest(High, N) + Lowest(Low, N)) / 2, over the N=optInTimePeriod bars ending at each index +// @param inHigh Input high price series +// @param inLow Input low price series +// @param inTimePeriod Lookback window length; default 14 (2-100000) +func MidPrice(inHigh, inLow []float64, inTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inHigh) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inHigh)) + ) + + if retCode := midprice( + startIdx, + endIdx, + inHigh, + inLow, + int32(inTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("MidPrice", "result", retCode) + return nil + } + + return outReal +} + +// SAR - Wilder's Parabolic SAR (Stop And Reverse): a trailing stop/reverse level that accelerates toward price via an acceleration factor. +// Signals trend direction and trailing exit points. +// SAR below price = uptrend (long); +// SAR above price = downtrend (short). +// Price crossing SAR flips direction. +// +// SAR_next = SAR + af * (EP - SAR) +// EP = extreme point (highest high in long / lowest low in short); af starts at Acceleration, += Acceleration each new EP, capped at Maximum. +// On penetration: reverse, SAR := prior EP, reset af = Acceleration. SAR clamped each bar so it does not penetrate the prior/current bar's range. +// @param inHigh Input high price series +// @param inLow Input low price series +// @param inAcceleration Step added to the acceleration factor on each new extreme point; default 0.02 (>=0) +// @param inMaximum Ceiling on the acceleration factor; default 0.2 (>=0) +func SAR(inHigh, inLow []float64, inAcceleration, inMaximum float64) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inHigh) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inHigh)) + ) + + if retCode := sar( + startIdx, + endIdx, + inHigh, + inLow, + inAcceleration, + inMaximum, + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("SAR", "result", retCode) + return nil + } + + return outReal +} + +// SARExt - Extended Parabolic SAR (stop and reverse) giving the caller full control over the initial state and separate acceleration factors for long and short positions. +// Unlike SAR, it returns negative values while short so reversals are distinguishable. +// Sign flip of the output marks a trend reversal (positive=long stop, negative=short stop). +// +// SAR_next = SAR + AF*(EP - SAR), then clamped within the prior and current bar's range. +// On penetration, reverse: set SAR=EP (clamped), reset AF to its Init value, EP=extreme of the new direction. +// Output is +SAR when long, -SAR when short. On reversal an optional offset is applied: long->short SAR*(1+offset), short->long SAR*(1-offset). +// @param inHigh Input high price series +// @param inLow Input low price series +// @param inStartValue Initial SAR/direction: 0 auto, >0 start long at value, <0 start short at |value|; default 0 (any real number) +// @param inOffsetOnReverse Fractional offset applied to the stop on each reversal; default 0 (>=0) +// @param inAccelerationInitLong Initial acceleration factor when long; default 0.02 (>=0) +// @param inAccelerationLong AF increment per new long extreme; default 0.02 (>=0) +// @param inAccelerationMaxLong Cap on the long acceleration factor; default 0.2 (>=0) +// @param inAccelerationInitShort Initial acceleration factor when short; default 0.02 (>=0) +// @param inAccelerationShort AF increment per new short extreme; default 0.02 (>=0) +// @param inAccelerationMaxShort Cap on the short acceleration factor; default 0.2 (>=0) +func SARExt( + inHigh, inLow []float64, + inStartValue, inOffsetOnReverse, + inAccelerationInitLong, inAccelerationLong, inAccelerationMaxLong, + inAccelerationInitShort, inAccelerationShort, inAccelerationMaxShort float64, +) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inHigh) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inHigh)) + ) + + if retCode := sarext( + startIdx, + endIdx, + inHigh, + inLow, + inStartValue, + inOffsetOnReverse, + inAccelerationInitLong, + inAccelerationLong, + inAccelerationMaxLong, + inAccelerationInitShort, + inAccelerationShort, + inAccelerationMaxShort, + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("SARExt", "result", retCode) + return nil + } + + return outReal +} + +// SMA - Simple Moving Average: the unweighted arithmetic mean of the last N input values. Used to smooth a series. +// +// SMA_t = (1/N) * sum_{i=t-N+1}^{t} inReal_i +// +// Note: A period of 1 performs no smoothing: the output is a copy of the input. Allowed since 0.6.5 (issues #48/#59). +func SMA(inReal []float64, inTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := sma( + startIdx, + endIdx, + inReal, + int32(inTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("SMA", "result", retCode) + return nil + } + + return outReal +} + +// T3 - Tillson's T3: a low-lag moving average built from six chained EMAs, combined via volume-factor-weighted coefficients. +// Not the same as EMA3, despite both being called "triple EMA". +// +// k = 2/(period+1); e1=EMA(x), e2=EMA(e1), ... e6=EMA(e5) (six chained EMAs). +// v = vFactor: c1 = -v^3; c2 = 3(v^2 - c1); c3 = -6v^2 - 3(v - c1); c4 = 1 + 3v - c1 + 3v^2. +// T3 = c1e6 + c2e5 + c3e4 + c4e3 +// +// Note: A period of 1 performs no smoothing: the output is a copy of the input. Allowed since 0.6.5 (issues #48/#59). +// @param inReal Input data series +// @param inTimePeriod EMA period for each of the six stages; default 5 (2-100000) +// @param inVFactor Volume factor weighting the coefficients (0 = plain triple EMA, higher = more DEMA-like sharpening); default 0.7 (0-1) +func T3(inReal []float64, inTimePeriod int, inVFactor float64) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := t3( + startIdx, + endIdx, + inReal, + int32(inTimePeriod), + inVFactor, + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("T3", "result", retCode) + return nil + } + + return outReal +} + +// TEMA - Triple Exponential Moving Average: a smoothed price overlay built from three successively-applied EMAs to reduce lag versus a plain EMA. +// Distinct from EMA3, also called "triple EMA" in the literature. +// +// EMA1=EMA(t,period); EMA2=EMA(EMA1,period); EMA3=EMA(EMA2,period); TEMA = 3EMA1 - 3EMA2 + EMA3 +// +// Note: A period of 1 performs no smoothing: the output is a copy of the input. Allowed since 0.6.5 (issues #48/#59). +func TEMA(inReal []float64, inTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := tema( + startIdx, + endIdx, + inReal, + int32(inTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("TEMA", "result", retCode) + return nil + } + + return outReal +} + +// TRIMA - Triangular Moving Average: a double-smoothed moving average that weights prices toward the middle of the window most heavily. +// Equivalent to an SMA of an SMA, computed here via an incremental triangular-weighted running numerator. +// +// Weights rise then fall (4-period: (1a+2b+2c+1d)/6; 5-period: (1a+2b+3c+2d+1e)/9). +// With n = period>>1: odd divides by (n+1)^2, even by n(n+1). +// Equivalent to odd: SMA(SMA(x,(period+1)/2),(period+1)/2); even: SMA(SMA(x,period/2),period/2+1). +// +// Note: Follows the generally accepted (Metastock) definition rather than the TradeStation variant. +// A period of 1 performs no smoothing: the output is a copy of the input. Allowed since 0.6.5 (issues #48/#59). +func TRIMA(inReal []float64, inTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := trima( + startIdx, + endIdx, + inReal, + int32(inTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("TRIMA", "result", retCode) + return nil + } + + return outReal +} + +// WMA - Linearly weighted moving average: each of the last N prices is weighted by its position, +// oldest getting weight 1 and newest weight N. Smooths price while emphasizing recent bars. +// +// WMA = ( sum_{k=1..N} k * P_k ) / (N(N+1)/2), where P_N is the most recent bar +// +// Note: A period of 1 performs no smoothing: the output is a copy of the input. Allowed since 0.6.5 (issues #48/#59). +func WMA(inReal []float64, inTimePeriod int) []float64 { + var ( + startIdx int32 + endIdx = int32(len(inReal) - 1) + outBegIdx int32 + outNBElement int32 + outReal = make([]float64, len(inReal)) + ) + + if retCode := wma( + startIdx, + endIdx, + inReal, + int32(inTimePeriod), + &outBegIdx, + &outNBElement, + outReal, + ); retCode != 0 { + slog.Debug("WMA", "result", retCode) + return nil + } + + return outReal +}