async def run( self, symbol: str, shortable: bool, position: int, minute_history: df, now: datetime, portfolio_value: float = None, trading_api: tradeapi = None, debug: bool = False, backtesting: bool = False, ) -> Tuple[bool, Dict]: data = minute_history.iloc[-1] prev_min = minute_history.iloc[-2] morning_rush = ( True if (now - config.market_open).seconds // 60 < 30 else False ) if ( await super().is_buy_time(now) and not position and not open_orders.get(symbol, None) and not await self.should_cool_down(symbol, now) ): # Check for buy signals lbound = config.market_open.replace(second=0, microsecond=0) ubound = lbound + timedelta(minutes=15) try: high_15m = minute_history[lbound:ubound]["high"].max() # type: ignore except Exception as e: tlog(f"{symbol}[{now}] failed to aggregate {ubound}:{lbound}") return False, {} if data.close > high_15m or ( hasattr(config, "bypass_market_schedule") and config.bypass_market_schedule ): close = ( minute_history["close"] .dropna() .between_time("9:30", "16:00") ) close_5m = ( minute_history["close"] .dropna() .between_time("9:30", "16:00") .resample("5min") .last() ).dropna() macds = MACD(close) # sell_macds = MACD(close, 13, 21) macd = macds[0] macd_signal = macds[1] macd_hist = macds[2] macd_trending = macd[-3] < macd[-2] < macd[-1] macd_above_signal = macd[-1] > macd_signal[-1] * 1.1 macd_hist_trending = ( macd_hist[-3] < macd_hist[-2] < macd_hist[-1] ) if ( macd[-1] > 0 and macd_trending and macd_above_signal and macd_hist_trending and ( data.vwap > data.open > prev_min.close and data.vwap != 0.0 or data.vwap == 0.0 and data.close > data.open > prev_min.close ) ): macd2 = MACD(close, 40, 60)[0] if macd2[-1] >= 0 and np.diff(macd2)[-1] >= 0: if debug: tlog( f"[{self.name}][{now}] slow macd confirmed trend" ) # check RSI does not indicate overbought rsi = RSI(close, 14) if debug: tlog( f"[{self.name}][{now}] {symbol} RSI={round(rsi[-1], 2)}" ) rsi_limit = 75 if rsi[-1] < rsi_limit: if debug: tlog( f"[{self.name}][{now}] {symbol} RSI {round(rsi[-1], 2)} <= {rsi_limit}" ) else: tlog( f"[{self.name}][{now}] {symbol} RSI over-bought, cool down for 5 min" ) cool_down[symbol] = now.replace( second=0, microsecond=0 ) + timedelta(minutes=5) return False, {} stop_price = find_stop( data.close if not data.vwap else data.vwap, minute_history, now, ) target_price = ( 3 * (data.close - stop_price) + data.close ) target_prices[symbol] = target_price stop_prices[symbol] = stop_price if portfolio_value is None: if trading_api: retry = 3 while retry > 0: try: portfolio_value = float( trading_api.get_account().portfolio_value ) break except ConnectionError as e: tlog( f"[{symbol}][{now}[Error] get_account() failed w/ {e}, retrying {retry} more times" ) await asyncio.sleep(0) retry -= 1 if not portfolio_value: tlog( "f[{symbol}][{now}[Error] failed to get portfolio_value" ) return False, {} else: raise Exception( f"{self.name}: both portfolio_value and trading_api can't be None" ) shares_to_buy = ( portfolio_value * config.risk // (data.close - stop_prices[symbol]) ) if not shares_to_buy: shares_to_buy = 1 shares_to_buy -= position if shares_to_buy > 0: buy_price = max(data.close, data.vwap) tlog( f"[{self.name}][{now}] Submitting buy for {shares_to_buy} shares of {symbol} at {buy_price} target {target_prices[symbol]} stop {stop_prices[symbol]}" ) buy_indicators[symbol] = { "macd": macd[-5:].tolist(), "macd_signal": macd_signal[-5:].tolist(), "vwap": data.vwap, "avg": data.average, } return ( True, { "side": "buy", "qty": str(shares_to_buy), "type": "limit", "limit_price": str(buy_price), } if not morning_rush else { "side": "buy", "qty": str(shares_to_buy), "type": "market", }, ) else: if debug: tlog(f"[{self.name}][{now}] {data.close} < 15min high ") if ( await super().is_sell_time(now) and position > 0 and symbol in latest_cost_basis and last_used_strategy[symbol].name == self.name and not open_orders.get(symbol) ): if ( not self.whipsawed.get(symbol, None) and data.close < latest_cost_basis[symbol] * 0.98 ): self.whipsawed[symbol] = True serie = ( minute_history["close"].dropna().between_time("9:30", "16:00") ) if data.vwap: serie[-1] = data.vwap macds = MACD(serie, 13, 21,) macd = macds[0] macd_signal = macds[1] rsi = RSI( minute_history["close"].dropna().between_time("9:30", "16:00"), 14, ) movement = ( data.close - latest_scalp_basis[symbol] ) / latest_scalp_basis[symbol] macd_val = macd[-1] macd_signal_val = macd_signal[-1] round_factor = ( 2 if macd_val >= 0.1 or macd_signal_val >= 0.1 else 3 ) scalp_threshold = ( target_prices[symbol] + latest_scalp_basis[symbol] ) / 2.0 macd_below_signal = round(macd_val, round_factor) < round( macd_signal_val, round_factor ) bail_out = ( ( latest_scalp_basis[symbol] > latest_cost_basis[symbol] or movement > 0.02 ) and macd_below_signal and round(macd[-1], round_factor) < round(macd[-2], round_factor) ) bail_on_whiplash = ( data.close > latest_cost_basis[symbol] and macd_below_signal and round(macd[-1], round_factor) < round(macd[-2], round_factor) ) scalp = movement > 0.04 or data.vwap > scalp_threshold below_cost_base = data.vwap < latest_cost_basis[symbol] rsi_limit = 79 if not morning_rush else 85 to_sell = False partial_sell = False limit_sell = False sell_reasons = [] if data.close <= stop_prices[symbol]: to_sell = True sell_reasons.append("stopped") elif ( below_cost_base and round(macd_val, 2) < 0 and rsi[-1] < rsi[-2] and round(macd[-1], round_factor) < round(macd[-2], round_factor) and data.vwap < 0.95 * data.average ): to_sell = True sell_reasons.append( "below cost & macd negative & RSI trending down and too far from VWAP" ) elif data.close >= target_prices[symbol] and macd[-1] <= 0: to_sell = True sell_reasons.append("above target & macd negative") elif rsi[-1] >= rsi_limit: to_sell = True sell_reasons.append("rsi max, cool-down for 5 minutes") cool_down[symbol] = now.replace( second=0, microsecond=0 ) + timedelta(minutes=5) elif bail_out: to_sell = True sell_reasons.append("bail") elif scalp: partial_sell = True to_sell = True sell_reasons.append("scale-out") elif bail_on_whiplash: to_sell = True sell_reasons.append("bail post whiplash") if to_sell: sell_indicators[symbol] = { "rsi": rsi[-3:].tolist(), "movement": movement, "sell_macd": macd[-5:].tolist(), "sell_macd_signal": macd_signal[-5:].tolist(), "vwap": data.vwap, "avg": data.average, "reasons": " AND ".join( [str(elem) for elem in sell_reasons] ), } if not partial_sell: if not limit_sell: tlog( f"[{self.name}][{now}] Submitting sell for {position} shares of {symbol} at market with reason:{sell_reasons}" ) return ( True, { "side": "sell", "qty": str(position), "type": "market", }, ) else: tlog( f"[{self.name}][{now}] Submitting sell for {position} shares of {symbol} at {data.close} with reason:{sell_reasons}" ) return ( True, { "side": "sell", "qty": str(position), "type": "limit", "limit_price": str(data.close), }, ) else: qty = int(position / 2) if position > 1 else 1 tlog( f"[{self.name}][{now}] Submitting sell for {str(qty)} shares of {symbol} at limit of {data.close }with reason:{sell_reasons}" ) return ( True, { "side": "sell", "qty": str(qty), "type": "limit", "limit_price": str(data.close), }, ) return False, {}
async def run( self, symbol: str, position: int, minute_history: df, now: datetime, portfolio_value: float = None, trading_api: tradeapi = None, debug: bool = False, backtesting: bool = False, ) -> Tuple[bool, Dict]: data = minute_history.iloc[-1] prev_min = minute_history.iloc[-2] morning_rush = (True if (now - config.market_open).seconds // 60 < 30 else False) if (await super().is_buy_time(now) and not position and not await self.should_cool_down(symbol, now)): # Check for buy signals lbound = config.market_open ubound = lbound + timedelta(minutes=15) if debug: tlog(f"15 schedule {lbound}/{ubound}") try: high_15m = minute_history[lbound:ubound]["high"].max( ) # type: ignore if debug: tlog(f"{minute_history[lbound:ubound]}") # type: ignore except Exception as e: # Because we're aggregating on the fly, sometimes the datetime # index can get messy until it's healed by the minute bars tlog( f"[{self.name}] error aggregation {e} - maybe should use nearest?" ) return False, {} if debug: tlog(f"15 minutes high:{high_15m}") # Get the change since yesterday's market close if data.close > high_15m or ( hasattr(config, "bypass_market_schedule") and config.bypass_market_schedule ): # and volume_today[symbol] > 30000: if debug: tlog( f"[{now}]{symbol} {data.close} above 15 minute high {high_15m}" ) last_30_max_close = minute_history[-30:]["close"].max() last_30_min_close = minute_history[-30:]["close"].min() if ((now - config.market_open).seconds // 60 > 90 and (last_30_max_close - last_30_min_close) / last_30_min_close > 0.1 and (not hasattr(config, "bypass_market_schedule") or not config.bypass_market_schedule)): tlog( f"[{self.name}][{now}] too sharp {symbol} increase in last 30 minutes, can't trust MACD, cool down for 15 minutes" ) cool_down[symbol] = now.replace( second=0, microsecond=0) + timedelta(minutes=15) return False, {} serie = (minute_history["close"].dropna().between_time( "9:30", "16:00")) if data.vwap: serie[-1] = data.vwap macds = MACD(serie) sell_macds = MACD(serie, 13, 21) macd1 = macds[0] macd_signal = macds[1] round_factor = (2 if macd1[-1] >= 0.1 or macd_signal[-1] >= 0.1 else 3) minute_shift = 0 if morning_rush or debug else -1 if debug: if macd1[-1 + minute_shift].round(round_factor) > 0: tlog(f"[{now}]{symbol} MACD > 0") if (macd1[-3 + minute_shift].round(round_factor) < macd1[-2 + minute_shift].round(round_factor) < macd1[-1 + minute_shift].round(round_factor)): tlog(f"[{now}]{symbol} MACD trending") else: tlog( f"[{now}]{symbol} MACD NOT trending -> failed {macd1[-3 + minute_shift].round(round_factor)} {macd1[-2 + minute_shift].round(round_factor)} {macd1[-1 + minute_shift].round(round_factor)}" ) if (macd1[-1 + minute_shift] > macd_signal[-1 + minute_shift]): tlog(f"[{now}]{symbol} MACD above signal") else: tlog(f"[{now}]{symbol} MACD BELOW signal -> failed") if data.close >= data.open: tlog(f"[{now}]{symbol} above open") else: tlog( f"[{now}]{symbol} close {data.close} BELOW open {data.open} -> failed" ) if (macd1[-1 + minute_shift].round(round_factor) > 0 and macd1[-3 + minute_shift].round(round_factor) < macd1[-2 + minute_shift].round(round_factor) < macd1[-1 + minute_shift].round(round_factor) and macd1[-1 + minute_shift] > macd_signal[-1 + minute_shift] and sell_macds[0][-1 + minute_shift] > 0 and data.vwap > data.open and data.close > prev_min.close and data.close > data.open): if symbol in voi and voi[symbol][-1] < 0: tlog( f"[{self.name}][{now}] Don't buy {symbol} on negative voi {voi[symbol]}" ) return False, {} if symbol in voi and voi[symbol][-1] < voi[symbol][-2]: tlog( f"[{self.name}][{now}] Don't buy {symbol} if voi not trending up {voi[symbol]}" ) return False, {} if symbol in voi: tlog( f"[{self.name}][{now}] {symbol} voi {voi[symbol]}") tlog( f"[{self.name}][{now}] MACD(12,26) for {symbol} trending up!, MACD(13,21) trending up and above signals" ) if False: # not morning_rush: back_time = ts(config.market_open) back_time_index = minute_history[ "close"].index.get_loc(back_time, method="nearest") close = ( minute_history["close"] [back_time_index:].dropna().between_time( "9:30", "16:00").resample("5min").last()).dropna() open = ( minute_history["open"] [back_time_index:].dropna().between_time( "9:30", "16:00").resample("5min").first()).dropna() if close[-1] > open[-1]: tlog( f"[{now}] {symbol} confirmed 5-min candle bull" ) if debug: tlog( f"[{now}] {symbol} open={open[-5:]} close={close[-5:]}" ) else: tlog( f"[{now}] {symbol} did not confirm 5-min candle bull {close[-5:]} {open[-5:]}" ) return False, {} macd2 = MACD(serie, 40, 60)[0] # await asyncio.sleep(0) if (macd2[-1 + minute_shift] >= 0 and np.diff(macd2)[-1 + minute_shift] >= 0): tlog( f"[{self.name}][{now}] MACD(40,60) for {symbol} trending up!" ) # check RSI does not indicate overbought rsi = RSI(serie, 14) if not (rsi[-1 + minute_shift] > rsi[-2 + minute_shift] > rsi[-3 + minute_shift]): tlog( f"[{self.name}][{now}] {symbol} RSI counter MACD trend ({rsi[-1+minute_shift]},{rsi[-2+minute_shift]},{rsi[-3+minute_shift]})" ) # return False, {} # await asyncio.sleep(0) tlog( f"[{self.name}][{now}] {symbol} RSI={round(rsi[-1+minute_shift], 2)}" ) rsi_limit = 71 if not morning_rush else 80 if rsi[-1 + minute_shift] <= rsi_limit: tlog( f"[{self.name}][{now}] {symbol} RSI {round(rsi[-1+minute_shift], 2)} <= {rsi_limit}" ) enforce_resistance = ( False # (True if not morning_rush else False) ) if enforce_resistance: resistances = await find_resistances( symbol, self.name, min( data.low, prev_min.close ), # data.close if not data.vwap else data.vwap, minute_history, debug, ) supports = await find_supports( symbol, self.name, min( data.low, prev_min.close ), # data.close if not data.vwap else data.vwap, minute_history, debug, ) if resistances is None or resistances == []: tlog( f"[{self.name}] no resistance for {symbol} -> skip buy" ) cool_down[symbol] = now.replace( second=0, microsecond=0) return False, {} if supports is None or supports == []: tlog( f"[{self.name}] no supports for {symbol} -> skip buy" ) cool_down[symbol] = now.replace( second=0, microsecond=0) return False, {} next_resistance = None for potential_resistance in resistances: if potential_resistance > data.close: next_resistance = potential_resistance break if not next_resistance: tlog( f"[{self.name}] did not find resistance above {data.close}" ) return False, {} if next_resistance - data.close < 0.05: tlog( f"[{self.name}] {symbol} at price {data.close} too close to resistance {next_resistance}" ) return False, {} if data.close - supports[-1] < 0.05: tlog( f"[{self.name}] {symbol} at price {data.close} too close to support {supports[-1]} -> trend not established yet" ) return False, {} if (next_resistance - data.close) / ( data.close - supports[-1]) < 0.8: tlog( f"[{self.name}] {symbol} at price {data.close} missed entry point between support {supports[-1]} and resistance {next_resistance}" ) cool_down[symbol] = now.replace( second=0, microsecond=0) return False, {} tlog( f"[{self.name}] {symbol} at price {data.close} found entry point between support {supports[-1]} and resistance {next_resistance}" ) # Stock has passed all checks; figure out how much to buy stop_price = find_stop( data.close if not data.vwap else data.vwap, minute_history, now, ) stop_prices[symbol] = min( stop_price, supports[-1] - 0.05) target_prices[symbol] = ( data.close + (data.close - stop_prices[symbol]) * 2) symbol_resistance[symbol] = next_resistance if next_resistance - data.vwap < 0.05: tlog( f"[{self.name}] {symbol} at price {data.close} too close to resistance {next_resistance}" ) return False, {} # if data.vwap - support < 0.05: # tlog( # f"[{self.name}] {symbol} at price {data.close} too close to support {support} -> trend not established yet" # ) # return False, {} if (next_resistance - data.vwap) / ( data.vwap - stop_prices[symbol]) < 0.8: tlog( f"[{self.name}] {symbol} at price {data.close} missed entry point between support {stop_prices[symbol] } and resistance {next_resistance}" ) cool_down[symbol] = now.replace( second=0, microsecond=0) return False, {} tlog( f"[{self.name}] {symbol} at price {data.close} found entry point between support {stop_prices[symbol]} and resistance {next_resistance}" ) resistance = next_resistance support = target_prices[symbol] else: stop_price = find_stop( data.close if not data.vwap else data.vwap, minute_history, now, ) target_price = (3 * (data.close - stop_price) + data.close) target_prices[symbol] = target_price stop_prices[symbol] = stop_price resistance = target_price support = stop_price symbol_resistance[symbol] = target_price if portfolio_value is None: if trading_api: portfolio_value = float( trading_api.get_account( ).portfolio_value) else: raise Exception( "MomentumLong.run(): both portfolio_value and trading_api can't be None" ) shares_to_buy = ( portfolio_value * config.risk // (data.close - stop_prices[symbol])) if not shares_to_buy: shares_to_buy = 1 shares_to_buy -= position if shares_to_buy > 0: buy_price = max(data.close, data.vwap) tlog( f"[{self.name}] Submitting buy for {shares_to_buy} shares of {symbol} at {buy_price} target {target_prices[symbol]} stop {stop_prices[symbol]}" ) # await asyncio.sleep(0) buy_indicators[symbol] = { "rsi": rsi[-1 + minute_shift].tolist(), "macd": macd1[-5 + minute_shift:].tolist(), "macd_signal": macd_signal[-5 + minute_shift:].tolist(), "slow macd": macd2[-5 + minute_shift:].tolist(), "sell_macd": sell_macds[0][-5 + minute_shift:].tolist(), "sell_macd_signal": sell_macds[1][-5 + minute_shift:].tolist(), "resistances": [resistance], "supports": [support], "vwap": data.vwap, "avg": data.average, "position_ratio": str( round( (resistance - data.vwap) / (data.vwap - support), 2, )), } if symbol in voi: buy_indicators[symbol]["voi"] = voi[symbol] return ( True, { "side": "buy", "qty": str(shares_to_buy), "type": "limit", "limit_price": str(buy_price), } if not morning_rush else { "side": "buy", "qty": str(shares_to_buy), "type": "market", }, ) else: tlog(f"[{self.name}] failed MACD(40,60) for {symbol}!") if (await super().is_sell_time(now) and position > 0 and symbol in latest_cost_basis and last_used_strategy[symbol].name == self.name): if open_orders.get(symbol) is not None: tlog( f"momentum_long: open order for {symbol} exists, skipping") return False, {} # Check for liquidation signals # Sell for a loss if it's fallen below our stop price # Sell for a loss if it's below our cost basis and MACD < 0 # Sell for a profit if it's above our target price macds = MACD( minute_history["close"].dropna().between_time("9:30", "16:00"), 13, 21, ) # await asyncio.sleep(0) macd = macds[0] macd_signal = macds[1] rsi = RSI( minute_history["close"].dropna().between_time("9:30", "16:00"), 14, ) movement = (data.close - latest_scalp_basis[symbol] ) / latest_scalp_basis[symbol] macd_val = macd[-1] macd_signal_val = macd_signal[-1] round_factor = (2 if macd_val >= 0.1 or macd_signal_val >= 0.1 else 3) # await asyncio.sleep(0) if (symbol_resistance and symbol in symbol_resistance and symbol_resistance[symbol]): scalp_threshold = (symbol_resistance[symbol] + latest_scalp_basis[symbol]) / 2.0 else: scalp_threshold = (target_prices[symbol] + latest_scalp_basis[symbol]) / 2.0 bail_threshold = (latest_scalp_basis[symbol] + scalp_threshold) / 2.0 macd_below_signal = round(macd_val, round_factor) < round( macd_signal_val, round_factor) open_rush = (False if (now - config.market_open).seconds // 60 > 45 else True) bail_out = ( # movement > min(0.02, movement_threshold) and macd_below_signal (movement > 0.01 or data.vwap > bail_threshold ) # or open_rush) and macd_below_signal and round(macd[-1], round_factor) < round(macd[-2], round_factor)) bail_on_rsi = (movement > 0.01 or data.vwap > bail_threshold) and rsi[-2] < rsi[-3] if debug and not bail_out: tlog( f"[{now}]{symbol} don't bail: data.vwap={data.vwap} bail_threshold={bail_threshold} macd_below_signal={macd_below_signal} macd[-1]={ macd[-1]} macd[-2]={macd[-2]}" ) scalp = (movement > 0.02 or data.vwap > scalp_threshold) and ( symbol not in voi or voi[symbol][-1] < voi[symbol][-2] < voi[symbol][-3]) below_cost_base = data.vwap < latest_cost_basis[symbol] rsi_limit = 79 if not morning_rush else 85 to_sell = False partial_sell = False limit_sell = False sell_reasons = [] if data.close <= stop_prices[symbol]: to_sell = True sell_reasons.append("stopped") elif (below_cost_base and round(macd_val, 2) < 0 and rsi[-1] < rsi[-2] and round(macd[-1], 2) < round(macd[-2], 2)): to_sell = True sell_reasons.append( "below cost & macd negative & RSI trending down") elif data.close >= target_prices[symbol] and macd[-1] <= 0: to_sell = True sell_reasons.append("above target & macd negative") elif rsi[-1] >= rsi_limit: to_sell = True sell_reasons.append("rsi max, cool-down for 5 minutes") cool_down[symbol] = now.replace( second=0, microsecond=0) + timedelta(minutes=5) elif bail_out: to_sell = True sell_reasons.append("bail") elif bail_on_rsi: to_sell = True sell_reasons.append("bail_on_rsi") elif scalp: partial_sell = True to_sell = True sell_reasons.append("scale-out") elif (symbol in voi and voi[symbol][-1] < 0 and voi[symbol][-1] < voi[symbol][-2] < voi[symbol][-3]): tlog(f"[{now}] {symbol} bail-on-voi identified but not acted") # to_sell = True # sell_reasons.append("bail on voi") # limit_sell = True # Check patterns if debug: tlog( f"[{now}] {symbol} min-2 = {minute_history.iloc[-2].open} {minute_history.iloc[-2].high}, {minute_history.iloc[-2].low}, {minute_history.iloc[-2].close}" ) if self.check_patterns: if (now - buy_time[symbol] > timedelta(minutes=1) and gravestone_doji( prev_min.open, prev_min.high, prev_min.low, prev_min.close, ) and data.close < data.open and data.vwap < data.open and prev_min.close > latest_cost_basis[symbol]): tlog( f"[{now}]{symbol} identified gravestone doji {prev_min.open, prev_min.high, prev_min.low, prev_min.close}" ) to_sell = True partial_sell = False sell_reasons.append("gravestone_doji") elif (now - buy_time[symbol] > timedelta(minutes=2) and spinning_top_bearish_followup( ( minute_history.iloc[-3].open, minute_history.iloc[-3].high, minute_history.iloc[-3].low, minute_history.iloc[-3].close, ), ( minute_history.iloc[-2].open, minute_history.iloc[-2].high, minute_history.iloc[-2].low, minute_history.iloc[-2].close, ), ) and data.vwap < data.open): tlog( f"[{now}] {symbol} identified bullish spinning top followed by bearish candle {(minute_history.iloc[-3].open, minute_history.iloc[-3].high,minute_history.iloc[-3].low, minute_history.iloc[-3].close), (minute_history.iloc[-2].open, minute_history.iloc[-2].high, minute_history.iloc[-2].low, minute_history.iloc[-2].close)}" ) to_sell = True partial_sell = False sell_reasons.append("bull_spinning_top_bearish_followup") elif (now - buy_time[symbol] > timedelta(minutes=2) and bullish_candle_followed_by_dragonfly( ( minute_history.iloc[-3].open, minute_history.iloc[-3].high, minute_history.iloc[-3].low, minute_history.iloc[-3].close, ), ( minute_history.iloc[-2].open, minute_history.iloc[-2].high, minute_history.iloc[-2].low, minute_history.iloc[-2].close, ), ) and data.vwap < data.open): tlog( f"[{now}] {symbol} identified bullish candle followed by dragonfly candle {(minute_history.iloc[-3].open, minute_history.iloc[-3].high,minute_history.iloc[-3].low, minute_history.iloc[-3].close), (minute_history.iloc[-2].open, minute_history.iloc[-2].high, minute_history.iloc[-2].low, minute_history.iloc[-2].close)}" ) to_sell = True partial_sell = False sell_reasons.append("bullish_candle_followed_by_dragonfly") elif (now - buy_time[symbol] > timedelta(minutes=2) and morning_rush and bearish_candle( minute_history.iloc[-3].open, minute_history.iloc[-3].high, minute_history.iloc[-3].low, minute_history.iloc[-3].close, ) and bearish_candle( minute_history.iloc[-2].open, minute_history.iloc[-2].high, minute_history.iloc[-2].low, minute_history.iloc[-2].close, ) and minute_history.iloc[-2].close < minute_history.iloc[-3].close): tlog( f"[{now}] {symbol} identified two consequtive bullish candles during morning rush{(minute_history.iloc[-3].open, minute_history.iloc[-3].high, minute_history.iloc[-3].low, minute_history.iloc[-3].close), (minute_history.iloc[-2].open, minute_history.iloc[-2].high, minute_history.iloc[-2].low, minute_history.iloc[-2].close)}" ) # to_sell = True # partial_sell = False # sell_reasons.append("two_bears_in_the_morning") if to_sell: close = minute_history["close"][-10:].dropna() open = minute_history["open"][-10:].dropna() high = minute_history["high"][-10:].dropna() low = minute_history["low"][-10:].dropna() patterns: Dict[ts, Dict[int, List[str]]] = {} pattern_functions = talib.get_function_groups( )["Pattern Recognition"] for pattern in pattern_functions: pattern_value = getattr(talib, pattern)(open, high, low, close) result = pattern_value.to_numpy().nonzero() if result[0].size > 0: for timestamp, value in pattern_value.iloc[ result].items(): t = ts(timestamp) if t not in patterns: patterns[t] = {} if value not in patterns[t]: patterns[t][value] = [pattern] else: patterns[t][value].append(pattern) candle_s = Series(patterns) candle_s = candle_s.sort_index() sell_indicators[symbol] = { "rsi": rsi[-3:].tolist(), "movement": movement, "sell_macd": macd[-5:].tolist(), "sell_macd_signal": macd_signal[-5:].tolist(), "vwap": data.vwap, "avg": data.average, "reasons": " AND ".join([str(elem) for elem in sell_reasons]), "patterns": candle_s.to_json() if candle_s.size > 0 else None, } if symbol in voi: sell_indicators[symbol]["voi"] = voi[symbol] if not partial_sell: if not limit_sell: tlog( f"[{self.name}] Submitting sell for {position} shares of {symbol} at market" ) return ( True, { "side": "sell", "qty": str(position), "type": "market", }, ) else: tlog( f"[{self.name}] Submitting sell for {position} shares of {symbol} at {data.close}" ) return ( True, { "side": "sell", "qty": str(position), "type": "limit", "limit_price": str(data.close), }, ) else: qty = int(position / 2) if position > 1 else 1 tlog( f"[{self.name}] Submitting sell for {str(qty)} shares of {symbol} at limit of {data.close}" ) return ( True, { "side": "sell", "qty": str(qty), "type": "limit", "limit_price": str(data.close), }, ) return False, {}
async def run( self, symbol: str, shortable: bool, position: int, minute_history: df, now: datetime, portfolio_value: float = None, trading_api: tradeapi = None, debug: bool = False, backtesting: bool = False, ) -> Tuple[bool, Dict]: data = minute_history.iloc[-1] morning_rush = (True if (now - config.market_open).seconds // 60 < 30 else False) if (await super().is_buy_time(now) and not position and not open_orders.get(symbol, None) and not await self.should_cool_down(symbol, now)): # Check for buy signals if symbol not in self.max15: lbound = config.market_open.replace(second=0, microsecond=0) ubound = lbound + timedelta(minutes=15) try: self.max15[symbol] = minute_history[lbound:ubound][ "high"].max() # type: ignore # print( # f"max={self.max15[symbol]} {lbound}, {ubound}, {minute_history}" # ) except Exception as e: tlog( f"{symbol}[{now}] failed to aggregate {lbound}:{ubound} {minute_history}" ) return False, {} if data.close > self.max15[symbol]: close = (minute_history["close"].dropna().between_time( "9:30", "16:00")) if (data.vwap < data.open and data.vwap) or data.close < data.open: if debug: tlog(f"[{self.name}][{now}] price action not positive") return False, {} macds = MACD(close) macd = macds[0].round(3) daiy_max_macd = ( macd[now.replace( # type: ignore hour=9, minute=30, second=0, microsecond=0):].between_time("9:30", "16:00").max()) macd_signal = macds[1].round(3) macd_hist = macds[2].round(3) # print(macd) macd_trending = macd[-3] < macd[-2] < macd[-1] macd_above_signal = macd[-1] > macd_signal[-1] # print(f"{symbol} {data.close} {self.max15[symbol]} {macd_signal}") macd_upper_crossover = (macd[-2] > macd_signal[-2] >= macd_signal[-3] > macd[-3]) macd_hist_trending = (macd_hist[-4] < macd_hist[-3] < macd_hist[-2] < macd_hist[-1]) to_buy = False reason = [] if (macd[-1] < 0 and macd_upper_crossover and macd_trending and macd_above_signal): to_buy = True reason.append("MACD crossover") if (macd_hist_trending and macd_hist[-3] <= 0 < macd_hist[-2] and macd[-1] < daiy_max_macd): reason.append("MACD histogram reversal") if macd[-2] > 0 >= macd[-3] and macd_trending: macd2 = MACD(close, 40, 60)[0] if macd2[-1] >= 0 and np.diff(macd2)[-1] >= 0: if (macd_hist_trending and macd_hist[-3] <= 0 < macd_hist[-2] and macd[-1] < daiy_max_macd): to_buy = True reason.append("MACD zero-cross") if debug: tlog( f"[{self.name}][{now}] slow macd confirmed trend" ) if to_buy: print("to buy!") # check RSI does not indicate overbought rsi = RSI(close, 14) if debug: tlog( f"[{self.name}][{now}] {symbol} RSI={round(rsi[-1], 2)}" ) rsi_limit = 75 if rsi[-1] < rsi_limit: if debug: tlog( f"[{self.name}][{now}] {symbol} RSI {round(rsi[-1], 2)} <= {rsi_limit}" ) else: tlog( f"[{self.name}][{now}] {symbol} RSI over-bought, cool down for 5 min" ) cool_down[symbol] = now.replace( second=0, microsecond=0) + timedelta(minutes=5) return False, {} stop_price = find_stop( data.close if not data.vwap else data.vwap, minute_history, now, ) stop_price = (stop_price - max(0.05, data.close * 0.02) if stop_price else data.close * config.default_stop) target_price = 3 * (data.close - stop_price) + data.close target_prices[symbol] = target_price stop_prices[symbol] = stop_price if portfolio_value is None: print("5555!") if trading_api: retry = 3 while retry > 0: try: portfolio_value = float( trading_api.get_account( ).portfolio_value) break except ConnectionError as e: tlog( f"[{symbol}][{now}[Error] get_account() failed w/ {e}, retrying {retry} more times" ) await asyncio.sleep(0) retry -= 1 if not portfolio_value: tlog( "f[{symbol}][{now}[Error] failed to get portfolio_value" ) return False, {} else: raise Exception( f"{self.name}: both portfolio_value and trading_api can't be None" ) shares_to_buy = (portfolio_value * config.risk // (data.close - stop_prices[symbol])) if not shares_to_buy: shares_to_buy = 1 shares_to_buy -= position if shares_to_buy > 0: self.whipsawed[symbol] = False buy_price = max(data.close, data.vwap) tlog( f"[{self.name}][{now}] Submitting buy for {shares_to_buy} shares of {symbol} at {buy_price} target {target_prices[symbol]} stop {stop_prices[symbol]}" ) buy_indicators[symbol] = { "macd": macd[-5:].tolist(), "macd_signal": macd_signal[-5:].tolist(), "vwap": data.vwap, "avg": data.average, "reason": reason, "rsi": rsi[-5:].tolist(), } self.top_up[symbol] = now return ( True, { "side": "buy", "qty": str(shares_to_buy), "type": "limit", "limit_price": str(buy_price), } if not morning_rush else { "side": "buy", "qty": str(shares_to_buy), "type": "market", }, ) else: if debug: tlog(f"[{self.name}][{now}] {data.close} < 15min high ") elif (await super().is_buy_time(now) and position and not open_orders.get(symbol, None) and not await self.should_cool_down(symbol, now) and symbol in last_used_strategy and last_used_strategy[symbol].name == self.name and data.volume > 500): if symbol not in self.top_up: return False, {} if (symbol not in latest_scalp_basis or latest_scalp_basis[symbol] > latest_cost_basis[symbol]): return False, {} if not ((now - self.top_up[symbol]).total_seconds() > timedelta(minutes=10).total_seconds()): return False, {} if (data.close > data.open and data.close > latest_scalp_basis[symbol]): close = (minute_history["close"].dropna().between_time( "9:30", "16:00")) macds = MACD(close) macd = macds[0] macd_signal = macds[1] if not (macd[-1] > macd_signal[-1] and macd[-1] > macd[-2] > macd_signal[-2]): return False, {} rsi = RSI(close, 14) if not (rsi[-2] < rsi[-1] < 75): return False, {} movement = (data.close - latest_scalp_basis[symbol] ) / latest_scalp_basis[symbol] if movement < 0.005: return False, {} shares_to_buy = int(position * 0.20) buy_price = max(data.close, data.vwap) reason = ["additional buy"] tlog( f"[{self.name}][{now}] Submitting additional buy for {shares_to_buy} shares of {symbol} at {buy_price}" ) self.top_up[symbol] = now buy_indicators[symbol] = { "macd": macd[-5:].tolist(), "macd_signal": macd_signal[-5:].tolist(), "vwap": data.vwap, "avg": data.average, "reason": reason, "rsi": rsi[-5:].tolist(), } return ( True, { "side": "buy", "qty": str(shares_to_buy), "type": "limit", "limit_price": str(buy_price), }, ) if (await super().is_sell_time(now) and position > 0 and symbol in latest_cost_basis and last_used_strategy[symbol].name == self.name and not open_orders.get(symbol)): if (not self.whipsawed.get(symbol, None) and data.close < latest_cost_basis[symbol] * 0.99): self.whipsawed[symbol] = True serie = (minute_history["close"].dropna().between_time( "9:30", "16:00")) if data.vwap: serie[-1] = data.vwap macds = MACD( serie, 13, 21, ) macd = macds[0] macd_signal = macds[1] rsi = RSI( minute_history["close"].dropna().between_time("9:30", "16:00"), 14, ) if not latest_scalp_basis[symbol]: latest_scalp_basis[symbol] = latest_cost_basis[symbol] = 1.0 movement = (data.close - latest_scalp_basis[symbol] ) / latest_scalp_basis[symbol] max_movement = ( minute_history["close"][buy_time[symbol]:].max() - latest_scalp_basis[symbol]) / latest_scalp_basis[symbol] macd_val = macd[-1] macd_signal_val = macd_signal[-1] round_factor = (2 if macd_val >= 0.01 or macd_signal_val >= 0.01 else 3) scalp_threshold = (target_prices[symbol] + latest_scalp_basis[symbol]) / 2.0 macd_below_signal = round(macd_val, round_factor) < round( macd_signal_val, round_factor) bail_out = ( (latest_scalp_basis[symbol] > latest_cost_basis[symbol] or (max_movement > 0.02 and max_movement > movement)) and macd_below_signal and round(macd[-1], round_factor) < round(macd[-2], round_factor)) bail_on_whipsawed = (self.whipsawed.get(symbol, False) and movement > 0.01 and macd_below_signal and round(macd[-1], round_factor) < round( macd[-2], round_factor)) scalp = movement > 0.04 or data.vwap > scalp_threshold below_cost_base = data.vwap < latest_cost_basis[symbol] rsi_limit = 79 if not morning_rush else 85 to_sell = False partial_sell = False limit_sell = False sell_reasons = [] if data.close <= stop_prices[symbol]: to_sell = True sell_reasons.append("stopped") elif (below_cost_base and round(macd_val, 2) < 0 and rsi[-1] < rsi[-2] and round( macd[-1], round_factor) < round(macd[-2], round_factor) and data.vwap < 0.95 * data.average): to_sell = True sell_reasons.append( "below cost & macd negative & RSI trending down and too far from VWAP" ) elif data.close >= target_prices[symbol] and macd[-1] <= 0: to_sell = True sell_reasons.append("above target & macd negative") elif (rsi[-1] >= rsi_limit and data.close > latest_cost_basis[symbol]): to_sell = True sell_reasons.append("rsi max, cool-down for 5 minutes") cool_down[symbol] = now.replace( second=0, microsecond=0) + timedelta(minutes=5) elif bail_out: to_sell = True sell_reasons.append("bail") elif scalp: partial_sell = True to_sell = True sell_reasons.append("scale-out") elif bail_on_whipsawed: to_sell = True partial_sell = False limit_sell = True sell_reasons.append("bail post whipsawed") elif macd[-1] < macd_signal[-1] <= macd_signal[-2] < macd[-2]: sell_reasons.append("MACD cross signal from above") if to_sell: sell_indicators[symbol] = { "rsi": rsi[-3:].tolist(), "movement": movement, "sell_macd": macd[-5:].tolist(), "sell_macd_signal": macd_signal[-5:].tolist(), "vwap": data.vwap, "avg": data.average, "reasons": " AND ".join([str(elem) for elem in sell_reasons]), } if not partial_sell: if not limit_sell: tlog( f"[{self.name}][{now}] Submitting sell for {position} shares of {symbol} at market with reason:{sell_reasons}" ) return ( True, { "side": "sell", "qty": str(position), "type": "market", }, ) else: tlog( f"[{self.name}][{now}] Submitting sell for {position} shares of {symbol} at {data.close} with reason:{sell_reasons}" ) return ( True, { "side": "sell", "qty": str(position), "type": "limit", "limit_price": str(data.close), }, ) else: qty = int(position / 2) if position > 1 else 1 tlog( f"[{self.name}][{now}] Submitting sell for {str(qty)} shares of {symbol} at limit of {data.close }with reason:{sell_reasons}" ) return ( True, { "side": "sell", "qty": str(qty), "type": "limit", "limit_price": str(data.close), }, ) return False, {}
async def run( self, symbol: str, position: int, minute_history: df, now: datetime, portfolio_value: float = None, trading_api: tradeapi = None, debug: bool = False, backtesting: bool = False, ) -> Tuple[bool, Dict]: data = minute_history.iloc[-1] prev_minute = minute_history.iloc[-2] prev_2minutes = minute_history.iloc[-3] if await self.is_buy_time(now) and not position: # Check for buy signals back_time = ts(config.market_open) back_time_index = minute_history["close"].index.get_loc( back_time, method="nearest") close = (minute_history["close"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").last()).dropna() open = (minute_history["open"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").first()).dropna() high = (minute_history["high"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").max()).dropna() low = (minute_history["low"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").min()).dropna() volume = (minute_history["volume"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").sum()).dropna() _df = concat( [ open.rename("open"), high.rename("high"), low.rename("low"), close.rename("close"), volume.rename("volume"), ], axis=1, ) if not add_daily_vwap(_df): tlog(f"[{now}]failed add_daily_vwap") return False, {} if debug: tlog( f"\n[{now}]{symbol} {tabulate(_df[-10:], headers='keys', tablefmt='psql')}" ) vwap_series = _df["average"] if debug: tlog( f"[{now}] {symbol} close:{round(data.close,2)} vwap:{round(vwap_series[-1],2)}" ) if len(vwap_series) < 3: tlog(f"[{now}]{symbol}: missing vwap values {vwap_series}") return False, {} if close[-2] > vwap_series[-2] and close[-1] < vwap_series[-1]: down_cross[symbol] = vwap_series.index[-1].to_pydatetime() tlog( f"[{now}] {symbol} down-crossing on 5-min bars at {down_cross[symbol]}" ) return False, {} if (close[-2] > vwap_series[-2] and close[-3] < vwap_series[-3] and data.close > prev_minute.close and data.close > data.average): if not symbol in down_cross: tlog( f"[{now}] {symbol} did not find download crossing in the past 15 min" ) return False, {} if minute_history.index[-1].to_pydatetime( ) - down_cross[symbol] > timedelta(minutes=30): tlog( f"[{now}] {symbol} down-crossing too far {down_cross[symbol]} from now" ) return False, {} stop_price = find_stop( data.close if not data.vwap else data.vwap, minute_history, now, ) target = data.close + 0.05 stop_prices[symbol] = stop_price target_prices[symbol] = target tlog( f"{symbol} found conditions for VWAP-Scalp strategy now:{now}" ) tlog( f"\n{tabulate(minute_history[-10:], headers='keys', tablefmt='psql')}" ) if portfolio_value is None: if trading_api: portfolio_value = float( trading_api.get_account().portfolio_value) else: raise Exception( "VWAPLong.run(): both portfolio_value and trading_api can't be None" ) shares_to_buy = ( portfolio_value * 20.0 * config.risk // data.close # // (data.close - stop_prices[symbol]) ) if not shares_to_buy: shares_to_buy = 1 shares_to_buy -= position if shares_to_buy > 0: tlog( f"[{self.name}] Submitting buy for {shares_to_buy} shares of {symbol} at {data.close} target {target_prices[symbol]} stop {stop_prices[symbol]}" ) buy_indicators[symbol] = { "average": round(data.average, 2), "vwap": round(data.vwap, 2), "patterns": None, } return ( True, { "side": "buy", "qty": str(shares_to_buy), "type": "limit", "limit_price": str(data.close + 0.01), }, ) elif (await super().is_sell_time(now) and position > 0 and symbol in latest_cost_basis and last_used_strategy[symbol].name == self.name): if open_orders.get(symbol) is not None: tlog(f"vwap_scalp: open order for {symbol} exists, skipping") return False, {} to_sell = False to_sell_market = False if data.vwap <= data.average - 0.05: to_sell = True reason = "below VWAP" to_sell_market = True elif data.close >= target_prices[symbol]: to_sell = True reason = "vwap scalp" elif data.close >= target_prices[symbol]: to_sell = True reason = "vwap scalp" elif (prev_minute.close < prev_minute.open and data.close < data.open): to_sell = True reason = "vwap scalp no bears" if to_sell: sell_indicators[symbol] = { "reason": reason, "average": data.average, "vwap": data.vwap, } return ( True, { "side": "sell", "qty": str(position), "type": "market" } if to_sell_market else { "side": "sell", "qty": str(position), "type": "limit", "limit_price": str(data.close), }, ) return False, {}
async def run( self, symbol: str, position: int, minute_history: df, now: datetime, portfolio_value: float = None, trading_api: tradeapi = None, debug: bool = False, backtesting: bool = False, ) -> Tuple[bool, Dict]: data = minute_history.iloc[-1] prev_minute = minute_history.iloc[-2] if await self.is_buy_time(now) and not position: # Check for buy signals lbound = config.market_open ubound = lbound + timedelta(minutes=15) try: high_15m = minute_history[lbound:ubound]["high"].max( ) # type: ignore if data.vwap < high_15m: return False, {} except Exception as e: # Because we're aggregating on the fly, sometimes the datetime # index can get messy until it's healed by the minute bars tlog( f"[{self.name}] error aggregation {e} - maybe should use nearest?" ) return False, {} back_time = ts(config.market_open) back_time_index = minute_history["close"].index.get_loc( back_time, method="nearest") close = (minute_history["close"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").last()).dropna() open = (minute_history["open"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").first()).dropna() high = (minute_history["high"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").max()).dropna() low = (minute_history["low"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").min()).dropna() volume = (minute_history["volume"] [back_time_index:-1].dropna().between_time( "9:30", "16:00").resample("5min").sum()).dropna() _df = concat( [ open.rename("open"), high.rename("high"), low.rename("low"), close.rename("close"), volume.rename("volume"), ], axis=1, ) if not add_daily_vwap(_df): tlog(f"[{now}]failed add_daily_vwap") return False, {} if debug: tlog( f"\n[{now}]{symbol} {tabulate(_df[-10:], headers='keys', tablefmt='psql')}" ) vwap_series = _df["average"] if ( # data.vwap > close_series[-1] > close_series[-2] # and round(data.average, 2) > round(vwap_series[-1], 2) # and data.vwap > data.average # and data.low > data.average and close[-1] > vwap_series[-1] > vwap_series[-2] > low[-2] and close[-1] > high[-2] and prev_minute.close > prev_minute.open and data.close > data.open and low[-2] < vwap_series[-2] - 0.2): stop_price = find_stop( data.close if not data.vwap else data.vwap, minute_history, now, ) # upperband, middleband, lowerband = BBANDS( # minute_history["close"], timeperiod=20 # ) # stop_price = min( # prev_minute.close, # data.average - 0.01, # lowerband[-1] - 0.03, # ) target = (3 * (data.close - stop_price) + data.close ) # upperband[-1] # if target - stop_price < 0.05: # tlog( # f"{symbol} target price {target} too close to stop price {stop_price}" # ) # return False, {} # if target - data.close < 0.05: # tlog( # f"{symbol} target price {target} too close to close price {data.close}" # ) # return False, {} stop_prices[symbol] = stop_price target_prices[symbol] = target patterns: Dict[ts, Dict[int, List[str]]] = {} pattern_functions = talib.get_function_groups( )["Pattern Recognition"] for pattern in pattern_functions: pattern_value = getattr(talib, pattern)(open, high, low, close) result = pattern_value.to_numpy().nonzero() if result[0].size > 0: for timestamp, value in pattern_value.iloc[ result].items(): t = ts(timestamp) if t not in patterns: patterns[t] = {} if value not in patterns[t]: patterns[t][value] = [pattern] else: patterns[t][value].append(pattern) tlog(f"{symbol} found conditions for VWAP strategy now:{now}") candle_s = Series(patterns) candle_s = candle_s.sort_index() tlog(f"{symbol} 5-min VWAP {vwap_series}") tlog(f"{symbol} 5-min close values {close}") tlog(f"{symbol} {candle_s}") tlog( f"\n{tabulate(minute_history[-10:], headers='keys', tablefmt='psql')}" ) if candle_s.size > 0 and -100 in candle_s[-1]: tlog( f"{symbol} Bullish pattern does not exists -> should skip" ) # return False, {} if portfolio_value is None: if trading_api: portfolio_value = float( trading_api.get_account().portfolio_value) else: raise Exception( "VWAPLong.run(): both portfolio_value and trading_api can't be None" ) shares_to_buy = ( portfolio_value * 20.0 * config.risk // data.close # // (data.close - stop_prices[symbol]) ) print( f"shares to buy {shares_to_buy} {data.close} {stop_prices[symbol]}" ) if not shares_to_buy: shares_to_buy = 1 shares_to_buy -= position if shares_to_buy > 0: tlog( f"[{self.name}] Submitting buy for {shares_to_buy} shares of {symbol} at {data.close} target {target_prices[symbol]} stop {stop_prices[symbol]}" ) buy_indicators[symbol] = { # bbrand_lower": lowerband[-5:].tolist(), # "bbrand_middle": middleband[-5:].tolist(), # "bbrand_upper": upperband[-5:].tolist(), "average": round(data.average, 2), "vwap": round(data.vwap, 2), "patterns": candle_s.to_json(), } return ( True, { "side": "buy", "qty": str(shares_to_buy), "type": "limit", "limit_price": str(data.close), }, ) elif debug: tlog(f"[{now}]{symbol} failed vwap strategy") if not (data.low > data.average): tlog( f"[{now}]{symbol} failed data.low {data.low} > data.average {data.average}" ) if not (close[-1] > vwap_series[-1] > vwap_series[-2] > low[-2]): tlog( f"[{now}]{symbol} failed close[-1] {close[-1]} > vwap_series[-1] {vwap_series[-1]} > vwap_series[-2]{ vwap_series[-2]} > low[-2] {low[-2]}" ) if not (prev_minute.close > prev_minute.open): tlog( f"[{now}]{symbol} failed prev_minute.close {prev_minute.close} > prev_minute.open {prev_minute.open}" ) if not (close[-1] > high[-2]): tlog( f"[{now}]{symbol} failed close[-1] {close[-1]} > high[-2] {high[-2]}" ) if not (data.close > data.open): tlog( f"[{now}]{symbol} failed data.close {data.close} > data.open {data.open}" ) if not low[-2] < vwap_series[-2] - 0.2: tlog( f"[{now}]{symbol} failed low[-2] {low[-2]} < vwap_series[-2] {vwap_series[-2] } - 0.2" ) elif (await super().is_sell_time(now) and position > 0 and symbol in latest_cost_basis and last_used_strategy[symbol].name == self.name): if open_orders.get(symbol) is not None: tlog(f"vwap_long: open order for {symbol} exists, skipping") return False, {} if data.vwap <= data.average - 0.02: sell_indicators[symbol] = { "reason": "below VWAP", "average": data.average, "vwap": data.vwap, } return ( True, { "side": "sell", "qty": str(position), "type": "market" }, ) return False, {}