This guide shows you how to build systematic trading strategies using FinBrain’s news sentiment data. You’ll learn to interpret sentiment scores, detect sentiment shifts, and combine sentiment with other signals.
Understanding Sentiment Data
FinBrain’s sentiment scores are derived from AI analysis of financial news:
- Sentiment Score: Numeric values between -1 (bearish) and +1 (bullish) for each date
- sentiments: Array of
{date, score}objects containing daily sentiment scores
Prerequisites
pip install finbrain-python pandas numpyStep 1: Basic Sentiment Analysis
Fetch sentiment data from the v2 API and convert it to a pandas DataFrame:
from finbrain import FinBrainClientimport pandas as pd
fb = FinBrainClient(api_key="YOUR_API_KEY")
# Get sentiment data for AAPL as a DataFramesentiment = fb.sentiments.ticker("AAPL", as_dataframe=True)
print(sentiment.tail())# score# date# 2025-11-04 0.186# 2025-11-01 0.339# 2025-10-31 0.565
# Basic statisticsprint(f"\nSentiment Statistics:")print(f"Mean: {sentiment['score'].mean():.3f}")print(f"Std: {sentiment['score'].std():.3f}")print(f"Current: {sentiment['score'].iloc[-1]:.3f}")
# Add technical indicatorssentiment["ma5"] = sentiment["score"].rolling(5).mean()sentiment["ma20"] = sentiment["score"].rolling(20).mean()sentiment["momentum"] = sentiment["score"] - sentiment["ma5"]
print(sentiment.tail())Step 2: Sentiment Signal Generation
Generate trading signals based on sentiment levels and momentum:
def fetch_sentiment(symbol): """Fetch sentiment data and return as DataFrame""" return fb.sentiments.ticker(symbol, as_dataframe=True)
def generate_sentiment_signal(symbol): """Generate trading signal from sentiment data""" df = fetch_sentiment(symbol)
if df.empty or len(df) < 5: return {"signal": "neutral", "reason": "insufficient_data"}
# Current values current_score = df["score"].iloc[-1]
# Moving averages ma5 = df["score"].tail(5).mean() ma20 = df["score"].tail(20).mean() if len(df) >= 20 else ma5
# Momentum momentum = current_score - ma5
# Generate signal score = 0 reasons = []
# Level-based signals if current_score > 0.5: score += 2 reasons.append(f"Strong positive sentiment ({current_score:.2f})") elif current_score > 0.2: score += 1 reasons.append(f"Positive sentiment ({current_score:.2f})") elif current_score < -0.5: score -= 2 reasons.append(f"Strong negative sentiment ({current_score:.2f})") elif current_score < -0.2: score -= 1 reasons.append(f"Negative sentiment ({current_score:.2f})")
# Momentum signals if momentum > 0.2: score += 1 reasons.append(f"Positive momentum ({momentum:.2f})") elif momentum < -0.2: score -= 1 reasons.append(f"Negative momentum ({momentum:.2f})")
# Trend signals if ma5 > ma20: score += 0.5 reasons.append("Short-term trend positive") elif ma5 < ma20: score -= 0.5 reasons.append("Short-term trend negative")
# Determine signal if score >= 2: signal = "strong_buy" elif score >= 1: signal = "buy" elif score <= -2: signal = "strong_sell" elif score <= -1: signal = "sell" else: signal = "neutral"
return { "symbol": symbol, "signal": signal, "score": score, "sentiment": current_score, "momentum": momentum, "reasons": reasons }
# Generate signalsignal = generate_sentiment_signal("AAPL")print(f"\nSignal: {signal['signal']}")print(f"Score: {signal['score']}")for reason in signal["reasons"]: print(f" - {reason}")Step 3: Contrarian Sentiment Strategy
Use extreme sentiment as contrarian signals:
def contrarian_signal(symbol, lookback=20): """Generate contrarian signals from extreme sentiment""" df = fetch_sentiment(symbol)
if df.empty or len(df) < lookback: return None
df = df.tail(lookback)
# Calculate z-score current = df["score"].iloc[-1] mean = df["score"].mean() std = df["score"].std()
if std == 0: return None
z_score = (current - mean) / std
# Contrarian signals if z_score > 2: signal = "contrarian_sell" # Extreme optimism reason = "Extreme bullish sentiment - potential reversal" elif z_score < -2: signal = "contrarian_buy" # Extreme pessimism reason = "Extreme bearish sentiment - potential reversal" else: signal = "neutral" reason = "Sentiment within normal range"
return { "symbol": symbol, "signal": signal, "z_score": z_score, "current_sentiment": current, "mean_sentiment": mean, "reason": reason }
# Check for contrarian signalsresult = contrarian_signal("TSLA")if result: print(f"\n{result['symbol']}: {result['signal']}") print(f"Z-Score: {result['z_score']:.2f}") print(f"Reason: {result['reason']}")Step 4: Sentiment Momentum Strategy
Trade based on sentiment momentum shifts:
def sentiment_momentum(symbol, fast_period=5, slow_period=20): """Detect sentiment momentum crossovers""" df = fetch_sentiment(symbol)
if df.empty or len(df) < slow_period: return None
# Calculate moving averages df["fast_ma"] = df["score"].rolling(fast_period).mean() df["slow_ma"] = df["score"].rolling(slow_period).mean()
# Current values current = df.iloc[-1] previous = df.iloc[-2]
# Detect crossovers bullish_cross = (current["fast_ma"] > current["slow_ma"] and previous["fast_ma"] <= previous["slow_ma"]) bearish_cross = (current["fast_ma"] < current["slow_ma"] and previous["fast_ma"] >= previous["slow_ma"])
if bullish_cross: signal = "buy" reason = "Sentiment momentum turning positive" elif bearish_cross: signal = "sell" reason = "Sentiment momentum turning negative" elif current["fast_ma"] > current["slow_ma"]: signal = "hold_long" reason = "Positive sentiment momentum" else: signal = "hold_short" reason = "Negative sentiment momentum"
return { "symbol": symbol, "signal": signal, "fast_ma": current["fast_ma"], "slow_ma": current["slow_ma"], "reason": reason, "is_crossover": bullish_cross or bearish_cross }
# Check momentumresult = sentiment_momentum("NVDA")if result: print(f"\n{result['symbol']}: {result['signal']}") print(f"Fast MA: {result['fast_ma']:.3f}") print(f"Slow MA: {result['slow_ma']:.3f}") if result["is_crossover"]: print("** CROSSOVER DETECTED **")Step 5: Multi-Ticker Sentiment Scanner
Scan multiple tickers for sentiment opportunities:
class SentimentScanner: def __init__(self, api_key): self.fb = FinBrainClient(api_key=api_key)
def scan(self, symbols): """Scan symbols for sentiment signals""" results = []
for symbol in symbols: try: df = self.fb.sentiments.ticker(symbol, as_dataframe=True)
if df.empty: continue
# Get latest sentiment latest_date = df.index[-1] latest_score = df["score"].iloc[-1]
results.append({ "symbol": symbol, "sentiment": latest_score, "date": latest_date.strftime("%Y-%m-%d") }) except Exception: continue
# Sort by sentiment return sorted(results, key=lambda x: x["sentiment"], reverse=True)
def find_extremes(self, symbols, threshold=0.5): """Find symbols with extreme sentiment""" scanned = self.scan(symbols)
bullish = [r for r in scanned if r["sentiment"] > threshold] bearish = [r for r in scanned if r["sentiment"] < -threshold]
return {"bullish": bullish, "bearish": bearish}
# Run scannerscanner = SentimentScanner(api_key="YOUR_API_KEY")
symbols = ["AAPL", "MSFT", "GOOGL", "AMZN", "NVDA", "META", "TSLA", "NFLX"]results = scanner.scan(symbols)
print("\n=== Sentiment Scan Results ===")print("\nMost Bullish:")for r in results[:3]: print(f" {r['symbol']}: {r['sentiment']:.2f} ({r['date']})")
print("\nMost Bearish:")for r in results[-3:]: print(f" {r['symbol']}: {r['sentiment']:.2f} ({r['date']})")
# Find extremesextremes = scanner.find_extremes(symbols)print(f"\nBullish extremes: {[r['symbol'] for r in extremes['bullish']]}")print(f"Bearish extremes: {[r['symbol'] for r in extremes['bearish']]}")Step 6: Combined Strategy
Combine sentiment with AI predictions for higher conviction:
def combined_signal(symbol): """Combine sentiment with AI predictions""" # Get sentiment sent_df = fb.sentiments.ticker(symbol, as_dataframe=True) if sent_df.empty: return None
# Get most recent sentiment score sentiment_score = sent_df["score"].iloc[-1]
# Get prediction pred_result = fb.predictions.ticker(symbol, prediction_type="daily") prediction = pred_result.get("prediction") if not prediction: return None
# Combine signals sentiment_signal = 1 if sentiment_score > 0.3 else -1 if sentiment_score < -0.3 else 0 expected_short = float(prediction.get("expectedShort", 0)) pred_signal = 1 if expected_short > 1 else -1 if expected_short < -1 else 0
# High conviction when both agree combined = sentiment_signal + pred_signal
if combined >= 2: signal = "strong_buy" confidence = "high" elif combined == 1: signal = "buy" confidence = "medium" elif combined <= -2: signal = "strong_sell" confidence = "high" elif combined == -1: signal = "sell" confidence = "medium" else: signal = "neutral" confidence = "low" if sentiment_signal != 0 or pred_signal != 0 else "n/a"
return { "symbol": symbol, "signal": signal, "confidence": confidence, "sentiment_score": sentiment_score, "sentiment_signal": sentiment_signal, "prediction_signal": pred_signal, "expected_short": expected_short, "aligned": sentiment_signal == pred_signal and sentiment_signal != 0 }
# Generate combined signalresult = combined_signal("AAPL")if result: print(f"\n{result['symbol']}: {result['signal']} ({result['confidence']} confidence)") print(f"Sentiment: {result['sentiment_score']:.2f}") print(f"Expected Short: {result['expected_short']:.2f}%") if result["aligned"]: print("** SIGNALS ALIGNED **")Step 7: Complete Sentiment Strategy
Here’s a complete implementation:
class SentimentStrategy: def __init__(self, api_key): self.fb = FinBrainClient(api_key=api_key)
def _fetch_sentiment(self, symbol): """Fetch sentiment and return as DataFrame""" return self.fb.sentiments.ticker(symbol, as_dataframe=True)
def analyze(self, symbol): """Complete sentiment analysis""" try: # Get sentiment data df = self._fetch_sentiment(symbol)
# Get prediction data pred_data = self.fb.predictions.ticker(symbol, prediction_type="daily")
if df.empty: return None
# Get current values from DataFrame current_score = df["score"].iloc[-1] latest_date = df.index[-1]
analysis = { "symbol": symbol, "date": latest_date.strftime("%Y-%m-%d"), "sentiment": { "current": current_score, "ma5": df["score"].tail(5).mean(), "ma20": df["score"].tail(20).mean() if len(df) >= 20 else None }, "signals": [] }
# Level signals if current_score > 0.5: analysis["signals"].append("STRONG_BULLISH_SENTIMENT") elif current_score > 0.2: analysis["signals"].append("BULLISH_SENTIMENT") elif current_score < -0.5: analysis["signals"].append("STRONG_BEARISH_SENTIMENT") elif current_score < -0.2: analysis["signals"].append("BEARISH_SENTIMENT")
# Momentum signals if len(df) >= 5: momentum = current_score - df["score"].tail(5).mean() if momentum > 0.1: analysis["signals"].append("POSITIVE_MOMENTUM") elif momentum < -0.1: analysis["signals"].append("NEGATIVE_MOMENTUM")
# Add prediction if available if pred_data and pred_data.get("prediction"): pred = pred_data.get("prediction", {}) expected_short = float(pred.get("expectedShort", 0)) analysis["prediction"] = { "expectedShort": expected_short, "expectedMid": float(pred.get("expectedMid", 0)) }
# Check alignment pred_bullish = expected_short > 1 sent_bullish = current_score > 0.2
if pred_bullish and sent_bullish: analysis["signals"].append("ALIGNED_BULLISH") elif expected_short < -1 and current_score < -0.2: analysis["signals"].append("ALIGNED_BEARISH")
return analysis
except Exception as e: return {"error": str(e)}
# Use the strategystrategy = SentimentStrategy(api_key="YOUR_API_KEY")
symbols = ["AAPL", "MSFT", "NVDA", "TSLA"]for symbol in symbols: analysis = strategy.analyze(symbol) if analysis and "signals" in analysis: print(f"\n{symbol}:") print(f" Sentiment: {analysis['sentiment']['current']:.2f}") print(f" Date: {analysis['date']}") if analysis["signals"]: print(f" Signals: {', '.join(analysis['signals'])}")Best Practices
- Combine with other signals - Sentiment alone may not be sufficient
- Consider market context - Sentiment may behave differently in bull/bear markets
- Watch for extreme readings - Can indicate reversals
- Use appropriate timeframes - Short-term for trading, longer for investing