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 +9 -0
- hfvol-0.1.0/CHANGELOG.md +58 -0
- hfvol-0.1.0/LICENSE +21 -0
- hfvol-0.1.0/NOTICE +12 -0
- hfvol-0.1.0/PKG-INFO +119 -0
- hfvol-0.1.0/docs/api.md +87 -0
- hfvol-0.1.0/docs/ecosystem.md +31 -0
- hfvol-0.1.0/docs/index.md +77 -0
- hfvol-0.1.0/docs/methods.md +59 -0
- hfvol-0.1.0/docs/provenance.md +30 -0
- hfvol-0.1.0/examples/hawkes_trade_intensity.py +26 -0
- hfvol-0.1.0/examples/numpy_quickstart.py +42 -0
- hfvol-0.1.0/examples/sktime_pipeline.py +44 -0
- hfvol-0.1.0/pyproject.toml +67 -0
- hfvol-0.1.0/src/hfvol/__init__.py +85 -0
- hfvol-0.1.0/src/hfvol/forecasting.py +153 -0
- hfvol-0.1.0/src/hfvol/har.py +114 -0
- hfvol-0.1.0/src/hfvol/hawkes.py +323 -0
- hfvol-0.1.0/src/hfvol/py.typed +0 -0
- hfvol-0.1.0/src/hfvol/realized.py +161 -0
- hfvol-0.1.0/src/hfvol/transformers.py +416 -0
- hfvol-0.1.0/tests/test_har.py +80 -0
- hfvol-0.1.0/tests/test_hawkes.py +152 -0
- hfvol-0.1.0/tests/test_realized.py +106 -0
- hfvol-0.1.0/tests/test_sktime.py +152 -0
hfvol-0.1.0/.gitignore
ADDED
hfvol-0.1.0/CHANGELOG.md
ADDED
|
@@ -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).
|
hfvol-0.1.0/docs/api.md
ADDED
|
@@ -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]))
|