12.2021第四期AI量化投资训练营

组合优化器使用文档

由small_q创建,最终由small_q 被浏览 26 用户

组合优化器是宽邦科技为满足机构投资者对于股票组合优化、绩效归因、风险控制和指数增强需求而提供的一款优化器。

使用概览

  1. 调用接口:T.PORTFOLIO_OPTIMIZERS
  2. 每日初始化:T.PORTFOLIO_OPTIMIZERS.get_today_factor_data
  3. 权重优化:T.PORTFOLIO_OPTIMIZERS.optimize

\

调用接口

opt=T.PORTFOLIO_OPTIMIZERS(stock_pool, start_date, end_date, model_type='daily', benchmark='000905.HIX')

该命令是整个组合优化器的初始化接口,即使用组合优化进行权重选择之前,必须得先运行此行命令并赋值于一个变量

字段解释:

  • stock_pool: 标的池数据(比如中证500成份股数据,需包含字段:date, instrument, weight/score)

  • start_date: 标的池中数据的起始日期

  • end_date: 标的池中数据的结束日期

  • model_type: 因子收益协方差矩阵和特意收益率矩阵的数据类型,三选一:daily, short, long(默认为daily)

  • benchmark: 基准数据代码(默认为’000905.HIX’)

    \

输出定义为opt的方法,可继续调用函数有:opt.get_today_factor_data, opt.optimize, 以及其余所有的目标函数和约束条件等

\

每日初始化接口

opt.get_today_factor_data(init_pool,current_date) (无需定义变量赋值)

该命令的用法是在整体初始化之后,进行每日权重分配以及约束条件和目标函数确定之前调用该接口

字段解释:

  • init_pool: 每日标的池数据(比如中证500成份股数据,需包含字段:date, instrument, weight/score)
  • current_date: 当前初始化日期

无输出结果,仅作每日初始化使用

\

权重优化接口

weights = opt.optimize(target_func, current_date, cons, stock_count=None, response=True, hard=True, verbose=False)

字段解释:

  • target_func: 目标函数(类型: method)
  • current_date: 当前日期(类型: string)
  • cons: 约束条件,其中包含边界约束bounds(类型: methods)
  • stock_count: 权重优化最大股票数量,若为None则表示没有设定限制(类型: int),默认为None
  • response: 优化错误时,设置为True则返回0值,设置为False则不返回值(类型: bool),默认为True
  • hard: 软硬约束定义,设置为True的话则为软约束,为False则为硬约束(类型: bool),默认为True
  • verbose: 是否需要输出优化日志(类型: bool),默认为False

输出结果为每天优化后的组合权重结果,主要列名有【date, instrument, weights】

目标函数 & 约束条件

目标函数target_func和约束条件constraints会以方法的形式传入权重优化接口opt.optimize,所以需要在传入之前进行方法的定义

目标函数 接口 可调参数
最大化风险调整后收益 MaxReturn 风险厌恶指数lam,默认为0
最小化风险 MinRisk
最小化风格偏离 MinStyleDeviation target_function: style - 风格因子名称以及偏离值(类型:dict),例如 {‘size‘:1} 表达的是将size因子暴露于其均值+1倍标准差之上 \n if_pred - 是否为预测数据(类型:bool),默认为False \n relative - 是否相对于基准(类型:bool),默认为False
最大化预测得分 MaxScore
约束条件or边界条件 接口 可调参数
总预算权重约束 TotalWeightsConstraint upper_limit - 资金组合总权重上限(类型:int / float)
个股权重边界 Bounds lower_limit - 个股权重下限(类型:int / float) \n upper_limit - 个股权重上限(类型:int / float) \n relative - 是否基于基准(类型:bool;默认为False)
个股风格约束 StyleConstraint style - 约束风格名称(类型:str / list)- 可使用风格因子列表见后表 \n lower_limit - 风格约束下限(单位为均值加n倍标准差,类型:int / float) \n upper_limit - 风格约束上限(单位为均值加n倍标准差,类型:int / float) \n relative - 是否基于基准(类型:bool;默认为False) \n priority - 软约束优先级(类型:int)
个股风格除外约束 ExcludeStyleConstraint exclude_style - 除外风格名称(类型:str / list) \n lower_limit - 风格约束下限(单位为均值加n倍标准差,类型:int / float) \n upper_limit - 风格约束上限(单位为均值加n倍标准差,类型:int / float) \n relative - 是否基于基准(类型:bool;默认为False) \n priority - 软约束优先级(类型:int)
个股行业约束 IndustryWeigtedConstraint style - 约束行业名称(类型:str / list)- 可使用行业因子列表见后表 \n lower_limit - 行业约束下限(单位为均值加n倍标准差,类型:int / float) \n upper_limit - 行业约束上限(单位为均值加n倍标准差,类型:int / float) \n relative - 是否基于基准(类型:bool;默认为False) \n priority - 软约束优先级(类型:int)
个股行业除外约束 ExcludeIndustryWeigtedConstraint industry - 除外行业(类型:str / list) \n lower_limit - 除外行业约束下限(单位为均值加n倍标准差,类型:int / float) \n upper_limit - 除外行业约束上限(单位为均值加n倍标准差,类型:int / float) \n relative - 是否基于基准(类型:bool;默认为False) \n priority - 软约束优先级(类型:int)
预期收益约束 PredictReturnConstraint lower_limit - 收益约束下限(类型:int / float) \n priority - 软约束优先级(类型:int)
波动率约束 VolatilityConstraint upper_limit - 约束上限(类型:int / float) \n relative - 是否基于基准(类型:bool;默认为False) \n priority - 软约束优先级(类型:int)
换手率约束 TurnoverConstraint turnrate - 换手率约束(类型:int / float) \n priority - 软约束优先级(类型:int)
追踪误差上限约束 TrackingError upper_limit - 追踪误差上限(类型:int / float) \n priority - 软约束优先级(类型:int)
基准成份股权重约束 BenchmarkWeightedConstraint lower_limit - 最低基准成份占比(类型:int / float) \n priority - 软约束优先级(类型:int)
行业内个股权重约束 IndustryComponentConstraint industry - 行业名称(类型:int / list) \n limit - 最低行业内个股占比(类型:int / float) \n priority - 软约束优先级(类型:int)

可用风格因子&行业因子

  • 风格因子
    • leverage
    • beta
    • momentum
    • growth
    • nonlinear_size
    • liquidity
    • size
    • residual_volatility
    • value
  • 行业因子
    • industry_prefix_* → 其中星号为申万一级行业code,具体code可见:
DataSource("basic_info_IndustrySw").read()

使用方法示例

目标函数&约束条件定义方法

目标函数只能有一个,约束条件可以通过列表的方式进行多个方法的存入

## 定义多个约束条件(list / str)
constraints = [
               opt.StyleConstraint('beta', lower_limit=0.1, upper_limit=1, relative=True, priority=1), ## 风格约束
               opt.ExcludeStyleConstraint('beta', lower_limit=-0.1, upper_limit=0.1, relative=True, priority=0), ## 风格除外约束
               opt.TotalWeightsConstraint(upper_limit=1), ## 总权重约束
               opt.Bounds(lower_limit=0, upper_limit=0.04, relative=True) ## 个股边界约束
              ]
## 定义目标函数
objective= opt.MaxReturn(lam=0.1)
## 权重优化
weights = opt.optimize(objective, current_date, constraints, stock_count=50, hard=False)

软硬约束定义:

其中,priority为当optimize中的hard=False时,即定义优化约束为软约束的时候,优化失败时会优先去掉priority值越小的约束。比如约束A的参数priority=1,约束B的参数priority=0,则在hard=False的时候,首次优化失败则会删除约束B进行第二次约束,若再次优化失败则删掉约束A,依次类推。约束条件中的TotalWeightsConstraint和Bounds是不带priority参数的,也就是说其不参与软硬约束的判断。

\

基于成长因子的中证500指数增强策略示例

# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
def m2_run_bigquant_run(input_1, input_2, input_3):
    start_date = '2020-01-01'
    end_date = '2020-10-16'
    index_cons = DataSource('index_element_weight').read(start_date=start_date, end_date=end_date)
    pred_data = index_cons[index_cons.instrument_index=='000905.HIX'][['instrument','weight','date']].reset_index(drop=True)
    pred_df = DataSource.write_df(pred_data)
    ## instruments
    ins = {}
    stock_list = pred_data.instrument.unique().tolist()
    ins['instruments'] = [x for x in stock_list if str(x) != 'nan']
    ins['start_date'] = start_date
    ins['end_date'] = end_date
    ins = DataSource.write_pickle(ins)
    return Outputs(data_1=ins, data_2=pred_df, data_3=None)
 
# 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。
def m2_post_run_bigquant_run(outputs):
    return outputs
 
# 回测引擎:初始化函数,只执行一次
def m1_initialize_bigquant_run(context):
    # 加载预测数据
    context.stock_pools = context.options['data'].read()
    context.show_debug_info = False
    # 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
    context.set_commission(PerOrder(buy_cost=0.0001, sell_cost=0.0001, min_cost=5))
    context.options['hold_days'] = 22
    context.stock_count = 100
    context.trade_index = 0
    context.opt = T.PORTFOLIO_OPTIMIZERS(context.stock_pools, context.start_date, context.end_date, model_type='daily', benchmark='000905.HIX')
 
# 回测引擎:每日数据处理函数,每天执行一次
def m1_handle_data_bigquant_run(context, data):
    context.trade_index += 1  # 交易日历递增1
    today = data.current_dt.strftime("%Y-%m-%d")
    context.stock_pool = context.stock_pools[context.stock_pools.date == today]
 
    if context.trade_index == 1:  # 第一天建仓
        try:
            context.opt.get_today_factor_data(context.stock_pool, today) ##当日数据初始化
            tf = context.opt.MinStyleDeviation({"growth":1}, if_pred=False, relative=False)
            objective = tf[0]
            cons = tf[1:]
#             objective = context.opt.MaxReturn()
            constraints = [context.opt.TotalWeightsConstraint(upper_limit=1), context.opt.Bounds(lower_limit=0, upper_limit=0.03),
                           context.opt.ExcludeStyleConstraint("growth", lower_limit=-0.1, upper_limit=0.1, relative=False, priority=0),
#                            context.opt.StyleConstraint("size", lower_limit=-0.1, upper_limit=0.1, relative=False, priority=0)
                          ]
            constraints.append(cons)
            weights_data = context.opt.optimize(objective, today, constraints, stock_count=context.stock_count, verbose=False, response=True, hard=False)
 
            def buy_1(df):
                target = df["instrument"]
                weight = df["weight"]
                sid = context.symbol(target)
                if data.can_trade(sid):
                    context.order_target_percent(sid, weight)
            weights_data.apply(buy_1, axis=1)
             
        except Exception as e:
            print(today, "当前日期建仓失败! except:", e)
            context.trade_index -= 1  # 交易日历索引保持不变,以便当日优化失败后次日接着优化
        print('----------------------date {} over----------------------'.format(today))
 
    if context.trade_index % context.options["hold_days"] == 0 and context.trade_index != 1:  # 每隔调仓日进行调仓
        positions_weight = {e.symbol: p.amount * p.last_sale_price / context.portfolio.portfolio_value for e, p in context.portfolio.positions.items()} # 持仓权重
        equities = [e.symbol for e, p in context.portfolio.positions.items()]  # 持仓股票列表
        w0 = pd.Series(positions_weight, index=equities)
        w0 = pd.DataFrame({'pre_weight': w0.values, 'instrument': w0.index})
        context.stock_pool = pd.merge(context.stock_pool, w0, on=['instrument'], how='left').fillna(0)
        context.opt.get_today_factor_data(context.stock_pool, today)
 
        try:
            tf = context.opt.MinStyleDeviation({"growth":1}, if_pred=False, relative=False)
            objective = tf[0]
            cons = tf[1:]
#             objective = context.opt.MaxReturn()
            constraints = [context.opt.TotalWeightsConstraint(upper_limit=1), context.opt.Bounds(lower_limit=0, upper_limit=0.03),
                           context.opt.ExcludeStyleConstraint("growth", lower_limit=-0.1, upper_limit=0.1, relative=False, priority=0),
#                            context.opt.StyleConstraint("size", lower_limit=-0.1, upper_limit=0.1, relative=False, priority=0)
                          ]
            constraints.append(cons)
            weights_data = context.opt.optimize(objective, today, constraints, stock_count=context.stock_count, verbose=False, response=True, hard=False)
 
            # 卖出逻辑
            need_hold_stocks = set(weights_data.instrument)
            for sx in equities:
                if sx not in need_hold_stocks:  # 无法交易的持仓、优化股票之外的持仓 直接卖出
                    order_target_percent(context.symbol(sx), 0)
                    equities.remove(sx)
                    positions_weight = {e:p for e, p in positions_weight.items() if e != sx}
 
            # 买入逻辑
            def buy_2(df):
                target = df["instrument"]
                weight = df["weight"]
                sid = context.symbol(target)
                if data.can_trade(sid):
                    context.order_target_percent(sid, weight)
            weights_data.apply(buy_2, axis=1)
                 
        except Exception as e:
            print(today, "当前日期调仓失败! except:", e)
        print('----------------------date {} over----------------------'.format(today))
# 回测引擎:准备数据,只执行一次
def m1_prepare_bigquant_run(context):
    pass
 
# 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。
def m1_before_trading_start_bigquant_run(context, data):
    pass
 
 
m2 = M.cached.v3(
    run=m2_run_bigquant_run,
    post_run=m2_post_run_bigquant_run,
    input_ports='',
    params='{}',
    output_ports=''
)
 
m1 = M.trade.v4(
    instruments=m2.data_1,
    options_data=m2.data_2,
    start_date='',
    end_date='',
    initialize=m1_initialize_bigquant_run,
    handle_data=m1_handle_data_bigquant_run,
    prepare=m1_prepare_bigquant_run,
    before_trading_start=m1_before_trading_start_bigquant_run,
    volume_limit=0.025,
    order_price_field_buy='open',
    order_price_field_sell='open',
    capital_base=100000000,
    auto_cancel_non_tradable_orders=True,
    data_frequency='daily',
    price_type='真实价格',
    product_type='股票',
    plot_charts=True,
    backtest_only=False,
    benchmark='000905.HIX'
)
 
m3 = M.strategy_ret_risk_analysis.v2(
    input_1=m1.raw_perf,
    analysis_flag='relative',
    benchmark_index='000905.HIX',
    terms='daily'
)

基于动量因子的中证500指数增强策略示例

# Python 代码入口函数,input_1/2/3 对应三个输入端,data_1/2/3 对应三个输出端
def m1_run_bigquant_run(input_1, input_2, input_3):
    start_date = '2020-01-01'
    end_date = '2020-10-16'
    index_cons = DataSource('index_element_weight').read(start_date=start_date, end_date=end_date)
    pred_data = index_cons[index_cons.instrument_index=='000905.HIX'][['instrument','weight','date']].reset_index(drop=True)
    pred_df = DataSource.write_df(pred_data)
    ## instruments
    ins = {}
    stock_list = pred_data.instrument.unique().tolist()
    ins['instruments'] = [x for x in stock_list if str(x) != 'nan']
    ins['start_date'] = start_date
    ins['end_date'] = end_date
    ins = DataSource.write_pickle(ins)
    return Outputs(data_1=ins, data_2=pred_df, data_3=None)
 
# 后处理函数,可选。输入是主函数的输出,可以在这里对数据做处理,或者返回更友好的outputs数据格式。此函数输出不会被缓存。
def m1_post_run_bigquant_run(outputs):
    return outputs
 
# 回测引擎:初始化函数,只执行一次
def m2_initialize_bigquant_run(context):
    # 加载预测数据
    context.stock_pools = context.options['data'].read()
    context.show_debug_info = False
    # 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
    context.set_commission(PerOrder(buy_cost=0.0001, sell_cost=0.0001, min_cost=5))
    context.options['hold_days'] = 22
    context.stock_count = 100
    context.trade_index = 0
    context.opt = T.PORTFOLIO_OPTIMIZERS(context.stock_pools, context.start_date, context.end_date, model_type='daily', benchmark='000905.HIX')
 
# 回测引擎:每日数据处理函数,每天执行一次
def m2_handle_data_bigquant_run(context, data):
    context.trade_index += 1  # 交易日历递增1
    today = data.current_dt.strftime("%Y-%m-%d")
    context.stock_pool = context.stock_pools[context.stock_pools.date == today]
 
    if context.trade_index == 1:  # 第一天建仓
        try:
            context.opt.get_today_factor_data(context.stock_pool, today) ##当日数据初始化
            tf = context.opt.MinStyleDeviation({"momentum":1})
            objective = tf[0]
            cons = tf[1:]
#             objective = context.opt.MaxReturn()
            constraints = [context.opt.TotalWeightsConstraint(upper_limit=1), context.opt.Bounds(lower_limit=0, upper_limit=0.03),
                           context.opt.ExcludeStyleConstraint("momentum", lower_limit=-0.1, upper_limit=0.1, priority=0),
                          ]
            constraints.append(cons)
            weights_data = context.opt.optimize(objective, today, constraints, stock_count=context.stock_count, verbose=False, response=True, hard=False)
 
            def buy_1(df):
                target = df["instrument"]
                weight = df["weight"]
                sid = context.symbol(target)
                if data.can_trade(sid):
                    context.order_target_percent(sid, weight)
            weights_data.apply(buy_1, axis=1)
             
        except Exception as e:
            print(today, "当前日期建仓失败! except:", e)
            context.trade_index -= 1  # 交易日历索引保持不变,以便当日优化失败后次日接着优化
        print('----------------------date {} over----------------------'.format(today))
 
    if context.trade_index % context.options["hold_days"] == 0 and context.trade_index != 1:  # 每隔调仓日进行调仓
        positions_weight = {e.symbol: p.amount * p.last_sale_price / context.portfolio.portfolio_value for e, p in context.portfolio.positions.items()} # 持仓权重
        equities = [e.symbol for e, p in context.portfolio.positions.items()]  # 持仓股票列表
        w0 = pd.Series(positions_weight, index=equities)
        w0 = pd.DataFrame({'pre_weight': w0.values, 'instrument': w0.index})
        context.stock_pool = pd.merge(context.stock_pool, w0, on=['instrument'], how='left').fillna(0)
        context.opt.get_today_factor_data(context.stock_pool, today)
 
        try:
            tf = context.opt.MinStyleDeviation({"momentum":1})
            objective = tf[0]
            cons = tf[1:]
            constraints = [context.opt.TotalWeightsConstraint(upper_limit=1), context.opt.Bounds(lower_limit=0, upper_limit=0.03),
                           context.opt.ExcludeStyleConstraint("momentum", lower_limit=-0.1, upper_limit=0.1, priority=0),
                          ]
            constraints.append(cons)
            weights_data = context.opt.optimize(objective, today, constraints, stock_count=context.stock_count, verbose=False, response=True, hard=False)
 
            # 卖出逻辑
            need_hold_stocks = set(weights_data.instrument)
            for sx in equities:
                if sx not in need_hold_stocks:  # 无法交易的持仓、优化股票之外的持仓 直接卖出
                    order_target_percent(context.symbol(sx), 0)
                    equities.remove(sx)
                    positions_weight = {e:p for e, p in positions_weight.items() if e != sx}
 
            # 买入逻辑
            def buy_2(df):
                target = df["instrument"]
                weight = df["weight"]
                sid = context.symbol(target)
                if data.can_trade(sid):
                    context.order_target_percent(sid, weight)
            weights_data.apply(buy_2, axis=1)
                 
        except Exception as e:
            print(today, "当前日期调仓失败! except:", e)
        print('----------------------date {} over----------------------'.format(today))
# 回测引擎:准备数据,只执行一次
def m2_prepare_bigquant_run(context):
    pass
 
# 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。
def m2_before_trading_start_bigquant_run(context, data):
    pass
 
 
m1 = M.cached.v3(
    run=m1_run_bigquant_run,
    post_run=m1_post_run_bigquant_run,
    input_ports='',
    params='{}',
    output_ports='',
    m_cached=False
)
 
m2 = M.trade.v4(
    instruments=m1.data_1,
    options_data=m1.data_2,
    start_date='',
    end_date='',
    initialize=m2_initialize_bigquant_run,
    handle_data=m2_handle_data_bigquant_run,
    prepare=m2_prepare_bigquant_run,
    before_trading_start=m2_before_trading_start_bigquant_run,
    volume_limit=0.025,
    order_price_field_buy='open',
    order_price_field_sell='open',
    capital_base=100000000,
    auto_cancel_non_tradable_orders=True,
    data_frequency='daily',
    price_type='真实价格',
    product_type='股票',
    plot_charts=True,
    backtest_only=False,
    benchmark='000905.HIX'
)
 
m4 = M.strategy_ret_risk_analysis.v2(
    input_1=m2.raw_perf,
    analysis_flag='relative',
    benchmark_index='000905.HIX',
    terms='daily',
    m_cached=False
)

基于AI模型预测结果的指数增强策略

# 回测引擎:初始化函数,只执行一次
def m19_initialize_bigquant_run(context):
    context.stock_pools = context.options['data'].read()
    context.show_debug_info = False
    context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
    context.options['hold_days'] = 5
    context.stock_count = 5
    context.trade_index = 0
    context.opt = T.PORTFOLIO_OPTIMIZERS(context.stock_pools, context.start_date, context.end_date, model_type='daily', benchmark='000905.HIX')

# 回测引擎:每日数据处理函数,每天执行一次
def m19_handle_data_bigquant_run(context, data):
    context.trade_index += 1  # 交易日历递增1
    today = data.current_dt.strftime("%Y-%m-%d")
    print('current_date is:', today)
    context.stock_pool = context.stock_pools[context.stock_pools.date == today]

    if context.trade_index == 1:  # 第一天建仓
        try:
            context.opt.get_today_factor_data(context.stock_pool, today) ##当日数据初始化
            objective = context.opt.MaxScore()
            constraints = [
                           context.opt.TotalWeightsConstraint(upper_limit=1), context.opt.Bounds(lower_limit=0, upper_limit=0.2, relative=True),
                           context.opt.ExcludeStyleConstraint("size",lower_limit=-0.1,upper_limit=0.1,relative=True, priority=1)
                          ]
            weights_data = context.opt.optimize(objective, today, constraints, stock_count=context.stock_count, verbose=False, response=True, hard=False)

            def buy_1(df):
                target = df["instrument"]
                weight = df["weight"]
                sid = context.symbol(target)
                if data.can_trade(sid):
                    context.order_target_percent(sid, weight)
                else:
                    print(f"{today} {target} 无法交易")
            weights_data.apply(buy_1, axis=1)
            
        except Exception as e:
            print(today, "当前日期建仓失败! except:", e)
            context.trade_index -= 1  # 交易日历索引保持不变,以便当日优化失败后次日接着优化

    if context.trade_index % context.options["hold_days"] == 0 and context.trade_index != 1:  # 每隔调仓日进行调仓
        positions_weight = {e.symbol: p.amount * p.last_sale_price / context.portfolio.portfolio_value for e, p in context.portfolio.positions.items()} # 持仓权重
        equities = [e.symbol for e, p in context.portfolio.positions.items()]  # 持仓股票列表
        w0 = pd.Series(positions_weight, index=equities)
        w0 = pd.DataFrame({'pre_weight': w0.values, 'instrument': w0.index})
        context.stock_pool = pd.merge(context.stock_pool, w0, on=['instrument'], how='left').fillna(0)
        context.opt.get_today_factor_data(context.stock_pool, today)

        try:
            objective = context.opt.MaxScore()
            constraints = [
                           context.opt.TotalWeightsConstraint(upper_limit=1), context.opt.Bounds(lower_limit=0, upper_limit=0.2, relative=True),
                           context.opt.ExcludeStyleConstraint("size",lower_limit=-0.1,upper_limit=0.1,relative=True, priority=1),
                           context.opt.TurnoverConstraint(turnrate=0.2, priority=0)
                          ]
            weights_data = context.opt.optimize(objective, today, constraints, stock_count=context.stock_count, verbose=False, response=True, hard=False)

            # 卖出逻辑
            need_hold_stocks = set(weights_data.instrument)
            for sx in equities:
                if sx not in need_hold_stocks:  # 无法交易的持仓、优化股票之外的持仓 直接卖出
                    order_target_percent(context.symbol(sx), 0)
                    equities.remove(sx)
                    positions_weight = {e:p for e, p in positions_weight.items() if e != sx}

            # 买入逻辑
            def buy_2(df):
                target = df["instrument"]
                weight = df["weight"]
                sid = context.symbol(target)
                if data.can_trade(sid):
                    context.order_target_percent(sid, weight)
                else:
                    print(f"{today} {target} 无法交易")
            weights_data.apply(buy_2, axis=1)
                
        except Exception as e:
            print(today, "当前日期调仓失败! except:", e)
            
        print('----------------------------------------------------------------------------------------date {} over----------------------------------------------------------------------------------------'.format(today))

# 回测引擎:准备数据,只执行一次
def m19_prepare_bigquant_run(context):
    pass


m1 = M.instruments.v2(
    start_date='2010-01-01',
    end_date='2016-01-01',
    market='CN_STOCK_A',
    instrument_list='',
    max_count=0
)

m2 = M.advanced_auto_labeler.v2(
    instruments=m1.data,
    label_expr="""# #号开始的表示注释
# 0. 每行一个,顺序执行,从第二个开始,可以使用label字段
# 1. 可用数据字段见 https://bigquant.com/docs/develop/datasource/deprecated/history_data.html
#   添加benchmark_前缀,可使用对应的benchmark数据
# 2. 可用操作符和函数见 `表达式引擎 <https://bigquant.com/docs/develop/bigexpr/usage.html>`_

# 计算收益:5日收盘价(作为卖出价格)除以明日开盘价(作为买入价格)
shift(close, -5) / shift(open, -1)

# 极值处理:用1%和99%分位的值做clip
clip(label, all_quantile(label, 0.01), all_quantile(label, 0.99))

# 将分数映射到分类,这里使用20个分类
all_wbins(label, 20)

# 过滤掉一字涨停的情况 (设置label为NaN,在后续处理和训练中会忽略NaN的label)
where(shift(high, -1) == shift(low, -1), NaN, label)
""",
    start_date='',
    end_date='',
    benchmark='000300.SHA',
    drop_na_label=True,
    cast_label_int=True
)

m3 = M.input_features.v1(
    features="""# #号开始的表示注释
# 多个特征,每行一个,可以包含基础特征和衍生特征
return_5
return_10
return_20
avg_amount_0/avg_amount_5
avg_amount_5/avg_amount_20
rank_avg_amount_0/rank_avg_amount_5
rank_avg_amount_5/rank_avg_amount_10
rank_return_0
rank_return_5
rank_return_10
rank_return_0/rank_return_5
rank_return_5/rank_return_10
pe_ttm_0
"""
)

m15 = M.general_feature_extractor.v7(
    instruments=m1.data,
    features=m3.data,
    start_date='',
    end_date='',
    before_start_days=90
)

m16 = M.derived_feature_extractor.v3(
    input_data=m15.data,
    features=m3.data,
    date_col='date',
    instrument_col='instrument',
    drop_na=False,
    remove_extra_columns=False
)

m7 = M.join.v3(
    data1=m2.data,
    data2=m16.data,
    on='date,instrument',
    how='inner',
    sort=False
)

m13 = M.dropnan.v1(
    input_data=m7.data
)

m6 = M.stock_ranker_train.v5(
    training_ds=m13.data,
    features=m3.data,
    learning_algorithm='排序',
    number_of_leaves=30,
    minimum_docs_per_leaf=1000,
    number_of_trees=20,
    learning_rate=0.1,
    max_bins=1023,
    feature_fraction=1,
    m_lazy_run=False
)

m9 = M.instruments.v2(
    start_date=T.live_run_param('trading_date', '2016-01-01'),
    end_date=T.live_run_param('trading_date', '2016-12-31'),
    market='CN_STOCK_A',
    instrument_list='',
    max_count=0
)

m17 = M.general_feature_extractor.v7(
    instruments=m9.data,
    features=m3.data,
    start_date='',
    end_date='',
    before_start_days=90
)

m18 = M.derived_feature_extractor.v3(
    input_data=m17.data,
    features=m3.data,
    date_col='date',
    instrument_col='instrument',
    drop_na=False,
    remove_extra_columns=False
)

m14 = M.dropnan.v1(
    input_data=m18.data
)

m8 = M.stock_ranker_predict.v5(
    model=m6.model,
    data=m14.data,
    m_lazy_run=False
)

m19 = M.trade.v4(
    instruments=m9.data,
    options_data=m8.predictions,
    start_date='',
    end_date='',
    initialize=m19_initialize_bigquant_run,
    handle_data=m19_handle_data_bigquant_run,
    prepare=m19_prepare_bigquant_run,
    volume_limit=0.025,
    order_price_field_buy='open',
    order_price_field_sell='close',
    capital_base=1000000,
    auto_cancel_non_tradable_orders=True,
    data_frequency='daily',
    price_type='真实价格',
    product_type='股票',
    plot_charts=True,
    backtest_only=False,
    benchmark='000905.HIX'
)

m4 = M.barra_risk_factor_analysis1.v8(
    input_1=m19.raw_perf,
    analysis_flag='relative',
    benchmark_index='000905.HIX',
    terms='daily'
)

\

{link}