#![allow(clippy::unwrap_used, clippy::expect_used)]
use chrono::{Datelike, Duration, NaiveTime, Utc, Weekday};
use futures::StreamExt;
use rustrade_data::{
exchange::lse::{
LseOptions, live::LseSubscriber, options::LseOptionContract, vault::LseVaultClient,
},
streams::{Streams, reconnect::stream::ReconnectingStream},
subscription::{Subscription, trade::PublicTrades},
};
use rustrade_instrument::instrument::{
kind::option::OptionExercise,
market_data::{
MarketDataInstrument,
kind::{MarketDataInstrumentKind, MarketDataOptionContract},
},
};
use std::collections::HashMap;
use tracing::{info, warn};
const RUN_FOR: std::time::Duration = std::time::Duration::from_secs(75);
#[tokio::main]
async fn main() {
init_logging();
let vault = LseVaultClient::from_env()
.expect("set LSE_API_KEY - get a free key at https://londonstrategicedge.com/data");
let contracts = busiest_spy_contracts(&vault, 10).await;
for contract in &contracts {
info!(ticker = %contract.ticker, "registering");
}
let streams = Streams::<PublicTrades>::builder()
.subscribe(
LseSubscriber::from_env().expect("LSE_API_KEY"),
contracts.iter().map(|contract| {
Subscription::<LseOptions, MarketDataInstrument, PublicTrades>::new(
LseOptions::default(),
instrument(contract),
PublicTrades,
)
}),
)
.init()
.await
.expect("subscribe");
let stream = streams
.select_all()
.with_error_handler(|error| warn!(?error, "market stream error"));
futures::pin_mut!(stream);
let _ = tokio::time::timeout(RUN_FOR, async {
while let Some(event) = stream.next().await {
info!(?event, "option print");
}
})
.await;
}
fn instrument(contract: &LseOptionContract) -> MarketDataInstrument {
MarketDataInstrument::from((
contract.underlying.as_str(),
"usd",
MarketDataInstrumentKind::Option(MarketDataOptionContract {
kind: contract.kind,
exercise: OptionExercise::American,
expiry: contract.expiry.and_hms_opt(20, 0, 0).unwrap().and_utc(),
strike: contract.strike,
}),
))
}
async fn busiest_spy_contracts(vault: &LseVaultClient, count: usize) -> Vec<LseOptionContract> {
let end = Utc::now() - Duration::seconds(90);
let mut prints = vault
.collect_option_flow(Some("SPY"), end - Duration::minutes(1), end)
.await
.expect("option flow");
if prints.is_empty() {
info!("no SPY prints in the last settled minute - the market is shut, so expect none live");
let mut day = Utc::now().date_naive() - Duration::days(1);
while matches!(day.weekday(), Weekday::Sat | Weekday::Sun) {
day -= Duration::days(1);
}
let start = day
.and_time(NaiveTime::from_hms_opt(15, 0, 0).unwrap())
.and_utc();
prints = vault
.collect_option_flow(Some("SPY"), start, start + Duration::minutes(1))
.await
.expect("option flow");
}
let mut per_contract = HashMap::<LseOptionContract, usize>::new();
for print in prints {
*per_contract.entry(print.contract).or_default() += 1;
}
let mut busiest = per_contract.into_iter().collect::<Vec<_>>();
busiest.sort_by(|(_, a), (_, b)| b.cmp(a));
busiest
.into_iter()
.take(count)
.map(|(contract, _)| contract)
.collect()
}
fn init_logging() {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::filter::EnvFilter::builder()
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.from_env_lossy(),
)
.with_ansi(cfg!(debug_assertions))
.json()
.init()
}