from abc import ABC import pandas as pd from dateutil.relativedelta import relativedelta from py_jftech import get_config, autowired, component from api import PortfoliosRisk, SignalType, Navs from rebalance.base_signal import BaseRebalanceSignal from rebalance.dao import robo_rebalance_signal as rrs class CrisisSignal(BaseRebalanceSignal, ABC): @autowired def __init__(self, navs: Navs = None): super().__init__() self._navs = navs self._config = get_config(__name__) @property def exp_init(self): return pd.to_datetime(self._config['exp-init']) if 'exp-init' in self._config else None @property def exp_years(self): return self._config['exp-years'] if 'exp-years' in self._config else 1 @property def inversion_years(self): return self._config['inversion-years'] if 'inversion-years' in self._config else 1 @property def inversion_threshold(self): return self._config['inversion-threshold'] if 'inversion-threshold' in self._config else 0.3 def get_exp_start_date(self, day, risk: PortfoliosRisk): assert day, "get crisis exp start date, day can not be none" assert risk, "get crisis exp start date, PortfoliosRisk can not be none" exp_date = day - relativedelta(years=self.exp_years) if self.exp_init and self.exp_init >= exp_date: return self.exp_init exp_signal = rrs.get_first_after(type=SignalType.CRISIS_EXP, risk=risk, min_date=exp_date) if not exp_signal: inversion_date = day - relativedelta(years=self.inversion_years) ten_before = self._navs.get_last_index_close(max_date=inversion_date, ticker='USGG10YR Index') ten_today = self._navs.get_last_index_close(max_date=day, ticker='USGG10YR Index') two_before = self._navs.get_last_index_close(max_date=inversion_date, ticker='USGG2YR Index') two_today = self._navs.get_last_index_close(max_date=day, ticker='USGG2YR Index') if ten_today['close'] - two_today['close'] <= ten_before['close'] - two_before['close'] and \ ten_today['close'] - two_today['close'] <= self.inversion_threshold: last_signal = rrs.get_last_one(max_date=day, risk=risk) if SignalType(last_signal['type']) is SignalType.NONE: rrs.update(last_signal['id'], { 'date': day, 'type': SignalType.CRISIS_EXP, }) else: rrs.insert({ 'date': day, 'type': SignalType.CRISIS_EXP, 'risk': risk, }) exp_signal = rrs.get_first_after(type=SignalType.CRISIS_EXP, risk=risk, min_date=exp_date) return exp_signal['date'] if exp_signal else None @component(bean_name='crisis_one') class ConsecutFiveDaysCrisisOneSignal(CrisisSignal, BaseRebalanceSignal): ''' 连续5个交易入跌破850天平均值 ''' @property def consecut_days(self): return self._config['crisis-1']['consecut-days'] @property def mean_count(self): return self._config['crisis-1']['mean-count'] @property def signal_type(self): return SignalType.CRISIS_ONE def is_trigger(self, day, risk: PortfoliosRisk) -> bool: exp_date = self.get_exp_start_date(day, risk) if exp_date: crisis_one = rrs.get_first_after(type=SignalType.CRISIS_ONE, risk=risk, min_date=exp_date) if not crisis_one: spx = self._navs.get_last_index_close(max_date=day, ticker='SPX Index', count=self.mean_count + self.consecut_days) spx = pd.DataFrame(spx) spx.sort_values(by='date', inplace=True, ascending=False) spx.reset_index(drop=True, inplace=True) for offset in range(self.consecut_days): spx.loc[0 + offset, 'mean'] = spx.loc[0 + offset: 850 + offset].close.mean() spx.dropna(inplace=True) spx['diff'] = spx['close'] - spx['mean'] return spx[spx['diff'] >= 0].empty return False @component(bean_name='crisis_one') class LastRateCrisisOneSignal(CrisisSignal, BaseRebalanceSignal): ''' (close / 850ma – 1) < threshold,threshold=-0.05、-0.1 ''' @property def mean_count(self): return self._config['crisis-1']['mean-count'] @property def threshold(self): return self._config['crisis-1']['threshold'] @property def signal_type(self): return SignalType.CRISIS_ONE def is_trigger(self, day, risk: PortfoliosRisk) -> bool: exp_date = self.get_exp_start_date(day, risk) if exp_date: crisis_one = rrs.get_first_after(type=SignalType.CRISIS_ONE, risk=risk, min_date=exp_date) if not crisis_one: spx = self._navs.get_last_index_close(max_date=day, ticker='SPX Index', count=self.mean_count) spx = pd.DataFrame(spx) spx.sort_values(by='date', inplace=True) return spx.iloc[-1]['close'] / spx['close'].mean() - 1 < self.threshold return False @component(bean_name='crisis_two') class CrisisTwoSignal(CrisisSignal, BaseRebalanceSignal): @property def negative_growth_years(self): return self._config['crisis-2']['negative-growth'] @property def fed_months(self): return self._config['crisis-2']['fed-months'] @property def fed_threshold(self): return self._config['crisis-2']['fed-threshold'] @property def signal_type(self): return SignalType.CRISIS_TWO def is_trigger(self, day, risk: PortfoliosRisk) -> bool: exp_date = self.get_exp_start_date(day, risk) if exp_date: crisis_two = rrs.get_first_after(type=SignalType.CRISIS_TWO, risk=risk, min_date=exp_date) if not crisis_two: ng_date = day - relativedelta(years=self.negative_growth_years) ten_today = self._navs.get_last_index_close(max_date=day, ticker='USGG10YR Index') cpi_today = self._navs.get_last_eco_values(max_date=day, ticker='CPI YOY Index', by_release_date=True) ten_before = self._navs.get_last_index_close(max_date=ng_date, ticker='USGG10YR Index') cpi_before = self._navs.get_last_eco_values(max_date=ng_date, ticker='CPI YOY Index', by_release_date=True) before = ten_before['close'] - cpi_before['indicator'] today = ten_today['close'] - cpi_today['indicator'] fed_today = self._navs.get_last_eco_values(max_date=day, ticker='FDTR Index', by_release_date=True) fed_before = self._navs.get_last_eco_values(max_date=day - relativedelta(months=self.fed_months), ticker='FDTR Index', by_release_date=True) return today <= before and fed_today['indicator'] - fed_before['indicator'] < self.fed_threshold return False