Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Scaling

Aside from normalization which scales values often 0 to 1, it can be useful to set some values proportionally to some other range of values. Scaling. It can move your code closer to being more gradually adaptive.

What are some examples where scaling might come in handy?
- Adjust number of stocks to trade proportionally based on available cash compared to starting_cash. With less cash, fewer and more cream-of-the-crop stocks.
- Increase price threshold of candidate stocks in before_trading_start() as portfolio increases.
- Set stops (to close any position) proportional to each stock's volatility.
- If portfolio is under starting_cash, progressively tighten amount of loss allowed per stock.

Below is an example of scale() using that last example to help progressively reduce the chances of an algo moving toward the 90% mark in the contest (where it would be disqualified).

pf is portfolio and prc is price here ...  
For pf at .93 to 1, scale stop 1 (no wiggle room) to .97 (more loose),  
  that is, at pf .93 and below, stop is set to prc,  
  at pf 1 and above, stop is .97,  
  and with dropping pf toward .93 set stop proportionally tighter toward price.  

Code something like this:

def set_stop(c, data, sec):    # c is context, sec is security, stock  
    ''' Set stop proportionally tighter with portfolio trending toward 93% of start.  
              scale(pf_ratio, .93, 1.0, 1.0, .97)  Note low to high and inverse high to low  
                              --------  --------  
          Portfolio range _______|         |  
          Stop range ______________________|  
    '''  
    prc = data.current(sec, 'price')  
    amt = c.portfolio.positions[sec].amount  
    cb  = c.portfolio.positions[sec].cost_basis  
    pf_ratio = c.portfolio.portfolio_value / c.portfolio.starting_cash  
    new_stop = prc  
    if   amt > 0:    # long  
        new_stop = max(cb, prc) *        scale(pf_ratio,   .93, 1.0,   1.0, .97)  
    elif amt < 0:    # short, stop to buy back higher than prc  
        new_stop = min(cb, prc) * (2.0 - scale(pf_ratio,   .93, 1.0,   1.0, .97))  
    return new_stop

def scale(wild, a_lo, a_hi, b_lo, b_hi):  
    ''' Based on wild value relative to a_lo_hi range,  
          return its analog within b_hi_lo, with min b_lo and max b_hi  
    '''  
    return min(b_hi, max(b_lo, (b_hi * (wild - a_lo)) / (a_hi - a_lo)))  

And use this to see straight.

1 response

Updated Oct 17, 2017. Handles negatives ...

def scaling(wild, a, b, c, d):  
    ''' Handles negatives.  
      Based on where wild value fits in range a to b,  
      return its analog within c to d.  
      If outside a to b, fit to nearest c to d edge. '''  
    # shift any upward to positive values if necessary  
    ab_shift = 0  
    if a < 0 or b < 0:  
        ab_shift = abs(min(a, b, wild))  
        a += ab_shift  
        b += ab_shift  
        wild += ab_shift  
    cd_shift = 0  
    if c < 0 or d < 0:  
        cd_shift = abs(min(c, d))  
        c += cd_shift  
        d += cd_shift  
    # if off the edges  
    if   wild < a < b: return c - cd_shift  
    elif wild > a > b: return c - cd_shift  
    elif wild > b > a: return d - cd_shift  
    elif wild < b < a: return d - cd_shift  
    e  = c + (((float(wild) - a) / (b - a)) * (d - c)) # proportion  
    e -= cd_shift  # shift back downward if shifted  
    return float(e)