Overall Statistics
Total Trades
100
Average Win
0.04%
Average Loss
-0.02%
Compounding Annual Return
3.762%
Drawdown
17.300%
Expectancy
0.897
Net Profit
9.312%
Sharpe Ratio
0.359
Probabilistic Sharpe Ratio
13.704%
Loss Rate
33%
Win Rate
67%
Profit-Loss Ratio
1.82
Alpha
0.041
Beta
-0.074
Annual Standard Deviation
0.1
Annual Variance
0.01
Information Ratio
-0.137
Tracking Error
0.255
Treynor Ratio
-0.485
Total Fees
$0.00
from datetime import timedelta


class QuarterlyRebalance(QCAlgorithm):

    def Initialize(self):
        
        # Set Start Date, End Date, and Cash
        #---------------------------------------------------------------------------------------
        self.SetStartDate(2018, 1, 1)     
        self.SetEndDate(2020, 5, 31)        
        self.SetCash(100000)                 
        #---------------------------------------------------------------------------------------
        
        # Resolution
        #---------------------------------------------------------------------------------------
        self.resolution = Resolution.Daily    
        #---------------------------------------------------------------------------------------
        
        # Brokerage, Orders, Portfolio Settings
        #---------------------------------------------------------------------------------------
        self.SetBrokerageModel(BrokerageName.Alpaca, AccountType.Margin)      # AccountType.Margin required for "non-null" Portfolio and Execution Models
        self.DefaultOrderProperties.TimeInForce = TimeInForce.GoodTilCanceled           # Time in Force for open orders. Default is GoodTilCanceled (GTC)
        self.Settings.RebalancePortfolioOnInsightChanges = False          
        self.Settings.RebalancePortfolioOnSecurityChanges = False
        #---------------------------------------------------------------------------------------

        # Universe Settings
        #---------------------------------------------------------------------------------------
        self.UniverseSettings.Resolution = self.resolution
        self.UniverseSettings.SetDataNormalizationMode = DataNormalizationMode.SplitAdjusted
        self.UniverseSettings.FeeModel = ConstantFeeModel(0.0)
        self.UniverseSettings.ExtendedMarketHours = False
        self.UniverseSettings.Leverage = 0
        self.weekday = self.Time.isocalendar()[2]     
        #---------------------------------------------------------------------------------------
        
        # Algorithm Framework Configuration
        #---------------------------------------------------------------------------------------
        self.SetUniverseSelection(CustomUniverseSelectionModel("CustomUniverseSelectionModel", lambda time: [ "SPY", "AGG"]))
        self.SetAlpha(EMAAlphaModel())  
        self.SetPortfolioConstruction(MyInsightWeightingPortfolioConstructionModel(timedelta(weeks=2)))       
        self.SetExecution(ImmediateExecutionModel())                               
        self.SetRiskManagement(NullRiskManagementModel()) 
        #---------------------------------------------------------------------------------------

        
     
class EMAAlphaModel(AlphaModel):
    
    def __init__(self):
        
        pass
            
        
    def OnSecuritiesChanged(self, algorithm, changes):
        # Create indicator for each new security
        for security in changes.AddedSecurities:
            pass
                
        for security in changes.RemovedSecurities:
            pass
        
            
            
    def Update(self, algorithm, data):
        
        insights = []
        
        for security in algorithm.ActiveSecurities.Values:
            insights.append(Insight.Price(security.Symbol, timedelta(minutes = 20), InsightDirection.Up, 0.003, None, None, 0.45))


        return insights



class MyInsightWeightingPortfolioConstructionModel(EqualWeightingPortfolioConstructionModel):
    '''Provides an implementation of IPortfolioConstructionModel that generates percent targets based on the
    Insight.Weight. The target percent holdings of each Symbol is given by the Insight.Weight from the last
    active Insight for that symbol.
    For insights of direction InsightDirection.Up, long targets are returned and for insights of direction
    InsightDirection.Down, short targets are returned.
    If the sum of all the last active Insight per symbol is bigger than 1, it will factor down each target
    percent holdings proportionally so the sum is 1.
    It will ignore Insight that have no Insight.Weight value.'''
    
    def __init__(self, rebalance = Resolution.Daily, portfolioBias = PortfolioBias.LongShort):
        '''Initialize a new instance of InsightWeightingPortfolioConstructionModel
        Args:
            rebalance: Rebalancing parameter. If it is a timedelta, date rules or Resolution, it will be converted into a function.
                              If None will be ignored.
                              The function returns the next expected rebalance time for a given algorithm UTC DateTime.
                              The function returns null if unknown, in which case the function will be called again in the
                              next loop. Returning current time will trigger rebalance.
            portfolioBias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
            
        #if algorithm.Time.month == 6:    # $$$$$$$$$$CROSSFIELD CAPITAL EDIT$$$$$$$$$$$$$
        #    algorithm.Debug("YOLO")
        self.months = 0
        super().__init__(rebalance, portfolioBias)
    
    def ShouldCreateTargetForInsight(self, insight):
        '''Method that will determine if the portfolio construction model should create a
        target for this insight
        Args:
            insight: The insight to create a target for'''
        # Ignore insights that don't have Weight value
        return insight.Weight is not None
    
    def DetermineTargetPercent(self, activeInsights):
        '''Will determine the target percent for each insight
        Args:
            activeInsights: The active insights to generate a target for'''
        
        result = {}

        # We will adjust weights proportionally in case the sum is > 1 so it sums to 1.
        weightSums = sum(self.GetValue(insight) for insight in activeInsights if self.RespectPortfolioBias(insight))
        weightFactor = 1.0
        if weightSums > 1:
            weightFactor = 1 / weightSums
        for insight in activeInsights:
            result[insight] = (insight.Direction if self.RespectPortfolioBias(insight) else InsightDirection.Flat) * self.GetValue(insight) * weightFactor
        return result
    
    def GetValue(self, insight):
        '''Method that will determine which member will be used to compute the weights and gets its value
        Args:
            insight: The insight to create a target for
        Returns:
            The value of the selected insight member'''
        return insight.Weight