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 _update_extension_interval(self): if not self._extension_interval_stale: return self._extension_interval_stale = False if self._extension_stale: self._update_extension_if_needed() if self.start and self.start_valid and self.end and self.end_valid: self.extension_interval = Interval.union([self.start_func.domain, self.end_func.domain]) elif self.start and self.start_valid: self.extension_interval = Interval.intersection([self.start_func.domain, self.curve.domain.get_lt()]) elif self.end and self.end_valid: self.extension_interval = Interval.intersection([self.end_func.domain, self.curve.domain.get_gt()]) else: self.extension_interval = Interval.empty() self.update_extension_interval() if (self.start and not self.extension_interval.is_negative_infinite) or (self.end and not self.extension_interval.is_positive_infinite): if self.raise_on_empty: raise Exception('Unable to extend func')
def missing_domains(cls, quotes, domain=None): # TODO: optimise by recursively dividing quotes # in half and checking if the quotes are # contiguous. Then collect domains between # contiguous quotes. quotes_len = len(quotes) if quotes_len == 0: if domain is None: return [] else: return [domain] if domain is None: domain = cls.list_domain(quotes) else: domain = Interval.parse(domain) if domain.is_empty: return [] missing_list = [] head = Interval.intersection([domain, quotes[0].domain.get_lt()]) if not head.is_empty: missing_list.append(head) for i in range(1, quotes_len): q0 = quotes[i - 1] q1 = quotes[i] if q0.end_date != q1.start_date: if q0.end_date > q1.start_date: continue missing = Interval(q0.domain.end, q1.domain.start, start_open=not q0.domain.end_open, end_open=not q1.domain.start_open) missing_list.append(missing) tail = Interval.intersection([domain, quotes[-1].domain.get_gt()]) if not tail.is_empty: missing_list.append(tail) return missing_list
def sample_points(self, domain=None, min_step=MIN_STEP, step=None): min_step = self.resolve_min_step(min_step) if domain is None: domain = self.domain else: domain = Interval.intersection([self.domain, domain]) if domain.is_empty: return [] elif not domain.is_finite: raise Exception( "Cannot sample points on an infinite domain {}. Specify a finite domain." .format(domain)) x_start, x_end = domain x_end_bin = round(x_end / min_step) if min_step is not None else x_end if domain.start_open: points = [] else: points = [(x_start, self.y(x_start))] if step is not None: x = x_start + step while x <= x_end: y = self.y(x) points.append((x, y)) x += step elif min_step is not None and min_step > 0: x = self.x_next(x_start, min_step=min_step, limit=x_end) while x is not None and x <= x_end: y = self.y(x) points.append((x, y)) x_bin = round(x / min_step) if min_step is not None else x if x_bin == x_end_bin: break x1 = self.x_next(x, min_step=min_step, limit=x_end) if x1 is not None: x1_bin = round(x1 / min_step) if min_step is not None else x1 if x1_bin <= x_bin: raise Exception( 'Next x value {} should be greater than the previous x value {} by at least the minimum step of {}' .format(x1, x, min_step)) x = x1 if not domain.end_open and points[-1][0] != x_end: points.append((x_end, self.y(x_end))) else: raise Exception("Bad functions sample parameters.") return points
def sample_points(self, domain=None, min_step=MIN_STEP, step=None): min_step = self.resolve_min_step(min_step) if domain is None: domain = self.domain else: domain = Interval.intersection([self.domain, domain]) if domain.is_empty: return [] elif domain.is_infinite: raise Exception( "Cannot sample points on an infinite domain. Specify a finite domain." ) self.scan(domain.start) self.scan(domain.end) return super().sample_points(domain=domain, min_step=min_step, step=step)
def _domain_indexes(self, domain): """ Turn a domain into start and end indexes (inclusive and exclusive respectively). """ points_len = len(self._points) if domain.is_superset_of(self.domain): return 0, points_len domain = Interval.intersection([domain, self.domain]) if domain.is_empty: return 0, 0 if domain.is_negative_infinite: start_i = 0 else: i = max(0, int(math.floor(self.x_index(domain.start)))) while i < points_len: x = self._points[i][0] if x >= domain.end: # i -= 1 break if domain.contains(x): break i += 1 start_i = i if domain.is_positive_infinite: end_i = points_len else: i = min(points_len, int(math.ceil(self.x_index(domain.end)))) while i >= 0: x = self._points[i][0] if x <= domain.start: # i += 1 break if domain.contains(x): break i -= 1 end_i = i + 1 return start_i, end_i
def sample_points(self, domain=None, min_step=None, step=None): domain = Interval.parse(domain, default_inf=True) if self.domain.is_empty: return [] if self.interval is not None and ( (min_step is not None and min_step > self.interval) or (step is not None and step != self.interval)): # Irregular sampling return super().sample_points(domain=domain, min_step=min_step, step=step) if domain is None or domain.is_superset_of(self.domain): # Sample all return list(self._points) # Sample some domain = Interval.intersection([self.domain, domain]) if domain.is_empty: return [] i0, i1 = self._domain_indexes(domain) return self._points[i0:i1]
def get_domain(self): domains = list(map(lambda f: f.domain, self.funcs)) if self.is_union: return Interval.union(domains) else: return Interval.intersection(domains)
def update_points_of_interest(self, underlying_points): if not self.is_ready: return False i_end = len(underlying_points) if i_end == 0: return False def process_points_on_trend(points_on_trend, insert_index): if len(points_on_trend) == 0: return closest_point = None closest_dist = 0 for p in points_on_trend: dist = abs(self.y(p[0]) - p[1]) if closest_point is None or dist < closest_dist: closest_dist = dist closest_point = p self.tangent_points.insert(insert_index, closest_point) # find intersections, tangent points and extremas phase = 0 phase_point = None previous_phase = phase intersection_found = False intersection_insert_index = len(self.intersections) tangent_insert_index = len(self.tangent_points) points_on_trend = [] new_extremas = [] extrema = None extrema_dist = 0 # search up to the last intersection or tangent point update_interval = self.search_interval if len(self.intersections) != 0: update_interval = Interval.intersection( [update_interval, Interval.gte(self.intersections[-1][0])]) if len(self.tangent_points) != 0: update_interval = Interval.intersection( [update_interval, Interval.gt(self.tangent_points[-1][0])]) for i in reversed(range(len(underlying_points))): p = underlying_points[i] if not update_interval.contains(p[0]): break if self.is_point_on_trend(p): points_on_trend.insert(0, p) if extrema is not None: new_extremas.insert(0, extrema) extrema = None continue elif len(points_on_trend) != 0: process_points_on_trend(points_on_trend, tangent_insert_index) points_on_trend = [] phase = p[1] - self.y(p[0]) at_intersection = previous_phase != 0 and phase * previous_phase < 0 if at_intersection: # found intersection intersection = _line_intersection(self._line, (phase_point, p)) if intersection is None or intersection[0] < self._line[0][0]: intersection = _average_point_of_2(phase_point, p) self.intersections.insert(intersection_insert_index, intersection) intersection_found = True if extrema is not None: new_extremas.insert(0, extrema) extrema = None if phase > 0: dist = p[1] else: dist = -p[1] if extrema is None or dist > extrema_dist: extrema = p extrema_dist = dist previous_phase = phase phase_point = p if len(points_on_trend) != 0: process_points_on_trend(points_on_trend, tangent_insert_index) if extrema is not None: new_extremas.insert(0, extrema) if len(new_extremas) != 0: # merge extremas with existing ones if len(self.extremas) == 0: self.extremas += new_extremas else: if self.extremas[-1][0] >= update_interval.start: # old extrema may be outdated del self.extremas[-1] self.extremas += new_extremas if intersection_found: self.update_interval() return intersection_found
def test_intersection(): # closed, closed d1 = Interval(0, 2, start_open=False, end_open=False) d2 = Interval(1, 3, start_open=False, end_open=False) assert d1.contains(0) assert d1.contains(1) assert d1.contains(2) d = Interval.intersection([d1, d2]) assert d.start == 1 assert d.end == 2 assert not d.start_open assert not d.end_open d = Interval.union([d1, d2]) assert d.start == 0 assert d.end == 3 assert not d.start_open assert not d.end_open # closed, open d1 = Interval(0, 2, start_open=False, end_open=False) d2 = Interval(1, 3, start_open=True, end_open=True) d = Interval.intersection([d1, d2]) assert d.start == 1 assert d.end == 2 assert d.start_open assert not d.end_open d = Interval.union([d1, d2]) assert d.start == 0 assert d.end == 3 assert not d.start_open assert d.end_open # open, open d1 = Interval(0, 2, start_open=True, end_open=True) d2 = Interval(1, 3, start_open=True, end_open=True) assert not d1.contains(0) assert d1.contains(1) assert not d1.contains(2) d = Interval.intersection([d1, d2]) assert d.start == 1 assert d.end == 2 assert d.start_open assert d.end_open d = Interval.union([d1, d2]) assert d.start == 0 assert d.end == 3 assert d.start_open assert d.end_open d = Interval.intersection([Interval(0, 1), Interval(2, 3)]) assert d.is_empty d = Interval.intersection([Interval(0, 1, end_open=True), Interval(1, 3, start_open=True)]) assert d.is_empty d = Interval.intersection([Interval(0, 1), Interval.empty()]) assert d.is_empty d = Interval.union([Interval.empty(), 1]) assert d.start == 1 assert d.end == 1
def test_intersection_inf(): assert Interval.intersection([Interval.gte(100), (98, 101)]) == (100, 101) assert Interval.intersection([Interval.point(100), Interval.open_closed(100, 101)]) == Interval.empty()