Domain: Financial & Accounting

You are given two files in the current task folder:

- `OTS_DS_Feb_AN2_007 - All.csv`
- `nifty50.csv`

Use only these local files to evaluate an equal-weighted portfolio of four Indian stocks:

- `SBIN` -> `SBI`
- `MARUTI` -> `Maruti Suzuki`
- `APOLLOHOSP` -> `Apollo Hospitals`
- `EICHERMOT` -> `Eicher Motors`

Treat the four stocks as a 25% / 25% / 25% / 25% portfolio.

Requirements:

1. Parse `OTS_DS_Feb_AN2_007 - All.csv`, which contains four stock sections in one file.
2. For each stock, use the `CLOSE` column and the trading `DATE` values.
3. Align the four stocks on their common trading dates.
4. Compute daily simple returns `r_t = close_t / close_{t-1} - 1`.
5. Compute each stock's annualized return from compounded daily returns using a 252-trading-day convention.
6. Compute each stock's annualized volatility from the population standard deviation of daily returns (`ddof=0`) scaled by `sqrt(252)`.
7. Compute the equal-weighted portfolio daily return as the arithmetic mean of the four stock daily returns on each aligned date.
8. Compute the portfolio's annualized return, annualized volatility, coefficient of variation, and excess return per unit of total risk using a risk-free rate of `0.055`.
9. From `nifty50.csv`, use the `NIFTY 50` series on the same aligned trading dates and compute the benchmark annualized return, annualized volatility, and excess return per unit of total risk.
10. Compute relative performance metrics:
   - `return_differential = portfolio_return - benchmark_return`
   - `volatility_differential = portfolio_volatility - benchmark_volatility`
   - `risk_adjusted_return_differential = portfolio_excess_return_per_unit_risk - benchmark_excess_return_per_unit_risk`
11. Provide a recommendation stating whether the portfolio delivers superior risk-adjusted returns and whether the incremental volatility is justified.

Round all reported numeric values to exactly 4 decimal places using round-half-to-even.

Return exactly one JSON object with this structure:

```json
{
  "individual_stocks": {
    "SBI": {
      "annualized_return": <number>,
      "annualized_volatility": <number>
    },
    "Maruti Suzuki": {
      "annualized_return": <number>,
      "annualized_volatility": <number>
    },
    "Apollo Hospitals": {
      "annualized_return": <number>,
      "annualized_volatility": <number>
    },
    "Eicher Motors": {
      "annualized_return": <number>,
      "annualized_volatility": <number>
    }
  },
  "portfolio": {
    "price_observations": <integer>,
    "return_observations": <integer>,
    "annualized_return": <number>,
    "annualized_volatility": <number>,
    "coefficient_of_variation": <number>,
    "excess_return_per_unit_risk": <number>
  },
  "benchmark": {
    "annualized_return": <number>,
    "annualized_volatility": <number>,
    "excess_return_per_unit_risk": <number>
  },
  "relative_performance": {
    "return_differential": <number>,
    "volatility_differential": <number>,
    "risk_adjusted_return_differential": <number>
  },
  "recommendation": "<text>"
}
```

Do not include any text before or after the JSON.

Grading rubric:
criterion: The final answer is exactly one JSON object with the keys individual_stocks, portfolio, benchmark, relative_performance, and recommendation.
weight: 5
criterion: The SBI entry is exactly annualized_return=0.6338 and annualized_volatility=0.1988.
weight: 5
criterion: The Maruti Suzuki entry is exactly annualized_return=0.2159 and annualized_volatility=0.2022.
weight: 5
criterion: The Apollo Hospitals entry is exactly annualized_return=0.1925 and annualized_volatility=0.1971.
weight: 5
criterion: The Eicher Motors entry is exactly annualized_return=0.5770 and annualized_volatility=0.2144.
weight: 5
criterion: The portfolio entry is exactly price_observations=248, return_observations=247, annualized_return=0.4053, annualized_volatility=0.1402, coefficient_of_variation=0.3459, and excess_return_per_unit_risk=2.4990.
weight: 5
criterion: The benchmark entry is exactly annualized_return=0.1276, annualized_volatility=0.1168, and excess_return_per_unit_risk=0.6215.
weight: 5
criterion: The relative_performance entry is exactly return_differential=0.2777, volatility_differential=0.0233, and risk_adjusted_return_differential=1.8775.
weight: 5
criterion: The recommendation states that the portfolio delivers superior risk-adjusted returns to the benchmark and that the incremental volatility is justified by the excess return.
weight: 5
criterion: The four stock sections are parsed from the multi-section local file `OTS_DS_Feb_AN2_007 - All.csv` and mapped to SBI, Maruti Suzuki, Apollo Hospitals, and Eicher Motors.
weight: 5
criterion: Daily returns are computed as close_t / close_{t-1} - 1 after aligning the four stocks on their common trading dates.
weight: 5
criterion: The equal-weighted portfolio daily return is the arithmetic mean of the four stock daily returns for each aligned trading day.
weight: 5
criterion: Annualized returns use compounded daily returns with a 252-trading-day convention, and annualized volatilities use population standard deviation with ddof=0 scaled by sqrt(252).
weight: 5
criterion: All reported numeric values are rounded to exactly 4 decimal places using round-half-to-even.
weight: 5

Golden answer:
```python
import csv
import json
import math
from collections import defaultdict
from datetime import datetime
from pathlib import Path


def parse_number(value: str) -> float:
    return float(value.strip().replace(",", ""))


def parse_stock_date(value: str):
    return datetime.strptime(value.strip(), "%d-%b-%Y").date()


def population_stddev(values):
    mean_value = sum(values) / len(values)
    variance = sum((value - mean_value) ** 2 for value in values) / len(values)
    return math.sqrt(variance)


def annualized_return_from_daily_returns(daily_returns):
    cumulative = 1.0
    for daily_return in daily_returns:
        cumulative *= 1.0 + daily_return
    return cumulative ** (252 / len(daily_returns)) - 1.0


def annualized_volatility_from_daily_returns(daily_returns):
    return population_stddev(daily_returns) * math.sqrt(252)


def load_stock_sections(file_path: Path):
    with file_path.open(encoding="utf-8-sig", newline="") as handle:
        rows = list(csv.reader(handle))

    sections = defaultdict(list)
    i = 0
    while i < len(rows):
        row = rows[i]
        first = row[0].strip() if row else ""
        if first and all(cell == "" for cell in row[1:]):
            section_name = first
            i += 1
            header = rows[i]
            i += 1
            while i < len(rows):
                current_row = rows[i]
                if current_row and current_row[0].strip() and all(cell == "" for cell in current_row[1:]):
                    break
                if any(cell.strip() for cell in current_row):
                    sections[section_name].append(dict(zip(header, current_row)))
                i += 1
            continue
        i += 1
    return sections


def main() -> None:
    base_path = Path(__file__).resolve().parent
    stock_sections = load_stock_sections(base_path / "OTS_DS_Feb_AN2_007 - All.csv")
    nifty_path = base_path / "nifty50.csv"

    stock_name_map = {
        "SBIN": "SBI",
        "MARUTI": "Maruti Suzuki",
        "APOLLOHOSP": "Apollo Hospitals",
        "EICHERMOT": "Eicher Motors",
    }

    stock_series = {}
    for raw_name, display_name in stock_name_map.items():
        stock_rows = []
        for row in stock_sections[raw_name]:
            stock_rows.append((parse_stock_date(row["DATE"]), parse_number(row["CLOSE"])))
        stock_rows.sort()
        stock_series[display_name] = stock_rows

    common_dates = {trade_date for trade_date, _ in next(iter(stock_series.values()))}
    for values in stock_series.values():
        common_dates &= {trade_date for trade_date, _ in values}
    common_dates = sorted(common_dates)

    stock_close_maps = {
        name: {trade_date: close_value for trade_date, close_value in values if trade_date in common_dates}
        for name, values in stock_series.items()
    }

    individual_stocks = {}
    stock_daily_returns = {}
    for name, close_map in stock_close_maps.items():
        prices = [close_map[trade_date] for trade_date in common_dates]
        daily_returns = [prices[i] / prices[i - 1] - 1.0 for i in range(1, len(prices))]
        stock_daily_returns[name] = daily_returns
        individual_stocks[name] = {
            "annualized_return": round(annualized_return_from_daily_returns(daily_returns), 4),
            "annualized_volatility": round(annualized_volatility_from_daily_returns(daily_returns), 4),
        }

    portfolio_daily_returns = []
    for day_returns in zip(*stock_daily_returns.values()):
        portfolio_daily_returns.append(sum(day_returns) / 4.0)

    risk_free_rate = 0.055
    portfolio_return = annualized_return_from_daily_returns(portfolio_daily_returns)
    portfolio_volatility = annualized_volatility_from_daily_returns(portfolio_daily_returns)
    portfolio_excess_return_per_unit_risk = (portfolio_return - risk_free_rate) / portfolio_volatility

    portfolio = {
        "price_observations": len(common_dates),
        "return_observations": len(portfolio_daily_returns),
        "annualized_return": round(portfolio_return, 4),
        "annualized_volatility": round(portfolio_volatility, 4),
        "coefficient_of_variation": round(portfolio_volatility / portfolio_return, 4),
        "excess_return_per_unit_risk": round(portfolio_excess_return_per_unit_risk, 4),
    }

    nifty_close_map = {}
    with nifty_path.open(encoding="utf-8-sig", newline="") as handle:
        reader = csv.DictReader(handle)
        for row in reader:
            trade_date = datetime.strptime(row["DateTime"].strip('"'), "%Y-%m-%d %H:%M:%S").date()
            nifty_close_map[trade_date] = float(row["NIFTY 50"])

    benchmark_prices = [nifty_close_map[trade_date] for trade_date in common_dates]
    benchmark_daily_returns = [benchmark_prices[i] / benchmark_prices[i - 1] - 1.0 for i in range(1, len(benchmark_prices))]
    benchmark_return = annualized_return_from_daily_returns(benchmark_daily_returns)
    benchmark_volatility = annualized_volatility_from_daily_returns(benchmark_daily_returns)
    benchmark_excess_return_per_unit_risk = (benchmark_return - risk_free_rate) / benchmark_volatility

    benchmark = {
        "annualized_return": round(benchmark_return, 4),
        "annualized_volatility": round(benchmark_volatility, 4),
        "excess_return_per_unit_risk": round(benchmark_excess_return_per_unit_risk, 4),
    }

    relative_performance = {
        "return_differential": round(portfolio_return - benchmark_return, 4),
        "volatility_differential": round(portfolio_volatility - benchmark_volatility, 4),
        "risk_adjusted_return_differential": round(
            portfolio_excess_return_per_unit_risk - benchmark_excess_return_per_unit_risk,
            4,
        ),
    }

    recommendation = (
        "The equal-weighted portfolio delivers superior risk-adjusted returns versus the Nifty 50, "
        "and the incremental volatility is justified by the materially higher excess return."
    )

    result = {
        "individual_stocks": individual_stocks,
        "portfolio": portfolio,
        "benchmark": benchmark,
        "relative_performance": relative_performance,
        "recommendation": recommendation,
    }
    print(json.dumps(result, indent=2))


if __name__ == "__main__":
    main()
```

This solution parses the local multi-section stock file, aligns the four stocks on common trading dates, computes simple daily returns, annualizes returns by compounding with a 252-trading-day convention, computes annualized volatility from the population standard deviation of daily returns, evaluates the equal-weighted portfolio against the aligned Nifty 50 benchmark, and rounds all reported values to 4 decimal places using round-half-to-even.

Verified output:

```json
{
  "individual_stocks": {
    "SBI": {
      "annualized_return": 0.6338,
      "annualized_volatility": 0.1988
    },
    "Maruti Suzuki": {
      "annualized_return": 0.2159,
      "annualized_volatility": 0.2022
    },
    "Apollo Hospitals": {
      "annualized_return": 0.1925,
      "annualized_volatility": 0.1971
    },
    "Eicher Motors": {
      "annualized_return": 0.577,
      "annualized_volatility": 0.2144
    }
  },
  "portfolio": {
    "price_observations": 248,
    "return_observations": 247,
    "annualized_return": 0.4053,
    "annualized_volatility": 0.1402,
    "coefficient_of_variation": 0.3459,
    "excess_return_per_unit_risk": 2.499
  },
  "benchmark": {
    "annualized_return": 0.1276,
    "annualized_volatility": 0.1168,
    "excess_return_per_unit_risk": 0.6215
  },
  "relative_performance": {
    "return_differential": 0.2777,
    "volatility_differential": 0.0233,
    "risk_adjusted_return_differential": 1.8775
  },
  "recommendation": "The equal-weighted portfolio delivers superior risk-adjusted returns versus the Nifty 50, and the incremental volatility is justified by the materially higher excess return."
}
```