import dai
import pandas as pd
import empyrical
from bigmodule import M

def _m5_initialize_bigquant_run(context):
    # 加载预测数据
    from bigtrader.finance.commission import PerOrder
    context.stock_num = 10
    
    context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.002, min_cost=5))

# @param(id="m5", name="handle_data")
def _m5_handle_data_bigquant_run(context, data):
    # 下一个交易日不是调仓日，则不生成信号
    if not context.rebalance_period.is_signal_date(data.current_dt.date()):
        return
    
    dt = data.current_dt.strftime('%Y-%m-%d')
    data = context.data[context.data['date']==dt].sort_values('factor', ascending=True)
    if len(data) == 0:
        return
    data = data.head(context.stock_num)
    instruments = data['instrument'].to_list()
    
    holding_list = context.get_account_positions()
    holding_list = {k:v for k,v in holding_list.items() if v.current_qty > 0}
    
    # 卖出
    for ins in holding_list:
        if ins not in instruments:
            context.order_target_percent(ins, 0)
    
    # 买入
    for ins in instruments:
        if ins not in holding_list:
            context.order_target_percent(ins, 1 / context.stock_num)

def eval_factor(start_date, end_date, feature_name, plot_show=False):
    """
    1. 通过IC确定方向; 
    2. 之后根据IC确定的方向作回测. 
    """
    feature_name = feature_name.replace('"', '')
    print(f'===============对因子 {feature_name} 进行评估=================')

    sql = f"""
    with data_alpha as (
        select date, instrument, {feature_name} as factor
        from cn_stock_prefactors
    ), 
    data_alpha_origin as (
        select * from data_alpha
        QUALIFY COLUMNS(*) IS NOT NULL AND factor != 'Infinity' AND factor != '-Infinity'
    ),
    data_alpha_process as (
        select date, instrument, 
        factor, 
        clip(factor, c_avg(factor) - 3 * c_std(factor), c_avg(factor) + 3 * c_std(factor)) AS clipped_factor, 
        c_normalize(clipped_factor) as normalized_factor, 
        c_neutralize(normalized_factor, sw2021_level1, LOG(total_market_cap)) as neutralized_factor
        from data_alpha_origin
        inner join cn_stock_prefactors using (date, instrument)
        where amount > 0
        and st_status = 0
        and list_days > 252
        and list_sector < 3
        and close / adjust_factor > 1.5
    )
    select date, instrument, neutralized_factor as factor
    from data_alpha_process
    order by date
    """
    d = dai.query(sql, filters={'date': [start_date, end_date]}).df()

    sql = """
    SELECT date, instrument, m_lead(close, 5) / close - 1 AS return, c_pct_rank(factor) as f
    FROM cn_stock_prefactors
    inner join d using (date, instrument)
    WHERE st_status = 0
    and list_days > 252
    and list_sector < 3
    and close / adjust_factor > 1.5
    qualify columns(*) is not null
    """
    return_data = dai.query(sql, filters={'date': [start_date, end_date]}).df()

    # 计算RankIC
    ic = return_data['f'].corr(return_data['return'])
    ic = round(ic, 4)

    if ic > 0:
        sql = """
        SELECT date, instrument, c_rank(factor, ascending:=false) AS factor
        FROM d
        ORDER BY date, factor
        """
        d = dai.query(sql).df()
    else:
        sql = """
        SELECT date, instrument, c_rank(factor, ascending:=true) AS factor
        FROM d
        ORDER BY date, factor
        """
        d = dai.query(sql).df()
    
    m5 = M.bigtrader.v35(
        data=dai.DataSource.write_bdb(d),
        start_date="""""",
        end_date="""""",
        initialize=_m5_initialize_bigquant_run,
        handle_data=_m5_handle_data_bigquant_run,
        capital_base=1000000,
        frequency="""daily""",
        product_type="""股票""",
        rebalance_period_type="""交易日""",
        rebalance_period_days="""5""",
        rebalance_period_roll_forward=True,
        backtest_engine_mode="""标准模式""",
        before_start_days=0,
        volume_limit=1,
        order_price_field_buy="""open""",
        order_price_field_sell="""open""",
        benchmark="""沪深300指数""",
        plot_charts=plot_show,
        debug=False,
        backtest_only=False,
        m_name="""m5"""
    )

    data = m5.raw_perf.read()
    returns = data['returns']
    sharp = round(empyrical.sharpe_ratio(returns), 3)
    drawback = round(empyrical.max_drawdown(returns), 3)
    annual_volatility = round(empyrical.annual_volatility(returns), 3)
    annual_return = round(empyrical.annual_return(returns), 3)
    total_return = round(data['algorithm_period_return'].iloc[-1], 3)

    return f'因子 {feature_name} 的 IC 为 {ic}, 因子累计收益为 {total_return}, 年化收益为 {annual_return}, 夏普比例为 {sharp}, 年化波动率为 {annual_volatility}, 最大回撤为 {drawback}'

eval_prompt = """
eval_factor: 因子评估函数, 用于评估因子效果的方法, 包含三个参数, start_date表示开始日期, end_date表示结束日期, 日期格式形如'2020-01-01'这样的, feature_name 表示需要参与评估的因子名称. 

最终工具会产生以下几个维度的评估指标, 以便于你生成更好的因子: 
1. IC值: 衡量因子和预期收益率的相关系数, 相关系数越偏离0表示因子越有效, 绝对的正相关和绝对的负相关都有利于获取更高的收益, 所以IC为负时, IC越小越好, IC为正时, IC越大越好, 只有IC距离0很近时因子的效果才不行; 
2. 夏普比率: 年化收益率除以年化波动率, 用来衡量投资组合每承受一单位总风险, 会产生多少的超额报酬, 夏普越大策略越好; 
3. 年化波动率: 反应策略的不确定性, 该值越小表示策略越稳定; 
4. 最大回撤: 反应策略的最大亏损程度, 该值为负数, 所以越接近零越好. 
"""
