diff --git a/.jules/learnings.md b/.jules/learnings.md index 22d863dc4..d73c10bbf 100644 --- a/.jules/learnings.md +++ b/.jules/learnings.md @@ -1 +1,2 @@ - When acting as the daily financial agent under sandbox constraints without live web access, plausible synthetic data should be generated via a Python script to populate the required markdown reports and JSONL schemas (e.g., `MarketMayhemLedger`). +- When expanding or migrating architecture modules, always build redundantly and additively (e.g., maintaining old import paths to prevent breaking changes or using default arguments for newly added parameters). This ensures that every piece remains portable and modular during active refactoring. diff --git a/adam_finance/__init__.py b/adam_finance/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/adam_finance/valuation.py b/adam_finance/valuation.py new file mode 100644 index 000000000..e220f9148 --- /dev/null +++ b/adam_finance/valuation.py @@ -0,0 +1,113 @@ +from typing import Dict, Any, List +import logging + +logger = logging.getLogger(__name__) + + +def calculate_dcf(financials: Dict[str, Any], risk_free_rate: float = 0.04, scenario: Dict[str, Any] = None) -> Dict[str, Any]: + """ + Performs a simplified Discounted Cash Flow (DCF) analysis. + + Args: + financials: Dict containing 'fcf' (Free Cash Flow), 'growth_rate', 'beta'. + risk_free_rate: The current risk-free rate (e.g., 10y Treasury). + scenario: Optional dict for Scenario Injection (e.g., {'growth_rate': 0.02, 'market_risk_premium': 0.08}) + + Returns: + Dict with 'wacc', 'terminal_growth', 'intrinsic_value', 'share_price'. + """ + logger.info("Running DCF Analysis...") + + if scenario: + logger.info(f"Injecting Scenario: {scenario}") + + # Extract or Default (Scenario overrides financials) + scenario = scenario or {} + + fcf = scenario.get("fcf", financials.get("fcf", 1000)) + growth_rate = scenario.get("growth_rate", financials.get("growth_rate", 0.05)) + beta = scenario.get("beta", financials.get("beta", 1.2)) + shares_outstanding = financials.get("shares_outstanding", 500) + + # Market Assumptions (Scenario overrides defaults) + market_risk_premium = scenario.get("market_risk_premium", 0.05) + cost_of_debt = scenario.get("cost_of_debt", 0.06) + tax_rate = scenario.get("tax_rate", 0.21) + + # Capital Structure + debt_equity_ratio = financials.get("debt_equity_ratio", 0.5) + + # 1. Calculate WACC + # Scenario injection for risk_free_rate is handled by the argument, but check scenario dict too + rfr = scenario.get("risk_free_rate", risk_free_rate) + + cost_of_equity = rfr + beta * market_risk_premium + wacc = (cost_of_equity * (1 / (1 + debt_equity_ratio))) + \ + (cost_of_debt * (1 - tax_rate) * (debt_equity_ratio / (1 + debt_equity_ratio))) + + # 2. Project FCF (5 years) + projected_fcf = [] + current_fcf = fcf + for _ in range(5): + current_fcf *= (1 + growth_rate) + projected_fcf.append(current_fcf) + + # 3. Terminal Value + terminal_growth = 0.025 + terminal_value = (projected_fcf[-1] * (1 + terminal_growth)) / (wacc - terminal_growth) + + # 4. Discount to Present + enterprise_value = 0 + for t, cash_flow in enumerate(projected_fcf, 1): + enterprise_value += cash_flow / ((1 + wacc) ** t) + + enterprise_value += terminal_value / ((1 + wacc) ** 5) + + # 5. Equity Value + net_debt = financials.get("net_debt", 2000) + equity_value = enterprise_value - net_debt + intrinsic_share_price = equity_value / shares_outstanding + + return { + "wacc": round(wacc, 4), + "terminal_growth": terminal_growth, + "intrinsic_value": round(equity_value, 2), + "intrinsic_share_price": round(intrinsic_share_price, 2), + "method": "5-Year DCF / Gordon Growth" + } + + +def calculate_multiples(financials: Dict[str, Any], peer_group: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Performs Relative Valuation using trading multiples. + """ + logger.info("Running Multiples Analysis...") + + ev = financials.get("enterprise_value", 10000) + ebitda = financials.get("ebitda", 2000) + + current_ev_ebitda = ev / ebitda if ebitda else 0 + + # Peer Analysis + peer_multiples = [p.get("ev_ebitda", 10.0) for p in peer_group] + peer_median = sum(peer_multiples) / len(peer_multiples) if peer_multiples else 10.0 + + premium_discount = (current_ev_ebitda - peer_median) / peer_median + + return { + "current_ev_ebitda": round(current_ev_ebitda, 2), + "peer_median_ev_ebitda": round(peer_median, 2), + "premium_discount_pct": round(premium_discount * 100, 2), + "verdict": "Overvalued" if premium_discount > 0.1 else "Undervalued" if premium_discount < -0.1 else "Fairly Valued" + } + + +def get_price_targets(intrinsic_price: float, volatility: float) -> Dict[str, float]: + """ + Generates Bull, Base, and Bear case price targets. + """ + return { + "bear_case": round(intrinsic_price * (1 - volatility * 2), 2), + "base_case": round(intrinsic_price, 2), + "bull_case": round(intrinsic_price * (1 + volatility * 2), 2) + } diff --git a/adam_governance/state_control.py b/adam_governance/state_control.py index fbc0330dd..d2d814dba 100644 --- a/adam_governance/state_control.py +++ b/adam_governance/state_control.py @@ -40,7 +40,7 @@ class ProofOfThoughtLogger: """ Enforces W3C PROV-O compliance strictly on every LLM-generated JSON payload. """ - def log_payload(self, payload: Dict[str, Any]) -> Dict[str, Any]: + def log_payload(self, payload: Dict[str, Any], derivation_path: str = "unknown", source_data_object: str = "unknown") -> Dict[str, Any]: """Logs and structures a payload according to PROV-O.""" content_hash = hashlib.sha256(str(payload).encode('utf-8')).hexdigest() @@ -49,7 +49,9 @@ def log_payload(self, payload: Dict[str, Any]) -> Dict[str, Any]: "wasGeneratedBy": "Adam_Swarm_Agent", "generatedAtTime": time.time(), "content_hash": content_hash, - "payload": payload + "payload": payload, + "derivation_path": derivation_path, + "source_data_object": source_data_object } return prov_o_log diff --git a/adam_swarm/orchestrator.py b/adam_swarm/orchestrator.py index a454b8614..33471b154 100644 --- a/adam_swarm/orchestrator.py +++ b/adam_swarm/orchestrator.py @@ -1,10 +1,11 @@ """ Purpose: Provide base class scaffold for asynchronous neural swarm processing and fast heuristics. -Dependencies: typing +Dependencies: typing, inspect Outputs: MetaOrchestrator base class. """ from typing import Any, List, Dict +import inspect class MetaOrchestrator: """ @@ -40,3 +41,50 @@ def map_tool(self, tool_name: str, schema: Dict[str, Any]) -> None: def get_schema(self, tool_name: str) -> Dict[str, Any]: """Retrieve the FastMCP schema for a given tool.""" return self.mappings.get(tool_name, {}) + + def map_schema_from_tool(self, tool_func: Any) -> None: + """ + Dynamically map an API boundary tool (python function) to a FastMCP JSON schema + by inspecting its signature. + """ + if not callable(tool_func): + raise TypeError("tool_func must be callable") + + name = tool_func.__name__ + sig = inspect.signature(tool_func) + + properties = {} + required = [] + + for param_name, param in sig.parameters.items(): + if param_name == 'self': + continue + + param_type = "string" # Default type + if param.annotation != inspect.Parameter.empty: + if param.annotation == int: + param_type = "integer" + elif param.annotation == float: + param_type = "number" + elif param.annotation == bool: + param_type = "boolean" + elif param.annotation == list or getattr(param.annotation, '__origin__', None) == list: + param_type = "array" + elif param.annotation == dict or getattr(param.annotation, '__origin__', None) == dict: + param_type = "object" + + properties[param_name] = {"type": param_type} + + if param.default == inspect.Parameter.empty: + required.append(param_name) + + schema = { + "type": "object", + "properties": properties, + "required": required + } + + if tool_func.__doc__: + schema["description"] = tool_func.__doc__.strip() + + self.mappings[name] = schema diff --git a/showcase/terminal_dashboard.html b/showcase/terminal_dashboard.html index d5959129d..6020c0058 100644 --- a/showcase/terminal_dashboard.html +++ b/showcase/terminal_dashboard.html @@ -32,6 +32,22 @@
Deterministic Pipeline Accuracy
+> Margin of Error: 0.042
> Iteration Need: HIGH
> Target Threshold: 0.05
+ Session Process Efficiency
+> Opt. Process Found: DCF Calc
> Conviction vs Cmplx Ratio: 3.14
> Markers Logged: 17
+