| Overall Statistics |
|
Total Orders 2679 Average Win 0.91% Average Loss -0.47% Compounding Annual Return 20.907% Drawdown 14.500% Expectancy 0.192 Start Equity 100000 End Equity 312836.54 Net Profit 212.837% Sharpe Ratio 1.015 Sortino Ratio 1.306 Probabilistic Sharpe Ratio 70.750% Loss Rate 60% Win Rate 40% Profit-Loss Ratio 1.94 Alpha 0.123 Beta -0.043 Annual Standard Deviation 0.118 Annual Variance 0.014 Information Ratio 0.197 Tracking Error 0.215 Treynor Ratio -2.755 Total Fees $13314.81 Estimated Strategy Capacity $75000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X Portfolio Turnover 300.03% |
# region imports
from AlgorithmImports import *
# endregion
class NoiseAreaBreakoutAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2019, 5, 1)
self.set_end_date(2025, 5, 1)
self.set_cash(100_000)
# Set some parameters.
self._trading_interval_length = timedelta(minutes=30)
self._lookback = 14 # days
self._target_volatility = 0.02 # 0.02 = 2%
self._max_exposure = 4 # 4 = 400%
# Add SPY to trade.
self._equity = self.add_equity('SPY', leverage=self._max_exposure)
self._equity.vwap = self.vwap(self._equity.symbol)
self._equity.daily_volatility = IndicatorExtensions.of(StandardDeviation(self._lookback), self.roc(self._equity.symbol, 1, Resolution.DAILY))
self._equity.avg_move_by_interval = {}
self._equity.yesterdays_close = None
self._equity.todays_open = None
# Add Scheduled Events to update members and place trades.
date_rule = self.date_rules.every_day(self._equity.symbol)
self.schedule.on(date_rule, self.time_rules.midnight, lambda: setattr(self._equity, 'yesterdays_close', self._equity.price))
self.schedule.on(date_rule, self.time_rules.after_market_open(self._equity.symbol, 1), lambda: setattr(self._equity, 'todays_open', self._equity.open))
self.schedule.on(date_rule, self.time_rules.every(self._trading_interval_length), self._rebalance)
self.schedule.on(date_rule, self.time_rules.before_market_close(self._equity.symbol, 1), self.liquidate)
# Set a warm-up period to warm-up the indicators.
self.set_warm_up(timedelta(30))
def _rebalance(self):
# Wait until the market is open.
t = self.time
if (not self._equity.yesterdays_close or
not self._equity.exchange.hours.is_open(t, False) or
not self._equity.exchange.hours.is_open(t - self._trading_interval_length, False)):
return
# Create an indicator for this time interval if it doesn't already exist.
trading_interval = (t.hour, t.minute)
if trading_interval not in self._equity.avg_move_by_interval:
self._equity.avg_move_by_interval[trading_interval] = SimpleMovingAverage(self._lookback)
avg_move = self._equity.avg_move_by_interval[trading_interval]
# Update the average move indicator.
move = abs(self._equity.price / self._equity.todays_open - 1)
if not avg_move.update(t, move):
return
# Wait until the daily volatility indicator is ready.
if not self._equity.daily_volatility.is_ready or self.is_warming_up:
return
# Calculate the noise area.
upper_bound = max(self._equity.yesterdays_close, self._equity.todays_open) * (1+avg_move.current.value)
lower_bound = min(self._equity.yesterdays_close, self._equity.todays_open) * (1-avg_move.current.value)
# Scan for entries.
weight = min(self._max_exposure, self._target_volatility/self._equity.daily_volatility.current.value)
if not self._equity.holdings.is_long and self._equity.price > upper_bound:
self.set_holdings(self._equity.symbol, weight)
elif not self._equity.holdings.is_short and self._equity.price < lower_bound:
self.set_holdings(self._equity.symbol, -weight)
# Scan for exits.
elif (self._equity.holdings.is_long and self._equity.price < max(upper_bound, self._equity.vwap.current.value) or
self._equity.holdings.is_short and self._equity.price > min(lower_bound, self._equity.vwap.current.value)):
self.liquidate()
# Plot the current state.
self.plot('Weight', 'value', weight)
self.plot('Noise Area', 'Upper Bound', upper_bound)
self.plot('Noise Area', 'Lower Bound', lower_bound)
self.plot('Noise Area', 'Price', self._equity.price)
self.plot('Noise Area', 'VWAP', self._equity.vwap.current.value)
self.plot('Volatility', 'SPY', self._equity.daily_volatility.current.value)