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#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct TrailingStop {
212    pub distance: TrailingDistance,
213    pub activation_price: Option<Price>,
214    pub trigger_price_source: Option<TriggerPriceSourceKind>,
215    pub order_type: Option<CreateOrderType>,
216    pub max_slippage: Option<MaxSlippage>,
217}
218
219/// Typed attached risk policy for order create/modify (TP/SL/trailing).
220///
221/// Prefer this over raw proto/`map` escape hatches: trigger/limit prices use [`Price`].
222#[derive(Debug, Clone, Default, PartialEq, Eq)]
223pub struct AttachedRisk {
224    pub take_profit: Option<RiskLeg>,
225    pub stop_loss: Option<RiskLeg>,
226    pub trailing_stop: Option<TrailingStop>,
227    /// When true, take-profit and the stop leg form an OCO pair.
228    pub oco: bool,
229}
230
231/// Typed internal-transfer create params.
232#[derive(Debug, Clone)]
233pub struct CreateInternalTransferParams {
234    pub asset_id: u32,
235    pub quantity: AssetAmount,
236    pub idempotency_key: String,
237    pub subaccount_id: Option<u64>,
238    pub destination_account_id: Option<String>,
239    pub destination_subaccount_id: Option<String>,
240    pub destination_smart_account_address: Option<String>,
241    /// Input quantity scale when `quantity` does not carry one. Wire
242    /// `amount_e18` is always rescaled exactly to 18 decimals.
243    pub quantity_scale: Option<u32>,
244}
245
246/// Typed trading-withdraw create params.
247#[derive(Debug, Clone)]
248pub struct CreateTradingWithdrawParams {
249    pub asset_id: u32,
250    pub amount: AssetAmount,
251    pub payload_signature: Vec<u8>,
252    pub destination_address: String,
253    /// Stable key for this logical withdrawal. Persist it and reuse it for
254    /// every retry; generating a new key per attempt defeats deduplication.
255    pub idempotency_key: String,
256    /// Input amount scale when `amount` does not carry one. Wire `amount_e18`
257    /// is always rescaled exactly to 18 decimals.
258    pub amount_scale: Option<u32>,
259    /// Exact deadline covered by `payload_signature`. Required for this
260    /// precomputed-signature path.
261    pub deadline_ts_sec: Option<u64>,
262    /// Non-zero nonce included in the signed withdrawal payload.
263    pub nonce: u128,
264}
265
266/// API-key trading-withdraw params for SDK-owned payload construction/signing.
267#[derive(Debug, Clone)]
268pub struct CreateApiKeyTradingWithdrawParams {
269    pub asset_id: u32,
270    pub amount: AssetAmount,
271    pub destination_address: String,
272    /// Stable key for this logical withdrawal.
273    pub idempotency_key: String,
274    /// Input amount scale when `amount` does not carry one. Wire `amount_e18`
275    /// is always rescaled exactly to 18 decimals.
276    pub amount_scale: Option<u32>,
277    /// Optional explicit deadline. The SDK uses now + five minutes when absent.
278    pub deadline_ts_sec: Option<u64>,
279    /// Optional explicit nonce. The SDK generates a secure non-zero nonce when absent.
280    pub nonce: Option<u128>,
281}
282
283/// Typed wallet trading-withdraw create params.
284#[derive(Debug, Clone)]
285pub struct CreateWalletTradingWithdrawParams {
286    pub action: String,
287    pub asset_id: u32,
288    pub amount: AssetAmount,
289    pub idempotency_key: String,
290    pub payload_signature: Vec<u8>,
291    pub signer_wallet: String,
292    pub destination_chain_id: u64,
293    pub destination_address: String,
294    pub subaccount_id: Option<u64>,
295    /// Input amount scale when `amount` does not carry one. Wire `amount_e18`
296    /// is always rescaled exactly to 18 decimals.
297    pub amount_scale: Option<u32>,
298    /// Exact deadline covered by `payload_signature`. Required for this
299    /// precomputed-signature path.
300    pub deadline_ts_sec: Option<u64>,
301    /// Non-zero nonce included in the signed withdrawal payload.
302    pub nonce: u128,
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
306pub struct BatchReplaceAdmissionItem {
307    pub item_index: u32,
308    pub status: String,
309    pub old_order_id: String,
310    pub client_order_id: String,
311    pub replacement_order_id: String,
312    pub code: String,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316pub struct BatchReplaceOrdersResult {
317    pub batch_request_id: String,
318    pub status: String,
319    pub results: Vec<BatchReplaceAdmissionItem>,
320    pub accepted_count: u32,
321    pub rejected_count: u32,
322    pub accepted_ts_ns: u64,
323}
324
325#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct BatchReplaceStatusItem {
327    pub item_index: u32,
328    pub phase: String,
329    pub old_order_id: String,
330    pub replacement_order_id: String,
331    pub order_status: String,
332    pub code: String,
333    pub updated_ts_ns: u64,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct BatchReplaceStatusResult {
338    pub batch_request_id: String,
339    pub admission_status: String,
340    pub items: Vec<BatchReplaceStatusItem>,
341    pub accepted_count: u32,
342    pub rejected_count: u32,
343    pub accepted_ts_ns: u64,
344    pub updated_ts_ns: u64,
345}
346
347impl BatchReplaceStatusResult {
348    /// Returns true once every item has left admission processing.
349    ///
350    /// `working` means the replacement is live, not that it has reached an
351    /// execution terminal state. Continue polling/reconciling order state when
352    /// execution finality is required.
353    pub fn is_settled(&self) -> bool {
354        is_batch_replace_settled(self)
355    }
356}
357
358/// Returns true once every batch-replace item is `working`, `rejected`, or
359/// `terminal`. An empty status is not considered settled.
360pub fn is_batch_replace_settled(status: &BatchReplaceStatusResult) -> bool {
361    !status.items.is_empty()
362        && status
363            .items
364            .iter()
365            .all(|item| matches!(item.phase.as_str(), "working" | "rejected" | "terminal"))
366}
367
368#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct CancelAllAfterResult {
370    pub status: String,
371    pub effective_timeout_sec: u32,
372    pub expires_at_ts_ns: String,
373}
374
375/// Options for [`crate::services::OrdersService::list_open_with`].
376#[derive(Debug, Clone, Default)]
377pub struct ListOpenOrdersOpts {
378    pub subaccount_id: Option<u64>,
379    pub page_token: Option<String>,
380    pub limit: Option<u32>,
381    pub include_attached_risk: bool,
382    pub include_attached_risk_state: bool,
383}
384
385/// Options for [`crate::services::OrdersService::list_history_with`].
386#[derive(Debug, Clone, Default)]
387pub struct ListOrderHistoryOpts {
388    pub subaccount_id: Option<u64>,
389    pub symbol: Option<String>,
390    pub symbol_id: Option<u32>,
391    pub page_token: Option<String>,
392    pub limit: Option<u32>,
393    pub include_attached_risk: bool,
394    pub include_attached_risk_state: bool,
395}
396
397/// Options for [`crate::services::OrdersService::get_with`].
398#[derive(Debug, Clone)]
399pub struct GetOrderOpts {
400    pub key: OrderKey,
401    pub subaccount_id: Option<u64>,
402    pub include_attached_risk: bool,
403    pub include_attached_risk_state: bool,
404}
405
406/// Params for [`crate::services::OrdersService::cancel_with`].
407#[derive(Debug, Clone)]
408pub struct CancelOrderParams {
409    pub key: OrderKey,
410    pub symbol: Option<String>,
411    pub symbol_id: Option<u32>,
412    pub subaccount_id: Option<u64>,
413}
414
415/// Options for [`crate::services::OrdersService::cancel_all_with`].
416#[derive(Debug, Clone, Default)]
417pub struct CancelAllOpts {
418    pub symbol: Option<String>,
419    pub dry_run: bool,
420    pub subaccount_id: Option<u64>,
421    pub side: Option<String>,
422    /// Optional mutation request id (API-required on the wire).
423    ///
424    /// When omitted or blank, the SDK generates a unique id (TypeScript/Go/Python parity).
425    /// Set a stable non-empty value when you may retry the same logical cancel-all after an
426    /// ambiguous failure, and reuse that same value on retry. A blind retry that omits
427    /// `request_id` mints a *new* id and is not an idempotent replay.
428    pub request_id: Option<String>,
429}
430
431#[derive(Debug, Clone)]
432pub struct CreateOrderParams {
433    pub symbol: String,
434    pub side: CreateSide,
435    pub order_type: CreateOrderType,
436    /// Base quantity. Set exactly one of this and `max_quote_debit_scaled`.
437    pub quantity: Option<Quantity>,
438    /// Hard all-in quote debit limit. The [`Quantity`] must use
439    /// [`crate::types::QuantityDomain::OrderQuote`] and carry the pair's
440    /// catalog quote scale. Set exactly one of this and `quantity`.
441    pub max_quote_debit_scaled: Option<Quantity>,
442    pub price: Option<Price>,
443    pub time_in_force: Option<CreateTimeInForce>,
444    /// Optional client order id (API-optional).
445    ///
446    /// Set a stable non-empty value when you may retry after an ambiguous failure
447    /// (`Error::mutation_outcome_unknown`), and reuse that same value on retry.
448    /// Omit (`None`) for one-shot creates where you will not reconcile by client id.
449    pub client_order_id: Option<String>,
450    pub subaccount_id: Option<u64>,
451    pub post_only: Option<bool>,
452    /// Client reference price for MARKET order reservation (price ticks domain).
453    pub market_client_ref_price: Option<Price>,
454    /// Fee asset. `Base` is valid only for BUY orders; SELL orders use `Quote`.
455    pub fee_asset: Option<FeeAsset>,
456    /// Self-trade prevention policy for this order.
457    pub self_trade_prevention: Option<OrderSelfTradePrevention>,
458    /// Optional market-order slippage guard.
459    pub market_max_slippage: Option<MaxSlippage>,
460    /// Optional TP/SL/trailing controls that arm after the parent fills.
461    pub attached_risk: Option<AttachedRisk>,
462}
463
464#[derive(Debug, Clone, Copy, PartialEq, Eq)]
465pub enum FeeAsset {
466    Quote,
467    Base,
468}
469
470/// Legacy name for [`FeeAsset`].
471///
472/// `Received` was removed by the API contract; use `FeeAsset::Base` for a
473/// BUY fee deducted from received base quantity.
474#[deprecated(note = "renamed to FeeAsset; use FeeAsset::Base instead of the removed Received")]
475pub type OrderFeeSource = FeeAsset;
476
477/// Order inputs accepted by [`crate::services::OrdersService::preview`].
478///
479/// Preview uses the same [`OrderIntent`](crate::proto::orders::v1::OrderIntent)
480/// contract as create. The host performs an admissibility check only: no hold is
481/// placed, and `client_order_id` is accepted but not claimed.
482#[derive(Debug, Clone)]
483pub struct PreviewOrderParams {
484    pub symbol: String,
485    pub side: CreateSide,
486    pub order_type: CreateOrderType,
487    /// Base quantity. Set exactly one of this and `max_quote_debit_scaled`.
488    pub quantity: Option<Quantity>,
489    /// Hard all-in quote debit limit. The [`Quantity`] must use
490    /// [`crate::types::QuantityDomain::OrderQuote`] and carry the pair's
491    /// catalog quote scale. Set exactly one of this and `quantity`.
492    pub max_quote_debit_scaled: Option<Quantity>,
493    pub price: Option<Price>,
494    pub time_in_force: Option<CreateTimeInForce>,
495    /// Optional client order id. Accepted for shape parity with create; preview
496    /// does not claim it.
497    pub client_order_id: Option<String>,
498    pub subaccount_id: Option<u64>,
499    pub post_only: Option<bool>,
500    pub market_client_ref_price: Option<Price>,
501    pub fee_asset: Option<FeeAsset>,
502    pub self_trade_prevention: Option<OrderSelfTradePrevention>,
503    pub market_max_slippage: Option<MaxSlippage>,
504    /// Optional TP/SL/trailing controls. Preview validates the full intent;
505    /// nothing is armed until a subsequent create.
506    pub attached_risk: Option<AttachedRisk>,
507}
508
509/// One actionable field-level validation failure from preview/create rejection.
510#[derive(Debug, Clone, PartialEq, Eq)]
511pub struct OrderFieldViolation {
512    pub field_path: String,
513    pub rule_id: String,
514    pub message: String,
515}
516
517/// Typed rejection detail when a preview (or related) admission check fails.
518#[derive(Debug, Clone, PartialEq, Eq)]
519pub struct OrderErrorDetail {
520    /// Public error code label (for example `BAD_QTY`), or
521    /// `UNKNOWN_ERROR_CODE(<n>)` for open-enum forward compatibility.
522    pub code: String,
523    pub violations: Vec<OrderFieldViolation>,
524}
525
526/// Advisory admission result for [`crate::services::OrdersService::preview`].
527///
528/// Preview no longer returns fee/quote estimates. It reports whether the intent
529/// is currently admissible, any typed rejection, and any sizing / price-
530/// protection values resolved during evaluation.
531#[derive(Debug, Clone, PartialEq, Eq)]
532pub struct PreviewOrderResult {
533    pub admissible: Option<bool>,
534    pub rejection: Option<OrderErrorDetail>,
535    pub resolved_base_qty: Option<Quantity>,
536    /// Protective execution boundary (renamed from `price_bound`).
537    pub protected_price_bound: Option<Price>,
538    /// Evaluation completion time as epoch milliseconds.
539    pub evaluated_at_ms: i64,
540}
541
542#[derive(Debug, Clone, Copy, PartialEq, Eq)]
543pub enum OrderSelfTradePrevention {
544    ExpireTaker,
545    ExpireMaker,
546    ExpireBoth,
547}
548
549#[derive(Debug, Clone, Copy, PartialEq, Eq)]
550pub enum CreateSide {
551    Buy,
552    Sell,
553}
554
555#[derive(Debug, Clone, Copy, PartialEq, Eq)]
556pub enum CreateOrderType {
557    Limit,
558    Market,
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq)]
562pub enum CreateTimeInForce {
563    Gtc,
564    Ioc,
565    Fok,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
569pub struct InternalTransferResult {
570    pub request_id: String,
571    pub transfer_id: String,
572    pub asset_id: u32,
573    pub asset_code: String,
574    pub quantity: Option<AssetAmount>,
575}
576
577#[derive(Debug, Clone, PartialEq, Eq)]
578pub struct DepositAddress {
579    pub chain_id: u32,
580    pub deposit_address: String,
581}
582
583#[derive(Debug, Clone, PartialEq, Eq)]
584pub struct DepositAddressesList {
585    pub addresses: Vec<DepositAddress>,
586}
587
588#[derive(Debug, Clone, PartialEq, Eq)]
589pub struct WithdrawIntentResult {
590    pub intent_id: String,
591    pub status: String,
592    pub flow_id: String,
593}
594
595#[derive(Debug, Clone, PartialEq)]
596pub struct ApiKeySummary {
597    pub key_id: String,
598    pub label: String,
599    pub status: String,
600    pub public_key_ed25519: String,
601    pub created_at: Option<Timestamp>,
602    pub last_used_at: Option<Timestamp>,
603    pub updated_at: Option<Timestamp>,
604    /// Monotonic resource revision for conditional updates.
605    pub revision: u64,
606}
607
608#[derive(Debug, Clone, PartialEq)]
609pub struct ApiKeysList {
610    pub keys: Vec<ApiKeySummary>,
611}
612
613#[derive(Debug, Clone, PartialEq, Eq)]
614pub struct ResolvedAccount {
615    pub account_id: String,
616    pub username: String,
617    pub smart_account_address: String,
618}
619
620#[derive(Debug, Clone, PartialEq, Eq)]
621pub struct ResolvedAccountsList {
622    pub accounts: Vec<ResolvedAccount>,
623}