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