// Bar10s defined inline (strategy.ts archived)
interface Bar10s { o: number; h: number; l: number; c: number; buyVol: number; sellVol: number; n: number; exVol: Record<string, number> }

/**
 * Risk sizing analysis: sweep riskPct from 0.5% to 20%.
 * For each: max drawdown, recovery time, ruin probability via Monte Carlo.
 */
import fs from 'node:fs'
import readline from 'node:readline'


interface RBar extends Bar10s { ts: number }

// ── Helpers ──
function tTest(v: number[]): { mean: number; t: number; n: number } {
  const n = v.length; if (n < 3) return { mean: 0, t: 0, n }
  const m = v.reduce((a, b) => a + b, 0) / n
  const se = Math.sqrt(v.reduce((s, x) => s + (x - m) ** 2, 0) / (n - 1) / n)
  return { mean: m, t: se > 0 ? m / se : 0, n }
}

function pct(arr: number[], p: number) {
  const s = [...arr].sort((a, b) => a - b)
  return s[Math.min(Math.floor(s.length * p), s.length - 1)]
}

async function loadAll(): Promise<Map<string, RBar[]>> {
  const sessions = new Map<string, RBar[]>()
  const histDir = 'data/historical'
  for (const date of fs.readdirSync(histDir).filter(d => {
    try { return fs.statSync(`${histDir}/${d}/trades.jsonl`).size > 100_000 } catch { return false }
  }).sort()) {
    process.stderr.write(`Loading ${date}...`)
    const m = new Map<number, RBar>()
    const rl = readline.createInterface({ input: fs.createReadStream(`${histDir}/${date}/trades.jsonl`) })
    for await (const line of rl) {
      const d = JSON.parse(line)
      const k = Math.floor(d.ts / 10000), p = d.data.price, vol = d.data.notionalUsd, side = d.data.side, ex = d.data.exchange
      const e = m.get(k)
      if (!e) m.set(k, { ts: k*10000, o:p, h:p, l:p, c:p, buyVol: side==='buy'?vol:0, sellVol: side==='sell'?vol:0, n:1, exVol:{[ex]:vol} })
      else { e.h=Math.max(e.h,p); e.l=Math.min(e.l,p); e.c=p; if(side==='buy') e.buyVol+=vol; else e.sellVol+=vol; e.n++; e.exVol[ex]=(e.exVol[ex]||0)+vol }
    }
    sessions.set(date, [...m.values()].sort((a, b) => a.ts - b.ts))
    process.stderr.write(` ${sessions.get(date)!.length}\n`)
  }
  return sessions
}

// ── Extract raw trades (grossBps) from momentum signal ──
function extractTrades(bars: RBar[], cfg = { lb: 360, hold: 180, thresh: 5, feeBps: 2, cooldown: 183 }): number[] {
  const grossBps: number[] = []
  let lastExit = -cfg.cooldown
  for (let i = Math.max(cfg.lb, 60); i < bars.length - cfg.hold; i++) {
    if (i - lastExit < cfg.cooldown) continue
    const ref = bars[i - cfg.lb].c; if (ref <= 0) continue
    const mom = (bars[i].c - ref) / ref * 10000
    if (Math.abs(mom) < cfg.thresh) continue
    const side = mom > 0 ? 1 : -1
    const exit = bars[i + cfg.hold].c
    const gross = side * (exit - bars[i].c) / bars[i].c * 10000
    grossBps.push(gross)
    lastExit = i + cfg.hold
  }
  return grossBps
}

// ── Equity curve simulation ──
function equityCurve(
  grossBpsTrades: number[],
  startCapital: number,
  riskPct: number,
  leverage: number,
  feeBps: number,
  stopBps: number   // for position sizing (catastrophe stop distance)
): { curve: number[]; maxDdUsd: number; maxDdPct: number; finalEquity: number; ruined: boolean; ruinAt: number } {
  const stopDist = stopBps / 10000
  let eq = startCapital
  let peak = eq
  let maxDdUsd = 0, maxDdPct = 0
  let ruined = false, ruinAt = -1
  const curve = [eq]

  for (let i = 0; i < grossBpsTrades.length; i++) {
    const gross = grossBpsTrades[i]
    const net = gross - 2 * feeBps

    const riskUsd = eq * riskPct
    const notional = Math.min(riskUsd / stopDist, eq * leverage)
    const pnlUsd = notional * (net / 10000)
    eq += pnlUsd

    if (eq > peak) peak = eq
    const dd = peak - eq
    if (dd > maxDdUsd) maxDdUsd = dd
    const ddPct = peak > 0 ? dd / peak : 0
    if (ddPct > maxDdPct) maxDdPct = ddPct

    // Ruin = equity below 20% of start (can't meaningfully trade)
    if (!ruined && eq < startCapital * 0.20) { ruined = true; ruinAt = i }

    curve.push(Math.max(0, eq))
  }

  return { curve, maxDdUsd, maxDdPct, finalEquity: eq, ruined, ruinAt }
}

// ── Monte Carlo: shuffle trade order N times, compute DD distribution ──
function monteCarlo(
  grossBps: number[],
  startCapital: number,
  riskPct: number,
  leverage: number,
  feeBps: number,
  stopBps: number,
  iterations = 5000
): { p50Dd: number; p75Dd: number; p90Dd: number; p99Dd: number; ruinPct: number; p50Final: number; p10Final: number } {
  const stopDist = stopBps / 10000
  const dds: number[] = [], finals: number[] = []
  let ruins = 0

  for (let iter = 0; iter < iterations; iter++) {
    // Fisher-Yates shuffle
    const trades = [...grossBps]
    for (let i = trades.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [trades[i], trades[j]] = [trades[j], trades[i]]
    }

    let eq = startCapital, peak = eq, maxDdPct = 0, ruined = false
    for (const gross of trades) {
      const net = gross - 2 * feeBps
      const notional = Math.min(eq * riskPct / stopDist, eq * leverage)
      eq += notional * (net / 10000)
      if (eq > peak) peak = eq
      const ddPct = peak > 0 ? (peak - eq) / peak : 0
      if (ddPct > maxDdPct) maxDdPct = ddPct
      if (!ruined && eq < startCapital * 0.20) ruined = true
    }
    dds.push(maxDdPct)
    finals.push(eq)
    if (ruined) ruins++
  }

  dds.sort((a, b) => a - b); finals.sort((a, b) => a - b)
  return {
    p50Dd: pct(dds, 0.50),
    p75Dd: pct(dds, 0.75),
    p90Dd: pct(dds, 0.90),
    p99Dd: pct(dds, 0.99),
    ruinPct: ruins / iterations,
    p50Final: pct(finals, 0.50),
    p10Final: pct(finals, 0.10),
  }
}

// ── Recovery analysis ──
function recoveryTrades(drawdownUsd: number, avgNetUsd: number): number {
  if (avgNetUsd <= 0) return Infinity
  return Math.ceil(drawdownUsd / avgNetUsd)
}

async function main() {
  const sessions = await loadAll()

  // Collect all trades across all sessions
  const allGross: number[] = []
  const sessionGross = new Map<string, number[]>()
  for (const [name, bars] of sessions) {
    const trades = extractTrades(bars)
    sessionGross.set(name, trades)
    allGross.push(...trades)
  }

  const feeBps = 2
  const stopBps = 100
  const leverage = 100
  const startCapital = 500

  const netBps = allGross.map(g => g - 2 * feeBps)
  const tt = tTest(netBps)
  const wr = netBps.filter(r => r > 0).length / netBps.length

  console.log(`\n${'═'.repeat(90)}`)
  console.log('RISK SIZING ANALYSIS — MOMENTUM STRATEGY')
  console.log(`${'═'.repeat(90)}`)
  console.log()
  console.log(`Dataset: ${allGross.length} trades across ${sessions.size} days`)
  console.log(`Signal:  lb=60min hold=30min thresh=5bps fee=${feeBps}bps/side stop=${stopBps}bps (sizing only)`)
  console.log(`Mean net: ${tt.mean.toFixed(2)}bps/trade  WR: ${(wr * 100).toFixed(0)}%  t=${tt.t.toFixed(2)}`)
  console.log()

  // ── Max consecutive losses ──
  let maxStreak = 0, streak = 0
  for (const n of netBps) { if (n <= 0) { streak++; if (streak > maxStreak) maxStreak = streak } else streak = 0 }
  console.log(`Max consecutive losses in historical data: ${maxStreak}`)
  console.log(`Worst single trade: ${Math.min(...netBps).toFixed(1)}bps  Best: ${Math.max(...netBps).toFixed(1)}bps`)
  console.log()

  // ── Per-session loss streaks ──
  console.log('Per-session worst streaks:')
  for (const [name, gross] of sessionGross) {
    const nets = gross.map(g => g - 2 * feeBps)
    let ms = 0, s = 0
    for (const n of nets) { if (n <= 0) { s++; if (s > ms) ms = s } else s = 0 }
    const cum = nets.reduce((a, b) => a + b, 0)
    console.log(`  ${name}  trades=${nets.length} cumBps=${cum.toFixed(0).padStart(6)} maxStreak=${ms}`)
  }
  console.log()

  // ── Main sweep ──
  console.log(`${'─'.repeat(90)}`)
  console.log('SEQUENTIAL BACKTEST (trades in historical order, compounding)')
  console.log()
  console.log('riskPct   finalEq    return   maxDD$    maxDD%   recov-trades  recov-days  survive?')
  console.log('─'.repeat(90))

  const btcPrice = 70000
  const tradesPerDay = allGross.length / sessions.size

  for (const riskPct of [0.005, 0.01, 0.015, 0.02, 0.03, 0.05, 0.075, 0.10, 0.15, 0.20]) {
    const { finalEquity, maxDdUsd, maxDdPct, ruined, ruinAt } = equityCurve(
      allGross, startCapital, riskPct, leverage, feeBps, stopBps
    )

    // Average net $ per trade at the start equity (conservative: use startCapital)
    const avgNotional = Math.min(startCapital * riskPct / (stopBps / 10000), startCapital * leverage)
    const avgNetUsd = avgNotional * (tt.mean / 10000)
    const recovTrades = recoveryTrades(maxDdUsd, avgNetUsd)
    const recovDays = recovTrades / tradesPerDay

    const ret = ((finalEquity - startCapital) / startCapital * 100).toFixed(0)
    const ruinStr = ruined ? ` !! RUIN at trade ${ruinAt}` : '  ✓'

    console.log(
      `  ${(riskPct * 100).toFixed(1).padStart(4)}%` +
      `   $${finalEquity.toFixed(0).padStart(7)}` +
      `   ${(ret + '%').padStart(7)}` +
      `   $${maxDdUsd.toFixed(0).padStart(6)}` +
      `   ${(maxDdPct * 100).toFixed(1).padStart(5)}%` +
      `   ${String(isFinite(recovTrades) ? recovTrades : '∞').padStart(12)}` +
      `   ${String(isFinite(recovDays) ? recovDays.toFixed(1) : '∞').padStart(8)}d` +
      ruinStr
    )
  }

  // ── Monte Carlo sweep ──
  console.log()
  console.log(`${'─'.repeat(90)}`)
  console.log('MONTE CARLO (5000 random orderings of same trades)')
  console.log('Shows worst-case DD distribution regardless of which day was bad first')
  console.log()
  console.log('riskPct   DD_p50   DD_p75   DD_p90   DD_p99   ruin%   finalEq_p50   finalEq_p10')
  console.log('─'.repeat(90))

  for (const riskPct of [0.01, 0.02, 0.03, 0.05, 0.075, 0.10, 0.15, 0.20]) {
    const mc = monteCarlo(allGross, startCapital, riskPct, leverage, feeBps, stopBps, 5000)
    console.log(
      `  ${(riskPct * 100).toFixed(1).padStart(4)}%` +
      `   ${(mc.p50Dd * 100).toFixed(1).padStart(6)}%` +
      `   ${(mc.p75Dd * 100).toFixed(1).padStart(6)}%` +
      `   ${(mc.p90Dd * 100).toFixed(1).padStart(6)}%` +
      `   ${(mc.p99Dd * 100).toFixed(1).padStart(6)}%` +
      `   ${(mc.ruinPct * 100).toFixed(1).padStart(5)}%` +
      `   $${mc.p50Final.toFixed(0).padStart(9)}` +
      `   $${mc.p10Final.toFixed(0).padStart(9)}`
    )
  }

  // ── Worst-case run analysis ──
  console.log()
  console.log(`${'─'.repeat(90)}`)
  console.log('WORST-CASE STREAK IMPACT at different risk levels')
  console.log(`Max observed losing streak: ${maxStreak} trades`)
  console.log()
  console.log('riskPct   streak-loss$   streak-loss%   eq-after-streak   trades-to-recover')
  console.log('─'.repeat(90))

  for (const riskPct of [0.01, 0.02, 0.03, 0.05, 0.075, 0.10, 0.15, 0.20]) {
    // Simulate the max streak against equity using Kelly-style decay
    let eq = startCapital
    const avgLoss = Math.abs(pct(netBps.filter(n => n <= 0), 0.50))  // median loss in bps

    for (let i = 0; i < maxStreak; i++) {
      const notional = Math.min(eq * riskPct / (stopBps / 10000), eq * leverage)
      eq -= notional * (avgLoss / 10000)
    }

    const streakLossUsd = startCapital - eq
    const streakLossPct = streakLossUsd / startCapital * 100

    // Recovery: from eq, how many avg-net trades to get back to startCapital
    const avgNetUsd = Math.min(eq * riskPct / (stopBps / 10000), eq * leverage) * (tt.mean / 10000)
    const recov = Math.ceil(streakLossUsd / Math.max(avgNetUsd, 0.01))
    const recovDays = recov / tradesPerDay

    console.log(
      `  ${(riskPct * 100).toFixed(1).padStart(4)}%` +
      `   $${streakLossUsd.toFixed(0).padStart(12)}` +
      `   ${streakLossPct.toFixed(1).padStart(12)}%` +
      `   $${eq.toFixed(0).padStart(15)}` +
      `   ${recov} trades (~${recovDays.toFixed(1)}d)`
    )
  }

  // ── Kelly criterion ──
  console.log()
  console.log(`${'─'.repeat(90)}`)
  const avgWin = pct(netBps.filter(n => n > 0), 0.50)
  const avgLoss2 = Math.abs(pct(netBps.filter(n => n <= 0), 0.50))
  const kelly = wr - (1 - wr) / (avgWin / avgLoss2)
  const halfKelly = kelly / 2

  // Convert Kelly fraction (of bankroll) to riskPct
  // Kelly fraction = riskPct * (avgWin/stopBps) approximately
  // Full derivation: kelly × stopBps / avgWin
  const kellyRiskPct = kelly * (stopBps / 10000) / (avgWin / 10000) * (stopBps / 10000)

  console.log('KELLY CRITERION')
  console.log(`  WR=${(wr*100).toFixed(0)}%  avgWin=${avgWin.toFixed(1)}bps  avgLoss=${avgLoss2.toFixed(1)}bps  W/L ratio=${(avgWin/avgLoss2).toFixed(2)}`)
  console.log(`  Full Kelly fraction:   ${(kelly*100).toFixed(1)}% of bankroll per trade`)
  console.log(`  Half Kelly (safer):    ${(halfKelly*100).toFixed(1)}% of bankroll per trade`)
  console.log()
  console.log('  NOTE: Kelly fraction here is the fraction of bankroll to risk ON THE LOSS.')
  console.log(`  At 2% risk with ${stopBps}bps stop: max loss per trade = 2% of equity = Kelly input.`)
  console.log(`  Full Kelly → risk ${(kelly*100).toFixed(1)}% per trade (very aggressive for live trading)`)
  console.log(`  Half Kelly → risk ${(halfKelly*100).toFixed(1)}% per trade (recommended maximum)`)
  console.log()

  // ── Recommendation ──
  console.log(`${'═'.repeat(90)}`)
  console.log('RECOMMENDATION')
  console.log(`${'═'.repeat(90)}`)

  const scenarios = [
    { label: 'Conservative',  riskPct: 0.01 },
    { label: 'Current',       riskPct: 0.02 },
    { label: 'Aggressive',    riskPct: 0.05 },
    { label: 'Max-Kelly/2',   riskPct: Math.min(halfKelly * (stopBps / 10000), 0.10) },
  ]

  for (const s of scenarios) {
    const { finalEquity, maxDdUsd, maxDdPct } = equityCurve(
      allGross, startCapital, s.riskPct, leverage, feeBps, stopBps
    )
    const mc = monteCarlo(allGross, startCapital, s.riskPct, leverage, feeBps, stopBps, 2000)
    const ret = ((finalEquity - startCapital) / startCapital * 100)
    console.log(`\n  ${s.label.padEnd(14)} (risk=${(s.riskPct * 100).toFixed(1)}%):`)
    console.log(`    Return on 10 days:  $${startCapital} → $${finalEquity.toFixed(0)} (+${ret.toFixed(0)}%)`)
    console.log(`    Sequential max DD:  $${maxDdUsd.toFixed(0)} (${(maxDdPct * 100).toFixed(1)}%)`)
    console.log(`    MC p90 worst DD:    ${(mc.p90Dd * 100).toFixed(1)}%`)
    console.log(`    MC ruin prob:       ${(mc.ruinPct * 100).toFixed(1)}%  (equity < $${(startCapital * 0.20).toFixed(0)})`)
    const avgNetUsd = Math.min(startCapital * s.riskPct / (stopBps / 10000), startCapital * leverage) * (tt.mean / 10000)
    console.log(`    Avg net per trade:  $${avgNetUsd.toFixed(2)}`)
    console.log(`    Recovery from DD:   ~${Math.ceil(maxDdUsd / avgNetUsd)} trades (~${(Math.ceil(maxDdUsd / avgNetUsd) / tradesPerDay).toFixed(1)} days)`)
  }
  console.log()
}

main().catch(e => { process.stderr.write(`FATAL: ${e instanceof Error ? e.stack : e}\n`); process.exit(1) })
