use super::{orderflow, FixedStr};
use crate::symbology;
use anyhow::bail;
use chrono::{DateTime, Utc};
use compact_str::CompactString;
use indexed_json::{
parser::{LeafFn, LeafTbl},
Indexable, IndexableField,
};
use netidx::{pack::Pack, utils};
use netidx_derive::Pack;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{
any::Any, cmp::Ordering, collections::HashMap, fmt::Display, ops::Deref,
str::FromStr, sync::Arc,
};
macro_rules! ntwrap {
($name:ident, $type:ty) => {
ntwrap!($name, $type, );
};
($name:ident, $type:ty, $($derive:ident),*) => {
#[derive(Clone, Debug, Serialize, Deserialize, Pack, PartialEq, Eq, $($derive),*)]
#[serde(transparent)]
#[pack(unwrapped)]
pub struct $name($type);
impl From<$type> for $name {
fn from(value: $type) -> Self {
Self(value)
}
}
impl Deref for $name {
type Target = $type;
fn deref(&self) -> &Self::Target {
&self.0
}
}
};
}
macro_rules! indexable_pack {
($name:ident, $key:expr) => {
impl IndexableField for $name {
fn key(&self) -> &'static str {
$key
}
fn byte_compareable(&self) -> bool {
true
}
fn encode(
&self,
buf: &mut smallvec::SmallVec<[u8; 128]>,
) -> anyhow::Result<()> {
let l = Pack::encoded_len(self);
buf.resize(l, 0);
Ok(Pack::encode(self, &mut &mut buf[..])?)
}
fn as_any(&self) -> &dyn std::any::Any {
self as &dyn Any
}
}
impl $name {
pub fn leaftbl_add(tbl: &mut LeafTbl) {
let f: LeafFn = Box::new(|s| Ok(Arc::new(s.parse::<Self>()?)));
tbl.insert($key, f);
}
}
};
}
macro_rules! indexable_decimal {
($name:ident, $key:expr) => {
impl IndexableField for $name {
fn key(&self) -> &'static str {
$key
}
fn byte_compareable(&self) -> bool {
false
}
fn encode(
&self,
buf: &mut smallvec::SmallVec<[u8; 128]>,
) -> anyhow::Result<()> {
let l = Pack::encoded_len(self);
buf.resize(l, 0);
Ok(Pack::encode(self, &mut &mut buf[..])?)
}
fn decode_cmp(&self, mut b: &[u8]) -> anyhow::Result<std::cmp::Ordering> {
let other: Decimal = Pack::decode(&mut b)?;
Ok(self.cmp(&other))
}
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
impl $name {
pub fn leaftbl_add(tbl: &mut LeafTbl) {
let f: LeafFn = Box::new(|s| Ok(Arc::new(s.parse::<Self>()?)));
tbl.insert($key, f);
}
}
};
}
macro_rules! disp_inner {
($name:ident) => {
impl Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self.0)
}
}
};
}
macro_rules! disp_inner_esc {
($name:ident) => {
impl Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", utils::escape(&self.0, '\\', &['"']))
}
}
};
}
macro_rules! fromstr_inner {
($name:ident) => {
impl FromStr for $name {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(FromStr::from_str(s)?))
}
}
};
}
macro_rules! fromstr_by_id_or_inner {
($name:ident, $inner:ident) => {
impl FromStr for $name {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<super::symbology::$inner>() {
Err(_) => Ok(Self(s.parse::<symbology::$inner>()?)),
Ok(id) => match symbology::$inner::get_by_id(&id) {
Some(r) => Ok(Self(r)),
None => bail!("{} is an unknown {}", id, stringify!($name)),
},
}
}
}
};
}
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Pack,
PartialEq,
Eq,
PartialOrd,
Ord,
JsonSchema,
)]
pub enum LiquidityIndicator {
Maker,
Taker,
Unknown,
}
indexable_pack!(LiquidityIndicator, "liquidity_indicator");
impl FromStr for LiquidityIndicator {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Maker" => Ok(Self::Maker),
"Taker" => Ok(Self::Taker),
"Unknown" => Ok(Self::Unknown),
s => bail!("{} is not a valid liquidity indicator", s),
}
}
}
impl Display for LiquidityIndicator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{:?}\"", self)
}
}
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
Pack,
PartialEq,
PartialOrd,
Ord,
Eq,
JsonSchema,
)]
pub enum FillKind {
Normal,
Correction,
Reversal,
}
impl Display for FillKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{:?}\"", self)
}
}
impl FromStr for FillKind {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Normal" => Ok(Self::Normal),
"Correction" => Ok(Self::Correction),
"Reversal" => Ok(Self::Reversal),
s => bail!("{} is not a valid fill kind", s),
}
}
}
indexable_pack!(FillKind, "fill_kind");
ntwrap!(ExchangeSymbol, CompactString);
indexable_pack!(ExchangeSymbol, "exchange_symbol");
disp_inner_esc!(ExchangeSymbol);
fromstr_inner!(ExchangeSymbol);
ntwrap!(ExchangeFillId, FixedStr<48>, JsonSchema, Copy, PartialOrd, Ord);
indexable_pack!(ExchangeFillId, "exchange_fill_id");
disp_inner_esc!(ExchangeFillId);
fromstr_inner!(ExchangeFillId);
ntwrap!(ExchangeOrderId, FixedStr<48>, JsonSchema, Copy, PartialOrd, Ord);
indexable_pack!(ExchangeOrderId, "exchange_order_id");
disp_inner_esc!(ExchangeOrderId);
fromstr_inner!(ExchangeOrderId);
ntwrap!(TradableProduct, symbology::TradableProduct, JsonSchema, Copy);
indexable_pack!(TradableProduct, "tradable_product");
disp_inner!(TradableProduct);
fromstr_by_id_or_inner!(TradableProduct, TradableProduct);
ntwrap!(Base, symbology::Product, JsonSchema, Copy);
indexable_pack!(Base, "base");
disp_inner!(Base);
fromstr_by_id_or_inner!(Base, Product);
ntwrap!(Quote, symbology::Product, JsonSchema, Copy);
indexable_pack!(Quote, "quote");
disp_inner!(Quote);
fromstr_by_id_or_inner!(Quote, Product);
ntwrap!(Venue, symbology::Venue, JsonSchema, Copy);
indexable_pack!(Venue, "venue");
disp_inner!(Venue);
fromstr_by_id_or_inner!(Venue, Venue);
ntwrap!(Route, symbology::Route, JsonSchema, Copy);
indexable_pack!(Route, "route");
disp_inner!(Route);
fromstr_by_id_or_inner!(Route, Route);
ntwrap!(Price, Decimal, JsonSchema, Copy);
indexable_decimal!(Price, "price");
disp_inner!(Price);
fromstr_inner!(Price);
ntwrap!(Quantity, Decimal, JsonSchema, Copy);
indexable_decimal!(Quantity, "quantity");
disp_inner!(Quantity);
fromstr_inner!(Quantity);
ntwrap!(Fee, Decimal, JsonSchema, Copy);
indexable_decimal!(Fee, "fee");
disp_inner!(Fee);
fromstr_inner!(Fee);
ntwrap!(Dir, super::Dir, JsonSchema, Copy);
indexable_pack!(Dir, "dir");
fromstr_inner!(Dir);
impl Display for Dir {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{:?}\"", self.0)
}
}
ntwrap!(Time, DateTime<Utc>, JsonSchema, Copy);
fromstr_inner!(Time);
impl Display for Time {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self.0)
}
}
impl Time {
pub fn leaftbl_add(tbl: &mut LeafTbl) {
let f: LeafFn = Box::new(|s| Ok(Arc::new(s.parse::<Self>()?)));
tbl.insert("time", f);
}
}
impl IndexableField for Time {
fn key(&self) -> &'static str {
"time"
}
fn byte_compareable(&self) -> bool {
false
}
fn encode(&self, buf: &mut smallvec::SmallVec<[u8; 128]>) -> anyhow::Result<()> {
let l = Pack::encoded_len(&self.0);
buf.resize(l, 0);
Ok(Pack::encode(&self.0, &mut &mut buf[..])?)
}
fn decode_cmp(&self, mut b: &[u8]) -> anyhow::Result<std::cmp::Ordering> {
let other: DateTime<Utc> = Pack::decode(&mut b)?;
Ok(self.0.cmp(&other))
}
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
#[derive(
Clone, Debug, Serialize, Deserialize, Pack, PartialEq, Eq, PartialOrd, Ord, JsonSchema,
)]
pub enum Type {
Order,
Fill,
}
impl FromStr for Type {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Order" => Ok(Self::Order),
"Fill" => Ok(Self::Fill),
s => bail!("{} is not a valid type", s),
}
}
}
impl From<&Msg> for Type {
fn from(value: &Msg) -> Self {
match value {
Msg::Order(_) => Self::Order,
Msg::Fill(_) => Self::Fill,
}
}
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{:?}\"", self)
}
}
indexable_pack!(Type, "type");
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Pack, PartialEq, Eq, JsonSchema)]
pub struct Order {
pub time: Time,
pub venue: Venue,
pub route: Route,
#[schemars(with = "Option<String>")]
pub exchange_order_id: Option<ExchangeOrderId>,
pub tradable_product: Option<TradableProduct>,
pub dir: Dir,
pub price: Price,
pub quantity: Quantity,
}
#[derive(Clone, Debug, Serialize, Deserialize, Pack, PartialEq, Eq, JsonSchema)]
pub struct Fill {
pub time: Time,
pub venue: Venue,
pub route: Route,
pub exchange_order_id: Option<ExchangeOrderId>,
pub exchange_fill_id: Option<ExchangeFillId>,
#[schemars(with = "String")]
pub exchange_symbol: Option<ExchangeSymbol>,
pub tradable_product: Option<TradableProduct>,
pub price: Price,
pub quantity: Quantity,
pub dir: Option<Dir>,
pub fee: Option<Fee>,
pub liquidity_indicator: LiquidityIndicator,
pub fill_kind: FillKind,
}
impl Fill {
fn from_normal(f: &orderflow::NormalFill, fill_kind: FillKind) -> Self {
let exchange_symbol = (**f.target.trade_info.load())
.as_ref()
.map(|ti| CompactString::from(ti.trading_symbol.name.as_str()).into());
Self {
time: f.recv_time.into(),
venue: f.target.venue.into(),
route: f.target.route.into(),
exchange_order_id: f.exchange_order_id.map(|i| i.into()),
exchange_fill_id: f.exchange_fill_id.map(|i| i.into()),
exchange_symbol,
tradable_product: Some(f.target.into()),
dir: Some(f.dir.into()),
fee: None,
liquidity_indicator: LiquidityIndicator::Unknown,
fill_kind,
price: f.price.into(),
quantity: f.quantity.into(),
}
}
fn from_aberrant(f: &orderflow::AberrantFill, fill_kind: FillKind) -> Self {
let tp = f.info.get_tradable_product();
let exchange_symbol = tp.and_then(|tp| {
(**tp.trade_info.load())
.as_ref()
.map(|ti| CompactString::from(ti.trading_symbol.name.as_str()).into())
});
Self {
time: f.recv_time.into(),
venue: f.info.venue.into(),
route: f.info.route.into(),
exchange_order_id: f.exchange_order_id.map(|i| i.into()),
exchange_fill_id: f.exchange_fill_id.map(|i| i.into()),
exchange_symbol,
tradable_product: tp.map(|tp| tp.into()),
dir: f.info.dir.map(|d| d.into()),
fee: None,
liquidity_indicator: LiquidityIndicator::Unknown,
fill_kind,
price: f.info.price.map(|p| p.into()).unwrap_or(dec!(0).into()),
quantity: f.info.price.map(|q| q.into()).unwrap_or(dec!(0).into()),
}
}
pub fn from_orderflow(fill: &orderflow::Fill) -> Self {
match fill {
orderflow::Fill::Normal(f) => match f {
Ok(f) => Self::from_normal(f, FillKind::Normal),
Err(f) => Self::from_aberrant(f, FillKind::Normal),
},
orderflow::Fill::Correction(f) => match f {
Ok(f) => Self::from_normal(f, FillKind::Correction),
Err(f) => Self::from_aberrant(f, FillKind::Correction),
},
orderflow::Fill::Reversal(f) => match f {
Ok(f) => Self::from_normal(f, FillKind::Reversal),
Err(f) => Self::from_aberrant(f, FillKind::Reversal),
},
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Pack, PartialEq, Eq, JsonSchema)]
#[serde(tag = "type")]
pub enum Msg {
Order(Order),
Fill(Fill),
}
impl Indexable for Msg {
type Iter = SmallVec<[Box<dyn IndexableField>; 16]>;
fn timestamp(&self) -> DateTime<Utc> {
match self {
Msg::Order(o) => *o.time,
Msg::Fill(f) => *f.time,
}
}
fn index(&self) -> Self::Iter {
let mut fields: SmallVec<[Box<dyn IndexableField>; 16]> = SmallVec::new();
match self {
Self::Order(Order {
time,
venue,
route,
exchange_order_id,
tradable_product,
dir,
price,
quantity,
}) => {
fields.push(Box::new(Type::Order));
fields.push(Box::new(*time));
fields.push(Box::new(*venue));
fields.push(Box::new(*route));
if let Some(id) = exchange_order_id.clone() {
fields.push(Box::new(id));
}
if let Some(tp) = *tradable_product {
fields.push(Box::new(tp));
fields.push(Box::new(Base(tp.base)));
fields.push(Box::new(Quote(tp.quote)));
}
fields.push(Box::new(*dir));
fields.push(Box::new(*price));
fields.push(Box::new(*quantity));
fields
}
Self::Fill(Fill {
time,
venue,
route,
exchange_order_id,
exchange_fill_id,
exchange_symbol,
tradable_product,
price,
quantity,
dir,
fee,
liquidity_indicator,
fill_kind,
}) => {
fields.push(Box::new(Type::Fill));
fields.push(Box::new(*time));
fields.push(Box::new(*venue));
fields.push(Box::new(*route));
if let Some(id) = exchange_order_id {
fields.push(Box::new(id.clone()));
}
if let Some(id) = exchange_fill_id {
fields.push(Box::new(id.clone()));
}
if let Some(sym) = exchange_symbol.clone() {
fields.push(Box::new(sym));
}
if let Some(tp) = *tradable_product {
fields.push(Box::new(tp));
fields.push(Box::new(Base(tp.base)));
fields.push(Box::new(Quote(tp.quote)));
}
fields.push(Box::new(*price));
fields.push(Box::new(*quantity));
if let Some(dir) = *dir {
fields.push(Box::new(dir));
}
if let Some(fee) = *fee {
fields.push(Box::new(fee));
}
fields.push(Box::new(*liquidity_indicator));
fields.push(Box::new(*fill_kind));
fields
}
}
}
fn dyn_partial_cmp(&self, i: &dyn IndexableField) -> Option<Ordering> {
if let Some(ityp) = i.as_any().downcast_ref::<Type>() {
return Some(Type::from(self).cmp(ityp));
}
match self {
Msg::Order(Order {
time,
venue,
route,
exchange_order_id,
tradable_product,
dir,
price,
quantity,
}) => {
if let Some(itime) = i.as_any().downcast_ref::<Time>() {
return Some(time.cmp(itime));
}
if let Some(ivenue) = i.as_any().downcast_ref::<Venue>() {
return Some(venue.cmp(ivenue));
}
if let Some(iroute) = i.as_any().downcast_ref::<Route>() {
return Some(route.cmp(iroute));
}
if let Some(iexchange_order_id) =
i.as_any().downcast_ref::<ExchangeOrderId>()
{
if let Some(exchange_order_id) = exchange_order_id {
return Some(exchange_order_id.cmp(iexchange_order_id));
}
}
if let Some(itradable_product) =
i.as_any().downcast_ref::<TradableProduct>()
{
if let Some(tradable_product) = tradable_product {
return Some(tradable_product.cmp(itradable_product));
}
}
if let Some(ibase) = i.as_any().downcast_ref::<Base>() {
if let Some(tradable_product) = tradable_product {
return Some(tradable_product.base.cmp(ibase));
}
}
if let Some(iquote) = i.as_any().downcast_ref::<Quote>() {
if let Some(tradable_product) = tradable_product {
return Some(tradable_product.quote.cmp(iquote));
}
}
if let Some(idir) = i.as_any().downcast_ref::<Dir>() {
return Some(dir.cmp(idir));
}
if let Some(iprice) = i.as_any().downcast_ref::<Price>() {
return Some(price.cmp(iprice));
}
if let Some(iquantity) = i.as_any().downcast_ref::<Quantity>() {
return Some(quantity.cmp(iquantity));
}
}
Msg::Fill(Fill {
time,
venue,
route,
exchange_order_id,
exchange_fill_id,
exchange_symbol,
tradable_product,
price,
quantity,
dir,
fee,
liquidity_indicator,
fill_kind,
}) => {
if let Some(itime) = i.as_any().downcast_ref::<Time>() {
return Some(time.cmp(itime));
}
if let Some(ivenue) = i.as_any().downcast_ref::<Venue>() {
return Some(venue.cmp(ivenue));
}
if let Some(iroute) = i.as_any().downcast_ref::<Route>() {
return Some(route.cmp(iroute));
}
if let Some(iexchange_order_id) =
i.as_any().downcast_ref::<ExchangeOrderId>()
{
if let Some(exchange_order_id) = exchange_order_id {
return Some(exchange_order_id.cmp(iexchange_order_id));
}
}
if let Some(iexchange_fill_id) =
i.as_any().downcast_ref::<ExchangeFillId>()
{
if let Some(exchange_fill_id) = exchange_fill_id {
return Some(exchange_fill_id.cmp(iexchange_fill_id));
}
}
if let Some(iexchange_symbol) =
i.as_any().downcast_ref::<ExchangeSymbol>()
{
if let Some(exchange_symbol) = exchange_symbol {
return Some(exchange_symbol.cmp(iexchange_symbol));
}
}
if let Some(itradable_product) =
i.as_any().downcast_ref::<TradableProduct>()
{
if let Some(tradable_product) = tradable_product {
return Some(tradable_product.cmp(itradable_product));
}
}
if let Some(ibase) = i.as_any().downcast_ref::<Base>() {
if let Some(tradable_product) = tradable_product {
return Some(tradable_product.base.cmp(ibase));
}
}
if let Some(iquote) = i.as_any().downcast_ref::<Quote>() {
if let Some(tradable_product) = tradable_product {
return Some(tradable_product.quote.cmp(iquote));
}
}
if let Some(iprice) = i.as_any().downcast_ref::<Price>() {
return Some(price.cmp(iprice));
}
if let Some(iquantity) = i.as_any().downcast_ref::<Quantity>() {
return Some(quantity.cmp(iquantity));
}
if let Some(idir) = i.as_any().downcast_ref::<Dir>() {
if let Some(dir) = dir {
return Some(dir.cmp(idir));
}
}
if let Some(ifee) = i.as_any().downcast_ref::<Fee>() {
if let Some(fee) = fee {
return Some(fee.cmp(ifee));
}
}
if let Some(iliquidity_indicator) =
i.as_any().downcast_ref::<LiquidityIndicator>()
{
return Some(liquidity_indicator.cmp(iliquidity_indicator));
}
if let Some(ifill_kind) = i.as_any().downcast_ref::<FillKind>() {
return Some(fill_kind.cmp(ifill_kind));
}
}
}
None
}
}
pub fn leaf_table() -> LeafTbl {
let mut tbl = HashMap::default();
LiquidityIndicator::leaftbl_add(&mut tbl);
FillKind::leaftbl_add(&mut tbl);
ExchangeSymbol::leaftbl_add(&mut tbl);
ExchangeFillId::leaftbl_add(&mut tbl);
ExchangeOrderId::leaftbl_add(&mut tbl);
TradableProduct::leaftbl_add(&mut tbl);
Base::leaftbl_add(&mut tbl);
Quote::leaftbl_add(&mut tbl);
Venue::leaftbl_add(&mut tbl);
Route::leaftbl_add(&mut tbl);
Price::leaftbl_add(&mut tbl);
Quantity::leaftbl_add(&mut tbl);
Fee::leaftbl_add(&mut tbl);
Dir::leaftbl_add(&mut tbl);
Time::leaftbl_add(&mut tbl);
Type::leaftbl_add(&mut tbl);
tbl
}