Exemple #1
0
def get_historical_data_from_poylgon_for_symbols(
    api: tradeapi, symbols: List[str], start_date: date, end_date: date
) -> Dict[str, df]:
    minute_history = {}
    for symbol in symbols:
        if symbol not in minute_history:
            minute_history[symbol] = api.polygon.historic_agg_v2(
                symbol,
                1,
                "minute",
                _from=start_date,
                to=end_date,
            ).df.tz_convert("US/Eastern")
            add_daily_vwap(minute_history[symbol])

    return minute_history
        async def backtest_symbol(
            symbol: str, scanner_start_time: datetime
        ) -> None:
            est = pytz.timezone("America/New_York")
            scanner_start_time = (
                pytz.utc.localize(scanner_start_time).astimezone(est)
                if scanner_start_time.tzinfo is None
                else scanner_start_time
            )
            start_time = pytz.utc.localize(start).astimezone(est)

            if scanner_start_time > start_time + duration:
                print(
                    f"{symbol} picked too late at {scanner_start_time} ({start_time}, {duration})"
                )
                return

            start_time = scanner_start_time
            if start_time.second > 0:
                start_time = start_time.replace(second=0, microsecond=0)
            print(
                f"--> back-testing {symbol} from {str(start_time)} duration {duration}"
            )
            if debug_symbols and symbol in debug_symbols:
                print("--> using DEBUG mode")

            re_try = 3

            while re_try > 0:
                # load historical data
                try:
                    symbol_data = data_api.polygon.historic_agg_v2(
                        symbol,
                        1,
                        "minute",
                        _from=str(start_time - timedelta(days=8)),
                        to=str(start_time + timedelta(days=1)),
                        limit=10000,
                    ).df
                except HTTPError as e:
                    tlog(f"Received HTTP error {e} for {symbol}")
                    return

                if len(symbol_data) < 100:
                    tlog(f"not enough data-points  for {symbol}")
                    return

                add_daily_vwap(
                    symbol_data,
                    debug=debug_symbols and symbol in debug_symbols,
                )
                market_data.minute_history[symbol] = symbol_data
                print(
                    f"loaded {len(market_data.minute_history[symbol].index)} agg data points"
                )

                position: int = 0
                try:
                    minute_index = symbol_data["close"].index.get_loc(
                        start_time, method="nearest"
                    )
                    break
                except (Exception, ValueError) as e:
                    print(f"[EXCEPTION] {e} - trying to reload-data. ")
                    re_try -= 1

            new_now = symbol_data.index[minute_index]
            print(f"start time with data {new_now}")
            price = 0.0
            last_run_id = None
            # start_time + duration
            while (
                new_now < config.market_close
                and minute_index < symbol_data.index.size - 1
            ):
                if symbol_data.index[minute_index] != new_now:
                    print(
                        "mismatch!", symbol_data.index[minute_index], new_now
                    )
                    print(
                        symbol_data["close"][
                            minute_index - 10 : minute_index + 1
                        ]
                    )
                    raise Exception()

                price = symbol_data["close"][minute_index]
                for strategy in trading_data.strategies:
                    if debug_symbols and symbol in debug_symbols:
                        print(
                            f"Execute strategy {strategy.name} on {symbol} at {new_now}"
                        )
                    do, what = await strategy.run(
                        symbol,
                        True,
                        position,
                        symbol_data[: minute_index + 1],
                        new_now,
                        portfolio_value,
                        debug=debug_symbols and symbol in debug_symbols,  # type: ignore
                        backtesting=True,
                    )
                    if do:
                        if (
                            what["side"] == "buy"
                            and float(what["qty"]) > 0
                            or what["side"] == "sell"
                            and float(what["qty"]) < 0
                        ):
                            position += int(float(what["qty"]))
                            trading_data.buy_time[symbol] = new_now.replace(
                                second=0, microsecond=0
                            )
                        else:
                            position -= int(float(what["qty"]))

                        trading_data.last_used_strategy[symbol] = strategy

                        db_trade = NewTrade(
                            algo_run_id=strategy.algo_run.run_id,
                            symbol=symbol,
                            qty=int(float(what["qty"])),
                            operation=what["side"],
                            price=price,
                            indicators=trading_data.buy_indicators[symbol]
                            if what["side"] == "buy"
                            else trading_data.sell_indicators[symbol],
                        )

                        await db_trade.save(
                            config.db_conn_pool,
                            str(new_now),
                            trading_data.stop_prices[symbol],
                            trading_data.target_prices[symbol],
                        )

                        if what["side"] == "buy":
                            await strategy.buy_callback(
                                symbol, price, int(float(what["qty"]))
                            )
                            break
                        elif what["side"] == "sell":
                            await strategy.sell_callback(
                                symbol, price, int(float(what["qty"]))
                            )
                            break
                    last_run_id = strategy.algo_run.run_id

                minute_index += 1
                new_now = symbol_data.index[minute_index]

            if position:
                if (
                    trading_data.last_used_strategy[symbol].type
                    == StrategyType.DAY_TRADE
                ):
                    tlog(
                        f"[{new_now}]{symbol} liquidate {position} at {price}"
                    )
                    db_trade = NewTrade(
                        algo_run_id=last_run_id,  # type: ignore
                        symbol=symbol,
                        qty=int(position)
                        if int(position) > 0
                        else -int(position),
                        operation="sell" if position > 0 else "buy",
                        price=price,
                        indicators={"liquidate": 1},
                    )
                    await db_trade.save(
                        config.db_conn_pool,
                        str(symbol_data.index[minute_index - 1]),
                    )
    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, {}
async def backtest_symbol(
    data_loader: DataLoader,
    portfolio_value: float,
    symbol: str,
    start: datetime,
    duration: timedelta,
    scanner_start_time: datetime,
    debug_symbol: bool = False,
) -> None:
    est = pytz.timezone("America/New_York")
    scanner_start_time = (pytz.utc.localize(scanner_start_time).astimezone(est)
                          if scanner_start_time.tzinfo is None else
                          scanner_start_time)
    start_time = pytz.utc.localize(start).astimezone(est)

    if scanner_start_time > start_time + duration:
        print(
            f"{symbol} picked too late at {scanner_start_time} ({start_time}, {duration})"
        )
        return

    start_time = scanner_start_time
    if start_time.second > 0:
        start_time = start_time.replace(second=0, microsecond=0)
    print(
        f"--> back-testing {symbol} from {str(start_time)} duration {duration}"
    )
    if debug_symbol:
        print("--> using DEBUG mode")

    symbol_data = pd.DataFrame(data_loader[symbol][start_time:start_time +
                                                   duration]  # type: ignore
                               )
    add_daily_vwap(
        symbol_data,
        debug=debug_symbol,
    )
    print(
        f"loaded {len(symbol_data)} agg data points({start_time}-{start_time + duration})"
    )

    minute_index = symbol_data["close"].index.get_loc(start_time,
                                                      method="nearest")

    position: int = 0
    new_now = symbol_data.index[minute_index]
    print(f"start time with data {new_now}")
    price = 0.0
    last_run_id = None

    # start_time + duration
    rejected: Dict[str, List] = {}
    while (new_now < config.market_close
           and minute_index < symbol_data.index.size - 1):
        if symbol_data.index[minute_index] != new_now:
            print("mismatch!", symbol_data.index[minute_index], new_now)
            print(symbol_data["close"][minute_index - 10:minute_index + 1])
            raise Exception()

        price = symbol_data["close"][minute_index]
        for strategy in trading_data.strategies:
            if debug_symbol:
                print(
                    f"Execute strategy {strategy.name} on {symbol} at {new_now}"
                )
            if symbol in rejected.get(strategy.name, []):
                continue

            try:
                do, what = await strategy.run(
                    symbol,
                    True,
                    position,
                    symbol_data[:minute_index + 1],
                    new_now,
                    portfolio_value,
                    debug=debug_symbol,  # type: ignore
                    backtesting=True,
                )
            except Exception as e:
                traceback.print_exc()
                tlog(
                    f"[ERROR] exception {e} on symbol {symbol} @ {strategy.name}"
                )
                continue

            if do:
                if (what["side"] == "buy" and float(what["qty"]) > 0
                        or what["side"] == "sell" and float(what["qty"]) < 0):
                    position += int(float(what["qty"]))
                    trading_data.buy_time[symbol] = new_now.replace(
                        second=0, microsecond=0)
                else:
                    position -= int(float(what["qty"]))

                trading_data.last_used_strategy[symbol] = strategy

                db_trade = NewTrade(
                    algo_run_id=strategy.algo_run.run_id,
                    symbol=symbol,
                    qty=int(float(what["qty"])),
                    operation=what["side"],
                    price=price,
                    indicators=trading_data.buy_indicators[symbol]
                    if what["side"] == "buy" else
                    trading_data.sell_indicators[symbol],
                )

                await db_trade.save(
                    config.db_conn_pool,
                    str(new_now),
                    trading_data.stop_prices[symbol],
                    trading_data.target_prices[symbol],
                )

                if what["side"] == "buy":
                    await strategy.buy_callback(symbol, price,
                                                int(float(what["qty"])))
                    break
                elif what["side"] == "sell":
                    await strategy.sell_callback(symbol, price,
                                                 int(float(what["qty"])))
                    break
            elif what.get("reject", False):
                if strategy.name in rejected:
                    rejected[strategy.name].append(symbol)
                else:
                    rejected[strategy.name] = [symbol]

            last_run_id = strategy.algo_run.run_id

        minute_index += 1
        new_now = symbol_data.index[minute_index]

    if position:
        if (trading_data.last_used_strategy[symbol].type ==
                StrategyType.DAY_TRADE):
            tlog(f"[{new_now}]{symbol} liquidate {position} at {price}")
            db_trade = NewTrade(
                algo_run_id=last_run_id,  # type: ignore
                symbol=symbol,
                qty=int(position) if int(position) > 0 else -int(position),
                operation="sell" if position > 0 else "buy",
                price=price,
                indicators={"liquidate": 1},
            )
            await db_trade.save(
                config.db_conn_pool,
                str(symbol_data.index[minute_index - 1]),
            )
Exemple #5
0
 async def update_vwap(self, symbol: str, now: datetime) -> None:
     if not self.data_loader[symbol][now].average:
         add_daily_vwap(self.data_loader[symbol])
    async def run(
        self,
        symbol: str,
        position: int,
        minute_history: df,
        now: datetime,
        portfolio_value: float = None,
        trading_api: tradeapi = None,
        debug: bool = False,
        backtesting: bool = False,
    ) -> Tuple[bool, Dict]:
        data = minute_history.iloc[-1]
        prev_minute = minute_history.iloc[-2]
        prev_2minutes = minute_history.iloc[-3]

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

            back_time = ts(config.market_open)
            back_time_index = minute_history["close"].index.get_loc(
                back_time, method="nearest")
            close = (minute_history["close"]
                     [back_time_index:-1].dropna().between_time(
                         "9:30", "16:00").resample("5min").last()).dropna()
            open = (minute_history["open"]
                    [back_time_index:-1].dropna().between_time(
                        "9:30", "16:00").resample("5min").first()).dropna()
            high = (minute_history["high"]
                    [back_time_index:-1].dropna().between_time(
                        "9:30", "16:00").resample("5min").max()).dropna()
            low = (minute_history["low"]
                   [back_time_index:-1].dropna().between_time(
                       "9:30", "16:00").resample("5min").min()).dropna()
            volume = (minute_history["volume"]
                      [back_time_index:-1].dropna().between_time(
                          "9:30", "16:00").resample("5min").sum()).dropna()

            _df = concat(
                [
                    open.rename("open"),
                    high.rename("high"),
                    low.rename("low"),
                    close.rename("close"),
                    volume.rename("volume"),
                ],
                axis=1,
            )
            if not add_daily_vwap(_df):
                tlog(f"[{now}]failed add_daily_vwap")
                return False, {}

            if debug:
                tlog(
                    f"\n[{now}]{symbol} {tabulate(_df[-10:], headers='keys', tablefmt='psql')}"
                )
            vwap_series = _df["average"]

            if debug:
                tlog(
                    f"[{now}] {symbol} close:{round(data.close,2)} vwap:{round(vwap_series[-1],2)}"
                )

            if len(vwap_series) < 3:
                tlog(f"[{now}]{symbol}: missing vwap values {vwap_series}")
                return False, {}

            if close[-2] > vwap_series[-2] and close[-1] < vwap_series[-1]:
                down_cross[symbol] = vwap_series.index[-1].to_pydatetime()
                tlog(
                    f"[{now}] {symbol} down-crossing on 5-min bars at {down_cross[symbol]}"
                )
                return False, {}

            if (close[-2] > vwap_series[-2] and close[-3] < vwap_series[-3]
                    and data.close > prev_minute.close
                    and data.close > data.average):
                if not symbol in down_cross:
                    tlog(
                        f"[{now}] {symbol} did not find download crossing in the past 15 min"
                    )
                    return False, {}
                if minute_history.index[-1].to_pydatetime(
                ) - down_cross[symbol] > timedelta(minutes=30):
                    tlog(
                        f"[{now}] {symbol} down-crossing too far {down_cross[symbol]} from now"
                    )
                    return False, {}
                stop_price = find_stop(
                    data.close if not data.vwap else data.vwap,
                    minute_history,
                    now,
                )
                target = data.close + 0.05

                stop_prices[symbol] = stop_price
                target_prices[symbol] = target

                tlog(
                    f"{symbol} found conditions for VWAP-Scalp strategy now:{now}"
                )

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

                if portfolio_value is None:
                    if trading_api:
                        portfolio_value = float(
                            trading_api.get_account().portfolio_value)
                    else:
                        raise Exception(
                            "VWAPLong.run(): both portfolio_value and trading_api can't be None"
                        )

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

                if shares_to_buy > 0:
                    tlog(
                        f"[{self.name}] Submitting buy for {shares_to_buy} shares of {symbol} at {data.close} target {target_prices[symbol]} stop {stop_prices[symbol]}"
                    )
                    buy_indicators[symbol] = {
                        "average": round(data.average, 2),
                        "vwap": round(data.vwap, 2),
                        "patterns": None,
                    }

                    return (
                        True,
                        {
                            "side": "buy",
                            "qty": str(shares_to_buy),
                            "type": "limit",
                            "limit_price": str(data.close + 0.01),
                        },
                    )

        elif (await super().is_sell_time(now) and position > 0
              and symbol in latest_cost_basis
              and last_used_strategy[symbol].name == self.name):
            if open_orders.get(symbol) is not None:
                tlog(f"vwap_scalp: open order for {symbol} exists, skipping")
                return False, {}

            to_sell = False
            to_sell_market = False
            if data.vwap <= data.average - 0.05:
                to_sell = True
                reason = "below VWAP"
                to_sell_market = True
            elif data.close >= target_prices[symbol]:
                to_sell = True
                reason = "vwap scalp"
            elif data.close >= target_prices[symbol]:
                to_sell = True
                reason = "vwap scalp"
            elif (prev_minute.close < prev_minute.open
                  and data.close < data.open):
                to_sell = True
                reason = "vwap scalp no bears"

            if to_sell:
                sell_indicators[symbol] = {
                    "reason": reason,
                    "average": data.average,
                    "vwap": data.vwap,
                }
                return (
                    True,
                    {
                        "side": "sell",
                        "qty": str(position),
                        "type": "market"
                    } if to_sell_market else {
                        "side": "sell",
                        "qty": str(position),
                        "type": "limit",
                        "limit_price": str(data.close),
                    },
                )

        return False, {}
    async def run(
        self,
        symbol: str,
        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, {}
Exemple #8
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, {}
Exemple #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, {}