use std::future::Future;
use crate::{
Book, Candle, CandleQuery, ClassifiedError, EventGroup, FundingRate, Instrument, MarketKey,
Quote, Trade, UnixMillis,
};
#[dynosaur::dynosaur(pub DynMarketData = dyn(box) MarketData)]
pub trait MarketData: Send + Sync {
fn instrument(
&self,
key: &MarketKey,
) -> impl Future<Output = Result<Instrument, ClassifiedError>> + Send;
fn quote(&self, key: &MarketKey)
-> impl Future<Output = Result<Quote, ClassifiedError>> + Send;
fn book(&self, key: &MarketKey) -> impl Future<Output = Result<Book, ClassifiedError>> + Send;
}
#[dynosaur::dynosaur(pub DynInstruments = dyn(box) Instruments)]
pub trait Instruments: Send + Sync {
fn instruments(&self) -> impl Future<Output = Result<Vec<Instrument>, ClassifiedError>> + Send;
}
#[dynosaur::dynosaur(pub DynTrades = dyn(box) Trades)]
pub trait Trades: Send + Sync {
fn trades(
&self,
key: &MarketKey,
) -> impl Future<Output = Result<Vec<Trade>, ClassifiedError>> + Send;
}
#[dynosaur::dynosaur(pub DynCandles = dyn(box) Candles)]
pub trait Candles: Send + Sync {
fn candles(
&self,
key: &MarketKey,
query: &CandleQuery,
) -> impl Future<Output = Result<Vec<Candle>, ClassifiedError>> + Send;
fn intervals(&self) -> &'static [crate::CandleInterval] {
crate::CandleInterval::ALL
}
}
#[dynosaur::dynosaur(pub DynFunding = dyn(box) Funding)]
pub trait Funding: Send + Sync {
fn funding_rate(
&self,
key: &MarketKey,
) -> impl Future<Output = Result<FundingRate, ClassifiedError>> + Send;
fn funding_history(
&self,
key: &MarketKey,
start: UnixMillis,
) -> impl Future<Output = Result<Vec<FundingRate>, ClassifiedError>> + Send;
}
#[dynosaur::dynosaur(pub DynEventGroups = dyn(box) EventGroups)]
pub trait EventGroups: Send + Sync {
fn event_group(
&self,
key: &MarketKey,
) -> impl Future<Output = Result<EventGroup, ClassifiedError>> + Send;
}
pub trait PerpetualMarket: MarketData + Funding {}
impl<T: MarketData + Funding> PerpetualMarket for T {}
pub trait EventContractMarket: MarketData + EventGroups {}
impl<T: MarketData + EventGroups> EventContractMarket for T {}
pub trait SpotMarket: MarketData {}
impl<T: MarketData> SpotMarket for T {}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use std::sync::Arc;
use rust_decimal::Decimal;
use super::*;
use crate::{
CandleInterval, Class, EventOutcome, KeySpace, Level, NotListed, ProductClass, ProductId,
SizeUnit, VenueId,
};
struct Spot;
impl KeySpace for Spot {
const VENUE: VenueId = VenueId::from_static("acme");
const PRODUCT: ProductId = ProductId::from_static("spot");
const CLASS: ProductClass = ProductClass::PerpetualFuture;
type Id = String;
fn parse_id(text: &str) -> Result<String, String> {
Ok(text.to_owned())
}
fn id_text(id: &String) -> Cow<'_, str> {
Cow::Borrowed(id)
}
}
struct Acme;
impl Acme {
fn check(key: &MarketKey) -> Result<String, ClassifiedError> {
let id = Spot::id(key)?;
if id != "ABC" {
return Err(NotListed::new(key.clone()).into());
}
Ok(id)
}
}
impl MarketData for Acme {
async fn instrument(&self, key: &MarketKey) -> Result<Instrument, ClassifiedError> {
let id = Self::check(key)?;
Ok(Instrument::new(key.clone(), id, SizeUnit::Base))
}
async fn quote(&self, key: &MarketKey) -> Result<Quote, ClassifiedError> {
Self::check(key)?;
let mut quote = Quote::new(key.clone());
quote.bid = Some(Level::new(Decimal::ONE, Decimal::TWO));
Ok(quote)
}
async fn book(&self, key: &MarketKey) -> Result<Book, ClassifiedError> {
Self::check(key)?;
let mut book = Book::new(key.clone());
book.bids.push(Level::new(Decimal::ONE, Decimal::TWO));
Ok(book)
}
}
impl Instruments for Acme {
async fn instruments(&self) -> Result<Vec<Instrument>, ClassifiedError> {
let key = Spot::key(&"ABC".to_owned());
Ok(vec![self.instrument(&key).await?])
}
}
impl Trades for Acme {
async fn trades(&self, key: &MarketKey) -> Result<Vec<Trade>, ClassifiedError> {
Self::check(key)?;
Ok(vec![Trade::new(
key.clone(),
Decimal::ONE,
Decimal::ONE,
UnixMillis(1),
)])
}
}
impl Candles for Acme {
async fn candles(
&self,
key: &MarketKey,
query: &CandleQuery,
) -> Result<Vec<Candle>, ClassifiedError> {
Self::check(key)?;
Ok(vec![Candle::new(
key.clone(),
query.interval,
query.start,
Decimal::ONE,
)])
}
}
impl Funding for Acme {
async fn funding_rate(&self, key: &MarketKey) -> Result<FundingRate, ClassifiedError> {
Self::check(key)?;
Ok(FundingRate::new(key.clone(), Decimal::ONE, UnixMillis(1)))
}
async fn funding_history(
&self,
key: &MarketKey,
start: UnixMillis,
) -> Result<Vec<FundingRate>, ClassifiedError> {
Self::check(key)?;
Ok(vec![FundingRate::new(key.clone(), Decimal::ONE, start)])
}
}
impl EventGroups for Acme {
async fn event_group(&self, key: &MarketKey) -> Result<EventGroup, ClassifiedError> {
Self::check(key)?;
let mut group = EventGroup::new("E");
group
.outcomes
.push(EventOutcome::new(key.clone(), "M", "Yes"));
Ok(group)
}
}
fn abc() -> MarketKey {
Spot::key(&"ABC".to_owned())
}
fn ready<F: Future>(future: F) -> F::Output {
use std::pin::pin;
use std::task::{Context, Poll, Waker};
let mut future = pin!(future);
match future
.as_mut()
.poll(&mut Context::from_waker(Waker::noop()))
{
Poll::Ready(output) => output,
Poll::Pending => panic!("the test venue never waits"),
}
}
#[test]
fn every_trait_is_send_and_sync_and_held_through_its_dyn_wrapper() {
fn send_sync<T: Send + Sync + ?Sized>() {}
send_sync::<DynMarketData<'static>>();
send_sync::<DynInstruments<'static>>();
send_sync::<DynTrades<'static>>();
send_sync::<DynCandles<'static>>();
send_sync::<DynFunding<'static>>();
send_sync::<DynEventGroups<'static>>();
let market: Arc<DynMarketData<'static>> = DynMarketData::new_arc(Acme);
let listing: Arc<DynInstruments<'static>> = DynInstruments::new_arc(Acme);
let trades: Arc<DynTrades<'static>> = DynTrades::new_arc(Acme);
let candles: Arc<DynCandles<'static>> = DynCandles::new_arc(Acme);
let funding: Arc<DynFunding<'static>> = DynFunding::new_arc(Acme);
let groups: Arc<DynEventGroups<'static>> = DynEventGroups::new_arc(Acme);
let key = abc();
assert_eq!(
ready(market.quote(&key)).unwrap().bid,
Some(Level::new(Decimal::ONE, Decimal::TWO))
);
assert_eq!(ready(market.book(&key)).unwrap().bids.len(), 1);
assert_eq!(ready(market.instrument(&key)).unwrap().key, key);
assert_eq!(ready(listing.instruments()).unwrap().len(), 1);
assert_eq!(ready(trades.trades(&key)).unwrap().len(), 1);
let query = CandleQuery::new(CandleInterval::Hour1, UnixMillis(7));
assert_eq!(
ready(candles.candles(&key, &query)).unwrap()[0].open_time,
UnixMillis(7)
);
assert!(ready(funding.funding_rate(&key)).is_ok());
assert_eq!(
ready(funding.funding_history(&key, UnixMillis(3))).unwrap()[0].time,
UnixMillis(3)
);
assert_eq!(
ready(groups.event_group(&key)).unwrap().outcomes[0].key,
key
);
}
#[test]
fn a_key_of_another_product_is_an_invalid_request_and_an_unknown_one_a_refusal() {
let market: Arc<DynMarketData<'static>> = DynMarketData::new_arc(Acme);
struct Other;
impl KeySpace for Other {
const VENUE: VenueId = VenueId::from_static("other");
const PRODUCT: ProductId = ProductId::from_static("spot");
const CLASS: ProductClass = ProductClass::PerpetualFuture;
type Id = String;
fn parse_id(text: &str) -> Result<String, String> {
Ok(text.to_owned())
}
fn id_text(id: &String) -> Cow<'_, str> {
Cow::Borrowed(id)
}
}
let foreign = Other::key(&"ABC".to_owned());
let err = ready(market.quote(&foreign)).unwrap_err();
assert_eq!(err.class, Class::InvalidRequest);
let unknown = Spot::key(&"XYZ".to_owned());
let err = ready(market.book(&unknown)).unwrap_err();
assert_eq!(err.class, Class::VenueRefusal { code: None });
assert!(err.source.downcast_ref::<NotListed>().is_some());
}
#[test]
fn a_product_with_a_bundles_members_is_held_by_its_one_bound() {
fn perpetual<M: PerpetualMarket>(market: &M, key: &MarketKey) -> (Quote, FundingRate) {
(
ready(market.quote(key)).unwrap(),
ready(market.funding_rate(key)).unwrap(),
)
}
fn event_contract<M: EventContractMarket>(
market: &M,
key: &MarketKey,
) -> (Quote, EventGroup) {
(
ready(market.quote(key)).unwrap(),
ready(market.event_group(key)).unwrap(),
)
}
let (quote, rate) = perpetual(&Acme, &abc());
assert_eq!(quote.bid, Some(Level::new(Decimal::ONE, Decimal::TWO)));
assert_eq!(rate.rate, Decimal::ONE);
let (quote, group) = event_contract(&Acme, &abc());
assert_eq!(quote.key, abc());
assert_eq!(group.outcomes.len(), 1);
}
}