How to Properly Implement Broker-Side Bracket Orders (OCO Stop + Target) in Custom Strategy

Ozzyparkinson

New member
Joined
Feb 13, 2026
Posts
2
Likes
0
Hi all,

I currently have AI developing a custom strategy in the MotiveWave SDK (Java), and I’m looking to transition from virtual backtest stop/target logic to proper live broker-side bracket orders.

At the moment:
  • Entry orders are submitted using:
    oc.buy(qty);
    oc.sell(qty);
  • Stops and targets are managed virtually using bar High/Low logic.
  • This works correctly for back testing, however for live trading i would like to submit a market bracket order (SL/TP). Ensuring they are linked as OCO, having the broker manage the execution.
What is the correct SDK method for creating true bracket orders? Should AI be using:
  • createStopOrder()
  • createLimitOrder()
  • Or a specific bracket/OCO API?
How is OCO grouping properly defined in the SDK?
Is there an example of a minimal working bracket order implementation?
 
TLDR; There is no server-side OCO in the SDK, you have to manage those orders yourself.

I have decompiled the SDK to understand what's going on, this is my condensed knowledge so far:

Order Types Available

Enums.OrderType: MARKET, STOP, LIMIT, TRAIL
No native OCO, bracket, or order-group types exist in the SDK.

OrderContext Methods​

Order Creation

- createMarketOrder(action, qty) — immediate fill
- createStopOrder(action, tif, qty, price) — pending stop
- createLimitOrder(action, tif, qty, price) — pending limit
- submitOrders(Order...) / cancelOrders(Order...) — batch operations

Convenience (Market)

- ctx.buy(qty) / ctx.sell(qty) — market entry
- ctx.closeAtMarket() — flatten position

Display-Only(!)

- setStopPrice(Float) / setTargetPrice(Float) — chart visuals, no orders placed

Order Adjustment (Live)

- order.setAdjStopPrice(Float) / order.setAdjLimitPrice(Float) — modify pending orders

Live vs Backtest Behavior​


There is a vast difference in how the backtester handles orders vs. how orders are processed when the strategy is applied to live data. So you might be ending up implementing the same entries in two different ways to make them work under both conditions.

FeatureLiveBacktest (autoEntry=true)
ctx.signal()ProcessedProcessed
ctx.buy()/sell()FilledFilled (at bar close)
ctx.closeAtMarket()FilledFilled
createStopOrder()Sent to brokerIgnored — never fills
createLimitOrder()Sent to brokerIgnored — never fills
setStopPrice/TargetPriceDisplay onlyDisplay only

With manualEntry=true (TradeManager pattern), stop/limit orders ARE evaluated in backtest — but the strategy is hidden from the backtest panel entirely.

How to Handle OCO (Stop + Target) Exits​

The SDK has no built-in OCO. All reference strategies solve this the same way:
Track levels as variables, check OHLC per bar, exit via market order.

Java:
 // In calculate() — guarded by completeBar
  if (isLong && completeBar) {
      if (barL <= stopLevel) {
          ctx.signal(index, Signals.EXIT_LONG, "Stop hit", stopLevel);
          return;
      }
      if (barH >= targetLevel) {
          ctx.signal(index, Signals.EXIT_LONG, "Target hit", targetLevel);
          return;
      }
  }

  // In onBarUpdate() — mid-bar exits for live/replay
  if (isLong) {
      float last = ctx.getLastPrice();
      if (last <= stopLevel || last >= targetLevel) {
          ctx.closeAtMarket();
      }
      setStopPrice((float) stopLevel);
      setTargetPrice((float) targetLevel);
  }

This gives identical behavior in live and backtest. The calculate() path drives backtesting (complete bars only), while onBarUpdate() catches mid-bar exits during live/replay.

Reference Implementations​

See the last available source code of core studies here: https://github.com/MotiveWave/motivewave-studies

Signal-based exits: PSARStrategy.java, SuperTrendStrategy.java
Manual order management: TradeManager.java (uses manualEntry=true)

If anyone has better insights or a valid approach that is not mentioned here, feel free to step in and correct me!
 
Top