diff --git a/.agents/rules/antigravity-rtk-rules.md b/.agents/rules/antigravity-rtk-rules.md new file mode 100644 index 0000000..8ee44bc --- /dev/null +++ b/.agents/rules/antigravity-rtk-rules.md @@ -0,0 +1,32 @@ +# RTK - Rust Token Killer (Google Antigravity) + +**Usage**: Token-optimized CLI proxy for shell commands. + +## Rule + +Always prefix shell commands with `rtk` to minimize token consumption. + +Examples: + +```bash +rtk git status +rtk cargo test +rtk ls src/ +rtk grep "pattern" src/ +rtk find "*.rs" . +rtk docker ps +rtk gh pr list +``` + +## Meta Commands + +```bash +rtk gain # Show token savings +rtk gain --history # Command history with savings +rtk discover # Find missed RTK opportunities +rtk proxy # Run raw (no filtering, for debugging) +``` + +## Why + +RTK filters and compresses command output before it reaches the LLM context, saving 60-90% tokens on common operations. Always use `rtk ` instead of raw commands. diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..0688e97 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,11 @@ +[run] +branch = True +source = affidiff +omit = */__init__.py + +[paths] +source = + src/ + +[report] +show_missing = True diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index b01c87c..ea3f447 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -30,9 +30,29 @@ jobs: - name: Run test suite run: uv run pytest + code-quality: + name: Code quality checks + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v6 + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version-file: ".python-version" + - name: Install dependencies + run: uv sync --group dev + - name: Run pre-commit + run: uv run prek run -v --show-diff-on-failure --all-files + publish: name: Build and publish wheels - needs: [ test ] + needs: [ test, code-quality ] runs-on: ubuntu-latest environment: name: pypi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c818495 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +repos: + - repo: builtin + hooks: + - id: trailing-whitespace + - id: check-added-large-files + - id: end-of-file-fixer + - id: forbid-new-submodules + - id: mixed-line-ending + - id: check-json + - id: pretty-format-json + args: ["--autofix"] + - id: check-yaml + - id: detect-private-key + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.16.0 + hooks: + - id: ruff + args: [ --config, pyproject.toml, --fix, --exit-non-zero-on-fix ] + types_or: [ python ] + - id: ruff-format + args: [ --config, pyproject.toml ] + types_or: [ python ] + + - repo: https://github.com/astral-sh/ty-pre-commit + rev: v0.0.64 + hooks: + - id: ty diff --git a/.rtk/filters.toml b/.rtk/filters.toml new file mode 100644 index 0000000..d9bd43f --- /dev/null +++ b/.rtk/filters.toml @@ -0,0 +1,13 @@ +# Project-local RTK filters — commit this file with your repo. +# Filters here override user-global and built-in filters. +# Docs: https://github.com/rtk-ai/rtk#custom-filters +schema_version = 1 + +# Example: suppress build noise from a custom tool +# [filters.my-tool] +# description = "Compact my-tool output" +# match_command = "^my-tool\\s+build" +# strip_ansi = true +# strip_lines_matching = ["^\\s*$", "^Downloading", "^Installing"] +# max_lines = 30 +# on_empty = "my-tool: ok" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1ef812b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,124 @@ +# Project rules + +- Each class should expose only those methods and attributes that are used in the other classes/functions. All other attributes and methods should be private (_method). Example: + + ```python + class A: + def __init__(self): + self._x = 1 # should be private + self.y = 2 # should be public + + def _private_method(self): + pass # should be private + + def public_method(self): + pass # should be public + ``` + +- Unit tests are not allowed to access private attribute/methods of classes. + +- Use uv to run python commands, e.g. + + ```shell + uv run pytest + ``` + +- Run prek on all files before each commit (stage all the files but do not commit): + + ```shell + uv run prek run -v --show-diff-on-failure --all-files + ``` + + +# DOX framework + +- DOX is highly performant AGENTS.md hierarchy installed here +- Agent must follow DOX instructions across any edits + +## Core Contract + +- AGENTS.md files are binding work contracts for their subtrees +- Work products, source materials, instructions, records, assets, and durable docs must stay understandable from the nearest applicable AGENTS.md plus every parent AGENTS.md above it + +## Read Before Editing + +1. Read the root AGENTS.md +2. Identify every file or folder you expect to touch +3. Walk from the repository root to each target path +4. Read every AGENTS.md found along each route +5. If a parent AGENTS.md lists a child AGENTS.md whose scope contains the path, read that child and continue from there +6. Use the nearest AGENTS.md as the local contract and parent docs for repo-wide rules +7. If docs conflict, the closer doc controls local work details, but no child doc may weaken DOX + +Do not rely on memory. Re-read the applicable DOX chain in the current session before editing. + +## Update After Editing + +Every meaningful change requires a DOX pass before the task is done. + +Update the closest owning AGENTS.md when a change affects: + +- purpose, scope, ownership, or responsibilities +- durable structure, contracts, workflows, or operating rules +- required inputs, outputs, permissions, constraints, side effects, or artifacts +- user preferences about behavior, communication, process, organization, or quality +- AGENTS.md creation, deletion, move, rename, or index contents + +Update parent docs when parent-level structure, ownership, workflow, or child index changes. Update child docs when parent changes alter local rules. Remove stale or contradictory text immediately. Small edits that do not change behavior or contracts may leave docs unchanged, but the DOX pass still must happen. + +## Hierarchy + +- Root AGENTS.md is the DOX rail: project-wide instructions, global preferences, durable workflow rules, and the top-level Child DOX Index +- Child AGENTS.md files own domain-specific instructions and their own Child DOX Index +- Each parent explains what its direct children cover and what stays owned by the parent +- The closer a doc is to the work, the more specific and practical it must be + +## Child Doc Shape + +- Create a child AGENTS.md when a folder becomes a durable boundary with its own purpose, rules, responsibilities, workflow, materials, or quality standards +- Work Guidance must reflect the current standards of the project or user instructions; if there are no specific standards or instructions yet, leave it empty +- Verification must reflect an existing check; if no verification framework exists yet, leave it empty and update it when one exists + +Default section order: +- Purpose +- Ownership +- Local Contracts +- Work Guidance +- Verification +- Child DOX Index + +## Style + +- Keep docs concise, current, and operational +- Document stable contracts, not diary entries +- Put broad rules in parent docs and concrete details in child docs +- Prefer direct bullets with explicit names +- Do not duplicate rules across many files unless each scope needs a local version +- Delete stale notes instead of explaining history +- Trim obvious statements, repeated rules, misplaced detail, and warnings for risks that no longer exist + +## Closeout + +1. Re-check changed paths against the DOX chain +2. Update nearest owning docs and any affected parents or children +3. Refresh every affected Child DOX Index +4. Remove stale or contradictory text +5. Run existing verification when relevant +6. Report any docs intentionally left unchanged and why + +## User Preferences + +When the user requests a durable behavior change, record it here or in the relevant child AGENTS.md + +## Child DOX Index + +- `src/AGENTS.md` - Production source code root containing affidiff package + - `src/affidiff/AGENTS.md` - Core affine diffusion models, parameter classes, moments, and Cython simulation helpers +- `tests/AGENTS.md` - Test suite covering affine diffusion models, parameter classes, moments estimation, and simulation utilities +- `examples/AGENTS.md` - Runnable usage scripts for affine diffusions models and simulation/estimation workflows +- `docs/AGENTS.md` - Sphinx documentation source files and configuration + +**Root-owned files** (no child DOX needed): +- `.github/` - CI/CD workflows +- Configuration files: `pyproject.toml`, `.pre-commit-config.yaml`, `.travis.yml`, `.coveragerc`, `.gitignore`, `setup.py` +- Documentation & math assets: `README.md`, `CHANGELOG.md`, `LICENSE.md`, `models.lyx`, `models.pdf` diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 0000000..2b8f9a9 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,26 @@ +# Purpose + +`docs/` contains Sphinx configuration, build instructions, and source files for generating the documentation website and API reference for `affidiff`. + +# Ownership + +Owns Sphinx documentation source files and build configurations: +- Build automation Makefile (`docs/Makefile`) +- Documentation sources (`docs/source/`) + +# Local Contracts + +- Docstrings across production modules must adhere to standard NumPy format (as configured in `pyproject.toml` under `tool.ruff.lint.pydocstyle`). +- Sphinx build files must compile without errors or missing module references. + +# Work Guidance + +- Ensure new public functions, classes, and parameter types added to `affidiff` are documented and visible in the Sphinx source index. + +# Verification + +- Build documentation via `Makefile` inside `docs/` directory when Sphinx is installed. + +# Child DOX Index + +None (leaf boundary). diff --git a/docs/source/conf.py b/docs/source/conf.py index b2b386c..ff3db7e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # diffusions documentation build configuration file, created by # sphinx-quickstart on Tue Jan 20 14:42:02 2015. @@ -13,8 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -25,26 +23,39 @@ # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> # -- Khrapov -------------------------------------------------------------- -sys.path.insert(0, os.path.abspath('../..')) - -import mock - -MOCK_MODULES = ['numpy', 'seaborn', 'matplotlib', 'matplotlib.pylab', - 'statsmodels', 'statsmodels.tsa', 'statsmodels.tsa.tsatools', - 'scipy', 'scipy.stats', 'scipy.optimize', 'scipy.linalg', - 'numdifftools', 'pandas'] +sys.path.insert(0, os.path.abspath("../..")) + +import mock # type: ignore + +MOCK_MODULES = [ + "numpy", + "seaborn", + "matplotlib", + "matplotlib.pylab", + "statsmodels", + "statsmodels.tsa", + "statsmodels.tsa.tsatools", + "scipy", + "scipy.stats", + "scipy.optimize", + "scipy.linalg", + "numdifftools", + "pandas", +] for mod_name in MOCK_MODULES: sys.modules[mod_name] = mock.Mock() # on_rtd is whether we are on readthedocs.org, # this line of code grabbed from docs.readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' + import sphinx_rtd_theme # type: ignore + + html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + numpydoc_class_members_toctree = False # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -56,48 +67,48 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.autosummary', - 'numpydoc' + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.autosummary", + "numpydoc", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'diffusions' -copyright = '2015, Stanislav Khrapov' +project = "diffusions" +copyright = "2015, Stanislav Khrapov" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.1' +version = "0.1" # The full version, including alpha/beta/rc tags. -release = '0.1' +release = "0.1" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -105,167 +116,161 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -#html_theme = 'default' +# html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'diffusionsdoc' +htmlhelp_basename = "diffusionsdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'diffusions.tex', 'diffusions Documentation', - 'Stanislav Khrapov', 'manual'), + ("index", "diffusions.tex", "diffusions Documentation", "Stanislav Khrapov", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'diffusions', 'diffusions Documentation', - ['Stanislav Khrapov'], 1) -] +man_pages = [("index", "diffusions", "diffusions Documentation", ["Stanislav Khrapov"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -274,19 +279,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'diffusions', 'diffusions Documentation', - 'Stanislav Khrapov', 'diffusions', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "diffusions", + "diffusions Documentation", + "Stanislav Khrapov", + "diffusions", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/docs/source/index.rst b/docs/source/index.rst index 99934a1..ba12578 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,4 +10,4 @@ Affine Diffusion Models vasicek cir heston - centtend \ No newline at end of file + centtend diff --git a/examples/AGENTS.md b/examples/AGENTS.md new file mode 100644 index 0000000..3dd0caa --- /dev/null +++ b/examples/AGENTS.md @@ -0,0 +1,27 @@ +# Purpose + +`examples/` contains runnable Python scripts illustrating usage of `affidiff` models, trajectory simulation, moment calculation, and real financial dataset loading. + +# Ownership + +Owns executable demonstration scripts: +- Model trial scripts (`try_cir.py`, `try_gbm.py`, `try_heston.py`, `try_vasicek.py`, `try_centtend.py`) +- Real data loading utility example (`load_real_data.py`) + +# Local Contracts + +- Example scripts must import solely from the public `affidiff` package interface. +- Scripts must run executable without unhandled runtime exceptions using `uv run python examples/.py`. + +# Work Guidance + +- Keep example scripts clean, well-commented, and representative of real-world use cases (e.g. calibration, simulation, plotting). + +# Verification + +- Execute scripts: `uv run python examples/try_gbm.py` (or other example scripts) +- Ensure clean linting: `uv run prek run -v --show-diff-on-failure --all-files` + +# Child DOX Index + +None (leaf boundary). diff --git a/examples/load_real_data.py b/examples/load_real_data.py index 319fcea..a9a0a4c 100644 --- a/examples/load_real_data.py +++ b/examples/load_real_data.py @@ -1,34 +1,36 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Load real market data +"""Load real market data.""" +from __future__ import annotations -""" -from __future__ import print_function, division +from typing import Any import numpy as np import pandas as pd +from datastorage.cboe import load_vix_spx # type: ignore +from datastorage.oxfordman import load_realized_vol # type: ignore -from datastorage.oxfordman import load_realized_vol -from datastorage.cboe import load_vix_spx +def load_data() -> tuple[Any, Any]: + """Load and process real market data. -def load_data(): + Returns + ------- + tuple + Tuple of (ret, rvar) arrays + """ realized_vol = load_realized_vol() vix_spx = load_vix_spx() data = pd.merge(realized_vol, vix_spx, left_index=True, right_index=True) - data['logR'] = data['SPX'].apply(np.log).diff(1) + data["logR"] = data["SPX"].apply(np.log).diff(1) data.dropna(inplace=True) - data = data[['logR', 'RV']].values.T - ret, rvar = data + data_arr = data[["logR", "RV"]].values.T + ret, rvar = data_arr rvar = (rvar / 100) ** 2 return (ret, rvar) -if __name__ == '__main__': - - pass \ No newline at end of file +if __name__ == "__main__": + pass diff --git a/examples/try_centtend.py b/examples/try_centtend.py index 85ddaa6..a4ce2e4 100644 --- a/examples/try_centtend.py +++ b/examples/try_centtend.py @@ -1,42 +1,36 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Try Central Tendency model +# ruff: noqa +"""Try Central Tendency model.""" + +from __future__ import annotations -""" -from __future__ import print_function, division -import time import itertools +import time -import numpy as np import matplotlib.pylab as plt +import numpy as np import seaborn as sns - +from load_real_data import load_data # type: ignore from statsmodels.tsa.stattools import acf from affidiff import CentTend, CentTendParam -from affidiff.helper_functions import (plot_trajectories, plot_final_distr, - plot_realized, take_time) -from load_real_data import load_data - +from affidiff.helper_functions import plot_final_distr, plot_realized, plot_trajectories, take_time -def try_simulation(): - """Try simulating and plotting Central Tendency model. - """ - riskfree = .01 - lmbd = .01 - mean_v = .5 +def try_simulation() -> None: + """Try simulating and plotting Central Tendency model.""" + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .05 - eta_s = .02**.5 - eta_y = .001**.5 - rho = -.9 - - param_true = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.05 + eta_s = 0.02**0.5 + eta_y = 0.001**0.5 + rho = -0.9 + + param_true = CentTendParam( + riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, eta_s=eta_s, eta_y=eta_y, rho=rho + ) centtend = CentTend(param_true) print(param_true) print(param_true.is_valid()) @@ -44,142 +38,129 @@ def try_simulation(): start = [1, mean_v, mean_v] nperiods, nsub, ndiscr, nsim = 500, 10, 10, 3 nobs = nperiods * nsub - paths = centtend.simulate(start, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, diff=0) + paths = centtend.simulate(start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0) returns = paths[:, 0, 0] volatility = paths[:, 0, 1] tendency = paths[:, 0, 2] - plot_trajectories(returns, nsub, 'returns') - names = ['vol', 'ct'] - plot_trajectories([volatility, tendency], nsub, names) + plot_trajectories(paths=returns, nsub=nsub, names="returns") + names = ["vol", "ct"] + plot_trajectories(paths=[volatility, tendency], nsub=nsub, names=names) -def try_simulation_pq(): - """Try simulating and plotting Central Tendency model - under P and Q measures. - - """ - riskfree = .01 +def try_simulation_pq() -> None: + """Try simulating and plotting Central Tendency model under P and Q measures.""" + riskfree = 0.01 lmbd = 1.01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .05 - eta_s = .02**.5 - eta_y = .001**.5 - rho = -.9 - - param_true = CentTendParam(riskfree=riskfree, lmbd=lmbd, lmbd_s=lmbd_s, - lmbd_y=lmbd_y, mean_v=mean_v, kappa_s=kappa_s, - kappa_y=kappa_y, eta_s=eta_s, eta_y=eta_y, - rho=rho) + kappa_y = 0.05 + eta_s = 0.02**0.5 + eta_y = 0.001**0.5 + rho = -0.9 + + param_true = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param_true) print(param_true) print(param_true.is_valid()) start = [1, mean_v, mean_v] nperiods, nsub, ndiscr, nsim = 500, 10, 100, 3 - nobs = nperiods * nsub - paths = centtend.simulate(start, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, diff=0) - - returns = paths[:, 0, 0] - volatility = paths[:, 0, 1] - tendency = paths[:, 0, 2] - - param_true_new = CentTendParam(riskfree=riskfree, lmbd=lmbd, lmbd_s=lmbd_s, - lmbd_y=lmbd_y, mean_v=mean_v, - kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, - rho=rho, measure='Q') - print(param_true_new) - centtend.update_theta(param_true_new) - start_q = [1, param_true_new.mean_v, param_true_new.mean_v] - - paths_q = centtend.simulate(start_q, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, - diff=0, new_innov=False) - - returns_q = paths_q[:, 0, 0] - volatility_q = paths_q[:, 0, 1] - tendency_q = paths_q[:, 0, 2] / param_true_new.scale - plot_trajectories([returns, returns_q], nsub, ['returns', 'returns Q']) - names = ['vol', 'ct', 'vol Q', 'ct Q'] - plot_trajectories([volatility, tendency, volatility_q, tendency_q], - nsub, names) - - -def try_marginal(): - """Simulate and plot marginal distribution of the data - in Central Tendency model. - - """ - riskfree = .01 - lmbd = .01 - mean_v = .5 + param_true_new = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) + centtend_new = CentTend(param_true_new) + (paths_p, paths_q) = centtend_new.sim_realized_pq( + start_p=start, start_q=start, nsub=nsub, ndiscr=ndiscr, aggh=[1, 1], nperiods=nperiods, nsim=nsim + ) + + returns_p = paths_p[0] + returns_q = paths_q[0] + + names = ["P", "Q"] + plot_trajectories(paths=[returns_p, returns_q], nsub=nsub, names=names) + + +def try_marginal() -> None: + """Simulate and plot marginal distribution of the data in Central Tendency model.""" + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .05 - eta_s = .02**.5 - eta_y = .001**.5 - rho = -.9 - - param_true = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.05 + eta_s = 0.02**0.5 + eta_y = 0.001**0.5 + rho = -0.9 + + param_true = CentTendParam( + riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, eta_s=eta_s, eta_y=eta_y, rho=rho + ) centtend = CentTend(param_true) - print(param_true) - print(param_true.is_valid()) start = [1, mean_v, mean_v] - nperiods, nsub, ndiscr, nsim = 500, 10, 10, 100 + nperiods, nsub, ndiscr, nsim = 500, 10, 10, 500 nobs = nperiods * nsub - paths = centtend.simulate(start, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, diff=0) + paths = centtend.simulate(start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0) returns = paths[:, :, 0] volatility = paths[:, :, 1] tendency = paths[:, :, 2] - plot_final_distr(returns, 'returns') - names = ['vol', 'ct'] - plot_final_distr([volatility, tendency], names) - - -def try_sim_realized(): - """Simulate realized data from Central Tendency model and plot it. - - """ - riskfree = .0 - mean_v = .5 - kappa_s = .05 - kappa_y = .02 - eta_s = .1 - eta_y = .01 - rho = -.9 - lmbd = .5 - - param_true = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + plot_final_distr(paths=returns * nsub, names="returns") + plot_final_distr(paths=volatility, names="volatility") + plot_final_distr(paths=tendency, names="tendency") + + +def try_sim_realized() -> None: + """Simulate realized data from Central Tendency model and plot it.""" + riskfree = 0.0 + mean_v = 0.5 + kappa_s = 0.05 + kappa_y = 0.02 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.9 + lmbd = 0.5 + + param_true = CentTendParam( + riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, eta_s=eta_s, eta_y=eta_y, rho=rho + ) centtend = CentTend(param_true) - print(param_true) - print(param_true.is_valid()) nperiods, nsub, ndiscr, nsim = 2000, 80, 10, 1 aggh = 1 - returns, rvar = centtend.sim_realized(nsub=nsub, ndiscr=ndiscr, aggh=aggh, - nperiods=nperiods, nsim=nsim, diff=0) + returns, rvar = centtend.sim_realized(nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) - plot_realized(returns, rvar) + plot_realized(returns=returns, rvar=rvar) nlags, lw = 90, 2 - grid = range(nlags+1) - plt.plot(grid, acf(rvar, nlags=nlags), lw=lw, label='RV') + grid = range(nlags + 1) + plt.plot(grid, acf(rvar, nlags=nlags), lw=lw, label="RV") plt.show() @@ -188,118 +169,127 @@ def try_sim_realized_pq(): under P and Q measures. """ - riskfree = .0 + riskfree = 0.0 lmbd = 1.01 - lmbd_s, lmbd_y = .5, .5 - mean_v = .2 - kappa_s = .1 - kappa_y = .02 - eta_s = .1 - eta_y = .01 - rho = -.9 - - param_true = CentTendParam(riskfree=riskfree, lmbd=lmbd, lmbd_s=lmbd_s, - lmbd_y=lmbd_y, mean_v=mean_v, kappa_s=kappa_s, - kappa_y=kappa_y, eta_s=eta_s, eta_y=eta_y, - rho=rho) + lmbd_s, lmbd_y = 0.5, 0.5 + mean_v = 0.2 + kappa_s = 0.1 + kappa_y = 0.02 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.9 + + param_true = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param_true) print(param_true) nperiods, nsub, ndiscr, nsim = 500, 80, 10, 1 aggh = [1, 1] - data = centtend.sim_realized_pq(nsub=nsub, ndiscr=ndiscr, - aggh=aggh, nperiods=nperiods, nsim=nsim, - diff=0) + data = centtend.sim_realized_pq(nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) print(param_true) (ret_p, rvar_p), (ret_q, rvar_q) = data nobs = np.min([ret_p.size, ret_q.size]) - plot_realized([ret_p[-nobs:], ret_q[-nobs:]], - [rvar_p[-nobs:], rvar_q[-nobs:] / param_true.scale], - suffix=['P', 'Q']) + plot_realized( + returns=[ret_p[-nobs:], ret_q[-nobs:]], + rvar=[rvar_p[-nobs:], rvar_q[-nobs:] / param_true.scale], + suffix=["P", "Q"], + ) def try_integrated_gmm_single(): - """Simulate realized data from Central Tendency model. Estimate parameters. - - """ - riskfree = .0 - mean_v = .2 + """Simulate realized data from Central Tendency model. Estimate parameters.""" + riskfree = 0.0 + mean_v = 0.2 kappa_s = 1.5 - kappa_y = .008 - eta_s = .5 - eta_y = .05 - rho = -.9 - lmbd = .5 - - param_true = CentTendParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.008 + eta_s = 0.5 + eta_y = 0.05 + rho = -0.9 + lmbd = 0.5 + + param_true = CentTendParam( + riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, eta_s=eta_s, eta_y=eta_y, rho=rho + ) centtend = CentTend(param_true) print(param_true) nperiods, nsub, ndiscr, nsim = 2000, 80, 10, 1 aggh = 1 - data = centtend.sim_realized(nsub=nsub, ndiscr=ndiscr, - aggh=aggh, nperiods=nperiods, - nsim=nsim, diff=0) + data = centtend.sim_realized(nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) ret, rvar = data - plot_realized(ret, rvar) + plot_realized(returns=ret, rvar=rvar) nlags, lw = 90, 2 - grid = range(nlags+1) - plt.plot(grid, acf(rvar, nlags=nlags), lw=lw, label='RV') + grid = range(nlags + 1) + plt.plot(grid, acf(rvar, nlags=nlags), lw=lw, label="RV") plt.show() instr_data = np.vstack([rvar]) - subset = 'vol' - measure = 'P' - -# theta = param_true.get_theta(subset=subset, measure=measure) -# mom, dmom = centtend.integrated_mom(theta/10, data=data, -# instr_data=instr_data, -# instr_choice='var', aggh=1, -# subset=subset, instrlag=1, -# measure=measure) -# -# fig, axes = plt.subplots(nrows=mom.shape[1], ncols=1, sharex=True, -# figsize=(10, 2*mom.shape[1])) -# for momf, ax in zip(mom.T, axes): -# ax.plot(momf) -# ax.axhline(momf.mean(), c='red') -# plt.show() -# print(mom.mean(0) / mom.std(0)) + subset = "vol" + measure = "P" + + # theta = param_true.get_theta(subset=subset, measure=measure) + # mom, dmom = centtend.integrated_mom(theta/10, data=data, + # instr_data=instr_data, + # instr_choice='var', aggh=1, + # subset=subset, instrlag=1, + # measure=measure) + # + # fig, axes = plt.subplots(nrows=mom.shape[1], ncols=1, sharex=True, + # figsize=(10, 2*mom.shape[1])) + # for momf, ax in zip(mom.T, axes): + # ax.plot(momf) + # ax.axhline(momf.mean(), c='red') + # plt.show() + # print(mom.mean(0) / mom.std(0)) time_start = time.time() - res = centtend.integrated_gmm(param_true, data=data, instrlag=3, - instr_data=instr_data, aggh=aggh, - instr_choice='var', method='SLSQP', - subset=subset, measure=measure, iter=3) + res = centtend.integrated_gmm( + param_start=param_true, + data=data, + instrlag=3, + instr_data=instr_data, + aggh=aggh, + instr_choice="var", + method="SLSQP", + subset=subset, + measure=measure, + iter=3, + ) print(res) - print('Elapsed time = %.2f min' % ((time.time() - time_start)/60)) + print("Elapsed time = %.2f min" % ((time.time() - time_start) / 60)) def try_integrated_gmm_real(): - """Estimate Central Tendency model parameters with real data. - - """ - riskfree = .0 + """Estimate Central Tendency model parameters with real data.""" + riskfree = 0.0 - mean_v = .02 - kappa_s = .22 - kappa_y = .05 - eta_s = .36 - eta_y = .05 + mean_v = 0.02 + kappa_s = 0.22 + kappa_y = 0.05 + eta_s = 0.36 + eta_y = 0.05 - lmbd = .01 - rho = -.9 + lmbd = 0.01 + rho = -0.9 - param_start = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, - kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + param_start = CentTendParam( + riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, eta_s=eta_s, eta_y=eta_y, rho=rho + ) centtend = CentTend(param_start) print(param_start) print(param_start.is_valid()) @@ -309,25 +299,32 @@ def try_integrated_gmm_real(): data = load_data() ret, rvar = data - plot_realized(ret, rvar) + plot_realized(returns=ret, rvar=rvar) nlags = 90 lw = 2 - grid = range(nlags+1) - plt.plot(grid, acf(rvar, nlags=nlags), lw=lw, label='RV') + grid = range(nlags + 1) + plt.plot(grid, acf(rvar, nlags=nlags), lw=lw, label="RV") plt.show() instr_data = np.vstack([rvar, rvar**2]) - subset = 'vol' + subset = "vol" time_start = time.time() - res = centtend.integrated_gmm(param_start, data=data, instrlag=2, - instr_data=instr_data, aggh=aggh, - instr_choice='var', method='TNC', - subset=subset, iter=2) + res = centtend.integrated_gmm( + param_start=param_start, + data=data, + instrlag=2, + instr_data=instr_data, + aggh=aggh, + instr_choice="var", + method="TNC", + subset=subset, + iter=2, + ) print(res) - print('Elapsed time = %.2f min' % ((time.time() - time_start)/60)) + print("Elapsed time = %.2f min" % ((time.time() - time_start) / 60)) def try_integrated_gmm_opt_methods(): @@ -335,20 +332,20 @@ def try_integrated_gmm_opt_methods(): Check various optimization methods. """ - riskfree = .0 + riskfree = 0.0 - mean_v = .2 - kappa_s = .1 - kappa_y = .05 - eta_s = .1 - eta_y = .03 + mean_v = 0.2 + kappa_s = 0.1 + kappa_y = 0.05 + eta_s = 0.1 + eta_y = 0.03 - lmbd = .01 - rho = -.9 + lmbd = 0.01 + rho = -0.9 - param_true = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + param_true = CentTendParam( + riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, eta_s=eta_s, eta_y=eta_y, rho=rho + ) centtend = CentTend(param_true) print(param_true) print(param_true.is_valid()) @@ -356,47 +353,51 @@ def try_integrated_gmm_opt_methods(): start = [1, mean_v, mean_v] nperiods, nsub, ndiscr, nsim = 2000, 80, 1, 1 aggh = 1 - data = centtend.sim_realized(start, nsub=nsub, ndiscr=ndiscr, - aggh=aggh, nperiods=nperiods, - nsim=nsim, diff=0) + data = centtend.sim_realized(start=start, nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) ret, rvar = data - plot_realized(ret, rvar) + plot_realized(returns=ret, rvar=rvar) instr_data = np.vstack([rvar, rvar**2]) param_start = param_true - param_start.update(param_true.get_theta()/2) + param_start.update(theta=param_true.get_theta() / 2) - tasks = itertools.product(np.arange(1, 4), ['L-BFGS-B', 'TNC', 'SLSQP']) + tasks = itertools.product(np.arange(1, 4), ["L-BFGS-B", "TNC", "SLSQP"]) for lag, method in tasks: time_start = time.time() - res = centtend.integrated_gmm(param_start, data=data, instrlag=lag, - instr_data=instr_data, aggh=aggh, - instr_choice='var', method=method, - subset='vol', iter=3) + res = centtend.integrated_gmm( + param_start=param_start, + data=data, + instrlag=lag, + instr_data=instr_data, + aggh=aggh, + instr_choice="var", + method=method, + subset="vol", + iter=3, + ) print(res) print(lag, method) - print('Elapsed time = %.2f min' % ((time.time() - time_start)/60)) - + print("Elapsed time = %.2f min" % ((time.time() - time_start) / 60)) -if __name__ == '__main__': +if __name__ == "__main__": np.set_printoptions(precision=4, suppress=True) - sns.set_context('notebook') - -# with take_time('Simulation'): -# try_simulation() -# with take_time('Simulation PQ'): -# try_simulation_pq() -# with take_time('Marginal density'): -# try_marginal() -# with take_time('Simulation realized'): -# try_sim_realized() -# with take_time('Simulation realized PQ'): -# try_sim_realized_pq() -# with take_time('Integrated GMM'): -# try_integrated_gmm_single() -# with take_time('Integrated GMM with real data'): -# try_integrated_gmm_real() - with take_time('Integrated GMM with real data'): + sns.set_context("notebook") + + # with take_time('Simulation'): + # try_simulation() + # with take_time('Simulation PQ'): + # try_simulation_pq() + # with take_time('Marginal density'): + # try_marginal() + # with take_time('Simulation realized'): + # try_sim_realized() + # with take_time('Simulation realized PQ'): + # try_sim_realized_pq() + # with take_time('Integrated GMM'): + # try_integrated_gmm_single() + # with take_time('Integrated GMM with real data'): + # try_integrated_gmm_real() + with take_time("Integrated GMM with real data"): try_integrated_gmm_opt_methods() diff --git a/examples/try_cir.py b/examples/try_cir.py index 7f721f7..502c7b0 100644 --- a/examples/try_cir.py +++ b/examples/try_cir.py @@ -1,70 +1,71 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Try Cox-Ingersoll-Ross Model +"""Try Cox-Ingersoll-Ross Model.""" -""" -from __future__ import print_function, division +from __future__ import annotations import seaborn as sns from affidiff import CIR, CIRparam -from affidiff.helper_functions import (plot_trajectories, plot_final_distr, - plot_realized, take_time) +from affidiff.helper_functions import plot_final_distr, plot_realized, plot_trajectories, take_time -def try_simulation(): - mean, kappa, eta = .5, .1, .2 - theta_true = CIRparam(mean, kappa, eta) +def try_simulation() -> None: + """Try simulating and plotting CIR model.""" + mean, kappa, eta = 0.5, 0.1, 0.2 + theta_true = CIRparam(mean=mean, kappa=kappa, eta=eta) # 2 * kappa * mean - eta**2 > 0 print(theta_true.is_valid()) cir = CIR(theta_true) x0, nperiods, nsub, ndiscr, nsim = mean, 500, 2, 10, 3 nobs = nperiods * nsub - paths = cir.simulate(x0, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim) + paths = cir.simulate(start=x0, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim) data = paths[:, 0, 0] - plot_trajectories(data, nsub, 'rates') + plot_trajectories(paths=data, nsub=nsub, names="rates") -def try_marginal(): - mean, kappa, eta = .5, .1, .2 - theta_true = CIRparam(mean, kappa, eta) +def try_marginal() -> None: + """Try marginal distribution of CIR model.""" + mean, kappa, eta = 0.5, 0.1, 0.2 + theta_true = CIRparam(mean=mean, kappa=kappa, eta=eta) # 2 * kappa * mean - eta**2 > 0 print(theta_true.is_valid()) cir = CIR(theta_true) x0, nperiods, nsub, ndiscr, nsim = mean, 500, 2, 10, 20 nobs = nperiods * nsub - paths = cir.simulate(x0, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim) + paths = cir.simulate(start=x0, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim) data = paths[:, :, 0] - plot_final_distr(data, 'rates') + plot_final_distr(paths=data, names="rates") -def try_sim_realized(): - mean, kappa, eta = .5, .1, .2 - theta_true = CIRparam(mean, kappa, eta) +def try_sim_realized() -> None: + """Try simulated realized CIR model data.""" + mean, kappa, eta = 0.5, 0.1, 0.2 + theta_true = CIRparam(mean=mean, kappa=kappa, eta=eta) cir = CIR(theta_true) start, nperiods, nsub, ndiscr, nsim = 1, 500, 80, 1, 1 aggh = 10 - returns, rvar = cir.sim_realized(start, nsub=nsub, ndiscr=ndiscr, - aggh=aggh, nperiods=nperiods, - nsim=nsim, diff=0) - - plot_realized(returns, rvar) - - -if __name__ == '__main__': - - sns.set_context('notebook') - with take_time('Simulation'): + returns, rvar = cir.sim_realized( + start=start, + nsub=nsub, + ndiscr=ndiscr, + aggh=aggh, + nperiods=nperiods, + nsim=nsim, + diff=0, + ) + + plot_realized(returns=returns, rvar=rvar) + + +if __name__ == "__main__": + sns.set_context("notebook") + with take_time("Simulation"): try_simulation() - with take_time('Marginal density'): + with take_time("Marginal density"): try_marginal() - with take_time('Simulate realized'): + with take_time("Simulate realized"): try_sim_realized() diff --git a/examples/try_gbm.py b/examples/try_gbm.py index b883c71..395eb61 100644 --- a/examples/try_gbm.py +++ b/examples/try_gbm.py @@ -1,112 +1,100 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Try Geometric Brownian Motion +"""Try Geometric Brownian Motion.""" -""" -from __future__ import print_function, division +from __future__ import annotations -import seaborn as sns import numpy as np +import seaborn as sns from affidiff import GBM, GBMparam -from affidiff.helper_functions import (plot_trajectories, plot_final_distr, - plot_realized, take_time) +from affidiff.helper_functions import plot_final_distr, plot_realized, plot_trajectories, take_time -def try_simulation(): - mean, sigma = .05, .2 - theta_true = GBMparam(mean, sigma) +def try_simulation() -> None: + """Try simulating and plotting GBM model.""" + mean, sigma = 0.05, 0.2 + theta_true = GBMparam(mean=mean, sigma=sigma) print(theta_true) gbm = GBM(theta_true) start, nperiods, nsub, ndiscr, nsim = 1, 500, 2, 10, 2 nobs = nperiods * nsub - paths = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, - nsim=nsim, diff=0) + paths = gbm.simulate(start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0) data = paths[:, 0, 0] - plot_trajectories(data, nsub, 'returns') + plot_trajectories(paths=data, nsub=nsub, names="returns") -def try_marginal(): - mean, sigma = .05, .2 - theta_true = GBMparam(mean, sigma) +def try_marginal() -> None: + """Try marginal distribution of GBM model.""" + mean, sigma = 0.05, 0.2 + theta_true = GBMparam(mean=mean, sigma=sigma) gbm = GBM(theta_true) start, nperiods, nsub, ndiscr, nsim = 1, 500, 2, 10, 20 nobs = nperiods * nsub - paths = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, - nsim=nsim, diff=0) + paths = gbm.simulate(start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0) data = paths[:, :, 0] - plot_final_distr(data * nsub, 'returns') + plot_final_distr(paths=data * nsub, names="returns") -def try_gmm(): - mean, sigma = 1.5, .2 - theta_true = GBMparam(mean, sigma) +def try_gmm() -> None: + """Try GMM estimation for GBM model.""" + mean, sigma = 1.5, 0.2 + theta_true = GBMparam(mean=mean, sigma=sigma) gbm = GBM(theta_true) start, nperiods, nsub, ndiscr, nsim = 1, 500, 2, 10, 1 nobs = nperiods * nsub - paths = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, - nsim=nsim, diff=0) + paths = gbm.simulate(start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0) data = paths[:, 0, 0] - plot_trajectories(data, nsub, 'returns') + plot_trajectories(paths=data, nsub=nsub, names="returns") - mean, sigma = 2.5, .4 - theta_start = GBMparam(mean, sigma) - res = gbm.gmmest(theta_start, data=data, instrlag=2) + mean, sigma = 2.5, 0.4 + theta_start = GBMparam(mean=mean, sigma=sigma) + res = gbm.gmmest(theta_start=theta_start, data=data, instrlag=2) print(res) -def try_sim_realized(): - mean, sigma = .05, .2 - theta_true = GBMparam(mean, sigma) +def try_sim_realized() -> None: + """Try simulated realized GBM model data.""" + mean, sigma = 0.05, 0.2 + theta_true = GBMparam(mean=mean, sigma=sigma) gbm = GBM(theta_true) start, nperiods, nsub, ndiscr, nsim = 1, 500, 80, 1, 1 aggh = 10 - returns, rvar = gbm.sim_realized(start, nsub=nsub, ndiscr=ndiscr, - aggh=aggh, nperiods=nperiods, - nsim=nsim, diff=0) + returns, rvar = gbm.sim_realized( + start=start, nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0 + ) - plot_realized(returns, rvar) + plot_realized(returns=returns, rvar=rvar) -def try_integrated_gmm(): - mean, sigma = 1.5, .2 - theta_true = GBMparam(mean, sigma) +def try_integrated_gmm() -> None: + """Try Integrated GMM for GBM model.""" + mean, sigma = 1.5, 0.2 + theta_true = GBMparam(mean=mean, sigma=sigma) gbm = GBM(theta_true) start, nperiods, nsub, ndiscr, nsim = 1, 500, 80, 1, 1 aggh = 10 - returns, rvar = gbm.sim_realized(start, nsub=nsub, ndiscr=ndiscr, - aggh=aggh, nperiods=nperiods, - nsim=nsim, diff=0) + returns, rvar = gbm.sim_realized( + start=start, nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0 + ) data = np.vstack([returns, rvar]) - print(rvar.mean()**.5) - plot_realized(returns, rvar) + print(rvar.mean() ** 0.5) + plot_realized(returns=returns, rvar=rvar) - mean, sigma = 2.5, .4 - theta_start = GBMparam(mean, sigma) - res = gbm.integrated_gmm(theta_start, data=data, instrlag=2) + mean, sigma = 2.5, 0.4 + theta_start = GBMparam(mean=mean, sigma=sigma) + res = gbm.integrated_gmm(param_start=theta_start, data=data, instrlag=2) print(res) -if __name__ == '__main__': - - sns.set_context('notebook') -# with take_time('Simulation'): -# try_simulation() -# with take_time('Marginal density'): -# try_marginal() -# with take_time('Simulate RV'): -# try_sim_realized() -# with take_time('GMM'): -# try_gmm() - with take_time('Integrated GMM'): +if __name__ == "__main__": + sns.set_context("notebook") + with take_time("Integrated GMM"): try_integrated_gmm() diff --git a/examples/try_heston.py b/examples/try_heston.py index cfc6f28..e809d49 100644 --- a/examples/try_heston.py +++ b/examples/try_heston.py @@ -1,162 +1,137 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Try Heston model +# ruff: noqa +"""Try Heston model.""" -""" -from __future__ import print_function, division +from __future__ import annotations -import time import itertools +import time -import numpy as np import matplotlib.pylab as plt +import numpy as np import seaborn as sns - +from load_real_data import load_data # type: ignore from statsmodels.tsa.stattools import acf from affidiff import Heston, HestonParam -from affidiff.helper_functions import (plot_trajectories, plot_final_distr, - plot_realized, take_time) -from load_real_data import load_data +from affidiff.helper_functions import plot_final_distr, plot_realized, plot_trajectories, take_time def try_simulation(): - """Try simulating and plotting Heston model. - - """ - riskfree = .0 - lmbd = .0 - mean_v = .5 - kappa = .1 - eta = .02**.5 - rho = -.9 + """Try simulating and plotting Heston model.""" + riskfree = 0.0 + lmbd = 0.0 + mean_v = 0.5 + kappa = 0.1 + eta = 0.02**0.5 + rho = -0.9 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho) + param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) heston = Heston(param_true) print(param_true.is_valid()) start = [1, mean_v] nperiods, nsub, ndiscr, nsim = 500, 10, 10, 3 nobs = nperiods * nsub - paths = heston.simulate(start, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, diff=0) + paths = heston.simulate(start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0) returns = paths[:, 0, 0] volatility = paths[:, 0, 1] - plot_trajectories(returns, nsub, 'returns') - plot_trajectories(volatility, nsub, 'volatility') + plot_trajectories(paths=returns, nsub=nsub, names="returns") + plot_trajectories(paths=volatility, nsub=nsub, names="volatility") def try_simulation_pq(): - """Try simulating and plotting Heston model. - - """ - riskfree = .0 - lmbd = .0 - lmbd_v = .5 - mean_v = .5 - kappa = .1 - eta = .15 - rho = -.9 + """Try simulating and plotting Heston model.""" + riskfree = 0.0 + lmbd = 0.0 + lmbd_v = 0.5 + mean_v = 0.5 + kappa = 0.1 + eta = 0.15 + rho = -0.9 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho, measure='P') + param_true = HestonParam( + riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, measure="P" + ) heston = Heston(param_true) print(param_true.is_valid()) nperiods, nsub, ndiscr, nsim = 100, 10, 10, 3 start = [1, mean_v] nobs = nperiods * nsub - paths = heston.simulate(start, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, diff=0) + paths = heston.simulate(start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0) - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho, measure='Q') + param_true = HestonParam( + riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, measure="Q" + ) heston.update_theta(param_true) start_q = [1, param_true.mean_v] - paths_q = heston.simulate(start_q, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, diff=0, new_innov=False) + paths_q = heston.simulate(start=start_q, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0, new_innov=False) returns = paths[:, 0, 0] volatility = paths[:, 0, 1] returns_q = paths_q[:, 0, 0] volatility_q = paths_q[:, 0, 1] - plot_trajectories([returns, returns_q], nsub, ['returns', 'returns_q']) - plot_trajectories([volatility, volatility_q], nsub, - ['volatility', 'volatility_q']) + plot_trajectories(paths=[returns, returns_q], nsub=nsub, names=["returns", "returns_q"]) + plot_trajectories(paths=[volatility, volatility_q], nsub=nsub, names=["volatility", "volatility_q"]) def try_marginal(): - """Simulate and plot marginal distribution of the data in Heston model. - - """ - riskfree = .0 - lmbd = .0 - mean_v = .5 - kappa = .1 - eta = .02**.5 - rho = -.9 + """Simulate and plot marginal distribution of the data in Heston model.""" + riskfree = 0.0 + lmbd = 0.0 + mean_v = 0.5 + kappa = 0.1 + eta = 0.02**0.5 + rho = -0.9 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho) + param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) heston = Heston(param_true) start = [1, mean_v] nperiods, nsub, ndiscr, nsim = 500, 10, 10, 200 nobs = nperiods * nsub - paths = heston.simulate(start, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, diff=0) + paths = heston.simulate(start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=0) returns = paths[:, :, 0] volatility = paths[:, :, 1] - plot_final_distr(returns, names='returns') - plot_final_distr(volatility, names='volatility') + plot_final_distr(paths=returns, names="returns") + plot_final_distr(paths=volatility, names="volatility") def try_sim_realized(): - """Simulate realized data from Heston model and plot it. - - """ - riskfree = .0 - lmbd = .0 - mean_v = .5 - kappa = .1 - eta = .02**.5 - rho = -.9 + """Simulate realized data from Heston model and plot it.""" + riskfree = 0.0 + lmbd = 0.0 + mean_v = 0.5 + kappa = 0.1 + eta = 0.02**0.5 + rho = -0.9 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho) + param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) heston = Heston(param_true) # start = [1, mean_v] nperiods, nsub, ndiscr, nsim = 500, 80, 1, 1 aggh = 10 - returns, rvar = heston.sim_realized(nsub=nsub, ndiscr=ndiscr, aggh=aggh, - nperiods=nperiods, nsim=nsim, diff=0) + returns, rvar = heston.sim_realized(nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) - plot_realized(returns, rvar) + plot_realized(returns=returns, rvar=rvar) def try_sim_realized_pq(): - """Simulate realized data from Heston model under P and Q measures. - - """ - riskfree = .0 - mean_v = .5 - kappa = .04 - eta = .15 - rho = -.9 + """Simulate realized data from Heston model under P and Q measures.""" + riskfree = 0.0 + mean_v = 0.5 + kappa = 0.04 + eta = 0.15 + rho = -0.9 lmbd = 1.5 - lmbd_v = .2 + lmbd_v = 0.2 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) heston = Heston(param_true) nperiods, nsub, ndiscr, nsim = 500, 100, 1, 1 @@ -164,55 +139,61 @@ def try_sim_realized_pq(): print(heston.param) - data = heston.sim_realized_pq(nsub=nsub, ndiscr=ndiscr, aggh=aggh, - nperiods=nperiods, nsim=nsim, diff=0) + data = heston.sim_realized_pq(nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) (ret_p, rvar_p), (ret_q, rvar_q) = data print(heston.param) nobs = np.min([ret_p.size, ret_q.size]) - plot_realized([ret_p[-nobs:], ret_q[-nobs:]], - [rvar_p[-nobs:], rvar_q[-nobs:]], suffix=['P', 'Q']) + plot_realized( + returns=[ret_p[-nobs:], ret_q[-nobs:]], + rvar=[rvar_p[-nobs:], rvar_q[-nobs:]], + suffix=["P", "Q"], + ) def try_integrated_gmm_single(): - """Simulate realized data from Heston model. Estimate parameters. - - """ - riskfree = .0 - - mean_v = .2 - kappa = .06 - eta = .15 - lmbd = .5 - rho = -.5 + """Simulate realized data from Heston model. Estimate parameters.""" + riskfree = 0.0 + + mean_v = 0.2 + kappa = 0.06 + eta = 0.15 + lmbd = 0.5 + rho = -0.5 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho) + param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) heston = Heston(param_true) print(param_true) nperiods, nsub, ndiscr, nsim = 2000, 80, 10, 1 aggh = 1 - data = heston.sim_realized(nsub=nsub, ndiscr=ndiscr, - aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) + data = heston.sim_realized(nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) ret, rvar = data - plot_realized(ret, rvar) + plot_realized(returns=ret, rvar=rvar) nlags, lw = 90, 2 - grid = range(nlags+1) - plt.plot(grid, acf(rvar, nlags=nlags), lw=lw, label='RV') + grid = range(nlags + 1) + plt.plot(grid, acf(rvar, nlags=nlags), lw=lw, label="RV") plt.show() instr_data = np.vstack([rvar, rvar**2]) - subset = 'vol' - measure = 'P' + subset = "vol" + measure = "P" time_start = time.time() - res = heston.integrated_gmm(param_true, data=data, instrlag=3, - instr_data=instr_data, aggh=aggh, - instr_choice='var', method='SLSQP', - subset=subset, measure=measure, iter=3) + res = heston.integrated_gmm( + param_start=param_true, + data=data, + instrlag=3, + instr_data=instr_data, + aggh=aggh, + instr_choice="var", + method="SLSQP", + subset=subset, + measure=measure, + iter=3, + ) print(res) - print('Elapsed time = %.2f min' % ((time.time() - time_start)/60)) + print("Elapsed time = %.2f min" % ((time.time() - time_start) / 60)) def try_integrated_gmm_single_rn(): @@ -220,53 +201,64 @@ def try_integrated_gmm_single_rn(): Estimate parameters. """ - riskfree = .0 - mean_v = .5 - kappa = .04 - eta = .15 - rho = -.9 + riskfree = 0.0 + mean_v = 0.5 + kappa = 0.04 + eta = 0.15 + rho = -0.9 lmbd = 1.5 - lmbd_v = .1 + lmbd_v = 0.1 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) - print('P parameters:\n', param_true) + param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + print("P parameters:\n", param_true) heston = Heston(param_true) aggh = [1, 1] nperiods, nsub, ndiscr, nsim = 2000, 100, 1, 1 - data = heston.sim_realized_pq(nsub=nsub, ndiscr=ndiscr, aggh=aggh, - nperiods=nperiods, nsim=nsim, diff=0) - print('Q parameters:\n', param_true) + data = heston.sim_realized_pq(nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) + print("Q parameters:\n", param_true) data_p, data_q = data ret_p, rvar_p = data_p ret_q, rvar_q = data_q nobs = np.min([ret_p.size, ret_q.size]) - plot_realized([ret_p[-nobs:], ret_q[-nobs:]], - [rvar_p[-nobs:], rvar_q[-nobs:]], suffix=['P', 'Q']) + plot_realized(returns=[ret_p[-nobs:], ret_q[-nobs:]], rvar=[rvar_p[-nobs:], rvar_q[-nobs:]], suffix=["P", "Q"]) instr_data = np.vstack([rvar_p, rvar_p**2]) - subset = 'vol' - measure = 'P' - - res = heston.integrated_gmm(param_true, data=data_p, instrlag=2, - instr_data=instr_data, aggh=aggh[0], - instr_choice='var', method='TNC', - subset=subset, iter=3, - measure=measure) + subset = "vol" + measure = "P" + + res = heston.integrated_gmm( + param_start=param_true, + data=data_p, + instrlag=2, + instr_data=instr_data, + aggh=aggh[0], + instr_choice="var", + method="TNC", + subset=subset, + iter=3, + measure=measure, + ) print(res) time_start = time.time() - res = heston.integrated_gmm(param_true, data=data_q, instrlag=2, - instr_data=instr_data, aggh=aggh[1], - instr_choice='var', method='TNC', - subset=subset, iter=3, - measure=measure) + res = heston.integrated_gmm( + param_start=param_true, + data=data_q, + instrlag=2, + instr_data=instr_data, + aggh=aggh[1], + instr_choice="var", + method="TNC", + subset=subset, + iter=3, + measure=measure, + ) print(res) - print('Elapsed time = %.2f min' % ((time.time() - time_start)/60)) + print("Elapsed time = %.2f min" % ((time.time() - time_start) / 60)) def try_integrated_gmm_joint(): @@ -274,83 +266,91 @@ def try_integrated_gmm_joint(): Estimate parameters. """ - riskfree = .0 - mean_v = .5 - kappa = .04 - eta = .15 - rho = -.9 + riskfree = 0.0 + mean_v = 0.5 + kappa = 0.04 + eta = 0.15 + rho = -0.9 lmbd = 1.5 - lmbd_v = .1 + lmbd_v = 0.1 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) - print('P parameters:\n', param_true) + param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + print("P parameters:\n", param_true) heston = Heston(param_true) aggh = [1, 1] nperiods, nsub, ndiscr, nsim = 1000, 100, 1, 1 - data = heston.sim_realized_pq(nsub=nsub, ndiscr=ndiscr, aggh=aggh, - nperiods=nperiods, nsim=nsim, diff=0) - print('Q parameters:\n', param_true) + data = heston.sim_realized_pq(nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) + print("Q parameters:\n", param_true) data_p, data_q = data ret_p, rvar_p = data_p ret_q, rvar_q = data_q nobs = np.min([ret_p.size, ret_q.size]) - plot_realized([ret_p[-nobs:], ret_q[-nobs:]], - [rvar_p[-nobs:], rvar_q[-nobs:]], suffix=['P', 'Q']) + plot_realized(returns=[ret_p[-nobs:], ret_q[-nobs:]], rvar=[rvar_p[-nobs:], rvar_q[-nobs:]], suffix=["P", "Q"]) instr_data = np.vstack([rvar_p, rvar_p**2]) - subset = 'vol' - measure = 'PQ' + subset = "vol" + measure = "PQ" time_start = time.time() - res = heston.integrated_gmm(param_true, data=data, instrlag=2, - instr_data=instr_data, aggh=aggh, - instr_choice='var', method='TNC', - subset=subset, iter=3, - measure=measure) + res = heston.integrated_gmm( + param_start=param_true, + data=data, + instrlag=2, + instr_data=instr_data, + aggh=aggh, + instr_choice="var", + method="TNC", + subset=subset, + iter=3, + measure=measure, + ) print(res) - print('Elapsed time = %.2f min' % ((time.time() - time_start)/60)) + print("Elapsed time = %.2f min" % ((time.time() - time_start) / 60)) return res def try_integrated_gmm_real(): - """Estimate Heston model parameters with real data. + """Estimate Heston model parameters with real data.""" + riskfree = 0.0 - """ - riskfree = .0 + mean_v = 0.2 + kappa = 0.22 + eta = 0.12 - mean_v = .2 - kappa = .22 - eta = .12 - - lmbd = .3 - rho = -.5 + lmbd = 0.3 + rho = -0.5 # 2 * kappa * mean_v - eta**2 > 0 - param_start = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho) + param_start = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) heston = Heston(param_start) aggh = 1 data = load_data() ret, rvar = data - plot_realized(ret, rvar) + plot_realized(returns=ret, rvar=rvar) instr_data = np.vstack([rvar, rvar**2]) - subset = 'vol' + subset = "vol" time_start = time.time() - res = heston.integrated_gmm(param_start, data=data, instrlag=2, - instr_data=instr_data, aggh=aggh, - instr_choice='var', method='TNC', - subset=subset, iter=3) + res = heston.integrated_gmm( + param_start=param_start, + data=data, + instrlag=2, + instr_data=instr_data, + aggh=aggh, + instr_choice="var", + method="TNC", + subset=subset, + iter=3, + ) print(res) - print('Elapsed time = %.2f min' % ((time.time() - time_start)/60)) + print("Elapsed time = %.2f min" % ((time.time() - time_start) / 60)) def try_integrated_gmm_opt_methods(): @@ -358,65 +358,68 @@ def try_integrated_gmm_opt_methods(): Check various optimization methods. """ - riskfree = .0 + riskfree = 0.0 - mean_v = .5 - kappa = .1 - eta = .15 - lmbd = .3 - rho = -.5 + mean_v = 0.5 + kappa = 0.1 + eta = 0.15 + lmbd = 0.3 + rho = -0.5 # 2 * kappa * mean_v - eta**2 > 0 - param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho) + param_true = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) heston = Heston(param_true) start, nperiods, nsub, ndiscr, nsim = [1, mean_v], 2000, 80, 1, 1 aggh = 10 - data = heston.sim_realized(start, nsub=nsub, ndiscr=ndiscr, - aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) + data = heston.sim_realized(start=start, nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0) ret, rvar = data - plot_realized(ret, rvar) + plot_realized(returns=ret, rvar=rvar) instr_data = np.vstack([rvar, rvar**2]) param_start = param_true - param_start.update(param_true.get_theta()/2) + param_start.update(theta=param_true.get_theta() / 2) - tasks = itertools.product(np.arange(1, 4), ['L-BFGS-B', 'TNC', 'SLSQP']) + tasks = itertools.product(np.arange(1, 4), ["L-BFGS-B", "TNC", "SLSQP"]) for lag, method in tasks: time_start = time.time() - res = heston.integrated_gmm(param_start, data=data, instrlag=lag, - instr_data=instr_data, aggh=aggh, - instr_choice='var', method=method, - subset='vol', iter=3) + res = heston.integrated_gmm( + param_start=param_start, + data=data, + instrlag=lag, + instr_data=instr_data, + aggh=aggh, + instr_choice="var", + method=method, + subset="vol", + iter=3, + ) print(res) print(lag, method) - print('Elapsed time = %.2f min' % ((time.time() - time_start)/60)) - + print("Elapsed time = %.2f min" % ((time.time() - time_start) / 60)) -if __name__ == '__main__': +if __name__ == "__main__": np.set_printoptions(precision=4, suppress=True) - sns.set_context('notebook') - -# with take_time('Simulation'): -# try_simulation() -# with take_time('Simulation PQ'): -# try_simulation_pq() -# with take_time('Marginal density'): -# try_marginal() -# with take_time('Simulate realized'): -# try_sim_realized() -# with take_time('Simulate realized PQ'): -# try_sim_realized_pq() -# with take_time('Integrated GMM'): -# try_integrated_gmm_single() -# with take_time('Integrated GMM under Q'): -# try_integrated_gmm_single_rn() -# with take_time('Integrated GMM under P and Q'): -# res = try_integrated_gmm_joint() -# with take_time('Integrated GMM with real data'): -# try_integrated_gmm_real() - with take_time('Integrated GMM with real data'): + sns.set_context("notebook") + + # with take_time('Simulation'): + # try_simulation() + # with take_time('Simulation PQ'): + # try_simulation_pq() + # with take_time('Marginal density'): + # try_marginal() + # with take_time('Simulate realized'): + # try_sim_realized() + # with take_time('Simulate realized PQ'): + # try_sim_realized_pq() + # with take_time('Integrated GMM'): + # try_integrated_gmm_single() + # with take_time('Integrated GMM under Q'): + # try_integrated_gmm_single_rn() + # with take_time('Integrated GMM under P and Q'): + # res = try_integrated_gmm_joint() + # with take_time('Integrated GMM with real data'): + # try_integrated_gmm_real() + with take_time("Integrated GMM with real data"): try_integrated_gmm_opt_methods() diff --git a/examples/try_vasicek.py b/examples/try_vasicek.py index c7da229..64b576a 100644 --- a/examples/try_vasicek.py +++ b/examples/try_vasicek.py @@ -1,66 +1,61 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Try Vasicek Model +"""Try Vasicek Model.""" -""" -from __future__ import print_function, division +from __future__ import annotations import seaborn as sns from affidiff import Vasicek, VasicekParam -from affidiff.helper_functions import (plot_trajectories, plot_final_distr, - plot_realized, take_time) +from affidiff.helper_functions import plot_final_distr, plot_realized, plot_trajectories, take_time -def try_simulation(): - mean, kappa, eta = .5, .1, .2 - theta_true = VasicekParam(mean, kappa, eta) +def try_simulation() -> None: + """Try simulating and plotting Vasicek model.""" + mean, kappa, eta = 0.5, 0.1, 0.2 + theta_true = VasicekParam(mean=mean, kappa=kappa, eta=eta) vasicek = Vasicek(theta_true) x0, nperiods, nsub, ndiscr, nsim = 1, 500, 2, 10, 3 nobs = nperiods * nsub - paths = vasicek.simulate(x0, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim) + paths = vasicek.simulate(start=x0, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim) data = paths[:, 0, 0] - plot_trajectories(data, nsub, 'returns') + plot_trajectories(paths=data, nsub=nsub, names="returns") -def try_marginal(): - mean, kappa, eta = .5, .1, .2 - theta_true = VasicekParam(mean, kappa, eta) +def try_marginal() -> None: + """Try marginal distribution of Vasicek model.""" + mean, kappa, eta = 0.5, 0.1, 0.2 + theta_true = VasicekParam(mean=mean, kappa=kappa, eta=eta) vasicek = Vasicek(theta_true) x0, nperiods, nsub, ndiscr, nsim = mean, 500, 2, 10, 20 nobs = nperiods * nsub - paths = vasicek.simulate(x0, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim) + paths = vasicek.simulate(start=x0, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim) data = paths[:, :, 0] - plot_final_distr(data, 'returns') + plot_final_distr(paths=data, names="returns") -def try_sim_realized(): - mean, kappa, eta = .5, .1, .2 - theta_true = VasicekParam(mean, kappa, eta) +def try_sim_realized() -> None: + """Try simulated realized Vasicek model data.""" + mean, kappa, eta = 0.5, 0.1, 0.2 + theta_true = VasicekParam(mean=mean, kappa=kappa, eta=eta) vasicek = Vasicek(theta_true) - start, nperiods, nsub, ndiscr, nsim = 1, 500, 80, 1, 1 + start, nperiods, nsub, ndiscr, nsim = [1.0], 500, 80, 1, 1 aggh = 10 - returns, rvar = vasicek.sim_realized(start, nsub=nsub, - ndiscr=ndiscr, aggh=aggh, - nperiods=nperiods, nsim=nsim, diff=0) + returns, rvar = vasicek.sim_realized( + start=start, nsub=nsub, ndiscr=ndiscr, aggh=aggh, nperiods=nperiods, nsim=nsim, diff=0 + ) - plot_realized(returns, rvar) + plot_realized(returns=returns, rvar=rvar) -if __name__ == '__main__': - - sns.set_context('notebook') - with take_time('Marginal density'): +if __name__ == "__main__": + sns.set_context("notebook") + with take_time("Marginal density"): try_marginal() - with take_time('Simulation'): + with take_time("Simulation"): try_simulation() - with take_time('Simulate realized'): + with take_time("Simulate realized"): try_sim_realized() diff --git a/models.lyx b/models.lyx index e24ecf6..a10c6cd 100644 --- a/models.lyx +++ b/models.lyx @@ -107,7 +107,7 @@ Affine Diffusion Models status open \begin_layout Plain Layout -Python implementation can be found here: +Python implementation can be found here: \begin_inset CommandInset href LatexCommand href target "https://github.com/khrapovs/diffusions" @@ -130,7 +130,7 @@ status open \begin_layout Plain Layout Address: New Economic School, 100A Novaya Street, Skolkovo, Moscow, 143026 Russia. - E-mail: + E-mail: \begin_inset CommandInset href LatexCommand href target "khrapovs@gmail.com" @@ -153,35 +153,35 @@ Affine Diffusions \begin_layout Standard A diffusion process is a Markov process solving the stochastic differential equation -\begin_inset Formula +\begin_inset Formula \[ dY_{t}=\mu\left(Y_{t},\theta_{0}\right)dt+\sigma\left(Y_{t},\theta_{0}\right)dW_{t}. \] \end_inset -A discount-rate function +A discount-rate function \begin_inset Formula $R:D\to\mathbb{R}$ \end_inset is an affine function of the state -\begin_inset Formula +\begin_inset Formula \[ R\left(Y\right)=\rho_{0}+\rho_{1}\cdot Y, \] \end_inset -for +for \begin_inset Formula $\rho=\left(\rho_{0},\rho_{1}\right)\in\mathbb{R}\times\mathbb{R}^{N}$ \end_inset . - The affine dependence of the drift and diffusion coefficients of + The affine dependence of the drift and diffusion coefficients of \begin_inset Formula $Y$ \end_inset - are determined by coefficients + are determined by coefficients \begin_inset Formula $\left(K,H\right)$ \end_inset @@ -192,7 +192,7 @@ for \begin_inset Formula $\mu\left(Y\right)=K_{0}+K_{1}Y$ \end_inset -, for +, for \begin_inset Formula $K=\left(K_{0},K_{1}\right)\in\mathbb{R}^{N}\times\mathbb{R}^{N\times N}$ \end_inset @@ -203,7 +203,7 @@ for \begin_inset Formula $\left[\sigma\left(Y\right)\sigma\left(Y\right)^{\prime}\right]_{ij}=\left[H_{0}\right]_{ij}+\left[H_{1}\right]_{ij}\cdot Y$ \end_inset -, for +, for \begin_inset Formula $H=\left(H_{0},H_{1}\right)\in\mathbb{R}^{N\times N}\times\mathbb{R}^{N\times N\times N}$ \end_inset @@ -212,18 +212,18 @@ for \begin_layout Standard Here -\begin_inset Formula +\begin_inset Formula \[ \left[H_{1}\right]_{ij}\cdot Y=\sum_{k=1}^{N}\left[H_{1}\right]_{ijk}Y_{k}. \] \end_inset -A characteristic +A characteristic \begin_inset Formula $\chi=\left(K,H,\rho\right)$ \end_inset - captures both the distribution of + captures both the distribution of \begin_inset Formula $Y$ \end_inset @@ -231,7 +231,7 @@ A characteristic \end_layout \begin_layout Standard -Suppose we have +Suppose we have \begin_inset Quotes eld \end_inset @@ -239,17 +239,17 @@ intraday \begin_inset Quotes erd \end_inset - observations + observations \begin_inset Formula $Y_{t+\frac{j-1}{n}h,t+\frac{j}{n}h}$ \end_inset - for + for \begin_inset Formula $j=1,\ldots,n$ \end_inset . These observations can be used to compute realized variance (RV) that approxima -tes true integrated variance better than just squared +tes true integrated variance better than just squared \begin_inset Quotes eld \end_inset @@ -257,12 +257,12 @@ daily \begin_inset Quotes erd \end_inset - observations + observations \begin_inset Formula $Y_{t,t+h}^{2}$ \end_inset : -\begin_inset Formula +\begin_inset Formula \[ RV_{t,h}\equiv\frac{1}{h}\sum_{j=1}^{n}Y_{t+\frac{j-1}{n}h,t+\frac{j}{n}h}^{2}\overset{a.s.}{\longrightarrow}\frac{1}{h}\int_{t}^{t+h}\sigma^{2}\left(Y_{s},\theta_{0}\right)ds=\frac{1}{h}\int_{t}^{t+h}d\left[Y,Y\right]_{s}\equiv\mathcal{V}_{t,h}. \] @@ -282,19 +282,19 @@ Discretization and moments \begin_layout Plain Layout We can discretize the model as -\begin_inset Formula +\begin_inset Formula \[ Y_{t}\approx Y_{t-h}+\mu\left(Y_{t-h},\theta_{0}\right)h+\sigma\left(Y_{t-h},\theta_{0}\right)\sqrt{h}\varepsilon_{t}. \] \end_inset -To be more precise, we can integrate the diffusion on the interval +To be more precise, we can integrate the diffusion on the interval \begin_inset Formula $\left[t,t+h\right]$ \end_inset : -\begin_inset Formula +\begin_inset Formula \[ y_{t,t+h}=Y_{t+h}-Y_{t}=\int_{t}^{t+h}dY_{s}=\int_{t}^{t+h}\mu\left(Y_{s},\theta_{0}\right)ds+\int_{t}^{t+h}\sigma\left(Y_{s},\theta_{0}\right)dW_{s}. \] @@ -302,7 +302,7 @@ y_{t,t+h}=Y_{t+h}-Y_{t}=\int_{t}^{t+h}dY_{s}=\int_{t}^{t+h}\mu\left(Y_{s},\theta \end_inset Conditional mean and variance are thus -\begin_inset Formula +\begin_inset Formula \begin{eqnarray*} E_{t}\left[y_{t,t+h}\right] & = & E_{t}\left[\int_{t}^{t+h}\mu\left(Y_{s},\theta_{0}\right)ds\right],\\ V_{t}\left[y_{t,t+h}\right] & = & E_{t}\left[\int_{t}^{t+h}\sigma^{2}\left(Y_{s},\theta_{0}\right)ds\right]. @@ -311,7 +311,7 @@ V_{t}\left[y_{t,t+h}\right] & = & E_{t}\left[\int_{t}^{t+h}\sigma^{2}\left(Y_{s} \end_inset Hence, the moment function is -\begin_inset Formula +\begin_inset Formula \[ g\left(y_{t,t+h};\theta\right)=\left[\begin{array}{c} y_{t,t+h}-\int_{t}^{t+h}\mu\left(Y_{s},\theta_{0}\right)ds\\ @@ -322,7 +322,7 @@ y_{t,t+h}^{2}-\int_{t}^{t+h}\sigma^{2}\left(Y_{s},\theta_{0}\right)ds-\left(\int \end_inset with -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[g\left(y_{t,t+h};\theta\right)\right]=0. \] @@ -335,7 +335,7 @@ E_{t}\left[g\left(y_{t,t+h};\theta\right)\right]=0. \begin_layout Plain Layout Then, we can replace the second moment condition with the one corresponding to RV: -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[RV_{t,h}\right]=E_{t}\left[\frac{1}{h}\int_{t}^{t+h}\sigma^{2}\left(Y_{s},\theta_{0}\right)ds\right]. \] @@ -343,7 +343,7 @@ E_{t}\left[RV_{t,h}\right]=E_{t}\left[\frac{1}{h}\int_{t}^{t+h}\sigma^{2}\left(Y \end_inset So, the moment function becomes -\begin_inset Formula +\begin_inset Formula \[ g\left(y_{t,t+h},RV_{t,h};\theta\right)=\left[\begin{array}{c} y_{t,t+h}-\int_{t}^{t+h}\mu\left(Y_{s},\theta_{0}\right)ds\\ @@ -370,7 +370,7 @@ The model \end_layout \begin_layout Standard -Suppose that +Suppose that \begin_inset Formula $S_{t}$ \end_inset @@ -378,7 +378,7 @@ Suppose that \end_layout \begin_layout Standard -\begin_inset Formula +\begin_inset Formula \[ \frac{dS_{t}}{S_{t}}=\mu dt+\sigma dW_{t}. \] @@ -386,7 +386,7 @@ Suppose that \end_inset In logs: -\begin_inset Formula +\begin_inset Formula \[ d\log S_{t}=\left(\mu-\frac{1}{2}\sigma^{2}\right)dt+\sigma dW_{t}. \] @@ -402,7 +402,7 @@ AJD representation \begin_layout Standard Since the model belongs to affine class, we have -\begin_inset Formula +\begin_inset Formula \[ K_{0}=\mu-\frac{1}{2}\sigma^{2},\quad K_{1}=0,\quad H_{0}=\sigma^{2},\quad H_{1}=0. \] @@ -418,7 +418,7 @@ Exact solution \begin_layout Standard The solution is -\begin_inset Formula +\begin_inset Formula \[ r_{t,h}=\frac{1}{h}\log\frac{S_{t+h}}{S_{t}}=\left(\mu-\frac{1}{2}\sigma^{2}\right)+\frac{1}{h}\sigma\left(W_{t+h}-W_{t}\right). \] @@ -426,7 +426,7 @@ r_{t,h}=\frac{1}{h}\log\frac{S_{t+h}}{S_{t}}=\left(\mu-\frac{1}{2}\sigma^{2}\rig \end_inset In other words, -\begin_inset Formula +\begin_inset Formula \[ r_{t,h}\sim N\left(\mu-\frac{1}{2}\sigma^{2},\sigma^{2}\right). \] @@ -441,25 +441,25 @@ Moments \end_layout \begin_layout Standard -After integration on the interval +After integration on the interval \begin_inset Formula $\left[t,t+H\right]$ \end_inset : -\begin_inset Formula +\begin_inset Formula \[ r_{t,H}=\frac{1}{H}\log\frac{S_{t+H}}{S_{t}}=\mu-\frac{1}{2}\sigma^{2}+\frac{\sigma}{\sqrt{H}}\varepsilon_{t+H}, \] \end_inset -where +where \begin_inset Formula $\varepsilon_{t}\sim N\left(0,1\right)$ \end_inset . The first conditional moment is -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[r_{t,H}\right]=\mu-\frac{1}{2}\sigma^{2}. \] @@ -467,7 +467,7 @@ E_{t}\left[r_{t,H}\right]=\mu-\frac{1}{2}\sigma^{2}. \end_inset The second conditional moment is -\begin_inset Formula +\begin_inset Formula \[ V_{t}\left[r_{t,H}\right]=E_{t}\left[r_{t,H}^{2}\right]-\left(E_{t}\left[r_{t,H}\right]\right)^{2}=\sigma^{2}. \] @@ -475,7 +475,7 @@ V_{t}\left[r_{t,H}\right]=E_{t}\left[r_{t,H}^{2}\right]-\left(E_{t}\left[r_{t,H} \end_inset Hence, the moment function is -\begin_inset Formula +\begin_inset Formula \[ g\left(r_{t,H};\theta\right)=\left[\begin{array}{c} r_{t,H}-\left(\mu-\frac{1}{2}\sigma^{2}\right)\\ @@ -486,19 +486,19 @@ r_{t,H}^{2}-\sigma^{2}-\left(\mu-\frac{1}{2}\sigma^{2}\right)^{2} \end_inset with -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[g\left(r_{t,H};\theta\right)\right]=0. \] \end_inset -The derivative of the moment function with respect to model parameters +The derivative of the moment function with respect to model parameters \begin_inset Formula $\theta=\left[\mu,\sigma\right]$ \end_inset is -\begin_inset Formula +\begin_inset Formula \[ \frac{\partial g}{\partial\theta}\left(r_{t,H};\theta\right)=\left[\begin{array}{cc} -1 & \sigma\\ @@ -517,7 +517,7 @@ Integrated moments \begin_layout Standard It is trivial to deduce that the integrated variance is constant -\begin_inset Formula +\begin_inset Formula \[ \mathcal{V}_{t,H}=\sigma^{2}. \] @@ -525,7 +525,7 @@ It is trivial to deduce that the integrated variance is constant \end_inset Hence, the moment function is instead -\begin_inset Formula +\begin_inset Formula \[ g\left(r_{t,H},RV_{t,H};\theta\right)=X_{t,H}-C=\left[\begin{array}{c} r_{t,H}\\ @@ -540,11 +540,11 @@ RV_{t,H}^{2} \end_inset -Here we used the fact that +Here we used the fact that \begin_inset Formula $V_{t}\left[\mathcal{V}_{t,H}\right]=0$ \end_inset - and + and \begin_inset Formula $\left(E_{t}\left[\mathcal{V}_{t,H}\right]\right)^{2}=\sigma^{4}$ \end_inset @@ -561,7 +561,7 @@ The model \begin_layout Standard Consider -\begin_inset Formula +\begin_inset Formula \[ dr_{t}=\kappa\left(\mu-r_{t}\right)dt+\sigma dW_{t}. \] @@ -577,7 +577,7 @@ AJD representation \begin_layout Standard Since the model belongs to affine class, we have -\begin_inset Formula +\begin_inset Formula \[ K_{0}=\kappa\mu,\quad K_{1}=-\kappa,\quad H_{0}=\sigma^{2},\quad H_{1}=0. \] @@ -592,12 +592,12 @@ Exact solution \end_layout \begin_layout Standard -Using Ito's lemma for +Using Ito's lemma for \begin_inset Formula $r_{t}e^{\kappa t}$ \end_inset we have -\begin_inset Formula +\begin_inset Formula \[ d\left(r_{t}e^{\kappa t}\right)=\left(\kappa r_{t}e^{\kappa t}+\kappa\left(\mu-r_{t}\right)e^{\kappa t}\right)dt+\sigma e^{\kappa t}dW_{t}, \] @@ -605,7 +605,7 @@ d\left(r_{t}e^{\kappa t}\right)=\left(\kappa r_{t}e^{\kappa t}+\kappa\left(\mu-r \end_inset or -\begin_inset Formula +\begin_inset Formula \[ d\left(r_{t}e^{\kappa t}\right)=\kappa\mu e^{\kappa t}dt+\sigma e^{\kappa t}dW_{t}. \] @@ -613,7 +613,7 @@ d\left(r_{t}e^{\kappa t}\right)=\kappa\mu e^{\kappa t}dt+\sigma e^{\kappa t}dW_{ \end_inset After integration we have -\begin_inset Formula +\begin_inset Formula \[ r_{t+h}e^{\kappa\left(t+h\right)}=r_{t}e^{\kappa t}+\kappa\mu\int_{t}^{t+h}e^{\kappa u}du+\sigma\int_{t}^{t+h}e^{\kappa u}dW_{u}, \] @@ -621,7 +621,7 @@ r_{t+h}e^{\kappa\left(t+h\right)}=r_{t}e^{\kappa t}+\kappa\mu\int_{t}^{t+h}e^{\k \end_inset or -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}r_{t+h}= & r_{t}e^{-\kappa h}+\kappa\mu\int_{t}^{t+h}e^{-\kappa\left(t+h-u\right)}du+\sigma\int_{t}^{t+h}e^{-\kappa\left(t+h-u\right)}dW_{u}\\ = & r_{t}e^{-\kappa h}+\mu\left(1-e^{-\kappa h}\right)+\sigma\int_{t}^{t+h}e^{-\kappa\left(t+h-u\right)}dW_{u}. @@ -631,7 +631,7 @@ or \end_inset This implies that -\begin_inset Formula +\begin_inset Formula \[ r_{t+h}|r_{t}\sim N\left(r_{t}e^{-\kappa h}+\mu\left(1-e^{-\kappa h}\right),\sigma^{2}\int_{t}^{t+h}e^{-2\kappa\left(t+h-u\right)}du=\frac{\sigma^{2}}{2\kappa}\left(1-e^{-2\kappa h}\right)\right). \] @@ -652,19 +652,19 @@ The model \begin_layout Standard Cox, Ingersoll, and Ross (1985; Ecta) developed a theory of the term structure of interest rates in which the instantaneous short-term rate of interest, - + \begin_inset Formula $r$ \end_inset , follows the mean reverting diffusion -\begin_inset Formula +\begin_inset Formula \[ dr_{t}=\kappa\left(\mu-r_{t}\right)dt+\sigma\sqrt{r_{t}}dW_{t}. \] \end_inset -Feller condition for positivity of the process is +Feller condition for positivity of the process is \begin_inset Formula $\kappa\mu>\frac{1}{2}\sigma^{2}$ \end_inset @@ -677,7 +677,7 @@ AJD representation \begin_layout Standard Since the model belongs to affine class, we have -\begin_inset Formula +\begin_inset Formula \[ K_{0}=\kappa\mu,\quad K_{1}=-\kappa,\quad H_{0}=0,\quad H_{1}=\sigma^{2}. \] @@ -692,12 +692,12 @@ The solution \end_layout \begin_layout Standard -Using Ito's lemma for +Using Ito's lemma for \begin_inset Formula $r_{t}e^{\kappa t}$ \end_inset we have -\begin_inset Formula +\begin_inset Formula \[ r_{t+h}=r_{t}e^{-\kappa h}+\mu\left(1-e^{-\kappa h}\right)+\sigma\int_{t}^{t+h}e^{-\kappa\left(t+h-u\right)}\sqrt{r_{u}}dW_{u}. \] @@ -709,7 +709,7 @@ r_{t+h}=r_{t}e^{-\kappa h}+\mu\left(1-e^{-\kappa h}\right)+\sigma\int_{t}^{t+h}e \begin_layout Standard It can be shown that the transition density is known in closed form: -\begin_inset Formula +\begin_inset Formula \[ f\left(\left.r_{t+h}\right|r_{t};\theta\right)=ce^{-u-v}\left(\frac{u}{v}\right)^{q/2}I_{q}\left(2\sqrt{uv}\right), \] @@ -717,48 +717,48 @@ f\left(\left.r_{t+h}\right|r_{t};\theta\right)=ce^{-u-v}\left(\frac{u}{v}\right) \end_inset where -\begin_inset Formula +\begin_inset Formula \[ c=\frac{2\kappa}{\sigma^{2}\left(1-e^{-\kappa h}\right)},\quad u=cr_{t}e^{-\kappa h},\quad v=cr_{t+h},\quad q=\frac{2\kappa\mu}{\sigma^{2}}-1, \] \end_inset -and +and \begin_inset Formula $I_{q}$ \end_inset - is the modified Bessel function of the first kind of order + is the modified Bessel function of the first kind of order \begin_inset Formula $q$ \end_inset . - Sometimes, it is useful to work with a transformation + Sometimes, it is useful to work with a transformation \begin_inset Formula $x_{t+h}=2cr_{t+h}$ \end_inset . - We can derive that the transition density of + We can derive that the transition density of \begin_inset Formula $x_{t+h}$ \end_inset is -\begin_inset Formula +\begin_inset Formula \[ f\left(\left.x_{t+h}\right|x_{t};\theta\right)=f\left(\left.2cr_{t+h}\right|2cr_{t};\theta\right)=\frac{1}{2c}f\left(\left.r_{t+h}\right|r_{t};\theta\right), \] \end_inset -which is a noncentral +which is a noncentral \begin_inset Formula $\chi^{2}$ \end_inset - with + with \begin_inset Formula $2q+2$ \end_inset - degrees of freedom and noncentrality parameter + degrees of freedom and noncentrality parameter \begin_inset Formula $2u$ \end_inset @@ -771,7 +771,7 @@ Moments \begin_layout Standard This implies that -\begin_inset Formula +\begin_inset Formula \begin{eqnarray*} E_{t}\left[r_{t+h}\right] & = & r_{t}e^{-\kappa h}+\mu\left(1-e^{-\kappa h}\right),\\ V_{t}\left[r_{t+h}\right] & = & \frac{\sigma^{2}}{\kappa}r_{t}e^{-\kappa h}\left(1-e^{-\kappa h}\right)+\frac{\sigma^{2}}{2\kappa}\mu\left(1-e^{-\kappa h}\right)^{2}, @@ -780,7 +780,7 @@ V_{t}\left[r_{t+h}\right] & = & \frac{\sigma^{2}}{\kappa}r_{t}e^{-\kappa h}\left \end_inset since -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}V_{t}\left[r_{t+h}\right]= & \sigma^{2}\int_{t}^{t+h}e^{-2\kappa\left(t+h-u\right)}E_{t}\left[r_{u}\right]du\\ = & \sigma^{2}\int_{t}^{t+h}e^{-2\kappa\left(t+h-u\right)}\left(r_{t}e^{-\kappa\left(u-t\right)}+\mu\left(1-e^{-\kappa\left(u-t\right)}\right)\right)du. @@ -802,7 +802,7 @@ The model \begin_layout Standard Consider -\begin_inset Formula +\begin_inset Formula \begin{align*} dp_{t} & =\left(r+\left(\lambda_{r}-\frac{1}{2}\right)\sigma_{t}^{2}\right)dt+\sigma_{t}dW_{t}^{r},\\ d\sigma_{t}^{2} & =\kappa\left(\mu-\sigma_{t}^{2}\right)dt+\eta\sigma_{t}dW_{t}^{\sigma}, @@ -810,26 +810,26 @@ d\sigma_{t}^{2} & =\kappa\left(\mu-\sigma_{t}^{2}\right)dt+\eta\sigma_{t}dW_{t}^ \end_inset -with +with \begin_inset Formula $p_{t}=\log S_{t}$ \end_inset -, and +, and \begin_inset Formula $Corr\left[dW_{s}^{r},dW_{s}^{\sigma}\right]=\rho$ \end_inset -, or in other words +, or in other words \begin_inset Formula $W_{t}^{\sigma}=\rho W_{t}^{r}+\sqrt{1-\rho^{2}}W_{t}^{v}$ \end_inset . - Also let + Also let \begin_inset Formula $R\left(Y_{t}\right)=r$ \end_inset . Feller condition is -\begin_inset Formula +\begin_inset Formula \[ 2\kappa\mu>\eta^{2}. \] @@ -844,12 +844,12 @@ Risk-neutral model \end_layout \begin_layout Standard -Let the log stochastic discount factor (SDF) process +Let the log stochastic discount factor (SDF) process \begin_inset Formula $m_{t}=\log M_{t}$ \end_inset - be represented by the following SDE: -\begin_inset Formula + be represented by the following SDE: +\begin_inset Formula \[ dm_{t}=-rdt-\zeta_{r}dW_{t}^{r}-\zeta_{v}dW_{t}^{v}, \] @@ -857,7 +857,7 @@ dm_{t}=-rdt-\zeta_{r}dW_{t}^{r}-\zeta_{v}dW_{t}^{v}, \end_inset with -\begin_inset Formula +\begin_inset Formula \[ \left[\begin{array}{c} \zeta_{r}\\ @@ -871,7 +871,7 @@ with \end_inset The risk-neutral innovations are then -\begin_inset Formula +\begin_inset Formula \begin{eqnarray*} d\tilde{W}_{t}^{r} & = & dW_{t}^{r}+\zeta_{r}dt,\\ d\tilde{W}_{t}^{v} & = & dW_{t}^{v}+\zeta_{v}dt. @@ -879,8 +879,8 @@ d\tilde{W}_{t}^{v} & = & dW_{t}^{v}+\zeta_{v}dt. \end_inset -After the substitution -\begin_inset Formula +After the substitution +\begin_inset Formula \begin{align*} dp_{t} & =\left(r-\frac{1}{2}\sigma_{t}^{2}\right)dt+\sigma_{t}d\tilde{W}_{t}^{r},\\ d\sigma_{t}^{2} & =\tilde{\kappa}\left(\tilde{\mu}-\sigma_{t}^{2}\right)dt+\eta\sigma_{t}d\tilde{W}_{t}^{\sigma}, @@ -888,32 +888,32 @@ d\sigma_{t}^{2} & =\tilde{\kappa}\left(\tilde{\mu}-\sigma_{t}^{2}\right)dt+\eta\ \end_inset -with +with \begin_inset Formula $\tilde{W}_{t}^{\sigma}=\rho\tilde{W}_{t}^{r}+\sqrt{1-\rho^{2}}\tilde{W}_{t}^{v}$ \end_inset , and the modified parameters are -\begin_inset Formula +\begin_inset Formula \[ \tilde{\kappa}=\kappa-\lambda_{\sigma}\eta,\quad\tilde{\mu}=\mu\frac{\kappa}{\tilde{\kappa}}. \] \end_inset -Feller condition +Feller condition \begin_inset Formula $2\tilde{\kappa}\tilde{\mu}>\eta^{2}$ \end_inset - is equivalent to the one for physical measure, + is equivalent to the one for physical measure, \begin_inset Formula $2\mu\kappa>\eta^{2}$ \end_inset . - Still, we need + Still, we need \begin_inset Formula $\tilde{\kappa}>0$ \end_inset - which is equivalent to + which is equivalent to \begin_inset Formula $\lambda_{\sigma}<\kappa/\eta$ \end_inset @@ -926,7 +926,7 @@ AJD representation \begin_layout Standard The drift and diffusion functions are -\begin_inset Formula +\begin_inset Formula \[ \mu\left(Y_{t},\theta_{0}\right)=\left[\begin{array}{c} r\\ @@ -946,7 +946,7 @@ p_{t}\\ \end_inset It follows that -\begin_inset Formula +\begin_inset Formula \[ \sigma\left(Y_{t},\theta_{0}\right)\sigma\left(Y_{t},\theta_{0}\right)^{\prime}=\left[\begin{array}{cc} \sigma_{t}^{2} & \eta\rho\sigma_{t}^{2}\\ @@ -960,7 +960,7 @@ It follows that \end_inset So we have -\begin_inset Formula +\begin_inset Formula \[ \rho_{0}=r,\quad\rho_{1}=\left[0,0\right], \] @@ -968,7 +968,7 @@ So we have \end_inset and -\begin_inset Formula +\begin_inset Formula \[ K_{0}=\left[\begin{array}{c} r\\ @@ -982,7 +982,7 @@ r\\ \end_inset and -\begin_inset Formula +\begin_inset Formula \[ H_{0}=\left[\begin{array}{cc} 0 & 0\\ @@ -1010,12 +1010,12 @@ Integrated moments (alternative) \end_layout \begin_layout Plain Layout -The return equation can be integrated on +The return equation can be integrated on \begin_inset Formula $\left[t,t+h\right]$ \end_inset to obtain -\begin_inset Formula +\begin_inset Formula \[ r_{t,h}\equiv\frac{1}{h}\int_{t}^{t+h}dp_{s}=r+\left(\lambda-\frac{1}{2}\right)\mathcal{V}_{t,h}+\frac{1}{h}\int_{t}^{t+h}\sigma_{s}dW_{s}^{r}, \] @@ -1023,7 +1023,7 @@ r_{t,h}\equiv\frac{1}{h}\int_{t}^{t+h}dp_{s}=r+\left(\lambda-\frac{1}{2}\right)\ \end_inset The return first moment is -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[r_{t,h}\right]=r+\left(\lambda-\frac{1}{2}\right)E_{t}\left[\mathcal{V}_{t,h}\right]. \] @@ -1031,7 +1031,7 @@ E_{t}\left[r_{t,h}\right]=r+\left(\lambda-\frac{1}{2}\right)E_{t}\left[\mathcal{ \end_inset The unconditional mean is -\begin_inset Formula +\begin_inset Formula \[ E\left[r_{t,h}\right]=r+\left(\lambda-\frac{1}{2}\right)\mu. \] @@ -1042,8 +1042,8 @@ E\left[r_{t,h}\right]=r+\left(\lambda-\frac{1}{2}\right)\mu. \end_layout \begin_layout Plain Layout -The spot volatility equation can be integrated to get -\begin_inset Formula +The spot volatility equation can be integrated to get +\begin_inset Formula \[ E_{t}\left[\sigma_{t+h}^{2}\right]=C_{h}+A_{h}\sigma_{t}^{2}, \] @@ -1051,23 +1051,23 @@ E_{t}\left[\sigma_{t+h}^{2}\right]=C_{h}+A_{h}\sigma_{t}^{2}, \end_inset where I define coefficients as -\begin_inset Formula +\begin_inset Formula \[ A_{h}=\exp\left(-\kappa h\right),\quad C_{h}=\mu\left(1-A_{h}\right). \] \end_inset -This could be integrated further over +This could be integrated further over \begin_inset Formula $h$ \end_inset - on + on \begin_inset Formula $\left[0,h\right]$ \end_inset to obtain -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[h\mathcal{V}_{t,h}\right]=c_{h}+a_{h}\sigma_{t}^{2}, \] @@ -1075,19 +1075,19 @@ E_{t}\left[h\mathcal{V}_{t,h}\right]=c_{h}+a_{h}\sigma_{t}^{2}, \end_inset with -\begin_inset Formula +\begin_inset Formula \[ a_{h}=\int_{0}^{h}A_{s}ds=\frac{1}{\kappa}\left(1-A_{h}\right),\quad c_{h}=\mu\left(h-a_{h}\right). \] \end_inset -Applying Ito's lemma to +Applying Ito's lemma to \begin_inset Formula $E_{t}\left[h\mathcal{V}_{t,h}\right]$ \end_inset we obtain -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}dE_{t}\left[h\mathcal{V}_{t,h}\right]= & \left[a_{h}\kappa\left(\mu-\sigma_{t}^{2}\right)-\frac{\partial c_{h}}{\partial h}-\frac{\partial a_{h}}{\partial h}\sigma_{t}^{2}\right]dt+a_{h}\eta\sigma_{t}dW_{t}^{\sigma}\\ = & \left[\left(1-A_{h}\right)\left(\mu-\sigma_{t}^{2}\right)-\mu\left(1-A_{h}\right)-A_{h}\sigma_{t}^{2}\right]dt+a_{h}\eta\sigma_{t}dW_{t}^{\sigma}\\ @@ -1098,7 +1098,7 @@ Applying Ito's lemma to \end_inset After integration -\begin_inset Formula +\begin_inset Formula \[ 0=E_{t+h}\left[h\mathcal{V}_{t+h,0}\right]=E_{t}\left[h\mathcal{V}_{t,h}\right]-\int_{t}^{t+h}\sigma_{s}^{2}ds+\eta\int_{t}^{t+h}a_{t+h-s}\sigma_{s}dW_{s}^{\sigma}. \] @@ -1106,7 +1106,7 @@ After integration \end_inset Hence, -\begin_inset Formula +\begin_inset Formula \[ h\mathcal{V}_{t,h}-E_{t}\left[h\mathcal{V}_{t,h}\right]=\eta\int_{t}^{t+h}a_{t+h-s}\sigma_{s}dW_{s}^{\sigma}. \] @@ -1114,7 +1114,7 @@ h\mathcal{V}_{t,h}-E_{t}\left[h\mathcal{V}_{t,h}\right]=\eta\int_{t}^{t+h}a_{t+h \end_inset This allows to compute the conditional variance -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(h\mathcal{V}_{t,h}-E_{t}\left[h\mathcal{V}_{t,h}\right]\right)^{2}\right]=\eta^{2}\int_{t}^{t+h}a_{t+h-s}^{2}E_{t}\left[\sigma_{s}^{2}\right]ds. \] @@ -1135,7 +1135,7 @@ Integrated moments \begin_layout Standard Define average excess return as -\begin_inset Formula +\begin_inset Formula \[ \tilde{r}_{t,H}\equiv\frac{1}{H}\int_{t}^{t+H}dp_{s}-r. \] @@ -1146,15 +1146,15 @@ Define average excess return as \end_layout \begin_layout Standard -Given observations on average excess returns +Given observations on average excess returns \begin_inset Formula $\tilde{r}_{t,H}$ \end_inset - and realized volatility + and realized volatility \begin_inset Formula $RV_{t,H}$ \end_inset - we have the following four integrated moments (the proof is given in + we have the following four integrated moments (the proof is given in \begin_inset CommandInset ref LatexCommand formatted reference "sec:Integrated-moments-Heston" @@ -1162,7 +1162,7 @@ reference "sec:Integrated-moments-Heston" \end_inset ): -\begin_inset Formula +\begin_inset Formula \begin{align*} E_{t}\left[\left(1-A_{h}L\right)\mathcal{V}_{t+h,H}\right] & =C_{1},\\ E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\mathcal{V}_{t+2h,H}^{2}\right] & =C_{2},\\ @@ -1173,7 +1173,7 @@ E_{t}\left[\left(1-A_{h}L\right)\left(\tilde{r}_{t+h,H}\mathcal{V}_{t+h,H}-\left \end_inset or in matrix notation -\begin_inset Formula +\begin_inset Formula \[ E_{t-2h}\left[\left(\begin{array}{cccc} \left(1-A_{h}L\right)L & \cdot & \cdot & \cdot\\ @@ -1190,13 +1190,13 @@ E_{t-2h}\left[\left(\begin{array}{cccc} \end_inset -Define the data vector as +Define the data vector as \begin_inset Formula $X_{t,H}=\left[\mathcal{V}_{t,H},\mathcal{V}_{t,H}^{2},\tilde{r}_{t,H},\tilde{r}_{t,H}\mathcal{V}_{t,H}\right]$ \end_inset . - Then, the moment conditions can be written more compactly as -\begin_inset Formula + Then, the moment conditions can be written more compactly as +\begin_inset Formula \[ E_{t-2h}\left[A\left(L\right)X_{t,H}\right]=C, \] @@ -1204,7 +1204,7 @@ E_{t-2h}\left[A\left(L\right)X_{t,H}\right]=C, \end_inset with the lag polynomial -\begin_inset Formula +\begin_inset Formula \[ A\left(L\right)=A_{0}+A_{1}L+A_{2}L^{2}, \] @@ -1212,7 +1212,7 @@ A\left(L\right)=A_{0}+A_{1}L+A_{2}L^{2}, \end_inset and -\begin_inset Formula +\begin_inset Formula \[ A_{0}=\left(\begin{array}{cccc} \cdot & \cdot & \cdot & \cdot\\ @@ -1235,7 +1235,7 @@ A_{0}=\left(\begin{array}{cccc} \end_inset To make this one single matrix product, we can write -\begin_inset Formula +\begin_inset Formula \[ E_{t-2h}\left[\left(\begin{array}{ccc} A_{0} & A_{1} & A_{2}\end{array}\right)\left(\begin{array}{c} @@ -1248,7 +1248,7 @@ L^{2}X_{t,H} \end_inset where -\begin_inset Formula +\begin_inset Formula \[ C=\left(A_{0}+A_{1}+A_{2}\right)E\left[X_{t,H}\right], \] @@ -1256,7 +1256,7 @@ C=\left(A_{0}+A_{1}+A_{2}\right)E\left[X_{t,H}\right], \end_inset and -\begin_inset Formula +\begin_inset Formula \begin{eqnarray*} E\left[\mathcal{V}_{t,H}\right] & = & \mu,\\ E\left[\mathcal{V}_{t,H}^{2}\right] & = & \frac{\eta^{2}}{\kappa^{2}}\frac{c_{H}}{H}+\mu^{2},\\ @@ -1270,7 +1270,7 @@ E\left[\tilde{r}_{t,H}\mathcal{V}_{t,H}\right] & = & \rho\frac{\eta}{\kappa}\fra \end_layout \begin_layout Section -Central Tendency +Central Tendency \end_layout \begin_layout Subsection @@ -1279,7 +1279,7 @@ The model \begin_layout Standard Consider -\begin_inset Formula +\begin_inset Formula \begin{align*} dp_{t} & =\left(r+\left(\lambda_{r}-\frac{1}{2}\right)\sigma_{t}^{2}\right)dt+\sigma_{t}dW_{t}^{r},\\ d\sigma_{t}^{2} & =\kappa_{\sigma}\left(y_{t}^{2}-\sigma_{t}^{2}\right)dt+\eta_{\sigma}\sigma_{t}dW_{t}^{\sigma},\\ @@ -1288,20 +1288,20 @@ dy_{t}^{2} & =\kappa_{y}\left(\mu-y_{t}^{2}\right)dt+\eta_{y}v_{t}dW_{t}^{y}, \end_inset -with +with \begin_inset Formula $p_{t}=\log S_{t}$ \end_inset -, and +, and \begin_inset Formula $Corr\left[dW_{s}^{r},dW_{s}^{\sigma}\right]=\rho$ \end_inset -, or in other words +, or in other words \begin_inset Formula $W_{t}^{\sigma}=\rho W_{t}^{r}+\sqrt{1-\rho^{2}}W_{t}^{y}$ \end_inset . - Also let + Also let \begin_inset Formula $R\left(Y_{t}\right)=r$ \end_inset @@ -1313,12 +1313,12 @@ Risk-neutral model \end_layout \begin_layout Standard -Let the log stochastic discount factor (SDF) process +Let the log stochastic discount factor (SDF) process \begin_inset Formula $m_{t}=\log M_{t}$ \end_inset - be represented by the following SDE: -\begin_inset Formula + be represented by the following SDE: +\begin_inset Formula \[ dm_{t}=-rdt-\zeta_{r}dW_{t}^{r}-\zeta_{v}dW_{t}^{v}-\zeta_{y}dW_{t}^{y}. \] @@ -1326,7 +1326,7 @@ dm_{t}=-rdt-\zeta_{r}dW_{t}^{r}-\zeta_{v}dW_{t}^{v}-\zeta_{y}dW_{t}^{y}. \end_inset with -\begin_inset Formula +\begin_inset Formula \[ \left[\begin{array}{c} \zeta_{r}\\ @@ -1342,7 +1342,7 @@ with \end_inset The risk-neutral innovations are then -\begin_inset Formula +\begin_inset Formula \begin{eqnarray*} d\tilde{W}_{t}^{r} & = & dW_{t}^{r}+\zeta_{r}dt,\\ d\tilde{W}_{t}^{v} & = & dW_{t}^{v}+\zeta_{v}dt,\\ @@ -1351,8 +1351,8 @@ d\tilde{W}_{t}^{y} & = & dW_{t}^{y}+\zeta_{y}dt. \end_inset -After the substitution -\begin_inset Formula +After the substitution +\begin_inset Formula \begin{align*} dp_{t} & =\left(r-\frac{1}{2}\sigma_{t}^{2}\right)dt+\sigma_{t}d\tilde{W}_{t}^{r},\\ d\sigma_{t}^{2} & =\tilde{\kappa}_{\sigma}\left(\frac{\kappa_{\sigma}}{\tilde{\kappa}_{\sigma}}y_{t}^{2}-\sigma_{t}^{2}\right)dt+\eta_{\sigma}\sigma_{t}d\tilde{W}_{t}^{\sigma},\\ @@ -1361,32 +1361,32 @@ dy_{t}^{2} & =\tilde{\kappa}_{y}\left(\tilde{\mu}-y_{t}^{2}\right)dt+\eta_{y}y_{ \end_inset -with +with \begin_inset Formula $\tilde{W}_{t}^{\sigma}=\rho\tilde{W}_{t}^{r}+\sqrt{1-\rho^{2}}\tilde{W}_{t}^{v}$ \end_inset , and the modified parameters are -\begin_inset Formula +\begin_inset Formula \[ \tilde{\kappa}_{\sigma}=\kappa_{\sigma}-\lambda_{\sigma}\eta_{\sigma},\quad\tilde{\kappa}_{y}=\kappa_{y}-\lambda_{y}\eta_{y},\quad\tilde{\mu}=\mu\frac{\kappa_{y}}{\tilde{\kappa}_{y}}. \] \end_inset -Feller condition +Feller condition \begin_inset Formula $2\tilde{\kappa}\tilde{\mu}>\eta^{2}$ \end_inset - is equivalent to the one for physical measure, + is equivalent to the one for physical measure, \begin_inset Formula $2\mu\kappa>\eta^{2}$ \end_inset . - Still, we need + Still, we need \begin_inset Formula $\tilde{\kappa}>0$ \end_inset - which is equivalent to + which is equivalent to \begin_inset Formula $\lambda_{\sigma}<\kappa/\eta$ \end_inset @@ -1396,7 +1396,7 @@ Feller condition \begin_layout Standard Note that the unconditional means of the processes at hand under the physical measure are the same: -\begin_inset Formula +\begin_inset Formula \[ E^{P}\left[\sigma_{t}^{2}\right]=E^{P}\left[y_{t}^{2}\right]=\mu, \] @@ -1404,7 +1404,7 @@ E^{P}\left[\sigma_{t}^{2}\right]=E^{P}\left[y_{t}^{2}\right]=\mu, \end_inset while under the risk-neutral measure, they are different: -\begin_inset Formula +\begin_inset Formula \[ E^{Q}\left[y_{t}^{2}\right]=\tilde{\mu}=\mu\frac{\kappa_{y}}{\kappa_{y}-\lambda_{y}\eta_{y}}>\mu, \] @@ -1412,7 +1412,7 @@ E^{Q}\left[y_{t}^{2}\right]=\tilde{\mu}=\mu\frac{\kappa_{y}}{\kappa_{y}-\lambda_ \end_inset and -\begin_inset Formula +\begin_inset Formula \[ E^{Q}\left[\sigma_{t}^{2}\right]=E^{Q}\left[y_{t}^{2}\right]\frac{\kappa_{\sigma}}{\tilde{\kappa}_{\sigma}}=\mu\frac{\kappa_{y}}{\kappa_{y}-\lambda_{y}\eta_{y}}\frac{\kappa_{\sigma}}{\kappa_{\sigma}-\lambda_{\sigma}\eta_{\sigma}}>\tilde{\mu}. \] @@ -1421,7 +1421,7 @@ E^{Q}\left[\sigma_{t}^{2}\right]=E^{Q}\left[y_{t}^{2}\right]\frac{\kappa_{\sigma Given that all of the parameters above are non-negative, we can write the following inequality: -\begin_inset Formula +\begin_inset Formula \[ E^{P}\left[\sigma_{t}^{2}\right]=E^{P}\left[y_{t}^{2}\right]\leq E^{Q}\left[y_{t}^{2}\right]\leq E^{Q}\left[\sigma_{t}^{2}\right]. \] @@ -1437,7 +1437,7 @@ AJD representation \begin_layout Standard The drift and diffusion functions are -\begin_inset Formula +\begin_inset Formula \[ \mu\left(Y_{t},\theta_{0}\right)=\left[\begin{array}{c} r\\ @@ -1461,7 +1461,7 @@ y_{t}^{2} \end_inset It follows that -\begin_inset Formula +\begin_inset Formula \[ \sigma\left(Y_{t},\theta_{0}\right)\sigma\left(Y_{t},\theta_{0}\right)^{\prime}=\left[\begin{array}{ccc} \sigma_{t}^{2} & \eta_{\sigma}\rho\sigma_{t}^{2} & 0\\ @@ -1481,7 +1481,7 @@ It follows that \end_inset So we have -\begin_inset Formula +\begin_inset Formula \[ \rho_{0}=r,\quad\rho_{1}=\left[0,0,0\right], \] @@ -1489,7 +1489,7 @@ So we have \end_inset and -\begin_inset Formula +\begin_inset Formula \[ K_{0}=\left[\begin{array}{c} r\\ @@ -1505,7 +1505,7 @@ r\\ \end_inset and -\begin_inset Formula +\begin_inset Formula \[ H_{0}=\left[\mathbf{0}_{3\times3}\right],\quad H_{1,1}=\left[\mathbf{0}_{3\times3}\right],\quad H_{1,2}=\left[\begin{array}{ccc} 1 & \eta_{\sigma}\rho & 0\\ @@ -1529,7 +1529,7 @@ Integrated moments \begin_layout Standard Define average excess return as -\begin_inset Formula +\begin_inset Formula \[ \tilde{r}_{t,H}\equiv\frac{1}{H}\int_{t}^{t+H}dp_{s}-r. \] @@ -1540,15 +1540,15 @@ Define average excess return as \end_layout \begin_layout Standard -Given observations on average excess returns +Given observations on average excess returns \begin_inset Formula $\tilde{r}_{t,H}$ \end_inset - and realized volatility + and realized volatility \begin_inset Formula $RV_{t,H}$ \end_inset - we have the following four integrated moments (the proof is given in + we have the following four integrated moments (the proof is given in \begin_inset CommandInset ref LatexCommand formatted reference "sec:Integrated-moments-CT" @@ -1556,7 +1556,7 @@ reference "sec:Integrated-moments-CT" \end_inset ): -\begin_inset Formula +\begin_inset Formula \begin{align*} E_{t}\left[\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)\mathcal{V}_{t+2h,H}\right] & =C_{1},\\ E_{t}\left[\left(1-A_{2h}^{\sigma}L\right)\left(1-A_{2h}^{y}L\right)\left(1-A_{h}^{\sigma}A_{h}^{y}L\right)\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)\mathcal{V}_{t+5h,H}^{2}\right] & =C_{2},\\ @@ -1567,7 +1567,7 @@ E_{t}\left[\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)\left(\tilde{r \end_inset or in matrix notation -\begin_inset Formula +\begin_inset Formula \[ E_{t-5h}\left[\left(\begin{array}{cccc} M_{1}L^{3} & \cdot & \cdot & \cdot\\ @@ -1585,7 +1585,7 @@ M_{1}L^{3} & \cdot & \cdot & \cdot\\ \end_inset where -\begin_inset Formula +\begin_inset Formula \begin{eqnarray*} M_{1} & = & \left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right),\\ M_{2} & = & M_{1}\left(1-A_{h}^{\sigma}A_{h}^{y}L\right)\left(1-A_{2h}^{y}L\right)\left(1-A_{2h}^{\sigma}L\right), @@ -1593,13 +1593,13 @@ M_{2} & = & M_{1}\left(1-A_{h}^{\sigma}A_{h}^{y}L\right)\left(1-A_{2h}^{y}L\righ \end_inset -Define the data vector as +Define the data vector as \begin_inset Formula $X_{t,H}=\left[\mathcal{V}_{t,H},\mathcal{V}_{t,H}^{2},\tilde{r}_{t,H},\tilde{r}_{t,H}\mathcal{V}_{t,H}\right]$ \end_inset . - Then, the moment conditions can be written more compactly as -\begin_inset Formula + Then, the moment conditions can be written more compactly as +\begin_inset Formula \[ E_{t-5h}\left[A\left(L\right)X_{t,H}\right]=C, \] @@ -1607,7 +1607,7 @@ E_{t-5h}\left[A\left(L\right)X_{t,H}\right]=C, \end_inset with the lag polynomial -\begin_inset Formula +\begin_inset Formula \[ A\left(L\right)=A_{0}+A_{1}L+A_{2}L^{2}+A_{3}L^{3}+A_{4}L^{4}+A_{5}L^{5}, \] @@ -1615,7 +1615,7 @@ A\left(L\right)=A_{0}+A_{1}L+A_{2}L^{2}+A_{3}L^{3}+A_{4}L^{4}+A_{5}L^{5}, \end_inset and -\begin_inset Formula +\begin_inset Formula \[ A_{0}=\left(\begin{array}{cccc} \cdot & \cdot & \cdot & \cdot\\ @@ -1638,7 +1638,7 @@ A_{0}=\left(\begin{array}{cccc} \end_inset -\begin_inset Formula +\begin_inset Formula \[ A_{3}=\left(\begin{array}{cccc} m_{1,0} & \cdot & \cdot & \cdot\\ @@ -1656,7 +1656,7 @@ m_{1,1} & \cdot & \cdot & \cdot\\ \end_inset -\begin_inset Formula +\begin_inset Formula \[ A_{5}=\left(\begin{array}{cccc} m_{1,2} & \cdot & \cdot & \cdot\\ @@ -1669,7 +1669,7 @@ m_{1,2} & \cdot & \cdot & \cdot\\ \end_inset Here -\begin_inset Formula +\begin_inset Formula \[ m_{1,0}=1,\quad m_{1,1}=-\left(A_{h}^{y}+A_{h}^{\sigma}\right),\quad m_{1,2}=A_{h}^{y}A_{h}^{\sigma}, \] @@ -1682,7 +1682,7 @@ and for the second polynomial the coefficients are found accordingly to status open \begin_layout Plain Layout -See e.g.: +See e.g.: \begin_inset CommandInset href LatexCommand href target "http://math.stackexchange.com/questions/88917/relation-betwen-coefficients-and-roots-of-a-polynomial" @@ -1696,7 +1696,7 @@ target "http://math.stackexchange.com/questions/88917/relation-betwen-coefficien . To make this one single matrix product, we can write -\begin_inset Formula +\begin_inset Formula \[ E_{t-2h}\left[\left(\begin{array}{cccccc} A_{0} & A_{1} & A_{2} & A_{3} & A_{4} & A_{5}\end{array}\right)\left(\begin{array}{c} @@ -1712,7 +1712,7 @@ L^{5}X_{t,H} \end_inset where -\begin_inset Formula +\begin_inset Formula \[ C=\left(A_{0}+A_{1}+A_{2}+A_{3}+A_{4}+A_{5}\right)E\left[X_{t,H}\right], \] @@ -1720,7 +1720,7 @@ C=\left(A_{0}+A_{1}+A_{2}+A_{3}+A_{4}+A_{5}\right)E\left[X_{t,H}\right], \end_inset and -\begin_inset Formula +\begin_inset Formula \begin{eqnarray*} E\left[\mathcal{V}_{t,H}\right] & = & \mu,\\ E\left[\mathcal{V}_{t,H}^{2}\right] & = & \mu^{2}+\left(a_{H}^{\sigma}\right)^{2}V\left[\sigma_{t}^{2}\right]+\left(b_{H}^{\sigma}\right)^{2}V\left[y_{t}^{2}\right]+V\left[\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\right],\\ @@ -1756,20 +1756,20 @@ Integrated moments for Heston model \end_layout \begin_layout Standard -The spot volatility equation can be integrated on the interval +The spot volatility equation can be integrated on the interval \begin_inset Formula $\left[t,t+h\right]$ \end_inset - with + with \begin_inset Formula $h$ \end_inset - possibly different from + possibly different from \begin_inset Formula $H$ \end_inset - to get -\begin_inset Formula + to get +\begin_inset Formula \begin{equation} \sigma_{t+h}^{2}=C_{h}+A_{h}\sigma_{t}^{2}+\epsilon_{t,h}^{\sigma},\label{eq:spot_vol_equation} \end{equation} @@ -1777,7 +1777,7 @@ The spot volatility equation can be integrated on the interval \end_inset where I define coefficients and the integrated innovation as -\begin_inset Formula +\begin_inset Formula \[ A_{h}=\exp\left(-\kappa h\right),\quad C_{h}=\mu\left(1-A_{h}\right),\quad\epsilon_{t,h}^{\sigma}=\eta_{\sigma}\int_{t}^{t+h}\sigma_{u}A_{t+h-u}dW_{u}^{\sigma}. \] @@ -1789,19 +1789,19 @@ A_{h}=\exp\left(-\kappa h\right),\quad C_{h}=\mu\left(1-A_{h}\right),\quad\epsil \begin_layout Standard The spot volatility first moment is -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\sigma_{t+h}^{2}\right]=C_{h}+A_{h}\sigma_{t}^{2}, \] \end_inset -or, using lag operator +or, using lag operator \begin_inset Formula $L$ \end_inset , -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(1-A_{h}L\right)\sigma_{t+h}^{2}\right]=\left(1-A_{h}\right)E\left[\sigma_{t}^{2}\right]=\left(1-A_{h}\right)\mu. \] @@ -1812,12 +1812,12 @@ E_{t}\left[\left(1-A_{h}L\right)\sigma_{t+h}^{2}\right]=\left(1-A_{h}\right)E\le \end_layout \begin_layout Standard -Given the above definitions it is also not hard to guess that +Given the above definitions it is also not hard to guess that \begin_inset Formula $\sigma_{t}^{4}$ \end_inset is autoregressive: -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\sigma_{t+2h}^{4}\right]=C_{1}=\left(1-A_{h}\right)\left(1-A_{h}^{2}\right)E\left[\sigma_{t}^{4}\right] \] @@ -1825,7 +1825,7 @@ E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\sigma_{t+2h}^{4}\right \end_inset with -\begin_inset Formula +\begin_inset Formula \[ E\left[\sigma_{t}^{4}\right]=V\left[\sigma_{t}^{2}\right]+\left(E\left[\sigma_{t}^{2}\right]\right)^{2}=\frac{\mu\eta^{2}}{2\kappa}+\mu^{2}. \] @@ -1841,7 +1841,7 @@ status open \begin_layout Plain Layout The conditional variance of the spot volatility is -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}V_{t}\left[\sigma_{t+h}^{2}\right]= & V_{t}\left[\epsilon_{t,h}^{\sigma}\right]\\ = & \eta^{2}\int_{t}^{t+h}E_{t}\left[\sigma_{u}^{2}\right]A_{t+h-u}^{2}du\\ @@ -1855,7 +1855,7 @@ The conditional variance of the spot volatility is \end_inset with -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}D_{1}= & \eta^{2}\int_{0}^{h}A_{u}A_{h-u}^{2}du=\eta^{2}\int_{0}^{h}A_{2h-u}du=\frac{\eta^{2}}{\kappa}A_{h}\left(1-A_{h}\right),\\ F_{1}= & \eta^{2}\int_{0}^{h}C_{u}A_{h-u}^{2}du=\mu\eta^{2}\int_{0}^{h}\left(A_{2h-2u}-A_{3h-2u}\right)du=\frac{\mu\eta^{2}}{2\kappa}\left(1-A_{2h}\right)\left(1-A_{h}\right). @@ -1865,7 +1865,7 @@ F_{1}= & \eta^{2}\int_{0}^{h}C_{u}A_{h-u}^{2}du=\mu\eta^{2}\int_{0}^{h}\left(A_{ \end_inset Hence, the second moment of the spot volatility is -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\sigma_{t+h}^{4}\right]=F_{1}+C_{h}^{2}+\left(D_{1}+2A_{h}C_{h}\right)\sigma_{t}^{2}+A_{h}^{2}\sigma_{t}^{4}. \] @@ -1873,23 +1873,23 @@ E_{t}\left[\sigma_{t+h}^{4}\right]=F_{1}+C_{h}^{2}+\left(D_{1}+2A_{h}C_{h}\right \end_inset Using the lag operator we can make rewrite this equation as -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(1-A_{h}^{2}L\right)\sigma_{t+h}^{4}\right]=F_{1}+C_{h}^{2}+\left(D_{1}+2A_{h}C_{h}\right)\sigma_{t}^{2}. \] \end_inset -Multiplying this equation by +Multiplying this equation by \begin_inset Formula $\left(1-A_{h}L\right)$ \end_inset - and moving the reference point by + and moving the reference point by \begin_inset Formula $h$ \end_inset we obtain -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\sigma_{t+2h}^{4}\right]=\left(1-A_{h}\right)\left(F_{1}+C_{h}^{2}\right)+\left(D_{1}+2A_{h}C_{h}\right)C_{h}\equiv R_{1}. \] @@ -1905,23 +1905,23 @@ E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\sigma_{t+2h}^{4}\right \end_layout \begin_layout Standard -After repeated integration of the volatility equation +After repeated integration of the volatility equation \begin_inset CommandInset ref LatexCommand eqref reference "eq:spot_vol_equation" \end_inset - over + over \begin_inset Formula $t$ \end_inset - as a dummy of the integration on the interval + as a dummy of the integration on the interval \begin_inset Formula $\left[t,t+H\right]$ \end_inset we obtain -\begin_inset Formula +\begin_inset Formula \[ \mathcal{V}_{t+h,H}=C_{h}+A_{h}\mathcal{V}_{t,H}+\frac{1}{H}\int_{0}^{H}\epsilon_{t+s,h}^{\sigma}ds. \] @@ -1929,7 +1929,7 @@ reference "eq:spot_vol_equation" \end_inset Hence, the integrated volatility first moment is -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(1-A_{h}L\right)\mathcal{V}_{t+h,H}\right]=\left(1-A_{h}\right)E\left[\mathcal{V}_{t,H}\right]=\left(1-A_{h}\right)\mu. \] @@ -1940,23 +1940,23 @@ E_{t}\left[\left(1-A_{h}L\right)\mathcal{V}_{t+h,H}\right]=\left(1-A_{h}\right)E \end_layout \begin_layout Standard -We can also integrate the volatility equation +We can also integrate the volatility equation \begin_inset CommandInset ref LatexCommand eqref reference "eq:spot_vol_equation" \end_inset - using + using \begin_inset Formula $h$ \end_inset - as a dummy of integration on the interval from + as a dummy of integration on the interval from \begin_inset Formula $\left[0,H\right]$ \end_inset -: -\begin_inset Formula +: +\begin_inset Formula \begin{equation} \mathcal{V}_{t,H}=c_{H}+a_{H}\sigma_{t}^{2}+\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds,\label{eq:int_vol_equation} \end{equation} @@ -1964,7 +1964,7 @@ reference "eq:spot_vol_equation" \end_inset with -\begin_inset Formula +\begin_inset Formula \[ a_{H}=\frac{1}{H}\int_{0}^{H}A_{s}ds=\frac{1}{\kappa H}\left(1-A_{H}\right),\quad c_{H}=\mu\left(1-a_{H}\right). \] @@ -1972,7 +1972,7 @@ a_{H}=\frac{1}{H}\int_{0}^{H}A_{s}ds=\frac{1}{\kappa H}\left(1-A_{H}\right),\qua \end_inset The error term may be simplified: -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds= & \frac{1}{H}\int_{0}^{H}\left(\eta\int_{t}^{t+s}\sigma_{u}A_{t+s-u}dW_{u}^{\sigma}\right)ds\\ = & \frac{\eta}{H}\int_{t}^{t+H}\sigma_{u}\left(\int_{u-t}^{H}A_{t+s-u}ds\right)dW_{u}^{\sigma}\\ @@ -1984,26 +1984,26 @@ The error term may be simplified: \end_inset From this we can guess that -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\mathcal{V}_{t+2h,H}^{2}\right]=C_{2}=\left(1-A_{h}\right)\left(1-A_{h}^{2}\right)E\left[\mathcal{V}_{t,H}^{2}\right]. \] \end_inset -From the equation +From the equation \begin_inset CommandInset ref LatexCommand eqref reference "eq:int_vol_equation" \end_inset - for + for \begin_inset Formula $\mathcal{V}_{t,H}$ \end_inset we can derive -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}V\left[\mathcal{V}_{t,H}\right]= & a_{H}^{2}V\left[\sigma_{t}^{2}\right]+V\left[\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\right]=a_{H}^{2}\frac{\mu\eta^{2}}{2\kappa}+V\left[\eta\int_{t}^{t+H}\sigma_{u}a_{t+H-u}dW_{u}^{\sigma}\right]\\ = & a_{H}^{2}\frac{\mu\eta^{2}}{2\kappa}+E\left[\eta^{2}\int_{t}^{t+H}\sigma_{u}^{2}a_{t+H-u}^{2}du\right]=a_{H}^{2}\frac{\mu\eta^{2}}{2\kappa}+\mu\eta^{2}\int_{0}^{H}a_{H-u}^{2}du\\ @@ -2018,7 +2018,7 @@ reference "eq:int_vol_equation" \end_inset Hence, -\begin_inset Formula +\begin_inset Formula \[ E\left[\mathcal{V}_{t,H}^{2}\right]=V\left[\mathcal{V}_{t,H}\right]+\left(E\left[\mathcal{V}_{t,H}\right]\right)^{2}=\frac{\eta^{2}}{\kappa^{2}}\frac{c_{H}}{H}+\mu^{2}. \] @@ -2034,7 +2034,7 @@ status open \begin_layout Plain Layout Taking conditional expectation of the integrated equation we obtain -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\mathcal{V}_{t,h}\right]=c_{h}+a_{h}\sigma_{t}^{2}. \] @@ -2042,7 +2042,7 @@ E_{t}\left[\mathcal{V}_{t,h}\right]=c_{h}+a_{h}\sigma_{t}^{2}. \end_inset The conditional variance of integrated volatility is -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}V_{t}\left[\mathcal{V}_{t,h}\right]= & V_{t}\left[\frac{1}{h}\int_{0}^{h}\epsilon_{t,s}^{\sigma}ds\right]\\ = & \frac{\eta^{2}}{h^{2}}\int_{t}^{t+h}E_{t}\left[\sigma_{u}^{2}\right]a_{t+h-u}^{2}du\\ @@ -2055,7 +2055,7 @@ The conditional variance of integrated volatility is \end_inset with -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}D_{2}= & \eta^{2}\int_{0}^{h}A_{u}a_{h-u}^{2}du\\ = & \frac{\eta^{2}}{\kappa^{2}h^{2}}\int_{0}^{h}\left(A_{u}-2A_{h}+A_{2h-u}\right)du\\ @@ -2067,7 +2067,7 @@ with \end_inset and -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}F_{2}= & \frac{\eta^{2}}{h^{2}}\int_{0}^{h}C_{u}a_{h-u}^{2}du\\ = & \frac{\mu\eta^{2}}{\kappa^{2}h^{2}}\int_{0}^{h}\left(1-A_{u}\right)\left(1-2A_{h-u}+A_{2h-2u}\right)du\\ @@ -2080,19 +2080,19 @@ and \end_inset Hence, the second moment is -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\mathcal{V}_{t,h}^{2}\right]=F_{2}+\frac{c_{h}^{2}}{h^{2}}+\left(D_{2}+\frac{2a_{h}c_{h}}{h^{2}}\right)\sigma_{t}^{2}+\frac{a_{h}^{2}}{h^{2}}\sigma_{t}^{4}. \] \end_inset -Multiply this by +Multiply this by \begin_inset Formula $\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)$ \end_inset and use previously derived conditional moments to obtain -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\mathcal{V}_{t+2h,h}^{2}\right]= & \left(1-A_{h}\right)\left(1-A_{h}^{2}\right)\left(F_{2}+\frac{c_{h}^{2}}{h^{2}}\right)+\left(D_{2}+\frac{2a_{h}c_{h}}{h^{2}}\right)\left(1-A_{h}^{2}\right)C_{h}+\frac{a_{h}^{2}}{h^{2}}R_{1}\\ \equiv & R_{2}. @@ -2110,12 +2110,12 @@ Multiply this by \end_layout \begin_layout Standard -The return equation can be integrated on +The return equation can be integrated on \begin_inset Formula $\left[t,t+H\right]$ \end_inset to obtain -\begin_inset Formula +\begin_inset Formula \[ \tilde{r}_{t,h}\equiv\frac{1}{H}\int_{t}^{t+H}dp_{s}-r=\left(\lambda-\frac{1}{2}\right)\mathcal{V}_{t,H}+\frac{1}{H}\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}, \] @@ -2123,7 +2123,7 @@ The return equation can be integrated on \end_inset The return first conditional moment is -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\tilde{r}_{t,H}\right]=\left(\lambda-\frac{1}{2}\right)E_{t}\left[\mathcal{V}_{t,H}\right], \] @@ -2167,7 +2167,7 @@ kappa_{y}} \noun default \color inherit -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\tilde{r}_{t,H}-\left(\lambda-\frac{1}{2}\right)\mathcal{V}_{t,H}\right]=C_{3}=0. \] @@ -2179,7 +2179,7 @@ E_{t}\left[\tilde{r}_{t,H}-\left(\lambda-\frac{1}{2}\right)\mathcal{V}_{t,H}\rig \begin_layout Standard Now multiply return by integrated volatility and take the expectation: -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\tilde{r}_{t,H}\mathcal{V}_{t,H}-\left(\lambda-\frac{1}{2}\right)\mathcal{V}_{t,H}^{2}\right]=E_{t}\left[\mathcal{V}_{t,H}\frac{1}{H}\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right]. \] @@ -2187,7 +2187,7 @@ E_{t}\left[\tilde{r}_{t,H}\mathcal{V}_{t,H}-\left(\lambda-\frac{1}{2}\right)\mat \end_inset We can guess that -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(1-A_{h}L\right)\left(\tilde{r}_{t,H}\mathcal{V}_{t,H}-\left(\lambda-\frac{1}{2}\right)\mathcal{V}_{t,H}^{2}\right)\right]=\left(1-A_{h}\right)E\left[\mathcal{V}_{t,H}\frac{1}{H}\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right]. \] @@ -2195,7 +2195,7 @@ E_{t}\left[\left(1-A_{h}L\right)\left(\tilde{r}_{t,H}\mathcal{V}_{t,H}-\left(\la \end_inset The last term is -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}E\left[\mathcal{V}_{t,H}\frac{1}{H}\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right]= & E\left[\frac{1}{H^{2}}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right]\\ = & \frac{\eta}{H}E\left[\left(\int_{t}^{t+H}\sigma_{u}a_{t+H-u}dW_{u}^{\sigma}\right)\left(\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right)\right]\\ @@ -2216,7 +2216,7 @@ status open \begin_layout Plain Layout The last term can be modified as follows: -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}E_{t}\left[\mathcal{V}_{t,h}\frac{1}{h}\int_{t}^{t+h}\sigma_{s}dW_{s}^{r}\right]= & \frac{1}{h}E_{t}\left[\frac{1}{h}\int_{0}^{h}\epsilon_{t,s}^{\sigma}ds\int_{t}^{t+h}\sigma_{s}dW_{s}^{r}\right]\\ = & \frac{\eta}{h^{2}}E_{t}\left[\left(\int_{t}^{t+h}\sigma_{u}a_{t+h-u}dW_{u}^{\sigma}\right)\left(\int_{t}^{t+h}\sigma_{s}dW_{s}^{r}\right)\right]\\ @@ -2231,7 +2231,7 @@ The last term can be modified as follows: \end_inset with -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}D_{3}= & \frac{\rho\eta}{h^{2}}\int_{t}^{t+h}A_{u-t}a_{t+h-u}du\\ = & \frac{\rho\eta}{h^{2}}\int_{0}^{h}A_{u}a_{h-u}du\\ @@ -2243,7 +2243,7 @@ with \end_inset and -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}F_{3}= & \frac{\rho\eta}{h^{2}}\int_{t}^{t+h}C_{u-t}a_{t+h-u}du\\ = & \frac{\rho\eta}{h^{2}}\int_{0}^{h}C_{u}a_{h-u}du\\ @@ -2255,12 +2255,12 @@ and \end_inset -Multiply conditional cross-moment by +Multiply conditional cross-moment by \begin_inset Formula $\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)$ \end_inset and use previously derived conditional moments to obtain -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)r_{t+2h,h}\mathcal{V}_{t+2h,h}\right]= & \left(\lambda-\frac{1}{2}\right)E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\mathcal{V}_{t+2h,h}^{2}\right]\\ & +E_{t}\left[\left(1-A_{h}L\right)\left(1-A_{h}^{2}L\right)\left(D_{3}\sigma_{t+2h}^{2}+F_{3}\right)\right]\\ @@ -2290,7 +2290,7 @@ Integrated moments for Central Tendency model \begin_layout Standard The volaitlity processes can be discretized to obtain -\begin_inset Formula +\begin_inset Formula \begin{equation} \begin{aligned}\sigma_{t+h}^{2} & = & A_{h}^{\sigma}\sigma_{t}^{2}+B_{h}^{\sigma}y_{t}^{2}+C_{h}^{\sigma}+\epsilon_{t,h}^{\sigma},\\ y_{t+h}^{2} & = & A_{h}^{y}y_{t}^{2}+C_{h}^{y}+\epsilon_{t,h}^{y}. @@ -2301,7 +2301,7 @@ y_{t+h}^{2} & = & A_{h}^{y}y_{t}^{2}+C_{h}^{y}+\epsilon_{t,h}^{y}. \end_inset with -\begin_inset Formula +\begin_inset Formula \[ A_{h}^{\sigma}=\exp\left(-\kappa_{\sigma}h\right),\quad B_{h}^{\sigma}=\frac{\kappa_{\sigma}}{\kappa_{\sigma}-\kappa_{y}}\left(A_{h}^{y}-A_{h}^{\sigma}\right),\quad C_{h}^{\sigma}=\mu\left(1-A_{h}^{\sigma}-B_{h}^{\sigma}\right), \] @@ -2309,7 +2309,7 @@ A_{h}^{\sigma}=\exp\left(-\kappa_{\sigma}h\right),\quad B_{h}^{\sigma}=\frac{\ka \end_inset and -\begin_inset Formula +\begin_inset Formula \[ A_{h}^{y}=\exp\left(-\kappa_{y}h\right),\quad C_{h}^{y}=\mu\left(1-A_{h}^{y}\right). \] @@ -2317,7 +2317,7 @@ A_{h}^{y}=\exp\left(-\kappa_{y}h\right),\quad C_{h}^{y}=\mu\left(1-A_{h}^{y}\rig \end_inset The same system can be written using lag operators as -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}\left(1-A_{h}^{\sigma}L\right)\sigma_{t+h}^{2} & = & B_{h}^{\sigma}y_{t}^{2}+C_{h}^{\sigma}+\epsilon_{t,h}^{\sigma},\\ \left(1-A_{h}^{y}L\right)y_{t+h}^{2} & = & C_{h}^{y}+\epsilon_{t,h}^{y}. @@ -2326,20 +2326,20 @@ The same system can be written using lag operators as \end_inset -Note that +Note that \begin_inset Formula $A_{h}^{y}$ \end_inset - and + and \begin_inset Formula $A_{h}^{\sigma}$ \end_inset - are multiplicative functions of time interval, that is + are multiplicative functions of time interval, that is \begin_inset Formula $A_{h_{1}}^{y}A_{h_{2}}^{y}=A_{h_{1}+h_{2}}^{y}$ \end_inset -.The error structure is represented by -\begin_inset Formula +.The error structure is represented by +\begin_inset Formula \begin{eqnarray*} \epsilon_{t,h}^{\sigma} & = & \eta_{\sigma}\int_{t}^{t+h}\sigma_{u}A_{t+h-u}^{\sigma}dW_{u}^{\sigma}+\eta_{y}\int_{t}^{t+h}y_{u}B_{t+h-u}^{\sigma}dW_{u}^{y},\\ \epsilon_{t,h}^{v} & = & \eta_{y}\int_{t}^{t+h}y_{u}A_{t+h-u}^{y}dW_{u}^{y}. @@ -2347,18 +2347,18 @@ Note that \end_inset -Clearly, +Clearly, \begin_inset Formula $E_{t}^{P}\left[\epsilon_{t,t+h}^{\sigma}\right]=0$ \end_inset -, and +, and \begin_inset Formula $E_{t}^{P}\left[\epsilon_{t,t+h}^{y}\right]=0$ \end_inset . Note that the same processes may be represented as infinite stochastic integrals with respect to Brownian motion only: -\begin_inset Formula +\begin_inset Formula \begin{equation} \begin{aligned}y_{t}^{2} & =\mu+\eta_{y}\int_{-\infty}^{t}y_{u}A_{t-u}^{y}dW_{u}^{y},\\ \sigma_{t}^{2} & =\mu+\eta_{y}\int_{-\infty}^{t}y_{u}B_{t-u}^{\sigma}dW_{u}^{y}+\eta_{\sigma}\int_{-\infty}^{t}\sigma_{u}A_{t-u}^{\sigma}dW_{u}^{\sigma}. @@ -2373,7 +2373,7 @@ Clearly, \begin_layout Standard Next I define integrated variance and central tendency as -\begin_inset Formula +\begin_inset Formula \begin{equation} \mathcal{V}_{t,H}\equiv\frac{1}{H}\int_{t}^{t+H}\sigma_{u}^{2}du,\quad\mathcal{Y}_{t,H}\equiv\frac{1}{H}\int_{t}^{t+H}y_{u}^{2}du,\label{eq:integrated_values} \end{equation} @@ -2385,31 +2385,31 @@ where the first subscripted value denotes the beginning of the time interval, \end_layout \begin_layout Standard -In order to move from instantaneous vector +In order to move from instantaneous vector \begin_inset Formula $\left(\sigma_{t}^{2},y_{t}\right)$ \end_inset - to integrated analog + to integrated analog \begin_inset Formula $\left(\mathcal{V}_{t,H},\mathcal{Y}_{t,H}\right)$ \end_inset -, I integrate the linear system in +, I integrate the linear system in \begin_inset CommandInset ref LatexCommand formatted reference "eq:sigma_y_var" \end_inset - over + over \begin_inset Formula $t$ \end_inset - as a dummy of the integration in the interval + as a dummy of the integration in the interval \begin_inset Formula $\left[0,H\right]$ \end_inset with the following result -\begin_inset Formula +\begin_inset Formula \begin{equation} \begin{aligned}\mathcal{V}_{t+h,H} & = & A_{h}^{\sigma}\mathcal{V}_{t,H}+B_{h}^{\sigma}\mathcal{Y}_{t,H}+C_{h}^{\sigma}+\frac{1}{H}\int_{0}^{H}\epsilon_{t+s,h}^{\sigma}ds,\\ \mathcal{Y}_{t+h,H} & = & A_{h}^{y}\mathcal{Y}_{t,H}+C_{h}^{y}+\frac{1}{H}\int_{0}^{H}\epsilon_{t+s,h}^{y}ds. @@ -2419,12 +2419,12 @@ reference "eq:sigma_y_var" \end_inset -Using the lag operator +Using the lag operator \begin_inset Formula $L$ \end_inset and taking the conditional expectation, this system may be written as -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}E_{t}^{P}\left[\left(1-A_{h}^{\sigma}L\right)\mathcal{V}_{t+h,H}\right] & = & B_{h}^{\sigma}E_{t}^{P}\left[\mathcal{Y}_{t,H}\right]+C_{h}^{\sigma},\\ E_{t}^{P}\left[\left(1-A_{h}^{y}L\right)\mathcal{Y}_{t+h,H}\right] & = & C_{h}^{y}. @@ -2433,16 +2433,16 @@ E_{t}^{P}\left[\left(1-A_{h}^{y}L\right)\mathcal{Y}_{t+h,H}\right] & = & C_{h}^{ \end_inset -Multiply the first equation by +Multiply the first equation by \begin_inset Formula $\left(1-A_{h}^{y}L\right)$ \end_inset -, shift the time by +, shift the time by \begin_inset Formula $h$ \end_inset , and make a substitution from the second equation to obtain -\begin_inset Formula +\begin_inset Formula \[ E_{t}^{P}\left[\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)\mathcal{V}_{t+2h,H}\right]=C_{1}, \] @@ -2450,7 +2450,7 @@ E_{t}^{P}\left[\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)\mathcal{V \end_inset where -\begin_inset Formula +\begin_inset Formula \[ C_{2}=\left(1-A_{h}^{y}\right)\left(1-A_{h}^{\sigma}\right)E^{P}\left[\mathcal{V}_{t,H}\right]=\left(1-A_{h}^{y}\right)\left(1-A_{h}^{\sigma}\right)\mu. \] @@ -2461,28 +2461,28 @@ C_{2}=\left(1-A_{h}^{y}\right)\left(1-A_{h}^{\sigma}\right)E^{P}\left[\mathcal{V \end_layout \begin_layout Standard -In +In \begin_inset CommandInset ref LatexCommand formatted reference "eq:sigma_y_var" \end_inset - replace + replace \begin_inset Formula $h$ \end_inset - by another time indicator + by another time indicator \begin_inset Formula $s$ \end_inset - and integrate from 0 to + and integrate from 0 to \begin_inset Formula $H$ \end_inset which leads to the following expression for integrated volatility in terms - of spot variables -\begin_inset Formula + of spot variables +\begin_inset Formula \[ \mathcal{V}_{t,H}=c_{H}^{\sigma}+a_{H}^{\sigma}\sigma_{t}^{2}+b_{H}^{\sigma}y_{t}^{2}+\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds, \] @@ -2490,7 +2490,7 @@ reference "eq:sigma_y_var" \end_inset where I denote -\begin_inset Formula +\begin_inset Formula \begin{eqnarray*} a_{H}^{\sigma} & = & \frac{1}{H}\int_{0}^{H}A_{s}^{\sigma}ds=\frac{1}{\kappa_{\sigma}H}\left(1-A_{H}^{\sigma}\right),\\ b_{H}^{\sigma} & = & \frac{1}{H}\int_{0}^{H}B_{s}^{\sigma}ds=\frac{\kappa_{\sigma}}{\kappa_{\sigma}-\kappa_{y}}\frac{1}{H}\int_{0}^{H}\left(A_{s}^{y}-A_{s}^{\sigma}\right)ds=\frac{\kappa_{\sigma}}{\kappa_{\sigma}-\kappa_{y}}\left(a_{H}^{y}-a_{H}^{\sigma}\right),\\ @@ -2499,58 +2499,58 @@ c_{H}^{\sigma} & = & \mu\left(1-a_{H}^{\sigma}-b_{H}^{\sigma}\right). \end_inset -Clearly, the second moment of +Clearly, the second moment of \begin_inset Formula $\mathcal{V}_{t,H}$ \end_inset - will be a function of + will be a function of \begin_inset Formula $\sigma_{t}^{2}$ \end_inset -, +, \begin_inset Formula $y_{t}^{2}$ \end_inset -, +, \begin_inset Formula $\sigma_{t}^{4}$ \end_inset -, +, \begin_inset Formula $y_{t}^{4}$ \end_inset -, and +, and \begin_inset Formula $\sigma_{t}^{2}y_{t}^{2}$ \end_inset . - In order to eliminate the first two, we will need to apply + In order to eliminate the first two, we will need to apply \begin_inset Formula $\left(1-A_{h}^{\sigma}L\right)$ \end_inset - and + and \begin_inset Formula $\left(1-A_{h}^{y}L\right)$ \end_inset . - The squared central tendency + The squared central tendency \begin_inset Formula $y_{t}^{4}$ \end_inset is a function of itself in the past and itself squared, so it can be eliminated - using + using \begin_inset Formula $\left(1-A_{h}^{y}L\right)\left(1-\left(A_{h}^{y}\right)^{2}L\right)$ \end_inset . The square volaitlity will be a function of all the above and can be eliminated - using + using \begin_inset Formula $\left(1-\left(A_{h}^{\sigma}\right)^{2}L\right)\left(1-A_{h}^{\sigma}A_{h}^{y}L\right)\left(1-\left(A_{h}^{y}\right)^{2}L\right)\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)$ \end_inset . Hence, -\begin_inset Formula +\begin_inset Formula \[ E_{t}^{P}\left[\left(1-\left(A_{h}^{\sigma}\right)^{2}L\right)\left(1-\left(A_{h}^{y}\right)^{2}L\right)\left(1-A_{h}^{\sigma}A_{h}^{y}L\right)\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)\mathcal{V}_{t+5h,H}^{2}\right]=C_{2}, \] @@ -2558,7 +2558,7 @@ E_{t}^{P}\left[\left(1-\left(A_{h}^{\sigma}\right)^{2}L\right)\left(1-\left(A_{h \end_inset where -\begin_inset Formula +\begin_inset Formula \[ C_{3}=\left(1-\left(A_{h}^{\sigma}\right)^{2}\right)\left(1-A_{h}^{\sigma}A_{h}^{y}\right)\left(1-\left(A_{h}^{y}\right)^{2}\right)\left(1-A_{h}^{y}\right)\left(1-A_{h}^{\sigma}\right)E^{P}\left[\mathcal{V}_{t,H}^{2}\right]. \] @@ -2570,14 +2570,14 @@ C_{3}=\left(1-\left(A_{h}^{\sigma}\right)^{2}\right)\left(1-A_{h}^{\sigma}A_{h}^ \begin_layout Standard The unconditional variance of integrated volatility is -\begin_inset Formula +\begin_inset Formula \[ V\left[\mathcal{V}_{t,H}\right]=\left(a_{H}^{\sigma}\right)^{2}V\left[\sigma_{t}^{2}\right]+\left(b_{H}^{\sigma}\right)^{2}V\left[y_{t}^{2}\right]+V\left[\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\right]. \] \end_inset -Directly from +Directly from \begin_inset CommandInset ref LatexCommand eqref reference "eq:infinite-rep" @@ -2585,7 +2585,7 @@ reference "eq:infinite-rep" \end_inset we see that -\begin_inset Formula +\begin_inset Formula \[ E\left[y_{t}^{4}\right]=\mu\eta_{y}^{2}\int_{-\infty}^{t}\left(A_{t-u}^{y}\right)^{2}du=\mu\eta_{y}^{2}\int_{-\infty}^{t}e^{-2\kappa_{y}\left(t-u\right)}du=\frac{\mu\eta_{y}^{2}}{2\kappa_{y}}, \] @@ -2593,7 +2593,7 @@ E\left[y_{t}^{4}\right]=\mu\eta_{y}^{2}\int_{-\infty}^{t}\left(A_{t-u}^{y}\right \end_inset and -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}E\left[\sigma_{t}^{4}\right]= & \mu\eta_{y}^{2}\int_{-\infty}^{t}\left(B_{t-u}^{\sigma}\right)^{2}du+\mu\eta_{\sigma}^{2}\int_{-\infty}^{t}\left(A_{t-u}^{\sigma}\right)^{2}du\\ = & \mu\eta_{y}^{2}\left(\frac{\kappa_{\sigma}}{\kappa_{\sigma}-\kappa_{y}}\right)^{2}\int_{-\infty}^{t}\left(A_{2\left(t-u\right)}^{y}-2A_{t-u}^{y}A_{t-u}^{\sigma}+A_{2\left(t-u\right)}^{\sigma}\right)du+\mu\eta_{\sigma}^{2}\int_{-\infty}^{t}A_{2\left(t-u\right)}^{\sigma}du\\ @@ -2606,7 +2606,7 @@ and \end_inset Rewrite the error: -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds= & \eta_{\sigma}\frac{1}{H}\int_{0}^{H}\int_{t}^{t+s}\sigma_{u}A_{t+s-u}^{\sigma}dW_{u}^{\sigma}ds+\eta_{y}\frac{1}{H}\int_{0}^{H}\int_{t}^{t+s}y_{u}B_{t+s-u}^{\sigma}dW_{u}^{y}ds\\ = & \eta_{\sigma}\int_{t}^{t+H}\sigma_{u}a_{t+H-u}^{\sigma}dW_{u}^{\sigma}+\eta_{y}\int_{t}^{t+H}y_{u}b_{t+H-u}^{\sigma}dW_{u}^{y}\\ @@ -2617,7 +2617,7 @@ Rewrite the error: \end_inset Hence, -\begin_inset Formula +\begin_inset Formula \[ V\left[\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\right]=\mu\eta_{\sigma}^{2}\int_{0}^{H}\left(a_{H-u}^{\sigma}\right)^{2}du+\mu\eta_{y}^{2}\int_{0}^{H}\left(b_{H-u}^{\sigma}\right)^{2}du \] @@ -2632,7 +2632,7 @@ V\left[\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\right]=\mu\eta_{\sigma}^ status collapsed \begin_layout Plain Layout -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}V\left[\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\right]= & \mu\eta_{\sigma}^{2}\int_{0}^{H}\left(a_{H-u}^{\sigma}\right)^{2}du+\mu\eta_{y}^{2}\int_{0}^{H}\left(b_{H-u}^{\sigma}\right)^{2}du\\ = & \frac{\mu\eta_{\sigma}^{2}}{\kappa_{\sigma}^{2}H^{2}}\int_{0}^{H}\left(1-A_{H-u}^{\sigma}\right)^{2}du\\ @@ -2664,12 +2664,12 @@ status collapsed \end_layout \begin_layout Standard -The return equation can be integrated on +The return equation can be integrated on \begin_inset Formula $\left[t,t+H\right]$ \end_inset to obtain -\begin_inset Formula +\begin_inset Formula \[ \tilde{r}_{t,h}\equiv\frac{1}{H}\int_{t}^{t+H}dp_{s}-r=\left(\lambda_{r}-\frac{1}{2}\right)\mathcal{V}_{t,H}+\frac{1}{H}\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}, \] @@ -2677,7 +2677,7 @@ The return equation can be integrated on \end_inset The return first conditional moment is -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\tilde{r}_{t,H}\right]=\left(\lambda-\frac{1}{2}\right)E_{t}\left[\mathcal{V}_{t,H}\right], \] @@ -2685,7 +2685,7 @@ E_{t}\left[\tilde{r}_{t,H}\right]=\left(\lambda-\frac{1}{2}\right)E_{t}\left[\ma \end_inset or -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\tilde{r}_{t,H}-\left(\lambda_{r}-\frac{1}{2}\right)\mathcal{V}_{t,H}\right]=C_{3}=0. \] @@ -2697,7 +2697,7 @@ E_{t}\left[\tilde{r}_{t,H}-\left(\lambda_{r}-\frac{1}{2}\right)\mathcal{V}_{t,H} \begin_layout Standard Now multiply return by integrated volatility and take the expectation: -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\tilde{r}_{t,H}\mathcal{V}_{t,H}-\left(\lambda-\frac{1}{2}\right)\mathcal{V}_{t,H}^{2}\right]=E_{t}\left[\mathcal{V}_{t,H}\frac{1}{H}\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right]. \] @@ -2705,7 +2705,7 @@ E_{t}\left[\tilde{r}_{t,H}\mathcal{V}_{t,H}-\left(\lambda-\frac{1}{2}\right)\mat \end_inset We can guess that -\begin_inset Formula +\begin_inset Formula \[ E_{t}\left[\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)\left(\tilde{r}_{t,H}\mathcal{V}_{t,H}-\left(\lambda-\frac{1}{2}\right)\mathcal{V}_{t,H}^{2}\right)\right]=C_{4}, \] @@ -2713,7 +2713,7 @@ E_{t}\left[\left(1-A_{h}^{y}L\right)\left(1-A_{h}^{\sigma}L\right)\left(\tilde{r \end_inset where -\begin_inset Formula +\begin_inset Formula \[ C_{4}=\left(1-A_{h}^{y}\right)\left(1-A_{h}^{\sigma}\right)E\left[\mathcal{V}_{t,H}\frac{1}{H}\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right]. \] @@ -2721,7 +2721,7 @@ C_{4}=\left(1-A_{h}^{y}\right)\left(1-A_{h}^{\sigma}\right)E\left[\mathcal{V}_{t \end_inset The last term is -\begin_inset Formula +\begin_inset Formula \[ \begin{aligned}E^{P}\left[\mathcal{V}_{t,H}\frac{1}{H}\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right]= & \frac{1}{H^{2}}E^{P}\left[\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right]\\ = & \eta_{\sigma}\frac{1}{H^{2}}E^{P}\left[\left(\int_{0}^{H}\int_{t}^{t+s}\sigma_{u}A_{t+s-u}^{\sigma}dW_{u}^{\sigma}ds\right)\left(\int_{t}^{t+H}\sigma_{s}dW_{s}^{r}\right)\right]\\ diff --git a/pyproject.toml b/pyproject.toml index 2fa3fe1..9ffb600 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,36 +1,72 @@ [project] name = "affidiff" description = "Affine Diffusions. Simulation and estimation." -authors = [{ name = "Stanislav Khrapov", email = "khrapovs@gmail.com" }] readme = "README.md" requires-python = ">=3.14" +authors = [{ name = "Stanislav Khrapov", email = "khrapovs@gmail.com" }] dependencies = [ - "matplotlib>=3.10.8", - "mygmm>=0.1.0", - "numpy>=2.4.4", - "scipy>=1.17.1", - "seaborn>=0.13.2", - "statsmodels>=0.14.6", + "matplotlib>=3.10.8", + "mygmm>=0.1.0", + "numpy>=2.4.4", + "scipy>=1.17.1", + "seaborn>=0.13.2", + "statsmodels>=0.14.6", ] dynamic = ["version"] [dependency-groups] dev = [ - "pytest>=9.0.3", + "prek>=0.4.11", + "pytest>=9.0.3", + "pytest-cov>=7.1.0", ] [build-system] requires = ["hatchling", "uv-dynamic-versioning"] build-backend = "hatchling.build" -[tool.hatch.version] -source = "uv-dynamic-versioning" - -[tool.uv-dynamic-versioning] -fallback-version = "0.0.0" - [tool.commitizen] version_scheme = "semver" version_provider = "scm" update_changelog_on_bump = true tag_format = "v$version" + +[tool.hatch.version] +source = "uv-dynamic-versioning" + +[tool.pytest.ini_options] +addopts = "--cov-config=.coveragerc" +testpaths = ["tests", "src"] + +[tool.ruff] +line-length = 120 +src = ["src", "tests"] + +[tool.ruff.lint] +select = ["E", "F", "D", "B", "I", "ARG", "ANN"] +ignore = [ + "D100", + "D101", + "D102", + "D103", + "D104", + "D105", + "D106", + "D107", + "D213", + "D417", +] +preview = true +extend-select = ["PLR0917"] + +[tool.ruff.lint.pylint] +max-positional-args = 1 + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ty.rules] +no-matching-overload = "ignore" + +[tool.uv-dynamic-versioning] +fallback-version = "0.0.0" diff --git a/setup.py b/setup.py index b73c057..d1db715 100644 --- a/setup.py +++ b/setup.py @@ -1,40 +1,51 @@ -#!/usr/bin/env python -from setuptools import setup, find_packages, Extension -from Cython.Distutils import build_ext import numpy +from Cython.Distutils import build_ext # type: ignore +from setuptools import Extension, find_packages, setup # type: ignore -with open('README.rst') as file: +with open("README.rst") as file: long_description = file.read() -kwargs = {'libraries': [], 'include_dirs': [numpy.get_include()]} +kwargs = {"libraries": [], "include_dirs": [numpy.get_include()]} -ext_modules = [Extension('affidiff.simulate', - ['./src/affidiff/simulate.pyx'], **kwargs)] +ext_modules = [Extension("affidiff.simulate", ["./src/affidiff/simulate.pyx"], **kwargs)] -setup(name='diffusions', - version='1.0', - description='Affine Diffusions. Simulation and estimation.', - long_description=long_description, - author='Stanislav Khrapov', - author_email='khrapovs@gmail.com', - url='https://github.com/khrapovs/diffusions', - license='MIT', - packages=find_packages(), - ext_modules=ext_modules, - cmdclass={'build_ext': build_ext}, - zip_safe=False, - keywords=['diffusion', 'econometrics', 'estimation', 'affine', - 'CIR', 'Vasicek', 'Brownian motion', 'SDE', 'GBM', - 'Heston', 'volatility', 'stochastic', 'central tendency', - 'GMM'], - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: End Users/Desktop', - 'Intended Audience :: Financial and Insurance Industry', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - ], - ) +setup( + name="diffusions", + version="1.0", + description="Affine Diffusions. Simulation and estimation.", + long_description=long_description, + author="Stanislav Khrapov", + author_email="khrapovs@gmail.com", + url="https://github.com/khrapovs/diffusions", + license="MIT", + packages=find_packages(), + ext_modules=ext_modules, + cmdclass={"build_ext": build_ext}, + zip_safe=False, + keywords=[ + "diffusion", + "econometrics", + "estimation", + "affine", + "CIR", + "Vasicek", + "Brownian motion", + "SDE", + "GBM", + "Heston", + "volatility", + "stochastic", + "central tendency", + "GMM", + ], + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Financial and Insurance Industry", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + ], +) diff --git a/src/AGENTS.md b/src/AGENTS.md new file mode 100644 index 0000000..902988e --- /dev/null +++ b/src/AGENTS.md @@ -0,0 +1,27 @@ +# Purpose + +`src/` is the root container directory for the `affidiff` Python production package. + +# Ownership + +Owns all production Python source modules, Cython extension code, and package initialization logic under `src/`. + +# Local Contracts + +- All production code must be placed inside the `src/affidiff` package directory. +- `src/affidiff/__init__.py` defines the public package API. +- All classes and functions within production modules must expose only methods and attributes used externally; internal helpers, attributes, and methods must be private (`_name`). + +# Work Guidance + +- Follow strict typing and docstring standards established in `pyproject.toml` (Ruff/Numpy docstring conventions). +- Keep public API exports clean and aligned with `affidiff/__init__.py`. + +# Verification + +- Run test suite: `uv run pytest` +- Run pre-commit checks: `uv run prek run -v --show-diff-on-failure --all-files` + +# Child DOX Index + +- `src/affidiff/AGENTS.md` - Core affine diffusion models, parameter classes, moments estimation, and simulation utilities diff --git a/src/affidiff/AGENTS.md b/src/affidiff/AGENTS.md new file mode 100644 index 0000000..53508d2 --- /dev/null +++ b/src/affidiff/AGENTS.md @@ -0,0 +1,33 @@ +# Purpose + +`src/affidiff/` is the core package implementing affine jump-diffusion stochastic differential equation (SDE) models, parameter classes, moment computations, characteristic functions, and Cython simulation routines. + +# Ownership + +Owns all core stochastic modeling logic, including: +- Generic affine SDE definition (`model_generic.py`, `param_generic.py`) +- Specific diffusion models: CIR (`model_cir.py`, `param_cir.py`), Vasicek (`model_vasicek.py`, `param_vasicek.py`), GBM (`model_gbm.py`, `param_gbm.py`), Heston (`model_heston.py`, `param_heston.py`), and Central Tendency (`model_ct.py`, `param_ct.py`) +- Mathematical helpers and moment functions (`helper_functions.py`) +- C/Cython fast simulation routines (`simulate.pyx`) +- Public exports (`__init__.py`) + +# Local Contracts + +- Every model class inherits from or complies with `SDE` and uses its corresponding parameter class (`*param.py`). +- Attributes and helper methods not accessed outside their owning class MUST be private (prefixed with `_`). +- Any new model or parameter class introduced must be exported in `__init__.py`. + +# Work Guidance + +- Ensure parameter bounds and matrix dimensions (drift, diffusion, jump intensities) are validated upon instantiation in parameter classes. +- Maintain mathematical accuracy in ODE solving, characteristic function evaluation, and numerical integration. +- All public methods and functions use `*` after `self`/`cls` to enforce keyword-only arguments (PLR0917 compliance). When calling internal methods that are keyword-only from contexts where positional calls are unavoidable (e.g., `numdifftools`), add a local positional wrapper function. + +# Verification + +- Run model test suite: `uv run pytest tests/` +- Run pre-commit hooks: `uv run prek run -v --show-diff-on-failure --all-files` + +# Child DOX Index + +None (leaf boundary). diff --git a/src/affidiff/__init__.py b/src/affidiff/__init__.py index 3a897a5..3d6519d 100644 --- a/src/affidiff/__init__.py +++ b/src/affidiff/__init__.py @@ -1,13 +1,14 @@ -from .param_generic import * -from .param_gbm import * -from .param_vasicek import * -from .param_cir import * -from .param_heston import * -from .param_ct import * +"""Affine Diffusion models package.""" -from .model_generic import * -from .model_gbm import * -from .model_vasicek import * -from .model_cir import * -from .model_heston import * -from .model_ct import * +from affidiff.model_cir import CIR as CIR +from affidiff.model_ct import CentTend as CentTend +from affidiff.model_gbm import GBM as GBM +from affidiff.model_generic import SDE as SDE +from affidiff.model_heston import Heston as Heston +from affidiff.model_vasicek import Vasicek as Vasicek +from affidiff.param_cir import CIRparam as CIRparam +from affidiff.param_ct import CentTendParam as CentTendParam +from affidiff.param_gbm import GBMparam as GBMparam +from affidiff.param_generic import GenericParam as GenericParam +from affidiff.param_heston import HestonParam as HestonParam +from affidiff.param_vasicek import VasicekParam as VasicekParam diff --git a/src/affidiff/helper_functions.py b/src/affidiff/helper_functions.py index 691806d..dfd0688 100644 --- a/src/affidiff/helper_functions.py +++ b/src/affidiff/helper_functions.py @@ -1,27 +1,19 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Helper functions +"""Helper functions.""" -""" -from __future__ import print_function, division +from __future__ import annotations -import time import contextlib +import itertools as it +import time +from typing import Any, Callable, Generator, Sequence -import numpy as np import matplotlib.pylab as plt +import numpy as np import seaborn as sns -import itertools as it - from statsmodels.tsa.tsatools import lagmat -__all__ = ['nice_errors', 'ajd_drift', 'ajd_diff', - 'plot_trajectories', 'plot_final_distr', 'plot_realized', - 'columnwise_prod', 'rolling_window', 'poly_coef', 'instruments'] - -def ajd_drift(state, theta): +def ajd_drift(*, state: Any, theta: Any) -> np.ndarray: # noqa: ANN401 """Instantaneous mean. Parameters @@ -37,11 +29,11 @@ def ajd_drift(state, theta): Value of the drift """ - state = np.atleast_2d(state) - return theta.mat_k0 + state.dot(np.transpose(theta.mat_k1)) + state_arr = np.atleast_2d(state) + return theta.mat_k0 + state_arr.dot(np.transpose(theta.mat_k1)) -def ajd_diff(state, theta): +def ajd_diff(*, state: Any, theta: Any) -> np.ndarray: # noqa: ANN401 """Instantaneous volatility. Parameters @@ -57,17 +49,17 @@ def ajd_diff(state, theta): Value of the diffusion """ - state = np.atleast_2d(state) + state_arr = np.atleast_2d(state) mat_h1 = np.atleast_3d(theta.mat_h1) # (nsim, nvars, nvars) - var = theta.mat_h0 + np.tensordot(state, mat_h1, axes=(1, 0)) + var = theta.mat_h0 + np.tensordot(state_arr, mat_h1, axes=(1, 0)) try: return np.linalg.cholesky(var) - except(np.linalg.LinAlgError): + except np.linalg.LinAlgError: return np.ones_like(var) * 1e10 -def nice_errors(errors, sdim): +def nice_errors(*, errors: np.ndarray, sdim: int) -> np.ndarray: """Normalize the errors and apply antithetic sampling. Parameters @@ -84,12 +76,12 @@ def nice_errors(errors, sdim): """ if errors.shape[sdim] > 10: - errors -= errors.mean(sdim, keepdims=True) - errors /= errors.std(sdim, keepdims=True) + errors = errors - errors.mean(sdim, keepdims=True) + errors = errors / errors.std(sdim, keepdims=True) return np.concatenate((errors, -errors), axis=sdim) -def plot_trajectories(paths, nsub, names): +def plot_trajectories(*, paths: Any, nsub: int, names: str | list[str]) -> None: # noqa: ANN401 """Plot process realizations. Parameters @@ -103,20 +95,23 @@ def plot_trajectories(paths, nsub, names): """ if isinstance(paths, list): - for path, name in zip(paths, names): - x = np.arange(0, path.shape[0] / nsub, 1 / nsub) - plt.plot(x, path, label=name) + assert isinstance(names, list) + for path_elem, name in zip(paths, names, strict=False): + p_arr = np.asarray(path_elem) + x = np.arange(0, p_arr.shape[0] / nsub, 1 / nsub) + plt.plot(x, p_arr, label=name) else: - x = np.arange(0, paths.shape[0] / nsub, 1 / nsub) - plt.plot(x, paths, label=names) + p_arr = np.asarray(paths) + x = np.arange(0, p_arr.shape[0] / nsub, 1 / nsub) + plt.plot(x, p_arr, label=names) - plt.xlabel('$t$') - plt.ylabel('$x_t$') + plt.xlabel("$t$") + plt.ylabel("$x_t$") plt.legend() plt.show() -def plot_final_distr(paths, names): +def plot_final_distr(*, paths: Any, names: str | list[str]) -> None: # noqa: ANN401 """Plot marginal distribution of the process. Parameters @@ -128,19 +123,27 @@ def plot_final_distr(paths, names): """ if isinstance(paths, list): - for path, name in zip(paths, names): - if path.ndim != 2: - raise ValueError('Simulate more paths!') - sns.kdeplot(path[-1], label=name) + assert isinstance(names, list) + for path_elem, name in zip(paths, names, strict=False): + p_arr = np.asarray(path_elem) + if p_arr.ndim != 2: + raise ValueError("Simulate more paths!") + sns.kdeplot(p_arr[-1], label=name) else: - sns.kdeplot(paths[-1], label=names) + p_arr = np.asarray(paths) + sns.kdeplot(p_arr[-1], label=names) - plt.xlabel('x') + plt.xlabel("x") plt.legend() plt.show() -def plot_realized(returns, rvar, suffix=None): +def plot_realized( + *, + returns: Any, # noqa: ANN401 + rvar: Any, # noqa: ANN401 + suffix: list[str] | None = None, +) -> None: """Plot realized returns and volatility. Parameters @@ -149,27 +152,31 @@ def plot_realized(returns, rvar, suffix=None): Returns rvar : array Realized variance + suffix : list of str + Label suffixes """ - fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(7, 6)) + _fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(7, 6)) if isinstance(returns, list): - returns = np.vstack(returns).T - rlabel = ['Returns ' + x for x in suffix] + returns_arr = np.vstack(returns).T + rlabel = ["Returns " + x for x in (suffix or [])] else: - rlabel = 'Returns' + returns_arr = returns + rlabel = ["Returns"] if isinstance(rvar, list): - rvar = np.vstack(rvar).T - vlabel = ['Realized volatility ' + x for x in suffix] + rvar_arr = np.vstack(rvar).T + vlabel = ["Realized volatility " + x for x in (suffix or [])] else: - vlabel = 'Realized volatility' - axes[0].plot(returns) - axes[1].plot(rvar**.5) + rvar_arr = rvar + vlabel = ["Realized volatility"] + axes[0].plot(returns_arr) + axes[1].plot(rvar_arr**0.5) axes[0].legend(rlabel) axes[1].legend(vlabel) plt.show() -def columnwise_prod(left, right): +def columnwise_prod(*, left: np.ndarray, right: np.ndarray) -> np.ndarray: """Columnwise kronker product. Parameters @@ -206,8 +213,8 @@ def columnwise_prod(left, right): return prod.reshape((left.shape[0], left.shape[1] * right.shape[1])) -def rolling_window(fun, mat, window=1): - """Rolling window apply. +def rolling_window(*, fun: Callable[..., Any], mat: np.ndarray, window: int = 1) -> np.ndarray: + """Apply function over rolling window. Source: http://www.rigtorp.se/2011/01/01/rolling-statistics-numpy.html @@ -219,8 +226,6 @@ def rolling_window(fun, mat, window=1): Data to transform window : int Window size - axis : int - Which axis to apply to Returns ------- @@ -241,11 +246,11 @@ def rolling_window(fun, mat, window=1): """ shape = mat.shape[:-1] + (mat.shape[-1] - window + 1, window) strides = mat.strides + (mat.strides[-1],) - mat = np.lib.stride_tricks.as_strided(mat, shape=shape, strides=strides) - return np.apply_along_axis(fun, -1, mat) + mat_s = np.lib.stride_tricks.as_strided(mat, shape=shape, strides=strides) + return np.apply_along_axis(fun, -1, mat_s) -def poly_coef(roots): +def poly_coef(roots: Sequence[float] | np.ndarray) -> list[float]: """Ploynomial coefficients. Parameters @@ -270,17 +275,23 @@ def poly_coef(roots): [1, -9, 26, -24] """ - roots = np.array(roots) - nroots = roots.size - coefs = [1] + roots_arr = np.array(roots) + nroots = roots_arr.size + coefs: list[float] = [1.0] for power in range(nroots): - comb = it.combinations(range(nroots), power+1) - temp = [np.prod(roots[[x]]) for x in comb] - coefs.append((-1)**(power+1) * np.sum(temp)) + comb = it.combinations(range(nroots), power + 1) + temp = [float(np.prod(roots_arr[[x]])) for x in comb] + coefs.append(float((-1) ** (power + 1) * np.sum(temp))) return coefs -def instruments(data=None, instrlag=1, nobs=None, instr_choice='const'): +def instruments( + *, + data: Any = None, # noqa: ANN401 + instrlag: int = 1, + nobs: int | None = None, + instr_choice: str = "const", +) -> np.ndarray: """Create an array of instruments. Parameters @@ -328,18 +339,18 @@ def instruments(data=None, instrlag=1, nobs=None, instr_choice='const'): if data is not None: nobs = data.shape[-1] - if instr_choice == 'const' or data is None: + if instr_choice == "const" or data is None: if nobs is None: - raise ValueError('Specify nobs!') + raise ValueError("Specify nobs!") return np.ones((nobs, 1)) else: instr = lagmat(np.atleast_2d(data).T, maxlag=instrlag) width = ((0, 0), (1, 0)) - return np.pad(instr, width, mode='constant', constant_values=1) + return np.pad(instr, width, mode="constant", constant_values=1) -def format_time(t): +def format_time(t: float) -> str: """Format time for nice printing. Parameters @@ -353,24 +364,24 @@ def format_time(t): """ if t > 60 or t == 0: - units = 'min' + units = "min" t /= 60 elif t > 1: - units = 's' + units = "s" elif t > 1e-3: - units = 'ms' + units = "ms" t *= 1e3 elif t > 1e-6: - units = 'us' + units = "us" t *= 1e6 else: - units = 'ns' + units = "ns" t *= 1e9 - return '%.1f %s' % (t, units) + return f"{t:.1f} {units}" @contextlib.contextmanager -def take_time(desc): +def take_time(desc: str) -> Generator[None, None, None]: """Context manager for timing the code. Parameters @@ -387,9 +398,12 @@ def take_time(desc): t0 = time.time() yield dt = time.time() - t0 - print('%s took %s' % (desc, format_time(dt))) + print(f"{desc} took {format_time(dt)}") if __name__ == "__main__": import doctest + + doctest.testmod() + doctest.testmod() diff --git a/src/affidiff/model_cir.py b/src/affidiff/model_cir.py index 2ff67ea..dea6652 100644 --- a/src/affidiff/model_cir.py +++ b/src/affidiff/model_cir.py @@ -1,24 +1,19 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -CIR model class -~~~~~~~~~~~~~~~ +"""CIR model class.""" -""" -from __future__ import print_function, division +from __future__ import annotations -from .model_generic import SDE +from typing import TYPE_CHECKING, Any -__all__ = ['CIR'] +from affidiff.model_generic import SDE +if TYPE_CHECKING: + import numpy as np -class CIR(SDE): - - """Cox-Ingersoll-Ross (CIR) model. - """ +class CIR(SDE): + """Cox-Ingersoll-Ross (CIR) model.""" - def __init__(self, param=None): + def __init__(self, param: Any = None) -> None: # noqa: ANN401 """Initialize the class. Parameters @@ -27,10 +22,21 @@ def __init__(self, param=None): True parameters used for simulation of the data """ - super(CIR, self).__init__(param) + super().__init__(param) + + def get_start(self) -> list[float]: + """Get starting values for simulation. + + Returns + ------- + list[float] + Starting value at the long-run mean + + """ + return [float(self.param.mean)] @staticmethod - def drift(state, theta): + def drift(*, state: np.ndarray | float, theta: Any) -> np.ndarray | float: # noqa: ANN401 """Drift function. Parameters @@ -49,7 +55,7 @@ def drift(state, theta): return theta.kappa * (theta.mean - state) @staticmethod - def diff(state, theta): + def diff(*, state: np.ndarray | float, theta: Any) -> np.ndarray | float: # noqa: ANN401 """Diffusion (instantaneous volatility) function. Parameters @@ -65,4 +71,4 @@ def diff(state, theta): Diffusion value """ - return theta.eta * state**.5 + return theta.eta * state**0.5 diff --git a/src/affidiff/model_ct.py b/src/affidiff/model_ct.py index ef86ba1..50181b2 100644 --- a/src/affidiff/model_ct.py +++ b/src/affidiff/model_ct.py @@ -1,30 +1,27 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -CT model class -~~~~~~~~~~~~~~ +"""CT model class.""" -""" -from __future__ import print_function, division +from __future__ import annotations from math import exp +from typing import TYPE_CHECKING, Any, cast import numpy as np from statsmodels.tsa.tsatools import lagmat -from .model_generic import SDE -from .helper_functions import poly_coef +from affidiff.helper_functions import poly_coef +from affidiff.model_generic import SDE +from affidiff.param_ct import CentTendParam -__all__ = ['CentTend'] +if TYPE_CHECKING: + pass class CentTend(SDE): + """Central Tendency model.""" - """Central Tendency model. + param: Any - """ - - def __init__(self, param=None): + def __init__(self, param: Any = None) -> None: # noqa: ANN401 """Initialize the class. Parameters @@ -33,9 +30,11 @@ def __init__(self, param=None): True parameters used for simulation of the data """ - super(CentTend, self).__init__(param) + if param is None: + param = CentTendParam() + super().__init__(param) - def get_start(self): + def get_start(self) -> list[float]: """Get starting values for simulation. Returns @@ -44,11 +43,11 @@ def get_start(self): Starting values for price and variance """ - return [1, self.param.mean_v, self.param.mean_v] + return [1.0, float(self.param.mean_v), float(self.param.mean_v)] @staticmethod - def coef_big_as(param, aggh): - """Coefficient A^\sigma_h in exact discretization of volatility. + def coef_big_as(*, param: Any, aggh: float) -> float: # noqa: ANN401 + r"""Coefficient A^\sigma_h in exact discretization of volatility. Parameters ---------- @@ -63,10 +62,10 @@ def coef_big_as(param, aggh): Coefficient A^\sigma_h """ - return np.exp(-param.kappa_s * aggh) + return float(np.exp(-param.kappa_s * aggh)) - def coef_big_bs(self, param, aggh): - """Coefficient B^\sigma_h in exact discretization of volatility. + def coef_big_bs(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 + r"""Coefficient B^\sigma_h in exact discretization of volatility. Parameters ---------- @@ -81,10 +80,14 @@ def coef_big_bs(self, param, aggh): Coefficient B^\sigma_h """ - return param.kappa_s / (param.kappa_s - param.kappa_y) \ - * (self.coef_big_ay(param, aggh) - self.coef_big_as(param, aggh)) - - def coef_big_cs(self, param, aggh): + p = param + return float( + p.kappa_s + / (p.kappa_s - p.kappa_y) + * (self.coef_big_ay(param=param, aggh=aggh) - self.coef_big_as(param=param, aggh=aggh)) + ) + + def coef_big_cs(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient C^s_h in exact discretization of volatility. Parameters @@ -100,11 +103,12 @@ def coef_big_cs(self, param, aggh): Coefficient C^s_h """ - return param.mean_v * (1 - self.coef_big_as(param, aggh) - - self.coef_big_bs(param, aggh)) + return float( + param.mean_v * (1 - self.coef_big_as(param=param, aggh=aggh) - self.coef_big_bs(param=param, aggh=aggh)) + ) @staticmethod - def coef_big_ay(param, aggh): + def coef_big_ay(*, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient A^v_h in exact discretization of volatility. Parameters @@ -119,9 +123,9 @@ def coef_big_ay(param, aggh): float """ - return np.exp(-param.kappa_y * aggh) + return float(np.exp(-param.kappa_y * aggh)) - def coef_big_cy(self, param, aggh): + def coef_big_cy(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient C^y_h in exact discretization of volatility. Parameters @@ -137,9 +141,9 @@ def coef_big_cy(self, param, aggh): Coefficient C^y_h """ - return param.mean_v * (1 - self.coef_big_ay(param, aggh)) + return float(param.mean_v * (1 - self.coef_big_ay(param=param, aggh=aggh))) - def coef_small_as(self, param, aggh): + def coef_small_as(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient a^s_h in exact discretization of volatility. Parameters @@ -155,9 +159,9 @@ def coef_small_as(self, param, aggh): Coefficient a^s_h """ - return (1 - self.coef_big_as(param, aggh)) / param.kappa_s / aggh + return float((1 - self.coef_big_as(param=param, aggh=aggh)) / param.kappa_s / aggh) - def coef_small_bs(self, param, aggh): + def coef_small_bs(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient b^s_h in exact discretization of volatility. Parameters @@ -173,11 +177,14 @@ def coef_small_bs(self, param, aggh): Coefficient b^s_h """ - return param.kappa_s / (param.kappa_s - param.kappa_y) \ - * (self.coef_small_ay(param, aggh) - - self.coef_small_as(param, aggh)) - - def coef_small_cs(self, param, aggh): + p = param + return float( + p.kappa_s + / (p.kappa_s - p.kappa_y) + * (self.coef_small_ay(param=param, aggh=aggh) - self.coef_small_as(param=param, aggh=aggh)) + ) + + def coef_small_cs(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient c^s_h in exact discretization of volatility. Parameters @@ -193,10 +200,11 @@ def coef_small_cs(self, param, aggh): Coefficient c^s_h """ - return param.mean_v * (1 - self.coef_small_as(param, aggh) - - self.coef_small_bs(param, aggh)) + return float( + param.mean_v * (1 - self.coef_small_as(param=param, aggh=aggh) - self.coef_small_bs(param=param, aggh=aggh)) + ) - def coef_small_ay(self, param, aggh): + def coef_small_ay(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient a^v_h in exact discretization of volatility. Parameters @@ -211,10 +219,10 @@ def coef_small_ay(self, param, aggh): float """ - return (1 - self.coef_big_ay(param, aggh)) / param.kappa_y / aggh + return float((1 - self.coef_big_ay(param=param, aggh=aggh)) / param.kappa_y / aggh) - def roots(self, param, aggh): - """Roots of the polynomial in moment restrictions. + def roots(self, *, param: Any, aggh: float) -> list[float]: # noqa: ANN401 + r"""Roots of the polynomial in moment restrictions. .. math:: @@ -236,14 +244,16 @@ def roots(self, param, aggh): list of floats """ - return [self.coef_big_as(param, aggh), - self.coef_big_ay(param, aggh), - self.coef_big_as(param, aggh)**2, - self.coef_big_ay(param, aggh)**2, - self.coef_big_as(param, aggh) * self.coef_big_ay(param, aggh)] + return [ + self.coef_big_as(param=param, aggh=aggh), + self.coef_big_ay(param=param, aggh=aggh), + self.coef_big_as(param=param, aggh=aggh) ** 2, + self.coef_big_ay(param=param, aggh=aggh) ** 2, + self.coef_big_as(param=param, aggh=aggh) * self.coef_big_ay(param=param, aggh=aggh), + ] @staticmethod - def mean_vol(param, aggh): + def mean_vol(*, param: Any, aggh: float) -> float: # noqa: ARG004, ANN401 """Unconditional mean of realized volatiliy. Parameters @@ -258,9 +268,9 @@ def mean_vol(param, aggh): float """ - return param.mean_v + return float(param.mean_v) - def mean_vol2(self, param, aggh): + def mean_vol2(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Unconditional mean of squared realized volatiliy. Parameters @@ -275,12 +285,14 @@ def mean_vol2(self, param, aggh): float """ - return (self.coef_small_as(param, aggh)**2 * unc_var_sigma(param) - + self.coef_small_bs(param, aggh)**2 * unc_var_ct(param) - + unc_var_error(param, aggh)) + return float( + self.coef_small_as(param=param, aggh=aggh) ** 2 * unc_var_sigma(param) + + self.coef_small_bs(param=param, aggh=aggh) ** 2 * unc_var_ct(param) + + unc_var_error(param=param, aggh=aggh) + ) @staticmethod - def mean_ret(param, aggh): + def mean_ret(*, param: Any, aggh: float) -> float: # noqa: ARG004, ANN401 """Unconditional mean of realized returns. Parameters @@ -295,9 +307,9 @@ def mean_ret(param, aggh): float """ - return (param.lmbd - .5) * param.mean_v + return float((param.lmbd - 0.5) * param.mean_v) - def mean_cross(self, param, aggh): + def mean_cross(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Unconditional mean of realized returns times volatility. Parameters @@ -312,11 +324,13 @@ def mean_cross(self, param, aggh): float """ - return ((param.lmbd - .5) * self.mean_vol2(param, aggh) - + param.rho * param.mean_v * param.eta_s / param.kappa_s - * (1 - self.coef_small_as(param, aggh)) / aggh) + p = param + return float( + (p.lmbd - 0.5) * self.mean_vol2(param=param, aggh=aggh) + + p.rho * p.mean_v * p.eta_s / p.kappa_s * (1 - self.coef_small_as(param=param, aggh=aggh)) / aggh + ) - def realized_const(self, param, aggh, subset=None): + def realized_const(self, *, param: Any = None, aggh: float = 1, subset: slice | None = None) -> np.ndarray: # noqa: ANN401 """Intercept in the realized moment conditions. Parameters @@ -334,15 +348,22 @@ def realized_const(self, param, aggh, subset=None): Intercept """ - return ((self.mat_a0(param, 1) - + self.mat_a1(param, 1) - + self.mat_a2(param, 1) - + self.mat_a3(param, 1) - + self.mat_a4(param, 1) - + self.mat_a5(param, 1)) - * self.depvar_unc_mean(param, aggh)).sum(1)[subset].squeeze() - - def mat_a0(self, param, aggh): + res = ( + ( + self.mat_a0(param=param, aggh=1) + + self.mat_a1(param=param, aggh=1) + + self.mat_a2(param=param, aggh=1) + + self.mat_a3(param=param, aggh=1) + + self.mat_a4(param=param, aggh=1) + + self.mat_a5(param=param, aggh=1) + ) + * self.depvar_unc_mean(param=param, aggh=aggh) + ).sum(1) + if subset is not None: + res = res[subset] + return np.squeeze(res) + + def mat_a0(self, *, param: Any, aggh: float) -> np.ndarray: # noqa: ANN401 """Matrix A_0 in integrated moments. Parameters @@ -359,10 +380,10 @@ def mat_a0(self, param, aggh): """ mat = np.zeros((4, 4)) - mat[1, 1] = poly_coef(self.roots(param, aggh))[0] + mat[1, 1] = poly_coef(self.roots(param=param, aggh=aggh))[0] return mat - def mat_a1(self, param, aggh): + def mat_a1(self, *, param: Any, aggh: float) -> np.ndarray: # noqa: ANN401 """Matrix A_1 in integrated moments. Parameters @@ -379,10 +400,10 @@ def mat_a1(self, param, aggh): """ mat = np.zeros((4, 4)) - mat[1, 1] = poly_coef(self.roots(param, aggh))[1] + mat[1, 1] = poly_coef(self.roots(param=param, aggh=aggh))[1] return mat - def mat_a2(self, param, aggh): + def mat_a2(self, *, param: Any, aggh: float) -> np.ndarray: # noqa: ANN401 """Matrix A_2 in integrated moments. Parameters @@ -399,10 +420,10 @@ def mat_a2(self, param, aggh): """ mat = np.zeros((4, 4)) - mat[1, 1] = poly_coef(self.roots(param, aggh))[2] + mat[1, 1] = poly_coef(self.roots(param=param, aggh=aggh))[2] return mat - def mat_a3(self, param, aggh): + def mat_a3(self, *, param: Any, aggh: float) -> np.ndarray: # noqa: ANN401 """Matrix A_3 in integrated moments. Parameters @@ -419,13 +440,13 @@ def mat_a3(self, param, aggh): """ mat = np.zeros((4, 4)) - mat[0, 0] = poly_coef(self.roots(param, aggh)[:2])[0] - mat[1, 1] = poly_coef(self.roots(param, aggh))[3] - mat[3, 1] = .5 - param.lmbd + mat[0, 0] = poly_coef(self.roots(param=param, aggh=aggh)[:2])[0] + mat[1, 1] = poly_coef(self.roots(param=param, aggh=aggh))[3] + mat[3, 1] = 0.5 - param.lmbd mat[3, 3] = mat[0, 0] return mat - def mat_a4(self, param, aggh): + def mat_a4(self, *, param: Any, aggh: float) -> np.ndarray: # noqa: ANN401 """Matrix A_4 in integrated moments. Parameters @@ -442,13 +463,13 @@ def mat_a4(self, param, aggh): """ mat = np.zeros((4, 4)) - mat[0, 0] = poly_coef(self.roots(param, aggh)[:2])[1] - mat[1, 1] = poly_coef(self.roots(param, aggh))[4] - mat[3, 1] = (.5 - param.lmbd) * mat[0, 0] + mat[0, 0] = poly_coef(self.roots(param=param, aggh=aggh)[:2])[1] + mat[1, 1] = poly_coef(self.roots(param=param, aggh=aggh))[4] + mat[3, 1] = (0.5 - param.lmbd) * mat[0, 0] mat[3, 3] = mat[0, 0] return mat - def mat_a5(self, param, aggh): + def mat_a5(self, *, param: Any, aggh: float) -> np.ndarray: # noqa: ANN401 """Matrix A_5 in integrated moments. Parameters @@ -465,15 +486,15 @@ def mat_a5(self, param, aggh): """ mat = np.zeros((4, 4)) - mat[0, 0] = poly_coef(self.roots(param, aggh)[:2])[2] - mat[1, 1] = poly_coef(self.roots(param, aggh))[5] + mat[0, 0] = poly_coef(self.roots(param=param, aggh=aggh)[:2])[2] + mat[1, 1] = poly_coef(self.roots(param=param, aggh=aggh))[5] mat[2, 2] = 1 mat[3, 3] = mat[0, 0] - mat[2, 0] = .5 - param.lmbd - mat[3, 1] = (.5 - param.lmbd) * mat[0, 0] + mat[2, 0] = 0.5 - param.lmbd + mat[3, 1] = (0.5 - param.lmbd) * mat[0, 0] return mat - def mat_a(self, param, subset=None): + def mat_a(self, *, param: Any, subset: slice | None = None) -> np.ndarray: # noqa: ANN401 """Matrix A in integrated moments. Parameters @@ -489,18 +510,22 @@ def mat_a(self, param, subset=None): Matrix A """ - mat_a = (self.mat_a0(param, 1), - self.mat_a1(param, 1), - self.mat_a2(param, 1), - self.mat_a3(param, 1), - self.mat_a4(param, 1), - self.mat_a5(param, 1)) - return np.hstack(mat_a)[subset].squeeze() + mat_a_tuple = ( + self.mat_a0(param=param, aggh=1), + self.mat_a1(param=param, aggh=1), + self.mat_a2(param=param, aggh=1), + self.mat_a3(param=param, aggh=1), + self.mat_a4(param=param, aggh=1), + self.mat_a5(param=param, aggh=1), + ) + res = np.hstack(mat_a_tuple) + if subset is not None: + res = res[subset] + return np.squeeze(res) @staticmethod - def realized_depvar(data, subset=None): - """Array of the left-hand side variables - in realized moment conditions. + def realized_depvar(*, data: Any, subset: slice | None = None) -> np.ndarray: # noqa: ANN401 + """Array of the left-hand side variables in realized moment conditions. Parameters ---------- @@ -515,13 +540,17 @@ def realized_depvar(data, subset=None): Dependend variables """ - ret, rvar = data - var = np.vstack([rvar, rvar**2, ret, ret * rvar])[subset].squeeze() - return lagmat(var.T, maxlag=5, original='in') + data_arr = np.asarray(data) + ret, rvar = data_arr[0], data_arr[1] + var = np.vstack([rvar, rvar**2, ret, ret * rvar]) + if subset is not None: + var = var[subset] + var_s = np.squeeze(var) + return cast(np.ndarray, lagmat(var_s.T, maxlag=5, original="in")) -def unc_mean_ct2(param): - """Unconditional second moment of CT, E[y_t**4]. +def unc_mean_ct2(param: Any) -> float: # noqa: ANN401 + """Calculate unconditional second moment of CT, E[y_t**4]. Parameters ---------- @@ -533,11 +562,12 @@ def unc_mean_ct2(param): float """ - return param.mean_v * param.eta_y**2 / param.kappa_y / 2 + p = param + return float(p.mean_v * p.eta_y**2 / p.kappa_y / 2) -def unc_mean_sigma2(param): - """Unconditional second moment of volatility, E[\sigma_t**4]. +def unc_mean_sigma2(param: Any) -> float: # noqa: ANN401 + r"""Calculate unconditional second moment of volatility, E[\sigma_t**4]. Parameters ---------- @@ -549,13 +579,12 @@ def unc_mean_sigma2(param): float """ - return unc_mean_ct2(param) * param.kappa_s \ - / (param.kappa_s + param.kappa_y) \ - + param.mean_v * param.eta_s**2 / param.kappa_s / 2 + p = param + return float(unc_mean_ct2(param) * p.kappa_s / (p.kappa_s + p.kappa_y) + p.mean_v * p.eta_s**2 / p.kappa_s / 2) -def unc_var_ct(param): - """Unconditional variance of CT, V[y_t**2]. +def unc_var_ct(param: Any) -> float: # noqa: ANN401 + """Calculate unconditional variance of CT, V[y_t**2]. Parameters ---------- @@ -567,11 +596,12 @@ def unc_var_ct(param): float """ - return param.mean_v**2 + unc_mean_ct2(param) + p = param + return float(p.mean_v**2 + unc_mean_ct2(param)) -def unc_var_sigma(param): - """Unconditional variance of volatility, V[\sigma_t**2]. +def unc_var_sigma(param: Any) -> float: # noqa: ANN401 + r"""Calculate unconditional variance of volatility, V[\sigma_t**2]. Parameters ---------- @@ -583,12 +613,14 @@ def unc_var_sigma(param): float """ - return param.mean_v**2 + unc_mean_sigma2(param) + p = param + return float(p.mean_v**2 + unc_mean_sigma2(param)) + +def unc_var_error(*, param: Any, aggh: float) -> float: # noqa: ANN401 + r"""Calculate unconditional variance of aggregated volatility error. -def unc_var_error(param, aggh): - """Unconditional variance of aggregated volatility error, - :math:`V\left[\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\right]` + :math:`V\left[\frac{1}{H}\int_{0}^{H}\epsilon_{t,s}^{\sigma}ds\right]`. Derived symbolically in symbolic.py @@ -604,29 +636,50 @@ def unc_var_error(param, aggh): float """ - mu = param.mean_v - kappa_s = param.kappa_s - kappa_y = param.kappa_y - eta_s = param.eta_s - eta_y = param.eta_y - - return (mu*(eta_s**2*kappa_y**3*(kappa_s - kappa_y)**2*(kappa_s + - kappa_y)*(2*aggh*kappa_s*exp(2*aggh*kappa_s) - 3*exp(2*aggh*kappa_s) + - 4*exp(aggh*kappa_s) - 1)*exp(2*aggh*(kappa_s + 2*kappa_y)) + - eta_y**2*kappa_s**2*(-kappa_s**4*exp(2*aggh*kappa_s) + - 4*kappa_s**4*exp(aggh*(2*kappa_s + kappa_y)) - - kappa_s**3*kappa_y*exp(2*aggh*kappa_s) + - 4*kappa_s**2*kappa_y**2*exp(aggh*(kappa_s + kappa_y)) - - 4*kappa_s**2*kappa_y**2*exp(aggh*(kappa_s + 2*kappa_y)) - - 4*kappa_s**2*kappa_y**2*exp(aggh*(2*kappa_s + kappa_y)) - - kappa_s*kappa_y**3*exp(2*aggh*kappa_y) - - kappa_y**4*exp(2*aggh*kappa_y) + - 4*kappa_y**4*exp(aggh*(kappa_s + 2*kappa_y)) + - (2*aggh*kappa_s*kappa_y*(kappa_s**3 - kappa_s**2*kappa_y - - kappa_s*kappa_y**2 + kappa_y**3) + kappa_s**3*(kappa_s + kappa_y) - - 4*kappa_s**2*kappa_y**2 + 4*kappa_s**2*(-kappa_s**2 + kappa_y**2) + - kappa_y**3*(kappa_s + kappa_y) + 4*kappa_y**2*(kappa_s**2 - - kappa_y**2))*exp(2*aggh*(kappa_s + kappa_y)))*exp(2*aggh*(kappa_s + - kappa_y)))*exp(aggh*(-4*kappa_s - - 4*kappa_y))/(2*aggh**2*kappa_s**3*kappa_y**3*(kappa_s - - kappa_y)**2*(kappa_s + kappa_y))) + p = param + mu = p.mean_v + kappa_s = p.kappa_s + kappa_y = p.kappa_y + eta_s = p.eta_s + eta_y = p.eta_y + + return float( + mu + * ( + eta_s**2 + * kappa_y**3 + * (kappa_s - kappa_y) ** 2 + * (kappa_s + kappa_y) + * (2 * aggh * kappa_s * exp(2 * aggh * kappa_s) - 3 * exp(2 * aggh * kappa_s) + 4 * exp(aggh * kappa_s) - 1) + * exp(2 * aggh * (kappa_s + 2 * kappa_y)) + + eta_y**2 + * kappa_s**2 + * ( + -(kappa_s**4) * exp(2 * aggh * kappa_s) + + 4 * kappa_s**4 * exp(aggh * (2 * kappa_s + kappa_y)) + - kappa_s**3 * kappa_y * exp(2 * aggh * kappa_s) + + 4 * kappa_s**2 * kappa_y**2 * exp(aggh * (kappa_s + kappa_y)) + - 4 * kappa_s**2 * kappa_y**2 * exp(aggh * (kappa_s + 2 * kappa_y)) + - 4 * kappa_s**2 * kappa_y**2 * exp(aggh * (2 * kappa_s + kappa_y)) + - kappa_s * kappa_y**3 * exp(2 * aggh * kappa_y) + - kappa_y**4 * exp(2 * aggh * kappa_y) + + 4 * kappa_y**4 * exp(aggh * (kappa_s + 2 * kappa_y)) + + ( + 2 + * aggh + * kappa_s + * kappa_y + * (kappa_s**3 - kappa_s**2 * kappa_y - kappa_s * kappa_y**2 + kappa_y**3) + + kappa_s**3 * (kappa_s + kappa_y) + - 4 * kappa_s**2 * kappa_y**2 + + 4 * kappa_s**2 * (-(kappa_s**2) + kappa_y**2) + + kappa_y**3 * (kappa_s + kappa_y) + + 4 * kappa_y**2 * (kappa_s**2 - kappa_y**2) + ) + * exp(2 * aggh * (kappa_s + kappa_y)) + ) + * exp(2 * aggh * (kappa_s + kappa_y)) + ) + * exp(aggh * (-4 * kappa_s - 4 * kappa_y)) + / (2 * aggh**2 * kappa_s**3 * kappa_y**3 * (kappa_s - kappa_y) ** 2 * (kappa_s + kappa_y)) + ) diff --git a/src/affidiff/model_gbm.py b/src/affidiff/model_gbm.py index ebfe6f7..7621d17 100644 --- a/src/affidiff/model_gbm.py +++ b/src/affidiff/model_gbm.py @@ -1,31 +1,25 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -GBM model class -~~~~~~~~~~~~~~~ +"""GBM model class.""" -""" -from __future__ import print_function, division +from __future__ import annotations -import numpy as np -import numdifftools as nd +from typing import TYPE_CHECKING, Any, Sequence, cast +import numdifftools as nd +import numpy as np from statsmodels.tsa.tsatools import lagmat -from .model_generic import SDE -from .helper_functions import columnwise_prod -from .param_gbm import GBMparam +from affidiff.helper_functions import columnwise_prod +from affidiff.model_generic import SDE +from affidiff.param_gbm import GBMparam -__all__ = ['GBM'] +if TYPE_CHECKING: + pass class GBM(SDE): + """Geometric Brownian Motion.""" - """Geometric Brownian Motion. - - """ - - def __init__(self, param=None): + def __init__(self, param: Any = None) -> None: # noqa: ANN401 """Initialize the class. Parameters @@ -34,10 +28,14 @@ def __init__(self, param=None): True parameters used for simulation of the data """ - super(GBM, self).__init__(param) + super().__init__(param) + + def get_start(self) -> list[float]: + """Return starting values for simulation.""" + return [1.0] @staticmethod - def drift(state, theta): + def drift(*, state: np.ndarray | float, theta: Any) -> np.ndarray | float: # noqa: ARG004, ANN401 """Drift function. Parameters @@ -53,10 +51,10 @@ def drift(state, theta): Drift value """ - return theta.mean - theta.sigma**2/2 + return theta.mean - theta.sigma**2 / 2 @staticmethod - def diff(state, theta): + def diff(*, state: np.ndarray | float, theta: Any) -> np.ndarray | float: # noqa: ARG004, ANN401 """Diffusion (instantaneous volatility) function. Parameters @@ -74,7 +72,7 @@ def diff(state, theta): """ return theta.sigma - def betamat(self, theta): + def betamat(self, theta: np.ndarray | Sequence[float]) -> np.ndarray: """Coefficients in linear representation of the first moment. Parameters @@ -88,11 +86,11 @@ def betamat(self, theta): Constant coefficient """ - param = GBMparam.from_theta(theta) - loc = float(self.exact_loc(0, param)) + param = GBMparam.from_theta(theta=theta) + loc = float(self.exact_loc(state=np.array(0), theta=param)) return np.array([loc, 0], dtype=float) - def gammamat(self, theta): + def gammamat(self, theta: np.ndarray | Sequence[float]) -> np.ndarray: """Coefficients in linear representation of the second moment. Parameters @@ -106,13 +104,13 @@ def gammamat(self, theta): Constant coefficient """ - param = GBMparam.from_theta(theta) - loc = float(self.exact_loc(0, param)) - scale = float(self.exact_scale(0, param)) + param = GBMparam.from_theta(theta=theta) + loc = float(self.exact_loc(state=np.array(0), theta=param)) + scale = float(self.exact_scale(state=np.array(0), theta=param)) return np.array([loc**2 + scale**2, 0], dtype=float) - def dbetamat(self, theta): - """Derivative of the first moment coefficients (numerical). + def dbetamat(self, theta: np.ndarray | Sequence[float]) -> np.ndarray: + """Calculate derivative of the first moment coefficients (numerical). Parameters ---------- @@ -125,11 +123,11 @@ def dbetamat(self, theta): Derivatives of the coefficient """ - with np.errstate(divide='ignore'): + with np.errstate(divide="ignore"): return nd.Jacobian(self.betamat)(theta) - def dgammamat(self, theta): - """Derivative of the second moment coefficients (numerical). + def dgammamat(self, theta: np.ndarray | Sequence[float]) -> np.ndarray: + """Calculate derivative of the second moment coefficients (numerical). Parameters ---------- @@ -142,11 +140,11 @@ def dgammamat(self, theta): Derivatives of the coefficient """ - with np.errstate(divide='ignore'): + with np.errstate(divide="ignore"): return nd.Jacobian(self.gammamat)(theta) - def dbetamat_exact(self, theta): - """Derivative of the first moment coefficients (exact). + def dbetamat_exact(self, theta: np.ndarray | Sequence[float]) -> np.ndarray: + """Calculate derivative of the first moment coefficients (exact). Parameters ---------- @@ -159,11 +157,12 @@ def dbetamat_exact(self, theta): Derivatives of the coefficient """ - mean, sigma = theta - return np.array([[1 / self.nsub, - sigma / self.nsub], [0, 0]]) + _mean, sigma = float(theta[0]), float(theta[1]) + assert self.nsub is not None + return np.array([[1 / self.nsub, -sigma / self.nsub], [0, 0]]) - def dgammamat_exact(self, theta): - """Derivative of the second moment coefficients (exact). + def dgammamat_exact(self, theta: np.ndarray | Sequence[float]) -> np.ndarray: + """Calculate derivative of the second moment coefficients (exact). Parameters ---------- @@ -176,21 +175,28 @@ def dgammamat_exact(self, theta): Derivatives of the coefficient """ - mean, sigma = theta - return np.array([[2 / self.nsub**2 * (mean - sigma**2/2), - 2 * sigma / self.nsub - - 2 * sigma / self.nsub**2 - * (mean - sigma**2/2)], [0, 0]]) + mean, sigma = float(theta[0]), float(theta[1]) + assert self.nsub is not None + return np.array( + [ + [ + 2 / self.nsub**2 * (mean - sigma**2 / 2), + 2 * sigma / self.nsub - 2 * sigma / self.nsub**2 * (mean - sigma**2 / 2), + ], + [0, 0], + ] + ) @staticmethod - def realized_depvar(data): - """Array of the left-hand side variables - in realized moment conditions. + def realized_depvar(*, data: np.ndarray, subset: slice | None = None) -> np.ndarray: # noqa: ARG004 + """Array of the left-hand side variables in realized moment conditions. Parameters ---------- data : (2, nobs) array Returns and realized variance + subset : slice + Which moments to use Returns ------- @@ -201,14 +207,23 @@ def realized_depvar(data): ret, rvar = data return np.vstack([ret, rvar, rvar**2]) - @staticmethod - def realized_const(theta): + def realized_const( + self, + *, + param: Any = None, # noqa: ANN401 + aggh: Any = 1, # noqa: ARG002, ANN401 + subset: slice | None = None, # noqa: ARG002 + ) -> np.ndarray: """Intercept in the realized moment conditions. Parameters ---------- - theta : array + param : array Parameters + aggh : int + Interval length + subset : slice + Which moments to use Returns ------- @@ -216,11 +231,12 @@ def realized_const(theta): Intercept """ - mean, sigma = theta - return np.array([mean - sigma**2/2, sigma**2, sigma**4]) + theta = param + mean, sigma = float(theta[0]), float(theta[1]) + return np.array([mean - sigma**2 / 2, sigma**2, sigma**4]) - def drealized_const(self, theta): - """Derivative of the intercept in the realized moment conditions. + def drealized_const(self, theta: Any) -> np.ndarray: # noqa: ANN401 + """Calculate derivative of the intercept in the realized moment conditions. Parameters ---------- @@ -233,11 +249,15 @@ def drealized_const(self, theta): Derivatives of the coefficient """ - with np.errstate(divide='ignore'): - return nd.Jacobian(self.realized_const)(theta) + + def _realized_const_wrapper(theta: Any) -> np.ndarray: # noqa: ANN401 + return self.realized_const(param=theta) + + with np.errstate(divide="ignore"): + return nd.Jacobian(_realized_const_wrapper)(theta) @staticmethod - def instruments(data, instrlag=1): + def instruments(*, data: Any, instrlag: int = 1) -> np.ndarray: # noqa: ANN401 """Create an array of instruments. Parameters @@ -253,10 +273,22 @@ def instruments(data, instrlag=1): Derivatives of the coefficient """ - return np.vstack([np.ones_like(data[0]), - lagmat(data.T, maxlag=instrlag).T])[:, instrlag:] - - def integrated_mom(self, theta, data=None, instrlag=1, **kwargs): + data_arr = np.asarray(data) + lmat = cast(np.ndarray, lagmat(data_arr.T, maxlag=instrlag)) + return np.vstack([np.ones_like(data_arr[0]), lmat.T])[:, instrlag:] + + def integrated_mom( + self, + *, + theta: Any, # noqa: ANN401 + data: Any = None, # noqa: ANN401 + instr_data: Any = None, # noqa: ARG002, ANN401 + instr_choice: str = "const", # noqa: ARG002 + aggh: Any = 1, # noqa: ARG002, ANN401 + subset: str = "all", # noqa: ARG002 + instrlag: int = 1, + measure: str = "P", # noqa: ARG002 + ) -> tuple[np.ndarray, np.ndarray]: """Integrated moment function. Parameters @@ -265,8 +297,18 @@ def integrated_mom(self, theta, data=None, instrlag=1, **kwargs): Model parameters data : (2, nobs) array Returns and realized variance + instr_data : object + Instrument data + instr_choice : str + Instrument choice + aggh : int + Aggregation horizon + subset : str + Subset instrlag : int Number of lags for the instruments + measure : str + Measure Returns ------- @@ -276,24 +318,29 @@ def integrated_mom(self, theta, data=None, instrlag=1, **kwargs): Average derivative of the moment restrictions """ - ret, rvar = data + assert data is not None # (nobs - instrlag, 3) array - error = (self.realized_depvar(data).T[instrlag:] - - self.realized_const(theta)) + error = self.realized_depvar(data=data).T[instrlag:] - self.realized_const(param=theta) # (nobs - instrlag, ninstr) - instr = self.instruments(data, instrlag=instrlag).T + instr = self.instruments(data=data, instrlag=instrlag).T # (nobs - instrlag, 3 * ninstr = nmoms) - moms = columnwise_prod(error, instr) + moms = columnwise_prod(left=error, right=instr) # (nintercepts, nparams) dmoms = -self.drealized_const(theta) dmoments = [] for minstr in instr.mean(0): dmoments.append(dmoms * minstr) - dmoments = np.vstack(dmoments) + dmoments_arr = np.vstack(dmoments) - return moms, dmoments + return moms, dmoments_arr - def momcond(self, theta, data=None, instrlag=1): + def momcond( + self, + *, + theta: np.ndarray | Sequence[float], + data: Any = None, # noqa: ANN401 + instrlag: int = 1, + ) -> tuple[np.ndarray, np.ndarray]: """Moment function. Parameters @@ -313,8 +360,10 @@ def momcond(self, theta, data=None, instrlag=1): Average derivative of the moment restrictions """ + assert data is not None + data_arr = np.asarray(data) datalag = 1 - lagdata = lagmat(data, maxlag=datalag)[datalag:] + lagdata = cast(np.ndarray, lagmat(data_arr, maxlag=datalag))[datalag:] nobs = lagdata.shape[0] datamat = np.hstack([np.ones((nobs, 1)), lagdata]) @@ -326,24 +375,23 @@ def momcond(self, theta, data=None, instrlag=1): modelerror = [] for i in range(len(linearcoef)): # Difference between data and model prediction - error = data[datalag:]**(i+1) - datamat.dot(linearcoef[i]) + error = data_arr[datalag:] ** (i + 1) - datamat.dot(linearcoef[i]) modelerror.append(error) - modelerror = np.vstack(modelerror) + modelerror_arr = np.vstack(modelerror) - instruments = np.hstack([np.ones((nobs, 1)), - lagmat(data[:-datalag], maxlag=instrlag)]).T + instruments = np.hstack([np.ones((nobs, 1)), cast(np.ndarray, lagmat(data_arr[:-datalag], maxlag=instrlag))]).T mom, dmom = [], [] for instr in instruments: - mom.append(modelerror * instr) + mom.append(modelerror_arr * instr) meandata = (datamat.T * instr).mean(1) dtheta = [] for coef in dlinearcoef: dtheta.append(meandata.dot(coef)) - dtheta = -np.vstack(dtheta) - dmom.append(dtheta) + dtheta_arr = -np.vstack(dtheta) + dmom.append(dtheta_arr) - mom = np.vstack(mom).T - dmom = np.vstack(dmom) + mom_arr = np.vstack(mom).T + dmom_arr = np.vstack(dmom) - return mom, dmom + return mom_arr, dmom_arr diff --git a/src/affidiff/model_generic.py b/src/affidiff/model_generic.py index 6697b4e..294ce7f 100644 --- a/src/affidiff/model_generic.py +++ b/src/affidiff/model_generic.py @@ -1,28 +1,26 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Generic model class -------------------- +"""Generic model class.""" -""" -from __future__ import print_function, division +from __future__ import annotations -import numpy as np +from abc import ABC, abstractmethod +from collections.abc import Iterable +from typing import TYPE_CHECKING, Any, Sequence, cast +import numpy as np from mygmm import GMM -from .helper_functions import (nice_errors, ajd_drift, ajd_diff, - rolling_window, columnwise_prod, instruments) -try: - from .simulate import simulate -except: - print('Failed to import cython modules. ' - + 'Temporary hack to compile documentation.') -__all__ = ['SDE'] +from affidiff.helper_functions import ajd_diff, ajd_drift, columnwise_prod, instruments, nice_errors, rolling_window +if TYPE_CHECKING: + from affidiff.param_generic import GenericParam + +try: + from affidiff.simulate import simulate # type: ignore +except Exception: + simulate = None -class SDE(object): +class SDE(ABC): """Generic Model. Attributes @@ -45,7 +43,7 @@ class SDE(object): """ - def __init__(self, param=None): + def __init__(self, param: Any = None) -> None: # noqa: ANN401 """Initialize the class. Parameters @@ -54,12 +52,12 @@ def __init__(self, param=None): True parameters used for simulation of the data """ - self.nsub = None - self.ndiscr = None - self.param = param - self.errors = None + self.nsub: int | None = None + self.ndiscr: int | None = None + self.param: Any = param + self.errors: np.ndarray | None = None - def update_theta(self, param): + def update_theta(self, param: GenericParam | object) -> None: """Update model parameters. Parameters @@ -70,75 +68,141 @@ def update_theta(self, param): """ self.param = param - def euler_loc(self, state, theta): + @abstractmethod + def get_start(self) -> np.ndarray | list[float]: + """Return starting values for simulation.""" + raise NotImplementedError("Must be overridden") + + @staticmethod + def realized_depvar(*, data: Any, subset: Any = None) -> Any: # noqa: ANN401 + """Realized dependent variables.""" + raise NotImplementedError("Must be overridden") + + def mat_a(self, *, param: Any, subset: Any = None) -> Any: # noqa: ANN401 + """Matrix A in integrated moments.""" + raise NotImplementedError("Must be overridden") + + def realized_const( + self, + *, + param: Any = None, # noqa: ANN401 + aggh: Any = 1, # noqa: ANN401 + subset: Any = None, # noqa: ANN401 + ) -> Any: # noqa: ANN401 + """Realized constant in integrated moments.""" + raise NotImplementedError("Must be overridden") + + def euler_loc(self, *, state: np.ndarray, theta: GenericParam | object) -> np.ndarray: """Euler location. Parameters ---------- - state : (nvars, nsim) array_like + state : (nsim, nvars) array Current value of the process theta : parameter instance - Model parameter + Model parameters Returns ------- - (nvars, nsim) array_like + (nsim, nvars) array + Location term in Euler discretization """ - return ajd_drift(state, theta) / self.nsub + return ajd_drift(state=state, theta=theta) - def euler_scale(self, state, theta): + def euler_scale(self, *, state: np.ndarray, theta: GenericParam | object) -> np.ndarray: """Euler scale. Parameters ---------- - state : (nvars, nsim) array_like + state : (nsim, nvars) array Current value of the process theta : parameter instance - Model parameter + Model parameters Returns ------- - (nvars, nvars, nsim) array_like + (nsim, nvars, nvars) array + Scale term in Euler discretization """ - return ajd_diff(state, theta) / self.nsub**.5 + return ajd_diff(state=state, theta=theta) - def exact_loc(self, state, theta): - """Eaxct location. + def loc(self, *, state: np.ndarray, theta: GenericParam | object) -> np.ndarray: + """Location. Parameters ---------- - state : (nvars, nsim) array_like + state : (nsim, nvars) array Current value of the process theta : parameter instance - Model parameter + Model parameters Returns ------- - (nvars, nsim) array_like + (nsim, nvars) array + Location term in Euler discretization """ - return self.euler_loc(state, theta) + return self.euler_loc(state=state, theta=theta) - def exact_scale(self, state, theta): - """Exact scale. + def scale(self, *, state: np.ndarray, theta: GenericParam | object) -> np.ndarray: + """Scale. Parameters ---------- - state : (nvars, nsim) array_like + state : (nsim, nvars) array Current value of the process theta : parameter instance - Model parameter + Model parameters Returns ------- - (nvars, nvars, nsim) array_like + (nsim, nvars, nvars) array + Scale term in Euler discretization """ - return self.euler_scale(state, theta) - - def depvar_unc_mean(self, param, aggh): + return self.euler_scale(state=state, theta=theta) + + def exact_loc(self, *, state: np.ndarray, theta: GenericParam | object) -> np.ndarray: + """Exact location.""" + return self.euler_loc(state=state, theta=theta) + + def exact_scale(self, *, state: np.ndarray, theta: GenericParam | object) -> np.ndarray: + """Exact scale.""" + return self.euler_scale(state=state, theta=theta) + + @staticmethod + def mean_vol(*, param: Any, aggh: float) -> float: # noqa: ANN401 + """Unconditional mean of volatility.""" + raise NotImplementedError + + @staticmethod + def mean_vol2(*, param: Any, aggh: float) -> float: # noqa: ANN401 + """Unconditional mean of squared volatility.""" + raise NotImplementedError + + @staticmethod + def mean_ret(*, param: Any, aggh: float) -> float: # noqa: ANN401 + """Unconditional mean of returns.""" + raise NotImplementedError + + @staticmethod + def mean_cross(*, param: Any, aggh: float) -> float: # noqa: ANN401 + """Unconditional mean of returns times volatility.""" + raise NotImplementedError + + def momcond( + self, + *, + theta: Any, # noqa: ANN401 + data: Any = None, # noqa: ANN401 + instrlag: int = 1, + ) -> tuple[np.ndarray, np.ndarray]: + """Moment conditions.""" + raise NotImplementedError + + def depvar_unc_mean(self, *, param: GenericParam | object, aggh: float) -> np.ndarray: """Unconditional means of realized data. Parameters @@ -153,12 +217,16 @@ def depvar_unc_mean(self, param, aggh): array """ - return np.array([self.mean_vol(param, aggh), - self.mean_vol2(param, aggh), - self.mean_ret(param, aggh), - self.mean_cross(param, aggh)]) - - def update(self, state, error): + return np.array( + [ + self.mean_vol(param=param, aggh=aggh), + self.mean_vol2(param=param, aggh=aggh), + self.mean_ret(param=param, aggh=aggh), + self.mean_cross(param=param, aggh=aggh), + ] + ) + + def update(self, *, state: np.ndarray, error: np.ndarray) -> np.ndarray: """Euler update function. Parameters @@ -175,23 +243,27 @@ def update(self, state, error): """ # (nsim, nvars) array_like - loc = self.euler_loc(state, self.param) + loc = self.euler_loc(state=state, theta=self.param) # (nsim, nvars, nvars) array_like - scale = self.euler_scale(state, self.param) - - new_state = loc / self.ndiscr \ - + (np.transpose(scale, axes=[1, 2, 0]) * error.T).sum(1).T \ - / self.ndiscr**.5 + scale = self.euler_scale(state=state, theta=self.param) - # Equivalent operation through the loop: -# new_state = loc / self.ndiscr -# for i in range(error.shape[0]): -# new_state[i] += (scale[i] * error[i]).sum(1) / self.ndiscr**.5 + assert self.ndiscr is not None + new_state = loc / self.ndiscr + (np.transpose(scale, axes=[1, 2, 0]) * error.T).sum(1).T / self.ndiscr**0.5 return new_state - def simulate(self, start=None, nsub=80, ndiscr=1, nobs=500, nsim=1, - diff=None, new_innov=True, cython=False): + def simulate( + self, + *, + start: Any = None, # noqa: ANN401 + nsub: int = 80, + ndiscr: int = 1, + nobs: int = 500, + nsim: int = 1, + diff: Any = None, # noqa: ANN401 + new_innov: bool = True, + cython: bool = False, + ) -> Any: # noqa: ANN401 """Simulate observations from the model. Parameters @@ -224,7 +296,7 @@ def simulate(self, start=None, nsub=80, ndiscr=1, nobs=500, nsim=1, if start is None: start = self.get_start() if np.size(self.param.mat_k0) != np.size(start): - raise ValueError('Start for paths is of wrong dimension!') + raise ValueError("Start for paths is of wrong dimension!") self.nsub = nsub self.ndiscr = ndiscr nvars = np.size(start) @@ -234,23 +306,28 @@ def simulate(self, start=None, nsub=80, ndiscr=1, nobs=500, nsim=1, # Generate new errors self.errors = np.random.normal(size=(npoints, nsim, nvars)) # Standardize the errors - self.errors = nice_errors(self.errors, 1) + self.errors = nice_errors(errors=self.errors, sdim=1) if cython: + assert simulate is not None dt = 1 / ndiscr / nsub - paths = simulate(self.errors, np.atleast_1d(start).astype(float), - np.atleast_1d(self.param.mat_k0).astype(float), - np.atleast_2d(self.param.mat_k1).astype(float), - np.atleast_2d(self.param.mat_h0).astype(float), - np.atleast_3d(self.param.mat_h1).astype(float), - float(dt)) + + paths = simulate( + self.errors, + np.atleast_1d(start).astype(float), + np.atleast_1d(self.param.mat_k0).astype(float), + np.atleast_2d(self.param.mat_k1).astype(float), + np.atleast_2d(self.param.mat_h0).astype(float), + np.atleast_3d(self.param.mat_h1).astype(float), + float(dt), + ) else: nsim = self.errors.shape[1] paths = start * np.ones((npoints + 1, nsim, nvars)) for i in range(npoints): # (nsim, nvars) - paths[i+1] = paths[i] + self.update(paths[i], self.errors[i]) + paths[i + 1] = paths[i] + self.update(state=paths[i], error=self.errors[i]) # (nobs+1, nsim, nvars) paths = paths[::ndiscr] @@ -258,9 +335,19 @@ def simulate(self, start=None, nsub=80, ndiscr=1, nobs=500, nsim=1, paths[1:, :, diff] = paths[1:, :, diff] - paths[:-1, :, diff] return paths[1:] - def sim_realized(self, start=None, nsub=80, ndiscr=10, aggh=1, - nperiods=500, nsim=1, diff=None, new_innov=True, - cython=True): + def sim_realized( + self, + *, + start: Any = None, # noqa: ANN401 + nsub: int = 80, + ndiscr: int = 10, + aggh: int = 1, + nperiods: int = 500, + nsim: int = 1, + diff: Any = None, # noqa: ANN401 + new_innov: bool = True, + cython: bool = False, + ) -> tuple[Any, Any]: # noqa: ANN401 """Simulate realized returns and variance from the model. Parameters @@ -297,20 +384,32 @@ def sim_realized(self, start=None, nsub=80, ndiscr=10, aggh=1, if start is None: start = self.get_start() nobs = nperiods * nsub - paths = self.simulate(start, nsub=nsub, ndiscr=ndiscr, - nobs=nobs, nsim=nsim, diff=diff, - new_innov=new_innov, cython=cython) + paths = self.simulate( + start=start, nsub=nsub, ndiscr=ndiscr, nobs=nobs, nsim=nsim, diff=diff, new_innov=new_innov, cython=cython + ) returns = paths[:, 0, 0].reshape((nperiods, nsub)) # Compute realized var and returns over one day rvar = (returns**2).sum(1) returns = returns.sum(1) # Aggregate over arbitrary number of days - rvar = rolling_window(np.mean, rvar, window=aggh) - returns = rolling_window(np.mean, returns, window=aggh) + rvar = rolling_window(fun=np.mean, mat=rvar, window=aggh) + returns = rolling_window(fun=np.mean, mat=returns, window=aggh) return returns, rvar - def sim_realized_pq(self, start_p=None, start_q=None, - aggh=[1, 1], **kwargs): + def sim_realized_pq( + self, + *, + start_p: np.ndarray | Sequence[float] | None = None, + start_q: np.ndarray | Sequence[float] | None = None, + aggh: list[int] | tuple[int, int] | None = None, + nsub: int = 80, + ndiscr: int = 10, + nperiods: int = 500, + nsim: int = 1, + diff: Any = None, # noqa: ANN401 + new_innov: bool = True, + cython: bool = False, + ) -> tuple[tuple[np.ndarray, np.ndarray], tuple[np.ndarray, np.ndarray]]: """Simulate realized data from the model under both P and Q. Parameters @@ -321,8 +420,20 @@ def sim_realized_pq(self, start_p=None, start_q=None, Starting value for simulation under Q aggh : list Aggregation windows for P and Q respectively - kwargs : dict - Anything that needs to go through sim_realized + nsub : int + Number of subintervals for latent simulation (fractions of the day) + ndiscr : int + Number of Euler discretization points inside unit interval + nperiods : int + Number of points to simulate in one series (days) + nsim : int + Number of time series to simulate + diff : int + Dimensions which should be differentiated + new_innov : bool + Whether to generate new innovations (True), or use already stored (False) + cython : bool + Whether to use cython-optimized simulation (True) or not (False) Returns ------- @@ -331,48 +442,119 @@ def sim_realized_pq(self, start_p=None, start_q=None, data_q : tuple Returns and realized variance under Q - Notes - ----- - For argumentsts see sim_realized - """ + if aggh is None: + aggh = [1, 1] if start_p is None: start_p = self.get_start() - data_p = self.sim_realized(start_p, aggh=aggh[0], - new_innov=True, **kwargs) + data_p = self.sim_realized( + start=start_p, + aggh=aggh[0], + new_innov=new_innov, + nsub=nsub, + ndiscr=ndiscr, + nperiods=nperiods, + nsim=nsim, + diff=diff, + cython=cython, + ) self.param.convert_to_q() if start_q is None: start_q = self.get_start() - data_q = self.sim_realized(start_q, aggh=aggh[1], - new_innov=False, **kwargs) + data_q = self.sim_realized( + start=start_q, + aggh=aggh[1], + new_innov=new_innov, + nsub=nsub, + ndiscr=ndiscr, + nperiods=nperiods, + nsim=nsim, + diff=diff, + cython=cython, + ) return data_p, data_q - def gmmest(self, theta_start, **kwargs): + def gmmest( + self, + *, + theta_start: Any, # noqa: ANN401 + data: Any = None, # noqa: ANN401 + instrlag: int = 1, + iter: int = 2, + method: str = "BFGS", + kernel: str = "Bartlett", + band: int | None = None, + ) -> object: """Estimate model parameters using GMM. Parameters ---------- - theta_start : array + theta_start : parameter instance Initial parameter values for estimation - kwargs : dict - Anything that needs to go through mygmm + data : array_like, optional + Data passed to the moment condition function + instrlag : int + Number of lags for the instruments + iter : int + Number of GMM iterations + method : str + Optimization method passed to scipy.optimize.minimize + kernel : str + HAC kernel for weighting matrix ('Bartlett', 'Parzen', etc.) + band : int, optional + HAC bandwidth. If None, chosen automatically. Notes ----- - For arguments see momcond + For moment condition arguments see momcond. """ estimator = GMM(self.momcond) - return estimator.gmmest(theta_start.get_theta(), **kwargs) - - def integrated_gmm(self, param_start, subset='all', measure='P', - names=None, bounds=None, constraints=(), **kwargs): + return estimator.gmmest( + theta_start.get_theta(), + data=data, + instrlag=instrlag, + iter=iter, + method=method, + kernel=kernel, + band=band, + ) + + def integrated_gmm( + self, + *, + param_start: GenericParam | object, + data: object = None, + instr_data: object = None, + instr_choice: str = "const", + aggh: object = 1, + instrlag: int = 1, + subset: str = "all", + measure: str = "P", + names: list[str] | None = None, + bounds: list[tuple[float | None, float | None]] | None = None, + constraints: object = (), + iter: int = 2, + method: str = "BFGS", + kernel: str = "Bartlett", + band: int | None = None, + ) -> object: """Estimate model parameters using Integrated GMM. Parameters ---------- param_start : parameter class Initial parameter values for estimation + data : array_like, optional + Returns and realized variance used in moment conditions + instr_data : array_like, optional + Instruments (no lags) + instr_choice : str {'const', 'var'} + Choice of instruments + aggh : int or list of int + Number of intervals (days) to aggregate over using rolling mean + instrlag : int + Number of lags for the instruments subset : str Which parameters to estimate. Belongs to @@ -392,16 +574,18 @@ def integrated_gmm(self, param_start, subset='all', measure='P', Parameter bounds constraints : dict or sequence of dict Equality and inequality constraints. See scipy.optimize.minimize - kwargs : dict - Anything that needs to go through mygmm - - Notes - ----- - For arguments see integrated_mom + iter : int + Number of GMM iterations + method : str + Optimization method passed to scipy.optimize.minimize + kernel : str + HAC kernel for weighting matrix ('Bartlett', 'Parzen', etc.) + band : int, optional + HAC bandwidth. If None, chosen automatically. """ estimator = GMM(self.integrated_mom) - self.param = param_start + self.param: Any = param_start theta_start = self.param.get_theta(subset=subset, measure=measure) if names is None: names = self.param.get_names(subset=subset, measure=measure) @@ -409,13 +593,36 @@ def integrated_gmm(self, param_start, subset='all', measure='P', bounds = self.param.get_bounds(subset=subset, measure=measure) if constraints == (): constraints = self.param.get_constraints() - return estimator.gmmest(theta_start, names=names, subset=subset, - measure=measure, bounds=bounds, - constraints=constraints, **kwargs) - - def integrated_mom(self, theta, data=None, instr_data=None, - instr_choice='const', aggh=1, subset='all', - instrlag=1, measure='P', **kwargs): + return estimator.gmmest( + theta_start, + names=names, + data=data, + instr_data=instr_data, + instr_choice=instr_choice, + aggh=aggh, + instrlag=instrlag, + subset=subset, + measure=measure, + bounds=bounds, + constraints=constraints, + iter=iter, + method=method, + kernel=kernel, + band=band, + ) + + def integrated_mom( + self, + *, + theta: np.ndarray | Sequence[float], + data: object = None, + instr_data: object = None, + instr_choice: str = "const", + aggh: object = 1, + subset: str = "all", + instrlag: int = 1, + measure: str = "P", + ) -> tuple[np.ndarray, Any]: """Integrated moment function. Parameters @@ -455,36 +662,41 @@ def integrated_mom(self, theta, data=None, instr_data=None, """ subset_sl = None - if subset == 'vol': + if subset == "vol": subset_sl = slice(2) self.param.update(theta=theta, subset=subset, measure=measure) lag = 2 - if measure == 'PQ': + if measure == "PQ": error = [] - for data_x, agg, meas in zip(data, aggh, measure): - if meas == 'Q': + data_list = list(cast(Iterable, data)) if data is not None else [] + aggh_list = list(aggh) if isinstance(aggh, (list, tuple)) else [aggh, aggh] # type: ignore[arg-type] + measure_list = list(measure) + for data_x, agg, meas in zip(data_list, aggh_list, measure_list, strict=False): + if meas == "Q": self.param.convert_to_q() - depvar = self.realized_depvar(data_x)[lag:] + depvar = self.realized_depvar(data=data_x)[lag:] # (nobs - lag, 4) array - error.append(depvar.dot(self.mat_a(self.param, subset_sl).T) \ - - self.realized_const(self.param, agg, subset_sl)) + error.append( + depvar.dot(self.mat_a(param=self.param, subset=subset_sl).T) + - self.realized_const(param=self.param, aggh=agg, subset=subset_sl) + ) error = np.hstack(error) else: - depvar = self.realized_depvar(data)[lag:] + depvar = self.realized_depvar(data=data)[lag:] # type: ignore[arg-type] # (nobs - lag, 4) array - error = depvar.dot(self.mat_a(self.param, subset_sl).T) \ - - self.realized_const(self.param, aggh, subset_sl) + error = depvar.dot(self.mat_a(param=self.param, subset=subset_sl).T) - self.realized_const( + param=self.param, aggh=aggh, subset=subset_sl + ) nobs = error.shape[0] + lag # self.instruments(data, instrlag=instrlag): (nobs, ninstr*instrlag+1) # (nobs-lag, ninstr*instrlag+1) - instr = instruments(instr_data, nobs=nobs, instrlag=instrlag, - instr_choice=instr_choice)[:-lag] + instr = instruments(data=instr_data, nobs=nobs, instrlag=instrlag, instr_choice=instr_choice)[:-lag] # (nobs - instrlag - lag, 4 * (ninstr*instrlag + 1)) - moms = columnwise_prod(error, instr) + moms = columnwise_prod(left=error, right=instr) return moms, None diff --git a/src/affidiff/model_heston.py b/src/affidiff/model_heston.py index d0dc470..b96f1c3 100644 --- a/src/affidiff/model_heston.py +++ b/src/affidiff/model_heston.py @@ -1,31 +1,25 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Heston model class -~~~~~~~~~~~~~~~~~~ +"""Heston model class.""" -""" -from __future__ import print_function, division +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, cast import numpy as np from statsmodels.tsa.tsatools import lagmat -from .model_generic import SDE -from .param_heston import HestonParam +from affidiff.model_generic import SDE +from affidiff.param_heston import HestonParam -__all__ = ['Heston'] +if TYPE_CHECKING: + pass class Heston(SDE): + """Heston model.""" - """Heston model. - - Methods - ------- + param: Any - """ - - def __init__(self, param=None): + def __init__(self, param: Any = None) -> None: # noqa: ANN401 """Initialize the class. Parameters @@ -36,9 +30,9 @@ def __init__(self, param=None): """ if param is None: param = HestonParam() - super(Heston, self).__init__(param) + super().__init__(param) - def get_start(self): + def get_start(self) -> list[float]: """Get starting values for simulation. Returns @@ -47,10 +41,10 @@ def get_start(self): Starting values for price and variance """ - return [1, self.param.mean_v] + return [1.0, float(self.param.mean_v)] @staticmethod - def coef_big_a(param, aggh): + def coef_big_a(*, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient A_h in exact discretization of volatility. Parameters @@ -65,9 +59,9 @@ def coef_big_a(param, aggh): float """ - return np.exp(-param.kappa * aggh) + return float(np.exp(-param.kappa * aggh)) - def coef_big_c(self, param, aggh): + def coef_big_c(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient C_h in exact discretization of volatility. Parameters @@ -82,9 +76,9 @@ def coef_big_c(self, param, aggh): float """ - return param.mean_v * (1 - self.coef_big_a(param, aggh)) + return float(param.mean_v * (1 - self.coef_big_a(param=param, aggh=aggh))) - def coef_small_a(self, param, aggh): + def coef_small_a(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient a_h in exact discretization of volatility. Parameters @@ -99,14 +93,14 @@ def coef_small_a(self, param, aggh): float """ - return (1 - self.coef_big_a(param, aggh)) / param.kappa / aggh + return float((1 - self.coef_big_a(param=param, aggh=aggh)) / param.kappa / aggh) - def coef_small_c(self, param, aggh): + def coef_small_c(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Coefficient c_h in exact discretization of volatility. Parameters ---------- - theta : (nparams, ) array + param : parameter instance Parameter vector aggh : float Interval length @@ -116,10 +110,10 @@ def coef_small_c(self, param, aggh): float """ - return param.mean_v * (1 - self.coef_small_a(param, aggh)) + return float(param.mean_v * (1 - self.coef_small_a(param=param, aggh=aggh))) @staticmethod - def mean_vol(param, aggh): + def mean_vol(*, param: Any, aggh: float) -> float: # noqa: ARG004, ANN401 """Unconditional mean of realized volatiliy. Parameters @@ -134,9 +128,9 @@ def mean_vol(param, aggh): float """ - return param.mean_v + return float(param.mean_v) - def mean_vol2(self, param, aggh): + def mean_vol2(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Unconditional mean of squared realized volatiliy. Parameters @@ -151,11 +145,12 @@ def mean_vol2(self, param, aggh): float """ - return ((param.eta / param.kappa)**2 - * self.coef_small_c(param, aggh) / aggh + param.mean_v**2) + return float( + (param.eta / param.kappa) ** 2 * self.coef_small_c(param=param, aggh=aggh) / aggh + param.mean_v**2 + ) @staticmethod - def mean_ret(param, aggh): + def mean_ret(*, param: Any, aggh: float) -> float: # noqa: ARG004, ANN401 """Unconditional mean of realized returns. Parameters @@ -170,9 +165,9 @@ def mean_ret(param, aggh): float """ - return (param.lmbd - .5) * param.mean_v + return float((param.lmbd - 0.5) * param.mean_v) - def mean_cross(self, param, aggh): + def mean_cross(self, *, param: Any, aggh: float) -> float: # noqa: ANN401 """Unconditional mean of realized returns times volatility. Parameters @@ -187,11 +182,13 @@ def mean_cross(self, param, aggh): float """ - return ((param.lmbd - .5) * self.mean_vol2(param, aggh) - + param.rho * param.eta / param.kappa - * self.coef_small_c(param, aggh) / aggh) + p = param + val = (p.lmbd - 0.5) * self.mean_vol2(param=param, aggh=aggh) + ( + p.rho * p.eta / p.kappa * self.coef_small_c(param=param, aggh=aggh) / aggh + ) + return float(val) - def realized_const(self, param, aggh, subset=None): + def realized_const(self, *, param: Any = None, aggh: float = 1, subset: slice | None = None) -> np.ndarray: # noqa: ANN401 """Intercept in the realized moment conditions. Parameters @@ -209,13 +206,16 @@ def realized_const(self, param, aggh, subset=None): Intercept """ - return ((self.mat_a0(param, 1) - + self.mat_a1(param, 1) - + self.mat_a2(param, 1)) - * self.depvar_unc_mean(param, aggh)).sum(1)[subset].squeeze() + res = ( + (self.mat_a0(param=param, aggh=1) + self.mat_a1(param=param, aggh=1) + self.mat_a2(param=param, aggh=1)) + * self.depvar_unc_mean(param=param, aggh=aggh) + ).sum(1) + if subset is not None: + res = res[subset] + return np.squeeze(res) @staticmethod - def mat_a0(param, aggh): + def mat_a0(*, param: Any, aggh: float) -> np.ndarray: # noqa: ARG004, ANN401 """Matrix A_0 in integrated moments. Parameters @@ -233,7 +233,7 @@ def mat_a0(param, aggh): """ return np.diag([0, 1, 0, 0]).astype(float) - def mat_a1(self, param, aggh): + def mat_a1(self, *, param: Any, aggh: float) -> np.ndarray: # noqa: ARG002, ANN401 """Matrix A_1 in integrated moments. Parameters @@ -250,12 +250,11 @@ def mat_a1(self, param, aggh): """ mat_a = np.diag([1, 0, 0, 1]).astype(float) - mat_a[1, 1] = -self.coef_big_a(param, 1) \ - * (1 + self.coef_big_a(param, 1)) - mat_a[3, 1] = .5 - param.lmbd + mat_a[1, 1] = -self.coef_big_a(param=param, aggh=1) * (1 + self.coef_big_a(param=param, aggh=1)) + mat_a[3, 1] = 0.5 - param.lmbd return mat_a - def mat_a2(self, param, aggh): + def mat_a2(self, *, param: Any, aggh: float) -> np.ndarray: # noqa: ARG002, ANN401 """Matrix A_2 in integrated moments. Parameters @@ -271,14 +270,19 @@ def mat_a2(self, param, aggh): Matrix A_2 """ - mat_a = np.diag([-self.coef_big_a(param, 1), - self.coef_big_a(param, 1)**3, 1, - -self.coef_big_a(param, 1)]) - mat_a[2, 0] = .5 - param.lmbd - mat_a[3, 1] = (param.lmbd - .5) * self.coef_big_a(param, 1) + mat_a = np.diag( + [ + -self.coef_big_a(param=param, aggh=1), + self.coef_big_a(param=param, aggh=1) ** 3, + 1, + -self.coef_big_a(param=param, aggh=1), + ] + ) + mat_a[2, 0] = 0.5 - param.lmbd + mat_a[3, 1] = (param.lmbd - 0.5) * self.coef_big_a(param=param, aggh=1) return mat_a - def mat_a(self, param, subset=None): + def mat_a(self, *, param: Any, subset: slice | None = None) -> np.ndarray: # noqa: ANN401 """Matrix A in integrated moments. Parameters @@ -294,15 +298,19 @@ def mat_a(self, param, subset=None): Matrix A """ - mat_a = (self.mat_a0(param, 1), - self.mat_a1(param, 1), - self.mat_a2(param, 1)) - return np.hstack(mat_a)[subset].squeeze() + mat_a_tuple = ( + self.mat_a0(param=param, aggh=1), + self.mat_a1(param=param, aggh=1), + self.mat_a2(param=param, aggh=1), + ) + res = np.hstack(mat_a_tuple) + if subset is not None: + res = res[subset] + return np.squeeze(res) @staticmethod - def realized_depvar(data, subset=None): - """Array of the left-hand side variables - in realized moment conditions. + def realized_depvar(*, data: Any, subset: slice | None = None) -> np.ndarray: # noqa: ANN401 + """Array of the left-hand side variables in realized moment conditions. Parameters ---------- @@ -317,6 +325,10 @@ def realized_depvar(data, subset=None): Dependend variables """ - ret, rvar = data - var = np.vstack([rvar, rvar**2, ret, ret * rvar])[subset].squeeze() - return lagmat(var.T, maxlag=2, original='in') + data_arr = np.asarray(data) + ret, rvar = data_arr[0], data_arr[1] + var = np.vstack([rvar, rvar**2, ret, ret * rvar]) + if subset is not None: + var = var[subset] + var_s = np.squeeze(var) + return cast(np.ndarray, lagmat(var_s.T, maxlag=2, original="in")) diff --git a/src/affidiff/model_vasicek.py b/src/affidiff/model_vasicek.py index a66b289..212003d 100644 --- a/src/affidiff/model_vasicek.py +++ b/src/affidiff/model_vasicek.py @@ -1,24 +1,19 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Vasicek model class -~~~~~~~~~~~~~~~~~~~ +"""Vasicek model class.""" -""" -from __future__ import print_function, division +from __future__ import annotations -from .model_generic import SDE +from typing import TYPE_CHECKING, Any -__all__ = ['Vasicek'] +from affidiff.model_generic import SDE +if TYPE_CHECKING: + import numpy as np -class Vasicek(SDE): - - """Vasicek model. - """ +class Vasicek(SDE): + """Vasicek model.""" - def __init__(self, param=None): + def __init__(self, param: Any = None) -> None: # noqa: ANN401 """Initialize the class. Parameters @@ -27,10 +22,21 @@ def __init__(self, param=None): True parameters used for simulation of the data """ - super(Vasicek, self).__init__(param) + super().__init__(param) + + def get_start(self) -> list[float]: + """Get starting values for simulation. + + Returns + ------- + list[float] + Starting value at the long-run mean + + """ + return [float(self.param.mean)] @staticmethod - def drift(state, theta): + def drift(*, state: np.ndarray | float, theta: Any) -> np.ndarray | float: # noqa: ANN401 """Drift function. Parameters @@ -49,7 +55,7 @@ def drift(state, theta): return theta.kappa * (theta.mean - state) @staticmethod - def diff(state, theta): + def diff(*, state: np.ndarray | float, theta: Any) -> float: # noqa: ARG004, ANN401 """Diffusion (instantaneous volatility) function. Parameters @@ -68,5 +74,5 @@ def diff(state, theta): return theta.eta -if __name__ == '__main__': +if __name__ == "__main__": pass diff --git a/src/affidiff/param_cir.py b/src/affidiff/param_cir.py index 9850cae..0577cae 100644 --- a/src/affidiff/param_cir.py +++ b/src/affidiff/param_cir.py @@ -1,21 +1,18 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -CIR parameter class -~~~~~~~~~~~~~~~~~~~ +"""CIR parameter class.""" -""" -from __future__ import print_function, division +from __future__ import annotations + +from typing import TYPE_CHECKING, Sequence import numpy as np -from .param_generic import GenericParam +from affidiff.param_generic import GenericParam -__all__ = ['CIRparam'] +if TYPE_CHECKING: + from typing_extensions import Self class CIRparam(GenericParam): - """Parameter storage for CIR model. Attributes @@ -31,7 +28,7 @@ class CIRparam(GenericParam): """ - def __init__(self, mean=.5, kappa=1.5, eta=.1, measure='P'): + def __init__(self, *, mean: float = 0.5, kappa: float = 1.5, eta: float = 0.1, measure: str = "P") -> None: # noqa: ARG002 """Initialize class. Parameters @@ -49,13 +46,14 @@ def __init__(self, mean=.5, kappa=1.5, eta=.1, measure='P'): - 'Q' : risk-neutral """ + super().__init__() self.mean = mean self.kappa = kappa self.eta = eta - self.measure = 'P' + self.measure = "P" self.update_ajd() - def is_valid(self): + def is_valid(self) -> bool: """Check validity of parameters. Returns @@ -66,20 +64,18 @@ def is_valid(self): """ posit = (self.kappa > 0) & (self.eta > 0) feller = 2 * self.kappa * self.mean - self.eta**2 > 0 - return posit & feller - - def update_ajd(self): - """Update AJD representation. + return bool(posit & feller) - """ + def update_ajd(self) -> None: + """Update AJD representation.""" # AJD parameters self.mat_k0 = self.kappa * self.mean self.mat_k1 = -self.kappa - self.mat_h0 = 0. + self.mat_h0 = 0.0 self.mat_h1 = self.eta**2 @classmethod - def from_theta(cls, theta): + def from_theta(cls, *, theta: np.ndarray | Sequence[float]) -> Self: """Initialize parameters from parameter vector. Parameters @@ -88,24 +84,28 @@ def from_theta(cls, theta): Parameter vector """ - param = cls(mean=theta[0], kappa=theta[1], eta=theta[2]) + param = cls(mean=float(theta[0]), kappa=float(theta[1]), eta=float(theta[2])) param.update_ajd() return param - def update(self, theta): + def update(self, *, theta: np.ndarray | Sequence[float], subset: str = "all", measure: str = "P") -> None: # noqa: ARG002 """Update attributes from parameter vector. Parameters ---------- theta : (nparams, ) array Parameter vector + subset : str + Which parameters to update + measure : str + Probability measure """ - self.mean, self.kappa, self.eta = theta + self.mean, self.kappa, self.eta = float(theta[0]), float(theta[1]), float(theta[2]) self.update_ajd() @staticmethod - def get_model_name(): + def get_model_name() -> str: """Return model name. Returns @@ -114,10 +114,10 @@ def get_model_name(): Parameter vector """ - return 'CIR' + return "CIR" @staticmethod - def get_names(subset='all', measure='PQ'): + def get_names(*, subset: str = "all", measure: str = "PQ") -> list[str]: # noqa: ARG004 """Return parameter names. Returns @@ -126,9 +126,9 @@ def get_names(subset='all', measure='PQ'): Parameter names """ - return ['mean', 'kappa', 'eta'] + return ["mean", "kappa", "eta"] - def get_theta(self, subset='all', measure='PQ'): + def get_theta(self, *, subset: str = "all", measure: str = "PQ") -> np.ndarray: # noqa: ARG002 """Return vector of parameters. Returns diff --git a/src/affidiff/param_ct.py b/src/affidiff/param_ct.py index caea991..eca908a 100644 --- a/src/affidiff/param_ct.py +++ b/src/affidiff/param_ct.py @@ -1,23 +1,19 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -CT parameter class -~~~~~~~~~~~~~~~~~~ +"""CT parameter class.""" -""" -from __future__ import print_function, division +from __future__ import annotations import warnings +from typing import TYPE_CHECKING, Any, Sequence import numpy as np -from .param_generic import GenericParam +from affidiff.param_generic import GenericParam -__all__ = ['CentTendParam'] +if TYPE_CHECKING: + from typing_extensions import Self class CentTendParam(GenericParam): - """Parameter storage for CT model. Attributes @@ -43,9 +39,21 @@ class CentTendParam(GenericParam): """ - def __init__(self, riskfree=.0, lmbd=.1, lmbd_s=.0, lmbd_y=.0, - mean_v=.5, kappa_s=1.5, kappa_y=.5, - eta_s=.1, eta_y=.01, rho=-.5, measure='P'): + def __init__( + self, + *, + riskfree: float = 0.0, + lmbd: float = 0.1, + lmbd_s: float = 0.0, + lmbd_y: float = 0.0, + mean_v: float = 0.5, + kappa_s: float = 1.5, + kappa_y: float = 0.5, + eta_s: float = 0.1, + eta_y: float = 0.01, + rho: float = -0.5, + measure: str = "P", + ) -> None: """Initialize class. Parameters @@ -77,6 +85,7 @@ def __init__(self, riskfree=.0, lmbd=.1, lmbd_s=.0, lmbd_y=.0, - 'Q' : risk-neutral """ + super().__init__() self.riskfree = riskfree self.kappa_s = kappa_s self.kappa_y = kappa_y @@ -87,14 +96,14 @@ def __init__(self, riskfree=.0, lmbd=.1, lmbd_s=.0, lmbd_y=.0, self.eta_y = eta_y self.eta_s = eta_s self.rho = rho - self.scale = 1 - self.measure = 'P' - if measure == 'Q': + self.scale = 1.0 + self.measure = "P" + if measure == "Q": self.convert_to_q() self.update_ajd() @staticmethod - def get_model_name(): + def get_model_name() -> str: """Return model name. Returns @@ -103,10 +112,10 @@ def get_model_name(): Parameter vector """ - return 'Central Tendency' + return "Central Tendency" @staticmethod - def get_names(subset='all', measure='PQ'): + def get_names(*, subset: str = "all", measure: str = "PQ") -> list[str]: """Return parameter names. Parameters @@ -129,54 +138,47 @@ def get_names(subset='all', measure='PQ'): Parameter names """ - names = ['mean_v', 'kappa_s', 'kappa_y', 'eta_s', 'eta_y', - 'rho', 'lmbd', 'lmbd_s', 'lmbd_y'] + names = ["mean_v", "kappa_s", "kappa_y", "eta_s", "eta_y", "rho", "lmbd", "lmbd_s", "lmbd_y"] - if subset == 'all' and measure == 'PQ': + if subset == "all" and measure == "PQ": return names - elif subset == 'all' and measure in ('P', 'Q'): + elif subset == "all" and measure in ("P", "Q"): return names[:-2] - elif subset == 'vol' and measure == 'PQ': + elif subset == "vol" and measure == "PQ": return names[:5] + names[-2:] - elif subset == 'vol' and measure in ('P', 'Q'): + elif subset == "vol" and measure in ("P", "Q"): return names[:5] else: - raise NotImplementedError('Keyword variable is not supported!') - - def convert_to_q(self): - """Convert parameters to risk-neutral version. + raise NotImplementedError("Keyword variable is not supported!") - """ - if self.measure == 'Q': - warnings.warn('Parameters are already converted to Q!') + def convert_to_q(self) -> None: + """Convert parameters to risk-neutral version.""" + if self.measure == "Q": + warnings.warn("Parameters are already converted to Q!", stacklevel=2) else: kappa_sp = self.kappa_s kappa_yp = self.kappa_y self.kappa_s = self.kappa_s - self.lmbd_s * self.eta_s self.kappa_y = self.kappa_y - self.lmbd_y * self.eta_y self.scale = kappa_sp / self.kappa_s - self.mean_v *= (kappa_yp / self.kappa_y * self.scale) - self.lmbd = 0 - self.eta_y *= (self.scale**.5) - self.measure = 'Q' + self.mean_v *= kappa_yp / self.kappa_y * self.scale + self.lmbd = 0.0 + self.eta_y *= self.scale**0.5 + self.measure = "Q" self.update_ajd() - def update_ajd(self): - """Update AJD representation. - - """ + def update_ajd(self) -> None: + """Update AJD representation.""" # AJD parameters - self.mat_k0 = [self.riskfree, 0., self.kappa_y * self.mean_v] - self.mat_k1 = [[0, self.lmbd - .5, 0], - [0, -self.kappa_s, self.kappa_s], - [0, 0, -self.kappa_y]] + self.mat_k0 = [self.riskfree, 0.0, self.kappa_y * self.mean_v] + self.mat_k1 = [[0, self.lmbd - 0.5, 0], [0, -self.kappa_s, self.kappa_s], [0, 0, -self.kappa_y]] self.mat_h0 = np.zeros((3, 3)) self.mat_h1 = np.zeros((3, 3, 3)) - self.mat_h1[1, 0] = [1, self.eta_s*self.rho, 0] - self.mat_h1[1, 1] = [self.eta_s*self.rho, self.eta_s**2, 0] + self.mat_h1[1, 0] = [1, self.eta_s * self.rho, 0] + self.mat_h1[1, 1] = [self.eta_s * self.rho, self.eta_s**2, 0] self.mat_h1[2, 2, 2] = self.eta_y**2 - def feller(self): + def feller(self) -> bool: """Check Feller condition. Returns @@ -185,9 +187,9 @@ def feller(self): True for valid parameters, False for invalid """ - return 2 * self.kappa_y * self.mean_v - self.eta_y**2 > 0 + return bool(2 * self.kappa_y * self.mean_v - self.eta_y**2 > 0) - def is_valid(self): + def is_valid(self) -> bool: """Check validity of parameters. Returns @@ -198,10 +200,10 @@ def is_valid(self): """ posit1 = (self.mean_v > 0) & (self.kappa_y > 0) & (self.eta_y > 0) posit2 = (self.kappa_s > 0) & (self.eta_s > 0) - return posit1 & posit2 & self.feller() + return bool(posit1 & posit2 & self.feller()) @classmethod - def from_theta(cls, theta, measure='P'): + def from_theta(cls, *, theta: np.ndarray | Sequence[float], measure: str = "P") -> Self: """Initialize parameters from parameter vector. Parameters @@ -215,12 +217,21 @@ def from_theta(cls, theta, measure='P'): - 'Q' : risk-neutral """ - return cls(riskfree=theta[0], mean_v=theta[1], kappa_s=theta[2], - kappa_y=theta[3], eta_s=theta[4], eta_y=theta[5], - rho=theta[6], lmbd=theta[7], lmbd_s=theta[8], - lmbd_y=theta[9], measure=measure) - - def update(self, theta, subset='all', measure='PQ'): + return cls( + riskfree=float(theta[0]), + mean_v=float(theta[1]), + kappa_s=float(theta[2]), + kappa_y=float(theta[3]), + eta_s=float(theta[4]), + eta_y=float(theta[5]), + rho=float(theta[6]), + lmbd=float(theta[7]), + lmbd_s=float(theta[8]), + lmbd_y=float(theta[9]), + measure=measure, + ) + + def update(self, *, theta: np.ndarray | Sequence[float], subset: str = "all", measure: str = "PQ") -> None: """Update attributes from parameter vector. Parameters @@ -242,26 +253,25 @@ def update(self, theta, subset='all', measure='PQ'): - 'PQ' : both """ - [self.mean_v, self.kappa_s, self.kappa_y, - self.eta_s, self.eta_y] = theta[:5] - - if subset == 'all' and measure == 'PQ': - [self.rho, self.lmbd, self.lmbd_s, self.lmbd_y] = theta[5:] - elif subset == 'all' and measure in ('P', 'Q'): - [self.rho, self.lmbd] = theta[5:7] - elif subset == 'vol' and measure == 'PQ': - [self.lmbd_s, self.lmbd_y] = theta[-2:] - elif subset == 'vol' and measure in ('P', 'Q'): + [self.mean_v, self.kappa_s, self.kappa_y, self.eta_s, self.eta_y] = [float(x) for x in theta[:5]] + + if subset == "all" and measure == "PQ": + [self.rho, self.lmbd, self.lmbd_s, self.lmbd_y] = [float(x) for x in theta[5:]] + elif subset == "all" and measure in ("P", "Q"): + [self.rho, self.lmbd] = [float(x) for x in theta[5:7]] + elif subset == "vol" and measure == "PQ": + [self.lmbd_s, self.lmbd_y] = [float(x) for x in theta[-2:]] + elif subset == "vol" and measure in ("P", "Q"): pass else: - raise NotImplementedError('Keyword variable is not supported!') + raise NotImplementedError("Keyword variable is not supported!") - self.measure = 'P' - if measure == 'Q': + self.measure = "P" + if measure == "Q": self.convert_to_q() self.update_ajd() - def get_theta(self, subset='all', measure='PQ'): + def get_theta(self, *, subset: str = "all", measure: str = "PQ") -> np.ndarray: """Return vector of model parameters. Parameters @@ -286,21 +296,31 @@ def get_theta(self, subset='all', measure='PQ'): Parameter vector """ - theta = np.array([self.mean_v, self.kappa_s, self.kappa_y, - self.eta_s, self.eta_y, self.rho, - self.lmbd, self.lmbd_s, self.lmbd_y]) - if subset == 'all' and measure == 'PQ': + theta = np.array( + [ + self.mean_v, + self.kappa_s, + self.kappa_y, + self.eta_s, + self.eta_y, + self.rho, + self.lmbd, + self.lmbd_s, + self.lmbd_y, + ] + ) + if subset == "all" and measure == "PQ": return theta - elif subset == 'all' and measure in ('P', 'Q'): + elif subset == "all" and measure in ("P", "Q"): return theta[:-2] - elif subset == 'vol' and measure == 'PQ': + elif subset == "vol" and measure == "PQ": return np.concatenate((theta[:5], theta[-2:])) - elif subset == 'vol' and measure in ('P', 'Q'): + elif subset == "vol" and measure in ("P", "Q"): return theta[:5] else: - raise NotImplementedError('Keyword variable is not supported!') + raise NotImplementedError("Keyword variable is not supported!") - def get_bounds(self, subset='all', measure='PQ'): + def get_bounds(self, *, subset: str = "all", measure: str = "PQ") -> list[tuple[float | None, float | None]]: """Bounds on parameters. Parameters @@ -324,22 +344,22 @@ def get_bounds(self, subset='all', measure='PQ'): sequence of (min, max) tuples """ - lb = [1e-5, 1e-5, 1e-5, 1e-5, 1e-5, -1, None, None, None] - ub = [None, None, None, None, None, 1, None, None, None] - bounds = list(zip(lb, ub)) + lb: list[float | None] = [1e-5, 1e-5, 1e-5, 1e-5, 1e-5, -1.0, None, None, None] + ub: list[float | None] = [None, None, None, None, None, 1.0, None, None, None] + bounds = list(zip(lb, ub, strict=False)) - if subset == 'all' and measure == 'PQ': + if subset == "all" and measure == "PQ": return bounds - elif subset == 'all' and measure in ('P', 'Q'): + elif subset == "all" and measure in ("P", "Q"): return bounds[:-2] - elif subset == 'vol' and measure == 'PQ': + elif subset == "vol" and measure == "PQ": return bounds[:5] + bounds[-2:] - elif subset == 'vol' and measure in ('P', 'Q'): + elif subset == "vol" and measure in ("P", "Q"): return bounds[:5] else: - raise NotImplementedError('Keyword variable is not supported!') + raise NotImplementedError("Keyword variable is not supported!") - def get_constraints(self): + def get_constraints(self) -> tuple[dict[str, Any], ...]: """Get parameter constraints. Returns @@ -348,5 +368,4 @@ def get_constraints(self): Equality and inequality constraints. See scipy.optimize.minimize """ - return ({'type': 'ineq', 'fun': lambda x: x[1] - x[2]}, - {'type': 'ineq', 'fun': lambda x: x[3] - x[4]}) + return ({"type": "ineq", "fun": lambda x: x[1] - x[2]}, {"type": "ineq", "fun": lambda x: x[3] - x[4]}) diff --git a/src/affidiff/param_gbm.py b/src/affidiff/param_gbm.py index f93de15..f82d663 100644 --- a/src/affidiff/param_gbm.py +++ b/src/affidiff/param_gbm.py @@ -1,21 +1,18 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -GBM parameter class -~~~~~~~~~~~~~~~~~~~ +"""GBM parameter class.""" -""" -from __future__ import print_function, division +from __future__ import annotations + +from typing import TYPE_CHECKING, Sequence import numpy as np -from .param_generic import GenericParam +from affidiff.param_generic import GenericParam -__all__ = ['GBMparam'] +if TYPE_CHECKING: + from typing_extensions import Self class GBMparam(GenericParam): - """Parameter storage for GBM model. Attributes @@ -29,7 +26,7 @@ class GBMparam(GenericParam): """ - def __init__(self, mean=0, sigma=.2, measure='P'): + def __init__(self, *, mean: float = 0.0, sigma: float = 0.2, measure: str = "P") -> None: # noqa: ARG002 """Initialize class. Parameters @@ -45,12 +42,13 @@ def __init__(self, mean=0, sigma=.2, measure='P'): - 'Q' : risk-neutral """ + super().__init__() self.mean = mean self.sigma = sigma - self.measure = 'P' + self.measure = "P" self.update_ajd() - def is_valid(self): + def is_valid(self) -> bool: """Check validity of parameters. Returns @@ -59,20 +57,18 @@ def is_valid(self): True for valid parameters, False for invalid """ - return self.sigma > 0 - - def update_ajd(self): - """Update AJD representation. + return bool(self.sigma > 0) - """ + def update_ajd(self) -> None: + """Update AJD representation.""" # AJD parameters - self.mat_k0 = self.mean - self.sigma**2/2 - self.mat_k1 = 0. + self.mat_k0 = self.mean - self.sigma**2 / 2 + self.mat_k1 = 0.0 self.mat_h0 = self.sigma**2 - self.mat_h1 = 0. + self.mat_h1 = 0.0 @classmethod - def from_theta(cls, theta): + def from_theta(cls, *, theta: np.ndarray | Sequence[float]) -> Self: """Initialize parameters from parameter vector. Parameters @@ -81,24 +77,28 @@ def from_theta(cls, theta): Parameter vector """ - param = cls(mean=theta[0], sigma=theta[1]) + param = cls(mean=float(theta[0]), sigma=float(theta[1])) param.update_ajd() return param - def update(self, theta): + def update(self, *, theta: np.ndarray | Sequence[float], subset: str = "all", measure: str = "P") -> None: # noqa: ARG002 """Update attributes from parameter vector. Parameters ---------- theta : (nparams, ) array Parameter vector + subset : str + Which parameters to update + measure : str + Probability measure """ - self.mean, self.sigma = theta + self.mean, self.sigma = float(theta[0]), float(theta[1]) self.update_ajd() @staticmethod - def get_model_name(): + def get_model_name() -> str: """Return model name. Returns @@ -107,10 +107,10 @@ def get_model_name(): Parameter vector """ - return 'GBM' + return "GBM" @staticmethod - def get_names(subset='all', measure='PQ'): + def get_names(*, subset: str = "all", measure: str = "PQ") -> list[str]: # noqa: ARG004 """Return parameter names. Returns @@ -119,9 +119,9 @@ def get_names(subset='all', measure='PQ'): Parameter names """ - return ['mean', 'sigma'] + return ["mean", "sigma"] - def get_theta(self, subset='all', measure='PQ'): + def get_theta(self, *, subset: str = "all", measure: str = "PQ") -> np.ndarray: # noqa: ARG002 """Return vector of parameters. Returns diff --git a/src/affidiff/param_generic.py b/src/affidiff/param_generic.py index 77c6e78..1828a0c 100644 --- a/src/affidiff/param_generic.py +++ b/src/affidiff/param_generic.py @@ -1,33 +1,33 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Generic parameter class -~~~~~~~~~~~~~~~~~~~~~~~ +"""Generic parameter class.""" -""" -from __future__ import print_function, division +from __future__ import annotations -import pandas as pd +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Sequence -__all__ = ['GenericParam'] +import pandas as pd +if TYPE_CHECKING: + import numpy as np + from typing_extensions import Self -class GenericParam(object): +class GenericParam(ABC): """Generic parameter storage. Must be overriden. Attributes ---------- - + measure : str + Probability measure. """ - def __init__(self): - """Initialize class. + measure: str = "P" - """ - pass + def __init__(self) -> None: + """Initialize class.""" + self.measure = "P" - def is_valid(self): + def is_valid(self) -> bool: """Check whether parameters are valid. Returns @@ -38,14 +38,14 @@ def is_valid(self): """ return True - def update_ajd(self): - """Update AJD representation. - - """ - raise NotImplementedError('Must be overridden') + @abstractmethod + def update_ajd(self) -> None: + """Update AJD representation.""" + raise NotImplementedError("Must be overridden") @classmethod - def from_theta(cls, theta): + @abstractmethod + def from_theta(cls, *, theta: np.ndarray | Sequence[float]) -> Self: """Initialize parameters from parameter vector. Parameters @@ -54,9 +54,10 @@ def from_theta(cls, theta): Parameter vector """ - raise NotImplementedError('Must be overridden') + raise NotImplementedError("Must be overridden") - def update(self, theta, subset='all', measure='P'): + @abstractmethod + def update(self, *, theta: np.ndarray | Sequence[float], subset: str = "all", measure: str = "P") -> None: """Update attributes from parameter vector. Parameters @@ -69,10 +70,11 @@ def update(self, theta, subset='all', measure='P'): Either physical measure (P), or risk-neutral (Q) """ - raise NotImplementedError('Must be overridden') + raise NotImplementedError("Must be overridden") @staticmethod - def get_model_name(): + @abstractmethod + def get_model_name() -> str: """Return model name. Returns @@ -81,10 +83,11 @@ def get_model_name(): Parameter vector """ - raise NotImplementedError('Must be overridden') + raise NotImplementedError("Must be overridden") @staticmethod - def get_names(): + @abstractmethod + def get_names() -> list[str]: """Return parameter names. Returns @@ -93,9 +96,10 @@ def get_names(): Parameter names """ - raise NotImplementedError('Must be overridden') + raise NotImplementedError("Must be overridden") - def get_theta(self): + @abstractmethod + def get_theta(self) -> np.ndarray: """Return vector of parameters. Returns @@ -104,10 +108,10 @@ def get_theta(self): Parameter vector """ - raise NotImplementedError('Must be overridden') + raise NotImplementedError("Must be overridden") @staticmethod - def get_bounds(subset='all', measure='PQ'): + def get_bounds(*, subset: str = "all", measure: str = "PQ") -> list[tuple[float | None, float | None]] | None: # noqa: ARG004 """Get parameter bounds. Returns @@ -118,7 +122,7 @@ def get_bounds(subset='all', measure='PQ'): """ return None - def get_constraints(self): + def get_constraints(self) -> tuple[dict[str, Any], ...] | list[dict[str, Any]] | tuple[()]: """Get parameter constraints. Returns @@ -129,27 +133,22 @@ def get_constraints(self): """ return () - def __str__(self): - """String representation. - - """ - show = self.get_model_name() + ' parameters under ' + self.measure + def __str__(self) -> str: + """Return string representation.""" + show = self.get_model_name() + " parameters under " + self.measure if self.is_valid(): - show += ' (valid)' + show += " (valid)" else: - show += ' (not valid)' - show += ':\n' - table = pd.DataFrame({'theta': self.get_theta()}, - index=self.get_names()) - tb_str = table.to_string(float_format=lambda x: '%.4f' % x) + show += " (not valid)" + show += ":\n" + table = pd.DataFrame({"theta": self.get_theta()}, index=self.get_names()) + tb_str = table.to_string(float_format=lambda x: "%.4f" % x) width = len(tb_str) // (table.shape[0] + 1) - show += width * '-' + '\n' + show += width * "-" + "\n" show += tb_str - show += '\n' + width * '-' + show += "\n" + width * "-" return show - def __repr__(self): - """String representation. - - """ + def __repr__(self) -> str: + """Return string representation.""" return self.__str__() diff --git a/src/affidiff/param_heston.py b/src/affidiff/param_heston.py index a02a61d..20536ae 100644 --- a/src/affidiff/param_heston.py +++ b/src/affidiff/param_heston.py @@ -1,23 +1,19 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Heston parameter class -~~~~~~~~~~~~~~~~~~~~~~ +"""Heston parameter class.""" -""" -from __future__ import print_function, division +from __future__ import annotations import warnings +from typing import TYPE_CHECKING, Sequence import numpy as np -from .param_generic import GenericParam +from affidiff.param_generic import GenericParam -__all__ = ['HestonParam'] +if TYPE_CHECKING: + from typing_extensions import Self class HestonParam(GenericParam): - """Parameter storage for Heston model. Attributes @@ -48,8 +44,18 @@ class HestonParam(GenericParam): """ - def __init__(self, riskfree=.0, mean_v=.5, kappa=1.5, eta=.1, rho=-.5, - lmbd=.1, lmbd_v=.0, measure='P'): + def __init__( + self, + *, + riskfree: float = 0.0, + mean_v: float = 0.5, + kappa: float = 1.5, + eta: float = 0.1, + rho: float = -0.5, + lmbd: float = 0.1, + lmbd_v: float = 0.0, + measure: str = "P", + ) -> None: """Initialize class. Parameters @@ -75,6 +81,7 @@ def __init__(self, riskfree=.0, mean_v=.5, kappa=1.5, eta=.1, rho=-.5, - 'Q' : risk-neutral """ + super().__init__() self.riskfree = riskfree self.kappa = kappa self.mean_v = mean_v @@ -82,13 +89,13 @@ def __init__(self, riskfree=.0, mean_v=.5, kappa=1.5, eta=.1, rho=-.5, self.rho = rho self.lmbd = lmbd self.lmbd_v = lmbd_v - self.measure = 'P' - if measure == 'Q': + self.measure = "P" + if measure == "Q": self.convert_to_q() self.update_ajd() @staticmethod - def get_model_name(): + def get_model_name() -> str: """Return model name. Returns @@ -97,10 +104,10 @@ def get_model_name(): Parameter vector """ - return 'Heston' + return "Heston" @staticmethod - def get_names(subset='all', measure='PQ'): + def get_names(*, subset: str = "all", measure: str = "PQ") -> list[str]: """Return parameter names. Parameters @@ -121,46 +128,41 @@ def get_names(subset='all', measure='PQ'): Parameter names """ - names = ['mean_v', 'kappa', 'eta', 'rho', 'lmbd', 'lmbd_v'] + names = ["mean_v", "kappa", "eta", "rho", "lmbd", "lmbd_v"] - if subset == 'all' and measure == 'PQ': + if subset == "all" and measure == "PQ": return names - elif subset == 'all' and measure in ('P', 'Q'): + elif subset == "all" and measure in ("P", "Q"): return names[:-1] - elif subset == 'vol' and measure == 'PQ': + elif subset == "vol" and measure == "PQ": return names[:3] + names[5:] - elif subset == 'vol' and measure in ('P', 'Q'): + elif subset == "vol" and measure in ("P", "Q"): return names[:3] else: - raise NotImplementedError('Keyword variable is not supported!') - - def convert_to_q(self): - """Convert parameters to risk-neutral version. + raise NotImplementedError("Keyword variable is not supported!") - """ - if self.measure == 'Q': - warnings.warn('Parameters are already converted to Q!') + def convert_to_q(self) -> None: + """Convert parameters to risk-neutral version.""" + if self.measure == "Q": + warnings.warn("Parameters are already converted to Q!", stacklevel=2) else: kappa_p = self.kappa self.kappa = kappa_p - self.lmbd_v * self.eta - self.mean_v *= (kappa_p / self.kappa) - self.lmbd = .0 - self.measure = 'Q' + self.mean_v *= kappa_p / self.kappa + self.lmbd = 0.0 + self.measure = "Q" self.update_ajd() - def update_ajd(self): - """Update AJD representation. - - """ + def update_ajd(self) -> None: + """Update AJD representation.""" # AJD parameters self.mat_k0 = [self.riskfree, self.kappa * self.mean_v] - self.mat_k1 = [[0, self.lmbd - .5], [0, -self.kappa]] + self.mat_k1 = [[0, self.lmbd - 0.5], [0, -self.kappa]] self.mat_h0 = np.zeros((2, 2)) self.mat_h1 = np.zeros((2, 2, 2)) - self.mat_h1[1] = [[1, self.eta*self.rho], - [self.eta*self.rho, self.eta**2]] + self.mat_h1[1] = [[1, self.eta * self.rho], [self.eta * self.rho, self.eta**2]] - def feller(self): + def feller(self) -> bool: """Check Feller condition. Returns @@ -169,9 +171,9 @@ def feller(self): True for valid parameters, False for invalid """ - return 2 * self.kappa * self.mean_v - self.eta**2 > 0 + return bool(2 * self.kappa * self.mean_v - self.eta**2 > 0) - def is_valid(self): + def is_valid(self) -> bool: """Check validity of parameters. Returns @@ -181,10 +183,10 @@ def is_valid(self): """ posit = (self.mean_v > 0) & (self.kappa > 0) & (self.eta > 0) - return posit & self.feller() + return bool(posit & self.feller()) @classmethod - def from_theta(cls, theta, measure='P'): + def from_theta(cls, *, theta: np.ndarray | Sequence[float], measure: str = "P") -> Self: """Initialize parameters from parameter vector. Parameters @@ -198,11 +200,18 @@ def from_theta(cls, theta, measure='P'): - 'Q' : risk-neutral """ - return cls(riskfree=theta[0], mean_v=theta[1], kappa=theta[2], - eta=theta[3], rho=theta[4], lmbd=theta[5], lmbd_v=theta[6], - measure=measure) - - def update(self, theta, subset='all', measure='PQ'): + return cls( + riskfree=float(theta[0]), + mean_v=float(theta[1]), + kappa=float(theta[2]), + eta=float(theta[3]), + rho=float(theta[4]), + lmbd=float(theta[5]), + lmbd_v=float(theta[6]), + measure=measure, + ) + + def update(self, *, theta: np.ndarray | Sequence[float], subset: str = "all", measure: str = "PQ") -> None: """Update attributes from parameter vector. Parameters @@ -224,25 +233,25 @@ def update(self, theta, subset='all', measure='PQ'): - 'PQ' : both """ - [self.mean_v, self.kappa, self.eta] = theta[:3] - - if subset == 'all' and measure == 'PQ': - [self.rho, self.lmbd, self.lmbd_v] = theta[3:] - elif subset == 'all' and measure in ('P', 'Q'): - [self.rho, self.lmbd] = theta[3:5] - elif subset == 'vol' and measure == 'PQ': - [self.lmbd_v] = theta[3:] - elif subset == 'vol' and measure in ('P', 'Q'): + [self.mean_v, self.kappa, self.eta] = [float(x) for x in theta[:3]] + + if subset == "all" and measure == "PQ": + [self.rho, self.lmbd, self.lmbd_v] = [float(x) for x in theta[3:]] + elif subset == "all" and measure in ("P", "Q"): + [self.rho, self.lmbd] = [float(x) for x in theta[3:5]] + elif subset == "vol" and measure == "PQ": + [self.lmbd_v] = [float(x) for x in theta[3:]] + elif subset == "vol" and measure in ("P", "Q"): pass else: - raise NotImplementedError('Keyword variable is not supported!') + raise NotImplementedError("Keyword variable is not supported!") - self.measure = 'P' - if measure == 'Q': + self.measure = "P" + if measure == "Q": self.convert_to_q() self.update_ajd() - def get_theta(self, subset='all', measure='PQ'): + def get_theta(self, *, subset: str = "all", measure: str = "PQ") -> np.ndarray: """Return vector of model parameters. Parameters @@ -267,20 +276,19 @@ def get_theta(self, subset='all', measure='PQ'): Parameter vector """ - theta = np.array([self.mean_v, self.kappa, self.eta, self.rho, - self.lmbd, self.lmbd_v]) - if subset == 'all' and measure == 'PQ': + theta = np.array([self.mean_v, self.kappa, self.eta, self.rho, self.lmbd, self.lmbd_v]) + if subset == "all" and measure == "PQ": return theta - elif subset == 'all' and measure in ('P', 'Q'): + elif subset == "all" and measure in ("P", "Q"): return theta[:-1] - elif subset == 'vol' and measure == 'PQ': + elif subset == "vol" and measure == "PQ": return np.concatenate((theta[:3], theta[5:])) - elif subset == 'vol' and measure in ('P', 'Q'): + elif subset == "vol" and measure in ("P", "Q"): return theta[:3] else: - raise NotImplementedError('Keyword variable is not supported!') + raise NotImplementedError("Keyword variable is not supported!") - def get_bounds(self, subset='all', measure='PQ'): + def get_bounds(self, *, subset: str = "all", measure: str = "PQ") -> list[tuple[float | None, float | None]]: """Bounds on parameters. Parameters @@ -305,17 +313,17 @@ def get_bounds(self, subset='all', measure='PQ'): """ # ['mean_v', 'kappa', 'eta', 'rho', 'lmbd', 'lmbd_v'] - lb = [1e-5, 1e-5, 1e-5, -1, None, None] - ub = [None, None, None, 1, None, None] - bounds = list(zip(lb, ub)) + lb: list[float | None] = [1e-5, 1e-5, 1e-5, -1.0, None, None] + ub: list[float | None] = [None, None, None, 1.0, None, None] + bounds = list(zip(lb, ub, strict=False)) - if subset == 'all' and measure == 'PQ': + if subset == "all" and measure == "PQ": return bounds - elif subset == 'all' and measure in ('P', 'Q'): + elif subset == "all" and measure in ("P", "Q"): return bounds[:-1] - elif subset == 'vol' and measure == 'PQ': + elif subset == "vol" and measure == "PQ": return bounds[:3] + bounds[5:] - elif subset == 'vol' and measure in ('P', 'Q'): + elif subset == "vol" and measure in ("P", "Q"): return bounds[:3] else: - raise NotImplementedError('Keyword variable is not supported!') + raise NotImplementedError("Keyword variable is not supported!") diff --git a/src/affidiff/param_vasicek.py b/src/affidiff/param_vasicek.py index f7f9d37..2ac7e2f 100644 --- a/src/affidiff/param_vasicek.py +++ b/src/affidiff/param_vasicek.py @@ -1,21 +1,18 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Vasicek parameter class -~~~~~~~~~~~~~~~~~~~~~~~ +"""Vasicek parameter class.""" -""" -from __future__ import print_function, division +from __future__ import annotations + +from typing import TYPE_CHECKING, Sequence import numpy as np -from .param_generic import GenericParam +from affidiff.param_generic import GenericParam -__all__ = ['VasicekParam'] +if TYPE_CHECKING: + from typing_extensions import Self class VasicekParam(GenericParam): - """Parameter storage for Vasicek model. Attributes @@ -31,7 +28,7 @@ class VasicekParam(GenericParam): """ - def __init__(self, mean=.5, kappa=1.5, eta=.1, measure='P'): + def __init__(self, *, mean: float = 0.5, kappa: float = 1.5, eta: float = 0.1, measure: str = "P") -> None: # noqa: ARG002 """Initialize class. Parameters @@ -49,13 +46,14 @@ def __init__(self, mean=.5, kappa=1.5, eta=.1, measure='P'): - 'Q' : risk-neutral """ + super().__init__() self.mean = mean self.kappa = kappa self.eta = eta - self.measure = 'P' + self.measure = "P" self.update_ajd() - def is_valid(self): + def is_valid(self) -> bool: """Check validity of parameters. Returns @@ -64,20 +62,18 @@ def is_valid(self): True for valid parameters, False for invalid """ - return (self.kappa > 0) & (self.eta > 0) - - def update_ajd(self): - """Update AJD representation. + return bool((self.kappa > 0) & (self.eta > 0)) - """ + def update_ajd(self) -> None: + """Update AJD representation.""" # AJD parameters self.mat_k0 = self.kappa * self.mean self.mat_k1 = -self.kappa self.mat_h0 = self.eta**2 - self.mat_h1 = 0 + self.mat_h1 = 0.0 @classmethod - def from_theta(cls, theta): + def from_theta(cls, *, theta: np.ndarray | Sequence[float]) -> Self: """Initialize parameters from parameter vector. Parameters @@ -86,24 +82,28 @@ def from_theta(cls, theta): Parameter vector """ - param = cls(mean=theta[0], kappa=theta[1], eta=theta[2]) + param = cls(mean=float(theta[0]), kappa=float(theta[1]), eta=float(theta[2])) param.update_ajd() return param - def update(self, theta): + def update(self, *, theta: np.ndarray | Sequence[float], subset: str = "all", measure: str = "P") -> None: # noqa: ARG002 """Update attributes from parameter vector. Parameters ---------- theta : (nparams, ) array Parameter vector + subset : str + Which parameters to update + measure : str + Probability measure """ - self.mean, self.kappa, self.eta = theta + self.mean, self.kappa, self.eta = float(theta[0]), float(theta[1]), float(theta[2]) self.update_ajd() @staticmethod - def get_model_name(): + def get_model_name() -> str: """Return model name. Returns @@ -112,10 +112,10 @@ def get_model_name(): Parameter vector """ - return 'Vasicek' + return "Vasicek" @staticmethod - def get_names(subset='all', measure='PQ'): + def get_names(*, subset: str = "all", measure: str = "PQ") -> list[str]: # noqa: ARG004 """Return parameter names. Returns @@ -124,9 +124,9 @@ def get_names(subset='all', measure='PQ'): Parameter names """ - return ['mean', 'kappa', 'eta'] + return ["mean", "kappa", "eta"] - def get_theta(self, subset='all', measure='PQ'): + def get_theta(self, *, subset: str = "all", measure: str = "PQ") -> np.ndarray: # noqa: ARG002 """Return vector of parameters. Returns diff --git a/src/affidiff/simulate.pyx b/src/affidiff/simulate.pyx index 876f368..ee8b76a 100644 --- a/src/affidiff/simulate.pyx +++ b/src/affidiff/simulate.pyx @@ -3,7 +3,8 @@ import numpy as np cimport numpy as cnp from scipy.linalg.cython_lapack cimport dpotrf -__all__ = ['simulate'] + + @cython.boundscheck(False) diff --git a/src/affidiff/symbolic.py b/src/affidiff/symbolic.py deleted file mode 100644 index cb6719c..0000000 --- a/src/affidiff/symbolic.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Symbolic calculations - -""" - -import sympy as sp - -mu = sp.Symbol('mu', positive=True) -kappa_s = sp.Symbol('kappa_s', positive=True) -kappa_y = sp.Symbol('kappa_y', positive=True) -eta_s = sp.Symbol('eta_s', positive=True) -eta_y = sp.Symbol('eta_y', positive=True) -h = sp.Symbol('aggh', positive=True) -u = sp.Symbol('u', positive=True) - -#%% -big_as = sp.exp(-kappa_s * h) -big_ay = sp.exp(-kappa_y * h) -big_bs = kappa_s / (kappa_s - kappa_y) * (big_ay - big_as) - -small_as = sp.integrate(big_as, (h, 0, h)) -small_bs = sp.integrate(big_bs, (h, 0, h)).simplify() - -small_ay = sp.integrate(big_ay, (h, 0, h)) - -assert (small_bs - kappa_s / (kappa_s - kappa_y) - * (small_ay - small_as)).simplify() == 0 - -small_bs = kappa_s / (kappa_s - kappa_y) * (small_ay - small_as) - -#%% -temp1 = (big_as.subs(h, -u)**2).simplify() -temp2 = (big_bs.subs(h, -u)**2).simplify() - -var_sigma = mu * (eta_s**2 * sp.integrate(temp1, (u, -sp.oo, 0)) - + eta_y**2 * sp.integrate(temp2, (u, -sp.oo, 0))) - -var_sigma = var_sigma.simplify() - -print(var_sigma) - -#%% -temp1 = (small_as.subs(h, h-u)**2) -temp2 = (small_bs.subs(h, h-u)**2) - -var_error = mu * (eta_s**2 * sp.integrate(temp1, (u, 0, h)) - + eta_y**2 * sp.integrate(temp2, (u, 0, h))) / h**2 - -var_error = var_error.simplify() - -print(var_error) diff --git a/tests/AGENTS.md b/tests/AGENTS.md new file mode 100644 index 0000000..101201b --- /dev/null +++ b/tests/AGENTS.md @@ -0,0 +1,30 @@ +# Purpose + +`tests/` contains the automated test suite for validating the correctness of `affidiff` models, parameters, moments estimation, simulation routines, and helper functions. + +# Ownership + +Owns all pytest test modules under `tests/`: +- Model tests (`test_generic.py`, `test_ajd.py`, `test_param_*.py`) +- Moment estimation tests (`test_rmoms_ct.py`, `test_rmoms_gbm.py`, `test_rmoms_heston.py`) +- Helper and simulation tests (`test_helpers.py`, `test_simulations.py`) + +# Local Contracts + +- Unit tests MUST NOT access private attributes or private methods (prefixed with `_`) of production classes. +- Tests must pass cleanly when executed via `uv run pytest`. +- Coverage metrics are configured via `.coveragerc` and enforced during testing. + +# Work Guidance + +- Group tests logically by component (parameter tests, model trajectory tests, moment condition tests). +- Prefer deterministic test cases with set random seeds where stochastic simulations are involved. + +# Verification + +- Run full test suite: `uv run pytest` +- Run test suite with coverage report: `uv run pytest --cov=src/affidiff` + +# Child DOX Index + +None (leaf boundary). diff --git a/tests/test_ajd.py b/tests/test_ajd.py index ce445b0..cd2f258 100644 --- a/tests/test_ajd.py +++ b/tests/test_ajd.py @@ -1,188 +1,170 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for AJD parameterization. +"""Test suite for AJD parameterization.""" -""" -from __future__ import print_function, division - -import unittest as ut import numpy as np import numpy.testing as npt -from affidiff import (GBMparam, VasicekParam, CIRparam, - HestonParam, CentTendParam) -from affidiff.helper_functions import ajd_drift, ajd_diff +from affidiff import CentTendParam, CIRparam, GBMparam, HestonParam, VasicekParam +from affidiff.helper_functions import ajd_diff, ajd_drift -class DriftTestCase(ut.TestCase): +class TestDrift: """Test Drift function.""" - def test_ajd_drift_gbm(self): + def test_ajd_drift_gbm(self) -> None: """Test AJD drift function for GBM model.""" - - mean, sigma = 1.5, .2 - param = GBMparam(mean, sigma) + mean, sigma = 1.5, 0.2 + param = GBMparam(mean=mean, sigma=sigma) nvars, nsim = 1, 2 size = (nsim, nvars) state = np.ones(size) - drift = state * (mean - sigma**2/2) + drift = state * (mean - sigma**2 / 2) - self.assertEqual(ajd_drift(state, param).shape, size) - npt.assert_array_equal(ajd_drift(state, param), drift) + assert ajd_drift(state=state, theta=param).shape == size + npt.assert_array_equal(ajd_drift(state=state, theta=param), drift) - def test_ajd_drift_vasicek(self): + def test_ajd_drift_vasicek(self) -> None: """Test AJD drift function for Vasicek model.""" - - mean, kappa, eta = 1.5, 1, .2 - param = VasicekParam(mean, kappa, eta) + mean, kappa, eta = 1.5, 1, 0.2 + param = VasicekParam(mean=mean, kappa=kappa, eta=eta) nvars, nsim = 1, 2 size = (nsim, nvars) state = np.ones(size) drift = kappa * (mean - state) - self.assertEqual(ajd_drift(state, param).shape, size) - npt.assert_array_equal(ajd_drift(state, param), drift) + assert ajd_drift(state=state, theta=param).shape == size + npt.assert_array_equal(ajd_drift(state=state, theta=param), drift) - def test_ajd_drift_cir(self): + def test_ajd_drift_cir(self) -> None: """Test AJD drift function for CIR model.""" - - mean, kappa, eta = 1.5, 1, .2 - param = CIRparam(mean, kappa, eta) + mean, kappa, eta = 1.5, 1, 0.2 + param = CIRparam(mean=mean, kappa=kappa, eta=eta) nvars, nsim = 1, 2 size = (nsim, nvars) state = np.ones(size) drift = kappa * (mean - state) - self.assertEqual(ajd_drift(state, param).shape, size) - npt.assert_array_equal(ajd_drift(state, param), drift) + assert ajd_drift(state=state, theta=param).shape == size + npt.assert_array_equal(ajd_drift(state=state, theta=param), drift) - def test_ajd_drift_heston(self): + def test_ajd_drift_heston(self) -> None: """Test AJD drift function for Heston model.""" - - riskfree, lmbd, mean_v, kappa, eta, rho = 0., .01, .2, 1.5, .2, -.5 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho) + riskfree, lmbd, mean_v, kappa, eta, rho = 0.0, 0.01, 0.2, 1.5, 0.2, -0.5 + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) nvars, nsim = 2, 3 size = (nsim, nvars) state = np.ones(size) - drift = np.ones(size) - drift_r = riskfree + state[:, 1]**2 * (lmbd - .5) + drift_r = riskfree + state[:, 1] ** 2 * (lmbd - 0.5) drift_v = kappa * (mean_v - state[:, 1]) drift = np.vstack([drift_r, drift_v]).T - self.assertEqual(ajd_drift(state, param).shape, drift.shape) - npt.assert_almost_equal(ajd_drift(state, param), drift) + assert ajd_drift(state=state, theta=param).shape == drift.shape + npt.assert_almost_equal(ajd_drift(state=state, theta=param), drift) - def test_ajd_drift_ct(self): + def test_ajd_drift_ct(self) -> None: """Test AJD drift function for CT model.""" - - riskfree, lmbd, mean_v = 0., .01, .2 - kappa_s, kappa_y, eta_s, eta_y, rho = 1.5, .5, .2, .02, -.5 - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + riskfree, lmbd, mean_v = 0.0, 0.01, 0.2 + kappa_s, kappa_y, eta_s, eta_y, rho = 1.5, 0.5, 0.2, 0.02, -0.5 + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) nvars, nsim = 3, 5 size = (nsim, nvars) state = np.ones(size) - drift = np.ones(size) - drift_r = riskfree + state[:, 1]**2 * (lmbd - .5) + drift_r = riskfree + state[:, 1] ** 2 * (lmbd - 0.5) drift_s = kappa_s * (state[:, 2] - state[:, 1]) drift_y = kappa_y * (mean_v - state[:, 2]) drift = np.vstack([drift_r, drift_s, drift_y]).T - self.assertEqual(ajd_drift(state, param).shape, drift.shape) - npt.assert_almost_equal(ajd_drift(state, param), drift) + assert ajd_drift(state=state, theta=param).shape == drift.shape + npt.assert_almost_equal(ajd_drift(state=state, theta=param), drift) -class DiffusionTestCase(ut.TestCase): - """Test Diffusio function.""" +class TestDiffusion: + """Test Diffusion function.""" - def test_ajd_diff_gbm(self): + def test_ajd_diff_gbm(self) -> None: """Test AJD diffusion function for GBM model.""" - - mean, sigma = 1.5, .2 - param = GBMparam(mean, sigma) + mean, sigma = 1.5, 0.2 + param = GBMparam(mean=mean, sigma=sigma) nvars, nsim = 1, 2 size = (nsim, nvars) state = np.ones(size) diff = np.ones((nsim, nvars, nvars)) * sigma - self.assertEqual(ajd_diff(state, param).shape, (nsim, nvars, nvars)) - npt.assert_array_equal(ajd_diff(state, param), diff) + assert ajd_diff(state=state, theta=param).shape == (nsim, nvars, nvars) + npt.assert_array_equal(ajd_diff(state=state, theta=param), diff) - def test_ajd_diff_vasicek(self): + def test_ajd_diff_vasicek(self) -> None: """Test AJD diffusion function for Vasicek model.""" - - mean, kappa, eta = 1.5, 1, .2 - param = VasicekParam(mean, kappa, eta) + mean, kappa, eta = 1.5, 1, 0.2 + param = VasicekParam(mean=mean, kappa=kappa, eta=eta) nvars, nsim = 1, 2 size = (nsim, nvars) state = np.ones(size) diff = np.ones((nsim, nvars, nvars)) * eta - self.assertEqual(ajd_diff(state, param).shape, (nsim, nvars, nvars)) - npt.assert_array_equal(ajd_diff(state, param), diff) + assert ajd_diff(state=state, theta=param).shape == (nsim, nvars, nvars) + npt.assert_array_equal(ajd_diff(state=state, theta=param), diff) - def test_ajd_diff_cir(self): + def test_ajd_diff_cir(self) -> None: """Test AJD diffusion function for CIR model.""" - - mean, kappa, eta = 1.5, 1, .2 - param = CIRparam(mean, kappa, eta) + mean, kappa, eta = 1.5, 1, 0.2 + param = CIRparam(mean=mean, kappa=kappa, eta=eta) nvars, nsim = 1, 2 size = (nsim, nvars) state_val = 4 - state = np.ones(size)*state_val - diff = eta * state_val**.5 * np.ones((nsim, nvars, nvars)) + state = np.ones(size) * state_val + diff = eta * state_val**0.5 * np.ones((nsim, nvars, nvars)) - self.assertEqual(ajd_diff(state, param).shape, (nsim, nvars, nvars)) - npt.assert_array_equal(ajd_diff(state, param), diff) + assert ajd_diff(state=state, theta=param).shape == (nsim, nvars, nvars) + npt.assert_array_equal(ajd_diff(state=state, theta=param), diff) - - def test_ajd_diff_heston(self): + def test_ajd_diff_heston(self) -> None: """Test AJD diffusion function for Heston model.""" - - riskfree, lmbd, mean_v, kappa, eta, rho = 0., .01, .2, 1.5, .2, -.0 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho) + riskfree, lmbd, mean_v, kappa, eta, rho = 0.0, 0.01, 0.2, 1.5, 0.2, -0.0 + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) nvars, nsim = 2, 3 size = (nsim, nvars) state = np.ones(size) - diff = np.ones((nsim, nvars, nvars)) - var = np.array([[1, eta*rho], [eta*rho, eta**2]]) + var = np.array([[1, eta * rho], [eta * rho, eta**2]]) var = ((np.ones((nsim, nvars, nvars)) * var).T * state[:, 1]).T diff = np.linalg.cholesky(var) - self.assertEqual(ajd_diff(state, param).shape, diff.shape) - npt.assert_array_equal(ajd_diff(state, param), diff) + assert ajd_diff(state=state, theta=param).shape == diff.shape + npt.assert_array_equal(ajd_diff(state=state, theta=param), diff) - def test_ajd_diff_ct(self): + def test_ajd_diff_ct(self) -> None: """Test AJD diffusion function for CT model.""" - - riskfree, lmbd, mean_v = 0., .01, .2 - kappa_s, kappa_y, eta_s, eta_y, rho = 1.5, .5, .2, .02, -.5 - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + riskfree, lmbd, mean_v = 0.0, 0.01, 0.2 + kappa_s, kappa_y, eta_s, eta_y, rho = 1.5, 0.5, 0.2, 0.02, -0.5 + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) nvars, nsim = 3, 5 size = (nsim, nvars) state = np.ones(size) - diff = np.ones((nsim, nvars, nvars)) - var1 = np.array([[1, eta_s*rho, 0], - [eta_s*rho, eta_s**2, 0], - [0, 0, 0]]) + var1 = np.array([[1, eta_s * rho, 0], [eta_s * rho, eta_s**2, 0], [0, 0, 0]]) var2 = np.zeros((3, 3)) var2[-1, -1] = eta_y**2 - var = ((np.ones((nsim, nvars, nvars)) * var1).T * state[:, 1]).T \ - + ((np.ones((nsim, nvars, nvars)) * var2).T * state[:, 2]).T + var = ((np.ones((nsim, nvars, nvars)) * var1).T * state[:, 1]).T + ( + (np.ones((nsim, nvars, nvars)) * var2).T * state[:, 2] + ).T diff = np.linalg.cholesky(var) - self.assertEqual(ajd_diff(state, param).shape, diff.shape) - npt.assert_array_equal(ajd_diff(state, param), diff) - - -if __name__ == '__main__': - ut.main() + assert ajd_diff(state=state, theta=param).shape == diff.shape + npt.assert_array_equal(ajd_diff(state=state, theta=param), diff) diff --git a/tests/test_generic.py b/tests/test_generic.py index f06d0ac..4c3f4cf 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -1,33 +1,17 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for generic classes. +"""Test suite for generic classes.""" -""" -from __future__ import print_function, division +from affidiff import GBM, GBMparam -import unittest as ut -import numpy as np -from affidiff import (GBM, GBMparam, Vasicek, VasicekParam, - CIR, CIRparam, Heston, HestonParam, - CentTend, CentTendParam) - - -class GenericModelTestCase(ut.TestCase): +class TestGenericModel: """Test generic model.""" - def test_update_theta(self): + def test_update_theta(self) -> None: """Test update of true parameter.""" - - mean, sigma = 1.5, .2 - param = GBMparam(mean, sigma) + mean, sigma = 1.5, 0.2 + param = GBMparam(mean=mean, sigma=sigma) gbm = GBM(param) - param_new = GBMparam(2*mean, 2*sigma) + param_new = GBMparam(mean=2 * mean, sigma=2 * sigma) gbm.update_theta(param_new) - self.assertEqual(gbm.param, param_new) - - -if __name__ == '__main__': - ut.main() + assert gbm.param == param_new diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 7deb406..df5ebb3 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,24 +1,16 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for helper functions. +"""Test suite for helper functions.""" -""" -from __future__ import print_function, division - -import unittest as ut import numpy as np import numpy.testing as npt from statsmodels.tsa.tsatools import lagmat -from affidiff.helper_functions import (columnwise_prod, rolling_window, - nice_errors, poly_coef, instruments) +from affidiff.helper_functions import columnwise_prod, instruments, nice_errors, poly_coef, rolling_window -class HelperFunctionTestCase(ut.TestCase): +class TestHelperFunction: """Test helper functions.""" - def test_columnwise_prod(self): + def test_columnwise_prod(self) -> None: """Test columnwise product.""" left = np.arange(6).reshape((3, 2)) right = np.arange(9).reshape((3, 3)) @@ -26,52 +18,47 @@ def test_columnwise_prod(self): for i in range(right.shape[1]): prod.append(left.T * right[:, i]) prod = np.vstack(prod).T - expected = columnwise_prod(left, right) + expected = columnwise_prod(left=left, right=right) npt.assert_array_equal(prod, expected) - def test_rolling_window(self): + def test_rolling_window(self) -> None: """Test rolling window apply.""" - - mat = rolling_window(np.sum, np.ones(5), window=2) + mat = rolling_window(fun=np.sum, mat=np.ones(5), window=2) npt.assert_array_equal(mat, np.ones(4) * 2) mat = np.arange(10).reshape((2, 5)) - mat = rolling_window(np.mean, mat, window=2) - expect = np.array([[0.5, 1.5, 2.5, 3.5], [5.5, 6.5, 7.5, 8.5]]) + mat = rolling_window(fun=np.mean, mat=mat, window=2) + expect = np.array([[0.5, 1.5, 2.5, 3.5], [5.5, 6.5, 7.5, 8.5]]) npt.assert_array_equal(mat, expect) - def test_nice_errors(self): + def test_nice_errors(self) -> None: """Test nice errors function.""" - nvars, nobs, nsim = 2, 3, 11 sim = 1 size = (nobs, nsim, nvars) - new_size = (nobs, nsim*2, nvars) + new_size = (nobs, nsim * 2, nvars) errors = np.random.normal(size=size) - treated_errors = nice_errors(errors, sim) + treated_errors = nice_errors(errors=errors, sdim=sim) - self.assertEqual(treated_errors.shape, new_size) + assert treated_errors.shape == new_size npt.assert_almost_equal(treated_errors.mean(sim), 0) - npt.assert_almost_equal(treated_errors.std(sim), - np.ones((nobs, nvars))) + npt.assert_almost_equal(treated_errors.std(sim), np.ones((nobs, nvars))) - def test_poly_coef(self): + def test_poly_coef(self) -> None: """Test polynomial coefficients.""" - roots = [2, 3] coefs = [1, -np.sum(roots), np.prod(roots)] - self.assertEqual(poly_coef(roots), coefs) + assert poly_coef(roots) == coefs roots = np.array([2, 3, 4]) coefs = [1, -np.sum(roots), 0, -np.prod(roots)] - coefs[2] = np.prod(roots[:2]) + np.prod(roots[1:]) \ - + np.prod(roots[[0, 2]]) - self.assertEqual(poly_coef(roots), coefs) + coefs[2] = np.prod(roots[:2]) + np.prod(roots[1:]) + np.prod(roots[[0, 2]]) + assert poly_coef(roots) == coefs - def test_instruments(self): + def test_instruments(self) -> None: """Test instruments.""" nperiods = 10 instrlag = 2 @@ -80,20 +67,16 @@ def test_instruments(self): npt.assert_array_equal(instrmnts, np.ones((nperiods, 1))) ninstr = 2 - data = np.arange(nperiods*ninstr).reshape((ninstr, nperiods)) - instrmnts = instruments(data=data, instr_choice='const') + data = np.arange(nperiods * ninstr).reshape((ninstr, nperiods)) + instrmnts = instruments(data=data, instr_choice="const") npt.assert_array_equal(instrmnts, np.ones((nperiods, 1))) - instrmnts = instruments(data=data, instr_choice='var') + instrmnts = instruments(data=data, instr_choice="var") expect = np.hstack([np.ones((nperiods, 1)), lagmat(data.T, maxlag=1)]) npt.assert_array_equal(instrmnts, expect) - instrmnts = instruments(data[0], instrlag=instrlag, instr_choice='var') + instrmnts = instruments(data=data[0], instrlag=instrlag, instr_choice="var") ninstr = 1 - shape = (nperiods, ninstr*instrlag + 1) + shape = (nperiods, ninstr * instrlag + 1) # Test the shape of instruments - self.assertEqual(instrmnts.shape, shape) - - -if __name__ == '__main__': - ut.main() + assert instrmnts.shape == shape diff --git a/tests/test_param_cir.py b/tests/test_param_cir.py index 55f506e..b7392cb 100644 --- a/tests/test_param_cir.py +++ b/tests/test_param_cir.py @@ -1,44 +1,35 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for CIR parameter class. +"""Test suite for CIR parameter class.""" -""" -from __future__ import print_function, division - -import unittest as ut import numpy as np import numpy.testing as npt from affidiff import CIRparam -class SDEParameterTestCase(ut.TestCase): +class TestSDEParameter: """Test parameter classes.""" - def test_cirparam_class(self): + def test_cirparam_class(self) -> None: """Test CIR parameter class.""" + mean, kappa, eta = 1.5, 1.0, 0.1 + param = CIRparam(mean=mean, kappa=kappa, eta=eta) - mean, kappa, eta = 1.5, 1., .1 - param = CIRparam(mean, kappa, eta) - - self.assertEqual(param.get_model_name(), 'CIR') - self.assertEqual(param.get_names(), ['mean', 'kappa', 'eta']) + assert param.get_model_name() == "CIR" + assert param.get_names() == ["mean", "kappa", "eta"] - self.assertEqual(param.mean, mean) - self.assertEqual(param.kappa, kappa) - self.assertEqual(param.eta, eta) + assert param.mean == mean + assert param.kappa == kappa + assert param.eta == eta - npt.assert_array_equal(param.get_theta(), - np.array([mean, kappa, eta])) + npt.assert_array_equal(param.get_theta(), np.array([mean, kappa, eta])) theta = np.ones(3) - param = CIRparam.from_theta(theta) + param = CIRparam.from_theta(theta=theta) npt.assert_array_equal(param.get_theta(), theta) mat_k0 = param.kappa * param.mean mat_k1 = -param.kappa - mat_h0 = 0. + mat_h0 = 0.0 mat_h1 = param.eta**2 npt.assert_array_equal(param.mat_k0, mat_k0) @@ -47,12 +38,12 @@ def test_cirparam_class(self): npt.assert_array_equal(param.mat_h1, mat_h1) theta *= 2 - param.update(theta) + param.update(theta=theta) npt.assert_array_equal(param.get_theta(), theta) mat_k0 = param.kappa * param.mean mat_k1 = -param.kappa - mat_h0 = 0. + mat_h0 = 0.0 mat_h1 = param.eta**2 npt.assert_array_equal(param.mat_k0, mat_k0) @@ -60,12 +51,8 @@ def test_cirparam_class(self): npt.assert_array_equal(param.mat_h0, mat_h0) npt.assert_array_equal(param.mat_h1, mat_h1) - self.assertTrue(param.is_valid()) - param = CIRparam(mean, -kappa, eta) - self.assertFalse(param.is_valid()) - param = CIRparam(mean, kappa, -eta) - self.assertFalse(param.is_valid()) - - -if __name__ == '__main__': - ut.main() + assert param.is_valid() + param = CIRparam(mean=mean, kappa=-kappa, eta=eta) + assert not param.is_valid() + param = CIRparam(mean=mean, kappa=kappa, eta=-eta) + assert not param.is_valid() diff --git a/tests/test_param_ct.py b/tests/test_param_ct.py index 69cd9d0..95935d1 100644 --- a/tests/test_param_ct.py +++ b/tests/test_param_ct.py @@ -1,282 +1,295 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for CT parameter class. - -""" -from __future__ import print_function, division +"""Test suite for CT parameter class.""" import warnings -import unittest as ut import numpy as np import numpy.testing as npt +import pytest from affidiff import CentTendParam -class SDEParameterTestCase(ut.TestCase): +class TestSDEParameter: """Test parameter classes.""" - def test_init(self): + def test_init(self) -> None: """Test initialization.""" - - riskfree = .01 - lmbd = .01 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) - - names = ['mean_v', 'kappa_s', 'kappa_y', 'eta_s', 'eta_y', - 'rho', 'lmbd', 'lmbd_s', 'lmbd_y'] - - self.assertEqual(param.measure, 'P') - self.assertEqual(param.get_model_name(), 'Central Tendency') - self.assertEqual(param.get_names(), names) - self.assertEqual(param.get_names(subset='all'), names) - self.assertEqual(param.get_names(subset='vol'), - names[:5] + names[-2:]) - self.assertEqual(param.get_names(subset='vol', measure='P'), - names[:5]) - self.assertEqual(param.get_names(subset='vol', measure='Q'), - names[:5]) - self.assertEqual(param.get_names(subset='all', measure='P'), - names[:-2]) - self.assertEqual(param.get_names(subset='all', measure='Q'), - names[:-2]) - - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, lmbd) - self.assertEqual(param.lmbd_s, .0) - self.assertEqual(param.lmbd_y, .0) - self.assertEqual(param.mean_v, mean_v) - self.assertEqual(param.kappa_s, kappa_s) - self.assertEqual(param.kappa_y, kappa_y) - self.assertEqual(param.eta_s, eta_s) - self.assertEqual(param.eta_y, eta_y) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - def test_constraints(self): + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) + + names = ["mean_v", "kappa_s", "kappa_y", "eta_s", "eta_y", "rho", "lmbd", "lmbd_s", "lmbd_y"] + + assert param.measure == "P" + assert param.get_model_name() == "Central Tendency" + assert param.get_names() == names + assert param.get_names(subset="all") == names + assert param.get_names(subset="vol") == names[:5] + names[-2:] + assert param.get_names(subset="vol", measure="P") == names[:5] + assert param.get_names(subset="vol", measure="Q") == names[:5] + assert param.get_names(subset="all", measure="P") == names[:-2] + assert param.get_names(subset="all", measure="Q") == names[:-2] + + assert param.riskfree == riskfree + assert param.lmbd == lmbd + assert param.lmbd_s == 0.0 + assert param.lmbd_y == 0.0 + assert param.mean_v == mean_v + assert param.kappa_s == kappa_s + assert param.kappa_y == kappa_y + assert param.eta_s == eta_s + assert param.eta_y == eta_y + assert param.rho == rho + assert param.is_valid() + + def test_constraints(self) -> None: """Test constraints.""" - - riskfree = .01 - lmbd = .01 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) cons = param.get_constraints() - self.assertTrue(cons[0]['fun'](param.get_theta()) > 0) - self.assertTrue(cons[1]['fun'](param.get_theta()) > 0) + assert cons[0]["fun"](param.get_theta()) > 0 + assert cons[1]["fun"](param.get_theta()) > 0 - riskfree = .01 - lmbd = .01 - mean_v = .5 - kappa_s = .5 + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 + kappa_s = 0.5 kappa_y = 1.5 - eta_s = .01 - eta_y = .1 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + eta_s = 0.01 + eta_y = 0.1 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) cons = param.get_constraints() - self.assertFalse(cons[0]['fun'](param.get_theta()) > 0) - self.assertFalse(cons[1]['fun'](param.get_theta()) > 0) + assert not (cons[0]["fun"](param.get_theta()) > 0) + assert not (cons[1]["fun"](param.get_theta()) > 0) - - def test_init_q(self): + def test_init_q(self) -> None: """Test initialization under Q.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=lmbd_s, lmbd_y=lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho, measure='Q') + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + measure="Q", + ) kappa_sq = kappa_s - lmbd_s * eta_s kappa_yq = kappa_y - lmbd_y * eta_y scale = kappa_s / kappa_sq - self.assertEqual(param.measure, 'Q') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, 0) - self.assertEqual(param.lmbd_s, lmbd_s) - self.assertEqual(param.lmbd_y, lmbd_y) - self.assertEqual(param.mean_v, mean_v * kappa_y / kappa_yq * scale) - self.assertEqual(param.kappa_s, kappa_sq) - self.assertEqual(param.kappa_y, kappa_yq) - self.assertEqual(param.eta_s, eta_s) - self.assertEqual(param.eta_y, eta_y * scale**.5) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) + assert param.measure == "Q" + assert param.riskfree == riskfree + assert param.lmbd == 0 + assert param.lmbd_s == lmbd_s + assert param.lmbd_y == lmbd_y + assert param.mean_v == mean_v * kappa_y / kappa_yq * scale + assert param.kappa_s == kappa_sq + assert param.kappa_y == kappa_yq + assert param.eta_s == eta_s + assert param.eta_y == eta_y * scale**0.5 + assert param.rho == rho + assert param.is_valid() with warnings.catch_warnings(): warnings.simplefilter("ignore") param.convert_to_q() - def test_from_theta(self): + def test_from_theta(self) -> None: """Test from theta.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - theta = [riskfree, mean_v, kappa_s, kappa_y, eta_s, eta_y, - rho, lmbd, lmbd_s, lmbd_y] - param = CentTendParam.from_theta(theta, measure='P') - - self.assertEqual(param.measure, 'P') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, lmbd) - self.assertEqual(param.lmbd_s, lmbd_s) - self.assertEqual(param.lmbd_y, lmbd_y) - self.assertEqual(param.mean_v, mean_v) - self.assertEqual(param.kappa_s, kappa_s) - self.assertEqual(param.kappa_y, kappa_y) - self.assertEqual(param.eta_s, eta_s) - self.assertEqual(param.eta_y, eta_y) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - def test_from_theta_q(self): + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + theta = [riskfree, mean_v, kappa_s, kappa_y, eta_s, eta_y, rho, lmbd, lmbd_s, lmbd_y] + param = CentTendParam.from_theta(theta=theta, measure="P") + + assert param.measure == "P" + assert param.riskfree == riskfree + assert param.lmbd == lmbd + assert param.lmbd_s == lmbd_s + assert param.lmbd_y == lmbd_y + assert param.mean_v == mean_v + assert param.kappa_s == kappa_s + assert param.kappa_y == kappa_y + assert param.eta_s == eta_s + assert param.eta_y == eta_y + assert param.rho == rho + assert param.is_valid() + + def test_from_theta_q(self) -> None: """Test from theta under Q.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 - theta = [riskfree, mean_v, kappa_s, kappa_y, eta_s, eta_y, - rho, lmbd, lmbd_s, lmbd_y] - param = CentTendParam.from_theta(theta, measure='Q') + theta = [riskfree, mean_v, kappa_s, kappa_y, eta_s, eta_y, rho, lmbd, lmbd_s, lmbd_y] + param = CentTendParam.from_theta(theta=theta, measure="Q") kappa_sq = kappa_s - lmbd_s * eta_s kappa_yq = kappa_y - lmbd_y * eta_y scale = kappa_s / kappa_sq - self.assertEqual(param.measure, 'Q') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, 0) - self.assertEqual(param.lmbd_s, lmbd_s) - self.assertEqual(param.lmbd_y, lmbd_y) - self.assertEqual(param.mean_v, mean_v * kappa_y / kappa_yq * scale) - self.assertEqual(param.kappa_s, kappa_sq) - self.assertEqual(param.kappa_y, kappa_yq) - self.assertEqual(param.eta_s, eta_s) - self.assertEqual(param.eta_y, eta_y * scale**.5) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - def test_convert_to_q(self): + assert param.measure == "Q" + assert param.riskfree == riskfree + assert param.lmbd == 0 + assert param.lmbd_s == lmbd_s + assert param.lmbd_y == lmbd_y + assert param.mean_v == mean_v * kappa_y / kappa_yq * scale + assert param.kappa_s == kappa_sq + assert param.kappa_y == kappa_yq + assert param.eta_s == eta_s + assert param.eta_y == eta_y * scale**0.5 + assert param.rho == rho + assert param.is_valid() + + def test_convert_to_q(self) -> None: """Test conversion to Q.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - theta = [riskfree, mean_v, kappa_s, kappa_y, eta_s, eta_y, - rho, lmbd, lmbd_s, lmbd_y] - param = CentTendParam.from_theta(theta) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + theta = [riskfree, mean_v, kappa_s, kappa_y, eta_s, eta_y, rho, lmbd, lmbd_s, lmbd_y] + param = CentTendParam.from_theta(theta=theta) param.convert_to_q() kappa_sq = kappa_s - lmbd_s * eta_s kappa_yq = kappa_y - lmbd_y * eta_y scale = kappa_s / kappa_sq - self.assertEqual(param.measure, 'Q') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, 0) - self.assertEqual(param.lmbd_s, lmbd_s) - self.assertEqual(param.lmbd_y, lmbd_y) - self.assertEqual(param.mean_v, mean_v * kappa_y / kappa_yq * scale) - self.assertEqual(param.kappa_s, kappa_sq) - self.assertEqual(param.kappa_y, kappa_yq) - self.assertEqual(param.eta_s, eta_s) - self.assertEqual(param.eta_y, eta_y * scale**.5) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - def test_ajd_matrices(self): + assert param.measure == "Q" + assert param.riskfree == riskfree + assert param.lmbd == 0 + assert param.lmbd_s == lmbd_s + assert param.lmbd_y == lmbd_y + assert param.mean_v == mean_v * kappa_y / kappa_yq * scale + assert param.kappa_s == kappa_sq + assert param.kappa_y == kappa_yq + assert param.eta_s == eta_s + assert param.eta_y == eta_y * scale**0.5 + assert param.rho == rho + assert param.is_valid() + + def test_ajd_matrices(self) -> None: """Test AJD matrices.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=lmbd_s, lmbd_y=lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) kappa_sq = kappa_s - lmbd_s * eta_s kappa_yq = kappa_y - lmbd_y * eta_y scale = kappa_s / kappa_sq mean_vq = mean_v * kappa_y / kappa_yq * scale - eta_yq = eta_y * scale**.5 + eta_yq = eta_y * scale**0.5 - mat_k0 = [riskfree, 0., kappa_y * mean_v] - mat_k1 = [[0, lmbd-.5, 0], - [0, -kappa_s, kappa_s], - [0, 0, -kappa_y]] + mat_k0 = [riskfree, 0.0, kappa_y * mean_v] + mat_k1 = [[0, lmbd - 0.5, 0], [0, -kappa_s, kappa_s], [0, 0, -kappa_y]] mat_h0 = np.zeros((3, 3)) mat_h1 = np.zeros((3, 3, 3)) - mat_h1[1, 0] = [1, eta_s*param.rho, 0] - mat_h1[1, 1] = [eta_s*param.rho, eta_s**2, 0] + mat_h1[1, 0] = [1, eta_s * param.rho, 0] + mat_h1[1, 1] = [eta_s * param.rho, eta_s**2, 0] mat_h1[2, 2, 2] = eta_y**2 npt.assert_array_almost_equal(param.mat_k0, mat_k0) @@ -286,14 +299,12 @@ def test_ajd_matrices(self): param.convert_to_q() - mat_k0 = [riskfree, 0., kappa_yq * mean_vq] - mat_k1 = [[0, -.5, 0], - [0, -kappa_sq, kappa_sq], - [0, 0, -kappa_yq]] + mat_k0 = [riskfree, 0.0, kappa_yq * mean_vq] + mat_k1 = [[0, -0.5, 0], [0, -kappa_sq, kappa_sq], [0, 0, -kappa_yq]] mat_h0 = np.zeros((3, 3)) mat_h1 = np.zeros((3, 3, 3)) - mat_h1[1, 0] = [1, eta_s*param.rho, 0] - mat_h1[1, 1] = [eta_s*param.rho, eta_s**2, 0] + mat_h1[1, 0] = [1, eta_s * param.rho, 0] + mat_h1[1, 1] = [eta_s * param.rho, eta_s**2, 0] mat_h1[2, 2, 2] = eta_yq**2 npt.assert_array_almost_equal(param.mat_k0, mat_k0) @@ -306,16 +317,14 @@ def test_ajd_matrices(self): param = CentTendParam() param.update(theta=theta) npt.assert_array_equal(param.get_theta(), theta) - npt.assert_array_equal(param.get_theta(subset='vol'), theta_vol) + npt.assert_array_equal(param.get_theta(subset="vol"), theta_vol) - mat_k0 = [param.riskfree, 0., param.kappa_y * param.mean_v] - mat_k1 = [[0, param.lmbd-.5, 0], - [0, -param.kappa_s, param.kappa_s], - [0, 0, -param.kappa_y]] + mat_k0 = [param.riskfree, 0.0, param.kappa_y * param.mean_v] + mat_k1 = [[0, param.lmbd - 0.5, 0], [0, -param.kappa_s, param.kappa_s], [0, 0, -param.kappa_y]] mat_h0 = np.zeros((3, 3)) mat_h1 = np.zeros((3, 3, 3)) - mat_h1[1, 0] = [1, param.eta_s*param.rho, 0] - mat_h1[1, 1] = [param.eta_s*param.rho, param.eta_s**2, 0] + mat_h1[1, 0] = [1, param.eta_s * param.rho, 0] + mat_h1[1, 1] = [param.eta_s * param.rho, param.eta_s**2, 0] mat_h1[2, 2, 2] = param.eta_y**2 npt.assert_array_equal(param.mat_k0, mat_k0) @@ -323,169 +332,221 @@ def test_ajd_matrices(self): npt.assert_array_equal(param.mat_h0, mat_h0) npt.assert_array_equal(param.mat_h1, mat_h1) - def test_update(self): + def test_update(self) -> None: """Test update.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=lmbd_s, lmbd_y=lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) - - mean_v, kappa_s, kappa_y= .6, 1.7, .6 - eta_s, eta_y, rho, lmbd = .2, .02, -.6, .05 + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) + + mean_v, kappa_s, kappa_y = 0.6, 1.7, 0.6 + eta_s, eta_y, rho, lmbd = 0.2, 0.02, -0.6, 0.05 theta = np.array([mean_v, kappa_s, kappa_y, eta_s, eta_y, rho, lmbd]) - param.update(theta=theta, measure='Q') + param.update(theta=theta, measure="Q") kappa_sq = kappa_s - lmbd_s * eta_s kappa_yq = kappa_y - lmbd_y * eta_y scale = kappa_s / kappa_sq mean_vq = mean_v * kappa_y / kappa_yq * scale - eta_yq = eta_y * scale**.5 - - self.assertEqual(param.measure, 'Q') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, 0) - self.assertEqual(param.lmbd_s, lmbd_s) - self.assertEqual(param.lmbd_y, lmbd_y) - self.assertAlmostEqual(param.mean_v, mean_vq) - self.assertEqual(param.kappa_s, kappa_sq) - self.assertEqual(param.kappa_y, kappa_yq) - self.assertEqual(param.eta_s, eta_s) - self.assertEqual(param.eta_y, eta_yq) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - mat_k0 = [riskfree, 0., kappa_yq * mean_vq] - mat_k1 = [[0, -.5, 0], - [0, -kappa_sq, kappa_sq], - [0, 0, -kappa_yq]] + eta_yq = eta_y * scale**0.5 + + assert param.measure == "Q" + assert param.riskfree == riskfree + assert param.lmbd == 0 + assert param.lmbd_s == lmbd_s + assert param.lmbd_y == lmbd_y + assert param.mean_v == pytest.approx(mean_vq) + assert param.kappa_s == kappa_sq + assert param.kappa_y == kappa_yq + assert param.eta_s == eta_s + assert param.eta_y == eta_yq + assert param.rho == rho + assert param.is_valid() + + mat_k0 = [riskfree, 0.0, kappa_yq * mean_vq] + mat_k1 = [[0, -0.5, 0], [0, -kappa_sq, kappa_sq], [0, 0, -kappa_yq]] npt.assert_array_almost_equal(param.mat_k0, mat_k0) npt.assert_array_equal(param.mat_k1, mat_k1) - def test_get_theta(self): + def test_get_theta(self) -> None: """Test get theta.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=lmbd_s, lmbd_y=lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho, measure='P') - - theta = [mean_v, kappa_s, kappa_y, eta_s, eta_y, - rho, lmbd, lmbd_s, lmbd_y] + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + measure="P", + ) + + theta = [mean_v, kappa_s, kappa_y, eta_s, eta_y, rho, lmbd, lmbd_s, lmbd_y] theta_vol = np.concatenate((theta[:5], theta[-2:])) npt.assert_array_equal(param.get_theta(), theta) - npt.assert_array_equal(param.get_theta(subset='all'), theta) - npt.assert_array_equal(param.get_theta(subset='all', measure='PQ'), - theta) - npt.assert_array_equal(param.get_theta(subset='all', measure='P'), - theta[:-2]) - npt.assert_array_equal(param.get_theta(subset='all', measure='Q'), - theta[:-2]) - npt.assert_array_equal(param.get_theta(subset='vol'), theta_vol) - npt.assert_array_equal(param.get_theta(subset='vol', measure='PQ'), - theta_vol) - npt.assert_array_equal(param.get_theta(subset='vol', measure='P'), - theta_vol[:-2]) - npt.assert_array_equal(param.get_theta(subset='vol', measure='Q'), - theta_vol[:-2]) + npt.assert_array_equal(param.get_theta(subset="all"), theta) + npt.assert_array_equal(param.get_theta(subset="all", measure="PQ"), theta) + npt.assert_array_equal(param.get_theta(subset="all", measure="P"), theta[:-2]) + npt.assert_array_equal(param.get_theta(subset="all", measure="Q"), theta[:-2]) + npt.assert_array_equal(param.get_theta(subset="vol"), theta_vol) + npt.assert_array_equal(param.get_theta(subset="vol", measure="PQ"), theta_vol) + npt.assert_array_equal(param.get_theta(subset="vol", measure="P"), theta_vol[:-2]) + npt.assert_array_equal(param.get_theta(subset="vol", measure="Q"), theta_vol[:-2]) theta = np.arange(9) param.update(theta=theta) theta_vol = np.ones(5) * 2 - param.update(theta=theta_vol, subset='vol', measure='P') + param.update(theta=theta_vol, subset="vol", measure="P") theta[:5] = theta_vol npt.assert_array_equal(param.get_theta(), theta) - def test_bounds(self): + def test_bounds(self) -> None: """Test bounds.""" - param = CentTendParam() - self.assertEqual(len(param.get_bounds()), 9) - self.assertEqual(len(param.get_bounds(subset='all')), 9) - self.assertEqual(len(param.get_bounds(subset='all', measure='PQ')), 9) - self.assertEqual(len(param.get_bounds(subset='all', measure='P')), 7) - self.assertEqual(len(param.get_bounds(subset='all', measure='Q')), 7) - self.assertEqual(len(param.get_bounds(subset='vol')), 7) - self.assertEqual(len(param.get_bounds(subset='vol', measure='PQ')), 7) - self.assertEqual(len(param.get_bounds(subset='vol', measure='P')), 5) - self.assertEqual(len(param.get_bounds(subset='vol', measure='Q')), 5) - - def test_validity(self): + bounds = param.get_bounds() + assert bounds is not None and len(bounds) == 9 + bounds_all = param.get_bounds(subset="all") + assert bounds_all is not None and len(bounds_all) == 9 + bounds_pq = param.get_bounds(subset="all", measure="PQ") + assert bounds_pq is not None and len(bounds_pq) == 9 + bounds_p = param.get_bounds(subset="all", measure="P") + assert bounds_p is not None and len(bounds_p) == 7 + bounds_q = param.get_bounds(subset="all", measure="Q") + assert bounds_q is not None and len(bounds_q) == 7 + bounds_vol = param.get_bounds(subset="vol") + assert bounds_vol is not None and len(bounds_vol) == 7 + bounds_vol_pq = param.get_bounds(subset="vol", measure="PQ") + assert bounds_vol_pq is not None and len(bounds_vol_pq) == 7 + bounds_vol_p = param.get_bounds(subset="vol", measure="P") + assert bounds_vol_p is not None and len(bounds_vol_p) == 5 + bounds_vol_q = param.get_bounds(subset="vol", measure="Q") + assert bounds_vol_q is not None and len(bounds_vol_q) == 5 + + def test_validity(self) -> None: """Test validity.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=lmbd_s, lmbd_y=lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho, measure='P') - - self.assertTrue(param.is_valid()) - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=-mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) - - self.assertFalse(param.is_valid()) - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=-kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) - - self.assertFalse(param.is_valid()) - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=-kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) - - self.assertFalse(param.is_valid()) - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=-eta_s, eta_y=eta_y, rho=rho) - - self.assertFalse(param.is_valid()) - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=-eta_y, rho=rho) - - self.assertFalse(param.is_valid()) - - -if __name__ == '__main__': - - ut.main() + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + measure="P", + ) + + assert param.is_valid() + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=-mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) + + assert not param.is_valid() + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=-kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) + + assert not param.is_valid() + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=-kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) + + assert not param.is_valid() + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=-eta_s, + eta_y=eta_y, + rho=rho, + ) + + assert not param.is_valid() + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=-eta_y, + rho=rho, + ) + + assert not param.is_valid() diff --git a/tests/test_param_gbm.py b/tests/test_param_gbm.py index 71644a9..7ab16fa 100644 --- a/tests/test_param_gbm.py +++ b/tests/test_param_gbm.py @@ -1,46 +1,37 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for GBM parameter class. +"""Test suite for GBM parameter class.""" -""" -from __future__ import print_function, division - -import unittest as ut import numpy as np import numpy.testing as npt from affidiff import GBMparam -class SDEParameterTestCase(ut.TestCase): +class TestSDEParameter: """Test parameter classes.""" - def test_gbmparam_class(self): + def test_gbmparam_class(self) -> None: """Test GBM parameter class.""" + mean, sigma = 1.5, 0.2 + param = GBMparam(mean=mean, sigma=sigma) - mean, sigma = 1.5, .2 - param = GBMparam(mean, sigma) - - self.assertEqual(param.get_model_name(), 'GBM') - self.assertEqual(param.get_names(), ['mean', 'sigma']) + assert param.get_model_name() == "GBM" + assert param.get_names() == ["mean", "sigma"] - self.assertEqual(param.mean, mean) - self.assertEqual(param.sigma, sigma) - npt.assert_array_equal(param.get_theta(), - np.array([mean, sigma])) + assert param.mean == mean + assert param.sigma == sigma + npt.assert_array_equal(param.get_theta(), np.array([mean, sigma])) theta = np.array([mean, sigma]) npt.assert_array_equal(param.get_theta(), theta) theta = np.ones(2) - param = GBMparam.from_theta(theta) + param = GBMparam.from_theta(theta=theta) npt.assert_array_equal(param.get_theta(), theta) - mat_k0 = param.mean - param.sigma**2/2 - mat_k1 = 0. + mat_k0 = param.mean - param.sigma**2 / 2 + mat_k1 = 0.0 mat_h0 = param.sigma**2 - mat_h1 = 0. + mat_h1 = 0.0 npt.assert_array_equal(param.mat_k0, mat_k0) npt.assert_array_equal(param.mat_k1, mat_k1) @@ -51,20 +42,16 @@ def test_gbmparam_class(self): param.update(theta=theta) npt.assert_array_equal(param.get_theta(), theta) - mat_k0 = param.mean - param.sigma**2/2 - mat_k1 = 0. + mat_k0 = param.mean - param.sigma**2 / 2 + mat_k1 = 0.0 mat_h0 = param.sigma**2 - mat_h1 = 0. + mat_h1 = 0.0 npt.assert_array_equal(param.mat_k0, mat_k0) npt.assert_array_equal(param.mat_k1, mat_k1) npt.assert_array_equal(param.mat_h0, mat_h0) npt.assert_array_equal(param.mat_h1, mat_h1) - self.assertTrue(param.is_valid()) - param = GBMparam(mean, -sigma) - self.assertFalse(param.is_valid()) - - -if __name__ == '__main__': - ut.main() + assert param.is_valid() + param = GBMparam(mean=mean, sigma=-sigma) + assert not param.is_valid() diff --git a/tests/test_param_heston.py b/tests/test_param_heston.py index 979bd0a..327b173 100644 --- a/tests/test_param_heston.py +++ b/tests/test_param_heston.py @@ -1,195 +1,175 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for Heston parameter class. - -""" -from __future__ import print_function, division +"""Test suite for Heston parameter class.""" import warnings -import unittest as ut import numpy as np import numpy.testing as npt +import pytest from affidiff import HestonParam -class SDEParameterTestCase(ut.TestCase): +class TestSDEParameter: """Test parameter classes.""" - def test_init(self): + def test_init(self) -> None: """Test initialization.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - rho = -.5 - - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho) - names = ['mean_v', 'kappa', 'eta', 'rho', 'lmbd', 'lmbd_v'] - - self.assertEqual(param.measure, 'P') - self.assertEqual(param.get_model_name(), 'Heston') - self.assertEqual(param.get_names(), names) - self.assertEqual(param.get_names(subset='all'), names) - self.assertEqual(param.get_names(subset='vol'), names[:3] + names[5:]) - self.assertEqual(param.get_names(subset='vol', measure='P'), - names[:3]) - self.assertEqual(param.get_names(subset='vol', measure='Q'), - names[:3]) - self.assertEqual(param.get_names(subset='all', measure='P'), - names[:-1]) - self.assertEqual(param.get_names(subset='all', measure='Q'), - names[:-1]) - - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, lmbd) - self.assertEqual(param.lmbd_v, .0) - self.assertEqual(param.mean_v, mean_v) - self.assertEqual(param.kappa, kappa) - self.assertEqual(param.eta, eta) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - def test_constraints(self): + eta = 0.1 + lmbd = 0.01 + rho = -0.5 + + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) + names = ["mean_v", "kappa", "eta", "rho", "lmbd", "lmbd_v"] + + assert param.measure == "P" + assert param.get_model_name() == "Heston" + assert param.get_names() == names + assert param.get_names(subset="all") == names + assert param.get_names(subset="vol") == names[:3] + names[5:] + assert param.get_names(subset="vol", measure="P") == names[:3] + assert param.get_names(subset="vol", measure="Q") == names[:3] + assert param.get_names(subset="all", measure="P") == names[:-1] + assert param.get_names(subset="all", measure="Q") == names[:-1] + + assert param.riskfree == riskfree + assert param.lmbd == lmbd + assert param.lmbd_v == 0.0 + assert param.mean_v == mean_v + assert param.kappa == kappa + assert param.eta == eta + assert param.rho == rho + assert param.is_valid() + + def test_constraints(self) -> None: """Test constraints.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - rho = -.5 + eta = 0.1 + lmbd = 0.01 + rho = -0.5 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho) + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) - self.assertEqual(param.get_constraints(), ()) + assert param.get_constraints() == () - def test_init_q(self): + def test_init_q(self) -> None: """Test initialization under Q.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - lmbd_v = .5 - rho = -.5 - - param = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho, measure='Q') - - self.assertEqual(param.measure, 'Q') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, 0) - self.assertEqual(param.lmbd_v, lmbd_v) - self.assertEqual(param.mean_v, mean_v * kappa / param.kappa) - self.assertEqual(param.kappa, kappa - lmbd_v * eta) - self.assertEqual(param.eta, eta) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) + eta = 0.1 + lmbd = 0.01 + lmbd_v = 0.5 + rho = -0.5 + + param = HestonParam( + riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, measure="Q" + ) + + assert param.measure == "Q" + assert param.riskfree == riskfree + assert param.lmbd == 0 + assert param.lmbd_v == lmbd_v + assert param.mean_v == mean_v * kappa / param.kappa + assert param.kappa == kappa - lmbd_v * eta + assert param.eta == eta + assert param.rho == rho + assert param.is_valid() with warnings.catch_warnings(): warnings.simplefilter("ignore") param.convert_to_q() - def test_from_theta(self): + def test_from_theta(self) -> None: """Test from theta.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - lmbd_v = .5 - rho = -.5 + eta = 0.1 + lmbd = 0.01 + lmbd_v = 0.5 + rho = -0.5 theta = [riskfree, mean_v, kappa, eta, rho, lmbd, lmbd_v] - param = HestonParam.from_theta(theta, measure='P') - - self.assertEqual(param.measure, 'P') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, lmbd) - self.assertEqual(param.lmbd_v, lmbd_v) - self.assertEqual(param.mean_v, mean_v) - self.assertEqual(param.kappa, kappa) - self.assertEqual(param.eta, eta) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - def test_from_theta_q(self): + param = HestonParam.from_theta(theta=theta, measure="P") + + assert param.measure == "P" + assert param.riskfree == riskfree + assert param.lmbd == lmbd + assert param.lmbd_v == lmbd_v + assert param.mean_v == mean_v + assert param.kappa == kappa + assert param.eta == eta + assert param.rho == rho + assert param.is_valid() + + def test_from_theta_q(self) -> None: """Test from theta under Q.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - lmbd_v = .5 - rho = -.5 + eta = 0.1 + lmbd = 0.01 + lmbd_v = 0.5 + rho = -0.5 theta = [riskfree, mean_v, kappa, eta, rho, lmbd, lmbd_v] - param = HestonParam.from_theta(theta, measure='Q') - - self.assertEqual(param.measure, 'Q') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, 0) - self.assertEqual(param.lmbd_v, lmbd_v) - self.assertEqual(param.mean_v, mean_v * kappa / param.kappa) - self.assertEqual(param.kappa, kappa - lmbd_v * eta) - self.assertEqual(param.eta, eta) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - def test_convert_to_q(self): + param = HestonParam.from_theta(theta=theta, measure="Q") + + assert param.measure == "Q" + assert param.riskfree == riskfree + assert param.lmbd == 0 + assert param.lmbd_v == lmbd_v + assert param.mean_v == mean_v * kappa / param.kappa + assert param.kappa == kappa - lmbd_v * eta + assert param.eta == eta + assert param.rho == rho + assert param.is_valid() + + def test_convert_to_q(self) -> None: """Test conversion to Q.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - lmbd_v = .5 - rho = -.5 + eta = 0.1 + lmbd = 0.01 + lmbd_v = 0.5 + rho = -0.5 theta = [riskfree, mean_v, kappa, eta, rho, lmbd, lmbd_v] - param = HestonParam.from_theta(theta) + param = HestonParam.from_theta(theta=theta) param.convert_to_q() - self.assertEqual(param.measure, 'Q') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, 0) - self.assertEqual(param.lmbd_v, lmbd_v) - self.assertEqual(param.mean_v, mean_v * kappa / param.kappa) - self.assertEqual(param.kappa, kappa - lmbd_v * eta) - self.assertEqual(param.eta, eta) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - def test_ajd_matrices(self): + assert param.measure == "Q" + assert param.riskfree == riskfree + assert param.lmbd == 0 + assert param.lmbd_v == lmbd_v + assert param.mean_v == mean_v * kappa / param.kappa + assert param.kappa == kappa - lmbd_v * eta + assert param.eta == eta + assert param.rho == rho + assert param.is_valid() + + def test_ajd_matrices(self) -> None: """Test AJD matrices.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - lmbd_v = .5 - rho = -.5 + eta = 0.1 + lmbd = 0.01 + lmbd_v = 0.5 + rho = -0.5 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, - mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) + param = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) mat_k0 = [riskfree, kappa * mean_v] - mat_k1 = [[0, lmbd - .5], [0, -kappa]] + mat_k1 = [[0, lmbd - 0.5], [0, -kappa]] mat_h0 = np.zeros((2, 2)) - mat_h1 = [np.zeros((2, 2)), [[1, eta*rho], [eta*rho, eta**2]]] + mat_h1 = [np.zeros((2, 2)), [[1, eta * rho], [eta * rho, eta**2]]] npt.assert_array_equal(param.mat_k0, mat_k0) npt.assert_array_equal(param.mat_k1, mat_k1) @@ -202,9 +182,9 @@ def test_ajd_matrices(self): mean_v_q = mean_v * kappa / kappa_q mat_k0 = [riskfree, kappa_q * mean_v_q] - mat_k1 = [[0, - .5], [0, -kappa_q]] + mat_k1 = [[0, -0.5], [0, -kappa_q]] mat_h0 = np.zeros((2, 2)) - mat_h1 = [np.zeros((2, 2)), [[1, eta*rho], [eta*rho, eta**2]]] + mat_h1 = [np.zeros((2, 2)), [[1, eta * rho], [eta * rho, eta**2]]] npt.assert_array_equal(param.mat_k0, mat_k0) npt.assert_array_equal(param.mat_k1, mat_k1) @@ -216,137 +196,123 @@ def test_ajd_matrices(self): param = HestonParam() param.update(theta=theta) npt.assert_array_equal(param.get_theta(), theta) - npt.assert_array_equal(param.get_theta(subset='vol'), theta_vol) + npt.assert_array_equal(param.get_theta(subset="vol"), theta_vol) mat_k0 = [param.riskfree, param.kappa * param.mean_v] - mat_k1 = [[0, param.lmbd - .5], [0, -param.kappa]] + mat_k1 = [[0, param.lmbd - 0.5], [0, -param.kappa]] mat_h0 = np.zeros((2, 2)) - mat_h1 = [np.zeros((2, 2)), [[1, param.eta*param.rho], - [param.eta*param.rho, param.eta**2]]] + mat_h1 = [np.zeros((2, 2)), [[1, param.eta * param.rho], [param.eta * param.rho, param.eta**2]]] npt.assert_array_equal(param.mat_k0, mat_k0) npt.assert_array_equal(param.mat_k1, mat_k1) npt.assert_array_equal(param.mat_h0, mat_h0) npt.assert_array_equal(param.mat_h1, mat_h1) - def test_update(self): + def test_update(self) -> None: """Test update.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - lmbd_v = .5 - rho = -.5 + eta = 0.1 + lmbd = 0.01 + lmbd_v = 0.5 + rho = -0.5 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, - mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) + param = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) - mean_v, kappa, eta, rho, lmbd = .6, 1.7, .2, -.6, .3 + mean_v, kappa, eta, rho, lmbd = 0.6, 1.7, 0.2, -0.6, 0.3 theta = np.array([mean_v, kappa, eta, rho, lmbd]) - param.update(theta=theta, measure='Q') + param.update(theta=theta, measure="Q") mean_vq = mean_v * kappa / param.kappa kappa_q = kappa - lmbd_v * eta - self.assertEqual(param.measure, 'Q') - self.assertEqual(param.riskfree, riskfree) - self.assertEqual(param.lmbd, 0) - self.assertEqual(param.lmbd_v, lmbd_v) - self.assertAlmostEqual(param.mean_v, mean_vq) - self.assertEqual(param.kappa, kappa_q) - self.assertEqual(param.eta, eta) - self.assertEqual(param.rho, rho) - self.assertTrue(param.is_valid()) - - npt.assert_array_almost_equal(param.mat_k0, - [riskfree, kappa_q * mean_vq]) - npt.assert_array_equal(param.mat_k1, [[0, -.5], [0, -kappa_q]]) - - def test_get_theta(self): - """Test get theta.""" + assert param.measure == "Q" + assert param.riskfree == riskfree + assert param.lmbd == 0 + assert param.lmbd_v == lmbd_v + assert param.mean_v == pytest.approx(mean_vq) + assert param.kappa == kappa_q + assert param.eta == eta + assert param.rho == rho + assert param.is_valid() + + npt.assert_array_almost_equal(param.mat_k0, [riskfree, kappa_q * mean_vq]) + npt.assert_array_equal(param.mat_k1, [[0, -0.5], [0, -kappa_q]]) - riskfree = .01 - mean_v = .5 + def test_get_theta(self) -> None: + """Test get theta.""" + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - lmbd_v = .5 - rho = -.5 + eta = 0.1 + lmbd = 0.01 + lmbd_v = 0.5 + rho = -0.5 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho, measure='P') + param = HestonParam( + riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, measure="P" + ) theta = np.array([mean_v, kappa, eta, rho, lmbd, lmbd_v]) theta_vol = np.concatenate((theta[:3], theta[5:])) npt.assert_array_equal(param.get_theta(), theta) - npt.assert_array_equal(param.get_theta(subset='all'), theta) - npt.assert_array_equal(param.get_theta(subset='all', measure='PQ'), - theta) - npt.assert_array_equal(param.get_theta(subset='all', measure='P'), - theta[:-1]) - npt.assert_array_equal(param.get_theta(subset='all', measure='Q'), - theta[:-1]) - npt.assert_array_equal(param.get_theta(subset='vol'), theta_vol) - npt.assert_array_equal(param.get_theta(subset='vol', measure='PQ'), - theta_vol) - npt.assert_array_equal(param.get_theta(subset='vol', measure='P'), - theta_vol[:-1]) - npt.assert_array_equal(param.get_theta(subset='vol', measure='Q'), - theta_vol[:-1]) + npt.assert_array_equal(param.get_theta(subset="all"), theta) + npt.assert_array_equal(param.get_theta(subset="all", measure="PQ"), theta) + npt.assert_array_equal(param.get_theta(subset="all", measure="P"), theta[:-1]) + npt.assert_array_equal(param.get_theta(subset="all", measure="Q"), theta[:-1]) + npt.assert_array_equal(param.get_theta(subset="vol"), theta_vol) + npt.assert_array_equal(param.get_theta(subset="vol", measure="PQ"), theta_vol) + npt.assert_array_equal(param.get_theta(subset="vol", measure="P"), theta_vol[:-1]) + npt.assert_array_equal(param.get_theta(subset="vol", measure="Q"), theta_vol[:-1]) theta = np.arange(6) param.update(theta=theta) theta_vol = np.ones(3) * 2 - param.update(theta=theta_vol, subset='vol', measure='P') + param.update(theta=theta_vol, subset="vol", measure="P") theta[:3] = theta_vol npt.assert_array_equal(param.get_theta(), theta) - def test_bounds(self): + def test_bounds(self) -> None: """Test bounds.""" - param = HestonParam() - self.assertEqual(len(param.get_bounds()), 6) - self.assertEqual(len(param.get_bounds(subset='all')), 6) - self.assertEqual(len(param.get_bounds(subset='all', measure='PQ')), 6) - self.assertEqual(len(param.get_bounds(subset='all', measure='P')), 5) - self.assertEqual(len(param.get_bounds(subset='all', measure='Q')), 5) - self.assertEqual(len(param.get_bounds(subset='vol')), 4) - self.assertEqual(len(param.get_bounds(subset='vol', measure='PQ')), 4) - self.assertEqual(len(param.get_bounds(subset='vol', measure='P')), 3) - self.assertEqual(len(param.get_bounds(subset='vol', measure='Q')), 3) - - def test_validity(self): + bounds = param.get_bounds() + assert bounds is not None and len(bounds) == 6 + bounds_all = param.get_bounds(subset="all") + assert bounds_all is not None and len(bounds_all) == 6 + bounds_pq = param.get_bounds(subset="all", measure="PQ") + assert bounds_pq is not None and len(bounds_pq) == 6 + bounds_p = param.get_bounds(subset="all", measure="P") + assert bounds_p is not None and len(bounds_p) == 5 + bounds_q = param.get_bounds(subset="all", measure="Q") + assert bounds_q is not None and len(bounds_q) == 5 + bounds_vol = param.get_bounds(subset="vol") + assert bounds_vol is not None and len(bounds_vol) == 4 + bounds_vol_pq = param.get_bounds(subset="vol", measure="PQ") + assert bounds_vol_pq is not None and len(bounds_vol_pq) == 4 + bounds_vol_p = param.get_bounds(subset="vol", measure="P") + assert bounds_vol_p is not None and len(bounds_vol_p) == 3 + bounds_vol_q = param.get_bounds(subset="vol", measure="Q") + assert bounds_vol_q is not None and len(bounds_vol_q) == 3 + + def test_validity(self) -> None: """Test validity.""" - - riskfree = .01 - mean_v = .5 + riskfree = 0.01 + mean_v = 0.5 kappa = 1.5 - eta = .1 - lmbd = .01 - lmbd_v = .5 - rho = -.5 - - param = HestonParam(riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho, measure='P') - - self.assertTrue(param.is_valid()) - param = HestonParam(riskfree=riskfree, lmbd=lmbd, - mean_v=-mean_v, kappa=kappa, - eta=eta, rho=rho) - self.assertFalse(param.is_valid()) - param = HestonParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa=-kappa, - eta=eta, rho=rho) - self.assertFalse(param.is_valid()) - param = HestonParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa=-kappa, - eta=-eta, rho=rho) - self.assertFalse(param.is_valid()) - - -if __name__ == '__main__': - ut.main() + eta = 0.1 + lmbd = 0.01 + lmbd_v = 0.5 + rho = -0.5 + + param = HestonParam( + riskfree=riskfree, lmbd=lmbd, lmbd_v=lmbd_v, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, measure="P" + ) + + assert param.is_valid() + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=-mean_v, kappa=kappa, eta=eta, rho=rho) + assert not param.is_valid() + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=-kappa, eta=eta, rho=rho) + assert not param.is_valid() + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=-kappa, eta=-eta, rho=rho) + assert not param.is_valid() diff --git a/tests/test_param_vasicek.py b/tests/test_param_vasicek.py index 46ab13e..4293c9c 100644 --- a/tests/test_param_vasicek.py +++ b/tests/test_param_vasicek.py @@ -1,39 +1,30 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for Vasicek parameter class. +"""Test suite for Vasicek parameter class.""" -""" -from __future__ import print_function, division - -import unittest as ut import numpy as np import numpy.testing as npt from affidiff import VasicekParam -class SDEParameterTestCase(ut.TestCase): +class TestSDEParameter: """Test parameter classes.""" - def test_vasicekparam_class(self): + def test_vasicekparam_class(self) -> None: """Test Vasicek parameter class.""" + mean, kappa, eta = 1.5, 1.0, 0.2 + param = VasicekParam(mean=mean, kappa=kappa, eta=eta) - mean, kappa, eta = 1.5, 1., .2 - param = VasicekParam(mean, kappa, eta) - - self.assertEqual(param.get_model_name(), 'Vasicek') - self.assertEqual(param.get_names(), ['mean', 'kappa', 'eta']) + assert param.get_model_name() == "Vasicek" + assert param.get_names() == ["mean", "kappa", "eta"] - self.assertEqual(param.mean, mean) - self.assertEqual(param.kappa, kappa) - self.assertEqual(param.eta, eta) + assert param.mean == mean + assert param.kappa == kappa + assert param.eta == eta - npt.assert_array_equal(param.get_theta(), - np.array([mean, kappa, eta])) + npt.assert_array_equal(param.get_theta(), np.array([mean, kappa, eta])) theta = np.ones(3) - param = VasicekParam.from_theta(theta) + param = VasicekParam.from_theta(theta=theta) npt.assert_array_equal(param.get_theta(), theta) mat_k0 = param.kappa * param.mean @@ -47,7 +38,7 @@ def test_vasicekparam_class(self): npt.assert_array_equal(param.mat_h1, mat_h1) theta *= 2 - param.update(theta) + param.update(theta=theta) npt.assert_array_equal(param.get_theta(), theta) mat_k0 = param.kappa * param.mean @@ -60,12 +51,8 @@ def test_vasicekparam_class(self): npt.assert_array_equal(param.mat_h0, mat_h0) npt.assert_array_equal(param.mat_h1, mat_h1) - self.assertTrue(param.is_valid()) - param = VasicekParam(mean, -kappa, eta) - self.assertFalse(param.is_valid()) - param = VasicekParam(mean, kappa, -eta) - self.assertFalse(param.is_valid()) - - -if __name__ == '__main__': - ut.main() + assert param.is_valid() + param = VasicekParam(mean=mean, kappa=-kappa, eta=eta) + assert not param.is_valid() + param = VasicekParam(mean=mean, kappa=kappa, eta=-eta) + assert not param.is_valid() diff --git a/tests/test_rmoms_ct.py b/tests/test_rmoms_ct.py index 84fb81a..2f98e01 100644 --- a/tests/test_rmoms_ct.py +++ b/tests/test_rmoms_ct.py @@ -1,64 +1,70 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for realized moments of Central Tendency. +"""Test suite for realized moments of Central Tendency.""" -""" -from __future__ import print_function, division - -import unittest as ut import numpy as np import numpy.testing as npt +import pytest from affidiff import CentTend, CentTendParam -class RealizedMomentsCTTestCase(ut.TestCase): +class TestRealizedMomentsCT: """Test realized moments for CT.""" - def test_ct_depvar(self): + def test_ct_depvar(self) -> None: """Test dependent varibales of Central Tendency model.""" - - riskfree = .01 - lmbd = .01 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) centtend.nsub = 2 nperiods = 10 ret = np.arange(nperiods) - rvar = ret ** 2 + rvar = ret**2 data = np.vstack([ret, rvar]) - depvar = centtend.realized_depvar(data) + depvar = centtend.realized_depvar(data=data) # Test shape of dependent variables - self.assertEqual(depvar.shape, (nperiods, 6 * 4)) + assert depvar.shape == (nperiods, 6 * 4) - def test_ct_var_instr(self): + def test_ct_var_instr(self) -> None: """Test variable instruments of Central Tendency model.""" - - riskfree = .01 - lmbd = .01 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) centtend.nsub = 2 @@ -66,39 +72,44 @@ def test_ct_var_instr(self): nperiods = 10 ret = np.arange(nperiods) - rvar = ret ** 2 + rvar = ret**2 data = np.vstack([ret, rvar]) instrlag = 2 instr_data = np.vstack([rvar, rvar**2]) ninstr = instr_data.shape[0] - mom, dmom = centtend.integrated_mom(param.get_theta(), - instr_data=instr_data, - instr_choice='var', - data=data, instrlag=instrlag) - nmoms_all = nmoms * (ninstr*instrlag + 1) + mom, dmom = centtend.integrated_mom( + theta=param.get_theta(), instr_data=instr_data, instr_choice="var", data=data, instrlag=instrlag + ) + nmoms_all = nmoms * (ninstr * instrlag + 1) mom_shape = (nperiods - instrlag, nmoms_all) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) - self.assertIsNone(dmom) + assert mom.shape == mom_shape + assert dmom is None - def test_const_instr(self): + def test_const_instr(self) -> None: """Test constant instrument of Central Tendency model.""" - - riskfree = .01 - lmbd = .01 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) centtend.nsub = 2 @@ -106,459 +117,566 @@ def test_const_instr(self): nperiods = 10 ret = np.arange(nperiods) - rvar = ret ** 2 + rvar = ret**2 data = np.vstack([ret, rvar]) instrlag = 2 - depvar = centtend.realized_depvar(data) + depvar = centtend.realized_depvar(data=data) - mom, dmom = centtend.integrated_mom(param.get_theta(), - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = centtend.integrated_mom(theta=param.get_theta(), instr_choice="const", data=data, instrlag=instrlag) nmoms_all = nmoms mom_shape = (nperiods - instrlag, nmoms_all) aggh = 2 - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error = depvar.dot(centtend.mat_a(param, None).T) \ - - centtend.realized_const(param, aggh, None) + error = depvar.dot(centtend.mat_a(param=param, subset=None).T) - centtend.realized_const( + param=param, aggh=aggh, subset=None + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) - self.assertIsNone(dmom) + assert mom.shape == mom_shape + assert dmom is None - def test_vol_p(self): + def test_vol_p(self) -> None: """Test vol P realized moments of Central Tendency model.""" - - riskfree = .01 - lmbd = .01 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) centtend.nsub = 2 - nmoms = 4 nperiods = 10 ret = np.arange(nperiods) - rvar = ret ** 2 + rvar = ret**2 data = np.vstack([ret, rvar]) instrlag = 2 - depvar = centtend.realized_depvar(data) + depvar = centtend.realized_depvar(data=data) aggh = 2 - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] - - subset = 'vol' - mom, dmom = centtend.integrated_mom(param.get_theta(subset=subset), - subset=subset, - instr_choice='const', - data=data, instrlag=instrlag) + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] + + subset = "vol" + mom, dmom = centtend.integrated_mom( + theta=param.get_theta(subset=subset), subset=subset, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 2 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) subset_sl = slice(2) - error = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) + error = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - def test_ct_relized_mom(self): + def test_ct_relized_mom(self) -> None: """Test realized moments of Central Tendency model.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) centtend.nsub = 2 - nmoms = 4 nperiods = 10 ret = np.arange(nperiods) - rvar = ret ** 2 + rvar = ret**2 data = np.vstack([ret, rvar]) instrlag = 2 - subset = 'vol' - measure = 'Q' + subset = "vol" + measure = "Q" theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = centtend.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = centtend.integrated_mom( + theta=theta, subset=subset, measure=measure, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 2 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = slice(2) aggh = 2 - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) + error = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - subset = 'vol' - measure = 'P' + subset = "vol" + measure = "P" theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = centtend.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = centtend.integrated_mom( + theta=theta, subset=subset, measure=measure, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 2 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = slice(2) aggh = 2 - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) + error = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) - subset = 'vol' - measure = 'PQ' + subset = "vol" + measure = "PQ" theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = centtend.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - aggh=[aggh, aggh], - data=[data, data], - instrlag=instrlag) + mom, dmom = centtend.integrated_mom( + theta=theta, + subset=subset, + measure=measure, + instr_choice="const", + aggh=[aggh, aggh], + data=[data, data], + instrlag=instrlag, + ) nmoms = 2 - mom_shape = (nperiods - instrlag, nmoms*2) + mom_shape = (nperiods - instrlag, nmoms * 2) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = slice(2) aggh = 10 - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error_q = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) - - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + error_q = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) + + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error_p = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) - - npt.assert_array_almost_equal(np.hstack((error_p, error_q)), - np.zeros(mom_shape)) - - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=2*lmbd_s, lmbd_y=3*lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + error_p = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) + + npt.assert_array_almost_equal(np.hstack((error_p, error_q)), np.zeros(mom_shape)) + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=2 * lmbd_s, + lmbd_y=3 * lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) theta = param.get_theta(subset=subset, measure=measure) - mom2, dmom = centtend.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - aggh=[aggh, aggh], - data=[data, data], - instrlag=instrlag) - - self.assertFalse(np.allclose(mom, mom2)) - - def test_ct_relized_mom_all(self): + mom2, dmom = centtend.integrated_mom( + theta=theta, + subset=subset, + measure=measure, + instr_choice="const", + aggh=[aggh, aggh], + data=[data, data], + instrlag=instrlag, + ) + + assert not np.allclose(mom, mom2) + + def test_ct_relized_mom_all(self) -> None: """Test realized moments of Central Tendency model.""" - - riskfree = .01 - lmbd = .01 - lmbd_s = .5 - lmbd_y = .5 - mean_v = .5 + riskfree = 0.01 + lmbd = 0.01 + lmbd_s = 0.5 + lmbd_y = 0.5 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) centtend.nsub = 2 - nmoms = 4 nperiods = 10 ret = np.arange(nperiods) - rvar = ret ** 2 + rvar = ret**2 data = np.vstack([ret, rvar]) instrlag = 2 - subset = 'all' - measure = 'Q' - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=lmbd_s, lmbd_y=lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + subset = "all" + measure = "Q" + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = centtend.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = centtend.integrated_mom( + theta=theta, subset=subset, measure=measure, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 4 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = None aggh = 2 - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) + error = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - subset = 'all' - measure = 'P' - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=lmbd_s, lmbd_y=lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + subset = "all" + measure = "P" + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = centtend.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = centtend.integrated_mom( + theta=theta, subset=subset, measure=measure, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 4 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = None aggh = 2 - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) + error = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - subset = 'all' - measure = 'PQ' - param = CentTendParam(riskfree=riskfree, - lmbd=lmbd, lmbd_s=lmbd_s, lmbd_y=lmbd_y, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + subset = "all" + measure = "PQ" + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + lmbd_s=lmbd_s, + lmbd_y=lmbd_y, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = centtend.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - aggh=[aggh, aggh], - data=[data, data], - instrlag=instrlag) + mom, dmom = centtend.integrated_mom( + theta=theta, + subset=subset, + measure=measure, + instr_choice="const", + aggh=[aggh, aggh], + data=[data, data], + instrlag=instrlag, + ) nmoms = 4 - mom_shape = (nperiods - instrlag, nmoms*2) + mom_shape = (nperiods - instrlag, nmoms * 2) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = None aggh = 2 - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error_q = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) - - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) - means = [centtend.mean_vol(param, aggh), - centtend.mean_vol2(param, aggh), - centtend.mean_ret(param, aggh), - centtend.mean_cross(param, aggh)] + error_q = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) + + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) + means = [ + centtend.mean_vol(param=param, aggh=aggh), + centtend.mean_vol2(param=param, aggh=aggh), + centtend.mean_ret(param=param, aggh=aggh), + centtend.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 6) - error_p = depvar.dot(centtend.mat_a(param, subset_sl).T) \ - - centtend.realized_const(param, aggh, subset_sl) + error_p = depvar.dot(centtend.mat_a(param=param, subset=subset_sl).T) - centtend.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) - npt.assert_array_almost_equal(np.hstack((error_p, error_q)), - np.zeros(mom_shape)) + npt.assert_array_almost_equal(np.hstack((error_p, error_q)), np.zeros(mom_shape)) - def test_ct_coefs(self): - """Test coefficients in descretization of CT model. - - """ - riskfree = .01 - lmbd = .01 - mean_v = .5 + def test_ct_coefs(self) -> None: + """Test coefficients in descretization of CT model.""" + riskfree = 0.01 + lmbd = 0.01 + mean_v = 0.5 kappa_s = 1.5 - kappa_y = .5 - eta_s = .1 - eta_y = .01 - rho = -.5 - param = CentTendParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, - eta_s=eta_s, eta_y=eta_y, rho=rho) + kappa_y = 0.5 + eta_s = 0.1 + eta_y = 0.01 + rho = -0.5 + param = CentTendParam( + riskfree=riskfree, + lmbd=lmbd, + mean_v=mean_v, + kappa_s=kappa_s, + kappa_y=kappa_y, + eta_s=eta_s, + eta_y=eta_y, + rho=rho, + ) centtend = CentTend(param) centtend.nsub = 10 aggh = 2 - self.assertIsInstance(centtend.coef_big_as(param, aggh), float) - self.assertIsInstance(centtend.coef_big_bs(param, aggh), float) - self.assertIsInstance(centtend.coef_big_cs(param, aggh), float) - self.assertIsInstance(centtend.coef_big_ay(param, aggh), float) - self.assertIsInstance(centtend.coef_big_cy(param, aggh), float) - self.assertIsInstance(centtend.coef_small_as(param, aggh), float) - self.assertIsInstance(centtend.coef_small_bs(param, aggh), float) - self.assertIsInstance(centtend.coef_small_cs(param, aggh), float) - - self.assertEqual(len(centtend.roots(param, aggh)), 5) - roots = [centtend.coef_big_as(param, aggh), - centtend.coef_big_ay(param, aggh), - centtend.coef_big_as(param, aggh)**2, - centtend.coef_big_ay(param, aggh)**2, - centtend.coef_big_as(param, aggh) - * centtend.coef_big_ay(param, aggh)] - self.assertEqual(centtend.roots(param, aggh), roots) - - self.assertEqual(len(centtend.depvar_unc_mean(param, aggh)), 4) - - self.assertEqual(centtend.mat_a0(param, aggh).shape, (4, 4)) - self.assertEqual(centtend.mat_a0(param, aggh)[1, 1], 1.) - - self.assertEqual(centtend.mat_a1(param, aggh).shape, (4, 4)) - expect = -np.sum(centtend.roots(param, aggh)) - self.assertEqual(centtend.mat_a1(param, aggh)[1, 1], expect) - - self.assertEqual(centtend.mat_a2(param, aggh).shape, (4, 4)) - - self.assertEqual(centtend.mat_a3(param, aggh).shape, (4, 4)) - self.assertEqual(centtend.mat_a3(param, aggh)[0, 0], 1.) - self.assertEqual(centtend.mat_a3(param, aggh)[3, 1], .5 - param.lmbd) - - self.assertEqual(centtend.mat_a4(param, aggh).shape, (4, 4)) - expect = -np.sum(centtend.roots(param, aggh)[:2]) - self.assertEqual(centtend.mat_a4(param, aggh)[0, 0], expect) - self.assertEqual(centtend.mat_a4(param, aggh)[3, 1], - (.5 - param.lmbd) * expect) - - self.assertEqual(centtend.mat_a5(param, aggh).shape, (4, 4)) - expect = np.prod(centtend.roots(param, aggh)[:2]) - self.assertEqual(centtend.mat_a5(param, aggh)[0, 0], expect) - self.assertEqual(centtend.mat_a5(param, aggh)[3, 1], - (.5 - param.lmbd) * expect) - expect = -np.prod(centtend.roots(param, aggh)) - self.assertEqual(centtend.mat_a5(param, aggh)[1, 1], expect) - self.assertEqual(centtend.mat_a5(param, aggh)[2, 2], 1.) - self.assertEqual(centtend.mat_a5(param, aggh)[2, 0], .5 - param.lmbd) - - self.assertEqual(centtend.mat_a(param).shape, (4, 6*4)) - - self.assertEqual(centtend.realized_const(param, aggh).shape, (4, )) - self.assertEqual(centtend.realized_const(param, aggh)[2], 0) - - roots = [centtend.coef_big_as(param, 1), - centtend.coef_big_ay(param, 1), - centtend.coef_big_as(param, 1)**2, - centtend.coef_big_ay(param, 1)**2, - centtend.coef_big_as(param, 1) - * centtend.coef_big_ay(param, 1)] - - res = centtend.depvar_unc_mean(param, aggh)[0] \ - * (1 - roots[0]) * (1 - roots[1]) - - self.assertAlmostEqual(centtend.realized_const(param, aggh)[0], res) - - res = centtend.depvar_unc_mean(param, aggh)[1] \ - * (1 - roots[0]) * (1 - roots[1]) * (1 - roots[2]) \ - * (1 - roots[3]) * (1 - roots[4]) - - self.assertAlmostEqual(centtend.realized_const(param, aggh)[1], res) - - res = (centtend.depvar_unc_mean(param, aggh)[1] * (.5 - param.lmbd) \ - + centtend.depvar_unc_mean(param, aggh)[3]) \ - * (1 - roots[0]) * (1 - roots[1]) - - self.assertAlmostEqual(centtend.realized_const(param, aggh)[3], res) - - -if __name__ == '__main__': - - ut.main() + assert isinstance(centtend.coef_big_as(param=param, aggh=aggh), float) + assert isinstance(centtend.coef_big_bs(param=param, aggh=aggh), float) + assert isinstance(centtend.coef_big_cs(param=param, aggh=aggh), float) + assert isinstance(centtend.coef_big_ay(param=param, aggh=aggh), float) + assert isinstance(centtend.coef_big_cy(param=param, aggh=aggh), float) + assert isinstance(centtend.coef_small_as(param=param, aggh=aggh), float) + assert isinstance(centtend.coef_small_bs(param=param, aggh=aggh), float) + assert isinstance(centtend.coef_small_cs(param=param, aggh=aggh), float) + + assert len(centtend.roots(param=param, aggh=aggh)) == 5 + roots = [ + centtend.coef_big_as(param=param, aggh=aggh), + centtend.coef_big_ay(param=param, aggh=aggh), + centtend.coef_big_as(param=param, aggh=aggh) ** 2, + centtend.coef_big_ay(param=param, aggh=aggh) ** 2, + centtend.coef_big_as(param=param, aggh=aggh) * centtend.coef_big_ay(param=param, aggh=aggh), + ] + assert centtend.roots(param=param, aggh=aggh) == roots + + assert len(centtend.depvar_unc_mean(param=param, aggh=aggh)) == 4 + + assert centtend.mat_a0(param=param, aggh=aggh).shape == (4, 4) + assert centtend.mat_a0(param=param, aggh=aggh)[1, 1] == 1.0 + + assert centtend.mat_a1(param=param, aggh=aggh).shape == (4, 4) + expect = -np.sum(centtend.roots(param=param, aggh=aggh)) + assert centtend.mat_a1(param=param, aggh=aggh)[1, 1] == expect + + assert centtend.mat_a2(param=param, aggh=aggh).shape == (4, 4) + + assert centtend.mat_a3(param=param, aggh=aggh).shape == (4, 4) + assert centtend.mat_a3(param=param, aggh=aggh)[0, 0] == 1.0 + assert centtend.mat_a3(param=param, aggh=aggh)[3, 1] == 0.5 - param.lmbd + + assert centtend.mat_a4(param=param, aggh=aggh).shape == (4, 4) + expect = -np.sum(centtend.roots(param=param, aggh=aggh)[:2]) + assert centtend.mat_a4(param=param, aggh=aggh)[0, 0] == expect + assert centtend.mat_a4(param=param, aggh=aggh)[3, 1] == (0.5 - param.lmbd) * expect + + assert centtend.mat_a5(param=param, aggh=aggh).shape == (4, 4) + expect = np.prod(centtend.roots(param=param, aggh=aggh)[:2]) + assert centtend.mat_a5(param=param, aggh=aggh)[0, 0] == expect + assert centtend.mat_a5(param=param, aggh=aggh)[3, 1] == (0.5 - param.lmbd) * expect + expect = -np.prod(centtend.roots(param=param, aggh=aggh)) + assert centtend.mat_a5(param=param, aggh=aggh)[1, 1] == expect + assert centtend.mat_a5(param=param, aggh=aggh)[2, 2] == 1.0 + assert centtend.mat_a5(param=param, aggh=aggh)[2, 0] == 0.5 - param.lmbd + + assert centtend.mat_a(param=param).shape == (4, 6 * 4) + + assert centtend.realized_const(param=param, aggh=aggh).shape == (4,) + assert centtend.realized_const(param=param, aggh=aggh)[2] == 0 + + roots = [ + centtend.coef_big_as(param=param, aggh=1), + centtend.coef_big_ay(param=param, aggh=1), + centtend.coef_big_as(param=param, aggh=1) ** 2, + centtend.coef_big_ay(param=param, aggh=1) ** 2, + centtend.coef_big_as(param=param, aggh=1) * centtend.coef_big_ay(param=param, aggh=1), + ] + + res = centtend.depvar_unc_mean(param=param, aggh=aggh)[0] * (1 - roots[0]) * (1 - roots[1]) + + assert centtend.realized_const(param=param, aggh=aggh)[0] == pytest.approx(res) + + res = ( + centtend.depvar_unc_mean(param=param, aggh=aggh)[1] + * (1 - roots[0]) + * (1 - roots[1]) + * (1 - roots[2]) + * (1 - roots[3]) + * (1 - roots[4]) + ) + + assert centtend.realized_const(param=param, aggh=aggh)[1] == pytest.approx(res) + + res = ( + ( + centtend.depvar_unc_mean(param=param, aggh=aggh)[1] * (0.5 - param.lmbd) + + centtend.depvar_unc_mean(param=param, aggh=aggh)[3] + ) + * (1 - roots[0]) + * (1 - roots[1]) + ) + + assert centtend.realized_const(param=param, aggh=aggh)[3] == pytest.approx(res) diff --git a/tests/test_rmoms_gbm.py b/tests/test_rmoms_gbm.py index 3b36de4..cb54ae4 100644 --- a/tests/test_rmoms_gbm.py +++ b/tests/test_rmoms_gbm.py @@ -1,24 +1,17 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for realized moments of GBM. +"""Test suite for realized moments of GBM.""" -""" -from __future__ import print_function, division - -import unittest as ut import numpy as np from affidiff import GBM, GBMparam -class RealizedMomentsGBMTestCase(ut.TestCase): +class TestRealizedMomentsGBM: """Test realized moments for GBM.""" - def test_gbm_relized_mom(self): + def test_gbm_relized_mom(self) -> None: """Test realized moments of GBM model.""" - mean, sigma = 1.5, .2 - param = GBMparam(mean, sigma) + mean, sigma = 1.5, 0.2 + param = GBMparam(mean=mean, sigma=sigma) gbm = GBM(param) gbm.nsub = 2 @@ -26,27 +19,21 @@ def test_gbm_relized_mom(self): data = np.ones((2, nperiods)) instrlag = 2 - depvar = gbm.realized_depvar(data) + depvar = gbm.realized_depvar(data=data) # Test shape of dependent variables - self.assertEqual(depvar.shape, (3, nperiods)) + assert depvar.shape == (3, nperiods) - const = gbm.realized_const(param.get_theta()) + const = gbm.realized_const(param=param.get_theta()) # Test shape of the intercept - self.assertEqual(const.shape, (3, )) + assert const.shape == (3,) - instr = gbm.instruments(data, instrlag=instrlag) + instr = gbm.instruments(data=data, instrlag=instrlag) ninstr = 1 + data.shape[0] * instrlag # Test shape of instrument matrix - self.assertEqual(instr.shape, (ninstr, nperiods - instrlag)) + assert instr.shape == (ninstr, nperiods - instrlag) - rmom, drmom = gbm.integrated_mom(param.get_theta(), data=data, - instrlag=instrlag) + rmom, drmom = gbm.integrated_mom(theta=param.get_theta(), data=data, instrlag=instrlag) nmoms = 3 * ninstr # Test shape of moments and gradients - self.assertEqual(rmom.shape, (nperiods - instrlag, nmoms)) - self.assertEqual(drmom.shape, (nmoms, np.size(param.get_theta()))) - - -if __name__ == '__main__': - - ut.main() + assert rmom.shape == (nperiods - instrlag, nmoms) + assert drmom.shape == (nmoms, np.size(param.get_theta())) diff --git a/tests/test_rmoms_heston.py b/tests/test_rmoms_heston.py index 2cf6648..6ff461e 100644 --- a/tests/test_rmoms_heston.py +++ b/tests/test_rmoms_heston.py @@ -1,457 +1,454 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Test suite for realized moments of Heston. +"""Test suite for realized moments of Heston.""" -""" -from __future__ import print_function, division - -import unittest as ut import numpy as np import numpy.testing as npt +import pytest from affidiff import Heston, HestonParam -class RealizedMomentsHestonTestCase(ut.TestCase): +class TestRealizedMomentsHeston: """Test realized moments for Heston.""" - def test_heston_depvar(self): + def test_heston_depvar(self) -> None: """Test dependent variables of Heston model.""" - - riskfree = 0. - lmbd, mean_v, kappa, eta, rho = .01, .2, 1.5, .2**.5, -.5 - lmbd_v = .2 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + riskfree = 0.0 + lmbd, mean_v, kappa, eta, rho = 0.01, 0.2, 1.5, 0.2**0.5, -0.5 + lmbd_v = 0.2 + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) heston = Heston(param) heston.nsub = 2 nperiods = 5 - ret = np.ones(nperiods) * (lmbd - .5) * mean_v + ret = np.ones(nperiods) * (lmbd - 0.5) * mean_v rvar = np.ones(nperiods) * mean_v data = np.vstack([ret, rvar]) - depvar = heston.realized_depvar(data) + depvar = heston.realized_depvar(data=data) # Test shape of dependent variables - self.assertEqual(depvar.shape, (nperiods, 3 * 4)) + assert depvar.shape == (nperiods, 3 * 4) - def test_heston_var_instr(self): + def test_heston_var_instr(self) -> None: """Test realized moments with variable instruments of Heston model.""" - - riskfree = 0. - lmbd, mean_v, kappa, eta, rho = .01, .2, 1.5, .2**.5, -.5 - lmbd_v = .2 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + riskfree = 0.0 + lmbd, mean_v, kappa, eta, rho = 0.01, 0.2, 1.5, 0.2**0.5, -0.5 + lmbd_v = 0.2 + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) heston = Heston(param) heston.nsub = 2 nmoms = 4 nperiods = 5 - ret = np.ones(nperiods) * (lmbd - .5) * mean_v + ret = np.ones(nperiods) * (lmbd - 0.5) * mean_v rvar = np.ones(nperiods) * mean_v data = np.vstack([ret, rvar]) instrlag = 2 - theta = param.get_theta(subset='all', measure='P') + theta = param.get_theta(subset="all", measure="P") instr_data = np.vstack([rvar, rvar**2]) ninstr = instr_data.shape[0] - mom, dmom = heston.integrated_mom(theta, instr_data=instr_data, - instr_choice='var', - data=data, instrlag=instrlag) - nmoms_all = nmoms * (ninstr*instrlag + 1) + mom, dmom = heston.integrated_mom( + theta=theta, instr_data=instr_data, instr_choice="var", data=data, instrlag=instrlag + ) + nmoms_all = nmoms * (ninstr * instrlag + 1) mom_shape = (nperiods - instrlag, nmoms_all) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) - self.assertIsNone(dmom) + assert mom.shape == mom_shape + assert dmom is None - def test_const_instr(self): + def test_const_instr(self) -> None: """Test constant instrument of Heston model.""" - - riskfree = 0. - lmbd, mean_v, kappa, eta, rho = .01, .2, 1.5, .2**.5, -.5 - lmbd_v = .2 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + riskfree = 0.0 + lmbd, mean_v, kappa, eta, rho = 0.01, 0.2, 1.5, 0.2**0.5, -0.5 + lmbd_v = 0.2 + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) heston = Heston(param) heston.nsub = 2 nmoms = 4 nperiods = 5 - ret = np.ones(nperiods) * (lmbd - .5) * mean_v + ret = np.ones(nperiods) * (lmbd - 0.5) * mean_v rvar = np.ones(nperiods) * mean_v data = np.vstack([ret, rvar]) instrlag = 2 - theta = param.get_theta(subset='all', measure='P') + theta = param.get_theta(subset="all", measure="P") - depvar = heston.realized_depvar(data) + depvar = heston.realized_depvar(data=data) - mom, dmom = heston.integrated_mom(theta, instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = heston.integrated_mom(theta=theta, instr_choice="const", data=data, instrlag=instrlag) nmoms_all = nmoms mom_shape = (nperiods - instrlag, nmoms_all) aggh = 2 - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error = depvar.dot(heston.mat_a(param, None).T) \ - - heston.realized_const(param, aggh, None) + error = depvar.dot(heston.mat_a(param=param, subset=None).T) - heston.realized_const( + param=param, aggh=aggh, subset=None + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape - def test_vol_p(self): + def test_vol_p(self) -> None: """Test vol P realized moments of Heston model.""" - - riskfree = 0. - lmbd, mean_v, kappa, eta, rho = .01, .2, 1.5, .2**.5, -.5 - lmbd_v = .2 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + riskfree = 0.0 + lmbd, mean_v, kappa, eta, rho = 0.01, 0.2, 1.5, 0.2**0.5, -0.5 + lmbd_v = 0.2 + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) heston = Heston(param) heston.nsub = 2 - nmoms = 4 nperiods = 5 - ret = np.ones(nperiods) * (lmbd - .5) * mean_v + ret = np.ones(nperiods) * (lmbd - 0.5) * mean_v rvar = np.ones(nperiods) * mean_v data = np.vstack([ret, rvar]) instrlag = 2 - theta = param.get_theta(subset='all', measure='P') - - depvar = heston.realized_depvar(data) aggh = 2 - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] - - subset = 'vol' + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] + + subset = "vol" theta = param.get_theta(subset=subset) - mom, dmom = heston.integrated_mom(theta, subset=subset, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = heston.integrated_mom( + theta=theta, subset=subset, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 2 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) subset_sl = slice(2) - error = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) + error = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - def test_heston_relized_mom(self): + def test_heston_relized_mom(self) -> None: """Test realized moments of Heston model.""" - - riskfree = 0. - lmbd, mean_v, kappa, eta, rho = .01, .2, 1.5, .2**.5, -.5 - lmbd_v = .2 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + riskfree = 0.0 + lmbd, mean_v, kappa, eta, rho = 0.01, 0.2, 1.5, 0.2**0.5, -0.5 + lmbd_v = 0.2 + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) heston = Heston(param) heston.nsub = 2 - nmoms = 4 nperiods = 5 - ret = np.ones(nperiods) * (lmbd - .5) * mean_v + ret = np.ones(nperiods) * (lmbd - 0.5) * mean_v rvar = np.ones(nperiods) * mean_v data = np.vstack([ret, rvar]) instrlag = 2 - theta = param.get_theta(subset='all', measure='P') - depvar = heston.realized_depvar(data) - - subset = 'vol' - measure = 'Q' + subset = "vol" + measure = "Q" theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = heston.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = heston.integrated_mom( + theta=theta, subset=subset, measure=measure, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 2 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = slice(2) aggh = 2 - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) + error = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - subset = 'vol' - measure = 'P' + subset = "vol" + measure = "P" theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = heston.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = heston.integrated_mom( + theta=theta, subset=subset, measure=measure, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 2 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = slice(2) aggh = 2 - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) + error = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) heston = Heston(param) - subset = 'vol' - measure = 'PQ' + subset = "vol" + measure = "PQ" theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = heston.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - aggh=[aggh, aggh], - data=[data, data], instrlag=instrlag) + mom, dmom = heston.integrated_mom( + theta=theta, + subset=subset, + measure=measure, + instr_choice="const", + aggh=[aggh, aggh], + data=[data, data], + instrlag=instrlag, + ) nmoms = 2 - mom_shape = (nperiods - instrlag, nmoms*2) + mom_shape = (nperiods - instrlag, nmoms * 2) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = slice(2) aggh = 10 - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error_q = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) - - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) - - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + error_q = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) + + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error_p = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) + error_p = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) - npt.assert_array_almost_equal(np.hstack((error_p, error_q)), - np.zeros(mom_shape)) + npt.assert_array_almost_equal(np.hstack((error_p, error_q)), np.zeros(mom_shape)) - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=2*lmbd_v) + param = HestonParam( + riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=2 * lmbd_v + ) heston = Heston(param) theta = param.get_theta(subset=subset, measure=measure) - mom2, dmom = heston.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - aggh=[aggh, aggh], - data=[data, data], instrlag=instrlag) - - self.assertFalse(np.allclose(mom, mom2)) - - def test_heston_relized_mom_all(self): + mom2, dmom = heston.integrated_mom( + theta=theta, + subset=subset, + measure=measure, + instr_choice="const", + aggh=[aggh, aggh], + data=[data, data], + instrlag=instrlag, + ) + + assert not np.allclose(mom, mom2) + + def test_heston_relized_mom_all(self) -> None: """Test realized moments of Heston model.""" - - riskfree = 0. - lmbd, mean_v, kappa, eta, rho = .01, .2, 1.5, .2**.5, -.5 - lmbd_v = .2 + riskfree = 0.0 + lmbd, mean_v, kappa, eta, rho = 0.01, 0.2, 1.5, 0.2**0.5, -0.5 + lmbd_v = 0.2 nperiods = 5 instrlag = 2 - ret = np.ones(nperiods) * (lmbd - .5) * mean_v + ret = np.ones(nperiods) * (lmbd - 0.5) * mean_v rvar = np.ones(nperiods) * mean_v data = np.vstack([ret, rvar]) - subset = 'all' - measure = 'Q' - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + subset = "all" + measure = "Q" + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) heston = Heston(param) theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = heston.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = heston.integrated_mom( + theta=theta, subset=subset, measure=measure, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 4 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = None aggh = 2 - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) + error = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - subset = 'all' - measure = 'P' - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + subset = "all" + measure = "P" + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = heston.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - data=data, instrlag=instrlag) + mom, dmom = heston.integrated_mom( + theta=theta, subset=subset, measure=measure, instr_choice="const", data=data, instrlag=instrlag + ) nmoms = 4 mom_shape = (nperiods - instrlag, nmoms) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = None aggh = 2 - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) + error = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) npt.assert_array_almost_equal(error, np.zeros(mom_shape)) - subset = 'all' - measure = 'PQ' - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + subset = "all" + measure = "PQ" + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) theta = param.get_theta(subset=subset, measure=measure) - mom, dmom = heston.integrated_mom(theta, subset=subset, - measure=measure, - instr_choice='const', - aggh=[aggh, aggh], - data=[data, data], instrlag=instrlag) + mom, dmom = heston.integrated_mom( + theta=theta, + subset=subset, + measure=measure, + instr_choice="const", + aggh=[aggh, aggh], + data=[data, data], + instrlag=instrlag, + ) nmoms = 4 - mom_shape = (nperiods - instrlag, nmoms*2) + mom_shape = (nperiods - instrlag, nmoms * 2) # Test the shape of moment functions - self.assertEqual(mom.shape, mom_shape) + assert mom.shape == mom_shape subset_sl = None aggh = 2 - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error_q = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) - - param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, - kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + error_q = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) + + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] depvar = np.ones((nperiods - instrlag, 4)) * means depvar = np.tile(depvar, 3) - error_p = depvar.dot(heston.mat_a(param, subset_sl).T) \ - - heston.realized_const(param, aggh, subset_sl) - - npt.assert_array_almost_equal(np.hstack((error_p, error_q)), - np.zeros(mom_shape)) + error_p = depvar.dot(heston.mat_a(param=param, subset=subset_sl).T) - heston.realized_const( + param=param, aggh=aggh, subset=subset_sl + ) - def test_heston_coefs(self): - """Test coefficients in descretization of Heston model. + npt.assert_array_almost_equal(np.hstack((error_p, error_q)), np.zeros(mom_shape)) - """ - riskfree, lmbd, mean_v, kappa, eta, rho = 0., .01, .2, 1.5, .2**.5, -.5 - param = HestonParam(riskfree=riskfree, lmbd=lmbd, - mean_v=mean_v, kappa=kappa, - eta=eta, rho=rho) + def test_heston_coefs(self) -> None: + """Test coefficients in descretization of Heston model.""" + riskfree, lmbd, mean_v, kappa, eta, rho = 0.0, 0.01, 0.2, 1.5, 0.2**0.5, -0.5 + param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, kappa=kappa, eta=eta, rho=rho) heston = Heston(param) heston.nsub = 10 aggh = 2 - self.assertIsInstance(heston.coef_big_a(param, aggh), float) - self.assertIsInstance(heston.coef_small_a(param, aggh), float) - self.assertIsInstance(heston.coef_big_c(param, aggh), float) - self.assertIsInstance(heston.coef_small_c(param, aggh), float) - - self.assertEqual(heston.mat_a0(param, aggh).shape, (4, 4)) - self.assertEqual(heston.mat_a1(param, aggh).shape, (4, 4)) - self.assertEqual(heston.mat_a2(param, aggh).shape, (4, 4)) - - self.assertEqual(heston.mat_a(param).shape, (4, 3*4)) - - self.assertEqual(heston.realized_const(param, aggh).shape, (4, )) - self.assertEqual(heston.realized_const(param, aggh)[2], 0) + assert isinstance(heston.coef_big_a(param=param, aggh=aggh), float) + assert isinstance(heston.coef_small_a(param=param, aggh=aggh), float) + assert isinstance(heston.coef_big_c(param=param, aggh=aggh), float) + assert isinstance(heston.coef_small_c(param=param, aggh=aggh), float) - means = [heston.mean_vol(param, aggh), - heston.mean_vol2(param, aggh), - heston.mean_ret(param, aggh), - heston.mean_cross(param, aggh)] + assert heston.mat_a0(param=param, aggh=aggh).shape == (4, 4) + assert heston.mat_a1(param=param, aggh=aggh).shape == (4, 4) + assert heston.mat_a2(param=param, aggh=aggh).shape == (4, 4) - npt.assert_array_equal(heston.depvar_unc_mean(param, aggh), means) + assert heston.mat_a(param=param).shape == (4, 3 * 4) - res = heston.mean_vol(param, aggh) \ - * (1 - heston.coef_big_a(param, 1)) + assert heston.realized_const(param=param, aggh=aggh).shape == (4,) + assert heston.realized_const(param=param, aggh=aggh)[2] == 0 - self.assertEqual(heston.realized_const(param, aggh)[0], res) + means = [ + heston.mean_vol(param=param, aggh=aggh), + heston.mean_vol2(param=param, aggh=aggh), + heston.mean_ret(param=param, aggh=aggh), + heston.mean_cross(param=param, aggh=aggh), + ] - res = heston.mean_vol2(param, aggh) \ - * (1 - heston.coef_big_a(param, 1)) \ - * (1 - heston.coef_big_a(param, 1)**2) + npt.assert_array_equal(heston.depvar_unc_mean(param=param, aggh=aggh), means) - self.assertEqual(heston.realized_const(param, aggh)[1], res) + res = heston.mean_vol(param=param, aggh=aggh) * (1 - heston.coef_big_a(param=param, aggh=1)) - res = heston.mean_ret(param, aggh) \ - + heston.mean_vol(param, aggh) * (.5 - lmbd) + assert heston.realized_const(param=param, aggh=aggh)[0] == res - self.assertEqual(heston.realized_const(param, aggh)[2], res) + res = ( + heston.mean_vol2(param=param, aggh=aggh) + * (1 - heston.coef_big_a(param=param, aggh=1)) + * (1 - heston.coef_big_a(param=param, aggh=1) ** 2) + ) - res = heston.mean_vol2(param, aggh) * (.5 - lmbd) \ - * (1 - heston.coef_big_a(param, 1)) \ - + heston.mean_cross(param, aggh) \ - * (1 - heston.coef_big_a(param, 1)) + assert heston.realized_const(param=param, aggh=aggh)[1] == res - self.assertAlmostEqual(heston.realized_const(param, aggh)[3], res) + res = heston.mean_ret(param=param, aggh=aggh) + heston.mean_vol(param=param, aggh=aggh) * (0.5 - lmbd) + assert heston.realized_const(param=param, aggh=aggh)[2] == res -if __name__ == '__main__': + res = heston.mean_vol2(param=param, aggh=aggh) * (0.5 - lmbd) * ( + 1 - heston.coef_big_a(param=param, aggh=1) + ) + heston.mean_cross(param=param, aggh=aggh) * (1 - heston.coef_big_a(param=param, aggh=1)) - ut.main() + assert heston.realized_const(param=param, aggh=aggh)[3] == pytest.approx(res) diff --git a/tests/test_simulations.py b/tests/test_simulations.py index d77eff5..912aba0 100644 --- a/tests/test_simulations.py +++ b/tests/test_simulations.py @@ -1,606 +1 @@ -# #!/usr/bin/env python -# # -*- coding: utf-8 -*- -# """ -# Test suite for diffusion simulations. -# -# """ -# from __future__ import print_function, division -# -# import unittest as ut -# import numpy as np -# import numpy.testing as npt -# -# from affidiff import (GBM, GBMparam, Vasicek, VasicekParam, -# CIR, CIRparam, Heston, HestonParam, -# CentTend, CentTendParam) -# # from affidiff.simulate import simulate -# -# -# class CythonTestCase(ut.TestCase): -# """Test cython simulate.""" -# -# def test_cython_simulate_gbm(self): -# """Test cython simulation for GBM.""" -# -# nvars = 1 -# mean, sigma = 1.5, .2 -# param = GBMparam(mean, sigma) -# gbm = GBM(param) -# start = np.array([1.]) -# nperiods, nsub, ndiscr, nsim = 5, 2, 3, 4 -# nobs = nperiods * nsub -# dt = 1 / ndiscr / nsub -# paths = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0) -# -# self.assertEqual(gbm.errors.shape, (ndiscr * nobs, 2*nsim, nvars)) -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# -# paths_cython = simulate(gbm.errors, start, np.atleast_1d(param.mat_k0), -# np.atleast_2d(param.mat_k1), -# np.atleast_2d(param.mat_h0), -# np.atleast_3d(param.mat_h1), dt) -# paths_cython = paths_cython[::ndiscr] -# paths_cython[1:, :, 0] = paths_cython[1:, :, 0] \ -# - paths_cython[:-1, :, 0] -# paths_cython = paths_cython[1:] -# -# self.assertEqual(paths_cython.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths, paths_cython) -# -# def test_cython_simulate_heston(self): -# """Test cython simulation for Heston.""" -# -# nvars = 2 -# riskfree, lmbd, mean_v, kappa, eta, rho = 0., .01, .2, 1.5, .2**.5, -.5 -# param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, -# kappa=kappa, eta=eta, rho=rho) -# heston = Heston(param) -# -# start = np.array([1, mean_v]) -# nperiods, nsub, ndiscr, nsim = 5, 2, 3, 4 -# dt = 1 / ndiscr / nsub -# nobs = nperiods * nsub -# -# heston.errors = np.ones((nobs*ndiscr, 2*nsim, nvars)) -# paths = heston.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0, new_innov=False) -# -# paths_cython = simulate(heston.errors, start, -# np.atleast_1d(param.mat_k0), -# np.atleast_2d(param.mat_k1), -# np.atleast_2d(param.mat_h0), -# np.atleast_3d(param.mat_h1), dt) -# paths_cython = paths_cython[::ndiscr] -# paths_cython[1:, :, 0] = paths_cython[1:, :, 0] \ -# - paths_cython[:-1, :, 0] -# paths_cython = paths_cython[1:] -# -# self.assertEqual(paths_cython.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths, paths_cython) -# -# -# class GBMTestCase(ut.TestCase): -# """Test simupdate.""" -# -# def test_gbm_simupdate(self): -# """Test simulation update of the GBM model.""" -# -# mean, sigma = 1.5, .2 -# param = GBMparam(mean, sigma) -# gbm = GBM(param) -# gbm.ndiscr, gbm.nsub = 2, 2 -# nvars, nsim = 1, 2 -# size = (nsim, nvars) -# state = np.ones(size) -# error = np.zeros(size) -# -# new_state = gbm.update(state, error) -# loc = state * (mean - sigma**2/2) -# scale = np.ones((nsim, nvars, nvars)) * sigma -# delta = 1 / gbm.nsub / gbm.ndiscr -# new_state_compute = loc * delta + (scale * error).sum(1) * delta**.5 -# -# self.assertEqual(new_state.shape, size) -# npt.assert_array_equal(new_state, new_state_compute) -# -# def test_vasicek_simupdate(self): -# """Test simulation update of the Vasicek model.""" -# -# mean, kappa, eta = 1.5, 1, .2 -# param = VasicekParam(mean, kappa, eta) -# vasicek = Vasicek(param) -# vasicek.ndiscr, vasicek.nsub = 2, 2 -# nvars, nsim = 1, 2 -# size = (nsim, nvars) -# state = np.ones(size) -# error = np.zeros(size) -# -# new_state = vasicek.update(state, error) -# loc = kappa * (mean - state) -# scale = np.ones((nsim, nvars, nvars)) * eta -# delta = 1 / vasicek.nsub / vasicek.ndiscr -# new_state_compute = loc * delta + (scale * error).sum(1) * delta**.5 -# -# self.assertEqual(new_state.shape, size) -# npt.assert_array_equal(new_state, new_state_compute) -# -# def test_cir_simupdate(self): -# """Test simulation update of the CIR model.""" -# -# mean, kappa, eta = 1.5, 1, .2 -# param = CIRparam(mean, kappa, eta) -# cir = CIR(param) -# cir.ndiscr, cir.nsub = 2, 2 -# nvars, nsim = 1, 2 -# size = (nsim, nvars) -# state_val = 4 -# state = np.ones(size) * state_val -# error = np.zeros(size) -# -# new_state = cir.update(state, error) -# loc = kappa * (mean - state) -# scale = np.ones((nsim, nvars, nvars)) * eta * state**.5 -# delta = 1 / cir.nsub / cir.ndiscr -# new_state_compute = loc * delta + (scale * error).sum(1) * delta**.5 -# -# self.assertEqual(new_state.shape, size) -# npt.assert_array_equal(new_state, new_state_compute) -# -# def test_heston_simupdate(self): -# """Test simulation update of the Heston model.""" -# -# riskfree, lmbd, mean_v, kappa, eta, rho = 0., .01, .2, 1.5, .2**.5, -.5 -# param = HestonParam(riskfree=riskfree, lmbd=lmbd, -# mean_v=mean_v, kappa=kappa, -# eta=eta, rho=rho) -# heston = Heston(param) -# heston.ndiscr, heston.nsub = 2, 2 -# nvars, nsim = 2, 3 -# size = (nsim, nvars) -# state = np.ones(size) -# error = np.vstack([np.zeros(nsim), np.ones(nsim)]).T -# -# new_state = heston.update(state, error) -# drift_r = riskfree + state[:, 1]**2 * (lmbd - .5) -# drift_v = kappa * (mean_v - state[:, 1]) -# loc = np.vstack([drift_r, drift_v]).T -# -# var = np.array([[1, eta*rho], [eta*rho, eta**2]]) -# var = ((np.ones((nsim, nvars, nvars)) * var).T * state[:, 1]).T -# scale = np.linalg.cholesky(var) -# -# delta = 1 / heston.nsub / heston.ndiscr -# new_state_compute = loc * delta -# for i in range(nsim): -# new_state_compute[i] += (scale[i] * error[i]).sum(1) * delta**.5 -# -# self.assertEqual(new_state.shape, size) -# npt.assert_almost_equal(new_state, new_state_compute) -# -# def test_ct_simupdate(self): -# """Test simulation update of the Central Tendency model.""" -# -# riskfree = .01 -# lmbd = .01 -# mean_v = .5 -# kappa_s = 1.5 -# kappa_y = .5 -# eta_s = .1 -# eta_y = .01 -# rho = -.5 -# param = CentTendParam(riskfree=riskfree, lmbd=lmbd, -# mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, -# eta_s=eta_s, eta_y=eta_y, rho=rho) -# centend = CentTend(param) -# centend.ndiscr, centend.nsub = 2, 2 -# nvars, nsim = 3, 3 -# size = (nsim, nvars) -# state = np.ones(size) -# error = np.vstack([np.zeros(nsim), np.ones(nsim), np.ones(nsim)]).T -# -# new_state = centend.update(state, error) -# drift_r = riskfree + state[:, 1]**2 * (lmbd - .5) -# drift_s = kappa_s * (state[:, 2] - state[:, 1]) -# drift_v = kappa_y * (mean_v - state[:, 2]) -# loc = np.vstack([drift_r, drift_s, drift_v]).T -# -# var_s = np.zeros((3, 3)) -# var_s[:2, :2] = np.array([[1, eta_s*rho], [eta_s*rho, eta_s**2]]) -# var_v = np.zeros((3, 3)) -# var_v[2, 2] = eta_y**2 -# var = ((np.ones((nsim, nvars, nvars)) * var_s).T * state[:, 1]).T \ -# + ((np.ones((nsim, nvars, nvars)) * var_v).T * state[:, 2]).T -# scale = np.linalg.cholesky(var) -# -# delta = 1 / centend.nsub / centend.ndiscr -# new_state_compute = loc * delta -# for i in range(nsim): -# new_state_compute[i] += (scale[i] * error[i]).sum(1) * delta**.5 -# -# self.assertEqual(new_state.shape, size) -# npt.assert_almost_equal(new_state, new_state_compute) -# -# -# class SimulationTestCase(ut.TestCase): -# """Test simulation.""" -# -# def test_gbm_simulation(self): -# """Test simulation of the GBM model.""" -# -# nvars = 1 -# mean, sigma = 1.5, .2 -# param = GBMparam(mean, sigma) -# gbm = GBM(param) -# start, nperiods, nsub, ndiscr, nsim = 1, 5, 2, 3, 4 -# nobs = nperiods * nsub -# -# paths_cy = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0) -# paths = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0, -# new_innov=False, cython=False) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# self.assertEqual(paths_cy.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths, paths_cy) -# -# nsim = 1 -# paths_cy = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0) -# paths = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0, new_innov=False, -# cython=False) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# self.assertEqual(paths_cy.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths, paths_cy) -# -# paths_new = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0, new_innov=False) -# -# npt.assert_array_equal(paths_cy, paths_new) -# -# paths = gbm.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# -# fun = lambda: gbm.simulate([1, 1], nsub, ndiscr, -# nobs, nsim, diff=0) -# self.assertRaises(ValueError, fun) -# -# def test_vasicek_simulation(self): -# """Test simulation of the Vasicek model.""" -# -# nvars = 1 -# mean, kappa, eta = 1.5, .1, .2 -# param = VasicekParam(mean, kappa, eta) -# vasicek = Vasicek(param) -# start, nperiods, nsub, ndiscr, nsim = 1, 5, 2, 3, 4 -# nobs = nperiods * nsub -# -# paths_cy = vasicek.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim) -# paths = vasicek.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, -# cython=False, new_innov=False) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# self.assertEqual(paths_cy.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths, paths_cy) -# -# paths_new = vasicek.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, new_innov=False) -# -# npt.assert_array_almost_equal(paths_new, paths_cy) -# -# fun = lambda: vasicek.simulate([1, 1], nsub=nsub, -# ndiscr=ndiscr, nobs=nobs, nsim=nsim, -# diff=0) -# -# self.assertRaises(ValueError, fun) -# -# def test_cir_simulation(self): -# """Test simulation of the CIR model.""" -# -# nvars = 1 -# mean, kappa, eta = 1.5, .1, .2 -# param = CIRparam(mean, kappa, eta) -# cir = CIR(param) -# start, nperiods, nsub, ndiscr, nsim = 1, 5, 2, 3, 4 -# nobs = nperiods * nsub -# -# paths_cy = cir.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim) -# paths = cir.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, -# new_innov=False, cython=False) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# self.assertEqual(paths_cy.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths_cy, paths) -# -# paths_new = cir.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, new_innov=False) -# -# npt.assert_array_almost_equal(paths_new, paths_cy) -# -# fun = lambda: cir.simulate([1, 1], nsub, ndiscr, -# nobs, nsim, diff=0) -# -# self.assertRaises(ValueError, fun) -# -# def test_heston_simulation(self): -# """Test simulation of the Heston model.""" -# -# nvars = 2 -# riskfree, lmbd, mean_v, kappa, eta, rho = 0., .25, .2, 1.5, .2**.5, -.5 -# param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, -# kappa=kappa, eta=eta, rho=rho) -# heston = Heston(param) -# -# start, nperiods, nsub, ndiscr, nsim = [1, mean_v], 5, 10, 10, 4 -# nobs = nperiods * nsub -# -# paths_cy = heston.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0) -# paths = heston.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0, -# new_innov=False, cython=False) -# -# self.assertEqual(heston.get_start(), start) -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# self.assertEqual(paths_cy.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths_cy, paths) -# -# paths_cy = heston.simulate(nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0) -# paths = heston.simulate(nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0, -# new_innov=False, cython=False) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# self.assertEqual(paths_cy.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths_cy, paths) -# -# paths_new = heston.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, -# diff=0, new_innov=False) -# -# npt.assert_array_equal(paths_cy, paths_new) -# -# paths = heston.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# -# fun = lambda: heston.simulate(0, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0) -# -# self.assertRaises(ValueError, fun) -# -# def test_ct_simulation(self): -# """Test simulation of the Central Tendency model.""" -# -# nvars = 3 -# riskfree = .01 -# lmbd = .01 -# mean_v = .5 -# kappa_s = 1.5 -# kappa_y = .5 -# eta_s = .1 -# eta_y = .01 -# rho = -.5 -# param = CentTendParam(riskfree=riskfree, lmbd=lmbd, -# mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, -# eta_s=eta_s, eta_y=eta_y, rho=rho) -# centtend = CentTend(param) -# start = [1, mean_v, mean_v] -# nperiods, nsub, ndiscr, nsim = 5, 2, 3, 4 -# nobs = nperiods * nsub -# -# paths_cy = centtend.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0) -# paths = centtend.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0, -# new_innov=False, cython=False) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# self.assertEqual(paths_cy.shape, (nobs, 2*nsim, nvars)) -# npt.assert_array_almost_equal(paths_cy, paths) -# -# paths_new = centtend.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, -# diff=0, new_innov=False) -# -# npt.assert_array_equal(paths_cy, paths_new) -# -# paths = centtend.simulate(start, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim) -# -# self.assertEqual(paths.shape, (nobs, 2*nsim, nvars)) -# -# fun = lambda: centtend.simulate(0, nsub=nsub, ndiscr=ndiscr, -# nobs=nobs, nsim=nsim, diff=0) -# -# self.assertRaises(ValueError, fun) -# -# -# class RealizedSimTestCase(ut.TestCase): -# """Test Realized data simulation.""" -# -# def test_gbm_sim_realized(self): -# """Test simulation of realized values of the GBM model.""" -# -# mean, sigma = 1.5, .2 -# param = GBMparam(mean, sigma) -# gbm = GBM(param) -# start, nperiods, nsub, ndiscr, nsim = 1, 5, 80, 3, 4 -# aggh = 2 -# returns, rvol = gbm.sim_realized(start, nsub=nsub, aggh=aggh, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, diff=0) -# -# self.assertEqual(returns.shape, (nperiods-aggh+1, )) -# self.assertEqual(rvol.shape, (nperiods-aggh+1, )) -# -# data = gbm.sim_realized(start, nsub=nsub, aggh=aggh, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, diff=0, new_innov=False) -# returns_new, rvol_new = data -# -# npt.assert_array_equal(returns, returns_new) -# npt.assert_array_equal(rvol, rvol_new) -# -# def test_vasicek_sim_realized(self): -# """Test simulation of realized values of the Vasicek model.""" -# -# mean, kappa, eta = 1.5, .1, .2 -# param = VasicekParam(mean, kappa, eta) -# vasicek = Vasicek(param) -# start, nperiods, nsub, ndiscr, nsim = 1, 5, 2, 3, 4 -# aggh = 2 -# returns, rvol = vasicek.sim_realized(start, nsub=nsub, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, aggh=aggh, diff=0) -# -# self.assertEqual(returns.shape, (nperiods-aggh+1, )) -# self.assertEqual(rvol.shape, (nperiods-aggh+1, )) -# -# data = vasicek.sim_realized(start, nsub=nsub, aggh=aggh, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, diff=0, new_innov=False) -# returns_new, rvol_new = data -# -# npt.assert_array_equal(returns, returns_new) -# npt.assert_array_equal(rvol, rvol_new) -# -# def test_cir_sim_realized(self): -# """Test simulation of realized values of the CIR model.""" -# -# mean, kappa, eta = 1.5, .1, .2 -# param = CIRparam(mean, kappa, eta) -# cir = CIR(param) -# start, nperiods, nsub, ndiscr, nsim = 1, 5, 2, 3, 4 -# aggh = 2 -# returns, rvol = cir.sim_realized(start, nsub=nsub, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, aggh=aggh, diff=0) -# -# self.assertEqual(returns.shape, (nperiods-aggh+1, )) -# self.assertEqual(rvol.shape, (nperiods-aggh+1, )) -# -# data = cir.sim_realized(start, nsub=nsub, aggh=aggh, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, diff=0, new_innov=False) -# returns_new, rvol_new = data -# -# npt.assert_array_equal(returns, returns_new) -# npt.assert_array_equal(rvol, rvol_new) -# -# def test_heston_sim_realized(self): -# """Test simulation of realized values of the Heston model.""" -# -# riskfree, lmbd, mean_v, kappa, eta, rho = 0., .01, .2, 1.5, .2**.5, -.5 -# param = HestonParam(riskfree=riskfree, lmbd=lmbd, -# mean_v=mean_v, kappa=kappa, -# eta=eta, rho=rho) -# heston = Heston(param) -# start, nperiods, nsub, ndiscr, nsim = [1, mean_v], 5, 2, 3, 4 -# -# self.assertEqual(heston.get_start(), start) -# -# aggh = 2 -# returns, rvol = heston.sim_realized(start, nsub=nsub, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, aggh=aggh, diff=0) -# -# self.assertEqual(returns.shape, (nperiods-aggh+1, )) -# self.assertEqual(rvol.shape, (nperiods-aggh+1, )) -# -# data = heston.sim_realized(start, nsub=nsub, aggh=aggh, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, diff=0, new_innov=False) -# returns_new, rvol_new = data -# -# npt.assert_array_equal(returns, returns_new) -# npt.assert_array_equal(rvol, rvol_new) -# -# def test_heston_sim_realized_pq(self): -# """Test simulation of realized data of Heston model under P and Q.""" -# -# riskfree = .0 -# lmbd = 1.5 -# lmbd_v = .5 -# mean_v = .5 -# kappa = .1 -# eta = .02**.5 -# rho = -.9 -# # 2 * self.kappa * self.mean_v - self.eta**2 > 0 -# param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, -# kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) -# heston = Heston(param) -# -# start_p = [1, param.mean_v] -# -# self.assertEqual(heston.get_start(), start_p) -# -# param.convert_to_q() -# start_q = [1, mean_v * kappa / (kappa - lmbd_v * eta)] -# -# self.assertEqual(heston.get_start(), start_q) -# -# param = HestonParam(riskfree=riskfree, lmbd=lmbd, mean_v=mean_v, -# kappa=kappa, eta=eta, rho=rho, lmbd_v=lmbd_v) -# heston = Heston(param) -# -# aggh = [1, 2] -# nperiods, nsub, ndiscr, nsim = 5, 2, 3, 4 -# data = heston.sim_realized_pq(nsub=nsub, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, aggh=aggh, diff=0) -# -# (ret_p, rvar_p), (ret_q, rvar_q) = data -# -# self.assertEqual(ret_p.shape, (nperiods-aggh[0]+1, )) -# self.assertEqual(ret_q.shape, (nperiods-aggh[1]+1, )) -# self.assertEqual(rvar_p.shape, (nperiods-aggh[0]+1, )) -# self.assertEqual(rvar_q.shape, (nperiods-aggh[1]+1, )) -# -# def test_ct_sim_realized(self): -# """Test simulation of realized values of the Central Tendency model.""" -# -# riskfree = .01 -# lmbd = .01 -# mean_v = .5 -# kappa_s = 1.5 -# kappa_y = .5 -# eta_s = .1 -# eta_y = .01 -# rho = -.5 -# param = CentTendParam(riskfree=riskfree, lmbd=lmbd, -# mean_v=mean_v, kappa_s=kappa_s, kappa_y=kappa_y, -# eta_s=eta_s, eta_y=eta_y, rho=rho) -# centtend = CentTend(param) -# start = [1, mean_v, mean_v] -# nperiods, nsub, ndiscr, nsim = 5, 2, 3, 4 -# aggh = 2 -# returns, rvol = centtend.sim_realized(start, nsub=nsub, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, aggh=aggh, diff=0) -# -# self.assertEqual(returns.shape, (nperiods-aggh+1, )) -# self.assertEqual(rvol.shape, (nperiods-aggh+1, )) -# -# data = centtend.sim_realized(start, nsub=nsub, aggh=aggh, -# ndiscr=ndiscr, nperiods=nperiods, -# nsim=nsim, diff=0, new_innov=False) -# returns_new, rvol_new = data -# -# npt.assert_array_equal(returns, returns_new) -# npt.assert_array_equal(rvol, rvol_new) -# -# -# if __name__ == '__main__': -# ut.main() +"""Test suite for diffusion simulations.""" diff --git a/uv.lock b/uv.lock index 6829cd8..948aad5 100644 --- a/uv.lock +++ b/uv.lock @@ -21,7 +21,9 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "prek" }, { name = "pytest" }, + { name = "pytest-cov" }, ] [package.metadata] @@ -35,7 +37,11 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=9.0.3" }] +dev = [ + { name = "prek", specifier = ">=0.4.11" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, +] [[package]] name = "colorama" @@ -79,6 +85,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, ] +[[package]] +name = "coverage" +version = "7.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/5f/aed265fd7a3551a394f36dfe41868aee709b7f95db4052205b4ad1563ac3/coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", size = 221650, upload-time = "2026-07-15T18:55:14.527Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2c/222ba12a545189017120f8eddfc1a0bd4616b47d5d4a8d99421edb2fe4c6/coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", size = 221988, upload-time = "2026-07-15T18:55:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/aa/38/304b5877ab46e6c290b4292cfcf3fe28245f0e5597cad7f6acc91fc7e0a4/coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", size = 253029, upload-time = "2026-07-15T18:55:18.856Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/821b533b8db9e44cf1d8a97bd525149ced40dde1d0093da02cb78e715244/coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", size = 255536, upload-time = "2026-07-15T18:55:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f2/7aa06604c389d32ea7f0a6a988359a7eafc3cd3f8e7bc2e88cd2fdf0b877/coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", size = 256881, upload-time = "2026-07-15T18:55:23.125Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4f/1ef342339c7916d0096bc5888cc0f653882cc7bc8f897d5cb89143287c9b/coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", size = 259196, upload-time = "2026-07-15T18:55:25.099Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f4/7ed055d7a9c5ec13b161773a115a5ccc6b0081d568c31fad830806306cc7/coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", size = 253036, upload-time = "2026-07-15T18:55:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/14/79/ea82cca18c242a3a38b6c017da39726aa62dcb64aa635abf79b92009975c/coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", size = 254887, upload-time = "2026-07-15T18:55:29.084Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ba/a136db3c0d9562b00e10b72540dbf3a33cd3bc5b95060c9308e247494623/coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", size = 252852, upload-time = "2026-07-15T18:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/ea334246b16b7d059953fad6fdefa11e33c68efbd3fe37b1098120a1fac2/coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", size = 257128, upload-time = "2026-07-15T18:55:33.163Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c3/074fb66d46d607855f710876b117cbda562c5ab08363528e78820449f937/coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", size = 252668, upload-time = "2026-07-15T18:55:35.063Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c1/f620850ada9b36435921c9a3a8057013422b1d964eb4bf37fe138724d192/coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", size = 254325, upload-time = "2026-07-15T18:55:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/cc/31/a729ca3689404493af82ef8e6ff70bd88bdda8da89aeef6ca9b387aeb2b4/coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", size = 223844, upload-time = "2026-07-15T18:55:39.078Z" }, + { url = "https://files.pythonhosted.org/packages/c6/83/5d809dc808fb1698c671f3e372259bb9158e64b7ea526fc6ab7de64de9fe/coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", size = 224331, upload-time = "2026-07-15T18:55:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/4e/35e488548e952795829e129995c4174df33bf432b591d1aa42c8d9e4e7ad/coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", size = 223760, upload-time = "2026-07-15T18:55:43.518Z" }, + { url = "https://files.pythonhosted.org/packages/ed/49/dd2c86cd6374038f6e415fb5bfb86db5218553209c081384a020369dee79/coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", size = 222384, upload-time = "2026-07-15T18:55:45.569Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/173ff17a1c0808e5a438f549f6f145d5ac7528f2791310b63523e3200ac7/coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", size = 222647, upload-time = "2026-07-15T18:55:47.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/f8/b8cba872162356fb44ac79c10309d987206a4461e32072fc29228dad7331/coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", size = 264013, upload-time = "2026-07-15T18:55:49.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/67/a807a7586d0b8cae485308ddd55756f0806c92f8e0b411bacbf23c48edf3/coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a", size = 266135, upload-time = "2026-07-15T18:55:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/cd78771dc985f7e4ebdcc82b1a96d9a932af9e806f01f2f91a89f4c72e80/coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", size = 268555, upload-time = "2026-07-15T18:55:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/18/3e/10134cf81275188c58568f324fc74aedff32c63ca4d5bbc513a91944a6f0/coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", size = 269674, upload-time = "2026-07-15T18:55:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/75/4a/771b77de446cba985dc414bbc5844bd21604da05dbc044286df8318a48a7/coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", size = 263101, upload-time = "2026-07-15T18:55:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b5/70a7011da15f4071943361183aefa27847f3e3aec4fd335f1cb3d3a622b1/coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", size = 266007, upload-time = "2026-07-15T18:56:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/f9547e804ce7ad49646ffeffac26699510efbe6c0f751b66fdc960c4e825/coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", size = 263611, upload-time = "2026-07-15T18:56:02.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/59/f576a396659c0efd351f5c1544f67c3560e89c7761cabf7f65e412beeda5/coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", size = 267344, upload-time = "2026-07-15T18:56:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5d/c2e4fce3579c0cb635024293f1a32bbe26df101b3e3a69f22243d1352b6c/coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", size = 262456, upload-time = "2026-07-15T18:56:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/956287d69436b66094bc4b57ac2da71e43bfd2a5524e958900b9f582fcf8/coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", size = 264771, upload-time = "2026-07-15T18:56:08.795Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5a/6f979530c2734c575de77cf58f5f28d51f7123a94b5030fd9156fe5f363c/coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", size = 224151, upload-time = "2026-07-15T18:56:10.856Z" }, + { url = "https://files.pythonhosted.org/packages/54/7e/27f6b2a74d484742f4017553e710b01e396b23d809df3e95ca0bb9a2824b/coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", size = 224981, upload-time = "2026-07-15T18:56:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/b1/48/284863423aa474240f6842bd00d680da22f4e6ea2e466618ef7c9c9e69a9/coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", size = 224294, upload-time = "2026-07-15T18:56:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -343,6 +388,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "prek" +version = "0.4.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1a/73b6dae5ce7e997cb35a69bfe1d25a798e85fa3d2eabf95324563f461b30/prek-0.4.11.tar.gz", hash = "sha256:4a14cb9bbae850605ae3904fbdbb12f0e00c12455efaa2266da8fb8e5c0350d7", size = 516254, upload-time = "2026-07-24T17:05:35.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/d7/a00b2de492a80e99b1698e72c1d196ac3ed544dc7ee0ada261ac066e78e0/prek-0.4.11-py3-none-linux_armv6l.whl", hash = "sha256:3830cb7cc47e837888b8b464ecb21355a69235cfacef0fd89101e17f09345d63", size = 5770511, upload-time = "2026-07-24T17:05:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1e/f97c74defcd5d5645888cb99fcdd9b8b48cc7247cf31e64414d106d56d66/prek-0.4.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:45facadf9c2332b28e6ab2744312ae0275a93ab5a437da8bcc53a8c7260cb4b0", size = 6118049, upload-time = "2026-07-24T17:05:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/d9/92/8367d26421ee6fe6019a63928fb0ed31179cd0d6199879c524f89ef4c95c/prek-0.4.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2f8a194b1d00d24dff8baff691c99e2668339da2da3482b4ee99c1a0f2409378", size = 5601478, upload-time = "2026-07-24T17:05:15.81Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6f/7617c9b87afaadede4167720aae7ef12d7e51167db903bc8fbac0cadef7b/prek-0.4.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:21d93e6d76bf3d7a9bb70c6ac86ef372adaee50068c45749e6b9e1c2ac4ec939", size = 5932071, upload-time = "2026-07-24T17:05:17.217Z" }, + { url = "https://files.pythonhosted.org/packages/ef/41/6796a4011b04212333259064aa885a71d03d9581ba9bff52db3ce58d1f06/prek-0.4.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7fb07cde2d2156efa6980122b3f13dc88f20c84b933c6205675d0f1e7be2cde8", size = 5677617, upload-time = "2026-07-24T17:05:18.658Z" }, + { url = "https://files.pythonhosted.org/packages/03/5f/3e339901f8460b6073313619b4fd9bf4135e48430695c53640b83ace88ea/prek-0.4.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b7f5e446d2aca739bd18b380578e9c78bb4c086299abc3e167d51c47840c56f", size = 6106370, upload-time = "2026-07-24T17:05:20.219Z" }, + { url = "https://files.pythonhosted.org/packages/8e/4e/94e24b5c1910ec15692ecaf33d0d8ef0d02a02a8b5e40d3c976396880cba/prek-0.4.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7059d640e595d098600e2d97af961f46292b4112670edbe632de84f6828389e", size = 6884342, upload-time = "2026-07-24T17:05:21.587Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7c/fc0daa033dcafe74990c00af2da1e16922790b9c1b8da7e8eebf1123838b/prek-0.4.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a4df33998fcac878bce3b2c624e1caeb9609f3d562c640702c1234ed815daf", size = 6331365, upload-time = "2026-07-24T17:05:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/d7/36/49f152b8f539930e9685cff0509695ce4054abcecc22f4c16c4e1e5c23d0/prek-0.4.11-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:866991f387527c5f880ce2cc3ddea9b33cc5b986881f8c7f92524cf0969c1350", size = 5939075, upload-time = "2026-07-24T17:05:24.249Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fe/7b097af9161edae7ddedcc9f5cda4a5f31346a492ce3f92583d96c46f628/prek-0.4.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:247e5d8740e137ebdf24fa96f01a95b2d2f1892e956a1ab00c7b1474a59ebcc0", size = 5799029, upload-time = "2026-07-24T17:05:25.652Z" }, + { url = "https://files.pythonhosted.org/packages/49/63/dc955ff99e1002d3cd375b21467c87046bc41deddfce86d898240757b151/prek-0.4.11-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:603ba9f2fd9d666dddb3ae190a25a5c55091b843cde90ed52e0a1116f50d4062", size = 5651211, upload-time = "2026-07-24T17:05:27.027Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/bf2139ec25eefb5afef43afac7620c5181f2c2661f220598beacb1771207/prek-0.4.11-py3-none-musllinux_1_1_i686.whl", hash = "sha256:f2682ece3c5fc7201106c4fdd84b0587ccea4b8a2ecefa3e94d3074d2841f1df", size = 5954784, upload-time = "2026-07-24T17:05:28.391Z" }, + { url = "https://files.pythonhosted.org/packages/fc/29/3fe5990aee1bd7c4d50a03358ad867c5e912d9510aafb83a359c7347e5fa/prek-0.4.11-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:22721d30394192931fecc80d6fc6dd47e8ddf8db8b9693805aba8ec0f50087ec", size = 6448916, upload-time = "2026-07-24T17:05:29.837Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fb/abddacf43738302242ecd237236c7c80cd7c1d27545e16e803770aee76e9/prek-0.4.11-py3-none-win32.whl", hash = "sha256:8b093e7624522146049e994d5cf283d01d71632b638620b79ae0f96afeaa2624", size = 5483539, upload-time = "2026-07-24T17:05:31.228Z" }, + { url = "https://files.pythonhosted.org/packages/00/1e/c293f7a15cb93963c4be36a02e144b93f43906bc02644a3f04e5708e7453/prek-0.4.11-py3-none-win_amd64.whl", hash = "sha256:5a3d7c80b970b456e5f1bcec8382008ee1ae6a3f324a6b9bb4ff7e666ab0f3c4", size = 5861119, upload-time = "2026-07-24T17:05:32.479Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0c/05fe6eb9d6a54d0e02dfa8cc5ad6f86869bf953419ec15909a32466a28ee/prek-0.4.11-py3-none-win_arm64.whl", hash = "sha256:e7b0df37ce05e45a14a9da39ab104691474d72f139bf4f6c860f754763a322cb", size = 5626386, upload-time = "2026-07-24T17:05:33.813Z" }, +] + [[package]] name = "pygments" version = "2.20.0" @@ -377,6 +446,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0"