Hogyan kapok ingyen opciós adatokat

Bevezetés a pénzügyi kaparáshoz

Azt kívánta valaha, hogy hozzáférjen a korábbi opciós adatokhoz, de egy fizetőfal blokkolta? Mi van, ha csak kutatásra, szórakozásra vagy személyes kereskedési stratégia kidolgozására vágyik?

Ebben az oktatóanyagban megtudhatja, hogyan használhatja a Python és a BeautifulSoup programot pénzügyi adatok webről történő lekaparására és saját adatkészletének elkészítésére.

Elkezdeni

Az oktatóanyag megkezdése előtt legalább ismernie kell a Python és a web technológiákat. Ezek felépítéséhez nagyon ajánlom, hogy nézzen meg egy olyan webhelyet, mint a codecademy, hogy új készségeket tanuljon, vagy feljavítsa a régieket.

Először pörgessük fel kedvenc IDE-jét. Normál esetben a PyCharm-ot használom, de egy ilyen gyors szkripthez a Repl.the is elvégzi a munkát. Gyors nyomtatás („Hello world”) hozzáadása a környezet megfelelő beállításához.

Most ki kell találnunk egy adatforrást.

Sajnos a Cboe félelmetes opciós láncadatai eléggé zárolva vannak, még a jelenlegi késleltetett árajánlatok esetében is. Szerencsére a Yahoo Finance elég szilárd opciós adatokkal rendelkezik itt. Ezt az oktatóanyagot fogjuk használni, mivel a webes lehúzónak gyakran kell némi tartalomtudat, de könnyen adaptálható minden kívánt adatforráshoz.

Függőségek

Nincs szükségünk sok külső függőségre. Csak a Requests és a BeautifulSoup modulokra van szükségünk a Pythonban. Adja hozzá ezeket a program tetejéhez:

from bs4 import BeautifulSoupimport requests

Hozzon létre egy mainmódszert:

def main(): print(“Hello World!”)if __name__ == “__main__”: main()

HTML kaparása

Most már készen áll a kaparásra! Belül main()ezeket a sorokat adja hozzá az oldal teljes letöltéséhez HTML:

data_url = “//finance.yahoo.com/quote/SPY/options"data_html = requests.get(data_url).contentprint(data_html)

Ez lekéri az oldal teljes HTMLtartalmát, így megtalálhatjuk benne a kívánt adatokat. Nyugodtan futtasson és figyelje meg a kimenetet.

Nyugodtan kommentálja a nyomtatott utasításokat menet közben - ezek csak azért vannak, hogy segítsen megérteni, hogy a program mit csinál az adott lépésben.

A BeautifulSoup tökéletes eszköz az HTMLadatok Python-ban történő kezeléséhez. Szűkítsük le az HTMLopciók árazási táblázatait, hogy jobban megértsük:

 content = BeautifulSoup(data_html, “html.parser”) # print(content)
 options_tables = content.find_all(“table”) print(options_tables)

Ez még mindig elég HTMLkevés - ebből nem sokat tudunk kihozni, és a Yahoo kódja sem a legbarátságosabb a webkaparókkal szemben. Bontjuk fel két táblára a hívásokhoz és a következőhöz:

 options_tables = [] tables = content.find_all(“table”) for i in range(0, len(content.find_all(“table”))): options_tables.append(tables[i])
 print(options_tables)

A Yahoo adatai olyan opciókat tartalmaznak, amelyek meglehetősen mélyek a pénzben és a pénzből, ami bizonyos célokra nagyszerű lehet. Csak a pénzhez közeli lehetőségek érdekelnek, nevezetesen a két hívás és a két legközelebb eső ár a jelenlegi árhoz.

Keressük meg ezeket a BeautifulSoup és a Yahoo differenciál tábla bejegyzéseivel a pénzben és a pénzben opcióknál:

expiration = datetime.datetime.fromtimestamp(int(datestamp)).strftime(“%Y-%m-%d”)
calls = options_tables[0].find_all(“tr”)[1:] # first row is header
itm_calls = []otm_calls = []
for call_option in calls: if “in-the-money” in str(call_option): itm_calls.append(call_option) else: otm_calls.append(call_option)
itm_call = itm_calls[-1]otm_call = otm_calls[0]
print(str(itm_call) + “ \n\n “ + str(otm_call))

Most van a táblázatban szereplő bejegyzés a két lehetőséghez, amelyek legközelebb vannak a pénzhez HTML. Kaparjuk meg az árképzési adatokat, mennyiséget és implicit volatilitást az első hívási opcióból:

 itm_call_data = [] for td in BeautifulSoup(str(itm_call), “html.parser”).find_all(“td”): itm_call_data.append(td.text)
print(itm_call_data)
itm_call_info = {‘contract’: itm_call_data[0], ‘strike’: itm_call_data[2], ‘last’: itm_call_data[3], ‘bid’: itm_call_data[4], ‘ask’: itm_call_data[5], ‘volume’: itm_call_data[8], ‘iv’: itm_call_data[10]}
print(itm_call_info)

Adja hozzá ezt a kódot a következő hívási lehetőséghez:

# otm callotm_call_data = []for td in BeautifulSoup(str(otm_call), “html.parser”).find_all(“td”): otm_call_data.append(td.text)
# print(otm_call_data)
otm_call_info = {‘contract’: otm_call_data[0], ‘strike’: otm_call_data[2], ‘last’: otm_call_data[3], ‘bid’: otm_call_data[4], ‘ask’: otm_call_data[5], ‘volume’: otm_call_data[8], ‘iv’: otm_call_data[10]}
print(otm_call_info)

Futtassa programját!

Most már rendelkezik a két pénzközeli hívási lehetőség szótárával. Elég csak összekaparni az eladási opciók táblázatot ugyanezen adatokhoz:

puts = options_tables[1].find_all("tr")[1:] # first row is header
itm_puts = [] otm_puts = []
for put_option in puts: if "in-the-money" in str(put_option): itm_puts.append(put_option) else: otm_puts.append(put_option)
itm_put = itm_puts[0] otm_put = otm_puts[-1]
# print(str(itm_put) + " \n\n " + str(otm_put) + "\n\n")
itm_put_data = [] for td in BeautifulSoup(str(itm_put), "html.parser").find_all("td"): itm_put_data.append(td.text)
# print(itm_put_data)
itm_put_info = {'contract': itm_put_data[0], 'last_trade': itm_put_data[1][:10], 'strike': itm_put_data[2], 'last': itm_put_data[3], 'bid': itm_put_data[4], 'ask': itm_put_data[5], 'volume': itm_put_data[8], 'iv': itm_put_data[10]}
# print(itm_put_info)
# otm put otm_put_data = [] for td in BeautifulSoup(str(otm_put), "html.parser").find_all("td"): otm_put_data.append(td.text)
# print(otm_put_data)
otm_put_info = {'contract': otm_put_data[0], 'last_trade': otm_put_data[1][:10], 'strike': otm_put_data[2], 'last': otm_put_data[3], 'bid': otm_put_data[4], 'ask': otm_put_data[5], 'volume': otm_put_data[8], 'iv': otm_put_data[10]}

Gratulálunk! Csak lekapartad az S&P 500 ETF összes pénzközeli opciójának adatait, és így nézheted meg őket:

 print("\n\n") print(itm_call_info) print(otm_call_info) print(itm_put_info) print(otm_put_info)

Futtassa a programot - ehhez hasonló adatokat kell kinyomtatnia a konzolra:

{‘contract’: ‘SPY190417C00289000’, ‘last_trade’: ‘2019–04–15’, ‘strike’: ‘289.00’, ‘last’: ‘1.46’, ‘bid’: ‘1.48’, ‘ask’: ‘1.50’, ‘volume’: ‘4,646’, ‘iv’: ‘8.94%’}{‘contract’: ‘SPY190417C00290000’, ‘last_trade’: ‘2019–04–15’, ‘strike’: ‘290.00’, ‘last’: ‘0.80’, ‘bid’: ‘0.82’, ‘ask’: ‘0.83’, ‘volume’: ‘38,491’, ‘iv’: ‘8.06%’}{‘contract’: ‘SPY190417P00290000’, ‘last_trade’: ‘2019–04–15’, ‘strike’: ‘290.00’, ‘last’: ‘0.77’, ‘bid’: ‘0.75’, ‘ask’: ‘0.78’, ‘volume’: ‘11,310’, ‘iv’: ‘7.30%’}{‘contract’: ‘SPY190417P00289000’, ‘last_trade’: ‘2019–04–15’, ‘strike’: ‘289.00’, ‘last’: ‘0.41’, ‘bid’: ‘0.40’, ‘ask’: ‘0.42’, ‘volume’: ‘44,319’, ‘iv’: ‘7.79%’}

Ismétlődő adatgyűjtés beállítása

Yahoo, by default, only returns the options for the date you specify. It’s this part of the URL: //finance.yahoo.com/quote/SPY/options?date=1555459200

This is a Unix timestamp, so we’ll need to generate or scrape one, rather than hardcoding it in our program.

Add some dependencies:

import datetime, time

Let’s write a quick script to generate and verify a Unix timestamp for our next set of options:

def get_datestamp(): options_url = “//finance.yahoo.com/quote/SPY/options?date=" today = int(time.time()) # print(today) date = datetime.datetime.fromtimestamp(today) yy = date.year mm = date.month dd = date.day

The above code holds the base URL of the page we are scraping and generates a datetime.date object for us to use in the future.

Let’s increment this date by one day, so we don’t get options that have already expired.

dd += 1

Now, we need to convert it back into a Unix timestamp and make sure it’s a valid date for options contracts:

 options_day = datetime.date(yy, mm, dd) datestamp = int(time.mktime(options_day.timetuple())) # print(datestamp) # print(datetime.datetime.fromtimestamp(options_stamp))
 # vet timestamp, then return if valid for i in range(0, 7): test_req = requests.get(options_url + str(datestamp)).content content = BeautifulSoup(test_req, “html.parser”) # print(content) tables = content.find_all(“table”)
 if tables != []: # print(datestamp) return str(datestamp) else: # print(“Bad datestamp!”) dd += 1 options_day = datetime.date(yy, mm, dd) datestamp = int(time.mktime(options_day.timetuple())) return str(-1)

Let’s adapt our fetch_options method to use a dynamic timestamp to fetch options data, rather than whatever Yahoo wants to give us as the default.

Change this line:

data_url = “//finance.yahoo.com/quote/SPY/options"

To this:

datestamp = get_datestamp()data_url = “//finance.yahoo.com/quote/SPY/options?date=" + datestamp

Congratulations! You just scraped real-world options data from the web.

Now we need to do some simple file I/O and set up a timer to record this data each day after market close.

Improving the program

Rename main() to fetch_options() and add these lines to the bottom:

options_list = {‘calls’: {‘itm’: itm_call_info, ‘otm’: otm_call_info}, ‘puts’: {‘itm’: itm_put_info, ‘otm’: otm_put_info}, ‘date’: datetime.date.fromtimestamp(time.time()).strftime(“%Y-%m-%d”)}return options_list

Create a new method called schedule(). We’ll use this to control when we scrape for options, every twenty-four hours after market close. Add this code to schedule our first job at the next market close:

from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler()
def schedule(): scheduler.add_job(func=run, trigger=”date”, run_date = datetime.datetime.now()) scheduler.start()

In your if __name__ == “__main__”: statement, delete main() and add a call to schedule() to set up your first scheduled job.

Create another method called run(). This is where we’ll handle the bulk of our operations, including actually saving the market data. Add this to the body of run():

 today = int(time.time()) date = datetime.datetime.fromtimestamp(today) yy = date.year mm = date.month dd = date.day
 # must use 12:30 for Unix time instead of 4:30 NY time next_close = datetime.datetime(yy, mm, dd, 12, 30)
 # do operations here “”” This is where we’ll write our last bit of code. “””
 # schedule next job scheduler.add_job(func=run, trigger=”date”, run_date = next_close)
 print(“Job scheduled! | “ + str(next_close))

This lets our code call itself in the future, so we can just put it on a server and build up our options data each day. Add this code to actually fetch data under “”” This is where we’ll write our last bit of code. “””

options = {}
 # ensures option data doesn’t break the program if internet is out try: if next_close > datetime.datetime.now(): print(“Market is still open! Waiting until after close…”) else: # ensures program was run after market hours if next_close < datetime.datetime.now(): dd += 1 next_close = datetime.datetime(yy, mm, dd, 12, 30) options = fetch_options() print(options) # write to file write_to_csv(options)except: print(“Check your connection and try again.”)

Saving data

You may have noticed that write_to_csv isn’t implemented yet. No worries — let’s take care of that here:

def write_to_csv(options_data): import csv with open(‘options.csv’, ‘a’, newline=’\n’) as csvfile: spamwriter = csv.writer(csvfile, delimiter=’,’) spamwriter.writerow([str(options_data)])

Cleaning up

As options contracts are time-sensitive, we might want to add a field for their expiration date. This capability is not included in the raw HTML we scraped.

Add this line of code to save and format the expiration date towards the top of fetch_options():

expiration = datetime.datetime.fromtimestamp(int(get_datestamp())).strftime("%Y-%m-%d")

Add ‘expiration’: expiration to the end of each option_info dictionary like so:

itm_call_info = {'contract': itm_call_data[0], 'strike': itm_call_data[2], 'last': itm_call_data[3], 'bid': itm_call_data[4], 'ask': itm_call_data[5], 'volume': itm_call_data[8], 'iv': itm_call_data[10], 'expiration': expiration}

Give your new program a run — it’ll scrape the latest options data and write it to a .csv file as a string representation of a dictionary. The .csv file will be ready to be parsed by a backtesting program or served to users through a webapp. Congratulations!