← Назад
function calculateMaxPain(optionsData, expiry) { const exOpts = optionsData.filter( o => o.symbol.includes('-' + expiry + '-')); if (exOpts.length === 0) return null; const strikes = [...new Set( exOpts.map(o => parseFloat(o.strikePrice)) )].sort((a,b) => a-b); const painByStrike = strikes.map(testPrice => { let totalPain = 0; for (const opt of exOpts) { const strike = parseFloat(opt.strikePrice); const oi = parseFloat( opt.openInterest || opt.oi || 0); if (oi === 0) continue; const isCall = opt.side === 'CALL' || opt.symbol.includes('-C'); if (isCall) totalPain += Math.max(0, testPrice-strike) * oi; else totalPain += Math.max(0, strike-testPrice) * oi; } return { strike: testPrice, totalPain }; }); return painByStrike.reduce((min, cur) => cur.totalPain < min.totalPain ? cur : min); } function calculateAllMaxPain(optionsData, spotPrices) { const expiries = [...new Set( optionsData.map(o => o.symbol.split('-')[1]) )].sort(); const results = []; for (const expiry of expiries) { const mp = calculateMaxPain(optionsData, expiry); if (!mp) continue; const underlying = optionsData.find( o => o.symbol.includes('-'+expiry+'-') ).symbol.split('-')[0]; const spot = spotPrices[underlying] || 0; const distPct = spot > 0 ? ((mp.strike-spot)/spot)*100 : 0; results.push({ underlying, expiry, maxPainStrike: mp.strike, spotPrice: spot, distancePercent: parseFloat(distPct.toFixed(2)), distanceDirection: mp.strike > spot ? 'ABOVE' : 'BELOW', }); } return results; } module.exports = { calculateMaxPain, calculateAllMaxPain };