stock/tests/integration/test_decision_fusion_integration.py
2026-02-27 03:17:12 +08:00

396 lines
14 KiB
Python

"""Integration tests for DecisionFusion with PortfolioRiskManager.
Tests the integration between decision fusion and portfolio risk management
to ensure portfolio-level risk checks can modify or block trading decisions.
"""
import pytest
from openclaw.fusion.decision_fusion import (
AgentOpinion,
AgentRole,
DecisionFusion,
FusionConfig,
SignalType,
)
from openclaw.portfolio.risk import (
PortfolioRiskManager,
create_portfolio_risk_manager,
)
class TestDecisionFusionWithPortfolioRisk:
"""Integration tests for DecisionFusion with PortfolioRiskManager."""
def test_fusion_without_risk_manager(self):
"""Test fusion works without portfolio risk manager."""
fusion = DecisionFusion()
fusion.start_fusion("AAPL")
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.8,
reasoning="Technical breakout",
)
)
result = fusion.fuse()
assert result.symbol == "AAPL"
assert result.final_signal == SignalType.BUY
assert "risk_validated" not in result.execution_plan or not result.execution_plan["risk_validated"]
def test_fusion_with_risk_manager(self):
"""Test fusion with portfolio risk manager integration."""
risk_manager = PortfolioRiskManager(
portfolio_id="test_portfolio",
max_concentration_pct=0.20,
max_drawdown_pct=0.10,
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.8,
reasoning="Technical breakout",
)
)
positions = {}
result = fusion.fuse(portfolio_value=100000.0, positions=positions)
assert result.symbol == "AAPL"
assert result.execution_plan["risk_validated"] is True
assert "risk_score" in result.execution_plan
def test_risk_manager_blocks_excessive_position(self):
"""Test that risk manager blocks trade exceeding concentration limit."""
risk_manager = PortfolioRiskManager(
portfolio_id="test_portfolio",
max_concentration_pct=0.20, # 20% max concentration
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.STRONG_BUY,
confidence=0.9,
reasoning="Strong buy signal",
)
)
# Already have a large position that would exceed limit
positions = {"AAPL": 25000.0} # 25% of 100k portfolio
result = fusion.fuse(portfolio_value=100000.0, positions=positions)
# Risk manager should block or reduce
assert result.execution_plan["action"] == "HOLD"
assert result.execution_plan["position_size"] == "blocked"
assert any("BLOCKED" in note for note in result.execution_plan["notes"])
def test_risk_manager_allows_valid_trade(self):
"""Test that risk manager allows trade within limits."""
risk_manager = PortfolioRiskManager(
portfolio_id="test_portfolio",
max_concentration_pct=0.20,
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.8,
reasoning="Good entry",
)
)
# Small position that won't exceed limit
positions = {"AAPL": 5000.0} # 5% of 100k
result = fusion.fuse(portfolio_value=100000.0, positions=positions)
assert result.execution_plan["action"] == "BUY"
assert result.execution_plan["position_size"] != "blocked"
def test_risk_manager_reduces_position_size(self):
"""Test that risk manager reduces position size for high risk."""
risk_manager = create_portfolio_risk_manager(
portfolio_id="test_portfolio",
risk_profile="conservative",
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.7,
reasoning="Entry signal",
)
)
# Position that would be at warning level
positions = {"AAPL": 14000.0} # 14% of 100k, close to 15% limit
result = fusion.fuse(portfolio_value=100000.0, positions=positions)
# Should reduce position size due to risk
assert result.execution_plan["position_size"] == "reduced"
def test_risk_alerts_in_execution_plan(self):
"""Test that risk alerts are included in execution plan."""
risk_manager = PortfolioRiskManager(
portfolio_id="test_portfolio",
max_concentration_pct=0.10, # Strict 10% limit
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.8,
reasoning="Buy signal",
)
)
# Position that exceeds strict limit
positions = {"AAPL": 15000.0} # 15% exceeds 10% limit
result = fusion.fuse(portfolio_value=100000.0, positions=positions)
# Should have risk alerts
assert len(result.execution_plan["risk_alerts"]) > 0
alert = result.execution_plan["risk_alerts"][0]
assert alert["type"] == "concentration_limit"
def test_position_size_limit_in_plan(self):
"""Test that position size limit is calculated and included."""
risk_manager = PortfolioRiskManager(
portfolio_id="test_portfolio",
max_concentration_pct=0.20,
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.8,
reasoning="Buy signal",
)
)
positions = {"AAPL": 10000.0} # 10% current
result = fusion.fuse(portfolio_value=100000.0, positions=positions)
# Should have position size limit (20% max - 10% current = 10% available)
assert "position_size_limit" in result.execution_plan
assert result.execution_plan["position_size_limit"] > 0
def test_hold_signal_no_risk_check(self):
"""Test that HOLD signals skip risk checks."""
risk_manager = PortfolioRiskManager(
portfolio_id="test_portfolio",
max_concentration_pct=0.20,
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
# Add conflicting opinions that result in HOLD
fusion.add_opinion(
AgentOpinion(
agent_id="bull",
role=AgentRole.BULL_RESEARCHER,
signal=SignalType.BUY,
confidence=0.5,
reasoning="Bullish",
)
)
fusion.add_opinion(
AgentOpinion(
agent_id="bear",
role=AgentRole.BEAR_RESEARCHER,
signal=SignalType.SELL,
confidence=0.5,
reasoning="Bearish",
)
)
result = fusion.fuse(portfolio_value=100000.0, positions={})
# Should be HOLD
assert result.final_signal == SignalType.HOLD
# Risk validation still happens but position size is 0
if result.execution_plan.get("risk_validated"):
assert result.execution_plan.get("position_size_limit", 0) == 0.0
def test_multiple_symbols_with_different_risk_profiles(self):
"""Test fusion with different risk profiles for different symbols."""
conservative_manager = create_portfolio_risk_manager(
portfolio_id="conservative",
risk_profile="conservative",
)
aggressive_manager = create_portfolio_risk_manager(
portfolio_id="aggressive",
risk_profile="aggressive",
)
# Test conservative - should be more restrictive
fusion1 = DecisionFusion(portfolio_risk_manager=conservative_manager)
fusion1.start_fusion("AAPL")
fusion1.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.8,
reasoning="Entry",
)
)
result1 = fusion1.fuse(
portfolio_value=100000.0,
positions={"AAPL": 14000.0} # 14% of 100k
)
# Test aggressive - should allow more
fusion2 = DecisionFusion(portfolio_risk_manager=aggressive_manager)
fusion2.start_fusion("AAPL")
fusion2.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.8,
reasoning="Entry",
)
)
result2 = fusion2.fuse(
portfolio_value=100000.0,
positions={"AAPL": 14000.0} # 14% of 100k
)
# Conservative should be more restrictive
assert result1.execution_plan["position_size"] == "reduced"
# Aggressive should allow the trade
assert result2.execution_plan["position_size"] != "blocked"
def test_risk_manager_factory_function(self):
"""Test using the factory function to create risk manager."""
risk_manager = create_portfolio_risk_manager(
portfolio_id="test",
risk_profile="moderate",
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.BUY,
confidence=0.8,
reasoning="Entry",
)
)
result = fusion.fuse(portfolio_value=100000.0, positions={})
assert result.execution_plan["risk_validated"] is True
class TestRiskOverrideAndPortfolioRisk:
"""Tests combining risk manager override with portfolio risk."""
def test_risk_manager_override_takes_precedence(self):
"""Test that risk manager opinion override takes precedence over portfolio risk."""
risk_manager = PortfolioRiskManager(
portfolio_id="test_portfolio",
max_concentration_pct=0.20,
)
config = FusionConfig(enable_risk_override=True)
fusion = DecisionFusion(config=config, portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
# Bullish opinions
fusion.add_opinion(
AgentOpinion(
agent_id="market-1",
role=AgentRole.MARKET_ANALYST,
signal=SignalType.STRONG_BUY,
confidence=0.9,
reasoning="Perfect setup",
)
)
# Risk manager strongly warns with high confidence
fusion.add_opinion(
AgentOpinion(
agent_id="risk-1",
role=AgentRole.RISK_MANAGER,
signal=SignalType.STRONG_SELL,
confidence=0.9,
reasoning="Market crash imminent",
)
)
result = fusion.fuse(portfolio_value=100000.0, positions={})
# Risk manager override should result in SELL
assert result.final_signal == SignalType.SELL
def test_portfolio_risk_blocks_after_agreement(self):
"""Test portfolio risk can block even after agents agree."""
risk_manager = PortfolioRiskManager(
portfolio_id="test_portfolio",
max_concentration_pct=0.05, # Very strict 5% limit
)
fusion = DecisionFusion(portfolio_risk_manager=risk_manager)
fusion.start_fusion("AAPL")
# All agents agree to buy
for role in [AgentRole.MARKET_ANALYST, AgentRole.FUNDAMENTAL_ANALYST]:
fusion.add_opinion(
AgentOpinion(
agent_id=f"agent-{role.value}",
role=role,
signal=SignalType.STRONG_BUY,
confidence=0.9,
reasoning="Strong agreement",
)
)
# But portfolio already has large position
positions = {"AAPL": 10000.0} # 10% of 100k, exceeds 5% limit
result = fusion.fuse(portfolio_value=100000.0, positions=positions)
# Agents agreed on BUY, but portfolio risk blocked it
assert result.execution_plan["action"] == "HOLD"
assert result.execution_plan["position_size"] == "blocked"