Skip to content

Reduce-Only Orders - Preventing Accidental Position Flips

TL;DR

Reduce-only is a safety modifier that ensures an order can only close your existing position, never open a new one or flip you to the opposite side.

  • Only applies to futures/perpetuals (not spot - spot has no "position")
  • Automatically applied to all stop-loss and take-profit orders
  • Protects against race conditions where your position changes while orders are resting
  • If your position shrinks, reduce-only orders are automatically trimmed or cancelled

The Problem: Positions Change While Orders Rest

You might think: "I know I'm LONG 100 BTC, so I'll just SELL 100 BTC to close. Why do I need reduce-only?"

The issue is that your position is not static. It changes as:

  • Other orders fill
  • You manually trade
  • You get partially liquidated
  • Multiple strategies interact

Your resting orders don't know about these changes. Without reduce-only, they can flip you.


Example: The Greedy Take-Profit Trap

Initial Position: LONG 100 BTC @ $100,000

You set multiple take-profits:
  TP1: SELL 40 @ $105,000
  TP2: SELL 40 @ $110,000
  TP3: SELL 40 @ $115,000  ← "I'll scale out"

Total sell quantity: 120 BTC (but you only have 100!)

Price rallies, all three hit:
  TP1 fills → LONG 60
  TP2 fills → LONG 20
  TP3 fills → SHORT 20  ← Flipped!

You meant to take profit, now you're short in a rally.

With reduce-only on TP3:

  TP1 fills → LONG 60
  TP2 fills → LONG 20
  TP3 tries to fill 40, but position is only 20
      → Only 20 sold, position closed to FLAT
      → No flip

Example: The Forgotten Stop-Loss

Position: LONG 100 BTC
Stop-Loss: SELL 100 @ $95,000

A week later, you manually sell 30 BTC at $102,000.
Position: LONG 70 BTC

You forgot to update your stop-loss.

Market dumps to $95,000:
  Stop triggers → SELL 100
  Result: SHORT 30 BTC in a falling market

You went from "protected" to "maximum pain."

With reduce-only:

Market dumps to $95,000:
  Stop triggers → tries to SELL 100
  Position is only 70 → SELL 70
  Result: FLAT (position closed, no flip)

Example: Partial Liquidation + Stop-Loss

Position: LONG 100 BTC @ $100,000 (10x leverage)
Stop-Loss: SELL 100 @ $95,000

Market drops sharply to $96,000.
Liquidation engine partially closes 40 BTC to reduce risk.
Position: LONG 60 BTC

Price recovers to $97,000 briefly, then dumps to $95,000.
Stop triggers → SELL 100 → SHORT 40

You got liquidated AND flipped short. Double whammy.

With reduce-only:

Stop triggers → tries to SELL 100
Position is only 60 → SELL 60
Result: FLAT (safely closed)

Example: Multiple Bots / Strategies

Account runs three strategies:
  Strategy A: Places SELL 50 (trend following)
  Strategy B: Places SELL 50 (mean reversion)
  Strategy C: Places SELL 30 (momentum)

Position: LONG 100 BTC

Total pending sells: 130 BTC

If all fill: SHORT 30 BTC

Each strategy thought it was being conservative,
but they don't coordinate with each other.

With reduce-only on all orders: The engine tracks total pending quantity and trims orders to prevent flip.


How Reduce-Only Actually Works

Rule 1: Orders Must Be On The Opposite Side

Reduce-only orders can only exist on the opposite side of your position:

Position: LONG (positive quantity, you bought)
  ✓ Reduce-only SELL allowed (closes the long)
  ✗ Reduce-only BUY cancelled (would add to long)

Position: SHORT (negative quantity, you sold)
  ✓ Reduce-only BUY allowed (closes the short)
  ✗ Reduce-only SELL cancelled (would add to short)

Position: FLAT (no position)
  ✗ ALL reduce-only orders cancelled (nothing to reduce)

Rule 2: Total Quantity Cannot Exceed Position

The engine tracks the cumulative quantity of all your resting orders on the closing side, ordered by price-time priority.

Position: LONG 100 BTC

Resting SELL orders (ordered by price, then time):
  Order A: SELL 30 @ $105,000 (regular)     Running total: 30 ✓
  Order B: SELL 40 @ $106,000 (regular)     Running total: 70 ✓
  Order C: SELL 50 @ $107,000 (reduce-only) Running total: 120 ✗

Order C would cause total (120) to exceed position (100).
→ Order C is MODIFIED to 30 units (100 - 70 = 30)
→ Or CANCELLED if modification would result in 0

Key insight: Regular orders are counted too! The engine ensures that even if ALL your sells filled in sequence, you wouldn't flip.

Rule 3: Dynamic Updates After Every Fill

Every time your position changes (from any fill), the engine re-evaluates all reduce-only orders:

Position: LONG 100 BTC

Orders:
  SELL 40 @ $105k (reduce-only)
  SELL 40 @ $106k (reduce-only)
  SELL 40 @ $107k (reduce-only)

Step 1: $105k fills
  Position: LONG 60
  Remaining sells: 80 (exceeds 60!)
  → $107k order trimmed to 20

Step 2: $106k fills
  Position: LONG 20
  Remaining sells: 20 (the trimmed $107k order)
  → Exactly matches, no change

Step 3: $107k fills
  Position: FLAT
  → Any remaining reduce-only orders cancelled

Why Stop-Loss and Take-Profit Are Always Reduce-Only

When you attach SL/TP to an order, the system creates them with reduce_only: true automatically. Here's why:

Order: BUY 100 BTC @ $100,000
  with stop_loss @ $95,000
  with take_profit @ $110,000

Scenario A: Order fully fills (100 BTC)
  → SL: SELL 100 @ $95k (reduce-only) ✓
  → TP: SELL 100 @ $110k (reduce-only) ✓
  Works perfectly.

Scenario B: Order partially fills (60 BTC)
  Without reduce-only:
    → SL: SELL 100 @ $95k would flip you SHORT 40

  With reduce-only:
    → SL: SELL 100 @ $95k, but only 60 can fill
    → Position closes to FLAT, no flip

The SL/TP quantity is based on what you requested, but reduce-only ensures it only closes what you actually have.


Implementation Details

The reduce-only logic lives in engine/src/engine/order/mod.rs:

Position Detection

rust
fn get_position_net_quantity_and_side(...) -> (Decimal, Option<Side>) {
    // Positive quantity → LONG (Bid side)
    // Negative quantity → SHORT (Ask side)
    // Zero → FLAT (None)
}

Order Cancellation (Wrong Side)

rust
pub fn update_reduce_only_orders(...) {
    match position_side {
        None => {
            // No position: cancel ALL reduce-only orders
            cancel_resting_reduce_only_orders(Side::Bid, ...);
            cancel_resting_reduce_only_orders(Side::Ask, ...);
        }
        Some(position_side) => {
            // Cancel reduce-only orders on SAME side as position
            // (they would add to position, not reduce it)
            cancel_resting_reduce_only_orders(position_side, ...);

            // Trim orders on opposite side if total exceeds position
            cancel_or_modify_quantity(...);
        }
    }
}

Quantity Trimming

rust
fn cancel_or_modify_quantity(...) {
    let mut total_posted_quantity = Decimal::ZERO;

    for order in orders_by_price_time_priority {
        total_posted_quantity += order.unfilled_quantity();

        if order.is_reduce_only() && total_posted_quantity > position_abs_quantity {
            let excess = total_posted_quantity - position_abs_quantity;
            let new_quantity = order.unfilled_quantity() - excess;

            if new_quantity > 0 {
                order_book.modify(order.id(), new_quantity);  // Trim
            } else {
                order_book.cancel(order.id());  // Cancel entirely
            }
        }
    }
}

Common Misconceptions

"Reduce-only means my order will fully close my position"

Wrong. Reduce-only is a constraint, not a guarantee. It means:

  • Order will close at most your position size
  • Order might partially fill (normal limit order behavior)
  • Order might not fill at all if price never reaches it

"I don't need reduce-only if I track my position carefully"

Wrong. You can't predict:

  • Partial liquidations
  • Other orders filling while this one rests
  • Race conditions in fast markets
  • Mistakes in multi-strategy setups

Reduce-only is cheap insurance.

"Reduce-only works on spot markets"

Wrong. Spot markets don't have "positions" - you just have balances. Reduce-only only makes sense for futures/perpetuals where you have a directional position that can flip.


When to Use Reduce-Only

ScenarioUse Reduce-Only?
Stop-loss ordersYes (automatic)
Take-profit ordersYes (automatic)
Closing a position manuallyYes (safety)
Opening a new positionNo (doesn't make sense)
Adding to existing positionNo (would be cancelled)
Spot tradingN/A (not applicable)

Summary

Reduce-only is a defensive mechanism that accounts for the dynamic nature of positions:

  1. Your position changes - fills, liquidations, manual trades
  2. Your orders don't know - they were placed with old information
  3. Reduce-only adapts - automatically trims or cancels to prevent flips

It's not about knowing your position size now. It's about ensuring your orders behave correctly regardless of what your position is when they fill.

Think of it as a circuit breaker: "No matter what happens, don't flip my position."