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.Broker — Type
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
trueOrcus.broker — Function
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
3Orcus.status — Function
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
========================================Base.length — Function
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
3651Orcus.cash_history — Function
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)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
2Orcus.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 orderOrcus.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
2Orcus.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
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
1Orcus.position_direction — Function
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
:longposition_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
:longOrcus.unrealized_pnl — Method
unrealized_pnl(B::Broker) -> Float64Total 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.0Orcus.realized_pnl — Method
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.0Positions
Orcus.Position — Type
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 theprice(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.0unless opened under a margin model withinitial_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
falseOrcus.Trade — Type
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.71178523604Orcus.abs_return — Method
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.0Orcus.pct_return — Method
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.0Orcus.log_return — Method
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.0Orcus.value — Method
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.55734786039032Orcus.value — Method
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.71178523604Orcus.realized_pnl — Method
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.0Orcus.is_closed — Function
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
trueOrcus.volume — Method
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.0Orders
Orcus.Order — Type
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
falseOrcus.OrderKind — Type
OrderKindAbstract type for order trigger semantics. A concrete OrderKind determines whether/at what price an Order fills on a given bar via check_trigger.
Orcus.MarketOrder — Type
MarketOrder()Fills unconditionally at price(order.derivative) — the default, unchanged since before order kinds existed.
MarketOrder()
# output
MarketOrder()Orcus.Limit — Type
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)Orcus.Stop — Type
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)Orcus.order — Function
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
falseOrcus.limit_order — Function
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)Orcus.stop_order — Function
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)Orcus.isfulfilled — Function
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
falseOrcus.remaining — Function
remaining(order::Order)Signed quantity still unfilled on order.
Random.seed!(1);
A=asset();
remaining(order(Buy(A,10)))
# output
1.0Orcus.volume — Method
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.0Orcus.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
2Orcus.has_position — Method
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
truePortfolio
Orcus.Portfolio — Type
Orcus.total_value — Function
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.50173968887256Orcus.total_loan — Function
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.0Orcus.has_position — Method
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
trueCost & margin models
Orcus.CostModel — Type
CostModelAbstract 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
trueOrcus.NoCost — Type
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)Orcus.FlatCost — Type
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)Orcus.transaction_cost — Function
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)Orcus.MarginModel — Type
MarginModelAbstract type for margin/leverage models, governing leverage on longs, cash required for shorts, the liquidation threshold, and the per-bar financing rate — see initial_margin_pct, short_margin_rate, maintenance_margin_pct, borrow_rate. Implement a new model by adding methods for all four.
Orcus.NoMargin — Type
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()Orcus.RegTMargin — Type
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.5Orcus.initial_margin_pct — Function
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.0Orcus.maintenance_margin_pct — Function
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.0Orcus.short_margin_rate — Function
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.0Orcus.borrow_rate — Function
borrow_rate(m::MarginModel)Per-bar rate charged on financed-long and short exposure under m.
borrow_rate(NoMargin())
# output
0.0Orcus.BrokerOrcus.CostModelOrcus.FlatCostOrcus.LimitOrcus.MarginModelOrcus.MarketOrderOrcus.NoCostOrcus.NoMarginOrcus.OrderOrcus.OrderKindOrcus.PortfolioOrcus.PositionOrcus.RegTMarginOrcus.StopOrcus.TradeBase.lengthOrcus.abs_returnOrcus.borrow_rateOrcus.brokerOrcus.cash_historyOrcus.has_positionOrcus.has_positionOrcus.initial_margin_pctOrcus.is_closedOrcus.isfulfilledOrcus.limit_orderOrcus.log_returnOrcus.maintenance_margin_pctOrcus.orderOrcus.pct_returnOrcus.place_order!Orcus.position_directionOrcus.process_all!Orcus.process_orders!Orcus.realized_pnlOrcus.realized_pnlOrcus.remainingOrcus.request_to_close!Orcus.request_to_close_all!Orcus.resolve_portfolio!Orcus.short_margin_rateOrcus.statusOrcus.stop_orderOrcus.total_loanOrcus.total_valueOrcus.transaction_costOrcus.unrealized_pnlOrcus.valueOrcus.valueOrcus.volumeOrcus.volume