← Назад
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 };