| Overall Statistics |
|
Total Orders 397 Average Win 5.74% Average Loss -2.03% Compounding Annual Return -9.435% Drawdown 61.200% Expectancy -0.015 Start Equity 100000 End Equity 82678.6 Net Profit -17.321% Sharpe Ratio -0.175 Sortino Ratio -0.22 Probabilistic Sharpe Ratio 4.440% Loss Rate 74% Win Rate 26% Profit-Loss Ratio 2.83 Alpha -0.09 Beta 0.206 Annual Standard Deviation 0.35 Annual Variance 0.122 Information Ratio -0.564 Tracking Error 0.359 Treynor Ratio -0.296 Total Fees $956.75 Estimated Strategy Capacity $8800000000.00 Lowest Capacity Asset ES YOGVNNAOI1OH Portfolio Turnover 198.30% Drawdown Recovery 301 |
# region imports
from AlgorithmImports import *
# endregion
class MuscularYellowGreenCow(QCAlgorithm):
def initialize(self):
self.set_start_date(2023, 1, 1)
self.set_end_date(2024, 12, 1)
self.set_cash(100000)
# Add ES futures with hourly resolution
self._es = self.add_future(Futures.Indices.SP_500_E_MINI, Resolution.HOUR)
self._es.set_filter(0, 90)
# Momentum indicators
self._roc = None # Rate of Change
self._fast_sma = None
self._slow_sma = None
# Current contract
self._contract = None
# Position tracking
self._entry_price = None
self._position_side = None # 1 for long, -1 for short
# Momentum parameters
self._roc_period = 20
self._fast_period = 10
self._slow_period = 30
self._momentum_threshold = 0
def on_securities_changed(self, changes):
for security in changes.added_securities:
# Initialize indicators for the new contract
if security.symbol.security_type == SecurityType.FUTURE:
self._contract = security.symbol
self._roc = self.roc(self._contract, self._roc_period, Resolution.HOUR)
self._fast_sma = self.sma(self._contract, self._fast_period, Resolution.HOUR)
self._slow_sma = self.sma(self._contract, self._slow_period, Resolution.HOUR)
def on_data(self, data: Slice):
# Wait for contract to be set and indicators to be ready
if self._contract is None or self._roc is None:
return
if not self._roc.is_ready or not self._fast_sma.is_ready or not self._slow_sma.is_ready:
return
# Skip if no data for current contract
if not data.bars.contains_key(self._contract):
return
# Get current values
roc_value = self._roc.current.value
fast_sma = self._fast_sma.current.value
slow_sma = self._slow_sma.current.value
price = self.securities[self._contract].price
# Momentum signals
bullish_momentum = roc_value > self._momentum_threshold and fast_sma > slow_sma
bearish_momentum = roc_value < -self._momentum_threshold and fast_sma < slow_sma
# Current position
holdings = self.portfolio[self._contract]
# Entry logic
if not holdings.invested:
if bullish_momentum:
# Go long
quantity = self._calculate_position_size()
self.market_order(self._contract, quantity)
self._entry_price = price
self._position_side = 1
self.debug(f"Long entry at {price}, ROC: {roc_value:.2f}")
elif bearish_momentum:
# Go short
quantity = self._calculate_position_size()
self.market_order(self._contract, -quantity)
self._entry_price = price
self._position_side = -1
self.debug(f"Short entry at {price}, ROC: {roc_value:.2f}")
# Exit logic
else:
if self._entry_price is None:
return
# Calculate P&L in points
if self._position_side == 1: # Long position
pnl_points = price - self._entry_price
# Exit long on bearish momentum or stop loss
if bearish_momentum or pnl_points < -20:
self.liquidate(self._contract)
self.debug(f"Long exit at {price}, P&L: {pnl_points:.2f} points")
self._entry_price = None
self._position_side = None
elif self._position_side == -1: # Short position
pnl_points = self._entry_price - price
# Exit short on bullish momentum or stop loss
if bullish_momentum or pnl_points < -20:
self.liquidate(self._contract)
self.debug(f"Short exit at {price}, P&L: {pnl_points:.2f} points")
self._entry_price = None
self._position_side = None
def _calculate_position_size(self):
# Risk 2% of portfolio per trade
# ES contract multiplier is 50
risk_amount = self.portfolio.total_portfolio_value * 0.02
stop_loss_points = 20
quantity = int(risk_amount / (stop_loss_points * 50))
return max(1, quantity)