def series_filter(predicate, s): '''For each observation in series, check the predicate against the value. Qualifying observations pass through to the output series.''' base_f = s.f def f(dt): val = base_f(dt) return val if (core.is_valid_num(val) and predicate(val)) else None name='filter({},{})'.format(abbreviate(predicate),abbreviate(s)) return core.series(f, name=name)
def binop_series_function(a, b): a = convert(a) b = convert(b) af = a.f bf = b.f 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) name = '{}({},{})'.format(opname, abbreviate(a), abbreviate(b)) return core.series(f, name=name)
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)
def series_and(a, b): '''Given two series, return a series such that the output value is the value of the second input series if both input series have values at a date.''' af = a.f bf = b.f 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 name = 'and({},{})'.format(abbreviate(a), abbreviate(b)) return core.series(f, name=name)
def series_or(a, b): '''Given two series, return a series such that the output value is the value from the first input series if that value is available. Otherwise, the the value from the second input series is returned.''' af = a.f bf = b.f def f(dt): result = af(dt) if is_valid_num(result): return result return bf(dt) name = 'or({},{})'.format(abbreviate(a), abbreviate(b)) return core.series(f, name=name)
def fudge(s, days=6): '''fudge decorates a series such that the query by date will continue looking backward until it finds a value, up to the number of days specified, defaulting to six days. Six days permits weekly and daily market data to align dates with ends of months, quarters and years.''' sf = s.f def f(dt): for i in range(days + 1): val = sf(dt - i) if core.is_valid_num(val): return val return None name = ("fudge({})".format(abbreviate(s)) if days == 6 else "fudge({},{})".format(abbreviate(s), days)) return core.series(f, name=name)
def to_signals(s, dates=None): '''Transform a series into non-repeating negative-one (-1) where the input is negative non-repeating one (1) where the input is non-negative.''' dates = dates or core.current_dates() vals = list(map(s.f, dates.vec)) sigv = signalify_vector_copy(vals) fd = dates.first_date() ld = dates.last_date() outv = [None for dt in range(fd, ld + 1)] for (dt, sig) in zip(dates, sigv): if sig: outv[dt - fd] = sig return core.vector_series(outv, fd, name='sigs({})'.format(abbreviate(s)))
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))
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)
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)))
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)))