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.

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).

180
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

# 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
close_threshold = 1 # Ratio threshold at which to close buy position

# Functions
order(context.stock, context.order_size)
context.position_closed = False

order(context.stock, -context.order_size)
context.position_closed = True

# 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

if ratio > close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold

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.

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.

93
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

# 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
close_threshold = 1 # Ratio threshold at which to close buy position

# Functions
order(context.stock, context.order_size)
context.position_closed = False

order(context.stock, -context.order_size)
context.position_closed = True

# 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

if ratio > close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold

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.

22
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

# 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

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.

93
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

# 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
close_threshold = 1 # Ratio threshold at which to close buy position

# Functions
order(context.stock, context.order_size)
context.position_closed = False

order(context.stock, -context.order_size)
context.position_closed = True

# 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

if ratio > close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold

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:


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

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

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
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
order(stock, context.order_size)
context.position_closed = False

order(stock, -context.order_size)
context.position_closed = True

# 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

if ratio > close_threshold and context.position_closed == False: # Close if ratio is greater than close threshold

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

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

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
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
order(stock, context.order_size)
context.position_closed[stock] = False

order(stock, -context.order_size)
context.position_closed[stock] = True

# 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

if ratio > close_threshold and context.position_closed[stock] == False: # Close if ratio is greater than close threshold

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.

37
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

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
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
order(stock, context.order_size)
context.position_closed[stock] = False

order(stock, -context.order_size)
context.position_closed[stock] = True

# 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

if ratio > close_threshold and context.position_closed[stock] == False: # Close if ratio is greater than close threshold

if current_position_value < context.position_cost*0.98 and context.position_closed[stock] == False : # Close if unrealised loss exceeds 2%
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?

37
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

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
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
order(stock, context.order_size)
context.position_closed[stock] = False

order(stock, -context.order_size)
context.position_closed[stock] = True

# 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

if ratio > close_threshold and context.position_closed[stock] == False: # Close if ratio is greater than close threshold

if current_position_value < context.position_cost*0.98 and context.position_closed[stock] == False : # Close if unrealised loss exceeds 2%
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! :)

103
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

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
sell_threshold = 1.025 # Ratio threshold at which to buy
close_sell_threshold = 0.99 # 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"
sellsize = -100
current_sell_position_value = sellsize*price
# Functions that take in the current context and stock as parameters
context.long_position_closed[stock] = False

context.long_position_closed[stock] = True

def sell_order(context, stock): # Make a sell order
order(stock, sellsize)
context.short_position_closed[stock] = False

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

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

if current_buy_position_value < context.position_cost*0.97 and  context.long_position_closed[stock] == False : # Close if unrealised loss exceeds 2%

if  current_buy_position_value > context.position_cost*1.03 and  context.long_position_closed[stock] == False: # Close if unrealised loss exceeds 2%

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?