信用违约掉期(CDS)期限结构预测股票收益
登录后收藏回测表现
年化收益27.57%
波动率23.84%
贝塔0.166
夏普比率0.99
索提诺比率-0.096
胜率50%
完整 Python 代码
from AlgorithmImports import *
import data_tools
from typing import List, Dict
#endregion
class CombinedStockandCDSMomentum(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2003, 1, 1)
self.SetCash(100_000)
self.UniverseSettings.Leverage = 5
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.settings.daily_precise_end_time = False
self.quantile: int = 10
self.selection_flag: bool = False
self.tickers: List[str] = []
self.long_symbols: List[Symbol] = []
self.short_symbols: List[Symbol] = []
self.cds_1y: Symbol = self.AddData(data_tools.EquityCDS1Y, 'CDS1Y', Resolution.Daily).Symbol
self.cds_5y: Symbol = self.AddData(data_tools.EquityCDS5Y, 'CDS5Y', Resolution.Daily).Symbol
market: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.MonthStart(market),
self.TimeRules.AfterMarketOpen(market),
self.Selection)
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetFeeModel(data_tools.CustomFeeModel())
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
slope: Dict[Symbol, int] = {}
# calculate slope of the CDS term structure
if self.Securities.ContainsKey(self.cds_1y) and self.Securities.ContainsKey(self.cds_5y):
cds_1y_data = self.Securities[self.cds_1y].GetLastData()
cds_5y_data = self.Securities[self.cds_5y].GetLastData()
if cds_1y_data and cds_5y_data:
# data has not been initialized yet
if len(self.tickers) == 0:
self.tickers = list([x.upper() for x in cds_1y_data.GetStorageDictionary().Keys])
if self.selection_flag:
for f in fundamental:
symbol: Symbol = f.Symbol
ticker: str = symbol.Value
# calculate slope
if ticker in self.tickers:
cds_1y: int = cds_1y_data[ticker]
cds_5y: int = cds_5y_data[ticker]
slope[symbol] = cds_5y - cds_1y
if not self.selection_flag:
return Universe.Unchanged
last_update_date_1y: datetime.date = data_tools.EquityCDS1Y.get_last_update_date()
last_update_date_5y: datetime.date = data_tools.EquityCDS5Y.get_last_update_date()
if (self.Securities[self.cds_1y].GetLastData() and last_update_date_1y < self.Time.date() or
self.Securities[self.cds_5y].GetLastData() and last_update_date_5y < self.Time.date()):
return []
if len(slope) >= self.quantile:
sorted_by_slope: List[Symbol] = sorted(slope, key=slope.get, reverse=True)
quantile: int = int(len(sorted_by_slope) / self.quantile)
self.long_symbols = sorted_by_slope[-quantile:]
self.short_symbols = sorted_by_slope[:quantile]
return self.long_symbols + self.short_symbols
def OnData(self, slice: Slice) -> None:
if not self.selection_flag:
return
self.selection_flag = False
# trade execution
targets: List[PortfolioTarget] = []
for i, portfolio in enumerate([self.long_symbols, self.short_symbols]):
for symbol in portfolio:
if slice.ContainsKey(symbol) and slice[symbol] is not None:
targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
self.SetHoldings(targets, True)
self.long_symbols.clear()
self.short_symbols.clear()
def Selection(self) -> None:
self.selection_flag = True