Go bindings for TA-Lib that embed the C source directly —
no system library installation required. Users need only go get. A working CGO environment is the only requirement, works out of the box with typical Go toolchain installations.
Note: The maintainers of TA-Lib have graciously accepted this project under the official TA-Lib GitHub organization. The import path has changed to
github.com/TA-Lib/ta-lib-cgoand this is the canonical location going forward.
import ta "github.com/TA-Lib/ta-lib-cgo"
// SMA over 10 prices with a 3-bar period.
prices := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := ta.Sma(prices, 3, nil)
// result: [NaN, NaN, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
// MACD.
macd, signal, hist := ta.Macd(prices, 12, 26, 9, nil, nil, nil)Pass a slice as the last argument(s) to reuse an existing buffer and avoid allocation:
buf := make([]float64, 0, 1000) // allocate once
for _, batch := range batches {
result := ta.Sma(batch, 20, buf) // reuses buf if cap >= len(batch)
process(result)
buf = result // keep the buffer for next iteration
}If outBuf is nil or its capacity is less than the input length, a new slice is allocated.
All output slices have the same length as the input. The first Lookback positions are filled
with NaN (the "warmup" period where insufficient data exists for a valid output).
lb := ta.SmaLookback(3) // returns 2
// The first 2 values of Sma(..., 3, nil) will be NaN.Use lookback functions to sub-slice into valid data:
result := ta.Sma(prices, 20, nil)
lb := ta.SmaLookback(20)
validResult := result[lb:] // only valid (non-NaN) valuesRequires CGO. Tested on macOS, Linux, and Windows 11 (MSYS2/GCC).
- Download and install MSYS2.
- From the MSYS2 terminal, install GCC and Go:
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-go - Use your project as normal —
go buildandgo testwork out of the box.
Or feel free to try using other C compilers, untested but might work just fine.
Existing Go TA-Lib approaches have significant trade-offs:
- Pure-Go reimplementations diverge from the reference C implementation and require ongoing maintenance to stay in sync. Any upstream bug fix or edge-case change must be manually ported.
- System-library wrappers require TA-Lib to be pre-installed (
apt install,brew install, etc.), complicating builds, CI, and distribution — especially across macOS and Linux.
This library takes a different approach: the TA-Lib C source is embedded directly as an
amalgamation file (talib_amalgamation.c), committed to the repository. CGO compiles it as
part of the normal go build. Algorithm correctness is guaranteed by the upstream C
implementation, and you get a single go get with no external dependencies.
The API is designed for high-frequency and batch processing workloads. Callers control output
allocation via the outBuf parameter pattern — buffers can be reused across calls to eliminate
allocations in tight loops.
A sensible set of tests are included, some of them generated. The objective with the test suite is to provide some decent coverage that provides some proof this thing actually works, while not introducing a significant maintenance burden of trying to exhaustively prove what TA-Lib already implements and handles.
If you wish to do work on this wrapper library or update it, you can:
- Edit the version tag in the
//go:generatedirective intalib.goas needed. - Run
go generatefrom the repository root. - Run
go test -v -count=1 ./...to make sure it's all working - Commit
include/,talib_amalgamation.c, and*_gen.gofiles.
The go generate step requires additional tooling including CMake and uv. It will download the TA-Lib source and (re)generate the various function wrappers and test code. Python (handled via uv) is used to generate sample outputs using it's own TA-Lib bindings which we then verify in Go tests.
Note that it's always possible some structural change in a new TA-Lib version will require the generator code to be updated. YMMV.
This module uses its own semver sequence independent of TA-Lib's version numbering. The embedded TA-Lib version is available at runtime via the TALibVersion constant and is the single source of truth in the //go:generate directive in talib.go. (The reasoning: mirroring TA-Lib versions (e.g. v0.6.4) leaves no room for Go-side fixes without inventing non-standard version suffixes.)
To release a new version:
- Edit the version in the
//go:generateline intalib.go://go:generate go run ./cmd/gen v0.7.0 - Run codegen and tests:
go generate go test -v -count=1 ./... - Commit all regenerated files (
include/,talib_amalgamation.c,*_gen.go). - Tag a new minor version noting the TA-Lib bump:
git tag -a v0.2.0 -m "Update to TA-Lib v0.7.0" git push origin v0.2.0
ta-lib-cgo
│
│ # Hand-written, committed once
├── talib.go CGO directives, init(), buffer/pointer helpers
├── errors.go TALibError type, retCodeMessage()
│
│ # Generated by cmd/gen — committed, do not edit by hand
├── talib_amalgamation.c All TA-Lib .c files concatenated into one
├── errors_gen.go retCodeMessages map (from ta_retcode.csv)
├── functions_gen.go Go wrapper for every TA-Lib indicator
├── lookback_gen.go XxxLookback() companion for every indicator
├── version_gen.go TALibVersion constant
│
│ # Test files
├── talib_test.go Shape/invariant/behavior tests (no fixtures needed)
├── golden_test.go Fixture-based tests; skips if testdata/ missing
│
│ # Committed headers (populated by cmd/gen)
├── include/
│ ├── ta_libc.h Public TA-Lib headers
│ ├── ta_func.h
│ ├── ta_common.h
│ ├── ta_defs.h
│ ├── ta_abstract.h
│ ├── ta_config.h Generated by cmake during go generate
│ └── *.h Internal headers copied from src/ subdirectories
│
│ # Committed test fixtures (generated by scripts/gen_fixtures.py)
├── testdata/
│ ├── input.csv Synthetic OHLCV input (200 bars, fixed seed)
│ └── expected/
│ ├── sma_10.csv Expected outputs from Python ta-lib reference
│ ├── macd_12_26_9.csv
│ └── atr_14.csv
│
│ # Code generation tool
└── cmd/
└── gen/
└── main.go Clones TA-Lib, runs cmake, builds amalgamation,
generates *_gen.go files; invoked via go generate
# Python fixture generator and benchmarks (require uv)
└── scripts/
├── gen_fixtures.py Generates testdata/ using Python ta-lib as an
│ independent reference; invoked via go generate
└── bench.py Python benchmark counterpart for perf comparison
Since both this library and Python's ta-lib package wrap the same C code, per-call performance should be equivalent. This was verified on Apple M1 Max with SMA, MACD, and ATR at 1k and 100k bars — Go with buffer reuse and Python were within noise of each other at every size. At 100k bars both are purely measuring the C library.
To reproduce:
# Go
go test -bench=. -benchmem -benchtime=3s -run='^$'
# Python (requires uv)
uv run ./scripts/bench.py