← Back
function getAtmIv(optionsData, underlying, spotPrice) {
  const calls = optionsData.filter(o =>
    o.symbol.startsWith(underlying)
    && (o.side==='CALL' || o.symbol.includes('-C'))
  );
  if (calls.length===0 || !spotPrice) return null;
  let closest = calls[0];
  let minD = Math.abs(
    parseFloat(closest.strikePrice) - spotPrice);
  for (const opt of calls) {
    const d = Math.abs(
      parseFloat(opt.strikePrice) - spotPrice);
    if (d < minD) { minD = d; closest = opt; }
  }
  return {
    iv: parseFloat(closest.markIV || closest.iv || 0),
    strike: parseFloat(closest.strikePrice),
    symbol: closest.symbol,
  };
}

function calculateIvRankAndPercentile(currentIv, hist) {
  if (!hist || hist.length === 0) {
    return { ivRank: null, ivPercentile: null,
      dataPoints: 0,
      message: 'Not enough historical data' };
  }
  const minIv = Math.min(...hist);
  const maxIv = Math.max(...hist);
  const avgIv = hist.reduce((a,b) => a+b, 0)/hist.length;
  const ivRank = maxIv !== minIv
    ? ((currentIv-minIv)/(maxIv-minIv))*100 : 50;
  const belowCount = hist.filter(
    iv => iv < currentIv).length;
  const ivPercentile = (belowCount/hist.length)*100;
  const ivChange = ((currentIv-avgIv)/avgIv)*100;
  let status = 'NORMAL';
  if (ivChange > 20) status = 'SPIKE';
  else if (ivChange < -20) status = 'CRUSH';
  else if (ivChange > 10) status = 'ELEVATED';
  else if (ivChange < -10) status = 'DEPRESSED';
  let signal = { direction: 'NEUTRAL',
    confidence: 'LOW',
    description: `IV в норме (Rank ${ivRank.toFixed(0)}%). Опционы адекватно оценены.` };
  if (status==='SPIKE' || ivRank > 80) {
    signal = { direction: 'SELL_PREMIUM',
      confidence: ivRank>90 ? 'HIGH' : 'MEDIUM',
      description: `🔴 IV высокая (Rank ${ivRank.toFixed(0)}%) — опционы переоценены. Продажа премии выгодна.` };
  } else if (status==='CRUSH' || ivRank < 20) {
    signal = { direction: 'BUY_PREMIUM',
      confidence: ivRank<10 ? 'HIGH' : 'MEDIUM',
      description: `🟢 IV низкая (Rank ${ivRank.toFixed(0)}%) — опционы дёшевы. Покупка премии выгодна.` };
  }
  return {
    currentIv,
    ivRank: parseFloat(ivRank.toFixed(2)),
    ivPercentile: parseFloat(ivPercentile.toFixed(2)),
    minIv: parseFloat(minIv.toFixed(4)),
    maxIv: parseFloat(maxIv.toFixed(4)),
    avgIv: parseFloat(avgIv.toFixed(4)),
    ivChangeFromAvg: parseFloat(ivChange.toFixed(2)),
    status, dataPoints: hist.length, signal,
  };
}
module.exports = {
  getAtmIv, calculateIvRankAndPercentile };

📜 Git History

e841599feat: expired options tracking + exercise history + exit reason fix3 months ago
163bb5dfeat: migrate to options-screener-v2 folder to isolate deployment4 months ago
Show last diff
Loading...