2023-02-15 08:08:55 -08:00
|
|
|
use std::{
|
|
|
|
str::FromStr,
|
|
|
|
sync::{
|
|
|
|
atomic::{AtomicBool, AtomicU64, Ordering},
|
2023-03-14 05:39:19 -07:00
|
|
|
Arc,
|
2023-02-15 08:08:55 -08:00
|
|
|
},
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
|
|
|
|
|
|
|
use chrono::Utc;
|
2023-02-21 02:04:02 -08:00
|
|
|
use iter_tools::Itertools;
|
2023-03-14 05:39:19 -07:00
|
|
|
use log::{debug, info, warn};
|
2023-02-15 08:08:55 -08:00
|
|
|
use mango::{
|
|
|
|
instruction::{cancel_all_perp_orders, place_perp_order2},
|
|
|
|
matching::Side,
|
|
|
|
};
|
2023-02-24 06:48:08 -08:00
|
|
|
use rand::{distributions::Uniform, prelude::Distribution, seq::SliceRandom};
|
2023-02-15 08:08:55 -08:00
|
|
|
use solana_program::pubkey::Pubkey;
|
|
|
|
use solana_sdk::{
|
2023-02-21 02:04:02 -08:00
|
|
|
compute_budget, hash::Hash, instruction::Instruction, message::Message, signature::Keypair,
|
|
|
|
signer::Signer, transaction::Transaction,
|
2023-02-15 08:08:55 -08:00
|
|
|
};
|
2023-03-15 08:13:25 -07:00
|
|
|
use tokio::{sync::RwLock, task::JoinHandle};
|
2023-02-15 08:08:55 -08:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
helpers::{to_sdk_instruction, to_sp_pk},
|
|
|
|
mango::AccountKeys,
|
|
|
|
states::{PerpMarketCache, TransactionSendRecord},
|
2023-03-15 08:13:25 -07:00
|
|
|
tpu_manager::TpuManager,
|
2023-02-15 08:08:55 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
pub fn create_ask_bid_transaction(
|
|
|
|
c: &PerpMarketCache,
|
|
|
|
mango_account_pk: Pubkey,
|
|
|
|
mango_account_signer: &Keypair,
|
2023-02-22 04:29:10 -08:00
|
|
|
prioritization_fee: u64,
|
2023-02-15 08:08:55 -08:00
|
|
|
) -> Transaction {
|
|
|
|
let mango_account_signer_pk = to_sp_pk(&mango_account_signer.pubkey());
|
|
|
|
let offset = rand::random::<i8>() as i64;
|
|
|
|
let spread = rand::random::<u8>() as i64;
|
|
|
|
debug!(
|
|
|
|
"price:{:?} price_quote_lots:{:?} order_base_lots:{:?} offset:{:?} spread:{:?}",
|
|
|
|
c.price, c.price_quote_lots, c.order_base_lots, offset, spread
|
|
|
|
);
|
2023-02-21 02:04:02 -08:00
|
|
|
let mut instructions = vec![];
|
2023-02-22 04:29:10 -08:00
|
|
|
if prioritization_fee > 0 {
|
2023-02-23 02:02:57 -08:00
|
|
|
let pfees =
|
|
|
|
compute_budget::ComputeBudgetInstruction::set_compute_unit_price(prioritization_fee);
|
2023-02-21 02:04:02 -08:00
|
|
|
instructions.push(pfees);
|
|
|
|
}
|
|
|
|
|
2023-02-15 08:08:55 -08:00
|
|
|
let cancel_ix: Instruction = to_sdk_instruction(
|
|
|
|
cancel_all_perp_orders(
|
|
|
|
&c.mango_program_pk,
|
|
|
|
&c.mango_group_pk,
|
|
|
|
&mango_account_pk,
|
|
|
|
&mango_account_signer_pk,
|
|
|
|
&c.perp_market_pk,
|
|
|
|
&c.perp_market.bids,
|
|
|
|
&c.perp_market.asks,
|
|
|
|
10,
|
|
|
|
)
|
|
|
|
.unwrap(),
|
|
|
|
);
|
2023-02-21 02:04:02 -08:00
|
|
|
instructions.push(cancel_ix);
|
2023-02-15 08:08:55 -08:00
|
|
|
|
|
|
|
let place_bid_ix: Instruction = to_sdk_instruction(
|
|
|
|
place_perp_order2(
|
|
|
|
&c.mango_program_pk,
|
|
|
|
&c.mango_group_pk,
|
|
|
|
&mango_account_pk,
|
|
|
|
&mango_account_signer_pk,
|
|
|
|
&c.mango_cache_pk,
|
|
|
|
&c.perp_market_pk,
|
|
|
|
&c.perp_market.bids,
|
|
|
|
&c.perp_market.asks,
|
|
|
|
&c.perp_market.event_queue,
|
|
|
|
None,
|
|
|
|
&[],
|
|
|
|
Side::Bid,
|
|
|
|
c.price_quote_lots + offset - spread,
|
|
|
|
c.order_base_lots,
|
|
|
|
i64::MAX,
|
2023-04-14 04:45:22 -07:00
|
|
|
Utc::now().timestamp_micros() as u64,
|
2023-02-15 08:08:55 -08:00
|
|
|
mango::matching::OrderType::Limit,
|
|
|
|
false,
|
|
|
|
None,
|
|
|
|
64,
|
|
|
|
mango::matching::ExpiryType::Absolute,
|
|
|
|
)
|
|
|
|
.unwrap(),
|
|
|
|
);
|
2023-02-21 02:04:02 -08:00
|
|
|
instructions.push(place_bid_ix);
|
2023-02-15 08:08:55 -08:00
|
|
|
|
|
|
|
let place_ask_ix: Instruction = to_sdk_instruction(
|
|
|
|
place_perp_order2(
|
|
|
|
&c.mango_program_pk,
|
|
|
|
&c.mango_group_pk,
|
|
|
|
&mango_account_pk,
|
|
|
|
&mango_account_signer_pk,
|
|
|
|
&c.mango_cache_pk,
|
|
|
|
&c.perp_market_pk,
|
|
|
|
&c.perp_market.bids,
|
|
|
|
&c.perp_market.asks,
|
|
|
|
&c.perp_market.event_queue,
|
|
|
|
None,
|
|
|
|
&[],
|
|
|
|
Side::Ask,
|
|
|
|
c.price_quote_lots + offset + spread,
|
|
|
|
c.order_base_lots,
|
|
|
|
i64::MAX,
|
2023-04-14 04:45:22 -07:00
|
|
|
Utc::now().timestamp_micros() as u64,
|
2023-02-15 08:08:55 -08:00
|
|
|
mango::matching::OrderType::Limit,
|
|
|
|
false,
|
|
|
|
None,
|
|
|
|
64,
|
|
|
|
mango::matching::ExpiryType::Absolute,
|
|
|
|
)
|
|
|
|
.unwrap(),
|
|
|
|
);
|
2023-02-21 02:04:02 -08:00
|
|
|
instructions.push(place_ask_ix);
|
2023-02-15 08:08:55 -08:00
|
|
|
|
|
|
|
Transaction::new_unsigned(Message::new(
|
2023-02-21 02:04:02 -08:00
|
|
|
instructions.as_slice(),
|
2023-02-15 08:08:55 -08:00
|
|
|
Some(&mango_account_signer.pubkey()),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2023-02-23 02:02:57 -08:00
|
|
|
fn generate_random_fees(
|
|
|
|
prioritization_fee_proba: u8,
|
|
|
|
n: usize,
|
|
|
|
min_fee: u64,
|
|
|
|
max_fee: u64,
|
|
|
|
) -> Vec<u64> {
|
2023-02-22 04:29:10 -08:00
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
let range = Uniform::from(min_fee..max_fee);
|
|
|
|
let range_probability = Uniform::from(1..100);
|
|
|
|
(0..n)
|
|
|
|
.map(|_| {
|
|
|
|
if prioritization_fee_proba == 0 {
|
|
|
|
0
|
|
|
|
} else {
|
|
|
|
if range_probability.sample(&mut rng) <= prioritization_fee_proba {
|
|
|
|
range.sample(&mut rng) as u64
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
2023-02-23 02:02:57 -08:00
|
|
|
}
|
2023-02-22 04:29:10 -08:00
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
pub async fn send_mm_transactions(
|
2023-02-15 08:08:55 -08:00
|
|
|
quotes_per_second: u64,
|
|
|
|
perp_market_caches: &Vec<PerpMarketCache>,
|
2023-03-15 08:13:25 -07:00
|
|
|
tpu_manager: TpuManager,
|
2023-02-15 08:08:55 -08:00
|
|
|
mango_account_pk: Pubkey,
|
|
|
|
mango_account_signer: &Keypair,
|
|
|
|
blockhash: Arc<RwLock<Hash>>,
|
|
|
|
slot: &AtomicU64,
|
2023-02-22 04:29:10 -08:00
|
|
|
prioritization_fee_proba: u8,
|
2023-02-15 08:08:55 -08:00
|
|
|
) {
|
|
|
|
let mango_account_signer_pk = to_sp_pk(&mango_account_signer.pubkey());
|
|
|
|
// update quotes 2x per second
|
|
|
|
for _ in 0..quotes_per_second {
|
2023-02-23 02:02:57 -08:00
|
|
|
let prioritization_fee_by_market = generate_random_fees(
|
|
|
|
prioritization_fee_proba,
|
|
|
|
perp_market_caches.len(),
|
|
|
|
100,
|
|
|
|
1000,
|
|
|
|
);
|
|
|
|
for (i, c) in perp_market_caches.iter().enumerate() {
|
2023-02-22 04:29:10 -08:00
|
|
|
let prioritization_fee = prioritization_fee_by_market[i];
|
2023-02-21 02:04:02 -08:00
|
|
|
let mut tx = create_ask_bid_transaction(
|
|
|
|
c,
|
|
|
|
mango_account_pk,
|
|
|
|
&mango_account_signer,
|
|
|
|
prioritization_fee,
|
|
|
|
);
|
2023-02-15 08:08:55 -08:00
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
let recent_blockhash = *blockhash.read().await;
|
|
|
|
tx.sign(&[mango_account_signer], recent_blockhash);
|
2023-02-23 02:02:57 -08:00
|
|
|
|
2023-03-15 08:13:25 -07:00
|
|
|
let tx_send_record = TransactionSendRecord {
|
2023-02-15 08:08:55 -08:00
|
|
|
signature: tx.signatures[0],
|
|
|
|
sent_at: Utc::now(),
|
|
|
|
sent_slot: slot.load(Ordering::Acquire),
|
2023-03-15 08:13:25 -07:00
|
|
|
market_maker: Some(mango_account_signer_pk),
|
|
|
|
market: Some(c.perp_market_pk),
|
2023-02-22 04:29:10 -08:00
|
|
|
priority_fees: prioritization_fee,
|
2023-03-15 08:13:25 -07:00
|
|
|
keeper_instruction: None,
|
|
|
|
};
|
|
|
|
if !tpu_manager.send_transaction(&tx, tx_send_record).await {
|
|
|
|
println!("sending failed on tpu client");
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start_market_making_threads(
|
|
|
|
account_keys_parsed: Vec<AccountKeys>,
|
|
|
|
perp_market_caches: Vec<PerpMarketCache>,
|
|
|
|
exit_signal: Arc<AtomicBool>,
|
|
|
|
blockhash: Arc<RwLock<Hash>>,
|
|
|
|
current_slot: Arc<AtomicU64>,
|
2023-03-15 08:13:25 -07:00
|
|
|
tpu_manager: TpuManager,
|
2023-02-15 08:08:55 -08:00
|
|
|
duration: &Duration,
|
|
|
|
quotes_per_second: u64,
|
2023-02-22 04:29:10 -08:00
|
|
|
prioritization_fee_proba: u8,
|
2023-02-24 06:48:08 -08:00
|
|
|
number_of_markers_per_mm: u8,
|
2023-02-15 08:08:55 -08:00
|
|
|
) -> Vec<JoinHandle<()>> {
|
2023-02-24 06:48:08 -08:00
|
|
|
let mut rng = rand::thread_rng();
|
2023-02-15 08:08:55 -08:00
|
|
|
account_keys_parsed
|
|
|
|
.iter()
|
|
|
|
.map(|account_keys| {
|
2023-03-14 05:39:19 -07:00
|
|
|
let exit_signal = exit_signal.clone();
|
2023-02-15 08:08:55 -08:00
|
|
|
let blockhash = blockhash.clone();
|
|
|
|
let current_slot = current_slot.clone();
|
|
|
|
let duration = duration.clone();
|
|
|
|
let perp_market_caches = perp_market_caches.clone();
|
|
|
|
let mango_account_pk =
|
|
|
|
Pubkey::from_str(account_keys.mango_account_pks[0].as_str()).unwrap();
|
|
|
|
let mango_account_signer =
|
|
|
|
Keypair::from_bytes(account_keys.secret_key.as_slice()).unwrap();
|
2023-03-15 08:13:25 -07:00
|
|
|
let tpu_manager = tpu_manager.clone();
|
2023-02-15 08:08:55 -08:00
|
|
|
|
|
|
|
info!(
|
2023-03-06 02:05:25 -08:00
|
|
|
"wallet: {:?} mango account: {:?}",
|
2023-02-15 08:08:55 -08:00
|
|
|
mango_account_signer.pubkey(),
|
|
|
|
mango_account_pk
|
|
|
|
);
|
2023-02-24 06:48:08 -08:00
|
|
|
let perp_market_caches = perp_market_caches
|
|
|
|
.choose_multiple(&mut rng, number_of_markers_per_mm as usize)
|
|
|
|
.map(|x| x.clone())
|
|
|
|
.collect_vec();
|
2023-02-15 08:08:55 -08:00
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
tokio::spawn(async move {
|
|
|
|
for _i in 0..duration.as_secs() {
|
|
|
|
if exit_signal.load(Ordering::Relaxed) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let start = Instant::now();
|
2023-02-21 02:04:02 -08:00
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
// send market maker transactions
|
|
|
|
send_mm_transactions(
|
|
|
|
quotes_per_second,
|
|
|
|
&perp_market_caches,
|
2023-03-15 08:13:25 -07:00
|
|
|
tpu_manager.clone(),
|
2023-03-14 05:39:19 -07:00
|
|
|
mango_account_pk,
|
|
|
|
&mango_account_signer,
|
|
|
|
blockhash.clone(),
|
|
|
|
current_slot.as_ref(),
|
|
|
|
prioritization_fee_proba,
|
|
|
|
)
|
|
|
|
.await;
|
2023-02-15 08:08:55 -08:00
|
|
|
|
2023-03-14 05:39:19 -07:00
|
|
|
let elapsed_millis: u64 = start.elapsed().as_millis() as u64;
|
|
|
|
if elapsed_millis < 1000 {
|
|
|
|
tokio::time::sleep(Duration::from_millis(1000 - elapsed_millis)).await;
|
|
|
|
} else {
|
|
|
|
warn!(
|
|
|
|
"time taken to send transactions is greater than 1000ms {}",
|
|
|
|
elapsed_millis
|
|
|
|
);
|
2023-02-15 08:08:55 -08:00
|
|
|
}
|
2023-03-14 05:39:19 -07:00
|
|
|
}
|
|
|
|
})
|
2023-02-15 08:08:55 -08:00
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|