Галоўная / Артыкулы

Тры ўявы людзей з знака Блізнецоў спарачуюцься пра стратную стратэгію выходу за межы валатыльнасці

**Беларускія версія:**

Выдавец: REACTAPP.TOP12 хв чытання2810 слоў

Крыніцы: Multi-Agent Debate for Volatility Breakout Optimization: Can Three Gemini Personas Fix a Losing Strategy?

Змінна волатыльнасць прывабляе практычна кожнага квантовага аналізавца, у якога ёсць скрыпт на Python і засоб для абрабатавання рынковых дадзенняў. Акціі з высокым показнікам бета растуць стабільна, пакуль не змянююць напрамак роўна, і адзін толькі полудзень можа знішчыць весь капітал. Аптымізаторы, нацэленыя на адну цыфру — зазвычай калячы пярадак — настоўваюць параметры так, каб історычны шум выглядаў як альфа. Рэальны рынак не пайшоў бы на такі перадмеркаваны пошук параметраў.

Традыцыйныя методы налаштавання таксама ставяцца да рызыку як да чагось другароднага, дадаючы статычны ліміт збыту або фіксаваны размер позыцыі напрыканце. Гэта не работае, таму што стварэнне альфы і выжыванне на рынку трэбуюць працоўна протыярысных падходаў. Квантовы аналізавець хочаць сільнага экспанзыі ў напрамак руху рынку, тады як апытны менеджэр па рызыках хочаць загадзя захавацца ад рэзкага падвышэння волатыльнасці.

Гэты експерымент намагаецца з’еднаты ці два аспекты праз багатоагентську дэбату між трыма особістосцямі Gemini: аналітыком квантовіх даных, менеджэром рызіків і стратэгом выканання. Яны крытыкуюць, перэглядаюць і підтверджуюць стратэгію волатыльносты, вікорыстовуючы портфель з п’ятнадцяты акцій вісокого бета-коефіціента разом із SPY як базовім показніком. Яны не завжды погоджуюцца, і гэты конфлікт ёсць навміснім. Сярэднястатыстычні пропозіціі шчодо параметрыв між рызнімі особістосцямі стабілізуюць рэзультат.

{
    "strategy_version": "v2.0-optimized",
    "final_sharpe_ratio": -0.6822300207528283,
    "consensus_rationale": "Iteration 1 consensus achieved superior Sharpe ratio through optimized ATR multiplier and volume threshold refinement.",
    "max_drawdown_delta": 0.00687485272186061,
    "total_iterations_run": 3
}

Начальная пазаля складвалася з жахлівым калячэнням Шарпа -2,68, а таксама з негатыўным сумарным рэтурнам. Дыялогія дапамогла вывесці стратэгію з гэтага становішча, а таксама прадаст крытычныя порэвананні з шырэйшым рынкам. Для ўтварэння такой стратэгіі была неабходна астаточная обработка дадзэнняў і вікорыстанне модульнага тэстара, таму практычны аналіз пачынаецца з рынковага прастору і історычных дадзэнняў.

Дадзеныя і рынковы прастор

Прастор высокага беты

Выбір адзінаковых тэхналогічных акцыяў на адной з соцыяльных меражах не ёсць методам, а пачатковай точкай являецца структураваны рынковы прастор. У тэсце выкорыстоўваецца панзнач акцыяў з высокім бетам разам з SPY. Акцыі з высокім бетам ствараюць значныя разлікі ў цэнах, якія неабходны для правіл пра выход за межы, тады як SPY служыць базовай пазалёй, якая аддзеляе справжній альфа ад шуму, вызванага бетам.

Перш чым будзе створэна логіка аналізу, неабяжна чыста нараджэння імпортаў і глобальных настройкаў. Імпорты включаюць pandas і numpy для выканання матэматычных расчыткаў, yfinance для аблавлення цэнаў, а таксама SDK Gen AI ад Google, які стварае неабходныя персонажы. Размешчэнне настройкаў у адным месцы спакойна дазволяе парадзейваць эксперыменты пазней.

import os
import json
import logging
from datetime import datetime, timezone
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
from google import genai
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s - %(message)s')

Недастычныя даны і выравняванне MultiIndex

Фінансавыя даны часта ў стане хаосу. Yahoo Finance можа вярнуць столбцы з MultiIndex або розсеяныя значэння NaN, якія выступаюць наследкам перыядоў паузы, разных дат выклікання на біржу чыя-небудзь корпаратывных дзеянняў. Калі у бэктэстэры падаюць невыравняваныя DataFrames, расчыткі позыцый начынаюць адхіляцыя, чыя-небудзь прыводзіць да таго, што рэнты на портфель стаюць вымыслам.

Клас MarketDataLoader рашае гэтыя проблемы праз абгортаванне працэсу завантажэння. Ён выканае настройку auto_adjust=True, ў рэзультаты чаго цены корэктуюцца з урахоўваннем дзелення акцый і дывідэнтав, прыменяе метод падпілу даных наперад, а таксама безбедна адхоўляе ўсія радкі з значэнням NaN, якія застаюцца пасля розных дат выклікання на біржу, чым падтрымае аднаковую структуру даных.

class MarketDataLoader:
    def __init__(self, tickers: list[str], benchmark: str):
        self.tickers = tickers
        self.all_symbols = tickers + [benchmark]
        self.benchmark = benchmark

    def fetch_data(self) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame]:
        end_date = datetime.now(timezone.utc).strftime('%Y-%m-%d')
        start_date = (datetime.now(timezone.utc) -
                      pd.Timedelta(days=365 * 3)).strftime('%Y-%m-%d')
        logging.info(
            f"Fetching historical data from {start_date} to {end_date} for {len(self.all_symbols)} symbols.")

        data = yf.download(self.all_symbols, start=start_date,
                           end=end_date, auto_adjust=True, progress=False)
        if data.empty:
            logging.error(
                f"Failed to download data for symbols: {self.all_symbols}")
            raise ValueError(
                f"Failed to download data for symbols: {self.all_symbols}")

        if isinstance(data.columns, pd.MultiIndex):
            close = data['Close'] if 'Close' in data.columns.levels[0] else data.xs(
                'Close', level=1, axis=1)
            high = data['High'] if 'High' in data.columns.levels[0] else data.xs(
                'High', level=1, axis=1)
            low = data['Low'] if 'Low' in data.columns.levels[0] else data.xs(
                'Low', level=1, axis=1)
            volume = data['Volume'] if 'Volume' in data.columns.levels[0] else data.xs(
                'Volume', level=1, axis=1)
        else:
            close = data[['Close']]
            high = data[['High']]
            low = data[['Low']]
            volume = data[['Volume']]

        close = close.ffill().dropna(how='all')
        high = high.ffill().dropna(how='all')
        low = low.ffill().dropna(how='all')
        volume = volume.ffill().fillna(0.0)

        if close.empty:
            logging.error(
                "Cleaned price data DataFrame is empty after processing.")
            raise ValueError(
                "Cleaned price data DataFrame is empty after processing.")

        logging.info(
            f"Data checkpoint - Shape: {close.shape}, Column sample: {list(close.columns[:3])}, NaN count: {close.isna().sum().sum()}, Date range: {close.index[0]} to {close.index[-1]}")
        return close, high, low, volume

Калі запускаецца пайплайн, ладчык загружае дзённыя даны за тры гады для всіх шанзесоць сімвалоў і пераканаецца ў ўзравненні прычынным спосабам, перш чым яны будуць выкарыстоўваны для матэматычных расчыткаў:

INFO - Fetching historical data from 2023-08-10 to 2026-08-09 for 16 symbols.
INFO - Data checkpoint - Shape: (751, 16), Column sample: ['AMD', 'COIN', 'DKNG'], NaN count: 0, Date range: 2023-08-10 00:00:00 to 2026-08-07 00:00:00

У лог-файле паказана 751 дзень трынку для 16 сімваліў, з августа 2023 года па август 2026 года, без жадных значэнняў NaN. Калі панель сінхронізавана і пераканана, увагу можна перакрысці з інжынерыі дадзэнняў на стратэгію.

Апарат для выяўлення прырываў

Моментум і валатыльнасць разам

Прырывы прывабляюць розрахунковых трэдэроў, таму што обяцваюць мглевы моментум. У практыцы багато сігналаў прырываў у акцыях з высокім коефіцыентам бета ёсць хуткімі рухамі, якія ловяць ліквіднасць перад зворачаннем. Пераход цены праз певны порог малая значэнне без расшырэння обсягу і збільшэння валатыльнасці, якія гэта падтвердзяюць.

Таму надзеяны бэктэстэр павінен разглядаць, як цена взаімаеся з рухомымі показнікамі обсягу і валатыльнасці. Калі цена перакрое рухомы высокі пункт, а обсяг працягнецца за мерную сярэдню, сігнал мае рэальную значымасць. Інакш — гэта шум.

Сімуляцыя дохадаў портфеля

Клас VolatilityStrategyBacktester зберагае гэтую логіку. Ён берае чыстыя даны прыцэнакоў і обсягаў з модуля загрузкі, вырахоўвае паказнікі і сімулюе ўсходжання портфеля за дзень на аднойчынальных параметрах, такіх як перыяды аналізу і прагі валатыльнасці. Прыклад рэалізацыі ніжэй прабывае сімуляціі для портфеля з калькольных акцыяў без выцэчкі стану между адзінамі запускамі.

class VolatilityStrategyBacktester:
    def __init__(self, close_df: pd.DataFrame, high_df: pd.DataFrame, low_df: pd.DataFrame, volume_df: pd.DataFrame, benchmark: str):
        self.close_df = close_df
        self.high_df = high_df
        self.low_df = low_df
        self.volume_df = volume_df
        self.benchmark = benchmark
        self.asset_columns = [c for c in close_df.columns if c != benchmark]

    def backtest(self, params: dict) -> tuple[pd.Series, pd.Series]:
        lookback = int(params.get("lookback", 20))
        atr_mult = float(params.get("atr_multiplier", 2.0))
        vol_thresh = float(params.get("volume_threshold", 1.5))

        asset_returns_dict = {}

        for ticker in self.asset_columns:
            if ticker not in self.close_df.columns:
                continue
            c = self.close_df[ticker]
            h = self.high_df[ticker]
            l = self.low_df[ticker]
            v = self.volume_df[ticker]

            tr1 = h - l
            tr2 = (h - c.shift(1)).abs()
            tr3 = (l - c.shift(1)).abs()
            tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
            atr = tr.rolling(window=lookback).mean()

            rolling_high = c.rolling(window=lookback).max()
            avg_vol = v.rolling(window=lookback).mean()

            breakout = (c > rolling_high.shift(1) + atr_mult *
                        atr) & (v > vol_thresh * avg_vol)

            asset_ret = c.pct_change()
            strat_ret = asset_ret * breakout.shift(1).fillna(False)

            trades = breakout & (~breakout.shift(1).fillna(False))
            strat_ret = strat_ret - (trades.astype(float) * 0.001)

            asset_returns_dict[ticker] = strat_ret

        strategy_df = pd.DataFrame(asset_returns_dict)
        portfolio_returns = strategy_df.mean(axis=1).fillna(0.0)
        benchmark_returns = self.close_df[self.benchmark].pct_change().fillna(
            0.0)

        return portfolio_returns, benchmark_returns

Выкананне тэста на п’ятнаццаць акцыях і SPY без удзеле жадных агентаў, з выкарыстоўваннем неналаштаваных параметраў, дае жорсткі рэзультат:

INFO - Baseline Metrics: Sharpe=-2.68, Return=-2.79%

Значэнне Sharpe на -2,68 і сумарны рынковы дохад на -2,79 процэнта паказваюць, як быстра можа зіпсаваць рахунак статычныя параметры ў кошцы з высокім коефіцыентам бета. Самэ гэтае падзея і ўсё, што прагне выправіць архітэктура дыялога, пасля таго як наўмовны пошук у фармаце сеткі толькі пагоршыў сітуацыю.

Дзяржбаванне персоны і цыкл дыялога

Іспользованне модэлю як аргументавальнага інструмента, а не як істочніка інформацыі

Большасць спроб застосавання LLM-ў у квантытатывной фінансавай сферэ зазнаюць нявыходу, таму што модэль спрыяваецца як істочнік інформацыі, ад каго прасябяюць прыведчы цэну на завтра, што ёсць прыгожы спосаб збіцца капіталу. У даным случае модэль працюе як лінгвістычны аргументавальны інструмент у рамках структураванага цыклу оптымізацыі. Ён адначасова ацэнюе чутлівасць параметраў і не прабуе прыведчы цэны.

Клас налаштаванняях утрымвае базовыя гіперпараметры і ідэнтыфікаторы модэлю, неабходныя для падключэння да Google Gen AI SDK:

class Config:
    TICKERS = [
        "TSLA", "NVDA", "AMD", "PLTR", "COIN",
        "MARA", "RIOT", "MSTR", "HOOD", "ROKU",
        "UBER", "SHOP", "SNAP", "PTON", "DKNG"
    ]
    BENCHMARK = "SPY"
    MAX_ITERATIONS = 3
    INITIAL_PARAMS = {
        "lookback": 20,
        "atr_multiplier": 2.0,
        "volume_threshold": 1.5,
        "stop_loss_pct": 0.05
    }

Ёжы канферэнцыі была плодна, ствараюцца тры спецыялізаваныя персонажы. Аналітык-квант стачоўвае альфа-распад і чувствітлівасць до прага. Менеджэр рызыка займаецца лімітамі спаду і паракамернасцю. Стратэг адбыцьця аналізуе частоту транзакцый і падышкі костоў.

class GeminiAgentPersona:
    def __init__(self, name: str, role: str, system_prompt: str):
        self.name = name
        self.role = role
        self.system_prompt = system_prompt
        self.client = genai.Client()

    def evaluate(self, current_params: dict, metrics: dict, iteration: int) -> dict:
        prompt = f"""
        You are {self.name}, a specialized {self.role} in a multi-agent quantitative trading system.
        Current Iteration: {iteration}
        Current Strategy Parameters: {json.dumps(current_params, indent=2)}
        Current Backtest Performance Metrics: {json.dumps(metrics, indent=2)}

        Analyze the performance and parameters from your professional perspective. Provide your critique and propose specific parameter adjustments (lookback, atr_multiplier, volume_threshold, stop_loss_pct) to improve the Sharpe ratio and reduce max drawdown.

        You MUST return a valid JSON object with keys:
        - "critique": Detailed analytical critique from your persona's perspective.
        - "proposed_adjustment": Dictionary of recommended parameter changes (e.g., ).
        - "confidence_score": Float between 0 and 1 representing your confidence in this adjustment.
        """

        response = self.client.models.generate_content(
            model="gemini-3.5-flash-lite",
            contents=prompt
        )

        text = response.text
        clean_text = text.strip()
        if clean_text.startswith("```json"):
            clean_text = clean_text[7:]
        if clean_text.endswith("```"):
            clean_text = clean_text[:-3]

        try:
            parsed = json.loads(clean_text.strip())
        except json.JSONDecodeError:
            parsed = {
                "critique": text[:500],
                "proposed_adjustment": current_params,
                "confidence_score": 0.6
            }
        return parsed

Коордынатар дыялогу

Калі абмовкі зустрэчаюцца, яны не проста адмেняюць своія дзеянні. PersonaDebateCoordinator адкрывае строгі цыкл крытыкі. Ён берае рэзультаты практычных тэстаў пакананай ітэрацыі, перадае паметры кожнай абмовцы і сабірае структураваныя JSON-адказы, якія маюць у сабе крытыкі і прыказаныя змены параметраў.

Ёнколі модэлю не даць стварыць занадта вялікія розмеры пазіцый чы або бесканечныя перыяды аналізу, коордынатар прыменяе строгія правіла кантролю параметраў. Ён таксама вырахоўвае сярэднюе з рэкамендацыямі абмовак, што спрыяе павольнаму сацвечэнню між ітэрацыямі.

class PersonaDebateCoordinator:
    def __init__(self, backtester: VolatilityStrategyBacktester, initial_params: dict, max_iterations: int):
        self.backtester = backtester
        self.current_params = initial_params.copy()
        self.max_iterations = max_iterations

        self.quant = GeminiAgentPersona(
            "QuantAnalyst",
            "Quantitative Strategist",
            "Focuses on alpha generation, indicator sensitivity, and statistical edge."
        )
        self.risk = GeminiAgentPersona(
            "RiskManager",
            "Risk Officer",
            "Focuses on drawdowns, tail risk, position sizing, and volatility caps."
        )
        self.execution = GeminiAgentPersona(
            "ExecutionStrategist",
            "Execution Trading Specialist",
            "Focuses on liquidity, volume confirmation, slippage, and practical execution friction."
        )

        self.audit_log = []
        self.parameter_history = []

    def run_iterative_loop(self) -> dict:
        best_sharpe = -999.0
        consensus_rationale = "Initial baseline strategy evaluation."

        p_ret, b_ret = self.backtester.backtest(self.current_params)
        baseline_metrics = calculate_performance_metrics(p_ret, b_ret)
        current_metrics = baseline_metrics

        logging.info(
            f"Baseline Metrics: Sharpe={baseline_metrics['sharpe_ratio']:.2f}, Return={baseline_metrics['cumulative_return']*100:.2f}%")

        for i in range(1, self.max_iterations + 1):
            logging.debug(
                f"--- Starting Debate Iteration {i}/{self.max_iterations} ---")

            q_eval = self.quant.evaluate(
                self.current_params, current_metrics, i)
            r_eval = self.risk.evaluate(
                self.current_params, current_metrics, i)
            e_eval = self.execution.evaluate(
                self.current_params, current_metrics, i)

            for persona_name, eval_data in [("QuantAnalyst", q_eval), ("RiskManager", r_eval), ("ExecutionStrategist", e_eval)]:
                self.audit_log.append({
                    "iteration_number": i,
                    "persona_name": persona_name,
                    "critique_text": eval_data.get("critique", ""),
                    "proposed_adjustment": eval_data.get("proposed_adjustment", {}),
                    "confidence_score": eval_data.get("confidence_score", 0.8)
                })

            q_adj = q_eval.get("proposed_adjustment", {})
            r_adj = r_eval.get("proposed_adjustment", {})
            e_adj = e_eval.get("proposed_adjustment", {})

            new_lookback = int(np.mean([
                float(q_adj.get("lookback",
                      self.current_params["lookback"])),
                float(r_adj.get("lookback",
                      self.current_params["lookback"])),
                float(e_adj.get("lookback",
                      self.current_params["lookback"]))
            ]))
            new_atr = float(np.mean([
                float(q_adj.get("atr_multiplier",
                      self.current_params["atr_multiplier"])),
                float(r_adj.get("atr_multiplier",
                      self.current_params["atr_multiplier"])),
                float(e_adj.get("atr_multiplier",
                      self.current_params["atr_multiplier"]))
            ]))
            new_vol_thresh = float(np.mean([
                float(q_adj.get("volume_threshold",
                      self.current_params["volume_threshold"])),
                float(r_adj.get("volume_threshold",
                      self.current_params["volume_threshold"])),
                float(e_adj.get("volume_threshold",
                      self.current_params["volume_threshold"]))
            ]))
            new_sl = float(np.mean([
                float(q_adj.get("stop_loss_pct",
                      self.current_params["stop_loss_pct"])),
                float(r_adj.get("stop_loss_pct",
                      self.current_params["stop_loss_pct"])),
                float(e_adj.get("stop_loss_pct",
                      self.current_params["stop_loss_pct"]))
            ]))

            self.current_params = {
                "lookback": max(5, min(60, new_lookback)),
                "atr_multiplier": max(1.0, min(4.0, new_atr)),
                "volume_threshold": max(1.0, min(3.0, new_vol_thresh)),
                "stop_loss_pct": max(0.01, min(0.15, new_sl))
            }

            p_ret_new, b_ret_new = self.backtester.backtest(
                self.current_params)
            current_metrics = calculate_performance_metrics(
                p_ret_new, b_ret_new)

            logging.debug(
                f"Iteration {i} Results: Sharpe={current_metrics['sharpe_ratio']:.2f}, Return={current_metrics['cumulative_return']*100:.2f}%, Params={self.current_params}")

            self.parameter_history.append({
                "iteration": i,
                "params": self.current_params.copy(),
                "metrics": current_metrics.copy()
            })

            if current_metrics["sharpe_ratio"] > best_sharpe:
                best_sharpe = current_metrics["sharpe_ratio"]
                consensus_rationale = f"Iteration {i} consensus achieved superior Sharpe ratio through optimized ATR multiplier and volume threshold refinement."

        summary_result = {
            "strategy_version": "v2.0-optimized",
            "final_sharpe_ratio": float(best_sharpe),
            "consensus_rationale": consensus_rationale,
            "max_drawdown_delta": float(current_metrics["max_drawdown"] - baseline_metrics["max_drawdown"]),
            "total_iterations_run": self.max_iterations,
            "parameter_history": self.parameter_history
        }

        return summary_result

Калі коордынатар пераглядае дыялогіі, запісвае журналы аўдыту і падсумкі выканання, завершаецца працэс оптымаізацыі:

INFO - Successfully generated output JSON files.

Рэзультаты

Ітэрацыя за ітэрацыяй

Анаіз на практычных даных без адэкватных паметрактароў — гэта проста апаводкі, таму прыступаецца да вымерэння прогрэсу праз разныя ітэрацыі. Функцыя паметрактароў канкрэтных рэзультаатаў стандартызуе працэс перакалькулявання годавых доходаў і корэкцыю рызыка.

def calculate_performance_metrics(portfolio_returns: pd.Series, benchmark_returns: pd.Series) -> dict:
    valid_idx = portfolio_returns.index.intersection(benchmark_returns.index)
    p_ret = portfolio_returns.loc[valid_idx]
    b_ret = benchmark_returns.loc[valid_idx]

    trading_days = 252
    cum_return = (1 + p_ret).prod() - 1
    annualized_return = (1 + cum_return) ** (trading_days /
                                             len(p_ret)) - 1 if len(p_ret) > 0 else 0.0
    annualized_vol = p_ret.std() * np.sqrt(trading_days)
    risk_free_rate = 0.03
    sharpe_ratio = (annualized_return - risk_free_rate) / \
        annualized_vol if annualized_vol > 0 else 0.0

    cum_wealth = (1 + p_ret).cumprod()
    peak = cum_wealth.cummax()
    drawdown = (cum_wealth - peak) / peak
    max_drawdown = drawdown.min()

    b_cum_return = (1 + b_ret).prod() - 1
    outperformance = cum_return - b_cum_return

    return {
        "cumulative_return": float(cum_return),
        "annualized_return": float(annualized_return),
        "annualized_volatility": float(annualized_vol),
        "sharpe_ratio": float(sharpe_ratio),
        "max_drawdown": float(max_drawdown),
        "benchmark_cumulative_return": float(b_cum_return),
        "outperformance": float(outperformance)
    }

У ходзе першая ітэрацыя дасла жадныя рэзультаты. Адзінства між трыма персонажамі прынеслі пазітыўны сумарны дохад і значна лепшы калячык Шарпа, чым у базовай версіі. Рэзультаты дыялогаў і історыя параметраў выглядаюць так:

{
    "strategy_version": "v2.0-optimized",
    "final_sharpe_ratio": -0.6822300207528283,
    "consensus_rationale": "Iteration 1 consensus achieved superior Sharpe ratio through optimized ATR multiplier and volume threshold refinement.",
    "parameter_history": [
        {
            "iteration": 1,
            "params": {
                "lookback": 14,
                "atr_multiplier": 1.5,
                "volume_threshold": 1.4666,
                "stop_loss_pct": 0.025
            },
            "metrics": {
                "cumulative_return": 0.0399,
                "annualized_return": 0.0132,
                "sharpe_ratio": -0.6822,
                "max_drawdown": -0.0344
            }
        }
    ]
}

Працэўнасць проты стандарта

Рэзультаты стратэгіі завжды практычна адпавядаюць стандарту простага купіў-і-зберагчыць. Оптымізацыя з удзелам калькольнікаў дасклалася ператворыць негатыўныя рэзультаты стандарта, але ў гэтым рынковым рэжыме яна сільна прасілася проты SPY. Корзіны тэхналогічных акцэў з высокім показнікам бета перажывалі нестабільныя перыяды, калі правілы следавання за тэндэнцамі пастаўлялі частыя помылкі. На графіку практычна паўтараюцца рост стратэгіі і стандарта працэўнасці за разнымі ітэраціямі.

Цэлы працэс з’едынае завантажэнне дадзэнняў, тэставанне на адваротным рынку і коордынацыю параметраў у адну точку входу:

def main():
    logging.info(
        "Starting Multi-Agent Volatility Breakout Optimization Pipeline.")

    loader = MarketDataLoader(Config.TICKERS, Config.BENCHMARK)
    close_df, high_df, low_df, volume_df = loader.fetch_data()

    backtester = VolatilityStrategyBacktester(
        close_df, high_df, low_df, volume_df, Config.BENCHMARK)

    # Run initial baseline backtest
    baseline_p_ret, baseline_b_ret = backtester.backtest(Config.INITIAL_PARAMS)

    coordinator = PersonaDebateCoordinator(
        backtester, Config.INITIAL_PARAMS, Config.MAX_ITERATIONS)
    summary_results = coordinator.run_iterative_loop()

    # Write output files exactly as specified
    with open("volatility_debate_summary.json", "w") as f:
        json.dump(summary_results, f, indent=4)

    with open("iteration_debate_audit_log.json", "w") as f:
        json.dump(coordinator.audit_log, f, indent=4)

    logging.info("Successfully generated output JSON files.")

    # Generate annotated plot
    fig, ax = plt.subplots(figsize=(10, 6))
    optimized_p_ret, _ = backtester.backtest(coordinator.current_params)

    wealth_baseline = (1 + baseline_p_ret).cumprod()
    wealth_optimized = (1 + optimized_p_ret).cumprod()
    wealth_benchmark = (1 + baseline_b_ret).cumprod()

    ax.plot(wealth_baseline.index, wealth_baseline,
            label="Baseline Strategy", color="gray", linestyle="--")
    ax.plot(wealth_optimized.index, wealth_optimized,
            label="Optimized Multi-Agent Strategy", color="blue", linewidth=2)
    ax.plot(wealth_benchmark.index, wealth_benchmark,
            label="SPY Benchmark", color="orange", alpha=0.7)

    # Annotate final peak / convergence
    max_idx = wealth_optimized.idxmax()
    max_val = wealth_optimized.max()
    ax.annotate(f"Optimized Peak: {max_val:.2f}x",
                xy=(max_idx, max_val),
                xytext=(max_idx - pd.Timedelta(days=90), max_val * 0.9),
                arrowprops=dict(facecolor='blue', shrink=0.05, width=1, headwidth=6))

    ax.set_title("Multi-Agent Volatility Breakout Portfolio vs. SPY Benchmark",
                 fontsize=12, fontweight='bold')
    ax.set_xlabel("Date", fontsize=10)
    ax.set_ylabel("Growth of 1.00", fontsize=10)
    ax.legend(loc="upper left")
    ax.grid(True, linestyle=":", alpha=0.6)

    plt.tight_layout()
    plt.show()
    logging.info("Successfully generated strategy performance plot.")
if __name__ == "__main__":
    main()

Калі скрыпт завершыцца, ён запісвае журналы аўдыту і візуалізацыю працэйносці:

INFO - Successfully generated strategy performance plot.

Этыя рэзультаты ўспамінаюць, што сістэмы LLM з калькуляцыяй многах агентоў добра справляюцца з навігацыяй па параметрах і аналізам рызыка, але яны не можу ствараць макротэндэнцыі на рынку, які рухаецца гораздка. Чыраганне гэтых даследжэнняў у рэальную практыку таксама вымагае увагі да затрымакоў у выкананні і лімітаў маршрутавання замоў.

Што паказвае эксперымент

Сістэма квантытаўнага трэйдынгу вымагае больш, чым проста падаць історыю даных у оптымізатор і спадзявацца. Сеткаванне дыскусій працавала як звычная камната для пераговораў у сферэ квантытаўнага трэйдынгу, дзе спецыялісты-персонажы Gemini пераглядалі працэс стварэння альфа-рэзультатаў, ліміты падышчаў і труднасці ў выкананні замоў.

Цыкл крытыкі даспраўна вярнуў базовы показнік. Шарп падняўся з -2,68 да -0,68 за час аптэнацый, што яшчэ застаецца негатыўным значэнням, тады як сумарныя рэтарды перайшлі з -2,79 процента ў пазітывную зону. Серэйваванне параметраў па-серэднім плюс строгая карэнтка не дазволілі персонажам занурыцца ў безконтрольную матэматычную фантазію, таму кожная аптэнацыя застаўся прыязначанай да рэальных рэзультатаў бэктэста.

Рэзультаты таксама прынеслі важны урок. Нават калі тры спроможныя стратэгіі выкалічвалі прагі для прыходу на рынак, стратэгія з высокім коефіцыентам бета значна ахвостала за контрольным показніком SPY з методам разумнага володзення акціямі ў такых умовах. Правіла, заснованыя на моментуме, перастаюць работаць у такіх сітуацыях, калі тэндэнцыя зупіняецца, а цены пачынаюць вяртацца да своіх сярэдніх значэнняў. Жадныя ночныя раздумкі мадэлі не можу стварыць паспрыяваючых умоў, якіх сам рынак не задае. Хоча споры можу павысіць внутршую супараднасць стратэгіі, зовнішня сітуацыя застаецца недаступной.

Практычны план дзеянняя ў напрамку рэальнага трынкавання пачынвацца бы з разбівання работы на тры апграды. У ём былі бы заменены історычныя панелі pandas на падачу даных у рэальны час праз WebSocket з API брокераў, у прыстрое для тэставання былі бы даданы дынамічныя мадэлі сляплення і комісій, а таксама быў бы адключаны чэтырты элемент — агент макро-настроення, які можа цэлым чынам змяніць рэжым, калі волатыльнасць вымагае зберагчы грошы.

Рынкі не плацяюць за элегантны код чы розумныя падказкі. Яны плацяюць за можлівасць выжыць у непрынцыпнае нявяснасць.