Orders & accounting

Orders fill deterministically FIFO in execute!, updating netted Positions (signed net_qty, weighted-average avg_cost, realized_pnl) through the Broker; rejected orders are recorded separately rather than silently dropped, and a CostModel/MarginModel is applied at the fill boundary.

Broker and Order processing

Orcus.BrokerType
Broker(market::Market, cash::Real; cost_model::CostModel=NoCost(), margin_model::MarginModel=NoMargin())

The order-executing unit of the system. Holds cash, a Market, a pluggable transaction cost_model, a pluggable margin_model, a netted portfolio (keyed by instrument_key), the open orders queue (FIFO), the executed-trade history, a list of rejected orders, a list of margin_calls (bar indices where a maintenance breach forced a full liquidation), and the equity_history.

Random.seed!(1234);
x=asset();
y=asset();
M=Market([x,y]);
B = Broker(M,1000);
isa(B,Broker)
# output

true
source
Orcus.brokerFunction
broker(market::Market, cash::Real; cost_model::CostModel=NoCost(), margin_model::MarginModel=NoMargin())
broker(n_assets::Int, cash::Real; cost_model::CostModel=NoCost(), margin_model::MarginModel=NoMargin())

Build a Broker. With a market, wraps it directly. With an asset count, builds a market of that many random assets first.

Random.seed!(1);
B = broker(3, 1000);
length(B.market.assets)
# output

3
source
Orcus.statusFunction
status(B::Broker,digits::Int=2)

Print the status of the Broker

Random.seed!(1234);
x=asset();
y=asset();
M=market([x,y]);
B = broker(M,1000);
status(B)

# output

========================================
Date: 3651
Cash: 1000.0
Number of orders: 0
Number of positions: 0
Number of trades: 0
========================================
source
Base.lengthFunction
length(B::Broker)

Return the length of the market the Broker is operating on.

Random.seed!(1234);
x=asset();
y=asset();
M=market([x,y]);
B=broker(M,1000);
length(B)
# output

3651
source
Orcus.cash_historyFunction
cash_history(B::Broker)

Return the cash history of the Broker as a vector of (date, cash) tuples, one per trade plus the opening and current balance.

Random.seed!(1234);
M=market([asset(),asset()]);
T=Backtest(M,CrossOverStrategy,1000);
run_test(T);
h=cash_history(T.broker);
last(h)
# output

(3651, -18.284421217236527)
source
Orcus.request_to_close_all!Function
request_to_close_all!(B::Broker)

Flag every open position to be closed on the next resolve_portfolio!/process_all!.

Random.seed!(1);
B=broker(3,1000);
A=B.market.assets[2];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
request_to_close_all!(B);
resolve_portfolio!(B);
length(B.history)
# output

2
source
Orcus.place_order!Function
place_order!(B::Broker,O::Order)

Place an Order in the Broker's Orderbook

Random.seed!(1234);
B = broker(3,1000);
A = B.market.assets[2]
O = Order(Buy(A,10))
place_order!(B,O)
B
# output

Broker with 1000.0 funds and 1 open order
source
Orcus.resolve_portfolio!Function
resolve_portfolio!(B::Broker)

Close every position whose requestToClose flag is set, liquidating at the current market value. Each close realizes P&L, applies transaction costs, books the cash, records a closing Trade, and removes the position from the portfolio. Forced — always executes.

Random.seed!(1234);
B = broker(3,1000);
A = B.market.assets[2]
O = Order(Sell(A,10))
place_order!(B,O)
Orcus.process_order!(B,O)
request_to_close_all!(B)
resolve_portfolio!(B)
length(B.history)

# output

2
source
Orcus.process_orders!Function
process_orders!(B::Broker)

Process the whole orderbook in FIFO order via execute!. Orders that fully fill (the whole book, for plain market orders) or are rejected for insufficient funds are removed; resting limit/stop orders that don't trigger on this bar, and partially-filled orders with quantity still outstanding, stay queued for a future bar.

Random.seed!(1234);
B = broker(3,1000);
A = B.market.assets[2]
O = Order(Buy(A,10))
place_order!(B,O)
process_orders!(B)
length(B.history)

# output

1
source
Orcus.process_all!Function
process_all!(B::Broker)

Run one full bar for the broker: process the orderbook, resolve any pending closes, accrue borrow fees and check margin, then record the bar's equity.

Random.seed!(1);
x=asset();
y=asset();
M=market([x,y]);
B=broker(M,1000);
advance_to!(M, 1);
process_all!(B);
length(B.equity_history)
# output

1
source
Orcus.position_directionFunction
position_direction(pf::Portfolio, ticker::String)

Return :long, :short, or :flat for the first open position in ticker.

Random.seed!(1);
B=broker(3,1000);
ticker=B.market.assets[2].ticker;
A=B.market.data[ticker];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
position_direction(B.portfolio, ticker)
# output

:long
source
position_direction(B::Broker, ticker::String)

Return :long, :short, or :flat for the current open position in ticker.

Random.seed!(1);
B=broker(3,1000);
ticker=B.market.assets[2].ticker;
A=B.market.data[ticker];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
position_direction(B, ticker)
# output

:long
source
Orcus.unrealized_pnlMethod
unrealized_pnl(B::Broker) -> Float64

Total unrealized P&L (mark minus cost basis) across all currently open positions.

Random.seed!(1);
B=broker(3,1000);
A=B.market.assets[2];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
unrealized_pnl(B)
# output

-10.0
source
Orcus.realized_pnlMethod
realized_pnl(B::Broker)

Total realized trading P&L, net of all commissions and slippage, across still-open positions.

Random.seed!(1);
B=broker(3,1000);
A=B.market.assets[2];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
realized_pnl(B)
# output

0.0
source

Positions

Orcus.PositionType
Position(D::Derivative)

A netted position in a single instrument (identified by instrument_key). All fills on the same instrument aggregate here:

  • net_qty — signed open quantity; the position is closed when this reaches 0.
  • avg_cost — weighted-average per-unit entry price, in the price(derivative) convention (raw, excluding fees).
  • realized_pnl— realized trading P&L net of all commissions and slippage charged on this instrument.
  • loan — broker-financed dollar amount still owed against this position (0.0 unless opened under a margin model with initial_margin_pct < 1.0).

Fees are not folded into avg_cost (so mark-to-market basis stays clean); they are subtracted from realized_pnl as they occur. Equity (cash + Σ value(P) - Σ loan) is the source of truth and already reflects fees via the cash ledger.

Random.seed!(1);
A = asset()
P = Position(Buy(A, 0))
Orcus.apply_trade!(P, 10.0, value(A), 0.0)   # open 10 @ spot
is_closed(P)
# output

false
source
Orcus.TradeType
Trade(O::Order{D,K}, date::Int=length(O.derivative.underlying)) where {D<:Derivative,K<:OrderKind}
Trade(derivative::D, volume::Real, date::Int, delta_cash::Real=0.0) where {D<:Derivative}

A recorded fill: the derivative traded, signed volume, bar date, and net cash impact.

Random.seed!(1234);
A = asset();
B = Buy(A, 10);
O = order(B, 100);
T = Trade(O);
value(T)
# output

6887.71178523604
source
Orcus.abs_returnMethod
abs_return(P::Position)

Unrealized P&L: current value minus cost basis.

Random.seed!(1);
A=asset();
P=Position(Buy(A,0));
Orcus.apply_trade!(P, 10.0, value(A), 0.0);
abs_return(P)
# output

0.0
source
Orcus.pct_returnMethod
pct_return(P::Position)

Unrealized P&L as a fraction of the cost basis (0.0 if the cost basis is 0.0).

Random.seed!(1);
A=asset();
P=Position(Buy(A,0));
Orcus.apply_trade!(P, 10.0, value(A), 0.0);
pct_return(P)
# output

0.0
source
Orcus.log_returnMethod
log_return(P::Position)

Log return of current value over cost basis (-Inf if the value is non-positive).

Random.seed!(1);
A=asset();
P=Position(Buy(A,0));
Orcus.apply_trade!(P, 10.0, value(A), 0.0);
log_return(P)
# output

0.0
source
Orcus.valueMethod
value(P::Position)

Current mark-to-market value of the position, in the broker's base currency.

Random.seed!(1);
A=asset();
P=Position(Buy(A,0));
Orcus.apply_trade!(P, 10.0, value(A), 0.0);
value(P)
# output

94.55734786039032
source
Orcus.valueMethod
value(T::Trade)

Current mark-to-market value of the trade's volume at its derivative's current value.

Random.seed!(1234);
A = asset();
B = Buy(A, 10);
O = order(B, 100);
T = Trade(O);
value(T)
# output

6887.71178523604
source
Orcus.realized_pnlMethod
realized_pnl(P::Position)

Realized trading P&L on the position, net of commissions and slippage.

Random.seed!(1);
A=asset();
P=Position(Buy(A,0));
Orcus.apply_trade!(P, 10.0, value(A), 0.0);
realized_pnl(P)
# output

0.0
source
Orcus.is_closedFunction
is_closed(P::Position)

Whether the position's net quantity is zero.

Random.seed!(1);
A=asset();
P=Position(Buy(A,0));
is_closed(P)
# output

true
source
Orcus.volumeMethod
volume(P::Position)

The position's signed net quantity.

Random.seed!(1);
A=asset();
P=Position(Buy(A,0));
Orcus.apply_trade!(P, 10.0, value(A), 0.0);
volume(P)
# output

10.0
source

Orders

Orcus.OrderType
Order(derivative::Derivative, volume::Real=1, kind::OrderKind=MarketOrder(); allow_partial::Bool=false)

An order to buy or sell a derivative, not yet fulfilled. volume is the original requested quantity and is never mutated; remaining tracks the unfilled quantity as partial fills occur.

A=asset();
B=Buy(A,10);
O=order(B);
O.fulfilled
# output

false
source
Orcus.OrderKindType
OrderKind

Abstract type for order trigger semantics. A concrete OrderKind determines whether/at what price an Order fills on a given bar via check_trigger.

source
Orcus.MarketOrderType
MarketOrder()

Fills unconditionally at price(order.derivative) — the default, unchanged since before order kinds existed.

MarketOrder()
# output

MarketOrder()
source
Orcus.LimitType
Limit(price::Real)

Resting order that only fills once the bar's range reaches a price at least as good as price (in the underlying asset's own price units). Fills at the better of the bar's open and price.

Limit(100.0)
# output

Limit(100.0)
source
Orcus.StopType
Stop(price::Real)

Resting order that fills once the bar's range breaches price (in the underlying asset's own price units) — the mirror image of Limit. Fills at the worse of the bar's open and price.

Stop(95.0)
# output

Stop(95.0)
source
Orcus.orderFunction
order(derivative::Derivative, volume::Real=1, kind::OrderKind=MarketOrder(); allow_partial::Bool=false)

Convenience constructor for an Order.

Random.seed!(1);
A=asset();
order(Buy(A,10)).fulfilled
# output

false
source
Orcus.limit_orderFunction
limit_order(derivative::Derivative, volume::Real, price::Real; allow_partial::Bool=false)

Convenience constructor for an Order with a Limit kind.

Random.seed!(1);
A=asset();
limit_order(Buy(A,10), 10, 95.0).kind
# output

Limit(95.0)
source
Orcus.stop_orderFunction
stop_order(derivative::Derivative, volume::Real, price::Real; allow_partial::Bool=false)

Convenience constructor for an Order with a Stop kind.

Random.seed!(1);
A=asset();
stop_order(Buy(A,10), 10, 95.0).kind
# output

Stop(95.0)
source
Orcus.isfulfilledFunction
isfulfilled(order::Order)

Returns true if the Order has been fulfilled.

Random.seed!(1234);
A=asset();
B=Buy(A,10);
O=Order(B,10);
isfulfilled(O)
# output

false
source
Orcus.remainingFunction
remaining(order::Order)

Signed quantity still unfilled on order.

Random.seed!(1);
A=asset();
remaining(order(Buy(A,10)))
# output

1.0
source
Orcus.volumeMethod
volume(order::Order)

The order's original requested quantity (never mutated as fills occur).

Random.seed!(1);
A=asset();
volume(order(Buy(A,10), 5))
# output

5.0
source
Orcus.request_to_close!Function
request_to_close!(B::Broker, ticker::String)

Mark all open positions for ticker to be closed on the next process_all! call.

Random.seed!(1);
B=broker(3,1000);
ticker=B.market.assets[2].ticker;
A=B.market.data[ticker];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
request_to_close!(B, ticker);
resolve_portfolio!(B);
length(B.history)
# output

2
source
Orcus.has_positionMethod
has_position(B::Broker, ticker::String)

Return true if the broker currently holds any open position (long or short) in ticker.

Random.seed!(1);
B=broker(3,1000);
ticker=B.market.assets[2].ticker;
A=B.market.data[ticker];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
has_position(B, ticker)
# output

true
source

Portfolio

Orcus.total_valueFunction
total_value(pf::Portfolio)

Sum of value(P) over all open positions.

Random.seed!(1);
B=broker(3,1000);
A=B.market.assets[2];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
total_value(B.portfolio)
# output

22.50173968887256
source
Orcus.total_loanFunction
total_loan(pf::Portfolio)

Sum of P.loan over all open positions — the aggregate broker-financed debt outstanding. Always 0.0 unless positions were opened under a margin model with initial_margin_pct < 1.0.

Random.seed!(1);
B=broker(3,1000);
A=B.market.assets[2];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
total_loan(B.portfolio)
# output

0.0
source
Orcus.has_positionMethod
has_position(pf::Portfolio, ticker::String)

Return true if the portfolio holds any open position (long or short) in ticker.

Random.seed!(1);
B=broker(3,1000);
ticker=B.market.assets[2].ticker;
A=B.market.data[ticker];
O=Order(Buy(A,10));
place_order!(B,O);
Orcus.process_order!(B,O);
has_position(B.portfolio, ticker)
# output

true
source

Cost & margin models

Orcus.CostModelType
CostModel

Abstract type for transaction-cost models. A cost model maps the gross notional of a fill to a (commission, slippage) pair, both expressed as positive cash amounts that always worsen the fill (commission is paid, slippage is an adverse price move).

Implement a new model by adding a transaction_cost(::MyCostModel, notional) method.

NoCost() isa CostModel
# output

true
source
Orcus.NoCostType
NoCost()

Frictionless fills — zero commission, zero slippage. The default so existing backtests behave exactly as before.

transaction_cost(NoCost(), 1000.0)
# output

(commission = 0.0, slippage = 0.0)
source
Orcus.FlatCostType
FlatCost(; commission_pct=0.0, slippage_bps=0.0)

Flat proportional cost model.

  • commission_pct — commission as a fraction of |notional| (e.g. 0.001 = 10 bps).
  • slippage_bps — adverse fill as basis points of |notional| (e.g. 5.0 = 5 bps).

Both are charged on every fill regardless of trade direction.

fc=FlatCost(commission_pct=0.001, slippage_bps=5.0);
transaction_cost(fc, 1000.0)
# output

(commission = 1.0, slippage = 0.5)
source
Orcus.transaction_costFunction
transaction_cost(model::CostModel, notional::Real)

Return the (commission, slippage) cash amounts for a fill of the given gross notional. Both values are non-negative.

transaction_cost(NoCost(), 1000.0)
# output

(commission = 0.0, slippage = 0.0)
source
Orcus.NoMarginType
NoMargin()

No leverage, no liquidation, no borrow fee — the default, so existing backtests behave exactly as before margin models existed. Longs are fully cash-secured (initial_margin_pct == 1.0); shorts are unconstrained (short_margin_rate == 0.0, today's exact behavior); the maintenance check and borrow-fee accrual are no-ops.

NoMargin()
# output

NoMargin()
source
Orcus.RegTMarginType
RegTMargin(initial_pct::Real, maintenance_pct::Real, borrow_rate::Real)

A configurable leverage model — not a regulatory-accurate Reg-T implementation, just named after the familiar initial/maintenance-margin shape. initial_pct sets leverage for both long opens (e.g. 0.5 → 2x leverage) and the cash required to open a short; maintenance_pct sets the liquidation threshold; borrow_rate is the per-bar financing rate.

m=RegTMargin(0.5, 0.25, 0.05);
initial_margin_pct(m)
# output

0.5
source
Orcus.initial_margin_pctFunction
initial_margin_pct(m::MarginModel)

Fraction of notional that must be cash-secured on a same-direction long open/add under m.

initial_margin_pct(NoMargin())
# output

1.0
source
Orcus.maintenance_margin_pctFunction
maintenance_margin_pct(m::MarginModel)

Fraction of gross open position value that account equity must stay above under m before the whole book is liquidated.

maintenance_margin_pct(NoMargin())
# output

0.0
source
Orcus.short_margin_rateFunction
short_margin_rate(m::MarginModel)

Fraction of notional that must be on hand to open/add a short position under m.

short_margin_rate(NoMargin())
# output

0.0
source
Orcus.borrow_rateFunction
borrow_rate(m::MarginModel)

Per-bar rate charged on financed-long and short exposure under m.

borrow_rate(NoMargin())
# output

0.0
source