Skip to main content

schwab_sdk/orders/
enums.rs

1//! String-valued enums shared across the orders request/response shapes.
2//!
3//! Every variant has a `Unknown(String)` catch-all so wire values added by
4//! Schwab after this crate was published deserialize cleanly. The
5//! `string_enum!` macro lives in [`crate::macros`].
6
7use crate::macros::string_enum;
8
9string_enum! {
10    /// Which trading session the order is valid in.
11    Session {
12        /// Regular session.
13        Normal = "NORMAL",
14        /// Pre-market session.
15        Am = "AM",
16        /// Post-market session.
17        Pm = "PM",
18        /// All sessions; Schwab routes wherever the order is fillable.
19        Seamless = "SEAMLESS",
20    }
21}
22
23string_enum! {
24    /// Time-in-force for an order.
25    Duration {
26        /// Expires at the end of the regular session.
27        Day = "DAY",
28        /// Stays open until filled or explicitly cancelled.
29        GoodTillCancel = "GOOD_TILL_CANCEL",
30        /// Fill the entire order immediately or cancel it.
31        FillOrKill = "FILL_OR_KILL",
32        /// Fill whatever can fill immediately; cancel the rest.
33        ImmediateOrCancel = "IMMEDIATE_OR_CANCEL",
34        /// Expires at the end of the trading week.
35        EndOfWeek = "END_OF_WEEK",
36        /// Expires at the end of the trading month.
37        EndOfMonth = "END_OF_MONTH",
38        /// Expires at the end of the next trading month.
39        NextEndOfMonth = "NEXT_END_OF_MONTH",
40        /// Schwab sent the literal string `"UNKNOWN"`.
41        UnknownSchwab = "UNKNOWN",
42    }
43}
44
45string_enum! {
46    /// How the order's fill price is determined.
47    OrderType {
48        /// Fill at the best available market price.
49        Market = "MARKET",
50        /// Fill at the specified price or better.
51        Limit = "LIMIT",
52        /// Becomes a market order once the stop is touched.
53        Stop = "STOP",
54        /// Becomes a limit order once the stop is touched.
55        StopLimit = "STOP_LIMIT",
56        /// Stop that follows the market at a fixed offset.
57        TrailingStop = "TRAILING_STOP",
58        /// Cabinet (zero-premium) options trade.
59        Cabinet = "CABINET",
60        /// Limit order priced away from the inside market.
61        NonMarketable = "NON_MARKETABLE",
62        /// Market order executed in the closing auction.
63        MarketOnClose = "MARKET_ON_CLOSE",
64        /// Exercise of a long option.
65        Exercise = "EXERCISE",
66        /// Trailing stop that becomes a limit order once triggered.
67        TrailingStopLimit = "TRAILING_STOP_LIMIT",
68        /// Multi-leg order with a net debit price.
69        NetDebit = "NET_DEBIT",
70        /// Multi-leg order with a net credit price.
71        NetCredit = "NET_CREDIT",
72        /// Multi-leg order with a net price of zero.
73        NetZero = "NET_ZERO",
74        /// Limit order executed in the closing auction.
75        LimitOnClose = "LIMIT_ON_CLOSE",
76        /// Schwab sent the literal string `"UNKNOWN"`.
77        UnknownSchwab = "UNKNOWN",
78    }
79}
80
81string_enum! {
82    /// Top-level structure of an order envelope.
83    OrderStrategyType {
84        /// Single-leg order.
85        Single = "SINGLE",
86        /// Cancel an existing order.
87        Cancel = "CANCEL",
88        /// Recall an existing order.
89        Recall = "RECALL",
90        /// Pair-trade strategy.
91        Pair = "PAIR",
92        /// Flatten an account or position.
93        Flatten = "FLATTEN",
94        /// Two-day swap strategy.
95        TwoDaySwap = "TWO_DAY_SWAP",
96        /// Blast-all (send to multiple venues).
97        BlastAll = "BLAST_ALL",
98        /// One-cancels-other.
99        Oco = "OCO",
100        /// One-triggers-other.
101        Trigger = "TRIGGER",
102    }
103}
104
105string_enum! {
106    /// Multi-leg option strategy shape.
107    ComplexOrderStrategyType {
108        /// Not a complex strategy.
109        None = "NONE",
110        /// Covered call / covered put.
111        Covered = "COVERED",
112        /// Vertical spread.
113        Vertical = "VERTICAL",
114        /// Back-ratio spread.
115        BackRatio = "BACK_RATIO",
116        /// Calendar (horizontal) spread.
117        Calendar = "CALENDAR",
118        /// Diagonal spread.
119        Diagonal = "DIAGONAL",
120        /// Straddle.
121        Straddle = "STRADDLE",
122        /// Strangle.
123        Strangle = "STRANGLE",
124        /// Synthetic collar.
125        CollarSynthetic = "COLLAR_SYNTHETIC",
126        /// Butterfly.
127        Butterfly = "BUTTERFLY",
128        /// Condor.
129        Condor = "CONDOR",
130        /// Iron condor.
131        IronCondor = "IRON_CONDOR",
132        /// Vertical roll.
133        VerticalRoll = "VERTICAL_ROLL",
134        /// Collar paired with the underlying stock.
135        CollarWithStock = "COLLAR_WITH_STOCK",
136        /// Double diagonal.
137        DoubleDiagonal = "DOUBLE_DIAGONAL",
138        /// Unbalanced butterfly.
139        UnbalancedButterfly = "UNBALANCED_BUTTERFLY",
140        /// Unbalanced condor.
141        UnbalancedCondor = "UNBALANCED_CONDOR",
142        /// Unbalanced iron condor.
143        UnbalancedIronCondor = "UNBALANCED_IRON_CONDOR",
144        /// Unbalanced vertical roll.
145        UnbalancedVerticalRoll = "UNBALANCED_VERTICAL_ROLL",
146        /// Mutual-fund swap.
147        MutualFundSwap = "MUTUAL_FUND_SWAP",
148        /// Custom strategy that does not match a named pattern.
149        Custom = "CUSTOM",
150    }
151}
152
153string_enum! {
154    /// Trade direction / intent for an order leg.
155    Instruction {
156        /// Buy.
157        Buy = "BUY",
158        /// Sell.
159        Sell = "SELL",
160        /// Buy shares to cover an existing short position.
161        BuyToCover = "BUY_TO_COVER",
162        /// Open a new short position.
163        SellShort = "SELL_SHORT",
164        /// Open a long option position.
165        BuyToOpen = "BUY_TO_OPEN",
166        /// Close a long option position.
167        BuyToClose = "BUY_TO_CLOSE",
168        /// Open a short option position.
169        SellToOpen = "SELL_TO_OPEN",
170        /// Close a short option position.
171        SellToClose = "SELL_TO_CLOSE",
172        /// Mutual-fund exchange.
173        Exchange = "EXCHANGE",
174        /// Short sale exempt from the SEC short-sale price test.
175        SellShortExempt = "SELL_SHORT_EXEMPT",
176    }
177}
178
179string_enum! {
180    /// Lifecycle status of an order in Schwab's system.
181    ApiOrderStatus {
182        /// Waiting for a parent order in a multi-leg strategy.
183        AwaitingParentOrder = "AWAITING_PARENT_ORDER",
184        /// Waiting for a trigger condition to be met.
185        AwaitingCondition = "AWAITING_CONDITION",
186        /// Waiting for a stop condition to be met.
187        AwaitingStopCondition = "AWAITING_STOP_CONDITION",
188        /// Pending manual review by a Schwab rep.
189        AwaitingManualReview = "AWAITING_MANUAL_REVIEW",
190        /// Schwab accepted the order.
191        Accepted = "ACCEPTED",
192        /// Awaiting an "unable to route" outcome.
193        AwaitingUrOut = "AWAITING_UR_OUT",
194        /// Activation pending (e.g. for stop / trigger orders).
195        PendingActivation = "PENDING_ACTIVATION",
196        /// Queued for routing to a venue.
197        Queued = "QUEUED",
198        /// Live at the venue and working for a fill.
199        Working = "WORKING",
200        /// Schwab or the venue rejected the order.
201        Rejected = "REJECTED",
202        /// Cancel request submitted, not yet confirmed.
203        PendingCancel = "PENDING_CANCEL",
204        /// Cancelled.
205        Canceled = "CANCELED",
206        /// Replace request submitted, not yet confirmed.
207        PendingReplace = "PENDING_REPLACE",
208        /// Replaced; the original order is no longer valid.
209        Replaced = "REPLACED",
210        /// Filled in full.
211        Filled = "FILLED",
212        /// Expired (e.g. unfilled day order at session end).
213        Expired = "EXPIRED",
214        /// Newly submitted, not yet acked.
215        New = "NEW",
216        /// Waiting for a scheduled release time.
217        AwaitingReleaseTime = "AWAITING_RELEASE_TIME",
218        /// Awaiting venue acknowledgement.
219        PendingAcknowledgement = "PENDING_ACKNOWLEDGEMENT",
220        /// Recall request submitted.
221        PendingRecall = "PENDING_RECALL",
222        /// Schwab sent the literal string `"UNKNOWN"`.
223        UnknownSchwab = "UNKNOWN",
224    }
225}
226
227string_enum! {
228    /// What price feed triggers a stop order.
229    StopType {
230        /// Default stop type for the venue.
231        Standard = "STANDARD",
232        /// Trigger when the bid touches the stop.
233        Bid = "BID",
234        /// Trigger when the ask touches the stop.
235        Ask = "ASK",
236        /// Trigger when the last trade touches the stop.
237        Last = "LAST",
238        /// Trigger when the mark touches the stop.
239        Mark = "MARK",
240    }
241}
242
243string_enum! {
244    /// Reference price for a linked stop.
245    StopPriceLinkBasis {
246        /// Caller supplies the stop price directly.
247        Manual = "MANUAL",
248        /// Tied to the order's base price.
249        Base = "BASE",
250        /// Tied to the order's trigger price.
251        Trigger = "TRIGGER",
252        /// Tied to the instrument's last trade.
253        Last = "LAST",
254        /// Tied to the bid.
255        Bid = "BID",
256        /// Tied to the ask.
257        Ask = "ASK",
258        /// Tied to the ask / bid spread.
259        AskBid = "ASK_BID",
260        /// Tied to the mark.
261        Mark = "MARK",
262        /// Tied to an averaged reference price.
263        Average = "AVERAGE",
264    }
265}
266
267string_enum! {
268    /// How the stop offset is interpreted.
269    StopPriceLinkType {
270        /// Absolute dollar offset.
271        Value = "VALUE",
272        /// Percentage offset.
273        Percent = "PERCENT",
274        /// Offset measured in ticks.
275        Tick = "TICK",
276    }
277}
278
279string_enum! {
280    /// Reference price for a linked limit.
281    PriceLinkBasis {
282        /// Caller supplies the limit price directly.
283        Manual = "MANUAL",
284        /// Tied to the order's base price.
285        Base = "BASE",
286        /// Tied to the order's trigger price.
287        Trigger = "TRIGGER",
288        /// Tied to the instrument's last trade.
289        Last = "LAST",
290        /// Tied to the bid.
291        Bid = "BID",
292        /// Tied to the ask.
293        Ask = "ASK",
294        /// Tied to the ask / bid spread.
295        AskBid = "ASK_BID",
296        /// Tied to the mark.
297        Mark = "MARK",
298        /// Tied to an averaged reference price.
299        Average = "AVERAGE",
300    }
301}
302
303string_enum! {
304    /// How the limit offset is interpreted.
305    PriceLinkType {
306        /// Absolute dollar offset.
307        Value = "VALUE",
308        /// Percentage offset.
309        Percent = "PERCENT",
310        /// Offset measured in ticks.
311        Tick = "TICK",
312    }
313}
314
315string_enum! {
316    /// Tax-lot relief method to apply when closing positions.
317    TaxLotMethod {
318        /// First-in, first-out.
319        Fifo = "FIFO",
320        /// Last-in, first-out.
321        Lifo = "LIFO",
322        /// Highest cost basis first.
323        HighCost = "HIGH_COST",
324        /// Lowest cost basis first.
325        LowCost = "LOW_COST",
326        /// Average cost basis.
327        AverageCost = "AVERAGE_COST",
328        /// Caller-specified lot.
329        SpecificLot = "SPECIFIC_LOT",
330        /// Schwab's loss-harvester selection.
331        LossHarvester = "LOSS_HARVESTER",
332    }
333}
334
335string_enum! {
336    /// Special-instruction flag attached to an order.
337    SpecialInstruction {
338        /// All-or-none: do not fill partially.
339        AllOrNone = "ALL_OR_NONE",
340        /// Do-not-reduce on ex-dividend day.
341        DoNotReduce = "DO_NOT_REDUCE",
342        /// Both all-or-none and do-not-reduce.
343        AllOrNoneDoNotReduce = "ALL_OR_NONE_DO_NOT_REDUCE",
344    }
345}
346
347string_enum! {
348    /// Explicit venue the order should be routed to.
349    RequestedDestination {
350        /// INET (Nasdaq's ECN).
351        Inet = "INET",
352        /// NYSE Arca ECN.
353        EcnArca = "ECN_ARCA",
354        /// Cboe.
355        Cboe = "CBOE",
356        /// NYSE American (formerly AMEX).
357        Amex = "AMEX",
358        /// Philadelphia Stock Exchange.
359        Phlx = "PHLX",
360        /// International Securities Exchange.
361        Ise = "ISE",
362        /// Boston Options Exchange.
363        Box_ = "BOX",
364        /// New York Stock Exchange.
365        Nyse = "NYSE",
366        /// Nasdaq.
367        Nasdaq = "NASDAQ",
368        /// BATS Global Markets.
369        Bats = "BATS",
370        /// Cboe C2.
371        C2 = "C2",
372        /// Let Schwab choose the venue.
373        Auto = "AUTO",
374    }
375}
376
377string_enum! {
378    /// Asset class of an order leg.
379    OrderLegType {
380        /// Listed equity.
381        Equity = "EQUITY",
382        /// Listed option.
383        Option = "OPTION",
384        /// Index.
385        Index = "INDEX",
386        /// Mutual fund.
387        MutualFund = "MUTUAL_FUND",
388        /// Cash equivalent.
389        CashEquivalent = "CASH_EQUIVALENT",
390        /// Fixed income.
391        FixedIncome = "FIXED_INCOME",
392        /// Currency.
393        Currency = "CURRENCY",
394        /// Collective investment vehicle.
395        CollectiveInvestment = "COLLECTIVE_INVESTMENT",
396    }
397}
398
399string_enum! {
400    /// Whether a leg opens or closes a position.
401    PositionEffect {
402        /// Opening a new position.
403        Opening = "OPENING",
404        /// Closing an existing position.
405        Closing = "CLOSING",
406        /// Schwab determines the effect automatically.
407        Automatic = "AUTOMATIC",
408    }
409}
410
411string_enum! {
412    /// How a leg's quantity is denominated.
413    QuantityType {
414        /// Close out the entire existing position.
415        AllShares = "ALL_SHARES",
416        /// Dollar-denominated (fractional shares).
417        Dollars = "DOLLARS",
418        /// Whole-share count.
419        Shares = "SHARES",
420    }
421}
422
423string_enum! {
424    /// Mutual-fund dividend / capital-gains handling.
425    DivCapGains {
426        /// Reinvest distributions back into the fund.
427        Reinvest = "REINVEST",
428        /// Pay out distributions as cash.
429        Payout = "PAYOUT",
430    }
431}
432
433string_enum! {
434    /// Lifecycle event kind on an order's activity history.
435    OrderActivityType {
436        /// Execution (fill).
437        Execution = "EXECUTION",
438        /// Order lifecycle action (place / replace / cancel).
439        OrderAction = "ORDER_ACTION",
440    }
441}
442
443string_enum! {
444    /// Execution-event kind. Schwab currently only emits `FILL`.
445    ExecutionType {
446        /// Fill (partial or complete).
447        Fill = "FILL",
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454
455    #[test]
456    fn unknown_status_preserves_raw_string() {
457        let parsed: ApiOrderStatus = serde_json::from_str(r#""SOME_NEW_STATE""#).unwrap();
458        assert!(matches!(parsed, ApiOrderStatus::Unknown(ref s) if s == "SOME_NEW_STATE"));
459        assert_eq!(
460            serde_json::to_string(&parsed).unwrap(),
461            r#""SOME_NEW_STATE""#
462        );
463    }
464
465    #[test]
466    fn unknown_order_type_preserves_raw_string() {
467        let parsed: OrderType = serde_json::from_str(r#""NEW_TYPE""#).unwrap();
468        assert!(matches!(parsed, OrderType::Unknown(ref s) if s == "NEW_TYPE"));
469    }
470
471    #[test]
472    fn order_status_round_trips_each_known_variant() {
473        for raw in [
474            "AWAITING_PARENT_ORDER",
475            "ACCEPTED",
476            "WORKING",
477            "FILLED",
478            "REJECTED",
479            "CANCELED",
480            "EXPIRED",
481            "PENDING_CANCEL",
482            "PENDING_REPLACE",
483            "REPLACED",
484        ] {
485            let json = format!(r#""{raw}""#);
486            let parsed: ApiOrderStatus = serde_json::from_str(&json).unwrap();
487            assert_eq!(serde_json::to_string(&parsed).unwrap(), json);
488        }
489    }
490}