Overall Statistics
Total Orders
7444
Average Win
0.42%
Average Loss
-0.26%
Compounding Annual Return
34.263%
Drawdown
61.700%
Expectancy
0.048
Start Equity
1000000
End Equity
1463360.9
Net Profit
46.336%
Sharpe Ratio
0.698
Sortino Ratio
0.78
Probabilistic Sharpe Ratio
31.436%
Loss Rate
60%
Win Rate
40%
Profit-Loss Ratio
1.59
Alpha
0
Beta
0
Annual Standard Deviation
0.605
Annual Variance
0.366
Information Ratio
0.785
Tracking Error
0.605
Treynor Ratio
0
Total Fees
$319938.60
Estimated Strategy Capacity
$51000000.00
Lowest Capacity Asset
MES YJHOAMPYKQGX
Portfolio Turnover
2629.34%
# region imports
from datetime import timedelta
from AlgorithmImports import *
import numpy as np
import json
# endregion

class Purplereignv2(QCAlgorithm):

    def Initialize(self):
        # Set the start and end dates of the backtest
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2024, 6, 1)
        self.set_cash(1000000)
        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
        # Initialize the future chains dictionary
        self.future_chains = {}
        # Initialize the portfolio dictionary
        self.portfolio_data = {}
        # List of futures to trade
        self.portfolio_assets = {
            Futures.Indices.NASDAQ_100_E_MINI: "NQM4",
            Futures.Indices.MICRO_NASDAQ_100_E_MINI: "MNQM4",
            Futures.Indices.SP_500_E_MINI: "ESM4",
            Futures.Indices.MICRO_SP_500_E_MINI: "MESM4"
        }
        for future_api, ticker in self.portfolio_assets.items():
            
            # Store the initalizer in the portfolio dictionary
            self.portfolio_data[ticker] = {}
            
            # Add the future to the algorithm
            future = self.add_future(future_api, Resolution.MINUTE, data_normalization_mode = DataNormalizationMode.RAW, leverage = 1, data_mapping_mode = DataMappingMode.LAST_TRADING_DAY)
            future.set_filter(lambda future_filter_universe: future_filter_universe.front_month())

            # Store the symbol, squeeze, and MACD long entry indicator
            self.portfolio_data[ticker]['symbol'] = future.Symbol
            self.portfolio_data[ticker]['squeeze'] = False
            self.portfolio_data[ticker]['macd_long_in'] = False
            
            # Set up indicators
            self.portfolio_data[ticker]['macd'] = self.MACD(self.portfolio_data[ticker]['symbol'], 8, 17, 9, MovingAverageType.Simple)
            self.portfolio_data[ticker]['bb'] = self.BB(self.portfolio_data[ticker]['symbol'], 18, 2, MovingAverageType.Simple)
            self.portfolio_data[ticker]['kc'] = self.KCH(self.portfolio_data[ticker]['symbol'], 18, 1.5, MovingAverageType.Simple)
            
            # Create a RollingWindow to store the past 3 values of the MACD Histogram
            self.portfolio_data[ticker]['macd_hist_window'] = RollingWindow[IndicatorDataPoint](3)

            # Create a RollingWindow to store the past 2 values of trading bars
            self.portfolio_data[ticker]['trading_window'] = RollingWindow[TradeBar](2)

            # Consolidate the data into 5-minute bars
            self.Consolidate(self.portfolio_data[ticker]['symbol'], timedelta(minutes=5), self.on_data_consolidated)
            self.register_indicator(self.portfolio_data[ticker]['symbol'], self.portfolio_data[ticker]['macd'], timedelta(minutes=5))
            self.register_indicator(self.portfolio_data[ticker]['symbol'], self.portfolio_data[ticker]['bb'], timedelta(minutes=5))
            self.register_indicator(self.portfolio_data[ticker]['symbol'], self.portfolio_data[ticker]['kc'], timedelta(minutes=5))

            # Store the open, high, low, and close prices
            self.portfolio_data[ticker]['open'] = 0
            self.portfolio_data[ticker]['high'] = 0
            self.portfolio_data[ticker]['low'] = 0
            self.portfolio_data[ticker]['close'] = 0

            # Setting stoploss
            self.portfolio_data[ticker]['stop_loss_len'] = 5*20
            self.portfolio_data[ticker]['stop_loss_indicator'] = self.MIN(self.portfolio_data[ticker]['symbol'], self.portfolio_data[ticker]['stop_loss_len'], Resolution.MINUTE)
            self.portfolio_data[ticker]['stop_loss'] = 0
            self.portfolio_data[ticker]['start_stop_loss'] = False

        # Warming up engine
        self.set_warm_up(5*20, Resolution.MINUTE)

        self.settings.free_portfolio_value = 0.3
    
        # Schedule the algo to run every 5 minutes
        self.schedule.on(self.date_rules.every_day(), self.time_rules.every(timedelta(minutes=5)), self.trade_logic)

    def on_data(self, data: Slice):

        # Check if the data strategy is warming up
        if self.is_warming_up:
            return
        
        for ticker in self.portfolio_data.keys():

            if data.bars.contains_key(self.portfolio_data[ticker]['symbol']):
                bar = data.bars[self.portfolio_data[ticker]['symbol']]
                self.portfolio_data[ticker]['open'] = bar.Open
                self.portfolio_data[ticker]['high'] = bar.High
                self.portfolio_data[ticker]['low'] = bar.Low
                self.portfolio_data[ticker]['close'] = bar.Close

            self.portfolio_data[ticker]['stop_loss'] = self.portfolio_data[ticker]['stop_loss_indicator'].Current.Value


    def on_data_consolidated(self, data: slice):
        # Check if the data strategy is warming up
        if self.is_warming_up:
            return
        
    def trade_logic(self):
        if self.is_warming_up:
            return

        data = self.portfolio_data
    
        # Loop through the tickers in the portfolio
        for ticker in self.portfolio_data.keys():
            
            # Check if the Bollinger Bands are within the Keltner Channels
            self.portfolio_data[ticker]['squeeze'] = self.portfolio_data[ticker]['bb'].UpperBand.Current.Value < self.portfolio_data[ticker]['kc'].UpperBand.Current.Value and self.portfolio_data[ticker]['bb'].LowerBand.Current.Value > self.portfolio_data[ticker]['kc'].LowerBand.Current.Value
            # self.log(f"Squeeze indicator {ticker}: {self.portfolio_data[ticker]['squeeze']}")

            # Check for MACD entry signal
            self.portfolio_data[ticker]['macd_hist_window'].Add(self.portfolio_data[ticker]['macd'].Histogram.Current)

            # Ensure we have 3 data points in the window
            if self.portfolio_data[ticker]['macd_hist_window'].IsReady:
                macd_hist = self.portfolio_data[ticker]['macd_hist_window'][0].Value
                macd_hist_1 = self.portfolio_data[ticker]['macd_hist_window'][1].Value
                macd_hist_2 = self.portfolio_data[ticker]['macd_hist_window'][2].Value
            
                self.portfolio_data[ticker]['macd_long_in'] = (macd_hist > macd_hist_1 or macd_hist > macd_hist_2) and macd_hist > 0
                # self.log(f"MACD entry {ticker}: {self.portfolio_data[ticker]['macd_long_in']}")

            # Get the current contract
            continuous_future_symbol = self.portfolio_data[ticker]['symbol']
            current_contract = self.securities[continuous_future_symbol].mapped

            # Entry signal
            if not self.portfolio[current_contract].invested:
                if self.portfolio_data[ticker]['squeeze'] and self.portfolio_data[ticker]['macd_long_in']:
                    # Entering the trade
                    self.set_holdings(current_contract, 0.1)
                    # Tracking stop loss
                    self.portfolio_data[ticker]['start_stop_loss'] = True
                    self.portfolio_data[ticker]['stop_loss'] = self.portfolio_data[ticker]['stop_loss_indicator'].Current.Value

            # Exit signal
            else:
                if self.portfolio_data[ticker]['trading_window'].IsReady:
                    if self.portfolio_data[ticker]['close'] > self.portfolio_data[ticker]['trading_window'][1].Close and self.portfolio_data[ticker]['start_stop_loss']:
                        self.portfolio_data[ticker]['stop_loss'] += self.portfolio_data[ticker['close'] - self.portfolio_data[ticker]['trading_window'][1].Close]
                
                if self.portfolio_data[ticker]['start_stop_loss'] and (self.portfolio_data[ticker]['close'] < self.portfolio_data[ticker]['stop_loss'] or not self.Time.hour in range(9, 16)):
                    self.liquidate(current_contract)
                    self.portfolio_data[ticker]['start_stop_loss'] = False
                    self.send_webhook_notification("Sell", ticker)
                    self.log(f"Stop loss for {ticker}/{current_contract} at {self.portfolio_data[ticker]['close']}")

    
    def send_webhook_notification(self, position, ticker):
        url = 'https://newagewallstreet.io/version-test/api/1.1/wf/catch-trades?api_token=cc7d071598606d101e84f252c9654956'
        data = {
            "strat": "1716875896866x801605936682184200",
            "ticker": ticker,
            "position": position
        }
        headers = {'Content-Type': 'application/json'}
        self.Notify.Web(url, json.dumps(data), headers)