| Overall Statistics |
|
Total Orders 3644 Average Win 0.41% Average Loss -0.26% Compounding Annual Return 29.811% Drawdown 35.000% Expectancy 0.081 Start Equity 1000000 End Equity 1400959.7 Net Profit 40.096% Sharpe Ratio 0.673 Sortino Ratio 0.753 Probabilistic Sharpe Ratio 37.300% Loss Rate 58% Win Rate 42% Profit-Loss Ratio 1.59 Alpha 0 Beta 0 Annual Standard Deviation 0.294 Annual Variance 0.087 Information Ratio 0.852 Tracking Error 0.294 Treynor Ratio 0 Total Fees $68137.80 Estimated Strategy Capacity $540000000.00 Lowest Capacity Asset NQ YJHOAMPYKQGX Portfolio Turnover 1246.88% |
# 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)