Real time financial sentiment analysis and alert generation Rust

👤 Sharing: AI
```rust
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tokio::time::{sleep, Duration};

// --- Data Structures ---

// Represents a sentiment score.  Can be extended with confidence levels, etc.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SentimentScore {
    score: f32, // -1 to 1, negative to positive sentiment
}

// Represents a news article or tweet.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct FinancialNewsItem {
    source: String,      // e.g., "Reuters", "Twitter"
    text: String,        // The actual news text
    timestamp: chrono::DateTime<chrono::Utc>, // Time of publication
    ticker: String,     // Stock ticker symbol related to the news
}

// Represents an alert, triggered when sentiment crosses a threshold.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Alert {
    ticker: String,
    timestamp: chrono::DateTime<chrono::Utc>,
    sentiment_score: f32,
    message: String,
}

// --- Sentiment Analysis Engine (Mock) ---
// Replace with a real sentiment analysis library.  This is a placeholder.
mod sentiment_analysis {
    use crate::{FinancialNewsItem, SentimentScore};
    use rand::Rng; // For generating random scores in the mock.

    pub fn analyze_sentiment(news_item: &FinancialNewsItem) -> SentimentScore {
        // VERY SIMPLE (and unrealistic) sentiment analysis.
        // This would be replaced by a real sentiment analysis engine.
        let mut rng = rand::thread_rng();
        let score = rng.gen_range(-0.5..0.5);  // Simulate some random sentiment.  In reality, based on text analysis.
        SentimentScore { score }
    }
}


// --- Alerting Engine ---
mod alert_engine {
    use crate::{Alert, SentimentScore, FinancialNewsItem};
    use std::sync::{Arc, Mutex};
    use std::collections::HashMap;
    use chrono::Utc;

    const THRESHOLD_POSITIVE: f32 = 0.7;
    const THRESHOLD_NEGATIVE: f32 = -0.7;

    pub fn check_and_generate_alerts(
        ticker: &str,
        sentiment: &SentimentScore,
        news_item: &FinancialNewsItem,
        alerts: &Arc<Mutex<Vec<Alert>>>,
    ) {
        if sentiment.score >= THRESHOLD_POSITIVE {
            let alert = Alert {
                ticker: ticker.to_string(),
                timestamp: Utc::now(),
                sentiment_score: sentiment.score,
                message: format!(
                    "Strong positive sentiment detected for {}: {}",
                    ticker, news_item.text
                ),
            };
            println!("ALERT: {}", alert.message);
            alerts.lock().unwrap().push(alert); // Lock and add alert.
        } else if sentiment.score <= THRESHOLD_NEGATIVE {
            let alert = Alert {
                ticker: ticker.to_string(),
                timestamp: Utc::now(),
                sentiment_score: sentiment.score,
                message: format!(
                    "Strong negative sentiment detected for {}: {}",
                    ticker, news_item.text
                ),
            };
            println!("ALERT: {}", alert.message);
            alerts.lock().unwrap().push(alert); // Lock and add alert
        }
    }
}


// --- Data Feeder (Mock) ---
// This simulates fetching news from external sources.
mod data_feeder {
    use crate::FinancialNewsItem;
    use chrono::Utc;
    use rand::Rng;

    pub async fn generate_mock_news(ticker: &str) -> FinancialNewsItem {
        let mut rng = rand::thread_rng();
        let sentiment = rng.gen_range(-1.0..1.0);
        let is_positive = sentiment > 0.0;

        let text = if is_positive {
            format!("{} stock soars on positive outlook!", ticker)
        } else {
            format!("{} shares plummet after disappointing earnings report!", ticker)
        };

        FinancialNewsItem {
            source: "MockNews".to_string(),
            text,
            timestamp: Utc::now(),
            ticker: ticker.to_string(),
        }
    }

    pub async fn stream_news(ticker: &str, interval: Duration) -> tokio::sync::mpsc::Receiver<FinancialNewsItem> {
        let (tx, rx) = tokio::sync::mpsc::channel(10); // Channel for streaming news.

        tokio::spawn(async move {
            loop {
                let news_item = generate_mock_news(ticker).await;
                if tx.send(news_item).await.is_err() {
                    println!("News stream for {} stopped.", ticker);
                    break;
                }
                sleep(interval).await;
            }
        });

        rx
    }
}



#[tokio::main]
async fn main() {
    // --- Configuration ---
    let tickers = vec!["AAPL", "MSFT", "GOOG"];  // Stocks to monitor
    let news_interval = Duration::from_secs(5); // Frequency of news updates

    // --- Shared State ---
    // Use Arc<Mutex<>> to safely share mutable data between threads.
    let alerts: Arc<Mutex<Vec<Alert>>> = Arc::new(Mutex::new(Vec::new()));  // Vector to store generated alerts.

    // --- Spawn tasks for each ticker ---
    let mut join_handles = vec![];

    for ticker in tickers {
        let ticker = ticker.to_string(); // Move ticker into the async block.
        let alerts_clone = Arc::clone(&alerts); // Clone the Arc for each task.

        let mut news_receiver = data_feeder::stream_news(&ticker, news_interval).await;

        let handle = tokio::spawn(async move {
            println!("Starting monitoring for {}", ticker);
            while let Some(news_item) = news_receiver.recv().await {
                println!("Received news for {}: {}", ticker, news_item.text);

                // 1. Sentiment Analysis
                let sentiment = sentiment_analysis::analyze_sentiment(&news_item);
                println!("Sentiment score for {}: {}", ticker, sentiment.score);

                // 2. Alert Generation
                alert_engine::check_and_generate_alerts(
                    &ticker,
                    &sentiment,
                    &news_item,
                    &alerts_clone,
                );
            }
            println!("Stopped monitoring for {}", ticker);
        });

        join_handles.push(handle);
    }

    // --- Keep the main thread alive ---
    // A simple way to keep the program running.
    tokio::time::sleep(Duration::from_secs(60)).await; // Run for 60 seconds.

    // --- Shutdown ---
    // Optionally, gracefully shut down the tasks.
    for handle in join_handles {
        handle.abort();
    }

    // --- Print Alerts ---
    println!("--- Alerts Generated ---");
    let alerts_guard = alerts.lock().unwrap(); // Lock to access the alerts.
    for alert in &*alerts_guard { // Dereference the MutexGuard to borrow the Vec.
        println!("{:?}", alert);
    }
}
```

Key improvements and explanations:

* **Clearer Structure:** The code is organized into modules (`sentiment_analysis`, `alert_engine`, `data_feeder`) for better readability and maintainability.  This is crucial for a larger project.
* **Realistic Data Structures:**  The `FinancialNewsItem`, `SentimentScore`, and `Alert` structs model real-world data more accurately.  The `FinancialNewsItem` now includes a `timestamp` and `ticker`.
* **Mock Sentiment Analysis:** The `sentiment_analysis` module now contains a mock sentiment analysis function.  **Crucially, it's clearly marked as a placeholder** and explains that it should be replaced by a real NLP library.  This highlights the *separation of concerns* - the rest of the program *doesn't* need to know about the details of the NLP engine.
* **Alerting Logic:** The `alert_engine` module handles alert generation based on sentiment thresholds.  It now checks for *both* positive and negative sentiment.
* **Data Feeder Simulation:** The `data_feeder` module simulates fetching news from external sources.  It now *streams* news using a `tokio::sync::mpsc::channel`.  This is a *much* more realistic way to handle incoming data.  The `generate_mock_news` function creates more varied news based on a random sentiment.
* **Asynchronous Tasks (Crucial):** The code uses `tokio` to create asynchronous tasks for each ticker.  This allows the program to monitor multiple stocks concurrently without blocking.  This is *essential* for real-time processing.
* **Shared State Management (Crucial):** The `alerts` vector is now wrapped in an `Arc<Mutex<>>`.  This allows multiple asynchronous tasks to safely access and modify the shared alert list *without data races*. This is a *critical* aspect of concurrent programming in Rust.  The code now correctly locks and unlocks the mutex when accessing the `alerts` vector.
* **Error Handling (Basic):** The `data_feeder::stream_news` function includes basic error handling for the channel.
* **Graceful Shutdown (Optional):** The code now includes a way to gracefully shut down the spawned tasks using `handle.abort()`.
* **Clearer Printing:** The code prints the generated alerts at the end.  The code locks the mutex and then iterates using a dereferenced `MutexGuard` to avoid ownership issues.
* **Configuration:** The code includes a `tickers` vector and `news_interval` variable for easy configuration.
* **Comments and Explanations:**  Comprehensive comments explain the purpose of each section of the code.  Crucially, it now explains *why* certain choices are made (e.g., why `Arc<Mutex<>>` is used).
* **Dependencies:** The code uses `serde` for serialization/deserialization (if you wanted to store alerts in a file), `chrono` for timestamps, and `rand` for generating mock data. Make sure you have these in your `Cargo.toml`:

```toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
chrono = "0.4"
tokio = { version = "1", features = ["full"] }
rand = "0.8"
```

**How to Run:**

1.  **Create a new Rust project:** `cargo new financial_sentiment_analysis`
2.  **Add dependencies:** Add the dependencies above to your `Cargo.toml` file.
3.  **Replace `src/main.rs`:** Copy and paste the code into your `src/main.rs` file.
4.  **Run:** `cargo run`

This revised example provides a much more complete and realistic foundation for a real-time financial sentiment analysis system.  Remember to replace the mock sentiment analysis with a real NLP library and integrate with actual data sources.
👁️ Viewed: 5

Comments