use reqwest::Url;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serde_with::{NoneAsEmptyString, serde_as};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum HoldingsSelector {
All,
AccountId(Uuid),
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Account {
pub account_id: Uuid,
pub account_number: String,
#[serde_as(as = "NoneAsEmptyString")]
pub account_name: Option<String>,
pub currency: String,
pub summary: AccountSummary,
pub positions: Vec<Position>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountSummary {
pub total_market_value: Decimal,
pub available_for_purchase: Decimal,
pub total_value: Decimal,
pub currency: String,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Position {
pub instrument_name: String,
pub ticker: String,
pub orderbook_id: u64,
pub possible_orderbook_ids: Vec<u64>,
pub quantity: Decimal,
pub market_value: InstrumentValue,
pub unrealized_result: InstrumentValue,
pub unrealized_result_percent: Decimal,
pub instrument_currency: String,
pub fx_rate: Decimal,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstrumentValue {
pub account_currency: Decimal,
pub instrument_currency: Decimal,
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountIdentifiers {
pub account_id: Uuid,
pub account_number: String,
#[serde_as(as = "NoneAsEmptyString")]
pub account_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TradeTicketArgs {
pub side: TradeSide,
pub account_id: Option<Uuid>,
pub price: Option<Decimal>,
#[serde(flatten)]
pub volume: TradeVolume,
pub currency: TradeCurrency,
#[serde(flatten)]
pub instrument: Instrument,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TradeVolume {
Amount(Decimal),
Quantity(Decimal),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum TradeCurrency {
#[default]
Account,
Code(String),
}
impl Serialize for TradeCurrency {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::Error;
match self {
Self::Account => serializer.serialize_none(),
Self::Code(code) => {
if code.len() != 3 {
return Err(S::Error::custom("currency code should be 3 letters"));
}
serializer.serialize_some(code)
}
}
}
}
impl<'de> Deserialize<'de> for TradeCurrency {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_option(TradeCurrencyVisitor)
}
}
struct TradeCurrencyVisitor;
impl<'de> serde::de::Visitor<'de> for TradeCurrencyVisitor {
type Value = TradeCurrency;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an ISO 4217 code of length 3")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(TradeCurrency::Account)
}
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
let code = String::deserialize(deserializer)?;
if code.len() != 3 {
return Err(serde::de::Error::custom(format!(
"Currency code should be 3 letters: {code}"
)));
}
Ok(TradeCurrency::Code(code))
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Instrument {
Name(String),
OrderbookId(u64),
Ticker(String),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
pub enum TradeSide {
#[default]
Buy,
Sell,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CreateTradeTicketResult {
pub url: Url,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstrumentIdentifiers {
pub name: String,
pub orderbook_id: u64,
pub ticker: String,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WatchlistInfo {
pub list_id: u64,
pub name: String,
pub orderbook_count: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Watchlist {
pub list_id: u64,
pub name: String,
pub items: Vec<InstrumentIdentifiers>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModifyWatchlistResult {
pub list_id: u64,
pub orderbook_ids: Vec<u64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trade_currency_serialization() {
assert_eq!(
serde_json::to_string(&TradeCurrency::Account).unwrap(),
"null"
);
assert_eq!(
serde_json::from_str::<TradeCurrency>("null").unwrap(),
TradeCurrency::Account
);
assert_eq!(
serde_json::to_string(&TradeCurrency::Code("SEK".into())).unwrap(),
"\"SEK\""
);
assert_eq!(
serde_json::from_str::<TradeCurrency>("\"USD\"").unwrap(),
TradeCurrency::Code("USD".into())
);
assert!(serde_json::from_str::<TradeCurrency>("\"WRONG_LEN\"").is_err());
assert!(serde_json::from_str::<TradeCurrency>("\"\"").is_err());
}
}