Jaspers Hub

biseroff / option-chain

pluginReviewed★ 0↓ 0

Options Chain: Shows a multi-vendor options chain straddle table with IV, delta, OI, volume, and max pain for a ticker.

4.9 KB
1import { Fragment } from 'react'
2import type { ChainRow } from './math.ts'
3
4function fmt(n: number | null | undefined, digits = 2): string {
5 if (n == null || !Number.isFinite(n)) return '-'
6 return n.toFixed(digits)
7}
8
9function fmtInt(n: number | null | undefined): string {
10 if (n == null || !Number.isFinite(n)) return '-'
11 return Math.round(n).toLocaleString()
12}
13
14function fmtQuote(px: number | null | undefined, sz: number | null | undefined): string {
15 if (px == null || !Number.isFinite(px)) return '-'
16 if (sz != null && Number.isFinite(sz) && sz > 0) return `${px.toFixed(2)} x${Math.round(sz)}`
17 return px.toFixed(2)
18}
19
20export function StraddleTable({
21 rows,
22 spot,
23 atmStrike,
24 onPickContract,
25}: {
26 rows: ChainRow[]
27 spot: number | null
28 atmStrike: number | null
29 onPickContract?: (occ: string) => void
30}) {
31 let maxOi = 1
32 for (const r of rows) {
33 if ((r.callOi ?? 0) > maxOi) maxOi = r.callOi!
34 if ((r.putOi ?? 0) > maxOi) maxOi = r.putOi!
35 }
36
37 let atmInserted = false
38 const pick = (occ: string) => {
39 if (onPickContract) onPickContract(occ)
40 }
41
42 return (
43 <div className="oc-table-wrap">
44 <table className="oc-table">
45 <thead>
46 <tr>
47 <th colSpan={7} className="oc-center">Calls</th>
48 <th className="oc-center">Strike</th>
49 <th colSpan={7} className="oc-center">Puts</th>
50 </tr>
51 <tr>
52 <th>OI</th>
53 <th>Vol</th>
54 <th>IV%</th>
55 <th>Delta</th>
56 <th>Bid</th>
57 <th>Ask</th>
58 <th>Last</th>
59 <th className="oc-center">K</th>
60 <th>Last</th>
61 <th>Bid</th>
62 <th>Ask</th>
63 <th>Delta</th>
64 <th>IV%</th>
65 <th>Vol</th>
66 <th>OI</th>
67 </tr>
68 </thead>
69 <tbody>
70 {rows.map((r, i) => {
71 const next = rows[i + 1]
72 const showDividerAfter =
73 !atmInserted &&
74 spot != null &&
75 r.strike <= spot &&
76 (next == null || next.strike > spot)
77 if (showDividerAfter) atmInserted = true
78
79 const callOiPct = r.callOi ? Math.min(100, Math.round((r.callOi / maxOi) * 100)) : 0
80 const putOiPct = r.putOi ? Math.min(100, Math.round((r.putOi / maxOi) * 100)) : 0
81 const isAtm = atmStrike != null && r.strike === atmStrike
82 const cClass = `oc-clickable ${r.callItm ? 'oc-itm' : ''}`
83 const pClass = `oc-clickable ${r.putItm ? 'oc-itm' : ''}`
84
85 return (
86 <Fragment key={r.strike}>
87 <tr>
88 <td className={cClass} onClick={() => pick(r.callContract)} title={r.callContract}>
89 {callOiPct > 0 && <span className="oc-oi-bar oc-oi-call" style={{ width: `${callOiPct}%` }} />}
90 {fmtInt(r.callOi)}
91 </td>
92 <td className={cClass} onClick={() => pick(r.callContract)}>{fmtInt(r.callVol)}</td>
93 <td className={cClass} onClick={() => pick(r.callContract)}>{r.callIv != null ? `${fmt(r.callIv, 1)}%` : '-'}</td>
94 <td className={cClass} onClick={() => pick(r.callContract)}>{fmt(r.callDelta, 2)}</td>
95 <td className={cClass} onClick={() => pick(r.callContract)}>{fmtQuote(r.callBid, r.callBidSize)}</td>
96 <td className={cClass} onClick={() => pick(r.callContract)}>{fmtQuote(r.callAsk, r.callAskSize)}</td>
97 <td className={cClass} onClick={() => pick(r.callContract)}>{fmt(r.callLast)}</td>
98 <td className="oc-strike">
99 {fmt(r.strike)}
100 {isAtm ? ' *' : ''}
101 </td>
102 <td className={pClass} onClick={() => pick(r.putContract)} title={r.putContract}>{fmt(r.putLast)}</td>
103 <td className={pClass} onClick={() => pick(r.putContract)}>{fmtQuote(r.putBid, r.putBidSize)}</td>
104 <td className={pClass} onClick={() => pick(r.putContract)}>{fmtQuote(r.putAsk, r.putAskSize)}</td>
105 <td className={pClass} onClick={() => pick(r.putContract)}>{fmt(r.putDelta, 2)}</td>
106 <td className={pClass} onClick={() => pick(r.putContract)}>{r.putIv != null ? `${fmt(r.putIv, 1)}%` : '-'}</td>
107 <td className={pClass} onClick={() => pick(r.putContract)}>{fmtInt(r.putVol)}</td>
108 <td className={pClass} onClick={() => pick(r.putContract)} title={r.putContract}>
109 {putOiPct > 0 && <span className="oc-oi-bar oc-oi-put" style={{ width: `${putOiPct}%` }} />}
110 {fmtInt(r.putOi)}
111 </td>
112 </tr>
113 {showDividerAfter && (
114 <tr className="oc-atm-divider">
115 <td colSpan={15}>
116 CALLS ITM - UNDERLYING SPOT ${fmt(spot)} - PUTS ITM
117 </td>
118 </tr>
119 )}
120 </Fragment>
121 )
122 })}
123 </tbody>
124 </table>
125 </div>
126 )
127}