Overall Statistics Total Trades0Average Win0%Average Loss0%Compounding Annual Return0%Drawdown0%Expectancy0Net Profit0%Sharpe Ratio0Probabilistic Sharpe Ratio0%Loss Rate0%Win Rate0%Profit-Loss Ratio0Alpha0Beta0Annual Standard Deviation0Annual Variance0Information Ratio-3.128Tracking Error0.149Treynor Ratio0Total Fees$0.00Estimated Strategy Capacity$0
from scipy.optimize import minimize, LinearConstraint
import numpy as np
import pandas as pd

from QuantConnect import *
from QuantConnect.Research import QuantBook

from factors import *

class AlphaStreamOptimizer:
"""
Provides an implementation of a portfolio optimizer that maximizes the Sortino ratio.
"""

def Optimize(self, equity_curves):
"""
Use SciPy to optimize the portfolio weights of the alphas included in the equity_curves DataFrame.

Input:
- equity_curves
DataFrame of trailing equity curves for n alphas

Array of doubles, representing the optimized portfolio weights for the alphas
"""
size = equity_curves.columns.size
x0 = np.array(size * [1. / size]) # initial guess is equal-weighting

# Σw <= 1
constraints = [{'type': 'eq', 'fun': lambda weights: self.get_budget_constraint(weights)}]
opt = minimize(lambda weights: self.objective_function(equity_curves, weights), # Objective function
x0,                                                              # Initial guess
bounds = self.get_boundary_conditions(size),                     # Bounds for variables: 0 ≤ w ≤ 1
constraints = constraints,                                       # Constraints definition
method='SLSQP',          # Optimization method:  Sequential Least Squares Programming
options ={'ftol': 1e-10, 'maxiter': 200, 'disp': False})                        # Additional options

return opt['x'] if opt['success'] else x0

def objective_function(self, equity_curves, weights):
"""
Objective function to use when optimizing the portfolio weights

Input:
- equity_curves
DataFrame of equity curves for the alphas
- weights
Test weights selected by the optimizer

Returns a score for the weights that's calculated by applying the Sortino factor.
"""
equity_curve = (equity_curves * weights).sum(axis=1)
return self.f_scale(-Sortino.evaluate(equity_curve)) # negative so we maximize it

def f_scale(self, x):
"""
Bounds the value of x to [-5, +5] using a sigmoidal curve

Input:
- x
Value to be bounded

Returns the bounded x value.
"""
return x*5/np.sqrt(10+x*x)

def get_boundary_conditions(self, size):
"""
Creates the boundary condition for the portfolio weights

Input:
- size
"""
return tuple((0.0, 1.0) for x in range(size))

def get_budget_constraint(self, weights):
"""
Defines a budget constraint: the sum of the weights = 1

Input:
- weights
Array of portfolio weights
"""
return  np.sum(weights) - 1

def optimize_allocations(equity_curves, lookback):
"""
Determines how much of the portfolio to allocate to each alpha on a monthly basis.

Input:
- equity_curves
DataFrame of equity curves of individual Alpha Streams algorithms
- lookback
An integer representing the number of days to look back when calculating the factor values

Returns a DataFrame that shows how much to allocate to each alpha for each month in order
to maximize the trailing portfolio factor.
"""
allocation_by_alpha_id = {}
allocations_over_time = pd.DataFrame()
optimizer = AlphaStreamOptimizer()
month = 0
for time, row in equity_curves.iterrows():
# Rebalance monthly
if time.month == month:
continue

# Select active alphas
active_alphas = list(row.index[~row.isna()])

# Get trailing history
window = equity_curves[active_alphas].loc[:time].iloc[-lookback:].dropna(axis=1)
active_alphas = list(window.columns)
if len(active_alphas) < 2 or len(window) < lookback:
continue

month = time.month

# Scale each equity curve to have start value of 1 over the lookback period
normalized_equity_curves = window / window.iloc

best_allocation_scheme = optimizer.Optimize(normalized_equity_curves)

# Save allocation scheme that the optimizer has selected
allocation_by_alpha_id = dict(zip(window.columns, best_allocation_scheme))
for alpha_name, allocation in allocation_by_alpha_id.items():
allocations_over_time.loc[time.date(), alpha_name] = allocation
return allocations_over_time

def get_composite_equity_curve(equity_curves, allocations_over_time):
"""
Builds the composite equity curve that's produced by following the
allocations_over_time in real time.

Input:
- equity_curves
A DataFrame holding the equity curves of all the alphas under analysis
- allocations_over_time
A DataFrame which lists how much to allocate to each alpha over time

Returns the composite equity curve
"""
composite_equity_curve = pd.Series()
daily_returns = equity_curves.pct_change().shift(-1)
current_allocation = pd.Series()
for time, row in daily_returns.iterrows():
date = time.date()
if date in allocations_over_time.index:
current_allocation = allocations_over_time.loc[date].dropna()
if current_allocation.empty:
continue
daily_return = sum(current_allocation * row[current_allocation.index])
composite_equity_curve = composite_equity_curve.append(pd.Series([daily_return], index=[date]))
composite_equity_curve = (composite_equity_curve + 1).cumprod().shift(1).dropna()
composite_equity_curve.name = '*CompositeAlpha*'
return composite_equity_curve
from clr import AddReference
from QuantConnect import *
from QuantConnect.Research import QuantBook

from abc import ABC, abstractmethod
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

class Factor(ABC):
"""
Abstract base class used to create factors
"""
@abstractmethod
def evaluate(equity_curve):
"""
Calculates the factor value using the provided equity curve.

Input:
- equity_curve
The equity curve to calculate the factor on

Returns the factor value when applied to the equity curve.
"""
raise Exception("evaluate method not implemented yet.")

class Sortino(Factor):
"""
Sortino Ratio
"""
def evaluate(equity_curve):
returns = equity_curve.pct_change().dropna()
ann_ret = ((np.mean(returns) + 1) ** 252) - 1
ann_down_std = np.std(returns.loc[returns < 0]) * np.sqrt(252)
return ann_ret / ann_down_std if ann_down_std is not 0 else None

class Beta(Factor):
"""
Beta
"""
benchmark_data = pd.DataFrame()

@staticmethod
"""
Loads the benchmark data so we can calculate the factor value

Input:
- benchmark_security_id
Security ID of the benchmark security
- benchmark_name
The column name to use for the benchmark in the DataFrame that's loaded
"""
Beta.benchmark_data = pd.DataFrame()
qb = QuantBook()
benchmark_symbol = qb.Symbol(benchmark_security_id)

history = qb.History(benchmark_symbol, datetime(1998, 1, 2), datetime.now(), Resolution.Daily)
Beta.benchmark_data = history.loc[benchmark_symbol].close
Beta.benchmark_data = Beta.benchmark_data.resample('D').mean().interpolate(method='linear', limit_area='inside')
Beta.benchmark_data.name = benchmark_name

def evaluate(equity_curve):
# Get benchmark equity curve
if Beta.benchmark_data.empty:
start = equity_curve.index
end = equity_curve.index[-1] + timedelta(days=1)
benchmark_equity_curve = Beta.benchmark_data.loc[start:end]

# Calculate Beta
equity_curve_returns = equity_curve.pct_change().dropna()
benchmark_returns = benchmark_equity_curve.pct_change().dropna()
equity_df = pd.concat([equity_curve_returns, benchmark_returns], axis=1)
corr = equity_df.corr()[benchmark_equity_curve.name]
std = equity_curve_returns.std()
if std == 0:
return np.nan
std_ratio = benchmark_returns.std() / std
return corr * std_ratio

class Drawdown(Factor):
"""
Drawdown
"""
def evaluate(equity_curve):
equity_curve = equity_curve.values
i = np.argmax(np.maximum.accumulate(equity_curve) - equity_curve)
if equity_curve[:i].size == 0:
return np.nan
j = np.argmax(equity_curve[:i])
return abs((equity_curve[i]/equity_curve[j]) - 1) #round(abs((equity_curve[i]/equity_curve[j]) - 1), 3)
from clr import AddReference
from QuantConnect import *
from QuantConnect.Research import QuantBook

import pandas as pd
from io import StringIO

def get_live_equity_curves(alpha_id_by_name):
"""
Gathers the live equity curves of active alphas. We declare an alpha as 'inactive'
if the last data point in its equity curve is older that the last data point in the
equity curve of another alpha in the alpha_id_by_name dictionary. We truncate
the start of the equity curves so that the resulting DataFrame has always atleast
2 live alphas running at each timestep.

Input:
- client
Client used to communicate with alpha stream REST api
- alpha_id_by_name
Dictionary of alpha IDs, keyed by the alpha name

Returns a DataFrame of normalized live equity curves for the active alphas.
"""
# Get equity curves into a DataFrame
qb = QuantBook()
url = "https://s3.amazonaws.com/alphastreams.quantconnect.com/alphas/equity-unified-live-factors.csv"
equity_curves['Time'] = pd.to_datetime(equity_curves['Time'])
equity_curves.set_index('Time', inplace=True)
equity_curves = equity_curves[[alpha_id for alpha_id in alpha_id_by_name.values()]]
equity_curves.columns = [alpha_name for alpha_name in alpha_id_by_name.keys()]
equity_curves = equity_curves.resample('D').mean().interpolate(method='linear', limit_area='inside')

# Drop inactive alphas
inactive_alphas = equity_curves.iloc[-1].isna().values
for alpha_name in equity_curves.columns[inactive_alphas]:
print(f"'{alpha_name}' excluded because it's marked as inactive.")
has_data = equity_curves.columns[~inactive_alphas]

# Truncate start of history to when there are atleast 2 alphas
equity_curves = equity_curves[has_data].dropna(thresh=2)

# Normalize the equity curves
normalized_curves = pd.DataFrame()
for alpha_id in equity_curves.columns:
alpha_equity = equity_curves[alpha_id].dropna()
alpha_equity = alpha_equity / alpha_equity.iloc
normalized_curves = normalized_curves.join(alpha_equity, how = 'outer')

return normalized_curves
class LogicalFluorescentOrangeSalamander(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2020, 10, 5)  # Set Start Date
self.SetCash(100000)  # Set Strategy Cash

def OnData(self, data):
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the stock data
'''
# if not self.Portfolio.Invested:
#    self.SetHoldings("SPY", 1)
import calendar
import matplotlib.pyplot as plt
import pandas as pd

def plot_allocations_over_time(allocations_over_time):
"""
Creates a plot to show the allocations given to each alpha over time.

Input:
- allocations_over_time
A DataFrame which lists how much to allocate to each alpha over time
"""
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(16, 10))

# Calculate bar plot widths
widths = []
for time, row in allocations_over_time.iterrows():
days_in_month = calendar.monthrange(time.year, time.month)
widths.append(days_in_month - time.day - 0.5)

# Stacked bar plot
ax1.set_title('Alpha Allocations Over Time')
ax1.set_ylabel('Portfolio Weight (%)')
x = allocations_over_time.index
previous_allocations =  * allocations_over_time.shape
for alpha in allocations_over_time.columns:
current_allocations = allocations_over_time[alpha].fillna(0) * 100
ax1.bar(x, current_allocations, widths, bottom=previous_allocations, align='edge', label=alpha)
previous_allocations = current_allocations + previous_allocations
ax1.legend(allocations_over_time.columns, frameon=True, framealpha=0.7, facecolor='white')

# Bar plot
number_of_alphas_allocated_to = []
for time, row in allocations_over_time.iterrows():
number_of_alphas_allocated_to.append(len(row[row > 0]))
ax2.bar(x, number_of_alphas_allocated_to, width=widths, align='edge')
ax2.set_xlabel('Month')
ax2.set_ylabel('Number of Alphas Allocated To')
plt.xticks(rotation=-90)
y_ticks = range(0, max(number_of_alphas_allocated_to)+1)
ax2.yaxis.set_ticks(y_ticks)
ax2.margins(0)
plt.show()

def plot_all_equity_curves(equity_curves, composite_equity_curve):
"""
Plot the equity curves and composite equity curve in a single line chart.

Input:
- equity_curves
Equity curves of the alphas
- composite_equity_curve
Equity curve of the optimized portfolio
"""
all_curves = pd.DataFrame()
all_curves = equity_curves.join(composite_equity_curve, how = 'outer')
all_curves[equity_curves.columns].plot(figsize=(16, 6), c='grey')
all_curves['*CompositeAlpha*'].plot(c='orange', linewidth=4)
plt.legend(all_curves.columns)
plt.title('Alpha Equity vs Optimized Portfolio Equity')
plt.xlabel('Date')
plt.ylabel('Normalized Equity')
plt.show()
from clr import AddReference
from QuantConnect import *
from QuantConnect.Research import QuantBook
from QuantConnect.Statistics import *
from factors import Sortino, Drawdown

from datetime import timedelta
import pandas as pd

def get_performance_statistics(composite_equity_curve, bechmark_security_id="SPY R735QTJ8XC9X"):
"""
Calculates several performance statistics on the composite equity curve.

Input:
- composite_equity_curve
Equity curve of the optimized portfolio
- bechmark_security_id
Security ID of the benchmark to use in some of the statistic calculations (like Beta)

Returns a DataFrame that lists the performance statistics of the composite equity curve.
"""
performance_statistics = {}
daily_returns = list(composite_equity_curve.pct_change().dropna().values)

# CompoundingAnnualPerformance
start_equity = composite_equity_curve.iloc
end_equity = composite_equity_curve.iloc[-1]
num_years = (composite_equity_curve.index[-1] - composite_equity_curve.index).days / 365
comp_annual_performance = Statistics.CompoundingAnnualPerformance(start_equity, end_equity, num_years)
performance_statistics['CompoundingAnnualPerformance'] = "{:.2%}".format(comp_annual_performance)

# AnnualPerformance
performance_statistics['AnnualPerformance'] = Statistics.AnnualPerformance(daily_returns, 365)

# AnnualVariance
performance_statistics['AnnualVariance'] = Statistics.AnnualVariance(daily_returns, 365)

# AnnualStandardDeviation
performance_statistics['AnnualStandardDeviation'] = Statistics.AnnualStandardDeviation(daily_returns, 365)

# Fetch daily benchmark returns
qb = QuantBook()
start_date = composite_equity_curve.index - timedelta(days=5) # 5 day buffer incase of holidays/weekends
end_date = composite_equity_curve.index[-1] + timedelta(days=5)
benchmark_symbol = qb.Symbol(bechmark_security_id)
history = qb.History(benchmark_symbol, start_date, end_date, Resolution.Daily)
closes = history.loc[benchmark_symbol].close
closes = closes.resample('D').mean().interpolate(method='linear')
closes = closes.reindex(pd.DatetimeIndex(composite_equity_curve.index)) # Line up benchmark index with portfolio index
benchmark_daily_returns = list(closes.pct_change().dropna().values)

# Beta
performance_statistics['Beta'] = Statistics.Beta(daily_returns, benchmark_daily_returns)

# Alpha
performance_statistics['Alpha'] = Statistics.Alpha(daily_returns, benchmark_daily_returns, 0)

# Tracking Error
performance_statistics['TrackingError'] = Statistics.TrackingError(daily_returns, benchmark_daily_returns, 365)

# Information Ratio
performance_statistics['InformationRatio'] = Statistics.InformationRatio(daily_returns, benchmark_daily_returns)

# Sharpe
performance_statistics['SharpeRatio'] = Statistics.SharpeRatio(daily_returns, 0)

# Sortino
performance_statistics['Sortino'] = Sortino.evaluate(composite_equity_curve)

# Max Drawdown
performance_statistics['MaxDrawdown'] = "{:.2%}".format(Drawdown.evaluate(composite_equity_curve))

# Treynor Ratio
performance_statistics['TreynorRatio'] = Statistics.TreynorRatio(daily_returns, benchmark_daily_returns, 0)

# PSR
#benchmark_sharpe = Statistics.SharpeRatio(benchmark_daily_returns, 0)
#performance_statistics['ProbabilisticSharpeRatio'] = Statistics.ProbabilisticSharpeRatio(daily_returns, benchmark_sharpe)

# Observed Sharpe Ratio
performance_statistics['ObservedSharpeRatio'] = Statistics.ObservedSharpeRatio(daily_returns)

# Round the statistics for a nice display
for key, value in performance_statistics.items():
if not isinstance(value, str):
performance_statistics[key] = round(value, 4)

return pd.DataFrame(pd.Series(performance_statistics, name='value'))