1use std::fmt;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
14#[serde(rename_all = "lowercase")]
15pub enum Side {
16 Buy,
18 Sell,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
25#[serde(rename_all = "lowercase")]
26pub enum OptionPositionEffect {
27 Open,
29 Close,
31}
32
33impl fmt::Display for OptionPositionEffect {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::Open => write!(formatter, "open"),
37 Self::Close => write!(formatter, "close"),
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
44#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
45#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
46#[serde(rename_all = "lowercase")]
47pub enum OrderType {
48 Market,
50 Limit,
52}
53
54impl fmt::Display for OrderType {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::Market => write!(f, "market"),
58 Self::Limit => write!(f, "limit"),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
65#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
66#[serde(rename_all = "lowercase")]
67pub enum TimeInForce {
68 Gfd,
70 Gtc,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
77#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
78#[serde(rename_all = "lowercase")]
79pub enum Trigger {
80 Immediate,
82 Stop,
84}
85
86impl fmt::Display for Trigger {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 Self::Immediate => write!(f, "immediate"),
90 Self::Stop => write!(f, "stop"),
91 }
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
98#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
99#[serde(rename_all = "snake_case")]
100pub enum MarketHours {
101 #[cfg_attr(feature = "clap", value(name = "regular"))]
103 RegularHours,
104 #[cfg_attr(feature = "clap", value(name = "extended"))]
106 ExtendedHours,
107 #[cfg_attr(feature = "clap", value(name = "all-day"))]
109 AllDayHours,
110}
111
112impl fmt::Display for MarketHours {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 Self::RegularHours => write!(f, "regular"),
116 Self::ExtendedHours => write!(f, "extended"),
117 Self::AllDayHours => write!(f, "all-day"),
118 }
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq)]
124pub enum OrderAmount {
125 Quantity(f64),
127 DollarAmount(f64),
129}
130
131#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
133pub struct CancelAllOutcome {
134 pub cancelled: Vec<String>,
136 pub failed: Vec<CancelAllFailure>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142pub struct CancelAllFailure {
143 pub order_id: Option<String>,
145 pub error: String,
147}
148
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151pub struct StockOrder {
152 pub id: Option<String>,
154 pub account: Option<String>,
156 pub instrument: Option<String>,
158 pub symbol: Option<String>,
160 pub side: Option<String>,
162 pub quantity: Option<String>,
164 pub price: Option<String>,
166 pub average_price: Option<String>,
168 pub cumulative_quantity: Option<String>,
170 pub state: Option<String>,
172 pub created_at: Option<String>,
174 pub updated_at: Option<String>,
176 pub cancel: Option<String>,
178 #[serde(rename = "type")]
180 pub order_type: Option<String>,
181 pub time_in_force: Option<String>,
183 pub extended_hours: Option<bool>,
185 pub stop_price: Option<String>,
187 pub trigger: Option<String>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct OptionOrder {
194 pub id: Option<String>,
196 pub chain_id: Option<String>,
198 pub chain_symbol: Option<String>,
200 pub direction: Option<String>,
202 pub legs: Option<Vec<serde_json::Value>>,
204 pub premium: Option<String>,
206 pub price: Option<String>,
208 pub processed_premium: Option<String>,
210 pub quantity: Option<String>,
212 pub state: Option<String>,
214 pub time_in_force: Option<String>,
216 #[serde(rename = "type")]
218 pub order_type: Option<String>,
219 pub created_at: Option<String>,
221 pub updated_at: Option<String>,
223 pub cancel_url: Option<String>,
225}
226
227#[derive(Debug, Clone)]
229pub struct StockOrderRequest {
230 pub symbol: String,
232 pub amount: OrderAmount,
234 pub side: Side,
236 pub order_type: OrderType,
238 pub limit_price: Option<f64>,
240 pub trigger: Trigger,
242 pub stop_price: Option<f64>,
244 pub time_in_force: TimeInForce,
246 pub market_hours: MarketHours,
248}
249
250#[derive(Debug, Clone)]
252pub struct OptionOrderRequest {
253 pub symbol: String,
255 pub expiration_date: String,
257 pub strike_price: f64,
259 pub option_type: String,
261 pub side: Side,
263 pub quantity: f64,
265 pub limit_price: f64,
267 pub position_effect: OptionPositionEffect,
269 pub time_in_force: TimeInForce,
271}
272
273#[derive(Debug, Serialize)]
275pub(crate) struct DollarBasedAmount {
276 pub amount: String,
277 pub currency_code: String,
278}
279
280#[derive(Debug, Serialize)]
281pub(crate) struct StockOrderPayload {
282 pub account: String,
283 pub instrument: String,
284 pub symbol: String,
285 pub quantity: String,
286 pub side: Side,
287 #[serde(rename = "type")]
288 pub order_type: OrderType,
289 pub time_in_force: TimeInForce,
290 pub trigger: Trigger,
291 pub market_hours: MarketHours,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub price: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub stop_price: Option<String>,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub dollar_based_amount: Option<DollarBasedAmount>,
298}
299
300#[derive(Debug, Serialize)]
301pub(crate) struct OptionOrderPayload {
302 pub account: String,
303 pub direction: String,
304 pub time_in_force: TimeInForce,
305 pub legs: Vec<OptionLeg>,
306 #[serde(rename = "type")]
307 pub order_type: &'static str,
308 pub trigger: &'static str,
309 pub price: String,
310 pub quantity: String,
311 pub override_day_trade_checks: bool,
312 pub override_dtbp_checks: bool,
313 pub ref_id: String,
314}
315
316#[derive(Debug, Serialize)]
317pub(crate) struct OptionLeg {
318 pub position_effect: OptionPositionEffect,
319 pub side: Side,
320 pub ratio_quantity: u32,
321 pub option: String,
322}
323
324#[cfg(test)]
325mod tests {
326 use super::{CancelAllFailure, CancelAllOutcome};
327
328 #[test]
329 fn cancel_all_outcome_serializes_round_trip() {
330 let outcome = CancelAllOutcome {
331 cancelled: vec!["order-1".to_string(), "order-2".to_string()],
332 failed: vec![
333 CancelAllFailure {
334 order_id: Some("order-3".to_string()),
335 error: "already filled".to_string(),
336 },
337 CancelAllFailure {
338 order_id: None,
339 error: "open order was missing an ID".to_string(),
340 },
341 ],
342 };
343
344 let json = serde_json::to_string(&outcome).unwrap();
345 let round_tripped: CancelAllOutcome = serde_json::from_str(&json).unwrap();
346
347 assert_eq!(round_tripped, outcome);
348 }
349}