Managed orders

Strategies

Managed orders

Position-aware intent: say "be long 5" or "go flat" and let the framework work out the reversal arithmetic and guard against double entries.

Managed orders (position-aware intent)

Managed orders are the simplest way to trade and the right default for most strategies. You state the position you want and the framework works out the market order. EnterLong and EnterShort handle reversals: from a short position, EnterLong buys back the short and opens the long in one order. ExitLong and ExitShort return to flat and do nothing when you are not on that side. Quantities are in trading units (lots on a base-unit forex venue); see Instrument metadata & sizing.

C#EnterLong(5);    // flat: buy 5; short 3: buy 8 (cover 3 + open 5); already long: nothing
EnterShort(5);   // mirror of EnterLong
ExitLong();      // long: sell to flat; otherwise nothing
ExitShort();     // short: buy to flat; otherwise nothing

if (!HasPendingManagedOrder && signal) EnterLong(qty);   // wait while one is in flight
Call Behaviour by starting position
EnterLong(qty) Flat: enters long. Short: reverses to long. Already long: nothing (one entry per direction).
EnterShort(qty) Mirror of EnterLong.
ExitLong() Long: closes with a market order. Flat or short: nothing.
ExitShort() Short: closes with a market order. Flat or long: nothing.

EnterLong and EnterShort throw ArgumentOutOfRangeException for a quantity of zero or less; close with ExitLong / ExitShort instead. While the host has paused a live run for feed recovery, the two entry calls are held and report the reason through NotifyEntryBlocked; the exit calls still go out.

The pending-order guard

The managed helpers keep at most one order in flight. While it is live (submitted and not yet terminal), HasPendingManagedOrder is true and every managed call does nothing, so a strategy that fires on each bar cannot stack duplicate entries before the first one fills. Two EnterLong calls in the same OnBar therefore send one order. The flag clears when the managed order is cancelled, rejected or expired, and for a filled order when the position shows the fill or the venue's settle time has passed. The base class feeds each order update to the managed layer before your OnOrderUpdate runs.

C#protected override void OnBar()
{
    if (HasPendingManagedOrder) return;          // let the previous intent settle
    if (crossUp && IsFlat)  EnterLong(_qty.Value);
    if (crossDown && IsLong) ExitLong();
}

A fill counts as settled once the position shows it. Several venues state the position after the order, so the base class also feeds each position statement to the managed layer before your OnPositionUpdate runs, and a managed order that filled stays in flight until the position shows the fill.

A managed order can also fail before it becomes an order: the placement call faults or is cancelled, for example while the connection is down. No OnOrderUpdate ever comes for it, so the base class calls OnManagedOrderFailed instead, once per such order, on the strategy thread and never inside the callback that placed it. HasPendingManagedOrder is already false at that point. Override it to put the strategy back in a known state. An order the venue rejects is not reported here: it reaches OnOrderUpdate as a rejected order, so react to one or the other, never to both.

C#protected override void OnManagedOrderFailed(string clientTag, Order? order, Exception? error)
{
    // order is always null: no order exists for a placement that failed.
    Log($"Managed order {clientTag} was not placed: {error?.Message}");
    _exitRequested = false;      // back to a known state
}
Do not resend in a loop. A failure that persists (a connection that is down, a venue that is closed) does not go away by sending the same order again. A throw from OnManagedOrderFailed faults the run, like a throw from any other callback.
Market orders only. The managed helpers enter and exit with market orders. For resting limits, custom OCO or absolute-price brackets use raw orders. For multi-target scale-outs with auto-breakeven and trailing, add an ATM plan: ATM brackets whatever position the strategy opens, so EnterLong states the intent and the ATM protects the result.

Behind the surface

The helpers delegate to an IManagedOrderController that the base class creates on first use through IStrategyContext.CreateManagedOrderController. Its members are the four intent calls, HasPendingOrder, and HandleOrderUpdate(Order), which the base class calls with every order update so the controller can clear its pending flag. The base class also calls HandlePositionUpdate(Position) with every position statement of the run's instrument, and subscribes to the controller's Failed event, which it forwards to OnManagedOrderFailed. The controller recognises its own orders by the ClientTag it stamps on them. Strategy code uses the protected helpers; a custom host supplies its own controller through the context factory without any change to the strategy.

Start here. Reach for raw orders or ATM only when managed intent cannot express what you need. For directional strategies these four calls are usually the whole trading surface.