Skip to content

Orders, positions, and trades

View Markdown

An Order is a request; a Trade is one filled lot; Position summarizes all open lots. Obtain them from self.buy/sell, self.trades, and self.position. Do not construct these handles yourself.

Order · Position · Trade · OrderRequest · OrderGroup · OrderStatus · Commission · Slippage · fixed_quantity · equity_fraction · stop_risk · SizingDecision

Submit an order

Place these calls inside Strategy.next():

price = float(self.close[-1])
order = self.buy(qty=1, limit=price * 0.99, sl=price * 0.95,
                 tp=price * 1.05, valid_bars=3, tag="pullback")
self.log.info(f"Submitted {order.order_id}: {order.status}")
Argument to buy() / sell() Meaning
qty Positive asset units; must match the instrument quantity step. qty=0.1 means one tenth of a unit.
amount Positive notional budget in settlement currency, excluding fees.
percent Percentage of request-time equity in (0, 100]; 20 means 20%.
limit Absolute limit price: a buy cannot execute above it; a sell cannot execute below it.
stop Absolute trigger price. With limit, creates a stop-limit order.
sl, tp Absolute stop-loss and take-profit prices attached to the filled lot. For a long entry, SL is below entry and TP above it; reversed for shorts.
tag Optional JSON-compatible label/data copied into trading records.
reduce_only Only reduce existing exposure; requires explicit qty.
valid_bars Allow execution on the next n bars, then expire; positive integer.
expires_at Alternative timezone-aware expiry timestamp; the data index must also be aware.

Choose exactly one sizing argument, or omit all to use a configured default. None is not a sizing value. valid_bars and expires_at cannot be combined. Without an expiry, an order remains active until filled, canceled, rejected, or the run ends.

Amounts and percentages convert to quantity using Close/equity at submission. They do not reserve cash; gaps and fees may make an order fail at execution. A returned handle confirms submission, not execution. The default timing fills eligible market orders at the next open.

Cancel, exit, or change protection

# Inside next(): iterate over a snapshot while canceling.
for order in self.orders:
    if order.tag == "pullback":
        order.cancel()

for trade in self.trades:
    if trade.is_long:
        trade.sl = float(self.close[-1]) * 0.95
        trade.tp = None  # Remove take-profit protection.

# Submit exits for every open lot.
exits = self.position.close()

Trade.close(qty=1) exits an explicit quantity. Trade.close(portion=0.5) exits half, rounded toward zero to the quantity step; rounding to zero raises an error. Omit both to exit all. Position.close(portion=...) and self.close_position(portion=...) return tuples of exit orders, empty when flat. They submit requests, so exposure remains until execution.

For a native trailing stop, call trade.set_trailing_stop(amount=5) or trade.set_trailing_stop(percent=0.05); here 0.05 means 5%. Choose one positive distance. Favorable extremes update protection for subsequent bars; a stop does not guarantee the exit price after a gap.

Target a position

Method Example and unit
self.order_target_quantity(target=10) Target net holdings of 10 units.
self.order_target_value(target=2000) Target net notional of 2,000 currency units.
self.order_target_percent(target=0.2) Target net notional of 20% of equity; this method uses a fraction, unlike buy(percent=20).

Targets may be negative for short exposure or zero to exit. They support the same price/expiry/tag conditions as ordinary orders, return Order or None, and require hedging=False. Identical pending targets are reused; changing a target replaces it based on actual holdings. Other pending entry/manual-exit orders conflict with target orders. Target weight is calculated at submission and is not continuously rebalanced.

Submit one-cancels-other entries

from openbacktesting.broker import OrderRequest

def next(self):
    if self.bar_index == 0:
        price = float(self.close[-1])
        self.entry_group = self.submit_oco(
            first=OrderRequest(side="buy", qty=1, stop=price * 1.02),
            second=OrderRequest(side="sell", qty=1, stop=price * 0.98),
        )

Use this next() in a strategy with init() defined. OrderRequest requires side="buy"/"sell" and one explicit qty/amount/percent. Its other arguments follow the ordinary-order table. Submitting the group validates both entries together. Completion or cancellation of either terminates its peer; if both trigger within a bar, first takes priority. group.orders reads the pair and group.cancel() cancels pending members.

Interpret exposure and profit

position.size is signed net quantity. With hedging, equal long/short holdings have zero net size but still have exposure: use bool(position) or position.has_exposure. position.snapshot separates gross/long/short quantities and their average entry prices. Trade.pl and Position.pl include the remaining entry fees; return_fraction uses fractional units and may be None for flat or hedged aggregate exposure.

self.account gives balance, equity, margin, and cumulative fees. Balance includes realized gross P/L minus paid fees; equity adds unrealized gross P/L. self.closed_trade_stats counts closed fragments, so a partial exit can increase its count while a lot remains open.

Costs

from openbacktesting.broker import Commission, Slippage

commission = Commission(fixed=1, relative=0.001, per_unit=0.01)
slippage = Slippage(mode="bps", value=5)

Pass these to Backtest(..., commission=commission, slippage=slippage). Commission adds a fixed fee, a fraction of absolute notional, and a fee per executed unit. Slippage adds either basis points (bps) or a fixed price distance (fixed) in the adverse direction. Fees apply on each actual execution, including exits.

OHLC bars cannot reveal intrabar price order. If a bar reaches both SL and TP, the framework chooses SL and records an ambiguity. Protection from a conditional or same-close entry starts on a later bar. See execution rules for timing and end-of-data behavior.

Order

Stable order handle; request and snapshot are immutable values, and cancel is repeatable.

Attributes

Attribute Meaning
is_contingent Whether this is a lot protection exit.
is_long Whether this is a buy-side request.
is_short Whether this is a sell-side request.
limit Immutable requested limit price.
parent_trade Associated active lot; None after closure.
size Original signed asset quantity.
sl Initial requested stop-loss price.
stop Currently effective trigger price; the original condition remains in request.
tag Independent plain-value copy of the user tag.
tp Initial requested take-profit price.
status Current state; query events for historical states.
request Return a frozen NormalizedOrderRequest with readable side, qty, limit, stop, sl, tp, reduce_only, trade_id, expires_at, valid_bars, tag_json, and tag. tag returns an independent JSON copy. This object observes a registered order; authors use OrderRequest to describe an entry awaiting submission.
snapshot Latest frozen state, without exposing writable state.
group Associated OCO or protection group; None for ordinary standalone orders.
order_id Stable, read-only order identity within each run.
sizing Sizing basis frozen at submission; None for targets, lot exits, and internal protection orders.

Order.cancel

Order.cancel()

Cancel an unfinished order; calls on terminal states are idempotent.

Position

Aggregate active lots; truthiness depends on total exposure.

Attributes

Attribute Meaning
is_long Whether the net position is long.
is_short Whether the net position is short.
pl Unrealized net profit or loss including remaining entry fees.
size Signed net quantity.
has_exposure Whether any lot is still held.
snapshot Frozen snapshot of net position, gross exposure, and fees.
return_fraction Net return fraction; None when a hedged or flat position has no common cost basis.

Position.close

Position.close(portion=None)

Submit reduce-only orders for each lot and return an ordered tuple.

Trade

Entry lot that can be exited and protected separately; partial exits preserve trade_id.

Attributes

Attribute Meaning
entry_time Market-data index value at entry (a timestamp when the data has a DatetimeIndex).
exit_time Market-data index value at exit, or None while open.
is_long Whether this is a long lot.
is_short Whether this is a short lot.
pl Net profit or loss including allocated fees; active lots include remaining entry fees.
size Signed quantity of the remaining or closed portion.
sl Current stop-loss price; writes record cancellation and replacement events.
tag Copy of the entry tag.
tp Current take-profit price; None cancels protection.
value Absolute notional value at the current or exit price.
pl_gross Gross price profit or loss.
return_fraction Net return fraction using remaining or allocated entry notional as the denominator.
group Lot protection group, including historical replaced members.

Trade.close

Trade.close(portion=None, *, qty=None)

Reduce-only exit; qty and portion are mutually exclusive, and the default closes the entire lot.

Trade.set_trailing_stop

Trade.set_trailing_stop(*, amount=None, percent=None)

Create a native trailing stop, replacing the fixed SL; effective from the next eligible bar.

entry_bar — Zero-based bar position at entry.

entry_price — Actual entry price.

exit_bar — Zero-based exit bar, or None while the lot is open.

exit_price — Actual exit price, or None while the lot is open.

OrderRequest

broker.OrderRequest(
    *,
    side,
    qty=...,
    amount=...,
    percent=...,
    limit=None,
    stop=None,
    sl=None,
    tp=None,
    tag=None,
    expires_at=None,
    valid_bars=None,
)

Frozen entry request awaiting submission; construction does not register an order and is allowed during initialization.

Attribute Type Meaning
side Literal['buy', 'sell'] Buy or sell direction.
limit float \| None Absolute limit price, or None for no limit.
stop float \| None Absolute stop trigger, or None for no trigger.
sl float \| None Absolute stop-loss price, or None.
tp float \| None Absolute take-profit price, or None.
expires_at pd.Timestamp \| None Timezone-aware expiry instant, or None.
valid_bars int \| None Maximum eligible future bars, or None for no bar expiry.

Attributes

Attribute Meaning
tag Independent JSON tag copy; modifying it does not change the request.
qty Explicit asset quantity; None in other modes.
amount Explicit notional amount; converted at submission, None in other modes.
percent Explicit equity percentage (20 means 20%); None in other modes.

OrderGroup

Queryable OCO or lot protection group; cancellation does not close positions.

Attributes

Attribute Meaning
orders All orders in this group, including historical terminal orders.

OrderGroup.cancel

OrderGroup.cancel()

Cancel all still-active members without automatically exiting positions.

OrderStatus

Observable order states; terminal states cannot reopen.

Member Value
OrderStatus.SUBMITTED Submitted
OrderStatus.ACCEPTED Accepted
OrderStatus.PARTIALLY_FILLED PartiallyFilled
OrderStatus.FILLED Filled
OrderStatus.CANCELED Canceled
OrderStatus.EXPIRED Expired
OrderStatus.REJECTED Rejected

order.status.terminal is True after a fill, cancellation, expiry, or rejection. Terminal orders cannot reopen; order.cancel() is safe to repeat.

Commission

broker.Commission(fixed=0.0, relative=0.0, per_unit=0.0)

Fixed fee per fill, notional rate, and fee per asset unit; a rate of 0.002 means 0.2%.

Attribute Type Meaning
fixed float Fixed fee per actual fill.
relative float Fraction of execution notional; 0.002 means 0.2%.
per_unit float Fee per executed asset unit.

Commission.snapshot

Commission.snapshot()

Export independent plain values with a model version.

Commission.from_snapshot

Commission.from_snapshot(value)

Restore a complete snapshot strictly; reject unknown versions, fields, and implicit defaults.

Slippage

broker.Slippage(mode='none', value=0)

No slippage, fixed-price slippage, or basis-point slippage; apply once per fill.

Attribute Type Meaning
mode str none, fixed price distance, or bps (basis points).
value float Nonnegative slippage amount in the selected mode.

Slippage.cost

Slippage.cost(price)

Return a nonnegative slippage distance per unit.

Size an order from a budget or stop

The pure helpers in openbacktesting.strategy.sizing return a SizingDecision. Supply reference_price, usually float(self.close[-1]), and instrument=self.instrument. Budget-based helpers also take account=self.account. Submit only when decision.qty > 0; decision.reason explains a zero result.

from openbacktesting.strategy.sizing import stop_risk

def next(self):
    if self.bar_index == 0:
        price = float(self.close[-1])
        stop = price * 0.95
        decision = stop_risk(0.01, account=self.account, reference_price=price,
                             stop_price=stop, side="buy", instrument=self.instrument)
        if decision.qty > 0:
            self.buy(qty=decision.qty, sl=stop)

fixed_quantity(qty, ...) rounds units toward zero; equity_fraction(fraction, ...) spends an equity fraction; stop_risk(fraction, ..., stop_price, side) budgets an equity fraction for the stop distance. cost_per_unit is your nonnegative estimate of costs, not a guarantee of actual fees or gap losses.

fixed_quantity

strategy.sizing.fixed_quantity(qty, *, reference_price, instrument=None, cost_per_unit=0)

Round fixed asset quantities toward zero without inferring capital fractions.

equity_fraction

strategy.sizing.equity_fraction(
    fraction,
    *,
    account,
    reference_price,
    instrument=None,
    cost_per_unit=0,
)

Equity net-notional budget, with quantity costs charged to the same budget; margin fraction does not amplify it.

stop_risk

strategy.sizing.stop_risk(
    fraction,
    *,
    account,
    reference_price,
    stop_price,
    side='buy',
    instrument=None,
    cost_per_unit=0,
)

Stop-loss risk budgets include estimated round-trip costs per unit; gaps may exceed the planned loss.

SizingDecision

Budget result; estimated_cost estimates costs for the given quantity without guaranteeing fill prices.

Attribute Type Meaning
qty float Asset quantity in native units.
reference_price float Price used to calculate the requested quantity.
estimated_cost float Estimated costs for the calculated quantity.
reason str \| None Reason for the recorded decision or termination.
budget float \| None Notional or risk budget; None for a pure quantity request.

Account values and event callbacks

Read self.account for balance/equity/margin, self.position.snapshot for exposure, and self.closed_trade_stats for running closed-fragment statistics. Account snapshots and events explains every returned field and shows on_order, on_fill, and on_finish in use.

Strategy · Results · User manual