На главную > Блог > Категория > 🤖 Пишем торгового робота на Go: от котировок до сделок
Вы написали прототип бота на Python, но он тормозит при 1000 свечей? Устали от GIL и медленных циклов? Хотите создать лёгкого, быстрого и надёжного бота, который будет работать 24/7 без перезагрузки? Встречайте Go (Golang).
Go создавался в Google для высоконагруженных сетевых сервисов. Он компилируется в машинный код, имеет встроенную поддержку многопоточности (горутины), простой синтаксис и отличную экосистему для работы с HTTP и WebSocket. В этой статье мы создадим полноценного торгового робота, который:
«Go — это язык, который позволяет вашему боту обрабатывать тысячи сделок в секунду, не теряя ни капли производительности. И при этом код остаётся читаемым».
mkdir trading-bot-go
cd trading-bot-go
go mod init trading-bot
go get github.com/gorilla/websocket
go get github.com/shopspring/decimal
go get github.com/markcheno/go-talib
go get github.com/go-resty/resty/v2
go get github.com/joho/godotenv
gorilla/websocket — работа с WebSocket (котировки в реальном времени).shopspring/decimal — точные вычисления с деньгами (чтобы не потерять сатоши).markcheno/go-talib — 100+ технических индикаторов (SMA, RSI, MACD).go-resty/resty — удобный HTTP-клиент для Binance API.godotenv — загрузка API-ключей из .env файла.Создадим модуль websocket_client.go, который будет получать поток цен BTC/USDT.
// websocket_client.go
package main
import (
"encoding/json"
"log"
"github.com/gorilla/websocket"
)
type TradeEvent struct {
Price string `json:"p"`
Volume string `json:"q"`
Time int64 `json:"T"`
}
type WebSocketClient struct {
conn *websocket.Conn
}
func NewWebSocketClient() (*WebSocketClient, error) {
url := "wss://stream.binance.com:9443/ws/btcusdt@trade"
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
return nil, err
}
return &WebSocketClient{conn: conn}, nil
}
func (wsc *WebSocketClient) Listen(onTrade func(price float64)) {
for {
_, message, err := wsc.conn.ReadMessage()
if err != nil {
log.Println("WebSocket ошибка:", err)
return
}
var trade TradeEvent
if err := json.Unmarshal(message, &trade); err != nil {
log.Println("Ошибка парсинга JSON:", err)
continue
}
var price float64
if _, err := fmt.Sscanf(trade.Price, "%f", &price); err == nil {
onTrade(price)
}
}
}
Создадим модуль strategy.go. Мы будем использовать библиотеку ta4go (форк go-talib для типизированных индикаторов).
// strategy.go
package main
import (
"github.com/markcheno/go-talib"
)
type SMACrossoverStrategy struct {
Prices []float64
ShortPeriod int
LongPeriod int
lastSignal string
}
func NewSMACrossoverStrategy(shortPeriod, longPeriod int) *SMACrossoverStrategy {
return &SMACrossoverStrategy{
ShortPeriod: shortPeriod,
LongPeriod: longPeriod,
Prices: []float64{},
lastSignal: "",
}
}
func (s *SMACrossoverStrategy) AddPrice(price float64) {
s.Prices = append(s.Prices, price)
if len(s.Prices) > s.LongPeriod+20 {
s.Prices = s.Prices[1:]
}
}
func (s *SMACrossoverStrategy) GetSignal() string {
if len(s.Prices) < s.LongPeriod {
return "HOLD"
}
smaShort := talib.Sma(s.Prices, s.ShortPeriod)
smaLong := talib.Sma(s.Prices, s.LongPeriod)
lastShort := smaShort[len(smaShort)-1]
lastLong := smaLong[len(smaLong)-1]
prevShort := smaShort[len(smaShort)-2]
prevLong := smaLong[len(smaLong)-2]
// Бычье пересечение (быстрая пересекает медленную снизу вверх)
if prevShort <= prevLong && lastShort > lastLong && s.lastSignal != "BUY" {
s.lastSignal = "BUY"
return "BUY"
}
// Медвежье пересечение (быстрая пересекает медленную сверху вниз)
if prevShort >= prevLong && lastShort < lastLong && s.lastSignal != "SELL" {
s.lastSignal = "SELL"
return "SELL"
}
return "HOLD"
}
func (s *SMACrossoverStrategy) IsStrongBuy() bool {
if len(s.Prices) < 30 {
return false
}
rsi := talib.Rsi(s.Prices, 14)
lastRSI := rsi[len(rsi)-1]
return lastRSI > 50
}
// risk_manager.go
package main
import (
"log"
"time"
)
type RiskManager struct {
MaxDailyLoss float64
CurrentDailyLoss float64
LastResetDate string
}
func NewRiskManager(maxDailyLoss float64) *RiskManager {
return &RiskManager{
MaxDailyLoss: maxDailyLoss,
CurrentDailyLoss: 0,
LastResetDate: time.Now().Format("2006-01-02"),
}
}
func (rm *RiskManager) CanTrade(riskAmount float64) bool {
// Сброс дневного счётчика
today := time.Now().Format("2006-01-02")
if today != rm.LastResetDate {
rm.CurrentDailyLoss = 0
rm.LastResetDate = today
log.Println("Дневной лимит убытков сброшен")
}
if rm.CurrentDailyLoss+riskAmount > rm.MaxDailyLoss {
log.Printf("❌ Риск: превышен дневной лимит убытков (%.2f / %.2f)", rm.CurrentDailyLoss+riskAmount, rm.MaxDailyLoss)
return false
}
return true
}
func (rm *RiskManager) RegisterLoss(loss float64) {
rm.CurrentDailyLoss += loss
log.Printf("⚠️ Зафиксирован убыток %.2f. Дневной убыток: %.2f", loss, rm.CurrentDailyLoss)
}
// order_executor.go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"log"
"net/url"
"time"
"github.com/go-resty/resty/v2"
)
type OrderExecutor struct {
ApiKey string
ApiSecret string
PaperMode bool
client *resty.Client
}
func NewOrderExecutor(apiKey, apiSecret string, paperMode bool) *OrderExecutor {
return &OrderExecutor{
ApiKey: apiKey,
ApiSecret: apiSecret,
PaperMode: paperMode,
client: resty.New(),
}
}
func (oe *OrderExecutor) PlaceOrder(symbol, side string, quantity float64) (string, error) {
if oe.PaperMode {
log.Printf("📝 [PAPER] %s %s %.5f %s", side, symbol, quantity, "по рыночной цене")
return "paper_order_id", nil
}
// Реальная отправка ордера на Binance
timestamp := time.Now().UnixMilli()
params := url.Values{}
params.Set("symbol", symbol)
params.Set("side", side)
params.Set("type", "MARKET")
params.Set("quantity", fmt.Sprintf("%.5f", quantity))
params.Set("timestamp", fmt.Sprintf("%d", timestamp))
queryString := params.Encode()
signature := oe.signRequest(queryString)
resp, err := oe.client.R().
SetHeader("X-MBX-APIKEY", oe.ApiKey).
SetQueryParams(params).
SetQueryParam("signature", signature).
Post("https://api.binance.com/api/v3/order")
if err != nil {
return "", err
}
log.Printf("✅ Реальный ордер отправлен: %s", resp.String())
return resp.String(), nil
}
func (oe *OrderExecutor) signRequest(queryString string) string {
h := hmac.New(sha256.New, []byte(oe.ApiSecret))
h.Write([]byte(queryString))
return hex.EncodeToString(h.Sum(nil))
}
// main.go
package main
import (
"log"
"os"
"time"
"github.com/joho/godotenv"
)
const (
RISK_PER_TRADE = 100.0 // риск на сделку в USDT
MAX_DAILY_LOSS = 500.0 // дневной лимит убытков
SHORT_PERIOD = 10
LONG_PERIOD = 30
)
var currentPosition struct {
Open bool
Side string
Entry float64
Quantity float64
}
func main() {
// Загрузка .env файла
godotenv.Load()
apiKey := os.Getenv("BINANCE_API_KEY")
apiSecret := os.Getenv("BINANCE_API_SECRET")
// Инициализация модулей
riskManager := NewRiskManager(MAX_DAILY_LOSS)
strategy := NewSMACrossoverStrategy(SHORT_PERIOD, LONG_PERIOD)
orderExecutor := NewOrderExecutor(apiKey, apiSecret, true) // true = paper trading
// Подключение к WebSocket
wsClient, err := NewWebSocketClient()
if err != nil {
log.Fatal("Ошибка подключения к WebSocket:", err)
}
log.Println("✅ Бот запущен. Ожидание сигналов...")
wsClient.Listen(func(price float64) {
// Обновляем стратегию новой ценой
strategy.AddPrice(price)
signal := strategy.GetSignal()
log.Printf("Цена: %.2f, Сигнал: %s", price, signal)
// Если нет открытой позиции и сигнал не HOLD
if !currentPosition.Open && signal != "HOLD" {
// Проверяем риск
if !riskManager.CanTrade(RISK_PER_TRADE) {
return
}
// Рассчитываем размер позиции (примерный, фиксированный)
quantity := 0.01 // 0.01 BTC для примера
if signal == "BUY" {
orderExecutor.PlaceOrder("BTCUSDT", "BUY", quantity)
currentPosition.Open = true
currentPosition.Side = "BUY"
currentPosition.Entry = price
currentPosition.Quantity = quantity
} else if signal == "SELL" {
orderExecutor.PlaceOrder("BTCUSDT", "SELL", quantity)
currentPosition.Open = true
currentPosition.Side = "SELL"
currentPosition.Entry = price
currentPosition.Quantity = quantity
}
} else if currentPosition.Open && signal != "HOLD" {
// Если позиция открыта, и сигнал противоположный → закрываем
if (currentPosition.Side == "BUY" && signal == "SELL") ||
(currentPosition.Side == "SELL" && signal == "BUY") {
orderExecutor.PlaceOrder("BTCUSDT", currentPosition.Side == "BUY" ? "SELL" : "BUY", currentPosition.Quantity)
currentPosition.Open = false
}
}
})
}
Добавим простой трейлинг-стоп для защиты прибыли.
type Position struct {
Open bool
Side string
Entry float64
Quantity float64
HighestPrice float64 // для лонга
LowestPrice float64 // для шорта
TrailingStop float64 // процент для стопа (0.02 = 2%)
}
func (p *Position) UpdateTrailing(currentPrice float64) bool {
if p.Side == "BUY" {
if currentPrice > p.HighestPrice {
p.HighestPrice = currentPrice
}
stopPrice := p.HighestPrice * (1 - p.TrailingStop)
if currentPrice <= stopPrice {
return true // стоп сработал
}
} else if p.Side == "SELL" {
if currentPrice < p.LowestPrice {
p.LowestPrice = currentPrice
}
stopPrice := p.LowestPrice * (1 + p.TrailingStop)
if currentPrice >= stopPrice {
return true
}
}
return false
}
BINANCE_API_KEY=ваш_api_ключ
BINANCE_API_SECRET=ваш_секретный_ключ
go run .
go build -o trading-bot .
./trading-bot
Вы создали каркас профессионального торгового бота на Go. Он получает котировки в реальном времени через WebSocket, рассчитывает индикаторы, генерирует сигналы и отправляет ордеры. Этот бот может работать неделями без перезагрузки, обрабатывая тысячи тиков в секунду. Go даёт вам производительность C++ и безопасность типов, не жертвуя читаемостью.
Конечно, бота нужно дорабатывать: добавлять обработку ошибок, логирование, стоп-лоссы, несколько торговых пар. Но фундамент заложен. И теперь вы можете развивать его в любую сторону.
Запустите бота в режиме симуляции на демо-счёте, понаблюдайте за его работой. Затем постепенно добавляйте функционал. И когда почувствуете уверенность — переходите на реальные деньги, начиная с минимальных сумм.
И помните: даже самый быстрый бот не спасёт от плохой стратегии. Но он даст вам инструмент, который позволит тестировать идеи быстрее, чем конкуренты. А в трейдинге скорость итераций — это половина успеха.
«Go — это язык, который позволяет вашему боту быть одновременно быстрым, как гепард, и надёжным, как швейцарские часы. А горутины делают параллельную обработку данных простой, как приготовление кофе».
Дата размещения статьи: 07-06-2026 в 12:50:04