---
title: "Market Data Pricing"
description: "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."
---

<!-- markdownlint-disable MD010 MD033 -->
# 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](Spot-Funds.md) policy prices
market orders from the quote cache. For registration, quote buckets, and the
read/write API see [Market Data](Market-Data.md); for quote lifetimes see
[Market Data TTL](Market-Data-TTL.md). For the threading model see
[Threading Contract](Threading-Contract.md).

## Pricing Market Orders

The [spot funds](Spot-Funds.md) 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](Account-Groups.md), 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`.

The publisher supplies the source age with each book snapshot. For example, a
book observed 20 ms earlier is already 20 ms into the TTL budget when a policy
uses it; publishing it now does not reset that age.

<details>
<summary>Go</summary>

<!-- markdownlint-disable-next-line MD013 -->
<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/go/examples_market_data_test.go -->
```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),
    0,
); 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. An instrument-level override
// leaves the account and group ids unset.
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),
    0,
); 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")
}
```

</details>

<details>
<summary>Python</summary>

<!-- markdownlint-disable-next-line MD013 -->
<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/python/tests/integration/test_examples_market_data.py -->
```python
from datetime import timedelta

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"),
    timedelta(),
)

# 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"), timedelta())
rejected = engine.execute_pre_trade(order=market_buy())
assert not rejected.ok
assert rejected.rejects[0].code == openpit.pretrade.RejectCode.MARK_PRICE_UNAVAILABLE
```

</details>

<details>
<summary>JavaScript</summary>

<!-- markdownlint-disable-next-line MD013 -->
<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/js/tests/examples_market_data.test.ts -->
```ts
import { Engine } from "@openpit/engine";
import { QuoteTtl } from "@openpit/engine/marketdata";
import { TradeAmount } from "@openpit/engine/param";
import { type OrderInit } from "@openpit/engine/model";
import { AdjustmentAmount } from "@openpit/engine/param";
import {
  buildSpotFunds,
  SpotFundsOverride,
} from "@openpit/engine/pretrade/policies";

const builder = Engine.builder();

// A shared market-data service feeds the policy's market-order pricing.
const marketData = builder.marketData(QuoteTtl.infinite()).build();
const aaplId = marketData.register({
  underlyingAsset: "AAPL",
  settlementAsset: "USD",
}).value;
marketData.push(aaplId, { mark: "200", bid: "199.5", ask: "200.5" }, 0);

// Price from the top of book; AAPL overrides the global 100 bps slippage to
// zero, so a buy is priced exactly at the ask.
const engine = builder
  .builtin(
    buildSpotFunds().marketData(marketData, 100, "BookTop", [
      new SpotFundsOverride(aaplId, null, null, 0),
    ]),
  )
  .build();

const accountId = 99224416;
engine.applyAccountAdjustment(accountId, [
  {
    operation: { asset: "USD" },
    amount: { balance: AdjustmentAmount.absolute("1000") },
  },
]);

const marketBuy = (): OrderInit => ({
  operation: {
    underlyingAsset: "AAPL",
    settlementAsset: "USD",
    accountId,
    side: "BUY",
    tradeAmount: TradeAmount.quantity("1"),
  },
});

const passed = engine.executePreTrade(marketBuy());
if (!passed.ok) {
  throw new Error("market buy must pass with a complete book quote");
}
const reservation = passed.reservation;
if (reservation === undefined) {
  throw new Error("accepted execute result is missing its reservation");
}
reservation.commit();

marketData.push(aaplId, { mark: "215" }, 0);
const rejected = engine.executePreTrade(marketBuy());
if (rejected.ok || rejected.rejects[0]?.code !== "MarkPriceUnavailable") {
  throw new Error("BookTop must reject a quote without bid and ask");
}
```

</details>

<details>
<summary>C++</summary>

<!-- markdownlint-disable-next-line MD013 -->
<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/cpp/test/wiki/market_data_pricing_test.cpp -->
```cpp
#include <openpit/openpit.hpp>

#include <cassert>
#include <chrono>

// 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(::openpit::param::Asset("AAPL"),
                                      ::openpit::param::Asset("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")),
                       std::chrono::nanoseconds::zero()) ==
       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 = ::openpit::param::Asset("USD");
seed.operation =
    openpit::accountadjustment::Operation::OfBalance(std::move(balanceOp));
openpit::accountadjustment::Amount seedAmount;
seedAmount.balance = openpit::param::AdjustmentAmount::Absolute(
    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")),
                       std::chrono::nanoseconds::zero()) ==
       openpit::marketdata::RegisterStatus::Ok);
openpit::pretrade::ExecuteResult second = engine.ExecutePreTrade(marketBuy());
assert(!second.Passed());
assert(second.rejects[0].code ==
       openpit::pretrade::RejectCode::MarkPriceUnavailable);
```

</details>

<details>
<summary>Rust</summary>

<!-- markdownlint-disable-next-line MD013 -->
<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/crates/openpit/tests/examples_market_data.rs -->
```rust
use std::sync::Arc;
use std::time::Duration;

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<openpit::AccountAdjustmentAmount>,
    >,
>;

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")?),
    Duration::ZERO,
)?;

// 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: AccountAdjustmentAmount::default(),
            operation: AccountAdjustmentBalanceOperation {
                asset: Asset::new("USD")?,
                average_entry_price: 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")?),
    Duration::ZERO,
)?;
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);
```

</details>

## Related Pages

- [Spot Funds](Spot-Funds.md) - the policy that prices market orders from this feed.
- [Market Data](Market-Data.md) - registration, quote buckets, and the read/write
  API.
- [Market Data TTL](Market-Data-TTL.md) - quote freshness and the TTL cascade.
- [Reject Codes](Reject-Codes.md) - the `mark price unavailable` business reject.
