Skip to content

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.

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
Terminal window
pip install finbrain-python pandas numpy

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 statistics
print(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 indicators
sentiment["ma5"] = sentiment["sentiment"].rolling(5).mean()
sentiment["ma20"] = sentiment["sentiment"].rolling(20).mean()
sentiment["momentum"] = sentiment["sentiment"] - sentiment["ma5"]
print(sentiment.tail())

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 signal
signal = 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}")

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 signals
result = 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']}")

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 momentum
result = 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 **")

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 scanner
scanner = 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 extremes
extremes = 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']]}")

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 signal
result = 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 **")

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 strategy
strategy = 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'])}")
  1. Combine with other signals - Sentiment alone may not be sufficient
  2. Consider market context - Sentiment may behave differently in bull/bear markets
  3. Watch for extreme readings - Can indicate reversals
  4. Use appropriate timeframes - Short-term for trading, longer for investing