use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum TriggerMethod {
#[default]
Default = 0,
DoubleBidAsk = 1,
Last = 2,
DoubleLast = 3,
BidAsk = 4,
LastOrBidAsk = 7,
Midpoint = 8,
}
impl From<TriggerMethod> for i32 {
fn from(method: TriggerMethod) -> i32 {
method as i32
}
}
impl From<i32> for TriggerMethod {
fn from(value: i32) -> Self {
match value {
0 => TriggerMethod::Default,
1 => TriggerMethod::DoubleBidAsk,
2 => TriggerMethod::Last,
3 => TriggerMethod::DoubleLast,
4 => TriggerMethod::BidAsk,
7 => TriggerMethod::LastOrBidAsk,
8 => TriggerMethod::Midpoint,
_ => TriggerMethod::Default,
}
}
}
impl crate::ToField for TriggerMethod {
fn to_field(&self) -> String {
i32::from(*self).to_string()
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PriceCondition {
pub contract_id: i32,
pub exchange: String,
pub price: f64,
#[serde(serialize_with = "serialize_trigger_method", deserialize_with = "deserialize_trigger_method")]
pub trigger_method: TriggerMethod,
pub is_more: bool,
pub is_conjunction: bool,
}
impl Default for PriceCondition {
fn default() -> Self {
Self {
contract_id: 0,
exchange: String::new(),
price: 0.0,
trigger_method: TriggerMethod::Default,
is_more: true,
is_conjunction: true,
}
}
}
fn serialize_trigger_method<S>(method: &TriggerMethod, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i32((*method).into())
}
fn deserialize_trigger_method<'de, D>(deserializer: D) -> Result<TriggerMethod, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = i32::deserialize(deserializer)?;
Ok(value.into())
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct TimeCondition {
pub time: String,
pub is_more: bool,
pub is_conjunction: bool,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct MarginCondition {
pub percent: i32,
pub is_more: bool,
pub is_conjunction: bool,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ExecutionCondition {
pub symbol: String,
pub security_type: String,
pub exchange: String,
pub is_conjunction: bool,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct VolumeCondition {
pub contract_id: i32,
pub exchange: String,
pub volume: i32,
pub is_more: bool,
pub is_conjunction: bool,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct PercentChangeCondition {
pub contract_id: i32,
pub exchange: String,
pub percent: f64,
pub is_more: bool,
pub is_conjunction: bool,
}
#[derive(Debug, Clone)]
pub struct PriceConditionBuilder {
contract_id: i32,
exchange: String,
price: Option<f64>,
trigger_method: TriggerMethod,
is_more: bool,
is_conjunction: bool,
}
impl PriceCondition {
pub fn builder(contract_id: i32, exchange: impl Into<String>) -> PriceConditionBuilder {
PriceConditionBuilder::new(contract_id, exchange)
}
}
impl PriceConditionBuilder {
pub fn new(contract_id: i32, exchange: impl Into<String>) -> Self {
Self {
contract_id,
exchange: exchange.into(),
price: None, trigger_method: TriggerMethod::Default, is_more: true, is_conjunction: true, }
}
pub fn greater_than(mut self, price: f64) -> Self {
self.price = Some(price);
self.is_more = true;
self
}
pub fn less_than(mut self, price: f64) -> Self {
self.price = Some(price);
self.is_more = false;
self
}
pub fn trigger_method(mut self, method: TriggerMethod) -> Self {
self.trigger_method = method;
self
}
pub fn conjunction(mut self, is_conjunction: bool) -> Self {
self.is_conjunction = is_conjunction;
self
}
pub fn build(self) -> PriceCondition {
PriceCondition {
contract_id: self.contract_id,
exchange: self.exchange,
price: self
.price
.expect("PriceConditionBuilder requires a price threshold; call greater_than() or less_than() before build()"),
trigger_method: self.trigger_method,
is_more: self.is_more,
is_conjunction: self.is_conjunction,
}
}
}
#[derive(Debug, Clone)]
pub struct TimeConditionBuilder {
time: Option<String>,
is_more: bool,
is_conjunction: bool,
}
impl TimeCondition {
pub fn builder() -> TimeConditionBuilder {
TimeConditionBuilder::new()
}
}
impl TimeConditionBuilder {
pub fn new() -> Self {
Self {
time: None, is_more: true, is_conjunction: true, }
}
pub fn greater_than(mut self, time: impl Into<String>) -> Self {
self.time = Some(time.into());
self.is_more = true;
self
}
pub fn less_than(mut self, time: impl Into<String>) -> Self {
self.time = Some(time.into());
self.is_more = false;
self
}
pub fn conjunction(mut self, is_conjunction: bool) -> Self {
self.is_conjunction = is_conjunction;
self
}
pub fn build(self) -> TimeCondition {
TimeCondition {
time: self
.time
.expect("TimeConditionBuilder requires a time value; call greater_than() or less_than() before build()"),
is_more: self.is_more,
is_conjunction: self.is_conjunction,
}
}
}
impl Default for TimeConditionBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MarginConditionBuilder {
percent: Option<i32>,
is_more: bool,
is_conjunction: bool,
}
impl MarginCondition {
pub fn builder() -> MarginConditionBuilder {
MarginConditionBuilder::new()
}
}
impl MarginConditionBuilder {
pub fn new() -> Self {
Self {
percent: None, is_more: true, is_conjunction: true, }
}
pub fn greater_than(mut self, percent: i32) -> Self {
self.percent = Some(percent);
self.is_more = true;
self
}
pub fn less_than(mut self, percent: i32) -> Self {
self.percent = Some(percent);
self.is_more = false;
self
}
pub fn conjunction(mut self, is_conjunction: bool) -> Self {
self.is_conjunction = is_conjunction;
self
}
pub fn build(self) -> MarginCondition {
MarginCondition {
percent: self
.percent
.expect("MarginConditionBuilder requires a percentage threshold; call greater_than() or less_than() before build()"),
is_more: self.is_more,
is_conjunction: self.is_conjunction,
}
}
}
impl Default for MarginConditionBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ExecutionConditionBuilder {
symbol: String,
security_type: String,
exchange: String,
is_conjunction: bool,
}
impl ExecutionCondition {
pub fn builder(symbol: impl Into<String>, security_type: impl Into<String>, exchange: impl Into<String>) -> ExecutionConditionBuilder {
ExecutionConditionBuilder::new(symbol, security_type, exchange)
}
}
impl ExecutionConditionBuilder {
pub fn new(symbol: impl Into<String>, security_type: impl Into<String>, exchange: impl Into<String>) -> Self {
Self {
symbol: symbol.into(),
security_type: security_type.into(),
exchange: exchange.into(),
is_conjunction: true, }
}
pub fn conjunction(mut self, is_conjunction: bool) -> Self {
self.is_conjunction = is_conjunction;
self
}
pub fn build(self) -> ExecutionCondition {
ExecutionCondition {
symbol: self.symbol,
security_type: self.security_type,
exchange: self.exchange,
is_conjunction: self.is_conjunction,
}
}
}
#[derive(Debug, Clone)]
pub struct VolumeConditionBuilder {
contract_id: i32,
exchange: String,
volume: Option<i32>,
is_more: bool,
is_conjunction: bool,
}
impl VolumeCondition {
pub fn builder(contract_id: i32, exchange: impl Into<String>) -> VolumeConditionBuilder {
VolumeConditionBuilder::new(contract_id, exchange)
}
}
impl VolumeConditionBuilder {
pub fn new(contract_id: i32, exchange: impl Into<String>) -> Self {
Self {
contract_id,
exchange: exchange.into(),
volume: None, is_more: true, is_conjunction: true, }
}
pub fn greater_than(mut self, volume: i32) -> Self {
self.volume = Some(volume);
self.is_more = true;
self
}
pub fn less_than(mut self, volume: i32) -> Self {
self.volume = Some(volume);
self.is_more = false;
self
}
pub fn conjunction(mut self, is_conjunction: bool) -> Self {
self.is_conjunction = is_conjunction;
self
}
pub fn build(self) -> VolumeCondition {
VolumeCondition {
contract_id: self.contract_id,
exchange: self.exchange,
volume: self
.volume
.expect("VolumeConditionBuilder requires a volume threshold; call greater_than() or less_than() before build()"),
is_more: self.is_more,
is_conjunction: self.is_conjunction,
}
}
}
#[derive(Debug, Clone)]
pub struct PercentChangeConditionBuilder {
contract_id: i32,
exchange: String,
percent: Option<f64>,
is_more: bool,
is_conjunction: bool,
}
impl PercentChangeCondition {
pub fn builder(contract_id: i32, exchange: impl Into<String>) -> PercentChangeConditionBuilder {
PercentChangeConditionBuilder::new(contract_id, exchange)
}
}
impl PercentChangeConditionBuilder {
pub fn new(contract_id: i32, exchange: impl Into<String>) -> Self {
Self {
contract_id,
exchange: exchange.into(),
percent: None, is_more: true, is_conjunction: true, }
}
pub fn greater_than(mut self, percent: f64) -> Self {
self.percent = Some(percent);
self.is_more = true;
self
}
pub fn less_than(mut self, percent: f64) -> Self {
self.percent = Some(percent);
self.is_more = false;
self
}
pub fn conjunction(mut self, is_conjunction: bool) -> Self {
self.is_conjunction = is_conjunction;
self
}
pub fn build(self) -> PercentChangeCondition {
PercentChangeCondition {
contract_id: self.contract_id,
exchange: self.exchange,
percent: self
.percent
.expect("PercentChangeConditionBuilder requires a threshold; call greater_than() or less_than() before build()"),
is_more: self.is_more,
is_conjunction: self.is_conjunction,
}
}
}
impl From<PriceConditionBuilder> for crate::orders::OrderCondition {
fn from(builder: PriceConditionBuilder) -> Self {
crate::orders::OrderCondition::Price(builder.build())
}
}
impl From<TimeConditionBuilder> for crate::orders::OrderCondition {
fn from(builder: TimeConditionBuilder) -> Self {
crate::orders::OrderCondition::Time(builder.build())
}
}
impl From<MarginConditionBuilder> for crate::orders::OrderCondition {
fn from(builder: MarginConditionBuilder) -> Self {
crate::orders::OrderCondition::Margin(builder.build())
}
}
impl From<VolumeConditionBuilder> for crate::orders::OrderCondition {
fn from(builder: VolumeConditionBuilder) -> Self {
crate::orders::OrderCondition::Volume(builder.build())
}
}
impl From<PercentChangeConditionBuilder> for crate::orders::OrderCondition {
fn from(builder: PercentChangeConditionBuilder) -> Self {
crate::orders::OrderCondition::PercentChange(builder.build())
}
}
impl From<ExecutionConditionBuilder> for crate::orders::OrderCondition {
fn from(builder: ExecutionConditionBuilder) -> Self {
crate::orders::OrderCondition::Execution(builder.build())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_price_condition_builder() {
let condition = PriceCondition::builder(12345, "NASDAQ")
.greater_than(150.0)
.trigger_method(TriggerMethod::DoubleBidAsk)
.conjunction(false)
.build();
assert_eq!(condition.contract_id, 12345);
assert_eq!(condition.exchange, "NASDAQ");
assert_eq!(condition.price, 150.0);
assert_eq!(condition.trigger_method, TriggerMethod::DoubleBidAsk);
assert!(condition.is_more);
assert!(!condition.is_conjunction);
}
#[test]
fn test_time_condition_builder() {
let condition = TimeCondition::builder().less_than("20251230 23:59:59 UTC").build();
assert_eq!(condition.time, "20251230 23:59:59 UTC");
assert!(!condition.is_more);
assert!(condition.is_conjunction);
}
#[test]
fn test_margin_condition_builder() {
let condition = MarginCondition::builder().less_than(30).conjunction(false).build();
assert_eq!(condition.percent, 30);
assert!(!condition.is_more);
assert!(!condition.is_conjunction);
}
#[test]
fn test_execution_condition_builder() {
let condition = ExecutionCondition::builder("AAPL", "STK", "SMART").conjunction(false).build();
assert_eq!(condition.symbol, "AAPL");
assert_eq!(condition.security_type, "STK");
assert_eq!(condition.exchange, "SMART");
assert!(!condition.is_conjunction);
}
#[test]
fn test_volume_condition_builder() {
let condition = VolumeCondition::builder(12345, "NASDAQ").less_than(1000000).build();
assert_eq!(condition.contract_id, 12345);
assert_eq!(condition.exchange, "NASDAQ");
assert_eq!(condition.volume, 1000000);
assert!(!condition.is_more);
assert!(condition.is_conjunction);
}
#[test]
fn test_percent_change_condition_builder() {
let condition = PercentChangeCondition::builder(12345, "NASDAQ")
.greater_than(5.0)
.conjunction(false)
.build();
assert_eq!(condition.contract_id, 12345);
assert_eq!(condition.exchange, "NASDAQ");
assert_eq!(condition.percent, 5.0);
assert!(condition.is_more);
assert!(!condition.is_conjunction);
}
#[test]
fn test_default_values() {
let condition = PriceCondition::builder(12345, "NASDAQ").greater_than(150.0).build();
assert_eq!(condition.trigger_method, TriggerMethod::Default);
assert!(condition.is_more);
assert!(condition.is_conjunction);
}
#[test]
#[should_panic(expected = "PriceConditionBuilder requires a price threshold")]
fn test_price_condition_builder_missing_threshold_panics() {
let _ = PriceCondition::builder(12345, "NASDAQ").build();
}
#[test]
#[should_panic(expected = "TimeConditionBuilder requires a time value")]
fn test_time_condition_builder_missing_time_panics() {
let _ = TimeCondition::builder().build();
}
#[test]
#[should_panic(expected = "MarginConditionBuilder requires a percentage threshold")]
fn test_margin_condition_builder_missing_threshold_panics() {
let _ = MarginCondition::builder().build();
}
#[test]
#[should_panic(expected = "VolumeConditionBuilder requires a volume threshold")]
fn test_volume_condition_builder_missing_threshold_panics() {
let _ = VolumeCondition::builder(12345, "NASDAQ").build();
}
#[test]
#[should_panic(expected = "PercentChangeConditionBuilder requires a threshold")]
fn test_percent_change_condition_builder_missing_threshold_panics() {
let _ = PercentChangeCondition::builder(12345, "NASDAQ").build();
}
}