예제 #1
0
    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]:
        if not shortable:
            return False, {}

        data = minute_history.iloc[-1]

        if data.close > data.average:
            self.was_above_vwap[symbol] = True

        if (
            await super().is_buy_time(now)
            and not position
            and not open_orders.get(symbol, None)
        ):
            tlog(f"{self.name} CONSIDER SHORT SELL {symbol}")

        if (
            await super().is_sell_time(now)
            and position
            and last_used_strategy[symbol].name == self.name
            and not open_orders.get(symbol)
        ):
            tlog(f"{self.name} CONSIDER SHORT BUY {symbol}")

        return False, {}
예제 #2
0
    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]:
        tlog(f"ahm {symbol}")
        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)):
            tlog(f"consider buy {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
                and not open_orders.get(symbol)):
            tlog(f"consider sell {symbol}")

        return False, {}
예제 #3
0
    async def run(
        self,
        symbol: str,
        shortable: bool,
        position: int,
        now: datetime,
        minute_history: df,
        portfolio_value: float = None,
        debug: bool = False,
        backtesting: bool = False,
    ) -> Tuple[bool, Dict]:
        """
        :param symbol: the symbol of the stock,
        :param shortable: can the stock be sold short,
        :param position: the current held position,
        :param minute_history: DataFrame holding OLHC
                               updated per *second*,
        :param now: current timestamp, specially important when called
                    from the backtester application,
        :param portfolio_value: your total porfolio value
        :param debug:       true / false, should be used mostly
                            for adding more verbosity.
        :param backtesting: true / false, which more are we running at
        :return: False w/ {} dictionary, or True w/ order execution
                 details (see below examples)
        """
        current_second_data = minute_history.iloc[-1]
        tlog(f"{symbol} data: {current_second_data}")

        if await super().is_buy_time(now) and not position:
            #
            # Check for buy signals??
            #

            #
            # Global, cross strategies passed via the framework
            #
            target_prices[symbol] = 15.0
            stop_prices[symbol] = 3.8

            #
            # indicators *should* be filled
            #
            buy_indicators[symbol] = {"my_indicator": "random"}

            return (
                True,
                {
                    "side": "buy",
                    "qty": str(10),
                    "type": "limit",
                    "limit_price": "4.4",
                },
            )
        elif (await super().is_sell_time(now) and position > 0
              and last_used_strategy[symbol].name == self.name  # important!
              ):
            # check if we already have open order
            if open_orders.get(symbol) is not None:
                tlog(f"{self.name}: open order for {symbol} exists, skipping")
                return False, {}

            # Check for liquidation signals
            sell_indicators[symbol] = {"my_indicator": "random"}

            tlog(
                f"[{self.name}] Submitting sell for {position} shares of {symbol} at {current_second_data.close}"
            )
            return (
                True,
                {
                    "side": "sell",
                    "qty": str(position),
                    "type": "limit",
                    "limit_price": str(current_second_data.close),
                },
            )

        return False, {}
예제 #4
0
    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]
        mama, fama = MAMA(minute_history["close"])

        if mama[-1] > fama[-1]:
            buy_price = data.close
            stop_price = data.close * 0.98
            target_price = data.close * 1.05

            stop_prices[symbol] = stop_price
            target_prices[symbol] = target_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

            buy_indicators[symbol] = {
                "mama": mama[-5:].tolist(),
                "fama": fama[-5:].tolist(),
            }

            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]}"
            )

            return True, {
                "side": "buy",
                "qty": str(shares_to_buy),
                "type": "limit",
                "limit_price": str(buy_price),
            }

        if (await self.is_sell_time(now) and position
                and last_used_strategy[symbol].name == self.name
                and not open_orders.get(symbol)):
            mama, fama = MAMA(minute_history["close"])

            to_sell: bool = False
            if data.close < stop_prices[symbol]:
                reason = "stopped"
                to_sell = True
            elif data.close >= target_prices[symbol]:
                reason = "target reached"
                to_sell = True
            elif mama[-1] < fama[-1]:
                reason = "fama below mama"
                to_sell = True

            if to_sell:
                tlog(
                    f"[{self.name}][{now}] Submitting sell for {position} shares of {symbol} at market {data.close} w reason {reason}"
                )

                sell_indicators[symbol] = {
                    "mama": mama[-5:].tolist(),
                    "fama": fama[-5:].tolist(),
                    "reason": reason,
                }

                return (
                    True,
                    {
                        "side": "sell",
                        "qty": str(position),
                        "type": "market",
                    },
                )

        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]:
        if not shortable:
            return False, {}

        data = minute_history.iloc[-1]
        if data.close > data.average:
            self.was_above_vwap[symbol] = True

        if (await super().is_buy_time(now) and not position
                and not open_orders.get(symbol, None)
                and self.was_above_vwap.get(symbol, False)
                and not self.traded.get(symbol, False)):
            # Check for buy signals
            lbound = config.market_open.replace(second=0, microsecond=0)

            close = (minute_history["close"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").last()).dropna()
            open = (minute_history["open"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").first()).dropna()
            high = (minute_history["high"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").max()).dropna()
            low = (minute_history["low"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").min()).dropna()
            volume = (minute_history["volume"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").sum()).dropna()
            volume = volume[volume != 0]

            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}]{symbol} failed in add_daily_vwap")
                return False, {}

            vwap_series = df["average"]

            # calc macd on 5 min
            close_5min = (minute_history["close"].dropna().between_time(
                "9:30", "16:00").resample("5min").last()).dropna()

            if debug:
                tlog(
                    f"\n{tabulate(df[-10:], headers='keys', tablefmt='psql')}")
            macds = MACD(close_5min)
            macd = macds[0].round(3)
            macd_signal = macds[1].round(3)
            macd_hist = macds[2].round(3)
            vwap_series = vwap_series.round(3)
            close = close.round(3)
            to_buy = False
            if (not self.potential_trap.get(symbol, False)
                    and close[-1] < vwap_series[-1]
                    and close[-2] < vwap_series[-2]
                    and close[-3] < vwap_series[-3] and close[-1] < open[-1]
                    and close[-2] < open[-2] and close[-3] < open[-3]
                    and macd[-1] < macd_signal[-1] < 0 and macd[-1] < 0
                    and macd_hist[-1] < macd_hist[-2] < macd_hist[-3] < 0 and
                    minute_history["close"][-2] < minute_history["open"][-2]
                    and minute_history["close"][lbound] <
                    minute_history["close"][-2] < minute_history["close"][-3] <
                    minute_history["close"][-4]):
                # if data.close < high_2m:
                #    return False, {}

                self.potential_trap[symbol] = True
                self.trap_start_time[symbol] = now
                tlog(
                    f"[{self.name}]:{symbol}@{now} potential short-trap {data.close}"
                )
                return False, {}
            elif self.potential_trap.get(symbol, False):
                a_vwap = anchored_vwap(minute_history,
                                       self.trap_start_time[symbol])
                if (len(a_vwap) > 10 and minute_history.close[-1] > a_vwap[-1]
                        and minute_history.close[-2] > a_vwap[-2]):
                    tlog(
                        f"[{self.name}]:{symbol}@{now} crossed above anchored-vwap {data.close}"
                    )
                    slope_min, _ = get_series_trend(minute_history.close[-10:])
                    slope_a_vwap, _ = get_series_trend(a_vwap[-10:])

                    if round(slope_min, 2) > round(slope_a_vwap, 2):
                        tlog(
                            f"[{self.name}]:{symbol}@{now} symbol slop {slope_min} above anchored-vwap slope {slope_a_vwap}"
                        )
                    else:
                        tlog(
                            f"[{self.name}]:{symbol}@{now} anchored-vwap slope {slope_a_vwap} below symbol {slope_min}"
                        )
                        return False, {}

                    stop_price = data.close * 0.99  # a_vwap[-1] * 0.99
                    target_price = stop_price * 1.1
                    stop_prices[symbol] = round(stop_price, 2)
                    target_prices[symbol] = round(target_price, 2)

                    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 = int(portfolio_value * 0.02 / data.close)
                    if not shares_to_buy:
                        shares_to_buy = 1

                    buy_price = data.close
                    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] = {
                        "vwap_series":
                        vwap_series[-5:].tolist(),
                        "a_vwap_series":
                        a_vwap[-5:].tolist(),
                        "5-min-close":
                        close[-5:].tolist(),
                        "vwap":
                        data.vwap,
                        "avg":
                        data.average,
                        "volume":
                        minute_history["volume"][-5:].tolist(),
                        "slope_min":
                        slope_min,
                        "slope_a_vwap":
                        slope_a_vwap,
                        "volume_mean":
                        minute_history["volume"][lbound:].mean(),
                        "volume_std":
                        minute_history["volume"][lbound:].std(ddof=0, ),
                    }
                    self.buy_time[symbol] = now
                    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
                and last_used_strategy[symbol].name == self.name
                and not open_orders.get(symbol)):
            rsi = RSI(
                minute_history["close"].dropna().between_time("9:30", "16:00"),
                14,
            )
            a_vwap = anchored_vwap(minute_history, self.buy_time[symbol])
            sell_reasons = []
            to_sell = False
            if data.close <= stop_prices[symbol]:
                to_sell = True
                sell_reasons.append("stopped")
            elif data.close >= target_prices[symbol]:
                to_sell = True
                sell_reasons.append("above target")
            elif rsi[-1] > 79 and data.close > latest_cost_basis[symbol]:
                to_sell = True
                sell_reasons.append("RSI maxed")
            elif round(data.vwap, 2) >= round(data.average, 2):
                # to_sell = True
                sell_reasons.append("crossed above vwap")
            elif round(data.vwap, 2) < a_vwap[-1] * 0.99:
                to_sell = True
                sell_reasons.append("crossed below a-vwap")

            if to_sell:
                sell_indicators[symbol] = {
                    "vwap": data.vwap,
                    "avg": data.average,
                    "reasons": sell_reasons,
                    "rsi": rsi[-5:].round(2).tolist(),
                    "a_vwap": a_vwap[-5:].round(2).tolist(),
                }

                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",
                    },
                )

        return False, {}
예제 #7
0
    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]

        if (
            await super().is_buy_time(now)
            and not position
            and not open_orders.get(symbol, None)
            and data.volume > 1000
        ):
            close = (
                minute_history["close"].dropna().between_time("9:30", "16:00")
            )

            # 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
                min_15m = minute_history[lbound:ubound]["low"].min()  # type: ignore
            except Exception as e:
                tlog(
                    f"{symbol}[{now}] failed to aggregate {lbound}:{ubound} {minute_history}"
                )
                return False, {}

            if (high_15m - min_15m) / min_15m > 0.10:
                return False, {}

            macds = MACD(close, 13, 21)
            macd = macds[0]
            macd_signal = macds[1]
            macd_hist = macds[2]

            macd_above_signal = (
                macd[-2] > macd_signal[-2] and macd[-3] > macd_signal[-3]
            )
            hist_trending_down = macd_hist[-1] < macd_hist[-2] < macd_hist[-3]

            macd_treding_down = macd[-1] < macd[-2] < macd[-3]
            to_buy_short = False
            reason: List[str] = []
            if (
                macd_above_signal
                and hist_trending_down
                and macd_treding_down
                and macd[-2] > 0
                and macd[-1] > 0
                and data.close < data.open
                and data.vwap < data.open
            ):
                to_buy_short = True
                reason.append(
                    "MACD positive & above signal, MACD hist trending down, MACD trended down"
                )

            if to_buy_short:
                stop_price = data.close * 1.03
                target_price = data.average * 0.90

                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 * 10 // data.close
                )
                if not shares_to_buy:
                    shares_to_buy = 1

                buy_price = data.close
                tlog(
                    f"[{self.name}][{now}] Submitting buy short for {-shares_to_buy} shares of {symbol} at {buy_price} target {target_prices[symbol]} stop {stop_prices[symbol]}"
                )

                sell_indicators[symbol] = {
                    "reason": reason,
                    "macd": macd[-4:].tolist(),
                    "macd_signal": macd_signal[-4:].tolist(),
                    "hist_signal": macd_hist[-4:].tolist(),
                }

                return (
                    True,
                    {
                        "side": "sell",
                        "qty": str(-shares_to_buy),
                        "type": "market",
                    },
                )
        elif (
            await super().is_sell_time(now)
            and position
            and symbol in latest_cost_basis
            and last_used_strategy[symbol].name == self.name
            and not open_orders.get(symbol)
            and data.volume > 1000
        ):
            close = (
                minute_history["close"].dropna().between_time("9:30", "16:00")
            )
            macds = MACD(close, 13, 21)
            macd = macds[0]
            macd_signal = macds[1]
            macd_hist = macds[2]

            hist_change_trend = macd_hist[-2] > macd_hist[-3]
            below_signal = macd[-1] < macd_signal[-1]
            crossing_above_signal = (
                macd[-1] > macd_signal[-1] and macd_signal[-2] >= macd[-2]
            )
            to_sell = False
            reason = []
            if crossing_above_signal:
                to_sell = True
                reason.append("MACD crossing above signal")
            if (
                below_signal
                and hist_change_trend
                and data.close < latest_cost_basis[symbol]
                and macd[-1] > macd[-2]
            ):
                to_sell = True
                reason.append("reversing direction")
            elif data.close >= stop_prices[symbol]:
                to_sell = True
                reason.append("stopped")
            elif data.close <= target_prices[symbol]:
                reason.append("target reached")

            if to_sell:
                buy_indicators[symbol] = {
                    "close_5m": close[-5:].tolist(),
                    "reason": reason,
                }

                tlog(
                    f"[{self.name}][{now}] Submitting sell short for {position} shares of {symbol} at market {data.close} with reason:{reason}"
                )
                return (
                    True,
                    {
                        "side": "buy",
                        "qty": str(-position),
                        "type": "market",
                    },
                )

        return False, {}
예제 #8
0
    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)
                and data.volume > 500):
            close = (minute_history["close"].dropna().between_time(
                "9:30", "16:00"))

            # calc macd on 5 min
            close_5min = (minute_history["close"].dropna().between_time(
                "9:30", "16:00").resample("5min").last()).dropna()

            macds = MACD(close_5min, 13, 21)
            macd = macds[0]
            macd_signal = macds[1]

            # check if zero-crossing into negative, mark that point
            if macd[-1] < 0 <= macd[-2] and not self.down_cross.get(
                    symbol, None):
                self.down_cross[symbol] = data.close
                tlog(
                    f"{self.name}: [{now}]{symbol} identified down-ward zero-crossing of 5-min MACD w/ { self.down_cross[symbol]}"
                )
                return False, {}
            elif self.down_cross.get(symbol, None) and macd[-1] >= 0:
                self.down_cross[symbol] = None
                tlog(
                    f"{self.name}: [{now}]identified up-ward zero-crossing of 5-min MACD"
                )
                return False, {}

            to_buy = False
            reason = []
            # if passed zero crossing -> look for change in trend
            if (self.down_cross.get(symbol, None)
                    and macd[-1] > macd[-2] > macd[-3]
                    and macd[-1] > macd_signal[-1] > macd_signal[-2]
                    and macd[-2] > macd_signal[-2]):
                tlog(
                    f"{self.name}: [{now}]{symbol }identified up-ward trend {macd[-1]}, {macd[-2]}, {macd[-3]} price {data.close}"
                )

                # check if price actually went down and not up
                if data.close < self.down_cross[symbol]:
                    to_buy = True
                    reason.append("MACD signal")

            if 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 = data.close * 0.96
                target_price = self.down_cross[symbol] * 1.12
                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:
                    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,
                    }

                    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",
                        },
                    )
        elif (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,
            )

            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.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
                 (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 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_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]:
            #    to_sell = True
            #    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, {}
예제 #9
0
    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]:
        if not shortable:
            return False, {}

        data = minute_history.iloc[-1]

        if data.close > data.average:
            self.was_above_vwap[symbol] = True

        if (await super().is_buy_time(now) and not position
                and not open_orders.get(symbol, None)):
            if data.open > data.average:
                if debug:
                    tlog(f"{self.name} {symbol} trending up: {data}")
                return False, {}

            lbound = config.market_open.replace(second=0, microsecond=0)
            close = (minute_history["close"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").last()).dropna()
            open = (minute_history["open"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").first()).dropna()
            high = (minute_history["high"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").max()).dropna()
            low = (minute_history["low"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").min()).dropna()
            volume = (minute_history["volume"][lbound:].dropna().between_time(
                "9:30", "16:00").resample("5min").sum()).dropna()
            volume = volume[volume != 0]

            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}]{symbol} failed in add_daily_vwap")
                return False, {}

            vwap_series = df["average"]

            # calc macd on 5 min
            close_5min = (minute_history["close"].dropna().between_time(
                "9:30", "16:00").resample("5min").last()).dropna()

            if debug:
                tlog(
                    f"\n{tabulate(df[-10:], headers='keys', tablefmt='psql')}")
            macds = MACD(close_5min)
            macd = macds[0].round(3)
            macd_signal = macds[1].round(3)
            macd_hist = macds[2].round(3)
            vwap_series = vwap_series.round(3)
            close = close.round(3)
            if (self.was_above_vwap.get(symbol, False)
                    and close[-1] < vwap_series[-1]
                    and close[-2] < vwap_series[-2]
                    and close[-3] < vwap_series[-3] and close[-1] < open[-1]
                    and close[-2] < open[-2] and close[-3] < open[-3]
                    and macd[-1] < macd_signal[-1] < 0
                    # and macd[-1] < 0
                    and macd_hist[-1] < macd_hist[-2] < macd_hist[-3] < 0 and
                    data.close < data.open and data.close <
                    minute_history["close"][-2] < minute_history["close"][-3]):
                stops = find_supports(data.close, minute_history, now,
                                      StopRangeType.LAST_2_HOURS)

                if stops:
                    tlog(
                        f"[self.name]:{symbol}@{data.close} potential short-trap {stops}"
                    )
                    return False, {}

                tlog(
                    f"\n{tabulate(df[-10:], headers='keys', tablefmt='psql')}")

                stop_price = vwap_series[-1] * 1.005
                target_price = round(
                    min(
                        data.close - 10 * (stop_price - data.close),
                        data.close * 0.98,
                    ),
                    2,
                )

                stop_prices[symbol] = round(stop_price, 2)
                target_prices[symbol] = round(target_price, 2)

                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 = int(
                    max(
                        portfolio_value * config.risk //
                        (data.close - stop_prices[symbol]),
                        -portfolio_value * 0.02 // data.close,
                    ))
                if not shares_to_buy:
                    shares_to_buy = 1

                buy_price = data.close
                tlog(
                    f"[{self.name}][{now}] Submitting buy short for {-shares_to_buy} shares of {symbol} at {buy_price} target {target_prices[symbol]} stop {stop_prices[symbol]}"
                )
                sell_indicators[symbol] = {
                    "vwap_series": vwap_series[-5:].tolist(),
                    "5-min-close": close[-5:].tolist(),
                    "vwap": data.vwap,
                    "avg": data.average,
                    "volume": minute_history["volume"][-5:].tolist(),
                    "stops": [] if not stops else stops.tolist(),
                }
                self.volume_test_time[symbol] = now
                return (
                    True,
                    {
                        "side": "sell",
                        "qty": str(-shares_to_buy),
                        "type": "market",
                    },
                )

        if (await super().is_sell_time(now) and position
                and last_used_strategy[symbol].name == self.name
                and not open_orders.get(symbol)):
            volume = minute_history["volume"][
                self.volume_test_time[symbol]:].dropna()
            mu, std = norm.fit(volume)
            a_vwap = anchored_vwap(minute_history,
                                   self.volume_test_time[symbol])
            close_5min = (minute_history["close"].dropna().between_time(
                "9:30", "16:00").resample("5min").last()).dropna()
            to_sell: bool = False
            reason: str = ""

            macds = MACD(close_5min, 13, 21)
            macd = macds[0].round(2)
            macd_signal = macds[1].round(2)
            macd_hist = macds[2].round(2)
            close_5min = close_5min.round(2)
            movement = (data.close -
                        latest_cost_basis[symbol]) / latest_cost_basis[symbol]
            if (data.close >= stop_prices[symbol]
                    and macd[-1] > macd_signal[-1]):
                to_sell = True
                reason = "stopped"
            elif data.close <= target_prices[symbol]:
                to_sell = True
                reason = "target reached"
            elif (close_5min[-1] > close_5min[-2] > close_5min[-3] <
                  close_5min[-4] and data.close < latest_cost_basis[symbol]):
                to_sell = True
                reason = "reversing direction"
            elif (macd[-1] > macd_signal[-1]
                  and data.close < latest_cost_basis[symbol]):
                to_sell = True
                reason = "MACD changing trend"
            elif (0 > macd_hist[-4] > macd_hist[-3] < macd_hist[-2] <
                  macd_hist[-1] < 0
                  and data.close < latest_cost_basis[symbol]):
                to_sell = True
                reason = "MACD histogram trend reversal"
            elif (len(a_vwap) > 10 and minute_history.close[-1] > a_vwap[-2]
                  and minute_history.close[-2] > a_vwap[-2]):
                slope_min, intercept_min, _, _, _ = linregress(
                    range(10), minute_history.close[-10:])
                slope_a_vwap, intercept_a_vwap, _, _, _ = linregress(
                    range(10), a_vwap[-10:])

                if round(slope_min, 2) > round(slope_a_vwap, 2):
                    to_sell = True
                    reason = f"deviate from anchored-vwap {round(slope_min, 2)}>{round(slope_a_vwap, 2)}"

            # elif data.volume > mu + 2 * std and data.close > data.open and data.vwap > data.open:
            #    to_sell = True
            #    reason = "suspicious spike in volume, may be short-trap"

            if to_sell:
                buy_indicators[symbol] = {
                    "close_5m": close_5min[-5:].tolist(),
                    "movement": movement,
                    "reason": reason,
                    "volume": minute_history.volume[-5:].tolist(),
                    "volume fit": (mu, std),
                }

                tlog(
                    f"[{self.name}][{now}] Submitting sell short for {position} shares of {symbol} at market {data.close} with reason:{reason}"
                )
                return (
                    True,
                    {
                        "side": "buy",
                        "qty": str(-position),
                        "type": "market",
                    },
                )

        return False, {}
예제 #10
0
    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]:
        if not shortable:
            return False, {}

        data = minute_history.iloc[-1]

        if data.close > data.average:
            self.was_above_vwap[symbol] = True

        if (await super().is_buy_time(now) and not position
                and not open_orders.get(symbol, None)):
            day_start = ts(config.market_open)

            try:
                day_start_index = minute_history["close"].index.get_loc(
                    day_start, method="nearest")
            except Exception as e:
                tlog(
                    f"[ERROR]{self.name}[{now}]{symbol} can't load index for {day_start} w/ {e}"
                )
                return False, {}

            close = (minute_history["close"]
                     [day_start_index:-1].dropna().between_time(
                         "9:30", "16:00").resample("5min").last()).dropna()
            open = (minute_history["open"]
                    [day_start_index:-1].dropna().between_time(
                        "9:30", "16:00").resample("5min").first()).dropna()
            high = (minute_history["high"]
                    [day_start_index:-1].dropna().between_time(
                        "9:30", "16:00").resample("5min").max()).dropna()
            low = (minute_history["low"]
                   [day_start_index:-1].dropna().between_time(
                       "9:30", "16:00").resample("5min").min()).dropna()
            volume = (minute_history["volume"]
                      [day_start_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}]{symbol} failed in add_daily_vwap")
                return False, {}

            vwap_series = df["average"]

            if (data.close < vwap_series[-1] * 0.99
                    and self.was_above_vwap.get(symbol, False) and close[-1] <
                    open[-1] <= close[-2] < open[-2] <= close[-3] < open[-3]):

                stop_price = vwap_series[-1]
                target_price = data.close - 3 * (stop_price - data.close)

                stop_prices[symbol] = stop_price
                target_prices[symbol] = target_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

                buy_price = data.close
                tlog(
                    f"[{self.name}][{now}] Submitting buy short for {-shares_to_buy} shares of {symbol} at {buy_price} target {target_prices[symbol]} stop {stop_prices[symbol]}"
                )

                sell_indicators[symbol] = {
                    "vwap_series": vwap_series[-5:].tolist(),
                    "vwap": data.vwap,
                    "avg": data.average,
                }

                return (
                    True,
                    {
                        "side": "sell",
                        "qty": str(-shares_to_buy),
                        "type": "market",
                    },
                )

        if (await super().is_sell_time(now) and position
                and last_used_strategy[symbol].name == self.name
                and not open_orders.get(symbol)):
            day_start = ts(config.market_open)
            day_start_index = minute_history["close"].index.get_loc(
                day_start, method="nearest")
            close = (minute_history["close"]
                     [day_start_index:-1].dropna().between_time(
                         "9:30", "16:00").resample("5min").last()).dropna()
            to_sell: bool = False
            reason: str = ""

            if data.close >= stop_prices[symbol]:
                to_sell = True
                reason = "stopped"
            elif data.close <= target_prices[symbol]:
                to_sell = True
                reason = "target reached"
            elif close[-1] > close[-2] > close[-3] < close[-4]:
                to_sell = True
                reason = "reversing direction"

            if to_sell:
                buy_indicators[symbol] = {
                    "close_5m": close[-5:].tolist(),
                    "reason": reason,
                }

                tlog(
                    f"[{self.name}][{now}] Submitting sell short for {position} shares of {symbol} at market {data.close} with reason:{reason}"
                )
                return (
                    True,
                    {
                        "side": "buy",
                        "qty": str(-position),
                        "type": "market",
                    },
                )

        return False, {}
예제 #11
0
    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, {}
예제 #12
0
    async def run(
        self,
        symbol: str,
        shortable: bool,
        position: int,
        now: datetime,
        minute_history: df,
        portfolio_value: float = None,
        debug: bool = False,
        backtesting: bool = False,
    ) -> Tuple[bool, Dict]:
        data = minute_history.iloc[-1]
        mama, fama = MAMA(minute_history["close"])

        if mama[-1] > fama[-1]:
            buy_price = data.close
            stop_price = data.close * 0.98
            target_price = data.close * 1.05

            stop_prices[symbol] = stop_price
            target_prices[symbol] = target_price

            shares_to_buy = ((portfolio_value * config.risk //
                              (data.close - stop_prices[symbol]))
                             if portfolio_value else 0)
            if not shares_to_buy:
                shares_to_buy = 1

            buy_indicators[symbol] = {
                "mama": mama[-5:].tolist(),
                "fama": fama[-5:].tolist(),
            }

            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]}"
            )

            return True, {
                "side": "buy",
                "qty": str(shares_to_buy),
                "type": "limit",
                "limit_price": str(buy_price),
            }

        if (await self.is_sell_time(now) and position
                and last_used_strategy[symbol].name == self.name
                and not open_orders.get(symbol)):
            mama, fama = MAMA(minute_history["close"])

            to_sell: bool = False
            if data.close < stop_prices[symbol]:
                reason = "stopped"
                to_sell = True
            elif data.close >= target_prices[symbol]:
                reason = "target reached"
                to_sell = True
            elif mama[-1] < fama[-1]:
                reason = "fama below mama"
                to_sell = True

            if to_sell:
                tlog(
                    f"[{self.name}][{now}] Submitting sell for {position} shares of {symbol} at market {data.close} w reason {reason}"
                )

                sell_indicators[symbol] = {
                    "mama": mama[-5:].tolist(),
                    "fama": fama[-5:].tolist(),
                    "reason": reason,
                }

                return (
                    True,
                    {
                        "side": "sell",
                        "qty": str(position),
                        "type": "market",
                    },
                )

        return False, {}
예제 #13
0
    async def run(
        self,
        symbol: str,
        shortable: bool,
        position: int,
        now: datetime,
        minute_history: df = None,
        portfolio_value: float = None,
        trading_api: tradeapi = None,
        debug: bool = False,
        backtesting: bool = False,
    ) -> Tuple[bool, Dict]:
        current_price = (minute_history.close[-1] if minute_history is not None
                         else self.data_loader[symbol].open[now])
        # tlog(f"{now} {current_price}")
        if (await self.is_buy_time(now) and not position
                and not open_orders.get(symbol, None)
                and not await self.should_cool_down(symbol, now)):
            # Calculate 7 day Bolinger Band, w 1 std
            if minute_history is not None:
                self.resampled_close[symbol] = (minute_history.between_time(
                    "9:30", "16:00").close.resample("1D").last().dropna())
                # print(self.resampled_close[symbol])
            else:
                self.resampled_close[symbol] = (
                    self.data_loader[symbol].close[now - timedelta(
                        days=30):now]  # type: ignore
                    .between_time("9:30",
                                  "16:00").resample("1D").last().dropna())

            self.bband[symbol] = BBANDS(
                self.resampled_close[symbol],
                timeperiod=7,
                nbdevdn=1,
                nbdevup=1,
                matype=MA_Type.EMA,
            )

            # print(self.resampled_close[symbol])  # , self.bband[symbol])

            # if previous day finish below band,
            # and current day open above previous day close
            # and cross above band -> buy
            yesterday_lower_band = self.bband[symbol][2][-2]
            today_lower_band = self.bband[symbol][2][-1]
            yesterday_close = self.resampled_close[symbol][-2]

            # print(
            #    f"yesterday_lower_band:{yesterday_lower_band} today_lower_band:{today_lower_band} yesterday_close:{yesterday_close}"
            # )
            if minute_history is not None:
                start_day_index = minute_history.index.get_loc(
                    config.market_open.replace(second=0, microsecond=0),
                    method="nearest",
                )
                today_open = minute_history.iloc[start_day_index].open
                # print("today open:", today_open)
            else:
                today_open = self.data_loader[symbol].open[
                    config.market_open.replace(second=0, microsecond=0)]

            if (yesterday_close < yesterday_lower_band
                    and today_open > yesterday_close
                    and current_price > today_lower_band):
                # check if not sell signal not triggered too
                # (if price pops above upper-band -> sell)
                yesterday_upper_band = self.bband[symbol][0][-2]
                if current_price > yesterday_upper_band:
                    return False, {}

                print(
                    config.market_close.replace(second=0, microsecond=0) -
                    timedelta(days=1),
                    self.data_loader[symbol].close[
                        config.market_close.replace(second=0, microsecond=0) -
                        timedelta(days=1)],
                )
                print(
                    f"{now}-{yesterday_close}<{yesterday_lower_band} {today_open}>{yesterday_close} {current_price} > {today_lower_band}"
                )
                buy_indicators[symbol] = {
                    "lower_band": self.bband[symbol][2][-2:].tolist(),
                }
                shares_to_buy = self.amount // current_price
                tlog(
                    f"[{self.name}][{now}] Submitting buy for {shares_to_buy} shares of {symbol} at {current_price}"
                )
                tlog(f"indicators:{buy_indicators[symbol]}")
                return (
                    True,
                    {
                        "side": "buy",
                        "qty": str(shares_to_buy),
                        "type": "limit",
                        "limit_price": str(current_price),
                    },
                )

        if (await self.is_sell_time(now) and position
                and last_used_strategy[symbol].name == self.name
                and not open_orders.get(symbol)):
            # Calculate 7 day Bolinger Band, w 1 std
            if minute_history is not None:
                self.resampled_close[symbol] = (minute_history.between_time(
                    "9:30", "16:00").close.resample("1D").last().dropna())
            else:
                self.resampled_close[symbol] = (
                    self.data_loader[symbol].close[now - timedelta(
                        days=30):now]  # type: ignore
                    .between_time("9:30",
                                  "16:00").resample("1D").last().dropna())
            self.bband[symbol] = BBANDS(
                self.resampled_close[symbol],
                timeperiod=7,
                nbdevdn=1,
                nbdevup=1,
                matype=MA_Type.EMA,
            )

            # if price pops above upper-band -> sell
            yesterday_upper_band = self.bband[symbol][0][-2]
            # print(current_price, yesterday_upper_band)
            if current_price > yesterday_upper_band:
                sell_indicators[symbol] = {
                    "upper_band": self.bband[symbol][0][-2:].tolist(),
                }

                tlog(
                    f"[{self.name}][{now}] Submitting sell for {position} shares of {symbol} at market"
                )
                tlog(f"indicators:{sell_indicators[symbol]}")
                return (
                    True,
                    {
                        "side": "sell",
                        "qty": str(position),
                        "type": "market",
                    },
                )

        return False, {}
예제 #14
0
    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, {}
예제 #15
0
    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]:
        """

        :param symbol: the symbol of the stock,
        :param shortable: can the stock be sold short,
        :param position: the current held position,
        :param minute_history: DataFrame holding OLHC
                               updated per *second*,
        :param now: current timestamp, specially important when called
                    from the backtester application,
        :param portfolio_value: your total porfolio value
        :param trading_api: the Alpca tradeapi, may either be
                            paper or live, depending on the
                            environment variable configurations,
        :param debug:       true / false, should be used mostly
                            for adding more verbosity.
        :param backtesting: true / false, which more are we running at
        :return: False w/ {} dictionary, or True w/ order execution
                 details (see below examples)
        """
        current_second_data = minute_history.iloc[-1]
        tlog(f"{symbol} data: {current_second_data}")

        morning_rush = (
            True if (now - config.market_open).seconds // 60 < 30 else False
        )
        if await super().is_buy_time(now) and not position:
            # 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:
                return False, {}

            if (
                current_second_data.close > high_15m
                or config.bypass_market_schedule
            ):

                #
                # Global, cross strategies passed via the framework
                #
                target_prices[symbol] = 15.0
                stop_prices[symbol] = 3.8

                #
                # indicators *should* be filled
                #
                buy_indicators[symbol] = {"my_indicator": "random"}

                return (
                    True,
                    {
                        "side": "buy",
                        "qty": str(10),
                        "type": "limit",
                        "limit_price": "4.4",
                    }
                    if not morning_rush
                    else {
                        "side": "buy",
                        "qty": str(5),
                        "type": "market",
                    },
                )
        if (
            await super().is_sell_time(now)
            and position > 0
            and last_used_strategy[symbol].name == self.name  # important!
        ):
            # check if we already have open order
            if open_orders.get(symbol) is not None:
                tlog(f"{self.name}: open order for {symbol} exists, skipping")
                return False, {}

            # Check for liquidation signals
            sell_indicators[symbol] = {"my_indicator": "random"}

            tlog(
                f"[{self.name}] Submitting sell for {position} shares of {symbol} at {current_second_data.close}"
            )
            return (
                True,
                {
                    "side": "sell",
                    "qty": str(position),
                    "type": "limit",
                    "limit_price": str(current_second_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]
        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, {}
예제 #17
0
    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, {}