Back to Community
Abstracting an Algorithm from One Stock to Many

Hello everybody,
I wanted to demo a simple way to develop and test an algorithm with one stock, and then abstract the idea to several stocks in a way that minimizes the risk of breaking what you have already done.

It's easier to know that something works when you only use one stock to test it, but a lot of the time, you don't want to just use one stock. The majority of the algorithms I have seen in Quantopian use some type of for loop to apply the same logic to several stocks, which is fine, but you run the risk of breaking something in the process of adding the extra logic.

Here's a different way apply the same logic to several stocks, but with minimal change to the existing code that you already know works. This is also demos some of the beauty of object oriented programming.

Steps:

  1. Write an algo using 1 stock, and make sure it works.
  2. Indent the handle_data function and make it a class.
  3. Give the class an appropriate initialize function (init) and change all of the context variables to "self."
  4. Create several instances of the new class, each with its own single stock.
  5. Have the Quantopian handle_data function call the handle_data function of each instance.

This backtest is the first example algo from the help docs with the comments stripped out and a 20 day moving average instead of 5. The next reply is the same algorithm but with multiple stocks using the method described above.

If you aren't familiar with object oriented programming, it can be tough to wrap your head around at first, but I promise that once you get the hang of it, it will become indispensable.

I hope some of you find this helpful.

David

6 responses

Here is the second version of the same algorithm, but with multiple stocks.

Clone Algorithm
122
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
# Quantopian Functions

def initialize(context):
    context.stocks = symbols('SPY', 'NFLX', 'GE', 'DIA', 'GLD')
    
    # Set the allocation per stock
    pct_per_algo = 1.0 / len(context.stocks)
    
    # Make a separate algo for each stock.
    context.algos = [MovingAverage(stock, pct_per_algo) for stock in context.stocks]


def handle_data(context, data):
    # Call each individual algorithm's handle_data method.
    for algo in context.algos:
        algo.handle_data(context, data)
        
        
        
# This is the original algorithm but it has been turned into its own class 
# with its own handle data method. Now you can create multiple instances of it.

class MovingAverage(object):
    
    # Initialize with a single stock and assign a proportion of the account.
    def __init__(self, security, pct_of_account):
        self.security = security
        self.pct = pct_of_account
        
    def handle_data(self, context, data):

        average_price = data[self.security].mavg(20)
        current_price = data[self.security].price
        
        # Adjust the cash by the proportion allocated to this stock
        cash = context.portfolio.cash * self.pct

        if current_price > 1.01*average_price and cash > current_price:

            number_of_shares = int(cash / current_price)

            order(self.security, number_of_shares)
            log.info("Buying %s" % (self.security.symbol))

        elif current_price < average_price:

            order_target(self.security, 0)
            log.info("Selling %s" % (self.security.symbol))

There was a runtime error.

Thanks David. A very elegant solution. I have been struggling with this problem for some time now with limited, although functioning, results.
How about implementing an indicator that uses a window of data, say the recent 30 days price? Do you have a suggestion how to include this in your solution?

Omar, I like to put any of those parameters in the classes init function. In handle data, you can add history with a larger number of days, and re-sample it down to the windows sizes you passed. That also makes it easy to pass specific parameters to different stocks if you'd like.

Clone Algorithm
122
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
# Quantopian Functions

def initialize(context):
    context.stocks = symbols('SPY', 'NFLX', 'GE', 'DIA', 'GLD')
    
    # Set the allocation per stock
    pct_per_algo = 1.0 / len(context.stocks)
    
    # Make a separate algo for each stock.
    context.algos = [MovingAverage(stock, pct_per_algo) for stock in context.stocks]


def handle_data(context, data):
    # Call each individual algorithm's handle_data method.
    for algo in context.algos:
        algo.handle_data(context, data)
        
        
        
# This is the original algorithm but it has been turned into its own class 
# with its own handle data method. Now you can create multiple instances of it.

class MovingAverage(object):
    
    # Initialize with a single stock and assign a proportion of the account.
    def __init__(self, security, pct_of_account, ma_window=20):
        self.security = security
        self.pct = pct_of_account
        self.ma_window = ma_window
        
    def handle_data(self, context, data):
        if get_open_orders(self.security):
            return
        
        prices = history(200, '1d', 'price')[self.security]
        
        average_price = prices.iloc[-self.ma_window::].mean()
        current_price = data[self.security].price
        
        # Adjust the cash by the proportion allocated to this stock
        cash = context.portfolio.cash * self.pct

        if current_price > 1.01*average_price and cash > current_price:

            number_of_shares = int(cash / current_price)

            order(self.security, number_of_shares)
            log.info("Buying %s" % (self.security.symbol))

        elif current_price < average_price:

            order_target(self.security, 0)
            log.info("Selling %s" % (self.security.symbol))

There was a runtime error.

Thanks David, That is a neat solution.

Thank you for this elegant solution David.

Thank you David