Go

Go module github.com/pratikbhadane24/ferro-ta/bindings/go (package ferrota) wraps ferro_ta_core through the shared C ABI crate ferro_ta_ffi via cgo. No indicator is reimplemented in Go.

Release tags ship prebuilt static archives for Linux and macOS (amd64/arm64) and Windows (amd64), so go get needs no Rust toolchain. cgo must be enabled (the default when a C compiler is installed; on Windows use a MinGW-w64 gcc).

Installation

go get github.com/pratikbhadane24/ferro-ta/bindings/go@latest

Quick start

package main

import (
     "fmt"

     ferrota "github.com/pratikbhadane24/ferro-ta/bindings/go"
)

func main() {
     closes := []float64{44.3, 44.1, 44.6, 45.2, 45.6, 45.1, 46.0, 46.4}

     sma, err := ferrota.Sma(closes, 3)
     if err != nil {
             panic(err)
     }
     upper, middle, lower, err := ferrota.Bbands(closes, 5, 2, 2, ferrota.MATypeSMA)
     if err != nil {
             panic(err)
     }
     fmt.Println(sma, upper, middle, lower)
}

API shape

  • Names are the core function names in Go CamelCase: sma → Sma, linearreg_slope → LinearregSlope, cdl3blackcrows → Cdl3blackcrows.

  • Arrays are []float64 in; outputs have the same length with NaN for warm-up bars. Candle patterns return []int32; some regime/alert helpers return []int8.

  • Multiple outputs are named results: Macd(...) (macd, signal, hist []float64, err error).

  • Errors are *ferrota.Error; compare with errors.Is against ErrInvalidParam (period below its minimum or above 2^24, MA type outside 0–8, non-finite float parameter, invalid enum, or a broken cross-parameter rule such as fastperiod < slowperiod, which is listed under “Requires:” in each function’s doc), ErrLengthMismatch (series of different lengths), or ErrPanic (internal error, contained).

  • Typed constants: MAType (MATypeSMA … MATypeT3), OptionKind (OptionCall, OptionPut), DigitalKind, PricingModel.

  • Concurrency: every function is safe to call from many goroutines.

Streaming

Stateful bar-by-bar indicators wrap a Rust handle. Methods are safe for concurrent use; call Close when done (a finalizer is only a backstop).

rsi, err := ferrota.NewStreamRsi(14)
if err != nil {
     panic(err)
}
defer rsi.Close()
for _, price := range closes {
     value, err := rsi.Update(price) // NaN during warm-up
     if err != nil {
             panic(err)
     }
     _ = value
}

Available: StreamSma, StreamEma, StreamRsi, StreamAtr, StreamBbands, StreamMacd, StreamStoch, StreamVwap, StreamSupertrend.

Options, futures and sizing

price, _ := ferrota.BlackScholesPrice(100, 105, 0.03, 0.01, 0.5, 0.25, ferrota.OptionCall)
delta, gamma, vega, theta, rho, _ := ferrota.BlackScholesGreeks(
     100, 105, 0.03, 0.01, 0.5, 0.25, ferrota.OptionCall)
iv, _ := ferrota.ImpliedVolatility(price, ferrota.ModelBlackScholes,
     100, 105, 0.03, 0.02, 0.5, ferrota.OptionCall, 0.2, 1e-8, 100)

Backtesting

BacktestOhlcv runs the full OHLCV engine: stops, take-profit, trailing and breakeven stops, max-hold exits, limit orders, leverage, loss circuit breakers, the commission model, ATR slippage and volume-capped partial fills. Settings travel as a vector indexed by the Bt* constants. Start from the defaults and overwrite what you need:

s, _ := ferrota.BacktestDefaultSettings()
s[ferrota.BtStopLossPct] = 0.02
s[ferrota.BtRateOfValue] = 0.0005          // 5 bps commission
s[ferrota.BtSlippageAtrMult] = 0.1         // slippage = 0.1 x ATR / price
s[ferrota.BtMaxParticipation] = 0.05       // <= 5% of bar volume (needs volume)
pos, fills, _, rets, equity, err := ferrota.BacktestOhlcv(
     open, high, low, close, volume, signals, nil /* limit prices */, s)
metrics, _ := ferrota.PerformanceMetrics(rets, equity, 252, 0)
sharpe := metrics[ferrota.MetricSharpe]

volume and limitPrices may be nil. A settings slice from an older release (shorter) still works, because missing entries take defaults.

The rest of the toolkit:

sig, _ := ferrota.SmaCrossoverSignals(close, 10, 50)       // also MacdCrossoverSignals
_, _, rets, eq, _ := ferrota.BacktestClose(close, sig, s) // close-only engine, same settings
n, entry, exit, dir, _, _, pnl, _, mae, mfe, _ := ferrota.ExtractTrades(pos, fills, high, low)
paths, _ := ferrota.MonteCarloBootstrap(rets, 1000, 42, 20) // (1000, n_bars) row-major
folds, _ := ferrota.WalkForwardIndices(len(close), 500, 100, 0, 0)
assets, port, peq, _ := ferrota.BacktestMultiAsset(closes, weights, nAssets, 0, 2, 1, 0, 0)
metrics, bench, _ := ferrota.PerformanceMetrics(rets, eq, benchRets, pos, 252, 0)
beta := bench[ferrota.BenchBeta]

live, _ := ferrota.NewStreamBacktest(0.0005, 2) // bar-by-bar, safe for concurrent use
defer live.Close()
position, ret, equity, trades, _, winRate, _, _, kelly, _ := live.Update(lastClose, signal)

ExtractTrades columns have capacity len(positions); the first n rows are trades and the rest are padded with -1 or NaN.

Parameter sweeps and overfitting diagnostics

Sweeps run entirely in Rust on all cores. Matrices are flat, row-major slices; a metric row has len(metrics)/nCombos columns (also SweepNMetrics()), indexed by the Metric* constants:

// SMA crossover over (fast, slow) pairs; invalid pairs (fast >= slow) are skipped.
combos := []float64{5, 50, 10, 50, 10, 100, 60, 50}
valid, returns, metrics, err := ferrota.SweepBuiltin(
     close, ferrota.StrategySmaCrossover, combos, 1.0, 2.0, 100_000, 252, 0)
cols := len(metrics) / len(valid)
for c := range valid {
     if valid[c] == 1 {
             sharpe := metrics[c*cols+int(ferrota.MetricSharpe)]
             _ = sharpe
     }
}

// How much of the winner's edge survives the search?
nCombos := len(valid)
pbo, _, _, _, _, _ := ferrota.PboCscv(returns, nCombos, 16) // P(overfit)
dsr, _ := ferrota.DeflatedSharpeRatio(bestColumn, nCombos, sharpeVariance)

returns is (n_bars, n_combos); columns of skipped combos are NaN. SweepSignals backtests your own signal matrix the same way, and SweepPlateauScores, PurgedKfoldIndices, ProbabilisticSharpeRatio and PboNCombinations are also available.

Not exported yet

The C ABI covers same-length array functions, scalar functions, streaming handles and multi-shape kernels; the whole backtesting toolkit is available. These core APIs are not in the Go module yet: batch/2-D portfolio functions, bar resampling and tick aggregation, volatility cones and smile metrics, option chain/strategy helpers, chunked processing helpers, pivot_points, dtw_*, calendar_spreads. Cross-language coverage lists every function per language.

Performance

Each call crosses cgo once (about 30 ns), then the Rust kernel runs on the whole slice. Results are copied into Go-allocated slices, which costs one extra pass per output: on 1M bars, RSI is within ~6% of calling the Rust core directly, SMA ~12%, and three-output BBANDS ~35%.

Building from source

On a repository checkout, lib/ is not committed. Build the archive for your machine, then use the module normally:

make go-lib              # cargo build -p ferro_ta_ffi --release + copy
cd bindings/go && go test ./...

Development

The Go wrappers, the C header and ffi_spec.json are generated from the Rust export declarations; never edit *_gen.go by hand.

make ffi-gen     # regenerate after changing crates/ferro_ta_ffi
make ffi-check   # full gate: Rust tests, clippy, codegen freshness, C, Go

Golden fixtures in tests/fixtures/golden/ffi_golden.json are replayed through every Go wrapper; regenerate them with python3 scripts/build_golden_fixtures.py after a core change that alters numbers.