"""Inspect a chain in the pinned free sample. No dependencies beyond Python 3.10+.""" import argparse from collections import Counter import csv from decimal import Decimal import hashlib import io import json from pathlib import Path import platform import zipfile from download_options_sample import SHA256, digest HEADER = 'contract,underlying,expiration,type,strike,style,quote_date,bid,bid_size,ask,ask_size,quote_time,volume,open_interest,open,high,low,close,trade_vwap,transactions,multileg_volume,active_minutes,last_trade_date,underlying_close,settlement_time,iv_bid,iv_ask,iv,iv_flag,delta,gamma,theta,vega,rho'.split(',') def number(cell): # Blank is unavailable. A recorded zero remains zero. if cell == '': return None value = Decimal(cell) if not value.is_finite(): raise ValueError('Unexpected non-finite numeric value') return value def analyze(archive, symbol, day, expiration, low, high, option_type): if digest(archive) != SHA256: raise ValueError('Sample SHA-256 does not match tutorial revision 1') name = day + '_options.csv' with zipfile.ZipFile(archive) as z: manifest_bytes = z.read('day_by_date/manifest.json') manifest = json.loads(manifest_bytes) entries = [e for e in manifest['files'] if e['name'] == name] if len(entries) != 1: raise ValueError('Select exactly one published sample date') entry = entries[0] raw = z.read('day_by_date/' + name) if hashlib.sha256(raw).hexdigest() != entry['sha256'] or len(raw) != entry['bytes']: raise ValueError('Selected CSV differs from manifest') reader = csv.DictReader(io.StringIO(raw.decode('utf-8'), newline='')) if reader.fieldnames != HEADER: raise ValueError('Unexpected column order') rows = list(reader) if len(rows) != entry['rows'] or any(None in r or None in r.values() for r in rows): raise ValueError('Row count or column structure differs from manifest') # Exact symbol matching, without merging contract roots or renaming securities. selected = [r for r in rows if r['underlying'] == symbol and r['quote_date'] == day and r['expiration'] == expiration and r['type'] == option_type and low <= number(r['strike']) <= high] counts = Counter() flags = Counter() mids = [] ivs = [] seen = set() for r in selected: if r['contract'] in seen: raise ValueError('Duplicate selected contract') seen.add(r['contract']) bid, ask = number(r['bid']), number(r['ask']) volume, iv = number(r['volume']), number(r['iv']) flags[r['iv_flag']] += 1 counts['missing_bid'] += bid is None counts['missing_ask'] += ask is None counts['missing_volume'] += volume is None counts['zero_volume'] += volume == 0 counts['missing_iv'] += iv is None # Do not invent zero quotes or turn crossed/one-sided quotes into a midpoint. usable = bid is not None and ask is not None and 0 < bid <= ask counts['positive_noncrossed_quotes'] += usable if usable: mids.append((bid + ask) / 2) # Flag 0 uses parity forward; flag 1 is the documented fallback forward. if r['iv_flag'] in ('0', '1') and iv is not None: ivs.append(iv) counts['zero_iv_bid'] += number(r['iv_bid']) == 0 def bounds(values): return [str(min(values)), str(max(values))] if values else None return {'tutorial_revision': 1, 'python': platform.python_version(), 'sample_url': 'https://historicaldata.net/file/options_sample_2022H2.zip', 'sample_sha256': SHA256, 'manifest_sha256': hashlib.sha256(manifest_bytes).hexdigest(), 'csv': name, 'csv_sha256': entry['sha256'], 'csv_rows': len(rows), 'columns': len(HEADER), 'selection': {'underlying': symbol, 'quote_date': day, 'expiration': expiration, 'type': option_type, 'strike_min_inclusive': str(low), 'strike_max_inclusive': str(high)}, 'selected_contracts': len(selected), 'counts': dict(counts), 'iv_flags': dict(sorted(flags.items())), 'quote_unit': 'index_points' if symbol in ('SPX', 'SPXW') else 'USD_per_share', 'midpoint_range': bounds(mids), 'solved_iv_decimal_range': bounds(ivs)} if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--sample', type=Path, default=Path('options_sample_2022H2.zip')) parser.add_argument('--symbol', choices=['TSLA', 'KO', 'SPY', 'SPX', 'SPXW'], default='SPY') parser.add_argument('--date', default='2022-08-25') parser.add_argument('--expiration', default='2022-09-16') parser.add_argument('--strike-min', type=Decimal, default=Decimal('400')) parser.add_argument('--strike-max', type=Decimal, default=Decimal('440')) parser.add_argument('--type', choices=['call', 'put'], default='call') a = parser.parse_args() if a.strike_min > a.strike_max: parser.error('strike-min must not exceed strike-max') print(json.dumps(analyze(a.sample, a.symbol, a.date, a.expiration, a.strike_min, a.strike_max, a.type), indent=2))