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
[]float64in; 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 witherrors.IsagainstErrInvalidParam(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 asfastperiod < slowperiod, which is listed under “Requires:” in each function’s doc),ErrLengthMismatch(series of different lengths), orErrPanic(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.