############################################################################
#
#                       策略方案
#
#   一、指数：
#       1、 创业板指（399006） 创业板股票（399006，剔除上市不满130日创业板股票）
#       2、 中小板指（399005） 小市值（流通市值小于100亿，与现有的小市值策略一致）
#       3、 沪深300（399300） 买入510310（回测时按这个ETF来做，但实际上可能要变成买所有的股票，这个成交量太小了）
#       4、 上证50（000016） 买入510050 
    
#   二、市值回撤：
#       1、 当日高点回撤2%，空仓2天
#       2、 3日高点回撤5%，空仓3天
#       3、 5日高点回撤8%，空仓3天 

#   三、仓位：（调仓时判断） 
#       1、3个指数向上，全仓
#       2、2个指数向上，7成仓
#       3、1个指数向上，3成仓
#       4、都向下，空仓
#       备注：指数向上指20日均线斜率>1.01 

#   四、选股:（调仓时判断）
#       1、 选择斜率最高的指数对于的股票标的
#       2、 流通按市值排序（小市值优先） 
#       3、仓位调整不是每天调整，等调整周期到的时候再调整。

############################################################################

import copy
import pandas as pd
import requests
from requests import Request
from six.moves.urllib.parse import urlencode
import datetime
try:
    import shipane_sdk
except:
    pass

'''选择规则组合成一个量化策略'''
def select_strategy(context):
    '''
    策略选择设置说明, 策略由以下步骤规则组合，组合而成:
    1.持仓股票的处理规则
    2.调仓条件判断规则
    3.Query选股规则 (可选)
    4.股票池过滤规则 (可选)
    5.调仓规则
    6.其它规则(如统计)

    不同的规则组合可能存在一定的顺序关系。
    二维指定具体规则配置，由 [0.是否启用，1.描述，2.规则实现类名，3.规则传递参数(dict)]] 组成。
    注：所有规则类都必需继承自Rule类或Rule类的子类
    '''

    # 规则配置list下标描述变量。提高可读性与未来添加更多规则配置。
    g.cs_enabled, g.cs_name, g.cs_memo, g.cs_class_name, g.cs_param = range(5)

    # 默认不开仓的标志为-1
    g.not_open_days = -1

    # 默认全仓
    g.position_scale = 1
    g.index_selected = ''
    g.index_selected_growth = 1
    # 每天最大净值
    g.max_portfolio_everyday = []
    
    period = 3  # 调仓频率
    
    # 配置 1.持仓股票的处理规则 (这里主要配置是否进行个股止损止盈)
    g.position_stock_config = [
        [False, '', '个股止损', Stop_loss_stocks, {
            'period': period  # 调仓频率，日
            }, ],
        [False, '', '个股止盈', Stop_profit_stocks,
            {'period': period,  # 调仓频率，日
             }]
    ]

    # 配置 2.调仓条件判断规则
    g.adjust_condition_config = [
        [True, '', '统计净值', Stat_portfolio, {}],
        [False, '', '指数最高低价比值止损', Stop_loss_by_price, {
            'index': '000001.XSHG',  # 使用的指数,默认 '000001.XSHG'
             'day_count': 160,  # 可选 取day_count天内的最高价，最低价。默认160
             'multiple': 2.2  # 可选 最高价为最低价的multiple倍时，触 发清仓
            }],
        [False, '', '指数三乌鸦止损', Stop_loss_by_3_black_crows, {
            'index': '000001.XSHG',  # 使用的指数,默认 '000001.XSHG'
             'dst_drop_minute_count': 60,  # 可选，在三乌鸦触发情况下，一天之内有多少分钟涨幅<0,则触发止损，默认60分钟
            }],

        [False, '', '28调仓择时', Index28_condition, {  # 该调仓条件可能会产生清仓行为
                'index2': '000016.XSHG',  # 大盘指数
                'index8': '399333.XSHE',  # 小盘指数
                'index_growth_rate': 0.01,  # 判定调仓的二八指数20日增幅
            }],

        [True, '', '调仓时间', Time_condition, {
                'times': [[14, 50]],
            }],
        [True, '', '调仓日计数器', Period_condition, {
                'period': period,  # 调仓频率,日
            }],
        [True, '', '指数选择器', Index_selection, {
                'index_1': '399006.XSHE', # 创业板指
                'index_2': '399005.XSHE', # 中小板指
                'index_3': '399300.XSHE', # 沪深 300
                'index_4': '000016.XSHG', # 上证 50
                'index_growth_rate': 0.01
            }],
        [True, '', '当天净值管理风控', Stop_loss_by_currentday_net_worth, {
            'check_days': 1,
            'back_percent': 2,
            }],
        [False, '', '三天净值管理风控', Stop_loss_by_last3day_net_worth, {
            'check_days': 3,
            'back_percent': 5,
            }],
        [False, '', '五天净值管理风控', Stop_loss_by_last5day_net_worth, {
            'check_days': 5,
            'back_percent': 8,
            }],

    ]

    # 配置 3.Query选股规则
    g.pick_stock_by_query_config = [
        [True, '', '选取小市值', Pick_small_cap, {}],
        [True, '', '斜率最高指数选股器', Pick_stock_by_index, {}],
        [False, '', '评分大于10', Pick_score_up, {}],
        [True, '', '过滤EPS', Filter_eps, {
            'eps_min': 0  # 最小EPS
            }],
        [True, '', '初选股票数量', Filter_limite, {
            'pick_stock_count': 100  # 备选股票数目
            }]
    ]

    # 配置 4.股票池过滤规则
    g.filter_stock_list_config = [
        [False, '_filter_gem_', '过滤创业板', Filter_gem, {}],
        [True, '', '过滤ST', Filter_st, {}],
        [True, '', '过滤停牌', Filter_paused_stock, {}],
        [True, '', '过滤涨停', Filter_limitup, {}],
        [True, '', '过滤跌停', Filter_limitdown, {}],
        [True, '', '过滤次新', Filter_new_stock, {'day_count': 130}],
        [False, '', '三日或五日均线向上', Filter_3or5_mean_up, {
            'day_count1': 3,
            'day_count2': 5}],
        [True, '', '股票评分', Filter_rank, {
            'rank_stock_count': 20  # 评分股数
            }],
        [True, '', '获取最终选股数', Filter_buy_count, {
            'buy_count': 20  # 最终入选股票数
            }],
    ]

    # 配置 5.调仓规则
    g.adjust_position_config = [
        [True, '', '卖出股票', Sell_stocks, {}],
        [True, '', '买入股票', Buy_stocks, {
            'buy_count': 20  # 最终买入股票数
            }]
    ]

    # 判断是否在模拟盘运行
    g.is_sim_trade = context.run_params.type == 'sim_trade'

    # 配置 6.其它规则
    g.other_config = [
        # 注意：Shipane_order 和 Shipane_sync_p 启用一个就行了。
        [False, 'Shipane_order_moni', '实盘易跟order下单', Shipane_order, {}],
        [False, '_shipane_moni_', '实盘易-对比持仓下单', Shipane_sync_p, {}],

        # 通过实盘易自动申购新股
        [False,'_Purchase_new_stocks_','实盘易申购新股',Purchase_new_stocks,{'times': [[9, 40]]}],

        [True, '', '统计', Stat, {}]
    ]


# 创建一个规则执行器，并初始化一些通用事件
def create_rule(class_type, name, params, memo):
    obj = class_type(params)
    obj.name = name
    obj.on_open_position = open_position  # 买股
    obj.on_close_position = close_position  # 卖股
    obj.on_clear_position = clear_position  # 清仓
    obj.on_get_obj_by_class_type = get_obj_by_class_type  # 通过类名得到类的实例
    obj.memo = memo
    return obj

# 根据规则配置创建规则执行器
def create_rules(config):
    # config里 0.是否启用，1.描述，2.规则实现类名，3.规则传递参数(dict)]
    return [create_rule(c[g.cs_class_name],c[g.cs_name],c[g.cs_param],c[g.cs_memo]) for c in config if c[g.cs_enabled]]

def initialize(context):
    log.info("==> initialize @ %s" % (str(context.current_dt)))
    try:
        set_commission(PerTrade(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
        set_benchmark('000300.XSHG')
        set_option('use_real_price', True)
        log.set_level('order', 'error')
    except:
        pass
    # 判定是否是模拟盘状态
    g.is_sim_trade = context.run_params.type == 'sim_trade'

    select_strategy(context)
    '''-----1.持仓股票的处理规则-----'''
    g.position_stock_rules = create_rules(g.position_stock_config)

    '''-----2.调仓条件判断规则-----'''
    g.adjust_condition_rules = create_rules(g.adjust_condition_config)

    '''-----3.Query选股规则-----'''
    g.pick_stock_by_query_rules = create_rules(g.pick_stock_by_query_config)

    '''-----4.股票池过滤规则-----'''
    g.filter_stock_list_rules = create_rules(g.filter_stock_list_config)

    '''-----5.调仓规则器-----'''
    g.adjust_position_rules = create_rules(g.adjust_position_config)

    '''-----6.其它规则-------'''
    g.other_rules = create_rules(g.other_config)

    # 把所有规则合并排重生成一个总的规则收录器。以方便各处共同调用的
    g.all_rules = list(set(g.position_stock_rules
                           + g.adjust_condition_rules
                           + g.pick_stock_by_query_rules
                           + g.filter_stock_list_rules
                           + g.adjust_position_rules
                           + g.other_rules
                           ))

    for rule in g.all_rules:
        rule.initialize(context)

    # 打印规则参数
    log_param()

# 按分钟回测


def handle_data(context, data):
    if g.not_open_days >= 0:
        return
    g.clear_by_index = False
    # 执行其它辅助规则
    for rule in g.other_rules:
        rule.handle_data(context, data)

    # 持仓股票动作的执行,目前为个股止损止盈
    for rule in g.position_stock_rules:
        rule.handle_data(context, data)

    # ----------这部分当前本策略其实并没有啥用，扩展用--------------
    # 这里执行选股器调仓器的handle_data主要是为了扩展某些选股方式可能需要提前处理数据。
    # 举例：动态获取黑名单，可以调仓前一段时间先执行。28小市值规则这里都是空动作。
    for rule in g.pick_stock_by_query_rules:
        rule.handle_data(context, data)

    for rule in g.filter_stock_list_rules:
        rule.handle_data(context, data)

    # 调仓器的分钟处理
    for rule in g.adjust_position_rules:
        rule.handle_data(context, data)
    # -----------------------------------------------------------

    # 判断是否满足调仓条件，所有规则以and 逻辑执行
    for rule in g.adjust_condition_rules:
        rule.handle_data(context, data)
        if not rule.can_adjust:
            #log.info(rule)
            return
    # ---------------------调仓--------------------------
    log.info("handle_data: ==> 满足条件进行调仓")
    # 调仓前预处理
    for rule in g.all_rules:
        rule.before_adjust_start(context, data)

    # Query 选股
    q = None
    for rule in g.pick_stock_by_query_rules:
        q = rule.filter(context, data, q)

    # 过滤股票列表
    stock_list = []
    if g.index_selected == '399300.XSHE':
        stock_list.append('510310.XSHG')
    elif g.index_selected == '000016.XSHG':
        stock_list.append('510050.XSHG')
    else:
        stock_list = list(get_fundamentals(q)['code']) if q is not None else []
    log.info("before filter: ", stock_list)
    for rule in g.filter_stock_list_rules:
        stock_list = rule.filter(context, data, stock_list)
        log.info("after filter: ", rule)
        #log.info("stock list: ", stock_list)

    log.info("handle_data: 选股后可买股票: %s" % (stock_list))

    # 执行调仓
    for rule in g.adjust_position_rules:
        rule.adjust(context, data, stock_list)

    # 调仓后处理
    for rule in g.all_rules:
        rule.after_adjust_end(context, data)
    # ----------------------------------------------------

# 开盘


def before_trading_start(context):
    log.info(
        "==========================================================================")
    for rule in g.all_rules:
        rule.before_trading_start(context)

# 收盘


def after_trading_end(context):
    for rule in g.all_rules:
        rule.after_trading_end(context)

    # 得到当前未完成订单
    orders = get_open_orders()
    for _order in orders.values():
        log.info("canceled uncompleted order: %s" % (_order.order_id))

# 进程启动(一天一次)


def process_initialize(context):
    # 这里Try一下，防止更新代码时出错。
    try:
        for rule in g.all_rules:
            rule.process_initialize(context)
    except:
        pass

# 这里示例进行模拟更改回测时，如何调整策略,基本通用代码。


def after_code_changed(context):
    try:
        g.all_rules
    except:
        print('原先非面向对象策略代码，重新初始化')
        initialize(context)
        return

    print('更新代码：')
    # 调整策略通用实例代码
    # 获取新策略
    select_strategy(context)
    # 按新策略顺序重整规则列表，如果对象之前存在，则移到新列表，不存在则新建。
    # 不管之前旧的规则列表是什么顺序，一率按新列表重新整理。

    def check_chang(rules, config):
        nl = []
        for c in config:
            # 按顺序循环处理新规则
            if not c[g.cs_enabled]:  # 不使用则跳过
                continue
            # 查找旧规则是否存在
            find_old = None
            for old_r in rules:
                if old_r.__class__ == c[
                        g.cs_class_name] and old_r.name == c[
                        g.cs_name]:
                    find_old = old_r
                    break
            if find_old is not None:
                # 旧规则存在则添加到新列表中,并调用规则的更新函数，更新参数。
                nl.append(find_old)
                find_old.update_params(context, c[g.cs_param])
            else:
                # 旧规则不存在，则创建并添加
                new_r = create_rule(
                    c[g.cs_class_name],
                    c[g.cs_name],
                    c[g.cs_param],
                    c[g.cs_memo])
                nl.append(new_r)
                # 调用初始化时该执行的函数
                rule.initialize(context)
        return nl

    # 重整所有规则
    g.position_stock_rules = check_chang(
        g.position_stock_rules, g.position_stock_config)
    g.adjust_condition_rules = check_chang(
        g.adjust_condition_rules, g.adjust_condition_config)
    g.pick_stock_by_query_rules = check_chang(
        g.pick_stock_by_query_rules,
     g.pick_stock_by_query_config)
    g.filter_stock_list_rules = check_chang(
        g.filter_stock_list_rules,
     g.filter_stock_list_config)
    g.adjust_position_rules = check_chang(
        g.adjust_position_rules, g.adjust_position_config)
    g.other_rules = check_chang(g.other_rules, g.other_config)

    # 重新生成所有规则的list
    g.all_rules = list(set(
            g.position_stock_rules
            + g.adjust_condition_rules
            + g.pick_stock_by_query_rules
            + g.filter_stock_list_rules
            + g.adjust_position_rules
            + g.other_rules
        ))
    log_param()

# 显示策略组成


def log_param():
    def get_rules_str(rules):
        return '\n'.join(['   %d.%s ' % (i + 1, str(r))
                          for i, r in enumerate(rules)]) + '\n'
    s = '\n---------------------策略一览：规则组合与参数----------------------------\n'
    s += '一、持仓股票的处理规则:\n' + get_rules_str(g.position_stock_rules)
    s += '二、调仓条件判断规则:\n' + get_rules_str(g.adjust_condition_rules)
    s += '三、Query选股规则:\n' + get_rules_str(g.pick_stock_by_query_rules)
    s += '四、股票池过滤规则:\n' + get_rules_str(g.filter_stock_list_rules)
    s += '五、调仓规则:\n' + get_rules_str(g.adjust_position_rules)
    s += '六、其它规则:\n' + get_rules_str(g.other_rules)
    s += '--------------------------------------------------------------------------'
    print(s)

''' ==============================持仓操作函数，共用================================'''
# 开仓，买入指定价值的证券
# 报单成功并成交（包括全部成交或部分成交，此时成交量大于0），返回True
# 报单失败或者报单成功但被取消（此时成交量等于0），返回False
# 报单成功，触发所有规则的when_buy_stock函数


def open_position(sender, security, value):
    order = order_target_value_(sender, security, value)
    if order is not None and order.filled > 0:
        for rule in g.all_rules:
            rule.when_buy_stock(security, order)
        return True
    return False

# 平仓，卖出指定持仓
# 平仓成功并全部成交，返回True
# 报单失败或者报单成功但被取消（此时成交量等于0），或者报单非全部成交，返回False
# 报单成功，触发所有规则的when_sell_stock函数


def close_position(sender, position, is_normal=True):
    security = position.security
    order = order_target_value_(sender, security, 0)  # 可能会因停牌失败
    if order is not None:
        if order.filled > 0:
            for rule in g.all_rules:
                rule.when_sell_stock(position, order, is_normal)
            return True
    return False

# 清空卖出所有持仓
# 清仓时，调用所有规则的 when_clear_position


def clear_position(sender, context):
    if context.portfolio.positions:
        sender.log_info("==> 清仓，卖出所有股票")
        for stock in context.portfolio.positions.keys():
            position = context.portfolio.positions[stock]
            close_position(sender, position, False)
    for rule in g.all_rules:
        rule.when_clear_position(context)

# 自定义下单
# 根据Joinquant文档，当前报单函数都是阻塞执行，报单函数（如order_target_value）返回即表示报单完成
# 报单成功返回报单（不代表一定会成交），否则返回None


def order_target_value_(sender, security, value):
    df = get_fundamentals(query(
        valuation, income
    ).filter(
        valuation.code == security
    ))

    if value == 0:
        #sender.log_info("Selling out %s" % (security))
        pass
    else:
        #sender.log_info("Order %s to value %f, Total market_cap: %f " % (security, value, df['market_cap']))
        pass

    # 如果股票停牌，创建报单会失败，order_target_value 返回None
    # 如果股票涨跌停，创建报单会成功，order_target_value 返回Order，但是报单会取消
    # 部成部撤的报单，聚宽状态是已撤，此时成交量>0，可通过成交量判断是否有成交
    return order_target_value(security, value)

# 通过类的类型得到已创建的对象实例


def get_obj_by_class_type(class_type):
    for rule in g.all_rules:
        if rule.__class__ == class_type:
            return rule
''' ==============================规则基类================================'''


class Rule(object):
    # 持仓操作的事件
    on_open_position = None  # 买股调用外部函数
    on_close_position = None  # 卖股调用外部函数
    on_clear_position = None  # 清仓调用外部函数
    on_get_obj_by_class_type = None  # 通过类的类型查找已创建的类的实例
    memo = ''  # 对象简要说明
    name = ''

    def __init__(self, params):
        pass

    def initialize(self, context):
        pass

    def handle_data(self, context, data):
        pass

    def before_trading_start(self, context):
        pass

    def after_trading_end(self, context):
        pass

    def process_initialize(self, context):
        pass

    def after_code_changed(self, context):
        pass
    # 卖出股票时调用的函数
    # is_normail正常规则卖出为True，止损卖出为False

    def when_sell_stock(self, position, order, is_normal):
        pass
    # 买入股票时调用的函数

    def when_buy_stock(self, stock, order):
        pass
    # 清仓时调用的函数

    def when_clear_position(self, context):
        pass
    # 调仓前调用

    def before_adjust_start(self, context, data):
        pass
    # 调仓后调用用

    def after_adjust_end(slef, context, data):
        pass
    # 更改参数

    def update_params(self, context, params):
        pass

    # 持仓操作事件的简单判断处理，方便使用。
    def open_position(self, security, value):
        if self.on_open_position is not None:
            return self.on_open_position(self, security, value)

    def close_position(self, position, is_normal=True):
        if self.on_close_position is not None:
            return self.on_close_position(self, position, is_normal=True)

    def clear_position(self, context):
        if self.on_clear_position is not None:
            self.on_clear_position(self, context)
    # 通过类的类型获取已创建的类的实例对象
    # 示例 obj = get_obj_by_class_type(Index28_condition)

    def get_obj_by_class_type(self, class_type):
        if self.on_get_obj_by_class_type is not None:
            return self.on_get_obj_by_class_type(class_type)
        else:
            return None
    # 为日志显示带上是哪个规则器输出的

    def log_info(self, msg):
        log.info('%s: %s' % (self.memo, msg))

    def log_warn(self, msg):
        log.warn('%s: %s' % (self.memo, msg))

    def log_debug(self, msg):
        log.debug('%s: %s' % (self.memo, msg))

    def log_error(self, msg):
        log.error('%s: %s' % (self.memo, msg))

'''==============================调仓条件判断器基类=============================='''


class Adjust_condition(Rule):
    # 返回能否进行调仓

    @property
    def can_adjust(self):
        return True

'''==============================选股 query过滤器基类=============================='''


class Filter_query(Rule):

    def filter(self, context, data, q):
        return None
'''==============================选股 stock_list过滤器基类=============================='''


class Filter_stock_list(Rule):

    def filter(self, context, data, stock_list):
        return None
'''==============================调仓的操作基类=============================='''


class Adjust_position(Rule):

    def adjust(self, context, data, buy_stocks):
        pass

'''-------------------------调仓时间控制器-----------------------'''
class Time_condition(Adjust_condition):

    def __init__(self, params):
        # 配置调仓时间（24小时分钟制）
        self.times = params.get('times', [])
        g.currentday_max_portfolio = 0;

    def update_params(self, context, params):
        self.times = params.get('times', self.times)
        pass

    @property
    def can_adjust(self):
        return self.t_can_adjust

    def handle_data(self, context, data):
        hour = context.current_dt.hour
        minute = context.current_dt.minute

        self.t_can_adjust = [hour, minute] in self.times
        pass

    def __str__(self):
        return '调仓时间控制器: [调仓时间: %s ]' % (
                str(['%d:%d' % (x[0], x[1]) for x in self.times]))



'''-------------------------指数选择器-----------------------'''
class Index_selection(Adjust_condition):

    def __init__(self, params):
        self.CYBZ = params.get('index_1', '399006.XSHE')
        self.ZXBZ = params.get('index_2', '399005.XSHE')
        self.HS300 = params.get('index_3', '399300.XSHE')
        self.SZ50 = params.get('index_4', '000016.XSHG')
        self.index_growth_rate = params.get('index_growth_rate', 0.01)
        self.t_can_adjust = True
        g.clear_by_index = False

    def update_params(self, context, params):
        self.CYBZ = params.get('index_1', '399006.XSHE')
        self.ZXBZ = params.get('index_2', '399005.XSHE')
        self.HS300 = params.get('index_3', '399300.XSHE')
        self.SZ50 = params.get('index_4', '000016.XSHG')
        self.index_growth_rate = params.get('index_growth_rate', 0.01)
        self.t_can_adjust = True
        g.clear_by_index = False
    @property
    def can_adjust(self):
        return self.t_can_adjust

    def handle_data(self, context, data):
        count = 0
        index_selected = None
        index_selected_growth = 0
        index_growth_dict = {}
        index_growth_dict[self.CYBZ] = get_growth_rate(self.CYBZ)
        index_growth_dict[self.ZXBZ] = get_growth_rate(self.ZXBZ)
        index_growth_dict[self.HS300] = get_growth_rate(self.HS300)
        index_growth_dict[self.SZ50] = get_growth_rate(self.SZ50)

        index_growth_dict_sorted = sorted(index_growth_dict.items(), key=lambda d:d[1], reverse = True)
        for (index, growth) in index_growth_dict_sorted:
            log.info("index: %s ===> growth: %f", index, growth)
            if growth > self.index_growth_rate:
                count += 1
        log.info("%d index growth over 1.01", count)
        g.clear_by_index = True
        if count > 0:
            g.index_selected = index_growth_dict_sorted[0][0]
            g.index_selected_growth = index_growth_dict_sorted[0][1]
            log.info("index_selected: %s ===> growth: %f", g.index_selected, g.index_selected_growth)

            if count == 1:
                g.position_scale = 0.3
                self.log_info("only one index growth over 1.01, so position scale: 0.3")
            elif count == 2:
                g.position_scale = 0.7
                self.log_info("only two indexes growth over 1.01, so position scale: 0.7")
            else:
                g.position_scale = 1
                self.log_info("above three indexes growth over 1.01, so position scale: 1")
            self.t_can_adjust = True
        else:
            log.info("all indexes go down, so not selected any one")
            self.clear_position(context)
            self.t_can_adjust = False
        g.clear_by_index = False
    def __str__(self):
        return '指数选择器, 仓位控制器'


'''-------------------------调仓日计数器-----------------------'''


class Period_condition(Adjust_condition):

    def __init__(self, params):
        # 调仓日计数器，单位：日
        self.period = params.get('period', 3)
        self.day_count = 0
        self.t_can_adjust = False

    def update_params(self, context, params):
        self.period = params.get('period', self.period)

    @property
    def can_adjust(self):
        return self.t_can_adjust

    def handle_data(self, context, data):
        self.log_info("调仓日计数 [%d]" % (self.day_count))
        self.t_can_adjust = self.day_count % self.period == 0
        self.day_count += 1
        pass

    def before_trading_start(self, context):
        self.t_can_adjust = False
        pass

    def when_sell_stock(self, position, order, is_normal):
        if not is_normal and g.clear_by_index != True:
            # 个股止损止盈时，即非正常卖股时，重置计数，原策略是这么写的
            self.log_info("sell stock day count: %d ---> 0" % self.day_count)
            self.day_count = 0
        pass
    # 清仓时调用的函数

    def when_clear_position(self, context):
        if g.clear_by_index != True:
            self.log_info("clear position day count: %d ---> 0" % self.day_count)
            self.day_count = 0
        pass

    def __str__(self):
        return '调仓日计数器:[调仓频率: %d日] [调仓日计数 %d]' % (
                self.period, self.day_count)
'''-------------------------28指数涨幅调仓判断器----------------------'''


class Index28_condition(Adjust_condition):

    def __init__(self, params):
        self.index2 = params.get('index2', '')
        self.index8 = params.get('index8', '')
        self.index_growth_rate = params.get('index_growth_rate', 0.01)
        self.t_can_adjust = False

    def update_params(self, context, params):
        self.index2 = params.get('index2', self.index2)
        self.index8 = params.get('index8', self.index8)
        self.index_growth_rate = params.get(
            'index_growth_rate', self.index_growth_rate)

    @property
    def can_adjust(self):
        return self.t_can_adjust

    def handle_data(self, context, data):
        # 回看指数前20天的涨幅
        gr_index2 = get_growth_rate(self.index2)
        gr_index8 = get_growth_rate(self.index8)
        self.log_info(
            "当前%s指数的20日涨幅 [%.2f%%]" %
            (get_security_info(
                self.index2).display_name,
                gr_index2 *
                100))
        self.log_info(
            "当前%s指数的20日涨幅 [%.2f%%]" %
            (get_security_info(
                self.index8).display_name,
                gr_index8 *
                100))
        if gr_index2 <= self.index_growth_rate and gr_index8 <= self.index_growth_rate:
            self.clear_position(context)
            self.t_can_adjust = False
        else:
            self.t_can_adjust = True
        pass

    def before_trading_start(self, context):
        pass

    def __str__(self):
        return '28指数择时:[大盘指数:%s %s] [小盘指数:%s %s] [判定调仓的二八指数20日增幅 %.2f%%]' % (
                self.index2, get_security_info(self.index2).display_name,
                self.index8, get_security_info(self.index8).display_name,
                self.index_growth_rate * 100)

'''------------------小市值选股器-----------------'''
class Pick_small_cap(Filter_query):

    def filter(self, context, data, q):
        return query(valuation).order_by(valuation.market_cap.asc())

    def __str__(self):
        return '按市值倒序选取股票'


'''------------------指数选股器-----------------'''
class Pick_stock_by_index(Filter_query):
    def filter(self, context, data, q):
        stock_list = []
        if g.index_selected != '':
            self.log_info("selected index: %s" % g.index_selected)
            if g.index_selected == '399300.XSHE':
                pass
            elif g.index_selected == '000016.XSHG':
                pass
            else:
                stock_list = get_index_stocks(g.index_selected)
            #self.log_info(stock_list)
            return query(valuation).filter(valuation.code.in_(stock_list)).order_by(valuation.market_cap.asc())
        else:
            self.log_info("no selected index")


    def __str__(self):
        return '按斜率最高指数选取股票'

# 以下为选评分高于10分的股票
class Pick_score_up(Filter_query):
    def filter(self, context, data, dst_stocks):
        dst_stocks = []
        stock_list = get_index_stocks(g.index_selected)
        stock_score = {}
        log.info("stock list : %d" % len(stock_list))
        for stock in stock_list:
            h = attribute_history(
                stock, 130, unit='1d', fields=(
                    'close', 'high', 'low'), skip_paused=True)
            if len(h) < 130:
                log.info("This stock is not over 130 days, is %d" % len(h))
                continue
            low_price_130 = h.low.min()
            high_price_130 = h.high.max()

            avg_15 = data[stock].mavg(15, field='close')
            cur_price = data[stock].close

            score = (
                cur_price - low_price_130) + (cur_price - high_price_130) + (cur_price - avg_15)
            if score >= 10:
                #log.info("stock: %s, score: %f", stock, score)
                stock_score[stock] = score

        stock_score_sort = sorted(stock_score.items(), key=lambda d:d[1], reverse = True)
        for (stock, score) in stock_score_sort:
            #log.info("stock: %s --> score: %f", stock, score)
            dst_stocks.append(stock)

        return query(valuation.code).filter(valuation.code.in_(dst_stocks))

    def __str__(self):
        return '股票评分'


class Filter_pe(Filter_query):

    def __init__(self, params):
        self.pe_min = params.get('pe_min', 0)
        self.pe_max = params.get('pe_max', 200)

    def update_params(self, context, params):
        self.pe_min = params.get('pe_min', self.pe_min)
        self.pe_max = params.get('pe_max', self.pe_max)

    def filter(self, context, data, q):
        return q.filter(
            valuation.pe_ratio > self.pe_min,
            valuation.pe_ratio < self.pe_max
            )

    def __str__(self):
        return '根据PE范围选取股票： [ %d < pe < %d]' % (self.pe_min, self.pe_max)


class Filter_eps(Filter_query):

    def __init__(self, params):
        self.eps_min = params.get('eps_min', 0)

    def update_params(self, context, params):
        self.eps_min = params.get('eps_min', self.eps_min)

    def filter(self, context, data, q):
        return q.filter(
            indicator.eps > self.eps_min,
            )

    def __str__(self):
        return '根据EPS范围选取股票： [ %d < eps ]' % (self.eps_min)


class Filter_limite(Filter_query):

    def __init__(self, params):
        self.pick_stock_count = params.get('pick_stock_count', 0)

    def update_params(self, context, params):
        self.pick_stock_count = params.get(
            'pick_stock_count', self.pick_stock_count)

    def filter(self, context, data, q):
        return q.limit(self.pick_stock_count)

    def __str__(self):
        return '初选股票数量: %d' % (self.pick_stock_count)


class Filter_gem(Filter_stock_list):

    def filter(self, context, data, stock_list):
        return [stock for stock in stock_list if stock[0:3] != '300']

    def __str__(self):
        return '过滤创业板股票'


class Filter_paused_stock(Filter_stock_list):

    def filter(self, context, data, stock_list):
        current_data = get_current_data()
        return [stock for stock in stock_list if not current_data[stock].paused]

    def __str__(self):
        return '过滤停牌股票'


class Filter_limitup(Filter_stock_list):

    def filter(self, context, data, stock_list):
        threshold = 1.00
        return [stock for stock in stock_list if data[stock].close < data[stock].high_limit * threshold]

    def __str__(self):
        return '过滤涨停股票'


class Filter_limitdown(Filter_stock_list):

    def filter(self, context, data, stock_list):
        threshold = 1.00
        if (len(context.portfolio.positions) == 0):
            return stock_list
        else:
            return [stock for stock in stock_list if data[stock].close > data[stock].low_limit * threshold]

    def __str__(self):
        return '过滤跌停股票'


class Filter_old_stock(Filter_stock_list):

    def __init__(self, params):
        self.day_count = params.get('day_count', 365)

    def update_params(self, context, params):
        self.day_count = params.get('day_count', self.day_count)

    def filter(self, context, data, stock_list):
        tmpList = []
        for stock in stock_list:
            days_public = (
                context.current_dt.date() -
                get_security_info(stock).start_date).days
            # 上市未超过1年
            if days_public < self.day_count:
                tmpList.append(stock)
        return tmpList

    def __str__(self):
        return '过滤上市时间超过 %d 天的股票' % (self.day_count)


class Filter_new_stock(Filter_stock_list):

    def __init__(self, params):
        self.day_count = params.get('day_count', 365)

    def update_params(self, context, params):
        self.day_count = params.get('day_count', self.day_count)

    def filter(self, context, data, stock_list):
        tmpList = []
        for stock in stock_list:
            days_public = (
                context.current_dt.date() -
                get_security_info(stock).start_date).days
            if days_public > self.day_count:
                tmpList.append(stock)
        return tmpList

    def __str__(self):
        return '过滤上市时间未超过 %d 天的次新股' % (self.day_count)


class Filter_st(Filter_stock_list):

    def filter(self, context, data, stock_list):
        current_data = get_current_data()
        return [stock for stock in stock_list
                if not current_data[stock].is_st
                and not current_data[stock].name.startswith('退')]

    def __str__(self):
        return '过滤ST股票'


'''------------------三天或五天均线上选股器-----------------'''
class Filter_3or5_mean_up(Filter_query):
    def __init__(self, params):
        self.day_count1 = params.get('day_count1', 3)
        self.day_count2 = params.get('day_count2', 5)

    def update_params(self, context, params):
        self.day_count1 = params.get('day_count1', self.day_count1)
        self.day_count2 = params.get('day_count2', self.day_count2)

    def filter(self, context, data, stock_list):
        return [stock for stock in stock_list if (get_growth_rate(stock, self.day_count1) > 0) or (get_growth_rate(stock, self.day_count2) > 0) ]

    def __str__(self):
        return '三天或五天均线向上选股器'

class Filter_blacklist(Filter_stock_list):

    def __get_blacklist(self):
        # 黑名单一览表，更新时间 2016.7.10 by 沙米
        # 科恒股份、太空板业，一旦2016年继续亏损，直接面临暂停上市风险
        blacklist = [
            "600656.XSHG",
            "300372.XSHE",
            "600403.XSHG",
            "600421.XSHG",
            "600733.XSHG",
            "300399.XSHE",
            "600145.XSHG",
            "002679.XSHE",
            "000020.XSHE",
            "002330.XSHE",
            "300117.XSHE",
            "300135.XSHE",
            "002566.XSHE",
            "002119.XSHE",
            "300208.XSHE",
            "002237.XSHE",
            "002608.XSHE",
            "000691.XSHE",
            "002694.XSHE",
            "002715.XSHE",
            "002211.XSHE",
            "000788.XSHE",
            "300380.XSHE",
            "300028.XSHE",
            "000668.XSHE",
            "300033.XSHE",
            "300126.XSHE",
            "300340.XSHE",
            "300344.XSHE",
            "002473.XSHE"]
        return blacklist

    def filter(self, context, data, stock_list):
        blacklist = self.__get_blacklist()
        return [stock for stock in stock_list if stock not in blacklist]

    def __str__(self):
        return '过滤黑名单股票'


class Filter_rank(Filter_stock_list):

    def __init__(self, params):
        self.rank_stock_count = params.get('rank_stock_count', 20)

    def update_params(self, context, params):
        self.rank_stock_count = params.get(
            'self.rank_stock_count', self.rank_stock_count)

    def filter(self, context, data, stock_list):
        if len(stock_list) == 0:
            return stock_list
        if len(stock_list) > self.rank_stock_count:
            stock_list = stock_list[:self.rank_stock_count]

        dst_stocks = {}
        for stock in stock_list:
            h = attribute_history(
                stock, 130, unit='1d', fields=(
                    'close', 'high', 'low'), skip_paused=True)
            low_price_130 = h.low.min()
            high_price_130 = h.high.max()

            avg_15 = data[stock].mavg(15, field='close')
            cur_price = data[stock].close

            score = (
                cur_price - low_price_130) + (cur_price - high_price_130) + (cur_price - avg_15)
            dst_stocks[stock] = score

        df = pd.DataFrame(dst_stocks.values(), index=dst_stocks.keys())
        df.columns = ['score']
        df = df.sort(columns='score', ascending=True)
        return list(df.index)

    def __str__(self):
        return '股票评分排序 [评分股数: %d ]' % (self.rank_stock_count)


class Filter_buy_count(Filter_stock_list):

    def __init__(self, params):
        self.buy_count = params.get('buy_count', 3)

    def update_params(self, context, params):
        self.buy_count = params.get('buy_count', self.buy_count)

    def filter(self, context, data, stock_list):
        if len(stock_list) > self.buy_count:
            return stock_list[:self.buy_count]
        else:
            return stock_list

    def __str__(self):
        return '获取最终待购买股票数:[ %d ]' % (self.buy_count)

'''---------------卖出股票规则--------------'''


class Sell_stocks(Adjust_position):

    def adjust(self, context, data, buy_stocks):
        # 卖出不在待买股票列表中的股票
        # 对于因停牌等原因没有卖出的股票则继续持有
        
        for stock in context.portfolio.positions.keys():
            if stock not in buy_stocks:
                self.log_info("stock [%s] in position is not buyable" % (stock))
                position = context.portfolio.positions[stock]
                self.close_position(position)
            else:
                self.log_info("stock [%s] is already in position" % (stock))

    def __str__(self):
        return '股票调仓卖出规则：卖出不在buy_stocks的股票'

'''---------------买入股票规则--------------'''


class Buy_stocks(Adjust_position):

    def __init__(self, params):
        self.buy_count = params.get('buy_count', 3)

    def update_params(self, context, params):
        self.buy_count = params.get('buy_count', self.buy_count)

    def adjust(self, context, data, buy_stocks):
        # 买入股票
        # 始终保持持仓数目为g.buy_stock_count
        # 根据股票数量分仓
        # 此处只根据可用金额平均分配购买，不能保证每个仓位平均分配
        position_count = len(context.portfolio.positions)
        if g.index_selected == '399300.XSHE' or g.index_selected == '000016.XSHG':
            value = context.portfolio.cash * g.position_scale
            for stock in buy_stocks:
                if context.portfolio.positions[stock].total_amount == 0:
                    if self.open_position(stock, value):
                        if len(context.portfolio.positions) == self.buy_count:
                            break
        elif self.buy_count > position_count:
            value = context.portfolio.cash * g.position_scale / (self.buy_count - position_count)
            for stock in buy_stocks:
                if context.portfolio.positions[stock].total_amount == 0:
                    if self.open_position(stock, value):
                        if len(context.portfolio.positions) == self.buy_count:
                            break
        pass

    def __str__(self):
        return '股票调仓买入规则：现金平分式买入股票达目标股票数'

'''---------------个股止损--------------'''


class Stop_loss_stocks(Rule):
    # get_period_func 为获取period的函数,无传入参数，传出参数为period
    # on_close_position_func 卖出股票时触发的事件，传入参数为 stock,无返回

    def __init__(self, params):
        self.last_high = {}
        self.period = params.get('period', 3)
        self.pct_change = {}

    def update_params(self, context, params):
        self.period = params.get('period', self.period)
    # 个股止损

    def handle_data(self, context, data):
        for stock in context.portfolio.positions.keys():
            cur_price = data[stock].close
            xi = attribute_history(stock, 2, '1d', 'high', skip_paused=True)
            ma = xi.max()
            if self.last_high[stock] < cur_price:
                self.last_high[stock] = cur_price

            threshold = self.__get_stop_loss_threshold(stock, self.period)
            # log.debug("个股止损阈值, stock: %s, threshold: %f" %(stock, threshold))
            if cur_price < self.last_high[stock] * (1 - threshold):
                self.log_info(
                    "==> 个股止损, stock: %s, cur_price: %f, last_high: %f, threshold: %f" %
                    (stock, cur_price, self.last_high[stock], threshold))

                position = context.portfolio.positions[stock]
                self.close_position(position, False)

    # 获取个股前n天的m日增幅值序列
    # 增加缓存避免当日多次获取数据
    def __get_pct_change(self, security, n, m):
        pct_change = None
        if security in self.pct_change.keys():
            pct_change = self.pct_change[security]
        else:
            h = attribute_history(
                security,
                n,
                unit='1d',
                fields=('close'),
                skip_paused=True)
            pct_change = h['close'].pct_change(m)  # 3日的百分比变比（即3日涨跌幅）
            self.pct_change[security] = pct_change
        return pct_change

    # 计算个股回撤止损阈值
    # 即个股在持仓n天内能承受的最大跌幅
    # 算法：(个股250天内最大的n日跌幅 + 个股250天内平均的n日跌幅)/2
    # 返回正值
    def __get_stop_loss_threshold(self, security, n=3):
        pct_change = self.__get_pct_change(security, 250, n)
        # log.debug("pct of security [%s]: %s", pct)
        maxd = pct_change.min()
        # maxd = pct[pct<0].min()
        avgd = pct_change.mean()
        # avgd = pct[pct<0].mean()
        # maxd和avgd可能为正，表示这段时间内一直在增长，比如新股
        bstd = (maxd + avgd) / 2

        # 数据不足时，计算的bstd为nan
        if not isnan(bstd):
            if bstd != 0:
                return abs(bstd)
            else:
                # bstd = 0，则 maxd <= 0
                if maxd < 0:
                    # 此时取最大跌幅
                    return abs(maxd)

        return 0.099  # 默认配置回测止损阈值最大跌幅为-9.9%，阈值高貌似回撤降低

    def when_sell_stock(self, position, order, is_normal):
        if position.security in self.last_high:
            self.last_high.pop(position.security)
        pass

    def when_buy_stock(self, stock, order):
        if order.status == OrderStatus.held and order.filled == order.amount:
            # 全部成交则删除相关证券的最高价缓存
            self.last_high[stock] = get_close_price(stock, 1, '1m')
        pass

    def after_trading_end(self, context):
        self.pct_change = {}
        pass

    def __str__(self):
        return '个股止损器:[当前缓存价格数: %d ]' % (len(self.last_high))

''' ----------------------个股止盈------------------------------'''


class Stop_profit_stocks(Rule):

    def __init__(self, params):
        self.last_high = {}
        self.period = params.get('period', 3)
        self.pct_change = {}

    def update_params(self, context, params):
        self.period = params.get('period', self.period)
    # 个股止盈

    def handle_data(self, context, data):
        for stock in context.portfolio.positions.keys():
            position = context.portfolio.positions[stock]
            cur_price = data[stock].close
            threshold = self.__get_stop_profit_threshold(stock, self.period)
            # log.debug("个股止盈阈值, stock: %s, threshold: %f" %(stock, threshold))
            if cur_price > position.avg_cost * (1 + threshold):
                self.log_info(
                    "==> 个股止盈, stock: %s, cur_price: %f, avg_cost: %f, threshold: %f" %
                    (stock, cur_price, self.last_high[stock], threshold))

                position = context.portfolio.positions[stock]
                self.close_position(position, False)

    # 获取个股前n天的m日增幅值序列
    # 增加缓存避免当日多次获取数据
    def __get_pct_change(self, security, n, m):
        pct_change = None
        if security in self.pct_change.keys():
            pct_change = self.pct_change[security]
        else:
            h = attribute_history(
                security,
                n,
                unit='1d',
                fields=('close'),
                skip_paused=True)
            pct_change = h['close'].pct_change(m)  # 3日的百分比变比（即3日涨跌幅）
            self.pct_change[security] = pct_change
        return pct_change

    # 计算个股止盈阈值
    # 算法：个股250天内最大的n日涨幅
    # 返回正值
    def __get_stop_profit_threshold(self, security, n=3):
        pct_change = self.__get_pct_change(security, 250, n)
        maxr = pct_change.max()

        # 数据不足时，计算的maxr为nan
        # 理论上maxr可能为负
        if (not isnan(maxr)) and maxr != 0:
            return abs(maxr)
        return 0.30  # 默认配置止盈阈值最大涨幅为30%

    def when_sell_stock(self, position, order, is_normal):
        if order.status == OrderStatus.held and order.filled == order.amount:
            # 全部成交则删除相关证券的最高价缓存
            if position.security in self.last_high:
                self.last_high.pop(position.security)
        pass

    def when_buy_stock(self, stock, order):
        self.last_high[stock] = get_close_price(stock, 1, '1m')
        pass

    def after_trading_end(self, context):
        self.pct_change = {}
        pass

    def __str__(self):
        return '个股止盈器:[当前缓存价格数: %d ]' % (len(self.last_high))

''' ----------------------最高价最低价比例止损------------------------------'''


class Stop_loss_by_price(Adjust_condition):

    def __init__(self, params):
        self.index = params.get('index', '000001.XSHG')
        self.day_count = params.get('day_count', 160)
        self.multiple = params.get('multiple', 2.2)
        self.is_day_stop_loss_by_price = False

    def update_params(self, context, params):
        self.index = params.get('index', self.index)
        self.day_count = params.get('day_count', self.day_count)
        self.multiple = params.get('multiple', self.multiple)

    def handle_data(self, context, data):
        # 大盘指数前130日内最高价超过最低价2倍，则清仓止损
        # 基于历史数据判定，因此若状态满足，则当天都不会变化
        # 增加此止损，回撤降低，收益降低

        if not self.is_day_stop_loss_by_price:
            h = attribute_history(
                self.index, self.day_count, unit='1d', fields=(
                    'close', 'high', 'low'), skip_paused=True)
            low_price_130 = h.low.min()
            high_price_130 = h.high.max()
            if high_price_130 > self.multiple * low_price_130 and h['close'][-1] < h[
                    'close'][-4] * 1 and h['close'][-1] > h['close'][-100]:
                # 当日第一次输出日志
                self.log_info(
                    "==> 大盘止损，%s指数前130日内最高价超过最低价2倍, 最高价: %f, 最低价: %f" %
                    (get_security_info(
                        self.index).display_name,
                        high_price_130,
                        low_price_130))
                self.is_day_stop_loss_by_price = True

        if self.is_day_stop_loss_by_price:
            self.clear_position(context)

    def before_trading_start(self, context):
        self.is_day_stop_loss_by_price = False
        pass

    def __str__(self):
        return '大盘高低价比例止损器:[指数: %s] [参数: %s日内最高最低价: %s倍] [当前状态: %s]' % (
                self.index, self.day_count, self.multiple, self.is_day_stop_loss_by_price)

    @property
    def can_adjust(self):
        return not self.is_day_stop_loss_by_price

''' ----------------------最高价最低价比例止损------------------------------'''


class Stop_loss_by_growth_rate(Adjust_condition):

    def __init__(self, params):
        self.index = params.get('index', '000001.XSHG')
        self.stop_loss_growth_rate = params.get('stop_loss_growth_rate', -0.03)
        self.to_stop_loss = False

    def update_params(self, context, params):
        self.index = params.get('index', '000001.XSHG')
        self.stop_loss_growth_rate = params.get('stop_loss_growth_rate', -0.03)
        self.to_stop_loss = False

    def handle_data(self, context, data):
        if self.to_stop_loss:
            return
        cur_growth_rate = get_growth_rate(self.index, 1)
        if cur_growth_rate < self.stop_loss_growth_rate:
            self.log_warn(
                '当日涨幅 [%s : %.2f%%] 低于阀值 %.2f%%,清仓止损!' %
                (self.index, cur_growth_rate * 100, self.stop_loss_growth_rate))
            self.to_stop_loss = True
            self.clear_position(context)
            return
        self.to_stop_loss = False

    def before_trading_start(self, context):
        self.to_stop_loss = False

    def __str__(self):
        return '指数当日涨幅限制止损器:[指数: %s] [最低涨幅: %.2f%%]' % (
                self.index, self.stop_loss_growth_rate * 100)

    @property
    def can_adjust(self):
        return not self.to_stop_loss

''' ----------------------三乌鸦止损------------------------------'''
class Stop_loss_by_3_black_crows(Adjust_condition):

    def __init__(self, params):
        self.index = params.get('index', '000001.XSHG')
        self.dst_drop_minute_count = params.get('dst_drop_minute_count', 60)
        # 临时参数
        self.is_last_day_3_black_crows = False
        self.t_can_adjust = True
        self.cur_drop_minute_count = 0

    def update_params(self, context, params):
        self.index = params.get('index', self.index)
        self.dst_drop_minute_count = params.get(
            'dst_drop_minute_count', self.dst_drop_minute_count)

    def initialize(self, context):
        pass

    def handle_data(self, context, data):
        # 前日三黑鸦，累计当日每分钟涨幅<0的分钟计数
        # 如果分钟计数超过一定值，则开始进行三黑鸦止损
        # 避免无效三黑鸦乱止损
        if self.is_last_day_3_black_crows:
            if get_growth_rate(self.index, 1) < 0:
                self.cur_drop_minute_count += 1

            if self.cur_drop_minute_count >= self.dst_drop_minute_count:
                if self.cur_drop_minute_count == self.dst_drop_minute_count:
                    self.log_info("==> 超过三黑鸦止损开始")

                self.clear_position(context)
                self.t_can_adjust = False
        else:
            self.t_can_adjust = True
        pass

    def before_trading_start(self, context):
        self.is_last_day_3_black_crows = is_3_black_crows(self.index)
        if self.is_last_day_3_black_crows:
            self.log_info("==> 前4日已经构成三黑鸦形态")
        pass

    def after_trading_end(self, context):
        self.is_last_day_3_black_crows = False
        self.cur_drop_minute_count = 0
        pass

    def __str__(self):
        return '大盘三乌鸦止损器:[指数: %s] [跌计数分钟: %d] [当前状态: %s]' % (
            self.index, self.dst_drop_minute_count, self.is_last_day_3_black_crows)

    @property
    def can_adjust(self):
        return self.t_can_adjust

''' ----------------------净值止损------------------------------'''
class  Stat_portfolio(Adjust_condition):

    def handle_data(self, context, data):
        if context.portfolio.total_value > g.currentday_max_portfolio:
            g.currentday_max_portfolio = context.portfolio.total_value

    def after_trading_end(self, context):
        g.max_portfolio_everyday.append(g.currentday_max_portfolio)
        g.currentday_max_portfolio = 0
    def __str__(self):
        return '净值统计'



''' ----------------------净值止损------------------------------'''
class Stop_loss_by_last3day_net_worth(Adjust_condition):
    pass
class Stop_loss_by_last5day_net_worth(Adjust_condition):
    pass

class Stop_loss_by_currentday_net_worth(Adjust_condition):

    def __init__(self, params):
        self.check_days = params.get('check_days', 1)
        self.back_percent = params.get('back_percent', 2)
        self.t_can_adjust = True


    def update_params(self, context, params):
        self.check_days = params.get('check_days', self.check_days)
        self.back_percent = params.get('back_percent', self.back_percent)

    def initialize(self, context):
        g.currentday_max_portfolio = 0
        g.record = 0;
        g.not_open_days = -1
        pass

    def handle_data(self, context, data):
        if context.portfolio.total_value <= (1.0 - int(self.back_percent) / 100.0) * g.currentday_max_portfolio:
            log.warn('currentday_max_portfolio: %f, current_portfolio: %f', g.currentday_max_portfolio, context.portfolio.total_value)
            g.not_open_days = 2
            self.t_can_adjust = False
            self.clear_position(context)

    def before_trading_start(self, context):
        pass

    def after_trading_end(self, context):
        if g.not_open_days >= 0:
            g.not_open_days -= 1
        else:
            self.t_can_adjust = True
        log.info("after trade open day: %d", g.not_open_days)

    def __str__(self):
        return '当天净值止损器: [回撤比例: %f]' % (self.back_percent)

    @property
    def can_adjust(self):
        return self.t_can_adjust


''' ----------------------28指数值实时进行止损------------------------------'''


class Stop_loss_by_28_index(Adjust_condition):

    def __init__(self, params):
        self.index2 = params.get('index2', '')
        self.index8 = params.get('index8', '')
        self.index_growth_rate = params.get('index_growth_rate', 0.01)
        self.dst_minute_count_28index_drop = params.get(
            'dst_minute_count_28index_drop', 120)
        # 临时参数
        self.t_can_adjust = True
        self.minute_count_28index_drop = 0

    def update_params(self, context, params):
        self.index2 = params.get('index2', self.index2)
        self.index8 = params.get('index8', self.index8)
        self.index_growth_rate = params.get(
            'index_growth_rate', self.index_growth_rate)
        self.dst_minute_count_28index_drop = params.get(
            'dst_minute_count_28index_drop', self.dst_minute_count_28index_drop)

    def initialize(self, context):
        pass

    def handle_data(self, context, data):
        # 回看指数前20天的涨幅
        gr_index2 = get_growth_rate(self.index2)
        gr_index8 = get_growth_rate(self.index8)

        if gr_index2 <= self.index_growth_rate and gr_index8 <= self.index_growth_rate:
            if (self.minute_count_28index_drop == 0):
                self.log_info(
                    "当前二八指数的20日涨幅同时低于[%.2f%%], %s指数: [%.2f%%], %s指数: [%.2f%%]" %
                    (self.index_growth_rate *
                     100,
                     get_security_info(
                         self.index2).display_name,
                        gr_index2 *
                        100,
                        get_security_info(
                         self.index8).display_name,
                        gr_index8 *
                        100))

            self.minute_count_28index_drop += 1
        else:
            # 不连续状态归零
            if self.minute_count_28index_drop < self.dst_minute_count_28index_drop:
                self.minute_count_28index_drop = 0

        if self.minute_count_28index_drop >= self.dst_minute_count_28index_drop:
            if self.minute_count_28index_drop == self.dst_minute_count_28index_drop:
                self.log_info(
                    "==> 当日%s指数和%s指数的20日增幅低于[%.2f%%]已超过%d分钟，执行28指数止损" %
                    (get_security_info(
                        self.index2).display_name,
                        get_security_info(
                        self.index8).display_name,
                        self.index_growth_rate *
                        100,
                        self.dst_minute_count_28index_drop))

            self.clear_position(context)
            self.t_can_adjust = False
        else:
            self.t_can_adjust = True
        pass

    def after_trading_end(self, context):
        self.t_can_adjust = False
        self.minute_count_28index_drop = 0
        pass

    def __str__(self):
        return '28指数值实时进行止损:[大盘指数: %s %s] [小盘指数: %s %s] [判定调仓的二八指数20日增幅 %.2f%%] [连续 %d 分钟则清仓] ' % (
                self.index2, get_security_info(self.index2).display_name,
                self.index8, get_security_info(self.index8).display_name,
                self.index_growth_rate * 100,
                self.dst_minute_count_28index_drop)

    @property
    def can_adjust(self):
        return self.t_can_adjust

''' ----------------------统计类----------------------------'''


class Stat(Rule):

    def __init__(self, params):
        # 加载统计模块
        self.trade_total_count = 0
        self.trade_success_count = 0
        self.statis = {'win': [], 'loss': []}

    def after_trading_end(self, context):
        self.report(context)

    def when_sell_stock(self, position, order, is_normal):
        if order.filled > 0:
            # 只要有成交，无论全部成交还是部分成交，则统计盈亏
            self.watch(
                position.security,
                order.filled,
                position.avg_cost,
                position.price)

    def reset(self):
        self.trade_total_count = 0
        self.trade_success_count = 0
        self.statis = {'win': [], 'loss': []}

    # 记录交易次数便于统计胜率
    # 卖出成功后针对卖出的量进行盈亏统计
    def watch(self, stock, sold_amount, avg_cost, cur_price):
        self.trade_total_count += 1
        current_value = sold_amount * cur_price
        cost = sold_amount * avg_cost

        percent = round((current_value - cost) / cost * 100, 2)
        if current_value > cost:
            self.trade_success_count += 1
            win = [stock, percent]
            self.statis['win'].append(win)
        else:
            loss = [stock, percent]
            self.statis['loss'].append(loss)

    def report(self, context):
        cash = context.portfolio.cash
        totol_value = context.portfolio.portfolio_value
        position = 1 - cash / totol_value
        self.log_info("收盘后持仓概况:%s" % str(list(context.portfolio.positions)))
        self.log_info("仓位概况:%.2f" % position)
        self.print_win_rate(
            context.current_dt.strftime("%Y-%m-%d"),
            context.current_dt.strftime("%Y-%m-%d"),
            context)

    # 打印胜率
    def print_win_rate(self, current_date, print_date, context):
        if str(current_date) == str(print_date):
            win_rate = 0
            if 0 < self.trade_total_count and 0 < self.trade_success_count:
                win_rate = round(
                    self.trade_success_count /
                    float(
                        self.trade_total_count),
                    3)

            most_win = self.statis_most_win_percent()
            most_loss = self.statis_most_loss_percent()
            starting_cash = context.portfolio.starting_cash
            total_profit = self.statis_total_profit(context)
            if len(most_win) == 0 or len(most_loss) == 0:
                return

            s = '\n------------绩效报表------------'
            s += '\n交易次数: {0}, 盈利次数: {1}, 胜率: {2}'.format(
                self.trade_total_count, self.trade_success_count,
                str(win_rate * 100) + str('%'))
            s += '\n单次盈利最高: {0}, 盈利比例: {1}%'.format(
                most_win['stock'], most_win['value'])
            s += '\n单次亏损最高: {0}, 亏损比例: {1}%'.format(
                most_loss['stock'], most_loss['value'])
            s += '\n总资产: {0}, 本金: {1}, 盈利: {2}, 盈亏比率：{3}%'.format(
                starting_cash + total_profit, starting_cash, total_profit,
                total_profit / starting_cash * 100)
            s += '\n--------------------------------'
            self.log_info(s)
            #self.log_info('每日最大净值：')
            #self.log_info(g.max_portfolio_everyday)
    # 统计单次盈利最高的股票
    def statis_most_win_percent(self):
        result = {}
        for statis in self.statis['win']:
            if {} == result:
                result['stock'] = statis[0]
                result['value'] = statis[1]
            else:
                if statis[1] > result['value']:
                    result['stock'] = statis[0]
                    result['value'] = statis[1]

        return result

    # 统计单次亏损最高的股票
    def statis_most_loss_percent(self):
        result = {}
        for statis in self.statis['loss']:
            if {} == result:
                result['stock'] = statis[0]
                result['value'] = statis[1]
            else:
                if statis[1] < result['value']:
                    result['stock'] = statis[0]
                    result['value'] = statis[1]

        return result

    # 统计总盈利金额
    def statis_total_profit(self, context):
        return context.portfolio.portfolio_value - context.portfolio.starting_cash

    def __str__(self):
        return '策略绩效统计'

'''-------------------实盘易对接-----------------------'''


class Shipane_sync_p(Rule):

    def __init__(self, params):
        self.host = params.get('host', '')
        self.port = params.get('port', 8888)
        self.key = params.get('key', '')
        self._client_param = params.get('client', '')
        self.strong_op = params.get('strong_op', True)

    def update_params(self, context, params):
        self.host = params.get('host', '')
        self.port = params.get('port', 8888)
        self.key = params.get('key', '')
        self._client_param = params.get('client', '')
        self.strong_op = params.get('strong_op', True)

    # 调仓后调用
    def after_adjust_end(self, context, data):
        self.__sync_position(context)
    # 清仓时调用的函数

    def when_clear_position(self, context):
        self.__sync_position(context)

    def __sync_position(self, context):
        try:
            client = shipane_sdk.Client(
                shipane_sdk._Logger(),
                key=self.key,
                host=self.host,
                port=self.port)
            op_count = 2 if self.strong_op else 1
            # 强力买卖条件下,卖两次股,防止五档都没卖掉,扫两次五档没卖完就算了吧
            for i in range(op_count):
                self.__sell(client, context)
            # 强力买卖条件下,买股,买两遍，第一遍为买，第二遍为检查，防止部分五档扫完还没买够的
            for i in range(op_count):
                self.__buy(client, context)
            pass
        except Exception as e:
            send_message('实盘易操作异常！！请检查!<br>' + str(e), channel='weixin')
            self.log_warn('实盘易操作异常！！请检查!' + str(e))

    # 卖股
    def __sell(self, client, context):
        sp = self.__get_shipan_p(client)
        mp = self.__get_moni_p(context)
        op_list = self.__get_dif(mp, sp)
        self.log_info('持仓差异:' + str(op_list))
        for x in op_list:
            if x[1] > 0:
                continue
            try:
                actual_order = client.execute(
                    self._client_param, action='SELL', symbol=x[0],
                    type='MARKET', priceType=4, amount=abs(x[1]))
            except Exception as e:
                self.log_warn("[实盘易] 卖单异常 [%s : %d]：%s" % (x[0], x[1], str(e)))

    # 买股
    def __buy(self, client, context):
        # 获取实盘总持仓
        sp = self.__get_shipan_p(client)
        # 获取模拟盘持仓
        mp = self.__get_moni_p(context)
        # 通过对比持仓获得要操作的股票及数量
        op_list = self.__get_dif(mp, sp)
        self.log_info('持仓差异:' + str(op_list))
        # 买股
        for x in op_list:
            if x[1] < 0:
                continue
            stock = x[0]
            buy_count = abs(x[1])
            try:
                # 买单时需要获取实际资金，以涨停价计算最大挂单数买入。
                # 最后一单经常需要拆成两单买入。还有误差就不管了。
                data = get_current_data()
                max_price = data[stock].high_limit
                for i in range(2):
                    # 获取实盘可用资金
                    cash = self.__get_shipan_cash(client)
                    if cash < 0:
                        send_message('实盘易获取实盘可用资金失败，请检查！', channel='weixin')
                        self.log_warn('实盘易获取实盘可用资金失败，请检查')
                        return

                    # 计算当前可用资金以涨停价挂单的最大挂单数
                    max_count = int(int(cash * 1.0 / max_price / 100) * 100)
                    self.log_info(
                        '%d 次买计算:[stock : %s][max_price:%f] [cash: %f] [max_count:%d] [aim:%d]' %
                        (i + 1, stock, max_price, cash, max_count, buy_count))
                    if max_count <= 0:
                        break
                    if max_count >= buy_count:
                        # 可用资金足够，一次性买入。
                        actual_order = client.execute(
                            self._client_param, action='BUY', symbol=stock,
                            type='MARKET', priceType=4, amount=buy_count)
                        break
                    else:
                        # 资金不足，分次买入。
                        actual_order = client.execute(
                            self._client_param, action='BUY', symbol=stock,
                            type='MARKET', priceType=4, amount=max_count)
                        buy_count -= max_count
            except Exception as e:
                self.log_warn(
                    "[实盘易] 买单异常 [%s : %d]：%s" %
                    (stock, buy_count, str(e)))

    # 获取实盘现金
    def __get_shipan_cash(self, client):
        r = None
        # 重复三次获取，防止偶然性网络错误
        for i in range(3):
            try:
                r = client.get_positions(self._client_param)
                break
            except:
                pass
        if r is None:
            return -1

        try:
            cash = float(r['sub_accounts'][u'可用'])
        except:
            cash = -1
        return cash

    # 获取实盘持仓
    def __get_shipan_p(self, client):
        r = None
        e1 = None
        # 重复三次获取，防止偶然性网络错误
        for i in range(3):
            try:
                r = client.get_positions(self._client_param)
                break
            except Exception as e:
                e1 = e
                pass
        if r is None:
            # 重抛异常
            if e1 is not None:
                raise e1
            return
        positions = r.get('positions', None)
        sp = zip(positions[u'证券代码'], positions[u'证券数量'])
        sp = [[normalize_code(x[0]).encode('utf-8'), int(float(x[1]))]
              for x in sp if x[0] != '' and x[1] != '']
        return sp

    # 获取模拟盘持仓
    def __get_moni_p(self, context):
        result = []
        total_values = context.portfolio.positions_value + context.portfolio.cash
        for stock in context.portfolio.positions.keys():
            position = context.portfolio.positions[stock]
            if position.total_amount == 0:
                continue
            result.append([stock, position.total_amount])

        return result

    # 获取两持仓之差 mp为模拟盘，sp为实盘。
    def __get_dif(self, mp, sp):
        sp = [[x[0], -x[1]] for x in sp]
        op_list = mp + sp
        # 取两个列表之差
        s_list = list(set([x[0] for x in op_list]))
        s_list = [[s, sum([x[1] for x in op_list if x[0] == s])]
                  for s in s_list]
        # s_list = [x for x in s_list if x[1] >= 100 or x[1] <= -100]
        # s_list += [x for x in sp if x[1] > -100 and x[1] < 0]
        new_l = []
        for s in s_list:
            if s[1] % 100 == 0:
                new_l.append(s)
                continue
            if s[1] < 0:
                # 卖股
                # 取模拟盘的持仓量
                t = [x[1] for x in mp if x[0] == s[0]]
                if len(t) == 0 or t[0] == 0:
                    # 如果模拟盘已清仓该股，则实盘全卖
                    new_l.append(s)
                    continue
                # 对股票取整
                n = int(round(s[1] * 1.0 / 100) * 100)
                new_l.append([s[0], n])
            else:
                # 买股直接四舍五入取整
                n = int(round(s[1] * 1.0 / 100) * 100)
                new_l.append([s[0], n])
        new_l = [x for x in new_l if x[1] != 0]
        new_l = sorted(new_l, key=lambda x: x[1])
        return new_l

    def __str__(self):
        return '实盘易对接券商'

'''-----------------根据聚宽Order用实盘易下单------------------'''


class Shipane_order(Rule):
    def __init__(self,params):
        pass
    def update_params(self,context,params):
        g.__manager = shipane_sdk.StrategyManager(context, 'manager-1')
    def initialize(self,context):
        g.__manager = shipane_sdk.StrategyManager(context, 'manager-1')

    def after_trading_end(self,context):
        pass

    # 卖出股票时调用的函数
    def when_sell_stock(self,position,order,is_normal):
        try:
            g.__manager.execute(order)
            self.log_info('实盘易卖股成功:' + str(order))
        except:
            self.log_error('实盘易卖股失败:' + str(order))
        pass

    # 买入股票时调用的函数
    def when_buy_stock(self,stock,order):
        try:
            g.__manager.execute(order)
            self.log_info('实盘易买股成功:' + str(order))
        except:
            self.log_error('实盘易买股失败:' + str(order))
        pass
'''------------------------------通过实盘易申购新股----------------------'''


class Purchase_new_stocks(Rule):

    def __init__(self, params):
        self.times = params.get('times',[[10,00]])

    def initialize(self,context):
        g.__manager = shipane_sdk.StrategyManager(context, 'manager-1')
    def update_params(self, context, params):
        g.__manager = shipane_sdk.StrategyManager(context, 'manager-1')
        self.times = params.get('times', [[10, 00]])

    def handle_data(self, context, data):
        hour = context.current_dt.hour
        minute = context.current_dt.minute
        if not [hour, minute] in self.times:
            return

        g.__manager.purchase_new_stocks()

    def __str__(self):
        return '实盘易申购新股[time: %s] ' % (self.times)

'''~~~~~~~~~~~~~~~~~~~~~~~~~~~基础函数~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'''


def is_3_black_crows(stock):
    # talib.CDL3BLACKCROWS

    # 三只乌鸦说明来自百度百科
    # 1. 连续出现三根阴线，每天的收盘价均低于上一日的收盘
    # 2. 三根阴线前一天的市场趋势应该为上涨
    # 3. 三根阴线必须为长的黑色实体，且长度应该大致相等
    # 4. 收盘价接近每日的最低价位
    # 5. 每日的开盘价都在上根K线的实体部分之内；
    # 6. 第一根阴线的实体部分，最好低于上日的最高价位
    #
    # 算法
    # 有效三只乌鸦描述众说纷纭，这里放宽条件，只考虑1和2
    # 根据前4日数据判断
    # 3根阴线跌幅超过4.5%（此条件忽略）

    h = attribute_history(
        stock, 4, '1d', ('close', 'open'),
        skip_paused=True, df=False)
    h_close = list(h['close'])
    h_open = list(h['open'])

    if len(h_close) < 4 or len(h_open) < 4:
        return False

    # 一阳三阴
    if h_close[-4] > h_open[-4] and (h_close[-1] < h_open[-1]
                                     and h_close[-2] < h_open[-2] and h_close[-3] < h_open[-3]):
        # and (h_close[-1] < h_close[-2] and h_close[-2] < h_close[-3]) \
        # and h_close[-1] / h_close[-4] - 1 < -0.045:
        return True
    return False


# 获取股票n日以来涨幅，根据当前价计算
# n 默认20日
def get_growth_rate(security, n=20):
    lc = get_close_price(security, n)
    # c = data[security].close
    c = get_close_price(security, 1, '1m')

    if not isnan(lc) and not isnan(c) and lc != 0:
        return (c - lc) / lc
    else:
        log.error(
            "数据非法, security: %s, %d日收盘价: %f, 当前价: %f" %
            (security, n, lc, c))
        return 0

# 获取前n个单位时间当时的收盘价


def get_close_price(security, n, unit='1d'):
    return attribute_history(security, n, unit, ('close'), True)['close'][0]


############################################################################
#
#                       策略方案
#
#   一、指数：
#       1、 创业板指（399006） 创业板股票（399006，剔除上市不满130日创业板股票）
#       2、 中小板指（399005） 小市值（流通市值小于100亿，与现有的小市值策略一致）
#       3、 沪深300（399300） 买入510310（回测时按这个ETF来做，但实际上可能要变成买所有的股票，这个成交量太小了）
#       4、 上证50（000016） 买入510050 
    
#   二、市值回撤：
#       1、 当日高点回撤2%，空仓2天
#       2、 3日高点回撤5%，空仓3天
#       3、 5日高点回撤8%，空仓3天 

#   三、仓位：（调仓时判断） 
#       1、3个指数向上，全仓
#       2、2个指数向上，7成仓
#       3、1个指数向上，3成仓
#       4、都向下，空仓
#       备注：指数向上指20日均线斜率>1.01 

#   四、选股:（调仓时判断）
#       1、 选择斜率最高的指数对于的股票标的
#       2、 流通按市值排序（小市值优先） 
#       3、仓位调整不是每天调整，等调整周期到的时候再调整。

############################################################################

import copy
import pandas as pd
import requests
from requests import Request
from six.moves.urllib.parse import urlencode
import datetime
try:
    import shipane_sdk
except:
    pass

'''选择规则组合成一个量化策略'''
def select_strategy(context):
    '''
    策略选择设置说明, 策略由以下步骤规则组合，组合而成:
    1.持仓股票的处理规则
    2.调仓条件判断规则
    3.Query选股规则 (可选)
    4.股票池过滤规则 (可选)
    5.调仓规则
    6.其它规则(如统计)

    不同的规则组合可能存在一定的顺序关系。
    二维指定具体规则配置，由 [0.是否启用，1.描述，2.规则实现类名，3.规则传递参数(dict)]] 组成。
    注：所有规则类都必需继承自Rule类或Rule类的子类
    '''

    # 规则配置list下标描述变量。提高可读性与未来添加更多规则配置。
    g.cs_enabled, g.cs_name, g.cs_memo, g.cs_class_name, g.cs_param = range(5)

    # 默认不开仓的标志为-1
    g.not_open_days = -1

    # 默认全仓
    g.position_scale = 1
    g.index_selected = ''
    g.index_selected_growth = 1
    # 每天最大净值
    g.max_portfolio_everyday = []
    
    period = 3  # 调仓频率
    
    # 配置 1.持仓股票的处理规则 (这里主要配置是否进行个股止损止盈)
    g.position_stock_config = [
        [False, '', '个股止损', Stop_loss_stocks, {
            'period': period  # 调仓频率，日
            }, ],
        [False, '', '个股止盈', Stop_profit_stocks,
            {'period': period,  # 调仓频率，日
             }]
    ]

    # 配置 2.调仓条件判断规则
    g.adjust_condition_config = [
        [True, '', '统计净值', Stat_portfolio, {}],
        [False, '', '指数最高低价比值止损', Stop_loss_by_price, {
            'index': '000001.XSHG',  # 使用的指数,默认 '000001.XSHG'
             'day_count': 160,  # 可选 取day_count天内的最高价，最低价。默认160
             'multiple': 2.2  # 可选 最高价为最低价的multiple倍时，触 发清仓
            }],
        [False, '', '指数三乌鸦止损', Stop_loss_by_3_black_crows, {
            'index': '000001.XSHG',  # 使用的指数,默认 '000001.XSHG'
             'dst_drop_minute_count': 60,  # 可选，在三乌鸦触发情况下，一天之内有多少分钟涨幅<0,则触发止损，默认60分钟
            }],

        [False, '', '28调仓择时', Index28_condition, {  # 该调仓条件可能会产生清仓行为
                'index2': '000016.XSHG',  # 大盘指数
                'index8': '399333.XSHE',  # 小盘指数
                'index_growth_rate': 0.01,  # 判定调仓的二八指数20日增幅
            }],

        [True, '', '调仓时间', Time_condition, {
                'times': [[14, 50]],
            }],
        [True, '', '调仓日计数器', Period_condition, {
                'period': period,  # 调仓频率,日
            }],
        [True, '', '指数选择器', Index_selection, {
                'index_1': '399006.XSHE', # 创业板指
                'index_2': '399005.XSHE', # 中小板指
                'index_3': '399300.XSHE', # 沪深 300
                'index_4': '000016.XSHG', # 上证 50
                'index_growth_rate': 0.01
            }],
        [True, '', '当天净值管理风控', Stop_loss_by_currentday_net_worth, {
            'check_days': 1,
            'back_percent': 2,
            }],
        [False, '', '三天净值管理风控', Stop_loss_by_last3day_net_worth, {
            'check_days': 3,
            'back_percent': 5,
            }],
        [False, '', '五天净值管理风控', Stop_loss_by_last5day_net_worth, {
            'check_days': 5,
            'back_percent': 8,
            }],

    ]

    # 配置 3.Query选股规则
    g.pick_stock_by_query_config = [
        [True, '', '选取小市值', Pick_small_cap, {}],
        [True, '', '斜率最高指数选股器', Pick_stock_by_index, {}],
        [False, '', '评分大于10', Pick_score_up, {}],
        [True, '', '过滤EPS', Filter_eps, {
            'eps_min': 0  # 最小EPS
            }],
        [True, '', '初选股票数量', Filter_limite, {
            'pick_stock_count': 100  # 备选股票数目
            }]
    ]

    # 配置 4.股票池过滤规则
    g.filter_stock_list_config = [
        [False, '_filter_gem_', '过滤创业板', Filter_gem, {}],
        [True, '', '过滤ST', Filter_st, {}],
        [True, '', '过滤停牌', Filter_paused_stock, {}],
        [True, '', '过滤涨停', Filter_limitup, {}],
        [True, '', '过滤跌停', Filter_limitdown, {}],
        [True, '', '过滤次新', Filter_new_stock, {'day_count': 130}],
        [False, '', '三日或五日均线向上', Filter_3or5_mean_up, {
            'day_count1': 3,
            'day_count2': 5}],
        [True, '', '股票评分', Filter_rank, {
            'rank_stock_count': 20  # 评分股数
            }],
        [True, '', '获取最终选股数', Filter_buy_count, {
            'buy_count': 20  # 最终入选股票数
            }],
    ]

    # 配置 5.调仓规则
    g.adjust_position_config = [
        [True, '', '卖出股票', Sell_stocks, {}],
        [True, '', '买入股票', Buy_stocks, {
            'buy_count': 20  # 最终买入股票数
            }]
    ]

    # 判断是否在模拟盘运行
    g.is_sim_trade = context.run_params.type == 'sim_trade'

    # 配置 6.其它规则
    g.other_config = [
        # 注意：Shipane_order 和 Shipane_sync_p 启用一个就行了。
        [False, 'Shipane_order_moni', '实盘易跟order下单', Shipane_order, {}],
        [False, '_shipane_moni_', '实盘易-对比持仓下单', Shipane_sync_p, {}],

        # 通过实盘易自动申购新股
        [False,'_Purchase_new_stocks_','实盘易申购新股',Purchase_new_stocks,{'times': [[9, 40]]}],

        [True, '', '统计', Stat, {}]
    ]


# 创建一个规则执行器，并初始化一些通用事件
def create_rule(class_type, name, params, memo):
    obj = class_type(params)
    obj.name = name
    obj.on_open_position = open_position  # 买股
    obj.on_close_position = close_position  # 卖股
    obj.on_clear_position = clear_position  # 清仓
    obj.on_get_obj_by_class_type = get_obj_by_class_type  # 通过类名得到类的实例
    obj.memo = memo
    return obj

# 根据规则配置创建规则执行器
def create_rules(config):
    # config里 0.是否启用，1.描述，2.规则实现类名，3.规则传递参数(dict)]
    return [create_rule(c[g.cs_class_name],c[g.cs_name],c[g.cs_param],c[g.cs_memo]) for c in config if c[g.cs_enabled]]

def initialize(context):
    log.info("==> initialize @ %s" % (str(context.current_dt)))
    try:
        set_commission(PerTrade(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
        set_benchmark('000300.XSHG')
        set_option('use_real_price', True)
        log.set_level('order', 'error')
    except:
        pass
    # 判定是否是模拟盘状态
    g.is_sim_trade = context.run_params.type == 'sim_trade'

    select_strategy(context)
    '''-----1.持仓股票的处理规则-----'''
    g.position_stock_rules = create_rules(g.position_stock_config)

    '''-----2.调仓条件判断规则-----'''
    g.adjust_condition_rules = create_rules(g.adjust_condition_config)

    '''-----3.Query选股规则-----'''
    g.pick_stock_by_query_rules = create_rules(g.pick_stock_by_query_config)

    '''-----4.股票池过滤规则-----'''
    g.filter_stock_list_rules = create_rules(g.filter_stock_list_config)

    '''-----5.调仓规则器-----'''
    g.adjust_position_rules = create_rules(g.adjust_position_config)

    '''-----6.其它规则-------'''
    g.other_rules = create_rules(g.other_config)

    # 把所有规则合并排重生成一个总的规则收录器。以方便各处共同调用的
    g.all_rules = list(set(g.position_stock_rules
                           + g.adjust_condition_rules
                           + g.pick_stock_by_query_rules
                           + g.filter_stock_list_rules
                           + g.adjust_position_rules
                           + g.other_rules
                           ))

    for rule in g.all_rules:
        rule.initialize(context)

    # 打印规则参数
    log_param()

# 按分钟回测


def handle_data(context, data):
    if g.not_open_days >= 0:
        return
    g.clear_by_index = False
    # 执行其它辅助规则
    for rule in g.other_rules:
        rule.handle_data(context, data)

    # 持仓股票动作的执行,目前为个股止损止盈
    for rule in g.position_stock_rules:
        rule.handle_data(context, data)

    # ----------这部分当前本策略其实并没有啥用，扩展用--------------
    # 这里执行选股器调仓器的handle_data主要是为了扩展某些选股方式可能需要提前处理数据。
    # 举例：动态获取黑名单，可以调仓前一段时间先执行。28小市值规则这里都是空动作。
    for rule in g.pick_stock_by_query_rules:
        rule.handle_data(context, data)

    for rule in g.filter_stock_list_rules:
        rule.handle_data(context, data)

    # 调仓器的分钟处理
    for rule in g.adjust_position_rules:
        rule.handle_data(context, data)
    # -----------------------------------------------------------

    # 判断是否满足调仓条件，所有规则以and 逻辑执行
    for rule in g.adjust_condition_rules:
        rule.handle_data(context, data)
        if not rule.can_adjust:
            #log.info(rule)
            return
    # ---------------------调仓--------------------------
    log.info("handle_data: ==> 满足条件进行调仓")
    # 调仓前预处理
    for rule in g.all_rules:
        rule.before_adjust_start(context, data)

    # Query 选股
    q = None
    for rule in g.pick_stock_by_query_rules:
        q = rule.filter(context, data, q)

    # 过滤股票列表
    stock_list = []
    if g.index_selected == '399300.XSHE':
        stock_list.append('510310.XSHG')
    elif g.index_selected == '000016.XSHG':
        stock_list.append('510050.XSHG')
    else:
        stock_list = list(get_fundamentals(q)['code']) if q is not None else []
    log.info("before filter: ", stock_list)
    for rule in g.filter_stock_list_rules:
        stock_list = rule.filter(context, data, stock_list)
        log.info("after filter: ", rule)
        #log.info("stock list: ", stock_list)

    log.info("handle_data: 选股后可买股票: %s" % (stock_list))

    # 执行调仓
    for rule in g.adjust_position_rules:
        rule.adjust(context, data, stock_list)

    # 调仓后处理
    for rule in g.all_rules:
        rule.after_adjust_end(context, data)
    # ----------------------------------------------------

# 开盘


def before_trading_start(context):
    log.info(
        "==========================================================================")
    for rule in g.all_rules:
        rule.before_trading_start(context)

# 收盘


def after_trading_end(context):
    for rule in g.all_rules:
        rule.after_trading_end(context)

    # 得到当前未完成订单
    orders = get_open_orders()
    for _order in orders.values():
        log.info("canceled uncompleted order: %s" % (_order.order_id))

# 进程启动(一天一次)


def process_initialize(context):
    # 这里Try一下，防止更新代码时出错。
    try:
        for rule in g.all_rules:
            rule.process_initialize(context)
    except:
        pass

# 这里示例进行模拟更改回测时，如何调整策略,基本通用代码。


def after_code_changed(context):
    try:
        g.all_rules
    except:
        print('原先非面向对象策略代码，重新初始化')
        initialize(context)
        return

    print('更新代码：')
    # 调整策略通用实例代码
    # 获取新策略
    select_strategy(context)
    # 按新策略顺序重整规则列表，如果对象之前存在，则移到新列表，不存在则新建。
    # 不管之前旧的规则列表是什么顺序，一率按新列表重新整理。

    def check_chang(rules, config):
        nl = []
        for c in config:
            # 按顺序循环处理新规则
            if not c[g.cs_enabled]:  # 不使用则跳过
                continue
            # 查找旧规则是否存在
            find_old = None
            for old_r in rules:
                if old_r.__class__ == c[
                        g.cs_class_name] and old_r.name == c[
                        g.cs_name]:
                    find_old = old_r
                    break
            if find_old is not None:
                # 旧规则存在则添加到新列表中,并调用规则的更新函数，更新参数。
                nl.append(find_old)
                find_old.update_params(context, c[g.cs_param])
            else:
                # 旧规则不存在，则创建并添加
                new_r = create_rule(
                    c[g.cs_class_name],
                    c[g.cs_name],
                    c[g.cs_param],
                    c[g.cs_memo])
                nl.append(new_r)
                # 调用初始化时该执行的函数
                rule.initialize(context)
        return nl

    # 重整所有规则
    g.position_stock_rules = check_chang(
        g.position_stock_rules, g.position_stock_config)
    g.adjust_condition_rules = check_chang(
        g.adjust_condition_rules, g.adjust_condition_config)
    g.pick_stock_by_query_rules = check_chang(
        g.pick_stock_by_query_rules,
     g.pick_stock_by_query_config)
    g.filter_stock_list_rules = check_chang(
        g.filter_stock_list_rules,
     g.filter_stock_list_config)
    g.adjust_position_rules = check_chang(
        g.adjust_position_rules, g.adjust_position_config)
    g.other_rules = check_chang(g.other_rules, g.other_config)

    # 重新生成所有规则的list
    g.all_rules = list(set(
            g.position_stock_rules
            + g.adjust_condition_rules
            + g.pick_stock_by_query_rules
            + g.filter_stock_list_rules
            + g.adjust_position_rules
            + g.other_rules
        ))
    log_param()

# 显示策略组成


def log_param():
    def get_rules_str(rules):
        return '\n'.join(['   %d.%s ' % (i + 1, str(r))
                          for i, r in enumerate(rules)]) + '\n'
    s = '\n---------------------策略一览：规则组合与参数----------------------------\n'
    s += '一、持仓股票的处理规则:\n' + get_rules_str(g.position_stock_rules)
    s += '二、调仓条件判断规则:\n' + get_rules_str(g.adjust_condition_rules)
    s += '三、Query选股规则:\n' + get_rules_str(g.pick_stock_by_query_rules)
    s += '四、股票池过滤规则:\n' + get_rules_str(g.filter_stock_list_rules)
    s += '五、调仓规则:\n' + get_rules_str(g.adjust_position_rules)
    s += '六、其它规则:\n' + get_rules_str(g.other_rules)
    s += '--------------------------------------------------------------------------'
    print(s)

''' ==============================持仓操作函数，共用================================'''
# 开仓，买入指定价值的证券
# 报单成功并成交（包括全部成交或部分成交，此时成交量大于0），返回True
# 报单失败或者报单成功但被取消（此时成交量等于0），返回False
# 报单成功，触发所有规则的when_buy_stock函数


def open_position(sender, security, value):
    order = order_target_value_(sender, security, value)
    if order is not None and order.filled > 0:
        for rule in g.all_rules:
            rule.when_buy_stock(security, order)
        return True
    return False

# 平仓，卖出指定持仓
# 平仓成功并全部成交，返回True
# 报单失败或者报单成功但被取消（此时成交量等于0），或者报单非全部成交，返回False
# 报单成功，触发所有规则的when_sell_stock函数


def close_position(sender, position, is_normal=True):
    security = position.security
    order = order_target_value_(sender, security, 0)  # 可能会因停牌失败
    if order is not None:
        if order.filled > 0:
            for rule in g.all_rules:
                rule.when_sell_stock(position, order, is_normal)
            return True
    return False

# 清空卖出所有持仓
# 清仓时，调用所有规则的 when_clear_position


def clear_position(sender, context):
    if context.portfolio.positions:
        sender.log_info("==> 清仓，卖出所有股票")
        for stock in context.portfolio.positions.keys():
            position = context.portfolio.positions[stock]
            close_position(sender, position, False)
    for rule in g.all_rules:
        rule.when_clear_position(context)

# 自定义下单
# 根据Joinquant文档，当前报单函数都是阻塞执行，报单函数（如order_target_value）返回即表示报单完成
# 报单成功返回报单（不代表一定会成交），否则返回None


def order_target_value_(sender, security, value):
    df = get_fundamentals(query(
        valuation, income
    ).filter(
        valuation.code == security
    ))

    if value == 0:
        #sender.log_info("Selling out %s" % (security))
        pass
    else:
        #sender.log_info("Order %s to value %f, Total market_cap: %f " % (security, value, df['market_cap']))
        pass

    # 如果股票停牌，创建报单会失败，order_target_value 返回None
    # 如果股票涨跌停，创建报单会成功，order_target_value 返回Order，但是报单会取消
    # 部成部撤的报单，聚宽状态是已撤，此时成交量>0，可通过成交量判断是否有成交
    return order_target_value(security, value)

# 通过类的类型得到已创建的对象实例


def get_obj_by_class_type(class_type):
    for rule in g.all_rules:
        if rule.__class__ == class_type:
            return rule
''' ==============================规则基类================================'''


class Rule(object):
    # 持仓操作的事件
    on_open_position = None  # 买股调用外部函数
    on_close_position = None  # 卖股调用外部函数
    on_clear_position = None  # 清仓调用外部函数
    on_get_obj_by_class_type = None  # 通过类的类型查找已创建的类的实例
    memo = ''  # 对象简要说明
    name = ''

    def __init__(self, params):
        pass

    def initialize(self, context):
        pass

    def handle_data(self, context, data):
        pass

    def before_trading_start(self, context):
        pass

    def after_trading_end(self, context):
        pass

    def process_initialize(self, context):
        pass

    def after_code_changed(self, context):
        pass
    # 卖出股票时调用的函数
    # is_normail正常规则卖出为True，止损卖出为False

    def when_sell_stock(self, position, order, is_normal):
        pass
    # 买入股票时调用的函数

    def when_buy_stock(self, stock, order):
        pass
    # 清仓时调用的函数

    def when_clear_position(self, context):
        pass
    # 调仓前调用

    def before_adjust_start(self, context, data):
        pass
    # 调仓后调用用

    def after_adjust_end(slef, context, data):
        pass
    # 更改参数

    def update_params(self, context, params):
        pass

    # 持仓操作事件的简单判断处理，方便使用。
    def open_position(self, security, value):
        if self.on_open_position is not None:
            return self.on_open_position(self, security, value)

    def close_position(self, position, is_normal=True):
        if self.on_close_position is not None:
            return self.on_close_position(self, position, is_normal=True)

    def clear_position(self, context):
        if self.on_clear_position is not None:
            self.on_clear_position(self, context)
    # 通过类的类型获取已创建的类的实例对象
    # 示例 obj = get_obj_by_class_type(Index28_condition)

    def get_obj_by_class_type(self, class_type):
        if self.on_get_obj_by_class_type is not None:
            return self.on_get_obj_by_class_type(class_type)
        else:
            return None
    # 为日志显示带上是哪个规则器输出的

    def log_info(self, msg):
        log.info('%s: %s' % (self.memo, msg))

    def log_warn(self, msg):
        log.warn('%s: %s' % (self.memo, msg))

    def log_debug(self, msg):
        log.debug('%s: %s' % (self.memo, msg))

    def log_error(self, msg):
        log.error('%s: %s' % (self.memo, msg))

'''==============================调仓条件判断器基类=============================='''


class Adjust_condition(Rule):
    # 返回能否进行调仓

    @property
    def can_adjust(self):
        return True

'''==============================选股 query过滤器基类=============================='''


class Filter_query(Rule):

    def filter(self, context, data, q):
        return None
'''==============================选股 stock_list过滤器基类=============================='''


class Filter_stock_list(Rule):

    def filter(self, context, data, stock_list):
        return None
'''==============================调仓的操作基类=============================='''


class Adjust_position(Rule):

    def adjust(self, context, data, buy_stocks):
        pass

'''-------------------------调仓时间控制器-----------------------'''
class Time_condition(Adjust_condition):

    def __init__(self, params):
        # 配置调仓时间（24小时分钟制）
        self.times = params.get('times', [])
        g.currentday_max_portfolio = 0;

    def update_params(self, context, params):
        self.times = params.get('times', self.times)
        pass

    @property
    def can_adjust(self):
        return self.t_can_adjust

    def handle_data(self, context, data):
        hour = context.current_dt.hour
        minute = context.current_dt.minute

        self.t_can_adjust = [hour, minute] in self.times
        pass

    def __str__(self):
        return '调仓时间控制器: [调仓时间: %s ]' % (
                str(['%d:%d' % (x[0], x[1]) for x in self.times]))



'''-------------------------指数选择器-----------------------'''
class Index_selection(Adjust_condition):

    def __init__(self, params):
        self.CYBZ = params.get('index_1', '399006.XSHE')
        self.ZXBZ = params.get('index_2', '399005.XSHE')
        self.HS300 = params.get('index_3', '399300.XSHE')
        self.SZ50 = params.get('index_4', '000016.XSHG')
        self.index_growth_rate = params.get('index_growth_rate', 0.01)
        self.t_can_adjust = True
        g.clear_by_index = False

    def update_params(self, context, params):
        self.CYBZ = params.get('index_1', '399006.XSHE')
        self.ZXBZ = params.get('index_2', '399005.XSHE')
        self.HS300 = params.get('index_3', '399300.XSHE')
        self.SZ50 = params.get('index_4', '000016.XSHG')
        self.index_growth_rate = params.get('index_growth_rate', 0.01)
        self.t_can_adjust = True
        g.clear_by_index = False
    @property
    def can_adjust(self):
        return self.t_can_adjust

    def handle_data(self, context, data):
        count = 0
        index_selected = None
        index_selected_growth = 0
        index_growth_dict = {}
        index_growth_dict[self.CYBZ] = get_growth_rate(self.CYBZ)
        index_growth_dict[self.ZXBZ] = get_growth_rate(self.ZXBZ)
        index_growth_dict[self.HS300] = get_growth_rate(self.HS300)
        index_growth_dict[self.SZ50] = get_growth_rate(self.SZ50)

        index_growth_dict_sorted = sorted(index_growth_dict.items(), key=lambda d:d[1], reverse = True)
        for (index, growth) in index_growth_dict_sorted:
            log.info("index: %s ===> growth: %f", index, growth)
            if growth > self.index_growth_rate:
                count += 1
        log.info("%d index growth over 1.01", count)
        g.clear_by_index = True
        if count > 0:
            g.index_selected = index_growth_dict_sorted[0][0]
            g.index_selected_growth = index_growth_dict_sorted[0][1]
            log.info("index_selected: %s ===> growth: %f", g.index_selected, g.index_selected_growth)

            if count == 1:
                g.position_scale = 0.3
                self.log_info("only one index growth over 1.01, so position scale: 0.3")
            elif count == 2:
                g.position_scale = 0.7
                self.log_info("only two indexes growth over 1.01, so position scale: 0.7")
            else:
                g.position_scale = 1
                self.log_info("above three indexes growth over 1.01, so position scale: 1")
            self.t_can_adjust = True
        else:
            log.info("all indexes go down, so not selected any one")
            self.clear_position(context)
            self.t_can_adjust = False
        g.clear_by_index = False
    def __str__(self):
        return '指数选择器, 仓位控制器'


'''-------------------------调仓日计数器-----------------------'''


class Period_condition(Adjust_condition):

    def __init__(self, params):
        # 调仓日计数器，单位：日
        self.period = params.get('period', 3)
        self.day_count = 0
        self.t_can_adjust = False

    def update_params(self, context, params):
        self.period = params.get('period', self.period)

    @property
    def can_adjust(self):
        return self.t_can_adjust

    def handle_data(self, context, data):
        self.log_info("调仓日计数 [%d]" % (self.day_count))
        self.t_can_adjust = self.day_count % self.period == 0
        self.day_count += 1
        pass

    def before_trading_start(self, context):
        self.t_can_adjust = False
        pass

    def when_sell_stock(self, position, order, is_normal):
        if not is_normal and g.clear_by_index != True:
            # 个股止损止盈时，即非正常卖股时，重置计数，原策略是这么写的
            self.log_info("sell stock day count: %d ---> 0" % self.day_count)
            self.day_count = 0
        pass
    # 清仓时调用的函数

    def when_clear_position(self, context):
        if g.clear_by_index != True:
            self.log_info("clear position day count: %d ---> 0" % self.day_count)
            self.day_count = 0
        pass

    def __str__(self):
        return '调仓日计数器:[调仓频率: %d日] [调仓日计数 %d]' % (
                self.period, self.day_count)
'''-------------------------28指数涨幅调仓判断器----------------------'''


class Index28_condition(Adjust_condition):

    def __init__(self, params):
        self.index2 = params.get('index2', '')
        self.index8 = params.get('index8', '')
        self.index_growth_rate = params.get('index_growth_rate', 0.01)
        self.t_can_adjust = False

    def update_params(self, context, params):
        self.index2 = params.get('index2', self.index2)
        self.index8 = params.get('index8', self.index8)
        self.index_growth_rate = params.get(
            'index_growth_rate', self.index_growth_rate)

    @property
    def can_adjust(self):
        return self.t_can_adjust

    def handle_data(self, context, data):
        # 回看指数前20天的涨幅
        gr_index2 = get_growth_rate(self.index2)
        gr_index8 = get_growth_rate(self.index8)
        self.log_info(
            "当前%s指数的20日涨幅 [%.2f%%]" %
            (get_security_info(
                self.index2).display_name,
                gr_index2 *
                100))
        self.log_info(
            "当前%s指数的20日涨幅 [%.2f%%]" %
            (get_security_info(
                self.index8).display_name,
                gr_index8 *
                100))
        if gr_index2 <= self.index_growth_rate and gr_index8 <= self.index_growth_rate:
            self.clear_position(context)
            self.t_can_adjust = False
        else:
            self.t_can_adjust = True
        pass

    def before_trading_start(self, context):
        pass

    def __str__(self):
        return '28指数择时:[大盘指数:%s %s] [小盘指数:%s %s] [判定调仓的二八指数20日增幅 %.2f%%]' % (
                self.index2, get_security_info(self.index2).display_name,
                self.index8, get_security_info(self.index8).display_name,
                self.index_growth_rate * 100)

'''------------------小市值选股器-----------------'''
class Pick_small_cap(Filter_query):

    def filter(self, context, data, q):
        return query(valuation).order_by(valuation.market_cap.asc())

    def __str__(self):
        return '按市值倒序选取股票'


'''------------------指数选股器-----------------'''
class Pick_stock_by_index(Filter_query):
    def filter(self, context, data, q):
        stock_list = []
        if g.index_selected != '':
            self.log_info("selected index: %s" % g.index_selected)
            if g.index_selected == '399300.XSHE':
                pass
            elif g.index_selected == '000016.XSHG':
                pass
            else:
                stock_list = get_index_stocks(g.index_selected)
            #self.log_info(stock_list)
            return query(valuation).filter(valuation.code.in_(stock_list)).order_by(valuation.market_cap.asc())
        else:
            self.log_info("no selected index")


    def __str__(self):
        return '按斜率最高指数选取股票'

# 以下为选评分高于10分的股票
class Pick_score_up(Filter_query):
    def filter(self, context, data, dst_stocks):
        dst_stocks = []
        stock_list = get_index_stocks(g.index_selected)
        stock_score = {}
        log.info("stock list : %d" % len(stock_list))
        for stock in stock_list:
            h = attribute_history(
                stock, 130, unit='1d', fields=(
                    'close', 'high', 'low'), skip_paused=True)
            if len(h) < 130:
                log.info("This stock is not over 130 days, is %d" % len(h))
                continue
            low_price_130 = h.low.min()
            high_price_130 = h.high.max()

            avg_15 = data[stock].mavg(15, field='close')
            cur_price = data[stock].close

            score = (
                cur_price - low_price_130) + (cur_price - high_price_130) + (cur_price - avg_15)
            if score >= 10:
                #log.info("stock: %s, score: %f", stock, score)
                stock_score[stock] = score

        stock_score_sort = sorted(stock_score.items(), key=lambda d:d[1], reverse = True)
        for (stock, score) in stock_score_sort:
            #log.info("stock: %s --> score: %f", stock, score)
            dst_stocks.append(stock)

        return query(valuation.code).filter(valuation.code.in_(dst_stocks))

    def __str__(self):
        return '股票评分'


class Filter_pe(Filter_query):

    def __init__(self, params):
        self.pe_min = params.get('pe_min', 0)
        self.pe_max = params.get('pe_max', 200)

    def update_params(self, context, params):
        self.pe_min = params.get('pe_min', self.pe_min)
        self.pe_max = params.get('pe_max', self.pe_max)

    def filter(self, context, data, q):
        return q.filter(
            valuation.pe_ratio > self.pe_min,
            valuation.pe_ratio < self.pe_max
            )

    def __str__(self):
        return '根据PE范围选取股票： [ %d < pe < %d]' % (self.pe_min, self.pe_max)


class Filter_eps(Filter_query):

    def __init__(self, params):
        self.eps_min = params.get('eps_min', 0)

    def update_params(self, context, params):
        self.eps_min = params.get('eps_min', self.eps_min)

    def filter(self, context, data, q):
        return q.filter(
            indicator.eps > self.eps_min,
            )

    def __str__(self):
        return '根据EPS范围选取股票： [ %d < eps ]' % (self.eps_min)


class Filter_limite(Filter_query):

    def __init__(self, params):
        self.pick_stock_count = params.get('pick_stock_count', 0)

    def update_params(self, context, params):
        self.pick_stock_count = params.get(
            'pick_stock_count', self.pick_stock_count)

    def filter(self, context, data, q):
        return q.limit(self.pick_stock_count)

    def __str__(self):
        return '初选股票数量: %d' % (self.pick_stock_count)


class Filter_gem(Filter_stock_list):

    def filter(self, context, data, stock_list):
        return [stock for stock in stock_list if stock[0:3] != '300']

    def __str__(self):
        return '过滤创业板股票'


class Filter_paused_stock(Filter_stock_list):

    def filter(self, context, data, stock_list):
        current_data = get_current_data()
        return [stock for stock in stock_list if not current_data[stock].paused]

    def __str__(self):
        return '过滤停牌股票'


class Filter_limitup(Filter_stock_list):

    def filter(self, context, data, stock_list):
        threshold = 1.00
        return [stock for stock in stock_list if data[stock].close < data[stock].high_limit * threshold]

    def __str__(self):
        return '过滤涨停股票'


class Filter_limitdown(Filter_stock_list):

    def filter(self, context, data, stock_list):
        threshold = 1.00
        if (len(context.portfolio.positions) == 0):
            return stock_list
        else:
            return [stock for stock in stock_list if data[stock].close > data[stock].low_limit * threshold]

    def __str__(self):
        return '过滤跌停股票'


class Filter_old_stock(Filter_stock_list):

    def __init__(self, params):
        self.day_count = params.get('day_count', 365)

    def update_params(self, context, params):
        self.day_count = params.get('day_count', self.day_count)

    def filter(self, context, data, stock_list):
        tmpList = []
        for stock in stock_list:
            days_public = (
                context.current_dt.date() -
                get_security_info(stock).start_date).days
            # 上市未超过1年
            if days_public < self.day_count:
                tmpList.append(stock)
        return tmpList

    def __str__(self):
        return '过滤上市时间超过 %d 天的股票' % (self.day_count)


class Filter_new_stock(Filter_stock_list):

    def __init__(self, params):
        self.day_count = params.get('day_count', 365)

    def update_params(self, context, params):
        self.day_count = params.get('day_count', self.day_count)

    def filter(self, context, data, stock_list):
        tmpList = []
        for stock in stock_list:
            days_public = (
                context.current_dt.date() -
                get_security_info(stock).start_date).days
            if days_public > self.day_count:
                tmpList.append(stock)
        return tmpList

    def __str__(self):
        return '过滤上市时间未超过 %d 天的次新股' % (self.day_count)


class Filter_st(Filter_stock_list):

    def filter(self, context, data, stock_list):
        current_data = get_current_data()
        return [stock for stock in stock_list
                if not current_data[stock].is_st
                and not current_data[stock].name.startswith('退')]

    def __str__(self):
        return '过滤ST股票'


'''------------------三天或五天均线上选股器-----------------'''
class Filter_3or5_mean_up(Filter_query):
    def __init__(self, params):
        self.day_count1 = params.get('day_count1', 3)
        self.day_count2 = params.get('day_count2', 5)

    def update_params(self, context, params):
        self.day_count1 = params.get('day_count1', self.day_count1)
        self.day_count2 = params.get('day_count2', self.day_count2)

    def filter(self, context, data, stock_list):
        return [stock for stock in stock_list if (get_growth_rate(stock, self.day_count1) > 0) or (get_growth_rate(stock, self.day_count2) > 0) ]

    def __str__(self):
        return '三天或五天均线向上选股器'

class Filter_blacklist(Filter_stock_list):

    def __get_blacklist(self):
        # 黑名单一览表，更新时间 2016.7.10 by 沙米
        # 科恒股份、太空板业，一旦2016年继续亏损，直接面临暂停上市风险
        blacklist = [
            "600656.XSHG",
            "300372.XSHE",
            "600403.XSHG",
            "600421.XSHG",
            "600733.XSHG",
            "300399.XSHE",
            "600145.XSHG",
            "002679.XSHE",
            "000020.XSHE",
            "002330.XSHE",
            "300117.XSHE",
            "300135.XSHE",
            "002566.XSHE",
            "002119.XSHE",
            "300208.XSHE",
            "002237.XSHE",
            "002608.XSHE",
            "000691.XSHE",
            "002694.XSHE",
            "002715.XSHE",
            "002211.XSHE",
            "000788.XSHE",
            "300380.XSHE",
            "300028.XSHE",
            "000668.XSHE",
            "300033.XSHE",
            "300126.XSHE",
            "300340.XSHE",
            "300344.XSHE",
            "002473.XSHE"]
        return blacklist

    def filter(self, context, data, stock_list):
        blacklist = self.__get_blacklist()
        return [stock for stock in stock_list if stock not in blacklist]

    def __str__(self):
        return '过滤黑名单股票'


class Filter_rank(Filter_stock_list):

    def __init__(self, params):
        self.rank_stock_count = params.get('rank_stock_count', 20)

    def update_params(self, context, params):
        self.rank_stock_count = params.get(
            'self.rank_stock_count', self.rank_stock_count)

    def filter(self, context, data, stock_list):
        if len(stock_list) == 0:
            return stock_list
        if len(stock_list) > self.rank_stock_count:
            stock_list = stock_list[:self.rank_stock_count]

        dst_stocks = {}
        for stock in stock_list:
            h = attribute_history(
                stock, 130, unit='1d', fields=(
                    'close', 'high', 'low'), skip_paused=True)
            low_price_130 = h.low.min()
            high_price_130 = h.high.max()

            avg_15 = data[stock].mavg(15, field='close')
            cur_price = data[stock].close

            score = (
                cur_price - low_price_130) + (cur_price - high_price_130) + (cur_price - avg_15)
            dst_stocks[stock] = score

        df = pd.DataFrame(dst_stocks.values(), index=dst_stocks.keys())
        df.columns = ['score']
        df = df.sort(columns='score', ascending=True)
        return list(df.index)

    def __str__(self):
        return '股票评分排序 [评分股数: %d ]' % (self.rank_stock_count)


class Filter_buy_count(Filter_stock_list):

    def __init__(self, params):
        self.buy_count = params.get('buy_count', 3)

    def update_params(self, context, params):
        self.buy_count = params.get('buy_count', self.buy_count)

    def filter(self, context, data, stock_list):
        if len(stock_list) > self.buy_count:
            return stock_list[:self.buy_count]
        else:
            return stock_list

    def __str__(self):
        return '获取最终待购买股票数:[ %d ]' % (self.buy_count)

'''---------------卖出股票规则--------------'''


class Sell_stocks(Adjust_position):

    def adjust(self, context, data, buy_stocks):
        # 卖出不在待买股票列表中的股票
        # 对于因停牌等原因没有卖出的股票则继续持有
        
        for stock in context.portfolio.positions.keys():
            if stock not in buy_stocks:
                self.log_info("stock [%s] in position is not buyable" % (stock))
                position = context.portfolio.positions[stock]
                self.close_position(position)
            else:
                self.log_info("stock [%s] is already in position" % (stock))

    def __str__(self):
        return '股票调仓卖出规则：卖出不在buy_stocks的股票'

'''---------------买入股票规则--------------'''


class Buy_stocks(Adjust_position):

    def __init__(self, params):
        self.buy_count = params.get('buy_count', 3)

    def update_params(self, context, params):
        self.buy_count = params.get('buy_count', self.buy_count)

    def adjust(self, context, data, buy_stocks):
        # 买入股票
        # 始终保持持仓数目为g.buy_stock_count
        # 根据股票数量分仓
        # 此处只根据可用金额平均分配购买，不能保证每个仓位平均分配
        position_count = len(context.portfolio.positions)
        if g.index_selected == '399300.XSHE' or g.index_selected == '000016.XSHG':
            value = context.portfolio.cash * g.position_scale
            for stock in buy_stocks:
                if context.portfolio.positions[stock].total_amount == 0:
                    if self.open_position(stock, value):
                        if len(context.portfolio.positions) == self.buy_count:
                            break
        elif self.buy_count > position_count:
            value = context.portfolio.cash * g.position_scale / (self.buy_count - position_count)
            for stock in buy_stocks:
                if context.portfolio.positions[stock].total_amount == 0:
                    if self.open_position(stock, value):
                        if len(context.portfolio.positions) == self.buy_count:
                            break
        pass

    def __str__(self):
        return '股票调仓买入规则：现金平分式买入股票达目标股票数'

'''---------------个股止损--------------'''


class Stop_loss_stocks(Rule):
    # get_period_func 为获取period的函数,无传入参数，传出参数为period
    # on_close_position_func 卖出股票时触发的事件，传入参数为 stock,无返回

    def __init__(self, params):
        self.last_high = {}
        self.period = params.get('period', 3)
        self.pct_change = {}

    def update_params(self, context, params):
        self.period = params.get('period', self.period)
    # 个股止损

    def handle_data(self, context, data):
        for stock in context.portfolio.positions.keys():
            cur_price = data[stock].close
            xi = attribute_history(stock, 2, '1d', 'high', skip_paused=True)
            ma = xi.max()
            if self.last_high[stock] < cur_price:
                self.last_high[stock] = cur_price

            threshold = self.__get_stop_loss_threshold(stock, self.period)
            # log.debug("个股止损阈值, stock: %s, threshold: %f" %(stock, threshold))
            if cur_price < self.last_high[stock] * (1 - threshold):
                self.log_info(
                    "==> 个股止损, stock: %s, cur_price: %f, last_high: %f, threshold: %f" %
                    (stock, cur_price, self.last_high[stock], threshold))

                position = context.portfolio.positions[stock]
                self.close_position(position, False)

    # 获取个股前n天的m日增幅值序列
    # 增加缓存避免当日多次获取数据
    def __get_pct_change(self, security, n, m):
        pct_change = None
        if security in self.pct_change.keys():
            pct_change = self.pct_change[security]
        else:
            h = attribute_history(
                security,
                n,
                unit='1d',
                fields=('close'),
                skip_paused=True)
            pct_change = h['close'].pct_change(m)  # 3日的百分比变比（即3日涨跌幅）
            self.pct_change[security] = pct_change
        return pct_change

    # 计算个股回撤止损阈值
    # 即个股在持仓n天内能承受的最大跌幅
    # 算法：(个股250天内最大的n日跌幅 + 个股250天内平均的n日跌幅)/2
    # 返回正值
    def __get_stop_loss_threshold(self, security, n=3):
        pct_change = self.__get_pct_change(security, 250, n)
        # log.debug("pct of security [%s]: %s", pct)
        maxd = pct_change.min()
        # maxd = pct[pct<0].min()
        avgd = pct_change.mean()
        # avgd = pct[pct<0].mean()
        # maxd和avgd可能为正，表示这段时间内一直在增长，比如新股
        bstd = (maxd + avgd) / 2

        # 数据不足时，计算的bstd为nan
        if not isnan(bstd):
            if bstd != 0:
                return abs(bstd)
            else:
                # bstd = 0，则 maxd <= 0
                if maxd < 0:
                    # 此时取最大跌幅
                    return abs(maxd)

        return 0.099  # 默认配置回测止损阈值最大跌幅为-9.9%，阈值高貌似回撤降低

    def when_sell_stock(self, position, order, is_normal):
        if position.security in self.last_high:
            self.last_high.pop(position.security)
        pass

    def when_buy_stock(self, stock, order):
        if order.status == OrderStatus.held and order.filled == order.amount:
            # 全部成交则删除相关证券的最高价缓存
            self.last_high[stock] = get_close_price(stock, 1, '1m')
        pass

    def after_trading_end(self, context):
        self.pct_change = {}
        pass

    def __str__(self):
        return '个股止损器:[当前缓存价格数: %d ]' % (len(self.last_high))

''' ----------------------个股止盈------------------------------'''


class Stop_profit_stocks(Rule):

    def __init__(self, params):
        self.last_high = {}
        self.period = params.get('period', 3)
        self.pct_change = {}

    def update_params(self, context, params):
        self.period = params.get('period', self.period)
    # 个股止盈

    def handle_data(self, context, data):
        for stock in context.portfolio.positions.keys():
            position = context.portfolio.positions[stock]
            cur_price = data[stock].close
            threshold = self.__get_stop_profit_threshold(stock, self.period)
            # log.debug("个股止盈阈值, stock: %s, threshold: %f" %(stock, threshold))
            if cur_price > position.avg_cost * (1 + threshold):
                self.log_info(
                    "==> 个股止盈, stock: %s, cur_price: %f, avg_cost: %f, threshold: %f" %
                    (stock, cur_price, self.last_high[stock], threshold))

                position = context.portfolio.positions[stock]
                self.close_position(position, False)

    # 获取个股前n天的m日增幅值序列
    # 增加缓存避免当日多次获取数据
    def __get_pct_change(self, security, n, m):
        pct_change = None
        if security in self.pct_change.keys():
            pct_change = self.pct_change[security]
        else:
            h = attribute_history(
                security,
                n,
                unit='1d',
                fields=('close'),
                skip_paused=True)
            pct_change = h['close'].pct_change(m)  # 3日的百分比变比（即3日涨跌幅）
            self.pct_change[security] = pct_change
        return pct_change

    # 计算个股止盈阈值
    # 算法：个股250天内最大的n日涨幅
    # 返回正值
    def __get_stop_profit_threshold(self, security, n=3):
        pct_change = self.__get_pct_change(security, 250, n)
        maxr = pct_change.max()

        # 数据不足时，计算的maxr为nan
        # 理论上maxr可能为负
        if (not isnan(maxr)) and maxr != 0:
            return abs(maxr)
        return 0.30  # 默认配置止盈阈值最大涨幅为30%

    def when_sell_stock(self, position, order, is_normal):
        if order.status == OrderStatus.held and order.filled == order.amount:
            # 全部成交则删除相关证券的最高价缓存
            if position.security in self.last_high:
                self.last_high.pop(position.security)
        pass

    def when_buy_stock(self, stock, order):
        self.last_high[stock] = get_close_price(stock, 1, '1m')
        pass

    def after_trading_end(self, context):
        self.pct_change = {}
        pass

    def __str__(self):
        return '个股止盈器:[当前缓存价格数: %d ]' % (len(self.last_high))

''' ----------------------最高价最低价比例止损------------------------------'''


class Stop_loss_by_price(Adjust_condition):

    def __init__(self, params):
        self.index = params.get('index', '000001.XSHG')
        self.day_count = params.get('day_count', 160)
        self.multiple = params.get('multiple', 2.2)
        self.is_day_stop_loss_by_price = False

    def update_params(self, context, params):
        self.index = params.get('index', self.index)
        self.day_count = params.get('day_count', self.day_count)
        self.multiple = params.get('multiple', self.multiple)

    def handle_data(self, context, data):
        # 大盘指数前130日内最高价超过最低价2倍，则清仓止损
        # 基于历史数据判定，因此若状态满足，则当天都不会变化
        # 增加此止损，回撤降低，收益降低

        if not self.is_day_stop_loss_by_price:
            h = attribute_history(
                self.index, self.day_count, unit='1d', fields=(
                    'close', 'high', 'low'), skip_paused=True)
            low_price_130 = h.low.min()
            high_price_130 = h.high.max()
            if high_price_130 > self.multiple * low_price_130 and h['close'][-1] < h[
                    'close'][-4] * 1 and h['close'][-1] > h['close'][-100]:
                # 当日第一次输出日志
                self.log_info(
                    "==> 大盘止损，%s指数前130日内最高价超过最低价2倍, 最高价: %f, 最低价: %f" %
                    (get_security_info(
                        self.index).display_name,
                        high_price_130,
                        low_price_130))
                self.is_day_stop_loss_by_price = True

        if self.is_day_stop_loss_by_price:
            self.clear_position(context)

    def before_trading_start(self, context):
        self.is_day_stop_loss_by_price = False
        pass

    def __str__(self):
        return '大盘高低价比例止损器:[指数: %s] [参数: %s日内最高最低价: %s倍] [当前状态: %s]' % (
                self.index, self.day_count, self.multiple, self.is_day_stop_loss_by_price)

    @property
    def can_adjust(self):
        return not self.is_day_stop_loss_by_price

''' ----------------------最高价最低价比例止损------------------------------'''


class Stop_loss_by_growth_rate(Adjust_condition):

    def __init__(self, params):
        self.index = params.get('index', '000001.XSHG')
        self.stop_loss_growth_rate = params.get('stop_loss_growth_rate', -0.03)
        self.to_stop_loss = False

    def update_params(self, context, params):
        self.index = params.get('index', '000001.XSHG')
        self.stop_loss_growth_rate = params.get('stop_loss_growth_rate', -0.03)
        self.to_stop_loss = False

    def handle_data(self, context, data):
        if self.to_stop_loss:
            return
        cur_growth_rate = get_growth_rate(self.index, 1)
        if cur_growth_rate < self.stop_loss_growth_rate:
            self.log_warn(
                '当日涨幅 [%s : %.2f%%] 低于阀值 %.2f%%,清仓止损!' %
                (self.index, cur_growth_rate * 100, self.stop_loss_growth_rate))
            self.to_stop_loss = True
            self.clear_position(context)
            return
        self.to_stop_loss = False

    def before_trading_start(self, context):
        self.to_stop_loss = False

    def __str__(self):
        return '指数当日涨幅限制止损器:[指数: %s] [最低涨幅: %.2f%%]' % (
                self.index, self.stop_loss_growth_rate * 100)

    @property
    def can_adjust(self):
        return not self.to_stop_loss

''' ----------------------三乌鸦止损------------------------------'''
class Stop_loss_by_3_black_crows(Adjust_condition):

    def __init__(self, params):
        self.index = params.get('index', '000001.XSHG')
        self.dst_drop_minute_count = params.get('dst_drop_minute_count', 60)
        # 临时参数
        self.is_last_day_3_black_crows = False
        self.t_can_adjust = True
        self.cur_drop_minute_count = 0

    def update_params(self, context, params):
        self.index = params.get('index', self.index)
        self.dst_drop_minute_count = params.get(
            'dst_drop_minute_count', self.dst_drop_minute_count)

    def initialize(self, context):
        pass

    def handle_data(self, context, data):
        # 前日三黑鸦，累计当日每分钟涨幅<0的分钟计数
        # 如果分钟计数超过一定值，则开始进行三黑鸦止损
        # 避免无效三黑鸦乱止损
        if self.is_last_day_3_black_crows:
            if get_growth_rate(self.index, 1) < 0:
                self.cur_drop_minute_count += 1

            if self.cur_drop_minute_count >= self.dst_drop_minute_count:
                if self.cur_drop_minute_count == self.dst_drop_minute_count:
                    self.log_info("==> 超过三黑鸦止损开始")

                self.clear_position(context)
                self.t_can_adjust = False
        else:
            self.t_can_adjust = True
        pass

    def before_trading_start(self, context):
        self.is_last_day_3_black_crows = is_3_black_crows(self.index)
        if self.is_last_day_3_black_crows:
            self.log_info("==> 前4日已经构成三黑鸦形态")
        pass

    def after_trading_end(self, context):
        self.is_last_day_3_black_crows = False
        self.cur_drop_minute_count = 0
        pass

    def __str__(self):
        return '大盘三乌鸦止损器:[指数: %s] [跌计数分钟: %d] [当前状态: %s]' % (
            self.index, self.dst_drop_minute_count, self.is_last_day_3_black_crows)

    @property
    def can_adjust(self):
        return self.t_can_adjust

''' ----------------------净值止损------------------------------'''
class  Stat_portfolio(Adjust_condition):

    def handle_data(self, context, data):
        if context.portfolio.total_value > g.currentday_max_portfolio:
            g.currentday_max_portfolio = context.portfolio.total_value

    def after_trading_end(self, context):
        g.max_portfolio_everyday.append(g.currentday_max_portfolio)
        g.currentday_max_portfolio = 0
    def __str__(self):
        return '净值统计'



''' ----------------------净值止损------------------------------'''
class Stop_loss_by_last3day_net_worth(Adjust_condition):
    pass
class Stop_loss_by_last5day_net_worth(Adjust_condition):
    pass

class Stop_loss_by_currentday_net_worth(Adjust_condition):

    def __init__(self, params):
        self.check_days = params.get('check_days', 1)
        self.back_percent = params.get('back_percent', 2)
        self.t_can_adjust = True


    def update_params(self, context, params):
        self.check_days = params.get('check_days', self.check_days)
        self.back_percent = params.get('back_percent', self.back_percent)

    def initialize(self, context):
        g.currentday_max_portfolio = 0
        g.record = 0;
        g.not_open_days = -1
        pass

    def handle_data(self, context, data):
        if context.portfolio.total_value <= (1.0 - int(self.back_percent) / 100.0) * g.currentday_max_portfolio:
            log.warn('currentday_max_portfolio: %f, current_portfolio: %f', g.currentday_max_portfolio, context.portfolio.total_value)
            g.not_open_days = 2
            self.t_can_adjust = False
            self.clear_position(context)

    def before_trading_start(self, context):
        pass

    def after_trading_end(self, context):
        if g.not_open_days >= 0:
            g.not_open_days -= 1
        else:
            self.t_can_adjust = True
        log.info("after trade open day: %d", g.not_open_days)

    def __str__(self):
        return '当天净值止损器: [回撤比例: %f]' % (self.back_percent)

    @property
    def can_adjust(self):
        return self.t_can_adjust


''' ----------------------28指数值实时进行止损------------------------------'''


class Stop_loss_by_28_index(Adjust_condition):

    def __init__(self, params):
        self.index2 = params.get('index2', '')
        self.index8 = params.get('index8', '')
        self.index_growth_rate = params.get('index_growth_rate', 0.01)
        self.dst_minute_count_28index_drop = params.get(
            'dst_minute_count_28index_drop', 120)
        # 临时参数
        self.t_can_adjust = True
        self.minute_count_28index_drop = 0

    def update_params(self, context, params):
        self.index2 = params.get('index2', self.index2)
        self.index8 = params.get('index8', self.index8)
        self.index_growth_rate = params.get(
            'index_growth_rate', self.index_growth_rate)
        self.dst_minute_count_28index_drop = params.get(
            'dst_minute_count_28index_drop', self.dst_minute_count_28index_drop)

    def initialize(self, context):
        pass

    def handle_data(self, context, data):
        # 回看指数前20天的涨幅
        gr_index2 = get_growth_rate(self.index2)
        gr_index8 = get_growth_rate(self.index8)

        if gr_index2 <= self.index_growth_rate and gr_index8 <= self.index_growth_rate:
            if (self.minute_count_28index_drop == 0):
                self.log_info(
                    "当前二八指数的20日涨幅同时低于[%.2f%%], %s指数: [%.2f%%], %s指数: [%.2f%%]" %
                    (self.index_growth_rate *
                     100,
                     get_security_info(
                         self.index2).display_name,
                        gr_index2 *
                        100,
                        get_security_info(
                         self.index8).display_name,
                        gr_index8 *
                        100))

            self.minute_count_28index_drop += 1
        else:
            # 不连续状态归零
            if self.minute_count_28index_drop < self.dst_minute_count_28index_drop:
                self.minute_count_28index_drop = 0

        if self.minute_count_28index_drop >= self.dst_minute_count_28index_drop:
            if self.minute_count_28index_drop == self.dst_minute_count_28index_drop:
                self.log_info(
                    "==> 当日%s指数和%s指数的20日增幅低于[%.2f%%]已超过%d分钟，执行28指数止损" %
                    (get_security_info(
                        self.index2).display_name,
                        get_security_info(
                        self.index8).display_name,
                        self.index_growth_rate *
                        100,
                        self.dst_minute_count_28index_drop))

            self.clear_position(context)
            self.t_can_adjust = False
        else:
            self.t_can_adjust = True
        pass

    def after_trading_end(self, context):
        self.t_can_adjust = False
        self.minute_count_28index_drop = 0
        pass

    def __str__(self):
        return '28指数值实时进行止损:[大盘指数: %s %s] [小盘指数: %s %s] [判定调仓的二八指数20日增幅 %.2f%%] [连续 %d 分钟则清仓] ' % (
                self.index2, get_security_info(self.index2).display_name,
                self.index8, get_security_info(self.index8).display_name,
                self.index_growth_rate * 100,
                self.dst_minute_count_28index_drop)

    @property
    def can_adjust(self):
        return self.t_can_adjust

''' ----------------------统计类----------------------------'''


class Stat(Rule):

    def __init__(self, params):
        # 加载统计模块
        self.trade_total_count = 0
        self.trade_success_count = 0
        self.statis = {'win': [], 'loss': []}

    def after_trading_end(self, context):
        self.report(context)

    def when_sell_stock(self, position, order, is_normal):
        if order.filled > 0:
            # 只要有成交，无论全部成交还是部分成交，则统计盈亏
            self.watch(
                position.security,
                order.filled,
                position.avg_cost,
                position.price)

    def reset(self):
        self.trade_total_count = 0
        self.trade_success_count = 0
        self.statis = {'win': [], 'loss': []}

    # 记录交易次数便于统计胜率
    # 卖出成功后针对卖出的量进行盈亏统计
    def watch(self, stock, sold_amount, avg_cost, cur_price):
        self.trade_total_count += 1
        current_value = sold_amount * cur_price
        cost = sold_amount * avg_cost

        percent = round((current_value - cost) / cost * 100, 2)
        if current_value > cost:
            self.trade_success_count += 1
            win = [stock, percent]
            self.statis['win'].append(win)
        else:
            loss = [stock, percent]
            self.statis['loss'].append(loss)

    def report(self, context):
        cash = context.portfolio.cash
        totol_value = context.portfolio.portfolio_value
        position = 1 - cash / totol_value
        self.log_info("收盘后持仓概况:%s" % str(list(context.portfolio.positions)))
        self.log_info("仓位概况:%.2f" % position)
        self.print_win_rate(
            context.current_dt.strftime("%Y-%m-%d"),
            context.current_dt.strftime("%Y-%m-%d"),
            context)

    # 打印胜率
    def print_win_rate(self, current_date, print_date, context):
        if str(current_date) == str(print_date):
            win_rate = 0
            if 0 < self.trade_total_count and 0 < self.trade_success_count:
                win_rate = round(
                    self.trade_success_count /
                    float(
                        self.trade_total_count),
                    3)

            most_win = self.statis_most_win_percent()
            most_loss = self.statis_most_loss_percent()
            starting_cash = context.portfolio.starting_cash
            total_profit = self.statis_total_profit(context)
            if len(most_win) == 0 or len(most_loss) == 0:
                return

            s = '\n------------绩效报表------------'
            s += '\n交易次数: {0}, 盈利次数: {1}, 胜率: {2}'.format(
                self.trade_total_count, self.trade_success_count,
                str(win_rate * 100) + str('%'))
            s += '\n单次盈利最高: {0}, 盈利比例: {1}%'.format(
                most_win['stock'], most_win['value'])
            s += '\n单次亏损最高: {0}, 亏损比例: {1}%'.format(
                most_loss['stock'], most_loss['value'])
            s += '\n总资产: {0}, 本金: {1}, 盈利: {2}, 盈亏比率：{3}%'.format(
                starting_cash + total_profit, starting_cash, total_profit,
                total_profit / starting_cash * 100)
            s += '\n--------------------------------'
            self.log_info(s)
            #self.log_info('每日最大净值：')
            #self.log_info(g.max_portfolio_everyday)
    # 统计单次盈利最高的股票
    def statis_most_win_percent(self):
        result = {}
        for statis in self.statis['win']:
            if {} == result:
                result['stock'] = statis[0]
                result['value'] = statis[1]
            else:
                if statis[1] > result['value']:
                    result['stock'] = statis[0]
                    result['value'] = statis[1]

        return result

    # 统计单次亏损最高的股票
    def statis_most_loss_percent(self):
        result = {}
        for statis in self.statis['loss']:
            if {} == result:
                result['stock'] = statis[0]
                result['value'] = statis[1]
            else:
                if statis[1] < result['value']:
                    result['stock'] = statis[0]
                    result['value'] = statis[1]

        return result

    # 统计总盈利金额
    def statis_total_profit(self, context):
        return context.portfolio.portfolio_value - context.portfolio.starting_cash

    def __str__(self):
        return '策略绩效统计'

'''-------------------实盘易对接-----------------------'''


class Shipane_sync_p(Rule):

    def __init__(self, params):
        self.host = params.get('host', '')
        self.port = params.get('port', 8888)
        self.key = params.get('key', '')
        self._client_param = params.get('client', '')
        self.strong_op = params.get('strong_op', True)

    def update_params(self, context, params):
        self.host = params.get('host', '')
        self.port = params.get('port', 8888)
        self.key = params.get('key', '')
        self._client_param = params.get('client', '')
        self.strong_op = params.get('strong_op', True)

    # 调仓后调用
    def after_adjust_end(self, context, data):
        self.__sync_position(context)
    # 清仓时调用的函数

    def when_clear_position(self, context):
        self.__sync_position(context)

    def __sync_position(self, context):
        try:
            client = shipane_sdk.Client(
                shipane_sdk._Logger(),
                key=self.key,
                host=self.host,
                port=self.port)
            op_count = 2 if self.strong_op else 1
            # 强力买卖条件下,卖两次股,防止五档都没卖掉,扫两次五档没卖完就算了吧
            for i in range(op_count):
                self.__sell(client, context)
            # 强力买卖条件下,买股,买两遍，第一遍为买，第二遍为检查，防止部分五档扫完还没买够的
            for i in range(op_count):
                self.__buy(client, context)
            pass
        except Exception as e:
            send_message('实盘易操作异常！！请检查!<br>' + str(e), channel='weixin')
            self.log_warn('实盘易操作异常！！请检查!' + str(e))

    # 卖股
    def __sell(self, client, context):
        sp = self.__get_shipan_p(client)
        mp = self.__get_moni_p(context)
        op_list = self.__get_dif(mp, sp)
        self.log_info('持仓差异:' + str(op_list))
        for x in op_list:
            if x[1] > 0:
                continue
            try:
                actual_order = client.execute(
                    self._client_param, action='SELL', symbol=x[0],
                    type='MARKET', priceType=4, amount=abs(x[1]))
            except Exception as e:
                self.log_warn("[实盘易] 卖单异常 [%s : %d]：%s" % (x[0], x[1], str(e)))

    # 买股
    def __buy(self, client, context):
        # 获取实盘总持仓
        sp = self.__get_shipan_p(client)
        # 获取模拟盘持仓
        mp = self.__get_moni_p(context)
        # 通过对比持仓获得要操作的股票及数量
        op_list = self.__get_dif(mp, sp)
        self.log_info('持仓差异:' + str(op_list))
        # 买股
        for x in op_list:
            if x[1] < 0:
                continue
            stock = x[0]
            buy_count = abs(x[1])
            try:
                # 买单时需要获取实际资金，以涨停价计算最大挂单数买入。
                # 最后一单经常需要拆成两单买入。还有误差就不管了。
                data = get_current_data()
                max_price = data[stock].high_limit
                for i in range(2):
                    # 获取实盘可用资金
                    cash = self.__get_shipan_cash(client)
                    if cash < 0:
                        send_message('实盘易获取实盘可用资金失败，请检查！', channel='weixin')
                        self.log_warn('实盘易获取实盘可用资金失败，请检查')
                        return

                    # 计算当前可用资金以涨停价挂单的最大挂单数
                    max_count = int(int(cash * 1.0 / max_price / 100) * 100)
                    self.log_info(
                        '%d 次买计算:[stock : %s][max_price:%f] [cash: %f] [max_count:%d] [aim:%d]' %
                        (i + 1, stock, max_price, cash, max_count, buy_count))
                    if max_count <= 0:
                        break
                    if max_count >= buy_count:
                        # 可用资金足够，一次性买入。
                        actual_order = client.execute(
                            self._client_param, action='BUY', symbol=stock,
                            type='MARKET', priceType=4, amount=buy_count)
                        break
                    else:
                        # 资金不足，分次买入。
                        actual_order = client.execute(
                            self._client_param, action='BUY', symbol=stock,
                            type='MARKET', priceType=4, amount=max_count)
                        buy_count -= max_count
            except Exception as e:
                self.log_warn(
                    "[实盘易] 买单异常 [%s : %d]：%s" %
                    (stock, buy_count, str(e)))

    # 获取实盘现金
    def __get_shipan_cash(self, client):
        r = None
        # 重复三次获取，防止偶然性网络错误
        for i in range(3):
            try:
                r = client.get_positions(self._client_param)
                break
            except:
                pass
        if r is None:
            return -1

        try:
            cash = float(r['sub_accounts'][u'可用'])
        except:
            cash = -1
        return cash

    # 获取实盘持仓
    def __get_shipan_p(self, client):
        r = None
        e1 = None
        # 重复三次获取，防止偶然性网络错误
        for i in range(3):
            try:
                r = client.get_positions(self._client_param)
                break
            except Exception as e:
                e1 = e
                pass
        if r is None:
            # 重抛异常
            if e1 is not None:
                raise e1
            return
        positions = r.get('positions', None)
        sp = zip(positions[u'证券代码'], positions[u'证券数量'])
        sp = [[normalize_code(x[0]).encode('utf-8'), int(float(x[1]))]
              for x in sp if x[0] != '' and x[1] != '']
        return sp

    # 获取模拟盘持仓
    def __get_moni_p(self, context):
        result = []
        total_values = context.portfolio.positions_value + context.portfolio.cash
        for stock in context.portfolio.positions.keys():
            position = context.portfolio.positions[stock]
            if position.total_amount == 0:
                continue
            result.append([stock, position.total_amount])

        return result

    # 获取两持仓之差 mp为模拟盘，sp为实盘。
    def __get_dif(self, mp, sp):
        sp = [[x[0], -x[1]] for x in sp]
        op_list = mp + sp
        # 取两个列表之差
        s_list = list(set([x[0] for x in op_list]))
        s_list = [[s, sum([x[1] for x in op_list if x[0] == s])]
                  for s in s_list]
        # s_list = [x for x in s_list if x[1] >= 100 or x[1] <= -100]
        # s_list += [x for x in sp if x[1] > -100 and x[1] < 0]
        new_l = []
        for s in s_list:
            if s[1] % 100 == 0:
                new_l.append(s)
                continue
            if s[1] < 0:
                # 卖股
                # 取模拟盘的持仓量
                t = [x[1] for x in mp if x[0] == s[0]]
                if len(t) == 0 or t[0] == 0:
                    # 如果模拟盘已清仓该股，则实盘全卖
                    new_l.append(s)
                    continue
                # 对股票取整
                n = int(round(s[1] * 1.0 / 100) * 100)
                new_l.append([s[0], n])
            else:
                # 买股直接四舍五入取整
                n = int(round(s[1] * 1.0 / 100) * 100)
                new_l.append([s[0], n])
        new_l = [x for x in new_l if x[1] != 0]
        new_l = sorted(new_l, key=lambda x: x[1])
        return new_l

    def __str__(self):
        return '实盘易对接券商'

'''-----------------根据聚宽Order用实盘易下单------------------'''


class Shipane_order(Rule):
    def __init__(self,params):
        pass
    def update_params(self,context,params):
        g.__manager = shipane_sdk.StrategyManager(context, 'manager-1')
    def initialize(self,context):
        g.__manager = shipane_sdk.StrategyManager(context, 'manager-1')

    def after_trading_end(self,context):
        pass

    # 卖出股票时调用的函数
    def when_sell_stock(self,position,order,is_normal):
        try:
            g.__manager.execute(order)
            self.log_info('实盘易卖股成功:' + str(order))
        except:
            self.log_error('实盘易卖股失败:' + str(order))
        pass

    # 买入股票时调用的函数
    def when_buy_stock(self,stock,order):
        try:
            g.__manager.execute(order)
            self.log_info('实盘易买股成功:' + str(order))
        except:
            self.log_error('实盘易买股失败:' + str(order))
        pass
'''------------------------------通过实盘易申购新股----------------------'''


class Purchase_new_stocks(Rule):

    def __init__(self, params):
        self.times = params.get('times',[[10,00]])

    def initialize(self,context):
        g.__manager = shipane_sdk.StrategyManager(context, 'manager-1')
    def update_params(self, context, params):
        g.__manager = shipane_sdk.StrategyManager(context, 'manager-1')
        self.times = params.get('times', [[10, 00]])

    def handle_data(self, context, data):
        hour = context.current_dt.hour
        minute = context.current_dt.minute
        if not [hour, minute] in self.times:
            return

        g.__manager.purchase_new_stocks()

    def __str__(self):
        return '实盘易申购新股[time: %s] ' % (self.times)

'''~~~~~~~~~~~~~~~~~~~~~~~~~~~基础函数~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'''


def is_3_black_crows(stock):
    # talib.CDL3BLACKCROWS

    # 三只乌鸦说明来自百度百科
    # 1. 连续出现三根阴线，每天的收盘价均低于上一日的收盘
    # 2. 三根阴线前一天的市场趋势应该为上涨
    # 3. 三根阴线必须为长的黑色实体，且长度应该大致相等
    # 4. 收盘价接近每日的最低价位
    # 5. 每日的开盘价都在上根K线的实体部分之内；
    # 6. 第一根阴线的实体部分，最好低于上日的最高价位
    #
    # 算法
    # 有效三只乌鸦描述众说纷纭，这里放宽条件，只考虑1和2
    # 根据前4日数据判断
    # 3根阴线跌幅超过4.5%（此条件忽略）

    h = attribute_history(
        stock, 4, '1d', ('close', 'open'),
        skip_paused=True, df=False)
    h_close = list(h['close'])
    h_open = list(h['open'])

    if len(h_close) < 4 or len(h_open) < 4:
        return False

    # 一阳三阴
    if h_close[-4] > h_open[-4] and (h_close[-1] < h_open[-1]
                                     and h_close[-2] < h_open[-2] and h_close[-3] < h_open[-3]):
        # and (h_close[-1] < h_close[-2] and h_close[-2] < h_close[-3]) \
        # and h_close[-1] / h_close[-4] - 1 < -0.045:
        return True
    return False


# 获取股票n日以来涨幅，根据当前价计算
# n 默认20日
def get_growth_rate(security, n=20):
    lc = get_close_price(security, n)
    # c = data[security].close
    c = get_close_price(security, 1, '1m')

    if not isnan(lc) and not isnan(c) and lc != 0:
        return (c - lc) / lc
    else:
        log.error(
            "数据非法, security: %s, %d日收盘价: %f, 当前价: %f" %
            (security, n, lc, c))
        return 0

# 获取前n个单位时间当时的收盘价


def get_close_price(security, n, unit='1d'):
    return attribute_history(security, n, unit, ('close'), True)['close'][0]


############################################################################ # #                       策略方案 # #   一、指数： #       1、 创业板指（399006） 创业板股票（399006，剔除上市不满130日创业板股票） #       2、 中小板指（399005） 小市值（流通市值小于100亿，与现有的小市值策略一致） #       3、 沪深300（399300） 买入510310（回测时按这个ETF来做，但实际上可能要变成买所有的股票，这个成交量太小了） #       4、 上证50（000016） 买入510050 #   二、市值回撤： #       1、 当日高点回撤2%，空仓2天 #       2、 3日高点回撤5%，空仓3天 #       3、 5日高点回撤8%，空仓3天 #   三、仓位：（调仓时判断） #       1、3个指数向上，全仓 #       2、2个指数向上，7成仓 #       3、1个指数向上，3成仓 #       4、都向下，空仓 #       备注：指数向上指20日均线斜率>1.01 #   四、选股:（调仓时判断） #       1、 选择斜率最高的指数对于的股票标的 #       2、 流通按市值排序（小市值优先） #       3、仓位调整不是每天调整，等调整周期到的时候再调整。 ############################################################################ 选择规则组合成一个量化策略 策略选择设置说明, 策略由以下步骤规则组合，组合而成:     1.持仓股票的处理规则     2.调仓条件判断规则     3.Query选股规则 (可选)     4.股票池过滤规则 (可选)     5.调仓规则     6.其它规则(如统计)      不同的规则组合可能存在一定的顺序关系。     二维指定具体规则配置，由 [0.是否启用，1.描述，2.规则实现类名，3.规则传递参数(dict)]] 组成。     注：所有规则类都必需继承自Rule类或Rule类的子类 # 规则配置list下标描述变量。提高可读性与未来添加更多规则配置。 # 默认不开仓的标志为-1 # 默认全仓 # 每天最大净值 # 调仓频率 # 配置 1.持仓股票的处理规则 (这里主要配置是否进行个股止损止盈) # 调仓频率，日 # 调仓频率，日 # 配置 2.调仓条件判断规则 # 使用的指数,默认 '000001.XSHG' # 可选 取day_count天内的最高价，最低价。默认160 # 可选 最高价为最低价的multiple倍时，触 发清仓 # 使用的指数,默认 '000001.XSHG' # 可选，在三乌鸦触发情况下，一天之内有多少分钟涨幅<0,则触发止损，默认60分钟 # 该调仓条件可能会产生清仓行为 # 大盘指数 # 小盘指数 # 判定调仓的二八指数20日增幅 # 调仓频率,日 # 创业板指 # 中小板指 # 沪深 300 # 上证 50 # 配置 3.Query选股规则 # 最小EPS # 备选股票数目 # 配置 4.股票池过滤规则 # 评分股数 # 最终入选股票数 # 配置 5.调仓规则 # 最终买入股票数 # 判断是否在模拟盘运行 # 配置 6.其它规则 # 注意：Shipane_order 和 Shipane_sync_p 启用一个就行了。 # 通过实盘易自动申购新股 # 创建一个规则执行器，并初始化一些通用事件 # 买股 # 卖股 # 清仓 # 通过类名得到类的实例 # 根据规则配置创建规则执行器 # config里 0.是否启用，1.描述，2.规则实现类名，3.规则传递参数(dict)] # 判定是否是模拟盘状态 -----1.持仓股票的处理规则----- -----2.调仓条件判断规则----- -----3.Query选股规则----- -----4.股票池过滤规则----- -----5.调仓规则器----- -----6.其它规则------- # 把所有规则合并排重生成一个总的规则收录器。以方便各处共同调用的 # 打印规则参数 # 按分钟回测 # 执行其它辅助规则 # 持仓股票动作的执行,目前为个股止损止盈 # ----------这部分当前本策略其实并没有啥用，扩展用-------------- # 这里执行选股器调仓器的handle_data主要是为了扩展某些选股方式可能需要提前处理数据。 # 举例：动态获取黑名单，可以调仓前一段时间先执行。28小市值规则这里都是空动作。 # 调仓器的分钟处理 # ----------------------------------------------------------- # 判断是否满足调仓条件，所有规则以and 逻辑执行 #log.info(rule) # ---------------------调仓-------------------------- # 调仓前预处理 # Query 选股 # 过滤股票列表 #log.info("stock list: ", stock_list) # 执行调仓 # 调仓后处理 # ---------------------------------------------------- # 开盘 # 收盘 # 得到当前未完成订单 # 进程启动(一天一次) # 这里Try一下，防止更新代码时出错。 # 这里示例进行模拟更改回测时，如何调整策略,基本通用代码。 # 调整策略通用实例代码 # 获取新策略 # 按新策略顺序重整规则列表，如果对象之前存在，则移到新列表，不存在则新建。 # 不管之前旧的规则列表是什么顺序，一率按新列表重新整理。 # 按顺序循环处理新规则 # 不使用则跳过 # 查找旧规则是否存在 # 旧规则存在则添加到新列表中,并调用规则的更新函数，更新参数。 # 旧规则不存在，则创建并添加 # 调用初始化时该执行的函数 # 重整所有规则 # 重新生成所有规则的list # 显示策略组成 ==============================持仓操作函数，共用================================ # 开仓，买入指定价值的证券 # 报单成功并成交（包括全部成交或部分成交，此时成交量大于0），返回True # 报单失败或者报单成功但被取消（此时成交量等于0），返回False # 报单成功，触发所有规则的when_buy_stock函数 # 平仓，卖出指定持仓 # 平仓成功并全部成交，返回True # 报单失败或者报单成功但被取消（此时成交量等于0），或者报单非全部成交，返回False # 报单成功，触发所有规则的when_sell_stock函数 # 可能会因停牌失败 # 清空卖出所有持仓 # 清仓时，调用所有规则的 when_clear_position # 自定义下单 # 根据Joinquant文档，当前报单函数都是阻塞执行，报单函数（如order_target_value）返回即表示报单完成 # 报单成功返回报单（不代表一定会成交），否则返回None #sender.log_info("Selling out %s" % (security)) #sender.log_info("Order %s to value %f, Total market_cap: %f " % (security, value, df['market_cap'])) # 如果股票停牌，创建报单会失败，order_target_value 返回None # 如果股票涨跌停，创建报单会成功，order_target_value 返回Order，但是报单会取消 # 部成部撤的报单，聚宽状态是已撤，此时成交量>0，可通过成交量判断是否有成交 # 通过类的类型得到已创建的对象实例 ==============================规则基类================================ # 持仓操作的事件 # 买股调用外部函数 # 卖股调用外部函数 # 清仓调用外部函数 # 通过类的类型查找已创建的类的实例 # 对象简要说明 # 卖出股票时调用的函数 # is_normail正常规则卖出为True，止损卖出为False # 买入股票时调用的函数 # 清仓时调用的函数 # 调仓前调用 # 调仓后调用用 # 更改参数 # 持仓操作事件的简单判断处理，方便使用。 # 通过类的类型获取已创建的类的实例对象 # 示例 obj = get_obj_by_class_type(Index28_condition) # 为日志显示带上是哪个规则器输出的 ==============================调仓条件判断器基类============================== # 返回能否进行调仓 ==============================选股 query过滤器基类============================== ==============================选股 stock_list过滤器基类============================== ==============================调仓的操作基类============================== -------------------------调仓时间控制器----------------------- # 配置调仓时间（24小时分钟制） -------------------------指数选择器----------------------- -------------------------调仓日计数器----------------------- # 调仓日计数器，单位：日 # 个股止损止盈时，即非正常卖股时，重置计数，原策略是这么写的 # 清仓时调用的函数 -------------------------28指数涨幅调仓判断器---------------------- # 回看指数前20天的涨幅 ------------------小市值选股器----------------- ------------------指数选股器----------------- #self.log_info(stock_list) # 以下为选评分高于10分的股票 #log.info("stock: %s, score: %f", stock, score) #log.info("stock: %s --> score: %f", stock, score) # 上市未超过1年 ------------------三天或五天均线上选股器----------------- # 黑名单一览表，更新时间 2016.7.10 by 沙米 # 科恒股份、太空板业，一旦2016年继续亏损，直接面临暂停上市风险 ---------------卖出股票规则-------------- # 卖出不在待买股票列表中的股票 # 对于因停牌等原因没有卖出的股票则继续持有 ---------------买入股票规则-------------- # 买入股票 # 始终保持持仓数目为g.buy_stock_count # 根据股票数量分仓 # 此处只根据可用金额平均分配购买，不能保证每个仓位平均分配 ---------------个股止损-------------- # get_period_func 为获取period的函数,无传入参数，传出参数为period # on_close_position_func 卖出股票时触发的事件，传入参数为 stock,无返回 # 个股止损 # log.debug("个股止损阈值, stock: %s, threshold: %f" %(stock, threshold)) # 获取个股前n天的m日增幅值序列 # 增加缓存避免当日多次获取数据 # 3日的百分比变比（即3日涨跌幅） # 计算个股回撤止损阈值 # 即个股在持仓n天内能承受的最大跌幅 # 算法：(个股250天内最大的n日跌幅 + 个股250天内平均的n日跌幅)/2 # 返回正值 # log.debug("pct of security [%s]: %s", pct) # maxd = pct[pct<0].min() # avgd = pct[pct<0].mean() # maxd和avgd可能为正，表示这段时间内一直在增长，比如新股 # 数据不足时，计算的bstd为nan # bstd = 0，则 maxd <= 0 # 此时取最大跌幅 # 默认配置回测止损阈值最大跌幅为-9.9%，阈值高貌似回撤降低 # 全部成交则删除相关证券的最高价缓存 ----------------------个股止盈------------------------------ # 个股止盈 # log.debug("个股止盈阈值, stock: %s, threshold: %f" %(stock, threshold)) # 获取个股前n天的m日增幅值序列 # 增加缓存避免当日多次获取数据 # 3日的百分比变比（即3日涨跌幅） # 计算个股止盈阈值 # 算法：个股250天内最大的n日涨幅 # 返回正值 # 数据不足时，计算的maxr为nan # 理论上maxr可能为负 # 默认配置止盈阈值最大涨幅为30% # 全部成交则删除相关证券的最高价缓存 ----------------------最高价最低价比例止损------------------------------ # 大盘指数前130日内最高价超过最低价2倍，则清仓止损 # 基于历史数据判定，因此若状态满足，则当天都不会变化 # 增加此止损，回撤降低，收益降低 # 当日第一次输出日志 ----------------------最高价最低价比例止损------------------------------ ----------------------三乌鸦止损------------------------------ # 临时参数 # 前日三黑鸦，累计当日每分钟涨幅<0的分钟计数 # 如果分钟计数超过一定值，则开始进行三黑鸦止损 # 避免无效三黑鸦乱止损 ----------------------净值止损------------------------------ ----------------------净值止损------------------------------ ----------------------28指数值实时进行止损------------------------------ # 临时参数 # 回看指数前20天的涨幅 # 不连续状态归零 ----------------------统计类---------------------------- # 加载统计模块 # 只要有成交，无论全部成交还是部分成交，则统计盈亏 # 记录交易次数便于统计胜率 # 卖出成功后针对卖出的量进行盈亏统计 # 打印胜率 #self.log_info('每日最大净值：') #self.log_info(g.max_portfolio_everyday) # 统计单次盈利最高的股票 # 统计单次亏损最高的股票 # 统计总盈利金额 -------------------实盘易对接----------------------- # 调仓后调用 # 清仓时调用的函数 # 强力买卖条件下,卖两次股,防止五档都没卖掉,扫两次五档没卖完就算了吧 # 强力买卖条件下,买股,买两遍，第一遍为买，第二遍为检查，防止部分五档扫完还没买够的 # 卖股 # 买股 # 获取实盘总持仓 # 获取模拟盘持仓 # 通过对比持仓获得要操作的股票及数量 # 买股 # 买单时需要获取实际资金，以涨停价计算最大挂单数买入。 # 最后一单经常需要拆成两单买入。还有误差就不管了。 # 获取实盘可用资金 # 计算当前可用资金以涨停价挂单的最大挂单数 # 可用资金足够，一次性买入。 # 资金不足，分次买入。 # 获取实盘现金 # 重复三次获取，防止偶然性网络错误 # 获取实盘持仓 # 重复三次获取，防止偶然性网络错误 # 重抛异常 # 获取模拟盘持仓 # 获取两持仓之差 mp为模拟盘，sp为实盘。 # 取两个列表之差 # s_list = [x for x in s_list if x[1] >= 100 or x[1] <= -100] # s_list += [x for x in sp if x[1] > -100 and x[1] < 0] # 卖股 # 取模拟盘的持仓量 # 如果模拟盘已清仓该股，则实盘全卖 # 对股票取整 # 买股直接四舍五入取整 -----------------根据聚宽Order用实盘易下单------------------ # 卖出股票时调用的函数 # 买入股票时调用的函数 ------------------------------通过实盘易申购新股---------------------- ~~~~~~~~~~~~~~~~~~~~~~~~~~~基础函数~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # talib.CDL3BLACKCROWS # 三只乌鸦说明来自百度百科 # 1. 连续出现三根阴线，每天的收盘价均低于上一日的收盘 # 2. 三根阴线前一天的市场趋势应该为上涨 # 3. 三根阴线必须为长的黑色实体，且长度应该大致相等 # 4. 收盘价接近每日的最低价位 # 5. 每日的开盘价都在上根K线的实体部分之内； # 6. 第一根阴线的实体部分，最好低于上日的最高价位 # # 算法 # 有效三只乌鸦描述众说纷纭，这里放宽条件，只考虑1和2 # 根据前4日数据判断 # 3根阴线跌幅超过4.5%（此条件忽略） # 一阳三阴 # and (h_close[-1] < h_close[-2] and h_close[-2] < h_close[-3]) \ # and h_close[-1] / h_close[-4] - 1 < -0.045: # 获取股票n日以来涨幅，根据当前价计算 # n 默认20日 # c = data[security].close # 获取前n个单位时间当时的收盘价