Esempio n. 1
0
class TrackingStopLoss(BaseStopLoss):
    def __init__(self, account, max_loss):
        BaseStopLoss.__init__(self, account)
        self.max_loss = max_loss
        self.dm = DataModule()

    def update_holding(self, code, date):
        """
        更新持仓股的最高价
        :param code:
        :param date:
        :return:
        """
        df_daily = self.dm.get_k_data(code,
                                      autype='hfq',
                                      begin_date=date,
                                      end_date=date)
        if df_daily.index.size > 0:
            df_daily.set_index(['date'], 1, inplace=True)
            close = df_daily.loc[date]['close']
            holding = self.account.get_holding(code)
            if 'highest' not in holding or holding['highest'] < close:
                holding['highest'] = close
                self.account.update_holding(code, holding)

    def is_stop(self, code, date):
        """
        判断股票在当前日期是否需要止损
        :param code: 股票代码
        :param date: 日期
        :return: True - 止损, False - 不止损
        """
        holding_stock = self.account.get_holding(code)
        print('止损判断:%s' % code, flush=True)
        print(holding_stock, flush=True)
        if holding_stock is not None:
            df_daily = self.dm.get_k_data(code,
                                          autype='hfq',
                                          begin_date=date,
                                          end_date=date)
            if df_daily.index.size > 0:
                df_daily.set_index(['date'], 1, inplace=True)
                close = df_daily.loc[date]['close']
                # 计算回落的百分比
                if 'highest' in holding_stock and close < holding_stock[
                        'highest']:
                    profit = (close - holding_stock['highest']) \
                            * 100 / holding_stock['highest']
                    print('止损判断:收盘价:%6.2f, 历史最高:%6.2f, 回撤:%5.2f' %
                          (close, holding_stock['highest'], profit),
                          flush=True)
                    return (profit < 0) & (abs(profit) >= abs(self.max_loss))

        return False
Esempio n. 2
0
    def is_stop(self, code, date):
        """
        判断是否需要止盈,盈利超过指定值,则止盈
        :param code: 股票代码
        :param date: 日期
        :return: True - 止盈,False - 不止盈
        """
        holding_stock = self.account.get_holding(code)
        print('止盈判断:%s' % code, flush=True)
        print(holding_stock, flush=True)
        dm = DataModule()
        if holding_stock is not None:
            df_daily = dm.get_k_data(code,
                                     autype=None,
                                     begin_date=date,
                                     end_date=date)
            if df_daily.index.size > 0:
                df_daily.set_index(['date'], 1, inplace=True)

                profit = (holding_stock['volume'] * df_daily.loc[date]['close'] - holding_stock['cost']) \
                         * 100 / holding_stock['cost']

                return profit >= self.max_profit

        return False
    def compute(self, begin_date, end_date):
        codes = get_all_codes()
        dm = DataModule()

        for code in codes:
            df_daily = dm.get_k_data(code, begin_date=begin_date, end_date=end_date)

            if df_daily.index.size == 0:
                continue

            # 当日放量下跌
            df_daily['change'] = df_daily['close'] - df_daily['pre_close']
            df_daily = df_daily[df_daily['change'] < 0]
            df_daily['last_volume'] = df_daily['volume'].shift(1)
            df_daily.dropna(inplace=True)
            df_daily['volume_change'] = round(df_daily['volume']/df_daily['last_volume'], 2)
            df_daily = df_daily[df_daily['volume_change'] > 1.5]

            # 收的阴线实体不大
            df_daily['entity'] = round(abs((df_daily['open'] -df_daily['close'])) * 100/df_daily['close'], 2)
            df_daily = df_daily[df_daily['entity'] < 3]

            # 大部分时间在昨日之上运行
            df_daily.set_index(['date'], 1, inplace=True)

            update_requests = []
            for date in df_daily.index:

                # 大部分时间在昨日之上运行
                pre_close = df_daily.loc[date]['pre_close']
                df_minute = dm.get_k_data(code, period='M1', begin_date=date, end_date=date)
                df_minute = df_minute[df_minute['close'] > pre_close]

                if df_minute.index.size > 150:
                    update_requests.append(UpdateOne({
                        'code': code, 'date': date},
                        {'$set': {'code': code, 'date': date}},
                        upsert=True))

            if len(update_requests) > 0:
                save_result = self.collection.bulk_write(update_requests, ordered=False)
                print('股票代码: %s, 因子: %s, 插入:%4d, 更新: %4d' %
                      (code, self.name, save_result.upserted_count, save_result.modified_count), flush=True)
Esempio n. 4
0
    def compute(self, begin_date, end_date):
        """
        计算指定时间段内所有股票的该因子的值,并保存到数据库中
        :param begin_date:  开始时间
        :param end_date: 结束时间
        """
        dm = DataModule()
        frc = FinanceReportCrawler()

        code_report_dict = frc.get_code_reports()

        codes = set(code_report_dict.keys())
        for code in codes:
            dailies = dm.get_k_data(code, autype=None, begin_date=begin_date, end_date=end_date)
            # 如果没有合适的数据
            if dailies.index.size == 0:
                continue

            # 业绩报告列表
            reports = code_report_dict[code]

            dailies.set_index(['date'], inplace=True)

            update_requests = []
            for current_date in dailies.index:
                # 用来保存最后一个公告日期小于等于当前日期的财报
                last_report = None
                for report in reports:
                    announced_date = report['announced_date']
                    # 如果公告日期大于当前调整日,则结束循环
                    if announced_date > current_date:
                        break

                    last_report = report

                # 如果找到了正确时间范围的年报, 则计算PE
                if last_report is not None:
                    pe = dailies.loc[current_date]['close'] / last_report['eps']
                    pe = round(pe, 3)

                    print('%s, %s, %s, eps: %5.2f, pe: %6.2f' %
                          (code, current_date, last_report['announced_date'], last_report['eps'], pe),
                          flush=True)

                    update_requests.append(
                        UpdateOne(
                            {'code': code, 'date': current_date},
                            {'$set': {'code': code, 'date': current_date, 'pe': pe}}, upsert=True))

            if len(update_requests) > 0:
                save_result = self.collection.bulk_write(update_requests, ordered=False)
                print('股票代码: %s, 因子: %s, 插入:%4d, 更新: %4d' %
                      (code, self.name, save_result.upserted_count, save_result.modified_count), flush=True)
Esempio n. 5
0
    def is_stop(self, code, date):
        """
        判断股票在当前日期是否需要止损
        :param code: 股票代码
        :param date: 日期
        :return: True - 止损, False - 不止损
        """
        holding_stock = self.account.get_holding(code)
        print('止损判断:%s' % code, flush=True)
        print(holding_stock, flush=True)
        dm = DataModule()
        if holding_stock is not None:
            df_daily = dm.get_k_data(code, autype=None, begin_date=date, end_date=date)
            if df_daily.index.size > 0:
                df_daily.set_index(['date'], 1, inplace=True)

                profit = (holding_stock['volume'] * df_daily.loc[date]['close'] - holding_stock['cost']) \
                        * 100 / holding_stock['cost']

                return (profit < 0) & (abs(profit) >= abs(self.max_loss))

        return False
Esempio n. 6
0
    def compute(self, begin_date, end_date):
        print(self.name, flush=True)

        dm = DataModule()
        df_daily = dm.get_k_data()
        print(df_daily)
Esempio n. 7
0
    def get_option_stocks(self):
        """
        实现股票池选股逻辑,找到指定日期范围的候选股票
        条件:0 < PE < 30, 按从小到大排序,剔除停牌后,取前100个;再平衡周期:7个交易日
        :return: tuple,再平衡的日期列表,以及一个dict(key: 再平衡日, value: 当期的股票列表)
        """

        factor_module = FactorModule()
        dm = DataModule()

        # 因为上证指数没有停牌不会缺数,所以用它作为交易日历,
        szzz_hq_df = dm.get_k_data('000001',
                                   index=True,
                                   begin_date=self.begin_date,
                                   end_date=self.end_date)
        all_dates = list(szzz_hq_df['date'])

        # 缓存股票和其对应有交易的日期
        code_dates_cache = dict()

        # 调整日和其对应的股票
        rebalance_date_codes_dict = dict()
        rebalance_dates = []

        # 保存上一期的股票池
        last_phase_codes = []
        # 所有的交易日数
        dates_count = len(all_dates)
        # 用再平衡周期作为步长循环
        for index in range(0, dates_count, self.interval):
            # 当前的调整日
            rebalance_date = all_dates[index]

            # 获取本期符合条件的备选股票
            df_pe = factor_module.get_single_date_factors('pe', rebalance_date)
            df_pe.sort_values('pe', ascending=True, inplace=True)
            # 只保留小于30的数据
            df_pe = df_pe[(0 < df_pe['pe']) & (df_pe['pe'] < 30)]
            df_pe.set_index(['code'], inplace=True)
            this_phase_option_codes = list(df_pe.index)[0:100]
            print(this_phase_option_codes, flush=True)

            # 本期入选的股票代码列表
            this_phase_codes = []

            # 找到在上一期的股票池,但是当前停牌的股票,保留在当期股票池中
            if len(last_phase_codes) > 0:
                for code in last_phase_codes:
                    if code not in list(code_dates_cache.keys()):
                        daily_ks = dm.get_k_data(code,
                                                 autype=None,
                                                 begin_date=self.begin_date,
                                                 end_date=self.end_date)
                        daily_ks.set_index(['date'], inplace=True)
                        code_dates_cache[code] = list(daily_ks.index)
                    if rebalance_date not in code_dates_cache[code]:
                        this_phase_codes.append(code)

            print('上期停牌的股票:', flush=True)
            print(this_phase_codes, flush=True)

            # 剩余的位置用当前备选股票的
            option_size = len(this_phase_option_codes)
            if option_size > (100 - len(this_phase_codes)):
                this_phase_codes += this_phase_option_codes[
                    0:100 - len(this_phase_codes)]
            else:
                this_phase_codes += this_phase_option_codes

            # 当期股票池作为下次循环的上期股票池
            last_phase_codes = this_phase_codes

            # 保存到返回结果中
            rebalance_date_codes_dict[rebalance_date] = this_phase_codes
            rebalance_dates.append(rebalance_date)

            print('当前最终的备选票:%s' % rebalance_date, flush=True)
            print(this_phase_codes, flush=True)

        return rebalance_dates, rebalance_date_codes_dict
Esempio n. 8
0
    def compute(self, begin_date, end_date):
        """
        计算指定时间段内所有股票的该因子的值,并保存到数据库中
        :param begin_date:  开始时间
        :param end_date: 结束时间
        """
        dm = DataModule()

        # 获取所有股票
        codes = get_all_codes()

        for code in codes:
            print('计算市盈率, %s' % code)
            df_daily = dm.get_k_data(code,
                                     autype=None,
                                     begin_date=begin_date,
                                     end_date=end_date)

            if df_daily.index.size > 0:
                df_daily.set_index(['date'], 1, inplace=True)

                update_requests = []
                for date in df_daily.index:
                    finance_report = DB_CONN['finance_report'].find_one(
                        {
                            'code': code,
                            'report_date': {
                                '$regex': '\d{4}-12-31'
                            },
                            'announced_date': {
                                '$lte': date
                            }
                        },
                        sort=[('announced_date', DESCENDING)])

                    if finance_report is None:
                        continue

                    # 计算滚动市盈率并保存到daily_k中
                    eps = 0
                    if finance_report['eps'] != '-':
                        eps = finance_report['eps']

                    # 计算PE
                    if eps != 0:
                        pe = round(df_daily.loc[date]['close'] / eps, 3)

                        print('%s, %s, %s, eps: %5.2f, pe: %6.2f' %
                              (code, date, finance_report['announced_date'],
                               finance_report['eps'], pe),
                              flush=True)

                        update_requests.append(
                            UpdateOne({
                                'code': code,
                                'date': date
                            }, {
                                '$set': {
                                    'code': code,
                                    'date': date,
                                    'pe': pe
                                }
                            },
                                      upsert=True))

                if len(update_requests) > 0:
                    save_result = self.collection.bulk_write(update_requests,
                                                             ordered=False)
                    print('股票代码: %s, 因子: %s, 插入:%4d, 更新: %4d' %
                          (code, self.name, save_result.upserted_count,
                           save_result.modified_count),
                          flush=True)
Esempio n. 9
0
class Backtest:
    def __init__(self, begin_date, end_date):
        self.begin_date = begin_date
        self.end_date = end_date
        self.dm = DataModule()
        self.code_daily_cache = dict()

    def start(self):
        """
        策略回测。结束后打印出收益曲线(沪深300基准)、年化收益、最大回撤、

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

        # 初始化信号对象
        daily_k_break_ma10 = DailyKBreakMA10Signal()

        low_pe_stock_pool = LowPeStockPool(self.begin_date, self.end_date, 7)

        # 保存持仓股的日期
        code_date_volume_dict = dict()

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

        # 因为上证指数没有停牌不会缺数,所以用它作为交易日历,
        szzz_hq_df = self.dm.get_k_data('000001',
                                        index=True,
                                        begin_date=self.begin_date,
                                        end_date=self.end_date)
        all_dates = list(szzz_hq_df['date'])

        # 获取沪深300在统计周期内的第一天的值
        hs300_k = self.dm.get_k_data('000300',
                                     index=True,
                                     begin_date=all_dates[0],
                                     end_date=all_dates[0])
        hs300_begin_value = hs300_k.loc[hs300_k.index[0]]['close']

        # 获取股票池数据
        rebalance_dates, date_codes_dict = low_pe_stock_pool.get_option_stocks(
        )

        # 获取回测周期内股票池内所有股票的收盘价和前收价
        all_option_code_set = set()
        for rebalance_date in rebalance_dates:
            for code in date_codes_dict[rebalance_date]:
                all_option_code_set.add(code)

        # 缓存股票的日线数据
        for code in all_option_code_set:
            dailies_df = self.dm.get_k_data(code,
                                            autype=None,
                                            begin_date=self.begin_date,
                                            end_date=self.end_date)
            dailies_hfq_df = self.dm.get_k_data(code,
                                                autype='hfq',
                                                begin_date=self.begin_date,
                                                end_date=self.end_date)
            # 计算复权因子
            dailies_df[
                'au_factor'] = dailies_hfq_df['close'] / dailies_df['close']
            dailies_df.set_index(['date'], inplace=True)

            self.code_daily_cache[code] = dailies_df

        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:

                for code in before_sell_holding_codes:
                    try:
                        dailies = self.code_daily_cache[code]

                        current_au_factor = dailies.loc[_date]['au_factor']
                        before_volume = holding_code_dict[code]['volume']
                        last_au_factor = dailies.loc[last_date]['au_factor']

                        after_volume = int(
                            before_volume *
                            (current_au_factor / last_au_factor))
                        holding_code_dict[code]['volume'] = after_volume
                        print('持仓量调整:%s, %6d, %10.6f, %6d, %10.6f' %
                              (code, before_volume, last_au_factor,
                               after_volume, current_au_factor),
                              flush=True)
                    except:
                        print('持仓量调整时,发生错误:%s, %s' % (code, _date), flush=True)

            # 卖出
            if len(to_be_sold_codes) > 0:
                code_set_tmp = set(to_be_sold_codes)
                for code in code_set_tmp:
                    try:
                        if code in before_sell_holding_codes:
                            holding_stock = holding_code_dict[code]
                            holding_volume = holding_stock['volume']
                            sell_price = self.code_daily_cache[code].loc[
                                _date]['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)
                    except:
                        print('卖出时,发生异常:%s, %s' % (code, _date), flush=True)

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

            # 买入
            if len(to_be_bought_codes) > 0:
                sorted_to_be_bought_list = list(to_be_bought_codes)
                sorted_to_be_bought_list.sort()
                for code in sorted_to_be_bought_list:
                    try:
                        if cash > single_position:
                            buy_price = self.code_daily_cache[code].loc[_date][
                                'open']
                            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),
                                  flush=True)
                    except:
                        print('买入时,发生错误:%s, %s' % (code, _date), flush=True)

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

            # 持仓股代码列表
            holding_codes = list(holding_code_dict.keys())
            # 如果调整日,则获取新一期的股票列表
            if _date in rebalance_dates:
                # 暂存为上期的日期
                if this_phase_codes is not None:
                    last_phase_codes = this_phase_codes
                this_phase_codes = date_codes_dict[_date]

                # 找到所有调出股票代码,在第二日开盘时卖出
                if last_phase_codes is not None:
                    out_codes = self.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)

            # 获取检测信号的开始日期和结束日期
            current_date_index = all_dates.index(_date)
            signal_begin_date = None
            if current_date_index >= 10:
                signal_begin_date = all_dates[current_date_index - 10]

            # 检查是否有需要第二天卖出的股票
            for holding_code in holding_codes:
                if daily_k_break_ma10.is_k_down_break_ma10(
                        holding_code, begin_date=signal_begin_date,
                        end_date=_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 \
                            daily_k_break_ma10.is_k_up_break_ma10(_code, begin_date=signal_begin_date, end_date=_date):
                        to_be_bought_codes.add(_code)

            # 计算总资产
            total_value = 0
            for code in holding_codes:
                try:
                    holding_stock = holding_code_dict[code]
                    value = self.code_daily_cache[code].loc[_date][
                        '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))

                    # 保存每一日股票的持仓数
                    code_date_volume_dict[code + '_' +
                                          _date] = holding_stock['volume']
                except:
                    print('计算收益时发生错误:%s, %s' % (code, _date), flush=True)

            total_capital = total_value + cash

            hs300_k_current = self.dm.get_k_data('000300',
                                                 index=True,
                                                 begin_date=_date,
                                                 end_date=_date)
            hs300_current_value = hs300_k_current.loc[
                hs300_k_current.index[0]]['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('Profit history start')
        for index_date in df_profit.index:
            print('%s, %6.2f, %6.2f' %
                  (index_date, df_profit.loc[index_date]['profit'],
                   df_profit.loc[index_date]['hs300']),
                  flush=True)
        print('Profit history end')

        drawdown = self.compute_drawdown(df_profit['net_value'])
        annual_profit, sharpe_ratio = self.compute_sharpe_ratio(
            df_profit['net_value'])

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

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

    def compute_sharpe_ratio(self, net_values):
        """
        计算夏普比率
        :param net_values: 净值列表
        """

        # 总交易日数
        trading_days = len(net_values)
        # 所有收益的DataFrame
        profit_df = DataFrame(columns={'profit'})
        # 收益之后,初始化为第一天的收益
        profit_df.loc[0] = {'profit': round((net_values[0] - 1) * 100, 2)}
        # 计算每天的收益
        for index in range(1, trading_days):
            # 计算每日的收益变化
            profit = (net_values[index] -
                      net_values[index - 1]) / net_values[index - 1]
            profit = round(profit * 100, 2)
            profit_df.loc[index] = {'profit': profit}

        # 计算标准差
        profit_std = pow(profit_df.var()['profit'], 1 / 2)

        # 年化收益
        annual_profit = self.compute_annual_profit(trading_days,
                                                   net_values[-1])

        # 夏普比率
        sharpe_ratio = (annual_profit - 4.75) / profit_std

        return annual_profit, sharpe_ratio

    def compute_drawdown(self, net_values):
        """
        计算最大回撤
        :param net_values: 净值列表
        """
        # 最大回撤初始值设为0
        max_drawdown = 0
        index = 0
        # 双层循环找出最大回撤
        for net_value in net_values:
            for sub_net_value in net_values[index:]:
                drawdown = 1 - sub_net_value / net_value
                if drawdown > max_drawdown:
                    max_drawdown = drawdown

            index += 1

        return max_drawdown

    def compute_annual_profit(self, trading_days, net_value):
        """
        计算年化收益
        """

        annual_profit = 0
        if trading_days > 0:
            # 计算年数
            years = trading_days / 245
            # 计算年化收益
            annual_profit = pow(net_value, 1 / years) - 1

        annual_profit = round(annual_profit * 100, 2)

        return annual_profit

    def find_out_stocks(self, last_phase_codes, this_phase_codes):
        """
        找到上期入选本期被调出的股票,这些股票将必须卖出
        :param last_phase_codes: 上期的股票列表
        :param this_phase_codes: 本期的股票列表
        :return: 被调出的股票列表
        """
        out_stocks = []

        for code in last_phase_codes:
            if code not in this_phase_codes:
                out_stocks.append(code)

        return out_stocks
Esempio n. 10
0
class Account:
    def __init__(self):
        self.holding = dict()
        self.holding_codes = set()
        self.dm = DataModule()

    def buy_in(self, code, volume, cost):
        self.holding[code] = {
            'volume': volume,
            'cost': cost,
            'last_value': cost
        }
        self.holding_codes.add(code)

    def sell_out(self, code):
        del self.holding[code]
        self.holding_codes.remove(code)

    def get_holding(self, code):
        """
        通过股票代码获取该股票的持仓情况
        :param code: 股票代码
        :return: 持仓对象,如果没有该股票的持仓,则返回None
        """
        if code in self.holding_codes:
            return self.holding[code]
        else:
            return None

    def update_holding(self, code, updated_holding):
        """
        更新该持仓股的字段
        :param code: 股票代码
        :param updated_holding: 新的持仓股信息
        """
        if code in self.holding_codes:
            self.holding[code] = updated_holding

    def adjust_holding_volume_at_open(self, last_date=None, current_date=None):
        """
        开盘时,处理持仓股的复权
        :param last_date: 上一个交易日
        :param current_date: 当前交易日
        """

        if last_date is not None and len(self.holding_codes) > 0:
            for code in self.holding_codes:
                try:
                    dailies = self.dm.get_k_data(code=code,
                                                 begin_date=last_date,
                                                 end_date=current_date)
                    if dailies.index.size == 2:
                        dailies.set_index(['date'], 1, inplace=True)
                        current_au_factor = dailies.loc[current_date][
                            'au_factor']
                        before_volume = self.holding[code]['volume']
                        last_au_factor = dailies.loc[last_date]['au_factor']

                        after_volume = int(
                            before_volume *
                            (current_au_factor / last_au_factor))
                        self.holding[code]['volume'] = after_volume
                        print('持仓量调整:%s, %6d, %10.6f, %6d, %10.6f' %
                              (code, before_volume, last_au_factor,
                               after_volume, current_au_factor),
                              flush=True)
                except:
                    print('持仓量调整时,发生错误:%s, %s' % (code, current_date),
                          flush=True)
                    traceback.print_exc()

    def get_total_value(self, date):
        """
        计算当期那持仓股在某一天的总市值,并更新持仓股的上一个市值
        :param date: 日期
        """
        total_value = 0
        dailies = self.dm.get_stocks_one_day_k_data(list(self.holding_codes),
                                                    date=date)
        if dailies.index.size > 0:
            dailies.set_index(['code'], 1, inplace=True)
            for code in self.holding_codes:
                try:
                    holding_stock = self.holding[code]
                    value = dailies.loc[code]['close'] * holding_stock['volume']
                    total_value += value

                    # 计算总收益
                    profit = (value - holding_stock['cost']
                              ) * 100 / holding_stock['cost']
                    # 计算单日收益
                    last_value = holding_stock['last_value']
                    one_day_profit = (value - last_value) * 100 / last_value
                    # 暂存当日市值
                    self.holding[code]['last_value'] = value

                    print('持仓: %s, %10.2f, %10.2f, %4.2f, %4.2f' %
                          (code, value, last_value, profit, one_day_profit))
                except:
                    print('计算收益时发生错误:%s, %s' % (code, date), flush=True)

        return total_value
    def analyze(self):
        # 初始化对数据管理子系统接口的调用
        dm = DataModule()
        # 初始化对因子管理子系统接口的调用
        fm = FactorModule()

        # 获取分析周期内的
        all_dates = get_trading_dates(self.begin_date, self.end_date)

        # 首档和末档,股票代码和后复权价格的Dictionary
        top_dailies = dict()
        bottom_dailies = dict()
        # 暂存上一个调整
        last_adjust_date = None

        # 设置沪深300的首日值
        hs300_k = dm.get_k_data('000300',
                                index=True,
                                begin_date=all_dates[0],
                                end_date=all_dates[0])
        self.hs300_first_value = hs300_k.loc[0]['close']

        # 计算每日收益
        for index in range(0, len(all_dates), self.interval):
            adjust_date = all_dates[index]

            # 获取因子值,按照指定的顺序排序
            df_factor = fm.get_single_date_factors(self.factor, adjust_date)
            if df_factor.index.size == 0:
                continue
            df_factor.sort_values(self.factor,
                                  ascending=self.ascending,
                                  inplace=True)
            # 将股票代码设为index
            df_factor.set_index(['code'], inplace=True)

            # 获取当日所有股票的行情
            df_dailies = dm.get_one_day_k_data(autype='hfq', date=adjust_date)
            # 将code设为index
            df_dailies.set_index(['code'], inplace=True)

            # 计算收益
            self.compute_profit(last_adjust_date, df_dailies, top_dailies,
                                bottom_dailies, adjust_date)

            # 删除停牌股票
            df_dailies = df_dailies[df_dailies['is_trading']]
            # 计算每当包含的股票数
            total_size = df_dailies.index.size
            single_position_count = int(total_size / self.position)

            # 调整首档组合
            self.adjust_top_position(top_dailies, df_factor, df_dailies,
                                     single_position_count)

            # 调整末档组合
            self.adjust_bottom_position(bottom_dailies, df_factor, df_dailies,
                                        single_position_count)

            # 保存上一个调整日
            last_adjust_date = adjust_date

        # 生成零投资组合的组合收益
        self.profit_df[
            'portfolio'] = self.profit_df['top'] - self.profit_df['bottom']

        self.draw()
    def compute_profit(self, last_adjust_date, df_dailies, top_dailies,
                       bottom_dailies, adjust_date):
        """
        计算收益
        :param last_adjust_date: 上一个调整日
        :param df_dailies:
        :param top_dailies:
        :param bottom_dailies:
        :param adjust_date: 当前调整日
        :return:
        """
        # 只有存在上一个调整日,才计算上期的收益
        if last_adjust_date is not None:
            # 计算首档收益
            top_profit = self.compute_average_profit(df_dailies, top_dailies)

            # 计算末档收益
            bottom_profit = self.compute_average_profit(
                df_dailies, bottom_dailies)

            # 计算组合收益
            portfolio_profit = top_profit[0] - bottom_profit[0]

            # 添加结果的DataFrame中
            self.profit_df.loc[last_adjust_date] = {
                'top': top_profit[0],
                'bottom': bottom_profit[0],
                'portfolio': portfolio_profit
            }

            # 计算累积收益(复利方式)
            # 首档
            top_cumulative_profit = round(
                (self.last_top_net_value *
                 (1 + top_profit[0] / 100) - 1) * 100, 2)
            self.last_top_net_value *= (1 + top_profit[0] / 100)
            # 末档
            bottom_cumulative_profit = round(
                (self.last_bottom_net_value *
                 (1 + bottom_profit[0] / 100) - 1) * 100, 2)
            self.last_bottom_net_value *= (1 + bottom_profit[0] / 100)
            # 组合
            portfolio_cumulative_profit = round(
                (self.last_portfolio_net_value *
                 (1 + portfolio_profit / 100) - 1) * 100, 2)
            self.last_portfolio_net_value *= (1 + portfolio_profit / 100)

            # 计算沪深300的累计收益
            dm = DataModule()
            hs300_k = dm.get_k_data('000300',
                                    index=True,
                                    begin_date=adjust_date,
                                    end_date=adjust_date)
            hs300_k.set_index(['date'], 1, inplace=True)
            hs300_profit = (hs300_k.loc[adjust_date]['close'] -
                            self.hs300_first_value) / self.hs300_first_value

            self.cumulative_profit.loc[last_adjust_date] = {
                'top': top_cumulative_profit,
                'bottom': bottom_cumulative_profit,
                'portfolio': portfolio_cumulative_profit,
                'hs300': hs300_profit
            }

            self.count_df.loc[last_adjust_date] = {
                'top': top_profit[1],
                'bottom': bottom_profit[1]
            }
Esempio n. 13
0
    def statistic_profit(self):
        """
        统计股票池的收益
        """
        # 设定评测周期
        rebalance_dates, codes_dict = self.get_option_stocks()

        dm = DataModule()

        # 用DataFrame保存收益
        df_profit = DataFrame(columns=['profit', 'hs300'])

        df_profit.loc[rebalance_dates[0]] = {'profit': 0, 'hs300': 0}

        # 获取沪深300在统计周期内的第一天的值
        hs300_k = dm.get_k_data('000300',
                                index=True,
                                begin_date=rebalance_dates[0],
                                end_date=rebalance_dates[0])
        hs300_begin_value = hs300_k.loc[hs300_k.index[0]]['close']

        # 通过净值计算累计收益
        net_value = 1
        for _index in range(1, len(rebalance_dates) - 1):
            last_rebalance_date = rebalance_dates[_index - 1]
            current_rebalance_date = rebalance_dates[_index]
            # 获取上一期的股票池
            codes = codes_dict[last_rebalance_date]

            # 统计当前的收益
            profit_sum = 0
            # 参与统计收益的股票个数
            profit_code_count = 0
            for code in codes:
                daily_ks = dm.get_k_data(code,
                                         autype='hfq',
                                         begin_date=last_rebalance_date,
                                         end_date=current_rebalance_date)

                index_size = daily_ks.index.size
                # 如果没有数据,则跳过,长期停牌
                if index_size == 0:
                    continue
                # 买入价
                in_price = daily_ks.loc[daily_ks.index[0]]['close']
                # 卖出价
                out_price = daily_ks.loc[daily_ks.index[index_size -
                                                        1]]['close']
                # 股票池内所有股票的收益
                profit_sum += (out_price - in_price) / in_price
                profit_code_count += 1

            profit = round(profit_sum / profit_code_count, 4)

            hs300_k_current = dm.get_k_data('000300',
                                            index=True,
                                            begin_date=current_rebalance_date,
                                            end_date=current_rebalance_date)
            hs300_close = hs300_k_current.loc[
                hs300_k_current.index[0]]['close']

            # 计算净值和累积收益
            net_value = net_value * (1 + profit)
            df_profit.loc[current_rebalance_date] = {
                'profit':
                round((net_value - 1) * 100, 4),
                'hs300':
                round((hs300_close - hs300_begin_value) * 100 /
                      hs300_begin_value, 4)
            }

            print(df_profit)

        # 绘制曲线
        df_profit.plot(title='Stock Pool Profit Statistic', kind='line')
        # 显示图像
        plt.show()
Esempio n. 14
0
    def compute(self, begin_date=None, end_date=None):
        """
        计算指定日期内的信号
        :param begin_date: 开始日期
        :param end_date: 结束日期
        """
        codes = get_all_codes()

        dm = DataModule()

        for code in codes:
            try:
                df_dailies = dm.get_k_data(code,
                                           autype='hfq',
                                           begin_date=begin_date,
                                           end_date=end_date)

                if df_dailies.index.size == 0:
                    continue

                # 计算MA10
                df_dailies['ma'] = df_dailies['close'].rolling(10).mean()
                # 计算当日收盘和MA10的差值
                df_dailies['delta'] = df_dailies['close'] - df_dailies['ma']

                # 删除不再使用的ma和close列
                df_dailies.drop(['ma', 'close'], 1, inplace=True)

                # 判断突破类型
                index_size = df_dailies.index.size
                breaks = [0]
                for index in range(1, index_size):
                    # 如果当前日期为停牌状态,则后面连续11日不参与计算
                    if df_dailies.loc[
                            df_dailies.index[index]]['is_trading'] is False:
                        count = 10
                        while count > 0:
                            index += 1
                            count -= 1
                            breaks.append(0)

                        index += 1

                    last = df_dailies.loc[df_dailies.index[index - 1]]['delta']
                    current = df_dailies.loc[df_dailies.index[index]]['delta']

                    # 向上突破设为1,向下突破设为-1,不是突破设为0
                    break_direction = 1 if last <= 0 < current else -1 if last >= 0 > current else 0
                    breaks.append(break_direction)

                # 设置突破信号
                df_dailies['break'] = breaks

                # 将日期作为索引
                df_dailies.set_index(['date'], 1, inplace=True)
                # 删除不再使用的trade_status和delta数据列
                df_dailies.drop(['is_trading', 'delta'], 1, inplace=True)
                # 只保留突破的日期
                df_dailies = df_dailies[df_dailies['break'] != 0]

                # 将信号保存到数据库
                update_requests = []
                for index in df_dailies.index:
                    doc = {
                        'code':
                        code,
                        'date':
                        index,
                        # 方向,向上突破 up,向下突破 down
                        'direction':
                        'up' if df_dailies.loc[index]['break'] == 1 else 'down'
                    }
                    update_requests.append(
                        UpdateOne(doc, {'$set': doc}, upsert=True))

                if len(update_requests) > 0:
                    update_result = self.collection.bulk_write(update_requests,
                                                               ordered=False)
                    print('%s, upserted: %4d, modified: %4d' %
                          (code, update_result.upserted_count,
                           update_result.modified_count),
                          flush=True)
            except:
                traceback.print_exc()
Esempio n. 15
0
class Backtest:
    def __init__(self, strategy_option, begin_date=None, end_date=None):
        self.strategy_option = strategy_option
        if begin_date is None:
            self.begin_date = self.strategy_option.begin_date()
        else:
            self.begin_date = begin_date

        if end_date is None:
            self.end_date = self.strategy_option.end_date()
        else:
            self.end_date = end_date

        self.dm = DataModule()
        self.code_daily_cache = dict()

        self.logger = QuantLogger('backtest')
        self.candidatesLogger = QuantLogger('candidates')

    def start(self):
        """
        策略回测。结束后打印出收益曲线(沪深300基准)、年化收益、最大回撤
        """
        # 初始总资金
        initial_capital = self.strategy_option.capital()
        # 初始现金
        cash = initial_capital
        # 单只股票仓位上限
        single_position = self.strategy_option.single_position()

        # 从获取策略配置中获取股票池
        stock_pool = self.strategy_option.stock_pool()

        # 保存持仓股的日期
        account = Account()

        # 获取卖出信号
        sell_signal = self.strategy_option.sell_signal(account)

        # 获取买入信号
        buy_signal = self.strategy_option.buy_signal(account)

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

        # 获取交易日历,
        all_dates = get_trading_dates(begin_date=self.begin_date,
                                      end_date=self.end_date)

        # 获取沪深300在统计周期内的第一天的值
        hs300_k = self.dm.get_k_data('000300',
                                     index=True,
                                     begin_date=all_dates[0],
                                     end_date=all_dates[0])
        hs300_begin_value = hs300_k.loc[hs300_k.index[0]]['close']

        # 获取股票池数据
        rebalance_dates, date_codes_dict = stock_pool.get_option_stocks()

        # 获取止损策略
        stop_loss_policy = self.strategy_option.get_stop_loss(account)

        # 获取止盈策略
        stop_profit_policy = self.strategy_option.get_stop_profit(account)

        # 获取加仓策略
        add_position_policy = self.strategy_option.get_add_position(account)

        # 获取回测周期内股票池内所有股票的收盘价和前收价
        all_option_code_set = set()
        for rebalance_date in rebalance_dates:
            for code in date_codes_dict[rebalance_date]:
                all_option_code_set.add(code)

        # 缓存股票的日线数据
        for code in all_option_code_set:
            dailies_df = self.dm.get_k_data(code,
                                            autype=None,
                                            begin_date=self.begin_date,
                                            end_date=self.end_date)
            dailies_df.set_index(['date'], inplace=True)

            self.code_daily_cache[code] = dailies_df

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

        # 加仓
        to_be_added_signals = dict()
        to_be_added_codes = set()

        # 按照日期一步步回测
        for _date in all_dates:
            self.logger.info('开始回测,日期:' + _date)

            # 处理复权
            account.adjust_holding_volume_at_open(last_date, _date)

            # 卖出
            if len(to_be_sold_codes) > 0:
                sold_codes_tmp = set(to_be_sold_codes)
                for code in sold_codes_tmp:
                    try:
                        if code in account.holding_codes:
                            holding_stock = account.get_holding(code)
                            holding_volume = holding_stock['volume']
                            sell_price = self.code_daily_cache[code].loc[
                                _date]['open']
                            low_limit = self.code_daily_cache[code].loc[_date][
                                'low_limit']
                            if sell_price > low_limit:
                                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))
                            else:
                                print(
                                    '当日跌停,无法卖出,股票代码:%s, 日期: %s,价格:%7.2f,跌停价:%7.2f'
                                    % (code, _date, sell_price, low_limit),
                                    flush=True)

                            # 从持仓股中卖出
                            account.sell_out(code)
                            # 从代码列表中删除
                            to_be_sold_codes.remove(code)
                    except:
                        print('卖出时,发生异常:%s, %s' % (code, _date), flush=True)

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

            # 加仓逻辑
            add_codes_tmp = set(to_be_added_codes)
            for code in add_codes_tmp:
                add_signal = to_be_added_signals[code]
                try:
                    if cash > add_signal['position']:
                        daily = self.code_daily_cache[code].loc[_date]
                        buy_price = daily['open']
                        high_limit = daily['high_limit']
                        if buy_price < high_limit:
                            volume = int(
                                int(add_signal['position'] / buy_price) /
                                100) * 100
                            buy_amount = buy_price * volume
                            cash -= buy_amount
                            print('加仓 %s, %6d, %6.2f, %8.2f' %
                                  (code, volume, buy_price, buy_amount),
                                  flush=True)

                            # 更新加仓后的持仓股
                            holding = account.get_holding(code)
                            holding['cost'] += buy_amount
                            holding['last_value'] += buy_amount
                            holding['volume'] += volume
                            holding['add_times'] += 1
                            holding['last_buy_hfq_price'] = buy_price * daily[
                                'au_factor']
                            account.update_holding(code, holding)

                            # 从待加仓列表中删除
                            to_be_added_codes.remove(code)
                            del to_be_added_signals[code]
                        else:
                            print(
                                '当日涨停,无法加仓,股票代码:%s, 日期: %s,价格:%7.2f,涨停价:%7.2f'
                                % (code, _date, buy_price, high_limit),
                                flush=True)

                except:
                    print('加仓时,发生错误:%s, %s' % (code, _date), flush=True)

            # 买入
            if len(to_be_bought_codes) > 0:
                sorted_to_be_bought_list = list(to_be_bought_codes)
                sorted_to_be_bought_list.sort()
                for code in sorted_to_be_bought_list:
                    try:
                        if cash > single_position:
                            daily = self.code_daily_cache[code].loc[_date]
                            buy_price = daily['open']
                            high_limit = daily['high_limit']
                            if buy_price < high_limit:
                                volume = int(
                                    int(single_position / buy_price) /
                                    100) * 100
                                buy_amount = buy_price * volume
                                cash -= buy_amount
                                print('买入 %s, %6d, %6.2f, %8.2f' %
                                      (code, volume, buy_price, buy_amount),
                                      flush=True)

                                # 维护账户的持仓股
                                account.buy_in(code,
                                               volume=volume,
                                               cost=buy_amount)

                                # 如果加仓策略不为空,则更新持仓股
                                if add_position_policy is not None:
                                    holding = account.get_holding(code)
                                    holding[
                                        'last_buy_hfq_price'] = buy_price * daily[
                                            'au_factor']
                                    add_position_policy.update_holding(
                                        code, _date, holding)
                            else:
                                print(
                                    '当日涨停,无法买入,股票代码:%s, 日期: %s,价格:%7.2f,涨停价:%7.2f'
                                    % (code, _date, buy_price, high_limit),
                                    flush=True)

                    except:
                        print('买入时,发生错误:%s, %s' % (code, _date), flush=True)

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

            # 持仓股代码列表
            holding_codes = account.holding_codes
            # 如果调整日,则获取新一期的股票列表
            if _date in rebalance_dates:
                # 暂存为上期的日期
                if this_phase_codes is not None:
                    last_phase_codes = this_phase_codes
                this_phase_codes = date_codes_dict[_date]

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

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

            # 检测止损信号
            if stop_loss_policy is not None:
                for holding_code in holding_codes:
                    if stop_loss_policy.is_stop(holding_code, _date):
                        to_be_sold_codes.add(holding_code)
                        print('止损,股票:%s' % holding_code, flush=True)
                    else:
                        stop_loss_policy.update_holding(holding_code, _date)

            # 检测止盈信号
            if stop_profit_policy is not None:
                for holding_code in holding_codes:
                    if stop_profit_policy.is_stop(holding_code, _date):
                        to_be_sold_codes.add(holding_code)
                        print('止盈,股票:%s' % holding_code, flush=True)
                    else:
                        stop_profit_policy.update_holding(holding_code, _date)

            print('待卖股票,日期:%s,代码列表:' % _date, to_be_sold_codes, flush=True)

            # 检测是否有需要建仓的股票
            if add_position_policy is not None:
                for holding_code in holding_codes:
                    add_signal = add_position_policy.get_add_signal(
                        holding_code, _date)
                    if add_signal is not None:
                        to_be_added_signals[holding_code] = add_signal
                        to_be_added_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 \
                            buy_signal.is_match(_code, _date):
                        to_be_bought_codes.add(_code)

            self.logger.info('待买股票,日期:%s,代码列表:%s', _date, to_be_bought_codes)
            self.candidatesLogger.info('待买股票,日期:%s,代码列表:%s', _date,
                                       to_be_bought_codes)

            # 计算总市值
            total_value = account.get_total_value(_date)
            # 计算总资产
            total_capital = total_value + cash

            print('收盘后,现金: %10.2f, 总资产: %10.2f' % (cash, total_capital))

            # 计算沪深300的增长
            hs300_k_current = self.dm.get_k_data('000300',
                                                 index=True,
                                                 begin_date=_date,
                                                 end_date=_date)
            hs300_current_value = hs300_k_current.loc[
                hs300_k_current.index[0]]['close']

            last_date = _date
            df_profit.loc[_date] = {
                'net_value':
                round(total_capital / initial_capital, 2),
                'profit':
                round(
                    100 * (total_capital - initial_capital) / initial_capital,
                    2),
                'hs300':
                round(
                    100 * (hs300_current_value - hs300_begin_value) /
                    hs300_begin_value, 2)
            }

        # 打印回测收益曲线数值
        print('Profit history start')
        for index_date in df_profit.index:
            print('%s, %6.2f, %6.2f' %
                  (index_date, df_profit.loc[index_date]['profit'],
                   df_profit.loc[index_date]['hs300']),
                  flush=True)
        print('Profit history end')

        drawdown = self.compute_drawdown(df_profit['net_value'])
        annual_profit, sharpe_ratio = self.compute_sharpe_ratio(
            df_profit['net_value'])

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

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

    def compute_sharpe_ratio(self, net_values):
        """
        计算夏普比率
        :param net_values: 净值列表
        """

        # 总交易日数
        trading_days = len(net_values)
        # 所有收益的DataFrame
        profit_df = DataFrame(columns={'profit'})
        # 收益之后,初始化为第一天的收益
        profit_df.loc[0] = {'profit': round((net_values[0] - 1) * 100, 2)}
        # 计算每天的收益
        for index in range(1, trading_days):
            # 计算每日的收益变化
            profit = (net_values[index] -
                      net_values[index - 1]) / net_values[index - 1]
            profit = round(profit * 100, 2)
            profit_df.loc[index] = {'profit': profit}

        # 计算标准差
        profit_std = pow(profit_df.var()['profit'], 1 / 2)

        # 年化收益
        annual_profit = self.compute_annual_profit(trading_days,
                                                   net_values[-1])

        # 夏普比率
        sharpe_ratio = (annual_profit - 4.75) / profit_std

        return annual_profit, sharpe_ratio

    def compute_drawdown(self, net_values):
        """
        计算最大回撤
        :param net_values: 净值列表
        """
        # 最大回撤初始值设为0
        max_drawdown = 0
        index = 0
        # 双层循环找出最大回撤
        for net_value in net_values:
            for sub_net_value in net_values[index:]:
                drawdown = 1 - sub_net_value / net_value
                if drawdown > max_drawdown:
                    max_drawdown = drawdown

            index += 1

        return max_drawdown

    def compute_annual_profit(self, trading_days, net_value):
        """
        计算年化收益
        """

        annual_profit = 0
        if trading_days > 0:
            # 计算年数
            years = trading_days / 245
            # 计算年化收益
            annual_profit = pow(net_value, 1 / years) - 1

        annual_profit = round(annual_profit * 100, 2)

        return annual_profit

    def find_out_stocks(self, last_phase_codes, this_phase_codes):
        """
        找到上期入选本期被调出的股票,这些股票将必须卖出
        :param last_phase_codes: 上期的股票列表
        :param this_phase_codes: 本期的股票列表
        :return: 被调出的股票列表
        """
        out_stocks = []

        for code in last_phase_codes:
            if code not in this_phase_codes:
                out_stocks.append(code)

        return out_stocks
Esempio n. 16
0
class DailyKBreakMA10Signal:
    def __init__(self):
        self.dm = DataModule()

    def is_k_up_break_ma10(self, code, begin_date, end_date):
        """
        判断某只股票在某日是否满足K线上穿10日均线

        :param code: 股票代码
        :param begin_date: 开始日期
        :param end_date: 结束日期
        :return: True/False
        """

        # 如果没有指定开始日期和结束日期,则直接返回False
        if begin_date is None or end_date is None:
            return False

        daily_ks = self.dm.get_k_data(code,
                                      autype='hfq',
                                      begin_date=begin_date,
                                      end_date=end_date)
        # 需要判断两日的K线和10日均线的相对位置,所以如果K线数不满足11个,
        # 也就是无法计算两个MA10,则直接返回False
        index_size = daily_ks.index.size
        if index_size < 11:
            return False

        # 比较收盘价和MA10的关系
        daily_ks['ma'] = daily_ks['close'].rolling(10).mean()
        daily_ks['delta'] = daily_ks['close'] - daily_ks['ma']

        is_break_up = (daily_ks.loc[daily_ks.index[9]]['delta'] <= 0 <
                       daily_ks.loc[daily_ks.index[10]]['delta'])

        return is_break_up

    def is_k_down_break_ma10(self, code, begin_date, end_date):
        """
        判断某只股票在某日是否满足K线下穿10日均线

        :param code: 股票代码
        :param begin_date: 开始日期
        :param end_date: 结束日期
        :return: True/False
        """

        # 如果没有指定开始日期和结束日期,则直接返回False
        if begin_date is None or end_date is None:
            return False

        daily_ks = self.dm.get_k_data(code,
                                      autype='hfq',
                                      begin_date=begin_date,
                                      end_date=end_date)

        # 需要判断两日的K线和10日均线的相对位置,所以如果K线数不满足11个,
        # 也就是无法计算两个MA10,则直接返回False
        if daily_ks.index.size < 11:
            return False

        # 比较收盘价和MA10的关系
        daily_ks['ma'] = daily_ks['close'].rolling(10).mean()
        daily_ks['delta'] = daily_ks['close'] - daily_ks['ma']

        return daily_ks.loc[daily_ks.index[9]]['delta'] >= 0 > daily_ks.loc[
            daily_ks.index[10]]['delta']
Esempio n. 17
0
class ATRPosition(BasePosition):
    def __init__(self, account, strategy_option):
        BasePosition.__init__(self, account, strategy_option)
        self.dm = DataModule()

    def update_holding(self, code, date, updated_holding):
        """
        更新持仓股,为新的持仓股增加ATR、加仓次数
        :param code: 股票代码
        :param date: 日期
        :param updated_holding: 更新后的持仓股
        """
        try:
            existing_holding = self.account.get_holding(code)
            print(existing_holding, flush=True)
            if existing_holding is not None and 'atr' not in existing_holding:
                atr = self.compute_atr(code, date)
                print('Computed atr: %s' % str(atr), flush=True)
                if atr is not None:
                    updated_holding['atr'] = atr
                    updated_holding['add_times'] = 0
                    self.account.update_holding(code, updated_holding)
        except:
            print('加仓策略,更新持仓股信息时,发生错误,股票代码:%s,日期:%s,' % (code, date),
                  flush=True)
            traceback.print_exc()

    def get_add_signal(self, code, date):
        """
        如果符合加仓条件,则计算加仓信号
        :param code: 股票代码
        :param date: 日期
        :return: 加仓信号,如果没有则返回None,正常信号包括:code - 股票代码,position - 仓位
        """
        add_signal = None

        try:
            df_daily = self.dm.get_k_data(code,
                                          autype=None,
                                          begin_date=date,
                                          end_date=date)
            if df_daily.index.size > 0:
                holding_stock = self.account.get_holding(code)

                a = 5 / 0

                df_daily.set_index(['date'], 1, inplace=True)
                daily = df_daily.loc[date]
                if 'atr' in holding_stock and daily['is_trading']:
                    au_factor = daily['au_factor']
                    # ATR
                    atr = holding_stock['atr']
                    # 已加仓次数
                    add_times = holding_stock['add_times']
                    # 末次加仓价格,比较时用的实际价格
                    last_buy_price = holding_stock['last_buy_hfq_price']
                    hfq_close = daily['close'] * au_factor
                    # 最多加仓4次 价格超过上一个加仓点+atr
                    if add_times < 4 and hfq_close - last_buy_price > atr:
                        position = self.compute_position(code, date)
                        add_signal = {'code': code, 'position': position}
        except:
            self.logger.error('计算加仓信号时,发生错误,股票代码:%s, 日期:%s' % (code, date))

        return add_signal

    def compute_atr(self, code, date):
        """
        计算ATR
        :param code: 股票代码
        :param date: 日期
        :return: ATR的值
        """
        minute_cursor = DB_CONN['minute'].find(
            {
                'code': code,
                'time': {
                    '$lte': date + ' 15:00'
                }
            },
            sort=[('time', DESCENDING)],
            projection={
                'date': True,
                'time': True,
                'close': True,
                'high': True,
                'low': True,
                '_id': False
            },
            limit=61)
        minutes = [minute for minute in minute_cursor]

        minutes.reverse()

        if len(minutes) >= 60:
            before_date = (datetime.strptime(date, '%Y-%m-%d') -
                           timedelta(days=20)).strftime('%Y-%m-%d')
            df_daily = self.dm.get_k_data(code,
                                          autype=None,
                                          begin_date=before_date,
                                          end_date=date)
            if df_daily.index.size == 0:
                return None

            df_daily.set_index(['date'], 1, inplace=True)

            atr_df = DataFrame(columns=['tr'])
            for index in range(1, len(minutes)):
                minute = minutes[index]
                minute_date = minute['date']
                try:
                    au_factor = df_daily.loc[minute_date]['au_factor']
                    high = minute['high'] * au_factor
                    low = minute['low'] * au_factor
                    pre_close = minutes[index - 1]['close'] * au_factor
                    atr_df.loc[index] = {
                        'tr':
                        max([
                            high - low,
                            abs(high - pre_close),
                            abs(pre_close - low)
                        ])
                    }
                except Exception as e:
                    self.logger.error(
                        '计算ATR发生异常,股票代码:%s, 分钟线日期:%s' % (code, minute_date), e)

            print(atr_df)
            atrs = atr_df['tr'].rolling(60).mean()
            self.logger.debug(atrs)
            return atrs[60]
        else:
            print('加仓策略(ATR),分钟线数据不足,股票代码:%s,日期:%s,分钟线数:%2d' %
                  (code, date, len(minutes)),
                  flush=True)
            return None

    def compute_position(self, code, date):
        """
        计算应该分配的仓位, 这里采用固定仓位,总资金的0.015
        :param code: 股票代码
        :param date: 日期
        :return:
        """
        amount = self.strategy_option.capital() * 0.015
        return amount
Esempio n. 18
0
class TrackingStopProfit(BaseStopProfit):
    def __init__(self, account, max_profit, profit_drawdown):
        BaseStopProfit.__init__(self, account)
        self.max_profit = max_profit
        self.profit_drawdown = profit_drawdown
        self.dm = DataModule()

    def update_holding(self, code, date):
        """
        更新持仓股的最高市值
        :param code:
        :param date:
        :return:
        """
        df_daily = self.dm.get_k_data(code,
                                      autype=None,
                                      begin_date=date,
                                      end_date=date)
        if df_daily.index.size > 0:
            df_daily.set_index(['date'], 1, inplace=True)
            close = df_daily.loc[date]['close']
            holding = self.account.get_holding(code)

            current_value = holding['volume'] * close
            if 'highest_value' in holding.keys():
                if current_value > holding['highest_value']:
                    holding['highest_value'] = current_value
                    self.account.update_holding(code, holding)
            else:
                # 判断是否已经达到了最高的收益线
                profit = (current_value -
                          holding['cost']) * 100 / holding['cost']
                if profit > self.max_profit:
                    holding['highest_value'] = current_value
                    self.account.update_holding(code, holding)

    def is_stop(self, code, date):
        """
        判断股票在当前日期是否需要止盈,如果当前收益相对于最高收益的回撤已经达到了指定幅度,则止盈
        :param code: 股票代码
        :param date: 日期
        :return: True - 止盈, False - 不止盈
        """
        holding_stock = self.account.get_holding(code)
        print('止盈判断:%s' % code, flush=True)
        print(holding_stock, flush=True)
        if holding_stock is not None:
            df_daily = self.dm.get_k_data(code,
                                          autype=None,
                                          begin_date=date,
                                          end_date=date)
            if df_daily.index.size > 0:
                df_daily.set_index(['date'], 1, inplace=True)
                current_value = df_daily.loc[date]['close'] * holding_stock[
                    'volume']
                # 计算回落的百分比
                if 'highest_value' in holding_stock and current_value < holding_stock[
                        'highest_value']:
                    profit = (current_value - holding_stock['highest_value']) \
                            * 100 / holding_stock['highest_value']

                    print('止盈判断, profit: %5.2f, drawdown: %5.2f' %
                          (profit, self.profit_drawdown),
                          flush=True)
                    return (profit < 0) & (abs(profit) >= abs(
                        self.profit_drawdown))

        return False
Esempio n. 19
0
    def compute(self, begin_date, end_date):
        """
        计算指定日期内的信号
        :param begin_date: 开始日期
        :param end_date: 结束日期
        """
        all_codes = get_all_codes()

        dm = DataModule()

        N = 20
        k = 2

        for code in all_codes:
            try:
                df_daily = dm.get_k_data(code,
                                         autype='hfq',
                                         begin_date=begin_date,
                                         end_date=end_date)

                # 计算MB,盘后计算,这里用当日的Close
                df_daily['MID'] = df_daily['close'].rolling(N).mean()
                # 计算STD20
                df_daily['std'] = df_daily['close'].rolling(N).std()
                # 计算UP
                df_daily['UP'] = df_daily['MID'] + k * df_daily['std']
                # 计算down
                df_daily['DOWN'] = df_daily['MID'] - k * df_daily['std']

                # 将日期作为索引
                df_daily.set_index(['date'], inplace=True)

                # 上轨和中轨右移一位
                shifted_up = df_daily['UP'].shift(1)
                shifted_middle = df_daily['MID'].shift(1)

                # 收盘价突破或者跌破中轨的幅度占上轨和中轨宽度的比例
                ref_line = (df_daily['close'] -
                            shifted_middle) / (shifted_up - shifted_middle)

                ref_prev = ref_line.shift(1)

                # 找到时间窗口内的最小值
                min_val = ref_line.rolling(10).min()

                # 找到时间窗口内最低点前的最大值
                max_leading_value = ref_line.rolling(10).apply(
                    lambda vec: vec[:np.argmin(vec) + 1].max().astype(float),
                    raw=True)

                # 中轨支撑的作用的范围
                delta = 0.15

                # 判断是否存在中轨支撑反弹的信号,要求:
                # 时间窗口的最低点之前的最大值大于delta,最小值的绝对值小于delta,就有一个穿越阈值分界线的动作;
                # 当前日期在也在阈值之上,表示又从最低点穿越到阈值分界线之上;
                # 而判断前一日在阈值分界线之下,表示穿越是在当前交易日完成
                m_rebound_mask = (abs(min_val) <= delta) & (ref_line > delta) & (ref_prev <= delta) & \
                                 (max_leading_value > delta)

                # 将信号保存到数据库
                update_requests = []
                df_daily['m_rebound_mask'] = m_rebound_mask
                df_daily = df_daily[df_daily['m_rebound_mask']]
                for date in df_daily.index:
                    doc = {'code': code, 'date': date, 'signal': 'mid_rebound'}
                    update_requests.append(
                        UpdateOne(doc, {'$set': doc}, upsert=True))

                if len(update_requests) > 0:
                    update_result = self.collection.bulk_write(update_requests,
                                                               ordered=False)
                    print('%s, upserted: %4d, modified: %4d' %
                          (code, update_result.upserted_count,
                           update_result.modified_count),
                          flush=True)
            except:
                traceback.print_exc()