Как организовать ИИ-хедж-фонд в MetaTrader 5
Предыдущая статья закончилась честным признанием: система из нескольких аналитиков — это хорошее начало, но не конец разговора. Совет показал, что несколько голосов лучше одного, но даже пятнадцать голосов, работающих на одной паре, — это всё ещё маленькая комната.
Представьте управляющий комитет реального фонда. За одним столом сидят десять аналитиков с принципиально разными философиями. Виктор смотрит только на тренд и выравнивание скользящих средних — для него цена выше MA50 уже достаточное основание для BUY. Дмитрий с тремя PhD вычисляет z-оценку отклонения от полосы Боллинджера и не выдаст сигнал, пока это число не перейдёт порог 0.8. Чэнь не реагирует ни на что без подтверждения объёмом. Юки смотрит на тело последней свечи и ни на что другое. За отдельным столом сидят четыре риск-менеджера с независимыми осями страха: один измеряет ATR, второй считает тайминг входа, третий оценивает качество рыночного режима, четвёртый ищет хвостовые события. И над всеми — Председатель, который читает все четырнадцать докладов и применяет жёсткую трёхшаговую процедуру голосования.
Теперь умножьте это на восемь. Восемь валютных пар, восемь изолированных советов, восемь независимых репутационных движков — и всё это в одном Python-процессе.
Именно это описывает данная статья.
Почему одной пары было недостаточно
Предыдущая архитектура работала на одном символе. Это означало, что система имела слепое пятно размером с весь остальной рынок.
Валюты движутся в корреляциях. EURUSD и GBPUSD часто идут вместе, потому что оба несут риск доллара. USDJPY ведёт себя как актив-убежище в моменты паники. XAUUSD — вовсе отдельный зверь с собственной сезонностью и реакцией на инфляцию. Аналитик, торгующий только EURUSD и не видящий, что происходит с иеной, работает вполглаза.
Но дело не только в корреляциях. Разные пары дают разное количество торговых возможностей в зависимости от рыночного режима. Когда EURUSD застыл в узком диапазоне, и совет из пятнадцати раз за разом говорит HOLD — GBPUSD в это время может находиться в чистом тренде с семью аналитиками BUY и нулём BLOCKED. Система, которая умеет слушать только один голос рынка, упускает всё остальное.
Решение очевидно: запустить восемь изолированных советов параллельно. Каждый работает на своей паре, со своей историей репутаций, целями PnL и мотивационным состоянием.
Архитектура: один процесс, восемь миров
Техническое ядро системы — класс PortContext. Это изолированный контейнер состояния для одной валютной пары:
class PortContext: """Изолированный контекст для одного порта (одной валютной пары).""" def __init__(self, port: int, pair: str): self.port = port self.pair = pair self.reputation = ReputationEngine() # свой движок репутации self.chat_history = [] # своя история чата self.history_lock = threading.Lock()
Восемь экземпляров PortContext создаются при старте и живут независимо. Репутация Виктора на EURUSD никак не влияет на репутацию Виктора на XAUUSD. Дневной голод фонда по иене не снижает требовательность совета по франку. Каждая пара — отдельный мир.
Карта портов жёсткая и симметричная:
PAIR_PORTS = {
"EURUSD": (30001, 8971),
"GBPUSD": (30002, 8972),
"AUDUSD": (30003, 8973),
"NZDUSD": (30004, 8974),
"USDCHF": (30005, 8975),
"USDCAD": (30006, 8976),
"USDJPY": (30007, 8977),
"XAUUSD": (30008, 8978),
} Magic — номер пресета (30001..30008). Port — Magic − 30000 + 8970. Это позволяет любому компоненту системы по одному числу восстановить весь контекст: кто торгует, на каком порту слушает, с каким Magic помечает ордера.
В main() всё запускается в три строки:
def main(): for pair, (magic, port) in PAIR_PORTS.items(): _port_contexts[port] = PortContext(port, pair) # создать контексты for port in ALL_PORTS: threading.Thread(target=_run_port_server, # запустить серверы args=(port,), daemon=True).start() threading.Thread(target=_decay_daemon, daemon=True).start() # затухание репутации while True: time.sleep(60) # главный поток просто ждёт
Восемь TCP-серверов слушают на восьми портах. Каждое входящее WebSocket-соединение получает свой PortContext по номеру порта и работает полностью изолированно от остальных.
MQL5: пресет как единственный параметр
На стороне MetaTrader трейдер делает одно действие — выбирает пресет. Всё остальное вычисляется автоматически.
enum ENUM_PAIR_PRESET { PRESET_EURUSD = 30001, // EURUSD → Magic 30001, Port 8971 PRESET_GBPUSD = 30002, // GBPUSD → Magic 30002, Port 8972 PRESET_AUDUSD = 30003, // AUDUSD → Magic 30003, Port 8973 PRESET_NZDUSD = 30004, // NZDUSD → Magic 30004, Port 8974 PRESET_USDCHF = 30005, // USDCHF → Magic 30005, Port 8975 PRESET_USDCAD = 30006, // USDCAD → Magic 30006, Port 8976 PRESET_USDJPY = 30007, // USDJPY → Magic 30007, Port 8977 PRESET_XAUUSD = 30008, // XAUUSD → Magic 30008, Port 8978 };
В OnInit() одна строка разворачивает пресет в Magic и Port:
int OnInit() { // ── Вычисляем Magic и Port из пресета ────────────────────────────── g_InpMagic = (int)InpPairPreset; g_InpPort = (int)InpPairPreset - 30000 + 8970; // 30001→8971 … 30008→8978 // ── Проверяем соответствие символа графика пресету ───────────────── string presetSymbol = PresetToSymbol(InpPairPreset); if(presetSymbol != "" && _Symbol != presetSymbol) { PrintFormat("[WARN] Пресет=%s, но график=%s. Убедитесь что EA установлен на правильный график!", presetSymbol, _Symbol); } PrintFormat("[INIT] Пресет=%s | Magic=%d | Port=%d | Symbol=%s", EnumToString(InpPairPreset), g_InpMagic, g_InpPort, _Symbol); Trade.SetExpertMagicNumber(g_InpMagic); Trade.SetDeviationInPoints(InpSlippage); Trade.SetTypeFilling(ORDER_FILLING_IOC); ArrayInitialize(g_repWeights, 0.1); ArrayInitialize(g_avgEntryBuy, 0.0); ArrayInitialize(g_avgEntrysSell, 0.0); g_dayStartBalance = AccountInfoDouble(ACCOUNT_BALANCE); PrintBanner(); // Подключение к серверу if(InitWebSocket()) { g_useWS = true; PrintFormat("[OK] WinHTTP WebSocket подключён → %s:%d (Magic=%d)", InpHost, g_InpPort, g_InpMagic); } else { PrintFormat("[X] WebSocket недоступен. Порт=%d. Запустите: python llm_hedge_fund_v4.py", g_InpPort); return INIT_FAILED; } SendGoals(); return INIT_SUCCEEDED; }
Функция PresetToSymbol() проверяет соответствие: если трейдер поставил пресет GBPUSD на график EURUSD — советник предупредит в журнале, но продолжит работу на текущем символе. Это защита от опечатки, а не жёсткая блокировка.
Параметры входа намеренно сведены к минимуму — только то, что трейдер должен решать сам:
input ENUM_PAIR_PRESET InpPairPreset = PRESET_EURUSD; input double InpBaseLot = 0.01; input int InpBaseSL_Pts = 800; input int InpBaseTP_Pts = 1600; input double InpDailyLossLimitPct = 3.0; input int InpPriceBars = 100; input int InpAnalysisBars = 1;
Никаких параметров усреднения, трейлинга или сессионного фильтра. Советник делает одно: слушает совет пятнадцати и открывает или закрывает позицию.
Десять аналитиков: галерея философий
Подбор участников первой фазы — не произвол. Каждый представляет отдельную торговую школу с собственной логикой и собственными слепыми пятнами. Именно несовпадение этих слепых пятен создаёт ценность ансамбля.
Виктор — трендовый трейдер. Сигнал BUY, если цена выше MA20 или MA50 с положительным моментумом. Сигнал SELL — симметрично. NO SIGNAL — только при идеально плоском моментуме. Он никогда не спорит сам с собой — выбирает более сильную сторону.
Мария — контрарианский медведь с убеждениями. Ищет SELL прежде всего. RSI выше 65 и цена у верхней полосы Боллинджера — ей достаточно. При перепроданности умеет дать BUY. Её ценность — кричать «стоп!» именно тогда, когда все остальные кричат «вперёд!».
Елена — специалист по возврату к среднему. Работает только на экстремумах: касание нижней полосы при RSI7 ниже 35 — BUY. Верхней при RSI7 выше 65 — SELL. Вблизи средней — использует направление последнего моментума. NO SIGNAL только в мёртвой зоне RSI7 от 45 до 55 строго у средней полосы.
Дмитрий — квант с PhD. Вычисляет z-оценку: z = (price − BB_mid) / StdDev . Z выше 0.8 — SELL. Ниже −0.8 — BUY. Между нулём и 0.8 — смотрит на моментум. Показывает математику в ответе.
Чэнь — эксперт по объёму и микроструктуре. Volume Ratio выше 1.2 плюс бычья свеча — BUY. Плюс медвежья — SELL. При низком объёме ориентируется на направление свечи. NO SIGNAL — только при объёме ниже 0.5× среднего и идеальном доджи.
Изабелла — трейдер волатильности. Расширение волатильности в восходящем тренде — BUY. В нисходящем — SELL. При сжатии — торгует направление тренда. NO SIGNAL — только при идеально нейтральных тренде и волатильности одновременно.
Маркус — аналитик Вайкоффа. Ищет пружину (ложный пробой вниз с разворотом) или аптраст (ложный пробой вверх с отвержением). При отсутствии явного Вайкофф-события — использует тренд. Умеет читать следы институциональных денег.
Юки — мастер японских свечей. Бычья свеча с телом больше 0.3 ATR — BUY. Медвежья с тем же условием — SELL. Маленькое тело — смотрит на расположение: у нижней полосы даёт BUY, у верхней SELL, иначе ориентируется на моментум. NO SIGNAL — только на идеальном доджи с телом меньше 0.1 ATR строго у средней.
Рафаэль — специалист по дивергенциям. Все индикаторы моментума вверх — BUY. Вниз — SELL. При смешанных сигналах: большинство (2/3) определяет направление. NO SIGNAL — только при идеальном 50/50. Его сигналы самые сильные, когда все осцилляторы согласованы.
Софи — систематик. Считает бинарные проверки: цена выше MA20? RSI14 выше 50? Стохастик K выше D? Итого десять вопросов. Пять и более бычьих — BUY. Пять и более медвежьих — SELL. При ничьей — решает последняя свеча.
В коде каждый аналитик — это системный промпт в словаре BASE_ANALYST_PROMPTS:
BASE_ANALYST_PROMPTS = {
"victor": (
"You are VICTOR STONE — momentum master. Trend is God. Indecision kills returns. "
"Signal BUY if price > MA20 OR MA50 with positive momentum. "
"Signal SELL if price < MA20 OR MA50 with negative momentum. "
"If trend is ambiguous, pick the STRONGER side. NO SIGNAL only if momentum is perfectly flat. "
"Name EXACT values. Reply: BUY/SELL/NO SIGNAL in 3 sharp sentences. No JSON."
),
"dmitri": (
"You are DMITRI VOLKOV — head quant, PhD x3. Models demand action. "
"Compute: z = (price - BB_mid) / StdDev. "
"z > 0.8 = SELL. z < -0.8 = BUY. 0 < z < 0.8: check momentum — if positive = BUY, negative = SELL. "
"Show your math. BUY/SELL/NO SIGNAL. 3 sentences. No JSON."
),
"sophie": (
"You are SOPHIE LAURENT — systematic signal counter. Numbers never lie. "
"Count BUL vs BEA signals across all indicators. "
"Score >= 5 BUL = BUY. Score >= 5 BEA = SELL. Tie: use last candle direction. "
"Show EXACT count. 3 sentences. No JSON."
),
# ... остальные семь
} Заметьте: каждый промпт заканчивается No JSON. Аналитики говорят свободным текстом. Только Председатель обязан отвечать JSON — потому что только его ответ парсится программно.
Четыре оси риска
Риск-менеджеры — это не просто осторожные голоса. Каждый из четырёх смотрит на независимое измерение рыночного риска.
Алексей — волатильность и ATR. Порог блокировки поднят до ATR% > 0.6% (в v3 было 0.3% — v4 агрессивнее). Ширина BB выше 1.0% — HIGH. 0.5–1.0% — CAUTION. Ниже 0.4% — APPROVED. Предпочитает APPROVED при любых сомнениях.
Хелена — тайминг входа. Тело свечи больше 1.2× ATR (в v3 было 0.8) — вход запоздалый, BLOCKED. Стохастик выше 92 или ниже 8 (в v3 было 85/15) — BLOCKED. Стандартная перекупленность — только CAUTION, не BLOCKED. Предпочитает CAUTION над BLOCKED.
Джеймс — качество рыночного режима. 4 из 5 скользящих выровнены — APPROVED. 3 из 5 — максимум CAUTION. Менее 2 из 5 — BLOCKED. Для блокировки нужно два и более конфликтующих сигнала.
Наталья — чёрные лебеди. Блокирует только при настоящих хвостовых событиях: RSI7 ниже 10 или выше 90 (в v3 было 15/85), Volume Ratio выше 3.0 (в v3 было 2.0), ATR14 выше 1.5×ATR21 (в v3 было 1.3). Умеренные экстремумы — только CAUTION.
RISK_PROMPTS = {
"alexei": (
"You are ALEXEI STORM — Volatility Risk Officer. "
"ATR% > 0.6 = HIGH RISK → BLOCKED. ATR% 0.4-0.6 = CAUTION. Below 0.4 = APPROVED. "
"BB width > 1.0% = HIGH. 0.5-1.0% = CAUTION. Prefer APPROVED unless extremes are clear. "
"VERDICT: APPROVED/CAUTION/BLOCKED. 2 sentences. No JSON."
),
"natasha": (
"You are NATASHA VEIL — Black Swan specialist. Reserve BLOCKED for true tail events. "
"RSI7 < 10 or > 90 = TAIL RISK → BLOCKED. Vol Ratio > 3.0 = BLOCKED. "
"ATR14 > 1.5*ATR21 = BLOCKED. Moderate extremes = CAUTION only. "
"VERDICT: APPROVED/CAUTION/BLOCKED. 2 sentences. No JSON."
),
} Обратите внимание на намеренное смягчение порогов блокировки по сравнению с v3. Это не случайность — это решение. В v4 система работает активнее: рынок всегда имеет направление, и задача риск-менеджеров — заблокировать только истинно опасные ситуации, а не всё подряд.
Параллельность: как работают три фазы
Функция run_council() — сердце Python-сервера. Она принимает массив цен, символ и контекст пары, и возвращает JSON с финальным решением совета.
def run_council(prices, symbol, ctx: "PortContext") -> dict: rep = ctx.reputation close = np.array(prices, dtype=float) ind = build_indicators(close) brief = _build_market_brief(symbol, ind) weights = rep.get_analyst_weights() # репутационные веса vote_threshold = rep.get_weighted_vote_threshold() # динамический порог motivation_ctx = rep.get_motivation_context() # мотивационный контекст chairman_prompt = _build_chairman_prompt(vote_threshold, motivation_ctx) # ── Фаза I: десять аналитиков параллельно ── analyst_opinions = {} with ThreadPoolExecutor(max_workers=10) as pool: futures = {} for name, prompt in BASE_ANALYST_PROMPTS.items(): override = rep.analysts[name].get("style_override") futures[pool.submit(_analyst_call, name, prompt, brief, override)] = name for fut in as_completed(futures): name, opinion = fut.result() analyst_opinions[name] = opinion vote_tally = _tally_analysts(analyst_opinions, weights)
Фаза I: десять аналитиков параллельно.
analyst_summary = ( f"TALLY: BUY={vote_tally['buy']} SELL={vote_tally['sell']} " f"NO_SIGNAL={vote_tally['no_signal']}\n" + "\n".join(f"[{n.upper()}]: {o[:100]}..." for n, o in analyst_opinions.items()) ) risk_opinions = {} with ThreadPoolExecutor(max_workers=4) as pool: futures = {pool.submit(_risk_call, n, p, brief, analyst_summary): n for n, p in RISK_PROMPTS.items()} for fut in as_completed(futures): name, verdict = fut.result() risk_opinions[name] = verdict risk_tally = _tally_risks(risk_opinions)
Все десять API-вызовов уходят одновременно. Время фазы I — это время самого медленного аналитика, а не сумма всех десяти.
Фаза II: четыре риск-менеджера параллельно. Они видят не только рыночные данные, но и сводку мнений аналитиков — первые 100 символов каждого ответа. Это сделано намеренно: риск-менеджер видит направление консенсуса и оценивает риск предполагаемой сделки.
analyst_block = "\n".join( f"[{n.upper()}] (rep={weights.get(n,0):.3f}): {o}" for n, o in analyst_opinions.items() ) risk_block = "\n".join(f"[{n.upper()}]: {v}" for n, v in risk_opinions.items()) chairman_input = ( f"{brief}\n\n" f"━━━ ANALYST OPINIONS (weighted) ━━━\n{analyst_block}\n\n" f"━━━ VOTE TALLY ━━━\n" f"BUY: {vote_tally['buy']} | SELL: {vote_tally['sell']} | " f"NO_SIGNAL: {vote_tally['no_signal']}\n" f"Weighted: BUY={vote_tally['weighted']['buy']:.3f} " f"SELL={vote_tally['weighted']['sell']:.3f}\n\n" f"━━━ RISK VERDICTS ━━━\n{risk_block}\n\n" f"━━━ RISK GATE ━━━\n" f"APPROVED: {risk_tally['approved']} | CAUTION: {risk_tally['caution']} | " f"BLOCKED: {risk_tally['blocked']}\n\n" f"━━━ DELIVER FINAL VERDICT ━━━" ) msgs = [{"role": "system", "content": chairman_prompt}, {"role": "user", "content": chairman_input}] chairman_raw = _call_api(msgs, temperature=0.15, max_tokens=400, label="CHAIRMAN")
Фаза III: Председатель. Видит всё: рыночный брифинг, все четырнадцать докладов с репутационными весами, итоговое голосование аналитиков, вердикты риск-менеджеров.
analyst_block = "\n".join( f"[{n.upper()}] (rep={weights.get(n,0):.3f}): {o}" for n, o in analyst_opinions.items() ) risk_block = "\n".join(f"[{n.upper()}]: {v}" for n, v in risk_opinions.items()) chairman_input = ( f"{brief}\n\n" f"━━━ ANALYST OPINIONS (weighted) ━━━\n{analyst_block}\n\n" f"━━━ VOTE TALLY ━━━\n" f"BUY: {vote_tally['buy']} | SELL: {vote_tally['sell']} | " f"NO_SIGNAL: {vote_tally['no_signal']}\n" f"Weighted: BUY={vote_tally['weighted']['buy']:.3f} " f"SELL={vote_tally['weighted']['sell']:.3f}\n\n" f"━━━ RISK VERDICTS ━━━\n{risk_block}\n\n" f"━━━ RISK GATE ━━━\n" f"APPROVED: {risk_tally['approved']} | CAUTION: {risk_tally['caution']} | " f"BLOCKED: {risk_tally['blocked']}\n\n" f"━━━ DELIVER FINAL VERDICT ━━━" ) msgs = [{"role": "system", "content": chairman_prompt}, {"role": "user", "content": chairman_input}] chairman_raw = _call_api(msgs, temperature=0.15, max_tokens=400, label="CHAIRMAN")
Температура Председателя — 0.15; у аналитиков — 0.85. Председатель не должен быть творческим. Он должен быть предсказуемым.
Председатель и процедура голосования
Промпт Председателя — это не просто описание роли, это жёстко зашитая процедура с конкретными числовыми условиями:
def _build_chairman_prompt(vote_threshold: int, motivation_ctx: str) -> str: return ( "You are THE CHAIRMAN — supreme decision-maker of the world's most elite hedge fund. " "40% annual returns for 15 years. Every missed trade is a cost. Inaction is NOT safe.\n" "CORE PHILOSOPHY: The market always has a lean. Your job is to find it.\n\n" "DECISION FRAMEWORK:\n" "STEP 1 — RISK GATE:\n" f" • 4 BLOCKED → 'hold'. Non-negotiable.\n" f" • 3 BLOCKED → 'hold' unless analyst consensus is overwhelming (9+).\n" f" • 2 BLOCKED → proceed only with strong consensus ({vote_threshold}+ analysts).\n" f" • 1 BLOCKED → proceed with normal consensus ({vote_threshold-1}+ analysts).\n" f" • 0 BLOCKED → proceed, even with {vote_threshold-2}+ analysts.\n" f"STEP 2 — ANALYST VOTE: Threshold = {vote_threshold} (dynamic).\n" f" • {vote_threshold}+ analysts BUY → BUY signal.\n" f" • {vote_threshold}+ analysts SELL → SELL signal.\n" " • Weighted votes apply: higher-reputation analysts count more.\n" f" • 1 below threshold: if 0-1 BLOCKED → APPROVE the signal.\n" f" • 2+ below threshold: lean on direction of most recent momentum.\n" " • True 'hold' only when buy/sell votes within 1 AND risk is elevated.\n" "STEP 3 — FUND CONTEXT:\n" " AGGRESSIVE MODE: lower threshold by 1, push for signals.\n" " HUNGRY: prefer active signals.\n" " COMFORTABLE: discipline, but still prefer signals over inaction.\n\n" f"{motivation_ctx}\n\n" 'Reply ONLY with JSON: {"signal":"buy"|"sell"|"hold",' '"comment":"chairman reasoning max 200 chars",' '"analyst_votes":{"buy":N,"sell":N,"no_signal":N},' '"risk_verdicts":{"approved":N,"caution":N,"blocked":N},' '"weighted_conviction":0.0}' )
Обратите внимание: порог голосования vote_threshold — это параметр, не константа. Он вычисляется динамически в зависимости от состояния репутационного движка:
def get_weighted_vote_threshold(self) -> int: if self.aggression_mode: return 4 # фонд критически голоден — снижаем планку if self.hunger_level > 0.4: return 5 # умеренный голод return 6 # стандартный режим
В стандартном режиме нужно шесть аналитиков. При агрессивном режиме — четыре. Один и тот же рыночный расклад получит разный ответ в зависимости от того, насколько фонд выполнил дневные цели.
Репутационный движок: кто заслужил доверие
Каждая пара имеет свой ReputationEngine. Каждый аналитик начинает с репутацией 50.0 и изменяет её по результатам торговли:
REWARD_CORRECT = +8.0 # правильный прогноз REWARD_CORRECT_BIG = +15.0 # крупный прибыльный прогноз (PnL > порога) PENALTY_WRONG = -6.0 # неверный прогноз PENALTY_WRONG_BIG = -12.0 # крупный убыточный прогноз REWARD_STREAK_BONUS = +3.0 # бонус за серию 3+ правильных подряд PENALTY_STREAK_MULT = 1.5 # множитель штрафа при серии 3+ ошибок REPUTATION_DECAY = 0.995 # постепенное угасание каждый цикл
Параметр REPUTATION_DECAY = 0.995 важен. Каждый цикл репутация умножается на 0.995. Без этого аналитик, набравший 90 очков в первый месяц, сохранял бы доминирование вечно — даже если рыночный режим изменился и его методология перестала работать. Decay заставляет постоянно доказывать актуальность.
Кроме decay, каждые 15 минут работает фоновый демон:
def _decay_daemon(): while True: time.sleep(900) for port, ctx in _port_contexts.items(): ctx.reputation.decay_reputations() # логируем топ-3 и аутсайдеров на каждой паре
При падении репутации ниже 15 аналитик получает принудительную смену стиля — style_override = "ADAPTIVE" . Его промпт расширяется инструкцией быть более взвешенным и осторожным. Это не исключение из голосования — это «короткий поводок» от управляющего.
Веса в голосовании рассчитываются на основе репутаций:
def get_analyst_weights(self) -> dict: names = ["victor","maria","elena","dmitri","chen", "isabella","marcus","yuki","rafael","sophie"] reps = {n: max(self.analysts[n]["reputation"], 1.0) for n in names} total = sum(reps.values()) return {n: round(reps[n] / total, 4) for n in names}
Victor с репутацией 80 имеет в три раза больший вес, чем Maria с репутацией 25. Председатель видит не только {buy: 6, sell: 2, no_signal: 2} , но и {weighted_buy: 0.61, weighted_sell: 0.19} — принципиально разная картина, если шесть голосов принадлежат аналитикам с низкой репутацией.
Мотивационный движок: фонд как живой организм
Между репутацией и торговым решением стоит ещё один слой — мотивационный контекст. Председатель получает его в каждом запросе:
def get_motivation_context(self) -> str: lines = ["\n╔═══ FUND MOTIVATION CONTEXT ═══╗"] lines.append(f"Daily PnL Goal : {daily['current']:+.1f} / {daily['target']:.0f} ({daily['pct']:.0f}%)") lines.append(f"Fund Total PnL : {self.total_pnl:+.2f}") lines.append(f"Hunger Level : {'█' * int(hunger*10)}{'░' * (10-int(hunger*10))} {hunger*100:.0f}%") if self.aggression_mode: lines.append("[!] MODE: AGGRESSIVE — Goals severely undermet. Chairman must PUSH for trades.") lines.append(" -> Lower your threshold. Signal BUY/SELL with 5+ analysts instead of 7+.") elif hunger > 0.4: lines.append("[~] MODE: HUNGRY — Goals partially behind. Prefer active signals over HOLD.") else: lines.append("[+] MODE: COMFORTABLE — Goals on track. Maintain standard discipline.") lines.append("Recent streaks :") for name in ["victor", "maria", "elena", "dmitri", "chen"]: a = self.analysts[name] s = a["streak"] symbol = f"[+{s}]" if s > 2 else (f"[-{s}]" if s < -2 else f" {s:+d} ") lines.append(f" {name.upper():12} rep={a['reputation']:5.1f} streak={symbol}") lines.append("╚════════════════════════════════╝") return "\n".join(lines)
Фонд с выполненной дневной целью становится консервативным. Фонд, критически отставший, переходит в агрессивный режим и снижает порог с шести до четырёх. Это прямое управление через мотивационный контекст в промпте Председателя. Одни и те же рыночные данные дадут разный ответ в 09:00 (фонд голоден) и в 17:00 (дневная цель выполнена на 90%).
Обратная связь: петля, которая замыкает систему
После закрытия позиции советник сообщает Python о результате:
void ReportLastTrade(ENUM_POSITION_TYPE closedType) { HistorySelect(TimeCurrent() - 3600, TimeCurrent()); double totalPnL = 0.0; int deals = HistoryDealsTotal(); for(int i = deals - 1; i >= MathMax(0, deals - 5); i--) { ulong ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket, DEAL_MAGIC) != g_InpMagic) continue; if(HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol) continue; if(HistoryDealGetInteger(ticket, DEAL_ENTRY) != DEAL_ENTRY_OUT) continue; totalPnL += HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_SWAP) + HistoryDealGetDouble(ticket, DEAL_COMMISSION); break; } string sig = (closedType == POSITION_TYPE_BUY) ? "buy" : "sell"; string resp = SendRawCmd(StringFormat("RESULT:%s:%s:%.2f", _Symbol, sig, totalPnL)); }
Python получает команду RESULT:EURUSD:buy:87.50 и обновляет репутации всех аналитиков, которые голосовали за эту сделку.
Получается замкнутый цикл: результаты сделок влияют на репутации, репутации — на веса голосов, а веса — на следующее решение. Система не просто принимает решения — она учится на их последствиях, хотя и ограниченным образом: знает, чьи прогнозы оказались правы, но не знает, почему.
Торговая логика советника: три состояния
Советник AIHedgeFund_v4_clean.mq5 намеренно устроен просто. Весь алгоритм управления позицией — это ExecuteSignal() :
void ExecuteSignal() { if(g_tradingHalted) return; bool hasBuy = HasPositionType(POSITION_TYPE_BUY); bool hasSell = HasPositionType(POSITION_TYPE_SELL); if(g_lastSignal == "buy") { // Обратный сигнал — немедленно закрыть SELL if(hasSell) { CloseAllByType(POSITION_TYPE_SELL); ReportLastTrade(POSITION_TYPE_SELL); } // Открыть BUY если позиции нет if(!hasBuy) { g_avgLevelBuy = 0; g_partialDoneBuy = false; ArrayInitialize(g_avgEntryBuy, 0.0); OpenBuy(InpBaseLot, "AI BUY L0"); } } else if(g_lastSignal == "sell") { if(hasBuy) { CloseAllByType(POSITION_TYPE_BUY); ReportLastTrade(POSITION_TYPE_BUY); } if(!hasSell) { g_avgLevelSell = 0; g_partialDoneSell = false; ArrayInitialize(g_avgEntrysSell, 0.0); OpenSell(InpBaseLot, "AI SELL L0"); } } // HOLD — позиция живёт, ничего не делаем }
BUY при открытой BUY — советник не добавляет вторую позицию. Повторный сигнал — это подтверждение, а не новый вход. Позиция продолжает жить со своим исходным SL и TP.
HOLD при любой открытой позиции — ничего не происходит. Позиция ждёт либо смены сигнала, либо брокерского SL/TP.
Переворот (BUY при открытой SELL) — немедленное закрытие SELL с отчётом в Python, затем открытие BUY. Два действия, чистый переворот.
Открытие позиции:
bool OpenBuy(double lot, string comment) { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double sl = (InpBaseSL_Pts > 0) ? NormalizeDouble(ask - InpBaseSL_Pts * pt, _Digits) : 0; double tp = (InpBaseTP_Pts > 0) ? NormalizeDouble(ask + InpBaseTP_Pts * pt, _Digits) : 0; if(Trade.Buy(lot, _Symbol, ask, sl, tp, comment)) { g_totalBuys++; if(g_avgLevelBuy < 6) g_avgEntryBuy[g_avgLevelBuy] = ask; if(InpVerboseLog) PrintFormat("[OK] BUY %.2f lot @ %.5f SL=%.5f TP=%.5f [%s]", lot, ask, sl, tp, comment); return true; } PrintFormat("[ERR] BUY ERROR: %d %s", Trade.ResultRetcode(), Trade.ResultRetcodeDescription()); return false; }
SL и TP выставляются сразу в одном ордере. Если соединение с сервером пропадёт — позиция останется защищённой стоп-ордерами на стороне брокера.
Закрытие выполняется с конца массива (i = PositionsTotal() - 1). Это стандартная практика MQL5: при закрытии позиции индексы пересчитываются, и обход с начала может пропустить следующую позицию.
Что видит советник в журнале
Журнал MetaTrader при работе системы читается как стенограмма профессионального комитета:
[12:41:03] [EURUSD:8971] threshold=5 | hunger=0.42 | aggression=off [12:41:03] [EURUSD] ══ Phase I: 10 Analysts ══ [12:41:03] ↳ Analyst [VICTOR] thinking... [12:41:03] ↳ Analyst [DMITRI] thinking... [12:41:08] ✓ [SOPHIE]: BUL=7 BEA=3 → BUY signal confirmed by score. [12:41:08] ✓ [CHEN]: Vol Ratio=1.49x, BULL candle — approaching threshold. [12:41:09] ✓ [VICTOR]: Price above MA20/MA50, momentum +0.00418 — BUY. [12:41:09] [EURUSD] Phase I: BUY=6 SELL=2 NO_SIGNAL=2 [12:41:09] [EURUSD] ══ Phase II: 4 Risk Managers ══ [12:41:12] ✓ [ALEXEI]: APPROVED — ATR%=0.18%, BB Width=0.61%, within limits. [12:41:12] ✓ [JAMES]: APPROVED — 4/5 MAs aligned upward, trending regime. [12:41:13] ✓ [HELENA]: CAUTION — Body/ATR=0.85, slightly elevated. [12:41:13] ✓ [NATASHA]: APPROVED — RSI7=61, Vol=1.49x, no tail events. [12:41:13] [EURUSD] Phase II: APPROVED=3 CAUTION=1 BLOCKED=0 [12:41:13] [EURUSD] ══ Phase III: The Chairman (threshold=5) ══ [12:41:16] ✓ [EURUSD CHAIRMAN]: {"signal":"buy","comment":"6/10 BUY, 0 BLOCKED..."} [12:41:16] [EURUSD] ══ FINAL: BUY | conviction=0.71 ══ >> BAR #47 | [BUY] conviction=0.71 | hunger=0.42 | thr=5 | 6/10 BUY, momentum+volume [OK] BUY 0.01 @ 1.08621 SL=1.07821 TP=1.10221
Шесть аналитиков из десяти проголосовали BUY. Двое против. Двое воздержались. Все четыре риск-менеджера либо одобрили, либо выразили осторожность — ни одного BLOCKED. Порог 5 (умеренный голод). Шесть больше пяти. Председатель дал BUY.
Особенно важно то, что NO SIGNAL несёт информацию. Каждое молчание в системе — это данные, а не отсутствие данных. Если Чэнь молчит — это конкретный факт: объём не подтверждал движение.
Обратная совместимость
Переход с предыдущей версии — это одно слово в коде советника:
// Режим одного аналитика (старые версии): string cmd = "PRICES:EURUSD:" + csv; // Режим четырёх дебатов (предыдущая статья): string cmd = "DEBATE:EURUSD:" + csv; // Режим совета пятнадцати (эта статья): string cmd = "COUNCIL:EURUSD:" + csv;
Сервер понимает все три префикса. Советник, который умеет читать только поля signal и comment , продолжит работать без изменений — расширенный блок council со всеми пятнадцатью голосами он просто проигнорирует при парсинге.
Зависимости не изменились: Python 3.8+, requests , numpy . Всё остальное — стандартная библиотека.
Запуск системы
Python-сервер:
python llm_hedge_fund_v4.pyСервер выведет карту портов и запустит восемь WebSocket-серверов:
QUANTUM AI HEDGE FUND v4.0 — MULTI-PORT / MULTI-PAIR ┌─ PAIR → MAGIC → PORT ──────────────────┐ │ EURUSD Magic=30001 Port=8971 │ │ GBPUSD Magic=30002 Port=8972 │ │ ... │ │ XAUUSD Magic=30008 Port=8978 │ └────────────────────────────────────────┘ INITIAL REPUTATION: all analysts start at 50.0 per port
MetaTrader 5: открыть восемь графиков, на каждый прикрепить AIHedgeFund_v4_clean.mq5 , выбрать соответствующий пресет. Magic и Port вычисляются автоматически.
Рекомендуемые параметры для начала:
| Параметр | Значение | Комментарий |
|---|---|---|
| InpBaseLot | 0.01 | минимальный лот при тестировании |
| InpBaseSL_Pts | 800 | стандарт для H1 |
| InpBaseTP_Pts | 1600 | соотношение 1:2 |
| InpDailyLossLimitPct | 3.0 | жёсткий стоп на день |
| InpPriceBars | 100 | 100 баров достаточно для всех индикаторов |
| InpAnalysisBars | 1 | совет на каждый новый бар |
| InpReceiveMs | 60000 | 60 секунд таймаут для 15 агентов |
Типичное время ответа при grok-3-fast : фаза I — 5–8 секунд, фаза II — 3–5 секунд, Председатель — 2–4 секунды. Итого 10–17 секунд на полный цикл. Для H1 это незаметно. Для M5 — уже на границе. Для M1 — неприемлемо.
Результаты бэктеста системы
До этого момента архитектура Council of 15 выглядела убедительно на уровне идей: десять аналитиков с декоррелированными философиями, четыре риск-менеджера с независимыми осями оценки, Председатель с жёсткой процедурой голосования, репутационный движок, который помнит, кто ошибался. Но в трейдинге архитектура сама по себе ничего не стоит, если её нельзя прогнать через беспощадную машину фактов — Strategy Tester. Именно там заканчиваются красивые схемы и начинается единственный разговор, который имеет значение.

Бэктест проводился на AIHedgeFund_v4_clean.mq5 — чистой версии советника без усреднения и трейлинга. Это принципиально: результаты отражают качество именно AI-решений, а не дополнительной торговой механики. Важно: на разных бэктестах результаты могут отличаться, иногда довольно сильно, разброс результатов велик, так как это всё-таки ИИ - система трейдинга.
| Параметр | Значение |
|---|---|
| Инструмент | EURUSD |
| Таймфрейм | H1 |
| Период | 01.10.2025 — 25.11.2025 (~8 недель) |
| Начальный депозит | $10 000 |
| Лот | 0.5 |
| Stop Loss | 400 пунктов (40 пипсов) |
| Take Profit | 300 пунктов (30 пипсов) |
| Анализ | каждые 6 баров (раз в 6 часов) |
| Качество истории | 100% (тиковые данные) |
| Брокер | RoboForex-ECN |
Пояснения к параметрам: SL 400 и TP 300 — это соотношение риск/прибыль 1:0.75 в пользу стопа. Это нетипичная конфигурация для ручных систем, но логика здесь другая: система торгует часто, закрывает позиции не только по TP/SL, но и по обратному сигналу совета, и рассчитана на то, что высокий winrate компенсирует более широкий стоп. Проверим, работает ли это предположение.
| Метрика | Значение |
|---|---|
| Чистая прибыль | $2 471.17 (+24.7%) |
| Profit Factor | 2.09 |
| Sharpe Ratio | 6.31 |
| Recovery Factor | 6.16 |
| LR Correlation | 0.97 |
| Максимальная просадка (equity) | 3.57% ($401.07) |
| Максимальная просадка (balance) | 2.78% ($303.15) |
| Всего сделок | 59 |
| Прибыльных сделок | 38 (64.41%) |
| Убыточных сделок | 21 (35.59%) |
| Средняя прибыльная сделка | $124.91 |
| Средний убыток | $106.72 |
| Максимальная серия убытков | 2 подряд |
| Среднее время в сделке | 14 часов 1 минута |
За восемь недель система совершила 59 сделок и заработала $2 471 при депозите $10 000. Profit Factor 2.09 — это означает, что на каждый потерянный доллар система приносила два. Это сильный показатель.

Sharpe Ratio 6.31 — это не опечатка. Для сравнения: профессиональные хедж-фонды считают Sharpe выше 1.0 хорошим результатом, выше 2.0 — отличным. Значение 6.31 объясняется сочетанием двух факторов: высокий winrate (64%) при одновременно очень низкой волатильности результатов — максимальная серия убытков составила всего 2 сделки подряд. Система не уходила в долгие просадки.

LR Correlation 0.97 — коэффициент корреляции кривой капитала с прямой линией. Значение близкое к 1.0 означает, что рост депозита был равномерным, без резких провалов и скачков. Не «заработали всё за три дня и потом стояли», а стабильный прирост неделя за неделей.
Recovery Factor 6.16 показывает, что система заработала в 6.16 раза больше, чем составила максимальная просадка. При просадке $401 — прибыль $2 471. Это хорошее соотношение.
Чистая архитектура советника позволяет чётко разделить источники закрытий:
- По Take Profit — 29 сделок (49%). Почти половина сделок доходила до целевого уровня. Это означает, что совет достаточно часто давал сигналы в правильном направлении с достаточной уверенностью — позиция успевала пройти 300 пунктов до того, как пришёл обратный сигнал.
- По обратному сигналу совета — 24 сделки (41%). Четыре из десяти сделок закрылись не по TP/SL, а потому что Председатель сменил направление. Это ключевая особенность системы: она не ждёт стоп-ордер механически, а реагирует на изменение рыночной картины. Часть этих закрытий — преждевременная фиксация, часть — спасение от убытка, который иначе дошёл бы до SL.
- По Stop Loss — 6 сделок (10%). Только каждая десятая сделка дошла до жёсткого стопа. Это говорит о том, что обратный сигнал совета в большинстве случаев срабатывал раньше, чем цена доходила до SL. Риск-менеджеры и Председатель успевали заметить изменение картины до того, как убыток становился максимальным.
Распределение по направлениям: шорты — 36 сделок с winrate 66.67%, лонги — 23 сделки с winrate 60.87%. Медвежий уклон периода (октябрь-ноябрь 2025 на EURUSD) совпал с более точной работой системы на продажах.

Среднее время удержания позиции — 14 часов 1 минута. При H1 таймфрейме и анализе каждые 6 часов это примерно 2-3 новых бара до очередного запроса совета. Система не скальпирует — она держит позиции через несколько сессий.
Минимальное время в сделке — 1 час 6 минут. Это случаи, когда уже на следующем баре пришёл обратный сигнал. Максимальное — 69 часов 16 минут, почти три дня. Такие длинные позиции характерны для случаев, когда совет устойчиво удерживал одно направление через несколько циклов анализа.

Максимальная серия убытков — 2 подряд. За восемь недель и 59 сделок система ни разу не показала трёх убытков подряд. Средняя серия убытков — 1. Это прямое следствие того, что при срабатывании SL система немедленно перестраивается: убыток чаще всего означает, что рыночный контекст изменился, и следующий запрос совета уже даёт другой сигнал.
Тест проводился с лотом 0.5 при депозите $10 000. Это 5% от депозита на сделку при SL 400 пунктов — достаточно агрессивная конфигурация. При более консервативном лоте 0.01 все метрики масштабируются линейно: прибыль $4.94, Sharpe и Profit Factor остаются теми же. Числа в таблице выше отражают конкретную конфигурацию теста, а не «рекомендуемые параметры».
Второе: период теста — октябрь-ноябрь 2025 года. Это два конкретных месяца с конкретным характером рынка. Результаты могут существенно отличаться на других периодах, особенно в боковых рынках с малым ATR, где даже при хорошем голосовании совета движений для достижения TP недостаточно.
Третье: соотношение SL/TP = 400/300 выглядит нелогичным (стоп шире цели), но практика показала, что это работает при winrate выше 57%. При 64% математическое ожидание положительное: 0.64 × $124 − 0.36 × $107 ≈ +$41 на сделку. Именно это значение и показывает метрика Expected Payoff: $41.88.
Подтверждено: совет пятнадцати аналитиков с репутационными весами и мотивационным контекстом способен генерировать сигналы с положительным математическим ожиданием на реальных данных. Высокий winrate при минимальных сериях убытков говорит о том, что фильтрация через четыре независимых риск-менеджера работает — система действительно допускает меньше неудачных входов, чем допустил бы одиночный алгоритм.
Не подтверждено: устойчивость к смене рыночного режима. Восемь недель — слишком короткий период для выводов о поведении системы в разных режимах. Нет данных о работе в боковом рынке с узким ATR, в периоды повышенной новостной волатильности, и на других валютных парах с иными корреляционными свойствами.
Также не проверено поведение системы при заниженной агрессивности: в тесте порог голосования динамически опускался до 4 при голоде фонда. Насколько часто это происходило и как повлияло на качество сигналов — отдельный вопрос для анализа.
Результаты бэктеста — это не доказательство того, что система будет работать в будущем. Это доказательство того, что на выбранном периоде архитектура коллективного принятия решений показала измеримое преимущество перед случайным выбором. Это достаточное основание для следующего шага: форвардного тестирования на демо-счёте с реальными задержками API.
Честный разговор об ограничениях
Система из пятнадцати голосов производит впечатление. Это впечатление нужно намеренно охлаждать.
Все пятнадцать участников — это одна и та же модель grok-3-fast с разными системными промптами. Их независимость — это независимость точек зрения, а не независимость весов нейронной сети. Если базовая модель систематически ошибается в каком-то специфическом рыночном паттерне — все пятнадцать «аналитиков» будут ошибаться там одновременно. Консенсус пятнадцати копий одной модели — не то же самое, что консенсус пятнадцати независимо обученных моделей.
Второй предел — параллельность на восьми парах. При одновременной работе на восьми символах система делает до 14 параллельных API-вызовов на пару, то есть до 112 запросов в одном цикле анализа. Нужно понимать, какой тарифный план выдержит такую нагрузку.
Третье: репутационная память ограничена текущей сессией процесса. При перезапуске Python-сервера все репутации сбрасываются до 50.0. Решение — персистентное хранилище (SQLite), это следующий шаг.
Четвёртое: советник не знает, что произошло после предыдущего сигнала. Каждое решение принимается в вакууме последних 100 баров цен. Система не видит, что неделю назад при похожей конфигурации голосов Председатель оказался неправ. Именно это и является главным аргументом в пользу следующего шага: дать системе долгосрочную память и механизм оценки качества собственных решений на исторических данных.
Заключение
Мы начали с одного голоса. Предыдущие статьи показали, что четыре голоса с разными ролями дают качественно иной результат. Эта статья сделала следующий шаг: пятнадцать голосов с принципиально разными философиями, работающие параллельно на восьми валютных парах с изолированными репутационными движками и мотивационной системой.
Технически это прямое расширение предыдущей версии. Та же Python-архитектура, тот же WebSocket-протокол, те же технические индикаторы на чистом NumPy. Новое — мультипортовость, восемь изолированных контекстов, репутационный движок с обратной связью и мотивационный контекст, который делает пороги голосования живыми, а не фиксированными.
Советник AIHedgeFund_v4_clean.mq5 намеренно лишён лишней механики: нет усреднения, нет трейлинга, нет частичного закрытия. Только сигнал совета, SL, TP и закрытие по обратному сигналу. Это позволяет оценить качество именно AI-решений, без наслоений дополнительной логики.
Результат — это не просто BUY, SELL или HOLD, это стенограмма профессионального инвестиционного комитета с десятью специалистами, четырьмя риск-менеджерами и одним Председателем. Каждый голос именован, каждый аргумент виден в журнале, каждое вето риск-менеджера объяснёно конкретными числами.
Полный код llm_hedge_fund_v4.py и AIHedgeFund_v4_clean.mq5 доступен в приложении к статье.
| Название файла | Назначение файла |
|---|---|
| llm_hedge_fund_v4.py | Сервер нашего ИИ-хедж-фонда |
| AIHedgeFund_v4.mq5 | Советник (трейдер - исполнитель фонда) |
| winhttp.mqh | Библиотека сокет - связи |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Неопределённость как модель (Часть 5): Основы регрессии
Тестовые чемпионы против реальных задач оптимизации
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования