Реализация LLM-агента с SQL-памятью в MetaTrader 5
Вы уже подключили языковую модель к MetaTrader 5 и получаете торговые сигналы в реальном времени. Система работает — но каждый новый бар она начинает с чистого листа. Она не помнит, что вчера RSI на EURUSD давал ложный сигнал в это же время суток. Она не знает, что агент трижды ошибся на медвежьем рынке и ни разу — на бычьем. Она не понимает, что последние пять её советов закончились убытком, и продолжает действовать с той же уверенностью, с которой начинала.
Это не недостаток языковой модели — это архитектурное ограничение. LLM не имеет долгосрочной памяти между запросами по определению. Каждый вызов API — это новый разговор с незнакомцем. Советник может работать неделями, накапливать статистику, совершать паттернные ошибки — и модель об этом никогда не узнает.
Цель этой статьи — устранить это ограничение. Мы добавим к архитектуре LLM+MetaTrader 5 постоянную SQL — базу знаний, которая накапливается между сессиями, передаётся в контекст каждого запроса и позволяет агенту учиться на собственных ошибках. Критерии успеха определены инженерно: агент знает свою историческую точность по инструменту и таймфрейму; каждый запрос к модели содержит релевантный контекст из прошлых решений; база автоматически обновляется по факту закрытия позиции.
Почему контекстного окна недостаточно
Первая идея, которая приходит в голову: просто передавать историю сделок в тексте, прямо в тело запроса. Сто последних сигналов — это примерно 8 000 токенов. Звучит разумно.
Но это решение ломается по трём причинам.
Первая причина — стоимость. Каждый запрос к grok-3-mini или аналогичной модели тарифицируется по числу входных токенов. Если вы торгуете восемью парами на M15 — это 256 запросов в день на пару, итого 2 048 запросов. При 8 000 токенов контекста на каждый запрос счёт быстро растёт.
Вторая — нерелевантность. Модель не умеет автоматически выделять нужное из потока текста. Если вы передаёте сто записей, а релевантных для текущего момента — три, модель обрабатывает весь массив с одинаковым вниманием. Сигнал тонет в шуме.
Третья — персистентность. Контекстное окно живёт ровно один запрос. Перезапустили сервер — история исчезла. SQLite-база остаётся между сессиями, между перезапусками, между версиями советника.
Правильное решение — не передавать всю историю, а передавать только то, что релевантно прямо сейчас. Это и есть задача SQL-слоя памяти.
Архитектура: что добавляется к существующей системе
Для работы системы понадобятся следующие пакеты Python:
pip install requests numpy
Система строится поверх WebSocket-сервера, описанного в предыдущих статьях серии. Добавляется один новый компонент — MemoryLayer, обёртка над SQLite, которая пишет и читает через единственный интерфейс.
MetaTrader 5 (EA_Memory_Agent.mq5) │ ├─ ANALYZE:SYMBOL:csv ────────────────────────────┐ │ │ ├─ RESULT:SYMBOL:signal:pnl ──────────────────┐ │ │ WebSocket │ │ │ Python—сервер (llm_server_memory.py) │ │ │ │ │ ├─ MemoryLayer (SQLite) │ │ │ ├─ write_decision() ◄─────────────────────┘ │ │ ├─ get_context() → релевантный фрагмент │ │ └─ get_stats() → точность по инструменту │ │ │ ├─ build_prompt(context, stats) ──────────────┐ │ │ │ │ └─ Grok (xAI API) ◄──────────────────────────┘ │ {"signal","comment","confidence"} ────────────┘
Советник отправляет команду ANALYZE с ценами, сервер достаёт из базы релевантный контекст и статистику агента, включает их в системный промпт и запрашивает модель. После закрытия позиции советник отправляет RESULT — и база обновляется. Круг замыкается.
Структура базы данных
Две таблицы. Первая хранит все решения агента, вторая — агрегированную статистику.
CREATE TABLE IF NOT EXISTS decisions ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, symbol TEXT NOT NULL, timeframe TEXT NOT NULL, signal TEXT NOT NULL, -- buy / sell / hold confidence REAL, comment TEXT, pnl REAL, -- NULL до закрытия outcome TEXT, -- win / loss / open rsi REAL, ma_align TEXT, -- above / below / mixed volatility TEXT -- low / normal / high ) CREATE TABLE IF NOT EXISTS agent_stats ( symbol TEXT, timeframe TEXT, total INTEGER DEFAULT 0, wins INTEGER DEFAULT 0, losses INTEGER DEFAULT 0, avg_pnl REAL DEFAULT 0, best_signal TEXT, -- buy / sell PRIMARY KEY (symbol, timeframe) )
Поля rsi, ma_align, volatility — это рыночный режим в момент решения. Они нужны для релевантного поиска: когда RSI сейчас на 68, база отдаёт решения, принятые при похожих значениях RSI, а не все подряд.
Класс MemoryLayer: ключевые методы
import sqlite3 import json from datetime import datetime, timedelta from typing import Optional class MemoryLayer: def __init__(self, db_path: str = "agent_memory.db"): self.conn = sqlite3.connect(db_path, check_same_thread=False) self._init_tables() def _init_tables(self): self.conn.executescript(""" CREATE TABLE IF NOT EXISTS decisions ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, symbol TEXT NOT NULL, timeframe TEXT NOT NULL, signal TEXT NOT NULL, confidence REAL, comment TEXT, pnl REAL, outcome TEXT DEFAULT 'open', rsi REAL, ma_align TEXT, volatility TEXT ); CREATE TABLE IF NOT EXISTS agent_stats ( symbol TEXT, timeframe TEXT, total INTEGER DEFAULT 0, wins INTEGER DEFAULT 0, losses INTEGER DEFAULT 0, avg_pnl REAL DEFAULT 0.0, best_signal TEXT, PRIMARY KEY (symbol, timeframe) ); """) self.conn.commit() def write_decision(self, symbol: str, timeframe: str, signal: str, confidence: float, comment: str, rsi: float, ma_align: str, volatility: str) —> int: cur = self.conn.execute( """INSERT INTO decisions (timestamp, symbol, timeframe, signal, confidence, comment, rsi, ma_align, volatility) VALUES (?,?,?,?,?,?,?,?,?)""", (datetime.utcnow().isoformat(), symbol, timeframe, signal, confidence, comment, rsi, ma_align, volatility) ) self.conn.commit() return cur.lastrowid def update_result(self, decision_id: int, pnl: float): outcome = "win" if pnl > 0 else "loss" self.conn.execute( "UPDATE decisions SET pnl=?, outcome=? WHERE id=?", (pnl, outcome, decision_id) ) self.conn.commit() self._refresh_stats(decision_id) def _refresh_stats(self, decision_id: int): row = self.conn.execute( "SELECT symbol, timeframe FROM decisions WHERE id=?", (decision_id,) ).fetchone() if not row: return symbol, timeframe = row stats = self.conn.execute( """SELECT COUNT(*), SUM(outcome='win'), SUM(outcome='loss'), AVG(pnl) FROM decisions WHERE symbol=? AND timeframe=? AND outcome != 'open'""", (symbol, timeframe) ).fetchone() total, wins, losses, avg_pnl = stats # определяем, какой сигнал исторически точнее buy_wr = self.conn.execute( """SELECT AVG(outcome='win') FROM decisions WHERE symbol=? AND timeframe=? AND signal='buy' AND outcome != 'open'""", (symbol, timeframe) ).fetchone()[0] or 0 sell_wr = self.conn.execute( """SELECT AVG(outcome='win') FROM decisions WHERE symbol=? AND timeframe=? AND signal='sell' AND outcome != 'open'""", (symbol, timeframe) ).fetchone()[0] or 0 best_signal = "buy" if buy_wr >= sell_wr else "sell" self.conn.execute( """INSERT INTO agent_stats VALUES (?,?,?,?,?,?,?) ON CONFLICT(symbol, timeframe) DO UPDATE SET total=excluded.total, wins=excluded.wins, losses=excluded.losses, avg_pnl=excluded.avg_pnl, best_signal=excluded.best_signal""", (symbol, timeframe, total or 0, int(wins or 0), int(losses or 0), avg_pnl or 0.0, best_signal) ) self.conn.commit() def get_context(self, symbol: str, timeframe: str, rsi: float, limit: int = 5) -> str: """Релевантный контекст: похожие условия RSI, последние N решений.""" similar = self.conn.execute( """SELECT signal, outcome, pnl, comment, rsi FROM decisions WHERE symbol=? AND timeframe=? AND outcome != 'open' AND ABS(rsi - ?) < 8 ORDER BY timestamp DESC LIMIT ?""", (symbol, timeframe, rsi, limit) ).fetchall() recent = self.conn.execute( """SELECT signal, outcome, pnl, comment FROM decisions WHERE symbol=? AND timeframe=? AND outcome != 'open' ORDER BY timestamp DESC LIMIT 3""", (symbol, timeframe) ).fetchall() parts = [] if similar: parts.append("Similar RSI conditions in the past:") for sig, out, pnl, cmt, r in similar: pnl_str = f"{pnl:+.2f}" if pnl is not None else "?" parts.append( f" [{sig.upper()}] → {out} ({pnl_str} pts) | RSI≈{r:.0f} | {cmt}" ) if recent: parts.append("Recent decisions:") for sig, out, pnl, cmt in recent: pnl_str = f"{pnl:+.2f}" if pnl is not None else "?" parts.append( f" [{sig.upper()}] → {out} ({pnl_str} pts) | {cmt}" ) return "\n".join(parts) if parts else "No historical data yet." def get_stats(self, symbol: str, timeframe: str) -> str: row = self.conn.execute( "SELECT total, wins, losses, avg_pnl, best_signal " "FROM agent_stats WHERE symbol=? AND timeframe=?", (symbol, timeframe) ).fetchone() if not row or row[0] == 0: return "No statistics yet — acting on current data only." total, wins, losses, avg_pnl, best_signal = row win_rate = 100 * wins / total if total else 0 return ( f"Agent stats on {symbol}/{timeframe}: " f"{total} trades, win-rate {win_rate:.0f}%, " f"avg PnL {avg_pnl:+.2f} pts. " f"Historically stronger signal: {best_signal.upper()}." )
Метод get_context() — ключевой. Он не возвращает всю историю, он ищет решения при похожем RSI (отклонение не более 8 пунктов) и добавляет три последних решения для понимания текущей серии. Итоговый текст занимает 200–400 токенов — незначительная добавка к контексту.
Построение промпта с памятью
Функция build_prompt_with_memory() собирает системный промпт, который агент получает при каждом запросе.
def build_prompt_with_memory(symbol: str, timeframe: str, memory: MemoryLayer, rsi: float) —> str: stats = memory.get_stats(symbol, timeframe) context = memory.get_context(symbol, timeframe, rsi) return f"""You are a disciplined quantitative trading agent with persistent memory. SELF—KNOWLEDGE: {stats} HISTORICAL CONTEXT (similar market conditions): {context} RULES: 1. If your win—rate on this instrument is below 40% — be extra conservative. 2. If recent 3 decisions were all losses — reduce confidence by 0.1. 3. If historical context shows repeated failures on this signal type — downgrade it. 4. Never ignore your own track record. OUTPUT: Reply ONLY with valid JSON, no markdown. Example: {{"signal": "buy", "comment": "MA aligned, RSI rising from 42", "confidence": 0.74}} signal must be exactly: buy sell or hold BIAS: hold only when genuinely uncertain. Commit to a direction. """
Три блока в промпте выполняют разные функции. SELF-KNOWLEDGE даёт агенту общую картину его эффективности: он знает, что на данном инструменте у него 58% побед, и что продажи исторически точнее покупок. HISTORICAL CONTEXT даёт конкретные примеры похожих ситуаций: «в прошлый раз при RSI≈65 я дал buy — оказалось loss». RULES переводят эту информацию в явные поведенческие корректировки.
Интеграция в сервер
Основная логика сервера минимально отличается от предыдущих версий. Добавляется инициализация MemoryLayer и две точки вызова.
import threading, json, numpy as np from memory_layer import MemoryLayer memory = MemoryLayer("agent_memory.db") send_lock = threading.Lock() def handle_analyze(symbol: str, prices: list[float]) —> dict: close = np.array(prices, dtype=float) ind = calc_indicators(close) # ваша существующая функция rsi = ind["rsi14"] system = build_prompt_with_memory( symbol = symbol, timeframe = "M15", # можно передавать из советника memory = memory, rsi = rsi ) user = build_market_context(symbol, ind) # ваша существующая функция raw = ask_grok(system, user) result = json.loads(raw) signal = result.get("signal", "hold") confidence = float(result.get("confidence", 0.5)) comment = result.get("comment", "") # Сохраняем решение — получаем ID для последующего update_result decision_id = memory.write_decision( symbol = symbol, timeframe = "M15", signal = signal, confidence = confidence, comment = comment, rsi = rsi, ma_align = ind.get("ma_align", "mixed"), volatility = ind.get("volatility", "normal") ) result["decision_id"] = decision_id return result def handle_result(symbol: str, decision_id: int, pnl: float): memory.update_result(decision_id, pnl) return {"status": "ok"} ``` Обратите внимание: `decision_id` возвращается советнику вместе с сигналом. Советник обязан его запомнить и вернуть при отправке `RESULT`. Это единственная связь между решением и его исходом. --- ## Советник MQL5: команды и трекинг ID Советник отправляет две команды. Команда анализа: ``` ANALYZE:EURUSD:1.0912,1.0915,...,1.0921
Ответ содержит decision_id — советник сохраняет его в глобальную переменную вместе с тикетом открытой позиции.
string g_activeSymbol = ""; int g_activeDecisionId = -1; ulong g_activeTicket = 0; // При разборе ответа сервера: g_activeDecisionId = (int)resp["decision_id"]; g_activeTicket = ticket;
Команда результата отправляется при закрытии позиции:
double pnl = GetDealProfit(g_activeTicket); // существующая функция if(pnl != EMPTY_VALUE && g_activeDecisionId >= 0) { string cmd = StringFormat( "RESULT:%s:%d:%.2f", g_activeSymbol, g_activeDecisionId, pnl ); WebSocketSend(cmd); // ваша существующая функция отправки g_activeDecisionId = -1; g_activeTicket = 0; } ``` Нормализация PnL в пунктах, а не в деньгах, сохраняет корректность сравнений между разными лотами и инструментами — тот же принцип, что и в предыдущих статьях серии. --- ## Что агент видит спустя неделю реальной торговли После 30–40 закрытых сделок промпт агента перестаёт начинаться с чистого листа. Вместо этого он видит, например: ``` SELF-KNOWLEDGE: Agent stats on EURUSD/M15: 34 trades, win-rate 53%, avg PnL +1.8 pts. Historically stronger signal: SELL. HISTORICAL CONTEXT (similar market conditions): Similar RSI conditions in the past: [BUY] → loss (-4.20 pts) | RSI≈67 | MA aligned but upper BB resistance [SELL] → win (+3.10 pts) | RSI≈65 | Bearish divergence confirmed [BUY] → loss (-2.80 pts) | RSI≈69 | Momentum weakening Recent decisions: [SELL] → win (+2.40 pts) | Short-term downtrend confirmed [HOLD] → open | Conflicting signals [BUY] → loss (-3.60 pts) | False breakout
Агент читает это перед тем, как смотреть на текущий рынок. Он видит, что при RSI около 65–70 его покупки исторически убыточны. Он видит, что последние три решения были убыточной покупкой, нейтралью и прибыльной продажей. Он видит, что продажи работают лучше.
Это не гарантирует правильного решения. Но это принципиально меняет то, с чем агент приступает к анализу текущего бара.
Ошибки, которые нужно знать
Разработка этой системы выявила несколько нетривиальных проблем, решение которых не очевидно из документации.
Гонка decision_id при смене сигнала. Когда сигнал на новом баре меняется с buy на sell, порядок выполнения в OnTick таков: сначала вызывается ParseAnalyzeResponse и g_decisionId получает новое значение, затем ExecuteSignal вызывает CloseByType для закрытия старой позиции и тут же OpenSell с присвоением g_posDecisionId = g_decisionId. В итоге CheckClosedPosition на следующем тике находит закрытую позицию, но отправляет RESULT уже с ID новой позиции, а не закрытой. База обновляет не ту запись. Решение — сохранять savedDecId и savedTicket локально внутри ExecuteSignal до вызова CloseByType, а RESULT отправлять сразу в момент закрытия через отдельную функцию SendResultForClosed, не дожидаясь следующего тика.
EMPTY_VALUE в тестере находится не сразу. Функция GetDealProfitByPosition иногда возвращает EMPTY_VALUE сразу после закрытия позиции в Strategy Tester — сделка ещё не появилась в истории. Стандартный HistorySelect(0, TimeCurrent() + 86400) в тестере работает с задержкой. Надёжное решение: при получении EMPTY_VALUE сделать паузу 200 мс и повторить запрос один раз. Этого достаточно для 99% случаев в тестере и не создаёт заметной задержки в реальной торговле.
Как проверить, что память работает
Запустить систему и увидеть в консоли decision_id=13 — это ещё не проверка. Проверка — это убедиться, что агент действительно меняет поведение под влиянием накопленной истории, а не просто игнорирует контекст в промпте.
Самый простой способ — сравнить два прогона на одних и тех же данных. В первом прогоне база пустая, агент работает без истории. Во втором прогоне база уже содержит результаты первого прогона. Если распределение сигналов между прогонами одинаковое — память не влияет. Если второй прогон показывает заметно меньше покупок в зонах RSI, где первый прогон систематически проигрывал — адаптация работает.
Второй способ — прямой запрос к базе после прогона. Откройте agent_memory.db любым SQLite-браузером и выполните:
SELECT signal, outcome, rsi, AVG(pnl) as avg_pnl, COUNT(*) as cnt FROM decisions WHERE symbol='EURUSD' AND timeframe='M15' AND outcome != 'open' GROUP BY signal, CASE WHEN rsi < 30 THEN 'oversold' WHEN rsi > 70 THEN 'overbought' ELSE 'neutral' END ORDER BY signal, avg_pnl;
Этот запрос показывает среднюю доходность каждого типа сигнала в разных зонах RSI. Если BUY при RSI > 70 даёт avg_pnl = −3.2, а агент продолжает выдавать buy в этих условиях — что-то в промпте не работает как ожидается. Если после накопления истории агент переключается на hold или sell в этой зоне — всё идёт правильно.
Третий способ — смотреть на best_signal в таблице agent_stats. Это поле пересчитывается автоматически после каждого закрытия позиции. Если в начале прогона там стоит buy, а через 20 сделок — sell, значит система корректно отслеживает, какое направление приносит больше побед на данном инструменте, и агент читает это в блоке SELF-KNOWLEDGE перед каждым решением.
Важно понимать: LLM не гарантирует точного следования правилам промпта в 100% случаев. Модель может проигнорировать исторический контекст, если текущие рыночные сигналы выглядят убедительно. Это не баг — это природа языковых моделей. Память не делает агента детерминированным автоматом, она смещает вероятность решения в сторону того, что исторически работало. Разница между «агент без памяти» и «агент с памятью» — это не жёсткое правило, а статистический сдвиг в поведении на дистанции 100+ сделок.
Результаты тестирования
Система протестирована на паре EURUSD, таймфрейм M15, платформа одного известного ECN-брокера (Build 5672), качество моделирования 100%. Период тестирования: 1 октября 2025 года — 16 марта 2026 года. Начальный депозит — $10 000, лот 0.01, кредитное плечо 1:500. Параметры советника: SL 1200 пунктов, TP 2000 пунктов, анализ каждые 10 баров, InpMinConf = 0.00001 — фактически без фильтра по уверенности.
За тестовый период советник совершил 101 сделку: 52 коротких (55.77% прибыльных) и 49 длинных (51.02% прибыльных). Общий процент прибыльных сделок — 53.47% (54 из 101), убыточных — 46.53% (47 из 101).

Чистая прибыль составила $35.97 при валовой прибыли $181.29 и валовом убытке $145.32. Профит—фактор — 1.25, ожидаемая выплата — $0.36 на сделку. Средняя прибыльная сделка — $3.36, средняя убыточная — $3.07. Лучшая сделка принесла $15.77, худшая — убыток $12.11.

Просадки минимальны: максимальная просадка по балансу — $37.30 (0.37%), по эквити — $46.22 (0.46%). Фактор восстановления — 0.78, коэффициент Шарпа — 1.19. Z-Score составил −0.35 (27.37%), что указывает на отсутствие статистически значимой серийной зависимости между выигрышами и проигрышами.
Максимальная серия выигрышей — 7 сделок подряд ($12.88), максимальная серия проигрышей — 6 сделок ($29.63). Средняя серия в обоих направлениях — 2 сделки.
Корреляция прибыли с MFE (Maximum Favorable Excursion) — 0.69, с MAE (Maximum Adverse Excursion) — 0.69. Корреляция MFE/MAE между собой — 0.18. Высокая корреляция прибыли с MFE говорит о том, что агент достаточно хорошо удерживает прибыльные позиции до цели. Низкая корреляция MFE/MAE подтверждает, что сделки разделяются на два выраженных класса: те, которые сразу идут в нужную сторону, и те, которые быстро уходят в минус — промежуточных «метаний» мало.
Важно: это результат одного прогона на одном инструменте с фиксированным лотом. Цель теста — не доказать прибыльность, а подтвердить, что архитектура памяти функционирует корректно: решения записываются, результаты обновляются, контекст влияет на последующие сигналы. Profit factor 1.25 и win—rate 53% при минимальной просадке показывают, что агент с памятью как минимум не деградирует на дистанции в 101 сделку — и это уже отправная точка для дальнейшей оптимизации.
Ключевая проблематика и что важно учитывать
Холодный старт. Первые 10–15 сделок агент работает без существенной истории. Контекст пустой, статистика нулевая. Поведение системы в этот период идентично работе без памяти. Это нормально и ожидаемо.
Дрейф рынка. База накапливает всё подряд. Паттерн, который работал в феврале при низкой волатильности, может не работать в марте при высокой. Поле volatility в схеме позволяет фильтровать контекст по режиму рынка — это рекомендуемое улучшение для следующей итерации.
Размер базы. При 50 сделках в день база вырастает примерно на 50 KB в сутки. Это незначительно. При необходимости можно добавить очистку записей старше 90 дней — одна SQL-команда в периодическом задании.
Thread safety. SQLite поддерживает несколько читателей, но только одного писателя одновременно. При параллельной торговле несколькими символами write_decision() должен вызываться через блокировку — threading.Lock(), аналогично send_lock из предыдущих версий системы.
db_lock = threading.Lock() def safe_write(symbol, timeframe, signal, **kwargs) —> int: with db_lock: return memory.write_decision(symbol, timeframe, signal, **kwargs) ```
Что дальше
Описанная архитектура реализует первый уровень памяти: агент знает свою историю и использует её при принятии решений. Но он всё ещё не знает, при каких именно рыночных режимах его паттерны работают, а при каких — нет.
Следующий логичный шаг — режимная память. Разбить историю на кластеры: трендовый рынок, боковик, высокая волатильность. При каждом запросе определять текущий режим и передавать агенту только контекст из соответствующего кластера. Агент, который ошибался в боковике, перестанет применять свой «трендовый» опыт в флете.
Параллельное направление — скрещивание памяти с эволюционной архитектурой из предыдущей статьи. Каждый из 20 агентов с разным промптом ведёт собственную SQL—базу. Фитнес считается не только по PnL, но и по качеству памяти: агент, который лучше использует свою историю для улучшения точности, получает преимущество при отборе. Это уже другая задача — но строится на том же фундаменте.
Инструкция по запуску
Первый шаг — Python-сервер. Получите API-ключ xAI на официальном сайте API Grok — console.x.ai. Само получение ключа бесплатно, но дальнейшая работа потребует пополнения баланса для покупки токенов. Иногда, если хорошо попросить у самого оригинального Grok-чата, он может выдать тестовый API ключ — на время и с токенами на балансе.
Затем нужно установить Python и открыть окно ввода команды нажатием Win + R. Через это окно далее нужно установить зависимости:
pip install requests numpy Скопируйте файл самого сервера llm_server_memory.py — в любую рабочую директорию (у меня папки с серверами на рабочем столе), далее — вставьте ключ в XAI_API_KEY в переменные сервера прямо внутри кода llm-сервера:
# ─── НАСТРОЙКИ ─────────────────────────────────────────────────────────────── XAI_API_KEY = "" # ← вставьте ключ xAI с console.x.ai XAI_URL = "https://api.x.ai/v1/chat/completions" MODEL = "grok-4-1-fast-reasoning" HOST = "127.0.0.1" PORT = 8976 MAX_TOKENS = 1200 DB_PATH = "agent_memory.db"
Затем нажатием правой клавиши по py-файлу сервера нужно открыть его исходный код, выбрав после нажатие правой кнопки — Edit in IDLE, и запустить сервер нажатием кнопки Run или F5:

Далее — подготовка MetaTrader 5. Перед компиляцией советника установите заголовочный файл WinHTTP. Этот файл для работы с WebSocket изначально был описан в этой статье разработчика Francis Dube. Скопируйте файл WinHTTP.mqh в папку:
<каталог данных MT5>\MQL5\Include\WinAPI\ Каталог данных MetaTrader 5 открывается через меню: Файл → Открыть каталог данных. Если папка WinAPI отсутствует, создайте её вручную. Без этого файла советник не скомпилируется: компилятор выдаст ошибку 'WinHTTP.mqh' — no such file or directory.
Ну и в итоге — компиляция и запуск советника. Откройте EA_Memory_Agent.mq5 в редакторе MetaEditor (F4 из терминала), скомпилируйте клавишей F7. При успешной компиляции в журнале MetaEditor должно быть 0 errors, 0 warnings.
Перетащите скомпилированный советник на график нужного инструмента.
Если все хорошо, после запуска вы увидите работу агентов внутри Python:

И также мы увидим результаты первого открытия сделки:

Заключение
Мы начали с архитектурного ограничения: языковая модель без памяти каждый раз начинает с нуля, игнорируя накопленный опыт. Мы устранили это ограничение через постоянную SQL-базу знаний, которая накапливается между сессиями и передаётся в контекст каждого запроса.
После прочтения у вас есть воспроизводимый каркас: схема базы данных из двух таблиц с поддержкой релевантного поиска по рыночным условиям; класс MemoryLayer с методами записи решений, обновления результатов и построения контекстного фрагмента; функция построения промпта с блоком самознания агента и историческим контекстом; протокол RESULT для обратной связи между советником и сервером; и рекомендации по thread safety при параллельной торговле несколькими символами.
Система проверяется через Strategy Tester с контролируемыми метриками: точность сигналов, динамика win-rate по мере накопления памяти, поведение агента в условиях, где он исторически ошибался. Адаптация становится видимой и измеримой, а не просто обещанной.
| Название файла | Содержание и назначение |
|---|---|
| http://llm_server_memory_mql5.py | Python сервер системы |
| EA_Memory_Agent.mq5 | MQL5 — эксперт для торговли |
| winhttp.mqh | Включаемая библиотека |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Унифицированное смешивание признаков для торговых решений (UniMixer)
Неопределенность как модель (Часть 4): Случайные процессы — динамика неопределённости
Разработка инструментария для анализа движения цен (Часть 22): Панель корреляции
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования