def _filter_sec_on_tiaocang_date(self, tiaocang_date, sec_id): sse_cal = Calendar('China.SSE') tiaocang_date_prev = sse_cal.advanceDate( Date.strptime(str(tiaocang_date)[:10]), Period('-1b')).toDateTime() tiaocang_date_prev2 = sse_cal.advanceDate( Date.strptime(str(tiaocang_date)[:10]), Period('-2b')).toDateTime() price_data = get_sec_price(start_date=tiaocang_date_prev2, end_date=tiaocang_date, sec_ids=sec_id, data_source=self._data_source, csv_path=self._csv_path) price_data = price_data.transpose() price_data.index.name = 'secID' # 去除涨幅过大可能买不到的 price_data['returnFilter'] = price_data[tiaocang_date] / price_data[ tiaocang_date_prev] > 1 + self._filter_return_on_tiaocang_date # 去除有NaN的, 新股 price_data['ipoFilter'] = pd.isnull(price_data[tiaocang_date] * price_data[tiaocang_date_prev] * price_data[tiaocang_date_prev2]) # 去除停牌的,此处判断标准就是连续三天收盘价格一样 price_data['tingpaiFilter'] = ( (price_data[tiaocang_date] == price_data[tiaocang_date_prev]) & (price_data[tiaocang_date_prev] == price_data[tiaocang_date_prev2])) price_data['filters'] = 1 - (1 - price_data['returnFilter']) * ( 1 - price_data['ipoFilter']) * (1 - price_data['tingpaiFilter']) return price_data['filters']
def makeSchedule(firstDate, endDate, tenor, calendar='NullCalendar', dateRule=BizDayConventions.Following, dateGenerationRule=DateGeneration.Forward): cal = Calendar(calendar) firstDate = check_date(firstDate) endDate = check_date(endDate) tenor = check_period(tenor) if tenor.units() == TimeUnits.BDays: schedule = [] if dateGenerationRule == DateGeneration.Forward: d = cal.adjustDate(firstDate, dateRule) while d <= endDate: schedule.append(d) d = cal.advanceDate(d, tenor, dateRule) elif dateGenerationRule == DateGeneration.Backward: d = cal.adjustDate(endDate, dateRule) while d >= firstDate: schedule.append(d) d = cal.advanceDate(d, -tenor, dateRule) schedule = sorted(schedule) else: schedule = Schedule(firstDate, endDate, tenor, cal, convention=dateRule, dateGenerationRule=dateGenerationRule) return [d.toDateTime() for d in schedule]
def forward_date(date, tenor, date_format='%Y-%m-%d'): try: # use pyfin instead to get more accurate and flexible date math start_date = Date.strptime(date, date_format) sseCal = Calendar('China.SSE') ret = sseCal.advanceDate(start_date, Period('-' + tenor), endOfMonth=True) # 此处返回的是上一期期末日期,再向后调整一天,以避免区间日期重叠 ret = sseCal.advanceDate(ret, Period('1b')) return str(ret) except NameError: pass
def advanceDateByCalendar(holidayCenter, referenceDate, period, convention=BizDayConventions.Following): cal = Calendar(holidayCenter) refer = check_date(referenceDate) return cal.advanceDate(refer, period, convention).toDateTime()
def testAdvanceDate(self): referenceDate = Date(2014, 1, 31) sseCal = Calendar('China.SSE') ibCal = Calendar('China.IB') bizDayConv = BizDayConventions.Following # test null period self.assertEqual(sseCal.advanceDate(referenceDate, '0b', bizDayConv), Date(2014, 2, 7)) # test negative period self.assertEqual(sseCal.advanceDate(referenceDate, '-5b', bizDayConv), Date(2014, 1, 24)) # The difference is caused by Feb 8 is SSE holiday but a working day for IB market self.assertEqual(sseCal.advanceDate(referenceDate, '2b', bizDayConv), Date(2014, 2, 10)) self.assertEqual(sseCal.advanceDate(referenceDate, '2d', bizDayConv), Date(2014, 2, 7)) self.assertEqual(ibCal.advanceDate(referenceDate, '2b', bizDayConv), Date(2014, 2, 8)) self.assertEqual(ibCal.advanceDate(referenceDate, '2d', bizDayConv), Date(2014, 2, 7)) bizDayConv = BizDayConventions.ModifiedFollowing # May 31, 2014 is a holiday self.assertEqual( sseCal.advanceDate(referenceDate, '4m', bizDayConv, True), Date(2014, 5, 30))
def testAdvanceDate(self): referenceDate = Date(2014, 1, 31) sseCal = Calendar('China.SSE') ibCal = Calendar('China.IB') bizDayConv = BizDayConventions.Following # test null period self.assertEqual(sseCal.advanceDate(referenceDate, '0b', bizDayConv), Date(2014, 2, 7)) # test negative period self.assertEqual(sseCal.advanceDate(referenceDate, '-5b', bizDayConv), Date(2014, 1, 24)) # The difference is caused by Feb 8 is SSE holiday but a working day for IB market self.assertEqual(sseCal.advanceDate(referenceDate, '2b', bizDayConv), Date(2014, 2, 10)) self.assertEqual(sseCal.advanceDate(referenceDate, '2d', bizDayConv), Date(2014, 2, 7)) self.assertEqual(ibCal.advanceDate(referenceDate, '2b', bizDayConv), Date(2014, 2, 8)) self.assertEqual(ibCal.advanceDate(referenceDate, '2d', bizDayConv), Date(2014, 2, 7)) bizDayConv = BizDayConventions.ModifiedFollowing # May 31, 2014 is a holiday self.assertEqual(sseCal.advanceDate(referenceDate, '4m', bizDayConv, True), Date(2014, 5, 30))
def _filter_sec_on_tiaocang_date(self, tiaocang_date, sec_id): sse_cal = Calendar('China.SSE') tiaocang_date_prev = sse_cal.advanceDate(Date.strptime(str(tiaocang_date)[:10]), '-1b').toDateTime() tiaocang_date_prev2 = sse_cal.advanceDate(Date.strptime(str(tiaocang_date)[:10]), '-2b').toDateTime() price_data = WindMarketDataHandler.get_sec_price_on_date(start_date=tiaocang_date_prev2, end_date=tiaocang_date, sec_ids=sec_id) price_data = price_data.transpose() price_data.index.name = 'sec_id' # 去除涨幅过大可能买不到的 price_data['returnFilter'] = price_data[tiaocang_date] / price_data[ tiaocang_date_prev] > 1 + self._filterReturnOnTiaoCangDate # 去除有NaN的, 新股 price_data['ipoFilter'] = pd.isnull( price_data[tiaocang_date] * price_data[tiaocang_date_prev] * price_data[tiaocang_date_prev2]) # 去除停牌的,此处判断标准就是连续三天收盘价格一样 price_data['tingpaiFilter'] = ((price_data[tiaocang_date] == price_data[tiaocang_date_prev]) & ( price_data[tiaocang_date_prev] == price_data[tiaocang_date_prev2])) price_data['filters'] = 1 - (1 - price_data['returnFilter']) * (1 - price_data['ipoFilter']) * ( 1 - price_data['tingpaiFilter']) return price_data['filters']
def dcam_strat_main(factor_loader_params, analyzer_params, selector_params, portfolio_params, update_params): # FactorLoader params start_date = factor_loader_params['startDate'] end_date = factor_loader_params['endDate'] factor_norm_dict = factor_loader_params['factorNormDict'] # dcam analyzer params factor_weight_type = analyzer_params.get('factorWeightType', FactorWeightType.ICWeight) tiaocang_date_window_size = analyzer_params.get('tiaoCangDateWindowSize', 12) save_sec_score = analyzer_params.get('saveSecScore', True) # selector params save_sec_selected = selector_params.get('saveSecSelected', True) nb_sec_selected_per_industry_min = selector_params.get( 'nbSecSelectedPerIndustryMin', 5) use_industry_name = selector_params.get('useIndustryName', True) nb_sec_selected_total = selector_params.get('nbSecSelectedTotal', 100) ignore_zero_weight = selector_params.get('ignoreZeroWeight', False) # portfolio params benchmark_sec_id = portfolio_params.get('benchmarkSecID', '000905.SH') re_balance_freq = portfolio_params.get('rebalanceFreq', FreqType.EOM) initial_capital = portfolio_params.get('initialCapital', 1000000000.0) filter_return_on_tiaocang_date = portfolio_params.get( 'filterReturnOnTiaoCangDate', 0.09) data_source = portfolio_params.get('dataSource', DataSource.WIND) update_factor = update_params.get('updateFactor', False) update_sec_score = update_params.get('updateSecScore', False) update_sec_select = update_params.get('updateSecSelect', False) if update_factor: factor = FactorLoader(start_date=start_date, end_date=end_date, factor_norm_dict=factor_norm_dict) factor_data = factor.get_factor_data() else: # TODO factor = None factor_data = None pass if update_sec_score: layer_factor = [ factor_data[name] for name in factor_norm_dict.keys() if factor_norm_dict[name][1] == DCAMFactorType.layerFactor ] alpha_factor = [ factor_data[name] for name in factor_norm_dict.keys() if factor_norm_dict[name][1] == DCAMFactorType.alphaFactor ] alpha_factor_sign = [ factor_data[name][2] for name in factor_norm_dict.keys() if factor_norm_dict[name][1] == DCAMFactorType.alphaFactor ] analyzer = DCAMAnalyzer( layer_factor=layer_factor, alpha_factor=alpha_factor, sec_return=factor_data['RETURN'], tiaocang_date=factor.get_tiaocang_date(), tiaocang_date_window_size=tiaocang_date_window_size, save_sec_score=save_sec_score, factor_weight_type=factor_weight_type, alpha_factor_sign=alpha_factor_sign) sec_score = analyzer.calc_sec_score() else: sec_score = load_sec_score(_secScorePath) if update_sec_select: index_comp = IndexComp(industry_weight=factor_data['IND_WGT']) selector = Selector( sec_score=sec_score, industry=factor_data['INDUSTRY'], nb_sec_selected_per_industry_min=nb_sec_selected_per_industry_min, index_comp=index_comp, save_sec_selected=save_sec_selected, use_industry_name=use_industry_name, nb_sec_selected_total=nb_sec_selected_total, ignore_zero_weight=ignore_zero_weight) selector.industry_neutral = True selector.sec_selection() sec_selected = selector.sec_selected_full_info pprint(selector.sec_selected_full_info) else: sec_selected = load_sec_selected(_secSelectedPath) # construct strategy ptf # 价格数据需要使用到最后一个调仓日的后一个月末 sse_cal = Calendar('China.SSE') end_date_for_price_data = str( sse_cal.advanceDate(Date.strptime(end_date), '1m')) strategy = Portfolio( sec_selected=sec_selected, end_date=end_date_for_price_data, initial_capital=initial_capital, filter_return_on_tiaocang_date=filter_return_on_tiaocang_date, data_source=data_source, benchmark_sec_id=benchmark_sec_id, re_balance_freq=re_balance_freq) strategy.evaluate_ptf_return()
def __init__(self, effectiveDate, terminationDate, tenor, calendar, convention=BizDayConventions.Following, terminationConvention=BizDayConventions.Following, dateGenerationRule=DateGeneration.Forward, endOfMonth=False, firstDate=None, nextToLastDate=None): # Initialize private data self._tenor = tenor self._cal = calendar self._convention = convention self._terminationConvention = terminationConvention self._rule = dateGenerationRule self._dates = [] self._isRegular = [] if tenor < Period("1M"): self._endOfMonth = False else: self._endOfMonth = endOfMonth if firstDate is None or firstDate == effectiveDate: self._firstDate = None else: self._firstDate = firstDate if nextToLastDate is None or nextToLastDate == terminationDate: self._nextToLastDate = None else: self._nextToLastDate = nextToLastDate # in many cases (e.g. non-expired bonds) the effective date is not # really necessary. In these cases a decent placeholder is enough if effectiveDate is None and firstDate is None and dateGenerationRule == DateGeneration.Backward: evalDate = Settings.evaluationDate pyFinAssert(evalDate < terminationDate, ValueError, "null effective date") if nextToLastDate is not None: y = int((nextToLastDate - evalDate) / 366) + 1 effectiveDate = nextToLastDate - Period(y, TimeUnits.Years) else: y = int((terminationDate - evalDate) / 366) + 1 effectiveDate = terminationDate - Period(y, TimeUnits.Years) else: pyFinAssert(effectiveDate is not None, ValueError, "null effective date") pyFinAssert(effectiveDate < terminationDate, ValueError, "effective date ({0}) " "later than or equal to termination date ({1}" .format(effectiveDate, terminationDate)) if tenor.length == 0: self._rule = DateGeneration.Zero else: pyFinAssert(tenor.length > 0, ValueError, "non positive tenor ({0:d}) not allowed".format(tenor.length)) if self._firstDate is not None: if self._rule == DateGeneration.Backward or self._rule == DateGeneration.Forward: pyFinAssert(effectiveDate < self._firstDate < terminationDate, ValueError, "first date ({0}) out of effective-termination date range [{1}, {2})" .format(self._firstDate, effectiveDate, terminationDate)) # we should ensure that the above condition is still # verified after adjustment elif self._rule == DateGeneration.Zero: raise ValueError("first date incompatible with {0:d} date generation rule".format(self._rule)) else: raise ValueError("unknown rule ({0:d})".format(self._rule)) if self._nextToLastDate is not None: if self._rule == DateGeneration.Backward or self._rule == DateGeneration.Forward: pyFinAssert(effectiveDate < self._nextToLastDate < terminationDate, ValueError, "next to last date ({0}) out of effective-termination date range [{1}, {2})" .format(self._nextToLastDate, effectiveDate, terminationDate)) # we should ensure that the above condition is still # verified after adjustment elif self._rule == DateGeneration.Zero: raise ValueError("next to last date incompatible with {0:d} date generation rule".format(self._rule)) else: raise ValueError("unknown rule ({0:d})".format(self._rule)) # calendar needed for endOfMonth adjustment nullCalendar = Calendar("Null") periods = 1 if self._rule == DateGeneration.Zero: self._tenor = Period(0, TimeUnits.Years) self._dates.extend([effectiveDate, terminationDate]) self._isRegular.append(True) elif self._rule == DateGeneration.Backward: self._dates.append(terminationDate) seed = terminationDate if self._nextToLastDate is not None: self._dates.insert(0, self._nextToLastDate) temp = nullCalendar.advanceDate(seed, Period(-periods * self._tenor.length, self._tenor.units), convention, self._endOfMonth) if temp != self._nextToLastDate: self._isRegular.insert(0, False) else: self._isRegular.insert(0, True) seed = self._nextToLastDate exitDate = effectiveDate if self._firstDate is not None: exitDate = self._firstDate while True: temp = nullCalendar.advanceDate(seed, Period(-periods * self._tenor.length, self._tenor.units), convention, self._endOfMonth) if temp < exitDate: if self._firstDate is not None and self._cal.adjustDate(self._dates[0], convention) != self._cal.adjustDate( self._firstDate, convention): self._dates.insert(0, self._firstDate) self._isRegular.insert(0, False) break else: # skip dates that would result in duplicates # after adjustment if self._cal.adjustDate(self._dates[0], convention) != self._cal.adjustDate(temp, convention): self._dates.insert(0, temp) self._isRegular.insert(0, True) periods += 1 if self._cal.adjustDate(self._dates[0], convention) != self._cal.adjustDate(effectiveDate, convention): self._dates.insert(0, effectiveDate) self._isRegular.insert(0, False) elif self._rule == DateGeneration.Forward: self._dates.append(effectiveDate) seed = self._dates[-1] if self._firstDate is not None: self._dates.append(self._firstDate) temp = nullCalendar.advanceDate(seed, Period(periods * self._tenor.length, self._tenor.units), convention, self._endOfMonth) if temp != self._firstDate: self._isRegular.append(False) else: self._isRegular.append(True) seed = self._firstDate exitDate = terminationDate if self._nextToLastDate is not None: exitDate = self._nextToLastDate while True: temp = nullCalendar.advanceDate(seed, Period(periods * self._tenor.length, self._tenor.units), convention, self._endOfMonth) if temp > exitDate: if self._nextToLastDate is not None and self._cal.adjustDate(self._dates[-1], convention) != self._cal.adjustDate( self._nextToLastDate, convention): self._dates.append(self._nextToLastDate) self._isRegular.append(False) break else: # skip dates that would result in duplicates # after adjustment if self._cal.adjustDate(self._dates[-1], convention) != self._cal.adjustDate(temp, convention): self._dates.append(temp) self._isRegular.append(True) periods += 1 if self._cal.adjustDate(self._dates[-1], terminationConvention) != self._cal.adjustDate(terminationDate, terminationConvention): self._dates.append(terminationDate) self._isRegular.append(False) else: raise ValueError("unknown rule ({0:d})".format(self._rule)) # adjustments if self._endOfMonth and self._cal.isEndOfMonth(seed): # adjust to end of month if convention == BizDayConventions.Unadjusted: for i in range(len(self._dates) - 1): self._dates[i] = Date.endOfMonth(self._dates[i]) else: for i in range(len(self._dates) - 1): self._dates[i] = self._cal.endOfMonth(self._dates[i]) if terminationConvention != BizDayConventions.Unadjusted: self._dates[0] = self._cal.endOfMonth(self._dates[0]) self._dates[-1] = self._cal.endOfMonth(self._dates[-1]) else: if self._rule == DateGeneration.Backward: self._dates[-1] = Date.endOfMonth(self._dates[-1]) else: self._dates[0] = Date.endOfMonth(self._dates[0]) else: for i in range(len(self._dates) - 1): self._dates[i] = self._cal.adjustDate(self._dates[i], convention) if terminationConvention != BizDayConventions.Unadjusted: self._dates[-1] = self._cal.adjustDate(self._dates[-1], terminationConvention) # Final safety checks to remove extra next-to-last date, if # necessary. It can happen to be equal or later than the end # date due to EOM adjustments (see the Schedule test suite # for an example). if len(self._dates) >= 2 and self._dates[len(self._dates) - 2] >= self._dates[-1]: self._isRegular[len(self._dates) - 2] = (self._dates[len(self._dates) - 2] == self._dates[-1]) self._dates[len(self._dates) - 2] = self._dates[-1] self._dates.pop() self._isRegular.pop() if len(self._dates) >= 2 and self._dates[1] <= self._dates[0]: self._isRegular[1] = (self._dates[1] == self._dates[0]) self._dates[1] = self._dates[0] self._dates = self._dates[1:] self._isRegular = self._isRegular[1:] pyFinAssert(len(self._dates) >= 1, ValueError, "degenerate single date ({0}) schedule\n" "seed date: {1}\n" "exit date: {2}\n" "effective date: {3}\n" "first date: {4}\n" "next to last date: {5}\n" "termination date: {6}\n" "generation rule: {7}\n" "end of month: {8}\n" .format(self._dates[0], seed, exitDate, effectiveDate, firstDate, nextToLastDate, terminationDate, self._rule, self._endOfMonth))