Overall Statistics
Total Orders
403
Average Win
0.65%
Average Loss
-0.48%
Compounding Annual Return
33.209%
Drawdown
5.900%
Expectancy
0.340
Start Equity
100000
End Equity
123689.81
Net Profit
23.690%
Sharpe Ratio
1.939
Sortino Ratio
2.325
Probabilistic Sharpe Ratio
91.021%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
1.35
Alpha
0.052
Beta
0.785
Annual Standard Deviation
0.087
Annual Variance
0.008
Information Ratio
0.539
Tracking Error
0.036
Treynor Ratio
0.215
Total Fees
$281.20
Estimated Strategy Capacity
$12000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
81.58%
from datetime import timedelta
import numpy as np
from AlgorithmImports import *

class MyFrameworkAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2024, 1, 1)
        self.SetEndDate(datetime.now())
        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  # Adjust stop-loss to 5% for tighter risk management

        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.45)

    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)):  # Slope less than 30 degrees
            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  # Use remaining capital for trading
        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)  # Sell the oldest position
                    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