def test_first(): funcs = [[(0, None), (1, 12), (2, None)], [(0, 1), (1, 1), (2, 1)]] first = Curve.first(funcs) assert first(-1) is None assert first(0) == 1 assert first(1) == 12 assert first(2) == 1 assert first(3) is None funcs = [[(0, None), (1, 12), (2, None)], 1] first = Curve.first(funcs) assert first(-1) == 1 assert first(0) == 1 assert first(1) == 12 assert first(2) == 1 assert first(3) == 1 first = Curve.first([ Generic(lambda x: 1, domain=Interval(0, 2)), Generic(lambda x: 2, domain=Interval.gte(1)) ]) assert first(-1) is None assert first(0) == 1 assert first(1) == 1 assert first(2) == 1 assert first(2.1) == 2 assert first(3) == 2 assert first.sample_points(Interval(0, 3), min_step=1) == [(0, 1), (1, 1), (2, 1), (3, 2)]
def test_update_with_obj_autoremove(): begin_update_count = 0 def begin_update(domain): nonlocal begin_update_count begin_update_count += 1 end_update_count = 0 def end_update(domain): nonlocal end_update_count end_update_count += 1 f = Curve() # Add and remove observer using object obj = T() f.add_observer(obj, begin=begin_update, end=end_update, autoremove=True) f.begin_update(Interval(1, 3)) assert begin_update_count == 1 f.end_update(Interval(1, 3)) assert end_update_count == 1 obj = None f.begin_update(Interval(1, 3)) assert begin_update_count == 1 f.end_update(Interval(1, 3)) assert end_update_count == 1
def end_update(self, domain): if domain.is_empty or self._end_update_interval.is_superset_of(domain): return self._end_update_interval = Interval.union( [self._end_update_interval, domain]) if not self._end_update_interval.is_superset_of( self._begin_update_interval): # Keep collecting updates return # Updates complete update_interval = self._end_update_interval self._begin_update_interval = Interval.empty() self._end_update_interval = Interval.empty() self.set_needs_interval_update() for token in list(self._ordered_observer_tokens): _, callback_interval, _, callback, _, callback_with_interval = self._observer_data[ token] if callback_interval is None or update_interval.intersects( callback_interval): if callback is not None: if callback_with_interval: callback(update_interval) else: callback()
def append_list(self, points): """ `points` are assumed to be strictly ordered in ascending order w.r.t. to `x`. """ points_len = len(self._points) if points_len == 0: return self.set(points) new_points_len = len(points) if new_points_len == 0: return if points[0][0] <= self._points[-1][0]: raise Exception( 'Attempting to append points in non-ascending order') if self.domain.is_empty: domain = Interval.closed(points[0][0], points[-1][0]) else: domain = Interval(self.domain.end, points[-1][0], start_open=True, end_open=False) self.begin_update(domain) self._is_equally_spaced = self._points_equally_spaced( self._points, points) if self._force_equally_spaced and not self._is_equally_spaced: raise Exception( 'Attempting to append points at non-regular intervals: {}{} + {}{}' .format('...' if len(self._points) > 2 else '', self._points[len(self._points) - 2:], points[:2], '...' if len(points) > 2 else '')) self._points += points self._did_change_points() self.end_update(domain)
def get_domain(self): if len(self._points) == 0: return Interval.empty() return Interval(self._points[0][0], self._points[-1][0], start_open=False, end_open=False)
def span_interval(self, interval, start_open=False) -> Interval: """ Returns the time interval which fully contains the specified interval. """ interval = Interval.parse(interval, default_inf=True) if interval.is_empty: return Interval.empty() elif interval.start == interval.end: return self.span_date(interval.start, start_open=start_open) end_open = not start_open if interval.is_negative_infinite: start = -math.inf else: # Move outward if interval is closed but should be open o = not interval.start_open and start_open span = self.span_date(interval.start, start_open=o) start = span.start if interval.is_positive_infinite: end = math.inf else: # Move outward if interval is closed but should be open o = not interval.end_open and end_open span = self.span_date(interval.end, start_open=not o) end = span.end return Interval(start, end, start_open=start_open, end_open=not start_open)
def test_interval_contains_inf(): inf = Interval.infinite() assert inf.contains(math.inf) is True assert inf.contains(-math.inf) is True assert Interval.gte(0).contains(math.inf) is True assert Interval.gte(0).contains(-math.inf) is False assert Interval.lte(0).contains(math.inf) is False assert Interval.lte(0).contains(-math.inf) is True
def __init__(self, y, domain=None): super().__init__(min_step=math.inf) self._value = y if domain is not None: domain = Interval.parse(domain) else: domain = Interval.infinite() self._const_interval = domain
def __init__(self, min_step=None): self.name = None self._domain = None self._observer_data = {} self._ordered_observer_tokens = [] self._begin_update_interval = Interval.empty() self._end_update_interval = Interval.empty() self.min_step = min_step
def update_extension(self): if self.start: x = self.curve.domain.start if x is not None and self.curve.domain.start_open and self.curve.domain.contains(x + self.min_step): x += self.min_step y = None d_y = None if self.regression_degree is not None: x1 = x for i in range(self.regression_degree): x1 = self.curve.x_next(x1, min_step=self.min_step) domain = Interval(x, x1) tangent = self.curve.regression(domain, min_step=self.min_step) if tangent is not None: y = tangent.y(x) d_y = tangent.slope elif self.regression_period is not None: domain = Interval(x, x + self.regression_period) tangent = self.curve.regression(domain, min_step=self.min_step) if tangent is not None: y = tangent.y(x) d_y = tangent.slope else: y = self.curve.y(x) d_y = self.curve.d_y(x, forward=True) self.start_valid = y is not None and d_y is not None if self.start_valid: self.update_extension_func(self.start_func, x, y, d_y) if self.end: x = self.curve.domain.end if x is not None and self.curve.domain.end_open and self.curve.domain.contains(x - self.min_step): x -= self.min_step y = None d_y = None if self.regression_degree is not None: x0 = x for i in range(self.regression_degree): x0 = self.curve.x_previous(x0, min_step=self.min_step) domain = Interval(x0, x) tangent = self.curve.regression(domain, min_step=self.min_step) if tangent is not None: y = tangent.y(x) d_y = tangent.slope elif self.regression_period is not None: domain = Interval(x - self.regression_period, x) tangent = self.curve.regression(domain, min_step=self.min_step) if tangent is not None: y = tangent.y(x) d_y = tangent.slope else: y = self.curve.y(x) d_y = self.curve.d_y(x, forward=False) self.end_valid = y is not None and d_y is not None if self.end_valid: self.update_extension_func(self.end_func, x, y, d_y)
def test_round(): assert Interval(1.2, 3.4).round() == (1, 3) assert Interval(1.2, 3.4).round(method=math.floor) == (1, 3) assert Interval(1.2, 3.4).round(method=math.ceil) == (2, 4) assert Interval.open_closed(1.2, 3.4).round() == Interval.open_closed(1, 3) assert Interval.closed_open(1.2, 3.4).round() == Interval.closed_open(1, 3) assert Interval.empty().round() == Interval.empty()
def _did_update_extremas(self): self.possible_extrema = None self.possible_extrema_phase = None if len(self.extremas) == 0: self.extrema_interval = Interval.empty() else: self.extrema_interval = Interval(self.extrema_xs[0], self.extrema_xs[-1], start_open=False, end_open=False)
def get_range(self, domain=None, **kwargs): points = self.sample_points(domain=domain, **kwargs) low = None high = None for p in points: if low is None or p[1] < low: low = p[1] if high is None or p[1] > high: high = p[1] if low is None or high is None: return Interval.empty() return Interval(low, high)
def test_duplicate_multiple_nested_function_update(): points = Points([]) f = (points * 0.5 + points * 0.5) * 2 points.append((0, 1)) assert f.is_updating is False assert f.domain == Interval.point(0) assert f.y(0) == 2 points.append((1, 1)) assert f.is_updating is False assert f.domain == Interval(0, 1) assert f.y(1) == 2
def test_sample_infinite(): f = Constant(1) with pytest.raises(Exception): f.sample_points() with pytest.raises(Exception): f.sample_points(domain=Interval.infinite()) with pytest.raises(Exception): f.sample_points(domain=Interval.gte(0)) with pytest.raises(Exception): f.sample_points(domain=Interval.lte(0))
def test_span_interval(): assert Duration('1h').span_interval(Interval.open(3600, 2 * 3600), start_open=False) == span( 3600, 2 * 3600, start_open=False) assert Duration('1h').span_interval(Interval.open(3600, 2 * 3600), start_open=True) == span( 3600, 2 * 3600, start_open=True) assert Duration('1h').span_interval(Interval.closed(3600, 2 * 3600), start_open=False) == span( 3600, 3 * 3600, start_open=False) assert Duration('1h').span_interval(Interval.closed(3600, 2 * 3600), start_open=True) == span( 0, 2 * 3600, start_open=True)
def reset(self, domain=None): points_len = len(self._points) if points_len == 0: return if domain is None: return self.set([]) domain = Interval.parse(domain) if not self.domain.intersects(domain): return if domain.is_superset_of(self.domain): self.set([]) return remove_start_i, remove_end_i = self._domain_indexes(domain) if remove_start_i == remove_end_i: # Nothing to remove return head = self._points[:remove_start_i] tail = self._points[remove_end_i:] if len(head) != 0 and domain.contains(head[-1][0]): del head[-1] if len(tail) != 0 and domain.contains(tail[0][0]): del tail[0] head_len = len(head) tail_len = len(tail) if head_len + tail_len == 0: self.set([]) return points = head + tail update_start = self._points[0][0] update_start_open = False if head_len != 0: update_start = head[-1][0] update_start_open = True update_end = self._points[-1][0] update_end_open = False if tail_len != 0: update_end = tail[0][0] update_end_open = True update_domain = Interval(update_start, update_end, start_open=update_start_open, end_open=update_end_open) self.set(points, update_domain=update_domain)
def test_duplicate_function_update(): points = Points([]) f = points + points points.append((0, 1)) assert points.is_updating is False assert f.is_updating is False assert f.domain == Interval.point(0) assert f.y(0) == 2 points.append((1, 1)) assert points.is_updating is False assert f.is_updating is False assert f.domain == Interval(0, 1) assert f.y(1) == 2
def test_max(): f = Curve.max([Points([(0, -1), (1, 0), (2, 1)]), 0]) assert f.domain == Interval.closed(0, 2) assert f.y(-1) is None assert f.y(0) == 0 assert f.y(1) == 0 assert f.y(2) == 1 assert f.y(3) is None f = Curve.max([0, Points([(0, -1), (1, 0), (2, 1)])]) assert f.domain == Interval.closed(0, 2) assert f.y(-1) is None assert f.y(0) == 0 assert f.y(1) == 0 assert f.y(2) == 1 assert f.y(3) is None
def trim_list(cls, quotes: List['Quote'], domain: Any = None) -> List['Quote']: """ Removes empty quotes from the start and end of the list. If a domain is specified, only empty quotes outside of the domain are removed. """ if len(quotes) == 0: return [] i0: Optional[int] = None i1: Optional[int] = None r = range(len(quotes)) if domain is not None: domain = Interval.parse(domain) for i in r: q = quotes[i] if not q.is_empty or (domain is not None and domain.intersects(q.domain)): i0 = i break if i0 is None: return [] for i in reversed(r): q = quotes[i] if not q.is_empty or (domain is not None and domain.intersects(q.domain)): i1 = i break assert i1 is not None return quotes[i0:i1 + 1]
def empty_between(cls, q0: 'Quote', q1: 'Quote') -> List['Quote']: """ Returns empty quotes between `q0` and `q1`. """ resolution = q0.resolution if q1.resolution != q1.resolution: raise Exception('Quote resolutions must be equal') missing_interval = q1.start_date - q0.end_date if missing_interval == 0: return [] elif missing_interval < 0: raise Exception('Quotes must be in ascending order') ohlc = [q0.close] * 4 quotes = [] domain = Interval.intersection( [q0.domain.get_gt(), q1.domain.get_lt()]) assert domain.is_finite for span in resolution.iterate(domain, start_open=domain.start_open): q = Quote(ohlc, date=span.start, volume=0.0, resolution=resolution) quotes.append(q) if quotes[-1].end_date > q1.start_date: raise Exception( f'Unregular quote distance between filled quote {quotes[-1]} and {q1}' ) return quotes
def __init__(self, func, start=True, end=True, uniform=True, raise_on_empty=False, min_step=MIN_STEP): super().__init__(min_step=min_step) self.curve = Curve.parse(func) if self.curve.domain.is_negative_infinite: start = False if self.curve.domain.is_positive_infinite: end = False self.start = start self.end = end self.start_valid = True self.end_valid = True self.uniform = uniform self.raise_on_empty = raise_on_empty self.extension_interval = Interval.empty() self._extension_stale = True self._extension_interval_stale = True self.curve.add_observer(begin=self.begin_extension_update, end=self.end_extension_update, prioritize=True) self.start_func = None self.end_func = None if self.start: self.start_func = self.create_extension_func(start=True) self.start_func.add_observer(self, begin=self.begin_update, end=self.end_update) if self.end: self.end_func = self.create_extension_func(start=False) self.end_func.add_observer(self, begin=self.begin_update, end=self.end_update)
def replace(self, point, or_append=False): if not self.domain.contains(point[0]): if or_append: return self.append(point) else: raise Exception( 'Attempting to replace point outside of bounds') update_domain = Interval.point(point[0]) self.begin_update(update_domain) nearest_i = int(math.ceil(self.x_index(point[0]))) nearest_p = self._points[nearest_i] if point[0] != nearest_p[0]: if self._force_equally_spaced: raise Exception( 'Attempting to replace point {} between existing points. The nearest is {}.' .format(point, nearest_p)) else: self._points.insert(nearest_i, point) self._is_equally_spaced = False else: self._points[nearest_i] = point self._did_change_points() self.end_update(update_domain)
def __init__(self, min_width, is_upper_trend=True, min_points=None, min_point_distance=0, search_length_rel=None, search_length_int_rel=None, x_tol_rel=None, x_tol_abs=None, y_tol_rel=None, y_tol_abs=None): self.min_width = min_width self.is_upper_trend = is_upper_trend self.min_points = min_points self.min_point_distance = min_point_distance self.search_length_rel = search_length_rel self.search_length_int_rel = search_length_int_rel self.x_tol_rel = x_tol_rel self.x_tol_abs = x_tol_abs self.y_tol_rel = y_tol_rel self.y_tol_abs = y_tol_abs """A list of points through which the trend line is formed.""" self.trend_points = [] """An unfiltered list of points from which trend points are derived.""" self._boundary_points = [] """A list of points which touch the trend line without the underlying trend crossing the trend.""" self.tangent_points = [] self._line = None self.search_interval = Interval.empty() self.reset_points_of_interest() self.reset_prediction()
def domain(self): if self._domain is None: self._domain = Interval(self.start_date, self.end_date, start_open=DOMAIN_START_OPEN, end_open=not DOMAIN_START_OPEN) return self._domain
def update_interval(self): len_trend_points = len(self.trend_points) if self._line is None: if len_trend_points == 1: self.search_interval = Interval.point(self.trend_points[0][0]) else: self.search_interval = Interval.empty() return p0, p1 = self._line if p0[0] == p1[0]: # vertical line self.search_interval = Interval.point(p0[0]) return if self.search_length_rel <= 0: self.search_interval = Interval.closed(p0[0], p1[0]) return x_min = p0[0] x_max = p1[0] intersection = self.intersections[0] if len( self.intersections) != 0 else None if intersection is not None and intersection[0] <= x_min: intersection = None if intersection is None or len_trend_points >= self.min_points: # search ahead x_length = (x_max - x_min) * self.search_length_rel else: # not enought points to go through intersection x_length = intersection[0] - x_min # if intersection is None: # # search ahead # x_length = (x_max - x_min) * self.search_length_rel # elif len_trend_points >= self.min_points: # # search ahead of intersection # x_length = (intersection[0] - x_min) * self.search_length_int_rel # else: # # not enought points to go through intersection # x_length = intersection[0] - x_min x_max = x_min + x_length self.search_interval = Interval.closed_open(x_min, x_max)
def test_subset(): d = Interval(1, 3) assert d.is_subset_of((0, 4)) assert d.is_subset_of((1, 3)) assert not d.is_subset_of(Interval.closed_open(1, 3)) assert d.is_superset_of((2, 2)) assert d.is_superset_of((1, 3)) assert d.is_superset_of(Interval.closed_open(1, 3))
def _calendar_span(self, date, start_open=False) -> Interval: """ Returns the time interval which contains the specified `date`. """ date = arrow.get(date) t = self._normalized_date(date) msec = timedelta(microseconds=1) start = date - msec end = date + msec degree = self.degree unit = self.unit sunit = type(self).singular_unit(unit) date_span = None force_agr = unit == WEEKS if force_agr: # Special case sunit = type(self).singular_unit(DAYS) # Find matching unit interval (1d, 1h, etc) for r0, r1 in arrow.Arrow.span_range(sunit, start, end): # Normalize range end r1 += msec interval = Interval(r0.float_timestamp, r1.float_timestamp, start_open=start_open, end_open=not start_open) if interval.contains(t): if degree == 1 and not force_agr: return interval else: date_span = r0, r1 break # Expand into aggregate interval if self.unit == YEARS: start_date, end_date = self._expand_years(date_span) elif self.parent is not None and self.parent.unit == YEARS: start_date, end_date = self._expand_within_year(date_span) else: raise Exception(f'Unable to expand unit: {self.unit}') return Interval(self._normalized_date(start_date), self._normalized_date(end_date), start_open=start_open, end_open=not start_open)
def pad(self, interval, start=0, end=0, start_open=False) -> Interval: """ Appends calendar lengths to the start and end of a interval. """ interval = self.span_interval(interval, start_open=start_open) if interval.is_empty: return Interval.empty() l = interval.start if not interval.is_negative_infinite: l = self.step(l, count=start, backward=True) h = interval.end if not interval.is_positive_infinite: h = self.step(h, count=end) return Interval(l, h, start_open=start_open, end_open=not start_open)
def test_iterate_uniform(): spans = list( Duration('1h').iterate(Interval.closed(2 * 3600, 4 * 3600), start_open=False)) assert len(spans) == 3 assert spans[0] == span(2 * 3600, 3 * 3600, start_open=False) assert spans[1] == span(3 * 3600, 4 * 3600, start_open=False) assert spans[2] == span(4 * 3600, 5 * 3600, start_open=False) spans = list( Duration('1h').iterate(Interval.closed(2 * 3600, 4 * 3600), backward=True, start_open=False)) assert len(spans) == 3 assert spans[0] == span(4 * 3600, 5 * 3600, start_open=False) assert spans[1] == span(3 * 3600, 4 * 3600, start_open=False) assert spans[2] == span(2 * 3600, 3 * 3600, start_open=False)