"""
Day 10：另类因子单因子选股策略
100天经典策略学习计划

测试因子：分析师关注度、股东人数变化、限售解禁
方法：根据另类因子信号选股，等权持有
股票池：沪深300
调仓频率：每月

使用方法：修改 FACTOR_NAME 切换测试不同因子
回测建议：2020-01-01 至 2026-02-01，初始资金100万

注意：另类因子数据获取受限，使用替代方案
"""


# ========== 切换因子 ==========
# 可选: 'analyst_attention', 'shareholder_change', 'lockup_expiry'
FACTOR_NAME = 'analyst_attention'
# ==============================


def initialize(context):
    set_params()
    set_backtest()
    run_monthly(rebalance, monthday=1, time='09:31')


def set_params():
    g.stock_pool = '000300.XSHG'
    g.stock_num = 20
    g.factor = FACTOR_NAME


def set_backtest():
    set_benchmark('000300.XSHG')
    set_option('use_real_price', True)
    set_slippage(FixedSlippage(0.02))
    set_commission(PerTrade(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
    log.set_level('order', 'error')


def rebalance(context):
    stocks = get_index_stocks(g.stock_pool)
    stocks = filter_stocks(stocks)

    df = get_factor_data(stocks, context)

    if df is None or df.empty:
        return

    # 另类因子：根据不同指标选股
    df = df.sort_values(g.factor, ascending=False)
    target = df['code'].head(g.stock_num).tolist()

    log.info(f'[{g.factor}] 选出{len(target)}只，前3: {target[:3]}')

    adjust_portfolio(context, target)


def get_factor_data(stocks, context):
    """获取另类因子数据"""
    import pandas as pd
    import numpy as np

    if g.factor == 'analyst_attention':
        # 分析师关注度（替代方案：用换手率的反向作为"冷门股"）
        # 真实场景应该用研报数量，但聚宽免费版无此数据
        # 这里用一个简化逻辑：选关注度适中的股票
        return calculate_analyst_proxy(stocks, context)

    elif g.factor == 'shareholder_change':
        # 股东人数变化
        return calculate_shareholder_change(stocks, context)

    elif g.factor == 'lockup_expiry':
        # 限售解禁（简化版：避开高解禁压力的股票）
        return calculate_lockup_proxy(stocks, context)


def calculate_analyst_proxy(stocks, context):
    """
    分析师关注度代理指标
    由于无法获取真实研报数据，用市值和换手率的组合作为代理
    逻辑：选中等市值、中等换手率的股票（关注度适中）
    """
    import pandas as pd

    q = query(
        valuation.code,
        valuation.market_cap,
        valuation.circulating_market_cap
    ).filter(
        valuation.code.in_(stocks)
    )

    df = get_fundamentals(q, date=context.current_dt)

    if df.empty:
        return None

    # 计算市值分位数（选中等市值）
    df['cap_rank'] = df['market_cap'].rank(pct=True)

    # 计算过去20日平均换手率
    prices = get_price(
        stocks,
        end_date=context.current_dt,
        count=20,
        frequency='daily',
        fields=['money'],
        panel=False
    )

    if prices is not None and not prices.empty:
        turnover = prices.groupby('code')['money'].mean()
        df = df.set_index('code')
        df['avg_turnover'] = turnover
        df = df.reset_index()
        df = df.dropna(subset=['avg_turnover'])

        # 计算换手率分位数
        df['turnover_rank'] = df['avg_turnover'].rank(pct=True)

        # 综合得分：选市值和换手率都在40-60分位的股票
        df['score'] = 100 - abs(df['cap_rank'] - 0.5) * 100 - abs(df['turnover_rank'] - 0.5) * 100
        df = df.rename(columns={'score': 'analyst_attention'})

        return df[['code', 'analyst_attention']]

    return None


def calculate_shareholder_change(stocks, context):
    """
    股东人数变化（替代方案：前十大股东持股比例）
    逻辑：选前十大股东持股比例高的股票（筹码集中）

    由于聚宽免费版无法直接获取股东户数数据，
    使用前十大股东持股比例作为筹码集中度的代理指标
    """
    import pandas as pd

    # 使用valuation表中的数据
    q = query(
        valuation.code,
        valuation.market_cap
    ).filter(
        valuation.code.in_(stocks)
    )

    df = get_fundamentals(q, date=context.current_dt)

    if df.empty:
        return None

    # 获取过去20日的成交量标准差作为筹码集中度的代理
    # 成交量波动小 = 筹码稳定 = 可能筹码集中
    prices = get_price(
        stocks,
        end_date=context.current_dt,
        count=20,
        frequency='daily',
        fields=['volume'],
        panel=False
    )

    if prices is None or prices.empty:
        return None

    results = []
    for stock in stocks:
        stock_data = prices[prices['code'] == stock]
        if len(stock_data) >= 20:
            volume = stock_data['volume'].values
            # 计算成交量的变异系数（标准差/均值）
            vol_mean = volume.mean()
            vol_std = volume.std()

            if vol_mean > 0:
                # 变异系数越小，成交量越稳定，筹码越集中
                cv = vol_std / vol_mean
                # 转换为正分（变异系数越小，分数越高）
                score = 100 / (1 + cv)
                results.append({'code': stock, 'shareholder_change': score})

    if results:
        return pd.DataFrame(results)

    return None


def calculate_lockup_proxy(stocks, context):
    """
    限售解禁代理指标
    由于解禁数据复杂，这里用一个简化逻辑：
    选流通市值/总市值比例高的股票（解禁压力小）
    """
    import pandas as pd

    q = query(
        valuation.code,
        valuation.market_cap,
        valuation.circulating_market_cap
    ).filter(
        valuation.code.in_(stocks),
        valuation.market_cap > 0
    )

    df = get_fundamentals(q, date=context.current_dt)

    if df.empty:
        return None

    # 流通比例 = 流通市值 / 总市值
    df['circulation_ratio'] = df['circulating_market_cap'] / df['market_cap']

    # 流通比例越高，解禁压力越小，分数越高
    df['lockup_expiry'] = df['circulation_ratio'] * 100

    return df[['code', 'lockup_expiry']]


def filter_stocks(stocks):
    """过滤ST和停牌"""
    current_data = get_current_data()
    return [s for s in stocks
            if not current_data[s].is_st
            and not current_data[s].paused]


def adjust_portfolio(context, target):
    """调仓：先卖后买"""
    for stock in list(context.portfolio.positions):
        if stock not in target:
            order_target(stock, 0)

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