Оптимизация в алгоритмической торговле решает задачи поиска наилучших параметров при заданных ограничениях. Портфельные менеджеры максимизируют доходность при контроле риска, трейдеры минимизируют издержки исполнения, квантовые исследователи подбирают параметры стратегий. Каждая задача требует специфических методов оптимизации.
Основные классы оптимизационных задач в трейдинге:
- Портфельная оптимизация (распределение капитала между активами),
- Параметрическая оптимизация (настройка торговых стратегий),
- Оптимизация исполнения (минимизация влияния ордеров на рынок и проскальзываний).
Методы решения варьируются от классических алгоритмов выпуклой оптимизации до метаэвристик для сложных нелинейных задач.
Оптимизация портфеля
Mean-Variance оптимизация
Подход Mean-Variance, разработанный Марковицем, формализует компромисс между доходностью и риском. Задача: найти веса активов w, максимизирующие ожидаемую доходность при заданном уровне риска.
Целевая функция:
max w^T μ — λ/2 w^T Σ w
где:
- w — вектор весов активов (доли капитала);
- μ — вектор ожидаемых доходностей;
- Σ — ковариационная матрица доходностей;
- λ — коэффициент неприятия риска.
Параметр λ контролирует компромисс: при λ→0 максимизируется доходность без учета риска, при больших λ минимизируется волатильность. Ограничения включают условие полной инвестиции (сумма весов равна 1) и неотрицательность весов для long-only портфелей.
import numpy as np
import pandas as pd
import yfinance as yf
from scipy.optimize import minimize
import matplotlib.pyplot as plt
# Загрузка данных для диверсифицированного портфеля
tickers = ['TSM', 'BABA', 'NIO', 'JD', 'BIDU']
data = yf.download(tickers, start='2022-09-01', end='2025-09-01')['Close']
# Расчет доходностей
returns = data.pct_change().dropna()
mean_returns = returns.mean() * 252 # Аннуализация
cov_matrix = returns.cov() * 252
def portfolio_performance(weights, mean_returns, cov_matrix):
"""Расчет доходности и риска портфеля"""
portfolio_return = np.dot(weights, mean_returns)
portfolio_std = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
return portfolio_return, portfolio_std
def negative_sharpe(weights, mean_returns, cov_matrix, risk_free_rate=0.02):
"""Минимизируем негативный Sharpe ratio"""
p_return, p_std = portfolio_performance(weights, mean_returns, cov_matrix)
return -(p_return - risk_free_rate) / p_std
# Ограничения и границы
constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
bounds = tuple((0, 1) for _ in range(len(tickers)))
initial_weights = np.array([1/len(tickers)] * len(tickers))
# Оптимизация максимального Sharpe ratio
result = minimize(negative_sharpe, initial_weights,
args=(mean_returns, cov_matrix),
method='SLSQP', bounds=bounds, constraints=constraints)
optimal_weights = result.x
opt_return, opt_std = portfolio_performance(optimal_weights, mean_returns, cov_matrix)
# Построение эффективной границы
target_returns = np.linspace(mean_returns.min(), mean_returns.max(), 50)
efficient_portfolios = []
for target in target_returns:
constraints_frontier = [
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1},
{'type': 'eq', 'fun': lambda w: portfolio_performance(w, mean_returns, cov_matrix)[0] - target}
]
result_frontier = minimize(lambda w: portfolio_performance(w, mean_returns, cov_matrix)[1],
initial_weights, method='SLSQP',
bounds=bounds, constraints=constraints_frontier)
if result_frontier.success:
efficient_portfolios.append(result_frontier.x)
# Визуализация
efficient_std = [portfolio_performance(w, mean_returns, cov_matrix)[1] for w in efficient_portfolios]
efficient_ret = [portfolio_performance(w, mean_returns, cov_matrix)[0] for w in efficient_portfolios]
plt.figure(figsize=(10, 6))
plt.plot(efficient_std, efficient_ret, 'k-', linewidth=2, label='Efficient Frontier')
plt.scatter(opt_std, opt_return, color='red', s=100, marker='*', label='Max Sharpe Ratio')
plt.xlabel('Волатильность (Std Dev)', fontsize=12)
plt.ylabel('Ожидаемая доходность', fontsize=12)
plt.title('Эффективная граница Mean-Variance', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# Вывод оптимальных весов
weights_df = pd.DataFrame({'Asset': tickers, 'Weight': optimal_weights})
print(f"\nOptimal Portfolio Weights:\n{weights_df}")
print(f"\nExpected Annual Return: {opt_return:.2%}")
print(f"Annual Volatility: {opt_std:.2%}")
print(f"Sharpe Ratio: {(opt_return - 0.02) / opt_std:.2f}")

Рис. 1: Эффективная граница Марковица для портфеля из пяти активов. Черная кривая показывает оптимальные комбинации риска и доходности. Красная звезда отмечает портфель с максимальным Sharpe ratio — оптимальный выбор для инвестора, максимизирующего доходность на единицу риска
Optimal Portfolio Weights:
Asset Weight
0 TSM 1.453048e-01
1 BABA 1.866996e-16
2 NIO 5.527263e-16
3 JD 0.000000e+00
4 BIDU 8.546952e-01
Expected Annual Return: 41.22%
Annual Volatility: 35.43%
Sharpe Ratio: 1.11
Представленный выше код реализует полный цикл портфельной оптимизации:
- Загружаются исторические данные для китайских tech-компаний, рассчитываются статистики доходностей;
- Функция negative_sharpe минимизирует отрицательный коэффициент Шарпа, что эквивалентно максимизации оригинального показателя;
- Затем используется оптимизация методом SLSQP (Sequential Least Squares Programming). Он эффективен для задач квадратичного программирования с ограничениями-равенствами и неравенствами.
Построение эффективной границы демонстрирует множество оптимальных портфелей для различных уровней риска. Каждая точка на кривой представляет минимальный риск для заданной целевой доходности. Портфель с максимальным коэффициентом Шарпа (Sharpe ratio) находится на касательной из безрисковой ставки к эффективной границе.
Метод Риск-паритет (Risk Parity) и альтернативные подходы
Оптимизационный метод Risk Parity распределяет капитал так, чтобы каждый актив вносил равный вклад в общий риск портфеля. Классический подход Mean-Variance концентрирует риск в волатильных активах с высокой ожидаемой доходностью, а Risk Parity диверсифицирует источники риска.
Вклад актива i в риск портфеля:
RC_i = w_i × (Σw)_i / √(w^T Σ w)
где:
- RC_i — risk contribution актива i;
- w_i — вес актива i;
- (Σw)_i — i-й элемент вектора Σw (ковариация актива с портфелем);
- √(w^T Σ w) — волатильность портфеля.
Risk Parity находит веса, при которых RC_i = 1/N для всех активов, где N — количество позиций. Низковолатильные активы получают больший вес, высокорисковые — меньший.
import numpy as np
import pandas as pd
import yfinance as yf
from scipy.optimize import minimize
# Загрузка данных для multi-asset портфеля
tickers = ['GLD', 'TLT', 'VNQ', 'DBC', 'EEM'] # Золото, облигации, недвижимость, сырье, emerging markets
data = yf.download(tickers, start='2022-09-01', end='2025-09-01')['Close']
returns = data.pct_change().dropna()
cov_matrix = returns.cov() * 252
def risk_contribution(weights, cov_matrix):
"""Расчет вкладов активов в риск портфеля"""
portfolio_var = np.dot(weights.T, np.dot(cov_matrix, weights))
portfolio_std = np.sqrt(portfolio_var)
marginal_contrib = np.dot(cov_matrix, weights)
risk_contrib = weights * marginal_contrib / portfolio_std
return risk_contrib
def risk_parity_objective(weights, cov_matrix):
"""Минимизация разницы между вкладами в риск"""
risk_contrib = risk_contribution(weights, cov_matrix)
target_risk = np.ones(len(weights)) / len(weights)
return np.sum((risk_contrib - target_risk * risk_contrib.sum())**2)
# Оптимизация Risk Parity
n_assets = len(tickers)
constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
bounds = tuple((0.01, 1) for _ in range(n_assets))
initial_weights = np.array([1/n_assets] * n_assets)
result_rp = minimize(risk_parity_objective, initial_weights,
args=(cov_matrix,), method='SLSQP',
bounds=bounds, constraints=constraints)
rp_weights = result_rp.x
rp_risk_contrib = risk_contribution(rp_weights, cov_matrix)
# Сравнение с равновесным портфелем
equal_weights = np.array([1/n_assets] * n_assets)
equal_risk_contrib = risk_contribution(equal_weights, cov_matrix)
# Mean-Variance портфель для сравнения
mean_returns = returns.mean() * 252
def negative_sharpe_mv(weights, mean_returns, cov_matrix):
ret = np.dot(weights, mean_returns)
vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
return -(ret - 0.02) / vol
result_mv = minimize(negative_sharpe_mv, initial_weights,
args=(mean_returns, cov_matrix),
method='SLSQP', bounds=bounds, constraints=constraints)
mv_weights = result_mv.x
mv_risk_contrib = risk_contribution(mv_weights, cov_matrix)
# Визуализация сравнения
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
portfolios = [
('Equal Weight', equal_weights, equal_risk_contrib),
('Risk Parity', rp_weights, rp_risk_contrib),
('Mean-Variance', mv_weights, mv_risk_contrib)
]
for idx, (title, weights, risk_contrib) in enumerate(portfolios):
axes[idx].bar(tickers, weights, alpha=0.7, color='darkgray', label='Weights')
axes[idx].set_ylabel('Weight', fontsize=11)
axes[idx].set_ylim(0, max(max(equal_weights), max(rp_weights), max(mv_weights)) * 1.1)
ax2 = axes[idx].twinx()
ax2.plot(tickers, risk_contrib, 'ko-', linewidth=2, markersize=8, label='Risk Contribution')
ax2.set_ylabel('Risk Contribution', fontsize=11)
ax2.set_ylim(0, max(max(equal_risk_contrib), max(rp_risk_contrib), max(mv_risk_contrib)) * 1.1)
axes[idx].set_title(title, fontsize=12, fontweight='bold')
axes[idx].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
# Количественное сравнение
comparison_df = pd.DataFrame({
'Asset': tickers,
'Equal Weight': equal_weights,
'Risk Parity': rp_weights,
'Mean-Variance': mv_weights
})
risk_df = pd.DataFrame({
'Asset': tickers,
'Equal RC': equal_risk_contrib,
'RP RC': rp_risk_contrib,
'MV RC': mv_risk_contrib
})
print("Portfolio Weights:\n", comparison_df.round(3))
print("\nRisk Contributions:\n", risk_df.round(3))
print(f"\nRisk Parity std of RC: {np.std(rp_risk_contrib):.4f}")
print(f"Equal Weight std of RC: {np.std(equal_risk_contrib):.4f}")
print(f"Mean-Variance std of RC: {np.std(mv_risk_contrib):.4f}")

Рис. 2: Сравнение распределения весов и вкладов в риск для трех методов портфельной оптимизации. Столбцы показывают веса активов, черные линии — вклады в риск портфеля. Risk Parity выравнивает вклады в риск, тогда как Equal Weight и Mean-Variance концентрируют риск в отдельных позициях. Стандартное отклонение risk contributions минимально для Risk Parity, что подтверждает равномерную диверсификацию источников риска
Portfolio Weights:
Asset Equal Weight Risk Parity Mean-Variance
0 GLD 0.2 0.223 0.010
1 TLT 0.2 0.178 0.013
2 VNQ 0.2 0.216 0.957
3 DBC 0.2 0.226 0.010
4 EEM 0.2 0.158 0.010
Risk Contributions:
Asset Equal RC RP RC MV RC
0 GLD 0.018 0.021 0.001
1 TLT 0.026 0.023 0.001
2 VNQ 0.020 0.023 0.148
3 DBC 0.017 0.021 0.000
4 EEM 0.029 0.021 0.000
Risk Parity std of RC: 0.0009
Equal Weight std of RC: 0.0047
Mean-Variance std of RC: 0.0591
Код демонстрирует три подхода к построению портфеля из разных классов активов. Risk Parity минимизирует квадратичное отклонение вкладов в риск от равномерного распределения. Функция risk_contribution вычисляет маргинальный вклад каждого актива, умножая его вес на ковариацию с портфелем и нормируя на общую волатильность.
Интерпретация результатов:
Risk Parity (RP): Веса распределены почти равномерно. Вклады в риск (RC) тоже почти одинаковые — около 0.021–0.023, а стандартное отклонение RC = 0.0009, что говорит о почти идеальном балансе риска. Такой портфель стабилен, хорошо диверсифицирован и менее чувствителен к падению отдельных активов.
Equal Weight (EW): Все активы имеют долю по 0.2, но вклады в риск сильно различаются. Стандартное отклонение RC = 0.0047, что указывает на умеренный дисбаланс. Хотя капитал распределен равномерно, более волатильные активы создают больший риск, и портфель может быть нестабилен в стрессовых условиях.
Mean-Variance (MV): Почти весь капитал в VNQ (0.957), остальные активы занимают лишь ~1%. Вклад VNQ в риск = 0.148, тогда как у остальных активов почти ноль. Стандартное отклонение RC = 0.0591, что означает крайнюю концентрацию риска. Такой портфель ориентирован на максимальную доходность, но при этом очень уязвим — падение одного актива способно обрушить весь результат.
Сравнение показывает ключевые различия подходов:
- Equal Weight игнорирует структуру ковариаций, что приводит к концентрации риска в волатильных активах.
- Mean-Variance концентрирует капитал в активах с высоким Sharpe ratio, риск доминируется несколькими позициями.
- Risk Parity обеспечивает равномерное распределение риска, часто увеличивая веса низковолатильных активов типа TLT (казначейские облигации).
Оптимизация параметров торговых стратегий
Grid Search и Random Search
Метод поиска по сетке Grid Search перебирает все комбинации параметров из заданной сетки. Для стратегии с параметрами lookback period и threshold создается двумерная сетка значений, каждая комбинация тестируется на исторических данных. Метод гарантирует нахождение оптимума в пределах сетки, однако вычислительная сложность растет экспоненциально с количеством параметров.
Метод случайного поиска Random Search сэмплирует случайные комбинации параметров из заданных распределений. Подход эффективнее Grid Search для многомерных пространств: при фиксированном бюджете вычислений Random Search исследует больше различных значений по каждому измерению. Bergstra и Bengio (2012) показали превосходство Random Search для оптимизации гиперпараметров нейросетей.
import numpy as np
import pandas as pd
import yfinance as yf
from itertools import product
import matplotlib.pyplot as plt
# Загрузка данных
ticker = 'QCOM'
data = yf.download(ticker, start='2022-09-01', end='2025-09-01')
prices = data['Close']
if isinstance(prices, pd.DataFrame):
prices = prices.squeeze()
def momentum_strategy(prices, lookback, threshold):
"""
Простая momentum стратегия с параметрами:
- lookback: период расчета доходности
- threshold: порог для входа в позицию
"""
returns = prices.pct_change()
momentum = prices.pct_change(lookback)
# Сигналы: long при momentum > threshold, short при < -threshold positions = np.where(momentum > threshold, 1,
np.where(momentum < -threshold, -1, 0))
positions = pd.Series(positions, index=prices.index)
positions = positions.shift(1).fillna(0) # Избегаем look-ahead bias
strategy_returns = positions * returns
cumulative_return = (1 + strategy_returns).prod() - 1
sharpe = strategy_returns.mean() / strategy_returns.std() * np.sqrt(252)
return {
'cumulative_return': cumulative_return,
'sharpe': sharpe,
'positions': positions
}
# Grid Search
lookback_range = range(5, 51, 5) # 5, 10, 15, ..., 50
threshold_range = np.arange(0.01, 0.11, 0.01) # 0.01, 0.02, ..., 0.10
grid_results = []
for lookback, threshold in product(lookback_range, threshold_range):
result = momentum_strategy(prices, lookback, threshold)
grid_results.append({
'lookback': lookback,
'threshold': threshold,
'sharpe': result['sharpe'],
'return': result['cumulative_return']
})
grid_df = pd.DataFrame(grid_results)
best_grid = grid_df.loc[grid_df['sharpe'].idxmax()]
# Random Search (то же количество итераций)
n_iterations = len(grid_results)
random_results = []
np.random.seed(42)
for _ in range(n_iterations):
lookback = np.random.randint(5, 51)
threshold = np.random.uniform(0.01, 0.11)
result = momentum_strategy(prices, lookback, threshold)
random_results.append({
'lookback': lookback,
'threshold': threshold,
'sharpe': result['sharpe'],
'return': result['cumulative_return']
})
random_df = pd.DataFrame(random_results)
best_random = random_df.loc[random_df['sharpe'].idxmax()]
# Визуализация
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Grid Search heatmap
grid_pivot = grid_df.pivot(index='threshold', columns='lookback', values='sharpe')
im1 = axes[0].imshow(grid_pivot, cmap='RdYlGn', aspect='auto', origin='lower')
axes[0].set_xticks(range(len(lookback_range)))
axes[0].set_xticklabels(lookback_range)
axes[0].set_yticks(range(len(threshold_range)))
axes[0].set_yticklabels([f'{t:.2f}' for t in threshold_range])
axes[0].set_xlabel('Lookback Period', fontsize=11)
axes[0].set_ylabel('Threshold', fontsize=11)
axes[0].set_title('Grid Search: Sharpe Ratio Heatmap', fontsize=12)
plt.colorbar(im1, ax=axes[0])
# Random Search scatter
scatter = axes[1].scatter(random_df['lookback'], random_df['threshold'],
c=random_df['sharpe'], cmap='RdYlGn', s=50, alpha=0.6)
axes[1].scatter(best_random['lookback'], best_random['threshold'],
color='red', s=200, marker='*', edgecolors='black', linewidths=2,
label=f"Best (Sharpe={best_random['sharpe']:.2f})")
axes[1].set_xlabel('Lookback Period', fontsize=11)
axes[1].set_ylabel('Threshold', fontsize=11)
axes[1].set_title('Random Search: Explored Parameter Space', fontsize=12)
axes[1].legend()
plt.colorbar(scatter, ax=axes[1])
plt.tight_layout()
plt.show()
print(f"Grid Search Best Parameters:")
print(f"Lookback: {best_grid['lookback']}, Threshold: {best_grid['threshold']:.3f}")
print(f"Sharpe: {best_grid['sharpe']:.2f}, Return: {best_grid['return']:.2%}\n")
print(f"Random Search Best Parameters:")
print(f"Lookback: {best_random['lookback']}, Threshold: {best_random['threshold']:.3f}")
print(f"Sharpe: {best_random['sharpe']:.2f}, Return: {best_random['return']:.2%}")

Рис. 3: Сравнение Grid Search и Random Search для оптимизации momentum стратегии. Левая панель показывает полную карту Sharpe ratio для всех комбинаций параметров. Правая панель отображает случайно исследованные точки, красная звезда отмечает найденный оптимум. Grid Search обеспечивает систематическое покрытие, Random Search быстрее находит конкурентоспособные параметры при ограниченном бюджете вычислений
Grid Search Best Parameters:
Lookback: 20.0, Threshold: 0.010
Sharpe: -0.00, Return: -17.15%
Random Search Best Parameters:
Lookback: 12.0, Threshold: 0.083
Sharpe: 0.23, Return: 8.13%
Реализация демонстрирует базовую momentum стратегию с двумя параметрами. Grid Search строит полную карту пространства параметров, каждая комбинация тестируется один раз. Random Search исследует то же количество точек, но распределенных случайным образом. При малом числе параметров (2-3) Grid Search покрывает пространство равномерно, для 5+ параметров Random Search эффективнее.
Тепловая карта Heatmap Grid Search показывает структуру целевой функции: регионы высокого Sharpe ratio концентрируются вокруг определенных значений lookback и threshold. Random Search может пропустить оптимум в дискретной сетке, но находит хорошие решения быстрее в непрерывных пространствах. Для production стратегий оба метода служат начальным этапом перед более сложной оптимизацией.
Байесовская оптимизация
Байесовская оптимизация эффективна для оптимизации дорогостоящих «чёрных ящиков» — функций, вычисление которых требует значительных ресурсов. Метод строит вероятностную модель целевой функции (обычно на основе гауссовского процесса) и использует функцию выбора следующей точки (Acquisition function), чтобы сбалансировать эксплуатацию (поиск около текущего оптимума) и исследование (поиск в еще не изученных областях).
Функция выбора (Acquisition function) формализует компромисс между эксплуатацией и исследованием. Метод Expected Improvement (EI) максимизирует ожидаемое улучшение относительно текущего наилучшего значения:
EI(x) = E[max(f(x) — f(x⁺), 0)]
где:
- f(x) — значение целевой функции в точке x;
- x⁺ — текущая лучшая найденная точка;
- E[·] — математическое ожидание по апостериорному распределению.
Показатель ожидаемого улучшения (EI) обычно выше в областях с высоким предсказанным значением функции (mean) и высокой неопределенностью (uncertainty). Гауссовский процесс предоставляет оба этих компонента: μ(x) — предсказанное значение, σ(x) — меру неопределенности.
import numpy as np
import pandas as pd
import yfinance as yf
from hyperopt import hp, fmin, tpe, Trials, STATUS_OK
from hyperopt.pyll import scope
import matplotlib.pyplot as plt
# Загрузка данных
ticker = 'AMD'
data = yf.download(ticker, start='2023-09-01', end='2025-09-01')
# Берем только Close и превращаем в Series
prices = data['Close']
if isinstance(prices, pd.DataFrame):
prices = prices.iloc[:, 0]
returns = prices.pct_change().dropna()
# Пространство поиска Hyperopt
space = {
'window': scope.int(hp.quniform('window', 10, 100, 1)),
'entry_threshold': hp.uniform('entry_threshold', 1.0, 3.0),
'exit_threshold': hp.uniform('exit_threshold', 0.1, 1.0)
}
# Стратегия
def mean_reversion_strategy(params, prices, returns):
window = int(params['window'])
entry_threshold = float(params['entry_threshold'])
exit_threshold = float(params['exit_threshold'])
if window >= len(prices):
window = max(2, len(prices) // 2)
rolling_mean = prices.rolling(window).mean()
rolling_std = prices.rolling(window).std()
z_score = (prices - rolling_mean) / rolling_std
z_score = z_score.fillna(0)
positions = pd.Series(0, index=prices.index)
position = 0
for i in range(window, len(prices)):
z = z_score.iat[i] # скалярное значение
if position == 0:
if z < -entry_threshold: position = 1 elif z > entry_threshold:
position = -1
elif position == 1 and z > -exit_threshold:
position = 0
elif position == -1 and z < exit_threshold: position = 0 positions.iat[i] = position positions = positions.shift(1).fillna(0) strategy_returns = positions * returns strategy_returns = strategy_returns.replace([np.inf, -np.inf], 0).fillna(0) mean_ret = float(strategy_returns.mean()) std_ret = float(strategy_returns.std()) sharpe = float(mean_ret / std_ret * np.sqrt(252)) if std_ret > 0 else 0.0
cumulative_return = float((1 + strategy_returns).prod() - 1)
max_dd = float((strategy_returns.cumsum() - strategy_returns.cumsum().expanding().max()).min())
n_trades = int((positions.diff().fillna(0) != 0).sum())
return {
'sharpe': sharpe,
'return': cumulative_return,
'max_drawdown': max_dd,
'n_trades': n_trades
}
# Objective function для Hyperopt
def objective(params):
params_safe = {
'window': int(params['window']),
'entry_threshold': float(params['entry_threshold']),
'exit_threshold': float(params['exit_threshold'])
}
result = mean_reversion_strategy(params_safe, prices, returns)
sharpe = float(result['sharpe'])
n_trades = int(result['n_trades'])
# Штраф за малое количество сделок
if n_trades < 10:
return {'loss': 10, 'status': STATUS_OK}
return {'loss': -sharpe, 'status': STATUS_OK, 'result': result}
# Байесовская оптимизация
trials = Trials()
best_params = fmin(
fn=objective,
space=space,
algo=tpe.suggest,
max_evals=50,
trials=trials,
rstate=np.random.default_rng(42)
)
best_params['window'] = int(best_params['window'])
final_result = mean_reversion_strategy(best_params, prices, returns)
# Вывод результатов
print(f"Best Parameters Found:")
print(f"Window: {best_params['window']}")
print(f"Entry Threshold: {best_params['entry_threshold']:.2f}")
print(f"Exit Threshold: {best_params['exit_threshold']:.2f}\n")
print(f"Performance Metrics:")
print(f"Sharpe Ratio: {final_result['sharpe']:.2f}")
print(f"Cumulative Return: {final_result['return']:.2%}")
print(f"Max Drawdown: {final_result['max_drawdown']:.2%}")
print(f"Number of Trades: {final_result['n_trades']}")
Best Parameters Found:
Window: 35
Entry Threshold: 2.22
Exit Threshold: 0.76
Performance Metrics:
Sharpe Ratio: 0.61
Cumulative Return: 32.35%
Max Drawdown: -32.36%
Number of Trades: 26
Данный код демонстрирует автоматизированный подход к поиску оптимальных параметров торговой стратегии с использованием байесовской оптимизации, что позволяет минимизировать человеческий фактор и повысить эффективность тестирования. Давайте рассмотрим его этапы:
- Сначала мы загружаем исторические цены акций AMD, выбираем Close и вычисляем доходности;
- Затем определяем пространства поиска Hyperopt – задаем диапазоны для параметров стратегии (window, entry_threshold, exit_threshold);
- Далее следует функция стратегии mean-reversion – вычисляем скользящее среднее, стандартное отклонение, z-score и генерируем торговые позиции;
- Следующий этап — расчет метрик стратегии. Здесь вычисляем коэффициент Шарпа, кумулятивную доходность, максимальную просадку и количество сделок;
- Реализуем Objective-функцию для Hyperopt. Она оценивает стратегию по параметрам и возвращает функцию потерь (минус Sharpe), добавляя штраф за малое количество сделок;
- Следующий этап — байесовская оптимизация Hyperopt. Она находит оптимальные параметры стратегии путем последовательного тестирования различных комбинаций;
- Вывод финальных результатов – отображаются лучшие параметры и показатели эффективности стратегии.
Оптимальные параметры стратегии указывают на использование скользящего окна в 35 дней для расчета средних значений цены, с порогом входа 2.22 и порогом выхода 0.76. Это означает, что стратегия активно реагирует на относительно сильные отклонения цены от среднего, открывая позиции при заметной перепроданности или перекупленности, и закрывает их при достижении более умеренного уровня.
Полученный Sharpe Ratio 0.61 свидетельствует о хорошей соотношении доходности к риску, а кумулятивная доходность 32.35% показывает, что стратегия могла приносить стабильный прирост капитала на данном историческом периоде. Максимальная просадка в -32.36% отражает риски значительных краткосрочных потерь, а количество сделок (26) указывает на умеренную торговую активность, что снижает издержки на комиссии.
Использование байесовской оптимизации позволяет эффективно подбирать параметры стратегии с минимальным количеством исторических прогонов, учитывая сложные взаимодействия между окнами скользящей средней и порогами входа/выхода. Так достигается баланс между исследованием новых параметров (exploration) и использованием уже известных хороших решений (exploitation), ускоряя процесс поиска оптимальной стратегии.
Практически такой подход делает стратегию адаптивной, систематической и легко применимой в алгоритмической торговле, снижая субъективность принятия решений и повышая стабильность результатов на исторических данных.
Walk-forward оптимизация
Оптимизация методом Walk-forward предотвращает переобучение модели за счет разделения данных на последовательные периоды: in-sample (IS) для обучения и out-of-sample (OOS) для тестирования. Параметры настраиваются на IS, затем проверяются на следующем OOS, после чего окно сдвигается вперед. Такой подход имитирует реальную торговлю: стратегия калибруется на исторических данных и применяется к новым, еще не использованным данным.
Процедура walk-forward:
- Выбрать размеры IS и OOS периодов (например, 12 месяцев IS, 3 месяца OOS);
- Оптимизировать параметры на первом IS периоде;
- Применить найденные параметры к следующему OOS периоду;
- Сдвинуть окно вперед на размер OOS;
- Повторить до конца данных.
В практике тестирования торговых стратегий применяются различные подходы Walk-forward для оценки стабильности модели:
- Anchored walk-forward использует все данные с самого начала в качестве IS;
- Expanding walk-forward постепенно увеличивает IS окно на каждом шаге;
- Rolling walk-forward фиксирует размер IS окна, что позволяет быстрее адаптироваться к изменяющимся рыночным условиям.
Выбор метода влияет на баланс между стабильностью параметров и чувствительностью к новым рыночным событиям, помогая лучше имитировать реальную торговлю.
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from scipy.optimize import differential_evolution
# Загрузка данных
ticker = 'BKR' #Baker Hughes Co.
data = yf.download(ticker, start='2022-09-01', end='2025-09-01', auto_adjust=True)
# Убираем мультииндекс (если есть)
if isinstance(data.columns, pd.MultiIndex):
data.columns = data.columns.get_level_values(0)
prices = data['Close']
returns = prices.pct_change().dropna()
# Функция стратегии
def dual_ma_crossover(prices, short_window, long_window):
short_ma = prices.rolling(window=short_window).mean()
long_ma = prices.rolling(window=long_window).mean()
positions = pd.Series(0, index=prices.index)
positions[short_ma > long_ma] = 1
positions = positions.shift(1).fillna(0)
strategy_returns = positions * prices.pct_change()
return strategy_returns
# Оптимизация параметров
def optimize_strategy(prices_train):
def objective(params):
short, long = int(params[0]), int(params[1])
if short >= long:
return -999
strat_returns = dual_ma_crossover(prices_train, short, long)
sharpe = strat_returns.mean() / strat_returns.std() * np.sqrt(252) if strat_returns.std() > 0 else -999
return -sharpe
bounds = [(5, 50), (20, 200)]
result = differential_evolution(objective, bounds, seed=42, maxiter=100, polish=False)
return int(result.x[0]), int(result.x[1])
# Walk-forward параметры
is_period = 252 # 1 год
oos_period = 63 # 3 месяца
min_train_size = 252
wf_results = []
all_oos_returns = pd.Series(dtype=float)
start_idx = min_train_size
while start_idx + is_period + oos_period <= len(prices): is_start = start_idx is_end = start_idx + is_period oos_end = is_end + oos_period prices_is = prices.iloc[is_start:is_end] short_opt, long_opt = optimize_strategy(prices_is) oos_returns = dual_ma_crossover(prices, short_opt, long_opt).iloc[is_end:oos_end] oos_sharpe = oos_returns.mean() / oos_returns.std() * np.sqrt(252) if oos_returns.std() > 0 else 0
wf_results.append({
'is_start': prices.index[is_start],
'is_end': prices.index[is_end-1],
'oos_end': prices.index[oos_end-1],
'short_window': short_opt,
'long_window': long_opt,
'oos_sharpe': oos_sharpe,
'oos_return': (1 + oos_returns).prod() - 1
})
all_oos_returns = pd.concat([all_oos_returns, oos_returns])
start_idx += oos_period
wf_df = pd.DataFrame(wf_results)
# Полная оптимизация на всех данных
short_full, long_full = optimize_strategy(prices)
full_returns = dual_ma_crossover(prices, short_full, long_full)
full_returns_aligned = full_returns.loc[all_oos_returns.index.intersection(full_returns.index)]
# Метрики
wf_cumulative = (1 + all_oos_returns).cumprod()
full_cumulative = (1 + full_returns_aligned).cumprod()
buy_hold = prices.loc[all_oos_returns.index.intersection(prices.index)]
buy_hold_cum = buy_hold / buy_hold.iloc[0]
wf_sharpe = all_oos_returns.mean() / all_oos_returns.std() * np.sqrt(252)
full_sharpe = full_returns_aligned.mean() / full_returns_aligned.std() * np.sqrt(252)
# Визуализация
fig, axes = plt.subplots(3, 1, figsize=(14, 12))
axes[0].plot(wf_cumulative.index, wf_cumulative.values, 'k-', linewidth=2, label='Walk-Forward')
axes[0].plot(full_cumulative.index, full_cumulative.values, 'gray', linewidth=2, alpha=0.7, label='Full In-Sample Optimization')
axes[0].plot(buy_hold_cum.index, buy_hold_cum.values, 'darkgray', linewidth=1, alpha=0.5, label='Buy & Hold')
axes[0].set_ylabel('Cumulative Return', fontsize=11)
axes[0].set_title('Walk-Forward vs Full Optimization', fontsize=12, fontweight='bold')
axes[0].legend(loc='upper left')
axes[0].grid(True, alpha=0.3)
axes[1].plot(wf_df['oos_end'], wf_df['short_window'], 'o-', color='black', label='Short Window')
axes[1].plot(wf_df['oos_end'], wf_df['long_window'], 's-', color='gray', label='Long Window')
axes[1].set_ylabel('Window Size', fontsize=11)
axes[1].set_title('Parameter Evolution Over Time', fontsize=12)
axes[1].legend()
axes[1].grid(True, alpha=0.3)
colors = ['green' if x > 0 else 'red' for x in wf_df['oos_sharpe']]
axes[2].bar(range(len(wf_df)), wf_df['oos_sharpe'], color=colors, alpha=0.7)
axes[2].axhline(y=0, color='black', linestyle='-', linewidth=0.8)
axes[2].set_xlabel('Walk-Forward Period', fontsize=11)
axes[2].set_ylabel('OOS Sharpe Ratio', fontsize=11)
axes[2].set_title('Out-of-Sample Performance by Period', fontsize=12)
axes[2].grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.show()
# Статистика
print(f"Walk-Forward Results:")
print(f"Overall OOS Sharpe: {wf_sharpe:.2f}")
print(f"Cumulative Return: {(wf_cumulative.iloc[-1] - 1):.2%}")
print(f"Win Rate (periods): {(wf_df['oos_sharpe'] > 0).mean():.1%}")
print(f"Average params: Short={wf_df['short_window'].mean():.0f}, Long={wf_df['long_window'].mean():.0f}\n")
print(f"Full In-Sample Optimization:")
print(f"Parameters: Short={short_full}, Long={long_full}")
print(f"OOS Sharpe: {full_sharpe:.2f}")
print(f"Cumulative Return: {(full_cumulative.iloc[-1] - 1):.2%}")
Walk-Forward Results:
Overall OOS Sharpe: 1.61
Cumulative Return: 29.69%
Win Rate (periods): 100.0%
Average params: Short=39, Long=31
Full In-Sample Optimization:
Parameters: Short=31, Long=27
OOS Sharpe: 0.56
Cumulative Return: 9.53%
Результаты показывают, что стратегия на основе скользящих средних демонстрирует высокую эффективность при применении метода walk-forward: показатель Sharpe вне выборки составляет 1.61, что указывает на хорошее соотношение доходности и риска, а совокупная доходность за период достигла почти 30% при 100% успешных периодах, при этом оптимальные параметры короткой и длинной скользящей средней усредненно равны 39 и 31 соответственно.
Для полной оптимизации на всем периоде без разбиения на окна показатели ниже: Sharpe вне выборки 0.56 и доходность около 9.5%, что отражает переобучение на исторических данных и подчеркивает преимущество walk-forward подхода для более устойчивой и надежной оценки стратегии.

Рис. 4: Результаты walk-forward оптимизации торговой стратегии по 2 MA. Верхний график сравнивает equity curves: черная линия — WF подход с переоптимизацией параметров, серая — фиксированные параметры из полной IS оптимизации. Средний график показывает эволюцию оптимальных параметров во времени, отражая адаптацию к меняющимся рыночным условиям. Нижний график — OOS Sharpe ratio для каждого периода
В коде выше мы использовали метод walk-forward с фиксированным in-sample периодом в 2 года. На каждом IS окне параметры стратегии оптимизируются с помощью Differential Evolution — метаэвристики, хорошо подходящей для негладких функций вроде коэффициента Шарпа. Каждый последующий тестовый (OOS, out-of-sample) период проверяет стратегию на еще «невиданных» данных, имитируя реальные условия ее применения.
График эволюции параметров отражает адаптацию стратегии к меняющимся рыночным условиям: оптимальные окна скользящих средних меняются в зависимости от волатильности и направлений трендов. В периоды высокой волатильности чаще требуются более короткие окна для быстрой реакции. OOS Sharpe ratio по периодам показывает стабильность стратегии: естественно, что прибыльные и убыточные периоды чередуются, ключевое — положительное среднее значение.
Стратегия с хорошей производительностью в walk-forward (WF) имеет больше шансов на успех в реальной торговле. Сравнение с полной in-sample оптимизацией выявляет переобучение: если full optimization заметно превосходит WF на тех же out-of-sample данных, это значит, что параметры подогнаны под исторический шум.
Оптимизация исполнения ордеров
Оптимизация исполнения ордеров минимизирует рыночное воздействие (market impact) при размещении крупных ордеров. Размещение всего объема одной заявкой сдвигает цену против трейдера (price impact), тогда как разбиение ордера на части снижает это воздействие, однако увеличивает риск из-за времени исполнения (timing risk).
VWAP и TWAP стратегии
Методы VWAP (Volume-Weighted Average Price) и TWAP (Time-Weighted Average Price) помогают сбалансировать эти два фактора.
VWAP исполняет ордер пропорционально ожидаемому объему торгов в каждый временной интервал. Если исторически 30% дневного объема торгуется в первый час, VWAP размещает 30% ордера в этот период. Подход минимизирует информационную утечку и price impact, следуя естественному ритму рынка.
TWAP равномерно распределяет исполнение во времени независимо от объема. Простота реализации делает TWAP популярным для менее ликвидных инструментов, где порой очень трудно сделать реалистичный прогноз внутридневного профиля объема (volume profile).
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Симуляция финансового ряда
np.random.seed(42)
minutes = 390 # торговый день 6.5 часов
dates = pd.date_range("2025-10-31 09:30", periods=minutes, freq="1min")
prices = 100 + np.cumsum(np.random.randn(minutes) * 0.1)
volumes = 100 + 900*np.sin(np.linspace(0, np.pi, minutes)) + np.random.randint(0,50,minutes)
data = pd.DataFrame({'Close': prices, 'Volume': volumes}, index=dates)
# VWAP и TWAP исполнение
def vwap_exec(df, total_shares, intervals):
df['bucket'] = pd.cut(range(len(df)), bins=intervals, labels=False)
agg = df.groupby('bucket').agg({'Close':'mean','Volume':'sum'})
agg['shares'] = (agg['Volume']/agg['Volume'].sum()*total_shares).astype(int)
agg.iloc[-1, agg.columns.get_loc('shares')] += total_shares - agg['shares'].sum()
return (agg['shares']*agg['Close']).sum()/total_shares, agg
def twap_exec(df, total_shares, intervals):
df['bucket'] = pd.cut(range(len(df)), bins=intervals, labels=False)
agg = df.groupby('bucket').agg({'Close':'mean'})
agg['shares'] = total_shares // intervals
agg.iloc[-1, agg.columns.get_loc('shares')] += total_shares - agg['shares'].sum()
return (agg['shares']*agg['Close']).sum()/total_shares, agg
# Параметры
total_shares = 10000
num_intervals = 10
vwap_price, vwap_sched = vwap_exec(data, total_shares, num_intervals)
twap_price, twap_sched = twap_exec(data, total_shares, num_intervals)
# Визуализация
fig, ax = plt.subplots(2, 1, figsize=(10,6))
ax[0].plot(data.index, data['Close'], color='black', label='Price')
ax[0].axhline(vwap_price, color='blue', linestyle='--', label=f'VWAP ${vwap_price:.2f}')
ax[0].axhline(twap_price, color='red', linestyle='--', label=f'TWAP ${twap_price:.2f}')
ax[0].set_title("Intraday Price vs Execution Price")
ax[0].legend()
width = 0.35
ax[1].bar(range(num_intervals), vwap_sched['shares'], width, label='VWAP', alpha=0.7)
ax[1].bar(np.arange(num_intervals)+width, twap_sched['shares'], width, label='TWAP', alpha=0.7)
ax[1].set_title("Execution Schedule")
ax[1].legend()
plt.tight_layout()
plt.show()
# Сравнение
print(f"VWAP Price: {vwap_price:.2f}, TWAP Price: {twap_price:.2f}")
VWAP Price: 99.52, TWAP Price: 99.62

Рис. 5: Сравнение VWAP и TWAP алгоритмов исполнения. Верхняя панель показывает внутридневное движение цены с достигнутыми средними ценами исполнения. Нижняя панель — объем сделок по времени суток при подходе VWAP и TWAP
Алгоритм VWAP распределяет ордер пропорционально объему на каждом временном интервале, учитывая пиковые моменты активности, тогда как TWAP равномерно делит объем по времени без учета объёмного профиля. Визуализация показывает, как различаются цены исполнения и распределение объема между интервалами для двух методов.
Оптимизация размеров ордеров с помощью VWAP и TWAP позволяет трейдерам и алгоритмическим системам выбирать оптимальную стратегию исполнения для крупных ордеров, минимизируя рыночное воздействие и снижая риск проскальзывания.
Алгоритмы оптимального исполнения
Модель Almgren-Chriss формализует компромисс между рыночным воздействием (market impact) и риском времени исполнения (timing risk): постоянное воздействие на цену (permanent impact) сдвигает цену необратимо, временное воздействие на цену (temporary impact) действует только во время исполнения ордера. Модель вычисляет оптимальную траекторию размещения ордера, минимизируя ожидаемые издержки с учетом допустимого уровня риска.
Целевая функция Almgren-Chriss:
J = E[C] + λ Var[C]
где:
- C — общая стоимость исполнения;
- E[C] — ожидаемые издержки (market impact);
- Var[C] — дисперсия издержек (timing risk);
- λ — параметр risk aversion.
Параметр λ контролирует агрессивность исполнения: λ→0 минимизирует ожидаемые издержки (медленное исполнение), большие λ снижают риск за счет быстрого исполнения. Модель выдает оптимальную траекторию исполнения: сколько акций торговать в каждый момент времени.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
class AlmgrenChrissOptimizer:
"""
Модель Almgren-Chriss для оптимального исполнения
"""
def __init__(self, total_shares, T, N, sigma, eta, gamma, lambda_risk=1e-6):
self.X = total_shares
self.T = T
self.N = N
self.tau = T / N
self.sigma = sigma
self.eta = eta
self.gamma = gamma
self.lambda_risk = lambda_risk
def compute_optimal_trajectory(self):
"""Расчет оптимальной траектории исполнения"""
kappa = np.sqrt(self.lambda_risk * self.sigma**2 / self.eta)
sinh_total = np.sinh(kappa * self.T) if kappa != 0 else 1
holdings = np.zeros(self.N + 1)
for j in range(self.N + 1):
t_j = j * self.tau
holdings[j] = self.X * np.sinh(kappa * (self.T - t_j)) / sinh_total
trade_list = -np.diff(holdings)
return holdings, trade_list
def compute_costs(self, trade_list, price_path):
"""Расчет издержек исполнения"""
permanent_impact = np.cumsum(self.gamma * trade_list)
temporary_impact = self.eta * trade_list
execution_prices = price_path[:-1] + permanent_impact + temporary_impact
total_cost = np.sum(trade_list * execution_prices)
benchmark_cost = self.X * price_path[0]
implementation_shortfall = total_cost - benchmark_cost
return {
'total_cost': total_cost,
'benchmark_cost': benchmark_cost,
'shortfall': implementation_shortfall,
'shortfall_bps': (implementation_shortfall / benchmark_cost) * 10000,
'permanent_impact': permanent_impact,
'temporary_impact': temporary_impact
}
# Параметры симуляции
total_shares = 1000000
T = 1.0
N = 20
sigma = 0.1 # 10% дневная волатильность
eta = 1e-6 # временный импакт
gamma = 1e-5 # постоянный импакт
initial_price = 100
lambda_values = [1e-2, 1e-1, 1.0]
np.random.seed(42)
price_path = initial_price * np.exp(np.cumsum(np.concatenate([[0],
np.random.normal(0, sigma / np.sqrt(N), N)])))
results = {}
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
for lambda_risk in lambda_values:
optimizer = AlmgrenChrissOptimizer(total_shares, T, N, sigma, eta, gamma, lambda_risk)
holdings, trade_list = optimizer.compute_optimal_trajectory()
costs = optimizer.compute_costs(trade_list, price_path)
results[lambda_risk] = {
'holdings': holdings,
'trade_list': trade_list,
'costs': costs
}
time_points = np.linspace(0, T, N + 1)
axes[0, 0].plot(time_points, holdings / total_shares, linewidth=2, label=f'λ = {lambda_risk:.2f}')
axes[0, 1].plot(time_points[1:], trade_list / total_shares, linewidth=2, label=f'λ = {lambda_risk:.2f}')
axes[0, 0].set_xlabel('Time (fraction of day)')
axes[0, 0].set_ylabel('Remaining Holdings (fraction)')
axes[0, 0].set_title('Optimal Execution Trajectories')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
axes[0, 1].set_xlabel('Time (fraction of day)')
axes[0, 1].set_ylabel('Trading Rate (fraction)')
axes[0, 1].set_title('Optimal Trading Rates')
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)
# Сравнение затрат на исполнение
lambda_labels = [f'{lam:.2f}' for lam in lambda_values]
shortfalls = [results[lam]['costs']['shortfall_bps'] for lam in lambda_values]
colors = ['green', 'blue', 'red']
axes[1, 0].bar(lambda_labels, shortfalls, color=colors, alpha=0.7)
axes[1, 0].set_xlabel('Risk Aversion (λ)')
axes[1, 0].set_ylabel('Implementation Shortfall (bps)')
axes[1, 0].set_title('Execution Cost vs Risk Aversion')
axes[1, 0].grid(True, alpha=0.3, axis='y')
# Декомпозиция импакта на рынок для среднего λ
mid_lambda = lambda_values[1]
permanent = results[mid_lambda]['costs']['permanent_impact']
temporary = results[mid_lambda]['costs']['temporary_impact']
time_intervals = np.linspace(optimizer.tau, T, N)
axes[1, 1].fill_between(time_intervals, 0, permanent, alpha=0.5, color='red', label='Permanent Impact')
axes[1, 1].fill_between(time_intervals, permanent, permanent + temporary, alpha=0.5, color='orange', label='Temporary Impact')
axes[1, 1].set_xlabel('Time (fraction of day)')
axes[1, 1].set_ylabel('Cumulative Price Impact')
axes[1, 1].set_title(f'Market Impact Components (λ = {mid_lambda:.2f})')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Вывод результатов
print("Almgren-Chriss Optimization Results:\n")
for lambda_risk in lambda_values:
costs = results[lambda_risk]['costs']
print(f"λ = {lambda_risk:.2f}:")
print(f" Implementation Shortfall: {costs['shortfall_bps']:.2f} bps")
print(f" Total Execution Cost: ${costs['total_cost']:,.0f}")
print(f" vs Benchmark: ${costs['shortfall']:,.0f}\n")

Рис. 6: Оптимизация исполнения биржевых ордеров по модели Almgren-Chriss для различных уровней неприятия к риску (risk aversion). Верхняя левая панель показывает траектории holdings: агрессивные стратегии (высокий λ) быстро снижают позицию, консервативные (низкий λ) исполняют медленнее. Верхняя правая — темп торговли в каждый интервал. Нижняя левая демонстрирует сравнение затрат Execution Cost в сравнении с Risk Aversion. Нижняя правая разлагает маркет-импакт на компоненты: permanent (необратимый сдвиг цены) и temporary (краткосрочное давление)
Almgren-Chriss Optimization Results:
λ = 0.01:
Implementation Shortfall: 789.75 bps
Total Execution Cost: $107,897,471
vs Benchmark: $7,897,471
λ = 0.10:
Implementation Shortfall: 918.79 bps
Total Execution Cost: $109,187,937
vs Benchmark: $9,187,937
λ = 1.00:
Implementation Shortfall: 1092.72 bps
Total Execution Cost: $110,927,198
vs Benchmark: $10,927,198
Результаты показывают, что с ростом λ (то есть с увеличением неприятия риска) стратегия исполняет ордер быстрее, что приводит к росту Implementation Shortfall: при λ = 0.01 оно составляет 789,75 б.п., при λ = 0.10 – 918,79 б.п., а при λ = 1.00 – уже 1092,72 б.п. Соответственно, общая стоимость исполнения и отклонение от базовой цены также увеличиваются, отражая компромисс между минимизацией риска задержки и ростом market impact.
Представленный выше код решает задачу оптимального исполнения аналитически через систему дифференциальных уравнений. Оптимальная траектория имеет экспоненциальную форму: более агрессивное исполнение в начале снижает экспозицию к ценовой неопределенности (price uncertainty), однако увеличивает market impact. Параметр κ определяет кривизну траектории.
Модель Almgren-Chriss широко применяется институциональными трейдерами для калибровки алгоритмов исполнения ордеров. Она требует оценки коэффициентов рыночного воздействия η и γ, обычно через регрессионный анализ исторических данных. Современные расширения модели учитывают нелинейный импакт, информированную торговлю и динамику книги заявок (LOB).
Выпуклая (конвексная) оптимизация в трейдинге
Конвексная оптимизация решает задачи с выпуклой целевой функцией и выпуклым множеством ограничений, что гарантирует нахождение глобального оптимума с помощью эффективных алгоритмов.
Библиотека CVXPY для Python позволяет удобно формулировать и решать конвексные задачи и широко применяется в финансах: для портфельной оптимизации, хеджирования и ребалансировки с учетом транзакционных издержек.
Преимущества конвексной постановки очевидны:
- Гарантированный глобальный оптимум;
- Высокая скорость решения даже при тысячах переменных;
- Строгие математические гарантии.
Во многих задачах трейдинга структура естественно выпуклая: минимизация риска портфеля, максимизация функций полезности с убывающей предельной полезностью, оптимизация с линейными ограничениями и ограничениями на объемы сделок. Это делает конвексный подход особенно ценным для построения надежных и масштабируемых торговых стратегий.
import numpy as np
import pandas as pd
import yfinance as yf
import cvxpy as cp
import matplotlib.pyplot as plt
# Загрузка данных для portfolio и hedge
portfolio_tickers = ['TSLA', 'F', 'GM', 'RIVN'] # Авто сектор
hedge_tickers = ['SPY', 'XLY'] # S&P 500 и Consumer Discretionary ETF
portfolio_data = yf.download(portfolio_tickers, start='2023-09-01', end='2025-09-01')['Close']
hedge_data = yf.download(hedge_tickers, start='2023-09-01', end='2025-09-01')['Close']
portfolio_returns = portfolio_data.pct_change().dropna()
hedge_returns = hedge_data.pct_change().dropna()
# Текущие holdings (в акциях)
current_holdings = np.array([500, 1000, 800, 300]) # TSLA, F, GM, RIVN
current_prices = portfolio_data.iloc[-1].values
portfolio_value = np.sum(current_holdings * current_prices)
print(f"Current Portfolio Value: ${portfolio_value:,.0f}")
print(f"Current Holdings: {dict(zip(portfolio_tickers, current_holdings))}\n")
# Задача 1: Minimum Variance Hedge
# Найти веса hedge instruments, минимизирующие variance хеджированного портфеля
# Объединенная матрица доходностей
combined_returns = pd.concat([portfolio_returns, hedge_returns], axis=1)
cov_matrix = combined_returns.cov().values * 252
n_portfolio = len(portfolio_tickers)
n_hedge = len(hedge_tickers)
# Текущие веса портфеля
portfolio_weights = (current_holdings * current_prices) / portfolio_value
# CVXPY переменные для hedge позиций (как доля portfolio value)
hedge_weights = cp.Variable(n_hedge)
# Комбинированные веса: portfolio (фиксирован) + hedge
all_weights = cp.hstack([portfolio_weights, hedge_weights])
# Целевая функция: минимизация variance
portfolio_variance = cp.quad_form(all_weights, cov_matrix)
# Ограничения
max_hedge_notional = 0.4 # Максимум 40% от стоимости портфеля на хедж
constraints = [
cp.sum(cp.abs(hedge_weights)) <= max_hedge_notional, # Максимальный размер хеджа ] # Решение problem = cp.Problem(cp.Minimize(portfolio_variance), constraints) problem.solve() optimal_hedge_weights = hedge_weights.value optimal_variance = portfolio_variance.value # Hedged portfolio характеристики unhedged_weights = np.concatenate([portfolio_weights, np.zeros(n_hedge)]) unhedged_variance = unhedged_weights.T @ cov_matrix @ unhedged_weights print("Minimum Variance Hedge Results:") print(f"Optimal Hedge Weights: {dict(zip(hedge_tickers, optimal_hedge_weights))}") print(f"Hedge Notional: ${np.sum(np.abs(optimal_hedge_weights)) * portfolio_value:,.0f}") print(f"Unhedged Variance: {unhedged_variance:.6f}") print(f"Hedged Variance: {optimal_variance:.6f}") print(f"Variance Reduction: {(1 - optimal_variance/unhedged_variance) * 100:.1f}%\n") # Задача 2: Portfolio Rebalancing с Transaction Costs # Ребалансировка к целевым весам с минимизацией издержек target_weights = np.array([0.30, 0.25, 0.25, 0.20]) # Целевое распределение transaction_cost_rate = 0.001 # 10 bps на покупку/продажу # Текущие веса (до ребалансировки) current_weights_actual = (current_holdings * current_prices) / portfolio_value # Переменные: новые веса new_weights = cp.Variable(n_portfolio) # Tracking error к целевым весам tracking_error = cp.sum_squares(new_weights - target_weights) # Транзакционные издержки (пропорциональны turnover) turnover = cp.sum(cp.abs(new_weights - current_weights_actual)) transaction_costs = transaction_cost_rate * turnover # Ковариационная матрица портфеля portfolio_cov = portfolio_returns.cov().values * 252 # Multi-objective: минимизация tracking error + transaction costs + небольшой penalty на риск risk_penalty = 0.1 objective = tracking_error + transaction_costs + risk_penalty * cp.quad_form(new_weights, portfolio_cov) # Ограничения rebalance_constraints = [ cp.sum(new_weights) == 1, # Fully invested new_weights >= 0, # Long only
new_weights <= 0.40 # Максимум 40% в один актив ] # Решение rebalance_problem = cp.Problem(cp.Minimize(objective), rebalance_constraints) rebalance_problem.solve() optimal_new_weights = new_weights.value # Расчет требуемых сделок required_trades_shares = (optimal_new_weights - current_weights_actual) * portfolio_value / current_prices total_turnover = np.sum(np.abs(required_trades_shares * current_prices)) / portfolio_value estimated_costs = total_turnover * transaction_cost_rate * portfolio_value print("Portfolio Rebalancing Results:") print(f"\nCurrent Weights: {dict(zip(portfolio_tickers, np.round(current_weights_actual, 3)))}") print(f"Target Weights: {dict(zip(portfolio_tickers, target_weights))}") print(f"Optimal New Weights: {dict(zip(portfolio_tickers, np.round(optimal_new_weights, 3)))}") print(f"\nRequired Trades (shares):") for ticker, trades in zip(portfolio_tickers, required_trades_shares): action = "BUY" if trades > 0 else "SELL"
print(f" {ticker}: {action} {abs(trades):.0f} shares")
print(f"\nTotal Turnover: {total_turnover:.2%}")
print(f"Estimated Transaction Costs: ${estimated_costs:,.0f}")
# Визуализация
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Hedge comparison
categories = ['Unhedged', 'Hedged']
variances = [unhedged_variance, optimal_variance]
volatilities = [np.sqrt(v) * 100 for v in variances]
axes[0].bar(categories, volatilities, color=['red', 'green'], alpha=0.7)
axes[0].set_ylabel('Annual Volatility (%)', fontsize=11)
axes[0].set_title('Portfolio Risk: Unhedged vs Hedged', fontsize=12)
axes[0].grid(True, alpha=0.3, axis='y')
for i, (cat, vol) in enumerate(zip(categories, volatilities)):
axes[0].text(i, vol + 0.5, f'{vol:.1f}%', ha='center', fontsize=10, fontweight='bold')
# Rebalancing comparison
x = np.arange(len(portfolio_tickers))
width = 0.25
axes[1].bar(x - width, current_weights_actual, width, label='Current', color='gray', alpha=0.7)
axes[1].bar(x, target_weights, width, label='Target', color='blue', alpha=0.7)
axes[1].bar(x + width, optimal_new_weights, width, label='Optimal', color='green', alpha=0.7)
axes[1].set_xlabel('Assets', fontsize=11)
axes[1].set_ylabel('Weight', fontsize=11)
axes[1].set_title('Portfolio Weights Comparison', fontsize=12)
axes[1].set_xticks(x)
axes[1].set_xticklabels(portfolio_tickers)
axes[1].legend()
axes[1].grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.show()
Current Portfolio Value: $175,341
Current Holdings: {'TSLA': np.int64(500), 'F': np.int64(1000), 'GM': np.int64(800), 'RIVN': np.int64(300)}
Minimum Variance Hedge Results:
Optimal Hedge Weights: {'SPY': np.float64(-1.387574815140777e-23), 'XLY': np.float64(-0.4)}
Hedge Notional: $70,136
Unhedged Variance: 0.187115
Hedged Variance: 0.131513
Variance Reduction: 29.7%
Portfolio Rebalancing Results:
Current Weights: {'TSLA': np.float64(0.034), 'F': np.float64(0.333), 'GM': np.float64(0.062), 'RIVN': np.float64(0.571)}
Target Weights: {'TSLA': np.float64(0.3), 'F': np.float64(0.25), 'GM': np.float64(0.25), 'RIVN': np.float64(0.2)}
Optimal New Weights: {'TSLA': np.float64(0.304), 'F': np.float64(0.255), 'GM': np.float64(0.243), 'RIVN': np.float64(0.198)}
Required Trades (shares):
TSLA: BUY 4022 shares
F: SELL 234 shares
GM: BUY 2343 shares
RIVN: SELL 196 shares
Total Turnover: 90.27%
Estimated Transaction Costs: $158

Рис. 7: Результаты конвексной оптимизации для хеджирования и ребалансировки портфеля. Левая панель показывает снижение волатильности через оптимальный хедж с использованием ETF. Правая панель сравнивает текущие, целевые, и оптимальные веса после учета транзакционных издержек. Алгоритм находит баланс между точностью следования за целевыми весами и минимизацией объема сделок, поэтому новые веса изменяются относительно текущих лишь настолько, чтобы сократить транзакционные издержки
Интерпретация:
- Оптимальный хедж через ETF XLY снизил годовую волатильность портфеля с ~43% до ~36%, что дало 29,7% сокращение дисперсии;
- Ребалансировка портфеля с учетом транзакционных издержек изменила текущие веса: TSLA увеличена с 3,4% до 30,4%, F с 33,3% до 25,5%, GM с 6,2% до 24,3%, RIVN уменьшена с 57,1% до 19,8%;
- Общий оборот составил 90,3% стоимости портфеля;
- Предполагаемые издержки на ребалансировки – всего $158, что демонстрирует, что оптимизация одновременно снижает риск и контролирует расходы на сделки.
Приведенный код решает две задачи конвексной оптимизации:
- Minimum Variance Hedge — вычисляет оптимальные позиции в хеджирующих инструментах для снижения риска существующего портфеля. Задача формулируется как квадратичное программирование: минимизация квадратичной формы w^T Σ w при линейных ограничениях на размер хеджа.
- Portfolio Rebalancing — учитывает транзакционные издержки при приведении портфеля к целевым весам. Многоцелевой функционал одновременно минимизирует ошибку к целевому распределению (tracking error), издержки от оборота портфеля (portfolio turnover) и небольшое штрафное значение за риск. CVXPY автоматически подбирает подходящий solver (OSQP, ECOS, SCS) в зависимости от структуры задачи.
Результаты демонстрируют практическую ценность оптимизации: хеджирование снижает волатильность портфеля при ограниченном объеме капитала, а ребалансировка находит баланс между точным отслеживанием целевых весов и минимизацией транзакционных издержек. Оптимальные веса отклоняются от целевых там, где прямое соответствие потребовало бы больших сделок, что позволяет снизить расходы и поддерживать эффективность портфеля.
Квадратичное программирование
Квадратичное программирование (QP) — частный случай конвексной оптимизации с квадратичной целевой функцией и линейными ограничениями. Большинство портфельных задач естественно формулируются как QP: минимизация variance, максимизация utility, оптимизация mean-variance.
Стандартная форма QP:
min (1/2) x^T Q x + c^T x
subject to: Ax ≤ b, A_eq x = b_eq
где:
- x — вектор переменных (веса активов);
- Q — матрица квадратичных коэффициентов (обычно ковариация);
- c — вектор линейных коэффициентов (ожидаемые доходности с минусом);
- A, b — матрица и вектор линейных неравенств;
- A_eq, b_eq — ограничения-равенства.
Множитель 1/2 перед квадратичным членом упрощает вычисление производных. Для портфельной оптимизации:
Q = Σ
c = -λμ
где:
- Σ — ковариационная матрица доходностей;
- λ — коэффициент компромисса между доходностью и риском;
- μ — вектор ожидаемых доходностей активов.
import numpy as np
import pandas as pd
import yfinance as yf
import cvxpy as cp
import matplotlib.pyplot as plt
from scipy.optimize import minimize
# Загрузка данных
tickers = ['VALE', 'PBR', 'ITUB', 'BBD', 'ABEV'] # Бразильские акции
data = yf.download(tickers, start='2022-09-01', end='2025-09-01')['Close']
returns = data.pct_change().dropna()
mean_returns = returns.mean() * 252
cov_matrix = returns.cov().values * 252
n_assets = len(tickers)
# Задача: Максимизация utility = доходность - (λ/2) * риск с ограничениями
def solve_qp_cvxpy(mean_returns, cov_matrix, risk_aversion, min_weight=0.0, max_weight=0.30):
"""Решение QP через CVXPY"""
n = len(mean_returns)
w = cp.Variable(n)
# Utility function: return - (risk_aversion/2) * variance
portfolio_return = mean_returns @ w
portfolio_variance = cp.quad_form(w, cov_matrix)
utility = portfolio_return - (risk_aversion / 2) * portfolio_variance
constraints = [
cp.sum(w) == 1,
w >= min_weight,
w <= max_weight ] problem = cp.Problem(cp.Maximize(utility), constraints) problem.solve(solver=cp.OSQP) return w.value, problem.value def solve_qp_scipy(mean_returns, cov_matrix, risk_aversion, min_weight=0.0, max_weight=0.30): """Решение QP через scipy.optimize (квадратичная аппроксимация)""" n = len(mean_returns) def objective(w): portfolio_return = np.dot(mean_returns, w) portfolio_variance = np.dot(w.T, np.dot(cov_matrix, w)) return -(portfolio_return - (risk_aversion / 2) * portfolio_variance) def objective_grad(w): return -(mean_returns - risk_aversion * np.dot(cov_matrix, w)) constraints = [ {'type': 'eq', 'fun': lambda w: np.sum(w) - 1} ] bounds = tuple((min_weight, max_weight) for _ in range(n)) initial_guess = np.array([1/n] * n) result = minimize(objective, initial_guess, method='SLSQP', jac=objective_grad, bounds=bounds, constraints=constraints) return result.x, -result.fun # Сравнение методов для различных уровней risk aversion risk_aversions = [0.5, 2.0, 10.0] comparison_results = [] for ra in risk_aversions: # CVXPY solution weights_cvx, utility_cvx = solve_qp_cvxpy(mean_returns.values, cov_matrix, ra) ret_cvx = np.dot(weights_cvx, mean_returns.values) risk_cvx = np.sqrt(np.dot(weights_cvx.T, np.dot(cov_matrix, weights_cvx))) # Scipy solution weights_scipy, utility_scipy = solve_qp_scipy(mean_returns.values, cov_matrix, ra) ret_scipy = np.dot(weights_scipy, mean_returns.values) risk_scipy = np.sqrt(np.dot(weights_scipy.T, np.dot(cov_matrix, weights_scipy))) comparison_results.append({ 'risk_aversion': ra, 'weights_cvx': weights_cvx, 'weights_scipy': weights_scipy, 'return_cvx': ret_cvx, 'return_scipy': ret_scipy, 'risk_cvx': risk_cvx, 'risk_scipy': risk_scipy, 'utility_cvx': utility_cvx, 'utility_scipy': utility_scipy }) # Построение эффективной границы с QP target_returns = np.linspace(mean_returns.min(), mean_returns.max(), 30) efficient_frontier_qp = [] for target in target_returns: w = cp.Variable(n_assets) portfolio_risk = cp.quad_form(w, cov_matrix) constraints = [ cp.sum(w) == 1, mean_returns.values @ w == target, w >= 0,
w <= 0.30
]
problem = cp.Problem(cp.Minimize(portfolio_risk), constraints)
problem.solve(solver=cp.OSQP)
if problem.status == 'optimal':
efficient_frontier_qp.append({
'return': target,
'risk': np.sqrt(problem.value),
'weights': w.value
})
ef_df = pd.DataFrame(efficient_frontier_qp)
# Визуализация
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Efficient Frontier
axes[0, 0].plot(ef_df['risk'] * 100, ef_df['return'] * 100, 'k-', linewidth=2, label='Efficient Frontier')
# Оптимальные портфели для разных risk aversion
colors = ['green', 'blue', 'red']
for idx, (ra, color) in enumerate(zip(risk_aversions, colors)):
res = comparison_results[idx]
axes[0, 0].scatter(res['risk_cvx'] * 100, res['return_cvx'] * 100,
s=150, c=color, marker='*', edgecolors='black', linewidths=1.5,
label=f'λ = {ra}')
axes[0, 0].set_xlabel('Volatility (%)', fontsize=11)
axes[0, 0].set_ylabel('Expected Return (%)', fontsize=11)
axes[0, 0].set_title('Efficient Frontier with Optimal Portfolios', fontsize=12)
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
# Weights comparison для низкого risk aversion
ra_idx = 0
res = comparison_results[ra_idx]
[ra_idx]
x = np.arange(len(tickers))
width = 0.35
axes[0, 1].bar(x - width/2, res['weights_cvx'], width, label='CVXPY', color='blue', alpha=0.7)
axes[0, 1].bar(x + width/2, res['weights_scipy'], width, label='Scipy', color='green', alpha=0.7)
axes[0, 1].set_xlabel('Assets', fontsize=11)
axes[0, 1].set_ylabel('Weight', fontsize=11)
axes[0, 1].set_title(f'Weights Comparison (λ = {risk_aversions[ra_idx]})', fontsize=12)
axes[0, 1].set_xticks(x)
axes[0, 1].set_xticklabels(tickers)
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3, axis='y')
# Risk-return trade-off
returns_list = [res['return_cvx'] for res in comparison_results]
risks_list = [res['risk_cvx'] for res in comparison_results]
axes[1, 0].plot(risk_aversions, returns_list, 'o-', color='blue', linewidth=2, markersize=8, label='Return')
ax2 = axes[1, 0].twinx()
ax2.plot(risk_aversions, risks_list, 's-', color='red', linewidth=2, markersize=8, label='Risk')
axes[1, 0].set_xlabel('Risk Aversion (λ)', fontsize=11)
axes[1, 0].set_ylabel('Expected Return', fontsize=11, color='blue')
ax2.set_ylabel('Volatility', fontsize=11, color='red')
axes[1, 0].set_title('Risk-Return Trade-off vs Risk Aversion', fontsize=12)
axes[1, 0].tick_params(axis='y', labelcolor='blue')
ax2.tick_params(axis='y', labelcolor='red')
axes[1, 0].grid(True, alpha=0.3)
# Portfolio concentration (Herfindahl index)
herfindahl_indices = []
for ra in risk_aversions:
w_cvx, _ = solve_qp_cvxpy(mean_returns.values, cov_matrix, ra)
hhi = np.sum(w_cvx**2)
herfindahl_indices.append(hhi)
axes[1, 1].bar([str(ra) for ra in risk_aversions], herfindahl_indices, color=colors, alpha=0.7)
axes[1, 1].set_xlabel('Risk Aversion (λ)', fontsize=11)
axes[1, 1].set_ylabel('Herfindahl Index', fontsize=11)
axes[1, 1].set_title('Portfolio Concentration', fontsize=12)
axes[1, 1].axhline(y=1/n_assets, color='black', linestyle='--', linewidth=1, label='Equal Weight')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.show()
# Численные результаты
print("Quadratic Programming Results:\n")
for idx, ra in enumerate(risk_aversions):
res = comparison_results[idx]
print(f"Risk Aversion λ = {ra}:")
print(f" Expected Return: {res['return_cvx'] * 100:.2f}%")
print(f" Volatility: {res['risk_cvx'] * 100:.2f}%")
print(f" Sharpe Ratio: {res['return_cvx'] / res['risk_cvx']:.2f}")
print(f" Utility: {res['utility_cvx']:.4f}")
print(f" Weights: {dict(zip(tickers, np.round(res['weights_cvx'], 3)))}")
print(f" Herfindahl Index: {np.sum(res['weights_cvx']**2):.3f}\n")
# Проверка согласованности методов
print("Solver Comparison (CVXPY vs Scipy):")
for idx, ra in enumerate(risk_aversions):
res = comparison_results[idx]
weights_diff = np.max(np.abs(res['weights_cvx'] - res['weights_scipy']))
utility_diff = abs(res['utility_cvx'] - res['utility_scipy'])
print(f"λ = {ra}: Max weight diff = {weights_diff:.6f}, Utility diff = {utility_diff:.6f}")

Рис. 8: Квадратичное программирование для портфельной оптимизации. Верхняя левая панель показывает эффективную границу с оптимальными портфелями для трех уровней risk aversion (зеленый — низкий, синий — средний, красный — высокий). Верхняя правая сравнивает веса от CVXPY и Scipy solvers. Нижняя левая демонстрирует влияние λ на риск и доходность: синяя линия — ожидаемая доходность снижается, красная — волатильность падает. Нижняя правая — концентрация портфеля через Herfindahl index: высокий risk aversion приводит к более диверсифицированным портфелям
Quadratic Programming Results:
Risk Aversion λ = 0.5:
Expected Return: 19.10%
Volatility: 29.23%
Sharpe Ratio: 0.65
Utility: 0.1697
Weights: {'VALE': np.float64(-0.0), 'PBR': np.float64(0.3), 'ITUB': np.float64(0.3), 'BBD': np.float64(0.3), 'ABEV': np.float64(0.1)}
Herfindahl Index: 0.280
Risk Aversion λ = 2.0:
Expected Return: 18.61%
Volatility: 27.41%
Sharpe Ratio: 0.68
Utility: 0.1109
Weights: {'VALE': np.float64(-0.0), 'PBR': np.float64(0.115), 'ITUB': np.float64(0.3), 'BBD': np.float64(0.3), 'ABEV': np.float64(0.285)}
Herfindahl Index: 0.274
Risk Aversion λ = 10.0:
Expected Return: 16.76%
Volatility: 25.72%
Sharpe Ratio: 0.65
Utility: -0.1631
Weights: {'VALE': np.float64(0.222), 'PBR': np.float64(-0.0), 'ITUB': np.float64(0.3), 'BBD': np.float64(0.291), 'ABEV': np.float64(0.186)}
Herfindahl Index: 0.259
Solver Comparison (CVXPY vs Scipy):
λ = 0.5: Max weight diff = 0.000000, Utility diff = 0.000000
λ = 2.0: Max weight diff = 0.001170, Utility diff = 0.000000
λ = 10.0: Max weight diff = 0.001227, Utility diff = 0.000001
Код сравнивает два подхода к решению QP: специализированный solver CVXPY (OSQP) и общий оптимизатор scipy с SLSQP. OSQP эксплуатирует структуру квадратичной задачи и работает быстрее для больших портфелей (1000+ активов). SLSQP использует последовательное квадратичное программирование — итеративную аппроксимацию нелинейных задач.
Эффективная граница портфеля (Efficient frontier) строится через серию QP задач: для каждой целевой доходности минимизируется риск. Полученная кривая представляет Парето-оптимальные портфели — улучшение доходности возможно только через увеличение риска. Звездочки на графике показывают оптимальные портфели для разных λ: они лежат на касательных к эффективной границе.
График risk-return trade-off демонстрирует эффект параметра λ: его увеличение снижает ожидаемую доходность, однако при этом снижает и волатильность. Herfindahl index измеряет концентрацию портфеля: значения близкие к 1/N указывают на равномерное распределение, высокие — на концентрацию в нескольких активах.
Заключение
Оптимизация в биржевой торговле позволяет системно балансировать доходность, риск и издержки. Использование конвексных методов, квадратичного программирования и современных подходов к настройке стратегий (Random Search, байесовская оптимизация, Walk-forward) демонстрирует, что даже при ограниченном объеме капитала и высокой волатильности можно находить эффективные портфели и адаптивные параметры стратегий с высоким коэффициентом Шарпа и контролируемым риском.
Понимание того, как использовать оптимизационные методы в биржевой торговле, позволяет принимать более обоснованные инвестиционные решения: минимизировать волатильность портфеля через хеджирование, балансировать риск между активами, снижать транзакционные издержки и повышать устойчивость торговых стратегий к рыночным изменениям. Такой подход обеспечивает системность в управлении капиталом и делает стратегии воспроизводимыми и пригодными для реальной торговли.