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 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'))
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 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)
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)
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