| Overall Statistics |
|
Total Orders 5765 Average Win 1.81% Average Loss -0.94% Compounding Annual Return 7.189% Drawdown 43.500% Expectancy 0.037 Start Equity 1000000 End Equity 1517537.8 Net Profit 51.754% Sharpe Ratio 0.244 Sortino Ratio 0.356 Probabilistic Sharpe Ratio 1.639% Loss Rate 65% Win Rate 35% Profit-Loss Ratio 1.93 Alpha 0.116 Beta -0.155 Annual Standard Deviation 0.427 Annual Variance 0.183 Information Ratio 0.058 Tracking Error 0.471 Treynor Ratio -0.676 Total Fees $514362.20 Estimated Strategy Capacity $4000000.00 Lowest Capacity Asset NKD YT87FWY1TBLT Portfolio Turnover 1397.10% |
# 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(1_000_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 a universe of Futures contracts to trade.
self._futures = []
tickers = [
Futures.Indices.SP_500_E_MINI,
Futures.Indices.NIKKEI_225_DOLLAR,
#Futures.Indices.HANG_SENG, # https://github.com/QuantConnect/Lean/issues/8751
#Futures.Indices.EURO_STOXX_50 # https://github.com/QuantConnect/Lean/issues/8748
]
for ticker in tickers:
future = self.add_future(
ticker,
data_mapping_mode=DataMappingMode.LAST_TRADING_DAY,
data_normalization_mode=DataNormalizationMode.BACKWARDS_PANAMA_CANAL,
contract_depth_offset=0
)
future.set_filter(0, 180)
future.vwap = self.vwap(future.symbol)
future.daily_volatility = IndicatorExtensions.of(StandardDeviation(self._lookback), self.roc(future.symbol, 1, Resolution.DAILY))
future.avg_move_by_interval = {}
future.yesterdays_close = None
future.todays_open = None
self._futures.append(future)
# Add a Scheduled Event to place orders 30 minutes after market open.
date_rule = self.date_rules.every_day(future.symbol)
self.schedule.on(date_rule, self.time_rules.after_market_close(future.symbol, 1), lambda future=future: setattr(future, 'yesterdays_close', future.price))
self.schedule.on(date_rule, self.time_rules.after_market_open(future.symbol, 1), lambda future=future: setattr(future, 'todays_open', future.open))
self.schedule.on(date_rule, self.time_rules.every(self._trading_interval_length), lambda future=future: self._rebalance(future))
self.schedule.on(date_rule, self.time_rules.before_market_close(future.symbol, 1), lambda future=future: self.liquidate(future.mapped))
# Set a warm-up period to warm-up the indicators.
self.set_warm_up(timedelta(30))
def _rebalance(self, future):
# Wait until the market is open.
t = self.time
if (not future.yesterdays_close or
not future.todays_open or
not future.exchange.hours.is_open(t, False) or
not future.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 future.avg_move_by_interval:
future.avg_move_by_interval[trading_interval] = SimpleMovingAverage(self._lookback)
avg_move = future.avg_move_by_interval[trading_interval]
# Update the average move indicator.
move = abs(future.price / future.todays_open - 1)
if not avg_move.update(t, move):
return
# Wait until the daily volatility indicator is ready.
if not future.daily_volatility.is_ready or self.is_warming_up:
return
# Calculate the noise area.
upper_bound = max(future.yesterdays_close, future.todays_open) * (1+avg_move.current.value)
lower_bound = min(future.yesterdays_close, future.todays_open) * (1-avg_move.current.value)
# Scan for entries.
weight = min(self._max_exposure, self._target_volatility/future.daily_volatility.current.value) / self._max_exposure / len(self._futures)
contract = self.securities[future.mapped]
if not contract.holdings.is_long and future.price > upper_bound:
self.set_holdings(contract.symbol, weight)
elif not contract.holdings.is_short and future.price < lower_bound:
self.set_holdings(contract.symbol, -weight)
# Scan for exits.
elif (contract.holdings.is_long and future.price < max(upper_bound, future.vwap.current.value) or
contract.holdings.is_short and future.price > min(lower_bound, future.vwap.current.value)):
self.liquidate(contract.symbol)
# Plot the current state.
#self.plot('Weight', str(future.symbol), weight)
#self.plot('Noise Area', 'Upper Bound', upper_bound)
#self.plot('Noise Area', 'Lower Bound', lower_bound)
#self.plot('Noise Area', 'Price', future.price)
#self.plot('Noise Area', 'VWAP', future.vwap.current.value)
#self.plot('Volatility', 'Future', future.daily_volatility.current.value)