# Day 14: Alpha-101因子库精选
# 测试WorldQuant Alpha-101中适合月度调仓的因子

# ==================== 可切换参数（在这里修改）====================
VERSION = 'v1'  # 'v1': Alpha#6量价背离 | 'v2': Alpha#12量价动量 | 'v3': Alpha#42 VWAP偏离 | 'v4': 等权合成
# ================================================================

import pandas as pd
import numpy as np
from jqdata import *

"""
回测参数：2020-01-01 至 2026-02-01，100万，沪深300，月度调仓，持仓20只

V1: Alpha#6  = -correlation(open, volume, 10)
V2: Alpha#12 = sign(delta(volume,1)) * (-delta(close,1))
V3: Alpha#42 = rank(vwap-close) / rank(vwap+close)
V4: 等权合成（V1+V2+V3）
"""

def initialize(context):
    g.stock_num = 20
    g.index = '000300.XSHG'
    set_option('use_real_price', True)
    log.set_level('order', 'error')
    run_monthly(trade, 1)

# ==================== 预处理 ====================

def winsorize(series):
    return series.clip(series.quantile(0.05), series.quantile(0.95))

def standardize(series):
    std = series.std()
    if std == 0:
        return series * 0
    return (series - series.mean()) / std

# ==================== Alpha因子计算 ====================

def get_alpha6(stocks, date):
    """
    Alpha#6: -correlation(open, volume, 10)
    量价背离因子
    """
    try:
        price_df = get_price(stocks, end_date=date, count=11,
                             fields=['open', 'volume'], panel=False, fq='pre')
        if price_df is None or price_df.empty:
            return pd.Series(dtype=float)

        open_pivot = price_df.pivot(index='time', columns='code', values='open')
        vol_pivot = price_df.pivot(index='time', columns='code', values='volume')

        alpha = {}
        for stock in open_pivot.columns:
            op = open_pivot[stock].dropna()
            vo = vol_pivot[stock].dropna()
            if len(op) >= 10 and len(vo) >= 10:
                corr = op.corr(vo)
                if not pd.isna(corr):
                    alpha[stock] = -corr  # 负相关 = 量价背离

        result = pd.Series(alpha)
        if result.empty:
            return result
        return standardize(winsorize(result))

    except Exception as e:
        log.error(f"Alpha#6失败: {e}")
        return pd.Series(dtype=float)

def get_alpha12(stocks, date):
    """
    Alpha#12: sign(delta(volume,1)) * (-delta(close,1))
    量价动量因子
    """
    try:
        price_df = get_price(stocks, end_date=date, count=2,
                             fields=['close', 'volume'], panel=False, fq='pre')
        if price_df is None or price_df.empty:
            return pd.Series(dtype=float)

        close_pivot = price_df.pivot(index='time', columns='code', values='close')
        vol_pivot = price_df.pivot(index='time', columns='code', values='volume')

        alpha = {}
        for stock in close_pivot.columns:
            cl = close_pivot[stock].dropna()
            vo = vol_pivot[stock].dropna()
            if len(cl) >= 2 and len(vo) >= 2:
                delta_vol = vo.iloc[-1] - vo.iloc[-2]
                delta_close = cl.iloc[-1] - cl.iloc[-2]
                sign_vol = 1 if delta_vol > 0 else (-1 if delta_vol < 0 else 0)
                alpha[stock] = sign_vol * (-delta_close)

        result = pd.Series(alpha)
        if result.empty:
            return result
        return standardize(winsorize(result))

    except Exception as e:
        log.error(f"Alpha#12失败: {e}")
        return pd.Series(dtype=float)

def get_alpha42(stocks, date):
    """
    Alpha#42: rank(vwap - close) / rank(vwap + close)
    VWAP偏离因子
    """
    try:
        price_df = get_price(stocks, end_date=date, count=1,
                             fields=['close', 'money', 'volume'], panel=False, fq='pre')
        if price_df is None or price_df.empty:
            return pd.Series(dtype=float)

        price_df = price_df.set_index('code')

        # 计算VWAP = 成交额 / 成交量
        price_df['vwap'] = price_df['money'] / price_df['volume']
        price_df = price_df[price_df['volume'] > 0]

        if price_df.empty:
            return pd.Series(dtype=float)

        diff = price_df['vwap'] - price_df['close']
        total = price_df['vwap'] + price_df['close']

        # rank: 截面排名归一化到0-1
        rank_diff = diff.rank(pct=True)
        rank_total = total.rank(pct=True)

        # 避免除以0
        rank_total = rank_total.replace(0, np.nan)
        alpha = rank_diff / rank_total
        alpha = alpha.dropna()

        if alpha.empty:
            return alpha
        return standardize(winsorize(alpha))

    except Exception as e:
        log.error(f"Alpha#42失败: {e}")
        return pd.Series(dtype=float)

# ==================== 因子合成 ====================

def combine_equal(factors):
    common = None
    for f in factors.values():
        if common is None:
            common = set(f.index)
        else:
            common = common.intersection(set(f.index))
    common = list(common)
    if not common:
        return pd.Series(dtype=float)
    result = pd.Series(0.0, index=common)
    for f in factors.values():
        result += f.loc[common] / len(factors)
    return result

# ==================== 交易 ====================

def trade(context):
    stocks = get_index_stocks(g.index)

    if VERSION == 'v1':
        factor = get_alpha6(stocks, context.current_dt)
    elif VERSION == 'v2':
        factor = get_alpha12(stocks, context.current_dt)
    elif VERSION == 'v3':
        factor = get_alpha42(stocks, context.current_dt)
    elif VERSION == 'v4':
        a6 = get_alpha6(stocks, context.current_dt)
        a12 = get_alpha12(stocks, context.current_dt)
        a42 = get_alpha42(stocks, context.current_dt)
        valid = {}
        if not a6.empty:
            valid['a6'] = a6
        if not a12.empty:
            valid['a12'] = a12
        if not a42.empty:
            valid['a42'] = a42
        if not valid:
            return
        factor = combine_equal(valid)
    else:
        factor = get_alpha6(stocks, context.current_dt)

    if factor.empty:
        return

    target = factor.sort_values(ascending=False).head(g.stock_num).index.tolist()

    for stock in list(context.portfolio.positions.keys()):
        if stock not in target:
            order_target(stock, 0)

    if target:
        w = 1.0 / len(target)
        for stock in target:
            order_target_value(stock, context.portfolio.total_value * w)
