Sentiment-Based Trading Strategies
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
Section titled “Understanding Sentiment Data”FinBrain’s sentiment scores are derived from AI analysis of financial news:
- Sentiment Score: Values between -1 (bearish) and +1 (bullish) for each date
- sentimentAnalysis: Object with date keys containing daily sentiment scores
Prerequisites
Section titled “Prerequisites”pip install finbrain-python pandas numpyStep 1: Basic Sentiment Analysis
Section titled “Step 1: Basic Sentiment Analysis”Use as_dataframe=True to get sentiment data as a pandas DataFrame directly:
from finbrain import FinBrainClient
fb = FinBrainClient(api_key="YOUR_API_KEY")
# Get sentiment as DataFrame (no manual conversion needed!)sentiment = fb.sentiments.ticker("S&P 500", "AAPL", as_dataframe=True)
print(sentiment.tail())# sentiment# date# 2024-11-04 0.186# 2024-11-01 0.339# 2024-10-31 0.565
# Basic statisticsprint(f"\nSentiment Statistics:")print(f"Mean: {sentiment['sentiment'].mean():.3f}")print(f"Std: {sentiment['sentiment'].std():.3f}")print(f"Current: {sentiment['sentiment'].iloc[-1]:.3f}")
# Add technical indicatorssentiment["ma5"] = sentiment["sentiment"].rolling(5).mean()sentiment["ma20"] = sentiment["sentiment"].rolling(20).mean()sentiment["momentum"] = sentiment["sentiment"] - sentiment["ma5"]
print(sentiment.tail())Step 2: Sentiment Signal Generation
Section titled “Step 2: Sentiment Signal Generation”Generate trading signals based on sentiment levels and momentum using as_dataframe=True:
def generate_sentiment_signal(market, ticker): """Generate trading signal from sentiment data""" fb = FinBrainClient(api_key="YOUR_API_KEY")
# Get sentiment as DataFrame directly df = fb.sentiments.ticker(market, ticker, as_dataframe=True)
if df.empty or len(df) < 5: return {"signal": "neutral", "reason": "insufficient_data"}
# Current values current_score = df["sentiment"].iloc[-1]
# Moving averages ma5 = df["sentiment"].tail(5).mean() ma20 = df["sentiment"].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 { "ticker": ticker, "signal": signal, "score": score, "sentiment": current_score, "momentum": momentum, "reasons": reasons }
# Generate signalsignal = generate_sentiment_signal("S&P 500", "AAPL")print(f"\nSignal: {signal['signal']}")print(f"Score: {signal['score']}")for reason in signal["reasons"]: print(f" - {reason}")Step 3: Contrarian Sentiment Strategy
Section titled “Step 3: Contrarian Sentiment Strategy”Use extreme sentiment as contrarian signals with as_dataframe=True:
def contrarian_signal(market, ticker, lookback=20): """Generate contrarian signals from extreme sentiment""" fb = FinBrainClient(api_key="YOUR_API_KEY")
# Get sentiment as DataFrame directly df = fb.sentiments.ticker(market, ticker, as_dataframe=True)
if df.empty or len(df) < lookback: return None
df = df.tail(lookback)
# Calculate z-score current = df["sentiment"].iloc[-1] mean = df["sentiment"].mean() std = df["sentiment"].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 { "ticker": ticker, "signal": signal, "z_score": z_score, "current_sentiment": current, "mean_sentiment": mean, "reason": reason }
# Check for contrarian signalsresult = contrarian_signal("S&P 500", "TSLA")if result: print(f"\n{result['ticker']}: {result['signal']}") print(f"Z-Score: {result['z_score']:.2f}") print(f"Reason: {result['reason']}")Step 4: Sentiment Momentum Strategy
Section titled “Step 4: Sentiment Momentum Strategy”Trade based on sentiment momentum shifts using as_dataframe=True:
def sentiment_momentum(market, ticker, fast_period=5, slow_period=20): """Detect sentiment momentum crossovers""" fb = FinBrainClient(api_key="YOUR_API_KEY")
# Get sentiment as DataFrame directly df = fb.sentiments.ticker(market, ticker, as_dataframe=True)
if df.empty or len(df) < slow_period: return None
# Calculate moving averages df["fast_ma"] = df["sentiment"].rolling(fast_period).mean() df["slow_ma"] = df["sentiment"].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 { "ticker": ticker, "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("S&P 500", "NVDA")if result: print(f"\n{result['ticker']}: {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
Section titled “Step 5: Multi-Ticker Sentiment Scanner”Scan multiple tickers for sentiment opportunities using as_dataframe=True:
class SentimentScanner: def __init__(self, api_key): self.fb = FinBrainClient(api_key=api_key)
def scan(self, tickers, market="S&P 500"): """Scan tickers for sentiment signals""" results = []
for ticker in tickers: try: # Get sentiment as DataFrame directly df = self.fb.sentiments.ticker(market, ticker, as_dataframe=True)
if df.empty: continue
# Get latest sentiment from DataFrame index latest_date = df.index[-1] latest_score = df["sentiment"].iloc[-1]
results.append({ "ticker": ticker, "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, tickers, market="S&P 500", threshold=0.5): """Find tickers with extreme sentiment""" scanned = self.scan(tickers, market)
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")
tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "NVDA", "META", "TSLA", "NFLX"]results = scanner.scan(tickers)
print("\n=== Sentiment Scan Results ===")print("\nMost Bullish:")for r in results[:3]: print(f" {r['ticker']}: {r['sentiment']:.2f} ({r['date']})")
print("\nMost Bearish:")for r in results[-3:]: print(f" {r['ticker']}: {r['sentiment']:.2f} ({r['date']})")
# Find extremesextremes = scanner.find_extremes(tickers)print(f"\nBullish extremes: {[r['ticker'] for r in extremes['bullish']]}")print(f"Bearish extremes: {[r['ticker'] for r in extremes['bearish']]}")Step 6: Combined Strategy
Section titled “Step 6: Combined Strategy”Combine sentiment with AI predictions for higher conviction using as_dataframe=True:
def combined_signal(market, ticker): """Combine sentiment with AI predictions""" fb = FinBrainClient(api_key="YOUR_API_KEY")
# Get sentiment as DataFrame sent_df = fb.sentiments.ticker(market, ticker, as_dataframe=True) if sent_df.empty: return None
# Get most recent sentiment score sentiment_score = sent_df["sentiment"].iloc[-1]
# Get prediction pred_data = fb.predictions.ticker(ticker, prediction_type="daily") if not pred_data.get("prediction"): return None
prediction = pred_data["prediction"]
# 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 { "ticker": ticker, "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("S&P 500", "AAPL")if result: print(f"\n{result['ticker']}: {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
Section titled “Step 7: Complete Sentiment Strategy”Here’s a complete implementation using as_dataframe=True:
class SentimentStrategy: def __init__(self, api_key): self.fb = FinBrainClient(api_key=api_key)
def analyze(self, market, ticker): """Complete sentiment analysis""" try: # Get data as DataFrames df = self.fb.sentiments.ticker(market, ticker, as_dataframe=True) pred_data = self.fb.predictions.ticker(ticker, prediction_type="daily")
if df.empty: return None
# Get current values from DataFrame current_score = df["sentiment"].iloc[-1] latest_date = df.index[-1]
analysis = { "ticker": ticker, "date": latest_date.strftime("%Y-%m-%d"), "sentiment": { "current": current_score, "ma5": df["sentiment"].tail(5).mean(), "ma20": df["sentiment"].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["sentiment"].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.get("prediction"): pred = pred_data["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")
tickers = ["AAPL", "MSFT", "NVDA", "TSLA"]for ticker in tickers: analysis = strategy.analyze("S&P 500", ticker) if analysis and "signals" in analysis: print(f"\n{ticker}:") print(f" Sentiment: {analysis['sentiment']['current']:.2f}") print(f" Date: {analysis['date']}") if analysis["signals"]: print(f" Signals: {', '.join(analysis['signals'])}")Best Practices
Section titled “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