Исходный код
Как Senior Quant Developer, я часто сталкиваюсь с необходимостью глубокого анализа эффективности торговых стратегий. Одним из ключевых аспектов такого анализа является понимание того, как цена движется относительно нашей позиции после входа в сделку. Здесь на помощь приходят метрики Maximum Favorable Excursion (MFE) и Maximum Adverse Excursion (MAE).
MFE (Maximum Favorable Excursion) показывает максимальную прибыль, которую можно было бы получить от сделки, если бы она была закрыта в самый благоприятный момент до фактического выхода. MAE (Maximum Adverse Excursion), напротив, измеряет максимальный убыток, который сделка испытала от точки входа до фактического выхода или до текущего момента, прежде чем цена, возможно, развернулась в нашу сторону. Эти метрики критически важны для:
- Оценки качества точек входа: Высокий MAE может указывать на плохие точки входа.
- Оптимизации стоп-лоссов: MAE помогает определить адекватные уровни стоп-лосса.
- Оптимизации тейк-профитов: MFE помогает понять потенциал прибыли и установить реалистичные цели.
- Понимания психологии трейдинга: Сравнение MFE с фактической прибылью/убытком показывает, насколько эффективно трейдер управляет сделками.
В этой статье мы рассмотрим, как эффективно рассчитать MFE и MAE для вашей торговой системы с использованием Python и библиотеки pandas. Для более сложных сценариев, где требуется ускорение вычислений для больших объемов данных, можно рассмотреть подходы, описанные в статье Ускорение финансовых индикаторов на Python с помощью Numba JIT.
import pandas as pd
import numpy as np
def calculate_mfe_mae(trades_df: pd.DataFrame, price_series_df: pd.DataFrame, price_col: str = 'Close') -> pd.DataFrame:
"""
Рассчитывает Maximum Favorable Excursion (MFE) и Maximum Adverse Excursion (MAE)
для каждой сделки в торговой системе.
Параметры:
----------
trades_df : pd.DataFrame
DataFrame, содержащий информацию о сделках. Должен включать столбцы:
'entry_time' (datetime), 'exit_time' (datetime), 'entry_price' (float),
'trade_type' (str, 'long' или 'short').
price_series_df : pd.DataFrame
DataFrame с ценовыми данными. Должен иметь datetime индекс и столбец с ценой.
price_col : str, optional
Название столбца с ценой в price_series_df. По умолчанию 'Close'.
Возвращает:
----------
pd.DataFrame
Исходный DataFrame trades_df с добавленными столбцами 'mfe' и 'mae'.
"""
# Убедимся, что столбцы времени в trades_df имеют тип datetime
trades_df_copy = trades_df.copy() # Работаем с копией, чтобы не изменять оригинал
trades_df_copy['entry_time'] = pd.to_datetime(trades_df_copy['entry_time'])
trades_df_copy['exit_time'] = pd.to_datetime(trades_df_copy['exit_time'])
# Убедимся, что индекс price_series_df имеет тип datetime
if not isinstance(price_series_df.index, pd.DatetimeIndex):
raise ValueError("Индекс price_series_df должен быть DatetimeIndex.")
mfe_values = []
mae_values = []
for idx, trade in trades_df_copy.iterrows():
entry_time = trade['entry_time']
exit_time = trade['exit_time']
entry_price = trade['entry_price']
trade_type = trade['trade_type']
# Извлекаем ценовой ряд для текущей сделки
# Используем .loc для точного среза по времени, включая entry_time и exit_time
trade_prices = price_series_df.loc[entry_time:exit_time, price_col]
if trade_prices.empty:
mfe_values.append(np.nan)
mae_values.append(np.nan)
continue
if trade_type == 'long':
# Для длинной позиции:
# MFE = max(цена_в_сделке) - цена_входа
# MAE = цена_входа - min(цена_в_сделке)
mfe = trade_prices.max() - entry_price
mae = entry_price - trade_prices.min()
elif trade_type == 'short':
# Для короткой позиции:
# MFE = цена_входа - min(цена_в_сделке)
# MAE = max(цена_в_сделке) - цена_входа
mfe = entry_price - trade_prices.min()
mae = trade_prices.max() - entry_price
else:
mfe = np.nan
mae = np.nan
print(f"Предупреждение: Неизвестный тип сделки '{trade_type}' для сделки с ID {idx}. MFE/MAE будут NaN.")
mfe_values.append(mfe)
mae_values.append(mae)
trades_df_copy['mfe'] = mfe_values
trades_df_copy['mae'] = mae_values
return trades_df_copy
Разбор параметров
trades_df:DataFrame, содержащий информацию о сделках. Ожидаются столбцы:
'entry_time'(время входа,datetime),'exit_time'(время выхода,datetime),'entry_price'(цена входа,float) и'trade_type'(тип сделки,str, значения'long'или'short').price_series_df:DataFrame с ценовыми данными. Должен иметь индекс типа
pd.DatetimeIndexи содержать столбец с ценой, по которой будут рассчитываться MFE/MAE.price_col:Строка, указывающая название столбца с ценой в
price_series_df. По умолчанию'Close'.
Как запустить
Для запуска функции calculate_mfe_mae вам потребуется два основных DataFrame: один для ваших торговых сделок и один для исторических ценовых данных. Давайте создадим примеры таких данных и продемонстрируем использование.
Понимание MFE и MAE является частью более широкого спектра инструментов для оценки рисков. Например, для оценки общего рыночного риска портфеля часто используются метрики вроде Value at Risk, о расчете которых можно узнать в статье Расчет Value at Risk (VaR) через Монте-Карло и GARCH на Python.
Пример данных:
# Создаем пример ценового ряда
dates = pd.date_range(start='2023-01-01', periods=100, freq='H')
prices = np.random.rand(100) * 100 + 100 # Базовая цена 100, колебания до 200
price_series_df = pd.DataFrame({'Close': prices}, index=dates)
# Создаем пример DataFrame с торговыми сделками
trades_data = [
{'entry_time': '2023-01-01 05:00:00', 'exit_time': '2023-01-01 10:00:00', 'entry_price': 105.0, 'trade_type': 'long'},
{'entry_time': '2023-01-02 12:00:00', 'exit_time': '2023-01-02 18:00:00', 'entry_price': 150.0, 'trade_type': 'short'},
{'entry_time': '2023-01-03 08:00:00', 'exit_time': '2023-01-03 09:00:00', 'entry_price': 120.0, 'trade_type': 'long'},
{'entry_time': '2023-01-04 01:00:00', 'exit_time': '2023-01-04 01:00:00', 'entry_price': 130.0, 'trade_type': 'long'}, # Сделка без длительности
{'entry_time': '2023-01-05 00:00:00', 'exit_time': '2023-01-05 02:00:00', 'entry_price': 110.0, 'trade_type': 'invalid_type'}, # Некорректный тип
]
trades_df = pd.DataFrame(trades_data)
# Выполняем расчет MFE и MAE
trades_with_mfe_mae = calculate_mfe_mae(trades_df, price_series_df)
print(trades_with_mfe_mae)
После выполнения этого кода вы получите DataFrame trades_with_mfe_mae, который будет содержать исходные данные о сделках, а также два новых столбца: 'mfe' и 'mae', отражающие рассчитанные значения Maximum Favorable Excursion и Maximum Adverse Excursion для каждой сделки.
Эти метрики, наряду с другими инструментами анализа временных рядов, такими как Фильтр Калмана на Python для сглаживания ценовых рядов, позволяют получить всестороннее представление о поведении цен и эффективности вашей стратегии.




