This guide takes you from an empty Go project to the three common ways to use the repository:
- call a venue adapter for normalized market data;
- write a strategy against
strategy.Runtime; - run the same strategy shape in backtest and live node wiring.
The module targets Go 1.26.
go get github.com/QuantProcessing/exchangesFor local test and example runs, keep the Go build cache outside the repository:
env GOCACHE=/private/tmp/go-build-exchanges go test -count=1 ./examples/...Use the lowest layer that solves your problem:
| Need | Use | Why |
|---|---|---|
| Direct access to a venue endpoint | sdk/<venue> |
You want the native request and response shape. |
| Normalized venue data or order methods | adapter/<venue> plus venue interfaces |
You want a stable cross-venue surface. |
| Strategy, backtest, live trading, risk, portfolio, and reconciliation | strategy, backtest, live, platform |
You want lifecycle-correct trading behavior. |
Most applications should not import an SDK from strategy code. Strategies should use normalized model types and submit commands through the runtime so risk, execution, cache, and portfolio remain consistent.
Adapter methods return normalized model values and declare capabilities
through venue.DeclaredCapabilities.
package main
import (
"context"
"fmt"
"github.com/QuantProcessing/exchanges/adapter/binance"
"github.com/QuantProcessing/exchanges/model"
)
func main() {
ctx := context.Background()
adp, err := binance.NewSpotAdapter(ctx, binance.Options{})
if err != nil {
panic(err)
}
defer adp.Close(ctx)
instrumentID := model.MustInstrumentID("BTC-USDT-SPOT.BINANCE")
ticker, err := adp.Data().FetchTicker(ctx, instrumentID)
if err != nil {
panic(err)
}
fmt.Println(ticker.Last)
}Compiled example: 01_fetch_ticker_with_adapter.go.
If you need to support several venues, code against the venue.DataClient and
venue.ExecutionClient interfaces instead of a concrete adapter package.
Strategies are ordinary Go values. strategy.NewTyped dispatches runtime
events into whichever typed methods your value implements.
type ImbalanceStrategy struct {
runtime strategy.Runtime
accountID model.AccountID
instrumentID model.InstrumentID
submitted bool
}
func (s *ImbalanceStrategy) OnStart(ctx context.Context, rt strategy.Runtime) error {
s.runtime = rt
return rt.SubscribeOrderBookDepth(ctx, s.instrumentID, 2)
}
func (s *ImbalanceStrategy) OnOrderBook(ctx context.Context, book model.OrderBook) error {
if s.submitted || len(book.Bids) == 0 || len(book.Asks) == 0 {
return nil
}
if !book.Bids[0].Size.GreaterThan(book.Asks[0].Size.Mul(decimal.NewFromInt(2))) {
return nil
}
s.submitted = true
order := s.runtime.OrderFactory(s.accountID).Limit(
book.InstrumentID,
model.OrderSideBuy,
decimal.RequireFromString("0.01"),
book.Asks[0].Price,
)
_, err := s.runtime.SubmitOrder(ctx, order)
return err
}Important rules:
- Store the runtime passed to
OnStart; do not create one yourself. - Use
OrderFactoryso account IDs, client order IDs, list IDs, and command metadata are generated consistently. - Keep venue-specific details outside the strategy.
- Query open orders, fills, positions, balances, and exposure through
rt.Cache()andrt.Portfolio().
Compiled examples: 02_build_orders_with_order_factory.go and 04_run_strategy_backtest.go.
Backtests replay timestamped events through the same strategy callback shape. They are the fastest way to lock down strategy behavior.
events := []backtest.Event{
{
At: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC),
Topic: strategy.TopicMarketData,
Message: model.MarketEvent{OrderBook: &model.OrderBook{
InstrumentID: model.MustInstrumentID("BTC-USDT-SPOT.BINANCE"),
Bids: []model.OrderBookLevel{{
Price: decimal.RequireFromString("100"),
Size: decimal.RequireFromString("3"),
}},
Asks: []model.OrderBookLevel{{
Price: decimal.RequireFromString("101"),
Size: decimal.RequireFromString("1"),
}},
}},
},
}
runner := backtest.NewRunner(backtest.Config{
Strategies: []strategy.Strategy{
strategy.NewTyped("imbalance", &ImbalanceStrategy{
accountID: "main",
instrumentID: model.MustInstrumentID("BTC-USDT-SPOT.BINANCE"),
}),
},
Events: events,
})
result, err := runner.Run(context.Background())
if err != nil {
panic(err)
}
fmt.Println(result.EventsProcessed)Compiled example: 04_run_strategy_backtest.go.
A live node wires data clients, execution clients, strategies, risk, cache, portfolio, bus, reconnect policy, and health reporting into one runtime.
node, err := live.NewNodeBuilder().
WithCache(cache.New()).
WithRiskConfig(risk.Config{
MaxOrderNotional: decimal.RequireFromString("100"),
}).
AddDataClient(dataClient).
AddExecutionClient(executionClient).
AddStrategy(strategy.NewTyped("imbalance", strategyImpl)).
Build()
if err != nil {
return err
}
if err := node.Start(ctx); err != nil {
return err
}
defer node.Stop(context.Background())The live node startup path loads instruments, connects market data, connects
execution, queries account state, starts strategies, forwards stream events,
and records health. Keep Node.Stop in a defer or shutdown handler.
Compiled example: 06_run_live_node_with_in_memory_venue.go.