Skip to content

fix(MarketResilience): require the depleted side to come back at its price, not just its size - #80

Merged
silahian merged 1 commit into
masterfrom
port/market-resilience-price-anchor
Sep 21, 2026
Merged

silahian merged 1 commit into
masterfrom
port/market-resilience-price-anchor

Conversation

@silahian

Copy link
Copy Markdown
Collaborator

The defect

Immediacy-weighted depth is measured from the touch of the frame being measured, so the reference price moves with the book. A bid that was swept and rebuilt three ticks lower therefore read as a full recovery — the size was back, but the price that had been bid for was gone.

The score published 0.3125 and the bias tile said nothing, on a book that had just given way.

The rule

A depleted side now counts as recovered only when both hold inside the window:

  1. its immediacy-weighted depth is back to 90% of the way from its trough to its baseline (unchanged), and
  2. its best price is back within one spread baseline of where it was quoted on the frame before the depletion was detected.

Where the anchor comes from

The frame before the depletion — not either of the two obvious alternatives:

  • Not the depletion frame itself. On a sweep that frame already shows the moved touch, so the event would be measured against its own damage.
  • Not the anchoring print. A print admits events for the whole shock window — five seconds by default — so that price can be seconds stale. Ordinary drift between the print and the depletion would then read as a failure to redeploy and publish a directional arrow from nothing.

Why one spread baseline

It is the only distance unit already in the file, it is the instrument's own typical distance, and it carries no tick size and no price scale — this plugin serves crypto, equities, futures and FX. Routine requoting inside the spread still counts as a recovery; a touch that moved further than the instrument's own spread does not.

The alternatives were each rejected for a concrete reason: zero tolerance fires on ordinary quote competition, a fraction of a spread is a second constant with no basis, and a tick count is a hardcoded multiplier.

The test is one-sided. A side that came back at a better price came back, and is credited. The tile reports which side FAILED, and nothing failed there.

Skipped, crediting the recovery

When there is no anchor (the print arrived before any book) or no measurable spread baseline. Neither can support the comparison, and silence is the safe direction for a tile that only speaks about failures.

Also in this change

ActivateDepthEvent's cold-start spread baseline floored at 1.0 regardless of the book, while IsLOBDepleted prefers the book's own spread when it has one. That measured the event on a different scale than the detector that admitted it, and on an instrument quoting below 1.0 it is a tolerance larger than the whole price. The two now agree.

Both tile tooltips are corrected: for the new rule, for the arrow holding its last reading between the hysteresis levels rather than resetting to neutral, and for the first-recovery rule, which had lost the qualifier that a first-of-session non-recovery does publish.

Deliberately NOT changed

The plugin Description strings. They are hashed into the plugin id that keys saved settings, so editing one orphans every existing user's symbol and provider selection and leaves the tile dead on launch. Verified byte-unchanged in this PR. The corrected wording lives in the tooltip, which is not hashed.

Evidence

dotnet test tests/Unit/Studies.MarketResilience.Test68 passed, 0 failed, with no test modified by this PR.

dotnet build VisualHFT.Plugins/Studies.MarketResilience0 errors.

The rule was mutation-checked three ways before this port:

mutation result
price test removed entirely 4 tests red
written as a symmetric band abs(touch - anchor) <= tolerance 1 test red — the better-price case
anchor read off the depletion frame instead of the frame before 2 tests red

Reviewer note

The zero-tests problem called out in #79 is gone — this test project now executes and reports its 68 results normally.

…price, not just its size

Immediacy-weighted depth is measured from the touch of the frame being
measured, so the reference price moves with the book. A bid that was swept and
rebuilt three ticks lower therefore read as a full recovery: the size was back,
but the price that had been bid for was gone. The market moved down and the
tile reported nothing.

A depleted side now counts as recovered only when both hold inside the window:
its depth is back to 90% of the way from its trough to its baseline, and its
best price is back within one spread baseline of where it was quoted on the
frame before the depletion was detected.

The anchor is the frame before the depletion, and neither of the two obvious
alternatives. Not the depletion frame itself: on a sweep that frame already
shows the moved touch, so the event would be measured against its own damage.
Not the anchoring print: a print admits events for the whole shock window, five
seconds by default, so that price can be seconds stale, and ordinary drift in
between would read as a failure to redeploy and publish a directional arrow
from nothing.

The tolerance is one spread baseline, inclusive, taken from the event itself.
It is the instrument's own typical distance, so it carries no tick size and no
price scale. Routine requoting inside the spread still counts as a recovery; a
touch that moved further than the instrument's own spread does not. Zero
tolerance fires on ordinary quote competition, a fraction of a spread is a
second constant with no basis, and a tick count is a hardcoded multiplier. The
test is one-sided: a side that came back at a better price came back, and the
tile reports which side failed.

Also aligns ActivateDepthEvent's cold-start spread baseline with the one
IsLOBDepleted already uses. It floored at 1.0 regardless of the book, which
measured the event on a different scale than the detector that admitted it and,
on an instrument quoting below 1.0, is a tolerance larger than the whole price.

Both tile tooltips are corrected for the new rule, for the arrow holding its
last reading between the hysteresis levels rather than resetting to neutral,
and for the first-recovery rule, which had lost the qualifier that a
first-of-session non-recovery does publish. The plugin Description strings are
deliberately untouched: they are hashed into the id that keys saved settings.
Copilot AI lite review requested due to automatic review settings September 21, 2026 13:10
@silahian
silahian merged commit 2b6970d into master Sep 21, 2026
1 check passed
@silahian
silahian deleted the port/market-resilience-price-anchor branch September 21, 2026 13:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The documented “skip anchor check when no measurable spread baseline” behavior isn’t achievable with the current state model because the anchor tolerance is conflated with the depth-normalization baseline.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Updates the Market Resilience study so a depleted side is only credited as “recovered” when depth returns and the best price returns close enough to the pre-depletion touch, preventing false recoveries after repricing.

Changes:

  • Anchor the recovery price check to the previous book frame (pre-depletion) and require the touch to return within one spread-baseline.
  • Align cold-start spread-baseline behavior between depletion detection and depth-event activation.
  • Update both tiles’ tooltip text to reflect the new recovery rule and clarified hysteresis behavior.
File Description
VisualHFT.Plugins/​Studies.MarketResilience/​Model/​MarketResilienceCalculator.cs Adds previous-touch anchoring and enforces price-return gating for depth recovery.
VisualHFT.Plugins/​Studies.MarketResilience/​MarketResilienceStudy.cs Updates Market Resilience tooltip to describe price-return requirement.
VisualHFT.Plugins/​Studies.MarketResilience/​MarketResilienceBiasStudy.cs Updates Bias tile tooltip to reflect price-aware recovery and hysteresis persistence.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


// The touch on the frame before the depletion was detected: the price each side has to
// come back to. Null when no earlier frame was seen.
public decimal? AnchorBid, AnchorAsk;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants