Overall Statistics
Total Orders
2984
Average Win
0.76%
Average Loss
-0.77%
Compounding Annual Return
11.688%
Drawdown
34.000%
Expectancy
0.091
Start Equity
100000
End Equity
187103.40
Net Profit
87.103%
Sharpe Ratio
0.465
Sortino Ratio
0.463
Probabilistic Sharpe Ratio
14.334%
Loss Rate
45%
Win Rate
55%
Profit-Loss Ratio
0.99
Alpha
-0.016
Beta
0.753
Annual Standard Deviation
0.138
Annual Variance
0.019
Information Ratio
-0.604
Tracking Error
0.07
Treynor Ratio
0.085
Total Fees
$3053.46
Estimated Strategy Capacity
$7900000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
77.67%
from datetime import timedelta
import numpy as np
from AlgorithmImports import *

class M_StrengthAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetUniverseSelection(ManualUniverseSelectionModel(["SPY"]))
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
        self.SetExecution(ImmediateExecutionModel())
        # self.add_alpha(RsiAlphaModel())
        # self.add_risk_management(NullRiskManagementModel())

        ## included stop-loss at 4% ##

        self.SetStartDate(2019, 1, 1)
        self.SetEndDate(2024, 9, 1)
        self.SetCash(100000)
        self.spy = self.AddEquity("SPY", Resolution.Minute).Symbol
        self.lookback = timedelta(minutes=5)
        self.position_count = 0
        self.positions = []
        self.halt_trades = True
        self.initial_price = None
        self.stop_loss_percent = 0.04
        self.Settings.MinimumOrderMarginPortfolioValue = 0
        self.SetWarmUp(self.lookback)

        self.Schedule.On(
            self.DateRules.EveryDay(),
            self.TimeRules.AfterMarketOpen(self.spy, 1),
            self.InitialInvestment
        )

        self.Schedule.On(
            self.DateRules.EveryDay(),
            self.TimeRules.At(10, 0),
            self.CheckInitialSlope
        )

        self.Schedule.On(
            self.DateRules.EveryDay(),
            self.TimeRules.Every(timedelta(minutes=5)),
            self.EvaluateMarket
        )

        self.Schedule.On(
            self.DateRules.EveryDay(),
            self.TimeRules.At(12, 15),
            self.SellAllPositions
        )

    def InitialInvestment(self):
        if self.IsWarmingUp:
            return
        if not self.Portfolio[self.spy].Invested:
            self.SetHoldings(self.spy, 0.4)

    def CheckInitialSlope(self):
        history = self.History(self.spy, self.lookback, Resolution.Minute)
        if history.empty or self.spy not in history.index or 'close' not in history.columns:
            return
        closes = history.loc[self.spy]["close"].values
        if len(closes) < self.lookback.total_seconds() / 60:
            return
        x = np.arange(len(closes))
        m, _ = np.polyfit(x, closes, 1)
        if abs(m) <= np.tan(np.radians(30)):
            self.halt_trades = False
            self.initial_price = closes[-1]
        else:
            self.halt_trades = True

    def EvaluateMarket(self):
        if self.IsWarmingUp or self.halt_trades:
            return
        history = self.History(self.spy, self.lookback, Resolution.Minute)
        if history.empty or self.spy not in history.index or 'close' not in history.columns:
            return
        closes = history.loc[self.spy]["close"].values
        if len(closes) < self.lookback.total_seconds() / 60:
            return
        current_price = closes[-1]
        allocation = (self.Portfolio.TotalPortfolioValue - self.Portfolio[self.spy].HoldingsValue) / current_price
        quantity = int(allocation)
        if quantity > 0 and self.CanPlaceOrder(quantity, current_price):
            if not self.Portfolio[self.spy].Invested:
                self.MarketOrder(self.spy, quantity)
                self.positions.append(current_price)
                self.position_count += 1
                self.SetStopLoss(self.spy, self.stop_loss_percent)
            elif len(self.positions) > 0 and self.position_count < 3:
                if current_price > self.positions[-1]:
                    self.MarketOrder(self.spy, quantity)
                    self.positions.append(current_price)
                    self.position_count += 1
                    self.SetStopLoss(self.spy, self.stop_loss_percent)
                elif current_price < self.initial_price:
                    self.halt_trades = True
            elif len(self.positions) == 3:
                if current_price > self.positions[-1]:
                    self.MarketOrder(self.spy, quantity)
                    sell_quantity = self.Portfolio[self.spy].Quantity / len(self.positions)
                    if sell_quantity > 0:
                        self.MarketOrder(self.spy, -sell_quantity)
                    self.positions.pop(0)
                    self.positions.append(current_price)
                    self.SetStopLoss(self.spy, self.stop_loss_percent)
                elif current_price < self.initial_price:
                    self.halt_trades = True

    def CanPlaceOrder(self, quantity, price):
        cost = quantity * price
        margin_remaining = self.Portfolio.GetMarginRemaining(self.spy, OrderDirection.Buy)
        if self.Portfolio.Cash >= cost and margin_remaining >= cost:
            return True
        return False

    def SetStopLoss(self, symbol, stop_loss_percent):
        quantity = self.Portfolio[symbol].Quantity
        if quantity > 0:
            stop_price = self.Portfolio[symbol].AveragePrice * (1 - stop_loss_percent)
            self.StopMarketOrder(symbol, -quantity, stop_price)

    def SellAllPositions(self):
        if self.Portfolio.Invested:
            history = self.History(self.spy, self.lookback, Resolution.Minute)
            if history.empty or self.spy not in history.index or 'close' not in history.columns:
                self.Liquidate(self.spy)
                self.positions = []
                self.position_count = 0
                return
            closes = history.loc[self.spy]["close"].values
            if len(closes) < 2:
                self.Liquidate(self.spy)
                self.positions = []
                self.position_count = 0
                return
            m, _ = np.polyfit(np.arange(len(closes)), closes, 1)
            if m > 0:
                return
            self.Liquidate(self.spy)
            self.positions = []
            self.position_count = 0