An example for createOptionsChart is missing
#1967
-
|
Hi there, I am having trouble finding a sample of I generated and improved a little bit the following React component. Cannot replicate the layout options that TradingView uses. Also, the breakeven line(s) and the current PnL line with the hover effect. import {useEffect, useMemo, useState, useRef} from "react";
import {
createOptionsChart,
LineStyle,
LineSeries,
PriceScaleMode,
CrosshairMode,
type IChartApiBase,
type ISeriesApi,
} from "lightweight-charts";
export type OptionRight = "call" | "put";
export type OptionSide = "long" | "short";
export interface OptionLeg {
right: OptionRight;
side: OptionSide;
strike: number;
premium: number;
qty?: number;
label?: string;
}
export interface OptionsPnLChartProps {
legs: OptionLeg[];
underlyingPrice: number;
lotSize?: number;
pricePaddingPct?: number;
priceSamples?: number;
height?: number;
}
function legPayoffAtExpiry(s: number, leg: OptionLeg, lotSize: number): number {
const qty = leg.qty ?? 1;
const mult = leg.side === "long" ? 1 : -1;
const intrinsic = leg.right === "call" ? Math.max(0, s - leg.strike) : Math.max(0, leg.strike - s);
const payoffPerContract = intrinsic - leg.premium;
return mult * payoffPerContract * qty * lotSize;
}
function strategyPayoff(s: number, legs: OptionLeg[], lotSize: number): number {
return legs.reduce((acc, leg) => acc + legPayoffAtExpiry(s, leg, lotSize), 0);
}
function linspace(min: number, max: number, n: number): number[] {
if (n < 2) return [min, max];
const step = (max - min) / (n - 1);
return Array.from({length: n}, (_, i) => min + i * step);
}
function estimateBreakevens(xs: number[], ys: number[]): number[] {
const bes: number[] = [];
for (let i = 1; i < xs.length; i++) {
const y0 = ys[i - 1], y1 = ys[i];
if ((y0 <= 0 && y1 >= 0) || (y0 >= 0 && y1 <= 0)) {
const x0 = xs[i - 1], x1 = xs[i];
const t = y1 === y0 ? 0 : -y0 / (y1 - y0);
bes.push(x0 + t * (x1 - x0));
}
}
return Array.from(new Set(bes.map(v => +v.toFixed(2))));
}
function minMax(arr: number[]): { min: number; max: number } {
return {min: Math.min(...arr), max: Math.max(...arr)};
}
function OptionsPnLChart(props: OptionsPnLChartProps) {
const {
legs,
underlyingPrice,
lotSize = 100,
pricePaddingPct = 0.15,
priceSamples = 400,
height = 380,
} = props;
const containerRef = useRef<HTMLDivElement | null>(null);
const chartRef = useRef<IChartApiBase<number> | null>(null);
const lineRef = useRef<ISeriesApi<"Line", number> | null>(null);
const computed = useMemo(() => {
if (!legs || legs.length === 0) return null;
const strikes = legs.map(l => l.strike);
const {min: kmin, max: kmax} = minMax(strikes);
const span = kmax - kmin || Math.max(5, kmax * 0.05);
const pad = span * pricePaddingPct;
const xMin = Math.max(0, Math.min(kmin, underlyingPrice) - pad);
const xMax = Math.max(kmax, underlyingPrice) + pad;
const xs = linspace(xMin, xMax, priceSamples);
const ys = xs.map(x => strategyPayoff(x, legs, lotSize));
const {min: yMin, max: yMax} = minMax(ys);
const breakevens = estimateBreakevens(xs, ys);
const maxProfit = yMax;
const maxLoss = yMin;
return {xs, ys, breakevens, maxProfit, maxLoss, xMin, xMax, yMin, yMax};
}, [legs, lotSize, pricePaddingPct, priceSamples, underlyingPrice]);
useEffect(() => {
if (!containerRef.current) return;
if (chartRef.current) {
chartRef.current.remove();
chartRef.current = null;
lineRef.current = null;
}
const chart = createOptionsChart(containerRef.current, {
height,
layout: {
attributionLogo: false,
textColor: "#C9D1D9",
background: {type: "solid" as any, color: "#0B0E11"}
},
rightPriceScale: {borderVisible: false, scaleMargins: {top: 0.1, bottom: 0.1}, mode: PriceScaleMode.Normal},
grid: {vertLines: {color: "#22262B"}, horzLines: {color: "#22262B"}},
crosshair: {mode: CrosshairMode.Normal},
localization: {priceFormatter: (p: any) => p.toFixed(2)},
});
chartRef.current = chart;
chartRef.current.timeScale().fitContent();
const series = chart.addSeries(LineSeries, {lineWidth: 3, lineStyle: LineStyle.Solid});
lineRef.current = series;
}, [height, underlyingPrice]);
useEffect(() => {
if (!computed || !lineRef.current || !chartRef.current) return;
const {xs, ys} = computed;
lineRef.current.setData(xs.map((x, i) => ({time: x, value: ys[i]})));
}, [computed]);
if (!computed) {
return (
<div style={{padding: 12}}>
<h3 style={{margin: 0, color: "#e6edf3"}}>Options PnL</h3>
<p style={{color: "#8B949E"}}>Add some legs to plot.</p>
</div>
);
}
const {breakevens, maxProfit, maxLoss} = computed;
return (
<div style={{width: "100%"}}>
<div ref={containerRef} style={{width: "100%", height}}/>
<div style={{
display: "grid",
gridTemplateColumns: "repeat(6, minmax(0, 1fr))",
gap: 12,
padding: "10px 6px 0 6px",
color: "#C9D1D9",
fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica, Arial, Apple Color Emoji, Segoe UI Emoji",
fontSize: 13
}}>
<Stat label="Underlying" value={underlyingPrice.toFixed(2)}/>
<Stat label="Lot size" value={String(props.lotSize ?? 100)}/>
<Stat label="Breakevens"
value={breakevens.length ? breakevens.map(v => v.toFixed(2)).join(", ") : "—"}/>
<Stat label="Max profit*" value={fmtPnL(maxProfit)} positive/>
<Stat label="Max loss*" value={fmtPnL(maxLoss)} negative/>
<Stat label="Legs" value={props.legs.length.toString()}/>
</div>
<p style={{color: "#6e7681", fontSize: 11, marginTop: 6, marginLeft: 6}}>*Calculated over sampled domain;
unlimited-risk strategies may exceed these values.</p>
</div>
);
}
function Stat({label, value, positive, negative}: {
label: string;
value: string;
positive?: boolean;
negative?: boolean
}) {
const color = positive ? "#11C46A" : negative ? "#F85149" : "#C9D1D9";
return (
<div>
<div style={{color: "#8B949E", marginBottom: 2}}>{label}</div>
<div style={{color, fontWeight: 600}}>{value}</div>
</div>
);
}
function fmtPnL(v: number): string {
const sign = v >= 0 ? "+" : "";
return `${sign}${v.toFixed(2)}`;
}
export default function Demo() {
const [strategy, setStrategy] = useState<string>("iron-condor");
const {legs, spot} = useMemo(() => demoStrategy(strategy), [strategy]);
return (
<div style={{padding: 16}}>
<div style={{marginBottom: 8}}>
<select
value={strategy}
onChange={e => setStrategy(e.target.value)}
style={{
padding: 8,
background: "#0B0E11",
color: "#C9D1D9",
border: "1px solid #30363d",
borderRadius: 8
}}
>
<option value="iron-condor">Defined Risk: Iron Condor</option>
<option value="vertical-bear-call">Defined Risk: Bear Call Spread</option>
<option value="short-straddle">Undefined Risk: Short Straddle</option>
<option value="long-butterfly">Defined Risk: Long Call Butterfly</option>
<option value="long-call">Single: Long Call</option>
</select>
</div>
<OptionsPnLChart
legs={legs}
underlyingPrice={spot}
lotSize={100}
height={420}
/>
</div>
);
}
function demoStrategy(key: string): { legs: OptionLeg[]; spot: number } {
switch (key) {
case "vertical-bear-call":
return {
spot: 175,
legs: [
{right: "call", side: "short", strike: 180, premium: 4.20, qty: 1, label: "-C180"},
{right: "call", side: "long", strike: 185, premium: 2.20, qty: 1, label: "+C185"},
],
};
case "short-straddle":
return {
spot: 175,
legs: [
{right: "call", side: "short", strike: 175, premium: 7.50, qty: 1, label: "-C175"},
{right: "put", side: "short", strike: 175, premium: 7.10, qty: 1, label: "-P175"},
],
};
case "long-butterfly":
return {
spot: 175,
legs: [
{right: "call", side: "long", strike: 170, premium: 7.70, qty: 1, label: "+C170"},
{right: "call", side: "short", strike: 175, premium: 5.30, qty: 2, label: "-2*C175"},
{right: "call", side: "long", strike: 180, premium: 3.10, qty: 1, label: "+C180"},
],
};
case "long-call":
return {
spot: 175,
legs: [{right: "call", side: "long", strike: 180, premium: 3.60, qty: 1, label: "+C180"}]
};
case "iron-condor":
default:
return {
spot: 175,
legs: [
{right: "put", side: "short", strike: 170, premium: 3.20, qty: 1, label: "-P170"},
{right: "put", side: "long", strike: 165, premium: 1.70, qty: 1, label: "+P165"},
{right: "call", side: "short", strike: 180, premium: 3.10, qty: 1, label: "-C180"},
{right: "call", side: "long", strike: 185, premium: 1.40, qty: 1, label: "+C185"},
],
};
}
}Can you please provide a minimal example that takes custom options legs and plots the Options' Chart the way it is plotted at the https://www.tradingview.com/options/ page - without the Greeks.
@KatPierce, I can see you actively manage the documentation pages and examples. Can you please take a look on this? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
|
@SlicedSilver, @edew, @kirchet, and @KatPierce, as the latest and most active contributors of the project, could you please give me an answer? TL;DR:I'm trying to recreate TradingView's price-based options P&L chart using |
Beta Was this translation helpful? Give feedback.

The options chart on TradingView.com uses Lightweight Charts “plugins” in several places to add UI/behavior that isn’t available out-of-the-box. Those specific plugins used on tradingview.com aren’t open-sourced.
To recreate the visual you shared:
Notes and tips: