/**
 * Momentum strategy: parameter optimization + full backtest.
 *
 * Signal: 60-min price momentum → hold 30min continuation.
 * Tests: entry threshold, lookback, hold, stop loss, trailing stop.
 * Then runs best config with full PnL simulation on all 10 historical days.
 */
import fs from 'node:fs'
import readline from 'node:readline'
import type { Bar10s } from '../core/strategy.js'

// ── Types ──

interface RBar extends Bar10s { ts: number }

interface MomConfig {
  lookbackBars: number   // momentum computation window
  holdBars: number       // position hold time
  entryThreshBps: number // min |momentum| to enter
  stopBps: number        // stop loss from entry (0 = no stop)
  trailActBps: number    // trail activation (0 = no trail)
  trailDistPct: number   // trail distance as pct of peak excursion
  feeBps: number         // one-way fee (maker)
  cooldownBars: number   // min bars between trade exits and new entries
}

interface Trade {
  session: string
  entryBar: number; exitBar: number; holdBars: number
  side: 'long' | 'short'
  entryPrice: number; exitPrice: number
  grossBps: number; netBps: number
  exitReason: 'time' | 'stop' | 'trail'
  maxFavBps: number; maxAdvBps: number  // max favorable / adverse excursion
}

// ── Stats ──

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

function sig(t: number): string {
  const a = Math.abs(t); return a > 3.29 ? '***' : a > 2.58 ? '**' : a > 1.96 ? '*' : ''
}

// ── Data loading ──

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
}

// ── Simulation engine ──

function simulate(bars: RBar[], session: string, cfg: MomConfig): Trade[] {
  const trades: Trade[] = []
  let lastExit = -cfg.cooldownBars

  for (let i = Math.max(cfg.lookbackBars, 60); i < bars.length - cfg.holdBars; i++) {
    if (i - lastExit < cfg.cooldownBars) continue

    // Momentum signal
    const refPrice = bars[i - cfg.lookbackBars].c
    if (refPrice <= 0) continue
    const momBps = (bars[i].c - refPrice) / refPrice * 10000
    if (Math.abs(momBps) < cfg.entryThreshBps) continue

    const side: 'long' | 'short' = momBps > 0 ? 'long' : 'short'
    const entryPrice = bars[i].c

    // Walk forward through hold period with stop/trail logic
    let exitBar = i + cfg.holdBars
    let exitPrice = bars[exitBar].c
    let exitReason: 'time' | 'stop' | 'trail' = 'time'
    let peakFavBps = 0
    let maxAdvBps = 0
    let trailActive = false
    let trailStop = 0

    for (let j = i + 1; j <= i + cfg.holdBars && j < bars.length; j++) {
      const mid = bars[j].c
      const pnlBps = side === 'long'
        ? (mid - entryPrice) / entryPrice * 10000
        : (entryPrice - mid) / entryPrice * 10000

      if (pnlBps > peakFavBps) peakFavBps = pnlBps
      if (-pnlBps > maxAdvBps) maxAdvBps = -pnlBps

      // Stop loss
      if (cfg.stopBps > 0 && pnlBps <= -cfg.stopBps) {
        exitBar = j; exitPrice = mid; exitReason = 'stop'; break
      }

      // Trailing stop
      if (cfg.trailActBps > 0 && peakFavBps >= cfg.trailActBps) {
        trailActive = true
        const trailDist = peakFavBps * cfg.trailDistPct
        trailStop = peakFavBps - trailDist
      }
      if (trailActive && pnlBps <= trailStop) {
        exitBar = j; exitPrice = mid; exitReason = 'trail'; break
      }
    }

    if (exitBar >= bars.length) exitBar = bars.length - 1
    exitPrice = bars[exitBar].c

    const grossBps = side === 'long'
      ? (exitPrice - entryPrice) / entryPrice * 10000
      : (entryPrice - exitPrice) / entryPrice * 10000
    const netBps = grossBps - 2 * cfg.feeBps

    trades.push({
      session, entryBar: i, exitBar, holdBars: exitBar - i,
      side, entryPrice, exitPrice, grossBps, netBps,
      exitReason, maxFavBps: peakFavBps, maxAdvBps
    })
    lastExit = exitBar
  }
  return trades
}

// ── Reporting ──

function report(name: string, trades: Trade[], detail = false) {
  if (trades.length < 3) { console.log(`  ${name.padEnd(50)} n=${trades.length} (too few)`); return }
  const net = trades.map(t => t.netBps), gross = trades.map(t => t.grossBps)
  const tN = tTest(net), tG = tTest(gross)
  const wr = net.filter(r => r > 0).length / net.length * 100
  let maxDd = 0, cum = 0, peak = 0
  for (const r of net) { cum += r; if (cum > peak) peak = cum; maxDd = Math.max(maxDd, peak - cum) }
  const stops = trades.filter(t => t.exitReason === 'stop').length
  const trails = trades.filter(t => t.exitReason === 'trail').length
  const times = trades.filter(t => t.exitReason === 'time').length
  const avgHold = trades.reduce((s, t) => s + t.holdBars, 0) / trades.length * 10 / 60 // minutes

  console.log(`  ${name.padEnd(50)} n=${String(tN.n).padStart(4)} WR=${String(Math.round(wr)).padStart(2)}% net=${tN.mean.toFixed(1).padStart(6)}bps t=${tN.t.toFixed(2).padStart(5)} cum=${cum.toFixed(0).padStart(6)}bps maxDD=${maxDd.toFixed(0).padStart(5)}bps exits:T${times}/S${stops}/TR${trails} avgHold=${avgHold.toFixed(0)}min ${sig(tN.t)}`)

  if (detail) {
    const bySession = new Map<string, Trade[]>()
    for (const t of trades) { if (!bySession.has(t.session)) bySession.set(t.session, []); bySession.get(t.session)!.push(t) }
    let pos = 0, total = 0
    for (const [s, tr] of [...bySession.entries()].sort()) {
      const nets = tr.map(t => t.netBps)
      const cumS = nets.reduce((a, b) => a + b, 0)
      const wrS = nets.filter(r => r > 0).length / nets.length * 100
      total++; if (cumS > 0) pos++
      console.log(`    ${s} n=${String(tr.length).padStart(3)} WR=${String(Math.round(wrS)).padStart(2)}% cum=${cumS.toFixed(1).padStart(8)}bps exits:T${tr.filter(t=>t.exitReason==='time').length}/S${tr.filter(t=>t.exitReason==='stop').length}/TR${tr.filter(t=>t.exitReason==='trail').length}`)
    }
    console.log(`    Positive sessions: ${pos}/${total}`)
  }
}

// ── PnL simulation (realistic equity curve with leverage + sizing) ──

function equitySim(trades: Trade[], startCapital: number, leverage: number, riskPct: number, stopBps: number) {
  let equity = startCapital
  let peak = equity
  let maxDd = 0
  let maxDdPct = 0

  const equityCurve: number[] = [equity]
  for (const t of trades) {
    // Position size based on risk
    const riskUsd = equity * riskPct
    const stopDist = (stopBps > 0 ? stopBps : 50) / 10000 // default 50bps if no stop
    const positionNotional = Math.min(riskUsd / stopDist, equity * leverage)
    const positionBtc = positionNotional / t.entryPrice

    const pnlUsd = positionBtc * t.entryPrice * (t.netBps / 10000)
    equity += pnlUsd
    equityCurve.push(equity)

    if (equity > peak) peak = equity
    const dd = peak - equity
    if (dd > maxDd) maxDd = dd
    const ddPct = peak > 0 ? dd / peak * 100 : 0
    if (ddPct > maxDdPct) maxDdPct = ddPct
  }

  const totalReturn = (equity - startCapital) / startCapital * 100
  return { equity, totalReturn, maxDd, maxDdPct, trades: trades.length, equityCurve }
}

// ── Main ──

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

  console.log(`\n${'═'.repeat(110)}`)
  console.log(`MOMENTUM STRATEGY: PARAMETER OPTIMIZATION + BACKTEST`)
  console.log(`${'═'.repeat(110)}\n`)

  const BASE: MomConfig = {
    lookbackBars: 360, holdBars: 180, entryThreshBps: 10,
    stopBps: 0, trailActBps: 0, trailDistPct: 0,
    feeBps: 2, cooldownBars: 183
  }

  function runAll(cfg: MomConfig): Trade[] {
    const all: Trade[] = []
    for (const [name, bars] of sessions) all.push(...simulate(bars, name, cfg))
    return all
  }

  // ════════════════════════════════════════
  // PHASE 1: Sweep entry threshold
  // ════════════════════════════════════════
  console.log('PHASE 1: Entry threshold (lb=60min, hold=30min, no stop/trail)')
  for (const thresh of [3, 5, 7, 10, 15, 20, 30, 50]) {
    report(`thresh=${thresh}bps`, runAll({ ...BASE, entryThreshBps: thresh }))
  }

  // ════════════════════════════════════════
  // PHASE 2: Sweep lookback (fix thresh=10)
  // ════════════════════════════════════════
  console.log(`\n${'─'.repeat(110)}`)
  console.log('PHASE 2: Lookback period (thresh=10bps, hold=30min)')
  for (const lbMin of [15, 30, 45, 60, 90, 120, 180, 240]) {
    report(`lb=${lbMin}min`, runAll({ ...BASE, lookbackBars: lbMin * 6 }))
  }

  // ════════════════════════════════════════
  // PHASE 3: Sweep hold period (fix lb=60min, thresh=10)
  // ════════════════════════════════════════
  console.log(`\n${'─'.repeat(110)}`)
  console.log('PHASE 3: Hold period (lb=60min, thresh=10bps)')
  for (const holdMin of [10, 15, 20, 30, 45, 60, 90, 120]) {
    const hBars = holdMin * 6
    report(`hold=${holdMin}min`, runAll({ ...BASE, holdBars: hBars, cooldownBars: hBars + 3 }))
  }

  // ════════════════════════════════════════
  // PHASE 4: Stop loss (fix lb=60, hold=30, thresh=10)
  // ════════════════════════════════════════
  console.log(`\n${'─'.repeat(110)}`)
  console.log('PHASE 4: Stop loss (lb=60min, hold=30min, thresh=10bps)')
  for (const stop of [0, 10, 15, 20, 30, 50, 80, 120]) {
    report(`stop=${stop}bps`, runAll({ ...BASE, stopBps: stop }))
  }

  // ════════════════════════════════════════
  // PHASE 5: Trailing stop (fix lb=60, hold=30, thresh=10, stop=30)
  // ════════════════════════════════════════
  console.log(`\n${'─'.repeat(110)}`)
  console.log('PHASE 5: Trailing stop (lb=60min, hold=30min, thresh=10bps, stop=30bps)')
  for (const [act, dist] of [[0, 0], [5, 0.3], [10, 0.3], [10, 0.5], [15, 0.3], [15, 0.5], [20, 0.3], [20, 0.5], [30, 0.4], [30, 0.5]] as [number, number][]) {
    report(`trail act=${act} dist=${(dist*100).toFixed(0)}%`, runAll({ ...BASE, stopBps: 30, trailActBps: act, trailDistPct: dist }))
  }

  // ════════════════════════════════════════
  // PHASE 6: Combined best (multi-config comparison)
  // ════════════════════════════════════════
  console.log(`\n${'─'.repeat(110)}`)
  console.log('PHASE 6: Best configurations head-to-head')
  console.log()

  const configs: [string, MomConfig][] = [
    ['BASELINE (no risk mgmt)', { ...BASE }],
    ['STOP-30', { ...BASE, stopBps: 30 }],
    ['STOP-50', { ...BASE, stopBps: 50 }],
    ['STOP-30 + TRAIL-15/50%', { ...BASE, stopBps: 30, trailActBps: 15, trailDistPct: 0.5 }],
    ['STOP-30 + TRAIL-20/40%', { ...BASE, stopBps: 30, trailActBps: 20, trailDistPct: 0.4 }],
    ['lb=90min hold=30min stop=30', { ...BASE, lookbackBars: 540, stopBps: 30 }],
    ['lb=60min hold=45min stop=30', { ...BASE, holdBars: 270, cooldownBars: 273, stopBps: 30 }],
    ['lb=60min hold=60min stop=50', { ...BASE, holdBars: 360, cooldownBars: 363, stopBps: 50 }],
    ['lb=120min hold=60min stop=50', { ...BASE, lookbackBars: 720, holdBars: 360, cooldownBars: 363, stopBps: 50 }],
    ['THRESH-5 stop=30 trail=15/50%', { ...BASE, entryThreshBps: 5, stopBps: 30, trailActBps: 15, trailDistPct: 0.5 }],
    ['THRESH-15 stop=30 trail=15/50%', { ...BASE, entryThreshBps: 15, stopBps: 30, trailActBps: 15, trailDistPct: 0.5 }],
  ]

  for (const [name, cfg] of configs) {
    report(name, runAll(cfg))
  }

  // ════════════════════════════════════════
  // PHASE 7: Winner — session-level detail
  // ════════════════════════════════════════
  console.log(`\n${'═'.repeat(110)}`)
  console.log('PHASE 7: WINNER — SESSION-LEVEL DETAIL')
  console.log(`${'═'.repeat(110)}\n`)

  // Pick the best 3 configs by t-stat and show session detail
  const ranked = configs.map(([name, cfg]) => {
    const trades = runAll(cfg)
    const tt = tTest(trades.map(t => t.netBps))
    return { name, cfg, trades, t: tt.t, mean: tt.mean }
  }).sort((a, b) => b.t - a.t)

  for (const r of ranked.slice(0, 3)) {
    report(r.name, r.trades, true)
    console.log()
  }

  // ════════════════════════════════════════
  // PHASE 8: Equity simulation on winner
  // ════════════════════════════════════════
  console.log(`${'═'.repeat(110)}`)
  console.log('PHASE 8: EQUITY SIMULATION — $500, 100x leverage')
  console.log(`${'═'.repeat(110)}\n`)

  const best = ranked[0]
  console.log(`  Config: ${best.name}`)
  console.log(`  Params: lb=${best.cfg.lookbackBars/6}min hold=${best.cfg.holdBars/6}min thresh=${best.cfg.entryThreshBps}bps stop=${best.cfg.stopBps}bps trail=${best.cfg.trailActBps}/${(best.cfg.trailDistPct*100).toFixed(0)}%`)
  console.log()

  for (const riskPct of [0.01, 0.02, 0.03, 0.05]) {
    const sim = equitySim(best.trades, 500, 100, riskPct, best.cfg.stopBps)
    console.log(`  risk=${(riskPct*100).toFixed(0)}%: $500→$${sim.equity.toFixed(2)} (${sim.totalReturn > 0 ? '+' : ''}${sim.totalReturn.toFixed(1)}%) maxDD=$${sim.maxDd.toFixed(2)} (${sim.maxDdPct.toFixed(1)}%) trades=${sim.trades}`)
  }

  // Per-session equity with 2% risk
  console.log()
  console.log('  Per-session equity (risk=2%):')
  const sortedTrades = [...best.trades].sort((a, b) => {
    if (a.session !== b.session) return a.session < b.session ? -1 : 1
    return a.entryBar - b.entryBar
  })
  const bySession = new Map<string, Trade[]>()
  for (const t of sortedTrades) {
    if (!bySession.has(t.session)) bySession.set(t.session, [])
    bySession.get(t.session)!.push(t)
  }
  let runningEquity = 500
  for (const [s, tr] of [...bySession.entries()].sort()) {
    const dayStart = runningEquity
    for (const t of tr) {
      const riskUsd = runningEquity * 0.02
      const stopDist = (best.cfg.stopBps > 0 ? best.cfg.stopBps : 50) / 10000
      const posNotional = Math.min(riskUsd / stopDist, runningEquity * 100)
      const posBtc = posNotional / t.entryPrice
      runningEquity += posBtc * t.entryPrice * (t.netBps / 10000)
    }
    const dayPnl = runningEquity - dayStart
    console.log(`    ${s}: $${dayStart.toFixed(2)} → $${runningEquity.toFixed(2)} (${dayPnl >= 0 ? '+' : ''}$${dayPnl.toFixed(2)}) n=${tr.length}`)
  }
  console.log(`    FINAL: $500.00 → $${runningEquity.toFixed(2)} (${((runningEquity-500)/500*100).toFixed(1)}%)`)

  // ════════════════════════════════════════
  // PHASE 9: Excursion analysis for winner
  // ════════════════════════════════════════
  console.log(`\n${'─'.repeat(110)}`)
  console.log('PHASE 9: TRADE EXCURSION ANALYSIS')
  console.log()

  const wins = best.trades.filter(t => t.netBps > 0)
  const losses = best.trades.filter(t => t.netBps <= 0)

  const 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)]
  }

  console.log('  Winners:')
  if (wins.length > 0) {
    const favEx = wins.map(t => t.maxFavBps)
    const advEx = wins.map(t => t.maxAdvBps)
    console.log(`    n=${wins.length} avgNet=${(wins.reduce((s,t)=>s+t.netBps,0)/wins.length).toFixed(1)}bps`)
    console.log(`    Max favorable: p25=${pct(favEx,0.25).toFixed(1)} p50=${pct(favEx,0.50).toFixed(1)} p75=${pct(favEx,0.75).toFixed(1)} p95=${pct(favEx,0.95).toFixed(1)}`)
    console.log(`    Max adverse:   p25=${pct(advEx,0.25).toFixed(1)} p50=${pct(advEx,0.50).toFixed(1)} p75=${pct(advEx,0.75).toFixed(1)} p95=${pct(advEx,0.95).toFixed(1)}`)
  }
  console.log('  Losers:')
  if (losses.length > 0) {
    const favEx = losses.map(t => t.maxFavBps)
    const advEx = losses.map(t => t.maxAdvBps)
    console.log(`    n=${losses.length} avgNet=${(losses.reduce((s,t)=>s+t.netBps,0)/losses.length).toFixed(1)}bps`)
    console.log(`    Max favorable: p25=${pct(favEx,0.25).toFixed(1)} p50=${pct(favEx,0.50).toFixed(1)} p75=${pct(favEx,0.75).toFixed(1)} p95=${pct(favEx,0.95).toFixed(1)}`)
    console.log(`    Max adverse:   p25=${pct(advEx,0.25).toFixed(1)} p50=${pct(advEx,0.50).toFixed(1)} p75=${pct(advEx,0.75).toFixed(1)} p95=${pct(advEx,0.95).toFixed(1)}`)
  }

  // Fee sensitivity
  console.log()
  console.log('  Fee sensitivity:')
  for (const fee of [0, 1, 2, 3, 4, 5.5]) {
    const nets = best.trades.map(t => t.grossBps - 2 * fee)
    const tt = tTest(nets)
    console.log(`    fee=${String(fee).padStart(4)}bps: net=${tt.mean.toFixed(2).padStart(7)}bps t=${tt.t.toFixed(2).padStart(5)} ${sig(tt.t)}`)
  }
}

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