trading-tools / utils /phase_scoring.py
Deploy Bot
Deploy Trading Analysis Platform to HuggingFace Spaces
a1bf219
"""
Phase Scoring Utilities
Provides utilities for calculating phase scores (0-10 scale) based on
agent outputs and analysis results.
Score interpretation:
- 9-10: Strong bullish conviction
- 7-8: Moderate bullish bias
- 5-6: Neutral/mixed signals
- 3-4: Moderate bearish bias
- 0-2: Strong bearish conviction
"""
from typing import Any, Dict
def calculate_technical_phase_score(state: Dict[str, Any]) -> float:
"""
Calculate score for technical analysis phase.
Combines indicator, pattern, trend, and decision agent outputs.
Args:
state: Workflow state containing technical analysis results
Returns:
Score from 0-10 (0=bearish, 5=neutral, 10=bullish)
"""
scores = []
# Indicator analysis contribution
indicator_analysis = state.get("indicator_analysis", {})
if indicator_analysis:
rsi_value = indicator_analysis.get("rsi", {}).get("value")
macd_histogram = indicator_analysis.get("macd", {}).get("histogram")
stochastic = indicator_analysis.get("stochastic", {})
indicator_score = 5.0 # Start neutral
# RSI contribution (-1 to +1)
if rsi_value is not None:
if rsi_value < 30:
indicator_score += 1.5 # Oversold = bullish
elif rsi_value > 70:
indicator_score -= 1.5 # Overbought = bearish
elif rsi_value < 40:
indicator_score += 0.5
elif rsi_value > 60:
indicator_score -= 0.5
# MACD contribution
if macd_histogram is not None:
if macd_histogram > 0:
indicator_score += 1.0
else:
indicator_score -= 1.0
# Stochastic contribution
if stochastic:
k_value = stochastic.get("k")
if k_value is not None:
if k_value < 20:
indicator_score += 1.0
elif k_value > 80:
indicator_score -= 1.0
scores.append(max(0, min(10, indicator_score)))
# Pattern analysis contribution
pattern_analysis = state.get("pattern_analysis", {})
if pattern_analysis:
bullish_patterns = pattern_analysis.get("bullish_patterns", [])
bearish_patterns = pattern_analysis.get("bearish_patterns", [])
pattern_score = 5.0
pattern_score += len(bullish_patterns) * 0.5
pattern_score -= len(bearish_patterns) * 0.5
scores.append(max(0, min(10, pattern_score)))
# Trend analysis contribution
trend_analysis = state.get("trend_analysis", {})
if trend_analysis:
trend_direction = trend_analysis.get("trend", {}).get("direction", "neutral")
trend_strength = trend_analysis.get("trend", {}).get("strength", 0.5)
if trend_direction == "bullish":
trend_score = 5.0 + (trend_strength * 5.0)
elif trend_direction == "bearish":
trend_score = 5.0 - (trend_strength * 5.0)
else:
trend_score = 5.0
scores.append(max(0, min(10, trend_score)))
# Decision analysis contribution
decision_analysis = state.get("decision_analysis", {})
if decision_analysis:
decision = decision_analysis.get("decision", "hold").lower()
confidence = decision_analysis.get("confidence", 0.5)
if decision == "buy":
decision_score = 5.0 + (confidence * 5.0)
elif decision == "sell":
decision_score = 5.0 - (confidence * 5.0)
else:
decision_score = 5.0
scores.append(max(0, min(10, decision_score)))
# Return weighted average (or neutral if no scores)
if scores:
return round(sum(scores) / len(scores), 1)
return 5.0
def calculate_fundamental_phase_score(state: Dict[str, Any]) -> float:
"""
Calculate score for fundamental analysis phase.
Combines fundamentals and sentiment agent outputs.
Args:
state: Workflow state containing fundamental analysis results
Returns:
Score from 0-10 (0=bearish, 5=neutral, 10=bullish)
"""
scores = []
# Fundamentals analysis contribution
fundamentals_analysis = state.get("fundamentals_analysis", {})
if fundamentals_analysis:
summary = fundamentals_analysis.get("summary", {})
financial_health = summary.get("financial_health", "moderate")
valuation = summary.get("valuation", "fairly_valued")
growth_potential = summary.get("growth_potential", "moderate")
# Map qualitative assessments to scores
health_map = {"strong": 8.5, "moderate": 5.0, "weak": 2.0}
valuation_map = {"undervalued": 8.0, "fairly_valued": 5.0, "overvalued": 2.0}
growth_map = {"high": 8.0, "moderate": 5.0, "low": 3.0}
fundamentals_score = (
health_map.get(financial_health, 5.0) * 0.4
+ valuation_map.get(valuation, 5.0) * 0.4
+ growth_map.get(growth_potential, 5.0) * 0.2
)
scores.append(fundamentals_score)
# Sentiment analysis contribution
sentiment_analysis = state.get("sentiment_analysis", {})
if sentiment_analysis:
sentiment_score_raw = sentiment_analysis.get("sentiment_score", 0.0)
# Convert from -1/+1 scale to 0-10 scale
sentiment_score = (sentiment_score_raw + 1) * 5.0
scores.append(sentiment_score)
# Return average (or neutral if no scores)
if scores:
return round(sum(scores) / len(scores), 1)
return 5.0
def calculate_sentiment_phase_score(state: Dict[str, Any]) -> float:
"""
Calculate score for sentiment analysis phase.
Based on news agent outputs.
Args:
state: Workflow state containing sentiment analysis results
Returns:
Score from 0-10 (0=bearish, 5=neutral, 10=bullish)
"""
news_analysis = state.get("news_analysis", {})
if news_analysis:
sentiment_score_raw = news_analysis.get("sentiment_score", 0.0)
# Convert from -1/+1 scale to 0-10 scale
sentiment_score = (sentiment_score_raw + 1) * 5.0
return round(sentiment_score, 1)
return 5.0
def calculate_research_synthesis_phase_score(state: Dict[str, Any]) -> float:
"""
Calculate score for research synthesis phase.
Combines technical analyst and researcher team outputs.
Args:
state: Workflow state containing research synthesis results
Returns:
Score from 0-10 (0=bearish, 5=neutral, 10=bullish)
"""
scores = []
# Technical analyst contribution
technical_analyst = state.get("technical_analyst", {})
if technical_analyst:
alignment = technical_analyst.get("alignment", {})
technical_bias = alignment.get("technical_bias", "neutral")
alignment_score = alignment.get("alignment_score", 0.5)
if technical_bias == "positive":
analyst_score = 5.0 + (alignment_score * 5.0)
elif technical_bias == "negative":
analyst_score = 5.0 - (alignment_score * 5.0)
else:
analyst_score = 5.0
scores.append(analyst_score)
# Researcher synthesis contribution
researcher_synthesis = state.get("researcher_synthesis", {})
if researcher_synthesis:
synthesis = researcher_synthesis.get("synthesis", {})
overall_lean = synthesis.get("overall_lean", "neutral")
signal_ratio = synthesis.get("signal_ratio", 0.5)
if overall_lean == "bullish":
synthesis_score = 5.0 + (signal_ratio * 5.0)
elif overall_lean == "bearish":
synthesis_score = 5.0 - ((1 - signal_ratio) * 5.0)
else:
synthesis_score = 5.0
scores.append(synthesis_score)
# Return average (or neutral if no scores)
if scores:
return round(sum(scores) / len(scores), 1)
return 5.0
def calculate_risk_phase_score(state: Dict[str, Any]) -> float:
"""
Calculate score for risk assessment phase.
Inverts risk score (low risk = high score).
Args:
state: Workflow state containing risk assessment results
Returns:
Score from 0-10 (0=high risk, 5=moderate risk, 10=low risk)
"""
risk_assessment = state.get("risk_assessment", {})
if risk_assessment:
risk_score_raw = risk_assessment.get("risk_score", 50.0) # 0-100 scale
# Invert: low risk (0) = high score (10), high risk (100) = low score (0)
risk_score = 10 - (risk_score_raw / 10.0)
return round(max(0, min(10, risk_score)), 1)
return 5.0
def get_phase_weights(investment_style: str) -> Dict[str, float]:
"""
Get phase weights based on investment style.
Args:
investment_style: Investment style identifier
Returns:
Dictionary mapping phase names to weights (sum = 1.0)
"""
# Weight schemes per investment style
if investment_style == "long_term":
return {
"fundamental": 0.40,
"technical": 0.25,
"sentiment": 0.20,
"research_synthesis": 0.0, # Aggregates others
"risk": 0.15,
"decision": 0.0, # Aggregates others
}
elif investment_style == "swing_trading":
return {
"fundamental": 0.20,
"technical": 0.40,
"sentiment": 0.25,
"research_synthesis": 0.0, # Aggregates others
"risk": 0.15,
"decision": 0.0, # Aggregates others
}
else:
# Default balanced weights
return {
"fundamental": 0.30,
"technical": 0.30,
"sentiment": 0.20,
"research_synthesis": 0.0,
"risk": 0.20,
"decision": 0.0,
}
def calculate_weighted_confidence(
phase_scores: Dict[str, float], investment_style: str
) -> float:
"""
Calculate overall confidence percentage from phase scores.
Args:
phase_scores: Dictionary mapping phase names to scores (0-10)
investment_style: Investment style identifier
Returns:
Confidence percentage (0-100)
"""
weights = get_phase_weights(investment_style)
# Filter to only include phases that have scores and weights
weighted_sum = 0.0
total_weight = 0.0
for phase, score in phase_scores.items():
weight = weights.get(phase, 0.0)
if weight > 0 and score is not None:
weighted_sum += score * weight
total_weight += weight
# Normalize if we don't have all phases
if total_weight > 0:
# Convert from 0-10 scale to 0-100 percentage
confidence = (weighted_sum / total_weight) * 10.0
return round(confidence, 1)
return 50.0 # Default neutral confidence