Back to Community
Simple "Moving Average Ratio" Strategy

Hello guys,

I have some experience with mql4 but none with other programming codes. Since I'd like to test a very simple strategy maybe someone among you is so generous and will help me. I'd like to code the following strategy:

  1. Calculate a RATIO between price and its simple moving average (20): RATIO = LAST/ SMA(20)
  2. SELL when ratio is above a given value. CLOSE position when ratio is below 1 or when unrealized loss is 2% of capital.
  3. BUY when ratio is below a given value. CLOSE position when ratio is above 1 or when unrealized loss is 2% above capital.

Thank you in advance.

Matteo

23 responses

If I understand correctly you are trying to detect overbought/oversold conditions to trade, and closing when the average reflects the current value (suggesting the price has reached it's true price)?

I've created a long-only version of your above logic (if I've understood you correctly), as a place to start.

It seems at first glance to meet that age old problem stated by Keynes... "The market can stay irrational far longer than you can remain solvent".

Either that or it's my coding (I'm tired and out of practice :P).

Clone Algorithm
180
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stock = sid(26578)
    context.position_closed = True
    context.order_size = 1000
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=10))
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    
    # Variables
    price = data[context.stock].close_price # The price of the stock
    moving_average = data[context.stock].mavg(20) # The stocks 20 day moving average
    ratio = price/moving_average # The price:moving average "ratio"
    current_position_value = context.order_size*price # The current value of the position
    buy_threshold = 0.95 # Ratio threshold at which to buy
    close_threshold = 1 # Ratio threshold at which to close buy position
    
    # Functions
    def buy_order(): # Make a buy order
        order(context.stock, context.order_size)
        context.position_closed = False
        print "Buy Order Made"
        
    def close_buy_order(): # Close the buy order
        order(context.stock, -context.order_size)
        context.position_closed = True
        print "Buy Order Closed"
        
    # Conditions
    if ratio < buy_threshold and context.position_closed == True: # Buy if ratio is less than buy threshold 
        context.position_cost = context.order_size*price
        buy_order()
        
    if ratio > close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold 
        close_buy_order()
        
    if current_position_value < context.position_cost*0.98 and context.position_closed == False: # Close if unrealised loss exceeds 2% 
        close_buy_order()
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Dear Adam,

thank you very much for your help. I forgot to mention that my analysis is based on daily data and on major stock indexes such as SPY and DIA. Here you find another backtest set with my parameters. From a study I'm conducting at the moment I'm finding that these major stock indexes tend to revert to the mean when some extremes are reached. I perfectly know that using most oscillators you end up with losing strategies; however, this RATIO I thought of seems to work.

Clone Algorithm
93
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stock = sid(8554)
    context.position_closed = True
    context.order_size = 100
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=1))
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    
    # Variables
    price = data[context.stock].close_price # The price of the stock
    moving_average = data[context.stock].mavg(20) # The stocks 20 day moving average
    ratio = price/moving_average # The price:moving average "ratio"
    current_position_value = context.order_size*price # The current value of the position
    buy_threshold = 0.97 # Ratio threshold at which to buy
    close_threshold = 1 # Ratio threshold at which to close buy position
    
    # Functions
    def buy_order(): # Make a buy order
        order(context.stock, context.order_size)
        context.position_closed = False
        print "Buy Order Made"
        
    def close_buy_order(): # Close the buy order
        order(context.stock, -context.order_size)
        context.position_closed = True
        print "Buy Order Closed"
        
    # Conditions
    if ratio < buy_threshold and context.position_closed == True: # Buy if ratio is less than buy threshold 
        context.position_cost = context.order_size*price
        buy_order()
        
    if ratio > close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold 
        close_buy_order()
        
    if current_position_value < context.position_cost*0.98 and context.position_closed == False: # Close if unrealised loss exceeds 2% 
        close_buy_order()
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

I managed to code two versions, one for the shorts and one for the shorts. Below are the backtest of the short version.

Clone Algorithm
22
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stock = sid(8554)
    context.position_closed = True
    context.order_size = -100
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=1))
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    
    # Variables
    price = data[context.stock].close_price # The price of the stock
    moving_average = data[context.stock].mavg(20) # The stocks 20 day moving average 
    moving_average2 = data[context.stock].mavg(200) # The stocks 200 day moving average
    ratio = price/moving_average # The price:moving average "ratio"
    current_position_value = context.order_size*price # The current value of the position
    sell_threshold = 1.025 # Ratio threshold at which to buy
    close_threshold = 1 # Ratio threshold at which to close buy position
    
    # Functions
    def sell_order(): # Make a buy order
        order(context.stock, context.order_size)
        context.position_closed = False
        print "Sell Order Made"
        
    def close_sell_order(): # Close the buy order
        order(context.stock, -context.order_size)
        context.position_closed = True
        print "Sell Order Closed"
        
    # Conditions
    if moving_average < moving_average2 and  ratio > sell_threshold and context.position_closed == True: # Sell if ratio is less than buy threshold 
        context.position_cost = context.order_size*price
        sell_order()
        
    if ratio < close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold 
        close_sell_order()
        
    if current_position_value > context.position_cost*0.98 and context.position_closed == False: # Close if unrealised loss exceeds 2% 
        close_sell_order()
       
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

and the backtest of the long version.

Clone Algorithm
93
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stock = sid(8554)
    context.position_closed = True
    context.order_size = 100
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=1))
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    
    # Variables
    price = data[context.stock].close_price # The price of the stock
    moving_average = data[context.stock].mavg(20) # The stocks 20 day moving average
    moving_average2 = data[context.stock].mavg(200) # The stocks 20 day moving average
    ratio = price/moving_average # The price:moving average "ratio"
    current_position_value = context.order_size*price # The current value of the position
    buy_threshold = 0.975 # Ratio threshold at which to buy
    close_threshold = 1 # Ratio threshold at which to close buy position
    
    # Functions
    def buy_order(): # Make a buy order
        order(context.stock, context.order_size)
        context.position_closed = False
        print "Buy Order Made"
        
    def close_buy_order(): # Close the buy order
        order(context.stock, -context.order_size)
        context.position_closed = True
        print "Buy Order Closed"
        
    # Conditions
    if moving_average > moving_average2 and ratio < buy_threshold and context.position_closed == True: # Buy if ratio is less than buy threshold 
        context.position_cost = context.order_size*price
        buy_order()
        
    if ratio > close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold 
        close_buy_order()
        
    if current_position_value < context.position_cost*0.98 and context.position_closed == False: # Close if unrealised loss exceeds 2% 
        close_buy_order()
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

I'd like to do some test on multiple stocks. I read the sample code provided for multiple securities algorithms but I always get an error message. Could anyone suggest a feasible solution for converting this single stock code to a multiple stocks one? Thank you in advance.

Matteo,

One way to incorporate multiple stocks into your algorithm is to declare the code below in your initialize method

context.stocks = [#your multiple sids here]  

and in your handle_data method, you would iterate through each of these stocks like so

for stocks in context.stocks:  
# Your main methods below  

One other thing to note is that your buy_order and close_buy_order methods must also be changed to take in the current stock as a parameter as in

def buy_order(context, stock):  

I've attached a backtest to show what kind of changes you'd need to have.

Hope this helps!

-Seong

Clone Algorithm
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stocks = [sid(8554), sid(26578), sid(24)]
    context.position_closed = True
    context.order_size = 100
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=1))
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    buy_threshold = 0.97 # Ratio threshold at which to buy
    close_threshold = 1 # Ratio threshold at which to close buy position
    
    for stock in context.stocks:
        # Variables
        price = data[stock].close_price # The price of the stock
        moving_average = data[stock].mavg(20) # The stocks 20 day moving average
        ratio = price/moving_average # The price:moving average "ratio"
        current_position_value = context.order_size*price # The current value of the position
        
        # Functions that take in the current context and stock as parameters
        def buy_order(context, stock): # Make a buy order
            order(stock, context.order_size)
            context.position_closed = False
            print "Buy Order Made"
            
        def close_buy_order(context, stock): # Close the buy order
            order(stock, -context.order_size)
            context.position_closed = True
            print "Buy Order Closed"
            
        # Conditions
        if ratio < buy_threshold and context.position_closed == True: # Buy if ratio is less than buy threshold 
            context.position_cost = context.order_size*price
            buy_order(context, stock)
            
        if ratio > close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold 
            close_buy_order(context, stock)
            
        if current_position_value < context.position_cost*0.98 and context.position_closed == False: # Close if unrealised loss exceeds 2% 
            close_buy_order(context, stock)
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.
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.

Thank you very much Seong.

I'm testing the new code you provided. However, I'm trying to discover why the results for two stocks backtests differ dramatically from the results of single backtests combined...

I'm investigating the transaction details and it looks that the algorithm now trades like a pair trading strategy, and so buying one security and selling the other one at the same time.

Hi Matteo,

Is there an ideal behavior instead of what's happening now?

(I hope I'm getting your question) Yes of course there's an ideal behavior. The idea is quite simple: this algorithm monitors the divergence of price from a moving average and enters in favor of trend, when extreme counter-trend deviations are detected. It trades very infrequently but when it does it is accurate. The divergence is measured with the variable called "ratio". The major trend is detected simply by comparing two moving averages. At the moment I'm fine with the algorithm and next steps would be:
1. including multiple securities
2. combining the long version and the short version under one single algorithm.

Matteo,

I've found what's causing the strategy to trade like a pair trading strategy. It was in the logic of the boolean, context.position_closed, which was being switched on and off every time a security was bought instead of being turned on and off for each individual security. E.g. If you bought SPY, it would turn False for the entire algorithm, instead of just for the SPY. Thankfully, there's a fix to this by using a Python dictionary and I've attached a solution here:

-Seong

Clone Algorithm
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stocks = [sid(8554), sid(33916)]
    # Set a dictionary for different closes for each position
    context.position_closed = { sid(8554) : True, sid(33916): True }
    context.order_size = 100
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=1))
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    buy_threshold = 0.97 # Ratio threshold at which to buy
    close_threshold = 1 # Ratio threshold at which to close buy position
    
    for stock in context.stocks:
        # Variables
        price = data[stock].close_price # The price of the stock
        moving_average = data[stock].mavg(20) # The stocks 20 day moving average
        ratio = price/moving_average # The price:moving average "ratio"
        current_position_value = context.order_size*price # The current value of the position
        
        # Functions that take in the current context and stock as parameters
        def buy_order(context, stock): # Make a buy order
            order(stock, context.order_size)
            context.position_closed[stock] = False
            print "Buy Order Made"
            
        def close_buy_order(context, stock): # Close the buy order
            order(stock, -context.order_size)
            context.position_closed[stock] = True
            print "Buy Order Closed"
            
        # Conditions // Note that context.position_closed[stock] gets the boolean for each sid
        if ratio < buy_threshold and context.position_closed[stock] == True: # Buy if ratio is less than buy threshold 
            context.position_cost = context.order_size*price
            buy_order(context, stock)
            
        if ratio > close_threshold and context.position_closed[stock] == False: # Close if ratio is greater than close threshold 
            close_buy_order(context, stock)
            
        if current_position_value < context.position_cost*0.98 and context.position_closed[stock] == False: # Close if unrealised loss exceeds 2% 
            close_buy_order(context, stock)
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Thank you very much Seong. I'm currently testing different ETFs for this strategy. The final step will be combining the long version and the short version under one single algorithm. I'll post some backtest soon. Thank you again. :)

No Problem! Looking forward to seeing your results!

Below you find a back test for one aggressive Long version. Underlyings are SPY and DIA.

Clone Algorithm
37
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stocks = [sid(2174), sid(8554)]
    # Set a dictionary for different closes for each position
    context.position_closed = { sid(2174) : True,sid(8554):True }
    context.order_size = 100
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=1))
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    buy_threshold = 0.99 # Ratio threshold at which to buy
    close_threshold = 1 # Ratio threshold at which to close buy position
    
    for stock in context.stocks:
        # Variables
        price = data[stock].close_price # The price of the stock
        moving_average = data[stock].mavg(10) # The stocks 20 day moving average
        moving_average2 = data[stock].mavg(200) # The stocks 20 day moving average
        ratio = price/moving_average # The price:moving average "ratio"
        current_position_value = context.order_size*price # The current value of the position
        
        # Functions that take in the current context and stock as parameters
        def buy_order(context, stock): # Make a buy order
            order(stock, context.order_size)
            context.position_closed[stock] = False
            print "Buy Order Made"
            
        def close_buy_order(context, stock): # Close the buy order
            order(stock, -context.order_size)
            context.position_closed[stock] = True
            print "Buy Order Closed"
            
        # Conditions // Note that context.position_closed[stock] gets the boolean for each sid
        if  moving_average > moving_average2 and ratio < buy_threshold and context.position_closed[stock] == True: # Buy if ratio is less than buy threshold 
            context.position_cost = context.order_size*price
            buy_order(context, stock)
            
        if ratio > close_threshold and context.position_closed[stock] == False: # Close if ratio is greater than close threshold 
            close_buy_order(context, stock)
            
        if current_position_value < context.position_cost*0.98 and context.position_closed[stock] == False : # Close if unrealised loss exceeds 2% 
            close_buy_order(context, stock)
        if current_position_value > context.position_cost*1.04 and context.position_closed[stock] == False: # Close if unrealised loss exceeds 2% 
            close_buy_order(context, stock)
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

I'm trying to figure out if the algorithm works fine with multiple securities. I can't understand how, if I backtest the algorithm at first only on the SPY, then only on the DIA, and then on the SPY and DIA together I get the following results (from 2010 to 2013 with daily data):
1. only SPY: 14.5% return
2. only DIA: 17.8% return
3. SPY and DIA: 17.1% return

Shouldn't I expect to get roughly 14.5% + 17.8% return?

Clone Algorithm
37
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stocks = [ sid(2174), sid(8554)] #, sid(2174)
    # Set a dictionary for different closes for each position
    context.position_closed = { sid(2174) :True, sid(8554): True} #, 
    context.order_size = 100
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=1))
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    buy_threshold = 0.975 # Ratio threshold at which to buy
    close_threshold = 1 # Ratio threshold at which to close buy position
    
    for stock in context.stocks:
        # Variables
        price = data[stock].close_price # The price of the stock
        moving_average = data[stock].mavg(10) # The stocks 20 day moving average
        moving_average2 = data[stock].mavg(200) # The stocks 20 day moving average
        ratio = price/moving_average # The price:moving average "ratio"
        current_position_value = context.order_size*price # The current value of the position
        
        # Functions that take in the current context and stock as parameters
        def buy_order(context, stock): # Make a buy order
            order(stock, context.order_size)
            context.position_closed[stock] = False
            print "Buy Order Made"
            
        def close_buy_order(context, stock): # Close the buy order
            order(stock, -context.order_size)
            context.position_closed[stock] = True
            print "Buy Order Closed"
            
        # Conditions // Note that context.position_closed[stock] gets the boolean for each sid
        if  moving_average > moving_average2 and ratio < buy_threshold and context.position_closed[stock] == True: # Buy if ratio is less than buy threshold 
            context.position_cost = context.order_size*price
            buy_order(context, stock)
            
        if ratio > close_threshold and context.position_closed[stock] == False: # Close if ratio is greater than close threshold 
            close_buy_order(context, stock)
            
        if current_position_value < context.position_cost*0.98 and context.position_closed[stock] == False : # Close if unrealised loss exceeds 2% 
            close_buy_order(context, stock)
        if current_position_value > context.position_cost*1.04 and context.position_closed[stock] == False: # Close if unrealised loss exceeds 2% 
            close_buy_order(context, stock)
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Matteo,

Looking over the algorithm again, I have some questions that may help us get to the root of the problem.

  • I believe the logic of current_position_value <> context.position_cost is an issue. Do you think context.position_cost should be individual for each security like we did for context.position_closed?

SEONG, I think I solved that problem separating, in the code, the variables for the buy and sell orders. Here's a backtest on SPY, DIA, IWR. The next step I find a bit tricky is to allocate to each ticker a portion of capital. For example I tried setting the buy and sell size as: size=(10000*0.1)/price , the problem is that it opens orders in the correct way, but when it closes an order, the price of the stock has changed and though the closing size results slightly different than the opening size, and this creates some "positions residuals" that disturb the strategy. Any help or suggestion in this sense or for the strategy in general is welcome! :)

Clone Algorithm
103
Loading...
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
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.stocks = [sid(8554), sid(22908), sid(2174)] #,  , sid(8554), sid(22908),sid(23408), sid(27100), sid(25907)
    # Set a dictionary for different closes for each position
    context.long_position_closed = {sid(2174):True,sid(8554):True, sid(22908):True } #sid(32268) : True,sid(32406):True, sid(23911):True 
    context.short_position_closed = {sid(2174):True,sid(8554):True, sid(22908):True } #,sid(8554):True, sid(22908):True
    context.order_size = 100
    context.position_cost = 0
    set_commission(commission.PerTrade(cost=1))
    
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    buy_threshold = 0.975 # Ratio threshold at which to buy
    sell_threshold = 1.025 # Ratio threshold at which to buy
    close_sell_threshold = 0.99 # Ratio threshold at which to close buy position
    close_buy_threshold = 1.01
    
    for stock in context.stocks:
        # Variables
        price = data[stock].close_price # The price of the stock
        moving_average = data[stock].mavg(10) # The stocks 20 day moving average
        moving_average2 = data[stock].mavg(200) # The stocks 20 day moving average
        ratio = price/moving_average # The price:moving average "ratio"
        buysize = 100
        sellsize = -100
        current_buy_position_value = buysize*price # The current value of the position
        current_sell_position_value = sellsize*price
        # Functions that take in the current context and stock as parameters
        def buy_order(context, stock): # Make a buy order
            order(stock, buysize)
            context.long_position_closed[stock] = False
       
            print "Buy Order Made"
            
        def close_buy_order(context, stock): # Close the buy order
            order(stock, -buysize)
            context.long_position_closed[stock] = True
          
            print "Buy Order Closed"
           
             
        def sell_order(context, stock): # Make a sell order
            order(stock, sellsize)
            context.short_position_closed[stock] = False
        
            print "Sell Order Made"
         
            
        def close_sell_order(context, stock): # Close the sell order
            order(stock, -sellsize)
            context.short_position_closed[stock] = True
            
            print "Sell Order Closed"
           
            
            
        # Conditions // Note that context.position_closed[stock] gets the boolean for each sid
    
        if moving_average > moving_average2 and ratio < buy_threshold and  context.long_position_closed[stock] == True: #Buy if ratio is less than buy threshold  
            context.position_cost = buysize*price
            buy_order(context, stock)
       
        if moving_average < moving_average2 and ratio > sell_threshold and  context.short_position_closed[stock] == True: #  Buy if ratio is less than buy threshold  
            context.position_cost = sellsize*price
            sell_order(context, stock)
            
        if ratio > close_buy_threshold and  context.long_position_closed[stock] == False: # Close if ratio is greater than close threshold 
            close_buy_order(context, stock)
            
        if current_buy_position_value < context.position_cost*0.97 and  context.long_position_closed[stock] == False : # Close if unrealised loss exceeds 2% 
            close_buy_order(context, stock)
       
        if  current_buy_position_value > context.position_cost*1.03 and  context.long_position_closed[stock] == False: # Close if unrealised loss exceeds 2% 
            close_buy_order(context, stock)
       
        if ratio < close_sell_threshold and  context.short_position_closed[stock] == False: # Close if ratio is greater than close threshold 
            close_sell_order(context, stock)
            
        if current_sell_position_value > context.position_cost*0.97  and  context.short_position_closed[stock] == False: # Close if unrealised loss exceeds 2% 
            close_sell_order(context, stock)
        
        if  current_sell_position_value < context.position_cost*1.03 and  context.short_position_closed[stock] == False: # Close if unrealised loss exceeds 2% 
            close_sell_order(context, stock)  
 
    
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Matteo,

Great to hear!! The algo is looking very impressive now.

Do you think you provide a short example on what "position residuals" you are referring to?

For now, I'd like to suggest a target weight to use for each security (e.g. everytime the SPY triggers the conditions, it will hold 30% of the portfolio?) but am getting the feeling that you'd get the same problem as before.

Yes. The problem is that when you define the entry size using a formula like "portfolio/price", and then set the exit size just as "- entry size" you end up having different entry and exit size: this leads to imperfect closing of position and so to the "residuals". For example you enter with a size of 10000/10= 1000 shares, but on closing day the price has risen to 15 and your closing size becomes 10000/15= 667 shares.

For exit size you could possibly do

context.portfolio.positions[sid].amount * data[sid].price  

And this would give you the current number of shares held * the current price of the security, giving you a complete exit

Yes. Thank you Seong. I solved that problem. Backtests look interesting. What do you think? Do you have any suggestion on the strategy?

Glad to hear.

As for suggestions, I say try testing the strategy using leverage constraints and commissions and see how your results look there.
To see ideas on leverage constraint, check this thread