| Overall Statistics |
|
Total Orders 658 Average Win 0.42% Average Loss -0.40% Compounding Annual Return 0.637% Drawdown 10.300% Expectancy 0.006 Start Equity 100000 End Equity 100422.8 Net Profit 0.423% Sharpe Ratio -0.568 Sortino Ratio -0.647 Probabilistic Sharpe Ratio 18.822% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 1.06 Alpha -0.055 Beta 0.054 Annual Standard Deviation 0.083 Annual Variance 0.007 Information Ratio -1.559 Tracking Error 0.129 Treynor Ratio -0.874 Total Fees $1414.70 Estimated Strategy Capacity $860000000.00 Lowest Capacity Asset ES YLZ9Z50BJE2P Portfolio Turnover 741.48% |
from AlgorithmImports import *
from math import *
class ESFuturesVWAP9EMACrossAlgorithm(QCAlgorithm):
def Initialize(self):
# Set start and end date for backtesting
self.SetStartDate(2024, 1, 1)
#self.SetEndDate(2024, 1, 1)
# Set cash allocation
self.SetCash(100000)
resolution = Resolution.Second
# Add ES futures contract (S&P 500 e-mini)
self.es_future = self.AddFuture(Futures.Indices.SP500EMini, resolution, Market.CME)
self.es_future.SetFilter(timedelta(0), timedelta(180)) # Set contract expiry filter
# Variables to keep track of positions and entry state
self.current_position = None
self.position_entry_time = None # Track entry time for position
self.contract = {self.es_future.Symbol: None}
# Schedule to liquidate positions outside regular market hours
self.Schedule.On(
self.DateRules.EveryDay(self.es_future.Symbol),
self.TimeRules.BeforeMarketClose(self.es_future.Symbol, 1),
self.ExitPositions
)
# Create indicators
self.vwap = self.VWAP(self.es_future.Symbol, 14, resolution)
self.ema9 = self.EMA(self.es_future.Symbol, 9, resolution)
# Rolling windows to keep track of EMA, VWAP values, and recent bars
self.ema_window = RollingWindow[float](2) # Store the last 2 EMA values
self.vwap_window = RollingWindow[float](2) # Store the last 2 VWAP values
self.price_window = RollingWindow[TradeBar](2) # Store the last 2 bars
# Request consolidated minute data
self.consolidator = TradeBarConsolidator(timedelta(minutes=10))
self.consolidator.DataConsolidated += self.OnDataConsolidated
# Register the consolidator
self.SubscriptionManager.AddConsolidator(self.es_future.Symbol, self.consolidator)
def OnData(self, slice):
for kvp in slice.FutureChains:
symbol = kvp.Key
chain = kvp.Value
# Find the most liquid contract
if symbol in self.contract:
most_liquid_contract = sorted(chain, key=lambda contract: contract.OpenInterest, reverse=True)[0]
self.contract[symbol] = most_liquid_contract
# Check profit and stop loss targets for active positions
if self.current_position is not None:
contract = self.contract[self.es_future.Symbol]
if contract is None:
return
symbol = contract.Symbol
# Retrieve current position details
current_holdings = self.Portfolio[symbol]
entry_price = current_holdings.AveragePrice if current_holdings.Invested else None
if entry_price is not None:
# Define profit and stop loss targets
profit_target_points = 10
stop_loss_points = 10
if self.current_position == "long":
# Profit target and stop loss for long position
stop_loss_price = entry_price - stop_loss_points
profit_target_price = entry_price + profit_target_points
if slice[symbol].Close <= stop_loss_price:
self.Liquidate(symbol)
self.current_position = None
self.Debug(f"Long Stop Loss Hit: {self.Time}, Price: {slice[symbol].Close}")
elif slice[symbol].Close >= profit_target_price:
self.Liquidate(symbol)
self.current_position = None
self.Debug(f"Long Profit Target Hit: {self.Time}, Price: {slice[symbol].Close}")
elif self.current_position == "short":
# Profit target and stop loss for short position
stop_loss_price = entry_price + stop_loss_points
profit_target_price = entry_price - profit_target_points
if slice[symbol].Close >= stop_loss_price:
self.Liquidate(symbol)
self.current_position = None
self.Debug(f"Short Stop Loss Hit: {self.Time}, Price: {slice[symbol].Close}")
elif slice[symbol].Close <= profit_target_price:
self.Liquidate(symbol)
self.current_position = None
self.Debug(f"Short Profit Target Hit: {self.Time}, Price: {slice[symbol].Close}")
def OnDataConsolidated(self, sender, bar):
contract = self.contract[self.es_future.Symbol]
if contract is None:
return
symbol = contract.Symbol
# Add the latest bar to the rolling window
self.price_window.Add(bar)
# Ensure indicators are ready and rolling windows have enough data
if not self.vwap.IsReady or not self.ema9.IsReady or self.price_window.Count < 2:
return
# Add the latest EMA and VWAP values to their respective rolling windows
self.ema_window.Add(self.ema9.Current.Value)
self.vwap_window.Add(self.vwap.Current.Value)
# Ensure rolling windows have enough data
if self.ema_window.Count < 2 or self.vwap_window.Count < 2:
return
# Get the current and previous values from rolling windows
current_ema = self.ema_window[0]
previous_ema = self.ema_window[1]
current_vwap = self.vwap_window[0]
previous_vwap = self.vwap_window[1]
# Get the current and previous bars
current_bar = self.price_window[0]
previous_bar = self.price_window[1]
# Long entry condition: 9EMA crosses above VWAP and the last 2 bars close above VWAP
if previous_ema <= previous_vwap and current_ema > current_vwap and \
current_bar.Close > previous_bar.Close and previous_bar.Close > previous_vwap:
if self.current_position != "long" or not self.Portfolio.Invested:
#self.Liquidate()
limit_price = ceil(max(current_bar.High, previous_bar.High) + 1) # Set limit price slightly above the high
#self.LimitOrder(symbol, 1, limitPrice=limit_price) # Use limit order for long entry
self.MarketOrder(symbol, 1)
self.current_position = "long"
self.Debug(f"Entered Long: {self.Time}, EMA: {current_ema}, VWAP: {current_vwap}")
# Short entry condition: 9EMA crosses below VWAP and the last 2 bars close below VWAP
elif previous_ema >= previous_vwap and current_ema < current_vwap and \
current_bar.Close < previous_bar.Close and previous_bar.Close < previous_vwap:
if self.current_position != "short" or not self.Portfolio.Invested:
#self.Liquidate()
limit_price = ceil(min(current_bar.Low, previous_bar.Low) - 1) # Set limit price slightly below the low
#self.LimitOrder(symbol, -1, limitPrice=limit_price) # Use limit order for short entry
self.MarketOrder(symbol, -1)
self.current_position = "short"
self.Debug(f"Entered Short: {self.Time}, EMA: {current_ema}, VWAP: {current_vwap}")
def ExitPositions(self):
# Liquidate all positions if they exist
if self.current_position is not None:
self.Liquidate()
self.current_position = None
self.Debug(f"Position liquidated after the next candle: {self.Time}")