Overall Statistics
Total Orders
5280
Average Win
0.12%
Average Loss
-0.20%
Compounding Annual Return
9.449%
Drawdown
41.400%
Expectancy
0.099
Start Equity
100000
End Equity
157080.80
Net Profit
57.081%
Sharpe Ratio
0.235
Sortino Ratio
0.239
Probabilistic Sharpe Ratio
5.330%
Loss Rate
31%
Win Rate
69%
Profit-Loss Ratio
0.60
Alpha
0.014
Beta
0.631
Annual Standard Deviation
0.213
Annual Variance
0.045
Information Ratio
-0.038
Tracking Error
0.2
Treynor Ratio
0.079
Total Fees
$5404.74
Estimated Strategy Capacity
$37000000.00
Lowest Capacity Asset
DELL X0QFBGE1E9GL
Portfolio Turnover
3.86%
Drawdown Recovery
1121
# region imports
from AlgorithmImports import *
# endregion


class MomentumAlphaModel(AlphaModel):

    def __init__(self, algorithm, date_rule, lookback, resolution, num_insights):
        self._lookback = lookback
        self._resolution = resolution
        self._num_insights = num_insights
        self._securities = []
        self._insights = []
        # Schedule monthly alpha emission at 8:00 for daily equity workflows.
        algorithm.schedule.on(date_rule, algorithm.time_rules.at(8, 0), self._create_insights)

    def _create_insights(self):
        insights = []
        for security in self._securities:
            if not security.roc.is_ready:
                continue
            magnitude = security.roc.current.value
            if magnitude < 0:
                continue
            insights.append(Insight.price(security, Expiry.END_OF_MONTH, InsightDirection.UP, magnitude))
        # Keep only the strongest monthly signals by ROC magnitude.
        self._insights = sorted(insights, key=lambda i: i.magnitude)[-self._num_insights:]

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

    def on_securities_changed(self, algorithm, changes):
        for security in changes.removed_securities:
            if security in self._securities:
                self._securities.remove(security)
        for security in changes.added_securities:
            security.roc = algorithm.roc(security, self._lookback, self._resolution)
            self._securities.append(security)
# region imports
from AlgorithmImports import *
from alpha import MomentumAlphaModel
from risk import RiskModelWithSPY
# endregion


class MomentumFrameworkAlgo(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(100_000)
        self.settings.automatic_indicator_warm_up = True
        self.settings.seed_initial_prices = True
        self.universe_settings.resolution = Resolution.HOUR
        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
        spy = self.add_equity("SPY")
        self.set_benchmark(spy)
        universe_size = 20
        # Use a monthly universe schedule anchored to the US equity calendar.
        date_rule = self.date_rules.month_start(spy)
        self.universe_settings.schedule.on(date_rule)
        self.universe_settings.asynchronous = True
        self.add_universe_selection(
            FundamentalUniverseSelectionModel(
                # Sort by the highest dollar-volume symbols after filtering by price.
                lambda fundamental: [
                    x.symbol
                    for x in sorted(
                        [x for x in fundamental if x.has_fundamental_data and x.price > 10],
                        key=lambda x: x.dollar_volume,
                    )[-universe_size:]
                ]
            )
        )
        # Emit monthly momentum insights based on rolling ROC values.
        self.add_alpha(
            MomentumAlphaModel(self, date_rule, 60, Resolution.DAILY, 10)
        )
        self.set_portfolio_construction(
            EqualWeightingPortfolioConstructionModel(Expiry.END_OF_MONTH, PortfolioBias.LONG)
        )
        self.add_risk_management(
            RiskModelWithSPY(self, spy, 200, Resolution.DAILY)
        )
# region imports
from AlgorithmImports import *
# endregion


class RiskModelWithSPY(RiskManagementModel):

    def __init__(self, algorithm, spy, lookback, resolution):
        self._spy = spy
        self._ema = algorithm.ema(spy, lookback, resolution)

    def manage_risk(self, algorithm, targets):
        # Liquidate invested assets when SPY falls below its EMA filter.       
        if not self._ema.is_ready or self._spy.price > self._ema.current.value:
            return []
        targets = [PortfolioTarget(security, 0) for security in algorithm.active_securities.values()]
        # Cancel insights to prevent the PCM from re-entering liquidated positions.
        algorithm.insights.cancel([t.symbol for t in targets])
        return targets