Raw orders & brackets

Strategies

Raw orders & brackets

When managed intent is not enough: submit order requests directly, attach a protective bracket, and modify or cancel by id.

Raw orders & brackets

For full control over price, type and time in force, build an OrderRequest and submit it with PlaceOrder. A raw order does exactly what it says, independent of your position, so the lifecycle is yours. Results arrive through OnOrderUpdate and OnFill, where you react to fills, rejections and cancels. The request's Instrument must equal the run's Instrument: a strategy trades only the instrument it runs on. A structurally invalid request throws on the strategy thread when you call PlaceOrder. Raw quantities are in venue units; see Instrument metadata & sizing.

To get a protective stop and target without managing the OCO yourself, attach a BracketSpec to the entry. The provider places the bracket and cancels the remaining leg when one fills. Bracket prices are absolute, which keeps the spec venue-neutral.

C#using SabrTrader.Pipeline.Venues.Trading;

// Market entry with a bracket (absolute prices).
PlaceOrder(new OrderRequest(
    Instrument: Instrument,
    Side: OrderSide.Buy,
    Type: OrderType.Market,
    Quantity: 5m,
    Bracket: new BracketSpec(StopLossPrice: 4340m, TakeProfitPrice: 4360m)));

// Resting limit order:
PlaceOrder(new OrderRequest(Instrument, OrderSide.Buy, OrderType.Limit, 2m, LimitPrice: 4350.25m));

ModifyOrder(orderId, new OrderModification(NewLimitPrice: 4351m));
CancelOrder(orderId);
Flatten();   // close this run's open position

A BracketSpec needs at least one of its two prices, and for a buy the stop must be below the target (reversed for a sell); BracketSpec.Validate(entrySide) checks this and runs as part of the request's validation. The request uses these enums:

Type Values
OrderSide Buy, Sell
OrderType Market, Limit, Stop, StopLimit
TimeInForce Day (default), Gtc, Ioc, Fok
OrderSession Regular (default), PreMarket, PostMarket, Extended; set on OrderRequest.Session. See Time in force & session for what a venue takes.
OrderState Pending, Working, PartiallyFilled, Filled, Cancelled, Rejected, Expired, Stale

Limit requires a positive LimitPrice, Stop a positive StopPrice, StopLimit both, and Market neither; Validate() enforces this. Filled, Cancelled, Rejected and Expired are terminal (Order.IsTerminal). Stale marks an order whose state the provider re-checks after a reconnect.

The full OrderRequest

The examples use the common arguments. The complete constructor is:

OrderRequest.csnew OrderRequest(
    Instrument, Side, Type, Quantity,
    LimitPrice: null, StopPrice: null,
    TimeInForce: TimeInForce.Day,
    Bracket: null,            // BracketSpec: protective stop / target
    OcoGroupId: null,         // links orders into one one-cancels-other group
    ClientTag: null,          // your own label, echoed back on the resulting Order
    InstrumentKey: null,      // cross-venue routing: the canonical instrument id
    InstrumentVenue: null,    // and the venue the symbol is native to
    OriginId: null);          // which component created the order
  • OcoGroupId ties several working orders into one one-cancels-other group (a manual OCO; BracketSpec is the automatic two-leg case). OcoGroupKind (Alternatives, ProtectiveExits) says what the members are to each other.
  • ClientTag is a free label echoed back on the resulting Order. Use it to recognise your orders in OnOrderUpdate, since PlaceOrder returns before the order has an id.
  • InstrumentKey / InstrumentVenue route an order to a broker on a different venue than the data feed; the execution layer translates the symbol. Null means same venue.
  • OriginId records which component created the order, so an automatic manager (a chart-trader ATM, the copier) acts only on positions it opened.

A few venue-specific fields are init-only properties outside the constructor: ReduceOnly, PostOnly, PositionEffect (Open, Close), NativeInstrumentId and NativeInstrumentType. Set them with an object initializer or a with expression. Call request.Validate() to fail fast on an inconsistent request; the same validation runs on submit. Modifications travel as an OrderModification (NewQuantity, NewLimitPrice, NewStopPrice, NewTimeInForce), where a null field means "leave unchanged".

Reading orders & fills

OnOrderUpdate(Order) hands you the order's latest state. Besides State it carries Id, FilledQuantity, AverageFillPrice, RemainingQuantity, BrokerOrderId, RejectReason, the current Request, the OriginalRequest as first submitted, and IsTerminal. Keep the OrderId (a GUID wrapper) to modify or cancel later. The run's live orders are also available as WorkingOrders.

OnFill(Fill) carries each execution (OrderId, Quantity, Price, Commission, TimestampUtc, ExecutionId), and OnOrderUpdate fires alongside with the rolled-up order state. Use OnFill for per-execution bookkeeping and OnOrderUpdate for state transitions.

C#protected override void OnOrderUpdate(Order order)
{
    if (order.State == OrderState.Rejected)
        Log($"rejected: {order.RejectReason}");
    else if (order.State == OrderState.Filled)
        Log($"filled {order.FilledQuantity} @ {order.AverageFillPrice}");
}

protected override void OnFill(Fill fill)
    => Log($"exec {fill.Quantity} @ {fill.Price} (comm {fill.Commission})");

CancelOrder on an order that fills first produces a fill, not a cancel; that race is normal.

Reusable bracket presets

For named stop/target templates, the SabrTrader.Pipeline.Venues.Trading.Brackets namespace offers BracketPreset (a name plus stop and target offsets as positive magnitudes, in Price, Ticks or Pips) and BracketPresetMath.ComputeBracketPrices(entryPrice, side, preset, tickSize, pipSize), which turns the offsets into the absolute prices a BracketSpec takes. Long entries put the stop below and the target above; shorts reverse it. See the Trading reference.