Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe 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
-3.128
Tracking Error
0.149
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
from scipy.optimize import minimize, LinearConstraint
import numpy as np
import pandas as pd

from clr import AddReference
AddReference("QuantConnect.Research")
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
    print("Working please wait...")
    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[0]
    
        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
AddReference("QuantConnect.Research")
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
    def load_data(benchmark_security_id='SPY R735QTJ8XC9X', benchmark_name='*Benchmark*'):
        """
        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)
        
        # Load benchmark history
        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:
            Beta.load_data()
        start = equity_curve.index[0]
        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][0]
        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
AddReference("QuantConnect.Research")
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"
    csv = qb.Download(url)
    equity_curves = pd.read_csv(StringIO(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[0]
        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
        # self.AddEquity("SPY", Resolution.Minute)


    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)[1]
        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 = [0] * allocations_over_time.shape[0]
    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
AddReference("QuantConnect.Research")
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[0]
    end_equity = composite_equity_curve.iloc[-1]
    num_years = (composite_equity_curve.index[-1] - composite_equity_curve.index[0]).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[0] - 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'))