HawkesIntensity#

Description#

HawkesIntensity computes the conditional intensity of an exponential-kernel Hawkes process driven by an event-mark series. A Hawkes process is self-exciting: each event raises the near-term rate of further events by an amount proportional to the mark, and that excitation then decays exponentially over time. In order-flow terms, a burst of aggressive buying momentarily increases the probability of more buying, capturing the clustering and momentum structure of trades.

The recursion is:

lambda_t = mu + kappa_t
kappa_{t+1} = decay * (kappa_t + alpha * x_t)
kappa_0 = 0

where x_t is the event mark at time t (trade count, signed flow magnitude, or a 0/1 event indicator), mu is the baseline intensity, alpha scales the excitation from each event, and decay controls how fast the excitation fades.

The operator is causal: lambda_t depends only on x_{t-1} and earlier. At t = 0, when no history exists, the output is mu.

A NaN event mark is treated as missing (nan_policy: ignore): the output at that step is NaN, but kappa is not updated, so intensity recovers on the next finite sample. This prevents a single bad tick from permanently corrupting the state.

The operator processes one sample per step.

References:

  • Hawkes, A. G. (1971). "Spectra of some self-exciting and mutually exciting point processes." Biometrika, 58(1), 83-90.

  • Bacry, E., Mastromatteo, I., and Muzy, J.-F. (2015). "Hawkes processes in finance." Market Microstructure and Liquidity, 1(1), 1550005.

Examples#

Basic usage#

import numpy as np
from screamer import HawkesIntensity

# Event-mark series: one burst at t=0, then two events at t=3
x = np.array([1.0, 0.0, 0.0, 2.0, 0.0])
intensity = HawkesIntensity(decay=0.9, alpha=1.0, mu=0.0)(x)
# Output: [0.0, 0.9, 0.81, 0.729, 2.4561]
# At t=0: no prior history, intensity = mu = 0
# At t=1: the event at t=0 excites intensity to 0.9
# At t=3: the excitation has decayed to 0.729, then the 2-unit event fires
# At t=4: the combined excitation from t=3 is 0.9*(0.729 + 1.0*2.0) = 2.4561

Streaming one sample at a time#

op = HawkesIntensity(decay=0.9, alpha=1.0, mu=0.0)
for v in x:
    lam = op(float(v))   # returns a Python float each call

NaN tolerance#

x = np.array([1.0, np.nan, 1.0])
out = HawkesIntensity(decay=0.5, alpha=1.0, mu=0.0)(x)
# out[1] is NaN (missing input), but out[2] is finite (state not poisoned)

Usage plot#

import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from screamer import HawkesIntensity

rng = np.random.default_rng(10)
n = 300
events = np.zeros(n)
bursts = np.sort(rng.choice(n, size=70, replace=False))   # clustered arrivals
events[bursts] = 1.0
intensity = HawkesIntensity(decay=0.9, alpha=0.6, mu=0.2)(events)

fig = make_subplots(rows=2, cols=1, shared_xaxes=True, row_heights=[0.35, 0.65],
                    vertical_spacing=0.08)
fig.add_trace(go.Bar(y=events, name='events', marker_color='lightslategray'),
              row=1, col=1)
fig.add_trace(go.Scatter(y=intensity, name='intensity',
                         line=dict(color='purple')), row=2, col=1)
fig.update_layout(title='HawkesIntensity: each event raises the arrival rate',
                  yaxis=dict(title='event'), yaxis2=dict(title='intensity'),
                  margin=dict(l=20, r=20, t=60, b=20), legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1))
fig.show()