Skip to main content

polyester/models/
trading.rs

1//! Trading read/write models (Go `models/trading.go` parity).
2
3use crate::types::{AssetAmount, Price, Quantity};
4use buffa_types::google::protobuf::Timestamp;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Order {
8    pub order_id: String,
9    pub symbol_id: u32,
10    pub client_order_id: String,
11    pub side: String,
12    pub status: String,
13    pub order_type: String,
14    pub tif: String,
15    pub orig_qty: Option<Quantity>,
16    pub cum_qty: Option<Quantity>,
17    pub leaves_qty: Option<Quantity>,
18    pub price: Option<Price>,
19    pub avg_px: Option<Price>,
20    pub created_ts_ns: String,
21    pub version: u32,
22    pub post_only: bool,
23    /// Asset selected to pay fees: `quote`, `base`, or an
24    /// `UNKNOWN(<number>)` forward-compatible enum value.
25    pub fee_asset: String,
26    /// Hard all-in quote debit submitted with quote-budget sizing, when used.
27    pub submitted_max_quote_debit_scaled: Option<i64>,
28    /// Attached risk policy when requested via `include_attached_risk`.
29    pub attached_risk: Option<AttachedRisk>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct OrdersList {
34    pub orders: Vec<Order>,
35    pub next_page_token: String,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct OrderMutationResult {
40    pub status: String,
41    pub order_id: String,
42    pub client_order_id: String,
43    /// Gross base quantity resolved by the admission service.
44    pub resolved_base_qty: Option<Quantity>,
45    /// Hard all-in quote debit submitted with quote-budget sizing, when used.
46    pub submitted_max_quote_debit_scaled: Option<i64>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct GetOrderResult {
51    pub order: Option<Order>,
52    pub trades: Vec<UserTrade>,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct UserTrade {
57    pub symbol_id: u32,
58    pub match_id: String,
59    pub order_id: String,
60    pub side: String,
61    pub is_maker: bool,
62    pub price: Option<Price>,
63    pub qty: Option<Quantity>,
64    pub fee_scaled: String,
65    /// Asset used to pay the fee: `quote`, `base`, or an
66    /// `UNKNOWN(<number>)` forward-compatible enum value.
67    pub fee_asset: String,
68    pub referral_share_scaled: String,
69    pub ts_ns: String,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct UserTradesList {
74    pub trades: Vec<UserTrade>,
75    pub next_page_token: String,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct ModifyOrderResult {
80    pub action_taken: String,
81    pub old_order_id: String,
82    pub final_order_id: String,
83    pub code: String,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct CancelAllOrdersResult {
88    pub status: String,
89    pub matched_orders: u32,
90    pub submitted_cancels: u32,
91    pub failed_cancels: u32,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct BatchCreateResultItem {
96    pub status: String,
97    pub order_id: String,
98    pub client_order_id: String,
99    pub code: String,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct BatchCreateOrdersResult {
104    pub results: Vec<BatchCreateResultItem>,
105    pub accepted_count: u32,
106    pub rejected_count: u32,
107}
108
109/// Identifies an order by exactly one of exchange order id or client order id.
110///
111/// Matches TypeScript/Go oneOf semantics for get/cancel/modify and batch items.
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub enum OrderKey {
114    OrderId(String),
115    ClientOrderId(String),
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct BatchCancelItem {
120    pub key: OrderKey,
121    pub symbol_id: Option<u32>,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct BatchCancelResultItem {
126    pub status: String,
127    pub order_id: String,
128    pub client_order_id: String,
129    pub code: String,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct BatchCancelOrdersResult {
134    pub results: Vec<BatchCancelResultItem>,
135    pub accepted_count: u32,
136    pub rejected_count: u32,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct BatchReplaceItem {
141    pub key: OrderKey,
142    pub new_price: Option<Price>,
143    pub new_qty: Option<Quantity>,
144    pub new_attached_risk: Option<AttachedRisk>,
145    pub new_client_order_id: Option<String>,
146}
147
148/// Typed single-order modify params.
149#[derive(Debug, Clone)]
150pub struct ModifyOrderParams {
151    pub symbol: String,
152    pub key: OrderKey,
153    pub subaccount_id: Option<u64>,
154    /// Optional mutation request id (API-required on the wire).
155    ///
156    /// When omitted or blank, the SDK generates a unique id (TypeScript/Go/Python parity).
157    /// Set a stable non-empty value when you may retry the same logical modification after an
158    /// ambiguous failure, and reuse that same value on retry. A blind retry that omits
159    /// `request_id` mints a *new* id and is not an idempotent replay.
160    pub request_id: Option<String>,
161    pub new_price: Option<Price>,
162    pub new_qty: Option<Quantity>,
163    pub new_attached_risk: Option<AttachedRisk>,
164    pub behavior: Option<String>,
165    pub new_client_order_id: Option<String>,
166}
167
168/// Price source requested for trigger evaluation.
169///
170/// Attached order risk currently evaluates against last trade and cannot
171/// encode a caller-selected source. Standalone triggers expose their own
172/// supported semantics.
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub enum TriggerPriceSourceKind {
175    LastPrice,
176    IndexPrice,
177    MarkPrice,
178}
179
180/// Take-profit or stop-loss leg (trigger + optional LIMIT child).
181#[derive(Debug, Clone, PartialEq, Eq)]
182pub struct RiskLeg {
183    pub trigger_price: Price,
184    /// Deprecated for attached risk: any supplied value is rejected because
185    /// the wire contract always evaluates against last trade.
186    #[deprecated(
187        note = "attached risk always uses last trade; supplying trigger_price_source is rejected"
188    )]
189    pub trigger_price_source: Option<TriggerPriceSourceKind>,
190    pub order_type: Option<CreateOrderType>,
191    pub limit_price: Option<Price>,
192}
193
194/// Trailing-stop distance (exactly one of ticks or bps).
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum TrailingDistance {
197    Ticks(i64),
198    Bps(i32),
199}
200
201/// Optional max slippage for trailing-stop MARKET children.
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum MaxSlippage {
204    /// Quote ticks (proto field is int32).
205    Ticks(i32),
206    Bps(i32),
207}
208
209/// Trailing-stop attached-risk leg.
210///
211/// Distance and optional max slippage must be positive. The child is always a
212/// market-IOC execution evaluated against last trade; supplying
213/// [`trigger_price_source`](Self::trigger_price_source) or
214/// [`order_type`](Self::order_type) is rejected.
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct TrailingStop {
217    pub distance: TrailingDistance,
218    pub activation_price: Option<Price>,
219    /// Deprecated for attached trailing: any supplied value is rejected because
220    /// the wire contract always evaluates against last trade.
221    #[deprecated(
222        note = "attached trailing always uses last trade; supplying trigger_price_source is rejected"
223    )]
224    pub trigger_price_source: Option<TriggerPriceSourceKind>,
225    /// Deprecated for attached trailing: any supplied value is rejected because
226    /// the child is always an implicit market execution.
227    #[deprecated(
228        note = "attached trailing child is always market; supplying order_type is rejected"
229    )]
230    pub order_type: Option<CreateOrderType>,
231    pub max_slippage: Option<MaxSlippage>,
232}
233
234/// Typed attached risk policy for order create/modify (TP/SL/trailing).
235///
236/// Prefer this over raw proto/`map` escape hatches: trigger/limit prices use [`Price`].
237#[derive(Debug, Clone, Default, PartialEq, Eq)]
238pub struct AttachedRisk {
239    pub take_profit: Option<RiskLeg>,
240    pub stop_loss: Option<RiskLeg>,
241    pub trailing_stop: Option<TrailingStop>,
242    /// When true, take-profit and the stop leg form an OCO pair.
243    pub oco: bool,
244}
245
246/// Typed internal-transfer create params.
247#[derive(Debug, Clone)]
248pub struct CreateInternalTransferParams {
249    pub asset_id: u32,
250    pub quantity: AssetAmount,
251    pub idempotency_key: String,
252    pub subaccount_id: Option<u64>,
253    pub destination_account_id: Option<String>,
254    pub destination_subaccount_id: Option<String>,
255    pub destination_smart_account_address: Option<String>,
256    /// Input quantity scale when `quantity` does not carry one. Wire
257    /// `amount_e18` is always rescaled exactly to 18 decimals.
258    pub quantity_scale: Option<u32>,
259}
260
261/// Typed trading-withdraw create params.
262#[derive(Debug, Clone)]
263pub struct CreateTradingWithdrawParams {
264    pub asset_id: u32,
265    pub amount: AssetAmount,
266    pub payload_signature: Vec<u8>,
267    pub destination_address: String,
268    /// Stable key for this logical withdrawal. Persist it and reuse it for
269    /// every retry; generating a new key per attempt defeats deduplication.
270    pub idempotency_key: String,
271    /// Input amount scale when `amount` does not carry one. Wire `amount_e18`
272    /// is always rescaled exactly to 18 decimals.
273    pub amount_scale: Option<u32>,
274    /// Exact deadline covered by `payload_signature`. Required for this
275    /// precomputed-signature path.
276    pub deadline_ts_sec: Option<u64>,
277    /// Non-zero nonce included in the signed withdrawal payload.
278    pub nonce: u128,
279}
280
281/// API-key trading-withdraw params for SDK-owned payload construction/signing.
282#[derive(Debug, Clone)]
283pub struct CreateApiKeyTradingWithdrawParams {
284    pub asset_id: u32,
285    pub amount: AssetAmount,
286    pub destination_address: String,
287    /// Stable key for this logical withdrawal.
288    pub idempotency_key: String,
289    /// Input amount scale when `amount` does not carry one. Wire `amount_e18`
290    /// is always rescaled exactly to 18 decimals.
291    pub amount_scale: Option<u32>,
292    /// Optional explicit deadline. The SDK uses now + five minutes when absent.
293    pub deadline_ts_sec: Option<u64>,
294    /// Optional explicit nonce. The SDK generates a secure non-zero nonce when absent.
295    pub nonce: Option<u128>,
296}
297
298/// Typed wallet trading-withdraw create params.
299#[derive(Debug, Clone)]
300pub struct CreateWalletTradingWithdrawParams {
301    pub action: String,
302    pub asset_id: u32,
303    pub amount: AssetAmount,
304    pub idempotency_key: String,
305    pub payload_signature: Vec<u8>,
306    pub signer_wallet: String,
307    pub destination_chain_id: u64,
308    pub destination_address: String,
309    pub subaccount_id: Option<u64>,
310    /// Input amount scale when `amount` does not carry one. Wire `amount_e18`
311    /// is always rescaled exactly to 18 decimals.
312    pub amount_scale: Option<u32>,
313    /// Exact deadline covered by `payload_signature`. Required for this
314    /// precomputed-signature path.
315    pub deadline_ts_sec: Option<u64>,
316    /// Non-zero nonce included in the signed withdrawal payload.
317    pub nonce: u128,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub struct BatchReplaceAdmissionItem {
322    pub item_index: u32,
323    pub status: String,
324    pub old_order_id: String,
325    pub client_order_id: String,
326    pub replacement_order_id: String,
327    pub code: String,
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331pub struct BatchReplaceOrdersResult {
332    pub batch_request_id: String,
333    pub status: String,
334    pub results: Vec<BatchReplaceAdmissionItem>,
335    pub accepted_count: u32,
336    pub rejected_count: u32,
337    pub accepted_ts_ns: u64,
338}
339
340#[derive(Debug, Clone, PartialEq, Eq)]
341pub struct BatchReplaceStatusItem {
342    pub item_index: u32,
343    pub phase: String,
344    pub old_order_id: String,
345    pub replacement_order_id: String,
346    pub order_status: String,
347    pub code: String,
348    pub updated_ts_ns: u64,
349}
350
351#[derive(Debug, Clone, PartialEq, Eq)]
352pub struct BatchReplaceStatusResult {
353    pub batch_request_id: String,
354    pub admission_status: String,
355    pub items: Vec<BatchReplaceStatusItem>,
356    pub accepted_count: u32,
357    pub rejected_count: u32,
358    pub accepted_ts_ns: u64,
359    pub updated_ts_ns: u64,
360}
361
362impl BatchReplaceStatusResult {
363    /// Returns true once every item has left admission processing.
364    ///
365    /// `working` means the replacement is live, not that it has reached an
366    /// execution terminal state. Continue polling/reconciling order state when
367    /// execution finality is required.
368    pub fn is_settled(&self) -> bool {
369        is_batch_replace_settled(self)
370    }
371}
372
373/// Returns true once every batch-replace item is `working`, `rejected`, or
374/// `terminal`. An empty status is not considered settled.
375pub fn is_batch_replace_settled(status: &BatchReplaceStatusResult) -> bool {
376    !status.items.is_empty()
377        && status
378            .items
379            .iter()
380            .all(|item| matches!(item.phase.as_str(), "working" | "rejected" | "terminal"))
381}
382
383#[derive(Debug, Clone, PartialEq, Eq)]
384pub struct CancelAllAfterResult {
385    pub status: String,
386    pub effective_timeout_sec: u32,
387    pub expires_at_ts_ns: String,
388}
389
390/// Options for [`crate::services::OrdersService::list_open_with`].
391#[derive(Debug, Clone, Default)]
392pub struct ListOpenOrdersOpts {
393    pub subaccount_id: Option<u64>,
394    pub page_token: Option<String>,
395    pub limit: Option<u32>,
396    pub include_attached_risk: bool,
397    pub include_attached_risk_state: bool,
398}
399
400/// Options for [`crate::services::OrdersService::list_history_with`].
401#[derive(Debug, Clone, Default)]
402pub struct ListOrderHistoryOpts {
403    pub subaccount_id: Option<u64>,
404    pub symbol: Option<String>,
405    pub symbol_id: Option<u32>,
406    pub page_token: Option<String>,
407    pub limit: Option<u32>,
408    pub include_attached_risk: bool,
409    pub include_attached_risk_state: bool,
410}
411
412/// Options for [`crate::services::OrdersService::get_with`].
413#[derive(Debug, Clone)]
414pub struct GetOrderOpts {
415    pub key: OrderKey,
416    pub subaccount_id: Option<u64>,
417    pub include_attached_risk: bool,
418    pub include_attached_risk_state: bool,
419}
420
421/// Params for [`crate::services::OrdersService::cancel_with`].
422#[derive(Debug, Clone)]
423pub struct CancelOrderParams {
424    pub key: OrderKey,
425    pub symbol: Option<String>,
426    pub symbol_id: Option<u32>,
427    pub subaccount_id: Option<u64>,
428}
429
430/// Options for [`crate::services::OrdersService::cancel_all_with`].
431#[derive(Debug, Clone, Default)]
432pub struct CancelAllOpts {
433    pub symbol: Option<String>,
434    pub dry_run: bool,
435    pub subaccount_id: Option<u64>,
436    pub side: Option<String>,
437    /// Optional mutation request id (API-required on the wire).
438    ///
439    /// When omitted or blank, the SDK generates a unique id (TypeScript/Go/Python parity).
440    /// Set a stable non-empty value when you may retry the same logical cancel-all after an
441    /// ambiguous failure, and reuse that same value on retry. A blind retry that omits
442    /// `request_id` mints a *new* id and is not an idempotent replay.
443    pub request_id: Option<String>,
444}
445
446#[derive(Debug, Clone)]
447pub struct CreateOrderParams {
448    pub symbol: String,
449    pub side: CreateSide,
450    pub order_type: CreateOrderType,
451    /// Base quantity. Set exactly one of this and `max_quote_debit_scaled`.
452    pub quantity: Option<Quantity>,
453    /// Hard all-in quote debit limit. The [`Quantity`] must use
454    /// [`crate::types::QuantityDomain::OrderQuote`] and carry the pair's
455    /// catalog quote scale. Set exactly one of this and `quantity`.
456    pub max_quote_debit_scaled: Option<Quantity>,
457    pub price: Option<Price>,
458    pub time_in_force: Option<CreateTimeInForce>,
459    /// Optional client order id (API-optional).
460    ///
461    /// Set a stable non-empty value when you may retry after an ambiguous failure
462    /// (`Error::mutation_outcome_unknown`), and reuse that same value on retry.
463    /// Omit (`None`) for one-shot creates where you will not reconcile by client id.
464    pub client_order_id: Option<String>,
465    pub subaccount_id: Option<u64>,
466    pub post_only: Option<bool>,
467    /// Client reference price for MARKET order reservation (price ticks domain).
468    pub market_client_ref_price: Option<Price>,
469    /// Fee asset. `Base` is valid only for BUY orders; SELL orders use `Quote`.
470    pub fee_asset: Option<FeeAsset>,
471    /// Self-trade prevention policy for this order.
472    pub self_trade_prevention: Option<OrderSelfTradePrevention>,
473    /// Optional market-order slippage guard.
474    pub market_max_slippage: Option<MaxSlippage>,
475    /// Optional TP/SL/trailing controls that arm after the parent fills.
476    pub attached_risk: Option<AttachedRisk>,
477}
478
479#[derive(Debug, Clone, Copy, PartialEq, Eq)]
480pub enum FeeAsset {
481    Quote,
482    Base,
483}
484
485/// Legacy name for [`FeeAsset`].
486///
487/// `Received` was removed by the API contract; use `FeeAsset::Base` for a
488/// BUY fee deducted from received base quantity.
489#[deprecated(note = "renamed to FeeAsset; use FeeAsset::Base instead of the removed Received")]
490pub type OrderFeeSource = FeeAsset;
491
492/// Order inputs accepted by [`crate::services::OrdersService::preview`].
493///
494/// Preview uses the same [`OrderIntent`](crate::proto::orders::v1::OrderIntent)
495/// contract as create. The host performs an admissibility check only: no hold is
496/// placed, and `client_order_id` is accepted but not claimed.
497#[derive(Debug, Clone)]
498pub struct PreviewOrderParams {
499    pub symbol: String,
500    pub side: CreateSide,
501    pub order_type: CreateOrderType,
502    /// Base quantity. Set exactly one of this and `max_quote_debit_scaled`.
503    pub quantity: Option<Quantity>,
504    /// Hard all-in quote debit limit. The [`Quantity`] must use
505    /// [`crate::types::QuantityDomain::OrderQuote`] and carry the pair's
506    /// catalog quote scale. Set exactly one of this and `quantity`.
507    pub max_quote_debit_scaled: Option<Quantity>,
508    pub price: Option<Price>,
509    pub time_in_force: Option<CreateTimeInForce>,
510    /// Optional client order id. Accepted for shape parity with create; preview
511    /// does not claim it.
512    pub client_order_id: Option<String>,
513    pub subaccount_id: Option<u64>,
514    pub post_only: Option<bool>,
515    pub market_client_ref_price: Option<Price>,
516    pub fee_asset: Option<FeeAsset>,
517    pub self_trade_prevention: Option<OrderSelfTradePrevention>,
518    pub market_max_slippage: Option<MaxSlippage>,
519    /// Optional TP/SL/trailing controls. Preview validates the full intent;
520    /// nothing is armed until a subsequent create.
521    pub attached_risk: Option<AttachedRisk>,
522}
523
524/// One actionable field-level validation failure from preview/create rejection.
525#[derive(Debug, Clone, PartialEq, Eq)]
526pub struct OrderFieldViolation {
527    pub field_path: String,
528    pub rule_id: String,
529    pub message: String,
530}
531
532/// Typed rejection detail when a preview (or related) admission check fails.
533#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct OrderErrorDetail {
535    /// Public error code label (for example `BAD_QTY`), or
536    /// `UNKNOWN_ERROR_CODE(<n>)` for open-enum forward compatibility.
537    pub code: String,
538    pub violations: Vec<OrderFieldViolation>,
539}
540
541/// Advisory admission result for [`crate::services::OrdersService::preview`].
542///
543/// Preview no longer returns fee/quote estimates. It reports whether the intent
544/// is currently admissible, any typed rejection, and any sizing / price-
545/// protection values resolved during evaluation.
546#[derive(Debug, Clone, PartialEq, Eq)]
547pub struct PreviewOrderResult {
548    pub admissible: Option<bool>,
549    pub rejection: Option<OrderErrorDetail>,
550    pub resolved_base_qty: Option<Quantity>,
551    /// Protective execution boundary (renamed from `price_bound`).
552    pub protected_price_bound: Option<Price>,
553    /// Evaluation completion time as epoch milliseconds.
554    pub evaluated_at_ms: i64,
555}
556
557#[derive(Debug, Clone, Copy, PartialEq, Eq)]
558pub enum OrderSelfTradePrevention {
559    ExpireTaker,
560    ExpireMaker,
561    ExpireBoth,
562}
563
564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
565pub enum CreateSide {
566    Buy,
567    Sell,
568}
569
570#[derive(Debug, Clone, Copy, PartialEq, Eq)]
571pub enum CreateOrderType {
572    Limit,
573    Market,
574}
575
576#[derive(Debug, Clone, Copy, PartialEq, Eq)]
577pub enum CreateTimeInForce {
578    Gtc,
579    Ioc,
580    Fok,
581}
582
583#[derive(Debug, Clone, PartialEq, Eq)]
584pub struct InternalTransferResult {
585    pub request_id: String,
586    pub transfer_id: String,
587    pub asset_id: u32,
588    pub asset_code: String,
589    pub quantity: Option<AssetAmount>,
590}
591
592#[derive(Debug, Clone, PartialEq, Eq)]
593pub struct DepositAddress {
594    pub chain_id: u32,
595    pub deposit_address: String,
596}
597
598#[derive(Debug, Clone, PartialEq, Eq)]
599pub struct DepositAddressesList {
600    pub addresses: Vec<DepositAddress>,
601}
602
603#[derive(Debug, Clone, PartialEq, Eq)]
604pub struct WithdrawIntentResult {
605    pub intent_id: String,
606    pub status: String,
607    pub flow_id: String,
608}
609
610#[derive(Debug, Clone, PartialEq)]
611pub struct ApiKeySummary {
612    pub key_id: String,
613    pub label: String,
614    pub status: String,
615    pub public_key_ed25519: String,
616    pub created_at: Option<Timestamp>,
617    pub last_used_at: Option<Timestamp>,
618    pub updated_at: Option<Timestamp>,
619    /// Monotonic resource revision for conditional updates.
620    pub revision: u64,
621}
622
623#[derive(Debug, Clone, PartialEq)]
624pub struct ApiKeysList {
625    pub keys: Vec<ApiKeySummary>,
626}
627
628#[derive(Debug, Clone, PartialEq, Eq)]
629pub struct ResolvedAccount {
630    pub account_id: String,
631    pub username: String,
632    pub smart_account_address: String,
633}
634
635#[derive(Debug, Clone, PartialEq, Eq)]
636pub struct ResolvedAccountsList {
637    pub accounts: Vec<ResolvedAccount>,
638}