Overall Statistics |
Total Orders 8 Average Win 0.74% Average Loss 0% Compounding Annual Return 3.013% Drawdown 1.100% Expectancy 0 Start Equity 1029000 End Equity 1029920.89 Net Profit 0.089% Sharpe Ratio -6.445 Sortino Ratio -5.744 Probabilistic Sharpe Ratio 2.549% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha -0.117 Beta 0.267 Annual Standard Deviation 0.044 Annual Variance 0.002 Information Ratio 3.152 Tracking Error 0.105 Treynor Ratio -1.052 Total Fees $12.42 Estimated Strategy Capacity $57000000.00 Lowest Capacity Asset GPCR 2T Portfolio Turnover 72.07% |
# region imports from AlgorithmImports import * # endregion class CreativeApricotShark(QCAlgorithm): def initialize(self): self.SetStartDate(2024, 8, 29) self.SetEndDate(2024, 12, 30) self.SetCash(1029000) ####################################################### # # # FOREX # # # ####################################################### #Forex initialize **** self.pair = "USDJPY" self.forex_pair = self.AddForex(self.pair, Resolution.Minute) # Create a consolidator for 30-minute QuoteBars thirtyMinuteConsolidator = QuoteBarConsolidator(timedelta(minutes=30)) self.SubscriptionManager.AddConsolidator(self.forex_pair.Symbol, thirtyMinuteConsolidator) thirtyMinuteConsolidator.DataConsolidated += self.OnThirtyMinuteForexBar # Define moving averages self.short_window = 50 self.long_window = 200 self.short_sma = SimpleMovingAverage(self.forex_pair.Symbol, self.short_window) self.long_sma = SimpleMovingAverage(self.forex_pair.Symbol, self.long_window) self.previous = None #self.SetWarmUp(200) self.trade_enter = None self.stop_loss_price = 0.00 self.stop_loss = 0.005 self.trailing_stop_price = 0.00 #price self.trailing_stop = 0.003 #pct of price self.pos_type = None #End Forex Initialize ***** ####################################################### # # # CANDLE # # STICK STRAT # # # ####################################################### #self.AddEquity("SPY", Resolution.Daily) #self.tickers = ['AAPL','MSFT','GOOGL','AMZN', 'META','TSLA', 'SPY']#: 287% # winners #self.tickers = ['XOM','JNJ','KO','MCD', 'MDT','SHW', 'CTAS'] #182.98 % # high dividend self.tickers = ['FIVE','GPCR','STRL','NVMI', 'ONTO','ASML', 'VKTX'] # losers: 1,050.50 % self.candles = {} self.percent = 0.25 self.open_positions = [] self.latest_data = None # Positions to keep track of the short positions we enter self.open_short_positions = [] self.transactions_history = pd.DataFrame(columns=['Date', 'Stock', 'Type of Transaction', 'Candle', 'Buy Price', 'Qty', 'Sell Price', 'Buy Price', 'P/L']) # determines the investment percentage of the totalCash self.percent = 0.05 # We are also going to have a stop-loss metric associated with each position # to reduce the drawdown self.stop_loss_threshold = -0.2 # We will see if trailing stop loss works better self.trailing_stop_loss_percent = 0.2 self.SetWarmUp(3*1440, Resolution.Minute) # # Initialize moving averages for ticker in self.tickers: equity = self.AddEquity(ticker, Resolution.Daily) self.candles[ticker] = Candle(self, ticker) self.UniverseSettings.Leverage = 2 # Schedule the candlestick strategy to run once per day at 15:59 self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(16,1), self.handle_candlestick_strategy) def OnData(self, data): if self.IsWarmingUp: return self.latest_data = data # Handle Forex strategy (minutely data) if self.forex_pair.Symbol in data: price = data[self.forex_pair.Symbol].Close self.handle_forex_strategy(price) def handle_forex_strategy(self, price): # Handle Forex strategy logic using minutely data if self.pos_type == "LONG": if price < self.trailing_stop_price or price < self.stop_loss_price: self.Liquidate(self.forex_pair.Symbol) self.Log(f"******Closing trade stopped_out MINUTE: {self.pos_type}, trade_price: {self.trade_enter}, Price: {price}") self.clear_vars() elif self.pos_type == "SHORT": if price > self.trailing_stop_price or price > self.stop_loss_price: self.Liquidate(self.forex_pair.Symbol) self.Log(f"*******Closing trade stopped_out MINUTE: {self.pos_type}, trade_price: {self.trade_enter}, Price: {price}") self.clear_vars() def clear_vars(self): self.stop_loss_price = 0.00 self.trailing_stop_price = 0.00 #price self.pos_type = None return def OnThirtyMinuteForexBar(self, sender, bar): exchange = self.MarketHoursDatabase.GetExchangeHours(Market.USA, None, SecurityType.Equity) buying_power = self.Portfolio.GetBuyingPower("USDJPY", OrderDirection.Buy) selling_power = self.Portfolio.GetBuyingPower("USDJPY", OrderDirection.Sell) #self.debug(f'cash on hand: {self.portfolio.cash}, target_val: {buying_power}, sell: {selling_power}') if not exchange.IsOpen(self.Time, False): current_time = self.time price = bar.close #stop loss during trading hours if self.pos_type == "LONG": if price < self.trailing_stop_price or price < self.stop_loss_price: self.liquidate(self.forex_pair.Symbol) self.clear_vars() self.log(f"Closing trade stopped_out: {self.pos_type}, trade_price: {self.trade_enter}, Price: {price}") if self.pos_type == "SHORT": if price > self.trailing_stop_price or price > self.stop_loss_price: self.liquidate(self.forex_pair.Symbol) self.clear_vars() self.log(f"Closing trade stopped_out: {self.pos_type}, trade_price: {self.trade_enter}, Price: {price}") if self.previous is not None and self.pos_type == None: # Enter new position LONG if self.short_sma.Current.Value > self.long_sma.Current.Value and self.previous <= self.long_sma.Current.Value: self.SetHoldings(self.forex_pair.Symbol, 1.95) self.trade_enter = price self.pos_type = "LONG" self.stop_loss_price = price - price*self.stop_loss self.trailing_stop_price = self.stop_loss_price self.log(f"Entering trade LONG: {current_time}, Price: {price}") # Enter new position SHORT elif self.short_sma.Current.Value < self.long_sma.Current.Value and self.previous >= self.long_sma.Current.Value: quantity = (selling_power*0.90) / price self.SetHoldings(self.forex_pair.Symbol, -1.95) self.trade_enter = price self.pos_type = "SHORT" self.stop_loss_price = price + price*self.stop_loss self.trailing_stop_price = self.stop_loss_price self.log(f"Entering trade SHORT: {current_time}, Price: {price}") #if in a postion check to take profit signal if self.pos_type == "LONG": if self.short_sma.Current.Value < self.long_sma.Current.Value and self.previous >= self.long_sma.Current.Value: self.liquidate(self.forex_pair.Symbol) self.clear_vars() self.log(f"Closing trade signal: {self.pos_type}, trade_price: {self.trade_enter}, Price: {price}, previous:{self.previous},short:{self.short_sma.Current.Value}, long:{self.long_sma.Current.Value}") if self.pos_type == "SHORT": if self.short_sma.Current.Value > self.long_sma.Current.Value and self.previous <= self.long_sma.Current.Value: self.liquidate(self.forex_pair.Symbol) self.clear_vars() self.log(f"Closing trade signal: {self.pos_type}, trade_price: {self.trade_enter}, Price: {price}, previous:{self.previous},short:{self.short_sma.Current.Value}, long:{self.long_sma.Current.Value}") #update the trailing stop price ONLY IF INVESTED if self.pos_type == "LONG": self.trailing_stop_price = price - price*self.trailing_stop elif self.pos_type == "SHORT": self.trailing_stop_price = price + price*self.trailing_stop #outside of trading hours liquidate if exchange.IsOpen(self.Time, False) and self.pos_type != None: current_time = self.time price = bar.close self.log(f"Closing trade after hours: {self.pos_type}, trade_price: {self.trade_enter}, Price: {price}") self.liquidate(self.forex_pair.Symbol) self.clear_vars() self.previous = self.short_sma.Current.Value self.short_sma.update(bar.end_time, bar.close) self.long_sma.update(bar.end_time, bar.close) pass def handle_candlestick_strategy(self): # Use the latest data stored in OnData if not self.latest_data: return # Exit if there's no data available #Forex Trading hours stop losses data = self.latest_data for ticker, candle in self.candles.items(): if ticker not in self.latest_data.Bars: continue bar = self.latest_data.Bars[ticker] candle.Update(bar) # if ticker not in data.Bars: # self.debug(ticker) # continue # bar = data.Bars[ticker] # candle.Update(bar) # self.log(bar) ####################################################### # # # LONG # # POSITIONS # # # ####################################################### if candle.shouldExit(): self.close_positions([position for position in self.open_positions if position['Stock'] == ticker], data[ticker].Close, 'SELL', candleStick=candle.getPatternName()) elif candle.shouldEnter(): portfolio_value = self.Portfolio.TotalPortfolioValue allocation = portfolio_value * self.percent # Allocate self.percent of portfolio value to each position quantity = allocation // data[ticker].Close # quantity = (10000 / data[ticker].Close + 1) self.Debug(f"Buying {quantity} shares of {ticker} at {data[ticker].Close} on {self.Time}") self.MarketOrder(ticker, quantity) self.open_positions.append( {'Date': self.Time, 'Qty': quantity, 'Buy Price': data[ticker].Close, 'Stock': ticker, 'Paper P/L': 0, 'Paper P/L %': 0, 'TrailingStopLoss': data[ticker].Close * (1 - self.trailing_stop_loss_percent) } ) ####################################################### # # # SHORT # # POSITIONS # # # ####################################################### #We will also look at entering/exiting short positions: if candle.shouldExitShortPositions(): # Exiting short positions self.Debug(f"Exiting the short position for the stock :{ticker}") self.close_positions([position for position in self.open_short_positions if position['Stock'] == ticker], data[ticker].Close, 'BUY TO COVER', candleStick=candle.getPatternName()) elif candle.shouldEnterShortPositions(): # Enter the short positions: self.Debug(f"Entering the short position for the stock :{ticker}") portfolio_value = self.Portfolio.TotalPortfolioValue allocation = portfolio_value * self.percent # Allocate self.percent of portfolio value to each position quantity = allocation // data[ticker].Close self.MarketOrder(ticker, -quantity) self.open_short_positions.append( {'Date': self.Time, 'Qty': quantity, 'Sell Price': data[ticker].Close, 'Stock': ticker, 'Paper P/L': 0, 'Paper P/L %': 0, 'TrailingStopLoss': data[ticker].Close * (1 + self.trailing_stop_loss_percent) } ) # Everyday we will calculate the Paper profit of each open position self.calculate_paper_pl(self.latest_data) # Each day we will calculate to see if our trailing stop-loss thresholf is being hit self.check_trailing_stop_loss(self.latest_data) def close_positions(self, open_positions, price, heading, candleStick=""): ''' We sell 25% of each open position whenever our exit position candle occurs ''' for o in open_positions: qty = o['Qty'] sellQty = self.percent * qty if qty <= 4: sellQty = qty leftQty = qty - sellQty if heading == 'SELL': # Closing long positions paperValue = sellQty * price PL = paperValue - sellQty * o['Buy Price'] elif heading == 'BUY TO COVER': # Closing short positions paperValue = sellQty * o['Sell Price'] PL = paperValue - sellQty * price # paperValue = sellQty * price # PL = paperValue - sellQty * o['Buy Price'] transaction = { 'Date': o["Date"], 'Stock': o['Stock'], 'Type of Transaction': heading, 'Candle': candleStick, 'Buy Price' if heading == 'SELL' else 'Sell Price': o['Buy Price'] if heading == 'SELL' else o['Sell Price'], 'Sell Price' if heading == 'SELL' else 'Buy Price': price, 'Qty': sellQty, 'P/L': PL } #self.Debug(f"{transaction}") #transaction = {'Date': o["Date"], 'Stock' : o['Stock'],'Type of Transaction' : heading, 'Candle': candleStick, 'Buy Price' : o['Buy Price'], 'Sell Price' : price, 'Qty': sellQty , 'P/L': PL} self.transactions_history.loc[len(self.transactions_history)] = transaction #self.Debug(f"Transacton {transaction}") if heading == 'SELL': self.MarketOrder(o['Stock'], -sellQty) # Selling for long position self.open_positions = [o for o in open_positions if o['Qty'] > 0] elif heading == 'BUY TO COVER': self.MarketOrder(o['Stock'], sellQty) # Buying to cover short position self.open_short_positions = [o for o in open_positions if o['Qty'] > 0] o['Qty'] = leftQty # self.MarketOrder(o['Stock'], -sellQty) # o['Qty'] = leftQty def calculate_paper_pl(self, data): ''' We regularly take out profits, if a current open position has a unrealized profit of greater than 30% ''' ####################################################### # # # LONG # # POSITIONS # # # ####################################################### getProfit = [] for position in self.open_positions: ticker = position['Stock'] if ticker not in data.Bars: continue # Calculating the paper profit price = data.Bars[ticker].Open qty = position['Qty'] paperValue = qty * price paperPL = paperValue - qty * position['Buy Price'] paperPLPercentage = paperPL / (qty * position['Buy Price']) if paperPLPercentage > 0.3: # Selling 25% of the position if paper profit is > 30% sellQty = 0.25 * qty if qty <= 4: sellQty = qty position['Qty'] -= sellQty transaction = {'Date': self.Time, 'Stock': ticker, 'Type of Transaction': 'SELL FRAC', 'Buy Price': position['Buy Price'], 'Sell Price': price, 'Qty': sellQty, 'P/L': paperPL * (sellQty / qty)} getProfit.append(transaction) for transaction in getProfit: ticker = transaction['Stock'] self.transactions_history.loc[len(self.transactions_history)] = transaction #self.Debug(f"Transacton {transaction}") #self.transactions_history = self.transactions_history.append(transaction, ignore_index=True) # Selling a part of the position self.MarketOrder(ticker, -transaction['Qty']) self.open_positions = [p for p in self.open_positions if p['Qty'] > 0] ####################################################### # # # SHORT # # POSITIONS # # # ####################################################### getProfit = [] for position in self.open_short_positions: ticker = position['Stock'] if ticker not in data.Bars: continue # Calculating the paper profit price = data.Bars[ticker].Open qty = position['Qty'] paperValue = qty * position['Sell Price'] paperPL = paperValue - qty * price paperPLPercentage = paperPL / (qty * position['Sell Price']) if paperPLPercentage > 0.3: # Selling 25% of the position if paper profit is > 30% sellQty = 0.25 * qty self.Debug(f"Selling {sellQty} stocks of {position['Stock']} from {position['Qty']}") if qty <= 4: sellQty = qty position['Qty'] -= sellQty transaction = { 'Date': self.Time, 'Stock': ticker, 'Type of Transaction': 'BUY TO COVER FRAC', 'Sell Price': position['Sell Price'], 'Buy Price': price, # For short positions, Sell Price is the opening price 'Qty': sellQty, 'P/L': paperPL * (sellQty / qty) } getProfit.append(transaction) for transaction in getProfit: ticker = transaction['Stock'] self.transactions_history.loc[len(self.transactions_history)] = transaction #self.Debug(f"Transacton {transaction}") #self.transactions_history = self.transactions_history.append(transaction, ignore_index=True) # Selling a part of the position self.MarketOrder(ticker, transaction['Qty']) self.open_short_positions = [p for p in self.open_short_positions if p['Qty'] > 0] def check_stop_loss(self, data): ''' This function iterates through all the open positions and liquidates the entire position if our stop-loss is hit ''' positions_to_liquidate = [] for position in self.open_positions: ticker = position['Stock'] if ticker not in data.Bars: continue price = data.Bars[ticker].Open qty = position['Qty'] paperValue = qty * price paperPL = paperValue - qty * position['Buy Price'] paperPLPercentage = paperPL / (qty * position['Buy Price']) if paperPLPercentage <= self.stop_loss_threshold: self.Debug(f"Stop loss hit for position: {position}, current price = {price}, loss = {paperPL} ,loss per = {paperPLPercentage}") positions_to_liquidate.append(position) for position in positions_to_liquidate: ticker = position['Stock'] # Liquidating the entire position self.MarketOrder(ticker, -qty) #self.close_positions([position], data[ticker].Close, 'STOP LOSS') # We need to remove the position from open positions since we have liquidated # the entire position self.open_positions.remove(position) def check_trailing_stop_loss(self, data): ''' This function iterates through all the open positions and liquidates the entire position if our trailing stop-loss is hit ''' ####################################################### # # # LONG # # POSITIONS # # # ####################################################### positions_to_liquidate = [] for position in self.open_positions: ticker = position['Stock'] if ticker not in data.Bars: continue price = data.Bars[ticker].Open qty = position['Qty'] # Update trailing stop loss if current price is higher than the previous highest price if price > position['TrailingStopLoss'] / (1 - self.trailing_stop_loss_percent): position['TrailingStopLoss'] = price * (1 - self.trailing_stop_loss_percent) # Check if current price hits trailing stop loss if price <= position['TrailingStopLoss']: self.Debug(f"Trailing stop loss hit for {ticker} at {price}") positions_to_liquidate.append(position) continue paperValue = qty * price paperPL = paperValue - qty * position['Buy Price'] paperPLPercentage = paperPL / (qty * position['Buy Price']) if paperPLPercentage <= self.stop_loss_threshold: self.Debug(f"Stop loss hit for position: {position}, current price = {price}, loss = {paperPL} ,loss per = {paperPLPercentage}") positions_to_liquidate.append(position) for position in positions_to_liquidate: ticker = position['Stock'] # Liquidating the entire position self.MarketOrder(ticker, -qty) #self.close_positions([position], data[ticker].Close, 'STOP LOSS') # We need to remove the position from open positions since we have liquidated # the entire position self.open_positions.remove(position) ####################################################### # # # SHORT # # POSITIONS # # # ####################################################### positions_to_liquidate = [] # Check short positions for position in self.open_short_positions: ticker = position['Stock'] if ticker not in data.Bars: continue price = data.Bars[ticker].Open qty = position['Qty'] # Update trailing stop loss if current price is lower than the previous lowest price if price < position['TrailingStopLoss'] / (1 + self.trailing_stop_loss_percent): position['TrailingStopLoss'] = price * (1 + self.trailing_stop_loss_percent) # Check if current price hits trailing stop loss if price >= position['TrailingStopLoss']: self.Debug(f"Trailing stop loss hit for short position {ticker} at {price}") positions_to_liquidate.append(position) continue # Liquidate short positions that hit the trailing stop loss for position in positions_to_liquidate: ticker = position['Stock'] qty = position['Qty'] self.MarketOrder(ticker, qty) # Buying back to cover the short position self.open_short_positions.remove(position) class Candle: def __init__(self, algorithm, ticker, frac=0.9): self.algorithm = algorithm self.ticker = ticker self.frac = frac self.bb = BollingerBands(20, 2, MovingAverageType.Simple) self.bb2 = BollingerBands(20, 1, MovingAverageType.Simple) self.rsi = RelativeStrengthIndex(14, MovingAverageType.Simple) self.macd = MovingAverageConvergenceDivergence(12, 26, 9, MovingAverageType.Simple) self.sma = SimpleMovingAverage(50) self.data = [] self.pattern_name = "" def Update(self, bar): self.data.append(bar) if len(self.data) > 2: self.data.pop(0) self.bb.Update(bar.EndTime, bar.Close) self.bb2.Update(bar.EndTime, bar.Close) self.rsi.Update(bar.EndTime, bar.Close) self.macd.Update(bar.EndTime, bar.Close) self.sma.Update(bar.EndTime, bar.Close) def return_OHLC(self, candle): return candle.Open, candle.High, candle.Low, candle.Close def return_stats(self, candle): delta_v = candle.Close - candle.Open max_vi = max(candle.Close, candle.Open) min_vi = min(candle.Close, candle.Open) return delta_v, max_vi, min_vi def shouldEnter(self): if len(self.data) < 2: return False if self.isHangingMan() or self.isBullishEngulfing() or self.isDragonFlyDoji(): return True return False def shouldExit(self): if len(self.data) < 2: return False if self.isInvertedHammer() : # look into why no. of trades is decreasing on more candle sticks return True return False def shouldEnterShortPositions(self): ''' We want to enter short positions when we are going to see a change from uptrend to downtrend ''' if len(self.data) < 2: return False if self.isInvertedHammer(): # look into why no. of trades is decreasing on more candle sticks return True return False def shouldExitShortPositions(self): ''' We want to exit short positions when we are going to see a change from downtrend to uptrend ''' if len(self.data) < 2: return False if self.isHangingMan() or self.isBullishEngulfing() or self.isDragonFlyDoji(): return True return False def isHangingMan(self): candle = self.data[-1] curr_open, curr_high, curr_low, curr_close = self.return_OHLC(candle) curr_delta_v, curr_max_vi, curr_min_vi = self.return_stats(candle) if ( (curr_high - curr_low) > -4 * curr_delta_v) and \ ((curr_close - curr_low)/(0.001 + curr_high - curr_low ) > 0.6) and \ (curr_open - curr_low)/(0.001 + curr_high - curr_low ) > 0.6 and \ (curr_close >= self.bb.UpperBand.Current.Value * self.frac or curr_close <= self.bb.LowerBand.Current.Value): self.pattern_name = "Hanging Man" return True return False def isInvertedHammer(self): candle = self.data[-1] curr_open, curr_high, curr_low, curr_close = self.return_OHLC(candle) curr_delta_v, curr_max_vi, curr_min_vi = self.return_stats(candle) if ( (curr_high - curr_low) > -3 * curr_delta_v) and \ ((curr_high - curr_close)/(0.001 + curr_high - curr_low ) > 0.6) and \ (curr_high - curr_open)/(0.001 + curr_high - curr_low ) > 0.6 and \ (curr_close >= self.bb.UpperBand.Current.Value or curr_close <= self.bb.LowerBand.Current.Value) and \ (curr_close >= self.bb.UpperBand.Current.Value * self.frac or curr_close <= self.bb.LowerBand.Current.Value): self.pattern_name = "Inverted Hammer" return True return False def isDragonFlyDoji(self): candle = self.data[-1] curr_open, curr_high, curr_low, curr_close = self.return_OHLC(candle) curr_delta_v, curr_max_vi, curr_min_vi = self.return_stats(candle) if ( curr_open == curr_close or (abs(curr_delta_v) / (curr_high - curr_low) < 0.1 )) and \ ((curr_high - curr_max_vi) < (3 * abs(curr_delta_v)) ) and \ ((curr_min_vi - curr_low) > (3 * abs(curr_delta_v)) ) and \ (curr_close >= self.bb.UpperBand.Current.Value * self.frac or curr_close <= self.bb.LowerBand.Current.Value): self.pattern_name = "DragonFly Doji" return True return False def isGravestoneDoji(self): candle = self.data[-1] curr_open, curr_high, curr_low, curr_close = self.return_OHLC(candle) curr_delta_v, curr_max_vi, curr_min_vi = self.return_stats(candle) if ( curr_open == curr_close or(abs(curr_delta_v) / (curr_high - curr_low) < 0.1 )) and \ ((curr_high - curr_max_vi) > (3 * abs(curr_delta_v)) ) and \ ((curr_min_vi - curr_low) <= (3 * abs(curr_delta_v)) ) and \ (curr_close >= self.bb.UpperBand.Current.Value * self.frac or curr_close <= self.bb.LowerBand.Current.Value): self.pattern_name = "Gravestone Doji" return True return False def isBullishEngulfing(self): if len(self.data) < 2: return False candle = self.data[-1] prev_candle = self.data[-2] curr_open, curr_high, curr_low, curr_close = self.return_OHLC(candle) prev_open, prev_high, prev_low, prev_close = self.return_OHLC(prev_candle) curr_delta_v, curr_max_vi, curr_min_vi = self.return_stats(candle) prev_delta_v, prev_max_vi, prev_min_vi = self.return_stats(prev_candle) if curr_close >= prev_open and prev_open > prev_close and \ curr_close > curr_open and prev_close >= curr_open and \ (curr_close - curr_open) > (prev_open - prev_close) and \ (curr_close >= self.bb.UpperBand.Current.Value * self.frac or curr_close <= self.bb.LowerBand.Current.Value): self.pattern_name = "Bullish Engulfing" return True return False def isBearishEngulfing(self): if len(self.data) < 2: return False candle = self.data[-1] prev_candle = self.data[-2] curr_open, curr_high, curr_low, curr_close = self.return_OHLC(candle) prev_open, prev_high, prev_low, prev_close = self.return_OHLC(prev_candle) curr_delta_v, curr_max_vi, curr_min_vi = self.return_stats(candle) prev_delta_v, prev_max_vi, prev_min_vi = self.return_stats(prev_candle) if curr_open >= prev_close and prev_close > prev_open and \ curr_close < curr_open and prev_open >= curr_close and \ (curr_open - curr_close) > (prev_close - prev_open) and \ (curr_close >= self.bb.UpperBand.Current.Value * self.frac or curr_close <= self.bb.LowerBand.Current.Value): self.pattern_name = "Bearish Engulfing" return True return False def getPatternName(self): return self.pattern_name