Ejemplo n.º 1
0
    def finit(self):

        self.clientr = QACKClient(clickhouse_ip,
                                  clickhouse_port,
                                  user=clickhouse_user,
                                  password=clickhouse_password)
        self.factor_name = 'MA10'
Ejemplo n.º 2
0
    def __init__(self,
                 feature,
                 quantile=0.998,
                 init_cash=50000000,
                 rolling=5,
                 portfolioname='feature',
                 mongo_ip=mongo_ip,
                 clickhouse_host=clickhouse_ip,
                 clickhouse_port=clickhouse_port,
                 clickhouse_user=clickhouse_user,
                 clickhouse_password=clickhouse_password) -> None:
        """
        feature --> standard QAFeature
        quantile -> prue long only   upper quantile can be selected

        init_cash --> account backtest initcash

        rolling --> dategap for rolling sell

        clickhouse should be save data first
        
        mongoip -->  use to save qifiaccount

        """
        self.feature = feature.reset_index().drop_duplicates(
            ['date', 'code']).set_index(['date',
                                         'code']).sort_index().dropna()
        self.featurename = feature.columns[0]
        self.start = str(self.feature.index.levels[0][0])[0:10]
        self.end = str(self.feature.index.levels[0][-1])[0:10]
        self.codelist = self.feature.index.levels[1].tolist()

        self.client = QACKClient(host=clickhouse_host,
                                 port=clickhouse_port,
                                 user=clickhouse_user,
                                 password=clickhouse_password)
        self.quantile = quantile
        self.preload = self.feature.groupby(
            level=0, as_index=False,
            group_keys=False).apply(lambda x: self.slice_feature(x))
        self.datacenter = self.client.get_stock_day_qfq_adv(
            self.codelist, self.start, self.end)
        self.closepanel = self.datacenter.closepanel.bfill(
        )  ## 向前复权 匹配股票停牌模式 使用复牌后第一个收盘价卖出
        self.account = QIFI_Account(init_cash=init_cash,
                                    username='******'.format(
                                        self.featurename, uuid4()),
                                    broker_name='feature',
                                    portfolioname=portfolioname,
                                    password='******',
                                    nodatabase=False,
                                    model='BACKTEST',
                                    trade_host=mongo_ip)
        self.tradetable = {}
        self.rolling = rolling
        self.cashpre = init_cash / rolling

        self.account.initial()
Ejemplo n.º 3
0
    def __init__(self,
                 featuredata,
                 feature_name=None,
                 stock_data=None,
                 returnday=5,
                 host=clickhouse_ip,
                 port=clickhouse_port,
                 user=clickhouse_user,
                 password=clickhouse_password) -> None:

        self.feature = featuredata
        self.featurename = featuredata.name if feature_name is None else feature_name
        self.feature.name = self.featurename

        self.codelist = self.feature.index.levels[1].unique().tolist()
        self.start = str(self.feature.index.levels[0][0])[0:10]
        self.end = str(self.feature.index.levels[0][-1])[0:10]

        self._host = host
        self._port = port
        self._user = user
        self._password = password

        self.factorclient = clickhouse_driver.Client(host=self._host,
                                                     port=self._port,
                                                     user=self._user,
                                                     password=self._password,
                                                     database='factor')

        self.dataclient = QACKClient(host=self._host,
                                     port=self._port,
                                     user=self._user,
                                     password=self._password)

        if stock_data is None:
            self.stock_data = self.dataclient.get_stock_day_qfq_adv(
                self.codelist, self.start,
                QA_util_get_next_day(self.end, returnday))
        else:
            self.stock_data = stock_data

        self.returns = self.make_ret(self.stock_data.data).dropna()
Ejemplo n.º 4
0
class MA10(QASingleFactor_DailyBase):
    def finit(self):

        self.clientr = QACKClient(clickhouse_ip,
                                  clickhouse_port,
                                  user=clickhouse_user,
                                  password=clickhouse_password)
        self.factor_name = 'MA10'

    def calc(self) -> pd.DataFrame:
        """

        the example is just a day datasource, u can use the min data to generate a day-frequence factor

        the factor should be in day frequence 
        """

        codellist = self.clientr.get_stock_list().order_book_id.tolist()
        start = '2020-01-01'
        end = '2021-09-22'
        data = self.clientr.get_stock_day_qfq_adv(codellist, start, end)
        res = data.add_func(QA_indicator_MA, 10)
        res.columns = ['factor']
        return res.reset_index().dropna()
Ejemplo n.º 5
0
class QAFeatureAnalysis():
    def __init__(self,
                 featuredata,
                 feature_name=None,
                 stock_data=None,
                 returnday=5,
                 host=clickhouse_ip,
                 port=clickhouse_port,
                 user=clickhouse_user,
                 password=clickhouse_password) -> None:

        self.feature = featuredata
        self.featurename = featuredata.name if feature_name is None else feature_name
        self.feature.name = self.featurename

        self.codelist = self.feature.index.levels[1].unique().tolist()
        self.start = str(self.feature.index.levels[0][0])[0:10]
        self.end = str(self.feature.index.levels[0][-1])[0:10]

        self._host = host
        self._port = port
        self._user = user
        self._password = password

        self.factorclient = clickhouse_driver.Client(host=self._host,
                                                     port=self._port,
                                                     user=self._user,
                                                     password=self._password,
                                                     database='factor')

        self.dataclient = QACKClient(host=self._host,
                                     port=self._port,
                                     user=self._user,
                                     password=self._password)

        if stock_data is None:
            self.stock_data = self.dataclient.get_stock_day_qfq_adv(
                self.codelist, self.start,
                QA_util_get_next_day(self.end, returnday))
        else:
            self.stock_data = stock_data

        self.returns = self.make_ret(self.stock_data.data).dropna()

    def remake_returns(self, model='next_open', day=5):
        self.returns = self.make_ret(self.stock_data.data, model, day)

    def make_ret(self, data, model='next_open', day=5):
        """
        use open data make ret
        """
        if model == 'next_open':
            r = data.groupby(level=1).open.apply(
                lambda x: x.pct_change(day).shift(-day - 1))
            r.name = 'ret_{}'.format(day)
            return r
        elif model == 'close':
            r = data.groupby(
                level=1).close.apply(lambda x: x.pct_change(day).shift(-day))
            r.name = 'ret_{}'.format(day)
            return r

    @property
    @lru_cache()
    def rank(self):
        res = self.feature.groupby(level=0).rank()
        res.columns = [res.columns[0] + '_rank']
        return res

    def apply_rank(self):
        return QAFeatureAnalysis(self.rank,
                                 stock_data=self.stock_data,
                                 host=self._host,
                                 port=self._port,
                                 user=self._user,
                                 password=self._password)

    @property
    @lru_cache()
    def factor_and_forward_returns(self):
        feature = self.feature.reset_index()
        feature = feature.assign(
            date=pd.to_datetime(feature.date)).set_index('date')
        feature.index = feature.index.tz_localize('UTC')
        feature = feature.reset_index().set_index(['date', 'code'])

        panelprice = deepcopy(self.stock_data.closepanel)
        panelprice.index = pd.to_datetime(panelprice.index).tz_localize('UTC')
        return get_clean_factor_and_forward_returns(feature,
                                                    panelprice,
                                                    groupby=None,
                                                    binning_by_group=False,
                                                    quantiles=10,
                                                    bins=None,
                                                    periods=(1, 5, 10),
                                                    filter_zscore=20,
                                                    groupby_labels=None,
                                                    max_loss=0.15,
                                                    zero_aware=False,
                                                    cumulative_returns=True)

    def create_tear_sheet(self):
        return create_full_tear_sheet(self.factor_and_forward_returns)

    @property
    @lru_cache()
    def concatRes(self):
        res = pd.concat([
            self.feature,
            self.returns,
        ], axis=1)

        res = pd.concat([
            res,
            self.get_industry().set_index(['date', 'order_book_id'
                                           ]).first_industry_name
        ],
                        axis=1)
        return res

    @property
    @lru_cache()
    def ic(self):
        res = self.concatRes.dropna().groupby(
            level=0).apply(lambda x: x.corr('spearman').values[0, 1])
        res.names = 'ic'
        return res

    @property
    @lru_cache()
    def ir(self):
        return self.ic.rolling(20).apply(lambda x: x.mean() / x.std())

    def get_benchmark(self, benchmarkcode='000905.XSHG'):
        return self.dataclient.get_index_day(benchmarkcode, self.start,
                                             self.end)

    def get_industry(self):
        """
        index: stock
        columns: industry

                 industry
        stock1      A
        stock2      B

        """
        return self.dataclient.get_stock_industry(self.codelist, self.start,
                                                  self.end)

    def categorical(self, data: pd.DataFrame, key='industry'):
        """

        pd.DataFrame


        index : stock

        columns : key must be some of the columns, default: industry

                 industry    factor_i   ....   marketvalue
        stock1      A
        stock2      B


        return panel data

                    农林牧渔   非银金融   industry3 industry4 .....   industry29
        stock1          0       1           0       0               0
        stock2          1       0           0       0               0


        """
        return pd.DataFrame(sm.categorical(data[key].values, drop=True),
                            index=data.index,
                            columns=data[key].sort_values().unique())

    def standardize(self, data):
        """
        Data sample standardize 
        """
        return (data - data.mean()) / data.std()

    def winsorize(self, data, upper_q=0.99, lower_q=0.01):
        upper_end = data.quantile(upper_q)
        lower_end = data.quantile(lower_q)
        data.where(data < upper_end, upper_end, inplace=True)
        data.where(data > lower_end, lower_end, inplace=True)
        return data

    def neut(neuted_data, neut_data, weight_data=None):

        X = sm.add_constant(neut_data)
        results = sm.WLS(neuted_data, X, weights=weight_data).fit()
        return results.resid

    def ic_statistic(self):
        """
        使用 concatRes 保证数据对齐
        """

        feature_data = self.concatRes.iloc[:, 0]
        return_data = self.concatRes.iloc[:, 1]

        cor_pearson = feature_data.corr(return_data, method='pearson')
        cor_spearman = feature_data.corr(return_data, method='spearman')
        t_stat = stats.ttest_ind(feature_data, return_data).statistic
        p_value = stats.ttest_ind(feature_data, return_data).pvalue

        return pd.DataFrame({
            'cor_pearson': [cor_pearson],
            'cor_spearman': [cor_spearman],
            't_stat': [t_stat],
            'p_value': [p_value]
        })
Ejemplo n.º 6
0
class QAFeatureBacktest():
    def __init__(self,
                 feature,
                 quantile=0.998,
                 init_cash=50000000,
                 rolling=5,
                 portfolioname='feature',
                 mongo_ip=mongo_ip,
                 clickhouse_host=clickhouse_ip,
                 clickhouse_port=clickhouse_port,
                 clickhouse_user=clickhouse_user,
                 clickhouse_password=clickhouse_password) -> None:
        """
        feature --> standard QAFeature
        quantile -> prue long only   upper quantile can be selected

        init_cash --> account backtest initcash

        rolling --> dategap for rolling sell

        clickhouse should be save data first
        
        mongoip -->  use to save qifiaccount

        """
        self.feature = feature.reset_index().drop_duplicates(
            ['date', 'code']).set_index(['date',
                                         'code']).sort_index().dropna()
        self.featurename = feature.columns[0]
        self.start = str(self.feature.index.levels[0][0])[0:10]
        self.end = str(self.feature.index.levels[0][-1])[0:10]
        self.codelist = self.feature.index.levels[1].tolist()

        self.client = QACKClient(host=clickhouse_host,
                                 port=clickhouse_port,
                                 user=clickhouse_user,
                                 password=clickhouse_password)
        self.quantile = quantile
        self.preload = self.feature.groupby(
            level=0, as_index=False,
            group_keys=False).apply(lambda x: self.slice_feature(x))
        self.datacenter = self.client.get_stock_day_qfq_adv(
            self.codelist, self.start, self.end)
        self.closepanel = self.datacenter.closepanel.bfill(
        )  ## 向前复权 匹配股票停牌模式 使用复牌后第一个收盘价卖出
        self.account = QIFI_Account(init_cash=init_cash,
                                    username='******'.format(
                                        self.featurename, uuid4()),
                                    broker_name='feature',
                                    portfolioname=portfolioname,
                                    password='******',
                                    nodatabase=False,
                                    model='BACKTEST',
                                    trade_host=mongo_ip)
        self.tradetable = {}
        self.rolling = rolling
        self.cashpre = init_cash / rolling

        self.account.initial()

    def slice_feature(self, data):
        res = data[data > data.quantile(self.quantile)].dropna()
        res.index = res.index.remove_unused_levels()
        return res

    def get_feature(self, start, end=None):
        start = parser.parse(start).date()

        end = start if end is None else parser.parse(end).date()

        return self.feature.loc[start:end, :, :]

    def get_buy_list(self, date):
        """
        date --> real date
        """
        signaldate = QA_util_get_last_day(date)
        try:
            buy = self.preload.loc[parser.parse(signaldate).date(), :, :]
            buy.index = buy.index.remove_unused_levels()
            return buy.index.levels[1].tolist()
        except:
            return []

    def get_sell_list(self, date):
        #signaldate = QA.QA_util_get_last_day(date, 5)
        try:
            sell = list(self.tradetable[QA_util_get_last_day(
                date, self.rolling - 1)].keys())
            return sell
        except:
            return []

    def run(self, ):
        """
        buy nextday open
        
        sell next Nday close
        """

        for date in QA_util_get_trade_range(self.start, self.end):
            buylist = self.get_buy_list(date)
            selllist = self.get_sell_list(date)
            self.tradetable[date] = {}

            if len(selllist) == 0:
                pass

            else:
                data = self.closepanel.loc[
                    parser.parse(date).date(),
                    selllist].map(lambda x: round(x, 2)).to_dict()
                cashpre = self.cashpre / len(selllist)
                for code in selllist:

                    volume = self.tradetable[QA_util_get_last_day(
                        date, self.rolling - 1)][code]
                    if volume < 100:
                        pass
                    else:
                        order = self.account.send_order(code[0:6],
                                                        volume,
                                                        price=data[code],
                                                        datetime=date +
                                                        ' 15:00:00',
                                                        towards=-1)
                        self.account.make_deal(order)

            if len(buylist) != 0:

                d = self.datacenter.selects(
                    buylist, date, date).open.map(lambda x: round(x, 2))

                d.index = d.index.droplevel(0)

                data = d.to_dict()
                cashpre = self.cashpre / len(buylist)
                for code in buylist:
                    try:
                        volume = int(
                            0.01 * cashpre /
                            data[code]) * 100 if data[code] != 0 else 0
                        if volume < 100:
                            pass
                        else:
                            order = self.account.send_order(code[0:6],
                                                            volume,
                                                            price=data[code],
                                                            datetime=date +
                                                            ' 09:30:00',
                                                            towards=1)
                            self.account.make_deal(order)
                            self.tradetable[date][code] = volume
                    except:
                        """
                        主要是停牌买不入 直接放弃
                        
                        此处买入未加入连续一字板的检测 rust 会增加此处的逻辑
                        
                        """
                        pass
            else:
                pass

            holdinglist = [
                QA_util_code_change_format(code)
                for code in list(self.account.positions.keys())
            ]
            pricepanel = self.closepanel.loc[parser.parse(date).date(),
                                             holdinglist].map(
                                                 lambda x: round(x, 2))
            #pricepanel.index = pricepanel.index.droplevel(0)
            pricepanel = pricepanel.to_dict()
            for code in holdinglist:

                self.account.on_price_change(code[0:6], pricepanel[code])
            self.account.settle()