🤖 Пишем торгового бота на C++: от идеи до live-торговли


На главную > Блог > Категория > 🤖 Пишем торгового бота на C++: от идеи до live-торговли

bot_c++

Вступление: почему C++ — лучший выбор для серьёзного бота

Python хорош для прототипов. JavaScript — для веба. Но когда речь заходит о торговом боте, который должен работать 24/7 без зависаний, обрабатывать тысячи ордеров в секунду и не «тормозить» на тиковых данных — выбор профессионалов C++. В этой статье я покажу, как создать полноценного торгового бота на C++ с нуля: от получения котировок через WebSocket до отправки ордеров и управления рисками.

Вы научитесь:

  • 📡 Подключаться к бирже (Binance) через WebSocket
  • 📊 Рассчитывать технические индикаторы (RSI, SMA) в реальном времени
  • 🤖 Принимать торговые решения на основе сигналов
  • 💸 Отправлять ордера и управлять позициями
  • 🛡️ Добавлять риск-менеджмент и логирование
«Бот на C++ — это как швейцарские часы: тикает без остановки, не зависает, не тормозит. И делает это годами».

1. Архитектура торгового бота (минимальный набор)

Хороший бот строится из независимых модулей. Это упрощает отладку и расширение.

МодульЗадачаТехнологии/библиотеки
Market Data (получение данных) Подключение к WebSocket биржи, парсинг тиков и свечей Boost.Beast, WebSocket++
Strategy Engine (стратегия) Расчёт индикаторов, генерация сигналов (BUY/SELL/HOLD) Собственный код + библиотека ta-lib (опционально)
Risk Manager (риск-менеджмент) Проверка ордеров перед отправкой (размер лота, дневной лимит) Собственная логика
Order Executor (исполнение) Отправка ордеров через REST API биржи cpprestsdk, libcurl
Logger (логирование) Запись действий и ошибок в файл spdlog

2. Настройка проекта и необходимые библиотеки

Что нужно установить:

  • CMake — сборка проекта
  • vcpkg (Windows) или apt-get (Linux) — установка библиотек
  • Boost (включая Beast для WebSocket)
  • nlohmann/json — парсинг JSON
  • spdlog — логирование
  • libcurl — для REST запросов к API биржи

Пример CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(TradingBot)

set(CMAKE_CXX_STANDARD 20)

find_package(Boost REQUIRED COMPONENTS system)
find_package(nlohmann_json REQUIRED)
find_package(spdlog REQUIRED)
find_package(CURL REQUIRED)

add_executable(trading_bot main.cpp websocket_client.cpp strategy.cpp risk_manager.cpp order_executor.cpp)

target_link_libraries(trading_bot
    Boost::system
    nlohmann_json::nlohmann_json
    spdlog::spdlog
    CURL::libcurl
    pthread
)

3. Подключение к WebSocket Binance (рыночные данные)

Начнём с модуля получения данных. Подключимся к WebSocket Binance и будем получать тики в реальном времени.


#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <iostream>
#include <string>

using json = nlohmann::json;
namespace beast = boost::beast;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;

class WebSocketClient {
private:
    net::io_context ioc;
    websocket::stream<tcp::socket> ws;
    std::string host = "stream.binance.com";
    std::string port = "9443";
    std::string endpoint = "/ws/btcusdt@trade";
    
public:
    WebSocketClient() : ws(ioc) {}
    
    bool connect() {
        try {
            tcp::resolver resolver(ioc);
            auto const results = resolver.resolve(host, port);
            net::connect(ws.next_layer(), results.begin(), results.end());
            ws.handshake(host, endpoint);
            spdlog::info("WebSocket подключен к {}", host);
            return true;
        } catch (std::exception const& e) {
            spdlog::error("Ошибка подключения: {}", e.what());
            return false;
        }
    }
    
    void listen(std::function<void(double price, double volume)> onTrade) {
        beast::flat_buffer buffer;
        while (true) {
            ws.read(buffer);
            auto data = beast::buffers_to_string(buffer.data());
            buffer.consume(buffer.size());
            
            try {
                json j = json::parse(data);
                if (j.contains("p") && j.contains("q")) {
                    double price = j["p"].get<double>();
                    double volume = j["q"].get<double>();
                    onTrade(price, volume);
                }
            } catch (json::parse_error& e) {
                spdlog::error("Ошибка парсинга JSON: {}", e.what());
            }
        }
    }
};

4. Стратегия: пересечение скользящих средних (SMA)

Реализуем простую, но рабочую стратегию: покупаем, когда быстрая SMA пересекает медленную снизу вверх; продаём — при пересечении сверху вниз.


#include <deque>
#include <numeric>

class SMACrossoverStrategy {
private:
    std::deque<double> prices;
    int shortPeriod = 10;
    int longPeriod = 30;
    int lastSignal = 0; // 1 - buy, -1 - sell, 0 - hold
    
    double calculateSMA(const std::deque<double>& data, int period) {
        if (data.size() < period) return 0.0;
        double sum = std::accumulate(data.end() - period, data.end(), 0.0);
        return sum / period;
    }
    
public:
    int onPrice(double price) {
        prices.push_back(price);
        if (prices.size() > longPeriod) {
            prices.pop_front();
        }
        
        if (prices.size() < longPeriod) return 0;
        
        double shortSMA = calculateSMA(prices, shortPeriod);
        double longSMA = calculateSMA(prices, longPeriod);
        
        // Сигнал на покупку
        if (shortSMA > longSMA && lastSignal != 1) {
            lastSignal = 1;
            spdlog::info("SIGNAL: BUY at price {}", price);
            return 1;
        }
        // Сигнал на продажу
        else if (shortSMA < longSMA && lastSignal != -1) {
            lastSignal = -1;
            spdlog::info("SIGNAL: SELL at price {}", price);
            return -1;
        }
        
        return 0;
    }
};

5. Отправка ордеров через REST API (Binance)

Для отправки ордеров используем libcurl. Потребуются API-ключи с разрешением на торговлю.


#include <curl/curl.h>
#include <string>
#include <openssl/hmac.h>

class OrderExecutor {
private:
    std::string apiKey;
    std::string apiSecret;
    std::string baseUrl = "https://api.binance.com";
    
    std::string signRequest(const std::string& queryString) {
        unsigned char* digest;
        digest = HMAC(EVP_sha256(), apiSecret.c_str(), apiSecret.length(),
                     (unsigned char*)queryString.c_str(), queryString.length(), NULL, NULL);
        return std::string((char*)digest);
    }
    
    static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
        ((std::string*)userp)->append((char*)contents, size * nmemb);
        return size * nmemb;
    }
    
public:
    OrderExecutor(const std::string& key, const std::string& secret) 
        : apiKey(key), apiSecret(secret) {}
    
    bool placeOrder(const std::string& symbol, const std::string& side, double quantity) {
        CURL* curl = curl_easy_init();
        if (!curl) return false;
        
        long timestamp = std::time(nullptr) * 1000;
        std::string query = "symbol=" + symbol + "&side=" + side + 
                            "&type=MARKET&quantity=" + std::to_string(quantity) +
                            "&timestamp=" + std::to_string(timestamp);
        
        std::string signature = signRequest(query);
        std::string url = baseUrl + "/api/v3/order?" + query + "&signature=" + signature;
        
        struct curl_slist* headers = nullptr;
        headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
        
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POST, 1L);
        
        std::string response;
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
        
        CURLcode res = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        curl_slist_free_all(headers);
        
        if (res == CURLE_OK) {
            spdlog::info("Order placed: {} {} {}", symbol, side, quantity);
            return true;
        } else {
            spdlog::error("Order failed: {}", response);
            return false;
        }
    }
};

6. Риск-менеджмент: защита от потерь

Самый важный модуль — он не даст боту слить депозит.

class RiskManager {
private:
    double maxPositionSize = 1000.0;   // максимальный размер позиции в $
    double dailyLossLimit = 500.0;     // дневной лимит убытков
    double dailyLoss = 0.0;
    double maxLeverage = 3.0;          // максимальное плечо
    
public:
    bool canOpenPosition(double positionSize, double leverage) {
        if (positionSize > maxPositionSize) {
            spdlog::warn("Risk: position size {} exceeds max {}", positionSize, maxPositionSize);
            return false;
        }
        if (leverage > maxLeverage) {
            spdlog::warn("Risk: leverage {} exceeds max {}", leverage, maxLeverage);
            return false;
        }
        return true;
    }
    
    void updateDailyLoss(double loss) {
        dailyLoss += loss;
        if (dailyLoss > dailyLossLimit) {
            spdlog::error("Risk: Daily loss limit reached! Stopping bot.");
            // Здесь можно отправить уведомление и остановить бота
        }
    }
    
    void resetDaily() {
        dailyLoss = 0.0;
    }
};

7. Логирование с spdlog

Логирование критически важно: вы должны знать, что бот делал и почему.


#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>

void initLogger() {
    auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
        "logs/trading_bot.log", 1024 * 1024 * 10, 3);
    
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    
    std::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};
    auto logger = std::make_shared<spdlog::logger>("bot", sinks.begin(), sinks.end());
    
    spdlog::set_default_logger(logger);
    spdlog::set_level(spdlog::level::info);
    spdlog::info("Logger initialized");
}

8. Главный цикл: собираем всё вместе


int main() {
    initLogger();
    spdlog::info("Starting trading bot...");
    
    // 1. Вебсокет для получения данных
    WebSocketClient wsClient;
    if (!wsClient.connect()) {
        spdlog::error("Failed to connect to Binance WebSocket");
        return 1;
    }
    
    // 2. Стратегия
    SMACrossoverStrategy strategy;
    
    // 3. Риск-менеджмент
    RiskManager riskManager;
    
    // 4. Исполнение ордеров (нужны реальные ключи!)
    // OrderExecutor executor("YOUR_API_KEY", "YOUR_SECRET_KEY");
    
    bool hasPosition = false;
    double entryPrice = 0.0;
    
    // 5. Слушаем WebSocket
    wsClient.listen([&](double price, double volume) {
        int signal = strategy.onPrice(price);
        
        if (signal == 1 && !hasPosition) {
            // Сигнал на покупку
            double positionSize = 100.0; // $100
            if (riskManager.canOpenPosition(positionSize, 1.0)) {
                spdlog::info("BUY signal executed at {}", price);
                // executor.placeOrder("BTCUSDT", "BUY", positionSize / price);
                hasPosition = true;
                entryPrice = price;
            }
        }
        else if (signal == -1 && hasPosition) {
            // Сигнал на продажу
            double profit = (price - entryPrice) / entryPrice * positionSize;
            spdlog::info("SELL signal at {}, PnL: ${:.2f}", price, profit);
            // executor.placeOrder("BTCUSDT", "SELL", positionSize / price);
            hasPosition = false;
            
            if (profit < 0) {
                riskManager.updateDailyLoss(-profit);
            }
        }
    });
    
    return 0;
}

9. Сборка и запуск

Сборка (пример для Linux):


mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg.cmake
make
./trading_bot
⚠️ Перед запуском на реальные деньги:
  • Протестируйте бота на демо-счёте (Binance Testnet)
  • Проверьте обработку ошибок (разрыв соединения, таймауты)
  • Протестируйте на исторических данных (бэктест)
  • Начните с минимальной суммы ($10-50)
  • Никогда не храните API-ключи в коде — используйте файл .env или переменные окружения

10. Как улучшить бота (следующие шаги)

  • 🔹 Асинхронный ввод-вывод — использовать Boost.Asio для всех операций
  • 🔹 Поддержка нескольких торговых пар — многопоточность + std::unordered_map
  • 🔹 Состояние бота (Journaling) — запись всех сделок в базу данных (SQLite)
  • 🔹 Web-интерфейс для мониторинга — REST API на Crow или Drogon
  • 🔹 Уведомления в Telegram — отправка сообщений о сделках и ошибках
  • 🔹 Backtesting движок — тот же код стратегии, но с историческими данными из CSV

Заключение: ваш первый C++ бот готов

Мы создали каркас полноценного торгового бота на C++. Он получает данные через WebSocket, вычисляет индикаторы, принимает решения, отправляет ордеры и управляет рисками. Это уже не игрушка, а основа для серьёзной торговой системы.

Дальше — дело за вами. Добавляйте новые индикаторы, оптимизируйте производительность, подключайтесь к разным биржам. C++ даёт вам полный контроль над железом и скоростью. В руках профессионала этот бот может стать настоящей «печатной машиной».

Но помните: даже самый совершенный бот не заменит здравого смысла и дисциплины. Всегда тестируйте на демо-счёте, используйте стоп-лоссы и не рискуйте больше, чем готовы потерять.

«Хороший бот не делает миллион за день. Он делает 0.5% каждый день, но годами. И именно так становятся миллионерами».

 

Дата размещения статьи: 2026-05-29T04:39:00