示例#1
0
def repeated(s, repeat_last=False, dates=None):
    '''Fill missing values in a series with preceding ones, optionally
    continuing the last valid observation.'''

    dates = dates or core.current_dates()
    vals = list(map(s.f, dates))

    fd = dates.first_date()
    ld = dates.last_date()

    prev = vals[0]
    last_valid_date = fd
    for ndx, val in enumerate(vals):
        if ndx and not core.is_valid_num(val):
            vals[ndx] = prev
        else:
            last_valid_date = dates.vec[ndx]
        prev = vals[ndx]

    fd = dates.first_date()
    ld = dates.last_date()

    outv = [None for dt in range(fd, ld + 1)]

    for (dt, val) in zip(dates, vals):
        if (not repeat_last) and (dt > last_valid_date):
            break
        if core.is_valid_num(val):
            outv[dt - fd] = val

    return core.vector_series(outv, fd)
示例#2
0
 def f(dt):
     a_val = af(dt)
     if core.is_valid_num(a_val):
         b_val = bf(dt)
         if core.is_valid_num(b_val):
             return b_val
     return False
示例#3
0
def prepend(s, *, surrogate=None, dates=None):

    dates = (dates or core.current_dates())
    if surrogate is None:
        raise Exception("surrogate required")
    ob = first_ob(add(s, surrogate), dates=dates)
    if ob is None:
        raise Exception("no observation")

    base_f = s.f
    surr_f = surrogate.f
    common_date = ob[0]
    ratio = s.f(ob[0]) / surr_f(ob[0])

    dv = dates.vec
    fd = dates.first_date()
    ld = dates.last_date()
    outv = [None for dt in range(fd, ld + 1)]

    for (ndx, dt) in enumerate(dv):
        val = base_f(dt)
        if not core.is_valid_num(val) and dt < common_date:
            val = surr_f(dt) * ratio
        if core.is_valid_num(val):
            outv[dt - fd] = val

    return core.vector_series(outv, fd, name="prepend(...)")
示例#4
0
def mo_days(s, days, dates=None):
    '''Similar to :code:`mo(s,N)`, :code:`mo_days(s,days)` calculates 
    the new/old ratios along the specified dates for each new, but 
    each old-date is based upon calendar days rather than periods.
    If the calendar days back does not line up with a market day, the
    value of most recent available earlier observation is selected.'''

    sf = s.f
    sf_old = (fudge(s)).f

    if days < 1:
        raise Exception('expected positive days')

    dates = dates or core.current_dates()
    dv = dates.vec
    fd = dv[0]
    outv = [None for dt in range(dv[0], dv[-1] + 1)]

    for dt in dates:
        r = ratio(sf_old(dt - days), sf(dt))
        if core.is_valid_num(r):
            outv[dt - fd] = r

    name = "mo_days({},{})".format(abbreviate(s), days)
    return core.vector_series(outv, fd, name=name)
示例#5
0
def ma(s, N, dates=None):
    '''Create running N-period arithmetic average of input series.'''

    dates = dates or core.current_dates()
    vals = series_dates_values(s, dates)
    fd = dates.first_date()
    ld = dates.last_date()
    outv = [None for dt in range(fd, ld + 1)]

    total = 0
    consecutive = 0

    for ndx, dt in enumerate(dates):
        val = vals[ndx]
        if core.is_valid_num(val):
            total += val
            consecutive += 1
        else:
            consecutive = 0
            total = 0
        if consecutive > N:
            total -= vals[ndx - N]
        if consecutive >= N:
            outv[dt - fd] = (total / N)

    return core.vector_series(outv, fd, name="SMA({})".format(N))
示例#6
0
def allocation_equity_line(allocations, *, initial_value=100):
    '''Generate an equity line based upon weighted portfolios on multiple dates, either
       changing the weight or rebalancing.  The first value of the equity line begins
       at the first allocation date.'''

    portfolios = allocations_to_portfolios(allocations)

    holdings_by_date = {p.date: p.holdings for p in portfolios}

    fd = portfolios[0].date

    holdings = [Holding(CASH, 1)]

    obs = []

    for dt in range(fd, core.today(1)):

        valuation = value_of_holdings(dt, holdings)

        new_holdings = holdings_by_date.get(dt)

        if new_holdings:
            if not valuation:
                raise Exception(
                    "missing observation at allocate date {}".format(
                        jdate_to_text(dt)))
            holdings = new_holdings

        if core.is_valid_num(valuation) and valuation > 0:
            obs.append((dt, initial_value * valuation))

    return obs_to_series(obs)
示例#7
0
def allocations_to_portfolios(allocations):

    allocations = sorted([normalize_allocation(a) for a in allocations],
                         key=lambda a: a.date)

    holdings = [Holding(CASH, 1)]

    result = []

    for a in allocations:

        date = a.date

        valuation = value_of_holdings(date, holdings)

        holdings = []

        for p in a.portions:
            f = p.series.f
            price = f(date)
            if not core.is_valid_num(price):
                raise Exception("missing price at {}".format(
                    jdate_to_text(date)))
            dollars_for_buy = p.amount * valuation
            shares_to_buy = dollars_for_buy / price
            holdings.append(Holding(p.series, shares_to_buy))

        result.append(Portfolio(date, holdings))

    return result
示例#8
0
def value_of_holdings(date, holdings):
    total = 0
    for h in holdings:
        price = h.series.f(date)
        if not core.is_valid_num(price):
            return None
        total = total + (h.shares * price)
    return total
示例#9
0
def first_ob(s, dates=None):
    '''Return first available (dt,value) tuple.'''
    dates = dates or core.current_dates()
    f = s.f
    for dt in dates:
        val = f(dt)
        if core.is_valid_num(val):
            return (dt, val)
    return None
示例#10
0
def dump(*seriess, first=None, last=None, dts=None):
    '''dump takes multiple series, with optional dates constraints and
    lists them in date order.  It is only useful interactively, but in that
    case, very useful.'''
    def format(n):
        return '{:12.4f}'.format(n)

    sfs = [s.f for s in seriess]
    dts = dts or core.current_dates()
    fd = (first or dts.first_date())
    ld = (last or dts.last_date())
    for dt in range(fd, ld + 1):
        nums = [f(dt) for f in sfs]
        if sum([(1 if core.is_valid_num(n) else 0) for n in nums]):
            rounded = map(
                lambda n: (format(n)
                           if core.is_valid_num(n) else '            '), nums)
            print("{} {}".format(core.jdate_to_text(dt), ' '.join(rounded)))
示例#11
0
def cross(*,
          slower=None,
          faster=None,
          upfactor=1.0,
          downfactor=1.0,
          dates=None):
    '''cross generates a signal series, i.e. series of [1,-1,None], set to 1
    where the faster series moves above the slower series and -1 where it moves
    below. Changing the upfactor from its default 1.0 changes the border for 
    crossing.  For instance, upfactor=1.02 means that the faster series must 
    exceed the lower series by 2% before generating the buy value of 1.  Likewise,
    setting downfactor to .98 would require passing 2% below the faster series
    to generate a sell signal of -1.  As usual, dates can be supplied, but default
    to the current_dates() value.'''

    if (not slower) or (not faster):
        raise Exception('slower,faster are required keyword arguments')

    dates = dates or core.current_dates()
    sv = series_dates_values(slower, dates)
    fv = series_dates_values(faster, dates)

    obs = []
    prev_sig = None

    for ndx, dt in enumerate(dates):
        s_val = sv[ndx]
        f_val = fv[ndx]

        if not (core.is_valid_num(s_val) and core.is_valid_num(f_val)):
            continue

        if f_val > s_val * upfactor:
            sig = 1
        elif f_val < s_val * downfactor:
            sig = -1
        else:
            sig = None

        if sig and (sig != prev_sig):
            obs.append((dt, sig))
            prev_sig = sig

    return obs_to_series(obs)
示例#12
0
def last_ob(s, dates=None):
    '''Return last available (dt,value) tuple.'''
    dates = dates or core.current_dates()
    dv = dates.vec
    f = s.f
    for ndx in range(len(dv) - 1, -1, -1):
        dt = dv[ndx]
        val = f(dt)
        if core.is_valid_num(val):
            return (dt, val)
    return None
示例#13
0
def fractional(s, fraction, dates=None):
    '''A fractional smoothes a series such that the current value is weighted by some fraction 
    in (0..1) added to the previous value weighted by (1 - fraction).'''

    dates = dates or core.current_dates()
    fd, ld = s.first_date(), s.last_date()
    outv = [None for dt in range(fd, ld + 1)]
    remainder = 1 - fraction
    prev = None
    f = s.f
    for dt in dates:
        val = f(dt)
        newVal = ((fraction * val + remainder * prev) if
                  (core.is_valid_num(prev)
                   and core.is_valid_num(val)) else val)
        outv[dt - fd] = newVal
        prev = newVal
    return core.vector_series(outv,
                              fd,
                              name="fractional({},{})".format(
                                  abbreviate(s), fraction))
示例#14
0
def window_series (s, N, proc, dates=None, missing_data_permitted=False):

    dates = (dates or core.current_dates())
    dv = dates.vec
    sf = s.f
    fd = dates.first_date()
    ld = dates.last_date()
    vv = [(n if core.is_valid_num(n) else None) for n in (sf(dt) for dt in dv)]
    outv = [ None for dt in range(fd,ld+1) ]

    count = 0
    for (ndx, dt) in enumerate(dv):
        val = sf(dt)
        count = (count+1 if (missing_data_permitted or core.is_valid_num(val)) else 0)
        if count >= N:
            stop = ndx+1
            start = stop-N
            result = proc(vv[start:stop])
            if core.is_valid_num(result):
                outv[dt - fd] = result

    return core.vector_series(outv, fd, name="window_series({})".format(N))
示例#15
0
def capture(s, benchmark, period, dates, predicate):

    if not benchmark:
        raise Exception("benchmark series must be specified")
    if not period:
        raise Exception("expected period > 0")

    dates = (dates or core.current_dates())

    sf = mo(s, period).f
    bf = mo(benchmark, period).f

    B_total = 0
    S_total = 0

    for dt in dates.vec:
        B_val = bf(dt)
        if core.is_valid_num(B_val) and predicate(B_val):
            S_val = sf(dt)
            if core.is_valid_num(S_val):
                B_total = B_total + B_val
                S_total = S_total + S_val

    return (S_total / B_total) if B_total else None
示例#16
0
def series_map(proc, *seriesz, missing_data_permitted=False, dts=None):
    '''Analogous to Python's standard map operation, map the arity-compatible 
    function :code:`proc` over one or more series returning a 
    single value on each date.'''
    def missing_nums(nums):
        return len(nums) != sum((1 for n in nums if core.is_valid_num(n)))

    converted = [convert(s) for s in seriesz]
    dts = dts or dates.current_dates()
    sfs = [s.f for s in converted]
    obs = []

    for dt in dts:
        nums = [f(dt) for f in sfs]
        if (not missing_data_permitted) and missing_nums(nums):
            continue
        val = proc(*nums)
        if core.is_valid_num(val):
            obs.append((dt, val))

    return obs_to_series(obs)
示例#17
0
def warp(s, N, dates=None):
    '''warp creates an N-period shift of values within dateset.  Negative periods shift data
    backward in time.  Often a signal series if warped with N=1 to measure 
    performance of a trading scheme if trading happens the next market day.'''

    dates = (dates or core.current_dates())
    dv = dates.vec
    sf = s.f
    fd = dates.first_date()
    ld = dates.last_date()
    outv = [None for dt in range(fd, ld + 1)]

    for (ndx, dt) in enumerate(dv):
        val = sf(dt)
        if core.is_valid_num(val):
            new_ndx = ndx + N
            if 0 <= new_ndx < len(dv):
                new_dt = dv[new_ndx]
                outv[new_dt - fd] = val

    return core.vector_series(outv, fd, name="warp({})".format(N))
示例#18
0
def unrepeated(s, dates=None):
    '''Copy the input series, suppressing repeated values.'''

    dates = dates or core.current_dates()
    vals = list(map(s.f, dates))

    prev = vals[0]
    for ndx, val in enumerate(vals):
        if ndx and val == prev:
            vals[ndx] = None
        prev = val

    fd = dates.first_date()
    ld = dates.last_date()

    outv = [None for dt in range(fd, ld + 1)]

    for (dt, val) in zip(dates, vals):
        if core.is_valid_num(val):
            outv[dt - fd] = val

    return core.vector_series(outv, fd)
示例#19
0
def mo(s, N, dates=None):
    '''Return the N-period ratio series of new/old values.'''

    if N < 1:
        raise Exception('requires period > 0')

    dates = dates or core.current_dates()
    sf = s.f
    dv = dates.vec
    shifted_dv = dv[min(N, len(dv)):]
    outv = [None for dt in range(dv[0], dv[-1] + 1)]
    fd = dv[0]

    for (early, late) in zip(dv, shifted_dv):
        e_val = sf(early)
        l_val = sf(late)
        r = ratio(e_val, l_val)
        if core.is_valid_num(r):
            outv[late - fd] = r

    name = "mo({},{})".format(abbreviate(s), N)
    return core.vector_series(outv, fd, name=name)
示例#20
0
def min_max_obs(s, dates=None):
    '''Return a 2-tuple of (dt,value) observations representing
    the points of minimum and maximum values respectively.'''

    dates = dates or core.current_dates()
    min_ob = None
    max_ob = None
    f = s.f

    for dt in dates:
        val = f(dt)
        if core.is_valid_num(val):
            if not min_ob:
                min_ob = max_ob = (dt, val)
            if min_ob[1] > val:
                min_ob = (dt, val)
            if max_ob[1] < val:
                max_ob = (dt, val)

    if not min_ob:
        raise Exception("no observations")

    return min_ob, max_ob
示例#21
0
def reversals(s, down_factor=1.0, up_factor=1.0, dates=None):
    '''When a series ascends above up-factor multiplied by a preceding 
    local minimum, a buy (1) signal is produced.  Upon 
    descending below the product of a local maximum and down-factor, 
    a sell (-1) signal is produced.
    '''
    dates = dates or core.current_dates()
    fd, ld = core.first_date(dates), core.last_date(dates)
    outv = [None for dt in range(fd, ld + 1)]
    min_ob = max_ob = first_ob(s, dates)
    sf = s.f
    state = None

    for dt in dates:

        val = sf(dt)
        if not core.is_valid_num(val):
            continue

        if val > max_ob[1]:
            max_ob = (dt, val)
        if val < min_ob[1]:
            min_ob = (dt, val)

        if (1 != state) and (val > min_ob[1] * up_factor):
            max_ob = min_ob = (dt, val)
            outv[dt - fd] = 1
            state = 1

        elif (-1 != state) and (val < max_ob[1] * down_factor):
            max_ob = min_ob = (dt, val)
            outv[dt - fd] = -1
            state = -1

    return core.vector_series(outv,
                              fd,
                              name="reversals({})".format(abbreviate(s)))
示例#22
0
def calibrate(s, init=100, date=None):

    '''To calibrate a series is to make it value-compatible with another.
    For example, a spider graph picks a point at which multiple time
    series are the same value.  The default date is the beginning of the
    dateset and the default value of the series there is 100, making any
    value in the series equivalent to the percentage of the beginning.'''

    date = core.to_date(date) or core.first_date()
    f = s.f
    val = f(date)

    if not core.is_valid_num(val):
        raise Exception("no observation at {}".format(date))

    ratio = init / val

    def fetcher(dt):
        val = f(dt)
        if core.is_valid_num(val):
            val = val * ratio
        return val

    return core.series(fetcher, "calibrate({})".format(abbreviate(s)))
示例#23
0
 def f(dt):
     a_val = af(dt)
     b_val = bf(dt)
     if core.is_valid_num(a_val) and core.is_valid_num(b_val):
         return op(a_val, b_val)
示例#24
0
 def f(dt):
     for i in range(days + 1):
         val = sf(dt - i)
         if core.is_valid_num(val):
             return val
     return None
示例#25
0
def ratio(early, late):
    return (late / early - 1.0) if (core.is_valid_num(late)
                                    and core.is_valid_num(early)) else None
示例#26
0
def convert(s):
    if core.is_valid_num(s):
        return constant(s)
    else:
        return s
示例#27
0
 def missing_nums(nums):
     return len(nums) != sum((1 for n in nums if core.is_valid_num(n)))
示例#28
0
 def valid(dt):
     return core.is_valid_num(s.f(dt))
示例#29
0
def equity_line(s,
                signals,
                initial_value=100,
                alternate_investment=None,
                dates=None):
    '''The point of most signal generation is to go in and out of invested position
    on some series, optionally buying an alternate investment such as short-duration debt.
    The equity_line series represents the value of the investment after treating signals
    as entrance and exit points during the dateset indicated.
    '''

    dates = dates or core.current_dates()
    dv = dates.vec
    fd = dates.first_date()
    ld = dates.last_date()
    alternate_investment = alternate_investment or constant(1)

    alt_vals = series_dates_values(alternate_investment, dates)
    inv_vals = series_dates_values(s, dates)
    sig_vals = series_dates_values(signals, dates)

    outv = [None for dt in range(fd, ld + 1)]

    product = 1.0
    buy = True
    prev_inv = prev_alt = None
    first_sig_ob = first_ob(signals, dates=dates)
    if not first_sig_ob:
        raise Exception("signal series is empty")

    for (dt, alt, inv, sig) in zip(dv, alt_vals, inv_vals, sig_vals):
        if dt < first_sig_ob[0]:
            continue
        if sig or (core.is_valid_num(alt) and core.is_valid_num(inv)):
            change = None
            if not core.is_valid_num(inv):
                raise Exception("missing investment observation at {}".format(
                    core.jdate_to_text(dt)))
            if not core.is_valid_num(alt):
                raise Exception(
                    "missing alternate_investment observation at {}".format(
                        core.jdate_to_text(dt)))
            if buy:
                if prev_inv:
                    change = inv / prev_inv
                else:
                    change = 1.0
            else:
                if prev_alt:
                    change = alt / prev_alt
                else:
                    change = 1.0

            # prior to having signal, nothing was invested
            if dt <= first_sig_ob[0]:
                change = 1.0

            new_buy = (sig > 0) if sig else buy
            new_product = product * change

            outv[dt - fd] = new_product * initial_value

            product = new_product
            buy = new_buy
            prev_inv = inv
            prev_alt = alt

    return core.vector_series(outv, fd)
示例#30
0
 def f(dt):
     val = base_f(dt)
     return val if (core.is_valid_num(val) and predicate(val)) else None