| Overall Statistics |
|
Total Orders 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Start Equity 10000000 End Equity 10000000 Net Profit 0% Sharpe Ratio 0 Sortino Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -0.767 Tracking Error 0.138 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset Portfolio Turnover 0% |
# region imports
from AlgorithmImports import *
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from industry_codes import industry_codes
# endregion
fundamental_factors = [
'operation_ratios.revenue_growth.three_months', # How a company's sales are affected over time, possibly reflecting policy impacts like tax changes or sector-specific subsidies.
#'valuation_ratios.first_year_estimated_eps_growth', # Indicates how profitable a company has become, potentially reflecting beneficial or adverse policy effects.
'valuation_ratios.pe_ratio', # Changes in this ratio might reflect investor expectations under different political climates. For instance, sectors expected to benefit from new policies might see an increase in their P/E ratios due to higher anticipated earnings.
'operation_ratios.roe.three_months', # Indicate how well companies utilize their assets to generate profit, which could be influenced by corporate tax policies or regulatory environments.
'operation_ratios.roa.three_months', # Indicate how well companies utilize their assets to generate profit, which could be influenced by corporate tax policies or regulatory environments.
'operation_ratios.current_ratio.three_months', # To see how policies affecting economic stability might impact a company's short-term financial health.
'operation_ratios.total_debt_equity_ratio.three_months', # Policies affecting interest rates or corporate taxes could influence a company's capital structure.
'valuation_ratios.trailing_dividend_yield', # Reflects income generation for investors, which might be influenced by tax policies on dividends.
]
class CreativeRedCow(QCAlgorithm):
def initialize(self):
self.set_start_date(2012, 3, 1)
self.set_end_date(2024, 9, 30)
self.set_cash(10_000_000)
self.set_warm_up(self.start_date - datetime(1998, 2, 1))
self.universe_settings.schedule.on(self.date_rules.month_start())
self.add_universe(self._select_assets)
self._daily_returns_by_industry = {industry: pd.Series() for industry in industry_codes}
self._scaler = StandardScaler()
self._clustering_model = KMeans(n_clusters=3, random_state=0)
self._lookback = timedelta(12*365) # 144 months = 12 years = 3 presidential terms.
self._previous_selection_time = None
self._winning_party_by_result_date = {date(1996, 11, 6): "D", date(2000, 11, 8): "R", date(2004, 11, 3): "R", date(2008, 11, 5): "D", date(2012, 11, 7): "D", date(2016, 11, 9): "R", date(2020, 11, 8): "D"}
self._periods_by_party = {
'D': [
(datetime(1996, 11, 6), datetime(2000, 11, 7)),
(datetime(2008, 11, 5), datetime(2016, 11, 8)),
(datetime(2020, 11, 8), self.end_date)
],
'R': [
(datetime(2000, 11, 8), datetime(2008, 11, 4)),
(datetime(2016, 11, 9), datetime(2020, 11, 7))
]
}
def _has_all_factors(self, fundamental):
values = []
for factor in fundamental_factors:
values.append(np.isnan(eval(f"fundamental.{factor}")))
#values.append(eval(f"fundamental.{factor}"))
return not any(values)
def _select_assets(self, fundamentals):
ruling_party = [party for t, party in self._winning_party_by_result_date.items() if t < self.time.date()][-1]
# Update the daily returns of each industry.
if self._previous_selection_time:
# Get the daily returns of each industry leader.
daily_returns = self.history([leader['symbol'] for leader in self._leader_by_industry.values()], self._previous_selection_time-timedelta(2), self.time, Resolution.DAILY)['close'].unstack(0).pct_change()
for industry in industry_codes:
if industry not in self._leader_by_industry or self._leader_by_industry[industry]['symbol'] not in daily_returns.columns:
continue # Fill with 0 later
# Append the daily returns of the current leader to the series of daily returns for the industry.
industry_daily_returns = pd.concat([self._daily_returns_by_industry[industry], daily_returns[self._leader_by_industry[industry]['symbol']].dropna()])
self._daily_returns_by_industry[industry] = industry_daily_returns[~industry_daily_returns.index.duplicated(keep='first')]
# Trim off history that has fallen out of the lookback window.
self._daily_returns_by_industry[industry] = self._daily_returns_by_industry[industry][self._daily_returns_by_industry[industry].index >= self.time - self._lookback]
self._previous_selection_time = self.time
# Get the largest asset of each industry (and its fundamental factors).
self._leader_by_industry = {}
for industry_code in industry_codes:
industry_assets = [
f for f in fundamentals
if f.asset_classification.morningstar_industry_code == eval(f"MorningstarIndustryCode.{industry_code}") and f.market_cap and self._has_all_factors(f)
]
if industry_assets:
leader = sorted(industry_assets, key=lambda f: f.market_cap)[-1]
leader_factors = {}
for factor in fundamental_factors + ['symbol']:
leader_factors[factor] = eval(f"leader.{factor}")
self._leader_by_industry[industry_code] = leader_factors
# During warm-up, keep the universe empty.
if self.is_warming_up or not (self.time.year % 4 == 0 and self.time.month == 11):
return []
# Cluster the assets based on their fundamental factors.
factors_df = pd.DataFrame(columns=fundamental_factors)
for industry, leader in self._leader_by_industry.items():
factors_df.loc[industry, :] = [leader[f] for f in fundamental_factors]
cluster_by_industry = pd.Series(
self._clustering_model.fit_predict(self._scaler.fit_transform(factors_df)),
index=self._leader_by_industry.keys()
)
# Calculate the performance of each cluster.
other_party = 'D' if ruling_party == 'R' else 'R'
cluster_scores = []
for cluster in cluster_by_industry.unique():
result = {}
# Get the daily returns of the cluster.
industries = cluster_by_industry[cluster_by_industry == cluster].index
cluster_daily_returns = pd.concat([self._daily_returns_by_industry[industry] for industry in industries], axis=1)
# Select the periods of time when the current party was in office.
sliced_daily_returns = pd.DataFrame()
for start_date, end_date in self._periods_by_party[ruling_party]:
sliced_daily_returns = pd.concat([sliced_daily_returns, cluster_daily_returns.loc[start_date:end_date]])
# Calculate metric (ex: Return of an equal-weighted portfolio).
if sliced_daily_returns.empty:
result[ruling_party] = 0
else:
equity_curve = (sliced_daily_returns.fillna(0).mean(axis=1) + 1).cumprod()
result[ruling_party] = (equity_curve.iloc[-1] - equity_curve.iloc[0]) / equity_curve.iloc[0]
# Select the periods of time when the other party was in office.
sliced_daily_returns = pd.DataFrame()
for start_date, end_date in self._periods_by_party[other_party]:
sliced_daily_returns = pd.concat([sliced_daily_returns, cluster_daily_returns.loc[start_date:end_date]])
# Calculate metric (ex: Return of an equal-weighted portfolio).
if sliced_daily_returns.empty:
result[other_party] = 0
else:
equity_curve = (sliced_daily_returns.fillna(0).mean(axis=1) + 1).cumprod()
result[other_party] = (equity_curve.iloc[-1] - equity_curve.iloc[0]) / equity_curve.iloc[0]
# Select the ROC over the trailing month.
result['last_month'] = (cluster_daily_returns.fillna(0).mean(axis=1) + 1).cumprod().pct_change(21).iloc[-1]
cluster_scores.append(result)
# Whichever cluster performs best over the last month, find which party it leans towards.
winning_cluster = sorted(cluster_scores, key=lambda scores: scores['last_month'])[-1]
predicted_party = ruling_party if winning_cluster[ruling_party] > winning_cluster[other_party] else other_party
self.debug(f'Predicted party: {predicted_party}')
self.log(f'Predicted party: {predicted_party}')
# Return an empty universe
return []
# region imports
from AlgorithmImports import *
# endregion
industry_codes = [
'AGRICULTURAL_INPUTS',
'BUILDING_MATERIALS',
'CHEMICALS',
'SPECIALTY_CHEMICALS',
'LUMBER_AND_WOOD_PRODUCTION',
'PAPER_AND_PAPER_PRODUCTS',
'ALUMINUM',
'COPPER',
'OTHER_INDUSTRIAL_METALS_AND_MINING',
'GOLD',
'SILVER',
'OTHER_PRECIOUS_METALS_AND_MINING',
'COKING_COAL',
'STEEL',
'AUTO_AND_TRUCK_DEALERSHIPS',
'AUTO_MANUFACTURERS',
'AUTO_PARTS',
'RECREATIONAL_VEHICLES',
'FURNISHINGS',
'FIXTURES_AND_APPLIANCES',
'RESIDENTIAL_CONSTRUCTION',
'TEXTILE_MANUFACTURING',
'APPAREL_MANUFACTURING',
'FOOTWEAR_AND_ACCESSORIES',
'PACKAGING_AND_CONTAINERS',
'PERSONAL_SERVICES',
'RESTAURANTS',
'APPAREL_RETAIL',
'DEPARTMENT_STORES',
'HOME_IMPROVEMENT_RETAIL',
'LUXURY_GOODS',
'INTERNET_RETAIL',
'SPECIALTY_RETAIL',
'GAMBLING',
'LEISURE',
'LODGING',
'RESORTS_AND_CASINOS',
'TRAVEL_SERVICES',
'ASSET_MANAGEMENT',
'BANKS_DIVERSIFIED',
'BANKS_REGIONAL',
'MORTGAGE_FINANCE',
'CAPITAL_MARKETS',
'FINANCIAL_DATA_AND_STOCK_EXCHANGES',
'INSURANCE_LIFE',
'INSURANCE_PROPERTY_AND_CASUALTY',
'INSURANCE_REINSURANCE',
'INSURANCE_SPECIALTY',
'INSURANCE_BROKERS',
'INSURANCE_DIVERSIFIED',
'SHELL_COMPANIES',
'FINANCIAL_CONGLOMERATES',
'CREDIT_SERVICES',
'REAL_ESTATE_DEVELOPMENT',
'REAL_ESTATE_SERVICES',
'REAL_ESTATE_DIVERSIFIED',
'REIT_HEALTHCARE_FACILITIES',
'REIT_HOTEL_AND_MOTEL',
'REIT_INDUSTRIAL',
'REIT_OFFICE',
'REIT_RESIDENTIAL',
'REIT_RETAIL',
'REIT_MORTGAGE',
'REIT_SPECIALTY',
'REIT_DIVERSIFIED',
'BEVERAGES_BREWERS',
'BEVERAGES_WINERIES_AND_DISTILLERIES',
'BEVERAGES_NON_ALCOHOLIC',
'CONFECTIONERS',
'FARM_PRODUCTS',
'HOUSEHOLD_AND_PERSONAL_PRODUCTS',
'PACKAGED_FOODS',
'EDUCATION_AND_TRAINING_SERVICES',
'DISCOUNT_STORES',
'FOOD_DISTRIBUTION',
'GROCERY_STORES',
'TOBACCO',
'BIOTECHNOLOGY',
'DRUG_MANUFACTURERS_GENERAL',
'DRUG_MANUFACTURERS_SPECIALTY_AND_GENERIC',
'HEALTHCARE_PLANS',
'MEDICAL_CARE_FACILITIES',
'PHARMACEUTICAL_RETAILERS',
'HEALTH_INFORMATION_SERVICES',
'MEDICAL_DEVICES',
'MEDICAL_INSTRUMENTS_AND_SUPPLIES',
'DIAGNOSTICS_AND_RESEARCH',
'MEDICAL_DISTRIBUTION',
'UTILITIES_INDEPENDENT_POWER_PRODUCERS',
'UTILITIES_RENEWABLE',
'UTILITIES_REGULATED_WATER',
'UTILITIES_REGULATED_ELECTRIC',
'UTILITIES_REGULATED_GAS',
'UTILITIES_DIVERSIFIED',
'TELECOM_SERVICES',
'ADVERTISING_AGENCIES',
'PUBLISHING',
'BROADCASTING',
'ENTERTAINMENT',
'INTERNET_CONTENT_AND_INFORMATION',
'ELECTRONIC_GAMING_AND_MULTIMEDIA',
'OIL_AND_GAS_DRILLING',
'OIL_AND_GAS_E_AND_P',
'OIL_AND_GAS_INTEGRATED',
'OIL_AND_GAS_MIDSTREAM',
'OIL_AND_GAS_REFINING_AND_MARKETING',
'OIL_AND_GAS_EQUIPMENT_AND_SERVICES',
'THERMAL_COAL',
'URANIUM',
'AEROSPACE_AND_DEFENSE',
'SPECIALTY_BUSINESS_SERVICES',
'CONSULTING_SERVICES',
'RENTAL_AND_LEASING_SERVICES',
'SECURITY_AND_PROTECTION_SERVICES',
'STAFFING_AND_EMPLOYMENT_SERVICES',
'CONGLOMERATES',
'ENGINEERING_AND_CONSTRUCTION',
'INFRASTRUCTURE_OPERATIONS',
'BUILDING_PRODUCTS_AND_EQUIPMENT',
'FARM_AND_HEAVY_CONSTRUCTION_MACHINERY',
'INDUSTRIAL_DISTRIBUTION',
'BUSINESS_EQUIPMENT_AND_SUPPLIES',
'SPECIALTY_INDUSTRIAL_MACHINERY',
'METAL_FABRICATION',
'POLLUTION_AND_TREATMENT_CONTROLS',
'TOOLS_AND_ACCESSORIES',
'ELECTRICAL_EQUIPMENT_AND_PARTS',
'AIRPORTS_AND_AIR_SERVICES',
'AIRLINES',
'RAILROADS',
'MARINE_SHIPPING',
'TRUCKING',
'INTEGRATED_FREIGHT_AND_LOGISTICS',
'WASTE_MANAGEMENT',
'INFORMATION_TECHNOLOGY_SERVICES',
'SOFTWARE_APPLICATION',
'SOFTWARE_INFRASTRUCTURE',
'COMMUNICATION_EQUIPMENT',
'COMPUTER_HARDWARE',
'CONSUMER_ELECTRONICS',
'ELECTRONIC_COMPONENTS',
'ELECTRONICS_AND_COMPUTER_DISTRIBUTION',
'SCIENTIFIC_AND_TECHNICAL_INSTRUMENTS',
'SEMICONDUCTOR_EQUIPMENT_AND_MATERIALS',
'SEMICONDUCTORS',
'SOLAR'
]