Your Python script is designed to filter both call and put options whose strike prices are within ±10% of the current stock price. This ensures that only relevant option contracts are considered for further analysis. Here's a breakdown of how this is achieved:
The script utilizes the following condition to enforce the 10% strike price range:
if abs(stock_price - strike_price) / stock_price > PRICE_WITHIN_PERCENT:
continue
With PRICE_WITHIN_PERCENT
set to 0.1
, this condition effectively includes only those options where the strike price is within 10% above or below the current stock price. For instance, if stock_price = $1.00
, only strike prices between $0.90
and $1.10
are considered.
The script processes both 'calls'
and 'puts'
within each option chain, ensuring comprehensive coverage of available options that meet the specified criteria. This dual processing ensures that both bullish and bearish strategies are accommodated.
While your intention is to sort the CSV output by the ask
price, the current implementation sorts the data by the bid
price. This discrepancy needs to be addressed to align the output with your requirements.
The existing sorting logic is as follows:
for option in sorted(filtered_options, key=lambda x: x['bid']):
writer.writerow(option)
This sorts the options in ascending order based on the bid
price.
To sort the CSV by the ask
price instead, you should modify the sorting key to ask
as shown below:
for option in sorted(filtered_options, key=lambda x: x['ask']):
writer.writerow(option)
This change ensures that the options are listed from the lowest to the highest ask
price, facilitating better analysis based on ask prices.
Encountering the message "No options data to export." indicates that no options met all the filtering criteria specified in your script. Several factors could contribute to this outcome:
Your script uses an API key set to "cu"
, which is likely invalid. An invalid API key would prevent successful data retrieval from Finnhub.io. To resolve this:
API_KEY = "YOUR_VALID_API_KEY" # Replace with your actual API key
The combination of filters might be too stringent, leading to no options passing all the criteria. Key areas to review include:
OPEN_INTEREST_THRESHOLD
set to 100
: If few options have open interest above this threshold, they will be excluded.EXPIRY_MIN_DAYS
and EXPIRY_MAX_DAYS
set to 25
and 120
respectively: This limits the options to those expiring within 25 to 120 days.PRICE_WITHIN_PERCENT
set to 0.1
: Only options within 10% of the current price are considered.To diagnose and potentially mitigate this issue:
OPEN_INTEREST_THRESHOLD
to a smaller number (e.g., 10
) to see if more options are included.PRICE_WITHIN_PERCENT
to a higher value (e.g., 0.2
for 20%) to include a broader set of strike prices.Exceeding API rate limits can result in incomplete data retrieval. Ensure that your script respects the rate limits imposed by Finnhub.io by appropriately setting the RATE_LIMIT_DELAY
.
Additionally, verify that options data is available for the tickers you're querying. Some large-cap tickers may have limited or no options data available.
Incorporate additional print statements to monitor the data being fetched and processed. For example:
print(f"Stock price for {ticker}: {stock_price}")
print(f"Options data for {ticker}: {options_data}")
This will help identify whether the issue stems from data retrieval failures or stringent filtering.
Here's an updated snippet of your script incorporating the necessary corrections and debugging statements:
import requests
import csv
from datetime import datetime, timedelta
import time
# Finnhub.io API key
API_KEY = "YOUR_VALID_API_KEY" # Replace with your actual API key
BASE_URL = "https://finnhub.io/api/v1"
# Large-cap tickers
LARGE_CAP_TICKERS = [
"A", "AA", "AAOI", "AAL", "AAMC", "AAMRQ", "AAN", "AAPL", "AAPU", "AAXJ", "AB", "ABCB"
]
# Define filters
OPEN_INTEREST_THRESHOLD = 100
EXPIRY_MIN_DAYS = 25
EXPIRY_MAX_DAYS = 120
PRICE_WITHIN_PERCENT = 0.1 # 10%
RATE_LIMIT_DELAY = 1 # Delay between API calls to respect rate limits
def get_stock_price(ticker):
"""Get current stock price from Finnhub"""
url = f"{BASE_URL}/quote"
params = {
"symbol": ticker,
"token": API_KEY
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
print(f"Current price for {ticker}: {data.get('c')}")
return data.get('c') # Current price
except Exception as e:
print(f"Error fetching price for {ticker}: {e}")
return None
def fetch_options_chain(ticker):
"""Fetch options chain data from Finnhub"""
url = f"{BASE_URL}/stock/option-chain"
params = {
"symbol": ticker,
"token": API_KEY
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
print(f"Options data for {ticker}: {data}")
return data
except Exception as e:
print(f"Error fetching options chain for {ticker}: {e}")
return None
def parse_expiry_date(date_str):
"""Parse expiry date string to datetime object"""
try:
# Try parsing ISO format (YYYY-MM-DD)
return datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
try:
# Try parsing other common formats
return datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
except ValueError:
print(f"Unrecognized date format: {date_str}")
return None
def fetch_and_filter_options(tickers):
filtered_options = []
today = datetime.now()
for ticker in tickers:
print(f"Fetching options for {ticker}...")
# Get current stock price
stock_price = get_stock_price(ticker)
if not stock_price:
print(f"Could not get price for {ticker}, skipping...")
continue
# Get options chain
options_data = fetch_options_chain(ticker)
if not options_data or 'data' not in options_data:
print(f"No options data available for {ticker}")
continue
# Process each expiration date
for chain in options_data.get('data', []):
expiry_date = parse_expiry_date(chain.get('expirationDate'))
if not expiry_date:
continue
# Check if expiration is within our desired range
days_to_expiry = (expiry_date - today).days
if not (EXPIRY_MIN_DAYS <= days_to_expiry <= EXPIRY_MAX_DAYS):
print(f"Option for {ticker} expired in {days_to_expiry} days, excluding...")
continue
# Process calls and puts
for option_type in ['calls', 'puts']:
for option in chain.get(option_type, []):
try:
strike_price = float(option.get('strike', 0))
# Check if strike is within our desired range of current price
if abs(stock_price - strike_price) / stock_price > PRICE_WITHIN_PERCENT:
print(f"Strike price {strike_price} out of range for {ticker}, excluding...")
continue
# Check open interest threshold
if option.get('openInterest', 0) < OPEN_INTEREST_THRESHOLD:
print(f"Open interest {option.get('openInterest')} too low for {ticker}, excluding...")
continue
filtered_options.append({
"ticker": ticker,
"expiration_date": expiry_date.strftime("%Y-%m-%d"),
"strike_price": strike_price,
"option_type": option_type.rstrip('s'), # 'calls' -> 'call'
"bid": option.get('bid', 0),
"ask": option.get('ask', 0),
"last_price": option.get('lastPrice', 0),
"open_interest": option.get('openInterest', 0),
"implied_volatility": option.get('impliedVolatility', 0),
"delta": option.get('delta', 0),
"underlying_price": stock_price
})
except Exception as e:
print(f"Error processing option: {e}")
continue
# Respect API rate limits
time.sleep(RATE_LIMIT_DELAY)
return filtered_options
def export_to_csv(filtered_options):
if not filtered_options:
print("No options data to export.")
return
with open("options_research.csv", mode="w", newline="") as file:
writer = csv.DictWriter(file, fieldnames=[
"ticker", "expiration_date", "strike_price", "option_type",
"bid", "ask", "last_price", "open_interest", "implied_volatility",
"delta", "underlying_price"
])
writer.writeheader()
# Sort by ask price for easier analysis
for option in sorted(filtered_options, key=lambda x: x['ask']):
writer.writerow(option)
def main():
print("Fetching and filtering options...")
filtered_options = fetch_and_filter_options(LARGE_CAP_TICKERS)
print(f"Found {len(filtered_options)} options matching criteria.")
print("Exporting to CSV...")
export_to_csv(filtered_options)
print("Export complete: options_research.csv")
if __name__ == "__main__":
main()
To further enhance the reliability and functionality of your script, consider implementing the following recommendations:
Instead of hardcoding filter values like OPEN_INTEREST_THRESHOLD
and PRICE_WITHIN_PERCENT
, allow these to be configurable parameters. This provides flexibility to adjust the filtering criteria without modifying the codebase.
Implement robust error handling to manage unexpected API responses or data anomalies. This includes handling cases where specific fields might be missing or contain unexpected values.
Incorporate a logging system to record the script's operations, successes, and failures. This aids in troubleshooting and provides a historical record of script executions.
Ensure that your script adheres to the API rate limits specified by Finnhub.io. Adjust the RATE_LIMIT_DELAY
as necessary to prevent throttling or temporary bans.
Before processing, validate the integrity and completeness of the fetched data. This ensures that only reliable and accurate data is used for analysis and export.
Your Python script effectively filters call and put options within a 10% range of the current stock price, ensuring focus on the most relevant option contracts. However, the current sorting mechanism prioritizes bid prices instead of ask prices, which can be rectified by modifying the sorting key. The issue of no data export is primarily due to an invalid API key or overly stringent filtering criteria. By addressing these areas and implementing the recommended enhancements, your script will function more reliably and provide valuable insights through the exported CSV data.