"""DJ031—DJ050 构造算例;仅用 Python 标准库,运行后生成同目录 JSON。""" import json import math import random import statistics import sys from pathlib import Path def balance(start, returns, withdrawals): rows = [] b = start for y, (r, w) in enumerate(zip(returns, withdrawals), 1): opening = b available = b * (1 + r) paid = min(w, available) b = max(0, available - w) rows.append(dict(year=y, opening=opening, rate=r, withdrawal=w, paid=paid, shortfall=w-paid, end=b)) return rows def monte(seed, n, initial=2000000, withdrawal=90000, years=30): rng = random.Random(seed) # 年度对数总回报独立正态;不是简单收益正态,避免低于负百分之百。 ends = [] survived = [] for _ in range(n): b = initial ok = True for y in range(years): available = b * math.exp(rng.gauss(.04, .15)) w = withdrawal * 1.02**y if available < w: ok = False b = max(0, available - w) ends.append(b) survived.append(ok) return ends, survived def sigma(a, b, rho, wa=.6, wb=.4): return math.sqrt((wa*a)**2+(wb*b)**2+2*wa*wb*rho*a*b) def wilson(wins, n, z=1.959963984540054): p = wins/n d = 1+z*z/n center = (p+z*z/(2*n))/d half = z*math.sqrt(p*(1-p)/n+z*z/(4*n*n))/d return [center-half, center+half] def calculate(): d = {} rows=[] b=600000 for year in range(1, 11): salary=216000 if year<=3 else 96000 if year<=6 else 0 pension=0 if year<=3 else 60000 if year<=6 else 108000 spending=144000 if year<=3 else 132000 if year<=6 else 120000 opening=b b=b*1.02+salary+pension-spending rows.append(dict(year=year,salary=salary,pension=pension,spending=spending, net=salary+pension-spending,opening=opening,end=b)) d['DJ031']={'initial':600000,'rate':.02,'years':rows, 'wrong_early_pension_excess_year6':sum(48000*1.02**i for i in range(3))} rows=[] b=180000 for m in range(1,31): pension=0 if m<=24 else 5000 income=pension w=8000-income b-=w rows.append(dict(month=m,pension=pension,gap=w,end=b)) d['DJ032']={'initial':180000,'monthly_spend':8000,'receipt_month':25, 'months':rows,'bridge_need':sum(r['gap'] for r in rows[:24]), 'maturity_buckets':[dict(start_month=i,end_month=i+5,amount=8000*6) for i in range(1,25,6)], 'receipt_19_first24_need':18*8000+6*(8000-5000), 'receipt_31_first30_need':30*8000} ret=[.04]*20 d['DJ033']={'fixed':balance(1500000,ret,[80000]*20), 'indexed':balance(1500000,ret,[80000*1.03**i for i in range(20)]), 'year20_fixed_spend_real':80000/1.03**19} annual=.06; monthly=1.06**(1/12)-1 rows=[] for mode in ['年初','月末','年末']: b=1000000 for m in range(1,13): opening=b if mode=='年初' and m==1: b-=120000 b*=1+monthly if mode=='月末': b-=10000 if mode=='年末' and m==12: b-=120000 rows.append(dict(mode=mode,month=m,opening=opening,end=b)) d['DJ034']={'annual_rate':annual,'monthly_rate':monthly,'months':rows} rows=balance(2000000,[.04]*20,[100000*1.025**i+(200000*1.04**i if i==7 else 0) for i in range(20)]) for i,r in enumerate(rows): r['daily']=100000*1.025**i r['oneoff']=200000*1.04**i if i==7 else 0 d['DJ035']={'with_oneoff':rows,'without_oneoff':balance(2000000,[.04]*20,[100000*1.025**i for i in range(20)]),'oneoff_wrong_general_inflation':200000*1.025**7} seq=[-.2,-.1,.05,.1,.2] a=seq+[.03]*25; b=list(reversed(seq))+[.03]*25 d['DJ036']={'forward':balance(1000000,a,[80000]*30),'reverse':balance(1000000,b,[80000]*30), 'no_withdrawal_forward':balance(1000000,a,[0]*30)[-1]['end'], 'no_withdrawal_reverse':balance(1000000,b,[0]*30)[-1]['end']} rows=[] for n in [100,1000,10000]: for seed in [20261023,20261024,20261025]: ends,ok=monte(seed,n) k=sum(ok); p=k/n rows.append(dict(seed=seed,n=n,success=k,rate=p,se=math.sqrt(p*(1-p)/n))) ends,ok=monte(20261023,1000) ends2,ok2=monte(20261023,1000) assert ends==ends2 and ok==ok2 d['DJ037']={'assumptions':{'initial':2000000,'withdrawal':90000,'inflation':.02,'log_mean':.04,'log_sd':.15,'years':30},'runs':rows,'fixed_seed_repeat_identical':ends==ends2} ends,ok=monte(20261023,10000) criteria=[('全程足额且终值非负',0),('全程足额且留应急金',300000*1.02**30),('全程足额且留实际本金',2000000*1.02**30)] d['DJ038']={'seed':20261023,'n':len(ends),'criteria':[dict(name=name,nominal_target=target,success=sum(v and e>=target for e,v in zip(ends,ok))) for name,target in criteria], 'zero_balance_count':sum(e==0 for e in ends)} d['DJ039']={'housing_value':3000000,'financial':1200000,'annual_base_spend':120000, 'self_use':{'liquid':1200000,'net_rent':0,'spend':120000}, 'rented':{'liquid':1200000,'net_rent':90000-18000,'spend':120000+60000}, 'sold':{'liquid':1200000+3000000-90000,'net_rent':0,'spend':120000+60000}} rows=[] for g in [0,.02,.04]: for t in [0,5,10,20]: pension=72000*(1+g)**t price=1.03**t rows.append(dict(growth=g,year=t,pension=pension,real_pension=pension/price, replacement=(pension/price)/120000,spend=120000*price,gap=120000*price-pension)) d['DJ040']={'initial_pension':72000,'base_salary':120000,'inflation':.03,'scenarios':rows} rows=[] for cash in [0,10000,20000,50000,80000]: total=200000+cash need_a=.6*total-150000; need_b=.4*total-50000 buy_a=max(0,need_a); buy_b=cash-buy_a rows.append(dict(cash=cash,total=total,buy_a=buy_a,buy_b=buy_b,a_weight=(150000+buy_a)/total, b_weight=(50000+buy_b)/total,a_excess=max(0,-need_a),b_gap=max(0,need_b-buy_b))) d['DJ041']={'a':150000,'b':50000,'target_a':.6,'minimum_cash':150000/.6-200000,'scenarios':rows} rows=[] for withdraw in [20000,100000,200000]: remaining=500000-withdraw; ta=remaining*.6; tb=remaining*.4 rows.append(dict(withdraw=withdraw,target_a=ta,target_b=tb,sell_a=350000-ta,sell_b=150000-tb)) d['DJ042']={'a':350000,'b':150000,'target_a':.6,'scenarios':rows} path=[('2026-01-31',.6),('2026-02-28',.625),('2026-03-31',.631),('2026-04-30',.648),('2026-05-31',.65),('2026-06-30',.66),('2026-07-31',.59),('2026-08-31',.565),('2026-09-30',.545),('2026-10-31',.6)] d['DJ043']={'target':.6,'rows':[dict(date=date,weight=w,pp=100*abs(w-.6),relative=abs(w-.6)/.6, trigger_pp=abs(w-.6)>=.05-1e-12,trigger_relative=abs(w-.6)/.6>=.05-1e-12) for date,w in path]} d['DJ044']={'rate':.0002,'minimum':5,'rows':[dict(amount=v,proportional=v*.0002,fee=max(5,v*.0002),fraction=max(5,v*.0002)/v,two_leg_fee=2*max(5,v*.0002)) for v in [200,500,1000,5000,10000,25000,50000]],'split_5000_into_ten':10*max(5,500*.0002)} d['DJ045']={'a_sd':.2,'b_sd':.06,'rho':.2,'rows':[dict(name=name,wa=wa,wb=wb,sum=wa+wb,raw_sigma=sigma(.2,.06,.2,wa,wb),normalized_sigma=sigma(.2,.06,.2,wa/(wa+wb),wb/(wa+wb))) for name,wa,wb in [('遗漏现金',.5,.3),('重复行',.7,.4),('融资敞口',.9,.6)]]} a=[]; b=[] for i in range(252): x=.012*math.sin(.73*i)+.006*math.cos(.19*i) y=(.004*math.cos(.53*i)-.001*x) if i<222 else (.45*x+.002*math.cos(.53*i)) a.append(x); b.append(y) rows=[] for n in [30,252]: aa=a[-n:];bb=b[-n:] sa=statistics.stdev(aa)*math.sqrt(252);sb=statistics.stdev(bb)*math.sqrt(252) rho=statistics.correlation(aa,bb) direct=statistics.stdev([.6*x+.4*y for x,y in zip(aa,bb)])*math.sqrt(252) assert math.isclose(direct,sigma(sa,sb,rho)) rows.append(dict(window=n,a_sigma=sa,b_sigma=sb,rho=rho,portfolio_sigma=sigma(sa,sb,rho),direct_series_sigma=direct)) d['DJ046']={'synthetic_daily_a':a,'synthetic_daily_b':b,'windows':rows,'fixed_sigma_window_comparison':[dict(window=r['window'],sigma=sigma(.2,.06,r['rho'])) for r in rows]} d['DJ047']={'rows':[dict(name=name,cash_sd=sd,rho=rho,portfolio_sd=sigma(.2,sd,rho)) for name,sd,rho in [('同币种名义',0,0),('外币现金零相关',.08,0),('外币现金正相关',.08,.5)]], 'foreign_return':1.02*.9-1,'real_local_cash_return':1/1.03-1} funds={'甲':{'X':.5,'Y':.3,'Z':.2},'乙':{'X':.4,'Y':.4,'Z':.2},'丙':{'X':.6,'Y':.2,'Q':.2}} fw={'甲':.4,'乙':.35,'丙':.25} totals={k:sum(fw[f]*funds[f].get(k,0) for f in funds) for k in ['X','Y','Z','Q']} d['DJ048']={'funds':funds,'weights':fw,'lookthrough':totals,'pair_overlap':[{ 'pair':f'{f}/{g}','overlap':sum(min(funds[f].get(k,0),funds[g].get(k,0)) for k in totals)} for f,g in [('甲','乙'),('甲','丙'),('乙','丙')]],'hhi':sum(w*w for w in totals.values())} rows=[] for sell,fee in [(45,0),(42,0),(42,.001)]: loss=1000*(50-sell)+1000*(50+sell)*fee rows.append(dict(shares=1000,buy=50,planned_stop=45,actual_sell=sell,fee_rate=fee,loss=loss,budget_ratio=loss/5000)) d['DJ049']={'budget':5000,'rows':rows,'fee_inclusive_quantity_for_given_stress':math.floor(5000/((50-42)+(50+42)*.001))} rows=[] for n,wins in [(40,24),(100,60),(400,240)]: lo,hi=wilson(wins,n) rows.append(dict(n=n,wins=wins,p=wins/n,p_low=lo,p_high=hi,kelly_low=lo-(1-lo)/1.5,kelly_point=wins/n-(1-wins/n)/1.5,kelly_high=hi-(1-hi)/1.5)) d['DJ050']={'reward_loss_ratio':1.5,'confidence':.95,'method':'Wilson score','rows':rows,'p_sensitivity':[dict(p=p,kelly=p-(1-p)/1.5) for p in [.4,.45,.5,.55,.6,.65,.7,.75]]} assert len(d)==20 for r in d['DJ042']['scenarios']: assert math.isclose(r['sell_a']+r['sell_b'],r['withdraw']) assert math.isclose(sum(totals.values()),sum(fw.values())) assert math.isclose(d['DJ036']['no_withdrawal_forward'],d['DJ036']['no_withdrawal_reverse']) for key in d: d[key]['python_version']=sys.version.split()[0] return d if __name__=='__main__': output=Path(__file__).with_suffix('.json') result=calculate() output.write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf-8') print(json.dumps({k:({x:v for x,v in obj.items() if x not in ['synthetic_daily_a','synthetic_daily_b']} if k=='DJ046' else obj) for k,obj in result.items()},ensure_ascii=False,indent=2))