rustrade_execution/order/request.rs
1use crate::{
2 error::OrderError,
3 order::{
4 OrderEvent, OrderKind, TimeInForce,
5 id::{OrderId, PositionId},
6 state::Cancelled,
7 },
8};
9use derive_more::Constructor;
10use rust_decimal::Decimal;
11use rustrade_instrument::{
12 Side,
13 asset::{AssetIndex, name::AssetNameExchange},
14 exchange::{ExchangeId, ExchangeIndex},
15 instrument::{InstrumentIndex, name::InstrumentNameExchange},
16};
17use serde::{Deserialize, Serialize};
18
19pub type OrderRequestOpen<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> =
20 OrderEvent<RequestOpen, ExchangeKey, InstrumentKey>;
21
22pub type OrderRequestCancel<ExchangeKey = ExchangeIndex, InstrumentKey = InstrumentIndex> =
23 OrderEvent<RequestCancel, ExchangeKey, InstrumentKey>;
24
25pub type OrderResponseCancel<
26 ExchangeKey = ExchangeIndex,
27 AssetKey = AssetIndex,
28 InstrumentKey = InstrumentIndex,
29> = OrderEvent<Result<Cancelled, OrderError<AssetKey, InstrumentKey>>, ExchangeKey, InstrumentKey>;
30
31pub type UnindexedOrderResponseCancel =
32 OrderResponseCancel<ExchangeId, AssetNameExchange, InstrumentNameExchange>;
33
34/// Parameters for opening a new order.
35///
36/// # Warning: `reduce_only` Default Behavior
37///
38/// The `reduce_only` field defaults to `false`, which means:
39/// - A `Sell` order defaults to `SellToOpen` (open short / write option)
40/// - A `Buy` order defaults to `BuyToOpen` (open long)
41///
42/// **For closing positions, callers MUST explicitly set `reduce_only: true`.**
43/// Failure to do so on non-crypto instruments (equities, options) will:
44/// - On non-margin accounts: result in a 422 rejection from the exchange
45/// - On margin accounts: silently open a short position instead of closing the long
46///
47/// The `close_open_positions_with_market_orders`
48/// helper sets this correctly. Direct `RequestOpen` construction must handle it manually.
49#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
50pub struct RequestOpen {
51 pub side: Side,
52 /// Limit price for the order. Required for Limit/StopLimit/TrailingStopLimit orders.
53 /// `None` for Market/Stop/TrailingStop orders (which execute at market price when triggered).
54 pub price: Option<Decimal>,
55 pub quantity: Decimal,
56 pub kind: OrderKind,
57 pub time_in_force: TimeInForce,
58 /// Target `PositionId` for this order in `OmsMode::Hedging`.
59 ///
60 /// For opening orders: the position this fill should open or add to.
61 /// For closing orders: the position this fill should reduce or close.
62 /// In `OmsMode::Netting`, leave as `None` (ignored).
63 #[serde(default)]
64 pub position_id: Option<PositionId>,
65 /// Constrain this order to only reduce existing positions, never open new ones.
66 ///
67 /// Used by exchanges that require explicit open/close intent (Alpaca, Interactive
68 /// Brokers, Schwab). Adapters derive venue-specific semantics from this flag + `side`:
69 /// - `reduce_only=false, Buy` → BuyToOpen (open long / add to long)
70 /// - `reduce_only=false, Sell` → SellToOpen (open short / write option)
71 /// - `reduce_only=true, Buy` → BuyToClose (close short)
72 /// - `reduce_only=true, Sell` → SellToClose (close long)
73 ///
74 /// Exchanges that infer intent from positions (Binance, Deribit) may map this to
75 /// their `reduceOnly` parameter or ignore it entirely.
76 #[serde(default)]
77 pub reduce_only: bool,
78}
79
80#[derive(
81 Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Deserialize, Serialize, Constructor,
82)]
83pub struct RequestCancel {
84 pub id: Option<OrderId>,
85}