`UlcerIndex::update` previously scanned the full `period`-window every
tick via `prices.iter().fold(NEG_INFINITY, f64::max)`, breaking the
`Indicator` trait's O(1) contract. For long windows (e.g. period 50+ on
a live tick stream) this turned a constant-time update into an O(period)
one, and full-history batch replays into O(n · period).
The window of raw prices is replaced with a monotonically-decreasing
deque of `(index, price)` pairs. On every push, all back entries
`<= input` are popped (they can never be the trailing max again, since
they are dominated and at least as old). On every step, the front is
popped if its index is older than `count - period + 1`. The deque's
front is therefore always the trailing max in O(1). `count: u64` is the
1-based input counter that drives expiration; on `reset()` it returns
to zero alongside the deque and the drawdown state.
Behaviour is unchanged: same per-tick values, same warmup
(`2 * period - 1`), same non-finite-input semantics. A new test
`monotone_deque_matches_naive_max_on_adversarial_inputs` compares the
deque output bar-by-bar against an independent O(n) trailing-max scan on
inputs designed to hit every code path: strictly increasing (full tail
pops), strictly decreasing (head expirations only), constants (the
`<= input` pop rule keeps a single newest entry), and a sawtooth.
The doc comment on `warmup_period()` is also corrected (B-Opus-2): the
two windows overlap by one bar, so the formula is `2 * period - 1`, not
`2 * period`.