#!/usr/bin/env python3
"""行业动量(Moskowitz-Grinblatt)A股回测 —— /reports/industry-momentum 的可复现脚本。
数据: data/fundamental_cache.db (daily + sw_member). 申万一级31行业成分股等权月收益, top5/bottom5 月度轮动。
口径对比(回答"11月动量是不是12-1"): 报告用的是 B=12-1(回看12月跳最近1月).
  A 含最近月(plain 11月): 价差+7.8 train+14.5 test-1.3(翻负)
  B 12-1 跳最近月:        价差+11.7 train+18.6 test+2.3  <-- 报告口径, 对上+11.2/+17.4/+3.0
  C 12月无跳:             价差+12.0 train+20.1 test+1.3
结论: 最近一个月带短期反转, 行业层面也需跳过(12-1); 不跳 test 就翻负.
运行: python3 backtest.py
"""

import sqlite3, pandas as pd, numpy as np
c=sqlite3.connect("data/fundamental_cache.db")
dd=pd.read_sql("SELECT trade_date,ts_code,pct_chg FROM daily WHERE trade_date>='20141201'",c)
sw=pd.read_sql("SELECT l1_name,ts_code FROM sw_member",c)
sb=pd.read_sql("SELECT ts_code,name FROM stock_basic",c).set_index("ts_code")
c.close()
dd["dt"]=pd.to_datetime(dd.trade_date,format="%Y%m%d")
r=dd.pivot(index="dt",columns="ts_code",values="pct_chg").sort_index()/100.0; r=r.clip(-0.11,0.21)
st=[x for x in sb.index[sb.name.fillna("").str.contains("ST")] if x in r.columns]; r=r.drop(columns=list(set(st)))
mo=(1+r).resample("ME").prod()-1; cnt=r.resample("ME").count(); mo=mo.where(cnt>=5)
s2i=sw.drop_duplicates("ts_code").set_index("ts_code").l1_name
# 行业月收益 = 成分股等权
indret={}
for ind,g in sw.groupby("l1_name"):
    cols=[x for x in g.ts_code.unique() if x in mo.columns]
    if len(cols)>=3: indret[ind]=mo[cols].mean(axis=1)
IND=pd.DataFrame(indret)   # 月 x 行业
nav=(1+IND.fillna(0)).cumprod()
def bt(mom, lab):
    fwd=IND.shift(-1); me=IND.index[(IND.index>="2016-01-31")&(IND.index<"2026-05-31")]
    win=[];los=[];mk=[]
    for t in me:
        if t not in mom.index: continue
        s=mom.loc[t].dropna()
        if len(s)<15: continue
        top=list(s.nlargest(5).index); bot=list(s.nsmallest(5).index)
        if t in fwd.index:
            win.append(fwd.loc[t,top].mean()); los.append(fwd.loc[t,bot].mean()); mk.append(fwd.loc[t].mean())
    win=pd.Series(win,index=me[:len(win)]);los=pd.Series(los,index=win.index);mk=pd.Series(mk,index=win.index)
    ann=lambda x:((1+x).prod()**(12/len(x))-1)*100
    spr=lambda a,b: (ann(a)-ann(b))
    tr=slice(None, "2021-12-31"); te=slice("2022-01-01",None)
    print(f"{lab:22s} 赢家超额{ann(win)-ann(mk):+5.1f} 价差(赢-输){spr(win,los):+5.1f}pp | train价差{spr(win[tr],los[tr]):+5.1f} test价差{spr(win[te],los[te]):+5.1f}")
# 口径A: plain 11月(含最近月) price[t]/price[t-11]
momA=nav/nav.shift(11)-1
# 口径B: 12-1 跳最近月 price[t-1]/price[t-12]
momB=nav.shift(1)/nav.shift(12)-1
# 口径C: plain 12月 无跳
momC=nav/nav.shift(12)-1
bt(momA,"A: 11月(含最近月)")
bt(momB,"B: 12-1(跳最近月)")
bt(momC,"C: 12月无跳")
