Exemple #1
0
def fixing_is_st(start, end):
    # 第一阶段
    df = pd.read_excel('data/stock_basic.xlsx', header=0, dtype={'code':str})
    df = df.set_index('code')
    codes = df[df['是否ST过'] == 1].index.tolist()
    total = len(codes)
#    all_dates = get_trading_dates(start, end)
    
    daily = DB_CONN['daily']
    
    excel_name = 'data/st_info.xlsx'
    for i in range(4):
        if i == 0:
            all_dates = get_trading_dates('2015-01-01', '2015-12-31')
        elif i == 1:
            all_dates = get_trading_dates('2016-01-01', '2016-12-31')
        if i == 2:
            all_dates = get_trading_dates('2017-01-01', '2017-12-31')
        elif i == 3:
            all_dates = get_trading_dates('2018-01-01', '2018-09-30')
        
        
        print('数据读取中')
        df = pd.read_excel(excel_name, i, header=0, dtype={'code':str})
        df = df.set_index(['code','state'])
        df.columns = df.columns.astype(np.datetime64)
        df.columns = df.columns.to_period('D')
        df.columns = df.columns.astype('str')
        print('数据读取完毕')
        
        
        for j, code in enumerate(codes):
            update_requests = []
            for date in all_dates:
                try:
                    st_state = df.xs([code])[date]['是否ST']
                    sst_state = df.xs([code])[date]['是否*ST']
                    if (st_state == '否') and (sst_state == '否'):
                        is_st_flag = False
                    else:
                        is_st_flag = True
                    
                    update_requests.append(
                        UpdateOne(
                                {'code':code, 'date':date, 'index':False},
                                {'$set':{'is_st':is_st_flag}}
                                )
                        )
                except:
                    print('something is wrong, code : %s, date : %s' % (code, date))
                        
            if len(update_requests)>0:
                update_result = daily.bulk_write(update_requests, ordered=False)
                print('第%s年填充进度: %s/%s, 字段名: is_st,数据集:%s,插入:%4d条,更新:%4d条' %
                          (i+1, j+1, total, 'daily', update_result.upserted_count, update_result.modified_count), flush=True)
Exemple #2
0
def crawl_basic(begin_date=None, end_date=None):
    """
    抓取指定时间范围内的股票基础信息
    :param begin_date: 开始日期
    :param end_date: 结束日期
    """

    # 如果没有指定开始日期,则默认为前一日
    if begin_date is None:
        begin_date = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')

    # 如果没有指定结束日期,则默认为前一日
    if end_date is None:
        end_date = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')

    # 获取指定日期范围的所有交易日列表
    all_dates = get_trading_dates(begin_date, end_date)

    # 按照每个交易日抓取
    for date in all_dates:
        try:
            # 抓取当日的基本信息
            crawl_basic_at_date(date)
        except:
            print('抓取股票基本信息时出错,日期:%s' % date, flush=True)
Exemple #3
0
def fill_daily_k_at_suspension_days(begin_date=None, end_date=None):
    """

    :param begin_date:
    :param end_date:
    :return:
    """
    before = datetime.now() - timedelta(days=1)
    while 1:
        last_trading_date = before.strftime('%Y-%m-%d')
        basic_cursor = DB_CONN['basic'].find({'date': last_trading_date},
                                             projection={
                                                 'code': True,
                                                 'timeToMarket': True,
                                                 '_id': False
                                             },
                                             batch_size=5000)

        basics = [basic for basic in basic_cursor]

        if len(basics) > 0:
            break

        before -= timedelta(days=1)

    all_dates = get_trading_dates(begin_date, end_date)

    fill_daily_k_at_suspension_days_at_date_one_collection(
        basics, all_dates, 'daily')
    fill_daily_k_at_suspension_days_at_date_one_collection(
        basics, all_dates, 'daily_hfq')
Exemple #4
0
def fill_high_and_low_price_between(start, end):

    all_trades = get_trading_dates(start, end)
    codes = ts.get_stock_basics().index.tolist()  # get_all_codes()
    #    basics = getBasics()
    total = len(all_trades)
    for i, date in enumerate(all_trades):
        fill_high_and_low_price_at_one_date(codes, date)
        print('涨跌停计算进度: (%s/%s)' % (i + 1, total))
Exemple #5
0
def fill_is_trading_between(start, end):
    """
    
    """
    pass
    all_dates = get_trading_dates(start, end)

    for date in all_dates:
        fill_single_date_is_trading(date, 'daily')
        fill_single_date_is_trading(date, 'daily_hfq')
Exemple #6
0
def fill_daily_k_at_suspension_days(begin_date=None, end_date=None):
    """
    填充指定日期范围内,股票停牌日的行情数据。
    填充时,停牌的开盘价、最高价、最低价和收盘价都为最近一个交易日的收盘价,成交量为0,
    is_trading是False

    :param begin_date: 开始日期
    :param end_date: 结束日期
    """

    # 当前日期的前一天
    before = datetime.now() - timedelta(days=1)
    # 找到据当前最近一个交易日的所有股票的基本信息
    basics = []
    while 1:
        # 转化为str
        last_trading_date = before.strftime('%Y-%m-%d')
        # 因为TuShare的基本信息最早知道2016-08-09,所以如果日期早于2016-08-09
        # 则结束查找
        if last_trading_date < '2016-08-09':
            break

        # 找到当日的基本信息
        basic_cursor = DB_CONN['basic'].find(
            {'date': last_trading_date},
            # 填充时需要用到两个字段股票代码code和上市日期timeToMarket,
            # 上市日期用来判断
            projection={
                'code': True,
                'timeToMarket': True,
                '_id': False
            },
            # 一次返回5000条,可以降低网络IO开销,提高速度
            batch_size=5000)

        # 将数据放到basics列表中
        basics = [basic for basic in basic_cursor]

        # 如果查询到了数据,在跳出循环
        if len(basics) > 0:
            break

        # 如果没有找到数据,则继续向前一天
        before -= timedelta(days=1)

    # 获取指定日期范围内所有交易日列表
    all_dates = get_trading_dates(begin_date, end_date)

    # 填充daily数据集中的停牌日数据
    fill_daily_k_at_suspension_days_at_date_one_collection(
        basics, all_dates, 'daily')
    # 填充daily_hfq数据中的停牌日数据
    fill_daily_k_at_suspension_days_at_date_one_collection(
        basics, all_dates, 'daily_hfq')
Exemple #7
0
def fill_is_trading_between(begin_date=None, end_date=None):
    """
    填充指定时间段内的is_trading字段
    :param begin_date: 开始日期
    :param end_date: 结束日期
    """
    all_dates = get_trading_dates(begin_date, end_date)

    for date in all_dates:
        fill_single_date_is_trading(date, 'daily')
        fill_single_date_is_trading(date, 'daily_hfq')
Exemple #8
0
def fill_is_trading_between(begin_date=None, end_date=None):
    """
    填充指定时间段内的is_trading字段
    :param begin_date: 开始日期
    :param end_date: 结束日期
    """
    all_dates = get_trading_dates(begin_date, end_date)
    total = len(all_dates)
    for i,date in enumerate(all_dates):
        fill_single_date_is_trading(date, 'daily')
        fill_single_date_is_trading(date, 'daily_hfq')
        print('is_trading字段填充进度: (%s/%s)' % (i+1, total))
Exemple #9
0
def get_tushare_code(begin_date='2000-01-01', end_date=None):
    if (end_date is None):
        end_date = datetime.now().strftime('%Y-%m-%d')
    # 初始化pro接口
    pro = ts.pro_api(
        'f3ef4ac4dc04104e0573aa75c29aef70f30837a416baf6cd1a0f8e81')
    tradingdays_list = get_trading_dates(begin_date=begin_date,
                                         end_date=end_date)
    # tradingdays_list = get_trade_days(begin_date= begin_date,end_date = end_date)
    codes = set()
    for day in tradingdays_list:
        data = pro.daily(trade_date=day.replace('-', ''))
        codes = codes | set(data.ts_code)
    return list(codes).sort()
Exemple #10
0
def crawl_basic(start, end=None):
    """
    
    """
    end = start if end is None else end
    start = str(start)[0:10]
    end = str(end)[0:10]

    all_dates = get_trading_dates(start, end)

    for date in all_dates:
        try:
            crawl_basic_at_date(date)
        except:
            print('抓取股票基本信息时出错,日期:%s' % date, flush=True)
Exemple #11
0
def fill_is_trading_between(begin_date=None, end_date=None):
    """
    填充指定时间段内的is_trading字段
    :param begin_date: 开始日期
    :param end_date: 结束日期
    """

    # 获取指定日期范围的所有交易日列表,按日期正序排列
    all_dates = get_trading_dates(begin_date, end_date)

    # 循环填充所有交易日的is_trading字段
    for date in all_dates:
        # 填充daily数据集
        fill_single_date_is_trading(date, 'daily')
        # 填充daily_hfq数据集
        fill_single_date_is_trading(date, 'daily_hfq')
Exemple #12
0
def fill_is_trading_between(begin_date=None, end_date=None):
    """
    填充指定时间段内的is_trading字段
    :param begin_date: 开始日期
    :param end_date: 结束日期
    """
    all_dates = get_trading_dates(begin_date, end_date)
    total = len(all_dates)
    for i, date in enumerate(all_dates):
        _tic = time.process_time()
        fill_single_date_is_trading(date, 'daily')
        fill_single_date_is_trading(date, 'daily_hfq')
        _toc = time.process_time()
        expect_time = (_toc - _tic) * (total - i - 1)
        print('is_trading字段填充进度: (%s/%s), 预计还需要%.2fs' %
              (i + 1, total, expect_time))
Exemple #13
0
def fill_is_trading(date=None):
    """
    为日线数据增加is_trading字段,表示是否交易的状态,True - 交易  False - 停牌
    从Tushare来的数据不包含交易状态,也不包含停牌的日K数据,为了系统中使用的方便,我们需要填充停牌是的K数据。
    一旦填充了停牌的数据,那么数据库中就同时包含了停牌和交易的数据,为了区分这两种数据,就需要增加这个字段。

    在填充该字段时,要考虑到是否最坏的情况,也就是数据库中可能已经包含了停牌和交易的数据,但是却没有is_trading
    字段。这个方法通过交易量是否为0,来判断是否停牌
    """
    if date is None:
        all_dates = get_trading_dates()
    else:
        all_dates = [date]
    for date in all_dates:
        fill_single_date_is_trading(date, 'daily')
        fill_single_date_is_trading(date, 'daily_hfq')
Exemple #14
0
def crawl_basic(start, end=None):
    """
    
    """
    end = start if end is None else end
    start = str(start)[0:10]
    end = str(end)[0:10]

    all_dates = get_trading_dates(start, end)
    if all_dates is None:
        print('没有获取到交易日历')
    total = len(all_dates)
    for i, date in enumerate(all_dates):
        try:
            crawl_basic_at_date(date)
        except:
            print('抓取股票基本信息时出错,日期:%s' % date, flush=True)
        print('基础信息数据获取进度: (%s/%s)' % (i + 1, total))
Exemple #15
0
def fill_is_trading_between(begin_date=None, end_date=None, dates=None):
    '''
    填充指定时间段内的is_trading字段

    parameter:
    begin_date: 开始日期
    end_date: 结束日期
    '''

    # 得到时间段
    if dates is None:
        all_trading_dates = get_trading_dates(begin_date, end_date)
    else: 
        if isinstance(dates, list) is False:
            dates = [dates]
        all_trading_dates = dates
    # 填充单个数据
    for trading_date in all_trading_dates:
        fill_single_date_is_trading(trading_date, 'daily_none')
        fill_single_date_is_trading(trading_date, 'daily_hfq')
Exemple #16
0
def crawl_basic(begin_date=None, end_date=None):
    """
    抓取指定时间范围内的股票基础信息
    :param begin_date: 开始日期
    :param end_date: 结束日期
    """

    if begin_date is None:
        begin_date = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')

    if end_date is None:
        end_date = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')

    all_dates = get_trading_dates(begin_date, end_date)

    for date in all_dates:
        try:
            crawl_basic_at_date(date)
        except:
            print('抓取股票基本信息时出错,日期:%s' % date, flush=True)
Exemple #17
0
def threads_supension_days(begin_date=None, end_date=None, dates=None):
    '''
    多线程补充停牌数据

    使用说明:
    使用时在main中加入如下代码:
    dates, threads = threads_supension_days()
    for i in range(len(dates)):
        threads[i].start()
    for i in range(len(dates)):
        threads[i].join()
    '''
    # codes = get_all_codes()
    if dates is None:
        dates = get_trading_dates(begin_date, end_date)
    threads = []
    # dates = get_trading_dates()
    for date in dates:
        t = threading.Thread(target=fill_daily_k_supension_days, args=(None, None, date))
        threads.append(t)
    return dates, threads
Exemple #18
0
def threads_fill_is_trading():
    '''
    多线程补充字段:is_trading

    使用说明:
    使用时在main中加入如下代码:
    dates, threads = threads_fill_is_trading()
    for i in range(len(dates)):
        threads[i].start()
    for i in range(len(dates)):
        threads[i].join()
    '''
    # codes = get_all_codes()
    dates = get_trading_dates()
    threads = []
    # dates = get_trading_dates()
    for date in dates:
        t = threading.Thread(target=fill_is_trading_between, args=(None, None, date))
        threads.append(t)
    length = len(dates)
    return length, threads
Exemple #19
0
    def crawl_basic(self, begin_date=None, end_date=None):
        '''
        从tushare中抓取指定时间段的基本数据

        parameter:
        begin_date: 开始日期
        end_date: 结束日期
        '''
        # 没有指定日期范围时设置日期
        if begin_date is None:
            begin_date = '2016-08-08'
        if end_date is None:
            end_date = datetime.now().strftime('%Y-%m-%d')
        # 得到日期内所有的交易日
        all_dates = get_trading_dates(begin_date, end_date)
        
        for date in all_dates:
            # 从数据库中拿到需要补充基本信息的股票代码
            # 从tushare中得到股票代码的基本数据
            # 将股票代码的基本数据补充到原有的数据当中
            # 保存更新完的股票数据
            self.crawl_basic_at_date(date)
Exemple #20
0
def threads_dates(begin_date, end_date, fun):
    '''
    date 多线程函数
    有几个date就有几个线程
    fun:需要多线程的函数

    使用说明:
    使用时在main中加入如下代码:
    dates, threads = threads_dates()
    for i in range(len(dates)):
        threads[i].start()
    for i in range(len(dates)):
        threads[i].join()
    '''
    # codes = get_all_codes()
    dates = get_trading_dates(begin_date, end_date)
    threads = []
    # dates = get_trading_dates()
    for date in dates:
        t = threading.Thread(target=fun, args=(None, None, date))
        threads.append(t)
    length = len(dates)
    return length, threads
Exemple #21
0
    def crawl_basic_one_code(self, code):
        all_dates = get_trading_dates()    
        updata_requests = []
        for date in all_dates:
            try:
                df_basics = ts.get_stock_basics(date)
                if df_basics is None:
                    print('no basic data in tushare, code %s, date %s' % (code, date))
                    continue
                doc = dict(df_basics.loc[code])
                time_to_market = datetime.strptime(str(doc['timeToMarket']), '%Y%m%d').strftime('%Y-%m-%d')
                totals = float(doc['totals'])
                outstanding = float(doc['outstanding'])
                doc.update({
                    'code': code,
                    'date': date,
                    'timeToMarket': time_to_market,
                    'totals': totals,
                    'outstanding': outstanding
                })
                # 将股票的基本数据补充到原有的数据当中
                updata_requests.append(
                    UpdateOne(
                        {'code': doc['code'], 'date': doc['date']},
                        {'$set': doc}, upsert=True)
                )

            except:
                print('Error!, code: %s, date: %s' % (code, date), flush=True)
                print(doc, flush=True)
                
        # 将更新好的数据保存到数据库中
        if len(updata_requests) > 0:
            # self.basic.create_index([("code", 1), ("date", -1)], background=True)
            request_result = self.basic.bulk_write(updata_requests, ordered=False)
            print('save basic data for one code, code: %s, date: %s, insert: %4d, update: %4d' % 
                (code, date, request_result.upserted_count, request_result.modified_count), flush=True)
Exemple #22
0
def fill_daily_k_supension_days(begin_date=None, end_date=None, dates=None):
    '''
    补充股票在停牌日的数据

    parameter:
    begin_date: 开始日期,为None时即从‘2008-01-01’开始
    end_date: 结束日期, 为None时为数据库中能找到的最新日期
    '''
    # 找到指定时间段所有的交易日期
    if dates is None:
        all_dates = get_trading_dates(begin_date, end_date)
    else:
        if isinstance(dates, list) is False:
            dates = [dates]
        all_dates = dates

    # 找到所有股票的上市日期
    basic_date = datetime.now().strftime('%Y-%m-%d')
    while True:
        basic_cursor = DB_CONN['basic'].find(
            {'date': basic_date},
            projection={'code': True, 'date': True, 'timeToMarket': True, '_id': False},
            ).hint([('date', -1)])
        basics = [basic for basic in basic_cursor]
        if len(basics) > 0:
            break
        basic_date = (datetime.strptime(basic_date, '%Y-%m-%d') - timedelta(days=1)).strftime('%Y-%m-%d')

    for date in all_dates:
        for basic in basics:
            code = basic['code']
            # 判断股票是否在当前交易日期停牌
            is_supension_flag = is_supension(date, code)
            # 对停牌日期补充数据
            if is_supension_flag:
                fill_daily_k_supension_days_at_date_one_collection(date, code, basic, 'daily_none')
                fill_daily_k_supension_days_at_date_one_collection(date, code, basic, 'daily_hfq')
def stock_pool(begin_date, end_date):

    adjust_date_codes_dict = dict()
    # 调整周期
    adjust_interval = 7
    all_adjust_dates = []
    last_phase_codes = []

    # 从数据库中挑选出0<pe<30的股票
    all_dates = get_trading_dates(begin_date=begin_date, end_date=end_date)

    for _index in range(0, len(all_dates), adjust_interval):
        adjust_date = all_dates[_index]
        all_adjust_dates.append(adjust_date)
        print('adjust date: %s' % adjust_date)
        daily_cursor = daily_collection.find(
            {
                'date': adjust_date,
                'pe': {
                    '$gt': 0,
                    '$lt': 30
                },
                'is_trading': True,
                'index': False
            },
            projection={
                'code': True,
                '_id': False
            },
            sort=[('pe', ASCENDING)],
            limit=100)

        codes = [x['code'] for x in daily_cursor]
        if codes == []:
            continue
        this_phase_codes = []

        # 判断是否在调整日停牌
        supension_codes = []
        if len(last_phase_codes) > 0:
            supension_cursor = daily_collection.find(
                {
                    'code': {
                        '$in': last_phase_codes
                    },
                    'date': adjust_date,
                    'is_trading': False
                },
                projection={
                    'code': True
                }).hint([('code', 1), ('date', -1)])
            supension_codes = [x['code'] for x in supension_cursor]
            this_phase_codes = supension_codes

        # 判断是否在上一期股票池中
        print('last phase code supended in this adjust day:')
        print(supension_codes)

        # 得到这一期的股票池
        this_phase_codes += codes[0:100 - len(this_phase_codes)]
        last_phase_codes = this_phase_codes
        # 建立该调整日和股票列表的对应关系
        adjust_date_codes_dict[adjust_date] = this_phase_codes

        print('this phase codes:')
        print(this_phase_codes)

    return all_adjust_dates, adjust_date_codes_dict
Exemple #24
0
def backtest(begin_date, end_date):
    """
    策略回测。结束后打印出收益曲线(沪深300基准)、年化收益、最大回撤、

    :param begin_date: 回测开始日期
    :param end_date: 回测结束日期
    """

    # 初始现金1000万
    cash = 1E7
    # 单只股票的仓位是20万
    single_position = 2E5

    # 时间为key的净值、收益和同期沪深基准
    df_profit = pd.DataFrame(columns=['net_value', 'profit', 'hs300'])

    # 获取回测开始日期和结束之间的所有交易日,并且是按照正序排列
    all_dates = get_trading_dates(begin_date, end_date)

    # 获取沪深300的在回测开始的第一个交易日的值
    hs300_begin_value = DB_CONN['daily'].find_one(
        {'code': '000300', 'index': True, 'date': all_dates[0]},
        projection={'close': True})['close']

    # 获取回测周期内的股票池数据,
    # adjust_dates:正序排列的调整日列表;
    # date_codes_dict: 调整日和当期的股票列表组成的dict,key是调整日,value是股票代码列表
    adjust_dates, date_codes_dict = stock_pool(begin_date, end_date)

    # 股票池上期股票代码列表
    last_phase_codes = None
    # 股票池当期股票代码列表
    this_phase_codes = None
    # 待卖的股票代码集合
    to_be_sold_codes = set()
    # 待买的股票代码集合
    to_be_bought_codes = set()
    # 持仓股票dict,key是股票代码,value是一个dict,
    # 三个字段分别为:cost - 持仓成本,volume - 持仓数量,last_value:前一天的市值
    holding_code_dict = dict()
    # 前一个交易日
    last_date = None
    # 在交易日的顺序,一天天完成信号检测
    for _date in all_dates:
        print('Backtest at %s.' % _date)

        # 当期持仓股票的代码列表
        before_sell_holding_codes = list(holding_code_dict.keys())

        """
        持仓股的除权除息处理
        如果当前不是第一个交易日,并且有持仓股票,则处理除权除息对持仓股的影响
        这里的处理只考虑复权因子的变化,而实际的复权因子变化有可能是因为除权、除息以及配股,
        那么具体的持仓股变化要根据它们的不同逻辑来处理
        """
        if last_date is not None and len(before_sell_holding_codes) > 0:
            # 从daily数据集中查询出所有持仓股的前一个交易日的复权因子
            last_daily_cursor = DB_CONN['daily'].find(
                {'code': {'$in': before_sell_holding_codes}, 'date': last_date, 'index': False},
                projection={'code': True, 'au_factor': True})

            # 构造一个dict,key是股票代码,value是上一个交易日的复权因子
            code_last_aufactor_dict = dict([(daily['code'], daily['au_factor']) for daily in last_daily_cursor])

            # 从daily数据集中查询出所有持仓股的当前交易日的复权因子
            current_daily_cursor = DB_CONN['daily'].find(
                {'code': {'$in': before_sell_holding_codes}, 'date': _date, 'index': False},
                projection={'code': True, 'au_factor': True})

            # 一只股票一只股票进行处理
            for current_daily in current_daily_cursor:
                # 当前交易日的复权因子
                current_aufactor = current_daily['au_factor']
                # 股票代码
                code = current_daily['code']
                # 从持仓股中找到该股票的持仓数量
                last_volume = holding_code_dict[code]['volume']
                # 如果该股票存在前一个交易日的复权因子,则对持仓股数量进行处理
                if code in code_last_aufactor_dict:
                    # 上一个交易日的复权因子
                    last_aufactor = code_last_aufactor_dict[code]
                    # 计算复权因子变化后的持仓股票数量,如果复权因子不发生变化,那么持仓数量是不发生变化的
                    # 相关公式是:
                    # 市值不变:last_close * last_volume = pre_close * current_volume
                    # 价格的关系:last_close * last_aufactor = pre_close * current_aufactor
                    # 转换之后得到下面的公式:
                    current_volume = int(last_volume * (current_aufactor / last_aufactor))
                    # 改变持仓数量
                    holding_code_dict[code]['volume'] = current_volume
                    print('持仓量调整:%s, %6d, %10.6f, %6d, %10.6f' %
                          (code, last_volume, last_aufactor, current_volume, current_aufactor))

        """
        卖出的逻辑处理:
        卖出价格是当日的开盘价,卖出的数量就是持仓股的数量,卖出后获得的资金累加到账户的可用现金上
        """

        print('待卖股票池:', to_be_sold_codes, flush=True)
        # 如果有待卖股票,则继续处理
        if len(to_be_sold_codes) > 0:
            # 从daily数据集中查询所有待卖股票的开盘价,这里用的不复权的价格,以模拟出真实的交易情况
            sell_daily_cursor = DB_CONN['daily'].find(
                {'code': {'$in': list(to_be_sold_codes)}, 'date': _date, 'index': False, 'is_trading': True},
                projection={'open': True, 'code': True}
            )

            # 一只股票一只股票处理
            for sell_daily in sell_daily_cursor:
                # 待卖股票的代码
                code = sell_daily['code']
                # 如果股票在持仓股里
                if code in before_sell_holding_codes:
                    # 获取持仓股
                    holding_stock = holding_code_dict[code]
                    # 获取持仓数量
                    holding_volume = holding_stock['volume']
                    # 卖出价格为当日开盘价
                    sell_price = sell_daily['open']
                    # 卖出获得金额为持仓量乘以卖出价格
                    sell_amount = holding_volume * sell_price
                    # 卖出得到的资金加到账户的可用现金上
                    cash += sell_amount

                    # 获取该只股票的持仓成本
                    cost = holding_stock['cost']
                    # 计算持仓的收益
                    single_profit = (sell_amount - cost) * 100 / cost
                    print('卖出 %s, %6d, %6.2f, %8.2f, %4.2f' %
                          (code, holding_volume, sell_price, sell_amount, single_profit))

                    # 删除该股票的持仓信息
                    del holding_code_dict[code]
                    to_be_sold_codes.remove(code)

        print('卖出后,现金: %10.2f' % cash)

        """
        买入的逻辑处理:
        买入的价格是当日的开盘价,每只股票可买入的金额为20万,如果可用现金少于20万,就不再买入了
        """
        print('待买股票池:', to_be_bought_codes, flush=True)
        # 如果待买股票集合不为空,则执行买入操作
        if len(to_be_bought_codes) > 0:
            # 获取所有待买入股票的开盘价
            buy_daily_cursor = DB_CONN['daily'].find(
                {'code': {'$in': list(to_be_bought_codes)}, 'date': _date, 'is_trading': True, 'index': False},
                projection={'code': True, 'open': True}
            )

            # 处理所有待买入股票
            for buy_daily in buy_daily_cursor:
                # 判断可用资金是否够用
                if cash > single_position:
                    # 获取买入价格
                    buy_price = buy_daily['open']
                    # 获取股票代码
                    code = buy_daily['code']
                    # 获取可买的数量,数量必须为正手数
                    volume = int(int(single_position / buy_price) / 100) * 100
                    # 买入花费的成本为买入价格乘以实际的可买入数量
                    buy_amount = buy_price * volume
                    # 从现金中减去本次花费的成本
                    cash -= buy_amount
                    # 增加持仓股中
                    holding_code_dict[code] = {
                        'volume': volume,         # 持仓量
                        'cost': buy_amount,       # 持仓成本
                        'last_value': buy_amount  # 初始前一日的市值为持仓成本
                    }

                    print('买入 %s, %6d, %6.2f, %8.2f' % (code, volume, buy_price, buy_amount))

        print('买入后,现金: %10.2f' % cash)

        # 持仓股代码列表
        holding_codes = list(holding_code_dict.keys())

        """
        股票池调整日的处理逻辑:
        如果当前日期是股票池调整日,那么需要获取当期的备选股票列表,同时找到
        本期被调出的股票,如果这些被调出的股票是持仓股,则需要卖出
        """
        # 判断当前交易日是否为股票池的调整日
        if _date in adjust_dates:
            print('股票池调整日:%s,备选股票列表:' % _date, flush=True)

            # 如果上期股票列表存在,也就是当前不是第一期股票,则将
            # 当前股票列表设为上期股票列表
            if this_phase_codes is not None:
                last_phase_codes = this_phase_codes

            # 获取当期的股票列表
            this_phase_codes = date_codes_dict[_date]
            print(this_phase_codes, flush=True)

            # 如果存在上期的股票列表,则需要找出被调出的股票列表
            if last_phase_codes is not None:
                # 找到被调出股票池的股票列表
                out_codes = find_out_stocks(last_phase_codes, this_phase_codes)
                # 将所有被调出的且是在持仓中的股票添加到待卖股票集合中
                for out_code in out_codes:
                    if out_code in holding_code_dict:
                        to_be_sold_codes.add(out_code)

        # 检查是否有需要第二天卖出的股票
        for holding_code in holding_codes:
            #if is_rsi_over_sold(holding_code, _date):
            #if is_boll_break_up(holding_code, _date):
            #if is_macd_gold(holding_code, _date):
            #if is_fractal_up(holding_code, _date):
            if is_k_down_break_ma10(holding_code, _date):
                to_be_sold_codes.add(holding_code)

        # 检查是否有需要第二天买入的股票
        to_be_bought_codes.clear()
        if this_phase_codes is not None:
            for _code in this_phase_codes:
                #if _code not in holding_codes and is_rsi_over_bought(_code, _date):
                #if _code not in holding_codes and is_boll_break_down(_code, _date):
                #if _code not in holding_codes and is_macd_gold(_code, _date):
                #if _code not in holding_codes and is_fractal_down(_code, _date):
                if _code not in holding_codes and is_k_up_break_ma10(_code, _date):
                    to_be_bought_codes.add(_code)

        # 计算总资产
        total_value = 0

        # 获取所有持仓股的当日收盘价
        holding_daily_cursor = DB_CONN['daily'].find(
            {'code': {'$in': holding_codes}, 'date': _date, 'index':False},
            projection={'close': True, 'code': True}
        )

        # 计算所有持仓股的总市值
        for holding_daily in holding_daily_cursor:
            code = holding_daily['code']
            holding_stock = holding_code_dict[code]
            # 单只持仓的市值等于收盘价乘以持仓量
            value = holding_daily['close'] * holding_stock['volume']
            # 总市值等于所有持仓股市值的累加之和
            total_value += value

            # 计算单只股票的持仓收益
            profit = (value - holding_stock['cost']) * 100 / holding_stock['cost']
            # 计算单只股票的单日收益
            one_day_profit = (value - holding_stock['last_value']) * 100 / holding_stock['last_value']
            # 更新前一日市值
            holding_stock['last_value'] = value
            print('持仓: %s, %10.2f, %4.2f, %4.2f' %
                  (code, value, profit, one_day_profit))

        # 总资产等于总市值加上总现金
        total_capital = total_value + cash

        # 获取沪深300的当日收盘值
        hs300_current_value = DB_CONN['daily'].find_one(
            {'code': '000300', 'index': True, 'date': _date},
            projection={'close': True})['close']

        print('收盘后,现金: %10.2f, 总资产: %10.2f' % (cash, total_capital))
        last_date = _date
        # 将当日的净值、收益和沪深300的涨跌幅放入DataFrame
        df_profit.loc[_date] = {
            'net_value': round(total_capital / 1e7, 2),
            'profit': round(100 * (total_capital - 1e7) / 1e7, 2),
            'hs300': round(100 * (hs300_current_value - hs300_begin_value) / hs300_begin_value, 2)
        }

    print(df_profit)
    # 计算最大回撤
    drawdown = compute_drawdown(df_profit['net_value'])
    # 计算年化收益和夏普比率
    annual_profit, sharpe_ratio = compute_sharpe_ratio(df_profit['net_value'])

    print('回测结果 %s - %s,年化收益: %7.3f, 最大回撤:%7.3f, 夏普比率:%4.2f' %
          (begin_date, end_date, annual_profit, drawdown, sharpe_ratio))

    # 绘制收益曲线
    df_profit.plot(title='Backtest Result', y=['profit', 'hs300'], kind='line')
    plt.show()
def stock_pool(begin_date, end_date):
    """
    股票池
    :param begin_date: 开始日期
    :param end_date: 结束日期
    :return: tuple,所有调整日,以及调整日和代码列表对应的dict
    """

    adjust_date_codes_dict = dict()

    # 获取指定时间区间的所有交易日
    all_dates = get_trading_dates(begin_date=begin_date, end_date=end_date)

    # 上一期的所有股票代码
    last_phase_codes = []
    # 调整周期是7个交易日
    adjust_interval = 7
    # 所有的调整日
    all_adjust_dates = []
    # 在调整日调整股票池
    for _index in range(0, len(all_dates), adjust_interval):
        # 保存调整日
        adjust_date = all_dates[_index]
        all_adjust_dates.append(adjust_date)

        print('调整日期: %s' % adjust_date, flush=True)

        # 查询出调整当日,0 < pe_ttm < 30,且非停牌的股票
        # 最重要的一点是,按照pe_ttm正序排列,只取前100只
        daily_cursor = daily.find(
            {
                'date': adjust_date,
                'pe': {
                    '$lt': 30,
                    '$gt': 0
                },
                'is_trading': True
            },
            sort=[('pe', ASCENDING)],
            projection={'code': True},
            limit=100)

        codes = [x['code'] for x in daily_cursor]

        # 本期股票列表
        this_phase_codes = []

        # 查询出上次股票池中正在停牌的股票
        if len(last_phase_codes) > 0:
            suspension_cursor = daily.find(
                {
                    'code': {
                        '$in': last_phase_codes
                    },
                    'date': adjust_date,
                    'is_trading': False
                },
                projection={'code': True})
            suspension_codes = [x['code'] for x in suspension_cursor]

            # 保留股票池中正在停牌的股票
            this_phase_codes = suspension_codes

        print('上期停牌', flush=True)
        print(this_phase_codes, flush=True)

        # 用新的股票将剩余位置补齐
        this_phase_codes += codes[0:100 - len(this_phase_codes)]
        # 将本次股票设为下次运行的时的上次股票池
        last_phase_codes = this_phase_codes

        # 建立该调整日和股票列表的对应关系
        adjust_date_codes_dict[adjust_date] = this_phase_codes

        print('最终出票', flush=True)
        print(this_phase_codes, flush=True)

    # 返回结果
    return all_adjust_dates, adjust_date_codes_dict
def backtest(begin_date, end_date):
    """
    策略回测。结束后打印出收益曲线(沪深300基准)、年化收益、最大回撤、

    :param begin_date: 回测开始日期
    :param end_date: 回测结束日期
    """
    cash = 1E7
    single_position = 2E5

    # 时间为key的净值、收益和同期沪深基准
    df_profit = pd.DataFrame(columns=['net_value', 'profit', 'hs300'])

    all_dates = get_trading_dates(begin_date, end_date)

    hs300_begin_value = DB_CONN['daily'].find_one(
        {
            'code': '000300',
            'index': True,
            'date': all_dates[0]
        },
        projection={'close': True})['close']

    adjust_dates, date_codes_dict = stock_pool(begin_date, end_date)

    last_phase_codes = None
    this_phase_codes = None
    to_be_sold_codes = set()
    to_be_bought_codes = set()
    holding_code_dict = dict()
    last_date = None
    # 按照日期一步步回测
    for _date in all_dates:
        print('Backtest at %s.' % _date)

        # 当期持仓股票列表
        before_sell_holding_codes = list(holding_code_dict.keys())

        # 处理复权
        if last_date is not None and len(before_sell_holding_codes) > 0:
            last_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': before_sell_holding_codes
                    },
                    'date': last_date,
                    'index': False
                },
                projection={
                    'code': True,
                    'au_factor': True,
                    '_id': False
                })

            code_last_aufactor_dict = dict()
            for last_daily in last_daily_cursor:
                code_last_aufactor_dict[
                    last_daily['code']] = last_daily['au_factor']

            current_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': before_sell_holding_codes
                    },
                    'date': _date,
                    'index': False
                },
                projection={
                    'code': True,
                    'au_factor': True,
                    '_id': False
                })

            for current_daily in current_daily_cursor:
                print(current_daily['code'], _date)
                current_aufactor = current_daily['au_factor']
                code = current_daily['code']
                before_volume = holding_code_dict[code]['volume']
                if code in code_last_aufactor_dict:
                    last_aufactor = code_last_aufactor_dict[code]
                    after_volume = int(before_volume *
                                       (current_aufactor / last_aufactor))
                    holding_code_dict[code]['volume'] = after_volume
                    print('持仓量调整:%s, %6d, %10.6f, %6d, %10.6f' %
                          (code, before_volume, last_aufactor, after_volume,
                           current_aufactor))

        # 卖出
        print('待卖股票池:', to_be_sold_codes, flush=True)
        if len(to_be_sold_codes) > 0:
            sell_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': list(to_be_sold_codes)
                    },
                    'date': _date,
                    'index': False,
                    'is_trading': True
                },
                projection={
                    'open': True,
                    'code': True
                })

            for sell_daily in sell_daily_cursor:
                code = sell_daily['code']
                if code in before_sell_holding_codes:
                    holding_stock = holding_code_dict[code]
                    holding_volume = holding_stock['volume']
                    sell_price = sell_daily['open']
                    sell_amount = holding_volume * sell_price
                    cash += sell_amount

                    cost = holding_stock['cost']
                    single_profit = (sell_amount - cost) * 100 / cost
                    print('卖出 %s, %6d, %6.2f, %8.2f, %4.2f' %
                          (code, holding_volume, sell_price, sell_amount,
                           single_profit))

                    del holding_code_dict[code]
                    to_be_sold_codes.remove(code)

        print('卖出后,现金: %10.2f' % cash)

        # 买入
        print('待买股票池:', to_be_bought_codes, flush=True)
        if len(to_be_bought_codes) > 0:
            buy_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': list(to_be_bought_codes)
                    },
                    'date': _date,
                    'is_trading': True,
                    'index': False
                },
                projection={
                    'code': True,
                    'open': True
                })

            for buy_daily in buy_daily_cursor:
                if cash > single_position:
                    buy_price = buy_daily['open']
                    code = buy_daily['code']
                    volume = int(int(single_position / buy_price) / 100) * 100
                    buy_amount = buy_price * volume
                    cash -= buy_amount
                    holding_code_dict[code] = {
                        'volume': volume,
                        'cost': buy_amount,
                        'last_value': buy_amount
                    }

                    print('买入 %s, %6d, %6.2f, %8.2f' %
                          (code, volume, buy_price, buy_amount))

        print('买入后,现金: %10.2f' % cash)

        # 持仓股代码列表
        holding_codes = list(holding_code_dict.keys())
        # 如果调整日,则获取新一期的股票列表
        if _date in adjust_dates:
            print('股票池调整日:%s,备选股票列表:' % _date, flush=True)

            # 暂存为上期的日期
            if this_phase_codes is not None:
                last_phase_codes = this_phase_codes
            this_phase_codes = date_codes_dict[_date]
            print(this_phase_codes, flush=True)

            # 找到所有调出股票代码,在第二日开盘时卖出
            if last_phase_codes is not None:
                out_codes = find_out_stocks(last_phase_codes, this_phase_codes)
                for out_code in out_codes:
                    if out_code in holding_code_dict:
                        to_be_sold_codes.add(out_code)

        # 检查是否有需要第二天卖出的股票
        for holding_code in holding_codes:
            if is_k_down_break_ma10(holding_code, _date):
                to_be_sold_codes.add(holding_code)

        # 检查是否有需要第二天买入的股票
        to_be_bought_codes.clear()
        if this_phase_codes is not None:
            for _code in this_phase_codes:
                if _code not in holding_codes and is_k_up_break_ma10(
                        _code, _date):
                    to_be_bought_codes.add(_code)

        # 计算总资产
        total_value = 0
        holding_daily_cursor = DB_CONN['daily'].find(
            {
                'code': {
                    '$in': holding_codes
                },
                'date': _date
            },
            projection={
                'close': True,
                'code': True
            })
        for holding_daily in holding_daily_cursor:
            code = holding_daily['code']
            holding_stock = holding_code_dict[code]
            value = holding_daily['close'] * holding_stock['volume']
            total_value += value

            profit = (value -
                      holding_stock['cost']) * 100 / holding_stock['cost']
            one_day_profit = (value - holding_stock['last_value']
                              ) * 100 / holding_stock['last_value']

            holding_stock['last_value'] = value
            print('持仓: %s, %10.2f, %4.2f, %4.2f' %
                  (code, value, profit, one_day_profit))

        total_capital = total_value + cash

        hs300_current_value = DB_CONN['daily'].find_one(
            {
                'code': '000300',
                'index': True,
                'date': _date
            },
            projection={'close': True})['close']

        print('收盘后,现金: %10.2f, 总资产: %10.2f' % (cash, total_capital))
        last_date = _date
        df_profit.loc[_date] = {
            'net_value':
            round(total_capital / 1e7, 2),
            'profit':
            round(100 * (total_capital - 1e7) / 1e7, 2),
            'hs300':
            round(
                100 * (hs300_current_value - hs300_begin_value) /
                hs300_begin_value, 2)
        }

    df_profit.plot(title='Backtest Result', y=['profit', 'hs300'], kind='line')
    plt.show()
Exemple #27
0
def backtest(begin_date,
             end_date,
             stop_method=None,
             pos_method='equal',
             is_saving=False,
             save_name=None):
    """
    Arguments:
        begin_date: 回测开始日期
        end_date: 回测结束日期
        stop_method : 止损方式
            None : 无止损
            fixed : 固定比例止损
            float : 浮动止损
            ATR_float_dynamic : 动态ATR浮动止损
            ATR_float_static : 静态ATR浮动止损
        pos_method : 头寸分配方式
            equal : 均仓分配头寸
            atr : 按照ATR分配头寸
    
    Returns:
        Account: 数据类型,dict
            
            init_assets : 初始资产, 默认1E7
            history_table : 交割单
            net_value : 每日净值
            final_net_value : 最终日净值
            profit : 收益
            day_profit : 每日收益
            positions : 每日仓位
            stop_loss : 止损的方式和止损参数
            position_manage : 头寸管理方式和相关参数
    """
    # 记录止损时间点
    #    stop_lose_position_date_current = []
    #    stop_lose_position_date = []

    # 记录回测账户信息
    Account = {}

    # 仓位相关的初始化
    position_manage = {}
    if pos_method == 'equal':
        single_position = 2E5
        position_manage['头寸分配方式'] = '均仓'
        Account['position_manage'] = position_manage
    elif pos_method == 'atr':
        single_position = 0
        position_manage['头寸分配方式'] = 'ATR分配头寸'
        position_manage['ATR_WIN'] = ATR_WIN
        position_manage['RISK_RATIO'] = RISK_RATIO
        Account['position_manage'] = position_manage

    positions = pd.Series()  # 记录每日仓位信息
    stop_loss = {}

    cash = INIT_ASSETS
    init_assets = cash
    Account['init_assets'] = init_assets
    Account['start'] = begin_date
    Account['end'] = end_date

    if stop_method is None:
        Account['stop_loss'] = '无止损'
    elif stop_method == 'fixed':
        stop_loss['单日跌幅比例'] = SINGLE_DAY_MAX_DROP_RATE
        stop_loss['累计跌幅比例'] = MAX_DROP_RATE
        stop_loss['止损方式'] = '固定比例止损'
        Account['stop_loss'] = stop_loss
    elif stop_method == 'float':
        stop_loss['跌幅比例'] = MAX_DROP_RATE
        stop_loss['止损方式'] = '浮动止损'
        Account['stop_loss'] = stop_loss
    elif (stop_method == 'ATR_float_dynamic') or (stop_method
                                                  == 'ATR_float_static'):
        stop_loss['ATR_WIN'] = ATR_WIN
        stop_loss['ATR_RATIO'] = ATR_RATIO
        if stop_method == 'ATR_float_dynamic':
            stop_loss['止损方式'] = '动态ATR浮动止损'
        elif stop_method == 'ATR_float_static':
            stop_loss['止损方式'] = '静态ATR浮动止损'
        Account['stop_loss'] = stop_loss

    # 时间为key的净值、收益和同期沪深基准
    df_profit = pd.DataFrame(columns=['net_value', 'profit', 'hs300'])
    # 时间为key的单日收益和同期沪深基准
    df_day_profit = pd.DataFrame(columns=['profit', 'hs300'])

    all_dates = get_trading_dates(begin_date, end_date)

    hs300_begin_value = DB_CONN['daily'].find_one(
        {
            'code': '000300',
            'date': all_dates[0],
            'index': True
        },
        projection={'close': True})['close']

    adjust_dates, date_codes_dict = stock_pool(begin_date, end_date)

    last_phase_codes = None
    this_phase_codes = None
    to_be_sold_codes = set()
    to_be_bought_codes = set()
    holding_code_dict = dict()
    last_date = None

    last_entry_dates = {}  # 用于记录入场时间
    history_table = pd.DataFrame()  # 记录 交割单

    last_total_capital = 1e7  # 前一天的总资产值,初始值为初始总资产
    last_hs300_close = hs300_begin_value  # 前一天的HS300值,初始值为第一天的值
    net_value = 1  # 净值

    count = 0
    # 按照日期一步步回测
    for _date in all_dates:
        print('Backtest at %s.' % _date)

        # 当期持仓股票列表
        before_sell_holding_codes = list(holding_code_dict.keys())

        # 处理复权
        if last_date is not None and len(before_sell_holding_codes) > 0:
            last_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': before_sell_holding_codes
                    },
                    'date': last_date,
                    'index': False
                },
                projection={
                    'code': True,
                    'au_factor': True,
                    '_id': False
                })

            code_last_aufactor_dict = dict()
            for last_daily in last_daily_cursor:
                code_last_aufactor_dict[
                    last_daily['code']] = last_daily['au_factor']

            current_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': before_sell_holding_codes
                    },
                    'date': _date,
                    'index': False
                },
                projection={
                    'code': True,
                    'au_factor': True,
                    '_id': False
                })

            for current_daily in current_daily_cursor:
                print(current_daily['code'], _date)
                current_aufactor = current_daily['au_factor']
                code = current_daily['code']
                before_volume = holding_code_dict[code]['volume']
                if code in code_last_aufactor_dict:
                    last_aufactor = code_last_aufactor_dict[code]
                    after_volume = int(before_volume *
                                       (current_aufactor / last_aufactor))
                    holding_code_dict[code]['volume'] = after_volume
                    print('持仓量调整:%s, %6d, %10.6f, %6d, %10.6f' %
                          (code, before_volume, last_aufactor, after_volume,
                           current_aufactor))

        # 卖出
        print('待卖股票池:', to_be_sold_codes, flush=True)
        if len(to_be_sold_codes) > 0:
            sell_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': list(to_be_sold_codes)
                    },
                    'date': _date,
                    'index': False,
                    'is_trading': True
                },
                projection={
                    'open': True,
                    'code': True,
                    'low_limit': True
                })

            for sell_daily in sell_daily_cursor:
                code = sell_daily['code']

                # 若开盘价是跌停价不准卖出
                open_price = sell_daily['open']
                low_limit = sell_daily['low_limit']

                if (code in before_sell_holding_codes) & (open_price >
                                                          low_limit):
                    holding_stock = holding_code_dict[code]
                    holding_volume = holding_stock['volume']
                    sell_price = sell_daily['open']
                    sell_amount = holding_volume * sell_price
                    cash += sell_amount

                    cost = holding_stock['cost']
                    single_profit = (sell_amount - cost) * 100 / cost

                    last_entry_dates[code] = None

                    print('卖出 %s, %6d, %6.2f, %8.2f, %4.2f' %
                          (code, holding_volume, sell_price, sell_amount,
                           single_profit))

                    # 记录 交易记录
                    count += 1
                    _order = {
                        'datetime': _date,
                        'code': code,
                        'price': sell_price,
                        'amount': -1 * holding_volume,
                        'cash': cash
                    }
                    temp = pd.DataFrame(data=_order, index=[count])
                    history_table = pd.concat([history_table, temp])

                    del holding_code_dict[code]
                    to_be_sold_codes.remove(code)

        print('卖出后,现金: %10.2f' % cash)

        # 买入
        print('待买股票池:', to_be_bought_codes, flush=True)
        if len(to_be_bought_codes) > 0:
            buy_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': list(to_be_bought_codes)
                    },
                    'date': _date,
                    'index': False,
                    'is_trading': True
                },
                projection={
                    'code': True,
                    'open': True,
                    'high_limit': True
                })

            for buy_daily in buy_daily_cursor:

                # 若开盘价是涨停价不准买入
                open_price = buy_daily['open']
                high_limit = buy_daily['high_limit']
                code = buy_daily['code']

                # ===========================ATR分配头寸 code start=========================
                if pos_method == 'atr':
                    ATR = calc_ATR(code, _date)
                    if ATR is not None:
                        single_position = init_assets * RISK_RATIO / (
                            ATR_RATIO * ATR) // 100 * 100
                    else:
                        single_position = 0

                if (cash > single_position) & (open_price < high_limit) & (
                        single_position != 0):
                    buy_price = buy_daily['open']

                    volume = int(int(single_position / buy_price) / 100) * 100
                    if volume > 100:
                        buy_amount = buy_price * volume
                        cash -= buy_amount
                        holding_code_dict[code] = {
                            'volume': volume,
                            'cost': buy_amount,
                            'last_value': buy_amount
                        }

                        last_entry_dates[code] = _date

                        print('买入 %s, %6d, %6.2f, %8.2f' %
                              (code, volume, buy_price, buy_amount))

                        # 记录 交易记录
                        count += 1
                        _order = {
                            'datetime': _date,
                            'code': code,
                            'price': buy_price,
                            'amount': volume,
                            'cash': cash
                        }
                        temp = pd.DataFrame(data=_order, index=[count])
                        history_table = pd.concat([history_table, temp])

        print('买入后,现金: %10.2f' % cash)

        # 持仓股代码列表
        holding_codes = list(holding_code_dict.keys())
        # 如果调整日,则获取新一期的股票列表
        if _date in adjust_dates:
            print('股票池调整日:%s,备选股票列表:' % _date, flush=True)

            # 暂存为上期的日期
            if this_phase_codes is not None:
                last_phase_codes = this_phase_codes
            this_phase_codes = date_codes_dict[_date]
            print(this_phase_codes, flush=True)

            # 找到所有调出股票代码,在第二日开盘时卖出
            if last_phase_codes is not None:
                out_codes = find_out_stocks(last_phase_codes, this_phase_codes)
                for out_code in out_codes:
                    if out_code in holding_code_dict:
                        to_be_sold_codes.add(out_code)

        # 检查是否有需要第二天卖出的股票
        for holding_code in holding_codes:
            if is_k_down_break_ma10(holding_code, _date):
                to_be_sold_codes.add(holding_code)

            # ===========================止损判断 code start=========================
            if stop_method is not None:
                stop_loss_positions(holding_code, _date, last_entry_dates,
                                    to_be_sold_codes, stop_method)

        # 检查是否有需要第二天买入的股票
        to_be_bought_codes.clear()
        if this_phase_codes is not None:
            for _code in this_phase_codes:
                if _code not in holding_codes and is_k_up_break_ma10(
                        _code, _date):
                    to_be_bought_codes.add(_code)

        # 计算总资产
        total_value = 0
        holding_daily_cursor = DB_CONN['daily'].find(
            {
                'code': {
                    '$in': holding_codes
                },
                'date': _date
            },
            projection={
                'close': True,
                'code': True
            })
        for holding_daily in holding_daily_cursor:
            code = holding_daily['code']
            holding_stock = holding_code_dict[code]
            value = holding_daily['close'] * holding_stock['volume']
            total_value += value

            profit = (value -
                      holding_stock['cost']) * 100 / holding_stock['cost']
            one_day_profit = (value - holding_stock['last_value']
                              ) * 100 / holding_stock['last_value']

            holding_stock['last_value'] = value
            print('持仓: %s, %10.2f, %4.2f, %4.2f' %
                  (code, value, profit, one_day_profit))

        total_capital = total_value + cash
        positions.loc[_date] = total_value / total_capital

        hs300_current_value = DB_CONN['daily'].find_one(
            {
                'code': '000300',
                'date': _date,
                'index': True
            },
            projection={'close': True})['close']

        print('收盘后,现金: %10.2f, 总资产: %10.2f' % (cash, total_capital))
        last_date = _date
        net_value = np.round(total_capital / 1e7, 4)
        df_profit.loc[_date] = {
            'net_value':
            np.round(total_capital / 1e7, 4),
            'profit':
            np.round(100 * (total_capital - 1e7) / 1e7, 4),
            'hs300':
            np.round(
                100 * (hs300_current_value - hs300_begin_value) /
                hs300_begin_value, 4)
        }
        # 计算单日收益
        df_day_profit.loc[_date] = {
            'profit':
            np.round(
                100 * (total_capital - last_total_capital) /
                last_total_capital, 4),
            'hs300':
            np.round(
                100 * (hs300_current_value - last_hs300_close) /
                last_hs300_close, 4)
        }
        # 暂存当日的总资产和HS300,作为下一个交易日计算单日收益的基础
        last_total_capital = total_capital
        last_hs300_close = hs300_current_value

    Account['history_table'] = history_table
    Account['net_value'] = df_profit['net_value']
    Account['final_net_value'] = net_value
    Account['profit'] = df_profit
    Account['day_profit'] = df_day_profit
    Account['positions'] = positions

    try:
        if is_saving:
            if save_name is None:
                print('保存失败,没有输入save_name, 请使用save_file函数重新保存')
            else:
                save_file(Account, save_name)
    except:
        print('保存失败,请使用save_file函数重新保存')

    return Account
def stock_pool(begin_date, end_date):
    """
    股票池的选股逻辑

    :param begin_date: 开始日期
    :param end_date: 结束日期
    :return: tuple,所有调整日,以及调整日和代码列表对应的dict
    """

    """
    下面的几个参数可以自己修改
    """
    # 调整周期是7个交易日,可以改变的参数
    adjust_interval = 7
    # PE的范围
    pe_range = (0, 30)
    # PE的排序方式, ASCENDING - 从小到大,DESCENDING - 从大到小
    sort = ASCENDING
    # 股票池内的股票数量
    pool_size = 100

    # 返回值:调整日和当期股票代码列表
    adjust_date_codes_dict = dict()
    # 返回值:所有的调整日列表
    all_adjust_dates = []

    # 获取指定时间范围内的所有交易日列表,按照日期正序排列
    all_dates = get_trading_dates(begin_date=begin_date, end_date=end_date)

    # 上一期的所有股票代码
    last_phase_codes = []
    # 在调整日调整股票池
    for _index in range(0, len(all_dates), adjust_interval):
        # 保存调整日
        adjust_date = all_dates[_index]
        all_adjust_dates.append(adjust_date)

        print('调整日期: %s' % adjust_date, flush=True)

        # 查询出调整当日,0 < pe < 30,且非停牌的股票
        # 最重要的一点是,按照pe正序排列,只取前100只
        daily_cursor = daily.find(
            {'date': adjust_date, 'pe': {'$lt': pe_range[1], '$gt': pe_range[0]},
             'is_trading': True},
            sort=[('pe', sort)],
            projection={'code': True},
            limit=pool_size
        )

        # 拿到所有的股票代码
        codes = [x['code'] for x in daily_cursor]

        # 本期股票列表
        this_phase_codes = []

        # 如果上期股票代码列表不为空,则查询出上次股票池中正在停牌的股票
        if len(last_phase_codes) > 0:
            suspension_cursor = daily.find(
                # 查询是股票代码、日期和是否为交易,这里is_trading=False
                {'code': {'$in': last_phase_codes}, 'date': adjust_date, 'is_trading': False},
                # 只需要使用股票代码
                projection={'code': True}
            )
            # 拿到股票代码
            suspension_codes = [x['code'] for x in suspension_cursor]

            # 保留股票池中正在停牌的股票
            this_phase_codes = suspension_codes

        # 打印出所有停牌的股票代码
        print('上期停牌', flush=True)
        print(this_phase_codes, flush=True)

        # 用新的股票将剩余位置补齐
        this_phase_codes += codes[0: pool_size - len(this_phase_codes)]
        # 将本次股票设为下次运行的时的上次股票池
        last_phase_codes = this_phase_codes

        # 建立该调整日和股票列表的对应关系
        adjust_date_codes_dict[adjust_date] = this_phase_codes

        print('最终出票', flush=True)
        print(this_phase_codes, flush=True)

    # 返回结果
    return all_adjust_dates, adjust_date_codes_dict
Exemple #29
0
def backtest(begin_date, end_date):
    """
    策略回测。结束后打印出收益曲线(沪深300基准)、年化收益、最大回撤、

    :param begin_date: 回测开始日期
    :param end_date: 回测结束日期
    """
    stop_lose_position_date_current = []
    stop_lose_position_date = []

    cash = 1E7
    single_position = 2E5

    # 时间为key的净值、收益和同期沪深基准
    df_profit = pd.DataFrame(columns=['net_value', 'profit', 'hs300'])

    all_dates = get_trading_dates(begin_date, end_date)

    hs300_begin_value = DB_CONN['daily'].find_one(
        {
            'code': '000300',
            'date': all_dates[0],
            'index': True
        },
        projection={'close': True})['close']

    adjust_dates, date_codes_dict = stock_pool(begin_date, end_date)

    last_phase_codes = None
    this_phase_codes = None
    to_be_sold_codes = set()
    to_be_bought_codes = set()
    holding_code_dict = dict()
    last_date = None
    last_entry_dates = {}

    # 按照日期一步步回测
    for _date in all_dates:
        print('Backtest at %s.' % _date)

        # 当期持仓股票列表
        before_sell_holding_codes = list(holding_code_dict.keys())

        # 处理复权
        if last_date is not None and len(before_sell_holding_codes) > 0:
            last_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': before_sell_holding_codes
                    },
                    'date': last_date,
                    'index': False
                },
                projection={
                    'code': True,
                    'au_factor': True,
                    '_id': False
                })

            code_last_aufactor_dict = dict()
            for last_daily in last_daily_cursor:
                code_last_aufactor_dict[
                    last_daily['code']] = last_daily['au_factor']

            current_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': before_sell_holding_codes
                    },
                    'date': _date,
                    'index': False
                },
                projection={
                    'code': True,
                    'au_factor': True,
                    '_id': False
                })

            for current_daily in current_daily_cursor:
                print(current_daily['code'], _date)
                current_aufactor = current_daily['au_factor']
                code = current_daily['code']
                before_volume = holding_code_dict[code]['volume']
                if code in code_last_aufactor_dict:
                    last_aufactor = code_last_aufactor_dict[code]
                    after_volume = int(before_volume *
                                       (current_aufactor / last_aufactor))
                    holding_code_dict[code]['volume'] = after_volume
                    print('持仓量调整:%s, %6d, %10.6f, %6d, %10.6f' %
                          (code, before_volume, last_aufactor, after_volume,
                           current_aufactor))

        # 卖出
        print('待卖股票池:', to_be_sold_codes, flush=True)
        if len(to_be_sold_codes) > 0:
            sell_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': list(to_be_sold_codes)
                    },
                    'date': _date,
                    'index': False,
                    'is_trading': True
                },
                projection={
                    'open': True,
                    'code': True,
                    'low_limit': True
                })

            for sell_daily in sell_daily_cursor:
                code = sell_daily['code']

                # 若开盘价是跌停价不准卖出
                open_price = sell_daily['open']
                low_limit = sell_daily['low_limit']

                if (code in before_sell_holding_codes) & (open_price >
                                                          low_limit):
                    holding_stock = holding_code_dict[code]
                    holding_volume = holding_stock['volume']
                    sell_price = sell_daily['open']
                    sell_amount = holding_volume * sell_price
                    cash += sell_amount

                    cost = holding_stock['cost']
                    single_profit = (sell_amount - cost) * 100 / cost

                    last_entry_dates[code] = None

                    print('卖出 %s, %6d, %6.2f, %8.2f, %4.2f' %
                          (code, holding_volume, sell_price, sell_amount,
                           single_profit))

                    del holding_code_dict[code]
                    to_be_sold_codes.remove(code)

        print('卖出后,现金: %10.2f' % cash)

        # 买入
        print('待买股票池:', to_be_bought_codes, flush=True)
        if len(to_be_bought_codes) > 0:
            buy_daily_cursor = DB_CONN['daily'].find(
                {
                    'code': {
                        '$in': list(to_be_bought_codes)
                    },
                    'date': _date,
                    'index': False,
                    'is_trading': True
                },
                projection={
                    'code': True,
                    'open': True,
                    'high_limit': True
                })

            for buy_daily in buy_daily_cursor:

                # 若开盘价是涨停价不准买入
                open_price = buy_daily['open']
                high_limit = buy_daily['high_limit']

                if (cash > single_position) & (open_price < high_limit):
                    buy_price = buy_daily['open']
                    code = buy_daily['code']
                    volume = int(int(single_position / buy_price) / 100) * 100
                    buy_amount = buy_price * volume
                    cash -= buy_amount
                    holding_code_dict[code] = {
                        'volume': volume,
                        'cost': buy_amount,
                        'last_value': buy_amount
                    }

                    last_entry_dates[code] = _date

                    print('买入 %s, %6d, %6.2f, %8.2f' %
                          (code, volume, buy_price, buy_amount))

        print('买入后,现金: %10.2f' % cash)

        # 持仓股代码列表
        holding_codes = list(holding_code_dict.keys())
        # 如果调整日,则获取新一期的股票列表
        if _date in adjust_dates:
            print('股票池调整日:%s,备选股票列表:' % _date, flush=True)

            # 暂存为上期的日期
            if this_phase_codes is not None:
                last_phase_codes = this_phase_codes
            this_phase_codes = date_codes_dict[_date]
            print(this_phase_codes, flush=True)

            # 找到所有调出股票代码,在第二日开盘时卖出
            if last_phase_codes is not None:
                out_codes = find_out_stocks(last_phase_codes, this_phase_codes)
                for out_code in out_codes:
                    if out_code in holding_code_dict:
                        to_be_sold_codes.add(out_code)

        # 检查是否有需要第二天卖出的股票
        for holding_code in holding_codes:
            if is_k_down_break_ma10(holding_code, _date):
                to_be_sold_codes.add(holding_code)
            """
            止损条件:
                1.当日亏损超过3%
                2.累计亏损超过10%
            满足其一就卖出
            注意,这里的回测逻辑是无法应用到模拟盘的,因为用了当天的收盘价去计算亏损;
            当然在回测里没问题,毕竟是第二天要卖出的股票,所以姑且当做收盘后的判断吧;
            """
            # 当天收盘价
            current_close = DB_CONN['daily'].find_one({
                'code': holding_code,
                'date': _date,
                'index': False
            })['close']

            # 买入时的价格和日期
            entry_date = last_entry_dates[holding_code]
            entry_daily_cursor = DB_CONN['daily'].find_one({
                'code': holding_code,
                'date': entry_date,
                'index': False
            })
            entry_price = entry_daily_cursor['open']
            entry_date_close = entry_daily_cursor['close']

            if (entry_date == _date) & (((entry_price - entry_date_close) /
                                         entry_price) > CURRENT_MAX_DROP_RATE):
                to_be_sold_codes.add(holding_code)
                stop_lose_position_date_current.append(_date)

            elif ((entry_price - current_close) / entry_price) > MAX_DROP_RATE:
                to_be_sold_codes.add(holding_code)
                stop_lose_position_date.append(_date)

        # 检查是否有需要第二天买入的股票
        to_be_bought_codes.clear()
        if this_phase_codes is not None:
            for _code in this_phase_codes:
                if _code not in holding_codes and is_k_up_break_ma10(
                        _code, _date):
                    to_be_bought_codes.add(_code)

        # 计算总资产
        total_value = 0
        holding_daily_cursor = DB_CONN['daily'].find(
            {
                'code': {
                    '$in': holding_codes
                },
                'date': _date
            },
            projection={
                'close': True,
                'code': True
            })
        for holding_daily in holding_daily_cursor:
            code = holding_daily['code']
            holding_stock = holding_code_dict[code]
            value = holding_daily['close'] * holding_stock['volume']
            total_value += value

            profit = (value -
                      holding_stock['cost']) * 100 / holding_stock['cost']
            one_day_profit = (value - holding_stock['last_value']
                              ) * 100 / holding_stock['last_value']

            holding_stock['last_value'] = value
            print('持仓: %s, %10.2f, %4.2f, %4.2f' %
                  (code, value, profit, one_day_profit))

        total_capital = total_value + cash

        hs300_current_value = DB_CONN['daily'].find_one(
            {
                'code': '000300',
                'date': _date,
                'index': True
            },
            projection={'close': True})['close']

        print('收盘后,现金: %10.2f, 总资产: %10.2f' % (cash, total_capital))
        last_date = _date
        df_profit.loc[_date] = {
            'net_value':
            round(total_capital / 1e7, 2),
            'profit':
            round(100 * (total_capital - 1e7) / 1e7, 2),
            'hs300':
            round(
                100 * (hs300_current_value - hs300_begin_value) /
                hs300_begin_value, 2)
        }

    print(np.sort(list(set(stop_lose_position_date))))
    print(np.sort(list(set(stop_lose_position_date_current))))
    df_profit.plot(title='Backtest Result', y=['profit', 'hs300'], kind='line')
    plt.show()
Exemple #30
0
def backtest(begin_date, end_date, fun_sell, fun_buy):
    '''
    回测系统
    parameter:
    fun_sell: 卖出信号
    fun_buy: 买入信号函数
    '''
    # 设置初始值
    cash = 1E7
    single_positon = 2E5
    df_profit = pd.DataFrame(columns=['net_value', 'profit', 'hs300'])
    # 得到回测日期
    all_dates = get_trading_dates(begin_date, end_date)
    adjust_dates, date_codes_dict = stock_pool(begin_date, end_date)
    hs300_begin_value = daily_collection.find_one(
        {
            'code': '000300',
            'index': True,
            'date': adjust_dates[0]
        },
        projection={
            'close': True,
            '_id': False
        })['close']

    holding_code_dict = dict()
    last_date = None
    this_phase_codes = None
    last_phase_codes = None
    to_be_bought_codes = set()
    to_be_sold_codes = set()

    for _date in all_dates:
        print('Back test begin at: %s' % _date)
        before_sell_holding_codes = list(holding_code_dict.keys())

        # 对于每一个回测日期处理复权
        if last_date is not None and len(before_sell_holding_codes) > 0:
            # produce_au(before_sell_holding_codes)
            last_daily_cursor = daily_collection.find(
                {
                    'code': {
                        '$in': before_sell_holding_codes
                    },
                    'date': last_date
                },
                projection={
                    'code': True,
                    '_id': False,
                    'au_factor': True
                }).hint([('code', 1), ('date', 1)])

            code_last_aufactor_dict = dict()
            for last_daily in last_daily_cursor:
                code_last_aufactor_dict[
                    last_daily['code']] = last_daily['au_factor']

            current_daily_cursor = daily_collection.find(
                {
                    'code': {
                        '$in': before_sell_holding_codes
                    },
                    'date': _date
                },
                projection={
                    'code': True,
                    '_id': False,
                    'au_factor': True
                }).hint([('code', 1), ('date', 1)])

            for current_daily in current_daily_cursor:
                current_aufactor = current_daily['au_factor']
                code = current_daily['code']
                before_volume = holding_code_dict[code]['volume']
                if code in code_last_aufactor_dict:
                    last_aufactor = code_last_aufactor_dict[code]
                    after_volume = int(before_volume *
                                       (current_aufactor / last_aufactor))
                    holding_code_dict[code]['volume'] = after_volume
                    print(
                        'hold volume adjust: code: %s, %6d, %10.6f, %6d, %10.6f'
                        % (code, before_volume, last_aufactor, after_volume,
                           current_aufactor))

        # 卖出上一期持仓股
        # print('to sell stocks: %s' % to_be_sold_codes, flush=True)
        if len(to_be_sold_codes) > 0:
            sell_daily_cursor = daily_collection.find(
                {
                    'code': {
                        '$in': list(to_be_sold_codes)
                    },
                    'date': _date,
                    'index': True,
                    'is_trading': True
                },
                projection={
                    'open': True,
                    'code': True,
                    '_id': False
                }).hint([('code', 1), ('date', -1)])

            for sell_daily in sell_daily_cursor:
                sell_code = sell_daily['code']
                if sell_code in before_sell_holding_codes:
                    holding_stock = before_sell_holding_codes[code]
                    sell_price = sell_daily['open']
                    holding_volume = holding_stock['volume']
                    sell_amount = holding_volume * sell_price
                    cash += sell_amount

                    cost = holding_stock['cost']
                    single_profit = (sell_amount - cost) * 100 / cost
                    print('sell: %s, %6d, %6.2f, %8.2f, %4.2f' %
                          (code, holding_volume, sell_price, sell_amount,
                           single_profit))
                    del holding_code_dict[code]
                    to_be_sold_codes.remove(code)
        print('cash after sell: %10.2f' % cash)

        # 买入这一期股票
        # print('to buy stocks: ', to_be_bought_codes, flush=True)
        if len(to_be_bought_codes) > 0:
            buy_daily_cursor = daily_collection.find(
                {
                    'code': {
                        '$in': list(to_be_bought_codes)
                    },
                    'date': _date,
                    'index': False,
                    'is_trading': True
                },
                projection={
                    'code': True,
                    '_id': False,
                    'open': True
                }).hint([('code', 1), ('date', -1)])

            for buy_daily in buy_daily_cursor:
                if cash > single_positon:
                    code = buy_daily['code']
                    buy_price = buy_daily['open']
                    buy_volume = int(
                        int(single_positon / buy_price) / 100) * 100
                    buy_amount = buy_price * buy_volume
                    cash -= buy_amount
                    holding_code_dict[code] = {
                        'volume': buy_volume,
                        'last_value': buy_amount,
                        'cost': buy_amount
                    }
                    print('buy %s, %6d, %6.2f, %8.2f' %
                          (code, buy_volume, buy_price, buy_amount))

        print('cash after buy: %10.2f' % cash)

        # 计算收益率
        holding_codes = list(holding_code_dict.keys())

        if _date in adjust_dates:
            print('stock pool adjust date: %s' % _date)

            if this_phase_codes is not None:
                last_phase_codes = this_phase_codes
            this_phase_codes = date_codes_dict[_date]
            # print(this_phase_codes, flush=True)

            if last_phase_codes is not None:
                out_codes = find_out_stocks(last_phase_codes, this_phase_codes)
                for out_code in out_codes:
                    if out_code in holding_code_dict:
                        to_be_sold_codes.add(out_code)

        for holding_code in holding_codes:
            if fun_sell(holding_code, _date):
                to_be_sold_codes.add(holding_code)

        to_be_bought_codes.clear()
        if this_phase_codes is not None:
            for _code in this_phase_codes:
                if _code not in holding_codes and fun_buy(_code, _date):
                    to_be_bought_codes.add(_code)

        # 计算持仓股票市值
        total_value = 0
        holding_daily_cursor = daily_collection.find(
            {
                'code': {
                    '$in': holding_codes
                },
                'date': _date,
                'index': False
            },
            projection={
                'code': True,
                '_id': False,
                'close': True
            }).hint([('code', 1), ('date', -1)])

        for holding_daily in holding_daily_cursor:
            code = holding_daily['code']
            holding_stock = holding_code_dict[code]
            value = holding_daily['close'] * holding_stock['volume']
            total_value += value

            profit = (value -
                      holding_stock['cost']) * 100 / holding_stock['cost']
            one_day_profit = (value - holding_stock['last_value']
                              ) * 100 / holding_stock['last_value']

            holding_stock['last_value'] = value
            # print('holding stocks: %s, %10.2f, %4.2f, %4.2f' %
            #         (code, value, profit, one_day_profit))
        # 计算总资产
        total_capital = total_value + cash

        # 计算基准收益
        hs300_current_value = daily_collection.find_one(
            {
                'code': '000300',
                'date': _date,
                'index': True
            },
            projection={
                'code': True,
                'close': True,
                '_id': False
            })['close']
        print('after close, cash: %10.2f, total_capital: %10.2f' %
              (cash, total_capital))
        dt_date = datetime.strptime(_date, '%Y-%m-%d')
        df_profit.loc[dt_date] = {
            'net_value':
            round(total_capital / 1e7, 2),
            'profit':
            round(100 * (total_capital - 1e7) / 1e7, 2),
            'hs300':
            round(
                100 * (hs300_current_value - hs300_begin_value) /
                hs300_begin_value, 2)
        }
    drawdown = compute_drawdown(df_profit['net_value'])
    annual_profit, sharpe_ratio = compute_sharpe_ratio(df_profit['net_value'])
    print(
        'Backtest result: %s - %s,  annual_profit: %7.3f, maxdrawdown:%7.3f, sharpe ratio: %4.2f'
        % (begin_date, end_date, annual_profit, drawdown, sharpe_ratio))

    df_profit.plot(title='Backtest Result', y=['profit', 'hs300'], kind='line')
    plt.show()