"""Synthetic stat-arb accounting demonstration; Python 3.10+, standard library.

All prices are invented. Parameters are frozen before the six evaluation days.
Signal: previous close. Entry: today's open. Exit: today's close, flat overnight.
Returns use beginning-of-day equity, 0.5 long + 0.5 short, no leverage.
Cost is per dollar traded, charged on entry AND actual closing notionals.
Borrow is a deliberately simplified short-entry-notional * annual rate / 252.
This is an accounting exercise, not empirical evidence or a production system.
Run normally to print results; pass --export to save JSON/CSVs beside this file.
"""

import csv
import json
import math
import sys
from pathlib import Path
from statistics import mean, pstdev

TRAIN_A = [99.0, 100.0, 101.0, 99.0, 100.0, 101.0]
TRAIN_DATES = ["2026-09-14", "2026-09-15", "2026-09-16", "2026-09-17", "2026-09-18", "2026-09-21"]
# date, A open, A close, B open, B close -- every price is synthetic
EVALUATION = [
    ("2026-09-22", 104.0, 102.0, 100.0, 100.0),
    ("2026-09-23", 103.0, 106.0, 100.0, 100.0),
    ("2026-09-24", 106.0, 101.0, 100.0, 100.0),
    ("2026-09-25", 102.0, 100.0, 100.0, 100.0),
    ("2026-09-28", 100.0, 98.0, 100.0, 100.0),
    ("2026-09-29", 98.0, 99.0, 100.0, 100.0),
]
TRAIN_SPREADS = [price / 100.0 - 1.0 for price in TRAIN_A]
MU, SIGMA = mean(TRAIN_SPREADS), pstdev(TRAIN_SPREADS)


def simulate(cost_bps=5.0, borrow_percent=6.0, evaluation=EVALUATION):
    if not math.isfinite(cost_bps) or not math.isfinite(borrow_percent):
        raise ValueError("Costs must be finite")
    if cost_bps < 0 or borrow_percent < 0:
        raise ValueError("Costs must be nonnegative")
    previous_a, previous_b = TRAIN_A[-1], 100.0
    nav = gross_nav = 1.0
    rows = []
    for date, open_a, close_a, open_b, close_b in evaluation:
        z = (previous_a / previous_b - 1.0 - MU) / SIGMA
        direction = -1 if z > 1.0 else (1 if z < -1.0 else 0)
        weight_a, weight_b = 0.5 * direction, -0.5 * direction
        return_a, return_b = close_a / open_a - 1.0, close_b / open_b - 1.0
        gross = weight_a * return_a + weight_b * return_b
        # Entry notional + closing notional; closing prices can change turnover.
        turnover = abs(weight_a) * (1 + close_a / open_a) + abs(weight_b) * (1 + close_b / open_b)
        short_weight = max(-weight_a, 0.0) + max(-weight_b, 0.0)
        execution_cost = turnover * cost_bps / 10000.0
        borrow = short_weight * borrow_percent / 100.0 / 252.0
        net = gross - execution_cost - borrow
        nav *= 1.0 + net
        gross_nav *= 1.0 + gross
        rows.append({"date": date, "previous_close_a": previous_a, "z": z, "weight_a": weight_a, "weight_b": weight_b, "gross": gross, "turnover": turnover, "short_weight": short_weight, "execution_cost": execution_cost, "borrow": borrow, "net": net, "nav": nav, "gross_nav": gross_nav})
        previous_a, previous_b = close_a, close_b
    return {"cost_bps": cost_bps, "borrow_percent": borrow_percent, "net_compounded_return": nav - 1.0, "gross_compounded_return": gross_nav - 1.0, "sum_daily_net_returns": sum(row["net"] for row in rows), "rows": rows}


def verify():
    result = simulate()
    rows = result["rows"]
    assert math.isclose(rows[0]["gross"], 0.5 * (1 - 102 / 104))
    assert math.isclose(rows[0]["turnover"], 1 + 0.5 * (102 / 104) + 0.5)
    assert rows[1]["net"] < 0  # divergence produces a loss despite short signal
    assert rows[4]["weight_a"] == 0 and rows[4]["net"] == 0
    assert rows[-1]["weight_a"] > 0
    # Changing today's future close cannot alter today's earlier signal/weights.
    changed = [(EVALUATION[0][0], 104.0, 150.0, 100.0, 100.0), *EVALUATION[1:]]
    assert simulate(evaluation=changed)["rows"][0]["weight_a"] == rows[0]["weight_a"]
    # Cost accounting and compounding invariants, independently recomputed.
    assert math.isclose(simulate(0, 0)["net_compounded_return"], result["gross_compounded_return"])
    assert math.isclose(math.prod(1 + row["net"] for row in rows) - 1, result["net_compounded_return"])
    returns = [simulate(cost)["net_compounded_return"] for cost in [0, 5, 10, 25, 50]]
    assert all(left > right for left, right in zip(returns, returns[1:]))
    assert simulate(50)["net_compounded_return"] < 0
    return result


if __name__ == "__main__":
    result = verify()
    print("ALL PRICES SYNTHETIC -- no empirical performance claim")
    print(f"Training mean={MU:.6f}; population standard deviation={SIGMA:.6f}")
    print("date         z       wA     gross_bp turnover   costs_bp  borrow_bp   net_bp")
    for row in result["rows"]:
        print(f'{row["date"]} {row["z"]:7.3f} {row["weight_a"]:6.2f} {row["gross"]*10000:10.3f} {row["turnover"]:8.4f} {row["execution_cost"]*10000:10.3f} {row["borrow"]*10000:10.3f} {row["net"]*10000:9.3f}')
    sensitivity = [{"cost_bps": cost, "net_compounded_return": simulate(cost)["net_compounded_return"]} for cost in [0, 5, 10, 25, 50]]
    print("Gross compounded:", f'{result["gross_compounded_return"]:.6%}')
    print("Net compounded:", f'{result["net_compounded_return"]:.6%}')
    print("Sensitivity (6% annual borrow):", json.dumps(sensitivity))
    if "--export" in sys.argv:
        output_dir = Path(__file__).resolve().parent
        with (output_dir / "stat-arb-example-results.json").open("x", encoding="utf-8") as output:
            json.dump({"data_type": "synthetic", "python": sys.version.split()[0], "training_mean": MU, "training_population_sd": SIGMA, "result": result, "sensitivity": sensitivity}, output, indent=2)
        with (output_dir / "stat-arb-example-ledger.csv").open("x", newline="", encoding="utf-8") as output:
            writer = csv.DictWriter(output, fieldnames=list(result["rows"][0]))
            writer.writeheader()
            writer.writerows(result["rows"])
        with (output_dir / "stat-arb-synthetic-prices.csv").open("x", newline="", encoding="utf-8") as output:
            writer = csv.writer(output)
            writer.writerow(["date", "phase", "a_open", "a_close", "b_open", "b_close"])
            writer.writerows((date, "training", "", price, "", 100) for date, price in zip(TRAIN_DATES, TRAIN_A))
            writer.writerows((date, "evaluation", a_open, a_close, b_open, b_close) for date, a_open, a_close, b_open, b_close in EVALUATION)
