В финансовом и количественном анализе часто возникает необходимость работать не только с числовыми данными, но и с аналитическими выражениями. Символьные вычисления позволяют манипулировать математическими выражениями в их естественном, символьном виде, сохраняя точность и открывая возможности для аналитических решений там, где численные методы показывают свои ограничения.
В этой статье я поделюсь своим опытом использования символьных вычислений в Python, покажу практические примеры и расскажу о нюансах, которые часто упускают из виду.
Фундаментальные принципы и отличия символьных вычислений
Символьные вычисления работают с математическими выражениями как с объектами, а не с их численными значениями. Это принципиальное отличие открывает уникальные возможности для анализа. Когда я работаю с производными инструментами или сложными стохастическими процессами, символьные вычисления позволяют получить аналитические решения, которые затем можно оптимизировать для конкретных параметров.
Основное преимущество символьного подхода заключается в сохранении точности. Численные методы неизбежно накапливают ошибки округления, особенно при работе с многошаговыми алгоритмами. Символьные вычисления лишены этого недостатка, поскольку оперируют точными математическими выражениями. Это важно при разработке торговых алгоритмов, где даже небольшие ошибки могут привести к существенным потерям.
Современные системы символьных вычислений построены на принципах компьютерной алгебры. Каждое математическое выражение представляется в виде дерева, где узлы соответствуют операциям, а листья — переменным и константам. Такая структура позволяет эффективно применять алгебраические преобразования и упрощения.
В Python экосистема символьных вычислений развивается вокруг библиотеки SymPy, которая предоставляет полнофункциональную систему компьютерной алгебры. За время работы с SymPy я оценил ее гибкость и мощность, особенно в контексте финансового моделирования. Библиотека поддерживает символьную арифметику, дифференцирование, интегрирование, решение уравнений и многое другое.
Работа с SymPy: что важно знать
SymPy представляет собой полнофункциональную систему компьютерной алгебры, написанную на чистом Python. Каждое символьное выражение представляется как неизменяемый объект, что позволяет эффективно кэшировать результаты вычислений. Внутреннее представление выражений построено на древовидной структуре, где каждый узел соответствует математической операции или атомарному символу.
Центральным понятием в SymPy является концепция «предположений» (assumptions). Когда вы создаете символ, можно указать его математические свойства: является ли он положительным, действительным, целым, конечным и так далее. Эта информация важна для работы алгоритмов упрощения и решения уравнений.
Система предположений в SymPy работает через механизм трехзначной логики: True, False, или None (неопределено). Это позволяет библиотеке корректно обрабатывать случаи, когда определенные свойства символов неизвестны или не могут быть определены из контекста.
Основные категории предположений:
Категория | Примеры атрибутов | Влияние на вычисления |
Числовые | real, positive, negative, integer | Определяют допустимые значения переменных |
Алгебраические | commutative, finite, infinite | Влияют на порядок операций и упрощения |
Логические | nonzero, even, odd, prime | Управляют логическими выводами |
Топологические | bounded, unbounded | Важны для анализа пределов и интегралов |
Особое внимание следует уделить предположениям о комплексности. По умолчанию SymPy предполагает, что все символы могут быть комплексными числами. Это означает, что операции вроде sqrt(x^2) не упрощаются автоматически, поскольку результат зависит от того, является ли x действительным.
Алгоритмы упрощения и канонизации
SymPy использует сложную систему алгоритмов для упрощения выражений. Базовые упрощения выполняются автоматически при создании выражений, но для более сложных преобразований требуется явный вызов функций упрощения.
Иерархия функций упрощения:
- Автоматические — выполняются при создании выражения;
- Базовые — simplify(), expand(), factor();
- Специализированные — trigsimp(), radsimp(), ratsimp();
- Контекстные — collect(), cancel(), apart().
Сравнение методов упрощения:
Функция | Назначение | Когда использовать | Производительность |
simplify() | Универсальное упрощение | Когда неизвестен оптимальный метод | Медленная |
expand() | Раскрытие скобок | Для полиномиальных выражений | Быстрая |
factor() | Факторизация | Для поиска корней и упрощения дробей | Средняя |
collect() | Группировка по переменным | Для организации выражений | Быстрая |
cancel() | Сокращение дробей | Для рациональных функций | Средняя |
apart() | Разложение на простые дроби | Для интегрирования | Средняя |
Важно понимать, что «упрощение» в SymPy не всегда означает получение наиболее краткого выражения. Алгоритмы упрощения стремятся найти канонические формы выражений, которые облегчают дальнейшие символьные операции.
Производительность и оптимизация
Символьные вычисления по своей природе требуют значительных вычислительных ресурсов. SymPy реализует несколько стратегий оптимизации, но понимание их принципов важно для эффективной работы с большими выражениями.
Стратегии оптимизации производительности:
- Правильное определение символов — указание типов и ограничений;
- Использование специализированных функций вместо универсальных;
- Кэширование промежуточных результатов для повторных вычислений;
- Компиляция в численные функции через lambdify();
- Профилирование и мониторинг для выявления узких мест.
Механизмы кэширования:
Тип кэша | Область применения | Эффективность |
Expression cache | Результаты упрощений | Высокая |
Evaluation cache | Численные вычисления | Средняя |
Assumption cache | Логические выводы | Высокая |
Polynomial cache | Операции с полиномами | Средняя |
Для критически важных по производительности задач SymPy предоставляет механизм компиляции символьных выражений в численные функции через lambdify(). Эта функция генерирует оптимизированный код на NumPy, SciPy или других численных библиотеках, что может дать ускорение на несколько порядков.
Работа с уравнениями и системами
Модуль solve() представляет собой мощный инструмент для решения уравнений и систем уравнений. Он использует комбинацию алгебраических и численных методов для поиска решений.
Типы уравнений, поддерживаемых SymPy:
Тип уравнения | Примеры | Методы решения |
Полиномиальные | x^3 — 2x + 1 = 0 | Алгебраические формулы |
Рациональные | (x+1)/(x-1) = 2 | Приведение к полиномиальным |
Трансцендентные | exp(x) = x + 1 | Численные методы |
Тригонометрические | sin(x) = cos(x) | Тригонометрические тождества |
Логарифмические | log(x) = x — 1 | Специальные алгоритмы |
Функции для решения уравнений:
- solve() — основная функция для алгебраических уравнений;
- solveset() — современная альтернатива с лучшей обработкой множеств;
- dsolve() — для обыкновенных дифференциальных уравнений;
- pdsolve() — для дифференциальных уравнений в частных производных;
- nsolve() — численное решение уравнений.
Важные особенности работы с solve():
- Возвращает список решений и порядок может изменяться;
- Может не найти все решения, особенно для трансцендентных уравнений;
- Требует проверки решений подстановкой в исходное уравнение;
- Чувствителен к предположениям о типах переменных.
Символьные матрицы и линейная алгебра
Модуль Matrix в SymPy предоставляет полнофункциональную систему для работы с символьными матрицами. Он поддерживает все основные операции линейной алгебры с сохранением символьной точности.
Основные операции с символьными матрицами:
Операция | Функция | Применение в финансах |
Умножение | A * B | Композиция преобразований |
Обращение | A.inv() | Вычисление весов портфеля |
Определитель | A.det() | Проверка невырожденности |
Собственные значения | A.eigenvals() | Главные компоненты |
Собственные векторы | A.eigenvects() | Анализ рисковых факторов |
Разложения | A.LUdecomposition() | Численная стабильность |
Специальные функции и константы
SymPy предоставляет обширную библиотеку специальных функций, полностью интегрированных в символьную систему. Эти функции могут быть полезны для финансового моделирования, особенно при работе с опционами и производными инструментами.
Категории специальных функций:
Категория | Примеры функций | Применение в финансах |
Статистические | erf(), gamma(), beta() | Распределения, VaR |
Гиперболические | sinh(), cosh(), tanh() | Модели волатильности |
Интегральные | Ei(), Si(), Ci() | Интегральные опционы |
Ортогональные полиномы | legendre(), hermite() | Аппроксимация функций |
Эллиптические | elliptic_k(), elliptic_e() | Экзотические опционы |
Модули для специализированных задач
SymPy включает множество специализированных модулей, которые расширяют функциональность для конкретных областей применения. Для количественного анализа особенно важны модули статистики, геометрии и комбинаторики.
Ключевые специализированные модули:
Модуль | Основные функции | Применение |
stats | Распределения, моменты | Риск-менеджмент, моделирование |
geometry | Точки, линии, окружности | Геометрические методы оптимизации |
combinatorics | Перестановки, сочетания | Дискретные модели |
series | Ряды, последовательности | Анализ временных рядов |
solvers | Решение уравнений | Калибровка моделей |
plotting | Визуализация | Анализ функций |
Модуль stats — ключевые возможности:
- Непрерывные распределения — Normal, Uniform, Exponential;
- Дискретные распределения — Binomial, Poisson, Geometric;
- Многомерные распределения — Multivariate Normal;
- Статистики распределений — среднее, дисперсия, асимметрия;
- Символьные вычисления — для произвольных параметров.
Эффективная работа с SymPy требует понимания всех этих аспектов и умения правильно выбирать инструменты для конкретных задач. В контексте количественного анализа и финансового моделирования особенно важны правильное определение символов, использование предположений, и интеграция с численными методами.
Sympy: настройка окружения и основные инструменты
Для работы с символьными вычислениями в Python я рекомендую использовать комплексную настройку, которая включает не только SymPy, но и сопутствующие библиотеки для численного анализа и визуализации: numpy, pandas, scipy, matplotlib и др.
import sympy as sp
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sympy import symbols, Function, Eq, solve, diff, integrate, simplify
from sympy import Matrix, latex, init_printing
from sympy.plotting import plot
import warnings
warnings.filterwarnings('ignore')
# Настройка красивого отображения формул
init_printing(use_latex=True)
# Определение символьных переменных
t, x, y, z = symbols('t x y z', real=True)
sigma, mu, r = symbols('sigma mu r', positive=True)
S, K, T = symbols('S K T', positive=True)
# Генерация синтетических финансовых данных для примеров
np.random.seed(42)
dates = pd.date_range('2020-01-01', periods=252, freq='D')
returns = np.random.normal(0.001, 0.02, 252)
prices = 100 * np.exp(np.cumsum(returns))
financial_data = pd.DataFrame({'Date': dates, 'Price': prices, 'Returns': returns})
print("Символьные переменные определены:")
print(f"Время: {t}, Пространственные координаты: {x}, {y}, {z}")
print(f"Финансовые параметры: σ={sigma}, μ={mu}, r={r}")
Символьные переменные определены:
Время: t, Пространственные координаты: x, y, z
Финансовые параметры: σ=sigma, μ=mu, r=r
Этот код устанавливает базовую конфигурацию для работы с символьными вычислениями. Я создаю набор стандартных переменных, которые часто используются в финансовом моделировании, и генерирую синтетические данные, имитирующие поведение финансовых временных рядов.
Использование init_printing(use_latex=True) обеспечивает красивое отображение математических выражений, что существенно упрощает работу с сложными формулами.
Важный нюанс, который я заметил в практической работе: определение типов переменных (real=True, positive=True) значительно улучшает производительность символьных операций. SymPy может применять более эффективные алгоритмы упрощения, когда знает ограничения на значения переменных. Это особенно важно при работе с финансовыми моделями, где многие параметры имеют естественные ограничения.
Интеграция с численными библиотеками
Одна из сильных сторон символьных вычислений в Python — возможность интеграции с численными библиотеками. Я часто использую связку SymPy + NumPy для задач, где требуется сначала получить аналитическое решение, а затем эффективно его вычислить для множества параметров.
import numpy as np
import pandas as pd
import sympy as sp
from sympy import symbols, exp, sqrt, log, erf
# Описание символов
S0, K, T, r, sigma = symbols('S0 K T r sigma', positive=True)
# Символьная функция нормального распределения
def N(x):
return (1 + erf(x/sqrt(2))) / 2
# Формула Black-Scholes
d1 = (log(S0/K) + (r + sigma**2/2)*T) / (sigma*sqrt(T))
d2 = d1 - sigma*sqrt(T)
bs_call = S0*N(d1) - K*exp(-r*T)*N(d2)
# Числовая функция (через lambdify)
bs_func = sp.lambdify([S0, K, T, r, sigma], bs_call, 'numpy')
# Параметры
spot_prices = np.linspace(80, 120, 50)
strike = 100
time_to_expiry = 0.25
risk_free_rate = 0.05
volatility = 0.2
# Вычисление цен
option_prices = []
for s in spot_prices:
try:
price = float(bs_call.subs([
(S0, s), (K, strike), (T, time_to_expiry),
(r, risk_free_rate), (sigma, volatility)
]))
option_prices.append(price)
except Exception as e:
option_prices.append(np.nan)
# Результат
results_df = pd.DataFrame({
'Spot_Price': spot_prices,
'Option_Price': option_prices,
'Moneyness': spot_prices / strike
})
print(results_df.head(10))
Spot_Price Option_Price Moneyness
0 80.000000 0.056424 0.800000
1 80.816327 0.074788 0.808163
2 81.632653 0.098029 0.816327
3 82.448980 0.127113 0.824490
4 83.265306 0.163121 0.832653
5 84.081633 0.207235 0.840816
6 84.897959 0.260738 0.848980
7 85.714286 0.324999 0.857143
8 86.530612 0.401457 0.865306
9 87.346939 0.491603 0.873469
Этот пример демонстрирует мощь символьных вычислений в финансовом моделировании. Я создаю символьную формулу Black-Scholes, которая сохраняет всю математическую структуру модели. Затем эта формула может быть использована для аналитических вычислений — например, для нахождения Greeks (чувствительностей опциона к различным параметрам).
Ключевое преимущество такого подхода заключается в том, что символьная формула может быть легко модифицирована для различных типов опционов или рыночных условий. Например, добавление дивидендной доходности или изменение процентной ставки требует лишь небольших изменений в символьном выражении, после чего все производные вычисления автоматически обновляются.
Преимущества интеграции с численными библиотеками очевидны. Это более высокая производительность и масштабируемость. Рекомендации по выбору интеграций для различных задач:
- Финансовые расчеты — NumPy для базовых операций, SciPy для специальных функций;
- Статистический анализ — SciPy для распределений и тестов;
- Оптимизация — SciPy для алгоритмов оптимизации;
- Машинное обучение — NumPy для совместимости с ML библиотеками.
При переходе от символьных к численным вычислениям необходимо учитывать вопросы стабильности и точности. Следует помнить, что символьные выражения, которые математически корректны, могут приводить к численной неустойчивости при определенных значениях параметров.
Дифференцирование и анализ чувствительности
Аналитическое вычисление греков опционов (Greeks)
В количественном анализе опционов Greeks играют ключевую роль в управлении рисками. Символьные вычисления позволяют получить точные аналитические выражения для всех Greeks, что невозможно с численными методами конечных разностей.
import numpy as np
import pandas as pd
import sympy as sp
from sympy import symbols, exp, sqrt, log, erf, simplify
# Символы и формула Black-Scholes
S0, K, T, r, sigma = symbols('S0 K T r sigma', positive=True)
# Нормальное распределение через erf
def N(x):
return (1 + erf(x/sqrt(2))) / 2
# d1, d2
d1 = (log(S0/K) + (r + sigma**2/2)*T) / (sigma*sqrt(T))
d2 = d1 - sigma*sqrt(T)
# Формула цены колл-опциона
bs_call = S0*N(d1) - K*exp(-r*T)*N(d2)
# Вычисление аналитических Greeks
def calculate_greeks():
delta = sp.diff(bs_call, S0)
gamma = sp.diff(delta, S0)
theta = sp.diff(bs_call, T)
vega = sp.diff(bs_call, sigma)
rho = sp.diff(bs_call, r)
return {
'Delta': delta,
'Gamma': gamma,
'Theta': theta,
'Vega': vega,
'Rho': rho
}
# Вычисление и упрощение
greeks = calculate_greeks()
simplified_greeks = {name: simplify(expr) for name, expr in greeks.items()}
# Численный расчет Greeks
# Задание параметров
parameters = {
S0: 100,
K: 100,
T: 0.25,
r: 0.05,
sigma: 0.2
}
numerical_greeks = {}
for name, expr in simplified_greeks.items():
try:
value = float(expr.evalf(subs=parameters))
numerical_greeks[name] = value
except Exception as e:
numerical_greeks[name] = f"Error: {e}"
# Вывод
print("Аналитические выражения для Greeks:")
for name, expr in simplified_greeks.items():
print(f"{name}: {expr}\n")
print("Численные значения Greeks:")
for name, value in numerical_greeks.items():
print(f"{name}: {value:.6f}" if isinstance(value, float) else f"{name}: {value}")
Аналитические выражения для Greeks:
Delta: erf(sqrt(2)*(2*T*r + T*sigma**2 - 2*log(K) + 2*log(S0))/(4*sqrt(T)*sigma))/2 + 1/2
Gamma: sqrt(2)*K**(r/sigma**2 + 1/2)*S0**(-r/sigma**2 + log(K**(1/(T*sigma**2))) - 3/2)*exp(-T*r**2/(2*sigma**2) - T*r/2 - T*sigma**2/8 - log(K)**2/(2*T*sigma**2) - log(S0)**2/(2*T*sigma**2))/(2*sqrt(pi)*sqrt(T)*sigma)
Theta: K*r*exp(-T*r)*erf(sqrt(2)*sqrt(T)*r/(2*sigma) - sqrt(2)*sqrt(T)*sigma/4 + sqrt(2)*log(S0**2/K**2)/(4*sqrt(T)*sigma))/2 + K*r*exp(-T*r)/2 + sqrt(2)*K**(r/sigma**2 + 1/2)*S0**(-r/sigma**2 + log(K**(1/(T*sigma**2))) + 1/2)*sigma*exp(-T*r**2/(2*sigma**2) - T*r/2 - T*sigma**2/8 - log(K)**2/(2*T*sigma**2) - log(S0)**2/(2*T*sigma**2))/(4*sqrt(pi)*sqrt(T))
Vega: sqrt(2)*K**(r/sigma**2 + 1/2)*S0**(-r/sigma**2 + log(K**(1/(T*sigma**2))) + 1/2)*sqrt(T)*exp(-T*r**2/(2*sigma**2) - T*r/2 - T*sigma**2/8 - log(K)**2/(2*T*sigma**2) - log(S0)**2/(2*T*sigma**2))/(2*sqrt(pi))
Rho: K*T*(erf(sqrt(2)*(2*T*r - T*sigma**2 + log(S0**2/K**2))/(4*sqrt(T)*sigma)) + 1)*exp(-T*r)/2
Численные значения Greeks:
Delta: 0.569460
Gamma: 0.039288
Theta: 10.474151
Vega: 19.644000
Rho: 13.082755
Символьное дифференцирование позволяет получить точные результаты без численных ошибок. Это особенно важно для Gamma и других производных высокого порядка, где численные методы часто дают нестабильные результаты.
Интересная особенность символьных вычислений заключается в возможности анализа поведения Greeks в различных рыночных режимах. Например, можно аналитически определить, при каких условиях Gamma достигает максимума, или как изменяется Theta в зависимости от moneyness (нахождения вне денег) опциона.
Анализ стохастических процессов
Символьные вычисления особенно эффективны при работе со стохастическими дифференциальными уравнениями, которые широко используются в финансовом моделировании. Рассмотрим анализ процесса Орнштейна-Уленбека, который часто применяется для моделирования mean-reverting процессов.
# Анализ процесса Орнштейна-Уленбека
def analyze_ou_process():
# Определение параметров
theta, mu, sigma, t = symbols('theta mu sigma t', positive=True)
X0, Xt = symbols('X0 Xt', real=True)
# Аналитическое решение SDE dX = theta*(mu - X)*dt + sigma*dW
# X(t) = mu + (X0 - mu)*exp(-theta*t) + интегральный член
# Среднее значение процесса
mean_xt = mu + (X0 - mu) * exp(-theta * t)
# Дисперсия процесса
var_xt = (sigma**2 / (2 * theta)) * (1 - exp(-2 * theta * t))
# Автокорреляционная функция
tau = symbols('tau', positive=True)
autocorr = exp(-theta * tau)
# Стационарное распределение
stationary_mean = mu
stationary_var = sigma**2 / (2 * theta)
return {
'mean': mean_xt,
'variance': var_xt,
'autocorrelation': autocorr,
'stationary_mean': stationary_mean,
'stationary_variance': stationary_var
}
# Анализ процесса
ou_analysis = analyze_ou_process()
print("Аналитические свойства процесса Орнштейна-Уленбека:")
for property_name, expression in ou_analysis.items():
print(f"{property_name}: {expression}")
# Моделирование процесса
def simulate_ou_process(theta_val, mu_val, sigma_val, x0_val, T_val, n_steps):
dt = T_val / n_steps
times = np.linspace(0, T_val, n_steps + 1)
dW = np.random.normal(0, np.sqrt(dt), n_steps)
X = np.zeros(n_steps + 1)
X[0] = x0_val
for i in range(n_steps):
t = times[i]
mean_next = mu_val + (X[i] - mu_val) * np.exp(-theta_val * dt)
var_next = (sigma_val**2 / (2 * theta_val)) * (1 - np.exp(-2 * theta_val * dt))
X[i + 1] = mean_next + np.sqrt(var_next) * np.random.normal()
return times, X
# Параметры для моделирования
theta_val, mu_val, sigma_val = 2.0, 0.5, 0.3
x0_val, T_val, n_steps = 1.0, 2.0, 500
# Моделирование нескольких траекторий
n_simulations = 5
trajectories = []
for i in range(n_simulations):
times, X = simulate_ou_process(theta_val, mu_val, sigma_val, x0_val, T_val, n_steps)
trajectories.append(X)
# Создание DataFrame для анализа
simulation_data = pd.DataFrame({
'Time': times,
**{f'Path_{i+1}': traj for i, traj in enumerate(trajectories)}
})
print(f"\nМоделирование процесса OU с параметрами:")
print(f"θ = {theta_val}, μ = {mu_val}, σ = {sigma_val}")
print(f"Начальное значение: {x0_val}")
print(f"Стационарное среднее: {mu_val}")
print(f"Стационарная дисперсия: {sigma_val**2 / (2 * theta_val):.4f}")
Аналитические свойства процесса Орнштейна-Уленбека:
mean: mu + (X0 - mu)*exp(-t*theta)
variance: sigma**2*(1 - exp(-2*t*theta))/(2*theta)
autocorrelation: exp(-tau*theta)
stationary_mean: mu
stationary_variance: sigma**2/(2*theta)
Моделирование процесса OU с параметрами:
θ = 2.0, μ = 0.5, σ = 0.3
Начальное значение: 1.0
Стационарное среднее: 0.5
Стационарная дисперсия: 0.0225
Этот пример показывает, как символьные вычисления позволяют получить полное аналитическое описание стохастического процесса. Знание точных формул для среднего и дисперсии процесса Орнштейна-Уленбека пригодится для калибровки моделей и оценки рисков. В отличие от численного моделирования, аналитический подход дает точные результаты и позволяет понять фундаментальные свойства процесса.
Решение уравнений и оптимизация
Аналитическое решение систем уравнений
В количественном анализе часто возникает необходимость решать системы уравнений, особенно при калибровке моделей или поиске оптимальных портфелей. Символьные вычисления предоставляют мощные инструменты для получения аналитических решений.
# Решение задачи оптимизации портфеля Марковица
def markowitz_portfolio_optimization():
# Определение переменных
w1, w2, w3 = symbols('w1 w2 w3', real=True) # Веса портфеля
mu1, mu2, mu3 = symbols('mu1 mu2 mu3', real=True) # Ожидаемые доходности
sigma11, sigma12, sigma13 = symbols('sigma11 sigma12 sigma13', real=True)
sigma21, sigma22, sigma23 = symbols('sigma21 sigma22 sigma23', real=True)
sigma31, sigma32, sigma33 = symbols('sigma31 sigma32 sigma33', real=True)
lam = symbols('lambda', real=True) # Множитель Лагранжа
# Ковариационная матрица
Sigma = Matrix([
[sigma11, sigma12, sigma13],
[sigma21, sigma22, sigma23],
[sigma31, sigma32, sigma33]
])
# Вектор весов и ожидаемых доходностей
w = Matrix([w1, w2, w3])
mu = Matrix([mu1, mu2, mu3])
# Целевая функция (минимизация риска при заданной доходности)
portfolio_variance = w.T * Sigma * w
portfolio_return = w.T * mu
# Ограничения
# 1. Сумма весов равна 1
constraint1 = w1 + w2 + w3 - 1
# 2. Целевая доходность
target_return = symbols('target_return', real=True)
constraint2 = portfolio_return[0] - target_return
# Лагранжиан
L = portfolio_variance[0] + lam * constraint1 + symbols('lambda2') * constraint2
# Условия первого порядка
foc_w1 = diff(L, w1)
foc_w2 = diff(L, w2)
foc_w3 = diff(L, w3)
foc_lam = diff(L, lam)
# Система уравнений
equations = [foc_w1, foc_w2, foc_w3, foc_lam]
print("Система уравнений для оптимизации портфеля:")
for i, eq in enumerate(equations, 1):
print(f"Уравнение {i}: {eq} = 0")
return equations, portfolio_variance, portfolio_return
# Решение системы уравнений
equations, variance, expected_return = markowitz_portfolio_optimization()
# Численный пример с конкретными данными
def solve_numerical_example():
# Генерация синтетических данных
np.random.seed(42)
n_assets = 3
n_periods = 252
# Генерация доходностей
returns_data = np.random.multivariate_normal(
mean=[0.08, 0.12, 0.15],
cov=[[0.04, 0.01, 0.02],
[0.01, 0.09, 0.03],
[0.02, 0.03, 0.16]],
size=n_periods
)
# Вычисление эмпирических параметров
mu_emp = np.mean(returns_data, axis=0)
sigma_emp = np.cov(returns_data, rowvar=False)
# Решение задачи квадратичного программирования
from scipy.optimize import minimize
def portfolio_variance_func(weights):
return np.dot(weights, np.dot(sigma_emp, weights))
def portfolio_return_func(weights):
return np.dot(weights, mu_emp)
# Ограничения
constraints = [
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}, # Сумма весов = 1
{'type': 'eq', 'fun': lambda w: portfolio_return_func(w) - 0.10} # Целевая доходность
]
# Начальные веса
w0 = np.array([1/3, 1/3, 1/3])
# Оптимизация
result = minimize(portfolio_variance_func, w0, method='SLSQP', constraints=constraints)
optimal_weights = result.x
optimal_variance = portfolio_variance_func(optimal_weights)
optimal_return = portfolio_return_func(optimal_weights)
return {
'weights': optimal_weights,
'variance': optimal_variance,
'return': optimal_return,
'mu_empirical': mu_emp,
'sigma_empirical': sigma_emp
}
# Решение численного примера
numerical_solution = solve_numerical_example()
print("\nРезультаты оптимизации портфеля:")
print(f"Оптимальные веса: {numerical_solution['weights']}")
print(f"Дисперсия портфеля: {numerical_solution['variance']:.6f}")
print(f"Ожидаемая доходность: {numerical_solution['return']:.6f}")
print(f"Волатильность портфеля: {np.sqrt(numerical_solution['variance']):.6f}")
Система уравнений для оптимизации портфеля:
Уравнение 1: lambda + lambda2*mu1 + 2*sigma11*w1 + sigma12*w2 + sigma13*w3 + sigma21*w2 + sigma31*w3 = 0
Уравнение 2: lambda + lambda2*mu2 + sigma12*w1 + sigma21*w1 + 2*sigma22*w2 + sigma23*w3 + sigma32*w3 = 0
Уравнение 3: lambda + lambda2*mu3 + sigma13*w1 + sigma23*w2 + sigma31*w1 + sigma32*w2 + 2*sigma33*w3 = 0
Уравнение 4: w1 + w2 + w3 - 1 = 0
Результаты оптимизации портфеля:
Оптимальные веса: [0.5798888 0.23410828 0.18600292]
Дисперсия портфеля: 0.032697
Ожидаемая доходность: 0.100000
Волатильность портфеля: 0.180822
Символьное решение задачи оптимизации портфеля позволяет лучше понять структуру решения. Аналитические выражения для оптимальных весов показывают, как каждый параметр модели влияет на конечный результат. Так мы можем понять насколько высока чувствительность портфеля к изменениям в ковариационной матрице и ожидаемых доходностях.
В практической работе я часто использую символьные решения для создания параметрических моделей, где оптимальные веса выражаются как функции рыночных параметров. Это позволяет быстро пересчитывать портфели при изменении рыночных условий без повторного решения оптимизационной задачи.
Калибровка моделей волатильности
Символьные вычисления особенно эффективны при калибровке сложных моделей волатильности, таких как модель Хестона или модели с стохастической волатильностью. Рассмотрим калибровку упрощенной модели GARCH.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sympy import symbols, Eq, diff, simplify, log, pi
# Символьная калибровка GARCH(1,1)
def garch_model_calibration():
omega, alpha, beta = symbols('omega alpha beta', positive=True)
sigma2_t, sigma2_t_1, epsilon2_t_1 = symbols('sigma2_t sigma2_t_1 epsilon2_t_1', positive=True)
garch_equation = Eq(sigma2_t, omega + alpha * epsilon2_t_1 + beta * sigma2_t_1)
unconditional_variance = omega / (1 - alpha - beta)
log_likelihood_single = -log(2*pi)/2 - log(sigma2_t)/2 - epsilon2_t_1 / sigma2_t / 2
dll_domega = diff(log_likelihood_single, omega)
dll_dalpha = diff(log_likelihood_single, alpha)
dll_dbeta = diff(log_likelihood_single, beta)
print("Модель GARCH(1,1):")
print(f"Уравнение: {garch_equation}")
print(f"Условие стационарности: α + β < 1")
print(f"Безусловная дисперсия: {unconditional_variance}")
print("\nПроизводные логарифмической функции правдоподобия:")
print(f"∂LL/∂ω = {dll_domega}")
print(f"∂LL/∂α = {dll_dalpha}")
print(f"∂LL/∂β = {dll_dbeta}")
return garch_equation, unconditional_variance, [dll_domega, dll_dalpha, dll_dbeta]
# Численная калибровка GARCH(1,1)
def estimate_garch_parameters():
np.random.seed(42)
n_obs = 1000
omega_true, alpha_true, beta_true = 0.01, 0.1, 0.85
sigma2 = np.zeros(n_obs)
epsilon = np.zeros(n_obs)
sigma2[0] = omega_true / (1 - alpha_true - beta_true)
epsilon[0] = np.random.normal(0, np.sqrt(sigma2[0]))
for t in range(1, n_obs):
sigma2[t] = omega_true + alpha_true * epsilon[t-1]**2 + beta_true * sigma2[t-1]
epsilon[t] = np.random.normal(0, np.sqrt(sigma2[t]))
def log_likelihood(params):
omega, alpha, beta = params
if omega <= 0 or alpha <= 0 or beta <= 0 or alpha + beta >= 1:
return -1e10
sigma2_est = np.zeros(n_obs)
sigma2_est[0] = omega / (1 - alpha - beta)
ll = 0
for t in range(1, n_obs):
sigma2_est[t] = omega + alpha * epsilon[t-1]**2 + beta * sigma2_est[t-1]
ll += -0.5 * (np.log(2*np.pi) + np.log(sigma2_est[t]) + epsilon[t]**2 / sigma2_est[t])
return -ll
from scipy.optimize import minimize
initial_guess = [0.01, 0.05, 0.9]
bounds = [(1e-6, 1), (1e-6, 1), (1e-6, 1)]
result = minimize(log_likelihood, initial_guess, bounds=bounds, method='L-BFGS-B')
estimated_params = result.x
results_df = pd.DataFrame({
'Parameter': ['omega', 'alpha', 'beta'],
'True_Value': [omega_true, alpha_true, beta_true],
'Estimated_Value': estimated_params,
'Difference': np.abs(estimated_params - [omega_true, alpha_true, beta_true])
})
return results_df, epsilon, sigma2, estimated_params
# Визуализация результатов
def plot_garch_results(epsilon, sigma2, estimated_params):
omega_hat, alpha_hat, beta_hat = estimated_params
n_obs = len(epsilon)
# Восстановим оцененную условную дисперсию
sigma2_est = np.zeros(n_obs)
sigma2_est[0] = omega_hat / (1 - alpha_hat - beta_hat)
for t in range(1, n_obs):
sigma2_est[t] = omega_hat + alpha_hat * epsilon[t-1]**2 + beta_hat * sigma2_est[t-1]
# Графики
plt.figure(figsize=(16, 6))
plt.subplot(1, 2, 1)
plt.plot(epsilon, label='Simulated Returns (εₜ)', color='slategray')
plt.title('Симулированные доходности')
plt.xlabel('Time')
plt.ylabel('εₜ')
plt.grid(True)
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(sigma2, label='True σ²ₜ', color='steelblue')
plt.plot(sigma2_est, label='Estimated σ²ₜ', color='orange', linestyle='--')
plt.title('Условная дисперсия: Истинная vs Оцененная')
plt.xlabel('Time')
plt.ylabel('σ²ₜ')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
# Безусловная дисперсия
true_uncond_var = sigma2[0]
estimated_uncond_var = omega_hat / (1 - alpha_hat - beta_hat)
print(f"\nИстинная безусловная дисперсия: {true_uncond_var:.6f}")
print(f"Оцененная безусловная дисперсия: {estimated_uncond_var:.6f}")
# Запуск анализа и визуализация
if __name__ == "__main__":
garch_eq, uncond_var, derivatives = garch_model_calibration()
calibration_results, returns_series, variance_series, estimated_params = estimate_garch_parameters()
print("\nРезультаты калибровки модели GARCH(1,1):")
print(calibration_results)
plot_garch_results(returns_series, variance_series, estimated_params)
Модель GARCH(1,1):
Уравнение: Eq(sigma2_t, alpha*epsilon2_t_1 + beta*sigma2_t_1 + omega)
Условие стационарности: α + β < 1
Безусловная дисперсия: omega/(-alpha - beta + 1)
Производные логарифмической функции правдоподобия:
∂LL/∂ω = 0
∂LL/∂α = 0
∂LL/∂β = 0
Результаты калибровки модели GARCH(1,1):
Parameter True_Value Estimated_Value Difference
0 omega 0.01 0.011604 0.001604
1 alpha 0.10 0.075145 0.024855
2 beta 0.85 0.855881 0.005881
Истинная безусловная дисперсия: 0.200000
Оцененная безусловная дисперсия: 0.168236
Рис. 1: Симулированные доходности и сравнение истинной и оцененной дисперсии в модели GARCH(1,1)
Символьная калибровка GARCH моделей позволяет получить аналитические выражения для градиентов функции правдоподобия, что значительно ускоряет численную оптимизацию. В моей практике это приводит к более стабильной и быстрой сходимости алгоритмов калибровки, особенно при работе с высокочастотными данными.
Важное преимущество символьного подхода заключается в возможности анализа свойств модели до начала калибровки. Например, условие стационарности α + β < 1 может быть автоматически включено в ограничения оптимизации, что предотвращает получение нереалистичных параметров.
Интегрирование и работа с распределениями
Аналитическое вычисление интегралов в финансах
Многие задачи в количественном анализе требуют вычисления интегралов, особенно при работе с непрерывными распределениями и стохастическими процессами. Символьные вычисления позволяют получить точные аналитические решения там, где численные методы дают приближенные результаты.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sympy import symbols, exp, sqrt, pi, erf, simplify
from sympy.abc import x
# Аналитические интегралы для нормального распределения
def analytical_integration_examples():
# Объявление переменных
mu, sigma, a = symbols('mu sigma a', real=True)
sigma = symbols('sigma', positive=True)
# Плотность нормального распределения
normal_pdf = exp(-(x - mu)**2 / (2 * sigma**2)) / (sigma * sqrt(2 * pi))
# CDF через функцию ошибок (erf)
normal_cdf = 0.5 * (1 + erf((a - mu) / (sigma * sqrt(2))))
# Первый момент (матожидание), второй момент и дисперсия
first_moment = mu
second_moment = mu**2 + sigma**2
variance = sigma**2
print("Аналитические выражения для нормального распределения:")
print(f"PDF(x): {normal_pdf}")
print(f"CDF(x < a): {normal_cdf}")
print(f"Первый момент (E[X]): {first_moment}")
print(f"Второй момент (E[X²]): {second_moment}")
print(f"Дисперсия (Var[X]): {variance}")
return normal_pdf, normal_cdf, first_moment, second_moment, variance
# Визуализация PDF и CDF
def plot_normal_distribution(mu_val=0, sigma_val=1):
from scipy.stats import norm
x_vals = np.linspace(mu_val - 4*sigma_val, mu_val + 4*sigma_val, 500)
pdf_vals = norm.pdf(x_vals, loc=mu_val, scale=sigma_val)
cdf_vals = norm.cdf(x_vals, loc=mu_val, scale=sigma_val)
plt.figure(figsize=(14, 5))
# PDF
plt.subplot(1, 2, 1)
plt.plot(x_vals, pdf_vals, color='steelblue', label='PDF')
plt.title("Плотность нормального распределения (PDF)")
plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid(True)
plt.legend()
# CDF
plt.subplot(1, 2, 2)
plt.plot(x_vals, cdf_vals, color='darkorange', label='CDF')
plt.title("Функция распределения (CDF)")
plt.xlabel('x')
plt.ylabel('F(x)')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
# Запуск анализа
if __name__ == "__main__":
pdf_expr, cdf_expr, m1, m2, var = analytical_integration_examples()
plot_normal_distribution(mu_val=0, sigma_val=1)
Аналитические выражения для нормального распределения:
PDF(x): sqrt(2)*exp(-(-mu + x)**2/(2*sigma**2))/(2*sqrt(pi)*sigma)
CDF(x < a): 0.5*erf(sqrt(2)*(a - mu)/(2*sigma)) + 0.5
Первый момент (E[X]): mu
Второй момент (E[X²]): mu**2 + sigma**2
Дисперсия (Var[X]): sigma**2
Рис. 2: Плотность вероятности (PDF) и функция распределения (CDF) стандартного нормального распределения с математическим ожиданием 0 и стандартным отклонением 1
Аналитическое интегрирование в финансовых моделях дает точные результаты и глубокое понимание структуры решений. В случае барьерных опционов символьные вычисления позволяют получить точные выражения для вероятностей достижения барьеров, что критически важно для хеджирования и управления рисками.
Особенно ценным я считаю возможность символьного анализа сложных интегралов, которые возникают в моделях с несколькими факторами риска. Например, при работе с корреляционными структурами или моделями с переключением режимов, аналитические решения часто оказываются единственным способом получить точные результаты.
Преобразования и упрощения выражений
Алгебраические преобразования в финансовых моделях
Символьные вычисления предоставляют мощные инструменты для преобразования и упрощения сложных финансовых формул. Это особенно важно при работе с производными инструментами, где исходные выражения могут быть крайне сложными.
# Преобразования и упрощения в финансовых моделях
import sympy as sp
from sympy import symbols, cos, sin, sqrt, atan2, simplify, expand_trig
from sympy.stats import Normal, cdf
def algebraic_transformations():
# Определение переменных
S, K, T, r, sigma, q = symbols('S K T r sigma q', positive=True)
d1 = (log(S/K) + (r - q + sigma**2/2)*T) / (sigma*sqrt(T))
d2 = d1 - sigma*sqrt(T)
# Стандартная нормальная случайная величина
N = Normal('N', 0, 1)
pdf_N = N.pspace.density # функция плотности
european_call = S*exp(-q*T)*cdf(N)(d1) - K*exp(-r*T)*cdf(N)(d2)
early_exercise_premium = S*exp(-q*T)*sigma*sqrt(T)*pdf_N(d1) / (2*sqrt(2*pi))
american_call = european_call + early_exercise_premium
print("Исходные выражения:")
print(f"d1 = {d1}")
print(f"d2 = {d2}")
print(f"Европейский колл: {european_call}")
print(f"Премия за раннее исполнение: {early_exercise_premium}")
d1_simplified = simplify(d1)
d2_simplified = simplify(d2)
print("\nУпрощенные выражения:")
print(f"d1 упрощенное = {d1_simplified}")
print(f"d2 упрощенное = {d2_simplified}")
vol_expansion = sp.series(d1, sigma, 0, 3)
print(f"\nРазложение d1 по σ: {vol_expansion}")
limit_T_to_0 = limit(d1, T, 0)
limit_T_to_inf = limit(d1, T, sp.oo)
limit_sigma_to_0 = limit(d1, sigma, 0)
print(f"\nПредельные случаи:")
print(f"lim(T→0) d1 = {limit_T_to_0}")
print(f"lim(T→∞) d1 = {limit_T_to_inf}")
print(f"lim(σ→0) d1 = {limit_sigma_to_0}")
return d1_simplified, d2_simplified, vol_expansion
# Запуск
d1_simp, d2_simp, taylor_expansion = algebraic_transformations()
# Анализ преобразований
d1_simp, d2_simp, taylor_expansion = algebraic_transformations()
# Тригонометрические преобразования для циклических паттернов
def trigonometric_transformations():
# Моделирование сезонных эффектов в финансовых данных
t, A, B, omega, phi = symbols('t A B omega phi', real=True)
# Базовая сезонная модель
seasonal_pattern = A*cos(omega*t + phi) + B*sin(omega*t + phi)
# Преобразование в амплитудно-фазовую форму
# A*cos(ωt + φ) + B*sin(ωt + φ) = R*cos(ωt + φ - α)
R = sqrt(A**2 + B**2)
alpha = sp.atan2(B, A)
transformed_pattern = R*cos(omega*t + phi - alpha)
print("Тригонометрические преобразования:")
print(f"Исходная модель: {seasonal_pattern}")
print(f"Амплитuda: R = {R}")
print(f"Фазовый сдвиг: α = {alpha}")
print(f"Преобразованная модель: {transformed_pattern}")
# Проверка эквивалентности
expanded_transformed = sp.expand_trig(transformed_pattern)
print(f"Развернутая форма: {expanded_transformed}")
# Упрощение
simplified_diff = simplify(seasonal_pattern - transformed_pattern)
print(f"Разность (должна быть 0): {simplified_diff}")
return seasonal_pattern, transformed_pattern, R, alpha
# Анализ тригонометрических преобразований
seasonal_orig, seasonal_trans, amplitude, phase = trigonometric_transformations()
# Факторизация и канонические формы
def factorization_examples():
# Работа с характеристическими полиномами ARIMA моделей
z = symbols('z', complex=True)
phi1, phi2, theta1 = symbols('phi1 phi2 theta1', real=True)
# Характеристический полином AR(2)
ar_poly = 1 - phi1*z - phi2*z**2
# Характеристический полином MA(1)
ma_poly = 1 + theta1*z
print("Характеристические полиномы:")
print(f"AR(2): {ar_poly}")
print(f"MA(1): {ma_poly}")
# Факторизация AR(2) полинома
ar_factors = sp.factor(ar_poly)
print(f"Факторизация AR(2): {ar_factors}")
# Поиск корней для анализа стационарности
ar_roots = solve(ar_poly, z)
print(f"Корни AR(2): {ar_roots}")
# Условия стационарности
# Все корни должны лежать вне единичного круга
stationarity_conditions = []
for root in ar_roots:
condition = abs(root) > 1
stationarity_conditions.append(condition)
print("Условия стационарности:")
for i, condition in enumerate(stationarity_conditions):
print(f"Корень {i+1}: |{ar_roots[i]}| > 1")
# Численный пример
phi1_val, phi2_val = 0.5, -0.3
ar_numerical = ar_poly.subs([(phi1, phi1_val), (phi2, phi2_val)])
numerical_roots = solve(ar_numerical, z)
print(f"\nЧисленный пример (φ1={phi1_val}, φ2={phi2_val}):")
print(f"Корни: {numerical_roots}")
# Проверка стационарности
roots_moduli = [abs(complex(root.evalf())) for root in numerical_roots]
is_stationary = all(mod > 1 for mod in roots_moduli)
print(f"Модули корней: {roots_moduli}")
print(f"Процесс стационарен: {is_stationary}")
return ar_poly, ar_roots, numerical_roots, roots_moduli
# Анализ факторизации
ar_polynomial, ar_roots_symbolic, ar_roots_numerical, moduli = factorization_examples()
Исходные выражения:
d1 = (T*(-q + r + sigma**2/2) + log(S/K))/(sqrt(T)*sigma)
d2 = -sqrt(T)*sigma + (T*(-q + r + sigma**2/2) + log(S/K))/(sqrt(T)*sigma)
Европейский колл: -K*(erf(sqrt(2)*(-sqrt(T)*sigma + (T*(-q + r + sigma**2/2) + log(S/K))/(sqrt(T)*sigma))/2)/2 + 1/2)*exp(-T*r) + S*(erf(sqrt(2)*(T*(-q + r + sigma**2/2) + log(S/K))/(2*sqrt(T)*sigma))/2 + 1/2)*exp(-T*q)
Премия за раннее исполнение: S*sqrt(T)*sigma*exp(-T*q)*exp(-(T*(-q + r + sigma**2/2) + log(S/K))**2/(2*T*sigma**2))/(4*pi)
Упрощенные выражения:
d1 упрощенное = (T*(-2*q + 2*r + sigma**2) + log(S**2/K**2))/(2*sqrt(T)*sigma)
d2 упрощенное = (-2*T*q + 2*T*r - T*sigma**2 - 2*log(K) + 2*log(S))/(2*sqrt(T)*sigma)
Разложение d1 по σ: (-sqrt(T)*q + sqrt(T)*r + log(S/K)/sqrt(T))/sigma + sqrt(T)*sigma/2 + O(sigma**3)
Предельные случаи:
lim(T→0) d1 = oo*sign(-log(K) + log(S))
lim(T→∞) d1 = oo*sign(-2*q + 2*r + sigma**2)
lim(σ→0) d1 = oo*sign(-T*q + T*r - log(K) + log(S))
Исходные выражения:
d1 = (T*(-q + r + sigma**2/2) + log(S/K))/(sqrt(T)*sigma)
d2 = -sqrt(T)*sigma + (T*(-q + r + sigma**2/2) + log(S/K))/(sqrt(T)*sigma)
Европейский колл: -K*(erf(sqrt(2)*(-sqrt(T)*sigma + (T*(-q + r + sigma**2/2) + log(S/K))/(sqrt(T)*sigma))/2)/2 + 1/2)*exp(-T*r) + S*(erf(sqrt(2)*(T*(-q + r + sigma**2/2) + log(S/K))/(2*sqrt(T)*sigma))/2 + 1/2)*exp(-T*q)
Премия за раннее исполнение: S*sqrt(T)*sigma*exp(-T*q)*exp(-(T*(-q + r + sigma**2/2) + log(S/K))**2/(2*T*sigma**2))/(4*pi)
Упрощенные выражения:
d1 упрощенное = (T*(-2*q + 2*r + sigma**2) + log(S**2/K**2))/(2*sqrt(T)*sigma)
d2 упрощенное = (-2*T*q + 2*T*r - T*sigma**2 - 2*log(K) + 2*log(S))/(2*sqrt(T)*sigma)
Разложение d1 по σ: (-sqrt(T)*q + sqrt(T)*r + log(S/K)/sqrt(T))/sigma + sqrt(T)*sigma/2 + O(sigma**3)
Предельные случаи:
lim(T→0) d1 = oo*sign(-log(K) + log(S))
lim(T→∞) d1 = oo*sign(-2*q + 2*r + sigma**2)
lim(σ→0) d1 = oo*sign(-T*q + T*r - log(K) + log(S))
Тригонометрические преобразования:
Исходная модель: A*cos(omega*t + phi) + B*sin(omega*t + phi)
Амплитuda: R = sqrt(A**2 + B**2)
Фазовый сдвиг: α = atan2(B, A)
Преобразованная модель: sqrt(A**2 + B**2)*cos(omega*t + phi - atan2(B, A))
Развернутая форма: sqrt(A**2 + B**2)*(-(A*sin(omega*t)/sqrt(A**2 + B**2) - B*cos(omega*t)/sqrt(A**2 + B**2))*sin(phi) + (A*cos(omega*t)/sqrt(A**2 + B**2) + B*sin(omega*t)/sqrt(A**2 + B**2))*cos(phi))
Разность (должна быть 0): 0
Характеристические полиномы:
AR(2): -phi1*z - phi2*z**2 + 1
MA(1): theta1*z + 1
Факторизация AR(2): -phi1*z - phi2*z**2 + 1
Корни AR(2): [(-phi1 - sqrt(phi1**2 + 4*phi2))/(2*phi2), (-phi1 + sqrt(phi1**2 + 4*phi2))/(2*phi2)]
Условия стационарности:
Корень 1: |(-phi1 - sqrt(phi1**2 + 4*phi2))/(2*phi2)| > 1
Корень 2: |(-phi1 + sqrt(phi1**2 + 4*phi2))/(2*phi2)| > 1
Численный пример (φ1=0.5, φ2=-0.3):
Корни: [0.833333333333333 - 1.62446572413483*I, 0.833333333333333 + 1.62446572413483*I]
Модули корней: [1.8257418583505536, 1.8257418583505536]
Процесс стационарен: True
Алгебраические преобразования в символьных вычислениях позволяют выявить скрытые свойства финансовых моделей. Например, факторизация характеристических полиномов временных рядов предоставляет прямой способ анализа стационарности без численных вычислений. Это может быть полезно при автоматизации процесса идентификации моделей.
В практической работе я часто использую символьные преобразования для выявления эквивалентных формулировок моделей. Например, различные параметризации модели Хестона могут быть связаны через алгебраические преобразования, что позволяет выбрать наиболее подходящую форму для конкретной задачи калибровки.
Визуализация и интерпретация результатов
Визуализация символьных выражений помогает понять их поведение и найти оптимальные параметры. SymPy предоставляет встроенные инструменты для построения графиков, которые можно комбинировать с matplotlib для создания профессиональных визуализаций.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sympy import symbols, exp, sqrt, pi
# Визуализация символьных функций с выводом текста
def visualize_symbolic_functions():
# Определение переменных
S, vol, time_val = symbols('S vol time_val', positive=True)
# Параметры
K_val, r_val, T_val = 100, 0.05, 0.25
# Символьные выражения греков (упрощенные)
delta_func = exp(-(S - K_val)**2 / (2*vol**2*T_val)) / (vol*sqrt(2*pi*T_val))
gamma_func = exp(-(S - K_val)**2 / (2*vol**2*T_val)) * (K_val - S) / (vol**3*sqrt(2*pi*T_val**3))
vega_func = S * sqrt(T_val) * exp(-(S - K_val)**2 / (2*vol**2*T_val)) / sqrt(2*pi)
# Вывод символьных функций
print("Символьные функции для визуализации:")
print(f"Delta: {delta_func}")
print(f"Gamma: {gamma_func}")
print(f"Vega: {vega_func}")
# Численные диапазоны для визуализации
S_range = np.linspace(80, 120, 100)
vol_range = np.linspace(0.1, 0.5, 100)
# Вычисление Delta для различных цен (vol=0.2)
delta_values = [float(delta_func.subs({S: s_val, vol: 0.2})) for s_val in S_range]
# Вычисление Gamma для различных цен (vol=0.2)
gamma_values = [float(gamma_func.subs({S: s_val, vol: 0.2})) for s_val in S_range]
# Вычисление Vega для различных волатильностей (S=100)
vega_values = [float(vega_func.subs({S: 100, vol: v_val})) for v_val in vol_range]
# Создание DataFrame для удобства
greeks_df = pd.DataFrame({'Spot_Price': S_range, 'Delta': delta_values, 'Gamma': gamma_values})
vega_df = pd.DataFrame({'Volatility': vol_range, 'Vega': vega_values})
return greeks_df, vega_df, delta_func, gamma_func, vega_func
# Получаем данные и выводим символьные функции
greeks_data, vega_data, delta_expr, gamma_expr, vega_expr = visualize_symbolic_functions()
# Построение графиков
plt.figure(figsize=(14, 5))
# Delta и Gamma по цене
plt.subplot(1, 2, 1)
plt.plot(greeks_data['Spot_Price'], greeks_data['Delta'], label='Delta', color='blue')
plt.plot(greeks_data['Spot_Price'], greeks_data['Gamma'], label='Gamma', color='red')
plt.title('Delta и Gamma в зависимости от цены базового актива')
plt.xlabel('Цена базового актива (S)')
plt.ylabel('Значение')
plt.legend()
plt.grid(True)
# Vega по волатильности
plt.subplot(1, 2, 2)
plt.plot(vega_data['Volatility'], vega_data['Vega'], label='Vega', color='green')
plt.title('Vega в зависимости от волатильности')
plt.xlabel('Волатильность (vol)')
plt.ylabel('Значение')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Символьные функции для визуализации:
Delta: 1.41421356237309*exp(-2.0*(S - 100)**2/vol**2)/(sqrt(pi)*vol)
Gamma: 5.65685424949238*(100 - S)*exp(-2.0*(S - 100)**2/vol**2)/(sqrt(pi)*vol**3)
Vega: 0.25*sqrt(2)*S*exp(-2.0*(S - 100)**2/vol**2)/sqrt(pi)
Рис. 3: Визуализация греков опциона: зависимость Delta и Gamma от цены базового актива при фиксированной волатильности 0.2 (слева), и зависимость Vega от волатильности при фиксированной цене актива 100 (справа)
В этом коде мы построили символьные выражения для греков опциона — Delta, Gamma и Vega — используя упрощенные формулы, зависящие от цены базового актива и волатильности. Затем с помощью подстановки конкретных значений переменных мы получили численные данные и визуализировали поведение этих показателей. Такая визуализация позволяет наглядно понять, как чувствительность цены опциона изменяется при колебаниях цены актива и волатильности, что важно для управления рисками и принятия торговых решений.
Заключение
Символьные вычисления в Python открывают новые горизонты для количественного анализа и финансового моделирования. В отличие от традиционных численных методов, SymPy позволяет получать точные аналитические решения для понимания математической структуры финансовых моделей.
Возможность символьного дифференцирования обеспечивает точное вычисление греков опционов без численных ошибок, аналитическое интегрирование дает строгие решения для опционного ценообразования, а символьная оптимизация позволяет исследовать свойства портфелей в общем виде.
Символьные вычисления не заменяют численные методы, а дополняют их, предоставляя инструменты для глубокого анализа и верификации. Правильное использование SymPy — с учетом системы предположений, оптимизации производительности и интеграции с численными библиотеками — позволяет создавать более надежные и эффективные количественные модели.