This indicator displays a nearly real-time options chain directly on your chart, sourced from a third-party API. It focuses on the nearest expiration date and includes detailed data for both calls and puts, giving you immediate insight into market sentiment and positioning.
Features:
- Displays Call and Put contracts for the closest expiration
- Shows Last Price, Bid, Ask, and Open Interest (OI) for each strike
- Calculates the Put/Call Ratio and OI Put/Call Ratio
- Updates dynamically using live data from supported vendors
This indicator had been implemented by TrendSpider Team in JavaScript on TrendSpider. Check out the developer documentation to learn more about JS on TrendSpider.
describe_indicator('Options Chain');
const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36';
const assetType = current.assetType == 'etf' ? 'etf' : 'stocks';
const url = `https://api.nasdaq.com/api/quote/${current.symbol}/option-chain?assetclass=${assetType}&limit=1000`;
const optionsData = await request.http(url, 300, {'user-agent': UA, accept: 'application/json' });
assert(!optionsData.error, `Error fetching options data: ${optionsData.error}`);
const myData = optionsData.data;
assert(myData, `No data for ${current.ticker}`);
// Find the closest expiration date
const closestExpiry = myData.table.rows.find(row => row.expiryDate && row.strike);
assert(closestExpiry, "No valid expiration date found");
// Filter rows for the closest expiration date
const relevantRows = myData.table.rows.filter(row => row.expiryDate === closestExpiry.expiryDate && row.strike);
// Sort rows by strike price
const sortedRows = relevantRows.sort((a, b) => parseFloat(a.strike) - parseFloat(b.strike));
// Find the index of the current price
const currentPrice = close[close.length - 1];
const currentPriceIndex = sortedRows.findIndex(row => parseFloat(row.strike) >= currentPrice);
// Get 4 strikes above and 4 strikes below the current price
const startIndex = Math.max(0, currentPriceIndex - 4);
const endIndex = Math.min(sortedRows.length, currentPriceIndex + 4);
const limitedRows = sortedRows.slice(startIndex, endIndex + 1);
// Prepare the table data
const tableData = limitedRows.map(row => {
const callOI = parseInt(row.c_Openinterest) || 0;
const putOI = parseInt(row.p_Openinterest) || 0;
const putCallRatio = callOI > 0 ? (putOI / callOI).toFixed(2) : '-';
const callVolume = parseInt(row.c_Volume) || 0;
const putVolume = parseInt(row.p_Volume) || 0;
const volumePutCallRatio = callVolume > 0 ? (putVolume / callVolume).toFixed(2) : '-';
return {
c_Volume: row.c_Volume || '-',
c_Openinterest: row.c_Openinterest || '-',
c_Bid: row.c_Bid || '-',
c_Ask: row.c_Ask || '-',
c_Last: row.c_Last || '-',
strike: row.strike,
p_Last: row.p_Last || '-',
p_Bid: row.p_Bid || '-',
p_Ask: row.p_Ask || '-',
p_Volume: row.p_Volume || '-',
p_Openinterest: row.p_Openinterest || '-',
putCallRatio: putCallRatio,
volumePutCallRatio: volumePutCallRatio
};
});
const textCell = (text, background = 'transparent') => ({
text,
color: 'var(--text-color)',
paddingTop: 2,
paddingBottom: 2,
paddingLeft: 3,
paddingRight: 3,
background
});
paint_overlay('OptionsChain', { position: 'bottom_left', offset_y: 20, order: 'above_all' }, {
background: 'var(--background-color)',
rows: [
{
cells: [{
text: `Options Chain for ${current.symbol}, Next expiry (${closestExpiry.expiryDate})`,
color: 'var(--text-color)',
colspan: 13,
fontWeight: 'bold'
}]
},
{
cells: [
textCell('Calls<br/>OI'),
textCell('Calls<br/>Bid'),
textCell('Calls<br/>Ask'),
textCell('Calls<br/>Last'),
textCell('Strike'),
textCell('Puts<br/>Last'),
textCell('Puts<br/>Bid'),
textCell('Puts<br/>Ask'),
textCell('Puts<br/>OI'),
textCell('OI Put/Call<br/>Ratio'),
textCell('Vol Put/Call<br/>Ratio')
]
},
...tableData.map((row, index) => {
const colorOverride = (index == currentPriceIndex - startIndex) ? '#00ffff22' : null;
const callColor = colorOverride || (parseFloat(row.strike) <= currentPrice ? '#ff000022' : 'transparent');
const putColor = colorOverride || (parseFloat(row.strike) > currentPrice ? '#00ff0022' : 'transparent');
return {
cells: [
textCell(row.c_Openinterest, callColor),
textCell(row.c_Bid, callColor),
textCell(row.c_Ask, callColor),
textCell(row.c_Last, callColor),
textCell(row.strike, colorOverride || '#0000ff22'),
textCell(row.p_Last, putColor),
textCell(row.p_Bid, putColor),
textCell(row.p_Ask, putColor),
textCell(row.p_Openinterest, putColor),
textCell(row.putCallRatio, colorOverride || '#ffff0022'),
textCell(row.volumePutCallRatio, colorOverride || '#ffff0022')
],
};
})
]
});