use std::fmt::Display;
use nautilus_model::enums::{MarketStatusAction, OrderSide, OrderType, TimeInForce};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.binance",
eq,
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE"
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.binance")
)]
pub enum BinanceProductType {
#[default]
Spot,
Margin,
UsdM,
CoinM,
Options,
}
impl BinanceProductType {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Spot => "SPOT",
Self::Margin => "MARGIN",
Self::UsdM => "USD_M",
Self::CoinM => "COIN_M",
Self::Options => "OPTIONS",
}
}
#[must_use]
pub const fn suffix(self) -> &'static str {
match self {
Self::Spot => "-SPOT",
Self::Margin => "-MARGIN",
Self::UsdM => "-LINEAR",
Self::CoinM => "-INVERSE",
Self::Options => "-OPTION",
}
}
#[must_use]
pub const fn is_spot(self) -> bool {
matches!(self, Self::Spot | Self::Margin)
}
#[must_use]
pub const fn is_futures(self) -> bool {
matches!(self, Self::UsdM | Self::CoinM)
}
#[must_use]
pub const fn is_linear(self) -> bool {
matches!(self, Self::Spot | Self::Margin | Self::UsdM)
}
#[must_use]
pub const fn is_inverse(self) -> bool {
matches!(self, Self::CoinM)
}
#[must_use]
pub const fn is_options(self) -> bool {
matches!(self, Self::Options)
}
}
impl Display for BinanceProductType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.binance",
eq,
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE"
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.binance")
)]
pub enum BinanceEnvironment {
#[default]
Mainnet,
Testnet,
Demo,
}
impl BinanceEnvironment {
#[must_use]
pub const fn is_testnet(self) -> bool {
matches!(self, Self::Testnet)
}
#[must_use]
pub const fn is_sandbox(self) -> bool {
matches!(self, Self::Testnet | Self::Demo)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum BinanceSide {
Buy,
Sell,
}
impl TryFrom<OrderSide> for BinanceSide {
type Error = anyhow::Error;
fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
match value {
OrderSide::Buy => Ok(Self::Buy),
OrderSide::Sell => Ok(Self::Sell),
_ => anyhow::bail!("Unsupported `OrderSide` for Binance: {value:?}"),
}
}
}
impl From<BinanceSide> for OrderSide {
fn from(value: BinanceSide) -> Self {
match value {
BinanceSide::Buy => Self::Buy,
BinanceSide::Sell => Self::Sell,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.binance",
eq,
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.binance")
)]
pub enum BinancePositionSide {
Both,
Long,
Short,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.binance",
eq,
from_py_object,
rename_all = "SCREAMING_SNAKE_CASE"
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.binance")
)]
pub enum BinanceMarginType {
#[serde(rename = "CROSSED", alias = "cross")]
Cross,
#[serde(rename = "ISOLATED", alias = "isolated")]
Isolated,
#[default]
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceWorkingType {
ContractPrice,
MarkPrice,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceOrderStatus {
New,
PendingNew,
PartiallyFilled,
Filled,
Canceled,
PendingCancel,
Rejected,
Expired,
ExpiredInMatch,
NewInsurance,
NewAdl,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceAlgoStatus {
New,
Triggering,
Triggered,
Finished,
Canceled,
Expired,
Rejected,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceAlgoType {
#[default]
Conditional,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceFuturesOrderType {
Limit,
Market,
Stop,
StopMarket,
TakeProfit,
TakeProfitMarket,
TrailingStopMarket,
Liquidation,
Adl,
#[serde(other)]
Unknown,
}
impl From<BinanceFuturesOrderType> for OrderType {
fn from(value: BinanceFuturesOrderType) -> Self {
match value {
BinanceFuturesOrderType::Limit => Self::Limit,
BinanceFuturesOrderType::Market => Self::Market,
BinanceFuturesOrderType::Stop => Self::StopLimit,
BinanceFuturesOrderType::StopMarket => Self::StopMarket,
BinanceFuturesOrderType::TakeProfit => Self::LimitIfTouched,
BinanceFuturesOrderType::TakeProfitMarket => Self::MarketIfTouched,
BinanceFuturesOrderType::TrailingStopMarket => Self::TrailingStopMarket,
BinanceFuturesOrderType::Liquidation
| BinanceFuturesOrderType::Adl
| BinanceFuturesOrderType::Unknown => Self::Market, }
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum BinanceTimeInForce {
Gtc,
Ioc,
Fok,
Gtx,
Gtd,
Rpi,
#[serde(other)]
Unknown,
}
impl TryFrom<TimeInForce> for BinanceTimeInForce {
type Error = anyhow::Error;
fn try_from(value: TimeInForce) -> Result<Self, Self::Error> {
match value {
TimeInForce::Gtc => Ok(Self::Gtc),
TimeInForce::Ioc => Ok(Self::Ioc),
TimeInForce::Fok => Ok(Self::Fok),
TimeInForce::Gtd => Ok(Self::Gtd),
_ => anyhow::bail!("Unsupported `TimeInForce` for Binance: {value:?}"),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceIncomeType {
Transfer,
WelcomeBonus,
RealizedPnl,
FundingFee,
Commission,
CommissionRebate,
ApiRebate,
InsuranceClear,
ReferralKickback,
ContestReward,
CrossCollateralTransfer,
OptionsPremiumFee,
OptionsSettleProfit,
InternalTransfer,
AutoExchange,
#[serde(rename = "DELIVERED_SETTELMENT")]
DeliveredSettlement,
CoinSwapDeposit,
CoinSwapWithdraw,
PositionLimitIncreaseFee,
StrategyUmfuturesTransfer,
FeeReturn,
BfusdReward,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinancePriceMatch {
None,
Opponent,
#[serde(rename = "OPPONENT_5")]
Opponent5,
#[serde(rename = "OPPONENT_10")]
Opponent10,
#[serde(rename = "OPPONENT_20")]
Opponent20,
Queue,
#[serde(rename = "QUEUE_5")]
Queue5,
#[serde(rename = "QUEUE_10")]
Queue10,
#[serde(rename = "QUEUE_20")]
Queue20,
#[serde(other)]
Unknown,
}
impl BinancePriceMatch {
pub fn from_param(s: &str) -> anyhow::Result<Self> {
let value = s.to_uppercase();
serde_json::from_value(serde_json::Value::String(value))
.map_err(|_| anyhow::anyhow!("Invalid price_match value: {s:?}"))
.and_then(|pm: Self| {
if pm == Self::None || pm == Self::Unknown {
anyhow::bail!("Invalid price_match value: {s:?}")
}
Ok(pm)
})
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceSelfTradePreventionMode {
None,
ExpireMaker,
ExpireTaker,
ExpireBoth,
Decrement,
Transfer,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceTradingStatus {
Trading,
PendingTrading,
PreTrading,
PostTrading,
EndOfDay,
Halt,
AuctionMatch,
Break,
#[serde(other)]
Unknown,
}
impl From<BinanceTradingStatus> for MarketStatusAction {
fn from(status: BinanceTradingStatus) -> Self {
match status {
BinanceTradingStatus::Trading => Self::Trading,
BinanceTradingStatus::PendingTrading | BinanceTradingStatus::PreTrading => {
Self::PreOpen
}
BinanceTradingStatus::PostTrading => Self::PostClose,
BinanceTradingStatus::EndOfDay => Self::Close,
BinanceTradingStatus::Halt => Self::Halt,
BinanceTradingStatus::AuctionMatch => Self::Cross,
BinanceTradingStatus::Break => Self::Pause,
BinanceTradingStatus::Unknown => Self::NotAvailableForTrading,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceContractStatus {
Trading,
PendingTrading,
PreDelivering,
Delivering,
Delivered,
PreSettle,
Settling,
Close,
PreDelisting,
Delisting,
Down,
#[serde(other)]
Unknown,
}
impl From<BinanceContractStatus> for MarketStatusAction {
fn from(status: BinanceContractStatus) -> Self {
match status {
BinanceContractStatus::Trading => Self::Trading,
BinanceContractStatus::PendingTrading => Self::PreOpen,
BinanceContractStatus::PreDelivering
| BinanceContractStatus::PreDelisting
| BinanceContractStatus::PreSettle => Self::PreClose,
BinanceContractStatus::Delivering
| BinanceContractStatus::Delivered
| BinanceContractStatus::Settling
| BinanceContractStatus::Close => Self::Close,
BinanceContractStatus::Delisting => Self::Suspend,
BinanceContractStatus::Down | BinanceContractStatus::Unknown => {
Self::NotAvailableForTrading
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BinanceWsEventType {
AggTrade,
Trade,
BookTicker,
DepthUpdate,
MarkPriceUpdate,
Kline,
ForceOrder,
#[serde(rename = "24hrTicker")]
Ticker24Hr,
#[serde(rename = "24hrMiniTicker")]
MiniTicker24Hr,
#[serde(rename = "ACCOUNT_UPDATE")]
AccountUpdate,
#[serde(rename = "ORDER_TRADE_UPDATE")]
OrderTradeUpdate,
#[serde(rename = "ALGO_UPDATE")]
AlgoUpdate,
#[serde(rename = "MARGIN_CALL")]
MarginCall,
#[serde(rename = "ACCOUNT_CONFIG_UPDATE")]
AccountConfigUpdate,
#[serde(rename = "listenKeyExpired")]
ListenKeyExpired,
#[serde(other)]
Unknown,
}
impl BinanceWsEventType {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::AggTrade => "aggTrade",
Self::Trade => "trade",
Self::BookTicker => "bookTicker",
Self::DepthUpdate => "depthUpdate",
Self::MarkPriceUpdate => "markPriceUpdate",
Self::Kline => "kline",
Self::ForceOrder => "forceOrder",
Self::Ticker24Hr => "24hrTicker",
Self::MiniTicker24Hr => "24hrMiniTicker",
Self::AccountUpdate => "ACCOUNT_UPDATE",
Self::OrderTradeUpdate => "ORDER_TRADE_UPDATE",
Self::AlgoUpdate => "ALGO_UPDATE",
Self::MarginCall => "MARGIN_CALL",
Self::AccountConfigUpdate => "ACCOUNT_CONFIG_UPDATE",
Self::ListenKeyExpired => "listenKeyExpired",
Self::Unknown => "unknown",
}
}
}
impl Display for BinanceWsEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum BinanceWsMethod {
Subscribe,
Unsubscribe,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceFilterType {
PriceFilter,
PercentPrice,
PercentPriceBySide,
LotSize,
MarketLotSize,
Notional,
MinNotional,
IcebergParts,
MaxNumOrders,
MaxNumAlgoOrders,
MaxNumIcebergOrders,
MaxPosition,
TrailingDelta,
MaxNumOrderAmends,
MaxNumOrderLists,
MaxAsset,
ExchangeMaxNumOrders,
ExchangeMaxNumAlgoOrders,
ExchangeMaxNumIcebergOrders,
ExchangeMaxNumOrderLists,
TPlusSell,
#[serde(other)]
Unknown,
}
impl Display for BinanceEnvironment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Mainnet => write!(f, "Mainnet"),
Self::Testnet => write!(f, "Testnet"),
Self::Demo => write!(f, "Demo"),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceRateLimitType {
RequestWeight,
Orders,
RawRequests,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceRateLimitInterval {
Second,
Minute,
Day,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BinanceKlineInterval {
#[serde(rename = "1s")]
Second1,
#[default]
#[serde(rename = "1m")]
Minute1,
#[serde(rename = "3m")]
Minute3,
#[serde(rename = "5m")]
Minute5,
#[serde(rename = "15m")]
Minute15,
#[serde(rename = "30m")]
Minute30,
#[serde(rename = "1h")]
Hour1,
#[serde(rename = "2h")]
Hour2,
#[serde(rename = "4h")]
Hour4,
#[serde(rename = "6h")]
Hour6,
#[serde(rename = "8h")]
Hour8,
#[serde(rename = "12h")]
Hour12,
#[serde(rename = "1d")]
Day1,
#[serde(rename = "3d")]
Day3,
#[serde(rename = "1w")]
Week1,
#[serde(rename = "1M")]
Month1,
}
impl BinanceKlineInterval {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Second1 => "1s",
Self::Minute1 => "1m",
Self::Minute3 => "3m",
Self::Minute5 => "5m",
Self::Minute15 => "15m",
Self::Minute30 => "30m",
Self::Hour1 => "1h",
Self::Hour2 => "2h",
Self::Hour4 => "4h",
Self::Hour6 => "6h",
Self::Hour8 => "8h",
Self::Hour12 => "12h",
Self::Day1 => "1d",
Self::Day3 => "3d",
Self::Week1 => "1w",
Self::Month1 => "1M",
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use serde_json::json;
use super::*;
#[rstest]
fn test_product_type_as_str() {
assert_eq!(BinanceProductType::Spot.as_str(), "SPOT");
assert_eq!(BinanceProductType::Margin.as_str(), "MARGIN");
assert_eq!(BinanceProductType::UsdM.as_str(), "USD_M");
assert_eq!(BinanceProductType::CoinM.as_str(), "COIN_M");
assert_eq!(BinanceProductType::Options.as_str(), "OPTIONS");
}
#[rstest]
fn test_product_type_suffix() {
assert_eq!(BinanceProductType::Spot.suffix(), "-SPOT");
assert_eq!(BinanceProductType::Margin.suffix(), "-MARGIN");
assert_eq!(BinanceProductType::UsdM.suffix(), "-LINEAR");
assert_eq!(BinanceProductType::CoinM.suffix(), "-INVERSE");
assert_eq!(BinanceProductType::Options.suffix(), "-OPTION");
}
#[rstest]
fn test_product_type_predicates() {
assert!(BinanceProductType::Spot.is_spot());
assert!(BinanceProductType::Margin.is_spot());
assert!(!BinanceProductType::UsdM.is_spot());
assert!(BinanceProductType::UsdM.is_futures());
assert!(BinanceProductType::CoinM.is_futures());
assert!(!BinanceProductType::Spot.is_futures());
assert!(BinanceProductType::CoinM.is_inverse());
assert!(!BinanceProductType::UsdM.is_inverse());
assert!(BinanceProductType::Options.is_options());
assert!(!BinanceProductType::Spot.is_options());
}
#[rstest]
#[case("\"REQUEST_WEIGHT\"", BinanceRateLimitType::RequestWeight)]
#[case("\"ORDERS\"", BinanceRateLimitType::Orders)]
#[case("\"RAW_REQUESTS\"", BinanceRateLimitType::RawRequests)]
#[case("\"UNDOCUMENTED\"", BinanceRateLimitType::Unknown)]
fn test_rate_limit_type_deserializes(
#[case] raw: &str,
#[case] expected: BinanceRateLimitType,
) {
let value: BinanceRateLimitType = serde_json::from_str(raw).unwrap();
assert_eq!(value, expected);
}
#[rstest]
#[case("\"SECOND\"", BinanceRateLimitInterval::Second)]
#[case("\"MINUTE\"", BinanceRateLimitInterval::Minute)]
#[case("\"DAY\"", BinanceRateLimitInterval::Day)]
#[case("\"WEEK\"", BinanceRateLimitInterval::Unknown)]
fn test_rate_limit_interval_deserializes(
#[case] raw: &str,
#[case] expected: BinanceRateLimitInterval,
) {
let value: BinanceRateLimitInterval = serde_json::from_str(raw).unwrap();
assert_eq!(value, expected);
}
#[rstest]
#[case(BinanceMarginType::Cross, "CROSSED", "cross")]
#[case(BinanceMarginType::Isolated, "ISOLATED", "isolated")]
fn test_margin_type_serde_roundtrip(
#[case] variant: BinanceMarginType,
#[case] post_format: &str,
#[case] get_format: &str,
) {
let serialized = serde_json::to_value(variant).unwrap();
assert_eq!(serialized, json!(post_format));
let from_post: BinanceMarginType =
serde_json::from_str(&format!("\"{post_format}\"")).unwrap();
assert_eq!(from_post, variant);
let from_get: BinanceMarginType =
serde_json::from_str(&format!("\"{get_format}\"")).unwrap();
assert_eq!(from_get, variant);
}
#[rstest]
fn test_margin_type_unknown_fallback() {
let value: BinanceMarginType = serde_json::from_str("\"SOMETHING_NEW\"").unwrap();
assert_eq!(value, BinanceMarginType::Unknown);
}
#[rstest]
fn test_rate_limit_enums_serialize_to_binance_strings() {
assert_eq!(
serde_json::to_value(BinanceRateLimitType::RequestWeight).unwrap(),
json!("REQUEST_WEIGHT")
);
assert_eq!(
serde_json::to_value(BinanceRateLimitInterval::Minute).unwrap(),
json!("MINUTE")
);
}
#[rstest]
#[case("\"NONE\"", BinancePriceMatch::None)]
#[case("\"OPPONENT\"", BinancePriceMatch::Opponent)]
#[case("\"OPPONENT_5\"", BinancePriceMatch::Opponent5)]
#[case("\"OPPONENT_10\"", BinancePriceMatch::Opponent10)]
#[case("\"OPPONENT_20\"", BinancePriceMatch::Opponent20)]
#[case("\"QUEUE\"", BinancePriceMatch::Queue)]
#[case("\"QUEUE_5\"", BinancePriceMatch::Queue5)]
#[case("\"QUEUE_10\"", BinancePriceMatch::Queue10)]
#[case("\"QUEUE_20\"", BinancePriceMatch::Queue20)]
#[case("\"SOMETHING_NEW\"", BinancePriceMatch::Unknown)]
fn test_price_match_deserializes(#[case] raw: &str, #[case] expected: BinancePriceMatch) {
let value: BinancePriceMatch = serde_json::from_str(raw).unwrap();
assert_eq!(value, expected);
}
#[rstest]
#[case(BinancePriceMatch::None, "NONE")]
#[case(BinancePriceMatch::Opponent, "OPPONENT")]
#[case(BinancePriceMatch::Opponent5, "OPPONENT_5")]
#[case(BinancePriceMatch::Opponent10, "OPPONENT_10")]
#[case(BinancePriceMatch::Opponent20, "OPPONENT_20")]
#[case(BinancePriceMatch::Queue, "QUEUE")]
#[case(BinancePriceMatch::Queue5, "QUEUE_5")]
#[case(BinancePriceMatch::Queue10, "QUEUE_10")]
#[case(BinancePriceMatch::Queue20, "QUEUE_20")]
fn test_price_match_serializes(#[case] variant: BinancePriceMatch, #[case] expected: &str) {
let serialized = serde_json::to_value(variant).unwrap();
assert_eq!(serialized, json!(expected));
}
#[rstest]
#[case("OPPONENT", BinancePriceMatch::Opponent)]
#[case("opponent", BinancePriceMatch::Opponent)]
#[case("OPPONENT_5", BinancePriceMatch::Opponent5)]
#[case("opponent_5", BinancePriceMatch::Opponent5)]
#[case("QUEUE_20", BinancePriceMatch::Queue20)]
#[case("queue_20", BinancePriceMatch::Queue20)]
fn test_price_match_from_param_valid(#[case] input: &str, #[case] expected: BinancePriceMatch) {
let result = BinancePriceMatch::from_param(input).unwrap();
assert_eq!(result, expected);
}
#[rstest]
#[case("NONE")]
#[case("invalid")]
#[case("")]
fn test_price_match_from_param_invalid(#[case] input: &str) {
assert!(BinancePriceMatch::from_param(input).is_err());
}
}