Overall Statistics |
Total Trades 29 Average Win 4.27% Average Loss -1.92% Compounding Annual Return 19.720% Drawdown 11.600% Expectancy 0.503 Net Profit 13.457% Sharpe Ratio 0.811 Probabilistic Sharpe Ratio 38.882% Loss Rate 53% Win Rate 47% Profit-Loss Ratio 2.22 Alpha 0.143 Beta -0.061 Annual Standard Deviation 0.187 Annual Variance 0.035 Information Ratio 0.447 Tracking Error 0.672 Treynor Ratio -2.496 Total Fees $3762.32 Estimated Strategy Capacity $310000000.00 Lowest Capacity Asset ETHUSD E3 |
#region imports from AlgorithmImports import * #endregion import pandas as pd import numpy as np import math import datetime class JumpingBlueSalamander(QCAlgorithm): def Initialize(self): # Set Start Date self.SetStartDate(2022, 1, 15) # self.SetEndDate(2022, 1, 20) # Set Strategy Cash self.SetCash(100000) # Set brokerage model self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Margin) # Add Crypto btc = self.AddCrypto("BTCUSD", Resolution.Hour) # Set margin btc.BuyingPowerModel = SecurityMarginModel(3.3) # Symbol self.BTC_symbol = btc.Symbol # Create 4-hour consolidator four_hour = TradeBarConsolidator(timedelta(hours=4)) # Register "FourHourHandler" to receive 4-hour consolidated bars four_hour.DataConsolidated += self.FourHourHandler # Subscribe our 4-hour consolidator object to be automatically updated with 4-hour bars self.SubscriptionManager.AddConsolidator(self.BTC_symbol, four_hour) # RSI self.relative_strength = RelativeStrengthIndex(14) # Simple moving average self.simple_moving_average = SimpleMovingAverage(9) # 2-period EMA self.ema_two = ExponentialMovingAverage(2) # 5-period EMA self.ema_five = ExponentialMovingAverage(5) # 8-period EMA self.ema_eight = ExponentialMovingAverage(8) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.relative_strength, four_hour) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.simple_moving_average, four_hour) # History history = self.History([self.BTC_symbol], 1000, Resolution.Hour) # Loc history history = history.loc[self.BTC_symbol] # Four-hour bar storage self.storage = {"open":[],"high":[],"low":[],"close":[],"volume":[]} # Check self.check = False # Loop through history for time, row in history.iterrows(): # Create tradebar with history data bar = TradeBar(time, self.BTC_symbol, row.open, row.high, row.low, row.close, row.volume) # Update 4-hour consolidator four_hour.Update(bar) # Check false self.check = True # Difference EMA self.EMA_difference = 0 def OnData(self, data: Slice): pass def FourHourHandler(self, sender, bar): # Four-hour bar storage self.storage["open"].append(bar.Open) # Four-hour bar storage self.storage["high"].append(bar.High) # Four-hour bar storage self.storage["low"].append(bar.Low) # Four-hour bar storage self.storage["close"].append(bar.Close) # Four-hour bar storage self.storage["volume"].append(bar.Volume) # If more than 100 data points stored if len(self.storage["close"]) > 100: # Four-hour bar storage self.storage["open"] = self.storage["open"][-100:] # Four-hour bar storage self.storage["high"] = self.storage["high"][-100:] # Four-hour bar storage self.storage["low"] = self.storage["low"][-100:] # Four-hour bar storage self.storage["close"] = self.storage["close"][-100:] # Four-hour bar storage self.storage["volume"] = self.storage["volume"][-100:] # Count count = 0 # Convert storage into dataframe dataframe = pd.DataFrame(self.storage) # Fisher transform fish_value = self.Fisher_Transform_Indicator(dataframe) # Current fish value current_fish = fish_value[-1] # If current fish greater than SMA if current_fish > self.simple_moving_average.Current.Value: # Count +1 count += 1 # If current fish greater than previous fish if current_fish > fish_value[-2]: # Count +1 count += 1 # Squeeze momentum count += self.Squeeze_Momentum_Indicator(dataframe) # Update EMAs self.ema_two.Update(self.Time, count) self.ema_five.Update(self.Time, count) self.ema_eight.Update(self.Time, count) # If check is True if self.check == True: # If EMA difference is 0 if self.EMA_difference == 0: # Get ema difference self.EMA_difference = self.ema_five.Current.Value - self.ema_eight.Current.Value # Else else: # Get previous ema difference previous_ema_difference = self.EMA_difference # Get current ema difference current_ema_difference = self.ema_five.Current.Value - self.ema_eight.Current.Value # Update self.EMA_difference = current_ema_difference # If current ema difference is positive and previous is negative if current_ema_difference > 0 and previous_ema_difference < 0: # If long if self.Portfolio[self.BTC_symbol].IsShort: # Liquidate self.Liquidate() # If current close greater than 2-period ema if bar.Close > self.ema_two.Current.Value: # If RSI greater than 50 if self.relative_strength.Current.Value > 50: # Value of order value = self.Portfolio.TotalPortfolioValue / bar.Close # Long self.MarketOrder(self.BTC_symbol, value) # If current ema difference is negative and previous is positive if current_ema_difference < 0 and previous_ema_difference > 0: # If long if self.Portfolio[self.BTC_symbol].IsLong: # Liquidate self.Liquidate() # If current close greater than 2-period ema if bar.Close < self.ema_two.Current.Value: # If RSI greater than 50 if self.relative_strength.Current.Value > 50: # Value of order value = self.Portfolio.TotalPortfolioValue / bar.Close # Short self.MarketOrder(self.BTC_symbol, -value) def Fisher_Transform_Indicator(self, dataframe): # Dataframe df = dataframe window = 10 df["minLowPrice"] = df['low'].rolling(window = window).min() df["maxHighPrice"] = df['high'].rolling(window = window).max() df["mid_price"] = (df["low"] + df["high"])/2 df["minLowPrice"] = df["minLowPrice"].fillna(0) df["maxHighPrice"] = df["maxHighPrice"].fillna(0) diffRatio = 0.33 # diff calculation x = [] for index, row in df.iterrows(): if row.mid_price == 0 or row.minLowPrice == 0 or row.maxHighPrice == 0: x.append(0) else: diff = (row.mid_price - row.minLowPrice)/(row.maxHighPrice - row.minLowPrice) - 0.5 diff = 2 * diff diff = diffRatio * diff + (1 - diffRatio) * x[-1] x.append(diff) y = [] for i in x: if i > 0.99: y.append(0.999) elif i < -0.99: y.append(-0.999) else: y.append(i) # Fish calculation z = [] for i in y: fish = np.log((1.0 + i)/(1.0 - i)) fish = 0.5 * fish + 0.5 * fish z.append(fish) df["fish"] = z j = z[-2:] return j def Squeeze_Momentum_Indicator(self, dataframe): count = 0 # Dataframe df = dataframe # parameter setup length = 20 mult = 2 length_KC = 20 mult_KC = 1.5 # calculate BB m_avg = df['close'].rolling(window=length).mean() m_std = df['close'].rolling(window=length).std(ddof=0) df['upper_BB'] = m_avg + mult * m_std df['lower_BB'] = m_avg - mult * m_std # calculate true range df['tr0'] = abs(df["high"] - df["low"]) df['tr1'] = abs(df["high"] - df["close"].shift()) df['tr2'] = abs(df["low"] - df["close"].shift()) df['tr'] = df[['tr0', 'tr1', 'tr2']].max(axis=1) # calculate KC range_ma = df['tr'].rolling(window=length_KC).mean() df['upper_KC'] = m_avg + range_ma * mult_KC df['lower_KC'] = m_avg - range_ma * mult_KC # calculate bar value highest = df['high'].rolling(window = length_KC).max() lowest = df['low'].rolling(window = length_KC).min() m1 = (highest + lowest)/2 df['value'] = (df['close'] - (m1 + m_avg)/2) fit_y = np.array(range(0,length_KC)) df['value'] = df['value'].rolling(window = length_KC).apply(lambda x: np.polyfit(fit_y, x, 1)[0] * (length_KC-1) + np.polyfit(fit_y, x, 1)[1], raw=True) # check for 'squeeze' df['squeeze_on'] = (df['lower_BB'] > df['lower_KC']) & (df['upper_BB'] < df['upper_KC']) df['squeeze_off'] = (df['lower_BB'] < df['lower_KC']) & (df['upper_BB'] > df['upper_KC']) # lists value_list = df["value"].to_list() squeeze_list = df["squeeze_on"].to_list() # Count if value_list[-1] > value_list[-2]: count += 1 if value_list[-2] > value_list[-3]: count += 1 if value_list[-3] > value_list[-4]: count += 1 if value_list[-1] > 0: if squeeze_list[-1] == True: count += 0.5 elif value_list[-1] < 0: if squeeze_list[-1] == True: count -= 0.5 return count
#region imports from AlgorithmImports import * #endregion import pandas as pd import numpy as np import math import datetime class JumpingBlueSalamander(QCAlgorithm): def Initialize(self): # # # # # # # # # # # # # # # # # # # # User input section # # # # # # # # # # # # # # # # # # # # # # # General backtest settings # Set Start Date self.SetStartDate(2021, 5, 10) # Set End Date # self.SetEndDate(2022, 1, 20) # Set Strategy Cash self.SetCash(100000) # # # Indicator periods # RSI period self.RSI_period = 14 # Simple moving average period self.SMA_period = 9 # Average true range period self.ATR_period = 9 # Fastest EMA period self.fastest_ema_period = 2 # Medium EMA period self.mid_ema_period = 5 # Slowest EMA period self.slow_ema_period = 8 # # # Fisher count parameters # # # # Current fish greater than SMA count += self.current_fish_greater_than_SMA_add_count = 1 # Current fish greater than previous fish value count += self.current_fish_greater_than_previous_fish_add_count = 1 # # # Squeeze momentum count parameters # # # # If current value greater than previous count += self.current_greater_than_previous_value = 1 # If previous value greater than one before count += self.previous_greater_than_one_before_value = 1 # If value -2 greater than value -3 count += self.value_two_greater_than_value_three = 1 # If value greater than 0 and squeeze on count += self.value_greater_than_zero_squeeze_on_count = 0.5 # If value less than 0 and squeeze on count -= self.value_less_than_zero_squeeze_on_count = 0.5 # # # Liquidation percentages # Trailing stop loss % self.trailing_stop_loss_percent = 0.1 # Hard stop loss % self.hard_stop_loss_percent = 5 # # # SMS phone number (Needs to include country code) self.SMS_phone_number = "+1" # # # # # # # # # # # # # # # # # # # # End user input section # # # # # # # # # # # # # # # # # # # # # Set brokerage model self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Margin) # Add Crypto btc = self.AddCrypto("BTCUSD", Resolution.Hour) # Set margin btc.BuyingPowerModel = SecurityMarginModel(3.3) # Symbol self.BTC_symbol = btc.Symbol # Create 4-hour consolidator four_hour = TradeBarConsolidator(timedelta(hours=4)) # Register "FourHourHandler" to receive 4-hour consolidated bars four_hour.DataConsolidated += self.FourHourHandler # Subscribe our 4-hour consolidator object to be automatically updated with 4-hour bars self.SubscriptionManager.AddConsolidator(self.BTC_symbol, four_hour) # RSI self.relative_strength = RelativeStrengthIndex(self.RSI_period) # Simple moving average self.simple_moving_average = SimpleMovingAverage(self.SMA_period) # ATR self.average_true_range = AverageTrueRange(self.ATR_period) # 2-period EMA self.ema_two = ExponentialMovingAverage(self.fastest_ema_period) # 5-period EMA self.ema_five = ExponentialMovingAverage(self.mid_ema_period) # 8-period EMA self.ema_eight = ExponentialMovingAverage(self.slow_ema_period) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.relative_strength, four_hour) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.simple_moving_average, four_hour) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.average_true_range, four_hour) # History history = self.History([self.BTC_symbol], 1000, Resolution.Hour) # Loc history history = history.loc[self.BTC_symbol] # Four-hour bar storage self.storage = {"open":[],"high":[],"low":[],"close":[],"volume":[]} # Check self.check = False # Loop through history for time, row in history.iterrows(): # Create tradebar with history data bar = TradeBar(time, self.BTC_symbol, row.open, row.high, row.low, row.close, row.volume) # Update 4-hour consolidator four_hour.Update(bar) # Check false self.check = True # Difference EMA self.EMA_difference = 0 # ATR at time order submitted self.ATR_value_when_order_submitted = None # Trailing stop loss tracker self.trailing_stop_loss_tracker = None # Trailing ATR tracker self.trailing_ATR_tracker = None # Just submitted order tracker self.just_submitted_order = False def OnData(self, data: Slice): pass def FourHourHandler(self, sender, bar): # Four-hour bar storage self.storage["open"].append(bar.Open) # Four-hour bar storage self.storage["high"].append(bar.High) # Four-hour bar storage self.storage["low"].append(bar.Low) # Four-hour bar storage self.storage["close"].append(bar.Close) # Four-hour bar storage self.storage["volume"].append(bar.Volume) # If more than 100 data points stored if len(self.storage["close"]) > 100: # Four-hour bar storage self.storage["open"] = self.storage["open"][-100:] # Four-hour bar storage self.storage["high"] = self.storage["high"][-100:] # Four-hour bar storage self.storage["low"] = self.storage["low"][-100:] # Four-hour bar storage self.storage["close"] = self.storage["close"][-100:] # Four-hour bar storage self.storage["volume"] = self.storage["volume"][-100:] # Count count = 0 # Convert storage into dataframe dataframe = pd.DataFrame(self.storage) # Fisher transform fish_value = self.Fisher_Transform_Indicator(dataframe) # Current fish value current_fish = fish_value[-1] # If current fish greater than SMA if current_fish > self.simple_moving_average.Current.Value: # Count count += self.current_fish_greater_than_SMA_add_count # If current fish greater than previous fish if current_fish > fish_value[-2]: # Count count += self.current_fish_greater_than_previous_fish_add_count # Squeeze momentum count += self.Squeeze_Momentum_Indicator(dataframe) # Update EMAs self.ema_two.Update(self.Time, count) self.ema_five.Update(self.Time, count) self.ema_eight.Update(self.Time, count) # If check is True if self.check == True: # Just submitted order self.just_submitted_order = False # If EMA difference is 0 if self.EMA_difference == 0: # Get ema difference self.EMA_difference = self.ema_five.Current.Value - self.ema_eight.Current.Value # Else else: # Get previous ema difference previous_ema_difference = self.EMA_difference # Get current ema difference current_ema_difference = self.ema_five.Current.Value - self.ema_eight.Current.Value # Update self.EMA_difference = current_ema_difference # If current ema difference is positive and previous is negative if current_ema_difference > 0 and previous_ema_difference < 0: # If short if self.Portfolio[self.BTC_symbol].Quantity < -0.1: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity) # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because fast EMA crossed above slow EMA") # If current close greater than 2-period ema if bar.Close > self.ema_two.Current.Value: # If RSI greater than 50 if self.relative_strength.Current.Value > 50: # Value of order value = self.Portfolio.TotalPortfolioValue / bar.Close # Long self.MarketOrder(self.BTC_symbol, value) # Update ATR self.ATR_value_when_order_submitted = self.average_true_range.Current.Value # Just submitted order self.just_submitted_order = True # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position opened because fast EMA crossed above slow EMA and current price greater than 2-period EMA") # If current ema difference is negative and previous is positive if current_ema_difference < 0 and previous_ema_difference > 0: # If long if self.Portfolio[self.BTC_symbol].Quantity > 0.1: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity) # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because fast EMA crossed below slow EMA") # # If current close greater than 2-period ema # if bar.Close < self.ema_two.Current.Value: # If RSI greater than 50 if self.relative_strength.Current.Value > 50: # Value of order value = self.Portfolio.TotalPortfolioValue / bar.Close # Short self.MarketOrder(self.BTC_symbol, -value) # Update ATR self.ATR_value_when_order_submitted = self.average_true_range.Current.Value # Just submitted order self.just_submitted_order = True # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position opened because fast EMA crossed below slow EMA and current price lower than 2-period EMA") # If long and not just submitted order if self.Portfolio[self.BTC_symbol].Quantity > 0.1 and self.just_submitted_order == False: # If long hard stop not triggered if not self.long_liquidation_hard_stop_logic(bar.Close): # If long ATR trailing stop not triggered if not self.long_liquidation_ATR_trailing_stop_logic(bar.Close): # Run trailing stop loss self.long_liquidation_trailing_stop_loss(bar.Close) # If short and not just submitted order if self.Portfolio[self.BTC_symbol].Quantity < -0.1 and self.just_submitted_order == False: # If short hard stop not triggered if not self.short_liquidation_hard_stop_logic(bar.Close): # If short ATR trailing stop not triggered if not self.short_liquidation_ATR_trailing_stop_logic(bar.Close): # Run trailing stop loss self.short_liquidation_trailing_stop_loss(bar.Close) def Fisher_Transform_Indicator(self, dataframe): # Dataframe df = dataframe window = 10 df["minLowPrice"] = df['low'].rolling(window = window).min() df["maxHighPrice"] = df['high'].rolling(window = window).max() df["mid_price"] = (df["low"] + df["high"])/2 df["minLowPrice"] = df["minLowPrice"].fillna(0) df["maxHighPrice"] = df["maxHighPrice"].fillna(0) diffRatio = 0.33 # diff calculation x = [] for index, row in df.iterrows(): if row.mid_price == 0 or row.minLowPrice == 0 or row.maxHighPrice == 0: x.append(0) else: diff = (row.mid_price - row.minLowPrice)/(row.maxHighPrice - row.minLowPrice) - 0.5 diff = 2 * diff diff = diffRatio * diff + (1 - diffRatio) * x[-1] x.append(diff) y = [] for i in x: if i > 0.99: y.append(0.999) elif i < -0.99: y.append(-0.999) else: y.append(i) # Fish calculation z = [] for i in y: fish = np.log((1.0 + i)/(1.0 - i)) fish = 0.5 * fish + 0.5 * fish z.append(fish) df["fish"] = z j = z[-2:] return j def Squeeze_Momentum_Indicator(self, dataframe): count = 0 # Dataframe df = dataframe # parameter setup length = 20 mult = 2 length_KC = 20 mult_KC = 1.5 # calculate BB m_avg = df['close'].rolling(window=length).mean() m_std = df['close'].rolling(window=length).std(ddof=0) df['upper_BB'] = m_avg + mult * m_std df['lower_BB'] = m_avg - mult * m_std # calculate true range df['tr0'] = abs(df["high"] - df["low"]) df['tr1'] = abs(df["high"] - df["close"].shift()) df['tr2'] = abs(df["low"] - df["close"].shift()) df['tr'] = df[['tr0', 'tr1', 'tr2']].max(axis=1) # calculate KC range_ma = df['tr'].rolling(window=length_KC).mean() df['upper_KC'] = m_avg + range_ma * mult_KC df['lower_KC'] = m_avg - range_ma * mult_KC # calculate bar value highest = df['high'].rolling(window = length_KC).max() lowest = df['low'].rolling(window = length_KC).min() m1 = (highest + lowest)/2 df['value'] = (df['close'] - (m1 + m_avg)/2) fit_y = np.array(range(0,length_KC)) df['value'] = df['value'].rolling(window = length_KC).apply(lambda x: np.polyfit(fit_y, x, 1)[0] * (length_KC-1) + np.polyfit(fit_y, x, 1)[1], raw=True) # check for 'squeeze' df['squeeze_on'] = (df['lower_BB'] > df['lower_KC']) & (df['upper_BB'] < df['upper_KC']) df['squeeze_off'] = (df['lower_BB'] < df['lower_KC']) & (df['upper_BB'] > df['upper_KC']) # lists value_list = df["value"].to_list() squeeze_list = df["squeeze_on"].to_list() # Count if value_list[-1] > value_list[-2]: count += self.current_greater_than_previous_value if value_list[-2] > value_list[-3]: count += self.previous_greater_than_one_before_value if value_list[-3] > value_list[-4]: count += self.value_two_greater_than_value_three if value_list[-1] > 0: if squeeze_list[-1] == True: count += self.value_greater_than_zero_squeeze_on_count elif value_list[-1] < 0: if squeeze_list[-1] == True: count -= self.value_less_than_zero_squeeze_on_count return count def long_liquidation_hard_stop_logic(self, close): # If current price less than stop loss price if (close / self.Portfolio[self.BTC_symbol].AveragePrice) < (1 - (self.hard_stop_loss_percent * 0.01)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity) # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because hard stop loss triggered") # Return return True # Else else: # Return return False def long_liquidation_ATR_trailing_stop_logic(self, close): # Check if trailing ATR tracker is None if self.trailing_ATR_tracker is None: # Update self.trailing_ATR_tracker = close - (3.5 * self.average_true_range.Current.Value) # Else else: # Check if current value greater than previous if (close - (3.5 * self.average_true_range.Current.Value)) > self.trailing_ATR_tracker: # Update self.trailing_ATR_tracker = close - (3.5 * self.average_true_range.Current.Value) # If close is lower than trailing ATR tracker if close < self.trailing_ATR_tracker: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity) # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because ATR trailing stop loss triggered") # Return return True # Else else: # Return return False def long_liquidation_trailing_stop_loss(self, close): # If trailing stop loss not yet initiated if self.trailing_stop_loss_tracker is None: # If current price greater than 1.5 * ATR at order open + average price if close > ((1.5 * self.ATR_value_when_order_submitted) + self.Portfolio[self.BTC_symbol].AveragePrice): # Initiate trailing stop loss self.trailing_stop_loss_tracker = close # Else else: # If current price greater than trailing stop loss if close > self.trailing_stop_loss_tracker: # Update self.trailing_stop_loss_tracker = close # Else else: # If current price lower than trailing stop loss tracker by trailing stop loss percent if (close / self.trailing_stop_loss_tracker) < (1 - (0.01 * self.trailing_stop_loss_percent)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity) # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because % trailing stop loss triggered") def short_liquidation_hard_stop_logic(self, close): # If current price greater than stop loss price if (close / self.Portfolio[self.BTC_symbol].AveragePrice) > (1 + (self.hard_stop_loss_percent * 0.01)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity) # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because hard stop loss triggered") # Return return True # Else else: # Return return False def short_liquidation_ATR_trailing_stop_logic(self, close): # Check if trailing ATR tracker is None if self.trailing_ATR_tracker is None: # Update self.trailing_ATR_tracker = close + (3.5 * self.average_true_range.Current.Value) # Else else: # Check if current value less than previous if (close + (3.5 * self.average_true_range.Current.Value)) < self.trailing_ATR_tracker: # Update self.trailing_ATR_tracker = close + (3.5 * self.average_true_range.Current.Value) # If close is greater than trailing ATR tracker if close > self.trailing_ATR_tracker: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity) # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because ATR trailing stop loss triggered") # Return return True # Else else: # Return return False def short_liquidation_trailing_stop_loss(self, close): # If trailing stop loss not yet initiated if self.trailing_stop_loss_tracker is None: # If current price less than average price - 1.5 * ATR at order open if close < (self.Portfolio[self.BTC_symbol].AveragePrice - (1.5 * self.ATR_value_when_order_submitted)): # Initiate trailing stop loss self.trailing_stop_loss_tracker = close # Else else: # If current price less than trailing stop loss if close < self.trailing_stop_loss_tracker: # Update self.trailing_stop_loss_tracker = close # Else else: # If current price greater than trailing stop loss tracker by trailing stop loss percent if (close / self.trailing_stop_loss_tracker) > (1 + (0.01 * self.trailing_stop_loss_percent)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity) # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because % trailing stop loss triggered")
#region imports from AlgorithmImports import * #endregion import pandas as pd import numpy as np import math import datetime class JumpingBlueSalamander(QCAlgorithm): def Initialize(self): # # # # # # # # # # # # # # # # # # # # User input section # # # # # # # # # # # # # # # # # # # # # # # General backtest settings # Set Start Date self.SetStartDate(2021, 5, 10) # Set End Date # self.SetEndDate(2022, 1, 20) # Set Strategy Cash self.SetCash(100000) # # # Indicator periods # RSI period self.RSI_period = int(self.GetParameter("RSI-period")) # Simple moving average period self.SMA_period = int(self.GetParameter("SMA-period")) # Average true range period self.ATR_period = int(self.GetParameter("ATR-period")) # Fastest EMA period self.fastest_ema_period = int(self.GetParameter("fastest-ema")) # Medium EMA period self.mid_ema_period = int(self.GetParameter("mid-ema")) # Slowest EMA period self.slow_ema_period = int(self.GetParameter("slow-ema")) # # # Fisher count parameters # # # # Current fish greater than SMA count += self.current_fish_greater_than_SMA_add_count = float(self.GetParameter("fisher-one")) # Current fish greater than previous fish value count += self.current_fish_greater_than_previous_fish_add_count = float(self.GetParameter("fisher-two")) # # # Squeeze momentum count parameters # # # # If current value greater than previous count += self.current_greater_than_previous_value = float(self.GetParameter("squeeze-one")) # If previous value greater than one before count += self.previous_greater_than_one_before_value = float(self.GetParameter("squeeze-two")) # If value -2 greater than value -3 count += self.value_two_greater_than_value_three = float(self.GetParameter("squeeze-three")) # If value greater than 0 and squeeze on count += self.value_greater_than_zero_squeeze_on_count = float(self.GetParameter("squeeze-four")) # If value less than 0 and squeeze on count -= self.value_less_than_zero_squeeze_on_count = float(self.GetParameter("squeeze-five")) # # # Liquidation percentages # Trailing stop loss % self.trailing_stop_loss_percent = float(self.GetParameter("trailing-stop-loss-percent")) # Hard stop loss % self.hard_stop_loss_percent = float(self.GetParameter("hard-stop-percent")) # # # SMS phone number (Needs to include country code) self.SMS_phone_number = "+1" # # # # # # # # # # # # # # # # # # # # End user input section # # # # # # # # # # # # # # # # # # # # # Set brokerage model self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Margin) # Add Crypto btc = self.AddCrypto("BTCUSD", Resolution.Hour) # Set margin btc.BuyingPowerModel = SecurityMarginModel(3.3) # Symbol self.BTC_symbol = btc.Symbol # Create 4-hour consolidator four_hour = TradeBarConsolidator(timedelta(hours=4)) # Register "FourHourHandler" to receive 4-hour consolidated bars four_hour.DataConsolidated += self.FourHourHandler # Subscribe our 4-hour consolidator object to be automatically updated with 4-hour bars self.SubscriptionManager.AddConsolidator(self.BTC_symbol, four_hour) # RSI self.relative_strength = RelativeStrengthIndex(self.RSI_period) # Simple moving average self.simple_moving_average = SimpleMovingAverage(self.SMA_period) # ATR self.average_true_range = AverageTrueRange(self.ATR_period) # 2-period EMA self.ema_two = ExponentialMovingAverage(self.fastest_ema_period) # 5-period EMA self.ema_five = ExponentialMovingAverage(self.mid_ema_period) # 8-period EMA self.ema_eight = ExponentialMovingAverage(self.slow_ema_period) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.relative_strength, four_hour) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.simple_moving_average, four_hour) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.average_true_range, four_hour) # History history = self.History([self.BTC_symbol], 1000, Resolution.Hour) # Loc history history = history.loc[self.BTC_symbol] # Four-hour bar storage self.storage = {"open":[],"high":[],"low":[],"close":[],"volume":[]} # Check self.check = False # Loop through history for time, row in history.iterrows(): # Create tradebar with history data bar = TradeBar(time, self.BTC_symbol, row.open, row.high, row.low, row.close, row.volume) # Update 4-hour consolidator four_hour.Update(bar) # Check false self.check = True # Difference EMA self.EMA_difference = 0 # ATR at time order submitted self.ATR_value_when_order_submitted = None # Trailing stop loss tracker self.trailing_stop_loss_tracker = None # Trailing ATR tracker self.trailing_ATR_tracker = None # Just submitted order tracker self.just_submitted_order = False def OnData(self, data: Slice): pass def FourHourHandler(self, sender, bar): # Four-hour bar storage self.storage["open"].append(bar.Open) # Four-hour bar storage self.storage["high"].append(bar.High) # Four-hour bar storage self.storage["low"].append(bar.Low) # Four-hour bar storage self.storage["close"].append(bar.Close) # Four-hour bar storage self.storage["volume"].append(bar.Volume) # If more than 100 data points stored if len(self.storage["close"]) > 100: # Four-hour bar storage self.storage["open"] = self.storage["open"][-100:] # Four-hour bar storage self.storage["high"] = self.storage["high"][-100:] # Four-hour bar storage self.storage["low"] = self.storage["low"][-100:] # Four-hour bar storage self.storage["close"] = self.storage["close"][-100:] # Four-hour bar storage self.storage["volume"] = self.storage["volume"][-100:] # Count count = 0 # Convert storage into dataframe dataframe = pd.DataFrame(self.storage) # Fisher transform fish_value = self.Fisher_Transform_Indicator(dataframe) # Current fish value current_fish = fish_value[-1] # If current fish greater than SMA if current_fish > self.simple_moving_average.Current.Value: # Count count += self.current_fish_greater_than_SMA_add_count # If current fish greater than previous fish if current_fish > fish_value[-2]: # Count count += self.current_fish_greater_than_previous_fish_add_count # Squeeze momentum count += self.Squeeze_Momentum_Indicator(dataframe) # Update EMAs self.ema_two.Update(self.Time, count) self.ema_five.Update(self.Time, count) self.ema_eight.Update(self.Time, count) # If check is True if self.check == True: # Just submitted order self.just_submitted_order = False # If EMA difference is 0 if self.EMA_difference == 0: # Get ema difference self.EMA_difference = self.ema_five.Current.Value - self.ema_eight.Current.Value # Else else: # Get previous ema difference previous_ema_difference = self.EMA_difference # Get current ema difference current_ema_difference = self.ema_five.Current.Value - self.ema_eight.Current.Value # Update self.EMA_difference = current_ema_difference # If current ema difference is positive and previous is negative if current_ema_difference > 0 and previous_ema_difference < 0: # If short if self.Portfolio[self.BTC_symbol].Quantity < -0.1: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Short position EMA liquidated") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because fast EMA crossed above slow EMA") # If current close greater than 2-period ema if bar.Close > self.ema_two.Current.Value: # If RSI greater than 50 if self.relative_strength.Current.Value > 50: # Value of order value = self.Portfolio.TotalPortfolioValue / bar.Close # Long self.MarketOrder(self.BTC_symbol, value, tag = "Long position EMA opened") # Update ATR self.ATR_value_when_order_submitted = self.average_true_range.Current.Value # Just submitted order self.just_submitted_order = True # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position opened because fast EMA crossed above slow EMA and current price greater than 2-period EMA") # If current ema difference is negative and previous is positive if current_ema_difference < 0 and previous_ema_difference > 0: # If long if self.Portfolio[self.BTC_symbol].Quantity > 0.1: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Long position EMA liquidated") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because fast EMA crossed below slow EMA") # # If current close greater than 2-period ema # if bar.Close < self.ema_two.Current.Value: # If RSI greater than 50 if self.relative_strength.Current.Value > 50: # Value of order value = self.Portfolio.TotalPortfolioValue / bar.Close # Short self.MarketOrder(self.BTC_symbol, -value, tag = "Short position EMA opened") # Update ATR self.ATR_value_when_order_submitted = self.average_true_range.Current.Value # Just submitted order self.just_submitted_order = True # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position opened because fast EMA crossed below slow EMA and current price lower than 2-period EMA") # If long and not just submitted order if self.Portfolio[self.BTC_symbol].Quantity > 0.1 and self.just_submitted_order == False: # If long hard stop not triggered if not self.long_liquidation_hard_stop_logic(bar.Close): # If long ATR trailing stop not triggered if not self.long_liquidation_ATR_trailing_stop_logic(bar.Close): # Run trailing stop loss self.long_liquidation_trailing_stop_loss(bar.Close) # If short and not just submitted order if self.Portfolio[self.BTC_symbol].Quantity < -0.1 and self.just_submitted_order == False: # If short hard stop not triggered if not self.short_liquidation_hard_stop_logic(bar.Close): # If short ATR trailing stop not triggered if not self.short_liquidation_ATR_trailing_stop_logic(bar.Close): # Run trailing stop loss self.short_liquidation_trailing_stop_loss(bar.Close) def Fisher_Transform_Indicator(self, dataframe): # Dataframe df = dataframe window = 10 df["minLowPrice"] = df['low'].rolling(window = window).min() df["maxHighPrice"] = df['high'].rolling(window = window).max() df["mid_price"] = (df["low"] + df["high"])/2 df["minLowPrice"] = df["minLowPrice"].fillna(0) df["maxHighPrice"] = df["maxHighPrice"].fillna(0) diffRatio = 0.33 # diff calculation x = [] for index, row in df.iterrows(): if row.mid_price == 0 or row.minLowPrice == 0 or row.maxHighPrice == 0: x.append(0) else: diff = (row.mid_price - row.minLowPrice)/(row.maxHighPrice - row.minLowPrice) - 0.5 diff = 2 * diff diff = diffRatio * diff + (1 - diffRatio) * x[-1] x.append(diff) y = [] for i in x: if i > 0.99: y.append(0.999) elif i < -0.99: y.append(-0.999) else: y.append(i) # Fish calculation z = [] for i in y: fish = np.log((1.0 + i)/(1.0 - i)) fish = 0.5 * fish + 0.5 * fish z.append(fish) df["fish"] = z j = z[-2:] return j def Squeeze_Momentum_Indicator(self, dataframe): count = 0 # Dataframe df = dataframe # parameter setup length = 20 mult = 2 length_KC = 20 mult_KC = 1.5 # calculate BB m_avg = df['close'].rolling(window=length).mean() m_std = df['close'].rolling(window=length).std(ddof=0) df['upper_BB'] = m_avg + mult * m_std df['lower_BB'] = m_avg - mult * m_std # calculate true range df['tr0'] = abs(df["high"] - df["low"]) df['tr1'] = abs(df["high"] - df["close"].shift()) df['tr2'] = abs(df["low"] - df["close"].shift()) df['tr'] = df[['tr0', 'tr1', 'tr2']].max(axis=1) # calculate KC range_ma = df['tr'].rolling(window=length_KC).mean() df['upper_KC'] = m_avg + range_ma * mult_KC df['lower_KC'] = m_avg - range_ma * mult_KC # calculate bar value highest = df['high'].rolling(window = length_KC).max() lowest = df['low'].rolling(window = length_KC).min() m1 = (highest + lowest)/2 df['value'] = (df['close'] - (m1 + m_avg)/2) fit_y = np.array(range(0,length_KC)) df['value'] = df['value'].rolling(window = length_KC).apply(lambda x: np.polyfit(fit_y, x, 1)[0] * (length_KC-1) + np.polyfit(fit_y, x, 1)[1], raw=True) # check for 'squeeze' df['squeeze_on'] = (df['lower_BB'] > df['lower_KC']) & (df['upper_BB'] < df['upper_KC']) df['squeeze_off'] = (df['lower_BB'] < df['lower_KC']) & (df['upper_BB'] > df['upper_KC']) # lists value_list = df["value"].to_list() squeeze_list = df["squeeze_on"].to_list() # Count if value_list[-1] > value_list[-2]: count += self.current_greater_than_previous_value if value_list[-2] > value_list[-3]: count += self.previous_greater_than_one_before_value if value_list[-3] > value_list[-4]: count += self.value_two_greater_than_value_three if value_list[-1] > 0: if squeeze_list[-1] == True: count += self.value_greater_than_zero_squeeze_on_count elif value_list[-1] < 0: if squeeze_list[-1] == True: count -= self.value_less_than_zero_squeeze_on_count return count def long_liquidation_hard_stop_logic(self, close): # If current price less than stop loss price if (close / self.Portfolio[self.BTC_symbol].AveragePrice) < (1 - (self.hard_stop_loss_percent * 0.01)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Long hard stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because hard stop loss triggered") # Return return True # Else else: # Return return False def long_liquidation_ATR_trailing_stop_logic(self, close): # Check if trailing ATR tracker is None if self.trailing_ATR_tracker is None: # Update self.trailing_ATR_tracker = close - (3.5 * self.average_true_range.Current.Value) # Else else: # Check if current value greater than previous if (close - (3.5 * self.average_true_range.Current.Value)) > self.trailing_ATR_tracker: # Update self.trailing_ATR_tracker = close - (3.5 * self.average_true_range.Current.Value) # If close is lower than trailing ATR tracker if close < self.trailing_ATR_tracker: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Long ATR trailing stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because ATR trailing stop loss triggered") # Return return True # Else else: # Return return False def long_liquidation_trailing_stop_loss(self, close): # If trailing stop loss not yet initiated if self.trailing_stop_loss_tracker is None: # If current price greater than 1.5 * ATR at order open + average price if close > ((1.5 * self.ATR_value_when_order_submitted) + self.Portfolio[self.BTC_symbol].AveragePrice): # Initiate trailing stop loss self.trailing_stop_loss_tracker = close # Else else: # If current price greater than trailing stop loss if close > self.trailing_stop_loss_tracker: # Update self.trailing_stop_loss_tracker = close # Else else: # If current price lower than trailing stop loss tracker by trailing stop loss percent if (close / self.trailing_stop_loss_tracker) < (1 - (0.01 * self.trailing_stop_loss_percent)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Long trailing stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because % trailing stop loss triggered") def short_liquidation_hard_stop_logic(self, close): # If current price greater than stop loss price if (close / self.Portfolio[self.BTC_symbol].AveragePrice) > (1 + (self.hard_stop_loss_percent * 0.01)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Short hard stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because hard stop loss triggered") # Return return True # Else else: # Return return False def short_liquidation_ATR_trailing_stop_logic(self, close): # Check if trailing ATR tracker is None if self.trailing_ATR_tracker is None: # Update self.trailing_ATR_tracker = close + (3.5 * self.average_true_range.Current.Value) # Else else: # Check if current value less than previous if (close + (3.5 * self.average_true_range.Current.Value)) < self.trailing_ATR_tracker: # Update self.trailing_ATR_tracker = close + (3.5 * self.average_true_range.Current.Value) # If close is greater than trailing ATR tracker if close > self.trailing_ATR_tracker: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Short ATR trailing stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because ATR trailing stop loss triggered") # Return return True # Else else: # Return return False def short_liquidation_trailing_stop_loss(self, close): # If trailing stop loss not yet initiated if self.trailing_stop_loss_tracker is None: # If current price less than average price - 1.5 * ATR at order open if close < (self.Portfolio[self.BTC_symbol].AveragePrice - (1.5 * self.ATR_value_when_order_submitted)): # Initiate trailing stop loss self.trailing_stop_loss_tracker = close # Else else: # If current price less than trailing stop loss if close < self.trailing_stop_loss_tracker: # Update self.trailing_stop_loss_tracker = close # Else else: # If current price greater than trailing stop loss tracker by trailing stop loss percent if (close / self.trailing_stop_loss_tracker) > (1 + (0.01 * self.trailing_stop_loss_percent)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Short trailing stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because % trailing stop loss triggered")
#region imports from AlgorithmImports import * #endregion import pandas as pd import numpy as np import math import datetime class JumpingBlueSalamander(QCAlgorithm): def Initialize(self): # # # # # # # # # # # # # # # # # # # # User input section # # # # # # # # # # # # # # # # # # # # # # # General backtest settings # Set Start Date self.SetStartDate(2021, 5, 10) # Set End Date self.SetEndDate(2022, 1, 20) # Set Strategy Cash self.SetCash(100000) # # # Indicator periods # RSI period self.RSI_period = int(self.GetParameter("RSI-period")) # Simple moving average period self.SMA_period = int(self.GetParameter("SMA-period")) # Average true range period self.ATR_period = int(self.GetParameter("ATR-period")) # Fastest EMA period self.fastest_ema_period = int(self.GetParameter("fastest-ema")) # Medium EMA period self.mid_ema_period = int(self.GetParameter("mid-ema")) # Slowest EMA period self.slow_ema_period = int(self.GetParameter("slow-ema")) # # # Fisher count parameters # # # # Current fish greater than SMA count += self.current_fish_greater_than_SMA_add_count = float(self.GetParameter("fisher-one")) # Current fish greater than previous fish value count += self.current_fish_greater_than_previous_fish_add_count = float(self.GetParameter("fisher-two")) # # # Squeeze momentum count parameters # # # # If current value greater than previous count += self.current_greater_than_previous_value = float(self.GetParameter("squeeze-one")) # If previous value greater than one before count += self.previous_greater_than_one_before_value = float(self.GetParameter("squeeze-two")) # If value -2 greater than value -3 count += self.value_two_greater_than_value_three = float(self.GetParameter("squeeze-three")) # If value greater than 0 and squeeze on count += self.value_greater_than_zero_squeeze_on_count = float(self.GetParameter("squeeze-four")) # If value less than 0 and squeeze on count -= self.value_less_than_zero_squeeze_on_count = float(self.GetParameter("squeeze-five")) # # # Liquidation percentages # Trailing stop loss % self.trailing_stop_loss_percent = float(self.GetParameter("trailing-stop-loss-percent")) # Hard stop loss % self.hard_stop_loss_percent = float(self.GetParameter("hard-stop-percent")) # # # SMS phone number (Needs to include country code) self.SMS_phone_number = "+1" # # # # # # # # # # # # # # # # # # # # End user input section # # # # # # # # # # # # # # # # # # # # # Set brokerage model self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Margin) # Add Crypto btc = self.AddCrypto("ETHUSD", Resolution.Hour) # Set margin btc.BuyingPowerModel = SecurityMarginModel(3.3) # Symbol self.BTC_symbol = btc.Symbol # Create 4-hour consolidator four_hour = TradeBarConsolidator(timedelta(hours=4)) # Register "FourHourHandler" to receive 4-hour consolidated bars four_hour.DataConsolidated += self.FourHourHandler # Subscribe our 4-hour consolidator object to be automatically updated with 4-hour bars self.SubscriptionManager.AddConsolidator(self.BTC_symbol, four_hour) # RSI self.relative_strength = RelativeStrengthIndex(self.RSI_period) # Simple moving average self.simple_moving_average = SimpleMovingAverage(self.SMA_period) # ATR self.average_true_range = AverageTrueRange(self.ATR_period) # 2-period EMA self.ema_two = ExponentialMovingAverage(self.fastest_ema_period) # 5-period EMA self.ema_five = ExponentialMovingAverage(self.mid_ema_period) # 8-period EMA self.ema_eight = ExponentialMovingAverage(self.slow_ema_period) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.relative_strength, four_hour) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.simple_moving_average, four_hour) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.average_true_range, four_hour) # Four-hour bar storage self.storage = {"open":[],"high":[],"low":[],"close":[],"volume":[]} # Difference EMA self.EMA_difference = 0 # ATR at time order submitted self.ATR_value_when_order_submitted = None # Trailing stop loss tracker self.trailing_stop_loss_tracker = None # Trailing ATR tracker self.trailing_ATR_tracker = None # Just submitted order tracker self.just_submitted_order = False # # # # # Additions 8/21/2022 # # # # # # Moving average self.moving_average_rsi = SimpleMovingAverage(10) # Create 2-day consolidator two_day = TradeBarConsolidator(timedelta(days=2)) # Register "TwoDayHandler" to receive 2-day consolidated bars two_day.DataConsolidated += self.TwoDayHandler # Subscribe our 2-day consolidator object to be automatically updated with 2-day bars self.SubscriptionManager.AddConsolidator(self.BTC_symbol, two_day) # ADX self.ADX_indicator_daily = AverageDirectionalIndex(14) # RSI self.RSI_indicator_daily = RelativeStrengthIndex(14) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.ADX_indicator_daily, two_day) # Register indicator self.RegisterIndicator(self.BTC_symbol, self.RSI_indicator_daily, two_day) # RSI daily storage self.RSI_daily_storage = [] # Market judgement self.market_judgement = "None" # Warmup self.SetWarmUp(timedelta(days = 150)) def OnData(self, data: Slice): pass def TwoDayHandler(self, sender, bar): # If RSI daily is ready if self.RSI_indicator_daily.IsReady: # Update RSI daily storage self.RSI_daily_storage.append(self.RSI_indicator_daily.Current.Value) # If stored more than 14 if len(self.RSI_daily_storage) > 14: # Cut self.RSI_daily_storage = self.RSI_daily_storage[-14:] # If ADX is ready if self.ADX_indicator_daily.IsReady: # Get minimum of RSI daliy storage minimum_RSI = min(self.RSI_daily_storage) # Get maximum of RSI daily storage maximum_RSI = max(self.RSI_daily_storage) # Bull if ( # If ADX above 30 self.ADX_indicator_daily.Current.Value > 30 and # If DI+ greater than DI- self.ADX_indicator_daily.PositiveDirectionalIndex.Current.Value > self.ADX_indicator_daily.NegativeDirectionalIndex.Current.Value and # If minimum RSI below 40 minimum_RSI < 40 and # If Current RSI above 40 self.RSI_indicator_daily.Current.Value > 40 ): # Judgement self.market_judgement = "bull" # Bear elif ( # If ADX above 30 self.ADX_indicator_daily.Current.Value > 30 and # If DI+ less than DI- self.ADX_indicator_daily.PositiveDirectionalIndex.Current.Value < self.ADX_indicator_daily.NegativeDirectionalIndex.Current.Value and # If maximum RSI above 60 maximum_RSI > 60 and # If Current RSI below 60 self.RSI_indicator_daily.Current.Value < 60 ): # Judgement self.market_judgement = "bear" # Neutral bull elif ( # If ADX below 30 self.ADX_indicator_daily.Current.Value < 30 and # If DI+ greater than DI- self.ADX_indicator_daily.PositiveDirectionalIndex.Current.Value > self.ADX_indicator_daily.NegativeDirectionalIndex.Current.Value and # If minimum RSI below 30 minimum_RSI < 30 and # If Current RSI above 30 self.RSI_indicator_daily.Current.Value > 30 ): # Judgement self.market_judgement = "neutral bull" # Neutral bear elif ( # If ADX below 30 self.ADX_indicator_daily.Current.Value < 30 and # If DI+ less than DI- self.ADX_indicator_daily.PositiveDirectionalIndex.Current.Value < self.ADX_indicator_daily.NegativeDirectionalIndex.Current.Value and # If maximum RSI above 70 maximum_RSI > 70 and # If Current RSI below 70 self.RSI_indicator_daily.Current.Value < 70 ): # Judgement self.market_judgement = "neutral bear" # Else else: # Judgement is None self.market_judgement = "None" def FourHourHandler(self, sender, bar): # Four-hour bar storage self.storage["open"].append(bar.Open) # Four-hour bar storage self.storage["high"].append(bar.High) # Four-hour bar storage self.storage["low"].append(bar.Low) # Four-hour bar storage self.storage["close"].append(bar.Close) # Four-hour bar storage self.storage["volume"].append(bar.Volume) # If more than 100 data points stored if len(self.storage["close"]) > 100: # Four-hour bar storage self.storage["open"] = self.storage["open"][-100:] # Four-hour bar storage self.storage["high"] = self.storage["high"][-100:] # Four-hour bar storage self.storage["low"] = self.storage["low"][-100:] # Four-hour bar storage self.storage["close"] = self.storage["close"][-100:] # Four-hour bar storage self.storage["volume"] = self.storage["volume"][-100:] # Count count = 0 # Convert storage into dataframe dataframe = pd.DataFrame(self.storage) # Fisher transform fish_value = self.Fisher_Transform_Indicator(dataframe) # Current fish value current_fish = fish_value[-1] # If current fish greater than SMA if current_fish > self.simple_moving_average.Current.Value: # Count count += self.current_fish_greater_than_SMA_add_count # If current fish greater than previous fish if current_fish > fish_value[-2]: # Count count += self.current_fish_greater_than_previous_fish_add_count # Squeeze momentum count += self.Squeeze_Momentum_Indicator(dataframe) # Update EMAs self.ema_two.Update(self.Time, count) self.ema_five.Update(self.Time, count) self.ema_eight.Update(self.Time, count) # If RSI is ready if self.relative_strength.IsReady: # Update moving average self.moving_average_rsi.Update(self.Time, self.relative_strength.Current.Value) # If not warming up if not self.IsWarmingUp: # Just submitted order self.just_submitted_order = False # If EMA difference is 0 if self.EMA_difference == 0: # Get ema difference self.EMA_difference = self.ema_five.Current.Value - self.ema_eight.Current.Value # Else else: # Get previous ema difference previous_ema_difference = self.EMA_difference # Get current ema difference current_ema_difference = self.ema_five.Current.Value - self.ema_eight.Current.Value # Update self.EMA_difference = current_ema_difference # If current ema difference is positive and previous is negative if current_ema_difference > 0 and previous_ema_difference < 0: # If short if self.Portfolio[self.BTC_symbol].Quantity < -0.1: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Short position EMA liquidated") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because fast EMA crossed above slow EMA") # If current close greater than 2-period ema if bar.Close > self.ema_two.Current.Value: # If RSI greater than 50 if self.relative_strength.Current.Value > 50: # If RSI greater than 10-period MA of RSI if self.relative_strength.Current.Value > self.moving_average_rsi.Current.Value: # If market judgement is bull if self.market_judgement == "bull": # Value of order value = self.Portfolio.TotalPortfolioValue / bar.Close # Long self.MarketOrder(self.BTC_symbol, value, tag = "Bull position opened") # Update ATR self.ATR_value_when_order_submitted = self.average_true_range.Current.Value # Just submitted order self.just_submitted_order = True # Send SMS self.Notify.Sms(self.SMS_phone_number, "Bull position opened") # If market judgement is neutral bull elif self.market_judgement == "neutral bull": # Value of order value = (self.Portfolio.TotalPortfolioValue * 0.5) / bar.Close # Long self.MarketOrder(self.BTC_symbol, value, tag = "Neutral bull position opened") # Update ATR self.ATR_value_when_order_submitted = self.average_true_range.Current.Value # Just submitted order self.just_submitted_order = True # Send SMS self.Notify.Sms(self.SMS_phone_number, "Neutral bull position opened") # If current ema difference is negative and previous is positive elif current_ema_difference < 0 and previous_ema_difference > 0: # If long if self.Portfolio[self.BTC_symbol].Quantity > 0.1: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Long position EMA liquidated") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because fast EMA crossed below slow EMA") # If RSI below than 50 if self.relative_strength.Current.Value < 50: # If RSI below 10-period MA of RSI if self.relative_strength.Current.Value < self.moving_average_rsi.Current.Value: # If market judgement is bear if self.market_judgement == "bear": # Value of order value = self.Portfolio.TotalPortfolioValue / bar.Close # Short self.MarketOrder(self.BTC_symbol, -value, tag = "Bear position opened") # Update ATR self.ATR_value_when_order_submitted = self.average_true_range.Current.Value # Just submitted order self.just_submitted_order = True # Send SMS self.Notify.Sms(self.SMS_phone_number, "Bear position opened") # If market judgement is neutral bear elif self.market_judgement == "neutral bear": # Value of order value = (self.Portfolio.TotalPortfolioValue * 0.5) / bar.Close # Short self.MarketOrder(self.BTC_symbol, -value, tag = "Neutral bear position opened") # Update ATR self.ATR_value_when_order_submitted = self.average_true_range.Current.Value # Just submitted order self.just_submitted_order = True # Send SMS self.Notify.Sms(self.SMS_phone_number, "Neutral bear position opened") # If long and not just submitted order if self.Portfolio[self.BTC_symbol].Quantity > 0.1 and self.just_submitted_order == False and self.ATR_value_when_order_submitted is not None: # If long hard stop not triggered if not self.long_liquidation_hard_stop_logic(bar.Close): # If long ATR trailing stop not triggered if not self.long_liquidation_ATR_trailing_stop_logic(bar.Close): # Run trailing stop loss self.long_liquidation_trailing_stop_loss(bar.Close) # If short and not just submitted order if self.Portfolio[self.BTC_symbol].Quantity < -0.1 and self.just_submitted_order == False and self.ATR_value_when_order_submitted is not None: # If short hard stop not triggered if not self.short_liquidation_hard_stop_logic(bar.Close): # If short ATR trailing stop not triggered if not self.short_liquidation_ATR_trailing_stop_logic(bar.Close): # Run trailing stop loss self.short_liquidation_trailing_stop_loss(bar.Close) def Fisher_Transform_Indicator(self, dataframe): # Dataframe df = dataframe window = 10 df["minLowPrice"] = df['low'].rolling(window = window).min() df["maxHighPrice"] = df['high'].rolling(window = window).max() df["mid_price"] = (df["low"] + df["high"])/2 df["minLowPrice"] = df["minLowPrice"].fillna(0) df["maxHighPrice"] = df["maxHighPrice"].fillna(0) diffRatio = 0.33 # diff calculation x = [] for index, row in df.iterrows(): if row.mid_price == 0 or row.minLowPrice == 0 or row.maxHighPrice == 0: x.append(0) else: diff = (row.mid_price - row.minLowPrice)/(row.maxHighPrice - row.minLowPrice) - 0.5 diff = 2 * diff diff = diffRatio * diff + (1 - diffRatio) * x[-1] x.append(diff) y = [] for i in x: if i > 0.99: y.append(0.999) elif i < -0.99: y.append(-0.999) else: y.append(i) # Fish calculation z = [] for i in y: fish = np.log((1.0 + i)/(1.0 - i)) fish = 0.5 * fish + 0.5 * fish z.append(fish) df["fish"] = z j = z[-2:] return j def Squeeze_Momentum_Indicator(self, dataframe): count = 0 # Dataframe df = dataframe # parameter setup length = 20 mult = 2 length_KC = 20 mult_KC = 1.5 # calculate BB m_avg = df['close'].rolling(window=length).mean() m_std = df['close'].rolling(window=length).std(ddof=0) df['upper_BB'] = m_avg + mult * m_std df['lower_BB'] = m_avg - mult * m_std # calculate true range df['tr0'] = abs(df["high"] - df["low"]) df['tr1'] = abs(df["high"] - df["close"].shift()) df['tr2'] = abs(df["low"] - df["close"].shift()) df['tr'] = df[['tr0', 'tr1', 'tr2']].max(axis=1) # calculate KC range_ma = df['tr'].rolling(window=length_KC).mean() df['upper_KC'] = m_avg + range_ma * mult_KC df['lower_KC'] = m_avg - range_ma * mult_KC # calculate bar value highest = df['high'].rolling(window = length_KC).max() lowest = df['low'].rolling(window = length_KC).min() m1 = (highest + lowest)/2 df['value'] = (df['close'] - (m1 + m_avg)/2) fit_y = np.array(range(0,length_KC)) df['value'] = df['value'].rolling(window = length_KC).apply(lambda x: np.polyfit(fit_y, x, 1)[0] * (length_KC-1) + np.polyfit(fit_y, x, 1)[1], raw=True) # check for 'squeeze' df['squeeze_on'] = (df['lower_BB'] > df['lower_KC']) & (df['upper_BB'] < df['upper_KC']) df['squeeze_off'] = (df['lower_BB'] < df['lower_KC']) & (df['upper_BB'] > df['upper_KC']) # lists value_list = df["value"].to_list() squeeze_list = df["squeeze_on"].to_list() # Count if value_list[-1] > value_list[-2]: count += self.current_greater_than_previous_value if value_list[-2] > value_list[-3]: count += self.previous_greater_than_one_before_value if value_list[-3] > value_list[-4]: count += self.value_two_greater_than_value_three if value_list[-1] > 0: if squeeze_list[-1] == True: count += self.value_greater_than_zero_squeeze_on_count elif value_list[-1] < 0: if squeeze_list[-1] == True: count -= self.value_less_than_zero_squeeze_on_count return count def long_liquidation_hard_stop_logic(self, close): # If current price less than stop loss price if (close / self.Portfolio[self.BTC_symbol].AveragePrice) < (1 - (self.hard_stop_loss_percent * 0.01)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Long hard stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because hard stop loss triggered") # Return return True # Else else: # Return return False def long_liquidation_ATR_trailing_stop_logic(self, close): # Check if trailing ATR tracker is None if self.trailing_ATR_tracker is None: # Update self.trailing_ATR_tracker = close - (3.5 * self.average_true_range.Current.Value) # Else else: # Check if current value greater than previous if (close - (3.5 * self.average_true_range.Current.Value)) > self.trailing_ATR_tracker: # Update self.trailing_ATR_tracker = close - (3.5 * self.average_true_range.Current.Value) # If close is lower than trailing ATR tracker if close < self.trailing_ATR_tracker: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Long ATR trailing stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because ATR trailing stop loss triggered") # Return return True # Else else: # Return return False def long_liquidation_trailing_stop_loss(self, close): # If trailing stop loss not yet initiated if self.trailing_stop_loss_tracker is None: # If current price greater than 1.5 * ATR at order open + average price if close > ((1.5 * self.ATR_value_when_order_submitted) + self.Portfolio[self.BTC_symbol].AveragePrice): # Initiate trailing stop loss self.trailing_stop_loss_tracker = close # Else else: # If current price greater than trailing stop loss if close > self.trailing_stop_loss_tracker: # Update self.trailing_stop_loss_tracker = close # Else else: # If current price lower than trailing stop loss tracker by trailing stop loss percent if (close / self.trailing_stop_loss_tracker) < (1 - (0.01 * self.trailing_stop_loss_percent)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Long trailing stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Long position liquidated because % trailing stop loss triggered") def short_liquidation_hard_stop_logic(self, close): # If current price greater than stop loss price if (close / self.Portfolio[self.BTC_symbol].AveragePrice) > (1 + (self.hard_stop_loss_percent * 0.01)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Short hard stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because hard stop loss triggered") # Return return True # Else else: # Return return False def short_liquidation_ATR_trailing_stop_logic(self, close): # Check if trailing ATR tracker is None if self.trailing_ATR_tracker is None: # Update self.trailing_ATR_tracker = close + (3.5 * self.average_true_range.Current.Value) # Else else: # Check if current value less than previous if (close + (3.5 * self.average_true_range.Current.Value)) < self.trailing_ATR_tracker: # Update self.trailing_ATR_tracker = close + (3.5 * self.average_true_range.Current.Value) # If close is greater than trailing ATR tracker if close > self.trailing_ATR_tracker: # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Short ATR trailing stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because ATR trailing stop loss triggered") # Return return True # Else else: # Return return False def short_liquidation_trailing_stop_loss(self, close): # If trailing stop loss not yet initiated if self.trailing_stop_loss_tracker is None: # If current price less than average price - 1.5 * ATR at order open if close < (self.Portfolio[self.BTC_symbol].AveragePrice - (1.5 * self.ATR_value_when_order_submitted)): # Initiate trailing stop loss self.trailing_stop_loss_tracker = close # Else else: # If current price less than trailing stop loss if close < self.trailing_stop_loss_tracker: # Update self.trailing_stop_loss_tracker = close # Else else: # If current price greater than trailing stop loss tracker by trailing stop loss percent if (close / self.trailing_stop_loss_tracker) > (1 + (0.01 * self.trailing_stop_loss_percent)): # Liquidate self.MarketOrder(self.BTC_symbol, -self.Portfolio[self.BTC_symbol].Quantity, tag = "Short trailing stop triggered") # Reset ATR self.ATR_value_when_order_submitted = None # Reset trailing stop loss self.trailing_stop_loss_tracker = None # Reset trailng ATR tracker self.trailing_ATR_tracker = None # Send SMS self.Notify.Sms(self.SMS_phone_number, "Short position liquidated because % trailing stop loss triggered")