Overall Statistics
Total Orders
1004
Average Win
0.73%
Average Loss
-0.73%
Compounding Annual Return
7.859%
Drawdown
26.700%
Expectancy
0.113
Start Equity
1000000
End Equity
1459074.97
Net Profit
45.907%
Sharpe Ratio
0.171
Sortino Ratio
0.198
Probabilistic Sharpe Ratio
5.116%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
0.99
Alpha
-0.017
Beta
0.899
Annual Standard Deviation
0.167
Annual Variance
0.028
Information Ratio
-0.197
Tracking Error
0.11
Treynor Ratio
0.032
Total Fees
$6903.53
Estimated Strategy Capacity
$31000000.00
Lowest Capacity Asset
TMK R735QTJ8XC9X
Portfolio Turnover
4.86%
Drawdown Recovery
539
# Region imports.
from AlgorithmImports import *
# End region.


class LevermannFactorsAlphaModel(AlphaModel):

    def __init__(self, algorithm, date_rule, benchmark):
        self._benchmark = benchmark
        self._universe = []
        self._assets = 10
        # Add a Scheduled Event to emit monthly insights matching the universe.
        self._insights = []
        algorithm.schedule.on(date_rule, algorithm.time_rules.at(8, 0), self._create_insights)

    def on_securities_changed(self, algorithm, changes):
        # Create indicators for added securities.
        for security in changes.added_securities:
            security.roc_1m = algorithm.roc(security, 21)
            if security == self._benchmark:
                continue
            security.roc_6m = algorithm.roc(security, 6 * 21)
            security.roc_1y = algorithm.roc(security, 252)
            security.session.size = 252
            for bar in algorithm.history[TradeBar](security, 253):
                security.session.update(bar)
            self._universe.append(security)
        # Remove securities that leave the universe.
        for security in changes.removed_securities:
            self._universe.remove(security)

    def _create_insights(self):
        # Calculate the score of each security in the universe.
        # Scoring according to https://letyourmoneygrow.com/2018/03/09/susan-levermann-approach-an-investment-strategy-that-works/.
        score_by_security = {}
        for security in self._universe:
            if not (security.roc_1y.is_ready and self._benchmark.roc_1m.is_ready):
                continue
            f = security.fundamentals
            debt_to_equity_ratio = f.operation_ratios.total_debt_equity_ratio.one_month
            inverse_debt_to_equity_ratio = 1 / debt_to_equity_ratio if debt_to_equity_ratio else None
            quarterly_report_price_change = None
            filing_date = f.financial_statements.file_date.value
            if filing_date is not None and security.price is not None and security.price == security.price:
                prior_close_list = [bar.close for bar in security.session if bar.end_time <= filing_date]
                if prior_close_list:
                    quarterly_report_price_change = (security.price / prior_close_list[0]) - 1
            # Score the Levermann threshold factors.
            factors = (
                # Factor 1: One year RoE >20%: +1 ; <10%: -1.
                Factor(f.operation_ratios.roe.one_year, 0.2, 0.1, True),
                # Factor 2: EBIT One Year >12%: +1 ; <6%: -1.
                Factor(f.operation_ratios.ebit_margin.one_year, 0.12, 0.06, True),
                # Factor 3: Equity Ratio one year >25%: +1 ; <15%: -1.
                Factor(inverse_debt_to_equity_ratio, 0.25, 0.15, True),
                # Factor 4: P/E one Year <12: +1 ; >16: -1.
                Factor(f.valuation_ratios.pe_ratio, 12, 16, False),
                # Factor 5: P/E five years <13: +1 ; >17: -1.
                Factor(f.valuation_ratios.pe_ratio_5_year_average, 13, 17, False),
                # Factor 7: Real price reaction in % on quarterly EPS report >1%: +1 ; <-1%: -1.
                Factor(quarterly_report_price_change, 0.05, -0.05, True),
                # Factor 8: Current FQ Est EPS% change >5%: +1 ; <-5%: -1.
                Factor(f.valuation_ratios.forward_earning_yield, 0.05, -0.05, True),
                # Factor 9: 6 months price change >5%: +1 ; <-5%: -1.
                Factor(security.roc_6m.current.value, 0.05, -0.05, True),
                # Factor 10: 12 months price change >5%: +1 ; <-5%: -1.
                Factor(security.roc_1y.current.value, 0.05, -0.05, True),
                # Factor 11: EPS growth: Change of current to next FY Est EPS >5%: +1; <-5%: -1.
                Factor(f.valuation_ratios.second_year_estimated_eps_growth, 0.05, -0.05, True),
            )
            security_score = sum(factor.calculate() for factor in factors)
            # Factor 12: Momentum: if 6 months price change > 5% and 12 month price change < -5%: 1.
            # If 6 months price change < -5% and 12 month price change > 5%: -1.
            if security.roc_6m.current.value > 0.05 and security.roc_1y.current.value < -0.05:
                security_score += 1
            elif security.roc_6m.current.value < -0.05 and security.roc_1y.current.value > 0.05:
                security_score -= 1
            # Factor 13: Reversal: if better than benchmark: 1 ; if worse than benchmark -1.
            security_score += 1 if security.roc_1m < self._benchmark.roc_1m else -1
            score_by_security[security] = security_score
        # Create insights for the assets with the greatest scores: Score >= 4.
        self._insights = [
            Insight.price(security, Expiry.END_OF_MONTH, InsightDirection.UP)
            for security in sorted(score_by_security, key=lambda security: score_by_security[security])[-self._assets:]
            if score_by_security[security] >= 4
        ]

    def update(self, algorithm, data):
        insights = self._insights.copy()
        self._insights.clear()
        return insights


class Factor:

    def __init__(self, factor_value, bullish_threshold, bearish_threshold, higher_is_better=True):
        self._factor_value = factor_value
        self._bullish_threshold = bullish_threshold
        self._bearish_threshold = bearish_threshold
        self._higher_is_better = higher_is_better

    def calculate(self):
        if self._factor_value is None:
            return 0
        if self._higher_is_better:
            if self._factor_value > self._bullish_threshold:
                return 1
            if self._factor_value < self._bearish_threshold:
                return -1
            return 0
        if self._factor_value < self._bullish_threshold:
            return 1
        if self._factor_value > self._bearish_threshold:
            return -1
        return 0
# region imports
from AlgorithmImports import *
from alpha import LevermannFactorsAlphaModel
# endregion


class TheRelaxedWayToWealthAlgorithm(QCAlgorithmFramework):

    def initialize(self):
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(1_000_000)
        # Define some settings.
        self.settings.automatic_indicator_warm_up = True
        self.settings.rebalance_portfolio_on_security_changes = False
        # Add the universe of SPY constituents.
        benchmark = self.add_equity('SPY', Resolution.DAILY)
        date_rule = self.date_rules.month_start(benchmark)
        self.universe_settings.resolution = Resolution.DAILY
        self.universe_settings.schedule.on(date_rule)
        self.set_universe_selection(ETFConstituentsUniverseSelectionModel(benchmark))
        # Add the Alpha, PCM, and Execution models.
        self.set_alpha(LevermannFactorsAlphaModel(self, date_rule, benchmark))
        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel(lambda _: None))
        # Add a warm-up period so the algorithm trades right away.
        self.set_warm_up(timedelta(45))