Back to Community
Is there anyway I could sell any buys exactly after 2 years?

I need some help with a technique that lets me sell exactly after 2 years of holding it. But I didn't find any simple way to do this. I am buying different stocks at different dates. I can sell any of the buys when a conditions are met before the 2 years. Then I need to buy a new one to fill the empty seat. So each stocks will have different buy dates. I need some neat way to store the buy date for each different securities so that if certain conditions never met, I can still sell it after 2 years.

I know I got to add something around code line 191 and after...

Thank you

Clone Algorithm
10
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
"""
This example comes from a request in the forums. 
The post can be found here: https://www.quantopian.com/posts/ranking-system-based-on-trading-volume-slash-shares-outstanding

The request was: 

I am stuck trying to build a stock ranking system with two signals:
1. Trading Volume/Shares Outstanding.
2. Price of current day / Price of 60 days ago.
Then rank Russell 2000 stocks every month, long the top 5%, short the bottom 5%.

"""

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar


# Create custom factor #1 Trading Volume/Shares Outstanding
class Factor1(CustomFactor):   
    
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation_ratios.pe_ratio] 
    window_length = 1
    
    # Compute factor1 value
    def compute(self, today, assets, out, pe_ratio):       
        out[:] = pe_ratio[-1]

# Create custom factor #2 Price of current day / Price of 60 days ago.        
class Factor2(CustomFactor):   
    
    # Pre-declare inputs and window_length
    inputs = [morningstar.balance_sheet.total_liabilities,
              morningstar.balance_sheet.stockholders_equity] 
    
    window_length = 1
    
    # Compute factor2 value
    def compute(self, today, assets, out, debt, equity):  
    
        out[:] = debt[-1] /equity[-1]
        
        
class Factor3(CustomFactor):   
    
    # Pre-declare inputs and window_length
    inputs = [morningstar.balance_sheet.total_liabilities,
              morningstar.balance_sheet.stockholders_equity] 
    
    window_length = 1
    
    # Compute factor2 value
    def compute(self, today, assets, out, debt, equity):  
    
        out[:] = debt[-1] 
        
class Factor4(CustomFactor):   
    
    # Pre-declare inputs and window_length
    inputs = [morningstar.balance_sheet.total_liabilities,
              morningstar.balance_sheet.stockholders_equity] 
    
    window_length = 1
    
    # Compute factor2 value
    def compute(self, today, assets, out, debt, equity):  
    
        out[:] = equity[-1]
        
# Create custom factor to calculate a market cap based on yesterday's close
# We'll use this to get the top 2000 stocks by market cap
class MarketCap(CustomFactor):   
    
    # Pre-declare inputs and window_length
    inputs = [USEquityPricing.close, morningstar.valuation.shares_outstanding] 
    window_length = 1
    
    # Compute market cap value
    def compute(self, today, assets, out, close, shares):       
        out[:] = close[-1] * shares[-1]

def initialize(context):
    
    context.long_leverage = 0.50
    context.short_leverage = -0.50
    context.len_long_short = 0.0
    
    pipe = Pipeline()
    attach_pipeline(pipe, 'ranked_2000')
    
    #add the two factors defined to the pipeline
    factor1 = Factor1()
    pipe.add(factor1, 'factor_1') 
    factor2 = Factor2()
    pipe.add(factor2, 'factor_2')
    
    factor3 = Factor3()
    pipe.add(factor3, 'factor_3') 
    factor4 = Factor4()
    pipe.add(factor4, 'factor_4')
    
    # Create and apply a filter representing the top 2000 equities by MarketCap every day
    # This is an approximation of the Russell 2000
    mkt_cap = MarketCap()
    top_2000 = mkt_cap.top(2000) #2000

    factor_1_filter = (factor1 > 0) & (factor1 < 10.0)
    factor_2_filter = (factor2 > 0) & (factor2 < 0.5)
    
    total_filter = (top_2000 & factor_1_filter & factor_2_filter)    
   # use the combined filter to set the screen
    pipe.set_screen(total_filter)
    
    # Rank factor 1 and add the rank to our pipeline
    factor1_rank = factor1.rank(mask=total_filter)
    pipe.add(factor1_rank, 'f1_rank')
    # Rank factor 2 and add the rank to our pipeline
    factor2_rank = factor2.rank(mask=total_filter)
    pipe.add(factor2_rank, 'f2_rank')
    # Take the average of the two factor rankings, add this to the pipeline
    combo_raw = (factor1_rank+factor2_rank)
    pipe.add(combo_raw, 'combo_raw') 
    # Rank the combo_raw and add that to the pipeline
    pipe.add(combo_raw.rank(mask=total_filter), 'combo_rank')      
            
    # Scedule my rebalance function
    schedule_function(func=rebalance, 
                      date_rule=date_rules.month_start(days_offset=0), 
                      time_rule=time_rules.market_open(hours=0,minutes=30), 
                      half_days=True)
    
            
def before_trading_start(context, data):
    # Call pipelive_output to get the output
    # Note this is a dataframe where the index is the SIDs for all securities to pass my screen
    # and the colums are the factors which I added to the pipeline
    context.output = pipeline_output('ranked_2000')
    #there are some NaNs in factor 2, I'm removing those
    ranked_2000 = context.output.fillna(0)
    
    log.info("\n" + str(len(context.output)))
    # Narrow down the securities to only the top 500 & update my universe
    context.len_long_short = (len(ranked_2000) - len(ranked_2000)%2) / 2
    context.long_list = ranked_2000.sort(['combo_rank'], ascending=False).iloc[:context.len_long_short]
    context.short_list = ranked_2000.sort(['combo_rank'], ascending=False).iloc[-context.len_long_short:]   
    
    update_universe(context.long_list.index.union(context.short_list.index)) 


def handle_data(context, data):  
    
     # Record and plot the leverage of our portfolio over time. 
    record(leverage = context.account.leverage)
    
    print "Long List"
    log.info("\n" + str(context.long_list.sort(['combo_rank'], ascending=True).head(10)))
    
    print "Short List" 
    log.info("\n" + str(context.short_list.sort(['combo_rank'], ascending=False).head(10)))

# This rebalancing is called according to our schedule_function settings.     
def rebalance(context,data):
    
    long_weight = context.long_leverage / float(len(context.long_list))
    short_weight = context.short_leverage / float(len(context.short_list))

    
    for long_stock in context.long_list.index:
        if long_stock in data:
            log.info("ordering longs")
            log.info("weight is %s" % (long_weight))
            order_target_percent(long_stock, long_weight)
        
    for short_stock in context.short_list.index:
        if short_stock in data:
            log.info("ordering shorts")
            log.info("weight is %s" % (short_weight))
            order_target_percent(short_stock, short_weight)
        
    for stock in context.portfolio.positions.iterkeys():
        if stock not in context.long_list.index and stock not in context.short_list.index:
            order_target(stock, 0)
There was a runtime error.
6 responses

I am getting sense that there is no short cut to this. Is a dataframe work in order to track initial purchase date an answers?

I have asked them to add some dates to the position objects, but they've said it's quite difficult...

I've done some fixed buy-sell timeframe work by just creating a dict object in initialization:

def initialize(context):  
   [...]  
  context.maxdays = 14    # Max number of calendar days to hold a trade  
  context.tradedates = {}

Then, I created "buy" and "sell" functions to populate / de-populate the dict and call those in place of "order_target_percent()":

def buy(context, sid, pct):  
  if sid not in get_open_orders():  
    order_target_percent(sid, pct)  
    if sid not in context.tradedates:  # Don't update if we already have a position  
      context.tradedates[sid] = get_datetime()

def sell(context, sid):  
  if sid not in get_open_orders():  
    order_target_percent(sid, 0)  
    del(context.tradedates[sid])

Then, at regular intervals, just do:

for sid, tradedate in context.tradedates.items():  
  if (get_datetime() - tradedate).days > context.max_days:  
    sell(context, sid)  

Yeah I think that is basically required, with the downside that if you restart your algorithm, you lose track of how long you've owned everything. If the holding period is 2 years, that's a hefty limitation...

K.C. Budd,
Really Appreciated!

I don't understand how the IB thing works but my current generating engine copes with this by running a back test for say the past two years so as to populate the variables necessary and then spitting out orders. So that starting and then having to restart is not a problem.

Zipline works differently?