← Back
function findByDelta(options, targetDelta, type) {
  const filtered = options.filter(o => {
    const isType = type==='CALL'
      ? (o.side==='CALL' || o.symbol.includes('-C'))
      : (o.side==='PUT' || o.symbol.includes('-P'));
    return isType && o.delta!=null;
  });
  if (filtered.length===0) return null;
  let closest = filtered[0];
  let minD = Math.abs(
    Math.abs(parseFloat(closest.delta))-targetDelta);
  for (const o of filtered) {
    const d = Math.abs(
      Math.abs(parseFloat(o.delta))-targetDelta);
    if (d < minD) { minD=d; closest=o; }
  }
  return closest;
}

function findByMoneyness(options, spot, pctOtm, type) {
  const filtered = options.filter(o => {
    return type==='CALL'
      ? (o.side==='CALL' || o.symbol.includes('-C'))
      : (o.side==='PUT' || o.symbol.includes('-P'));
  });
  if (filtered.length===0) return null;
  const target = type==='CALL'
    ? spot*(1+pctOtm/100) : spot*(1-pctOtm/100);
  let closest = filtered[0];
  let minD = Math.abs(
    parseFloat(closest.strikePrice)-target);
  for (const o of filtered) {
    const d = Math.abs(
      parseFloat(o.strikePrice)-target);
    if (d < minD) { minD=d; closest=o; }
  }
  return closest;
}

function analyzeSkew(optionsData, underlying, spotPrice) {
  const filtered = optionsData.filter(
    o => o.symbol.startsWith(underlying));
  const expiries = [...new Set(
    filtered.map(o => o.symbol.split('-')[1])
  )].sort();
  const results = [];
  for (const expiry of expiries) {
    const exOpts = filtered.filter(
      o => o.symbol.includes('-'+expiry+'-'));
    const putOpt = findByDelta(exOpts, 0.25, 'PUT')
      || findByMoneyness(exOpts, spotPrice, 10, 'PUT');
    const callOpt = findByDelta(exOpts, 0.25, 'CALL')
      || findByMoneyness(exOpts, spotPrice, 10, 'CALL');
    if (!putOpt || !callOpt) continue;
    const putIv = parseFloat(
      putOpt.markIV || putOpt.iv || 0);
    const callIv = parseFloat(
      callOpt.markIV || callOpt.iv || 0);
    if (putIv===0 || callIv===0) continue;
    const skew = putIv - callIv;
    let signal='NEUTRAL',
      description='Skew в норме — баланс спроса на Calls/Puts.',
      severity='LOW';
    if (skew < -0.05) {
      signal='BULLISH';
      description='📈 Коллы дороже Путов — рынок ждёт роста.';
      severity='HIGH';
    } else if (skew > 0.15) {
      signal='STRONG_BEARISH';
      description='📉 Путы сильно дороже — массовое хеджирование!';
      severity='HIGH';
    } else if (skew > 0.05) {
      signal='BEARISH';
      description='📉 Путы немного дороже — умеренный медвежий уклон.';
      severity='LOW';
    }
    results.push({
      underlying, expiry,
      skew25d: parseFloat(skew.toFixed(4)),
      putIv: parseFloat(putIv.toFixed(4)),
      callIv: parseFloat(callIv.toFixed(4)),
      putStrike: parseFloat(putOpt.strikePrice),
      callStrike: parseFloat(callOpt.strikePrice),
      signal, description, severity,
    });
  }
  return results;
}
module.exports = { analyzeSkew };

📜 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...