克隆策略

数据准备函数

In [3]:
def prepare(context):
    # 确定起始时间
    start_date = context.start_date
    # 确定结束时间
    end_date = context.end_date
    instruments = context.instruments
    fields = ['fs_gross_profit_margin_0', 'fs_roe_0', 'fs_free_cash_flow_0', 'fs_net_profit_0']
    raw_data = D.features(instruments, start_date, end_date, fields)
    raw_data['cash_flow/profit'] = raw_data['fs_free_cash_flow_0'] / raw_data['fs_net_profit_0']
    context.daily_buy_stock = pd.DataFrame(raw_data.groupby('date').apply(seek_stock))
    
def seek_stock(df):
    ahead_f1 = set(df.sort_values('fs_roe_0',ascending=False)['instrument'][:500])
    ahead_f2 = set(df.sort_values('fs_gross_profit_margin_0',ascending=False)['instrument'][:500])
    ahead_f3 = set(df.sort_values('cash_flow/profit',ascending=False)['instrument'][:500])
    return list(ahead_f1 & ahead_f2 & ahead_f3)

策略逻辑主体函数

In [4]:
# 回测参数设置,initialize函数只运行一次
def initialize(context):
    # 手续费设置
    context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5)) 
    # 调仓规则(每月的第一天调仓)
    context.schedule_function(rebalance, date_rule=date_rules.month_start(days_offset=0)) 

# handle_data函数会每天运行一次
def handle_data(context,data):
    pass

# 换仓函数
def rebalance(context, data):
    # 当前的日期
    date = data.current_dt.strftime('%Y-%m-%d')
    # 根据日期获取调仓需要买入的股票的列表
    stock_to_buy = list(context.daily_buy_stock.ix[date][0])
    # 通过positions对象,使用列表生成式的方法获取目前持仓的股票列表
    stock_hold_now = [equity.symbol for equity in context.portfolio.positions]
    # 继续持有的股票:调仓时,如果买入的股票已经存在于目前的持仓里,那么应继续持有
    no_need_to_sell = [i for i in stock_hold_now if i in stock_to_buy]
    # 需要卖出的股票
    stock_to_sell = [i for i in stock_hold_now if i not in no_need_to_sell]
  
    # 卖出
    for stock in stock_to_sell:
        # 如果该股票停牌,则没法成交。因此需要用can_trade方法检查下该股票的状态
        # 如果返回真值,则可以正常下单,否则会出错
        # 因为stock是字符串格式,我们用symbol方法将其转化成平台可以接受的形式:Equity格式

        if data.can_trade(context.symbol(stock)):
            # order_target_percent是平台的一个下单接口,表明下单使得该股票的权重为0,
            #   即卖出全部股票,可参考回测文档
            context.order_target_percent(context.symbol(stock), 0)
    
    # 如果当天没有买入的股票,就返回
    if len(stock_to_buy) == 0:
        return

    # 等权重买入 
    weight =  1 / len(stock_to_buy)
    
    # 买入
    for stock in stock_to_buy:
        if data.can_trade(context.symbol(stock)):
            # 下单使得某只股票的持仓权重达到weight,因为
            # weight大于0,因此是等权重买入
            context.order_target_percent(context.symbol(stock), weight)

策略回测接口

In [6]:
# 策略运行调用函数
m=M.trade.v2( 
    instruments=D.instruments(market='CN_STOCK_A'),
    start_date='2013-01-01', 
    end_date='2017-05-01',
    prepare=prepare, # 在实盘或模拟交易,每天会更新数据,因此必须传入数据准备函数
    # 必须传入initialize,只在第一天运行
    initialize=initialize,
    # 必须传入handle_data,每个交易日都会运行
    handle_data=handle_data,
    # 买入以开盘价成交
    order_price_field_buy='open',
    # 卖出也以开盘价成交
    order_price_field_sell='open',
    # 策略本金
    capital_base=1000000,
    # 比较基准:沪深300
    benchmark='000300.INDX',
    m_deps='quantamental'
)
[2018-03-22 20:09:55.595595] INFO: bigquant: backtest.v7 开始运行..
[2018-03-22 20:10:44.921995] INFO: algo: set price type:backward_adjusted
[2018-03-22 20:13:27.976025] INFO: Performance: Simulated 1048 trading days out of 1048.
[2018-03-22 20:13:27.982553] INFO: Performance: first open: 2013-01-04 01:30:00+00:00
[2018-03-22 20:13:27.987412] INFO: Performance: last close: 2017-04-28 07:00:00+00:00
  • 收益率169.35%
  • 年化收益率26.9%
  • 基准收益率36.34%
  • 阿尔法0.2
  • 贝塔0.88
  • 夏普比率0.77
  • 胜率0.697
  • 盈亏比1.078
  • 收益波动率29.64%
  • 信息比率1.0
  • 最大回撤51.19%
[2018-03-22 20:13:43.717638] INFO: bigquant: backtest.v7 运行完成[228.122031s].