DEV Community

Luke
Luke

Posted on

Building Your First Trading Bot in Go with Wisp

Have you ever wanted to write a trading strategy that didn't require Python's performance overhead, complex async patterns, or polling loops? If you're a Go developer interested in algorithmic trading, Wisp might be exactly what you need.

In this tutorial, we'll build a real RSI momentum bot in ~50 lines of Go. It'll trade on Binance, react to live market data in real-time, and manage orders automatically.

Why Wisp?

Most trading frameworks are built in Python. They hit the GIL, require threading workarounds, and use polling to check if signals are ready. Wisp is different:

  • Event-driven: Strategies own their run loop. No polling.
  • Goroutine-native: Leverage Go's concurrency without fighting the runtime.
  • Multi-exchange: Binance, Bybit, Hyperliquid—same code.
  • Zero boilerplate: Focus on your strategy, not plumbing.

Prerequisites

  • Go 1.20+
  • A Binance API key (testnet is fine)
  • 10 minutes

Step 1: Install Wisp

go get github.com/wisp-trading/sdk
Enter fullscreen mode Exit fullscreen mode

Step 2: Write Your Strategy

Here's a complete RSI momentum bot:

package main

import (
    "context"
    "time"

    "github.com/wisp-trading/sdk/pkg/types/connector"
    "github.com/wisp-trading/sdk/pkg/types/strategy"
    "github.com/wisp-trading/sdk/pkg/types/wisp"
)

type RSIBot struct {
    strategy.BaseStrategy
    k wisp.Wisp
}

func NewRSIBot(k wisp.Wisp) strategy.Strategy {
    s := &RSIBot{k: k}
    s.BaseStrategy = *strategy.NewBaseStrategy(strategy.BaseStrategyConfig{
        Name: "rsi-bot",
    })
    return s
}

func (s *RSIBot) Start(ctx context.Context) error {
    return s.StartWithRunner(ctx, s.run)
}

func (s *RSIBot) run(ctx context.Context) {
    ticker := time.NewTicker(5 * time.Minute)
    defer ticker.Stop()

    pair := s.k.Pair("BTC", "USDT")

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            // Fetch last 100 candles
            klines := s.k.Spot().Klines(
                connector.Binance,
                pair,
                "1h",
                100,
            )

            // Calculate RSI
            rsi, err := s.k.Indicators().RSI(klines, 14)
            if err != nil || len(rsi) == 0 {
                continue
            }

            currentRSI := rsi[len(rsi)-1]

            // RSI < 30 = oversold, buy signal
            if currentRSI < 30 {
                signal, err := s.k.Spot().Signal(s.GetName()).
                    Buy(pair, connector.Binance, s.k.Asset("BTC").Qty(0.01)).
                    Build()
                if err == nil {
                    s.Emit(signal)
                }
            }

            // RSI > 70 = overbought, sell signal
            if currentRSI > 70 {
                signal, err := s.k.Spot().Signal(s.GetName()).
                    Sell(pair, connector.Binance, s.k.Asset("BTC").Qty(0.01)).
                    Build()
                if err == nil {
                    s.Emit(signal)
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

What's Happening?

BaseStrategy handles lifecycle and signals. You call StartWithRunner(ctx, s.run) to launch your tick loop as a managed goroutine.

Your run() method owns the loop. No framework thread constantly asking "are you ready?". You decide when to check indicators.

Fetch klines with s.k.Spot().Klines() — pulls historical candles from Binance.

Calculate RSI — pure function, takes klines array. No dependencies on exchange, symbol, or framework state.

Build & emit signals — domain-scoped builder, automatic routing. When you Emit(signal), Wisp handles order execution, position tracking, and PNL calculation.

Key Concepts

You Own Your Loop

Don't wait for a framework to call your strategy. Your run() goroutine runs until it returns.

Domain-Scoped APIs

k.Spot()    // Buy/sell
k.Perp()    // Leverage
k.Predict() // Outcome betting
Enter fullscreen mode Exit fullscreen mode

Same Emit() interface for all. Build once, route automatically.

Indicators Are Pure Functions

rsi, _ := k.Indicators().RSI(klines, 14)
macd, _ := k.Indicators().MACD(klines, 12, 26, 9)
bb, _ := k.Indicators().BollingerBands(klines, 20, 2.0)
Enter fullscreen mode Exit fullscreen mode

Fetch klines once, pass to multiple indicators. No redundant reads.

Why Go for Trading?

  • Concurrency: Run 100+ strategies in one process without thread overhead
  • Performance: No GIL, compile to machine code, microsecond latencies
  • Simplicity: One language end-to-end
  • Ops: Single binary, no Python dependencies

Next Steps

We have full documentation including 13 strategy patterns (MACD, Bollinger Bands, multi-indicator, arbitrage, risk management), working examples, and API reference at https://usewisp.dev/docs.

Clone the repo, run a strategy on testnet, and move to mainnet when confident.

Questions? Open an issue on GitHub or check the docs.

Happy trading 🚀

Top comments (0)