#[test]
fn example_wiki_market_data_register_push_get() -> Result<(), Box<dyn std::error::Error>> {
use openpit::param::{AccountGroupId, AccountId, Asset, Price};
use openpit::{Engine, Instrument, Quote, QuoteResolution, QuoteTtl};
let service = Engine::builder::<(), (), ()>()
.no_sync()
.market_data(QuoteTtl::Infinite)
.build();
let aapl = Instrument::new(Asset::new("AAPL")?, Asset::new("USD")?);
let aapl_id = service.register(aapl.clone())?;
service.push(
aapl_id,
Quote::new()
.with_mark(Price::from_str("150")?)
.with_bid(Price::from_str("149.5")?)
.with_ask(Price::from_str("150.5")?),
)?;
let account = AccountId::from_u64(1);
let quote = service
.get(
aapl_id,
account,
&None::<AccountGroupId>,
QuoteResolution::AccountThenGroupThenDefault,
)
.expect("quote must be present");
assert_eq!(quote.mark, Some(Price::from_str("150")?));
assert_eq!(quote.bid, Some(Price::from_str("149.5")?));
assert_eq!(service.resolve(&aapl), Some(aapl_id));
Ok(())
}
#[test]
fn example_wiki_market_data_replace_vs_patch() -> Result<(), Box<dyn std::error::Error>> {
use openpit::param::{AccountGroupId, AccountId, Asset, Price};
use openpit::{Engine, Instrument, Quote, QuoteResolution, QuoteTtl};
let service = Engine::builder::<(), (), ()>()
.no_sync()
.market_data(QuoteTtl::Infinite)
.build();
let aapl_id = service.register(Instrument::new(Asset::new("AAPL")?, Asset::new("USD")?))?;
service.push(
aapl_id,
Quote::new()
.with_mark(Price::from_str("100")?)
.with_bid(Price::from_str("99")?)
.with_ask(Price::from_str("101")?),
)?;
service.push_patch(aapl_id, Quote::new().with_mark(Price::from_str("105")?))?;
let account = AccountId::from_u64(1);
let quote = service
.get(
aapl_id,
account,
&None::<AccountGroupId>,
QuoteResolution::AccountThenGroupThenDefault,
)
.expect("quote must be present");
assert_eq!(quote.mark, Some(Price::from_str("105")?));
assert_eq!(quote.bid, Some(Price::from_str("99")?));
assert_eq!(quote.ask, Some(Price::from_str("101")?));
Ok(())
}
#[test]
fn example_wiki_market_data_finite_ttl_hides_stale_quote() -> Result<(), Box<dyn std::error::Error>>
{
use std::time::Duration;
use openpit::param::{AccountGroupId, AccountId, Asset, Price};
use openpit::{Engine, Instrument, Quote, QuoteResolution, QuoteTtl};
let service = Engine::builder::<(), (), ()>()
.no_sync()
.market_data(QuoteTtl::Within(Duration::from_millis(50)))
.build();
let aapl_id = service.register(Instrument::new(Asset::new("AAPL")?, Asset::new("USD")?))?;
let account = AccountId::from_u64(1);
let read = |id| {
service
.get(
id,
account,
&None::<AccountGroupId>,
QuoteResolution::AccountThenGroupThenDefault,
)
.ok()
};
service.push(aapl_id, Quote::new().with_mark(Price::from_str("200")?))?;
assert!(read(aapl_id).is_some());
std::thread::sleep(Duration::from_millis(80));
assert!(read(aapl_id).is_none());
service.push(aapl_id, Quote::new().with_mark(Price::from_str("205")?))?;
let quote = read(aapl_id).expect("quote must be present");
assert_eq!(quote.mark, Some(Price::from_str("205")?));
Ok(())
}
#[test]
fn example_wiki_market_data_clear_then_recover() -> Result<(), Box<dyn std::error::Error>> {
use openpit::param::{AccountGroupId, AccountId, Asset, Price};
use openpit::{Engine, Instrument, Quote, QuoteResolution, QuoteTtl};
let service = Engine::builder::<(), (), ()>()
.no_sync()
.market_data(QuoteTtl::Infinite)
.build();
let aapl_id = service.register(Instrument::new(Asset::new("AAPL")?, Asset::new("USD")?))?;
let account = AccountId::from_u64(1);
let read = |id| {
service
.get(
id,
account,
&None::<AccountGroupId>,
QuoteResolution::AccountThenGroupThenDefault,
)
.ok()
};
service.push(aapl_id, Quote::new().with_mark(Price::from_str("200")?))?;
assert!(read(aapl_id).is_some());
service.clear(aapl_id);
assert!(read(aapl_id).is_none());
service.push(aapl_id, Quote::new().with_mark(Price::from_str("210")?))?;
let quote = read(aapl_id).expect("quote must be present");
assert_eq!(quote.mark, Some(Price::from_str("210")?));
Ok(())
}
#[test]
fn example_wiki_market_data_market_orders_book_top_override(
) -> Result<(), Box<dyn std::error::Error>> {
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();
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")?),
)?;
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,
};
engine.execute_pre_trade(buy("1"))?.commit();
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);
Ok(())
}
#[test]
fn example_wiki_market_data_push_for_fan_out() -> Result<(), Box<dyn std::error::Error>> {
use openpit::param::{AccountGroupId, AccountId, Asset, Price};
use openpit::{Engine, Instrument, Quote, QuoteResolution, QuoteTtl};
let service = Engine::builder::<(), (), ()>()
.no_sync()
.market_data(QuoteTtl::Infinite)
.build();
let aapl_id = service.register(Instrument::new(Asset::new("AAPL")?, Asset::new("USD")?))?;
let group_id = AccountGroupId::from_u32(7)?;
service.push_for(
aapl_id,
Quote::new().with_mark(Price::from_str("150")?),
&[AccountId::from_u64(10), AccountId::from_u64(11)],
&[group_id],
)?;
let quote = service
.get(
aapl_id,
AccountId::from_u64(10),
&None::<AccountGroupId>,
QuoteResolution::AccountOnly,
)
.expect("quote must be present");
assert_eq!(quote.mark, Some(Price::from_str("150")?));
Ok(())
}