use std::fmt::Debug;
use nautilus_bybit::{
common::enums::BybitProductType, config::BybitDataClientConfig,
factories::BybitDataClientFactory,
};
use nautilus_common::{
actor::{DataActor, DataActorConfig, DataActorCore},
enums::Environment,
nautilus_actor,
timer::TimeEvent,
};
use nautilus_live::node::LiveNode;
use nautilus_model::{
data::option_chain::OptionGreeks,
enums::OptionKind,
identifiers::{ClientId, InstrumentId, TraderId, Venue},
instruments::Instrument,
stubs::TestDefault,
};
use ustr::Ustr;
#[derive(Debug)]
struct GreeksTester {
core: DataActorCore,
client_id: ClientId,
subscribed_instruments: Vec<InstrumentId>,
}
nautilus_actor!(GreeksTester);
impl GreeksTester {
fn new(client_id: ClientId) -> Self {
Self {
core: DataActorCore::new(DataActorConfig {
actor_id: Some("GREEKS_TESTER-001".into()),
..Default::default()
}),
client_id,
subscribed_instruments: Vec::new(),
}
}
}
impl DataActor for GreeksTester {
fn on_start(&mut self) -> anyhow::Result<()> {
let venue = Venue::new("BYBIT");
let underlying_filter = Ustr::from("BTC");
let mut options: Vec<(InstrumentId, f64, u64)> = {
let cache = self.cache();
let instruments = cache.instruments(&venue, Some(&underlying_filter));
instruments
.iter()
.filter_map(|inst| {
if inst.option_kind() == Some(OptionKind::Call) {
let expiry = inst.expiration_ns()?.as_u64();
let strike = inst.strike_price()?.as_f64();
Some((inst.id(), strike, expiry))
} else {
None
}
})
.collect()
};
let now_ns = self.timestamp_ns().as_u64();
options.retain(|(_, _, exp)| *exp > now_ns);
if options.is_empty() {
log::warn!("No BTC CALL options found in cache (all expired)");
return Ok(());
}
let nearest_expiry = options.iter().map(|(_, _, exp)| *exp).min().unwrap();
options.retain(|(_, _, exp)| *exp == nearest_expiry);
options.sort_by(|(_, a, _), (_, b, _)| a.partial_cmp(b).unwrap());
log::info!(
"Found {} BTC CALL options at nearest expiry (ts={})",
options.len(),
nearest_expiry,
);
for (id, strike, expiry) in &options {
log::info!(" {id} strike={strike} expiry={expiry}");
}
let client_id = self.client_id;
for (instrument_id, _, _) in &options {
self.subscribe_option_greeks(*instrument_id, Some(client_id), None);
self.subscribed_instruments.push(*instrument_id);
}
log::info!(
"Subscribed to option greeks for {} instruments",
self.subscribed_instruments.len(),
);
Ok(())
}
fn on_option_greeks(&mut self, greeks: &OptionGreeks) -> anyhow::Result<()> {
log::info!(
"GREEKS | {} | delta={:.4} gamma={:.6} vega={:.4} theta={:.4} rho={:.6} | \
mark_iv={} bid_iv={} ask_iv={} | \
underlying={} oi={}",
greeks.instrument_id,
greeks.delta,
greeks.gamma,
greeks.vega,
greeks.theta,
greeks.rho,
greeks
.mark_iv
.map_or("-".to_string(), |v| format!("{v:.2}")),
greeks.bid_iv.map_or("-".to_string(), |v| format!("{v:.2}")),
greeks.ask_iv.map_or("-".to_string(), |v| format!("{v:.2}")),
greeks
.underlying_price
.map_or("-".to_string(), |v| format!("{v:.2}")),
greeks
.open_interest
.map_or("-".to_string(), |v| format!("{v:.1}")),
);
Ok(())
}
fn on_stop(&mut self) -> anyhow::Result<()> {
let ids: Vec<InstrumentId> = self.subscribed_instruments.drain(..).collect();
let client_id = self.client_id;
for instrument_id in ids {
self.unsubscribe_option_greeks(instrument_id, Some(client_id), None);
}
log::info!("Unsubscribed from all option greeks");
Ok(())
}
fn on_time_event(&mut self, _event: &TimeEvent) -> anyhow::Result<()> {
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let environment = Environment::Live;
let trader_id = TraderId::test_default();
let client_id = ClientId::new("BYBIT");
let bybit_config = BybitDataClientConfig {
api_key: None, api_secret: None, product_types: vec![BybitProductType::Option],
..Default::default()
};
let client_factory = BybitDataClientFactory::new();
let mut node = LiveNode::builder(trader_id, environment)?
.with_name("BYBIT-GREEKS-TESTER-001".to_string())
.add_data_client(None, Box::new(client_factory), Box::new(bybit_config))?
.with_delay_post_stop_secs(5)
.build()?;
let tester = GreeksTester::new(client_id);
node.add_actor(tester)?;
node.run().await?;
Ok(())
}