// 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:Reward ratio optimization for momentum strategy.
 *
 * Current baseline: pure time exit at 30min, 51% WR, +9.4bps net/trade.
 * Goal: add SL+TP with defined R:R to improve expectancy despite lower WR.
 *
 * For each (stopBps, R:R multiplier) combo:
 *   TP = stopBps × multiplier
 *   Exit: TP hit → win | SL hit → loss | time → whatever
 *
 * Tests R:R from 1:1 to 6:1, stops from 10–80bps, holds 30–90min.
 */
import fs from 'node:fs'
import readline from 'node:readline'


interface RBar extends Bar10s { ts: number }

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 var_ = v.reduce((s, x) => s + (x - m) ** 2, 0) / (n - 1)
  const se = Math.sqrt(var_ / n)
  return { mean: m, t: se > 0 ? m / se : 0, n }
}

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

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
}

interface Trade {
  session: string; side: 'long'|'short'; grossBps: number; netBps: number
  exitReason: 'tp'|'sl'|'time'; holdBars: number; maxFavBps: number; maxAdvBps: number
}

interface SimConfig {
  lookbackBars: number
  holdBars: number
  entryThreshBps: number
  stopBps: number
  tpBps: number        // take-profit distance (0 = no TP)
  feeBps: number
  cooldownBars: number
}

function simulate(bars: RBar[], session: string, cfg: SimConfig): 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

    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 entry = bars[i].c

    let exitBar = i + cfg.holdBars
    let exitReason: 'tp'|'sl'|'time' = 'time'
    let peakFav = 0, maxAdv = 0

    for (let j = i + 1; j <= i + cfg.holdBars && j < bars.length; j++) {
      // Use bar high/low for SL/TP checks (more realistic than close-only)
      const barHigh = bars[j].h, barLow = bars[j].l, barClose = bars[j].c

      const pnlHigh = side === 'long'
        ? (barHigh - entry) / entry * 10000
        : (entry - barLow) / entry * 10000
      const pnlLow = side === 'long'
        ? (barLow - entry) / entry * 10000
        : (entry - barHigh) / entry * 10000
      const pnlClose = side === 'long'
        ? (barClose - entry) / entry * 10000
        : (entry - barClose) / entry * 10000

      if (pnlHigh > peakFav) peakFav = pnlHigh
      if (-pnlLow > maxAdv) maxAdv = -pnlLow

      // Check SL first (conservative: assume SL hits before TP on same bar)
      if (cfg.stopBps > 0 && pnlLow <= -cfg.stopBps) {
        exitBar = j; exitReason = 'sl'; break
      }
      // Then check TP
      if (cfg.tpBps > 0 && pnlHigh >= cfg.tpBps) {
        exitBar = j; exitReason = 'tp'; break
      }
    }

    let grossBps: number
    if (exitReason === 'sl') grossBps = -cfg.stopBps
    else if (exitReason === 'tp') grossBps = cfg.tpBps
    else {
      if (exitBar >= bars.length) exitBar = bars.length - 1
      grossBps = side === 'long'
        ? (bars[exitBar].c - entry) / entry * 10000
        : (entry - bars[exitBar].c) / entry * 10000
    }

    trades.push({
      session, side, grossBps, netBps: grossBps - 2 * cfg.feeBps,
      exitReason, holdBars: exitBar - i, maxFavBps: peakFav, maxAdvBps: maxAdv
    })
    lastExit = exitBar
  }
  return trades
}

function report(name: string, trades: Trade[], showSessions = false) {
  if (trades.length < 5) { console.log(`  ${name.padEnd(55)} n=${trades.length} (too few)`); return }
  const net = trades.map(t => t.netBps)
  const tt = tTest(net)
  const wr = net.filter(r => r > 0).length / net.length
  const tps = trades.filter(t => t.exitReason === 'tp').length
  const sls = trades.filter(t => t.exitReason === 'sl').length
  const times = trades.filter(t => t.exitReason === 'time').length
  const avgHold = trades.reduce((s, t) => s + t.holdBars, 0) / trades.length * 10 / 60

  let cum = 0, maxDd = 0, peak = 0
  for (const r of net) { cum += r; if (cum > peak) peak = cum; maxDd = Math.max(maxDd, peak - cum) }

  // Expectancy = WR × avgWin - (1-WR) × avgLoss
  const wins = net.filter(r => r > 0), losses = net.filter(r => r <= 0)
  const avgW = wins.length > 0 ? wins.reduce((a,b)=>a+b,0)/wins.length : 0
  const avgL = losses.length > 0 ? Math.abs(losses.reduce((a,b)=>a+b,0)/losses.length) : 0
  const expect = wr * avgW - (1 - wr) * avgL

  console.log(`  ${name.padEnd(55)} n=${String(trades.length).padStart(4)} WR=${(wr*100).toFixed(0).padStart(2)}% net=${tt.mean.toFixed(1).padStart(6)}bps t=${tt.t.toFixed(2).padStart(5)} cum=${cum.toFixed(0).padStart(6)} maxDD=${maxDd.toFixed(0).padStart(5)} expect=${expect.toFixed(1).padStart(6)} TP${tps}/SL${sls}/T${times} hold=${avgHold.toFixed(0).padStart(2)}m ${sig(tt.t)}`)

  if (showSessions) {
    const byS = new Map<string, number[]>()
    for (const t of trades) { if (!byS.has(t.session)) byS.set(t.session, []); byS.get(t.session)!.push(t.netBps) }
    let pos = 0
    for (const [s, rets] of [...byS.entries()].sort()) {
      const c = rets.reduce((a,b)=>a+b,0)
      if (c > 0) pos++
      console.log(`    ${s} n=${String(rets.length).padStart(3)} WR=${(rets.filter(r=>r>0).length/rets.length*100).toFixed(0).padStart(2)}% cum=${c.toFixed(1).padStart(8)}bps`)
    }
    console.log(`    Positive: ${pos}/${byS.size}`)
  }
}

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

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

  const BASE: SimConfig = {
    lookbackBars: 360, holdBars: 180, entryThreshBps: 5,
    stopBps: 0, tpBps: 0, feeBps: 2, cooldownBars: 183
  }

  console.log(`${'═'.repeat(120)}`)
  console.log('RISK:REWARD OPTIMIZATION — MOMENTUM STRATEGY')
  console.log(`${'═'.repeat(120)}\n`)

  // ── BASELINE ──
  console.log('BASELINE (no SL/TP, pure time exit):')
  report('BASELINE hold=30m', runAll(BASE), true)
  console.log()

  // ══════════════════════════════════════════════════════
  // PHASE 1: Fixed stop, sweep R:R multiplier
  // ══════════════════════════════════════════════════════
  console.log(`${'─'.repeat(120)}`)
  console.log('PHASE 1: Sweep R:R multiplier (hold=30min)')
  console.log()

  for (const stopBps of [15, 20, 25, 30, 40, 50]) {
    console.log(`  Stop = ${stopBps}bps:`)
    // No TP (stop only)
    report(`  SL=${stopBps} TP=none`, runAll({ ...BASE, stopBps, tpBps: 0 }))
    for (const rr of [1, 1.5, 2, 2.5, 3, 4, 5, 6]) {
      const tp = Math.round(stopBps * rr)
      report(`  SL=${stopBps} TP=${tp} (${rr}:1)`, runAll({ ...BASE, stopBps, tpBps: tp }))
    }
    console.log()
  }

  // ══════════════════════════════════════════════════════
  // PHASE 2: Longer hold with R:R
  // ══════════════════════════════════════════════════════
  console.log(`${'─'.repeat(120)}`)
  console.log('PHASE 2: Longer holds + R:R')
  console.log()

  for (const holdMin of [45, 60, 90, 120]) {
    const holdBars = holdMin * 6
    console.log(`  Hold = ${holdMin}min:`)
    report(`  no SL/TP`, runAll({ ...BASE, holdBars, cooldownBars: holdBars + 3 }))
    for (const stopBps of [20, 30, 50]) {
      for (const rr of [2, 3, 4, 5]) {
        const tp = Math.round(stopBps * rr)
        report(`  SL=${stopBps} TP=${tp} (${rr}:1)`, runAll({ ...BASE, holdBars, cooldownBars: holdBars + 3, stopBps, tpBps: tp }))
      }
    }
    console.log()
  }

  // ══════════════════════════════════════════════════════
  // PHASE 3: Asymmetric — wider stop, further TP
  // ══════════════════════════════════════════════════════
  console.log(`${'─'.repeat(120)}`)
  console.log('PHASE 3: Asymmetric setups (wider stop + distant TP)')
  console.log()

  for (const [stop, tp, hold] of [
    [30, 60, 30], [30, 90, 45], [30, 120, 60],
    [40, 80, 30], [40, 120, 45], [40, 160, 60], [40, 200, 90],
    [50, 100, 30], [50, 150, 45], [50, 200, 60], [50, 250, 90],
    [60, 120, 45], [60, 180, 60], [60, 240, 90], [60, 300, 120],
    [80, 160, 45], [80, 240, 60], [80, 320, 90], [80, 400, 120],
  ] as [number, number, number][]) {
    const holdBars = hold * 6
    report(`SL=${stop} TP=${tp} (${(tp/stop).toFixed(1)}:1) hold=${hold}m`, runAll({ ...BASE, stopBps: stop, tpBps: tp, holdBars, cooldownBars: holdBars + 3 }))
  }

  // ══════════════════════════════════════════════════════
  // PHASE 4: Entry threshold interaction
  // ══════════════════════════════════════════════════════
  console.log(`\n${'─'.repeat(120)}`)
  console.log('PHASE 4: Entry threshold × R:R (hold=30min)')
  console.log()

  for (const thresh of [3, 5, 10, 15, 20]) {
    console.log(`  Threshold = ${thresh}bps:`)
    report(`  baseline`, runAll({ ...BASE, entryThreshBps: thresh }))
    for (const [stop, rr] of [[20, 3], [30, 3], [30, 4], [40, 3], [50, 3]] as [number, number][]) {
      const tp = stop * rr
      report(`  SL=${stop} TP=${tp} (${rr}:1)`, runAll({ ...BASE, entryThreshBps: thresh, stopBps: stop, tpBps: tp }))
    }
    console.log()
  }

  // ══════════════════════════════════════════════════════
  // PHASE 5: Lookback interaction  
  // ══════════════════════════════════════════════════════
  console.log(`${'─'.repeat(120)}`)
  console.log('PHASE 5: Lookback × R:R (best setups from above)')
  console.log()

  for (const lbMin of [45, 60, 90, 120, 180]) {
    console.log(`  Lookback = ${lbMin}min:`)
    for (const [stop, rr, hold] of [[30, 3, 30], [40, 3, 45], [50, 3, 60], [50, 4, 60]] as [number, number, number][]) {
      const tp = stop * rr, holdBars = hold * 6
      report(`  SL=${stop} TP=${tp} (${rr}:1) hold=${hold}m`, runAll({ ...BASE, lookbackBars: lbMin * 6, stopBps: stop, tpBps: tp, holdBars, cooldownBars: holdBars + 3 }))
    }
    console.log()
  }

  // ══════════════════════════════════════════════════════
  // PHASE 6: TOP CONFIGS — session detail
  // ══════════════════════════════════════════════════════
  console.log(`${'═'.repeat(120)}`)
  console.log('PHASE 6: TOP 10 — RANKED BY t-stat')
  console.log(`${'═'.repeat(120)}\n`)

  // Collect all tested configs
  const candidates: { name: string; cfg: SimConfig }[] = []
  candidates.push({ name: 'BASELINE', cfg: { ...BASE } })
  for (const stop of [15, 20, 25, 30, 40, 50]) {
    for (const rr of [1, 1.5, 2, 2.5, 3, 4, 5, 6]) {
      candidates.push({ name: `SL=${stop} TP=${Math.round(stop*rr)} (${rr}:1) h=30m`, cfg: { ...BASE, stopBps: stop, tpBps: Math.round(stop * rr) } })
    }
  }
  for (const [stop, tp, hold] of [
    [30,90,45],[40,120,45],[40,160,60],[40,200,90],
    [50,150,45],[50,200,60],[50,250,90],
    [60,180,60],[60,240,90],[60,300,120],
    [80,240,60],[80,320,90],[80,400,120],
  ] as [number,number,number][]) {
    const hb = hold * 6
    candidates.push({ name: `SL=${stop} TP=${tp} (${(tp/stop).toFixed(1)}:1) h=${hold}m`, cfg: { ...BASE, stopBps: stop, tpBps: tp, holdBars: hb, cooldownBars: hb + 3 } })
  }

  const ranked = candidates.map(c => {
    const trades = runAll(c.cfg)
    const tt = tTest(trades.map(t => t.netBps))
    return { ...c, trades, t: tt.t, mean: tt.mean }
  }).sort((a, b) => b.t - a.t)

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

  // Fee sensitivity for #1
  console.log(`${'─'.repeat(120)}`)
  console.log('FEE SENSITIVITY — #1 config:')
  const best = ranked[0]
  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) })
