Как подключить LLM к советнику MQL5 через Python-сервер
Каждый, кто хоть раз пробовал прикрутить большую языковую модель к MetaTrader 5, рано или поздно натыкается на одну и ту же стену. Хочется, чтобы AI в реальном времени смотрел на график, считал индикаторы, видел паттерны и выдавал осмысленные сигналы — не раз в пять минут, а по-настоящему, на портфеле из 8–10 инструментов сразу. Чтобы задержка была 3–7 секунд максимум, чтобы расходы на API не съедали депозит за неделю, чтобы ключ от OpenRouter или Anthropic не торчал в коде советника и не светился при любой утечке.
А на деле всё ломается о три вещи разом. Во-первых, MQL5 — это не Python: ни нормального WebSocket из коробки, ни асинхронности, ни потоков — один долгий запрос, и весь советник висит, пока ждёт ответа от модели. Во-вторых, бесплатные API моментально упираются в лимиты: 10–20 запросов в минуту — и на M15 по восьми парам ты уже в 429 через полчаса. Платные тарифы решают проблему, но ценник взлетает до небес. В-третьих, куда девать API-ключ? Вшить в код — значит рано или поздно он уплывёт. Передать через параметры — чуть лучше, но всё равно небезопасно.
В итоге красивая идея тонет в техническом болоте: либо демо-скрипт на одной паре, либо блокирующий монстр, либо разорённый бюджет, либо скомпрометированный ключ. Нужна другая архитектура — такая, чтобы MetaTrader 5 оставался просто транспортом данных, а вся тяжёлая работа, вся логика и все секреты жили снаружи.
Python-сервер как мост между мирами
Ключевое архитектурное решение системы Shtenco AI V17 простое: советник MetaTrader 5 не должен знать ничего про AI. Его единственная задача — собирать рыночные данные и отправлять их по сети в нужном формате. Вся AI-логика целиком вынесена в Python-сервер, который работает на той же машине по адресу 127.0.0.1 или в локальной сети. Это разделение решает все три проблемы одновременно.
Советник больше не знает, к какому именно AI он обращается. Он не знает, какая модель обрабатывает его данные, сколько она стоит и где физически находится. Он знает только адрес сервера и протокол обмена. Это означает, что сегодня можно использовать одну модель, завтра — другую, и советник об этом даже не узнает. Достаточно изменить одну строку в Python-скрипте.
MetaTrader 5 → WebSocket (порт 8989) → Python Server Python Server → HTTPS POST → OpenRouter API OpenRouter → LLM (stepfun/step-3.5-flash) LLM → {"signal":"buy","comment":"..."} Python Server → WebSocket → MT5 MT5 → Торговое решение → Рынок
Python-сервер — это единственное место в системе, где хранится API-ключ. Он живёт в одном файле на одной машине и никогда не передаётся советнику. Советник не может его прочитать, скопировать или случайно опубликовать.
Почему OpenRouter, а не прямые API провайдеров
OpenRouter — это агрегатор LLM-провайдеров с единым API-интерфейсом к сотням моделей. Вместо того чтобы иметь отдельные ключи для OpenAI, Anthropic, Google и Mistral — один ключ и доступ ко всему «зоопарку» моделей по конкурентным ценам. Но главное преимущество не в удобстве, а в экономике: через OpenRouter можно получить доступ к платным моделям, у которых нет жёстких rate limits. Оплата происходит по факту потреблённых токенов — никаких ежемесячных подписок и никаких искусственных ограничений на количество запросов.
В системе используется модель stepfun/step-3.5-flash китайского провайдера StepFun. Выбор обусловлен несколькими практическими соображениями. Модель быстро отвечает — обычно от 2 до 5 секунд даже на промпт с полным набором из 15 индикаторов. Она хорошо работает с числовыми рядами и паттернами, что критично для задачи технического анализа. Её стоимость существенно ниже GPT-4 при сопоставимом качестве именно для этой конкретной задачи.
# Поменять одну строку — и работаем с любой другой моделью MODEL = "stepfun/step-3.5-flash" OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions" OPENROUTER_API_KEY = "sk-or-v1-..." # ключ только здесь, советник его никогда не видит HEADERS = { "Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json", "HTTP-Referer": "https://mt5-ai-advisor.local", "X-Title": "MT5 Step35Flash Advisor", }
Заголовок HTTP-Referer — это не реальный URL сайта, а произвольный идентификатор приложения для OpenRouter. X-Title отображается в статистике расходов на дашборде, что удобно для отслеживания трат по разным проектам.
WebSocket-протокол: рукопожатие вручную без зависимостей
MetaTrader 5 поддерживает WebSocket через библиотеку WinHTTP — стандартный Windows API, который есть на любой версии системы. Советник при инициализации сначала пробует установить WebSocket-соединение. Если по какой-то причине это не удаётся — он автоматически переходит на обычный TCP Socket, который является fallback-вариантом для совместимости с разными конфигурациями сети.
//+------------------------------------------------------------------+ //| Initialization | //+------------------------------------------------------------------+ int OnInit() { //--- Attempt 1: WinHTTP WebSocket (preferred) if(InitWebSocket()) { g_useWS = true; Print("WinHTTP WebSocket connected"); } else { //--- Attempt 2: fallback to plain TCP Socket g_socket = SocketCreate(); if(g_socket != INVALID_HANDLE && SocketConnect(g_socket, InpHost, InpPort, 3000)) { Print("Socket connected"); } else { if(g_socket != INVALID_HANDLE) { SocketClose(g_socket); g_socket = INVALID_HANDLE; } Print("No connection! Launch Shtenco_AI_V17_SERVER.py"); return(INIT_FAILED); } } return(INIT_SUCCEEDED); }
Вся красота этого решения в том, что функции SendRaw() и ReceiveRaw() автоматически выбирают нужный транспорт на основе флага g_useWS. Весь остальной код советника — сбор данных, формирование запроса, парсинг ответа — не знает и не должен знать, через какой именно протокол идёт общение с сервером. Это классический принцип разделения уровней абстракции.
Как сервер выполняет рукопожатие WebSocket
Python-сервер реализует WebSocket-протокол полностью вручную, без каких-либо сторонних библиотек типа websockets или aiohttp. Это принципиальное архитектурное решение: нулевые зависимости при развёртывании. Для запуска сервера нужны только Python 3.8 и два пакета — numpy и requests.
WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" # RFC 6455 magic constant def ws_handshake_response(http_request: str) -> str: key = "" for line in http_request.split("\r\n"): if "Sec-WebSocket-Key" in line: key = line.split(": ")[1].strip() break # SHA-1 of (client key + magic string), encode to base64 accept = base64.b64encode( hashlib.sha1((key + WS_GUID).encode("utf-8")).digest() ).decode("utf-8") return ( "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" f"Sec-WebSocket-Accept: {accept}\r\n\r\n" )
Строка WS_GUID = "258EAFA5-..." — это магическая константа из стандарта RFC 6455, одна и та же для всех WebSocket-серверов в мире без исключения. Именно по ней браузеры и клиенты проверяют подлинность ответа сервера — без правильного вычисления этого значения любой клиент немедленно разорвёт соединение. После рукопожатия сервер использует функции ws_decode() и ws_encode() — около 30 строк кода, обеспечивающих полную совместимость с WinHTTP в MetaTrader 5.
Технические индикаторы: NumPy вместо TA-Lib
Почему сервер считает индикаторы, а не советник
Это архитектурное решение удивляет многих разработчиков. Советник не просто передаёт сигнал — он формирует промпт для языковой модели. А хороший промпт должен содержать готовые числовые данные в структурированном виде. Советник — тупой транспорт, сервер — умная голова. Из сторонних библиотек используется только numpy. Никакого TA-Lib — пакета с печально известными проблемами при установке на разных операционных системах.
RSI, EMA, ATR, Bollinger — реализация с нуляdef calc_rsi(arr: np.ndarray, period: int = 14) -> float: if len(arr) < period + 1: return 50.0 # neutral value on insufficient data deltas = np.diff(arr) gains = np.where(deltas > 0, deltas, 0.0) losses = np.where(deltas < 0, -deltas, 0.0) avg_gain = np.mean(gains[-period:]) avg_loss = np.mean(losses[-period:]) if avg_loss == 0: return 100.0 rs = avg_gain / avg_loss return round(100.0 - 100.0 / (1.0 + rs), 2) def calc_ema(arr: np.ndarray, period: int) -> float: if len(arr) < 2: return float(arr[-1]) k = 2.0 / (period + 1) # classic smoothing coefficient ema = float(arr[0]) for v in arr[1:]: ema = float(v) * k + ema * (1 - k) return ema def calc_atr(high, low, close, period: int = 14) -> float: tr_list = [] for i in range(1, len(close)): tr = max( high[i] - low[i], abs(high[i] - close[i-1]), abs(low[i] - close[i-1]) ) tr_list.append(tr) return round(float(np.mean(np.array(tr_list)[-period:])), 6) def calc_bb(arr, period: int = 20, mult: float = 2.0): ma = calc_ma(arr, period) std = calc_stddev(arr, period) return round(ma + mult * std, 5), round(ma, 5), round(ma - mult * std, 5)
На каждый запрос сервер вычисляет: MA за периоды 5, 10, 20, 50, 100 и 200 баров с определением тренда UP/DOWN для каждой; EMA за периоды 9, 21 и 55; RSI сразу за три периода — 7, 14 и 21 бар; Stochastic %K и %D; ATR за 14 и 21 период; стандартное отклонение за 10 и 20 периодов; Bollinger Bands с позицией цены относительно полос; моментум за 5, 10 и 20 баров. Всё это попадает в промпт в виде структурированного текстового отчёта.
Формирование промпта: язык трейдера для AI
Промпт — это сердце системы. Плохой промпт даст бессмысленный сигнал даже от самой мощной модели. В Shtenco AI V17 промпт устроен как профессиональный технический брифинг.
prompt = ( f"Symbol: {symbol} | Bars: {ind['n']}\n\n" f"Last candle: {ind['candle']}\n" f"Volume: {ind['volume']}\n\n" f"Moving averages:\n {ind['ma']}\n {ind['ema']}\n\n" f"Oscillators:\n {ind['rsi']}\n Stoch {ind['stoch']}\n\n" f"Volatility:\n {ind['atr']}\n {ind['stddev']}\n\n" f"Bollinger Bands: {ind['bb']}\n" f"Momentum: {ind['momentum']}\n\n" "Give a trading signal. Reply ONLY with JSON, no markdown:\n" '{"signal":"buy"|"sell"|"hold","comment":"analysis up to 150 chars"}' )
Реальный промпт для EURUSD в конкретный момент времени выглядит так:
Symbol: EURUSD | Bars: 60 Last candle: BULL O=1.08412 H=1.08456 L=1.08389 C=1.08441 Body=0.00029 Volume: Last=1243 Avg20=987 Ratio=1.26x Moving averages: MA5=1.08398 MA10=1.08371 MA20=1.08312(UP) MA50=1.08145(UP) MA200=1.07891(UP) EMA9=1.08407 EMA21=1.08344 EMA55=1.08201 Oscillators: RSI7=64.2 RSI14=58.7 RSI21=55.1 Stoch K=71.3 D=65.8 Volatility: ATR14=0.00089 ATR21=0.00094 Bollinger Bands: BB_up=1.08578 BB_mid=1.08312 BB_lo=1.08046 Pos=MID Momentum: Mom5=+0.00043 Mom10=+0.00156 Mom20=+0.00312 Give a trading signal. Reply ONLY with JSON, no markdown: {"signal":"buy"|"sell"|"hold","comment":"analysis up to 150 chars"}
Системный промпт задаёт роль: «Ты — профессиональный трейдер и аналитик финансовых рынков. Анализируй данные ценового ряда и давай чёткие торговые сигналы. Отвечай кратко и по делу.» Параметр temperature установлен на 0.3 . Это принципиально важный выбор — при значениях 0.8–1.0 модель начинает «фантазировать» и может давать разные сигналы на абсолютно одинаковых данных. Значение 0.3 делает поведение детерминированным и предсказуемым, что для торговой системы несравнимо важнее «креативности».
PRICES и OHLCV — от простого к полному
Самая простая команда передаёт символ и CSV-строку с ценами закрытия в хронологическом порядке — от старых к новым. Хронологический порядок выбран не случайно: LLM обучены на текстах, где события описываются в хронологии. «Вчера было вот так, сегодня утром вот так, сейчас вот так» — эта нарративная структура даёт лучшее качество анализа, чем обратный порядок, который MetaTrader использует внутри для своих нужд.
double prices[]; ArraySetAsSeries(prices, true); int copied = CopyClose(sym, PERIOD_CURRENT, 0, InpPriceBars, prices); string csv = ""; for(int j = copied - 1; j >= 0; j--) { csv += DoubleToString(prices[j], digits); if(j > 0) csv += ","; } string cmd = "PRICES:" + sym + ":" + csv; string response = SendCommand(cmd);
Расширенный режим OHLCV передаёт полные данные свечей через символ | в качестве разделителя между компонентами. Это даёт серверу всё необходимое для точного расчёта ATR, Stochastic и анализа свечных паттернов по телу и теням. Объём передаваемых данных при 60 барах увеличивается примерно в пять раз, но качество технического контекста, который получает AI, существенно возрастает.
BATCH — параллельный анализ всех 8 пар за один запрос
Команда BATCH — это самое мощное, что есть в протоколе. Одним сетевым запросом советник передаёт данные всех 8 пар, разделённых точкой с запятой. Сервер разбирает этот пакет, создаёт задачи и запускает их параллельно через ThreadPoolExecutor с 8 воркерами. Все 8 запросов к OpenRouter API летят одновременно, каждый в своём потоке.
if message.upper().startswith("BATCH:"): entries = message[6:].split(";") tasks = [] for entry in entries: parts = entry.split(":", 1) sym, csv = parts[0].strip(), parts[1].strip() prices = [float(x) for x in csv.split(",")] tasks.append({"symbol": sym, "close": prices}) batch_result = {} with ThreadPoolExecutor(max_workers=8) as pool: futures = {pool.submit(analyze_one, t): t["symbol"] for t in tasks} for fut in as_completed(futures): sym, res = fut.result() batch_result[sym] = res conn.sendall(ws_encode(json.dumps(batch_result, ensure_ascii=False)))
Практический эффект от этого решения трудно переоценить. Если один запрос к AI занимает 3 секунды, то анализ 8 пар займёт не 24 секунды — он займёт те же 3–4 секунды, потому что все запросы выполняются параллельно. Именно это делает реальную торговлю на портфеле из 8 пар практически возможной, а не просто теоретически интересной задачей.
CHAT — диалог с AI в свободной форме
Необычная для торгового советника функция: режим чата с сохранением истории диалога. Трейдер может задать вопрос в свободной форме — «Что происходит с долларом сегодня?» или «Почему ты дал сигнал sell по GBPUSD, когда RSI был ещё в нейтральной зоне?» — и получить развёрнутый ответ. История диалога сохраняется до 20 последних сообщений, что позволяет вести связный разговор. Команда CLEAR сбрасывает историю — полезно когда нужно начать новый анализ без влияния предыдущих обсуждений.
def ask_deepseek(user_message: str, system_prompt: str = None) -> str: messages = [{"role": "system", "content": system_prompt}] with history_lock: # мьютекс для потокобезопасности messages += chat_history[-MAX_HISTORY:] messages.append({"role": "user", "content": user_message}) # ... отправка запроса и сохранение ответа в историю
history_lock = threading.Lock() здесь не опциональная деталь реализации. Без мьютекса несколько воркеров ThreadPoolExecutor , выполняющих параллельные запросы по разным парам, могут одновременно читать и писать в общий список chat_history , создавая гонку данных и непредсказуемые ошибки. Мьютекс гарантирует атомарность всех операций с историей — это стандартная практика при работе с разделяемым состоянием в многопоточном коде.
Советник MQL5: архитектура данных и цикл обработки
Советник использует параллельные массивы для хранения состояния по каждой паре. Этот классический C-паттерн в MQL5 работает эффективнее объектно-ориентированных подходов для небольшого фиксированного набора данных. Индекс 0 всегда соответствует EURUSD, индекс 1 — GBPUSD, и так далее. Все массивы имеют одинаковый размер PAIRS_COUNT = 8 , и любая операция с парой выглядит как обращение к элементу по её индексу.
string g_pairs [PAIRS_COUNT]; // символы пар datetime g_lastBar [PAIRS_COUNT]; // время последнего обработанного бара int g_barCnt [PAIRS_COUNT]; // счётчик баров для параметра InpAnalysisBars string g_signal [PAIRS_COUNT]; // последний сигнал AI: "buy" / "sell" / "hold" string g_comment [PAIRS_COUNT]; // текстовое обоснование от AI int g_buys [PAIRS_COUNT]; // статистика открытых BUY за сессию int g_sells [PAIRS_COUNT]; // статистика открытых SELL за сессию
OnTick: фильтр новых баров и отправка запросов
Функция OnTick() вызывается на каждое изменение цены — потенциально тысячи раз в час. Отправлять запрос к AI на каждый тик было бы бессмысленно и катастрофически дорого. Поэтому советник использует два уровня фильтрации: сначала проверяет, появился ли новый бар по данному символу, затем проверяет, кратен ли счётчик баров параметру InpAnalysisBars . Только если оба условия выполнены — данные идут на анализ.
//+------------------------------------------------------------------+ //| Tick handler | //+------------------------------------------------------------------+ void OnTick() { for(int i = 0; i < PAIRS_COUNT; i++) { string sym = g_pairs[i]; if(StringLen(sym) == 0) continue; //--- Filter 1: check for new bar datetime barTime = iTime(sym, PERIOD_CURRENT, 0); if(barTime == 0 || barTime == g_lastBar[i]) continue; g_lastBar[i] = barTime; //--- Filter 2: analyze every N-th bar to save API tokens g_barCnt[i]++; if(g_barCnt[i] % InpAnalysisBars != 0) continue; //--- Collect prices and send for analysis double prices[]; ArraySetAsSeries(prices, true); CopyClose(sym, PERIOD_CURRENT, 0, InpPriceBars, prices); string cmd = "PRICES:" + sym + ":" + csv; string response = SendCommand(cmd); g_signal[i] = ParseJson(response, "signal"); g_comment[i] = ParseJson(response, "comment"); } }
Параметр InpAnalysisBars = 3 означает «запрашивать AI каждые 3 новых бара». На таймфрейме M15 при InpAnalysisBars = 1 это 96 запросов в день на одну пару и 768 запросов в день на все восемь. При InpAnalysisBars = 4 — 192 запроса суммарно. Этот параметр является главным рычагом управления расходами на API без какого-либо изменения логики системы.
OpenBuy и OpenSell с правильным расчётом уровней
После получения сигнала советник принимает торговое решение. Логика намеренно проста и прозрачна: при сигнале BUY сначала закрываются все SELL-позиции по этому символу, если включён параметр InpCloseOpposite , затем открывается BUY — но только если такой позиции ещё нет. Проверка на существование позиции защищает от дублирования сделок в случаях, когда советник получил одинаковый сигнал на нескольких последовательных барах.
Использование SYMBOL_POINT вместо константы 0.0001 — не стилистический выбор, а техническая необходимость. USDJPY имеет 2–3 знака после запятой, XAUUSD работает по своей шкале, некоторые брокеры предоставляют нестандартные инструменты. Универсальный код через SYMBOL_POINT и SYMBOL_DIGITS работает правильно на любом символе без ручных корректировок. NormalizeDouble с правильным числом десятичных знаков критичен — без него брокерский сервер вернёт ошибку неправильного формата цены.
Параметр InpCloseOpposite заслуживает отдельного объяснения. При значении true система работает в режиме разворота: при смене сигнала с buy на sell старая позиция закрывается и открывается новая в противоположном направлении. При false система может одновременно держать BUY и SELL по одной паре — так называемый лок. Второй режим полезен для стратегий хеджирования, но требует дополнительной логики управления совокупным риском позиций.
//+------------------------------------------------------------------+ //| Open BUY position | //+------------------------------------------------------------------+ void OpenBuy(string sym, int idx, string cmt) { double ask = SymbolInfoDouble(sym, SYMBOL_ASK); double point = SymbolInfoDouble(sym, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); double sl = (InpSL_Points > 0) ? NormalizeDouble(ask - InpSL_Points * point, digits) : 0; double tp = (InpTP_Points > 0) ? NormalizeDouble(ask + InpTP_Points * point, digits) : 0; if(Trade.Buy(InpLotSize, sym, ask, sl, tp, "ShtAI BUY")) g_buys[idx]++; }
Функция ParseJson и двойная защита от нестандартных ответов
MQL5 не имеет встроенного JSON-парсера. Написание собственного может показаться излишним усложнением, но на практике это несколько десятков строк кода, которые работают стабильнее, чем любая сторонняя библиотека в экзотической среде MetaTrader. Функция обрабатывает строковые значения в кавычках, числовые значения без кавычек и escape-последовательности внутри строк — всё это встречается в реальных ответах языковых моделей.
//+------------------------------------------------------------------+ //| Parse JSON value by key | //+------------------------------------------------------------------+ string ParseJson(string json, string key) { string search = "\"" + key + "\""; int pos = StringFind(json, search); if(pos < 0) return(json); pos = StringFind(json, ":", pos); pos++; while(pos < StringLen(json) && StringSubstr(json, pos, 1) == " ") pos++; if(StringSubstr(json, pos, 1) == "\"") { pos++; string val = ""; while(pos < StringLen(json)) { string ch = StringSubstr(json, pos, 1); if(ch == "\"") break; if(ch == "\\" && pos + 1 < StringLen(json)) { pos++; ch = StringSubstr(json, pos, 1); } val += ch; pos++; } return(val); } string val = ""; while(pos < StringLen(json)) { string ch = StringSubstr(json, pos, 1); if(ch == "," || ch == "}" || ch == "]") break; val += ch; pos++; } return(val); }
Кроме парсера в MQL5, в Python-сервере есть запасной механизм защиты. Иногда языковая модель возвращает JSON с лишними пробелами, дополнительным текстом до или после фигурных скобок, или вовсе отвечает на русском языке без JSON-формата. В этих случаях сервер ищет ключевые слова «buy», «sell», «покупк», «продаж» в тексте ответа и формирует сигнал на основе их наличия. Такая двухуровневая защита делает систему устойчивой к любым сюрпризам со стороны языковой модели — и именно поэтому система стабильно работает в продакшне, а не только на демо-данных.
Развёртывание: от нуля до работающего советника
OpenRouter и выбор модели
Регистрация на openrouter.ai занимает две минуты. В разделе Keys создаётся новый API-ключ. Рекомендуется сразу установить spending limit — например $10 или $20 в месяц — чтобы неожиданный всплеск активности или ошибка в коде не привели к непредвиденным расходам. Ключ выглядит как sk-or-v1-... и вставляется в одну строку Python-скрипта.
Выбор модели зависит от приоритетов. stepfun/step-3.5-flash — быстрый, дешёвый, стабильный вариант, используемый в базовой конфигурации системы. deepseek/deepseek-chat — хорошая альтернатива с сильным пониманием числовых данных. google/gemini-flash-1.5 имеет бесплатный лимит, полезный для первоначального тестирования архитектуры без расходов. anthropic/claude-3-haiku отличается стабильным форматом JSON-ответов, что снижает нагрузку на парсер. Поменять модель — это одна строка в конфигурации сервера.
Запуск Python-сервера# Установка зависимостей pip install requests numpy # Запуск python deepseek_server.py # Ожидаемый вывод: # [12:34:56] Сервер запущен. Ожидаем подключения MT5...
Для постоянной работы сервер можно добавить в автозапуск Windows через Task Scheduler или создать простой .bat файл в папке Автозагрузка. Сервер не требует графического интерфейса и прекрасно работает в фоне, не потребляя заметных ресурсов в режиме ожидания.
Советник в MetaTrader 5
Файл winhttp.mqh — в MQL5/Include/WinAPI/ . После компиляции в MetaEditor советник прикрепляется к любому графику — пара этого графика совершенно не важна. Советник управляет своим собственным списком из 8 пар, прописанных в параметрах. Все пары автоматически добавляются в MarketWatch через SymbolSelect() при инициализации, так что дополнительных ручных действий не требуется.
# Диагностика: советник пишет "Нет связи!" netstat -an | findstr :8989 # Если порт занят — меняем в обоих файлах одновременно: # Python: PORT = 9090 # MQL5: input int InpPort = 9090; # HTTP 401 — неверный API-ключ # HTTP 429 — лимит модели, сменить на платную # Timeout — увеличить InpReceiveMs до 120000
Инфраструктура, а не чёрный ящик
Главное, что даёт эта система — не готовые сигналы с обещанием прибыли, а инфраструктуру для экспериментов. Полностью открытая, модульная, понятная архитектура. Добавить девятую пару — это изменить одну константу и одну строку в функции InitPairs() . Попробовать другую LLM-модель — одна строка в Python-скрипте. Добавить новый индикатор в промпт — написать функцию расчёта и добавить её результат в строку промпта. Расширить протокол новой командой — добавить обработчик в основной цикл сервера.

Такой подход принципиально отличается от проприетарных решений и «сигнальных сервисов». Трейдер понимает каждую строку кода, может изменить любую деталь поведения системы и не зависит ни от разработчика, ни от внешнего сервиса, который может закрыться или поднять цены.
Длительных бэктестов пока не проводилось, но в целом — они радуют:

Прозрачность каждого решения
Каждый сигнал содержит поле comment — краткое текстовое обоснование того, почему AI принял именно такое решение. Оно выводится в журнал MetaTrader, отображается в комментарии на графике и сохраняется в массиве g_comment[] . Трейдер видит не просто «купить», а «MA20 пробита снизу вверх, RSI14=58, стохастик выходит из перепроданности, цена в середине Bollinger, моментум положительный на всех горизонтах». Анализируя комментарии за несколько недель, можно понять, при каких конфигурациях индикаторов AI принимает решения, которые затем оказываются прибыльными. Это основа для итеративного улучшения промпта и выбора оптимальной модели.
Заключение: что мы получили
Прямая связь MetaTrader 5 с большой языковой моделью невозможна — и мы не стали её ломать. Вместо этого всю AI-логику, всю тяжёлую работу и все секреты вынесли в отдельный Python-сервер. Советник общается с ним по чистому, простому протоколу и никогда не видит ни названия модели, ни ключа, ни даже того, где именно живёт интеллект.
Бесплатные API душат лимитами уже через полчаса нормальной торговли — мы выбрали OpenRouter и модель с оплатой строго по факту сожжённых токенов. А главное — ввели режим BATCH: один сетевой запрос обрабатывает сразу восемь пар параллельно, вместо того чтобы слать восемь отдельных и ждать в восемь раз дольше.
Ключ в коде советника — это мина замедленного действия, которая рано или поздно взорвётся. Поэтому он туда вообще не попадает. Ключ существует только в одной-единственной строке на сервере, куда MT5 физически не имеет никакого доступа.
В итоге после этой статьи у вас на руках полноценная, воспроизводимая боевая связка: MT5 отправляет рыночные данные по WebSocket (или TCP в качестве запасного варианта) → локальный Python-сервер считает индикаторы, формирует промпт и общается с LLM → OpenRouter возвращает ответ → советник получает строгий JSON с сигналом и комментарием. Один запрос — анализ всего портфеля. Задержка 3–7 секунд. Расходы регулируются одной цифрой — частотой анализа. OnTick не блокируется никогда. Ключ в полной безопасности.
Shtenco AI V17 — это не очередной «умный» советник с громкими обещаниями прибыли. Это инфраструктура, которая впервые делает реально доступным для частного трейдера то, что раньше требовало серверной фермы, команды DevOps и корпоративного бюджета: настоящие большие языковые модели, анализирующие реальный рынок в реальном времени на восьми и более инструментах одновременно.
Весь код открыт, прозрачен и написан так, чтобы его можно было трогать руками. Те же принципы без переписывания работают на крипте, акциях, фьючерсах. Хотите добавить девятую пару — меняете одну константу. Новую модель — одну строку. Новый индикатор в промпт — одну функцию. Ничего не ломается.
Теперь у вас есть всё, чтобы запустить это сегодня. Следующий шаг — за вами.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От начального до среднего уровня: Наследование
Возможности Мастера MQL5, которые вам нужно знать (Часть 68): Использование паттернов TRIX и процентного диапазона Уильямса с сетью косинусного ядра
Знакомство с языком MQL5 (Часть 34): Освоение API и функции WebRequest в языке MQL5 (VIII)
Торговые инструменты на языке MQL5 (Часть 7): Информационная панель для мониторинга позиций на счете в разрезе символов
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования