Add strikeLimit parameter and refresh docs
This commit is contained in:
621
AGENTS.md
621
AGENTS.md
@@ -3,8 +3,9 @@
|
||||
## Context
|
||||
- This project exposes a Flask API that uses Playwright to scrape Yahoo Finance options chains.
|
||||
- Entry point: `scraper_service.py` (launched via `runner.bat` or directly with Python).
|
||||
- API route: `GET /scrape_sync` with `stock` and optional `expiration|expiry|date` parameters.
|
||||
- API route: `GET /scrape_sync` with `stock`, optional `expiration|expiry|date`, and `strikeLimit` parameters.
|
||||
- Expiration inputs: epoch seconds (Yahoo date param) or date strings supported by `DATE_FORMATS`.
|
||||
- strikeLimit defaults to 25 and controls the number of nearest strikes returned per side.
|
||||
|
||||
## Docker
|
||||
- Build: `docker build -t <image>:latest .`
|
||||
@@ -314,307 +315,329 @@
|
||||
- Line 299: Return a value to the caller. Code: `return []`
|
||||
- Line 300: Blank line for readability. Code: `<blank>`
|
||||
- Line 301: Blank line for readability. Code: `<blank>`
|
||||
- Line 302: Define the scrape_yahoo_options function. Code: `def scrape_yahoo_options(symbol, expiration=None):`
|
||||
- Line 303: Define the parse_table function. Code: `def parse_table(table_html, side):`
|
||||
- Line 304: Conditional branch. Code: `if not table_html:`
|
||||
- Line 305: Emit or configure a log message. Code: `app.logger.warning("No %s table HTML for %s", side, symbol)`
|
||||
- Line 306: Return a value to the caller. Code: `return []`
|
||||
- Line 307: Blank line for readability. Code: `<blank>`
|
||||
- Line 308: Execute the statement as written. Code: `soup = BeautifulSoup(table_html, "html.parser")`
|
||||
- Line 309: Blank line for readability. Code: `<blank>`
|
||||
- Line 310: Extract header labels from the table. Code: `headers = [th.get_text(strip=True) for th in soup.select("thead th")]`
|
||||
- Line 311: Collect table rows for parsing. Code: `rows = soup.select("tbody tr")`
|
||||
- Line 312: Blank line for readability. Code: `<blank>`
|
||||
- Line 313: Initialize the parsed rows list. Code: `parsed = []`
|
||||
- Line 314: Loop over items. Code: `for r in rows:`
|
||||
- Line 315: Collect table cells for the current row. Code: `tds = r.find_all("td")`
|
||||
- Line 316: Conditional branch. Code: `if len(tds) != len(headers):`
|
||||
- Line 317: Execute the statement as written. Code: `continue`
|
||||
- Line 318: Blank line for readability. Code: `<blank>`
|
||||
- Line 319: Initialize a row dictionary. Code: `item = {}`
|
||||
- Line 320: Loop over items. Code: `for i, c in enumerate(tds):`
|
||||
- Line 321: Read the header name for the current column. Code: `key = headers[i]`
|
||||
- Line 322: Read or convert the cell value. Code: `val = c.get_text(" ", strip=True)`
|
||||
- Line 323: Blank line for readability. Code: `<blank>`
|
||||
- Line 324: Comment describing the next block. Code: `# Convert numeric fields`
|
||||
- Line 325: Conditional branch. Code: `if key in ["Strike", "Last Price", "Bid", "Ask", "Change"]:`
|
||||
- Line 326: Start a try block for error handling. Code: `try:`
|
||||
- Line 327: Read or convert the cell value. Code: `val = float(val.replace(",", ""))`
|
||||
- Line 328: Handle exceptions for the preceding try block. Code: `except Exception:`
|
||||
- Line 329: Read or convert the cell value. Code: `val = None`
|
||||
- Line 330: Alternative conditional branch. Code: `elif key in ["Volume", "Open Interest"]:`
|
||||
- Line 331: Start a try block for error handling. Code: `try:`
|
||||
- Line 332: Read or convert the cell value. Code: `val = int(val.replace(",", ""))`
|
||||
- Line 333: Handle exceptions for the preceding try block. Code: `except Exception:`
|
||||
- Line 334: Read or convert the cell value. Code: `val = None`
|
||||
- Line 335: Alternative conditional branch. Code: `elif val in ["-", ""]:`
|
||||
- Line 336: Read or convert the cell value. Code: `val = None`
|
||||
- Line 337: Blank line for readability. Code: `<blank>`
|
||||
- Line 338: Execute the statement as written. Code: `item[key] = val`
|
||||
- Line 339: Blank line for readability. Code: `<blank>`
|
||||
- Line 340: Execute the statement as written. Code: `parsed.append(item)`
|
||||
- Line 341: Blank line for readability. Code: `<blank>`
|
||||
- Line 342: Emit or configure a log message. Code: `app.logger.info("Parsed %d %s rows", len(parsed), side)`
|
||||
- Line 343: Return a value to the caller. Code: `return parsed`
|
||||
- Line 344: Blank line for readability. Code: `<blank>`
|
||||
- Line 345: Define the read_option_chain function. Code: `def read_option_chain(page):`
|
||||
- Line 346: Capture the page HTML content. Code: `html = page.content()`
|
||||
- Line 347: Execute the statement as written. Code: `option_chain = extract_option_chain_from_html(html)`
|
||||
- Line 348: Conditional branch. Code: `if option_chain:`
|
||||
- Line 349: Extract expiration date timestamps from the HTML. Code: `expiration_dates = extract_expiration_dates_from_chain(option_chain)`
|
||||
- Line 350: Fallback branch. Code: `else:`
|
||||
- Line 351: Extract expiration date timestamps from the HTML. Code: `expiration_dates = extract_expiration_dates_from_html(html)`
|
||||
- Line 352: Return a value to the caller. Code: `return option_chain, expiration_dates`
|
||||
- Line 353: Blank line for readability. Code: `<blank>`
|
||||
- Line 354: Define the has_expected_expiry function. Code: `def has_expected_expiry(options, expected_code):`
|
||||
- Line 355: Conditional branch. Code: `if not expected_code:`
|
||||
- Line 356: Return a value to the caller. Code: `return False`
|
||||
- Line 357: Loop over items. Code: `for row in options or []:`
|
||||
- Line 358: Execute the statement as written. Code: `name = row.get("Contract Name")`
|
||||
- Line 359: Conditional branch. Code: `if extract_contract_expiry_code(name) == expected_code:`
|
||||
- Line 360: Return a value to the caller. Code: `return True`
|
||||
- Line 361: Return a value to the caller. Code: `return False`
|
||||
- Line 362: Blank line for readability. Code: `<blank>`
|
||||
- Line 363: URL-encode the stock symbol. Code: `encoded = urllib.parse.quote(symbol, safe="")`
|
||||
- Line 364: Build the base Yahoo Finance options URL. Code: `base_url = f"https://finance.yahoo.com/quote/{encoded}/options/"`
|
||||
- Line 365: Normalize the expiration input string. Code: `requested_expiration = expiration.strip() if expiration else None`
|
||||
- Line 366: Conditional branch. Code: `if not requested_expiration:`
|
||||
- Line 367: Normalize the expiration input string. Code: `requested_expiration = None`
|
||||
- Line 368: Set the URL to load. Code: `url = base_url`
|
||||
- Line 369: Blank line for readability. Code: `<blank>`
|
||||
- Line 370: Emit or configure a log message. Code: `app.logger.info(`
|
||||
- Line 371: Execute the statement as written. Code: `"Starting scrape for symbol=%s expiration=%s url=%s",`
|
||||
- Line 372: Execute the statement as written. Code: `symbol,`
|
||||
- Line 373: Execute the statement as written. Code: `requested_expiration,`
|
||||
- Line 374: Execute the statement as written. Code: `base_url,`
|
||||
- Line 375: Close the current block or container. Code: `)`
|
||||
- Line 376: Blank line for readability. Code: `<blank>`
|
||||
- Line 377: Reserve storage for options table HTML. Code: `calls_html = None`
|
||||
- Line 378: Reserve storage for options table HTML. Code: `puts_html = None`
|
||||
- Line 379: Parse the full calls and puts tables. Code: `calls_full = []`
|
||||
- Line 380: Parse the full calls and puts tables. Code: `puts_full = []`
|
||||
- Line 381: Initialize or assign the current price. Code: `price = None`
|
||||
- Line 382: Track the resolved expiration metadata. Code: `selected_expiration_value = None`
|
||||
- Line 383: Track the resolved expiration metadata. Code: `selected_expiration_label = None`
|
||||
- Line 384: Prepare or update the list of available expirations. Code: `expiration_options = []`
|
||||
- Line 385: Track the resolved expiration epoch timestamp. Code: `target_date = None`
|
||||
- Line 386: Track whether a base-page lookup is needed. Code: `fallback_to_base = False`
|
||||
- Line 387: Blank line for readability. Code: `<blank>`
|
||||
- Line 388: Enter a context manager block. Code: `with sync_playwright() as p:`
|
||||
- Line 389: Launch a Playwright browser instance. Code: `browser = p.chromium.launch(headless=True)`
|
||||
- Line 390: Create a new Playwright page. Code: `page = browser.new_page()`
|
||||
- Line 391: Interact with the Playwright page. Code: `page.set_extra_http_headers(`
|
||||
- Line 392: Execute the statement as written. Code: `{`
|
||||
- Line 393: Execute the statement as written. Code: `"User-Agent": (`
|
||||
- Line 394: Execute the statement as written. Code: `"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "`
|
||||
- Line 395: Execute the statement as written. Code: `"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36"`
|
||||
- Line 396: Close the current block or container. Code: `)`
|
||||
- Line 397: Close the current block or container. Code: `}`
|
||||
- Line 398: Close the current block or container. Code: `)`
|
||||
- Line 399: Interact with the Playwright page. Code: `page.set_default_timeout(60000)`
|
||||
- Line 400: Blank line for readability. Code: `<blank>`
|
||||
- Line 401: Start a try block for error handling. Code: `try:`
|
||||
- Line 402: Conditional branch. Code: `if requested_expiration:`
|
||||
- Line 403: Conditional branch. Code: `if requested_expiration.isdigit():`
|
||||
- Line 404: Track the resolved expiration epoch timestamp. Code: `target_date = int(requested_expiration)`
|
||||
- Line 405: Track the resolved expiration metadata. Code: `selected_expiration_value = target_date`
|
||||
- Line 406: Track the resolved expiration metadata. Code: `selected_expiration_label = format_expiration_label(target_date)`
|
||||
- Line 407: Fallback branch. Code: `else:`
|
||||
- Line 408: Execute the statement as written. Code: `parsed_date = parse_date(requested_expiration)`
|
||||
- Line 409: Conditional branch. Code: `if parsed_date:`
|
||||
- Line 410: Track the resolved expiration epoch timestamp. Code: `target_date = int(`
|
||||
- Line 411: Execute the statement as written. Code: `datetime(`
|
||||
- Line 412: Execute the statement as written. Code: `parsed_date.year,`
|
||||
- Line 413: Execute the statement as written. Code: `parsed_date.month,`
|
||||
- Line 414: Execute the statement as written. Code: `parsed_date.day,`
|
||||
- Line 415: Execute the statement as written. Code: `tzinfo=timezone.utc,`
|
||||
- Line 416: Execute the statement as written. Code: `).timestamp()`
|
||||
- Line 417: Close the current block or container. Code: `)`
|
||||
- Line 418: Track the resolved expiration metadata. Code: `selected_expiration_value = target_date`
|
||||
- Line 419: Track the resolved expiration metadata. Code: `selected_expiration_label = format_expiration_label(target_date)`
|
||||
- Line 420: Fallback branch. Code: `else:`
|
||||
- Line 421: Track whether a base-page lookup is needed. Code: `fallback_to_base = True`
|
||||
- Line 422: Blank line for readability. Code: `<blank>`
|
||||
- Line 423: Conditional branch. Code: `if target_date:`
|
||||
- Line 424: Set the URL to load. Code: `url = f"{base_url}?date={target_date}"`
|
||||
- Line 425: Blank line for readability. Code: `<blank>`
|
||||
- Line 426: Navigate the Playwright page to the target URL. Code: `page.goto(url, wait_until="domcontentloaded", timeout=60000)`
|
||||
- Line 427: Emit or configure a log message. Code: `app.logger.info("Page loaded (domcontentloaded) for %s", symbol)`
|
||||
- Line 428: Blank line for readability. Code: `<blank>`
|
||||
- Line 429: Execute the statement as written. Code: `option_chain, expiration_dates = read_option_chain(page)`
|
||||
- Line 430: Emit or configure a log message. Code: `app.logger.info("Option chain found: %s", bool(option_chain))`
|
||||
- Line 431: Prepare or update the list of available expirations. Code: `expiration_options = build_expiration_options(expiration_dates)`
|
||||
- Line 302: Define the parse_strike_limit function. Code: `def parse_strike_limit(value, default=25):`
|
||||
- Line 303: Conditional branch. Code: `if value is None:`
|
||||
- Line 304: Return a value to the caller. Code: `return default`
|
||||
- Line 305: Start a try block for error handling. Code: `try:`
|
||||
- Line 306: Execute the statement as written. Code: `limit = int(value)`
|
||||
- Line 307: Handle exceptions for the preceding try block. Code: `except (TypeError, ValueError):`
|
||||
- Line 308: Return a value to the caller. Code: `return default`
|
||||
- Line 309: Return a value to the caller. Code: `return limit if limit > 0 else default`
|
||||
- Line 310: Blank line for readability. Code: `<blank>`
|
||||
- Line 311: Blank line for readability. Code: `<blank>`
|
||||
- Line 312: Define the scrape_yahoo_options function. Code: `def scrape_yahoo_options(symbol, expiration=None, strike_limit=25):`
|
||||
- Line 313: Define the parse_table function. Code: `def parse_table(table_html, side):`
|
||||
- Line 314: Conditional branch. Code: `if not table_html:`
|
||||
- Line 315: Emit or configure a log message. Code: `app.logger.warning("No %s table HTML for %s", side, symbol)`
|
||||
- Line 316: Return a value to the caller. Code: `return []`
|
||||
- Line 317: Blank line for readability. Code: `<blank>`
|
||||
- Line 318: Execute the statement as written. Code: `soup = BeautifulSoup(table_html, "html.parser")`
|
||||
- Line 319: Blank line for readability. Code: `<blank>`
|
||||
- Line 320: Extract header labels from the table. Code: `headers = [th.get_text(strip=True) for th in soup.select("thead th")]`
|
||||
- Line 321: Collect table rows for parsing. Code: `rows = soup.select("tbody tr")`
|
||||
- Line 322: Blank line for readability. Code: `<blank>`
|
||||
- Line 323: Initialize the parsed rows list. Code: `parsed = []`
|
||||
- Line 324: Loop over items. Code: `for r in rows:`
|
||||
- Line 325: Collect table cells for the current row. Code: `tds = r.find_all("td")`
|
||||
- Line 326: Conditional branch. Code: `if len(tds) != len(headers):`
|
||||
- Line 327: Execute the statement as written. Code: `continue`
|
||||
- Line 328: Blank line for readability. Code: `<blank>`
|
||||
- Line 329: Initialize a row dictionary. Code: `item = {}`
|
||||
- Line 330: Loop over items. Code: `for i, c in enumerate(tds):`
|
||||
- Line 331: Read the header name for the current column. Code: `key = headers[i]`
|
||||
- Line 332: Read or convert the cell value. Code: `val = c.get_text(" ", strip=True)`
|
||||
- Line 333: Blank line for readability. Code: `<blank>`
|
||||
- Line 334: Comment describing the next block. Code: `# Convert numeric fields`
|
||||
- Line 335: Conditional branch. Code: `if key in ["Strike", "Last Price", "Bid", "Ask", "Change"]:`
|
||||
- Line 336: Start a try block for error handling. Code: `try:`
|
||||
- Line 337: Read or convert the cell value. Code: `val = float(val.replace(",", ""))`
|
||||
- Line 338: Handle exceptions for the preceding try block. Code: `except Exception:`
|
||||
- Line 339: Read or convert the cell value. Code: `val = None`
|
||||
- Line 340: Alternative conditional branch. Code: `elif key in ["Volume", "Open Interest"]:`
|
||||
- Line 341: Start a try block for error handling. Code: `try:`
|
||||
- Line 342: Read or convert the cell value. Code: `val = int(val.replace(",", ""))`
|
||||
- Line 343: Handle exceptions for the preceding try block. Code: `except Exception:`
|
||||
- Line 344: Read or convert the cell value. Code: `val = None`
|
||||
- Line 345: Alternative conditional branch. Code: `elif val in ["-", ""]:`
|
||||
- Line 346: Read or convert the cell value. Code: `val = None`
|
||||
- Line 347: Blank line for readability. Code: `<blank>`
|
||||
- Line 348: Execute the statement as written. Code: `item[key] = val`
|
||||
- Line 349: Blank line for readability. Code: `<blank>`
|
||||
- Line 350: Execute the statement as written. Code: `parsed.append(item)`
|
||||
- Line 351: Blank line for readability. Code: `<blank>`
|
||||
- Line 352: Emit or configure a log message. Code: `app.logger.info("Parsed %d %s rows", len(parsed), side)`
|
||||
- Line 353: Return a value to the caller. Code: `return parsed`
|
||||
- Line 354: Blank line for readability. Code: `<blank>`
|
||||
- Line 355: Define the read_option_chain function. Code: `def read_option_chain(page):`
|
||||
- Line 356: Capture the page HTML content. Code: `html = page.content()`
|
||||
- Line 357: Execute the statement as written. Code: `option_chain = extract_option_chain_from_html(html)`
|
||||
- Line 358: Conditional branch. Code: `if option_chain:`
|
||||
- Line 359: Extract expiration date timestamps from the HTML. Code: `expiration_dates = extract_expiration_dates_from_chain(option_chain)`
|
||||
- Line 360: Fallback branch. Code: `else:`
|
||||
- Line 361: Extract expiration date timestamps from the HTML. Code: `expiration_dates = extract_expiration_dates_from_html(html)`
|
||||
- Line 362: Return a value to the caller. Code: `return option_chain, expiration_dates`
|
||||
- Line 363: Blank line for readability. Code: `<blank>`
|
||||
- Line 364: Define the has_expected_expiry function. Code: `def has_expected_expiry(options, expected_code):`
|
||||
- Line 365: Conditional branch. Code: `if not expected_code:`
|
||||
- Line 366: Return a value to the caller. Code: `return False`
|
||||
- Line 367: Loop over items. Code: `for row in options or []:`
|
||||
- Line 368: Execute the statement as written. Code: `name = row.get("Contract Name")`
|
||||
- Line 369: Conditional branch. Code: `if extract_contract_expiry_code(name) == expected_code:`
|
||||
- Line 370: Return a value to the caller. Code: `return True`
|
||||
- Line 371: Return a value to the caller. Code: `return False`
|
||||
- Line 372: Blank line for readability. Code: `<blank>`
|
||||
- Line 373: URL-encode the stock symbol. Code: `encoded = urllib.parse.quote(symbol, safe="")`
|
||||
- Line 374: Build the base Yahoo Finance options URL. Code: `base_url = f"https://finance.yahoo.com/quote/{encoded}/options/"`
|
||||
- Line 375: Normalize the expiration input string. Code: `requested_expiration = expiration.strip() if expiration else None`
|
||||
- Line 376: Conditional branch. Code: `if not requested_expiration:`
|
||||
- Line 377: Normalize the expiration input string. Code: `requested_expiration = None`
|
||||
- Line 378: Set the URL to load. Code: `url = base_url`
|
||||
- Line 379: Blank line for readability. Code: `<blank>`
|
||||
- Line 380: Emit or configure a log message. Code: `app.logger.info(`
|
||||
- Line 381: Execute the statement as written. Code: `"Starting scrape for symbol=%s expiration=%s url=%s",`
|
||||
- Line 382: Execute the statement as written. Code: `symbol,`
|
||||
- Line 383: Execute the statement as written. Code: `requested_expiration,`
|
||||
- Line 384: Execute the statement as written. Code: `base_url,`
|
||||
- Line 385: Close the current block or container. Code: `)`
|
||||
- Line 386: Blank line for readability. Code: `<blank>`
|
||||
- Line 387: Reserve storage for options table HTML. Code: `calls_html = None`
|
||||
- Line 388: Reserve storage for options table HTML. Code: `puts_html = None`
|
||||
- Line 389: Parse the full calls and puts tables. Code: `calls_full = []`
|
||||
- Line 390: Parse the full calls and puts tables. Code: `puts_full = []`
|
||||
- Line 391: Initialize or assign the current price. Code: `price = None`
|
||||
- Line 392: Track the resolved expiration metadata. Code: `selected_expiration_value = None`
|
||||
- Line 393: Track the resolved expiration metadata. Code: `selected_expiration_label = None`
|
||||
- Line 394: Prepare or update the list of available expirations. Code: `expiration_options = []`
|
||||
- Line 395: Track the resolved expiration epoch timestamp. Code: `target_date = None`
|
||||
- Line 396: Track whether a base-page lookup is needed. Code: `fallback_to_base = False`
|
||||
- Line 397: Blank line for readability. Code: `<blank>`
|
||||
- Line 398: Enter a context manager block. Code: `with sync_playwright() as p:`
|
||||
- Line 399: Launch a Playwright browser instance. Code: `browser = p.chromium.launch(headless=True)`
|
||||
- Line 400: Create a new Playwright page. Code: `page = browser.new_page()`
|
||||
- Line 401: Interact with the Playwright page. Code: `page.set_extra_http_headers(`
|
||||
- Line 402: Execute the statement as written. Code: `{`
|
||||
- Line 403: Execute the statement as written. Code: `"User-Agent": (`
|
||||
- Line 404: Execute the statement as written. Code: `"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "`
|
||||
- Line 405: Execute the statement as written. Code: `"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36"`
|
||||
- Line 406: Close the current block or container. Code: `)`
|
||||
- Line 407: Close the current block or container. Code: `}`
|
||||
- Line 408: Close the current block or container. Code: `)`
|
||||
- Line 409: Interact with the Playwright page. Code: `page.set_default_timeout(60000)`
|
||||
- Line 410: Blank line for readability. Code: `<blank>`
|
||||
- Line 411: Start a try block for error handling. Code: `try:`
|
||||
- Line 412: Conditional branch. Code: `if requested_expiration:`
|
||||
- Line 413: Conditional branch. Code: `if requested_expiration.isdigit():`
|
||||
- Line 414: Track the resolved expiration epoch timestamp. Code: `target_date = int(requested_expiration)`
|
||||
- Line 415: Track the resolved expiration metadata. Code: `selected_expiration_value = target_date`
|
||||
- Line 416: Track the resolved expiration metadata. Code: `selected_expiration_label = format_expiration_label(target_date)`
|
||||
- Line 417: Fallback branch. Code: `else:`
|
||||
- Line 418: Execute the statement as written. Code: `parsed_date = parse_date(requested_expiration)`
|
||||
- Line 419: Conditional branch. Code: `if parsed_date:`
|
||||
- Line 420: Track the resolved expiration epoch timestamp. Code: `target_date = int(`
|
||||
- Line 421: Execute the statement as written. Code: `datetime(`
|
||||
- Line 422: Execute the statement as written. Code: `parsed_date.year,`
|
||||
- Line 423: Execute the statement as written. Code: `parsed_date.month,`
|
||||
- Line 424: Execute the statement as written. Code: `parsed_date.day,`
|
||||
- Line 425: Execute the statement as written. Code: `tzinfo=timezone.utc,`
|
||||
- Line 426: Execute the statement as written. Code: `).timestamp()`
|
||||
- Line 427: Close the current block or container. Code: `)`
|
||||
- Line 428: Track the resolved expiration metadata. Code: `selected_expiration_value = target_date`
|
||||
- Line 429: Track the resolved expiration metadata. Code: `selected_expiration_label = format_expiration_label(target_date)`
|
||||
- Line 430: Fallback branch. Code: `else:`
|
||||
- Line 431: Track whether a base-page lookup is needed. Code: `fallback_to_base = True`
|
||||
- Line 432: Blank line for readability. Code: `<blank>`
|
||||
- Line 433: Conditional branch. Code: `if fallback_to_base:`
|
||||
- Line 434: Execute the statement as written. Code: `resolved_value, resolved_label = resolve_expiration(`
|
||||
- Line 435: Execute the statement as written. Code: `requested_expiration, expiration_options`
|
||||
- Line 436: Close the current block or container. Code: `)`
|
||||
- Line 437: Conditional branch. Code: `if resolved_value is None:`
|
||||
- Line 438: Return a value to the caller. Code: `return {`
|
||||
- Line 439: Execute the statement as written. Code: `"error": "Requested expiration not available",`
|
||||
- Line 440: Execute the statement as written. Code: `"stock": symbol,`
|
||||
- Line 441: Execute the statement as written. Code: `"requested_expiration": requested_expiration,`
|
||||
- Line 442: Execute the statement as written. Code: `"available_expirations": [`
|
||||
- Line 443: Execute the statement as written. Code: `{"label": opt.get("label"), "value": opt.get("value")}`
|
||||
- Line 444: Loop over items. Code: `for opt in expiration_options`
|
||||
- Line 445: Close the current block or container. Code: `],`
|
||||
- Line 446: Close the current block or container. Code: `}`
|
||||
- Line 447: Blank line for readability. Code: `<blank>`
|
||||
- Line 448: Track the resolved expiration epoch timestamp. Code: `target_date = resolved_value`
|
||||
- Line 449: Track the resolved expiration metadata. Code: `selected_expiration_value = resolved_value`
|
||||
- Line 450: Track the resolved expiration metadata. Code: `selected_expiration_label = resolved_label or format_expiration_label(`
|
||||
- Line 451: Execute the statement as written. Code: `resolved_value`
|
||||
- Line 452: Close the current block or container. Code: `)`
|
||||
- Line 453: Set the URL to load. Code: `url = f"{base_url}?date={resolved_value}"`
|
||||
- Line 454: Navigate the Playwright page to the target URL. Code: `page.goto(url, wait_until="domcontentloaded", timeout=60000)`
|
||||
- Line 455: Emit or configure a log message. Code: `app.logger.info("Page loaded (domcontentloaded) for %s", symbol)`
|
||||
- Line 456: Blank line for readability. Code: `<blank>`
|
||||
- Line 457: Execute the statement as written. Code: `option_chain, expiration_dates = read_option_chain(page)`
|
||||
- Line 458: Prepare or update the list of available expirations. Code: `expiration_options = build_expiration_options(expiration_dates)`
|
||||
- Line 459: Blank line for readability. Code: `<blank>`
|
||||
- Line 460: Conditional branch. Code: `if target_date and expiration_options:`
|
||||
- Line 461: Execute the statement as written. Code: `matched = None`
|
||||
- Line 462: Loop over items. Code: `for opt in expiration_options:`
|
||||
- Line 463: Conditional branch. Code: `if opt.get("value") == target_date:`
|
||||
- Line 464: Execute the statement as written. Code: `matched = opt`
|
||||
- Line 465: Execute the statement as written. Code: `break`
|
||||
- Line 466: Conditional branch. Code: `if not matched:`
|
||||
- Line 467: Return a value to the caller. Code: `return {`
|
||||
- Line 468: Execute the statement as written. Code: `"error": "Requested expiration not available",`
|
||||
- Line 469: Execute the statement as written. Code: `"stock": symbol,`
|
||||
- Line 470: Execute the statement as written. Code: `"requested_expiration": requested_expiration,`
|
||||
- Line 471: Execute the statement as written. Code: `"available_expirations": [`
|
||||
- Line 472: Execute the statement as written. Code: `{"label": opt.get("label"), "value": opt.get("value")}`
|
||||
- Line 473: Loop over items. Code: `for opt in expiration_options`
|
||||
- Line 474: Close the current block or container. Code: `],`
|
||||
- Line 475: Close the current block or container. Code: `}`
|
||||
- Line 476: Track the resolved expiration metadata. Code: `selected_expiration_value = matched.get("value")`
|
||||
- Line 477: Track the resolved expiration metadata. Code: `selected_expiration_label = matched.get("label")`
|
||||
- Line 478: Alternative conditional branch. Code: `elif expiration_options and not target_date:`
|
||||
- Line 479: Track the resolved expiration metadata. Code: `selected_expiration_value = expiration_options[0].get("value")`
|
||||
- Line 480: Track the resolved expiration metadata. Code: `selected_expiration_label = expiration_options[0].get("label")`
|
||||
- Line 481: Blank line for readability. Code: `<blank>`
|
||||
- Line 482: Execute the statement as written. Code: `calls_full, puts_full = build_rows_from_chain(option_chain)`
|
||||
- Line 483: Emit or configure a log message. Code: `app.logger.info(`
|
||||
- Line 484: Execute the statement as written. Code: `"Option chain rows: calls=%d puts=%d",`
|
||||
- Line 485: Execute the statement as written. Code: `len(calls_full),`
|
||||
- Line 486: Execute the statement as written. Code: `len(puts_full),`
|
||||
- Line 487: Close the current block or container. Code: `)`
|
||||
- Line 488: Blank line for readability. Code: `<blank>`
|
||||
- Line 489: Conditional branch. Code: `if not calls_full and not puts_full:`
|
||||
- Line 490: Emit or configure a log message. Code: `app.logger.info("Waiting for options tables...")`
|
||||
- Line 433: Conditional branch. Code: `if target_date:`
|
||||
- Line 434: Set the URL to load. Code: `url = f"{base_url}?date={target_date}"`
|
||||
- Line 435: Blank line for readability. Code: `<blank>`
|
||||
- Line 436: Navigate the Playwright page to the target URL. Code: `page.goto(url, wait_until="domcontentloaded", timeout=60000)`
|
||||
- Line 437: Emit or configure a log message. Code: `app.logger.info("Page loaded (domcontentloaded) for %s", symbol)`
|
||||
- Line 438: Blank line for readability. Code: `<blank>`
|
||||
- Line 439: Execute the statement as written. Code: `option_chain, expiration_dates = read_option_chain(page)`
|
||||
- Line 440: Emit or configure a log message. Code: `app.logger.info("Option chain found: %s", bool(option_chain))`
|
||||
- Line 441: Prepare or update the list of available expirations. Code: `expiration_options = build_expiration_options(expiration_dates)`
|
||||
- Line 442: Blank line for readability. Code: `<blank>`
|
||||
- Line 443: Conditional branch. Code: `if fallback_to_base:`
|
||||
- Line 444: Execute the statement as written. Code: `resolved_value, resolved_label = resolve_expiration(`
|
||||
- Line 445: Execute the statement as written. Code: `requested_expiration, expiration_options`
|
||||
- Line 446: Close the current block or container. Code: `)`
|
||||
- Line 447: Conditional branch. Code: `if resolved_value is None:`
|
||||
- Line 448: Return a value to the caller. Code: `return {`
|
||||
- Line 449: Execute the statement as written. Code: `"error": "Requested expiration not available",`
|
||||
- Line 450: Execute the statement as written. Code: `"stock": symbol,`
|
||||
- Line 451: Execute the statement as written. Code: `"requested_expiration": requested_expiration,`
|
||||
- Line 452: Execute the statement as written. Code: `"available_expirations": [`
|
||||
- Line 453: Execute the statement as written. Code: `{"label": opt.get("label"), "value": opt.get("value")}`
|
||||
- Line 454: Loop over items. Code: `for opt in expiration_options`
|
||||
- Line 455: Close the current block or container. Code: `],`
|
||||
- Line 456: Close the current block or container. Code: `}`
|
||||
- Line 457: Blank line for readability. Code: `<blank>`
|
||||
- Line 458: Track the resolved expiration epoch timestamp. Code: `target_date = resolved_value`
|
||||
- Line 459: Track the resolved expiration metadata. Code: `selected_expiration_value = resolved_value`
|
||||
- Line 460: Track the resolved expiration metadata. Code: `selected_expiration_label = resolved_label or format_expiration_label(`
|
||||
- Line 461: Execute the statement as written. Code: `resolved_value`
|
||||
- Line 462: Close the current block or container. Code: `)`
|
||||
- Line 463: Set the URL to load. Code: `url = f"{base_url}?date={resolved_value}"`
|
||||
- Line 464: Navigate the Playwright page to the target URL. Code: `page.goto(url, wait_until="domcontentloaded", timeout=60000)`
|
||||
- Line 465: Emit or configure a log message. Code: `app.logger.info("Page loaded (domcontentloaded) for %s", symbol)`
|
||||
- Line 466: Blank line for readability. Code: `<blank>`
|
||||
- Line 467: Execute the statement as written. Code: `option_chain, expiration_dates = read_option_chain(page)`
|
||||
- Line 468: Prepare or update the list of available expirations. Code: `expiration_options = build_expiration_options(expiration_dates)`
|
||||
- Line 469: Blank line for readability. Code: `<blank>`
|
||||
- Line 470: Conditional branch. Code: `if target_date and expiration_options:`
|
||||
- Line 471: Execute the statement as written. Code: `matched = None`
|
||||
- Line 472: Loop over items. Code: `for opt in expiration_options:`
|
||||
- Line 473: Conditional branch. Code: `if opt.get("value") == target_date:`
|
||||
- Line 474: Execute the statement as written. Code: `matched = opt`
|
||||
- Line 475: Execute the statement as written. Code: `break`
|
||||
- Line 476: Conditional branch. Code: `if not matched:`
|
||||
- Line 477: Return a value to the caller. Code: `return {`
|
||||
- Line 478: Execute the statement as written. Code: `"error": "Requested expiration not available",`
|
||||
- Line 479: Execute the statement as written. Code: `"stock": symbol,`
|
||||
- Line 480: Execute the statement as written. Code: `"requested_expiration": requested_expiration,`
|
||||
- Line 481: Execute the statement as written. Code: `"available_expirations": [`
|
||||
- Line 482: Execute the statement as written. Code: `{"label": opt.get("label"), "value": opt.get("value")}`
|
||||
- Line 483: Loop over items. Code: `for opt in expiration_options`
|
||||
- Line 484: Close the current block or container. Code: `],`
|
||||
- Line 485: Close the current block or container. Code: `}`
|
||||
- Line 486: Track the resolved expiration metadata. Code: `selected_expiration_value = matched.get("value")`
|
||||
- Line 487: Track the resolved expiration metadata. Code: `selected_expiration_label = matched.get("label")`
|
||||
- Line 488: Alternative conditional branch. Code: `elif expiration_options and not target_date:`
|
||||
- Line 489: Track the resolved expiration metadata. Code: `selected_expiration_value = expiration_options[0].get("value")`
|
||||
- Line 490: Track the resolved expiration metadata. Code: `selected_expiration_label = expiration_options[0].get("label")`
|
||||
- Line 491: Blank line for readability. Code: `<blank>`
|
||||
- Line 492: Collect option tables from the page. Code: `tables = wait_for_tables(page)`
|
||||
- Line 493: Conditional branch. Code: `if len(tables) < 2:`
|
||||
- Line 494: Emit or configure a log message. Code: `app.logger.error(`
|
||||
- Line 495: Execute the statement as written. Code: `"Only %d tables found; expected 2. HTML may have changed.",`
|
||||
- Line 496: Execute the statement as written. Code: `len(tables),`
|
||||
- Line 492: Execute the statement as written. Code: `calls_full, puts_full = build_rows_from_chain(option_chain)`
|
||||
- Line 493: Emit or configure a log message. Code: `app.logger.info(`
|
||||
- Line 494: Execute the statement as written. Code: `"Option chain rows: calls=%d puts=%d",`
|
||||
- Line 495: Execute the statement as written. Code: `len(calls_full),`
|
||||
- Line 496: Execute the statement as written. Code: `len(puts_full),`
|
||||
- Line 497: Close the current block or container. Code: `)`
|
||||
- Line 498: Return a value to the caller. Code: `return {"error": "Could not locate options tables", "stock": symbol}`
|
||||
- Line 499: Blank line for readability. Code: `<blank>`
|
||||
- Line 500: Emit or configure a log message. Code: `app.logger.info("Found %d tables. Extracting Calls & Puts.", len(tables))`
|
||||
- Line 498: Blank line for readability. Code: `<blank>`
|
||||
- Line 499: Conditional branch. Code: `if not calls_full and not puts_full:`
|
||||
- Line 500: Emit or configure a log message. Code: `app.logger.info("Waiting for options tables...")`
|
||||
- Line 501: Blank line for readability. Code: `<blank>`
|
||||
- Line 502: Reserve storage for options table HTML. Code: `calls_html = tables[0].evaluate("el => el.outerHTML")`
|
||||
- Line 503: Reserve storage for options table HTML. Code: `puts_html = tables[1].evaluate("el => el.outerHTML")`
|
||||
- Line 504: Blank line for readability. Code: `<blank>`
|
||||
- Line 505: Comment describing the next block. Code: `# --- Extract current price ---`
|
||||
- Line 506: Start a try block for error handling. Code: `try:`
|
||||
- Line 507: Comment describing the next block. Code: `# Primary selector`
|
||||
- Line 508: Read the current price text from the page. Code: `price_text = page.locator(`
|
||||
- Line 509: Execute the statement as written. Code: `"fin-streamer[data-field='regularMarketPrice']"`
|
||||
- Line 510: Execute the statement as written. Code: `).inner_text()`
|
||||
- Line 511: Initialize or assign the current price. Code: `price = float(price_text.replace(",", ""))`
|
||||
- Line 512: Handle exceptions for the preceding try block. Code: `except Exception:`
|
||||
- Line 513: Start a try block for error handling. Code: `try:`
|
||||
- Line 514: Comment describing the next block. Code: `# Fallback`
|
||||
- Line 515: Read the current price text from the page. Code: `price_text = page.locator("span[data-testid='qsp-price']").inner_text()`
|
||||
- Line 516: Initialize or assign the current price. Code: `price = float(price_text.replace(",", ""))`
|
||||
- Line 517: Handle exceptions for the preceding try block. Code: `except Exception as e:`
|
||||
- Line 518: Emit or configure a log message. Code: `app.logger.warning("Failed to extract price for %s: %s", symbol, e)`
|
||||
- Line 519: Blank line for readability. Code: `<blank>`
|
||||
- Line 520: Emit or configure a log message. Code: `app.logger.info("Current price for %s = %s", symbol, price)`
|
||||
- Line 521: Execute the statement as written. Code: `finally:`
|
||||
- Line 522: Execute the statement as written. Code: `browser.close()`
|
||||
- Line 523: Blank line for readability. Code: `<blank>`
|
||||
- Line 524: Conditional branch. Code: `if not calls_full and not puts_full and calls_html and puts_html:`
|
||||
- Line 525: Parse the full calls and puts tables. Code: `calls_full = parse_table(calls_html, "calls")`
|
||||
- Line 526: Parse the full calls and puts tables. Code: `puts_full = parse_table(puts_html, "puts")`
|
||||
- Line 527: Blank line for readability. Code: `<blank>`
|
||||
- Line 528: Execute the statement as written. Code: `expected_code = expected_expiry_code(target_date)`
|
||||
- Line 529: Conditional branch. Code: `if expected_code:`
|
||||
- Line 530: Conditional branch. Code: `if not has_expected_expiry(calls_full, expected_code) and not has_expected_expiry(`
|
||||
- Line 531: Execute the statement as written. Code: `puts_full, expected_code`
|
||||
- Line 532: Close the current block or container. Code: `):`
|
||||
- Line 533: Return a value to the caller. Code: `return {`
|
||||
- Line 534: Execute the statement as written. Code: `"error": "Options chain does not match requested expiration",`
|
||||
- Line 535: Execute the statement as written. Code: `"stock": symbol,`
|
||||
- Line 536: Execute the statement as written. Code: `"requested_expiration": requested_expiration,`
|
||||
- Line 537: Execute the statement as written. Code: `"expected_expiration_code": expected_code,`
|
||||
- Line 538: Execute the statement as written. Code: `"selected_expiration": {`
|
||||
- Line 539: Execute the statement as written. Code: `"value": selected_expiration_value,`
|
||||
- Line 540: Execute the statement as written. Code: `"label": selected_expiration_label,`
|
||||
- Line 541: Close the current block or container. Code: `},`
|
||||
- Line 542: Close the current block or container. Code: `}`
|
||||
- Line 543: Blank line for readability. Code: `<blank>`
|
||||
- Line 544: Comment describing the next block. Code: `# ----------------------------------------------------------------------`
|
||||
- Line 545: Comment describing the next block. Code: `# Pruning logic`
|
||||
- Line 546: Comment describing the next block. Code: `# ----------------------------------------------------------------------`
|
||||
- Line 547: Define the prune_nearest function. Code: `def prune_nearest(options, price_value, limit=26, side=""):`
|
||||
- Line 548: Conditional branch. Code: `if price_value is None:`
|
||||
- Line 549: Return a value to the caller. Code: `return options, 0`
|
||||
- Line 550: Blank line for readability. Code: `<blank>`
|
||||
- Line 551: Filter options to numeric strike entries. Code: `numeric = [o for o in options if isinstance(o.get("Strike"), (int, float))]`
|
||||
- Line 552: Blank line for readability. Code: `<blank>`
|
||||
- Line 553: Conditional branch. Code: `if len(numeric) <= limit:`
|
||||
- Line 554: Return a value to the caller. Code: `return numeric, 0`
|
||||
- Line 555: Blank line for readability. Code: `<blank>`
|
||||
- Line 556: Sort options by distance to current price. Code: `sorted_opts = sorted(numeric, key=lambda x: abs(x["Strike"] - price_value))`
|
||||
- Line 557: Keep the closest strike entries. Code: `pruned = sorted_opts[:limit]`
|
||||
- Line 558: Compute how many rows were pruned. Code: `pruned_count = len(options) - len(pruned)`
|
||||
- Line 559: Return a value to the caller. Code: `return pruned, pruned_count`
|
||||
- Line 502: Collect option tables from the page. Code: `tables = wait_for_tables(page)`
|
||||
- Line 503: Conditional branch. Code: `if len(tables) < 2:`
|
||||
- Line 504: Emit or configure a log message. Code: `app.logger.error(`
|
||||
- Line 505: Execute the statement as written. Code: `"Only %d tables found; expected 2. HTML may have changed.",`
|
||||
- Line 506: Execute the statement as written. Code: `len(tables),`
|
||||
- Line 507: Close the current block or container. Code: `)`
|
||||
- Line 508: Return a value to the caller. Code: `return {"error": "Could not locate options tables", "stock": symbol}`
|
||||
- Line 509: Blank line for readability. Code: `<blank>`
|
||||
- Line 510: Emit or configure a log message. Code: `app.logger.info("Found %d tables. Extracting Calls & Puts.", len(tables))`
|
||||
- Line 511: Blank line for readability. Code: `<blank>`
|
||||
- Line 512: Reserve storage for options table HTML. Code: `calls_html = tables[0].evaluate("el => el.outerHTML")`
|
||||
- Line 513: Reserve storage for options table HTML. Code: `puts_html = tables[1].evaluate("el => el.outerHTML")`
|
||||
- Line 514: Blank line for readability. Code: `<blank>`
|
||||
- Line 515: Comment describing the next block. Code: `# --- Extract current price ---`
|
||||
- Line 516: Start a try block for error handling. Code: `try:`
|
||||
- Line 517: Comment describing the next block. Code: `# Primary selector`
|
||||
- Line 518: Read the current price text from the page. Code: `price_text = page.locator(`
|
||||
- Line 519: Execute the statement as written. Code: `"fin-streamer[data-field='regularMarketPrice']"`
|
||||
- Line 520: Execute the statement as written. Code: `).inner_text()`
|
||||
- Line 521: Initialize or assign the current price. Code: `price = float(price_text.replace(",", ""))`
|
||||
- Line 522: Handle exceptions for the preceding try block. Code: `except Exception:`
|
||||
- Line 523: Start a try block for error handling. Code: `try:`
|
||||
- Line 524: Comment describing the next block. Code: `# Fallback`
|
||||
- Line 525: Read the current price text from the page. Code: `price_text = page.locator("span[data-testid='qsp-price']").inner_text()`
|
||||
- Line 526: Initialize or assign the current price. Code: `price = float(price_text.replace(",", ""))`
|
||||
- Line 527: Handle exceptions for the preceding try block. Code: `except Exception as e:`
|
||||
- Line 528: Emit or configure a log message. Code: `app.logger.warning("Failed to extract price for %s: %s", symbol, e)`
|
||||
- Line 529: Blank line for readability. Code: `<blank>`
|
||||
- Line 530: Emit or configure a log message. Code: `app.logger.info("Current price for %s = %s", symbol, price)`
|
||||
- Line 531: Execute the statement as written. Code: `finally:`
|
||||
- Line 532: Execute the statement as written. Code: `browser.close()`
|
||||
- Line 533: Blank line for readability. Code: `<blank>`
|
||||
- Line 534: Conditional branch. Code: `if not calls_full and not puts_full and calls_html and puts_html:`
|
||||
- Line 535: Parse the full calls and puts tables. Code: `calls_full = parse_table(calls_html, "calls")`
|
||||
- Line 536: Parse the full calls and puts tables. Code: `puts_full = parse_table(puts_html, "puts")`
|
||||
- Line 537: Blank line for readability. Code: `<blank>`
|
||||
- Line 538: Execute the statement as written. Code: `expected_code = expected_expiry_code(target_date)`
|
||||
- Line 539: Conditional branch. Code: `if expected_code:`
|
||||
- Line 540: Conditional branch. Code: `if not has_expected_expiry(calls_full, expected_code) and not has_expected_expiry(`
|
||||
- Line 541: Execute the statement as written. Code: `puts_full, expected_code`
|
||||
- Line 542: Close the current block or container. Code: `):`
|
||||
- Line 543: Return a value to the caller. Code: `return {`
|
||||
- Line 544: Execute the statement as written. Code: `"error": "Options chain does not match requested expiration",`
|
||||
- Line 545: Execute the statement as written. Code: `"stock": symbol,`
|
||||
- Line 546: Execute the statement as written. Code: `"requested_expiration": requested_expiration,`
|
||||
- Line 547: Execute the statement as written. Code: `"expected_expiration_code": expected_code,`
|
||||
- Line 548: Execute the statement as written. Code: `"selected_expiration": {`
|
||||
- Line 549: Execute the statement as written. Code: `"value": selected_expiration_value,`
|
||||
- Line 550: Execute the statement as written. Code: `"label": selected_expiration_label,`
|
||||
- Line 551: Close the current block or container. Code: `},`
|
||||
- Line 552: Close the current block or container. Code: `}`
|
||||
- Line 553: Blank line for readability. Code: `<blank>`
|
||||
- Line 554: Comment describing the next block. Code: `# ----------------------------------------------------------------------`
|
||||
- Line 555: Comment describing the next block. Code: `# Pruning logic`
|
||||
- Line 556: Comment describing the next block. Code: `# ----------------------------------------------------------------------`
|
||||
- Line 557: Define the prune_nearest function. Code: `def prune_nearest(options, price_value, limit=25, side=""):`
|
||||
- Line 558: Conditional branch. Code: `if price_value is None:`
|
||||
- Line 559: Return a value to the caller. Code: `return options, 0`
|
||||
- Line 560: Blank line for readability. Code: `<blank>`
|
||||
- Line 561: Apply pruning to calls. Code: `calls, pruned_calls = prune_nearest(calls_full, price, side="calls")`
|
||||
- Line 562: Apply pruning to puts. Code: `puts, pruned_puts = prune_nearest(puts_full, price, side="puts")`
|
||||
- Line 563: Blank line for readability. Code: `<blank>`
|
||||
- Line 564: Define the strike_range function. Code: `def strike_range(opts):`
|
||||
- Line 565: Collect strike prices from the option list. Code: `strikes = [o["Strike"] for o in opts if isinstance(o.get("Strike"), (int, float))]`
|
||||
- Line 566: Return a value to the caller. Code: `return [min(strikes), max(strikes)] if strikes else [None, None]`
|
||||
- Line 567: Blank line for readability. Code: `<blank>`
|
||||
- Line 568: Return a value to the caller. Code: `return {`
|
||||
- Line 569: Execute the statement as written. Code: `"stock": symbol,`
|
||||
- Line 570: Execute the statement as written. Code: `"url": url,`
|
||||
- Line 571: Execute the statement as written. Code: `"requested_expiration": requested_expiration,`
|
||||
- Line 572: Execute the statement as written. Code: `"selected_expiration": {`
|
||||
- Line 573: Execute the statement as written. Code: `"value": selected_expiration_value,`
|
||||
- Line 574: Execute the statement as written. Code: `"label": selected_expiration_label,`
|
||||
- Line 575: Close the current block or container. Code: `},`
|
||||
- Line 576: Execute the statement as written. Code: `"current_price": price,`
|
||||
- Line 577: Execute the statement as written. Code: `"calls": calls,`
|
||||
- Line 578: Execute the statement as written. Code: `"puts": puts,`
|
||||
- Line 579: Execute the statement as written. Code: `"calls_strike_range": strike_range(calls),`
|
||||
- Line 580: Execute the statement as written. Code: `"puts_strike_range": strike_range(puts),`
|
||||
- Line 581: Execute the statement as written. Code: `"total_calls": len(calls),`
|
||||
- Line 582: Execute the statement as written. Code: `"total_puts": len(puts),`
|
||||
- Line 583: Execute the statement as written. Code: `"pruned_calls_count": pruned_calls,`
|
||||
- Line 584: Execute the statement as written. Code: `"pruned_puts_count": pruned_puts,`
|
||||
- Line 585: Close the current block or container. Code: `}`
|
||||
- Line 586: Blank line for readability. Code: `<blank>`
|
||||
- Line 561: Filter options to numeric strike entries. Code: `numeric = [o for o in options if isinstance(o.get("Strike"), (int, float))]`
|
||||
- Line 562: Blank line for readability. Code: `<blank>`
|
||||
- Line 563: Conditional branch. Code: `if len(numeric) <= limit:`
|
||||
- Line 564: Return a value to the caller. Code: `return numeric, 0`
|
||||
- Line 565: Blank line for readability. Code: `<blank>`
|
||||
- Line 566: Sort options by distance to current price. Code: `sorted_opts = sorted(numeric, key=lambda x: abs(x["Strike"] - price_value))`
|
||||
- Line 567: Keep the closest strike entries. Code: `pruned = sorted_opts[:limit]`
|
||||
- Line 568: Compute how many rows were pruned. Code: `pruned_count = len(options) - len(pruned)`
|
||||
- Line 569: Return a value to the caller. Code: `return pruned, pruned_count`
|
||||
- Line 570: Blank line for readability. Code: `<blank>`
|
||||
- Line 571: Apply pruning to calls. Code: `calls, pruned_calls = prune_nearest(`
|
||||
- Line 572: Execute the statement as written. Code: `calls_full,`
|
||||
- Line 573: Execute the statement as written. Code: `price,`
|
||||
- Line 574: Execute the statement as written. Code: `limit=strike_limit,`
|
||||
- Line 575: Execute the statement as written. Code: `side="calls",`
|
||||
- Line 576: Close the current block or container. Code: `)`
|
||||
- Line 577: Apply pruning to puts. Code: `puts, pruned_puts = prune_nearest(`
|
||||
- Line 578: Execute the statement as written. Code: `puts_full,`
|
||||
- Line 579: Execute the statement as written. Code: `price,`
|
||||
- Line 580: Execute the statement as written. Code: `limit=strike_limit,`
|
||||
- Line 581: Execute the statement as written. Code: `side="puts",`
|
||||
- Line 582: Close the current block or container. Code: `)`
|
||||
- Line 583: Blank line for readability. Code: `<blank>`
|
||||
- Line 584: Define the strike_range function. Code: `def strike_range(opts):`
|
||||
- Line 585: Collect strike prices from the option list. Code: `strikes = [o["Strike"] for o in opts if isinstance(o.get("Strike"), (int, float))]`
|
||||
- Line 586: Return a value to the caller. Code: `return [min(strikes), max(strikes)] if strikes else [None, None]`
|
||||
- Line 587: Blank line for readability. Code: `<blank>`
|
||||
- Line 588: Attach the route decorator to the handler. Code: `@app.route("/scrape_sync")`
|
||||
- Line 589: Define the scrape_sync function. Code: `def scrape_sync():`
|
||||
- Line 590: Read the stock symbol parameter. Code: `symbol = request.args.get("stock", "MSFT")`
|
||||
- Line 591: Read the expiration parameters from the request. Code: `expiration = (`
|
||||
- Line 592: Execute the statement as written. Code: `request.args.get("expiration")`
|
||||
- Line 593: Execute the statement as written. Code: `or request.args.get("expiry")`
|
||||
- Line 594: Execute the statement as written. Code: `or request.args.get("date")`
|
||||
- Line 595: Close the current block or container. Code: `)`
|
||||
- Line 596: Emit or configure a log message. Code: `app.logger.info(`
|
||||
- Line 597: Execute the statement as written. Code: `"Received /scrape_sync request for symbol=%s expiration=%s",`
|
||||
- Line 598: Execute the statement as written. Code: `symbol,`
|
||||
- Line 599: Execute the statement as written. Code: `expiration,`
|
||||
- Line 600: Close the current block or container. Code: `)`
|
||||
- Line 601: Return a value to the caller. Code: `return jsonify(scrape_yahoo_options(symbol, expiration))`
|
||||
- Line 602: Blank line for readability. Code: `<blank>`
|
||||
- Line 603: Blank line for readability. Code: `<blank>`
|
||||
- Line 604: Conditional branch. Code: `if __name__ == "__main__":`
|
||||
- Line 605: Run the Flask development server. Code: `app.run(host="0.0.0.0", port=9777)`
|
||||
- Line 588: Return a value to the caller. Code: `return {`
|
||||
- Line 589: Execute the statement as written. Code: `"stock": symbol,`
|
||||
- Line 590: Execute the statement as written. Code: `"url": url,`
|
||||
- Line 591: Execute the statement as written. Code: `"requested_expiration": requested_expiration,`
|
||||
- Line 592: Execute the statement as written. Code: `"selected_expiration": {`
|
||||
- Line 593: Execute the statement as written. Code: `"value": selected_expiration_value,`
|
||||
- Line 594: Execute the statement as written. Code: `"label": selected_expiration_label,`
|
||||
- Line 595: Close the current block or container. Code: `},`
|
||||
- Line 596: Execute the statement as written. Code: `"current_price": price,`
|
||||
- Line 597: Execute the statement as written. Code: `"calls": calls,`
|
||||
- Line 598: Execute the statement as written. Code: `"puts": puts,`
|
||||
- Line 599: Execute the statement as written. Code: `"calls_strike_range": strike_range(calls),`
|
||||
- Line 600: Execute the statement as written. Code: `"puts_strike_range": strike_range(puts),`
|
||||
- Line 601: Execute the statement as written. Code: `"total_calls": len(calls),`
|
||||
- Line 602: Execute the statement as written. Code: `"total_puts": len(puts),`
|
||||
- Line 603: Execute the statement as written. Code: `"pruned_calls_count": pruned_calls,`
|
||||
- Line 604: Execute the statement as written. Code: `"pruned_puts_count": pruned_puts,`
|
||||
- Line 605: Close the current block or container. Code: `}`
|
||||
- Line 606: Blank line for readability. Code: `<blank>`
|
||||
- Line 607: Blank line for readability. Code: `<blank>`
|
||||
- Line 608: Attach the route decorator to the handler. Code: `@app.route("/scrape_sync")`
|
||||
- Line 609: Define the scrape_sync function. Code: `def scrape_sync():`
|
||||
- Line 610: Read the stock symbol parameter. Code: `symbol = request.args.get("stock", "MSFT")`
|
||||
- Line 611: Read the expiration parameters from the request. Code: `expiration = (`
|
||||
- Line 612: Execute the statement as written. Code: `request.args.get("expiration")`
|
||||
- Line 613: Execute the statement as written. Code: `or request.args.get("expiry")`
|
||||
- Line 614: Execute the statement as written. Code: `or request.args.get("date")`
|
||||
- Line 615: Close the current block or container. Code: `)`
|
||||
- Line 616: Read or default the strikeLimit parameter. Code: `strike_limit = parse_strike_limit(request.args.get("strikeLimit"), default=25)`
|
||||
- Line 617: Emit or configure a log message. Code: `app.logger.info(`
|
||||
- Line 618: Execute the statement as written. Code: `"Received /scrape_sync request for symbol=%s expiration=%s strike_limit=%s",`
|
||||
- Line 619: Execute the statement as written. Code: `symbol,`
|
||||
- Line 620: Execute the statement as written. Code: `expiration,`
|
||||
- Line 621: Read or default the strikeLimit parameter. Code: `strike_limit,`
|
||||
- Line 622: Close the current block or container. Code: `)`
|
||||
- Line 623: Return a value to the caller. Code: `return jsonify(scrape_yahoo_options(symbol, expiration, strike_limit))`
|
||||
- Line 624: Blank line for readability. Code: `<blank>`
|
||||
- Line 625: Blank line for readability. Code: `<blank>`
|
||||
- Line 626: Conditional branch. Code: `if __name__ == "__main__":`
|
||||
- Line 627: Run the Flask development server. Code: `app.run(host="0.0.0.0", port=9777)`
|
||||
|
||||
@@ -299,7 +299,17 @@ def wait_for_tables(page):
|
||||
return []
|
||||
|
||||
|
||||
def scrape_yahoo_options(symbol, expiration=None):
|
||||
def parse_strike_limit(value, default=25):
|
||||
if value is None:
|
||||
return default
|
||||
try:
|
||||
limit = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
return limit if limit > 0 else default
|
||||
|
||||
|
||||
def scrape_yahoo_options(symbol, expiration=None, strike_limit=25):
|
||||
def parse_table(table_html, side):
|
||||
if not table_html:
|
||||
app.logger.warning("No %s table HTML for %s", side, symbol)
|
||||
@@ -544,7 +554,7 @@ def scrape_yahoo_options(symbol, expiration=None):
|
||||
# ----------------------------------------------------------------------
|
||||
# Pruning logic
|
||||
# ----------------------------------------------------------------------
|
||||
def prune_nearest(options, price_value, limit=26, side=""):
|
||||
def prune_nearest(options, price_value, limit=25, side=""):
|
||||
if price_value is None:
|
||||
return options, 0
|
||||
|
||||
@@ -558,8 +568,18 @@ def scrape_yahoo_options(symbol, expiration=None):
|
||||
pruned_count = len(options) - len(pruned)
|
||||
return pruned, pruned_count
|
||||
|
||||
calls, pruned_calls = prune_nearest(calls_full, price, side="calls")
|
||||
puts, pruned_puts = prune_nearest(puts_full, price, side="puts")
|
||||
calls, pruned_calls = prune_nearest(
|
||||
calls_full,
|
||||
price,
|
||||
limit=strike_limit,
|
||||
side="calls",
|
||||
)
|
||||
puts, pruned_puts = prune_nearest(
|
||||
puts_full,
|
||||
price,
|
||||
limit=strike_limit,
|
||||
side="puts",
|
||||
)
|
||||
|
||||
def strike_range(opts):
|
||||
strikes = [o["Strike"] for o in opts if isinstance(o.get("Strike"), (int, float))]
|
||||
@@ -593,12 +613,14 @@ def scrape_sync():
|
||||
or request.args.get("expiry")
|
||||
or request.args.get("date")
|
||||
)
|
||||
strike_limit = parse_strike_limit(request.args.get("strikeLimit"), default=25)
|
||||
app.logger.info(
|
||||
"Received /scrape_sync request for symbol=%s expiration=%s",
|
||||
"Received /scrape_sync request for symbol=%s expiration=%s strike_limit=%s",
|
||||
symbol,
|
||||
expiration,
|
||||
strike_limit,
|
||||
)
|
||||
return jsonify(scrape_yahoo_options(symbol, expiration))
|
||||
return jsonify(scrape_yahoo_options(symbol, expiration, strike_limit))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user