Математическое ожидание определяет среднее значение случайной величины при бесконечном количестве наблюдений. В трейдинге этот показатель отвечает на вопрос: какую прибыль или убыток принесет стратегия в долгосрочной перспективе. Стратегия с положительным матожиданием доходности генерирует прибыль при достаточном количестве сделок, стратегия с отрицательным — приводит к убыткам независимо от краткосрочных результатов.
Понимание матожидания позволяет отделить случайную удачу от систематического преимущества. Трейдер может выиграть серию сделок благодаря везению, но только положительное матожидание обеспечивает стабильную прибыльность на дистанции сотен и тысяч транзакций.
Расчет матожидания для дискретных случайных величин
Для дискретной случайной величины X с возможными значениями x₁, x₂, …, xₙ и соответствующими вероятностями p₁, p₂, …, pₙ математическое ожидание рассчитывается как:
E[X] = x₁ · p₁ + x₂ · p₂ + … + xₙ · pₙ
где:
- E[X] — математическое ожидание случайной величины X;
- xᵢ — возможное значение случайной величины;
- pᵢ — вероятность появления значения xᵢ.
Формула определяет взвешенное среднее всех возможных исходов, где веса соответствуют вероятностям.
Пример торговой стратегии: вероятность прибыльной сделки 40% с доходностью +2%, вероятность убыточной сделки 60% с доходностью -1%. Математическое ожидание доходности: E[R] = 0.4 · 2% + 0.6 · (-1%) = 0.8% — 0.6% = 0.2%. Положительное значение указывает на прибыльность стратегии в долгосрочной перспективе.
Математическое ожидание непрерывных распределений
Для непрерывной случайной величины с функцией плотности вероятности f(x) математическое ожидание определяется интегралом:
E[X] = ∫ x · f(x) dx
где f(x) — функция плотности вероятности, а интегрирование проводится по всей области определения.
В трейдинге непрерывные распределения моделируют доходности активов. Нормальное распределение часто используется как первое приближение, хотя реальные доходности демонстрируют толстые хвосты и асимметрию.
Свойства математического ожидания
Линейность — ключевое свойство для практических расчетов. Для случайных величин X и Y и констант a, b:
E[aX + bY] = aE[X] + bE[Y]
Это позволяет разбивать сложные стратегии на компоненты и анализировать каждый из них независимо. Математическое ожидание суммы равно сумме математических ожиданий независимо от корреляции между величинами.
Для независимых случайных величин выполняется мультипликативное свойство:
E[X · Y] = E[X] · E[Y]
Это свойство применяется при расчете ожидаемой доходности стратегий с реинвестированием прибыли, хотя на практике доходности в разные периоды редко бывают строго независимыми.
Расчет ожидаемой доходности сделки
Базовая формула ожидаемой доходности учитывает вероятность успеха и соотношение прибыли к убытку:
E[R] = P(win) · Avg(win) + P(loss) · Avg(loss)
где:
- P(win) — вероятность прибыльной сделки (винрейт);
- Avg(win) — средняя прибыль в прибыльной сделке;
- P(loss) — вероятность убыточной сделки;
- Avg(loss) — средний убыток в убыточной сделке (отрицательное значение).
Стратегия прибыльна, если E[R] > 0. При винрейте 45%, средней прибыли $150 и среднем убытке -$80: E[R] = 0.45 · 150 + 0.55 · (-80) = 67.5 — 44 = $23.5 на сделку.
Альтернативная запись через Risk/Reward Ratio (RRR):
E[R] = P(win) · RRR — P(loss)
где RRR = Avg(win) / |Avg(loss)|. Эта форма удобна для быстрой оценки: при RRR = 2 и винрейте 40% получаем E[R] = 0.4 · 2 — 0.6 = 0.2 или 20% доходности на риск.
Учет комиссий и проскальзывания
Профессионалы трейдинга всегда учитывают в расчете матожидания транзакционные издержки:
E[R_net] = E[R_gross] — 2 · commission — slippage
где:
- E[R_gross] — доходность без учета издержек;
- commission — комиссия брокера за сделку (вход и выход);
- slippage — проскальзывание при исполнении ордера.
Учет комиссий и проскальзывания особенно важен для высокочастотных стратегий. При комиссии 0.1% и проскальзывании 0.05% общие издержки составляют 0.3% на round-trip (вход + выход). Стратегия с валовой доходностью 0.4% на сделку имеет чистую доходность всего 0.1%.
Ниже пример кода для расчета этих метрик.
def calculate_expected_return(win_rate, avg_win, avg_loss, commission=0.001, slippage=0.0005):
"""
Расчет ожидаемой доходности с учетом издержек.
Parameters:
- win_rate: вероятность прибыльной сделки (0-1)
- avg_win: средняя прибыль в % (положительное число)
- avg_loss: средний убыток в % (отрицательное число)
- commission: комиссия за сделку в долях
- slippage: проскальзывание в долях
"""
loss_rate = 1 - win_rate
gross_return = win_rate * avg_win + loss_rate * avg_loss
transaction_costs = 2 * commission + slippage
net_return = gross_return - transaction_costs
return {
'gross_return': gross_return,
'transaction_costs': transaction_costs,
'net_return': net_return
}
# Пример использования
result = calculate_expected_return(
win_rate=0.52,
avg_win=1.5,
avg_loss=-1.2,
commission=0.001,
slippage=0.0005
)
print(f"Gross Return: {result['gross_return']:.4f}%")
print(f"Transaction Costs: {result['transaction_costs']:.4f}%")
print(f"Net Return: {result['net_return']:.4f}%")
Gross Return: 0.2040%
Transaction Costs: 0.0025%
Net Return: 0.2015%
Функция рассчитывает валовую и чистую доходность, явно разделяя влияние стратегии и издержек. Результат показывает, какую часть прибыли забирают комиссии.
Положительное матожидание как основа прибыльности
Положительное матожидание означает, что при достаточно большом числе независимых сделок средняя прибыль будет стремиться к положительному значению. Однако само по себе положительное матожидание не гарантирует прибыль в каждом отдельном периоде. Если дисперсия доходности высока или количество сделок недостаточно, стратегия может демонстрировать убыток из-за статистических флуктуаций.
Минимально приемлемое матожидание зависит от волатильности результатов. Например, для стратегии со средним доходом на сделку μ = 0.3% и стандартным отклонением σ = 2% коэффициент Шарпа составит всего 0.15. Это слишком низкое значение для практического применения: стратегия имеет преимущество «в среднем», но шум почти полностью «съедает» этот эффект. На практике для алгоритмических стратегий целевым ориентиром считается Sharpe Ratio > 1, что предполагает μ ≥ σ (при прочих равных).
Временной горизонт определяет, сможет ли стратегическое преимущество реализоваться. Если стратегия совершает 100 сделок в год, ожидаемая годовая доходность составит около 30% (100 × 0.3%). Однако суммарный риск также растет: стандартное отклонение годовой доходности будет σ × √100 = 2% × 10 = 20%.
Таким образом, годовой результат распределен примерно как N(30%, 20%), что означает широкий диапазон возможных исходов — от значительной прибыли до отрицательной доходности в отдельные годы. На практике это означает, что положительное матожидание должно сопровождаться контролем дисперсии и достаточным количеством сделок, иначе статистическое преимущество просто не успеет проявиться.
import numpy as np
import matplotlib.pyplot as plt
sigma = 0.02 # стандартное отклонение 2% одинаковое для всех
mu_low = 0.003 # низкий Sharpe
mu_mid = 0.01 # средний Sharpe
mu_high = 0.02 # высокий Sharpe
n_trades = 100
n_sims = 10000
# Моделирование годовой доходности
returns_low = np.random.normal(mu_low, sigma, (n_sims, n_trades)).sum(axis=1)
returns_mid = np.random.normal(mu_mid, sigma, (n_sims, n_trades)).sum(axis=1)
returns_high = np.random.normal(mu_high, sigma, (n_sims, n_trades)).sum(axis=1)
# Построение графика с тремя распределениями
plt.figure(figsize=(10, 6))
plt.hist(returns_low, bins=60, alpha=0.4, label="Низкий Sharpe (μ = 0.3% на сделку)", density=True)
plt.hist(returns_mid, bins=60, alpha=0.4, label="Средний Sharpe (μ = 1% на сделку)", density=True)
plt.hist(returns_high, bins=60, alpha=0.4, label="Высокий Sharpe (μ = 2% на сделку)", density=True)
plt.title("Сравнение распределений годовой доходности при разных Sharpe (σ = 2%)")
plt.xlabel("Годовая доходность")
plt.ylabel("Плотность распределения")
plt.legend()
plt.show()

Рис. 1: Сравнение распределений годовой доходности при разных значениях коэффициента Шарпа. При одинаковой волатильности (σ) увеличение математического ожидания (μ) сдвигает распределение доходности вправо и уменьшает вероятность отрицательного результата. Таким образом, Sharpe Ratio отражает не только «положительность матожидания», но и стабильность реализации прибыли во времени
Оценка математического ожидания торговых стратегий
Expectancy — метрика, нормализующая матожидание относительно риска одной сделки:
Expectancy = E[R] / |Avg(loss)|
Значение показывает, сколько единиц риска приносит средняя сделка. Expectancy = 0.5 означает среднюю прибыль в половину среднего убытка. Положительная величина указывает на вероятность альфа-стратегии.
Average P&L per trade — абсолютное значение ожидаемой прибыли на сделку без нормализации. Метрика полезна для сравнения стратегий с одинаковым размером позиции, но не учитывает масштаб риска.
System Quality Number (SQN) оценивает качество стратегии через отношение среднего P&L к его стандартному отклонению:
SQN = (E[R] / σ) · √n
где:
- σ — стандартное отклонение доходности сделок;
- n — количество сделок.
SQN > 3 указывает на качественную систему, SQN > 5 — на отличную. Метрика учитывает не только матожидание, но и стабильность результатов.
Связь с винрейтом и Risk/Reward Ratio
Показатели винрейта (winrate) и отношения риск/прибыль (RRR) совместно определяют математическое ожидание стратегии. Однако разные комбинации этих параметров могут иметь одинаковое матожидание при совершенно различных характеристиках кривой доходности — волатильности, глубине просадок и стабильности результатов.
Стратегия с винрейтом 60% и отношением риск/прибыль RRR = 1 имеет математическое ожидание:
E[R] = 0.6 · 1 − 0.4 · 1 = 0.2R.
Стратегия с винрейтом 40% и RRR = 2 дает то же математическое ожидание:
E[R] = 0.4 · 2 − 0.6 · 1 = 0.2R.
Таким образом, высокий winrate в сочетании с низким RRR действительно создает визуально плавную кривую доходности (equity curve) — прибыль поступает часто и небольшими шагами. Однако такие стратегии подвержены редким, но глубоким просадкам, когда один убыток перекрывает результат десятков прибыльных сделок.
Напротив, низкий winrate при высоком RRR приводит к частым небольшим убыткам и редким большим прибылям. Такая кривая выглядит более «рваной», но риск катастрофической просадки существенно ниже: размер убытка контролируем и ограничен.
Психологически большинству трейдеров комфортнее первый вариант, однако именно он несет более высокий риск разрушения капитала при нарушении дисциплины или аномальном рыночном движении.
Минимальный винрейт для прибыльности при заданном RRR:
P(win)_min = 1 / (1 + RRR)
При RRR = 2 минимальный винрейт составляет 33.3%. Стратегии с более низким винрейтом убыточны независимо от размера прибылей. Формула определяет точку безубыточности для любого соотношения риска к прибыли.
Практические примеры расчета
Анализ стратегии на исторических данных показывает распределение результатов сделок. Импортируем библиотеки и загружаем данные о сделках:
import numpy as np
import pandas as pd
from scipy import stats
# Данные о результатах сделок (в процентах)
trades = pd.Series([
1.2, -0.8, 2.1, -0.5, -0.9, 1.5, 0.7, -1.1, 3.2, -0.6,
1.8, -0.7, 0.9, -1.3, 2.5, 1.1, -0.4, -0.9, 1.6, -0.8,
2.3, -0.5, 1.4, -1.0, 0.8, 1.9, -0.6, -1.2, 2.0, -0.7
])
def analyze_strategy_expectancy(trades):
"""Анализ математического ожидания торговой стратегии."""
wins = trades[trades > 0]
losses = trades[trades < 0]
win_rate = len(wins) / len(trades)
avg_win = wins.mean()
avg_loss = losses.mean()
expectancy = win_rate * avg_win + (1 - win_rate) * avg_loss
rrr = avg_win / abs(avg_loss)
# Статистическая значимость
t_stat, p_value = stats.ttest_1samp(trades, 0)
# Доверительный интервал для матожидания
conf_interval = stats.t.interval(
0.95,
len(trades) - 1,
loc=trades.mean(),
scale=stats.sem(trades)
)
return {
'expectancy': expectancy,
'win_rate': win_rate,
'avg_win': avg_win,
'avg_loss': avg_loss,
'rrr': rrr,
'std_dev': trades.std(),
't_statistic': t_stat,
'p_value': p_value,
'conf_interval_95': conf_interval,
'sqn': (expectancy / trades.std()) * np.sqrt(len(trades))
}
results = analyze_strategy_expectancy(trades)
print(f"Expectancy: {results['expectancy']:.4f}%")
print(f"Win Rate: {results['win_rate']:.2%}")
print(f"Risk/Reward Ratio: {results['rrr']:.2f}")
print(f"SQN: {results['sqn']:.2f}")
print(f"95% CI: [{results['conf_interval_95'][0]:.4f}%, {results['conf_interval_95'][1]:.4f}%]")
print(f"P-value: {results['p_value']:.4f}")
Expectancy: 0.4333%
Win Rate: 50.00%
Risk/Reward Ratio: 2.08
SQN: 1.75
95% CI: [-0.0732%, 0.9399%]
P-value: 0.0908
Код вычисляет базовые метрики и проверяет статистическую значимость положительного матожидания. T-test определяет, можно ли отвергнуть гипотезу о нулевой доходности. Доверительный интервал показывает диапазон вероятных значений истинного матожидания.
Интерпретация:
- Expectancy (матожидание): 0.43% — небольшое, но положительное;
- Win Rate (% прибыльных сделок): 50% — сбалансированная стратегия;
- RRR: 2.08 — стратегия с хорошим отношением прибыли к риску;
- SQN: 1.75 — считается средним качеством системы (по Дрекслеру: <1 — плохо, 1–2 — средне, >2 — хорошо);
- CI: [-0.073%, 0.9399%] — среднее может быть и отрицательным, статистически не сильно значимо (p=0.0908).
Вывод: матожидание положительное и метрики в целом неплохие, однако статистическая значимость невысока, стратегия может быть прибыльной в долгосрочной перспективе, но для уверенности нужен больший объем данных.
Применение матожидания в расчете рисков
Критерий Келли
Формула Келли определяет оптимальную долю капитала для размещения в одной сделке, максимизируя долгосрочный рост портфеля:
f* = (p · b — q) / b
где:
- f* — оптимальная доля капитала;
- p — вероятность выигрыша;
- q — вероятность проигрыша (1 — p);
- b — отношение выигрыша к проигрышу (RRR).
При винрейте 55% и RRR = 1.5 получаем: f* = (0.55 · 1.5 — 0.45) / 1.5 = 0.25 или 25% капитала. Формула предполагает возможность дробных ставок и реинвестирования прибыли.
Критерий Келли максимизирует медианный рост капитала, но приводит к высокой волатильности эквити. Практическое применение использует дробный Келли (обычно 0.25-0.5 от f*) для снижения просадок. Half-Kelly при тех же параметрах дает размер позиции 12.5% вместо 25%.
def kelly_criterion(win_rate, rrr):
"""Расчет критерия Келли."""
p = win_rate
q = 1 - win_rate
b = rrr
kelly_fraction = (p * b - q) / b
# Проверка на положительное матожидание
if kelly_fraction <= 0:
return 0
return kelly_fraction
def fractional_kelly(win_rate, rrr, fraction=0.5):
"""Дробный Келли для снижения волатильности."""
full_kelly = kelly_criterion(win_rate, rrr)
return full_kelly * fraction
# Примеры расчета
strategies = [
{'name': 'High WR', 'win_rate': 0.65, 'rrr': 0.8},
{'name': 'Balanced', 'win_rate': 0.55, 'rrr': 1.5},
{'name': 'Low WR', 'win_rate': 0.40, 'rrr': 2.5}
]
for strategy in strategies:
full = kelly_criterion(strategy['win_rate'], strategy['rrr'])
half = fractional_kelly(strategy['win_rate'], strategy['rrr'], 0.5)
quarter = fractional_kelly(strategy['win_rate'], strategy['rrr'], 0.25)
print(f"\n{strategy['name']}:")
print(f" Full Kelly: {full:.2%}")
print(f" Half Kelly: {half:.2%}")
print(f" Quarter Kelly: {quarter:.2%}")
High WR:
Full Kelly: 21.25%
Half Kelly: 10.63%
Quarter Kelly: 5.31%
Balanced:
Full Kelly: 25.00%
Half Kelly: 12.50%
Quarter Kelly: 6.25%
Low WR:
Full Kelly: 16.00%
Half Kelly: 8.00%
Quarter Kelly: 4.00%
Функции рассчитывают полный и дробный критерий Келли для разных стратегий. Результаты показывают зависимость оптимального размера позиции от характеристик системы.
Оптимальный размер позиции
Размер позиции влияет на реализацию матожидания через количество сделок и риск разорения. Слишком большие позиции увеличивают просадки и вероятность маржин-кола. Слишком малые не используют потенциал стратегии полностью.
Показатель Fixed Fractional Position Sizing выделяет фиксированный процент капитала на сделку:
Position Size = Capital · f
где f — выбранная доля (обычно 1-5% для консервативных стратегий).
При капитале $100,000 и f = 2% размер позиции составляет $2,000. Метод прост в реализации, но не учитывает изменение волатильности активов.
Показатель Volatility-Based Position Sizing нормализует риск относительно волатильности:
Position Size = (Capital · f) / (ATR · multiplier)
где ATR (Average True Range) измеряет текущую волатильность. Подход выравнивает риск между активами с разной волатильностью и адаптируется к изменению рыночных условий.
Учет дисперсии доходности
Матожидание не учитывает разброс результатов. Две стратегии с одинаковым E[R] = 1% могут иметь σ = 0.5% и σ = 5%. Первая обеспечивает стабильный рост, вторая создает высокие просадки.
Максимальная просадка (Maximum Drawdown) связана с дисперсией через коэффициент Шарпа. Эмпирическое правило:
Maximum Drawdown ≈ σ · √(n) / 2
где n — количество периодов.
При σ = 2% и 250 торговых дней ожидаемая просадка около 15.8%.
Метрика Risk-Adjusted Return учитывает волатильность при оценке эффективности:
Sharpe Ratio = (E[R] — Rf) / σ
где Rf — безрисковая ставка.
Для внутридневных стратегий Rf ≈ 0. Sharpe Ratio > 1 указывает на качественную стратегию, > 2 — на отличную. Метрика позволяет сравнивать стратегии с разным уровнем риска.
Практический расчет матожидания на Python
Бэктестинг стратегии требует точного расчета доходности сделок с учетом всех факторов. Загружаем исторические данные и применяем торговую логику:
import pandas as pd
import numpy as np
def backtest_strategy(prices, entry_signal, exit_signal, initial_capital=100000,
commission=0.001, slippage=0.0005):
"""
Бэктест стратегии с расчетом матожидания.
Parameters:
- prices: DataFrame с колонками ['open', 'high', 'low', 'close']
- entry_signal: Series с булевыми значениями для входа
- exit_signal: Series с булевыми значениями для выхода
- initial_capital: начальный капитал
- commission: комиссия брокера (доля)
- slippage: проскальзывание (доля)
"""
position = 0
entry_price = 0
trades = []
capital = initial_capital
for i in range(len(prices)):
# Вход в позицию
if entry_signal.iloc[i] and position == 0:
entry_price = prices['close'].iloc[i] * (1 + slippage)
position = capital / entry_price
capital = 0
# Выход из позиции
elif exit_signal.iloc[i] and position > 0:
exit_price = prices['close'].iloc[i] * (1 - slippage)
# Расчет P&L с учетом комиссий
gross_pnl = position * (exit_price - entry_price)
transaction_costs = (position * entry_price * commission +
position * exit_price * commission)
net_pnl = gross_pnl - transaction_costs
capital = position * exit_price - transaction_costs
# Сохранение результата сделки
trades.append({
'entry_price': entry_price,
'exit_price': exit_price,
'pnl': net_pnl,
'return': net_pnl / (position * entry_price),
'gross_return': (exit_price - entry_price) / entry_price
})
position = 0
trades_df = pd.DataFrame(trades)
if len(trades_df) == 0:
return None
# Расчет метрик
expectancy = trades_df['return'].mean()
win_rate = (trades_df['return'] > 0).sum() / len(trades_df)
avg_win = trades_df[trades_df['return'] > 0]['return'].mean()
avg_loss = trades_df[trades_df['return'] < 0]['return'].mean() metrics = { 'total_trades': len(trades_df), 'expectancy': expectancy, 'win_rate': win_rate, 'avg_win': avg_win, 'avg_loss': avg_loss, 'rrr': avg_win / abs(avg_loss) if avg_loss != 0 else np.nan, 'total_return': (capital - initial_capital) / initial_capital, 'sharpe': expectancy / trades_df['return'].std() if trades_df['return'].std() > 0 else 0
}
return metrics, trades_df
# Пример генерации сигналов (простая стратегия на основе SMA)
# В реальности здесь будет логика вашей стратегии
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=500, freq='D')
prices = pd.DataFrame({
'open': 100 + np.random.randn(500).cumsum(),
'high': 101 + np.random.randn(500).cumsum(),
'low': 99 + np.random.randn(500).cumsum(),
'close': 100 + np.random.randn(500).cumsum()
}, index=dates)
# Простые сигналы для демонстрации
sma_short = prices['close'].rolling(20).mean()
sma_long = prices['close'].rolling(50).mean()
entry_signal = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
exit_signal = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
metrics, trades = backtest_strategy(prices, entry_signal, exit_signal)
if metrics:
print(f"Total Trades: {metrics['total_trades']}")
print(f"Expectancy: {metrics['expectancy']:.4%}")
print(f"Win Rate: {metrics['win_rate']:.2%}")
print(f"Risk/Reward: {metrics['rrr']:.2f}")
print(f"Sharpe Ratio: {metrics['sharpe']:.2f}")
Total Trades: 3
Expectancy: 3.7057%
Win Rate: 33.33%
Risk/Reward: 5.13
Sharpe Ratio: 0.29
Функция моделирует исполнение сделок с учетом реалистичных условий: проскальзывание при входе и выходе, комиссии на обе транзакции. Результат включает детализацию каждой сделки и агрегированные метрики стратегии.
Рассмотрим полученные показатели:
- Всего совершено 3 сделки;
- Матожидание (expectancy) составляет 3,71%, что означает, что в среднем каждая сделка приносит небольшую положительную доходность;
- Win Rate равен 33,33%, то есть одна из 3-х сделок оказалась прибыльной, а две — убыточными;
- Несмотря на низкий процент выигрышей, отношение риска к прибыли (RRR = 5,13) показывает, что прибыльная сделка в среднем значительно превышает по величине убытки от проигрышных сделок;
- Sharpe Ratio равен 0,29, что указывает на умеренную доходность стратегии относительно ее волатильности.
В совокупности эти показатели говорят о том, что стратегия имеет положительное матожидание и потенциально прибыльна, но ее эффективность сильно зависит от размера выигрышных сделок и может требовать увеличения выборки для более стабильной оценки. Другими словами, низкий процент выигрышей компенсируется высоким профитом на успешных сделках, что важно учитывать при управлении капиталом.
Оценка статистической значимости
Даже если стратегия показывает положительное математическое ожидание на исторических данных, это не гарантирует прибыль в будущем. Чтобы понять, насколько надежен такой результат и не является ли он случайным, применяются статистические методы оценки значимости.
Один из таких методов — t-тест для одной выборки. Он позволяет проверить нулевую гипотезу о том, что средняя доходность сделок равна нулю, то есть стратегия не имеет реального преимущества. Если результат теста показывает низкое значение p-value, это говорит о том, что положительное математическое ожидание статистически значимо и с высокой вероятностью отражает реальную способность стратегии приносить прибыль, а не случайное совпадение.
При этом важно помнить, что результаты t-теста зависят от распределения доходностей и объема выборки. Малое количество сделок или сильные отклонения от нормального распределения могут снизить надежность выводов. Поэтому при оценке стратегии рекомендуется использовать не только p-value, но и доверительные интервалы для средних доходностей, а также визуальный анализ распределения прибыли и убытков.
from scipy import stats
def test_expectancy_significance(trades_returns, alpha=0.05):
"""
Проверка статистической значимости положительного матожидания.
Parameters:
- trades_returns: Series с доходностями сделок
- alpha: уровень значимости (обычно 0.05)
"""
# T-test против нулевой гипотезы (матожидание = 0)
t_stat, p_value = stats.ttest_1samp(trades_returns, 0)
# Доверительный интервал
conf_interval = stats.t.interval(
1 - alpha,
len(trades_returns) - 1,
loc=trades_returns.mean(),
scale=stats.sem(trades_returns)
)
# Проверка нормальности распределения
_, normality_p = stats.shapiro(trades_returns)
result = {
'mean_return': trades_returns.mean(),
't_statistic': t_stat,
'p_value': p_value,
'is_significant': p_value < alpha, 'conf_interval': conf_interval, 'normality_p': normality_p, 'is_normal': normality_p > 0.05,
'n_trades': len(trades_returns)
}
return result
# Тестирование на данных из предыдущего примера
if trades is not None and len(trades) > 0:
significance = test_expectancy_significance(trades['return'])
print(f"\nStatistical Significance Test:")
print(f"Mean Return: {significance['mean_return']:.4%}")
print(f"T-statistic: {significance['t_statistic']:.2f}")
print(f"P-value: {significance['p_value']:.4f}")
print(f"Significant at 5%: {significance['is_significant']}")
print(f"95% CI: [{significance['conf_interval'][0]:.4%}, {significance['conf_interval'][1]:.4%}]")
print(f"Normal distribution: {significance['is_normal']}")
Statistical Significance Test:
Mean Return: 3.7057%
T-statistic: 0.51
P-value: 0.6605
Significant at 5%: False
95% CI: [-27.5318%, 34.9433%]
Normal distribution: True
Код оценивает вероятность того, что наблюдаемое матожидание получено случайно. P-value < 0.05 указывает на статистически значимый результат при уровне доверия 95%. Тест нормальности проверяет применимость t-test — при сильных отклонениях от нормального распределения потребуются непараметрические методы.
Интерпретация:
- Cредняя доходность сделок составляет 3,71%, что хорошо;
- Однако значение t-статистики равно 0,51, а p-value — 0,6605, что значительно выше стандартного порога 0,05. Это означает, что положительное математическое ожидание на исторических данных не является статистически значимым, и с высокой вероятностью его можно объяснить случайными колебаниями;
- Доверительный интервал [-27,53%, 34,94%] дополнительно подчеркивает, что средний результат сделки может быть как отрицательным, так и положительным.
При этом тест Шапиро показывает, что распределение доходностей не сильно отклоняется от нормального, что позволяет корректно применять t-тест. Тем не менее, низкая статистическая значимость указывает на необходимость увеличения выборки или дополнительного анализа стратегии перед практическим использованием, так как текущее количество сделок недостаточно для уверенной оценки ее эффективности.
Bootstrap-анализ устойчивости
Bootstrap-анализ позволяет оценить устойчивость ключевых показателей стратегии, таких как математическое ожидание, коэффициент выигрышей или соотношение риска к прибыли. Метод заключается в многократной случайной пересборке исходной выборки с возвращением (sampling with replacement), что создает множество новых «псевдовыборок». Для каждой из них рассчитываются метрики стратегии, после чего строится распределение этих показателей.
Главное преимущество Bootstrap заключается в том, что он не требует предположений о конкретном распределении доходностей сделок, в отличие, например, от t-теста. Это особенно важно для финансовых данных, которые часто имеют несимметричное распределение, «тяжелые хвосты» и выбросы.
def bootstrap_expectancy(trades_returns, n_bootstrap=10000, confidence=0.95):
"""
Bootstrap-анализ устойчивости матожидания.
Parameters:
- trades_returns: Series с доходностями сделок
- n_bootstrap: количество bootstrap-итераций
- confidence: уровень доверия для интервала
"""
bootstrap_means = []
bootstrap_sharpe = []
n_trades = len(trades_returns)
for _ in range(n_bootstrap):
# Случайная выборка с возвратом
sample = np.random.choice(trades_returns, size=n_trades, replace=True)
bootstrap_means.append(sample.mean())
if sample.std() > 0:
bootstrap_sharpe.append(sample.mean() / sample.std())
# Расчет доверительных интервалов
lower_percentile = (1 - confidence) / 2
upper_percentile = 1 - lower_percentile
mean_ci = np.percentile(bootstrap_means, [lower_percentile * 100, upper_percentile * 100])
sharpe_ci = np.percentile(bootstrap_sharpe, [lower_percentile * 100, upper_percentile * 100])
result = {
'original_mean': trades_returns.mean(),
'bootstrap_mean': np.mean(bootstrap_means),
'mean_ci': mean_ci,
'mean_std': np.std(bootstrap_means),
'original_sharpe': trades_returns.mean() / trades_returns.std(),
'bootstrap_sharpe': np.mean(bootstrap_sharpe),
'sharpe_ci': sharpe_ci,
'probability_positive': np.sum(np.array(bootstrap_means) > 0) / n_bootstrap
}
return result
# Bootstrap-анализ
if trades is not None and len(trades) > 0:
bootstrap_results = bootstrap_expectancy(trades['return'])
print(f"\nBootstrap Analysis ({len(trades)} trades):")
print(f"Original Mean: {bootstrap_results['original_mean']:.4%}")
print(f"Bootstrap Mean: {bootstrap_results['bootstrap_mean']:.4%}")
print(f"95% CI for Mean: [{bootstrap_results['mean_ci'][0]:.4%}, {bootstrap_results['mean_ci'][1]:.4%}]")
print(f"Probability of Positive Expectancy: {bootstrap_results['probability_positive']:.2%}")
print(f"Bootstrap Sharpe: {bootstrap_results['bootstrap_sharpe']:.2f}")
print(f"95% CI for Sharpe: [{bootstrap_results['sharpe_ci'][0]:.2f}, {bootstrap_results['sharpe_ci'][1]:.2f}]")
Bootstrap Analysis (3 trades):
Original Mean: 3.7057%
Bootstrap Mean: 3.7528%
95% CI for Mean: [-4.0642%, 18.2137%]
Probability of Positive Expectancy: 70.32%
Bootstrap Sharpe: -1.39
95% CI for Sharpe: [-7.65, 1.11]
Интерпретация результатов:
- Средняя доходность стратегии составляет 3,71%, среднее по Bootstrap-псевдовыборкам — 3,75%, что подтверждает положительное матожидание;
- Доверительный интервал [-4,06%, 18,21%] широкий, включающий отрицательные значения, что указывает на высокую вариативность результатов при малом числе сделок;
- Вероятность положительного матожидания в случайной выборке — 70,3%, то есть положительная доходность более вероятна, но не гарантирована;
- Bootstrap-Sharpe равен -1,39 с доверительным интервалом [-7,65, 1,11], что отражает сильные колебания доходности относительно риска.
Вывод: стратегия имеет положительное математическое ожидание, но при небольшой выборке ее надежность ограничена; для стабильной оценки необходима большая история сделок.
Бутстрап создает тысячи альтернативных версий истории торговли путем случайного отбора сделок с повторениями. Результаты такого анализа позволяют оценить доверительные интервалы для показателей стратегии, понять их разброс и чувствительность к отдельным сделкам:
- Если интервалы узкие и положительные, можно с большей уверенностью говорить о стабильной положительной доходности;
- Если же распределение широкое или включает отрицательные значения, это сигнализирует о высокой вариативности стратегии и потенциальных рисках для капитала.
Ограничения и распространенные ошибки
Смещение выборки (Sample bias) и Переобучение (Overfitting)
Малое количество сделок приводит к нестабильной оценке матожидания. Например, при 20 сделках стандартная ошибка среднего составляет σ/√20 ≈ 0,22σ. Для σ = 2% это дает погрешность ±0,44%. В результате доверительный интервал для истинного матожидания может включать ноль, даже если наблюдаемое значение положительное.
Минимальное количество сделок для надежной оценки зависит от желаемой точности. Для погрешности ±0,2% при σ = 2% требуется n = (2% / 0,2%)² = 100 сделок. Стратегии с редкими сигналами могут накапливать достаточную статистику только годами.
Переобучение возникает при чрезмерной оптимизации параметров на исторических данных. Например, стратегия с 15 настраиваемыми параметрами и тестированием на 500 барах имеет высокий риск подгонки под шум. В этом случае матожидание на обучающих данных завышается, а на тестовых данных — резко снижается.
Анализ скользящего тестирования (walk-forward) помогает снизить риск переобучения через последовательное тестирование на непересекающихся периодах. Оптимизация параметров проводится на первых 60% данных, валидация — на следующих 20%, а финальный тест — на последних 20%. Деградация метрик между периодами служит индикатором переобучения.
Нестационарность рынков
Финансовые рынки изменяются со временем. Матожидание стратегии, которая работала пять лет назад, может не соответствовать текущей ситуации. Изменения ликвидности, волатильности и корреляций между активами могут нарушать статистические свойства, на которых была построена стратегия.
Выявление рыночных режимов (regime detection) позволяет определять структурные сдвиги в поведении рынка. С помощью скрытых моделей Маркова (Hidden Markov Models) и других методов периоды классифицируются по уровню волатильности, направлению тренда и корреляциям между активами. Стратегия может либо адаптировать свои параметры, либо временно отключаться в неблагоприятных рыночных режимах.
Анализ скользящего окна (rolling window analysis) отслеживает динамику ключевых метрик во времени. Расчет математического ожидания, коэффициента выигрышей или соотношения риска к прибыли на скользящих окнах по 100–200 сделок позволяет выявлять тренды в эффективности стратегии. Устойчивое снижение коэффициента выигрышей (Win Rate) или соотношения прибыли к риску (RRR) сигнализирует о деградации торгового преимущества и служит сигналом для пересмотра стратегии.
Ниже пример кода Python для расчета матожидания на скользящем окне:
def rolling_expectancy(trades_df, window=100):
"""Расчет матожидания на скользящем окне."""
rolling_mean = trades_df['return'].rolling(window=window).mean()
rolling_std = trades_df['return'].rolling(window=window).std()
rolling_sharpe = rolling_mean / rolling_std
rolling_winrate = trades_df['return'].rolling(window=window).apply(
lambda x: (x > 0).sum() / len(x)
)
results = pd.DataFrame({
'expectancy': rolling_mean,
'volatility': rolling_std,
'sharpe': rolling_sharpe,
'winrate': rolling_winrate
})
return results
# Анализ деградации edge
if trades is not None and len(trades) >= 100:
rolling_metrics = rolling_expectancy(trades, window=50)
print("\nRolling Metrics (last 5 windows):")
print(rolling_metrics.tail())
# Проверка тренда в матожидании
recent_expectancy = rolling_metrics['expectancy'].tail(10).mean()
early_expectancy = rolling_metrics['expectancy'].head(10).mean()
print(f"\nEarly Expectancy: {early_expectancy:.4%}")
print(f"Recent Expectancy: {recent_expectancy:.4%}")
print(f"Change: {(recent_expectancy - early_expectancy):.4%}")
Мониторинг скользящих метрик выявляет деградацию стратегии до появления критических убытков. Снижение матожидания на 50% от исторического уровня — сигнал для остановки торговли и пересмотра подхода.
Зависимость результатов от периода тестирования
Выбор периода бэктестов существенно влияет на оценку матожидания стратегии. Например, тестирование торговли Bitcoin только в периоды роста (бычий рынок) завышает показатели прибыльности long-only стратегий. Включение кризисных периодов или резких спадов, наоборот, может значительно ухудшить метрики и показать реальные риски стратегии.
Репрезентативная выборка должна включать разные рыночные условия: тренды, флэт, высокую и низкую волатильность, кризисы. Минимальный период тестирования — 3-5 лет для дневных стратегий, охватывающий хотя бы один полный рыночный цикл.
Тестирование на отложенных данных (out-of-sample) крайне важно для проверки надежности стратегии. Оно заключается в проверке работы стратегии на данных, которые не использовались при ее оптимизации. Например, стратегия, оптимизированная на период 2019–2022 гг, должна подтвердить свои результаты на данных 2022–2025 годов. Существенное расхождение ключевых метрик, таких как математическое ожидание, коэффициент выигрышей или соотношение прибыли к риску, сигнализирует либо о переобучении, либо о смене рыночного режима.
Примеры из практики:
- Стратегии на малой ликвидности альткоинов 2019–2020 показывали хорошие результаты на исторических данных, но при росте ликвидности и появлении крупных маркет-мейкеров эффективность резко снижалась;
- Алгоритмические стратегии на ETF с низкой волатильностью в 2017–2018 годах теряли прибыль при увеличении волатильности и сезонных корреляциях в 2019–2020 годах, несмотря на стабильные метрики на обучающей выборке.
Метод Монте-Карло позволяет оценить устойчивость стратегии к случайным вариациям порядка сделок. Суть метода — многократная случайная перестановка сделок или их доходностей и расчет ключевых метрик на каждой новой выборке. Это дает распределение возможных исходов и показывает, как может меняться кривая капитала при том же наборе сделок, но другом порядке их исполнения. Такой анализ помогает оценить риск просадок и вероятность сохранения положительного математического ожидания.
import numpy as np
def monte_carlo_simulation(trades_returns, n_simulations=1000):
"""Monte Carlo симуляция альтернативных equity curves с расчетом доходности и просадок."""
trades_returns = np.array(trades_returns)
n_trades = len(trades_returns)
final_returns = np.zeros(n_simulations)
max_drawdowns = np.zeros(n_simulations)
for i in range(n_simulations):
# Случайная перестановка сделок
shuffled_trades = np.random.choice(trades_returns, size=n_trades, replace=False)
# Расчет equity curve
equity = np.cumprod(1 + shuffled_trades)
final_returns[i] = equity[-1] - 1
# Расчет максимальной просадки
running_max = np.maximum.accumulate(equity)
drawdown = (equity - running_max) / running_max
max_drawdowns[i] = drawdown.min()
results = {
'mean_return': final_returns.mean(),
'return_std': final_returns.std(),
'return_percentiles': np.percentile(final_returns, [5, 25, 50, 75, 95]),
'mean_max_dd': max_drawdowns.mean(),
'worst_dd_percentile': np.percentile(max_drawdowns, 5)
}
return results, final_returns, max_drawdowns
# Пример вызова
if trades is not None and len(trades) > 0:
mc_results, returns_dist, dd_dist = monte_carlo_simulation(trades['return'])
print(f"\nMonte Carlo Simulation (1000 runs):")
print(f"Mean Return: {mc_results['mean_return']:.2%}")
print(f"Return Std: {mc_results['return_std']:.2%}")
print(f"Return Percentiles [5%, 25%, 50%, 75%, 95%]:")
print(f" {[f'{p:.2%}' for p in mc_results['return_percentiles']]}")
print(f"Mean Max Drawdown: {mc_results['mean_max_dd']:.2%}")
print(f"5th Percentile Max DD: {mc_results['worst_dd_percentile']:.2%}")
Monte Carlo Simulation (1000 runs):
Mean Return: 9.97%
Return Std: 0.00%
Return Percentiles [5%, 25%, 50%, 75%, 95%]:
['9.97%', '9.97%', '9.97%', '9.97%', '9.97%']
Mean Max Drawdown: -4.60%
5th Percentile Max DD: -6.97%
Симуляция показывает вероятностное распределение возможных результатов. 5-й перцентиль доходности указывает на худший ожидаемый сценарий с вероятностью 95%. Если этот сценарий неприемлем, стратегия требует корректировки размера позиций или дополнительной диверсификации.
Использование комбинации тестирования на отложенных данных и симуляций Монте-Карло позволяет понять, насколько стратегия устойчива, выявить риск переобучения и подготовиться к различным рыночным сценариям.
Заключение
Математическое ожидание отражает долгосрочную прибыльность торговой стратегии. Положительное матожидание — необходимое, но недостаточное условие успеха. Его практическая реализация зависит от дисперсии доходности, числа сделок и устойчивости торговой стратегии во времени. Например, стратегия с E[R] = 0,3% и низкой волатильностью может быть предпочтительнее стратегии с E[R] = 0,5% и высокими просадками, если оценивать доходность с учетом риска (risk-adjusted returns).
Для точной оценки матожидания требуется достаточная статистика, учет транзакционных издержек и проверка на нескольких периодах. Методы Bootstrap и Monte Carlo дополняют классические статистические тесты, показывая, насколько устойчивы ключевые метрики к вариациям в порядке сделок.