Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
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.835
Tracking Error
0.145
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
Drawdown Recovery
0
# region imports
from AlgorithmImports import *
# endregion

class ContinuousFuturesConstantAlphaModel(AlphaModel):
    ''' Provides an implementation of IAlphaModel that always returns the same insight for each security'''

    def __init__(self, type, direction, period, magnitude = None, confidence = None):
        '''Initializes a new instance of the ConstantAlphaModel class
        Args:
            type: The type of insight
            direction: The direction of the insight
            period: The period over which the insight with come to fruition
            magnitude: The predicted change in magnitude as a +- percentage
            confidence: The confidence in the insight'''
        self.type = type
        self.direction = direction
        self.period = period
        self.magnitude = magnitude
        self.confidence = confidence
        self.securities = []
        self.insights_time_by_symbol = {}

    def update(self, algorithm, data):
        ''' Creates a constant insight for each security as specified via the constructor
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        insights = []

        for security in self.securities:
            # security price could be zero until we get the first data point. e.g. this could happen
            # when adding both forex and equities, we will first get a forex data point
            if security.price != 0 and self.should_emit_insight(algorithm.utc_time, security):
                symbol = security.mapped if security.type == SecurityType.FUTURE else security.symbol
                insights.append(Insight(symbol, self.period, self.type, self.direction, self.magnitude, self.confidence))

        return insights


    def on_securities_changed(self, algorithm, changes):
        ''' Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''
        for added in changes.added_securities:
            if ((added.type == SecurityType.FUTURE and added.symbol.is_canonical()) or \
                added.type == SecurityType.EQUITY):
                self.securities.append(added)

        # this will allow the insight to be re-sent when the security re-joins the universe
        for removed in changes.removed_securities:
            if removed in self.securities:
                self.securities.remove(removed)
            if removed.symbol in self.insights_time_by_symbol:
                self.insights_time_by_symbol.pop(removed.symbol)


    def should_emit_insight(self, utc_time, security):
        if not security.is_tradable or security.is_delisted:
            return False
            
        generated_time_utc = self.insights_time_by_symbol.get(security.symbol)

        if generated_time_utc is not None:
            # we previously emitted a insight for this symbol, check it's period to see
            # if we should emit another insight
            if utc_time - generated_time_utc < self.period:
                return False

        # we either haven't emitted a insight for this symbol or the previous
        # insight's period has expired, so emit a new insight now for this symbol
        self.insights_time_by_symbol[security.symbol] = utc_time
        return True
# region imports
from AlgorithmImports import *
from universe import FrontMonthFutureUniverseSelectionModel
from portfolio import ModifiedRiskParityPortfolioConstructionModel
# endregion

class AllWeatherPortfolio(QCAlgorithm):

    def initialize(self):
        self.set_end_date(datetime.now())
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(100000)  # Set Strategy Cash

        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
        
        self.universe_settings.resolution = Resolution.MINUTE
        self.universe_settings.leverage = 1
        # We try to include various intuitively unrelated assets to reduce individual asset's risk
        symbols = [Symbol.create(ticker, SecurityType.EQUITY, Market.USA)
            for ticker in [
                "SPY",  # US Equity
                "EEM",  # Emerging market Equity
                "UUP"   # cash
        ]]
        # self.add_universe_selection(ManualUniverseSelectionModel(symbols))
        self.add_universe_selection(FrontMonthFutureUniverseSelectionModel(self.select_future_chain_symbols))
        
        self.add_alpha(ConstantAlphaModel(InsightType.PRICE, InsightDirection.UP, timedelta(30)))

        # RiskParityPortfolioConstructionModel seeks to build a portfolio with the 
        # equal contribution of risk to the total portfolio risk from all assets
        # We build a modified version on top of that to handle corporate events
        # https://www.quantconnect.com/docs/v2/writing-algorithms/algorithm-framework/portfolio-construction/supported-models#11-Risk-Parity-Model
        self.set_portfolio_construction(ModifiedRiskParityPortfolioConstructionModel(
            self, lambda x: Expiry.END_OF_WEEK(x)))

        self.set_warm_up(timedelta(252))

        self.set_benchmark("SPY")
    
    def select_future_chain_symbols(self, utc_time):
        return [ 
            # Symbol.create(Futures.Financials.MICRO_Y_2_TREASURY_BOND, SecurityType.FUTURE, Market.CBOT),
            # Symbol.create(Futures.Financials.MICRO_Y_10_TREASURY_NOTE, SecurityType.FUTURE, Market.CBOT),
            # Symbol.create(Futures.Energies.MICRO_CRUDE_OIL_WTI, SecurityType.FUTURE, Market.NYMEX),
            # Symbol.create(Futures.Metals.MICRO_GOLD, SecurityType.FUTURE, Market.COMEX)
            Symbol.create(Futures.Softs.SUGAR_11, SecurityType.FUTURE, Market.ICE),
            Symbol.create(Futures.Softs.COTTON_2, SecurityType.FUTURE, Market.ICE),
            Symbol.create(Futures.Softs.COFFEE, SecurityType.FUTURE, Market.ICE)
        ]
# region imports
from AlgorithmImports import *
from scipy.optimize import *
# endregion

### <summary>
### Provides an implementation of a risk parity portfolio optimizer that calculate the optimal weights 
### with the weight range from 0 to 1 and equalize the risk carried by each asset
### </summary>
class RiskParityPortfolioOptimizer:
    
    def __init__(self, 
                 algorithm,
                 minimum_weight = 1e-05, 
                 maximum_weight = sys.float_info.max):
        self.algorithm = algorithm
        self.minimum_weight = minimum_weight if minimum_weight >= 1e-05 else 1e-05
        self.maximum_weight = maximum_weight if maximum_weight >= minimum_weight else minimum_weight

    def optimize(self, historical_returns, budget = None, covariance = None):
        if covariance is None:
            covariance = np.cov(historical_returns.T)

        size = historical_returns.columns.size   # K x 1
        if size == 1: 
            return np.array([1])
        
        # Optimization Problem
        # minimize_{x >= 0} f(x) = 1/2 * x^T.S.x - b^T.log(x)
        # b = 1 / num_of_assets (equal budget of risk)
        # df(x)/dx = S.x - b / x
        # H(x) = S + Diag(b / x^2)
        # lw <= x <= up
        x0 = np.array(size * [1. / size])
        budget = budget if budget is not None else x0
        objective = lambda weights: 0.5 * weights.T @ covariance @ weights - budget.T @ np.log(weights)
        gradient = lambda weights: covariance @ weights - budget / weights
        hessian = lambda weights: covariance + np.diag((budget / weights**2).flatten())
        bounds = tuple((self.minimum_weight, self.maximum_weight) for _ in range(size))
        solver = minimize(objective, jac=gradient, hess=hessian, x0=x0, bounds=bounds, method="trust-constr")

        if not solver["success"]:
            if self.algorithm.live_mode:
                self.algorithm.log("RiskParityPortfolioOptimizer.optimize: Did not converge. Return equal weighted")
            return x0
        # Normalize weights: w = x / x^T.1
        return solver["x"]/np.sum(solver["x"])
# region imports
from AlgorithmImports import *
from optimizer import RiskParityPortfolioOptimizer
# endregion

class ModifiedRiskParityPortfolioConstructionModel(PortfolioConstructionModel):
    '''Modified version of LEAN's Risk Parity Portfolio Construction Model to handle corporate events also'''
    def __init__(self,
                 algorithm,
                 rebalance = Resolution.DAILY,
                 portfolio_bias = PortfolioBias.LONG_SHORT,
                 lookback = 1,
                 period = 252,
                 optimizer = None) -> None:
        super().__init__()
        if portfolio_bias == PortfolioBias.SHORT:
            raise ArgumentException("Long position must be allowed in RiskParityPortfolioConstructionModel.")
        
        self.algorithm = algorithm
        self.lookback = lookback
        self.period = period
        self.sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)

        self.optimizer = RiskParityPortfolioOptimizer(algorithm) if optimizer is None else optimizer

        self.symbol_data_by_symbol = {}

        # If the argument is an instance of Resolution or Timedelta
        # Redefine rebalancing_func
        rebalancing_func = rebalance
        if isinstance(rebalance, int):
            rebalance = Extensions.to_time_span(rebalance)
        if isinstance(rebalance, timedelta):
            rebalancing_func = lambda dt: dt + rebalance
        if rebalancing_func:
            self.set_rebalancing_func(rebalancing_func)

    def determine_target_percent(self, active_insights: List[Insight]) -> Dict[Insight, float]:
        targets = {}

        # Reset indicators when corporate actions occur
        data = self.algorithm.current_slice
        for symbol in set(list(data.splits.keys()) + list(data.dividends.keys())):
            if symbol in self.symbol_data_by_symbol:
                self.symbol_data_by_symbol[symbol].roc.reset()
                self.symbol_data_by_symbol[symbol].window.reset()
                self.symbol_data_by_symbol[symbol].warm_up_indicators()

        # If we have no insights just return an empty target dict
        if not active_insights:
            self.algorithm.log('PortfolioContructionModel: No active insights. Create zero-quantity targets.')
            return targets

        symbols = [insight.symbol for insight in active_insights]

        # Create a dictionary keyed by the symbols in the insights with an pandas.series as value to create a data frame
        returns = { str(symbol.id) : data.returns for symbol, data in self.symbol_data_by_symbol.items() if symbol in symbols }
        returns = pd.DataFrame(returns).dropna(axis=1, how='all')

        # The portfolio optimizer finds the optional weights for the given data
        weights = self.optimizer.optimize(returns)
        weights = pd.Series(weights, index = returns.columns)

        # Create portfolio targets from the specified insights with a 10% buffer
        for insight in active_insights:
            targets[insight] = .9 * weights.get(str(insight.symbol.id), 0) / self.symbol_data_by_symbol[insight.symbol].multiplier

        return targets

    def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None:
        algorithm.log(f'PortfolioContructionModel.on_securities_changed: Changes: {changes}')
        
        # clean up data for removed securities
        super().on_securities_changed(algorithm, changes)
        for removed in changes.removed_securities:
            symbolData = self.symbol_data_by_symbol.pop(removed.symbol, None)
            symbolData.dispose()

        # initialize data for added securities
        for added in changes.added_securities:
            symbol = added.symbol
            if symbol not in self.symbol_data_by_symbol:
                symbolData = self.RiskParitySymbolData(
                    self.algorithm, added, self.lookback, self.period)
                self.symbol_data_by_symbol[symbol] = symbolData

    class RiskParitySymbolData:
        def __init__(self, algorithm, security, lookback, period):
            self.algorithm = algorithm
            self.symbol = security.symbol
            self.lookback = lookback
            self.period = period
            self.multiplier = security.symbol_properties.contract_multiplier \
                if security.type == SecurityType.FUTURE and security.symbol_properties.contract_multiplier > 1 \
                else 1

            self.roc = RateOfChange(f'{self.symbol}.ROC({lookback})', lookback)
            self.roc.updated += self.on_rate_of_change_updated
            self.window = RollingWindow[IndicatorDataPoint](period)

            self.consolidator = TradeBarConsolidator(1)
            algorithm.subscription_manager.add_consolidator(self.symbol, self.consolidator)
            algorithm.register_indicator(self.symbol, self.roc, self.consolidator)

            self.warm_up_indicators()

        def dispose(self):
            self.algorithm.subscription_manager.remove_consolidator(self.symbol, self.consolidator)
            self.roc.updated -= self.on_rate_of_change_updated
            self.roc.reset()
            self.window.reset()

        def warm_up_indicators(self):
            self.algorithm.warm_up_indicator(self.symbol, self.roc, Resolution.DAILY)

        def on_rate_of_change_updated(self, roc, updated):
            if roc.is_ready:
                self.window.add(updated)

        @property
        def returns(self):
            series = pd.Series(
                data = [x.value for x in self.window],
                index = [x.end_time.date() for x in self.window]
            )
            # if same date, only keep latest datum
            series = series[~series.index.duplicated(keep='last')]
            return series

        @property
        def is_ready(self):
            return self.window.is_ready
#region imports
from AlgorithmImports import *
from Selection.FutureUniverseSelectionModel import FutureUniverseSelectionModel
#endregion

class FrontMonthFutureUniverseSelectionModel(FutureUniverseSelectionModel):
    '''Creates futures chain universes that select the front month contract and runs a user
    defined futureChainSymbolSelector every day to enable choosing different futures chains'''
    def __init__(self, select_future_chain_symbols):
        super().__init__(timedelta(7), select_future_chain_symbols)

    def filter(self, filter):
        '''Defines the futures chain universe filter'''
        return (filter.front_month()
                      .only_apply_filter_at_market_open())