use dotenv::dotenv;
use serde_json::Value;
use std::env;
use tokio::time::{sleep, Duration};
use webull_unofficial::{
stream::{StreamConfig, TopicTypes},
PaperWebullClient, StreamConn,
};
#[tokio::main]
async fn main() -> webull_unofficial::error::Result<()> {
dotenv().ok();
env_logger::init();
let username = env::var("WEBULL_USERNAME").expect("WEBULL_USERNAME not set");
let password = env::var("WEBULL_PASSWORD").expect("WEBULL_PASSWORD not set");
let mut client = PaperWebullClient::new(Some(6))?;
println!("Logging in...");
let login_response = client
.login(&username, &password, None, None, None, None)
.await?;
println!("Login successful!");
let config = StreamConfig {
debug: true,
..Default::default()
};
let mut stream = StreamConn::new(Some(config));
stream.set_price_callback(|topic: Value, data: Value| {
println!("Price Update:");
println!(" Topic: {}", topic);
if let Some(ticker_id) = topic.get("tickerId") {
println!(" Ticker ID: {}", ticker_id);
}
if let Some(price) = data.get("close") {
println!(" Price: {}", price);
}
if let Some(volume) = data.get("volume") {
println!(" Volume: {}", volume);
}
if let Some(change) = data.get("changeRatio") {
if let Some(change_val) = change.as_f64() {
println!(" Change: {:.2}%", change_val * 100.0);
}
}
println!("---");
});
stream.set_order_callback(|topic: Value, data: Value| {
println!("Order Update:");
println!(" Topic: {}", topic);
if let Some(order_id) = data.get("orderId") {
println!(" Order ID: {}", order_id);
}
if let Some(status) = data.get("orderStatus") {
println!(" Status: {}", status);
}
if let Some(filled) = data.get("filledQuantity") {
println!(" Filled Quantity: {}", filled);
}
println!("---");
});
println!("\nConnecting to streaming service...");
let access_token = &login_response.access_token;
let did = client.get_did().to_string();
stream.connect(access_token, &did).await?;
println!("Connected to streaming service!");
let symbols = vec!["AAPL", "TSLA", "SPY"];
for symbol in &symbols {
println!("\nSearching for {}...", symbol);
if let Ok(tickers) = client.find_ticker(symbol).await {
if let Some(ticker) = tickers.first() {
println!(
"Subscribing to {} (ID: {})",
ticker.symbol, ticker.ticker_id
);
stream
.subscribe_ticker(&ticker.ticker_id.to_string(), TopicTypes::basic())
.await?;
}
}
}
if let Some(account_id) = client.get_account_id_str() {
println!("\nSubscribing to order updates for account: {}", account_id);
stream.subscribe_orders(&account_id).await?;
}
println!("\nStreaming data for 60 seconds...");
println!("Press Ctrl+C to stop\n");
for i in 0..60 {
sleep(Duration::from_secs(1)).await;
if i % 10 == 0 {
println!("Active subscriptions: {:?}", stream.get_subscriptions());
}
}
println!("\nDisconnecting...");
stream.disconnect().await?;
client.logout().await?;
println!("Done!");
Ok(())
}