Overall Statistics
Total Orders
136
Average Win
0.10%
Average Loss
-0.11%
Compounding Annual Return
-2.288%
Drawdown
1.700%
Expectancy
-0.159
Start Equity
1000000
End Equity
988465.95
Net Profit
-1.153%
Sharpe Ratio
-4.945
Sortino Ratio
-4.024
Probabilistic Sharpe Ratio
3.810%
Loss Rate
56%
Win Rate
44%
Profit-Loss Ratio
0.91
Alpha
-0.073
Beta
0.013
Annual Standard Deviation
0.014
Annual Variance
0
Information Ratio
-2.717
Tracking Error
0.089
Treynor Ratio
-5.523
Total Fees
$392.82
Estimated Strategy Capacity
$170000000.00
Lowest Capacity Asset
TSLA UNU3P8Y3WFAD
Portfolio Turnover
7.40%
from AlgorithmImports import *
from collections import deque

class FadingTheGapUniverse(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2024, 1, 1) # <------- CHANGE PARAMETER
        self.set_end_date(2024, 7, 1) # <------- CHANGE PARAMETER
        self.set_cash(1_000_000) # <------- CHANGE PARAMETER
        self.spy = Symbol.create('SPY', SecurityType.EQUITY, Market.USA)
        self.add_universe(self.coarse_filter)

        self.stocks = []
        self.window = {}
        self.volatility = {}

        self.schedule.on(self.date_rules.every_day(self.spy), 
                         self.time_rules.before_market_close(self.spy, 10), 
                         self.closing_bar)
        self.schedule.on(self.date_rules.every_day(self.spy), 
                         self.time_rules.after_market_open(self.spy, 10), 
                         self.opening_bar)
        self.schedule.on(self.date_rules.every_day(self.spy), 
                         self.time_rules.before_market_close(self.spy, 1), 
                         self.close_positions)

    def coarse_filter(self, coarse): # <------- CHANGE PARAMETER
        filtered = [x for x in coarse if x.price > 10 and x.dollar_volume > 1e6]
        return [x.Symbol for x in sorted(filtered, key=lambda x: x.dollar_volume, reverse=True)[:10]]

    def on_securities_changed(self, changes):
        for added in changes.added_securities:
            self.window[added.Symbol] = deque(maxlen=5) # <------- CHANGE PARAMETER
            self.volatility[added.Symbol] = StandardDeviation(60) # <------- CHANGE PARAMETER
            self.add_equity(added.Symbol, Resolution.MINUTE)
            self.stocks.append(added.Symbol)

        for removed in changes.removed_securities:
            if removed.Symbol in self.stocks:
                self.liquidate(removed.Symbol)
                self.stocks.remove(removed.Symbol)

            if removed.Symbol in self.window:
                del self.window[removed.Symbol]

            if removed.Symbol in self.volatility:
                del self.volatility[removed.Symbol]

    def on_data(self, data):
        for symbol in self.stocks:
            if symbol in data and data[symbol] is not None:
                self.volatility[symbol].update(self.time, data[symbol].close)
                if len(self.window[symbol]) == 5: # <------- CHANGE PARAMETER w/ DeQ
                    self.window[symbol].append(data[symbol])

    def opening_bar(self):
        self.log(f"Opening bar triggered at {self.time}")
        for symbol in self.stocks:
            if symbol in self.current_slice.bars:
                self.window[symbol].append(self.current_slice[symbol])

            if not self.volatility[symbol].is_ready:
                continue

            delta = self.window[symbol][0].open - self.window[symbol][1].close
            deviations = delta / self.volatility[symbol].current.value
            if deviations < -1: # <------- CHANGE PARAMETER
                self.set_holdings(symbol, 0.1) # <------- CHANGE PARAMETER

    def close_positions(self):
        self.log(f"Closing positions at {self.time}")
        self.liquidate()

    def closing_bar(self):
        self.log(f"Closing bar triggered at {self.time}")
        for symbol in self.stocks:
            if symbol in self.current_slice.bars:
                self.window[symbol].append(self.current_slice[symbol])