Option pricing

This notebook continues the evaluation of the Schwab API with option price data. October calls and puts are examined. Prices are calculated using the implied volatility measure given by Schwab and the implied volatility calculation from the Black-Scholes call formula. The results suggest that you should do your own price calculations from the data. The volatility offered by the API appears not to be calculated from the data. The FinancialDerivative[] models seem to work well with a volatility parameter calculated by a Black-Scholes fit to the data.
​
There have been no changes in the package code since the last post.

Get option chain

In[]:=
Needs["SchwabAPI`"];​​apiTokens=Import[tokenFile,"RawJSON"];​​apiTokens=refreshTokens[apiTokens["refresh_token"],appKey,appSecret];
In[]:=
ticker="LLY";
In[]:=
opts=SchwabGetOptionChain["LLY",apiTokens["access_token"],"strikeCount"50];​​If[opts===$Failed,Abort[]]
In[]:=
exp=getExpirationChain[ticker,apiTokens["access_token"]];​​If[exp===$Failed,Abort[]]
In[]:=
expDS=exp["expirationList"]//Dataset[#,MaxItems->12]&
Out[]=
expirationDate
daysToExpiration
expirationType
settlementType
optionRoots
standard
2025-05-09
4
W
P
LLY
True
2025-05-16
11
S
P
LLY
True
2025-05-23
18
W
P
LLY
True
2025-05-30
25
W
P
LLY
True
2025-06-06
32
W
P
LLY
True
2025-06-13
39
W
P
LLY
True
2025-06-20
46
S
P
LLY
True
2025-07-18
74
S
P
LLY
True
2025-08-15
102
S
P
LLY
True
2025-09-19
137
S
P
LLY
True
2025-10-17
165
S
P
LLY
True
2025-12-19
228
S
P
LLY
True
rows 1–12 of 18

Get option prices

This shows the data structure of the pricing for call options.
In[]:=
calls=opts["callExpDateMap"];
In[]:=
calls//Dataset
Out[]=
putCall
symbol
description
exchangeName
bid
ask
last
mark
bidSize
askSize
2025-05-09:4
757.5
CALL
LLY 250509C00757500
LLY 05/09/2025 757.50 C
OPR
66.8
72.4
52.25
69.6
45
47
760.0
CALL
LLY 250509C00760000
LLY 05/09/2025 760.00 C
OPR
64.8
69.8
68.32
67.3
49
52
762.5
CALL
LLY 250509C00762500
LLY 05/09/2025 762.50 C
OPR
60.0
67.7
55.0
63.85
44
45
765.0
CALL
LLY 250509C00765000
LLY 05/09/2025 765.00 C
OPR
59.6
65.1
61.72
62.35
41
55
50 total ›
2025-05-16:11
757.5
CALL
LLY 250516C00757500
LLY 05/16/2025 757.50 C
OPR
69.4
74.1
56.05
71.75
38
36
760.0
CALL
LLY 250516C00760000
LLY 05/16/2025 760.00 C
OPR
67.55
71.6
68.6
69.57
31
29
762.5
CALL
LLY 250516C00762500
LLY 05/16/2025 762.50 C
OPR
65.65
67.65
62.95
66.65
18
25
765.0
CALL
LLY 250516C00765000
LLY 05/16/2025 765.00 C
OPR
63.25
65.85
65.25
64.55
36
53
50 total ›
2025-05-23:18
725.0
CALL
LLY 250523C00725000
LLY 05/23/2025 725.00 C
OPR
101.7
106.75
103.37
104.23
34
45
730.0
CALL
LLY 250523C00730000
LLY 05/23/2025 730.00 C
OPR
96.45
101.45
157.65
98.95
48
36
735.0
CALL
LLY 250523C00735000
LLY 05/23/2025 735.00 C
OPR
91.75
96.7
139.85
94.23
50
33
740.0
CALL
LLY 250523C00740000
LLY 05/23/2025 740.00 C
OPR
87.6
92.05
148.75
89.82
28
31
50 total ›
2025-05-30:25
705.0
CALL
LLY 250530C00705000
LLY 05/30/2025 705.00 C
OPR
120.05
126.35
109.95
123.2
53
38
710.0
CALL
LLY 250530C00710000
LLY 05/30/2025 710.00 C
OPR
117.2
121.65
165.95
119.43
32
29
715.0
CALL
LLY 250530C00715000
LLY 05/30/2025 715.00 C
OPR
112.55
116.9
113.85
114.73
27
29
720.0
CALL
LLY 250530C00720000
LLY 05/30/2025 720.00 C
OPR
107.7
112.5
98.15
110.1
31
36
50 total ›
2025-06-06:32
705.0
CALL
LLY 250606C00705000
LLY 06/06/2025 705.00 C
OPR
121.1
129.35
124.0
125.23
45
43
710.0
CALL
LLY 250606C00710000
LLY 06/06/2025 710.00 C
OPR
117.05
124.05
164.0
120.55
42
42
715.0
CALL
LLY 250606C00715000
LLY 06/06/2025 715.00 C
OPR
111.6
119.8
0.0
115.7
45
44
720.0
CALL
LLY 250606C00720000
LLY 06/06/2025 720.00 C
OPR
107.1
114.9
112.85
111.0
45
41
50 total ›
rows 1–5 of 18
columns 1–10 of 52
Data not saved. Save now
Here is the bid pricing for the October expiration option chain. It is obviously based on a formula and not trading. In the option markets like the bond markets prices are set by the dealers, but at least in the options market you can place limit orders and wait for the dealers to match your price.
In[]:=
octOpt=Select[Keys@calls,StringContainsQ[#,"2025-10-17"]&][[1]]
Out[]=
2025-10-17:165
In[]:=
callPrices={#[["strikePrice"]],#[["bid"]]}&/@Flatten[Values[calls[octOpt]],1];
In[]:=
puts=opts["putExpDateMap"];​​putPrices={#[["strikePrice"]],#[["bid"]]}&/@Flatten[Values[puts[octOpt]],1];
Put prices are shown as negative values to highlight the symmetry between puts and calls.
In[]:=
g0=Plot[curprice-k,{k,500,1100},PlotStyle->{Black,Thin}];​​g1=ListPlot[{callPrices,{#1,-#2}&@@@putPrices},PlotRange->All,Frame->True,FrameLabel->{"Strike Price","Price"},PlotLabel->"Call and Put Prices"];​​Show[g1,g0]
Out[]=

Fitting the option chain using Mathematica functions and Schwab API parameters.

Here are the parameters available.
In[]:=
Keys@opts
Out[]=
{symbol,status,underlying,strategy,interval,isDelayed,isIndex,interestRate,underlyingPrice,volatility,daysToExpiration,dividendYield,numberOfContracts,assetMainType,assetSubType,isChainTruncated,callExpDateMap,putExpDateMap}
In[]:=
vol=opts["volatility"]/100​​intrate=opts["interestRate"]/100​​curprice=opts["underlyingPrice"]​​days=DateDifference[Today,StringSplit[octOpt,":"][[1]]][[1]]​​div=opts["dividendYield"]
Out[]=
0.29
Out[]=
0.0425
Out[]=
825.605
Out[]=
165
Out[]=
-1.
The minus 1. returned appears to be an error code. The dividend rate is from FinancialData[].
In[]:=
div=4QuantityMagnitude@FinancialData[ticker,"Dividend"]/curprice
Out[]=
0.0072674
In[]:=
americanCall[strike_?NumericQ,days_,intrate_,volatility_?NumericQ,dividend_,curprice_]:=FinancialDerivative[{"American","Call"},{"StrikePrice"strike,"Expiration"days/365},{"InterestRate"intrate,"Volatility"volatility,"CurrentPrice"curprice,"Dividend"dividend}];​​americanPut[strike_,days_,intrate_,volatility_,dividend_,curprice_]:=FinancialDerivative[{"American","Put"},{"StrikePrice"strike,"Expiration"days/365},{"InterestRate"intrate,"Volatility"volatility,"CurrentPrice"curprice,"Dividend"dividend}];
In[]:=
g2=Plot[{americanCall[k,days,intrate,vol,div,curprice],-americanPut[k,days,intrate,vol,div,curprice]},{k,500,1100}];
Here are the fits; they are not very good. The shape suggests that the volatility parameter is probably wrong.
In[]:=
Show[g1,g2,g0]
Out[]=

Black Scholes model to calculate volatility

Black-Scholes fit with NonlinearModelFit[]
The fit is a may be a little off but on most runs performs well.
Curves using the Black-Scholes volatility calculation look much better.
Trying to trick NonlinearModelFit[] into using americanCall doesn’t work, even with starting point. Volatility comes out way too high.

CITE THIS NOTEBOOK

Evaluating Schwab API option pricing: implied volatility and Black-Scholes analysis​
by Robert Rimmer​
Wolfram Community, STAFF PICKS, May 6, 2025
​https://community.wolfram.com/groups/-/m/t/3455558