"""Independent integer-cents/Fraction audit, with exhaustive prefix extrema.""" import csv,datetime as dt,hashlib,json from fractions import Fraction from pathlib import Path ROOT=Path(__file__).resolve().parents[1];DATA=ROOT/'articles/data/bitcoin-crash' def audit(): rows=list(csv.DictReader((DATA/'observations.csv').open()));result=json.loads((DATA/'results.json').read_text());manifest=json.loads((DATA/'sources.json').read_text());by={};raw_checks=0 assert manifest['provider']=='Bitstamp' and manifest['market']=='BTC/USD' and manifest['interval_seconds']==86400 for c in manifest['chunks']: raw=(DATA/'raw'/c['file']).read_bytes();assert hashlib.sha256(raw).hexdigest()==c['sha256'] payload=json.loads(raw);assert payload['data']['pair']=='BTC/USD' for r in payload['data']['ohlc']: date=dt.datetime.fromtimestamp(int(r['timestamp']),dt.timezone.utc).date().isoformat() if date in by:assert by[date]==r by[date]=r for r in rows: assert {k:v for k,v in r.items() if k!='date'}==by[r['date']];raw_checks+=1 assert all((Fraction(r['close'])*100).denominator==1 for r in rows) valid=[r for r in rows if Fraction(r['volume'])>0];dates=[dt.date.fromisoformat(r['date']) for r in valid];cents=[int(Fraction(r['close'])*100) for r in valid];checks=0 assert len(rows)==5057 and len(valid)==5054 for event in result['events']: indexes=[i for i,r in enumerate(valid) if event['window_start']<=r['date']<=event['window_end']] candidates=[] for position,index in enumerate(indexes): peak=max(indexes[:position+1],key=lambda i:cents[i]) candidates.append((Fraction(cents[index],cents[peak])-1,position,peak,index)) drop,_,peak,trough=min(candidates) recovery=next((i for i in range(trough+1,len(valid)) if cents[i]>=cents[peak]),None) expected={'peak_date':valid[peak]['date'],'peak_usd':valid[peak]['close'],'trough_date':valid[trough]['date'],'trough_usd':valid[trough]['close'],'peak_to_trough_days':(dates[trough]-dates[peak]).days,'recovery_date':valid[recovery]['date'] if recovery is not None else None,'peak_to_recovery_days':(dates[recovery]-dates[peak]).days if recovery is not None else None,'trough_to_recovery_days':(dates[recovery]-dates[trough]).days if recovery is not None else None,'unrecovered_days':(dates[-1]-dates[peak]).days if recovery is None else None} for k,v in expected.items():assert event[k]==v,(event['label'],k);checks+=1 assert abs(Fraction(event['drawdown_pct'])-drop*100)