hfvol 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hfvol-0.1.0/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ .venv/
6
+ build/
7
+ dist/
8
+ *.egg-info/
9
+ .coverage
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.0] - 2026-11-15
8
+
9
+ First release. Carved out of [srfm-lab](https://github.com/Mattbusel/srfm-lab)
10
+ (`lib/math/volatility_models.py` and `lib/math/hawkes_process.py`) at commit
11
+ `8cbfcb0` (MIT). See `docs/provenance.md`.
12
+
13
+ ### Added
14
+ - sktime transformers `RealizedVariance`, `BipowerVariation`, `JumpVariation`
15
+ (`freq` as bars per period or a calendar alias such as `"D"`; multivariate;
16
+ Panel and hierarchical data through sktime's vectorization) and
17
+ `HawkesIntensity` (fixed parameters or `method="mle"`, causal intensity by
18
+ default, `time_unit` for DatetimeIndex data).
19
+ - sktime forecaster `HARForecaster` (Corsi 2009) with `update`.
20
+ - All five pass `sktime.utils.estimator_checks.check_estimator` on sktime 1.0.0
21
+ and 1.3.0.
22
+ - numpy functions `bipower_variation_series`, `jump_variation_series`,
23
+ `har_forecast_path`, `hawkes_compensator`.
24
+ - Tests: hand-computed values, Barndorff-Nielsen and Shephard consistency and
25
+ jump-robustness checks, HAR OLS against the normal equations and coefficient
26
+ recovery, closed-form and brute-force Hawkes likelihood, intensity and
27
+ compensator checks, MLE recovery and time-rescaling residuals. srfm-lab had no
28
+ dedicated tests for either module.
29
+
30
+ ### Changed (relative to srfm-lab)
31
+ - `annualize` defaults to `False` in every realized-measure function.
32
+ - The Hawkes log-likelihood, intensity path, simulation and residuals run in
33
+ O(n) through the exponential-kernel recursion (srfm-lab was O(n^2) in three
34
+ of them).
35
+ - `fit_hawkes_mle` optimises over `(log mu, log beta, logit n)` so every
36
+ candidate is stationary, scales its starts to the observed event rate and
37
+ takes a seed (`rng`).
38
+ - `hawkes_intensity_path` takes `include_current` (default `True`, the srfm-lab
39
+ behaviour); `False` gives the predictable intensity.
40
+ - `hawkes_residual_analysis` includes the first compensator increment
41
+ `Lambda(t_1)`.
42
+ - `HARParams` and `HawkesParams` are frozen dataclasses; `har_features` is
43
+ vectorised.
44
+ - Multivariate Hawkes, GARCH, EWMA, VIX, surface and rough-volatility code from
45
+ the source files is not included.
46
+
47
+ ### Fixed (relative to srfm-lab)
48
+ - Events sharing a time stamp excited each other in the likelihood
49
+ (`exp(0) = 1`), which makes it unbounded on binned data: the MLE ran off to
50
+ `alpha, beta -> inf`. Only strictly earlier events now count, and
51
+ `hawkes_intensity_path` / `hawkes_compensator` count every tied event.
52
+ - `fit_hawkes_mle` returned the default `HawkesParams()` with log-likelihood
53
+ `-inf` whenever no Nelder-Mead run reported `success`, even when the runs had
54
+ found good parameters. The best finite result is now kept.
55
+ - `fit_hawkes_mle` and `branching_ratio` were not reproducible (unseeded
56
+ generator with no way to pass one).
57
+ - `hawkes_simulate` used `assert` for the stationarity check, which is skipped
58
+ under `python -O`; it now raises `ValueError`.
hfvol-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Busel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hfvol-0.1.0/NOTICE ADDED
@@ -0,0 +1,12 @@
1
+ hfvol
2
+ Copyright (c) 2026 Matthew Busel
3
+
4
+ This package was carved out of srfm-lab (https://github.com/Mattbusel/srfm-lab)
5
+ at commit 8cbfcb0551d44e3ed1c20bdae9dc805d61521c62. srfm-lab is released under
6
+ the MIT License, copyright (c) 2026 Matthew Busel.
7
+
8
+ src/hfvol/realized.py and src/hfvol/har.py are derived from
9
+ lib/math/volatility_models.py; src/hfvol/hawkes.py is derived from
10
+ lib/math/hawkes_process.py. docs/provenance.md maps every function to its
11
+ origin and lists what changed. The sktime estimators (transformers.py,
12
+ forecasting.py) and the tests are new.
hfvol-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.5
2
+ Name: hfvol
3
+ Version: 0.1.0
4
+ Summary: sktime-compatible realized variance, bipower and jump variation, Hawkes intensity transformers and a HAR-RV forecaster, on plain numpy functions. Carved out of srfm-lab.
5
+ Project-URL: Homepage, https://github.com/Mattbusel/hfvol
6
+ Project-URL: Repository, https://github.com/Mattbusel/hfvol
7
+ Project-URL: Documentation, https://github.com/Mattbusel/hfvol/tree/main/docs
8
+ Project-URL: Changelog, https://github.com/Mattbusel/hfvol/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/Mattbusel/hfvol/issues
10
+ Project-URL: Upstream (srfm-lab), https://github.com/Mattbusel/srfm-lab
11
+ Author: Matthew Busel
12
+ License-Expression: MIT
13
+ License-File: LICENSE
14
+ License-File: NOTICE
15
+ Keywords: bipower-variation,har-rv,hawkes-process,high-frequency,jump-variation,market-microstructure,quantitative-finance,realized-variance,realized-volatility,sktime,time-series,volatility-forecasting
16
+ Classifier: Development Status :: 4 - Beta
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Intended Audience :: Financial and Insurance Industry
19
+ Classifier: Intended Audience :: Science/Research
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Topic :: Office/Business :: Financial :: Investment
28
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
29
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
30
+ Classifier: Typing :: Typed
31
+ Requires-Python: >=3.10
32
+ Requires-Dist: numpy>=1.22
33
+ Provides-Extra: scipy
34
+ Requires-Dist: scipy>=1.8; extra == 'scipy'
35
+ Provides-Extra: sktime
36
+ Requires-Dist: scipy>=1.8; extra == 'sktime'
37
+ Requires-Dist: sktime>=1.0; extra == 'sktime'
38
+ Provides-Extra: test
39
+ Requires-Dist: pytest>=7; extra == 'test'
40
+ Requires-Dist: scipy>=1.8; extra == 'test'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # hfvol
44
+
45
+ Realized variance, bipower variation, jump variation, Hawkes intensity and the
46
+ HAR-RV forecaster, as sktime estimators on top of plain numpy functions.
47
+
48
+ ```
49
+ pip install hfvol # numpy functions only
50
+ pip install "hfvol[sktime]" # plus the sktime transformers and forecaster
51
+ ```
52
+
53
+ ## Why
54
+
55
+ sktime has a Hurst-exponent transformer but no realized measures and no Hawkes
56
+ process. The main Python Hawkes library (`tick`) needs a C++ build. hfvol fills
57
+ both gaps with small numpy code that passes sktime's own
58
+ `check_estimator` suite.
59
+
60
+ ## sktime
61
+
62
+ ```python
63
+ import numpy as np, pandas as pd
64
+ from hfvol import RealizedVariance, BipowerVariation, JumpVariation, HARForecaster
65
+
66
+ # 5-minute returns, 78 bars per session, DatetimeIndex
67
+ idx = pd.date_range("2024-01-02 09:35", periods=78 * 250, freq="5min")
68
+ r = pd.Series(np.random.default_rng(0).normal(0, 1e-3, len(idx)), index=idx)
69
+
70
+ rv = RealizedVariance(freq=78).fit_transform(r) # one value per session
71
+ bv = BipowerVariation(freq=78).fit_transform(r) # jump-robust
72
+ jv = JumpVariation(freq=78).fit_transform(r) # max(RV - BV, 0)
73
+
74
+ f = HARForecaster().fit(rv.reset_index(drop=True))
75
+ f.predict(fh=[1, 5, 22])
76
+ ```
77
+
78
+ `freq` is either an integer number of bars per period or a pandas offset alias
79
+ (`"D"`, `"W"`, ...) that groups a DatetimeIndex by calendar period. Multivariate
80
+ frames give one column per asset; Panel and hierarchical data work through
81
+ sktime's usual vectorization.
82
+
83
+ ```python
84
+ from hfvol import HawkesIntensity
85
+
86
+ # trade counts per second; the intensity at each second uses only earlier trades
87
+ lam = HawkesIntensity(method="mle", time_unit="1s", random_state=0).fit_transform(counts)
88
+ ```
89
+
90
+ ## numpy
91
+
92
+ ```python
93
+ from hfvol import (
94
+ realized_variance,
95
+ bipower_variation,
96
+ jump_variation,
97
+ fit_har,
98
+ har_forecast,
99
+ HawkesParams,
100
+ hawkes_simulate,
101
+ fit_hawkes_mle,
102
+ hawkes_residual_analysis,
103
+ )
104
+
105
+ events = hawkes_simulate(HawkesParams(mu=0.5, alpha=0.8, beta=1.6), T=5000, rng=1)
106
+ params, ll = fit_hawkes_mle(events, T=5000, rng=0)
107
+ params.branching_ratio # about 0.5
108
+ hawkes_residual_analysis(events, 5000, params)["well_fit"]
109
+ ```
110
+
111
+ ## More
112
+
113
+ - [API reference](api.md)
114
+ - [Formulas and references](methods.md)
115
+ - [Where this came from](provenance.md)
116
+ - [Ecosystem plan (sktime, OpenBB)](ecosystem.md)
117
+ - Examples: `examples/`
118
+
119
+ MIT licensed. Carved out of [srfm-lab](https://github.com/Mattbusel/srfm-lab).
@@ -0,0 +1,87 @@
1
+ # API reference
2
+
3
+ ## sktime estimators (`pip install "hfvol[sktime]"`)
4
+
5
+ All five pass `sktime.utils.estimator_checks.check_estimator` (tested on sktime
6
+ 1.0.0 and 1.3.0; `tests/test_sktime.py` runs it in CI).
7
+
8
+ | Estimator | sktime type | Input | Output |
9
+ |---|---|---|---|
10
+ | `RealizedVariance(freq="D", annualize=False, trading_days=252)` | transformer, Series to Series | intraday returns | one row per period: `sum r^2` |
11
+ | `BipowerVariation(freq="D", annualize=False, trading_days=252)` | transformer, Series to Series | intraday returns | `(pi/2) sum abs(r_i) abs(r_{i-1})` |
12
+ | `JumpVariation(freq="D", annualize=False, trading_days=252, relative=False)` | transformer, Series to Series | intraday returns | `max(RV - BV, 0)`, or that divided by RV if `relative` |
13
+ | `HawkesIntensity(mu=0.5, alpha=0.6, beta=1.0, method="fixed", n_restarts=10, time_unit=None, causal=True, random_state=None)` | transformer, Series to Series (same index) | event counts | conditional intensity |
14
+ | `HARForecaster(window_w=5, window_m=22)` | forecaster, univariate | per-period realized measure | forecasts at `fh` (out of sample) |
15
+
16
+ ### Realized measures
17
+
18
+ - `freq` int: bars per period. Rows are labelled by the last bar of each period.
19
+ A trailing incomplete period is dropped.
20
+ - `freq` str: pandas offset alias. Needs a DatetimeIndex (rows labelled by the
21
+ period start timestamp; time zones are dropped and wall-clock dates kept) or a
22
+ PeriodIndex (rows labelled by the period).
23
+ - Multivariate input: one output column per input column.
24
+ - No missing values (`capability:missing_values = False`). Drop or fill first.
25
+
26
+ ### HawkesIntensity
27
+
28
+ - Input values are event counts per time stamp, rounded to the nearest integer,
29
+ negatives counted as zero.
30
+ - Clock: DatetimeIndex in seconds unless `time_unit` is given ("1min", "1D", ...);
31
+ PeriodIndex in periods; integer or float index in its own values. Time starts
32
+ at 0 at the first label.
33
+ - Events in the same time stamp count as simultaneous and do not excite each
34
+ other, so a clock coarser than `1 / beta` hides the fastest clustering. Pick
35
+ bins well below the decay time you expect.
36
+ - `method="mle"` fits each series separately in `fit`. Fewer than 3 events
37
+ falls back to a Poisson rate (`alpha_ = 0`).
38
+ - Fitted attributes: `mu_`, `alpha_`, `beta_`, `branching_ratio_`,
39
+ `log_likelihood_`, `n_events_`.
40
+ - `transform` uses the events of the series it is given (not those seen in
41
+ `fit`) with the fitted parameters.
42
+ - `causal=True` (default) excludes events at the current stamp, so the output is
43
+ usable as a feature without look-ahead. `causal=False` includes them.
44
+
45
+ The original plan named the switch `fit="mle"`. An sktime estimator cannot have
46
+ a parameter called `fit` (it would hide the `fit` method), so it is `method`.
47
+
48
+ ### HARForecaster
49
+
50
+ - Needs at least `window_m + 4` observations.
51
+ - `fit` stores the series; `update` appends and refits (`update_params=True`).
52
+ - Fitted attributes: `params_` (`HARParams`), `coef_` (`const`, `rv_d`, `rv_w`, `rv_m`).
53
+ - No prediction intervals and no in-sample predictions in 0.1.0.
54
+
55
+ ## numpy functions (`pip install hfvol`)
56
+
57
+ ### `hfvol.realized`
58
+
59
+ | Function | Returns |
60
+ |---|---|
61
+ | `realized_variance(r, annualize=False, trading_days=252)` | float |
62
+ | `realized_variance_series(r, freq=78, ...)` | array, one value per `freq` bars |
63
+ | `bipower_variation(r, ...)` / `bipower_variation_series(r, freq=78, ...)` | float / array |
64
+ | `jump_variation(r, ...)` | `(continuous, jump)` |
65
+ | `jump_variation_series(r, freq=78, ...)` | array `(n_periods, 2)` |
66
+
67
+ ### `hfvol.har`
68
+
69
+ | Function | Returns |
70
+ |---|---|
71
+ | `har_features(rv, window_w=5, window_m=22)` | design matrix `(n - window_m, 4)` |
72
+ | `fit_har(rv, window_w=5, window_m=22)` | `HARParams(mu, beta_d, beta_w, beta_m)` |
73
+ | `har_forecast_path(params, rv, horizon, ...)` | array of forecasts for steps 1..horizon |
74
+ | `har_forecast(params, rv, horizon=1, ...)` | float, the `horizon`-step forecast |
75
+
76
+ ### `hfvol.hawkes` (fitting and residuals need scipy)
77
+
78
+ | Function | Returns |
79
+ |---|---|
80
+ | `HawkesParams(mu, alpha, beta)` | `.branching_ratio`, `.is_stationary`, `.unconditional_intensity` |
81
+ | `hawkes_simulate(params, T, rng=None)` | event times on `[0, T]` (Ogata thinning) |
82
+ | `hawkes_intensity_path(events, params, times, include_current=True)` | intensity on a grid |
83
+ | `hawkes_compensator(events, params, times)` | integrated intensity on a grid |
84
+ | `hawkes_log_likelihood(events, T, params)` | float |
85
+ | `fit_hawkes_mle(events, T, n_restarts=10, rng=None)` | `(HawkesParams, log_likelihood)` |
86
+ | `branching_ratio(events, T, n_restarts=10, rng=None)` | float in `[0, 1]` |
87
+ | `hawkes_residual_analysis(events, T, params)` | dict: `ks_statistic`, `ks_pvalue`, `mean_residual`, `std_residual`, `well_fit` |
@@ -0,0 +1,31 @@
1
+ # Ecosystem plan
2
+
3
+ hfvol exists so these pieces can be merged where people already look for them.
4
+
5
+ ## 1. sktime: realized-measure transformers (one estimator per PR)
6
+
7
+ sktime has `sktime/transformations/hurst.py` but no realized measures. Planned
8
+ PRs, each one file under `sktime/transformations/` and each passing
9
+ `check_estimator`:
10
+
11
+ 1. `RealizedVariance`
12
+ 2. `BipowerVariation`
13
+ 3. `JumpVariation`
14
+
15
+ The classes in `src/hfvol/transformers.py` follow sktime's transformer extension
16
+ template and need only numpy and pandas, so each PR is one class plus its
17
+ private helpers. Hurst is already in sktime and is not part of this.
18
+
19
+ ## 2. sktime: `HawkesIntensity`
20
+
21
+ Either a PR (its only extra dependency is scipy, which sktime already requires)
22
+ or a third-party estimator listing that points at hfvol. The usual alternative,
23
+ `tick`, needs a C++ build.
24
+
25
+ ## 3. OpenBB (after hfvol is on PyPI)
26
+
27
+ An OpenBB core-extension package that registers a router through the
28
+ `openbb_core_extension` entry point and calls hfvol. Its name must not lead with
29
+ the OpenBB trademark (for example `hfvol-router`, not `openbb-hfvol`).
30
+
31
+ Status at 0.1.0: none of these has been submitted.
@@ -0,0 +1,77 @@
1
+ # hfvol
2
+
3
+ Realized variance, bipower variation, jump variation, Hawkes intensity and the
4
+ HAR-RV forecaster, as sktime estimators on top of plain numpy functions.
5
+
6
+ ```
7
+ pip install hfvol # numpy functions only
8
+ pip install "hfvol[sktime]" # plus the sktime transformers and forecaster
9
+ ```
10
+
11
+ ## Why
12
+
13
+ sktime has a Hurst-exponent transformer but no realized measures and no Hawkes
14
+ process. The main Python Hawkes library (`tick`) needs a C++ build. hfvol fills
15
+ both gaps with small numpy code that passes sktime's own
16
+ `check_estimator` suite.
17
+
18
+ ## sktime
19
+
20
+ ```python
21
+ import numpy as np, pandas as pd
22
+ from hfvol import RealizedVariance, BipowerVariation, JumpVariation, HARForecaster
23
+
24
+ # 5-minute returns, 78 bars per session, DatetimeIndex
25
+ idx = pd.date_range("2024-01-02 09:35", periods=78 * 250, freq="5min")
26
+ r = pd.Series(np.random.default_rng(0).normal(0, 1e-3, len(idx)), index=idx)
27
+
28
+ rv = RealizedVariance(freq=78).fit_transform(r) # one value per session
29
+ bv = BipowerVariation(freq=78).fit_transform(r) # jump-robust
30
+ jv = JumpVariation(freq=78).fit_transform(r) # max(RV - BV, 0)
31
+
32
+ f = HARForecaster().fit(rv.reset_index(drop=True))
33
+ f.predict(fh=[1, 5, 22])
34
+ ```
35
+
36
+ `freq` is either an integer number of bars per period or a pandas offset alias
37
+ (`"D"`, `"W"`, ...) that groups a DatetimeIndex by calendar period. Multivariate
38
+ frames give one column per asset; Panel and hierarchical data work through
39
+ sktime's usual vectorization.
40
+
41
+ ```python
42
+ from hfvol import HawkesIntensity
43
+
44
+ # trade counts per second; the intensity at each second uses only earlier trades
45
+ lam = HawkesIntensity(method="mle", time_unit="1s", random_state=0).fit_transform(counts)
46
+ ```
47
+
48
+ ## numpy
49
+
50
+ ```python
51
+ from hfvol import (
52
+ realized_variance,
53
+ bipower_variation,
54
+ jump_variation,
55
+ fit_har,
56
+ har_forecast,
57
+ HawkesParams,
58
+ hawkes_simulate,
59
+ fit_hawkes_mle,
60
+ hawkes_residual_analysis,
61
+ )
62
+
63
+ events = hawkes_simulate(HawkesParams(mu=0.5, alpha=0.8, beta=1.6), T=5000, rng=1)
64
+ params, ll = fit_hawkes_mle(events, T=5000, rng=0)
65
+ params.branching_ratio # about 0.5
66
+ hawkes_residual_analysis(events, 5000, params)["well_fit"]
67
+ ```
68
+
69
+ ## More
70
+
71
+ - [API reference](api.md)
72
+ - [Formulas and references](methods.md)
73
+ - [Where this came from](provenance.md)
74
+ - [Ecosystem plan (sktime, OpenBB)](ecosystem.md)
75
+ - Examples: `examples/`
76
+
77
+ MIT licensed. Carved out of [srfm-lab](https://github.com/Mattbusel/srfm-lab).
@@ -0,0 +1,59 @@
1
+ # Formulas and references
2
+
3
+ ## Realized measures
4
+
5
+ For intraday returns `r_1, ..., r_n` of one period:
6
+
7
+ - Realized variance: `RV = sum_i r_i^2` (Andersen and Bollerslev 1998).
8
+ - Bipower variation: `BV = mu1^-2 sum_{i=2..n} |r_i| |r_{i-1}|`, with `mu1 = E|Z| = sqrt(2/pi)`
9
+ (Barndorff-Nielsen and Shephard 2004). No `n/(n-1)` small-sample factor.
10
+ - Jump variation: `JV = max(RV - BV, 0)`; relative jump `JV / RV`
11
+ (Barndorff-Nielsen and Shephard 2006).
12
+
13
+ As sampling gets finer, RV converges to integrated variance plus the sum of
14
+ squared jumps, and BV to integrated variance alone. The tests check both on
15
+ simulated data: with `r_i ~ N(0, s2/n)`, mean RV is `s2` and mean BV is
16
+ `s2 (n-1)/n`; adding one jump `J` per day moves RV by `J^2` and BV only by about
17
+ `pi mu1 sqrt(s2/n) J`.
18
+
19
+ ## HAR-RV
20
+
21
+ Corsi (2009):
22
+
23
+ RV_{t+1} = c + b_d RV_t + b_w RV_t^(w) + b_m RV_t^(m) + e_{t+1}
24
+
25
+ with `RV^(w)` and `RV^(m)` the means over the last 5 and 22 periods, estimated
26
+ by OLS. Multi-step forecasts iterate the equation. The tests compare the OLS
27
+ coefficients with the normal equations and recover known coefficients from a
28
+ simulated HAR process.
29
+
30
+ ## Hawkes process (exponential kernel)
31
+
32
+ lambda(t) = mu + sum_{t_i < t} alpha exp(-beta (t - t_i))
33
+
34
+ Branching ratio `n = alpha / beta`; stationary mean rate `mu / (1 - n)`.
35
+
36
+ Log-likelihood on `[0, T]` (Ozaki 1979):
37
+
38
+ LL = sum_i log(mu + alpha R_i) - mu T - (alpha/beta) sum_i (1 - exp(-beta (T - t_i)))
39
+ R_1 = 0, R_i = exp(-beta (t_i - t_{i-1})) (1 + R_{i-1})
40
+
41
+ Compensator: `Lambda(t) = mu t + (alpha/beta) sum_{t_i <= t} (1 - exp(-beta (t - t_i)))`.
42
+ Under the true model the increments `Lambda(t_i) - Lambda(t_{i-1})` are iid
43
+ Exp(1) (time-rescaling theorem); `hawkes_residual_analysis` tests that with a
44
+ Kolmogorov-Smirnov test. Simulation uses Ogata (1981) thinning.
45
+
46
+ The tests check the likelihood against a closed form for two events and a
47
+ brute-force O(n^2) sum, the compensator against numerical integration of the
48
+ intensity, the simulated mean rate against `mu / (1 - n)`, and MLE recovery of
49
+ `(mu, alpha, beta)`.
50
+
51
+ ## References
52
+
53
+ - Andersen, T. G. and Bollerslev, T. (1998). Answering the skeptics: yes, standard volatility models do provide accurate forecasts. International Economic Review 39(4), 885-905.
54
+ - Barndorff-Nielsen, O. E. and Shephard, N. (2004). Power and bipower variation with stochastic volatility and jumps. Journal of Financial Econometrics 2(1), 1-37.
55
+ - Barndorff-Nielsen, O. E. and Shephard, N. (2006). Econometrics of testing for jumps in financial economics using bipower variation. Journal of Financial Econometrics 4(1), 1-30.
56
+ - Corsi, F. (2009). A simple approximate long-memory model of realized volatility. Journal of Financial Econometrics 7(2), 174-196.
57
+ - Hawkes, A. G. (1971). Spectra of some self-exciting and mutually exciting point processes. Biometrika 58(1), 83-90.
58
+ - Ogata, Y. (1981). On Lewis' simulation method for point processes. IEEE Transactions on Information Theory 27(1), 23-31.
59
+ - Ozaki, T. (1979). Maximum likelihood estimation of Hawkes' self-exciting point processes. Annals of the Institute of Statistical Mathematics 31, 145-155.
@@ -0,0 +1,30 @@
1
+ # Provenance
2
+
3
+ hfvol was carved out of [srfm-lab](https://github.com/Mattbusel/srfm-lab)
4
+ (MIT, copyright 2026 Matthew Busel) at commit
5
+ `8cbfcb0551d44e3ed1c20bdae9dc805d61521c62`.
6
+
7
+ | hfvol | srfm-lab origin | Changes |
8
+ |---|---|---|
9
+ | `realized.realized_variance` | `lib/math/volatility_models.py` `realized_variance` (L130) | `annualize` default `False`; input validation |
10
+ | `realized.realized_variance_series` | `realized_variance_series` (L145) | vectorised reshape instead of a Python loop |
11
+ | `realized.bipower_variation` (+ `_series`) | `bipower_variation` (L169) | `annualize` default `False`; `_series` variant is new |
12
+ | `realized.jump_variation` (+ `_series`) | `jump_variation` (L189) | `_series` variant is new |
13
+ | `har.HARParams` | `HARParams` dataclass | frozen; `as_array()` |
14
+ | `har.har_features` | `har_features` (L206) | vectorised with a cumulative sum; same values |
15
+ | `har.fit_har` | `fit_har` (L226) | clear error when the series is too short |
16
+ | `har.har_forecast`, `har.har_forecast_path` | `har_forecast` (L244) | same recursion; the path variant returns every step |
17
+ | `hawkes.HawkesParams` | `lib/math/hawkes_process.py` `HawkesParams` | frozen; `_validate()` |
18
+ | `hawkes.hawkes_simulate` | `hawkes_simulate` | O(n) excitation state instead of re-summing all past events; seedable; `ValueError` instead of `assert` |
19
+ | `hawkes.hawkes_intensity_path` | `hawkes_intensity_path` | O(n + m) via `searchsorted` and the recursion; `include_current` switch |
20
+ | `hawkes.hawkes_compensator` | new (was inline in `hawkes_residual_analysis`) | O(n + m) |
21
+ | `hawkes.hawkes_log_likelihood` | `hawkes_log_likelihood` | same formula; checks event order |
22
+ | `hawkes.fit_hawkes_mle` | `fit_hawkes_mle` | reparametrised, rate-scaled starts, seeded, keeps the best finite run (see CHANGELOG, Fixed) |
23
+ | `hawkes.branching_ratio` | `branching_ratio` | seeded; unused local removed |
24
+ | `hawkes.hawkes_residual_analysis` | `hawkes_residual_analysis` | O(n); includes the first increment |
25
+ | `transformers.*`, `forecasting.*` | new | sktime wrappers built from sktime's extension templates |
26
+ | `tests/*` | new | srfm-lab has no tests for these modules |
27
+
28
+ Left behind: EWMA, GARCH and realized GARCH, VIX and variance risk premium,
29
+ volatility surface, rough volatility (sktime already ships a Hurst
30
+ transformer), and the multivariate Hawkes simulator.
@@ -0,0 +1,26 @@
1
+ """Hawkes intensity of trade arrivals as a look-ahead-free feature (pip install hfvol[sktime])."""
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+ from hfvol import HawkesIntensity, HawkesParams, hawkes_simulate
7
+
8
+ # Simulated trade times over one hour, in seconds. Excitation decays over ~20 s,
9
+ # well above the 1 s bins used below. Trades in the same bin count as
10
+ # simultaneous and do not excite each other, so pick bins finer than 1 / beta.
11
+ true = HawkesParams(mu=0.05, alpha=0.04, beta=0.05)
12
+ events = hawkes_simulate(true, T=3600.0, rng=7)
13
+
14
+ # Trades per second on a DatetimeIndex.
15
+ start = pd.Timestamp("2024-06-03 14:00:00")
16
+ seconds = pd.date_range(start, periods=3600, freq="1s")
17
+ counts = pd.Series(np.bincount(events.astype(int), minlength=3600)[:3600], index=seconds)
18
+
19
+ tr = HawkesIntensity(method="mle", time_unit="1s", random_state=0)
20
+ intensity = tr.fit_transform(counts)
21
+
22
+ print(f"{int(counts.sum())} trades; true {true}, branching ratio {true.branching_ratio:.2f}")
23
+ print(f"fitted mu={tr.mu_:.3f}/s alpha={tr.alpha_:.3f} beta={tr.beta_:.3f}/s")
24
+ print(f"fitted branching ratio {tr.branching_ratio_:.2f}")
25
+ print("busiest seconds by predicted intensity:")
26
+ print(intensity.nlargest(5))
@@ -0,0 +1,42 @@
1
+ """Realized measures, HAR-RV and Hawkes with numpy only (pip install hfvol[scipy])."""
2
+
3
+ import numpy as np
4
+
5
+ from hfvol import (
6
+ HawkesParams,
7
+ bipower_variation_series,
8
+ fit_har,
9
+ fit_hawkes_mle,
10
+ har_forecast_path,
11
+ hawkes_residual_analysis,
12
+ hawkes_simulate,
13
+ jump_variation_series,
14
+ realized_variance_series,
15
+ )
16
+
17
+ rng = np.random.default_rng(0)
18
+ bars, days = 78, 300
19
+
20
+ # Simulated 5-minute returns with slowly moving daily volatility and occasional jumps.
21
+ daily_vol = 0.01 * np.exp(np.cumsum(rng.normal(0, 0.05, days)))
22
+ r = rng.normal(0, 1, (days, bars)) * (daily_vol[:, None] / np.sqrt(bars))
23
+ jump_days = rng.random(days) < 0.05
24
+ r[jump_days, bars // 2] += 0.02
25
+ r = r.ravel()
26
+
27
+ rv = realized_variance_series(r, freq=bars)
28
+ bv = bipower_variation_series(r, freq=bars)
29
+ jv = jump_variation_series(r, freq=bars)[:, 1]
30
+ print(f"mean RV {rv.mean():.3e} mean BV {bv.mean():.3e}")
31
+ print(f"mean jump variation on jump days {jv[jump_days].mean():.3e}")
32
+ print(f"mean jump variation on other days {jv[~jump_days].mean():.3e}")
33
+
34
+ params = fit_har(bv)
35
+ print("HAR coefficients:", params)
36
+ print("next 5 days:", np.round(har_forecast_path(params, bv, horizon=5), 7))
37
+
38
+ true = HawkesParams(mu=0.5, alpha=0.8, beta=1.6)
39
+ events = hawkes_simulate(true, T=5000.0, rng=1)
40
+ est, ll = fit_hawkes_mle(events, T=5000.0, rng=0)
41
+ print(f"{len(events)} events; fitted {est}, branching ratio {est.branching_ratio:.3f}")
42
+ print("time-rescaling check:", hawkes_residual_analysis(events, 5000.0, est))
@@ -0,0 +1,44 @@
1
+ """Daily realized measures from intraday returns, then a HAR forecast.
2
+
3
+ Needs pip install "hfvol[sktime]".
4
+ """
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ from sktime.forecasting.model_evaluation import evaluate
9
+ from sktime.split import ExpandingWindowSplitter
10
+
11
+ from hfvol import BipowerVariation, HARForecaster, JumpVariation, RealizedVariance
12
+
13
+ rng = np.random.default_rng(0)
14
+ bars = 78
15
+ sessions = pd.bdate_range("2024-01-02", periods=250)
16
+ index = pd.DatetimeIndex(
17
+ np.concatenate(
18
+ [pd.date_range(d + pd.Timedelta("9h35min"), periods=bars, freq="5min") for d in sessions]
19
+ )
20
+ )
21
+ vol = np.repeat(0.01 * np.exp(np.cumsum(rng.normal(0, 0.05, len(sessions)))), bars)
22
+ returns = pd.DataFrame(
23
+ {
24
+ "SPY": rng.normal(0, 1, len(index)) * vol / np.sqrt(bars),
25
+ "QQQ": rng.normal(0, 1, len(index)) * 1.3 * vol / np.sqrt(bars),
26
+ },
27
+ index=index,
28
+ )
29
+
30
+ # One row per calendar day, one column per asset.
31
+ rv = RealizedVariance(freq="D").fit_transform(returns)
32
+ bv = BipowerVariation(freq="D").fit_transform(returns)
33
+ jv = JumpVariation(freq="D", relative=True).fit_transform(returns)
34
+ print(rv.tail(3), bv.tail(3), jv.tail(3), sep="\n\n")
35
+
36
+ # HAR on SPY's daily realized variance, evaluated with an expanding window.
37
+ y = rv["SPY"].reset_index(drop=True)
38
+ cv = ExpandingWindowSplitter(initial_window=150, step_length=10, fh=[1, 5])
39
+ results = evaluate(HARForecaster(), cv=cv, y=y)
40
+ print(results[["test_MeanAbsolutePercentageError", "cutoff"]].tail())
41
+
42
+ forecaster = HARForecaster().fit(y)
43
+ print("coefficients:\n", forecaster.coef_)
44
+ print("next 5 days:\n", forecaster.predict(fh=[1, 2, 3, 4, 5]))