Market Data Pricing¶
The market-data service is the price source for policies that value orders before they execute. This page shows how the spot funds policy prices market orders from the quote cache. For registration, quote buckets, and the read/write API see Market Data; for quote lifetimes see Market Data TTL. For the threading model see Threading Contract.
Pricing Market Orders¶
The spot funds policy reads this service to price market orders. It
can price from the quote mark, or from the top of book - the ask for a buy and
the bid for a sell - with slippage overrides that can be scoped per instrument,
per account, or per account group, resolved in the order
account -> group -> instrument -> global. With the book top source there is no
fallback to mark: once a replace drops the ask, a market buy is rejected with
mark price unavailable.
Go
eb := openpit.NewEngineBuilder().FullSync()
// A shared market-data service feeds the policy's market-order pricing.
marketData, err := eb.MarketData(marketdata.InfiniteTTL()).Build()
if err != nil {
panic(err)
}
defer marketData.Close()
aapl, _ := param.NewAsset("AAPL")
usd, _ := param.NewAsset("USD")
instrument := param.NewInstrument(aapl, usd)
aaplID, err := marketData.Register(instrument)
if err != nil {
panic(err)
}
mark, _ := param.NewPriceFromString("200")
bid, _ := param.NewPriceFromString("199.5")
ask, _ := param.NewPriceFromString("200.5")
if err := marketData.Push(
aaplID,
marketdata.NewQuote().WithMark(mark).WithBid(bid).WithAsk(ask),
); err != nil {
panic(err)
}
// Price from the top of book; AAPL overrides the global 100 bps slippage to
// zero, so a buy is priced exactly at the ask.
engine, err := eb.
Builtin(
policies.BuildSpotFunds().
WithMarketOrders(marketData, 100).
PricingSource(policies.SpotFundsPricingSourceBookTop).
Overrides(
policies.SpotFundsOverrideEntry{
Target: policies.SpotFundsOverrideTargetInstrument{
Instrument: aaplID,
},
Override: policies.SpotFundsOverride{
SlippageBps: optional.Some(uint16(0)),
},
},
),
).
Build()
if err != nil {
panic(err)
}
defer engine.Stop()
accountID := param.NewAccountIDFromUint64(99224416)
total, _ := param.NewPositionSizeFromString("1000")
seed, _ := model.NewAccountAdjustmentFromValues(model.AccountAdjustmentValues{
BalanceOperation: optional.Some(
model.NewAccountAdjustmentBalanceOperationFromValues(
model.AccountAdjustmentBalanceOperationValues{Asset: optional.Some(usd)},
),
),
Amount: optional.Some(
model.NewAccountAdjustmentAmountFromValues(model.AccountAdjustmentAmountValues{
Balance: optional.Some(param.NewAbsoluteAdjustmentAmount(total)),
}),
),
})
if _, _, err := engine.ApplyAccountAdjustment(
accountID, []model.AccountAdjustment{seed},
); err != nil {
panic(err)
}
buy := func() model.Order {
order := model.NewOrder()
op := order.EnsureOperationView()
op.SetInstrument(instrument)
op.SetAccountID(accountID)
op.SetSide(param.SideBuy)
qty, _ := param.NewQuantityFromString("1")
op.SetTradeAmount(param.NewQuantityTradeAmount(qty))
return order
}
// Market buy (no price): priced at the ask 200.5, which the balance covers.
reservation, execRejects, err := engine.ExecutePreTrade(buy())
if err != nil {
panic(err)
}
if execRejects != nil {
panic("unexpected rejects on first market buy")
}
reservation.CommitAndClose()
// Replace with a mark-only quote: bid and ask are gone, so BookTop can no
// longer price a buy and the next market order is rejected.
replaced, _ := param.NewPriceFromString("215")
if err := marketData.Push(
aaplID,
marketdata.NewQuote().WithMark(replaced),
); err != nil {
panic(err)
}
_, execRejects, err = engine.ExecutePreTrade(buy())
if err != nil {
panic(err)
}
if len(execRejects) == 0 ||
execRejects[0].Code != reject.CodeMarkPriceUnavailable {
panic("expected MarkPriceUnavailable reject")
}
Python
import openpit
import openpit.marketdata
import openpit.pretrade.policies
builder = openpit.Engine.builder().no_sync()
# A shared market-data service feeds the policy's market-order pricing.
market_data = builder.market_data(openpit.marketdata.QuoteTtl.infinite()).build()
aapl = openpit.Instrument("AAPL", "USD")
aapl_id = market_data.register(aapl)
market_data.push(
aapl_id,
openpit.marketdata.Quote(mark="200", bid="199.5", ask="200.5"),
)
# Price from the top of book; AAPL overrides the global 100 bps slippage to
# zero, so a buy is priced exactly at the ask.
engine = builder.builtin(
openpit.pretrade.policies.build_spot_funds().market_data(
market_data,
global_slippage_bps=100,
pricing_source=openpit.pretrade.policies.SpotFundsPricingSource.BOOK_TOP,
overrides=[
openpit.pretrade.policies.SpotFundsOverrideEntry(
target=(
openpit.pretrade.policies.SpotFundsOverrideTargetInstrument(
instrument=aapl_id
)
),
override=openpit.pretrade.policies.SpotFundsOverride(
slippage_bps=0,
),
)
],
)
).build()
account_id = openpit.param.AccountId.from_int(99224416)
seed = openpit.AccountAdjustment(
operation=openpit.AccountAdjustmentBalanceOperation(asset="USD"),
amount=openpit.AccountAdjustmentAmount(
balance=openpit.param.AdjustmentAmount.absolute(
openpit.param.PositionSize(1000)
)
),
)
engine.apply_account_adjustment(account_id=account_id, adjustments=[seed])
def market_buy() -> openpit.Order:
return openpit.Order(
operation=openpit.OrderOperation(
instrument=aapl,
account_id=account_id,
side=openpit.param.Side.BUY,
trade_amount=openpit.param.TradeAmount.quantity("1"),
price=None,
),
)
# Market buy (no price): priced at the ask 200.5, which the balance covers.
passed = engine.execute_pre_trade(order=market_buy())
assert passed.ok
passed.reservation.commit()
# Replace with a mark-only quote: bid and ask are gone, so BookTop can no
# longer price a buy and the next market order is rejected.
market_data.push(aapl_id, openpit.marketdata.Quote(mark="215"))
rejected = engine.execute_pre_trade(order=market_buy())
assert not rejected.ok
assert rejected.rejects[0].code == openpit.pretrade.RejectCode.MARK_PRICE_UNAVAILABLE
C++
#include <cassert>
// A shared market-data service feeds the policy's market-order pricing.
openpit::EngineBuilder builder(openpit::SyncPolicy::None);
openpit::marketdata::Service marketData =
openpit::marketdata::Builder::FromEngineSyncPolicy(
openpit::marketdata::QuoteTtl::Infinite(), openpit::SyncPolicy::None)
.Build();
const openpit::model::Instrument aapl("AAPL", "USD");
const openpit::marketdata::RegisterResult registration =
marketData.Register(aapl);
assert(registration.status == openpit::marketdata::RegisterStatus::Ok);
assert(registration.instrumentId.has_value());
const openpit::marketdata::InstrumentId aaplId =
registration.instrumentId.value();
assert(marketData.Push(aaplId,
openpit::marketdata::Quote()
.WithMark(openpit::param::Price::FromString("200"))
.WithBid(
openpit::param::Price::FromString("199.5"))
.WithAsk(
openpit::param::Price::FromString("200.5"))) ==
openpit::marketdata::RegisterStatus::Ok);
// Price from the top of book; AAPL overrides the global 100 bps slippage to
// zero, so a buy is priced exactly at the ask.
openpit::pretrade::policies::SpotFundsOverride aaplOverride(aaplId);
aaplOverride.slippageBps = 0;
openpit::pretrade::policies::SpotFundsPolicy{}
.WithMarketOrders(marketData, 100)
.PricingSource(openpit::pretrade::policies::SpotFundsPricingSource::BookTop)
.Override(aaplOverride)
.AddTo(builder);
openpit::Engine engine = builder.Build();
const openpit::param::AccountId accountId =
openpit::param::AccountId::FromUint64(99224416);
openpit::accountadjustment::AccountAdjustment seed;
openpit::accountadjustment::BalanceOperation balanceOp;
balanceOp.asset = "USD";
seed.operation =
openpit::accountadjustment::Operation::OfBalance(std::move(balanceOp));
openpit::accountadjustment::Amount seedAmount;
seedAmount.balance = openpit::param::AdjustmentAmount::OfAbsolute(
openpit::param::PositionSize::FromString("1000"));
seed.amount = std::move(seedAmount);
assert(engine
.ApplyAccountAdjustment(
accountId,
std::vector<openpit::accountadjustment::AccountAdjustment>{seed})
.Passed());
auto marketBuy = [&]() {
return openpit::model::Order::Market(
aapl, accountId, openpit::model::Side::Buy,
openpit::model::TradeAmount::OfQuantity(
openpit::param::Quantity::FromString("1")));
};
// Market buy (no price): priced at the ask 200.5, which the balance covers.
openpit::pretrade::ExecuteResult first = engine.ExecutePreTrade(marketBuy());
assert(first.Passed());
first.reservation->Commit();
// Replace with a mark-only quote: bid and ask are gone, so BookTop can no
// longer price a buy and the next market order is rejected.
assert(marketData.Push(aaplId,
openpit::marketdata::Quote().WithMark(
openpit::param::Price::FromString("215"))) ==
openpit::marketdata::RegisterStatus::Ok);
openpit::pretrade::ExecuteResult second = engine.ExecutePreTrade(marketBuy());
assert(!second.Passed());
assert(second.rejects[0].code ==
openpit::pretrade::RejectCode::MarkPriceUnavailable);
Rust
use std::sync::Arc;
use openpit::param::{
AccountId, AdjustmentAmount, Asset, PositionSize, Price, Quantity, Side, TradeAmount,
};
use openpit::pretrade::policies::{SpotFundsPolicy, SpotFundsSettings};
use openpit::pretrade::RejectCode;
use openpit::{
AccountAdjustmentAmount,
AccountAdjustmentBalanceOperation,
AccountAdjustmentBounds,
Engine,
FullSync,
Instrument,
OrderOperation,
Quote,
QuoteTtl,
SpotFundsMarketData,
SpotFundsOverride,
SpotFundsOverrideTarget,
SpotFundsPricingSource,
WithAccountAdjustmentAmount,
WithAccountAdjustmentBalanceOperation,
WithAccountAdjustmentBounds,
WithExecutionReportFillDetails,
WithExecutionReportOperation,
};
type SpotReport = WithExecutionReportOperation<WithExecutionReportFillDetails<()>>;
type SpotAdjustment = WithAccountAdjustmentAmount<
WithAccountAdjustmentBounds<WithAccountAdjustmentBalanceOperation<()>>,
>;
let builder = Engine::builder::<OrderOperation, SpotReport, SpotAdjustment>().full_sync();
// A shared market-data service feeds the policy's market-order pricing.
let market_data = builder.market_data(QuoteTtl::Infinite).build();
let aapl = Instrument::new(Asset::new("AAPL")?, Asset::new("USD")?);
let aapl_id = market_data.register(aapl.clone())?;
market_data.push(
aapl_id,
Quote::new()
.with_mark(Price::from_str("200")?)
.with_bid(Price::from_str("199.5")?)
.with_ask(Price::from_str("200.5")?),
)?;
// Price market orders from the top of book (ask for buys, bid for sells).
// The global slippage is 100 bps, but AAPL overrides it to zero, so a buy
// is priced exactly at the ask.
let settings = SpotFundsSettings::new(
100,
SpotFundsPricingSource::BookTop,
[(
SpotFundsOverrideTarget::Instrument(aapl_id),
SpotFundsOverride {
slippage_bps: Some(0),
},
)],
)?;
let bundle = SpotFundsMarketData::new(Arc::clone(&market_data));
let policy = SpotFundsPolicy::<FullSync, FullSync>::new(
settings,
Some(bundle),
builder.storage_builder(),
);
let engine = builder.pre_trade(policy).build()?;
let account = AccountId::from_u64(99224416);
let seed = WithAccountAdjustmentAmount {
inner: WithAccountAdjustmentBounds {
inner: WithAccountAdjustmentBalanceOperation {
inner: (),
operation: AccountAdjustmentBalanceOperation {
asset: Asset::new("USD")?,
average_entry_price: None,
realized_pnl: None,
},
},
bounds: AccountAdjustmentBounds::default(),
},
amount: AccountAdjustmentAmount {
balance: Some(AdjustmentAmount::Absolute(PositionSize::from_str("1000")?)),
held: None,
incoming: None,
},
};
engine.apply_account_adjustment(account, &[seed])?;
let buy = |quantity: &str| OrderOperation {
instrument: aapl.clone(),
account_id: account,
side: Side::Buy,
trade_amount: TradeAmount::Quantity(
Quantity::from_str(quantity).expect("valid quantity"),
),
price: None,
};
// Market buy (no price): priced at the ask 200.5 because the override
// pins slippage to zero. The seeded balance covers it, so it passes.
engine.execute_pre_trade(buy("1"))?.commit();
// A full replace that carries only the mark drops bid and ask. With the
// BookTop source there is no ask to price a buy, so it is rejected.
market_data.push(aapl_id, Quote::new().with_mark(Price::from_str("215")?))?;
let rejects = match engine.execute_pre_trade(buy("1")) {
Ok(_) => panic!("market buy must reject when the ask is missing"),
Err(rejects) => rejects,
};
assert_eq!(rejects[0].code, RejectCode::MarkPriceUnavailable);
Related Pages¶
- Spot Funds - the policy that prices market orders from this feed.
- Market Data - registration, quote buckets, and the read/write API.
- Market Data TTL - quote freshness and the TTL cascade.
- Reject Codes - the
mark price unavailablebusiness reject.