← Назад
function calcPCRatio(optionsData, expiry, underlying) { const filtered = optionsData.filter(o => { const matchExp = o.symbol.includes('-'+expiry+'-'); const matchUnd = underlying ? o.symbol.startsWith(underlying) : true; return matchExp && matchUnd; }); let cV=0,pV=0,cOi=0,pOi=0,cC=0,pC=0; for (const o of filtered) { const isCall = o.side==='CALL' || o.symbol.includes('-C'); const vol = parseFloat(o.volume || 0); const oi = parseFloat(o.openInterest||o.oi||0); if (isCall) { cV+=vol; cOi+=oi; cC++; } else { pV+=vol; pOi+=oi; pC++; } } const volR = cV>0 ? pV/cV : null; const oiR = cOi>0 ? pOi/cOi : null; return { expiry, underlying: underlying||'ALL', volumeRatio: volR?parseFloat(volR.toFixed(3)):null, oiRatio: oiR?parseFloat(oiR.toFixed(3)):null, callVolume:cV, putVolume:pV, callOi:cOi, putOi:pOi, callCount:cC, putCount:pC }; } function getSentiment(ratio) { if (ratio===null) return { sentiment:'UNKNOWN', sentimentStrength:0 }; if (ratio > 1.5) return { sentiment:'STRONG_BEARISH', sentimentStrength: Math.min((ratio-1)*50, 100) }; if (ratio > 1.0) return { sentiment:'BEARISH', sentimentStrength: (ratio-0.5)*50 }; if (ratio > 0.5) return { sentiment:'BULLISH', sentimentStrength: (1-ratio)*50 }; return { sentiment:'STRONG_BULLISH', sentimentStrength: Math.min((1-ratio)*100, 100) }; } function analyzeAllPCRatios(optionsData, underlying) { const expiries = [...new Set( optionsData.filter(o => underlying ? o.symbol.startsWith(underlying) : true) .map(o => o.symbol.split('-')[1]) )].sort(); const results = expiries.map(exp => { const r = calcPCRatio(optionsData, exp, underlying); return { ...r, ...getSentiment(r.volumeRatio) }; }); const avgArr = results .filter(r => r.volumeRatio!==null) .map(r => r.volumeRatio); const avg = avgArr.length > 0 ? avgArr.reduce((a,b)=>a+b,0)/avgArr.length : 0; for (const r of results) { r.isAnomaly = false; r.anomalyType = null; if (r.volumeRatio!==null && avg>0 && Math.abs(r.volumeRatio-avg) > avg*0.5) { r.isAnomaly = true; r.anomalyType = r.volumeRatio > avg ? 'EXCESS_PUTS' : 'EXCESS_CALLS'; } } return { underlying: underlying||'ALL', avgVolumeRatio: avg?parseFloat(avg.toFixed(3)):null, expiries: results, anomalies: results.filter(r => r.isAnomaly) }; } module.exports = { analyzeAllPCRatios };