Метод Монте-Карло — это мощный инструмент стохастического моделирования, который используется в самых разнообразных областях науки и инженерии. В финансах, этот метод часто применяется для анализа и прогнозирования временных рядов, таких как курс валют или акций. Использование Монте-Карло позволяет оценить не только ожидаемые значения, но и распределение возможных исходов, что крайне важно для управления рисками и принятия обоснованных инвестиционных решений.
Принцип метода заключается в выполнении большого количества стохастических экспериментов (симуляций), основанных на случайных выборках из вероятностных распределений входных параметров. В контексте прогнозирования курса валют, это позволяет моделировать различные экономические сценарии и оценивать потенциальные колебания валютных пар, используя исторические данные.
Ключевой аспект использования Монте-Карло в финансах — это его способность учитывать и анализировать волатильность и дрейф курсов валют. Для повышения точности моделирования и реалистичности получаемых данных часто применяется ГАРЧ модель (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 тестов, но всё же для понимания принципов вышло очень хорошо.
Это лишь пример "на пальцах" с демонстрацией кода, чтобы зажечь затейливые умы к совершенствованию уже существующих методов, ну и похардкодить тоже было приятно.
Комментарии (3)
S_gray
24.04.2024 13:31Один мой старший знакомый в 90-е годы в попытке "коммерциализации" своей научной работы написал программу СПАД ("статистических процессов анализатор дискретный(?)" - не уверен в последнем слове) и, в частности, попробовал с её помощью прогнозировать колебания курсов валют. В то время это было очень популярной темой и его даже с этим СПАДом взяли на работу в серьезную фирму на приличную (по тем временам, для наёмного работника) зарплату. В общем, он пришёл к выводу, что курсы валют не являются случайными величинами, а посему статистический инструментарий в части прогнозирования неэффективен...
Vilos
Бакс нужно закупать?
Travisw
судя по графику надо купить в мае, а продать в декабре