Back to Community
How to move portfolio to specific ETF or Stock instead of cash

is there a way to move the portfolio to a specific ETF (VGLT for example) or stock instead of going to cash when the SMA sell is triggered?

Clone Algorithm
20
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
#
# Quantapolis - Value Example (long only)
# (http://www.quantapolis.com)
#
# This is a simple implementation of a long-only value strategy for for a
# concentrated portfolio of US large-cap stocks on NYSE, based on the acquirers
# multiple (EV/EBITDA) with monthly rebalancing.
#
# Version:
#   1.0 (2015-08-18)
#
# Implementation credit:
#   Origin (origi[email protected])
#
#

def info(context, data):
    ''' Custom chart and/or log of maxspent returns and related information
    '''
    record_max_lvrg = 1      # maximum leverage encountered
    record_q_return = 1      # Quantopian return value (percentage)
    record_maxspent = 1      # maxspent returns (percentage)
    record_pnl      = 0      # profit-n-loss
    record_cash_low = 1      # new lowest cash levels

    # Also log to the logging window conditionally
    log_method = 'cash_low'  # 'daily' or 'cash_low' for only when new cash low

    c = context                          # For brevity
    new_cash_low = 0                     # To trigger logging in cash_low case
    date = str(get_datetime().date())    # To trigger logging in daily case

    if 'cash_low' not in c:  # init these instead in initialize for better efficiency
        c.cash_low = c.portfolio.starting_cash
        c.date_prv = date
        c.max_lvrg = 0

    if c.portfolio.cash < c.cash_low:    # New cash low
        new_cash_low = 1
        c.cash_low   = c.portfolio.cash
        if record_cash_low:
            record(CashLow = int(c.cash_low))

    maxspent_rtrn = 0        # Returns based on maximum spent
    q_rtrn        = 0        # Returns by Quantopian
    profit_loss   = 0        # Profit-n-loss
    cash_max_used = c.portfolio.starting_cash - c.cash_low

    if record_max_lvrg:      # Maximum leverage
        if c.account.leverage > c.max_lvrg:
            c.max_lvrg = c.account.leverage
            record(MaxLvrg = c.max_lvrg)
        
    if record_q_return:      # Quantopian returns to compare to maxspent returns curve
        start  = context.portfolio.starting_cash  # starting_cash is king
        q_rtrn = 100 * (context.portfolio.portfolio_value - start) / start
        record(QRet = q_rtrn)
        
    if record_maxspent:      # Returns based on amount actually spent
        if cash_max_used != 0:     # Avoid zero-divide
            maxspent_rtrn = 100 * context.portfolio.pnl / cash_max_used
            record(MaxSpntRet = maxspent_rtrn)

    if record_pnl:           # "Profit and Loss" in dollars
        profit_loss = context.portfolio.pnl
        record(PnL = profit_loss)

    if log_method == 'cash_low' and new_cash_low \
      or log_method == 'daily' and c.date_prv != date:
        mxlv  = 'MaxLvrg '    + '%.1f' % c.max_lvrg    if record_max_lvrg else ''
        qret  = 'QRet '       + '%.2f' % q_rtrn        if record_q_return else ''
        mxspt = 'MaxSpntRet ' + '%.2f' % maxspent_rtrn if record_maxspent else ''
        pnl   = 'PnL '        + '%.0f' % profit_loss   if record_pnl      else ''
        csh   = 'CashLow '    + '%.0f' % c.cash_low    if record_cash_low else ''
        log.info('{} {} {} {} {}'.format(mxlv, qret, mxspt, pnl, csh))

    c.date_prv = date
    
    '''
    w_shorting = 0  
    # Think of shorting like an expense to be able to include it? 
    #   Valid? You could help solve the puzzle of shorting wrt maxspent_rtrn.
    if context.portfolio.positions_value < 0:
        w_shorting = c.cash_low + context.portfolio.positions_value
        if w_shorting < c.cash_low:
            c.cash_low = w_shorting
    '''

import pandas as pd
import datetime

#
# Configuration
#

POSITION_COUNT     = 20
MARKETCAP_LIMIT    = 5e1

SYMBOLS_LIMIT      = 500
OBSOLETE_LOOKAHEAD = 60

HISTORY_PERIODS    = 200
HISTORY_MODE       = '1d'
SYMBOL_MARKET      = sid(8554)

#
# Setup
# - Schedule allocation and execution
#
def initialize(context):
    context.alloc = pd.Series()
    context.score = pd.Series()
    context.last_month = -1
    
    schedule_function(
        allocate,
        date_rule=date_rules.month_start(),
        time_rule=time_rules.market_close(hours=2)
    )

    schedule_function(
        execute_sell,
        date_rule=date_rules.month_start(),
        time_rule=time_rules.market_close(hours=2)
    )

    schedule_function(
        execute_buy,
        date_rule=date_rules.month_start(),
        time_rule=time_rules.market_close(hours=1)
    )

    schedule_function(
        info,
        date_rule=date_rules.month_start(),
        time_rule=time_rules.market_close()
    )

#
# Symbol selection
# - Only query once a month (for backtest performance)
# - Query fundamentals database for largest companies above market cap limit
# - Ensure data available for all active positions
#
def before_trading_start(context):
    # only query database at the beginning of the month
    month = get_datetime().month
    if context.last_month == month:
        return
    context.last_month = month
    
    # selected top K largest companies on NYSE
    fundamental_df = get_fundamentals(
        query(
            fundamentals.valuation_ratios.ev_to_ebitda,
            fundamentals.valuation_ratios.sales_yield,
            fundamentals.operation_ratios.roic
        )
        .filter(fundamentals.valuation.market_cap >= MARKETCAP_LIMIT)
        .filter(fundamentals.share_class_reference.is_primary_share == True)
        .filter(fundamentals.share_class_reference.is_depositary_receipt == False)
        .filter(fundamentals.company_reference.primary_exchange_id.in_(["NYSE", "NYS"]))
        .order_by(fundamentals.valuation.market_cap.desc())
        .limit(SYMBOLS_LIMIT)
    )
    
    dft = fundamental_df.T
    
    #####
    # calculate comosite value score from 3 metrics
    #####
    score = pd.Series(0, index=dft.index)
    
    # EV/EBITDA, in-order (lower is better), nan goes last
    score += dft['ev_to_ebitda'].rank(ascending=True, na_option='bottom')
    
    # sales yield, inverse (higher is better), nan goes last
    score += dft['sales_yield'].rank(ascending=False, na_option='top')
    
    # return on invested capital, inverse (higher is better), nan goes last
    score += dft['roic'].rank(ascending=False, na_option='top')
    
    # composit, lower is better, ignore nan
    score = score.dropna().order()
    
    context.score = score
    
    #####
    # ensure data available for all active positions
    #####
    must_have = set(context.portfolio.positions.keys()) | \
                set([SYMBOL_MARKET]) | \
                set(context.alloc.index)
    nice_to_have = set(fundamental_df.columns.values) - must_have
    selected = (list(must_have) + \
                list(score[nice_to_have].order().index))[:SYMBOLS_LIMIT]

    #print "tracking %d symbols" % len(selected)
    
    update_universe(selected)
    
#
# Allocation 
# - Retrieve acquirers multiple for all tracked stocks
# - Filter candidates for missing data and end date
# - Equal allocation to top k candidates
#
def allocate(context, data):
    value = context.score

    #####
    # filters
    #####
    candidates = set(value.index)
    
    # ignore symbols without current price data
    ignore_data = set([s for s in candidates if s not in data])
    
    # ignore symbols becoming obsolete soon (lookahead bias)
    ignore_obsolete = set([s for s in candidates if s.end_date <= get_datetime() + datetime.timedelta(days=OBSOLETE_LOOKAHEAD)])
    
    # ignore market below 200-day sma
    h = history(HISTORY_PERIODS, HISTORY_MODE, 'price')
    ignore_trend = candidates if h[SYMBOL_MARKET][-1] < h[SYMBOL_MARKET].mean() else set()
    
    candidates = candidates - ignore_data - ignore_obsolete - ignore_trend
    
    #print "selecting top %d among %d candidates" % (POSITION_COUNT, len(candidates))
    
    # sort by composit score, lower is better
    topk = list(value[candidates].order().index[:POSITION_COUNT])
    
    context.alloc = pd.Series(1.0 / POSITION_COUNT, index=topk)
    
    #print "Allocation:\n%s" % context.alloc
    
#
# Execution
# - Sell positions dropping out of top k
# - Buy and rebalance positions in top k
#
def execute_sell(context, data):
    # sell positions not in top k
    for s in context.portfolio.positions:
        if s not in context.alloc.index:
            order_target_percent(s, 0)
    
def execute_buy(context, data):
    # buy and adjust top k
    for s in context.alloc.index:
        order_target_percent(s, context.alloc[s])
    
#
# Logging
# - Track live account leverage
#
def handle_data(context, data):
    record(leverage = context.account.leverage)
    #record(exposure = context.account.net_leverage)
    
    info(context, data)
    
    
    
    
There was a runtime error.
1 response

Hi William,

To do this, you just sell your position and then open a new one in the desired ETF.

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.