На главную > Блог > Категория > 🤖 Пишем торгового бота на C++: от идеи до live-торговли
Python хорош для прототипов. JavaScript — для веба. Но когда речь заходит о торговом боте, который должен работать 24/7 без зависаний, обрабатывать тысячи ордеров в секунду и не «тормозить» на тиковых данных — выбор профессионалов C++. В этой статье я покажу, как создать полноценного торгового бота на C++ с нуля: от получения котировок через WebSocket до отправки ордеров и управления рисками.
Вы научитесь:
«Бот на C++ — это как швейцарские часы: тикает без остановки, не зависает, не тормозит. И делает это годами».
Хороший бот строится из независимых модулей. Это упрощает отладку и расширение.
| Модуль | Задача | Технологии/библиотеки |
|---|---|---|
| Market Data (получение данных) | Подключение к WebSocket биржи, парсинг тиков и свечей | Boost.Beast, WebSocket++ |
| Strategy Engine (стратегия) | Расчёт индикаторов, генерация сигналов (BUY/SELL/HOLD) | Собственный код + библиотека ta-lib (опционально) |
| Risk Manager (риск-менеджмент) | Проверка ордеров перед отправкой (размер лота, дневной лимит) | Собственная логика |
| Order Executor (исполнение) | Отправка ордеров через REST API биржи | cpprestsdk, libcurl |
| Logger (логирование) | Запись действий и ошибок в файл | spdlog |
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
)
Начнём с модуля получения данных. Подключимся к 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());
}
}
}
};
Реализуем простую, но рабочую стратегию: покупаем, когда быстрая 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;
}
};
Для отправки ордеров используем 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) +
"×tamp=" + 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;
}
}
};
Самый важный модуль — он не даст боту слить депозит.
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;
}
};
Логирование критически важно: вы должны знать, что бот делал и почему.
#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");
}
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;
}
mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg.cmake
make
./trading_bot
Мы создали каркас полноценного торгового бота на C++. Он получает данные через WebSocket, вычисляет индикаторы, принимает решения, отправляет ордеры и управляет рисками. Это уже не игрушка, а основа для серьёзной торговой системы.
Дальше — дело за вами. Добавляйте новые индикаторы, оптимизируйте производительность, подключайтесь к разным биржам. C++ даёт вам полный контроль над железом и скоростью. В руках профессионала этот бот может стать настоящей «печатной машиной».
Но помните: даже самый совершенный бот не заменит здравого смысла и дисциплины. Всегда тестируйте на демо-счёте, используйте стоп-лоссы и не рискуйте больше, чем готовы потерять.
«Хороший бот не делает миллион за день. Он делает 0.5% каждый день, но годами. И именно так становятся миллионерами».
Дата размещения статьи: 2026-05-29T04:39:00