Overall Statistics |
Total Trades 41 Average Win 4.96% Average Loss -1.90% Compounding Annual Return 37.716% Drawdown 12.900% Expectancy 0.803 Net Profit 40.522% Sharpe Ratio 1.372 Probabilistic Sharpe Ratio 60.382% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 2.61 Alpha 0.27 Beta -0.04 Annual Standard Deviation 0.205 Annual Variance 0.042 Information Ratio 0.952 Tracking Error 0.593 Treynor Ratio -7.033 Total Fees â‚®0.00 Estimated Strategy Capacity â‚®2000000.00 Lowest Capacity Asset ETHUSDT 18N |
# region imports from AlgorithmImports import * from datetime import timedelta from QuantConnect import Resolution, AccountType from QuantConnect.Algorithm import QCAlgorithm from QuantConnect.Brokerages import BrokerageName from QuantConnect.Data.Market import TradeBar # endregion # strategy parameters USE_EMA = True # enable EMAs? USE_EMA_CROSSOVER = False # only enter position on crossovers or every time EMA condition is valid? EMA1_LENGTH = 7 EMA2_LENGTH = 11 MAMA_LENGTH = 55 MAMA_EXP = True MAMA_M_LENGTH = 34 MAMA_ACCEL_FACTOR = True MAMA_P_LENGTH = 13 # Trailing stop USE_TS = False TS_DISTANCE = 10.0 # backtesting options TICKER = "ETHUSDT" MIN_POS = 10 # please enter minimum order quantity here to properly detect near-zero position # because sometimes after closing position there are leftovers remaining on balance TIMEFRAME = timedelta(days=1) # timeframe, use seconds, minutes, hours, days, weeks, months,.. RESOLUTION = Resolution.Minute # backtesting resolution, Second/Minute/Hour/Daily, should be lower than timeframe CASH = 3_000_000 # starting balance POSITION_SIZE_PCT = 33.0 # position size, percent of balance CURRENCY = "USDT" # should match ticker's second currency START_DATE = (2022, 1, 1) # backtesting start date, (YYYY, MM, DD) END_DATE = None # backtesting end date (YYYY, MM, DD) or None to run till the end FEES = False # enable fees class PF_EMA_MAMA(QCAlgorithm): def Initialize(self) -> None: self.SetTimeZone("UTC") self.UniverseSettings.Resolution = RESOLUTION self.SetAccountCurrency("USDT") self.SetCash(CURRENCY, CASH) self.SetStartDate(*START_DATE) if END_DATE: self.SetEndDate(*END_DATE) self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Margin) self.UpdateParameters() security = self.AddCrypto(TICKER, RESOLUTION, Market.Binance) if FEES: security.FeeModel = BinanceFeeModel() else: security.FeeModel = ConstantFeeModel(0.0) self.symbolData = SymbolData(self, security.Symbol) self.SetWarmUp(TIMEFRAME * max(EMA1_LENGTH*5, EMA2_LENGTH*5, MAMA_LENGTH*5, MAMA_M_LENGTH*2*5)) if USE_TS: self.SetRiskManagement(TrailingStopRiskManagementModel(TS_DISTANCE / 100.0)) def UpdateParameters(self): global USE_EMA, USE_EMA_CROSSOVER, EMA1_LENGTH, EMA2_LENGTH, MAMA_LENGTH, MAMA_EXP, \ MAMA_M_LENGTH, MAMA_ACCEL_FACTOR, MAMA_P_LENGTH, USE_TS, TS_DISTANCE USE_EMA = self.convert_param(bool, "USE_EMA", USE_EMA) USE_EMA_CROSSOVER = self.convert_param(bool, "USE_EMA_CROSSOVER", USE_EMA_CROSSOVER) EMA1_LENGTH = self.convert_param(int, "EMA1_LENGTH", EMA1_LENGTH) EMA2_LENGTH = self.convert_param(int, "EMA2_LENGTH", EMA2_LENGTH) MAMA_LENGTH = self.convert_param(int, "MAMA_LENGTH", MAMA_LENGTH) MAMA_EXP = self.convert_param(bool, "MAMA_EXP", MAMA_EXP) MAMA_M_LENGTH = self.convert_param(int, "MAMA_M_LENGTH", MAMA_M_LENGTH) MAMA_ACCEL_FACTOR = self.convert_param(float, "MAMA_ACCEL_FACTOR", MAMA_ACCEL_FACTOR) MAMA_P_LENGTH = self.convert_param(int, "MAMA_P_LENGTH", MAMA_P_LENGTH) USE_TS = self.convert_param(bool, "USE_TS", USE_TS) TS_DISTANCE = self.convert_param(float, "TS_DISTANCE", TS_DISTANCE) def convert_param(self, t, name, default): if t == bool: return bool(float(self.GetParameter(name) or default)) elif t == int: return int(float(self.GetParameter(name) or default)) elif t == float: return float(self.GetParameter(name) or default) class SymbolData: def __init__(self, algo: PF_EMA_MAMA, symbol: Symbol): self.algo = algo self.symbol = symbol # EMAs self.ema1 = ExponentialMovingAverage(EMA1_LENGTH) self.ema2 = ExponentialMovingAverage(EMA2_LENGTH) self.ema1_1 = IndicatorExtensions.Of(Delay(1), self.ema1) self.ema2_1 = IndicatorExtensions.Of(Delay(1), self.ema2) # MaMA self.momentum = Momentum(MAMA_M_LENGTH) self.acceleration = IndicatorExtensions.Of(Momentum(MAMA_M_LENGTH), self.momentum) self.change = Momentum(1) self.probability = SimpleMovingAverage(MAMA_P_LENGTH) if MAMA_EXP: self.mama = ExponentialMovingAverage(MAMA_LENGTH) else: self.mama = SimpleMovingAverage(MAMA_LENGTH) # consolidator consolidator = TradeBarConsolidator(self.CustomConsolidator) consolidator.DataConsolidated += self.OnBar algo.SubscriptionManager.AddConsolidator(symbol, consolidator) # register indicators with consolidator algo.RegisterIndicator(symbol, self.ema1, consolidator) algo.RegisterIndicator(symbol, self.ema2, consolidator) algo.RegisterIndicator(symbol, self.momentum, consolidator) algo.RegisterIndicator(symbol, self.change, consolidator) def CustomConsolidator(self, dt): period = TIMEFRAME if period >= timedelta(days=1): start = dt.replace(hour=0, minute=0, second=0) # elif period == timedelta(hours=1): # start = dt.replace(minute=30) # if start > dt: # start -= period else: start = dt return CalendarInfo(start, period) def OnBar(self, _sender, bar: TradeBar): # calculate and update MaMA if self.change.Current.Value > 0: self.probability.Update(bar.EndTime, 1) else: self.probability.Update(bar.EndTime, 0) if MAMA_ACCEL_FACTOR: val = (self.momentum.Current.Value + .5 * self.acceleration.Current.Value) * self.probability.Current.Value else: val = self.momentum.Current.Value * self.probability.Current.Value adjustedSource = bar.Close + val self.mama.Update(bar.EndTime, adjustedSource) if self.algo.IsWarmingUp: return # plotting self.algo.Plot("Indicators", "Price", bar.Close) self.algo.Plot("Indicators", "MaMA", self.mama.Current.Value) self.algo.Plot("Indicators", "EMA1", self.ema1.Current.Value) # self.algo.Plot("Indicators", "EMA1_1", self.ema1_1.Current.Value) self.algo.Plot("Indicators", "EMA2", self.ema2.Current.Value) # self.algo.Plot("Indicators", "EMA2_1", self.ema2_1.Current.Value) # entry if self.algo.Portfolio[self.symbol].AbsoluteQuantity < MIN_POS: signal = self.GetSignal(bar.Close) if signal != 0: qty = int(self.algo.CalculateOrderQuantity(self.symbol, signal * POSITION_SIZE_PCT / 100.0)) if abs(qty) >= MIN_POS: self.algo.MarketOrder(self.symbol, qty) # exit else: self.CheckExit(bar.Close) def GetSignal(self, price): if not self.mama.IsReady: return 0 ema_signal = 0 if self.ema2_1.IsReady and self.ema1_1.IsReady: if USE_EMA_CROSSOVER: if self.ema1_1.Current.Value <= self.ema2_1.Current.Value and self.ema1.Current.Value > self.ema2.Current.Value: ema_signal = 1 elif self.ema1_1.Current.Value >= self.ema2_1.Current.Value and self.ema1.Current.Value < self.ema2.Current.Value: ema_signal = -1 else: if self.ema1.Current.Value > self.ema2.Current.Value: ema_signal = 1 elif self.ema1.Current.Value < self.ema2.Current.Value: ema_signal = -1 mama_long = price > self.mama.Current.Value if mama_long and (ema_signal == 1 or not USE_EMA): return 1 elif not mama_long and (ema_signal == -1 or not USE_EMA): return -1 else: return 0 def CheckExit(self, price): if not self.mama.IsReady: return mama_long = price > self.mama.Current.Value if USE_EMA and (mama_long and self.ema1.Current.Value < self.ema2.Current.Value): self.algo.Liquidate(tag="Exit") if USE_EMA and (not mama_long and self.ema1.Current.Value > self.ema2.Current.Value): self.algo.Liquidate(tag="Exit")