Метод Монте-Карло — это мощный инструмент стохастического моделирования, который используется в самых разнообразных областях науки и инженерии. В финансах, этот метод часто применяется для анализа и прогнозирования временных рядов, таких как курс валют или акций. Использование Монте-Карло позволяет оценить не только ожидаемые значения, но и распределение возможных исходов, что крайне важно для управления рисками и принятия обоснованных инвестиционных решений.
Принцип метода заключается в выполнении большого количества стохастических экспериментов (симуляций), осно��анных на случайных выборках из вероятностных распределений входных параметров. В контексте прогнозирования курса валют, это позволяет моделировать различные экономические сценарии и оценивать потенциальные колебания валютных пар, используя исторические данные.
Ключевой аспект использования Монте-Карло в финансах — это его способность учитывать и анализировать волатильность и дрейф курсов валют. Для повышения точности моделирования и реалистичности получаемых данных часто применяется ГАРЧ модель (Generalized Autoregressive Conditional Heteroskedasticity). ГАРЧ помогает адекватно оценить и моделировать изменчивость волатильности, что является критичным при анализе финансовых временных рядов.
Идейно код выполнялся без готовых реализованных методов из различных либ, такое решение было принято для ознакомления с данным математическим аппаратом.
Проект использует следующие библиотеки и инструменты:
yfinanceдля загрузки финансовых данных.numpyиpandasдля обработки данных.matplotlibдля визуализации данных.scipyиarchдля статистического анализа и моделирования.
Импорт и подготовка данных
Для анализа и симуляции курса валют мы используем исторические данные по валютным парам USD/RUB и EUR/RUB, загруженные через библиотеку yfinance. Эти данные обеспечивают основу для нашего стохастического моделирования, начиная с 2013 года и заканчивая апрелем 2023 года. Сей временной промежуток был выбран, для того чтобы при сравнении сгенерированных значений с реальным учитывалось меньше дискретных значений из "линейного" поведения курса и моделировалось наиболее волатильное поведение курса.
Я брал отрезки [2004;2023], [2008;2020], [2010;2023]и т.д, и каждый раз у меня получался схожий временной ряд с временным рядом "спокойного" поведения доллара. Надеюсь, объяснил. Продолжим!
Анализируемые данные включают закрытие дня для каждой валюты, что позволяет вычислить дневные возвраты и волатильность.
Коротко о данных
# Загрузка исторических данных о валютных курсах data = yf.download("USDRUB=X EURRUB=X", start="2013-01-01", end="2023-04-13")

Из этих данных мы выбираем курс на закрытии
usd_rates = data['Close']['USDRUB=X'].dropna() eur_rates = data['Close']['EURRUB=X'].dropna()
Описание функций для симуляции
1. Функция simulate_exchange_rates
Эта функция предназначена для симуляции будущих курсов валют на основе заданных параметров дрейфа и волатильности для каждой валюты. Она использует модель геометрического броуновского движения (GBM), которая часто применяется в финансовом моделировании для описания временных рядов цен или индексов.
Математическое описание GBM:
где:
( St ) — цена актива в момент времени ( t ),
( S0 ) — начальная цена актива,
( μ ) — ожидаемый дрейф (среднее значение логарифмической доходности),
( σ ) — стандартное отклонение логарифмической доходности (волатильность),
( Wt ) — винеровский процесс (или стандартное броуновское движение).
В контексте симуляции валютных курсов:
Дневные возвраты рассчитываются как
где ( Z ) — значения из стандартного нормального распределения.
Эти возвраты затем умножаются на последнее известное значение курса валюты для получения симулированных курсов на каждый из дней.
Код
def simulate_exchange_rates(historical_rates, days, simulations, drifts, volatilities): dt = 1 / days simulated_rates = {} for currency, rates in historical_rates.items(): drift = drifts[currency] volatility = volatilities[currency] daily_returns = np.exp((drift - 0.5 * volatility**2) * dt + volatility * np.random.normal(0, np.sqrt(dt), (days, simulations))) simulation = rates.iloc[-1] * np.cumprod(daily_returns, axis=0) simulated_rates[currency] = simulation return simulated_rates
2. Функция portfolio_value
Функция portfolio_value рассчитывает стоимость портфеля в рублях, учитывая симулированные курсы валют и количество каждой валюты в портфеле. Стоимость портфеля определяется как:
где:
( xi ) — количество валюты ( i ) в портфеле,
( Si(T) ) — симулированный курс валюты ( i ) на конец периода ( T ),
( n ) — количество различных валют в портфеле.
Код
def portfolio_value(simulated_rates, portfolio): simulations = next(iter(simulated_rates.values())).shape[1] values = np.full(simulations, portfolio.get('RUB', 0), dtype=float) for currency, amount in portfolio.items(): if currency != 'RUB': values += amount * simulated_rates[currency][-1, :] return values
3. Функции calculate_var и calculate_cvar
Функция
calculate_varоценивает Value at Risk (VaR) портфеля, который представляет собой потенциальную максимальную потерю в стоимости портфеля на заданном уровне доверия (например, 95%). VaR рассчитывается как:
где V (α) — квантиль распред��ления стоимости портфеля на уровне α.
Код
def calculate_var(portfolio_values, confidence_level=0.95): sorted_values = np.sort(portfolio_values) index = int((1 - confidence_level) * len(sorted_values)) return sorted_values[index]
Функция
calculate_cvarрассчитывает Conditional Value at Risk (CVaR), который представляет среднюю потерю, превышающую VaR:
где ( Vu) — значение портфеля в точке квантиля (u).
Код
def calculate_cvar(portfolio_values, confidence_level=0.95): var = calculate_var(portfolio_values, confidence_level) cvar = portfolio_values[portfolio_values <= var].mean() return cvar
4. Функция calculate_confidence_interval
Функция calculate_confidence_interval предназначена для расчета доверительного интервала значений на основе симулированных данных. Доверительный интервал дает представление о том, в каком диапазоне могут колебаться значения с заданной вероятностью.
Математическое описание:
Для расчета доверительного интервала используется метод перцентилей. Он основан на следующих шагах:
Сначала симулированные данные сортируются по возрастанию.
Затем выбираются значения, соответствующие перцентилям, рассчитанным на основе заданного уровня доверия ( α ).
Этот метод позволяет оценить, например, верхнюю и нижнюю границу ожидаемых значений курса валюты с вероятностью ( α ).
Код
def calculate_confidence_interval(simulated_data, confidence_level=0.95): lower_percentile = (1 - confidence_level) / 2 * 100 upper_percentile = (1 - (1 - confidence_level) / 2) * 100 lower_bound = np.percentile(simulated_data, lower_percentile) upper_bound = np.percentile(simulated_data, upper_percentile) return lower_bound, upper_bound
5. Функция calculate_drift_volatility
Функция calculate_drift_volatility предназначена для расчета дрейфа и волатильности на основе исторических данных о курсах валют. Эти параметры критично важны для моделирования будущих цен по модели геометрического броуновского движения.
Процесс расчета:
Из исторических данных сначала рассчитываются логарифмические доходности:
Дрейф ( μ ) определяется как среднее значение этих доходностей.
Волатильность (σ) — это стандартное отклонение доходностей.
Обычно для расчета этих параметров используется скользящее окно (например, 252 дня, что соответствует количеству торговых дней в году), что позволяет учитывать последние изменения на рынке и делает модель более адаптивной к текущим условиям.
Код
def calculate_drift_volatility(rates, lookback_period=252): log_returns = np.log(rates / rates.shift(1)).dropna() rolling_mean = log_returns.rolling(window=lookback_period).mean() rolling_std = log_returns.rolling(window=lookback_period).std() drift = rolling_mean.iloc[-1] volatility = rolling_std.iloc[-1] return drift, volatility
6. Функция fit_garch_model
Функция fit_garch_model применяется для моделирования и оценки волатильности с использованием ГАРЧ модели. ГАРЧ модель особенно полезна для финансовых временных рядов, где волатильность не является постоянной, а изменяется со временем.
Процесс подгонки модели:
Входные данные для модели — это логарифмические доходности.
Модель ГАРЧ параметризуется значениями ( p ) и ( q ), которые представляют собой порядки модели для условной дисперсии и скользящего среднего соответственно.
Результат подгонки модели включает оценки долгосрочной волатильности и условного математического ожидания, что критично для точного прогнозирования будущих колебаний курсов валют.
ГАРЧ модель позволяет более точно оценить риск и управлять им, поскольку принимает во внимание изменчивость волатильности, что является стандартным явлением для финансовых рынков.
Инициализация и выполнение модели Монте-Карло для валютных курсов
После подготовки всех необходимых функций и данных, мы переходим к основному этапу анализа — инициализации и выполнению модели Монте-Карло. Этот процесс включает несколько ключевых шагов:
Расчет дрейфа и волатильности
Извлечение логарифмических доходностей: Используя исторические данные курсов USD и EUR, рассчитываются ежедневные логарифмические доходности. Это первый шаг в процессе определения дрейфа и волатильности, которые понадобятся для симуляций.
Расчет скользящих средних и стандартных отклонений: На основе логарифмических доходностей с помощью скользящего окна в 252 дня (примерно количество торговых дней в году) рассчитываются скользящие средние и стандартные отклонения. Эти значения представляют собой дрейф и волатильность каждой валюты.
Секретик
Вообще, функция calculate_confidence_interval была написана мною до того, как я узнал про GARCH, так что я из досады решил оставить инициализацию дрейфа и волатильности двумя способами
Подгонка ГАРЧ модели
Подгонка ГАРЧ модели: Для каждой валюты подгоняется ГАРЧ модель к логарифмическим доходностям. Модель ГАРЧ помогает оценить долгосрочную волатильность и условное среднее, что важно для точности симуляций.
Симуляция валютных курсов
Инициализация симуляций: С использованием полученных параметров дрейфа и волатильности запускаются симуляции курсов валют для заданного количества дней (252 дня в примере) и количества симуляций (1,000,000 в примере). Каждая симуляция представляет возможное будущее состояние курса валюты.
Симуляции проводятся по модели геометрического броуновского движения, описанного выше.
Ввод данных пользователем
Получение данных по��тфеля от пользователя: Запрашивается у пользователя ввод данных о количестве рублей, долларов и евро в его портфеле. Эти данные необходимы для дальнейшего расчета стоимости портфеля.
Весь код
historical_rates = {'USD': usd_rates, 'EUR': eur_rates} days = 252 simulations = 1000000 # Расчет дрейфа и волатильности для Моделирования # через calculate_drift_volatility drifts = {} volatilities = {} for currency, rates in historical_rates.items(): drifts[currency], volatilities[currency] = calculate_drift_volatility(rates) # Расчет дрейфа и волатильности для Моделирования via garch usd_log_returns = np.log(usd_rates / usd_rates.shift(1)).dropna() eur_log_returns = np.log(eur_rates / eur_rates.shift(1)).dropna() usd_drift_garch, usd_volatility_garch = fit_garch_model(usd_log_returns) eur_drift_garch, eur_volatility_garch = fit_garch_model(eur_log_returns) # Обновление словарей дрейфов и волатильности для использования в симуляции Монте-Карло drifts['USD'] = usd_drift_garch drifts['EUR'] = eur_drift_garch volatilities['USD'] = usd_volatility_garch volatilities['EUR'] = eur_volatility_garch # Моделирование курсов валют simulated_rates = simulate_exchange_rates(historical_rates, days, simulations, drifts, volatilities) # Ввод данных пользователем rub_amount = float(input("Введите количество рублей: ")) usd_amount = float(input("Введите количество долларов: ")) eur_amount = float(input("Введите количество евро: ")) # Получение данных пользователя portfolio = {'RUB': rub_amount, 'USD': usd_amount, 'EUR': eur_amount} # Расчет стоимости портфеля portfolio_values = portfolio_value(simulated_rates, portfolio)
После этого через input запрашивается предполагаемый портфель в RUB, EUR, USD
портфель из 1 доллара выбрал для наглядности проверки var и cvar дальше
Расчет стоимости портфеля и рисков
Расчет стоимости портфеля и рисков: Используя симулированные курсы валют и данные о количестве валют в портфеле пользователя, рассчитывается стоимость портфеля и ключевые показатели риска (VaR и CVaR).
Код
cvar = calculate_cvar(portfolio_values) print(f"Максимальный риск портфеля (95% довер): {cvar}")
Ответ:
Анализ и визуализация результатов
Статистический анализ и визуализация: Проводится сравнение исторических и симулированных данных с помощью статистических тестов, таких как Колмогорова-Смирнова тест и t-тест для независимых выборок. Результаты визуализируются, чтобы наглядно показать реальные и симулированные данные по курсам валют.
Выводы и доверительные интервалы: Определяются доверительные интервалы для курсов валют на конец периода симуляций, что дает представление о возможных колебаниях курсов в будущем.
Код для тестов
Загрузка данных за последний год
data_recent = yf.download("USDRUB=X EURRUB=X", start="2023-04-12", end="2024-04-13")['Close'].dropna() data_recent_usd = data_recent['USDRUB=X'].dropna() data_recent_eur = data_recent['EURRUB=X'].dropna()
real_data_last_year = data_recent_usd[-days:] simulated_data_first_path = simulated_rates['USD'][:, 0] ks_stat, p_value_ks = ks_2samp(real_data_last_year, simulated_data_first_path) t_stat, p_value_t = ttest_ind(real_data_last_year, simulated_data_first_path, equal_var=False) print(f"KS Statistic: {ks_stat}, P-value (KS-test): {p_value_ks}") print(f"T-statistic: {t_stat}, P-value (T-test): {p_value_t}")
Ответ:
Оба теста вместе показывают, что существует статистически значимое расхождение между реальными и симулированными данными, НО...
Код для доверительных интервалов
usd_confidence_interval = calculate_confidence_interval(simulated_rates['USD'][-1, :]) eur_confidence_interval = calculate_confidence_interval(simulated_rates['EUR'][-1, :]) print(f"Доверительный интервал 95% для курса доллара: {usd_confidence_interval}") print(f"Доверительный интервал 95% для курса евро: {eur_confidence_interval}")
Ответ:
Визуализация
# Визуализация исторических и симулированных данных plt.figure(figsize=(14, 7)) plt.plot(usd_rates.index, usd_rates, label='Реальные данные(2013-01-01 - 2023-04-12)') plt.plot(data_recent_usd.index, data_recent_usd, label='Реальные данные (2023-04-13 - 2024-04-13)', color='green', alpha=0.7) plt.plot(pd.date_range(start=usd_rates.index[-1], periods=days, freq='B'), simulated_rates['USD'][:, 0], label='Смоделированные данные', color='orange', alpha=0.7) plt.legend() plt.title('Реальные vs Смоделированные USD/RUB курсы') plt.xlabel('Дата') plt.ylabel('Курс') plt.savefig('RUBUSD.png') plt.show()

# Визуализация исторических и симулированных данных plt.figure(figsize=(14, 7)) plt.plot(eur_rates.index, eur_rates, label='Реальные данные(2013-01-01 - 2023-04-12)') plt.plot(data_recent_eur.index, data_recent_eur, label='Реальные данные (2023-04-13 - 2023-04-13)', color='green', alpha=0.7) plt.plot(pd.date_range(start=eur_rates.index[-1], periods=days, freq='B'), simulated_rates['EUR'][:, 0], label='Смоделированные данные', color='orange', alpha=0.7) plt.legend() plt.title('Реальные vs Смоделированные EUR/RUB курсы') plt.xlabel('Дата') plt.ylabel('Курс') plt.savefig('RUBEUR.png') plt.show()

plt.figure(figsize=(10, 6)) for i in range(15): plt.plot(simulated_rates['USD'][:, i], linewidth=1, alpha=0.8) plt.title('Моделирование траекторий обменного курса доллара к рублю') plt.xlabel('Дни') plt.ylabel('Курс') plt.show()

plt.figure(figsize=(10, 6)) for i in range(15): plt.plot(simulated_rates['EUR'][:, i], linewidth=1, alpha=0.8) plt.title('Моделирование траекторий обменного курса евро к рублю') plt.xlabel('Дни') plt.ylabel('Курс') plt.show()

Бонус
Смоделируем поведение доллара на год вперед (04.2024 - 04.2025]
Прогноз на год вперед
historical_rates_pred = {'USD': data_recent_usd, 'EUR': data_recent_eur} days_pred= 252 simulations_pred = 1000000 # Расчет дрейфа и волатильности для Моделирования drifts_pred = {} volatilities_pred = {} for currency_pred, rates_pred in historical_rates_pred.items(): drifts_pred[currency_pred], volatilities_pred[currency_pred] = calculate_drift_volatility(rates_pred) usd_log_returns_pred = np.log(usd_rates / usd_rates.shift(1)).dropna() eur_log_returns_pred = np.log(eur_rates / eur_rates.shift(1)).dropna() usd_drift_garch_pred, usd_volatility_garch_pred = fit_garch_model(usd_log_returns_pred) eur_drift_garch_pred, eur_volatility_garch_pred = fit_garch_model(eur_log_returns_pred) # Обновление словарей дрейфов и волатильности для использования в симуляции Монте-Карло drifts_pred['USD'] = usd_drift_garch_pred drifts_pred['EUR'] = eur_drift_garch_pred volatilities_pred['USD'] = usd_volatility_garch_pred volatilities_pred['EUR'] = eur_volatility_garch_pred # Моделирование курсов валют simulated_rates_pred = simulate_exchange_rates(historical_rates_pred, days_pred, simulations_pred, drifts_pred, volatilities_pred) # Ввод данных пользователем rub_amount_pred = float(input("Введите количество рублей: ")) usd_amount_pred = float(input("Введите количество долларов: ")) eur_amount_pred = float(input("Введите количество евро: ")) # Получение данных пользователя portfolio_pred = {'RUB': rub_amount_pred, 'USD': usd_amount_pred, 'EUR': eur_amount_pred} # Расчет стоимости портфеля portfolio_values_pred = portfolio_value(simulated_rates_pred, portfolio_pred)
Тут я с��здал портфель с 1000 USD
cvar_pred = calculate_cvar(portfolio_values_pred) print(f"Максимальный риск портфеля (95% довер): {cvar_pred}")
Максимальный риск портфеля (95% довер): 71827.22023513853
usd_confidence_interval_pred = calculate_confidence_interval(simulated_rates_pred['USD'][-1, :]) eur_confidence_interval_pred = calculate_confidence_interval(simulated_rates_pred['EUR'][-1, :]) print(f"Доверительный интервал 95% для курса доллара: {usd_confidence_interval_pred }") print(f"Доверительный интервал 95% для курса евро: {eur_confidence_interval_pred }")
Доверительный интервал 95% для курса доллара: (72.63404780533205, 116.80234915827717)
Доверительный интервал 95% для курса евро: (94.25965585147713, 104.5288904591821)
plt.figure(figsize=(14, 7)) plt.plot(pd.date_range(start=data_recent_usd.index[-1], periods=days, freq='B'), simulated_rates_pred['USD'][:, 0], label='Смоделированные данные', color='blue', alpha=0.7) plt.legend() plt.title('Смоделированные USD/RUB курс на год') plt.xlabel('Дата') plt.ylabel('Курс') plt.grid() plt.savefig('RUBUSD_pred.png') plt.show()

Этот прогноз представляет собой обобщенный анализ на основе модели Монте-Карло и не учитывает все возможные будущие условия и изменения. Он демонстрирует потенциал метода для создания информированных и обоснованных прогнозов, подкрепленных статистическим анализом и симуляцией данных.
Заключение
В данной статье мы провели обширный анализ временных рядов курсов валют с использованием метода Монте-Карло, обогащенного моделями ГАРЧ для уточнения параметров дрейфа и волатильности. Доверять полученным значениям курсов не стоит - определенно, об этом говорит разность графиков, значения к-с и t тестов, но всё же для понимания принципов вышло очень хорошо.
Это лишь пример "на пальцах" с демонстрацией кода, чтобы зажечь затейливые умы к совершенствованию уже существующих методов, ну и похардкодить тоже было приятно.
