# -*- coding: utf-8 -*-
"""封基折价横截面策略回测: 每月做多折价最深1/3 vs 最浅1/3, 场内价收益。
剔相似beta看折价收敛溢价。用 cef/detail/*.json(场内价+折价史 nav_date同日)。"""
import json, glob, os, numpy as np, pandas as pd
D="my-app/public/data/cef/detail"
funds={}
for f in glob.glob(f"{D}/*.json"):
    d=json.load(open(f))
    if len(d['dates'])<250: continue
    s=pd.DataFrame({'d':d['dates'],'px':d['price'],'disc':d['disc']}).set_index('d')
    funds[d['code']]=(d['name'],s)
print(f"{len(funds)} 只封基(≥250日)",flush=True)
alld=sorted(set().union(*[set(s.index) for _,s in funds.values()]))
# 月初日期
months=pd.Series(alld).groupby(pd.Series(alld).str[:6]).first().tolist()
def px(c,d): 
    s=funds[c][1]; return s.loc[d,'px'] if d in s.index else None
def disc(c,d):
    s=funds[c][1]; return s.loc[d,'disc'] if d in s.index else None
# 每月: 按折价排序, 深1/3做多, 浅1/3做多, 全部; 持有到下月, 场内价收益
def run(bucket):  # 'deep' | 'shallow' | 'all'
    rets=[]
    for i in range(len(months)-1):
        d0,d1=months[i],months[i+1]
        cand=[(c,disc(c,d0)) for c in funds if disc(c,d0) is not None and px(c,d0) and px(c,d1)]
        if len(cand)<9: continue
        cand.sort(key=lambda x:x[1])  # 折价最深(最负)在前
        n=len(cand)//3
        if bucket=='deep': sel=[c for c,_ in cand[:n]]
        elif bucket=='shallow': sel=[c for c,_ in cand[-n:]]
        else: sel=[c for c,_ in cand]
        r=np.mean([px(c,d1)/px(c,d0)-1 for c in sel])
        rets.append(r)
    nav=np.cumprod([1+x for x in rets]); yrs=len(rets)/12
    cagr=(nav[-1]**(1/yrs)-1)*100
    sharpe=np.mean(rets)/np.std(rets)*np.sqrt(12)
    mdd=((nav/np.maximum.accumulate(nav))-1).min()*100
    return dict(cagr=round(cagr,1),sharpe=round(float(sharpe),2),mdd=round(float(mdd),1),n=len(rets))
for b,lbl in [('deep','折价最深1/3'),('shallow','折价最浅1/3'),('all','全部等权')]:
    print(f"{lbl}: {run(b)}",flush=True)
print("DONE",flush=True)

# 加成本版 + 长短纯溢价
def run2(bucket, cost):
    rets=[]; prev=set()
    for i in range(len(months)-1):
        d0,d1=months[i],months[i+1]
        cand=[(c,disc(c,d0)) for c in funds if disc(c,d0) is not None and px(c,d0) and px(c,d1)]
        if len(cand)<9: continue
        cand.sort(key=lambda x:x[1]); n=len(cand)//3
        sel=set(c for c,_ in (cand[:n] if bucket=='deep' else cand[-n:]))
        r=np.mean([px(c,d1)/px(c,d0)-1 for c in sel])
        turn=len(sel^prev)/max(len(sel),1); r-=cost*turn; prev=sel
        rets.append(r)
    nav=np.cumprod([1+x for x in rets]); yrs=len(rets)/12
    return round((nav[-1]**(1/yrs)-1)*100,1), round(float(np.mean(rets)/np.std(rets)*np.sqrt(12)),2)
print("\n=== 加成本(0.3%/边)===",flush=True)
for c in (0.0,0.003,0.006):
    dc,ds=run2('deep',c); sc,ss=run2('shallow',c)
    print(f"成本{c*100:.1f}%: 深{dc}%/{ds}  浅{sc}%/{ss}  价差{dc-sc:.1f}pp",flush=True)
# -*- coding: utf-8 -*-
"""封基深-浅折价 多空序列 + 对创业板指beta。验证27pp价差是收敛alpha还是板块beta。"""
import json, glob, sys, importlib.util, numpy as np, pandas as pd
D="my-app/public/data/cef/detail"
funds={}
for f in glob.glob(f"{D}/*.json"):
    d=json.load(open(f))
    if len(d['dates'])<250: continue
    funds[d['code']]=pd.DataFrame({'d':d['dates'],'px':d['price'],'disc':d['disc']}).set_index('d')
alld=sorted(set().union(*[set(s.index) for s in funds.values()]))
months=pd.Series(alld).groupby(pd.Series(alld).str[:6]).first().tolist()
# 创业板指月收益(index_daily 399006.SZ)
sys.path.insert(0,'.'); sys.path.insert(0,'scripts')
spec=importlib.util.spec_from_file_location("ldm","lof_data_manager.py"); m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
import sqlite3; c=sqlite3.connect('data/fundamental_cache.db')
idx=pd.read_sql("SELECT trade_date,close FROM index_daily WHERE ts_code='399006.SZ'",c).set_index('trade_date')['close']
c.close()
def px(cc,d): s=funds[cc]; return s.loc[d,'px'] if d in s.index else None
def disc(cc,d): s=funds[cc]; return s.loc[d,'disc'] if d in s.index else None
ls=[]; bench=[]
for i in range(len(months)-1):
    d0,d1=months[i],months[i+1]
    cand=[(cc,disc(cc,d0)) for cc in funds if disc(cc,d0) is not None and px(cc,d0) and px(cc,d1)]
    if len(cand)<9: continue
    cand.sort(key=lambda x:x[1]); n=len(cand)//3
    deep=[cc for cc,_ in cand[:n]]; shal=[cc for cc,_ in cand[-n:]]
    rd=np.mean([px(cc,d1)/px(cc,d0)-1 for cc in deep]); rs=np.mean([px(cc,d1)/px(cc,d0)-1 for cc in shal])
    ls.append(rd-rs)
    # 创业板指同期
    if d0 in idx.index and d1 in idx.index: bench.append(idx[d1]/idx[d0]-1)
    else: bench.append(np.nan)
ls=np.array(ls); bench=np.array(bench)
mask=~np.isnan(bench)
print(f"多空(深-浅)月数: {len(ls)}")
print(f"  年化: {(np.prod([1+x for x in ls])**(12/len(ls))-1)*100:.1f}% | Sharpe: {np.mean(ls)/np.std(ls)*np.sqrt(12):.2f} | 胜率: {(ls>0).mean()*100:.0f}%")
# 回归 cre180
b=np.polyfit(bench[mask],ls[mask],1)
corr=np.corrcoef(bench[mask],ls[mask])[0,1]
alpha_m=np.mean(ls[mask]-b[0]*bench[mask])
print(f"  对创业板指: beta={b[0]:.2f} corr={corr:.2f} | beta中性化后月alpha={alpha_m*100:.2f}% (年化~{alpha_m*12*100:.0f}%)")
print("DONE")

