If you are looking for the original repo please checkout og
branch.
The Montreal Exchange is the oldest exchange in Canada and has a very important history to Canada's economy. Taken from Wikipedia,
The Montreal Exchange, formerly the Montreal Stock Exchange (MSE), is a derivatives exchange, located in Montreal, Quebec, Canada that trades futures contracts and options on equities, indices, currencies, ETFs, energy and interest rates. Since 1965, it has been located in the Tour de la Bourse (Stock Exchange Tower), Montreal's third-tallest building. It is owned by the Toronto-based TMX Group.
This README file is to showcase the functionality of this small web scraping module. It can be used to get options prices, both calls and puts for index and ETF options, equity options, currency options, and weekly options.
These prices are then displayed in a Pandas Dataframe for further analysis. This could include simple plots for visualizing the data or creating machine learning model or neural network to predict future prices. One could also use Black-Scholes model to gain further insight.
- Python 3.11.5
- Pandas 2.1.0 (older version will probably work)
- Requests 2.31.0 (older version will probably work)
- bs4 4.12.2 (for BeautifulSoup)
- lxml 5.3.0 used with bs4 to process xml and html
We start by showing a list of options available from TMX. There is one parameter for the function get_list()
and it can take one of four stings:
'Index'/'ETF', 'Equity', 'Currency', or 'Weekly'.
get_list('equity')
Name of underlying instrument | Option symbol | Underlying symbol | |
---|---|---|---|
0 | Aecon Group Inc. | ARE | ARE |
1 | AGF Management Ltd., Cl. B | AGF | AGF.B |
2 | Agnico Eagle Mines Limited | AEM | AEM |
3 | Air Canada | AC | AC |
4 | Alamos Gold Inc. | AGI | AGI |
... | ... | ... | ... |
264 | Wheaton Precious Metals Corp. | WPM | WPM |
265 | Whitecap Resources Inc. | WCP | WCP |
266 | Winpak Ltd. | WPK | WPK |
267 | WSP Global Inc. | WSP | WSP |
268 | Yamana Gold Inc. | YRI | YRI |
269 rows × 3 columns
The above DataFrame that we get with get_list('equity')
is from:
Now we can grab stock prices for Air Canada (ticker symbol AC) so we can compare them to the stock options:
get_stock('AC')
TICKER | Last price | Net change | Bid price | Ask price | |
---|---|---|---|---|---|
0 | AC | 15.520 | 0.000 | 15.520 | 15.550 |
We can also input a list of stock symbols to get a Pandas DataFrame of said stocks:
get_stock(['AC','ARE'])
TICKER | Last price | Net change | Bid price | Ask price | |
---|---|---|---|---|---|
0 | AC | 15.520 | 0.000 | 15.520 | 15.550 |
1 | ARE | 14.220 | 0.100 | 14.200 | 14.250 |
Finally, we obtain the a Pandas Dataframe of TMX stock options for Air Canada which comes from:
get('AC')
Call | Bid price | Ask price | Last price | Impl. vol. | Open int. | Vol. | Strike | Put | Bid price_ | Ask price_ | Last price.1 | Impl. vol_ | Open int_ | Vol_ | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Oct 23, 2020-W | 2.05 | 2.20 | 2.20 | 86.5% | 10 | 0 | 13.5 | Oct 23, 2020-W | 0.01 | 0.12 | 0.12 | 75.8% | 17 | 0 |
1 | Oct 23, 2020-W | 1.57 | 1.77 | 1.77 | 79.0% | 0 | 0 | 14.0 | Oct 23, 2020-W | 0.08 | 0.14 | 0.14 | 71.0% | 30 | 0 |
2 | Oct 23, 2020-W | 1.12 | 1.24 | 1.24 | 62.5% | 10 | 0 | 14.5 | Oct 23, 2020-W | 0.10 | 0.18 | 0.18 | 59.2% | 23 | 10 |
3 | Oct 23, 2020-W | 0.73 | 0.84 | 0.84 | 57.0% | 7 | 0 | 15.0 | Oct 23, 2020-W | 0.19 | 0.29 | 0.29 | 53.8% | 57 | 7 |
4 | Oct 23, 2020-W | 0.44 | 0.54 | 0.54 | 56.0% | 47 | 141 | 15.5 | Oct 23, 2020-W | 0.37 | 0.53 | 0.53 | 53.7% | 46 | 1 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
153 | Jan 20, 2023 | 3.55 | 7.35 | 7.35 | 76.4% | 2 | 0 | 21.0 | Jan 20, 2023 | 8.85 | 12.50 | 12.50 | 74.4% | 0 | 0 |
154 | Jan 20, 2023 | 3.30 | 7.20 | 7.20 | 76.3% | 0 | 0 | 22.0 | Jan 20, 2023 | 9.60 | 13.30 | 13.30 | 74.2% | 0 | 0 |
155 | Jan 20, 2023 | 3.10 | 7.00 | 7.00 | 76.2% | 0 | 0 | 23.0 | Jan 20, 2023 | 10.35 | 14.10 | 14.10 | 73.8% | 0 | 0 |
156 | Jan 20, 2023 | 2.84 | 6.80 | 6.80 | 75.6% | 0 | 0 | 24.0 | Jan 20, 2023 | 11.10 | 14.85 | 14.85 | 73.0% | 0 | 0 |
157 | Jan 20, 2023 | 3.15 | 5.00 | 5.00 | 69.3% | 32 | 7 | 25.0 | Jan 20, 2023 | 11.90 | 15.75 | 15.75 | 73.3% | 0 | 0 |
158 rows × 15 columns
import pandas as pd
import requests
from bs4 import BeautifulSoup
"""
Get a list of options from https://m-x.ca/nego_liste_en.php
TMX website
Index and ETF options
Equity options
Currency options
Weekly options
"""
def get_list(market=str) -> pd.DataFrame:
tmx = "https://m-x.ca/nego_liste_en.php" # TMX website, where data is taken from
#check that parameter is of type string
is_str1 = isinstance(market, str)
if not is_str1:
raise TypeError("market parameter must be of type string")
try:
market = market.lower()
except Exception as e:
print(e)
else:
if market == 'index' or market == 'etf':
market = 0
elif market == 'equity':
market = 1
elif market == 'currency':
market = 2
elif market == 'weekly':
market = 3
else:
raise Exception("Did not enter market type, choose from Index or ETF, Equity, Currency, Weekly.")
df = pd.read_html(tmx)
return df[market]
"""
Get options prices at predetermined dates from TMX website
Call/Puts
strike price
Bid/Ask spreads
open interest
implied volatility
volume
"""
def get(ticker_symbol=str) -> pd.DataFrame:
tmx = "https://m-x.ca/nego_cotes_en.php" # TMX website, where data is taken from
is_str1 = isinstance(ticker_symbol, str)
if not is_str1:
raise TypeError("ticker_symbol parameter must be of type string")
try:
ticker_symbol = ticker_symbol.upper()
except Exception as e:
print(e)
else:
url = tmx + '?symbol=' + ticker_symbol + '*'
df = pd.read_html(url)
df[0].rename(columns={'Bid price.1':'Bid price_', 'Ask price.1':'Ask price_', 'Last Price.1':'Last Price_',
'Impl. vol..1':'Impl. vol_', 'Open int..1':'Open int_', 'Vol..1':'Vol_'}, inplace=True)
return df[0].iloc[:-1] #do not include last row, rubbish information
"""
Get stock price from TMX to compare to strike price
can accept string or list of strings
"""
def get_stock(ticker_symbol=str) -> pd.DataFrame:
tmx = "https://m-x.ca/nego_cotes_en.php" # TMX website, where data is taken from
#check that parameter is of type string
is_str1 = checktype(ticker_symbol)
if not is_str1:
raise TypeError("market parameter must be of type string")
#download stock price, remember it is 15 minutes delayed
try:
symbols = []
for n in ticker_symbol:
symbols.append(n.upper())
except Exception as e:
print(e)
else:
price_dict = {}
is_list = isinstance(ticker_symbol, list)
if is_list:
df_list = []
for m in symbols:
URL = tmx + '?symbol=' + m + '*'
response = requests.get(URL)
soup = BeautifulSoup(response.text, 'html.parser')
x = soup.find('div', class_ = 'quote-info', attrs = 'ul')
y = x.ul.text.split('\n')[1:-2]
price_dict['TICKER'] = m
for z in y:
key, value = z.split(':')
price_dict[key] = value
tmp_df = pd.DataFrame.from_dict(price_dict, orient='index').T
df_list.append(tmp_df)
return pd.concat(df_list, ignore_index=True)
else:
ticker_symbol = ticker_symbol.upper()
URL = tmx + '?symbol=' + ticker_symbol + '*'
response = requests.get(URL)
soup = BeautifulSoup(response.text, 'html.parser')
x = soup.find('div', class_ = 'quote-info', attrs = 'ul')
y = x.ul.text.split('\n')[1:-2]
price_dict['TICKER'] = ticker_symbol
for z in y:
key, value = z.split(':')
price_dict[key] = value
tmp_df = pd.DataFrame.from_dict(price_dict, orient='index').T
return tmp_df
def checktype(obj):
return bool(obj) and all(isinstance(elem, str) for elem in obj)