@expect_types(start_date=(str, datetime), end_date=(str, datetime), freq=(str, FreqType))
def get_tiaocang_date(start_date, end_date, freq=FreqType.EOM, calendar='China.SSE', date_format='%Y-%m-%d'):
    """
    :param start_date: str/datetime.datetime, 开始日期
    :param end_date: str/datetime.datetime, 结束日期
    :param freq: str enum, default=EOM, 月度
    :param calendar: str, 日历名称
    :param date_format: str, start_date/end_date 如果是str格式，其格式的日期形式
    :return: list, datetime.datetime
    返回在开始日至结束日之间的调仓日(交易日)
    """
    calendar = calendar
    date_format = date_format
    start_date = ensure_pyfin_date(start_date, date_format)
    end_date = ensure_pyfin_date(end_date, date_format)

    cal = Calendar(calendar)
    tiaocang_date = Schedule(start_date,
                             end_date,
                             Period('1' + freq),
                             cal,
                             BizDayConventions.Unadjusted)

    if freq.upper() == FreqType.EOW:
        tiaocang_date = [Date.nextWeekday(date, Weekdays.Friday) for date in tiaocang_date]
    elif freq.upper() == FreqType.EOM:
        tiaocang_date = [cal.endOfMonth(date) for date in tiaocang_date]
    elif freq.upper() == FreqType.EOY:
        tiaocang_date = [Date(date.year(), 12, 31) for date in tiaocang_date]

    tiaocang_date = [cal.adjustDate(date, BizDayConventions.Preceding).toDateTime() for date in tiaocang_date if
                     start_date <= date <= end_date]

    return sorted(set(tiaocang_date)) @expect_types(arg=(list, np.ndarray, pd.DataFrame, pd.Series))
def ensure_pd_df(func, argname, arg):
    if isinstance(arg, pd.DataFrame):
        return arg
    else:
        return pd.DataFrame(arg) @expect_types(date=(str, datetime))
def ensure_datetime(date, date_format='%Y-%m-%d'):
    """
    :param date: str, datetime, 日期
    :param date_format: str, 时间格式
    :return: datetime
    """
    if isinstance(date, datetime):
        return date
    else:
        return datetime.strptime(date, date_format) @expect_types(arg=(np.ndarray, pd.DataFrame, pd.Series))
def ensure_np_array(func, argname, arg):
    """
    :return: 转换成np.ndarray格式
    """
    if isinstance(arg, np.ndarray):
        return arg
    else:
        return arg.values @expect_types(data=(pd.DataFrame, pd.Series), data_format=OutputDataFormat)
def ensure_pd_index_names(data, data_format, valid_index):
    if data_format == OutputDataFormat.MULTI_INDEX_DF:
        data.index.names = valid_index.full_index
    else:
        data.index.name = valid_index.date_index
    return data @expect_types(data=(pd.Series, pd.DataFrame))
def convert_df_format(data, target_format=OutputDataFormat.MULTI_INDEX_DF, col_name='factor',
                      multi_index=INDEX_FACTOR):
    if target_format == OutputDataFormat.MULTI_INDEX_DF:
        tmp = data.stack()
        data_ = pd.DataFrame(tmp)
        data_.index.names = multi_index.full_index
        data_.columns = [col_name]
    else:
        tmp = data.unstack()
        index = tmp.index
        columns = tmp.columns.get_level_values(multi_index.sec_index).tolist()
        data_ = pd.DataFrame(tmp.values, index=index, columns=columns)

    return data_ class FreqType(StrEnum):
    EOD = 'D'
    EOW = 'W'
    EOM = 'M'
    EOQ = 'Q'
    EOSY = 'S'
    EOY = 'Y' class FactorType(StrEnum):
    INDUSTY_CODE = 'industry_code'
    ALPHA_FACTOR = 'alpha_factor'
    RETURN = 'return'
    PRICE = 'price'
    ALPHA_FACTOR_MV = 'alpha_factor_mv'
    SCORE = 'score'
    FWD_RETURN = 'fwd_return'
    WEIGHT = 'weight' class OutputDataFormat(IntEnum):
    MULTI_INDEX_DF = 0
    PITVOT_TABLE_DF = 1 class FactorNormType(IntEnum):
    Null = 0
    Industry_Neutral = 1
    Industry_Cap_Neutral = 2 INDEX_FACTOR = namedtuple('INDEX_FACTOR', ['date_index', 'sec_index', 'full_index', 'col_score'])
import copy
import pandas as pd
from collections import defaultdict
from toolz.dicttoolz import (merge,
                             dissoc)
from xutils import (py_warning,
                    py_assert)
from argcheck import (expect_types,
                      optional,
                      preprocess)
from alphaware.utils import (ensure_datetime,
                             ensure_pd_df,
                             convert_df_format,
                             get_tiaocang_date,
                             ensure_np_array,
                             ensure_pd_index_names)
from alphaware.enums import (FreqType,
                             FactorType,
                             OutputDataFormat,
                             FactorNormType)
from alphaware.const import INDEX_FACTOR
# -*- coding: utf-8 -*-


_REQUIRED_FACTOR_PROPERTY = {'type': FactorType.ALPHA_FACTOR,
                             'data_format': OutputDataFormat.MULTI_INDEX_DF,
                             'norm_type': FactorNormType.Null,
                             'freq': FreqType.EOM}


@expect_types(factor_property=dict)
def update_factor_property(factor_property):
    ret = merge(_REQUIRED_FACTOR_PROPERTY, factor_property)
    return ret


class Factor(object):
    @preprocess(data=ensure_pd_df)
    def __init__(self, data, name, property_dict=defaultdict(str), **kwargs):
        self.data = copy.deepcopy(data)
        self.name = name
        self.property = update_factor_property(property_dict)
        self.production_data_format = kwargs.get('production_data_format', OutputDataFormat.MULTI_INDEX_DF)

        self._validate_data_format()
        self._validate_data_name()
        self._validate_date_index()

    def _validate_data_format(self):
        if self.property['data_format'] != self.production_data_format:
            self.data = convert_df_format(self.data, target_format=self.production_data_format)
        return

    def _validate_data_name(self):
        if self.production_data_format == OutputDataFormat.MULTI_INDEX_DF:
            self.data.columns = [self.name]
        return

    def _validate_date_index(self):
        self.data = ensure_pd_index_names(self.data,
                                          data_format=self.production_data_format,
                                          valid_index=INDEX_FACTOR)
        date_format = self.property.get('date_format', '%Y-%m-%d')
        date_index = self.data.index.get_level_values(INDEX_FACTOR.date_index)
        date_index = [ensure_datetime(date_, date_format) for date_ in date_index]
        self.data.reset_index(inplace=True)
        self.data[INDEX_FACTOR.date_index] = date_index
        if self.production_data_format == OutputDataFormat.MULTI_INDEX_DF:
            self.data.set_index(keys=INDEX_FACTOR.full_index, inplace=True)
        else:
            self.data.set_index(keys=INDEX_FACTOR.date_index, inplace=True)

    @property
    def type(self):
        return self.property['type']

    @property
    def freq(self):
        return self.property['freq']

    @property
    def production_format(self):
        return self.production_data_format

    @property
    def trade_date_list(self):
        if self.production_data_format == OutputDataFormat.MULTI_INDEX_DF:
            ret = set(self.data.index.get_level_values(INDEX_FACTOR.date_index))
            ret = list(sorted(set(ret)))
        else:
            ret = self.data.index.tolist()

        return ret


class FactorContainer(object):

    def __init__(self, start_date=None, end_date=None, factors=None, freq=FreqType.EOM, **kwargs):