""" 커버드콜 ETF 단점 · 백테스트 7호 재현 코드 (ajussiguide.com) ========================================================== 이 파일은 파이썬 3 스크립트다. 확장자를 .py로 바꿔 저장한 뒤 같은 폴더의 coveredcall-bt7-data.csv와 함께 실행한다. python coveredcall-bt7-calc.py coveredcall-bt7-data.csv 아래 첫 번째 '#' 줄부터가 코드다. 이 설명 블록은 파이썬이 실행하지 않도록 문자열로 감싸 두었다. """ # 커버드콜 ETF 단점 · 백테스트 7호 — 공개 재현 코드 # 사용: python coveredcall-bt7-calc.py coveredcall-bt7-data.csv # 입력 CSV 열: symbol,date,close,dividend_exdate (분배락일 주당 분배금, 없으면 빈칸) import csv,sys,datetime as dt path=sys.argv[1] if len(sys.argv)>1 else 'coveredcall-bt7-data.csv' raw={} for r in csv.DictReader(open(path)): c=float(r['close']); assert c>0 raw.setdefault(r['symbol'],[]).append((dt.date.fromisoformat(r['date']),c,float(r['dividend_exdate'] or 0))) syms={} for s,rows in raw.items(): rows.sort(); assert all(rows[i][0]0: tr*=(c+dv)/rows[i-1][1] # 분배락일 종가 재투자 x.append({'d':d,'c':c,'tr':tr,'dv':dv}) syms[s]=x pct=lambda v:round(v*100,1) def align(a,b): mb={r['d']:r for r in b}; return [(r,mb[r['d']]) for r in a if r['d'] in mb] def stats(A,B): x=align(A,B); n=len(x); f,l=x[0],x[-1]; yrs=(l[0]['d']-f[0]['d']).days/365.25 trA=l[0]['tr']/f[0]['tr']-1; trB=l[1]['tr']/f[1]['tr']-1 cagr=lambda r:(1+r)**(1/yrs)-1 def mdd(k): p=-1;pd=None;m=0;md=(None,None) for pair in x: r=pair[k] if r['tr']>p: p=r['tr'];pd=r['d'] dd=r['tr']/p-1 if ddx[i][1]['tr']/x[i-w][1]['tr']) return {'win':round(c/(n-w)*100,1),'t':n-w} def dca(k): u=inv=0 for i in range(0,n,21): u+=100/x[i][k]['tr']; inv+=100 return pct(u*x[-1][k]['tr']/inv-1) return {'from':str(f[0]['d']),'to':str(l[0]['d']),'yrs':round(yrs,2),'n':n, 'TR_cc':pct(trA),'TR_idx':pct(trB),'CAGR_cc':pct(cagr(trA)),'CAGR_idx':pct(cagr(trB)), 'PR_cc':pct(l[0]['c']/f[0]['c']-1),'PR_idx':pct(l[1]['c']/f[1]['c']-1), 'MDD_cc':mdd(0),'MDD_idx':mdd(1),'r1':roll(252),'r3':roll(756),'r5':roll(1260), 'DCA_cc':dca(0),'DCA_idx':dca(1),'end_value_100':[round(100*(1+trA),1),round(100*(1+trB),1)]} def yearly(s): x=syms[s];by={} for r in x: by.setdefault(r['d'].year,[r['tr'],r['tr']]); by[r['d'].year][1]=r['tr'] ys=sorted(by); return {str(ys[i]):pct(by[ys[i]][1]/by[ys[i-1]][1]-1) for i in range(1,len(ys))} def cash(s): x=syms[s];f,l=x[0],x[-1];sd=sum(r['dv'] for r in x);ds=[r for r in x if r['dv']>0] yrs=(l['d']-f['d']).days/365.25;last12=sum(r['dv'] for r in ds[-12:]) return {'p0':f['c'],'p1':l['c'],'n_div':len(ds),'sumDiv':round(sd,4),'priceRet':pct(l['c']/f['c']-1), 'cashTR_incl_unpaid':pct((l['c']+sd)/f['c']-1),'cash_annualized':pct(((l['c']+sd)/f['c'])**(1/yrs)-1), 'last12_yield':pct(last12/l['c'])} def pt(s,a,b): x={r['d']:r for r in syms[s]}; return {'TR':pct(x[b]['tr']/x[a]['tr']-1),'PR':pct(x[b]['c']/x[a]['c']-1)} def mdd_year(s,y): p=-1;m=0 for r in syms[s]: if r['d'].year==y: p=max(p,r['tr']); m=min(m,r['tr']/p-1) return pct(m) def worst(s,w): x=syms[s];b=(9,None,None) for i in range(w,len(x)): r=x[i]['tr']/x[i-w]['tr']-1 if r