// ═══════════════════════════════════════════════════════════
//  backtest.js — 機械式策略回測（純函式，吃 marketdata 的歷史點）
//  策略：
//   buyhold 買進持有（基準）
//   dca     定期定額（每月投入固定金額）
//   sma     均線趨勢（短均 > 長均在場、否則空手）
//   dip     逢大跌進場（距歷史高點回落 X% 才買進後續抱）
//  皆以 adjclose（還原股價）計算，初始資金 10,000。
//  輸出權益曲線與統計（總報酬、年化、最大回撤、在場比例、交易次數、勝率），
//  並附「買進持有」基準供對照。本工具僅供學習，過去績效不代表未來。
// ═══════════════════════════════════════════════════════════
const CAPITAL = 10000;

export const STRATEGIES = {
  buyhold: { label: '買進持有', params: [] },
  dca: { label: '定期定額（每月）', params: [{ key: 'monthly', label: '每月投入', def: 1000, min: 1 }] },
  sma: { label: '均線趨勢（短>長在場）', params: [
    { key: 'short', label: '短均線(日)', def: 50, min: 2 },
    { key: 'long', label: '長均線(日)', def: 200, min: 3 },
  ] },
  dip: { label: '逢大跌進場（回落%後買進）', params: [
    { key: 'drop', label: '距高點回落%', def: 15, min: 1 },
  ] },
};

function sma(vals, i, n) {
  if (i + 1 < n) return null;
  let s = 0; for (let k = i - n + 1; k <= i; k++) s += vals[k];
  return s / n;
}
function yearsBetween(a, b) { return Math.max((new Date(b) - new Date(a)) / (365.25 * 86400000), 1 / 365.25); }
function maxDrawdown(curve) {
  let peak = -Infinity, mdd = 0;
  for (const p of curve) { if (p.val > peak) peak = p.val; const dd = (p.val - peak) / peak; if (dd < mdd) mdd = dd; }
  return -mdd * 100; // 正的百分比
}
function statsFrom(curve, { trades = 1, roundTrips = [], inDays = null } = {}) {
  const first = curve[0].val, last = curve[curve.length - 1].val;
  const yrs = yearsBetween(curve[0].date, curve[curve.length - 1].date);
  const totalReturn = (last / first - 1) * 100;
  const cagr = (Math.pow(last / first, 1 / yrs) - 1) * 100;
  const wins = roundTrips.filter(r => r > 0).length;
  return {
    finalValue: last,
    totalReturn,
    cagr,
    maxDrawdown: maxDrawdown(curve),
    trades,
    winRate: roundTrips.length ? (wins / roundTrips.length) * 100 : null,
    exposure: inDays != null ? (inDays / curve.length) * 100 : 100,
  };
}

// 買進持有：期初一次投入 CAPITAL
function buyHold(points) {
  const a0 = points[0].adjclose;
  return points.map(p => ({ date: p.date, val: CAPITAL * (p.adjclose / a0) }));
}

// 定期定額：每月第一個交易日投入固定金額；基準＝同總額在期初一次買進
function runDca(points, monthly) {
  let shares = 0, invested = 0, lastMonth = '';
  const equity = [];
  for (const p of points) {
    const m = p.date.slice(0, 7);
    if (m !== lastMonth) { shares += monthly / p.adjclose; invested += monthly; lastMonth = m; }
    equity.push({ date: p.date, val: shares * p.adjclose, invested });
  }
  // 正規化成「以總投入為基底」：把曲線換成相對總投入的價值，期初基底=首次投入
  const totalInvested = invested;
  const a0 = points[0].adjclose;
  const benchmark = points.map(p => ({ date: p.date, val: totalInvested * (p.adjclose / a0) }));
  // DCA 報酬以總投入為分母
  const last = equity[equity.length - 1].val;
  const yrs = yearsBetween(points[0].date, points[points.length - 1].date);
  const stats = {
    finalValue: last,
    totalReturn: (last / totalInvested - 1) * 100,
    cagr: (Math.pow(Math.max(last / totalInvested, 0.0001), 1 / yrs) - 1) * 100,
    maxDrawdown: maxDrawdown(equity.map(e => ({ val: e.val, date: e.date }))),
    trades: equity.filter((e, i) => i === 0 || e.invested !== equity[i - 1].invested).length,
    winRate: null,
    exposure: 100,
    invested: totalInvested,
  };
  return {
    equity: equity.map(e => ({ date: e.date, val: e.val })),
    benchmark,
    stats,
    benchStats: statsFrom(benchmark),
    note: `共投入 ${stats.trades} 期、合計 ${Math.round(totalInvested).toLocaleString()} 元；基準線為「同總額在期初一次買進」。`,
  };
}

// 在場/空手型策略的共用模擬器：signal[i] = 是否該在場
function simulateSignal(points, signal) {
  let cash = CAPITAL, shares = 0, holding = false, buyPrice = 0, inDays = 0, trades = 0;
  const roundTrips = [];
  const equity = [];
  for (let i = 0; i < points.length; i++) {
    const px = points[i].adjclose;
    const want = signal[i];
    if (want && !holding) { shares = cash / px; cash = 0; holding = true; buyPrice = px; trades++; }
    else if (!want && holding) { cash = shares * px; shares = 0; holding = false; roundTrips.push((px / buyPrice - 1)); }
    if (holding) inDays++;
    equity.push({ date: points[i].date, val: holding ? shares * px : cash });
  }
  return { equity, trades, roundTrips, inDays };
}

function runSma(points, short, long) {
  short = Math.max(2, Math.round(short)); long = Math.max(short + 1, Math.round(long));
  const vals = points.map(p => p.adjclose);
  const signal = points.map((_, i) => {
    const s = sma(vals, i, short), l = sma(vals, i, long);
    return (s != null && l != null) ? s > l : false;
  });
  const sim = simulateSignal(points, signal);
  return {
    equity: sim.equity,
    benchmark: buyHold(points),
    stats: statsFrom(sim.equity, { trades: sim.trades, roundTrips: sim.roundTrips, inDays: sim.inDays }),
    benchStats: statsFrom(buyHold(points)),
    note: `${short}/${long} 日均線：短均上穿長均才在場，否則空手。`,
  };
}

function runDip(points, dropPct) {
  const drop = Math.max(1, dropPct) / 100;
  let peak = -Infinity, entered = false;
  const signal = points.map(p => {
    if (p.adjclose > peak) peak = p.adjclose;
    if (!entered && (p.adjclose / peak - 1) <= -drop) entered = true; // 一旦回落達標即進場，之後續抱
    return entered;
  });
  const sim = simulateSignal(points, signal);
  return {
    equity: sim.equity,
    benchmark: buyHold(points),
    stats: statsFrom(sim.equity, { trades: sim.trades, roundTrips: sim.roundTrips, inDays: sim.inDays }),
    benchStats: statsFrom(buyHold(points)),
    note: `先空手等待，距歷史高點回落達 ${dropPct}% 才一次買進並續抱到期末（對照「直接買進持有」）。`,
  };
}

// 主入口：points 需含 adjclose；params 為策略參數
export function runBacktest(points, { strategy = 'buyhold', monthly, short, long, drop } = {}) {
  if (!Array.isArray(points) || points.length < 3) throw new Error('歷史資料不足以回測');
  const meta = STRATEGIES[strategy] || STRATEGIES.buyhold;
  let out;
  if (strategy === 'dca') out = runDca(points, monthly > 0 ? monthly : 1000);
  else if (strategy === 'sma') out = runSma(points, short || 50, long || 200);
  else if (strategy === 'dip') out = runDip(points, drop || 15);
  else {
    const eq = buyHold(points);
    out = { equity: eq, benchmark: null, stats: statsFrom(eq), benchStats: null, note: '期初一次投入並持有到期末。' };
  }
  return {
    strategy, strategyLabel: meta.label,
    from: points[0].date, to: points[points.length - 1].date,
    capital: CAPITAL,
    ...out,
  };
}
