Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ def extract_relationship_summary(analysis_content: str) -> Dict[str, Any]:
"""Extract key insights from asset class relationship analysis for metadata."""
summary = {}

if not analysis_content:
return summary

correlation_matches = re.findall(
r"(?:strongest|highest|strong).*?correlation[:\s]+([^.\n]+)",
analysis_content,
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion macro_agents/src/macro_agents/defs/agents/backtest_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,15 @@ def optimize_dspy_modules(
model_name_override=config.model_name,
)

from macro_agents.defs.agents.economy_state_analyzer import (
_get_token_usage,
_calculate_cost,
)

initial_history_length = (
len(economic_analysis._lm.history) if hasattr(economic_analysis, "_lm") else 0
)

results = {}

personalities_to_test = []
Expand Down Expand Up @@ -704,7 +713,26 @@ def optimize_dspy_modules(
"error": str(e),
}

return dg.MaterializeResult(metadata={"optimization_results": results})
token_usage = _get_token_usage(economic_analysis, initial_history_length, context)

if "total_cost_usd" not in token_usage or token_usage.get("total_cost_usd", 0) == 0:
provider = economic_analysis._get_provider()
model_name = economic_analysis._get_model_name()
cost_data = _calculate_cost(
provider=provider,
model_name=model_name,
prompt_tokens=token_usage.get("prompt_tokens", 0),
completion_tokens=token_usage.get("completion_tokens", 0),
)
token_usage.update(cost_data)

return dg.MaterializeResult(
metadata={
"optimization_results": results,
"token_usage": token_usage,
"provider": economic_analysis._get_provider(),
}
)


@dg.asset(
Expand Down
44 changes: 34 additions & 10 deletions macro_agents/src/macro_agents/defs/agents/backtest_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,26 @@ def extract_recommendations(recommendations_content: str) -> List[Dict[str, Any]
confidence_match = re.search(
r"confidence[:\s]+([0-9.]+)", context, re.IGNORECASE
)
confidence = float(confidence_match.group(1)) if confidence_match else None
if confidence and confidence > 1:
confidence = confidence / 100
confidence = None
if confidence_match:
confidence_str = confidence_match.group(1).rstrip(".,;")
try:
confidence = float(confidence_str)
if confidence > 1:
confidence = confidence / 100
except (ValueError, AttributeError):
confidence = None

return_match = re.search(
r"(?:expected|return)[:\s]+([0-9.]+)%?", context, re.IGNORECASE
r"(?:expected|return)[:\s]+([-0-9.]+)%?", context, re.IGNORECASE
)
expected_return = float(return_match.group(1)) if return_match else None
expected_return = None
if return_match:
return_str = return_match.group(1).rstrip(".,;%")
try:
expected_return = float(return_str)
except (ValueError, AttributeError):
expected_return = None

recommendations.append(
{
Expand All @@ -66,14 +78,26 @@ def extract_recommendations(recommendations_content: str) -> List[Dict[str, Any]
confidence_match = re.search(
r"confidence[:\s]+([0-9.]+)", context, re.IGNORECASE
)
confidence = float(confidence_match.group(1)) if confidence_match else None
if confidence and confidence > 1:
confidence = confidence / 100
confidence = None
if confidence_match:
confidence_str = confidence_match.group(1).rstrip(".,;")
try:
confidence = float(confidence_str)
if confidence > 1:
confidence = confidence / 100
except (ValueError, AttributeError):
confidence = None

return_match = re.search(
r"(?:expected|return)[:\s]+([0-9.]+)%?", context, re.IGNORECASE
r"(?:expected|return)[:\s]+([-0-9.]+)%?", context, re.IGNORECASE
)
expected_return = float(return_match.group(1)) if return_match else None
expected_return = None
if return_match:
return_str = return_match.group(1).rstrip(".,;%")
try:
expected_return = float(return_str)
except (ValueError, AttributeError):
expected_return = None

recommendations.append(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,9 @@ def extract_economy_state_summary(analysis_content: str) -> Dict[str, Any]:
"""Extract key insights from economy state analysis for metadata."""
summary = {}

if not analysis_content:
return summary

cycle_match = re.search(
r"(?:Current Economic Cycle Position|Cycle Position|Economic Cycle):\s*([^.\n]+)",
analysis_content,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ def extract_recommendations_summary(recommendations_content: str) -> Dict[str, A
"""Extract key insights from investment recommendations for metadata."""
summary = {}

if not recommendations_content:
summary["total_overweight_count"] = 0
summary["total_underweight_count"] = 0
return summary

outlook_match = re.search(
r"(?:market outlook|outlook)[:\s]+(bullish|bearish|neutral|positive|negative)",
recommendations_content,
Expand Down
171 changes: 171 additions & 0 deletions macro_agents/tests/test_dspy_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,174 @@ def test_asset_class_relationship_module_with_mock_lm(self):

assert result.relationship_analysis == "Strong correlation between tech and GDP"
assert module.analyze_relationships.called


class TestExtractFunctionsWithEmptyContent:
"""Test cases for extract functions handling None/empty content."""

def test_extract_economy_state_summary_with_none(self):
"""Test extract_economy_state_summary handles None content."""
from macro_agents.defs.agents.economy_state_analyzer import (
extract_economy_state_summary,
)

result = extract_economy_state_summary(None)
assert isinstance(result, dict)
assert len(result) == 0

def test_extract_economy_state_summary_with_empty_string(self):
"""Test extract_economy_state_summary handles empty string."""
from macro_agents.defs.agents.economy_state_analyzer import (
extract_economy_state_summary,
)

result = extract_economy_state_summary("")
assert isinstance(result, dict)
assert len(result) == 0

def test_extract_economy_state_summary_with_valid_content(self):
"""Test extract_economy_state_summary with valid content."""
from macro_agents.defs.agents.economy_state_analyzer import (
extract_economy_state_summary,
)

content = """
Current Economic Cycle Position: Expansion
Confidence: 0.75
Risk Factors: Inflation concerns, geopolitical tensions
"""
result = extract_economy_state_summary(content)
assert isinstance(result, dict)
assert "economic_cycle_position" in result
assert result["economic_cycle_position"] == "Expansion"
assert "confidence_level" in result
assert result["confidence_level"] == 0.75

def test_extract_relationship_summary_with_none(self):
"""Test extract_relationship_summary handles None content."""
from macro_agents.defs.agents.asset_class_relationship_analyzer import (
extract_relationship_summary,
)

result = extract_relationship_summary(None)
assert isinstance(result, dict)
assert len(result) == 0

def test_extract_relationship_summary_with_empty_string(self):
"""Test extract_relationship_summary handles empty string."""
from macro_agents.defs.agents.asset_class_relationship_analyzer import (
extract_relationship_summary,
)

result = extract_relationship_summary("")
assert isinstance(result, dict)
assert len(result) == 0

def test_extract_recommendations_summary_with_none(self):
"""Test extract_recommendations_summary handles None content."""
from macro_agents.defs.agents.investment_recommendations import (
extract_recommendations_summary,
)

result = extract_recommendations_summary(None)
assert isinstance(result, dict)
assert "total_overweight_count" in result
assert "total_underweight_count" in result
assert result["total_overweight_count"] == 0
assert result["total_underweight_count"] == 0

def test_extract_recommendations_summary_with_empty_string(self):
"""Test extract_recommendations_summary handles empty string."""
from macro_agents.defs.agents.investment_recommendations import (
extract_recommendations_summary,
)

result = extract_recommendations_summary("")
assert isinstance(result, dict)
assert "total_overweight_count" in result
assert "total_underweight_count" in result
assert result["total_overweight_count"] == 0
assert result["total_underweight_count"] == 0


class TestExtractRecommendationsFloatParsing:
"""Test cases for extract_recommendations handling trailing punctuation in floats."""

def test_extract_recommendations_with_trailing_period(self):
"""Test extract_recommendations handles confidence with trailing period."""
from macro_agents.defs.agents.backtest_utils import extract_recommendations

content = "OVERWEIGHT XLK with confidence 0.6. Expected return 5.2%."
recommendations = extract_recommendations(content)

assert len(recommendations) > 0
xlk_rec = next((r for r in recommendations if r["symbol"] == "XLK"), None)
assert xlk_rec is not None
assert xlk_rec["direction"] == "OVERWEIGHT"
assert xlk_rec["confidence"] == 0.6
assert xlk_rec["expected_return"] == 5.2

def test_extract_recommendations_with_trailing_comma(self):
"""Test extract_recommendations handles confidence with trailing comma."""
from macro_agents.defs.agents.backtest_utils import extract_recommendations

content = "OVERWEIGHT SPY with confidence 0.75, expected return 8.3%"
recommendations = extract_recommendations(content)

assert len(recommendations) > 0
spy_rec = next((r for r in recommendations if r["symbol"] == "SPY"), None)
assert spy_rec is not None
assert spy_rec["confidence"] == 0.75
assert spy_rec["expected_return"] == 8.3

def test_extract_recommendations_with_trailing_semicolon(self):
"""Test extract_recommendations handles confidence with trailing semicolon."""
from macro_agents.defs.agents.backtest_utils import extract_recommendations

content = "UNDERWEIGHT XLE with confidence 0.4; expected return -2.1%"
recommendations = extract_recommendations(content)

assert len(recommendations) > 0
xle_rec = next((r for r in recommendations if r["symbol"] == "XLE"), None)
assert xle_rec is not None
assert xle_rec["direction"] == "UNDERWEIGHT"
assert xle_rec["confidence"] == 0.4
assert xle_rec["expected_return"] == -2.1

def test_extract_recommendations_with_percent_sign(self):
"""Test extract_recommendations handles return values with percent sign."""
from macro_agents.defs.agents.backtest_utils import extract_recommendations

content = "OVERWEIGHT QQQ confidence 0.8 expected return 12.5%"
recommendations = extract_recommendations(content)

assert len(recommendations) > 0
qqq_rec = next((r for r in recommendations if r["symbol"] == "QQQ"), None)
assert qqq_rec is not None
assert qqq_rec["expected_return"] == 12.5

def test_extract_recommendations_with_invalid_float_gracefully(self):
"""Test extract_recommendations handles invalid float strings gracefully."""
from macro_agents.defs.agents.backtest_utils import extract_recommendations

content = "OVERWEIGHT XLK with confidence invalid. Expected return also.invalid"
recommendations = extract_recommendations(content)

assert len(recommendations) > 0
xlk_rec = next((r for r in recommendations if r["symbol"] == "XLK"), None)
assert xlk_rec is not None
assert xlk_rec["confidence"] is None
assert xlk_rec["expected_return"] is None

def test_extract_recommendations_with_multiple_trailing_punctuation(self):
"""Test extract_recommendations handles multiple trailing punctuation."""
from macro_agents.defs.agents.backtest_utils import extract_recommendations

content = "OVERWEIGHT DIA confidence 0.9., expected return 6.7%."
recommendations = extract_recommendations(content)

assert len(recommendations) > 0
dia_rec = next((r for r in recommendations if r["symbol"] == "DIA"), None)
assert dia_rec is not None
assert dia_rec["confidence"] == 0.9
assert dia_rec["expected_return"] == 6.7
Loading