"""
01_statistical_validation.py

PURPOSE
-------
Answer the only question that matters: is the current run of losses within
normal distribution for the documented model, or is something genuinely wrong?

We do NOT have a backtest script we can re-run. So instead we use the model's
own documented statistics and ask: how often does a 23-year backtest with these
properties produce a sequence as bad (or worse) than what we've seen live?

INPUTS
------
You'll be prompted for the actual live trade results so far. Have these ready:
  - Number of EURUSD trades since deployment
  - Number of USDJPY trades since deployment
  - For each trade: pair, date, P&L in dollars, sizing tier (1.0x/1.5x/2.0x)

DOCUMENTED MODEL STATS (from v5 doc Section 1.3 and Section 5.4):
  EURUSD: 75.2% WR, ~182 trades/year, avg trade +0.000737 (real costs)
  USDJPY: 72.1% WR, ~31 trades/year, avg trade +0.00411 (real costs)
  Combined Sharpe (dynamic tiers): 3.85
  Worst calendar year DD: -7.76% (2022, dynamic tiers, $100k fresh)

DEPENDENCIES
------------
  pip install numpy pandas

USAGE
-----
  python 01_statistical_validation.py

OUTPUT
------
  - Probability the live result is consistent with documented model
  - Probability of seeing N losses in first M trades under model assumptions
  - Comparison of live tier-loss magnitudes vs documented expectations
  - Verdict: WITHIN NORMAL / TAIL EVENT / INCONSISTENT WITH MODEL
"""

import numpy as np
from scipy import stats
import sys

# ---------------------------------------------------------------
# DOCUMENTED MODEL PARAMETERS (from v5.1 doc)
# ---------------------------------------------------------------
EU_WR = 0.752
EU_TRADES_PER_YEAR = 182
EU_AVG_RETURN = 0.000737  # real costs

UJ_WR = 0.721
UJ_TRADES_PER_YEAR = 31
UJ_AVG_RETURN = 0.00411   # real costs

# Per-tier WR from v5 cover page (combined trade count)
TIER_WR = {
    "EU_1.0x": 0.721, "EU_1.5x": 0.805, "EU_2.0x": 0.914,
    "UJ_1.0x": 0.665, "UJ_1.5x": 0.794, "UJ_2.0x": 0.895,
}

# Dollar loss per stop at $100k (from v5 Section 6.4)
TIER_LOSS = {
    "EU_1.0x": 722,  "EU_1.5x": 1083, "EU_2.0x": 1444,
    "UJ_1.0x": 1110, "UJ_1.5x": 1666, "UJ_2.0x": 2221,
}


# ---------------------------------------------------------------
# INPUT GATHERING
# ---------------------------------------------------------------
def get_live_trades():
    """
    Prompts user for live trade data.
    Returns list of dicts: [{'pair', 'date', 'pnl', 'tier', 'won'}, ...]
    """
    print("=" * 70)
    print("LIVE TRADE INPUT")
    print("=" * 70)
    print("Enter each live trade since April 11 deployment.")
    print("Format: pair,YYYY-MM-DD,pnl_dollars,tier")
    print("  pair = EU or UJ")
    print("  pnl  = positive for win, negative for loss")
    print("  tier = 1.0, 1.5, or 2.0")
    print("Example: UJ,2026-04-30,-1262.64,1.5")
    print("Type 'done' when finished.\n")

    trades = []
    while True:
        line = input(f"Trade #{len(trades) + 1}: ").strip()
        if line.lower() in ("done", "d", ""):
            break
        try:
            parts = line.split(",")
            pair = parts[0].strip().upper()
            date = parts[1].strip()
            pnl = float(parts[2].strip())
            tier = float(parts[3].strip())
            trades.append({
                "pair": pair,
                "date": date,
                "pnl": pnl,
                "tier": tier,
                "won": pnl > 0,
            })
            print(f"   logged: {pair} {date} ${pnl:+.2f} {tier}x")
        except (IndexError, ValueError) as e:
            print(f"   parse error ({e}), try again")

    return trades


# ---------------------------------------------------------------
# TEST 1: BINOMIAL — losing streak probability
# ---------------------------------------------------------------
def test_losing_streak(trades):
    """
    Given the documented win rate, what's the probability of seeing
    AT LEAST as many losses as we've seen in the live sample?
    """
    print("\n" + "=" * 70)
    print("TEST 1: BINOMIAL — Loss Count Probability")
    print("=" * 70)

    eu_trades = [t for t in trades if t["pair"] == "EU"]
    uj_trades = [t for t in trades if t["pair"] == "UJ"]

    for label, trade_list, wr in [
        ("EURUSD", eu_trades, EU_WR),
        ("USDJPY", uj_trades, UJ_WR),
        ("COMBINED", trades, (EU_WR * len(eu_trades) + UJ_WR * len(uj_trades))
            / max(len(trades), 1)),
    ]:
        n = len(trade_list)
        if n == 0:
            print(f"\n{label}: no trades to evaluate")
            continue
        losses = sum(1 for t in trade_list if not t["won"])
        # P(losses >= observed) under null (model's WR is correct)
        p_at_least = 1 - stats.binom.cdf(losses - 1, n, 1 - wr)
        expected_losses = n * (1 - wr)
        print(f"\n{label}:")
        print(f"  Trades: {n}")
        print(f"  Losses: {losses} (expected under model: {expected_losses:.2f})")
        print(f"  P(losses >= {losses} | model WR {wr:.1%}) = {p_at_least:.3f}")
        if p_at_least > 0.10:
            print(f"  Verdict: WITHIN NORMAL — well inside expected variance")
        elif p_at_least > 0.05:
            print(f"  Verdict: TAIL — uncommon but not exceptional (5-10% range)")
        elif p_at_least > 0.01:
            print(f"  Verdict: TAIL — significant deviation (1-5%)")
        else:
            print(f"  Verdict: INCONSISTENT — < 1% probability under model")


# ---------------------------------------------------------------
# TEST 2: TRADE FREQUENCY — are signals firing at expected rate?
# ---------------------------------------------------------------
def test_frequency(trades, days_live):
    """
    The doc claims ~182 EU and ~31 UJ trades/year on average.
    What does live show?
    """
    print("\n" + "=" * 70)
    print("TEST 2: TRADE FREQUENCY — Live vs Documented")
    print("=" * 70)

    eu_trades = sum(1 for t in trades if t["pair"] == "EU")
    uj_trades = sum(1 for t in trades if t["pair"] == "UJ")

    # Expected over this period
    eu_expected = EU_TRADES_PER_YEAR * (days_live / 365)
    uj_expected = UJ_TRADES_PER_YEAR * (days_live / 365)

    # Poisson — how unusual is observed count given expected?
    # (Assumes uniform arrival rate, which IS NOT TRUE for macro signals,
    # but gives a useful first-pass indicator.)
    eu_p = stats.poisson.cdf(eu_trades, eu_expected)
    uj_p = stats.poisson.cdf(uj_trades, uj_expected)

    print(f"\nLive deployment window: {days_live} days")
    print(f"\nEURUSD:")
    print(f"  Documented rate: {EU_TRADES_PER_YEAR}/year = {eu_expected:.2f} expected over {days_live}d")
    print(f"  Live count: {eu_trades}")
    print(f"  Ratio: {eu_trades / max(eu_expected, 0.01):.2%} of expected")
    print(f"  Poisson P(X <= {eu_trades} | lambda={eu_expected:.2f}) = {eu_p:.4f}")
    if eu_p < 0.05:
        print(f"  Verdict: SIGNIFICANTLY FEWER signals than documented")
    elif eu_p < 0.20:
        print(f"  Verdict: BELOW EXPECTED but within normal variance")
    else:
        print(f"  Verdict: WITHIN NORMAL frequency range")

    print(f"\nUSDJPY:")
    print(f"  Documented rate: {UJ_TRADES_PER_YEAR}/year = {uj_expected:.2f} expected over {days_live}d")
    print(f"  Live count: {uj_trades}")
    print(f"  Ratio: {uj_trades / max(uj_expected, 0.01):.2%} of expected")
    print(f"  Poisson P(X <= {uj_trades} | lambda={uj_expected:.2f}) = {uj_p:.4f}")
    if uj_p < 0.05:
        print(f"  Verdict: SIGNIFICANTLY FEWER signals than documented")
    elif uj_p < 0.20:
        print(f"  Verdict: BELOW EXPECTED but within normal variance")
    else:
        print(f"  Verdict: WITHIN NORMAL frequency range")


# ---------------------------------------------------------------
# TEST 3: LOSS MAGNITUDE — are stops hitting at documented levels?
# ---------------------------------------------------------------
def test_loss_magnitude(trades):
    """
    Compare actual dollar losses on losing trades against doc's stop costs
    by tier. Catches cases where stops are slipping wider than expected.
    """
    print("\n" + "=" * 70)
    print("TEST 3: LOSS MAGNITUDE — Live Stops vs Documented")
    print("=" * 70)

    losers = [t for t in trades if not t["won"]]
    if not losers:
        print("\nNo losing trades to evaluate.")
        return

    print(f"\n{'Trade':<25} {'Live Loss':>12} {'Doc Stop':>10} {'Ratio':>10}")
    print("-" * 60)
    for t in losers:
        key = f"{t['pair']}_{t['tier']}x"
        doc_stop = TIER_LOSS.get(key, None)
        if doc_stop is None:
            print(f"{t['pair']} {t['date']} {t['tier']}x | unknown tier")
            continue
        ratio = abs(t["pnl"]) / doc_stop
        flag = ""
        if ratio > 1.20:
            flag = " ⚠ WIDER than documented"
        elif ratio < 0.80:
            flag = " ✓ tighter than documented"
        else:
            flag = " ✓ within ±20% of doc"
        print(f"{t['pair']} {t['date']} {t['tier']}x  ${abs(t['pnl']):>9.2f}  ${doc_stop:>7}  {ratio:.2f}x  {flag}")


# ---------------------------------------------------------------
# TEST 4: AGGREGATE — is total live P&L within Monte Carlo bounds?
# ---------------------------------------------------------------
def test_aggregate_pnl(trades, days_live):
    """
    Run a quick Monte Carlo: simulate N trade sequences using the documented
    win rates, average returns, and trade frequency. Where does live P&L
    sit in the distribution?
    """
    print("\n" + "=" * 70)
    print("TEST 4: AGGREGATE P&L — Monte Carlo over deployment window")
    print("=" * 70)

    live_pnl = sum(t["pnl"] for t in trades)

    # Simulate 10,000 windows of length days_live
    n_sims = 10000
    sim_results = []

    for _ in range(n_sims):
        # Sample number of trades from Poisson
        n_eu = np.random.poisson(EU_TRADES_PER_YEAR * days_live / 365)
        n_uj = np.random.poisson(UJ_TRADES_PER_YEAR * days_live / 365)

        # For each EU trade: WR 75.2%, avg return 0.0737% on $300k notional
        # = +$221 avg per trade. Loss avg = stop ~$722.
        # Simulate using win/loss bernoulli with avg win/loss magnitudes
        eu_wins = np.random.binomial(n_eu, EU_WR)
        eu_losses = n_eu - eu_wins
        # Approximate: $300k notional, TP 0.20% = $600 win, SL 0.25% = $750 loss
        eu_pnl = eu_wins * 600 - eu_losses * 750

        uj_wins = np.random.binomial(n_uj, UJ_WR)
        uj_losses = n_uj - uj_wins
        # $300k notional UJ, TP 0.70% = $2100 win, SL 0.40% = $1200 loss
        uj_pnl = uj_wins * 2100 - uj_losses * 1200

        sim_results.append(eu_pnl + uj_pnl)

    sim_results = np.array(sim_results)
    pct = (sim_results < live_pnl).mean()

    print(f"\nLive total P&L over {days_live} days: ${live_pnl:+.2f}")
    print(f"\nMonte Carlo distribution (10,000 sims, doc parameters):")
    print(f"  5th percentile:   ${np.percentile(sim_results, 5):+.2f}")
    print(f"  25th percentile:  ${np.percentile(sim_results, 25):+.2f}")
    print(f"  Median:           ${np.percentile(sim_results, 50):+.2f}")
    print(f"  75th percentile:  ${np.percentile(sim_results, 75):+.2f}")
    print(f"  95th percentile:  ${np.percentile(sim_results, 95):+.2f}")
    print(f"\nLive P&L sits at the {pct:.1%} percentile of simulated outcomes.")

    if pct < 0.05:
        print(f"Verdict: TAIL EVENT — only {pct:.1%} of simulations were this bad or worse")
    elif pct < 0.20:
        print(f"Verdict: BELOW MEDIAN but within normal variance")
    else:
        print(f"Verdict: WITHIN NORMAL range")


# ---------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------
if __name__ == "__main__":
    print("DUAL FX MACRO MODEL — Statistical Validation of Live Results")
    print("Comparing live to documented v5.1 model performance.\n")

    days_live = int(input("Days since live deployment (April 11 2026): "))
    trades = get_live_trades()

    if not trades:
        print("\nNo trades entered. Exiting.")
        sys.exit(0)

    print(f"\n{len(trades)} trades logged.")
    print(f"Total P&L: ${sum(t['pnl'] for t in trades):+.2f}")
    print(f"Wins: {sum(1 for t in trades if t['won'])} | Losses: {sum(1 for t in trades if not t['won'])}")

    test_losing_streak(trades)
    test_frequency(trades, days_live)
    test_loss_magnitude(trades)
    test_aggregate_pnl(trades, days_live)

    print("\n" + "=" * 70)
    print("OVERALL READING")
    print("=" * 70)
    print("""
The four tests above check different things:
  Test 1 (Binomial):    Is the win rate consistent with documented WR?
  Test 2 (Frequency):   Are signals firing at the expected rate?
  Test 3 (Magnitude):   Are losses sized as documented per tier?
  Test 4 (Aggregate):   Is total P&L within Monte Carlo bounds?

If all four say WITHIN NORMAL, current results are normal variance —
even if it doesn't feel that way. Statistically you cannot conclude
anything is wrong with the model from a small live sample.

If Test 1 or 4 says TAIL EVENT, you've hit unusual variance — possible
under the documented model but worth watching.

If Test 2 says SIGNIFICANTLY FEWER signals, the live frequency does
not match documented frequency — this points to either a regime issue
or a backtest accuracy problem.

If Test 3 shows stops WIDER than documented, execution is slipping
beyond the assumed cost model — investigate execution rather than model.
""")
