"""Small, deterministic, standard-library classroom experiments. No network/orders.

Run: python strategy_labs.py --output strategy-results
These are arithmetic/engineering demonstrations on explicit synthetic inputs,
not market backtests, production optimizers, or executable trading services.
"""
import argparse
import hashlib
import json
import math
from pathlib import Path


def finite(values):
    if not values or not all(math.isfinite(float(x)) for x in values):
        raise ValueError('nonempty finite input required')


def ranks(values):
    finite(values)
    result = [0.0] * len(values)
    order = sorted(range(len(values)), key=lambda i: values[i])
    start = 0
    while start < len(order):
        end = start + 1
        while end < len(order) and values[order[end]] == values[order[start]]:
            end += 1
        average_rank = ((start + 1) + end) / 2
        for i in order[start:end]:
            result[i] = average_rank
        start = end
    return result


def correlation(a, b):
    finite(a); finite(b)
    if len(a) != len(b) or len(a) < 2:
        raise ValueError('paired observations required')
    ma, mb = sum(a) / len(a), sum(b) / len(b)
    sa = sum((x - ma) ** 2 for x in a)
    sb = sum((x - mb) ** 2 for x in b)
    if sa == 0 or sb == 0:
        return None
    return sum((x - ma) * (y - mb) for x, y in zip(a, b)) / math.sqrt(sa * sb)


def drawdown(equity):
    finite(equity)
    if any(x <= 0 for x in equity):
        raise ValueError('this simple model requires positive equity')
    peak = equity[0]
    result = []
    for value in equity:
        peak = max(peak, value)
        result.append(1 - value / peak)
    return {'path': result, 'maximum': max(result)}


def tail(losses, confidence):
    finite(losses)
    if not math.isfinite(confidence) or not 0 < confidence < 1:
        raise ValueError('confidence must be strictly between zero and one')
    ordered = sorted(losses)
    # epsilon only avoids float representation turning exact integer counts into +1.
    rank = math.ceil(confidence * len(ordered) - 1e-12)
    count = max(1, math.ceil((1 - confidence) * len(ordered) - 1e-12))
    return {'var_nearest_rank': ordered[rank - 1],
            'es_worst_k_mean': sum(ordered[-count:]) / count, 'tail_count': count,
            'convention': 'positive losses; ES mean of worst ceil((1-p)*n) observations'}


def portfolio(weights, means, covariance):
    finite(weights); finite(means)
    n = len(weights)
    if len(means) != n or len(covariance) != n or any(len(row) != n for row in covariance):
        raise ValueError('dimension mismatch')
    for row in covariance:
        finite(row)
    if any(abs(covariance[i][j] - covariance[j][i]) > 1e-12 for i in range(n) for j in range(n)):
        raise ValueError('symmetric covariance required')
    marginal = [sum(covariance[i][j] * weights[j] for j in range(n)) for i in range(n)]
    variance = sum(weights[i] * marginal[i] for i in range(n))
    if variance <= 0:
        raise ValueError('positive variance required for this demonstration')
    sigma = math.sqrt(variance)
    contribution = [weights[i] * marginal[i] / sigma for i in range(n)]
    return {'expected_return': sum(w * m for w, m in zip(weights, means)),
            'volatility': sigma, 'risk_contributions': contribution,
            'note': 'supplied covariance only; no covariance estimation or general risk-parity optimizer'}


def pretrade(request, existing, cap, age_seconds, unknown_order=False):
    values = [request, existing, cap, age_seconds]
    if any(not isinstance(v, (int, float)) or isinstance(v, bool) or not math.isfinite(v) or v < 0 for v in values):
        return {'allowed': False, 'reason': 'invalid_numeric'}
    if unknown_order:
        return {'allowed': False, 'reason': 'unknown_order'}
    if age_seconds > 60:
        return {'allowed': False, 'reason': 'stale_quote'}
    if existing + request > cap:
        return {'allowed': False, 'reason': 'aggregate_limit'}
    return {'allowed': True, 'reason': 'within_classroom_limits'}


def walk_forward(n=240, training=60, gap=5, testing=20, label_horizon=5):
    if min(n, training, testing, label_horizon) <= 0 or gap < label_horizon:
        raise ValueError('positive windows and gap at least label horizon required')
    rows = []
    start = 0
    while start + training + gap + testing <= n:
        last_train = start + training - 1
        first_test = last_train + gap + 1
        rows.append({'train_start': start, 'last_train_feature': last_train,
                     'last_train_label_end': last_train + label_horizon,
                     'test_start': first_test, 'test_end': first_test + testing - 1})
        start += testing
    return rows


def grid(path, levels=(100, 98, 96), unit=1, cap=3, fee=0.1):
    """Long-only buy-on-downcross synthetic inventory demo, NOT a full grid EA.

    At each discrete downcross, fill at the observed price (not assumed level).
    Levels trigger at most once; no sells. This exposes open inventory risk.
    """
    finite(path); finite(list(levels))
    if unit <= 0 or cap < 0 or fee < 0:
        raise ValueError('invalid grid settings')
    cash, position = 1000.0, 0
    used, events, equity = set(), [], [1000.0]
    for before, price in zip(path, path[1:]):
        for level in sorted(levels, reverse=True):
            if level not in used and before > level >= price:
                used.add(level)
                if position + unit > cap or cash < price * unit + fee:
                    events.append({'level': level, 'status': 'rejected', 'reason': 'inventory_or_cash_cap'})
                    continue
                cash -= price * unit + fee
                position += unit
                events.append({'level': level, 'fill': price, 'quantity': unit, 'status': 'synthetic_fill'})
        equity.append(cash + position * price)
    return {'events': events, 'open_inventory': position, 'cash': cash,
            'equity': equity, 'net_change': equity[-1] - 1000,
            'limitations': 'no sells, no leverage, no real fills; observed synthetic prices only'}


def run():
    losses = [-2, -1, 0, 1, 2, 3, 4, 5, 6, 10]
    covariance = [[0.01, 0.0], [0.0, 0.04]]
    equal = portfolio([0.5, 0.5], [0.01, 0.03], covariance)
    inverse = portfolio([2/3, 1/3], [0.01, 0.03], covariance)
    folds = walk_forward()
    falling = grid([101, 100, 98, 96, 94])
    rebound = grid([101, 100, 98, 100, 102])
    assert math.isclose(correlation(ranks([1, 2, 3]), ranks([3, 2, 1])), -1)
    assert ranks([1, 1, 3]) == [1.5, 1.5, 3]
    assert math.isclose(equal['expected_return'], .02)
    assert math.isclose(equal['volatility'], math.sqrt(.0125))
    assert math.isclose(sum(equal['risk_contributions']), equal['volatility'])
    assert math.isclose(inverse['risk_contributions'][0], inverse['risk_contributions'][1])
    assert tail(losses, .9)['var_nearest_rank'] == 6
    assert tail(losses, .9)['es_worst_k_mean'] == 10
    assert tail(losses, .95)['var_nearest_rank'] == 10
    assert math.isclose(drawdown([100, 120, 108, 126, 113.4])['maximum'], .1)
    assert math.isclose(falling['net_change'], -12.3)
    assert falling['open_inventory'] == 3
    assert math.isclose(rebound['net_change'], 5.8)
    assert grid([101, 100, 98, 96], cap=2)['open_inventory'] == 2
    assert pretrade(100, 0, 100, 60)['allowed']
    assert pretrade(100.01, 0, 100, 0)['reason'] == 'aggregate_limit'
    assert pretrade(float('nan'), 0, 100, 0)['reason'] == 'invalid_numeric'
    assert pretrade(-1, 0, 100, 0)['reason'] == 'invalid_numeric'
    assert pretrade(1, 0, 100, 61)['reason'] == 'stale_quote'
    assert pretrade(1, 0, 100, 0, True)['reason'] == 'unknown_order'
    assert all(f['last_train_label_end'] < f['test_start'] for f in folds)
    assert len({t for f in folds for t in range(f['test_start'], f['test_end'] + 1)}) == len(folds) * 20
    # Invalid data must produce explicit failures rather than fabricated zero risk.
    rejected = 0
    for operation in (lambda: tail([], .95), lambda: tail(losses, 1.2),
                      lambda: drawdown([100, float('nan')]), lambda: walk_forward(gap=0)):
        try:
            operation()
        except ValueError:
            rejected += 1
    assert rejected == 4
    return {'kind': 'synthetic classroom arithmetic/engineering; no market claims',
            'self_tests': 'pass', 'factor_rank_ic': -1, 'equal_weight': equal,
            'inverse_volatility_example': inverse, 'losses': losses,
            'tail90': tail(losses, .9), 'tail95': tail(losses, .95),
            'drawdown': drawdown([100, 120, 108, 126, 113.4]), 'walk_forward_folds': folds,
            'grid_falling': falling, 'grid_rebound': rebound,
            'risk_events': [pretrade(90, 180, 250, 0), pretrade(1, 0, 100, 61),
                            pretrade(1, 0, 100, 0, True)], 'invalid_inputs_rejected': rejected}


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--output', default='strategy-results')
    args = parser.parse_args()
    result = run()
    result['source_sha256'] = hashlib.sha256(Path(__file__).read_bytes()).hexdigest()
    output = Path(args.output)
    output.mkdir(parents=True, exist_ok=True)
    (output / 'report.json').write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8')
    print(json.dumps({'self_tests': result['self_tests'], 'folds': len(result['walk_forward_folds']),
                      'invalid_inputs_rejected': result['invalid_inputs_rejected']}))
