"""Test all Python code snippets from kelly.mdx, quant.mdx, and signals.mdx docs.

Each snippet is tested independently. For mathematical functions, we test with
sample inputs. For hz.run() strategy code, we verify imports and function
construction only (no live engine). Prints PASS/FAIL for each snippet.
"""

import traceback

results = []


def run_test(name, fn):
    """Run a single test and record PASS/FAIL."""
    try:
        fn()
        results.append(("PASS", name))
        print(f"PASS: {name}")
    except Exception as e:
        results.append(("FAIL", name, str(e)))
        print(f"FAIL: {name}")
        traceback.print_exc()
        print()


# ============================================================================
# kelly.mdx snippets
# ============================================================================


def test_kelly_edge():
    """kelly.mdx - hz.edge(fair_prob, market_price)"""
    import horizon as hz
    edge = hz.edge(fair_prob=0.65, market_price=0.55)
    assert isinstance(edge, float), f"Expected float, got {type(edge)}"
    print(f"  Edge: {edge:.4f}")


def test_kelly_full():
    """kelly.mdx - hz.kelly(fair_prob, market_price)"""
    import horizon as hz
    fraction = hz.kelly(fair_prob=0.65, market_price=0.55)
    assert isinstance(fraction, float), f"Expected float, got {type(fraction)}"
    print(f"  Kelly fraction: {fraction:.4f}")


def test_kelly_no():
    """kelly.mdx - hz.kelly_no(fair_prob, market_price)"""
    import horizon as hz
    fraction = hz.kelly_no(fair_prob=0.35, market_price=0.55)
    assert isinstance(fraction, float), f"Expected float, got {type(fraction)}"
    print(f"  Kelly No fraction: {fraction:.4f}")


def test_fractional_kelly():
    """kelly.mdx - hz.fractional_kelly(fair_prob, market_price, fraction)"""
    import horizon as hz
    fraction = hz.fractional_kelly(
        fair_prob=0.65,
        market_price=0.55,
        fraction=0.5,
    )
    assert isinstance(fraction, float), f"Expected float, got {type(fraction)}"
    print(f"  Half-Kelly fraction: {fraction:.4f}")


def test_kelly_size():
    """kelly.mdx - hz.kelly_size(fair_prob, market_price, bankroll, fraction)"""
    import horizon as hz
    size = hz.kelly_size(
        fair_prob=0.65,
        market_price=0.55,
        bankroll=10000.0,
        fraction=0.5,
    )
    assert isinstance(size, float), f"Expected float, got {type(size)}"
    print(f"  Optimal position: {size:.1f} contracts")


def test_multi_kelly():
    """kelly.mdx - hz.multi_kelly(positions, bankroll)"""
    import horizon as hz
    positions = [
        (0.65, 0.55),
        (0.40, 0.30),
        (0.75, 0.60),
    ]
    sizes = hz.multi_kelly(positions=positions, bankroll=10000.0)
    assert isinstance(sizes, list), f"Expected list, got {type(sizes)}"
    for i, size in enumerate(sizes):
        print(f"  Position {i+1}: {size:.1f} contracts")


def test_liquidity_adjusted_kelly():
    """kelly.mdx - hz.liquidity_adjusted_kelly(fair_prob, market_price, fraction, available_liquidity, max_impact_pct)"""
    import horizon as hz
    size = hz.liquidity_adjusted_kelly(
        fair_prob=0.65,
        market_price=0.55,
        fraction=0.5,
        available_liquidity=500.0,
        max_impact_pct=0.10,
    )
    assert isinstance(size, float), f"Expected float, got {type(size)}"
    print(f"  Liquidity-adjusted size: {size:.1f}")


def test_kelly_sizer_pipeline_helper():
    """kelly.mdx - hz.kelly_sizer(fair, price, bankroll, fraction) as pipeline helper"""
    # The docs show kelly_sizer as a direct function: hz.kelly_sizer(fair=0.65, price=0.55, ...)
    # But actual implementation is a pipeline factory: kelly_sizer(fraction, bankroll, max_size) -> callable
    # Testing if the docs' calling convention works
    import horizon as hz
    try:
        size = hz.kelly_sizer(
            fair=0.65,
            price=0.55,
            bankroll=10000.0,
            fraction=0.5,
        )
        assert isinstance(size, (float, int)), f"Expected numeric, got {type(size)}"
        print(f"  kelly_sizer direct call: {size}")
    except TypeError as e:
        # If it's a pipeline factory, calling with these kwargs will fail
        raise AssertionError(
            f"kelly_sizer docs show direct call with (fair, price, bankroll, fraction) "
            f"but actual API is a pipeline factory: {e}"
        )


def test_kelly_sizer_with_liquidity_pipeline_helper():
    """kelly.mdx - hz.kelly_sizer_with_liquidity(fair, price, bankroll, fraction, liquidity, max_impact_pct)"""
    import horizon as hz
    try:
        size = hz.kelly_sizer_with_liquidity(
            fair=0.65,
            price=0.55,
            bankroll=10000.0,
            fraction=0.5,
            liquidity=500.0,
            max_impact_pct=0.10,
        )
        assert isinstance(size, (float, int)), f"Expected numeric, got {type(size)}"
        print(f"  kelly_sizer_with_liquidity direct call: {size}")
    except TypeError as e:
        raise AssertionError(
            f"kelly_sizer_with_liquidity docs show direct call with (fair, price, ...) "
            f"but actual API is a pipeline factory: {e}"
        )


def test_kelly_basic_sizing_example():
    """kelly.mdx - Basic Kelly Sizing example"""
    import horizon as hz

    fair = 0.65
    market = 0.55
    bankroll = 5000.0

    edge = hz.edge(fair, market)
    print(f"  Edge: {edge:.2%}")

    if edge > 0:
        full = hz.kelly(fair, market)
        print(f"  Full Kelly: {full:.2%} of bankroll")

        half = hz.fractional_kelly(fair, market, fraction=0.5)
        print(f"  Half Kelly: {half:.2%} of bankroll")

        size = hz.kelly_size(fair, market, bankroll, fraction=0.5)
        print(f"  Position size: {size:.0f} contracts")


def test_kelly_multi_position_portfolio():
    """kelly.mdx - Multi-Position Portfolio example"""
    import horizon as hz

    bankroll = 20000.0
    markets = {
        "election-winner":   (0.62, 0.50),
        "fed-rate-cut":      (0.45, 0.35),
        "btc-above-100k":    (0.70, 0.55),
        "recession-2025":    (0.30, 0.40),
    }

    positions = []
    market_names = []
    for name, (fair, price) in markets.items():
        edge = hz.edge(fair, price)
        if edge > 0:
            positions.append((fair, price))
            market_names.append(name)
            print(f"  {name}: edge={edge:.2%}")
        else:
            print(f"  {name}: no edge (skipping)")

    if positions:
        sizes = hz.multi_kelly(positions, bankroll)
        print("  --- Allocations ---")
        for name, size in zip(market_names, sizes):
            print(f"  {name}: {size:.0f} contracts")


def test_kelly_liquidity_aware_sizing():
    """kelly.mdx - Liquidity-Aware Sizing example"""
    import horizon as hz

    fair = 0.70
    price = 0.58
    bankroll = 10000.0

    naive_size = hz.kelly_size(fair, price, bankroll, fraction=0.5)
    print(f"  Naive Kelly size: {naive_size:.0f} contracts")

    adjusted_size = hz.liquidity_adjusted_kelly(
        fair_prob=fair,
        market_price=price,
        fraction=0.5,
        available_liquidity=200.0,
        max_impact_pct=0.10,
    )
    print(f"  Liquidity-adjusted size: {adjusted_size:.0f} contracts")


def test_kelly_pipeline_example():
    """kelly.mdx - Kelly in a Pipeline (verify imports and function construction only)"""
    import horizon as hz
    from horizon import Side, OrderSide, OrderRequest

    BANKROLL = 10000.0
    KELLY_FRACTION = 0.25

    def model(ctx):
        fair = 0.62
        return {"fair": fair}

    def kelly_quoter(ctx, fair):
        price = ctx.feed.price
        edge = hz.edge(fair, price)
        if edge < 0.02:
            return None
        liquidity = ctx.feed.ask_size if hasattr(ctx.feed, "ask_size") else 1000.0
        size = hz.kelly_sizer_with_liquidity(
            fair=fair,
            price=price,
            bankroll=BANKROLL,
            fraction=KELLY_FRACTION,
            liquidity=liquidity,
            max_impact_pct=0.05,
        )
        if size < 1.0:
            return None
        return OrderRequest(
            market_id=ctx.market_id,
            side=Side.Yes,
            order_side=OrderSide.Buy,
            price=price,
            size=round(size, 1),
        )

    # Just verify the functions are constructable (not actually calling hz.run)
    assert callable(model)
    assert callable(kelly_quoter)
    print("  Pipeline functions constructed OK")


def test_kelly_comparing_fractions():
    """kelly.mdx - Comparing Kelly Fractions example"""
    import horizon as hz

    fair = 0.65
    price = 0.55
    bankroll = 10000.0

    print("  Fraction | Size    | Expected Growth Rate")
    print("  " + "-" * 45)

    for frac in [0.1, 0.25, 0.5, 0.75, 1.0]:
        size = hz.kelly_size(fair, price, bankroll, fraction=frac)
        full_kelly = hz.kelly(fair, price)
        edge = hz.edge(fair, price)
        growth = frac * full_kelly * edge - (frac * full_kelly) ** 2 / 2
        print(f"    {frac:.2f}   | {size:7.0f} | {growth:.6f}")


# ============================================================================
# quant.mdx snippets
# ============================================================================


def test_quant_information_theory():
    """quant.mdx - Information Theory: shannon_entropy, joint_entropy, kl_divergence"""
    import horizon as hz

    h = hz.shannon_entropy(0.5)
    assert isinstance(h, float), f"Expected float, got {type(h)}"
    print(f"  shannon_entropy(0.5) = {h:.4f}")

    h = hz.joint_entropy([0.25, 0.25, 0.25, 0.25])
    assert isinstance(h, float), f"Expected float, got {type(h)}"
    print(f"  joint_entropy([0.25,0.25,0.25,0.25]) = {h:.4f}")

    kl = hz.kl_divergence([0.9, 0.1], [0.5, 0.5])
    assert isinstance(kl, float), f"Expected float, got {type(kl)}"
    print(f"  kl_divergence([0.9,0.1],[0.5,0.5]) = {kl:.4f}")


def test_quant_mutual_information():
    """quant.mdx - mutual_information(predictions, outcomes, n_bins)"""
    import horizon as hz
    import random
    random.seed(42)
    predictions = [random.random() for _ in range(100)]
    outcomes = [random.random() for _ in range(100)]
    mi = hz.mutual_information(predictions, outcomes, n_bins=10)
    assert isinstance(mi, float), f"Expected float, got {type(mi)}"
    print(f"  mutual_information = {mi:.4f}")


def test_quant_transfer_entropy():
    """quant.mdx - transfer_entropy(source, target, lag, n_bins)"""
    import horizon as hz
    import random
    random.seed(42)
    source_series = [random.random() for _ in range(100)]
    target_series = [random.random() for _ in range(100)]
    te = hz.transfer_entropy(source_series, target_series, lag=1, n_bins=10)
    assert isinstance(te, float), f"Expected float, got {type(te)}"
    print(f"  transfer_entropy = {te:.4f}")


def test_quant_microstructure():
    """quant.mdx - Microstructure: kyles_lambda, amihud_ratio, roll_spread, effective/realized spread, lob_imbalance, weighted_mid"""
    import horizon as hz

    price_changes = [0.01, -0.02, 0.005, -0.01, 0.015, -0.005, 0.02]
    signed_volumes = [100.0, -150.0, 50.0, -80.0, 120.0, -60.0, 90.0]
    returns = [0.01, -0.02, 0.005, -0.01, 0.015, -0.005, 0.02]
    volumes = [1000.0, 1200.0, 800.0, 1100.0, 900.0, 1300.0, 1000.0]

    lam = hz.kyles_lambda(price_changes, signed_volumes)
    print(f"  kyles_lambda = {lam:.6f}")

    illiq = hz.amihud_ratio(returns, volumes)
    print(f"  amihud_ratio = {illiq:.6f}")

    spread = hz.roll_spread(returns)
    print(f"  roll_spread = {spread:.6f}")

    eff = hz.effective_spread(fill_price=0.52, mid=0.50, is_buy=True)
    print(f"  effective_spread = {eff:.6f}")

    real = hz.realized_spread(fill_price=0.52, mid_after=0.51, is_buy=True)
    print(f"  realized_spread = {real:.6f}")

    imb = hz.lob_imbalance(bids=[(0.50, 100)], asks=[(0.51, 80)], levels=5)
    print(f"  lob_imbalance = {imb:.6f}")

    mid = hz.weighted_mid(bids=[(0.50, 200)], asks=[(0.52, 100)])
    print(f"  weighted_mid = {mid:.6f}")


def test_quant_cornish_fisher():
    """quant.mdx - Cornish-Fisher VaR/CVaR"""
    import horizon as hz
    import random
    random.seed(42)
    returns = [random.gauss(0.001, 0.02) for _ in range(200)]

    var = hz.cornish_fisher_var(returns, confidence=0.95)
    print(f"  cornish_fisher_var = {var:.6f}")

    cvar = hz.cornish_fisher_cvar(returns, confidence=0.95)
    print(f"  cornish_fisher_cvar = {cvar:.6f}")


def test_quant_prediction_greeks():
    """quant.mdx - prediction_greeks(price, size, is_yes, t_hours, vol)"""
    import horizon as hz

    greeks = hz.prediction_greeks(
        price=0.65,
        size=100.0,
        is_yes=True,
        t_hours=24.0,
        vol=0.2,
    )
    print(f"  delta={greeks.delta:.4f}, gamma={greeks.gamma:.4f}, theta={greeks.theta:.4f}, vega={greeks.vega:.4f}")


def test_quant_signal_analysis():
    """quant.mdx - information_coefficient, signal_half_life, hurst_exponent, variance_ratio"""
    import horizon as hz
    import random
    random.seed(42)

    predictions = [random.random() for _ in range(50)]
    outcomes = [random.random() for _ in range(50)]

    ic = hz.information_coefficient(predictions, outcomes)
    print(f"  information_coefficient = {ic:.4f}")

    signal_values = [random.gauss(0, 1) for _ in range(50)]
    hl = hz.signal_half_life(signal_values)
    print(f"  signal_half_life = {hl:.4f}")

    price_series = [50.0 + random.gauss(0, 1) for _ in range(100)]
    h = hz.hurst_exponent(price_series)
    print(f"  hurst_exponent = {h:.4f}")

    returns = [random.gauss(0, 0.01) for _ in range(100)]
    vr = hz.variance_ratio(returns, period=2)
    print(f"  variance_ratio = {vr:.4f}")


def test_quant_deflated_sharpe():
    """quant.mdx - deflated_sharpe, bonferroni_threshold, benjamini_hochberg"""
    import horizon as hz

    p_value = hz.deflated_sharpe(
        sharpe=2.1,
        n_obs=500,
        n_trials=50,
        skew=0.1,
        kurt=3.5,
    )
    print(f"  deflated_sharpe p-value = {p_value:.4f}")

    threshold = hz.bonferroni_threshold(alpha=0.05, n_trials=50)
    print(f"  bonferroni_threshold = {threshold:.6f}")

    rejected = hz.benjamini_hochberg([0.01, 0.03, 0.5], alpha=0.05)
    print(f"  benjamini_hochberg = {rejected}")


def test_quant_vpin_detector():
    """quant.mdx - VpinDetector streaming detector"""
    import horizon as hz

    vpin = hz.VpinDetector(bucket_volume=1000.0, n_buckets=50)
    toxicity = vpin.update(price=0.65, volume=50.0, is_buy=True)
    print(f"  VpinDetector.update() = {toxicity:.4f}")
    print(f"  VpinDetector.current_vpin() = {vpin.current_vpin():.4f}")


def test_quant_cusum_detector():
    """quant.mdx - CusumDetector streaming detector"""
    import horizon as hz

    cusum = hz.CusumDetector(threshold=5.0, drift=0.0)
    is_break = cusum.update(value=2.5)
    print(f"  CusumDetector.update(2.5) = {is_break}")
    print(f"  upper={cusum.upper():.4f}, lower={cusum.lower():.4f}")


def test_quant_ofi_tracker():
    """quant.mdx - OfiTracker streaming detector"""
    import horizon as hz

    ofi = hz.OfiTracker()
    delta = ofi.update(best_bid_qty=150.0, best_ask_qty=100.0)
    print(f"  OfiTracker.update() = {delta:.4f}")
    print(f"  OfiTracker.cumulative() = {ofi.cumulative():.4f}")


def test_quant_pipeline_toxic_flow():
    """quant.mdx - toxic_flow pipeline function (construction only)"""
    import horizon as hz

    # Docs show: hz.toxic_flow("book", bucket_volume=500, threshold=0.7)
    fn = hz.toxic_flow("book", bucket_volume=500, threshold=0.7)
    assert callable(fn), f"Expected callable, got {type(fn)}"
    print("  toxic_flow pipeline function constructed OK")


def test_quant_pipeline_microstructure():
    """quant.mdx - microstructure pipeline function (construction only)"""
    import horizon as hz

    fn = hz.microstructure("book", lookback=100)
    assert callable(fn), f"Expected callable, got {type(fn)}"
    print("  microstructure pipeline function constructed OK")


def test_quant_pipeline_change_detector():
    """quant.mdx - change_detector pipeline function (construction only)"""
    import horizon as hz

    fn = hz.change_detector(threshold=5.0)
    assert callable(fn), f"Expected callable, got {type(fn)}"
    print("  change_detector pipeline function constructed OK")


def test_quant_strategy_significance():
    """quant.mdx - strategy_significance(result, n_trials, alpha)"""
    import horizon as hz

    # Build a simple equity curve
    import random
    random.seed(42)
    equity_curve = [1000.0]
    for _ in range(200):
        equity_curve.append(equity_curve[-1] * (1 + random.gauss(0.001, 0.02)))

    result = hz.strategy_significance(
        result={"equity_curve": equity_curve},
        n_trials=50,
        alpha=0.05,
    )
    print(f"  deflated_sharpe_pvalue = {result['deflated_sharpe_pvalue']:.4f}")
    print(f"  is_significant = {result['is_significant']}")


def test_quant_signal_diagnostics():
    """quant.mdx - signal_diagnostics(predictions, outcomes)"""
    import horizon as hz
    import random
    random.seed(42)

    predictions = [random.random() for _ in range(50)]
    outcomes = [random.random() for _ in range(50)]

    diag = hz.signal_diagnostics(predictions, outcomes)
    print(f"  ic={diag['ic']:.4f}, half_life={diag['half_life']:.4f}, hurst={diag['hurst']:.4f}")


def test_quant_market_efficiency():
    """quant.mdx - market_efficiency(price_series)"""
    import horizon as hz
    import random
    random.seed(42)

    price_series = [50.0 + random.gauss(0, 1) for _ in range(100)]
    eff = hz.market_efficiency(price_series)
    print(f"  variance_ratio={eff['variance_ratio']:.4f}, hurst={eff['hurst']:.4f}, is_efficient={eff['is_efficient']}")


def test_quant_stress_test():
    """quant.mdx - stress_test with SimPosition"""
    from horizon import SimPosition, stress_test

    positions = [
        SimPosition("mkt1", "yes", 100.0, 0.50, 0.60),
        SimPosition("mkt2", "yes", 50.0, 0.40, 0.55),
    ]

    import horizon as hz
    result = hz.stress_test(positions, n_simulations=10000, seed=42)
    print(f"  worst_scenario = {result.worst_scenario}")
    print(f"  worst_pnl = {result.worst_pnl:.2f}")
    summary = result.summary()
    print(f"  summary keys: {list(summary.keys())}")


def test_quant_custom_stress_scenario():
    """quant.mdx - StressScenario custom scenario"""
    from horizon import SimPosition, StressScenario
    import horizon as hz

    positions = [
        SimPosition("mkt1", "yes", 100.0, 0.50, 0.60),
        SimPosition("mkt2", "yes", 50.0, 0.40, 0.55),
    ]

    crash = StressScenario(
        name="election_shock",
        correlation_override=0.99,
        price_shocks={"mkt1": 0.05, "mkt2": 0.10},
    )
    result = hz.stress_test(positions, scenarios=[crash])
    print(f"  custom scenario result: worst={result.worst_scenario}, pnl={result.worst_pnl:.2f}")


def test_quant_cpcv():
    """quant.mdx - CPCV (Combinatorial Purged Cross-Validation)"""
    from horizon import cpcv, probability_of_overfitting
    import random
    random.seed(42)

    returns_series = [random.gauss(0.001, 0.02) for _ in range(200)]

    def pipeline_factory(params):
        multiplier = params["multiplier"]
        def strategy(returns):
            adjusted = [r * multiplier for r in returns]
            mean = sum(adjusted) / len(adjusted)
            std = (sum((r - mean)**2 for r in adjusted) / len(adjusted)) ** 0.5
            return mean / std if std > 0 else 0.0
        return strategy

    result = cpcv(
        data=returns_series,
        pipeline_factory=pipeline_factory,
        param_grid=[{"multiplier": m} for m in [0.5, 1.0, 1.5, 2.0]],
        n_groups=8,
        purge_gap=0.02,
    )

    print(f"  PBO: {result.pbo:.2%}")
    print(f"  Overfit: {result.is_overfit}")
    print(f"  OOS Sharpes: {result.oos_sharpes[:5]}...")
    print(f"  Combinations tested: {result.n_combinations}")


# ============================================================================
# signals.mdx snippets
# ============================================================================


def test_signals_combine_signals():
    """signals.mdx - hz.combine_signals(signals, method)"""
    import horizon as hz

    score = hz.combine_signals(
        signals=[(0.6, 1.0), (0.8, 2.0)],
        method="weighted_avg",
    )
    assert isinstance(score, float), f"Expected float, got {type(score)}"
    print(f"  Combined: {score:.4f}")


def test_signals_ema():
    """signals.mdx - hz.ema(values, span)"""
    import horizon as hz

    result = hz.ema(values=[1.0, 2.0, 3.0, 4.0], span=3)
    assert isinstance(result, float), f"Expected float, got {type(result)}"
    print(f"  EMA: {result:.4f}")


def test_signals_zscore():
    """signals.mdx - hz.zscore(value, mean, std)"""
    import horizon as hz

    z = hz.zscore(value=10.0, mean=5.0, std=2.0)
    assert isinstance(z, float), f"Expected float, got {type(z)}"
    print(f"  Z-score: {z:.4f}")


def test_signals_decay_weight():
    """signals.mdx - hz.decay_weight(age_secs, half_life_secs)"""
    import horizon as hz

    w = hz.decay_weight(age_secs=60.0, half_life_secs=60.0)
    assert isinstance(w, float), f"Expected float, got {type(w)}"
    print(f"  Weight: {w:.4f}")


def test_signals_signal_combiner_factory():
    """signals.mdx - hz.signal_combiner([...], method, smoothing, clip) factory"""
    import horizon as hz

    combiner = hz.signal_combiner(
        signals=[
            hz.price_signal("btc", weight=0.4),
            hz.spread_signal("book", weight=0.3),
            hz.momentum_signal("btc", lookback=20, weight=0.2),
            hz.flow_signal("btc", window=50, weight=0.1),
        ],
        method="weighted_avg",
        smoothing=10,
        clip=(0.0, 1.0),
    )
    assert callable(combiner), f"Expected callable, got {type(combiner)}"
    print("  signal_combiner factory constructed OK")


def test_signals_price_signal():
    """signals.mdx - hz.price_signal(feed_name, weight)"""
    import horizon as hz

    sig = hz.price_signal("btc", weight=0.4)
    assert hasattr(sig, "name"), "Signal missing 'name' attribute"
    assert hasattr(sig, "fn"), "Signal missing 'fn' attribute"
    assert hasattr(sig, "weight"), "Signal missing 'weight' attribute"
    print(f"  price_signal: name={sig.name}, weight={sig.weight}")


def test_signals_spread_signal():
    """signals.mdx - hz.spread_signal(feed_name, weight)"""
    import horizon as hz

    sig = hz.spread_signal("book", weight=0.3)
    assert hasattr(sig, "name"), "Signal missing 'name' attribute"
    print(f"  spread_signal: name={sig.name}, weight={sig.weight}")


def test_signals_momentum_signal():
    """signals.mdx - hz.momentum_signal(feed_name, lookback, weight)"""
    import horizon as hz

    sig = hz.momentum_signal("btc", lookback=20, weight=0.2)
    assert hasattr(sig, "name"), "Signal missing 'name' attribute"
    print(f"  momentum_signal: name={sig.name}, weight={sig.weight}")


def test_signals_flow_signal():
    """signals.mdx - hz.flow_signal(feed_name, window, weight)"""
    import horizon as hz

    sig = hz.flow_signal("btc", window=50, weight=0.1)
    assert hasattr(sig, "name"), "Signal missing 'name' attribute"
    print(f"  flow_signal: name={sig.name}, weight={sig.weight}")


def test_signals_imbalance_signal():
    """signals.mdx - hz.imbalance_signal(feed_name, levels, weight)"""
    import horizon as hz

    sig = hz.imbalance_signal("book", levels=5, weight=0.3)
    assert hasattr(sig, "name"), "Signal missing 'name' attribute"
    print(f"  imbalance_signal: name={sig.name}, weight={sig.weight}")


def test_signals_basic_combination_example():
    """signals.mdx - Basic Signal Combination (hz.run construction, verify imports)"""
    import horizon as hz

    def quoter(ctx, score):
        spread = 0.04 + (1.0 - score) * 0.04
        return hz.quotes(fair=score, spread=spread, size=5)

    # Verify hz.Context type exists
    assert hasattr(hz, "Context")
    # Verify hz.quotes helper
    q = hz.quotes(fair=0.5, spread=0.04, size=5)
    assert isinstance(q, list), f"Expected list, got {type(q)}"
    print(f"  quoter function and hz.quotes verified OK, quotes={q}")


def test_signals_custom_signal_with_combiner():
    """signals.mdx - Custom Signal with Built-in Combiner"""
    import horizon as hz
    from horizon.signals import Signal
    from horizon.context import FeedData

    def my_model(ctx):
        price = ctx.feeds.get("btc", FeedData()).price or 0.5
        return min(max(price * 1.1 - 0.05, 0.0), 1.0)

    custom = Signal(name="my_model", fn=my_model, weight=2.0)
    assert custom.name == "my_model"
    assert custom.weight == 2.0
    assert callable(custom.fn)

    combiner = hz.signal_combiner([
        custom,
        hz.price_signal("book", weight=1.0),
        hz.spread_signal("book", weight=0.5),
    ])
    assert callable(combiner)
    print("  Custom signal with combiner constructed OK")


def test_signals_signal_kelly_example():
    """signals.mdx - Signal + Kelly Sizing example (verify construction)"""
    import horizon as hz

    # The docs show: hz.kelly_sizer(fraction=0.25, bankroll=1000)
    # This should work as the pipeline factory signature
    sizer = hz.kelly_sizer(fraction=0.25, bankroll=1000)
    assert callable(sizer), f"Expected callable, got {type(sizer)}"
    print("  signal+kelly pipeline construction OK")


def test_signals_signal_mm_example():
    """signals.mdx - Signal + Market Maker example (verify construction)"""
    import horizon as hz

    mm = hz.market_maker(feed_name="book", gamma=0.5, size=5.0)
    assert callable(mm), f"Expected callable, got {type(mm)}"
    print("  signal+market_maker pipeline construction OK")


def test_signals_signal_dataclass():
    """signals.mdx - Signal dataclass from horizon.signals"""
    from horizon.signals import Signal

    def my_function(ctx):
        return 0.5

    s = Signal(
        name="my_signal",
        fn=my_function,
        weight=1.0,
    )
    assert s.name == "my_signal"
    assert s.weight == 1.0
    assert callable(s.fn)
    assert s.fn(None) == 0.5
    print("  Signal dataclass OK")


# ============================================================================
# Run all tests
# ============================================================================

if __name__ == "__main__":
    print("=" * 70)
    print("Testing code snippets from kelly.mdx, quant.mdx, signals.mdx")
    print("=" * 70)

    # kelly.mdx tests
    print("\n--- kelly.mdx ---")
    run_test("kelly.mdx: hz.edge()", test_kelly_edge)
    run_test("kelly.mdx: hz.kelly()", test_kelly_full)
    run_test("kelly.mdx: hz.kelly_no()", test_kelly_no)
    run_test("kelly.mdx: hz.fractional_kelly()", test_fractional_kelly)
    run_test("kelly.mdx: hz.kelly_size()", test_kelly_size)
    run_test("kelly.mdx: hz.multi_kelly()", test_multi_kelly)
    run_test("kelly.mdx: hz.liquidity_adjusted_kelly()", test_liquidity_adjusted_kelly)
    run_test("kelly.mdx: hz.kelly_sizer() direct call (docs style)", test_kelly_sizer_pipeline_helper)
    run_test("kelly.mdx: hz.kelly_sizer_with_liquidity() direct call (docs style)", test_kelly_sizer_with_liquidity_pipeline_helper)
    run_test("kelly.mdx: Basic Kelly Sizing example", test_kelly_basic_sizing_example)
    run_test("kelly.mdx: Multi-Position Portfolio example", test_kelly_multi_position_portfolio)
    run_test("kelly.mdx: Liquidity-Aware Sizing example", test_kelly_liquidity_aware_sizing)
    run_test("kelly.mdx: Kelly in a Pipeline (construction)", test_kelly_pipeline_example)
    run_test("kelly.mdx: Comparing Kelly Fractions example", test_kelly_comparing_fractions)

    # quant.mdx tests
    print("\n--- quant.mdx ---")
    run_test("quant.mdx: Information Theory (entropy, KL)", test_quant_information_theory)
    run_test("quant.mdx: mutual_information()", test_quant_mutual_information)
    run_test("quant.mdx: transfer_entropy()", test_quant_transfer_entropy)
    run_test("quant.mdx: Microstructure functions", test_quant_microstructure)
    run_test("quant.mdx: Cornish-Fisher VaR/CVaR", test_quant_cornish_fisher)
    run_test("quant.mdx: prediction_greeks()", test_quant_prediction_greeks)
    run_test("quant.mdx: Signal analysis (IC, half-life, Hurst, VR)", test_quant_signal_analysis)
    run_test("quant.mdx: deflated_sharpe + bonferroni + BH", test_quant_deflated_sharpe)
    run_test("quant.mdx: VpinDetector", test_quant_vpin_detector)
    run_test("quant.mdx: CusumDetector", test_quant_cusum_detector)
    run_test("quant.mdx: OfiTracker", test_quant_ofi_tracker)
    run_test("quant.mdx: toxic_flow pipeline", test_quant_pipeline_toxic_flow)
    run_test("quant.mdx: microstructure pipeline", test_quant_pipeline_microstructure)
    run_test("quant.mdx: change_detector pipeline", test_quant_pipeline_change_detector)
    run_test("quant.mdx: strategy_significance()", test_quant_strategy_significance)
    run_test("quant.mdx: signal_diagnostics()", test_quant_signal_diagnostics)
    run_test("quant.mdx: market_efficiency()", test_quant_market_efficiency)
    run_test("quant.mdx: stress_test with SimPosition", test_quant_stress_test)
    run_test("quant.mdx: Custom StressScenario", test_quant_custom_stress_scenario)
    run_test("quant.mdx: CPCV", test_quant_cpcv)

    # signals.mdx tests
    print("\n--- signals.mdx ---")
    run_test("signals.mdx: hz.combine_signals()", test_signals_combine_signals)
    run_test("signals.mdx: hz.ema()", test_signals_ema)
    run_test("signals.mdx: hz.zscore()", test_signals_zscore)
    run_test("signals.mdx: hz.decay_weight()", test_signals_decay_weight)
    run_test("signals.mdx: signal_combiner factory", test_signals_signal_combiner_factory)
    run_test("signals.mdx: price_signal()", test_signals_price_signal)
    run_test("signals.mdx: spread_signal()", test_signals_spread_signal)
    run_test("signals.mdx: momentum_signal()", test_signals_momentum_signal)
    run_test("signals.mdx: flow_signal()", test_signals_flow_signal)
    run_test("signals.mdx: imbalance_signal()", test_signals_imbalance_signal)
    run_test("signals.mdx: Basic Signal Combination (imports+quotes)", test_signals_basic_combination_example)
    run_test("signals.mdx: Custom Signal with combiner", test_signals_custom_signal_with_combiner)
    run_test("signals.mdx: Signal + Kelly Sizing (construction)", test_signals_signal_kelly_example)
    run_test("signals.mdx: Signal + Market Maker (construction)", test_signals_signal_mm_example)
    run_test("signals.mdx: Signal dataclass", test_signals_signal_dataclass)

    # Summary
    print("\n" + "=" * 70)
    print("SUMMARY")
    print("=" * 70)
    passed = sum(1 for r in results if r[0] == "PASS")
    failed = sum(1 for r in results if r[0] == "FAIL")
    total = len(results)
    print(f"Total: {total}  |  PASS: {passed}  |  FAIL: {failed}")

    if failed > 0:
        print("\nFailed tests:")
        for r in results:
            if r[0] == "FAIL":
                print(f"  FAIL: {r[1]}")
                print(f"        Error: {r[2]}")

    print()
