Back to Community
Long-only non-day trading algorithm for live

This is a modified version of the algorithm presented in https://www.quantopian.com/posts/robinhood-based-non-day-trading-algo-yes-i-can-still-trade-on-robinhood

I have run the backtest for the last year and confirmed it still continues to perform well. In addition to that, I started to run this algo connecting to a broker using the new open source library pylivetrader.

https://github.com/alpacahq/pylivetrader/

pylivetrader is a(nother) zipline-API-compatible trading framework in python which focuses on live
trading, with less overhead and fewer dependency problems. It is written from the ground up for live trading use cases, so it removes the requirements for a data bundle that was a heavy task for average users here. Data is instead retrieved from your broker or remote data provider (backend). At the moment, the only supported backend is Alpaca*, but we are happy to connect to IB etc. if someone contributes the code.

You can download the working example that converted this presented algo to the live version here.

https://github.com/alpacahq/pylivetrader/tree/master/examples/q01

I also assembled some guidelines for converting Quantopian algorithms to run in live trading more generally.

https://github.com/alpacahq/pylivetrader/blob/master/migration.md

It’s a great community effort underway with knowledge and insights from everyone greatly appreciated in this forum, to enable algorithms in live trading independently. Again, this effort is going to be a continuous work and I'll keep improving both software and documentations, so please feel free to give me any questions or feedback.

* Brokerage services are provided to self-directed customers by Alpaca Securities LLC ("Alpaca"), member FINRA/SIPC. Alpaca is a wholly-owned subsidiary of AlpacaDB, Inc.

Clone Algorithm
1029
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from quantopian.pipeline import Pipeline
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume
from quantopian.pipeline.filters.morningstar import IsPrimaryShare

import numpy as np  # needed for NaN handling
import math  # ceil and floor are useful for rounding

from itertools import cycle


def initialize(context):
    # set_commission(commission.PerShare(cost=0.01, min_trade_cost=1.50))
    set_slippage(
        slippage.VolumeShareSlippage(
            volume_limit=.20,
            price_impact=0.0))
    # set_slippage(slippage.FixedSlippage(spread=0.00))
    set_commission(commission.PerTrade(cost=0.00))
    # set_slippage(slippage.FixedSlippage(spread=0.00))
    set_long_only()

    context.MaxCandidates = 100
    context.MaxBuyOrdersAtOnce = 30
    context.MyLeastPrice = 3.00
    context.MyMostPrice = 25.00
    context.MyFireSalePrice = context.MyLeastPrice
    context.MyFireSaleAge = 6

    # over simplistic tracking of position age
    context.age = {}
    print len(context.portfolio.positions)

    # Rebalance
    EveryThisManyMinutes = 10
    TradingDayHours = 6.5
    TradingDayMinutes = int(TradingDayHours * 60)
    for minutez in xrange(
        1,
        TradingDayMinutes,
        EveryThisManyMinutes
    ):
        schedule_function(
            my_rebalance,
            date_rules.every_day(),
            time_rules.market_open(
                minutes=minutez))

    # Prevent excessive logging of canceled orders at market close.
    schedule_function(
        cancel_open_orders,
        date_rules.every_day(),
        time_rules.market_close(
            hours=0,
            minutes=1))

    # Record variables at the end of each day.
    schedule_function(
        my_record_vars,
        date_rules.every_day(),
        time_rules.market_close())

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline(context)
    attach_pipeline(my_pipe, 'my_pipeline')


def make_pipeline(context):
    """
    Create our pipeline.
    """

    # Filter for primary share equities. IsPrimaryShare is a built-in filter.
    primary_share = IsPrimaryShare()

    # Equities listed as common stock (as opposed to, say, preferred stock).
    # 'ST00000001' indicates common stock.
    common_stock = morningstar.share_class_reference.security_type.latest.eq(
        'ST00000001')

    # Non-depositary receipts. Recall that the ~ operator inverts filters,
    # turning Trues into Falses and vice versa
    not_depositary = ~morningstar.share_class_reference.is_depositary_receipt.latest

    # Equities not trading over-the-counter.
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith(
        'OTC')

    # Not when-issued equities.
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')

    # Equities without LP in their name, .matches does a match using a regular
    # expression
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches(
        '.* L[. ]?P.?$')

    # Equities with a null value in the limited_partnership Morningstar
    # fundamental field.
    not_lp_balance_sheet = morningstar.balance_sheet.limited_partnership.latest.isnull()

    # Equities whose most recent Morningstar market cap is not null have
    # fundamental data and therefore are not ETFs.
    have_market_cap = morningstar.valuation.market_cap.latest.notnull()

    # At least a certain price
    price = USEquityPricing.close.latest
    AtLeastPrice = (price >= context.MyLeastPrice)
    AtMostPrice = (price <= context.MyMostPrice)

    # Filter for stocks that pass all of our previous filters.
    tradeable_stocks = (
        primary_share
        & common_stock
        & not_depositary
        & not_otc
        & not_wi
        & not_lp_name
        & not_lp_balance_sheet
        & have_market_cap
        & AtLeastPrice
        & AtMostPrice
    )

    LowVar = 6
    HighVar = 40

    log.info(
        '''
Algorithm initialized variables:
 context.MaxCandidates %s
 LowVar %s
 HighVar %s''' %
        (context.MaxCandidates, LowVar, HighVar))

    # High dollar volume filter.
    base_universe = AverageDollarVolume(
        window_length=20,
        mask=tradeable_stocks
    ).percentile_between(LowVar, HighVar)

    # Short close price average.
    ShortAvg = SimpleMovingAverage(
        inputs=[USEquityPricing.close],
        window_length=3,
        mask=base_universe
    )

    # Long close price average.
    LongAvg = SimpleMovingAverage(
        inputs=[USEquityPricing.close],
        window_length=45,
        mask=base_universe
    )

    percent_difference = (ShortAvg - LongAvg) / LongAvg

    # Filter to select securities to long.
    stocks_worst = percent_difference.bottom(context.MaxCandidates)
    securities_to_trade = (stocks_worst)

    return Pipeline(
        columns={
            'stocks_worst': stocks_worst
        },
        screen=(securities_to_trade),
    )


def my_compute_weights(context):
    """
    Compute ordering weights.
    """
    # Compute even target weights for our long positions and short positions.
    stocks_worst_weight = 1.00 / len(context.stocks_worst)

    return stocks_worst_weight


def before_trading_start(context, data):
    # Gets our pipeline output every day.
    context.output = pipeline_output('my_pipeline')

    context.stocks_worst = context.output[
        context.output['stocks_worst']].index.tolist()

    context.stocks_worst_weight = my_compute_weights(context)

    context.MyCandidate = cycle(context.stocks_worst)

    context.LowestPrice = context.MyLeastPrice  # reset beginning of day
    print len(context.portfolio.positions)
    for stock in context.portfolio.positions:
        CurrPrice = float(data.current([stock], 'price'))
        if CurrPrice < context.LowestPrice:
            context.LowestPrice = CurrPrice
        if stock in context.age:
            context.age[stock] += 1
        else:
            context.age[stock] = 1
    for stock in context.age:
        if stock not in context.portfolio.positions:
            context.age[stock] = 0
        message = 'stock.symbol: {symbol}  :  age: {age}'
        log.info(message.format(symbol=stock.symbol, age=context.age[stock]))

    pass


def my_rebalance(context, data):
    BuyFactor = .99
    SellFactor = 1.01
    cash = context.portfolio.cash

    cancel_open_buy_orders(context, data)

    # Order sell at profit target in hope that somebody actually buys it
    for stock in context.portfolio.positions:
        if not get_open_orders(stock):
            StockShares = context.portfolio.positions[stock].amount
            CurrPrice = float(data.current([stock], 'price'))
            CostBasis = float(context.portfolio.positions[stock].cost_basis)
            SellPrice = float(
                make_div_by_05(
                    CostBasis *
                    SellFactor,
                    buy=False))

            if np.isnan(SellPrice):
                pass  # probably best to wait until nan goes away
            elif (stock in context.age and context.age[stock] == 1):
                pass
            elif (
                stock in context.age
                and context.MyFireSaleAge <= context.age[stock]
                and (
                    context.MyFireSalePrice > CurrPrice
                    or CostBasis > CurrPrice
                )
            ):
                if (stock in context.age and context.age[stock] < 2):
                    pass
                elif stock not in context.age:
                    context.age[stock] = 1
                else:
                    SellPrice = float(
                        make_div_by_05(.95 * CurrPrice, buy=False))
                    order(stock, -StockShares,
                          style=LimitOrder(SellPrice)
                          )
            else:
                if (stock in context.age and context.age[stock] < 2):
                    pass
                elif stock not in context.age:
                    context.age[stock] = 1
                else:

                    order(stock, -StockShares,
                          style=LimitOrder(SellPrice)
                          )

    WeightThisBuyOrder = float(1.00 / context.MaxBuyOrdersAtOnce)
    for ThisBuyOrder in range(context.MaxBuyOrdersAtOnce):
        stock = context.MyCandidate.next()
        PH = data.history([stock], 'price', 20, '1d')
        PH_Avg = float(PH.mean())
        CurrPrice = float(data.current([stock], 'price'))
        if np.isnan(CurrPrice):
            pass  # probably best to wait until nan goes away
        else:
            if CurrPrice > float(1.25 * PH_Avg):
                BuyPrice = float(CurrPrice)
            else:
                BuyPrice = float(CurrPrice * BuyFactor)
            BuyPrice = float(make_div_by_05(BuyPrice, buy=True))
            StockShares = int(WeightThisBuyOrder * cash / BuyPrice)
            order(stock, StockShares,
                  style=LimitOrder(BuyPrice)
                  )

# if cents not divisible by .05, round down if buy, round up if sell


def make_div_by_05(s, buy=False):
    s *= 20.00
    s = math.floor(s) if buy else math.ceil(s)
    s /= 20.00
    return s


def my_record_vars(context, data):
    """
    Record variables at the end of each day.
    """

    # Record our variables.
    record(leverage=context.account.leverage)
    record(positions=len(context.portfolio.positions))
    if 0 < len(context.age):
        MaxAge = context.age[max(
            context.age.keys(), key=(lambda k: context.age[k]))]
        print MaxAge
        record(MaxAge=MaxAge)
    record(LowestPrice=context.LowestPrice)


def log_open_order(StockToLog):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        if stock == StockToLog:
            for o in orders:
                message = 'Found open order for {amount} shares in {stock}'
                log.info(message.format(amount=o.amount, stock=stock))


def log_open_orders():
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for o in orders:
            message = 'Found open order for {amount} shares in {stock}'
            log.info(message.format(amount=o.amount, stock=stock))


def cancel_open_buy_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for o in orders:
            # message = 'Canceling order of {amount} shares in {stock}'
            # log.info(message.format(amount=o.amount, stock=stock))
            if 0 < o.amount:  # it is a buy order
                cancel_order(o)


def cancel_open_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for o in orders:
            # message = 'Canceling order of {amount} shares in {stock}'
            # log.info(message.format(amount=o.amount, stock=stock))
            cancel_order(o)

# This is the every minute stuff


def handle_data(context, data):
    pass
There was a runtime error.
73 responses

Thank you for sharing! Is there a way to hook this up to Robinhood/Interactive Brokers? I didn't see any of those brokers in your examples.

@Tyler,

Thanks for asking. It has a plugin point to hook up with other API than Alpaca, but we haven't implemented Robinhood or IB yet. We'll be busy with improving its core functionality for the time being, but happy to accept other contribution to add code.

It's a miracle, the birth of an alternative to Robinhood on the side, Alpaca.

@Blue Seahawk Thanks!

@Blue Seahawk Thanks!
the pylivetrader Pipeline support morningstar ?

Pipeline API
This is the API under the quantopian.pipeline or zipline.pipeline package. pylivetrader does not provide direct replacement for pipeline, but you can use pipeline-live. You need to install this package separately as pylivetrader does not automatically include it. For more information, please read pipeline-live's migration document.

Under attach_pipeline and pipeline_output, pylivetrader uses the pipeline-live pakcage if it is installed, otherwise throws RuntimeError.

@Hitoshi - Quantopian doesn't really properly keep track of or enforce "buying power" limitations. The Q backtest makes no indication if you've placed orders for more than you have cash in your account -- it just lets you do it. The "leverage" variable doesn't factor in said "cash held for orders." There doesn't seem to be anything in this algorithm preventing it from placing orders with more than the actually available cash. So backtest results are probably inflated at least a bit due to this. In addition, it selects stocks from as low as the 6% lowest dollar volume stocks, which I believe is quite low volume. That combined with the fact that it orders in odd lots, I'd be surprised if it would perform well in real life. I'd expect significantly worse fills than the Q slippage model would suggest.

I'd be curious to see a side-by-side of Quantopian's simulation and a real live run.

@Nelson for morningstar support, we are discussing actively. There is no public API that someone can use by just signing up so that's the hardest point.

However, we also found that you can duplicate many things using default IEX without morningstar. The some of the fields are completely replaceable with IEX such as marketcap. Please also check out the migration example as well.

@Virdian Great point. I should admit I just copied the original post algo and reformatted without thinking much about leverage and odd lots. There should be some difference in the results between backtesting and real live testing, but I overheard also that some folks (more than one) were running this type of algo with Robinhood and performed quite well. I guess, one thing to note regarding the odd lot is that while it is true that odd lots might slip more than Quantopian backtesting simulation, the slippage may not be a biggest factor to the performance either. Especially, this algo doesn't aim day trading nor high frequent trading (though still notional size can be big), finding those potential spikes is the main driver of the performance here than perfecting slippage. I have been running it the broker paper trading for a week or so, and confirmed things like ALT were in the position (among many other stocks) that contributed a lot to the overall performance. I would say this type of universe, with many low dollar volume stocks, often work well for individual traders but not for large institutional asset managers.

Do you know if this trading algorithm requires a margin account? I was wondering what the purpose of holding for 1 day was, in other words how does it reduce risk?

This model runs with higher margin, but it does not require a margin account at all. It's just the performance may be lower as the re-investment is smaller.

Quick question for you Hitoshi. The title here says "non-day trading algorithm". It seems to be placing trades often enough, and turning over very frequently. What is the logic in the code that prevents the algorithm from fitting the definition of a "Pattern Day Trader" by Alpaca (https://support.alpaca.markets/hc/en-us/articles/360012203032-Pattern-Day-Trader) or FINRA (http://www.finra.org/investors/day-trading-margin-requirements-know-rules)?

[EDIT: oops, Does not rebalance once a day]

Just taking a cursory look, it would appear the context.age[stock] < 2check prevents it from selling stocks until they have been held for more than 2 days. I believe context.MyFireSaleAgeforces a sale after the stock has been held for more than 6 days, so you don't end up holding losing positions forever.

Thanks Viridian, yes that's exactly what I was going to write to Tanay!

I read too far into the "Rebalance":

 Rebalance  
    EveryThisManyMinutes = 10  
    TradingDayHours = 6.5  
    TradingDayMinutes = int(TradingDayHours * 60)  
    for minutez in xrange(  
        1,  
        TradingDayMinutes,  
        EveryThisManyMinutes  
    ):  
        schedule_function(  
            my_rebalance,  
            date_rules.every_day(),  
            time_rules.market_open(  
                minutes=minutez))  

It rescreens quite often but has different securities it adds through out the day if I understand correctly?

@ Hitoshi Harada, my apologies if you've already answered this question, but has this algorithm been tested on a live account?

Yes it keeps buying slowly a few securities throughout the day, and those are screened by the conditions defined in pipeline. So the "candidates" are consistent during the day but the buy logic go through the list of today's candidates and pick up a few every time "rebalance" runs.

That is Awesome! I've been working on a few side projects over the course of my career, but this takes the cake on the AWESOME scale. I would love to contribute to this project, is there anything in particular that you could use some help with?

It's been running in live and doing well for a few weeks now and it's doing what it is expected to do, while we have not compared the actual results between backtesting and live trading as we know there are divergence here like margin leverage, order filling and so on.

Thanks for the great interest! There are couple of issues waiting for fix/improvement in github, if you are interested in it.

https://github.com/alpacahq/pylivetrader/issues

More than anything, your help to get words out is very much appreciated!

Thanks Hitoshi, I'll take a look at the issues on github & help any way I can.

This is amazing.

Can you get fundamental data through Alpaca?

I'd be interested in seeing how this algorithm does in a bear market (as we may be entering one).

Does anybody on this thread have an account with alpaca? I’m on the waiting list.

@Peter thanks for the comment! You can get a basic fundamental data through Polygon API https://polygon.io/docs/ Also if you look at pipline-live tutorial, you can see IEX fundamental is also useful to some extent. https://github.com/alpacahq/pipeline-live

@Hitoshi, what broker & portfolio balance did you start with for the live trading run? Also, do you know of any brokers that could integrate with the algo that doesn’t have a minimum balance?

I meant to say brokers that don’t require a minimum balance. For example interactive brokers, they require a minimum balance of 10k, which is out of my reach.

I am running it with Alpaca, which does not have minimum balance.

Hi hitoshi, I'm just a beginner here, My apologies if this question is really stupid...
so I saw your code
" context.MaxCandidates = 100
context.MaxBuyOrdersAtOnce = 30
context.MyLeastPrice = 3.00
context.MyMostPrice = 25.00
context.MyFireSalePrice = context.MyLeastPrice
context.MyFireSaleAge = 6''

but i did not find any instruction on API, are these from the basic python functions?

Great work and thank you for sharing! What code would be needed to trade on Robinhood?

Guys this algo is not going to work, I ran something similar a while back. You're about to see why real trading != simulated trading.

@Trade Goat can you be more specific.
Also isn't Hitoshi running this on Alpaca right now? How is it performing there?

@trade goat, he’s already seeing great results in live.

@hunt cain, do you code bro?

@hitoshi, how’d you get an open account with alpacca? I’ve been on their waiting list for a while now.

@Bob, @Josh, The ability to get filled in these low volume stocks is very hard to simulate and will not match up with quantopian's results at all. I traded a very similar strat for about a month or two with a similar universe (6+ sharpe in backtests) and ended up with a wash.

Joshua, Not really..... I mostly copy and paste aspects that I want or like and make them fit as best as possible. I am hoping to go back to previous post about RH implementation and see if I can get it to work.

@hunt cain, if your interested in using Algos then I would say that your best investment would be in learning some programming fundamentals first, learning some python would be very beneficial to your objective, if it is to delploy algos in a live trading environment. Anyone can learn to code, if you need resources just let me know & I'll gladly point you in the right direction.

I got an email saying alpaca markets is live. However I don't find the company in the sec edgar. Is this firmed registered? I normally do this check before I put in my social security in,

Alpaca Securities is a registered broker dealer under SEC/FINRA https://brokercheck.finra.org/firm/summary/288202

Hi Hitoshi, I'm having trouble running the pylivetrader command from my terminal. I should mention that I had to install with pip3 instead of pip - could that be the issue? Maybe if you just told me the path to add that would solve it?

pylivetrader currently requires python 3, and depending on your environment, you may need to use pip3 not pip. Python environment setup is different from one to another, and I cannot tell exact answer without knowing it. If on windows, there is a tutorial https://docs.alpaca.markets/libraries/pylivetrader-windows/ and also there is docker setup sample https://github.com/alpacahq/pylivetrader/tree/master/examples/heroku-dockerfile

i did install with pip3 and i'm running python3. however (as a note) the instruction at https://github.com/alpacahq/pylivetrader says to run $ pip install pylivetrader, maybe they need to update that instruction. Of course that didn't work and I ran as $ pip3 install pylivetrader, successfully.... But now I can't run: $ pylivetrader run
-bash: pylivetrader: command not found

In some environments including mine, pip is fine. I know there is other environments where you need to say pip3. It's python's problem with this confusing command line names and almost nothing has to do with pylivetrader, but we may be able to add the note in the doc. Thanks for suggestion.. That said, I am not sure why you cannot find pylivetrader command. I can find it after installing using pip3 in my other environment.

$ docker run -it --rm python bash
[email protected]:/# pylivetrader  
bash: pylivetrader: command not found  
[email protected]:/# pip3 install pylivetrader  
[email protected]:/# pylivetrader  
Usage: pylivetrader [OPTIONS] COMMAND [ARGS]...

Options:  
  --help  Show this message and exit.

Commands:  
  run  
  shell  
  version  
...

ok HItoshi thanks for your help. I'll try again soon. Maybe if I understood how to deploy docker I would just do that but I'm not familiar with containers.

I was wondering if anyone could help me....? I am trying to limit my stock selection the members of the Russel 2000. I've reviewed numerous ways but cannot seem to get it integrated. Thank you.

Would this algo run in Alpaca already? or would i need to convert it?

@Brandon Egger There is a tutorial about how to convert the algo into something runnable with Alpaca API. https://github.com/alpacahq/pylivetrader/tree/master/examples/q01

Very interesting, thank you. But the algo does not run on a paper trade only account.

@carles this algo is only for real-time live trading on alpaca, correct?

I would be very skeptical of this algorithm producing real-world results. If you're going to trade it, I'd recommend starting with a very very small amount of capital. If you do, it would be nice to see a side-by-side comparison of real world results and a backtest over the same period.

@Brandon It can be tested here and there is a port for alpaca live trading, although it might only work if you have a brokerage account with them, if you have only a paper trade account it does not work because it uses polygon data source. I've made a small function to try circumvent this limitation but I just get a void list of stocks after pipeline creation... My isPrimaryShare:

###########################

import pandas as pd  
import io  
from ftplib import FTP  
from io import StringIO

nasdaq_domain="ftp.nasdaqtrader.com"  
nasdaq_uri="/symboldirectory/nasdaqlisted.txt"  
nyse_uri="/symboldirectory/otherlisted.txt"

def read_remote_csv(uri):  
    ftp = FTP(nasdaq_domain)  
    ftp.login()  
    lines = []  
    ftp.retrlines("RETR " + uri, lines.append)  
    text = '\n'.join(lines)

    return pd.read_csv(io.StringIO(text), sep='|')

def isPrimaryShare():  
    nasdaq_listed = read_remote_csv(nasdaq_uri)  
    nyse_listed = read_remote_csv(nyse_uri)  
    symbols = []

    #log.info(nasdaq_listed)

    for index, row in nasdaq_listed.iterrows():  
        if row['Symbol'] is not None and row['Financial Status'] == 'N' and row['ETF'] == 'N' and row['NextShares'] == 'N':  
            try:  
                sym = symbol(row['Symbol'])  
                symbols.append(sym)  
            except:  
                pass

    #log.info("Symbols: %s" % (len(symbols)))  
    #log.info(nyse_listed)

    for index, row in nyse_listed.iterrows():  
        if row['ACT Symbol'] is not None and row['Exchange'] == 'N' and row['Test Issue'] == 'N' and row['ETF'] == 'N':  
            try:  
                sym = symbol(row['ACT Symbol'])  
                symbols.append(sym)  
            except:  
                pass

    log.info("Symbols: %s" % (len(symbols)))

    return StaticAssets(symbols)

############  

@Viridian I'm not planning in putting real money in this algo, at most, if it worked on paper I would test it with a very small amount of cash then, in my opinion it profits from small caps spread to make its earnings, a thing that would be very difficult to replicate in real life. But I do want to test some algos from quantopian on paper and since this one is almost done, it is my first attempt.

@Carles Estevadeordal @Brandon Egger if you are interested in running the algo not having a live trading account in Alpaca (so you can not use Polygon) you might want to have a look at this example

Beginner / trivial question: I am new to python and I am curious how stocks are added to the context.age dictionary. There doesn't seem to be a line of code where the stock is added to the dictionary. Thanks.

@marc I have a live trading account, would this algo work as-is?

@Brandon Egger if you have a live trading account it will work either in live or paper trading as a live trading account can use the Polygon API (that is used in the alpaca.py backend).

@Marc so I can use source code as-is hypothetically?

Thank you @Marc!

Can anyone explain the hypothesis of this algorithm?

I'm not an expert at Python but from what I can tell, it takes low dollar volume stocks and buys the stocks that have the largest negative percentage difference between the 45 day MA and 3 day MA(Therefore stocks that are trading downward). Of the stocks that it purchases, the algorithm tries to make a profit of one percent off each of them.

Can anyone explain why this algorithm works? Does this strategy work better with low dollar volume stocks than with high? Is this a mean reversion strategy?

It is a mean reversion strategy, but that has nothing to do with why it backtests well. It fairs well in backtesting becuase the backtester can't model limit orders with an exact degree of certainty. Its fudge factor is more forgiving than the actual market, and so it fills orders that realistically would not have gotten filled during live trading. Try placing a limit order 1% in the profiting direction deviating from the last price on a low volume, high bid/ask spread-stock using your brokerage of choice. You'll get indeterminate results where some may fill but most won't. What fairs well in this example is not actual Alpha but instead, meta spoofing of sorts - being able to 'fool' the backtester into filling . . . instead of backtest filling based off of recorded historic fills. Maybe this is a case for backtesters doing ML on limit orders?

How can I set the leverage of the Algorithm to zero? I mean I want to backtest the algorithm without leverage.

@Harkishan 0x leverage would mean having $0 invested. 1x leverage means having your entire cash balance invested (zero margin). I think you mean 1x leverage.

@Viridian Yes. I meant to trade without any margin. I am thinking to live trade the algorithm. But since I am not a US citizen I can't open an account with Robinhood or Alpaca, also Interactive Brokers doesn't provide margin trading facilities for me. By the way, the algorithm doesn't exceed 1x leverage. So, that does mean that I can trade the algorithm without worrying of the Margin Requirement.

If you're going to trade with IB, you'll want to change the commission settings on the backtest to match IB's commission rate to get a sense for whether it can survive. (Also, I'm skeptical that this strategy will work regardless of fees. You can't trust backtests, especially ones that use limit orders on illiquid securities.)

@stephan

You'll get indeterminate results where some may fill but most won't

I'm not claiming that the great results of this backtest is not due to backtester bugs (which I think it is) but wouldn't the difference you describe between different brokers be illegal in regulated market like stock trading unless we are talking about dark pools etc..

The problem with quantopian limit fills is discussed in this thread and yes, I have also thought I got some great results even though it was really a bug of the backtester (backtester fills at close, so for example whipsaws are never filled even if they should be).

https://www.quantopian.com/posts/limit-order-execution-at-varying-prices

Can anyone suggest me a better option to Interactive Brokers for Trading the US market (for Non-US Citizen)?

@Mikko M -i wouldn't call it a bug (in this instance). more like a limitation of pricing model ability to predict. I didn't allude to different brokerage fill prices. . . at least I didn't mean too. I was trying to illustrate the indeterminate fills phenomenon... Go on Robinhood or Firstrade or any other brokerage (regardless of which brokerage you choose, not a comparison) and try to submit a limit order -> note your results... some will fill some won't (hit or miss), that was the point I was trying to make. I did test this algo on Robinhood and variations of it, and can confidently say the results are indeterminate. Sometimes fills are better, sometimes worse... What really stinks is when you're left holding the bag - nobody is buying what you're trying to sell - meanwhile the Q papertrade instance was able to sell the same exact limit order. In summary Automated algorithmic traders cannot hang their hat on low volume limit orders. This is a unicorn, buyer beware!

@Mikko as I understand it, many low-cost brokerages (Robinhood, Alpaca) and non-low-cost brokerages (TD Ameritrade, etc) send most of your orders to dark pools to get filled as opposed to exchanges, due to the rebates they receive. IB gives you control over how your order is routed and thus you are more likely to get a good fill. I don't think this is illegal.

Alpaca and Robinhood are not available for non US citizens.
Regarding how they make money, Alpaca are quite transparent: https://docs.alpaca.markets/about-us/
I think for retail trading it ends up being cheaper than non commission free brokers.

@Marc Alpaca offers live paper-trading for anyone globally.
Additionally, we started offering business account support for entities globally (yes non-US as well!) by invite-only basis. We are working on non-US residents account support currently. Please check out Alpaca's account types and how you get invited here :) https://docs.alpaca.markets/account-plans/

@Yoshi Glad to hear that. But when can we expect trading accounts for Non-US residents?

Note for everyone - this algo only works on backtesting because of the incorrect overoptimistic modeling of the limit orders in quantopian backtester because of the selected slippage model.

I created a simple long-short algo based on this thread and was surprised how well it worked even with volume percentiles of [50%, 90%] (see the screenshot below). With low volume stocks [6%, 40%] used by original algo the results are even more unrealistic..

2019 data.

Here is exactly the same algo results with more realistic slippage model described in this thread (with code): https://www.quantopian.com/posts/simulation-of-non-marketable-limit-orders

Here is a test run with a slippage model that always fills the limit orders at limit if bar close crosses the limit price:

As you can see, the end result solely depends on the slippage model which is quite a bad sign - IMHO the last slippage model is the most realistic even though it does not account for spikes (low/high crossing the limit price)

My point is that it's really wise to do your homework before gambling your hard-earned money for algo found on the internet.. If an algo seems too good to be true it usually is just that..

How can I run this Algorithm live with Interactive Brokers?

Hi @Hitoshi, I've attempted to run this on Alpaca but am getting an IEX query error. Has IEX changed their API since you posted this?

Initially I was getting an authentication error, which was resolved by adding an OS import and setting my secret key in the algo.py file (would not work in the YAML for some reason). Now it returns a generic query error:

raise IEXQueryError("The query could not be completed. "iexfinance.utils.exceptions.IEXQueryError: An error occurred while making the query.

Just curious if there's anything obvious here I'm missing

Hi, @Thomas, you could try using python environment variables. Also, I'm using the two following import statements:

from pipeline_live.data.alpaca.pricing import USEquityPricing
from pipeline_live.data.alpaca.factors import RSI, Returns