"""Synthetic quant-trading lesson. Python standard library only; no broker access.

Information at mark t sets exposure for the interval t -> t+1. Fills at the
signal mark are an idealization, not evidence that an actual close is tradable.
Costs use a return-space convention: one-way rate times exposure change,
as a fraction of start-interval equity; final liquidation uses marked equity.
"""

from math import isfinite

PATHS = {
    "trend": [100, 102, 104, 103, 105, 107, 109, 111],
    "reversal": [100, 102, 100, 102, 100, 102, 100, 102],
}


def backtest(prices, cost_bps=10, mode="momentum"):
    if len(prices) < 3 or any(not isfinite(p) or p <= 0 for p in prices):
        raise ValueError("Need at least three finite, positive prices")
    if not isfinite(cost_bps) or not 0 <= cost_bps <= 1000:
        raise ValueError("One-way cost must be between 0 and 1000 bps")
    if mode not in ("momentum", "buy-and-hold"):
        raise ValueError("Unknown strategy mode")
    cost = cost_bps / 10000
    equity = gross = 10000.0
    previous_position = changes = 0
    curve = [equity]
    rows = []
    for i in range(1, len(prices)):
        position = int(mode == "buy-and-hold" or
                       (i > 1 and prices[i - 1] > prices[i - 2]))
        asset_return = prices[i] / prices[i - 1] - 1
        turnover = abs(position - previous_position)
        growth = 1 + position * asset_return - cost * turnover
        if not isfinite(growth) or growth <= 0:
            raise ValueError("Outside this simplified model; use a share/cash ledger")
        gross *= 1 + position * asset_return
        equity *= growth
        rows.append((i, position, asset_return, turnover, equity))
        curve.append(equity)
        changes += turnover
        previous_position = position
    equity *= 1 - cost * previous_position  # sell at the last mark
    changes += previous_position
    curve.append(equity)
    peak = curve[0]
    drawdown = 0.0
    for value in curve:
        peak = max(peak, value)
        drawdown = max(drawdown, 1 - value / peak)
    return dict(equity=equity, gross=gross, changes=changes,
                drawdown=drawdown, rows=rows)


if __name__ == "__main__":
    for name, prices in PATHS.items():
        for mode in ("momentum", "buy-and-hold"):
            result = backtest(prices, mode=mode)
            net_return = result["equity"] / 10000 - 1
            print(f"{name:8} {mode:12} equity={result['equity']:.2f} "
                  f"return={net_return:.4%} dd={result['drawdown']:.4%} "
                  f"changes={result['changes']}")
