Overall Statistics
Total Trades
91
Average Win
0.41%
Average Loss
-0.83%
Compounding Annual Return
-47.600%
Drawdown
30.200%
Expectancy
-0.560
Net Profit
-18.133%
Sharpe Ratio
-1.205
Probabilistic Sharpe Ratio
4.484%
Loss Rate
71%
Win Rate
29%
Profit-Loss Ratio
0.50
Alpha
-0.319
Beta
0.272
Annual Standard Deviation
0.28
Annual Variance
0.078
Information Ratio
-0.88
Tracking Error
0.308
Treynor Ratio
-1.237
Total Fees
$290.75
Estimated Strategy Capacity
$8700000.00
Lowest Capacity Asset
GE R735QTJ8XC9X
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *

### <summary>
### Framework algorithm that uses the PearsonCorrelationPairsTradingAlphaModel.
### This model extendes BasePairsTradingAlphaModel and uses Pearson correlation
### to rank the pairs trading candidates and use the best candidate to trade.
###
### This modification limits the universe to a specific sector (as per MorningstarSectorCode)
### </summary>
class PearsonCorrelationPairsTradingAlphaModelFrameworkAlgorithm(QCAlgorithm):
    '''Framework algorithm that uses the PearsonCorrelationPairsTradingAlphaModel.
    This model extendes BasePairsTradingAlphaModel and uses Pearson correlation
    to rank the pairs trading candidates and use the best candidate to trade.'''

    def Initialize(self):

        self.SetStartDate(2022,7,1)

        self.sector = self.GetParameter("sector") or "FinancialServices"
        self.riskPercent = self.GetParameter("riskPercent", 0.05)
        self.topX = int(self.GetParameter("topX", 100))

        self.sectors = {
            "FinancialServices": MorningstarSectorCode.FinancialServices,
            "RealEstate": MorningstarSectorCode.RealEstate,
            "Healthcare": MorningstarSectorCode.Healthcare,
            "Utilities": MorningstarSectorCode.Utilities,
            "Technology": MorningstarSectorCode.Technology,
            "BasicMaterials": MorningstarSectorCode.BasicMaterials,
            "ConsumerCyclical": MorningstarSectorCode.ConsumerCyclical,
            "ConsumerDefensive": MorningstarSectorCode.ConsumerDefensive,
            "CommunicationServices": MorningstarSectorCode.CommunicationServices,
            "Energy": MorningstarSectorCode.Energy,
            "Industrials": MorningstarSectorCode.Industrials}
            
        self.AddUniverseSelection(FineFundamentalUniverseSelectionModel(self.SelectCoarse, self.SelectFine))

        self.SetAlpha(PearsonCorrelationPairsTradingAlphaModel(252, Resolution.Daily))
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
        self.SetExecution(ImmediateExecutionModel())

        # self.SetRiskManagement(TrailingStopRiskManagementModel(self.riskPercent))
        # self.AddRiskManagement(MaximumDrawdownPercentPerSecurity(self.riskPercent))
        # self.AddRiskManagement(MaximumDrawdownPercentPortfolio(self.riskPercent))
        self.SetRiskManagement(NullRiskManagementModel())

    def SelectCoarse(self, coarse: List[CoarseFundamental]) -> List[Symbol]:
        selected = [c for c in coarse if c.HasFundamentalData]
        sorted_by_dollar_volume = sorted(selected, key=lambda c: c.DollarVolume, reverse=True)
        return [c.Symbol for c in sorted_by_dollar_volume[:self.topX]] # Return most liquid assets w/ fundamentals


    def SelectFine(self, fine):

        if self.sector != "all":
            filtered_fine = [x.Symbol for x in fine if x.AssetClassification.MorningstarSectorCode == self.sectors[self.sector]]

        else:
            filtered_fine = [x.Symbol for x in fine]

        if len(filtered_fine) < 2:
            filtered_fine = []

        return filtered_fine # return only assets within one sector (unless sector is 'all', in which case no filter is applied)