PerpForge
Get started

Concept · Limit Orders

Limit Orders

A limit order is an instruction to buy or sell at a specific price or better — the order only fills if the market reaches that price, unlike a market order which fills immediately at whatever price is available.

Limit Orders

A limit order is an instruction to buy or sell at a specific price or better — the order only fills if the market reaches that price, unlike a market order which fills immediately at whatever price is available.

In plain English

There are two basic order types:

  • Market order: "Buy now, at whatever price the exchange can give me." Fills immediately. You pay a taker fee. The price you get may be slightly different from what you saw (slippage — the gap between expected and actual fill price).

  • Limit order: "Buy only if the price reaches $X or lower (for a long entry)." The order rests on the order book until either the market hits your price (you fill as a maker — see maker taker fees) or you cancel it (you miss the trade).

Why limit orders exist in a backtest context

The current engine uses market orders exclusively: every entry and exit fills at candle.close with no limit price. Adding limit orders would allow:

  1. Maker fee pricing — resting limit orders fill at maker rates (lower cost; see maker taker fees)
  2. Better entries — set a limit slightly below current price to buy dips instead of chasing momentum
  3. Tighter exits — post a limit at your target price instead of filling at whatever the close is

Fill mechanics in a candle-based engine (the OHLC check)

A candle gives four prices: open (first trade), high (peak), low (trough), close (last trade). The engine can check whether a limit price was reached during a candle's range:

Long limit entry at price P:

if (candle.low <= P) → order filled at P (maker), apply makerFeeBps
else → order still pending (missed this candle)

Short limit entry at price P:

if (candle.high >= P) → order filled at P (maker), apply makerFeeBps
else → order still pending

This is exactly the logic already used for TP/SL (take-profit/stop-loss — pre-set exit prices) exits in fill-resolver.ts:26 via overrideFillPrice. That machinery generalizes to entries.

The "missed trade" problem

The critical difference from market orders: a limit order that is never reached produces no trade. If you set a buy limit 0.3% below the current close, and the market never dips, you miss the entry. Over a full backtest, this can eliminate 10–25% of would-have-been trades, changing win rate, profit factor, and Sharpe meaningfully — not just the fee.

This is why maker/taker simulation is a Phase-level upgrade, not a fee-config change.

Intra-candle timing ambiguity

OHLC data tells you whether a candle hit your limit price, but not when. For a 1h candle with low ≤ your limit, the fill could have happened at the candle's open, its midpoint, or one second before close. This is a fundamental limitation of candle-based backtesting.

Common assumption: the order fills at exactly your limit price, at an unspecified point in the candle. This is optimistic — it ignores:

  • Queue position (others may have been ahead of you at that price)
  • Partial fills (not enough size available at that tick)
  • Price overshooting (the market blew through your limit in a spike, meaning you filled far below it)

More sophisticated simulators use tick data (trade-by-trade records) instead of candles to resolve this precisely — but that requires orders-of-magnitude more data storage and compute.

Related

Sources

  • wiki/qa-sessions/2026-06-29-session.md#q2 (first asked here)
  • apps/backend/src/evaluation/position/fill-resolver.ts:26 (overrideFillPrice — existing OHLC-range fill path for TP/SL exits)

Related concepts

See it in a real result →

Put it to the test

Does your idea have a real edge, or just a big number?

Spawn your variant, run it on the same engine, and read the edge-significance verdict — before you risk real money.

Test your own idea — free →Free account, no card