Class LpScorecard
LpRouter consumes it to price rejects into the routing decision.
All statistics are exponentially weighted per event (configurable α),
so the card tracks current LP behavior, not the session average of a
provider that changed its engine at lunch. Post-reject markout is
measured one horizon after each reject against the mid you feed via
onMid(double, long): positive markout = the market moved the way you were
trying to trade = the reject cost you real money.
Zero allocation, single writer (the execution/aggregation thread). Pending markouts live in a small ring per LP (4 slots): reject bursts — which happen precisely when the market runs and markouts are largest — are sampled rather than overwritten, so the stat cannot be biased low for exactly the LPs it must expose. Only a burst deeper than the ring within one horizon overwrites its oldest entry.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intPending-markout slots per LP (bursts deeper than this overwrite oldest). -
Constructor Summary
ConstructorsConstructorDescriptionLpScorecard(int lpCount) 5% event weight, 100 ms markout horizon.LpScorecard(int lpCount, double alpha, long markoutHorizonNanos) -
Method Summary
Modifier and TypeMethodDescriptionlongattempts(int lp) Invariant: attempts == fills + rejects (kept explicit for future outcomes).doubleavgHoldNanos(int lp) EWMA hold time across fills and rejects, in nanos.doubleeffectiveSpread(int lp) EWMA effective half-spread paid on fills, in price units.longfills(int lp) intlpCount()longMarkouts matured across all LPs — the router-degradation canary: zero while rejects accrue meansonMid(double, long)is not wired and the routing penalty is silently zero.voidonFill(int lp, boolean buy, double price, double midAtRequest, long holdNanos) An accepted fill.voidonMid(double mid, long timestampNanos) Composite mid update: matures every pending reject markout whose horizon has elapsed.voidonReject(int lp, boolean buy, double midAtRequest, long timestampNanos, long holdNanos) A last-look reject.doublepostRejectMarkout(int lp) EWMA post-reject markout in price units — positive means the market moved the way you were trying to trade after the LP declined: the realized cost of that LP's last look.voidRestores the card; pending markouts reset (restore at session start).doublerejectRate(int lp) EWMA reject probability in [0, 1]; 0 before any events.longrejects(int lp) voidwriteState(DataOutput out) Persists the learned LP behavior — reject rates, hold times, effective spreads and post-reject markouts.
-
Field Details
-
PENDING_RING
public static final int PENDING_RINGPending-markout slots per LP (bursts deeper than this overwrite oldest).- See Also:
-
-
Constructor Details
-
LpScorecard
public LpScorecard(int lpCount, double alpha, long markoutHorizonNanos) - Parameters:
lpCount- number of LPs (dense indices)alpha- EWMA weight per event, e.g. 0.05markoutHorizonNanos- how long after a reject the markout is read, e.g. 100ms =100_000_000L
-
LpScorecard
public LpScorecard(int lpCount) 5% event weight, 100 ms markout horizon.
-
-
Method Details
-
onFill
public void onFill(int lp, boolean buy, double price, double midAtRequest, long holdNanos) An accepted fill.- Parameters:
buy- our directionprice- the all-in fill pricemidAtRequest- composite mid when the order was sentholdNanos- time the LP held the order before accepting
-
onReject
public void onReject(int lp, boolean buy, double midAtRequest, long timestampNanos, long holdNanos) A last-look reject. The markout clock starts here: feed mids viaonMid(double, long)and the move one horizon later is attributed to this reject. -
onMid
public void onMid(double mid, long timestampNanos) Composite mid update: matures every pending reject markout whose horizon has elapsed. NaN mids (one-sided composite, feed gap) are ignored — a non-price must never poison the EWMA, which would otherwise stay NaN forever and disable the router's penalty. The common no-pending case is a single compare. -
rejectRate
public double rejectRate(int lp) EWMA reject probability in [0, 1]; 0 before any events. -
avgHoldNanos
public double avgHoldNanos(int lp) EWMA hold time across fills and rejects, in nanos. -
effectiveSpread
public double effectiveSpread(int lp) EWMA effective half-spread paid on fills, in price units. -
postRejectMarkout
public double postRejectMarkout(int lp) EWMA post-reject markout in price units — positive means the market moved the way you were trying to trade after the LP declined: the realized cost of that LP's last look. -
maturedMarkouts
public long maturedMarkouts()Markouts matured across all LPs — the router-degradation canary: zero while rejects accrue meansonMid(double, long)is not wired and the routing penalty is silently zero. -
attempts
public long attempts(int lp) Invariant: attempts == fills + rejects (kept explicit for future outcomes). -
fills
public long fills(int lp) -
rejects
public long rejects(int lp) -
lpCount
public int lpCount() -
writeState
Persists the learned LP behavior — reject rates, hold times, effective spreads and post-reject markouts. The pending-markout ring is intraday (a reject awaiting its horizon) and is not persisted. Format version 2 (v1, from before markout seeding, is still read). Seepersist.Checkpoint.- Throws:
IOException
-
readState
Restores the card; pending markouts reset (restore at session start). Reads both format versions — a v1 checkpoint carries no per-LP markout counts, so a restored nonzero markout EWMA counts as already-seeded (it is). Throws on an LP-count mismatch or an unknown version.- Throws:
IOException
-