"""Original Litecoin.watch teaching datasets; not observed market/account data. Python 3 standard library only. Writes CSV fixtures and computed JSON beside this file. """ from decimal import Decimal as D, ROUND_HALF_UP from pathlib import Path from datetime import date import csv,json,hashlib,math B=Path(__file__).parent def csvwrite(name,rows): with (B/name).open('w',newline='',encoding='utf-8') as f: w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows) rows=[] def row(id,day,account,type,amount,fee='0',transfer=''): rows.append(dict(row_id=id,date_utc=day+'T12:00:00Z',account=account,event=type,asset='LTC',amount=amount,fee=fee,transfer_id=transfer)) row('L01','2026-09-01','exchange','buy','10','.02') row('L02','2026-09-02','exchange','transfer_out','-6','.001','T01') row('L03','2026-09-02','hot','transfer_in','6','0','T01') row('L04','2026-09-03','hot','transfer_out','-2','.0002','T02') row('L05','2026-09-03','cold','transfer_in','2','0','T02') row('L06','2026-09-04','exchange','sell','-1.5') row('L07','2026-09-05','hot','reward','.25') row('L08','2026-09-06','cold','transfer_out','-.75','.0001','T03') row('L09','2026-09-06','exchange','transfer_in','.75','0','T03') row('L10','2026-09-07','exchange','buy','2') csvwrite('reconciliation-ledger.csv',rows) def balance(rs): b={a:D(0) for a in ['exchange','hot','cold']} for r in rs:b[r['account']]+=D(r['amount'])-D(r['fee']) return b b=balance(rows);assert b=={'exchange':D('5.229'),'hot':D('4.2498'),'cold':D('1.2499')} assert sum(b.values())==D('10.7287') mutations=[] for kind in range(6): rr=[dict(r) for r in rows] if kind==0:rr[0]['fee']='0';name='Omit LTC purchase fee' if kind==1: for r in rr: if r['event']=='transfer_out':r['fee']='0' name='Omit all transfer fees' if kind==2:rr=[r for r in rr if r['row_id']!='L05'];name='Drop cold-wallet receipt' if kind==3:rr.append(dict(rr[-1]));name='Duplicate final buy' if kind==4:rr[1]['amount']='-6.001';name='Subtract withdrawal fee twice' if kind==5:rr=[r for r in rr if r['row_id']!='L07'];name='Omit reward at export boundary' result=balance(rr);mutations.append(dict(error=name,total_ltc=str(sum(result.values())),residual_ltc=str(sum(result.values())-sum(b.values())))) csvwrite('reconciliation-errors.csv',mutations) # Year length is 365 days; the intermediate cash flow occurs after 182 days. dates=[date(2025,1,1),date(2025,7,2),date(2026,1,1)] def npv(r,flows):return sum(v/(1+r)**((d-dates[0]).days/365) for d,v in flows) def xirr(flows): lo=-.9999;hi=10 assert npv(lo,flows)*npv(hi,flows)<0 for _ in range(180): mid=(lo+hi)/2 if npv(mid,flows)>0:lo=mid else:hi=mid return (lo+hi)/2 cases=[('Hold',0,1000),('Add at midpoint',-9000,8500),('Withdraw at midpoint',900,250)] returns=[];flowscsv=[] for label,mid,end in cases: flows=[(dates[0],-1000),(dates[1],mid),(dates[2],end)] rate=xirr(flows);assert abs(npv(rate,flows))<1e-7 returns.append(dict(case=label,initial_contribution=1000,midpoint_investor_cashflow=mid,ending_value=end,profit=end+mid-1000,twr_pct=0,xirr_pct=rate*100)) for day,v in flows:flowscsv.append(dict(case=label,date=day.isoformat(),investor_cashflow=v)) csvwrite('performance-cashflows.csv',flowscsv);csvwrite('performance-results.csv',returns) # Toy Merkle tree. Explicit domain separation and fixed 8-decimal balances. def H(b):return hashlib.sha256(b).digest() def leaf(s):return H(b'\x00'+s.encode('ascii')) def parent(a,b):return H(b'\x01'+a+b) records=['A|2.00000000','B|3.00000000','C|4.00000000','D|1.00000000'] h=[leaf(s) for s in records];root=parent(parent(h[0],h[1]),parent(h[2],h[3])) proof=[{'side':'left','hash':h[0].hex()},{'side':'right','hash':parent(h[2],h[3]).hex()}] def verify(record,proof,expected): acc=leaf(record) for p in proof: s=bytes.fromhex(p['hash']);acc=parent(s,acc) if p['side']=='left' else parent(acc,s) return acc.hex()==expected assert verify(records[1],proof,root.hex());assert not verify('B|3.10000000',proof,root.hex()) # An omitted fifth customer's balance does not change inclusion of B in the published four-leaf tree. merkle={'scheme':'Teaching example only; not Kraken or OKX format','leaf_rule':'SHA256(0x00 || ASCII(id|amount_8dp))','parent_rule':'SHA256(0x01 || left_32_bytes || right_32_bytes)','records':records,'root':root.hex(),'B_proof':proof,'original_B_verifies':True,'altered_B_verifies':False,'published_liabilities_ltc':'10','reserves_ltc':'10.5','omitted_external_record':'E|5.00000000','reported_reserve_pct':'105','full_ledger_reserve_pct':'70','B_still_verifies_if_E_was_omitted':True} (B/'merkle-example.json').write_text(json.dumps(merkle,indent=2)) # Refund sensitivity; an illustrative $120 invoice originally settled with 1.5 LTC at $80. refund=[] for price in [60,80,100]: for policy in ['Original LTC','Original USD']: ltc=D('1.5') if policy=='Original LTC' else D(120)/D(price) refund.append(dict(price_usd=price,policy=policy,refund_ltc=str(ltc),refund_value_usd=str(ltc*D(price)))) csvwrite('refund-sensitivity.csv',refund) result={'dataset_type':'Synthetic teaching fixtures, computed 2026-09-22','reconciliation':{'rows':len(rows),'balances':{k:str(v) for k,v in b.items()},'total':'10.7287','errors':mutations},'performance':returns,'merkle':merkle,'refund':refund} (B/'calculations.json').write_text(json.dumps(result,indent=2));print(json.dumps(result,indent=2))