use deribit_websocket::prelude::*;
use serde_json::Value;
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
deribit_websocket::install_default_crypto_provider()?;
setup_logger();
let mut client = DeribitWebSocketClient::default();
tracing::info!("๐ Starting Mass Quote Options Example");
let quote_count = Arc::new(Mutex::new(0u32));
let quote_count_clone = Arc::clone("e_count);
client.set_message_handler(
move |message: &str| -> Result<(), WebSocketError> {
if let Ok(json_msg) = serde_json::from_str::<Value>(message)
&& let Some(method) = json_msg.get("method")
&& method.as_str() == Some("subscription")
&& let Some(params) = json_msg.get("params")
&& let Some(channel) = params.get("channel").and_then(|c| c.as_str())
{
if channel.starts_with("markprice.options") {
let mut count = quote_count_clone.lock().unwrap();
*count += 1;
tracing::info!("๐ Mark Price Update #{}: {}", *count, channel);
if let Some(data) = params.get("data") {
if let Some(mark_price) = data.get("mark_price") {
tracing::info!(" ๐ฐ Mark Price: {}", mark_price);
}
if let Some(delta) = data.get("delta") {
tracing::info!(" ๐ Delta: {}", delta);
}
if let Some(gamma) = data.get("gamma") {
tracing::info!(" ๐ Gamma: {}", gamma);
}
if let Some(theta) = data.get("theta") {
tracing::info!(" โฐ Theta: {}", theta);
}
if let Some(vega) = data.get("vega") {
tracing::info!(" ๐ Vega: {}", vega);
}
}
}
else if channel == "user.trades"
&& let Some(data) = params.get("data")
&& let Some(trades) = data.as_array()
{
for trade in trades {
if let Some(instrument) = trade.get("instrument_name")
&& instrument
.as_str()
.is_some_and(|s| s.contains("-C") || s.contains("-P"))
{
tracing::info!("๐ฐ Options Trade Executed:");
tracing::info!(" ๐ฏ Instrument: {}", instrument);
if let Some(side) = trade.get("direction") {
tracing::info!(" ๐ Side: {}", side);
}
if let Some(amount) = trade.get("amount") {
tracing::info!(" ๐ Amount: {}", amount);
}
if let Some(price) = trade.get("price") {
tracing::info!(" ๐ต Price: {}", price);
}
if let Some(mark_price) = trade.get("mark_price") {
tracing::info!(" ๐ฏ Mark Price: {}", mark_price);
}
}
}
}
}
Ok(())
},
|message, error| {
tracing::error!("โ Error processing message '{}': {}", message, error);
},
);
let client = client;
client.connect().await?;
tracing::info!("โ
Connected to Deribit WebSocket");
let (client_id, client_secret) = client.config.get_credentials().unwrap();
client.authenticate(client_id, client_secret).await?;
tracing::info!("๐ Authenticated successfully");
client
.subscribe(vec![
"markprice.options.BTC-29MAR24-50000-C".to_string(),
"markprice.options.BTC-29MAR24-50000-P".to_string(),
"user.trades.any.any".to_string(),
])
.await?;
tracing::info!("๐ก Subscribed to options mark prices and trades");
tracing::info!("๐ Setting up options MMP group...");
let options_mmp_config = MmpGroupConfig::new(
"btc_options_mm".to_string(),
50.0, 25.0, 2000, 10000, )?;
match client.set_mmp_config(options_mmp_config).await {
Ok(()) => tracing::info!("โ
Options MMP group 'btc_options_mm' configured"),
Err(WebSocketError::ApiError {
code: 11050,
message,
..
}) => tracing::warn!(
"โ ๏ธ Options MMP group skipped: {} (code 11050 โ MMP not activated on this account)",
message
),
Err(e) => return Err(e.into()),
}
tracing::info!("๐ Creating options mass quotes...");
let call_option = "BTC-29MAR24-50000-C";
let put_option = "BTC-29MAR24-50000-P";
let options_quotes = vec![
Quote::buy(call_option.to_string(), 0.1, 0.05)
.with_quote_set_id("atm_calls".to_string())
.with_post_only(true),
Quote::sell(call_option.to_string(), 0.1, 0.08)
.with_quote_set_id("atm_calls".to_string())
.with_post_only(true),
Quote::buy(put_option.to_string(), 0.1, 0.04)
.with_quote_set_id("atm_puts".to_string())
.with_post_only(true),
Quote::sell(put_option.to_string(), 0.1, 0.07)
.with_quote_set_id("atm_puts".to_string())
.with_post_only(true),
];
let options_request = MassQuoteRequest::new("btc_options_mm".to_string(), options_quotes)
.with_quote_id("options_batch_1".to_string())
.with_detailed_errors();
match client.mass_quote(options_request).await {
Ok(response) => {
tracing::info!(
"โ
Options quotes: {} placed, {} errors",
response.success_count,
response.error_count
);
if let Some(errors) = response.errors {
for error in errors {
tracing::warn!(
"โ Options quote error for {} {}: {} ({})",
error.instrument_name,
error.side,
error.error_message,
error.error_code
);
}
}
}
Err(e) => {
tracing::error!("โ Options mass quote failed: {}", e);
}
}
tracing::info!("๐ Monitoring options for 20 seconds...");
let start_time = std::time::Instant::now();
let monitor_duration = std::time::Duration::from_secs(20);
while start_time.elapsed() < monitor_duration {
tokio::select! {
result = client.receive_and_process_message() => {
if let Err(e) = result {
tracing::warn!("โ ๏ธ Message processing error: {}", e);
}
}
_ = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => {
}
}
}
tracing::info!("๐ Demonstrating delta-based quote management...");
let delta_cancel_request = CancelQuotesRequest::by_delta_range(0.3, 0.7);
match client.cancel_quotes(delta_cancel_request).await {
Ok(response) => {
tracing::info!(
"โ
Cancelled {} quotes in delta range 0.3-0.7",
response.cancelled_count
);
}
Err(e) => {
tracing::warn!("โ ๏ธ Failed to cancel quotes by delta: {}", e);
}
}
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
tracing::info!("๐ Updating options quotes with tighter spreads...");
let updated_options_quotes = vec![
Quote::buy(call_option.to_string(), 0.05, 0.055)
.with_quote_set_id("tight_calls".to_string()),
Quote::sell(call_option.to_string(), 0.05, 0.065)
.with_quote_set_id("tight_calls".to_string()),
Quote::buy(put_option.to_string(), 0.05, 0.045).with_quote_set_id("tight_puts".to_string()),
Quote::sell(put_option.to_string(), 0.05, 0.055)
.with_quote_set_id("tight_puts".to_string()),
];
let update_request =
MassQuoteRequest::new("btc_options_mm".to_string(), updated_options_quotes)
.with_quote_id("options_update_1".to_string());
match client.mass_quote(update_request).await {
Ok(response) => {
tracing::info!(
"โ
Updated options quotes: {} placed",
response.success_count
);
}
Err(e) => {
tracing::error!("โ Failed to update options quotes: {}", e);
}
}
tracing::info!("๐ Checking final options positions...");
match client
.get_open_orders(
Some("BTC".to_string()),
Some("option".to_string()),
Some("quote".to_string()),
)
.await
{
Ok(orders) => {
tracing::info!("๐ Found {} open options quotes:", orders.len());
for order in &orders {
tracing::info!(
" ๐ {} {} {} @ {} (Set: {})",
order.instrument_name,
order.side.to_uppercase(),
order.amount,
order.price,
order.quote_set_id.as_deref().unwrap_or("none")
);
}
}
Err(e) => {
tracing::warn!("โ ๏ธ Failed to get open options orders: {}", e);
}
}
tracing::info!("๐งน Cleaning up options quotes...");
let cancel_all_options = CancelQuotesRequest::by_currency("BTC".to_string());
match client.cancel_quotes(cancel_all_options).await {
Ok(response) => {
tracing::info!("โ
Cancelled {} options quotes", response.cancelled_count);
}
Err(e) => {
tracing::warn!("โ ๏ธ Failed to cancel options quotes: {}", e);
}
}
let cleanup_config = MmpGroupConfig::new(
"btc_options_mm".to_string(),
50.0,
25.0,
0, 10000,
)?
.disable();
match client.set_mmp_config(cleanup_config).await {
Ok(()) => {
tracing::info!("โ
Options MMP group disabled");
}
Err(e) => {
tracing::warn!("โ ๏ธ Failed to disable options MMP group: {}", e);
}
}
tracing::info!("๐ฏ Mass Quote Options Example completed successfully!");
Ok(())
}