Skip to content

TA-Lib/ta-lib-cgo

Repository files navigation

ta-lib-cgo

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-cgo and this is the canonical location going forward.

Quick start

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)

outBuf pattern

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.

Lookback and NaN padding

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) values

Platform support

Requires CGO. Tested on macOS, Linux, and Windows 11 (MSYS2/GCC).

Windows setup (MSYS2)

  1. Download and install MSYS2.
  2. From the MSYS2 terminal, install GCC and Go:
    pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-go
    
  3. Use your project as normal — go build and go test work out of the box.

Or feel free to try using other C compilers, untested but might work just fine.

Rationale

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.

Tests

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.

Maintenance - Updating TA-Lib

If you wish to do work on this wrapper library or update it, you can:

  • Edit the version tag in the //go:generate directive in talib.go as needed.
  • Run go generate from the repository root.
  • Run go test -v -count=1 ./... to make sure it's all working
  • Commit include/, talib_amalgamation.c, and *_gen.go files.

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.

Versioning

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:

  1. Edit the version in the //go:generate line in talib.go:
    //go:generate go run ./cmd/gen v0.7.0
  2. Run codegen and tests:
    go generate
    go test -v -count=1 ./...
  3. Commit all regenerated files (include/, talib_amalgamation.c, *_gen.go).
  4. 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

Repository structure

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

Performance

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

About

TA-Lib for Go - easy to use, cross platform, CGO wraps embedded TA-Lib, zero deps

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages