303 lines
12 KiB
Python
303 lines
12 KiB
Python
"""Multi-Agent Trading Example for OpenClaw.
|
|
|
|
This example demonstrates how multiple agents collaborate to make trading
|
|
decisions using the decision fusion system. Different agents with different
|
|
roles provide opinions that are combined into a final trading signal.
|
|
|
|
To run:
|
|
python examples/multi_agent.py
|
|
"""
|
|
|
|
import random
|
|
from typing import List
|
|
|
|
from openclaw.fusion.decision_fusion import (
|
|
AgentOpinion,
|
|
AgentRole,
|
|
DecisionFusion,
|
|
FusionConfig,
|
|
FusionResult,
|
|
SignalType,
|
|
)
|
|
|
|
|
|
class SimpleAgent:
|
|
"""A simplified agent for demonstration purposes.
|
|
|
|
In production, each agent would have its own analysis logic
|
|
and economic tracking.
|
|
"""
|
|
|
|
def __init__(self, agent_id: str, role: AgentRole, bias: float = 0.0):
|
|
self.agent_id = agent_id
|
|
self.role = role
|
|
self.bias = bias # Positive = bullish, negative = bearish
|
|
self.skill_level = random.uniform(0.5, 0.9)
|
|
|
|
def analyze(self, symbol: str) -> AgentOpinion:
|
|
"""Generate an opinion about a symbol."""
|
|
# Simulate analysis with some randomness
|
|
base_signal = random.randint(-2, 2)
|
|
|
|
# Apply bias
|
|
if self.bias > 0.3:
|
|
base_signal = max(base_signal, 0) # Bullish agents prefer buy/hold
|
|
elif self.bias < -0.3:
|
|
base_signal = min(base_signal, 0) # Bearish agents prefer sell/hold
|
|
|
|
# Map to signal type
|
|
signal_map = {
|
|
-2: SignalType.STRONG_SELL,
|
|
-1: SignalType.SELL,
|
|
0: SignalType.HOLD,
|
|
1: SignalType.BUY,
|
|
2: SignalType.STRONG_BUY,
|
|
}
|
|
signal = signal_map.get(base_signal, SignalType.HOLD)
|
|
|
|
# Generate reasoning based on role
|
|
reasoning = self._generate_reasoning(symbol, signal)
|
|
|
|
# Confidence based on skill level and signal clarity
|
|
confidence = self.skill_level * random.uniform(0.7, 1.0)
|
|
|
|
# Key factors
|
|
factors = self._generate_factors()
|
|
|
|
return AgentOpinion(
|
|
agent_id=self.agent_id,
|
|
role=self.role,
|
|
signal=signal,
|
|
confidence=confidence,
|
|
reasoning=reasoning,
|
|
factors=factors,
|
|
)
|
|
|
|
def _generate_reasoning(self, symbol: str, signal: SignalType) -> str:
|
|
"""Generate reasoning based on role and signal."""
|
|
templates = {
|
|
AgentRole.MARKET_ANALYST: {
|
|
SignalType.STRONG_BUY: f"{symbol} shows strong bullish momentum with volume surge",
|
|
SignalType.BUY: f"{symbol} technical indicators suggest upward movement",
|
|
SignalType.HOLD: f"{symbol} consolidating, wait for clearer direction",
|
|
SignalType.SELL: f"{symbol} showing weakness in support levels",
|
|
SignalType.STRONG_SELL: f"{symbol} breakdown below key support with high volume",
|
|
},
|
|
AgentRole.SENTIMENT_ANALYST: {
|
|
SignalType.STRONG_BUY: f"Extremely positive sentiment for {symbol} across social media",
|
|
SignalType.BUY: f"Positive news sentiment detected for {symbol}",
|
|
SignalType.HOLD: f"Mixed sentiment for {symbol}, no clear direction",
|
|
SignalType.SELL: f"Negative sentiment emerging for {symbol}",
|
|
SignalType.STRONG_SELL: f"Strong negative sentiment and fear for {symbol}",
|
|
},
|
|
AgentRole.FUNDAMENTAL_ANALYST: {
|
|
SignalType.STRONG_BUY: f"{symbol} fundamentals exceptionally strong, undervalued",
|
|
SignalType.BUY: f"{symbol} earnings beat expectations, solid growth",
|
|
SignalType.HOLD: f"{symbol} fundamentals stable, fairly valued",
|
|
SignalType.SELL: f"{symbol} showing signs of overvaluation",
|
|
SignalType.STRONG_SELL: f"{symbol} fundamentals deteriorating rapidly",
|
|
},
|
|
AgentRole.BULL_RESEARCHER: {
|
|
SignalType.STRONG_BUY: f"Catalyst identified: {symbol} poised for significant upside",
|
|
SignalType.BUY: f"Bullish thesis intact for {symbol}",
|
|
SignalType.HOLD: f"Waiting for better entry on {symbol}",
|
|
SignalType.SELL: f"Temporary setback, but {symbol} long-term bullish",
|
|
SignalType.STRONG_SELL: f"{symbol} thesis broken, exit position",
|
|
},
|
|
AgentRole.BEAR_RESEARCHER: {
|
|
SignalType.STRONG_BUY: f"{symbol} short squeeze potential, cover shorts",
|
|
SignalType.BUY: f"{symbol} oversold bounce likely",
|
|
SignalType.HOLD: f"{symbol} no clear bearish catalyst yet",
|
|
SignalType.SELL: f"Bearish pattern forming on {symbol}",
|
|
SignalType.STRONG_SELL: f"{symbol} significant downside risk identified",
|
|
},
|
|
AgentRole.RISK_MANAGER: {
|
|
SignalType.STRONG_BUY: f"Low risk environment, can increase {symbol} position",
|
|
SignalType.BUY: f"Acceptable risk levels for {symbol} trade",
|
|
SignalType.HOLD: f"Risk elevated, reduce {symbol} exposure",
|
|
SignalType.SELL: f"High risk detected for {symbol}, scale down",
|
|
SignalType.STRONG_SELL: f"Critical risk level for {symbol}, exit immediately",
|
|
},
|
|
}
|
|
|
|
role_templates = templates.get(self.role, templates[AgentRole.MARKET_ANALYST])
|
|
return role_templates.get(signal, "No clear signal")
|
|
|
|
def _generate_factors(self) -> List[str]:
|
|
"""Generate relevant factors based on role."""
|
|
factors_by_role = {
|
|
AgentRole.MARKET_ANALYST: [
|
|
"Moving Average Crossover", "Volume Profile", "Support/Resistance",
|
|
"MACD Divergence", "RSI Levels"
|
|
],
|
|
AgentRole.SENTIMENT_ANALYST: [
|
|
"Social Media Buzz", "News Sentiment", "Analyst Ratings",
|
|
"Insider Activity", "Options Flow"
|
|
],
|
|
AgentRole.FUNDAMENTAL_ANALYST: [
|
|
"P/E Ratio", "Revenue Growth", "Profit Margins",
|
|
"Debt/Equity", "Free Cash Flow"
|
|
],
|
|
AgentRole.BULL_RESEARCHER: [
|
|
"Growth Catalysts", "Market Expansion", "New Products",
|
|
"Competitive Advantage", "Industry Trends"
|
|
],
|
|
AgentRole.BEAR_RESEARCHER: [
|
|
"Valuation Concerns", "Competition Threats", "Regulatory Risks",
|
|
"Margin Pressure", "Slowing Growth"
|
|
],
|
|
AgentRole.RISK_MANAGER: [
|
|
"Volatility Spike", "Correlation Risk", "Liquidity Check",
|
|
"Position Size Limits", "Stop Loss Levels"
|
|
],
|
|
}
|
|
|
|
factors = factors_by_role.get(self.role, ["General Analysis"])
|
|
# Select 2-3 random factors
|
|
return random.sample(factors, min(3, len(factors)))
|
|
|
|
|
|
def create_agent_team() -> List[SimpleAgent]:
|
|
"""Create a team of agents with different roles and biases."""
|
|
return [
|
|
# Market Analysts - technical focus
|
|
SimpleAgent("market_01", AgentRole.MARKET_ANALYST, bias=0.1),
|
|
|
|
# Sentiment Analysts - news/social focus
|
|
SimpleAgent("sentiment_01", AgentRole.SENTIMENT_ANALYST, bias=0.0),
|
|
|
|
# Fundamental Analysts - company fundamentals
|
|
SimpleAgent("fundamental_01", AgentRole.FUNDAMENTAL_ANALYST, bias=0.2),
|
|
|
|
# Bull Researcher - optimistic view
|
|
SimpleAgent("bull_01", AgentRole.BULL_RESEARCHER, bias=0.5),
|
|
|
|
# Bear Researcher - cautious view
|
|
SimpleAgent("bear_01", AgentRole.BEAR_RESEARCHER, bias=-0.3),
|
|
|
|
# Risk Manager - risk focus
|
|
SimpleAgent("risk_01", AgentRole.RISK_MANAGER, bias=-0.1),
|
|
]
|
|
|
|
|
|
def print_opinion(opinion: AgentOpinion, index: int) -> None:
|
|
"""Print an agent's opinion in a formatted way."""
|
|
print(f"\n [{index}] {opinion.agent_id} ({opinion.role.value})")
|
|
print(f" Signal: {opinion.signal.name}")
|
|
print(f" Confidence: {opinion.confidence:.1%}")
|
|
print(f" Reasoning: {opinion.reasoning}")
|
|
print(f" Factors: {', '.join(opinion.factors)}")
|
|
|
|
|
|
def print_fusion_result(result: FusionResult) -> None:
|
|
"""Print fusion result in a formatted way."""
|
|
print("\n" + "-" * 50)
|
|
print("FUSION RESULT")
|
|
print("-" * 50)
|
|
print(f" Symbol: {result.symbol}")
|
|
print(f" Final Signal: {result.final_signal.name}")
|
|
print(f" Recommendation: {result.get_recommendation_text()}")
|
|
print(f" Confidence: {result.final_confidence:.1%}")
|
|
print(f" Consensus: {result.consensus_level:.1%}")
|
|
|
|
if result.supporting_opinions:
|
|
print(f"\n Supporting ({len(result.supporting_opinions)} opinions):")
|
|
for op in result.supporting_opinions[:3]:
|
|
print(f" - {op.agent_id}: {op.signal.name}")
|
|
|
|
if result.opposing_opinions:
|
|
print(f"\n Opposing ({len(result.opposing_opinions)} opinions):")
|
|
for op in result.opposing_opinions[:3]:
|
|
print(f" - {op.agent_id}: {op.signal.name}")
|
|
|
|
if result.risk_assessment:
|
|
print(f"\n Risk Assessment: {result.risk_assessment}")
|
|
|
|
if result.execution_plan:
|
|
plan = result.execution_plan
|
|
print(f"\n Execution Plan:")
|
|
print(f" Action: {plan.get('action', 'N/A')}")
|
|
print(f" Urgency: {plan.get('urgency', 'N/A')}")
|
|
print(f" Position Size: {plan.get('position_size', 'N/A')}")
|
|
if plan.get('notes'):
|
|
print(f" Notes: {'; '.join(plan['notes'])}")
|
|
|
|
|
|
def main():
|
|
"""Run the multi-agent example."""
|
|
print("=" * 60)
|
|
print("OpenClaw Trading - Multi-Agent Decision Fusion")
|
|
print("=" * 60)
|
|
|
|
# Create agent team
|
|
print("\n1. Creating agent team...")
|
|
agents = create_agent_team()
|
|
for agent in agents:
|
|
bias_str = "bullish" if agent.bias > 0.2 else "bearish" if agent.bias < -0.2 else "neutral"
|
|
print(f" - {agent.agent_id}: {agent.role.value} ({bias_str}, skill={agent.skill_level:.0%})")
|
|
|
|
# Create decision fusion engine
|
|
print("\n2. Initializing decision fusion engine...")
|
|
config = FusionConfig(
|
|
confidence_threshold=0.3,
|
|
consensus_threshold=0.6,
|
|
enable_risk_override=True,
|
|
)
|
|
fusion = DecisionFusion(config=config)
|
|
print(f" Confidence threshold: {config.confidence_threshold}")
|
|
print(f" Risk override enabled: {config.enable_risk_override}")
|
|
|
|
# Analyze multiple symbols
|
|
symbols = ["AAPL", "TSLA", "NVDA"]
|
|
|
|
for symbol in symbols:
|
|
print(f"\n{'=' * 60}")
|
|
print(f"Analyzing {symbol}")
|
|
print("=" * 60)
|
|
|
|
# Collect opinions
|
|
print("\n3. Collecting agent opinions...")
|
|
fusion.start_fusion(symbol)
|
|
|
|
for i, agent in enumerate(agents, 1):
|
|
opinion = agent.analyze(symbol)
|
|
fusion.add_opinion(opinion)
|
|
print_opinion(opinion, i)
|
|
|
|
# Execute fusion
|
|
print("\n4. Executing decision fusion...")
|
|
result = fusion.fuse(portfolio_value=100000.0)
|
|
|
|
# Print results
|
|
print_fusion_result(result)
|
|
|
|
# Summary
|
|
print("\n" + "=" * 60)
|
|
print("SUMMARY")
|
|
print("=" * 60)
|
|
|
|
history = fusion.get_fusion_history()
|
|
print(f"\nTotal decisions made: {len(history)}")
|
|
|
|
signal_counts = {}
|
|
for r in history:
|
|
signal_counts[r.final_signal.name] = signal_counts.get(r.final_signal.name, 0) + 1
|
|
|
|
print("\nSignal distribution:")
|
|
for signal, count in sorted(signal_counts.items()):
|
|
bar = "█" * count
|
|
print(f" {signal:15} {bar} ({count})")
|
|
|
|
avg_confidence = sum(r.final_confidence for r in history) / len(history)
|
|
avg_consensus = sum(r.consensus_level for r in history) / len(history)
|
|
print(f"\nAverage confidence: {avg_confidence:.1%}")
|
|
print(f"Average consensus: {avg_consensus:.1%}")
|
|
|
|
print("\n" + "=" * 60)
|
|
print("Multi-agent example complete!")
|
|
print("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|