1use ethers::{
2 signers::{LocalWallet, Signer},
3 types::H160,
4};
5use log::{error, info};
6
7use tokio::sync::mpsc::unbounded_channel;
8
9use crate::{
10 bps_diff, truncate_float, BaseUrl, ClientCancelRequest, ClientLimit, ClientOrder,
11 ClientOrderRequest, ExchangeClient, ExchangeDataStatus, ExchangeResponseStatus, InfoClient,
12 Message, Subscription, UserData, EPSILON,
13};
14#[derive(Debug)]
15pub struct MarketMakerRestingOrder {
16 pub oid: u64,
17 pub position: f64,
18 pub price: f64,
19}
20
21#[derive(Debug)]
22pub struct MarketMakerInput {
23 pub asset: String,
24 pub target_liquidity: f64, pub half_spread: u16, pub max_bps_diff: u16, pub max_absolute_position_size: f64, pub decimals: u32, pub wallet: LocalWallet, }
31
32#[derive(Debug)]
33pub struct MarketMaker {
34 pub asset: String,
35 pub target_liquidity: f64,
36 pub half_spread: u16,
37 pub max_bps_diff: u16,
38 pub max_absolute_position_size: f64,
39 pub decimals: u32,
40 pub lower_resting: MarketMakerRestingOrder,
41 pub upper_resting: MarketMakerRestingOrder,
42 pub cur_position: f64,
43 pub latest_mid_price: f64,
44 pub info_client: InfoClient,
45 pub exchange_client: ExchangeClient,
46 pub user_address: H160,
47}
48
49impl MarketMaker {
50 pub async fn new(input: MarketMakerInput) -> MarketMaker {
51 let user_address = input.wallet.address();
52
53 let info_client = InfoClient::new(None, Some(BaseUrl::Testnet)).await.unwrap();
54 let exchange_client =
55 ExchangeClient::new(None, input.wallet, Some(BaseUrl::Testnet), None, None)
56 .await
57 .unwrap();
58
59 MarketMaker {
60 asset: input.asset,
61 target_liquidity: input.target_liquidity,
62 half_spread: input.half_spread,
63 max_bps_diff: input.max_bps_diff,
64 max_absolute_position_size: input.max_absolute_position_size,
65 decimals: input.decimals,
66 lower_resting: MarketMakerRestingOrder {
67 oid: 0,
68 position: 0.0,
69 price: -1.0,
70 },
71 upper_resting: MarketMakerRestingOrder {
72 oid: 0,
73 position: 0.0,
74 price: -1.0,
75 },
76 cur_position: 0.0,
77 latest_mid_price: -1.0,
78 info_client,
79 exchange_client,
80 user_address,
81 }
82 }
83
84 pub async fn start(&mut self) {
85 let (sender, mut receiver) = unbounded_channel();
86
87 self.info_client
89 .subscribe(
90 Subscription::UserEvents {
91 user: self.user_address,
92 },
93 sender.clone(),
94 )
95 .await
96 .unwrap();
97
98 self.info_client
100 .subscribe(Subscription::AllMids, sender)
101 .await
102 .unwrap();
103
104 loop {
105 let message = receiver.recv().await.unwrap();
106 match message {
107 Message::AllMids(all_mids) => {
108 let all_mids = all_mids.data.mids;
109 let mid = all_mids.get(&self.asset);
110 if let Some(mid) = mid {
111 let mid: f64 = mid.parse().unwrap();
112 self.latest_mid_price = mid;
113 self.potentially_update().await;
115 } else {
116 error!(
117 "could not get mid for asset {}: {all_mids:?}",
118 self.asset.clone()
119 );
120 }
121 }
122 Message::User(user_events) => {
123 if self.latest_mid_price < 0.0 {
125 continue;
126 }
127 let user_events = user_events.data;
128 if let UserData::Fills(fills) = user_events {
129 for fill in fills {
130 let amount: f64 = fill.sz.parse().unwrap();
131 if fill.side.eq("B") {
133 self.cur_position += amount;
134 self.lower_resting.position -= amount;
135 info!("Fill: bought {amount} {}", self.asset.clone());
136 } else {
137 self.cur_position -= amount;
138 self.upper_resting.position -= amount;
139 info!("Fill: sold {amount} {}", self.asset.clone());
140 }
141 }
142 }
143 self.potentially_update().await;
145 }
146 _ => {
147 panic!("Unsupported message type");
148 }
149 }
150 }
151 }
152
153 async fn attempt_cancel(&self, asset: String, oid: u64) -> bool {
154 let cancel = self
155 .exchange_client
156 .cancel(ClientCancelRequest { asset, oid }, None)
157 .await;
158
159 match cancel {
160 Ok(cancel) => match cancel {
161 ExchangeResponseStatus::Ok(cancel) => {
162 if let Some(cancel) = cancel.data {
163 if !cancel.statuses.is_empty() {
164 match cancel.statuses[0].clone() {
165 ExchangeDataStatus::Success => {
166 return true;
167 }
168 ExchangeDataStatus::Error(e) => {
169 error!("Error with cancelling: {e}")
170 }
171 _ => unreachable!(),
172 }
173 } else {
174 error!("Exchange data statuses is empty when cancelling: {cancel:?}")
175 }
176 } else {
177 error!("Exchange response data is empty when cancelling: {cancel:?}")
178 }
179 }
180 ExchangeResponseStatus::Err(e) => error!("Error with cancelling: {e}"),
181 },
182 Err(e) => error!("Error with cancelling: {e}"),
183 }
184 false
185 }
186
187 async fn place_order(
188 &self,
189 asset: String,
190 amount: f64,
191 price: f64,
192 is_buy: bool,
193 ) -> (f64, u64) {
194 let order = self
195 .exchange_client
196 .order(
197 ClientOrderRequest {
198 asset,
199 is_buy,
200 reduce_only: false,
201 limit_px: price,
202 sz: amount,
203 cloid: None,
204 order_type: ClientOrder::Limit(ClientLimit {
205 tif: "Gtc".to_string(),
206 }),
207 },
208 None,
209 )
210 .await;
211 match order {
212 Ok(order) => match order {
213 ExchangeResponseStatus::Ok(order) => {
214 if let Some(order) = order.data {
215 if !order.statuses.is_empty() {
216 match order.statuses[0].clone() {
217 ExchangeDataStatus::Filled(order) => {
218 return (amount, order.oid);
219 }
220 ExchangeDataStatus::Resting(order) => {
221 return (amount, order.oid);
222 }
223 ExchangeDataStatus::Error(e) => {
224 error!("Error with placing order: {e}")
225 }
226 _ => unreachable!(),
227 }
228 } else {
229 error!("Exchange data statuses is empty when placing order: {order:?}")
230 }
231 } else {
232 error!("Exchange response data is empty when placing order: {order:?}")
233 }
234 }
235 ExchangeResponseStatus::Err(e) => {
236 error!("Error with placing order: {e}")
237 }
238 },
239 Err(e) => error!("Error with placing order: {e}"),
240 }
241 (0.0, 0)
242 }
243
244 async fn potentially_update(&mut self) {
245 let half_spread = (self.latest_mid_price * self.half_spread as f64) / 10000.0;
246 let (lower_price, upper_price) = (
248 self.latest_mid_price - half_spread,
249 self.latest_mid_price + half_spread,
250 );
251 let (mut lower_price, mut upper_price) = (
252 truncate_float(lower_price, self.decimals, true),
253 truncate_float(upper_price, self.decimals, false),
254 );
255
256 if (lower_price - upper_price).abs() < EPSILON {
258 lower_price = truncate_float(lower_price, self.decimals, false);
259 upper_price = truncate_float(upper_price, self.decimals, true);
260 }
261
262 let lower_order_amount = (self.max_absolute_position_size - self.cur_position)
264 .min(self.target_liquidity)
265 .max(0.0);
266
267 let upper_order_amount = (self.max_absolute_position_size + self.cur_position)
268 .min(self.target_liquidity)
269 .max(0.0);
270
271 let lower_change = (lower_order_amount - self.lower_resting.position).abs() > EPSILON
273 || bps_diff(lower_price, self.lower_resting.price) > self.max_bps_diff;
274 let upper_change = (upper_order_amount - self.upper_resting.position).abs() > EPSILON
275 || bps_diff(upper_price, self.upper_resting.price) > self.max_bps_diff;
276
277 if self.lower_resting.oid != 0 && self.lower_resting.position > EPSILON && lower_change {
280 let cancel = self
281 .attempt_cancel(self.asset.clone(), self.lower_resting.oid)
282 .await;
283 if !cancel {
285 return;
286 }
287 info!("Cancelled buy order: {:?}", self.lower_resting);
288 }
289
290 if self.upper_resting.oid != 0 && self.upper_resting.position > EPSILON && upper_change {
291 let cancel = self
292 .attempt_cancel(self.asset.clone(), self.upper_resting.oid)
293 .await;
294 if !cancel {
295 return;
296 }
297 info!("Cancelled sell order: {:?}", self.upper_resting);
298 }
299
300 if lower_order_amount > EPSILON && lower_change {
302 let (amount_resting, oid) = self
303 .place_order(self.asset.clone(), lower_order_amount, lower_price, true)
304 .await;
305
306 self.lower_resting.oid = oid;
307 self.lower_resting.position = amount_resting;
308 self.lower_resting.price = lower_price;
309
310 if amount_resting > EPSILON {
311 info!(
312 "Buy for {amount_resting} {} resting at {lower_price}",
313 self.asset.clone()
314 );
315 }
316 }
317
318 if upper_order_amount > EPSILON && upper_change {
319 let (amount_resting, oid) = self
320 .place_order(self.asset.clone(), upper_order_amount, upper_price, false)
321 .await;
322 self.upper_resting.oid = oid;
323 self.upper_resting.position = amount_resting;
324 self.upper_resting.price = upper_price;
325
326 if amount_resting > EPSILON {
327 info!(
328 "Sell for {amount_resting} {} resting at {upper_price}",
329 self.asset.clone()
330 );
331 }
332 }
333 }
334}