Class BenchmarkExecutor
TwapScheduler, VwapScheduler,
ImplementationShortfallScheduler), it re-decides every
interval from live market state. Cross-asset: prices/sizes are
doubles, so it serves equities and FX identically.
Two layers
- The benchmark curve: each
BenchmarkExecutor.Benchmarkdefines the fraction of the parent that should be complete by now. Time-driven benchmarks (TWAP linear, Arrival/IS front-loaded, Close back-loaded, Open aggressively front-loaded) use elapsed schedule fraction; volume-driven benchmarks (VWAP, POV) use the realized volume curve, VWAP against the expected cumulative profile and POV against a fixed share of actual volume. - The dynamic adjustment — the raw "behind schedule" quantity
is then shaped by the real-time inputs a production algo watches:
- Alpha — expected short-term move: trade faster when the price is about to move against you (alpha in your trading direction), slower when it favors waiting;
- Spread & volatility — the cost/urgency trade-off: a wide spread or (for passive benchmarks) high volatility damps aggression; for Arrival/IS, volatility raises urgency (timing risk);
- Liquidity — the interval child is capped at a participation fraction of displayed depth, so the algo never asks for more than the book can give;
- Fill rate / schedule drift — feed realized fills back
via
onFill(long); falling behind pulls the next child up, running ahead lets it ease off.
Usage: construct with the parent, benchmark and horizon; each interval
call dueQuantity(double, BenchmarkExecutor.MarketState) with the current BenchmarkExecutor.MarketState and the
elapsed schedule fraction (and, for volume benchmarks, feed market prints
via onMarketVolume(long)); send the returned child; report fills via
onFill(long). The executor is single-parent, single-writer.
In-flight children
Report each child with onChildSent(long) and, when the venue is
done with it, the unfilled remainder with onChildDone(long). Sent but
unfilled quantity counts against the schedule, so a child that rests
unfilled is not sent again next interval; without this bookkeeping the
deficit would be re-sent every interval while the first child still
works. Fills reported through onFill(long) move quantity from open to
executed.
Completion
Over the last 1 - rampStart of the horizon the depth cap is
relaxed linearly from maxDepthFraction to all of the displayed
depth, and at scheduleFraction >= 1 every curve's target is the
full parent, so the whole unworked remainder is due, limited only by what
is displayed and by the maximum child. A residual left at the horizon is
then one the book could not absorb, visible as remaining(). Optional child-size bounds
(withChildBounds(long, long)) suppress dribble orders and cap signalling.
Participation
The POV rate p is a share of total traded volume, including
ours (the industry definition). With V the volume printed by
others, executing x gives a share x / (x + V); setting this
equal to p gives the target x = p / (1 - p) * V. Using
p * V instead would realise only p / (1 + p) (9.1 % for a
10 % target).
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumThe benchmark this parent is measured against.static final recordA snapshot of the real-time inputs a benchmark algo evaluates. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final doubleHow hard a 1% relative trading cost (spread fraction of mid plus impact as a fraction) damps aggression: the term is1/(1 + cost × SPREAD_SENSITIVITY), so a 1% cost halves the pace and a 2-pip FX spread (~0.002%) barely registers. -
Constructor Summary
ConstructorsConstructorDescriptionBenchmarkExecutor(Side side, long parentQty, BenchmarkExecutor.Benchmark benchmark, double participationRate, double alphaUrgency, double maxDepthFraction) -
Method Summary
Modifier and TypeMethodDescriptionbooleandone()longdueQuantity(double scheduleFraction, BenchmarkExecutor.MarketState m) Shares to send now, given the current market and how far through the schedule we are.longexecuted()longstatic BenchmarkExecutorof(Side side, long parentQty, BenchmarkExecutor.Benchmark benchmark) Sensible defaults: alpha urgency 1 (a full-scale normalized alpha of ±1 doubles/halves the pace, smoothly and never rail-pinned), child capped at 25% of displayed depth.voidonChildDone(long unfilledQty) A child finished (cancelled, expired or IOC remainder) withunfilledQtynever executed; that quantity stops counting as open.voidonChildSent(long qty) A child order ofqtywas sent and is now working.voidonFill(long qty) Our own child fill; reduces the open quantity by the same amount.voidonMarketVolume(long qty) A market print that was not our fill (drives VWAP/POV realized volume).longSent child quantity not yet filled or reported done.longstatic BenchmarkExecutorPOV convenience.doubleRealised share of total volume,executed / (executed + others); NaN before any volume.longdoublescheduleDrift(double scheduleFraction, BenchmarkExecutor.MarketState m) Schedule drift: executed fraction minus the benchmark's target fraction atscheduleFraction.side()withChildBounds(long min, long max) Child-size bounds: belowminnothing is sent (except to finish the parent), abovemaxthe child is capped.withCompletionRamp(double start) Schedule fraction in [0, 1) from which the depth cap is relaxed linearly towards the full remainder at the horizon (default 0.9).withShortfallUrgency(double kappaT) UrgencykTof the IMPLEMENTATION_SHORTFALL trajectory (Almgren-Chriss kappa times horizon, kappa = sqrt(lambda sigma^2 / eta)); must be finite and positive.
-
Field Details
-
SPREAD_SENSITIVITY
public static final double SPREAD_SENSITIVITYHow hard a 1% relative trading cost (spread fraction of mid plus impact as a fraction) damps aggression: the term is1/(1 + cost × SPREAD_SENSITIVITY), so a 1% cost halves the pace and a 2-pip FX spread (~0.002%) barely registers. A calibration constant, not a unit conversion.- See Also:
-
-
Constructor Details
-
BenchmarkExecutor
public BenchmarkExecutor(Side side, long parentQty, BenchmarkExecutor.Benchmark benchmark, double participationRate, double alphaUrgency, double maxDepthFraction) - Parameters:
side- buy or sell (sets the alpha sign convention)parentQty- total quantity to executebenchmark- the benchmark to trackparticipationRate- POV target in (0,1] (ignored unless participation)alphaUrgency- how hard alpha shifts the pace (0 disables; ~5–20 typical)maxDepthFraction- cap each child at this fraction of displayed depth (0,1]
-
-
Method Details
-
of
public static BenchmarkExecutor of(Side side, long parentQty, BenchmarkExecutor.Benchmark benchmark) Sensible defaults: alpha urgency 1 (a full-scale normalized alpha of ±1 doubles/halves the pace, smoothly and never rail-pinned), child capped at 25% of displayed depth. Participation must state its rate; usepov(Side, long, double). -
pov
POV convenience. -
withShortfallUrgency
UrgencykTof the IMPLEMENTATION_SHORTFALL trajectory (Almgren-Chriss kappa times horizon, kappa = sqrt(lambda sigma^2 / eta)); must be finite and positive. Small values approach TWAP, large values front-load. -
withChildBounds
Child-size bounds: belowminnothing is sent (except to finish the parent), abovemaxthe child is capped. -
withCompletionRamp
Schedule fraction in [0, 1) from which the depth cap is relaxed linearly towards the full remainder at the horizon (default 0.9). -
onChildSent
public void onChildSent(long qty) A child order ofqtywas sent and is now working. -
onChildDone
public void onChildDone(long unfilledQty) A child finished (cancelled, expired or IOC remainder) withunfilledQtynever executed; that quantity stops counting as open. Fills must already have been reported withonFill(long). -
onMarketVolume
public void onMarketVolume(long qty) A market print that was not our fill (drives VWAP/POV realized volume). -
onFill
public void onFill(long qty) Our own child fill; reduces the open quantity by the same amount. -
dueQuantity
Shares to send now, given the current market and how far through the schedule we are. Returns 0 when on/ahead of schedule or done; caps at the parent remainder and atmaxDepthFraction × displayedDepth.- Parameters:
scheduleFraction- elapsed fraction of the execution horizon in [0, 1] (wall-clock progress); ignored by participation, which is volume-drivenm- the live market snapshot
-
executed
public long executed() -
remaining
public long remaining() -
openQuantity
public long openQuantity()Sent child quantity not yet filled or reported done. -
parentQty
public long parentQty() -
marketVolume
public long marketVolume() -
done
public boolean done() -
realizedParticipation
public double realizedParticipation()Realised share of total volume,executed / (executed + others); NaN before any volume. -
scheduleDrift
Schedule drift: executed fraction minus the benchmark's target fraction atscheduleFraction. Positive = ahead, negative = behind. For participation this compares against the participation target instead of the time curve. -
side
-
benchmark
-