Πώς λαμβάνω δεδομένα επιλογών δωρεάν

Εισαγωγή στο web scraping για χρηματοδότηση

Θέλατε ποτέ να έχετε πρόσβαση σε δεδομένα ιστορικών επιλογών, αλλά αποκλείστηκε από ένα paywall; Τι γίνεται αν θέλετε απλώς για έρευνα, διασκέδαση ή για να αναπτύξετε μια προσωπική στρατηγική συναλλαγών;

Σε αυτό το σεμινάριο, θα μάθετε πώς να χρησιμοποιείτε το Python και το BeautifulSoup για να αποκόψετε οικονομικά δεδομένα από τον Ιστό και να δημιουργήσετε το δικό σας σύνολο δεδομένων.

Ξεκινώντας

Πριν ξεκινήσετε αυτό το σεμινάριο, θα πρέπει να έχετε τουλάχιστον γνώση της τεχνολογίας Python και Web. Για να τα δημιουργήσετε, συνιστώ ανεπιφύλακτα να επισκεφτείτε έναν ιστότοπο όπως το codecademy για να μάθετε νέες δεξιότητες ή να βελτιώσετε τα παλιά.

Αρχικά, ας ανοίξουμε το αγαπημένο σας IDE. Κανονικά, χρησιμοποιώ το PyCharm αλλά, για ένα γρήγορο σενάριο όπως αυτό το Repl.it θα κάνει τη δουλειά επίσης. Προσθέστε μια γρήγορη εκτύπωση ("Hello world") για να βεβαιωθείτε ότι το περιβάλλον σας έχει ρυθμιστεί σωστά.

Τώρα πρέπει να καταλάβουμε μια πηγή δεδομένων.

Δυστυχώς, τα καταπληκτικά δεδομένα αλυσίδας επιλογών της Cboe είναι αρκετά κλειδωμένα, ακόμη και για τις τρέχουσες καθυστερημένες προσφορές. Ευτυχώς, το Yahoo Finance διαθέτει αρκετά σταθερά δεδομένα επιλογών εδώ. Θα το χρησιμοποιήσουμε για αυτό το σεμινάριο, καθώς οι ξύπες ιστού χρειάζονται συχνά κάποια ευαισθητοποίηση περιεχομένου, αλλά είναι εύκολα προσαρμόσιμες για οποιαδήποτε πηγή δεδομένων θέλετε.

Εξαρτήσεις

Δεν χρειαζόμαστε πολλές εξωτερικές εξαρτήσεις. Χρειαζόμαστε μόνο τις μονάδες Requests and BeautifulSoup στο Python. Προσθέστε τα στην κορυφή του προγράμματος:

from bs4 import BeautifulSoupimport requests

Δημιουργήστε μια mainμέθοδο:

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

Ξύσιμο HTML

Τώρα είστε έτοιμοι να ξεκινήσετε το ξύσιμο! Στο εσωτερικό main(), προσθέστε αυτές τις γραμμές για να ανακτήσετε το σύνολο της σελίδας HTML:

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

Αυτό παίρνει το πλήρες HTMLπεριεχόμενο της σελίδας , έτσι μπορούμε να βρούμε τα δεδομένα που θέλουμε σε αυτήν. Μη διστάσετε να το κάνετε και να παρατηρήσετε την έξοδο.

Μη διστάσετε να σχολιάσετε τις έντυπες δηλώσεις καθώς πηγαίνετε - αυτές είναι ακριβώς εκεί για να σας βοηθήσουν να καταλάβετε τι κάνει το πρόγραμμα σε οποιοδήποτε δεδομένο βήμα.

Το BeautifulSoup είναι το τέλειο εργαλείο για εργασία με HTMLδεδομένα στην Python. Ας περιορίσουμε τους HTMLπίνακες τιμολόγησης επιλογών, ώστε να μπορούμε να το κατανοήσουμε καλύτερα:

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

Αυτό εξακολουθεί να είναι λίγο HTML- δεν μπορούμε να βγούμε πολύ από αυτό, και ο κώδικας του Yahoo δεν είναι ο πιο φιλικός για τις ξύστρες Ιστού. Ας το χωρίσουμε σε δύο πίνακες, για κλήσεις και βολές:

 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)

Τα δεδομένα του Yahoo περιέχουν επιλογές που είναι αρκετά βαθιές εντός και εκτός χρημάτων, οι οποίες μπορεί να είναι εξαιρετικές για συγκεκριμένους σκοπούς. Ενδιαφέρομαι μόνο για επιλογές που πλησιάζουν το χρήμα, δηλαδή τις δύο κλήσεις και τις δύο θέσεις πλησιέστερες στην τρέχουσα τιμή.

Ας τα βρούμε, χρησιμοποιώντας τις καταχωρίσεις πίνακα του BeautifulSoup και του Yahoo για επιλογές in-the-money και out-of-the-money:

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))

Τώρα, έχουμε τις καταχωρήσεις πίνακα για τις δύο επιλογές που βρίσκονται πλησιέστερα στα χρήματα HTML. Ας απομακρύνουμε τα δεδομένα τιμολόγησης, τον όγκο και την υπονοούμενη αστάθεια από την πρώτη επιλογή κλήσης:

 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)

Προσαρμόστε αυτόν τον κωδικό για την επόμενη επιλογή κλήσης:

# 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)

Δοκιμάστε το πρόγραμμά σας!

Τώρα έχετε λεξικά από τις δύο επιλογές κλήσεων σχεδόν με το χρήμα. Αρκεί απλώς να αφαιρέσετε τον πίνακα επιλογών για αυτά τα ίδια δεδομένα:

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]}

Συγχαρητήρια! Απλώς καταγράψατε δεδομένα για όλες τις επιλογές σχεδόν του χρήματος του S&P 500 ETF και μπορείτε να τα δείτε ως εξής:

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

Εκτελέστε το πρόγραμμά σας σε εκτέλεση - θα πρέπει να λάβετε δεδομένα όπως αυτό τυπωμένα στην κονσόλα:

{‘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%’}

Ρύθμιση επαναλαμβανόμενης συλλογής δεδομένων

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}

Εκτελέστε το νέο σας πρόγραμμα - θα διαγράψει τα πιο πρόσφατα δεδομένα επιλογών και θα το γράψει σε ένα αρχείο .csv ως παράσταση συμβολοσειράς ενός λεξικού. Το αρχείο .csv θα είναι έτοιμο να αναλυθεί από ένα πρόγραμμα δοκιμαστικών δοκιμών ή να προβληθεί στους χρήστες μέσω διαδικτυακής εφαρμογής. Συγχαρητήρια!