Back to Community
Multiple Indicators Combined

I'm trying to improve this algorithm that uses two different crossing moving averages and the rsi as signals and combines them into one:

  • How can I reduce the max drawdown? I tried using the stop loss but it does not seem to work. Maybe the algorithm does many unsuccessfull trades.
  • How do I let the algorithm select the stocks? Right now they are hand picked and this suffers from both survivorship bias and selection bias.
  • Could an ai optimize the weight of the different signals? Now they are all equal to one...
  • I tried using the MACD signal too but got worst results so I removed it. It take a lot of code for something so common. Is there a better way to iterate on signals?

What are your thoughts?

Thanks

Clone Algorithm
337
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
import talib

def initialize(context):
    context.stocks = symbols('MMM', 'GOOG_L', 'PG', 'DIA', 'AAPL', 'TSLA', 'NFLX', 'SPY')
    context.pct_per_stock = 1.0 / len(context.stocks)
    
    context.max_cash_per_stock = 100000.0 / len(context.stocks)
    context.LOW_RSI = 20
    context.HIGH_RSI = 70
    context.STOP_LOSS = 0.97
    # Create a variable to track the date change
    context.date = None
    
# The handle_data function is where the real work is done.  
# This function is run either every minute 
# (in live trading and minute backtesting mode) 
# or every day (in daily backtesting mode).
def handle_data(context, data):
    todays_date = get_datetime().date()
    
    # Do nothing unless the date has changed
    if todays_date == context.date:
        return
    # Set the new date
    context.date = todays_date
    
    # Load historical data for the stocks
    prices = history(50, '1d', 'price')
    
    # Use pandas dataframe.apply to get the last RSI value
    # for for each stock in our basket
    rsi = prices.apply(talib.RSI, timeperiod=14).iloc[-1]
    
    for stock in context.stocks:
        ma20  = data[stock].mavg(20)
        ma50  = data[stock].mavg(50)
        ma100 = data[stock].mavg(100)
        ma200 = data[stock].mavg(200)
        price = data[stock].price
        
        # Signals : positive means buy
        # low RSI = oversold => buy, high RSI = overbought => sell
        r1 = (1 if rsi[stock] < context.LOW_RSI else 0) + \
             (-1 if rsi[stock] > context.HIGH_RSI else 0) 
        m1 = 1 if ma50 > ma200 else -1
        m2 = 1 if ma20 > ma100 else -1
        
        combo = m1 + m2 + r1
        record(combo=combo)
        
        current_position = context.portfolio.positions[stock].amount
        
        # Close position for the stock when the signal is negative and we own shares.
        if combo < -1 and current_position > 0:
            log.info("Selling {} at {}".format(stock.symbol, price))
            # remove stop loss
            orders = get_open_orders(stock)
            for o in orders:
                cancel_order(o)
            order_target(stock, 0)
            
        # Enter the position for the stock when the signal is positive and 
        # our portfolio shares are 0.
        elif combo > 1 and current_position <= 0:
            log.info("Buying {} at {}".format(stock.symbol, price))
            order_target_percent(stock, context.pct_per_stock, style=LimitOrder(price))
            order_target_percent(stock, context.pct_per_stock, style=StopLimitOrder(price*context.STOP_LOSS, price*context.STOP_LOSS))
There was a runtime error.
9 responses

Im no expert and quite a beginner but I believe that you can generate the stocks to be picked in the before_trading_start part of the source code and then use an SQL query to sift through them and then update_universe based on the parameters you set. Hope this somewhat helped.

https://www.quantopian.com/help#api-order

You will need a screener to select your candidate stocks. Your screening can be based on trading volumes, valuation ....whatever. It will make your life easy. Once you have screened the stocks, one way to reduce max drawdown is to have a set of performing strategies but with uncorrelated drawdowns. You can let a neural net or any regressor find the weights by itself. But be sure to check the output of your ai algorithm on unseen (out of sample). Hope this helps. Wish you luck.

Great Work, im trying to use this strategy LIVE by hand on an BULL OMX30 X18 derivate for the stockholm OMX30 index.. damn hard the analog way.

I think what you are trying to do is to risk-manage your strategy. Many strategies work alright on average, but using a little overlay of risk management will go a long way in improving your returns and reducing your drawdowns (volatility).

One way to reduce your drawn downs is to use optimization, instead of hard-coding your stop-loss or using moving averages as indicators, why not target a volatility, and scale your strategy weights in conjunction with your target volatility?

A good example will be this (http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2371227)

The authors found that using a dynamic weighted momentum strategy by optimizing the returns using a target volatility of 12% doubles the sharpe ratio of a traditional dual momentum strategy.

I've tried this cloned algo with a few different RSI and MA criteria but it doesent want to sell the shares once it reaches the point where the Combo becomes negative (it just holds them). Is there something that needs to be added to the close section once combo score becomes negative?

The backtest does show it sells shares.
Thanks guys for the suggestions on stock picking and volatility. I might work on it more this week.

Clone Algorithm
337
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
import talib

def initialize(context):
    context.stocks = symbols('MMM', 'GOOG_L', 'PG', 'DIA', 'AAPL', 'TSLA', 'NFLX', 'SPY')
    context.pct_per_stock = 1.0 / len(context.stocks)
    
    context.max_cash_per_stock = 100000.0 / len(context.stocks)
    context.LOW_RSI = 20
    context.HIGH_RSI = 70
    context.STOP_LOSS = 0.97
    # Create a variable to track the date change
    context.date = None
    
# The handle_data function is where the real work is done.  
# This function is run either every minute 
# (in live trading and minute backtesting mode) 
# or every day (in daily backtesting mode).
def handle_data(context, data):
    todays_date = get_datetime().date()
    
    # Do nothing unless the date has changed
    if todays_date == context.date:
        return
    # Set the new date
    context.date = todays_date
    
    # Load historical data for the stocks
    prices = history(50, '1d', 'price')
    
    # Use pandas dataframe.apply to get the last RSI value
    # for for each stock in our basket
    rsi = prices.apply(talib.RSI, timeperiod=14).iloc[-1]
    
    for stock in context.stocks:
        ma20  = data[stock].mavg(20)
        ma50  = data[stock].mavg(50)
        ma100 = data[stock].mavg(100)
        ma200 = data[stock].mavg(200)
        price = data[stock].price
        
        # Signals : positive means buy
        # low RSI = oversold => buy, high RSI = overbought => sell
        r1 = (1 if rsi[stock] < context.LOW_RSI else 0) + \
             (-1 if rsi[stock] > context.HIGH_RSI else 0) 
        m1 = 1 if ma50 > ma200 else -1
        m2 = 1 if ma20 > ma100 else -1
        
        combo = m1 + m2 + r1
        record(combo=combo)
        
        current_position = context.portfolio.positions[stock].amount
        
        # Close position for the stock when the signal is negative and we own shares.
        if combo < -1 and current_position > 0:
            log.info("Selling {} at {}".format(stock.symbol, price))
            # remove stop loss
            orders = get_open_orders(stock)
            for o in orders:
                cancel_order(o)
            order_target(stock, 0)
            
        # Enter the position for the stock when the signal is positive and 
        # our portfolio shares are 0.
        elif combo > 1 and current_position <= 0:
            log.info("Buying {} at {}".format(stock.symbol, price))
            order_target_percent(stock, context.pct_per_stock, style=LimitOrder(price))
            order_target_percent(stock, context.pct_per_stock, style=StopLimitOrder(price*context.STOP_LOSS, price*context.STOP_LOSS))
There was a runtime error.

I'm a new user, please forgive me if I understand your question incorrectly... I don't know why you only pick a few stocks as below
MMM', 'GOOG_L', 'PG', 'DIA', 'AAPL', 'TSLA', 'NFLX', 'SPY' which might be a reason to get high max drawdown. Since, GOOG, TSLA, AAPL are volatile stocks. Can you have a dynamic list of stocks based on ATR and price ratios?
Does Quantopian support dynamic list of stock?

Isn't this a bug?

order_target_percent(stock, context.pct_per_stock, style=StopLimitOrder(price*context.STOP_LOSS, price*context.STOP_LOSS))  

Should probably be:
order_target_percent(stock, 0, style=StopLimitOrder(price*context.STOP_LOSS, price*context.STOP_LOSS))

(Or your leverage will explode)

@Weimin, yes you can create a dynamic stock list using the set_universe() function or fundamental data. There's more info (and examples) in the API documentation.

We're working right now to allow you to create dynamic universes using any custom criteria (like indicators and price ratios). The high level details were laid out in this thread. Check it out!

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.