1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! Trading helpers that construct, sign and submit call messages.
use bullet_exchange_interface::decimals::PositiveDecimal;
use bullet_exchange_interface::message::{AmendOrderArgs, CancelOrderArgs, NewOrderArgs};
use bullet_exchange_interface::types::{MarketId, OrderType, Side};
use crate::generated::types::SubmitTxResponse;
use crate::types::{CallMessage, UserAction};
use crate::{Client, SDKError, SDKResult};
/// Extension constructors for [`NewOrderArgs`].
///
/// For `reduce_only`, `client_order_id` or `pending_tpsl_pair`, construct `NewOrderArgs` directly.
pub trait NewOrderExt {
/// Create a limit order. Defaults: `reduce_only: false`, no client order ID, no TP/SL.
fn limit(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self;
/// Create a post-only (maker) order. Rejected if it would cross the book.
fn post_only(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self;
/// Create an immediate-or-cancel order (market-order equivalent).
///
/// Fills what it can at the given price, cancels the rest.
fn ioc(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self;
}
impl NewOrderExt for NewOrderArgs {
fn limit(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self {
new_order(price, size, side, OrderType::Limit)
}
fn post_only(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self {
new_order(price, size, side, OrderType::PostOnly)
}
fn ioc(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self {
new_order(price, size, side, OrderType::ImmediateOrCancel)
}
}
fn new_order(
price: PositiveDecimal,
size: PositiveDecimal,
side: Side,
order_type: OrderType,
) -> NewOrderArgs {
NewOrderArgs {
price,
size,
side,
order_type,
reduce_only: false,
client_order_id: None,
pending_tpsl_pair: None,
}
}
impl Client {
/// Place orders on a market. Signs and submits the transaction.
///
/// # Arguments
///
/// * `market_id` — Numeric market ID (resolve via `client.market_id("BTC-USD")`)
/// * `orders` — One or more orders to place
/// * `replace` — If `true`, cancel existing orders before placing new ones
/// * `sub_account_index` — `None` for the main account, `Some(n)` for a sub-account
pub async fn place_orders(
&self,
market_id: MarketId,
orders: Vec<NewOrderArgs>,
replace: bool,
sub_account_index: Option<u8>,
) -> SDKResult<SubmitTxResponse> {
let call_msg = CallMessage::User(UserAction::PlaceOrders {
market_id,
orders,
replace,
sub_account_index,
});
self.send_call_message(call_msg).await
}
/// Cancel specific orders on a market. Signs and submits the transaction.
///
/// Cancel by exchange-assigned `OrderId`, client-assigned `ClientOrderId`, or both.
pub async fn cancel_orders(
&self,
market_id: MarketId,
orders: Vec<CancelOrderArgs>,
sub_account_index: Option<u8>,
) -> SDKResult<SubmitTxResponse> {
let call_msg = CallMessage::User(UserAction::CancelOrders {
market_id,
orders,
sub_account_index,
});
self.send_call_message(call_msg).await
}
/// Cancel all orders on a specific market. Signs and submits the transaction.
pub async fn cancel_market_orders(
&self,
market_id: MarketId,
sub_account_index: Option<u8>,
) -> SDKResult<SubmitTxResponse> {
let call_msg = CallMessage::User(UserAction::CancelMarketOrders {
market_id,
sub_account_index,
});
self.send_call_message(call_msg).await
}
/// Cancel all orders across all markets. Signs and submits the transaction.
pub async fn cancel_all_orders(
&self,
sub_account_index: Option<u8>,
) -> SDKResult<SubmitTxResponse> {
let call_msg = CallMessage::User(UserAction::CancelAllOrders { sub_account_index });
self.send_call_message(call_msg).await
}
/// Get the base58 address derived from the client's keypair.
///
/// Returns `Err(SDKError::MissingKeypair)` if no keypair is configured.
///
/// # Example
///
/// ```ignore
/// let address = client.address()?;
/// println!("My address: {address}"); // e.g. "5Hq3...xyz"
/// ```
pub fn address(&self) -> SDKResult<String> {
let kp = self.keypair().ok_or(SDKError::MissingKeypair)?;
Ok(kp.address())
}
/// Query open orders for the client's own account on a symbol.
pub async fn my_open_orders(
&self,
symbol: &str,
) -> SDKResult<Vec<crate::generated::types::BinanceOrder>> {
let address = self.address()?;
// The generated client takes `Option<symbol>` (None = all markets); keep it per-symbol.
let resp = self.query_open_orders(&address, Some(symbol)).await?;
Ok(resp.into_inner())
}
/// Query account info (positions, margins) for the client's own account.
///
/// Derives the address from the client's keypair and unwraps the response.
pub async fn my_account(&self) -> SDKResult<crate::generated::types::Account> {
let address = self.address()?;
let resp = self.account_info(&address).await?;
Ok(resp.into_inner())
}
/// Query balances for the client's own account.
///
/// Derives the address from the client's keypair and unwraps the response.
pub async fn my_balances(&self) -> SDKResult<Vec<crate::generated::types::Balance>> {
let address = self.address()?;
let resp = self.account_balance(&address).await?;
Ok(resp.into_inner())
}
/// Amend (cancel + replace) existing orders. Signs and submits the transaction.
///
/// Each [`AmendOrderArgs`] pairs a [`CancelOrderArgs`] with a [`NewOrderArgs`],
/// atomically replacing the cancelled order with a new one.
pub async fn amend_orders(
&self,
market_id: MarketId,
orders: Vec<AmendOrderArgs>,
sub_account_index: Option<u8>,
) -> SDKResult<SubmitTxResponse> {
let call_msg = CallMessage::User(UserAction::AmendOrders {
market_id,
orders,
sub_account_index,
});
self.send_call_message(call_msg).await
}
}